From b20daee68d96cca7e23f5c2e83f687ba2f3b1852 Mon Sep 17 00:00:00 2001 From: Mick Semb Wever Date: Mon, 14 Oct 2019 21:19:46 +0200 Subject: [PATCH] Fix SASI non-literal string comparisons (range operators) patch by mazhenlin; reviewed by Mick Semb Wever for CASSANDRA-15169 --- CHANGES.txt | 1 + .../cassandra/index/sasi/plan/Expression.java | 4 +- .../cassandra/index/sasi/SASICQLTest.java | 224 +++++++++++++++++- 3 files changed, 226 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ec88756517..361407171f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.11.5 + * Fix SASI non-literal string comparisons (range operators) (CASSANDRA-15169) * Make sure user defined compaction transactions are always closed (CASSANDRA-15123) * Fix cassandra-env.sh to use $CASSANDRA_CONF to find cassandra-jaas.config (CASSANDRA-14305) * Fixed nodetool cfstats printing index name twice (CASSANDRA-14903) diff --git a/src/java/org/apache/cassandra/index/sasi/plan/Expression.java b/src/java/org/apache/cassandra/index/sasi/plan/Expression.java index 93f193897c..fba7f34d49 100644 --- a/src/java/org/apache/cassandra/index/sasi/plan/Expression.java +++ b/src/java/org/apache/cassandra/index/sasi/plan/Expression.java @@ -335,7 +335,7 @@ public class Expression if (!hasLower()) return true; - int cmp = term.compareTo(validator, lower.value, false); + int cmp = term.compareTo(validator, lower.value, operation == Op.RANGE && !isLiteral); return cmp > 0 || cmp == 0 && lower.inclusive; } @@ -344,7 +344,7 @@ public class Expression if (!hasUpper()) return true; - int cmp = term.compareTo(validator, upper.value, false); + int cmp = term.compareTo(validator, upper.value, operation == Op.RANGE && !isLiteral); return cmp < 0 || cmp == 0 && upper.inclusive; } diff --git a/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java b/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java index 17bd1967b1..695f040b97 100644 --- a/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java +++ b/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java @@ -27,7 +27,9 @@ import org.junit.Test; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import com.datastax.driver.core.SimpleStatement; -import junit.framework.Assert; +import com.datastax.driver.core.exceptions.InvalidQueryException; +import org.junit.Assert; + import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.exceptions.InvalidRequestException; @@ -126,4 +128,224 @@ public class SASICQLTest extends CQLTester DatabaseDescriptor.setEnableSASIIndexes(enableSASIIndexes); } } + + /** + * Tests query condition '>' on string columns with is_literal=false. + */ + @Test + public void testNonLiteralStringCompare() throws Throwable + { + for (String mode : new String[]{ "PREFIX", "CONTAINS", "SPARSE"}) + { + for (boolean forceFlush : new boolean[]{ false, true }) + { + try + { + createTable("CREATE TABLE %s (pk int primary key, v text);"); + createIndex(String.format("CREATE CUSTOM INDEX ON %%s (v) USING 'org.apache.cassandra.index.sasi.SASIIndex' WITH OPTIONS = {'is_literal': 'false', 'mode': '%s'};", mode)); + + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 0, "a"); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 1, "abc"); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 2, "ac"); + + flush(forceFlush); + + Session session = sessionNet(); + SimpleStatement stmt = new SimpleStatement("SELECT * FROM " + KEYSPACE + '.' + currentTable() + " WHERE v = 'ab'"); + stmt.setFetchSize(5); + List rs = session.execute(stmt).all(); + Assert.assertEquals(0, rs.size()); + + try + { + sessionNet(); + stmt = new SimpleStatement("SELECT * FROM " + KEYSPACE + '.' + currentTable() + " WHERE v > 'ab'"); + stmt.setFetchSize(5); + rs = session.execute(stmt).all(); + Assert.assertFalse("CONTAINS mode on non-literal string type should not support RANGE operators", "CONTAINS".equals(mode)); + Assert.assertEquals(2, rs.size()); + Assert.assertEquals(1, rs.get(0).getInt("pk")); + } + catch (InvalidQueryException ex) + { + if (!"CONTAINS".equals(mode)) + throw ex; + } + } + catch (Throwable th) + { + throw new AssertionError(String.format("Failure with mode:%s and flush:%s ", mode, forceFlush), th); + } + + } + } + } + + /** + * Tests query condition '>' on string columns with is_literal=true (default). + */ + @Test + public void testStringCompare() throws Throwable + { + for (String mode : new String[]{ "PREFIX", "CONTAINS"}) + { + for (boolean forceFlush : new boolean[]{ false, true }) + { + try + { + createTable("CREATE TABLE %s (pk int primary key, v text);"); + createIndex(String.format("CREATE CUSTOM INDEX ON %%s (v) USING 'org.apache.cassandra.index.sasi.SASIIndex' WITH OPTIONS = {'mode': '%s'};", mode)); + + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 0, "a"); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 1, "abc"); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 2, "ac"); + + flush(forceFlush); + + Session session = sessionNet(); + SimpleStatement stmt = new SimpleStatement("SELECT * FROM " + KEYSPACE + '.' + currentTable() + " WHERE v = 'ab'"); + stmt.setFetchSize(5); + List rs = session.execute(stmt).all(); + Assert.assertEquals(0, rs.size()); + + try + { + session = sessionNet(); + stmt = new SimpleStatement("SELECT * FROM " + KEYSPACE + '.' + currentTable() + " WHERE v > 'ab'"); + stmt.setFetchSize(5); + rs = session.execute(stmt).all(); + throw new AssertionError("literal string type should not support RANGE operators"); + } + catch (InvalidQueryException ex) + {} + } + catch (Throwable th) + { + throw new AssertionError(String.format("Failure with mode:%s and flush:%s ", mode, forceFlush), th); + } + + } + } + } + + /** + * Tests query condition like_prefix on string columns. + */ + @Test + public void testStringLikePrefix() throws Throwable + { + for (String mode : new String[]{ "PREFIX", "CONTAINS"}) + { + for (boolean forceFlush : new boolean[]{ false, true }) + { + try + { + createTable("CREATE TABLE %s (pk int primary key, v text);"); + createIndex(String.format("CREATE CUSTOM INDEX ON %%s (v) USING 'org.apache.cassandra.index.sasi.SASIIndex' WITH OPTIONS = {'mode': '%s'};", mode)); + + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 0, "a"); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 1, "abc"); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 2, "ac"); + + flush(forceFlush); + + Session session = sessionNet(); + SimpleStatement stmt = new SimpleStatement("SELECT * FROM " + KEYSPACE + '.' + currentTable() + " WHERE v LIKE 'ab%'"); + stmt.setFetchSize(5); + List rs = session.execute(stmt).all(); + Assert.assertEquals(1, rs.size()); + Assert.assertEquals(1, rs.get(0).getInt("pk")); + } + catch (Throwable th) + { + throw new AssertionError(String.format("Failure with mode:%s and flush:%s ", mode, forceFlush), th); + } + + } + } + } + + /** + * Tests query condition '>' on blob columns. + */ + @Test + public void testBlobCompare() throws Throwable + { + for (String mode : new String[]{ "PREFIX", "CONTAINS", "SPARSE"}) + { + for (boolean forceFlush : new boolean[]{ false, true }) + { + try + { + createTable("CREATE TABLE %s (pk int primary key, v blob);"); + createIndex(String.format("CREATE CUSTOM INDEX ON %%s (v) USING 'org.apache.cassandra.index.sasi.SASIIndex' WITH OPTIONS = {'mode': '%s'};", mode)); + + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 0, 0x1234); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 1, 0x12345678); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 2, 0x12350000); + + flush(forceFlush); + + Session session = sessionNet(); + SimpleStatement stmt = new SimpleStatement("SELECT * FROM " + KEYSPACE + '.' + currentTable() + " WHERE v > 0x1234"); + stmt.setFetchSize(5); + List rs = session.execute(stmt).all(); + Assert.assertFalse("CONTAINS mode on non-literal blob type should not support RANGE operators", "CONTAINS".equals(mode)); + Assert.assertEquals(2, rs.size()); + Assert.assertEquals(1, rs.get(0).getInt("pk")); + } + catch (InvalidQueryException ex) + { + if (!"CONTAINS".equals(mode)) + throw ex; + } + catch (Throwable th) + { + throw new AssertionError(String.format("Failure with mode:%s and flush:%s ", mode, forceFlush), th); + } + + } + } + } + + @Test + public void testIntCompare() throws Throwable + { + for (String mode : new String[]{ "PREFIX", "CONTAINS", "SPARSE"}) + { + for (boolean forceFlush : new boolean[]{ false, true }) + { + try + { + createTable("CREATE TABLE %s (pk int primary key, v int);"); + + createIndex(String.format("CREATE CUSTOM INDEX ON %%s (v) USING 'org.apache.cassandra.index.sasi.SASIIndex' WITH OPTIONS = {'mode': '%s'};", mode)); + + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 0, 100); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 1, 200); + execute("INSERT INTO %s (pk, v) VALUES (?, ?);", 2, 300); + + flush(forceFlush); + + Session session = sessionNet(); + SimpleStatement stmt = new SimpleStatement("SELECT * FROM " + KEYSPACE + '.' + currentTable() + " WHERE v > 200"); + stmt.setFetchSize(5); + List rs = session.execute(stmt).all(); + Assert.assertFalse("CONTAINS mode on non-literal int type should not support RANGE operators", "CONTAINS".equals(mode)); + Assert.assertEquals(1, rs.size()); + Assert.assertEquals(2, rs.get(0).getInt("pk")); + } + catch (InvalidQueryException ex) + { + if (!"CONTAINS".equals(mode)) + throw ex; + } + catch (Throwable th) + { + throw new AssertionError(String.format("Failure with mode:%s and flush:%s ", mode, forceFlush), th); + } + + } + } + } }