| /* |
| * 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 java.nio.ByteBuffer; |
| import java.util.*; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CountDownLatch; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.junit.Test; |
| |
| import org.apache.cassandra.config.CFMetaData; |
| import org.apache.cassandra.config.ColumnDefinition; |
| import org.apache.cassandra.config.DatabaseDescriptor; |
| import org.apache.cassandra.cql3.CQLTester; |
| import org.apache.cassandra.cql3.ColumnIdentifier; |
| import org.apache.cassandra.cql3.Duration; |
| import org.apache.cassandra.cql3.QueryProcessor; |
| import org.apache.cassandra.cql3.restrictions.StatementRestrictions; |
| import org.apache.cassandra.cql3.statements.IndexTarget; |
| import org.apache.cassandra.db.ColumnFamilyStore; |
| import org.apache.cassandra.db.DeletionTime; |
| import org.apache.cassandra.db.marshal.AbstractType; |
| import org.apache.cassandra.db.rows.Cell; |
| import org.apache.cassandra.db.rows.Row; |
| import org.apache.cassandra.exceptions.ConfigurationException; |
| import org.apache.cassandra.exceptions.SyntaxException; |
| import org.apache.cassandra.index.IndexNotAvailableException; |
| import org.apache.cassandra.index.SecondaryIndexManager; |
| import org.apache.cassandra.index.StubIndex; |
| import org.apache.cassandra.index.internal.CustomCassandraIndex; |
| import org.apache.cassandra.index.sasi.SASIIndex; |
| import org.apache.cassandra.schema.IndexMetadata; |
| import org.apache.cassandra.service.ClientState; |
| import org.apache.cassandra.transport.messages.ResultMessage; |
| import org.apache.cassandra.utils.ByteBufferUtil; |
| import org.apache.cassandra.utils.MD5Digest; |
| import org.apache.cassandra.utils.Pair; |
| |
| import static org.apache.cassandra.Util.throwAssert; |
| import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER; |
| import static org.apache.cassandra.utils.ByteBufferUtil.bytes; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| public class SecondaryIndexTest extends CQLTester |
| { |
| private static final int TOO_BIG = 1024 * 65; |
| |
| @Test |
| public void testCreateAndDropIndex() throws Throwable |
| { |
| testCreateAndDropIndex("test", false); |
| testCreateAndDropIndex("test2", true); |
| } |
| |
| @Test |
| public void testCreateAndDropIndexWithQuotedIdentifier() throws Throwable |
| { |
| testCreateAndDropIndex("\"quoted_ident\"", false); |
| testCreateAndDropIndex("\"quoted_ident2\"", true); |
| } |
| |
| @Test |
| public void testCreateAndDropIndexWithCamelCaseIdentifier() throws Throwable |
| { |
| testCreateAndDropIndex("CamelCase", false); |
| testCreateAndDropIndex("CamelCase2", true); |
| } |
| |
| /** |
| * Test creating and dropping an index with the specified name. |
| * |
| * @param indexName the index name |
| * @param addKeyspaceOnDrop add the keyspace name in the drop statement |
| * @throws Throwable if an error occurs |
| */ |
| private void testCreateAndDropIndex(String indexName, boolean addKeyspaceOnDrop) throws Throwable |
| { |
| execute("USE system"); |
| assertInvalidMessage(String.format("Index '%s' could not be found", |
| removeQuotes(indexName.toLowerCase(Locale.US))), |
| "DROP INDEX " + indexName + ";"); |
| |
| createTable("CREATE TABLE %s (a int primary key, b int);"); |
| createIndex("CREATE INDEX " + indexName + " ON %s(b);"); |
| createIndex("CREATE INDEX IF NOT EXISTS " + indexName + " ON %s(b);"); |
| |
| assertInvalidMessage(String.format("Index %s already exists", |
| removeQuotes(indexName.toLowerCase(Locale.US))), |
| "CREATE INDEX " + indexName + " ON %s(b)"); |
| |
| // IF NOT EXISTS should apply in cases where the new index differs from an existing one in name only |
| String otherIndexName = "index_" + System.nanoTime(); |
| assertEquals(1, getCurrentColumnFamilyStore().metadata.getIndexes().size()); |
| createIndex("CREATE INDEX IF NOT EXISTS " + otherIndexName + " ON %s(b)"); |
| assertEquals(1, getCurrentColumnFamilyStore().metadata.getIndexes().size()); |
| assertInvalidMessage(String.format("Index %s is a duplicate of existing index %s", |
| removeQuotes(otherIndexName.toLowerCase(Locale.US)), |
| removeQuotes(indexName.toLowerCase(Locale.US))), |
| "CREATE INDEX " + otherIndexName + " ON %s(b)"); |
| |
| execute("INSERT INTO %s (a, b) values (?, ?);", 0, 0); |
| execute("INSERT INTO %s (a, b) values (?, ?);", 1, 1); |
| execute("INSERT INTO %s (a, b) values (?, ?);", 2, 2); |
| execute("INSERT INTO %s (a, b) values (?, ?);", 3, 1); |
| |
| assertRows(execute("SELECT * FROM %s where b = ?", 1), row(1, 1), row(3, 1)); |
| assertInvalidMessage(String.format("Index '%s' could not be found in any of the tables of keyspace 'system'", |
| removeQuotes(indexName.toLowerCase(Locale.US))), |
| "DROP INDEX " + indexName); |
| |
| if (addKeyspaceOnDrop) |
| { |
| dropIndex("DROP INDEX " + KEYSPACE + "." + indexName); |
| } |
| else |
| { |
| execute("USE " + KEYSPACE); |
| execute("DROP INDEX " + indexName); |
| } |
| |
| assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, |
| "SELECT * FROM %s where b = ?", 1); |
| dropIndex("DROP INDEX IF EXISTS " + indexName); |
| assertInvalidMessage(String.format("Index '%s' could not be found", |
| removeQuotes(indexName.toLowerCase(Locale.US))), |
| "DROP INDEX " + indexName); |
| } |
| |
| /** |
| * Removes the quotes from the specified index name. |
| * |
| * @param indexName the index name from which the quotes must be removed. |
| * @return the unquoted index name. |
| */ |
| private static String removeQuotes(String indexName) |
| { |
| return StringUtils.remove(indexName, '\"'); |
| } |
| |
| /** |
| * Check that you can query for an indexed column even with a key EQ clause, |
| * migrated from cql_tests.py:TestCQL.static_cf_test() |
| */ |
| @Test |
| public void testSelectWithEQ() throws Throwable |
| { |
| createTable("CREATE TABLE %s (userid uuid PRIMARY KEY, firstname text, lastname text, age int)"); |
| createIndex("CREATE INDEX byAge ON %s(age)"); |
| |
| UUID id1 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); |
| UUID id2 = UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"); |
| |
| execute("INSERT INTO %s (userid, firstname, lastname, age) VALUES (?, 'Frodo', 'Baggins', 32)", id1); |
| execute("UPDATE %s SET firstname = 'Samwise', lastname = 'Gamgee', age = 33 WHERE userid = ?", id2); |
| |
| beforeAndAfterFlush(() -> { |
| assertEmpty(execute("SELECT firstname FROM %s WHERE userid = ? AND age = 33", id1)); |
| |
| assertRows(execute("SELECT firstname FROM %s WHERE userid = ? AND age = 33", id2), |
| row("Samwise")); |
| }); |
| } |
| |
| /** |
| * Check CREATE INDEX without name and validate the index can be dropped, |
| * migrated from cql_tests.py:TestCQL.nameless_index_test() |
| */ |
| @Test |
| public void testNamelessIndex() throws Throwable |
| { |
| createTable(" CREATE TABLE %s (id text PRIMARY KEY, birth_year int)"); |
| |
| createIndex("CREATE INDEX on %s (birth_year)"); |
| |
| execute("INSERT INTO %s (id, birth_year) VALUES ('Tom', 42)"); |
| execute("INSERT INTO %s (id, birth_year) VALUES ('Paul', 24)"); |
| execute("INSERT INTO %s (id, birth_year) VALUES ('Bob', 42)"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("SELECT id FROM %s WHERE birth_year = 42"), |
| row("Tom"), |
| row("Bob")); |
| }); |
| |
| execute("DROP INDEX %s_birth_year_idx"); |
| |
| assertInvalid("SELECT id FROM users WHERE birth_year = 42"); |
| } |
| |
| /** |
| * Test range queries with 2ndary indexes (#4257), |
| * migrated from cql_tests.py:TestCQL.range_query_2ndary_test() |
| */ |
| @Test |
| public void testRangeQuery() throws Throwable |
| { |
| createTable("CREATE TABLE %s (id int primary key, row int, setid int)"); |
| createIndex("CREATE INDEX indextest_setid_idx ON %s (setid)"); |
| |
| execute("INSERT INTO %s (id, row, setid) VALUES (?, ?, ?)", 0, 0, 0); |
| execute("INSERT INTO %s (id, row, setid) VALUES (?, ?, ?)", 1, 1, 0); |
| execute("INSERT INTO %s (id, row, setid) VALUES (?, ?, ?)", 2, 2, 0); |
| execute("INSERT INTO %s (id, row, setid) VALUES (?, ?, ?)", 3, 3, 0); |
| |
| assertInvalid("SELECT * FROM %s WHERE setid = 0 AND row < 1"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("SELECT * FROM %s WHERE setid = 0 AND row < 1 ALLOW FILTERING"), |
| row(0, 0, 0)); |
| }); |
| } |
| |
| /** |
| * Check for unknown compression parameters options (#4266), |
| * migrated from cql_tests.py:TestCQL.compression_option_validation_test() |
| */ |
| @Test |
| public void testUnknownCompressionOptions() throws Throwable |
| { |
| String tableName = createTableName(); |
| assertInvalidThrow(SyntaxException.class, String.format("CREATE TABLE %s (key varchar PRIMARY KEY, password varchar, gender varchar) WITH compression_parameters:sstable_compressor = 'DeflateCompressor'", tableName)); |
| |
| assertInvalidThrow(ConfigurationException.class, String.format("CREATE TABLE %s (key varchar PRIMARY KEY, password varchar, gender varchar) WITH compression = { 'sstable_compressor': 'DeflateCompressor' }", |
| tableName)); |
| } |
| |
| /** |
| * Check one can use arbitrary name for datacenter when creating keyspace (#4278), |
| * migrated from cql_tests.py:TestCQL.keyspace_creation_options_test() |
| */ |
| @Test |
| public void testDataCenterName() throws Throwable |
| { |
| execute("CREATE KEYSPACE Foo WITH replication = { 'class' : 'NetworkTopologyStrategy', 'us-east' : 1, 'us-west' : 1 };"); |
| } |
| |
| /** |
| * Migrated from cql_tests.py:TestCQL.indexes_composite_test() |
| */ |
| @Test |
| public void testIndexOnComposite() throws Throwable |
| { |
| String tableName = createTable("CREATE TABLE %s (blog_id int, timestamp int, author text, content text, PRIMARY KEY (blog_id, timestamp))"); |
| |
| execute("INSERT INTO %s (blog_id, timestamp, author, content) VALUES (?, ?, ?, ?)", 0, 0, "bob", "1st post"); |
| execute("INSERT INTO %s (blog_id, timestamp, author, content) VALUES (?, ?, ?, ?)", 0, 1, "tom", "2nd post"); |
| execute("INSERT INTO %s (blog_id, timestamp, author, content) VALUES (?, ?, ?, ?)", 0, 2, "bob", "3rd post"); |
| execute("INSERT INTO %s (blog_id, timestamp, author, content) VALUES (?, ?, ?, ?)", 0, 3, "tom", "4th post"); |
| execute("INSERT INTO %s (blog_id, timestamp, author, content) VALUES (?, ?, ?, ?)", 1, 0, "bob", "5th post"); |
| |
| createIndex("CREATE INDEX authoridx ON %s (author)"); |
| |
| assertTrue(waitForIndex(keyspace(), tableName, "authoridx")); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("SELECT blog_id, timestamp FROM %s WHERE author = 'bob'"), |
| row(1, 0), |
| row(0, 0), |
| row(0, 2)); |
| }); |
| |
| execute("INSERT INTO %s (blog_id, timestamp, author, content) VALUES (?, ?, ?, ?)", 1, 1, "tom", "6th post"); |
| execute("INSERT INTO %s (blog_id, timestamp, author, content) VALUES (?, ?, ?, ?)", 1, 2, "tom", "7th post"); |
| execute("INSERT INTO %s (blog_id, timestamp, author, content) VALUES (?, ?, ?, ?)", 1, 3, "bob", "8th post"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("SELECT blog_id, timestamp FROM %s WHERE author = 'bob'"), |
| row(1, 0), |
| row(1, 3), |
| row(0, 0), |
| row(0, 2)); |
| }); |
| |
| execute("DELETE FROM %s WHERE blog_id = 0 AND timestamp = 2"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("SELECT blog_id, timestamp FROM %s WHERE author = 'bob'"), |
| row(1, 0), |
| row(1, 3), |
| row(0, 0)); |
| }); |
| } |
| |
| /** |
| * Test for the validation bug of #4709, |
| * migrated from cql_tests.py:TestCQL.refuse_in_with_indexes_test() |
| */ |
| @Test |
| public void testInvalidIndexSelect() throws Throwable |
| { |
| createTable("create table %s (pk varchar primary key, col1 varchar, col2 varchar)"); |
| createIndex("create index on %s (col1)"); |
| createIndex("create index on %s (col2)"); |
| |
| execute("insert into %s (pk, col1, col2) values ('pk1','foo1','bar1')"); |
| execute("insert into %s (pk, col1, col2) values ('pk1a','foo1','bar1')"); |
| execute("insert into %s (pk, col1, col2) values ('pk1b','foo1','bar1')"); |
| execute("insert into %s (pk, col1, col2) values ('pk1c','foo1','bar1')"); |
| execute("insert into %s (pk, col1, col2) values ('pk2','foo2','bar2')"); |
| execute("insert into %s (pk, col1, col2) values ('pk3','foo3','bar3')"); |
| assertInvalid("select * from %s where col2 in ('bar1', 'bar2')"); |
| |
| //Migrated from cql_tests.py:TestCQL.bug_6050_test() |
| createTable("CREATE TABLE %s (k int PRIMARY KEY, a int, b int)"); |
| |
| createIndex("CREATE INDEX ON %s (a)"); |
| assertInvalid("SELECT * FROM %s WHERE a = 3 AND b IN (1, 3)"); |
| |
| } |
| |
| /** |
| * Migrated from cql_tests.py:TestCQL.edge_2i_on_complex_pk_test() |
| */ |
| @Test |
| public void testIndexesOnComplexPrimaryKey() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk0 int, pk1 int, ck0 int, ck1 int, ck2 int, value int, PRIMARY KEY ((pk0, pk1), ck0, ck1, ck2))"); |
| |
| execute("CREATE INDEX ON %s (pk0)"); |
| execute("CREATE INDEX ON %s (ck0)"); |
| execute("CREATE INDEX ON %s (ck1)"); |
| execute("CREATE INDEX ON %s (ck2)"); |
| |
| execute("INSERT INTO %s (pk0, pk1, ck0, ck1, ck2, value) VALUES (0, 1, 2, 3, 4, 5)"); |
| execute("INSERT INTO %s (pk0, pk1, ck0, ck1, ck2, value) VALUES (1, 2, 3, 4, 5, 0)"); |
| execute("INSERT INTO %s (pk0, pk1, ck0, ck1, ck2, value) VALUES (2, 3, 4, 5, 0, 1)"); |
| execute("INSERT INTO %s (pk0, pk1, ck0, ck1, ck2, value) VALUES (3, 4, 5, 0, 1, 2)"); |
| execute("INSERT INTO %s (pk0, pk1, ck0, ck1, ck2, value) VALUES (4, 5, 0, 1, 2, 3)"); |
| execute("INSERT INTO %s (pk0, pk1, ck0, ck1, ck2, value) VALUES (5, 0, 1, 2, 3, 4)"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("SELECT value FROM %s WHERE pk0 = 2"), |
| row(1)); |
| |
| assertRows(execute("SELECT value FROM %s WHERE ck0 = 0"), |
| row(3)); |
| |
| assertRows(execute("SELECT value FROM %s WHERE pk0 = 3 AND pk1 = 4 AND ck1 = 0"), |
| row(2)); |
| |
| assertRows(execute("SELECT value FROM %s WHERE pk0 = 5 AND pk1 = 0 AND ck0 = 1 AND ck2 = 3 ALLOW FILTERING"), |
| row(4)); |
| }); |
| } |
| |
| /** |
| * Test for CASSANDRA-5240, |
| * migrated from cql_tests.py:TestCQL.bug_5240_test() |
| */ |
| @Test |
| public void testIndexOnCompoundRowKey() throws Throwable |
| { |
| createTable("CREATE TABLE %s (interval text, seq int, id int, severity int, PRIMARY KEY ((interval, seq), id) ) WITH CLUSTERING ORDER BY (id DESC)"); |
| |
| execute("CREATE INDEX ON %s (severity)"); |
| |
| execute("insert into %s (interval, seq, id , severity) values('t',1, 1, 1)"); |
| execute("insert into %s (interval, seq, id , severity) values('t',1, 2, 1)"); |
| execute("insert into %s (interval, seq, id , severity) values('t',1, 3, 2)"); |
| execute("insert into %s (interval, seq, id , severity) values('t',1, 4, 3)"); |
| execute("insert into %s (interval, seq, id , severity) values('t',2, 1, 3)"); |
| execute("insert into %s (interval, seq, id , severity) values('t',2, 2, 3)"); |
| execute("insert into %s (interval, seq, id , severity) values('t',2, 3, 1)"); |
| execute("insert into %s (interval, seq, id , severity) values('t',2, 4, 2)"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("select * from %s where severity = 3 and interval = 't' and seq =1"), |
| row("t", 1, 4, 3)); |
| |
| }); |
| } |
| |
| /** |
| * Migrated from cql_tests.py:TestCQL.secondary_index_counters() |
| */ |
| @Test |
| public void testIndexOnCountersInvalid() throws Throwable |
| { |
| createTable("CREATE TABLE %s (k int PRIMARY KEY, c counter)"); |
| assertInvalid("CREATE INDEX ON test(c)"); |
| } |
| |
| /** |
| * Migrated from cql_tests.py:TestCQL.collection_indexing_test() |
| */ |
| @Test |
| public void testIndexOnCollections() throws Throwable |
| { |
| createTable(" CREATE TABLE %s ( k int, v int, l list<int>, s set<text>, m map<text, int>, PRIMARY KEY (k, v))"); |
| |
| createIndex("CREATE INDEX ON %s (l)"); |
| createIndex("CREATE INDEX ON %s (s)"); |
| createIndex("CREATE INDEX ON %s (m)"); |
| |
| execute("INSERT INTO %s (k, v, l, s, m) VALUES (0, 0, [1, 2], {'a'}, {'a' : 1})"); |
| execute("INSERT INTO %s (k, v, l, s, m) VALUES (0, 1, [3, 4], {'b', 'c'}, {'a' : 1, 'b' : 2})"); |
| execute("INSERT INTO %s (k, v, l, s, m) VALUES (0, 2, [1], {'a', 'c'}, {'c' : 3})"); |
| execute("INSERT INTO %s (k, v, l, s, m) VALUES (1, 0, [1, 2, 4], {}, {'b' : 1})"); |
| execute("INSERT INTO %s (k, v, l, s, m) VALUES (1, 1, [4, 5], {'d'}, {'a' : 1, 'b' : 3})"); |
| |
| beforeAndAfterFlush(() -> { |
| // lists |
| assertRows(execute("SELECT k, v FROM %s WHERE l CONTAINS 1"), row(1, 0), row(0, 0), row(0, 2)); |
| assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND l CONTAINS 1"), row(0, 0), row(0, 2)); |
| assertRows(execute("SELECT k, v FROM %s WHERE l CONTAINS 2"), row(1, 0), row(0, 0)); |
| assertEmpty(execute("SELECT k, v FROM %s WHERE l CONTAINS 6")); |
| |
| // sets |
| assertRows(execute("SELECT k, v FROM %s WHERE s CONTAINS 'a'"), row(0, 0), row(0, 2)); |
| assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND s CONTAINS 'a'"), row(0, 0), row(0, 2)); |
| assertRows(execute("SELECT k, v FROM %s WHERE s CONTAINS 'd'"), row(1, 1)); |
| assertEmpty(execute("SELECT k, v FROM %s WHERE s CONTAINS 'e'")); |
| |
| // maps |
| assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS 1"), row(1, 0), row(1, 1), row(0, 0), row(0, 1)); |
| assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m CONTAINS 1"), row(0, 0), row(0, 1)); |
| assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS 2"), row(0, 1)); |
| assertEmpty(execute("SELECT k, v FROM %s WHERE m CONTAINS 4")); |
| }); |
| } |
| |
| @Test |
| public void testSelectOnMultiIndexOnCollectionsWithNull() throws Throwable |
| { |
| createTable(" CREATE TABLE %s ( k int, v int, x text, l list<int>, s set<text>, m map<text, int>, PRIMARY KEY (k, v))"); |
| |
| createIndex("CREATE INDEX ON %s (x)"); |
| createIndex("CREATE INDEX ON %s (v)"); |
| createIndex("CREATE INDEX ON %s (s)"); |
| createIndex("CREATE INDEX ON %s (m)"); |
| |
| |
| execute("INSERT INTO %s (k, v, x, l, s, m) VALUES (0, 0, 'x', [1, 2], {'a'}, {'a' : 1})"); |
| execute("INSERT INTO %s (k, v, x, l, s, m) VALUES (0, 1, 'x', [3, 4], {'b', 'c'}, {'a' : 1, 'b' : 2})"); |
| execute("INSERT INTO %s (k, v, x, l, s, m) VALUES (0, 2, 'x', [1], {'a', 'c'}, {'c' : 3})"); |
| execute("INSERT INTO %s (k, v, x, l, s, m) VALUES (1, 0, 'x', [1, 2, 4], {}, {'b' : 1})"); |
| execute("INSERT INTO %s (k, v, x, l, s, m) VALUES (1, 1, 'x', [4, 5], {'d'}, {'a' : 1, 'b' : 3})"); |
| execute("INSERT INTO %s (k, v, x, l, s, m) VALUES (1, 2, 'x', null, null, null)"); |
| |
| beforeAndAfterFlush(() -> { |
| // lists |
| assertRows(execute("SELECT k, v FROM %s WHERE x = 'x' AND l CONTAINS 1 ALLOW FILTERING"), row(1, 0), row(0, 0), row(0, 2)); |
| assertRows(execute("SELECT k, v FROM %s WHERE x = 'x' AND k = 0 AND l CONTAINS 1 ALLOW FILTERING"), row(0, 0), row(0, 2)); |
| assertRows(execute("SELECT k, v FROM %s WHERE x = 'x' AND l CONTAINS 2 ALLOW FILTERING"), row(1, 0), row(0, 0)); |
| assertEmpty(execute("SELECT k, v FROM %s WHERE x = 'x' AND l CONTAINS 6 ALLOW FILTERING")); |
| |
| // sets |
| assertRows(execute("SELECT k, v FROM %s WHERE x = 'x' AND s CONTAINS 'a' ALLOW FILTERING" ), row(0, 0), row(0, 2)); |
| assertRows(execute("SELECT k, v FROM %s WHERE x = 'x' AND k = 0 AND s CONTAINS 'a' ALLOW FILTERING"), row(0, 0), row(0, 2)); |
| assertRows(execute("SELECT k, v FROM %s WHERE x = 'x' AND s CONTAINS 'd' ALLOW FILTERING"), row(1, 1)); |
| assertEmpty(execute("SELECT k, v FROM %s WHERE x = 'x' AND s CONTAINS 'e' ALLOW FILTERING")); |
| |
| // maps |
| assertRows(execute("SELECT k, v FROM %s WHERE x = 'x' AND m CONTAINS 1 ALLOW FILTERING"), row(1, 0), row(1, 1), row(0, 0), row(0, 1)); |
| assertRows(execute("SELECT k, v FROM %s WHERE x = 'x' AND k = 0 AND m CONTAINS 1 ALLOW FILTERING"), row(0, 0), row(0, 1)); |
| assertRows(execute("SELECT k, v FROM %s WHERE x = 'x' AND m CONTAINS 2 ALLOW FILTERING"), row(0, 1)); |
| assertEmpty(execute("SELECT k, v FROM %s WHERE x = 'x' AND m CONTAINS 4 ALLOW FILTERING")); |
| }); |
| } |
| |
| /** |
| * Migrated from cql_tests.py:TestCQL.map_keys_indexing() |
| */ |
| @Test |
| public void testIndexOnMapKeys() throws Throwable |
| { |
| createTable("CREATE TABLE %s (k int, v int, m map<text, int>, PRIMARY KEY (k, v))"); |
| |
| createIndex("CREATE INDEX ON %s(keys(m))"); |
| |
| execute("INSERT INTO %s (k, v, m) VALUES (0, 0, {'a' : 1})"); |
| execute("INSERT INTO %s (k, v, m) VALUES (0, 1, {'a' : 1, 'b' : 2})"); |
| execute("INSERT INTO %s (k, v, m) VALUES (0, 2, {'c' : 3})"); |
| execute("INSERT INTO %s (k, v, m) VALUES (1, 0, {'b' : 1})"); |
| execute("INSERT INTO %s (k, v, m) VALUES (1, 1, {'a' : 1, 'b' : 3})"); |
| |
| // maps |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS KEY 'a'"), row(1, 1), row(0, 0), row(0, 1)); |
| assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m CONTAINS KEY 'a'"), row(0, 0), row(0, 1)); |
| assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS KEY 'c'"), row(0, 2)); |
| assertEmpty(execute("SELECT k, v FROM %s WHERE m CONTAINS KEY 'd'")); |
| }); |
| } |
| |
| /** |
| * Test for #6950 bug, |
| * migrated from cql_tests.py:TestCQL.key_index_with_reverse_clustering() |
| */ |
| @Test |
| public void testIndexOnKeyWithReverseClustering() throws Throwable |
| { |
| createTable("CREATE TABLE %s (k1 int, k2 int, v int, PRIMARY KEY ((k1, k2), v) ) WITH CLUSTERING ORDER BY (v DESC)"); |
| |
| createIndex("CREATE INDEX ON %s (k2)"); |
| |
| execute("INSERT INTO %s (k1, k2, v) VALUES (0, 0, 1)"); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (0, 1, 2)"); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (0, 0, 3)"); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (1, 0, 4)"); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (1, 1, 5)"); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (2, 0, 7)"); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (2, 1, 8)"); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (3, 0, 1)"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("SELECT * FROM %s WHERE k2 = 0 AND v >= 2 ALLOW FILTERING"), |
| row(2, 0, 7), |
| row(0, 0, 3), |
| row(1, 0, 4)); |
| }); |
| } |
| |
| /** |
| * Test for CASSANDRA-6612, |
| * migrated from cql_tests.py:TestCQL.bug_6612_test() |
| */ |
| @Test |
| public void testSelectCountOnIndexedColumn() throws Throwable |
| { |
| createTable("CREATE TABLE %s (username text, session_id text, app_name text, account text, last_access timestamp, created_on timestamp, PRIMARY KEY (username, session_id, app_name, account))"); |
| |
| createIndex("create index ON %s (app_name)"); |
| createIndex("create index ON %s (last_access)"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("select count(*) from %s where app_name='foo' and account='bar' and last_access > 4 allow filtering"), row(0L)); |
| }); |
| |
| execute("insert into %s (username, session_id, app_name, account, last_access, created_on) values ('toto', 'foo', 'foo', 'bar', 12, 13)"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("select count(*) from %s where app_name='foo' and account='bar' and last_access > 4 allow filtering"), row(1L)); |
| }); |
| } |
| |
| @Test |
| public void testSyntaxVariationsForIndexOnCollectionsValue() throws Throwable |
| { |
| createTable("CREATE TABLE %s (k int, m map<int, int>, l list<int>, s set<int>, PRIMARY KEY (k))"); |
| createAndDropCollectionValuesIndex("m"); |
| createAndDropCollectionValuesIndex("l"); |
| createAndDropCollectionValuesIndex("s"); |
| } |
| |
| private void createAndDropCollectionValuesIndex(String columnName) throws Throwable |
| { |
| String indexName = columnName + "_idx"; |
| SecondaryIndexManager indexManager = getCurrentColumnFamilyStore().indexManager; |
| createIndex(String.format("CREATE INDEX %s on %%s(%s)", indexName, columnName)); |
| IndexMetadata indexDef = indexManager.getIndexByName(indexName).getIndexMetadata(); |
| assertEquals(String.format("values(%s)", columnName), indexDef.options.get(IndexTarget.TARGET_OPTION_NAME)); |
| dropIndex(String.format("DROP INDEX %s.%s", KEYSPACE, indexName)); |
| assertFalse(indexManager.hasIndexes()); |
| createIndex(String.format("CREATE INDEX %s on %%s(values(%s))", indexName, columnName)); |
| assertEquals(indexDef, indexManager.getIndexByName(indexName).getIndexMetadata()); |
| dropIndex(String.format("DROP INDEX %s.%s", KEYSPACE, indexName)); |
| } |
| |
| @Test |
| public void testCreateIndexWithQuotedColumnNames() throws Throwable |
| { |
| createTable("CREATE TABLE %s (" + |
| " k int," + |
| " v int, " + |
| " lower_case_map map<int, int>," + |
| " \"MixedCaseMap\" map<int, int>," + |
| " lower_case_frozen_list frozen<list<int>>," + |
| " \"UPPER_CASE_FROZEN_LIST\" frozen<list<int>>," + |
| " \"set name with spaces\" set<int>," + |
| " \"column_name_with\"\"escaped quote\" int," + |
| " PRIMARY KEY (k))"); |
| |
| createAndDropIndexWithQuotedColumnIdentifier("\"v\""); |
| createAndDropIndexWithQuotedColumnIdentifier("keys(\"lower_case_map\")"); |
| createAndDropIndexWithQuotedColumnIdentifier("keys(\"MixedCaseMap\")"); |
| createAndDropIndexWithQuotedColumnIdentifier("full(\"lower_case_frozen_list\")"); |
| createAndDropIndexWithQuotedColumnIdentifier("full(\"UPPER_CASE_FROZEN_LIST\")"); |
| createAndDropIndexWithQuotedColumnIdentifier("values(\"set name with spaces\")"); |
| createAndDropIndexWithQuotedColumnIdentifier("\"column_name_with\"\"escaped quote\""); |
| } |
| |
| private void createAndDropIndexWithQuotedColumnIdentifier(String target) throws Throwable |
| { |
| String indexName = "test_mixed_case_idx"; |
| createIndex(String.format("CREATE INDEX %s ON %%s(%s)", indexName, target)); |
| SecondaryIndexManager indexManager = getCurrentColumnFamilyStore().indexManager; |
| IndexMetadata indexDef = indexManager.getIndexByName(indexName).getIndexMetadata(); |
| dropIndex(String.format("DROP INDEX %s.%s", KEYSPACE, indexName)); |
| // verify we can re-create the index using the target string |
| createIndex(String.format("CREATE INDEX %s ON %%s(%s)", |
| indexName, indexDef.options.get(IndexTarget.TARGET_OPTION_NAME))); |
| assertEquals(indexDef, indexManager.getIndexByName(indexName).getIndexMetadata()); |
| dropIndex(String.format("DROP INDEX %s.%s", KEYSPACE, indexName)); |
| } |
| |
| |
| /** |
| * Test for CASSANDRA-5732, Can not query secondary index |
| * migrated from cql_tests.py:TestCQL.bug_5732_test(), |
| * which was executing with a row cache size of 100 MB |
| * and restarting the node, here we just cleanup the cache. |
| */ |
| @Test |
| public void testCanQuerySecondaryIndex() throws Throwable |
| { |
| String tableName = createTable("CREATE TABLE %s (k int PRIMARY KEY, v int,)"); |
| |
| execute("ALTER TABLE %s WITH CACHING = { 'keys': 'ALL', 'rows_per_partition': 'ALL' }"); |
| execute("INSERT INTO %s (k,v) VALUES (0,0)"); |
| execute("INSERT INTO %s (k,v) VALUES (1,1)"); |
| |
| createIndex("CREATE INDEX testindex on %s (v)"); |
| assertTrue(waitForIndex(keyspace(), tableName, "testindex")); |
| |
| assertRows(execute("SELECT k FROM %s WHERE v = 0"), row(0)); |
| cleanupCache(); |
| assertRows(execute("SELECT k FROM %s WHERE v = 0"), row(0)); |
| } |
| |
| // CASSANDRA-8280/8081 |
| // reject updates with indexed values where value > 64k |
| // make sure we check conditional and unconditional statements, |
| // both singly and in batches (CASSANDRA-10536) |
| @Test |
| public void testIndexOnCompositeValueOver64k() throws Throwable |
| { |
| createTable("CREATE TABLE %s(a int, b int, c blob, PRIMARY KEY (a))"); |
| createIndex("CREATE INDEX ON %s(c)"); |
| failInsert("INSERT INTO %s (a, b, c) VALUES (0, 0, ?)", ByteBuffer.allocate(TOO_BIG)); |
| failInsert("INSERT INTO %s (a, b, c) VALUES (0, 0, ?) IF NOT EXISTS", ByteBuffer.allocate(TOO_BIG)); |
| failInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b, c) VALUES (0, 0, ?);\n" + |
| "APPLY BATCH", |
| ByteBuffer.allocate(TOO_BIG)); |
| failInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b, c) VALUES (0, 0, ?) IF NOT EXISTS;\n" + |
| "APPLY BATCH", |
| ByteBuffer.allocate(TOO_BIG)); |
| } |
| |
| @Test |
| public void testCompactTableWithValueOver64k() throws Throwable |
| { |
| createTable("CREATE TABLE %s(a int, b blob, PRIMARY KEY (a)) WITH COMPACT STORAGE"); |
| createIndex("CREATE INDEX ON %s(b)"); |
| failInsert("INSERT INTO %s (a, b) VALUES (0, ?)", ByteBuffer.allocate(TOO_BIG)); |
| failInsert("INSERT INTO %s (a, b) VALUES (0, ?) IF NOT EXISTS", ByteBuffer.allocate(TOO_BIG)); |
| failInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b) VALUES (0, ?);\n" + |
| "APPLY BATCH", |
| ByteBuffer.allocate(TOO_BIG)); |
| failInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b) VALUES (0, ?) IF NOT EXISTS;\n" + |
| "APPLY BATCH", |
| ByteBuffer.allocate(TOO_BIG)); |
| } |
| |
| @Test |
| public void testIndexOnPartitionKeyInsertValueOver64k() throws Throwable |
| { |
| createTable("CREATE TABLE %s(a int, b int, c blob, PRIMARY KEY ((a, b)))"); |
| createIndex("CREATE INDEX ON %s(a)"); |
| succeedInsert("INSERT INTO %s (a, b, c) VALUES (0, 0, ?) IF NOT EXISTS", ByteBuffer.allocate(TOO_BIG)); |
| succeedInsert("INSERT INTO %s (a, b, c) VALUES (0, 0, ?)", ByteBuffer.allocate(TOO_BIG)); |
| succeedInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b, c) VALUES (0, 0, ?);\n" + |
| "APPLY BATCH", ByteBuffer.allocate(TOO_BIG)); |
| |
| // the indexed value passes validation, but the batch size will |
| // exceed the default failure threshold, so temporarily raise it |
| // (the non-conditional batch doesn't hit this because |
| // BatchStatement::executeInternal skips the size check but CAS |
| // path does not) |
| long batchSizeThreshold = DatabaseDescriptor.getBatchSizeFailThreshold(); |
| try |
| { |
| DatabaseDescriptor.setBatchSizeFailThresholdInKB( (TOO_BIG / 1024) * 2); |
| succeedInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b, c) VALUES (1, 1, ?) IF NOT EXISTS;\n" + |
| "APPLY BATCH", ByteBuffer.allocate(TOO_BIG)); |
| } |
| finally |
| { |
| DatabaseDescriptor.setBatchSizeFailThresholdInKB((int) (batchSizeThreshold / 1024)); |
| } |
| } |
| |
| @Test |
| public void testIndexOnPartitionKeyWithStaticColumnAndNoRows() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk1 int, pk2 int, c int, s int static, v int, PRIMARY KEY((pk1, pk2), c))"); |
| createIndex("CREATE INDEX ON %s (pk2)"); |
| execute("INSERT INTO %s (pk1, pk2, c, s, v) VALUES (?, ?, ?, ?, ?)", 1, 1, 1, 9, 1); |
| execute("INSERT INTO %s (pk1, pk2, c, s, v) VALUES (?, ?, ?, ?, ?)", 1, 1, 2, 9, 2); |
| execute("INSERT INTO %s (pk1, pk2, s) VALUES (?, ?, ?)", 2, 1, 9); |
| execute("INSERT INTO %s (pk1, pk2, c, s, v) VALUES (?, ?, ?, ?, ?)", 3, 1, 1, 9, 1); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk2 = ?", 1), |
| row(2, 1, null, 9, null), |
| row(1, 1, 1, 9, 1), |
| row(1, 1, 2, 9, 2), |
| row(3, 1, 1, 9, 1)); |
| |
| execute("UPDATE %s SET s=?, v=? WHERE pk1=? AND pk2=? AND c=?", 9, 1, 1, 10, 2); |
| assertRows(execute("SELECT * FROM %s WHERE pk2 = ?", 10), row(1, 10, 2, 9, 1)); |
| |
| execute("UPDATE %s SET s=? WHERE pk1=? AND pk2=?", 9, 1, 20); |
| assertRows(execute("SELECT * FROM %s WHERE pk2 = ?", 20), row(1, 20, null, 9, null)); |
| } |
| |
| @Test |
| public void testIndexOnClusteringColumnInsertValueOver64k() throws Throwable |
| { |
| createTable("CREATE TABLE %s(a int, b int, c blob, PRIMARY KEY (a, b))"); |
| createIndex("CREATE INDEX ON %s(b)"); |
| succeedInsert("INSERT INTO %s (a, b, c) VALUES (0, 0, ?) IF NOT EXISTS", ByteBuffer.allocate(TOO_BIG)); |
| succeedInsert("INSERT INTO %s (a, b, c) VALUES (0, 0, ?)", ByteBuffer.allocate(TOO_BIG)); |
| succeedInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b, c) VALUES (0, 0, ?);\n" + |
| "APPLY BATCH", ByteBuffer.allocate(TOO_BIG)); |
| |
| // the indexed value passes validation, but the batch size will |
| // exceed the default failure threshold, so temporarily raise it |
| // (the non-conditional batch doesn't hit this because |
| // BatchStatement::executeInternal skips the size check but CAS |
| // path does not) |
| long batchSizeThreshold = DatabaseDescriptor.getBatchSizeFailThreshold(); |
| try |
| { |
| DatabaseDescriptor.setBatchSizeFailThresholdInKB( (TOO_BIG / 1024) * 2); |
| succeedInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b, c) VALUES (1, 1, ?) IF NOT EXISTS;\n" + |
| "APPLY BATCH", ByteBuffer.allocate(TOO_BIG)); |
| } |
| finally |
| { |
| DatabaseDescriptor.setBatchSizeFailThresholdInKB((int)(batchSizeThreshold / 1024)); |
| } |
| } |
| |
| @Test |
| public void testIndexOnFullCollectionEntryInsertCollectionValueOver64k() throws Throwable |
| { |
| createTable("CREATE TABLE %s(a int, b frozen<map<int, blob>>, PRIMARY KEY (a))"); |
| createIndex("CREATE INDEX ON %s(full(b))"); |
| Map<Integer, ByteBuffer> map = new HashMap<>(); |
| map.put(0, ByteBuffer.allocate(1024 * 65)); |
| failInsert("INSERT INTO %s (a, b) VALUES (0, ?)", map); |
| failInsert("INSERT INTO %s (a, b) VALUES (0, ?) IF NOT EXISTS", map); |
| failInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b) VALUES (0, ?);\n" + |
| "APPLY BATCH", map); |
| failInsert("BEGIN BATCH\n" + |
| "INSERT INTO %s (a, b) VALUES (0, ?) IF NOT EXISTS;\n" + |
| "APPLY BATCH", map); |
| } |
| |
| @Test |
| public void prepareStatementsWithLIKEClauses() throws Throwable |
| { |
| createTable("CREATE TABLE %s (a int, c1 text, c2 text, v1 text, v2 text, v3 int, PRIMARY KEY (a, c1, c2))"); |
| createIndex(String.format("CREATE CUSTOM INDEX c1_idx on %%s(c1) USING '%s' WITH OPTIONS = {'mode' : 'PREFIX'}", |
| SASIIndex.class.getName())); |
| createIndex(String.format("CREATE CUSTOM INDEX c2_idx on %%s(c2) USING '%s' WITH OPTIONS = {'mode' : 'CONTAINS'}", |
| SASIIndex.class.getName())); |
| createIndex(String.format("CREATE CUSTOM INDEX v1_idx on %%s(v1) USING '%s' WITH OPTIONS = {'mode' : 'PREFIX'}", |
| SASIIndex.class.getName())); |
| createIndex(String.format("CREATE CUSTOM INDEX v2_idx on %%s(v2) USING '%s' WITH OPTIONS = {'mode' : 'CONTAINS'}", |
| SASIIndex.class.getName())); |
| createIndex(String.format("CREATE CUSTOM INDEX v3_idx on %%s(v3) USING '%s'", SASIIndex.class.getName())); |
| |
| forcePreparedValues(); |
| // prefix mode indexes support prefix/contains/matches |
| assertInvalidMessage("c1 LIKE '%<term>' abc is only supported on properly indexed columns", |
| "SELECT * FROM %s WHERE c1 LIKE ?", |
| "%abc"); |
| assertInvalidMessage("c1 LIKE '%<term>%' abc is only supported on properly indexed columns", |
| "SELECT * FROM %s WHERE c1 LIKE ?", |
| "%abc%"); |
| execute("SELECT * FROM %s WHERE c1 LIKE ?", "abc%"); |
| execute("SELECT * FROM %s WHERE c1 LIKE ?", "abc"); |
| assertInvalidMessage("v1 LIKE '%<term>' abc is only supported on properly indexed columns", |
| "SELECT * FROM %s WHERE v1 LIKE ?", |
| "%abc"); |
| assertInvalidMessage("v1 LIKE '%<term>%' abc is only supported on properly indexed columns", |
| "SELECT * FROM %s WHERE v1 LIKE ?", |
| "%abc%"); |
| execute("SELECT * FROM %s WHERE v1 LIKE ?", "abc%"); |
| execute("SELECT * FROM %s WHERE v1 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"); |
| 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"); |
| |
| // LIKE is not supported on indexes of non-literal values |
| // this is rejected before binding, so the value isn't available in the error message |
| assertInvalidMessage("LIKE restriction is only supported on properly indexed columns. v3 LIKE ? is not valid", |
| "SELECT * FROM %s WHERE v3 LIKE ?", |
| "%abc"); |
| assertInvalidMessage("LIKE restriction is only supported on properly indexed columns. v3 LIKE ? is not valid", |
| "SELECT * FROM %s WHERE v3 LIKE ?", |
| "%abc%"); |
| assertInvalidMessage("LIKE restriction is only supported on properly indexed columns. v3 LIKE ? is not valid", |
| "SELECT * FROM %s WHERE v3 LIKE ?", |
| "%abc%"); |
| assertInvalidMessage("LIKE restriction is only supported on properly indexed columns. v3 LIKE ? is not valid", |
| "SELECT * FROM %s WHERE v3 LIKE ?", |
| "abc"); |
| } |
| |
| public void failInsert(String insertCQL, Object...args) throws Throwable |
| { |
| try |
| { |
| execute(insertCQL, args); |
| fail("Expected statement to fail validation"); |
| } |
| catch (Exception e) |
| { |
| // as expected |
| } |
| } |
| |
| public void succeedInsert(String insertCQL, Object...args) throws Throwable |
| { |
| execute(insertCQL, args); |
| flush(); |
| } |
| |
| /** |
| * Migrated from cql_tests.py:TestCQL.clustering_indexing_test() |
| */ |
| @Test |
| public void testIndexesOnClustering() throws Throwable |
| { |
| createTable("CREATE TABLE %s ( id1 int, id2 int, author text, time bigint, v1 text, v2 text, PRIMARY KEY ((id1, id2), author, time))"); |
| |
| createIndex("CREATE INDEX ON %s (time)"); |
| execute("CREATE INDEX ON %s (id2)"); |
| |
| execute("INSERT INTO %s (id1, id2, author, time, v1, v2) VALUES(0, 0, 'bob', 0, 'A', 'A')"); |
| execute("INSERT INTO %s (id1, id2, author, time, v1, v2) VALUES(0, 0, 'bob', 1, 'B', 'B')"); |
| execute("INSERT INTO %s (id1, id2, author, time, v1, v2) VALUES(0, 1, 'bob', 2, 'C', 'C')"); |
| execute("INSERT INTO %s (id1, id2, author, time, v1, v2) VALUES(0, 0, 'tom', 0, 'D', 'D')"); |
| execute("INSERT INTO %s (id1, id2, author, time, v1, v2) VALUES(0, 1, 'tom', 1, 'E', 'E')"); |
| |
| assertRows(execute("SELECT v1 FROM %s WHERE time = 1"), |
| row("B"), row("E")); |
| |
| assertRows(execute("SELECT v1 FROM %s WHERE id2 = 1"), |
| row("C"), row("E")); |
| |
| assertRows(execute("SELECT v1 FROM %s WHERE id1 = 0 AND id2 = 0 AND author = 'bob' AND time = 0"), |
| row("A")); |
| |
| // Test for CASSANDRA-8206 |
| execute("UPDATE %s SET v2 = null WHERE id1 = 0 AND id2 = 0 AND author = 'bob' AND time = 1"); |
| |
| assertRows(execute("SELECT v1 FROM %s WHERE id2 = 0"), |
| row("A"), row("B"), row("D")); |
| |
| assertRows(execute("SELECT v1 FROM %s WHERE time = 1"), |
| row("B"), row("E")); |
| } |
| |
| /** |
| * Migrated from cql_tests.py:TestCQL.invalid_clustering_indexing_test() |
| */ |
| @Test |
| public void testIndexesOnClusteringInvalid() throws Throwable |
| { |
| createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY ((a, b))) WITH COMPACT STORAGE"); |
| assertInvalid("CREATE INDEX ON %s (a)"); |
| assertInvalid("CREATE INDEX ON %s (b)"); |
| |
| createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b)) WITH COMPACT STORAGE"); |
| assertInvalid("CREATE INDEX ON %s (a)"); |
| assertInvalid("CREATE INDEX ON %s (b)"); |
| assertInvalid("CREATE INDEX ON %s (c)"); |
| } |
| |
| @Test |
| public void testMultipleIndexesOnOneColumn() throws Throwable |
| { |
| String indexClassName = StubIndex.class.getName(); |
| createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY ((a), b))"); |
| // uses different options otherwise the two indexes are considered duplicates |
| createIndex(String.format("CREATE CUSTOM INDEX c_idx_1 ON %%s(c) USING '%s' WITH OPTIONS = {'foo':'a'}", indexClassName)); |
| createIndex(String.format("CREATE CUSTOM INDEX c_idx_2 ON %%s(c) USING '%s' WITH OPTIONS = {'foo':'b'}", indexClassName)); |
| |
| ColumnFamilyStore cfs = getCurrentColumnFamilyStore(); |
| CFMetaData cfm = cfs.metadata; |
| StubIndex index1 = (StubIndex)cfs.indexManager.getIndex(cfm.getIndexes() |
| .get("c_idx_1") |
| .orElseThrow(throwAssert("index not found"))); |
| StubIndex index2 = (StubIndex)cfs.indexManager.getIndex(cfm.getIndexes() |
| .get("c_idx_2") |
| .orElseThrow(throwAssert("index not found"))); |
| Object[] row1a = row(0, 0, 0); |
| Object[] row1b = row(0, 0, 1); |
| Object[] row2 = row(2, 2, 2); |
| execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", row1a); |
| execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", row1b); |
| execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", row2); |
| |
| assertEquals(2, index1.rowsInserted.size()); |
| assertColumnValue(0, "c", index1.rowsInserted.get(0), cfm); |
| assertColumnValue(2, "c", index1.rowsInserted.get(1), cfm); |
| |
| assertEquals(2, index2.rowsInserted.size()); |
| assertColumnValue(0, "c", index2.rowsInserted.get(0), cfm); |
| assertColumnValue(2, "c", index2.rowsInserted.get(1), cfm); |
| |
| assertEquals(1, index1.rowsUpdated.size()); |
| assertColumnValue(0, "c", index1.rowsUpdated.get(0).left, cfm); |
| assertColumnValue(1, "c", index1.rowsUpdated.get(0).right, cfm); |
| |
| assertEquals(1, index2.rowsUpdated.size()); |
| assertColumnValue(0, "c", index2.rowsUpdated.get(0).left, cfm); |
| assertColumnValue(1, "c", index2.rowsUpdated.get(0).right, cfm); |
| } |
| |
| @Test |
| public void testDeletions() throws Throwable |
| { |
| // Test for bugs like CASSANDRA-10694. These may not be readily visible with the built-in secondary index |
| // implementation because of the stale entry handling. |
| |
| String indexClassName = StubIndex.class.getName(); |
| createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY ((a), b))"); |
| createIndex(String.format("CREATE CUSTOM INDEX c_idx ON %%s(c) USING '%s'", indexClassName)); |
| |
| ColumnFamilyStore cfs = getCurrentColumnFamilyStore(); |
| CFMetaData cfm = cfs.metadata; |
| StubIndex index1 = (StubIndex) cfs.indexManager.getIndex(cfm.getIndexes() |
| .get("c_idx") |
| .orElseThrow(throwAssert("index not found"))); |
| |
| execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?) USING TIMESTAMP 1", 0, 0, 0); |
| assertEquals(1, index1.rowsInserted.size()); |
| |
| execute("DELETE FROM %s USING TIMESTAMP 2 WHERE a = ? AND b = ?", 0, 0); |
| assertEquals(1, index1.rowsUpdated.size()); |
| Pair<Row, Row> update = index1.rowsUpdated.get(0); |
| Row existingRow = update.left; |
| Row newRow = update.right; |
| |
| // check the existing row from the update call |
| assertTrue(existingRow.deletion().isLive()); |
| assertEquals(DeletionTime.LIVE, existingRow.deletion().time()); |
| assertEquals(1L, existingRow.primaryKeyLivenessInfo().timestamp()); |
| |
| // check the new row from the update call |
| assertFalse(newRow.deletion().isLive()); |
| assertEquals(2L, newRow.deletion().time().markedForDeleteAt()); |
| assertFalse(newRow.cells().iterator().hasNext()); |
| |
| // delete the same row again |
| execute("DELETE FROM %s USING TIMESTAMP 3 WHERE a = ? AND b = ?", 0, 0); |
| assertEquals(2, index1.rowsUpdated.size()); |
| update = index1.rowsUpdated.get(1); |
| existingRow = update.left; |
| newRow = update.right; |
| |
| // check the new row from the update call |
| assertFalse(existingRow.deletion().isLive()); |
| assertEquals(2L, existingRow.deletion().time().markedForDeleteAt()); |
| assertFalse(existingRow.cells().iterator().hasNext()); |
| |
| // check the new row from the update call |
| assertFalse(newRow.deletion().isLive()); |
| assertEquals(3L, newRow.deletion().time().markedForDeleteAt()); |
| assertFalse(newRow.cells().iterator().hasNext()); |
| } |
| |
| @Test |
| public void testUpdatesToMemtableData() throws Throwable |
| { |
| // verify the contract specified by Index.Indexer::updateRow(oldRowData, newRowData), |
| // when a row in the memtable is updated, the indexer should be informed of: |
| // * new columns |
| // * removed columns |
| // * columns whose value, timestamp or ttl have been modified. |
| // Any columns which are unchanged by the update are not passed to the Indexer |
| // Note that for simplicity this test resets the index between each scenario |
| createTable("CREATE TABLE %s (k int, c int, v1 int, v2 int, PRIMARY KEY (k,c))"); |
| createIndex(String.format("CREATE CUSTOM INDEX test_index ON %%s() USING '%s'", StubIndex.class.getName())); |
| execute("INSERT INTO %s (k, c, v1, v2) VALUES (0, 0, 0, 0) USING TIMESTAMP 0"); |
| |
| ColumnDefinition v1 = getCurrentColumnFamilyStore().metadata.getColumnDefinition(new ColumnIdentifier("v1", true)); |
| ColumnDefinition v2 = getCurrentColumnFamilyStore().metadata.getColumnDefinition(new ColumnIdentifier("v2", true)); |
| |
| StubIndex index = (StubIndex)getCurrentColumnFamilyStore().indexManager.getIndexByName("test_index"); |
| assertEquals(1, index.rowsInserted.size()); |
| |
| // Overwrite a single value, leaving the other untouched |
| execute("UPDATE %s USING TIMESTAMP 1 SET v1=1 WHERE k=0 AND c=0"); |
| assertEquals(1, index.rowsUpdated.size()); |
| Row oldRow = index.rowsUpdated.get(0).left; |
| assertEquals(1, oldRow.columnCount()); |
| validateCell(oldRow.getCell(v1), v1, ByteBufferUtil.bytes(0), 0); |
| Row newRow = index.rowsUpdated.get(0).right; |
| assertEquals(1, newRow.columnCount()); |
| validateCell(newRow.getCell(v1), v1, ByteBufferUtil.bytes(1), 1); |
| index.reset(); |
| |
| // Overwrite both values |
| execute("UPDATE %s USING TIMESTAMP 2 SET v1=2, v2=2 WHERE k=0 AND c=0"); |
| assertEquals(1, index.rowsUpdated.size()); |
| oldRow = index.rowsUpdated.get(0).left; |
| assertEquals(2, oldRow.columnCount()); |
| validateCell(oldRow.getCell(v1), v1, ByteBufferUtil.bytes(1), 1); |
| validateCell(oldRow.getCell(v2), v2, ByteBufferUtil.bytes(0), 0); |
| newRow = index.rowsUpdated.get(0).right; |
| assertEquals(2, newRow.columnCount()); |
| validateCell(newRow.getCell(v1), v1, ByteBufferUtil.bytes(2), 2); |
| validateCell(newRow.getCell(v2), v2, ByteBufferUtil.bytes(2), 2); |
| index.reset(); |
| |
| // Delete one value |
| execute("DELETE v1 FROM %s USING TIMESTAMP 3 WHERE k=0 AND c=0"); |
| assertEquals(1, index.rowsUpdated.size()); |
| oldRow = index.rowsUpdated.get(0).left; |
| assertEquals(1, oldRow.columnCount()); |
| validateCell(oldRow.getCell(v1), v1, ByteBufferUtil.bytes(2), 2); |
| newRow = index.rowsUpdated.get(0).right; |
| assertEquals(1, newRow.columnCount()); |
| Cell newCell = newRow.getCell(v1); |
| assertTrue(newCell.isTombstone()); |
| assertEquals(3, newCell.timestamp()); |
| index.reset(); |
| |
| // Modify the liveness of the primary key, the delta rows should contain |
| // no cell data as only the pk was altered, but it should illustrate the |
| // change to the liveness info |
| execute("INSERT INTO %s(k, c) VALUES (0, 0) USING TIMESTAMP 4"); |
| assertEquals(1, index.rowsUpdated.size()); |
| oldRow = index.rowsUpdated.get(0).left; |
| assertEquals(0, oldRow.columnCount()); |
| assertEquals(0, oldRow.primaryKeyLivenessInfo().timestamp()); |
| newRow = index.rowsUpdated.get(0).right; |
| assertEquals(0, newRow.columnCount()); |
| assertEquals(4, newRow.primaryKeyLivenessInfo().timestamp()); |
| } |
| |
| @Test |
| public void testIndexQueriesWithIndexNotReady() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk int, ck int, value int, PRIMARY KEY (pk, ck))"); |
| |
| for (int i = 0; i < 10; i++) |
| for (int j = 0; j < 10; j++) |
| execute("INSERT INTO %s (pk, ck, value) VALUES (?, ?, ?)", i, j, i + j); |
| |
| createIndex("CREATE CUSTOM INDEX testIndex ON %s (value) USING '" + IndexBlockingOnInitialization.class.getName() + "'"); |
| try |
| { |
| execute("SELECT value FROM %s WHERE value = 2"); |
| fail(); |
| } |
| catch (IndexNotAvailableException e) |
| { |
| assertTrue(true); |
| } |
| finally |
| { |
| execute("DROP index " + KEYSPACE + ".testIndex"); |
| } |
| } |
| |
| @Test |
| public void droppingIndexInvalidatesPreparedStatements() throws Throwable |
| { |
| createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY ((a), b))"); |
| createIndex("CREATE INDEX c_idx ON %s(c)"); |
| MD5Digest cqlId = prepareStatement("SELECT * FROM %s.%s WHERE c=?", false).statementId; |
| Integer thriftId = prepareStatement("SELECT * FROM %s.%s WHERE c=?", true).toThriftPreparedResult().getItemId(); |
| |
| assertNotNull(QueryProcessor.instance.getPrepared(cqlId)); |
| assertNotNull(QueryProcessor.instance.getPreparedForThrift(thriftId)); |
| |
| dropIndex("DROP INDEX %s.c_idx"); |
| |
| assertNull(QueryProcessor.instance.getPrepared(cqlId)); |
| assertNull(QueryProcessor.instance.getPreparedForThrift(thriftId)); |
| } |
| |
| // See CASSANDRA-11021 |
| @Test |
| public void testIndexesOnNonStaticColumnsWhereSchemaIncludesStaticColumns() throws Throwable |
| { |
| createTable("CREATE TABLE %s (a int, b int, c int static, d int, PRIMARY KEY (a, b))"); |
| createIndex("CREATE INDEX b_idx on %s(b)"); |
| createIndex("CREATE INDEX d_idx on %s(d)"); |
| |
| execute("INSERT INTO %s (a, b, c ,d) VALUES (0, 0, 0, 0)"); |
| execute("INSERT INTO %s (a, b, c, d) VALUES (1, 1, 1, 1)"); |
| assertRows(execute("SELECT * FROM %s WHERE b = 0"), row(0, 0, 0, 0)); |
| assertRows(execute("SELECT * FROM %s WHERE d = 1"), row(1, 1, 1, 1)); |
| |
| execute("UPDATE %s SET c = 2 WHERE a = 0"); |
| execute("UPDATE %s SET c = 3, d = 4 WHERE a = 1 AND b = 1"); |
| assertRows(execute("SELECT * FROM %s WHERE b = 0"), row(0, 0, 2, 0)); |
| assertRows(execute("SELECT * FROM %s WHERE d = 4"), row(1, 1, 3, 4)); |
| |
| execute("DELETE FROM %s WHERE a = 0"); |
| execute("DELETE FROM %s WHERE a = 1 AND b = 1"); |
| assertEmpty(execute("SELECT * FROM %s WHERE b = 0")); |
| assertEmpty(execute("SELECT * FROM %s WHERE d = 3")); |
| } |
| |
| @Test |
| public void testWithEmptyRestrictionValueAndSecondaryIndex() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk blob, c blob, v blob, PRIMARY KEY ((pk), c))"); |
| createIndex("CREATE INDEX on %s(c)"); |
| createIndex("CREATE INDEX on %s(v)"); |
| |
| execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", bytes("foo123"), bytes("1"), bytes("1")); |
| execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", bytes("foo123"), bytes("2"), bytes("1")); |
| |
| beforeAndAfterFlush(() -> { |
| // Test clustering columns restrictions |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c = textAsBlob('');")); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) = (textAsBlob(''));")); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c IN (textAsBlob(''), textAsBlob('1'));"), |
| row(bytes("foo123"), bytes("1"), bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) IN ((textAsBlob('')), (textAsBlob('1')));"), |
| row(bytes("foo123"), bytes("1"), bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), |
| row(bytes("foo123"), bytes("1"), bytes("1")), |
| row(bytes("foo123"), bytes("2"), bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), |
| row(bytes("foo123"), bytes("1"), bytes("1")), |
| row(bytes("foo123"), bytes("2"), bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) >= (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;"), |
| row(bytes("foo123"), bytes("1"), bytes("1")), |
| row(bytes("foo123"), bytes("2"), bytes("1"))); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) <= (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;")); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) < (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;")); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('') AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) > (textAsBlob('')) AND (c) < (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;")); |
| }); |
| |
| execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", |
| bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")); |
| |
| beforeAndAfterFlush(() -> { |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c = textAsBlob('');"), |
| row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) = (textAsBlob(''));"), |
| row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c IN (textAsBlob(''), textAsBlob('1'));"), |
| row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")), |
| row(bytes("foo123"), bytes("1"), bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) IN ((textAsBlob('')), (textAsBlob('1')));"), |
| row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")), |
| row(bytes("foo123"), bytes("1"), bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), |
| row(bytes("foo123"), bytes("1"), bytes("1")), |
| row(bytes("foo123"), bytes("2"), bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), |
| row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")), |
| row(bytes("foo123"), bytes("1"), bytes("1")), |
| row(bytes("foo123"), bytes("2"), bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) >= (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;"), |
| row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")), |
| row(bytes("foo123"), bytes("1"), bytes("1")), |
| row(bytes("foo123"), bytes("2"), bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), |
| row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) <= (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;"), |
| row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"))); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) < (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;")); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('') AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) >= (textAsBlob('')) AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); |
| |
| // Test restrictions on non-primary key value |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('');")); |
| }); |
| |
| execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", |
| bytes("foo123"), bytes("3"), EMPTY_BYTE_BUFFER); |
| |
| beforeAndAfterFlush(() -> { |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('');"), |
| row(bytes("foo123"), bytes("3"), EMPTY_BYTE_BUFFER)); |
| }); |
| } |
| |
| @Test |
| public void testEmptyRestrictionValueWithSecondaryIndexAndCompactTables() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk blob, c blob, v blob, PRIMARY KEY ((pk), c)) WITH COMPACT STORAGE"); |
| assertInvalidMessage("Secondary indexes are not supported on COMPACT STORAGE tables that have clustering columns", |
| "CREATE INDEX on %s(c)"); |
| |
| createTable("CREATE TABLE %s (pk blob PRIMARY KEY, v blob) WITH COMPACT STORAGE"); |
| createIndex("CREATE INDEX on %s(v)"); |
| |
| execute("INSERT INTO %s (pk, v) VALUES (?, ?)", bytes("foo123"), bytes("1")); |
| |
| // Test restrictions on non-primary key value |
| assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('');")); |
| |
| execute("INSERT INTO %s (pk, v) VALUES (?, ?)", bytes("foo124"), EMPTY_BYTE_BUFFER); |
| |
| assertRows(execute("SELECT * FROM %s WHERE v = textAsBlob('');"), |
| row(bytes("foo124"), EMPTY_BYTE_BUFFER)); |
| } |
| |
| @Test |
| public void testPartitionKeyWithIndex() throws Throwable |
| { |
| createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY ((a, b)))"); |
| createIndex("CREATE INDEX ON %s (a);"); |
| createIndex("CREATE INDEX ON %s (b);"); |
| |
| execute("INSERT INTO %s (a, b, c) VALUES (1,2,3)"); |
| execute("INSERT INTO %s (a, b, c) VALUES (2,3,4)"); |
| execute("INSERT INTO %s (a, b, c) VALUES (5,6,7)"); |
| |
| beforeAndAfterFlush(() -> { |
| assertRows(execute("SELECT * FROM %s WHERE a = 1"), |
| row(1, 2, 3)); |
| assertRows(execute("SELECT * FROM %s WHERE b = 3"), |
| row(2, 3, 4)); |
| |
| }); |
| } |
| |
| @Test |
| public void testAllowFilteringOnPartitionKeyWithSecondaryIndex() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk1 int, pk2 int, c1 int, c2 int, v int, " + |
| "PRIMARY KEY ((pk1, pk2), c1, c2))"); |
| createIndex("CREATE INDEX v_idx_1 ON %s (v);"); |
| |
| for (int i = 1; i <= 5; i++) |
| { |
| for (int j = 1; j <= 2; j++) |
| { |
| execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", j, 1, 1, 1, i); |
| execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", j, 1, 1, i, i); |
| execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", j, 1, i, i, i); |
| execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", j, i, i, i, i); |
| } |
| } |
| |
| beforeAndAfterFlush(() -> { |
| assertEmpty(execute("SELECT * FROM %s WHERE pk1 = 1 AND c1 > 0 AND c1 < 5 AND c2 = 1 AND v = 3 ALLOW FILTERING;")); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk1 = 1 AND c1 > 0 AND c1 < 5 AND c2 = 3 AND v = 3 ALLOW FILTERING;"), |
| row(1, 3, 3, 3, 3), |
| row(1, 1, 1, 3, 3), |
| row(1, 1, 3, 3, 3)); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE pk1 = 1 AND c2 > 1 AND c2 < 5 AND v = 1 ALLOW FILTERING;")); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk1 = 1 AND c1 > 1 AND c2 > 2 AND v = 3 ALLOW FILTERING;"), |
| row(1, 3, 3, 3, 3), |
| row(1, 1, 3, 3, 3)); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk1 = 1 AND pk2 > 1 AND c2 > 2 AND v = 3 ALLOW FILTERING;"), |
| row(1, 3, 3, 3, 3)); |
| |
| assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE pk2 > 1 AND c1 IN(0,1,2) AND v <= 3 ALLOW FILTERING;"), |
| row(1, 2, 2, 2, 2), |
| row(2, 2, 2, 2, 2)); |
| |
| assertRows(execute("SELECT * FROM %s WHERE pk1 >= 2 AND pk2 <=3 AND c1 IN(0,1,2) AND c2 IN(0,1,2) AND v < 3 ALLOW FILTERING;"), |
| row(2, 2, 2, 2, 2), |
| row(2, 1, 1, 2, 2), |
| row(2, 1, 2, 2, 2)); |
| |
| assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, |
| "SELECT * FROM %s WHERE pk1 >= 1 AND pk2 <=3 AND c1 IN(0,1,2) AND c2 IN(0,1,2) AND v = 3"); |
| }); |
| } |
| |
| @Test |
| public void testAllowFilteringOnPartitionKeyWithIndexForContains() throws Throwable |
| { |
| createTable("CREATE TABLE %s (k1 int, k2 int, v set<int>, PRIMARY KEY ((k1, k2)))"); |
| createIndex("CREATE INDEX ON %s(k2)"); |
| |
| execute("INSERT INTO %s (k1, k2, v) VALUES (?, ?, ?)", 0, 0, set(1, 2, 3)); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (?, ?, ?)", 0, 1, set(2, 3, 4)); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (?, ?, ?)", 1, 0, set(3, 4, 5)); |
| execute("INSERT INTO %s (k1, k2, v) VALUES (?, ?, ?)", 1, 1, set(4, 5, 6)); |
| |
| beforeAndAfterFlush(() -> { |
| assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, |
| "SELECT * FROM %s WHERE k2 > ?", 0); |
| |
| assertRows(execute("SELECT * FROM %s WHERE k2 > ? ALLOW FILTERING", 0), |
| row(0, 1, set(2, 3, 4)), |
| row(1, 1, set(4, 5, 6))); |
| |
| assertRows(execute("SELECT * FROM %s WHERE k2 >= ? AND v CONTAINS ? ALLOW FILTERING", 1, 6), |
| row(1, 1, set(4, 5, 6))); |
| |
| assertEmpty(execute("SELECT * FROM %s WHERE k2 < ? AND v CONTAINS ? ALLOW FILTERING", 0, 7)); |
| }); |
| } |
| |
| @Test |
| public void testIndexOnStaticColumnWithPartitionWithoutRows() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk int, c int, s int static, v int, PRIMARY KEY(pk, c))"); |
| createIndex("CREATE INDEX ON %s (s)"); |
| |
| execute("INSERT INTO %s (pk, c, s, v) VALUES (?, ?, ?, ?)", 1, 1, 9, 1); |
| execute("INSERT INTO %s (pk, c, s, v) VALUES (?, ?, ?, ?)", 1, 2, 9, 2); |
| execute("INSERT INTO %s (pk, s) VALUES (?, ?)", 2, 9); |
| execute("INSERT INTO %s (pk, c, s, v) VALUES (?, ?, ?, ?)", 3, 1, 9, 1); |
| flush(); |
| |
| assertRows(execute("SELECT * FROM %s WHERE s = ?", 9), |
| row(1, 1, 9, 1), |
| row(1, 2, 9, 2), |
| row(2, null, 9, null), |
| row(3, 1, 9, 1)); |
| |
| execute("DELETE FROM %s WHERE pk = ?", 3); |
| |
| assertRows(execute("SELECT * FROM %s WHERE s = ?", 9), |
| row(1, 1, 9, 1), |
| row(1, 2, 9, 2), |
| row(2, null, 9, null)); |
| } |
| |
| @Test |
| public void testIndexOnRegularColumnWithPartitionWithoutRows() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk int, c int, s int static, v int, PRIMARY KEY(pk, c))"); |
| createIndex("CREATE INDEX ON %s (v)"); |
| |
| execute("INSERT INTO %s (pk, c, s, v) VALUES (?, ?, ?, ?)", 1, 1, 9, 1); |
| execute("INSERT INTO %s (pk, c, s, v) VALUES (?, ?, ?, ?)", 1, 2, 9, 2); |
| execute("INSERT INTO %s (pk, s) VALUES (?, ?)", 2, 9); |
| execute("INSERT INTO %s (pk, c, s, v) VALUES (?, ?, ?, ?)", 3, 1, 9, 1); |
| flush(); |
| |
| execute("DELETE FROM %s WHERE pk = ? and c = ?", 3, 1); |
| |
| assertRows(execute("SELECT * FROM %s WHERE v = ?", 1), |
| row(1, 1, 9, 1)); |
| } |
| |
| @Test |
| public void testIndexOnDurationColumn() throws Throwable |
| { |
| createTable("CREATE TABLE %s (k int PRIMARY KEY, d duration)"); |
| assertInvalidMessage("Secondary indexes are not supported on duration columns", |
| "CREATE INDEX ON %s (d)"); |
| |
| createTable("CREATE TABLE %s (k int PRIMARY KEY, l list<duration>)"); |
| assertInvalidMessage("Secondary indexes are not supported on collections containing durations", |
| "CREATE INDEX ON %s (l)"); |
| |
| createTable("CREATE TABLE %s (k int PRIMARY KEY, m map<int, duration>)"); |
| assertInvalidMessage("Secondary indexes are not supported on collections containing durations", |
| "CREATE INDEX ON %s (m)"); |
| |
| createTable("CREATE TABLE %s (k int PRIMARY KEY, t tuple<int, duration>)"); |
| assertInvalidMessage("Secondary indexes are not supported on tuples containing durations", |
| "CREATE INDEX ON %s (t)"); |
| |
| String udt = createType("CREATE TYPE %s (i int, d duration)"); |
| |
| createTable("CREATE TABLE %s (k int PRIMARY KEY, t " + udt + ")"); |
| assertInvalidMessage("Secondary indexes are not supported on UDTs containing durations", |
| "CREATE INDEX ON %s (t)"); |
| } |
| |
| @Test |
| public void testIndexOnFrozenUDT() throws Throwable |
| { |
| String type = createType("CREATE TYPE %s (a int)"); |
| String tableName = createTable("CREATE TABLE %s (k int PRIMARY KEY, v frozen<" + type + ">)"); |
| |
| Object udt1 = userType("a", 1); |
| Object udt2 = userType("a", 2); |
| |
| execute("INSERT INTO %s (k, v) VALUES (?, ?)", 0, udt1); |
| execute("CREATE INDEX idx ON %s (v)"); |
| execute("INSERT INTO %s (k, v) VALUES (?, ?)", 1, udt2); |
| execute("INSERT INTO %s (k, v) VALUES (?, ?)", 1, udt1); |
| assertTrue(waitForIndex(keyspace(), tableName, "idx")); |
| |
| assertRows(execute("SELECT * FROM %s WHERE v = ?", udt1), row(1, udt1), row(0, udt1)); |
| assertEmpty(execute("SELECT * FROM %s WHERE v = ?", udt2)); |
| |
| execute("DELETE FROM %s WHERE k = 0"); |
| assertRows(execute("SELECT * FROM %s WHERE v = ?", udt1), row(1, udt1)); |
| |
| dropIndex("DROP INDEX %s.idx"); |
| assertInvalidMessage("Index 'idx' could not be found", "DROP INDEX " + KEYSPACE + ".idx"); |
| assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, |
| "SELECT * FROM %s WHERE v = ?", udt1); |
| } |
| |
| @Test |
| public void testIndexOnFrozenCollectionOfUDT() throws Throwable |
| { |
| String type = createType("CREATE TYPE %s (a int)"); |
| String tableName = createTable("CREATE TABLE %s (k int PRIMARY KEY, v frozen<set<frozen<" + type + ">>>)"); |
| |
| Object udt1 = userType("a", 1); |
| Object udt2 = userType("a", 2); |
| |
| execute("INSERT INTO %s (k, v) VALUES (?, ?)", 1, set(udt1, udt2)); |
| assertInvalidMessage("Frozen collections only support full()", "CREATE INDEX idx ON %s (keys(v))"); |
| assertInvalidMessage("Frozen collections only support full()", "CREATE INDEX idx ON %s (values(v))"); |
| execute("CREATE INDEX idx ON %s (full(v))"); |
| |
| execute("INSERT INTO %s (k, v) VALUES (?, ?)", 2, set(udt2)); |
| assertTrue(waitForIndex(keyspace(), tableName, "idx")); |
| |
| assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, |
| "SELECT * FROM %s WHERE v CONTAINS ?", udt1); |
| |
| assertRows(execute("SELECT * FROM %s WHERE v = ?", set(udt1, udt2)), row(1, set(udt1, udt2))); |
| assertRows(execute("SELECT * FROM %s WHERE v = ?", set(udt2)), row(2, set(udt2))); |
| |
| execute("DELETE FROM %s WHERE k = 2"); |
| assertEmpty(execute("SELECT * FROM %s WHERE v = ?", set(udt2))); |
| |
| dropIndex("DROP INDEX %s.idx"); |
| assertInvalidMessage("Index 'idx' could not be found", "DROP INDEX " + KEYSPACE + ".idx"); |
| assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, |
| "SELECT * FROM %s WHERE v CONTAINS ?", udt1); |
| } |
| |
| @Test |
| public void testIndexOnNonFrozenCollectionOfFrozenUDT() throws Throwable |
| { |
| String type = createType("CREATE TYPE %s (a int)"); |
| String tableName = createTable("CREATE TABLE %s (k int PRIMARY KEY, v set<frozen<" + type + ">>)"); |
| |
| Object udt1 = userType("a", 1); |
| Object udt2 = userType("a", 2); |
| |
| execute("INSERT INTO %s (k, v) VALUES (?, ?)", 1, set(udt1)); |
| assertInvalidMessage("Cannot create index on keys of column v with non-map type", |
| "CREATE INDEX idx ON %s (keys(v))"); |
| assertInvalidMessage("full() indexes can only be created on frozen collections", |
| "CREATE INDEX idx ON %s (full(v))"); |
| execute("CREATE INDEX idx ON %s (values(v))"); |
| |
| execute("INSERT INTO %s (k, v) VALUES (?, ?)", 2, set(udt2)); |
| execute("UPDATE %s SET v = v + ? WHERE k = ?", set(udt2), 1); |
| assertTrue(waitForIndex(keyspace(), tableName, "idx")); |
| |
| assertRows(execute("SELECT * FROM %s WHERE v CONTAINS ?", udt1), row(1, set(udt1, udt2))); |
| assertRows(execute("SELECT * FROM %s WHERE v CONTAINS ?", udt2), row(1, set(udt1, udt2)), row(2, set(udt2))); |
| |
| execute("DELETE FROM %s WHERE k = 1"); |
| assertEmpty(execute("SELECT * FROM %s WHERE v CONTAINS ?", udt1)); |
| assertRows(execute("SELECT * FROM %s WHERE v CONTAINS ?", udt2), row(2, set(udt2))); |
| |
| dropIndex("DROP INDEX %s.idx"); |
| assertInvalidMessage("Index 'idx' could not be found", "DROP INDEX " + KEYSPACE + ".idx"); |
| assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, |
| "SELECT * FROM %s WHERE v CONTAINS ?", udt1); |
| } |
| |
| @Test |
| public void testIndexOnNonFrozenUDT() throws Throwable |
| { |
| String type = createType("CREATE TYPE %s (a int)"); |
| createTable("CREATE TABLE %s (k int PRIMARY KEY, v " + type + ")"); |
| assertInvalidMessage("Secondary indexes are not supported on non-frozen UDTs", "CREATE INDEX ON %s (v)"); |
| assertInvalidMessage("Non-collection columns support only simple indexes", "CREATE INDEX ON %s (keys(v))"); |
| assertInvalidMessage("Non-collection columns support only simple indexes", "CREATE INDEX ON %s (values(v))"); |
| assertInvalidMessage("full() indexes can only be created on frozen collections", "CREATE INDEX ON %s (full(v))"); |
| } |
| |
| @Test |
| public void testIndexOnPartitionKeyInsertExpiringColumn() throws Throwable |
| { |
| createTable("CREATE TABLE %s (k1 int, k2 int, a int, b int, PRIMARY KEY ((k1, k2)))"); |
| createIndex("CREATE INDEX on %s(k1)"); |
| execute("INSERT INTO %s (k1, k2, a, b) VALUES (1, 2, 3, 4)"); |
| assertRows(execute("SELECT * FROM %s WHERE k1 = 1"), row(1, 2, 3, 4)); |
| execute("UPDATE %s USING TTL 1 SET b = 10 WHERE k1 = 1 AND k2 = 2"); |
| Thread.sleep(1000); |
| assertRows(execute("SELECT * FROM %s WHERE k1 = 1"), row(1, 2, 3, null)); |
| } |
| |
| @Test |
| public void testIndexOnClusteringKeyInsertExpiringColumn() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk int, ck int, a int, b int, PRIMARY KEY (pk, ck))"); |
| createIndex("CREATE INDEX on %s(ck)"); |
| execute("INSERT INTO %s (pk, ck, a, b) VALUES (1, 2, 3, 4)"); |
| assertRows(execute("SELECT * FROM %s WHERE ck = 2"), row(1, 2, 3, 4)); |
| execute("UPDATE %s USING TTL 1 SET b = 10 WHERE pk = 1 AND ck = 2"); |
| Thread.sleep(1000); |
| assertRows(execute("SELECT * FROM %s WHERE ck = 2"), row(1, 2, 3, null)); |
| } |
| |
| @Test |
| public void testIndexOnRegularColumnInsertExpiringColumn() throws Throwable |
| { |
| createTable("CREATE TABLE %s (pk int, ck int, a int, b int, PRIMARY KEY (pk, ck))"); |
| createIndex("CREATE INDEX on %s(a)"); |
| execute("INSERT INTO %s (pk, ck, a, b) VALUES (1, 2, 3, 4)"); |
| assertRows(execute("SELECT * FROM %s WHERE a = 3"), row(1, 2, 3, 4)); |
| |
| execute("UPDATE %s USING TTL 1 SET b = 10 WHERE pk = 1 AND ck = 2"); |
| Thread.sleep(1000); |
| assertRows(execute("SELECT * FROM %s WHERE a = 3"), row(1, 2, 3, null)); |
| |
| execute("UPDATE %s USING TTL 1 SET a = 5 WHERE pk = 1 AND ck = 2"); |
| Thread.sleep(1000); |
| assertEmpty(execute("SELECT * FROM %s WHERE a = 3")); |
| assertEmpty(execute("SELECT * FROM %s WHERE a = 5")); |
| } |
| |
| @Test |
| public void testIndicesOnCompactTable() throws Throwable |
| { |
| assertInvalidMessage("COMPACT STORAGE with composite PRIMARY KEY allows no more than one column not part of the PRIMARY KEY (got: v1, v2)", |
| "CREATE TABLE test (pk int, c int, v1 int, v2 int, PRIMARY KEY(pk, c)) WITH COMPACT STORAGE"); |
| |
| createTable("CREATE TABLE %s (pk int, c int, v int, PRIMARY KEY(pk, c)) WITH COMPACT STORAGE"); |
| assertInvalidMessage("Secondary indexes are not supported on COMPACT STORAGE tables that have clustering columns", |
| "CREATE INDEX ON %s(v)"); |
| |
| createTable("CREATE TABLE %s (pk int PRIMARY KEY, v int) WITH COMPACT STORAGE"); |
| createIndex("CREATE INDEX ON %s(v)"); |
| |
| execute("INSERT INTO %s (pk, v) VALUES (?, ?)", 1, 1); |
| execute("INSERT INTO %s (pk, v) VALUES (?, ?)", 2, 1); |
| execute("INSERT INTO %s (pk, v) VALUES (?, ?)", 3, 3); |
| |
| assertRows(execute("SELECT pk, v FROM %s WHERE v = 1"), |
| row(1, 1), |
| row(2, 1)); |
| |
| assertRows(execute("SELECT pk, v FROM %s WHERE v = 3"), |
| row(3, 3)); |
| |
| assertEmpty(execute("SELECT pk, v FROM %s WHERE v = 5")); |
| |
| createTable("CREATE TABLE %s (pk int PRIMARY KEY, v1 int, v2 int) WITH COMPACT STORAGE"); |
| createIndex("CREATE INDEX ON %s(v1)"); |
| |
| execute("INSERT INTO %s (pk, v1, v2) VALUES (?, ?, ?)", 1, 1, 1); |
| execute("INSERT INTO %s (pk, v1, v2) VALUES (?, ?, ?)", 2, 1, 2); |
| execute("INSERT INTO %s (pk, v1, v2) VALUES (?, ?, ?)", 3, 3, 3); |
| |
| assertRows(execute("SELECT pk, v2 FROM %s WHERE v1 = 1"), |
| row(1, 1), |
| row(2, 2)); |
| |
| assertRows(execute("SELECT pk, v2 FROM %s WHERE v1 = 3"), |
| row(3, 3)); |
| |
| assertEmpty(execute("SELECT pk, v2 FROM %s WHERE v1 = 5")); |
| } |
| |
| private ResultMessage.Prepared prepareStatement(String cql, boolean forThrift) |
| { |
| return QueryProcessor.prepare(String.format(cql, KEYSPACE, currentTable()), |
| ClientState.forInternalCalls(), |
| forThrift); |
| } |
| |
| private void validateCell(Cell cell, ColumnDefinition def, ByteBuffer val, long timestamp) |
| { |
| assertNotNull(cell); |
| assertEquals(0, def.type.compare(cell.value(), val)); |
| assertEquals(timestamp, cell.timestamp()); |
| } |
| |
| private static void assertColumnValue(int expected, String name, Row row, CFMetaData cfm) |
| { |
| ColumnDefinition col = cfm.getColumnDefinition(new ColumnIdentifier(name, true)); |
| AbstractType<?> type = col.type; |
| assertEquals(expected, type.compose(row.getCell(col).value())); |
| } |
| |
| /** |
| * <code>CassandraIndex</code> that blocks during the initialization. |
| */ |
| public static class IndexBlockingOnInitialization extends CustomCassandraIndex |
| { |
| private final CountDownLatch latch = new CountDownLatch(1); |
| |
| public IndexBlockingOnInitialization(ColumnFamilyStore baseCfs, IndexMetadata indexDef) |
| { |
| super(baseCfs, indexDef); |
| } |
| |
| @Override |
| public Callable<?> getInitializationTask() |
| { |
| return () -> { |
| latch.await(); |
| return null; |
| }; |
| } |
| |
| @Override |
| public Callable<?> getInvalidateTask() |
| { |
| latch.countDown(); |
| return super.getInvalidateTask(); |
| } |
| } |
| } |