You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by bl...@apache.org on 2015/09/04 21:14:22 UTC
[4/5] cassandra git commit: Allow range deletions in CQL
Allow range deletions in CQL
patch by Benjamin Lerer; reviewed by Joshua McKenzie for CASSANDRA-6237
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/2e3727e3
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/2e3727e3
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/2e3727e3
Branch: refs/heads/trunk
Commit: 2e3727e3ff682dbab734aaccf641360bc62a8561
Parents: 8f249a6
Author: blerer <be...@datastax.com>
Authored: Fri Sep 4 21:10:29 2015 +0200
Committer: blerer <be...@datastax.com>
Committed: Fri Sep 4 21:10:29 2015 +0200
----------------------------------------------------------------------
NEWS.txt | 6 +-
doc/cql3/CQL.textile | 13 +-
.../cassandra/config/ColumnDefinition.java | 6 +-
.../cassandra/cql3/AbstractConditions.java | 65 ++
.../apache/cassandra/cql3/ColumnConditions.java | 167 ++++
.../apache/cassandra/cql3/ColumnIdentifier.java | 56 +-
.../org/apache/cassandra/cql3/Conditions.java | 100 +++
src/java/org/apache/cassandra/cql3/Cql.g | 6 +-
.../cassandra/cql3/IfExistsCondition.java | 36 +
.../cassandra/cql3/IfNotExistsCondition.java | 36 +
src/java/org/apache/cassandra/cql3/Json.java | 36 +-
.../org/apache/cassandra/cql3/Operations.java | 135 ++++
.../cassandra/cql3/SingleColumnRelation.java | 1 +
.../apache/cassandra/cql3/UpdateParameters.java | 9 +-
.../restrictions/StatementRestrictions.java | 246 ++++--
.../cql3/statements/BatchStatement.java | 91 +--
.../cql3/statements/CQL3CasRequest.java | 14 +-
.../cql3/statements/DeleteStatement.java | 99 +--
.../cql3/statements/ModificationStatement.java | 765 +++++++++----------
.../cql3/statements/SelectStatement.java | 9 +-
.../cql3/statements/StatementType.java | 138 ++++
.../cql3/statements/UpdateStatement.java | 198 +++--
.../cql3/statements/UpdatesCollector.java | 130 ++++
src/java/org/apache/cassandra/db/CBuilder.java | 9 +-
.../org/apache/cassandra/db/RangeTombstone.java | 4 +-
src/java/org/apache/cassandra/db/Slices.java | 9 +
.../cassandra/io/sstable/CQLSSTableWriter.java | 7 +-
.../cassandra/cql3/MaterializedViewTest.java | 33 +
.../cql3/validation/entities/UFAuthTest.java | 8 +-
.../entities/UFIdentificationTest.java | 44 +-
.../cql3/validation/operations/BatchTest.java | 79 +-
.../cql3/validation/operations/DeleteTest.java | 681 ++++++++++++++++-
.../cql3/validation/operations/InsertTest.java | 233 ++++++
.../operations/InsertUpdateIfConditionTest.java | 28 +-
.../cql3/validation/operations/UpdateTest.java | 447 +++++++++++
.../cassandra/db/RangeTombstoneListTest.java | 222 +++++-
36 files changed, 3384 insertions(+), 782 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/NEWS.txt
----------------------------------------------------------------------
diff --git a/NEWS.txt b/NEWS.txt
index c7976b9..af2f64c 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -18,6 +18,11 @@ using the provided 'sstableupgrade' tool.
New features
------------
+ - Support for IN restrictions on any partition key component or clustering key
+ as well as support for EQ and IN multicolumn restrictions has been added to
+ UPDATE and DELETE statement.
+ - Support for single-column and multi-colum slice restrictions (>, >=, <= and <)
+ has been added to DELETE statements
- nodetool rebuild_index accepts the index argument without
the redundant table name
- Materialized Views, which allow for server-side denormalization, is now
@@ -35,7 +40,6 @@ New features
you do not run repair for a long time, you will keep all tombstones around which
can cause other problems.
-
Upgrading
---------
- Max mutation size is now configurable via max_mutation_size_in_kb setting in
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/doc/cql3/CQL.textile
----------------------------------------------------------------------
diff --git a/doc/cql3/CQL.textile b/doc/cql3/CQL.textile
index 74ed64e..0e04528 100644
--- a/doc/cql3/CQL.textile
+++ b/doc/cql3/CQL.textile
@@ -863,8 +863,11 @@ bc(syntax)..
<where-clause> ::= <relation> ( AND <relation> )*
<relation> ::= <identifier> '=' <term>
- | <identifier> IN '(' ( <term> ( ',' <term> )* )? ')'
+ | '(' <identifier> (',' <identifier>)* ')' '=' <term-tuple>
+ | <identifier> IN '(' ( <term> ( ',' <term>)* )? ')'
| <identifier> IN '?'
+ | '(' <identifier> (',' <identifier>)* ')' IN '(' ( <term-tuple> ( ',' <term-tuple>)* )? ')'
+ | '(' <identifier> (',' <identifier>)* ')' IN '?'
<option> ::= TIMESTAMP <integer>
| TTL <integer>
@@ -914,10 +917,14 @@ bc(syntax)..
<where-clause> ::= <relation> ( AND <relation> )*
-<relation> ::= <identifier> '=' <term>
- | <identifier> IN '(' ( <term> ( ',' <term> )* )? ')'
+<relation> ::= <identifier> <op> <term>
+ | '(' <identifier> (',' <identifier>)* ')' <op> <term-tuple>
+ | <identifier> IN '(' ( <term> ( ',' <term>)* )? ')'
| <identifier> IN '?'
+ | '(' <identifier> (',' <identifier>)* ')' IN '(' ( <term-tuple> ( ',' <term-tuple>)* )? ')'
+ | '(' <identifier> (',' <identifier>)* ')' IN '?'
+<op> ::= '=' | '<' | '>' | '<=' | '>='
<condition> ::= <identifier> '=' <term>
| <identifier> '[' <term> ']' '=' <term>
p.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/config/ColumnDefinition.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/ColumnDefinition.java b/src/java/org/apache/cassandra/config/ColumnDefinition.java
index 6afd3e7..82f2556 100644
--- a/src/java/org/apache/cassandra/config/ColumnDefinition.java
+++ b/src/java/org/apache/cassandra/config/ColumnDefinition.java
@@ -23,7 +23,7 @@ import java.util.*;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Objects;
-import com.google.common.collect.Lists;
+import com.google.common.collect.Collections2;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.db.rows.*;
@@ -285,9 +285,9 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable<
* @param definitions the column definitions to convert.
* @return the column identifiers corresponding to the specified definitions
*/
- public static List<ColumnIdentifier> toIdentifiers(List<ColumnDefinition> definitions)
+ public static Collection<ColumnIdentifier> toIdentifiers(Collection<ColumnDefinition> definitions)
{
- return Lists.transform(definitions, new Function<ColumnDefinition, ColumnIdentifier>()
+ return Collections2.transform(definitions, new Function<ColumnDefinition, ColumnIdentifier>()
{
@Override
public ColumnIdentifier apply(ColumnDefinition columnDef)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/AbstractConditions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/AbstractConditions.java b/src/java/org/apache/cassandra/cql3/AbstractConditions.java
new file mode 100644
index 0000000..71e3595
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/AbstractConditions.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+import java.util.Collections;
+
+import org.apache.cassandra.config.ColumnDefinition;
+import org.apache.cassandra.cql3.functions.Function;
+
+/**
+ * Base class for <code>Conditions</code> classes.
+ *
+ */
+abstract class AbstractConditions implements Conditions
+{
+ public Iterable<Function> getFunctions()
+ {
+ return Collections.emptyList();
+ }
+
+ public Iterable<ColumnDefinition> getColumns()
+ {
+ return null;
+ }
+
+ public boolean isEmpty()
+ {
+ return false;
+ }
+
+ public boolean appliesToStaticColumns()
+ {
+ return false;
+ }
+
+ public boolean appliesToRegularColumns()
+ {
+ return false;
+ }
+
+ public boolean isIfExists()
+ {
+ return false;
+ }
+
+ public boolean isIfNotExists()
+ {
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/ColumnConditions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/ColumnConditions.java b/src/java/org/apache/cassandra/cql3/ColumnConditions.java
new file mode 100644
index 0000000..5ac8119
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/ColumnConditions.java
@@ -0,0 +1,167 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.cassandra.config.ColumnDefinition;
+import org.apache.cassandra.cql3.functions.Function;
+import org.apache.cassandra.cql3.statements.CQL3CasRequest;
+import org.apache.cassandra.db.Clustering;
+
+import static java.util.stream.StreamSupport.stream;
+
+/**
+ * A set of <code>ColumnCondition</code>s.
+ *
+ */
+public final class ColumnConditions extends AbstractConditions
+{
+ /**
+ * The conditions on regular columns.
+ */
+ private final List<ColumnCondition> columnConditions;
+
+ /**
+ * The conditions on static columns
+ */
+ private final List<ColumnCondition> staticConditions;
+
+ /**
+ * Creates a new <code>ColumnConditions</code> instance for the specified builder.
+ */
+ private ColumnConditions(Builder builder)
+ {
+ this.columnConditions = builder.columnConditions;
+ this.staticConditions = builder.staticConditions;
+ }
+
+ @Override
+ public boolean appliesToStaticColumns()
+ {
+ return !staticConditions.isEmpty();
+ }
+
+ @Override
+ public boolean appliesToRegularColumns()
+ {
+ return !columnConditions.isEmpty();
+ }
+
+ @Override
+ public Collection<ColumnDefinition> getColumns()
+ {
+ return Stream.concat(columnConditions.stream(), staticConditions.stream())
+ .map(e -> e.column)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return columnConditions.isEmpty() && staticConditions.isEmpty();
+ }
+
+ /**
+ * Adds the conditions to the specified CAS request.
+ *
+ * @param request the request
+ * @param clustering the clustering prefix
+ * @param options the query options
+ */
+ public void addConditionsTo(CQL3CasRequest request,
+ Clustering clustering,
+ QueryOptions options)
+ {
+ if (!columnConditions.isEmpty())
+ request.addConditions(clustering, columnConditions, options);
+ if (!staticConditions.isEmpty())
+ request.addConditions(Clustering.STATIC_CLUSTERING, staticConditions, options);
+ }
+
+ @Override
+ public Iterable<Function> getFunctions()
+ {
+ return Stream.concat(columnConditions.stream(), staticConditions.stream())
+ .flatMap(e -> stream(e.getFunctions().spliterator(), false))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Creates a new <code>Builder</code> for <code>ColumnConditions</code>.
+ * @return a new <code>Builder</code> for <code>ColumnConditions</code>
+ */
+ public static Builder newBuilder()
+ {
+ return new Builder();
+ }
+
+ /**
+ * A <code>Builder</code> for <code>ColumnConditions</code>.
+ *
+ */
+ public static final class Builder
+ {
+ /**
+ * The conditions on regular columns.
+ */
+ private List<ColumnCondition> columnConditions = Collections.emptyList();
+
+ /**
+ * The conditions on static columns
+ */
+ private List<ColumnCondition> staticConditions = Collections.emptyList();
+
+ /**
+ * Adds the specified <code>ColumnCondition</code> to this set of conditions.
+ * @param condition the condition to add
+ */
+ public Builder add(ColumnCondition condition)
+ {
+ List<ColumnCondition> conds = null;
+ if (condition.column.isStatic())
+ {
+ if (staticConditions.isEmpty())
+ staticConditions = new ArrayList<>();
+ conds = staticConditions;
+ }
+ else
+ {
+ if (columnConditions.isEmpty())
+ columnConditions = new ArrayList<>();
+ conds = columnConditions;
+ }
+ conds.add(condition);
+ return this;
+ }
+
+ public ColumnConditions build()
+ {
+ return new ColumnConditions(this);
+ }
+
+ private Builder()
+ {
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
index 47e4384..6102bb9 100644
--- a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
+++ b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
@@ -194,12 +194,18 @@ public class ColumnIdentifier extends org.apache.cassandra.cql3.selection.Select
* once the comparator is known with prepare(). This should only be used with identifiers that are actual
* column names. See CASSANDRA-8178 for more background.
*/
- public static class Raw implements Selectable.Raw
+ public static interface Raw extends Selectable.Raw
+ {
+
+ public ColumnIdentifier prepare(CFMetaData cfm);
+ }
+
+ public static class Literal implements Raw
{
private final String rawText;
private final String text;
- public Raw(String rawText, boolean keepCase)
+ public Literal(String rawText, boolean keepCase)
{
this.rawText = rawText;
this.text = keepCase ? rawText : rawText.toLowerCase(Locale.US);
@@ -239,9 +245,10 @@ public class ColumnIdentifier extends org.apache.cassandra.cql3.selection.Select
@Override
public final boolean equals(Object o)
{
- if(!(o instanceof ColumnIdentifier.Raw))
+ if(!(o instanceof Literal))
return false;
- ColumnIdentifier.Raw that = (ColumnIdentifier.Raw)o;
+
+ Literal that = (Literal) o;
return text.equals(that.text);
}
@@ -251,4 +258,45 @@ public class ColumnIdentifier extends org.apache.cassandra.cql3.selection.Select
return text;
}
}
+
+ public static class ColumnIdentifierValue implements Raw
+ {
+ private final ColumnIdentifier identifier;
+
+ public ColumnIdentifierValue(ColumnIdentifier identifier)
+ {
+ this.identifier = identifier;
+ }
+
+ public ColumnIdentifier prepare(CFMetaData cfm)
+ {
+ return identifier;
+ }
+
+ public boolean processesSelection()
+ {
+ return false;
+ }
+
+ @Override
+ public final int hashCode()
+ {
+ return identifier.hashCode();
+ }
+
+ @Override
+ public final boolean equals(Object o)
+ {
+ if(!(o instanceof ColumnIdentifierValue))
+ return false;
+ ColumnIdentifierValue that = (ColumnIdentifierValue) o;
+ return identifier.equals(that.identifier);
+ }
+
+ @Override
+ public String toString()
+ {
+ return identifier.toString();
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/Conditions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Conditions.java b/src/java/org/apache/cassandra/cql3/Conditions.java
new file mode 100644
index 0000000..85459c4
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/Conditions.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import org.apache.cassandra.config.ColumnDefinition;
+import org.apache.cassandra.cql3.functions.Function;
+import org.apache.cassandra.cql3.statements.CQL3CasRequest;
+import org.apache.cassandra.db.Clustering;
+
+/**
+ * Conditions that can be applied to a mutation statement.
+ *
+ */
+public interface Conditions
+{
+ /**
+ * An EMPTY condition
+ */
+ static final Conditions EMPTY_CONDITION = ColumnConditions.newBuilder().build();
+
+ /**
+ * IF EXISTS condition
+ */
+ static final Conditions IF_EXISTS_CONDITION = new IfExistsCondition();
+
+ /**
+ * IF NOT EXISTS condition
+ */
+ static final Conditions IF_NOT_EXISTS_CONDITION = new IfNotExistsCondition();
+
+ /**
+ * Returns the functions used by the conditions.
+ * @return the functions used by the conditions
+ */
+ Iterable<Function> getFunctions();
+
+ /**
+ * Returns the column definitions to which apply the conditions.
+ * @return the column definitions to which apply the conditions.
+ */
+ Iterable<ColumnDefinition> getColumns();
+
+ /**
+ * Checks if this <code>Conditions</code> is empty.
+ * @return <code>true</code> if this <code>Conditions</code> is empty, <code>false</code> otherwise.
+ */
+ boolean isEmpty();
+
+ /**
+ * Checks if this is a IF EXIST condition.
+ * @return <code>true</code> if this is a IF EXIST condition, <code>false</code> otherwise.
+ */
+ boolean isIfExists();
+
+ /**
+ * Checks if this is a IF NOT EXIST condition.
+ * @return <code>true</code> if this is a IF NOT EXIST condition, <code>false</code> otherwise.
+ */
+ boolean isIfNotExists();
+
+ /**
+ * Checks if some of the conditions apply to static columns.
+ *
+ * @return <code>true</code> if some of the conditions apply to static columns, <code>false</code> otherwise.
+ */
+ boolean appliesToStaticColumns();
+
+ /**
+ * Checks if some of the conditions apply to regular columns.
+ *
+ * @return <code>true</code> if some of the conditions apply to regular columns, <code>false</code> otherwise.
+ */
+ boolean appliesToRegularColumns();
+
+ /**
+ * Adds the conditions to the specified CAS request.
+ *
+ * @param request the request
+ * @param clustering the clustering prefix
+ * @param options the query options
+ */
+ public void addConditionsTo(CQL3CasRequest request,
+ Clustering clustering,
+ QueryOptions options);
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g
index 2149f10..87bec4b 100644
--- a/src/java/org/apache/cassandra/cql3/Cql.g
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -1154,9 +1154,9 @@ userPassword[RoleOptions opts]
// identifiers because the underlying comparator is not necessarily text. See
// CASSANDRA-8178 for details.
cident returns [ColumnIdentifier.Raw id]
- : t=IDENT { $id = new ColumnIdentifier.Raw($t.text, false); }
- | t=QUOTED_NAME { $id = new ColumnIdentifier.Raw($t.text, true); }
- | k=unreserved_keyword { $id = new ColumnIdentifier.Raw(k, false); }
+ : t=IDENT { $id = new ColumnIdentifier.Literal($t.text, false); }
+ | t=QUOTED_NAME { $id = new ColumnIdentifier.Literal($t.text, true); }
+ | k=unreserved_keyword { $id = new ColumnIdentifier.Literal(k, false); }
;
// Column identifiers where the comparator is known to be text
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/IfExistsCondition.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/IfExistsCondition.java b/src/java/org/apache/cassandra/cql3/IfExistsCondition.java
new file mode 100644
index 0000000..a24d8c0
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/IfExistsCondition.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import org.apache.cassandra.cql3.statements.CQL3CasRequest;
+import org.apache.cassandra.db.Clustering;
+
+final class IfExistsCondition extends AbstractConditions
+{
+ @Override
+ public void addConditionsTo(CQL3CasRequest request, Clustering clustering, QueryOptions options)
+ {
+ request.addExist(clustering);
+ }
+
+ @Override
+ public boolean isIfExists()
+ {
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/IfNotExistsCondition.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/IfNotExistsCondition.java b/src/java/org/apache/cassandra/cql3/IfNotExistsCondition.java
new file mode 100644
index 0000000..05cb864
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/IfNotExistsCondition.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import org.apache.cassandra.cql3.statements.CQL3CasRequest;
+import org.apache.cassandra.db.Clustering;
+
+final class IfNotExistsCondition extends AbstractConditions
+{
+ @Override
+ public void addConditionsTo(CQL3CasRequest request, Clustering clustering, QueryOptions options)
+ {
+ request.addNotExist(clustering);
+ }
+
+ @Override
+ public boolean isIfNotExists()
+ {
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/Json.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Json.java b/src/java/org/apache/cassandra/cql3/Json.java
index e4bce29..35c69ed 100644
--- a/src/java/org/apache/cassandra/cql3/Json.java
+++ b/src/java/org/apache/cassandra/cql3/Json.java
@@ -71,7 +71,7 @@ public class Json
public Prepared prepareAndCollectMarkers(CFMetaData metadata, Collection<ColumnDefinition> receivers, VariableSpecifications boundNames)
{
- return new PreparedLiteral(metadata.ksName, parseJson(text, receivers));
+ return new PreparedLiteral(parseJson(text, receivers));
}
}
@@ -91,7 +91,7 @@ public class Json
public Prepared prepareAndCollectMarkers(CFMetaData metadata, Collection<ColumnDefinition> receivers, VariableSpecifications boundNames)
{
boundNames.add(bindIndex, makeReceiver(metadata));
- return new PreparedMarker(metadata.ksName, bindIndex, receivers);
+ return new PreparedMarker(bindIndex, receivers);
}
private ColumnSpecification makeReceiver(CFMetaData metadata)
@@ -105,27 +105,7 @@ public class Json
*/
public static abstract class Prepared
{
- private final String keyspace;
-
- protected Prepared(String keyspace)
- {
- this.keyspace = keyspace;
- }
-
- protected abstract Term.Raw getRawTermForColumn(ColumnDefinition def);
-
- public Term getPrimaryKeyValueForColumn(ColumnDefinition def)
- {
- // Note that we know we don't have to call collectMarkerSpecification since it has already been collected
- return getRawTermForColumn(def).prepare(keyspace, def);
- }
-
- public Operation getSetOperationForColumn(ColumnDefinition def)
- {
- // Note that we know we don't have to call collectMarkerSpecification on the operation since we have
- // already collected all we need.
- return new Operation.SetValue(getRawTermForColumn(def)).prepare(keyspace, def);
- }
+ public abstract Term.Raw getRawTermForColumn(ColumnDefinition def);
}
/**
@@ -135,13 +115,12 @@ public class Json
{
private final Map<ColumnIdentifier, Term> columnMap;
- public PreparedLiteral(String keyspace, Map<ColumnIdentifier, Term> columnMap)
+ public PreparedLiteral(Map<ColumnIdentifier, Term> columnMap)
{
- super(keyspace);
this.columnMap = columnMap;
}
- protected Term.Raw getRawTermForColumn(ColumnDefinition def)
+ public Term.Raw getRawTermForColumn(ColumnDefinition def)
{
Term value = columnMap.get(def.name);
return value == null ? Constants.NULL_LITERAL : new ColumnValue(value);
@@ -158,14 +137,13 @@ public class Json
private Map<ColumnIdentifier, Term> columnMap;
- public PreparedMarker(String keyspace, int bindIndex, Collection<ColumnDefinition> columns)
+ public PreparedMarker(int bindIndex, Collection<ColumnDefinition> columns)
{
- super(keyspace);
this.bindIndex = bindIndex;
this.columns = columns;
}
- protected DelayedColumnValue getRawTermForColumn(ColumnDefinition def)
+ public DelayedColumnValue getRawTermForColumn(ColumnDefinition def)
{
return new DelayedColumnValue(this, def);
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/Operations.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Operations.java b/src/java/org/apache/cassandra/cql3/Operations.java
new file mode 100644
index 0000000..c4cade1
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/Operations.java
@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.cassandra.cql3.functions.Function;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+
+/**
+ * A set of <code>Operation</code>s.
+ *
+ */
+public final class Operations implements Iterable<Operation>
+{
+ /**
+ * The operations on regular columns.
+ */
+ private final List<Operation> regularOperations = new ArrayList<>();
+
+ /**
+ * The operations on static columns.
+ */
+ private final List<Operation> staticOperations = new ArrayList<>();
+
+ /**
+ * Checks if some of the operations apply to static columns.
+ *
+ * @return <code>true</code> if some of the operations apply to static columns, <code>false</code> otherwise.
+ */
+ public boolean appliesToStaticColumns()
+ {
+ return !staticOperations.isEmpty();
+ }
+
+ /**
+ * Checks if some of the operations apply to regular columns.
+ *
+ * @return <code>true</code> if some of the operations apply to regular columns, <code>false</code> otherwise.
+ */
+ public boolean appliesToRegularColumns()
+ {
+ return !regularOperations.isEmpty();
+ }
+
+ /**
+ * Returns the operation on regular columns.
+ * @return the operation on regular columns
+ */
+ public List<Operation> regularOperations()
+ {
+ return regularOperations;
+ }
+
+ /**
+ * Returns the operation on static columns.
+ * @return the operation on static columns
+ */
+ public List<Operation> staticOperations()
+ {
+ return staticOperations;
+ }
+
+ /**
+ * Adds the specified <code>Operation</code> to this set of operations.
+ * @param operation the operation to add
+ */
+ public void add(Operation operation)
+ {
+ if (operation.column.isStatic())
+ staticOperations.add(operation);
+ else
+ regularOperations.add(operation);
+ }
+
+ /**
+ * Checks if one of the operations requires a read.
+ *
+ * @return <code>true</code> if one of the operations requires a read, <code>false</code> otherwise.
+ */
+ public boolean requiresRead()
+ {
+ // Lists SET operation incurs a read.
+ for (Operation operation : this)
+ if (operation.requiresRead())
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Checks if this <code>Operations</code> is empty.
+ * @return <code>true</code> if this <code>Operations</code> is empty, <code>false</code> otherwise.
+ */
+ public boolean isEmpty()
+ {
+ return staticOperations.isEmpty() && regularOperations.isEmpty();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Iterator<Operation> iterator()
+ {
+ return Iterators.concat(staticOperations.iterator(), regularOperations.iterator());
+ }
+
+ public Iterable<? extends Function> getFunctions()
+ {
+ List<Function> functions = new ArrayList<>();
+ for (Operation operation : this)
+ Iterables.addAll(functions, operation.getFunctions());
+ return functions;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
index c848b9e..84e6274 100644
--- a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
+++ b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
@@ -223,6 +223,7 @@ public final class SingleColumnRelation extends Relation
}
checkFalse(isContainsKey() && !(receiver.type instanceof MapType), "Cannot use CONTAINS KEY on non-map column %s", receiver.name);
+ checkFalse(isContains() && !(receiver.type.isCollection()), "Cannot use CONTAINS on non-collection column %s", receiver.name);
if (mapKey != null)
{
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/UpdateParameters.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/UpdateParameters.java b/src/java/org/apache/cassandra/cql3/UpdateParameters.java
index cd81f84..dbcf803 100644
--- a/src/java/org/apache/cassandra/cql3/UpdateParameters.java
+++ b/src/java/org/apache/cassandra/cql3/UpdateParameters.java
@@ -216,9 +216,14 @@ public class UpdateParameters
return deletionTime;
}
- public RangeTombstone makeRangeTombstone(CBuilder cbuilder)
+ public RangeTombstone makeRangeTombstone(ClusteringComparator comparator, Clustering clustering)
{
- return new RangeTombstone(cbuilder.buildSlice(), deletionTime);
+ return makeRangeTombstone(Slice.make(comparator, clustering));
+ }
+
+ public RangeTombstone makeRangeTombstone(Slice slice)
+ {
+ return new RangeTombstone(slice, deletionTime);
}
public Row getPrefetchedRow(DecoratedKey key, Clustering clustering)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/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 b0c81b8..3cf6bfb 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@ -25,13 +25,16 @@ import com.google.common.collect.Iterables;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
-import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.Relation;
+import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.statements.Bound;
+import org.apache.cassandra.cql3.statements.StatementType;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.dht.*;
-import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.index.SecondaryIndexManager;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.btree.BTreeSet;
@@ -49,6 +52,11 @@ public final class StatementRestrictions
public static final String NO_INDEX_FOUND_MESSAGE =
"No supported secondary index found for the non primary key columns restrictions";
/**
+ * The type of statement
+ */
+ private final StatementType type;
+
+ /**
* The Column Family meta data
*/
public final CFMetaData cfm;
@@ -86,30 +94,33 @@ public final class StatementRestrictions
/**
* Creates a new empty <code>StatementRestrictions</code>.
*
+ * @param type the type of statement
* @param cfm the column family meta data
* @return a new empty <code>StatementRestrictions</code>.
*/
- public static StatementRestrictions empty(CFMetaData cfm)
+ public static StatementRestrictions empty(StatementType type, CFMetaData cfm)
{
- return new StatementRestrictions(cfm);
+ return new StatementRestrictions(type, cfm);
}
- private StatementRestrictions(CFMetaData cfm)
+ private StatementRestrictions(StatementType type, CFMetaData cfm)
{
+ this.type = type;
this.cfm = cfm;
this.partitionKeyRestrictions = new PrimaryKeyRestrictionSet(cfm.getKeyValidatorAsClusteringComparator(), true);
this.clusteringColumnsRestrictions = new PrimaryKeyRestrictionSet(cfm.comparator, false);
this.nonPrimaryKeyRestrictions = new RestrictionSet();
}
- public StatementRestrictions(CFMetaData cfm,
+ public StatementRestrictions(StatementType type,
+ CFMetaData cfm,
List<Relation> whereClause,
VariableSpecifications boundNames,
boolean selectsOnlyStaticColumns,
boolean selectACollection,
- boolean useFiltering) throws InvalidRequestException
+ boolean useFiltering)
{
- this(cfm);
+ this(type, cfm);
/*
* WHERE clause. For a given entity, rules are:
@@ -123,13 +134,19 @@ public final class StatementRestrictions
for (Relation relation : whereClause)
addRestriction(relation.toRestriction(cfm, boundNames));
- ColumnFamilyStore cfs = Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfName);
- SecondaryIndexManager secondaryIndexManager = cfs.indexManager;
+ boolean hasQueriableClusteringColumnIndex = false;
+ boolean hasQueriableIndex = false;
- boolean hasQueriableClusteringColumnIndex = clusteringColumnsRestrictions.hasSupportingIndex(secondaryIndexManager);
- boolean hasQueriableIndex = hasQueriableClusteringColumnIndex
- || partitionKeyRestrictions.hasSupportingIndex(secondaryIndexManager)
- || nonPrimaryKeyRestrictions.hasSupportingIndex(secondaryIndexManager);
+ if (type.allowUseOfSecondaryIndices())
+ {
+ ColumnFamilyStore cfs = Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfName);
+ SecondaryIndexManager secondaryIndexManager = cfs.indexManager;
+
+ hasQueriableClusteringColumnIndex = clusteringColumnsRestrictions.hasSupportingIndex(secondaryIndexManager);
+ hasQueriableIndex = hasQueriableClusteringColumnIndex
+ || partitionKeyRestrictions.hasSupportingIndex(secondaryIndexManager)
+ || nonPrimaryKeyRestrictions.hasSupportingIndex(secondaryIndexManager);
+ }
// At this point, the select statement if fully constructed, but we still have a few things to validate
processPartitionKeyRestrictions(hasQueriableIndex);
@@ -139,10 +156,26 @@ public final class StatementRestrictions
if (usesSecondaryIndexing)
indexRestrictions.add(partitionKeyRestrictions);
- checkFalse(selectsOnlyStaticColumns && hasClusteringColumnsRestriction(),
- "Cannot restrict clustering columns when selecting only static columns");
+ if (selectsOnlyStaticColumns && hasClusteringColumnsRestriction())
+ {
+ // If the only updated/deleted columns are static, then we don't need clustering columns.
+ // And in fact, unless it is an INSERT, we reject if clustering colums are provided as that
+ // suggest something unintended. For instance, given:
+ // CREATE TABLE t (k int, v int, s int static, PRIMARY KEY (k, v))
+ // it can make sense to do:
+ // INSERT INTO t(k, v, s) VALUES (0, 1, 2)
+ // but both
+ // UPDATE t SET s = 3 WHERE k = 0 AND v = 1
+ // DELETE v FROM t WHERE k = 0 AND v = 1
+ // sounds like you don't really understand what your are doing.
+ if (type.isDelete() || type.isUpdate())
+ throw invalidRequest("Invalid restrictions on clustering columns since the %s statement modifies only static columns",
+ type);
+ if (type.isSelect())
+ throw invalidRequest("Cannot restrict clustering columns when selecting only static columns");
+ }
- processClusteringColumnsRestrictions(hasQueriableIndex, selectACollection);
+ processClusteringColumnsRestrictions(hasQueriableIndex, selectsOnlyStaticColumns, selectACollection);
// Covers indexes on the first clustering column (among others).
if (isKeyRange && hasQueriableClusteringColumnIndex)
@@ -157,10 +190,18 @@ public final class StatementRestrictions
// there is restrictions not covered by the PK.
if (!nonPrimaryKeyRestrictions.isEmpty())
{
+ if (!type.allowNonPrimaryKeyInWhereClause())
+ {
+ Collection<ColumnIdentifier> nonPrimaryKeyColumns =
+ ColumnDefinition.toIdentifiers(nonPrimaryKeyRestrictions.getColumnDefs());
+
+ throw invalidRequest("Non PRIMARY KEY columns found in where clause: %s ",
+ Joiner.on(", ").join(nonPrimaryKeyColumns));
+ }
if (hasQueriableIndex)
usesSecondaryIndexing = true;
else if (!useFiltering)
- throw new InvalidRequestException(NO_INDEX_FOUND_MESSAGE);
+ throw invalidRequest(NO_INDEX_FOUND_MESSAGE);
indexRestrictions.add(nonPrimaryKeyRestrictions);
}
@@ -169,7 +210,7 @@ public final class StatementRestrictions
validateSecondaryIndexSelections(selectsOnlyStaticColumns);
}
- private void addRestriction(Restriction restriction) throws InvalidRequestException
+ private void addRestriction(Restriction restriction)
{
if (restriction.isMultiColumn())
clusteringColumnsRestrictions = clusteringColumnsRestrictions.mergeWith(restriction);
@@ -186,7 +227,7 @@ public final class StatementRestrictions
nonPrimaryKeyRestrictions.getFunctions());
}
- private void addSingleColumnRestriction(SingleColumnRestriction restriction) throws InvalidRequestException
+ private void addSingleColumnRestriction(SingleColumnRestriction restriction)
{
ColumnDefinition def = restriction.columnDef;
if (def.isPartitionKey())
@@ -241,8 +282,19 @@ public final class StatementRestrictions
return this.usesSecondaryIndexing;
}
- private void processPartitionKeyRestrictions(boolean hasQueriableIndex) throws InvalidRequestException
+ private void processPartitionKeyRestrictions(boolean hasQueriableIndex)
{
+ if (!type.allowPartitionKeyRanges())
+ {
+ checkFalse(partitionKeyRestrictions.isOnToken(),
+ "The token function cannot be used in WHERE clauses for %s statements", type);
+
+ if (hasUnrestrictedPartitionKeyComponents())
+ throw invalidRequest("Some partition key parts are missing: %s",
+ Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents()));
+ }
+ else
+ {
// If there is a queriable index, no special condition are required on the other restrictions.
// But we still need to know 2 things:
// - If we don't have a queriable index, is the query ok
@@ -252,17 +304,18 @@ public final class StatementRestrictions
if (partitionKeyRestrictions.isOnToken())
isKeyRange = true;
- if (hasPartitionKeyUnrestrictedComponents())
- {
- if (!partitionKeyRestrictions.isEmpty())
+ if (hasUnrestrictedPartitionKeyComponents())
{
- if (!hasQueriableIndex)
- throw invalidRequest("Partition key parts: %s must be restricted as other parts are",
- Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents()));
- }
+ if (!partitionKeyRestrictions.isEmpty())
+ {
+ if (!hasQueriableIndex)
+ throw invalidRequest("Partition key parts: %s must be restricted as other parts are",
+ Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents()));
+ }
- isKeyRange = true;
- usesSecondaryIndexing = hasQueriableIndex;
+ isKeyRange = true;
+ usesSecondaryIndexing = hasQueriableIndex;
+ }
}
}
@@ -270,7 +323,7 @@ public final class StatementRestrictions
* Checks if the partition key has some unrestricted components.
* @return <code>true</code> if the partition key has some unrestricted components, <code>false</code> otherwise.
*/
- private boolean hasPartitionKeyUnrestrictedComponents()
+ private boolean hasUnrestrictedPartitionKeyComponents()
{
return partitionKeyRestrictions.size() < cfm.partitionKeyColumns().size();
}
@@ -284,7 +337,7 @@ public final class StatementRestrictions
* Returns the partition key components that are not restricted.
* @return the partition key components that are not restricted.
*/
- private List<ColumnIdentifier> getPartitionKeyUnrestrictedComponents()
+ private Collection<ColumnIdentifier> getPartitionKeyUnrestrictedComponents()
{
List<ColumnDefinition> list = new ArrayList<>(cfm.partitionKeyColumns());
list.removeAll(partitionKeyRestrictions.getColumnDefs());
@@ -292,39 +345,65 @@ public final class StatementRestrictions
}
/**
+ * Checks if the restrictions on the partition key are token restrictions.
+ *
+ * @return <code>true</code> if the restrictions on the partition key are token restrictions,
+ * <code>false</code> otherwise.
+ */
+ public boolean isPartitionKeyRestrictionsOnToken()
+ {
+ return partitionKeyRestrictions.isOnToken();
+ }
+
+ /**
* Processes the clustering column restrictions.
*
* @param hasQueriableIndex <code>true</code> if some of the queried data are indexed, <code>false</code> otherwise
+ * @param selectsOnlyStaticColumns <code>true</code> if the selected or modified columns are all statics,
+ * <code>false</code> otherwise.
* @param selectACollection <code>true</code> if the query should return a collection column
- * @throws InvalidRequestException if the request is invalid
*/
private void processClusteringColumnsRestrictions(boolean hasQueriableIndex,
- boolean selectACollection) throws InvalidRequestException
+ boolean selectsOnlyStaticColumns,
+ boolean selectACollection)
{
- checkFalse(clusteringColumnsRestrictions.isIN() && selectACollection,
- "Cannot restrict clustering columns by IN relations when a collection is selected by the query");
- checkFalse(clusteringColumnsRestrictions.isContains() && !hasQueriableIndex,
- "Cannot restrict clustering columns by a CONTAINS relation without a secondary index");
+ checkFalse(!type.allowClusteringColumnSlices() && clusteringColumnsRestrictions.isSlice(),
+ "Slice restrictions are not supported on the clustering columns in %s statements", type);
- if (hasClusteringColumnsRestriction())
+ if (!type.allowClusteringColumnSlices()
+ && (!cfm.isCompactTable() || (cfm.isCompactTable() && !hasClusteringColumnsRestriction())))
{
- List<ColumnDefinition> clusteringColumns = cfm.clusteringColumns();
- List<ColumnDefinition> restrictedColumns = new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs());
+ if (!selectsOnlyStaticColumns && hasUnrestrictedClusteringColumns())
+ throw invalidRequest("Some clustering keys are missing: %s",
+ Joiner.on(", ").join(getUnrestrictedClusteringColumns()));
+ }
+ else
+ {
+ checkFalse(clusteringColumnsRestrictions.isIN() && selectACollection,
+ "Cannot restrict clustering columns by IN relations when a collection is selected by the query");
+ checkFalse(clusteringColumnsRestrictions.isContains() && !hasQueriableIndex,
+ "Cannot restrict clustering columns by a CONTAINS relation without a secondary index");
- for (int i = 0, m = restrictedColumns.size(); i < m; i++)
+ if (hasClusteringColumnsRestriction())
{
- ColumnDefinition clusteringColumn = clusteringColumns.get(i);
- ColumnDefinition restrictedColumn = restrictedColumns.get(i);
+ List<ColumnDefinition> clusteringColumns = cfm.clusteringColumns();
+ List<ColumnDefinition> restrictedColumns = new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs());
- if (!clusteringColumn.equals(restrictedColumn))
+ for (int i = 0, m = restrictedColumns.size(); i < m; i++)
{
- checkTrue(hasQueriableIndex,
- "PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted",
- restrictedColumn.name,
- clusteringColumn.name);
-
- usesSecondaryIndexing = true; // handle gaps and non-keyrange cases.
- break;
+ ColumnDefinition clusteringColumn = clusteringColumns.get(i);
+ ColumnDefinition restrictedColumn = restrictedColumns.get(i);
+
+ if (!clusteringColumn.equals(restrictedColumn))
+ {
+ checkTrue(hasQueriableIndex,
+ "PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted",
+ restrictedColumn.name,
+ clusteringColumn.name);
+
+ usesSecondaryIndexing = true; // handle gaps and non-keyrange cases.
+ break;
+ }
}
}
}
@@ -333,7 +412,27 @@ public final class StatementRestrictions
usesSecondaryIndexing = true;
}
- public RowFilter getRowFilter(SecondaryIndexManager indexManager, QueryOptions options) throws InvalidRequestException
+ /**
+ * Returns the clustering columns that are not restricted.
+ * @return the clustering columns that are not restricted.
+ */
+ private Collection<ColumnIdentifier> getUnrestrictedClusteringColumns()
+ {
+ List<ColumnDefinition> missingClusteringColumns = new ArrayList<>(cfm.clusteringColumns());
+ missingClusteringColumns.removeAll(new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs()));
+ return ColumnDefinition.toIdentifiers(missingClusteringColumns);
+ }
+
+ /**
+ * Checks if some clustering columns are not restricted.
+ * @return <code>true</code> if some clustering columns are not restricted, <code>false</code> otherwise.
+ */
+ private boolean hasUnrestrictedClusteringColumns()
+ {
+ return cfm.clusteringColumns().size() != clusteringColumnsRestrictions.size();
+ }
+
+ public RowFilter getRowFilter(SecondaryIndexManager indexManager, QueryOptions options)
{
if (indexRestrictions.isEmpty())
return RowFilter.NONE;
@@ -350,9 +449,8 @@ public final class StatementRestrictions
*
* @param options the query options
* @return the partition keys for which the data is requested.
- * @throws InvalidRequestException if the partition keys cannot be retrieved
*/
- public Collection<ByteBuffer> getPartitionKeys(final QueryOptions options) throws InvalidRequestException
+ public List<ByteBuffer> getPartitionKeys(final QueryOptions options)
{
return partitionKeyRestrictions.values(options);
}
@@ -363,13 +461,12 @@ public final class StatementRestrictions
* @param b the boundary type
* @param options the query options
* @return the specified bound of the partition key
- * @throws InvalidRequestException if the boundary cannot be retrieved
*/
- private ByteBuffer getPartitionKeyBound(Bound b, QueryOptions options) throws InvalidRequestException
+ private ByteBuffer getPartitionKeyBound(Bound b, QueryOptions options)
{
// Deal with unrestricted partition key components (special-casing is required to deal with 2i queries on the
// first component of a composite partition key).
- if (hasPartitionKeyUnrestrictedComponents())
+ if (hasUnrestrictedPartitionKeyComponents())
return ByteBufferUtil.EMPTY_BYTE_BUFFER;
// We deal with IN queries for keys in other places, so we know buildBound will return only one result
@@ -381,9 +478,8 @@ public final class StatementRestrictions
*
* @param options the query options
* @return the partition key bounds
- * @throws InvalidRequestException if the query is invalid
*/
- public AbstractBounds<PartitionPosition> getPartitionKeyBounds(QueryOptions options) throws InvalidRequestException
+ public AbstractBounds<PartitionPosition> getPartitionKeyBounds(QueryOptions options)
{
IPartitioner p = cfm.partitioner;
@@ -396,7 +492,7 @@ public final class StatementRestrictions
}
private AbstractBounds<PartitionPosition> getPartitionKeyBounds(IPartitioner p,
- QueryOptions options) throws InvalidRequestException
+ QueryOptions options)
{
ByteBuffer startKeyBytes = getPartitionKeyBound(Bound.START, options);
ByteBuffer finishKeyBytes = getPartitionKeyBound(Bound.END, options);
@@ -420,8 +516,7 @@ public final class StatementRestrictions
}
private AbstractBounds<PartitionPosition> getPartitionKeyBoundsForTokenRestrictions(IPartitioner p,
- QueryOptions options)
- throws InvalidRequestException
+ QueryOptions options)
{
Token startToken = getTokenBound(Bound.START, options, p);
Token endToken = getTokenBound(Bound.END, options, p);
@@ -450,7 +545,7 @@ public final class StatementRestrictions
return new Range<>(start, end);
}
- private Token getTokenBound(Bound b, QueryOptions options, IPartitioner p) throws InvalidRequestException
+ private Token getTokenBound(Bound b, QueryOptions options, IPartitioner p)
{
if (!partitionKeyRestrictions.hasBound(b))
return p.getMinimumToken();
@@ -476,9 +571,8 @@ public final class StatementRestrictions
*
* @param options the query options
* @return the requested clustering columns
- * @throws InvalidRequestException if the query is not valid
*/
- public NavigableSet<Clustering> getClusteringColumns(QueryOptions options) throws InvalidRequestException
+ public NavigableSet<Clustering> getClusteringColumns(QueryOptions options)
{
// If this is a names command and the table is a static compact one, then as far as CQL is concerned we have
// only a single row which internally correspond to the static parts. In which case we want to return an empty
@@ -495,9 +589,8 @@ public final class StatementRestrictions
* @param b the bound type
* @param options the query options
* @return the bounds (start or end) of the clustering columns
- * @throws InvalidRequestException if the request is not valid
*/
- public NavigableSet<Slice.Bound> getClusteringColumnsBounds(Bound b, QueryOptions options) throws InvalidRequestException
+ public NavigableSet<Slice.Bound> getClusteringColumnsBounds(Bound b, QueryOptions options)
{
return clusteringColumnsRestrictions.boundsAsClustering(b, options);
}
@@ -546,7 +639,7 @@ public final class StatementRestrictions
&& nonPrimaryKeyRestrictions.hasMultipleContains());
}
- private void validateSecondaryIndexSelections(boolean selectsOnlyStaticColumns) throws InvalidRequestException
+ private void validateSecondaryIndexSelections(boolean selectsOnlyStaticColumns)
{
checkFalse(keyIsInRelation(),
"Select on indexed columns and with IN clause for the PRIMARY KEY are not supported");
@@ -556,4 +649,19 @@ public final class StatementRestrictions
// so far, 2i means that you've restricted a non static column, so the query is somewhat non-sensical.
checkFalse(selectsOnlyStaticColumns, "Queries using 2ndary indexes don't support selecting only static columns");
}
+
+ /**
+ * Checks that all the primary key columns (partition key and clustering columns) are restricted by an equality
+ * relation ('=' or 'IN').
+ *
+ * @return <code>true</code> if all the primary key columns are restricted by an equality relation.
+ */
+ public boolean hasAllPKColumnsRestrictedByEqualities()
+ {
+ return !isPartitionKeyRestrictionsOnToken()
+ && !hasUnrestrictedPartitionKeyComponents()
+ && (partitionKeyRestrictions.isEQ() || partitionKeyRestrictions.isIN())
+ && !hasUnrestrictedClusteringColumns()
+ && (clusteringColumnsRestrictions.isEQ() || clusteringColumnsRestrictions.isIN());
+ }
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
index c8482b3..4a92ec1 100644
--- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
@@ -27,7 +27,6 @@ import com.google.common.collect.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.MessageFormatter;
-
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.*;
@@ -44,6 +43,8 @@ import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.Pair;
+import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
+
/**
* A <code>BATCH</code> statement parsed from a CQL query.
*/
@@ -217,8 +218,7 @@ public class BatchStatement implements CQLStatement
throws RequestExecutionException, RequestValidationException
{
Set<String> tablesWithZeroGcGs = null;
-
- Map<String, Map<ByteBuffer, IMutation>> mutations = new HashMap<>();
+ UpdatesCollector collector = new UpdatesCollector(updatedColumns, updatedRows());
for (int i = 0; i < statements.size(); i++)
{
ModificationStatement statement = statements.get(i);
@@ -230,7 +230,7 @@ public class BatchStatement implements CQLStatement
}
QueryOptions statementOptions = options.forStatement(i);
long timestamp = attrs.getTimestamp(now, statementOptions);
- addStatementMutations(statement, statementOptions, local, timestamp, mutations);
+ statement.addUpdates(collector, statementOptions, local, timestamp);
}
if (tablesWithZeroGcGs != null)
@@ -242,27 +242,7 @@ public class BatchStatement implements CQLStatement
.getMessage());
}
- return unzipMutations(mutations);
- }
-
- private Collection<? extends IMutation> unzipMutations(Map<String, Map<ByteBuffer, IMutation>> mutations)
- {
-
- // The case where all statement where on the same keyspace is pretty common
- if (mutations.size() == 1)
- return mutations.values().iterator().next().values();
-
-
- List<IMutation> ms = new ArrayList<>();
- for (Map<ByteBuffer, IMutation> ksMap : mutations.values())
- ms.addAll(ksMap.values());
-
- return ms;
- }
-
- private PartitionColumns updatedColumns()
- {
- return updatedColumns;
+ return collector.toMutations();
}
private int updatedRows()
@@ -272,55 +252,6 @@ public class BatchStatement implements CQLStatement
return statements.size();
}
- private void addStatementMutations(ModificationStatement statement,
- QueryOptions options,
- boolean local,
- long now,
- Map<String, Map<ByteBuffer, IMutation>> mutations)
- throws RequestExecutionException, RequestValidationException
- {
- String ksName = statement.keyspace();
- Map<ByteBuffer, IMutation> ksMap = mutations.get(ksName);
- if (ksMap == null)
- {
- ksMap = new HashMap<>();
- mutations.put(ksName, ksMap);
- }
-
- // The following does the same than statement.getMutations(), but we inline it here because
- // we don't want to recreate mutations every time as this is particularly inefficient when applying
- // multiple batch to the same partition (see #6737).
- List<ByteBuffer> keys = statement.buildPartitionKeyNames(options);
- CBuilder clustering = statement.createClustering(options);
- UpdateParameters params = statement.makeUpdateParameters(keys, clustering, options, local, now);
-
- for (ByteBuffer key : keys)
- {
- DecoratedKey dk = statement.cfm.decorateKey(key);
- IMutation mutation = ksMap.get(dk.getKey());
- Mutation mut;
- if (mutation == null)
- {
- mut = new Mutation(ksName, dk);
- mutation = statement.cfm.isCounter() ? new CounterMutation(mut, options.getConsistency()) : mut;
- ksMap.put(dk.getKey(), mutation);
- }
- else
- {
- mut = statement.cfm.isCounter() ? ((CounterMutation) mutation).getMutation() : (Mutation) mutation;
- }
-
- PartitionUpdate upd = mut.get(statement.cfm);
- if (upd == null)
- {
- upd = new PartitionUpdate(statement.cfm, dk, updatedColumns(), updatedRows());
- mut.add(upd);
- }
-
- statement.addUpdateForKey(upd, clustering, params);
- }
- }
-
/**
* Checks batch size to ensure threshold is met. If not, a warning is logged.
*
@@ -470,17 +401,23 @@ public class BatchStatement implements CQLStatement
throw new InvalidRequestException("Batch with conditions cannot span multiple partitions");
}
- CBuilder cbuilder = statement.createClustering(statementOptions);
+ SortedSet<Clustering> clusterings = statement.createClustering(statementOptions);
+
+ checkFalse(clusterings.size() > 1,
+ "IN on the clustering key columns is not supported with conditional updates");
+
+ Clustering clustering = Iterables.getOnlyElement(clusterings);
+
if (statement.hasConditions())
{
- statement.addConditions(cbuilder.build(), casRequest, statementOptions);
+ statement.addConditions(clustering, casRequest, statementOptions);
// As soon as we have a ifNotExists, we set columnsWithConditions to null so that everything is in the resultSet
if (statement.hasIfNotExistCondition() || statement.hasIfExistCondition())
columnsWithConditions = null;
else if (columnsWithConditions != null)
Iterables.addAll(columnsWithConditions, statement.getColumnsWithConditions());
}
- casRequest.addRowUpdate(cbuilder, statement, statementOptions, timestamp);
+ casRequest.addRowUpdate(clustering, statement, statementOptions, timestamp);
}
return Pair.create(casRequest, columnsWithConditions);
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
index dc70bd2..1c3c795 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
@@ -67,9 +67,9 @@ public class CQL3CasRequest implements CASRequest
this.updatesStaticRow = updatesStaticRow;
}
- public void addRowUpdate(CBuilder cbuilder, ModificationStatement stmt, QueryOptions options, long timestamp)
+ public void addRowUpdate(Clustering clustering, ModificationStatement stmt, QueryOptions options, long timestamp)
{
- updates.add(new RowUpdate(cbuilder, stmt, options, timestamp));
+ updates.add(new RowUpdate(clustering, stmt, options, timestamp));
}
public void addNotExist(Clustering clustering) throws InvalidRequestException
@@ -129,7 +129,7 @@ public class CQL3CasRequest implements CASRequest
return conditionColumns;
}
- public SinglePartitionReadCommand readCommand(int nowInSec)
+ public SinglePartitionReadCommand<?> readCommand(int nowInSec)
{
assert !conditions.isEmpty();
Slices.Builder builder = new Slices.Builder(cfm.comparator, conditions.size());
@@ -184,14 +184,14 @@ public class CQL3CasRequest implements CASRequest
*/
private class RowUpdate
{
- private final CBuilder cbuilder;
+ private final Clustering clustering;
private final ModificationStatement stmt;
private final QueryOptions options;
private final long timestamp;
- private RowUpdate(CBuilder cbuilder, ModificationStatement stmt, QueryOptions options, long timestamp)
+ private RowUpdate(Clustering clustering, ModificationStatement stmt, QueryOptions options, long timestamp)
{
- this.cbuilder = cbuilder;
+ this.clustering = clustering;
this.stmt = stmt;
this.options = options;
this.timestamp = timestamp;
@@ -201,7 +201,7 @@ public class CQL3CasRequest implements CASRequest
{
Map<DecoratedKey, Partition> map = stmt.requiresRead() ? Collections.<DecoratedKey, Partition>singletonMap(key, current) : null;
UpdateParameters params = new UpdateParameters(cfm, updates.columns(), options, timestamp, stmt.getTimeToLive(options), map, true);
- stmt.addUpdateForKey(updates, cbuilder, params);
+ stmt.addUpdateForKey(updates, clustering, params);
}
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e3727e3/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
index a33696e..cd6ce77 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
@@ -17,37 +17,38 @@
*/
package org.apache.cassandra.cql3.statements;
-import java.util.Iterator;
import java.util.List;
-import com.google.common.collect.Iterators;
-
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.*;
-import org.apache.cassandra.cql3.restrictions.Restriction;
-import org.apache.cassandra.db.CBuilder;
+import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
import org.apache.cassandra.db.Clustering;
+import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.utils.Pair;
+import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
+import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
+
/**
* A <code>DELETE</code> parsed from a CQL query statement.
*/
public class DeleteStatement extends ModificationStatement
{
- private DeleteStatement(StatementType type, int boundTerms, CFMetaData cfm, Attributes attrs)
+ private DeleteStatement(int boundTerms,
+ CFMetaData cfm,
+ Operations operations,
+ StatementRestrictions restrictions,
+ Conditions conditions,
+ Attributes attrs)
{
- super(type, boundTerms, cfm, attrs);
+ super(StatementType.DELETE, boundTerms, cfm, operations, restrictions, conditions, attrs);
}
- public boolean requireFullClusteringKey()
- {
- return false;
- }
-
- public void addUpdateForKey(PartitionUpdate update, CBuilder cbuilder, UpdateParameters params)
+ @Override
+ public void addUpdateForKey(PartitionUpdate update, Clustering clustering, UpdateParameters params)
throws InvalidRequestException
{
List<Operation> regularDeletions = getRegularOperations();
@@ -56,32 +57,29 @@ public class DeleteStatement extends ModificationStatement
if (regularDeletions.isEmpty() && staticDeletions.isEmpty())
{
// We're not deleting any specific columns so it's either a full partition deletion ....
- if (cbuilder.count() == 0)
+ if (clustering.size() == 0)
{
update.addPartitionDeletion(params.deletionTime());
}
// ... or a row deletion ...
- else if (cbuilder.remainingCount() == 0)
+ else if (clustering.size() == cfm.clusteringColumns().size())
{
- params.newRow(cbuilder.build());
+ params.newRow(clustering);
params.addRowDeletion();
update.add(params.buildRow());
}
// ... or a range of rows deletion.
else
{
- update.add(params.makeRangeTombstone(cbuilder));
+ update.add(params.makeRangeTombstone(cfm.comparator, clustering));
}
}
else
{
if (!regularDeletions.isEmpty())
{
- // We can't delete specific (regular) columns if not all clustering columns have been specified.
- if (cbuilder.remainingCount() > 0)
- throw new InvalidRequestException(String.format("Primary key column '%s' must be specified in order to delete column '%s'", getFirstEmptyKey().name, regularDeletions.get(0).column.name));
+ params.newRow(clustering);
- params.newRow(cbuilder.build());
for (Operation op : regularDeletions)
op.execute(update.partitionKey(), params);
update.add(params.buildRow());
@@ -99,21 +97,16 @@ public class DeleteStatement extends ModificationStatement
params.validateIndexedColumns(update);
}
- protected void validateWhereClauseForConditions() throws InvalidRequestException
+ @Override
+ public void addUpdateForKey(PartitionUpdate update, Slice slice, UpdateParameters params)
{
- Iterator<ColumnDefinition> iterator = Iterators.concat(cfm.partitionKeyColumns().iterator(), cfm.clusteringColumns().iterator());
- while (iterator.hasNext())
- {
- ColumnDefinition def = iterator.next();
- Restriction restriction = processedKeys.get(def.name);
- if (restriction == null || !(restriction.isEQ() || restriction.isIN()))
- {
- throw new InvalidRequestException(
- String.format("DELETE statements must restrict all PRIMARY KEY columns with equality relations in order " +
- "to use IF conditions, but column '%s' is not restricted", def.name));
- }
- }
+ List<Operation> regularDeletions = getRegularOperations();
+ List<Operation> staticDeletions = getStaticOperations();
+
+ checkTrue(regularDeletions.isEmpty() && staticDeletions.isEmpty(),
+ "Range deletions are not supported for specific columns");
+ update.add(params.makeRangeTombstone(slice));
}
public static class Parsed extends ModificationStatement.Parsed
@@ -133,28 +126,46 @@ public class DeleteStatement extends ModificationStatement
this.whereClause = whereClause;
}
- protected ModificationStatement prepareInternal(CFMetaData cfm, VariableSpecifications boundNames, Attributes attrs) throws InvalidRequestException
+
+ @Override
+ protected ModificationStatement prepareInternal(CFMetaData cfm,
+ VariableSpecifications boundNames,
+ Conditions conditions,
+ Attributes attrs)
{
- DeleteStatement stmt = new DeleteStatement(ModificationStatement.StatementType.DELETE, boundNames.size(), cfm, attrs);
+ Operations operations = new Operations();
for (Operation.RawDeletion deletion : deletions)
{
- ColumnIdentifier id = deletion.affectedColumn().prepare(cfm);
- ColumnDefinition def = cfm.getColumnDefinition(id);
- if (def == null)
- throw new InvalidRequestException(String.format("Unknown identifier %s", id));
+ ColumnDefinition def = getColumnDefinition(cfm, deletion.affectedColumn());
// For compact, we only have one value except the key, so the only form of DELETE that make sense is without a column
// list. However, we support having the value name for coherence with the static/sparse case
- if (def.isPrimaryKeyColumn())
- throw new InvalidRequestException(String.format("Invalid identifier %s for deletion (should not be a PRIMARY KEY part)", def.name));
+ checkFalse(def.isPrimaryKeyColumn(), "Invalid identifier %s for deletion (should not be a PRIMARY KEY part)", def.name);
Operation op = deletion.prepare(cfm.ksName, def);
op.collectMarkerSpecification(boundNames);
- stmt.addOperation(op);
+ operations.add(op);
}
- stmt.processWhereClause(whereClause, boundNames);
+ StatementRestrictions restrictions = newRestrictions(StatementType.DELETE,
+ cfm,
+ boundNames,
+ operations,
+ whereClause,
+ conditions);
+
+ DeleteStatement stmt = new DeleteStatement(boundNames.size(),
+ cfm,
+ operations,
+ restrictions,
+ conditions,
+ attrs);
+
+ if (stmt.hasConditions())
+ checkTrue(restrictions.hasAllPKColumnsRestrictedByEqualities(),
+ "DELETE statements must restrict all PRIMARY KEY columns with equality relations" +
+ " in order to use IF conditions");
return stmt;
}
}