6720_runtime_feedback.patch
| new/src/com/goldencode/p2j/jmx/FwdServerJMX.java 2026-06-10 08:13:45 +0000 | ||
|---|---|---|
| 248 | 248 |
OrmDataSetParam, |
| 249 | 249 |
/** Instrumentation for <code>DataSet.createDynamicDataSet</code>. */ |
| 250 | 250 |
OrmDynamicDataSetParam, |
| 251 |
|
|
| 252 |
/** Lazily hydrated an new record (not rehydrated from cache) */ |
|
| 253 |
HydratorNewRecord, |
|
| 254 |
/** Lazily hydrated a record (including rehydration from cache) */ |
|
| 255 |
HydratorLazyRecord, |
|
| 256 |
/** Fully or partially hydrated a record, but not lazy */ |
|
| 257 |
HydratorNonLazyRecord, |
|
| 258 |
/** The hydrator token matched the result-set token */ |
|
| 259 |
HydratorHit, |
|
| 260 |
/** The hydrator token mismatched the result-set token */ |
|
| 261 |
HydratorMiss, |
|
| 262 |
/** The hydrator fallbacked to refresh and succeeded */ |
|
| 263 |
HyratorSuccessfullRefresh, |
|
| 264 |
/** The hydrator fallbacked to refresh and failed */ |
|
| 265 |
HyratorUnsuccessfullRefresh, |
|
| 266 |
/** Created a {@code ScrollableResults} instance with lazy results */
|
|
| 267 |
HydratorLazyScrollable, |
|
| 268 |
/** Created a {@code ScrollableResults} instance without lazy results */
|
|
| 269 |
HydratorNonLazyScrollable, |
|
| 270 |
/** Executed uniqueResult instance with lazy results */ |
|
| 271 |
HydratorLazyUnique, |
|
| 272 |
/** Executed uniqueResult instance without lazy results */ |
|
| 273 |
HydratorNonLazyUnique, |
|
| 251 | 274 | |
| 252 | 275 |
/** Instrumentation for <code>OutputTableCopier.finished</code> */ |
| 253 | 276 |
OutputTableCopier, |
| ... | ... | |
| 489 | 512 |
public static enum Counter |
| 490 | 513 |
implements OptionalCounter |
| 491 | 514 |
{
|
| 515 |
TempTotalFields, |
|
| 516 |
PersistentTotalFields, |
|
| 517 |
TempHydratedRecordsWhenSessionCloses, |
|
| 518 |
PersistentHydratedRecordsWhenSessionCloses, |
|
| 519 |
TempHydratedFieldsWhenSessionCloses, |
|
| 520 |
PersistentHydratedFieldsWhenSessionCloses, |
|
| 521 |
PersistentHydratedFieldsWithLZS, |
|
| 522 |
PersistentHydratedFieldsWithLoader, |
|
| 523 |
TempHydratedFieldsWithLZS, |
|
| 524 |
TempHydratedFieldsWithLoader, |
|
| 525 |
|
|
| 526 |
|
|
| 527 |
/** Lazily hydrated an new record (not rehydrated from cache) */ |
|
| 528 |
HydratorNewRecord, |
|
| 529 |
/** Lazily hydrated a record (including rehydration from cache) */ |
|
| 530 |
HydratorLazyRecord, |
|
| 531 |
/** Fully or partially hydrated a record, but not lazy */ |
|
| 532 |
HydratorNonLazyRecord, |
|
| 533 |
/** The hydrator token matched the result-set token */ |
|
| 534 |
HydratorHit, |
|
| 535 |
/** The hydrator token mismatched the result-set token */ |
|
| 536 |
HydratorMiss, |
|
| 537 |
/** The hydrator fallbacked to refresh and succeeded */ |
|
| 538 |
HyratorSuccessfullRefresh, |
|
| 539 |
/** The hydrator fallbacked to refresh and failed */ |
|
| 540 |
HyratorUnsuccessfullRefresh, |
|
| 541 |
/** Created a {@code ScrollableResults} instance with lazy results */
|
|
| 542 |
HydratorLazyScrollable, |
|
| 543 |
/** Created a {@code ScrollableResults} instance without lazy results */
|
|
| 544 |
HydratorNonLazyScrollable, |
|
| 545 |
/** Executed uniqueResult instance with lazy results */ |
|
| 546 |
HydratorLazyUnique, |
|
| 547 |
/** Executed uniqueResult instance without lazy results */ |
|
| 548 |
HydratorNonLazyUnique, |
|
| 549 |
/** A call-site's field-usage profile locked in the EAGER hydration verdict */ |
|
| 550 |
HydrationVerdictEager, |
|
| 551 |
/** A call-site's field-usage profile locked in the LAZY hydration verdict */ |
|
| 552 |
HydrationVerdictLazy, |
|
| 553 | ||
| 492 | 554 |
/** Incomning network traffic */ |
| 493 | 555 |
NetworkReads, |
| 494 | 556 |
/** Outgoing network traffic */ |
| new/src/com/goldencode/p2j/persist/Persistence.java 2026-06-10 08:15:57 +0000 | ||
|---|---|---|
| 751 | 751 |
import java.util.function.*; |
| 752 | 752 |
import java.util.logging.*; |
| 753 | 753 |
import com.goldencode.cache.*; |
| 754 |
import com.goldencode.p2j.jmx.*; |
|
| 754 | 755 |
import com.goldencode.p2j.persist.deploy.*; |
| 755 | 756 |
import com.goldencode.p2j.persist.dialect.*; |
| 756 | 757 |
import com.goldencode.p2j.persist.dirty.DirtyShareSupport; |
| ... | ... | |
| 925 | 926 |
* name. |
| 926 | 927 |
*/ |
| 927 | 928 |
private final ConcurrentHashMap<String, Tenant> tenantData = new ConcurrentHashMap<>(); |
| 928 |
|
|
| 929 | ||
| 930 |
/** |
|
| 931 |
* Bounded per-database map of runtime field-usage profiles, keyed by FQL. Used by the |
|
| 932 |
* lazy-feedback hydration layer to decide eager vs lazy hydration per call-site. Hosted on the |
|
| 933 |
* per-database {@code Persistence} singleton (not the per-session context) so that all sessions
|
|
| 934 |
* against this database share the learned verdicts, while different databases stay isolated. The |
|
| 935 |
* map is capped (see {@code FieldUsageProfile.maxProfiles()}): dynamic queries generate unbounded
|
|
| 936 |
* distinct FQL strings, so once the cap is hit new call-sites get no profile and hydrate eagerly. |
|
| 937 |
*/ |
|
| 938 |
private final ConcurrentHashMap<String, FieldUsageProfile> usageProfiles = new ConcurrentHashMap<>(); |
|
| 939 | ||
| 929 | 940 |
/** |
| 930 | 941 |
* A container class which stores tenant-specific objects. Although the persistence tracks all these |
| 931 | 942 |
* {@code Tenant}, at any given moment, only one of is active in each user context.
|
| ... | ... | |
| 1036 | 1047 |
{
|
| 1037 | 1048 |
return PersistenceFactory.getInstance(name); |
| 1038 | 1049 |
} |
| 1039 |
|
|
| 1050 | ||
| 1051 |
/** |
|
| 1052 |
* Resolve (creating if necessary) the runtime field-usage profile for the given FQL call-site. |
|
| 1053 |
* Used by the lazy-feedback hydration layer to decide eager vs lazy hydration per call-site. |
|
| 1054 |
* |
|
| 1055 |
* @param fql |
|
| 1056 |
* The FQL identifying the call-site. |
|
| 1057 |
* |
|
| 1058 |
* @return The shared profile for the call-site, or {@code null} when the feature is disabled or
|
|
| 1059 |
* the per-database profile cap has been reached (in which case the call-site hydrates |
|
| 1060 |
* eagerly, the safe default). |
|
| 1061 |
*/ |
|
| 1062 |
public FieldUsageProfile getOrCreateProfile(String fql) |
|
| 1063 |
{
|
|
| 1064 |
if (!FieldUsageProfile.isEnabled() || fql == null) |
|
| 1065 |
{
|
|
| 1066 |
return null; |
|
| 1067 |
} |
|
| 1068 | ||
| 1069 |
FieldUsageProfile profile = usageProfiles.get(fql); |
|
| 1070 |
if (profile != null) |
|
| 1071 |
{
|
|
| 1072 |
return profile; |
|
| 1073 |
} |
|
| 1074 | ||
| 1075 |
// hard create-cap: dynamic queries produce unbounded distinct FQL, so refuse new profiles once |
|
| 1076 |
// full rather than evicting (a missing profile simply means EAGER). The size check races with |
|
| 1077 |
// concurrent creators, but only ever overshoots the cap by the number of racing threads. |
|
| 1078 |
if (usageProfiles.size() >= FieldUsageProfile.maxProfiles()) |
|
| 1079 |
{
|
|
| 1080 |
return null; |
|
| 1081 |
} |
|
| 1082 | ||
| 1083 |
return usageProfiles.computeIfAbsent(fql, FieldUsageProfile::new); |
|
| 1084 |
} |
|
| 1085 | ||
| 1040 | 1086 |
/** |
| 1041 | 1087 |
* Initialize various infrastructure components of the persistence framework: |
| 1042 | 1088 |
* <ul> |
| ... | ... | |
| 1947 | 1993 |
{
|
| 1948 | 1994 |
local.closeSession(true); |
| 1949 | 1995 |
ErrorEntry ee = exc.getErrorEntry(); |
| 1950 |
ErrorManager.recordOrShowError( |
|
| 1951 |
new int[] {ee.num},
|
|
| 1952 |
new String[] {ee.text},
|
|
| 1953 |
true, |
|
| 1954 |
ee.prefix, |
|
| 1955 |
false, |
|
| 1956 |
false, |
|
| 1957 |
false, |
|
| 1958 |
true); |
|
| 1996 |
ErrorManager.recordOrShowError(new int[] {ee.num},
|
|
| 1997 |
new String[] {ee.text},
|
|
| 1998 |
true, |
|
| 1999 |
ee.prefix, |
|
| 2000 |
false, |
|
| 2001 |
false, |
|
| 2002 |
false, |
|
| 2003 |
true); |
|
| 1959 | 2004 |
handleException(ErrorManager.buildErrorText(ee.num, ee.text, ee.prefix, false), exc); |
| 1960 | 2005 |
} |
| 1961 | 2006 |
catch (PersistenceException exc) |
| ... | ... | |
| 5248 | 5293 |
/** FQL statement */ |
| 5249 | 5294 |
private final String fql; |
| 5250 | 5295 |
|
| 5296 |
/** Database dialect */ |
|
| 5297 |
private final Dialect dialect; |
|
| 5298 |
|
|
| 5251 | 5299 |
/** Does the query was generated with maximum results option? */ |
| 5252 | 5300 |
private final boolean hasMaxResults; |
| 5253 | 5301 |
|
| ... | ... | |
| 5268 | 5316 |
* |
| 5269 | 5317 |
* @param hql |
| 5270 | 5318 |
* FQL statement |
| 5319 |
* @param dialect |
|
| 5320 |
* Database dialect. |
|
| 5271 | 5321 |
* @param maxResults |
| 5272 | 5322 |
* Maximum results. |
| 5273 | 5323 |
* @param startOffset |
| 5274 | 5324 |
* Starting row offset. |
| 5275 | 5325 |
*/ |
| 5276 |
QueryKey(String hql, int maxResults, int startOffset, DataRelation relation, boolean lazyMode) |
|
| 5326 |
QueryKey(String hql, Dialect dialect, int maxResults, int startOffset, DataRelation relation, boolean lazyMode)
|
|
| 5277 | 5327 |
{
|
| 5278 | 5328 |
this.fql = hql; |
| 5329 |
this.dialect = dialect; |
|
| 5279 | 5330 |
this.hasMaxResults = maxResults > 0; |
| 5280 | 5331 |
this.hasStartOffset = startOffset > 0; |
| 5281 | 5332 |
this.whereStr = relation == null ? "" : relation.getWhereString().toJavaType(); |
| 5282 | 5333 |
this.lazyMode = lazyMode; |
| 5283 | 5334 |
|
| 5284 | 5335 |
int result = 17; |
| 5285 |
result = 37 * result + hql.hashCode(); |
|
| 5336 |
result = 37 * result + fql.hashCode(); |
|
| 5337 |
result = 37 * result + dialect.hashCode(); |
|
| 5286 | 5338 |
result = 37 * result + (hasMaxResults ? 1 : 0) + (hasStartOffset ? 2 : 0); |
| 5287 | 5339 |
result = 37 * result + whereStr.hashCode(); |
| 5288 | 5340 |
result = 37 * result + (lazyMode ? 1 : 0); |
| ... | ... | |
| 5308 | 5360 |
QueryKey that = (QueryKey) o; |
| 5309 | 5361 |
|
| 5310 | 5362 |
return this.hash == that.hash && |
| 5363 |
this.dialect.equals(that.dialect) && |
|
| 5311 | 5364 |
this.hasMaxResults == that.hasMaxResults && |
| 5312 | 5365 |
this.hasStartOffset == that.hasStartOffset && |
| 5313 | 5366 |
this.fql.equals(that.fql) && |
| ... | ... | |
| 5334 | 5387 |
@Override |
| 5335 | 5388 |
public String toString() |
| 5336 | 5389 |
{
|
| 5337 |
return fql + "[" + hasMaxResults + ":" + hasStartOffset + "];" + whereStr;
|
|
| 5390 |
return fql + "[" + hasMaxResults + ":" + hasStartOffset + "] (" + dialect + ") " + whereStr;
|
|
| 5338 | 5391 |
} |
| 5339 | 5392 |
} |
| 5340 | 5393 |
|
| ... | ... | |
| 5561 | 5614 |
Query query = null; |
| 5562 | 5615 |
fql = fql.trim(); // just in case |
| 5563 | 5616 |
|
| 5564 |
// TODO: use the dialect also? I.e: is it possible to have different SQL for same FQL |
|
| 5565 |
// if different dialects are used? |
|
| 5566 |
QueryKey key = new QueryKey(fql, maxResults, startOffset, relation, lazyMode); |
|
| 5617 |
QueryKey key = new QueryKey(fql, dialect, maxResults, startOffset, relation, lazyMode); |
|
| 5567 | 5618 |
|
| 5568 |
synchronized (staticQueryCache) |
|
| 5619 |
query = staticQueryCache.get(key); |
|
| 5620 |
if (query == null) |
|
| 5569 | 5621 |
{
|
| 5570 |
query = staticQueryCache.get(key); |
|
| 5571 |
if (query == null) |
|
| 5622 |
Consumer<Query> c = (q) -> |
|
| 5572 | 5623 |
{
|
| 5573 |
query = Session.createQuery(q -> |
|
| 5574 |
{
|
|
| 5575 |
synchronized (staticQueryCache) |
|
| 5576 |
{
|
|
| 5577 |
// NOTE: the FQL string is not converted to SQL at this time. The conversion |
|
| 5578 |
// is performed once, lazily, when the first navigation is required by a |
|
| 5579 |
// call of list(), scroll(), or uniqueResult() on the new query |
|
| 5580 |
|
|
| 5581 |
staticQueryCache.put(key, q); |
|
| 5582 |
} |
|
| 5583 |
}, |
|
| 5584 |
fql); |
|
| 5585 |
} |
|
| 5624 |
// NOTE: the FQL string is not converted to SQL at this time. The conversion |
|
| 5625 |
// is performed once, lazily, when the first navigation is required by a |
|
| 5626 |
// call of list(), scroll(), or uniqueResult() on the new query |
|
| 5627 |
|
|
| 5628 |
staticQueryCache.put(key, q); |
|
| 5629 |
}; |
|
| 5630 |
|
|
| 5631 |
query = Session.createQuery(c, fql); |
|
| 5586 | 5632 |
} |
| 5587 | 5633 |
|
| 5588 | 5634 |
// update [maxResults] and [startOffset] because the cached query might have set |
| ... | ... | |
| 5979 | 6025 |
try |
| 5980 | 6026 |
{
|
| 5981 | 6027 |
session.associate(record); |
| 6028 |
record.refreshHydrator(session); |
|
| 5982 | 6029 |
} |
| 5983 | 6030 |
catch (StaleRecordException exc) |
| 5984 | 6031 |
{
|
| ... | ... | |
| 6142 | 6189 |
try |
| 6143 | 6190 |
{
|
| 6144 | 6191 |
closingSession = true; |
| 6145 |
|
|
| 6192 |
hydrateActiveRecords(); |
|
| 6146 | 6193 |
if (isTransactionOpen()) |
| 6147 | 6194 |
{
|
| 6148 | 6195 |
if (LOG.isLoggable(Level.WARNING)) |
| ... | ... | |
| 6221 | 6268 |
} |
| 6222 | 6269 |
} |
| 6223 | 6270 |
|
| 6271 |
private void hydrateActiveRecords() |
|
| 6272 |
{
|
|
| 6273 |
Iterator<RecordBuffer> iter = bufferManager.activeBuffers(Persistence.this); |
|
| 6274 |
|
|
| 6275 |
Record record; |
|
| 6276 |
|
|
| 6277 |
while (iter.hasNext()) |
|
| 6278 |
{
|
|
| 6279 |
record = iter.next().getCurrentRecord(); |
|
| 6280 |
if (record.primaryKey() < 0) |
|
| 6281 |
{
|
|
| 6282 |
continue; |
|
| 6283 |
} |
|
| 6284 |
|
|
| 6285 |
record.hydrateRemainingFields(); |
|
| 6286 |
} |
|
| 6287 |
} |
|
| 6288 |
|
|
| 6224 | 6289 |
/** |
| 6225 | 6290 |
* Notify all registered session listeners that the current ORM session is about to |
| 6226 | 6291 |
* close or the current, implicit transaction is being committed. Listeners which were set |
| new/src/com/goldencode/p2j/persist/Record.java 2026-06-10 06:43:53 +0000 | ||
|---|---|---|
| 102 | 102 |
import java.util.*; |
| 103 | 103 |
import java.util.function.*; |
| 104 | 104 | |
| 105 |
import com.goldencode.p2j.jmx.*; |
|
| 105 | 106 |
import com.goldencode.p2j.oo.lang.BaseObject; |
| 106 | 107 |
import com.goldencode.p2j.persist.orm.*; |
| 107 | 108 |
import com.goldencode.p2j.util.*; |
| ... | ... | |
| 115 | 116 |
extends BaseRecord |
| 116 | 117 |
implements PropertiesDescriptor |
| 117 | 118 |
{
|
| 119 |
private static final SimpleCounter HYDRATE_RECORDS_WHEN_SESSION_CLOSE = |
|
| 120 |
SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentHydratedRecordsWhenSessionCloses); |
|
| 121 |
|
|
| 122 |
private static final SimpleCounter TOTAL_FIELDS = |
|
| 123 |
SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentTotalFields); |
|
| 124 |
|
|
| 125 |
private static final SimpleCounter HYDRATE_FIELD_WHEN_SESSION_CLOSES = |
|
| 126 |
SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentHydratedFieldsWhenSessionCloses); |
|
| 127 | ||
| 128 |
private static final SimpleCounter HYDRATED_FIELDS_LZS = |
|
| 129 |
SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentHydratedFieldsWithLZS); |
|
| 130 |
|
|
| 131 |
private static final SimpleCounter HYDRATED_FIELDS_LOADER = |
|
| 132 |
SimpleCounter.getInstance(FwdServerJMX.Counter.PersistentHydratedFieldsWithLoader); |
|
| 133 |
|
|
| 118 | 134 |
/** |
| 119 | 135 |
* Default constructor needed by DMOs extending this class. |
| 120 | 136 |
*/ |
| ... | ... | |
| 163 | 179 |
{
|
| 164 | 180 |
checkIncomplete(offset); |
| 165 | 181 | |
| 166 |
Integer datum = (Integer) data[offset];
|
|
| 182 |
Integer datum = (Integer) getDatum(offset);
|
|
| 167 | 183 |
|
| 168 | 184 |
return datum != null ? new integer(datum) : new integer(); |
| 169 | 185 |
} |
| ... | ... | |
| 272 | 288 |
{
|
| 273 | 289 |
checkIncomplete(offset); |
| 274 | 290 | |
| 275 |
OffsetDateTime datum = (OffsetDateTime) data[offset];
|
|
| 291 |
OffsetDateTime datum = (OffsetDateTime) getDatum(offset);
|
|
| 276 | 292 |
|
| 277 | 293 |
return datum != null ? new datetimetz(datum) : new datetimetz(); |
| 278 | 294 |
} |
| ... | ... | |
| 362 | 378 |
} |
| 363 | 379 |
} |
| 364 | 380 |
|
| 381 |
@Override |
|
| 382 |
protected void countHydrateRecordWhenSessionCloses(long count) |
|
| 383 |
{
|
|
| 384 |
HYDRATE_RECORDS_WHEN_SESSION_CLOSE.update(count); |
|
| 385 |
} |
|
| 386 |
|
|
| 387 |
@Override |
|
| 388 |
protected void countTotalFields(long count) |
|
| 389 |
{
|
|
| 390 |
TOTAL_FIELDS.update(count); |
|
| 391 |
} |
|
| 392 |
|
|
| 393 |
@Override |
|
| 394 |
protected void countHydratedFieldsWithLoader(long count) |
|
| 395 |
{
|
|
| 396 |
HYDRATED_FIELDS_LOADER.update(count); |
|
| 397 |
} |
|
| 398 |
|
|
| 399 |
@Override |
|
| 400 |
protected void countHydratedFieldsWithLZS(long count) |
|
| 401 |
{
|
|
| 402 |
HYDRATED_FIELDS_LZS.update(count); |
|
| 403 |
} |
|
| 404 |
|
|
| 405 |
@Override |
|
| 406 |
protected void countHydratedFieldWhenSessionCloses(long count) |
|
| 407 |
{
|
|
| 408 |
HYDRATE_FIELD_WHEN_SESSION_CLOSES.update(count); |
|
| 409 |
} |
|
| 410 |
|
|
| 365 | 411 |
/** |
| 366 | 412 |
* Get an {@code datetime} value for the datum at the given offset in the property array.
|
| 367 | 413 |
* <p> |
| ... | ... | |
| 378 | 424 |
{
|
| 379 | 425 |
checkIncomplete(offset); |
| 380 | 426 | |
| 381 |
Timestamp datum = (Timestamp) data[offset];
|
|
| 427 |
Timestamp datum = (Timestamp) getDatum(offset);
|
|
| 382 | 428 |
|
| 383 | 429 |
return datum != null ? new datetime(datum) : new datetime(); |
| 384 | 430 |
} |
| ... | ... | |
| 483 | 529 |
{
|
| 484 | 530 |
checkIncomplete(offset); |
| 485 | 531 | |
| 486 |
Date datum = (Date) data[offset];
|
|
| 532 |
Date datum = (Date) getDatum(offset);
|
|
| 487 | 533 |
|
| 488 | 534 |
return datum != null ? new date(datum) : new date(); |
| 489 | 535 |
} |
| ... | ... | |
| 588 | 634 |
{
|
| 589 | 635 |
checkIncomplete(offset); |
| 590 | 636 | |
| 591 |
Boolean datum = (Boolean) data[offset];
|
|
| 637 |
Boolean datum = (Boolean) getDatum(offset);
|
|
| 592 | 638 |
|
| 593 | 639 |
return datum != null ? new logical(datum) : new logical(); |
| 594 | 640 |
} |
| ... | ... | |
| 691 | 737 |
*/ |
| 692 | 738 |
public final int64 _getInt64(int offset) |
| 693 | 739 |
{
|
| 694 |
Long datum = (Long) data[offset]; |
|
| 740 |
checkIncomplete(offset); |
|
| 741 |
|
|
| 742 |
Long datum = (Long) getDatum(offset); |
|
| 695 | 743 |
|
| 696 | 744 |
return datum != null ? new int64(datum) : new int64(); |
| 697 | 745 |
} |
| ... | ... | |
| 800 | 848 |
{
|
| 801 | 849 |
checkIncomplete(offset); |
| 802 | 850 | |
| 803 |
BigDecimal datum = (BigDecimal) data[offset];
|
|
| 851 |
BigDecimal datum = (BigDecimal) getDatum(offset);
|
|
| 804 | 852 |
|
| 805 | 853 |
return datum != null ? new decimal(datum) : new decimal(); |
| 806 | 854 |
} |
| ... | ... | |
| 917 | 965 |
public final character _getCharacter(int offset) |
| 918 | 966 |
{
|
| 919 | 967 |
checkIncomplete(offset); |
| 920 | ||
| 921 |
String datum = (String) data[offset];
|
|
| 968 |
|
|
| 969 |
String datum = (String) getDatum(offset);
|
|
| 922 | 970 |
|
| 923 | 971 |
if (datum != null) |
| 924 | 972 |
{
|
| ... | ... | |
| 1030 | 1078 |
{
|
| 1031 | 1079 |
checkIncomplete(offset); |
| 1032 | 1080 | |
| 1033 |
Long datum = (Long) data[offset];
|
|
| 1081 |
Long datum = (Long) getDatum(offset);
|
|
| 1034 | 1082 |
|
| 1035 | 1083 |
return datum != null ? new rowid(datum) : new rowid(); |
| 1036 | 1084 |
} |
| ... | ... | |
| 1137 | 1185 |
{
|
| 1138 | 1186 |
checkIncomplete(offset); |
| 1139 | 1187 | |
| 1140 |
Long datum = (Long) data[offset];
|
|
| 1188 |
Long datum = (Long) getDatum(offset);
|
|
| 1141 | 1189 |
|
| 1142 | 1190 |
return datum != null ? handle.fromResourceId(datum, false) : new handle(); |
| 1143 | 1191 |
} |
| ... | ... | |
| 1244 | 1292 |
{
|
| 1245 | 1293 |
checkIncomplete(offset); |
| 1246 | 1294 | |
| 1247 |
Long datum = (Long) data[offset];
|
|
| 1295 |
Long datum = (Long) getDatum(offset);
|
|
| 1248 | 1296 |
|
| 1249 | 1297 |
return datum != null ? new recid(datum) : new recid(); |
| 1250 | 1298 |
} |
| ... | ... | |
| 1350 | 1398 |
{
|
| 1351 | 1399 |
checkIncomplete(offset); |
| 1352 | 1400 | |
| 1353 |
byte[] datum = (byte[]) data[offset];
|
|
| 1401 |
byte[] datum = (byte[]) getDatum(offset);
|
|
| 1354 | 1402 |
|
| 1355 | 1403 |
return datum != null ? new blob(datum) : new blob(); |
| 1356 | 1404 |
} |
| ... | ... | |
| 1457 | 1505 |
{
|
| 1458 | 1506 |
checkIncomplete(offset); |
| 1459 | 1507 | |
| 1460 |
return new clob((String) data[offset], getCodePage(offset));
|
|
| 1508 |
return new clob((String) getDatum(offset), getCodePage(offset));
|
|
| 1461 | 1509 |
} |
| 1462 | 1510 |
|
| 1463 | 1511 |
/** |
| ... | ... | |
| 1560 | 1608 |
{
|
| 1561 | 1609 |
checkIncomplete(offset); |
| 1562 | 1610 | |
| 1563 |
String datum = (String) data[offset];
|
|
| 1611 |
String datum = (String) getDatum(offset);
|
|
| 1564 | 1612 |
|
| 1565 | 1613 |
return datum != null ? comhandle.fromString(datum) : new comhandle(); |
| 1566 | 1614 |
} |
| ... | ... | |
| 1665 | 1713 |
{
|
| 1666 | 1714 |
checkIncomplete(offset); |
| 1667 | 1715 | |
| 1668 |
Long datum = (Long) data[offset];
|
|
| 1716 |
Long datum = (Long) getDatum(offset);
|
|
| 1669 | 1717 |
|
| 1670 | 1718 |
// the instance must be typed to Progress.Lang.Object |
| 1671 | 1719 |
object<?> res = new object<>(BaseObject.class); |
| ... | ... | |
| 1781 | 1829 |
{
|
| 1782 | 1830 |
checkIncomplete(offset); |
| 1783 | 1831 | |
| 1784 |
byte[] datum = (byte[]) data[offset];
|
|
| 1832 |
byte[] datum = (byte[]) getDatum(offset);
|
|
| 1785 | 1833 |
|
| 1786 | 1834 |
return datum != null ? new raw(datum) : raw.instantiateUnknownRaw(); |
| 1787 | 1835 |
} |
| ... | ... | |
| 1956 | 2004 |
RecordMeta meta = _recordMeta(); |
| 1957 | 2005 |
if (oneLine) |
| 1958 | 2006 |
{
|
| 1959 |
int last = data.length;
|
|
| 2007 |
int last = getLength();
|
|
| 1960 | 2008 |
int start = (this instanceof TempTableRecord) ? 5 : 0; |
| 1961 | 2009 |
sb.append(meta.legacyName).append('/').append(meta.tables[0]).append(':').append(id).append(" {");
|
| 1962 | 2010 |
for (int i = start; i < last; i++) |
| ... | ... | |
| 1966 | 2014 |
sb.append(", ");
|
| 1967 | 2015 |
} |
| 1968 | 2016 |
|
| 1969 |
boolean isChar = data[i] instanceof String;
|
|
| 2017 |
boolean isChar = getSimpleDatum(i) instanceof String;
|
|
| 1970 | 2018 |
if (isChar) |
| 1971 | 2019 |
{
|
| 1972 | 2020 |
sb.append("\"");
|
| ... | ... | |
| 1974 | 2022 |
|
| 1975 | 2023 |
boolean incomplete = readFields != null && !readFields.get(i); |
| 1976 | 2024 |
|
| 1977 |
sb.append(data[i] == null ? incomplete ? "n/a" : "?" : data[i]); |
|
| 2025 |
Object datum = getSimpleDatum(i); |
|
| 2026 |
sb.append(datum == null ? incomplete ? "n/a" : "?" : datum); |
|
| 1978 | 2027 |
if (isChar) |
| 1979 | 2028 |
{
|
| 1980 | 2029 |
sb.append("\"");
|
| ... | ... | |
| 1997 | 2046 |
int extent = pms[i].getExtent(); |
| 1998 | 2047 |
if (extent == 0) |
| 1999 | 2048 |
{
|
| 2000 |
sb.append(data[i]);
|
|
| 2049 |
sb.append(getSimpleDatum(i));
|
|
| 2001 | 2050 |
} |
| 2002 | 2051 |
else |
| 2003 | 2052 |
{
|
| 2004 | 2053 |
for (int k = 0; k < extent; k++) |
| 2005 | 2054 |
{
|
| 2006 | 2055 |
sb.append(k == 0 ? "[" : ""); |
| 2007 |
sb.append(data[i + k]);
|
|
| 2056 |
sb.append(getSimpleDatum(i + k));
|
|
| 2008 | 2057 |
} |
| 2009 | 2058 |
|
| 2010 | 2059 |
i += extent - 1; |
| ... | ... | |
| 2042 | 2091 |
Object val = properties.get(prop.getLegacyName()); |
| 2043 | 2092 |
val = f.apply(prop, val); |
| 2044 | 2093 | |
| 2094 |
Object[] data = getData(); |
|
| 2045 | 2095 |
if (prop.getExtent() > 0) |
| 2046 | 2096 |
{
|
| 2047 | 2097 |
for (int j = 0; j < prop.getExtent(); j++, i++) |
| ... | ... | |
| 2050 | 2100 |
|
| 2051 | 2101 |
BaseDataType fval = (BaseDataType) Array.get(val, j); |
| 2052 | 2102 |
prop.getDataHandler().setField(data, fval, i, prop); |
| 2103 |
setLivePropsAtOffset(i); |
|
| 2053 | 2104 |
} |
| 2054 | 2105 |
} |
| 2055 | 2106 |
else |
| 2056 | 2107 |
{
|
| 2057 | 2108 |
prop.getDataHandler().setField(data, (BaseDataType) val, i, prop); |
| 2109 |
setLivePropsAtOffset(i); |
|
| 2058 | 2110 |
} |
| 2059 | 2111 |
} |
| 2060 | 2112 |
} |
| ... | ... | |
| 2127 | 2179 |
} |
| 2128 | 2180 |
|
| 2129 | 2181 |
/** |
| 2130 |
* Check if two {@link Record} objects are equal from the point of view of the {@code data} array.
|
|
| 2131 |
* <p> |
|
| 2132 |
* <strong>To be noted</strong>, that this method accesses the {@code data} arrays directly without using
|
|
| 2133 |
* any getter. This provides faster access, however should be changed if not all fields are hydrated and |
|
| 2134 |
* access is mandatory from a getter. |
|
| 2135 |
* |
|
| 2136 |
* @param otherRecord |
|
| 2137 |
* The instance to compare against. |
|
| 2138 |
* |
|
| 2139 |
* @return True if the {@code data} arrays are equal, false - otherwise
|
|
| 2140 |
*/ |
|
| 2141 |
public boolean equalData(Record otherRecord) |
|
| 2142 |
{
|
|
| 2143 |
return Arrays.equals(this.data, otherRecord.data); |
|
| 2144 |
} |
|
| 2145 |
|
|
| 2146 |
/** |
|
| 2147 | 2182 |
* Converts this {@code Record} into a POJO, if POJO class is available.
|
| 2148 | 2183 |
* To be implemented by DMO implementations. |
| 2149 | 2184 |
* |
| new/src/com/goldencode/p2j/persist/RecordBuffer.java 2026-06-10 08:17:33 +0000 | ||
|---|---|---|
| 12658 | 12658 |
// don't count usages of template records and don't register with persistence |
| 12659 | 12659 |
if (oldRecord.primaryKey() != null && oldRecord.primaryKey() > 0) |
| 12660 | 12660 |
{
|
| 12661 |
// the buffer is leaving this record: report the field demand observed during the visit |
|
| 12662 |
// to the runtime-feedback hydration layer (no-op unless the record was probing) |
|
| 12663 |
oldRecord.reportDemandSample(); |
|
| 12664 | ||
| 12661 | 12665 |
decrementDMOUseCount(oldRecord, false); |
| 12662 | 12666 |
} |
| 12663 | 12667 |
} |
| new/src/com/goldencode/p2j/persist/TempRecord.java 2026-06-10 06:43:53 +0000 | ||
|---|---|---|
| 17 | 17 |
** 004 CA 20240918 Removed default methods from interfaces and moved them as concrete implementations, as |
| 18 | 18 |
** default methods cause the Java metaspace to spike one order of magnitude. |
| 19 | 19 |
** 005 AS 20241009 Refactored activeOffset into activeOffsets(Bitset). |
| 20 |
** 006 AL2 20231102 Added computeIndexForHydrator for lazy hydration. |
|
| 20 | 21 |
*/ |
| 21 | 22 | |
| 22 | 23 |
/* |
| ... | ... | |
| 74 | 75 | |
| 75 | 76 |
package com.goldencode.p2j.persist; |
| 76 | 77 | |
| 78 |
import com.goldencode.p2j.jmx.*; |
|
| 77 | 79 |
import com.goldencode.p2j.persist.orm.*; |
| 78 | 80 |
import com.goldencode.p2j.util.object; |
| 79 | 81 | |
| ... | ... | |
| 87 | 89 |
extends Record |
| 88 | 90 |
implements TempTableRecord |
| 89 | 91 |
{
|
| 92 |
private static final SimpleCounter HYDRATE_RECORDS_WHEN_SESSION_CLOSE = |
|
| 93 |
SimpleCounter.getInstance(FwdServerJMX.Counter.TempHydratedRecordsWhenSessionCloses); |
|
| 94 |
|
|
| 95 |
private static final SimpleCounter TOTAL_FIELDS = |
|
| 96 |
SimpleCounter.getInstance(FwdServerJMX.Counter.TempTotalFields); |
|
| 97 |
|
|
| 98 |
private static final SimpleCounter HYDRATE_FIELD_WHEN_SESSION_CLOSES = |
|
| 99 |
SimpleCounter.getInstance(FwdServerJMX.Counter.TempHydratedFieldsWhenSessionCloses); |
|
| 100 |
|
|
| 101 |
private static final SimpleCounter HYDRATED_FIELDS_LZS = |
|
| 102 |
SimpleCounter.getInstance(FwdServerJMX.Counter.TempHydratedFieldsWithLZS); |
|
| 103 |
|
|
| 104 |
private static final SimpleCounter HYDRATED_FIELDS_LOADER = |
|
| 105 |
SimpleCounter.getInstance(FwdServerJMX.Counter.TempHydratedFieldsWithLoader); |
|
| 106 |
|
|
| 90 | 107 |
/** The property/column name of "__error-flag__" field. */ |
| 91 | 108 |
public static final String _ERROR_FLAG = "_errorFlag"; |
| 92 | 109 |
|
| ... | ... | |
| 197 | 214 |
@Override |
| 198 | 215 |
public final Long _peerRowid() |
| 199 | 216 |
{
|
| 200 |
return (Long) data[_PEER_ROWID_DATA_INDEX];
|
|
| 217 |
return (Long) getDatum(_PEER_ROWID_DATA_INDEX);
|
|
| 201 | 218 |
} |
| 202 | 219 |
|
| 203 | 220 |
/** |
| ... | ... | |
| 237 | 254 |
@Override |
| 238 | 255 |
public final Integer _rowState() |
| 239 | 256 |
{
|
| 240 |
return (Integer) data[_ROW_STATE_DATA_INDEX];
|
|
| 257 |
return (Integer) getDatum(_ROW_STATE_DATA_INDEX);
|
|
| 241 | 258 |
} |
| 242 | 259 |
|
| 243 | 260 |
/** |
| ... | ... | |
| 278 | 295 |
@Override |
| 279 | 296 |
public final Long _originRowid() |
| 280 | 297 |
{
|
| 281 |
return (Long) data[_ORIGIN_ROWID_DATA_INDEX];
|
|
| 298 |
return (Long) getDatum(_ORIGIN_ROWID_DATA_INDEX);
|
|
| 282 | 299 |
} |
| 283 | 300 |
|
| 284 | 301 |
/** |
| ... | ... | |
| 318 | 335 |
@Override |
| 319 | 336 |
public final Long _datasourceRowid() |
| 320 | 337 |
{
|
| 321 |
return (Long) data[_DATASOURCE_ROWID_INDEX];
|
|
| 338 |
return (Long) getDatum(_DATASOURCE_ROWID_INDEX);
|
|
| 322 | 339 |
} |
| 323 | 340 |
|
| 324 | 341 |
/** |
| ... | ... | |
| 356 | 373 |
@Override |
| 357 | 374 |
public final Integer _errorFlags() |
| 358 | 375 |
{
|
| 359 |
return (Integer) data[_ERROR_FLAG_DATA_INDEX];
|
|
| 376 |
return (Integer) getDatum(_ERROR_FLAG_DATA_INDEX);
|
|
| 360 | 377 |
} |
| 361 | 378 |
|
| 362 | 379 |
/** |
| ... | ... | |
| 392 | 409 |
@Override |
| 393 | 410 |
public final String _errorString() |
| 394 | 411 |
{
|
| 395 |
return (String) data[_ERROR_STRING_DATA_INDEX];
|
|
| 412 |
return (String) getDatum(_ERROR_STRING_DATA_INDEX);
|
|
| 396 | 413 |
} |
| 397 | 414 |
|
| 398 | 415 |
/** |
| ... | ... | |
| 507 | 524 |
Class<?> implIFace = _recordMeta().getDmoMeta().getAnnotatedInterface(); |
| 508 | 525 |
TemporaryBuffer.updateObjectRefCount(implIFace, oldVal, newVal); |
| 509 | 526 |
} |
| 527 |
|
|
| 528 |
@Override |
|
| 529 |
protected int getHydratorRowOffset() |
|
| 530 |
{
|
|
| 531 |
return 2; |
|
| 532 |
} |
|
| 533 |
|
|
| 534 |
@Override |
|
| 535 |
protected void countHydrateRecordWhenSessionCloses(long count) |
|
| 536 |
{
|
|
| 537 |
HYDRATE_RECORDS_WHEN_SESSION_CLOSE.update(count); |
|
| 538 |
} |
|
| 539 |
|
|
| 540 |
@Override |
|
| 541 |
protected void countTotalFields(long count) |
|
| 542 |
{
|
|
| 543 |
TOTAL_FIELDS.update(count); |
|
| 544 |
} |
|
| 545 |
|
|
| 546 |
@Override |
|
| 547 |
protected void countHydratedFieldsWithLZS(long count) |
|
| 548 |
{
|
|
| 549 |
HYDRATED_FIELDS_LZS.update(count); |
|
| 550 |
} |
|
| 551 |
|
|
| 552 |
@Override |
|
| 553 |
protected void countHydratedFieldsWithLoader(long count) |
|
| 554 |
{
|
|
| 555 |
HYDRATED_FIELDS_LOADER.update(count); |
|
| 556 |
} |
|
| 557 |
|
|
| 558 |
@Override |
|
| 559 |
protected void countHydratedFieldWhenSessionCloses(long count) |
|
| 560 |
{
|
|
| 561 |
HYDRATE_FIELD_WHEN_SESSION_CLOSES.update(count); |
|
| 562 |
} |
|
| 510 | 563 |
} |
| new/src/com/goldencode/p2j/persist/dialect/Dialect.java 2026-06-10 08:51:59 +0000 | ||
|---|---|---|
| 213 | 213 |
import java.util.logging.*; |
| 214 | 214 |
import com.goldencode.p2j.cfg.*; |
| 215 | 215 |
import com.goldencode.p2j.convert.*; |
| 216 |
import com.goldencode.p2j.directory.*; |
|
| 216 | 217 |
import com.goldencode.p2j.persist.*; |
| 217 | 218 |
import com.goldencode.p2j.persist.deploy.*; |
| 218 | 219 |
import com.goldencode.p2j.persist.orm.*; |
| ... | ... | |
| 264 | 265 |
|
| 265 | 266 |
/** Logger */ |
| 266 | 267 |
private static final CentralLogger log = CentralLogger.get(Dialect.class.getName()); |
| 267 |
|
|
| 268 | ||
| 269 |
/** Directory path for the lazy-hydration master switch. */ |
|
| 270 |
private static final String CFG_LAZY_HYDRATION = "persistence/lazy-hydration/enabled"; |
|
| 271 | ||
| 272 |
/** |
|
| 273 |
* Master switch for lazy (just-in-time) hydration of DMOs, read once from configuration. When |
|
| 274 |
* {@code false}, hydration is always eager and the runtime-feedback layer is moot (no call-site is
|
|
| 275 |
* ever wrapped lazily). Defaults to enabled. |
|
| 276 |
*/ |
|
| 277 |
private static final boolean lazyHydrationEnabled = |
|
| 278 |
DirectoryManager.getInstance().getBoolean(Directory.ID_RELATIVE_SERVER, CFG_LAZY_HYDRATION, false); |
|
| 279 | ||
| 268 | 280 |
/** Map of known/loaded dialect instances */ |
| 269 | 281 |
private static final Map<String, Dialect> knownDialects = new ConcurrentHashMap<>(); |
| 270 | 282 |
|
| ... | ... | |
| 605 | 617 |
} |
| 606 | 618 |
|
| 607 | 619 |
/** |
| 608 |
* Create the error descriptor from the SQLException message.
|
|
| 609 |
* |
|
| 620 |
* Create the error descriptor from the {@code SQLException} message.
|
|
| 621 |
*
|
|
| 610 | 622 |
* @param message |
| 611 | 623 |
* The SQLException message. |
| 612 | 624 |
* |
| ... | ... | |
| 620 | 632 |
/** |
| 621 | 633 |
* Check if Common Table Expressions should be used for converted CONTAINS operator |
| 622 | 634 |
* |
| 623 |
* @return <code>true</code> if Common Table Expressions should be used for converted CONTAINS operator |
|
| 635 |
* @return <code>true</code> if Common Table Expressions should be used for converted CONTAINS operator
|
|
| 624 | 636 |
*/ |
| 625 | 637 |
public boolean useCTE4Contains() |
| 626 | 638 |
{
|
| ... | ... | |
| 718 | 730 |
public abstract void generateWordTablesDDLImpl(String dbName, |
| 719 | 731 |
PrintStream out, |
| 720 | 732 |
String eoln, |
| 721 |
Collection<WordTable> wordTables);
|
|
| 733 |
Collection<WordTable> wordTables); |
|
| 722 | 734 |
|
| 723 | 735 |
/** |
| 724 | 736 |
* Create a data object, the purpose of which is specific to a particular |
| ... | ... | |
| 2189 | 2201 |
{
|
| 2190 | 2202 |
return null; |
| 2191 | 2203 |
} |
| 2204 | ||
| 2205 |
/** |
|
| 2206 |
* Indicate whether this dialect is likely to benefit from lazy hydration of DMOs when reading a record |
|
| 2207 |
* from the database. This serves as a hint, but may be overridden by configuration or runtime conditions. |
|
| 2208 |
* |
|
| 2209 |
* @return {@code true} unless disabled via the {@code persistence/lazy-hydration/enabled}
|
|
| 2210 |
* configuration or overridden by subclasses. |
|
| 2211 |
*/ |
|
| 2212 |
public boolean useLazyHydration() |
|
| 2213 |
{
|
|
| 2214 |
return lazyHydrationEnabled; |
|
| 2215 |
} |
|
| 2192 | 2216 |
|
| 2193 | 2217 |
/** |
| 2194 | 2218 |
* Use the provided database connection to extract the collation used by the database and report it |
| new/src/com/goldencode/p2j/persist/dirty/DefaultDirtyShareManager.java 2026-06-10 06:43:53 +0000 | ||
|---|---|---|
| 834 | 834 |
indexUpdateStates.remove(ident); |
| 835 | 835 |
} |
| 836 | 836 |
|
| 837 |
String hql = "delete from " + entity + |
|
| 838 |
" where " + DatabaseManager.PRIMARY_KEY + " = ?"; |
|
| 837 |
String fql = "delete from " + entity + " where " + Session.PK + " = ?"; |
|
| 839 | 838 |
Session session = null; |
| 840 | 839 |
boolean inTx = false; |
| 841 | 840 |
|
| ... | ... | |
| 843 | 842 |
{
|
| 844 | 843 |
session = new Session(dirtyDatabase); |
| 845 | 844 |
inTx = session.beginTransaction(); |
| 846 |
Query query = Session.createQuery(hql);
|
|
| 845 |
Query query = Session.createQuery(fql);
|
|
| 847 | 846 |
query.setParameter(0, id); |
| 848 | 847 |
query.executeUpdate(session); |
| 849 | 848 |
session.commit(); |
| ... | ... | |
| 1223 | 1222 |
} |
| 1224 | 1223 |
|
| 1225 | 1224 |
/** |
| 1226 |
* Execute an HQL query and return the results as a list. If the query returns multiple results
|
|
| 1225 |
* Execute an FQL query and return the results as a list. If the query returns multiple results
|
|
| 1227 | 1226 |
* per row, each element in the list will be an array of {@code Object}s.
|
| 1228 | 1227 |
* <p> |
| 1229 | 1228 |
* No locking is attempted. |
| new/src/com/goldencode/p2j/persist/orm/AbstractRowStructure.java 2026-06-10 06:43:53 +0000 | ||
|---|---|---|
| 12 | 12 |
** 003 LS 20241105 Added hasOnlyPK(). |
| 13 | 13 |
** 004 OM 20240701 Replaced + concatenation with append in StringBuilder. |
| 14 | 14 |
** SP 20250321 Removed usage of prop.index. |
| 15 |
** 005 AL2 20231214 Fixed hydrate extents to avoid honoring recOffset if set on -1. |
|
| 15 | 16 |
*/ |
| 16 | 17 | |
| 17 | 18 |
/* |
| ... | ... | |
| 177 | 178 |
* @throws PersistenceException |
| 178 | 179 |
* thrown when hydrating extents fails (as it requires extra SQL queries to be run). |
| 179 | 180 |
*/ |
| 180 |
protected void hydrateExtents(Session session, BaseRecord r, int recOffset) |
|
| 181 |
@Override |
|
| 182 |
public void hydrateExtents(Session session, BaseRecord r, int recOffset) |
|
| 181 | 183 |
throws PersistenceException |
| 182 | 184 |
{
|
| 183 | 185 |
// TODO: collect extent properties and do a new query from SQL, see Loader.readExtentData() |
| ... | ... | |
| 188 | 190 |
PropertyMeta[] propsMeta = recordMeta.getPropertyMeta(false); |
| 189 | 191 |
String[] loadSqls = recordMeta.loadSql; |
| 190 | 192 |
int[] loadSqlsIndexes = recordMeta.loadSqlIndexes; |
| 191 |
boolean incomplete = isIncomplete();
|
|
| 193 |
boolean arbitrary = isIncomplete() || recOffset == -1;
|
|
| 192 | 194 |
|
| 193 | 195 |
// only use the extent queries, skip the loadSqls[0] which contains the simple properties |
| 194 | 196 |
for (int i = 1; i < loadSqls.length; i++) // skip "plain" columns |
| 195 | 197 |
{
|
| 196 |
if (incomplete)
|
|
| 198 |
if (arbitrary)
|
|
| 197 | 199 |
{
|
| 198 | 200 |
// resolve the offset in the 'props' array where this sql starts. |
| 199 | 201 |
// all non-expanded extent fields are loaded even for incomplete records. |
| new/src/com/goldencode/p2j/persist/orm/AdaptiveRowStructure.java 2026-06-10 06:43:53 +0000 | ||
|---|---|---|
| 10 | 10 |
** AL2 20231212 Use dmoMeta directly, without getter. |
| 11 | 11 |
** 002 OM 20240715 Fixed copy/paste bug. |
| 12 | 12 |
** 003 LS 20250714 Added 'getNeededFields' implementation. |
| 13 |
** 004 AL2 20231214 Added implementation for hydrateAt. |
|
| 13 | 14 |
*/ |
| 14 | 15 | |
| 15 | 16 |
/* |
| ... | ... | |
| 69 | 70 | |
| 70 | 71 |
import java.sql.ResultSet; |
| 71 | 72 |
import java.sql.SQLException; |
| 72 |
import java.util.BitSet; |
|
| 73 |
import java.util.Iterator; |
|
| 73 |
import java.util.*; |
|
| 74 | 74 | |
| 75 | 75 |
import com.goldencode.p2j.persist.*; |
| 76 | 76 | |
| ... | ... | |
| 228 | 228 |
return delegate.getNeededFields(); |
| 229 | 229 |
} |
| 230 | 230 | |
| 231 | ||
| 232 |
/** |
|
| 233 |
* Hydrate only one property in the base-record. This is largely used by the lazy hydrator, |
|
| 234 |
* allowing it to hydrate only the property that is accessed now. The other hydration method |
|
| 235 |
* ({@link #hydrate}) eagerly hydrates all properties, is a slow process and may do useless
|
|
| 236 |
* hydrations on properties that are not accessed. |
|
| 237 |
* |
|
| 238 |
* @param resultSet |
|
| 239 |
* The ResultSet from which the hydration should be done |
|
| 240 |
* @param rsOffset |
|
| 241 |
* The result set offset from which the reading should be done. This is relevant as |
|
| 242 |
* the result set may contain a joined row, but we are interested only in a specific |
|
| 243 |
* record that may start at a specific offset. |
|
| 244 |
* @param r |
|
| 245 |
* The record that should be hydrated. |
|
| 246 |
* @param propOffset |
|
| 247 |
* The property that should be hydrated. The caller is responsible in assuring that |
|
| 248 |
* this property wasn't hydrated before. |
|
| 249 |
* |
|
| 250 |
* @return {@code true} if this worked due to the fact that it was a scalar value. {@code false}
|
|
| 251 |
* in case the property was a non-expanded |
|
| 252 |
* |
|
| 253 |
* @throws PersistenceException |
|
| 254 |
* This is thrown when reading the scalar property fails. |
|
| 255 |
*/ |
|
| 256 |
@Override |
|
| 257 |
public boolean hydrateAt(ResultSet resultSet, int rsOffset, BaseRecord r, int dataIndex) |
|
| 258 |
throws PersistenceException |
|
| 259 |
{
|
|
| 260 |
return delegate.hydrateAt(resultSet, rsOffset, r, dataIndex); |
|
| 261 |
} |
|
| 262 |
|
|
| 263 |
/** |
|
| 264 |
* Retrieve the fields that were loaded from the database. |
|
| 265 |
* |
|
| 266 |
* @return A BitSet object, where true means that the field associated with that position in the {@code
|
|
| 267 |
* data} array has been loaded from database, and false - otherwise. |
|
| 268 |
*/ |
|
| 269 |
@Override |
|
| 270 |
public BitSet getLoadedFields() |
|
| 271 |
{
|
|
| 272 |
return delegate.getLoadedFields(); |
|
| 273 |
} |
|
| 274 |
|
|
| 231 | 275 |
/** |
| 232 | 276 |
* Invalidation logic that will pick up everything that was appended until now and moved into |
| 233 | 277 |
* an un-ordered implementation. This routine should be avoided as much as possible in the |
| new/src/com/goldencode/p2j/persist/orm/BaseRecord.java 2026-06-10 08:17:13 +0000 | ||
|---|---|---|
| 2 | 2 |
** Module : BaseRecord.java |
| 3 | 3 |
** Abstract : The abstract base class for all business data model object classes. |
| 4 | 4 |
** |
| 5 |
** Copyright (c) 2019-2025, Golden Code Development Corporation.
|
|
| 5 |
** Copyright (c) 2019-2026, Golden Code Development Corporation.
|
|
| 6 | 6 |
** |
| 7 | 7 |
** -#- -I- --Date-- ---------------------------------------Description--------------------------------------- |
| 8 | 8 |
** 001 ECF 20191001 Created initial version with basic data and accessors. |
| ... | ... | |
| 112 | 112 | |
| 113 | 113 |
package com.goldencode.p2j.persist.orm; |
| 114 | 114 | |
| 115 |
import com.goldencode.p2j.jmx.*; |
|
| 115 | 116 |
import com.goldencode.p2j.persist.*; |
| 116 | 117 |
import com.goldencode.p2j.rest.serializers.*; |
| 117 | 118 |
import com.goldencode.p2j.schema.*; |
| 118 | 119 |
import com.goldencode.p2j.util.*; |
| 119 | 120 |
import com.goldencode.p2j.util.logging.*; |
| 120 | ||
| 121 | 121 |
import java.lang.reflect.*; |
| 122 | 122 |
import java.sql.*; |
| 123 | 123 |
import java.util.*; |
| ... | ... | |
| 162 | 162 |
/** Surrogate primary key of the record */ |
| 163 | 163 |
protected Long id = null; |
| 164 | 164 |
|
| 165 |
/** The data represented by this record, in it's low-level (database friendly) form */ |
|
| 166 |
protected final Object[] data; |
|
| 167 |
|
|
| 168 | 165 |
/** For an incomplete record, this holds the fields which were read or assigned in this record. */ |
| 169 | 166 |
protected BitSet readFields = null; |
| 170 | 167 |
|
| ... | ... | |
| 178 | 175 |
*/ |
| 179 | 176 |
protected long references = 0; |
| 180 | 177 |
|
| 178 |
/** The data represented by this record, in it's low-level (database friendly) form */ |
|
| 179 |
private final Object[] data; |
|
| 180 |
|
|
| 181 | 181 |
/** Map of properties touched, each bit corresponds with a property in the data array */ |
| 182 | 182 |
private final BitSet dirtyProps; |
| 183 | 183 |
|
| ... | ... | |
| 193 | 193 |
/** Record identifier (lock variant, which uses table name) for this record (created lazily) */ |
| 194 | 194 |
private RecordIdentifier<String> recid = null; |
| 195 | 195 |
|
| 196 |
/** Hydrator used to extract data from a result set positioned on a particular target row */ |
|
| 197 |
private Hydrator hydrator = null; |
|
| 198 |
|
|
| 199 |
/** Map of "live" properties (read or assigned), each bit corresponds with a property in the data array */ |
|
| 200 |
// TODO: resolve redundancy with readFields |
|
| 201 |
private BitSet liveProps; |
|
| 202 |
|
|
| 203 |
/** The number of bits set in {@link #liveProps} */
|
|
| 204 |
private int livePropsCardinality; |
|
| 205 | ||
| 206 |
/** |
|
| 207 |
* Process-wide fast-path gate for the runtime-feedback hydration probe. {@code true} while the
|
|
| 208 |
* feature is enabled and at least one {@link FieldUsageProfile} is still UNKNOWN. When {@code
|
|
| 209 |
* false}, {@link #getDatum} pays only this single volatile read per field access and never touches
|
|
| 210 |
* the per-record probe state, so steady-state hydration cost is unchanged. Maintained by |
|
| 211 |
* {@link FieldUsageProfile} as profiles are created and as verdicts lock in.
|
|
| 212 |
*/ |
|
| 213 |
static volatile boolean PROBING_ACTIVE = false; |
|
| 214 | ||
| 215 |
/** |
|
| 216 |
* The field-usage profile this record is probing for, or {@code null} if not probing. Distinct
|
|
| 217 |
* from {@link #hydrator}, so it survives the hydrator being nulled out in {@link #getDatum}.
|
|
| 218 |
*/ |
|
| 219 |
private FieldUsageProfile usageProfile; |
|
| 220 | ||
| 221 |
/** |
|
| 222 |
* While probing, the set of field offsets read via {@link #getDatum} during the current buffer
|
|
| 223 |
* visit; {@code null} except while a probe is armed (see {@link #armUsageProbe}).
|
|
| 224 |
*/ |
|
| 225 |
private BitSet accessed; |
|
| 226 | ||
| 196 | 227 |
/** The currently active buffer when state is changing */ |
| 197 | 228 |
private RecordBuffer activeBuffer = null; |
| 198 | 229 |
|
| ... | ... | |
| 298 | 329 |
} |
| 299 | 330 |
|
| 300 | 331 |
// TODO: LOB data is mutable, needs to be duplicated explicitly |
| 301 |
boolean change = to.data[i] != from.data[i]; |
|
| 332 |
Object toDatum = to.getDatum(i); |
|
| 333 |
Object fromDatum = from.getDatum(i); |
|
| 334 |
boolean change = toDatum != fromDatum; |
|
| 335 | ||
| 302 | 336 |
if (change) |
| 303 | 337 |
{
|
| 304 |
to.data[i] = from.data[i];
|
|
| 338 |
to.setSimpleDatum(i, fromDatum);
|
|
| 305 | 339 |
|
| 306 | 340 |
// update the null property bitset and mark the property as changed to be able to save it |
| 307 |
to.nullProps.set(i, to.data[i] == null);
|
|
| 341 |
to.nullProps.set(i, fromDatum == null);
|
|
| 308 | 342 |
to.dirtyProps.set(i); |
| 309 | 343 |
} |
| 310 | 344 |
} |
| ... | ... | |
| 488 | 522 |
public void copy(BaseRecord from) |
| 489 | 523 |
{
|
| 490 | 524 |
// TODO: BLOB data is mutable, needs to be duplicated explicitly |
| 491 |
int len = this.data.length;
|
|
| 525 |
int len = this.getLength();
|
|
| 492 | 526 |
this.id = from.id; |
| 493 |
System.arraycopy(from.data, 0, this.data, 0, len);
|
|
| 527 |
copyArray(from.getData());
|
|
| 494 | 528 |
dirtyProps.set(0, len); // TODO: nullProps? |
| 529 |
|
|
| 530 |
hydrator = from.hydrator; |
|
| 531 |
liveProps = from.liveProps != null ? (BitSet) from.liveProps.clone() : null; |
|
| 532 |
livePropsCardinality = from.livePropsCardinality; |
|
| 533 |
} |
|
| 534 |
|
|
| 535 |
/** |
|
| 536 |
* Check if two {@link BaseRecord} objects are equal from the point of view of the {@code data} array.
|
|
| 537 |
* <p> |
|
| 538 |
* <strong>To be noted</strong>, that this method accesses the {@code data} arrays directly without using
|
|
| 539 |
* any getter. This provides faster access, however should be changed if not all fields are hydrated and |
|
| 540 |
* access is mandatory from a getter. |
|
| 541 |
* |
|
| 542 |
* @param otherRecord |
|
| 543 |
* The instance to compare against. |
|
| 544 |
* |
|
| 545 |
* @return True if the {@code data} arrays are equal, false - otherwise
|
|
| 546 |
*/ |
|
| 547 |
public boolean equalData(BaseRecord otherRecord) |
|
| 548 |
{
|
|
| 549 |
if (this.getLength() != otherRecord.getLength()) |
|
| 550 |
{
|
|
| 551 |
return false; |
|
| 552 |
} |
|
| 553 |
for (int i = 0; i < this.getLength(); i++) |
|
| 554 |
{
|
|
| 555 |
if (this.areAllPropertiesLive() && otherRecord.areAllPropertiesLive()) |
|
| 556 |
{
|
|
| 557 |
return Arrays.equals(this.getData(), otherRecord.getData()); |
|
| 558 |
} |
|
| 559 |
} |
|
| 560 |
|
|
| 561 |
for (int i = 0; i < this.getLength(); i++) |
|
| 562 |
{
|
|
| 563 |
if (!Objects.equals(getDatum(i), otherRecord.getDatum(i))) |
|
| 564 |
{
|
|
| 565 |
return false; |
|
| 566 |
} |
|
| 567 |
} |
|
| 568 |
|
|
| 569 |
return true; |
|
| 570 |
} |
|
| 571 |
|
|
| 572 |
|
|
| 573 |
/** |
|
| 574 |
* Copy an array of objects into data. |
|
| 575 |
* |
|
| 576 |
* @param from |
|
| 577 |
* The source to copy from. |
|
| 578 |
*/ |
|
| 579 |
public void copyArray(Object[] from) |
|
| 580 |
{
|
|
| 581 |
System.arraycopy(from, 0, data, 0, getLength()); |
|
| 495 | 582 |
} |
| 496 | 583 |
|
| 497 | 584 |
/** |
| ... | ... | |
| 695 | 782 |
// previous state and roll this back as well |
| 696 | 783 |
for (int i = activeOffsets.nextSetBit(0); i != -1; i = activeOffsets.nextSetBit(i + 1)) |
| 697 | 784 |
{
|
| 698 |
data[i] = activePropPreviousValues[i];
|
|
| 785 |
setSimpleDatum(i, activePropPreviousValues[i]);
|
|
| 699 | 786 |
} |
| 700 | 787 |
} |
| 701 | 788 |
|
| ... | ... | |
| 728 | 815 |
buf.append(':').append(primaryKey()).append(sep)
|
| 729 | 816 |
.append(state).append(sep) |
| 730 | 817 |
.append("references: ").append(references).append(sep)
|
| 818 |
.append("read: ").append(readFields).append(sep)
|
|
| 819 |
.append("live: ").append(liveProps).append(sep)
|
|
| 731 | 820 |
.append("dirty: ").append(dirtyProps).append(sep)
|
| 732 | 821 |
.append("unvalidated: ").append(unvalidated);
|
| 733 | 822 |
|
| 734 | 823 |
if (includeData) |
| 735 | 824 |
{
|
| 736 |
int len = data.length;
|
|
| 825 |
int len = getLength();
|
|
| 737 | 826 |
buf.append(sep).append("data: {");
|
| 738 | 827 |
for (int i = 0; i < len; i++) |
| 739 | 828 |
{
|
| ... | ... | |
| 742 | 831 |
buf.append(", ");
|
| 743 | 832 |
} |
| 744 | 833 |
|
| 745 |
buf.append(data[i]);
|
|
| 834 |
buf.append(getSimpleDatum(i));
|
|
| 746 | 835 |
} |
| 747 | 836 |
|
| 748 | 837 |
buf.append('}');
|
| ... | ... | |
| 772 | 861 |
{
|
| 773 | 862 |
Objects.requireNonNull(pm); |
| 774 | 863 |
|
| 775 |
return data;
|
|
| 864 |
return getData();
|
|
| 776 | 865 |
} |
| 777 | 866 |
|
| 778 | 867 |
/** |
| ... | ... | |
| 791 | 880 |
public Object[] getData(RecordBuffer rb) |
| 792 | 881 |
{
|
| 793 | 882 |
Objects.requireNonNull(rb); |
| 794 |
|
|
| 883 | ||
| 884 |
return getData(); |
|
| 885 |
} |
|
| 886 |
|
|
| 887 |
/** |
|
| 888 |
* Get the data property. |
|
| 889 |
* |
|
| 890 |
* @return The value of the data property. |
|
| 891 |
*/ |
|
| 892 |
public Object[] getData() |
|
| 893 |
{
|
|
| 795 | 894 |
return data; |
| 796 | 895 |
} |
| 896 |
|
|
| 897 |
/** |
|
| 898 |
* Get the size of the data property. |
|
| 899 |
* |
|
| 900 |
* @return Size of the data property. |
|
| 901 |
*/ |
|
| 902 |
public int getLength() |
|
| 903 |
{
|
|
| 904 |
return data.length; |
|
| 905 |
} |
|
| 797 | 906 |
|
| 798 | 907 |
/** |
| 799 | 908 |
* Indicate whether any of this record's validatable properties are dirty and have not been validated. |
| ... | ... | |
| 860 | 969 |
public Object[] getActiveUpdateDiff(int ormIndex, int extentIndex) |
| 861 | 970 |
{
|
| 862 | 971 |
int actualIndex = ormIndex + extentIndex; |
| 863 |
if (dirtyProps.get(actualIndex) && !Objects.equals(activePropPreviousValues[actualIndex], |
|
| 864 |
data[actualIndex])) |
|
| 972 |
Object activeOffsetDatum = getDatum(actualIndex); |
|
| 973 |
|
|
| 974 |
if (dirtyProps.get(actualIndex) && |
|
| 975 |
!Objects.equals(activePropPreviousValues[actualIndex], activeOffsetDatum)) |
|
| 865 | 976 |
{
|
| 866 | 977 |
Object[] res = new Object[2]; |
| 867 | 978 |
res[0] = activePropPreviousValues[actualIndex]; |
| 868 |
res[1] = data[actualIndex];
|
|
| 979 |
res[1] = activeOffsetDatum;
|
|
| 869 | 980 |
return res; |
| 870 | 981 |
} |
| 871 | 982 |
return null; |
| ... | ... | |
| 890 | 1001 |
public BitSet _getReadFields() |
| 891 | 1002 |
{
|
| 892 | 1003 |
return readFields; |
| 893 |
} |
|
| 894 | ||
| 1004 |
} |
|
| 1005 |
|
|
| 1006 |
/** |
|
| 1007 |
* This should be called when the record is re-attached to a new session. This way, we refresh the |
|
| 1008 |
* underlying session in hydrator (if any) to properly refresh the record when needed. If this is |
|
| 1009 |
* not used, we may face {@link LazyHydrationException} due to stale sessions in the hydrator.
|
|
| 1010 |
* |
|
| 1011 |
* First time a hydrator is created, it will take the session from the generator result-set, thus, |
|
| 1012 |
* there is no need to call this method in that case. |
|
| 1013 |
* |
|
| 1014 |
* @param session |
|
| 1015 |
* The session to which this record is re-attached. |
|
| 1016 |
*/ |
|
| 1017 |
public void refreshHydrator(Session session) |
|
| 1018 |
{
|
|
| 1019 |
if (hydrator != null) |
|
| 1020 |
{
|
|
| 1021 |
hydrator.refresh(session); |
|
| 1022 |
} |
|
| 1023 |
} |
|
| 1024 |
|
|
| 1025 |
/** |
|
| 1026 |
* Sets the bit in {@link BaseRecord#liveProps} to true, meaning that the field at that index has been
|
|
| 1027 |
* read or written. |
|
| 1028 |
* |
|
| 1029 |
* @param offset |
|
| 1030 |
* Zero-based offset of the datum in the data array, which corresponds to the index in |
|
| 1031 |
* {@link BaseRecord#liveProps}
|
|
| 1032 |
*/ |
|
| 1033 |
protected void setLivePropsAtOffset(int offset) |
|
| 1034 |
{
|
|
| 1035 |
if (liveProps != null) |
|
| 1036 |
{
|
|
| 1037 |
if (!liveProps.get(offset)) |
|
| 1038 |
{
|
|
| 1039 |
livePropsCardinality++; |
|
| 1040 |
} |
|
| 1041 |
|
|
| 1042 |
liveProps.set(offset); |
|
| 1043 |
} |
|
| 1044 |
} |
|
| 1045 |
|
|
| 1046 |
protected void setAllLiveProps() |
|
| 1047 |
{
|
|
| 1048 |
if (liveProps == null) |
|
| 1049 |
{
|
|
| 1050 |
return; |
|
| 1051 |
} |
|
| 1052 |
for (int i = 0; i < data.length; i++) |
|
| 1053 |
{
|
|
| 1054 |
liveProps.set(i); |
|
| 1055 |
} |
|
| 1056 |
} |
|
| 1057 |
|
|
| 895 | 1058 |
/** |
| 896 | 1059 |
* Set a single datum into the data array, and update the record and property state accordingly. |
| 897 | 1060 |
* |
| ... | ... | |
| 901 | 1064 |
* Value to be set into the data array. |
| 902 | 1065 |
*/ |
| 903 | 1066 |
protected void setDatum(int offset, Object datum) |
| 904 |
{
|
|
| 905 |
if (readFields != null) |
|
| 906 |
{
|
|
| 907 |
readFields.set(offset); |
|
| 908 |
} |
|
| 909 | ||
| 1067 |
{
|
|
| 910 | 1068 |
if (activeBuffer != null) |
| 911 | 1069 |
{
|
| 912 | 1070 |
activeOffsets.set(offset); |
| ... | ... | |
| 915 | 1073 |
// update the bitset of dirty (touched) properties |
| 916 | 1074 |
dirtyProps.set(offset); |
| 917 | 1075 |
|
| 1076 |
Object offsetDatum = getDatum(offset); |
|
| 1077 |
|
|
| 918 | 1078 |
// temporarily save the old value (even if it is the same as the new value) |
| 919 |
activePropPreviousValues[offset] = data[offset]; |
|
| 1079 |
activePropPreviousValues[offset] = offsetDatum; |
|
| 1080 | ||
| 1081 |
if (readFields != null) |
|
| 1082 |
{
|
|
| 1083 |
readFields.set(offset); |
|
| 1084 |
} |
|
| 920 | 1085 |
|
| 921 | 1086 |
// for BigDecimal, equals checks the scale - we need compareTo, to check if the values are really the |
| 922 | 1087 |
// same, regardless of the scale value. |
| 923 |
if (!(Objects.equals(data[offset], datum) ||
|
|
| 1088 |
if (!(Objects.equals(offsetDatum, datum) ||
|
|
| 924 | 1089 |
(datum instanceof Comparable && |
| 925 |
data[offset] != null &&
|
|
| 926 |
((Comparable) datum).compareTo(data[offset]) == 0)))
|
|
| 1090 |
offsetDatum != null &&
|
|
| 1091 |
((Comparable) datum).compareTo(offsetDatum) == 0)))
|
|
| 927 | 1092 |
{
|
| 928 | 1093 |
// ensure we have an exclusive lock on the record |
| 929 | 1094 |
if (!lockForUpdate()) |
| ... | ... | |
| 942 | 1107 |
} |
| 943 | 1108 |
|
| 944 | 1109 |
// update the value in the data array |
| 945 |
data[offset] = datum;
|
|
| 1110 |
setSimpleDatum(offset, datum);
|
|
| 946 | 1111 |
} |
| 947 | 1112 |
} |
| 948 | 1113 |
|
| ... | ... | |
| 980 | 1145 |
continue; |
| 981 | 1146 |
} |
| 982 | 1147 |
|
| 983 |
if (fromProp.getType() != toProp.getType() || |
|
| 984 |
fromProp.getExtent() != toProp.getExtent()) |
|
| 1148 |
if (fromProp.getType() != toProp.getType() || fromProp.getExtent() != toProp.getExtent()) |
|
| 985 | 1149 |
{
|
| 986 | 1150 |
return false; |
| 987 | 1151 |
} |
| ... | ... | |
| 1012 | 1176 |
|
| 1013 | 1177 |
// for BigDecimal, equals checks the scale - we need compareTo, to check if the values are really the |
| 1014 | 1178 |
// same, regardless of the scale value. |
| 1015 |
if (!(Objects.equals(data[toOffset], datum) || |
|
| 1179 |
Object offsetDatum = getDatum(toOffset); |
|
| 1180 |
if (!(Objects.equals(offsetDatum, datum) || |
|
| 1016 | 1181 |
(datum instanceof Comparable && |
| 1017 |
data[toOffset] != null &&
|
|
| 1018 |
((Comparable) datum).compareTo(data[toOffset]) == 0)))
|
|
| 1182 |
offsetDatum != null &&
|
|
| 1183 |
((Comparable) datum).compareTo(offsetDatum) == 0)))
|
|
| 1019 | 1184 |
{
|
| 1020 | 1185 |
if (updatedOffsets == null) |
| 1021 | 1186 |
{
|
| ... | ... | |
| 1056 | 1221 |
{
|
| 1057 | 1222 |
int offset = updatedOffsets.get(idx); |
| 1058 | 1223 |
Object datum = updatedDatums.get(idx); |
| 1059 |
data[offset] = datum;
|
|
| 1224 |
setSimpleDatum(offset, datum);
|
|
| 1060 | 1225 |
} |
| 1061 | 1226 |
|
| 1062 | 1227 |
return true; |
| 1063 | 1228 |
} |
| 1064 | 1229 |
|
| 1065 | 1230 |
/** |
| 1066 |
* Get the field's value on the specified offset. |
|
| 1067 |
* |
|
| 1068 |
* @param offset |
|
| 1069 |
* Zero-based offset of the datum in the data array. |
|
| 1070 |
* |
|
| 1071 |
* @return The field's value. |
|
| 1231 |
* Get the field's value at the specified offset, with {@code null} indicating unknown value.
|
|
| 1232 |
* <p> |
|
| 1233 |
* N.B.: if lazy hydration can be active for this record, callers must catch and handle the unchecked |
|
| 1234 |
* {@code LazyHydrationException}.
|
|
| 1235 |
* |
|
| 1236 |
* @param offset |
|
| 1237 |
* Zero-based offset of the datum in the data array. |
|
| 1238 |
* |
|
| 1239 |
* @return The field's value, with {@code null} indicating unknown value.
|
|
| 1240 |
* |
|
| 1241 |
* @throws LazyHydrationException |
|
| 1242 |
* if this record is enabled for lazy hydration, but the attached hydrator was no longer valid. |
|
| 1243 |
* Although this is an unchecked exception to allow the DMO to be generated with simple getter |
|
| 1244 |
* methods, callers which invoke this method when lazy hydration could be active MUST catch and |
|
| 1245 |
* handle this exception. |
|
| 1072 | 1246 |
*/ |
| 1073 | 1247 |
protected Object getDatum(int offset) |
| 1248 |
throws LazyHydrationException |
|
| 1074 | 1249 |
{
|
| 1250 |
// runtime-feedback probe: record that this field was demanded during the current visit. Placed |
|
| 1251 |
// before the hydrator block because eager records have hydrator == null and would otherwise be |
|
| 1252 |
// invisible. Gated on a single volatile read so steady state (feature off / all verdicts |
|
| 1253 |
// locked) costs nothing more. |
|
| 1254 |
if (PROBING_ACTIVE) |
|
| 1255 |
{
|
|
| 1256 |
BitSet acc = accessed; |
|
| 1257 |
if (acc != null) |
|
| 1258 |
{
|
|
| 1259 |
acc.set(offset); |
|
| 1260 |
} |
|
| 1261 |
} |
|
| 1262 | ||
| 1263 |
if (hydrator != null && liveProps != null && !liveProps.get(offset)) |
|
| 1264 |
{
|
|
| 1265 |
boolean success = hydrator.hydrate(this, offset, getHydratorRowOffset(), true); |
|
| 1266 |
if (success) |
|
| 1267 |
{
|
|
| 1268 |
countHydratedFieldsWithLZS(1); |
|
| 1269 |
} |
|
| 1270 |
if (!success) |
|
| 1271 |
{
|
|
| 1272 |
BitSet unhydratedFields = getUnhydratedFields(); |
|
| 1273 |
hydrator.refresh(this, id, unhydratedFields); |
|
| 1274 |
countHydratedFieldsWithLoader(unhydratedFields.cardinality()); |
|
| 1275 |
} |
|
| 1276 |
if (!success || livePropsCardinality == data.length) |
|
| 1277 |
{
|
|
| 1278 |
hydrator = null; |
|
| 1279 |
liveProps = null; |
|
| 1280 |
livePropsCardinality = data.length; |
|
| 1281 |
} |
|
| 1282 |
} |
|
| 1283 |
|
|
| 1075 | 1284 |
return data[offset]; |
| 1076 | 1285 |
} |
| 1286 | ||
| 1287 |
/** |
|
| 1288 |
* Switch the process-wide probing fast-path gate on or off. Called by {@link FieldUsageProfile}
|
|
| 1289 |
* as profiles are created and as their verdicts lock in. |
|
| 1290 |
* |
|
| 1291 |
* @param active |
|
| 1292 |
* The new value of {@link #PROBING_ACTIVE}.
|
|
| 1293 |
*/ |
|
| 1294 |
static void setProbingActive(boolean active) |
|
| 1295 |
{
|
|
| 1296 |
PROBING_ACTIVE = active; |
|
| 1297 |
} |
|
| 1298 | ||
| 1299 |
/** |
|
| 1300 |
* Arm the runtime-feedback hydration probe for the given profile: while the profile is still |
|
| 1301 |
* learning, this record records which fields are read (via {@link #getDatum}) and reports the
|
|
| 1302 |
* sample when the buffer leaves it (see {@link #reportDemandSample}). Hydration itself stays eager
|
|
| 1303 |
* — only the demand is observed. No-op when the profile is {@code null} or already locked.
|
|
| 1304 |
* |
|
| 1305 |
* @param profile |
|
| 1306 |
* The call-site profile to probe for, or {@code null}.
|
|
| 1307 |
*/ |
|
| 1308 |
void armUsageProbe(FieldUsageProfile profile) |
|
| 1309 |
{
|
|
| 1310 |
if (profile == null || !profile.isUnknown()) |
|
| 1311 |
{
|
|
| 1312 |
return; |
|