diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java index 0c662acb7206c..ea46fb4f2193a 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java @@ -70,6 +70,7 @@ import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable; import org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlDecimalLiteral; import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory; +import org.apache.ignite.internal.processors.query.calcite.type.OtherType; import org.apache.ignite.internal.processors.query.calcite.util.IgniteResource; import org.apache.ignite.internal.util.typedef.F; import org.jetbrains.annotations.Nullable; @@ -102,7 +103,7 @@ public class IgniteSqlValidator extends SqlValidatorImpl { } /** Dynamic parameters. */ - Object[] parameters; + @Nullable private final Object[] parameters; /** * Creates a validator. @@ -110,12 +111,17 @@ public class IgniteSqlValidator extends SqlValidatorImpl { * @param opTab Operator table * @param catalogReader Catalog reader * @param typeFactory Type factory - * @param config Config + * @param cfg Config * @param parameters Dynamic parameters */ - public IgniteSqlValidator(SqlOperatorTable opTab, CalciteCatalogReader catalogReader, - IgniteTypeFactory typeFactory, SqlValidator.Config config, Object[] parameters) { - super(opTab, catalogReader, typeFactory, config); + public IgniteSqlValidator( + SqlOperatorTable opTab, + CalciteCatalogReader catalogReader, + IgniteTypeFactory typeFactory, + SqlValidator.Config cfg, + @Nullable Object[] parameters + ) { + super(opTab, catalogReader, typeFactory, cfg); this.parameters = parameters; } @@ -534,9 +540,45 @@ private boolean isSystemFieldName(String alias) { || QueryUtils.VAL_FIELD_NAME.equalsIgnoreCase(alias); } + /** {@inheritDoc} */ + @Override public RelDataType deriveType(SqlValidatorScope scope, SqlNode expr) { + if (expr instanceof SqlDynamicParam) { + RelDataType type = deriveDynamicParameterType((SqlDynamicParam)expr); + + if (type != null) + return type; + } + + return super.deriveType(scope, expr); + } + + /** @return A derived type or {@code null} if unable to determine. */ + @Nullable private RelDataType deriveDynamicParameterType(SqlDynamicParam node) { + RelDataType type = getValidatedNodeTypeIfKnown(node); + + // Do not clarify the widest type for any value. + if (type instanceof OtherType) + return type; + + if (parameters == null || node.getIndex() >= parameters.length) + return null; + + Object val = parameters[node.getIndex()]; + + if (val == null) + return null; + + type = typeFactory().createTypeWithNullability(typeFactory().toSql(typeFactory().createType(val.getClass())), true); + + setValidatedNodeType(node, type); + + return type; + } + /** {@inheritDoc} */ @Override protected void inferUnknownTypes(RelDataType inferredType, SqlValidatorScope scope, SqlNode node) { - if (inferDynamicParamType(inferredType, node)) + if (node instanceof SqlDynamicParam && unknownType.equals(inferredType) + && deriveDynamicParameterType((SqlDynamicParam)node) != null) return; if (node instanceof SqlCall) { @@ -583,40 +625,6 @@ else if (operandTypeChecker instanceof FamilyOperandTypeChecker) { super.inferUnknownTypes(inferredType, scope, node); } - /** - * Tries to set actual type of dynamic parameter if {@code node} is a {@link SqlDynamicParam} and if its index - * is actual to {@link #parameters}. - * - * @return {@code True} if a new type was set. {@code False} otherwise. - */ - private boolean inferDynamicParamType(RelDataType inferredType, SqlNode node) { - if (parameters == null || !(node instanceof SqlDynamicParam) || ((SqlDynamicParam)node).getIndex() >= parameters.length) - return false; - - Object val = parameters[((SqlDynamicParam)node).getIndex()]; - - if (val == null) { - if (inferredType.equals(unknownType)) { - setValidatedNodeType(node, typeFactory().createSqlType(SqlTypeName.NULL)); - - return true; - } - - return false; - } - - RelDataType valType = typeFactory().toSql(typeFactory().createType(val.getClass())); - - assert !unknownType.equals(valType); - - if (unknownType.equals(inferredType) || valType.getFamily().equals(inferredType.getFamily())) - setValidatedNodeType(node, valType); - else - setValidatedNodeType(node, inferredType); - - return true; - } - /** {@inheritDoc} */ @Override public SqlLiteral resolveLiteral(SqlLiteral literal) { if (literal instanceof SqlNumericLiteral && literal.createSqlType(typeFactory).getSqlTypeName() == SqlTypeName.BIGINT) { diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlanningContext.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlanningContext.java index 2f222083c6a61..d50c2f227604f 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlanningContext.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlanningContext.java @@ -33,6 +33,7 @@ import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory; import org.apache.ignite.internal.util.typedef.internal.U; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Planning context. @@ -45,7 +46,7 @@ public final class PlanningContext implements Context { private final String qry; /** */ - private final Object[] parameters; + @Nullable private final Object[] parameters; /** */ private final CancelFlag cancelFlag = new CancelFlag(new AtomicBoolean()); @@ -68,7 +69,7 @@ public final class PlanningContext implements Context { private PlanningContext( Context parentCtx, String qry, - Object[] parameters, + @Nullable Object[] parameters, long plannerTimeout ) { this.qry = qry; @@ -90,7 +91,7 @@ public String query() { * @return Query parameters. */ @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType") - public Object[] parameters() { + @Nullable public Object[] parameters() { return parameters; } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java index b3eea80c0f32f..78d825a9e3dcc 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java @@ -26,6 +26,8 @@ import java.time.Period; import java.util.List; import java.util.UUID; +import org.apache.ignite.IgniteCache; +import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.util.typedef.F; import org.junit.Test; @@ -60,6 +62,44 @@ public void testMetadataTypesForDynamicParameters() { } } + /** */ + @Test + public void testMissedValue() { + assertThrows("SELECT ?", IgniteSQLException.class, "Illegal use of dynamic parameter"); + + assertThrows("SELECT ?, ?", IgniteSQLException.class, "Illegal use of dynamic parameter", "arg0"); + } + + /** */ + @Test + public void testCasts() { + assertQuery("SELECT CAST(? as INTEGER)").withParams('1').returns(1).check(); + assertQuery("SELECT ?::INTEGER").withParams('1').returns(1).check(); + assertQuery("SELECT ?::VARCHAR").withParams(1).returns("1").check(); + assertQuery("SELECT CAST(? as VARCHAR)").withParams(1).returns("1").check(); + + IgniteCache<Integer, Employer> cache = createAndPopulateTable(); + + cache.put(cache.size(), new Employer("15", 15d)); + + assertQuery("SELECT name FROM Person WHERE id=?::INTEGER").withParams("2").returns("Ilya").check(); + assertQuery("SELECT name FROM Person WHERE id=CAST(? as INTEGER)").withParams("2").returns("Ilya").check(); + + assertQuery("SELECT id FROM Person WHERE name=CAST(? as VARCHAR)").withParams(15).returns(5).check(); + assertQuery("SELECT id FROM Person WHERE name IN (?::VARCHAR)").withParams(15).returns(5).check(); + assertQuery("SELECT name FROM Person WHERE id IN (?::INTEGER)").withParams("2").returns("Ilya").check(); + assertQuery("SELECT name FROM Person WHERE id IN (?::INTEGER, ?::INTEGER)").withParams("2", "3") + .returns("Ilya").returns("Roma").check(); + + assertQuery("SELECT count(*) FROM Person WHERE ? IS NOT NULL").withParams(1).returns(6L).check(); + assertQuery("SELECT count(*) FROM Person WHERE ? IS NOT NULL").withParams("abc").returns(6L).check(); + assertQuery("SELECT count(*) FROM Person WHERE ? IS NOT NULL").withParams(new Object[] { null }).returns(0L).check(); + + assertQuery("SELECT count(*) FROM Person WHERE ? IS NULL").withParams(1).returns(0L).check(); + assertQuery("SELECT count(*) FROM Person WHERE ? IS NULL").withParams("abc").returns(0L).check(); + assertQuery("SELECT count(*) FROM Person WHERE ? IS NULL").withParams(new Object[] {null}).returns(6L).check(); + } + /** */ @Test public void testDynamicParameters() { diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/JoinRehashIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/JoinRehashIntegrationTest.java index 2370eea664f0c..928ca43049e5f 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/JoinRehashIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/JoinRehashIntegrationTest.java @@ -49,7 +49,7 @@ public void testResourceCleanup() throws Exception { // AbstractBasicIntegrationTest.afterTest method. GridTestUtils.runMultiThreaded(() -> { for (int i = 0; i < 100; i++) - sql(sql, i % 10); + sql(sql, "region" + (i % 10)); }, 10, "query_starter"); }