| /* |
| * 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.distributed.test; |
| |
| import java.io.IOException; |
| import java.time.Instant; |
| import java.util.concurrent.TimeUnit; |
| |
| import com.google.common.util.concurrent.Uninterruptibles; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| |
| import org.apache.cassandra.db.ColumnFamilyStore; |
| import org.apache.cassandra.db.compaction.CompactionManager; |
| import org.apache.cassandra.distributed.Cluster; |
| import org.apache.cassandra.distributed.api.IIsolatedExecutor; |
| |
| import static java.time.temporal.ChronoUnit.MINUTES; |
| import static org.apache.cassandra.distributed.api.ConsistencyLevel.ONE; |
| import static org.apache.cassandra.distributed.api.Feature.NETWORK; |
| import static org.junit.Assert.assertEquals; |
| |
| /** |
| * Tests CASSANDRA-18105. |
| * <p> |
| * Truncation will insert an entry to system.local into truncated_at map for given table. Because id of an index |
| * is same as id of the base table, when dropping an index, it in fact drops a table with same id as the base table |
| * which will remove an entry for truncated_at column. Hence, after restart of the node, querying the base table |
| * will resurrect the data because commitlog was fully replayed, not taking into consideration (now removed) entry |
| * in truncated_at column. |
| * <p> |
| * The fix consists of not removing an entry in truncated_at column in system.local |
| * if the table being removed is an index. |
| */ |
| public class IndexDroppingTest extends TestBaseImpl |
| { |
| |
| private static Cluster CLUSTER; |
| |
| @BeforeClass |
| public static void init() throws IOException |
| { |
| CLUSTER = Cluster.build(1).withConfig(conf -> conf.with(NETWORK).set("materialized_views_enabled", "true")).start(); |
| CLUSTER.get(1).runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> CompactionManager.instance.disableAutoCompaction()); |
| } |
| |
| @AfterClass |
| public static void shutdown() |
| { |
| if (CLUSTER != null) |
| CLUSTER.close(); |
| } |
| |
| @Test |
| public void testIndexDropping() throws Throwable |
| { |
| CLUSTER.schemaChange("CREATE KEYSPACE IF NOT EXISTS " + KEYSPACE + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }"); |
| CLUSTER.schemaChange("CREATE TABLE " + KEYSPACE + ".tb1 (c3 TEXT, c4 TEXT, c2 INT, c1 TEXT, PRIMARY KEY (c1, c2, c3 ))"); |
| CLUSTER.coordinator(1).execute("INSERT INTO " + KEYSPACE + ".tb1 (c3, c1, c2) VALUES ('val1','val2',1)", ONE); |
| CLUSTER.schemaChange("CREATE INDEX tb ON " + KEYSPACE + ".tb1 (c3)"); |
| |
| waitForIndex(KEYSPACE, "tb1", "tb"); |
| |
| CLUSTER.schemaChange("TRUNCATE TABLE " + KEYSPACE + " .tb1"); |
| CLUSTER.schemaChange("DROP INDEX " + KEYSPACE + ".tb"); |
| |
| assertEmptyTable("tb1"); |
| restart(); |
| assertEmptyTable("tb1"); |
| } |
| |
| private void restart() throws Throwable |
| { |
| CLUSTER.get(1).shutdown(true).get(); |
| CLUSTER.get(1).startup(); |
| CLUSTER.get(1).runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> |
| CompactionManager.instance.disableAutoCompaction()); |
| } |
| |
| private void assertEmptyTable(String table) |
| { |
| assertEquals(0, CLUSTER.coordinator(1).execute("SELECT * FROM " + KEYSPACE + " ." + table, ONE).length); |
| } |
| |
| private static void waitForIndex(String keyspace, String table, String indexName) |
| { |
| Instant startTime = Instant.now(); |
| Instant maxTime = startTime.plus(10, MINUTES); |
| |
| boolean builtIndex = false; |
| while (!builtIndex) |
| { |
| if (maxTime.isBefore(Instant.now())) |
| return; |
| |
| Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS); |
| builtIndex = CLUSTER.get(1).applyOnInstance((IIsolatedExecutor.SerializableTriFunction<String, String, String, Boolean>) (ks, tbl, idx) -> { |
| ColumnFamilyStore cf = ColumnFamilyStore.getIfExists(ks, tbl); |
| if (cf == null) |
| return false; |
| return cf.indexManager.getBuiltIndexNames().contains(idx); |
| }, keyspace, table, indexName); |
| } |
| } |
| } |