=== modified file 'rules/schema/java_pojo.xml'
--- old/rules/schema/java_pojo.xml	2026-05-15 11:20:28 +0000
+++ new/rules/schema/java_pojo.xml	2026-05-22 13:57:10 +0000
@@ -3,11 +3,10 @@
 ** Module   : java_dmo.xml
 ** Abstract : walks data model AST and generates Java AST for each class node
 **
-** Copyright (c) 2022-2026, Golden Code Development Corporation.
+** Copyright (c) 2022, Golden Code Development Corporation.
 **
 ** _#_ _I_ __Date__ _______________________________________Description_______________________________________
 ** 001 OM  20220707 First commit with basic implementation.
-** 002 RNC 20260406 Migrate RECID from 32-bit to 64-bit values.
 -->
 
 <!--
@@ -113,7 +112,7 @@
       <rule>typeMapping.put("logical",     "Boolean")</rule>
       <rule>typeMapping.put("object",      "Long")</rule>
       <rule>typeMapping.put("raw",         "byte[]")</rule>
-      <rule>typeMapping.put("recid",       "Long")</rule>
+      <rule>typeMapping.put("recid",       "Integer")</rule>
       <rule>typeMapping.put("rowid",       "Long")</rule>
       
       <rule>importPacks = create("java.util.HashSet")</rule>

=== modified file 'rules/schema/p2o.xml'
--- old/rules/schema/p2o.xml	2026-05-15 11:20:28 +0000
+++ new/rules/schema/p2o.xml	2026-05-22 13:57:10 +0000
@@ -312,7 +312,6 @@
 **                           if this is specified in cfg file.
 **     SP  20250324          Removed support for custom denormalized extents.
 ** 108 SP  20250714          Fixed column collision check for expanded extent fields.
-** 109 RNC 20260409          Migrate RECID from 32-bit to 64-bit values.
 -->
 
 <!--
@@ -504,7 +503,7 @@
       <rule>dataTypes.put(prog.field_int64,       'int64'                   )</rule>
       <rule>dataTypes.put(prog.field_logical,     'logical'                 )</rule>
       <rule>dataTypes.put(prog.field_raw,         'raw'                     )</rule>
-      <rule>dataTypes.put(prog.field_recid,       'recid'                   )</rule>
+      <rule>dataTypes.put(prog.field_recid,       'integer'                 )</rule>
       <rule>dataTypes.put(prog.field_rowid,       'rowid'                   )</rule>
       <rule>dataTypes.put(prog.field_class,       'object&lt;? extends _BaseObject_&gt;')</rule>
       
@@ -1741,6 +1740,7 @@
                   <rule>
                      type == prog.field_int or
                      type == prog.field_date or
+                     type == prog.field_recid or
                      type == prog.field_handle
                      <action>sqlWidth = #(long) 4</action>
                   </rule>
@@ -1750,8 +1750,7 @@
                      <!-- TODO: determine the proper length -->
                      <action>sqlWidth = #(long) -1</action>
                   </rule>
-                  <rule>type == prog.field_rowid or
-                        type == prog.field_recid
+                  <rule>type == prog.field_rowid
                      <action>sqlWidth = #(long) 8</action>
                   </rule>
                   <rule>type == prog.field_char

=== modified file 'src/com/goldencode/p2j/jmx/FwdServerJMX.java'
--- old/src/com/goldencode/p2j/jmx/FwdServerJMX.java	2025-11-11 13:00:24 +0000
+++ new/src/com/goldencode/p2j/jmx/FwdServerJMX.java	2026-05-22 13:58:35 +0000
@@ -248,6 +248,29 @@
       OrmDataSetParam,
       /** Instrumentation for <code>DataSet.createDynamicDataSet</code>. */
       OrmDynamicDataSetParam,
+      
+      /** Lazily hydrated an new record (not rehydrated from cache) */
+      HydratorNewRecord,
+      /** Lazily hydrated a record (including rehydration from cache) */
+      HydratorLazyRecord,
+      /** Fully or partially hydrated a record, but not lazy */
+      HydratorNonLazyRecord,
+      /** The hydrator token matched the result-set token */
+      HydratorHit,
+      /** The hydrator token mismatched the result-set token */
+      HydratorMiss,
+      /** The hydrator fallbacked to refresh and succeeded */
+      HyratorSuccessfullRefresh,
+      /** The hydrator fallbacked to refresh and failed */
+      HyratorUnsuccessfullRefresh,
+      /** Created a {@code ScrollableResults} instance with lazy results */
+      HydratorLazyScrollable,
+      /** Created a {@code ScrollableResults} instance without lazy results */
+      HydratorNonLazyScrollable,
+      /** Executed uniqueResult instance with lazy results */
+      HydratorLazyUnique,
+      /** Executed uniqueResult instance without lazy results */
+      HydratorNonLazyUnique,
 
       /** Instrumentation for <code>OutputTableCopier.finished</code> */
       OutputTableCopier,
