Repository: cassandra Updated Branches: refs/heads/trunk a7b138ad6 -> b7d1d447b
Add static column support to SASI index patch by doanduyhai and xedin; reviewed by xedin for CASSANDRA-11183 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/b7d1d447 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/b7d1d447 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/b7d1d447 Branch: refs/heads/trunk Commit: b7d1d447bb52ed912fdd1199086cce34a2d82c45 Parents: a7b138a Author: Pavel Yaskevich <xe...@apache.org> Authored: Mon Apr 4 01:05:04 2016 -0700 Committer: Pavel Yaskevich <xe...@apache.org> Committed: Mon Apr 4 11:43:37 2016 -0700 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../org/apache/cassandra/db/ColumnIndex.java | 8 +- .../cassandra/index/sasi/SASIIndexBuilder.java | 3 + .../cassandra/index/sasi/conf/ColumnIndex.java | 8 ++ .../cassandra/index/sasi/plan/Operation.java | 26 ++-- .../cassandra/index/sasi/plan/QueryPlan.java | 4 +- .../unit/org/apache/cassandra/SchemaLoader.java | 38 ++++++ .../validation/entities/SecondaryIndexTest.java | 10 +- .../cassandra/index/sasi/SASIIndexTest.java | 102 ++++++++++++++- .../index/sasi/plan/OperationTest.java | 125 ++++++++++++++----- 10 files changed, 272 insertions(+), 53 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 13e9325..d576b8e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.6 + * Add static column support to SASI index (CASSANDRA-11183) * Support EQ/PREFIX queries in SASI CONTAINS mode without tokenization (CASSANDRA-11434) * Support LIKE operator in prepared statements (CASSANDRA-11456) * Add a command to see if a Materialized View has finished building (CASSANDRA-9967) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/src/java/org/apache/cassandra/db/ColumnIndex.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/ColumnIndex.java b/src/java/org/apache/cassandra/db/ColumnIndex.java index 930fc05..1942052 100644 --- a/src/java/org/apache/cassandra/db/ColumnIndex.java +++ b/src/java/org/apache/cassandra/db/ColumnIndex.java @@ -109,7 +109,13 @@ public class ColumnIndex ByteBufferUtil.writeWithShortLength(iterator.partitionKey().getKey(), writer); DeletionTime.serializer.serialize(iterator.partitionLevelDeletion(), writer); if (header.hasStatic()) - UnfilteredSerializer.serializer.serializeStaticRow(iterator.staticRow(), header, writer, version); + { + Row staticRow = iterator.staticRow(); + + UnfilteredSerializer.serializer.serializeStaticRow(staticRow, header, writer, version); + if (!observers.isEmpty()) + observers.forEach((o) -> o.nextUnfilteredCluster(staticRow)); + } } public ColumnIndex build() throws IOException http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/src/java/org/apache/cassandra/index/sasi/SASIIndexBuilder.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/index/sasi/SASIIndexBuilder.java b/src/java/org/apache/cassandra/index/sasi/SASIIndexBuilder.java index fc5b675..205bf7e 100644 --- a/src/java/org/apache/cassandra/index/sasi/SASIIndexBuilder.java +++ b/src/java/org/apache/cassandra/index/sasi/SASIIndexBuilder.java @@ -78,6 +78,9 @@ class SASIIndexBuilder extends SecondaryIndexBuilder try (SSTableIdentityIterator partition = new SSTableIdentityIterator(sstable, dataFile, key)) { + // if the row has statics attached, it has to be indexed separately + indexWriter.nextUnfilteredCluster(partition.staticRow()); + while (partition.hasNext()) indexWriter.nextUnfilteredCluster(partition.next()); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/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 8ee94d1..3f268e3 100644 --- a/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java +++ b/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java @@ -226,11 +226,19 @@ public class ColumnIndex public static ByteBuffer getValueOf(ColumnDefinition column, Row row, int nowInSecs) { + if (row == null) + return null; + switch (column.kind) { case CLUSTERING: return row.clustering().get(column.position()); + // treat static cell retrieval the same was as regular + // only if row kind is STATIC otherwise return null + case STATIC: + if (!row.isStatic()) + return null; case REGULAR: Cell cell = row.getCell(column); return cell == null || !cell.isLive(nowInSecs) ? null : cell.value(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/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 6f0904c..7c744e1 100644 --- a/src/java/org/apache/cassandra/index/sasi/plan/Operation.java +++ b/src/java/org/apache/cassandra/index/sasi/plan/Operation.java @@ -92,7 +92,7 @@ public class Operation extends RangeIterator<Long, Token> * and data from the lower level members using depth-first search * and bubbling the results back to the top level caller. * - * Most of the work here is done by {@link #localSatisfiedBy(Unfiltered, boolean)} + * Most of the work here is done by {@link #localSatisfiedBy(Unfiltered, Row, boolean)} * see it's comment for details, if there are no local expressions * assigned to Operation it will call satisfiedBy(Row) on it's children. * @@ -120,18 +120,20 @@ public class Operation extends RangeIterator<Long, Token> * Level #2 computes AND between "first_name" and result of level #3, AND (state, country) which is already computed * Level #1 does OR between results of AND (first_name) and AND (state, country) and returns final result. * - * @param row The row to check. + * @param currentCluster The row cluster to check. + * @param staticRow The static row associated with current cluster. + * @param allowMissingColumns allow columns value to be null. * @return true if give Row satisfied all of the expressions in the tree, * false otherwise. */ - public boolean satisfiedBy(Unfiltered row, boolean allowMissingColumns) + public boolean satisfiedBy(Unfiltered currentCluster, Row staticRow, boolean allowMissingColumns) { boolean sideL, sideR; if (expressions == null || expressions.isEmpty()) { - sideL = left != null && left.satisfiedBy(row, allowMissingColumns); - sideR = right != null && right.satisfiedBy(row, allowMissingColumns); + sideL = left != null && left.satisfiedBy(currentCluster, staticRow, allowMissingColumns); + sideR = right != null && right.satisfiedBy(currentCluster, staticRow, allowMissingColumns); // one of the expressions was skipped // because it had no indexes attached @@ -140,14 +142,14 @@ public class Operation extends RangeIterator<Long, Token> } else { - sideL = localSatisfiedBy(row, allowMissingColumns); + sideL = localSatisfiedBy(currentCluster, staticRow, allowMissingColumns); // if there is no right it means that this expression // is last in the sequence, we can just return result from local expressions if (right == null) return sideL; - sideR = right.satisfiedBy(row, allowMissingColumns); + sideR = right.satisfiedBy(currentCluster, staticRow, allowMissingColumns); } @@ -190,13 +192,15 @@ public class Operation extends RangeIterator<Long, Token> * * #4 return accumulator => true (row satisfied all of the conditions) * - * @param row The row to check. + * @param currentCluster The row cluster to check. + * @param staticRow The static row associated with current cluster. + * @param allowMissingColumns allow columns value to be null. * @return true if give Row satisfied all of the analyzed expressions, * false otherwise. */ - private boolean localSatisfiedBy(Unfiltered row, boolean allowMissingColumns) + private boolean localSatisfiedBy(Unfiltered currentCluster, Row staticRow, boolean allowMissingColumns) { - if (row == null || !row.isRow()) + if (currentCluster == null || !currentCluster.isRow()) return false; final int now = FBUtilities.nowInSeconds(); @@ -208,7 +212,7 @@ public class Operation extends RangeIterator<Long, Token> if (column.kind == Kind.PARTITION_KEY) continue; - ByteBuffer value = ColumnIndex.getValueOf(column, (Row) row, now); + ByteBuffer value = ColumnIndex.getValueOf(column, column.kind == Kind.STATIC ? staticRow : (Row) currentCluster, now); boolean isMissingColumn = value == null; if (!allowMissingColumns && isMissingColumn) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/src/java/org/apache/cassandra/index/sasi/plan/QueryPlan.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/index/sasi/plan/QueryPlan.java b/src/java/org/apache/cassandra/index/sasi/plan/QueryPlan.java index d34b05a..4410756 100644 --- a/src/java/org/apache/cassandra/index/sasi/plan/QueryPlan.java +++ b/src/java/org/apache/cassandra/index/sasi/plan/QueryPlan.java @@ -112,11 +112,13 @@ public class QueryPlan try (UnfilteredRowIterator partition = controller.getPartition(key, executionController)) { + Row staticRow = partition.staticRow(); List<Unfiltered> clusters = new ArrayList<>(); + while (partition.hasNext()) { Unfiltered row = partition.next(); - if (operationTree.satisfiedBy(row, true)) + if (operationTree.satisfiedBy(row, staticRow, true)) clusters.add(row); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/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 e68dd94..992c4d6 100644 --- a/test/unit/org/apache/cassandra/SchemaLoader.java +++ b/test/unit/org/apache/cassandra/SchemaLoader.java @@ -605,6 +605,44 @@ public class SchemaLoader return cfm; } + public static CFMetaData staticSASICFMD(String ksName, String cfName) + { + CFMetaData cfm = CFMetaData.Builder.create(ksName, cfName) + .addPartitionKey("sensor_id", Int32Type.instance) + .addStaticColumn("sensor_type", UTF8Type.instance) + .addClusteringColumn("date", LongType.instance) + .addRegularColumn("value", DoubleType.instance) + .addRegularColumn("variance", Int32Type.instance) + .build(); + + Indexes indexes = cfm.getIndexes(); + indexes = indexes.with(IndexMetadata.fromSchemaMetadata("sensor_type", IndexMetadata.Kind.CUSTOM, new HashMap<String, String>() + {{ + put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName()); + put(IndexTarget.TARGET_OPTION_NAME, "sensor_type"); + put("mode", OnDiskIndexBuilder.Mode.PREFIX.toString()); + put("analyzer_class", "org.apache.cassandra.index.sasi.analyzer.NonTokenizingAnalyzer"); + put("case_sensitive", "false"); + }})); + + indexes = indexes.with(IndexMetadata.fromSchemaMetadata("value", IndexMetadata.Kind.CUSTOM, new HashMap<String, String>() + {{ + put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName()); + put(IndexTarget.TARGET_OPTION_NAME, "value"); + put("mode", OnDiskIndexBuilder.Mode.PREFIX.toString()); + }})); + + indexes = indexes.with(IndexMetadata.fromSchemaMetadata("variance", IndexMetadata.Kind.CUSTOM, new HashMap<String, String>() + {{ + put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName()); + put(IndexTarget.TARGET_OPTION_NAME, "variance"); + put("mode", OnDiskIndexBuilder.Mode.PREFIX.toString()); + }})); + + cfm.indexes(indexes); + return cfm; + } + public static CompressionParams getCompressionParameters() { return getCompressionParameters(null); http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java index e357d74..feea656 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java @@ -719,16 +719,12 @@ public class SecondaryIndexTest extends CQLTester execute("SELECT * FROM %s WHERE v1 LIKE ?", "abc%"); execute("SELECT * FROM %s WHERE v1 LIKE ?", "abc"); - // contains mode indexes support suffix/contains/matches - assertInvalidMessage("c2 LIKE '<term>%' abc is only supported on properly indexed columns", - "SELECT * FROM %s WHERE c2 LIKE ?", - "abc%"); + // contains mode indexes support prefix/suffix/contains/matches + execute("SELECT * FROM %s WHERE c2 LIKE ?", "abc%"); execute("SELECT * FROM %s WHERE c2 LIKE ?", "%abc"); execute("SELECT * FROM %s WHERE c2 LIKE ?", "%abc%"); execute("SELECT * FROM %s WHERE c2 LIKE ?", "abc"); - assertInvalidMessage("v2 LIKE '<term>%' abc is only supported on properly indexed columns", - "SELECT * FROM %s WHERE v2 LIKE ?", - "abc%"); + execute("SELECT * FROM %s WHERE v2 LIKE ?", "abc%"); execute("SELECT * FROM %s WHERE v2 LIKE ?", "%abc"); execute("SELECT * FROM %s WHERE v2 LIKE ?", "%abc%"); execute("SELECT * FROM %s WHERE v2 LIKE ?", "abc"); http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/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 c44c48c..115c996 100644 --- a/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java +++ b/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java @@ -82,6 +82,7 @@ public class SASIIndexTest private static final String CF_NAME = "test_cf"; private static final String CLUSTERING_CF_NAME_1 = "clustering_test_cf_1"; private static final String CLUSTERING_CF_NAME_2 = "clustering_test_cf_2"; + private static final String STATIC_CF_NAME = "static_sasi_test_cf"; @BeforeClass public static void loadSchema() throws ConfigurationException @@ -92,7 +93,8 @@ public class SASIIndexTest KeyspaceParams.simpleTransient(1), Tables.of(SchemaLoader.sasiCFMD(KS_NAME, CF_NAME), SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTERING_CF_NAME_1), - SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTERING_CF_NAME_2, "location")))); + SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTERING_CF_NAME_2, "location"), + SchemaLoader.staticSASICFMD(KS_NAME, STATIC_CF_NAME)))); } @After @@ -1726,6 +1728,104 @@ public class SASIIndexTest } @Test + public void testStaticIndex() throws Exception + { + testStaticIndex(false); + cleanupData(); + testStaticIndex(true); + } + + public void testStaticIndex(boolean shouldFlush) throws Exception + { + ColumnFamilyStore store = Keyspace.open(KS_NAME).getColumnFamilyStore(STATIC_CF_NAME); + + executeCQL(STATIC_CF_NAME, "INSERT INTO %s.%s (sensor_id,sensor_type) VALUES(?, ?)", 1, "TEMPERATURE"); + executeCQL(STATIC_CF_NAME, "INSERT INTO %s.%s (sensor_id,date,value,variance) VALUES(?, ?, ?, ?)", 1, 20160401L, 24.46, 2); + executeCQL(STATIC_CF_NAME, "INSERT INTO %s.%s (sensor_id,date,value,variance) VALUES(?, ?, ?, ?)", 1, 20160402L, 25.62, 5); + executeCQL(STATIC_CF_NAME, "INSERT INTO %s.%s (sensor_id,date,value,variance) VALUES(?, ?, ?, ?)", 1, 20160403L, 24.96, 4); + + if (shouldFlush) + store.forceBlockingFlush(); + + executeCQL(STATIC_CF_NAME, "INSERT INTO %s.%s (sensor_id,sensor_type) VALUES(?, ?)", 2, "PRESSURE"); + executeCQL(STATIC_CF_NAME, "INSERT INTO %s.%s (sensor_id,date,value,variance) VALUES(?, ?, ?, ?)", 2, 20160401L, 1.03, 9); + executeCQL(STATIC_CF_NAME, "INSERT INTO %s.%s (sensor_id,date,value,variance) VALUES(?, ?, ?, ?)", 2, 20160402L, 1.04, 7); + executeCQL(STATIC_CF_NAME, "INSERT INTO %s.%s (sensor_id,date,value,variance) VALUES(?, ?, ?, ?)", 2, 20160403L, 1.01, 4); + + if (shouldFlush) + store.forceBlockingFlush(); + + UntypedResultSet results; + + // Prefix search on static column only + results = executeCQL(STATIC_CF_NAME ,"SELECT * FROM %s.%s WHERE sensor_type LIKE 'temp%%'"); + Assert.assertNotNull(results); + Assert.assertEquals(3, results.size()); + + Iterator<UntypedResultSet.Row> iterator = results.iterator(); + + UntypedResultSet.Row row1 = iterator.next(); + Assert.assertEquals(20160401L, row1.getLong("date")); + Assert.assertEquals(24.46, row1.getDouble("value")); + Assert.assertEquals(2, row1.getInt("variance")); + + + UntypedResultSet.Row row2 = iterator.next(); + Assert.assertEquals(20160402L, row2.getLong("date")); + Assert.assertEquals(25.62, row2.getDouble("value")); + Assert.assertEquals(5, row2.getInt("variance")); + + UntypedResultSet.Row row3 = iterator.next(); + Assert.assertEquals(20160403L, row3.getLong("date")); + Assert.assertEquals(24.96, row3.getDouble("value")); + Assert.assertEquals(4, row3.getInt("variance")); + + + // Combined static and non static filtering + results = executeCQL(STATIC_CF_NAME ,"SELECT * FROM %s.%s WHERE sensor_type=? AND value >= ? AND value <= ? AND variance=? ALLOW FILTERING", + "pressure", 1.02, 1.05, 7); + Assert.assertNotNull(results); + Assert.assertEquals(1, results.size()); + + row1 = results.one(); + Assert.assertEquals(20160402L, row1.getLong("date")); + Assert.assertEquals(1.04, row1.getDouble("value")); + Assert.assertEquals(7, row1.getInt("variance")); + + // Only non statc columns filtering + results = executeCQL(STATIC_CF_NAME ,"SELECT * FROM %s.%s WHERE value >= ? AND variance <= ? ALLOW FILTERING", 1.02, 7); + Assert.assertNotNull(results); + Assert.assertEquals(4, results.size()); + + iterator = results.iterator(); + + row1 = iterator.next(); + Assert.assertEquals("TEMPERATURE", row1.getString("sensor_type")); + Assert.assertEquals(20160401L, row1.getLong("date")); + Assert.assertEquals(24.46, row1.getDouble("value")); + Assert.assertEquals(2, row1.getInt("variance")); + + + row2 = iterator.next(); + Assert.assertEquals("TEMPERATURE", row2.getString("sensor_type")); + Assert.assertEquals(20160402L, row2.getLong("date")); + Assert.assertEquals(25.62, row2.getDouble("value")); + Assert.assertEquals(5, row2.getInt("variance")); + + row3 = iterator.next(); + Assert.assertEquals("TEMPERATURE", row3.getString("sensor_type")); + Assert.assertEquals(20160403L, row3.getLong("date")); + Assert.assertEquals(24.96, row3.getDouble("value")); + Assert.assertEquals(4, row3.getInt("variance")); + + UntypedResultSet.Row row4 = iterator.next(); + Assert.assertEquals("PRESSURE", row4.getString("sensor_type")); + Assert.assertEquals(20160402L, row4.getLong("date")); + Assert.assertEquals(1.04, row4.getDouble("value")); + Assert.assertEquals(7, row4.getInt("variance")); + } + + @Test public void testInvalidIndexOptions() { ColumnFamilyStore store = Keyspace.open(KS_NAME).getColumnFamilyStore(CF_NAME); http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7d1d447/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 52cee3b..e388cd4 100644 --- a/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java +++ b/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java @@ -50,9 +50,11 @@ public class OperationTest extends SchemaLoader private static final String KS_NAME = "sasi"; private static final String CF_NAME = "test_cf"; private static final String CLUSTERING_CF_NAME = "clustering_test_cf"; + private static final String STATIC_CF_NAME = "static_sasi_test_cf"; private static ColumnFamilyStore BACKEND; private static ColumnFamilyStore CLUSTERING_BACKEND; + private static ColumnFamilyStore STATIC_BACKEND; @BeforeClass public static void loadSchema() throws ConfigurationException @@ -62,10 +64,12 @@ public class OperationTest extends SchemaLoader MigrationManager.announceNewKeyspace(KeyspaceMetadata.create(KS_NAME, KeyspaceParams.simpleTransient(1), Tables.of(SchemaLoader.sasiCFMD(KS_NAME, CF_NAME), - SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTERING_CF_NAME)))); + SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTERING_CF_NAME), + SchemaLoader.staticSASICFMD(KS_NAME, STATIC_CF_NAME)))); BACKEND = Keyspace.open(KS_NAME).getColumnFamilyStore(CF_NAME); CLUSTERING_BACKEND = Keyspace.open(KS_NAME).getColumnFamilyStore(CLUSTERING_CF_NAME); + STATIC_BACKEND = Keyspace.open(KS_NAME).getColumnFamilyStore(STATIC_CF_NAME); } private QueryController controller; @@ -277,17 +281,18 @@ public class OperationTest extends SchemaLoader Operation op = builder.complete(); Unfiltered row = buildRow(buildCell(age, Int32Type.instance.decompose(6), System.currentTimeMillis())); + Row staticRow = buildRow(Clustering.STATIC_CLUSTERING); - Assert.assertTrue(op.satisfiedBy(row, false)); + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); row = buildRow(buildCell(age, Int32Type.instance.decompose(5), System.currentTimeMillis())); // and reject incorrect value - Assert.assertFalse(op.satisfiedBy(row, false)); + Assert.assertFalse(op.satisfiedBy(row, staticRow, false)); row = buildRow(buildCell(age, Int32Type.instance.decompose(6), System.currentTimeMillis())); - Assert.assertTrue(op.satisfiedBy(row, false)); + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); // range with exclusions - age != 5 AND age > 1 AND age != 6 AND age <= 10 builder = new Operation.Builder(OperationType.AND, controller, @@ -302,7 +307,7 @@ public class OperationTest extends SchemaLoader { row = buildRow(buildCell(age, Int32Type.instance.decompose(i), System.currentTimeMillis())); - boolean result = op.satisfiedBy(row, false); + boolean result = op.satisfiedBy(row, staticRow, false); Assert.assertTrue(exclusions.contains(i) != result); } @@ -318,7 +323,7 @@ public class OperationTest extends SchemaLoader { row = buildRow(buildCell(age, Int32Type.instance.decompose(i), System.currentTimeMillis())); - boolean result = op.satisfiedBy(row, false); + boolean result = op.satisfiedBy(row, staticRow, false); Assert.assertTrue(exclusions.contains(i) != result); } @@ -341,7 +346,7 @@ public class OperationTest extends SchemaLoader { row = buildRow(buildCell(age, Int32Type.instance.decompose(i), System.currentTimeMillis())); - boolean result = op.satisfiedBy(row, false); + boolean result = op.satisfiedBy(row, staticRow, false); Assert.assertTrue(exclusions.contains(i) != result); } @@ -355,17 +360,17 @@ public class OperationTest extends SchemaLoader row = buildRow(buildCell(age, Int32Type.instance.decompose(6), System.currentTimeMillis()), buildCell(timestamp, LongType.instance.decompose(11L), System.currentTimeMillis())); - Assert.assertFalse(op.satisfiedBy(row, false)); + Assert.assertFalse(op.satisfiedBy(row, staticRow, false)); row = buildRow(buildCell(age, Int32Type.instance.decompose(5), System.currentTimeMillis()), buildCell(timestamp, LongType.instance.decompose(22L), System.currentTimeMillis())); - Assert.assertTrue(op.satisfiedBy(row, false)); + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); row = buildRow(buildCell(age, Int32Type.instance.decompose(5), System.currentTimeMillis()), buildCell(timestamp, LongType.instance.decompose(9L), System.currentTimeMillis())); - Assert.assertFalse(op.satisfiedBy(row, false)); + Assert.assertFalse(op.satisfiedBy(row, staticRow, false)); // operation with internal expressions and right child builder = new Operation.Builder(OperationType.OR, controller, @@ -378,25 +383,26 @@ public class OperationTest extends SchemaLoader row = buildRow(buildCell(age, Int32Type.instance.decompose(5), System.currentTimeMillis()), buildCell(timestamp, LongType.instance.decompose(9L), System.currentTimeMillis())); - Assert.assertTrue(op.satisfiedBy(row, false)); + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); row = buildRow(buildCell(age, Int32Type.instance.decompose(20), System.currentTimeMillis()), buildCell(timestamp, LongType.instance.decompose(11L), System.currentTimeMillis())); - Assert.assertTrue(op.satisfiedBy(row, false)); + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); row = buildRow(buildCell(age, Int32Type.instance.decompose(0), System.currentTimeMillis()), buildCell(timestamp, LongType.instance.decompose(9L), System.currentTimeMillis())); - Assert.assertFalse(op.satisfiedBy(row, false)); + Assert.assertFalse(op.satisfiedBy(row, staticRow, false)); // and for desert let's try out null and deleted rows etc. builder = new Operation.Builder(OperationType.AND, controller); builder.add(new SimpleExpression(age, Operator.EQ, Int32Type.instance.decompose(30))); op = builder.complete(); - Assert.assertFalse(op.satisfiedBy(null, false)); - Assert.assertFalse(op.satisfiedBy(row, false)); + Assert.assertFalse(op.satisfiedBy(null, staticRow, false)); + Assert.assertFalse(op.satisfiedBy(row, null, false)); + Assert.assertFalse(op.satisfiedBy(row, staticRow, false)); long now = System.currentTimeMillis(); @@ -404,15 +410,15 @@ public class OperationTest extends SchemaLoader Row.Deletion.regular(new DeletionTime(now - 10, (int) (now / 1000))), buildCell(age, Int32Type.instance.decompose(6), System.currentTimeMillis())); - Assert.assertFalse(op.satisfiedBy(row, false)); + Assert.assertFalse(op.satisfiedBy(row, staticRow, false)); row = buildRow(deletedCell(age, System.currentTimeMillis(), FBUtilities.nowInSeconds())); - Assert.assertFalse(op.satisfiedBy(row, true)); + Assert.assertFalse(op.satisfiedBy(row, staticRow, true)); try { - Assert.assertFalse(op.satisfiedBy(buildRow(), false)); + Assert.assertFalse(op.satisfiedBy(buildRow(), staticRow, false)); } catch (IllegalStateException e) { @@ -421,7 +427,7 @@ public class OperationTest extends SchemaLoader try { - Assert.assertFalse(op.satisfiedBy(buildRow(), true)); + Assert.assertFalse(op.satisfiedBy(buildRow(), staticRow, true)); } catch (IllegalStateException e) { @@ -486,22 +492,20 @@ public class OperationTest extends SchemaLoader { final ColumnDefinition comment = getColumn(UTF8Type.instance.decompose("comment")); - Unfiltered row = buildRow( - buildCell(comment, - UTF8Type.instance.decompose("software engineer is working on a project"), - System.currentTimeMillis())); + Unfiltered row = buildRow(buildCell(comment,UTF8Type.instance.decompose("software engineer is working on a project"),System.currentTimeMillis())); + Row staticRow = buildRow(Clustering.STATIC_CLUSTERING); Operation.Builder builder = new Operation.Builder(OperationType.AND, controller, new SimpleExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("eng is a work"))); Operation op = builder.complete(); - Assert.assertTrue(op.satisfiedBy(row, false)); + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); builder = new Operation.Builder(OperationType.AND, controller, new SimpleExpression(comment, Operator.LIKE_CONTAINS, UTF8Type.instance.decompose("soft works fine"))); op = builder.complete(); - Assert.assertTrue(op.satisfiedBy(row, false)); + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); } @Test @@ -515,51 +519,52 @@ public class OperationTest extends SchemaLoader Unfiltered row = buildRow(Clustering.make(UTF8Type.instance.fromString("US"), Int32Type.instance.decompose(27)), buildCell(height, Int32Type.instance.decompose(182), System.currentTimeMillis()), buildCell(score, DoubleType.instance.decompose(1.0d), System.currentTimeMillis())); + Row staticRow = buildRow(Clustering.STATIC_CLUSTERING); Operation.Builder builder = new Operation.Builder(OperationType.AND, controller); builder.add(new SimpleExpression(age, Operator.EQ, Int32Type.instance.decompose(27))); builder.add(new SimpleExpression(height, Operator.EQ, Int32Type.instance.decompose(182))); - Assert.assertTrue(builder.complete().satisfiedBy(row, false)); + Assert.assertTrue(builder.complete().satisfiedBy(row, staticRow, false)); builder = new Operation.Builder(OperationType.AND, controller); builder.add(new SimpleExpression(age, Operator.EQ, Int32Type.instance.decompose(28))); builder.add(new SimpleExpression(height, Operator.EQ, Int32Type.instance.decompose(182))); - Assert.assertFalse(builder.complete().satisfiedBy(row, false)); + Assert.assertFalse(builder.complete().satisfiedBy(row, staticRow, false)); builder = new Operation.Builder(OperationType.AND, controller); builder.add(new SimpleExpression(location, Operator.EQ, UTF8Type.instance.decompose("US"))); builder.add(new SimpleExpression(age, Operator.GTE, Int32Type.instance.decompose(27))); - Assert.assertTrue(builder.complete().satisfiedBy(row, false)); + Assert.assertTrue(builder.complete().satisfiedBy(row, staticRow, false)); builder = new Operation.Builder(OperationType.AND, controller); builder.add(new SimpleExpression(location, Operator.EQ, UTF8Type.instance.decompose("BY"))); builder.add(new SimpleExpression(age, Operator.GTE, Int32Type.instance.decompose(28))); - Assert.assertFalse(builder.complete().satisfiedBy(row, false)); + Assert.assertFalse(builder.complete().satisfiedBy(row, staticRow, false)); builder = new Operation.Builder(OperationType.AND, controller); builder.add(new SimpleExpression(location, Operator.EQ, UTF8Type.instance.decompose("US"))); builder.add(new SimpleExpression(age, Operator.LTE, Int32Type.instance.decompose(27))); builder.add(new SimpleExpression(height, Operator.GTE, Int32Type.instance.decompose(182))); - Assert.assertTrue(builder.complete().satisfiedBy(row, false)); + Assert.assertTrue(builder.complete().satisfiedBy(row, staticRow, false)); builder = new Operation.Builder(OperationType.AND, controller); builder.add(new SimpleExpression(location, Operator.EQ, UTF8Type.instance.decompose("US"))); builder.add(new SimpleExpression(height, Operator.GTE, Int32Type.instance.decompose(182))); builder.add(new SimpleExpression(score, Operator.EQ, DoubleType.instance.decompose(1.0d))); - Assert.assertTrue(builder.complete().satisfiedBy(row, false)); + Assert.assertTrue(builder.complete().satisfiedBy(row, staticRow, false)); builder = new Operation.Builder(OperationType.AND, controller); builder.add(new SimpleExpression(height, Operator.GTE, Int32Type.instance.decompose(182))); builder.add(new SimpleExpression(score, Operator.EQ, DoubleType.instance.decompose(1.0d))); - Assert.assertTrue(builder.complete().satisfiedBy(row, false)); + Assert.assertTrue(builder.complete().satisfiedBy(row, staticRow, false)); } private Map<Expression.Op, Expression> convert(Multimap<ColumnDefinition, Expression> expressions) @@ -575,6 +580,62 @@ public class OperationTest extends SchemaLoader return converted; } + @Test + public void testSatisfiedByWithStatic() + { + final ColumnDefinition sensorType = getColumn(STATIC_BACKEND, UTF8Type.instance.decompose("sensor_type")); + final ColumnDefinition value = getColumn(STATIC_BACKEND, UTF8Type.instance.decompose("value")); + + Unfiltered row = buildRow(Clustering.make(UTF8Type.instance.fromString("date"), LongType.instance.decompose(20160401L)), + buildCell(value, DoubleType.instance.decompose(24.56), System.currentTimeMillis())); + Row staticRow = buildRow(Clustering.STATIC_CLUSTERING, + buildCell(sensorType, UTF8Type.instance.decompose("TEMPERATURE"), System.currentTimeMillis())); + + // sensor_type ='TEMPERATURE' AND value = 24.56 + Operation op = new Operation.Builder(OperationType.AND, controller, + new SimpleExpression(sensorType, Operator.EQ, UTF8Type.instance.decompose("TEMPERATURE")), + new SimpleExpression(value, Operator.EQ, DoubleType.instance.decompose(24.56))).complete(); + + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); + + // sensor_type ='TEMPERATURE' AND value = 30 + op = new Operation.Builder(OperationType.AND, controller, + new SimpleExpression(sensorType, Operator.EQ, UTF8Type.instance.decompose("TEMPERATURE")), + new SimpleExpression(value, Operator.EQ, DoubleType.instance.decompose(30.00))).complete(); + + Assert.assertFalse(op.satisfiedBy(row, staticRow, false)); + + // sensor_type ='PRESSURE' OR value = 24.56 + op = new Operation.Builder(OperationType.OR, controller, + new SimpleExpression(sensorType, Operator.EQ, UTF8Type.instance.decompose("TEMPERATURE")), + new SimpleExpression(value, Operator.EQ, DoubleType.instance.decompose(24.56))).complete(); + + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); + + // sensor_type ='PRESSURE' OR value = 30 + op = new Operation.Builder(OperationType.AND, controller, + new SimpleExpression(sensorType, Operator.EQ, UTF8Type.instance.decompose("PRESSURE")), + new SimpleExpression(value, Operator.EQ, DoubleType.instance.decompose(30.00))).complete(); + + Assert.assertFalse(op.satisfiedBy(row, staticRow, false)); + + // (sensor_type = 'TEMPERATURE' OR sensor_type = 'PRESSURE') AND value = 24.56 + op = new Operation.Builder(OperationType.OR, controller, + new SimpleExpression(sensorType, Operator.EQ, UTF8Type.instance.decompose("TEMPERATURE")), + new SimpleExpression(sensorType, Operator.EQ, UTF8Type.instance.decompose("PRESSURE"))) + .setRight(new Operation.Builder(OperationType.AND, controller, + new SimpleExpression(value, Operator.EQ, DoubleType.instance.decompose(24.56)))).complete(); + + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); + + // sensor_type = LIKE 'TEMP%' AND value = 24.56 + op = new Operation.Builder(OperationType.AND, controller, + new SimpleExpression(sensorType, Operator.LIKE_PREFIX, UTF8Type.instance.decompose("TEMP")), + new SimpleExpression(value, Operator.EQ, DoubleType.instance.decompose(24.56))).complete(); + + Assert.assertTrue(op.satisfiedBy(row, staticRow, false)); + } + private static class SimpleExpression extends RowFilter.Expression { SimpleExpression(ColumnDefinition column, Operator operator, ByteBuffer value)