This is an automated email from the ASF dual-hosted git repository.
korlov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 87eb584f765 IGNITE-25559 Sql. Implement collocation metadata builder.
(#6030)
87eb584f765 is described below
commit 87eb584f765e38c309fce7a16fc0e077eed68cf5
Author: Max Zhuravkov <[email protected]>
AuthorDate: Wed Jun 18 10:01:00 2025 +0300
IGNITE-25559 Sql. Implement collocation metadata builder. (#6030)
---
.../internal/sql/engine/prepare/DdlPlan.java | 8 +
.../internal/sql/engine/prepare/ExplainPlan.java | 8 +
.../sql/engine/prepare/KeyValueGetPlan.java | 20 +-
.../sql/engine/prepare/KeyValueModifyPlan.java | 14 +-
.../internal/sql/engine/prepare/KillPlan.java | 8 +
.../internal/sql/engine/prepare/MultiStepPlan.java | 7 +
.../sql/engine/prepare/PrepareServiceImpl.java | 19 +-
.../internal/sql/engine/prepare/QueryPlan.java | 7 +
.../sql/engine/prepare/SelectCountPlan.java | 7 +
.../PartitionAwarenessMetadata.java | 87 ++++++++
.../PartitionAwarenessMetadataExtractor.java | 121 +++++++++++
.../PartitionAwarenessMetadataTest.java | 235 +++++++++++++++++++++
12 files changed, 535 insertions(+), 6 deletions(-)
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/DdlPlan.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/DdlPlan.java
index 10f886bc8c3..2ca3da83560 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/DdlPlan.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/DdlPlan.java
@@ -23,9 +23,11 @@ import org.apache.ignite.internal.catalog.CatalogCommand;
import org.apache.ignite.internal.sql.ColumnMetadataImpl;
import org.apache.ignite.internal.sql.ResultSetMetadataImpl;
import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadata;
import org.apache.ignite.sql.ColumnMetadata;
import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.sql.ResultSetMetadata;
+import org.jetbrains.annotations.Nullable;
/**
* DdlPlan.
@@ -75,6 +77,12 @@ public class DdlPlan implements QueryPlan {
return EMPTY_PARAMETERS;
}
+ /** {@inheritDoc} */
+ @Override
+ public @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata() {
+ return null;
+ }
+
/** {@inheritDoc} */
@Override
public String toString() {
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainPlan.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainPlan.java
index b1e8d2bf206..d9cc4da9681 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainPlan.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainPlan.java
@@ -21,10 +21,12 @@ import java.util.List;
import org.apache.ignite.internal.sql.ColumnMetadataImpl;
import org.apache.ignite.internal.sql.ResultSetMetadataImpl;
import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadata;
import org.apache.ignite.internal.sql.engine.sql.IgniteSqlExplainMode;
import org.apache.ignite.sql.ColumnMetadata;
import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.sql.ResultSetMetadata;
+import org.jetbrains.annotations.Nullable;
/**
* Query explain plan.
@@ -73,6 +75,12 @@ public class ExplainPlan implements QueryPlan {
return plan.parameterMetadata();
}
+ /** {@inheritDoc} */
+ @Override
+ public @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata() {
+ return null;
+ }
+
public ExplainablePlan plan() {
return plan;
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueGetPlan.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueGetPlan.java
index 7425235dc20..0078584356d 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueGetPlan.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueGetPlan.java
@@ -47,6 +47,7 @@ import
org.apache.ignite.internal.sql.engine.exec.exp.SqlPredicate;
import org.apache.ignite.internal.sql.engine.exec.exp.SqlProjection;
import org.apache.ignite.internal.sql.engine.exec.exp.SqlRowProvider;
import org.apache.ignite.internal.sql.engine.exec.row.RowSchema;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadata;
import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueGet;
import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
import org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils;
@@ -70,16 +71,25 @@ public class KeyValueGetPlan implements ExplainablePlan,
ExecutablePlan {
private final IgniteKeyValueGet lookupNode;
private final ResultSetMetadata meta;
private final ParameterMetadata parameterMetadata;
+ @Nullable
+ private final PartitionAwarenessMetadata partitionAwarenessMetadata;
private volatile Performable<?> operation;
- KeyValueGetPlan(PlanId id, int catalogVersion, IgniteKeyValueGet
lookupNode, ResultSetMetadata meta,
- ParameterMetadata parameterMetadata) {
+ KeyValueGetPlan(
+ PlanId id,
+ int catalogVersion,
+ IgniteKeyValueGet lookupNode,
+ ResultSetMetadata meta,
+ ParameterMetadata parameterMetadata,
+ @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata
+ ) {
this.id = id;
this.catalogVersion = catalogVersion;
this.lookupNode = lookupNode;
this.meta = meta;
this.parameterMetadata = parameterMetadata;
+ this.partitionAwarenessMetadata = partitionAwarenessMetadata;
}
/** {@inheritDoc} */
@@ -106,6 +116,12 @@ public class KeyValueGetPlan implements ExplainablePlan,
ExecutablePlan {
return parameterMetadata;
}
+ /** {@inheritDoc} */
+ @Override
+ public @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata() {
+ return partitionAwarenessMetadata;
+ }
+
/** Returns a table in question. */
private IgniteTable table() {
IgniteTable table = lookupNode.getTable().unwrap(IgniteTable.class);
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueModifyPlan.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueModifyPlan.java
index eae69f78d0a..0b3bee355dc 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueModifyPlan.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueModifyPlan.java
@@ -33,6 +33,7 @@ import
org.apache.ignite.internal.sql.engine.exec.ExecutableTableRegistry;
import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
import org.apache.ignite.internal.sql.engine.exec.UpdatableTable;
import org.apache.ignite.internal.sql.engine.exec.exp.SqlRowProvider;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadata;
import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueModify;
import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
import org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils;
@@ -42,6 +43,7 @@ import org.apache.ignite.internal.sql.engine.util.Commons;
import org.apache.ignite.internal.sql.engine.util.IteratorToDataCursorAdapter;
import org.apache.ignite.internal.tx.InternalTransaction;
import org.apache.ignite.sql.ResultSetMetadata;
+import org.jetbrains.annotations.Nullable;
/**
* Plan representing simple modify operation that can be executed by Key-Value
API.
@@ -52,6 +54,8 @@ public class KeyValueModifyPlan implements ExplainablePlan,
ExecutablePlan {
private final IgniteKeyValueModify modifyNode;
private final ResultSetMetadata meta;
private final ParameterMetadata parameterMetadata;
+ @Nullable
+ private final PartitionAwarenessMetadata partitionAwarenessMetadata;
private volatile InsertExecution<?> operation;
@@ -60,13 +64,15 @@ public class KeyValueModifyPlan implements ExplainablePlan,
ExecutablePlan {
int catalogVersion,
IgniteKeyValueModify modifyNode,
ResultSetMetadata meta,
- ParameterMetadata parameterMetadata
+ ParameterMetadata parameterMetadata,
+ @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata
) {
this.id = id;
this.catalogVersion = catalogVersion;
this.modifyNode = modifyNode;
this.meta = meta;
this.parameterMetadata = parameterMetadata;
+ this.partitionAwarenessMetadata = partitionAwarenessMetadata;
}
/** {@inheritDoc} */
@@ -93,6 +99,12 @@ public class KeyValueModifyPlan implements ExplainablePlan,
ExecutablePlan {
return parameterMetadata;
}
+ /** {@inheritDoc} */
+ @Override
+ public @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata() {
+ return partitionAwarenessMetadata;
+ }
+
/** Returns a table in question. */
private IgniteTable table() {
IgniteTable table = modifyNode.getTable().unwrap(IgniteTable.class);
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KillPlan.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KillPlan.java
index 5a8511f3996..96fce97d26d 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KillPlan.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KillPlan.java
@@ -23,9 +23,11 @@ import org.apache.ignite.internal.sql.ColumnMetadataImpl;
import org.apache.ignite.internal.sql.ResultSetMetadataImpl;
import org.apache.ignite.internal.sql.engine.SqlQueryType;
import org.apache.ignite.internal.sql.engine.exec.kill.KillCommand;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadata;
import org.apache.ignite.sql.ColumnMetadata;
import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.sql.ResultSetMetadata;
+import org.jetbrains.annotations.Nullable;
/**
* KILL command execution plan.
@@ -73,6 +75,12 @@ public class KillPlan implements QueryPlan {
return EMPTY_PARAMETERS;
}
+ /** {@inheritDoc} */
+ @Override
+ public @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata() {
+ return null;
+ }
+
/** {@inheritDoc} */
@Override
public String toString() {
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepPlan.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepPlan.java
index 2adf84080e6..20ace2cce4d 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepPlan.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepPlan.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.sql.engine.prepare;
import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadata;
import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
import org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils;
import org.apache.ignite.internal.sql.engine.util.Cloner;
@@ -81,6 +82,12 @@ public class MultiStepPlan implements ExplainablePlan {
return parameterMetadata;
}
+ /** {@inheritDoc} */
+ @Override
+ public @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata() {
+ return null;
+ }
+
/** {@inheritDoc} */
@Override
public SqlQueryType type() {
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
index 4a435be21ad..98651b549be 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
@@ -60,6 +60,8 @@ import
org.apache.ignite.internal.sql.engine.SqlOperationContext;
import org.apache.ignite.internal.sql.engine.SqlQueryType;
import org.apache.ignite.internal.sql.engine.exec.kill.KillCommand;
import
org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadata;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadataExtractor;
import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueGet;
import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueModify;
import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
@@ -434,8 +436,13 @@ public class PrepareServiceImpl implements PrepareService {
int catalogVersion = ctx.catalogVersion();
if (optimizedRel instanceof IgniteKeyValueGet) {
+ IgniteKeyValueGet kvGet = (IgniteKeyValueGet) optimizedRel;
+ PartitionAwarenessMetadata partitionAwarenessMetadata =
+
PartitionAwarenessMetadataExtractor.getMetadata(kvGet);
+
return new KeyValueGetPlan(
- nextPlanId(), catalogVersion, (IgniteKeyValueGet)
optimizedRel, resultSetMetadata, parameterMetadata
+ nextPlanId(), catalogVersion, kvGet,
resultSetMetadata,
+ parameterMetadata, partitionAwarenessMetadata
);
}
@@ -502,7 +509,8 @@ public class PrepareServiceImpl implements PrepareService {
ExplainablePlan plan;
if (optimizedRel instanceof IgniteKeyValueModify) {
plan = new KeyValueModifyPlan(
- nextPlanId(), ctx.catalogVersion(), (IgniteKeyValueModify)
optimizedRel, DML_METADATA, parameterMetadata
+ nextPlanId(), ctx.catalogVersion(), (IgniteKeyValueModify)
optimizedRel, DML_METADATA,
+ parameterMetadata, null
);
} else {
plan = new MultiStepPlan(
@@ -557,8 +565,13 @@ public class PrepareServiceImpl implements PrepareService {
ExplainablePlan plan;
if (optimizedRel instanceof IgniteKeyValueModify) {
+ IgniteKeyValueModify kvModify = (IgniteKeyValueModify)
optimizedRel;
+ PartitionAwarenessMetadata partitionAwarenessMetadata =
+
PartitionAwarenessMetadataExtractor.getMetadata(kvModify);
+
plan = new KeyValueModifyPlan(
- nextPlanId(), catalogVersion,
(IgniteKeyValueModify) optimizedRel, DML_METADATA, parameterMetadata
+ nextPlanId(), catalogVersion,
(IgniteKeyValueModify) optimizedRel, DML_METADATA,
+ parameterMetadata, partitionAwarenessMetadata
);
} else {
plan = new MultiStepPlan(
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryPlan.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryPlan.java
index 4fb88e9c5a6..68138c112c4 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryPlan.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryPlan.java
@@ -17,7 +17,9 @@
package org.apache.ignite.internal.sql.engine.prepare;
+import javax.annotation.Nullable;
import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadata;
import org.apache.ignite.sql.ResultSetMetadata;
/**
@@ -44,4 +46,9 @@ public interface QueryPlan {
* Returns parameters metadata.
*/
ParameterMetadata parameterMetadata();
+
+ /**
+ * Returns partition-awareness metadata or {@code null} if it not present.
+ */
+ @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata();
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/SelectCountPlan.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/SelectCountPlan.java
index 8a19b1055f1..77e549c13b5 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/SelectCountPlan.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/SelectCountPlan.java
@@ -41,6 +41,7 @@ import
org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
import org.apache.ignite.internal.sql.engine.exec.RowHandler;
import org.apache.ignite.internal.sql.engine.exec.exp.SqlProjection;
import org.apache.ignite.internal.sql.engine.exec.row.RowSchema;
+import
org.apache.ignite.internal.sql.engine.prepare.partitionawareness.PartitionAwarenessMetadata;
import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
import org.apache.ignite.internal.sql.engine.rel.IgniteSelectCount;
import org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils;
@@ -52,6 +53,7 @@ import org.apache.ignite.internal.sql.engine.util.TypeUtils;
import org.apache.ignite.internal.tx.InternalTransaction;
import org.apache.ignite.internal.type.NativeTypes;
import org.apache.ignite.sql.ResultSetMetadata;
+import org.jetbrains.annotations.Nullable;
/**
* Plan representing a COUNT(*) query.
@@ -143,6 +145,11 @@ public class SelectCountPlan implements ExplainablePlan,
ExecutablePlan {
return parameterMetadata;
}
+ @Override
+ public @Nullable PartitionAwarenessMetadata partitionAwarenessMetadata() {
+ return null;
+ }
+
private <RowT> Function<Long, Iterator<InternalSqlRow>>
createResultProjection(ExecutionContext<RowT> ctx) {
RelDataType getCountType = new
RelDataTypeFactory.Builder(ctx.getTypeFactory())
.add("ROWCOUNT", SqlTypeName.BIGINT)
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadata.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadata.java
new file mode 100644
index 00000000000..ccc3fa81ad4
--- /dev/null
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadata.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.prepare.partitionawareness;
+
+import java.util.Arrays;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * Partition awareness metadata. The index array stores a mapping between
colocation key indexes and dynamic parameters and hash array.
+ * The mapping uses negated 1-based indexes for hashes and 0-based non-negated
indexes for dynamic parameters
+ *
+ * @see PartitionAwarenessMetadataExtractor
+ */
+public final class PartitionAwarenessMetadata {
+
+ private final int tableId;
+
+ private final int[] indexes;
+
+ private final int[] hash;
+
+ /**
+ * Constructor.
+ *
+ * @param tableId Table Id.
+ * @param indexes Mapping between positions in colocation key and dynamic
parameters.
+ * @param hash Array of computed hashes.
+ */
+ public PartitionAwarenessMetadata(int tableId, int[] indexes, int[] hash) {
+ this.tableId = tableId;
+ this.indexes = indexes;
+ this.hash = hash;
+ }
+
+ /** Return table id. */
+ public int tableId() {
+ return tableId;
+ }
+
+ /** Returns the number of colocation key columns. */
+ public int size() {
+ return indexes.length;
+ }
+
+ /**
+ * Returns a mapping between positions in colocation key columns and
dynamic parameter indices.
+ * If a colocation key column has a value, returns a negated 1-based index
into the hash array.
+ *
+ * @return Mapping.
+ */
+ public int[] indexes() {
+ return indexes;
+ }
+
+ /**
+ * Returns an array of precomputed hashes.
+ *
+ * @return Hash array.
+ */
+ public int[] hash() {
+ return hash;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return S.toString(PartitionAwarenessMetadata.class, this,
+ "indexes", Arrays.toString(indexes),
+ "hash", Arrays.toString(hash)
+ );
+ }
+}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadataExtractor.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadataExtractor.java
new file mode 100644
index 00000000000..49513a89633
--- /dev/null
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadataExtractor.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.prepare.partitionawareness;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.rex.RexDynamicParam;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueGet;
+import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueModify;
+import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Extracts partition awareness metadata from physical plans. Examples:
+ *
+ * <pre>
+ * SELECT * FROM t WHERE pk=?
+ * colocation key: [pk]
+ * =>
+ * indexes: [0], hash: []
+ *
+ * SELECT * FROM t WHERE pk1=? and pk2=?
+ * colocation key: [pk1, pk2]
+ * =>
+ * indexes: [0, 1], hash: []
+ *
+ * SELECT * FROM t WHERE pk1=? and pk2=V1 and pk3=?
+ * colocation key: [pk1, pk2, pk3]
+ * =>
+ * indexes: [0, -1, 1], hash: [hash(V1)]
+ * </pre>
+ *
+ * @see PartitionAwarenessMetadata
+ */
+public class PartitionAwarenessMetadataExtractor {
+
+ /**
+ * Extracts partition awareness metadata from the given IgniteKeyValueGet
plan.
+ *
+ * @param kv IgniteKeyValueGet Plan.
+ * @return Metadata.
+ */
+ @Nullable
+ public static PartitionAwarenessMetadata getMetadata(IgniteKeyValueGet kv)
{
+ RelOptTable optTable = kv.getTable();
+ assert optTable != null;
+
+ List<RexNode> expressions = kv.keyExpressions();
+
+ return buildMetadata(optTable, false, expressions);
+ }
+
+ /**
+ * Extracts partition awareness metadata from the given
IgniteKeyValueModify plan.
+ *
+ * @param kv IgniteKeyValueModify Plan.
+ * @return Metadata.
+ */
+ @Nullable
+ public static PartitionAwarenessMetadata getMetadata(IgniteKeyValueModify
kv) {
+ RelOptTable optTable = kv.getTable();
+ assert optTable != null;
+
+ List<RexNode> expressions = kv.expressions();
+
+ return buildMetadata(optTable, true, expressions);
+ }
+
+ private static @Nullable PartitionAwarenessMetadata buildMetadata(
+ RelOptTable optTable,
+ boolean fullRow,
+ List<RexNode> expressions
+ ) {
+ IgniteTable igniteTable = optTable.unwrap(IgniteTable.class);
+ assert igniteTable != null;
+
+ ImmutableIntList colocationKeys = igniteTable.distribution().getKeys();
+
+ // colocation key index to dynamic param index
+ int[] indexes = new int[colocationKeys.size()];
+ int[] hash = new int[0];
+
+ for (int i = 0; i < colocationKeys.size(); i++) {
+ int colIdx = colocationKeys.get(i);
+ RexNode expr;
+
+ if (fullRow) {
+ expr = expressions.get(colIdx);
+ } else {
+ int keyIdx = igniteTable.keyColumns().indexOf(colIdx);
+ expr = expressions.get(keyIdx);
+ }
+
+ if (expr instanceof RexDynamicParam) {
+ RexDynamicParam dynamicParam = (RexDynamicParam) expr;
+ indexes[i] = dynamicParam.getIndex();
+ } else {
+ return null;
+ }
+ }
+
+ return new PartitionAwarenessMetadata(igniteTable.id(), indexes, hash);
+ }
+}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadataTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadataTest.java
new file mode 100644
index 00000000000..14da955a818
--- /dev/null
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadataTest.java
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.prepare.partitionawareness;
+
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.internal.catalog.CatalogCommand;
+import org.apache.ignite.internal.catalog.CatalogManager;
+import org.apache.ignite.internal.catalog.commands.DropTableCommand;
+import org.apache.ignite.internal.catalog.descriptors.CatalogTableDescriptor;
+import org.apache.ignite.internal.sql.SqlCommon;
+import org.apache.ignite.internal.sql.engine.framework.TestBuilders;
+import org.apache.ignite.internal.sql.engine.framework.TestCluster;
+import org.apache.ignite.internal.sql.engine.framework.TestNode;
+import org.apache.ignite.internal.sql.engine.prepare.QueryPlan;
+import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Tests forr {@link PartitionAwarenessMetadata} in query plans.
+ */
+public class PartitionAwarenessMetadataTest extends BaseIgniteAbstractTest {
+
+ private static final String NODE_NAME = "N1";
+
+ private static final TestCluster CLUSTER = TestBuilders.cluster()
+ .nodes(NODE_NAME)
+ .build();
+
+ private final TestNode node = CLUSTER.node(NODE_NAME);
+
+ @BeforeAll
+ static void start() {
+ CLUSTER.start();
+ }
+
+ @AfterAll
+ static void stop() throws Exception {
+ CLUSTER.stop();
+ }
+
+ @AfterEach
+ void clearCatalog() {
+ Commons.resetFastQueryOptimizationFlag();
+
+ int version = CLUSTER.catalogManager().latestCatalogVersion();
+
+ List<CatalogCommand> commands = new ArrayList<>();
+ for (CatalogTableDescriptor table :
CLUSTER.catalogManager().catalog(version).tables()) {
+ commands.add(
+ DropTableCommand.builder()
+ .schemaName(SqlCommon.DEFAULT_SCHEMA_NAME)
+ .tableName(table.name())
+ .build()
+ );
+ }
+
+ await(CLUSTER.catalogManager().execute(commands));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "SELECT 1",
+ "KILL QUERY '1'",
+ "KILL TRANSACTION '1'",
+ "CREATE TABLE x (a INT, b INT, PRIMARY KEY (a))",
+ "SELECT * FROM table(system_range(1, 10)) WHERE x = 1",
+ "SELECT count(*) FROM t WHERE c1=?",
+ "UPDATE t SET c2=1 WHERE c1=?",
+ "DELETE FROM t WHERE c1=?",
+ })
+ public void noMetadata(String query) {
+ node.initSchema("CREATE TABLE t (c1 INT PRIMARY KEY, c2 INT)");
+
+ QueryPlan plan = node.prepare(query);
+ PartitionAwarenessMetadata metadata =
plan.partitionAwarenessMetadata();
+ assertNull(metadata);
+ }
+
+ @ParameterizedTest
+ @MethodSource("simpleKeyMetadata")
+ public void simpleKey(String query, PartitionAwarenessMetadata expected) {
+ node.initSchema("CREATE TABLE t (c1 INT PRIMARY KEY, c2 INT)");
+
+ QueryPlan plan = node.prepare(query);
+ PartitionAwarenessMetadata metadata =
plan.partitionAwarenessMetadata();
+
+ expectMedata(expected, metadata);
+ }
+
+ private static Stream<Arguments> simpleKeyMetadata() {
+ return Stream.of(
+ // KV GET
+ Arguments.of("SELECT * FROM t WHERE c1=?", dynamicParams(0)),
+ Arguments.of("SELECT * FROM t WHERE c2=? and c1=?",
dynamicParams(1)),
+ Arguments.of("SELECT * FROM t WHERE c1=1", null),
+ Arguments.of("SELECT * FROM t WHERE c1=1+1", null),
+ // the first condition goes into key lookup other into a
post-lookup filter.
+ Arguments.of("SELECT * FROM t WHERE c1=? and c1=?",
dynamicParams(0)),
+ Arguments.of("SELECT * FROM t WHERE c2=? and c1=? and c1=?",
dynamicParams(1)),
+
+ // KV PUT
+ Arguments.of("INSERT INTO t VALUES(?, ?)", dynamicParams(0)),
+ Arguments.of("INSERT INTO t VALUES(1, ?)", null),
+ Arguments.of("INSERT INTO t VALUES(1+1, ?)", null),
+ Arguments.of("INSERT INTO t(c2, c1) VALUES(?, ?)",
dynamicParams(1)),
+ Arguments.of("INSERT INTO t(c2, c1) VALUES(1, ?)",
dynamicParams(0)),
+ Arguments.of("INSERT INTO t(c2, c1) VALUES(?, 1)", null)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("shortKeyMetadata")
+ public void shortKey(String query, PartitionAwarenessMetadata expected) {
+ node.initSchema("CREATE TABLE t (c1 INT, c2 INT, c3 INT, PRIMARY KEY
(c3, c2)) COLOCATE BY (c3)");
+
+ QueryPlan plan = node.prepare(query);
+ PartitionAwarenessMetadata metadata =
plan.partitionAwarenessMetadata();
+
+ expectMedata(expected, metadata);
+ }
+
+ private static Stream<Arguments> shortKeyMetadata() {
+ return Stream.of(
+ // KV GET
+ Arguments.of("SELECT * FROM t WHERE c3=? and c2=?",
dynamicParams(0)),
+ Arguments.of("SELECT * FROM t WHERE c2=? and c3=?",
dynamicParams(1)),
+ Arguments.of("SELECT * FROM t WHERE c3=? and c2=?",
dynamicParams(0)),
+ Arguments.of("SELECT * FROM t WHERE c1=? and c2=? and c3=?",
dynamicParams(2)),
+ Arguments.of("SELECT * FROM t WHERE c3=? and c1=? and c2=?",
dynamicParams(0)),
+
+ Arguments.of("SELECT * FROM t WHERE c3=3", null),
+ Arguments.of("SELECT * FROM t WHERE c2=? and c3=3", null),
+ Arguments.of("SELECT * FROM t WHERE c1=? and c2=? and c3=3",
null),
+
+ // KV PUT
+ Arguments.of("INSERT INTO t VALUES (?, ?, ?)",
dynamicParams(2)),
+ Arguments.of("INSERT INTO t (c1, c2, c3) VALUES (?, ?, ?)",
dynamicParams(2)),
+ Arguments.of("INSERT INTO t (c3, c1, c2) VALUES (?, ?, ?)",
dynamicParams(0)),
+
+ Arguments.of("INSERT INTO t (c1, c2, c3) VALUES (?, ?, 3)",
null),
+ Arguments.of("INSERT INTO t (c1, c3, c2) VALUES (?, 3, ?)",
null),
+ Arguments.of("INSERT INTO t (c3, c1, c2) VALUES (3, ?, ?)",
null)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("compoundKeyMetadata")
+ public void compoundKey(String query, PartitionAwarenessMetadata expected)
{
+ node.initSchema("CREATE TABLE t (c1 INT, c2 INT, c3 INT, c4 INT,
PRIMARY KEY(c1, c2, c3)) COLOCATE BY (c3, c1, c2)");
+
+ QueryPlan plan = node.prepare(query);
+ PartitionAwarenessMetadata metadata =
plan.partitionAwarenessMetadata();
+
+ expectMedata(expected, metadata);
+ }
+
+ private static Stream<Arguments> compoundKeyMetadata() {
+ return Stream.of(
+ // KV GET
+ Arguments.of("SELECT * FROM t WHERE c1=? and c2=? and c3=?",
dynamicParams(2, 0, 1)),
+ Arguments.of("SELECT * FROM t WHERE c3=? and c1=? and c2=?",
dynamicParams(0, 1, 2)),
+ Arguments.of("SELECT * FROM t WHERE c3=? and c2=? and c1=?",
dynamicParams(0, 2, 1)),
+ Arguments.of("SELECT * FROM t WHERE c4=? and c1=? and c2=? and
1=? and c3=?", dynamicParams(4, 1, 2)),
+ // duplicate condition goes to a post lookup filter.
+ Arguments.of("SELECT * FROM t WHERE c1=? and c2=? and c3=? and
c2=?", dynamicParams(2, 0, 1)),
+
+ Arguments.of("SELECT * FROM t WHERE c1=1 and c2=? and c3=?",
null),
+ Arguments.of("SELECT * FROM t WHERE c1=? and c2=2 and c3=?",
null),
+ Arguments.of("SELECT * FROM t WHERE c1=? and c2=? and c3=3",
null),
+ Arguments.of("SELECT * FROM t WHERE c1=1 and c2=2 and c3=3",
null),
+
+ // KV PUT
+ Arguments.of("INSERT INTO t VALUES (?, ?, ?, ?)",
dynamicParams(2, 0, 1)),
+ Arguments.of("INSERT INTO t (c3, c2, c4, c1) VALUES (?, ?, ?,
?)", dynamicParams(0, 3, 1)),
+ Arguments.of("INSERT INTO t (c3, c2, c4, c1) VALUES (?, ?, 1,
?)", dynamicParams(0, 2, 1))
+ );
+ }
+
+ private static PartitionAwarenessMetadata dynamicParams(int...
dynamicParams) {
+ return new PartitionAwarenessMetadata(1, dynamicParams, new int[0]);
+ }
+
+ private static void expectMedata(PartitionAwarenessMetadata expected,
@Nullable PartitionAwarenessMetadata actual) {
+ if (expected == null) {
+ assertNull(actual, "Metadata should not be present");
+ } else {
+ assertNotNull(actual, "Metadata not found");
+
+ CatalogManager catalogManager = CLUSTER.catalogManager();
+ int v = catalogManager.latestCatalogVersion();
+
+ CatalogTableDescriptor table =
catalogManager.catalog(v).table("PUBLIC", "T");
+ assertNotNull(table, "table");
+
+ assertEquals(table.id(), actual.tableId(), "metadata tableId");
+ assertEquals(
+
Arrays.stream(expected.indexes()).boxed().collect(Collectors.toList()),
+
Arrays.stream(actual.indexes()).boxed().collect(Collectors.toList()),
+ "indexes"
+ );
+ }
+ }
+}