@@ -489,6 +512,41 @@
    public static enum Counter
    implements OptionalCounter
    {
+      TempTotalFields,
+      PersistentTotalFields,
+      TempHydratedRecordsWhenSessionCloses,
+      PersistentHydratedRecordsWhenSessionCloses,
+      TempHydratedFieldsWhenSessionCloses,
+      PersistentHydratedFieldsWhenSessionCloses,
+      PersistentHydratedFieldsWithLZS,
+      PersistentHydratedFieldsWithLoader,
+      TempHydratedFieldsWithLZS,
+      TempHydratedFieldsWithLoader,
+      
+      
+      /** Lazily hydrated an new record (not rehydrated from cache) */
+      HydratorNewRecord,
+      /** Lazily hydrated a record (including rehydration from cache) */
+      HydratorLazyRecord,
+      /** Fully or partially hydrated a record, but not lazy */
+      HydratorNonLazyRecord,
+      /** The hydrator token matched the result-set token */
+      HydratorHit,
+      /** The hydrator token mismatched the result-set token */
+      HydratorMiss,
+      /** The hydrator fallbacked to refresh and succeeded */
+      HyratorSuccessfullRefresh,
+      /** The hydrator fallbacked to refresh and failed */
+      HyratorUnsuccessfullRefresh,
+      /** Created a {@code ScrollableResults} instance with lazy results */
+      HydratorLazyScrollable,
+      /** Created a {@code ScrollableResults} instance without lazy results */
+      HydratorNonLazyScrollable,
+      /** Executed uniqueResult instance with lazy results */
+      HydratorLazyUnique,
+      /** Executed uniqueResult instance without lazy results */
+      HydratorNonLazyUnique,
+      
       /** Incomning network traffic */
       NetworkReads,
       /** Outgoing network traffic */

=== modified file 'src/com/goldencode/p2j/persist/BufferImpl.java'
--- old/src/com/goldencode/p2j/persist/BufferImpl.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/BufferImpl.java	2026-05-22 13:57:10 +0000
@@ -1030,12 +1030,18 @@
     * Get the record ID of the current buffer's current record, if any.  P2J
     * implementation of the RECID function.
     * <p>
-    * This method will return a 64-bit recid which represents the internal
-    * row ID of the given record.
+    * This method will return a 32-bit recid which represents the internal
+    * row ID of the given record.  If the actual row ID (which is implemented
+    * as a wider integer) cannot fit due to overflow, an error condition will
+    * be raised.
     *
     * @return  Record ID of the underlying data record. If no record currently
     *          backs the buffer associated with <code>dmo</code>, a recid
     *          initialized as unknown value is returned.
+    * 
+    * @throws  ErrorConditionException
+    *          if the actual record ID is too wide to fit within a 32-bit
+    *          integer.
     */
    public recid recordID()
    {

=== modified file 'src/com/goldencode/p2j/persist/Cursor.java'
--- old/src/com/goldencode/p2j/persist/Cursor.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/Cursor.java	2026-05-22 13:57:10 +0000
@@ -77,7 +77,6 @@
 **                           account for the case when its state can become invalid. Refs #9402.
 ** 032 AL2 20250415          Allow consumers of repositionByID to decide if it should be a scan or not.
 ** 033 AL2 20250521          Repositioning to a row is done with NO_LOCK.
-** 034 RNC 20260401          Modified method 'repositionByID()' to use a 64-bit integer.
 */
 
 /*
@@ -263,7 +262,7 @@
     * @throws  ErrorConditionException
     *          if the specified ID represents the unknown value.
     */
-   boolean repositionByID(boolean scan, int64 id)
+   boolean repositionByID(boolean scan, integer id)
    {
       Long[] serIDs = new Long[] { id.longValue() };
       

=== modified file 'src/com/goldencode/p2j/persist/FqlType.java'
--- old/src/com/goldencode/p2j/persist/FqlType.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/FqlType.java	2026-05-22 13:57:10 +0000
@@ -2,7 +2,7 @@
 ** Module   : FqlType.java
 ** Abstract : Enumeration of data types for various SQL constructs
 **
-** Copyright (c) 2004-2026, Golden Code Development Corporation.
+** Copyright (c) 2004-2020, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- --JPRM-- -----------------------------Description-----------------------------
 ** 001 ECF 20080310  @37500  Created initial version. Enumeration of data types for various HQL
@@ -11,7 +11,6 @@
 ** 003 SVL 20120331          Added DATETIME and DATETIMETZ types.
 ** 004 OM  20200402          Renamed class to FqlType and move to persist package.
 ** 005 IAS 20201123          Added ARRAY type
-** 006 RNC 20260401          Modified the underlying recid class to use BIGINT instead of INTEGER.
 */
 
 /*
@@ -120,7 +119,7 @@
       types.put(comhandle.class, Types.VARCHAR);
       types.put(handle.class   , Types.VARCHAR);
       types.put(object.class   , Types.VARCHAR);
-      types.put(recid.class    , Types.BIGINT);
+      types.put(recid.class    , Types.INTEGER);
       types.put(rowid.class    , Types.BIGINT);
 */
    

=== modified file 'src/com/goldencode/p2j/persist/Persistence.java'
--- old/src/com/goldencode/p2j/persist/Persistence.java	2025-11-06 15:39:50 +0000
+++ new/src/com/goldencode/p2j/persist/Persistence.java	2026-05-22 13:58:35 +0000
@@ -751,6 +751,7 @@
 import java.util.function.*;
 import java.util.logging.*;
 import com.goldencode.cache.*;
+import com.goldencode.p2j.jmx.*;
 import com.goldencode.p2j.persist.deploy.*;
 import com.goldencode.p2j.persist.dialect.*;
 import com.goldencode.p2j.persist.dirty.DirtyShareSupport;
@@ -1947,15 +1948,14 @@
       {
          local.closeSession(true);
          ErrorEntry ee = exc.getErrorEntry();
-         ErrorManager.recordOrShowError(
-                  new int[] {ee.num}, 
-                  new String[] {ee.text}, 
-                  true, 
-                  ee.prefix, 
-                  false,
-                  false,
-                  false,
-                  true);
+         ErrorManager.recordOrShowError(new int[] {ee.num}, 
+                                        new String[] {ee.text},
+                                        true,
+                                        ee.prefix,
+                                        false,
+                                        false,
+                                        false,
+                                        true);
          handleException(ErrorManager.buildErrorText(ee.num, ee.text, ee.prefix, false), exc);
       }
       catch (PersistenceException exc)
@@ -5248,6 +5248,9 @@
       /** FQL statement */
       private final String fql;
       
+      /** Database dialect */
+      private final Dialect dialect;
+      
       /** Does the query was generated with maximum results option? */
       private final boolean hasMaxResults;
       
@@ -5268,21 +5271,25 @@
        * 
        * @param   hql
        *          FQL statement
+       * @param   dialect
+       *          Database dialect.
        * @param   maxResults
        *          Maximum results.
        * @param   startOffset
        *          Starting row offset.
        */
-      QueryKey(String hql, int maxResults, int startOffset, DataRelation relation, boolean lazyMode)
+      QueryKey(String hql, Dialect dialect, int maxResults, int startOffset, DataRelation relation, boolean lazyMode)
       {
          this.fql = hql;
+         this.dialect = dialect;
          this.hasMaxResults = maxResults > 0;
          this.hasStartOffset = startOffset > 0;
          this.whereStr = relation == null ? "" : relation.getWhereString().toJavaType();
          this.lazyMode = lazyMode;
          
          int result = 17;
-         result = 37 * result + hql.hashCode();
+         result = 37 * result + fql.hashCode();
+         result = 37 * result + dialect.hashCode();
          result = 37 * result + (hasMaxResults ? 1 : 0) + (hasStartOffset ? 2 : 0);
          result = 37 * result + whereStr.hashCode();
          result = 37 * result + (lazyMode ? 1 : 0);
@@ -5308,6 +5315,7 @@
          QueryKey that = (QueryKey) o;
          
          return this.hash == that.hash                     &&
+                this.dialect.equals(that.dialect)          &&
                 this.hasMaxResults == that.hasMaxResults   &&
                 this.hasStartOffset == that.hasStartOffset &&
                 this.fql.equals(that.fql) &&
@@ -5334,7 +5342,7 @@
       @Override
       public String toString()
       {
-         return fql + "[" + hasMaxResults + ":" + hasStartOffset + "];" + whereStr;
+         return fql + "[" + hasMaxResults + ":" + hasStartOffset + "] (" + dialect + ") " + whereStr;
       }
    }
    
@@ -5561,28 +5569,21 @@
          Query query = null;
          fql = fql.trim(); // just in case
          
-         // TODO: use the dialect also? I.e: is it possible to have different SQL for same FQL
-         //       if different dialects are used?
-         QueryKey key = new QueryKey(fql, maxResults, startOffset, relation, lazyMode);
+         QueryKey key = new QueryKey(fql, dialect, maxResults, startOffset, relation, lazyMode);
          
-         synchronized (staticQueryCache)
+         query = staticQueryCache.get(key);
+         if (query == null)
          {
-            query = staticQueryCache.get(key);
-            if (query == null)
+            Consumer<Query> c = (q) ->
             {
-               query = Session.createQuery(q ->
-               {
-                  synchronized (staticQueryCache)
-                  {
-                     // NOTE: the FQL string is not converted to SQL at this time. The conversion
-                     //       is performed once, lazily, when the first navigation is required by a
-                     //       call of list(), scroll(), or uniqueResult() on the new query
-                     
-                     staticQueryCache.put(key, q);
-                  }
-               },
-               fql);
-            }
+               // NOTE: the FQL string is not converted to SQL at this time. The conversion
+               //       is performed once, lazily, when the first navigation is required by a
+               //       call of list(), scroll(), or uniqueResult() on the new query
+               
+               staticQueryCache.put(key, q);
+            };
+            
+            query = Session.createQuery(c, fql);
          }
          
          // update [maxResults] and [startOffset] because the cached query might have set 
@@ -5979,6 +5980,7 @@
                   try
                   {
                      session.associate(record);
+                     record.refreshHydrator(session);
                   }
                   catch (StaleRecordException exc)
                   {
@@ -6142,7 +6144,7 @@
          try
          {
             closingSession = true;
-            
+            hydrateActiveRecords();
             if (isTransactionOpen())
             {
                if (LOG.isLoggable(Level.WARNING))
@@ -6221,6 +6223,24 @@
          }
       }
       
+      private void hydrateActiveRecords()
+      {
+         Iterator<RecordBuffer> iter = bufferManager.activeBuffers(Persistence.this);
+         
+         Record record;
+         
+         while (iter.hasNext())
+         {
+            record = iter.next().getCurrentRecord();
+            if (record.primaryKey() < 0)
+            {
+               continue;
+            }
+            
+            record.hydrateRemainingFields();
+         }
+      }
+      
       /**
        * Notify all registered session listeners that the current ORM session is about to
        * close or the current, implicit transaction is being committed.  Listeners which were set

=== modified file 'src/com/goldencode/p2j/persist/Record.java'
--- old/src/com/goldencode/p2j/persist/Record.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/Record.java	2026-05-22 13:59:11 +0000
@@ -2,7 +2,7 @@
 ** Module   : Record.java
 ** Abstract : The abstract base class for all business data model object classes.
 **
-** Copyright (c) 2019-2026, Golden Code Development Corporation.
+** Copyright (c) 2019-2025, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
 ** 001 ECF 20191001 Created initial version with basic data and accessors.
@@ -35,7 +35,6 @@
 ** 012 AI  20250625 Added overload of getPropertyValues(boolean).
 ** 013 LS  20250523 Changed getPropertyValues() to set the key of the map in a more consistent way for
 **                  denormalized extents.
-** 014 RNC 20260401 Migrate RECID from 32-bit to 64-bit values.
 */
 
 /*
@@ -102,6 +101,7 @@
 import java.util.*;
 import java.util.function.*;
 
+import com.goldencode.p2j.jmx.*;
 import com.goldencode.p2j.oo.lang.BaseObject;
 import com.goldencode.p2j.persist.orm.*;
 import com.goldencode.p2j.util.*;
@@ -115,6 +115,21 @@
 extends BaseRecord
 implements PropertiesDescriptor
 {
+   private static final SimpleCounter HYDRATE_RECORDS_WHEN_SESSION_CLOSE =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentHydratedRecordsWhenSessionCloses);
+   
+   private static final SimpleCounter TOTAL_FIELDS =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentTotalFields);
+   
+   private static final SimpleCounter HYDRATE_FIELD_WHEN_SESSION_CLOSES =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentHydratedFieldsWhenSessionCloses);
+
+   private static final SimpleCounter HYDRATED_FIELDS_LZS =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentHydratedFieldsWithLZS);
+   
+   private static final SimpleCounter HYDRATED_FIELDS_LOADER =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentHydratedFieldsWithLoader);
+   
    /**
     * Default constructor needed by DMOs extending this class.
     */
@@ -163,7 +178,7 @@
    {
       checkIncomplete(offset);
 
-      Integer datum = (Integer) data[offset];
+      Integer datum = (Integer) getDatum(offset);
       
       return datum != null ? new integer(datum) : new integer();
    }
@@ -272,7 +287,7 @@
    {
       checkIncomplete(offset);
 
-      OffsetDateTime datum = (OffsetDateTime) data[offset];
+      OffsetDateTime datum = (OffsetDateTime) getDatum(offset);
       
       return datum != null ? new datetimetz(datum) : new datetimetz();
    }
@@ -362,6 +377,36 @@
       }
    }
    
+   @Override
+   protected void countHydrateRecordWhenSessionCloses(long count)
+   {
+      HYDRATE_RECORDS_WHEN_SESSION_CLOSE.update(count);
+   }
+   
+   @Override
+   protected void countTotalFields(long count)
+   {
+      TOTAL_FIELDS.update(count);
+   }
+   
+   @Override
+   protected void countHydratedFieldsWithLoader(long count)
+   {
+      HYDRATED_FIELDS_LOADER.update(count);
+   }
+   
+   @Override
+   protected void countHydratedFieldsWithLZS(long count)
+   {
+      HYDRATED_FIELDS_LZS.update(count);
+   }
+   
+   @Override
+   protected void countHydratedFieldWhenSessionCloses(long count)
+   {
+      HYDRATE_FIELD_WHEN_SESSION_CLOSES.update(count);
+   }
+   
    /**
     * Get an {@code datetime} value for the datum at the given offset in the property array.
     * <p>
@@ -378,7 +423,7 @@
    {
       checkIncomplete(offset);
 
-      Timestamp datum = (Timestamp) data[offset];
+      Timestamp datum = (Timestamp) getDatum(offset);
       
       return datum != null ? new datetime(datum) : new datetime();
    }
@@ -483,7 +528,7 @@
    {
       checkIncomplete(offset);
 
-      Date datum = (Date) data[offset];
+      Date datum = (Date) getDatum(offset);
       
       return datum != null ? new date(datum) : new date();
    }
@@ -588,7 +633,7 @@
    {
       checkIncomplete(offset);
 
-      Boolean datum = (Boolean) data[offset];
+      Boolean datum = (Boolean) getDatum(offset);
       
       return datum != null ? new logical(datum) : new logical();
    }
@@ -691,7 +736,9 @@
     */
    public final int64 _getInt64(int offset)
    {
-      Long datum = (Long) data[offset];
+      checkIncomplete(offset);
+      
+      Long datum = (Long) getDatum(offset);
       
       return datum != null ? new int64(datum) : new int64();
    }
@@ -800,7 +847,7 @@
    {
       checkIncomplete(offset);
 
-      BigDecimal datum = (BigDecimal) data[offset];
+      BigDecimal datum = (BigDecimal) getDatum(offset);
       
       return datum != null ? new decimal(datum) : new decimal();
    }
@@ -917,8 +964,8 @@
    public final character _getCharacter(int offset)
    {
       checkIncomplete(offset);
-
-      String datum = (String) data[offset];
+      
+      String datum = (String) getDatum(offset);
       
       if (datum != null)
       {
@@ -1030,7 +1077,7 @@
    {
       checkIncomplete(offset);
 
-      Long datum = (Long) data[offset];
+      Long datum = (Long) getDatum(offset);
       
       return datum != null ? new rowid(datum) : new rowid();
    }
@@ -1137,7 +1184,7 @@
    {
       checkIncomplete(offset);
 
-      Long datum = (Long) data[offset];
+      Long datum = (Long) getDatum(offset);
       
       return datum != null ? handle.fromResourceId(datum, false) : new handle();
    }
@@ -1244,7 +1291,7 @@
    {
       checkIncomplete(offset);
 
-      Long datum = (Long) data[offset];
+      Integer datum = (Integer) getDatum(offset);
       
       return datum != null ? new recid(datum) : new recid();
    }
@@ -1263,10 +1310,10 @@
     */
    protected final void _setRecid(int offset, NumberType w)
    {
-      Long datum = null;
+      Integer datum = null;
       if (w != null && !w.isUnknown())
       {
-         datum = w.longValue();
+         datum = w.intValue();
       }
       
       setDatum(offset, datum);
@@ -1350,7 +1397,7 @@
    {
       checkIncomplete(offset);
 
-      byte[] datum = (byte[]) data[offset];
+      byte[] datum = (byte[]) getDatum(offset);
       
       return datum != null ? new blob(datum) : new blob();
    }
@@ -1457,7 +1504,7 @@
    {
       checkIncomplete(offset);
 
-      return new clob((String) data[offset], getCodePage(offset));
+      return new clob((String) getDatum(offset), getCodePage(offset));
    }
    
    /**
@@ -1560,7 +1607,7 @@
    {
       checkIncomplete(offset);
 
-      String datum = (String) data[offset];
+      String datum = (String) getDatum(offset);
       
       return datum != null ? comhandle.fromString(datum) : new comhandle();
    }
@@ -1665,7 +1712,7 @@
    {
       checkIncomplete(offset);
 
-      Long datum = (Long) data[offset];
+      Long datum = (Long) getDatum(offset);
       
       // the instance must be typed to Progress.Lang.Object
       object<?> res = new object<>(BaseObject.class);
@@ -1781,7 +1828,7 @@
    {
       checkIncomplete(offset);
 
-      byte[] datum = (byte[]) data[offset];
+      byte[] datum = (byte[]) getDatum(offset);
       
       return datum != null ? new raw(datum) : raw.instantiateUnknownRaw();
    }
@@ -1956,7 +2003,7 @@
       RecordMeta meta = _recordMeta();
       if (oneLine)
       {
-         int last = data.length;
+         int last = getLength();
          int start = (this instanceof TempTableRecord) ? 5 : 0;
          sb.append(meta.legacyName).append('/').append(meta.tables[0]).append(':').append(id).append(" {");
          for (int i = start; i < last; i++)
@@ -1966,7 +2013,7 @@
                sb.append(", ");
             }
             
-            boolean isChar = data[i] instanceof String;
+            boolean isChar = getSimpleDatum(i) instanceof String;
             if (isChar)
             {
                sb.append("\"");
@@ -1974,7 +2021,8 @@
             
             boolean incomplete = readFields != null && !readFields.get(i);
             
-            sb.append(data[i] == null ? incomplete ? "n/a" : "?" : data[i]);
+            Object datum = getSimpleDatum(i);
+            sb.append(datum == null ? incomplete ? "n/a" : "?" : datum);
             if (isChar)
             {
                sb.append("\"");
@@ -1997,14 +2045,14 @@
             int extent = pms[i].getExtent();
             if (extent == 0)
             {
-               sb.append(data[i]);
+               sb.append(getSimpleDatum(i));
             }
             else
             {
                for (int k = 0; k < extent; k++)
                {
                   sb.append(k == 0 ? "[" : "");
-                  sb.append(data[i + k]);
+                  sb.append(getSimpleDatum(i + k));
                }
                
                i += extent - 1;
@@ -2042,6 +2090,7 @@
          Object val = properties.get(prop.getLegacyName());
          val = f.apply(prop, val);
 
+         Object[] data = getData();
          if (prop.getExtent() > 0)
          {
             for (int j = 0; j < prop.getExtent(); j++, i++)
@@ -2050,11 +2099,13 @@
                
                BaseDataType fval = (BaseDataType) Array.get(val, j);
                prop.getDataHandler().setField(data, fval, i, prop);
+               setLivePropsAtOffset(i);
             }
          }
          else
          {
             prop.getDataHandler().setField(data, (BaseDataType) val, i, prop);
+            setLivePropsAtOffset(i);
          }
       }
    }
@@ -2127,23 +2178,6 @@
    }
    
    /**
-    * Check if two {@link Record} objects are equal from the point of view of the {@code data} array.
-    * <p>
-    * <strong>To be noted</strong>, that this method accesses the {@code data} arrays directly without using 
-    * any getter. This provides faster access, however should be changed if not all fields are hydrated and
-    * access is mandatory from a getter.
-    * 
-    * @param  otherRecord
-    *         The instance to compare against.
-    *        
-    * @return True if the {@code data} arrays are equal, false - otherwise
-    */
-   public boolean equalData(Record otherRecord)
-   {
-      return Arrays.equals(this.data, otherRecord.data);
-   }
-   
-   /**
     * Converts this {@code Record} into a POJO, if POJO class is available.
     * To be implemented by DMO implementations.
     * 

=== modified file 'src/com/goldencode/p2j/persist/RecordBuffer.java'
--- old/src/com/goldencode/p2j/persist/RecordBuffer.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/RecordBuffer.java	2026-05-22 13:57:10 +0000
@@ -1437,8 +1437,7 @@
 ** 412 SP  20251029          Refactor propsMap to use destination as key and source as value.
 ** 413 TG  20250925          Avoid openScope call for the same buffer in the current scope.
 ** 414 RNC 20251118          Log DirectAccessException in 'setCurrentRecord()' only if it is not expected.
-** 415 AS  20251210          When -rereadfields is active, fully hydrate a record instaead of throwing 8826 error.
-** 416 RNC 20260401          Migrate RECID from 32-bit to 64-bit values.
+** 415 AS  20251210          When -rereadfields is active, fully hydrate a record instaead of throwing 8826 error. 
 */
 
 /*
@@ -7181,16 +7180,41 @@
     * Get the record ID of the current buffer's current record, if any.  P2J
     * implementation of the RECID function.
     * <p>
-    * This method will return a 64-bit recid which represents the internal
-    * row ID of the given record.
+    * This method will return a 32-bit recid which represents the internal
+    * row ID of the given record.  If the actual row ID (which is implemented
+    * as a wider integer) cannot fit due to overflow, an error condition will
+    * be raised.
     *
     * @return  Record ID of the underlying data record. If no record currently
     *          backs the buffer associated with <code>dmo</code>, a recid
     *          initialized as unknown value is returned.
+    * 
+    * @throws  ErrorConditionException
+    *          if the actual record ID is too wide to fit within a 32-bit
+    *          integer.
     */
    public recid recordID()
    {
-      return isAvailable() ? new recid(getCurrentRecord().primaryKey()) : new recid();
+      if (isAvailable())
+      {
+         Record record = getCurrentRecord();
+         long id = record.primaryKey();
+         if (id < Integer.MIN_VALUE || id > Integer.MAX_VALUE)
+         {
+            // TODO: don't know what 4GL's overflow behavior is or whether the corresponding
+            // error is more serious than an ERROR condition; internally, we support a 64-bit
+            // value, but the recid object follows the 32-bit constraint in terms of not
+            // formatting this
+            txHelper.errHlp.noRecordOrThrowError(
+               "Row id of " + id + " is too wide to fit within a 32-bit record ID");
+         }
+         
+         return new recid(id);
+      }
+      else
+      {
+         return new recid();
+      }
    }
    
    /**

=== modified file 'src/com/goldencode/p2j/persist/TempRecord.java'
--- old/src/com/goldencode/p2j/persist/TempRecord.java	2024-10-21 07:34:01 +0000
+++ new/src/com/goldencode/p2j/persist/TempRecord.java	2026-05-22 13:58:35 +0000
@@ -17,6 +17,7 @@
 ** 004 CA  20240918 Removed default methods from interfaces and moved them as concrete implementations, as
 **                  default methods cause the Java metaspace to spike one order of magnitude.
 ** 005 AS  20241009 Refactored activeOffset into activeOffsets(Bitset).
+** 006 AL2 20231102 Added computeIndexForHydrator for lazy hydration.
 */
 
 /*
@@ -74,6 +75,7 @@
 
 package com.goldencode.p2j.persist;
 
+import com.goldencode.p2j.jmx.*;
 import com.goldencode.p2j.persist.orm.*;
 import com.goldencode.p2j.util.object;
 
@@ -87,6 +89,21 @@
 extends Record
 implements TempTableRecord
 {
+   private static final SimpleCounter HYDRATE_RECORDS_WHEN_SESSION_CLOSE =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.TempHydratedRecordsWhenSessionCloses);
+   
+   private static final SimpleCounter TOTAL_FIELDS =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.TempTotalFields);
+   
+   private static final SimpleCounter HYDRATE_FIELD_WHEN_SESSION_CLOSES =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.TempHydratedFieldsWhenSessionCloses);
+   
+   private static final SimpleCounter HYDRATED_FIELDS_LZS =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.TempHydratedFieldsWithLZS);
+   
+   private static final SimpleCounter HYDRATED_FIELDS_LOADER =
+      SimpleCounter.getInstance(FwdServerJMX.Counter.TempHydratedFieldsWithLoader);
+   
    /** The property/column name of "__error-flag__" field. */
    public static final String _ERROR_FLAG = "_errorFlag";
    
@@ -197,7 +214,7 @@
    @Override
    public final Long _peerRowid()
    {
-      return (Long) data[_PEER_ROWID_DATA_INDEX];
+      return (Long) getDatum(_PEER_ROWID_DATA_INDEX);
    }
    
    /**
@@ -237,7 +254,7 @@
    @Override
    public final Integer _rowState()
    {
-      return (Integer) data[_ROW_STATE_DATA_INDEX];
+      return (Integer) getDatum(_ROW_STATE_DATA_INDEX);
    }
    
    /**
@@ -278,7 +295,7 @@
    @Override
    public final Long _originRowid()
    {
-      return (Long) data[_ORIGIN_ROWID_DATA_INDEX];
+      return (Long) getDatum(_ORIGIN_ROWID_DATA_INDEX);
    }
    
    /**
@@ -318,7 +335,7 @@
    @Override
    public final Long _datasourceRowid()
    {
-      return (Long) data[_DATASOURCE_ROWID_INDEX];
+      return (Long) getDatum(_DATASOURCE_ROWID_INDEX);
    }
    
    /**
@@ -356,7 +373,7 @@
    @Override
    public final Integer _errorFlags()
    {
-      return (Integer) data[_ERROR_FLAG_DATA_INDEX];
+      return (Integer) getDatum(_ERROR_FLAG_DATA_INDEX);
    }
    
    /**
@@ -392,7 +409,7 @@
    @Override
    public final String _errorString()
    {
-      return (String) data[_ERROR_STRING_DATA_INDEX];
+      return (String) getDatum(_ERROR_STRING_DATA_INDEX);
    }
    
    /**
@@ -507,4 +524,40 @@
       Class<?> implIFace = _recordMeta().getDmoMeta().getAnnotatedInterface();
       TemporaryBuffer.updateObjectRefCount(implIFace, oldVal, newVal);
    }
+   
+   @Override
+   protected int getHydratorRowOffset()
+   {
+      return 2;
+   }
+   
+   @Override
+   protected void countHydrateRecordWhenSessionCloses(long count)
+   {
+      HYDRATE_RECORDS_WHEN_SESSION_CLOSE.update(count);
+   }
+   
+   @Override
+   protected void countTotalFields(long count)
+   {
+      TOTAL_FIELDS.update(count);
+   }
+   
+   @Override
+   protected void countHydratedFieldsWithLZS(long count)
+   {
+      HYDRATED_FIELDS_LZS.update(count);
+   }
+   
+   @Override
+   protected void countHydratedFieldsWithLoader(long count)
+   {
+      HYDRATED_FIELDS_LOADER.update(count);
+   }
+   
+   @Override
+   protected void countHydratedFieldWhenSessionCloses(long count)
+   {
+      HYDRATE_FIELD_WHEN_SESSION_CLOSES.update(count);
+   }
 }

=== modified file 'src/com/goldencode/p2j/persist/dialect/Dialect.java'
--- old/src/com/goldencode/p2j/persist/dialect/Dialect.java	2026-04-15 05:49:39 +0000
+++ new/src/com/goldencode/p2j/persist/dialect/Dialect.java	2026-05-22 13:58:35 +0000
@@ -605,8 +605,8 @@
    }
    
    /**
-    * Create the error descriptor from the SQLException message.
-    *
+    * Create the error descriptor from the {@code SQLException} message.
+    * 
     * @param   message
     *          The SQLException message.
     *
@@ -620,7 +620,7 @@
    /**
     * Check if Common Table Expressions should be used for converted CONTAINS operator
     * 
-    * @return <code>true</code> if Common Table Expressions should be used for converted CONTAINS operator
+    * @return  <code>true</code> if Common Table Expressions should be used for converted CONTAINS operator
     */
    public boolean useCTE4Contains()
    {
@@ -718,7 +718,7 @@
    public abstract void generateWordTablesDDLImpl(String dbName,
                                                   PrintStream out,
                                                   String eoln,
-                                                  Collection<WordTable>  wordTables);
+                                                  Collection<WordTable> wordTables);
    
    /**
     * Create a data object, the purpose of which is specific to a particular
@@ -2189,6 +2189,17 @@
    {
       return null;
    }
+
+   /**
+    * Indicate whether this dialect is likely to benefit from lazy hydration of DMOs when reading a record
+    * from the database. This serves as a hint, but may be overridden by configuration or runtime conditions.
+    * 
+    * @return  {@code true} unless overridden by subclasses.
+    */
+   public boolean useLazyHydration()
+   {
+      return true;
+   }
    
    /**
     * Use the provided database connection to extract the collation used by the database and report it

=== modified file 'src/com/goldencode/p2j/persist/dialect/H2Helper.java'
--- old/src/com/goldencode/p2j/persist/dialect/H2Helper.java	2024-11-08 10:44:59 +0000
+++ new/src/com/goldencode/p2j/persist/dialect/H2Helper.java	2026-05-22 13:58:35 +0000
@@ -129,6 +129,7 @@
 import com.goldencode.p2j.directory.*;
 import com.goldencode.p2j.persist.*;
 import com.goldencode.p2j.persist.Database;
+import com.goldencode.p2j.persist.dialect.*;
 import com.goldencode.p2j.persist.orm.*;
 import com.goldencode.p2j.persist.orm.Session;
 import com.goldencode.p2j.persist.pl.*;

=== modified file 'src/com/goldencode/p2j/persist/dialect/P2JH2Dialect.java'
--- old/src/com/goldencode/p2j/persist/dialect/P2JH2Dialect.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/dialect/P2JH2Dialect.java	2026-05-22 13:57:10 +0000
@@ -145,8 +145,7 @@
 ** 054 OM  20250701          Multiple signatures changes in super class.
 ** 055 OM  20250718          Added new method: isSequenceMissing(Throwable).
 ** 056 OM  20250731          Dropped dead code related to old technology no longer in use.
-**     ES  20250805          Inject computed __null column in orderBy clause.
-** 057 RNC 20260401          Migrate RECID from 32-bit to 64-bit values.
+**     ES  20250805          Inject computed __null column in orderBy clause. 
 */
 
 /*
@@ -258,7 +257,7 @@
    {
       fwd2sql.put("integer",    "integer");
       fwd2sql.put("Integer",    "integer");
-      fwd2sql.put("recid",      "bigint");
+      fwd2sql.put("recid",      "integer");
       fwd2sql.put("int64",      "bigint");
       fwd2sql.put("Long",       "bigint");
       fwd2sql.put("rowid",      "bigint");

=== modified file 'src/com/goldencode/p2j/persist/dialect/P2JPostgreSQLDialect.java'
--- old/src/com/goldencode/p2j/persist/dialect/P2JPostgreSQLDialect.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/dialect/P2JPostgreSQLDialect.java	2026-05-22 13:57:10 +0000
@@ -148,7 +148,6 @@
 ** 061 OM  20250718          Added new method: isSequenceMissing(Throwable).
 ** 062 OM  20250731          Dropped dead code related to old technology no longer in use.
 ** 063 OM  20250922          Simplified MSSQL indexes.
-** 064 RNC 20260401          Migrate RECID from 32-bit to 64-bit values.
 */
 
 /*
@@ -396,7 +395,7 @@
    {
       fwd2sql.put("integer",    "int4");
       fwd2sql.put("Integer",    "int4");
-      fwd2sql.put("recid",      "int8");
+      fwd2sql.put("recid",      "int4");
       fwd2sql.put("int64",      "int8");
       fwd2sql.put("Long",       "int8");
       fwd2sql.put("rowid",      "int8");

=== modified file 'src/com/goldencode/p2j/persist/dialect/P2JSQLServer2012Dialect.java'
--- old/src/com/goldencode/p2j/persist/dialect/P2JSQLServer2012Dialect.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/dialect/P2JSQLServer2012Dialect.java	2026-05-22 13:57:10 +0000
@@ -33,7 +33,6 @@
 ** 013 ES  20250827 Make direction of the primary key to always use ascendent direction.
 ** 014 OM  20250918 Droped NULL components from indexes. Added PK for unique indexes.
 ** 015 RNC 20260127 Added SQLSERVER_LIKE_PATTERN_OPERATORS.
-** 016 RNC 20260401 Migrate RECID from 32-bit to 64-bit values.
 */
 
 /*
@@ -239,7 +238,7 @@
       indexFieldSizes.put(integer.class,     4); // int
       indexFieldSizes.put(logical.class,     1); // bit
       indexFieldSizes.put(int64.class,       8); // bigint
-      indexFieldSizes.put(recid.class,       8); // bigint
+      indexFieldSizes.put(recid.class,       4); // int
       indexFieldSizes.put(datetimetz.class, 10); // datetimeoffset
       indexFieldSizes.put(datetime.class,    8); // datetime2
       indexFieldSizes.put(date.class,        3); // date
@@ -253,7 +252,7 @@
       
       fwd2sql.put("integer",    "int");
       fwd2sql.put("Integer",    "int");
-      fwd2sql.put("recid",      "bigint");
+      fwd2sql.put("recid",      "int");
       fwd2sql.put("int64",      "bigint");
       fwd2sql.put("Long",       "bigint");
       fwd2sql.put("rowid",      "bigint");

=== modified file 'src/com/goldencode/p2j/persist/dirty/DefaultDirtyShareManager.java'
--- old/src/com/goldencode/p2j/persist/dirty/DefaultDirtyShareManager.java	2026-04-22 06:58:45 +0000
+++ new/src/com/goldencode/p2j/persist/dirty/DefaultDirtyShareManager.java	2026-05-22 13:58:35 +0000
@@ -834,8 +834,7 @@
                indexUpdateStates.remove(ident);
             }
             
-            String hql = "delete from " + entity + 
-                         " where " + DatabaseManager.PRIMARY_KEY + " = ?";
+            String fql = "delete from " + entity + " where " + Session.PK + " = ?";
             Session session = null;
             boolean inTx = false;
             
@@ -843,7 +842,7 @@
             {
                session = new Session(dirtyDatabase);
                inTx = session.beginTransaction();
-               Query query = Session.createQuery(hql);
+               Query query = Session.createQuery(fql);
                query.setParameter(0, id);
                query.executeUpdate(session);
                session.commit();
@@ -1223,7 +1222,7 @@
    }
    
    /**
-    * Execute an HQL query and return the results as a list. If the query returns multiple results
+    * Execute an FQL query and return the results as a list. If the query returns multiple results
     * per row, each element in the list will be an array of {@code Object}s.
     * <p>
     * No locking is attempted.

=== modified file 'src/com/goldencode/p2j/persist/hql/DataTypeHelper.java'
--- old/src/com/goldencode/p2j/persist/hql/DataTypeHelper.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/hql/DataTypeHelper.java	2026-05-22 13:57:10 +0000
@@ -45,7 +45,6 @@
 ** 026 SP  20240516          Process the function name in getTypeFunction() when there is an underscore
 ** 027 CA  20241107          Added character constant support.
 ** 028 ICP 20250123          Added support for integer and int64 constants.
-** 029 RNC 20260401          Migrate RECID from 32-bit to 64-bit values.
 */
 
 /*
@@ -561,7 +560,7 @@
       // P2J wrapper types.
       classes.put(logical.class    , FqlType.BOOLEAN);
       classes.put(integer.class    , FqlType.INTEGER);
-      classes.put(recid.class      , FqlType.LONG);
+      classes.put(recid.class      , FqlType.INTEGER);
       classes.put(decimal.class    , FqlType.DECIMAL);
       classes.put(date.class       , FqlType.DATE);
       classes.put(raw.class        , FqlType.RAW);

=== modified file 'src/com/goldencode/p2j/persist/meta/MetadataManager.java'
--- old/src/com/goldencode/p2j/persist/meta/MetadataManager.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/meta/MetadataManager.java	2026-05-22 13:57:10 +0000
@@ -73,7 +73,6 @@
 ** 032 OM  20250331 Make sure the [byName] map's keys are in lowercase. Avoid possible duplication of default
 **                  tenant record in _tenant meta table.
 ** 033 SP  20250321 Removed support for custom denormalized extents.
-** 034 RNC 20260401 Migrate RECID from 32-bit to 64-bit values.
 */
 
 /*
@@ -215,7 +214,7 @@
    private static final boolean LOG_INFO = log.isLoggable(Level.INFO);
    
    /** The recid of the database in {@code _Db} table. */
-   private static final long DB_RECID = 4032;
+   private static final int DB_RECID = 4032;
    
    /** Map of data type names to associated _Field._dtype value */
    private static final Map<String, Integer> DTYPE = Collections.unmodifiableMap(
@@ -252,7 +251,7 @@
             put("logical", "bit");
             put("integer", "integer");
             put("decimal", "numetic");
-            put("recid", "bigint");
+            put("recid", "integer");
             put("raw", "varbinary");
             put("blob", "varbinary");
             put("clob", "varchar");
@@ -825,27 +824,25 @@
          // extract the list of indices of this table ...
          PreparedStatement stmt = conn.prepareStatement(
                "select " + Session.PK + " from meta_index where file_recid=?");
-         stmt.setLong(1, fileNum2);
+         stmt.setLong(1, (long) fileNum2);
          ResultSet rs = stmt.executeQuery();
          while (rs.next())
          {
             // ... and drop their index components
-            deleteRecord(conn,
-                         rs.getLong(1),
-                         "delete from meta_index_field where index_recid=?",
-                         "index components");
+            deleteRecord(
+                  conn, rs.getInt(1), "delete from meta_index_field where index_recid=?", "index components");
          }
          rs.close();
          stmt.close();
          
          // then, delete them from _Index
-         deleteRecord(conn, fileNum2, "delete from meta_index where file_recid=?", "indices");
+         deleteRecord(conn, (long) fileNum2, "delete from meta_index where file_recid=?", "indices");
          
          // next, delete from _Field
-         deleteRecord(conn, fileNum2, "delete from meta_field where file_recid=?", "fields");
+         deleteRecord(conn, (long) fileNum2, "delete from meta_field where file_recid=?", "fields");
          
          // finally, delete from _File
-         deleteRecord(conn, fileNum2, "delete from meta_file where " + Session.PK + "=?", "tables");
+         deleteRecord(conn, (long) fileNum2, "delete from meta_file where " + Session.PK + "=?", "tables");
          
          if (inTx)
          {
@@ -999,22 +996,22 @@
       }
       
       long fileNum = nextId(meta, SystemTable._File.fileName);
-      int64 pk = new int64();
+      integer pk = new integer();
       Map<SystemTable, List<Map<Tuple<String, Integer>, Object>>> children =
-            childData(meta, dmoIface, fileNum, pk);
+            childData(meta, dmoIface, (int)fileNum, pk);
       setMetaTableRecord(p, fileHelper,
             new FileTableRecordBuilder()
                   .put(Session.PK, fileNum)
                   .put("fileNumber", integer.of(fileNum))
-                  .put("template", int64.of(-fileNum))
+                  .put("template", integer.of(-fileNum))
                   .put("tblType", character.of("T"))
                   .build(dmoIface, pk, children)
       );
       templateRecords.put(dmoImpl, -fileNum);
       FILE_NUM.computeIfAbsent(primary, d -> new ConcurrentHashMap<>()).
-            putIfAbsent(dmoImpl, integer.of((int) fileNum));
+            putIfAbsent(dmoImpl, new integer(fileNum));
       FILE_NUM_BY_TBL_NAME.computeIfAbsent(primary, d -> new ConcurrentHashMap<>()).
-            putIfAbsent(dmoIface.getAnnotation(Table.class).name(), (int) fileNum);
+            putIfAbsent(dmoIface.getAnnotation(Table.class).name(), (int)fileNum);
       saveChildren(p, children);
       
       if ((isDynamic && LOG_INFO) || log.isLoggable(Level.FINE))
@@ -1081,16 +1078,16 @@
          return false;
       }
       
-      int64 pk = new int64();
+      integer pk = new integer();
       Map<SystemTable, List<Map<Tuple<String, Integer>, Object>>> children = childData(
             metaDb, dmoIface, systemTable.fileNum, pk);
       long id = nextId(metaDb, SystemTable._File.fileName);
-
+      
       setMetaTableRecord(p, fileHelper,
             new FileTableRecordBuilder()
                   .put(Session.PK, id)
-                  .put("fileNumber", integer.of(systemTable.fileNum))
-                  .put("template", int64.of(-id))
+                  .put("fileNumber", new integer(systemTable.fileNum))
+                  .put("template", new integer(-id))
                   .put("tblType", new character("S"))
                   .build(dmoIface, pk, children)
       );
@@ -1188,13 +1185,13 @@
                   new MapBuilder<>().
                      put(Session.PK, nextId).
                      put("seqName", new character(seq.legacy())).
-                     put("seqInit", int64.of(seq.start())).
-                     put("seqIncr", int64.of(seq.increment())).
-                     put("seqMin", int64.of(seq.minValue())).
-                     put("seqMax",int64.of(seq.maxValue())).
+                     put("seqInit", new int64(seq.start())).
+                     put("seqIncr", new int64(seq.increment())).
+                     put("seqMin", new int64(seq.minValue())).
+                     put("seqMax",new int64(seq.maxValue())).
                      put("cycleOk",new logical(seq.cycle())).
                      put("dbRecid",new recid(nextId)).
-                     build()
+                  build()
             );
          }
       } catch (NoSuchMethodException | SecurityException | 
@@ -1281,7 +1278,7 @@
       
       setMetaTableRecord(p, h,
             new MapBuilder<>()
-                  .put(Session.PK, DB_RECID)
+                  .put(Session.PK, (long) DB_RECID)
                   .put("dbName",  new character("_meta"))
                   .put("dbType",  new character(""))
                   .put("dbComm",  new character(""))
@@ -1910,8 +1907,8 @@
    private static Map<SystemTable, List<Map<Tuple<String, Integer>, Object>>> childData(
              Database metaDb,
              Class<? extends DataModelObject> dmoIface,
-             long _file_num,
-             int64 pk)
+             int _file_num,
+             integer pk)
    {
       Map<SystemTable, List<Map<Tuple<String, Integer>, Object>>> children = new EnumMap<>(SystemTable.class);
       List<Map<Tuple<String, Integer>, Object>> fields = new LinkedList<>();
@@ -1919,7 +1916,7 @@
       List<Map<Tuple<String, Integer>, Object>> indexFields = new LinkedList<>();
       List<Map<Tuple<String, Integer>, Object>> fileTriggers = new LinkedList<>();
       List<Map<Tuple<String, Integer>, Object>> fieldTriggers = new LinkedList<>();
-      Map<String, int64> fieldRecidByName = new LinkedHashMap<>();
+      Map<String, integer> fieldRecidByName = new LinkedHashMap<>();
       Map<String, integer> fieldRposByName = new LinkedHashMap<>();
       children.put(SystemTable._Field, fields);
       children.put(SystemTable._Index, indexes);
@@ -1946,7 +1943,7 @@
          String field = trigger.field();
          if (!field.isEmpty())
          {
-            int64 fieldRecId = fieldRecidByName.get(field);
+            integer fieldRecId = fieldRecidByName.get(field);
             integer fieldRpos = fieldRposByName.get(field);
             if (fieldRecId == null || fieldRpos == null)
             {
@@ -1962,7 +1959,7 @@
                   new MapBuilder<>().
                   put(Session.PK, nextId(metaDb, SystemTable._Field_Trig.fileName)).
                   put("fieldRecid", fieldRecId).
-                  put("fileRecid", int64.of(_file_num)).
+                  put("fileRecid", integer.of(_file_num)).
                   put("fieldRpos", fieldRpos).
                   put("event", new character(trigger.event())).
                   put("procName", new character(trigger.procedure())).
@@ -1975,7 +1972,7 @@
             fileTriggers.add(
                   new MapBuilder<>().
                   put(Session.PK, nextId(metaDb, SystemTable._File_Trig.fileName)).
-                  put("fileRecid", int64.of(_file_num)).
+                  put("fileRecid", integer.of(_file_num)).
                   put("event", new character(trigger.event())).
                   put("procName", new character(trigger.procedure())).
                   put("override", logical.of(trigger.overridable())).
@@ -2008,12 +2005,12 @@
     */
    private static void indexData(Database metaDb,
                                  String tblDmoName,
-                                 long _file_num,
+                                 int _file_num,
                                  Iterator<P2JIndex> indices,
-                                 int64 pk,
+                                 integer pk,
                                  List<Map<Tuple<String, Integer>, Object>> indexes,
                                  List<Map<Tuple<String, Integer>, Object>> indexFields,
-                                 Map<String, int64> fieldRecidByName)
+                                 Map<String, integer> fieldRecidByName)
    {
       while (indices.hasNext())
       {
@@ -2021,11 +2018,11 @@
          long id = nextId(metaDb, SystemTable._Index.fileName);
          if (idx.isPrimary())
          {
-            pk.assign(int64.of(id));
+            pk.assign(integer.of(id));
          }
          indexes.add(new MapBuilder<>().
                put(Session.PK, id).
-               put("fileRecid", int64.of(_file_num)).
+               put("fileRecid", integer.of(_file_num)).
                put("indexName", new character(idx.getName())).
                put("unique", logical.of(idx.isUnique())).
                build()
@@ -2036,7 +2033,7 @@
          {
             P2JIndexComponent fld = comps.get(i);
             String compName = fld.getPropertyName();
-            int64 fieldRecid = fieldRecidByName.get(compName);
+            integer fieldRecid = fieldRecidByName.get(compName);
             
             if (fieldRecid == null)
             {
@@ -2045,8 +2042,8 @@
             }
             indexFields.add(new MapBuilder<>().
                   put(Session.PK, nextId(metaDb, SystemTable._Index_Field.fileName)).
-                  put("indexRecid", int64.of(id)).
-                  put("fieldRecid", fieldRecidByName.getOrDefault(compName, int64.UNKNOWN)).
+                  put("indexRecid", integer.of(id)).
+                  put("fieldRecid", fieldRecidByName.getOrDefault(compName, integer.UNKNOWN)).
                   put("indexSeq", integer.of(seqNo++)).
                   build()
             );
@@ -2072,14 +2069,14 @@
     * @return _Field table data for a DMO field.
     */
    private static Map<Tuple<String, Integer>, Object> fieldData(Database metaDb,
-                                                                long _file_num,
+                                                                int _file_num,
                                                                 Property property,
                                                                 Class<?> ftype,
-                                                                Map<String, int64> fieldRecidByName,
+                                                                Map<String, integer> fieldRecidByName,
                                                                 Map<String, integer> fieldRposByName)
    {
       long id = nextId(metaDb, SystemTable._Field.fileName);
-      fieldRecidByName.put(property.name, int64.of(id));
+      fieldRecidByName.put(property.name, new integer(id));
       fieldRposByName.put(property.name, integer.of(property.order));
       int  wd = property.width;
       integer width = wd >= 0 ? integer.of(wd) : integer.UNKNOWN;
@@ -2090,7 +2087,7 @@
       String collation =  charField ? (caseSensitive ? "basic_S" : "basic_I") : null;
       return new MapBuilder<>().
             put(Session.PK, id).
-            put("fileRecid", int64.of(_file_num)).
+            put("fileRecid", integer.of(_file_num)).
             put("fieldName", new character(property.legacy)).
             put("dataType", character.of(typeName)).
             put("mandatory", logical.of(property.mandatory)).
@@ -2739,13 +2736,13 @@
        */
       public Map<Tuple<String, Integer>, Object> build(
             Class<? extends DataModelObject> dmoIface,
-            int64 pk,
+            integer pk,
             Map<SystemTable, List<Map<Tuple<String, Integer>, Object>>> children)
       {
          Table table = dmoIface.getAnnotation(Table.class);
          int numfld = children.get(SystemTable._Field).size();
          
-         return put("dbRecid", int64.of(DB_RECID)).
+         return put("dbRecid", integer.of(DB_RECID)).
                 put("fileName", new character(table.legacy())).
                 put("owner", character.of("PUB")).
                 put("userMisc", new character(table.name())).

=== modified file 'src/com/goldencode/p2j/persist/orm/AbstractRowStructure.java'
--- old/src/com/goldencode/p2j/persist/orm/AbstractRowStructure.java	2025-03-24 08:47:18 +0000
+++ new/src/com/goldencode/p2j/persist/orm/AbstractRowStructure.java	2026-05-22 13:58:35 +0000
@@ -12,6 +12,7 @@
 ** 003 LS  20241105 Added hasOnlyPK().
 ** 004 OM  20240701 Replaced + concatenation with append in StringBuilder.
 **     SP  20250321 Removed usage of prop.index.
+** 005 AL2 20231214 Fixed hydrate extents to avoid honoring recOffset if set on -1.
 */
 
 /*
@@ -177,7 +178,8 @@
     * @throws  PersistenceException
     *          thrown when hydrating extents fails (as it requires extra SQL queries to be run).
     */
-   protected void hydrateExtents(Session session, BaseRecord r, int recOffset)
+   @Override
+   public void hydrateExtents(Session session, BaseRecord r, int recOffset)
    throws PersistenceException
    {
       // TODO: collect extent properties and do a new query from SQL, see Loader.readExtentData()
@@ -188,12 +190,12 @@
          PropertyMeta[] propsMeta = recordMeta.getPropertyMeta(false);
          String[] loadSqls = recordMeta.loadSql;
          int[] loadSqlsIndexes = recordMeta.loadSqlIndexes;
-         boolean incomplete = isIncomplete();
+         boolean arbitrary = isIncomplete() || recOffset == -1;
          
          // only use the extent queries, skip the loadSqls[0] which contains the simple properties
          for (int i = 1; i < loadSqls.length; i++) // skip "plain" columns 
          {            
-            if (incomplete)
+            if (arbitrary)
             {
                // resolve the offset in the 'props' array where this sql starts.  
                // all non-expanded extent fields are loaded even for incomplete records.

=== modified file 'src/com/goldencode/p2j/persist/orm/AdaptiveRowStructure.java'
--- old/src/com/goldencode/p2j/persist/orm/AdaptiveRowStructure.java	2025-08-14 09:57:08 +0000
+++ new/src/com/goldencode/p2j/persist/orm/AdaptiveRowStructure.java	2026-05-22 13:58:35 +0000
@@ -10,6 +10,7 @@
 **     AL2 20231212 Use dmoMeta directly, without getter.
 ** 002 OM  20240715 Fixed copy/paste bug.
 ** 003 LS  20250714 Added 'getNeededFields' implementation.
+** 004 AL2 20231214 Added implementation for hydrateAt.
 */
 
 /*
@@ -69,8 +70,7 @@
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.BitSet;
-import java.util.Iterator;
+import java.util.*;
 
 import com.goldencode.p2j.persist.*;
 
@@ -228,6 +228,50 @@
       return delegate.getNeededFields();
    }
 
+
+   /**
+    * Hydrate only one property in the base-record. This is largely used by the lazy hydrator,
+    * allowing it to hydrate only the property that is accessed now. The other hydration method 
+    * ({@link #hydrate}) eagerly hydrates all properties, is a slow process and may do useless
+    * hydrations on properties that are not accessed.
+    * 
+    * @param   resultSet
+    *          The ResultSet from which the hydration should be done
+    * @param   rsOffset
+    *          The result set offset from which the reading should be done. This is relevant as
+    *          the result set may contain a joined row, but we are interested only in a specific
+    *          record that may start at a specific offset.
+    * @param   r
+    *          The record that should be hydrated. 
+    * @param   propOffset
+    *          The property that should be hydrated. The caller is responsible in assuring that
+    *          this property wasn't hydrated before.
+    * 
+    * @return  {@code true} if this worked due to the fact that it was a scalar value. {@code false}
+    *          in case the property was a non-expanded 
+    *          
+    * @throws  PersistenceException
+    *          This is thrown when reading the scalar property fails.
+    */
+   @Override
+   public boolean hydrateAt(ResultSet resultSet, int rsOffset, BaseRecord r, int dataIndex)
+   throws PersistenceException
+   {
+      return delegate.hydrateAt(resultSet, rsOffset, r, dataIndex);
+   }
+   
+   /**
+    * Retrieve the fields that were loaded from the database.
+    *
+    * @return A BitSet object, where true means that the field associated with that position in the {@code
+    * data} array has been loaded from database, and false - otherwise.
+    */
+   @Override
+   public BitSet getLoadedFields()
+   {
+      return delegate.getLoadedFields();
+   }
+   
    /**
     * Invalidation logic that will pick up everything that was appended until now and moved into
     * an un-ordered implementation. This routine should be avoided as much as possible in the 

=== modified file 'src/com/goldencode/p2j/persist/orm/BaseRecord.java'
--- old/src/com/goldencode/p2j/persist/orm/BaseRecord.java	2025-11-28 08:30:04 +0000
+++ new/src/com/goldencode/p2j/persist/orm/BaseRecord.java	2026-05-22 13:58:35 +0000
@@ -2,7 +2,7 @@
 ** Module   : BaseRecord.java
 ** Abstract : The abstract base class for all business data model object classes.
 **
-** Copyright (c) 2019-2025, Golden Code Development Corporation.
+** Copyright (c) 2019-2026, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
 ** 001 ECF 20191001 Created initial version with basic data and accessors.
@@ -112,12 +112,12 @@
 
 package com.goldencode.p2j.persist.orm;
 
+import com.goldencode.p2j.jmx.*;
 import com.goldencode.p2j.persist.*;
 import com.goldencode.p2j.rest.serializers.*;
 import com.goldencode.p2j.schema.*;
 import com.goldencode.p2j.util.*;
 import com.goldencode.p2j.util.logging.*;
-
 import java.lang.reflect.*;
 import java.sql.*;
 import java.util.*;
@@ -162,9 +162,6 @@
    /** Surrogate primary key of the record */
    protected Long id = null;
    
-   /** The data represented by this record, in it's low-level (database friendly) form */
-   protected final Object[] data;
-   
    /** For an incomplete record, this holds the fields which were read or assigned in this record. */
    protected BitSet readFields = null;
    
@@ -178,6 +175,9 @@
     */
    protected long references = 0;
    
+   /** The data represented by this record, in it's low-level (database friendly) form */
+   private final Object[] data;
+   
    /** Map of properties touched, each bit corresponds with a property in the data array */
    private final BitSet dirtyProps;
    
@@ -193,6 +193,16 @@
    /** Record identifier (lock variant, which uses table name) for this record (created lazily) */
    private RecordIdentifier<String> recid = null;
    
+   /** Hydrator used to extract data from a result set positioned on a particular target row */
+   private Hydrator hydrator = null;
+   
+   /** Map of "live" properties (read or assigned), each bit corresponds with a property in the data array */
+   // TODO: resolve redundancy with readFields
+   private BitSet liveProps;
+   
+   /** The number of bits set in {@link #liveProps} */
+   private int livePropsCardinality;
+   
    /** The currently active buffer when state is changing */
    private RecordBuffer activeBuffer = null;
    
@@ -298,13 +308,16 @@
          }
          
          // TODO: LOB data is mutable, needs to be duplicated explicitly
-         boolean change = to.data[i] != from.data[i];
+         Object toDatum = to.getDatum(i);
+         Object fromDatum = from.getDatum(i);
+         boolean change = toDatum != fromDatum; 
+
          if (change)
          {
-            to.data[i] = from.data[i];
+            to.setSimpleDatum(i, fromDatum);
             
             // update the null property bitset and mark the property as changed to be able to save it
-            to.nullProps.set(i, to.data[i] == null);
+            to.nullProps.set(i, fromDatum == null);
             to.dirtyProps.set(i);
          }
       }
@@ -488,10 +501,63 @@
    public void copy(BaseRecord from)
    {
       // TODO: BLOB data is mutable, needs to be duplicated explicitly
-      int len = this.data.length;
+      int len = this.getLength();
       this.id = from.id;
-      System.arraycopy(from.data, 0, this.data, 0, len);
+      copyArray(from.getData());
       dirtyProps.set(0, len); // TODO: nullProps?
+      
+      hydrator = from.hydrator;
+      liveProps = from.liveProps != null ? (BitSet) from.liveProps.clone() : null;
+      livePropsCardinality = from.livePropsCardinality;
+   }
+   
+   /**
+    * Check if two {@link BaseRecord} objects are equal from the point of view of the {@code data} array.
+    * <p>
+    * <strong>To be noted</strong>, that this method accesses the {@code data} arrays directly without using 
+    * any getter. This provides faster access, however should be changed if not all fields are hydrated and
+    * access is mandatory from a getter.
+    *
+    * @param  otherRecord
+    *         The instance to compare against.
+    *
+    * @return True if the {@code data} arrays are equal, false - otherwise
+    */
+   public boolean equalData(BaseRecord otherRecord)
+   {
+      if (this.getLength() != otherRecord.getLength())
+      {
+         return false;
+      }
+      for  (int i = 0; i < this.getLength(); i++)
+      {
+         if (this.areAllPropertiesLive() && otherRecord.areAllPropertiesLive())
+         {
+            return Arrays.equals(this.getData(), otherRecord.getData());
+         }
+      }
+      
+      for  (int i = 0; i < this.getLength(); i++)
+      {
+         if (!Objects.equals(getDatum(i), otherRecord.getDatum(i)))
+         {
+            return false;
+         }
+      }
+      
+      return true;
+   }
+   
+   
+   /**
+    * Copy an array of objects into data.
+    * 
+    * @param   from
+    *          The source to copy from.
+    */
+   public void copyArray(Object[] from)
+   {
+      System.arraycopy(from, 0, data, 0, getLength());
    }
    
    /**
@@ -695,7 +761,7 @@
       //       previous state and roll this back as well
       for (int i = activeOffsets.nextSetBit(0); i != -1; i = activeOffsets.nextSetBit(i + 1))
       {
-         data[i] = activePropPreviousValues[i];
+         setSimpleDatum(i, activePropPreviousValues[i]);
       }
    }
    
@@ -728,12 +794,14 @@
       buf.append(':').append(primaryKey()).append(sep)
          .append(state).append(sep)
          .append("references: ").append(references).append(sep)
+         .append("read: ").append(readFields).append(sep)
+         .append("live: ").append(liveProps).append(sep)
          .append("dirty: ").append(dirtyProps).append(sep)
          .append("unvalidated: ").append(unvalidated);
       
       if (includeData)
       {
-         int len = data.length;
+         int len = getLength();
          buf.append(sep).append("data: {");
          for (int i = 0; i < len; i++)
          {
@@ -742,7 +810,7 @@
                buf.append(", ");
             }
             
-            buf.append(data[i]);
+            buf.append(getSimpleDatum(i));
          }
          
          buf.append('}');
@@ -772,7 +840,7 @@
    {
       Objects.requireNonNull(pm);
       
-      return data;
+      return getData();
    }
    
    /**
@@ -791,9 +859,29 @@
    public Object[] getData(RecordBuffer rb)
    {
       Objects.requireNonNull(rb);
-      
+
+      return getData();
+   }
+   
+   /**
+    * Get the data property.
+    * 
+    * @return   The value of the data property.
+    */
+   public Object[] getData()
+   {
       return data;
    }
+       
+   /**
+    * Get the size of the data property.
+    * 
+    * @return  Size of the data property.
+    */
+   public int getLength()
+   {
+      return data.length;
+   }
    
    /**
     * Indicate whether any of this record's validatable properties are dirty and have not been validated.
@@ -860,12 +948,14 @@
    public Object[] getActiveUpdateDiff(int ormIndex, int extentIndex)
    {
       int actualIndex = ormIndex + extentIndex;
-      if (dirtyProps.get(actualIndex) && !Objects.equals(activePropPreviousValues[actualIndex], 
-                                                        data[actualIndex]))
+      Object activeOffsetDatum = getDatum(actualIndex);
+      
+      if (dirtyProps.get(actualIndex) &&
+         !Objects.equals(activePropPreviousValues[actualIndex], activeOffsetDatum))
       {
          Object[] res = new Object[2];
          res[0] = activePropPreviousValues[actualIndex];
-         res[1] = data[actualIndex];
+         res[1] = activeOffsetDatum;
          return res;
       }
       return null;
@@ -890,8 +980,60 @@
    public BitSet _getReadFields()
    {
       return readFields;
-   }
-
+   }  
+   
+   /**
+    * This should be called when the record is re-attached to a new session. This way, we refresh the 
+    * underlying session in hydrator (if any) to properly refresh the record when needed. If this is 
+    * not used, we may face {@link LazyHydrationException} due to stale sessions in the hydrator. 
+    * 
+    * First time a hydrator is created, it will take the session from the generator result-set, thus,
+    * there is no need to call this method in that case.
+    * 
+    * @param   session
+    *          The session to which this record is re-attached.
+    */
+   public void refreshHydrator(Session session)
+   {
+      if (hydrator != null)
+      {
+         hydrator.refresh(session);
+      }
+   }
+   
+   /**
+    * Sets the bit in {@link BaseRecord#liveProps} to true, meaning that the field at that index has been 
+    * read or written.
+    * 
+    * @param offset
+    *        Zero-based offset of the datum in the data array, which corresponds to the index in 
+    *        {@link BaseRecord#liveProps}
+    */
+   protected void setLivePropsAtOffset(int offset)
+   {
+      if (liveProps != null)
+      {
+         if (!liveProps.get(offset))
+         {
+            livePropsCardinality++;
+         }
+         
+         liveProps.set(offset);
+      }
+   }
+   
+   protected void setAllLiveProps()
+   {
+      if (liveProps == null)
+      {
+         return;
+      }
+      for (int i = 0; i < data.length; i++)
+      {
+         liveProps.set(i);
+      }
+   }
+   
    /**
     * Set a single datum into the data array, and update the record and property state accordingly.
     * 
@@ -901,12 +1043,7 @@
     *          Value to be set into the data array.
     */
    protected void setDatum(int offset, Object datum)
-   {
-      if (readFields != null)
-      {
-         readFields.set(offset);
-      }
-
+   {      
       if (activeBuffer != null)
       {
          activeOffsets.set(offset);
@@ -915,15 +1052,22 @@
       // update the bitset of dirty (touched) properties
       dirtyProps.set(offset);
       
+      Object offsetDatum = getDatum(offset);
+      
       // temporarily save the old value (even if it is the same as the new value)
-      activePropPreviousValues[offset] = data[offset];
+      activePropPreviousValues[offset] = offsetDatum;
+
+      if (readFields != null)
+      {
+         readFields.set(offset);
+      }
       
       // for BigDecimal, equals checks the scale - we need compareTo, to check if the values are really the 
       // same, regardless of the scale value.
-      if (!(Objects.equals(data[offset], datum) || 
+      if (!(Objects.equals(offsetDatum, datum) || 
             (datum instanceof Comparable && 
-             data[offset] != null        && 
-             ((Comparable) datum).compareTo(data[offset]) == 0)))
+             offsetDatum != null        && 
+             ((Comparable) datum).compareTo(offsetDatum) == 0)))
       {
          // ensure we have an exclusive lock on the record
          if (!lockForUpdate())
@@ -942,7 +1086,7 @@
          }
          
          // update the value in the data array
-         data[offset] = datum;
+         setSimpleDatum(offset, datum);
       }
    }
    
@@ -980,8 +1124,7 @@
             continue;
          }
          
-         if (fromProp.getType()   != toProp.getType() || 
-             fromProp.getExtent() != toProp.getExtent())
+         if (fromProp.getType() != toProp.getType() || fromProp.getExtent() != toProp.getExtent())
          {
             return false;
          }
@@ -1012,10 +1155,11 @@
          
          // for BigDecimal, equals checks the scale - we need compareTo, to check if the values are really the 
          // same, regardless of the scale value.
-         if (!(Objects.equals(data[toOffset], datum) || 
+         Object offsetDatum = getDatum(toOffset);
+         if (!(Objects.equals(offsetDatum, datum) || 
               (datum instanceof Comparable && 
-               data[toOffset] != null && 
-               ((Comparable) datum).compareTo(data[toOffset]) == 0)))
+               offsetDatum != null && 
+               ((Comparable) datum).compareTo(offsetDatum) == 0)))
          {
             if (updatedOffsets == null)
             {
@@ -1056,25 +1200,83 @@
       {
          int offset = updatedOffsets.get(idx);
          Object datum = updatedDatums.get(idx);
-         data[offset] = datum; 
+         setSimpleDatum(offset, datum);
       }
       
       return true;
    }
    
    /**
-    * Get the field's value on the specified offset.
-    * 
-    * @param    offset
-    *           Zero-based offset of the datum in the data array.
-    * 
-    * @return   The field's value.
+    * Get the field's value at the specified offset, with {@code null} indicating unknown value.
+    * <p>
+    * N.B.: if lazy hydration can be active for this record, callers must catch and handle the unchecked
+    * {@code LazyHydrationException}.
+    * 
+    * @param   offset
+    *          Zero-based offset of the datum in the data array.
+    * 
+    * @return  The field's value, with {@code null} indicating unknown value.
+    * 
+    * @throws  LazyHydrationException
+    *          if this record is enabled for lazy hydration, but the attached hydrator was no longer valid.
+    *          Although this is an unchecked exception to allow the DMO to be generated with simple getter
+    *          methods, callers which invoke this method when lazy hydration could be active MUST catch and
+    *          handle this exception.
     */
    protected Object getDatum(int offset)
+   throws LazyHydrationException
    {
+      if (hydrator != null && liveProps != null && !liveProps.get(offset))
+      {
+         boolean success = hydrator.hydrate(this, offset, getHydratorRowOffset(), true);
+         if (success)
+         {
+            countHydratedFieldsWithLZS(1);
+         }
+         if (!success)
+         {
+            BitSet unhydratedFields = getUnhydratedFields();
+            hydrator.refresh(this, id, unhydratedFields);
+            countHydratedFieldsWithLoader(unhydratedFields.cardinality());
+         }
+         if (!success || livePropsCardinality == data.length)
+         {
+            hydrator = null;
+            liveProps = null;
+            livePropsCardinality = data.length;
+         }
+      }
+      
       return data[offset];
    }
    
+   public void hydrateRemainingFields()
+   {
+      BitSet unhydratedFields = getUnhydratedFields();
+      if (unhydratedFields == null)
+      {
+         return;
+      }
+      int unhydratedIndex = unhydratedFields.nextSetBit(0);
+      if (unhydratedIndex != -1)
+      {
+         countHydrateRecordWhenSessionCloses(1);
+         countHydratedFieldWhenSessionCloses(unhydratedFields.cardinality());
+      }
+      while (unhydratedIndex != -1 && hydrator != null)
+      {
+         getDatum(unhydratedIndex);
+         unhydratedIndex = unhydratedFields.nextSetBit(unhydratedIndex + 1);
+      }
+   }
+
+   public void clearHydrator()
+   {
+      hydrator = null;
+      liveProps = null;
+      livePropsCardinality = data.length;
+   }
+   
    /**
     * Get the static record metadata for the concrete subclass.
     * 
@@ -1161,17 +1363,85 @@
          }
       }
    }
-
+   
+   /**
+    * Retrieve the row offset required by the hydrator to skip any eventual fields that may
+    * reside the in the result set and not in {@link #data}. For instance, recid is not part
+    * of the data array, but is part of the result-set.
+    * 
+    * @return   Always 1 as we need to skip only the leading recid.
+    */
+   protected int getHydratorRowOffset()
+   {
+      return 1;
+   }
+
+   /**
+    * Retrieve the fields that are not live due to lazy hydration. This is used to identify which fields
+    * are left to hydrate by the hydrator. This array is used only when a refresh is needed (the record
+    * should be re-loaded from the database). Therefore, don't use this for any hydrate call. Let the
+    * hydrator decide when this is needed.
+    * 
+    * @return   A bit-set representing the fields that still require hydration.
+    */
+   protected BitSet getUnhydratedFields()
+   {
+      if (liveProps == null)
+      {
+         return null;
+      }
+      
+      BitSet partialFields;
+      if (readFields != null)
+      {
+         partialFields = (BitSet) readFields.clone();
+         partialFields.andNot(liveProps);
+      }
+      else
+      {
+         partialFields = (BitSet) liveProps.clone();
+         partialFields.flip(0, data.length);
+      }
+      return partialFields;
+   }
+   
+   /**
+    * Checks if a field at the specified index has been hydrated.
+    *
+    * @param   index
+    *          The index of the field to check.
+    * 
+    * @return {@code true} if the field has been hydrated, {@code false} otherwise.
+    */
+   protected boolean isFieldHydrated(int index)
+   {
+      return liveProps.get(index);
+   }
+   
+   protected abstract void countHydrateRecordWhenSessionCloses(long count);
+
+   protected abstract void countTotalFields(long count);
+   
+   protected abstract void countHydratedFieldsWithLZS(long count);
+   protected abstract void countHydratedFieldsWithLoader(long count);
+   
+   
+   protected abstract void countHydratedFieldWhenSessionCloses(long count);
+   
+   
    /**
     * Mark this record as incomplete.
     * 
     * @param    session
     *           Database session (used for UNDO purposes).
+    *           
+    * @param    loadedFields
+    *           Fields that were loaded from database.
     */
-   void markIncomplete(Session session)
+   void markIncomplete(Session session, BitSet loadedFields)
    {
       updateState(session, DmoState.INCOMPLETE, true);
-      readFields = new BitSet(data.length);
+      readFields = loadedFields;
    }
 
    /**
@@ -1387,7 +1657,7 @@
     *          The result set to read from.
     * @param   rsOffset
     *          The index in {@code ResultSet} to read from. If the property is a multiple column
-    *          then more than one column will ve read from {@code ResultSet}.
+    *          then more than one column will be read from {@code ResultSet}.
     *
     * @return  The number of columns actually read from the {@code ResultSet}.
     */
@@ -1424,14 +1694,21 @@
       
       try
       {
-         if (readFields != null)
+         int count = propertyReader.readProperty(rs, rsOffset, data, propOffset);
+         
+         if (liveProps != null)
          {
-            readFields.set(propOffset);
+            // this should always hold; we shouldn't ever read a field twice
+            // otherwise, the cardinality will fail
+            livePropsCardinality++;
+            
+            // mark this property as live
+            liveProps.set(propOffset);
          }
          
-         return propertyReader.readProperty(rs, rsOffset, data, propOffset);
+         return count;
       }
-      catch (java.sql.SQLException sqle)
+      catch (SQLException sqle)
       {
          throw new PersistenceException(sqle);
       }
@@ -1529,6 +1806,40 @@
    }
    
    /**
+    * Indicate whether all properties are live; that is, have all properties been initialized, either by
+    * hydration from a result set, explicitly setting them, or initializing them to their default values.
+    * 
+    * @return  {@code true} if all properties are live, else {@code false}.
+    */
+   boolean areAllPropertiesLive()
+   {
+      return hydrator == null;
+   }
+   
+   /**
+    * Create a lazy hydration helper object for the given result set, at the given offset into the column
+    * results, for the current result set row.
+    * 
+    * @param   rowStruct
+    *          The row structure of the of the result-set.
+    * @param   lazyResultSet
+    *          Backing result set for lazy hydration.
+    * @param   rsOffset
+    *          Offset of the primary key column for this record in the current row of the result set.
+    */
+   void createHydrator(RowStructure rowStruct, LazyResultSet lazyResultSet, int rsOffset)
+   {
+      hydrator = lazyResultSet.createHydrator(rowStruct, rsOffset, _recordMeta());
+      // don't reset liveProps as we want to avoid hydrating properties that were already hydrated
+      if (liveProps == null)
+      {
+         liveProps = new BitSet(data.length);
+         livePropsCardinality = 0;
+      }
+      countTotalFields(data.length);
+   }
+   
+   /**
     * Perform a sub-transaction level changeset reset of this record
     * @param   txLevel
     *          The nesting level of sub-transactions of the block being rolled back, where 0 represents
@@ -1668,6 +1979,7 @@
       int txLevel = prepareChangeSet(activeBuffer.getSession());
       if (txLevel >= 0)
       {
+         changeSet.updateBaseline(offset, getDatum(offset));
          changeSet.logStateChange(flags, txLevel);
          changeSet.logDataChange(txLevel, offset, datum);
       }
@@ -1715,6 +2027,7 @@
          for (int i = 0; i < offsets.size(); i++)
          {
             int offset = offsets.get(i);
+            changeSet.updateBaseline(offset, getDatum(offset));
             Object datum = datums.get(i);
             changeSet.logDataChange(txLevel, offset, datum);
          }
@@ -1792,9 +2105,8 @@
          // create a change set if one with the same transaction ID does not already exist
          if (changeSet == null || !changeSet.verify(session, txId))
          {
-            changeSet = new ChangeSet(session, txId, data, state.state, txLevel);
+            changeSet = new ChangeSet(session, txId, data, liveProps, state.state, txLevel);
          }
-         
          // register this undoable record with the savepoint manager at the current transaction nesting
          // level
          session.trackUndoable(this);
@@ -1871,4 +2183,49 @@
       }
       return size;
    }
+   
+   /**
+    * Simple method to retrieve the effective value at a specified offset.
+    * Note that this doesn't honor the lazy hydration technique and should be used
+    * ONLY for logging / debugging purposes. This method shouldn't be used functionally
+    * in the orm architecture. 
+    * 
+    * @param   offset
+    *          Zero-based offset of the datum in the data array.
+    */
+   protected Object getSimpleDatum(int offset)
+   {
+      return data[offset];
+   }
+   
+   /**
+    * Set a single datum into the data array, and update the record.
+    * This is a simple version of {@link #setDatum(int, Object)} used
+    * by BaseRecord.
+    * 
+    * @param   offset
+    *          Zero-based offset of the datum in the data array.
+    * @param   datum
+    *          Value to be set into the data array.
+    */
+   public void setSimpleDatum(int offset, Object datum)
+   {
+      if (readFields != null)
+      {
+         readFields.set(offset);
+      }
+      
+      if (liveProps != null)
+      {
+         if (!liveProps.get(offset))
+         {
+            livePropsCardinality++;
+         }
+         
+         liveProps.set(offset);
+      }
+      
+      data[offset] = datum;
+   }
+
 }

=== modified file 'src/com/goldencode/p2j/persist/orm/ChangeSet.java'
--- old/src/com/goldencode/p2j/persist/orm/ChangeSet.java	2025-11-18 12:38:12 +0000
+++ new/src/com/goldencode/p2j/persist/orm/ChangeSet.java	2026-05-22 13:58:35 +0000
@@ -145,7 +145,7 @@
    
    /** Baseline snapshot of data before first change is made */
    private final Object[] baselineData;
-   
+   private final BitSet activeFieldsInBaseline;
    /** Baseline state before first change is made */
    private final int baselineState;
    
@@ -180,13 +180,14 @@
     *          Anticipated maximum number of transaction nesting levels to record. It will be increased
     *          dynamically later, if this capacity is exceeded.
     */
-   ChangeSet(Session session, int txId, Object[] data, int state, int capacity)
+   ChangeSet(Session session, int txId, Object[] data, BitSet liveProps, int state, int capacity)
    {
       this.session = session;
       this.txId = txId;
       int len = data.length;
       this.baselineData = new Object[len];
       System.arraycopy(data, 0, this.baselineData, 0, len);
+      this.activeFieldsInBaseline = liveProps == null ? new BitSet(len) : (BitSet) liveProps.clone();
       this.baselineState = state;
       
       ensureCapacity(capacity);
@@ -263,9 +264,6 @@
          return;
       }
       
-      // live data to be updated by rollback
-      Object[] liveData = dmo.data;
-      
       // gather the positions of all the data elements that were changed at this block and in more
       // deeply nested blocks and clear those data structures after collecting this information
       BitSet touched = new BitSet(baselineData.length);
@@ -314,8 +312,7 @@
          for (int j = copy.nextSetBit(0); j >= 0; j = copy.nextSetBit(j + 1))
          {
             // overlay the live data
-            liveData[j] = changes[i][j];
-            
+            dmo.setSimpleDatum(j, changes[i][j]);
             // remember that we've rolled back this data element
             touched.clear(j);
          }
@@ -326,7 +323,7 @@
       {
          for (int j = touched.nextSetBit(0); j >= 0; j = touched.nextSetBit(j + 1))
          {
-            liveData[j] = baselineData[j];
+            dmo.setSimpleDatum(j, baselineData[j]);
          }
       }
       
@@ -406,6 +403,17 @@
       }
    }
    
+   public void updateBaseline(int offset, Object datum)
+   {
+      if (activeFieldsInBaseline.get(offset))
+      {
+         return;
+      }
+      
+      baselineData[offset] = datum;
+      activeFieldsInBaseline.set(offset);
+   }
+   
    /**
     * Prepare for a log entry into the change set by updating the outermost and innermost transaction
     * nesting levels at which changes occur, and by ensuring the internal data structures have enough

=== modified file 'src/com/goldencode/p2j/persist/orm/DirectAccessHelper.java'
--- old/src/com/goldencode/p2j/persist/orm/DirectAccessHelper.java	2025-11-18 12:38:12 +0000
+++ new/src/com/goldencode/p2j/persist/orm/DirectAccessHelper.java	2026-05-22 13:58:35 +0000
@@ -524,7 +524,7 @@
     *          
     */
    private static Record interpretResponse(Session session, DmoMeta meta, DirectAccessResponse response)
-   throws PersistenceException, 
+   throws PersistenceException,
           SQLException
    {
       if (response.recid == null)
@@ -540,6 +540,11 @@
       }
       
       ResultSet rs = response.getResultSet();
+      if (Query.getDialect(session).useLazyHydration())
+      {
+         rs = new LazyResultSet(rs.getStatement(), rs, session);
+      }
+
       if (rs.next())
       {
          RowStructure rowStructure = new FullRowStructure(meta);

=== modified file 'src/com/goldencode/p2j/persist/orm/DmoClass.java'
--- old/src/com/goldencode/p2j/persist/orm/DmoClass.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/orm/DmoClass.java	2026-05-22 13:57:10 +0000
@@ -20,7 +20,6 @@
 ** 006 AD  20240313 Added method that assembles implementation and returns the new DmoClass object.
 **     AD  20240403 Added callback method when assembling dmo implementation.
 ** 007 SP  20250206 Removed support for custom denormalized extents.
-** 008 RNC 20260401 Migrate RECID from 32-bit to 64-bit values.
 */
 
 /*
@@ -229,7 +228,7 @@
       converters.put(logical.class,    new Dmo2Pojo("java/lang/Boolean",      null, null, null));
       converters.put(object.class,     new Dmo2Pojo("java/lang/Long",         null, null, null));
       converters.put(raw.class,        new Dmo2Pojo("[B",                     null, null, null));
-      converters.put(recid.class,      new Dmo2Pojo("java/lang/Long",         null, null, null));
+      converters.put(recid.class,      new Dmo2Pojo("java/lang/Integer",      null, null, null));
       converters.put(rowid.class,      new Dmo2Pojo("java/lang/Long",         null, null, null));
    }
    

=== modified file 'src/com/goldencode/p2j/persist/orm/DmoMeta.java'
--- old/src/com/goldencode/p2j/persist/orm/DmoMeta.java	2025-11-10 10:38:44 +0000
+++ new/src/com/goldencode/p2j/persist/orm/DmoMeta.java	2026-05-22 13:58:35 +0000
@@ -679,8 +679,8 @@
     * Obtain the list of properties for this DMO.
     * 
     * @param   extra
-    *          If {@code true} the surrogate/hidden  properties ({@code id}, {@code _multiplex})
-     *         are also added, otherwise only the original fields are returned.
+    *          If {@code true} the surrogate/hidden properties ({@code id}, {@code _multiplex})
+    *          are also added, otherwise only the original fields are returned.
     * 
     * @return  An iterator for list of properties for this DMO.
     */

=== modified file 'src/com/goldencode/p2j/persist/orm/DmoMetadataManager.java'
--- old/src/com/goldencode/p2j/persist/orm/DmoMetadataManager.java	2025-04-02 14:49:50 +0000
+++ new/src/com/goldencode/p2j/persist/orm/DmoMetadataManager.java	2026-05-22 13:58:35 +0000
@@ -94,6 +94,7 @@
 import com.goldencode.p2j.persist.Record;
 import com.goldencode.p2j.persist.annotation.*;
 import com.goldencode.p2j.persist.meta.*;
+import com.goldencode.p2j.persist.dialect.*;
 import com.goldencode.p2j.util.*;
 import com.goldencode.p2j.util.logging.*;
 import java.util.*;
@@ -560,7 +561,6 @@
     *
     * @return  Unmodifiable map.
     *
-    *
     * @throws  PersistenceException
     *          if there is an error querying index information from the JDBC driver.
     */

=== modified file 'src/com/goldencode/p2j/persist/orm/FullRowStructure.java'
--- old/src/com/goldencode/p2j/persist/orm/FullRowStructure.java	2025-07-16 14:01:50 +0000
+++ new/src/com/goldencode/p2j/persist/orm/FullRowStructure.java	2026-05-22 13:58:35 +0000
@@ -12,6 +12,7 @@
 ** 002 OM  20240712 Added support for hydrating expanded extent fields.
 **     SP  20250321 Removed usage of property.index. Fixed typos.
 ** 003 LS  20250714 Added 'getNeededFields' implementation.
+** 004 AL2 20231214 Added implementation for hydrateAt
 */
 
 /*
@@ -71,8 +72,7 @@
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.BitSet;
-import java.util.Iterator;
+import java.util.*;
 import com.goldencode.p2j.persist.PersistenceException;
 import com.goldencode.p2j.persist.TempRecord;
 
@@ -241,11 +241,62 @@
       {
          hydrateExtents(session, r, recOffset);
       }
+
+      r.clearHydrator();
       
       return rsOffset;
    }
    
    /**
+    * Hydrate only one property in the base-record. This is largely used by the lazy hydrator,
+    * allowing it to hydrate only the property that is accessed now. The other hydration method 
+    * ({@link #hydrate}) eagerly hydrates all properties, is a slow process and may do useless
+    * hydrations on properties that are not accessed.
+    * 
+    * @param   resultSet
+    *          The ResultSet from which the hydration should be done
+    * @param   rsOffset
+    *          The result set offset from which the reading should be done. This is relevant as
+    *          the result set may contain a joined row, but we are interested only in a specific
+    *          record that may start at a specific offset.
+    * @param   r
+    *          The record that should be hydrated. 
+    * @param   propOffset
+    *          The property that should be hydrated. The caller is responsible in assuring that
+    *          this property wasn't hydrated before.
+    * 
+    * @return  {@code true} if this worked due to the fact that it was a scalar value. {@code false}
+    *          in case the property was a non-expanded 
+    *          
+    * @throws  PersistenceException
+    *          This is thrown when reading the scalar property fails.
+    */
+   @Override
+   public boolean hydrateAt(ResultSet resultSet, int rsOffset, BaseRecord r, int propOffset)
+   throws PersistenceException
+   {
+      // we know the result-set if properly formatted and matched the data in the dataIndex
+      PropertyMeta pm = getPropertyMeta()[propOffset];
+      if (pm.getExtent() == 0)
+      {
+         r.readProperty(propOffset, resultSet, (rsOffset - 1) + pm.columnIndex, false);
+         return true;
+      }
+      else if(pm.dmoProperty.expanded)
+      {
+         int extentIndex = (propOffset - pm.offset) * pm.columnCount; 
+         // pm.columnIndex refers to the index of the 1st element in the extent, 
+         // so we need to add the current extent index
+         // Multiplication by pm.columnCount is needed for cases when the extent element is represented on 
+         // more SQL columns
+         r.readProperty(propOffset, resultSet, (rsOffset - 1) + pm.columnIndex + extentIndex);
+         return true;
+      }
+      
+      return false;
+   }
+   
+   /**
     * Check if this structure is used for an incomplete record. 
     * 
     * This specific row structure (full) will always retrieve the whole record.
@@ -266,4 +317,18 @@
    {
       return null;
    }
+   
+   /**
+    * Retrieve the fields that were loaded from the database.
+    *
+    * @return A BitSet object, where true means that the field associated with that position in the {@code
+    * data} array has been loaded from database, and false - otherwise.
+    */
+   @Override
+   public BitSet getLoadedFields()
+   {
+      BitSet fields = new BitSet(getPropertyMeta().length);
+      fields.flip(0, fields.size());
+      return fields;
+   }
 }

=== added file 'src/com/goldencode/p2j/persist/orm/Hydrator.java'
--- old/src/com/goldencode/p2j/persist/orm/Hydrator.java	1970-01-01 00:00:00 +0000
+++ new/src/com/goldencode/p2j/persist/orm/Hydrator.java	2026-05-22 13:46:08 +0000
@@ -0,0 +1,324 @@
+/*
+** Module   : Hydrator.java
+** Abstract : Stores a lazy result set to enable lazy (just-in-time) hydration of a DMO.
+**
+** Copyright (c) 2023, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
+** 001 ECF 20230102 Created initial version.
+**     AL2 20231102 Added a loader as a fallback if the result-set is closed.
+**     AL2 20231103 Added a JMX to track lazy hydration
+**     AL2 20231106 Allow refresh with partial fields.
+**     AL2 20231110 Support hydration of normalized extent field in this class. The record will rely on this
+**                  instance to handle the whole hydration boilerplate.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+**
+** Additional terms under GNU Affero GPL version 3 section 7:
+**
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+**
+**   0. Attribution Requirement.
+**
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+**
+**   1. No License To Use Trademarks.
+**
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+**
+**   2. No Misrepresentation of Affiliation.
+**
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+**
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+**
+**   3. No Misrepresentation of Source or Origin.
+**
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.persist.orm;
+
+import java.lang.ref.*;
+import java.sql.*;
+import java.util.BitSet;
+
+import com.goldencode.p2j.jmx.*;
+import com.goldencode.p2j.persist.PersistenceException;
+
+/**
+ * This class stores a lazy result set to enable deferred/lazy (just-in-time) hydration of a DMO. One or
+ * more instances of this class is created when a scrollable result set is positioned at a new row, when
+ * reading through the results of a query. One instance is created per DMO that needs to be hydrated from
+ * that result row. At that moment, a token is retrieved from the result set. When the result set is needed
+ * for lazy hydration later, the token is compared to the internal state of the result set. If any state has
+ * changed which prevents lazy hydration (e.g., the result set was closed, repositioned to a new row, etc.),
+ * {@link #getResultSet()} will return {@code null} instead of the result set. It is expected that the use
+ * pattern for this object is to invoke {@link #getResultSet()} and then immediately retrieve data from the
+ * returned result set (if not {@code null}).
+ */
+class Hydrator
+{
+   /** Instrumentation for {@link #getResultSet}. */
+   private static final SimpleCounter VALID_HYDRATOR = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HydratorHit);
+   
+   /** Instrumentation for {@link #getResultSet}. */
+   private static final SimpleCounter HYDRATOR_MISS = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HydratorMiss);
+   
+   /** Instrumentation for {@link #refresh}. */
+   private static final SimpleCounter SUCCESSFULL_REFRESH = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HyratorSuccessfullRefresh);
+   
+   /** Instrumentation for {@link #refresh}. */
+   private static final SimpleCounter UNSUCCESSFULL_REFRESH = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HyratorUnsuccessfullRefresh);
+   
+   /** Token which is used to track validity of the hydrator vis-a-vis the backing result set */
+   final long token;
+   
+   /** Offset of the starting point in a result set row from which to begin reading DMO data */
+   final int rsOffset;
+   
+   /** Weak reference to the backing result set */
+   private final WeakReference<LazyResultSet> rsRef;
+   
+   /** Weak reference to the backing session used for refreshing */
+   private WeakReference<Session> sessionRef;
+   
+   /** Weak reference to the loader which should be used for refreshing*/
+   private WeakReference<Loader> loaderRef;
+   
+   /** The row structure of the result-set */
+   private RowStructure rowStruct;
+   
+   /**
+    * Constructor.
+    * 
+    * @param   token
+    *          Validity token for the backing result set.
+    * @param   rowStruct
+    *          The row-structure of the result-set.
+    * @param   resultSet
+    *          The result set to be stored for lazy hydration use.
+    * @param   rsOffset
+    *          The offset of the start of the data for the current record. Should point to the primary key.
+    * @param   meta
+    *          The record meta of the record which is meant to be hydrated by this hydrator. 
+    * @param   sessionRef
+    *          A weak reference to the session that backs this hydrator.
+    * @param   loaderRef
+    *          A weak reference to the loader that backs this hydrator.
+    */
+   Hydrator(long token,
+            RowStructure rowStruct,
+            LazyResultSet resultSet, 
+            int rsOffset, 
+            RecordMeta meta, 
+            WeakReference<Session> sessionRef,
+            WeakReference<Loader> loaderRef)
+   {
+      this.token = token;
+      this.rsRef = new WeakReference<>(resultSet);
+      this.rsOffset = rsOffset;
+      this.rowStruct = rowStruct;
+      this.sessionRef = sessionRef;
+      this.loaderRef = loaderRef;
+   }
+   
+   /**
+    * Get the result set backing this hydrator, or {@code null} if the result set is no longer valid for
+    * lazy hydration, based on the token that was stored when this hydrator was created. To be considered
+    * valid, the result set must be positioned on the same record it was when the hydrator was created, and
+    * its state must be such that data can still be fetched from it (e.g., it must not have been closed).
+    * A weak reference to the result set is maintained, so it can be garbage collected if is no longer in
+    * use.
+    * <p>
+    * The result set returned from this method is meant to be accessed immediately upon return from this
+    * method. If such access is deferred, the result set may become invalid (closed, repositioned on
+    * another record, etc.). In such a case, an error or undefined behavior might result.
+    * 
+    * @return  Result set backing this hydrator, or {@code null} if it has become invalid for lazy hydration
+    *          purposes.
+    */
+   LazyResultSet getResultSet()
+   {
+      LazyResultSet rs = rsRef.get();
+      if (rs != null && rs.isHydratorValid(this))
+      {
+         VALID_HYDRATOR.update(1);
+         return rs;
+      }
+
+      HYDRATOR_MISS.update(1);
+      return null;
+   }
+
+   /**
+    * Allow refreshing a dmo based on the fallback loader here. this may fail if the loader is no longer
+    * available. This is because the session may be closed.
+    * 
+    * It is enough to do a quick load without doing any session cache work.
+    * 
+    * @param   dmo
+    *          The dmo to be refreshed
+    * @param   recid
+    *          The id based on which the refreshing should be done.
+    *          
+    * @return  {@code true} if the loading succeeded. This may fail if the session was closed.
+    */
+   public void refresh(BaseRecord dmo, Long recid, BitSet partialFields)
+   {
+      Loader loader;
+      if (loaderRef == null || (loader = loaderRef.get()) == null)
+      {
+         UNSUCCESSFULL_REFRESH.update(1);
+         throw new LazyHydrationException(recid, -1);
+      }
+      
+      try
+      {
+         // don't mark as incomplete; the other fields were hydrated on-time
+         loader.load(dmo, recid, partialFields);
+      }
+      catch (PersistenceException e)
+      {         
+         UNSUCCESSFULL_REFRESH.update(1);
+         throw new LazyHydrationException(recid, -1, e);
+      }
+      
+      SUCCESSFULL_REFRESH.update(1);
+   }
+
+   /**
+    * Hydrate a specific field in a record based on the underlying result-set. This is used 
+    * to honor lazy hydration. When a record field is accessed and not yet hydrated, this method
+    * will read from the result-set. If the result-set was closed, attempt to reload from database
+    * using the loader. This is an expensive call, but empirically it happens very rarely.
+    * 
+    * @param   r
+    *          The record to be hydrated.
+    * @param   propOffset
+    *          The property which should be hydrated. This has the index of the data.
+    * @param   rowOffset
+    *          An eventual offset to skip leading fields (i.e. recid or multiplex).
+    * @param   allowOtherImplicitHydration
+    *          Allow other implicit hydration (e.g. hydrate the other fields in unique indices).
+    *          
+    * @return  {@code true} if this requires a refresh or {@code false} otherwise. This information
+    *          is used by the record to know if the hydrator should be dropped or not. If a refresh
+    *          happened, lazy hydration is no longer needed.
+    */
+   public boolean hydrate(BaseRecord r, int propOffset, int rowOffset, boolean allowOtherImplicitHydration)
+   { 
+      ResultSet rs = getResultSet();
+      Long id = r.primaryKey();
+      
+      if (rs != null)
+      {
+         try
+         {
+            boolean wasScalar = rowStruct.hydrateAt(rs, rsOffset + rowOffset, r, propOffset);
+            if (!wasScalar)
+            {
+               Session session = null;
+               if (sessionRef == null || (session = sessionRef.get()) == null)
+               {
+                  throw new LazyHydrationException(r.primaryKey(), -1);
+               }
+               rowStruct.hydrateExtents(session, r, -1);
+            }
+            
+            if (allowOtherImplicitHydration)
+            {
+               hydrateRemainingFieldsInUniqueIndexes(r, propOffset, rowOffset);
+            }
+         }
+         catch (PersistenceException e)
+         {
+            throw new LazyHydrationException(id, propOffset, e);
+         }
+         
+         return true;
+      }
+      
+      return false;
+   }
+   
+   /**
+    * Hydrates other unhydrated fields that belong to the same unique indexes as the specified property.
+    * 
+    *
+    * @param   r
+    *          The record to be hydrated.
+    * @param   propOffset
+    *          The property whose unique indices should be checked. This has the index of the data.
+    * @param   rowOffset
+    *          An eventual offset to skip leading fields (i.e. recid or multiplex).
+    */
+   private void hydrateRemainingFieldsInUniqueIndexes(BaseRecord r, int propOffset, int rowOffset)
+   {
+      BitSet[] uniqueIndices = r._recordMeta().getUniqueIndices();
+      
+      for(BitSet uIndex : uniqueIndices)
+      {
+         if (uIndex.get(propOffset))
+         {
+            for (int i = uIndex.nextSetBit(0); i >= 0; i = uIndex.nextSetBit(i + 1))
+            {
+               // don't hydrate if the field is the current one or if the field has already been hydrated
+               if (i == propOffset || r.isFieldHydrated(i))
+               {
+                  continue;
+               }
+               
+               hydrate(r, i, rowOffset, false);
+            }
+         }
+      }
+   }
+   
+   
+   /**
+    * Always attempt to refresh the session to the new one. Otherwise, this could still reference
+    * a stale session which waits to be garbage collected (or reclaimed). 
+    * 
+    * @param   session
+    *          The session to which we attempt to attach this hydrator.
+    */
+   public void refresh(Session session)
+   {
+      this.sessionRef = new WeakReference<>(session);
+      this.loaderRef = session.getLoader();
+   }
+}

=== added file 'src/com/goldencode/p2j/persist/orm/LazyHydrationException.java'
--- old/src/com/goldencode/p2j/persist/orm/LazyHydrationException.java	1970-01-01 00:00:00 +0000
+++ new/src/com/goldencode/p2j/persist/orm/LazyHydrationException.java	2026-05-22 13:46:08 +0000
@@ -0,0 +1,136 @@
+/*
+** Module   : LazyHydrationException.java
+** Abstract : An exception type for lazy hydration runtime errors.
+**
+** Copyright (c) 2023, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
+** 001 ECF 20230619 Created initial version.
+**     AL2 20231102 Added constructor with clause.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+**
+** Additional terms under GNU Affero GPL version 3 section 7:
+**
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+**
+**   0. Attribution Requirement.
+**
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+**
+**   1. No License To Use Trademarks.
+**
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+**
+**   2. No Misrepresentation of Affiliation.
+**
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+**
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+**
+**   3. No Misrepresentation of Source or Origin.
+**
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.persist.orm;
+
+/**
+ * This exception type is used with lazy hydration of DMOs from result sets. It is raised when a {@link
+ * Hydrator} associated with a {@link BaseRecord} is no longer valid, or when a database error occurs during
+ * lazy hydration of a DMO's data.
+ * <p>
+ * This class extends {@code RuntimeException} rather than {@code Exception} to avoid complicating all
+ * generated DMO interface getter and setter methods. Lazy hydration is used only by the persistence runtime,
+ * while DMO interfaces are used in proxy form by application logic. It is undesirable to clutter application
+ * logic with checked exception handling, since these exceptions will be caught and handled at lower levels
+ * within the runtime environment and must not propagate beyond the DMO proxy invocation handlers to the
+ * application level.
+ */
+@SuppressWarnings("serial")
+public class LazyHydrationException
+extends RuntimeException
+{
+   private final Long primaryKey;
+   
+   private final int offset;
+   
+   /**
+    * Constructor used when hydrating a datum lazily and the hydrator is no longer valid.
+    * 
+    * @param   primaryKey
+    *          Primary key of record which failed lazy hydration.
+    * @param   offset
+    *          Starting offset of the datum which could not be retrieved, in the record's data array.
+    */
+   LazyHydrationException(Long primaryKey, int offset)
+   {
+      this(primaryKey, offset, null);
+   }
+   
+   /**
+    * Constructor used when hydrating a datum lazily and the hydrator is no longer valid.
+    * 
+    * @param   primaryKey
+    *          Primary key of record which failed lazy hydration.
+    * @param   offset
+    *          Starting offset of the datum which could not be retrieved, in the record's data array.
+    */
+   LazyHydrationException(Long primaryKey, int offset, Throwable cause)
+   {
+      super(cause);
+      this.primaryKey = primaryKey;
+      this.offset = offset;
+   }
+   
+   public long getPrimaryKey()
+   {
+      return primaryKey;
+   }
+   
+   public int getOffset()
+   {
+      return offset;
+   }
+   
+   /**
+    * Since this exception is only thrown from one location, override the default implementation to avoid
+    * the overhead of filling in the stack trace.
+    * 
+    * @return  This object.
+    */
+   @Override
+   public LazyHydrationException fillInStackTrace()
+   {
+      return this;
+   }
+}

=== added file 'src/com/goldencode/p2j/persist/orm/LazyResultSet.java'
--- old/src/com/goldencode/p2j/persist/orm/LazyResultSet.java	1970-01-01 00:00:00 +0000
+++ new/src/com/goldencode/p2j/persist/orm/LazyResultSet.java	2026-05-22 13:46:08 +0000
@@ -0,0 +1,2097 @@
+/*
+** Module   : LazyResultSet.java
+** Abstract : Delegating result set wrapper which tracks validity for lazy hydration.
+**
+** Copyright (c) 2023, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
+** 001 ECF 20230804 Created initial version.
+**     AL2 20231102 Added loader to the hydrator to fall-back in case the result-set expires.
+**     AL2 20231110 Store a weak reference to the session instead of loader. Allow this to store the statement
+**                  to eventually return the statement that created this result-set, not the one that created
+**                  the underlying result-set (see UnclosablePreparedStatement).
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+**
+** Additional terms under GNU Affero GPL version 3 section 7:
+**
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+**
+**   0. Attribution Requirement.
+**
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+**
+**   1. No License To Use Trademarks.
+**
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+**
+**   2. No Misrepresentation of Affiliation.
+**
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+**
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+**
+**   3. No Misrepresentation of Source or Origin.
+**
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.persist.orm;
+
+import java.io.*;
+import java.lang.ref.WeakReference;
+import java.math.*;
+import java.net.*;
+import java.sql.*;
+import java.sql.Date;
+import java.util.*;
+
+/**
+ * A wrapper for a {@code ResultSet} which delegates all methods to the wrapped result set and which
+ * additionally can create and validate {@link Hydrator} objects. A hydrator can extract data from a
+ * particular row in the result set, but becomes invalid if that row is no longer accessible.
+ * <p>
+ * Hydrator validity is determined by comparing a counter's value at the time the hydrator instance is
+ * created with that counter's value at the time validity is checked. When {@link #createHydrator}
+ * creates a new hydrator instance, the counter's current value is saved as a token in the hydrator object.
+ * The counter is incremented every time the wrapped result set's state is changed to move to another row,
+ * or to otherwise make the result set unreadable (e.g., it is closed).
+ * <p>
+ * If the current value of the counter does not match a hydrator's stored token at the moment {@link
+ * #isHydratorValid(Hydrator)} is invoked, the validation fails.
+ *
+ */
+public final class LazyResultSet
+implements ResultSet
+{   
+   /** Delegate statement */
+   private final Statement stmt;
+   
+   /** Delegate result set */
+   private final ResultSet rs;
+   
+   /** Counter which is updated for every change which could invalidate a hydrator */
+   private long changes = 0L;
+   
+   /** The fallback loader to help generated hydrators to recover from invalidated result-sets */
+   private WeakReference<Session> sessionRef;
+   
+   /**
+    * Constructor which accepts result set delegate.
+    * 
+    * @param   stmt
+    *          The statement that generated this result-set. This is not always the statement of 
+    *          the provided rs.
+    * @param   rs
+    *          Result set to which all operations are delegated.
+    * @param   session
+    *          The session under which this result-set was generated. This is relevant to support
+    *          the changing of ownership for the underlying result-set.
+    */
+   public LazyResultSet(Statement stmt, ResultSet rs, Session session)
+   {
+      this.stmt = stmt;
+      this.rs = rs;
+      this.sessionRef = new WeakReference<>(session);
+   }
+   
+   /**
+    * Create a new {@link Hydrator} instance, suitable for reading a single DMO's data values from the
+    * current row of the wrapped result set. The hydrator will only remain valid until the result set is
+    * moved to another row, or it is closed or otherwise made invalid by some method of this class.
+    * 
+    * @param   rowStruct
+    *          The row structure of the of the result-set.
+    * @param   rsOffset
+    *          The offset at which the hydrator should operate. This helps for the cases when the result-set
+    *          was generated by a join statement. 
+    * @param   rm
+    *          The record meta used by the hydrating process.
+    *           
+    * @return  A hydator used by the record to do lazy hydration.
+    */
+   public Hydrator createHydrator(RowStructure rowStruct, int rsOffset, RecordMeta rm)
+   {
+      Session session;
+      if (sessionRef == null || (session = sessionRef.get()) == null)
+      {
+         // no loader could be achieved
+         return new Hydrator(changes, rowStruct, this, rsOffset, rm, sessionRef, null);
+      }
+      
+      return new Hydrator(changes, rowStruct, this, rsOffset, rm, sessionRef, session.getLoader());
+   }
+   
+   /**
+    * Indicate whether the given hydrator is valid. A hydrator is valid if the token it was created with
+    * equals the current change count. If the change count is not the same, it indicates the result set's
+    * state has changed and it may no longer be positioned on the result row which the hydrator needs to
+    * read its data, or the result set was closed.
+    * 
+    * @param   hydrator
+    *          Hydrator to test for validity. Must not be {@code null}.
+    * 
+    * @return  {@code true} if the hydrator can still be used with the backing result set.
+    */
+   public boolean isHydratorValid(Hydrator hydrator)
+   {
+      return hydrator.token == changes;
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public <T> T unwrap(Class<T> iface)
+   throws SQLException
+   {
+      return rs.unwrap(iface);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean isWrapperFor(Class<?> iface)
+   throws SQLException
+   {
+      return rs.isWrapperFor(iface);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean next()
+   throws SQLException
+   {
+      changes++;
+      return rs.next();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void close()
+   throws SQLException
+   {
+      changes++;
+      rs.close();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean wasNull()
+   throws SQLException
+   {
+      return rs.wasNull();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public String getString(int columnIndex)
+   throws SQLException
+   {
+      return rs.getString(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean getBoolean(int columnIndex)
+   throws SQLException
+   {
+      return rs.getBoolean(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public byte getByte(int columnIndex)
+   throws SQLException
+   {
+      return rs.getByte(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public short getShort(int columnIndex)
+   throws SQLException
+   {
+      return rs.getShort(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public int getInt(int columnIndex)
+   throws SQLException
+   {
+      return rs.getInt(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public long getLong(int columnIndex)
+   throws SQLException
+   {
+      return rs.getLong(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public float getFloat(int columnIndex)
+   throws SQLException
+   {
+      return rs.getFloat(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public double getDouble(int columnIndex)
+   throws SQLException
+   {
+      return rs.getDouble(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   @Deprecated
+   public BigDecimal getBigDecimal(int columnIndex, int scale)
+   throws SQLException
+   {
+      return rs.getBigDecimal(columnIndex, scale);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public byte[] getBytes(int columnIndex)
+   throws SQLException
+   {
+      return rs.getBytes(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Date getDate(int columnIndex)
+   throws SQLException
+   {
+      return rs.getDate(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Time getTime(int columnIndex)
+   throws SQLException
+   {
+      return rs.getTime(columnIndex);
+   }
+
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Timestamp getTimestamp(int columnIndex)
+   throws SQLException
+   {
+      return rs.getTimestamp(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public InputStream getAsciiStream(int columnIndex)
+   throws SQLException
+   {
+      return rs.getAsciiStream(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   @Deprecated
+   public InputStream getUnicodeStream(int columnIndex)
+   throws SQLException
+   {
+      return rs.getUnicodeStream(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public InputStream getBinaryStream(int columnIndex)
+   throws SQLException
+   {
+      return rs.getBinaryStream(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public String getString(String columnLabel)
+   throws SQLException
+   {
+      return rs.getString(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean getBoolean(String columnLabel)
+   throws SQLException
+   {
+      return rs.getBoolean(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public byte getByte(String columnLabel)
+   throws SQLException
+   {
+      return rs.getByte(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public short getShort(String columnLabel)
+   throws SQLException
+   {
+      return 0;
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public int getInt(String columnLabel)
+   throws SQLException
+   {
+      return rs.getInt(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public long getLong(String columnLabel)
+   throws SQLException
+   {
+      return rs.getLong(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public float getFloat(String columnLabel)
+   throws SQLException
+   {
+      return rs.getFloat(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public double getDouble(String columnLabel)
+      throws SQLException
+   {
+      return rs.getDouble(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   @Deprecated
+   public BigDecimal getBigDecimal(String columnLabel, int scale)
+   throws SQLException
+   {
+      return rs.getBigDecimal(columnLabel, scale);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public byte[] getBytes(String columnLabel)
+   throws SQLException
+   {
+      return rs.getBytes(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Date getDate(String columnLabel)
+   throws SQLException
+   {
+      return rs.getDate(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Time getTime(String columnLabel)
+   throws SQLException
+   {
+      return rs.getTime(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Timestamp getTimestamp(String columnLabel)
+   throws SQLException
+   {
+      return rs.getTimestamp(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public InputStream getAsciiStream(String columnLabel)
+   throws SQLException
+   {
+      return rs.getAsciiStream(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   @Deprecated
+   public InputStream getUnicodeStream(String columnLabel)
+   throws SQLException
+   {
+      return rs.getUnicodeStream(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public InputStream getBinaryStream(String columnLabel)
+   throws SQLException
+   {
+      return rs.getBinaryStream(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public SQLWarning getWarnings()
+   throws SQLException
+   {
+      return rs.getWarnings();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void clearWarnings()
+   throws SQLException
+   {
+      rs.clearWarnings();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public String getCursorName()
+   throws SQLException
+   {
+      return rs.getCursorName();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public ResultSetMetaData getMetaData()
+   throws SQLException
+   {
+      return rs.getMetaData();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Object getObject(int columnIndex)
+   throws SQLException
+   {
+      return rs.getObject(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Object getObject(String columnLabel)
+   throws SQLException
+   {
+      return rs.getObject(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public int findColumn(String columnLabel)
+   throws SQLException
+   {
+      return rs.findColumn(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Reader getCharacterStream(int columnIndex)
+   throws SQLException
+   {
+      return rs.getCharacterStream(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Reader getCharacterStream(String columnLabel)
+   throws SQLException
+   {
+      return rs.getCharacterStream(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public BigDecimal getBigDecimal(int columnIndex)
+   throws SQLException
+   {
+      return rs.getBigDecimal(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public BigDecimal getBigDecimal(String columnLabel)
+   throws SQLException
+   {
+      return rs.getBigDecimal(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean isBeforeFirst()
+   throws SQLException
+   {
+      return rs.isBeforeFirst();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean isAfterLast()
+   throws SQLException
+   {
+      return rs.isAfterLast();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean isFirst()
+   throws SQLException
+   {
+      return rs.isFirst();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean isLast()
+   throws SQLException
+   {
+      return rs.isLast();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void beforeFirst()
+   throws SQLException
+   {
+      changes++;
+      rs.beforeFirst();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void afterLast()
+   throws SQLException
+   {
+      changes++;
+      rs.afterLast();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean first()
+   throws SQLException
+   {
+      changes++;
+      return rs.first();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean last()
+   throws SQLException
+   {
+      changes++;
+      return rs.last();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public int getRow()
+   throws SQLException
+   {
+      return rs.getRow();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean absolute(int row)
+   throws SQLException
+   {
+      if (row != rs.getRow())
+      {
+         changes++;
+      }
+      return rs.absolute(row);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean relative(int rows)
+   throws SQLException
+   {
+      if (rows != 0)
+      {
+         changes++;
+      }
+      return rs.relative(rows);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean previous()
+   throws SQLException
+   {
+      changes++;
+      return rs.previous();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void setFetchDirection(int direction)
+   throws SQLException
+   {
+      rs.setFetchDirection(direction);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public int getFetchDirection()
+   throws SQLException
+   {
+      return rs.getFetchDirection();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void setFetchSize(int rows)
+   throws SQLException
+   {
+      rs.setFetchSize(rows);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public int getFetchSize()
+   throws SQLException
+   {
+      return rs.getFetchSize();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public int getType()
+   throws SQLException
+   {
+      return rs.getType();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public int getConcurrency()
+   throws SQLException
+   {
+      return rs.getConcurrency();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean rowUpdated()
+   throws SQLException
+   {
+      return rs.rowUpdated();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean rowInserted()
+   throws SQLException
+   {
+      return rs.rowInserted();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean rowDeleted()
+   throws SQLException
+   {
+      return rs.rowDeleted();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNull(int columnIndex)
+   throws SQLException
+   {
+      rs.updateNull(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBoolean(int columnIndex, boolean x)
+   throws SQLException
+   {
+      rs.updateBoolean(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateByte(int columnIndex, byte x)
+   throws SQLException
+   {
+      rs.updateByte(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateShort(int columnIndex, short x)
+   throws SQLException
+   {
+      rs.updateShort(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateInt(int columnIndex, int x)
+   throws SQLException
+   {
+      rs.updateInt(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateLong(int columnIndex, long x)
+   throws SQLException
+   {
+      rs.updateLong(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateFloat(int columnIndex, float x)
+   throws SQLException
+   {
+      rs.updateFloat(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateDouble(int columnIndex, double x)
+   throws SQLException
+   {
+      rs.updateDouble(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBigDecimal(int columnIndex, BigDecimal x)
+   throws SQLException
+   {
+      rs.updateBigDecimal(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateString(int columnIndex, String x)
+   throws SQLException
+   {
+      rs.updateString(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBytes(int columnIndex, byte[] x)
+   throws SQLException
+   {
+      rs.updateBytes(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateDate(int columnIndex, Date x)
+   throws SQLException
+   {
+      rs.updateDate(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateTime(int columnIndex, Time x)
+   throws SQLException
+   {
+      rs.updateTime(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateTimestamp(int columnIndex, Timestamp x)
+   throws SQLException
+   {
+      rs.updateTimestamp(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateAsciiStream(int columnIndex, InputStream x, int length)
+   throws SQLException
+   {
+      rs.updateAsciiStream(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBinaryStream(int columnIndex, InputStream x, int length)
+   throws SQLException
+   {
+      rs.updateBinaryStream(columnIndex, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateCharacterStream(int columnIndex, Reader x, int length)
+   throws SQLException
+   {
+      rs.updateCharacterStream(columnIndex, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateObject(int columnIndex, Object x, int scaleOrLength)
+   throws SQLException
+   {
+      rs.updateObject(columnIndex, x, scaleOrLength);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateObject(int columnIndex, Object x)
+   throws SQLException
+   {
+      rs.updateObject(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNull(String columnLabel)
+   throws SQLException
+   {
+      rs.updateNull(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBoolean(String columnLabel, boolean x)
+   throws SQLException
+   {
+      rs.updateBoolean(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateByte(String columnLabel, byte x)
+   throws SQLException
+   {
+      rs.updateByte(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateShort(String columnLabel, short x)
+   throws SQLException
+   {
+      rs.updateShort(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateInt(String columnLabel, int x)
+   throws SQLException
+   {
+      rs.updateInt(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateLong(String columnLabel, long x)
+   throws SQLException
+   {
+      rs.updateLong(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateFloat(String columnLabel, float x)
+   throws SQLException
+   {
+      rs.updateFloat(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateDouble(String columnLabel, double x)
+   throws SQLException
+   {
+      rs.updateDouble(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBigDecimal(String columnLabel, BigDecimal x)
+   throws SQLException
+   {
+      rs.updateBigDecimal(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateString(String columnLabel, String x)
+   throws SQLException
+   {
+      rs.updateString(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBytes(String columnLabel, byte[] x)
+   throws SQLException
+   {
+      rs.updateBytes(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateDate(String columnLabel, Date x)
+   throws SQLException
+   {
+      rs.updateDate(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateTime(String columnLabel, Time x)
+   throws SQLException
+   {
+      rs.updateTime(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateTimestamp(String columnLabel, Timestamp x)
+   throws SQLException
+   {
+      rs.updateTimestamp(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateAsciiStream(String columnLabel, InputStream x, int length)
+   throws SQLException
+   {
+      rs.updateAsciiStream(columnLabel, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBinaryStream(String columnLabel, InputStream x, int length)
+   throws SQLException
+   {
+      rs.updateBinaryStream(columnLabel, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateCharacterStream(String columnLabel, Reader reader, int length)
+   throws SQLException
+   {
+      rs.updateCharacterStream(columnLabel, reader, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateObject(String columnLabel, Object x, int scaleOrLength)
+   throws SQLException
+   {
+      rs.updateObject(columnLabel, x, scaleOrLength);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateObject(String columnLabel, Object x)
+   throws SQLException
+   {
+      rs.updateObject(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void insertRow()
+   throws SQLException
+   {
+      rs.insertRow();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateRow()
+   throws SQLException
+   {
+      rs.updateRow();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void deleteRow()
+   throws SQLException
+   {
+      rs.deleteRow();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void refreshRow()
+   throws SQLException
+   {
+      rs.refreshRow();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void cancelRowUpdates()
+   throws SQLException
+   {
+      rs.cancelRowUpdates();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void moveToInsertRow()
+   throws SQLException
+   {
+      rs.moveToInsertRow();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void moveToCurrentRow()
+   throws SQLException
+   {
+      rs.moveToCurrentRow();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Statement getStatement()
+   throws SQLException
+   {
+      return stmt;
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Object getObject(int columnIndex, Map<String, Class<?>> map)
+   throws SQLException
+   {
+      return rs.getObject(columnIndex, map);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Ref getRef(int columnIndex)
+   throws SQLException
+   {
+      return rs.getRef(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Blob getBlob(int columnIndex)
+   throws SQLException
+   {
+      return rs.getBlob(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Clob getClob(int columnIndex)
+   throws SQLException
+   {
+      return rs.getClob(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Array getArray(int columnIndex)
+   throws SQLException
+   {
+      return rs.getArray(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Object getObject(String columnLabel, Map<String, Class<?>> map)
+   throws SQLException
+   {
+      return rs.getObject(columnLabel, map);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Ref getRef(String columnLabel)
+   throws SQLException
+   {
+      return rs.getRef(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Blob getBlob(String columnLabel)
+   throws SQLException
+   {
+      return rs.getBlob(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Clob getClob(String columnLabel)
+   throws SQLException
+   {
+      return rs.getClob(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Array getArray(String columnLabel)
+   throws SQLException
+   {
+      return rs.getArray(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Date getDate(int columnIndex, Calendar cal)
+   throws SQLException
+   {
+      return rs.getDate(columnIndex, cal);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Date getDate(String columnLabel, Calendar cal)
+   throws SQLException
+   {
+      return rs.getDate(columnLabel, cal);
+   }
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Time getTime(int columnIndex, Calendar cal)
+   throws SQLException
+   {
+      return rs.getTime(columnIndex, cal);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Time getTime(String columnLabel, Calendar cal)
+   throws SQLException
+   {
+      return rs.getTime(columnLabel, cal);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Timestamp getTimestamp(int columnIndex, Calendar cal)
+   throws SQLException
+   {
+      return rs.getTimestamp(columnIndex, cal);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Timestamp getTimestamp(String columnLabel, Calendar cal)
+   throws SQLException
+   {
+      return rs.getTimestamp(columnLabel, cal);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public URL getURL(int columnIndex)
+   throws SQLException
+   {
+      return rs.getURL(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public URL getURL(String columnLabel)
+   throws SQLException
+   {
+      return rs.getURL(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateRef(int columnIndex, Ref x)
+   throws SQLException
+   {
+      rs.updateRef(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateRef(String columnLabel, Ref x)
+   throws SQLException
+   {
+      rs.updateRef(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBlob(int columnIndex, Blob x)
+   throws SQLException
+   {
+      rs.updateBlob(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBlob(String columnLabel, Blob x)
+   throws SQLException
+   {
+      rs.updateBlob(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateClob(int columnIndex, Clob x)
+   throws SQLException
+   {
+      rs.updateClob(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateClob(String columnLabel, Clob x)
+   throws SQLException
+   {
+      rs.updateClob(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateArray(int columnIndex, Array x)
+   throws SQLException
+   {
+      rs.updateArray(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateArray(String columnLabel, Array x)
+   throws SQLException
+   {
+      rs.updateArray(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public RowId getRowId(int columnIndex)
+   throws SQLException
+   {
+      return rs.getRowId(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public RowId getRowId(String columnLabel)
+   throws SQLException
+   {
+      return rs.getRowId(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateRowId(int columnIndex, RowId x)
+   throws SQLException
+   {
+      rs.updateRowId(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateRowId(String columnLabel, RowId x)
+   throws SQLException
+   {
+      rs.updateRowId(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public int getHoldability()
+   throws SQLException
+   {
+      return rs.getHoldability();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean isClosed()
+   throws SQLException
+   {
+      return rs.isClosed();
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNString(int columnIndex, String nString)
+   throws SQLException
+   {
+      rs.updateNString(columnIndex, nString);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNString(String columnLabel, String nString)
+   throws SQLException
+   {
+      rs.updateNString(columnLabel, nString);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNClob(int columnIndex, NClob nClob)
+   throws SQLException
+   {
+      rs.updateNClob(columnIndex, nClob);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNClob(String columnLabel, NClob nClob)
+   throws SQLException
+   {
+      rs.updateNClob(columnLabel, nClob);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public NClob getNClob(int columnIndex)
+   throws SQLException
+   {
+      return rs.getNClob(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public NClob getNClob(String columnLabel)
+   throws SQLException
+   {
+      return rs.getNClob(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public SQLXML getSQLXML(int columnIndex)
+   throws SQLException
+   {
+      return rs.getSQLXML(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public SQLXML getSQLXML(String columnLabel)
+   throws SQLException
+   {
+      return rs.getSQLXML(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateSQLXML(int columnIndex, SQLXML xmlObject)
+   throws SQLException
+   {
+      rs.updateSQLXML(columnIndex, xmlObject);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateSQLXML(String columnLabel, SQLXML xmlObject)
+   throws SQLException
+   {
+      rs.updateSQLXML(columnLabel, xmlObject);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public String getNString(int columnIndex)
+   throws SQLException
+   {
+      return rs.getNString(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public String getNString(String columnLabel)
+   throws SQLException
+   {
+      return rs.getNString(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Reader getNCharacterStream(int columnIndex)
+   throws SQLException
+   {
+      return rs.getNCharacterStream(columnIndex);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public Reader getNCharacterStream(String columnLabel)
+   throws SQLException
+   {
+      return rs.getNCharacterStream(columnLabel);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNCharacterStream(int columnIndex, Reader x, long length)
+   throws SQLException
+   {
+      rs.updateNCharacterStream(columnIndex, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNCharacterStream(String columnLabel, Reader reader, long length)
+   throws SQLException
+   {
+      rs.updateNCharacterStream(columnLabel, reader, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateAsciiStream(int columnIndex, InputStream x, long length)
+   throws SQLException
+   {
+      rs.updateAsciiStream(columnIndex, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBinaryStream(int columnIndex, InputStream x, long length)
+   throws SQLException
+   {
+      rs.updateBinaryStream(columnIndex, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateCharacterStream(int columnIndex, Reader x, long length)
+   throws SQLException
+   {
+      rs.updateCharacterStream(columnIndex, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateAsciiStream(String columnLabel, InputStream x, long length)
+   throws SQLException
+   {
+      rs.updateAsciiStream(columnLabel, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBinaryStream(String columnLabel, InputStream x, long length)
+   throws SQLException
+   {
+      rs.updateBinaryStream(columnLabel, x, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateCharacterStream(String columnLabel, Reader reader, long length)
+   throws SQLException
+   {
+      rs.updateCharacterStream(columnLabel, reader, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBlob(int columnIndex, InputStream inputStream, long length)
+   throws SQLException
+   {
+      rs.updateBlob(columnIndex, inputStream, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBlob(String columnLabel, InputStream inputStream, long length)
+   throws SQLException
+   {
+      rs.updateBlob(columnLabel, inputStream, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateClob(int columnIndex, Reader reader, long length)
+   throws SQLException
+   {
+      rs.updateClob(columnIndex, reader, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateClob(String columnLabel, Reader reader, long length)
+   throws SQLException
+   {
+      rs.updateClob(columnLabel, reader, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNClob(int columnIndex, Reader reader, long length)
+   throws SQLException
+   {
+      rs.updateNClob(columnIndex, reader, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNClob(String columnLabel, Reader reader, long length)
+   throws SQLException
+   {
+      rs.updateNClob(columnLabel, reader, length);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNCharacterStream(int columnIndex, Reader x)
+   throws SQLException
+   {
+      rs.updateNCharacterStream(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNCharacterStream(String columnLabel, Reader reader)
+   throws SQLException
+   {
+      rs.updateNCharacterStream(columnLabel, reader);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateAsciiStream(int columnIndex, InputStream x)
+   throws SQLException
+   {
+      rs.updateAsciiStream(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBinaryStream(int columnIndex, InputStream x)
+   throws SQLException
+   {
+      rs.updateBinaryStream(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateCharacterStream(int columnIndex, Reader x)
+   throws SQLException
+   {
+      rs.updateCharacterStream(columnIndex, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateAsciiStream(String columnLabel, InputStream x)
+   throws SQLException
+   {
+      rs.updateAsciiStream(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBinaryStream(String columnLabel, InputStream x)
+   throws SQLException
+   {
+      rs.updateBinaryStream(columnLabel, x);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateCharacterStream(String columnLabel, Reader reader)
+   throws SQLException
+   {
+      rs.updateCharacterStream(columnLabel, reader);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBlob(int columnIndex, InputStream inputStream)
+   throws SQLException
+   {
+      rs.updateBlob(columnIndex, inputStream);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateBlob(String columnLabel, InputStream inputStream)
+   throws SQLException
+   {
+      rs.updateBlob(columnLabel, inputStream);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateClob(int columnIndex, Reader reader)
+   throws SQLException
+   {
+      rs.updateClob(columnIndex, reader);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateClob(String columnLabel, Reader reader)
+   throws SQLException
+   {
+      rs.updateClob(columnLabel, reader);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNClob(int columnIndex, Reader reader)
+   throws SQLException
+   {
+      rs.updateNClob(columnIndex, reader);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNClob(String columnLabel, Reader reader)
+   throws SQLException
+   {
+      rs.updateNClob(columnLabel, reader);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public <T> T getObject(int columnIndex, Class<T> type)
+   throws SQLException
+   {
+      return rs.getObject(columnIndex, type);
+   }
+   
+   /** 
+    * {@inheritDoc}
+    */
+   @Override
+   public <T> T getObject(String columnLabel, Class<T> type)
+   throws SQLException
+   {
+      return rs.getObject(columnLabel, type);
+   }
+}

=== modified file 'src/com/goldencode/p2j/persist/orm/Loader.java'
--- old/src/com/goldencode/p2j/persist/orm/Loader.java	2025-07-31 10:28:13 +0000
+++ new/src/com/goldencode/p2j/persist/orm/Loader.java	2026-05-22 13:58:35 +0000
@@ -498,6 +498,17 @@
       {
          T dmo = dmoClass.getDeclaredConstructor().newInstance();
          dmo.primaryKey(id);
+
+         // mark as incomplete; as only some fields are hydrated on the new instance
+         if (partialFields != null)
+         {
+            dmo.markIncomplete(session, partialFields);
+
+            if (partialFields.isEmpty())
+            {
+               return dmo;
+            }
+         }
          
          return load(dmo, id, partialFields);
       }
@@ -543,15 +554,6 @@
                                  (props[0].getExtent() == 0 || props[0].dmoProperty.expanded);
          
          int start = 0;
-         if (partialFields != null)
-         {
-            dmo.markIncomplete(session);
-
-            if (partialFields.isEmpty())
-            {
-               return dmo;
-            }
-         }
          
          for (String sql : loadSql)
          {
@@ -606,6 +608,12 @@
       }
    }
    
+   <T extends BaseRecord> ResultSet loadResults(T dmo)
+   throws PersistenceException
+   {
+      return null;
+   }
+   
    /**
     * Read all values from the given result set for the series of scalar fields. Assumes the
     * {@code ResultSet}'s cursor is positioned on the right row.

=== modified file 'src/com/goldencode/p2j/persist/orm/OrderedRowStructure.java'
--- old/src/com/goldencode/p2j/persist/orm/OrderedRowStructure.java	2025-08-14 09:57:08 +0000
+++ new/src/com/goldencode/p2j/persist/orm/OrderedRowStructure.java	2026-05-22 13:58:35 +0000
@@ -11,6 +11,7 @@
 **     AL2 20231212 Fixed method used to retrieve propetry index.
 ** 002 LS  20250714 Added 'getNeededFields' implementation.
 ** 003 AS  20250814 Return null if hasExtents in getNeededFields.
+** 004 AL2 20231214 Added extraColumn and hydrateAt.
 */
 
 /*
@@ -85,6 +86,9 @@
 {
    /** The collected fields for select query */
    private BitSet fields;
+
+   /** Mark if some fields require an extra column or are represented on a single column */
+   private BitSet extraColumn;
    
    /** The last field (not recid or multiplex) that was appended. */
    private int lastFieldSet = -1;
@@ -183,6 +187,21 @@
          // we are not setting the fields in order
          return false;
       }
+
+      int columnCount = getPropertyMeta()[fieldId].columnCount;
+      if (columnCount == 2)
+      {
+         if (extraColumn == null)
+         {
+            this.extraColumn = new BitSet();
+         }
+         this.extraColumn.set(fieldId);
+      }
+      else if (columnCount != 1)
+      {
+         // can't handle fields that are represented on more than 2 columns
+         return false;
+      }
       
       if (fields == null)
       {
@@ -353,6 +372,8 @@
          // this is incomplete, so set the record offset to -1
          hydrateExtents(session, r, -1);
       }
+
+      r.clearHydrator();
       
       return rsOffset;
    }
@@ -368,4 +389,63 @@
    {
       return hasExtents ? null : fields;
    }
+
+   /**
+    * Hydrate only one property in the base-record. This is largely used by the lazy hydrator,
+    * allowing it to hydrate only the property that is accessed now. The other hydration method 
+    * ({@link #hydrate}) eagerly hydrates all properties, is a slow process and may do useless
+    * hydrations on properties that are not accessed.
+    * 
+    * @param   resultSet
+    *          The ResultSet from which the hydration should be done
+    * @param   rsOffset
+    *          The result set offset from which the reading should be done. This is relevant as
+    *          the result set may contain a joined row, but we are interested only in a specific
+    *          record that may start at a specific offset.
+    * @param   r
+    *          The record that should be hydrated. 
+    * @param   propOffset
+    *          The property that should be hydrated. The caller is responsible in assuring that
+    *          this property wasn't hydrated before.
+    * 
+    * @return  {@code true} if this worked due to the fact that it was a scalar value. {@code false}
+    *          in case the property was a non-expanded 
+    *          
+    * @throws  PersistenceException
+    *          This is thrown when reading the scalar property fails.
+    */
+   @Override
+   public boolean hydrateAt(ResultSet resultSet, int rsOffset, BaseRecord r, int propOffset)
+   throws PersistenceException
+   {
+      PropertyMeta pm = getPropertyMeta()[propOffset];
+      if (pm.getExtent() == 0 || pm.dmoProperty.expanded)
+      {
+         if (!fields.get(propOffset))
+         {
+            throw new PersistenceException("Can't hydrate property that was not selected into the result-set.");
+         }
+         int pos = 0;
+         for (int curr = fields.nextSetBit(0); curr < propOffset; curr = fields.nextSetBit(curr + 1))
+         {
+            pos += 1 + (extraColumn != null && extraColumn.get(curr) ? 1 : 0);
+         }
+         r.readProperty(propOffset, resultSet, rsOffset + pos, false);
+         return true;
+      }
+
+      return false;
+   }
+   
+   /**
+   * Retrieve the fields that were loaded from the database.
+   *
+   * @return A BitSet object, where true means that the field associated with that position in the {@code
+   *         data} array has been loaded from database, and false - otherwise.
+   */
+   @Override
+   public BitSet getLoadedFields()
+   {
+      return fields == null ? null : (BitSet) fields.clone();
+   }
 }

=== modified file 'src/com/goldencode/p2j/persist/orm/OrmUtils.java'
--- old/src/com/goldencode/p2j/persist/orm/OrmUtils.java	2024-11-28 03:26:23 +0000
+++ new/src/com/goldencode/p2j/persist/orm/OrmUtils.java	2026-05-22 13:58:35 +0000
@@ -132,7 +132,7 @@
             
             for (int k = uIndex.nextSetBit(0); k >= 0; k = uIndex.nextSetBit(k + 1))
             {
-               Object datum = buffer.getCurrentRecord().data[k];
+               Object datum = buffer.getCurrentRecord().getDatum(k);
                if (props[k].getType() == character.class)
                {
                   datum = TextOps.trim((String) datum, " ", false, true);

=== modified file 'src/com/goldencode/p2j/persist/orm/Persister.java'
--- old/src/com/goldencode/p2j/persist/orm/Persister.java	2025-05-20 11:19:03 +0000
+++ new/src/com/goldencode/p2j/persist/orm/Persister.java	2026-05-22 13:58:35 +0000
@@ -814,11 +814,11 @@
                         typeHandler = TypeManager.getTypeHandler(Integer.class);
                         break;
                      default:
-                        if (fieldId < 0 || fieldId > dmo.data.length)
+                        if (fieldId < 0 || fieldId > dmo.getData().length)
                         {
                            LOG.severe("Something went awfully wrong, array bounds exception");
                         }
-                        par = dmo.data[fieldId];
+                        par = dmo.getDatum(fieldId);
                         typeHandler = properties[fieldId].getDataHandler();
                   }
                   
@@ -930,10 +930,10 @@
          int i = 0;
          int count = 0;
          Long id = dmo.primaryKey();
-         Object[] data = dmo.data;
+         Object[] data = dmo.getData();
          boolean onlyExtentProps = (props.length == 0) || 
                                    (props[0].getExtent() > 0 && !props[0].dmoProperty.expanded);
-         
+
          if (onlyExtentProps)
          {
             // if the table has only extent fields or no fields at all, then insert the 'empty record' into

=== modified file 'src/com/goldencode/p2j/persist/orm/Query.java'
--- old/src/com/goldencode/p2j/persist/orm/Query.java	2025-07-05 21:30:12 +0000
+++ new/src/com/goldencode/p2j/persist/orm/Query.java	2026-05-22 13:58:35 +0000
@@ -108,7 +108,8 @@
 import com.goldencode.p2j.persist.meta.*;
 
 /**
- * 
+ * Representation of a query, expressed in FQL, which can generate and execute dialect-specific SQL in the
+ * current database session.
  */
 public class Query
 {
@@ -167,12 +168,13 @@
    /**
     * The expected structure of a query, as generated by the {@link FqlToSqlConverter}. The content
     * is not very explicit for the moment, only the DMO and the number of fields. If more info
-    * is needed ity can be added in subsequent developonet rounds.
+    * is needed it can be added in subsequent development rounds.
     */
    private ArrayList<RowStructure> rowStructure = null;
    
    /**
     * Creates a new {@code Query} object.
+    * 
     * @param   cache 
     *          cache query lambda
     * @param   fql
@@ -478,10 +480,10 @@
    
    /**
     * Get the dialect of the database the session is connected to.
-    * 
+    *
     * @param   session
     *          The session to be investigated.
-    * 
+    *
     * @return  the dialect of the database the session is connected to.
     */
    static Dialect getDialect(Session session)

=== modified file 'src/com/goldencode/p2j/persist/orm/RecordMeta.java'
--- old/src/com/goldencode/p2j/persist/orm/RecordMeta.java	2025-04-07 08:20:02 +0000
+++ new/src/com/goldencode/p2j/persist/orm/RecordMeta.java	2026-05-22 13:58:35 +0000
@@ -157,7 +157,7 @@
    /** 
     * The index in {@link #props} array where each {@link #loadSql} statement has the properties 
     * (with the same extent) stored.  The value of <code>loadSqlIndexes[0]</code> is always zero, as the
-    * {@link #loadSql}'s first element is the scalar properties SELET. 
+    * {@link #loadSql}'s first element is the scalar properties SELECT. 
     */
    final int[] loadSqlIndexes;
    
@@ -1101,6 +1101,11 @@
       return temporary;
    }
    
+   public boolean isSingleResultSetLoad()
+   {
+      return loadSql.length == 1;
+   }
+   
    /**
     * Get the array of {@link PropertyMeta property metadata} objects which describe how the core
     * data of this record is organized.

=== modified file 'src/com/goldencode/p2j/persist/orm/RowStructure.java'
--- old/src/com/goldencode/p2j/persist/orm/RowStructure.java	2025-07-16 14:01:50 +0000
+++ new/src/com/goldencode/p2j/persist/orm/RowStructure.java	2026-05-22 13:58:35 +0000
@@ -10,6 +10,7 @@
 ** 003 AL2 20231123 Refactored this as an interface. Moved implementation in AbstractRowStructure.
 **     AL2 20231211 Added getDmoMeta.
 ** 004 LS  20250714 Added 'getNeededFields'.
+** 005 AL2 20231214 Added hydrateAt and hydrateExtents
 */
 
 /*
@@ -69,8 +70,7 @@
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.BitSet;
-import java.util.Iterator;
+import java.util.*;
 
 import com.goldencode.p2j.persist.DataModelObject;
 import com.goldencode.p2j.persist.PersistenceException;
@@ -184,6 +184,49 @@
    public int hydrate(Session session, ResultSet resultSet, int rsOffset, BaseRecord r)
    throws PersistenceException,
           SQLException;
+   
+   /**
+    * Hydrate only one property in the base-record. This is largely used by the lazy hydrator,
+    * allowing it to hydrate only the property that is accessed now. The other hydration method 
+    * ({@link #hydrate}) eagerly hydrates all properties, is a slow process and may do useless
+    * hydrations on properties that are not accessed.
+    * 
+    * @param   resultSet
+    *          The ResultSet from which the hydration should be done
+    * @param   rsOffset
+    *          The result set offset from which the reading should be done. This is relevant as
+    *          the result set may contain a joined row, but we are interested only in a specific
+    *          record that may start at a specific offset.
+    * @param   r
+    *          The record that should be hydrated. 
+    * @param   propOffset
+    *          The property that should be hydrated. The caller is responsible in assuring that
+    *          this property wasn't hydrated before.
+    * 
+    * @return  {@code true} if this worked due to the fact that it was a scalar value. {@code false}
+    *          in case the property was a non-expanded 
+    *          
+    * @throws  PersistenceException
+    *          This is thrown when reading the scalar property fails.
+    */
+   public boolean hydrateAt(ResultSet resultSet, int rsOffset, BaseRecord r, int propOffset)
+   throws PersistenceException;
+
+   /**
+    * Hydrate record's non-expanded extents.
+    * 
+    * @param   session
+    *          The session to be used to query the non-expanded extent tables.
+    * @param   r
+    *          The record to be hydrated.
+    * @param   recOffset
+    *          The offset to which the extent data should be added into the record. 
+    * 
+    * @throws  PersistenceException
+    *          thrown when hydrating extents fails (as it requires extra SQL queries to be run).
+    */
+   public void hydrateExtents(Session session, BaseRecord r, int recOffset)
+   throws PersistenceException;
 
    /** 
     * Retrieve the DMO meta used by the underlying DMO of this structure.
@@ -219,4 +262,12 @@
    {
       return true;
    }
+   
+   /**
+    * Retrieve the fields that were loaded from the database.
+    * 
+    * @return A BitSet object, where true means that the field associated with that position in the {@code
+    * data} array has been loaded from database, and false - otherwise.
+    */
+   public BitSet getLoadedFields();
 }

=== modified file 'src/com/goldencode/p2j/persist/orm/SQLQuery.java'
--- old/src/com/goldencode/p2j/persist/orm/SQLQuery.java	2025-09-10 11:27:41 +0000
+++ new/src/com/goldencode/p2j/persist/orm/SQLQuery.java	2026-05-22 13:58:35 +0000
@@ -2,7 +2,7 @@
 ** Module   : SQLQuery.java
 ** Abstract : Object which performs an SQL query and processes the results.
 **
-** Copyright (c) 2019-2025, Golden Code Development Corporation.
+** Copyright (c) 2019-2026, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
 ** 001 ECF 20191001 Created initial version with stubbed methods.
@@ -133,6 +133,7 @@
 import java.lang.reflect.Array;
 import java.sql.*;
 import java.util.*;
+import java.util.function.*;
 import java.util.concurrent.atomic.*;
 import java.util.logging.*;
 import com.goldencode.p2j.directory.*;
@@ -155,7 +156,31 @@
  * Note: internally, the object is NOT synchronized, so the usage intervals MUST NOT overlap.
  */
 public class SQLQuery
-{
+
+{   /** Instrumentation for cases where we lazily hydrate or rehydrate a records. */
+   private static final SimpleCounter LAZY_HYDRATE_RECORD = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HydratorLazyRecord);
+   
+   /** Instrumentation for cases where we lazily hydrate a record (not from cache). */
+   private static final SimpleCounter NEW_LAZY_RECORD = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HydratorNewRecord);
+   
+   /** Instrumentation for cases where we fully hydrate a record. */
+   private static final SimpleCounter NON_LAZY_HYDRATE_RECORD = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HydratorNonLazyRecord);
+   
+   /** Instrumentation for cases where we lazily hydrate a record (not from cache). */
+   private static final SimpleCounter LAZY_UNIQUE = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HydratorLazyUnique);
+   
+   /** Instrumentation for cases where we fully hydrate a record. */
+   private static final SimpleCounter NON_LAZY_UNIQUE = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HydratorNonLazyUnique);
+   
+   /** Profiler done for queries: cache hits, cache misses, etc. */
+   private static final QueryProfilerJMX SIMPLE_QUERY_PROFILER =
+      QueryProfilerJMX.getInstance(FwdServerJMX.QueryProfiler.QueryProfiler);
+   
    /** Logger */
    private static final CentralLogger LOG = CentralLogger.get(SQLQuery.class);
    
@@ -180,10 +205,6 @@
       fetchSize = dir.getInt(Directory.ID_RELATIVE_SERVER, CFG_FETCH_SIZE, DEFAULT_FETCH_SIZE);
       LOG.fine("JDBC fetch size initialized to: " + fetchSize);
    }
-
-   /** Profiler done for queries: cache hits, cache misses, etc. */
-   private static final QueryProfilerJMX SIMPLE_QUERY_PROFILER =
-      QueryProfilerJMX.getInstance(FwdServerJMX.QueryProfiler.QueryProfiler);
    
    /** The SQL statement, possibly with placeholders. */
    private final String baseSql;
@@ -383,6 +404,9 @@
       try
       {
          activateErrorHandler(session);
+         // Make sure we close result-set still opened on this sql. Otherwise, the statement will be reparsed.
+         session.unownResultSet(sql);
+         
          stmt = conn.prepareStatement(sql, scrollMode, ResultSet.CONCUR_READ_ONLY);
          if (bindParam2SessionVar)
          {
@@ -451,6 +475,9 @@
       try
       {
          activateErrorHandler(session);
+         // Make sure we close result-set still opened on this sql. Otherwise, the statement will be reparsed.
+         session.unownResultSet(sql);
+         
          stmt = conn.prepareStatement(sql);
          if (bindParam2SessionVar)
          {
@@ -530,10 +557,16 @@
       Connection conn = session.getConnection();
       checkClosed(conn);
       
+      boolean sessionOwner = false;
+      ResultSet rs = null;
+      boolean shouldCloseSession = false;
       try
       {
          activateErrorHandler(session);
-         stmt = conn.prepareStatement(sql);
+         // Make sure we close result-set still opened on this sql. Otherwise, the statement will be reparsed.
+         session.evictResultSet(sql);
+         
+         stmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
          if (bindParam2SessionVar)
          {
             setSessionVars(conn);
@@ -549,11 +582,24 @@
          }
          
          QueryStatement f = PreparedStatement::executeQuery;
-         ResultSet resultSet = SQLExecutor.getInstance().execute(session.getDatabase(), sql, f, stmt);
+         rs = SQLExecutor.getInstance().execute(session.getDatabase(), sql, f, stmt);
+         // TODO: apply lazy hydration. Mind that this instantly closes the statement, so the result-set
+         // so the lazy won't lasy for very long.
+         Dialect dialect = Query.getDialect(session);
+         if (dialect != null && dialect.useLazyHydration())
+         {
+            rs = new LazyResultSet(stmt, rs, session);
+            sessionOwner = true;
+            LAZY_UNIQUE.update(1);
+         }
+         else
+         {
+            NON_LAZY_UNIQUE.update(1);
+         }
          
          reportSQLWarnings(session);
          
-         if (!resultSet.next()) // same effect as first()
+         if (!rs.next()) // same effect as first()
          {
             return null; // no record matching predicate
          }
@@ -562,21 +608,20 @@
          if (rowStructure == null || rowStructure.getCount() == 1)
          {
             // only the PK or other scalar value is returned by the query
-            ret = (T) resultSet.getObject(1);
+            ret = (T) rs.getObject(1);
          }
          else
          {
-            // when multiple values are returned in a row then a fully hydrated Record is expected
-            ret = (T) hydrateRecord(resultSet, rowStructure, 1, session);
+            // when multiple values are returned in a row then a full Record is expected
+            ret = (T) hydrateRecord(rs, rowStructure, 1, session);
          }
-         
-         if (resultSet.next())
+         // don't call any further next as it will invalidate lazy hydration
+         if (!rs.isLast())
          {
-            close(); // ???
+            shouldCloseSession = true;
             throw new UniqueResultException("Result not unique for " + stmt);
          }
-
-         SIMPLE_QUERY_PROFILER.updateRowsCount(resultSet, 1);
+         SIMPLE_QUERY_PROFILER.updateRowsCount(rs, 1);
          return ret;
       }
       catch (SQLException exc)
@@ -589,9 +634,18 @@
          }
          throw new PersistenceException("Error uniqueResult", exc);
       }
-      finally
+      finally 
       {
-         close();
+         if (sessionOwner && rs != null && !shouldCloseSession)
+         {
+            // don't eagerly close it now as it will instantly invalidate lazy hydration
+            // let the result-set live more being managed by the session
+            session.ownResultSet(sql, rs);
+         }
+         else
+         {
+            close();
+         }
          resetErrorHandler(session);
       }
    }
@@ -700,6 +754,9 @@
       {
          activateErrorHandler(session);
          int rowSize = rowStructures.size();
+         // Make sure we close result-set still opened on this sql. Otherwise, the statement will be reparsed.
+         session.unownResultSet(sql);
+         
          List<T> ret = new ArrayList<>();
          stmt = conn.prepareStatement(sql);
          if (bindParam2SessionVar)
@@ -820,11 +877,12 @@
     * @throws  SQLException
     *          On error in the database layer.
     */
-   private void reportSQLWarnings(Session session) throws SQLException
+   private void reportSQLWarnings(Session session)
+   throws SQLException
    {
        if (!canRaiseSqlWarning)
        {
-         return;
+          return;
        }
        Dialect dialect = Query.getDialect(session);
        SQLWarning warning = stmt.getWarnings();
@@ -833,15 +891,14 @@
           if (dialect.isUdfOriginatedError(warning))
           {
              ErrorEntry ee = dialect.errorEntry(warning.getMessage());
-             ErrorManager.recordOrShowError(
-                      new int[] {ee.num}, 
-                      new String[] {ee.text}, 
-                      true, 
-                      ee.prefix, 
-                      false,
-                      false,
-                      false,
-                      true);
+             ErrorManager.recordOrShowError(new int[] {ee.num},
+                                            new String[] {ee.text},
+                                            true,
+                                            ee.prefix,
+                                            false,
+                                            false,
+                                            false,
+                                            true);
           }
           warning = warning.getNextWarning();
       }
@@ -866,7 +923,8 @@
     * @param   session
     *          The {@code Session} to be used.
     *
-    * @return  The re-hydrated object.
+    * @return  The re-hydrated object. Whether the record is fully or partially hydrated depends on whether
+    *          lazy hydration is allowed or a fields/except clause is active.
     *
     * @throws  PersistenceException
     *          When an issue was encountered in the process.
@@ -877,18 +935,10 @@
                                    Session session)
    throws PersistenceException
    {
-      BaseRecord res;
-      if (FwdServerJMX.JMX_DEBUG)
-      {
-         res = HYDRATE_RECORD.timerWithReturn(
-             () -> hydrateRecordImpl(resultSet, rowStructure, rsOffset, session)
-         );
-      }
-      else
-      {
-         res = hydrateRecordImpl(resultSet, rowStructure, rsOffset, session);
-      }
-      return res;
+      BaseRecord[] res = new BaseRecord[1];
+      HYDRATE_RECORD.timer(() -> res[0] = hydrateRecordImpl(resultSet, rowStructure, rsOffset, session));
+      
+      return res[0];
    }
    
    /**
@@ -945,16 +995,16 @@
     *          The data source. Contain the result of a query with the "main" properties. The
     *          extent properties are queried and hydrated before the record object is returned.
     * @param   rowStructure
-    *          The holder for the DMO of the object to be returned and the expected number of properties of this object,
-    *          as the select query was constructed. This governs the order and the types of the fields and
-    *          the number of columns used for each one.
+    *          The holder for the DMO of the object to be returned and the expected number of properties of
+    *          this object, as the select query was constructed. This governs the order and the types of the
+    *          fields and the number of columns used for each one.
     * @param   rsOffset
     *          The offset in the {@code ResultSet}'s row where the hydration process begins (in
     *          case when multiple objects are queried at once).
     * @param   session
     *          The {@code Session} to be used.
     *
-    * @return  The re-hydrated object.
+    * @return  A partially or fully re-hydrated DMO representing the record represented by the parameters.
     *
     * @throws  PersistenceException
     *          When an issue was encountered in the process.
@@ -965,17 +1015,21 @@
                                                Session session)
    throws PersistenceException
    {
-      Connection conn = session.getConnection();
-      checkClosed(conn);
+	   Connection conn = session.getConnection();
+      checkClosed(conn);	
+
+      boolean lazy = resultSet instanceof LazyResultSet;
       DmoMeta dmoInfo = rowStructure.getDmoMeta();
       Class<? extends Record> recordClass = dmoInfo.getImplementationClass();
       int count = rowStructure.getCount();
       // Note: we should check whether
       //          Session.PK.equalsIgnoreCase(resultSet.getMetaData().getColumnName(rsOffset))
       //       but to avoid the possible extra load of processing the metadata we assume the
-      //       primary key RECID/ID is the FIRST COLUMN in the result set of the query 
+      //       primary key RECID/ID is the FIRST COLUMN in the result set of the query
       
-      long pk;
+      Long pk = null;
+      BaseRecord r = null;
+      BaseRecord cachedRec = null;
       try
       {
          pk = resultSet.getLong(rsOffset);
@@ -986,8 +1040,9 @@
          {
             return null;
          }
-         Record cachedRec = session.getCached(recordClass, pk);
-         if (cachedRec != null && !cachedRec.checkState(DmoState.STALE))
+         boolean newHydrator = true;
+         cachedRec = session.getCached(recordClass, pk);
+         if (cachedRec != null)
          {
             // when the record is incomplete, it must be refreshed from the database
             // but first, verify whether the required fields are already loaded to avoid unnecessary refresh
@@ -995,10 +1050,11 @@
             {
                BitSet readFields = cachedRec._getReadFields();
                BitSet neededFields = rowStructure.getNeededFields();
-
+               newHydrator = false;
                if (neededFields == null)
                {
-                  cachedRec = session.get(recordClass, pk);
+                  newHydrator = true;
+                  cachedRec.updateState(session, DmoState.INCOMPLETE, false);
                }
                else
                {
@@ -1008,36 +1064,97 @@
                   {
                      // this will update the cachedRec reference, so the returned record instance does not change,
                      // only its data is updated
-                     cachedRec = session.get(recordClass, pk, clone);
+                     newHydrator = true;
                   }
                }
             }
-            
-            if (cachedRec != null)
+            else if (rowStructure.isIncomplete() || cachedRec.areAllPropertiesLive())
             {
                SIMPLE_QUERY_PROFILER.updateCacheHits(resultSet, 1);
-               // we found an unSTALE CACHED record, do not bother reading from result set
+               
+               // we do not want to do lazy hydration if the cached record is already fully hydrated or
+               // if the rowStructure is incomplete - providing limited amount of data
                return cachedRec;
             }
-         }
-         
-         if (count == 1)
-         {
-            SIMPLE_QUERY_PROFILER.updateCacheHits(resultSet, 1);
-            cachedRec = session.get(recordClass, pk);
-            
-            return cachedRec;
-         }
-      }
-      catch (SQLException sql)
-      {
-         // just log it, but continue to let the generic hydration detect other issues
+
+            // we have a cached record which needs further hydration, either immediate or deferred/lazy
+            r = cachedRec;
+         }
+         
+         if (r == null)
+         {
+            SIMPLE_QUERY_PROFILER.updateCacheMisses(resultSet, 1);
+            
+            if (count == 1 && !lazy)
+            {
+               // special case: the result set only has the PK and we need immediate hydration
+               return session.get(recordClass, pk);
+            }
+            
+            // all other cases, we need to instantiate a new record
+            r = recordClass.getDeclaredConstructor().newInstance();
+
+            if (rowStructure.isIncomplete())
+            {
+               // this should be done before hydrating; otherwise the reading won't mark
+               // the read properties properly
+               r.markIncomplete(session, rowStructure.getLoadedFields());
+            }
+         }
+         
+         if (!newHydrator)
+         {
+            return r;
+         }
+         else if (lazy)
+         {
+            LAZY_HYDRATE_RECORD.update(1);
+            r.primaryKey(pk);
+            if (r instanceof TempRecord)
+            {
+               ((TempRecord) r)._multiplex(resultSet.getInt(rsOffset + 1));
+            }
+            
+            r.createHydrator(rowStructure, (LazyResultSet) resultSet, rsOffset);
+            
+            if (cachedRec == null)
+            {
+               NEW_LAZY_RECORD.update(1);
+               session.associate(r);
+            }
+            else
+            {
+               updateIncompleteState(r, rowStructure, session);
+            }
+            
+            return r;
+         }
+         else if (cachedRec != null)
+         {
+            // We are not in lazy mode, but in Session cache we have a DMO which was not hydrated yet,
+            // or we have an INCOMPLETE DMO that needs further hydration.
+            rowStructure.hydrate(session, resultSet, rsOffset, r);
+
+            updateIncompleteState(r, rowStructure, session);
+
+            return r;
+         }
+
+
+         NON_LAZY_HYDRATE_RECORD.update(1);
+      }
+      catch (ReflectiveOperationException exc)
+      {
+         throw new PersistenceException("Error creating record", exc);
+      }
+      catch (SQLException exc)
+      {
+         // just log it, but if we are not returning, continue to let the generic hydration detect other
+         // issues
          LOG.log(Level.WARNING,
-                 "Failed to locate the primary key of " + recordClass.getSimpleName() + ".",
-                 sql);
+                 "Failed to locate the primary key of " + rowStructure.getDmoClass().getSimpleName() + ".");
       }
       
-      BaseRecord r = null;
       try
       {
          SIMPLE_QUERY_PROFILER.updateCacheMisses(resultSet, 1);
@@ -1047,7 +1164,7 @@
          {
             // this should be done before hydrating; otherwise the reading won't mark
             // the read properties properly
-            r.markIncomplete(session);
+            r.markIncomplete(session, rowStructure.getLoadedFields());
          }
          
          // expanded fields take contiguous elements in current row of returned record set
@@ -1056,8 +1173,11 @@
          // cache the hydrated record
          session.associate(r);
       }
-      catch (SQLException |
-             ReflectiveOperationException e)
+      catch (ReflectiveOperationException exc)
+      {
+         throw new PersistenceException("Error creating record", exc);
+      }
+      catch (SQLException e)
       {
          // extract Interrupted Exception out of SQLException
          if (e instanceof SQLException && e.getCause() instanceof InterruptedException)
@@ -1072,7 +1192,34 @@
       
       return r;
    }
-   
+
+   /**
+    * Check the {@link BaseRecord} is it should still be marked as INCOMPLETE or not.
+    * If it still INCOMPLETE, update {@link BaseRecord#readFields} to match with the current
+    * {@link RowStructure}.
+    *
+    * @param   r
+    *          Record to check
+    * @param   rowStructure
+    *          Used if is INCOMPLETE and to get the loaded fields.
+    * @param   session
+    *          Current Session
+    */
+   private static void updateIncompleteState(BaseRecord r, RowStructure rowStructure, Session session)
+   {
+      if (r.checkState(DmoState.INCOMPLETE))
+      {
+         if (!rowStructure.isIncomplete())
+         {
+            r.updateState(session, DmoState.INCOMPLETE, false);
+         }
+         else if (r._getReadFields() != null)
+         {
+            r._getReadFields().or(rowStructure.getLoadedFields());
+         }
+      }
+   }
+
    /**
     * Closes this object by releasing all acquired resources.
     */
@@ -1155,7 +1302,7 @@
          setParameter(stmt, pos, parameters[pIndex], forUpdate);
       }
    }
-
+   
    /**
     * Copy the set of parameters temporarily stored in this object to the session variables.
     *
@@ -1180,7 +1327,7 @@
          setSessionVar(conn, pos, parameters[pIndex]);
       }
    }
-
+   
    /**
     * Activate error handler at the database side.
     * @param  session 
@@ -1377,7 +1524,21 @@
       pstmt.executeUpdate();
    }
    
-
+   /**
+    * Logs a message with {@code Level.FINE} tracing level to the logger associated with this
+    * class.
+    * 
+    * @param   msg
+    *          The message to be printed to log.
+    */
+   private void log(Supplier<String> msg)
+   {
+      if (LOG.isLoggable(Level.FINE))
+      {
+         LOG.log(Level.FINE, msg.get());
+      }
+   }
+   
    /**
     * Test whether the connection is alive (not closed). If the connection is usable the method
     * returns without any side-effects. If the session is not usable, {@code PersistenceException}

=== modified file 'src/com/goldencode/p2j/persist/orm/ScrollableResults.java'
--- old/src/com/goldencode/p2j/persist/orm/ScrollableResults.java	2025-08-01 16:24:09 +0000
+++ new/src/com/goldencode/p2j/persist/orm/ScrollableResults.java	2026-05-22 13:58:35 +0000
@@ -79,6 +79,7 @@
 
 package com.goldencode.p2j.persist.orm;
 
+import com.goldencode.p2j.jmx.*;
 import com.goldencode.p2j.persist.*;
 import com.goldencode.p2j.util.logging.*;
 
@@ -104,6 +105,14 @@
  */
 public class ScrollableResults<T>
 {
+   /** Instrumentation for {@link #LazyResultSet}. */
+   private static final SimpleCounter LAZY_SCROLLABLE = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HydratorLazyScrollable);
+
+   /** Instrumentation for {@link #LazyResultSet}. */
+   private static final SimpleCounter NON_LAZY_SCROLLABLE = 
+            SimpleCounter.getInstance(FwdServerJMX.Counter.HydratorNonLazyScrollable);
+   
    /** Logger */
    private static final CentralLogger LOG = CentralLogger.get(ScrollableResults.class);
    
@@ -140,7 +149,16 @@
    public ScrollableResults(Statement stmt, ResultSet rs, ArrayList<RowStructure> rowStructure, Session session)
    {
       this.stmt = stmt;
-      this.rs = rs;
+      if (Query.getDialect(session).useLazyHydration())
+      {
+         LAZY_SCROLLABLE.update(1);
+         this.rs = new LazyResultSet(stmt, rs, session);
+      }
+      else
+      {
+         NON_LAZY_SCROLLABLE.update(1);
+         this.rs = rs;
+      }
       this.rowStructure = rowStructure;
       this.session = session;
       
@@ -369,12 +387,13 @@
                {
                   BaseRecord rec = SQLQuery.hydrateRecord(rs, rowStruct, rsOffset, session);
                   Object[] filteredRec = new Object[cols.length];
+                  int lng = rec.getLength();
                   for (int k = 0; k < cols.length; k++)
                   {
                      Integer index = colsSelection.get(cols[k]);
-                     if (index > 0 && index <= rec.data.length)
+                     if (index > 0 && index <= lng)
                      {
-                        filteredRec[k] = rec.data[index - 1];
+                        filteredRec[k] = rec.getDatum(index - 1);
                      }
                      else
                      {
@@ -477,7 +496,8 @@
    public void close()
    throws PersistenceException
    {
-      execute(() -> { stmt.close(); onClose.run(); return null; }, null);
+      // explicitly close the result-set. If is is lazy result-set, it should invalidate its hydrators.
+      execute(() -> { rs.close(); stmt.close(); onClose.run(); return null; }, null);
    }
    
    /**
@@ -518,7 +538,7 @@
       int rowSize = (rowStructure == null) ? 0 : rowStructure.size();
       int columnCount = rs.getMetaData().getColumnCount();
       Object[] ret;
-
+      
       if (rowSize == 0 || rowSize == columnCount)
       {
          // if the [columnCount] matches the [rowSize] then we have only one column for each DMO to be
@@ -541,11 +561,11 @@
          for (int i = 0; i < rowStructure.size(); i++)
          {
             RowStructure rowStruct = rowStructure.get(i);
-
+            
             // if the current row contains only the recId we do not need to hydrate the record
             boolean needOnlyPK = !forceOnlyId && rowStruct instanceof AdaptiveRowStructure
-                    && ((AdaptiveRowStructure) (rowStruct)).hasOnlyPK();
-
+               && ((AdaptiveRowStructure) (rowStruct)).hasOnlyPK();
+            
             int expColumnCount = rowStruct.getCount();
             try
             {
@@ -559,13 +579,13 @@
                   ret[recCnt] =
                      SQLQuery.hydrateRecord(rs, rowStruct, rsOffset, session);
                }
-
+               
             }
             catch (PersistenceException e)
             {
                throw new SQLException(e);
             }
-
+            
             // position on next record
             rsOffset += expColumnCount;
             ++recCnt;
@@ -573,7 +593,6 @@
       }
       return ret;
    }
-
    /**
     * Internal worker method. Execute a piece of code that potentially throws a
     * {@code SQLException} and returns the result. If the exception is throws, a default value is

=== modified file 'src/com/goldencode/p2j/persist/orm/Session.java'
--- old/src/com/goldencode/p2j/persist/orm/Session.java	2026-04-22 07:07:12 +0000
+++ new/src/com/goldencode/p2j/persist/orm/Session.java	2026-05-22 13:58:35 +0000
@@ -138,11 +138,14 @@
 
 package com.goldencode.p2j.persist.orm;
 
+import java.lang.ref.WeakReference;
 import java.lang.reflect.*;
 import java.sql.*;
 import java.util.*;
 import java.util.function.*;
 import java.util.logging.*;
+import java.util.logging.Level;
+
 import javax.sql.DataSource;
 import com.goldencode.asm.*;
 import com.goldencode.cache.*;
@@ -169,6 +172,9 @@
            CacheExpiryListener<RecordIdentifier<String>, BaseRecord>,
            AutoCloseable
 {
+   /** The size of the number of result-sets to be owned by this session. */
+   private static final int RESULT_SET_CACHE_SIZE = 64; 
+   
    /**
     * The default name of the primary key. The value is the same as the default value of
     * {@code com.goldencode.p2j.schema.SchemaConfig#primaryKeyName}, used at conversion and
@@ -201,6 +207,13 @@
     */
    public static String PK = DEFAULT_PK;
    
+   /** 
+    * Some result-sets that are managed by the session. These result-sets are scoped 
+    * to the lifecycle of the session, because their generating queries were not able to properly
+    * determine the timing of their closing. 
+    */
+   private Cache<String, ResultSet> resultSetCache;
+   
    /** Cache of database records associated with this session */
    private ExpiryCache<RecordIdentifier<String>, BaseRecord> cache;
    
@@ -349,6 +362,7 @@
       this.cache = useCache ? createCache() : null;
       this.cacheExtension = null;
       this.versioning = versioning;
+      this.resultSetCache = createResultSetCache();
       this.tenantName = tenantName;
       
       DatabaseConfig config = cfg;
@@ -373,6 +387,7 @@
       {
          this.hook.onSessionClaim(this.conn);
       }
+      this.resultSetCache = createResultSetCache();
    }
    
    /**
@@ -529,6 +544,16 @@
             "Error accessing DMO implementation class metadata for " + dmoClass.getName());
       }
    }
+
+   /**
+    * Retrieve the loader as a weak reference to avoid leaking.
+    * 
+    * @return   Retrieve the loader used by this session.
+    */
+   public WeakReference<Loader> getLoader()
+   {
+      return new WeakReference<>(loader);
+   }
    
    /**
     * Get the cached DMO of the given type with the given primary key.
@@ -823,18 +848,19 @@
             for (int p = dirtyProps.nextSetBit(0); p >= 0; p = dirtyProps.nextSetBit(p + 1))
             {
                // direct access
-               loaded.data[p] = dmo.data[p];
+               loaded.setDatum(p, dmo.getDatum(p));
             }
 
             if (partialFields == null)
             {
                // copy the full record (preserving the dirty properties) to the cached record, to preserve the
                // instance
-               System.arraycopy(loaded.data, 0, dmo.data, 0, dmo.data.length);
+               dmo.copyArray(loaded.getData());
 
                // set the incomplete record as complete
                dmo.updateState(this, DmoState.INCOMPLETE, false);
                dmo.readFields = null;
+               dmo.setAllLiveProps();
             }
             else
             {
@@ -844,7 +870,7 @@
                // only overwrite the new fields that were set
                for (int p = partialFields.nextSetBit(0); p >= 0; p = partialFields.nextSetBit(p + 1))
                {
-                  dmo.data[p] = loaded.data[p];
+                  dmo.setDatum(p, loaded.getDatum(p));
                   dmo.readFields.set(p);
 
                   boolean extent = props[p].dmoProperty.expanded;
@@ -853,17 +879,18 @@
                      int size = props[p].dmoProperty.extent;
                      for (int offset = p + 1; offset < p + size; offset++)
                      {
-                        dmo.data[offset] = loaded.data[offset];
+                        dmo.setDatum(offset, loaded.getDatum(offset));
                         dmo.readFields.set(offset);
                      }
                   }
                }
                // we have progressively loaded all fields and the DMO is no longer incomplete
-               if (dmo.readFields.cardinality() == dmo.data.length)
+               if (dmo.readFields.cardinality() == dmo.getData().length)
                {
                   // set the incomplete record as complete
                   dmo.updateState(this, DmoState.INCOMPLETE, false);
                   dmo.readFields = null;
+                  dmo.setAllLiveProps();
                }
             }
          }
@@ -1408,7 +1435,7 @@
          }
       }
       
-      System.arraycopy(dmo.data, 0, persisted.data, 0, dmo.data.length);
+      persisted.copyArray(dmo.getData());
       
       return persisted;
    }
@@ -1845,6 +1872,8 @@
       deregisterVersioned();
       
       cache = createCache();
+      clearResultSetCache();
+      resultSetCache = createResultSetCache();
       cacheExtension = null;
       
       // TODO: cancel all pending saves, updates and deletions
@@ -1931,6 +1960,7 @@
       try
       {
          deregisterVersioned();
+         clearResultSetCache();
          
          if (!conn.isClosed())
          {
@@ -1954,6 +1984,7 @@
          conn = null;
          cache = null;
          cacheExtension = null;
+         resultSetCache = null;
       }
    }
    
@@ -2389,4 +2420,120 @@
       return copy.equals(subset);
    }
 
+   /**
+    * Mark the session as the owner o the provided result-set. In other words, the session will be
+    * responsible for the destroying of the result-set when suitable. To avoid large memory consumption,
+    * the session will limit the number of owner result-sets. 
+    * 
+    * @param   rs
+    *          The result-set that should be managed by the session.
+    */
+   public void ownResultSet(String sql, ResultSet rs)
+   {
+      if (resultSetCache != null)
+      {
+         resultSetCache.put(sql, rs);
+         return;
+      }
+      
+      try
+      {
+         Statement stmt = rs.getStatement();
+         
+         rs.close();
+         
+         if (stmt != null)
+         {
+            stmt.close();
+         }
+      }
+      catch (SQLException e)
+      {
+         log.log(Level.SEVERE, "Failed closing the result-set and statement owned by the session: " + sql);
+      }      
+   }
+   
+   /**
+    * Evict the result-set from the session ownership. This will release the result-set, so it
+    * needs to be closed. Mind closing the statement at the same time.
+    * 
+    * @param   sql
+    *          The sql key identifying the result-set to be released.
+    */
+   public void unownResultSet(String sql)
+   {
+      if (resultSetCache == null)
+      {
+         return;
+      }
+      
+      ResultSet old = resultSetCache.get(sql);
+      if (old != null)
+      {
+         try
+         {
+            Statement stmt = old.getStatement();
+            
+            old.close();
+            
+            if (stmt != null)
+            {
+               stmt.close();
+            }
+         }
+         catch (SQLException e)
+         {
+            if (log.isLoggable(Level.SEVERE))
+            {
+               log.log(Level.SEVERE, 
+                       "Failed closing the result-set and statement owned by the session: " + sql);
+            }
+         }
+      }
+   }
+   
+   public void evictResultSet(String sql)
+   {
+      if (resultSetCache == null)
+      {
+         return;
+      }
+      
+      unownResultSet(sql);
+      resultSetCache.remove(sql);
+   }
+   
+   /**
+    * Clear the result-set cache by un-owning all result-sets. This means only closing the stored result-sets
+    * and will not actually evict all its records. Make sure the caller is resetting the cache.
+    */
+   private void clearResultSetCache()
+   {
+      if (resultSetCache != null)
+      {
+         resultSetCache.entries().forEach((entry) ->
+         {
+            unownResultSet(entry.getKey());
+         });
+      }
+   }
+   
+   /**
+    * This generates a new cache for storing result-sets owned by the session. This will also attempt to attach
+    * a listener that un-owns their result-sets when evicted.
+    * 
+    * @return   A new cache suitable for storing result-sets owned by the session.
+    */
+   private Cache<String, ResultSet> createResultSetCache()
+   {
+      ExpiryCache<String, ResultSet> rsCache = new LRUCache<>(RESULT_SET_CACHE_SIZE);
+      rsCache.addCacheExpiryListener((event) -> 
+      {
+         for (String sqlKey : event.getExpiredEntries().keySet())
+         {
+            unownResultSet(sqlKey);
+         }
+      });
+      return rsCache;
+   }
 }

=== modified file 'src/com/goldencode/p2j/persist/orm/SessionFactory.java'
--- old/src/com/goldencode/p2j/persist/orm/SessionFactory.java	2025-04-17 12:49:52 +0000
+++ new/src/com/goldencode/p2j/persist/orm/SessionFactory.java	2026-05-22 13:58:35 +0000
@@ -26,6 +26,7 @@
 **                  based on [sharedDb] parameter.
 ** 005 OM  20250211 New parameter for TenantManager.getTenant().
 ** 006 AP  20250417 Call the hook when a Session is reclaimed.
+** 007 ECF 20231002 Lazy hydration prototype.
 */
 
 /*
@@ -85,10 +86,10 @@
 
 import java.util.*;
 import java.util.concurrent.ConcurrentLinkedQueue;
-
 import com.goldencode.p2j.directory.DirectoryService;
 import com.goldencode.p2j.persist.*;
 import com.goldencode.p2j.persist.orm.TenantManager.Tenant;
+import com.goldencode.p2j.persist.dialect.*;
 import com.goldencode.p2j.util.Utils;
 import com.goldencode.p2j.util.logging.CentralLogger;
 

=== modified file 'src/com/goldencode/p2j/persist/orm/UniqueTracker.java'
--- old/src/com/goldencode/p2j/persist/orm/UniqueTracker.java	2025-06-25 11:07:55 +0000
+++ new/src/com/goldencode/p2j/persist/orm/UniqueTracker.java	2026-05-22 13:58:35 +0000
@@ -1101,7 +1101,7 @@
        */
       Key createKey(BaseRecord dmo)
       {
-         return new Key(this, dmo.data);
+         return new Key(this, dmo.getData());
       }
       
       /**

=== modified file 'src/com/goldencode/p2j/persist/orm/UnorderedRowStructure.java'
--- old/src/com/goldencode/p2j/persist/orm/UnorderedRowStructure.java	2025-08-14 09:57:08 +0000
+++ new/src/com/goldencode/p2j/persist/orm/UnorderedRowStructure.java	2026-05-22 13:58:35 +0000
@@ -10,6 +10,7 @@
 ** 002 SP  20250319 Added support for hydrating expanded extent fields.
 ** 003 LS  20250714 Added 'getNeededFields' implementation.
 ** 004 AS  20250814 Return null if hasExtents in getNeededFields.
+** 005 AL2 20231214 Moved from List to Map representation to allow faster hydrateAt.
 */
 
 /*
@@ -87,7 +88,7 @@
     * The fields that should be hydrated. This stores only their ids in the data array.
     * For recid and multiplex, we use special markers.
     */
-   public final List<Integer> fields;
+   public final Map<Integer, Integer> fields;
    
    /** A cached value of the property meta array that can be found in the record meta */
    private final PropertyMeta[] pm;
@@ -104,7 +105,7 @@
    public UnorderedRowStructure(DmoMeta meta)
    {
       super(meta);
-      this.fields = new ArrayList<>();
+      this.fields = new LinkedHashMap<>();
       this.pm = dmoMeta.recordMeta.getPropertyMeta(false);
       this.count = 0;
    }
@@ -121,7 +122,7 @@
    @Override
    public boolean addRecid(Property recid)
    {
-      fields.add(ReservedProperty.ID_PRIMARY_KEY);
+      fields.put(ReservedProperty.ID_PRIMARY_KEY, count);
       count++;
       return true;
    }
@@ -138,7 +139,7 @@
    @Override
    public boolean addMultiplex(Property multiplex)
    {
-      fields.add(ReservedProperty.ID_MULTIPLEX);
+      fields.put(ReservedProperty.ID_MULTIPLEX, count);
       count++;
       return true;
    }
@@ -163,7 +164,7 @@
       }
       
       int idx = dmoMeta.recordMeta.getIndexOfProperty(prop.name);
-      fields.add(idx);
+      fields.put(idx, count);
       count += pm[idx].columnCount;
       return true;
    }
@@ -246,9 +247,9 @@
       int fieldCnt = fields.size();
       int lastId = 0;
       int recOffset = 0;
-      for (int i = 0; i < fieldCnt; i++)
+      for (Map.Entry<Integer, Integer> field : fields.entrySet())
       {
-         int idx = fields.get(i);
+         int idx = field.getKey();
          if (lastId != idx)
          {
             recOffset = 0;
@@ -277,9 +278,55 @@
       {
          hydrateExtents(session, r, -1);
       }
+
+      r.clearHydrator();
       
       return rsOffset;
    }
+   
+   /**
+    * Hydrate only one property in the base-record. This is largely used by the lazy hydrator,
+    * allowing it to hydrate only the property that is accessed now. The other hydration method 
+    * ({@link #hydrate}) eagerly hydrates all properties, is a slow process and may do useless
+    * hydrations on properties that are not accessed.
+    *
+    * @param   resultSet
+    *          The ResultSet from which the hydration should be done
+    * @param   rsOffset
+    *          The result set offset from which the reading should be done. This is relevant as
+    *          the result set may contain a joined row, but we are interested only in a specific
+    *          record that may start at a specific offset.
+    * @param   r
+    *          The record that should be hydrated. 
+    * @param   propOffset
+    *          The property that should be hydrated. The caller is responsible in assuring that
+    *          this property wasn't hydrated before.
+    *
+    * @return  {@code true} if this worked due to the fact that it was a scalar value. {@code false}
+    *          in case the property was a non-expanded 
+    *
+    * @throws  PersistenceException
+    *          This is thrown when reading the scalar property fails.
+    */
+   @Override
+   public boolean hydrateAt(ResultSet resultSet, int rsOffset, BaseRecord r, int propOffset)
+   throws PersistenceException
+   {
+      PropertyMeta pm = getPropertyMeta()[propOffset];
+      if (pm.getExtent() == 0 || pm.dmoProperty.expanded)
+      {
+         Integer pos;
+         if ((pos = fields.get(propOffset)) == null)
+         {
+            throw new PersistenceException("Can't hydrate property that was not selected into the result-set.");
+         }
+         boolean minus = fields.get(ReservedProperty.ID_PRIMARY_KEY) < pos;
+         r.readProperty(propOffset, resultSet, rsOffset + pos + (minus ? -1 : 0), false);
+         return true;
+      }
+      
+      return false;
+   }
 
    /**
     * Returns a BitSet representing the fields required by this unordered row structure.
@@ -297,7 +344,7 @@
       }
 
       BitSet bitSet = new BitSet();
-      for (Integer id : fields)
+      for (Integer id : fields.keySet())
       {
          if (id != null && id != ReservedProperty.ID_PRIMARY_KEY && id != ReservedProperty.ID_MULTIPLEX)
          {
@@ -306,4 +353,24 @@
       }
       return bitSet;
    }
+   
+   /**
+    * Retrieve the fields that were loaded from the database.
+    *
+    * @return A BitSet object, where true means that the field associated with that position in the {@code
+    * data} array has been loaded from database, and false - otherwise.
+    */
+   @Override
+   public BitSet getLoadedFields()
+   {
+      BitSet loadedFields = new BitSet(pm.length);
+      for (Integer index : fields.keySet())
+      {
+         if (index != ReservedProperty.ID_PRIMARY_KEY && index != ReservedProperty.ID_MULTIPLEX)
+         {
+            loadedFields.set(index);
+         }
+      }
+      return loadedFields;
+   }
 }

=== modified file 'src/com/goldencode/p2j/persist/orm/Validation.java'
--- old/src/com/goldencode/p2j/persist/orm/Validation.java	2025-07-29 12:32:17 +0000
+++ new/src/com/goldencode/p2j/persist/orm/Validation.java	2026-05-22 13:58:35 +0000
@@ -278,7 +278,7 @@
       
       for (int i = uIndex.nextSetBit(0); i >= 0; i = uIndex.nextSetBit(i + 1))
       {
-         Object datum = dmo.data[i];
+         Object datum = dmo.getDatum(i);
          if (props[i].getType() == recid.class)
          {
             // RECID values are not displayed here
@@ -828,7 +828,7 @@
       {
          // compute the size for an index property 
          PropertyMeta crtProp = properties[i];
-         int size = crtProp.getDataHandler().getFieldSizeInIndex(dmo.data[i]);
+         int size = crtProp.getDataHandler().getFieldSizeInIndex(dmo.getDatum(i));
          
          // search the unique indexes where it is used and add the size there. If an index is
          // breaking one of the constraints, stop with error and skip additional computations 
@@ -837,7 +837,7 @@
             if (uIndexes[uComp].get(i))
             {
                uiSizes[uComp] += size;
-               duiSizes[uComp] += dialect.computeIndexKeySize(crtProp.getType(), dmo.data[i]);
+               duiSizes[uComp] += dialect.computeIndexKeySize(crtProp.getType(), dmo.getDatum(i));
                checkIndexSize(uiSizes[uComp], duiSizes[uComp], dialectMaxIndexSize, uComp, true);
             }
          }
@@ -848,7 +848,7 @@
             if (nIndexes[nComp].get(i))
             {
                niSizes[nComp] += size;
-               dniSizes[nComp] += dialect.computeIndexKeySize(crtProp.getType(), dmo.data[i]);
+               dniSizes[nComp] += dialect.computeIndexKeySize(crtProp.getType(), dmo.getDatum(i));
                checkIndexSize(niSizes[nComp], dniSizes[nComp], dialectMaxIndexSize, nComp, false);
             }
          }
@@ -933,7 +933,7 @@
       BitSet nonNullable = dmo._recordMeta().nonNullableProps;
       for (int i = offsets.nextSetBit(0); i != -1; i = offsets.nextSetBit(i + 1))
       {
-         if (nonNullable.get(i) && dmo.data[i] == null)
+         if (dmo._recordMeta().nonNullableProps.get(i) && dmo.getDatum(i) == null)
          {
             failNotNull(i);  // err: 110
          }
@@ -1295,7 +1295,7 @@
       
       for (int i = uIndex.nextSetBit(0); i >= 0; i = uIndex.nextSetBit(i + 1))
       {
-         Object datum = dmo.data[i];
+         Object datum = dmo.getDatum(i);
          if (datum != null)
          {
             if (props[i].getType() == character.class)
@@ -1393,8 +1393,8 @@
             // the violation is cleared; note that the changed record may still fail its own validation later
             for (int i = index.nextSetBit(0); i >= 0; i = index.nextSetBit(i + 1))
             {
-               Object uniqueDatum = dmo.data[i];
-               Object cachedDatum = cached.data[i];
+               Object uniqueDatum = dmo.getDatum(i);
+               Object cachedDatum = cached.getDatum(i);
                if (!Objects.equals(uniqueDatum, cachedDatum))
                {
                   return false;

=== modified file 'src/com/goldencode/p2j/persist/orm/types/DataHandler.java'
--- old/src/com/goldencode/p2j/persist/orm/types/DataHandler.java	2024-04-17 10:14:07 +0000
+++ new/src/com/goldencode/p2j/persist/orm/types/DataHandler.java	2026-05-22 13:58:35 +0000
@@ -131,6 +131,9 @@
     * Sets a value of a property in a {@code Record}. The specified value is converted from
     * {@code BaseDataType} FWD standard to plain Java, as the {@code data} array expects before
     * assigning the value to right offset.
+    * NOTE: this method DOESN'T affect the {@link com.goldencode.p2j.persist.Record#liveProps}, so it is the 
+    * programmer's responsibility to change the value for the {@code offset} if in need.
+    * 
     *
     * @param   data
     *          The {@code data} array of the record to be altered.

=== modified file 'src/com/goldencode/p2j/persist/orm/types/RecidType.java'
--- old/src/com/goldencode/p2j/persist/orm/types/RecidType.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/orm/types/RecidType.java	2026-05-22 13:57:10 +0000
@@ -2,7 +2,7 @@
 ** Module   : RecidType.java
 ** Abstract : Handles conversion from/to SQL/FWD recid type.
 **
-** Copyright (c) 2019-2026, Golden Code Development Corporation.
+** Copyright (c) 2019-2024, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
 ** 001 OM  20191101 Created initial version.
@@ -11,7 +11,6 @@
 **     OM  20221117 Some properties (datetime-tz) may take a different number of positional parameters when
 **                  used in insert/update or where predicate.
 ** 003 TJD 20240123 Java 17 compatibility updates
-** 004 RNC 20260401 Migrate RECID from 32-bit to 64-bit values.
 */
 
 /*
@@ -104,12 +103,12 @@
    @Override
    public recid convert(Object sqlVal)
    {
-      return new recid((Long) sqlVal);
+      return new recid((Integer) sqlVal);
    }
    
    /**
     * Use the data from a {@link recid} field stored in {@code BaseRecord.data} and use it
-    * to initialize a parameter of type recid/long in a SQL prepared query.
+    * to initialize a parameter of type recid/integer in a SQL prepared query.
     *
     * @param   stmt
     *          The prepared statement whose parameter is to be set.
@@ -130,7 +129,7 @@
    public int setParameter(PreparedStatement stmt, int index, Object val, boolean forUpdate)
    throws SQLException
    {
-      return TypeManager.setLongParameter(stmt, index, val, forUpdate);
+      return TypeManager.setIntegerParameter(stmt, index, val, forUpdate);
    }
    
    /**
@@ -155,7 +154,7 @@
    public int readProperty(ResultSet rs, int rsOffset, Object[] data, int propIndex)
    throws SQLException
    {
-      long datum = rs.getLong(rsOffset);
+      int datum = rs.getInt(rsOffset);
       data[propIndex] = datum == 0 && rs.wasNull() ? null : datum;
       
       return 1;
@@ -178,7 +177,7 @@
          return null;
       }
       
-      return new recid(text).longValue();
+      return new recid(text).intValue();
    }
    
    /**
@@ -197,8 +196,9 @@
       {
          return 3;
       }
-
-      return int64Type.getFieldSizeInIndex(val);
+   
+      // here we expect [val] to be an Integer, but the method of int64Type expects a [Long]
+      return int64Type.getFieldSizeInIndex(((Integer) val).longValue());
    }
    
    /**
@@ -226,7 +226,7 @@
       }
       
       recid asBDT = (recid) val;
-      data[offset] = asBDT.longValue();
+      data[offset] = asBDT.intValue();
    }
    
    /**

=== modified file 'src/com/goldencode/p2j/persist/orm/types/TypeManager.java'
--- old/src/com/goldencode/p2j/persist/orm/types/TypeManager.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/persist/orm/types/TypeManager.java	2026-05-22 13:57:10 +0000
@@ -17,7 +17,6 @@
 **                  type checks must include sub-classes, too.
 ** 004 CA  20241107 Added character constant support.
 ** 005 ICP 20250123 Added support for integer and int64 constants.
-** 006 RNC 20260401 Migrate RECID from 32-bit to 64-bit values.
 */
 
 /*
@@ -1287,12 +1286,12 @@
          recid asBDT = (recid) val;
          if (!asBDT.isUnknown())
          {
-            stmt.setLong(index, asBDT.getValue());
+            stmt.setInt(index, asBDT.intValue());
             return 1;
          }
       }
       
-      stmt.setNull(index, Types.BIGINT);
+      stmt.setNull(index, Types.INTEGER);
       return 1;
    }
    

=== modified file 'src/com/goldencode/p2j/schema/DmoAsmWorker.java'
--- old/src/com/goldencode/p2j/schema/DmoAsmWorker.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/schema/DmoAsmWorker.java	2026-05-22 13:57:10 +0000
@@ -2,7 +2,7 @@
 ** Module   : DmoAsmWorker.java
 ** Abstract : Pattern worker which assembles bytecode for DMO interfaces and classes in memory
 **
-** Copyright (c) 2015-2026, Golden Code Development Corporation.
+** Copyright (c) 2015-2023, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- --------------------------------------Description----------------------------------------
 ** 001 ECF 20150407 Created initial version, implemented using ASM v5.0.3.
@@ -23,7 +23,6 @@
 ** 011 OM  20200906 New ORM implementation.
 **     ECF 20221207 Enable the assembly of permanent table DMO interfaces.
 ** 012 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
-** 013 RNC 20260401 Migrate RECID from 32-bit to 64-bit values.
 */
 
 /*
@@ -835,7 +834,6 @@
                      break;
                   }
                   case TYPE_INT64:
-                  case TYPE_RECID:
                   {
                      int64 i = new int64(initText);
                      mv.visitLdcInsn(i.longValue());
@@ -844,6 +842,7 @@
                   }
                   case TYPE_ARRAYLIST:
                   case TYPE_INTEGER:
+                  case TYPE_RECID:
                   {
                      fieldCtorParms = "I";
                      integer i = new integer(initText);

=== modified file 'src/com/goldencode/p2j/util/recid.java'
--- old/src/com/goldencode/p2j/util/recid.java	2026-05-15 11:20:28 +0000
+++ new/src/com/goldencode/p2j/util/recid.java	2026-05-22 13:57:10 +0000
@@ -2,7 +2,7 @@
 ** Module   : recid.java
 ** Abstract : Progress 4GL compatible record ID
 **
-** Copyright (c) 2005-2026, Golden Code Development Corporation.
+** Copyright (c) 2005-2023, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- --JPRM-- --------------------------Description-------------------------------
 ** 001 SVL 20090225   @41371 Created initial version. Progress 4GL compatible
@@ -25,7 +25,6 @@
 ** 009 GES 20171207          Removed forced version of assign().
 ** 010 IAS 20201007          Added Type enum
 ** 011 CA  20230215          'duplicate()' method returns the real type instead of BDT.
-** 012 RNC 20260401          Migrate RECID from 32-bit to 64-bit values.
 */
 /*
 ** This program is free software: you can redistribute it and/or modify
@@ -89,7 +88,7 @@
  * as {@link integer} except default format.
  */
 public class recid
-extends int64
+extends integer
 {
    /** Default format string for this data type. */
    private static String defFmt = ">>>>>>9";

