Skip to content

Commit 5c3cf86

Browse files
authored
IGNITE-22789 Throw an exception when join is used for partitioned cache with affinity key and replicated cache with different partitions count (#11449)
1 parent b158d2f commit 5c3cf86

File tree

4 files changed

+127
-7
lines changed

4 files changed

+127
-7
lines changed

Diff for: modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java

+7
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.apache.ignite.internal.util.typedef.internal.U;
4949
import org.h2.command.Prepared;
5050
import org.h2.command.dml.Query;
51+
import org.jetbrains.annotations.Nullable;
5152

5253
import static org.apache.ignite.internal.processors.query.h2.opt.join.CollocationModel.isCollocated;
5354
import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlConst.TRUE;
@@ -1271,6 +1272,12 @@ private void splitSelect(GridSqlAst parent, int childIdx) throws IgniteCheckedEx
12711272
SqlAstTraverser traverser = new SqlAstTraverser(mapQry, distributedJoins, log);
12721273
traverser.traverse();
12731274

1275+
@Nullable SqlAstTraverser.MixedModeCachesJoinIssue mixedJoinIssue = traverser.hasOuterJoinMixedCacheModeIssue();
1276+
1277+
if (mixedJoinIssue != null && mixedJoinIssue.error()) {
1278+
throw new CacheException(mixedJoinIssue.errorMessage());
1279+
}
1280+
12741281
map.columns(collectColumns(mapExps));
12751282
map.sortColumns(mapQry.sort());
12761283
map.partitioned(traverser.hasPartitionedTables());

Diff for: modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SqlAstTraverser.java

+60-4
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
package org.apache.ignite.internal.processors.query.h2.sql;
1919

2020
import java.util.HashSet;
21+
import java.util.Objects;
2122
import java.util.Set;
2223
import org.apache.ignite.IgniteLogger;
2324
import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
25+
import org.jetbrains.annotations.Nullable;
2426

2527
/**
2628
* Traverse over query AST to find info about partitioned table usage.
@@ -44,6 +46,9 @@ class SqlAstTraverser {
4446
/** Whether query has joins between replicated and partitioned tables. */
4547
private boolean hasOuterJoinReplicatedPartitioned;
4648

49+
/** */
50+
private @Nullable MixedModeCachesJoinIssue hasOuterJoinMixedCacheModeIssue;
51+
4752
/** Whether top-level table is replicated. */
4853
private boolean isRootTableReplicated;
4954

@@ -86,6 +91,11 @@ public boolean hasReplicatedWithPartitionedAndSubQuery() {
8691
return (isRootTableReplicated && hasSubQueries && hasPartitionedTables);
8792
}
8893

94+
/** */
95+
public @Nullable MixedModeCachesJoinIssue hasOuterJoinMixedCacheModeIssue() {
96+
return hasOuterJoinMixedCacheModeIssue;
97+
}
98+
8999
/**
90100
* Traverse AST while join operation isn't found. Check it if found.
91101
*
@@ -168,8 +178,24 @@ else if (ast instanceof GridSqlTable)
168178
if (left == null || right == null)
169179
return;
170180

171-
if (join.isLeftOuter() && !left.isPartitioned() && right.isPartitioned())
172-
hasOuterJoinReplicatedPartitioned = true;
181+
if (join.isLeftOuter() && !left.isPartitioned() && right.isPartitioned()) {
182+
if (left.cacheContext().affinity().partitions() != right.cacheContext().affinity().partitions()) {
183+
hasOuterJoinMixedCacheModeIssue = new MixedModeCachesJoinIssue("Cache [cacheName=" + left.cacheName() +
184+
", partitionsCount=" + left.cacheContext().affinity().partitions() +
185+
"] can`t be joined with [cacheName=" + right.cacheName() +
186+
", partitionsCount=" + right.cacheContext().affinity().partitions() +
187+
"] due to different affinity configuration. Join between PARTITIONED and REPLICATED caches is possible "
188+
+ "only with the same partitions number configuration.");
189+
}
190+
// the only way to compare predicate classes, not work for different class loaders.
191+
else if (!Objects.equals(className(left.cacheInfo().config().getNodeFilter()), className(right.cacheInfo().config()
192+
.getNodeFilter()))) {
193+
hasOuterJoinMixedCacheModeIssue = new MixedModeCachesJoinIssue("Cache [cacheName=" + left.cacheName() + "] "
194+
+ "can`t be joined with [cacheName=" + right.cacheName() + "] due to different node filters configuration.");
195+
}
196+
else
197+
hasOuterJoinReplicatedPartitioned = true;
198+
}
173199

174200
// Skip check if at least one of tables isn't partitioned.
175201
if (!(left.isPartitioned() && right.isPartitioned()))
@@ -179,6 +205,11 @@ else if (ast instanceof GridSqlTable)
179205
checkPartitionedJoin(join, where, left, right, log);
180206
}
181207

208+
/** Object class name. */
209+
@Nullable private static String className(@Nullable Object obj) {
210+
return obj != null ? obj.getClass().getName() : null;
211+
}
212+
182213
/**
183214
* Checks whether an AST contains valid join operation between partitioned tables.
184215
* Join condition should be an equality operation of affinity keys of tables. Conditions can be splitted between
@@ -242,7 +273,7 @@ private String getAlias(GridSqlElement el) {
242273
private Set<String> affKeys(boolean pk, GridH2Table tbl) {
243274
Set<String> affKeys = new HashSet<>();
244275

245-
// User explicitly specify an affinity key. Otherwise use primary key.
276+
// User explicitly specify an affinity key. Otherwise, use primary key.
246277
if (!pk)
247278
affKeys.add(tbl.getAffinityKeyColumn().columnName);
248279
else {
@@ -279,7 +310,7 @@ private boolean checkPartitionedCondition(GridSqlElement condition,
279310
if (GridSqlOperationType.EQUAL == op.operationType())
280311
checkEqualityOperation(op, leftTbl, leftAffKeys, pkLeft, rightTbl, rightAffKeys, pkRight);
281312

282-
// Check affinity condition is covered fully. If true then return. Otherwise go deeper.
313+
// Check affinity condition is covered fully. If true then return. Otherwise, go deeper.
283314
if (affinityCondIsCovered(leftAffKeys, rightAffKeys))
284315
return true;
285316

@@ -342,4 +373,29 @@ private void checkEqualityOperation(GridSqlOperation equalOp,
342373
private boolean affinityCondIsCovered(Set<String> leftAffKeys, Set<String> rightAffKeys) {
343374
return leftAffKeys.isEmpty() && rightAffKeys.isEmpty();
344375
}
376+
377+
/** Mixed cache mode join issues. */
378+
static class MixedModeCachesJoinIssue {
379+
/** */
380+
private final boolean err;
381+
382+
/** */
383+
private final String msg;
384+
385+
/** Constructor. */
386+
MixedModeCachesJoinIssue(String errMsg) {
387+
err = true;
388+
msg = errMsg;
389+
}
390+
391+
/** Return {@code true} if error present. */
392+
boolean error() {
393+
return err;
394+
}
395+
396+
/** Return appropriate error message. */
397+
String errorMessage() {
398+
return msg;
399+
}
400+
}
345401
}

