Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public class ColocationGroup implements Message {

/** Marshalled assignments serialization call holder. */
@Order(2)
@Nullable int[] marshalledAssignments;
int @Nullable [] marshalledAssignments;

/** */
public static ColocationGroup forNodes(List<UUID> nodeIds) {
Expand Down Expand Up @@ -110,7 +110,7 @@ public ColocationGroup() {
}

/** */
private ColocationGroup(long[] srcIds, List<UUID> nodeIds, List<List<UUID>> assignments) {
private ColocationGroup(long[] srcIds, List<UUID> nodeIds, @Nullable List<List<UUID>> assignments) {
this.srcIds = srcIds;
this.nodeIds = nodeIds;
this.assignments = assignments;
Expand All @@ -134,16 +134,21 @@ public List<UUID> nodeIds() {
* @return List of partitions (index) and nodes (items) having an appropriate partition in
* {@link GridDhtPartitionState#OWNING} state, calculated for distributed tables, involved in query execution.
*/
public List<List<UUID>> assignments() {
public @Nullable List<List<UUID>> assignments() {

@vldpyatkov vldpyatkov Aug 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @Nullable annotation is redundant in this method.

if (assignments != null)
return assignments;
return Collections.unmodifiableList(assignments);

if (!F.isEmpty(nodeIds))
return nodeIds.stream().map(Collections::singletonList).collect(Collectors.toList());

return Collections.emptyList();
}

/** Returns {@code true} if this group represents partitioned data. */
boolean hasAssignments() {
return assignments != null;
}

/** */
public boolean belongs(long srcId) {
if (srcIds == null)
Expand Down Expand Up @@ -210,7 +215,11 @@ public ColocationGroup colocate(ColocationGroup other) throws ColocationMappingE
}
}
else {
assert this.assignments.size() == other.assignments.size();
if (this.assignments.size() != other.assignments.size()) {
throw new ColocationMappingException("Failed to map fragment to location. " +
"Caches have different numbers of partitions");
}

assignments = new ArrayList<>(this.assignments.size());
Set<UUID> filter = nodeIds == null ? null : new HashSet<>(nodeIds);
for (int i = 0; i < this.assignments.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,23 @@ public FragmentMapping filterByPartitions(int[] parts) throws ColocationMappingE
return new FragmentMapping(colocationGrps);
}

/**
* Checks that all given partitioned data sources are collocated, even if they belong to different query fragments.
*/
public static void validateColocation(List<FragmentMapping> mappings, Set<Long> srcIds)
throws ColocationMappingException {
ColocationGroup res = null;

for (FragmentMapping mapping : mappings) {
for (ColocationGroup grp : mapping.colocationGrps) {
if (!grp.hasAssignments() || srcIds.stream().noneMatch(grp::belongs))
continue;

res = res == null ? grp : res.colocate(grp);
}
}
}

/** */
public @NotNull ColocationGroup findGroup(long srcId) {
List<ColocationGroup> grps = colocationGrps.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,24 @@

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.google.common.primitives.Ints;
import org.apache.calcite.util.Pair;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
import org.apache.ignite.internal.processors.query.calcite.exec.partition.PartitionNode;
import org.apache.ignite.internal.processors.query.calcite.exec.partition.PartitionPruningContext;
import org.apache.ignite.internal.processors.query.calcite.metadata.AffinityService;
import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationMappingException;
import org.apache.ignite.internal.processors.query.calcite.metadata.FragmentMapping;
import org.apache.ignite.internal.processors.query.calcite.metadata.FragmentMappingException;
import org.apache.ignite.internal.processors.query.calcite.metadata.MappingService;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
Expand Down Expand Up @@ -89,6 +98,15 @@ protected AbstractMultiStepPlan(
if (!F.isEmpty(mapCtx.partitions())) {
List<Fragment> fragments = executionPlan0.fragments();

try {
FragmentMapping.validateColocation(
Commons.transform(fragments, Fragment::mapping), dataSourceIds(fragments));
}
catch (ColocationMappingException e) {
throw new IgniteSQLException(
"Execution of non-collocated query with partition parameter is not possible", e);
}

fragments = Commons.transform(fragments, f -> {
try {
return f.filterByPartitions(mapCtx.partitions());
Expand Down Expand Up @@ -134,6 +152,41 @@ else if (!mapCtx.isLocal() && mapCtx.unwrap(BaseQueryContext.class) != null) {
return executionPlan0;
}

/** Returns IDs of table data sources from all query fragments. */
private static Set<Long> dataSourceIds(List<Fragment> fragments) {
Set<Long> srcIds = new HashSet<>();

IgniteRelShuttle collector = new IgniteRelShuttle() {
@Override public IgniteRel visit(IgniteIndexScan rel) {
srcIds.add(rel.sourceId());

return super.visit(rel);
}

@Override public IgniteRel visit(IgniteIndexCount rel) {
srcIds.add(rel.sourceId());

return super.visit(rel);
}

@Override public IgniteRel visit(IgniteIndexBound rel) {
srcIds.add(rel.sourceId());

return super.visit(rel);
}

@Override public IgniteRel visit(IgniteTableScan rel) {
srcIds.add(rel.sourceId());

return super.visit(rel);
}
};

fragments.forEach(fragment -> fragment.root().accept(collector));

return srcIds;
}

/** {@inheritDoc} */
@Override public String textPlan() {
return textPlan;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.ignite.internal.processors.query.IgniteSQLException;
import org.apache.ignite.internal.processors.query.calcite.exec.PartitionExtractor;
import org.apache.ignite.internal.processors.query.calcite.exec.partition.PartitionNode;
import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationMappingException;
import org.apache.ignite.internal.processors.query.calcite.metadata.FragmentMappingException;
import org.apache.ignite.internal.processors.query.calcite.metadata.MappingService;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
Expand All @@ -41,6 +42,7 @@
import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.X;
import org.jetbrains.annotations.NotNull;

/** */
Expand Down Expand Up @@ -86,6 +88,11 @@ public ExecutionPlan map(MappingService mappingService, MappingQueryContext ctx)
return executionPlan0;
}
catch (FragmentMappingException e) {
if (ctx.isLocal() && X.hasCause(e, ColocationMappingException.class)) {
throw new IgniteSQLException(
"Execution of non-collocated query in local mode is not possible", e);
}

if (ex == null)
ex = e;
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,62 @@
*/
package org.apache.ignite.internal.processors.query.calcite.integration;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.CachePeekMode;
import org.apache.ignite.cache.QueryEntity;
import org.apache.ignite.cache.affinity.Affinity;
import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
import org.apache.ignite.cache.query.SqlFieldsQuery;
import org.apache.ignite.cache.query.annotations.QuerySqlField;
import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.processors.query.QueryContext;
import org.apache.ignite.internal.processors.query.calcite.QueryChecker;
import org.apache.ignite.internal.util.typedef.F;
import org.junit.Test;

/** */
public class LocalQueryIntegrationTest extends AbstractBasicIntegrationTest {
/** */
private static final int ENTRIES_COUNT = 10000;

/** */
private static final String CACHE_NAME = "cache_name";

/** */
private static final String PERSON_TABLE = '"' + CACHE_NAME + '"' + ".Person";

/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
cfg.getSqlConfiguration().setQueryEnginesConfiguration(new CalciteQueryEngineConfiguration());

LinkedHashMap<String, String> fields = new LinkedHashMap<>();

fields.put("ID", Integer.class.getName());
fields.put("IDX_VAL", String.class.getName());
fields.put("VAL", String.class.getName());

QueryEntity qryEntity = new QueryEntity()
.setTableName("Person")
.setKeyType(Integer.class.getName())
.setValueType(TestValue.class.getName())
.setFields(fields)
.setKeyFieldName("ID");

CacheConfiguration<Integer, TestValue> ccfg = new CacheConfiguration<Integer, TestValue>()
.setName(CACHE_NAME)
.setQueryEntities(F.asList(qryEntity))
.setAffinity(new RendezvousAffinityFunction(false, 8))
.setCacheMode(CacheMode.PARTITIONED);

cfg.setCacheConfiguration(ccfg);

return cfg;
}

Expand Down Expand Up @@ -119,6 +152,13 @@ public void testJoinReplicated() {
Stream.of("ID", "IDX_VAL", "VAL").forEach(col -> testJoin("T1", "DICT", col));
}

/** */
@Test
public void testLocalQueryWithDifferentDistribution() {
Stream.of("ID", "IDX_VAL", "VAL").forEach(col -> assertThrowsSqlException(
fillJoinQuery("T1", PERSON_TABLE, col), "Execution of non-collocated query"));
}

/** */
@Test
public void testInsertFromSelect() {
Expand Down Expand Up @@ -180,10 +220,13 @@ public void testCount() {

/** */
private void testJoin(String table1, String table2, String joinCol) {
String sql = "select * from " + table1 + " join " + table2 +
" on " + table1 + "." + joinCol + "=" + table2 + "." + joinCol;
test(fillJoinQuery(table1, table2, joinCol), table1 + "_CACHE");
}

test(sql, table1 + "_CACHE");
/** */
private String fillJoinQuery(String table1, String table2, String joinCol) {
return "select * from " + table1 + " join " + table2 +
" on " + table1 + "." + joinCol + "=" + table2 + "." + joinCol;
}

/** */
Expand All @@ -198,8 +241,14 @@ private void test(String sql, String cacheName) {

return aff.isPrimary(locNode, part);
}
).collect(Collectors.toList());;
).toList();

assertEquals(primaries.size(), res.size());
}

/**
* @param idx_val
* @param val
*/
private record TestValue(@QuerySqlField String idx_val, @QuerySqlField String val) {}
}
Loading
Loading