Project

General

Profile

2137_20221109a.patch

Constantin Asofiei, 11/09/2022 02:09 PM

Download (39.1 KB)

View differences:

new/src/com/goldencode/p2j/persist/AbstractQuery.java 2022-11-09 18:36:33 +0000
220 220
**     OM  20210917          Code maintenance.
221 221
**     OM  20220112          Added implementation of SKIP-DELETED-RECORD attribute.
222 222
**     AL2 20220412          Do proxy checks for values used as query parameters.
223
**     CA  20221109          Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded 
224
**                           as 'partial/incomplete'.
223 225
*/
224 226

  
225 227
/*
......
282 284
import java.util.logging.*;
283 285
import com.goldencode.p2j.persist.event.*;
284 286
import com.goldencode.p2j.persist.lock.*;
287
import com.goldencode.p2j.persist.orm.*;
285 288
import com.goldencode.p2j.util.*;
286 289
import com.goldencode.p2j.util.ErrorManager;
287 290

  
......
323 326
   /** Flag to indicate if this query is optimized using indexed reposition */
324 327
   protected boolean indexedReposition = false;
325 328
   
329
   /** The included properties for each DMO (FIELDS option). */
330
   protected Map<DataModelObject, Property[]> included = null;
331

  
332
   /** The excluded properties for each DMO (EXCEPT option). */
333
   protected Map<DataModelObject, Property[]> excluded = null;
334
   
326 335
   /** Flag indicating fatal error was encountered processing query substitution parameters */
327 336
   private boolean fatalError = false;
328 337
   
......
3173 3182
    */
3174 3183
   public void include(DataModelObject dmo, String ... fields)
3175 3184
   {
3176
      // UnimplementedFeature.missing("RECORD-PHRASE: FIELDS (...) option");
3177
      // TODO: implement
3185
      RecordBuffer buffer = (RecordBuffer) ((BufferReference) dmo).buffer();
3186
      if (buffer.isTemporary())
3187
      {
3188
         return;
3189
      }
3190
      
3191
      if (included == null)
3192
      {
3193
         included = new IdentityHashMap<>();
3194
      }
3195
      
3196
      collectFields(included, dmo, fields);
3178 3197
   }
3179 3198
   
3180 3199
   /**
......
3188 3207
    */
3189 3208
   public void exclude(DataModelObject dmo, String ... fields)
3190 3209
   {
3191
      // UnimplementedFeature.missing("RECORD-PHRASE: EXCLUDE (...) option");
3192
      // TODO: implement
3210
      RecordBuffer buffer = (RecordBuffer) ((BufferReference) dmo).buffer();
3211
      if (buffer.isTemporary())
3212
      {
3213
         return;
3214
      }
3215
      
3216
      if (excluded == null)
3217
      {
3218
         excluded = new IdentityHashMap<>();
3219
      }
3220
      
3221
      collectFields(excluded, dmo, fields);
3193 3222
   }
3194 3223
   
3195 3224
   /**
......
3429 3458
         return new logical(false);
3430 3459
      }
3431 3460
   }
3461
   
3462
   /**
3463
    * Populate the map of with the ORM properties specified in the given fields.
3464
    * 
3465
    * @param    target
3466
    *           The target map.
3467
    * @param    dmo
3468
    *           The DMO instance.
3469
    * @param    fields
3470
    *           The fields to resolve.
3471
    */
3472
   private void collectFields(Map<DataModelObject, Property[]> target, DataModelObject dmo, String[] fields)
3473
   {
3474
      RecordBuffer buffer = (RecordBuffer) ((BufferReference) dmo).buffer();
3475
      DmoMeta meta = buffer.getDmoInfo();
3476

  
3477
      Property[] props = target.computeIfAbsent(dmo, (d) ->
3478
      {
3479
         return new Property[meta.getExistingFields().size()];
3480
      });
3481
      
3482
      for (int i = 0; i < fields.length; i++)
3483
      {
3484
         Property p = meta.getFieldInfo(fields[i]);
3485
         
3486
         props[p.id - 1] = p;
3487
      }
3488
   }
3432 3489
}
new/src/com/goldencode/p2j/persist/FastFindCache.java 2022-11-09 18:35:42 +0000
14 14
**                  negative result (i.e., no record is found) do not have the negative result cached.
15 15
**     CA  20220104 Node.l3Cache must be a weak reference, otherwise once the direct cache invalidates a key,
16 16
**                  the reverse lookup has no chance of being cleaned up, as this is not done explicitly.
17
**     CA  20221109 Partial/Incomplete records (loaded with EXCEPT/FIELDS options) can't be cached.
17 18
*/
18 19

  
19 20
/*
......
262 263
    */
263 264
   public void put(Key k, Record value)
