diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java index 28351847886..d58ae302d28 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java @@ -275,12 +275,18 @@ private static SqlCall transformConvert(SqlValidator validator, SqlCall call) { public static final SqlFunction LENGTH = SqlStdOperatorTable.CHAR_LENGTH.withName("LENGTH"); + // Helper function for deriving types for the *PAD functions + private static RelDataType deriveTypePad(SqlOperatorBinding binding, RelDataType type) { + SqlTypeName result = SqlTypeUtil.isBinary(type) ? SqlTypeName.VARBINARY : SqlTypeName.VARCHAR; + return binding.getTypeFactory().createSqlType(result); + } + /** The "LPAD(original_value, return_length[, pattern])" function. */ @LibraryOperator(libraries = {BIG_QUERY, ORACLE}) public static final SqlFunction LPAD = SqlBasicFunction.create( "LPAD", - ReturnTypes.ARG0_NULLABLE_VARYING, + ReturnTypes.ARG0.andThen(SqlLibraryOperators::deriveTypePad), OperandTypes.STRING_NUMERIC_OPTIONAL_STRING, SqlFunctionCategory.STRING); @@ -289,7 +295,7 @@ private static SqlCall transformConvert(SqlValidator validator, SqlCall call) { public static final SqlFunction RPAD = SqlBasicFunction.create( "RPAD", - ReturnTypes.ARG0_NULLABLE_VARYING, + ReturnTypes.ARG0.andThen(SqlLibraryOperators::deriveTypePad), OperandTypes.STRING_NUMERIC_OPTIONAL_STRING, SqlFunctionCategory.STRING); diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java index 71b80771ce9..b16a480203a 100644 --- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java +++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java @@ -262,6 +262,46 @@ private static boolean skipItem(RexNode expr) { .check(); } + /** + * Test case for [CALCITE-5989] + * Type inference for RPAD and LPAD functions (BIGQUERY) is incorrect. */ + @Test void testRpad() { + HepProgramBuilder builder = new HepProgramBuilder(); + builder.addRuleClass(ReduceExpressionsRule.class); + HepPlanner hepPlanner = new HepPlanner(builder.build()); + hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS); + + final String sql = "select RPAD('abc', 8, 'A')"; + fixture() + .withFactory( + t -> t.withOperatorTable(opTab -> + SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable( + SqlLibrary.BIG_QUERY))) // needed for RPAD function + .sql(sql) + .withPlanner(hepPlanner) + .check(); + } + + /** + * Test case for [CALCITE-5989] + * Type inference for RPAD and LPAD functions (BIGQUERY) is incorrect. */ + @Test void testLpad() { + HepProgramBuilder builder = new HepProgramBuilder(); + builder.addRuleClass(ReduceExpressionsRule.class); + HepPlanner hepPlanner = new HepPlanner(builder.build()); + hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS); + + final String sql = "select LPAD('abc', 8, 'A')"; + fixture() + .withFactory( + t -> t.withOperatorTable(opTab -> + SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable( + SqlLibrary.BIG_QUERY))) // needed for LPAD function + .sql(sql) + .withPlanner(hepPlanner) + .check(); + } + /** * Test case for [CALCITE-5971] * Add the RelRule to rewrite the bernoulli sample as Filter. */ diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml index 65f8268c223..e529b3ae444 100644 --- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml @@ -6221,6 +6221,23 @@ EnumerableLimitSort(sort0=[$0], dir0=[ASC], offset=[5], fetch=[10]) EnumerableTableScan(table=[[CATALOG, SALES, EMP]]) EnumerableProject(MGR=[$3]) EnumerableTableScan(table=[[CATALOG, SALES, EMP]]) +]]> + + + + + + + + + + + @@ -12876,6 +12893,23 @@ LogicalProject(EXPR$0=[1]) LogicalFilter(condition=[=($1, 'Charlie')]) LogicalTableScan(table=[[CATALOG, SALES, DEPT]]) LogicalTableScan(table=[[CATALOG, SALES, EMP]]) +]]> + + + + + + + + + + + diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index 0e23f666695..60bd95846d0 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -8225,20 +8225,20 @@ private void testCurrentDateFunc(Pair pair) { @Test void testLpadFunction() { final SqlOperatorFixture f = fixture().withLibrary(SqlLibrary.BIG_QUERY); f.setFor(SqlLibraryOperators.LPAD); - f.check("select lpad('12345', 8, 'a')", "VARCHAR(5) NOT NULL", "aaa12345"); - f.checkString("lpad('12345', 8)", " 12345", "VARCHAR(5) NOT NULL"); - f.checkString("lpad('12345', 8, 'ab')", "aba12345", "VARCHAR(5) NOT NULL"); - f.checkString("lpad('12345', 3, 'a')", "123", "VARCHAR(5) NOT NULL"); + f.check("select lpad('12345', 8, 'a')", "VARCHAR NOT NULL", "aaa12345"); + f.checkString("lpad('12345', 8)", " 12345", "VARCHAR NOT NULL"); + f.checkString("lpad('12345', 8, 'ab')", "aba12345", "VARCHAR NOT NULL"); + f.checkString("lpad('12345', 3, 'a')", "123", "VARCHAR NOT NULL"); f.checkFails("lpad('12345', -3, 'a')", "Second argument for LPAD/RPAD must not be negative", true); f.checkFails("lpad('12345', -3)", "Second argument for LPAD/RPAD must not be negative", true); f.checkFails("lpad('12345', 3, '')", "Third argument (pad pattern) for LPAD/RPAD must not be empty", true); - f.checkString("lpad(x'aa', 4, x'bb')", "bbbbbbaa", "VARBINARY(1) NOT NULL"); - f.checkString("lpad(x'aa', 4)", "202020aa", "VARBINARY(1) NOT NULL"); - f.checkString("lpad(x'aaaaaa', 2)", "aaaa", "VARBINARY(3) NOT NULL"); - f.checkString("lpad(x'aaaaaa', 2, x'bb')", "aaaa", "VARBINARY(3) NOT NULL"); + f.checkString("lpad(x'aa', 4, x'bb')", "bbbbbbaa", "VARBINARY NOT NULL"); + f.checkString("lpad(x'aa', 4)", "202020aa", "VARBINARY NOT NULL"); + f.checkString("lpad(x'aaaaaa', 2)", "aaaa", "VARBINARY NOT NULL"); + f.checkString("lpad(x'aaaaaa', 2, x'bb')", "aaaa", "VARBINARY NOT NULL"); f.checkFails("lpad(x'aa', -3, x'bb')", "Second argument for LPAD/RPAD must not be negative", true); f.checkFails("lpad(x'aa', -3)", @@ -8250,10 +8250,10 @@ private void testCurrentDateFunc(Pair pair) { @Test void testRpadFunction() { final SqlOperatorFixture f = fixture().withLibrary(SqlLibrary.BIG_QUERY); f.setFor(SqlLibraryOperators.RPAD); - f.check("select rpad('12345', 8, 'a')", "VARCHAR(5) NOT NULL", "12345aaa"); - f.checkString("rpad('12345', 8)", "12345 ", "VARCHAR(5) NOT NULL"); - f.checkString("rpad('12345', 8, 'ab')", "12345aba", "VARCHAR(5) NOT NULL"); - f.checkString("rpad('12345', 3, 'a')", "123", "VARCHAR(5) NOT NULL"); + f.check("select rpad('12345', 8, 'a')", "VARCHAR NOT NULL", "12345aaa"); + f.checkString("rpad('12345', 8)", "12345 ", "VARCHAR NOT NULL"); + f.checkString("rpad('12345', 8, 'ab')", "12345aba", "VARCHAR NOT NULL"); + f.checkString("rpad('12345', 3, 'a')", "123", "VARCHAR NOT NULL"); f.checkFails("rpad('12345', -3, 'a')", "Second argument for LPAD/RPAD must not be negative", true); f.checkFails("rpad('12345', -3)", @@ -8261,10 +8261,10 @@ private void testCurrentDateFunc(Pair pair) { f.checkFails("rpad('12345', 3, '')", "Third argument (pad pattern) for LPAD/RPAD must not be empty", true); - f.checkString("rpad(x'aa', 4, x'bb')", "aabbbbbb", "VARBINARY(1) NOT NULL"); - f.checkString("rpad(x'aa', 4)", "aa202020", "VARBINARY(1) NOT NULL"); - f.checkString("rpad(x'aaaaaa', 2)", "aaaa", "VARBINARY(3) NOT NULL"); - f.checkString("rpad(x'aaaaaa', 2, x'bb')", "aaaa", "VARBINARY(3) NOT NULL"); + f.checkString("rpad(x'aa', 4, x'bb')", "aabbbbbb", "VARBINARY NOT NULL"); + f.checkString("rpad(x'aa', 4)", "aa202020", "VARBINARY NOT NULL"); + f.checkString("rpad(x'aaaaaa', 2)", "aaaa", "VARBINARY NOT NULL"); + f.checkString("rpad(x'aaaaaa', 2, x'bb')", "aaaa", "VARBINARY NOT NULL"); f.checkFails("rpad(x'aa', -3, x'bb')", "Second argument for LPAD/RPAD must not be negative", true); f.checkFails("rpad(x'aa', -3)",