Diff for: modules/indexing/src/test/java/org/apache/ignite/sqltests/BaseSqlTest.java

-2
Original file line numberDiff line numberDiff line change
@@ -1224,7 +1224,6 @@ public void testRightJoin() {
12241224
/**
12251225
* Check that FULL OUTER JOIN (which is currently unsupported) causes valid error message.
12261226
*/
1227-
@SuppressWarnings("ThrowableNotThrown")
12281227
@Test
12291228
public void testFullOuterJoinIsNotSupported() {
12301229
testAllNodes(node -> {
@@ -1245,7 +1244,6 @@ public void testFullOuterJoinIsNotSupported() {
12451244
/**
12461245
* Check that distributed FULL OUTER JOIN (which is currently unsupported) causes valid error message.
12471246
*/
1248-
@SuppressWarnings("ThrowableNotThrown")
12491247
@Test
12501248
public void testFullOuterDistributedJoinIsNotSupported() {
12511249
testAllNodes(node -> {

Diff for: modules/indexing/src/test/java/org/apache/ignite/sqltests/ReplicatedSqlCustomPartitionsTest.java

+60-1
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,38 @@
1717

1818
package org.apache.ignite.sqltests;
1919

20+
import org.apache.ignite.IgniteException;
2021
import org.apache.ignite.cache.CacheMode;
2122
import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
2223
import org.apache.ignite.configuration.CacheConfiguration;
2324
import org.apache.ignite.configuration.IgniteConfiguration;
25+
import org.apache.ignite.testframework.GridTestUtils;
2426
import org.junit.Test;
2527

2628
/**
2729
* Includes all base sql test plus tests that make sense in replicated mode with a non-default number of partitions.
2830
*/
2931
public class ReplicatedSqlCustomPartitionsTest extends ReplicatedSqlTest {
3032
/** Test partitions count. */
31-
private static final int NUM_OF_PARTITIONS = 509;
33+
static final int NUM_OF_PARTITIONS = 509;
34+
35+
/** */
36+
static final String DEP_PART_TAB_DIFF = "DepartmentPartDiff";
37+
38+
/** */
39+
static final String DEP_PART_TAB_DIFF_NF = "DepartmentPartDiffNf";
3240

3341
/** {@inheritDoc} */
3442
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
3543
return super.getConfiguration(igniteInstanceName)
3644
.setCacheConfiguration(
3745
new CacheConfiguration("partitioned" + NUM_OF_PARTITIONS + "*")
3846
.setAffinity(new RendezvousAffinityFunction(false, NUM_OF_PARTITIONS)),
47+
new CacheConfiguration("partitioned" + NUM_OF_PARTITIONS + "_DIFF*")
48+
.setAffinity(new RendezvousAffinityFunction(false, NUM_OF_PARTITIONS + 1)),
49+
new CacheConfiguration("partitioned" + NUM_OF_PARTITIONS + "_DIFF_NF*")
50+
.setAffinity(new RendezvousAffinityFunction(false, NUM_OF_PARTITIONS))
51+
.setNodeFilter(clusterNode -> true),
3952
new CacheConfiguration("replicated" + NUM_OF_PARTITIONS + "*")
4053
.setCacheMode(CacheMode.REPLICATED)
4154
.setAffinity(new RendezvousAffinityFunction(false, NUM_OF_PARTITIONS))
@@ -55,6 +68,14 @@ public class ReplicatedSqlCustomPartitionsTest extends ReplicatedSqlTest {
5568
createDepartmentTable(DEP_PART_TAB, "template=partitioned" + NUM_OF_PARTITIONS);
5669

5770
fillDepartmentTable(DEP_PART_TAB);
71+
72+
createDepartmentTable(DEP_PART_TAB_DIFF, "template=partitioned" + NUM_OF_PARTITIONS + "_DIFF");
73+
74+
fillDepartmentTable(DEP_PART_TAB_DIFF);
75+
76+
createDepartmentTable(DEP_PART_TAB_DIFF_NF, "template=partitioned" + NUM_OF_PARTITIONS + "_DIFF_NF");
77+
78+
fillDepartmentTable(DEP_PART_TAB_DIFF_NF);
5879
}
5980

6081
/**
@@ -73,4 +94,42 @@ public void testLeftJoinReplicatedPartitioned() {
7394
public void testRightJoinPartitionedReplicated() {
7495
checkRightJoinDepartmentEmployee(DEP_PART_TAB);
7596
}
97+
98+
/**
99+
* Check LEFT JOIN with collocated data of replicated and partitioned tables with different affinity.
100+
* This test relies on having the same number of partitions in replicated and partitioned caches
101+
*/
102+
@Test
103+
public void testLeftJoinReplicatedPartitionedDiffPartitionsErr() {
104+
GridTestUtils.assertThrows(log, () -> checkLeftJoinEmployeeDepartment(DEP_PART_TAB_DIFF), IgniteException.class,
105+
"only with the same partitions number configuration");
106+
}
107+
108+
/**
109+
* Check RIGHT JOIN with collocated data of partitioned and replicated tables with different affinity.
110+
*/
111+
@Test
112+
public void testRightJoinPartitionedReplicatedDiffPartitionsErr() {
113+
GridTestUtils.assertThrows(log, () -> checkRightJoinDepartmentEmployee(DEP_PART_TAB_DIFF), IgniteException.class,
114+
"only with the same partitions number configuration");
115+
}
116+
117+
/**
118+
* Check LEFT JOIN with collocated data of replicated and partitioned tables with different node filter.
119+
* This test relies on having the same number of partitions in replicated and partitioned caches
120+
*/
121+
@Test
122+
public void testLeftJoinReplicatedPartitionedDiffNodeFilterErr() {
123+
GridTestUtils.assertThrows(log, () -> checkLeftJoinEmployeeDepartment(DEP_PART_TAB_DIFF_NF), IgniteException.class,
124+
"due to different node filters configuration");
125+
}
126+
127+
/**
128+
* Check RIGHT JOIN with collocated data of partitioned and replicated tables with different node filter.
129+
*/
130+
@Test
131+
public void testRightJoinPartitionedReplicatedDiffNodeFilterErr() {
132+
GridTestUtils.assertThrows(log, () -> checkRightJoinDepartmentEmployee(DEP_PART_TAB_DIFF_NF), IgniteException.class,
133+
"due to different node filters configuration");
134+
}
76135
}

0 commit comments

Comments
 (0)