This is an automated email from the ASF dual-hosted git repository.

maedhroz pushed a commit to branch cassandra-5.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cassandra-5.0 by this push:
     new e45c1092f9 Correctly remove Index.Group from IndexRegistry
e45c1092f9 is described below

commit e45c1092f91edd63591f562b2120ea6a5fd3edd5
Author: Mike Adamson <madam...@datastax.com>
AuthorDate: Wed Oct 4 11:27:50 2023 +0100

    Correctly remove Index.Group from IndexRegistry
    
    The Index.Group was being left in the list indexGroups in the 
SecondaryIndexManager because the incorrect
    key was being used to remove it from the map
    
    patch by Mike Adamson; reviewed by Caleb Rackliffe and Zhao Yang for 
CASSANDRA-18905
    
    Co-authored-by: Zhao Yang <zhaoyangsingap...@gmail.com>
---
 CHANGES.txt                                        |  1 +
 .../org/apache/cassandra/db/lifecycle/Tracker.java |  8 ++-
 src/java/org/apache/cassandra/index/Index.java     | 64 +++++++++++++++--
 .../org/apache/cassandra/index/IndexRegistry.java  | 26 +++++--
 .../cassandra/index/SecondaryIndexManager.java     | 60 +++++++---------
 .../cassandra/index/SingletonIndexGroup.java       | 17 +----
 .../cassandra/index/sai/StorageAttachedIndex.java  |  8 ++-
 .../index/sai/StorageAttachedIndexGroup.java       | 17 +++--
 .../org/apache/cassandra/index/sasi/SASIIndex.java |  2 +-
 .../apache/cassandra/index/CustomIndexTest.java    | 75 +++++++++-----------
 .../org/apache/cassandra/index/StubIndexGroup.java |  6 ++
 .../index/sai/cql/IndexGroupLifecycleTest.java     | 81 ++++++++++++++++++++++
 .../index/sai/cql/StorageAttachedIndexDDLTest.java |  6 +-
 .../index/sai/functional/CompactionTest.java       |  5 +-
 .../index/sai/metrics/IndexGroupMetricsTest.java   |  4 +-
 .../index/sai/metrics/QueryMetricsTest.java        |  6 +-
 .../index/sai/metrics/StateMetricsTest.java        |  6 +-
 17 files changed, 261 insertions(+), 131 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 9377b7a421..9a49af3955 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 5.0-alpha2
+ * Correctly remove Index.Group from IndexRegistry (CASSANDRA-18905)
  * Fix vector type to support DDM's mask_default function (CASSANDRA-18889)
  * Remove unnecessary reporter-config3 dependency (CASSANDRA-18907)
  * Remove support for empty values on the vector data type (CASSANDRA-18876)
diff --git a/src/java/org/apache/cassandra/db/lifecycle/Tracker.java 
b/src/java/org/apache/cassandra/db/lifecycle/Tracker.java
index 061765bd52..e959c72fa9 100644
--- a/src/java/org/apache/cassandra/db/lifecycle/Tracker.java
+++ b/src/java/org/apache/cassandra/db/lifecycle/Tracker.java
@@ -86,7 +86,7 @@ public class Tracker
 {
     private static final Logger logger = 
LoggerFactory.getLogger(Tracker.class);
 
-    private final Collection<INotificationConsumer> subscribers = new 
CopyOnWriteArrayList<>();
+    private final List<INotificationConsumer> subscribers = new 
CopyOnWriteArrayList<>();
 
     public final ColumnFamilyStore cfstore;
     final AtomicReference<View> view;
@@ -560,6 +560,12 @@ public class Tracker
         subscribers.add(consumer);
     }
 
