Return-Path: X-Original-To: apmail-cassandra-commits-archive@www.apache.org Delivered-To: apmail-cassandra-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id F2197C858 for ; Wed, 7 Jan 2015 20:06:38 +0000 (UTC) Received: (qmail 34708 invoked by uid 500); 7 Jan 2015 20:06:40 -0000 Delivered-To: apmail-cassandra-commits-archive@cassandra.apache.org Received: (qmail 34669 invoked by uid 500); 7 Jan 2015 20:06:40 -0000 Mailing-List: contact commits-help@cassandra.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cassandra.apache.org Delivered-To: mailing list commits@cassandra.apache.org Received: (qmail 34657 invoked by uid 99); 7 Jan 2015 20:06:40 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 07 Jan 2015 20:06:40 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id B23939D26B3; Wed, 7 Jan 2015 20:06:39 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: tylerhobbs@apache.org To: commits@cassandra.apache.org Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: cassandra git commit: Allow mixing token and partition key restrictions Date: Wed, 7 Jan 2015 20:06:39 +0000 (UTC) Repository: cassandra Updated Branches: refs/heads/trunk 0a80fe4b5 -> 493859bf6 Allow mixing token and partition key restrictions Patch by Benjamin Lerer; reviewed by Tyler Hobbs for CASSANDRA-7016 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/493859bf Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/493859bf Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/493859bf Branch: refs/heads/trunk Commit: 493859bf617ac80f560d02ad6d471aefd6a0ef91 Parents: 0a80fe4 Author: blerer Authored: Wed Jan 7 14:05:38 2015 -0600 Committer: Tyler Hobbs Committed: Wed Jan 7 14:06:28 2015 -0600 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../apache/cassandra/cql3/TokenRelation.java | 4 +- .../AbstractPrimaryKeyRestrictions.java | 12 + .../restrictions/MultiColumnRestriction.java | 4 +- .../SingleColumnPrimaryKeyRestrictions.java | 16 +- .../restrictions/StatementRestrictions.java | 5 +- .../cql3/restrictions/TokenFilter.java | 237 +++++++++++++++++++ .../cql3/restrictions/TokenRestriction.java | 57 +++-- .../cql3/SelectWithTokenFunctionTest.java | 139 ++++++++++- 9 files changed, 439 insertions(+), 36 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 8ccc014..9f946a3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.0 + * Allow mixing token and partition key restrictions (CASSANDRA-7016) * Support index key/value entries on map collections (CASSANDRA-8473) * Modernize schema tables (CASSANDRA-8261) * Support for user-defined aggregation functions (CASSANDRA-8053) http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/TokenRelation.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/TokenRelation.java b/src/java/org/apache/cassandra/cql3/TokenRelation.java index d1bd265..5896fae 100644 --- a/src/java/org/apache/cassandra/cql3/TokenRelation.java +++ b/src/java/org/apache/cassandra/cql3/TokenRelation.java @@ -69,7 +69,7 @@ public final class TokenRelation extends Relation { List columnDefs = getColumnDefinitions(cfm); Term term = toTerm(toReceivers(cfm, columnDefs), value, cfm.ksName, boundNames); - return new TokenRestriction.EQ(columnDefs, term); + return new TokenRestriction.EQ(cfm.getKeyValidatorAsCType(), columnDefs, term); } @Override @@ -86,7 +86,7 @@ public final class TokenRelation extends Relation { List columnDefs = getColumnDefinitions(cfm); Term term = toTerm(toReceivers(cfm, columnDefs), value, cfm.ksName, boundNames); - return new TokenRestriction.Slice(columnDefs, bound, inclusive, term); + return new TokenRestriction.Slice(cfm.getKeyValidatorAsCType(), columnDefs, bound, inclusive, term); } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java index f137a77..0107603 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java @@ -17,11 +17,23 @@ */ package org.apache.cassandra.cql3.restrictions; +import org.apache.cassandra.db.composites.CType; + /** * Base class for PrimaryKeyRestrictions. */ abstract class AbstractPrimaryKeyRestrictions extends AbstractRestriction implements PrimaryKeyRestrictions { + /** + * The composite type. + */ + protected final CType ctype; + + public AbstractPrimaryKeyRestrictions(CType ctype) + { + this.ctype = ctype; + } + @Override public final boolean isEmpty() { http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java index e3b3c4c..2d6deeb 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java @@ -43,8 +43,6 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.invalidReq public abstract class MultiColumnRestriction extends AbstractPrimaryKeyRestrictions { - protected final CType ctype; - /** * The columns to which the restriction apply. */ @@ -52,7 +50,7 @@ public abstract class MultiColumnRestriction extends AbstractPrimaryKeyRestricti public MultiColumnRestriction(CType ctype, List columnDefs) { - this.ctype = ctype; + super(ctype); this.columnDefs = columnDefs; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java index 5c8386e..d2a3885 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java @@ -43,11 +43,6 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue; final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestrictions { /** - * The composite type. - */ - private final CType ctype; - - /** * The restrictions. */ private final SingleColumnRestrictions restrictions; @@ -74,7 +69,7 @@ final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestric public SingleColumnPrimaryKeyRestrictions(CType ctype) { - this.ctype = ctype; + super(ctype); this.restrictions = new SingleColumnRestrictions(); this.eq = true; } @@ -82,8 +77,8 @@ final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestric private SingleColumnPrimaryKeyRestrictions(SingleColumnPrimaryKeyRestrictions primaryKeyRestrictions, SingleColumnRestriction restriction) throws InvalidRequestException { + super(primaryKeyRestrictions.ctype); this.restrictions = primaryKeyRestrictions.restrictions.addRestriction(restriction); - this.ctype = primaryKeyRestrictions.ctype; if (!primaryKeyRestrictions.isEmpty()) { @@ -166,9 +161,10 @@ final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestric if (restriction.isOnToken()) { - checkTrue(isEmpty(), "Columns \"%s\" cannot be restricted by both a normal relation and a token relation", - ((TokenRestriction) restriction).getColumnNamesAsString()); - return (PrimaryKeyRestrictions) restriction; + if (isEmpty()) + return (PrimaryKeyRestrictions) restriction; + + return new TokenFilter(this, (TokenRestriction) restriction); } return new SingleColumnPrimaryKeyRestrictions(this, (SingleColumnRestriction) restriction); http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java index 60c7465..598478c 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java @@ -259,10 +259,9 @@ public final class StatementRestrictions // If a component of the partition key is restricted by a relation, all preceding // components must have a EQ. Only the last partition key component can be in IN relation. if (partitionKeyRestrictions.isOnToken()) - { isKeyRange = true; - } - else if (hasPartitionKeyUnrestrictedComponents()) + + if (hasPartitionKeyUnrestrictedComponents()) { if (!partitionKeyRestrictions.isEmpty()) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java b/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java new file mode 100644 index 0000000..4b5383b --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java @@ -0,0 +1,237 @@ +/* + * 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.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.BoundType; +import com.google.common.collect.ImmutableRangeSet; +import com.google.common.collect.Range; +import com.google.common.collect.RangeSet; + +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.statements.Bound; +import org.apache.cassandra.db.composites.Composite; +import org.apache.cassandra.dht.IPartitioner; +import org.apache.cassandra.dht.Token; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.service.StorageService; + +import static org.apache.cassandra.cql3.statements.Bound.END; +import static org.apache.cassandra.cql3.statements.Bound.START; + +/** + * Restriction decorator used to merge non-token restriction and token restriction on partition keys. + */ +final class TokenFilter extends ForwardingPrimaryKeyRestrictions +{ + /** + * The decorated restriction + */ + private PrimaryKeyRestrictions restrictions; + + /** + * The restriction on the token + */ + private TokenRestriction tokenRestriction; + + /** + * The partitioner + */ + private static final IPartitioner partitioner = StorageService.getPartitioner(); + + @Override + protected PrimaryKeyRestrictions getDelegate() + { + return restrictions; + } + + @Override + public boolean isOnToken() + { + // if all partition key columns have non-token restrictions, we can simply use the token range to filter + // those restrictions and then ignore the token range + return restrictions.size() < tokenRestriction.size(); + } + + public TokenFilter(PrimaryKeyRestrictions restrictions, TokenRestriction tokenRestriction) + { + this.restrictions = restrictions; + this.tokenRestriction = tokenRestriction; + } + + @Override + public List values(QueryOptions options) throws InvalidRequestException + { + return filter(restrictions.values(options), options); + } + + @Override + public List valuesAsComposites(QueryOptions options) throws InvalidRequestException + { + throw new UnsupportedOperationException(); + } + + @Override + public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException + { + if (restriction.isOnToken()) + return new TokenFilter(restrictions, (TokenRestriction) tokenRestriction.mergeWith(restriction)); + + return new TokenFilter(super.mergeWith(restriction), tokenRestriction); + } + + @Override + public boolean isInclusive(Bound bound) + { + return tokenRestriction.isInclusive(bound); + } + + @Override + public boolean hasBound(Bound b) + { + return tokenRestriction.hasBound(b); + } + + @Override + public List bounds(Bound bound, QueryOptions options) throws InvalidRequestException + { + return tokenRestriction.bounds(bound, options); + } + + @Override + public List boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException + { + return tokenRestriction.boundsAsComposites(bound, options); + } + + /** + * Filter the values returned by the restriction. + * + * @param values the values returned by the decorated restriction + * @param options the query options + * @return the values matching the token restriction + * @throws InvalidRequestException if the request is invalid + */ + private List filter(List values, QueryOptions options) throws InvalidRequestException + { + RangeSet rangeSet = tokenRestriction.isSlice() ? toRangeSet(tokenRestriction, options) + : toRangeSet(tokenRestriction.values(options)); + + return filterWithRangeSet(rangeSet, values); + } + + /** + * Filter out the values for which the tokens are not included within the specified range. + * + * @param tokens the tokens range + * @param values the restricted values + * @return the values for which the tokens are not included within the specified range. + */ + private static List filterWithRangeSet(RangeSet tokens, List values) + { + List remaining = new ArrayList<>(); + + for (ByteBuffer value : values) + { + Token token = partitioner.getToken(value); + + if (!tokens.contains(token)) + continue; + + remaining.add(value); + } + return remaining; + } + + /** + * Converts the specified list into a range set. + * + * @param buffers the token restriction values + * @return the range set corresponding to the specified list + */ + private static RangeSet toRangeSet(List buffers) + { + ImmutableRangeSet.Builder builder = ImmutableRangeSet.builder(); + + for (ByteBuffer buffer : buffers) + builder.add(Range.singleton(deserializeToken(buffer))); + + return builder.build(); + } + + /** + * Converts the specified slice into a range set. + * + * @param slice the slice to convert + * @param options the query option + * @return the range set corresponding to the specified slice + * @throws InvalidRequestException if the request is invalid + */ + private static RangeSet toRangeSet(TokenRestriction slice, QueryOptions options) throws InvalidRequestException + { + if (slice.hasBound(START)) + { + Token start = deserializeToken(slice.bounds(START, options).get(0)); + + BoundType startBoundType = toBoundType(slice.isInclusive(START)); + BoundType endBoundType = toBoundType(slice.isInclusive(END)); + + if (slice.hasBound(END)) + { + Token end = deserializeToken(slice.bounds(END, options).get(0)); + + if (start.equals(end) && (BoundType.OPEN == startBoundType || BoundType.OPEN == endBoundType)) + return ImmutableRangeSet.of(); + + if (start.compareTo(end) <= 0) + return ImmutableRangeSet.of(Range.range(start, + startBoundType, + end, + endBoundType)); + + return ImmutableRangeSet. builder() + .add(Range.upTo(end, endBoundType)) + .add(Range.downTo(start, startBoundType)) + .build(); + } + return ImmutableRangeSet.of(Range.downTo(start, + startBoundType)); + } + Token end = deserializeToken(slice.bounds(END, options).get(0)); + return ImmutableRangeSet.of(Range.upTo(end, toBoundType(slice.isInclusive(END)))); + } + + /** + * Deserializes the token corresponding to the specified buffer. + * + * @param buffer the buffer + * @return the token corresponding to the specified buffer + */ + private static Token deserializeToken(ByteBuffer buffer) + { + return partitioner.getTokenFactory().fromByteArray(buffer); + } + + private static BoundType toBoundType(boolean inclusive) + { + return inclusive ? BoundType.CLOSED : BoundType.OPEN; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java index 85d614e..8d63fea 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java @@ -29,6 +29,7 @@ import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.Term; import org.apache.cassandra.cql3.statements.Bound; import org.apache.cassandra.db.IndexExpression; +import org.apache.cassandra.db.composites.CType; import org.apache.cassandra.db.composites.Composite; import org.apache.cassandra.db.index.SecondaryIndexManager; import org.apache.cassandra.exceptions.InvalidRequestException; @@ -48,10 +49,12 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions /** * Creates a new TokenRestriction that apply to the specified columns. * + * @param ctype the composite type * @param columnDefs the definition of the columns to which apply the token restriction */ - public TokenRestriction(List columnDefs) + public TokenRestriction(CType ctype, List columnDefs) { + super(ctype); this.columnDefs = columnDefs; } @@ -101,13 +104,43 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions return Joiner.on(", ").join(ColumnDefinition.toIdentifiers(columnDefs)); } + @Override + public final PrimaryKeyRestrictions mergeWith(Restriction otherRestriction) throws InvalidRequestException + { + if (!otherRestriction.isOnToken()) + return new TokenFilter(toPrimaryKeyRestriction(otherRestriction), this); + + return doMergeWith((TokenRestriction) otherRestriction); + } + + /** + * Merges this restriction with the specified TokenRestriction. + * @param otherRestriction the TokenRestriction to merge with. + */ + protected abstract PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction) throws InvalidRequestException; + + /** + * Converts the specified restriction into a PrimaryKeyRestrictions. + * + * @param restriction the restriction to convert + * @return a PrimaryKeyRestrictions + * @throws InvalidRequestException if a problem occurs while converting the restriction + */ + private PrimaryKeyRestrictions toPrimaryKeyRestriction(Restriction restriction) throws InvalidRequestException + { + if (restriction instanceof PrimaryKeyRestrictions) + return (PrimaryKeyRestrictions) restriction; + + return new SingleColumnPrimaryKeyRestrictions(ctype).mergeWith(restriction); + } + public static final class EQ extends TokenRestriction { private final Term value; - public EQ(List columnDefs, Term value) + public EQ(CType ctype, List columnDefs, Term value) { - super(columnDefs); + super(ctype, columnDefs); this.value = value; } @@ -124,7 +157,7 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions } @Override - public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException + protected PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction) throws InvalidRequestException { throw invalidRequest("%s cannot be restricted by more than one relation if it includes an Equal", Joiner.on(", ").join(ColumnDefinition.toIdentifiers(columnDefs))); @@ -141,9 +174,9 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions { private final TermSlice slice; - public Slice(List columnDefs, Bound bound, boolean inclusive, Term term) + public Slice(CType ctype, List columnDefs, Bound bound, boolean inclusive, Term term) { - super(columnDefs); + super(ctype, columnDefs); slice = TermSlice.newInstance(bound, inclusive, term); } @@ -185,13 +218,9 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions } @Override - public PrimaryKeyRestrictions mergeWith(Restriction otherRestriction) + protected PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction) throws InvalidRequestException { - if (!otherRestriction.isOnToken()) - throw invalidRequest("Columns \"%s\" cannot be restricted by both a normal relation and a token relation", - getColumnNamesAsString()); - if (!otherRestriction.isSlice()) throw invalidRequest("Columns \"%s\" cannot be restricted by both an equality and an inequality relation", getColumnNamesAsString()); @@ -206,7 +235,7 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions throw invalidRequest("More than one restriction was found for the end bound on %s", getColumnNamesAsString()); - return new Slice(columnDefs, slice.merge(otherSlice.slice)); + return new Slice(ctype, columnDefs, slice.merge(otherSlice.slice)); } @Override @@ -215,9 +244,9 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions return String.format("SLICE%s", slice); } - private Slice(List columnDefs, TermSlice slice) + private Slice(CType ctype, List columnDefs, TermSlice slice) { - super(columnDefs); + super(ctype, columnDefs); this.slice = slice; } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java b/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java index 39b62e3..a365c09 100644 --- a/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java +++ b/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java @@ -17,6 +17,8 @@ */ package org.apache.cassandra.cql3; +import java.util.Arrays; + import org.junit.Test; public class SelectWithTokenFunctionTest extends CQLTester @@ -30,10 +32,6 @@ public class SelectWithTokenFunctionTest extends CQLTester assertRows(execute("SELECT * FROM %s WHERE token(a) >= token(?)", 0), row(0, "a")); assertRows(execute("SELECT * FROM %s WHERE token(a) >= token(?) and token(a) < token(?)", 0, 1), row(0, "a")); assertInvalid("SELECT * FROM %s WHERE token(a) > token(?)", "a"); - assertInvalidMessage("Columns \"a\" cannot be restricted by both a normal relation and a token relation", - "SELECT * FROM %s WHERE token(a) > token(?) AND a = ?", 1, 1); - assertInvalidMessage("Columns \"a\" cannot be restricted by both a normal relation and a token relation", - "SELECT * FROM %s WHERE a = ? and token(a) > token(?)", 1, 1); assertInvalidMessage("The token() function must contains only partition key components", "SELECT * FROM %s WHERE token(a, b) >= token(?, ?)", "b", 0); assertInvalidMessage("More than one restriction was found for the start bound on a", @@ -88,4 +86,137 @@ public class SelectWithTokenFunctionTest extends CQLTester assertInvalidMessage("The token() function must be applied to all partition key components or none of them", "SELECT * FROM %s WHERE token(a) > token(?, ?) and token(b) > token(?, ?)", 0, "a", 0, "a"); } + + @Test + public void testSingleColumnPartitionKeyWithTokenNonTokenRestrictionsMix() throws Throwable + { + createTable("CREATE TABLE %s (a int primary key, b int)"); + + execute("INSERT INTO %s (a, b) VALUES (0, 0);"); + execute("INSERT INTO %s (a, b) VALUES (1, 1);"); + execute("INSERT INTO %s (a, b) VALUES (2, 2);"); + execute("INSERT INTO %s (a, b) VALUES (3, 3);"); + execute("INSERT INTO %s (a, b) VALUES (4, 4);"); + assertRows(execute("SELECT * FROM %s WHERE a IN (?, ?);", 1, 3), + row(1, 1), + row(3, 3)); + assertRows(execute("SELECT * FROM %s WHERE token(a)> token(?) and token(a) <= token(?);", 1, 3), + row(2, 2), + row(3, 3)); + assertRows(execute("SELECT * FROM %s WHERE token(a)= token(2);"), + row(2, 2)); + assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a IN (?, ?);", + 1, 3, 1, 3), + row(3, 3)); + assertRows(execute("SELECT * FROM %s WHERE token(a) < token(?) AND token(a) >= token(?) AND a IN (?, ?);", + 1, 3, 1, 3), + row(3, 3)); + assertInvalidMessage("Only EQ and IN relation are supported on the partition key (unless you use the token() function)", + "SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a > ?;", 1, 3, 1); + + assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a IN ?;", + 1, 3, Arrays.asList(1, 3)), + row(3, 3)); + assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND a = ?;", 1, 3), + row(3, 3)); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND token(a) > token(?);", 3, 1), + row(3, 3)); + assertEmpty(execute("SELECT * FROM %s WHERE token(a) > token(?) AND a = ?;", 3, 1)); + assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND token(a) > token(?);", 1, 3)); + assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND a IN (?, ?);", 2, 1, 3), + row(3, 3)); + assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) < token(?) AND a IN (?, ?) ;", 2, 5, 1, 3), + row(3, 3)); + assertRows(execute("SELECT * FROM %s WHERE a IN (?, ?) AND token(a) > token(?) AND token(a) < token(?);", 1, 3, 2, 5), + row(3, 3)); + assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND a IN (?, ?) AND token(a) < token(?);", 2, 1, 3, 5), + row(3, 3)); + assertEmpty(execute("SELECT * FROM %s WHERE a IN (?, ?) AND token(a) > token(?);", 1, 3, 3)); + assertRows(execute("SELECT * FROM %s WHERE token(a) <= token(?) AND a = ?;", 2, 2), + row(2, 2)); + assertEmpty(execute("SELECT * FROM %s WHERE token(a) <= token(?) AND a = ?;", 2, 3)); + assertEmpty(execute("SELECT * FROM %s WHERE token(a) = token(?) AND a = ?;", 2, 3)); + assertRows(execute("SELECT * FROM %s WHERE token(a) >= token(?) AND token(a) <= token(?) AND a = ?;", 2, 2, 2), + row(2, 2)); + assertEmpty(execute("SELECT * FROM %s WHERE token(a) >= token(?) AND token(a) < token(?) AND a = ?;", 2, 2, 2)); + assertEmpty(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a = ?;", 2, 2, 2)); + assertEmpty(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) < token(?) AND a = ?;", 2, 2, 2)); + } + + @Test + public void testMultiColumnPartitionKeyWithTokenNonTokenRestrictionsMix() throws Throwable + { + createTable("CREATE TABLE %s (a int, b int, c int, primary key((a, b)))"); + + execute("INSERT INTO %s (a, b, c) VALUES (0, 0, 0);"); + execute("INSERT INTO %s (a, b, c) VALUES (0, 1, 1);"); + execute("INSERT INTO %s (a, b, c) VALUES (0, 2, 2);"); + execute("INSERT INTO %s (a, b, c) VALUES (1, 0, 3);"); + execute("INSERT INTO %s (a, b, c) VALUES (1, 1, 4);"); + + assertRows(execute("SELECT * FROM %s WHERE token(a, b) > token(?, ?);", 0, 0), + row(0, 1, 1), + row(0, 2, 2), + row(1, 0, 3), + row(1, 1, 4)); + + assertRows(execute("SELECT * FROM %s WHERE token(a, b) > token(?, ?) AND a = ? AND b IN (?, ?);", + 0, 0, 1, 0, 1), + row(1, 0, 3), + row(1, 1, 4)); + + assertRows(execute("SELECT * FROM %s WHERE a = ? AND token(a, b) > token(?, ?) AND b IN (?, ?);", + 1, 0, 0, 0, 1), + row(1, 0, 3), + row(1, 1, 4)); + + assertRows(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) > token(?, ?) AND a = ?;", + 0, 1, 0, 0, 1), + row(1, 0, 3), + row(1, 1, 4)); + + assertEmpty(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) > token(?, ?) AND token(a, b) < token(?, ?) AND a = ?;", + 0, 1, 0, 0, 0, 0, 1)); + + assertEmpty(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) > token(?, ?) AND token(a, b) <= token(?, ?) AND a = ?;", + 0, 1, 0, 0, 0, 0, 1)); + + assertEmpty(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) >= token(?, ?) AND token(a, b) < token(?, ?) AND a = ?;", + 0, 1, 0, 0, 0, 0, 1)); + + assertEmpty(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) = token(?, ?) AND a = ?;", + 0, 1, 0, 0, 1)); + + assertInvalidMessage("Partition key parts: b must be restricted as other parts are", + "SELECT * FROM %s WHERE token(a, b) > token(?, ?) AND a = ?;", 0, 0, 1); + } + + @Test + public void testMultiColumnPartitionKeyWithIndexAndTokenNonTokenRestrictionsMix() throws Throwable + { + createTable("CREATE TABLE %s (a int, b int, c int, primary key((a, b)))"); + createIndex("CREATE INDEX ON %s(b)"); + createIndex("CREATE INDEX ON %s(c)"); + + execute("INSERT INTO %s (a, b, c) VALUES (0, 0, 0);"); + execute("INSERT INTO %s (a, b, c) VALUES (0, 1, 1);"); + execute("INSERT INTO %s (a, b, c) VALUES (0, 2, 2);"); + execute("INSERT INTO %s (a, b, c) VALUES (1, 0, 3);"); + execute("INSERT INTO %s (a, b, c) VALUES (1, 1, 4);"); + + assertRows(execute("SELECT * FROM %s WHERE b = ?;", 1), + row(0, 1, 1), + row(1, 1, 4)); + + assertRows(execute("SELECT * FROM %s WHERE token(a, b) > token(?, ?) AND b = ?;", 0, 0, 1), + row(0, 1, 1), + row(1, 1, 4)); + + assertRows(execute("SELECT * FROM %s WHERE b = ? AND token(a, b) > token(?, ?);", 1, 0, 0), + row(0, 1, 1), + row(1, 1, 4)); + + assertRows(execute("SELECT * FROM %s WHERE b = ? AND token(a, b) > token(?, ?) and c = ? ALLOW FILTERING;", 1, 0, 0, 4), + row(1, 1, 4)); + } }