blerer commented on code in PR #3095:
URL: https://github.com/apache/cassandra/pull/3095#discussion_r1589365937


##########
src/java/org/apache/cassandra/cql3/restrictions/MergedRestriction.java:
##########
@@ -0,0 +1,345 @@
+/*
+ * 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.restrictions;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.RangeSet;
+
+import org.apache.cassandra.cql3.Operator;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.functions.Function;
+import org.apache.cassandra.db.filter.RowFilter;
+import org.apache.cassandra.index.Index;
+import org.apache.cassandra.index.IndexRegistry;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+import static 
org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
+
+/**
+ * A {@code SingleRestriction} which is the result of merging multiple {@code 
SimpleRestriction} applying to
+ * the same set of columns.
+ * <p>For multi-columns restrictions it is possible to merge restrictions 
applying to different
+ * sets of columns as long as one is a superset of the other and both start 
with the same set of columns.</p>
+ */
+public final class MergedRestriction implements SingleRestriction
+{
+    /**
+     * The columns to which the restrictions apply.
+     */
+    private final List<ColumnMetadata> columns;
+
+    /**
+     * The restrictions composing this restriction
+     */
+    private final List<SimpleRestriction> restrictions;
+
+    private final boolean isOnToken;
+
+    private final boolean isSlice;
+
+    private final boolean isMultiColumn;
+
+    /**
+     * The number of restrictions that require {@code CONTAINS}, {@code 
CONTAINS_KEY} and Map equality restrictions.
+     */
+    private final int containsCount;
+
+    public MergedRestriction(SingleRestriction restriction,
+                             SimpleRestriction other)
+    {
+        assert restriction.isOnToken() == other.isOnToken();
+
+        this.columns = restriction.columns().size() < other.columns().size()
+                     ? other.columns()
+                     : restriction.columns();
+
+        ImmutableList.Builder<SimpleRestriction> builder = 
ImmutableList.builder();
+        int containsCount = 0;
+        if (restriction instanceof MergedRestriction)
+        {
+            MergedRestriction composite = (MergedRestriction) restriction;
+
+            for (SimpleRestriction r : composite.restrictions)
+            {
+                validate(r, other);
+            }
+
+            builder.addAll(composite.restrictions);
+            containsCount = composite.containsCount;
+        }
+        else
+        {
+            SimpleRestriction r = (SimpleRestriction) restriction;
+            validate(r, other);
+            builder.add(r);
+            if (isContains(r))
+                containsCount++;
+        }
+        builder.add(other);
+        if (isContains(restriction))
+            containsCount++;
+
+        this.restrictions = builder.build();
+        this.isOnToken = restriction.isOnToken();
+        this.isSlice = restriction.isSlice() && other.isSlice();
+        this.isMultiColumn = restriction.isMultiColumn() || 
other.isMultiColumn();
+        this.containsCount = containsCount;
+    }
+
+    @Override
+    public boolean isOnToken()
+    {
+        return isOnToken;
+    }
+
+    @Override
+    public boolean isMultiColumn()
+    {
+        return isMultiColumn;
+    }
+
+    private static void validate(SimpleRestriction restriction, 
SimpleRestriction other)
+    {
+        checkOperator(restriction);
+        checkOperator(other);
+
+        if (restriction.isContains() != other.isContains())
+            throw invalidRequest("Collection column %s can only be restricted 
by CONTAINS, CONTAINS KEY," +
+                                 " or map-entry equality if it already 
restricted by one of those",
+                                 restriction.firstColumn().name);
+
+        if (restriction.isSlice() && other.isSlice())
+        {
+            ColumnMetadata firstColumn = restriction.firstColumn();
+            ColumnMetadata otherFirstColumn = other.firstColumn();
+            if (!firstColumn.equals(otherFirstColumn))
+            {
+                ColumnMetadata column = firstColumn.position() > 
otherFirstColumn.position() ? firstColumn
+                                                                               
              : otherFirstColumn;
+
+                throw invalidRequest("Column \"%s\" cannot be restricted by 
two inequalities not starting with the same column",
+                                     column.name);
+            }
+
+            if ((restriction.operator() == Operator.GT || 
restriction.operator() == Operator.GTE) &&
+                    (other.operator() == Operator.GT || other.operator() == 
Operator.GTE))
+            {
+                throw invalidRequest("More than one restriction was found for 
the start bound on %s",
+                                     
toCQLString(getColumnsInCommons(restriction, other)));
+            }
+
+            if ((restriction.operator() == Operator.LT || 
restriction.operator() == Operator.LTE) &&
+                    (other.operator() == Operator.LT || other.operator() == 
Operator.LTE))
+            {
+                throw invalidRequest("More than one restriction was found for 
the end bound on %s",
+                                     
toCQLString(getColumnsInCommons(restriction, other)));
+            }
+        }
+    }
+
+    private static void checkOperator(SimpleRestriction restriction)
+    {
+        if (restriction.isColumnLevel() || restriction.isOnToken())
+        {
+            if (restriction.isEQ())
+                throw invalidRequest("%s cannot be restricted by more than one 
relation if it includes an Equal",
+                                      toCQLString(restriction.columns()));
+
+            if (restriction.isIN())
+                throw invalidRequest("%s cannot be restricted by more than one 
relation if it includes a IN",
+                                     toCQLString(restriction.columns()));
+            if (restriction.isANN())
+                throw invalidRequest("%s cannot be restricted by more than one 
relation in an ANN ordering",
+                                     toCQLString(restriction.columns()));
+        }
+    }
+
+    /**
+     * Returns the columns that are specified within the 2 {@code 
Restrictions}.
+     *
+     * @param restriction the first restriction
+     * @param other the other restriction
+     * @return the columns that are specified within the 2 {@code 
Restrictions}.
+     */
+    private static Set<ColumnMetadata> getColumnsInCommons(Restriction 
restriction, Restriction other)
+    {
+        Set<ColumnMetadata> commons = new HashSet<>(restriction.columns());
+        commons.retainAll(other.columns());
+        return commons;
+    }
+
+    private static String toCQLString(Iterable<ColumnMetadata> columns)
+    {
+        StringBuilder builder = new StringBuilder();
+        for (ColumnMetadata columnMetadata : columns)
+        {
+            if (builder.length() != 0)
+                builder.append(" ,");
+            builder.append(columnMetadata.name.toCQLString());
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Checks if the restriction operator is a CONTAINS, CONTAINS_KEY or is an 
equality on a map element.
+     * @param restriction the restriction to check
+     * @return {@code true} if the restriction operator is one of the contains 
operations, {@code false} otherwise.
+     */
+    private boolean isContains(SingleRestriction restriction)
+    {
+        return restriction instanceof SimpleRestriction && 
((SimpleRestriction) restriction).isContains();
+    }
+
+    @Override
+    public boolean isEQ() {
+        return false; // For the moment we do not support merging EQ 
restriction with anything else.
+    }
+
+    @Override
+    public boolean isIN()
+    {
+        return false; // For the moment we do not support merging IN 
restriction with anything else.
+    }
+
+    @Override
+    public boolean isANN() {
+        return false; // For the moment we do not support merging ANN 
restriction with anything else.
+    }
+
+    @Override
+    public boolean isSlice()
+    {
+        return isSlice;
+    }
+
+    @Override
+    public boolean isColumnLevel() {
+        return false;
+    }
+
+    @Override
+    public ColumnMetadata firstColumn()
+    {
+        return columns.get(0);
+    }
+
+    @Override
+    public ColumnMetadata lastColumn()
+    {
+        return columns.get(columns.size() - 1);
+    }
+
+    @Override
+    public List<ColumnMetadata> columns()
+    {
+        return columns;
+    }
+
+    @Override
+    public void addFunctionsTo(List<Function> functions)
+    {
+        for (int i = 0, m = restrictions.size(); i < m; i++)
+        {
+            restrictions.get(i).addFunctionsTo(functions);
+        }
+    }
+
+    @Override
+    public boolean needsFilteringOrIndexing()
+    {
+        for (int i = 0, m = restrictions.size(); i < m; i++)
+        {
+            if (restrictions.get(i).needsFilteringOrIndexing())
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean needsFiltering(Index.Group indexGroup)
+    {
+        // multiple contains might require filtering on some indexes, since 
that is equivalent to a disjunction (or)
+        boolean hasMultipleContains = containsCount > 1;
+
+        for (Index index : indexGroup.getIndexes())
+        {
+            if (isSupportedBy(index) && !(hasMultipleContains && 
index.filtersMultipleContains()))
+                return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public Index findSupportingIndex(Iterable<Index> indexes)
+    {
+        for (int i = 0, m = restrictions.size(); i < m; i++)
+        {
+            Index index = restrictions.get(i).findSupportingIndex(indexes);
+            if (index != null)
+                return index;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isSupportedBy(Index index)
+    {
+        for (SingleRestriction restriction : restrictions)
+        {
+            if (restriction.isSupportedBy(index))
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public List<ClusteringElements> values(QueryOptions options)
+    {
+        List<ClusteringElements> values = restrictions.get(0).values(options);
+        for (int i = 1, m = restrictions.size(); i < m; i++)
+        {
+            values.retainAll(restrictions.get(i).values(options));
+        }
+        return values;

Review Comment:
   I agree the naming is confusing. It is the consequence of the fact that we 
use `MultiCBuilder` and `Clustering` in the code to compute ByteBuffers for 
partition keys and token.



-- 
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: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to