This is an automated email from the ASF dual-hosted git repository. ivandasch pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push: new 8f2bbede0ed IGNITE-20253 SQL Calcite: Replace placeholder values from ModifyNode (backport of IGNITE-19096) (#10906) 8f2bbede0ed is described below commit 8f2bbede0edb2a87502790fe8b09e547945e1937 Author: Ivan Daschinskiy <ivanda...@apache.org> AuthorDate: Mon Aug 21 13:54:10 2023 +0300 IGNITE-20253 SQL Calcite: Replace placeholder values from ModifyNode (backport of IGNITE-19096) (#10906) Co-authored-by: zstan <stanilov...@gmail.com> --- .../query/calcite/exec/exp/RexImpTable.java | 7 +- .../calcite/prepare/IgniteSqlToRelConvertor.java | 129 +++++++++++++++++++++ .../calcite/schema/CacheTableDescriptorImpl.java | 20 +--- .../integration/TableDmlIntegrationTest.java | 37 ++++++ 4 files changed, 172 insertions(+), 21 deletions(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java index 366dcc199e3..0ec1dfb6ba6 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java @@ -260,9 +260,6 @@ public class RexImpTable { /** */ private final Map<SqlOperator, RexCallImplementor> map = new HashMap<>(); - /** Placeholder for DEFAULT operator value. */ - public static final Object DEFAULT_VALUE_PLACEHOLDER = new Object(); - /** */ RexImpTable() { defineMethod(ROW, BuiltInMethod.ARRAY.method, NullPolicy.NONE); @@ -2513,7 +2510,7 @@ public class RexImpTable { private static class DefaultImplementor extends AbstractRexCallImplementor { /** */ DefaultImplementor() { - super(NullPolicy.NONE, false); + super(NullPolicy.NONE, true); } /** {@inheritDoc} */ @@ -2524,7 +2521,7 @@ public class RexImpTable { /** {@inheritDoc} */ @Override Expression implementSafe(final RexToLixTranslator translator, final RexCall call, final List<Expression> argValueList) { - return Expressions.field(null, RexImpTable.class, "DEFAULT_VALUE_PLACEHOLDER"); + return Expressions.constant(null); } } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlToRelConvertor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlToRelConvertor.java index eda8011066e..d72729ab3a6 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlToRelConvertor.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlToRelConvertor.java @@ -17,7 +17,9 @@ package org.apache.ignite.internal.processors.query.calcite.prepare; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptTable; @@ -28,21 +30,28 @@ import org.apache.calcite.rel.core.JoinRelType; import org.apache.calcite.rel.logical.LogicalJoin; import org.apache.calcite.rel.logical.LogicalProject; import org.apache.calcite.rel.logical.LogicalTableModify; +import org.apache.calcite.rel.logical.LogicalValues; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rex.RexInputRef; import org.apache.calcite.rex.RexNode; +import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlIdentifier; import org.apache.calcite.sql.SqlInsert; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlMerge; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.SqlUpdate; +import org.apache.calcite.sql.util.SqlShuttle; import org.apache.calcite.sql.validate.SqlValidator; +import org.apache.calcite.sql.validate.SqlValidatorScope; import org.apache.calcite.sql.validate.SqlValidatorUtil; import org.apache.calcite.sql2rel.SqlRexConvertletTable; import org.apache.calcite.sql2rel.SqlToRelConverter; import org.apache.calcite.tools.RelBuilder; +import org.apache.calcite.util.ControlFlowException; +import org.apache.calcite.util.Pair; +import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable; import org.checkerframework.checker.nullness.qual.Nullable; import static java.util.Objects.requireNonNull; @@ -51,6 +60,12 @@ import static java.util.Objects.requireNonNull; * Converts a SQL parse tree into a relational algebra operators. */ public class IgniteSqlToRelConvertor extends SqlToRelConverter { + /** */ + private final Deque<SqlCall> datasetStack = new ArrayDeque<>(); + + /** */ + private RelBuilder relBuilder; + /** */ public IgniteSqlToRelConvertor( RelOptTable.ViewExpander viewExpander, @@ -60,6 +75,8 @@ public class IgniteSqlToRelConvertor extends SqlToRelConverter { Config cfg ) { super(viewExpander, validator, catalogReader, cluster, convertletTable, cfg); + + relBuilder = config.getRelBuilderFactory().create(cluster, null); } /** {@inheritDoc} */ @@ -70,6 +87,118 @@ public class IgniteSqlToRelConvertor extends SqlToRelConverter { return super.convertQueryRecursive(qry, top, targetRowType); } + /** {@inheritDoc} */ + @Override protected RelNode convertInsert(SqlInsert call) { + datasetStack.push(call); + + RelNode rel = super.convertInsert(call); + + datasetStack.pop(); + + return rel; + } + + /** {@inheritDoc} */ + @Override public RelNode convertValues(SqlCall values, RelDataType targetRowType) { + DefaultChecker checker = new DefaultChecker(); + + boolean hasDefaults = checker.hasDefaults(values); + + if (hasDefaults) { + SqlValidatorScope scope = validator.getOverScope(values); + assert scope != null; + Blackboard bb = createBlackboard(scope, null, false); + + convertValuesImplEx(bb, values, targetRowType); + return bb.root(); + } + else { + // a bit lightweight than default processing one. + return super.convertValues(values, targetRowType); + } + } + + /** */ + private void convertValuesImplEx(Blackboard bb, SqlCall values, RelDataType targetRowType) { + SqlCall insertOp = datasetStack.peek(); + + assert insertOp instanceof SqlInsert; + assert values == ((SqlInsert)insertOp).getSource(); + + RelOptTable targetTable = getTargetTable(insertOp); + + assert targetTable != null; + + IgniteTable ignTable = targetTable.unwrap(IgniteTable.class); + + List<RelDataTypeField> tblFields = targetTable.getRowType().getFieldList(); + List<String> targetFields = targetRowType.getFieldNames(); + + int[] mapping = new int[targetFields.size()]; + + int pos = 0; + for (String fld : targetFields) { + int tblPos = 0; + for (RelDataTypeField tblFld : tblFields) { + if (tblFld.getName().equals(fld)) { + mapping[pos++] = tblPos; + break; + } + ++tblPos; + } + } + + for (SqlNode rowConstructor : values.getOperandList()) { + SqlCall rowConstructor0 = (SqlCall)rowConstructor; + + List<Pair<RexNode, String>> exps = new ArrayList<>(targetFields.size()); + + pos = 0; + for (; pos < targetFields.size(); ++pos) { + SqlNode operand = rowConstructor0.getOperandList().get(pos); + + if (operand.getKind() == SqlKind.DEFAULT) { + RexNode def = ignTable.descriptor().newColumnDefaultValue(targetTable, mapping[pos], bb); + + exps.add(Pair.of(def, SqlValidatorUtil.getAlias(operand, pos))); + } + else { + exps.add(Pair.of(bb.convertExpression(operand), SqlValidatorUtil.getAlias(operand, pos))); + } + } + + RelNode in = (null == bb.root) ? LogicalValues.createOneRow(cluster) : bb.root; + + relBuilder.push(in).project(Pair.left(exps), Pair.right(exps)); + } + + bb.setRoot(relBuilder.union(true, values.getOperandList().size()).build(), true); + } + + /** */ + private static class DefaultChecker extends SqlShuttle { + /** */ + private boolean hasDefaults(SqlCall call) { + try { + call.accept(this); + + return false; + } + catch (ControlFlowException e) { + return true; + } + } + + /** {@inheritDoc} */ + @Override public @Nullable SqlNode visit(SqlCall call) { + if (call.getKind() == SqlKind.DEFAULT) { + throw new ControlFlowException(); + } + + return super.visit(call); + } + } + /** * This method was copy-pasted from super-method except this changes: * - For updateCall we require all columns in the project and should not skip anything. diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java index 2a64f93962e..e19a5420350 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java @@ -59,7 +59,6 @@ import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor; import org.apache.ignite.internal.processors.query.QueryUtils; import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext; import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler; -import org.apache.ignite.internal.processors.query.calcite.exec.exp.RexImpTable; import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup; import org.apache.ignite.internal.processors.query.calcite.prepare.BaseDataContext; import org.apache.ignite.internal.processors.query.calcite.prepare.MappingQueryContext; @@ -355,11 +354,8 @@ public class CacheTableDescriptorImpl extends NullInitializerExpressionFactory Object key = handler.get(keyField, row); - if (key != null) { - key = replaceDefault(key, descriptors[QueryUtils.KEY_COL]); - + if (key != null) return TypeUtils.fromInternal(ectx, key, descriptors[QueryUtils.KEY_COL].storageType()); - } // skip _key and _val for (int i = 2; i < descriptors.length; i++) { @@ -368,7 +364,7 @@ public class CacheTableDescriptorImpl extends NullInitializerExpressionFactory if (!desc.field() || !desc.key()) continue; - Object fieldVal = replaceDefault(handler.get(i, row), desc); + Object fieldVal = handler.get(i, row); if (fieldVal != null) { if (key == null) @@ -397,26 +393,18 @@ public class CacheTableDescriptorImpl extends NullInitializerExpressionFactory for (int i = 2; i < descriptors.length; i++) { final CacheColumnDescriptor desc = descriptors[i]; - Object fieldVal = replaceDefault(handler.get(i, row), desc); + Object fieldVal = handler.get(i, row); if (desc.field() && !desc.key() && fieldVal != null) desc.set(val, TypeUtils.fromInternal(ectx, fieldVal, desc.storageType())); } } - else { - val = replaceDefault(val, descriptors[QueryUtils.VAL_COL]); - + else val = TypeUtils.fromInternal(ectx, val, descriptors[QueryUtils.VAL_COL].storageType()); - } return val; } - /** */ - private Object replaceDefault(Object val, ColumnDescriptor desc) { - return val == RexImpTable.DEFAULT_VALUE_PLACEHOLDER ? desc.defaultValue() : val; - } - /** */ private Object newVal(String typeName, Class<?> typeCls) throws IgniteCheckedException { GridCacheContext<?, ?> cctx = cacheContext(); diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDmlIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDmlIntegrationTest.java index 06f5b58a142..73f4d2d29ae 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDmlIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDmlIntegrationTest.java @@ -29,6 +29,7 @@ import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.binary.BinaryObjectBuilder; @@ -492,6 +493,42 @@ public class TableDmlIntegrationTest extends AbstractBasicIntegrationTest { "Object 'NON_EXISTENT_TABLE' not found"); } + /** */ + @Test + public void testInsertMultipleDefaults() { + Stream.of(true, false).forEach(withPk -> { + try { + sql("CREATE TABLE integers(i INTEGER " + (withPk ? "PRIMARY KEY" : "") + + " , col1 INTEGER DEFAULT 200, col2 INTEGER DEFAULT 300)"); + + sql("INSERT INTO integers (i) VALUES (0)"); + sql("INSERT INTO integers VALUES (1, DEFAULT, DEFAULT)"); + sql("INSERT INTO integers(i, col2) VALUES (2, DEFAULT), (3, 4), (4, DEFAULT)"); + sql("INSERT INTO integers VALUES (5, DEFAULT, DEFAULT)"); + sql("INSERT INTO integers VALUES (6, 4, DEFAULT)"); + sql("INSERT INTO integers VALUES (7, 5, 5)"); + sql("INSERT INTO integers(col1, i) VALUES (DEFAULT, 8)"); + sql("INSERT INTO integers(i, col1) VALUES (9, DEFAULT)"); + + assertQuery("SELECT i, col1, col2 FROM integers ORDER BY i") + .returns(0, 200, 300) + .returns(1, 200, 300) + .returns(2, 200, 300) + .returns(3, 200, 4) + .returns(4, 200, 300) + .returns(5, 200, 300) + .returns(6, 4, 300) + .returns(7, 5, 5) + .returns(8, 200, 300) + .returns(9, 200, 300) + .check(); + } + finally { + sql("DROP TABLE IF EXISTS integers"); + } + }); + } + /** */ @Test public void testInsertDefaultValue() {