blob: b69948fa0717bc1fc68b0d62db99d741f981ebc5 [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.cql3.validation.entities;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.dht.ByteOrderedPartitioner;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.commons.lang3.StringUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class SecondaryIndexOnMapEntriesTest extends CQLTester
{
@BeforeClass
public static void setUp()
{
DatabaseDescriptor.setPartitionerUnsafe(ByteOrderedPartitioner.instance);
}
@Test
public void testShouldNotCreateIndexOnFrozenMaps() throws Throwable
{
createTable("CREATE TABLE %s (k TEXT PRIMARY KEY, v FROZEN<MAP<TEXT, TEXT>>)");
assertIndexInvalidForColumn("v");
}
@Test
public void testShouldNotCreateIndexOnNonMapTypes() throws Throwable
{
createTable("CREATE TABLE %s (k TEXT PRIMARY KEY, i INT, t TEXT, b BLOB, s SET<TEXT>, l LIST<TEXT>, tu TUPLE<TEXT>)");
assertIndexInvalidForColumn("i");
assertIndexInvalidForColumn("t");
assertIndexInvalidForColumn("b");
assertIndexInvalidForColumn("s");
assertIndexInvalidForColumn("l");
assertIndexInvalidForColumn("tu");
}
@Test
public void testShouldValidateMapKeyAndValueTypes() throws Throwable
{
createSimpleTableAndIndex();
String query = "SELECT * FROM %s WHERE v[?] = ?";
Object validKey = "valid key";
Object invalidKey = 31415;
Object validValue = 31415;
Object invalidValue = "invalid value";
assertInvalid(query, invalidKey, invalidValue);
assertInvalid(query, invalidKey, validValue);
assertInvalid(query, validKey, invalidValue);
assertReturnsNoRows(query, validKey, validValue);
}
@Test
public void testShouldFindRowsMatchingSingleEqualityRestriction() throws Throwable
{
createSimpleTableAndIndex();
Object[] foo = insertIntoSimpleTable("foo", map("a", 1,
"c", 3));
Object[] bar = insertIntoSimpleTable("bar", map("a", 1,
"b", 2));
Object[] baz = insertIntoSimpleTable("baz", map("b", 2,
"c", 5,
"d", 4));
Object[] qux = insertIntoSimpleTable("qux", map("b", 2,
"d", 4));
assertRowsForConditions(entry("a", 1), bar, foo);
assertRowsForConditions(entry("b", 2), bar, baz, qux);
assertRowsForConditions(entry("c", 3), foo);
assertRowsForConditions(entry("c", 5), baz);
assertRowsForConditions(entry("d", 4), baz, qux);
}
@Test
public void testRequireFilteringDirectiveIfMultipleRestrictionsSpecified() throws Throwable
{
createSimpleTableAndIndex();
String baseQuery = "SELECT * FROM %s WHERE v['foo'] = 31415 AND v['baz'] = 31416";
assertInvalid(baseQuery);
assertReturnsNoRows(baseQuery + " ALLOW FILTERING");
}
@Test
public void testShouldFindRowsMatchingMultipleEqualityRestrictions() throws Throwable
{
createSimpleTableAndIndex();
Object[] foo = insertIntoSimpleTable("foo", map("k1", 1));
Object[] bar = insertIntoSimpleTable("bar", map("k1", 1,
"k2", 2));
Object[] baz = insertIntoSimpleTable("baz", map("k2", 2,
"k3", 3));
Object[] qux = insertIntoSimpleTable("qux", map("k2", 2,
"k3", 3,
"k4", 4));
assertRowsForConditions(entry("k1", 1),
bar, foo);
assertRowsForConditions(entry("k1", 1).entry("k2", 2),
bar);
assertNoRowsForConditions(entry("k1", 1).entry("k2", 2).entry("k3", 3));
assertRowsForConditions(entry("k2", 2).entry("k3", 3),
baz, qux);
assertRowsForConditions(entry("k2", 2).entry("k3", 3).entry("k4", 4),
qux);
assertRowsForConditions(entry("k3", 3).entry("k4", 4),
qux);
assertNoRowsForConditions(entry("k3", 3).entry("k4", 4).entry("k5", 5));
}
@Test
public void testShouldFindRowsMatchingEqualityAndContainsRestrictions() throws Throwable
{
createSimpleTableAndIndex();
Object[] foo = insertIntoSimpleTable("foo", map("common", 31415,
"k1", 1,
"k2", 2,
"k3", 3));
Object[] bar = insertIntoSimpleTable("bar", map("common", 31415,
"k3", 3,
"k4", 4,
"k5", 5));
Object[] baz = insertIntoSimpleTable("baz", map("common", 31415,
"k5", 5,
"k6", 6,
"k7", 7));
assertRowsForConditions(entry("common", 31415),
bar, baz, foo);
assertRowsForConditions(entry("common", 31415).key("k1"),
foo);
assertRowsForConditions(entry("common", 31415).key("k2"),
foo);
assertRowsForConditions(entry("common", 31415).key("k3"),
bar, foo);
assertRowsForConditions(entry("common", 31415).key("k3").value(2),
foo);
assertRowsForConditions(entry("common", 31415).key("k3").value(3),
bar, foo);
assertRowsForConditions(entry("common", 31415).key("k3").value(4),
bar);
assertRowsForConditions(entry("common", 31415).key("k3").key("k5"),
bar);
assertRowsForConditions(entry("common", 31415).key("k5"),
bar, baz);
assertRowsForConditions(entry("common", 31415).key("k5").value(4),
bar);
assertRowsForConditions(entry("common", 31415).key("k5").value(5),
bar, baz);
assertRowsForConditions(entry("common", 31415).key("k5").value(6),
baz);
assertNoRowsForConditions(entry("common", 31415).key("k5").value(8));
}
@Test
public void testShouldNotAcceptUnsupportedRelationsOnEntries() throws Throwable
{
createSimpleTableAndIndex();
assertInvalidRelation("< 31415");
assertInvalidRelation("<= 31415");
assertInvalidRelation("> 31415");
assertInvalidRelation(">= 31415");
assertInvalidRelation("IN (31415, 31416, 31417)");
assertInvalidRelation("CONTAINS 31415");
assertInvalidRelation("CONTAINS KEY 'foo'");
}
@Test
public void testShouldRecognizeAlteredOrDeletedMapEntries() throws Throwable
{
createSimpleTableAndIndex();
Object[] foo = insertIntoSimpleTable("foo", map("common", 31415,
"target", 8192));
Object[] bar = insertIntoSimpleTable("bar", map("common", 31415,
"target", 8192));
Object[] baz = insertIntoSimpleTable("baz", map("common", 31415,
"target", 8192));
assertRowsForConditions(entry("target", 8192),
bar, baz, foo);
baz = updateMapInSimpleTable(baz, "target", 4096);
assertRowsForConditions(entry("target", 8192),
bar, foo);
bar = updateMapInSimpleTable(bar, "target", null);
assertRowsForConditions(entry("target", 8192),
foo);
execute("DELETE FROM %s WHERE k = 'foo'");
assertNoRowsForConditions(entry("target", 8192));
assertRowsForConditions(entry("common", 31415),
bar, baz);
assertRowsForConditions(entry("target", 4096),
baz);
}
@Test
public void testShouldRejectQueriesForNullEntries() throws Throwable
{
createSimpleTableAndIndex();
assertInvalid("SELECT * FROM %s WHERE v['somekey'] = null");
}
@Test
public void testShouldTreatQueriesAgainstFrozenMapIndexesAsInvalid() throws Throwable
{
createTable("CREATE TABLE %s (k TEXT PRIMARY KEY, v FROZEN<MAP<TEXT, TEXT>>)");
createIndex("CREATE INDEX ON %s(FULL(V))");
try
{
execute("SELECT * FROM %s WHERE v['somekey'] = 'somevalue'");
fail("Expected index query to fail");
}
catch (InvalidRequestException e)
{
String expectedMessage = "Map-entry equality predicates on frozen map column v are not supported";
assertTrue("Expected error message to contain '" + expectedMessage + "' but got '" +
e.getMessage() + "'", e.getMessage().contains(expectedMessage));
}
}
private void assertIndexInvalidForColumn(String colname) throws Throwable
{
String query = String.format("CREATE INDEX ON %%s(ENTRIES(%s))", colname);
assertInvalid(query);
}
private void assertReturnsNoRows(String query, Object... params) throws Throwable
{
assertRows(execute(query, params));
}
private void createSimpleTableAndIndex() throws Throwable
{
createTable("CREATE TABLE %s (k TEXT PRIMARY KEY, v MAP<TEXT, INT>)");
createIndex("CREATE INDEX ON %s(ENTRIES(v))");
}
private Object[] insertIntoSimpleTable(String key, Object value) throws Throwable
{
String query = "INSERT INTO %s (k, v) VALUES (?, ?)";
execute(query, key, value);
return row(key, value);
}
private void assertRowsForConditions(IndexWhereClause whereClause, Object[]... rows) throws Throwable
{
assertRows(execute("SELECT * FROM %s WHERE " + whereClause.text(), whereClause.params()), rows);
}
private void assertNoRowsForConditions(IndexWhereClause whereClause) throws Throwable
{
assertRowsForConditions(whereClause);
}
private void assertInvalidRelation(String rel) throws Throwable
{
String query = "SELECT * FROM %s WHERE v " + rel;
assertInvalid(query);
}
private Object[] updateMapInSimpleTable(Object[] row, String mapKey, Integer mapValue) throws Throwable
{
execute("UPDATE %s SET v[?] = ? WHERE k = ?", mapKey, mapValue, row[0]);
UntypedResultSet rawResults = execute("SELECT * FROM %s WHERE k = ?", row[0]);
Map<Object, Object> value = (Map<Object, Object>)row[1];
if (mapValue == null)
{
value.remove(mapKey);
}
else
{
value.put(mapKey, mapValue);
}
return row;
}
private IndexWhereClause entry(Object key, Object value)
{
return (new IndexWhereClause()).entry(key, value);
}
private static final class IndexWhereClause
{
private final List<String> preds = new ArrayList<>();
private final List<Object> params = new ArrayList<>();
public IndexWhereClause entry(Object key, Object value)
{
preds.add("v[?] = ?");
params.add(key);
params.add(value);
return this;
}
public IndexWhereClause key(Object key)
{
preds.add("v CONTAINS KEY ?");
params.add(key);
return this;
}
public IndexWhereClause value(Object value)
{
preds.add("v CONTAINS ?");
params.add(value);
return this;
}
public String text()
{
if (preds.size() == 1)
return preds.get(0);
return StringUtils.join(preds, " AND ") + " ALLOW FILTERING";
}
public Object[] params()
{
return params.toArray();
}
}
}