=== modified file 'src/com/goldencode/p2j/jmx/FwdJMX.java'
--- old/src/com/goldencode/p2j/jmx/FwdJMX.java	2023-05-12 10:05:12 +0000
+++ new/src/com/goldencode/p2j/jmx/FwdJMX.java	2023-07-04 11:18:24 +0000
@@ -27,6 +27,7 @@
 **     OM  20230112 Added finer-granulation instrumentation for processing of dynamic queries. 
 ** 004 RAA 20230302 Removed OrmRedo and OrmNoUndoTrace.
 ** 005 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
+** 006 RAA 20230704 Added InsertUpdateValidateTotal and InsertUpdateValidateSuccess.
 */ 
 
 /*
@@ -544,6 +545,15 @@
    public static enum Counter 
    implements OptionalCounter
    {
+      /** How many Insert/Update statements that contain the VALIDATE keyword are there */
+      InsertUpdateValidateTotal,
+      /**
+       * How many Insert/Update statements that contain the VALIDATE keyword have been executed successfully.
+       */
+      InsertUpdateValidateSuccess,
+      /** How many inserts with VALIDATE were cured after failing the first time. */
+      InsertValidateCured,
+      
       /** Incomning network traffic */
       NetworkReads,
       /** Outgoing network traffic */

=== modified file 'src/com/goldencode/p2j/persist/orm/Persister.java'
--- old/src/com/goldencode/p2j/persist/orm/Persister.java	2023-07-04 08:04:26 +0000
+++ new/src/com/goldencode/p2j/persist/orm/Persister.java	2023-07-04 11:18:24 +0000
@@ -27,6 +27,7 @@
 **                  more straight-forward.
 **     RAA 20230623 Instead of appending "validate" to the insert SQL, insertValidateSql is now used.
 **     RAA 20230704 Changed name of validateMode to allowDBUniqueCheck.
+**     RAA 20230704 Added JMXs for counting how many Inserts/Updates with VALIDATE are executed.
 */
 
 /*
@@ -88,6 +89,7 @@
 import java.util.*;
 import java.util.logging.*;
 import com.goldencode.cache.*;
+import com.goldencode.p2j.jmx.*;
 import com.goldencode.p2j.persist.*;
 import com.goldencode.p2j.persist.orm.types.*;
 import com.goldencode.p2j.util.logging.*;
@@ -112,6 +114,14 @@
    /** Cache of FQL strings, with or without max results and start offsets, to queries. */
    private static ExpiryCache<UpdateInfo, UpdateInfo> updateCache = new LRUCache<>(4096);
    
