blob: e388cd405599aa3f4e757f9f20e0731b4f9a4ada [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.index.sasi.plan;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.TimeUnit;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.DoubleType;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.index.sasi.plan.Operation.OperationType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.Tables;
import org.apache.cassandra.service.MigrationManager;
import org.apache.cassandra.utils.FBUtilities;
import org.junit.*;
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
{
System.setProperty("cassandra.config", "cassandra-murmur.yaml");
SchemaLoader.loadSchema();
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.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;
@Before
public void beforeTest()
{
controller = new QueryController(BACKEND,
PartitionRangeReadCommand.allDataRead(BACKEND.metadata, FBUtilities.nowInSeconds()),
TimeUnit.SECONDS.toMillis(10));
}
@After
public void afterTest()
{
controller.finish();
}
@Test
public void testAnalyze() throws Exception
{
final ColumnDefinition firstName = getColumn(UTF8Type.instance.decompose("first_name"));
final ColumnDefinition age = getColumn(UTF8Type.instance.decompose("age"));
final ColumnDefinition comment = getColumn(UTF8Type.instance.decompose("comment"));
// age != 5 AND age > 1 AND age != 6 AND age <= 10
Map<Expression.Op, Expression> expressions = convert(Operation.analyzeGroup(controller, OperationType.AND,
Arrays.asList(new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(5)),
new SimpleExpression(age, Operator.GT, Int32Type.instance.decompose(1)),
new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(6)),
new SimpleExpression(age, Operator.LTE, Int32Type.instance.decompose(10)))));
Expression expected = new Expression("age", Int32Type.instance)
{{
operation = Op.RANGE;
lower = new Bound(Int32Type.instance.decompose(1), false);
upper = new Bound(Int32Type.instance.decompose(10), true);
exclusions.add(Int32Type.instance.decompose(5));
exclusions.add(Int32Type.instance.decompose(6));
}};
Assert.assertEquals(1, expressions.size());
Assert.assertEquals(expected, expressions.get(Expression.Op.RANGE));
// age != 5 OR age >= 7
expressions = convert(Operation.analyzeGroup(controller, OperationType.OR,
Arrays.asList(new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(5)),
new SimpleExpression(age, Operator.GTE, Int32Type.instance.decompose(7)))));
Assert.assertEquals(2, expressions.size());
Assert.assertEquals(new Expression("age", Int32Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(Int32Type.instance.decompose(5), true);
upper = lower;
}}, expressions.get(Expression.Op.NOT_EQ));
Assert.assertEquals(new Expression("age", Int32Type.instance)
{{
operation = Op.RANGE;
lower = new Bound(Int32Type.instance.decompose(7), true);
}}, expressions.get(Expression.Op.RANGE));
// age != 5 OR age < 7
expressions = convert(Operation.analyzeGroup(controller, OperationType.OR,
Arrays.asList(new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(5)),
new SimpleExpression(age, Operator.LT, Int32Type.instance.decompose(7)))));
Assert.assertEquals(2, expressions.size());
Assert.assertEquals(new Expression("age", Int32Type.instance)
{{
operation = Op.RANGE;
upper = new Bound(Int32Type.instance.decompose(7), false);
}}, expressions.get(Expression.Op.RANGE));
Assert.assertEquals(new Expression("age", Int32Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(Int32Type.instance.decompose(5), true);
upper = lower;
}}, expressions.get(Expression.Op.NOT_EQ));
// age > 1 AND age < 7
expressions = convert(Operation.analyzeGroup(controller, OperationType.AND,
Arrays.asList(new SimpleExpression(age, Operator.GT, Int32Type.instance.decompose(1)),
new SimpleExpression(age, Operator.LT, Int32Type.instance.decompose(7)))));
Assert.assertEquals(1, expressions.size());
Assert.assertEquals(new Expression("age", Int32Type.instance)
{{
operation = Op.RANGE;
lower = new Bound(Int32Type.instance.decompose(1), false);
upper = new Bound(Int32Type.instance.decompose(7), false);
}}, expressions.get(Expression.Op.RANGE));
// first_name = 'a' OR first_name != 'b'
expressions = convert(Operation.analyzeGroup(controller, OperationType.OR,
Arrays.asList(new SimpleExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
new SimpleExpression(firstName, Operator.NEQ, UTF8Type.instance.decompose("b")))));
Assert.assertEquals(2, expressions.size());
Assert.assertEquals(new Expression("first_name", UTF8Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(UTF8Type.instance.decompose("b"), true);
upper = lower;
}}, expressions.get(Expression.Op.NOT_EQ));
Assert.assertEquals(new Expression("first_name", UTF8Type.instance)
{{
operation = Op.EQ;
lower = upper = new Bound(UTF8Type.instance.decompose("a"), true);
}}, expressions.get(Expression.Op.EQ));
// comment = 'soft eng' and comment != 'likes do'
ListMultimap<ColumnDefinition, Expression> e = Operation.analyzeGroup(controller, OperationType.OR,
Arrays.asList(new SimpleExpression(comment, Operator.LIKE_MATCHES, UTF8Type.instance.decompose("soft eng")),
new SimpleExpression(comment, Operator.NEQ, UTF8Type.instance.decompose("likes do"))));
List<Expression> expectedExpressions = new ArrayList<Expression>(2)
{{
add(new Expression("comment", UTF8Type.instance)
{{
operation = Op.MATCH;
lower = new Bound(UTF8Type.instance.decompose("soft"), true);
upper = lower;
}});
add(new Expression("comment", UTF8Type.instance)
{{
operation = Op.MATCH;
lower = new Bound(UTF8Type.instance.decompose("eng"), true);
upper = lower;
}});
add(new Expression("comment", UTF8Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(UTF8Type.instance.decompose("likes"), true);
upper = lower;
}});
add(new Expression("comment", UTF8Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(UTF8Type.instance.decompose("do"), true);
upper = lower;
}});
}};
Assert.assertEquals(expectedExpressions, e.get(comment));
// first_name = 'j' and comment != 'likes do'
e = Operation.analyzeGroup(controller, OperationType.OR,
Arrays.asList(new SimpleExpression(comment, Operator.NEQ, UTF8Type.instance.decompose("likes do")),
new SimpleExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("j"))));
expectedExpressions = new ArrayList<Expression>(2)
{{
add(new Expression("comment", UTF8Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(UTF8Type.instance.decompose("likes"), true);
upper = lower;
}});
add(new Expression("comment", UTF8Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(UTF8Type.instance.decompose("do"), true);
upper = lower;
}});
}};
Assert.assertEquals(expectedExpressions, e.get(comment));
// age != 27 first_name = 'j' and age != 25
e = Operation.analyzeGroup(controller, OperationType.OR,
Arrays.asList(new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(27)),
new SimpleExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("j")),
new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(25))));
expectedExpressions = new ArrayList<Expression>(2)
{{
add(new Expression("age", Int32Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(Int32Type.instance.decompose(27), true);
upper = lower;
}});
add(new Expression("age", Int32Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(Int32Type.instance.decompose(25), true);
upper = lower;
}});
}};
Assert.assertEquals(expectedExpressions, e.get(age));
}
@Test
public void testSatisfiedBy() throws Exception
{
final ColumnDefinition timestamp = getColumn(UTF8Type.instance.decompose("timestamp"));
final ColumnDefinition age = getColumn(UTF8Type.instance.decompose("age"));
Operation.Builder builder = new Operation.Builder(OperationType.AND, controller, new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(5)));
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, staticRow, false));
row = buildRow(buildCell(age, Int32Type.instance.decompose(5), System.currentTimeMillis()));
// and reject incorrect value
Assert.assertFalse(op.satisfiedBy(row, staticRow, false));
row = buildRow(buildCell(age, Int32Type.instance.decompose(6), System.currentTimeMillis()));
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,
new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(5)),
new SimpleExpression(age, Operator.GT, Int32Type.instance.decompose(1)),
new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(6)),
new SimpleExpression(age, Operator.LTE, Int32Type.instance.decompose(10)));
op = builder.complete();
Set<Integer> exclusions = Sets.newHashSet(0, 1, 5, 6, 11);
for (int i = 0; i <= 11; i++)
{
row = buildRow(buildCell(age, Int32Type.instance.decompose(i), System.currentTimeMillis()));
boolean result = op.satisfiedBy(row, staticRow, false);
Assert.assertTrue(exclusions.contains(i) != result);
}
// now let's do something more complex - age = 5 OR age = 6
builder = new Operation.Builder(OperationType.OR, controller,
new SimpleExpression(age, Operator.EQ, Int32Type.instance.decompose(5)),
new SimpleExpression(age, Operator.EQ, Int32Type.instance.decompose(6)));
op = builder.complete();
exclusions = Sets.newHashSet(0, 1, 2, 3, 4, 7, 8, 9, 10);
for (int i = 0; i <= 10; i++)
{
row = buildRow(buildCell(age, Int32Type.instance.decompose(i), System.currentTimeMillis()));
boolean result = op.satisfiedBy(row, staticRow, false);
Assert.assertTrue(exclusions.contains(i) != result);
}
// now let's test aggregated AND commands
builder = new Operation.Builder(OperationType.AND, controller);
// logical should be ignored by analyzer, but we still what to make sure that it is
//IndexExpression logical = new IndexExpression(ByteBufferUtil.EMPTY_BYTE_BUFFER, IndexOperator.EQ, ByteBufferUtil.EMPTY_BYTE_BUFFER);
//logical.setLogicalOp(LogicalIndexOperator.AND);
//builder.add(logical);
builder.add(new SimpleExpression(age, Operator.GTE, Int32Type.instance.decompose(0)));
builder.add(new SimpleExpression(age, Operator.LT, Int32Type.instance.decompose(10)));
builder.add(new SimpleExpression(age, Operator.NEQ, Int32Type.instance.decompose(7)));
op = builder.complete();
exclusions = Sets.newHashSet(7);
for (int i = 0; i < 10; i++)
{
row = buildRow(buildCell(age, Int32Type.instance.decompose(i), System.currentTimeMillis()));
boolean result = op.satisfiedBy(row, staticRow, false);
Assert.assertTrue(exclusions.contains(i) != result);
}
// multiple analyzed expressions in the Operation timestamp >= 10 AND age = 5
builder = new Operation.Builder(OperationType.AND, controller);
builder.add(new SimpleExpression(timestamp, Operator.GTE, LongType.instance.decompose(10L)));
builder.add(new SimpleExpression(age, Operator.EQ, Int32Type.instance.decompose(5)));
op = builder.complete();
row = buildRow(buildCell(age, Int32Type.instance.decompose(6), System.currentTimeMillis()),
buildCell(timestamp, LongType.instance.decompose(11L), System.currentTimeMillis()));
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, 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, staticRow, false));
// operation with internal expressions and right child
builder = new Operation.Builder(OperationType.OR, controller,
new SimpleExpression(timestamp, Operator.GT, LongType.instance.decompose(10L)));
builder.setRight(new Operation.Builder(OperationType.AND, controller,
new SimpleExpression(age, Operator.GT, Int32Type.instance.decompose(0)),
new SimpleExpression(age, Operator.LT, Int32Type.instance.decompose(10))));
op = builder.complete();
row = buildRow(buildCell(age, Int32Type.instance.decompose(5), System.currentTimeMillis()),
buildCell(timestamp, LongType.instance.decompose(9L), System.currentTimeMillis()));
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, 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, 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, staticRow, false));
Assert.assertFalse(op.satisfiedBy(row, null, false));
Assert.assertFalse(op.satisfiedBy(row, staticRow, false));
long now = System.currentTimeMillis();
row = OperationTest.buildRow(
Row.Deletion.regular(new DeletionTime(now - 10, (int) (now / 1000))),
buildCell(age, Int32Type.instance.decompose(6), System.currentTimeMillis()));
Assert.assertFalse(op.satisfiedBy(row, staticRow, false));
row = buildRow(deletedCell(age, System.currentTimeMillis(), FBUtilities.nowInSeconds()));
Assert.assertFalse(op.satisfiedBy(row, staticRow, true));
try
{
Assert.assertFalse(op.satisfiedBy(buildRow(), staticRow, false));
}
catch (IllegalStateException e)
{
// expected
}
try
{
Assert.assertFalse(op.satisfiedBy(buildRow(), staticRow, true));
}
catch (IllegalStateException e)
{
Assert.fail("IllegalStateException should not be thrown when missing column and allowMissingColumns=true");
}
}
@Test
public void testAnalyzeNotIndexedButDefinedColumn() throws Exception
{
final ColumnDefinition firstName = getColumn(UTF8Type.instance.decompose("first_name"));
final ColumnDefinition height = getColumn(UTF8Type.instance.decompose("height"));
// first_name = 'a' AND height != 10
Map<Expression.Op, Expression> expressions;
expressions = convert(Operation.analyzeGroup(controller, OperationType.AND,
Arrays.asList(new SimpleExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
new SimpleExpression(height, Operator.NEQ, Int32Type.instance.decompose(5)))));
Assert.assertEquals(2, expressions.size());
Assert.assertEquals(new Expression("height", Int32Type.instance)
{{
operation = Op.NOT_EQ;
lower = new Bound(Int32Type.instance.decompose(5), true);
upper = lower;
}}, expressions.get(Expression.Op.NOT_EQ));
expressions = convert(Operation.analyzeGroup(controller, OperationType.AND,
Arrays.asList(new SimpleExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
new SimpleExpression(height, Operator.GT, Int32Type.instance.decompose(0)),
new SimpleExpression(height, Operator.NEQ, Int32Type.instance.decompose(5)))));
Assert.assertEquals(2, expressions.size());
Assert.assertEquals(new Expression("height", Int32Type.instance)
{{
operation = Op.RANGE;
lower = new Bound(Int32Type.instance.decompose(0), false);
exclusions.add(Int32Type.instance.decompose(5));
}}, expressions.get(Expression.Op.RANGE));
expressions = convert(Operation.analyzeGroup(controller, OperationType.AND,
Arrays.asList(new SimpleExpression(firstName, Operator.EQ, UTF8Type.instance.decompose("a")),
new SimpleExpression(height, Operator.NEQ, Int32Type.instance.decompose(5)),
new SimpleExpression(height, Operator.GTE, Int32Type.instance.decompose(0)),
new SimpleExpression(height, Operator.LT, Int32Type.instance.decompose(10)))));
Assert.assertEquals(2, expressions.size());
Assert.assertEquals(new Expression("height", Int32Type.instance)
{{
operation = Op.RANGE;
lower = new Bound(Int32Type.instance.decompose(0), true);
upper = new Bound(Int32Type.instance.decompose(10), false);
exclusions.add(Int32Type.instance.decompose(5));
}}, expressions.get(Expression.Op.RANGE));
}
@Test
public void testSatisfiedByWithMultipleTerms()
{
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()));
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, 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, staticRow, false));
}
@Test
public void testSatisfiedByWithClustering()
{
ColumnDefinition location = getColumn(CLUSTERING_BACKEND, UTF8Type.instance.decompose("location"));
ColumnDefinition age = getColumn(CLUSTERING_BACKEND, UTF8Type.instance.decompose("age"));
ColumnDefinition height = getColumn(CLUSTERING_BACKEND, UTF8Type.instance.decompose("height"));
ColumnDefinition score = getColumn(CLUSTERING_BACKEND, UTF8Type.instance.decompose("score"));
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, 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, 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, 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, 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, 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, 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, staticRow, false));
}
private Map<Expression.Op, Expression> convert(Multimap<ColumnDefinition, Expression> expressions)
{
Map<Expression.Op, Expression> converted = new HashMap<>();
for (Expression expression : expressions.values())
{
Expression column = converted.get(expression.getOp());
assert column == null; // sanity check
converted.put(expression.getOp(), expression);
}
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)
{
super(column, operator, value);
}
@Override
protected Kind kind()
{
return Kind.SIMPLE;
}
@Override
public boolean isSatisfiedBy(CFMetaData metadata, DecoratedKey partitionKey, Row row)
{
throw new UnsupportedOperationException();
}
}
private static Unfiltered buildRow(Cell... cells)
{
return buildRow(Clustering.EMPTY, null, cells);
}
private static Row buildRow(Row.Deletion deletion, Cell... cells)
{
return buildRow(Clustering.EMPTY, deletion, cells);
}
private static Row buildRow(Clustering clustering, Cell... cells)
{
return buildRow(clustering, null, cells);
}
private static Row buildRow(Clustering clustering, Row.Deletion deletion, Cell... cells)
{
Row.Builder rowBuilder = BTreeRow.sortedBuilder();
rowBuilder.newRow(clustering);
for (Cell c : cells)
rowBuilder.addCell(c);
if (deletion != null)
rowBuilder.addRowDeletion(deletion);
return rowBuilder.build();
}
private static Cell buildCell(ColumnDefinition column, ByteBuffer value, long timestamp)
{
return BufferCell.live(column, timestamp, value);
}
private static Cell deletedCell(ColumnDefinition column, long timestamp, int nowInSeconds)
{
return BufferCell.tombstone(column, timestamp, nowInSeconds);
}
private static ColumnDefinition getColumn(ByteBuffer name)
{
return getColumn(BACKEND, name);
}
private static ColumnDefinition getColumn(ColumnFamilyStore cfs, ByteBuffer name)
{
return cfs.metadata.getColumnDefinition(name);
}
}