Feature #7379
cache the def and bound DMO property info in TemporaryBuffer$ReferenceProxy
100%
History
#1 Updated by Constantin Asofiei 12 months ago
Created task branch 7379a from trunk rev 14584.
Rev 14585 adds a cache for the DMO property info required by TemporaryBuffer$ReferenceProxy
. Ovidiu, should we have these in a different place? Please review.
#2 Updated by Ovidiu Maxiniuc 12 months ago
I think we already have (at least the BOUND_PROPERTIES). The method to call is defBuffer/boundBuffer.dmoInfo.getFields(false)
. It will return an iterator over the original fields from temp-table, in the order they were defined. Iterate them synchronously to create the property pair you need. If the parameter of getFields
is true
it will also include the 'reserved' properties which you extract as defaultProps
.
Accessing the TableMapper
is not recommended. It has a slow complex structure.
The PROPERTIES
are probably in the best location.
#3 Updated by Constantin Asofiei 12 months ago
Thanks, Ovidiu. I just noticed that TableMapper.getAllLegacyFieldInfo
is still unconditionally executed.
Is dmoInfo.getFields(false)
using the same order as getAllLegacyFieldInfo
?
#4 Updated by Constantin Asofiei 12 months ago
And there is also propertyIndexes
used in ReferenceProxy.bind
, which can be cached.
#5 Updated by Ovidiu Maxiniuc 12 months ago
- File buffer-binding2.png added
Constantin Asofiei wrote:
Is
dmoInfo.getFields(false)
using the same order asgetAllLegacyFieldInfo
?
Actually no. The sorting happens on slightly different keys. The former use the propId
, the latter use the id
attribute. If the table is not expended (normalized) these values are identical. But when an extent field is expanded each of its properties receives a unique propId
while they all keep the same id
.
And there is also
propertyIndexes
used inReferenceProxy.bind
, which can be cached.
propertyIndexes
are the associated values of id
.
However, I think the optimal solution here is to do the binding at BaseRecord
level, by redirecting the accesses to data
. This means no mapping (using Map
) at all. And I am almost sure, no proxies, too. Here is a diagram sketch:
So we have the lowest level object, the data
array of similar size and corresponding properties types (validated while binding). All we need to do is redirect R/W accesses of buffer1.currentRecord.data[i]
to buffer2.currentRecord.data[i]
. The Record
/BaseRecord
have to be kept since the access will still be done via the DMO interface, but the data
it writes to/read from is temporarily (during bind) changed (maybe the lower green arrow should be moved even lower).
#6 Updated by Constantin Asofiei 12 months ago
Ovidiu, the binding maps is not only for accessing the record datum. This info is used when translating queries, too.
Also, binding is not limited to REFERENCE-ONLY tables. We can't remove ReferenceProxy support - see this test:
def temp-table tt1 field f1 as int. def temp-table tt2 field f2 as int. create tt1. tt1.f1 = 10. procedure proc1. def input parameter table for tt2 bind. find first tt2. message tt2.f2 string(temp-table tt2:handle) string(temp-table tt1:handle). // this shows the same handle value for both tt1 and tt2. end. run proc1(input table tt1 by-reference).
#7 Updated by Ovidiu Maxiniuc 12 months ago
My solution will not require translation at all: the buffer1
from my diagram will access the data of buffer2
using its own name and interface.
I did not say the solution is limited to REFERENCE-ONLY tables, just that they should have no RecordBuffer
before being bound.
I have no solution for identical table handle yet :(.
#8 Updated by Constantin Asofiei 12 months ago
Ovidiu Maxiniuc wrote:
My solution will not require translation at all: the
buffer1
from my diagram will access the data ofbuffer2
using its own name and interface.
find first tt2 where tt2.f2 = 10
needs to translate and use the tt1.f1
table and field, at the SQL level. You don't have the record yet at that point. This is the translation I mean.
I have no solution for identical table handle yet :(.
Yes, this is another reason for this complexity.
#9 Updated by Eric Faulhaber 12 months ago
Please note that directly accessing the BaseRecord.data
buffer is going to go badly in cases of the FIELDS clause and lazy hydration (#6720). The latter (if I can make it performant) will be the default behavior for non-temp-tables. When I originally implemented that array, I envisioned "fast" scenarios, where the data
array could be used more liberally within the persistence framework. However, I had not contemplated FIELDS clause and lazy hydration implementations back then.
Now, null
elements in the data
array do not necessarily mean "unknown value". They might mean uninitialized value instead. A separate bit set determines which meaning is valid. Bottom line is that directly doing R/W operations on the data
array is no longer supported; we have to go through BaseRecord.{get|set}{Datum|Data}
APIs to be safe. I am still working through the implementation of those for lazy hydration.
#10 Updated by Constantin Asofiei 12 months ago
Eric, I'm using OrmUtils.setField
and OrmUtils.getField
, not direct data
array access. Is this of concern?
About FIELDS: I don't see how this can be used with a FILL query.
#11 Updated by Constantin Asofiei 12 months ago
7379a rev 14586 adds cache for propertyIndexes
. TableMapper
now is called only when there is a cache miss; so, even if it's expensive, it is called only once.
#12 Updated by Eric Faulhaber 12 months ago
Constantin Asofiei wrote:
Eric, I'm using
OrmUtils.setField
andOrmUtils.getField
, not directdata
array access. Is this of concern?
Hm, yes, but maybe more for me on the lazy hydration end. My work is primarily on the getter side. If a data
element is null
upon invoking BaseRecord.getDatum(int)
(which OrmUtils.getField
does), a null
will be returned. This currently indicates that field has unknown value assigned.
However, with lazy hydration, for a DMO which was created from a query's result set, it may mean that the field's value has not yet been assigned. This information is managed in a separate bit set in BaseRecord
. In that case, we will try to just-in-time hydrate the data
element, if the result set is still valid, and we are still positioned on the correct row in it. If not, null
is returned. null
can still be returned if the field value is actually unknown value. So, a null
return requires a follow-up call to check the bit set and determine the meaning.
I do it this way to avoid having to throw and catch an exception, which causes two problems:
- it is expensive; and
- unless it is unchecked (which means it cannot be a
PersistenceException
subclass), it would require re-working the DMO interfaces to declare athrows
, which I want to avoid.
However, this means all callers of BaseRecord.getDatum
have to be able to interpret a null
properly. If it means just-in-time hydration failed, we must hydrate the field (and currently, all uninitialized fields) from a new query. I'm still working this part out, but I'm avoiding doing this within BaseRecord.getDatum
directly, because it carries a lot more baggage in terms of possible error/exception handling, which we already deal with at higher levels.
If I have to deal with all this within OrmUtils
, it is not ideal, but there may not be a better way.
About FIELDS: I don't see how this can be used with a FILL query.
I was basing my comments generally on Ovidiu's diagram, so they aren't necessarily applicable in all cases.
#13 Updated by Eric Faulhaber 12 months ago
Constantin, the only place I see OrmUtils.getField
used currently is in 3 locations in RecordBuffer.copy
. Are you expanding this use?
#14 Updated by Constantin Asofiei 12 months ago
Eric Faulhaber wrote:
Constantin, the only place I see
OrmUtils.getField
used currently is in 3 locations inRecordBuffer.copy
. Are you expanding this use?
Yes, via this in RecordBuffer
:
protected Object getDirectField(int offset) { return OrmUtils.getField(currentRecord, offset); }
called from
BufferImpl.assign
.#15 Updated by Constantin Asofiei 12 months ago
The setter is this:
protected void setDirectField(Object value, int offset) { Runnable restoreActiveState = null; try { restoreActiveState = currentRecord.setActiveBuffer(RecordBuffer.this); OrmUtils.setField(currentRecord, offset, value); } finally { if (restoreActiveState != null) { restoreActiveState.run(); } } }
#16 Updated by Constantin Asofiei 12 months ago
Eric, my current changes which affect rely on direct record access are in #7366, not in this task.
#17 Updated by Constantin Asofiei 12 months ago
- Status changed from WIP to Test
- % Done changed from 0 to 100
Branch 7379a was merged to trunk rev 14588 and archived. All changes intended in current task are in the branch. If something further needs to be explored, we can do in another task.