Anton Vinogradov created IGNITE-28930:
-----------------------------------------

             Summary: Default field values are lost on remote nodes when a 
query entity is added dynamically
                 Key: IGNITE-28930
                 URL: https://issues.apache.org/jira/browse/IGNITE-28930
             Project: Ignite
          Issue Type: Task
            Reporter: Anton Vinogradov


GridQueryProcessor#dynamicAddQueryEntity delivers its QueryEntity list to the 
other nodes inside
SchemaAddQueryEntityOperation, whose entitiesMsgs field is a lazy view:

    entitiesMsgs = F.viewReadOnly(entities, this::makeEntityMessage);    // 
SchemaAddQueryEntityOperation:70

F.viewReadOnly returns a TransformCollectionView, so every traversal runs the 
closure again and hands out
fresh QueryEntityMessage instances. TcpDiscoveryIoSession#serializeMessage 
traverses the message twice:

    MessageMarshalling.marshal(m, ...);                             // 
TcpDiscoveryIoSession:244
    MessageSerialization.writeTo(spi.messageFactory(), m, msgWriter); // 
TcpDiscoveryIoSession:255

The first traversal fills dfltFieldValuesBytes on instances that are dropped 
right away; the second one
builds new instances whose companion is still null, and writes those. The 
default values never leave the
initiator. Every other field of the same entity arrives intact, because 
dfltFieldValues is the only one
that needs a marshalling step.

Reproduced on two nodes:

    n0: dynamicAddQueryEntity("C", "PUBLIC", <entity with 
defaultFieldValues=\{NAME=unknown}>, null, true)
    n0: getDefaultFieldValues() -> \{NAME=unknown}
    n1: getDefaultFieldValues() -> {}          <- lost
    n1: notNullFields          -> [ID]         <- a plain field of the same 
entity arrives

Same through SQL, when the cache already exists:

    CREATE TABLE PERSON (ID INT PRIMARY KEY, NAME VARCHAR DEFAULT 'unknown') 
WITH "cache_name=C"

User visible effect: an INSERT that omits the column writes NULL instead of the 
default on every node
except the one that ran the statement.

    n1: INSERT INTO PERSON(ID) VALUES(2)  ->  SELECT NAME -> null
    n0: INSERT INTO PERSON(ID) VALUES(1)  ->  SELECT NAME -> 'unknown'

Remote nodes also lose the defaults from their stored cache configuration, so a 
restart does not recover
them.

A plain CREATE TABLE (GridQueryProcessor#dynamicTableCreate) is not affected: 
the defaults reach both the
running nodes and a node that joins afterwards. Statically configured 
QueryEntity is not affected either.

The fix is to materialize the collection:

    entitiesMsgs = F.transform(entities, this::makeEntityMessage);

More generally, an @Order field holding messages that carry marshalling state 
must not be a lazy view,
because marshal and writeTo traverse it independently. QueryEntityMessage#idxs 
is such a view today and
is harmless only because QueryIndexMessage has no marshalling state - one 
@Marshalled field there would
break it the same way.

Introduced by bc1e3d277e3 (IGNITE-28767), which replaced the operation's own 
byte[] field - stable across
traversals - with the lazy view. The commit is in no release tag, so master is 
the only affected version.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to