=== modified file 'rules/schema/dmo_common.rules'
--- rules/schema/dmo_common.rules	2022-07-27 21:57:13 +0000
+++ rules/schema/dmo_common.rules	2023-02-06 13:10:05 +0000
@@ -923,6 +923,13 @@
                   tpl.graft('annotation_assign_true', null, tableAst, "key", "dirtyRead")
                </action>
             </rule>
+
+            <!-- graft "dirty-intra-read" annotation -->
+            <rule>copy.isAnnotation("dirty-intra-read") and getNoteBoolean("dirty-intra-read")
+               <action>
+                  tpl.graft('annotation_assign_true', null, tableAst, "key", "dirtyIntraRead")
+               </action>
+            </rule>
             
             <!-- index annotations -->
             <action>idxAst = copy.getImmediateChild(data.index, null)</action>

=== modified file 'rules/schema/p2o.xml'
--- rules/schema/p2o.xml	2022-07-27 21:57:13 +0000
+++ rules/schema/p2o.xml	2023-02-06 12:58:37 +0000
@@ -1141,6 +1141,10 @@
             <rule>getNoteBoolean("dirty-read") == true or hints.isTableDirtyRead(text)
                <action>peer.putAnnotation("dirty-read", true)</action>
             </rule>
+
+            <rule>getNoteBoolean("dirty-intra-read") == true or hints.isTableDirtyIntraRead(text)
+               <action>peer.putAnnotation("dirty-intra-read", true)</action>
+            </rule>
             
             <!-- if table has a description, apply it as an annotation -->
             <rule>descText != null

=== modified file 'src/com/goldencode/p2j/persist/AdaptiveFind.java'
--- src/com/goldencode/p2j/persist/AdaptiveFind.java	2021-03-10 12:42:16 +0000
+++ src/com/goldencode/p2j/persist/AdaptiveFind.java	2023-02-06 20:51:25 +0000
@@ -550,16 +550,19 @@
          return;
       }
       
-      DirtyShareContext dirtyContext = buffer.getDirtyContext();
-      if (dirtyContext == null)
-      {
-         return;
-      }
-      
-      if (dirtyContext.isEntityDirty(entity))
-      {
-         AdaptiveComponent comp = (AdaptiveComponent) components().next();
-         invalidate(comp);
+      if (DirtyShareSupport.enabledCrossSession)
+      {
+         DirtyShareContext dirtyContext = buffer.getDirtyContext();
+         if (dirtyContext == null)
+         {
+            return;
+         }
+
+         if (dirtyContext.isEntityDirty(entity))
+         {
+            AdaptiveComponent comp = (AdaptiveComponent) components().next();
+            invalidate(comp);
+         }
       }
    }
 }

=== modified file 'src/com/goldencode/p2j/persist/AdaptiveQuery.java'
--- src/com/goldencode/p2j/persist/AdaptiveQuery.java	2022-10-05 08:54:36 +0000
+++ src/com/goldencode/p2j/persist/AdaptiveQuery.java	2023-02-06 22:14:40 +0000
@@ -396,6 +396,8 @@
 import java.util.*;
 import java.util.function.*;
 import java.util.logging.*;
+
+import com.goldencode.p2j.persist.dirty.*;
 import com.goldencode.p2j.persist.event.*;
 import com.goldencode.p2j.persist.lock.*;
 import com.goldencode.p2j.persist.orm.*;
@@ -2831,7 +2833,9 @@
       }
       
       // Possibly invalidate if another session has made a change.
-      if (isGlobalInvalidation())
+      if (DirtyShareSupport.enabledCrossSession &&
+          DirtyShareSupport.globalNotifications &&
+          isGlobalInvalidation())
       {
          Iterator<QueryComponent> iter = components();
          while (iter.hasNext())

=== modified file 'src/com/goldencode/p2j/persist/PreselectQuery.java'
--- src/com/goldencode/p2j/persist/PreselectQuery.java	2022-11-17 12:31:48 +0000
+++ src/com/goldencode/p2j/persist/PreselectQuery.java	2023-02-06 20:54:47 +0000
@@ -5844,7 +5844,8 @@
                }
             }
             
