adelapena commented on code in PR #4249:
URL: https://github.com/apache/cassandra/pull/4249#discussion_r2230997291


##########
src/java/org/apache/cassandra/net/MessagingService.java:
##########
@@ -777,4 +786,28 @@ public void waitUntilListeningUnchecked()
             throw new RuntimeException(e);
         }
     }
+
+    /**
+     * Returns the endpoints for the given keyspace that are known to be alive 
and have a connection whose
+     * messaging version is older than the given version. To be used for 
example when we want to be sure a message
+     * can be serialized to all endpoints, according to their negotiated 
version at connection time.
+     *
+     * @param keyspace a keyspace
+     * @param version a messaging version
+     * @return a set of alive endpoints in the given keyspace with messaging 
version below the given version
+     */
+    public Set<InetAddressAndPort> 
endpointsWithConnectionsOnVersionBelow(String keyspace, int version)

Review Comment:
   Nit: the `keyspace` param is unused.



##########
src/java/org/apache/cassandra/db/filter/IndexHints.md:
##########
@@ -0,0 +1,134 @@
+<!---
+Copyright DataStax, Inc.
+
+Licensed 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.
+-->

Review Comment:
   This should be licensed to the ASF now.



##########
src/java/org/apache/cassandra/db/filter/IndexHints.java:
##########
@@ -0,0 +1,663 @@
+/*
+ * 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.db.filter;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nullable;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import org.apache.cassandra.cql3.QualifiedName;
+import org.apache.cassandra.db.TypeSizes;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.index.Index;
+import org.apache.cassandra.index.IndexRegistry;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.net.MessagingService;
+import org.apache.cassandra.schema.IndexMetadata;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.utils.FBUtilities;
+
+import static java.lang.String.format;
+
+/**
+ * User-provided directives about what indexes should be used by a {@code 
SELECT} query.
+ * See {@code IndexHints.md} for further details.
+ */
+public class IndexHints
+{
+    public static final String CONFLICTING_INDEXES_ERROR = "Indexes cannot be 
both included and excluded: ";
+    public static final String WRONG_KEYSPACE_ERROR = "Index %s is not in the 
same keyspace as the queried table.";
+    public static final String MISSING_INDEX_ERROR = "Table %s doesn't have an 
index named %s";
+    public static final String NON_INCLUDABLE_INDEXES_ERROR = "It's not 
possible to use all the specified included indexes with this query.";
+    public static final String TOO_MANY_INDEXES_ERROR = format("Cannot have 
more than %d included/excluded indexes, found ", Short.MAX_VALUE);
+
+    public static final IndexHints NONE = new 
IndexHints(Collections.emptySet(), Collections.emptySet())
+    {
+        @Override
+        public boolean includes(Index index)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean includes(String indexName)
+        {
+            return false;
+        }
+
+        @Override
+        public Set<Index> includedIn(Collection<Index> indexes)
+        {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public boolean includesAnyOf(Collection<Index> indexes)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean excludes(Index index)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean excludes(String indexName)
+        {
+            return false;
+        }
+
+        @Override
+        public <T extends Index> Set<T> notExcluded(Iterable<T> indexes)
+        {
+            return Sets.newHashSet(indexes);
+        }
+
+        @Override
+        public void validate(@Nullable Index.QueryPlan queryPlan)
+        {
+            // nothing to validate
+        }
+
+        @Override
+        public Comparator<Index.QueryPlan> comparator()
+        {
+            // no index hints, so all plans are equal in that respect
+            return (x, y) -> 0;
+        }
+    };
+
+    public static final Serializer serializer = new Serializer();
+
+    /**
+     * The indexes to use when executing a query.
+     */
+    public final Set<IndexMetadata> included;
+
+    /**
+     * The indexes not to use when executing the query.
+     */
+    public final Set<IndexMetadata> excluded;
+
+    private IndexHints(Set<IndexMetadata> included, Set<IndexMetadata> 
excluded)
+    {
+        this.included = included;
+        this.excluded = excluded;
+    }
+
+    /**
+     * @param index an index
+     * @return {@code true} if the index is included, {@code false} otherwise
+     */
+    public boolean includes(Index index)
+    {
+        return includes(index.getIndexMetadata().name);
+    }
+
+    /**
+     * @param indexName the name of an index
+     * @return {@code true} if the index is included, {@code false} otherwise
+     */
+    public boolean includes(String indexName)
+    {
+        for (IndexMetadata i : included)
+        {
+            if (i.name.equals(indexName))
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the indexes in the specified collection of indexes that are 
included by these hints.
+     *
+     * @param indexes a collection of indexes
+     * @return the indexes that are included by these hints
+     */
+    public Set<Index> includedIn(Collection<Index> indexes)

Review Comment:
   Nit: unused method



##########
src/java/org/apache/cassandra/db/filter/IndexHints.java:
##########
@@ -0,0 +1,663 @@
+/*
+ * 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.db.filter;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nullable;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import org.apache.cassandra.cql3.QualifiedName;
+import org.apache.cassandra.db.TypeSizes;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.index.Index;
+import org.apache.cassandra.index.IndexRegistry;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.net.MessagingService;
+import org.apache.cassandra.schema.IndexMetadata;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.utils.FBUtilities;
+
+import static java.lang.String.format;
+
+/**
+ * User-provided directives about what indexes should be used by a {@code 
SELECT} query.
+ * See {@code IndexHints.md} for further details.
+ */
+public class IndexHints
+{
+    public static final String CONFLICTING_INDEXES_ERROR = "Indexes cannot be 
both included and excluded: ";
+    public static final String WRONG_KEYSPACE_ERROR = "Index %s is not in the 
same keyspace as the queried table.";
+    public static final String MISSING_INDEX_ERROR = "Table %s doesn't have an 
index named %s";
+    public static final String NON_INCLUDABLE_INDEXES_ERROR = "It's not 
possible to use all the specified included indexes with this query.";
+    public static final String TOO_MANY_INDEXES_ERROR = format("Cannot have 
more than %d included/excluded indexes, found ", Short.MAX_VALUE);
+
+    public static final IndexHints NONE = new 
IndexHints(Collections.emptySet(), Collections.emptySet())
+    {
+        @Override
+        public boolean includes(Index index)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean includes(String indexName)
+        {
+            return false;
+        }
+
+        @Override
+        public Set<Index> includedIn(Collection<Index> indexes)
+        {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public boolean includesAnyOf(Collection<Index> indexes)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean excludes(Index index)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean excludes(String indexName)
+        {
+            return false;
+        }
+
+        @Override
+        public <T extends Index> Set<T> notExcluded(Iterable<T> indexes)
+        {
+            return Sets.newHashSet(indexes);
+        }
+
+        @Override
+        public void validate(@Nullable Index.QueryPlan queryPlan)
+        {
+            // nothing to validate
+        }
+
+        @Override
+        public Comparator<Index.QueryPlan> comparator()
+        {
+            // no index hints, so all plans are equal in that respect
+            return (x, y) -> 0;
+        }
+    };
+
+    public static final Serializer serializer = new Serializer();
+
+    /**
+     * The indexes to use when executing a query.
+     */
+    public final Set<IndexMetadata> included;
+
+    /**
+     * The indexes not to use when executing the query.
+     */
+    public final Set<IndexMetadata> excluded;
+
+    private IndexHints(Set<IndexMetadata> included, Set<IndexMetadata> 
excluded)
+    {
+        this.included = included;
+        this.excluded = excluded;
+    }
+
+    /**
+     * @param index an index
+     * @return {@code true} if the index is included, {@code false} otherwise
+     */
+    public boolean includes(Index index)
+    {
+        return includes(index.getIndexMetadata().name);
+    }
+
+    /**
+     * @param indexName the name of an index
+     * @return {@code true} if the index is included, {@code false} otherwise
+     */
+    public boolean includes(String indexName)
+    {
+        for (IndexMetadata i : included)
+        {
+            if (i.name.equals(indexName))
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the indexes in the specified collection of indexes that are 
included by these hints.
+     *
+     * @param indexes a collection of indexes
+     * @return the indexes that are included by these hints
+     */
+    public Set<Index> includedIn(Collection<Index> indexes)
+    {
+        Set<Index> result = new HashSet<>();
+        for (Index index : indexes)
+        {
+            if (includes(index))
+                result.add(index);
+        }
+        return result;
+    }
+
+    /**
+     * @param indexes a collection of indexes
+     * @return {@code true} if any of the indexes is included, {@code false} 
otherwise
+     */
+    public boolean includesAnyOf(Collection<Index> indexes)

Review Comment:
   Nit: unused method



##########
test/distributed/org/apache/cassandra/distributed/test/sai/IndexHintsDistributedTest.java:
##########
@@ -0,0 +1,148 @@
+/*
+ * 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.sai;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.ConsistencyLevel;
+import org.apache.cassandra.distributed.api.ICoordinator;
+import org.apache.cassandra.distributed.test.TestBaseImpl;
+import org.apache.cassandra.net.MessagingService;
+import org.apache.cassandra.net.MessagingService.Version;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
+import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL;
+import static org.apache.cassandra.distributed.api.Feature.NETWORK;
+import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
+import static org.apache.cassandra.distributed.shared.AssertUtils.row;
+
+/**
+ * Distributed tests for {@link org.apache.cassandra.db.filter.IndexHints}.
+ */
+public class IndexHintsDistributedTest extends TestBaseImpl
+{
+    private static final int NUM_REPLICAS = 2;
+    private static final int RF = 2;
+
+    @Test
+    public void testIndexHintsWithCurrentVersion() throws Throwable
+    {
+        try (Cluster cluster = init(Cluster.build(NUM_REPLICAS)
+                                           .withConfig(config -> 
config.with(GOSSIP).with(NETWORK).set("storage_compatibility_mode", "NONE"))
+                                           .start(), RF))
+        {
+            // null indicates that the query should succeed
+            testSelectWithIndexHints(cluster, null);
+        }
+    }
+
+    @Test
+    public void testIndexHintsWithAllOldVersion() throws Throwable
+    {
+        try (Cluster cluster = init(Cluster.build(NUM_REPLICAS)
+                                           .withConfig(config -> 
config.with(GOSSIP).with(NETWORK).set("storage_compatibility_mode", 
"CASSANDRA_4"))
+                                           .start(), RF))
+        {
+            testSelectWithIndexHints(cluster, "Index hints are not supported 
in clusters below 14.");
+        }
+    }
+
+    @Test
+    public void testIndexHintsWithMixedVersions() throws Throwable
+    {
+        try (Cluster cluster = init(Cluster.build(NUM_REPLICAS)
+                                           
.withInstanceInitializer(BB::install)
+                                           .withConfig(config -> 
config.with(GOSSIP).with(NETWORK).with(NATIVE_PROTOCOL).set("storage_compatibility_mode",
 "NONE"))
+                                           .start(), RF))
+        {
+            testSelectWithIndexHints(cluster, "Index hints are not supported 
in clusters below 14.");
+        }
+    }
+
+    private static void testSelectWithIndexHints(Cluster cluster, String 
expectedErrorMessage) throws Throwable
+    {
+        // create a schema with various indexes in the same column, so we can 
provide hints to select between them
+        cluster.schemaChange(withKeyspace("CREATE TABLE %s.t (k int PRIMARY 
KEY, v text)"));
+        cluster.schemaChange(withKeyspace("CREATE INDEX legacy_idx ON 
%s.t(v)"));
+        cluster.schemaChange(withKeyspace("CREATE CUSTOM INDEX 
non_analyzed_sai_idx ON %s.t(v) USING 'StorageAttachedIndex'"));

Review Comment:
   I guess this could be named just `sai_idx` until we introduce tokenizing 
analyzers, although maybe leaving it this way could help us to update it later?



##########
src/java/org/apache/cassandra/cql3/restrictions/MergedRestriction.java:
##########
@@ -335,11 +349,11 @@ public void restrict(RangeSet<ClusteringElements> 
rangeSet, QueryOptions options
     }
 
     @Override
-    public void addToRowFilter(RowFilter filter, IndexRegistry indexRegistry, 
QueryOptions options)
+    public void addToRowFilter(RowFilter filter, IndexRegistry indexRegistry, 
QueryOptions options,IndexHints indexHints)

Review Comment:
   ```suggestion
       public void addToRowFilter(RowFilter filter, IndexRegistry 
indexRegistry, QueryOptions options, IndexHints indexHints)
   ```



##########
test/unit/org/apache/cassandra/cql3/CQLTester.java:
##########
@@ -252,6 +257,16 @@ public abstract class CQLTester
     public static final String RACK1 = ServerTestUtils.RACK1;
     protected static final int ASSERTION_TIMEOUT_SECONDS = 15;
 
+    /**
+     * Whether to use coorfinator execution in {@link #execute(String, 
Object...)}, so queries get full validation and

Review Comment:
   ```suggestion
        * Whether to use coordinator execution in {@link #execute(String, 
Object...)}, so queries get full validation and
   ```



##########
src/java/org/apache/cassandra/index/SecondaryIndexManager.java:
##########
@@ -1224,6 +1226,15 @@ public void deletePartition(UnfilteredRowIterator 
partition, long nowInSec)
      * <p>
      * The filtered set then sorted by selectivity, as reported by the Index 
implementations' getEstimatedResultRows
      * method.
+     * Once we have the filtered set of indexes, one is selected as the best 
one according to the following rules:
+     * <ol>
+     *     <li>An index included by the query's index hints is better than an 
index not included by the hints.</li>
+     *     <li>If it's a contains restriction, then a non-analyzed index is 
better.</li>

Review Comment:
   This should be removed until we add analyzers.



##########
test/unit/org/apache/cassandra/cql3/CQLTester.java:
##########
@@ -3624,4 +3770,75 @@ public int hashCode()
             return java.util.Objects.hash(user, protocolVersion, 
shouldUseEncryption, shouldUseCertificate);
         }
     }
+
+    protected PlanSelectionAssertion assertThatIndexQueryPlanFor(String query, 
Object[]... expectedRows)
+    {
+        // First execute the query capturing warnings and check the query 
results
+        disablePreparedReuseForTest();
+        ClientWarn.instance.captureWarnings();
+        assertRowsIgnoringOrder(execute(query), expectedRows);
+        List<String> warnings = ClientWarn.instance.getWarnings();

Review Comment:
   Probably this and the two related methods (`warns` and `doesntWarn`) can be 
removed.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscr...@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


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

Reply via email to