264 265
   {
265
      // if no key or record represents a housekeeping (e.g., dirty database) copy, don't store it
266
      if (k == null || (value != null && value.checkState(DmoState.COPY)))
266
      // if no key or record represents a housekeeping (e.g., dirty database) copy or an incomplete record 
267
      // (partial fields are populated due to EXCEPT/FIELDS options), don't store it
268
      if (k == null || 
269
          (value != null && (value.checkState(DmoState.COPY) || value.checkState(DmoState.INCOMPLETE))))
267 270
      {
268 271
         return; // throw some error?
269 272
      }
new/src/com/goldencode/p2j/persist/Persistence.java 2022-11-09 18:36:45 +0000
577 577
**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
578 578
**                           processing all the scopeables for each and every block.
579 579
**     TJD 20220504          Upgrade do Java 11 minor changes
580
**     CA  20221109          Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded 
581
**                           as 'partial/incomplete'.
580 582
*/
581 583

  
582 584
/*
......
634 636

  
635 637
package com.goldencode.p2j.persist;
636 638

  
637
import java.lang.*;
638 639
import java.sql.*;
639 640
import java.util.*;
641
import java.util.BitSet;
640 642
import java.util.Date;
641 643
import java.util.function.*;
642 644
import java.util.logging.*;
......
1756 1758
            // have to take another hit retrieving the data almost immediately after this method
1757 1759
            // returns, which is the common case for dynamic record retrieval.
1758 1760
            Class<? extends Record> dmoClass = buffer.getDMOImplementationClass();
1759
            initialResult = result = session.get(dmoClass, id);
1761
            initialResult = result = session.get(dmoClass, id, null);
1760 1762
            
1761 1763
            if (result == null)
1762 1764
            {
......
1978 1980
   throws PersistenceException,
1979 1981
          LockTimeoutException
1980 1982
   {
1983
      return load(implClass, id, lockType, timeout, updateLock, null);
1984
   }
1985
   
1986
   /**
1987
    * Load a single record from the session-level cache or, optionally, refresh it from the
1988
    * database. The record will be locked with the specified lock type upon return from this
1989
    * method. Note that the lock refers to a runtime-level lock, not a database-level lock.
1990
    *
1991
    * @param   implClass
1992
    *          The DMO implementation class of record being read.
1993
    * @param   id
1994
    *          Primary key of the record to be loaded into the buffer.  May not be {@code null}.
1995
    * @param   lockType
1996
    *          Type of lock the lock manager should register on the resulting record for the
1997
    *          current user context.
1998
    * @param   timeout
1999
    *          Number of milliseconds to wait to acquire the requested lock type. Throws
2000
    *          {@link LockTimeoutException} if this time elapses before the lock is acquired.
2001
    *          Specify 0 to wait indefinitely for the lock. Ignored if {@code lockType} is
2002
    *          {@code NONE}.
2003
    * @param   updateLock
2004
    *          {@code true} to update the current lock state;
2005
    *          {@code false} to leave the current lock state unchanged.
2006
    * @param   partialFields
2007
    *          For an incomplete record, read only these fields.
2008
    *
2009
    * @return  A single DMO instance loaded from the database, or {@code null} if no result was
2010
    *          found.
2011
    *
2012
    * @throws  LockTimeoutException
2013
    *          if a record was found, but its lock could not be acquired within the positive
2014
    *          {@code timeout} period specified.
2015
    * @throws  LockUnavailableException
2016
    *          if a record was found and a no-wait lock type was specified, but the lock could
2017
    *          not be acquired immediately.
2018
    * @throws  PersistenceException
2019
    *          if there was an error loading the DMO from the ORM session and/or database.
2020
    */
2021
   public Record load(Class<? extends Record> implClass,
2022
                      Long id,
2023
                      LockType lockType,
2024
                      long timeout,
2025
                      boolean updateLock,
2026
                      BitSet partialFields)
2027
   throws PersistenceException,
2028
          LockTimeoutException
2029
   {
1981 2030
      boolean trace = LOG.isLoggable(Level.FINEST);
1982 2031
      boolean warn = (trace || LOG.isLoggable(Level.WARNING));
1983 2032
      boolean trackSlow = warn && loadThreshold > 0L;
......
2025 2074
         }
2026 2075
         
2027 2076
         Session session = local.getSession();
2028
         result = session.get(implClass, id);
2077
         result = session.get(implClass, id, partialFields);
2029 2078
         
2030 2079
         if (result == null)
2031 2080
         {
new/src/com/goldencode/p2j/persist/PreselectQuery.java 2022-11-09 18:37:12 +0000
587 587
**     CA  20221006          Added JMX instrumentation for query assemble. Refs #6814
588 588
**     CA  20221013          In 'repositionByID', if the row number is after the last row in the ResultSet,
589 589
**                           assume an error (query off-end) and do not try to fetch the record.
590
**     CA  20221109          Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded 
591
**                           as 'partial/incomplete'.   At this time, extent fields are always loaded.
590 592
*/
591 593

  
592 594
/*
......
4439 4441
         
4440 4442
         boolean qualified = !isOmitAlias();
4441 4443
         
4444
         RecordBuffer buffer = comp.getBuffer();
4445

  
4442 4446
         if (qualified)
4443 4447
         {
4444
            RecordBuffer buffer = comp.getBuffer();
4445 4448
            buf.append(buffer.getDMOAlias());
4446 4449
         }
4447 4450
         
......
4454 4457
            
4455 4458
            buf.append(DatabaseManager.PRIMARY_KEY);
4456 4459
         }
4460
         else 
4461
         {
4462
            Property[] iprops = included == null ? null : included.get(buffer.getDMOProxy());
4463
            Property[] xprops = excluded == null ? null : excluded.get(buffer.getDMOProxy());
4464
            if (iprops != null || xprops != null)
4465
            {
4466
               Property[] props = null;
4467
               
4468
               // only permanent buffers can use include/exclude
4469
               if (iprops == null)
4470
               {
4471
                  DmoMeta meta = buffer.getDmoInfo();
4472
                  
4473
                  props = new Property[meta.getExistingFields().size()];
4474
                  
4475
                  // include all fields
4476
                  Iterator<Property> piter = meta.getFields(false);
4477
                  while (piter.hasNext())
4478
                  {
4479
                     Property p = piter.next();
4480
                     
4481
                     if (p.propId > 0)
4482
                     {
4483
                        props[p.id - 1] = p;
4484
                     }
4485
                  }
4486
               }
4487
               else
4488
               {
4489
                  props = new Property[iprops.length];
4490
                  System.arraycopy(iprops, 0, props, 0, props.length);
4491
               }
4492
               
4493
               if (xprops != null)
4494
               {
4495
                  // exclude properties
4496
                  for (int j = 0; j < xprops.length; j++)
4497
                  {
4498
                     Property xprop = xprops[j];
4499
                     if (xprop != null)
4500
                     {
4501
                        props[j] = null;
4502
                     }
4503
                  }
4504
               }
4505
               
4506
               String alias = buffer.getDMOAlias();
4507
               buf.append('.').append(DatabaseManager.PRIMARY_KEY);
4508
               for (int j = 0; j < props.length; j++)
4509
               {
4510
                  Property p = props[j];
4511
                  if (p == null || p.extent != 0) // TODO: denormalized extents
4512
                  {
4513
                     continue;
4514
                  }
4515
                  
4516
                  buf.append(", ").append(alias).append(".").append(p.name);
4517
               }
4518
            }
4519
         }
4457 4520
      }
4458 4521
      
4459 4522
      buf.append(" ");
......
5802 5865
                  {
5803 5866
                     try
5804 5867
                     {
5805
                        dmo = p.load(buffer.getDMOImplementationClass(), id, lt, timeout, updateLock);
5868
                        // force the load of the entire record
5869
                        dmo = p.load(buffer.getDMOImplementationClass(), id, lt, timeout, updateLock, null);
5806 5870
                       
5807 5871
                        break;
5808 5872
                     }
......
5853 5917
               }
5854 5918
               else
5855 5919
               {
5856
                  dmo = p.load(buffer.getDMOImplementationClass(), id, lt, 0L, updateLock);
5920
                  dmo = p.load(buffer.getDMOImplementationClass(), 
5921
                               id, 
5922
                               lt, 
5923
                               0L, 
5924
                               updateLock, 
5925
                               // the entire record is loaded if the lock is not NONE
5926
                               updateLock || dmo == null ? null : dmo._getReadFields());
5857 5927
               }
5858 5928
            }
5859 5929
            
new/src/com/goldencode/p2j/persist/Record.java 2022-11-09 18:37:40 +0000
22 22
**     OM  20220713 Added support for converting to/from POJOs.
23 23
**     OM  20220718 Fixed looping in getPropertyValues(). Added to the returned list the denormalized props.
24 24
**     TJD 20220504 Upgrade do Java 11 minor changes
25
**     CA  20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as 
26
**                  'partial/incomplete'.
25 27
*/
26 28

  
27 29
/*
......
143 145
    */
144 146
   public final integer _getInteger(int offset)
145 147
   {
148
      checkIncomplete(offset);
149

  
146 150
      Integer datum = (Integer) data[offset];
147 151
      
148 152
      return datum != null ? new integer(datum) : new integer();
......
161 165
    */
162 166
   protected final void _setInteger(int offset, NumberType w)
163 167
   {
168
      checkIncomplete(offset);
169

  
164 170
      Integer datum = null;
165 171
      if (w != null)
166 172
      {
......
250 256
    */
251 257
   public final datetimetz _getDatetimetz(int offset)
252 258
   {
259
      checkIncomplete(offset);
260

  
253 261
      OffsetDateTime datum = (OffsetDateTime) data[offset];
254 262
      
255 263
      return datum != null ? new datetimetz(datum) : new datetimetz();
......
354 362
    */
355 363
   public final datetime _getDatetime(int offset)
356 364
   {
365
      checkIncomplete(offset);
366

  
357 367
      Timestamp datum = (Timestamp) data[offset];
358 368
      
359 369
      return datum != null ? new datetime(datum) : new datetime();
......
457 467
    */
458 468
   public final date _getDate(int offset)
459 469
   {
470
      checkIncomplete(offset);
471

  
460 472
      Date datum = (Date) data[offset];
461 473
      
462 474
      return datum != null ? new date(datum) : new date();
......
560 572
    */
561 573
   public final logical _isLogical(int offset)
562 574
   {
575
      checkIncomplete(offset);
576

  
563 577
      Boolean datum = (Boolean) data[offset];
564 578
      
565 579
      return datum != null ? new logical(datum) : new logical();
......
770 784
    */
771 785
   public final decimal _getDecimal(int offset)
772 786
   {
787
      checkIncomplete(offset);
788

  
773 789
      BigDecimal datum = (BigDecimal) data[offset];
774 790
      
775 791
      return datum != null ? new decimal(datum) : new decimal();
......
886 902
    */
887 903
   public final character _getCharacter(int offset)
888 904
   {
905
      checkIncomplete(offset);
906

  
889 907
      String datum = (String) data[offset];
890 908
      
891 909
      if (datum != null)
......
996 1014
    */
997 1015
   public final rowid _getRowid(int offset)
998 1016
   {
1017
      checkIncomplete(offset);
1018

  
999 1019
      Long datum = (Long) data[offset];
1000 1020
      
1001 1021
      return datum != null ? new rowid(datum) : new rowid();
......
1101 1121
    */
1102 1122
   public final handle _getHandle(int offset)
1103 1123
   {
1124
      checkIncomplete(offset);
1125

  
1104 1126
      Long datum = (Long) data[offset];
1105 1127
      
1106 1128
      return datum != null ? handle.fromResourceId(datum, false) : new handle();
......
1206 1228
    */
1207 1229
   public final recid _getRecid(int offset)
1208 1230
   {
1231
      checkIncomplete(offset);
1232

  
1209 1233
      Integer datum = (Integer) data[offset];
1210 1234
      
1211 1235
      return datum != null ? new recid(datum) : new recid();
......
1310 1334
    */
1311 1335
   public final blob _getBlob(int offset)
1312 1336
   {
1337
      checkIncomplete(offset);
1338

  
1313 1339
      byte[] datum = (byte[]) data[offset];
1314 1340
      
1315 1341
      return datum != null ? new blob(datum) : new blob();
......
1415 1441
    */
1416 1442
   public final clob _getClob(int offset)
1417 1443
   {
1444
      checkIncomplete(offset);
1445

  
1418 1446
      return new clob((String) data[offset], getCodePage(offset));
1419 1447
   }
1420 1448
   
......
1516 1544
    */
1517 1545
   public final comhandle _getComhandle(int offset)
1518 1546
   {
1547
      checkIncomplete(offset);
1548

  
1519 1549
      String datum = (String) data[offset];
1520 1550
      
1521 1551
      return datum != null ? comhandle.fromString(datum) : new comhandle();
......
1619 1649
    */
1620 1650
   public final object<?> _getObject(int offset)
1621 1651
   {
1652
      checkIncomplete(offset);
1653

  
1622 1654
      Long datum = (Long) data[offset];
1623 1655
      
1624 1656
      return datum != null ? object.fromResourceId(datum) : new object<>();
......
1726 1758
    */
1727 1759
   public final raw _getRaw(int offset)
1728 1760
   {
1761
      checkIncomplete(offset);
1762

  
1729 1763
      byte[] datum = (byte[]) data[offset];
1730 1764
      
1731 1765
      return datum != null ? new raw(datum) : raw.instantiateUnknownRaw();
......
1893 1927
            {
1894 1928
               sb.append("\"");
1895 1929
            }
1896
            sb.append(data[i] == null ? "?" : data[i]);
1930
            
1931
            boolean incomplete = readFields != null && !readFields.get(i);
1932
            
1933
            sb.append(data[i] == null ? incomplete ? "n/a" : "?" : data[i]);
1897 1934
            if (isChar)
1898 1935
            {
1899 1936
               sb.append("\"");
new/src/com/goldencode/p2j/persist/orm/BaseRecord.java 2022-11-09 18:38:23 +0000
28 28
**                  used in a Java REST service, which must not be associated with a session.
29 29
**     ECF 20221005 Added support for dynamic initial values.
30 30
**     TJD 20220504 Upgrade do Java 11 minor changes
31
**     CA  20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as 
32
**                  'partial/incomplete'.
31 33
*/
32 34

  
33 35
/*
......
128 130
   /** The data represented by this record, in it's low-level (database friendly) form */
129 131
   protected final Object[] data;
130 132
   
133
   /** For an incomplete record, this holds the fields which were read or assigned in this record. */
134
   protected BitSet readFields = null;
135
   
131 136
   /** Current state of the record */
132 137
   protected final RecordState state = new RecordState();
133 138
   
......
785 790
   }
786 791
   
787 792
   /**
793
    * Get the {@link #readFields}.
794
    * 
795
    * @return   The {@link #readFields}.
796
    */
797
   public BitSet _getReadFields()
798
   {
799
      return readFields;
800
   }
801

  
802
   /**
788 803
    * Set a single datum into the data array, and update the record and property state accordingly.
789 804
    * 
790 805
    * @param   offset
......
794 809
    */
795 810
   protected void setDatum(int offset, Object datum)
796 811
   {
812
      if (readFields != null)
813
      {
814
         readFields.set(offset);
815
      }
816

  
797 817
      if (activeBuffer != null)
798 818
      {
799 819
         activeOffset = offset;
......
1015 1035
   }
1016 1036
   
1017 1037
   /**
1038
    * Check if for an incomplete record, the specified field was read or assigned.
1039
    * 
1040
    * @param    index
1041
    *           The field index.
1042
    */
1043
   protected void checkIncomplete(int index)
1044
   {
1045
      if (checkState(DmoState.INCOMPLETE) && !readFields.get(index))
1046
      {
1047
         RecordMeta meta = _recordMeta();
1048
         String tname = meta.legacyName;
1049
         String fname = meta.getPropertyMeta(false)[index].getLegacyName();
1050
         String msg = "Field " + fname + " from " + tname + " record (recid " + primaryKey() + 
1051
                       ") was missing from FIELDS phrase";
1052
         
1053
         ErrorManager.recordOrThrowError(8826, msg, false);
1054
      }
1055
   }
1056

  
1057
   /**
1058
    * Mark this record as incomplete.
1059
    * 
1060
    * @param    session
1061
    *           Database session (used for UNDO purposes).
1062
    */
1063
   void markIncomplete(Session session)
1064
   {
1065
      updateState(session, DmoState.INCOMPLETE, true);
1066
      readFields = new BitSet(data.length);
1067
   }
1068

  
1069
   /**
1018 1070
    * An index was updated.
1019 1071
    * 
1020 1072
    * @param   idxUid
......
1225 1277
   final int readProperty(int propOffset, ResultSet rs, int rsOffset)
1226 1278
   throws PersistenceException
1227 1279
   {
1280
      return readProperty(propOffset, rs, rsOffset, false);
1281
   }
1282
   
1283
   /**
1284
    * Read a property directly from a {@code ResultSet}.
1285
    *
1286
    * @param   propOffset
1287
    *          The index of the property to be read.
1288
    * @param   rs
1289
    *          The result set to read from.
1290
    * @param   rsOffset
1291
    *          The index in {@code ResultSet} to read from. If the property is a multiple column
1292
    *          then more than one column will ve read from {@code ResultSet}.
1293
    * @param   skip
1294
    *          Flag indicating if this property will not be read from the result-set.
1295
    *          
1296
    * @return  The number of columns actually read from the {@code ResultSet}.
1297
    */
1298
   final int readProperty(int propOffset, ResultSet rs, int rsOffset, boolean skip)
1299
   throws PersistenceException
1300
   {
1228 1301
      PropertyMeta pm = _recordMeta().getPropertyMeta(false)[propOffset];
1229 1302
      PropertyReader propertyReader = pm.getDataHandler();
1303
      if (skip)
1304
      {
1305
         return propertyReader.propertySize();
1306
      }
1230 1307
      
1231 1308
      try
1232 1309
      {
1310
         if (readFields != null)
1311
         {
1312
            readFields.set(propOffset);
1313
         }
1314
         
1233 1315
         return propertyReader.readProperty(rs, rsOffset, data, propOffset);
1234 1316
      }
1235 1317
      catch (java.sql.SQLException sqle)
new/src/com/goldencode/p2j/persist/orm/DmoState.java 2022-11-09 18:38:44 +0000
2 2
** Module   : DmoState.java
3 3
** Abstract : DMO states which govern persistence behavior.
4 4
**
5
** Copyright (c) 2020-2021, Golden Code Development Corporation.
5
** Copyright (c) 2020-2022, Golden Code Development Corporation.
6 6
**
7 7
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
8 8
** 001 ECF 20200315 First revision.
9 9
** 002 ECF 20210519 Added TRACKED state.
10 10
**     ECF 20210923 Added DELETING state.
11
**     CA  20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as 
12
**                  'partial/incomplete'.
11 13
*/
12 14

  
13 15
/*
......
110 112
   
111 113
   /** DMO currently is being tracked for possible UNDO processing */
112 114
   public static final int TRACKED = 0x0400;
115
   
116
   /** DMO is read incomplete (due to EXCEPT/FIELDS options) and can't be cached. */
117
   public static final int INCOMPLETE = 0x0800;
113 118
}
new/src/com/goldencode/p2j/persist/orm/Loader.java 2022-11-09 18:39:09 +0000
11 11
**                  (Temporary?) set the properties to be accessed with old-fashioned accessors.
12 12
**     CA  20201005 Fixed the sort order when loading a child extent table.
13 13
**     TJD 20220504 Upgrade do Java 11 minor changes
14
**     CA  20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as 
15
**                  'partial/incomplete'.  Extent fields are always loaded at this time.
14 16
*/
15 17

  
16 18
/*
......
323 325
    *          DMO implementation class.
324 326
    * @param   id
325 327
    *          Surrogate primary key.
326
    * 
328
    *          
327 329
    * @return  An instance of {@code dmoClass}, loaded with data from the database, or {@code
328 330
    *          null} if the record was not found. The DMO is instantiated either way, but it will
329 331
    *          be discarded if the record is not found.
......
334 336
   <T extends BaseRecord> T load(Class<T> dmoClass, Long id)
335 337
   throws PersistenceException
336 338
   {
339
      return load(dmoClass, id, null);
340
   }
341
   
342
   /**
343
    * Load a single record from the database, given its DMO class and primary key. The primary
344
    * key is assumed to represent a valid identifier; if not, {@code null} will be returned.
345
    * <p>
346
    * TODO: a SQL statement is prepared and closed for each load statement executed by this
347
    * method. We rely on statement caching at the connection pool level to make this performant,
348
    * but perhaps there is some predictive analysis we can do to make this more efficient.
349
    * 
350
    * @param   dmoClass
351
    *          DMO implementation class.
352
    * @param   id
353
    *          Surrogate primary key.
354
    * @param   partialFields
355
    *          For an incomplete record, read only these fields.
356
    *          
357
    * @return  An instance of {@code dmoClass}, loaded with data from the database, or {@code
358
    *          null} if the record was not found. The DMO is instantiated either way, but it will
359
    *          be discarded if the record is not found.
360
    * 
361
    * @throws  PersistenceException
362
    *          if an error occurred accessing the database.
363
    */
364
   <T extends BaseRecord> T load(Class<T> dmoClass, Long id, BitSet partialFields)
365
   throws PersistenceException
366
   {
337 367
      try
338 368
      {
339 369
         T dmo = dmoClass.getDeclaredConstructor().newInstance();
340 370
         dmo.primaryKey(id);
341 371
         
342
         return load(dmo, id);
372
         return load(dmo, id, partialFields);
343 373
      }
344 374
      catch (ReflectiveOperationException  exc)
345 375
      {
......
360 390
    *          DMO implementation class.
361 391
    * @param   id
362 392
    *          Surrogate primary key.
393
    * @param   partialFields
394
    *          For an incomplete record, read only these fields.
363 395
    * 
364 396
    * @return  An instance of {@code dmoClass}, loaded with data from the database, or {@code
365 397
    *          null} if the record was not found. The DMO is instantiated either way, but it will
......
368 400
    * @throws  PersistenceException
369 401
    *          if an error occurred accessing the database.
370 402
    */
371
   <T extends BaseRecord> T load(T dmo, Long id)
403
   <T extends BaseRecord> T load(T dmo, Long id, BitSet partialFields)
372 404
   throws PersistenceException
373 405
   {
374 406
      try
......
380 412
         boolean hasScalarData = props.length > 0 && props[0].getExtent() == 0;
381 413
         
382 414
         int start = 0;
415
         if (partialFields != null)
416
         {
417
            dmo.markIncomplete(session);
418
         }
383 419
         
384 420
         for (String sql : loadSql)
385 421
         {
......
404 440
                  }
405 441
                  
406 442
                  // the first query is always on the primary table, if the DMO has scalar data
407
                  start = readScalarData(dmo, rs, 0, props);
443
                  start = readScalarData(dmo, rs, 0, props, partialFields);
408 444
               }
409 445
               else
410 446
               {
......
448 484
    *          use 0. If the record data is prepended by a single column (Ex: primaryKey), pass 1.
449 485
    * @param   props
450 486
    *          Array of property metadata objects.
487
    * @param   partialFields
488
    *          For an incomplete record, read only these fields.
451 489
    * 
452 490
    * @return  Index of next slot in the data array to be filled (if any) after this series of
453 491
    *          scalar data has been read, or -1 to indicate the result set was empty (record
......
459 497
   private static int readScalarData(BaseRecord r,
460 498
                                     ResultSet rs,
461 499
                                     int rowOffset,
462
                                     PropertyMeta[] props)
500
                                     PropertyMeta[] props,
501
                                     BitSet partialFields)
463 502
   throws SQLException, PersistenceException
464 503
   {
465 504
      // the current row is expected to be already selected, since the filter is the primary
......
480 519
            break;
481 520
         }
482 521
         
483
         int colCnt = r.readProperty(pm.offset, rs, pm.columnIndex + rowOffset);
522
         boolean skip = partialFields != null && !partialFields.get(pm.offset);
523
         int colCnt = r.readProperty(pm.offset, rs, pm.columnIndex + rowOffset, skip);
484 524
         
485 525
         if (colCnt != pm.columnCount)
486 526
         {
new/src/com/goldencode/p2j/persist/orm/PropertyReader.java 2022-11-09 18:39:36 +0000
3 3
** Abstract : Functional interface for reading a (possibly multiple column) property from a SQL
4 4
**            result set.
5 5
**
6
** Copyright (c) 2020, Golden Code Development Corporation.
6
** Copyright (c) 2020-2022, Golden Code Development Corporation.
7 7
**
8 8
** -#- -I- --Date-- ---------------------------------Description---------------------------------
9 9
** 001 OM  20200212 Created initial version.
10
** 002 CA  20221109 Added 'propertySize' method which returns the number of columns in the result-set for this
11
**                  property type.
10 12
*/
11 13

  
12 14
/*
......
93 95
    */
94 96
   public int readProperty(ResultSet rs, int rsOffset, Object[] data, int propIndex)
95 97
   throws SQLException;
98
   
99
   /**
100
    * Get the size (number of columns in the result-set) required by this property.
101
    * 
102
    * @return   Defaults to 1.
103
    */
104
   public default int propertySize()
105
   {
106
      return 1;
107
   }
96 108
}
new/src/com/goldencode/p2j/persist/orm/RecordState.java 2022-11-09 18:39:43 +0000
2 2
** Module   : RecordState.java
3 3
** Abstract : Current state of a database record in an application.
4 4
**
5
** Copyright (c) 2019-2021, Golden Code Development Corporation.
5
** Copyright (c) 2019-2022, Golden Code Development Corporation.
6 6
**
7 7
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
8 8
** 001 ECF 20191001 First revision. Object which tracks DMO state as changes are applied to it.
9 9
** 002 ECF 20210519 Added TRACKED state to toString.
10 10
**     ECF 20210924 Added DELETING state to toString.
11
**     CA  20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as 
12
**                  'partial/incomplete'.
11 13
*/
12 14

  
13 15
/*
......
144 146
         }
145 147
         if ((state & DmoState.TRACKED) == DmoState.TRACKED)
146 148
         {
147
            sb.append("TRACKED");
149
            sb.append("TRACKED ");
150
         }
151
         if ((state & DmoState.INCOMPLETE) == DmoState.INCOMPLETE)
152
         {
153
            sb.append("INCOMPLETE");
148 154
         }
149 155
      }
150 156
      sb.append("}");
new/src/com/goldencode/p2j/persist/orm/SQLQuery.java 2022-11-09 18:40:44 +0000
33 33
**                  a wrapper for two separate parameters, changed hydrateRecordImpl logic to skip fields that are not
34 34
**                  queried.
35 35
**     TJD 20220504 Upgrade do Java 11 minor changes
36
**     CA  20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as 
37
**                  'partial/incomplete'.  Extent fields are always loaded at this time.
36 38
*/
37 39

  
38 40
/*
......
755 757
      try
756 758
      {
757 759
         SIMPLE_QUERY_PROFILER.updateCacheMisses(resultSet, 1);
758
         r = recordClass.getDeclaredConstructor().newInstance();
760
         r = recordClass.newInstance();
759 761
         
760 762
         // see FqlToSqlConverter.expandAlias()
761 763
         Set<Map.Entry<String, Property>> allFieldEntries = dmoInfo.propsByName.entrySet();
......
771 773
         }
772 774
         int k = 0;
773 775
         int recOffset = 0;
774
         boolean hasExtents = false;
776
         // TODO: EXCEPT/FIELDS for extent fields; for now, load all extents
777
         boolean incomplete = fieldEntries != allFieldEntries;
778
         boolean hasExtents = incomplete; 
779
         if (incomplete)
780
         {
781
            r.markIncomplete(session);
782
         }
775 783
         
776 784
         for (Map.Entry<String, Property> prop : fieldEntries)
777 785
         {
new/src/com/goldencode/p2j/persist/orm/Session.java 2022-11-09 18:40:30 +0000
25 25
**     ECF 20211022 A cache expiration is not tracked as an undoable event.
26 26
**     ECF 20211230 Do not attempt to delete a newly created record that was never flushed to the database.
27 27
**     OM  20220628 Signature change for createSQLQuery() method.
28
**     CA  20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as 
29
**                  'partial/incomplete'.  Partial/Incomplete records are being cached, but when they try to
30
**                  be loaded with no 'partial field information', the cached record is fully refreshed from
31
**                  the database, and not just returned.
28 32
*/
29 33

  
30 34
/*
......
450 454
   public <T extends BaseRecord> T get(Class<T> dmoImplClass, Long id)
451 455
   throws PersistenceException
452 456
   {
453
      return getImpl(dmoImplClass, id, new RecordIdentifier<>(dmoImplClass.getName(), id));
457
      return getImpl(dmoImplClass, id, new RecordIdentifier<>(dmoImplClass.getName(), id), null);
458
   }
459

  
460
   /**
461
    * Gets a specified record of a table. To lookup the record local cache is analyzed first. If
462
    * the record is not found in cache, the backing database is queried. If the specified record
463
    * is not found in database, {@code null} is returned. If the record was fetched from the
464
    * database at this moment, it is kept in cache for further access.
465
    *
466
    * @param   <T>
467
    *          The type of the record.
468
    * @param   dmoImplClass
469
    *          The class of the entity (class that implements the table's DMO interface).
470
    * @param   id
471
    *          The id/rowid of the record to be returned.
472
    * @param   partialFields
473
    *          For an incomplete record, read only these fields.
474
    *
475
    * @return  The requested record, if found in cache or database and {@code null} otherwise.
476
    *
477
    * @throws  PersistenceException
478
    *          In case some error occurs in the process.
479
    */
480
   public <T extends BaseRecord> T get(Class<T> dmoImplClass, Long id, BitSet partialFields)
481
   throws PersistenceException
482
   {
483
      return getImpl(dmoImplClass, id, new RecordIdentifier<>(dmoImplClass.getName(), id), partialFields);
454 484
   }
455 485
   
456 486
   /**
......
473 503
   public <T extends BaseRecord> T get(RecordIdentifier<String> key)
474 504
   throws PersistenceException
475 505
   {
476
      return getImpl(null, key.getRecordID(), key);
506
      return getImpl(null, key.getRecordID(), key, null);
477 507
   }
478 508
   
479 509
   /**
......
497 527
   public <T extends BaseRecord> T get(String entity, Long id)
498 528
   throws PersistenceException
499 529
   {
500
      return getImpl(null, id, new RecordIdentifier<>(entity, id));
530
      return getImpl(null, id, new RecordIdentifier<>(entity, id), null);
501 531
   }
502 532
   
503 533
   /**
......
513 543
    * @param   key
514 544
    *          The cache key of the entity. If {@code null} the returned value is {@code null};
515 545
    *          provided.
546
    * @param   partialFields
547
    *          For an incomplete record, read only these fields.
516 548
    * @param   <T>
517 549
    *          The compile type of the record to be returned. 
518 550
    *
......
522 554
    * @throws  PersistenceException
523 555
    *          if there is an error getting a JDBC connection from the data source.
524 556
    */
525
   private <T extends BaseRecord> T getImpl(Class<T> dmoImplClass, Long id, RecordIdentifier<String> key)
557
   private <T extends BaseRecord> T getImpl(Class<T> dmoImplClass, 
558
                                            Long id, 
559
                                            RecordIdentifier<String> key, 
560
                                            BitSet partialFields)
526 561
   throws PersistenceException
527 562
   {
528 563
      // quick out
......
544 579
      
545 580
      T dmo = getCached(key);
546 581
      
582
      boolean refreshIncomplete = false;
547 583
      if (dmo != null)
548 584
      {
549 585
         // assert that we have the right DMO type for this primary key
......
557 593
            throw new PersistenceException(msg);
558 594
         }
559 595
         
560
         return dmo;
596
         if (dmo.checkState(DmoState.INCOMPLETE) && partialFields == null)
597
         {
598
            // we have a cached record which is incomplete, and this requires to refresh the incomplete record
599
            // from the database
600
            refreshIncomplete = true;
601
         }
602
         else
603
         {
604
            return dmo;
605
         }
561 606
      }
562 607
      
563 608
      if (dmoImplClass == null)
......
579 624
         throw new PersistenceException("Invalid entity name: " + dmoImplClass.getName());
580 625
      }
581 626
      
582
      dmo = loader.load(dmoImplClass, id);
627
      T loaded = loader.load(dmoImplClass, id, partialFields);
628
      if (refreshIncomplete)
629
      {
630
         // evict the incomplete record from the cache, will be recached if one was found
631
         evict(dmo);
632
         
633
         if (loaded == null)
634
         {
635
            dmo = null;
636
         }
637
         else
638
         {
639
            System.arraycopy(loaded.data, 0, dmo.data, 0, dmo.data.length);
640

  
641
            // set the incomplete record as complete
642
            dmo.updateState(this, DmoState.INCOMPLETE, false);
643
            dmo.readFields = null;
644
         }
645
      }
646
      else
647
      {
648
         dmo = loaded;
649
      }
583 650
      
584 651
      if (dmo != null)
585 652
      {
......
962 1029
      // ignore the cache and go directly to underlying database
963 1030
      // use the variant of load which overwrites the DMOs current field values with data
964 1031
      // fetched from the database
965
      T loaded = loader.load(dmo, dmo.id);
1032
      T loaded = loader.load(dmo, dmo.id, null);
966 1033
      
967 1034
      RecordIdentifier<String> key = new RecordIdentifier<>(dmo.getClass().getName(), dmo.id);
968 1035
      
new/src/com/goldencode/p2j/persist/orm/types/DatetimetzType.java 2022-11-09 18:41:02 +0000
13 13
**     CA  20210304 Fixed datetimetz literal parsing.
14 14
**     OM  20220218 Fixed timezone when the dtz is rehydrated.
15 15
**     ECF 20221005 Added support for dynamic initial values.
16
**     CA  20221109 Added 'propertySize' method which returns the number of columns in the result-set for this
17
**                  property type.
16 18
*/
17 19

  
18 20
/*
......
180 182
   }
181 183
   
182 184
   /**
185
    * Get the number of columns read from the result-set.
186
    * 
187
    * @return   always 2.
188
    */
189
   @Override
190
   public int propertySize()
191
   {
192
      return 2;
193
   }
194
   
195
   /**
183 196
    * Instantiate a low-level initial value suitable for storage in a {@link BaseRecord}'s data
184 197
    * array, parsed from a string representation.
185 198
    *