+    @VisibleForTesting
+    public boolean contains(INotificationConsumer consumer)
+    {
+        return subscribers.contains(consumer);
+    }
+
     public void unsubscribe(INotificationConsumer consumer)
     {
         subscribers.remove(consumer);
diff --git a/src/java/org/apache/cassandra/index/Index.java 
b/src/java/org/apache/cassandra/index/Index.java
index f116fdb3e0..fc5f5a6f00 100644
--- a/src/java/org/apache/cassandra/index/Index.java
+++ b/src/java/org/apache/cassandra/index/Index.java
@@ -23,6 +23,7 @@ package org.apache.cassandra.index;
 import java.io.UncheckedIOException;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Callable;
@@ -55,8 +56,8 @@ import 
org.apache.cassandra.index.transactions.IndexTransaction;
 import org.apache.cassandra.io.sstable.Component;
 import org.apache.cassandra.io.sstable.Descriptor;
 import org.apache.cassandra.io.sstable.ReducingKeyIterator;
-import org.apache.cassandra.io.sstable.SSTableFlushObserver;
 import org.apache.cassandra.io.sstable.SSTable;
+import org.apache.cassandra.io.sstable.SSTableFlushObserver;
 import org.apache.cassandra.io.sstable.format.SSTableReader;
 import org.apache.cassandra.schema.ColumnMetadata;
 import org.apache.cassandra.schema.IndexMetadata;
@@ -275,6 +276,17 @@ public interface Index
      */
     public void register(IndexRegistry registry);
 
+    /**
+     * Unregister current index when it's removed from system
+     *
+     * @param registry the index registry to register the instance with
+     */
+    default void unregister(IndexRegistry registry)
+    {
+        // for singleton index, the group key is the index itself
+        registry.unregisterIndex(this, new Index.Group.Key(this));
+    }
+
     /**
      * If the index implementation uses a local table to store its index data, 
this method should return a
      * handle to it. If not, an empty {@link Optional} should be returned. 
This exists to support legacy
@@ -677,11 +689,39 @@ public interface Index
      * Class providing grouped operations for indexes that communicate with 
each other.
      *
      * Index implementations should provide a {@code Group} implementation 
calling to
-     * {@link SecondaryIndexManager#registerIndex(Index, Object, Supplier)} 
during index registering
+     * {@link SecondaryIndexManager#registerIndex(Index, Index.Group.Key, 
Supplier)} during index registering
      * at {@link #register(IndexRegistry)} method.
      */
     interface Group
     {
+        /**
+         * Group key is used to uniquely identify a {@link Group} within a 
table
+         */
+        class Key
+        {
+            private final Object object;
+
+            public Key(Object object)
+            {
+                this.object = object;
+            }
+
+            @Override
+            public boolean equals(Object o)
+            {
+                if (this == o) return true;
+                if (o == null || getClass() != o.getClass()) return false;
+                Key key = (Key) o;
+                return Objects.equals(object, key.object);
+            }
+
+            @Override
+            public int hashCode()
+            {
+                return Objects.hash(object);
+            }
+        }
+
         /**
          * Returns the indexes that are members of this group.
          *
@@ -694,14 +734,16 @@ public interface Index
          *
          * @param index the index to be added
          */
-        void addIndex(Index index);
+        default void addIndex(Index index)
+        {}
 
         /**
          * Removes the specified {@link Index} from the members of this group.
          *
          * @param index the index to be removed
          */
-        void removeIndex(Index index);
+        default void removeIndex(Index index)
+        {}
 
         /**
          * Returns if this group contains the specified {@link Index}.
@@ -711,6 +753,16 @@ public interface Index
          */
         boolean containsIndex(Index index);
 
+        /**
+         * Returns whether this group can only ever contain a single index.
+         *
+         * @return {@code true} if this group only contains a single index, 
{@code false} otherwise
+         */
+        default boolean isSingleton()
+        {
+            return true;
+        }
+
         /**
          * Creates an new {@code Indexer} object for updates to a given 
partition.
          *
@@ -769,8 +821,8 @@ public interface Index
         }
 
         /**
-         * Called when the table associated with this group has been 
invalidated. Implementations
-         * should dispose of any resources tied to the lifecycle of the {@link 
Group}.
+         * Called when the table associated with this group has been 
invalidated or all indexes in the group are removed.
+         * Implementations should dispose of any resources tied to the 
lifecycle of the {@link Group}.
          */
         default void invalidate() { }
 
diff --git a/src/java/org/apache/cassandra/index/IndexRegistry.java 
b/src/java/org/apache/cassandra/index/IndexRegistry.java
index 308aeacd7a..46e87f357f 100644
--- a/src/java/org/apache/cassandra/index/IndexRegistry.java
+++ b/src/java/org/apache/cassandra/index/IndexRegistry.java
@@ -64,7 +64,12 @@ public interface IndexRegistry
     IndexRegistry EMPTY = new IndexRegistry()
     {
         @Override
-        public void registerIndex(Index index, Object groupKey, 
Supplier<Index.Group> groupSupplier)
+        public void registerIndex(Index index, Index.Group.Key groupKey, 
Supplier<Index.Group> groupSupplier)
+        {
+        }
+
+        @Override
+        public void unregisterIndex(Index index, Index.Group.Key groupKey)
         {
         }
 
@@ -125,7 +130,11 @@ public interface IndexRegistry
 
             public void register(IndexRegistry registry)
             {
+            }
 
+            @Override
+            public void unregister(IndexRegistry registry)
+            {
             }
 
             public Optional<ColumnFamilyStore> getBackingTable()
@@ -245,7 +254,12 @@ public interface IndexRegistry
             }
         };
 
-        public void registerIndex(Index index, Object groupKey, 
Supplier<Index.Group> groupSupplier)
+        public void registerIndex(Index index, Index.Group.Key groupKey, 
Supplier<Index.Group> groupSupplier)
+        {
+        }
+
+        @Override
+        public void unregisterIndex(Index index, Index.Group.Key groupKey)
         {
         }
 
@@ -277,9 +291,13 @@ public interface IndexRegistry
 
     default void registerIndex(Index index)
     {
-        registerIndex(index, index, () -> new SingletonIndexGroup(index));
+        registerIndex(index, new Index.Group.Key(index), () -> new 
SingletonIndexGroup(index));
     }
-    void registerIndex(Index index, Object groupKey, Supplier<Index.Group> 
groupSupplier);
+
+    void registerIndex(Index index, Index.Group.Key groupKey, 
Supplier<Index.Group> groupSupplier);
+
+    void unregisterIndex(Index index, Index.Group.Key groupKey);
+
     Collection<Index.Group> listIndexGroups();
 
     Index getIndex(IndexMetadata indexMetadata);
diff --git a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java 
b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
index 19985c304f..9fd24bcec3 100644
--- a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
+++ b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
@@ -162,7 +162,7 @@ public class SecondaryIndexManager implements 
IndexRegistry, INotificationConsum
     /**
      * The groups of all the registered indexes
      */
-    private final Map<Object, Index.Group> indexGroups = 
Maps.newConcurrentMap();
+    private final Map<Index.Group.Key, Index.Group> indexGroups = 
Maps.newConcurrentMap();
 
     /**
      * The count of pending index builds for each index.
@@ -344,15 +344,17 @@ public class SecondaryIndexManager implements 
IndexRegistry, INotificationConsum
 
     public synchronized void removeIndex(String indexName)
     {
-        Index index = unregisterIndex(indexName);
-        if (null != index)
+        Index removedIndex = indexes.remove(indexName);
+
+        if (removedIndex != null)
         {
+            removedIndex.unregister(this);
+
             markIndexRemoved(indexName);
-            executeBlocking(index.getInvalidateTask(), null);
+            executeBlocking(removedIndex.getInvalidateTask(), null);
         }
     }
 
-
     public Set<IndexMetadata> getDependentIndexes(ColumnMetadata column)
     {
         if (indexes.isEmpty())
@@ -1302,7 +1304,8 @@ public class SecondaryIndexManager implements 
IndexRegistry, INotificationConsum
     /*
      * IndexRegistry methods
      */
-    public void registerIndex(Index index, Object groupKey, 
Supplier<Index.Group> groupSupplier)
+    @Override
+    public void registerIndex(Index index, Index.Group.Key groupKey, 
Supplier<Index.Group> groupSupplier)
     {
         String name = index.getIndexMetadata().name;
         indexes.put(name, index);
@@ -1312,41 +1315,26 @@ public class SecondaryIndexManager implements 
IndexRegistry, INotificationConsum
         Index.Group group = indexGroups.computeIfAbsent(groupKey, k -> 
groupSupplier.get());
 
         // add the created index to its group if it is not a singleton group
-        if (!(group instanceof SingletonIndexGroup))
-        {
-            if (index.getBackingTable().isPresent())
-                throw new InvalidRequestException("Indexes belonging to a 
group of indexes shouldn't have a backing table");
-
-            group.addIndex(index);
-        }
+        group.addIndex(index);
     }
 
-    private Index unregisterIndex(String name)
+    @Override
+    public void unregisterIndex(Index removed, Index.Group.Key groupKey)
     {
-        Index removed = indexes.remove(name);
-        logger.trace(removed == null ? "Index {} was not registered" : 
"Removed index {} from registry", name);
-
-        if (removed != null)
+        Index.Group group = indexGroups.get(groupKey);
+        if (group != null && group.containsIndex(removed))
         {
-            // Remove the index from any non-singleton groups...
-            for (Index.Group group : listIndexGroups())
-            {
-                if (!(group instanceof SingletonIndexGroup) && 
group.containsIndex(removed))
-                {
-                    group.removeIndex(removed);
+            // Remove the index from non-singleton groups...
+            group.removeIndex(removed);
 
-                    if (group.getIndexes().isEmpty())
-                    {
-                        indexGroups.remove(group);
-                    }
-                }
+            // if the group is a singleton or there are no more indexes left 
in the group, remove it
+            if (group.isSingleton() || group.getIndexes().isEmpty())
+            {
+                Index.Group removedGroup = indexGroups.remove(groupKey);
+                if (removedGroup != null)
+                    removedGroup.invalidate();
             }
-
-            // ...and remove singleton groups entirely.
-            indexGroups.remove(removed);
         }
-
-        return removed;
     }
 
     public Index getIndex(IndexMetadata metadata)
@@ -1364,14 +1352,14 @@ public class SecondaryIndexManager implements 
IndexRegistry, INotificationConsum
         return ImmutableSet.copyOf(indexGroups.values());
     }
 
-    public Index.Group getIndexGroup(Object key)
+    public Index.Group getIndexGroup(Index.Group.Key key)
     {
         return indexGroups.get(key);
     }
 
     /**
      * Returns the {@link Index.Group} the specified index belongs to, as 
specified during registering with
-     * {@link #registerIndex(Index, Object, Supplier)}.
+     * {@link #registerIndex(Index, Index.Group.Key, Supplier)}.
      *
      * @param metadata the index metadata
      * @return the group the index belongs to, or {@code null} if the index is 
not registered or if it hasn't been
diff --git a/src/java/org/apache/cassandra/index/SingletonIndexGroup.java 
b/src/java/org/apache/cassandra/index/SingletonIndexGroup.java
index 304d35d6d4..162247fd74 100644
--- a/src/java/org/apache/cassandra/index/SingletonIndexGroup.java
+++ b/src/java/org/apache/cassandra/index/SingletonIndexGroup.java
@@ -62,18 +62,6 @@ public class SingletonIndexGroup implements Index.Group
         return delegate;
     }
 
-    @Override
-    public void addIndex(Index index)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void removeIndex(Index index)
-    {
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public boolean containsIndex(Index index)
     {
@@ -89,9 +77,8 @@ public class SingletonIndexGroup implements Index.Group
                                     IndexTransaction.Type transactionType,
                                     Memtable memtable)
     {
-        return indexSelector.test(delegate)
-               ? delegate.indexerFor(key, columns, nowInSec, ctx, 
transactionType, memtable)
-               : null;
+        return indexSelector.test(delegate) ? delegate.indexerFor(key, 
columns, nowInSec, ctx, transactionType, memtable)
+                                            : null;
     }
 
     @Override
diff --git a/src/java/org/apache/cassandra/index/sai/StorageAttachedIndex.java 
b/src/java/org/apache/cassandra/index/sai/StorageAttachedIndex.java
index a0cdf9c3ac..b09f04d80d 100644
--- a/src/java/org/apache/cassandra/index/sai/StorageAttachedIndex.java
+++ b/src/java/org/apache/cassandra/index/sai/StorageAttachedIndex.java
@@ -272,7 +272,13 @@ public class StorageAttachedIndex implements Index
     public void register(IndexRegistry registry)
     {
         // index will be available for writes
-        registry.registerIndex(this, StorageAttachedIndexGroup.class, () -> 
new StorageAttachedIndexGroup(baseCfs));
+        registry.registerIndex(this, StorageAttachedIndexGroup.GROUP_KEY, () 
-> new StorageAttachedIndexGroup(baseCfs));
+    }
+
+    @Override
+    public void unregister(IndexRegistry registry)
+    {
+        registry.unregisterIndex(this, StorageAttachedIndexGroup.GROUP_KEY);
     }
 
     @Override
diff --git 
a/src/java/org/apache/cassandra/index/sai/StorageAttachedIndexGroup.java 
b/src/java/org/apache/cassandra/index/sai/StorageAttachedIndexGroup.java
index de4c8b3570..5dac6edd47 100644
--- a/src/java/org/apache/cassandra/index/sai/StorageAttachedIndexGroup.java
+++ b/src/java/org/apache/cassandra/index/sai/StorageAttachedIndexGroup.java
@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
@@ -29,7 +30,6 @@ import javax.annotation.concurrent.ThreadSafe;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import com.google.common.primitives.Ints;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -75,10 +75,12 @@ public class StorageAttachedIndexGroup implements 
Index.Group, INotificationCons
 {
     private static final Logger logger = 
LoggerFactory.getLogger(StorageAttachedIndexGroup.class);
 
+    public static final Index.Group.Key GROUP_KEY = new 
Index.Group.Key(StorageAttachedIndexGroup.class);
+
     private final TableQueryMetrics queryMetrics;
     private final TableStateMetrics stateMetrics;
     private final IndexGroupMetrics groupMetrics;
-    private final Set<StorageAttachedIndex> indexes = 
Sets.newConcurrentHashSet();
+    private final Set<StorageAttachedIndex> indexes = 
ConcurrentHashMap.newKeySet();
     private final ColumnFamilyStore baseCfs;
 
     private final SSTableContextManager contextManager;
@@ -98,7 +100,7 @@ public class StorageAttachedIndexGroup implements 
Index.Group, INotificationCons
     @Nullable
     public static StorageAttachedIndexGroup getIndexGroup(ColumnFamilyStore 
cfs)
     {
-        return (StorageAttachedIndexGroup) 
cfs.indexManager.getIndexGroup(StorageAttachedIndexGroup.class);
+        return (StorageAttachedIndexGroup) 
cfs.indexManager.getIndexGroup(StorageAttachedIndexGroup.GROUP_KEY);
     }
 
     @Override
@@ -128,14 +130,13 @@ public class StorageAttachedIndexGroup implements 
Index.Group, INotificationCons
             for (SSTableReader sstable : contextManager.sstables())
                 
sstable.unregisterComponents(IndexDescriptor.create(sstable).getLivePerSSTableComponents(),
 baseCfs.getTracker());
             deletePerSSTableFiles(baseCfs.getLiveSSTables());
-            baseCfs.getTracker().unsubscribe(this);
         }
     }
 
     @Override
     public void invalidate()
     {
-        // in case of dropping table, sstable contexts should already been 
removed by SSTableListChangedNotification.
+        // in case of removing last index from group, sstable contexts should 
already been removed by removeIndex
         queryMetrics.release();
         groupMetrics.release();
         stateMetrics.release();
@@ -149,6 +150,12 @@ public class StorageAttachedIndexGroup implements 
Index.Group, INotificationCons
         return indexes.contains(index);
     }
 
+    @Override
+    public boolean isSingleton()
+    {
+        return false;
+    }
+
     @Override
     public Index.Indexer indexerFor(Predicate<Index> indexSelector,
                                     DecoratedKey key,
diff --git a/src/java/org/apache/cassandra/index/sasi/SASIIndex.java 
b/src/java/org/apache/cassandra/index/sasi/SASIIndex.java
index 1ae3a04943..93448f9e78 100644
--- a/src/java/org/apache/cassandra/index/sasi/SASIIndex.java
+++ b/src/java/org/apache/cassandra/index/sasi/SASIIndex.java
@@ -186,7 +186,7 @@ public class SASIIndex implements Index, 
INotificationConsumer
     @Override
     public void register(IndexRegistry registry)
     {
-        registry.registerIndex(this, this, () -> new SASIIndexGroup(this));
+        registry.registerIndex(this, new Group.Key(this), () -> new 
SASIIndexGroup(this));
     }
 
     public IndexMetadata getIndexMetadata()
diff --git a/test/unit/org/apache/cassandra/index/CustomIndexTest.java 
b/test/unit/org/apache/cassandra/index/CustomIndexTest.java
index 5c5f89f644..79987f34b1 100644
--- a/test/unit/org/apache/cassandra/index/CustomIndexTest.java
+++ b/test/unit/org/apache/cassandra/index/CustomIndexTest.java
@@ -25,6 +25,7 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 import com.google.common.collect.ImmutableList;
@@ -1399,13 +1400,13 @@ public class CustomIndexTest extends CQLTester
         // create two indexes belonging to the same group and verify that only 
one group is added to the manager
         String idx1 = createIndex(String.format("CREATE CUSTOM INDEX ON 
%%s(v1) USING '%s'", indexClassName));
         String idx2 = createIndex(String.format("CREATE CUSTOM INDEX ON 
%%s(v2) USING '%s'", indexClassName));
-        IndexWithSharedGroup.Group group = indexManager.listIndexGroups()
-                                                       .stream()
-                                                       .filter(g -> g 
instanceof IndexWithSharedGroup.Group)
-                                                       .map(g -> 
(IndexWithSharedGroup.Group) g)
-                                                       .findAny()
-                                                       
.orElseThrow(AssertionError::new);
-
+        Supplier<IndexWithSharedGroup.Group> groupSupplier =
+                () -> indexManager.listIndexGroups().stream()
+                                                    .filter(g -> g instanceof 
IndexWithSharedGroup.Group)
+                                                    .map(g -> 
(IndexWithSharedGroup.Group) g)
+                                                    .findAny()
+                                                    .orElse(null);
+        IndexWithSharedGroup.Group group = groupSupplier.get();
         // verify that only one group has been added to the manager
         assertEquals(2, indexManager.listIndexes().size());
         assertEquals(1, indexManager.listIndexGroups().size());
@@ -1435,20 +1436,26 @@ public class CustomIndexTest extends CQLTester
         assertEquals(2, indexManager.listIndexes().size());
         assertEquals(1, indexManager.listIndexGroups().size());
 
-        // drop the remaining members of the shared group and verify that it 
is kept empty in the manager
+        // drop the remaining members of the shared group and verify that it 
no longer exists in the manager
         dropIndex("DROP INDEX %s." + idx2);
         dropIndex("DROP INDEX %s." + idx5);
         assertEquals(0, indexManager.listIndexes().size());
-        assertEquals(1, indexManager.listIndexGroups().size());
+        assertEquals(0, indexManager.listIndexGroups().size());
         assertEquals(0, group.indexes.size());
 
-        // create the sharing group members again and verify that they are 
added to the existing group instance
+        // create the sharing group members again and verify that they are 
added to a new group instance
         createIndex(String.format("CREATE CUSTOM INDEX %s ON %%s(v1) USING 
'%s'", idx1, indexClassName));
         createIndex(String.format("CREATE CUSTOM INDEX %s ON %%s(v2) USING 
'%s'", idx2, indexClassName));
         createIndex(String.format("CREATE CUSTOM INDEX %s ON %%s(v3) USING 
'%s'", idx3, indexClassName));
+        IndexWithSharedGroup.Group newGroup = indexManager.listIndexGroups()
+                                                          .stream()
+                                                          .filter(g -> g 
instanceof IndexWithSharedGroup.Group)
+                                                          .map(g -> 
(IndexWithSharedGroup.Group) g)
+                                                          .findAny()
+                                                          
.orElseThrow(AssertionError::new);
         assertEquals(3, indexManager.listIndexes().size());
         assertEquals(1, indexManager.listIndexGroups().size());
-        assertEquals(3, group.indexes.size());
+        assertEquals(3, newGroup.indexes.size());
     }
 
     /**
@@ -1471,7 +1478,13 @@ public class CustomIndexTest extends CQLTester
         @Override
         public void register(IndexRegistry registry)
         {
-            registry.registerIndex(this, Group.class, Group::new);
+            registry.registerIndex(this, new Group.Key(Group.class), 
Group::new);
+        }
+
+        @Override
+        public void unregister(IndexRegistry registry)
+        {
+            registry.unregisterIndex(this, new Group.Key(Group.class));
         }
 
         private static class Group implements Index.Group
@@ -1533,6 +1546,12 @@ public class CustomIndexTest extends CQLTester
                 return indexes.containsKey(index.getIndexMetadata().name);
             }
 
+            @Override
+            public boolean isSingleton()
+            {
+                return false;
+            }
+
             @Override
             public Index.Indexer indexerFor(Predicate<Index> indexSelector,
                                             DecoratedKey key,
@@ -1663,36 +1682,4 @@ public class CustomIndexTest extends CQLTester
             }
         }
     }
-
-    @Test
-    public void testMulticolumnIndexWithBaseTable() throws Throwable
-    {
-        createTable("CREATE TABLE %s(k int PRIMARY KEY, v int)");
-        assertInvalidMessage("Indexes belonging to a group of indexes 
shouldn't have a backing table",
-                             String.format("CREATE CUSTOM INDEX ON %%s(v) 
USING '%s'",
-                                           
MulticolumnIndexWithBaseTable.class.getName()));
-    }
-
-    public static final class MulticolumnIndexWithBaseTable extends StubIndex
-    {
-        private final ColumnFamilyStore baseCfs;
-
-        public MulticolumnIndexWithBaseTable(ColumnFamilyStore baseCfs, 
IndexMetadata metadata)
-        {
-            super(baseCfs, metadata);
-            this.baseCfs = baseCfs;
-        }
-
-        @Override
-        public void register(IndexRegistry registry)
-        {
-            registry.registerIndex(this, MulticolumnIndexWithBaseTable.class, 
StubIndexGroup::new);
-        }
-
-        @Override
-        public Optional<ColumnFamilyStore> getBackingTable()
-        {
-            return Optional.of(baseCfs);
-        }
-    }    
 }
diff --git a/test/unit/org/apache/cassandra/index/StubIndexGroup.java 
b/test/unit/org/apache/cassandra/index/StubIndexGroup.java
index 0b6ad1c37d..22dfbe262b 100644
--- a/test/unit/org/apache/cassandra/index/StubIndexGroup.java
+++ b/test/unit/org/apache/cassandra/index/StubIndexGroup.java
@@ -67,6 +67,12 @@ public class StubIndexGroup implements Index.Group
         return indexes.contains(index);
     }
 
+    @Override
+    public boolean isSingleton()
+    {
+        return false;
+    }
+
     @Override
     public Index.Indexer indexerFor(Predicate<Index> indexSelector,
                                     DecoratedKey key,
diff --git 
a/test/unit/org/apache/cassandra/index/sai/cql/IndexGroupLifecycleTest.java 
b/test/unit/org/apache/cassandra/index/sai/cql/IndexGroupLifecycleTest.java
new file mode 100644
index 0000000000..13965439ab
--- /dev/null
+++ b/test/unit/org/apache/cassandra/index/sai/cql/IndexGroupLifecycleTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.index.sai.cql;
+
+import org.junit.Test;
+
+import org.apache.cassandra.db.ColumnFamilyStore;
+import org.apache.cassandra.db.lifecycle.Tracker;
+import org.apache.cassandra.index.sai.SAITester;
+import org.apache.cassandra.index.sai.StorageAttachedIndexGroup;
+
+import static java.lang.String.format;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class IndexGroupLifecycleTest extends SAITester
+{
+    @Test
+    public void testDropAndRecreate() throws Throwable
+    {
+        createTable("CREATE TABLE %s (pk text, value text, PRIMARY KEY (pk))");
+        populateOneSSTable();
+
+        ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
+        cfs.disableAutoCompaction();
+        Tracker tracker = cfs.getTracker();
+
+        // create index and drop it: StorageAttachedIndexGroup should be 
removed
+        createIndex("CREATE CUSTOM INDEX sai ON %s(value) USING 
'StorageAttachedIndex'");
+
+        StorageAttachedIndexGroup group = (StorageAttachedIndexGroup) 
cfs.indexManager.getIndexGroup(StorageAttachedIndexGroup.GROUP_KEY);
+        assertTrue(tracker.contains(group));
+        assertEquals(1, group.sstableContextManager().size());
+
+        dropIndex(format("DROP INDEX %s.sai", KEYSPACE));
+        assertFalse(tracker.contains(group));
+        assertEquals(0, group.sstableContextManager().size()); // sstable 
should be cleared from old group
+        
assertNull(cfs.indexManager.getIndexGroup(StorageAttachedIndexGroup.GROUP_KEY));
+
+        // populate 2nd sstable. Old group should not track it
+        populateOneSSTable();
+        assertEquals(0, group.sstableContextManager().size());
+
+        // create index again: expect a new StorageAttachedIndexGroup to be 
registered into tracker
+        createIndex("CREATE CUSTOM INDEX sai ON %s(value) USING 
'StorageAttachedIndex'");
+
+        StorageAttachedIndexGroup newGroup = (StorageAttachedIndexGroup) 
cfs.indexManager.getIndexGroup(StorageAttachedIndexGroup.GROUP_KEY);
+        assertNotSame(group, newGroup);
+        assertTrue(tracker.contains(newGroup));
+        assertEquals(2, newGroup.sstableContextManager().size());
+
+        // populate 3rd sstable. new group should  track it
+        populateOneSSTable();
+        assertEquals(3, newGroup.sstableContextManager().size());
+    }
+
+    private void populateOneSSTable()
+    {
+        execute("INSERT INTO %s(pk, value) VALUES('k', 'v')");
+        flush();
+    }
+}
\ No newline at end of file
diff --git 
a/test/unit/org/apache/cassandra/index/sai/cql/StorageAttachedIndexDDLTest.java 
b/test/unit/org/apache/cassandra/index/sai/cql/StorageAttachedIndexDDLTest.java
index 6dedd5ed2d..f10149855d 100644
--- 
a/test/unit/org/apache/cassandra/index/sai/cql/StorageAttachedIndexDDLTest.java
+++ 
b/test/unit/org/apache/cassandra/index/sai/cql/StorageAttachedIndexDDLTest.java
@@ -84,6 +84,7 @@ import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.when;
@@ -667,8 +668,7 @@ public class StorageAttachedIndexDDLTest extends SAITester
 
         dropIndex("DROP INDEX %s." + literalIndexName);
         verifyIndexFiles(numericIndexContext, literalIndexContext, 0, 0);
-        verifySSTableIndexes(numericIndexName, 0);
-        verifySSTableIndexes(literalIndexName, 0);
+        assertNull(getCurrentIndexGroup());
 
         assertEquals("Segment memory limiter should revert to zero on drop.", 
0L, getSegmentBufferUsedBytes());
         assertEquals("There should be no segment builders in progress.", 0L, 
getColumnIndexBuildsInProgress());
@@ -1182,7 +1182,7 @@ public class StorageAttachedIndexDDLTest extends SAITester
 
         delayIndexBuilderCompletion.disable();
 
-        verifySSTableIndexes(indexName, 0);
+        assertNull(getCurrentIndexGroup());
         assertFalse("Expect index not built", 
SystemKeyspace.isIndexBuilt(KEYSPACE, indexName));
 
         // create index again, it should succeed
diff --git 
a/test/unit/org/apache/cassandra/index/sai/functional/CompactionTest.java 
b/test/unit/org/apache/cassandra/index/sai/functional/CompactionTest.java
index da9a051f4f..aff3c5e862 100644
--- a/test/unit/org/apache/cassandra/index/sai/functional/CompactionTest.java
+++ b/test/unit/org/apache/cassandra/index/sai/functional/CompactionTest.java
@@ -61,8 +61,8 @@ import org.apache.cassandra.utils.TimeUUID;
 import org.apache.cassandra.utils.concurrent.Refs;
 
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -339,8 +339,7 @@ public class CompactionTest extends SAITester
         }
 
         // verify index group metrics are cleared.
-        assertEquals(0, getOpenIndexFiles());
-        assertEquals(0, getDiskUsage());
+        assertNull(getCurrentIndexGroup());
 
         // verify indexes are dropped
         // verify indexes are dropped
diff --git 
a/test/unit/org/apache/cassandra/index/sai/metrics/IndexGroupMetricsTest.java 
b/test/unit/org/apache/cassandra/index/sai/metrics/IndexGroupMetricsTest.java
index 7a54ebfc86..26a7ab6714 100644
--- 
a/test/unit/org/apache/cassandra/index/sai/metrics/IndexGroupMetricsTest.java
+++ 
b/test/unit/org/apache/cassandra/index/sai/metrics/IndexGroupMetricsTest.java
@@ -26,6 +26,7 @@ import org.apache.cassandra.index.sai.disk.format.Version;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 
 public class IndexGroupMetricsTest extends AbstractMetricsTest
 {
@@ -76,8 +77,7 @@ public class IndexGroupMetricsTest extends AbstractMetricsTest
 
         // drop last index, no open index files
         dropIndex("DROP INDEX %s." + v1IndexName);
-        assertEquals(0, getOpenIndexFiles());
-        assertEquals(0, getDiskUsage());
+        assertNull(getCurrentIndexGroup());
     }
 
     protected int getOpenIndexFiles()
diff --git 
a/test/unit/org/apache/cassandra/index/sai/metrics/QueryMetricsTest.java 
b/test/unit/org/apache/cassandra/index/sai/metrics/QueryMetricsTest.java
index 14931b173b..e1f1c6933d 100644
--- a/test/unit/org/apache/cassandra/index/sai/metrics/QueryMetricsTest.java
+++ b/test/unit/org/apache/cassandra/index/sai/metrics/QueryMetricsTest.java
@@ -91,12 +91,8 @@ public class QueryMetricsTest extends AbstractMetricsTest
 
         assertEquals(1L, getTableQueryMetrics(keyspace, table, 
"TotalQueriesCompleted"));
 
-        // Even if we drop the last index on the table, table-level metrics 
should still be visible:
+        // If we drop the last index on the table we should no longer see the 
table-level state metrics:
         dropIndex(String.format("DROP INDEX %s." + index, keyspace));
-        assertEquals(1L, getTableQueryMetrics(keyspace, table, 
"TotalQueriesCompleted"));
-
-        // When the whole table is dropped, we should finally fail to find 
table-level metrics:
-        dropTable(String.format("DROP TABLE %s." + table, keyspace));
         assertThatThrownBy(() -> getTableQueryMetrics(keyspace, table, 
"TotalQueriesCompleted")).hasCauseInstanceOf(InstanceNotFoundException.class);
     }
 
diff --git 
a/test/unit/org/apache/cassandra/index/sai/metrics/StateMetricsTest.java 
b/test/unit/org/apache/cassandra/index/sai/metrics/StateMetricsTest.java
index e6ecda02fb..8e83293e61 100644
--- a/test/unit/org/apache/cassandra/index/sai/metrics/StateMetricsTest.java
+++ b/test/unit/org/apache/cassandra/index/sai/metrics/StateMetricsTest.java
@@ -55,12 +55,8 @@ public class StateMetricsTest extends AbstractMetricsTest
         assertEquals(1, rows.all().size());
         assertEquals(1L, getTableStateMetrics(keyspace, table, 
"TotalIndexCount"));
 
-        // If we drop the last index on the table, table-level state metrics 
should still be visible:
+        // If we drop the last index on the table, we should no longer see the 
table-level state metrics:
         dropIndex(String.format("DROP INDEX %s." + index, keyspace));
-        assertEquals(0L, getTableStateMetrics(keyspace, table, 
"TotalIndexCount"));
-
-        // When the whole table is dropped, we should finally fail to find 
table-level state metrics:
-        dropTable(String.format("DROP TABLE %s." + table, keyspace));
         assertThatThrownBy(() -> getTableStateMetrics(keyspace, table, 
"TotalIndexCount")).hasCauseInstanceOf(InstanceNotFoundException.class);
     }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org


Reply via email to