You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by xe...@apache.org on 2016/02/05 21:01:26 UTC
[1/2] cassandra git commit: add LIKE support to CQL3 patch by xedin;
reviewed by beobal for CASSANDRA-11067
Repository: cassandra
Updated Branches:
refs/heads/trunk 64cfcf055 -> 392866546
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java b/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java
index cb5ec73..b32bfc5 100644
--- a/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java
+++ b/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java
@@ -45,6 +45,8 @@ import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.index.sasi.conf.ColumnIndex;
import org.apache.cassandra.index.sasi.disk.OnDiskIndexBuilder;
import org.apache.cassandra.index.sasi.exceptions.TimeQuotaExceededException;
@@ -76,7 +78,7 @@ public class SASIIndexTest
private static final String KS_NAME = "sasi";
private static final String CF_NAME = "test_cf";
- private static final String CLUSTRING_CF_NAME = "clustering_test_cf";
+ private static final String CLUSTERING_CF_NAME = "clustering_test_cf";
@BeforeClass
public static void loadSchema() throws ConfigurationException
@@ -86,7 +88,7 @@ public class SASIIndexTest
MigrationManager.announceNewKeyspace(KeyspaceMetadata.create(KS_NAME,
KeyspaceParams.simpleTransient(1),
Tables.of(SchemaLoader.sasiCFMD(KS_NAME, CF_NAME),
- SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTRING_CF_NAME))));
+ SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTERING_CF_NAME))));
}
@After
@@ -120,18 +122,24 @@ public class SASIIndexTest
Set<String> rows;
- rows = getIndexed(store, 10, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("av")));
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("av")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2", "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("as")));
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("as")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("aw")));
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("aw")));
Assert.assertEquals(rows.toString(), 0, rows.size());
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("avel")));
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2", "key3" }, rows.toArray(new String[rows.size()])));
+
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("n")));
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4" }, rows.toArray(new String[rows.size()])));
+
rows = getIndexed(store, 10, buildExpression(age, Operator.EQ, Int32Type.instance.decompose(27)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[]{"key3", "key4"}, rows.toArray(new String[rows.size()])));
@@ -188,51 +196,60 @@ public class SASIIndexTest
Set<String> rows;
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GT, Int32Type.instance.decompose(14)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.LT, Int32Type.instance.decompose(27)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[]{"key1", "key2"}, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GT, Int32Type.instance.decompose(14)),
buildExpression(age, Operator.LT, Int32Type.instance.decompose(27)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GT, Int32Type.instance.decompose(12)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[]{ "key1", "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GTE, Int32Type.instance.decompose(13)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GTE, Int32Type.instance.decompose(16)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.LT, Int32Type.instance.decompose(30)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.LTE, Int32Type.instance.decompose(29)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.LTE, Int32Type.instance.decompose(25)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[]{ "key1" }, rows.toArray(new String[rows.size()])));
+
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("avel")),
+ buildExpression(age, Operator.LTE, Int32Type.instance.decompose(25)));
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
+
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("n")),
+ buildExpression(age, Operator.LTE, Int32Type.instance.decompose(25)));
+ Assert.assertTrue(rows.isEmpty());
+
}
@Test
@@ -290,47 +307,64 @@ public class SASIIndexTest
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key0", "key11", "key12", "key13", "key14",
"key3", "key4", "key6", "key7", "key8" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 5,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertEquals(rows.toString(), 5, rows.size());
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GTE, Int32Type.instance.decompose(35)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key0", "key11", "key12", "key13", "key4", "key6", "key7" },
rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.LT, Int32Type.instance.decompose(32)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key14", "key3", "key8" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GT, Int32Type.instance.decompose(27)),
buildExpression(age, Operator.LT, Int32Type.instance.decompose(32)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key14" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GT, Int32Type.instance.decompose(10)));
Assert.assertEquals(rows.toString(), 10, rows.size());
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.LTE, Int32Type.instance.decompose(50)));
Assert.assertEquals(rows.toString(), 10, rows.size());
+
+ rows = getIndexed(store, 10,
+ buildExpression(firstName, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("ie")),
+ buildExpression(age, Operator.LT, Int32Type.instance.decompose(43)));
+
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key10" }, rows.toArray(new String[rows.size()])));
+
+ rows = getIndexed(store, 10,
+ buildExpression(firstName, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("a")));
+
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key12", "key13", "key3", "key4", "key6" }, rows.toArray(new String[rows.size()])));
+
+ rows = getIndexed(store, 10,
+ buildExpression(firstName, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("a")),
+ buildExpression(age, Operator.LT, Int32Type.instance.decompose(33)));
+
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
}
@Test
@@ -360,14 +394,14 @@ public class SASIIndexTest
final ByteBuffer age = UTF8Type.instance.decompose("age");
Set<String> rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ,
+ buildExpression(firstName, Operator.LIKE_CONTAINS,
UTF8Type.instance.decompose("What you get by achieving your goals")),
buildExpression(age, Operator.GT, Int32Type.instance.decompose(32)));
Assert.assertEquals(rows.toString(), Collections.singleton("key1"), rows);
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("do it.")));
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("do it.")));
Assert.assertEquals(rows.toString(), Arrays.asList("key0", "key1"), Lists.newArrayList(rows));
}
@@ -428,31 +462,31 @@ public class SASIIndexTest
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key6" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key0", "key11", "key12", "key13", "key14",
"key3", "key4", "key6", "key7", "key8" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 5,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertEquals(rows.toString(), 5, rows.size());
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GTE, Int32Type.instance.decompose(35)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key0", "key11", "key12", "key13", "key4", "key6", "key7" },
rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.LT, Int32Type.instance.decompose(32)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key14", "key3", "key8" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GT, Int32Type.instance.decompose(27)),
buildExpression(age, Operator.LT, Int32Type.instance.decompose(32)));
@@ -483,16 +517,22 @@ public class SASIIndexTest
Assert.assertTrue(rows.toString(), rows.size() == 0);
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GT, Int32Type.instance.decompose(10)));
Assert.assertEquals(rows.toString(), 10, rows.size());
rows = getIndexed(store, 10,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.LTE, Int32Type.instance.decompose(50)));
Assert.assertEquals(rows.toString(), 10, rows.size());
+
+ rows = getIndexed(store, 10,
+ buildExpression(firstName, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("ie")),
+ buildExpression(age, Operator.LTE, Int32Type.instance.decompose(43)));
+
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key0", "key1", "key10" }, rows.toArray(new String[rows.size()])));
}
@Test
@@ -554,7 +594,7 @@ public class SASIIndexTest
final ByteBuffer age = UTF8Type.instance.decompose("age");
Set<DecoratedKey> uniqueKeys = getPaged(store, 4,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GTE, Int32Type.instance.decompose(21)));
@@ -581,7 +621,7 @@ public class SASIIndexTest
// now let's test a single equals condition
- uniqueKeys = getPaged(store, 4, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ uniqueKeys = getPaged(store, 4, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
expected = new ArrayList<String>()
{{
@@ -607,7 +647,7 @@ public class SASIIndexTest
// now let's test something which is smaller than a single page
uniqueKeys = getPaged(store, 4,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.EQ, Int32Type.instance.decompose(36)));
expected = new ArrayList<String>()
@@ -622,14 +662,14 @@ public class SASIIndexTest
// the same but with the page size of 2 to test minimal pagination windows
uniqueKeys = getPaged(store, 2,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.EQ, Int32Type.instance.decompose(36)));
Assert.assertEquals(expected, convert(uniqueKeys));
// and last but not least, test age range query with pagination
uniqueKeys = getPaged(store, 4,
- buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.GT, Int32Type.instance.decompose(20)),
buildExpression(age, Operator.LTE, Int32Type.instance.decompose(36)));
@@ -653,17 +693,23 @@ public class SASIIndexTest
Set<String> rows;
- rows = executeCQL(String.format("SELECT * FROM %s.%s WHERE first_name = 'a' limit 10 ALLOW FILTERING;", KS_NAME, CF_NAME));
+ rows = executeCQLWithKeys(String.format("SELECT * FROM %s.%s WHERE first_name LIKE '%%a%%' limit 10 ALLOW FILTERING;", KS_NAME, CF_NAME));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key03", "key04", "key09", "key13", "key14", "key16", "key20", "key22", "key24", "key25" }, rows.toArray(new String[rows.size()])));
- rows = executeCQL(String.format("SELECT * FROM %s.%s WHERE first_name = 'a' and token(id) >= token('key14') limit 5 ALLOW FILTERING;", KS_NAME, CF_NAME));
+ rows = executeCQLWithKeys(String.format("SELECT * FROM %s.%s WHERE first_name LIKE '%%a%%' and token(id) >= token('key14') limit 5 ALLOW FILTERING;", KS_NAME, CF_NAME));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key03", "key04", "key14", "key16", "key24" }, rows.toArray(new String[rows.size()])));
- rows = executeCQL(String.format("SELECT * FROM %s.%s WHERE first_name = 'a' and token(id) >= token('key14') and token(id) <= token('key24') limit 5 ALLOW FILTERING;", KS_NAME, CF_NAME));
+ rows = executeCQLWithKeys(String.format("SELECT * FROM %s.%s WHERE first_name LIKE '%%a%%' and token(id) >= token('key14') and token(id) <= token('key24') limit 5 ALLOW FILTERING;", KS_NAME, CF_NAME));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key14", "key16", "key24" }, rows.toArray(new String[rows.size()])));
- rows = executeCQL(String.format("SELECT * FROM %s.%s WHERE first_name = 'a' and age > 30 and token(id) >= token('key14') and token(id) <= token('key24') limit 5 ALLOW FILTERING;", KS_NAME, CF_NAME));
+ rows = executeCQLWithKeys(String.format("SELECT * FROM %s.%s WHERE first_name LIKE '%%a%%' and age > 30 and token(id) >= token('key14') and token(id) <= token('key24') limit 5 ALLOW FILTERING;", KS_NAME, CF_NAME));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key14" }, rows.toArray(new String[rows.size()])));
+
+ rows = executeCQLWithKeys(String.format("SELECT * FROM %s.%s WHERE first_name like '%%ie' limit 5 ALLOW FILTERING;", KS_NAME, CF_NAME));
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key07", "key20", "key24" }, rows.toArray(new String[rows.size()])));
+
+ rows = executeCQLWithKeys(String.format("SELECT * FROM %s.%s WHERE first_name like '%%ie' AND token(id) > token('key24') limit 5 ALLOW FILTERING;", KS_NAME, CF_NAME));
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key07", "key24" }, rows.toArray(new String[rows.size()])));
}
@Test
@@ -711,10 +757,10 @@ public class SASIIndexTest
final ByteBuffer dataOutputId = UTF8Type.instance.decompose("/data/output/id");
- Set<String> rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.EQ, UTF8Type.instance.decompose("a")));
+ Set<String> rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.EQ, UTF8Type.instance.decompose("A")));
+ rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("A")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[]{ "key3" }, rows.toArray(new String[rows.size()])));
// doesn't really make sense to rebuild index for in-memory data
@@ -723,24 +769,27 @@ public class SASIIndexTest
store.indexManager.invalidateAllIndexesBlocking();
- rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), rows.isEmpty());
- rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.EQ, UTF8Type.instance.decompose("A")));
+ rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("A")));
Assert.assertTrue(rows.toString(), rows.isEmpty());
// now let's trigger index rebuild and check if we got the data back
store.indexManager.buildIndexBlocking(store.indexManager.getIndexByName("data_output_id"));
- rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2" }, rows.toArray(new String[rows.size()])));
// also let's try to build an index for column which has no data to make sure that doesn't fail
store.indexManager.buildIndexBlocking(store.indexManager.getIndexByName("first_name"));
store.indexManager.buildIndexBlocking(store.indexManager.getIndexByName("data_output_id"));
- rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2" }, rows.toArray(new String[rows.size()])));
+
+ rows = getIndexed(store, 10, buildExpression(dataOutputId, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("el")));
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
}
@Test
@@ -767,7 +816,7 @@ public class SASIIndexTest
final ByteBuffer firstName = UTF8Type.instance.decompose("first_name");
final ByteBuffer age = UTF8Type.instance.decompose("age");
- Set<String> rows = getIndexed(store, 10, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ Set<String> rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[]{ "key0", "key3", "key4" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10, buildExpression(age, Operator.EQ, Int32Type.instance.decompose(33)));
@@ -775,7 +824,7 @@ public class SASIIndexTest
store.indexManager.invalidateAllIndexesBlocking();
- rows = getIndexed(store, 10, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), rows.isEmpty());
rows = getIndexed(store, 10, buildExpression(age, Operator.EQ, Int32Type.instance.decompose(33)));
@@ -794,7 +843,7 @@ public class SASIIndexTest
loadData(part2, forceFlush);
- rows = getIndexed(store, 10, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 10, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[]{ "key6", "key7" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10, buildExpression(age, Operator.EQ, Int32Type.instance.decompose(40)));
@@ -849,28 +898,28 @@ public class SASIIndexTest
final ByteBuffer firstName = UTF8Type.instance.decompose("first_name");
- Set<String> rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ Set<String> rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertEquals(rows.toString(), 16, rows.size());
// make sure we don't prematurely delete anything
store.indexManager.truncateAllIndexesBlocking(500);
- rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertEquals(rows.toString(), 16, rows.size());
store.indexManager.truncateAllIndexesBlocking(1500);
- rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertEquals(rows.toString(), 10, rows.size());
store.indexManager.truncateAllIndexesBlocking(2500);
- rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertEquals(rows.toString(), 6, rows.size());
store.indexManager.truncateAllIndexesBlocking(3500);
- rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertEquals(rows.toString(), 0, rows.size());
// add back in some data just to make sure it all still works
@@ -882,7 +931,7 @@ public class SASIIndexTest
loadData(part4, 4000, true);
- rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertEquals(rows.toString(), 1, rows.size());
}
@@ -927,7 +976,7 @@ public class SASIIndexTest
// to make sure that concurrent updates don't interfere with reads, uses first_name and age
// indexes to test correctness of both Trie and SkipList ColumnIndex implementations.
- Set<DecoratedKey> rows = getPaged(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ Set<DecoratedKey> rows = getPaged(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.EQ, Int32Type.instance.decompose(26)));
Assert.assertTrue(previousCount <= rows.size());
@@ -936,7 +985,7 @@ public class SASIIndexTest
while (updates.get() < writeCount);
// to make sure that after all of the right are done we can read all "count" worth of rows
- Set<DecoratedKey> rows = getPaged(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ Set<DecoratedKey> rows = getPaged(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.EQ, Int32Type.instance.decompose(26)));
Assert.assertEquals(writeCount, rows.size());
@@ -975,21 +1024,21 @@ public class SASIIndexTest
loadData(data3, false);
- Set<String> rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")));
+ Set<String> rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1", "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.EQ, Int32Type.instance.decompose(15)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.EQ, Int32Type.instance.decompose(29)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 100, buildExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
+ rows = getIndexed(store, 100, buildExpression(firstName, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("a")),
buildExpression(age, Operator.EQ, Int32Type.instance.decompose(27)));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[]{"key2", "key3"}, rows.toArray(new String[rows.size()])));
@@ -1024,6 +1073,13 @@ public class SASIIndexTest
@Test
public void testUnicodeSupport()
{
+ testUnicodeSupport(false);
+ cleanupData();
+ testUnicodeSupport(true);
+ }
+
+ private void testUnicodeSupport(boolean forceFlush)
+ {
ColumnFamilyStore store = Keyspace.open(KS_NAME).getColumnFamilyStore(CF_NAME);
final ByteBuffer comment = UTF8Type.instance.decompose("comment");
@@ -1048,78 +1104,57 @@ public class SASIIndexTest
update(rm, comment, UTF8Type.instance.decompose("ベンジャミン ウエスト"), System.currentTimeMillis());
rm.apply();
+ if (forceFlush)
+ store.forceBlockingFlush();
Set<String> rows;
- /* Memtable */
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ⓈⓅⒺⒸⒾ")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("ⓈⓅⒺⒸⒾ")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("normal")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("normal")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("龍")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("龍")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("鬱")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("鬱")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("馭鬱")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("馭鬱")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("龍馭鬱")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("龍馭鬱")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ベンジャミン")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("ベンジャミン")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key5" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("レストラ")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("レストラ")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("インディ")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("インディ")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ベンジャミ")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("ベンジャミ")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key5" }, rows.toArray(new String[rows.size()])));
- store.forceBlockingFlush();
-
- /* OnDiskSA */
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ⓈⓅⒺⒸⒾ")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("normal")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("龍")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("鬱")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("馭鬱")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("龍馭鬱")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("ン")));
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4", "key5" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ベンジャミン")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key5" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("レストラ")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("レストラン")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("インディ")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ベンジャミ")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key5" }, rows.toArray(new String[rows.size()])));
}
@Test
- public void testUnicodeSuffixMode()
+ public void testUnicodeSuffixModeNoSplits()
+ {
+ testUnicodeSuffixModeNoSplits(false);
+ cleanupData();
+ testUnicodeSuffixModeNoSplits(true);
+ }
+
+ private void testUnicodeSuffixModeNoSplits(boolean forceFlush)
{
ColumnFamilyStore store = Keyspace.open(KS_NAME).getColumnFamilyStore(CF_NAME);
@@ -1141,68 +1176,43 @@ public class SASIIndexTest
update(rm, comment, UTF8Type.instance.decompose("ベンジャミン ウエスト"), System.currentTimeMillis());
rm.apply();
+ if (forceFlush)
+ store.forceBlockingFlush();
Set<String> rows;
- /* Memtable */
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("龍")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("龍")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("鬱")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("鬱")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("馭鬱")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("馭鬱")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("龍馭鬱")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("龍馭鬱")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ベンジャミン")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("ベンジャミン")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("トラン")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("トラン")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ディア")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("ディア")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ジャミン")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("ジャミン")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ン")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("ン")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
- store.forceBlockingFlush();
-
- /* OnDiskSA */
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("龍")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("鬱")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("馭鬱")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("龍馭鬱")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ベンジャミン")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("トラン")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_SUFFIX, UTF8Type.instance.decompose("ン")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ディア")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ジャミン")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ベンジャミン ウエスト")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key4" }, rows.toArray(new String[rows.size()])));
-
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("ン")));
- Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2", "key3", "key4" }, rows.toArray(new String[rows.size()])));
}
@Test
@@ -1251,7 +1261,7 @@ public class SASIIndexTest
ColumnFamilyStore store = loadData(data1, true);
RowFilter filter = RowFilter.create();
- filter.add(store.metadata.getColumnDefinition(firstName), Operator.EQ, AsciiType.instance.fromString("a"));
+ filter.add(store.metadata.getColumnDefinition(firstName), Operator.LIKE_CONTAINS, AsciiType.instance.fromString("a"));
ReadCommand command = new PartitionRangeReadCommand(store.metadata,
FBUtilities.nowInSeconds(),
@@ -1337,13 +1347,13 @@ public class SASIIndexTest
rows = getIndexed(store, 10, buildExpression(fullName, Operator.EQ, UTF8Type.instance.decompose("美加 八田")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(fullName, Operator.EQ, UTF8Type.instance.decompose("美加")));
+ rows = getIndexed(store, 10, buildExpression(fullName, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("美加")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
rows = getIndexed(store, 10, buildExpression(fullName, Operator.EQ, UTF8Type.instance.decompose("晃宏 高須")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(fullName, Operator.EQ, UTF8Type.instance.decompose("大輝")));
+ rows = getIndexed(store, 10, buildExpression(fullName, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("大輝")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key7" }, rows.toArray(new String[rows.size()])));
}
@@ -1370,52 +1380,52 @@ public class SASIIndexTest
Set<String> rows;
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("577 Rogahn Valleys")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("577 Rogahn Valleys")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("577 ROgAhn VallEYs")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("577 ROgAhn VallEYs")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("577 rogahn valleys")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("577 rogahn valleys")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("577 rogahn")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("577 rogahn")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("57")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("57")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("89809 Beverly Course")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("89809 Beverly Course")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("89809 BEVERly COURSE")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("89809 BEVERly COURSE")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("89809 beverly course")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("89809 beverly course")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("89809 Beverly")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("89809 Beverly")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("8980")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("8980")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("165 ClYdie OvAl APT. 399")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("165 ClYdie OvAl APT. 399")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("165 Clydie Oval Apt. 399")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("165 Clydie Oval Apt. 399")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("165 clydie oval apt. 399")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("165 clydie oval apt. 399")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("165 ClYdie OvA")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("165 ClYdie OvA")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("165 ClYdi")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("165 ClYdi")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(comment, Operator.EQ, UTF8Type.instance.decompose("165")));
+ rows = getIndexed(store, 10, buildExpression(comment, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("165")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3" }, rows.toArray(new String[rows.size()])));
}
@@ -1468,24 +1478,30 @@ public class SASIIndexTest
// since simple interval tree lookup is not going to cover it, prefix lookup actually required.
Set<String> rows;
- rows = getIndexed(store, 10, buildExpression(name, Operator.EQ, UTF8Type.instance.decompose("J")));
+ rows = getIndexed(store, 10, buildExpression(name, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("J")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2", "key5", "key6" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(name, Operator.EQ, UTF8Type.instance.decompose("j")));
+ rows = getIndexed(store, 10, buildExpression(name, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("j")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2", "key5", "key6" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(name, Operator.EQ, UTF8Type.instance.decompose("m")));
+ rows = getIndexed(store, 10, buildExpression(name, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("m")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key3", "key4" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(name, Operator.EQ, UTF8Type.instance.decompose("v")));
+ rows = getIndexed(store, 10, buildExpression(name, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("v")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key7" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(name, Operator.EQ, UTF8Type.instance.decompose("p")));
+ rows = getIndexed(store, 10, buildExpression(name, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("p")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
- rows = getIndexed(store, 10, buildExpression(name, Operator.EQ, UTF8Type.instance.decompose("j")),
+ rows = getIndexed(store, 10, buildExpression(name, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("j")),
buildExpression(name, Operator.NEQ, UTF8Type.instance.decompose("joh")));
Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key2", "key6" }, rows.toArray(new String[rows.size()])));
+
+ rows = getIndexed(store, 10, buildExpression(name, Operator.EQ, UTF8Type.instance.decompose("pavel")));
+ Assert.assertTrue(rows.toString(), Arrays.equals(new String[] { "key1" }, rows.toArray(new String[rows.size()])));
+
+ rows = getIndexed(store, 10, buildExpression(name, Operator.EQ, UTF8Type.instance.decompose("Pave")));
+ Assert.assertTrue(rows.isEmpty());
}
@Test
@@ -1583,7 +1599,7 @@ public class SASIIndexTest
public void testClusteringIndexes(boolean forceFlush) throws Exception
{
- ColumnFamilyStore store = Keyspace.open(KS_NAME).getColumnFamilyStore(CLUSTRING_CF_NAME);
+ ColumnFamilyStore store = Keyspace.open(KS_NAME).getColumnFamilyStore(CLUSTERING_CF_NAME);
executeCQL("INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Pavel", "US", 27, 183, 1.0);
executeCQL("INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Pavel", "BY", 28, 182, 2.0);
@@ -1621,6 +1637,55 @@ public class SASIIndexTest
results = executeCQL("SELECT * FROM %s.%s WHERE location = ? ALLOW FILTERING", "BY");
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
+
+ results = executeCQL("SELECT * FROM %s.%s WHERE location LIKE 'U%%' ALLOW FILTERING");
+ Assert.assertNotNull(results);
+ Assert.assertEquals(2, results.size());
+
+ results = executeCQL("SELECT * FROM %s.%s WHERE location LIKE 'U%%' AND height >= 183 ALLOW FILTERING");
+ Assert.assertNotNull(results);
+ Assert.assertEquals(1, results.size());
+
+ results = executeCQL("SELECT * FROM %s.%s WHERE location LIKE 'US%%' ALLOW FILTERING");
+ Assert.assertNotNull(results);
+ Assert.assertEquals(2, results.size());
+
+ results = executeCQL("SELECT * FROM %s.%s WHERE location LIKE 'US' ALLOW FILTERING");
+ Assert.assertNotNull(results);
+ Assert.assertEquals(2, results.size());
+
+ try
+ {
+ executeCQL("SELECT * FROM %s.%s WHERE location LIKE '%%U' ALLOW FILTERING");
+ Assert.fail();
+ }
+ catch (InvalidRequestException e)
+ {
+ Assert.assertTrue(e.getMessage().contains("only supported"));
+ // expected
+ }
+
+ try
+ {
+ executeCQL("SELECT * FROM %s.%s WHERE location LIKE '%%' ALLOW FILTERING");
+ Assert.fail();
+ }
+ catch (SyntaxException e)
+ {
+ Assert.assertTrue(e.getMessage().contains("empty"));
+ // expected
+ }
+
+ try
+ {
+ executeCQL("SELECT * FROM %s.%s WHERE location LIKE '%%%%' ALLOW FILTERING");
+ Assert.fail();
+ }
+ catch (SyntaxException e)
+ {
+ Assert.assertTrue(e.getMessage().contains("empty"));
+ // expected
+ }
}
private static ColumnFamilyStore loadData(Map<String, Pair<String, Integer>> data, boolean forceFlush)
@@ -1645,7 +1710,7 @@ public class SASIIndexTest
{
Keyspace ks = Keyspace.open(KS_NAME);
ks.getColumnFamilyStore(CF_NAME).truncateBlocking();
- ks.getColumnFamilyStore(CLUSTRING_CF_NAME).truncateBlocking();
+ ks.getColumnFamilyStore(CLUSTERING_CF_NAME).truncateBlocking();
}
private static Set<String> getIndexed(ColumnFamilyStore store, int maxResults, Expression... expressions)
@@ -1759,10 +1824,10 @@ public class SASIIndexTest
private UntypedResultSet executeCQL(String query, Object... values)
{
- return QueryProcessor.executeOnceInternal(String.format(query, KS_NAME, CLUSTRING_CF_NAME), values);
+ return QueryProcessor.executeOnceInternal(String.format(query, KS_NAME, CLUSTERING_CF_NAME), values);
}
- private Set<String> executeCQL(String rawStatement) throws Exception
+ private Set<String> executeCQLWithKeys(String rawStatement) throws Exception
{
SelectStatement statement = (SelectStatement) QueryProcessor.parseStatement(rawStatement).prepare().statement;
ResultMessage.Rows cqlRows = statement.executeInternal(QueryState.forInternalCalls(), QueryOptions.DEFAULT);
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/test/unit/org/apache/cassandra/index/sasi/disk/OnDiskIndexTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/index/sasi/disk/OnDiskIndexTest.java b/test/unit/org/apache/cassandra/index/sasi/disk/OnDiskIndexTest.java
index 57629b6..5e6b8b0 100644
--- a/test/unit/org/apache/cassandra/index/sasi/disk/OnDiskIndexTest.java
+++ b/test/unit/org/apache/cassandra/index/sasi/disk/OnDiskIndexTest.java
@@ -135,7 +135,7 @@ public class OnDiskIndexTest
for (Map.Entry<ByteBuffer, TokenTreeBuilder> e : data.entrySet())
{
- Assert.assertEquals(convert(e.getValue()), convert(onDisk.search(expressionFor(Int32Type.instance, e.getKey()))));
+ Assert.assertEquals(convert(e.getValue()), convert(onDisk.search(expressionFor(Operator.EQ, Int32Type.instance, e.getKey()))));
}
List<ByteBuffer> sortedNumbers = new ArrayList<ByteBuffer>()
@@ -536,8 +536,8 @@ public class OnDiskIndexTest
ByteBuffer number = Int32Type.instance.decompose(5);
- Assert.assertEquals(Collections.singleton(data.get(number).left), convert(onDisk1.search(expressionFor(Int32Type.instance, number))));
- Assert.assertEquals(Collections.singleton(data.get(number).left), convert(onDisk2.search(expressionFor(Int32Type.instance, number))));
+ Assert.assertEquals(Collections.singleton(data.get(number).left), convert(onDisk1.search(expressionFor(Operator.EQ, Int32Type.instance, number))));
+ Assert.assertEquals(Collections.singleton(data.get(number).left), convert(onDisk2.search(expressionFor(Operator.EQ, Int32Type.instance, number))));
Assert.assertEquals(onDisk1.descriptor.version.version, Descriptor.CURRENT_VERSION);
Assert.assertEquals(onDisk2.descriptor.version.version, Descriptor.VERSION_AA);
@@ -789,8 +789,13 @@ public class OnDiskIndexTest
private static Expression expressionFor(AbstractType<?> validator, ByteBuffer term)
{
+ return expressionFor(Operator.LIKE_CONTAINS, validator, term);
+ }
+
+ private static Expression expressionFor(Operator op, AbstractType<?> validator, ByteBuffer term)
+ {
Expression expression = new Expression("", validator);
- expression.add(Operator.EQ, term);
+ expression.add(op, term);
return expression;
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java b/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java
index d01e0a0..4f38b92 100644
--- a/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java
+++ b/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java
@@ -492,13 +492,13 @@ public class OperationTest extends SchemaLoader
System.currentTimeMillis()));
Operation.Builder builder = new Operation.Builder(OperationType.AND, controller,
- new SimpleExpression(comment, Operator.EQ, UTF8Type.instance.decompose("eng is a work")));
+ new SimpleExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("eng is a work")));
Operation op = builder.complete();
Assert.assertTrue(op.satisfiedBy(row, false));
builder = new Operation.Builder(OperationType.AND, controller,
- new SimpleExpression(comment, Operator.EQ, UTF8Type.instance.decompose("soft works fine")));
+ new SimpleExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("soft works fine")));
op = builder.complete();
Assert.assertTrue(op.satisfiedBy(row, false));
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java b/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java
index 3f34102..f2746f6 100644
--- a/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java
+++ b/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java
@@ -24,7 +24,9 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.util.Arrays;
+import java.util.concurrent.ThreadLocalRandom;
+import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.io.util.DataOutputBuffer;
@@ -247,4 +249,55 @@ public class ByteBufferUtilTest
assertEquals(bb, bb2);
assertEquals("0102", s);
}
+
+ @Test
+ public void testStartsAndEndsWith()
+ {
+ byte[] bytes = new byte[512];
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+
+ random.nextBytes(bytes);
+
+ ByteBuffer a = ByteBuffer.wrap(bytes);
+ ByteBuffer b = a.duplicate();
+
+ // let's take random slices of a and match
+ for (int i = 0; i < 512; i++)
+ {
+ // prefix from the original offset
+ b.position(0).limit(a.remaining() - random.nextInt(0, a.remaining() - 1));
+ Assert.assertTrue(ByteBufferUtil.startsWith(a, b));
+ Assert.assertTrue(ByteBufferUtil.startsWith(a, b.slice()));
+
+ // prefix from random position inside of array
+ int pos = random.nextInt(1, a.remaining() - 5);
+ a.position(pos);
+ b.limit(bytes.length - 1).position(pos);
+
+ Assert.assertTrue(ByteBufferUtil.startsWith(a, b));
+
+ a.position(0);
+
+ // endsWith at random position
+ b.limit(a.remaining()).position(random.nextInt(0, a.remaining() - 1));
+ Assert.assertTrue(ByteBufferUtil.endsWith(a, b));
+ Assert.assertTrue(ByteBufferUtil.endsWith(a, b.slice()));
+
+ }
+
+ a.limit(bytes.length - 1).position(0);
+ b.limit(bytes.length - 1).position(1);
+
+ Assert.assertFalse(ByteBufferUtil.startsWith(a, b));
+ Assert.assertFalse(ByteBufferUtil.startsWith(a, b.slice()));
+
+ Assert.assertTrue(ByteBufferUtil.endsWith(a, b));
+ Assert.assertTrue(ByteBufferUtil.endsWith(a, b.slice()));
+
+
+ a.position(5);
+
+ Assert.assertFalse(ByteBufferUtil.startsWith(a, b));
+ Assert.assertFalse(ByteBufferUtil.endsWith(a, b));
+ }
}
[2/2] cassandra git commit: add LIKE support to CQL3 patch by xedin;
reviewed by beobal for CASSANDRA-11067
Posted by xe...@apache.org.
add LIKE support to CQL3
patch by xedin; reviewed by beobal for CASSANDRA-11067
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/39286654
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/39286654
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/39286654
Branch: refs/heads/trunk
Commit: 392866546e6377aafd018c3fcb2e124674a318dc
Parents: 64cfcf0
Author: Pavel Yaskevich <xe...@apache.org>
Authored: Mon Jan 25 19:13:36 2016 -0800
Committer: Pavel Yaskevich <xe...@apache.org>
Committed: Fri Feb 5 11:56:23 2016 -0800
----------------------------------------------------------------------
CHANGES.txt | 1 +
doc/SASI.md | 64 +--
src/java/org/apache/cassandra/cql3/Cql.g | 43 ++
.../cassandra/cql3/MultiColumnRelation.java | 6 +
.../org/apache/cassandra/cql3/Operator.java | 31 ++
.../org/apache/cassandra/cql3/Relation.java | 15 +
.../cassandra/cql3/SingleColumnRelation.java | 18 +-
.../apache/cassandra/cql3/TokenRelation.java | 6 +
.../cql3/restrictions/AbstractRestriction.java | 6 +
.../ForwardingPrimaryKeyRestrictions.java | 6 +
.../cql3/restrictions/Restriction.java | 1 +
.../restrictions/SingleColumnRestriction.java | 78 ++++
.../restrictions/StatementRestrictions.java | 22 +-
.../apache/cassandra/db/filter/RowFilter.java | 3 +
.../apache/cassandra/index/sasi/SASIIndex.java | 21 +-
.../cassandra/index/sasi/conf/ColumnIndex.java | 6 +
.../cassandra/index/sasi/conf/IndexMode.java | 6 +
.../index/sasi/conf/view/PrefixTermTree.java | 11 +-
.../index/sasi/conf/view/RangeTermTree.java | 40 +-
.../cassandra/index/sasi/conf/view/View.java | 34 +-
.../cassandra/index/sasi/disk/OnDiskIndex.java | 16 +
.../index/sasi/disk/OnDiskIndexBuilder.java | 17 +-
.../index/sasi/memory/TrieMemIndex.java | 41 +-
.../cassandra/index/sasi/plan/Expression.java | 65 ++-
.../cassandra/index/sasi/plan/Operation.java | 32 +-
.../apache/cassandra/utils/ByteBufferUtil.java | 32 ++
.../unit/org/apache/cassandra/SchemaLoader.java | 1 +
.../cassandra/index/sasi/SASIIndexTest.java | 409 +++++++++++--------
.../index/sasi/disk/OnDiskIndexTest.java | 13 +-
.../index/sasi/plan/OperationTest.java | 4 +-
.../cassandra/utils/ByteBufferUtilTest.java | 53 +++
31 files changed, 855 insertions(+), 246 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 3fcca42..e7cde67 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
3.4
+ * Add LIKE support to CQL3 (CASSANDRA-11067)
* Generic Java UDF types (CASSANDRA-10819)
* cqlsh: Include sub-second precision in timestamps by default (CASSANDRA-10428)
* Set javac encoding to utf-8 (CASSANDRA-11077)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/doc/SASI.md
----------------------------------------------------------------------
diff --git a/doc/SASI.md b/doc/SASI.md
index 64573b8..90fcf65 100644
--- a/doc/SASI.md
+++ b/doc/SASI.md
@@ -124,18 +124,34 @@ cqlsh:demo> SELECT first_name, last_name, age, height, created_at FROM sasi;
#### Equality & Prefix Queries
-SASI supports simple queries already supported by CQL, however, for
-text fields like `first_name` equals queries perform prefix searches
--- this is similar to `first_name LIKE 'M*'` in SQL (excluding case
-sensitivity, which is dependent on the index configuration). The
-semantics of CQL's `=` were modified instead of making further
-modifications of the grammar with the introduction of a `LIKE`
-operator. Ideally, CQL would be modified to include such an operator,
-supporting both prefix and suffix searches.
+SASI supports all queries already supported by CQL, including LIKE statement
+for PREFIX, CONTAINS and SUFFIX searches.
```
cqlsh:demo> SELECT first_name, last_name, age, height, created_at FROM sasi
- ... WHERE first_name = 'M';
+ ... WHERE first_name = 'Pavel';
+
+ first_name | last_name | age | height | created_at
+-------------+-----------+-----+--------+---------------
+ Pavel | Yaskevich | 27 | 181 | 1442959315018
+
+(1 rows)
+```
+
+```
+cqlsh:demo> SELECT first_name, last_name, age, height, created_at FROM sasi
+ ... WHERE first_name = 'pavel';
+
+ first_name | last_name | age | height | created_at
+-------------+-----------+-----+--------+---------------
+ Pavel | Yaskevich | 27 | 181 | 1442959315018
+
+(1 rows)
+```
+
+```
+cqlsh:demo> SELECT first_name, last_name, age, height, created_at FROM sasi
+ ... WHERE first_name LIKE 'M%';
first_name | last_name | age | height | created_at
------------+-----------+-----+--------+---------------
@@ -150,7 +166,7 @@ column because of the options provided at index creation time.
```
cqlsh:demo> SELECT first_name, last_name, age, height, created_at FROM sasi
- ... WHERE first_name = 'm';
+ ... WHERE first_name LIKE 'm%';
first_name | last_name | age | height | created_at
------------+-----------+-----+--------+---------------
@@ -175,7 +191,7 @@ section.
```
cqlsh:demo> SELECT first_name, last_name, age, height, created_at FROM sasi
- ... WHERE first_name = 'M' and age < 30 ALLOW FILTERING;
+ ... WHERE first_name LIKE 'M%' and age < 30 ALLOW FILTERING;
first_name | last_name | age | height | created_at
------------+-----------+-----+--------+---------------
@@ -192,7 +208,7 @@ containing the search string as a sub-string. In this case the strings
containing "a" or "an".
```
-cqlsh:demo> SELECT * FROM sasi WHERE last_name = 'a';
+cqlsh:demo> SELECT * FROM sasi WHERE last_name LIKE '%a%';
id | age | created_at | first_name | height | last_name
--------------------------------------+-----+---------------+------------+--------+---------------
@@ -204,7 +220,7 @@ cqlsh:demo> SELECT * FROM sasi WHERE last_name = 'a';
(5 rows)
-cqlsh:demo> SELECT * FROM sasi WHERE last_name = 'an';
+cqlsh:demo> SELECT * FROM sasi WHERE last_name LIKE '%an%';
id | age | created_at | first_name | height | last_name
--------------------------------------+-----+---------------+------------+--------+-----------
@@ -220,7 +236,7 @@ SASI also supports filtering on non-indexed columns like `height`. The
expression can only narrow down an existing query using `AND`.
```
-cqlsh:demo> SELECT * FROM sasi WHERE last_name = 'a' AND height >= 175 ALLOW FILTERING;
+cqlsh:demo> SELECT * FROM sasi WHERE last_name LIKE '%a%' AND height >= 175 ALLOW FILTERING;
id | age | created_at | first_name | height | last_name
--------------------------------------+-----+---------------+------------+--------+---------------
@@ -274,7 +290,7 @@ property but for the
query demonstrates the stemming applied by [`StandardAnalyzer`](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/index/sasi/analyzer/StandardAnalyzer.java).
```
-cqlsh:demo> SELECT * FROM sasi WHERE bio = 'distributing';
+cqlsh:demo> SELECT * FROM sasi WHERE bio LIKE 'distributing';
id | age | bio | created_at | first_name | height | last_name
--------------------------------------+-----+----------------------------------------------------------------------------------+---------------+------------+--------+-----------
@@ -283,7 +299,7 @@ cqlsh:demo> SELECT * FROM sasi WHERE bio = 'distributing';
(2 rows)
-cqlsh:demo> SELECT * FROM sasi WHERE bio = 'they argued';
+cqlsh:demo> SELECT * FROM sasi WHERE bio LIKE 'they argued';
id | age | bio | created_at | first_name | height | last_name
--------------------------------------+-----+----------------------------------------------------------------------------------+---------------+------------+--------+-----------
@@ -292,7 +308,7 @@ cqlsh:demo> SELECT * FROM sasi WHERE bio = 'they argued';
(2 rows)
-cqlsh:demo> SELECT * FROM sasi WHERE bio = 'working at the company';
+cqlsh:demo> SELECT * FROM sasi WHERE bio LIKE 'working at the company';
id | age | bio | created_at | first_name | height | last_name
--------------------------------------+-----+----------------------------------------------------------------------------------+---------------+------------+--------+-----------
@@ -300,7 +316,7 @@ cqlsh:demo> SELECT * FROM sasi WHERE bio = 'working at the company';
(1 rows)
-cqlsh:demo> SELECT * FROM sasi WHERE bio = 'soft eng';
+cqlsh:demo> SELECT * FROM sasi WHERE bio LIKE 'soft eng';
id | age | bio | created_at | first_name | height | last_name
--------------------------------------+-----+----------------------------------------------------------------------------------+---------------+------------+--------+-----------
@@ -709,7 +725,7 @@ the `LookupIntersectionIterator`, otherwise the
### The SASIIndex Class
The above components are glued together by the
-[`SASIIndex`](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/index/sasiIndex.java)
+[`SASIIndex`](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/index/sasi/SASIIndex.java)
class which implements `Index`, and is instantiated
per-table containing SASI indexes. It manages all indexes for a table
via the
@@ -718,11 +734,11 @@ and
[`sasi.conf.view.View`](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/index/sasi/conf/view/View.java)
components, controls writing of all indexes for an SSTable via its
[`PerSSTableIndexWriter`](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/index/sasi/disk/PerSSTableIndexWriter.java), and initiates searches with
-`Indexer`. These classes glue the previously
+`Searcher`. These classes glue the previously
mentioned indexing components together with Cassandra's SSTable
life-cycle ensuring indexes are not only written when Memtable's flush
but also as SSTable's are compacted. For querying, the
-`Indexer` does little but defer to
+`Searcher` does little but defer to
[`QueryPlan`](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/index/sasi/plan/QueryPlan.java)
and update e.g. latency metrics exposed by SASI.
@@ -750,12 +766,6 @@ available in this repository or are not currently implemented.
`LongToken`s, e.g. `Murmur3Partitioner`. Other existing partitioners which
don't produce LongToken e.g. `ByteOrderedPartitioner` and `RandomPartitioner`
will not work with SASI.
-* `ALLOW FILTERING`, the requirement of at least one indexes `=`
- expression, and lack of `LIKE` limit SASIs
- feature-set. Modifications to the grammar to allow `Index`
- implementations to enumerate its supported features would allow SASI
- to expose more features without need to support them in other
- implementations.
* Not Equals and OR support have been removed in this release while
changes are made to Cassandra itself to support them.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g
index 6b4eff6..d560119 100644
--- a/src/java/org/apache/cassandra/cql3/Cql.g
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -182,6 +182,46 @@ options {
return filtered;
}
+
+ public void buildLIKERelation(WhereClause.Builder whereClause, ColumnIdentifier.Raw name, String likeValue)
+ {
+ Operator operator;
+ int beginIndex = 0;
+ int endIndex = likeValue.length() - 1;
+
+ if (likeValue.charAt(endIndex) == '\%')
+ {
+ if (likeValue.charAt(beginIndex) == '\%')
+ {
+ operator = Operator.LIKE_CONTAINS;
+ beginIndex =+ 1;
+ }
+ else
+ {
+ operator = Operator.LIKE_PREFIX;
+ }
+ }
+ else if (likeValue.charAt(beginIndex) == '\%')
+ {
+ operator = Operator.LIKE_SUFFIX;
+ beginIndex += 1;
+ endIndex += 1;
+ }
+ else
+ {
+ operator = Operator.EQ;
+ endIndex += 1;
+ }
+
+ if (endIndex == 0 || beginIndex == endIndex)
+ {
+ addRecognitionError("LIKE value can't be empty.");
+ return;
+ }
+
+ String value = likeValue.substring(beginIndex, endIndex);
+ whereClause.add(new SingleColumnRelation(name, operator, Constants.Literal.string(value)));
+ }
}
@lexer::header {
@@ -1423,6 +1463,7 @@ relationType returns [Operator op]
relation[WhereClause.Builder clauses]
: name=cident type=relationType t=term { $clauses.add(new SingleColumnRelation(name, type, t)); }
+ | name=cident K_LIKE v=STRING_LITERAL { buildLIKERelation($clauses, name, $v.text); }
| name=cident K_IS K_NOT K_NULL { $clauses.add(new SingleColumnRelation(name, Operator.IS_NOT, Constants.NULL_LITERAL)); }
| K_TOKEN l=tupleOfIdentifiers type=relationType t=term
{ $clauses.add(new TokenRelation(l, type, t)); }
@@ -1628,6 +1669,7 @@ basic_unreserved_keyword returns [String str]
| K_JSON
| K_CALLED
| K_INPUT
+ | K_LIKE
) { $str = $k.text; }
;
@@ -1767,6 +1809,7 @@ K_OR: O R;
K_REPLACE: R E P L A C E;
K_JSON: J S O N;
+K_LIKE: L I K E;
// Case-insensitive alpha characters
fragment A: ('a'|'A');
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java b/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java
index 143106d..2c1f97b 100644
--- a/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java
+++ b/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java
@@ -183,6 +183,12 @@ public class MultiColumnRelation extends Relation
}
@Override
+ protected Restriction newLikeRestriction(CFMetaData cfm, VariableSpecifications boundNames, Operator operator) throws InvalidRequestException
+ {
+ throw invalidRequest("%s cannot be used for multi-column relations", operator());
+ }
+
+ @Override
protected Term toTerm(List<? extends ColumnSpecification> receivers,
Raw raw,
String keyspace,
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/Operator.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Operator.java b/src/java/org/apache/cassandra/cql3/Operator.java
index 7b28a30..d518961 100644
--- a/src/java/org/apache/cassandra/cql3/Operator.java
+++ b/src/java/org/apache/cassandra/cql3/Operator.java
@@ -29,6 +29,7 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.SetType;
+import org.apache.cassandra.utils.ByteBufferUtil;
public enum Operator
{
@@ -101,6 +102,30 @@ public enum Operator
{
return "IS NOT";
}
+ },
+ LIKE_PREFIX(10)
+ {
+ @Override
+ public String toString()
+ {
+ return "LIKE '<term>%'";
+ }
+ },
+ LIKE_SUFFIX(11)
+ {
+ @Override
+ public String toString()
+ {
+ return "LIKE '%<term>'";
+ }
+ },
+ LIKE_CONTAINS(12)
+ {
+ @Override
+ public String toString()
+ {
+ return "LIKE '%<term>%'";
+ }
};
/**
@@ -193,6 +218,12 @@ public enum Operator
case CONTAINS_KEY:
Map map = (Map) type.getSerializer().deserialize(leftOperand);
return map.containsKey(((MapType) type).getKeysType().getSerializer().deserialize(rightOperand));
+ case LIKE_PREFIX:
+ return ByteBufferUtil.startsWith(leftOperand, rightOperand);
+ case LIKE_SUFFIX:
+ return ByteBufferUtil.endsWith(leftOperand, rightOperand);
+ case LIKE_CONTAINS:
+ return ByteBufferUtil.contains(leftOperand, rightOperand);
default:
// we shouldn't get CONTAINS, CONTAINS KEY, or IS NOT here
throw new AssertionError();
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/Relation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Relation.java b/src/java/org/apache/cassandra/cql3/Relation.java
index 334464f..81f46a6 100644
--- a/src/java/org/apache/cassandra/cql3/Relation.java
+++ b/src/java/org/apache/cassandra/cql3/Relation.java
@@ -108,6 +108,13 @@ public abstract class Relation {
return relationType == Operator.EQ;
}
+ public final boolean isLIKE()
+ {
+ return relationType == Operator.LIKE_PREFIX
+ || relationType == Operator.LIKE_SUFFIX
+ || relationType == Operator.LIKE_CONTAINS;
+ }
+
/**
* Checks if the operator of this relation is a <code>Slice</code> (GT, GTE, LTE, LT).
*
@@ -143,6 +150,10 @@ public abstract class Relation {
case CONTAINS: return newContainsRestriction(cfm, boundNames, false);
case CONTAINS_KEY: return newContainsRestriction(cfm, boundNames, true);
case IS_NOT: return newIsNotRestriction(cfm, boundNames);
+ case LIKE_PREFIX:
+ case LIKE_SUFFIX:
+ case LIKE_CONTAINS:
+ return newLikeRestriction(cfm, boundNames, relationType);
default: throw invalidRequest("Unsupported \"!=\" relation: %s", this);
}
}
@@ -200,6 +211,10 @@ public abstract class Relation {
protected abstract Restriction newIsNotRestriction(CFMetaData cfm,
VariableSpecifications boundNames) throws InvalidRequestException;
+ protected abstract Restriction newLikeRestriction(CFMetaData cfm,
+ VariableSpecifications boundNames,
+ Operator operator) throws InvalidRequestException;
+
/**
* Converts the specified <code>Raw</code> into a <code>Term</code>.
* @param receivers the columns to which the values must be associated at
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
index 867a919..9053514 100644
--- a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
+++ b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
@@ -34,6 +34,7 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
+import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
/**
* Relations encapsulate the relationship between an entity of some kind, and
@@ -150,6 +151,9 @@ public final class SingleColumnRelation extends Relation
if (isIN())
return String.format("%s IN %s", entityAsString, inValues);
+ if (isLIKE())
+ return String.format("%s %s", entityAsString, relationType);
+
return String.format("%s %s %s", entityAsString, relationType, value);
}
@@ -215,6 +219,18 @@ public final class SingleColumnRelation extends Relation
return new SingleColumnRestriction.IsNotNullRestriction(columnDef);
}
+ @Override
+ protected Restriction newLikeRestriction(CFMetaData cfm, VariableSpecifications boundNames, Operator operator) throws InvalidRequestException
+ {
+ if (mapKey != null)
+ throw invalidRequest("%s can't be used with collections.", operator());
+
+ ColumnDefinition columnDef = toColumnDefinition(cfm, entity);
+ Term term = toTerm(toReceivers(columnDef, cfm.isDense()), value, cfm.ksName, boundNames);
+
+ return new SingleColumnRestriction.LikeRestriction(columnDef, operator, term);
+ }
+
/**
* Returns the receivers for this relation.
* @param columnDef the column definition
@@ -305,6 +321,6 @@ public final class SingleColumnRelation extends Relation
private boolean canHaveOnlyOneValue()
{
- return isEQ() || (isIN() && inValues != null && inValues.size() == 1);
+ return isEQ() || isLIKE() || (isIN() && inValues != null && inValues.size() == 1);
}
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/TokenRelation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/TokenRelation.java b/src/java/org/apache/cassandra/cql3/TokenRelation.java
index 2c13b19..8b8b9ce 100644
--- a/src/java/org/apache/cassandra/cql3/TokenRelation.java
+++ b/src/java/org/apache/cassandra/cql3/TokenRelation.java
@@ -112,6 +112,12 @@ public final class TokenRelation extends Relation
}
@Override
+ protected Restriction newLikeRestriction(CFMetaData cfm, VariableSpecifications boundNames, Operator operator) throws InvalidRequestException
+ {
+ throw invalidRequest("%s cannot be used with the token function", operator);
+ }
+
+ @Override
protected Term toTerm(List<? extends ColumnSpecification> receivers,
Raw raw,
String keyspace,
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
index 023c2ac..932d3ae 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
@@ -51,6 +51,12 @@ abstract class AbstractRestriction implements Restriction
}
@Override
+ public boolean isLIKE()
+ {
+ return false;
+ }
+
+ @Override
public boolean isIN()
{
return false;
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
index 18e7105..4afac42 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
@@ -155,6 +155,12 @@ abstract class ForwardingPrimaryKeyRestrictions implements PrimaryKeyRestriction
}
@Override
+ public boolean isLIKE()
+ {
+ return getDelegate().isLIKE();
+ }
+
+ @Override
public boolean isIN()
{
return getDelegate().isIN();
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java b/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
index a84ebc4..2f066e0 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
@@ -39,6 +39,7 @@ public interface Restriction
public boolean isOnToken();
public boolean isSlice();
public boolean isEQ();
+ public boolean isLIKE();
public boolean isIN();
public boolean isContains();
public boolean isNotNull();
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
index d851253..318c49b 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
@@ -645,4 +645,82 @@ public abstract class SingleColumnRestriction extends AbstractRestriction
return index.supportsExpression(columnDef, Operator.IS_NOT);
}
}
+
+ public static final class LikeRestriction extends SingleColumnRestriction
+ {
+ private final Operator operator;
+ private final Term value;
+
+ public LikeRestriction(ColumnDefinition columnDef, Operator operator, Term value)
+ {
+ super(columnDef);
+ this.operator = operator;
+ this.value = value;
+ }
+
+ @Override
+ public Iterable<Function> getFunctions()
+ {
+ return value.getFunctions();
+ }
+
+ @Override
+ public boolean isEQ()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isLIKE()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean canBeConvertedToMultiColumnRestriction()
+ {
+ return false;
+ }
+
+ @Override
+ MultiColumnRestriction toMultiColumnRestriction()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addRowFilterTo(RowFilter filter,
+ SecondaryIndexManager indexManager,
+ QueryOptions options)
+ {
+ filter.add(columnDef, operator, value.bindAndGet(options));
+ }
+
+ @Override
+ public MultiCBuilder appendTo(MultiCBuilder builder, QueryOptions options)
+ {
+ // LIKE could be used with clustering columns as soon as they are indexed,
+ // but we have to hide such expression from clustering filter since it
+ // can only filter based on the complete values.
+ return builder;
+ }
+
+ @Override
+ public String toString()
+ {
+ return operator.toString();
+ }
+
+ @Override
+ public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
+ {
+ throw invalidRequest("%s cannot be restricted by more than one relation if it includes a %s", columnDef.name, operator);
+ }
+
+ @Override
+ protected boolean isSupportedBy(Index index)
+ {
+ return index.supportsExpression(columnDef, operator);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
index fee17c2..ccd4941 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@ -134,6 +134,16 @@ public final class StatementRestrictions
{
this(type, cfm);
+
+ ColumnFamilyStore cfs;
+ SecondaryIndexManager secondaryIndexManager = null;
+
+ if (type.allowUseOfSecondaryIndices())
+ {
+ cfs = Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfName);
+ secondaryIndexManager = cfs.indexManager;
+ }
+
/*
* WHERE clause. For a given entity, rules are:
* - EQ relation conflicts with anything else (including a 2nd EQ)
@@ -153,6 +163,15 @@ public final class StatementRestrictions
for (ColumnDefinition def : relation.toRestriction(cfm, boundNames).getColumnDefs())
this.notNullColumns.add(def);
}
+ else if (relation.isLIKE())
+ {
+ Restriction restriction = relation.toRestriction(cfm, boundNames);
+
+ if (!type.allowUseOfSecondaryIndices() || !restriction.hasSupportingIndex(secondaryIndexManager))
+ throw new InvalidRequestException(relation + " restriction is only supported on properly indexed columns");
+
+ addRestriction(restriction);
+ }
else
{
addRestriction(relation.toRestriction(cfm, boundNames));
@@ -164,9 +183,6 @@ public final class StatementRestrictions
if (type.allowUseOfSecondaryIndices())
{
- ColumnFamilyStore cfs = Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfName);
- SecondaryIndexManager secondaryIndexManager = cfs.indexManager;
-
if (whereClause.containsCustomExpressions())
processCustomIndexExpressions(whereClause.expressions, boundNames, secondaryIndexManager);
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/db/filter/RowFilter.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/filter/RowFilter.java b/src/java/org/apache/cassandra/db/filter/RowFilter.java
index 79bdbd7..1141fd9 100644
--- a/src/java/org/apache/cassandra/db/filter/RowFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/RowFilter.java
@@ -601,6 +601,9 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
case GTE:
case GT:
case NEQ:
+ case LIKE_PREFIX:
+ case LIKE_SUFFIX:
+ case LIKE_CONTAINS:
{
assert !column.isComplex() : "Only CONTAINS and CONTAINS_KEY are supported for 'complex' types";
ByteBuffer foundValue = getValue(metadata, partitionKey, row);
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/SASIIndex.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/SASIIndex.java b/src/java/org/apache/cassandra/index/sasi/SASIIndex.java
index d69b440..012d923 100644
--- a/src/java/org/apache/cassandra/index/sasi/SASIIndex.java
+++ b/src/java/org/apache/cassandra/index/sasi/SASIIndex.java
@@ -28,8 +28,10 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.db.*;
+import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.filter.RowFilter;
+import org.apache.cassandra.db.lifecycle.Tracker;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.partitions.PartitionUpdate;
@@ -96,7 +98,22 @@ public class SASIIndex implements Index, INotificationConsumer
ColumnDefinition column = CassandraIndex.parseTarget(baseCfs.metadata, config).left;
this.index = new ColumnIndex(baseCfs.metadata.getKeyValidator(), column, config);
- baseCfs.getTracker().subscribe(this);
+ Tracker tracker = baseCfs.getTracker();
+ tracker.subscribe(this);
+
+ SortedMap<SSTableReader, Map<ColumnDefinition, ColumnIndex>> toRebuild = new TreeMap<>((a, b)
+ -> Integer.compare(a.descriptor.generation, b.descriptor.generation));
+
+ for (SSTableReader sstable : index.init(tracker.getView().liveSSTables()))
+ {
+ Map<ColumnDefinition, ColumnIndex> perSSTable = toRebuild.get(sstable);
+ if (perSSTable == null)
+ toRebuild.put(sstable, (perSSTable = new HashMap<>()));
+
+ perSSTable.put(index.getDefinition(), index);
+ }
+
+ CompactionManager.instance.submitIndexBuild(new SASIIndexBuilder(baseCfs, toRebuild));
}
public static Map<String, String> validateOptions(Map<String, String> options)
@@ -164,7 +181,7 @@ public class SASIIndex implements Index, INotificationConsumer
public boolean supportsExpression(ColumnDefinition column, Operator operator)
{
- return dependsOn(column);
+ return dependsOn(column) && index.supports(operator);
}
public AbstractType<?> customExpressionValueType()
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java b/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java
index 0bc9a96..3363d21 100644
--- a/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java
+++ b/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java
@@ -25,6 +25,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.cassandra.config.ColumnDefinition;
+import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.AsciiType;
@@ -175,6 +176,11 @@ public class ColumnIndex
return isIndexed() ? mode.isLiteral : (validator instanceof UTF8Type || validator instanceof AsciiType);
}
+ public boolean supports(Operator operator)
+ {
+ return mode.supports(Expression.Op.valueOf(operator));
+ }
+
public static ByteBuffer getValueOf(ColumnDefinition column, Row row, int nowInSecs)
{
switch (column.kind)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/conf/IndexMode.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/conf/IndexMode.java b/src/java/org/apache/cassandra/index/sasi/conf/IndexMode.java
index 628a6ce..41ed718 100644
--- a/src/java/org/apache/cassandra/index/sasi/conf/IndexMode.java
+++ b/src/java/org/apache/cassandra/index/sasi/conf/IndexMode.java
@@ -32,6 +32,7 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.index.sasi.plan.Expression.Op;
import org.apache.cassandra.schema.IndexMetadata;
import org.slf4j.Logger;
@@ -166,4 +167,9 @@ public class IndexMode
return new IndexMode(mode, isLiteral, isAnalyzed, analyzerClass, maxMemMb);
}
+
+ public boolean supports(Op operator)
+ {
+ return !(isLiteral && operator == Op.RANGE) && mode.supports(operator);
+ }
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/conf/view/PrefixTermTree.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/conf/view/PrefixTermTree.java b/src/java/org/apache/cassandra/index/sasi/conf/view/PrefixTermTree.java
index 72b6daf..f7cd942 100644
--- a/src/java/org/apache/cassandra/index/sasi/conf/view/PrefixTermTree.java
+++ b/src/java/org/apache/cassandra/index/sasi/conf/view/PrefixTermTree.java
@@ -48,10 +48,11 @@ public class PrefixTermTree extends RangeTermTree
public PrefixTermTree(ByteBuffer min, ByteBuffer max,
Trie<ByteBuffer, Set<SSTableIndex>> trie,
- IntervalTree<ByteBuffer, SSTableIndex, Interval<ByteBuffer, SSTableIndex>> ranges,
- OnDiskIndexBuilder.Mode mode)
+ IntervalTree<Term, SSTableIndex, Interval<Term, SSTableIndex>> ranges,
+ OnDiskIndexBuilder.Mode mode,
+ AbstractType<?> comparator)
{
- super(min, max, ranges);
+ super(min, max, ranges, comparator);
this.mode = mode;
this.trie = trie;
@@ -64,7 +65,6 @@ public class PrefixTermTree extends RangeTermTree
Set<SSTableIndex> view = new HashSet<>(indexes.size());
indexes.values().forEach(view::addAll);
-
return Sets.union(view, super.search(e));
}
@@ -76,6 +76,7 @@ public class PrefixTermTree extends RangeTermTree
{
super(mode, comparator);
trie = new PatriciaTrie<>(new ByteBufferKeyAnalyzer(comparator));
+
}
public void addIndex(SSTableIndex index)
@@ -87,7 +88,7 @@ public class PrefixTermTree extends RangeTermTree
public TermTree build()
{
- return new PrefixTermTree(min, max, trie, IntervalTree.build(intervals), mode);
+ return new PrefixTermTree(min, max, trie, IntervalTree.build(intervals), mode, comparator);
}
private void addTerm(ByteBuffer term, SSTableIndex index)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/conf/view/RangeTermTree.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/conf/view/RangeTermTree.java b/src/java/org/apache/cassandra/index/sasi/conf/view/RangeTermTree.java
index 62e5636..d6b4551 100644
--- a/src/java/org/apache/cassandra/index/sasi/conf/view/RangeTermTree.java
+++ b/src/java/org/apache/cassandra/index/sasi/conf/view/RangeTermTree.java
@@ -33,13 +33,15 @@ import org.apache.cassandra.utils.IntervalTree;
public class RangeTermTree implements TermTree
{
protected final ByteBuffer min, max;
- protected final IntervalTree<ByteBuffer, SSTableIndex, Interval<ByteBuffer, SSTableIndex>> rangeTree;
+ protected final IntervalTree<Term, SSTableIndex, Interval<Term, SSTableIndex>> rangeTree;
+ protected final AbstractType<?> comparator;
- public RangeTermTree(ByteBuffer min, ByteBuffer max, IntervalTree<ByteBuffer, SSTableIndex, Interval<ByteBuffer, SSTableIndex>> rangeTree)
+ public RangeTermTree(ByteBuffer min, ByteBuffer max, IntervalTree<Term, SSTableIndex, Interval<Term, SSTableIndex>> rangeTree, AbstractType<?> comparator)
{
this.min = min;
this.max = max;
this.rangeTree = rangeTree;
+ this.comparator = comparator;
}
public Set<SSTableIndex> search(Expression e)
@@ -47,7 +49,9 @@ public class RangeTermTree implements TermTree
ByteBuffer minTerm = e.lower == null ? min : e.lower.value;
ByteBuffer maxTerm = e.upper == null ? max : e.upper.value;
- return new HashSet<>(rangeTree.search(Interval.create(minTerm, maxTerm, (SSTableIndex) null)));
+ return new HashSet<>(rangeTree.search(Interval.create(new Term(minTerm, comparator),
+ new Term(maxTerm, comparator),
+ (SSTableIndex) null)));
}
public int intervalCount()
@@ -57,7 +61,7 @@ public class RangeTermTree implements TermTree
static class Builder extends TermTree.Builder
{
- protected final List<Interval<ByteBuffer, SSTableIndex>> intervals = new ArrayList<>();
+ protected final List<Interval<Term, SSTableIndex>> intervals = new ArrayList<>();
protected Builder(OnDiskIndexBuilder.Mode mode, AbstractType<?> comparator)
{
@@ -66,12 +70,36 @@ public class RangeTermTree implements TermTree
public void addIndex(SSTableIndex index)
{
- intervals.add(Interval.create(index.minTerm(), index.maxTerm(), index));
+ intervals.add(Interval.create(new Term(index.minTerm(), comparator),
+ new Term(index.maxTerm(), comparator), index));
}
+
public TermTree build()
{
- return new RangeTermTree(min, max, IntervalTree.build(intervals));
+ return new RangeTermTree(min, max, IntervalTree.build(intervals), comparator);
+ }
+ }
+
+
+ /**
+ * This is required since IntervalTree doesn't support custom Comparator
+ * implementations and relied on items to be comparable which "raw" terms are not.
+ */
+ protected static class Term implements Comparable<Term>
+ {
+ private final ByteBuffer term;
+ private final AbstractType<?> comparator;
+
+ public Term(ByteBuffer term, AbstractType<?> comparator)
+ {
+ this.term = term;
+ this.comparator = comparator;
+ }
+
+ public int compareTo(Term o)
+ {
+ return comparator.compare(term, o.term);
}
}
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/conf/view/View.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/conf/view/View.java b/src/java/org/apache/cassandra/index/sasi/conf/view/View.java
index 378c3c6..505a4d7 100644
--- a/src/java/org/apache/cassandra/index/sasi/conf/view/View.java
+++ b/src/java/org/apache/cassandra/index/sasi/conf/view/View.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.index.sasi.conf.view;
import java.nio.ByteBuffer;
import java.util.*;
+import org.apache.cassandra.db.marshal.UUIDType;
import org.apache.cassandra.index.sasi.SSTableIndex;
import org.apache.cassandra.index.sasi.conf.ColumnIndex;
import org.apache.cassandra.index.sasi.plan.Expression;
@@ -39,7 +40,8 @@ public class View implements Iterable<SSTableIndex>
private final Map<Descriptor, SSTableIndex> view;
private final TermTree termTree;
- private final IntervalTree<ByteBuffer, SSTableIndex, Interval<ByteBuffer, SSTableIndex>> keyIntervalTree;
+ private final AbstractType<?> keyValidator;
+ private final IntervalTree<Key, SSTableIndex, Interval<Key, SSTableIndex>> keyIntervalTree;
public View(ColumnIndex index, Set<SSTableIndex> indexes)
{
@@ -58,7 +60,7 @@ public class View implements Iterable<SSTableIndex>
? new PrefixTermTree.Builder(index.getMode().mode, validator)
: new RangeTermTree.Builder(index.getMode().mode, validator);
- List<Interval<ByteBuffer, SSTableIndex>> keyIntervals = new ArrayList<>();
+ List<Interval<Key, SSTableIndex>> keyIntervals = new ArrayList<>();
for (SSTableIndex sstableIndex : Iterables.concat(currentView, newIndexes))
{
SSTableReader sstable = sstableIndex.getSSTable();
@@ -71,11 +73,14 @@ public class View implements Iterable<SSTableIndex>
newView.put(sstable.descriptor, sstableIndex);
termTreeBuilder.add(sstableIndex);
- keyIntervals.add(Interval.create(sstableIndex.minKey(), sstableIndex.maxKey(), sstableIndex));
+ keyIntervals.add(Interval.create(new Key(sstableIndex.minKey(), index.keyValidator()),
+ new Key(sstableIndex.maxKey(), index.keyValidator()),
+ sstableIndex));
}
this.view = newView;
this.termTree = termTreeBuilder.build();
+ this.keyValidator = index.keyValidator();
this.keyIntervalTree = IntervalTree.build(keyIntervals);
if (keyIntervalTree.intervalCount() != termTree.intervalCount())
@@ -89,7 +94,7 @@ public class View implements Iterable<SSTableIndex>
public List<SSTableIndex> match(ByteBuffer minKey, ByteBuffer maxKey)
{
- return keyIntervalTree.search(Interval.create(minKey, maxKey, (SSTableIndex) null));
+ return keyIntervalTree.search(Interval.create(new Key(minKey, keyValidator), new Key(maxKey, keyValidator), (SSTableIndex) null));
}
public Iterator<SSTableIndex> iterator()
@@ -101,4 +106,25 @@ public class View implements Iterable<SSTableIndex>
{
return view.values();
}
+
+ /**
+ * This is required since IntervalTree doesn't support custom Comparator
+ * implementations and relied on items to be comparable which "raw" keys are not.
+ */
+ private static class Key implements Comparable<Key>
+ {
+ private final ByteBuffer key;
+ private final AbstractType<?> comparator;
+
+ public Key(ByteBuffer key, AbstractType<?> comparator)
+ {
+ this.key = key;
+ this.comparator = comparator;
+ }
+
+ public int compareTo(Key o)
+ {
+ return comparator.compare(key, o.key);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndex.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndex.java b/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndex.java
index 0f9e389..a84e8bf 100644
--- a/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndex.java
+++ b/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndex.java
@@ -206,6 +206,16 @@ public class OnDiskIndex implements Iterable<OnDiskIndex.DataTerm>, Closeable
*/
public RangeIterator<Long, Token> search(Expression exp)
{
+ assert mode.supports(exp.getOp());
+
+ // optimization in case single term is requested from index
+ // we don't really need to build additional union iterator
+ if (exp.getOp() == Op.EQ)
+ {
+ DataTerm term = getTerm(exp.lower.value);
+ return term == null ? null : term.getTokens();
+ }
+
// convert single NOT_EQ to range with exclusion
final Expression expression = (exp.getOp() != Op.NOT_EQ)
? exp
@@ -426,6 +436,12 @@ public class OnDiskIndex implements Iterable<OnDiskIndex.DataTerm>, Closeable
return ptr;
}
+ private DataTerm getTerm(ByteBuffer query)
+ {
+ SearchResult<DataTerm> term = searchIndex(query, getDataBlock(query));
+ return term.cmp == 0 ? term.result : null;
+ }
+
private SearchResult<DataTerm> searchIndex(ByteBuffer query, int blockIdx)
{
return dataLevel.getBlock(blockIdx).search(comparator, query);
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndexBuilder.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndexBuilder.java b/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndexBuilder.java
index 7b8f5c9..20a8739 100644
--- a/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndexBuilder.java
+++ b/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndexBuilder.java
@@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.index.sasi.plan.Expression.Op;
import org.apache.cassandra.index.sasi.sa.IntegralSA;
import org.apache.cassandra.index.sasi.sa.SA;
import org.apache.cassandra.index.sasi.sa.TermIterator;
@@ -49,12 +50,26 @@ public class OnDiskIndexBuilder
public enum Mode
{
- PREFIX, CONTAINS, SPARSE;
+ PREFIX(EnumSet.of(Op.EQ, Op.PREFIX, Op.NOT_EQ, Op.RANGE)),
+ CONTAINS(EnumSet.of(Op.EQ, Op.CONTAINS, Op.SUFFIX, Op.NOT_EQ)),
+ SPARSE(EnumSet.of(Op.EQ, Op.NOT_EQ, Op.RANGE));
+
+ Set<Op> supportedOps;
+
+ Mode(Set<Op> ops)
+ {
+ supportedOps = ops;
+ }
public static Mode mode(String mode)
{
return Mode.valueOf(mode.toUpperCase());
}
+
+ public boolean supports(Op op)
+ {
+ return supportedOps.contains(op);
+ }
}
public enum TermSize
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/memory/TrieMemIndex.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/memory/TrieMemIndex.java b/src/java/org/apache/cassandra/index/sasi/memory/TrieMemIndex.java
index e4ee6eb..99a417a 100644
--- a/src/java/org/apache/cassandra/index/sasi/memory/TrieMemIndex.java
+++ b/src/java/org/apache/cassandra/index/sasi/memory/TrieMemIndex.java
@@ -18,6 +18,7 @@
package org.apache.cassandra.index.sasi.memory;
import java.nio.ByteBuffer;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentSkipListSet;
@@ -27,6 +28,7 @@ import org.apache.cassandra.index.sasi.conf.ColumnIndex;
import org.apache.cassandra.index.sasi.disk.OnDiskIndexBuilder;
import org.apache.cassandra.index.sasi.disk.Token;
import org.apache.cassandra.index.sasi.plan.Expression;
+import org.apache.cassandra.index.sasi.plan.Expression.Op;
import org.apache.cassandra.index.sasi.analyzer.AbstractAnalyzer;
import org.apache.cassandra.index.sasi.utils.RangeUnionIterator;
import org.apache.cassandra.index.sasi.utils.RangeIterator;
@@ -136,11 +138,9 @@ public class TrieMemIndex extends MemIndex
public RangeIterator<Long, Token> search(Expression expression)
{
- assert expression.getOp() == Expression.Op.EQ; // means that min == max
-
ByteBuffer prefix = expression.lower == null ? null : expression.lower.value;
- Iterable<ConcurrentSkipListSet<DecoratedKey>> search = search(definition.cellValueType().getString(prefix));
+ Iterable<ConcurrentSkipListSet<DecoratedKey>> search = search(expression.getOp(), definition.cellValueType().getString(prefix));
RangeUnionIterator.Builder<Long, Token> builder = RangeUnionIterator.builder();
for (ConcurrentSkipListSet<DecoratedKey> keys : search)
@@ -153,7 +153,7 @@ public class TrieMemIndex extends MemIndex
}
protected abstract ConcurrentSkipListSet<DecoratedKey> get(String value);
- protected abstract Iterable<ConcurrentSkipListSet<DecoratedKey>> search(String value);
+ protected abstract Iterable<ConcurrentSkipListSet<DecoratedKey>> search(Op operator, String value);
protected abstract ConcurrentSkipListSet<DecoratedKey> putIfAbsent(String value, ConcurrentSkipListSet<DecoratedKey> key);
}
@@ -177,9 +177,20 @@ public class TrieMemIndex extends MemIndex
return trie.putIfAbsent(value, newKeys);
}
- public Iterable<ConcurrentSkipListSet<DecoratedKey>> search(String value)
+ public Iterable<ConcurrentSkipListSet<DecoratedKey>> search(Op operator, String value)
{
- return trie.getValuesForKeysStartingWith(value);
+ switch (operator)
+ {
+ case EQ:
+ ConcurrentSkipListSet<DecoratedKey> keys = trie.getValueForExactKey(value);
+ return keys == null ? Collections.emptyList() : Collections.singletonList(keys);
+
+ case PREFIX:
+ return trie.getValuesForKeysStartingWith(value);
+
+ default:
+ throw new UnsupportedOperationException(String.format("operation %s is not supported.", operator));
+ }
}
}
@@ -203,9 +214,23 @@ public class TrieMemIndex extends MemIndex
return trie.putIfAbsent(value, newKeys);
}
- public Iterable<ConcurrentSkipListSet<DecoratedKey>> search(String value)
+ public Iterable<ConcurrentSkipListSet<DecoratedKey>> search(Op operator, String value)
{
- return trie.getValuesForKeysContaining(value);
+ switch (operator)
+ {
+ case EQ:
+ ConcurrentSkipListSet<DecoratedKey> keys = trie.getValueForExactKey(value);
+ return keys == null ? Collections.emptyList() : Collections.singletonList(keys);
+
+ case SUFFIX:
+ return trie.getValuesForKeysEndingWith(value);
+
+ case CONTAINS:
+ return trie.getValuesForKeysContaining(value);
+
+ default:
+ throw new UnsupportedOperationException(String.format("operation %s is not supported.", operator));
+ }
}
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/plan/Expression.java
----------------------------------------------------------------------
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 e215ec7..43f8251 100644
--- a/src/java/org/apache/cassandra/index/sasi/plan/Expression.java
+++ b/src/java/org/apache/cassandra/index/sasi/plan/Expression.java
@@ -46,7 +46,37 @@ public class Expression
public enum Op
{
- EQ, NOT_EQ, RANGE
+ EQ, PREFIX, SUFFIX, CONTAINS, NOT_EQ, RANGE;
+
+ public static Op valueOf(Operator operator)
+ {
+ switch (operator)
+ {
+ case EQ:
+ return EQ;
+
+ case NEQ:
+ return NOT_EQ;
+
+ case LT:
+ case GT:
+ case LTE:
+ case GTE:
+ return RANGE;
+
+ case LIKE_PREFIX:
+ return PREFIX;
+
+ case LIKE_SUFFIX:
+ return SUFFIX;
+
+ case LIKE_CONTAINS:
+ return CONTAINS;
+
+ default:
+ throw new IllegalArgumentException("unknown operator: " + operator);
+ }
+ }
}
private final QueryController controller;
@@ -107,10 +137,13 @@ public class Expression
boolean lowerInclusive = false, upperInclusive = false;
switch (op)
{
+ case LIKE_PREFIX:
+ case LIKE_SUFFIX:
+ case LIKE_CONTAINS:
case EQ:
lower = new Bound(value, true);
upper = lower;
- operation = Op.EQ;
+ operation = Op.valueOf(op);
break;
case NEQ:
@@ -151,7 +184,7 @@ public class Expression
return this;
}
- public boolean contains(ByteBuffer value)
+ public boolean isSatisfiedBy(ByteBuffer value)
{
if (!TypeUtil.isValid(value, validator))
{
@@ -224,7 +257,31 @@ public class Expression
while (analyzer.hasNext())
{
ByteBuffer term = analyzer.next();
- if (ByteBufferUtil.contains(term, requestedValue))
+
+ boolean isMatch = false;
+ switch (operation)
+ {
+ case EQ:
+ // Operation.isSatisfiedBy handles conclusion on !=,
+ // here we just need to make sure that term matched it
+ case NOT_EQ:
+ isMatch = validator.compare(term, requestedValue) == 0;
+ break;
+
+ case PREFIX:
+ isMatch = ByteBufferUtil.startsWith(term, requestedValue);
+ break;
+
+ case SUFFIX:
+ isMatch = ByteBufferUtil.endsWith(term, requestedValue);
+ break;
+
+ case CONTAINS:
+ isMatch = ByteBufferUtil.contains(term, requestedValue);
+ break;
+ }
+
+ if (isMatch)
return true;
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/index/sasi/plan/Operation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/sasi/plan/Operation.java b/src/java/org/apache/cassandra/index/sasi/plan/Operation.java
index 1857c56..28bcc51 100644
--- a/src/java/org/apache/cassandra/index/sasi/plan/Operation.java
+++ b/src/java/org/apache/cassandra/index/sasi/plan/Operation.java
@@ -160,7 +160,7 @@ public class Operation extends RangeIterator<Long, Token>
*
* The algorithm is as follows: for every given expression from analyzed
* list get corresponding column from the Row:
- * - apply {@link Expression#contains(ByteBuffer)}
+ * - apply {@link Expression#isSatisfiedBy(ByteBuffer)}
* method to figure out if it's satisfied;
* - apply logical operation between boolean accumulator and current boolean result;
* - if result == false and node's operation is AND return right away;
@@ -225,7 +225,7 @@ public class Operation extends RangeIterator<Long, Token>
for (int i = filters.size() - 1; i >= 0; i--)
{
Expression expression = filters.get(i);
- isMatch = !isMissingColumn && expression.contains(value);
+ isMatch = !isMissingColumn && expression.isSatisfiedBy(value);
if (expression.getOp() == Op.NOT_EQ)
{
// since this is NOT_EQ operation we have to
@@ -282,16 +282,29 @@ public class Operation extends RangeIterator<Long, Token>
AbstractAnalyzer analyzer = columnIndex.getAnalyzer();
analyzer.reset(e.getIndexValue());
- // EQ/NOT_EQ can have multiple expressions e.g. text = "Hello World",
+ // EQ/LIKE_*/NOT_EQ can have multiple expressions e.g. text = "Hello World",
// becomes text = "Hello" OR text = "World" because "space" is always interpreted as a split point (by analyzer),
// NOT_EQ is made an independent expression only in case of pre-existing multiple EQ expressions, or
// if there is no EQ operations and NOT_EQ is met or a single NOT_EQ expression present,
// in such case we know exactly that there would be no more EQ/RANGE expressions for given column
// since NOT_EQ has the lowest priority.
- if (e.operator() == Operator.EQ
- || (e.operator() == Operator.NEQ
- && (perColumn.size() == 0 || perColumn.size() > 1
- || (perColumn.size() == 1 && perColumn.get(0).getOp() == Op.NOT_EQ))))
+ boolean isMultiExpression = false;
+ switch (e.operator())
+ {
+ case EQ:
+ case LIKE_PREFIX:
+ case LIKE_SUFFIX:
+ case LIKE_CONTAINS:
+ isMultiExpression = true;
+ break;
+
+ case NEQ:
+ isMultiExpression = (perColumn.size() == 0 || perColumn.size() > 1
+ || (perColumn.size() == 1 && perColumn.get(0).getOp() == Op.NOT_EQ));
+ break;
+ }
+
+ if (isMultiExpression)
{
while (analyzer.hasNext())
{
@@ -323,6 +336,11 @@ public class Operation extends RangeIterator<Long, Token>
switch (op)
{
case EQ:
+ return 5;
+
+ case LIKE_PREFIX:
+ case LIKE_SUFFIX:
+ case LIKE_CONTAINS:
return 4;
case GTE:
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/utils/ByteBufferUtil.java b/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
index fcda9ba..87e8b9d 100644
--- a/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
+++ b/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
@@ -710,4 +710,36 @@ public class ByteBufferUtil
}
return false;
}
+
+ public static boolean startsWith(ByteBuffer src, ByteBuffer prefix)
+ {
+ return startsWith(src, prefix, 0);
+ }
+
+ public static boolean endsWith(ByteBuffer src, ByteBuffer suffix)
+ {
+ return startsWith(src, suffix, src.remaining() - suffix.remaining());
+ }
+
+ private static boolean startsWith(ByteBuffer src, ByteBuffer prefix, int offset)
+ {
+ if (offset < 0)
+ return false;
+
+ int sPos = src.position() + offset;
+ int pPos = prefix.position();
+
+ if (src.remaining() - offset < prefix.remaining())
+ return false;
+
+ int len = Math.min(src.remaining() - offset, prefix.remaining());
+
+ while (len-- > 0)
+ {
+ if (src.get(sPos++) != prefix.get(pPos++))
+ return false;
+ }
+
+ return true;
+ }
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/39286654/test/unit/org/apache/cassandra/SchemaLoader.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java
index 51db4cd..9a8c424 100644
--- a/test/unit/org/apache/cassandra/SchemaLoader.java
+++ b/test/unit/org/apache/cassandra/SchemaLoader.java
@@ -541,6 +541,7 @@ public class SchemaLoader
{{
put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName());
put(IndexTarget.TARGET_OPTION_NAME, "comment");
+ put("mode", OnDiskIndexBuilder.Mode.CONTAINS.toString());
put("analyzed", "true");
}}))
.with(IndexMetadata.fromSchemaMetadata("comment_suffix_split", IndexMetadata.Kind.CUSTOM, new HashMap<String, String>()