2137_20221114a.patch
new/src/com/goldencode/p2j/persist/AbstractQuery.java 2022-11-10 19:19:54 +0000 | ||
---|---|---|
220 | 220 |
** OM 20210917 Code maintenance. |
221 | 221 |
** OM 20220112 Added implementation of SKIP-DELETED-RECORD attribute. |
222 | 222 |
** AL2 20220412 Do proxy checks for values used as query parameters. |
223 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded |
|
224 |
** as 'partial/incomplete'. |
|
223 | 225 |
*/ |
224 | 226 | |
225 | 227 |
/* |
... | ... | |
282 | 284 |
import java.util.logging.*; |
283 | 285 |
import com.goldencode.p2j.persist.event.*; |
284 | 286 |
import com.goldencode.p2j.persist.lock.*; |
287 |
import com.goldencode.p2j.persist.orm.*; |
|
285 | 288 |
import com.goldencode.p2j.util.*; |
286 | 289 |
import com.goldencode.p2j.util.ErrorManager; |
287 | 290 | |
... | ... | |
323 | 326 |
/** Flag to indicate if this query is optimized using indexed reposition */ |
324 | 327 |
protected boolean indexedReposition = false; |
325 | 328 |
|
329 |
/** The included properties for each DMO (FIELDS option). */ |
|
330 |
protected Map<DataModelObject, Property[]> included = null; |
|
331 | ||
332 |
/** The excluded properties for each DMO (EXCEPT option). */ |
|
333 |
protected Map<DataModelObject, Property[]> excluded = null; |
|
334 |
|
|
326 | 335 |
/** Flag indicating fatal error was encountered processing query substitution parameters */ |
327 | 336 |
private boolean fatalError = false; |
328 | 337 |
|
... | ... | |
3173 | 3182 |
*/ |
3174 | 3183 |
public void include(DataModelObject dmo, String ... fields) |
3175 | 3184 |
{ |
3176 |
// UnimplementedFeature.missing("RECORD-PHRASE: FIELDS (...) option"); |
|
3177 |
// TODO: implement |
|
3185 |
RecordBuffer buffer = (RecordBuffer) ((BufferReference) dmo).buffer(); |
|
3186 |
if (buffer.isTemporary()) |
|
3187 |
{ |
|
3188 |
return; |
|
3189 |
} |
|
3190 |
|
|
3191 |
if (included == null) |
|
3192 |
{ |
|
3193 |
included = new IdentityHashMap<>(); |
|
3194 |
} |
|
3195 |
|
|
3196 |
collectFields(included, dmo, fields, buffer); |
|
3178 | 3197 |
} |
3179 | 3198 |
|
3180 | 3199 |
/** |
... | ... | |
3188 | 3207 |
*/ |
3189 | 3208 |
public void exclude(DataModelObject dmo, String ... fields) |
3190 | 3209 |
{ |
3191 |
// UnimplementedFeature.missing("RECORD-PHRASE: EXCLUDE (...) option"); |
|
3192 |
// TODO: implement |
|
3210 |
RecordBuffer buffer = (RecordBuffer) ((BufferReference) dmo).buffer(); |
|
3211 |
if (buffer.isTemporary()) |
|
3212 |
{ |
|
3213 |
return; |
|
3214 |
} |
|
3215 |
|
|
3216 |
if (excluded == null) |
|
3217 |
{ |
|
3218 |
excluded = new IdentityHashMap<>(); |
|
3219 |
} |
|
3220 |
|
|
3221 |
collectFields(excluded, dmo, fields, buffer); |
|
3193 | 3222 |
} |
3194 | 3223 |
|
3195 | 3224 |
/** |
... | ... | |
3429 | 3458 |
return new logical(false); |
3430 | 3459 |
} |
3431 | 3460 |
} |
3461 |
|
|
3462 |
/** |
|
3463 |
* Populate the map of with the ORM properties specified in the given fields. |
|
3464 |
* |
|
3465 |
* @param target |
|
3466 |
* The target map. |
|
3467 |
* @param dmo |
|
3468 |
* The DMO instance. |
|
3469 |
* @param fields |
|
3470 |
* The fields to resolve. |
|
3471 |
* @param buffer |
|
3472 |
* The buffer instance. |
|
3473 |
*/ |
|
3474 |
private void collectFields(Map<DataModelObject, Property[]> target, |
|
3475 |
DataModelObject dmo, |
|
3476 |
String[] fields, |
|
3477 |
RecordBuffer buffer) |
|
3478 |
{ |
|
3479 |
DmoMeta meta = buffer.getDmoInfo(); |
|
3480 | ||
3481 |
Property[] props = target.computeIfAbsent(dmo, (d) -> |
|
3482 |
{ |
|
3483 |
return new Property[meta.getFieldSize()]; |
|
3484 |
}); |
|
3485 |
|
|
3486 |
for (int i = 0; i < fields.length; i++) |
|
3487 |
{ |
|
3488 |
Property field = meta.getFieldInfo(fields[i]); |
|
3489 |
if (field == null) |
|
3490 |
{ |
|
3491 |
// must be a custom extent |
|
3492 |
List<Property> extFields = meta.getCustomExtentFieldInfo(fields[i]); |
|
3493 |
for (int j = 0; j < extFields.size(); j++) |
|
3494 |
{ |
|
3495 |
field = extFields.get(j); |
|
3496 |
props[field.propId - 1] = field; |
|
3497 |
} |
|
3498 |
} |
|
3499 |
else if (field.extent == 0) |
|
3500 |
{ |
|
3501 |
props[field.propId - 1] = field; |
|
3502 |
} |
|
3503 |
} |
|
3504 |
} |
|
3432 | 3505 |
} |
new/src/com/goldencode/p2j/persist/FastFindCache.java 2022-11-09 18:35:42 +0000 | ||
---|---|---|
14 | 14 |
** negative result (i.e., no record is found) do not have the negative result cached. |
15 | 15 |
** CA 20220104 Node.l3Cache must be a weak reference, otherwise once the direct cache invalidates a key, |
16 | 16 |
** the reverse lookup has no chance of being cleaned up, as this is not done explicitly. |
17 |
** CA 20221109 Partial/Incomplete records (loaded with EXCEPT/FIELDS options) can't be cached. |
|
17 | 18 |
*/ |
18 | 19 | |
19 | 20 |
/* |
... | ... | |
262 | 263 |
*/ |
263 | 264 |
public void put(Key k, Record value) |
264 | 265 |
{ |
265 |
// if no key or record represents a housekeeping (e.g., dirty database) copy, don't store it |
|
266 |
if (k == null || (value != null && value.checkState(DmoState.COPY))) |
|
266 |
// if no key or record represents a housekeeping (e.g., dirty database) copy or an incomplete record |
|
267 |
// (partial fields are populated due to EXCEPT/FIELDS options), don't store it |
|
268 |
if (k == null || |
|
269 |
(value != null && (value.checkState(DmoState.COPY) || value.checkState(DmoState.INCOMPLETE)))) |
|
267 | 270 |
{ |
268 | 271 |
return; // throw some error? |
269 | 272 |
} |
new/src/com/goldencode/p2j/persist/Persistence.java 2022-11-10 11:14:34 +0000 | ||
---|---|---|
577 | 577 |
** allows a lazy registration of scopeables, to avoid the unnecessary overhead of |
578 | 578 |
** processing all the scopeables for each and every block. |
579 | 579 |
** TJD 20220504 Upgrade do Java 11 minor changes |
580 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded |
|
581 |
** as 'partial/incomplete'. |
|
580 | 582 |
*/ |
581 | 583 | |
582 | 584 |
/* |
... | ... | |
634 | 636 | |
635 | 637 |
package com.goldencode.p2j.persist; |
636 | 638 | |
637 |
import java.lang.*; |
|
638 | 639 |
import java.sql.*; |
639 | 640 |
import java.util.*; |
641 |
import java.util.BitSet; |
|
640 | 642 |
import java.util.Date; |
641 | 643 |
import java.util.function.*; |
642 | 644 |
import java.util.logging.*; |
... | ... | |
1756 | 1758 |
// have to take another hit retrieving the data almost immediately after this method |
1757 | 1759 |
// returns, which is the common case for dynamic record retrieval. |
1758 | 1760 |
Class<? extends Record> dmoClass = buffer.getDMOImplementationClass(); |
1759 |
initialResult = result = session.get(dmoClass, id); |
|
1761 |
initialResult = result = session.get(dmoClass, id, null);
|
|
1760 | 1762 |
|
1761 | 1763 |
if (result == null) |
1762 | 1764 |
{ |
... | ... | |
1978 | 1980 |
throws PersistenceException, |
1979 | 1981 |
LockTimeoutException |
1980 | 1982 |
{ |
1983 |
return load(implClass, id, lockType, timeout, updateLock, null); |
|
1984 |
} |
|
1985 |
|
|
1986 |
/** |
|
1987 |
* Load a single record from the session-level cache or, optionally, refresh it from the |
|
1988 |
* database. The record will be locked with the specified lock type upon return from this |
|
1989 |
* method. Note that the lock refers to a runtime-level lock, not a database-level lock. |
|
1990 |
* |
|
1991 |
* @param implClass |
|
1992 |
* The DMO implementation class of record being read. |
|
1993 |
* @param id |
|
1994 |
* Primary key of the record to be loaded into the buffer. May not be {@code null}. |
|
1995 |
* @param lockType |
|
1996 |
* Type of lock the lock manager should register on the resulting record for the |
|
1997 |
* current user context. |
|
1998 |
* @param timeout |
|
1999 |
* Number of milliseconds to wait to acquire the requested lock type. Throws |
|
2000 |
* {@link LockTimeoutException} if this time elapses before the lock is acquired. |
|
2001 |
* Specify 0 to wait indefinitely for the lock. Ignored if {@code lockType} is |
|
2002 |
* {@code NONE}. |
|
2003 |
* @param updateLock |
|
2004 |
* {@code true} to update the current lock state; |
|
2005 |
* {@code false} to leave the current lock state unchanged. |
|
2006 |
* @param partialFields |
|
2007 |
* For an incomplete record, read only these fields. |
|
2008 |
* |
|
2009 |
* @return A single DMO instance loaded from the database, or {@code null} if no result was |
|
2010 |
* found. |
|
2011 |
* |
|
2012 |
* @throws LockTimeoutException |
|
2013 |
* if a record was found, but its lock could not be acquired within the positive |
|
2014 |
* {@code timeout} period specified. |
|
2015 |
* @throws LockUnavailableException |
|
2016 |
* if a record was found and a no-wait lock type was specified, but the lock could |
|
2017 |
* not be acquired immediately. |
|
2018 |
* @throws PersistenceException |
|
2019 |
* if there was an error loading the DMO from the ORM session and/or database. |
|
2020 |
*/ |
|
2021 |
public Record load(Class<? extends Record> implClass, |
|
2022 |
Long id, |
|
2023 |
LockType lockType, |
|
2024 |
long timeout, |
|
2025 |
boolean updateLock, |
|
2026 |
BitSet partialFields) |
|
2027 |
throws PersistenceException, |
|
2028 |
LockTimeoutException |
|
2029 |
{ |
|
1981 | 2030 |
boolean trace = LOG.isLoggable(Level.FINEST); |
1982 | 2031 |
boolean warn = (trace || LOG.isLoggable(Level.WARNING)); |
1983 | 2032 |
boolean trackSlow = warn && loadThreshold > 0L; |
... | ... | |
2025 | 2074 |
} |
2026 | 2075 |
|
2027 | 2076 |
Session session = local.getSession(); |
2028 |
result = session.get(implClass, id); |
|
2077 |
result = session.get(implClass, id, partialFields);
|
|
2029 | 2078 |
|
2030 | 2079 |
if (result == null) |
2031 | 2080 |
{ |
new/src/com/goldencode/p2j/persist/PreselectQuery.java 2022-11-10 15:36:23 +0000 | ||
---|---|---|
587 | 587 |
** CA 20221006 Added JMX instrumentation for query assemble. Refs #6814 |
588 | 588 |
** CA 20221013 In 'repositionByID', if the row number is after the last row in the ResultSet, |
589 | 589 |
** assume an error (query off-end) and do not try to fetch the record. |
590 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded |
|
591 |
** as 'partial/incomplete'. At this time, extent fields are always loaded. |
|
590 | 592 |
*/ |
591 | 593 | |
592 | 594 |
/* |
... | ... | |
4439 | 4441 |
|
4440 | 4442 |
boolean qualified = !isOmitAlias(); |
4441 | 4443 |
|
4444 |
RecordBuffer buffer = comp.getBuffer(); |
|
4445 | ||
4442 | 4446 |
if (qualified) |
4443 | 4447 |
{ |
4444 |
RecordBuffer buffer = comp.getBuffer(); |
|
4445 | 4448 |
buf.append(buffer.getDMOAlias()); |
4446 | 4449 |
} |
4447 | 4450 |
|
... | ... | |
4454 | 4457 |
|
4455 | 4458 |
buf.append(DatabaseManager.PRIMARY_KEY); |
4456 | 4459 |
} |
4460 |
else |
|
4461 |
{ |
|
4462 |
Property[] iprops = included == null ? null : included.get(buffer.getDMOProxy()); |
|
4463 |
Property[] xprops = excluded == null ? null : excluded.get(buffer.getDMOProxy()); |
|
4464 |
if (iprops != null || xprops != null) |
|
4465 |
{ |
|
4466 |
Property[] props = null; |
|
4467 |
|
|
4468 |
// only permanent buffers can use include/exclude |
|
4469 |
if (iprops == null) |
|
4470 |
{ |
|
4471 |
DmoMeta meta = buffer.getDmoInfo(); |
|
4472 |
|
|
4473 |
props = new Property[meta.getFieldSize()]; |
|
4474 |
|
|
4475 |
// include all fields |
|
4476 |
Iterator<Property> piter = meta.getFields(false); |
|
4477 |
while (piter.hasNext()) |
|
4478 |
{ |
|
4479 |
Property p = piter.next(); |
|
4480 |
|
|
4481 |
if (p.propId > 0) |
|
4482 |
{ |
|
4483 |
props[p.id - 1] = p; |
|
4484 |
} |
|
4485 |
} |
|
4486 |
} |
|
4487 |
else |
|
4488 |
{ |
|
4489 |
props = new Property[iprops.length]; |
|
4490 |
System.arraycopy(iprops, 0, props, 0, props.length); |
|
4491 |
} |
|
4492 |
|
|
4493 |
if (xprops != null) |
|
4494 |
{ |
|
4495 |
// exclude properties |
|
4496 |
for (int j = 0; j < xprops.length; j++) |
|
4497 |
{ |
|
4498 |
Property xprop = xprops[j]; |
|
4499 |
if (xprop != null) |
|
4500 |
{ |
|
4501 |
props[j] = null; |
|
4502 |
} |
|
4503 |
} |
|
4504 |
} |
|
4505 |
|
|
4506 |
String alias = buffer.getDMOAlias(); |
|
4507 |
buf.append('.').append(DatabaseManager.PRIMARY_KEY); |
|
4508 |
for (int j = 0; j < props.length; j++) |
|
4509 |
{ |
|
4510 |
Property p = props[j]; |
|
4511 |
// normalized extents are always loaded (can't be skipped) at this time. |
|
4512 |
if (p == null || (p.extent != 0 && !p.denormalized)) |
|
4513 |
{ |
|
4514 |
continue; |
|
4515 |
} |
|
4516 |
|
|
4517 |
buf.append(", ").append(alias).append(".").append(p.name); |
|
4518 |
} |
|
4519 |
} |
|
4520 |
} |
|
4457 | 4521 |
} |
4458 | 4522 |
|
4459 | 4523 |
buf.append(" "); |
... | ... | |
5802 | 5866 |
{ |
5803 | 5867 |
try |
5804 | 5868 |
{ |
5805 |
dmo = p.load(buffer.getDMOImplementationClass(), id, lt, timeout, updateLock); |
|
5869 |
// force the load of the entire record |
|
5870 |
dmo = p.load(buffer.getDMOImplementationClass(), id, lt, timeout, updateLock, null); |
|
5806 | 5871 |
|
5807 | 5872 |
break; |
5808 | 5873 |
} |
... | ... | |
5853 | 5918 |
} |
5854 | 5919 |
else |
5855 | 5920 |
{ |
5856 |
dmo = p.load(buffer.getDMOImplementationClass(), id, lt, 0L, updateLock); |
|
5921 |
dmo = p.load(buffer.getDMOImplementationClass(), |
|
5922 |
id, |
|
5923 |
lt, |
|
5924 |
0L, |
|
5925 |
updateLock, |
|
5926 |
// the entire record is loaded if the lock is not NONE |
|
5927 |
updateLock || dmo == null ? null : dmo._getReadFields()); |
|
5857 | 5928 |
} |
5858 | 5929 |
} |
5859 | 5930 |
|
new/src/com/goldencode/p2j/persist/Record.java 2022-11-10 06:41:22 +0000 | ||
---|---|---|
22 | 22 |
** OM 20220713 Added support for converting to/from POJOs. |
23 | 23 |
** OM 20220718 Fixed looping in getPropertyValues(). Added to the returned list the denormalized props. |
24 | 24 |
** TJD 20220504 Upgrade do Java 11 minor changes |
25 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as |
|
26 |
** 'partial/incomplete'. |
|
25 | 27 |
*/ |
26 | 28 | |
27 | 29 |
/* |
... | ... | |
143 | 145 |
*/ |
144 | 146 |
public final integer _getInteger(int offset) |
145 | 147 |
{ |
148 |
checkIncomplete(offset); |
|
149 | ||
146 | 150 |
Integer datum = (Integer) data[offset]; |
147 | 151 |
|
148 | 152 |
return datum != null ? new integer(datum) : new integer(); |
... | ... | |
250 | 254 |
*/ |
251 | 255 |
public final datetimetz _getDatetimetz(int offset) |
252 | 256 |
{ |
257 |
checkIncomplete(offset); |
|
258 | ||
253 | 259 |
OffsetDateTime datum = (OffsetDateTime) data[offset]; |
254 | 260 |
|
255 | 261 |
return datum != null ? new datetimetz(datum) : new datetimetz(); |
... | ... | |
354 | 360 |
*/ |
355 | 361 |
public final datetime _getDatetime(int offset) |
356 | 362 |
{ |
363 |
checkIncomplete(offset); |
|
364 | ||
357 | 365 |
Timestamp datum = (Timestamp) data[offset]; |
358 | 366 |
|
359 | 367 |
return datum != null ? new datetime(datum) : new datetime(); |
... | ... | |
457 | 465 |
*/ |
458 | 466 |
public final date _getDate(int offset) |
459 | 467 |
{ |
468 |
checkIncomplete(offset); |
|
469 | ||
460 | 470 |
Date datum = (Date) data[offset]; |
461 | 471 |
|
462 | 472 |
return datum != null ? new date(datum) : new date(); |
... | ... | |
560 | 570 |
*/ |
561 | 571 |
public final logical _isLogical(int offset) |
562 | 572 |
{ |
573 |
checkIncomplete(offset); |
|
574 | ||
563 | 575 |
Boolean datum = (Boolean) data[offset]; |
564 | 576 |
|
565 | 577 |
return datum != null ? new logical(datum) : new logical(); |
... | ... | |
770 | 782 |
*/ |
771 | 783 |
public final decimal _getDecimal(int offset) |
772 | 784 |
{ |
785 |
checkIncomplete(offset); |
|
786 | ||
773 | 787 |
BigDecimal datum = (BigDecimal) data[offset]; |
774 | 788 |
|
775 | 789 |
return datum != null ? new decimal(datum) : new decimal(); |
... | ... | |
886 | 900 |
*/ |
887 | 901 |
public final character _getCharacter(int offset) |
888 | 902 |
{ |
903 |
checkIncomplete(offset); |
|
904 | ||
889 | 905 |
String datum = (String) data[offset]; |
890 | 906 |
|
891 | 907 |
if (datum != null) |
... | ... | |
996 | 1012 |
*/ |
997 | 1013 |
public final rowid _getRowid(int offset) |
998 | 1014 |
{ |
1015 |
checkIncomplete(offset); |
|
1016 | ||
999 | 1017 |
Long datum = (Long) data[offset]; |
1000 | 1018 |
|
1001 | 1019 |
return datum != null ? new rowid(datum) : new rowid(); |
... | ... | |
1101 | 1119 |
*/ |
1102 | 1120 |
public final handle _getHandle(int offset) |
1103 | 1121 |
{ |
1122 |
checkIncomplete(offset); |
|
1123 | ||
1104 | 1124 |
Long datum = (Long) data[offset]; |
1105 | 1125 |
|
1106 | 1126 |
return datum != null ? handle.fromResourceId(datum, false) : new handle(); |
... | ... | |
1206 | 1226 |
*/ |
1207 | 1227 |
public final recid _getRecid(int offset) |
1208 | 1228 |
{ |
1229 |
checkIncomplete(offset); |
|
1230 | ||
1209 | 1231 |
Integer datum = (Integer) data[offset]; |
1210 | 1232 |
|
1211 | 1233 |
return datum != null ? new recid(datum) : new recid(); |
... | ... | |
1310 | 1332 |
*/ |
1311 | 1333 |
public final blob _getBlob(int offset) |
1312 | 1334 |
{ |
1335 |
checkIncomplete(offset); |
|
1336 | ||
1313 | 1337 |
byte[] datum = (byte[]) data[offset]; |
1314 | 1338 |
|
1315 | 1339 |
return datum != null ? new blob(datum) : new blob(); |
... | ... | |
1415 | 1439 |
*/ |
1416 | 1440 |
public final clob _getClob(int offset) |
1417 | 1441 |
{ |
1442 |
checkIncomplete(offset); |
|
1443 | ||
1418 | 1444 |
return new clob((String) data[offset], getCodePage(offset)); |
1419 | 1445 |
} |
1420 | 1446 |
|
... | ... | |
1516 | 1542 |
*/ |
1517 | 1543 |
public final comhandle _getComhandle(int offset) |
1518 | 1544 |
{ |
1545 |
checkIncomplete(offset); |
|
1546 | ||
1519 | 1547 |
String datum = (String) data[offset]; |
1520 | 1548 |
|
1521 | 1549 |
return datum != null ? comhandle.fromString(datum) : new comhandle(); |
... | ... | |
1619 | 1647 |
*/ |
1620 | 1648 |
public final object<?> _getObject(int offset) |
1621 | 1649 |
{ |
1650 |
checkIncomplete(offset); |
|
1651 | ||
1622 | 1652 |
Long datum = (Long) data[offset]; |
1623 | 1653 |
|
1624 | 1654 |
return datum != null ? object.fromResourceId(datum) : new object<>(); |
... | ... | |
1726 | 1756 |
*/ |
1727 | 1757 |
public final raw _getRaw(int offset) |
1728 | 1758 |
{ |
1759 |
checkIncomplete(offset); |
|
1760 | ||
1729 | 1761 |
byte[] datum = (byte[]) data[offset]; |
1730 | 1762 |
|
1731 | 1763 |
return datum != null ? new raw(datum) : raw.instantiateUnknownRaw(); |
... | ... | |
1893 | 1925 |
{ |
1894 | 1926 |
sb.append("\""); |
1895 | 1927 |
} |
1896 |
sb.append(data[i] == null ? "?" : data[i]); |
|
1928 |
|
|
1929 |
boolean incomplete = readFields != null && !readFields.get(i); |
|
1930 |
|
|
1931 |
sb.append(data[i] == null ? incomplete ? "n/a" : "?" : data[i]); |
|
1897 | 1932 |
if (isChar) |
1898 | 1933 |
{ |
1899 | 1934 |
sb.append("\""); |
new/src/com/goldencode/p2j/persist/orm/BaseRecord.java 2022-11-09 18:38:23 +0000 | ||
---|---|---|
28 | 28 |
** used in a Java REST service, which must not be associated with a session. |
29 | 29 |
** ECF 20221005 Added support for dynamic initial values. |
30 | 30 |
** TJD 20220504 Upgrade do Java 11 minor changes |
31 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as |
|
32 |
** 'partial/incomplete'. |
|
31 | 33 |
*/ |
32 | 34 | |
33 | 35 |
/* |
... | ... | |
128 | 130 |
/** The data represented by this record, in it's low-level (database friendly) form */ |
129 | 131 |
protected final Object[] data; |
130 | 132 |
|
133 |
/** For an incomplete record, this holds the fields which were read or assigned in this record. */ |
|
134 |
protected BitSet readFields = null; |
|
135 |
|
|
131 | 136 |
/** Current state of the record */ |
132 | 137 |
protected final RecordState state = new RecordState(); |
133 | 138 |
|
... | ... | |
785 | 790 |
} |
786 | 791 |
|
787 | 792 |
/** |
793 |
* Get the {@link #readFields}. |
|
794 |
* |
|
795 |
* @return The {@link #readFields}. |
|
796 |
*/ |
|
797 |
public BitSet _getReadFields() |
|
798 |
{ |
|
799 |
return readFields; |
|
800 |
} |
|
801 | ||
802 |
/** |
|
788 | 803 |
* Set a single datum into the data array, and update the record and property state accordingly. |
789 | 804 |
* |
790 | 805 |
* @param offset |
... | ... | |
794 | 809 |
*/ |
795 | 810 |
protected void setDatum(int offset, Object datum) |
796 | 811 |
{ |
812 |
if (readFields != null) |
|
813 |
{ |
|
814 |
readFields.set(offset); |
|
815 |
} |
|
816 | ||
797 | 817 |
if (activeBuffer != null) |
798 | 818 |
{ |
799 | 819 |
activeOffset = offset; |
... | ... | |
1015 | 1035 |
} |
1016 | 1036 |
|
1017 | 1037 |
/** |
1038 |
* Check if for an incomplete record, the specified field was read or assigned. |
|
1039 |
* |
|
1040 |
* @param index |
|
1041 |
* The field index. |
|
1042 |
*/ |
|
1043 |
protected void checkIncomplete(int index) |
|
1044 |
{ |
|
1045 |
if (checkState(DmoState.INCOMPLETE) && !readFields.get(index)) |
|
1046 |
{ |
|
1047 |
RecordMeta meta = _recordMeta(); |
|
1048 |
String tname = meta.legacyName; |
|
1049 |
String fname = meta.getPropertyMeta(false)[index].getLegacyName(); |
|
1050 |
String msg = "Field " + fname + " from " + tname + " record (recid " + primaryKey() + |
|
1051 |
") was missing from FIELDS phrase"; |
|
1052 |
|
|
1053 |
ErrorManager.recordOrThrowError(8826, msg, false); |
|
1054 |
} |
|
1055 |
} |
|
1056 | ||
1057 |
/** |
|
1058 |
* Mark this record as incomplete. |
|
1059 |
* |
|
1060 |
* @param session |
|
1061 |
* Database session (used for UNDO purposes). |
|
1062 |
*/ |
|
1063 |
void markIncomplete(Session session) |
|
1064 |
{ |
|
1065 |
updateState(session, DmoState.INCOMPLETE, true); |
|
1066 |
readFields = new BitSet(data.length); |
|
1067 |
} |
|
1068 | ||
1069 |
/** |
|
1018 | 1070 |
* An index was updated. |
1019 | 1071 |
* |
1020 | 1072 |
* @param idxUid |
... | ... | |
1225 | 1277 |
final int readProperty(int propOffset, ResultSet rs, int rsOffset) |
1226 | 1278 |
throws PersistenceException |
1227 | 1279 |
{ |
1280 |
return readProperty(propOffset, rs, rsOffset, false); |
|
1281 |
} |
|
1282 |
|
|
1283 |
/** |
|
1284 |
* Read a property directly from a {@code ResultSet}. |
|
1285 |
* |
|
1286 |
* @param propOffset |
|
1287 |
* The index of the property to be read. |
|
1288 |
* @param rs |
|
1289 |
* The result set to read from. |
|
1290 |
* @param rsOffset |
|
1291 |
* The index in {@code ResultSet} to read from. If the property is a multiple column |
|
1292 |
* then more than one column will ve read from {@code ResultSet}. |
|
1293 |
* @param skip |
|
1294 |
* Flag indicating if this property will not be read from the result-set. |
|
1295 |
* |
|
1296 |
* @return The number of columns actually read from the {@code ResultSet}. |
|
1297 |
*/ |
|
1298 |
final int readProperty(int propOffset, ResultSet rs, int rsOffset, boolean skip) |
|
1299 |
throws PersistenceException |
|
1300 |
{ |
|
1228 | 1301 |
PropertyMeta pm = _recordMeta().getPropertyMeta(false)[propOffset]; |
1229 | 1302 |
PropertyReader propertyReader = pm.getDataHandler(); |
1303 |
if (skip) |
|
1304 |
{ |
|
1305 |
return propertyReader.propertySize(); |
|
1306 |
} |
|
1230 | 1307 |
|
1231 | 1308 |
try |
1232 | 1309 |
{ |
1310 |
if (readFields != null) |
|
1311 |
{ |
|
1312 |
readFields.set(propOffset); |
|
1313 |
} |
|
1314 |
|
|
1233 | 1315 |
return propertyReader.readProperty(rs, rsOffset, data, propOffset); |
1234 | 1316 |
} |
1235 | 1317 |
catch (java.sql.SQLException sqle) |
new/src/com/goldencode/p2j/persist/orm/DmoMeta.java 2022-11-14 14:47:24 +0000 | ||
---|---|---|
37 | 37 |
** DmoMeta to avoid duplicating their collection and storage. |
38 | 38 |
** OM 20220727 FieldId and PropertyId are different for denormalized extent fields. Renamed some fields |
39 | 39 |
** to a better suited name. |
40 |
** TJD 20220504 Upgrade do Java 11 minor changes |
|
40 |
** TJD 20220504 Upgrade do Java 11 minor changes. |
|
41 |
** CA 20221114 Added API to get the denormalized properties for a DMO extent property name. |
|
41 | 42 |
*/ |
42 | 43 | |
43 | 44 |
/* |
... | ... | |
536 | 537 |
} |
537 | 538 |
|
538 | 539 |
/** |
540 |
* Get the list of denormalized properties associated with this extent DMO property. |
|
541 |
* |
|
542 |
* @param propName |
|
543 |
* The field's property name. |
|
544 |
* |
|
545 |
* @return The list of {@link #customExtents denormalized} properties, or <code>null</code> if this is |
|
546 |
* not a denormalized or not a extent property. |
|
547 |
*/ |
|
548 |
public List<Property> getCustomExtentFieldInfo(String propName) |
|
549 |
{ |
|
550 |
return customExtents.get(propName); |
|
551 |
} |
|
552 |
|
|
553 |
/** |
|
554 |
* Get the number of properties in this DMO. |
|
555 |
* |
|
556 |
* @return The size of {@link #propsByName}. |
|
557 |
*/ |
|
558 |
public int getFieldSize() |
|
559 |
{ |
|
560 |
return propsByName.size(); |
|
561 |
} |
|
562 |
|
|
563 |
/** |
|
539 | 564 |
* Looks up a {@code Property} by its legacy field name.<p> |
540 | 565 |
* <b>Note:</b><br> |
541 | 566 |
* This method is rather slow. Do not use it unless you have no other solution.<br> |
new/src/com/goldencode/p2j/persist/orm/DmoState.java 2022-11-09 18:38:44 +0000 | ||
---|---|---|
2 | 2 |
** Module : DmoState.java |
3 | 3 |
** Abstract : DMO states which govern persistence behavior. |
4 | 4 |
** |
5 |
** Copyright (c) 2020-2021, Golden Code Development Corporation.
|
|
5 |
** Copyright (c) 2020-2022, Golden Code Development Corporation.
|
|
6 | 6 |
** |
7 | 7 |
** -#- -I- --Date-- ---------------------------------------Description--------------------------------------- |
8 | 8 |
** 001 ECF 20200315 First revision. |
9 | 9 |
** 002 ECF 20210519 Added TRACKED state. |
10 | 10 |
** ECF 20210923 Added DELETING state. |
11 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as |
|
12 |
** 'partial/incomplete'. |
|
11 | 13 |
*/ |
12 | 14 | |
13 | 15 |
/* |
... | ... | |
110 | 112 |
|
111 | 113 |
/** DMO currently is being tracked for possible UNDO processing */ |
112 | 114 |
public static final int TRACKED = 0x0400; |
115 |
|
|
116 |
/** DMO is read incomplete (due to EXCEPT/FIELDS options) and can't be cached. */ |
|
117 |
public static final int INCOMPLETE = 0x0800; |
|
113 | 118 |
} |
new/src/com/goldencode/p2j/persist/orm/Loader.java 2022-11-14 15:28:02 +0000 | ||
---|---|---|
11 | 11 |
** (Temporary?) set the properties to be accessed with old-fashioned accessors. |
12 | 12 |
** CA 20201005 Fixed the sort order when loading a child extent table. |
13 | 13 |
** TJD 20220504 Upgrade do Java 11 minor changes |
14 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as |
|
15 |
** 'partial/incomplete'. Extent fields are always loaded at this time. |
|
14 | 16 |
*/ |
15 | 17 | |
16 | 18 |
/* |
... | ... | |
69 | 71 |
package com.goldencode.p2j.persist.orm; |
70 | 72 | |
71 | 73 |
import com.goldencode.p2j.persist.*; |
74 |
import com.goldencode.p2j.util.*; |
|
75 | ||
72 | 76 |
import java.sql.*; |
73 | 77 |
import java.util.*; |
74 | 78 | |
... | ... | |
312 | 316 |
} |
313 | 317 |
|
314 | 318 |
/** |
319 |
* Compose the index where each group of normalized extent properties start in the DMO properties array. |
|
320 |
* |
|
321 |
* @param allPropMeta |
|
322 |
* Array of {@link PropertyMeta} objects in their natural order. |
|
323 |
* |
|
324 |
* @return An integer array with the start index of each group of normalized extent properties. The |
|
325 |
* first element is always zero. |
|
326 |
*/ |
|
327 |
static int[] composeLoadStatementsIndexes(PropertyMeta[] allPropMeta) |
|
328 |
{ |
|
329 |
List<Integer> indexes = new ArrayList<>(); |
|
330 |
|
|
331 |
int i = 0; |
|
332 |
int len = allPropMeta.length; |
|
333 |
int seriesSize = 0; |
|
334 |
|
|
335 |
// primary record (if it contains scalar data) |
|
336 |
if (len > 0 && allPropMeta[0].getExtent() == 0) |
|
337 |
{ |
|
338 |
// first one is always zero for primary record |
|
339 |
indexes.add(i); |
|
340 |
seriesSize = allPropMeta[0].seriesSize; |
|
341 |
i = seriesSize; |
|
342 |
} |
|
343 |
|
|
344 |
// secondary records (if any) |
|
345 |
while (i < len) |
|
346 |
{ |
|
347 |
indexes.add(i); |
|
348 | ||
349 |
PropertyMeta propMeta = allPropMeta[i]; |
|
350 |
int extent = propMeta.getExtent(); |
|
351 |
seriesSize = propMeta.seriesSize; |
|
352 |
|
|
353 |
for (int j = 0; j < seriesSize; j++) |
|
354 |
{ |
|
355 |
propMeta = allPropMeta[i]; |
|
356 |
i += extent; |
|
357 |
} |
|
358 |
} |
|
359 |
|
|
360 |
return Utils.integerCollectionToPrimitive(indexes); |
|
361 |
} |
|
362 | ||
363 |
/** |
|
315 | 364 |
* Load a single record from the database, given its DMO class and primary key. The primary |
316 | 365 |
* key is assumed to represent a valid identifier; if not, {@code null} will be returned. |
317 | 366 |
* <p> |
... | ... | |
323 | 372 |
* DMO implementation class. |
324 | 373 |
* @param id |
325 | 374 |
* Surrogate primary key. |
326 |
* |
|
375 |
*
|
|
327 | 376 |
* @return An instance of {@code dmoClass}, loaded with data from the database, or {@code |
328 | 377 |
* null} if the record was not found. The DMO is instantiated either way, but it will |
329 | 378 |
* be discarded if the record is not found. |
... | ... | |
334 | 383 |
<T extends BaseRecord> T load(Class<T> dmoClass, Long id) |
335 | 384 |
throws PersistenceException |
336 | 385 |
{ |
386 |
return load(dmoClass, id, null); |
|
387 |
} |
|
388 |
|
|
389 |
/** |
|
390 |
* Load a single record from the database, given its DMO class and primary key. The primary |
|
391 |
* key is assumed to represent a valid identifier; if not, {@code null} will be returned. |
|
392 |
* <p> |
|
393 |
* TODO: a SQL statement is prepared and closed for each load statement executed by this |
|
394 |
* method. We rely on statement caching at the connection pool level to make this performant, |
|
395 |
* but perhaps there is some predictive analysis we can do to make this more efficient. |
|
396 |
* |
|
397 |
* @param dmoClass |
|
398 |
* DMO implementation class. |
|
399 |
* @param id |
|
400 |
* Surrogate primary key. |
|
401 |
* @param partialFields |
|
402 |
* For an incomplete record, read only these fields. |
|
403 |
* |
|
404 |
* @return An instance of {@code dmoClass}, loaded with data from the database, or {@code |
|
405 |
* null} if the record was not found. The DMO is instantiated either way, but it will |
|
406 |
* be discarded if the record is not found. |
|
407 |
* |
|
408 |
* @throws PersistenceException |
|
409 |
* if an error occurred accessing the database. |
|
410 |
*/ |
|
411 |
<T extends BaseRecord> T load(Class<T> dmoClass, Long id, BitSet partialFields) |
|
412 |
throws PersistenceException |
|
413 |
{ |
|
337 | 414 |
try |
338 | 415 |
{ |
339 | 416 |
T dmo = dmoClass.getDeclaredConstructor().newInstance(); |
340 | 417 |
dmo.primaryKey(id); |
341 | 418 |
|
342 |
return load(dmo, id); |
|
419 |
return load(dmo, id, partialFields);
|
|
343 | 420 |
} |
344 | 421 |
catch (ReflectiveOperationException exc) |
345 | 422 |
{ |
... | ... | |
360 | 437 |
* DMO implementation class. |
361 | 438 |
* @param id |
362 | 439 |
* Surrogate primary key. |
440 |
* @param partialFields |
|
441 |
* For an incomplete record, read only these fields. |
|
363 | 442 |
* |
364 | 443 |
* @return An instance of {@code dmoClass}, loaded with data from the database, or {@code |
365 | 444 |
* null} if the record was not found. The DMO is instantiated either way, but it will |
... | ... | |
368 | 447 |
* @throws PersistenceException |
369 | 448 |
* if an error occurred accessing the database. |
370 | 449 |
*/ |
371 |
<T extends BaseRecord> T load(T dmo, Long id) |
|
450 |
<T extends BaseRecord> T load(T dmo, Long id, BitSet partialFields)
|
|
372 | 451 |
throws PersistenceException |
373 | 452 |
{ |
374 | 453 |
try |
... | ... | |
380 | 459 |
boolean hasScalarData = props.length > 0 && props[0].getExtent() == 0; |
381 | 460 |
|
382 | 461 |
int start = 0; |
462 |
if (partialFields != null) |
|
463 |
{ |
|
464 |
dmo.markIncomplete(session); |
|
465 |
} |
|
383 | 466 |
|
384 | 467 |
for (String sql : loadSql) |
385 | 468 |
{ |
... | ... | |
404 | 487 |
} |
405 | 488 |
|
406 | 489 |
// the first query is always on the primary table, if the DMO has scalar data |
407 |
start = readScalarData(dmo, rs, 0, props); |
|
490 |
start = readScalarData(dmo, rs, 0, props, partialFields);
|
|
408 | 491 |
} |
409 | 492 |
else |
410 | 493 |
{ |
... | ... | |
448 | 531 |
* use 0. If the record data is prepended by a single column (Ex: primaryKey), pass 1. |
449 | 532 |
* @param props |
450 | 533 |
* Array of property metadata objects. |
534 |
* @param partialFields |
|
535 |
* For an incomplete record, read only these fields. |
|
451 | 536 |
* |
452 | 537 |
* @return Index of next slot in the data array to be filled (if any) after this series of |
453 | 538 |
* scalar data has been read, or -1 to indicate the result set was empty (record |
... | ... | |
459 | 544 |
private static int readScalarData(BaseRecord r, |
460 | 545 |
ResultSet rs, |
461 | 546 |
int rowOffset, |
462 |
PropertyMeta[] props) |
|
547 |
PropertyMeta[] props, |
|
548 |
BitSet partialFields) |
|
463 | 549 |
throws SQLException, PersistenceException |
464 | 550 |
{ |
465 | 551 |
// the current row is expected to be already selected, since the filter is the primary |
... | ... | |
480 | 566 |
break; |
481 | 567 |
} |
482 | 568 |
|
483 |
int colCnt = r.readProperty(pm.offset, rs, pm.columnIndex + rowOffset); |
|
569 |
boolean skip = partialFields != null && !partialFields.get(pm.offset); |
|
570 |
int colCnt = r.readProperty(pm.offset, rs, pm.columnIndex + rowOffset, skip); |
|
484 | 571 |
|
485 | 572 |
if (colCnt != pm.columnCount) |
486 | 573 |
{ |
new/src/com/goldencode/p2j/persist/orm/Property.java 2022-11-14 15:22:23 +0000 | ||
---|---|---|
10 | 10 |
** OM 20201218 Fixed implementation for error and rejected attributes/hidden fields. |
11 | 11 |
** CA 20210831 Track the getter method for TempRecord reserved properties. |
12 | 12 |
** OM 20220722 Added property identifier. |
13 |
** CA 20221114 Added flag to mark a denormalized extent property. |
|
13 | 14 |
*/ |
14 | 15 | |
15 | 16 |
/* |
... | ... | |
127 | 128 |
*/ |
128 | 129 |
public final int extent; |
129 | 130 |
|
131 |
/** Flag indicating if this extent property has been denormalized. */ |
|
132 |
public final boolean denormalized; |
|
133 |
|
|
130 | 134 |
/** |
131 | 135 |
* One-based index in the legacy extent field associated with a DMO property. Will be set only |
132 | 136 |
* for field with custom extents. |
... | ... | |
255 | 259 |
Property(com.goldencode.p2j.persist.annotation.Property source, Method annotatedMethod) |
256 | 260 |
{ |
257 | 261 |
this.id = source.id(); |
258 |
this.propId = (source.propertyId() == Integer.MIN_VALUE) ? this.id : source.propertyId(); |
|
262 |
this.denormalized = source.propertyId() != Integer.MIN_VALUE; |
|
263 |
this.propId = !this.denormalized ? this.id : source.propertyId(); |
|
259 | 264 |
this.name = source.name().intern(); |
260 | 265 |
this.legacy = source.legacy().intern(); |
261 | 266 |
this.column = source.column().intern(); |
... | ... | |
333 | 338 |
{ |
334 | 339 |
this.id = this.propId = id; // this constructor is only invoked for reserved properties which have |
335 | 340 |
// negative and equal values for both [id] and [propId] |
341 |
this.denormalized = false; |
|
336 | 342 |
this.name = name.intern(); |
337 | 343 |
this.legacy = (legacy == null) ? null : legacy.intern(); |
338 | 344 |
this.column = column.intern(); |
new/src/com/goldencode/p2j/persist/orm/PropertyReader.java 2022-11-09 18:39:36 +0000 | ||
---|---|---|
3 | 3 |
** Abstract : Functional interface for reading a (possibly multiple column) property from a SQL |
4 | 4 |
** result set. |
5 | 5 |
** |
6 |
** Copyright (c) 2020, Golden Code Development Corporation. |
|
6 |
** Copyright (c) 2020-2022, Golden Code Development Corporation.
|
|
7 | 7 |
** |
8 | 8 |
** -#- -I- --Date-- ---------------------------------Description--------------------------------- |
9 | 9 |
** 001 OM 20200212 Created initial version. |
10 |
** 002 CA 20221109 Added 'propertySize' method which returns the number of columns in the result-set for this |
|
11 |
** property type. |
|
10 | 12 |
*/ |
11 | 13 | |
12 | 14 |
/* |
... | ... | |
93 | 95 |
*/ |
94 | 96 |
public int readProperty(ResultSet rs, int rsOffset, Object[] data, int propIndex) |
95 | 97 |
throws SQLException; |
98 |
|
|
99 |
/** |
|
100 |
* Get the size (number of columns in the result-set) required by this property. |
|
101 |
* |
|
102 |
* @return Defaults to 1. |
|
103 |
*/ |
|
104 |
public default int propertySize() |
|
105 |
{ |
|
106 |
return 1; |
|
107 |
} |
|
96 | 108 |
} |
new/src/com/goldencode/p2j/persist/orm/RecordMeta.java 2022-11-14 15:26:55 +0000 | ||
---|---|---|
20 | 20 |
** OM 20220727 FieldId and PropertyId are different for denormalized extent fields. Both are computed |
21 | 21 |
** at conversion time. |
22 | 22 |
** ECF 20221005 Added support for dynamic initial values. |
23 |
** CA 20221114 Added a helper array to keep the index of the normalized extent properties in the DMO |
|
24 |
** 'props' array. |
|
23 | 25 |
*/ |
24 | 26 | |
25 | 27 |
/* |
... | ... | |
135 | 137 |
|
136 | 138 |
/** SQL query statement(s) to access data needed to load a single DMO instance */ |
137 | 139 |
final String[] loadSql; |
140 | ||
141 |
/** |
|
142 |
* The index in {@link #props} array where each {@link #loadSql} statement has the properties |
|
143 |
* (with the same extent) stored. The value of <code>loadSqlIndexes[0]</code> is always zero, as the |
|
144 |
* {@link #loadSql}'s first element is the scalar properties SELET. |
|
145 |
*/ |
|
146 |
final int[] loadSqlIndexes; |
|
138 | 147 |
|
139 | 148 |
/** SQL insert statement(s) which insert a single DMO's data into the database */ |
140 | 149 |
final String[] insertSql; |
... | ... | |
232 | 241 |
props = RecordMeta.readPropertyMeta(dmoMeta); |
233 | 242 |
tables = RecordMeta.tableNames(dmoMeta, props); |
234 | 243 |
loadSql = Loader.composeLoadStatements(tables[0], props, temporary); |
244 |
loadSqlIndexes = Loader.composeLoadStatementsIndexes(props); |
|
235 | 245 |
insertSql = Persister.composeInsertStatements(tables[0], props, temporary); |
236 | 246 |
deleteSql = Persister.composeDeleteStatements(tables); |
237 | 247 |
|
new/src/com/goldencode/p2j/persist/orm/RecordState.java 2022-11-09 18:39:43 +0000 | ||
---|---|---|
2 | 2 |
** Module : RecordState.java |
3 | 3 |
** Abstract : Current state of a database record in an application. |
4 | 4 |
** |
5 |
** Copyright (c) 2019-2021, Golden Code Development Corporation.
|
|
5 |
** Copyright (c) 2019-2022, Golden Code Development Corporation.
|
|
6 | 6 |
** |
7 | 7 |
** -#- -I- --Date-- ---------------------------------------Description--------------------------------------- |
8 | 8 |
** 001 ECF 20191001 First revision. Object which tracks DMO state as changes are applied to it. |
9 | 9 |
** 002 ECF 20210519 Added TRACKED state to toString. |
10 | 10 |
** ECF 20210924 Added DELETING state to toString. |
11 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as |
|
12 |
** 'partial/incomplete'. |
|
11 | 13 |
*/ |
12 | 14 | |
13 | 15 |
/* |
... | ... | |
144 | 146 |
} |
145 | 147 |
if ((state & DmoState.TRACKED) == DmoState.TRACKED) |
146 | 148 |
{ |
147 |
sb.append("TRACKED"); |
|
149 |
sb.append("TRACKED "); |
|
150 |
} |
|
151 |
if ((state & DmoState.INCOMPLETE) == DmoState.INCOMPLETE) |
|
152 |
{ |
|
153 |
sb.append("INCOMPLETE"); |
|
148 | 154 |
} |
149 | 155 |
} |
150 | 156 |
sb.append("}"); |
new/src/com/goldencode/p2j/persist/orm/SQLQuery.java 2022-11-10 14:50:30 +0000 | ||
---|---|---|
33 | 33 |
** a wrapper for two separate parameters, changed hydrateRecordImpl logic to skip fields that are not |
34 | 34 |
** queried. |
35 | 35 |
** TJD 20220504 Upgrade do Java 11 minor changes |
36 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as |
|
37 |
** 'partial/incomplete'. Extent fields are always loaded at this time. |
|
36 | 38 |
*/ |
37 | 39 | |
38 | 40 |
/* |
... | ... | |
684 | 686 |
return res[0]; |
685 | 687 |
} |
686 | 688 |
|
687 |
|
|
688 | 689 |
/** |
689 | 690 |
* Hydrate a {@code Record} from a {@code ResultSet}, ie a new object that implements the DMO |
690 | 691 |
* interface and having the properties set from the current row of the record set. The method |
... | ... | |
771 | 772 |
} |
772 | 773 |
int k = 0; |
773 | 774 |
int recOffset = 0; |
774 |
boolean hasExtents = false; |
|
775 |
// TODO: EXCEPT/FIELDS for extent fields; for now, load all extents |
|
776 |
boolean incomplete = fieldEntries != allFieldEntries; |
|
777 |
boolean hasExtents = incomplete; |
|
778 |
if (incomplete) |
|
779 |
{ |
|
780 |
r.markIncomplete(session); |
|
781 |
} |
|
775 | 782 |
|
776 | 783 |
for (Map.Entry<String, Property> prop : fieldEntries) |
777 | 784 |
{ |
... | ... | |
831 | 838 |
rsOffset += r.readProperty(recOffset++, resultSet, rsOffset); |
832 | 839 |
break; |
833 | 840 |
default: |
834 |
if (fieldEntries != allFieldEntries)
|
|
841 |
if (incomplete)
|
|
835 | 842 |
{ |
836 | 843 |
recOffset = dmoInfo.recordMeta.getIndexOfProperty(key); |
837 |
} |
|
838 |
rsOffset += r.readProperty(recOffset++, resultSet, rsOffset); |
|
844 |
rsOffset += r.readProperty(recOffset, resultSet, rsOffset); |
|
845 |
} |
|
846 |
else |
|
847 |
{ |
|
848 |
rsOffset += r.readProperty(recOffset++, resultSet, rsOffset); |
|
849 |
} |
|
839 | 850 |
break; |
840 | 851 |
} |
841 | 852 |
} |
... | ... | |
845 | 856 |
if (hasExtents) |
846 | 857 |
{ |
847 | 858 |
RecordMeta recordMeta = r._recordMeta(); |
859 |
PropertyMeta[] props = recordMeta.getPropertyMeta(false); |
|
848 | 860 |
String[] loadSqls = recordMeta.loadSql; |
861 |
int[] loadSqlsIndexes = recordMeta.loadSqlIndexes; |
|
862 |
|
|
849 | 863 |
// only use the extent queries, skip the loadSqls[0] which contains the simple properties |
850 | 864 |
for (int i = 1; i < loadSqls.length; i++) // skip "plain" columns |
851 | 865 |
{ |
866 |
if (incomplete) |
|
867 |
{ |
|
868 |
// resolve the offset in the 'props' array where this sql starts. |
|
869 |
// all normalized extent fields are loaded even for incomplete records. |
|
870 |
recOffset = loadSqlsIndexes[i]; |
|
871 |
} |
|
872 | ||
852 | 873 |
ResultSet rs = null; |
853 | 874 |
try (PreparedStatement ps = conn.prepareStatement(loadSqls[i])) |
854 | 875 |
{ |
855 | 876 |
// set the [parent_id] parameter |
856 | 877 |
ps.setLong(1, r.primaryKey()); |
857 | 878 |
rs = ps.executeQuery(); |
858 |
recOffset = Loader.readExtentData(r, rs, recordMeta.getPropertyMeta(false), recOffset);
|
|
879 |
recOffset = Loader.readExtentData(r, rs, props, recOffset);
|
|
859 | 880 |
} |
860 | 881 |
finally |
861 | 882 |
{ |
new/src/com/goldencode/p2j/persist/orm/Session.java 2022-11-09 18:40:30 +0000 | ||
---|---|---|
25 | 25 |
** ECF 20211022 A cache expiration is not tracked as an undoable event. |
26 | 26 |
** ECF 20211230 Do not attempt to delete a newly created record that was never flushed to the database. |
27 | 27 |
** OM 20220628 Signature change for createSQLQuery() method. |
28 |
** CA 20221109 Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded as |
|
29 |
** 'partial/incomplete'. Partial/Incomplete records are being cached, but when they try to |
|
30 |
** be loaded with no 'partial field information', the cached record is fully refreshed from |
|
31 |
** the database, and not just returned. |
|
28 | 32 |
*/ |
29 | 33 | |
30 | 34 |
/* |
... | ... | |
450 | 454 |
public <T extends BaseRecord> T get(Class<T> dmoImplClass, Long id) |
451 | 455 |
throws PersistenceException |
452 | 456 |
{ |
453 |
return getImpl(dmoImplClass, id, new RecordIdentifier<>(dmoImplClass.getName(), id)); |
|
457 |
return getImpl(dmoImplClass, id, new RecordIdentifier<>(dmoImplClass.getName(), id), null); |
|
458 |
} |
|
459 | ||
460 |
/** |
|
461 |
* Gets a specified record of a table. To lookup the record local cache is analyzed first. If |
|
462 |
* the record is not found in cache, the backing database is queried. If the specified record |
|
463 |
* is not found in database, {@code null} is returned. If the record was fetched from the |
|
464 |
* database at this moment, it is kept in cache for further access. |
|
465 |
* |
|
466 |
* @param <T> |
|
467 |
* The type of the record. |
|
468 |
* @param dmoImplClass |
|
469 |
* The class of the entity (class that implements the table's DMO interface). |
|
470 |
* @param id |
|
471 |
* The id/rowid of the record to be returned. |
|
472 |
* @param partialFields |
|
473 |
* For an incomplete record, read only these fields. |
|
474 |
* |
|
475 |
* @return The requested record, if found in cache or database and {@code null} otherwise. |
|
476 |
* |
|
477 |
* @throws PersistenceException |
|
478 |
* In case some error occurs in the process. |
|
479 |
*/ |
|
480 |
public <T extends BaseRecord> T get(Class<T> dmoImplClass, Long id, BitSet partialFields) |
|
481 |
throws PersistenceException |
|
482 |
{ |
|
483 |
return getImpl(dmoImplClass, id, new RecordIdentifier<>(dmoImplClass.getName(), id), partialFields); |
|
454 | 484 |
} |
455 | 485 |
|
456 | 486 |
/** |
... | ... | |
473 | 503 |
public <T extends BaseRecord> T get(RecordIdentifier<String> key) |
474 | 504 |
throws PersistenceException |
475 | 505 |
{ |
476 |
return getImpl(null, key.getRecordID(), key); |
|
506 |
return getImpl(null, key.getRecordID(), key, null);
|
|
477 | 507 |
} |
478 | 508 |
|
479 | 509 |
/** |
... | ... | |
497 | 527 |
public <T extends BaseRecord> T get(String entity, Long id) |
498 | 528 |
throws PersistenceException |
499 | 529 |
{ |
500 |
return getImpl(null, id, new RecordIdentifier<>(entity, id)); |
|
530 |
return getImpl(null, id, new RecordIdentifier<>(entity, id), null);
|
|
501 | 531 |
} |
502 | 532 |
|
503 | 533 |
/** |
... | ... | |
513 | 543 |
* @param key |
514 | 544 |
* The cache key of the entity. If {@code null} the returned value is {@code null}; |
515 | 545 |
* provided. |
546 |
* @param partialFields |
|
547 |
* For an incomplete record, read only these fields. |
|
516 | 548 |
* @param <T> |
517 | 549 |
* The compile type of the record to be returned. |
518 | 550 |
* |
... | ... | |
522 | 554 |
* @throws PersistenceException |
523 | 555 |
* if there is an error getting a JDBC connection from the data source. |
524 | 556 |
*/ |
525 |
private <T extends BaseRecord> T getImpl(Class<T> dmoImplClass, Long id, RecordIdentifier<String> key) |
|
557 |
private <T extends BaseRecord> T getImpl(Class<T> dmoImplClass, |
|
558 |
Long id, |
|
559 |
RecordIdentifier<String> key, |
|
560 |
BitSet partialFields) |
|
526 | 561 |
throws PersistenceException |
527 | 562 |
{ |
528 | 563 |
// quick out |
... | ... | |
544 | 579 |
|
545 | 580 |
T dmo = getCached(key); |
546 | 581 |
|
582 |
boolean refreshIncomplete = false; |
|
547 | 583 |
if (dmo != null) |
548 | 584 |
{ |
549 | 585 |
// assert that we have the right DMO type for this primary key |
... | ... | |
557 | 593 |
throw new PersistenceException(msg); |
558 | 594 |
} |
559 | 595 |
|
560 |
return dmo; |
|
596 |
if (dmo.checkState(DmoState.INCOMPLETE) && partialFields == null) |
|
597 |
{ |
|
598 |
// we have a cached record which is incomplete, and this requires to refresh the incomplete record |
|
599 |
// from the database |
|
600 |
refreshIncomplete = true; |
|
601 |
} |
|
602 |
else |
|
603 |
{ |
|
604 |
return dmo; |
|
605 |
} |
|
561 | 606 |
} |
562 | 607 |
|
563 | 608 |
if (dmoImplClass == null) |
... | ... | |
579 | 624 |
throw new PersistenceException("Invalid entity name: " + dmoImplClass.getName()); |
580 | 625 |
} |
581 | 626 |
|
582 |
dmo = loader.load(dmoImplClass, id); |
|
627 |
T loaded = loader.load(dmoImplClass, id, partialFields); |
|
628 |
if (refreshIncomplete) |
|
629 |
{ |
|
630 |
// evict the incomplete record from the cache, will be recached if one was found |
|
631 |
evict(dmo); |
|
632 |
|
|
633 |
if (loaded == null) |
|
634 |
{ |
|
635 |
dmo = null; |
|
636 |
} |
|
637 |
else |
|
638 |
{ |
|
639 |
System.arraycopy(loaded.data, 0, dmo.data, 0, dmo.data.length); |
|
640 | ||
641 |
// set the incomplete record as complete |
|
642 |
dmo.updateState(this, DmoState.INCOMPLETE, false); |
|
643 |
dmo.readFields = null; |
|
644 |
} |
|
645 |
} |
|
646 |
else |
|
647 |
{ |
|
648 |
dmo = loaded; |
|
649 |
} |
|
583 | 650 |
|
584 | 651 |
if (dmo != null) |
585 | 652 |
{ |
... | ... | |
962 | 1029 |
// ignore the cache and go directly to underlying database |
963 | 1030 |
// use the variant of load which overwrites the DMOs current field values with data |
964 | 1031 |
// fetched from the database |
965 |
T loaded = loader.load(dmo, dmo.id); |
|
1032 |
T loaded = loader.load(dmo, dmo.id, null);
|
|
966 | 1033 |
|
967 | 1034 |
RecordIdentifier<String> key = new RecordIdentifier<>(dmo.getClass().getName(), dmo.id); |
968 | 1035 |
|
new/src/com/goldencode/p2j/persist/orm/types/DatetimetzType.java 2022-11-09 18:41:02 +0000 | ||
---|---|---|
13 | 13 |
** CA 20210304 Fixed datetimetz literal parsing. |
14 | 14 |
** OM 20220218 Fixed timezone when the dtz is rehydrated. |
15 | 15 |
** ECF 20221005 Added support for dynamic initial values. |
16 |
** CA 20221109 Added 'propertySize' method which returns the number of columns in the result-set for this |
|
17 |
** property type. |
|
16 | 18 |
*/ |
17 | 19 | |
18 | 20 |
/* |
... | ... | |
180 | 182 |
} |
181 | 183 |
|
182 | 184 |
/** |
185 |
* Get the number of columns read from the result-set. |
|
186 |
* |
|
187 |
* @return always 2. |
|
188 |
*/ |
|
189 |
@Override |
|
190 |
public int propertySize() |
|
191 |
{ |
|
192 |
return 2; |
|
193 |
} |
|
194 |
|
|
195 |
/** |
|
183 | 196 |
* Instantiate a low-level initial value suitable for storage in a {@link BaseRecord}'s data |
184 | 197 |
* array, parsed from a string representation. |
185 | 198 |
* |