This is an automated email from the ASF dual-hosted git repository.
jiajunxie pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new ed42c35bd1 [CALCITE-5813] Type inference for sql functions REPEAT,
SPACE, XML_TRANSFORM, and XML_EXTRACT is incorrect
ed42c35bd1 is described below
commit ed42c35bd1a357dd0b5d5dc268e93573b724a800
Author: Mihai Budiu <[email protected]>
AuthorDate: Fri Aug 11 22:11:33 2023 -0700
[CALCITE-5813] Type inference for sql functions REPEAT, SPACE,
XML_TRANSFORM, and XML_EXTRACT is incorrect
Signed-off-by: Mihai Budiu <[email protected]>
---
.../calcite/sql/fun/SqlLibraryOperators.java | 8 +-
.../calcite/sql/fun/SqlStdOperatorTable.java | 2 +-
.../org/apache/calcite/test/RelOptRulesTest.java | 59 ++++++++++++
.../org/apache/calcite/test/RelOptRulesTest.xml | 51 ++++++++++
.../org/apache/calcite/test/SqlOperatorTest.java | 103 ++++++++++++++++-----
5 files changed, 197 insertions(+), 26 deletions(-)
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 6b234c2165..c3bcb53607 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
@@ -493,13 +493,13 @@ public abstract class SqlLibraryOperators {
@LibraryOperator(libraries = {ORACLE})
public static final SqlFunction XML_TRANSFORM =
SqlBasicFunction.create("XMLTRANSFORM",
- ReturnTypes.VARCHAR_2000.andThen(SqlTypeTransforms.FORCE_NULLABLE),
+ ReturnTypes.VARCHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE),
OperandTypes.STRING_STRING);
@LibraryOperator(libraries = {ORACLE})
public static final SqlFunction EXTRACT_XML =
SqlBasicFunction.create("EXTRACT",
- ReturnTypes.VARCHAR_2000.andThen(SqlTypeTransforms.FORCE_NULLABLE),
+ ReturnTypes.VARCHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE),
OperandTypes.STRING_STRING_OPTIONAL_STRING);
@LibraryOperator(libraries = {ORACLE})
@@ -802,7 +802,7 @@ public abstract class SqlLibraryOperators {
@LibraryOperator(libraries = {BIG_QUERY, MYSQL, POSTGRESQL})
public static final SqlFunction REPEAT =
SqlBasicFunction.create("REPEAT",
- ReturnTypes.ARG0_NULLABLE_VARYING,
+ ReturnTypes.VARCHAR_NULLABLE,
OperandTypes.STRING_INTEGER,
SqlFunctionCategory.STRING);
@@ -814,7 +814,7 @@ public abstract class SqlLibraryOperators {
@LibraryOperator(libraries = {MYSQL})
public static final SqlFunction SPACE =
SqlBasicFunction.create("SPACE",
- ReturnTypes.VARCHAR_2000_NULLABLE,
+ ReturnTypes.VARCHAR_NULLABLE,
OperandTypes.INTEGER,
SqlFunctionCategory.STRING);
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index 65bb44e9e7..530368d278 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -1574,7 +1574,7 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
/** The {@code REPLACE(string, search, replace)} function. Not standard SQL,
* but in Oracle and Postgres. */
public static final SqlFunction REPLACE =
- SqlBasicFunction.create("REPLACE", ReturnTypes.ARG0_NULLABLE_VARYING,
+ SqlBasicFunction.create("REPLACE", ReturnTypes.VARCHAR_NULLABLE,
OperandTypes.STRING_STRING_STRING, SqlFunctionCategory.STRING);
/** The {@code CONVERT(charValue, srcCharsetName, destCharsetName)}
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 a9b66f1076..c296ab7ca2 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -224,6 +224,65 @@ class RelOptRulesTest extends RelOptTestBase {
&& "item".equalsIgnoreCase(((RexCall) expr).getOperator().getName());
}
+ /**
+ * Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-5813">[CALCITE-5813]
+ * Type inference for sql functions REPEAT, SPACE, XML_TRANSFORM,
+ * and XML_EXTRACT is incorrect</a>. */
+ @Test void testRepeat() {
+ HepProgramBuilder builder = new HepProgramBuilder();
+ builder.addRuleClass(ReduceExpressionsRule.class);
+ HepPlanner hepPlanner = new HepPlanner(builder.build());
+ hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);
+
+ final String sql = "select REPEAT('abc', 2)";
+ fixture()
+ .withFactory(
+ t -> t.withOperatorTable(opTab ->
+ SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
+ SqlLibrary.BIG_QUERY))) // needed for REPEAT function
+ .sql(sql)
+ .withPlanner(hepPlanner)
+ .check();
+ }
+
+ /**
+ * Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-5813">[CALCITE-5813]
+ * Type inference for sql functions REPEAT, SPACE, XML_TRANSFORM,
+ * and XML_EXTRACT is incorrect</a>. */
+ @Test void testReplace() {
+ HepProgramBuilder builder = new HepProgramBuilder();
+ builder.addRuleClass(ReduceExpressionsRule.class);
+ HepPlanner hepPlanner = new HepPlanner(builder.build());
+ hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);
+
+ final String sql = "select REPLACE('abc', 'c', 'cd')";
+ fixture()
+ .sql(sql)
+ .withPlanner(hepPlanner)
+ .check();
+ }
+
+ /**
+ * Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-5813">[CALCITE-5813]
+ * Type inference for sql functions REPEAT, SPACE, XML_TRANSFORM,
+ * and XML_EXTRACT is incorrect</a>. */
+ @Test void testSpace() {
+ HepProgramBuilder builder = new HepProgramBuilder();
+ builder.addRuleClass(ReduceExpressionsRule.class);
+ HepPlanner hepPlanner = new HepPlanner(builder.build());
+ hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);
+
+ final String sql = "select SPACE(2001)";
+ fixture()
+ .withFactory(
+ t -> t.withOperatorTable(opTab ->
+ SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
+ SqlLibrary.MYSQL))) // needed for SPACE function
+ .sql(sql)
+ .withPlanner(hepPlanner)
+ .check();
+ }
+
@Test void testGroupByDateLiteralSimple() {
final String query = "select avg(sal)\n"
+ "from emp\n"
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 aa13309171..bdef8ae751 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -12256,6 +12256,40 @@ LogicalProject(ENAME=[$1])
LogicalFilter(condition=[=($1, 'foo')])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testRepeat">
+ <Resource name="sql">
+ <![CDATA[select REPEAT('abc', 2)]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(EXPR$0=[REPEAT('abc', 2)])
+ LogicalValues(tuples=[[{ 0 }]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EXPR$0=['abcabc':VARCHAR])
+ LogicalValues(tuples=[[{ 0 }]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testReplace">
+ <Resource name="sql">
+ <![CDATA[select REPLACE('abc', 'c', 'cd')]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(EXPR$0=[REPLACE('abc', 'c', 'cd')])
+ LogicalValues(tuples=[[{ 0 }]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EXPR$0=['abcd':VARCHAR])
+ LogicalValues(tuples=[[{ 0 }]])
]]>
</Resource>
</TestCase>
@@ -13747,6 +13781,23 @@ LogicalSort(sort0=[$0], dir0=[ASC], fetch=[0])
LogicalSort(sort0=[$0], dir0=[ASC], fetch=[0])
LogicalProject(NAME=[$1])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testSpace">
+ <Resource name="sql">
+ <![CDATA[select SPACE(2001)]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(EXPR$0=[SPACE(2001)])
+ LogicalValues(tuples=[[{ 0 }]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EXPR$0=['
[...]
+ LogicalValues(tuples=[[{ 0 }]])
]]>
</Resource>
</TestCase>
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 4ae29895df..35f23a8d3f 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -1678,21 +1678,21 @@ public class SqlOperatorTest {
f.checkScalar("{fn LTRIM(' xxx ')}", "xxx ", "VARCHAR(6) NOT NULL");
- f.checkScalar("{fn REPEAT('a', -100)}", "", "VARCHAR(1) NOT NULL");
+ f.checkScalar("{fn REPEAT('a', -100)}", "", "VARCHAR NOT NULL");
f.checkNull("{fn REPEAT('abc', cast(null as integer))}");
f.checkNull("{fn REPEAT(cast(null as varchar(1)), cast(null as
integer))}");
f.checkString("{fn REPLACE('JACK and JUE','J','BL')}",
- "BLACK and BLUE", "VARCHAR(12) NOT NULL");
+ "BLACK and BLUE", "VARCHAR NOT NULL");
// REPLACE returns NULL in Oracle but not in Postgres or in Calcite.
// When [CALCITE-815] is implemented and SqlConformance#emptyStringIsNull
is
// enabled, it will return empty string as NULL.
f.checkString("{fn REPLACE('ciao', 'ciao', '')}", "",
- "VARCHAR(4) NOT NULL");
+ "VARCHAR NOT NULL");
f.checkString("{fn REPLACE('hello world', 'o', '')}", "hell wrld",
- "VARCHAR(11) NOT NULL");
+ "VARCHAR NOT NULL");
f.checkNull("{fn REPLACE(cast(null as varchar(5)), 'ciao', '')}");
f.checkNull("{fn REPLACE('ciao', cast(null as varchar(3)), 'zz')}");
@@ -1707,7 +1707,7 @@ public class SqlOperatorTest {
f.checkScalar("{fn SOUNDEX('Miller')}", "M460", "VARCHAR(4) NOT NULL");
f.checkNull("{fn SOUNDEX(cast(null as varchar(1)))}");
- f.checkScalar("{fn SPACE(-100)}", "", "VARCHAR(2000) NOT NULL");
+ f.checkScalar("{fn SPACE(-100)}", "", "VARCHAR NOT NULL");
f.checkNull("{fn SPACE(cast(null as integer))}");
f.checkScalar(
@@ -3897,9 +3897,9 @@ public class SqlOperatorTest {
final SqlOperatorFixture f = fixture();
f.setFor(SqlStdOperatorTable.REPLACE, VmName.EXPAND);
f.checkString("REPLACE('ciao', 'ciao', '')", "",
- "VARCHAR(4) NOT NULL");
+ "VARCHAR NOT NULL");
f.checkString("REPLACE('hello world', 'o', '')", "hell wrld",
- "VARCHAR(11) NOT NULL");
+ "VARCHAR NOT NULL");
f.checkNull("REPLACE(cast(null as varchar(5)), 'ciao', '')");
f.checkNull("REPLACE('ciao', cast(null as varchar(3)), 'zz')");
f.checkNull("REPLACE('ciao', 'bella', cast(null as varchar(3)))");
@@ -4328,11 +4328,11 @@ public class SqlOperatorTest {
"No match found for function signature REPEAT\\(<CHARACTER>,
<NUMERIC>\\)",
false);
final Consumer<SqlOperatorFixture> consumer = f -> {
- f.checkString("REPEAT('a', -100)", "", "VARCHAR(1) NOT NULL");
- f.checkString("REPEAT('a', -1)", "", "VARCHAR(1) NOT NULL");
- f.checkString("REPEAT('a', 0)", "", "VARCHAR(1) NOT NULL");
- f.checkString("REPEAT('a', 2)", "aa", "VARCHAR(1) NOT NULL");
- f.checkString("REPEAT('abc', 3)", "abcabcabc", "VARCHAR(3) NOT NULL");
+ f.checkString("REPEAT('a', -100)", "", "VARCHAR NOT NULL");
+ f.checkString("REPEAT('a', -1)", "", "VARCHAR NOT NULL");
+ f.checkString("REPEAT('a', 0)", "", "VARCHAR NOT NULL");
+ f.checkString("REPEAT('a', 2)", "aa", "VARCHAR NOT NULL");
+ f.checkString("REPEAT('abc', 3)", "abcabcabc", "VARCHAR NOT NULL");
f.checkNull("REPEAT(cast(null as varchar(1)), -1)");
f.checkNull("REPEAT(cast(null as varchar(1)), 2)");
f.checkNull("REPEAT('abc', cast(null as integer))");
@@ -4345,11 +4345,11 @@ public class SqlOperatorTest {
final SqlOperatorFixture f = fixture()
.setFor(SqlLibraryOperators.SPACE)
.withLibrary(SqlLibrary.MYSQL);
- f.checkString("SPACE(-100)", "", "VARCHAR(2000) NOT NULL");
- f.checkString("SPACE(-1)", "", "VARCHAR(2000) NOT NULL");
- f.checkString("SPACE(0)", "", "VARCHAR(2000) NOT NULL");
- f.checkString("SPACE(2)", " ", "VARCHAR(2000) NOT NULL");
- f.checkString("SPACE(5)", " ", "VARCHAR(2000) NOT NULL");
+ f.checkString("SPACE(-100)", "", "VARCHAR NOT NULL");
+ f.checkString("SPACE(-1)", "", "VARCHAR NOT NULL");
+ f.checkString("SPACE(0)", "", "VARCHAR NOT NULL");
+ f.checkString("SPACE(2)", " ", "VARCHAR NOT NULL");
+ f.checkString("SPACE(5)", " ", "VARCHAR NOT NULL");
f.checkNull("SPACE(cast(null as integer))");
}
@@ -5316,7 +5316,45 @@ public class SqlOperatorTest {
+ "</xsl:stylesheet>')";
f.checkString(sql2,
" Article - My Article Authors: - Mr. Foo - Mr. Bar",
- "VARCHAR(2000)");
+ "VARCHAR");
+
+ // Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-5813">[CALCITE-5813]
+ // Type inference for REPEAT sql function is incorrect</a>. This test
shows that the
+ // output of the XML_TRANSFORM function can exceed 2000 characters. */
+ StringBuilder sql3 = new StringBuilder();
+ StringBuilder expected = new StringBuilder();
+ sql3.append("XMLTRANSFORM("
+ + "'<?xml version=\"1.0\"?>\n"
+ + "<Article>\n"
+ + " <Title>My Article</Title>\n"
+ + " <Authors>\n"
+ + " <Author>Mr. Foo</Author>\n"
+ + " <Author>Mr. Bar</Author>\n");
+ expected.append(" Article - My Article Authors: - Mr. Foo -
Mr. Bar");
+ for (int i = 0; i < 40; i++) {
+ final String row = "Mr. Bar
" + i;
+ sql3.append(" <Author>")
+ .append(row)
+ .append("</Author>\n");
+ expected.append(" - ").append(row);
+ }
+ sql3.append(" </Authors>\n"
+ + " <Body>This is my article text.</Body>\n"
+ + "</Article>'"
+ + ","
+ + "'<?xml version=\"1.0\"?>\n"
+ + "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3"
+ + ".org/1999/XSL/Transform\">"
+ + " <xsl:output method=\"text\"/>"
+ + " <xsl:template match=\"/\">"
+ + " Article - <xsl:value-of select=\"/Article/Title\"/>"
+ + " Authors: <xsl:apply-templates
select=\"/Article/Authors/Author\"/>"
+ + " </xsl:template>"
+ + " <xsl:template match=\"Author\">"
+ + " - <xsl:value-of select=\".\" />"
+ + " </xsl:template>"
+ + "</xsl:stylesheet>')");
+ f.checkString(sql3.toString(), expected.toString(), "VARCHAR");
}
@Test void testExtractXml() {
@@ -5339,7 +5377,7 @@ public class SqlOperatorTest {
+ "<Body>article text.</Body>"
+ "</Article>', '/Article/Title')",
"<Title>Article1</Title>",
- "VARCHAR(2000)");
+ "VARCHAR");
f.checkString("\"EXTRACT\"('"
+ "<Article>"
@@ -5349,7 +5387,30 @@ public class SqlOperatorTest {
+ "<Body>article text.</Body>"
+ "</Article>', '/Article/Title')",
"<Title>Article1</Title><Title>Article2</Title>",
- "VARCHAR(2000)");
+ "VARCHAR");
+
+ // Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-5813">[CALCITE-5813]
+ // Type inference for REPEAT sql function is incorrect</a>. This test
shows that
+ // the output of the 'XML_EXTRACT' function can exceed 2000 characters. */
+ StringBuilder sql = new StringBuilder();
+ StringBuilder expected = new StringBuilder();
+ sql.append("\"EXTRACT\"('"
+ + "<Article>"
+ + "<Title>Article1</Title>"
+ + "<Title>Article2");
+ expected.append("<Title>Article1</Title><Title>Article2");
+ final String spaces =
+ "
";
+ for (int i = 0; i < 40; i++) {
+ sql.append(spaces);
+ expected.append(spaces);
+ }
+ sql.append("Long</Title>"
+ + "<Authors><Author>Foo</Author><Author>Bar</Author></Authors>"
+ + "<Body>article text.</Body>"
+ + "</Article>', '/Article/Title')");
+ expected.append("Long</Title>");
+ f.checkString(sql.toString(), expected.toString(), "VARCHAR");
f.checkString("\"EXTRACT\"(\n"
+ "'<books xmlns=\"http://www.contoso.com/books\">"
@@ -5362,7 +5423,7 @@ public class SqlOperatorTest {
+ "'books=\"http://www.contoso.com/books\"')",
"<book
xmlns=\"http://www.contoso.com/books\"><title>Title</title><author>Author "
+ "Name</author><price>5.50</price></book>",
- "VARCHAR(2000)");
+ "VARCHAR");
}
@Test void testExistsNode() {