Skip to content

Commit b7f2e6e

Browse files
Vladsz83alex-plekhanov
authored andcommitted
IGNITE-23414 SQL Calcite: Fix cast of dynamic parameters - Fixes #11787.
Signed-off-by: Aleksey Plekhanov <plehanov.alex@gmail.com>
1 parent 1a75353 commit b7f2e6e

File tree

4 files changed

+97
-44
lines changed

4 files changed

+97
-44
lines changed

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
7171
import org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlDecimalLiteral;
7272
import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
73+
import org.apache.ignite.internal.processors.query.calcite.type.OtherType;
7374
import org.apache.ignite.internal.processors.query.calcite.util.IgniteResource;
7475
import org.apache.ignite.internal.util.typedef.F;
7576
import org.jetbrains.annotations.Nullable;
@@ -102,20 +103,25 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
102103
}
103104

104105
/** Dynamic parameters. */
105-
Object[] parameters;
106+
@Nullable private final Object[] parameters;
106107

107108
/**
108109
* Creates a validator.
109110
*
110111
* @param opTab Operator table
111112
* @param catalogReader Catalog reader
112113
* @param typeFactory Type factory
113-
* @param config Config
114+
* @param cfg Config
114115
* @param parameters Dynamic parameters
115116
*/
116-
public IgniteSqlValidator(SqlOperatorTable opTab, CalciteCatalogReader catalogReader,
117-
IgniteTypeFactory typeFactory, SqlValidator.Config config, Object[] parameters) {
118-
super(opTab, catalogReader, typeFactory, config);
117+
public IgniteSqlValidator(
118+
SqlOperatorTable opTab,
119+
CalciteCatalogReader catalogReader,
120+
IgniteTypeFactory typeFactory,
121+
SqlValidator.Config cfg,
122+
@Nullable Object[] parameters
123+
) {
124+
super(opTab, catalogReader, typeFactory, cfg);
119125

120126
this.parameters = parameters;
121127
}
@@ -534,9 +540,47 @@ private boolean isSystemFieldName(String alias) {
534540
|| QueryUtils.VAL_FIELD_NAME.equalsIgnoreCase(alias);
535541
}
536542

543+
/** {@inheritDoc} */
544+
@Override public RelDataType deriveType(SqlValidatorScope scope, SqlNode expr) {
545+
if (expr instanceof SqlDynamicParam) {
546+
RelDataType type = deriveDynamicParameterType((SqlDynamicParam)expr);
547+
548+
if (type != null)
549+
return type;
550+
}
551+
552+
return super.deriveType(scope, expr);
553+
}
554+
555+
/** @return A derived type or {@code null} if unable to determine. */
556+
@Nullable private RelDataType deriveDynamicParameterType(SqlDynamicParam node) {
557+
RelDataType type = getValidatedNodeTypeIfKnown(node);
558+
559+
// Do not clarify the widest type for any value.
560+
if (type instanceof OtherType)
561+
return type;
562+
563+
if (parameters == null || node.getIndex() >= parameters.length)
564+
return null;
565+
566+
Object val = parameters[node.getIndex()];
567+
568+
if (val == null && type != null)
569+
return type;
570+
571+
type = val == null
572+
? typeFactory.createSqlType(SqlTypeName.NULL)
573+
: typeFactory().createTypeWithNullability(typeFactory().toSql(typeFactory().createType(val.getClass())), true);
574+
575+
setValidatedNodeType(node, type);
576+
577+
return type;
578+
}
579+
537580
/** {@inheritDoc} */
538581
@Override protected void inferUnknownTypes(RelDataType inferredType, SqlValidatorScope scope, SqlNode node) {
539-
if (inferDynamicParamType(inferredType, node))
582+
if (node instanceof SqlDynamicParam && unknownType.equals(inferredType)
583+
&& deriveDynamicParameterType((SqlDynamicParam)node) != null)
540584
return;
541585

542586
if (node instanceof SqlCall) {
@@ -583,40 +627,6 @@ else if (operandTypeChecker instanceof FamilyOperandTypeChecker) {
583627
super.inferUnknownTypes(inferredType, scope, node);
584628
}
585629

586-
/**
587-
* Tries to set actual type of dynamic parameter if {@code node} is a {@link SqlDynamicParam} and if its index
588-
* is actual to {@link #parameters}.
589-
*
590-
* @return {@code True} if a new type was set. {@code False} otherwise.
591-
*/
592-
private boolean inferDynamicParamType(RelDataType inferredType, SqlNode node) {
593-
if (parameters == null || !(node instanceof SqlDynamicParam) || ((SqlDynamicParam)node).getIndex() >= parameters.length)
594-
return false;
595-
596-
Object val = parameters[((SqlDynamicParam)node).getIndex()];
597-
598-
if (val == null) {
599-
if (inferredType.equals(unknownType)) {
600-
setValidatedNodeType(node, typeFactory().createSqlType(SqlTypeName.NULL));
601-
602-
return true;
603-
}
604-
605-
return false;
606-
}
607-
608-
RelDataType valType = typeFactory().toSql(typeFactory().createType(val.getClass()));
609-
610-
assert !unknownType.equals(valType);
611-
612-
if (unknownType.equals(inferredType) || valType.getFamily().equals(inferredType.getFamily()))
613-
setValidatedNodeType(node, valType);
614-
else
615-
setValidatedNodeType(node, inferredType);
616-
617-
return true;
618-
}
619-
620630
/** {@inheritDoc} */
621631
@Override public SqlLiteral resolveLiteral(SqlLiteral literal) {
622632
if (literal instanceof SqlNumericLiteral && literal.createSqlType(typeFactory).getSqlTypeName() == SqlTypeName.BIGINT) {

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlanningContext.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
3434
import org.apache.ignite.internal.util.typedef.internal.U;
3535
import org.jetbrains.annotations.NotNull;
36+
import org.jetbrains.annotations.Nullable;
3637

3738
/**
3839
* Planning context.
@@ -45,7 +46,7 @@ public final class PlanningContext implements Context {
4546
private final String qry;
4647

4748
/** */
48-
private final Object[] parameters;
49+
@Nullable private final Object[] parameters;
4950

5051
/** */
5152
private final CancelFlag cancelFlag = new CancelFlag(new AtomicBoolean());
@@ -68,7 +69,7 @@ public final class PlanningContext implements Context {
6869
private PlanningContext(
6970
Context parentCtx,
7071
String qry,
71-
Object[] parameters,
72+
@Nullable Object[] parameters,
7273
long plannerTimeout
7374
) {
7475
this.qry = qry;
@@ -90,7 +91,7 @@ public String query() {
9091
* @return Query parameters.
9192
*/
9293
@SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
93-
public Object[] parameters() {
94+
@Nullable public Object[] parameters() {
9495
return parameters;
9596
}
9697

modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import java.time.Period;
2727
import java.util.List;
2828
import java.util.UUID;
29+
import org.apache.ignite.IgniteCache;
30+
import org.apache.ignite.internal.processors.query.IgniteSQLException;
2931
import org.apache.ignite.internal.util.typedef.F;
3032
import org.junit.Test;
3133

@@ -60,6 +62,46 @@ public void testMetadataTypesForDynamicParameters() {
6062
}
6163
}
6264

65+
/** */
66+
@Test
67+
public void testMissedValue() {
68+
assertThrows("SELECT ?", IgniteSQLException.class, "Illegal use of dynamic parameter");
69+
70+
assertThrows("SELECT ?, ?", IgniteSQLException.class, "Illegal use of dynamic parameter", "arg0");
71+
}
72+
73+
/** */
74+
@Test
75+
public void testCasts() {
76+
assertQuery("SELECT CAST(? as INTEGER)").withParams('1').returns(1).check();
77+
assertQuery("SELECT ?::INTEGER").withParams('1').returns(1).check();
78+
assertQuery("SELECT ?::VARCHAR").withParams(1).returns("1").check();
79+
assertQuery("SELECT CAST(? as VARCHAR)").withParams(1).returns("1").check();
80+
81+
createAndPopulateTable();
82+
83+
IgniteCache<Integer, Employer> cache = client.getOrCreateCache(TABLE_NAME);
84+
85+
cache.put(cache.size(), new Employer("15", 15d));
86+
87+
assertQuery("SELECT name FROM Person WHERE id=?::INTEGER").withParams("2").returns("Ilya").check();
88+
assertQuery("SELECT name FROM Person WHERE id=CAST(? as INTEGER)").withParams("2").returns("Ilya").check();
89+
90+
assertQuery("SELECT id FROM Person WHERE name=CAST(? as VARCHAR)").withParams(15).returns(5).check();
91+
assertQuery("SELECT id FROM Person WHERE name IN (?::VARCHAR)").withParams(15).returns(5).check();
92+
assertQuery("SELECT name FROM Person WHERE id IN (?::INTEGER)").withParams("2").returns("Ilya").check();
93+
assertQuery("SELECT name FROM Person WHERE id IN (?::INTEGER, ?::INTEGER)").withParams("2", "3")
94+
.returns("Ilya").returns("Roma").check();
95+
96+
assertQuery("SELECT count(*) FROM Person WHERE ? IS NOT NULL").withParams(1).returns(6L).check();
97+
assertQuery("SELECT count(*) FROM Person WHERE ? IS NOT NULL").withParams("abc").returns(6L).check();
98+
assertQuery("SELECT count(*) FROM Person WHERE ? IS NOT NULL").withParams(new Object[] { null }).returns(0L).check();
99+
100+
assertQuery("SELECT count(*) FROM Person WHERE ? IS NULL").withParams(1).returns(0L).check();
101+
assertQuery("SELECT count(*) FROM Person WHERE ? IS NULL").withParams("abc").returns(0L).check();
102+
assertQuery("SELECT count(*) FROM Person WHERE ? IS NULL").withParams(new Object[] {null}).returns(6L).check();
103+
}
104+
63105
/** */
64106
@Test
65107
public void testDynamicParameters() {

modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/JoinRehashIntegrationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public void testResourceCleanup() throws Exception {
4949
// AbstractBasicIntegrationTest.afterTest method.
5050
GridTestUtils.runMultiThreaded(() -> {
5151
for (int i = 0; i < 100; i++)
52-
sql(sql, i % 10);
52+
sql(sql, "region" + (i % 10));
5353
}, 10, "query_starter");
5454
}
5555

0 commit comments

Comments
 (0)