Project

General

Profile

7323-JMX-before.patch

Radu Apetrii, 07/20/2023 10:00 AM

Download (35.8 KB)

View differences:

new/src/com/goldencode/p2j/jmx/FwdJMX.java 2023-07-20 13:23:34 +0000
27 27
**     OM  20230112 Added finer-granulation instrumentation for processing of dynamic queries. 
28 28
** 004 RAA 20230302 Removed OrmRedo and OrmNoUndoTrace.
29 29
** 005 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
30
** 006 RAA 20230720 Added JMXs for measuring statistics about insert/update statements.
30 31
*/ 
31 32

  
32 33
/*
......
544 545
   public static enum Counter 
545 546
   implements OptionalCounter
546 547
   {
548
      /** How many Insert/Update statements are there */
549
      InsertUpdateTotal,
550
      /**
551
       * How many Insert/Update statements have been executed successfully.
552
       */
553
      InsertUpdateSuccess,
554
      /** How many Insert/Update statements were cured after failing the first time. */
555
      InsertUpdateCured,
556
      
547 557
      /** Incomning network traffic */
548 558
      NetworkReads,
549 559
      /** Outgoing network traffic */
new/src/com/goldencode/p2j/persist/Persistence.java 2023-07-20 13:23:34 +0000
592 592
**                           Added DATE-RELATION to the 'scroll', 'list', an 'getQuery' parameters
593 593
** 176 IAS 20230410          Added 'old' methods w/o DataRelation argument.
594 594
** 177 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
595
** 178 RAA 20230601          Added supportsValidateMode() function.
596
**     RAA 20230704          Changed name of validateMode to allowDBUniqueCheck.
597 595
*/
598 596

  
599 597
/*
......
3368 3366
   }
3369 3367
   
3370 3368
   /**
3371
    * Check if we can skip the index server-validation.
3372
    * 
3373
    * @return   {@code true} If the dialect has support for unique index validation, meaning we can skip 
3374
    *           this process in the server.
3375
    */
3376
   public boolean allowDBUniqueCheck()
3377
   {
3378
      return dialect.allowDBUniqueCheck() && temporary;
3379
   }
3380
   
3381
   /**
3382 3369
    * Initialize the helper objects to which this object delegates certain service requests.
3383 3370
    */
3384 3371
   protected void initializeInstance()