+   /** Counter for how many Inserts/Updates with VALIDATE are executed. */
+   private static final SimpleCounter INSERT_UPDATE_VALIDATE_TOTAL = 
+            SimpleCounter.getInstance(FwdJMX.Counter.InsertUpdateValidateTotal);
+   
+   /** Counter for how many Inserts/Updates with VALIDATE are successfully executed. */
+   private static final SimpleCounter INSERT_UPDATE_VALIDATE_SUCCESS = 
+            SimpleCounter.getInstance(FwdJMX.Counter.InsertUpdateValidateSuccess);
+   
    /**
     * Constructor.
     * 
@@ -786,11 +796,21 @@
                LOG.log(Level.FINE, "FWD ORM: " + ps);
             }
             
+            if (allowDBUniqueCheck)
+            {
+               INSERT_UPDATE_VALIDATE_TOTAL.update(1);
+            }
+            
             int[] result = new int[1];
             SQLStatementLogger.getLogger().log(session.getDatabase(), ps,
                                                () -> result[0] = ps.executeUpdate());
             updateCount += result[0];
             
+            if (allowDBUniqueCheck)
+            {
+               INSERT_UPDATE_VALIDATE_SUCCESS.update(1);
+            }
+            
             if (updateCount == 0 && LOG.isLoggable(Level.WARNING))
             {
                LOG.log(Level.WARNING,
@@ -963,9 +983,23 @@
                }
                else
                {
+                  boolean countValidateJMX = INSERT_UPDATE_VALIDATE_TOTAL.isEnabled()   &&
+                                             INSERT_UPDATE_VALIDATE_SUCCESS.isEnabled() && 
+                                             ps.toString().contains("validate");
+                  
+                  if (countValidateJMX)
+                  {
+                     INSERT_UPDATE_VALIDATE_TOTAL.update(1);
+                  }
+                           
                   // non-batch insert is executed inline here
                   SQLStatementLogger.getLogger().log(session.getDatabase(), ps, () -> ps.execute());
                   count++;
+                  
+                  if (countValidateJMX)
+                  {
+                     INSERT_UPDATE_VALIDATE_SUCCESS.update(1);
+                  }
                }
             }
             
@@ -973,10 +1007,24 @@
             // executes the batches (since the scope of the batch is larger than a single DMO)
             if (batch && !bulk)
             {
+               boolean countValidateJMX = INSERT_UPDATE_VALIDATE_TOTAL.isEnabled()   &&
+                                          INSERT_UPDATE_VALIDATE_SUCCESS.isEnabled() && 
+                                          ps.toString().contains("validate");
+               
+               if (countValidateJMX)
+               {
+                  INSERT_UPDATE_VALIDATE_TOTAL.update(1);
+               }
+                        
                int[] result = new int[1];
                SQLStatementLogger.getLogger().log(session.getDatabase(), ps,
                                                   () -> result[0] = executeBatch(ps));
                count += result[0];
+               
+               if (countValidateJMX)
+               {
+                  INSERT_UPDATE_VALIDATE_SUCCESS.update(1);
+               }
             }
             
             // [i] was only advanced through first element in the extent series; need to push

=== modified file 'src/com/goldencode/p2j/persist/orm/Validation.java'
--- old/src/com/goldencode/p2j/persist/orm/Validation.java	2023-07-04 11:11:35 +0000
+++ new/src/com/goldencode/p2j/persist/orm/Validation.java	2023-07-04 11:19:46 +0000
@@ -39,6 +39,7 @@
 **     RAA 20230619 Split flush function into two for more straight-forward use when dealing with VALIDATE.
 **     RAA 20230704 Changed name of validateMode to allowDBUniqueCheck.
 **     RAA 20230704 In case a flush with VALIDATE fails, but it gets cured, a second flush is now attempted.
+**     RAA 20230704 Added JMXs for counting how many Inserts/Updates with VALIDATE are cured.
 */
 
 /*
@@ -100,6 +101,7 @@
 import java.sql.*;
 import java.util.*;
 import com.goldencode.p2j.directory.*;
+import com.goldencode.p2j.jmx.*;
 import com.goldencode.p2j.persist.*;
 import com.goldencode.p2j.persist.dialect.*;
 import com.goldencode.p2j.persist.orm.types.*;
@@ -160,6 +162,10 @@
    /** Flag indicating the record was flushed to the database (either newly inserted or updated) */
    private boolean flushed = false;
    
+   /** Counter for how many Inserts/Updates with VALIDATE are executed. */
+   private static final SimpleCounter INSERT_VALIDATE_CURED = 
+            SimpleCounter.getInstance(FwdJMX.Counter.InsertValidateCured);
+   
    /**
     * The maximum permitted size for all combined fields of an index. By default this
     * is equal to Progress version 10.x limit of 1971 bytes.
@@ -447,7 +453,7 @@
          if ((multiplex != null || !flush) && !allowDBUniqueCheck)
          {
             // validate by executing unique index queries
-            validateUniqueByQuery(check);
+            validateUniqueByQuery(check, false);
          }
          
          if (willFlush)
@@ -709,7 +715,7 @@
             {
                // if the right index is found, this method will not return normally, instead it will
                // throw a specific exception
-               validateUniqueByQuery(null);
+               validateUniqueByQuery(null, allowDBUniqueCheck);
                if (allowDBUniqueCheck)
                {
                   // We were not able to execute the query with VALIDATE, but a "cure" occurred,
@@ -1113,13 +1119,16 @@
     * @param   check
     *          A bit set whose set bits represent the positions of the unique indices in the DMO's array
     *          of unique indices to be checked. If {@code null}, check all the unique indices.
+    * @param   allowDBUniqueCheck
+    *          Flag that marks whether the database is capable of doing the index validation itself, meaning
+    *          that server-side validation can be skipped.
     * 
     * @throws  ValidationException
     *          when the method detects a collision of current DMO with another record stored in the database.
     * @throws  PersistenceException
     *          if an error occurred while accessing the database.
     */
-   private void validateUniqueByQuery(BitSet check)
+   private void validateUniqueByQuery(BitSet check, boolean allowDBUniqueCheck)
    throws PersistenceException,
           ValidationException
    {
@@ -1188,6 +1197,10 @@
             {
                throw new ValidationException(getFailUniqueIndexMessage(uIndex, bufferName, dmo), 132);
             }
+            else if (allowDBUniqueCheck)
+            {
+               INSERT_VALIDATE_CURED.update(1);
+            }
             
             // if we exit the loop normally, all violations are cured by in-memory changes (which may yet
             // have to be validated and flushed themselves)