-            DirtyShareContext dirtyContext = buffer.getDirtyContext();
+            DirtyShareContext dirtyContext = DirtyShareSupport.enabledCrossSession ?
+                                                buffer.getDirtyContext() : null;
             String entity = buffer.getEntityName();
             
             // if we have an ID only, a cached DMO, or we are working with a temp-table, fetch

=== modified file 'src/com/goldencode/p2j/persist/RandomAccessQuery.java'
--- src/com/goldencode/p2j/persist/RandomAccessQuery.java	2022-12-06 03:33:32 +0000
+++ src/com/goldencode/p2j/persist/RandomAccessQuery.java	2023-02-06 22:03:35 +0000
@@ -4435,7 +4435,9 @@
                         // evict from the session if not otherwise in use
                         buffer.evictDMOIfUnused(dmo);
                         
-                        if (dirtyContext != null && dirtyContext.isDirtyDelete(entity, id, true))
+                        if (dirtyContext != null &&
+                            DirtyShareSupport.enabledCrossSession &&
+                            dirtyContext.isDirtyDelete(entity, id, true))
                         {
                            // a record was found but it is being deleted in another session's
                            // uncommitted transaction
@@ -4562,7 +4564,8 @@
             // Also handle the case where the DMO found in the primary
             // database has been modified in such a way that it may no longer
             // satisfy the search criteria.
-            if (activeBundleKey != UNIQUE &&
+            if (DirtyShareSupport.enabledCrossSession &&
+                activeBundleKey != UNIQUE &&
                 (info.isDeleted() ||
                  (info.isModified() &&
                   (!info.isInserted() ||

=== modified file 'src/com/goldencode/p2j/persist/annotation/Table.java'
--- src/com/goldencode/p2j/persist/annotation/Table.java	2022-07-14 01:33:49 +0000
+++ src/com/goldencode/p2j/persist/annotation/Table.java	2023-02-06 22:31:27 +0000
@@ -132,8 +132,13 @@
    /** The name of the parent {@code AFTER-TABLE} if this is its {@code BEFORE-TABLE}. */
    String afterTable() default "";
    
-   /** Indicates that this table/DMO needs to be tracked by the dirty share manager. */
+   /** Indicates that this table/DMO needs to be tracked by the dirty share manager across sessions. */
    boolean dirtyRead() default false;
+
+   /**
+    * Indicates that this table/DMO needs to be tracked by the dirty share manager within the same session.
+    */
+   boolean dirtyIntraRead() default false;
    
    /** The legacy VALEXP as string. */
    String valexp() default "";

=== modified file 'src/com/goldencode/p2j/persist/dirty/DefaultDirtyShareManager.java'
--- src/com/goldencode/p2j/persist/dirty/DefaultDirtyShareManager.java	2022-12-21 11:41:52 +0000
+++ src/com/goldencode/p2j/persist/dirty/DefaultDirtyShareManager.java	2023-02-06 22:48:09 +0000
@@ -262,7 +262,8 @@
       
       this.dirtyDatabase = dirty;
       this.indexHelper = IndexHelper.get(database);
-      this.eventManager = new GlobalEventManager(database);
+      this.eventManager = DirtyShareSupport.enabledCrossSession && DirtyShareSupport.globalNotifications ?
+                             new GlobalEventManager(database) : null;
       this.lockManager = new InMemoryLockManager<String>()
       {
          protected boolean isHeadless() { return true; }
@@ -279,14 +280,15 @@
     *          change.
     * 
     * @return  A unique identifier for the registrant, assigned by the event
-    *          clearinghouse, used later to collect events and deregister.
+    *          clearinghouse, used later to collect events and deregister or
+    *          <code>null</code> if global events are disabled.
     * 
     * @see     #getGlobalEvents(long, long)
     * @see     #deregisterForGlobalEvents(long)
     */
-   public long registerForGlobalEvents(String entity)
+   public Long registerForGlobalEvents(String entity)
    {
-      return eventManager.register(entity);
+      return eventManager != null ? eventManager.register(entity) : null;
    }
    
    /**
@@ -301,7 +303,10 @@
     */
    public void deregisterForGlobalEvents(long registerID)
    {
-      eventManager.deregister(registerID);
+      if (eventManager != null)
+      {
+         eventManager.deregister(registerID);
+      }
    }
    
    /**
@@ -327,7 +332,7 @@
    public GlobalChangeEvent[] getGlobalEvents(long registerID, long lastID)
    throws EventRegistrationException
    {
-      return eventManager.getEvents(registerID, lastID);
+      return eventManager != null ? eventManager.getEvents(registerID, lastID) : null;
    }
    
    /**
@@ -597,8 +602,11 @@
          applyUpdates(entity, copy, properties, extIndexes, values);
          session.save(copy, true);
          session.commit();
-         
-         eventManager.enqueueUpdate(entity, copy, properties);
+
+         if (eventManager != null)
+         {
+            eventManager.enqueueUpdate(entity, copy, properties);
+         }
          
          synchronized (changeLock)
          {
@@ -825,8 +833,11 @@
                }
             }
          }
-         
-         eventManager.enqueueDelete(entity, id);
+
+         if (eventManager != null)
+         {
+            eventManager.enqueueDelete(entity, id);
+         }
       }
    }
    
@@ -1580,12 +1591,18 @@
                   indexUpdateStates.put(ident, indexState);
                }
             }
-            
-            eventManager.enqueueInsert(entity, copy);
+
+            if (eventManager != null)
+            {
+               eventManager.enqueueInsert(entity, copy);
+            }
          }
          else
          {
-            eventManager.enqueueUpdate(entity, copy, properties);
+            if (eventManager != null)
+            {
+               eventManager.enqueueUpdate(entity, copy, properties);
+            }
          }
       }
       catch (PersistenceException exc)

=== modified file 'src/com/goldencode/p2j/persist/dirty/DirtyShareFactory.java'
--- src/com/goldencode/p2j/persist/dirty/DirtyShareFactory.java	2023-01-16 17:19:10 +0000
+++ src/com/goldencode/p2j/persist/dirty/DirtyShareFactory.java	2023-02-06 21:20:20 +0000
@@ -89,12 +89,6 @@
    /** Map of DirtyShareManager to Session instances. */
    private static final Map<DirtyShareManager, Session> sessions = new HashMap<>();
 
-   /** {@code true} if dirty sharing is enabled. */
-   private static final boolean enabled = Utils.getDirectoryNodeBoolean(null,
-                                                                        "persistence/dirty_share",
-                                                                        true,
-                                                                        false);
-
    /** Context local map of Databases to DirtyShareContext instances */
    private static final ContextLocal<Map<Database, DirtyShareContext>> local =
       new ContextLocal<Map<Database, DirtyShareContext>>()
@@ -132,7 +126,7 @@
     */
    public static DirtyShareContext getContextInstance(Database database)
    {
-      if (!enabled ||
+      if (!(DirtyShareSupport.enabledCrossSession || DirtyShareSupport.enabledIntraSession) ||
           database.getType() == Database.Type.PRIMARY_NON_DIRTY ||
           DatabaseManager.TEMP_TABLE_DB.equals(database))
       {

=== modified file 'src/com/goldencode/p2j/persist/dirty/DirtyShareManager.java'
--- src/com/goldencode/p2j/persist/dirty/DirtyShareManager.java	2020-09-06 23:15:41 +0000
+++ src/com/goldencode/p2j/persist/dirty/DirtyShareManager.java	2023-02-06 22:49:17 +0000
@@ -124,12 +124,13 @@
     *          change.
     * 
     * @return  A unique identifier for the registrant, assigned by the event
-    *          clearinghouse, used later to collect events and deregister.
+    *          clearinghouse, used later to collect events and deregister or
+    *          <code>null</code> if global events are disabled.
     * 
     * @see     #getGlobalEvents(long, long)
     * @see     #deregisterForGlobalEvents(long)
     */
-   public long registerForGlobalEvents(String entity);
+   public Long registerForGlobalEvents(String entity);
    
    /**
     * Deregister with a global event clearinghouse an interest in insert,

=== modified file 'src/com/goldencode/p2j/persist/dirty/DirtyShareSupport.java'
--- src/com/goldencode/p2j/persist/dirty/DirtyShareSupport.java	2020-07-02 15:51:44 +0000
+++ src/com/goldencode/p2j/persist/dirty/DirtyShareSupport.java	2023-02-06 23:22:23 +0000
@@ -91,9 +91,32 @@
  */
 public final class DirtyShareSupport
 {
-   /** Flag which overrides the absence of DMO dirty-read annotations to force dirty sharing globally */
-   public static final boolean forceDirtyShare;
-   
+   /**
+    * {@code true} if cross-session dirty sharing is enabled.
+    */
+   public static final boolean enabledCrossSession;
+
+   /**
+    * {@code true} if dirty sharing within the same session is enabled.
+    */
+   public static final boolean enabledIntraSession;
+
+   /**
+    * Flag which overrides the absence of DMO dirty-read annotations to force dirty sharing across sessions.
+    */
+   public static final boolean forceCrossSession;
+
+   /**
+    * Flag which overrides the absence of DMO dirty-intra-read annotations to force dirty sharing within the
+    * same session.
+    */
+   public static final boolean forceIntraSession;
+
+   /**
+    * {@code true} if global notifications are enabled for cross-session dirty sharing.
+    */
+   public static final boolean globalNotifications;
+
    /** Level of detailed information to collect and log */
    public static final Level logLevel;
    
@@ -103,33 +126,30 @@
    static
    {
       logLevel = log.getLevel();
-      
-      // The dirty share feature is enabled globally by default (i.e., all tables, all databases, except
-      // temp-tables). This is the safer thing to do functionally, but it is NOT good for performance.
-      // So, we allow this default behavior to be disabled in the directory, once the DMOs that really
-      // need it, if any, have been identified and marked with the "dirty-read" annotation. When disabled
-      // globally, only those DMOs will use the dirty-share features.
-      
-      // enabled by default, if there is no directory override
-      boolean disabled = false;
-      
+
       DirectoryService ds = DirectoryService.getInstance();
       if (!ds.bind())
       {
          throw new RuntimeException("Directory bind failed");
       }
-      
+
       try
       {
-         // configure global dirty sharing from directory, if available; enabled by default
-         String path = "persistence/disable-global-dirty-share";
-         disabled = Utils.getDirectoryNodeBoolean(ds, path, false, false);
+         // configure dirty sharing from the directory
+         enabledCrossSession = Utils.getDirectoryNodeBoolean(ds,
+                                 "persistence/dirty-cross-share", false, false);
+         enabledIntraSession = Utils.getDirectoryNodeBoolean(ds,
+                                 "persistence/dirty-intra-share", false, false);
+         forceCrossSession = Utils.getDirectoryNodeBoolean(ds,
+                                 "persistence/force-dirty-cross-share", false, false);
+         forceIntraSession = Utils.getDirectoryNodeBoolean(ds,
+                                 "persistence/force-dirty-intra-share", false, false);
+         globalNotifications = Utils.getDirectoryNodeBoolean(ds,
+                                 "persistence/dirty-share-global-notifications", false, false);
       }
       finally
       {
          ds.unbind();
-         
-         forceDirtyShare = !disabled;
       }
    }
 }

=== modified file 'src/com/goldencode/p2j/persist/orm/DmoMeta.java'
--- src/com/goldencode/p2j/persist/orm/DmoMeta.java	2022-12-23 00:51:34 +0000
+++ src/com/goldencode/p2j/persist/orm/DmoMeta.java	2023-02-06 17:05:21 +0000
@@ -219,6 +219,9 @@
    
    /** Caches the {@code dirtyRead} attribute from {@link Table} annotation. */
    public final boolean dirtyRead;
+
+   /** Caches the {@code dirtyIntraRead} attribute from {@link Table} annotation. */
+   public final boolean dirtyIntraRead;
    
    /** Caches the {@code hasValidation} attribute from {@link Table} annotation. */
    public final boolean hasValidation;
@@ -317,6 +320,7 @@
       this.legacyTable = table.legacy().intern();
       this.normalizedLegacyTable = this.legacyTable.toLowerCase().intern();
       this.dirtyRead = table.dirtyRead();
+      this.dirtyIntraRead = table.dirtyIntraRead();
       this.afterTable = table.afterTable();
       this.beforeTable = table.beforeTable();
       this.category = table.category();
@@ -521,7 +525,8 @@
     */
    public boolean isDirtyRead()
    {
-      return DirtyShareSupport.forceDirtyShare || dirtyRead;
+      return DirtyShareSupport.enabledCrossSession && (DirtyShareSupport.forceCrossSession || dirtyRead) ||
+             DirtyShareSupport.enabledIntraSession && (DirtyShareSupport.forceIntraSession || dirtyIntraRead);
    }
    
    /**

=== modified file 'src/com/goldencode/p2j/persist/remote/DirtyShareMultiplexer.java'
--- src/com/goldencode/p2j/persist/remote/DirtyShareMultiplexer.java	2020-09-01 20:09:38 +0000
+++ src/com/goldencode/p2j/persist/remote/DirtyShareMultiplexer.java	2023-02-06 22:52:02 +0000
@@ -141,12 +141,13 @@
     *          change.
     * 
     * @return  A unique identifier for the registrant, assigned by the event
-    *          clearinghouse, used later to collect events and deregister.
+    *          clearinghouse, used later to collect events and deregister or
+    *          <code>null</code> if global events are disabled.
     * 
     * @see     #getGlobalEvents(int, long, long)
     * @see     #deregisterForGlobalEvents(int, long)
     */
-   public long registerForGlobalEvents(int database, String entity);
+   public Long registerForGlobalEvents(int database, String entity);
    
    /**
     * Deregister with a global event clearinghouse an interest in insert,

=== modified file 'src/com/goldencode/p2j/persist/remote/DirtyShareMultiplexerImpl.java'
--- src/com/goldencode/p2j/persist/remote/DirtyShareMultiplexerImpl.java	2020-09-01 20:09:38 +0000
+++ src/com/goldencode/p2j/persist/remote/DirtyShareMultiplexerImpl.java	2023-02-06 22:55:22 +0000
@@ -165,12 +165,13 @@
     *          change.
     * 
     * @return  A unique identifier for the registrant, assigned by the event
-    *          clearinghouse, used later to collect events and deregister.
+    *          clearinghouse, used later to collect events and deregister or
+    *          <code>null</code> if global events are disabled.
     * 
     * @see     #getGlobalEvents(int, long, long)
     * @see     #deregisterForGlobalEvents(int, long)
     */
-   public long registerForGlobalEvents(int database, String entity)
+   public Long registerForGlobalEvents(int database, String entity)
    {
       return lookupWorker(database).registerForGlobalEvents(entity);
    }

=== modified file 'src/com/goldencode/p2j/persist/remote/RemoteDirtyShareManager.java'
--- src/com/goldencode/p2j/persist/remote/RemoteDirtyShareManager.java	2020-09-01 20:09:38 +0000
+++ src/com/goldencode/p2j/persist/remote/RemoteDirtyShareManager.java	2023-02-06 22:49:17 +0000
@@ -171,12 +171,13 @@
     *          change.
     * 
     * @return  A unique identifier for the registrant, assigned by the event
-    *          clearinghouse, used later to collect events and deregister.
+    *          clearinghouse, used later to collect events and deregister or
+    *          <code>null</code> if global events are disabled.
     * 
     * @see     #getGlobalEvents(long, long)
     * @see     #deregisterForGlobalEvents(long)
     */
-   public long registerForGlobalEvents(String entity)
+   public Long registerForGlobalEvents(String entity)
    {
       return multiplexer.registerForGlobalEvents(mpID, entity);
    }

=== modified file 'src/com/goldencode/p2j/schema/TableHints.java'
--- src/com/goldencode/p2j/schema/TableHints.java	2022-07-27 21:57:13 +0000
+++ src/com/goldencode/p2j/schema/TableHints.java	2023-02-06 23:03:34 +0000
@@ -136,6 +136,9 @@
    
    /** {@code dirty-read} attribute of table. */
    private static final String ATTR_DIRTY_READ = "dirty-read";
+
+   /** {@code dirty-intra-read} attribute of table. */
+   private static final String ATTR_DIRTY_INTRA_READ = "dirty-intra-read";
    
    /** Drop attribute of table and field elements */
    private static final String ATTR_DROP = "drop";
@@ -296,8 +299,13 @@
    /** Flag indicating that the table is read-only */  
    private final boolean readOnly;
    
-   /** Flag indicating that the table needs to be tracked by the dirty share manager. */
+   /** Flag indicating that the table needs to be tracked by the dirty share manager across sessions. */
    private final boolean dirtyRead;
+
+   /**
+    * Flag indicating that the table needs to be tracked by the dirty share manager within the same session.
+    */
+   private final boolean dirtyIntraRead;
    
    static
    {
@@ -349,6 +357,7 @@
       name = table.getAttribute(ATTR_NAME);
       readOnly = "true".equalsIgnoreCase(table.getAttribute(ATTR_READ_ONLY));
       dirtyRead = "true".equalsIgnoreCase(table.getAttribute(ATTR_DIRTY_READ));
+      dirtyIntraRead = "true".equalsIgnoreCase(table.getAttribute(ATTR_DIRTY_INTRA_READ));
       tableDrop = "true".equalsIgnoreCase(table.getAttribute(ATTR_DROP));
       escaped = "true".equalsIgnoreCase(table.getAttribute(HintsConstants.ATTR_ESCAPE));
       
@@ -1047,7 +1056,7 @@
    
    /**
     * Indicate whether the table described by this hint needs to be tracked by the dirty share
-    * manager.
+    * manager across sessions.
     *
     * @return  {@code true} if this table is dirty-read.
     */
@@ -1055,6 +1064,17 @@
    {
       return dirtyRead;
    }
+
+   /**
+    * Indicate whether the table described by this hint needs to be tracked by the dirty share
+    * manager within the same session.
+    *
+    * @return  {@code true} if this table is dirty-intra-read.
+    */
+   public boolean isDirtyIntraRead()
+   {
+      return dirtyIntraRead;
+   }
    
    /**
     * Indicate whether the field of the table described by this hint is  

=== modified file 'src/com/goldencode/p2j/uast/UastHints.java'
--- src/com/goldencode/p2j/uast/UastHints.java	2022-10-28 10:54:33 +0000
+++ src/com/goldencode/p2j/uast/UastHints.java	2023-02-06 23:05:23 +0000
@@ -1095,7 +1095,8 @@
    }
    
    /**
-    * Checks if table is dirty-read (that is, it needs to be tracked by the dirty share manager).
+    * Checks if table is dirty-read (that is, it needs to be tracked by the dirty share manager across
+    * sessions).
     *
     * @param    table
     *           The legacy table name.
@@ -1111,6 +1112,25 @@
       }
       return false;
    }
+
+   /**
+    * Checks if table is dirty-intra-read (that is, it needs to be tracked by the dirty share manager within
+    * the same session).
+    *
+    * @param    table
+    *           The legacy table name.
+    *
+    * @return   value of "dirty-intra-read" hint for table if hint is set, otherwise {@code false}.
+    */
+   public boolean isTableDirtyIntraRead(String table)
+   {
+      if (tableHints != null )
+      {
+         TableHints tableHint = tableHints.get(table);
+         return tableHint != null  && tableHint.isDirtyIntraRead();
+      }
+      return false;
+   }
    
    /**
     * Report whether the database schema name for this schema should be escaped.

=== modified file 'src/com/goldencode/p2j/uast/UastHintsWorker.java'
--- src/com/goldencode/p2j/uast/UastHintsWorker.java	2022-04-18 16:27:11 +0000
+++ src/com/goldencode/p2j/uast/UastHintsWorker.java	2023-02-06 23:10:50 +0000
@@ -493,7 +493,7 @@
       
       /**
        * Checks if table is dirty-read  (that is, it needs to be tracked by the dirty share
-       * manager).
+       * manager across sessions).
        *
        * @param   table
        *          The legacy table name.
@@ -509,6 +509,25 @@
          
          return hints.isTableDirtyRead(table);
       }
+
+      /**
+       * Checks if table is dirty-read  (that is, it needs to be tracked by the dirty share
+       * manager within the same session).
+       *
+       * @param   table
+       *          The legacy table name.
+       *
+       * @return  value of "dirty-intra-read" hint for table if hint is set, otherwise {@code false}.
+       */
+      public boolean isTableDirtyIntraRead(String table)
+      {
+         if (!loadHints())
+         {
+            return false;
+         }
+
+         return hints.isTableDirtyIntraRead(table);
+      }
       
       /**
        * Loads the hints file for an AST if it exists and hasn't already been loaded.

