Skip to content

Commit 671e07a

Browse files
authored
SQL: Fix issue with CAST and NULL checking. (#50371)
Previously, during expression optimisation, CAST would be considered nullable if the casted expression resulted to a NULL literal, and would be always non-nullable otherwise. As a result if CASE was wrapped by a null check function like IS NULL or IS NOT NULL it was simplified to TRUE/FALSE, eliminating the actual casting operation. So in case of an expression with an erroneous casting like CAST('foo' AS DATETIME) IS NULL it would be simplified to FALSE instead of throwing an Exception signifying the attempt to cast 'foo' to a DATETIME type. CAST now always returns Nullability.UKNOWN except from the case that its result evaluated to a constant NULL, where it returns Nullability.TRUE. This way the IS NULL/IS NOT NULL don't get simplified to FALSE/TRUE and the CAST actually gets evaluated resulting to a thrown Exception. Fixes: #50191
1 parent 1bffcf6 commit 671e07a

File tree

3 files changed

+51
-8
lines changed

3 files changed

+51
-8
lines changed

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Cast.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public Nullability nullable() {
6666
if (from().isNull()) {
6767
return Nullability.TRUE;
6868
}
69-
return field().nullable();
69+
return Nullability.UNKNOWN;
7070
}
7171

7272
@Override

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/predicate/nulls/CheckNullProcessorTests.java

-6
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,9 @@
99
import org.elasticsearch.common.io.stream.Writeable.Reader;
1010
import org.elasticsearch.test.AbstractWireSerializingTestCase;
1111
import org.elasticsearch.xpack.sql.expression.function.scalar.Processors;
12-
import org.elasticsearch.xpack.sql.expression.gen.processor.ConstantProcessor;
13-
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
1412

1513
public class CheckNullProcessorTests extends AbstractWireSerializingTestCase<CheckNullProcessor> {
1614

17-
private static final Processor FALSE = new ConstantProcessor(false);
18-
private static final Processor TRUE = new ConstantProcessor(true);
19-
private static final Processor NULL = new ConstantProcessor((Object) null);
20-
2115
public static CheckNullProcessor randomProcessor() {
2216
return new CheckNullProcessor(randomFrom(CheckNullProcessor.CheckNullOperation.values()));
2317
}

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/optimizer/OptimizerTests.java

+50-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.elasticsearch.xpack.sql.optimizer;
77

88
import org.elasticsearch.test.ESTestCase;
9+
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
910
import org.elasticsearch.xpack.sql.analysis.analyzer.Analyzer.PruneSubqueryAliases;
1011
import org.elasticsearch.xpack.sql.analysis.index.EsIndex;
1112
import org.elasticsearch.xpack.sql.expression.Alias;
@@ -438,10 +439,46 @@ public void testNullFoldingIsNull() {
438439
assertEquals(false, foldNull.rule(new IsNull(EMPTY, TRUE)).fold());
439440
}
440441

442+
public void testNullFoldingIsNullWithCast() {
443+
FoldNull foldNull = new FoldNull();
444+
445+
Cast cast = new Cast(EMPTY, L("foo"), DataType.DATE);
446+
IsNull isNull = new IsNull(EMPTY, cast);
447+
final IsNull isNullOpt = (IsNull) foldNull.rule(isNull);
448+
assertEquals(isNull, isNullOpt);
449+
450+
SqlIllegalArgumentException sqlIAE =
451+
expectThrows(SqlIllegalArgumentException.class, () -> isNullOpt.asPipe().asProcessor().process(null));
452+
assertEquals("cannot cast [foo] to [date]: Text 'foo' could not be parsed at index 0", sqlIAE.getMessage());
453+
454+
isNull = new IsNull(EMPTY, new Cast(EMPTY, NULL, randomFrom(DataType.values())));
455+
assertTrue((Boolean) ((IsNull) foldNull.rule(isNull)).asPipe().asProcessor().process(null));
456+
}
457+
441458
public void testNullFoldingIsNotNull() {
442459
FoldNull foldNull = new FoldNull();
443460
assertEquals(true, foldNull.rule(new IsNotNull(EMPTY, TRUE)).fold());
444461
assertEquals(false, foldNull.rule(new IsNotNull(EMPTY, NULL)).fold());
462+
463+
Cast cast = new Cast(EMPTY, L("foo"), DataType.DATE);
464+
IsNotNull isNotNull = new IsNotNull(EMPTY, cast);
465+
assertEquals(isNotNull, foldNull.rule(isNotNull));
466+
}
467+
468+
public void testNullFoldingIsNotNullWithCast() {
469+
FoldNull foldNull = new FoldNull();
470+
471+
Cast cast = new Cast(EMPTY, L("foo"), DataType.DATE);
472+
IsNotNull isNotNull = new IsNotNull(EMPTY, cast);
473+
final IsNotNull isNotNullOpt = (IsNotNull) foldNull.rule(isNotNull);
474+
assertEquals(isNotNull, isNotNullOpt);
475+
476+
SqlIllegalArgumentException sqlIAE =
477+
expectThrows(SqlIllegalArgumentException.class, () -> isNotNullOpt.asPipe().asProcessor().process(null));
478+
assertEquals("cannot cast [foo] to [date]: Text 'foo' could not be parsed at index 0", sqlIAE.getMessage());
479+
480+
isNotNull = new IsNotNull(EMPTY, new Cast(EMPTY, NULL, randomFrom(DataType.values())));
481+
assertFalse((Boolean) ((IsNotNull) foldNull.rule(isNotNull)).asPipe().asProcessor().process(null));
445482
}
446483

447484
public void testGenericNullableExpression() {
@@ -461,6 +498,18 @@ public void testGenericNullableExpression() {
461498
assertNullLiteral(rule.rule(new RLike(EMPTY, NULL, "123")));
462499
}
463500

501+
public void testNullFoldingOnCast() {
502+
FoldNull foldNull = new FoldNull();
503+
504+
Cast cast = new Cast(EMPTY, NULL, randomFrom(DataType.values()));
505+
assertEquals(Nullability.TRUE, cast.nullable());
506+
assertNull(foldNull.rule(cast).fold());
507+
508+
cast = new Cast(EMPTY, L("foo"), DataType.DATE);
509+
assertEquals(Nullability.UNKNOWN, cast.nullable());
510+
assertEquals(cast, foldNull.rule(cast));
511+
}
512+
464513
public void testNullFoldingDoesNotApplyOnLogicalExpressions() {
465514
FoldNull rule = new FoldNull();
466515

@@ -1684,4 +1733,4 @@ public void testReplaceAttributesWithTarget() {
16841733
gt = (GreaterThan) and.left();
16851734
assertEquals(a, gt.left());
16861735
}
1687-
}
1736+
}

0 commit comments

Comments
 (0)