From ae59263247ef01a2edff2734e23db19710bdc7ae Mon Sep 17 00:00:00 2001 From: Kirill Tkalenko Date: Mon, 24 Aug 2026 14:12:43 +0300 Subject: [PATCH 1/2] Wip --- .../calcite/exec/exp/IgniteRexBuilder.java | 11 +++++ .../calcite/prepare/IgniteSqlValidator.java | 4 ++ .../CacheWithInterceptorIntegrationTest.java | 16 +++---- .../calcite/integration/DataTypesTest.java | 42 +++++++++++++++++++ .../calcite/integration/FunctionsTest.java | 2 +- .../integration/StdSqlOperatorsTest.java | 2 +- 6 files changed, 67 insertions(+), 10 deletions(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteRexBuilder.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteRexBuilder.java index d4e2debbcad6f..7efcc8f4b9618 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteRexBuilder.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteRexBuilder.java @@ -23,8 +23,10 @@ import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.sql.SqlUtil; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.type.SqlTypeUtil; +import org.apache.calcite.util.NlsString; import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils; import org.jetbrains.annotations.Nullable; @@ -36,6 +38,15 @@ public IgniteRexBuilder(RelDataTypeFactory typeFactory) { super(typeFactory); } + /** {@inheritDoc} */ + @Override public RexLiteral makeCharLiteral(NlsString str) { + // VALUES conversion can retain the original character literal after validation. + if (str.getValue().isEmpty()) + return makeNullLiteral(SqlUtil.createNlsStringType(getTypeFactory(), str)); + + return super.makeCharLiteral(str); + } + /** {@inheritDoc} */ @Override protected RexLiteral makeLiteral(@Nullable Comparable o, RelDataType type, SqlTypeName typeName) { if (o != null && typeName == SqlTypeName.DECIMAL) { 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 b3c643b7b9b14..afdd452146773 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 @@ -813,6 +813,10 @@ else if (operandTypeChecker instanceof FamilyOperandTypeChecker) { /** {@inheritDoc} */ @Override public SqlLiteral resolveLiteral(SqlLiteral literal) { + // Replace before type inference so an empty character literal has a nullable SQL type. + if (literal.getTypeName() == SqlTypeName.CHAR && literal.getValueAs(String.class).isEmpty()) + return SqlLiteral.createNull(literal.getParserPosition()); + if (literal instanceof SqlNumericLiteral && literal.createSqlType(typeFactory).getSqlTypeName() == SqlTypeName.BIGINT) { BigDecimal bd = literal.getValueAs(BigDecimal.class); diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CacheWithInterceptorIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CacheWithInterceptorIntegrationTest.java index 94342b8d58895..7928c515aa5d2 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CacheWithInterceptorIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CacheWithInterceptorIntegrationTest.java @@ -150,34 +150,34 @@ public void testInterceptorUnwrapValIfNeeded() throws Exception { try (Transaction tx = client.transactions().txStart(PESSIMISTIC, READ_COMMITTED)) { cache.query(new SqlFieldsQuery("INSERT INTO PURE(id, name) VALUES (1, 'val')")).getAll(); - cache.query(new SqlFieldsQuery("UPDATE PURE SET name = '' WHERE id = 1")).getAll(); + cache.query(new SqlFieldsQuery("UPDATE PURE SET name = 'updated' WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("DELETE FROM PURE WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("INSERT INTO COMPLEX(id, name) VALUES (1, 'val')")).getAll(); - cache.query(new SqlFieldsQuery("UPDATE COMPLEX SET name = '' WHERE id = 1")).getAll(); + cache.query(new SqlFieldsQuery("UPDATE COMPLEX SET name = 'updated' WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("DELETE FROM COMPLEX WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("INSERT INTO CITY(id, name) VALUES (1, 'val')")).getAll(); - cache.query(new SqlFieldsQuery("UPDATE CITY SET name = '' WHERE id = 1")).getAll(); + cache.query(new SqlFieldsQuery("UPDATE CITY SET name = 'updated' WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("DELETE FROM CITY WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("INSERT INTO PERSON(id, name, city_id) VALUES (1, 'val', 1)")).getAll(); - cache.query(new SqlFieldsQuery("UPDATE PERSON SET name = '' WHERE id = 1")).getAll(); + cache.query(new SqlFieldsQuery("UPDATE PERSON SET name = 'updated' WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("DELETE FROM PERSON WHERE id = 1")).getAll(); tx.commit(); } cache.query(new SqlFieldsQuery("INSERT INTO PURE(id, name) VALUES (1, 'val')")).getAll(); - cache.query(new SqlFieldsQuery("UPDATE PURE SET name = '' WHERE id = 1")).getAll(); + cache.query(new SqlFieldsQuery("UPDATE PURE SET name = 'updated' WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("DELETE FROM PURE WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("INSERT INTO COMPLEX(id, name) VALUES (1, 'val')")).getAll(); - cache.query(new SqlFieldsQuery("UPDATE COMPLEX SET name = '' WHERE id = 1")).getAll(); + cache.query(new SqlFieldsQuery("UPDATE COMPLEX SET name = 'updated' WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("DELETE FROM COMPLEX WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("INSERT INTO CITY(id, name) VALUES (1, 'val')")).getAll(); - cache.query(new SqlFieldsQuery("UPDATE CITY SET name = '' WHERE id = 1")).getAll(); + cache.query(new SqlFieldsQuery("UPDATE CITY SET name = 'updated' WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("DELETE FROM CITY WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("INSERT INTO PERSON(id, name, city_id) VALUES (1, 'val', 1)")).getAll(); @@ -185,7 +185,7 @@ public void testInterceptorUnwrapValIfNeeded() throws Exception { cache.query(new SqlFieldsQuery("DELETE FROM PERSON WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("INSERT INTO PERSON_ATOMIC(id, name, city_id) VALUES (1, 'val', 1)")).getAll(); - cache.query(new SqlFieldsQuery("UPDATE PERSON_ATOMIC SET name = '' WHERE id = 1")).getAll(); + cache.query(new SqlFieldsQuery("UPDATE PERSON_ATOMIC SET name = 'updated' WHERE id = 1")).getAll(); cache.query(new SqlFieldsQuery("DELETE FROM PERSON_ATOMIC WHERE id = 1")).getAll(); } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java index e3f9d5e7cd935..6304094e72c5a 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java @@ -453,6 +453,48 @@ public void testUnsupportedTypes() { "'TIMESTAMP WITH LOCAL TIME ZONE' is not supported."); } + /** */ + @Test + public void testEmptyStringLiteralIsNull() { + executeSql("CREATE TABLE empty_string_test(id INT PRIMARY KEY, val VARCHAR) WITH " + atomicity()); + + assertQuery("SELECT '', '' IS NULL, COALESCE('', 'fallback')") + .returns(null, true, "fallback") + .check(); + + assertQuery("SELECT '' = ''") + .returns((Object)null) + .check(); + + assertQuery("SELECT 'value' = '', 'value' <> '', '' = 'value', '' <> 'value'") + .returns(null, null, null, null) + .check(); + + executeSql("INSERT INTO empty_string_test VALUES (1, '')"); + executeSql("INSERT INTO empty_string_test VALUES (2, 'value')"); + + assertQuery("SELECT id, val, val IS NULL FROM empty_string_test ORDER BY id") + .returns(1, null, true) + .returns(2, "value", false) + .check(); + + assertQuery("SELECT id FROM empty_string_test WHERE '' = ''") + .resultSize(0) + .check(); + + assertQuery("SELECT id FROM empty_string_test WHERE val = ''") + .resultSize(0) + .check(); + + assertQuery("SELECT id FROM empty_string_test WHERE val <> ''") + .resultSize(0) + .check(); + + assertQuery("SELECT id FROM empty_string_test WHERE val IS NOT NULL") + .returns(2) + .check(); + } + /** Cache API - SQL API cross check. */ @Test public void testBinaryCache() { diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/FunctionsTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/FunctionsTest.java index 2e52c5a34797a..bafb2bd596949 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/FunctionsTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/FunctionsTest.java @@ -258,7 +258,7 @@ public void testReplace() { assertQuery("SELECT REPLACE(NULL, '1', '5')").returns(NULL_RESULT).check(); assertQuery("SELECT REPLACE('1', NULL, '5')").returns(NULL_RESULT).check(); assertQuery("SELECT REPLACE('11', '1', NULL)").returns(NULL_RESULT).check(); - assertQuery("SELECT REPLACE('11', '1', '')").returns("").check(); + assertQuery("SELECT REPLACE('11', '1', '')").returns(NULL_RESULT).check(); assertQuery("SELECT REPLACE('aA', 'a', 'b')").returns("bA").check(); assertQuery("SELECT REPLACE('aA', 'A', 'b')").returns("ab").check(); } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/StdSqlOperatorsTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/StdSqlOperatorsTest.java index 2f78ee9c6f724..5b73a0a4c0f80 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/StdSqlOperatorsTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/StdSqlOperatorsTest.java @@ -308,7 +308,7 @@ public void testOtherFunctions() { assertExpression("DECODE(1, 1, 1, 2)").returns(1).check(); assertExpression("LEAST('a', 'b')").returns("a").check(); assertExpression("GREATEST('a', 'b')").returns("b").check(); - assertExpression("COMPRESS('')::VARCHAR").returns("").check(); + assertExpression("COMPRESS('')::VARCHAR").returns(NULL_RESULT).check(); assertExpression("OCTET_LENGTH(x'01')").returns(1).check(); assertExpression("CAST(INTERVAL 1 SECONDS AS INT)").returns(1).check(); // Converted to REINTERPRED. } From 1f60c8023c8690d8d8b9bff44c5bbc17ef1161ce Mon Sep 17 00:00:00 2001 From: Kirill Tkalenko Date: Mon, 24 Aug 2026 18:05:17 +0300 Subject: [PATCH 2/2] Wip --- .../query/calcite/exec/TableFunctionScan.java | 27 +++++++++- .../calcite/exec/exp/IgniteRexBuilder.java | 30 +++++++++++ .../calcite/exec/exp/IgniteSqlFunctions.java | 50 +++++++++++++++++++ .../query/calcite/exec/exp/RexImpTable.java | 18 +++---- .../calcite/exec/exp/RexToLixTranslator.java | 23 +++++++-- .../calcite/prepare/IgniteSqlValidator.java | 12 ++++- .../sql/fun/IgniteOwnSqlOperatorTable.java | 18 +++++++ .../query/calcite/util/IgniteMethod.java | 25 ++++++++++ .../calcite/integration/DataTypesTest.java | 8 +++ .../calcite/integration/FunctionsTest.java | 2 + .../UserDefinedFunctionsIntegrationTest.java | 26 ++++++++++ .../aggregates/test_aggregate_types.test | 4 +- .../test_aggregate_types_scalar.test | 2 +- .../sql/function/string/regex_search.test | 9 ++-- .../test/sql/function/string/test_ascii.test | 2 +- .../sql/function/string/test_caseconvert.test | 5 +- .../sql/function/string/test_char_length.test | 5 +- .../sql/function/string/test_compress.test | 2 +- .../sql/function/string/test_initcap.test | 2 +- .../test/sql/function/string/test_like.test | 5 +- .../test/sql/function/string/test_repeat.test | 5 +- .../sql/function/string/test_replace.test | 7 ++- .../sql/function/string/test_reverse.test | 5 +- .../test/sql/function/string/test_trim.test | 19 ++++--- .../src/test/sql/types/blob/test_blob.test | 7 ++- 25 files changed, 259 insertions(+), 59 deletions(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java index b29f91d6a7fe8..0e203acf0c99a 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.function.Supplier; import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.type.SqlTypeUtil; import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory; import org.apache.ignite.internal.util.typedef.F; @@ -36,6 +37,9 @@ public class TableFunctionScan implements Iterable { /** */ private final RowFactory rowFactory; + /** Character columns that require Oracle-compatible empty string handling. */ + private final boolean[] characterColumns; + /** */ public TableFunctionScan( RelDataType rowType, @@ -45,6 +49,11 @@ public TableFunctionScan( this.rowType = rowType; this.dataSupplier = dataSupplier; this.rowFactory = rowFactory; + + characterColumns = new boolean[rowType.getFieldCount()]; + + for (int i = 0; i < characterColumns.length; i++) + characterColumns[i] = SqlTypeUtil.isCharacter(rowType.getFieldList().get(i).getType()); } /** {@inheritDoc} */ @@ -66,6 +75,22 @@ private Row convertToRow(Object rowContainer) { + "] doesn't match defined columns number [" + rowType.getFieldCount() + "]."); } - return rowFactory.create(rowArr); + return rowFactory.create(nullIfEmpty(rowArr)); + } + + /** Converts empty strings returned for character columns to {@code null}. */ + private Object[] nullIfEmpty(Object[] row) { + Object[] normalizedRow = row; + + for (int i = 0; i < characterColumns.length; i++) { + if (characterColumns[i] && "".equals(row[i])) { + if (normalizedRow == row) + normalizedRow = row.clone(); + + normalizedRow[i] = null; + } + } + + return normalizedRow; } } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteRexBuilder.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteRexBuilder.java index 7efcc8f4b9618..d73d74c87cefd 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteRexBuilder.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteRexBuilder.java @@ -19,11 +19,16 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.List; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.SqlUtil; +import org.apache.calcite.sql.parser.SqlParserPos; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.type.SqlTypeUtil; import org.apache.calcite.util.NlsString; @@ -31,6 +36,8 @@ import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils; import org.jetbrains.annotations.Nullable; +import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.NULL_IF_EMPTY; + /** */ public class IgniteRexBuilder extends RexBuilder { /** */ @@ -38,6 +45,29 @@ public IgniteRexBuilder(RelDataTypeFactory typeFactory) { super(typeFactory); } + /** {@inheritDoc} */ + @Override public RexNode makeCall(SqlParserPos pos, RelDataType type, SqlOperator op, List exprs) { + return nullIfEmptyResult(pos, super.makeCall(pos, type, op, exprs), op); + } + + /** {@inheritDoc} */ + @Override public RexNode makeCall(SqlParserPos pos, SqlOperator op, List exprs) { + return nullIfEmptyResult(pos, super.makeCall(pos, op, exprs), op); + } + + /** Wraps a character expression so an empty result is represented as {@code null}. */ + private RexNode nullIfEmptyResult(SqlParserPos pos, RexNode call, SqlOperator op) { + if (op == NULL_IF_EMPTY || op.getKind() == SqlKind.AS || op.getKind() == SqlKind.CAST + || op.getKind() == SqlKind.DESCENDING || op.getKind() == SqlKind.NULLS_FIRST + || op.getKind() == SqlKind.NULLS_LAST + || !SqlTypeUtil.isCharacter(call.getType())) + return call; + + RelDataType type = getTypeFactory().createTypeWithNullability(call.getType(), true); + + return super.makeCall(pos, type, NULL_IF_EMPTY, List.of(call)); + } + /** {@inheritDoc} */ @Override public RexLiteral makeCharLiteral(NlsString str) { // VALUES conversion can retain the original character literal after validation. diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java index be9dc99330df0..80a0e489e6f73 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java @@ -51,6 +51,9 @@ public class IgniteSqlFunctions { /** */ private static final int DFLT_NUM_PRECISION = IgniteTypeSystem.INSTANCE.getDefaultPrecision(SqlTypeName.DECIMAL); + /** POSIX regular expression implementation. */ + private static final SqlFunctions.PosixRegexFunction POSIX_REGEX = new SqlFunctions.PosixRegexFunction(); + /** * Default constructor. */ @@ -73,6 +76,11 @@ public static String toString(BigDecimal x) { return x == null ? null : x.toPlainString(); } + /** Converts an empty character value to {@code null}. */ + public static @Nullable String nullIfEmpty(@Nullable String val) { + return val == null || val.isEmpty() ? null : val; + } + /** CAST(DOUBLE AS DECIMAL). */ public static BigDecimal toBigDecimal(double val, int precision, int scale) { return removeDefaultScale(precision, scale, toBigDecimal(BigDecimal.valueOf(val), precision, scale)); @@ -167,6 +175,48 @@ public static String toString(ByteString b) { return b == null ? null : new String(b.getBytes(), Commons.typeFactory().getDefaultCharset()); } + /** Case-sensitive POSIX regular expression match. */ + public static @Nullable Boolean posixRegexCaseSensitive(@Nullable String s, @Nullable String regex) { + return posixRegex(s, regex, true, false); + } + + /** Case-insensitive POSIX regular expression match. */ + public static @Nullable Boolean posixRegexCaseInsensitive(@Nullable String s, @Nullable String regex) { + return posixRegex(s, regex, false, false); + } + + /** Negated case-sensitive POSIX regular expression match. */ + public static @Nullable Boolean negatedPosixRegexCaseSensitive(@Nullable String s, @Nullable String regex) { + return posixRegex(s, regex, true, true); + } + + /** Negated case-insensitive POSIX regular expression match. */ + public static @Nullable Boolean negatedPosixRegexCaseInsensitive(@Nullable String s, @Nullable String regex) { + return posixRegex(s, regex, false, true); + } + + /** + * POSIX regular expression match. + * + *

The pattern is evaluated even when the source is {@code null}. This preserves an invalid-pattern error while + * the result of a valid match with a null operand remains {@code null}. + */ + private static @Nullable Boolean posixRegex( + @Nullable String s, + @Nullable String regex, + boolean caseSensitive, + boolean negate + ) { + if (regex == null) + return null; + + boolean matches = caseSensitive + ? POSIX_REGEX.posixRegexSensitive(s == null ? "" : s, regex) + : POSIX_REGEX.posixRegexInsensitive(s == null ? "" : s, regex); + + return s == null ? null : matches != negate; + } + /** LEAST2. */ public static Object least2(Object arg0, Object arg1) { return leastOrGreatest(true, arg0, arg1); 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 e3e3ce5be27cd..8dc7417bc9714 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 @@ -266,6 +266,7 @@ import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.GREATEST2; import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.LEAST2; import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.NULL_BOUND; +import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.NULL_IF_EMPTY; import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.QUERY_ENGINE; import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.SYSTEM_RANGE; import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.TYPEOF; @@ -324,6 +325,7 @@ public class RexImpTable { defineMethod(SOUNDEX, BuiltInMethod.SOUNDEX.method, NullPolicy.STRICT); defineMethod(DIFFERENCE, BuiltInMethod.DIFFERENCE.method, NullPolicy.STRICT); defineMethod(REVERSE, BuiltInMethod.REVERSE.method, NullPolicy.STRICT); + defineMethod(NULL_IF_EMPTY, IgniteMethod.NULL_IF_EMPTY.method(), NullPolicy.NONE); map.put(TRIM, new TrimImplementor()); @@ -455,16 +457,12 @@ public class RexImpTable { BuiltInMethod.SIMILAR_ESCAPE.method); // POSIX REGEX - ReflectiveImplementor insensitiveImplementor = - defineReflective(POSIX_REGEX_CASE_INSENSITIVE, - BuiltInMethod.POSIX_REGEX_INSENSITIVE.method); - ReflectiveImplementor sensitiveImplementor = - defineReflective(POSIX_REGEX_CASE_SENSITIVE, - BuiltInMethod.POSIX_REGEX_SENSITIVE.method); - map.put(NEGATED_POSIX_REGEX_CASE_INSENSITIVE, - NotImplementor.of(insensitiveImplementor)); - map.put(NEGATED_POSIX_REGEX_CASE_SENSITIVE, - NotImplementor.of(sensitiveImplementor)); + defineMethod(POSIX_REGEX_CASE_INSENSITIVE, IgniteMethod.POSIX_REGEX_CASE_INSENSITIVE.method(), NullPolicy.NONE); + defineMethod(POSIX_REGEX_CASE_SENSITIVE, IgniteMethod.POSIX_REGEX_CASE_SENSITIVE.method(), NullPolicy.NONE); + defineMethod(NEGATED_POSIX_REGEX_CASE_INSENSITIVE, + IgniteMethod.NEGATED_POSIX_REGEX_CASE_INSENSITIVE.method(), NullPolicy.NONE); + defineMethod(NEGATED_POSIX_REGEX_CASE_SENSITIVE, + IgniteMethod.NEGATED_POSIX_REGEX_CASE_SENSITIVE.method(), NullPolicy.NONE); defineReflective(REGEXP_REPLACE_3, BuiltInMethod.REGEXP_REPLACE3.method, BuiltInMethod.REGEXP_REPLACE4.method, diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToLixTranslator.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToLixTranslator.java index f11b432d1f010..2938b8ae50a4d 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToLixTranslator.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToLixTranslator.java @@ -206,7 +206,7 @@ Expression translate(RexNode expr, Type storageType) { Expression translate(RexNode expr, RexImpTable.NullAs nullAs, Type storageType) { currentStorageType = storageType; - final Result result = expr.accept(this); + final Result result = normalizeStringResult(expr, expr.accept(this)); final Expression translated = ConverterUtils.toInternal(result.valueVariable, storageType); assert translated != null; @@ -831,7 +831,24 @@ public List translateList(List operandList, * @return Whether expression is nullable */ public boolean isNullable(RexNode e) { - return e.getType().isNullable(); + return SqlTypeUtil.isCharacter(e.getType()) || e.getType().isNullable(); + } + + /** Converts an empty result of a character expression to {@code null}. */ + private Result normalizeStringResult(RexNode node, Result result) { + if (!SqlTypeUtil.isCharacter(node.getType()) || result.valueVariable.getType() != String.class) + return result; + + final ParameterExpression valVariable = Expressions.parameter( + String.class, list.newName(result.valueVariable.name + "_null_if_empty")); + list.add(Expressions.declare(Modifier.FINAL, valVariable, + Expressions.call(IgniteMethod.NULL_IF_EMPTY.method(), result.valueVariable))); + + final ParameterExpression isNullVariable = Expressions.parameter( + Boolean.TYPE, list.newName(result.isNullVariable.name + "_null_if_empty")); + list.add(Expressions.declare(Modifier.FINAL, isNullVariable, checkNull(valVariable))); + + return new Result(isNullVariable, valVariable); } /** */ @@ -1064,7 +1081,7 @@ private static Result implementCallOperand(final RexNode operand, final Type storageType, final RexToLixTranslator translator) { final Type originalStorageType = translator.currentStorageType; translator.currentStorageType = storageType; - Result operandResult = operand.accept(translator); + Result operandResult = translator.normalizeStringResult(operand, operand.accept(translator)); if (storageType != null) operandResult = translator.toInnerStorageType(operandResult, storageType); translator.currentStorageType = originalStorageType; 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 afdd452146773..d7b4b0da24491 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 @@ -61,6 +61,7 @@ import org.apache.calcite.sql.type.SqlTypeCoercionRule; import org.apache.calcite.sql.type.SqlTypeFamily; import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.sql.type.SqlTypeUtil; import org.apache.calcite.sql.validate.SelectScope; import org.apache.calcite.sql.validate.SqlQualified; import org.apache.calcite.sql.validate.SqlValidator; @@ -721,7 +722,16 @@ private IgniteTypeFactory typeFactory() { return type; } - return super.deriveType(scope, expr); + RelDataType type = super.deriveType(scope, expr); + + if (expr instanceof SqlCall && !((SqlCall)expr).getOperator().isAggregator() + && expr.getKind() != SqlKind.AS && expr.getKind() != SqlKind.CAST + && SqlTypeUtil.isCharacter(type) && !type.isNullable()) { + type = typeFactory.createTypeWithNullability(type, true); + setValidatedNodeType(expr, type); + } + + return type; } /** */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteOwnSqlOperatorTable.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteOwnSqlOperatorTable.java index d5a7dd434e332..6c5f561d5248d 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteOwnSqlOperatorTable.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteOwnSqlOperatorTable.java @@ -16,6 +16,8 @@ */ package org.apache.ignite.internal.processors.query.calcite.sql.fun; +import java.util.function.Supplier; +import org.apache.calcite.plan.Strong; import org.apache.calcite.sql.SqlAggFunction; import org.apache.calcite.sql.SqlFunction; import org.apache.calcite.sql.SqlFunctionCategory; @@ -96,6 +98,22 @@ public class IgniteOwnSqlOperatorTable extends ReflectiveSqlOperatorTable { OperandTypes.NILADIC, SqlFunctionCategory.SYSTEM); + /** Converts an empty character expression result to {@code null}. */ + public static final SqlFunction NULL_IF_EMPTY = + new SqlFunction( + "$NULL_IF_EMPTY", + SqlKind.OTHER_FUNCTION, + ReturnTypes.ARG0_FORCE_NULLABLE, + null, + OperandTypes.CHARACTER, + SqlFunctionCategory.SYSTEM + ) { + /** {@inheritDoc} */ + @Override public Supplier getStrongPolicyInference() { + return () -> Strong.Policy.AS_IS; + } + }; + /** * Least of two arguments. Unlike LEAST, which is converted to CASE WHEN THEN END clause, this function * is natively implemented. diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMethod.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMethod.java index f4773275fe6cb..c453ebcc7fbe0 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMethod.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMethod.java @@ -89,6 +89,31 @@ public enum IgniteMethod { /** See {@link IgniteSqlFunctions#toByteString(String)} */ STRING_TO_BYTESTRING(IgniteSqlFunctions.class, "toByteString", String.class), + /** See {@link IgniteSqlFunctions#nullIfEmpty(String)} */ + NULL_IF_EMPTY(IgniteSqlFunctions.class, "nullIfEmpty", String.class), + + /** See {@link IgniteSqlFunctions#posixRegexCaseSensitive(String, String)} */ + POSIX_REGEX_CASE_SENSITIVE(IgniteSqlFunctions.class, "posixRegexCaseSensitive", String.class, String.class), + + /** See {@link IgniteSqlFunctions#posixRegexCaseInsensitive(String, String)} */ + POSIX_REGEX_CASE_INSENSITIVE(IgniteSqlFunctions.class, "posixRegexCaseInsensitive", String.class, String.class), + + /** See {@link IgniteSqlFunctions#negatedPosixRegexCaseSensitive(String, String)} */ + NEGATED_POSIX_REGEX_CASE_SENSITIVE( + IgniteSqlFunctions.class, + "negatedPosixRegexCaseSensitive", + String.class, + String.class + ), + + /** See {@link IgniteSqlFunctions#negatedPosixRegexCaseInsensitive(String, String)} */ + NEGATED_POSIX_REGEX_CASE_INSENSITIVE( + IgniteSqlFunctions.class, + "negatedPosixRegexCaseInsensitive", + String.class, + String.class + ), + /** See {@link IgniteSqlFunctions#least2(Object, Object)} */ LEAST2(IgniteSqlFunctions.class, "least2", Object.class, Object.class), diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java index 6304094e72c5a..4b0597b3972bc 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java @@ -470,6 +470,14 @@ public void testEmptyStringLiteralIsNull() { .returns(null, null, null, null) .check(); + assertQuery("SELECT LTRIM(' '), RTRIM(' '), TRIM(' '), REPEAT('value', -1)") + .returns(null, null, null, null) + .check(); + + assertQuery("SELECT LTRIM(' ') IS NULL, LTRIM(' value')") + .returns(true, "value") + .check(); + executeSql("INSERT INTO empty_string_test VALUES (1, '')"); executeSql("INSERT INTO empty_string_test VALUES (2, 'value')"); diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/FunctionsTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/FunctionsTest.java index bafb2bd596949..7c23836bce4b4 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/FunctionsTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/FunctionsTest.java @@ -429,6 +429,8 @@ public void testRegex() { assertQuery("SELECT 'abcd' !~* null").returns(NULL_RESULT).check(); assertQuery("SELECT null !~* null").returns(NULL_RESULT).check(); assertThrows("SELECT 'abcd' ~ '[a-z'", IgniteSQLException.class, null); + assertThrows("SELECT '' ~ '[a-z'", IgniteSQLException.class, null); + assertThrows("SELECT CAST(NULL AS VARCHAR) ~ '[a-z'", IgniteSQLException.class, null); } /** */ diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java index 278b3bac52f7d..f937bd6a01709 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java @@ -207,6 +207,9 @@ public void testFunctions() throws Exception { assertQuery("SELECT EMP2_SCHEMA.add(1, 2, 3, 4)").returns(10).check(); assertQuery("SELECT sq(4)").returns(16d).check(); assertQuery("SELECT echo('test')").returns("test").check(); + assertQuery("SELECT stringIsNull(''), emptyString()") + .returns(true, null) + .check(); assertQuery("SELECT sq(salary) FROM emp3").returns(10_000d).returns(40_000d).check(); assertQuery("SELECT echo(name) FROM emp3").returns("Igor3").returns("Roman3").check(); assertQuery("SELECT sq(salary) FROM EMP2_SCHEMA.emp2").returns(100d).returns(400d).check(); @@ -312,6 +315,10 @@ public void testTableFunctions() throws Exception { .returns(1, 1, 2.0d, 2.0d) .check(); + assertQuery("SELECT * FROM stringNulls('')") + .returns(true, null) + .check(); + assertQuery("SELECT * from emp WHERE SALARY >= (SELECT COL_1 from collectionRow(1) WHERE COL_2=3)") .returns("Roman1", 2d) .check(); @@ -467,6 +474,13 @@ public static Collection> boxingUnboxing(int i1, Integer i2, double d1, return List.of(Arrays.asList(i1, i2, d1, d2)); } + /** Checks empty-string input and output normalization. */ + @QuerySqlTableFunction(columnTypes = {boolean.class, String.class}, + columnNames = {"INPUT_IS_NULL", "EMPTY_RESULT"}) + public static Collection> stringNulls(String val) { + return List.of(Arrays.asList(val == null, "")); + } + /** Alias test. */ @QuerySqlTableFunction(columnTypes = {int.class, int.class, int.class}, columnNames = {"COL_1", "COL_2", "COL_3"}, alias = "aliasedName") @@ -673,6 +687,18 @@ public static String echo(String s) { return s; } + /** Checks that an empty SQL string is passed as {@code null}. */ + @QuerySqlFunction + public static boolean stringIsNull(String s) { + return s == null; + } + + /** Returns an empty string to check result normalization. */ + @QuerySqlFunction + public static String emptyString() { + return ""; + } + /** The signature interferes with aliased {@link OtherFunctionsLibrary2#sameSign2(int)}. */ @QuerySqlFunction public static String sameSign(int v) { diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_aggregate_types.test b/modules/calcite/src/test/sql/aggregate/aggregates/test_aggregate_types.test index 3e66f05b257fb..5aedf37abcc6e 100644 --- a/modules/calcite/src/test/sql/aggregate/aggregates/test_aggregate_types.test +++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_aggregate_types.test @@ -30,8 +30,8 @@ query TTTT SELECT STRING_AGG(s, ' ' ORDER BY s ASC), STRING_AGG(s, '' ORDER BY s ASC), STRING_AGG('', ''), STRING_AGG('hello', ' ') FROM strings ---- hello my world -hellomyworld -(empty) +hello,my,world +NULL hello hello hello hello # more complex agg (groups) diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_aggregate_types_scalar.test b/modules/calcite/src/test/sql/aggregate/aggregates/test_aggregate_types_scalar.test index 9dba0a7177377..5f7a6568955e7 100644 --- a/modules/calcite/src/test/sql/aggregate/aggregates/test_aggregate_types_scalar.test +++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_aggregate_types_scalar.test @@ -100,7 +100,7 @@ hello query TTTTT SELECT STRING_AGG('hello', ' '), STRING_AGG('hello', NULL), STRING_AGG(NULL, ' '), STRING_AGG(NULL, NULL), STRING_AGG('', '') ---- -hello hello NULL NULL (empty) +hello hello NULL NULL NULL statement error SELECT STRING_AGG() diff --git a/modules/calcite/src/test/sql/function/string/regex_search.test b/modules/calcite/src/test/sql/function/string/regex_search.test index fa3eb0922e880..78eeef9285a8d 100644 --- a/modules/calcite/src/test/sql/function/string/regex_search.test +++ b/modules/calcite/src/test/sql/function/string/regex_search.test @@ -16,7 +16,7 @@ false query T SELECT 'asdf' ~ '' ---- -true +NULL # partial matches okay query T @@ -43,12 +43,12 @@ false query T SELECT '' ~ '.*yu.*' ---- -false +NULL query T SELECT '' ~ '.*' ---- -true +NULL # NULLs query T @@ -71,7 +71,7 @@ SELECT 'foobarbequebaz' ~ '(bar)(beque)' ---- true -# postgres says throw error on invalid regex +# Invalid regular expression is validated even when the source is NULL. statement error SELECT '' ~ '[a-z' @@ -126,4 +126,3 @@ SELECT v ~ 'h.*' FROM test ORDER BY v ---- false true - diff --git a/modules/calcite/src/test/sql/function/string/test_ascii.test b/modules/calcite/src/test/sql/function/string/test_ascii.test index e12f5c297b9ad..45c1829bffccf 100644 --- a/modules/calcite/src/test/sql/function/string/test_ascii.test +++ b/modules/calcite/src/test/sql/function/string/test_ascii.test @@ -6,7 +6,7 @@ query I SELECT ascii('') ---- -0 +NULL query I SELECT ascii('x') diff --git a/modules/calcite/src/test/sql/function/string/test_caseconvert.test b/modules/calcite/src/test/sql/function/string/test_caseconvert.test index 21840e36759ef..7d2aa2d22323c 100644 --- a/modules/calcite/src/test/sql/function/string/test_caseconvert.test +++ b/modules/calcite/src/test/sql/function/string/test_caseconvert.test @@ -25,12 +25,12 @@ SELECT UPPER('Αα Ββ Γγ Δδ Εε Ζζ Ηη Θθ Ιι Κκ Λλ Μμ Νν query TTT select UPPER(''), UPPER('hello'), UPPER('MotörHead') ---- -(empty) HELLO MOTÖRHEAD +NULL HELLO MOTÖRHEAD query TTT select LOWER(''), LOWER('hello'), LOWER('MotörHead') ---- -(empty) hello motörhead +NULL hello motörhead # test on entire tables statement ok @@ -66,4 +66,3 @@ select UPPER(a), LOWER(a) FROM strings WHERE b IS NOT NULL ORDER BY a ---- HELLO hello MOTÖRHEAD motörhead - diff --git a/modules/calcite/src/test/sql/function/string/test_char_length.test b/modules/calcite/src/test/sql/function/string/test_char_length.test index 1ebd5024a0b45..000a4894ddb78 100644 --- a/modules/calcite/src/test/sql/function/string/test_char_length.test +++ b/modules/calcite/src/test/sql/function/string/test_char_length.test @@ -11,7 +11,7 @@ SELECT CHARACTER_LENGTH('Gridgain.com + Гридгайн') query T SELECT CHARACTER_LENGTH('') ---- -0 +NULL query T SELECT CHARACTER_LENGTH(null) @@ -27,10 +27,9 @@ SELECT char_length('Gridgain.com + Гридгайн') query T SELECT char_length('') ---- -0 +NULL query T SELECT char_length(null) ---- NULL - diff --git a/modules/calcite/src/test/sql/function/string/test_compress.test b/modules/calcite/src/test/sql/function/string/test_compress.test index 96b99fd05a0a0..e7523225ff50f 100644 --- a/modules/calcite/src/test/sql/function/string/test_compress.test +++ b/modules/calcite/src/test/sql/function/string/test_compress.test @@ -6,5 +6,5 @@ query III select COMPRESS('BIG TEST STRING TEST STRING!!!!!!'), COMPRESS(''), COMPRESS(NULL) ---- 21000000789c73f2745708710d0e51080e09f2f443612b82010096e30847 -(empty) +NULL NULL diff --git a/modules/calcite/src/test/sql/function/string/test_initcap.test b/modules/calcite/src/test/sql/function/string/test_initcap.test index d8e3cca056186..60c70f41a95dd 100644 --- a/modules/calcite/src/test/sql/function/string/test_initcap.test +++ b/modules/calcite/src/test/sql/function/string/test_initcap.test @@ -21,7 +21,7 @@ SELECT initcap('🦆') query T SELECT initcap('') ---- -(empty) +NULL query T SELECT initcap(null) diff --git a/modules/calcite/src/test/sql/function/string/test_like.test b/modules/calcite/src/test/sql/function/string/test_like.test index a50b176f3b9c2..218b96e1b29a4 100644 --- a/modules/calcite/src/test/sql/function/string/test_like.test +++ b/modules/calcite/src/test/sql/function/string/test_like.test @@ -101,7 +101,7 @@ true query T SELECT 'zebra elephant tiger horse' LIKE '' ---- -false +NULL query T SELECT 'zebra elephant tiger horse' LIKE '%' @@ -191,7 +191,7 @@ false query T SELECT 'zebra elephant tiger horse' NOT LIKE '' ---- -true +NULL query T SELECT 'zebra elephant tiger horse' NOT LIKE '%' @@ -282,4 +282,3 @@ SELECT s FROM strings WHERE s LIKE pat ORDER BY s ---- aaa abab - diff --git a/modules/calcite/src/test/sql/function/string/test_repeat.test b/modules/calcite/src/test/sql/function/string/test_repeat.test index 1ed7684da4bc6..84bafec9e21b9 100644 --- a/modules/calcite/src/test/sql/function/string/test_repeat.test +++ b/modules/calcite/src/test/sql/function/string/test_repeat.test @@ -12,7 +12,7 @@ NULL NULL NULL query TTTT select REPEAT('', 3), REPEAT('MySQL', 3), REPEAT('MotörHead', 2), REPEAT('Hello', -1) ---- -(empty) MySQLMySQLMySQL MotörHeadMotörHead (empty) +NULL MySQLMySQLMySQL MotörHeadMotörHead NULL # test repeat on tables statement ok @@ -27,7 +27,7 @@ select REPEAT(a, 3) FROM strings ORDER BY id HelloHelloHello HuLlDHuLlDHuLlD MotörHeadMotörHeadMotörHead -(empty) +NULL query T select REPEAT(b, 2) FROM strings ORDER BY id @@ -55,4 +55,3 @@ select REPEAT('hello', 'world') statement error select REPEAT('hello', 'world', 3) - diff --git a/modules/calcite/src/test/sql/function/string/test_replace.test b/modules/calcite/src/test/sql/function/string/test_replace.test index a03594ac0e6b1..17469647dea1b 100644 --- a/modules/calcite/src/test/sql/function/string/test_replace.test +++ b/modules/calcite/src/test/sql/function/string/test_replace.test @@ -45,7 +45,7 @@ INSERT INTO strings VALUES ('Hello', 'World'), ('HuLlD', NULL), ('MotörHead','R query T select REPLACE(a, 'l', '-') FROM strings ORDER BY 1 ---- -(empty) +NULL He--o HuL-D MotörHead @@ -61,8 +61,8 @@ R--cks query T select REPLACE(a, 'H', '') FROM strings WHERE b IS NOT NULL ORDER BY a ---- -ello -Motöread +NULL +NULL # test incorrect usage of replace statement error @@ -73,4 +73,3 @@ select REPLACE(1, 2) statement error select REPLACE(1, 2, 3, 4) - diff --git a/modules/calcite/src/test/sql/function/string/test_reverse.test b/modules/calcite/src/test/sql/function/string/test_reverse.test index b3aad2fa94537..0f72c441b2701 100644 --- a/modules/calcite/src/test/sql/function/string/test_reverse.test +++ b/modules/calcite/src/test/sql/function/string/test_reverse.test @@ -6,7 +6,7 @@ query TTTT select REVERSE(''), REVERSE('Hello'), REVERSE('MotörHead'), REVERSE(NULL) ---- -(empty) olleH daeHrötoM NULL +NULL olleH daeHrötoM NULL # test reverse on tables statement ok @@ -21,7 +21,7 @@ select REVERSE(a) FROM strings ORDER BY id olleH DlLuH daeHrötoM -(empty) +NULL query T select REVERSE(b) FROM strings ORDER BY id @@ -46,4 +46,3 @@ select REVERSE(1, 2) statement error select REVERSE('hello', 'world') - diff --git a/modules/calcite/src/test/sql/function/string/test_trim.test b/modules/calcite/src/test/sql/function/string/test_trim.test index a65a29ccf11f6..95b987da9304d 100644 --- a/modules/calcite/src/test/sql/function/string/test_trim.test +++ b/modules/calcite/src/test/sql/function/string/test_trim.test @@ -6,19 +6,19 @@ query TTTTTTT select LTRIM(''), LTRIM('Neither'), LTRIM(' Leading'), LTRIM('Trailing '), LTRIM(' Both '), LTRIM(NULL), LTRIM(' ') ---- -(empty) Neither Leading Trailing Both NULL (empty) +NULL Neither Leading Trailing Both NULL NULL # test rtrim on scalars query TTTTTTT select RTRIM(''), RTRIM('Neither'), RTRIM(' Leading'), RTRIM('Trailing '), RTRIM(' Both '), RTRIM(NULL), RTRIM(' ') ---- -(empty) Neither Leading Trailing Both NULL (empty) +NULL Neither Leading Trailing Both NULL NULL # test trim on scalars query TTTTTTT select TRIM(''), TRIM('Neither'), TRIM(' Leading'), TRIM('Trailing '), TRIM(' Both '), TRIM(NULL), TRIM(' ') ---- -(empty) Neither Leading Trailing Both NULL (empty) +NULL Neither Leading Trailing Both NULL NULL # test trim with flags query TTTTTTT @@ -36,10 +36,10 @@ INSERT INTO strings VALUES (0, '', 'Neither'), (1, ' Leading', NULL), (2, ' Both query T select LTRIM(a) FROM strings ORDER BY id ---- -(empty) +NULL Leading Both -(empty) +NULL query T select LTRIM(b) FROM strings ORDER BY id @@ -52,17 +52,17 @@ NULL query T select LTRIM(a) FROM strings WHERE b IS NOT NULL ORDER BY id ---- -(empty) +NULL Both # test rtrim on tables query T select RTRIM(a) FROM strings ORDER BY id ---- -(empty) +NULL Leading Both -(empty) +NULL query T select RTRIM(b) FROM strings ORDER BY id @@ -75,7 +75,7 @@ NULL query T select RTRIM(a) FROM strings WHERE b IS NOT NULL ORDER BY id ---- -(empty) +NULL Both @@ -103,4 +103,3 @@ select TRIM() statement error select TRIM('hello', 'world', 'aaa') - diff --git a/modules/calcite/src/test/sql/types/blob/test_blob.test b/modules/calcite/src/test/sql/types/blob/test_blob.test index be0647ab8af10..f2ced6c182e3e 100644 --- a/modules/calcite/src/test/sql/types/blob/test_blob.test +++ b/modules/calcite/src/test/sql/types/blob/test_blob.test @@ -79,7 +79,7 @@ blablabla query T SELECT ''::VARBINARY ---- -(empty) +NULL query T SELECT NULL::VARBINARY @@ -103,6 +103,5 @@ SELECT * FROM blob_empty ---- NULL NULL -(empty) -(empty) - +NULL +NULL