new/src/com/goldencode/p2j/persist/TempTableHelper.java 2023-07-20 13:23:34 +0000
60 60
**     DDF 20230306          Removed usage of computed columns. Added a parameter to getSqlMappedType() 
61 61
**                           (refs: #7108).
62 62
** 017 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
63
** 018 RAA 20230601          The keyword SOFT_UNIQUE can appear when building the "create index" statement.
64
**     RAA 20230619          Casted dialect to P2JH2Dialect when calling getCreateIndexString.
65 63
*/
66 64

  
67 65
/*
......
453 451
         explicitIndexes.put(new CaseInsensitiveString(index.getName()), sqlIdxName);
454 452
         
455 453
         StringBuilder sb = new StringBuilder();
456
         sb.append(((P2JH2Dialect)dialect).getCreateIndexString(false, index.isUnique() && !isDirty)).append(sqlIdxName)
454
         sb.append(dialect.getCreateIndexString(false)).append(sqlIdxName)
457 455
           // to enable UNIQUE constraint on temp-table indexes, replace the above line with following:
458 456
           // sb.append(dialect.getCreateIndexString(index.isUnique() && !isDirty)).append(sqlIdxName)
459 457
           .append(" on ").append(dmo.getSqlTableName()).append(" (");
......
514 512
         // resource names are unique. The dirty temp-tables indexes do not need the suffix.
515 513
         implicitSqlIndexName = "idx_mpid__" + dmo.getSqlTableName() + getAdditionalIndexSuffix();
516 514
         implicitIndexName = null;
517
         String sb = ((P2JH2Dialect)dialect).getCreateIndexString(false, !isDirty) + implicitSqlIndexName + 
518
                     " on " + dmo.getSqlTableName() + " (" + ReservedProperty._MULTIPLEX.column + ", " + 
519
                     Session.PK + " )" + suffix;
515
         String sb = dialect.getCreateIndexString(false) + implicitSqlIndexName + " on " + dmo.getSqlTableName() +
516
                     " (" + ReservedProperty._MULTIPLEX.column + ", " + Session.PK + " )" + 
517
                     suffix;
520 518
         ddlCreateIndexes.add(sb);
521 519
         ddlDropIndexes.add(dialect.getDropIndexString(true, implicitSqlIndexName, dmo.getSqlTableName()) + 
522 520
                            createTemporaryTablePostfix);
new/src/com/goldencode/p2j/persist/dialect/Dialect.java 2023-07-20 13:23:34 +0000
109 109
** 031 DDF 20230330          Changed return value of getComputedColumnPrefix from null to empty string.
110 110
** 032 DDF 20230406          Removed isAutoRtrimAndCi. Added isAutoRtrim and isAutoCi methods.
111 111
** 033 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
112
** 034 RAA 20230601          Added support for SOFT_UNIQUE keyword.
113
**     RAA 20230619          There are two getCreateIndexString functions now, the second one being
114
**                           responsible with handling SOFT_UNIQUE indexes.
115
**     RAA 20230704          Changed name of validateMode to allowDBUniqueCheck.
116 112
*/
117 113

  
118 114
/*
......
1392 1388
   }
1393 1389
   
1394 1390
   /**
1395
    * Returns the beginning of the DDL statement for creating an index.
1396
    * 
1397
    * @param   unique
1398
    *          {@code true} for unique indexes.
1399
    * @param   softUnique
1400
    *          {@code true} for soft-unique indexes.
1401
    * 
1402
    * @return  the beginning of the DDL statement for creating an index. 
1403
    */
1404
   public String getCreateIndexString(boolean unique, boolean softUnique) 
1405
   throws PersistenceException
1406
   {
1407
      throw new PersistenceException("soft_unique is not accessible from this dialect");
1408
   }
1409
   
1410
   /**
1411 1391
    * Obtain a DDL statement which drops a SQL index.
1412 1392
    *
1413 1393
    * @param   ifExists
......
1654 1634
   {
1655 1635
      return false;
1656 1636
   }
1657
   
1658
   /**
1659
    * Determine if the {@code Dialect} supports index validation (with the VALIDATE keyword).
1660
    * 
1661
    * @return  {@code false}
1662
    *          By default, VALIDATE is not supported.   
1663
    */
1664
   public boolean allowDBUniqueCheck()
1665
   {
1666
      return false;
1667
   }
1668 1637

  
1669 1638
   /**
1670 1639
    * Provides SQL to load sequences.
new/src/com/goldencode/p2j/persist/dialect/P2JH2Dialect.java 2023-07-20 13:23:34 +0000
120 120
**                           insensitive in isCaseInsensitiveColumn because it is rarely used.
121 121
** 034 DDF 20230406          Removed isAutoRtrimAndCi. Added isAutoRtrim and isAutoCi methods.
122 122
** 035 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
123
** 036 RAA 20230601          Added supportsValidateMode function.
124
**     RAA 20230619          Overrided getCreateIndexString function that deals with SOFT_UNIQUE indexes.
125
**     RAA 20230704          Changed name of validateMode to allowDBUniqueCheck.
126 123
*/
127 124

  
128 125
/*
......
1774 1771
   }
1775 1772
   
1776 1773
   /**
1777
    * Determine if the {@code Dialect} supports index validation (with the VALIDATE keyword).
1778
    * 
1779
    * @return  {@code true}
1780
    *          H2 supports this type of validation.   
1781
    */
1782
   @Override
1783
   public boolean allowDBUniqueCheck()
1784
   {
1785
      return true;
1786
   }
1787
   
1788
   /**
1789
    * Returns the beginning of the DDL statement for creating an index.
1790
    * 
1791
    * @param   unique
1792
    *          {@code true} for unique indexes.
1793
    * @param   softUnique
1794
    *          {@code true} for soft-unique indexes.
1795
    * 
1796
    * @return  the beginning of the DDL statement for creating an index. 
1797
    */
1798
   @Override
1799
   public String getCreateIndexString(boolean unique, boolean softUnique)
1800
   {
1801
      if (unique)
1802
      {
1803
         return "create unique index ";
1804
      }
1805
      
1806
      if (softUnique)
1807
      {
1808
         return "create soft_unique index ";
1809
      }
1810
      
1811
      return "create index ";
1812
   }
1813
   
1814
   /**
1815 1774
    * Get dialect id.
1816 1775
    * 
1817 1776
    * @return  Dialect id.
new/src/com/goldencode/p2j/persist/dialect/P2JSQLServer2008Dialect.java 2023-07-20 13:23:34 +0000
46 46
** 016 RAA 20230208 Added parameter for getCreateTemporaryTablePostfix.
47 47
**     DDF 20230306 Added a parameter to getSqlMappedType() (refs: #7108).
48 48
** 017 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
49
** 018 RAA 20230601 Added support for the SOFT_UNIQUE keyword.
50
**     RAA 20230619 Reverted the call of getCreateIndexString so that SOFT_UNIQUE does not get involved.
51 49
*/
52 50

  
53 51
/*
new/src/com/goldencode/p2j/persist/orm/Persister.java 2023-07-20 13:23:34 +0000
22 22
**                  used in insert/update or where predicate.
23 23
**     CA  20230116 Increased updateCache size to 4096.
24 24
** 003 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
25
** 004 RAA 20230601 Added support for the VALIDATE keyword.
26
**     RAA 20230619 Split insert and update into two functions each, so dealing with VALIDATE is
27
**                  more straight-forward.
28
**     RAA 20230623 Instead of appending "validate" to the insert SQL, insertValidateSql is now used.
29
**     RAA 20230704 Changed name of validateMode to allowDBUniqueCheck.
30
**     RAA 20230719 In case of UNIQUE VIOLATION, the logging is now skipped in this class.
31 25
*/
32 26

  
33 27
/*
......
405 399
   <T extends BaseRecord> int insert(T dmo)
406 400
   throws PersistenceException
407 401
   {
408
      return insert(dmo, false);
409
   }
410
   
411
   /**
412
    * Insert the data of the given DMO into its corresponding primary table and any secondary
413
    * tables in the database. This method must be called within a transaction for the data to be
414
    * preserved. Scalar data is inserted first into the primary table, followed by normalized
415
    * extent field data, if any, into any secondary tables. Batch execution is used if supported
416
    * by the JDBC driver.
417
    * 
418
    * The Insert operation can be executed normally (having the server validate the index in use), 
419
    * or with the help of the {@code validate} and {@code soft_unique} keywords, which, when placed,
420
    * notify H2 that validation has not been done and it should do that process itself. This works only
421
    * with temporary databases.
422
    * 
423
    * @param   dmo
424
    *          DMO instance whose data is to be inserted into the database.
425
    * @param   allowDBUniqueCheck
426
    *          Flag (only for temporary databases) that marks if the index server-validation was skipped and
427
    *          it should be done by H2.
428
    * 
429
    * @return  The count of records (primary and secondary) successfully inserted into the
430
    *          database.
431
    * 
432
    * @throws  PersistenceException
433
    *          if an error occurs accessing the database.
434
    */
435
   <T extends BaseRecord> int insert(T dmo, boolean allowDBUniqueCheck)
436
   throws PersistenceException
437
   {
438
      PreparedStatement[] ps = prepareInsertStatements(dmo, allowDBUniqueCheck);
402
      PreparedStatement[] ps = prepareInsertStatements(dmo);
439 403
      
440 404
      try
441 405
      {
......
564 528
   <T extends BaseRecord> void update(T dmo)
565 529
   throws PersistenceException
566 530
   {
567
      update(dmo, false);
568
   }
569
   
570
   /**
571
    * Saves changes of the {@code dmo} by executing one or more {@code UPDATE} statements.
572
    * 
573
    * The Update operation can be executed normally (having the server validate the index in use), 
574
    * or with the help of the {@code validate} and {@code soft_unique} keywords, which, when placed,
575
    * notify H2 that validation has not been done and it should do that process itself. This works only
576
    * with temporary databases.
577
    * 
578
    * @param   dmo
579
    *          The object whose changes will be flushed to database.
580
    * @param   <T>
581
    *          The type(class) of the Record.
582
    * @param   allowDBUniqueCheck
583
    *          Flag (only for temporary databases) that marks whether index server-validation was skipped.
584
    *
585
    * @throws  PersistenceException
586
    *          In case of any issue encountered in the process.
587
    */
588
   <T extends BaseRecord> void update(T dmo, boolean allowDBUniqueCheck)
589
   throws PersistenceException
590
   {
591 531
      BitSet mods = dmo.getDirtyProps();
592 532
      if (mods.isEmpty())
593 533
      {
......
698 638
                  sqlBuilder.append(" and list__index=?");
699 639
                  sqlPar.add(ReservedProperty.ID_LIST__INDEX);  // extIdx
700 640
               }
701
               
702
               if (allowDBUniqueCheck)
703
               {
704
                  sqlBuilder.append(" validate");
705
               }
706 641
            }
707 642
            
708 643
            Map<Integer, String> sqlStrs = new LinkedHashMap<>();
......
802 737
         catch (SQLException exc)
803 738
         {
804 739
            String msg = "Failed to update dmo " + dmo.getClass().getName();
805
            
806 740
            if (LOG.isLoggable(Level.WARNING))
807 741
            {
808
               // Skip the logging of UNIQUE VIOLATION - unique_violation exception because this might be just a
809
               // failed attempt to update a record with validateMode enabled, meaning that no server-side index
810
               // validation took place. If this is not the case, logging will happen later anyway.
811
               Throwable cause = exc;
812
               while (cause != null                   &&
813
                     !(cause instanceof SQLException) &&
814
                     cause != cause.getCause())
815
               {
816
                  cause = cause.getCause();
817
               }
818
               while (cause != null && cause instanceof SQLException)
819
               {
820
                  SQLException sqle = (SQLException) cause;
821
                  String sqlState = sqle.getSQLState();
822
                  if (sqlState != null && !"23505".equals(sqlState)) // UNIQUE VIOLATION - unique_violation
823
                  {
824
                     // Log this error since it is not a UNIQUE VIOLATION
825
                     LOG.log(Level.WARNING, "Error during update", sqle);
826
                  }
827
                  
828
                  cause = sqle.getCause();
829
               }
742
               LOG.log(Level.WARNING, msg, exc);
830 743
            }
831 744
            
832 745
            throw new PersistenceException(msg, exc);
......
1013 926
      {
1014 927
         if (LOG.isLoggable(Level.WARNING))
1015 928
         {
1016
            // Skip the logging of UNIQUE VIOLATION - unique_violation exception because this might be just
1017
            // a failed attempt to insert a record with validateMode enabled, meaning that no server-side index
1018
            // validation took place. If this is not the case, logging will happen later anyway.
1019
            Throwable cause = exc;
1020
            while (cause != null                   &&
1021
                  !(cause instanceof SQLException) &&
1022
                  cause != cause.getCause())
1023
            {
1024
               cause = cause.getCause();
1025
            }
1026
            while (cause != null && cause instanceof SQLException)
1027
            {
1028
               SQLException sqle = (SQLException) cause;
1029
               String sqlState = sqle.getSQLState();
1030
               if (sqlState != null && !"23505".equals(sqlState)) // UNIQUE VIOLATION - unique_violation
1031
               {
1032
                  // Log this error since it is not a UNIQUE VIOLATION
1033
                  LOG.log(Level.WARNING, "Error during insert", sqle);
1034
               }
1035
               
1036
               cause = sqle.getCause();
1037
            }
929
            LOG.log(Level.WARNING, "Error during insert", exc);
1038 930
         }
1039 931
         
1040 932
         throw new PersistenceException(exc);
......
1060 952
   private <T extends BaseRecord> PreparedStatement[] prepareInsertStatements(T dmo)
1061 953
   throws PersistenceException
1062 954
   {
1063
      return prepareInsertStatements(dmo, false);
1064
   }
1065
   
1066
   /**
1067
    * Prepare the insert statements for the table(s) associated with the given DMO. There will
1068
    * be one statement for the primary table, plus one per secondary table for normalized extent
1069
    * fields.
1070
    * 
1071
    * @param   dmo
1072
    *          Data model object whose type is used to determine the insert statements to be
1073
    *          prepared.
1074
    * @param   allowDBUniqueCheck
1075
    *          Flag that marks whether index server-validation was skipped.
1076
    * 
1077
    * @return  An array of prepared insert statements, one for the primary table and the rest
1078
    *          (if any) for the secondary, normalized, extent field tables, in ascending order
1079
    *          of extent size.
1080
    * 
1081
    * @throws  PersistenceException
1082
    *          if there is an error preparing the statements.
1083
    */
1084
   private <T extends BaseRecord> PreparedStatement[] prepareInsertStatements(T dmo, 
1085
                                                                              boolean allowDBUniqueCheck)
1086
   throws PersistenceException
1087
   {
1088 955
      RecordMeta recMeta = dmo._recordMeta();
1089 956
      String[] insertSql = recMeta.insertSql;
1090
      String[] insertValidateSql = recMeta.insertValidateSql;
1091 957
      int tables = insertSql.length;
1092 958
      PreparedStatement[] ps = new PreparedStatement[tables];
1093 959
      
......
1097 963
         
1098 964
         for (int i = 0; i < tables; i++)
1099 965
         {
1100
            if (allowDBUniqueCheck && insertValidateSql[i] != null)
1101
            {
1102
               ps[i] = conn.prepareStatement(insertValidateSql[i]);
1103
            }
1104
            else
1105
            {
1106
               ps[i] = conn.prepareStatement(insertSql[i]);
1107
            }
966
            ps[i] = conn.prepareStatement(insertSql[i]);
1108 967
         }
1109 968
      }
1110 969
      catch (SQLException exc)
new/src/com/goldencode/p2j/persist/orm/RecordMeta.java 2023-07-20 13:23:34 +0000
27 27
**     CA  20221114 Added a helper array to keep the index of the normalized extent properties in the DMO
28 28
**                  'props' array.
29 29
** 003 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
30
** 004 RAA 20230623 Added insertValidateSql.
31 30
*/
32 31

  
33 32
/*
......
154 153
   /** SQL insert statement(s) which insert a single DMO's data into the database */
155 154
   final String[] insertSql;
156 155
   
157
   /**
158
    * SQL insert statement(s) which insert a single DMO's data into the database.
159
    * The keyword "validate" is included at the end. Pairing it with "soft_unique" would tell the server
160
    * that no index validation should be done because H2 will take care of it.
161
    * At the moment, this works only for temporary databases.
162
    */
163
   final String[] insertValidateSql;
164
   
165 156
   /** SQL delete statement(s) which delete a single DMO's data from the database */
166 157
   final String[] deleteSql;
167 158
   
......
259 250
      insertSql = Persister.composeInsertStatements(tables[0], props, temporary);
260 251
      deleteSql = Persister.composeDeleteStatements(tables);
261 252
      
262
      // compose the insert SQLs, but with the "validate" keyword at the end.
263
      // these SQLs are only for temporary databases and, paired with "soft_unique", notify the server
264
      // that no index validation is required because H2 will take care of it.
265
      if (temporary)
266
      {
267
         int insertSqlLength = insertSql.length;
268
         insertValidateSql = new String[insertSqlLength];
269
         for (int i = 0; i < insertSqlLength; i++)
270
         {
271
            insertValidateSql[i] = insertSql[i] + " validate";
272
         }
273
      }
274
      else
275
      {
276
         // making it null is fine because this field will be accessed only when we are dealing with a
277
         // temporary database.
278
         insertValidateSql = null;
279
      }
280
      
281 253
      nonNullableProps = new BitSet(props.length);
282 254
      
283 255
      // collect index data for record flushing purposes
new/src/com/goldencode/p2j/persist/orm/Session.java 2023-07-20 13:23:34 +0000
43 43
** 005 SR  20230519 Changed cacheExpired() so now the expired dmo is also deregistered from DmoVersioning.
44 44
**     SR  20230522 Moved the deregister operation above the state check so it will happen regardless of 
45 45
**                  how we exit the method.
46
** 006 RAA 20230601 Added support for VALIDATE keyword.
47
**     RAA 20230704 Changed name of validateMode to allowDBUniqueCheck.
48 46
*/
49 47

  
50 48
/*
......
707 705
   public <T extends BaseRecord> void save(T dmo, boolean updateDmoState)
708 706
   throws PersistenceException
709 707
   {
710
      save(dmo, updateDmoState, false);
711
   }
712
   
713
   /**
714
    * Saves a {@code dmo} to database. If so requested, its internal state information is updated.
715
    * 
716
    * The Save operation can be executed normally (having the server validate the index in use), 
717
    * or with the help of the {@code validate} and {@code soft_unique} keywords, which, when placed,
718
    * notify H2 that validation has not been done and it should do that process itself. This works only
719
    * with temporary databases.
720
    * 
721
    * @param   dmo
722
    *          The record to be saved.
723
    * @param   updateDmoState
724
    *          If {@code true} its internal state information is updated. This is the standard
725
    *          call. When the method is called for validation, use {@code false} as the database
726
    *          operation will be rolled back anyway, so the state must not be altered.
727
    * @param   allowDBUniqueCheck
728
    *          Flag (only for temporary databases) that marks whether index server-validation was skipped.
729
    * @param   <T>
730
    *          The compile type of the record.
731
    *
732
    * @throws  PersistenceException
733
    *          When an exception is encountered. Must be analyzed as the cause might be expected
734
    *          (ex: when validating a record).
735
    * @throws  StaleRecordException
736
    *          if the record being saved is stale.
737
    */
738
   public <T extends BaseRecord> void save(T dmo, boolean updateDmoState, boolean allowDBUniqueCheck)
739
   throws PersistenceException
740
   {
741 708
      checkClosed();
742 709
      
743 710
      checkTx();
......
784 751
      // decide what to do with the record
785 752
      if (wasNew)
786 753
      {
787
         persister.insert(dmo, allowDBUniqueCheck);
754
         persister.insert(dmo);
788 755
         wasDirty = true;
789 756
      }
790 757
      else if (wasDirty)
791 758
      {
792
         persister.update(dmo, allowDBUniqueCheck);
759
         persister.update(dmo);
793 760
         wasDirty = true;
794 761
      }
795 762
      
......
1638 1605
            // record was not saved since it was added to cache
1639 1606
            try
1640 1607
            {
1641
               persister.update(dmo, false);
1608
               persister.update(dmo);
1642 1609
            }
1643 1610
            catch (PersistenceException e)
1644 1611
            {
new/src/com/goldencode/p2j/persist/orm/Validation.java 2023-07-20 13:29:12 +0000
35 35
**                  used in insert/update or where predicate.
36 36
**     CA  20230110 getDirtyIndices and getUnvalidatedIndices now return null instead of an empty BitSet, when
37 37
**                  no indices are found.
38
** 003 RAA 20230601 Added support for VALIDATE keyword.
39
**     RAA 20230619 Split flush function into two for more straight-forward use when dealing with VALIDATE.
40
**     RAA 20230704 Changed name of validateMode to allowDBUniqueCheck.
41
**     RAA 20230704 In case a flush with VALIDATE fails, but it gets cured, a second flush is now attempted.
38
** 003 RAA 20230720 Added JMX for counting cured insert/update statements.    
42 39
*/
43 40

  
44 41
/*
......
100 97
import java.sql.*;
101 98
import java.util.*;
102 99
import com.goldencode.p2j.directory.*;
100
import com.goldencode.p2j.jmx.*;
103 101
import com.goldencode.p2j.persist.*;
104 102
import com.goldencode.p2j.persist.dialect.*;
105 103
import com.goldencode.p2j.persist.orm.types.*;
106 104
import com.goldencode.p2j.util.*;
107
import com.goldencode.p2j.util.logging.CentralLogger;
108 105

  
109 106
/**
110 107
 * An object which performs validation of the state of a DMO, and optionally flushes the DMO's
......
121 118
public class Validation
122 119
implements DmoState
123 120
{
124
   /** Logger */
125
   private static final CentralLogger LOG = CentralLogger.get(Validation.class);
126
   
127 121
   /** Record buffer requesting validation */
128 122
   private final RecordBuffer buffer;
129 123
   
......
160 154
   /** Flag indicating the record was flushed to the database (either newly inserted or updated) */
161 155
   private boolean flushed = false;
162 156
   
157
   /** Counter for how many Inserts/Updates are executed. */
158
   private static final SimpleCounter INSERT_UPDATE_TOTAL = 
159
            SimpleCounter.getInstance(FwdJMX.Counter.InsertUpdateTotal);
160
   
161
   /** Counter for how many Inserts/Updates are successfully executed. */
162
   private static final SimpleCounter INSERT_UPDATE_SUCCESS = 
163
            SimpleCounter.getInstance(FwdJMX.Counter.InsertUpdateSuccess);
164
   
165
   /** Counter for how many Inserts/Updates with VALIDATE are executed. */
166
   private static final SimpleCounter INSERT_UPDATE_CURED = 
167
            SimpleCounter.getInstance(FwdJMX.Counter.InsertUpdateCured);
168
   
163 169
   /**
164 170
    * The maximum permitted size for all combined fields of an index. By default this
165 171
    * is equal to Progress version 10.x limit of 1971 bytes.
......
438 444
      {
439 445
         // validate against uncommitted transaction data; no-op for temp-table
440 446
         token = uniqueTrackerCtx.lockAndChange(uniqueTracker, dmo, bufferName);
441
         boolean willFlush = flush || buffer.isAutoCommit();
442
         // allowDBUniqueCheck can be set to true only when dealing with temporary databases
443
         // allowDBUniqueCheck is used to determine if H2 will do index validation or should this be done
444
         // normally (server-side validation)
445
         boolean allowDBUniqueCheck = willFlush && canSkipServerSideUniqueCheck();
446 447
         
447
         if ((multiplex != null || !flush) && !allowDBUniqueCheck)
448
         if (multiplex != null || !flush)
448 449
         {
450
            boolean countJMX = flush || buffer.isAutoCommit();
451
            if (countJMX)
452
            {
453
               INSERT_UPDATE_TOTAL.update(1);
454
            }
449 455
            // validate by executing unique index queries
450 456
            validateUniqueByQuery(check);
457
            if (countJMX)
458
            {
459
               INSERT_UPDATE_SUCCESS.update(1);
460
            }
451 461
         }
452 462
         
453
         if (willFlush)
463
         if (flush || buffer.isAutoCommit())
454 464
         {
455 465
            // validation will be done by the database as part of the flush (for persistent tables)
456
            flush(allowDBUniqueCheck);
466
            flush();
457 467
         }
458 468
         
459 469
         // validation was performed and succeeded; changes may have been persisted to database;
......
508 518
   
509 519
   /**
510 520
    * Flush the record being validated to the database.
511
    *          
521
    * 
512 522
    * @throws  PersistenceException
513 523
    *          if there is any non-validation database error.
514 524
    * @throws  ValidationException
......
522 532
          ValidationException,
523 533
          StaleRecordException
524 534
   {
525
      flush(false);
526
   }
527
   
528
   /**
529
    * Flush the record being validated to the database.
530
    * 
531
    * The Flush operation can be executed normally (having the server validate the index in use), 
532
    * or with the help of the {@code validate} and {@code soft_unique} keywords, which, when placed,
533
    * notify H2 that validation has not been done and it should do that process itself. This works only
534
    * with temporary databases.
535
    * 
536
    * @param   allowDBUniqueCheck
537
    *          Flag (only for temporary databases) that marks whether index server-validation was skipped.
538
    *          
539
    * @throws  PersistenceException
540
    *          if there is any non-validation database error.
541
    * @throws  ValidationException
542
    *          if there is an error validating a unique or not-null constraint.
543
    * @throws  StaleRecordException
544
    *          if the record being flushed is stale. A stale record should not get this far under normal
545
    *          circumstances.
546
    */
547
   private void flush(boolean allowDBUniqueCheck)
548
   throws PersistenceException,
549
          ValidationException,
550
          StaleRecordException
551
   {
552 535
      // if not already fired, do it now. When the trigger is fired the oldDMO is reset, preventing double firing
553 536
      buffer.maybeFireWriteTrigger();
554 537
      
......
613 596
      
614 597
      try
615 598
      {
616
         flushImpl(session, sp, fullTx, wasNew, allowDBUniqueCheck);
599
         // this will perform the appropriate insert or update, based on the state of the DMO
600
         session.save(dmo, true);
601
         if (wasNew)
602
         {
603
            buffer.recordInserted();
604
         }
605
      }
606
      catch (PersistenceException exc)
607
      {
608
         // validation failed, or we had a different error; roll back to savepoint
609
         // TODO: is this always safe (or even possible) in the event of a non-unique constraint
610
         //       related database error?
611
         if (sp != null)
612
         {
613
            session.rollbackSavepoint(sp);
614
         }
615
         else if (fullTx)
616
         {
617
            session.rollback();
618
         }
619
         
620
         // go up the exception chain and look for a SQL exception:
621
         Throwable cause = exc.getCause();
622
         while (cause != null                   &&
623
               !(cause instanceof SQLException) &&
624
               cause != cause.getCause())
625
         {
626
            cause = cause.getCause();
627
         }
628
         if (cause instanceof SQLException)
629
         {
630
            SQLException sqle = (SQLException) cause;
631
            if ("23505".equals(sqle.getSQLState())) // UNIQUE VIOLATION - unique_violation
632
            {
633
               // if the right index is found, this method will not return normally, instead it will
634
               // throw a specific exception
635
               validateUniqueByQuery(null);
636
            }
637
         }
638
         
639
         // if the exception could not be identified as a SQL UNIQUE VIOLATION exception or the 
640
         // generateUniqueIndexesCondition() method could not identify the right index to compose the
641
         // P4GL error message, we let the caller to handle the exception
642
         throw new ValidationException(exc.getMessage(), exc);
617 643
      }
618 644
      finally
619 645
      {
......
639 665
   }
640 666
   
641 667
   /**
642
    * The implementation of the flushing process to the database.
643
    * In case a flush with VALIDATE fails, the intention is to see if it can be "cured". If that is the case,
644
    * a normal flush will be attempted.
645
    * 
646
    * @param   session
647
    *          The session in use.
648
    * @param   sp
649
    *          The savepoint used in case of failure.
650
    * @param   fullTx
651
    *          Flag that marks if this is a full transaction or not.
652
    * @param   wasNew
653
    *          Is the DMO marked with NEW?
654
    * @param   allowDBUniqueCheck
655
    *          Flag (only for temporary databases) that marks whether index server-validation was skipped.
656
    *          
657
    * @throws  PersistenceException
658
    *          if there is any non-validation database error.
659
    * @throws  ValidationException
660
    *          if there is an error validating a unique or not-null constraint.
661
    * @throws  StaleRecordException
662
    *          if the record being flushed is stale. A stale record should not get this far under normal
663
    *          circumstances.
664
    */
665
   private void flushImpl(Session session, 
666
                          Savepoint sp, 
667
                          boolean fullTx, 
668
                          boolean wasNew, 
669
                          boolean allowDBUniqueCheck)
670
   throws PersistenceException,
671
   ValidationException,
672
   StaleRecordException
673
   {
674
      try
675
      {
676
         // this will perform the appropriate insert or update, based on the state of the DMO
677
         session.save(dmo, true, allowDBUniqueCheck);
678
         if (wasNew)
679
         {
680
            buffer.recordInserted();
681
         }
682
      }
683
      catch (PersistenceException exc)
684
      {
685
         // validation failed, or we had a different error; roll back to savepoint
686
         // TODO: is this always safe (or even possible) in the event of a non-unique constraint
687
         //       related database error?
688
         if (sp != null)
689
         {
690
            session.rollbackSavepoint(sp);
691
         }
692
         else if (fullTx)
693
         {
694
            session.rollback();
695
         }
696
         
697
         // go up the exception chain and look for a SQL exception:
698
         Throwable cause = exc.getCause();
699
         while (cause != null                   &&
700
               !(cause instanceof SQLException) &&
701
               cause != cause.getCause())
702
         {
703
            cause = cause.getCause();
704
         }
705
         while (cause instanceof SQLException)
706
         {
707
            SQLException sqle = (SQLException) cause;
708
            if ("23505".equals(sqle.getSQLState())) // UNIQUE VIOLATION - unique_violation
709
            {
710
               // if the right index is found, this method will not return normally, instead it will
711
               // throw a specific exception
712
               validateUniqueByQuery(null);
713
               if (allowDBUniqueCheck)
714
               {
715
                  // We were not able to execute the query with VALIDATE, but a "cure" occurred,
716
                  // meaning that we can try executing the query normally.
717
                  LOG.severe("Flush with VALIDATE failed due to " + sqle);
718
                  flushImpl(session, sp, fullTx, wasNew, false);
719
                  return;
720
               }
721
               break;
722
            }
723
            
724
            // if this exception comes from H2, then we need to move to the inner exception (a.k.a. 
725
            // JdbcSQLIntegrityConstraintViolationException), because the outer one (SQLException) does not
726
            // have the SQLState set accordingly.
727
            cause = sqle.getCause();
728
         }
729
         
730
         // if the exception could not be identified as a SQL UNIQUE VIOLATION exception or the 
731
         // generateUniqueIndexesCondition() method could not identify the right index to compose the
732
         // P4GL error message, we let the caller to handle the exception
733
         throw new ValidationException(exc.getMessage(), exc);
734
      }
735
   }
736
   
737
   /**
738 668
    * Get the state of the {@link #flushed} flag.
739 669
    * 
740 670
    * @return   See above.
......
1188 1118
            {
1189 1119
               throw new ValidationException(getFailUniqueIndexMessage(uIndex, bufferName, dmo), 132);
1190 1120
            }
1121
            else
1122
            {
1123
               INSERT_UPDATE_CURED.update(1);
1124
            }
1191 1125
            
1192 1126
            // if we exit the loop normally, all violations are cured by in-memory changes (which may yet
1193 1127
            // have to be validated and flushed themselves)
......
1264 1198
   }
1265 1199
   
1266 1200
   /**
1267
    * Function that checks if VALIDATE keyword can be used with this statement.
1268
    * 
1269
    * @return  boolean
1270
    *          {@code true} if there exists support for the VALIDATE keyword.
1271
    */
1272
   private boolean canSkipServerSideUniqueCheck()
1273
   {
1274
      return buffer != null && buffer.getPersistence().allowDBUniqueCheck();
1275
   }
1276
   
1277
   /**
1278 1201
    * Verify a unique constraint violation detected by querying the database, by confirming that the record
1279 1202
    * found to be causing the constraint violation does not have unflushed changes associated with it in
1280 1203
    * memory, awaiting its own validation and flush operation. If a modified, unflushed copy of the suspect