7323-JMX-before.patch
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 |