[ 
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)

Reply via email to