[
https://issues.apache.org/jira/browse/IGNITE-28907?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Oleg Valuyskiy updated IGNITE-28907:
------------------------------------
Description:
h2. Problem
{{CacheConfiguration.setIndexedTypes(...)}} and
{{CacheConfiguration.setQueryEntities(...)}} both populate the same internal
collection of {{QueryEntity}} definitions. When both methods configure the same
value type, the current implementation treats the second {{QueryEntity}} as a
duplicate based only on its value type and silently ignores it. As a result,
SQL metadata supplied by the second configuration method is lost.
The issue is not specific to annotation-based indexes. {{setIndexedTypes(...)}}
creates a {{QueryEntity}} for every configured key/value type pair even when
the value class does not contain any {{@QuerySqlField}} annotations. Therefore,
merely configuring a value type through {{setIndexedTypes(...)}} is enough to
prevent a subsequent {{QueryEntity}} for the same value type from being
applied. Reproducer: [^MixedIndexConfigurationTest.patch]
h2. Root cause
Both methods store query metadata in the same internal {{qryEntities}}
collection. The existing behavior is conceptually equivalent to:
{code:java}
if (!containsQueryEntityWithSameValueType(newEntity))
qryEntities.add(newEntity);
{code}
If a {{QueryEntity}} with the same value type is already present:
* the entities are not merged;
* fields are not compared;
* indexes are not merged;
* aliases and constraints are not merged;
* conflicting metadata is not detected;
* the incoming entity is silently ignored.
This makes the resulting SQL schema incomplete and dependent on the order in
which configuration methods are invoked.
h2. Expected behavior
When {{setIndexedTypes(...)}} and {{setQueryEntities(...)}} configure the same
value type, Ignite should attempt to merge the corresponding {{QueryEntity}}
definitions:
* Compatible metadata should be combined.
* Conflicting metadata should result in a {{CacheException}} instead of
silently selecting one definition.
* The merge should be incremental and operate on the effective, already
accumulated {{{}QueryEntity{}}}.
Conceptually:
{code:java}
QueryEntity existing = findQueryEntity(valueType);
if (existing == null)
qryEntities.add(incoming);
else
replaceQueryEntity(existing, mergeQueryEntities(existing, incoming));
{code}
This allows multiple complementary calls to {{setQueryEntities(...)}} to
accumulate metadata instead of losing information from previous calls.
h2. Merge rules
h3. Scalar properties
For properties such as:
* key type;
* value type;
* table name;
* key field name;
* value field name;
the rules are:
{noformat}
null + X -> X
X + null -> X
X + X -> X
X + Y -> CacheException
{noformat}
h3. Fields
Field definitions should be merged while preserving the order of the existing
entity. Fields present only in the incoming entity should be appended. For the
same field:
* equal field types are compatible;
* different field types must cause a {{{}CacheException{}}}.
Example:
{noformat}
existing:
name : String
age : Integer
incoming:
age : Integer
city : String
result:
name : String
age : Integer
city : String
{noformat}
h3. Indexes
Indexes with different names should be combined.
For indexes with the same name:
* identical definitions should be deduplicated;
* different definitions must cause a {{{}CacheException{}}}.
The comparison must take the complete index definition into account, including:
* indexed fields;
* field order;
* ascending/descending order;
* index type;
* inline size where applicable.
h3. Map-based metadata
Metadata such as:
* aliases;
* default field values;
* field precision;
* field scale;
should be merged by key.
For the same key:
* equal values are compatible;
* different values must cause a {{{}CacheException{}}}.
h3. Set-based metadata
Metadata such as:
* key fields;
* not-null fields;
should be merged using set union.
was:
h2. Problem
{\{CacheConfiguration.setIndexedTypes(...)}} and
\{{CacheConfiguration.setQueryEntities(...)}} both populate the same internal
collection of \{{QueryEntity}} definitions.
When both methods configure the same value type, the current implementation
treats the second \{{QueryEntity}} as a duplicate based only on its value type
and silently ignores it.
As a result, SQL metadata supplied by the second configuration method is lost.
The issue is not specific to annotation-based indexes.
{\{setIndexedTypes(...)}} creates a \{{QueryEntity}} for every configured
key/value type pair even when the value class does not contain any
\{{@QuerySqlField}} annotations. Therefore, merely configuring a value type
through \{{setIndexedTypes(...)}} is enough to prevent a subsequent
\{{QueryEntity}} for the same value type from being applied.
For example:
{code:java}
CacheConfiguration<Integer, Person> ccfg =
new CacheConfiguration<>("person-cache");
ccfg.setIndexedTypes(Integer.class, Person.class);
ccfg.setQueryEntities(Collections.singletonList(
new QueryEntity()
.setKeyType(Integer.class.getName())
.setValueType(Person.class.getName())
.setFields(fields)
.setIndexes(Collections.singletonList(
new QueryIndex(Arrays.asList("name", "age"))
.setName("PERSON_NAME_AGE_IDX")
))
));
{code}
Even if \{{Person}} has no query annotations, \{{setIndexedTypes(...)}} creates
a \{{QueryEntity}} for \{{Person}} first.
The explicit \{{QueryEntity}} supplied later through \{{setQueryEntities(...)}}
has the same value type and is silently skipped.
The cache starts successfully, but \{{PERSON_NAME_AGE_IDX}} is not created.
If \{{Person}} additionally contains:
{code:java}
@QuerySqlField(index = true)
private String name;
{code}
the annotation-derived single-column index is created, while the explicitly
configured composite index is still missing.
Therefore, the actual conflict is between two \{{CacheConfiguration}} APIs:
* {\{setIndexedTypes(...)}};
* {\{setQueryEntities(...)}};
rather than between annotations and explicit configuration as such.
h2. Root cause
Both methods store query metadata in the same internal \{{qryEntities}}
collection.
The existing behavior is conceptually equivalent to:
{code:java}
if (!containsQueryEntityWithSameValueType(newEntity))
qryEntities.add(newEntity);
{code}
If a \{{QueryEntity}} with the same value type is already present:
* the entities are not merged;
* fields are not compared;
* indexes are not merged;
* aliases and constraints are not merged;
* conflicting metadata is not detected;
* the incoming entity is silently ignored.
This makes the resulting SQL schema incomplete and dependent on the order in
which configuration methods are invoked.
h2. Expected behavior
When \{{setIndexedTypes(...)}} and \{{setQueryEntities(...)}} configure the
same value type, Ignite should attempt to merge the corresponding
\{{QueryEntity}} definitions.
Compatible metadata should be combined.
Conflicting metadata should result in a \{{CacheException}} instead of silently
selecting one definition.
The merge should be incremental and operate on the effective, already
accumulated \{{QueryEntity}}.
Conceptually:
{code:java}
QueryEntity existing = findQueryEntity(valueType);
if (existing == null)
qryEntities.add(incoming);
else
replaceQueryEntity(existing, mergeQueryEntities(existing, incoming));
{code}
This allows multiple complementary calls to \{{setQueryEntities(...)}} to
accumulate metadata instead of losing information from previous calls.
h2. Merge rules
The following merge semantics should be applied.
h3. Scalar properties
For properties such as:
* key type;
* value type;
* table name;
* key field name;
* value field name;
the rules are:
{noformat}
null + X -> X
X + null -> X
X + X -> X
X + Y -> CacheException
{noformat}
h3. Fields
Field definitions should be merged while preserving the order of the existing
entity.
Fields present only in the incoming entity should be appended.
For the same field:
* equal field types are compatible;
* different field types must cause a \{{CacheException}}.
Example:
{noformat}
existing:
name : String
age : Integer
incoming:
age : Integer
city : String
result:
name : String
age : Integer
city : String
{noformat}
h3. Indexes
Indexes with different names should be combined.
For indexes with the same name:
* identical definitions should be deduplicated;
* different definitions must cause a \{{CacheException}}.
The comparison must take the complete index definition into account, including:
* indexed fields;
* field order;
* ascending/descending order;
* index type;
* inline size where applicable.
h3. Map-based metadata
Metadata such as:
* aliases;
* default field values;
* field precision;
* field scale;
should be merged by key.
For the same key:
* equal values are compatible;
* different values must cause a \{{CacheException}}.
h3. Set-based metadata
Metadata such as:
* key fields;
* not-null fields;
should be merged using set union.
h2. Multiple setQueryEntities calls
The implementation should support consecutive complementary calls to
\{{setQueryEntities(...)}}.
For example:
{code:java}
ccfg.setQueryEntities(Collections.singletonList(entityWithNameIndex));
ccfg.setQueryEntities(Collections.singletonList(entityWithAgeIndex));
ccfg.setQueryEntities(Collections.singletonList(entityWithCompositeIndex));
{code}
The resulting \{{QueryEntity}} must contain metadata from all three calls.
The merge must use the current effective entity rather than reconstructing the
original entity from \{{indexedTypes}}, otherwise metadata accumulated by
previous merge operations can be lost.
h2. setIndexedTypes state
{\{setIndexedTypes(...)}} creates a boxed copy of the supplied key/value type
array, but the resulting array must also be stored in the \{{indexedTypes}}
field.
The field is already exposed through \{{getIndexedTypes()}} and is also used to
prevent repeated \{{setIndexedTypes(...)}} calls.
The configuration should therefore preserve the successfully applied indexed
types:
{code:java}
this.indexedTypes = newIndexedTypes;
{code}
The assignment should happen only after successful processing of the supplied
types.
h2. Expected result
For the following configuration:
{code:java}
ccfg.setIndexedTypes(Integer.class, Person.class);
ccfg.setQueryEntities(Collections.singletonList(
personEntityWithCompositeIndex()
));
{code}
the resulting SQL schema should contain the explicitly configured composite
index even if \{{Person}} has no query annotations.
If \{{Person}} also declares:
{code:java}
@QuerySqlField(index = true)
private String name;
{code}
both indexes should be created:
{noformat}
PERSON_NAME_IDX
PERSON_NAME_AGE_IDX
{noformat}
along with the default primary-key index.
h2. Acceptance criteria
* {\{setIndexedTypes(...)}} followed by \{{setQueryEntities(...)}} merges
compatible metadata for the same value type.
* {\{setQueryEntities(...)}} followed by \{{setIndexedTypes(...)}} also merges
compatible metadata.
* Multiple complementary \{{setQueryEntities(...)}} calls accumulate metadata.
* Different value types remain separate \{{QueryEntity}} definitions.
* Fields with the same name and type are deduplicated.
* Fields with the same name and different types cause a \{{CacheException}}.
* Different indexes are combined.
* Identical indexes with the same name are deduplicated.
* Indexes with the same name but different definitions cause a
\{{CacheException}}.
* Compatible aliases, precision, scale, defaults, key fields and not-null
fields are merged.
* Conflicting scalar or map-based metadata causes a \{{CacheException}}.
* Annotation-derived metadata created through \{{setIndexedTypes(...)}} can be
combined with explicitly configured metadata from \{{setQueryEntities(...)}}.
* SQL metadata is no longer silently discarded based only on duplicate value
type.
* {\{getIndexedTypes()}} returns the types successfully configured through
\{{setIndexedTypes(...)}}.
* Integration tests verify that merged index definitions are actually
registered in the SQL schema and exposed through the \{{INDEXES}} system view.
> Support merging QueryEntity metadata configured through setIndexedTypes and
> setQueryEntities
> --------------------------------------------------------------------------------------------
>
> Key: IGNITE-28907
> URL: https://issues.apache.org/jira/browse/IGNITE-28907
> Project: Ignite
> Issue Type: Task
> Reporter: Oleg Valuyskiy
> Assignee: Oleg Valuyskiy
> Priority: Major
> Labels: ise
> Attachments: MixedIndexConfigurationTest.patch
>
>
> h2. Problem
> {{CacheConfiguration.setIndexedTypes(...)}} and
> {{CacheConfiguration.setQueryEntities(...)}} both populate the same internal
> collection of {{QueryEntity}} definitions. When both methods configure the
> same value type, the current implementation treats the second {{QueryEntity}}
> as a duplicate based only on its value type and silently ignores it. As a
> result, SQL metadata supplied by the second configuration method is lost.
> The issue is not specific to annotation-based indexes.
> {{setIndexedTypes(...)}} creates a {{QueryEntity}} for every configured
> key/value type pair even when the value class does not contain any
> {{@QuerySqlField}} annotations. Therefore, merely configuring a value type
> through {{setIndexedTypes(...)}} is enough to prevent a subsequent
> {{QueryEntity}} for the same value type from being applied. Reproducer:
> [^MixedIndexConfigurationTest.patch]
> h2. Root cause
> Both methods store query metadata in the same internal {{qryEntities}}
> collection. The existing behavior is conceptually equivalent to:
> {code:java}
> if (!containsQueryEntityWithSameValueType(newEntity))
> qryEntities.add(newEntity);
> {code}
> If a {{QueryEntity}} with the same value type is already present:
> * the entities are not merged;
> * fields are not compared;
> * indexes are not merged;
> * aliases and constraints are not merged;
> * conflicting metadata is not detected;
> * the incoming entity is silently ignored.
> This makes the resulting SQL schema incomplete and dependent on the order in
> which configuration methods are invoked.
> h2. Expected behavior
> When {{setIndexedTypes(...)}} and {{setQueryEntities(...)}} configure the
> same value type, Ignite should attempt to merge the corresponding
> {{QueryEntity}} definitions:
> * Compatible metadata should be combined.
> * Conflicting metadata should result in a {{CacheException}} instead of
> silently selecting one definition.
> * The merge should be incremental and operate on the effective, already
> accumulated {{{}QueryEntity{}}}.
> Conceptually:
> {code:java}
> QueryEntity existing = findQueryEntity(valueType);
> if (existing == null)
> qryEntities.add(incoming);
> else
> replaceQueryEntity(existing, mergeQueryEntities(existing, incoming));
> {code}
> This allows multiple complementary calls to {{setQueryEntities(...)}} to
> accumulate metadata instead of losing information from previous calls.
> h2. Merge rules
> h3. Scalar properties
> For properties such as:
> * key type;
> * value type;
> * table name;
> * key field name;
> * value field name;
> the rules are:
> {noformat}
> null + X -> X
> X + null -> X
> X + X -> X
> X + Y -> CacheException
> {noformat}
> h3. Fields
> Field definitions should be merged while preserving the order of the existing
> entity. Fields present only in the incoming entity should be appended. For
> the same field:
> * equal field types are compatible;
> * different field types must cause a {{{}CacheException{}}}.
> Example:
> {noformat}
> existing:
> name : String
> age : Integer
> incoming:
> age : Integer
> city : String
> result:
> name : String
> age : Integer
> city : String
> {noformat}
> h3. Indexes
> Indexes with different names should be combined.
> For indexes with the same name:
> * identical definitions should be deduplicated;
> * different definitions must cause a {{{}CacheException{}}}.
> The comparison must take the complete index definition into account,
> including:
> * indexed fields;
> * field order;
> * ascending/descending order;
> * index type;
> * inline size where applicable.
> h3. Map-based metadata
> Metadata such as:
> * aliases;
> * default field values;
> * field precision;
> * field scale;
> should be merged by key.
> For the same key:
> * equal values are compatible;
> * different values must cause a {{{}CacheException{}}}.
> h3. Set-based metadata
> Metadata such as:
> * key fields;
> * not-null fields;
> should be merged using set union.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)