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)