From 688c6b4da3298bd4ff072453e05c99daf4e443d4 Mon Sep 17 00:00:00 2001 From: bibi samina Date: Sat, 4 Jul 2026 15:28:55 +0530 Subject: [PATCH 1/2] Escape single quotes in string literals pushed to Cassandra and Geode --- .../calcite/adapter/cassandra/CassandraFilter.java | 2 +- .../org/apache/calcite/test/CassandraAdapterTest.java | 9 +++++++++ .../apache/calcite/adapter/geode/rel/GeodeFilter.java | 2 +- .../calcite/adapter/geode/rel/GeodeZipsTest.java | 11 +++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraFilter.java b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraFilter.java index f357079d61f1..9bca4419a5f4 100644 --- a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraFilter.java +++ b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraFilter.java @@ -302,7 +302,7 @@ private String translateOp2(String op, String name, RexLiteral right) { requireNonNull(rowType.getField(name, true, false)); SqlTypeName typeName = field.getType().getSqlTypeName(); if (typeName != SqlTypeName.CHAR) { - valueString = "'" + valueString + "'"; + valueString = "'" + valueString.replace("'", "''") + "'"; } } return name + " " + op + " " + valueString; diff --git a/cassandra/src/test/java/org/apache/calcite/test/CassandraAdapterTest.java b/cassandra/src/test/java/org/apache/calcite/test/CassandraAdapterTest.java index 411ffdbb65be..88898e6a0455 100644 --- a/cassandra/src/test/java/org/apache/calcite/test/CassandraAdapterTest.java +++ b/cassandra/src/test/java/org/apache/calcite/test/CassandraAdapterTest.java @@ -71,6 +71,15 @@ static void load(CqlSession session) { + " CassandraTableScan(table=[[twissandra, userline]]"); } + @Test void testFilterWithSingleQuote() { + // A string literal containing a single quote must be escaped so it does not + // break out of the CQL string literal in the generated query. + CalciteAssert.that() + .with(TWISSANDRA) + .query("select * from \"userline\" where \"username\" = 'a''b'") + .returnsCount(0); + } + @Test void testFilterUUID() { CalciteAssert.that() .with(TWISSANDRA) diff --git a/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeFilter.java b/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeFilter.java index dafc0122d139..27b85dd72f91 100644 --- a/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeFilter.java +++ b/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeFilter.java @@ -395,7 +395,7 @@ private String translateBinary2(String op, RexNode left, private static String quoteCharLiteral(RexLiteral literal) { String value = literalValue(literal); if (literal.getTypeName() == CHAR) { - value = "'" + value + "'"; + value = "'" + value.replace("'", "''") + "'"; } return value; } diff --git a/geode/src/test/java/org/apache/calcite/adapter/geode/rel/GeodeZipsTest.java b/geode/src/test/java/org/apache/calcite/adapter/geode/rel/GeodeZipsTest.java index bf42258edabc..e1072bcc6d87 100644 --- a/geode/src/test/java/org/apache/calcite/adapter/geode/rel/GeodeZipsTest.java +++ b/geode/src/test/java/org/apache/calcite/adapter/geode/rel/GeodeZipsTest.java @@ -202,6 +202,17 @@ public void testJoin() { GeodeAssertions.query(expectedQuery)); } + @Test void testFilterWithSingleQuoteLiteral() { + String expectedQuery = "SELECT city AS city FROM /zips " + + "WHERE city = 'a''b'"; + calciteAssert() + .query("SELECT city as city " + + "FROM view WHERE city = 'a''b'") + .returnsCount(0) + .queryContains( + GeodeAssertions.query(expectedQuery)); + } + @Test void testWhereWithOrForNumericField() { calciteAssert() .query("SELECT pop as pop " From 16fe1e51b229a6b1ed4f47eb1a0d40c720df42f0 Mon Sep 17 00:00:00 2001 From: bibi samina Date: Sun, 5 Jul 2026 19:16:02 +0530 Subject: [PATCH 2/2] Add test for adjacent single quotes in pushed Geode literal Signed-off-by: bibi samina --- .../calcite/adapter/geode/rel/GeodeZipsTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/geode/src/test/java/org/apache/calcite/adapter/geode/rel/GeodeZipsTest.java b/geode/src/test/java/org/apache/calcite/adapter/geode/rel/GeodeZipsTest.java index e1072bcc6d87..94fd3a41e4f6 100644 --- a/geode/src/test/java/org/apache/calcite/adapter/geode/rel/GeodeZipsTest.java +++ b/geode/src/test/java/org/apache/calcite/adapter/geode/rel/GeodeZipsTest.java @@ -213,6 +213,20 @@ public void testJoin() { GeodeAssertions.query(expectedQuery)); } + /** Value that already contains adjacent single quotes: the literal {@code 'I''''m fine'} + * denotes the runtime value {@code I''m fine}, and every embedded quote is doubled so the + * OQL round-trips back to that same value. */ + @Test void testFilterWithAdjacentSingleQuoteLiteral() { + String expectedQuery = "SELECT city AS city FROM /zips " + + "WHERE city = 'I''''m fine'"; + calciteAssert() + .query("SELECT city as city " + + "FROM view WHERE city = 'I''''m fine'") + .returnsCount(0) + .queryContains( + GeodeAssertions.query(expectedQuery)); + } + @Test void testWhereWithOrForNumericField() { calciteAssert() .query("SELECT pop as pop "