diff --git a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
index a15251bdefa7..89ae1d38b8a3 100644
--- a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
+++ b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
@@ -23,6 +23,8 @@
import java.util.Objects;
import org.apache.cassandra.db.marshal.VectorType;
+import org.apache.cassandra.index.IndexRegistry;
+import org.apache.cassandra.index.sai.analyzer.AnalyzerEqOperatorSupport;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.cql3.Term.Raw;
@@ -33,6 +35,7 @@
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.service.ClientWarn;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
@@ -191,7 +194,15 @@ protected Restriction newEQRestriction(TableMetadata table, VariableSpecificatio
if (mapKey == null)
{
Term term = toTerm(toReceivers(columnDef), value, table.keyspace, boundNames);
- return new SingleColumnRestriction.EQRestriction(columnDef, term);
+ var analyzedIndex = IndexRegistry.obtain(table).supportsAnalyzedEq(columnDef);
+ if (analyzedIndex == null)
+ return new SingleColumnRestriction.EQRestriction(columnDef, term);
+
+ ClientWarn.instance.warn(String.format(AnalyzerEqOperatorSupport.EQ_RESTRICTION_ON_ANALYZED_WARNING,
+ columnDef.toString(),
+ analyzedIndex.getIndexMetadata().name),
+ columnDef);
+ return new SingleColumnRestriction.AnalyzerMatchesRestriction(columnDef, term);
}
List extends ColumnSpecification> receivers = toReceivers(columnDef);
Term entryKey = toTerm(Collections.singletonList(receivers.get(0)), mapKey, table.keyspace, boundNames);
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictions.java
index 092cc93cc7b7..0b33dfb16f69 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictions.java
@@ -189,7 +189,7 @@ public ClusteringColumnRestrictions.Builder addRestriction(Restriction restricti
SingleRestriction lastRestriction = restrictions.lastRestriction();
ColumnMetadata lastRestrictionStart = lastRestriction.getFirstColumn();
ColumnMetadata newRestrictionStart = newRestriction.getFirstColumn();
- restrictions.addRestriction(newRestriction, isDisjunction, indexRegistry);
+ restrictions.addRestriction(newRestriction, isDisjunction);
checkFalse(lastRestriction.isSlice() && newRestrictionStart.position() > lastRestrictionStart.position(),
"Clustering column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)",
@@ -203,7 +203,7 @@ public ClusteringColumnRestrictions.Builder addRestriction(Restriction restricti
}
else
{
- restrictions.addRestriction(newRestriction, isDisjunction, indexRegistry);
+ restrictions.addRestriction(newRestriction, isDisjunction);
}
return this;
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeySingleRestrictionSet.java b/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeySingleRestrictionSet.java
index 50e6ea481f09..faddd9d9ff06 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeySingleRestrictionSet.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeySingleRestrictionSet.java
@@ -189,7 +189,7 @@ public PartitionKeyRestrictions build(IndexRegistry indexRegistry, boolean isDis
if (restriction.isOnToken())
return buildWithTokens(restrictionSet, i, indexRegistry);
- restrictionSet.addRestriction((SingleRestriction) restriction, isDisjunction, indexRegistry);
+ restrictionSet.addRestriction((SingleRestriction) restriction, isDisjunction);
}
return buildPartitionKeyRestrictions(restrictionSet);
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/RestrictionSet.java b/src/java/org/apache/cassandra/cql3/restrictions/RestrictionSet.java
index 92736c0d298a..f9dd22756fe0 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/RestrictionSet.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/RestrictionSet.java
@@ -413,45 +413,35 @@ private Builder()
{
}
- public void addRestriction(SingleRestriction restriction, boolean isDisjunction, IndexRegistry indexRegistry)
+ public void addRestriction(SingleRestriction restriction, boolean isDisjunction)
{
List columnDefs = restriction.getColumnDefs();
if (isDisjunction)
{
// If this restriction is part of a disjunction query then we don't want
- // to merge the restrictions (if that is possible), we just add the
- // restriction to the set of restrictions for the column.
+ // to merge the restrictions, we just add the new restriction
addRestrictionForColumns(columnDefs, restriction, null);
}
else
{
- // In some special cases such as EQ in analyzed index we need to skip merging the restriction,
- // so we can send multiple EQ restrictions to the index.
- if (restriction.skipMerge(indexRegistry))
+ // ANDed together restrictions against the same columns should be merged.
+ Set existingRestrictions = getRestrictions(newRestrictions, columnDefs);
+ // Trivial case of no existing restrictions
+ if (existingRestrictions.isEmpty())
{
addRestrictionForColumns(columnDefs, restriction, null);
return;
}
+ // Since we merge new restrictions into the existing ones at each pass, there should only be
+ // at most one existing restriction across the same columnDefs
+ assert existingRestrictions.size() == 1 : existingRestrictions;
- // If this restriction isn't part of a disjunction then we need to get
- // the set of existing restrictions for the column and merge them with the
- // new restriction
- Set existingRestrictions = getRestrictions(newRestrictions, columnDefs);
-
- SingleRestriction merged = restriction;
- Set replacedRestrictions = new HashSet<>();
-
- for (SingleRestriction existing : existingRestrictions)
- {
- if (!existing.skipMerge(indexRegistry))
- {
- merged = existing.mergeWith(merged);
- replacedRestrictions.add(existing);
- }
- }
+ // Perform the merge
+ SingleRestriction existing = existingRestrictions.iterator().next();
+ var merged = existing.mergeWith(restriction);
- addRestrictionForColumns(merged.getColumnDefs(), merged, replacedRestrictions);
+ addRestrictionForColumns(merged.getColumnDefs(), merged, Set.of(existing));
}
}
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
index 1cde8cfc00e4..ced7f9d31cdb 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
@@ -198,24 +198,6 @@ public String toString()
return String.format("EQ(%s)", term);
}
- @Override
- public boolean skipMerge(IndexRegistry indexRegistry)
- {
- // We should skip merging this EQ if there is an analyzed index for this column that supports EQ,
- // so there can be multiple EQs for the same column.
-
- if (indexRegistry == null)
- return false;
-
- for (Index index : indexRegistry.listIndexes())
- {
- if (index.supportsExpression(columnDef, Operator.ANALYZER_MATCHES) &&
- index.supportsExpression(columnDef, Operator.EQ))
- return true;
- }
- return false;
- }
-
@Override
public SingleRestriction doMergeWith(SingleRestriction otherRestriction)
{
@@ -1402,10 +1384,12 @@ public String toString()
@Override
public SingleRestriction doMergeWith(SingleRestriction otherRestriction)
{
- if (!(otherRestriction.isAnalyzerMatches()))
+ if (!otherRestriction.isAnalyzerMatches())
throw invalidRequest(CANNOT_BE_MERGED_ERROR, columnDef.name);
- List otherValues = ((AnalyzerMatchesRestriction) otherRestriction).getValues();
+ List otherValues = otherRestriction instanceof AnalyzerMatchesRestriction
+ ? ((AnalyzerMatchesRestriction) otherRestriction).getValues()
+ : List.of(((EQRestriction) otherRestriction).term);
List newValues = new ArrayList<>(values.size() + otherValues.size());
newValues.addAll(values);
newValues.addAll(otherValues);
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/SingleRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/SingleRestriction.java
index 595451f812de..207e1786a114 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleRestriction.java
@@ -20,7 +20,6 @@
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.statements.Bound;
import org.apache.cassandra.db.MultiClusteringBuilder;
-import org.apache.cassandra.index.IndexRegistry;
/**
* A single restriction/clause on one or multiple column.
@@ -97,17 +96,6 @@ public default boolean isInclusive(Bound b)
return true;
}
- /**
- * Checks if this restriction shouldn't be merged with other restrictions.
- *
- * @param indexRegistry the index registry
- * @return {@code true} if this shouldn't be merged with other restrictions
- */
- default boolean skipMerge(IndexRegistry indexRegistry)
- {
- return false;
- }
-
/**
* Merges this restriction with the specified one.
*
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
index 7e92d3273077..4d1b4bb68a09 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@ -442,7 +442,7 @@ else if (def.isClusteringColumn() && nestingLevel == 0)
}
else
{
- nonPrimaryKeyRestrictionSet.addRestriction((SingleRestriction) restriction, element.isDisjunction(), indexRegistry);
+ nonPrimaryKeyRestrictionSet.addRestriction((SingleRestriction) restriction, element.isDisjunction());
}
}
}
@@ -699,7 +699,7 @@ else if (indexOrderings.size() == 1)
throw new InvalidRequestException(String.format(NON_CLUSTER_ORDERING_REQUIRES_INDEX_MESSAGE,
restriction.getFirstColumn()));
}
- receiver.addRestriction(restriction, false, indexRegistry);
+ receiver.addRestriction(restriction, false);
}
}
diff --git a/src/java/org/apache/cassandra/db/ReadCommand.java b/src/java/org/apache/cassandra/db/ReadCommand.java
index a289987a1adc..c8b38695e004 100644
--- a/src/java/org/apache/cassandra/db/ReadCommand.java
+++ b/src/java/org/apache/cassandra/db/ReadCommand.java
@@ -383,8 +383,6 @@ static Index.QueryPlan findIndexQueryPlan(TableMetadata table, RowFilter rowFilt
@Override
public void maybeValidateIndexes()
{
- IndexRegistry.obtain(metadata()).validate(rowFilter());
-
if (null != indexQueryPlan)
indexQueryPlan.validate(this);
}
diff --git a/src/java/org/apache/cassandra/index/IndexRegistry.java b/src/java/org/apache/cassandra/index/IndexRegistry.java
index cc6b0d103ea9..ec41a068d9ac 100644
--- a/src/java/org/apache/cassandra/index/IndexRegistry.java
+++ b/src/java/org/apache/cassandra/index/IndexRegistry.java
@@ -102,12 +102,6 @@ public Optional getBestIndexFor(RowFilter.Expression expression)
public void validate(PartitionUpdate update)
{
}
-
- @Override
- public void validate(RowFilter filter)
- {
- // no-op since it's an empty registry
- }
};
/**
@@ -295,12 +289,6 @@ public Optional getBestIndexFor(RowFilter.Expression expression)
public void validate(PartitionUpdate update)
{
}
-
- @Override
- public void validate(RowFilter filter)
- {
- // no-op since it's an empty registry
- }
};
default void registerIndex(Index index)
@@ -341,8 +329,6 @@ default Optional getAnalyzerFor(ColumnMetadata column, Operator
*/
void validate(PartitionUpdate update);
- void validate(RowFilter filter);
-
/**
* Returns the {@code IndexRegistry} associated to the specified table.
*
@@ -356,4 +342,15 @@ public static IndexRegistry obtain(TableMetadata table)
return table.isVirtual() ? EMPTY : Keyspace.openAndGetStore(table).indexManager;
}
+
+ default Index supportsAnalyzedEq(ColumnMetadata cm)
+ {
+ for (Index index : listIndexes())
+ {
+ if (index.supportsExpression(cm, Operator.ANALYZER_MATCHES) &&
+ index.supportsExpression(cm, Operator.EQ))
+ return index;
+ }
+ return null;
+ }
}
diff --git a/src/java/org/apache/cassandra/index/RowFilterValidator.java b/src/java/org/apache/cassandra/index/RowFilterValidator.java
deleted file mode 100644
index fb70fbfc1452..000000000000
--- a/src/java/org/apache/cassandra/index/RowFilterValidator.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.cassandra.index;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.StringJoiner;
-
-import org.apache.cassandra.cql3.Operator;
-import org.apache.cassandra.db.filter.RowFilter;
-import org.apache.cassandra.index.sai.analyzer.AnalyzerEqOperatorSupport;
-import org.apache.cassandra.schema.ColumnMetadata;
-import org.apache.cassandra.service.ClientWarn;
-
-/**
- * Class for validating the index-related aspects of a {@link RowFilter}, without considering what index is actually used.
- *
- * It will emit a client warning when a query has EQ restrictions on columns having an analyzed index.
- */
-class RowFilterValidator
-{
- private final Iterable allIndexes;
-
- private Set columns;
- private Set indexes;
-
- private RowFilterValidator(Iterable allIndexes)
- {
- this.allIndexes = allIndexes;
- }
-
- private void addEqRestriction(ColumnMetadata column)
- {
- for (Index index : allIndexes)
- {
- if (index.supportsExpression(column, Operator.EQ) &&
- index.supportsExpression(column, Operator.ANALYZER_MATCHES))
- {
- if (columns == null)
- columns = new HashSet<>();
- columns.add(column);
-
- if (indexes == null)
- indexes = new HashSet<>();
- indexes.add(index);
- }
- }
- }
-
- private void validate()
- {
- if (columns == null || indexes == null)
- return;
-
- StringJoiner columnNames = new StringJoiner(", ");
- StringJoiner indexNames = new StringJoiner(", ");
- columns.forEach(column -> columnNames.add(column.name.toString()));
- indexes.forEach(index -> indexNames.add(index.getIndexMetadata().name));
-
- ClientWarn.instance.warn(String.format(AnalyzerEqOperatorSupport.EQ_RESTRICTION_ON_ANALYZED_WARNING, columnNames, indexNames));
- }
-
- /**
- * Emits a client warning if the filter contains EQ restrictions on columns having an analyzed index.
- *
- * @param filter the filter to validate
- * @param indexes the existing indexes
- */
- public static void validate(RowFilter filter, Iterable indexes)
- {
- RowFilterValidator validator = new RowFilterValidator(indexes);
- validate(filter.root(), validator);
- validator.validate();
- }
-
- private static void validate(RowFilter.FilterElement element, RowFilterValidator validator)
- {
- for (RowFilter.Expression expression : element.expressions())
- {
- if (expression.operator() == Operator.EQ)
- validator.addEqRestriction(expression.column());
- }
-
- for (RowFilter.FilterElement child : element.children())
- {
- validate(child, validator);
- }
- }
-}
diff --git a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
index 9954d1b3ee1c..21d633b30a39 100644
--- a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
+++ b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
@@ -1277,12 +1277,6 @@ public void validate(PartitionUpdate update) throws InvalidRequestException
index.validate(update);
}
- @Override
- public void validate(RowFilter filter)
- {
- RowFilterValidator.validate(filter, indexes.values());
- }
-
/*
* IndexRegistry methods
*/
diff --git a/src/java/org/apache/cassandra/service/ClientWarn.java b/src/java/org/apache/cassandra/service/ClientWarn.java
index 5a6a878681e1..38570a06d2b8 100644
--- a/src/java/org/apache/cassandra/service/ClientWarn.java
+++ b/src/java/org/apache/cassandra/service/ClientWarn.java
@@ -18,7 +18,9 @@
package org.apache.cassandra.service;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import io.netty.util.concurrent.FastThreadLocal;
import org.apache.cassandra.concurrent.ExecutorLocal;
@@ -45,10 +47,18 @@ public void set(State value)
}
public void warn(String text)
+ {
+ warn(text, null);
+ }
+
+ /**
+ * Issue the given warning if this is the first time `key` is seen.
+ */
+ public void warn(String text, Object key)
{
State state = warnLocal.get();
if (state != null)
- state.add(text);
+ state.add(text, key);
}
public void captureWarnings()
@@ -72,11 +82,16 @@ public void resetWarnings()
public static class State
{
private final List warnings = new ArrayList<>();
+ private final Set