| /* |
| * 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(); |
| } |
| } |
| } |