This is an automated email from the ASF dual-hosted git repository.

mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 1b36f07ce98f0e6d620b5ea1e1c773b376c3e81e
Author: Mihai Budiu <[email protected]>
AuthorDate: Thu Dec 7 05:20:35 2023 -0800

    [CALCITE-5811] Error messages produced for constant out-of-bounds arguments 
are confusing
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../org/apache/calcite/runtime/SqlFunctions.java   | 32 ++++++++++++++++++++++
 .../calcite/sql/fun/SqlSubstringFunction.java      |  5 ++--
 .../org/apache/calcite/test/SqlValidatorTest.java  | 14 ++++++----
 .../org/apache/calcite/test/SqlOperatorTest.java   | 27 ++++++++++++++++++
 4 files changed, 70 insertions(+), 8 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java 
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 4e11245ae3..adaf232add 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -986,6 +986,26 @@ public class SqlFunctions {
     return c.substring(s0);
   }
 
+  // Clamp very large long values to integer values.
+  // Used by the substring functions.
+  // Java strings do not support long indexes anyway,
+  // so this is most likely a safe approximation.
+  // But if a string has more than 2^31 characters
+  // the result of calling String.substring will be wrong anyway.
+  static int clamp(long s) {
+    if (s < Integer.MIN_VALUE) {
+      return Integer.MIN_VALUE;
+    }
+    if (s > Integer.MAX_VALUE) {
+      return Integer.MAX_VALUE;
+    }
+    return (int) s;
+  }
+
+  public static String substring(String c, long s) {
+    return substring(c, clamp(s));
+  }
+
   /** SQL SUBSTRING(string FROM ... FOR ...) function. */
   public static String substring(String c, int s, int l) {
     int lc = c.length();
@@ -1003,6 +1023,18 @@ public class SqlFunctions {
     return c.substring(s0, (int) e0);
   }
 
+  public static String substring(String c, int s, long l) {
+    return substring(c, s, clamp(l));
+  }
+
+  public static String substring(String c, long s, int l) {
+    return substring(c, clamp(s), l);
+  }
+
+  public static String substring(String c, long s, long l) {
+    return substring(c, clamp(s), clamp(l));
+  }
+
   /** SQL SUBSTRING(binary FROM ...) function for binary. */
   public static ByteString substring(ByteString c, int s) {
     final int s0 = s - 1;
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlSubstringFunction.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlSubstringFunction.java
index 53cc96fa83..2730e89c0c 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlSubstringFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlSubstringFunction.java
@@ -47,8 +47,9 @@ public class SqlSubstringFunction extends SqlFunction {
   /** Type checker for 3 argument calls. Put the STRING_INTEGER_INTEGER checker
    * first because almost every other type can be coerced to STRING. */
   private static final SqlSingleOperandTypeChecker CHECKER3 =
-      OperandTypes.STRING_INTEGER_INTEGER
-          .or(OperandTypes.STRING_STRING_STRING);
+      OperandTypes.STRING_INTEGER_INTEGER;
+          // Not yet implemented
+          // .or(OperandTypes.STRING_STRING_STRING)
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 1f6bafae61..c5947df813 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -1051,17 +1051,19 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
   }
 
   @Test void testSubstringFails() {
+    String error = "(?s).*Cannot apply 'SUBSTRING' to arguments of type.*";
     wholeExpr("substring('a' from 1 for 'b')")
         .withTypeCoercion(false)
-        .fails("(?s).*Cannot apply 'SUBSTRING' to arguments of type.*");
-    expr("substring('a' from 1 for 'b')")
-        .columnType("VARCHAR(1) NOT NULL");
+        .fails(error);
     wholeExpr("substring(_UTF16'10' FROM '0' FOR '\\')")
-        .fails("(?s).* not comparable to each other.*");
+        .withTypeCoercion(false)
+        .fails(error);
     wholeExpr("substring('10' FROM _UTF16'0' FOR '\\')")
-        .fails("(?s).* not comparable to each other.*");
+        .withTypeCoercion(false)
+        .fails(error);
     wholeExpr("substring('10' FROM '0' FOR _UTF16'\\')")
-        .fails("(?s).* not comparable to each other.*");
+        .withTypeCoercion(false)
+        .fails(error);
   }
 
   @Test void testLikeAndSimilar() {
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 f17ceb2a7b..9578522bd8 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -8979,6 +8979,33 @@ public class SqlOperatorTest {
             + "requires extra delimiter argument", false);
   }
 
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-5811";>[CALCITE-5811]
+   * Error messages produced for constant out-of-bounds arguments are 
confusing</a>. */
+  @Test void testIndexOutOfBounds() {
+    final SqlOperatorFixture f = fixture();
+    f.checkScalar("substring('abc' from 2 for 2147483650)",
+        "bc", "VARCHAR(3) NOT NULL");
+    f.checkScalar("substring('abc' from 2147483650)",
+        "", "VARCHAR(3) NOT NULL");
+    f.checkScalar("substring('abc' from 2147483650 for 2147483650)",
+        "", "VARCHAR(3) NOT NULL");
+    f.checkScalar("substring('abc' from 2147483650 for 2)",
+        "", "VARCHAR(3) NOT NULL");
+    f.checkFails("^substring('abc' from 2 for 2147483650.0)^",
+        "Cannot apply 'SUBSTRING' to arguments of type "
+            + "'SUBSTRING\\(<CHAR\\(3\\)> FROM <INTEGER> FOR <DECIMAL\\(11, 
1\\)>\\)'\\. "
+            + "Supported form\\(s\\): 'SUBSTRING\\(<CHAR> FROM <INTEGER>\\)'\n"
+            + "'SUBSTRING\\(<CHAR> FROM <INTEGER> FOR <INTEGER>\\)'\n"
+            + "'SUBSTRING\\(<VARCHAR> FROM <INTEGER>\\)'\n"
+            + "'SUBSTRING\\(<VARCHAR> FROM <INTEGER> FOR <INTEGER>\\)'\n"
+            + "'SUBSTRING\\(<BINARY> FROM <INTEGER>\\)'\n"
+            + "'SUBSTRING\\(<BINARY> FROM <INTEGER> FOR <INTEGER>\\)'\n"
+            + "'SUBSTRING\\(<VARBINARY> FROM <INTEGER>\\)'\n"
+            + "'SUBSTRING\\(<VARBINARY> FROM <INTEGER> FOR <INTEGER>\\)'", 
false);
+  }
+
   /** Tests the {@code SUBSTRING} operator. Many test cases that used to be
    * have been moved to {@link SubFunChecker#assertSubFunReturns}, and are
    * called for both {@code SUBSTRING} and {@code SUBSTR}. */

Reply via email to