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;
         }
     }