direct_access.patch
new/build.gradle 2023-02-09 11:42:31 +0000 | ||
---|---|---|
237 | 237 |
repositories { |
238 | 238 |
// https://repo1.maven.org/maven2/ |
239 | 239 |
mavenCentral() |
240 | ||
241 |
flatDir { |
|
242 |
dirs("custom") |
|
243 |
} |
|
240 | 244 |
|
241 | 245 |
maven { |
242 | 246 |
name "maven2" |
... | ... | |
441 | 445 |
fwdCCS group: 'org.apache.commons', name: 'commons-collections4', version: '4.4' |
442 | 446 |
fwdConvertServer group: 'org.jboss.logging', name: 'jboss-logging', version: '3.1.0.GA' |
443 | 447 |
fwdConvertServer group: 'antlr', name: 'antlr', version: '2.7.7' |
444 |
fwdConvertServer group: 'com.goldencode', name: 'fwd-h2', version: '1.4.200-6' |
|
448 |
// fwdConvertServer group: 'com.goldencode', name: 'fwd-h2', version: '1.4.200-6' |
|
449 |
fwdConvertServer files('custom/myh2.jar') |
|
445 | 450 |
fwdConvertServer group: 'javax.transaction', name: 'jta', version: '1.1' |
446 | 451 |
fwdConvertServer group: 'org.janusgraph', name: 'janusgraph-core', version: '0.1.0' |
447 | 452 |
new/src/com/goldencode/p2j/jmx/FwdJMX.java 2023-02-10 13:59:34 +0000 | ||
---|---|---|
14 | 14 |
** IAS 20220626 Added SSL counters. |
15 | 15 |
** AL2 20220828 Added H2 Query Profiler. |
16 | 16 |
** CA 20221006 Added instrumentation for some APIs in the ORM and persistence layer. Refs #6814 |
17 |
* CA 20221031 Added more instrumentation for: |
|
17 |
* CA 20221031 Added more instrumentation for:
|
|
18 | 18 |
** - the 'execute' time at H2 statements (OrmTempTableQuery). |
19 | 19 |
** - ContextLocal.get (ContextLocalGet). |
20 | 20 |
** - TableMapper.mapTemporaryTable (OrmTableMapperMap). |
... | ... | |
25 | 25 |
** AL2 20230104 Added OrmRedo. |
26 | 26 |
** Added OrmNoUndoTrace. |
27 | 27 |
** OM 20230112 Added finer-granulation instrumentation for processing of dynamic queries. |
28 |
** AL2 20230210 Added instrumentation for RandomAccessQuery resolutions: fast-find, direct access, SQL. |
|
28 | 29 |
*/ |
29 | 30 | |
30 | 31 |
/* |
... | ... | |
291 | 292 |
/** Instrumentation for <code>TableMapper.mapTemporaryTable</code>. */ |
292 | 293 |
OrmTableMapperMap, |
293 | 294 |
|
294 |
|
|
295 | 295 |
/** Instrumentation for <code>TemporaryBuffer.associate</code>. */ |
296 | 296 |
OrmTempTableParam, |
297 | 297 |
/** Instrumentation for <code>TemporaryBuffer.createDynamicTable</code>. */ |
... | ... | |
301 | 301 |
/** Instrumentation for <code>DataSet.createDynamicDataSet</code>. */ |
302 | 302 |
OrmDynamicDataSetParam, |
303 | 303 | |
304 |
/** Instrumentation for <code>RandomAccessQuery</code> fast-find cache hits. */ |
|
305 |
OrmFFCacheHit, |
|
306 |
/** Instrumentation for <code>RandomAccessQuery</code> fast-find cache hits with no record. */ |
|
307 |
OrmFFCacheHitNoRecord, |
|
308 |
/** Instrumentation for <code>RandomAccessQuery</code> direct access technique over unique index. */ |
|
309 |
OrmFindDirectAccess, |
|
310 |
/** Instrumentation for <code>RandomAccessQuery</code> direct access technique over recid */ |
|
311 |
OrmFindDirectAccessPk, |
|
312 |
/** Instrumentation for <code>RandomAccessQuery.executeImpl</code> */ |
|
313 |
OrmFindExecute, |
|
314 | ||
304 | 315 |
/** Instrumentation for <code>OutputTableCopier.finished</code> */ |
305 | 316 |
OutputTableCopier, |
306 | 317 |
/** Instrumentation for <code>OutputTableHandleCopier.finished</code> */ |
new/src/com/goldencode/p2j/persist/FQLPreprocessor.java 2023-02-10 14:06:39 +0000 | ||
---|---|---|
282 | 282 |
** are not the same), to be aware of the external buffers (i.e. added for CAN-FIND |
283 | 283 |
** sub-select clauses). The translate will be performed before the query is being |
284 | 284 |
** executed. |
285 |
** AL2 20230210 Use prop matches to identify if the where clause is a look-up on unique index. |
|
285 | 286 |
*/ |
286 | 287 | |
287 | 288 |
/* |
... | ... | |
630 | 631 |
/** Flag the query that it should performed on recid/rowid lookup table. */ |
631 | 632 |
private boolean findByRowid = false; |
632 | 633 |
|
634 |
/** Helper that helps indentifying if this where clause is in fact a unique index look-up. */ |
|
635 |
private UniqueIndexLookup uniqueIndexLookup = null; |
|
636 |
|
|
633 | 637 |
/** |
634 | 638 |
* The actual {@code rowid} value that is to be loaded if it is a constant detected in |
635 | 639 |
* {@code fql} preprocessing. If it is {@code null} but {@code findByRowid = true} then |
... | ... | |
1280 | 1284 |
// character datatype special case |
1281 | 1285 |
return new int64(((BaseDataType) params)).longValue(); |
1282 | 1286 |
} |
1283 |
|
|
1287 |
else if (params instanceof Integer) |
|
1288 |
{ |
|
1289 |
return ((Integer) params).longValue(); |
|
1290 |
} |
|
1291 |
else if (params instanceof Long) |
|
1292 |
{ |
|
1293 |
return ((Long) params).longValue(); |
|
1294 |
} |
|
1295 |
|
|
1284 | 1296 |
if (LOG.isLoggable(Level.WARNING)) |
1285 | 1297 |
{ |
1286 | 1298 |
LOG.log(Level.WARNING, "Incompatible query parameters"); |
... | ... | |
1289 | 1301 |
} |
1290 | 1302 |
|
1291 | 1303 |
/** |
1304 |
* Check if the where clause is a conjunction of equality clauses that strictly cover an |
|
1305 |
* unique index. This way, it is guaranteed that there is zero or one record fetched at |
|
1306 |
* the end. |
|
1307 |
* |
|
1308 |
* @param includeRowid |
|
1309 |
* {@code true} if the function should consider rowid as a field of an unique index. |
|
1310 |
* |
|
1311 |
* @return {@code true} if the target where clause if for zero or one record due to |
|
1312 |
* an unique index look-up. |
|
1313 |
*/ |
|
1314 |
public boolean isUniqueFind(boolean includeRowid) |
|
1315 |
{ |
|
1316 |
return uniqueIndexLookup != null || (includeRowid ? isFindByRowid() : false); |
|
1317 |
} |
|
1318 |
|
|
1319 |
/** |
|
1320 |
* Retrieve the unique index loop-up object, responsible for storing information on the |
|
1321 |
* unique index that is used. Note that the properties form a unique index, but not necessarily |
|
1322 |
* in the provided order. Also, some values may be null due to the fact that they are substitution. |
|
1323 |
* |
|
1324 |
* @return an object which may assist faster look-up based on unique indexes |
|
1325 |
*/ |
|
1326 |
public UniqueIndexLookup getUniqueIndexLookup() |
|
1327 |
{ |
|
1328 |
return uniqueIndexLookup; |
|
1329 |
} |
|
1330 |
|
|
1331 |
/** |
|
1292 | 1332 |
* Get the <code>ParameterIndices</code> object associated with this query. |
1293 | 1333 |
* This object maintains an array of zero-based indices into the array of |
1294 | 1334 |
* query substitution arguments for the where clause, as it exists |
... | ... | |
1491 | 1531 |
annotateParameters(root, paramTypes); |
1492 | 1532 |
} |
1493 | 1533 |
|
1494 |
if (informational && parameters != null) |
|
1495 |
{ |
|
1496 |
// at the moment, this method is only interesting for server-side join optimization |
|
1497 |
// analysis, which presumes at least one field reference parameter; if this becomes |
|
1498 |
// more useful generally, we can remove the conditional test above |
|
1499 |
collectPropertyMatches(root); |
|
1500 |
} |
|
1534 |
//if (informational && parameters != null) |
|
1535 |
// at the moment, this method is only interesting for server-side join optimization |
|
1536 |
// analysis, which presumes at least one field reference parameter; if this becomes |
|
1537 |
// more useful generally, we can remove the conditional test above |
|
1538 |
// it is useful for H2 direct access to identify queries on unique indexes |
|
1539 |
collectPropertyMatches(root); |
|
1501 | 1540 |
|
1502 | 1541 |
// simplify the predicate in raport to boolean values |
1503 | 1542 |
root = processCompareToUnknowns(root, parameters, indexedFields); |
... | ... | |
1554 | 1593 |
|
1555 | 1594 |
this.findByRowid = checkFindByRowid(root); |
1556 | 1595 |
|
1596 |
this.uniqueIndexLookup = checkUniqueFind(); |
|
1597 |
|
|
1557 | 1598 |
// Emit the preprocessed FQL expression text. |
1558 | 1599 |
this.fql = emit(root, false); |
1559 | 1600 |
|
... | ... | |
2086 | 2127 |
return false; |
2087 | 2128 |
} |
2088 | 2129 |
|
2130 | ||
2131 |
|
|
2132 |
/** |
|
2133 |
* Check if the where clause is a look-up over a unique index. |
|
2134 |
* <p> |
|
2135 |
* The goal here is to identify a common business logic pattern: find by an unique index, usually |
|
2136 |
* primary. The implementation doesn't detect if the where clauses combination suggest that |
|
2137 |
* zero or one record is expected. It just checks if the property matches match an unique |
|
2138 |
* index. Callers rely on this. Extending the method with heuristics for uniqueness beyond |
|
2139 |
* unique indexes may cause problems. |
|
2140 |
* |
|
2141 |
* @return {@code true} if the where clause is a conjunction of equalities covering all |
|
2142 |
* the fields of an unique index. |
|
2143 |
*/ |
|
2144 |
private UniqueIndexLookup checkUniqueFind() |
|
2145 |
{ |
|
2146 |
List<PropertyMatch> matches = getPropertyMatches(); |
|
2147 |
if (matches == null) |
|
2148 |
{ |
|
2149 |
return null; |
|
2150 |
} |
|
2151 |
|
|
2152 |
RecordBuffer targetBuffer = null; |
|
2153 |
List<String> properties = new ArrayList<>(); |
|
2154 |
for (int i = 0; i < matches.size(); i++) |
|
2155 |
{ |
|
2156 |
PropertyMatch match = matches.get(i); |
|
2157 |
|
|
2158 |
if (match.operator != EQUALS) |
|
2159 |
{ |
|
2160 |
return null; |
|
2161 |
} |
|
2162 |
|
|
2163 |
RecordBuffer buffer = DataTypeHelper.lookupBuffer(match.alias, bufferMap, fql); |
|
2164 |
if (buffer == null) |
|
2165 |
{ |
|
2166 |
// this is unexpected; return false for fault tolerance |
|
2167 |
return null; |
|
2168 |
} |
|
2169 |
|
|
2170 |
if (targetBuffer == null) |
|
2171 |
{ |
|
2172 |
targetBuffer = buffer; |
|
2173 |
} |
|
2174 |
else if (targetBuffer != buffer) |
|
2175 |
{ |
|
2176 |
// mixed where clauses |
|
2177 |
return null; |
|
2178 |
} |
|
2179 | ||
2180 |
properties.add(match.property); |
|
2181 |
} |
|
2182 |
|
|
2183 |
final DmoMeta meta = targetBuffer.getDmoInfo(); |
|
2184 |
final Dialect dialect = targetBuffer.getDialect(); |
|
2185 |
Function<String, String> honorComputed = (originalProp) -> |
|
2186 |
{ |
|
2187 |
Property propMeta = meta.getFieldInfo(originalProp); |
|
2188 |
if (propMeta._isCharacter && dialect.needsComputedColumns()) |
|
2189 |
{ |
|
2190 |
Boolean ccIgnoreCase = meta.isIndexedIgnoreCase(originalProp); |
|
2191 |
if (ccIgnoreCase != null) |
|
2192 |
{ |
|
2193 |
return dialect.getComputedColumnPrefix(!ccIgnoreCase) + originalProp; |
|
2194 |
} |
|
2195 |
} |
|
2196 |
|
|
2197 |
return originalProp; |
|
2198 |
}; |
|
2199 |
|
|
2200 |
Iterator<Set<String>> uniques = meta.getUniqueConstraints().iterator(); |
|
2201 |
while (uniques.hasNext()) |
|
2202 |
{ |
|
2203 |
Set<String> compSet = uniques.next(); |
|
2204 |
// match exactly an unique index; |
|
2205 |
// having less fields doesn't make the where unique |
|
2206 |
// having more fields allows the where clause to return zero records even if a record is |
|
2207 |
// found according to the unqiue index |
|
2208 |
if (compSet.size() == properties.size() && properties.containsAll(compSet)) |
|
2209 |
{ |
|
2210 |
UniqueIndexLookup lookup = new UniqueIndexLookup(); |
|
2211 |
for (int i = 0; i < matches.size(); i++) |
|
2212 |
{ |
|
2213 |
PropertyMatch match = matches.get(i); |
|
2214 |
Object arg = null; |
|
2215 |
switch (match.rvalType) |
|
2216 |
{ |
|
2217 |
case SUBST: |
|
2218 |
arg = null; |
|
2219 |
break; |
|
2220 |
case BOOL_TRUE: |
|
2221 |
case BOOL_FALSE: |
|
2222 |
arg = match.rvalType == BOOL_TRUE; |
|
2223 |
break; |
|
2224 |
case NUM_LITERAL: |
|
2225 |
arg = Long.parseLong(match.rval); |
|
2226 |
break; |
|
2227 |
case STRING: |
|
2228 |
if (match.rval.startsWith("'") && match.rval.endsWith("'")) |
|
2229 |
{ |
|
2230 |
arg = match.rval.substring(1, match.rval.length() - 1); |
|
2231 |
} |
|
2232 |
else |
|
2233 |
{ |
|
2234 |
// panic |
|
2235 |
return null; |
|
2236 |
} |
|
2237 |
break; |
|
2238 |
case DEC_LITERAL: |
|
2239 |
arg = Double.parseDouble(match.rval); |
|
2240 |
break; |
|
2241 |
default: |
|
2242 |
return null; // safety |
|
2243 |
} |
|
2244 |
String sqlProp = honorComputed.apply(match.property); |
|
2245 |
lookup.addMapping(sqlProp, arg); |
|
2246 |
} |
|
2247 |
return lookup; |
|
2248 |
} |
|
2249 |
} |
|
2250 |
|
|
2251 |
return null; |
|
2252 |
} |
|
2253 |
|
|
2089 | 2254 |
/** |
2090 | 2255 |
* Scan and parse the input where clause and return it in the form of an |
2091 | 2256 |
* abstract syntax tree. Rewriting is not performed at this stage. |
new/src/com/goldencode/p2j/persist/RandomAccessQuery.java 2023-02-10 14:18:23 +0000 | ||
---|---|---|
347 | 347 |
** are not the same), to be aware of the external buffers (i.e. added for CAN-FIND |
348 | 348 |
** sub-select clauses). The translate will be performed before the query is being |
349 | 349 |
** executed. |
350 |
** AL2 20230210 Allow direct access for recid/unique index look-ups. |
|
350 | 351 |
*/ |
351 | 352 | |
352 | 353 |
/* |
... | ... | |
409 | 410 |
import java.util.*; |
410 | 411 |
import java.util.function.*; |
411 | 412 |
import java.util.logging.*; |
413 | ||
414 |
import com.goldencode.p2j.jmx.FwdJMX; |
|
415 |
import com.goldencode.p2j.jmx.NanoTimer; |
|
412 | 416 |
import com.goldencode.p2j.persist.dirty.*; |
413 | 417 |
import com.goldencode.p2j.persist.event.*; |
414 | 418 |
import com.goldencode.p2j.persist.lock.*; |
... | ... | |
489 | 493 |
{ |
490 | 494 |
/** Logger */ |
491 | 495 |
private static final Logger LOG = LogHelper.getLogger(RandomAccessQuery.class.getName()); |
496 | ||
497 |
/** Instrumentation for <code>RandomAccessQuery</code> fast-find cache hits. */ |
|
498 |
private static final NanoTimer FF_CACHE_HIT = |
|
499 |
NanoTimer.getInstance(FwdJMX.TimeStat.OrmFFCacheHit); |
|
500 | ||
501 |
/** Instrumentation for <code>RandomAccessQuery</code> direct access technique over unique index. */ |
|
502 |
private static final NanoTimer FF_CACHE_HIT_NO_RECORD = |
|
503 |
NanoTimer.getInstance(FwdJMX.TimeStat.OrmFFCacheHitNoRecord); |
|
504 |
|
|
505 |
/** Instrumentation for <code>RandomAccessQuery</code> direct access technique over recid. */ |
|
506 |
private static final NanoTimer FIND_DIRECT_ACCESS_PK = |
|
507 |
NanoTimer.getInstance(FwdJMX.TimeStat.OrmFindDirectAccessPk); |
|
508 | ||
509 |
/** Instrumentation for <code>RandomAccessQuery</code> direct access technique over unique index. */ |
|
510 |
private static final NanoTimer FIND_DIRECT_ACCESS = |
|
511 |
NanoTimer.getInstance(FwdJMX.TimeStat.OrmFindDirectAccess); |
|
512 | ||
513 |
/** Instrumentation for <code>RandomAccessQuery.executeImpl</code>. */ |
|
514 |
private static final NanoTimer FIND_EXECUTE = |
|
515 |
NanoTimer.getInstance(FwdJMX.TimeStat.OrmFindExecute); |
|
492 | 516 |
|
493 | 517 |
/** Record buffer which backs this query */ |
494 | 518 |
private RecordBuffer buffer; |
... | ... | |
3444 | 3468 |
RecordIdentifier<String> cacheResult = null; |
3445 | 3469 |
FastFindCache.Key key = null; |
3446 | 3470 |
FQLPreprocessor fqlPreproc = helper.getFQLPreprocessor(); |
3471 |
DmoMeta meta = buffer.getDmoInfo(); |
|
3447 | 3472 |
|
3448 | 3473 |
// attempt to get the result from fast-find cache, avoiding the expensive database trip. |
3449 | 3474 |
// This is not possible and the cache lookup is skipped when: |
... | ... | |
3451 | 3476 |
// - the query has a join |
3452 | 3477 |
// - the query has an embedded CAN-FIND |
3453 | 3478 |
// - the query has a client-side where expression |
3454 |
if (ffCache != null && |
|
3455 |
(activeBundleKey == FIRST || activeBundleKey == LAST || activeBundleKey == UNIQUE) && |
|
3456 |
join == null && getExternalBuffers() == null) |
|
3479 |
if (ffCache != null && isSimpleQuery()) |
|
3457 | 3480 |
{ |
3458 | 3481 |
BitSet properties = fqlPreproc.getQueryProperties(); |
3459 | 3482 |
key = FastFindCache.createKey(buffer, index, where, properties, activeBundleKey, values); |
... | ... | |
3464 | 3487 |
if (cacheResult == FastFindCache.NO_RECORD) |
3465 | 3488 |
{ |
3466 | 3489 |
// record will not be found in database |
3490 |
FF_CACHE_HIT_NO_RECORD.timer(() -> { }); |
|
3467 | 3491 |
return null; |
3468 | 3492 |
} |
3469 | 3493 |
else |
... | ... | |
3483 | 3507 |
// were acquiring the lock; it is safe to use: |
3484 | 3508 |
try |
3485 | 3509 |
{ |
3486 |
return session.get(cacheResult); |
|
3510 |
Record[] r = new Record[1]; |
|
3511 |
RecordIdentifier<String> internalCacheResult = cacheResult; |
|
3512 |
FF_CACHE_HIT.timer(() -> r[0] = session.get(internalCacheResult)); |
|
3513 |
return r[0]; |
|
3487 | 3514 |
} |
3488 |
catch (PersistenceException e)
|
|
3515 |
catch (RuntimeException e)
|
|
3489 | 3516 |
{ |
3490 |
if (LOG.isLoggable(Level.WARNING)) |
|
3491 |
{ |
|
3492 |
LOG.log(Level.WARNING, |
|
3493 |
"Failed to get fast-find cached value for " + cacheResult, |
|
3494 |
e); |
|
3517 |
if (e.getCause() instanceof PersistenceException) |
|
3518 |
{ |
|
3519 |
if (LOG.isLoggable(Level.WARNING)) |
|
3520 |
{ |
|
3521 |
LOG.log(Level.WARNING, |
|
3522 |
"Failed to get fast-find cached value for " + cacheResult, |
|
3523 |
e.getCause()); |
|
3524 |
} |
|
3525 |
} |
|
3526 |
else |
|
3527 |
{ |
|
3528 |
throw e; |
|
3495 | 3529 |
} |
3496 | 3530 |
} |
3497 | 3531 |
} |
... | ... | |
3508 | 3542 |
} |
3509 | 3543 |
} |
3510 | 3544 |
|
3511 |
assert (cacheResult == null); |
|
3512 |
Record record = executeImpl(values, lockType, unique, updateLock); |
|
3545 |
Record record = null; |
|
3546 |
|
|
3547 |
// use direct-access after ffCache failed |
|
3548 |
Optional<Record> directAccessResult = executeDirectAccess(values); |
|
3549 |
if (directAccessResult.isPresent()) |
|
3550 |
{ |
|
3551 |
record = directAccessResult.get(); |
|
3552 |
} |
|
3553 |
else |
|
3554 |
{ |
|
3555 |
Record[] r = new Record[1]; |
|
3556 |
FIND_EXECUTE.timer(() -> r[0] = executeImpl(values, lockType, unique, updateLock)); |
|
3557 |
record = r[0]; |
|
3558 |
} |
|
3513 | 3559 |
|
3514 | 3560 |
if (ffCache != null && |
3515 | 3561 |
cacheResult == null && |
... | ... | |
4735 | 4781 |
} |
4736 | 4782 |
|
4737 | 4783 |
/** |
4784 |
* Core method of attempting to retrieve the record for this query using direct access. |
|
4785 |
* This does all the prerequisites checks and is safely implemented such that a failed direct |
|
4786 |
* access allows easy recovery. |
|
4787 |
* <p> |
|
4788 |
* A simple query (no join, external buffers and single result) over the temporary database |
|
4789 |
* can be resolved using a direct access driver (currently supported by H2). This avoids generating |
|
4790 |
* an FQL/SQL and simply uses the internal H2 structures (tables, indexes, etc.) to find the |
|
4791 |
* searched record. |
|
4792 |
* <p> |
|
4793 |
* Currently, this can be used only if the H2 row structure is very similar to the FWD record |
|
4794 |
* structure (to allow hydration) and the searched row can be uniquely identified: using recid |
|
4795 |
* or an unique index. |
|
4796 |
* |
|
4797 |
* @param values |
|
4798 |
* The resolved substitution parameters to be used by the query. |
|
4799 |
* |
|
4800 |
* @return An optional depending on the success of this attempt. If a direct access exception |
|
4801 |
* occurred, allow fallback by returning an empty optional. If a record could be retrieved, |
|
4802 |
* get it through the optional. |
|
4803 |
*/ |
|
4804 |
private Optional<Record> executeDirectAccess(Object[] values) |
|
4805 |
{ |
|
4806 |
try |
|
4807 |
{ |
|
4808 |
// reduce expenses; don't create a session just to check direct access |
|
4809 |
Session currentSession = buffer.getSession(false); |
|
4810 |
DmoMeta meta = buffer.getDmoInfo(); |
|
4811 |
FQLPreprocessor fqlPreproc = helper.getFQLPreprocessor(); |
|
4812 |
if (currentSession != null && |
|
4813 |
isSimpleQuery() && |
|
4814 |
buffer.isTemporary() && // prerequisites |
|
4815 |
fqlPreproc.isUniqueFind(true) && // this is the core condition right now |
|
4816 |
DirectAccessHelper.hasDirectAccess(currentSession) && // only if we have direct access to H2 |
|
4817 |
!meta.hasComputedColumns(false)) // only if all H2 columns represent the DMO fields |
|
4818 |
{ |
|
4819 |
Record[] r = new Record[1]; |
|
4820 |
if (fqlPreproc.isFindByRowid()) |
|
4821 |
{ |
|
4822 |
long recid = fqlPreproc.getFindByRowid(values); |
|
4823 |
FIND_DIRECT_ACCESS_PK.timer(() -> |
|
4824 |
r[0] = DirectAccessHelper.findByRowid(currentSession, meta, recid) |
|
4825 |
); |
|
4826 |
} |
|
4827 |
else if (fqlPreproc.isUniqueFind(false)) |
|
4828 |
{ |
|
4829 |
// make a copy to add the custom multiplex |
|
4830 |
UniqueIndexLookup lookup = new UniqueIndexLookup(fqlPreproc.getUniqueIndexLookup()); |
|
4831 |
if (!lookup.fill(values)) |
|
4832 |
{ |
|
4833 |
throw new DirectAccessException("Invalid fill operation"); |
|
4834 |
} |
|
4835 |
lookup.addMapping(TemporaryBuffer.MULTIPLEX_FIELD_NAME, buffer.getMultiplexID()); |
|
4836 |
FIND_DIRECT_ACCESS.timer(() -> |
|
4837 |
r[0] = DirectAccessHelper.findByUniqueIndex(currentSession, meta, lookup) |
|
4838 |
); |
|
4839 |
} |
|
4840 |
else |
|
4841 |
{ |
|
4842 |
throw new DirectAccessException("Invalid state for direct access"); |
|
4843 |
// not a valid state |
|
4844 |
} |
|
4845 |
|
|
4846 |
// the record can be null; make sure we let the caller know that we succeeded |
|
4847 |
return Optional.of(r[0]); |
|
4848 |
} |
|
4849 |
} |
|
4850 |
catch (Exception e) |
|
4851 |
{ |
|
4852 |
if (e instanceof DirectAccessException || e.getCause() instanceof DirectAccessException) |
|
4853 |
{ |
|
4854 |
return Optional.empty(); |
|
4855 |
} |
|
4856 |
else |
|
4857 |
{ |
|
4858 |
throw new RuntimeException(e); |
|
4859 |
} |
|
4860 |
} |
|
4861 | ||
4862 |
// don't use exceptions if we fail here; if we do, there will be a lot of exception being thrown |
|
4863 |
// it is faster to just let the caller know that we failed or not using DirectAccessResult |
|
4864 |
return Optional.empty(); |
|
4865 |
} |
|
4866 |
|
|
4867 |
/** |
|
4738 | 4868 |
* Process the results of a dirty index check and return the most |
4739 | 4869 |
* appropriate result for the query. This method may acquire a record lock |
4740 | 4870 |
* for the selected result. |
... | ... | |
4841 | 4971 |
} |
4842 | 4972 |
|
4843 | 4973 |
/** |
4974 |
* Check if this is a "simple" query. The term simple is vague. It refers to queries which can be |
|
4975 |
* easier resolved through fast-find cache or direct access. "simple" means no join, no external |
|
4976 |
* buffer and no dependent bundle key (FIRST, LAST and UNIQUE do not depend on a previous |
|
4977 |
* sorted query). |
|
4978 |
* |
|
4979 |
* @return {@code true} if this query can be used by fast-find or direct access. |
|
4980 |
*/ |
|
4981 |
private boolean isSimpleQuery() |
|
4982 |
{ |
|
4983 |
return (activeBundleKey == FIRST || activeBundleKey == LAST || activeBundleKey == UNIQUE) && |
|
4984 |
join == null && getExternalBuffers() == null; |
|
4985 |
} |
|
4986 |
|
|
4987 |
/** |
|
4844 | 4988 |
* Log debug details about the inputs and results of a query. |
4845 | 4989 |
* |
4846 | 4990 |
* @param hql |
new/src/com/goldencode/p2j/persist/TempTableHelper.java 2023-02-10 14:18:57 +0000 | ||
---|---|---|
54 | 54 |
** OM 20221027 H2 dialect requires that the index names to be truly unique. |
55 | 55 |
** SVL 20230110 P2JIndex.components() provides direct access to the components array in order to |
56 | 56 |
** improve performance. |
57 |
** AL2 20230210 Mark the computed columns as INVISIBLE. This works as a hint for the H2 engine. |
|
57 | 58 |
*/ |
58 | 59 | |
59 | 60 |
/* |
... | ... | |
591 | 592 |
{ |
592 | 593 |
String sqlType = dialect.getSqlMappedType(fieldMeta._fwdType, normalizedScale); |
593 | 594 |
String ccFormula = dialect.getComputedColumnFormula(fieldMeta.column, !fieldMeta.caseSensitive); |
595 |
String invisible = dialect.getInvisibleSpecificator(); |
|
594 | 596 |
sb.append(tab) |
595 | 597 |
.append(dialect.getComputedColumnPrefix(fieldMeta.caseSensitive)).append(fieldMeta.column) |
596 |
.append(" ").append(sqlType).append(" as ").append(ccFormula) |
|
598 |
.append(" ").append(sqlType) |
|
599 |
.append(invisible != null ? " " + invisible : "") |
|
600 |
.append(" as ").append(ccFormula) |
|
597 | 601 |
.append(",\n"); |
598 | 602 |
} |
599 | 603 |
|
... | ... | |
601 | 605 |
// in case it is lost |
602 | 606 |
if (fieldMeta._isDatetimeTz) |
603 | 607 |
{ |
608 |
String invisible = dialect.getInvisibleSpecificator(); |
|
604 | 609 |
sb.append(tab) |
605 | 610 |
.append(fieldMeta.column).append(DDLGeneratorWorker.DTZ_OFFSET).append(" ") |
606 | 611 |
.append(dialect.getSqlMappedType("integer", 0)) |
612 |
.append(invisible != null ? " " + invisible : "") |
|
607 | 613 |
.append(mandatoryColumn ? " not null,\n" : ",\n"); |
608 | 614 |
} |
609 | 615 |
} |
new/src/com/goldencode/p2j/persist/UniqueIndexLookup.java 2023-02-10 14:32:31 +0000 | ||
---|---|---|
1 |
/* |
|
2 |
** Module : UniqueIndexLookup.java |
|
3 |
** Abstract : Container for properties and values used when doing a unique index look-up. |
|
4 |
** |
|
5 |
** Copyright (c) 2023, Golden Code Development Corporation. |
|
6 |
** |
|
7 |
** -#- -I- --Date-- -----------------------------------Description----------------------------------- |
|
8 |
** 001 AL2 20230210 Created initial version. |
|
9 |
*/ |
|
10 | ||
11 |
/* |
|
12 |
** This program is free software: you can redistribute it and/or modify |
|
13 |
** it under the terms of the GNU Affero General Public License as |
|
14 |
** published by the Free Software Foundation, either version 3 of the |
|
15 |
** License, or (at your option) any later version. |
|
16 |
** |
|
17 |
** This program is distributed in the hope that it will be useful, |
|
18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 |
** GNU Affero General Public License for more details. |
|
21 |
** |
|
22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
24 |
** |
|
25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
26 |
** |
|
27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
28 |
** terms apply to the works covered under the License. These additional terms |
|
29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
30 |
** Affero GPL version 3 and may not be removed by you. |
|
31 |
** |
|
32 |
** 0. Attribution Requirement. |
|
33 |
** |
|
34 |
** You must preserve all legal notices or author attributions in the covered |
|
35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
36 |
** work. You may not remove from the covered work any author or developer |
|
37 |
** credit already included within the covered work. |
|
38 |
** |
|
39 |
** 1. No License To Use Trademarks. |
|
40 |
** |
|
41 |
** This license does not grant any license or rights to use the trademarks |
|
42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
45 |
** publicity purposes without written authorization. |
|
46 |
** |
|
47 |
** 2. No Misrepresentation of Affiliation. |
|
48 |
** |
|
49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
50 |
** |
|
51 |
** You may not represent yourself for publicity purposes as associated with |
|
52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
53 |
** the covered work, without written authorization. |
|
54 |
** |
|
55 |
** 3. No Misrepresentation of Source or Origin. |
|
56 |
** |
|
57 |
** You may not represent the covered work as solely your work. All modified |
|
58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
59 |
** clear that the modified work is not originating from Golden Code Development |
|
60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
61 |
** attribution required in this license. |
|
62 |
*/ |
|
63 | ||
64 |
package com.goldencode.p2j.persist; |
|
65 | ||
66 |
import java.util.ArrayList; |
|
67 |
import java.util.List; |
|
68 | ||
69 |
/** |
|
70 |
* Utility class which stores the properties and values of a look-up which is done |
|
71 |
* over an unique index. |
|
72 |
*/ |
|
73 |
public class UniqueIndexLookup |
|
74 |
{ |
|
75 |
/** The properties of the unique index, not necessarily in order */ |
|
76 |
private List<String> properties; |
|
77 |
|
|
78 |
/** |
|
79 |
* The values used for look-up; the order matched the order of the properties |
|
80 |
* A null value means a value that is provided latter (by substitution parameters). |
|
81 |
*/ |
|
82 |
private List<Object> values; |
|
83 | ||
84 |
/** |
|
85 |
* Basic constructor |
|
86 |
*/ |
|
87 |
public UniqueIndexLookup() |
|
88 |
{ |
|
89 |
properties = new ArrayList<>(); |
|
90 |
values = new ArrayList<>(); |
|
91 |
} |
|
92 |
|
|
93 |
/** |
|
94 |
* Copy constructor |
|
95 |
* |
|
96 |
* @param prototype |
|
97 |
* The prototype to be used when building this object. |
|
98 |
*/ |
|
99 |
public UniqueIndexLookup(UniqueIndexLookup prototype) |
|
100 |
{ |
|
101 |
this.properties = new ArrayList<>(prototype.properties); |
|
102 |
// deep copy is not needed; values are immutable |
|
103 |
this.values = new ArrayList<>(prototype.values); |
|
104 |
} |
|
105 |
|
|
106 |
/** |
|
107 |
* Fill the {@code null} values from the values array with the provided fill values. |
|
108 |
* This is used to lazily add the substitution parameters when needed. |
|
109 |
* |
|
110 |
* @param fillValues |
|
111 |
* The values to be used when filling the parameters array. |
|
112 |
* |
|
113 |
* @return {@code true} if the filling succeeded. If this returns {@code false}, the |
|
114 |
* object reaches a stale state, so avoid using it. |
|
115 |
*/ |
|
116 |
public boolean fill(Object[] fillValues) |
|
117 |
{ |
|
118 |
int from = 0; |
|
119 |
for (int i = 0; i < this.values.size(); i++) |
|
120 |
{ |
|
121 |
if (this.values.get(i) == null) |
|
122 |
{ |
|
123 |
if (fillValues == null || fillValues.length <= from) |
|
124 |
{ |
|
125 |
return false; |
|
126 |
} |
|
127 |
this.values.set(i, fillValues[from++]); |
|
128 |
} |
|
129 |
} |
|
130 |
if (fillValues == null) |
|
131 |
{ |
|
132 |
return from == 0; |
|
133 |
} |
|
134 |
return from == fillValues.length; |
|
135 |
} |
|
136 | ||
137 |
/** |
|
138 |
* Add a new property-value mapping to this look-up. |
|
139 |
* |
|
140 |
* @param property |
|
141 |
* The property from an unique index. |
|
142 |
* @param value |
|
143 |
* The value to be used when doing look-up. |
|
144 |
*/ |
|
145 |
public void addMapping(String property, Object value) |
|
146 |
{ |
|
147 |
properties.add(property); |
|
148 |
values.add(value); |
|
149 |
} |
|
150 | ||
151 |
/** |
|
152 |
* Retrieve the properties as an array. |
|
153 |
* |
|
154 |
* @return The array of properties. |
|
155 |
*/ |
|
156 |
public String[] getProperties() |
|
157 |
{ |
|
158 |
return properties.toArray(new String[0]); |
|
159 |
} |
|
160 |
|
|
161 |
/** |
|
162 |
* Retrieve the values as an array. |
|
163 |
* |
|
164 |
* @return The array of values. |
|
165 |
*/ |
|
166 |
public Object[] getValues() |
|
167 |
{ |
|
168 |
return values.toArray(); |
|
169 |
} |
|
170 |
|
|
171 |
} |
new/src/com/goldencode/p2j/persist/dialect/Dialect.java 2023-02-10 14:22:34 +0000 | ||
---|---|---|
95 | 95 |
** SVL 20230110 P2JIndex.components() provides direct access to the components array in order to |
96 | 96 |
** improve performance. |
97 | 97 |
** ECF 20230127 Added resolveBooleanConstant method. |
98 |
** AL2 20230210 Added getInvisibleSpecificator. |
|
98 | 99 |
*/ |
99 | 100 | |
100 | 101 |
/* |
... | ... | |
1061 | 1062 |
} |
1062 | 1063 |
|
1063 | 1064 |
/** |
1065 |
* Return a string which marks that a column is computed, so it should be marked as invisible |
|
1066 |
* to allow implicit skipping of it when using wildcard selection (*) or direct access. |
|
1067 |
* |
|
1068 |
* @return An SQL string specificator for invisible columns. {@code null} means that no such |
|
1069 |
* specificator is supported. |
|
1070 |
*/ |
|
1071 |
public String getInvisibleSpecificator() |
|
1072 |
{ |
|
1073 |
return null; |
|
1074 |
} |
|
1075 |
|
|
1076 |
/** |
|
1064 | 1077 |
* Prepare the given metadata query parameter. |
1065 | 1078 |
* |
1066 | 1079 |
* @param param |
new/src/com/goldencode/p2j/persist/dialect/P2JH2Dialect.java 2023-02-10 14:22:21 +0000 | ||
---|---|---|
106 | 106 |
** OM 20221026 Excluded table name from index name by default. It will be added as needed by |
107 | 107 |
** dialects. |
108 | 108 |
** RAA 20230109 Overrided methods getSequenceSetValString() and getSequenceCurrValString(). |
109 |
** AL2 20230210 Added getInvisibleSpecificator. |
|
109 | 110 |
*/ |
110 | 111 | |
111 | 112 |
/* |
... | ... | |
1017 | 1018 |
} |
1018 | 1019 | |
1019 | 1020 |
/** |
1021 |
* Return a string which marks that a column is computed, so it should be marked as invisible |
|
1022 |
* to allow implicit skipping of it when using wildcard selection (*) or direct access. |
|
1023 |
* |
|
1024 |
* @return An SQL string specificator for invisible columns. {@code null} means that no such |
|
1025 |
* specificator is supported. |
|
1026 |
*/ |
|
1027 |
public String getInvisibleSpecificator() |
|
1028 |
{ |
|
1029 |
return "invisible"; |
|
1030 |
} |
|
1031 | ||
1032 |
/** |
|
1020 | 1033 |
* Prepare the given metadata query parameter by making it uppercase. |
1021 | 1034 |
* |
1022 | 1035 |
* @param param |
new/src/com/goldencode/p2j/persist/hql/DataTypeHelper.java 2023-02-10 14:22:01 +0000 | ||
---|---|---|
37 | 37 |
** 022 IAS 20201123 Added ARRAY type |
38 | 38 |
** IAS 20220926 Process extra parentheses in the expressionType() method. |
39 | 39 |
** OM 20221103 New class names for FQLPreprocessor, FQLExpression, FQLBundle, and FQLCache. |
40 |
** AL2 20230210 Allow permissive look-up buffer (without exception of fail). |
|
40 | 41 |
*/ |
41 | 42 | |
42 | 43 |
/* |
... | ... | |
433 | 434 |
FQLExpression fql) |
434 | 435 |
{ |
435 | 436 |
String aliasName = alias != null ? alias.getText() : null; |
436 |
RecordBuffer buffer = bufferMap.get(aliasName); |
|
437 |
|
|
438 |
if (buffer == null) |
|
437 |
RecordBuffer rb = lookupBuffer(aliasName, bufferMap, fql); |
|
438 |
if (rb == null) |
|
439 | 439 |
{ |
440 |
throw new IllegalArgumentException( |
|
441 |
"Unrecognized buffer at column " + |
|
440 |
throw new IllegalArgumentException("Unrecognized buffer at column " + |
|
442 | 441 |
(alias != null ? alias.getColumn() : "<unknown>") + |
443 | 442 |
" in where clause: '" + |
444 | 443 |
aliasName + |
445 | 444 |
"' [" + |
446 | 445 |
(fql != null ? fql.toString() : "<unavailable>") + |
447 |
"]"); |
|
446 |
"]" |
|
447 |
); |
|
448 |
}; |
|
449 |
return rb; |
|
450 |
} |
|
451 |
|
|
452 |
/** |
|
453 |
* Attempt to look up a record buffer by alias from the given map. The DMO alias is extracted |
|
454 |
* from the {@code alias} AST. If there is no matching entry in the buffer map, an exception |
|
455 |
* is thrown. |
|
456 |
* <p> |
|
457 |
* The returned buffer is initialized by side effect. |
|
458 |
* |
|
459 |
* @param aliasName |
|
460 |
* Alias for DMO. If {@code null}, a {@code null} lookup key is used. |
|
461 |
* @param bufferMap |
|
462 |
* A map of DMO aliases to record buffers. If no alias is provided (e.g., for an |
|
463 |
* unqualified property reference, the appropriate buffer is expected to be mapped |
|
464 |
* to the {@code null} key. |
|
465 |
* @param fql |
|
466 |
* Optional FQL expression from which the alias is parsed. |
|
467 |
* |
|
468 |
* @return A record buffer matching the given alias. |
|
469 |
*/ |
|
470 |
public static RecordBuffer lookupBuffer(String aliasName, |
|
471 |
Map<String, RecordBuffer> bufferMap, |
|
472 |
FQLExpression fql) |
|
473 |
{ |
|
474 |
RecordBuffer buffer = bufferMap.get(aliasName); |
|
475 |
|
|
476 |
if (buffer == null) |
|
477 |
{ |
|
478 |
return null; |
|
448 | 479 |
} |
449 | 480 |
|
450 | 481 |
buffer.initialize(); |
new/src/com/goldencode/p2j/persist/orm/DirectAccessException.java 2023-02-10 14:34:13 +0000 | ||
---|---|---|
1 |
/* |
|
2 |
** Module : DirectAccessException.java |
|
3 |
** Abstract : Exception related to the direct access of internal database structures technique. |
|
4 |
** |
|
5 |
** Copyright (c) 2023, Golden Code Development Corporation. |
|
6 |
** |
|
7 |
** -#- -I- --Date-- -----------------------------------Description----------------------------------- |
|
8 |
** 001 AL2 20230210 Created initial version. |
|
9 |
*/ |
|
10 | ||
11 |
/* |
|
12 |
** This program is free software: you can redistribute it and/or modify |
|
13 |
** it under the terms of the GNU Affero General Public License as |
|
14 |
** published by the Free Software Foundation, either version 3 of the |
|
15 |
** License, or (at your option) any later version. |
|
16 |
** |
|
17 |
** This program is distributed in the hope that it will be useful, |
|
18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 |
** GNU Affero General Public License for more details. |
|
21 |
** |
|
22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
24 |
** |
|
25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
26 |
** |
|
27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
28 |
** terms apply to the works covered under the License. These additional terms |
|
29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
30 |
** Affero GPL version 3 and may not be removed by you. |
|
31 |
** |
|
32 |
** 0. Attribution Requirement. |
|
33 |
** |
|
34 |
** You must preserve all legal notices or author attributions in the covered |
|
35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
36 |
** work. You may not remove from the covered work any author or developer |
|
37 |
** credit already included within the covered work. |
|
38 |
** |
|
39 |
** 1. No License To Use Trademarks. |
|
40 |
** |
|
41 |
** This license does not grant any license or rights to use the trademarks |
|
42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
45 |
** publicity purposes without written authorization. |
|
46 |
** |
|
47 |
** 2. No Misrepresentation of Affiliation. |
|
48 |
** |
|
49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
50 |
** |
|
51 |
** You may not represent yourself for publicity purposes as associated with |
|
52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
53 |
** the covered work, without written authorization. |
|
54 |
** |
|
55 |
** 3. No Misrepresentation of Source or Origin. |
|
56 |
** |
|
57 |
** You may not represent the covered work as solely your work. All modified |
|
58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
59 |
** clear that the modified work is not originating from Golden Code Development |
|
60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
61 |
** attribution required in this license. |
|
62 |
*/ |
|
63 | ||
64 | ||
65 |
package com.goldencode.p2j.persist.orm; |
|
66 | ||
67 |
/** |
|
68 |
* Exception used when anything goes wrong in the direct access attempts. This is used to |
|
69 |
* do recovery in case direct access doesn't work. As direct access attempts are read-only, |
|
70 |
* throwing such exception doesn't imply stale states. Therefore, ensure that a fallback |
|
71 |
* plan is used if such exception occurs. |
|
72 |
*/ |
|
73 |
public class DirectAccessException |
|
74 |
extends Exception |
|
75 |
{ |
|
76 |
/** |
|
77 |
* Basic constructor |
|
78 |
* |
|
79 |
* @param msg |
|
80 |
* Exception message. |
|
81 |
*/ |
|
82 |
public DirectAccessException(String msg) |
|
83 |
{ |
|
84 |
super(msg); |
|
85 |
} |
|
86 |
|
|
87 |
/** |
|
88 |
* Basic constructor |
|
89 |
* |
|
90 |
* @param msg |
|
91 |
* Exception message. |
|
92 |
* @param cause |
|
93 |
* Exception cause. |
|
94 |
*/ |
|
95 |
public DirectAccessException(String msg, Exception cause) |
|
96 |
{ |
|
97 |
super(msg, cause); |
|
98 |
} |
|
99 | ||
100 |
} |
new/src/com/goldencode/p2j/persist/orm/DirectAccessHelper.java 2023-02-10 14:42:16 +0000 | ||
---|---|---|
1 |
/* |
|
2 |
** Module : DirectAccessHelper.java |
|
3 |
** Abstract : Middleware between FWD persistence layer and underlying database with direct access interface. |
|
4 |
** |
|
5 |
** Copyright (c) 2023, Golden Code Development Corporation. |
|
6 |
** |
|
7 |
** -#- -I- --Date-- -----------------------------------Description----------------------------------- |
|
8 |
** 001 AL2 20230210 Created initial version. |
|
9 |
*/ |
|
10 | ||
11 |
/* |
|
12 |
** This program is free software: you can redistribute it and/or modify |
|
13 |
** it under the terms of the GNU Affero General Public License as |
|
14 |
** published by the Free Software Foundation, either version 3 of the |
|
15 |
** License, or (at your option) any later version. |
|
16 |
** |
|
17 |
** This program is distributed in the hope that it will be useful, |
|
18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 |
** GNU Affero General Public License for more details. |
|
21 |
** |
|
22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
24 |
** |
|
25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
26 |
** |
|
27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
28 |
** terms apply to the works covered under the License. These additional terms |
|
29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
30 |
** Affero GPL version 3 and may not be removed by you. |
|
31 |
** |
|
32 |
** 0. Attribution Requirement. |
|
33 |
** |
|
34 |
** You must preserve all legal notices or author attributions in the covered |
|
35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
36 |
** work. You may not remove from the covered work any author or developer |
|
37 |
** credit already included within the covered work. |
|
38 |
** |
|
39 |
** 1. No License To Use Trademarks. |
|
40 |
** |
|
41 |
** This license does not grant any license or rights to use the trademarks |
|
42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
45 |
** publicity purposes without written authorization. |
|
46 |
** |
|
47 |
** 2. No Misrepresentation of Affiliation. |
|
48 |
** |
|
49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
50 |
** |
|
51 |
** You may not represent yourself for publicity purposes as associated with |
|
52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
53 |
** the covered work, without written authorization. |
|
54 |
** |
|
55 |
** 3. No Misrepresentation of Source or Origin. |
|
56 |
** |
|
57 |
** You may not represent the covered work as solely your work. All modified |
|
58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
59 |
** clear that the modified work is not originating from Golden Code Development |
|
60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
61 |
** attribution required in this license. |
|
62 |
*/ |
|
63 | ||
64 |
package com.goldencode.p2j.persist.orm; |
|
65 | ||
66 |
import java.sql.Connection; |
|
67 |
import java.sql.ResultSet; |
|
68 |
import java.sql.SQLException; |
|
69 | ||
70 |
import org.h2.embedded.DirectAccessDriver; |
|
71 |
import org.h2.jdbc.JdbcConnection; |
|
72 | ||
73 |
import com.goldencode.p2j.persist.PersistenceException; |
|
74 |
import com.goldencode.p2j.persist.Record; |
|
75 |
import com.goldencode.p2j.persist.UniqueIndexLookup; |
|
76 |
import com.goldencode.p2j.util.*; |
|
77 | ||
78 |
/** |
|
79 |
* Common point where all the direct access interface is used inside FWD. Any attempt |
|
80 |
* to use a database driver internal structures should be done through this gateway. |
|
81 |
*/ |
|
82 |
public class DirectAccessHelper |
|
83 |
{ |
|
84 |
/** |
|
85 |
* Direct access attempt on database structures to retrieve a single record based on recid. |
|
86 |
* |
|
87 |
* @param session |
|
88 |
* The session with a database with direct access interface. |
|
89 |
* @param meta |
|
90 |
* The meta of the table to be queried. |
|
91 |
* @param recid |
|
92 |
* The value of the recid for the record to be retrieved. |
|
93 |
* |
|
94 |
* @return A hydrated record directly from the underlying direct access interface. |
|
95 |
*/ |
|
96 |
public static Record findByRowid(Session session, DmoMeta meta, long recid) |
|
97 |
throws DirectAccessException |
|
98 |
{ |
|
99 |
try |
|
100 |
{ |
|
101 |
JdbcConnection conn = getH2Connection(session); |
|
102 |
DirectAccessDriver driver = conn.getDirectAccessDriver(); |
|
103 |
String tableName = meta.getSqlTableName(); |
|
104 |
ResultSet rs = driver.getRow(tableName, recid); |
|
105 |
return hydrateFromResultSet(session, meta, rs); |
|
106 |
} |
|
107 |
catch (SQLException | PersistenceException e) |
|
108 |
{ |
|
109 |
throw new DirectAccessException("Error direct accessing for finding record by rowid", e); |
|
110 |
} |
|
111 |
} |
|
112 | ||
113 |
/** |
|
114 |
* Direct access attempt on database structures to retrieve a single record based on a |
|
115 |
* unique index. |
|
116 |
* |
|
117 |
* @param session |
|
118 |
* The session with a database with direct access interface. |
|
119 |
* @param meta |
|
120 |
* The meta of the table to be queried. |
|
121 |
* @param lookup |
|
122 |
* Specific object which contains low-level information about the properties |
|
123 |
* and values needed for unique index lookup. |
|
124 |
* |
|
125 |
* @return A hydrated record directly from the underlying direct access interface. |
|
126 |
*/ |
|
127 |
public static Record findByUniqueIndex(Session session, DmoMeta meta, UniqueIndexLookup lookup) |
|
128 |
throws DirectAccessException |
|
129 |
{ |
|
130 |
try |
|
131 |
{ |
|
132 |
JdbcConnection conn = getH2Connection(session); |
|
133 |
DirectAccessDriver driver = conn.getDirectAccessDriver(); |
|
134 |
String tableName = meta.getSqlTableName(); |
|
135 |
Object[] values = lookup.getValues(); |
|
136 |
for (int i = 0; i < values.length; i++) |
|
137 |
{ |
|
138 |
values[i] = normalizeValue(values[i]); |
|
139 |
} |
|
140 |
ResultSet rs = driver.getUniqueRow(tableName, lookup.getProperties(), values); |
|
141 |
return hydrateFromResultSet(session, meta, rs); |
|
142 |
} |
|
143 |
catch (SQLException | PersistenceException e) |
|
144 |
{ |
|
145 |
throw new DirectAccessException("Error direct accessing for finding record by unique index", e); |
|
146 |
} |
|
147 |
} |
|
148 | ||
149 |
/** |
|
150 |
* Check if the provided session allows a direct access interface. |
|
151 |
* |
|
152 |
* @param session |
|
153 |
* The session with a database with direct access interface. |
|
154 |
* |
|
155 |
* @return {@code true} if this session allows direct access. |
|
156 |
*/ |
|
157 |
public static boolean hasDirectAccess(Session session) |
|
158 |
{ |
|
159 |
return getH2Connection(session) != null; |
|
160 |
} |
|
161 | ||
162 |
/** |
|
163 |
* Normalize the FWD specific data types into Java native types. This is needed |
|
164 |
* in order to interface with the underlying database through direct access. |
|
165 |
* |
|
166 |
* @param arg |
|
167 |
* The object to be "normalized" (converted to Java native type) |
|
168 |
* |
|
169 |
* @return A Java native type that can be used by the direct access interface. |
|
170 |
*/ |
|
171 |
private static Object normalizeValue(Object arg) |
|
172 |
throws DirectAccessException |
|
173 |
{ |
|
174 |
if (arg == null) |
|
175 |
{ |
|
176 |
return null; |
|
177 |
} |
|
178 |
if (arg instanceof Integer || |
|
179 |
arg instanceof Long || |
|
180 |
arg instanceof Boolean || |
|
181 |
arg instanceof Double || |
|
182 |
arg instanceof String) |
|
183 |
{ |
|
184 |
// already normalized |
|
185 |
return arg; |
|
186 |
} |
|
187 |
if (arg instanceof integer) |
|
188 |
{ |
|
189 |
return ((integer) arg).toJavaIntegerType(); |
|
190 |
} |
|
191 |
if (arg instanceof int64) |
|
192 |
{ |
|
193 |
return ((int64) arg).toJavaLongType(); |
|
194 |
} |
|
195 |
if (arg instanceof logical) |
|
196 |
{ |
|
197 |
return ((logical) arg).toJavaType(); |
|
198 |
} |
|
199 |
if (arg instanceof handle) |
|
200 |
{ |
|
201 |
return ((handle) arg).getResourceId(); |
|
202 |
} |
|
203 |
if (arg instanceof character) |
|
204 |
{ |
|
205 |
return ((character) arg).toJavaType(); |
|
206 |
} |
|
207 |
if (arg instanceof decimal) |
|
208 |
{ |
|
209 |
return ((decimal) arg).toJavaType(); |
|
210 |
} |
|
211 |
|
|
212 |
throw new DirectAccessException("Can't normalize FWD value to use it with direct access."); |
|
213 |
} |
|
214 |
|
|
215 |
/** |
|
216 |
* Core method of hydrating a record from the retrieved result set. Usually, the result-set |
|
217 |
* is a dummy container of row structures from the underlying database. Make sure these |
|
218 |
* are converted to FWD structures by hydrating. |
|
219 |
* |
|
220 |
* @param session |
|
221 |
* The session with a database with direct access interface. |
|
222 |
* @param meta |
|
223 |
* The meta of the table to be queried. |
|
224 |
* @param rs |
|
225 |
* The result-set returned by the direct access interface. |
|
226 |
* |
|
227 |
* @return A hydrated record directly from the underlying direct access interface. |
|
228 |
*/ |
|
229 |
private static Record hydrateFromResultSet(Session session, DmoMeta meta, ResultSet rs) |
|
230 |
throws PersistenceException, |
|
231 |
SQLException |
|
232 |
{ |
|
233 |
RowStructure rowStructure = new RowStructure(meta.dmoImplInterface, meta.propsByName.size()); |
|
234 |
if (rs.next()) |
|
235 |
{ |
|
236 |
return (Record) SQLQuery.hydrateRecord(rs, rowStructure, 1, session); |
|
237 |
} |
|
238 |
else |
|
239 |
{ |
|
240 |
return null; |
|
241 |
} |
|
242 |
} |
|
243 |
|
|
244 |
/** |
|
245 |
* Retrieve the H2 connection straight from the provided session. This returns {@code null} |
|
246 |
* if the H2 connection doesn't allows direct access interface of if this is not a H2 connection. |
|
247 |
* This should be refactored maybe to be more generic (not necessarily that close related to H2) |
|
248 |
* |
|
249 |
* @param session |
|
250 |
* The session with a database with direct access interface. |
|
251 |
* |
|
252 |
* @return A H2 specific connection. |
|
253 |
*/ |
|
254 |
private static JdbcConnection getH2Connection(Session session) |
|
255 |
{ |
|
256 |
if (session == null) |
|
257 |
{ |
|
258 |
return null; |
|
259 |
} |
|
260 |
|
|
261 |
Connection conn; |
|
262 |
try |
|
263 |
{ |
|
264 |
conn = session.getConnection(); |
|
265 |
} |
|
266 |
catch (PersistenceException e) |
|
267 |
{ |
|
268 |
return null; |
|
269 |
} |
|
270 |
|
|
271 |
if (TempTableDataSourceProvider.isProxy(conn)) |
|
272 |
{ |
|
273 |
conn = TempTableDataSourceProvider.unwrapProxy(conn); |
|
274 |
} |
|
275 |
|
|
276 |
if (conn != null && |
|
277 |
conn instanceof JdbcConnection && |
|
278 |
((JdbcConnection) conn).getDirectAccessDriver() != null) |
|
279 |
{ |
|
280 |
return (JdbcConnection) conn; |
|
281 |
} |
|
282 |
|
|
283 |
return null; |
|
284 |
} |
|
285 | ||
286 |
} |
new/src/com/goldencode/p2j/persist/orm/DmoMeta.java 2023-02-10 14:23:03 +0000 | ||
---|---|---|
49 | 49 |
** SVL 20230113 Improved performance by replacing some "for-each" loops with indexed "for" loops. |
50 | 50 |
** HC 20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase |
51 | 51 |
** for performance. |
52 |
** AL2 20230120 Compute if the underlying DMO requires extra computed columns. |
|
52 | 53 |
** CA 20221114 Added API to get the denormalized properties for a DMO extent property name. |
53 | 54 |
** ECF 20221207 Changed heuristic to extract schema name from full DMO interface name. |
54 | 55 |
*/ |
... | ... | |
282 | 283 |
/** Map of property names to extents for DMO array properties */ |
283 | 284 |
private Map<String, Integer> extentMap = null; |
284 | 285 |
|
286 |
/** Check if this DMO is going to have computed columns in the underlying database representation */ |
|
287 |
private Boolean[] hasComputedColumns = null; |
|
288 |
|
|
285 | 289 |
/** The map used in type-widening operations. */ |
286 | 290 |
private static final Map<Class<? extends BaseDataType>, Class<? extends BaseDataType>> toWider = |
287 | 291 |
new IdentityHashMap<>(); |
... | ... | |
1595 | 1599 |
return Category.VST.name().equals(category) && systemTable.fileName.equalsIgnoreCase(legacyTable); |
1596 | 1600 |
} |
1597 | 1601 |
|
1602 |
|
|
1603 |
/** |
|
1604 |
* Check if this DMO has computed columns in the underlying database representation. This is |
|
1605 |
* used mostly for temporary tables and may not be completely compatible with persistent tables. |
|
1606 |
* |
|
1607 |
* @param includeInvisible |
|
1608 |
* {@code true} to include the columns which are defined as invisible in the underlying |
|
1609 |
* database. Invisible columns are the ones which are not implicitly retrieved using |
|
1610 |
* SELECT * or direct access. |
|
1611 |
* |
|
1612 |
* @return {@code true} if we also have other columns that the legacy ones in the database. |
|
1613 |
*/ |
|
1614 |
public boolean hasComputedColumns(boolean includeInvisible) |
|
1615 |
{ |
|
1616 |
if (hasComputedColumns == null) |
|
1617 |
{ |
|
1618 |
hasComputedColumns = new Boolean[2]; |
|
1619 |
} |
|
1620 | ||
1621 |
int position = includeInvisible ? 0 : 1; |
|
1622 |
if (hasComputedColumns[position] == null) |
|
1623 |
{ |
|
1624 |
hasComputedColumns[position] = false; |
|
1625 |
|
|
1626 |
Set<Map.Entry<String, Property>> fieldEntries = this.propsByName.entrySet(); |
|
1627 |
|
|
1628 |
Set<Property> indexedProperties = new HashSet<>(); |
|
1629 |
Iterator<P2JIndex> databaseIndexes = this.getDatabaseIndexes(); |
|
1630 |
|
|
1631 |
while (databaseIndexes.hasNext()) |
|
1632 |
{ |
|
1633 |
P2JIndex index = databaseIndexes.next(); |
|
1634 |
ArrayList<P2JIndexComponent> components = index.components(); |
|
1635 |
for (int i = 0; i < components.size(); i++) |
|
1636 |
{ |
|
1637 |
P2JIndexComponent comp = components.get(i); |
|
1638 |
Property property = this.getFieldInfo(comp.getPropertyName()); |
|
1639 |
indexedProperties.add(property); |
|
1640 |
} |
|
1641 |
} |
|
1642 | ||
1643 |
// temp-tables have invisible columns |
|
1644 |
boolean relaxed = tempTable && !includeInvisible; |
|
1645 |
for (Map.Entry<String, Property> field : fieldEntries) |
|
1646 |
{ |
|
1647 |
Property property = field.getValue(); |
|
1648 |
|
|
1649 |
if (!relaxed && indexedProperties.contains(property) && property._isCharacter) |
|
1650 |
{ |
|
1651 |
hasComputedColumns[position] = true; |
|
1652 |
break; |
|
1653 |
} |
|
1654 | ||
1655 |
if (property.extent > 0) |
|
1656 |
{ |
|
1657 |
hasComputedColumns[position] = true; |
|
1658 |
break; |
|
1659 |
} |
|
1660 |
|
|
1661 |
if (!relaxed && property._isDatetimeTz) |
|
1662 |
{ |
|
1663 |
hasComputedColumns[position] = true; |
|
1664 |
break; |
|
1665 |
} |
|
1666 |
} |
|
1667 |
} |
|
1668 |
return hasComputedColumns[position]; |
|
1669 |
} |
|
1670 |
|
|
1598 | 1671 |
/** |
1599 | 1672 |
* Get the map of DMO property names to {@code P2JField} objects describing the corresponding fields, |
1600 | 1673 |
* creating it first, if necessary. |
new/src/com/goldencode/p2j/persist/orm/TempTableDataSourceProvider.java 2023-02-10 14:26:23 +0000 | ||
---|---|---|
15 | 15 |
** CA 20221011 Decreased prepared statement cache to 2048. The PreparedStatements are not cleaned when |
16 | 16 |
** a temp-table is dropped, and this will pin in memory the entire temp-table (with rows, |
17 | 17 |
** indexes, etc) at H2 level. |
18 |
** 003 AL2 20230210 Major refactor. Proxied connection is no longer an annonymous class. Make it named |
|
19 |
** to identify if the connection is proxied or not. |
|
18 | 20 |
*/ |
19 | 21 | |
20 | 22 |
/* |
... | ... | |
96 | 98 |
class TempTableDataSourceProvider |
97 | 99 |
implements DataSourceProvider |
98 | 100 |
{ |
101 |
|
|
102 |
/** |
|
103 |
* Check if the connection is a proxy delivered by this provider. |
|
104 |
* |
|
105 |
* @param conn |
|
106 |
* The connection to be checked. |
|
107 |
* |
|
108 |
* @return {@code true} if the provided connection is proxied by this provider. |
|
109 |
*/ |
|
110 |
public static boolean isProxy(Connection conn) |
|
111 |
{ |
|
112 |
return conn instanceof DataSourceImpl.TempProxyConnection; |
|
113 |
} |
|
114 |
|
|
115 |
/** |
|
116 |
* Retrieve the original connection without proxy. Use this with care as it bypassed |
|
117 |
* some critical business logic. Avoid executing SQL directly on the physical connection, |
|
118 |
* unless you are sure that the wrapper is not affected. |
|
119 |
* |
|
120 |
* @param conn |
|
121 |
* The connection to unwrap. |
|
122 |
* |
|
123 |
* @return The physical connection. The unwrapping works only if this is a proxy of this |
|
124 |
* provider. |
|
125 |
*/ |
|
126 |
public static Connection unwrapProxy(Connection conn) |
|
127 |
{ |
|
128 |
if (conn == null || !isProxy(conn)) |
|
129 |
{ |
|
130 |
return null; |
|
131 |
} |
|
132 |
DataSourceImpl.TempProxyConnection proxy = (DataSourceImpl.TempProxyConnection) conn; |
|
133 |
return proxy.ctx.physicalConn; |
|
134 |
} |
|
135 |
|
|
99 | 136 |
/** |
100 | 137 |
* Obtain the {@code DataSource} for a specific {@code Database}, within the current context. |
101 | 138 |
* |
... | ... | |
110 | 147 |
return new DataSourceImpl(); |
111 | 148 |
} |
112 | 149 |
|
150 |
|
|
113 | 151 |
/** |
114 | 152 |
* Implementation of the {@code DataSource} interface, which maintains context-local data for each |
115 | 153 |
* private temp-table connection. |
... | ... | |
177 | 215 |
String url = settings.getString(Settings.URL, null); |
178 | 216 |
if (username == null) |
179 | 217 |
{ |
180 |
username =settings.getString(Settings.USER, null); |
|
218 |
username = settings.getString(Settings.USER, null);
|
|
181 | 219 |
} |
182 | 220 |
if (password == null) |
183 | 221 |
{ |
... | ... | |
196 | 234 |
|
197 | 235 |
if (ctx.proxyConn == null) |
198 | 236 |
{ |
199 |
ctx.proxyConn = new Connection() |
|
200 |
{ |
|
201 |
public void close() throws SQLException |
|
202 |
{ |
|
203 |
DataSourceImpl.this.close(); |
|
204 |
} |
|
205 |
|
|
206 |
public PreparedStatement prepareStatement(String sql) |
|
207 |
throws SQLException |
|
208 |
{ |
|
209 |
// prepare using default result set type and concurrency |
|
210 |
return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); |
|
211 |
} |
|
212 |
|
|
213 |
public PreparedStatement prepareStatement(String sql, |
|
214 |
int resultSetType, |
|
215 |
int resultSetConcurrency) |
|
216 |
throws SQLException |
|
217 |
{ |
|
218 |
PSKey key = new PSKey(sql, resultSetType, resultSetConcurrency); |
|
219 |
UnclosablePreparedStatement ps = ctx.psCache.get(key); |
|
220 |
|
|
221 |
if (ps == null || ps.isClosed()) |
|
222 |
{ |
|
223 |
if (ps != null) |
|
224 |
{ |
|
225 |
if (LOG.isLoggable(Level.SEVERE)) |
|
226 |
{ |
|
227 |
LOG.log(Level.SEVERE, "Closed statements shouldn't be cached."); |
|
228 |
} |
|
229 |
} |
|
230 |
|
|
231 |
PreparedStatement raw = ctx.physicalConn.prepareStatement(sql, resultSetType, resultSetConcurrency); |
|
232 |
ps = new UnclosablePreparedStatement(raw, false); |
|
233 |
ctx.psCache.put(key, ps); |
|
234 |
} |
|
235 |
else if (ps.isCheckedOut()) |
|
236 |
{ |
|
237 |
// the prepared statement is already in use |