You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sl...@apache.org on 2014/08/18 10:22:48 UTC

[1/4] git commit: Properly reject operations on list index

Repository: cassandra
Updated Branches:
  refs/heads/cassandra-2.1 7035cfccf -> 5db108c31


Properly reject operations on list index

patch by slebresne; reviewed by thobbs for CASSANDRA-7499


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/700e8163
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/700e8163
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/700e8163

Branch: refs/heads/cassandra-2.1
Commit: 700e81634de3dde2d9c43bdb78716a7bb994c2ae
Parents: b87741c
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Mon Aug 18 10:01:58 2014 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Mon Aug 18 10:01:58 2014 +0200

----------------------------------------------------------------------
 CHANGES.txt                                       |  1 +
 .../cassandra/cql3/statements/BatchStatement.java |  3 +++
 .../cql3/statements/ModificationStatement.java    | 18 ++++++++++++++++--
 3 files changed, 20 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/700e8163/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 94169c1..f489702 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.0.10
+ * Properly reject operations on list index with conditions (CASSANDRA-7499)
  * (Hadoop) allow ACFRW to limit nodes to local DC (CASSANDRA-7252)
  * (cqlsh) Wait up to 10 sec for a tracing session (CASSANDRA-7222)
  * Fix NPE in FileCacheService.sizeInBytes (CASSANDRA-7756)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/700e8163/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 8fc1ecc..cbe3016 100644
--- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
@@ -125,6 +125,9 @@ public class BatchStatement implements CQLStatement, MeasurableForPreparedCache
                 throw new InvalidRequestException("Timestamp must be set either on BATCH or individual statements");
 
             statement.validate(state);
+
+            if (hasConditions && statement.requiresRead())
+                throw new InvalidRequestException("Operations using list indexes are not allowed with IF conditions");
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/700e8163/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index 11aa0b1..99dd9d9 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@ -151,8 +151,14 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
 
     public void validate(ClientState state) throws InvalidRequestException
     {
-        if (hasConditions() && attrs.isTimestampSet())
-            throw new InvalidRequestException("Cannot provide custom timestamp for conditional update");
+        if (hasConditions())
+        {
+            if (attrs.isTimestampSet())
+                throw new InvalidRequestException("Cannot provide custom timestamp for conditional update");
+
+            if (requiresRead())
+                throw new InvalidRequestException("Operations using list indexes are not allowed with IF conditions");
+        }
 
         if (isCounter())
         {
@@ -439,6 +445,14 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
         return null;
     }
 
+    public boolean requiresRead()
+    {
+        for (Operation op : columnOperations)
+            if (op.requiresRead())
+                return true;
+        return false;
+    }
+
     protected Map<ByteBuffer, ColumnGroupMap> readRequiredRows(Collection<ByteBuffer> partitionKeys, ColumnNameBuilder clusteringPrefix, boolean local, ConsistencyLevel cl)
     throws RequestExecutionException, RequestValidationException
     {


[4/4] git commit: Support list index operation with conditions

Posted by sl...@apache.org.
Support list index operation with conditions

patch by slebresne; reviewed by thobbs for CASSANDRA-7499


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/5db108c3
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/5db108c3
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/5db108c3

Branch: refs/heads/cassandra-2.1
Commit: 5db108c314fa5064669eefef8e5f6a52a1500b96
Parents: 2ea11c1
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Mon Aug 18 10:21:44 2014 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Mon Aug 18 10:22:40 2014 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 build.xml                                       |   2 +-
 .../cql3/statements/BatchStatement.java         |  23 +-
 .../cql3/statements/CQL3CasConditions.java      | 203 --------------
 .../cql3/statements/CQL3CasRequest.java         | 268 +++++++++++++++++++
 .../cql3/statements/ModificationStatement.java  |  57 ++--
 .../apache/cassandra/service/CASConditions.java |  39 ---
 .../apache/cassandra/service/CASRequest.java    |  45 ++++
 .../apache/cassandra/service/StorageProxy.java  |  13 +-
 .../cassandra/thrift/CassandraServer.java       |  16 +-
 10 files changed, 353 insertions(+), 314 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 4fa537b..cecf153 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.1.1
+ * Support list index operations with conditions (CASSANDRA-7499)
  * Add max live/tombstoned cells to nodetool cfstats output (CASSANDRA-7731)
  * Validate IPv6 wildcard addresses properly (CASSANDRA-7680)
  * (cqlsh) Error when tracing query (CASSANDRA-7613)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/build.xml
----------------------------------------------------------------------
diff --git a/build.xml b/build.xml
index 54f5f3d..d747bbc 100644
--- a/build.xml
+++ b/build.xml
@@ -25,7 +25,7 @@
     <property name="debuglevel" value="source,lines,vars"/>
 
     <!-- default version and SCM information -->
-    <property name="base.version" value="2.1.0-rc6"/>
+    <property name="base.version" value="2.1.1"/>
     <property name="scm.connection" value="scm:git://git.apache.org/cassandra.git"/>
     <property name="scm.developerConnection" value="scm:git://git.apache.org/cassandra.git"/>
     <property name="scm.url" value="http://git-wip-us.apache.org/repos/asf?p=cassandra.git;a=tree"/>

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/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 90be914..17d1771 100644
--- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
@@ -149,9 +149,6 @@ public class BatchStatement implements CQLStatement, MeasurableForPreparedCache
                     throw new InvalidRequestException("Batch with conditions cannot span multiple tables");
                 ksName = stmt.keyspace();
                 cfName = stmt.columnFamily();
-
-                if (stmt.requiresRead())
-                    throw new InvalidRequestException("Operations using list indexes are not allowed with IF conditions");
             }
         }
     }
@@ -240,7 +237,7 @@ public class BatchStatement implements CQLStatement, MeasurableForPreparedCache
      * Checks batch size to ensure threshold is met. If not, a warning is logged.
      * @param cfs ColumnFamilies that will store the batch's mutations.
      */
-    private void verifyBatchSize(Iterable<ColumnFamily> cfs)
+    public static void verifyBatchSize(Iterable<ColumnFamily> cfs)
     {
         long size = 0;
         long warnThreshold = DatabaseDescriptor.getBatchSizeWarnThreshold();
@@ -306,8 +303,7 @@ public class BatchStatement implements CQLStatement, MeasurableForPreparedCache
         ByteBuffer key = null;
         String ksName = null;
         String cfName = null;
-        ColumnFamily updates = null;
-        CQL3CasConditions conditions = null;
+        CQL3CasRequest casRequest = null;
         Set<ColumnDefinition> columnsWithConditions = new LinkedHashSet<>();
 
         for (int i = 0; i < statements.size(); i++)
@@ -323,8 +319,7 @@ public class BatchStatement implements CQLStatement, MeasurableForPreparedCache
                 key = pks.get(0);
                 ksName = statement.cfm.ksName;
                 cfName = statement.cfm.cfName;
-                conditions = new CQL3CasConditions(statement.cfm, now);
-                updates = ArrayBackedSortedColumns.factory.create(statement.cfm);
+                casRequest = new CQL3CasRequest(statement.cfm, key, true);
             }
             else if (!key.equals(pks.get(0)))
             {
@@ -334,22 +329,18 @@ public class BatchStatement implements CQLStatement, MeasurableForPreparedCache
             Composite clusteringPrefix = statement.createClusteringPrefix(statementOptions);
             if (statement.hasConditions())
             {
-                statement.addUpdatesAndConditions(key, clusteringPrefix, updates, conditions, statementOptions, timestamp);
+                statement.addConditions(clusteringPrefix, 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());
             }
-            else
-            {
-                UpdateParameters params = statement.makeUpdateParameters(Collections.singleton(key), clusteringPrefix, statementOptions, false, now);
-                statement.addUpdateForKey(updates, key, clusteringPrefix, params);
-            }
+            casRequest.addRowUpdate(clusteringPrefix, statement, statementOptions, timestamp);
         }
 
-        verifyBatchSize(Collections.singleton(updates));
-        ColumnFamily result = StorageProxy.cas(ksName, cfName, key, conditions, updates, options.getSerialConsistency(), options.getConsistency());
+        ColumnFamily result = StorageProxy.cas(ksName, cfName, key, casRequest, options.getSerialConsistency(), options.getConsistency());
+
         return new ResultMessage.Rows(ModificationStatement.buildCasResultSet(ksName, key, cfName, result, columnsWithConditions, true, options.forStatement(0)));
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/src/java/org/apache/cassandra/cql3/statements/CQL3CasConditions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CQL3CasConditions.java b/src/java/org/apache/cassandra/cql3/statements/CQL3CasConditions.java
deleted file mode 100644
index 8b5a403..0000000
--- a/src/java/org/apache/cassandra/cql3/statements/CQL3CasConditions.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * 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.statements;
-
-import java.nio.ByteBuffer;
-import java.util.*;
-
-import org.apache.cassandra.cql3.*;
-import org.apache.cassandra.config.CFMetaData;
-import org.apache.cassandra.db.*;
-import org.apache.cassandra.db.composites.Composite;
-import org.apache.cassandra.db.filter.*;
-import org.apache.cassandra.exceptions.InvalidRequestException;
-import org.apache.cassandra.service.CASConditions;
-import org.apache.cassandra.utils.Pair;
-
-/**
- * Processed CAS conditions on potentially multiple rows of the same partition.
- */
-public class CQL3CasConditions implements CASConditions
-{
-    private final CFMetaData cfm;
-    private final long now;
-
-    // We index RowCondition by the prefix of the row they applied to for 2 reasons:
-    //   1) this allows to keep things sorted to build the ColumnSlice array below
-    //   2) this allows to detect when contradictory conditions are set (not exists with some other conditions on the same row)
-    private final SortedMap<Composite, RowCondition> conditions;
-
-    public CQL3CasConditions(CFMetaData cfm, long now)
-    {
-        this.cfm = cfm;
-        // We will use now for Cell.isLive() which expects milliseconds but the argument is in microseconds.
-        this.now = now / 1000;
-        this.conditions = new TreeMap<>(cfm.comparator);
-    }
-
-    public void addNotExist(Composite prefix) throws InvalidRequestException
-    {
-        RowCondition previous = conditions.put(prefix, new NotExistCondition(prefix, now));
-        if (previous != null && !(previous instanceof NotExistCondition))
-        {
-            // these should be prevented by the parser, but it doesn't hurt to check
-            if (previous instanceof ExistCondition)
-                throw new InvalidRequestException("Cannot mix IF EXISTS and IF NOT EXISTS conditions for the same row");
-            else
-                throw new InvalidRequestException("Cannot mix IF conditions and IF NOT EXISTS for the same row");
-        }
-    }
-
-    public void addExist(Composite prefix) throws InvalidRequestException
-    {
-        RowCondition previous = conditions.put(prefix, new ExistCondition(prefix, now));
-        // this should be prevented by the parser, but it doesn't hurt to check
-        if (previous != null && previous instanceof NotExistCondition)
-            throw new InvalidRequestException("Cannot mix IF EXISTS and IF NOT EXISTS conditions for the same row");
-    }
-
-    public void addConditions(Composite prefix, Collection<ColumnCondition> conds, QueryOptions options) throws InvalidRequestException
-    {
-        RowCondition condition = conditions.get(prefix);
-        if (condition == null)
-        {
-            condition = new ColumnsConditions(prefix, now);
-            conditions.put(prefix, condition);
-        }
-        else if (!(condition instanceof ColumnsConditions))
-        {
-            throw new InvalidRequestException("Cannot mix IF conditions and IF NOT EXISTS for the same row");
-        }
-        ((ColumnsConditions)condition).addConditions(conds, options);
-    }
-
-    public IDiskAtomFilter readFilter()
-    {
-        assert !conditions.isEmpty();
-        ColumnSlice[] slices = new ColumnSlice[conditions.size()];
-        int i = 0;
-        // We always read CQL rows entirely as on CAS failure we want to be able to distinguish between "row exists
-        // but all values for which there were conditions are null" and "row doesn't exists", and we can't rely on the
-        // row marker for that (see #6623)
-        for (Composite prefix : conditions.keySet())
-            slices[i++] = prefix.slice();
-
-        int toGroup = cfm.comparator.isDense() ? -1 : cfm.clusteringColumns().size();
-        assert ColumnSlice.validateSlices(slices, cfm.comparator, false);
-        return new SliceQueryFilter(slices, false, slices.length, toGroup);
-    }
-
-    public boolean appliesTo(ColumnFamily current) throws InvalidRequestException
-    {
-        for (RowCondition condition : conditions.values())
-        {
-            if (!condition.appliesTo(current))
-                return false;
-        }
-        return true;
-    }
-
-    private static abstract class RowCondition
-    {
-        public final Composite rowPrefix;
-        protected final long now;
-
-        protected RowCondition(Composite rowPrefix, long now)
-        {
-            this.rowPrefix = rowPrefix;
-            this.now = now;
-        }
-
-        public abstract boolean appliesTo(ColumnFamily current) throws InvalidRequestException;
-    }
-
-    private static class NotExistCondition extends RowCondition
-    {
-        private NotExistCondition(Composite rowPrefix, long now)
-        {
-            super(rowPrefix, now);
-        }
-
-        public boolean appliesTo(ColumnFamily current)
-        {
-            if (current == null)
-                return true;
-
-            Iterator<Cell> iter = current.iterator(new ColumnSlice[]{ rowPrefix.slice() });
-            while (iter.hasNext())
-                if (iter.next().isLive(now))
-                    return false;
-            return true;
-        }
-    }
-
-    private static class ExistCondition extends RowCondition
-    {
-        private ExistCondition(Composite rowPrefix, long now)
-        {
-            super (rowPrefix, now);
-        }
-
-        public boolean appliesTo(ColumnFamily current)
-        {
-            if (current == null)
-                return false;
-
-            Iterator<Cell> iter = current.iterator(new ColumnSlice[]{ rowPrefix.slice() });
-            while (iter.hasNext())
-                if (iter.next().isLive(now))
-                    return true;
-            return false;
-        }
-    }
-
-    private static class ColumnsConditions extends RowCondition
-    {
-        private final Map<Pair<ColumnIdentifier, ByteBuffer>, ColumnCondition.Bound> conditions = new HashMap<>();
-
-        private ColumnsConditions(Composite rowPrefix, long now)
-        {
-            super(rowPrefix, now);
-        }
-
-        public void addConditions(Collection<ColumnCondition> conds, QueryOptions options) throws InvalidRequestException
-        {
-            for (ColumnCondition condition : conds)
-            {
-                // We will need the variables in appliesTo but with protocol batches, each condition in this object can have a
-                // different list of variables.
-                ColumnCondition.Bound current = condition.bind(options);
-                ColumnCondition.Bound previous = conditions.put(Pair.create(condition.column.name, current.getCollectionElementValue()), current);
-                // If 2 conditions are actually equal, let it slide
-                if (previous != null && !previous.equals(current))
-                    throw new InvalidRequestException("Duplicate and incompatible conditions for column " + condition.column.name);
-            }
-        }
-
-        public boolean appliesTo(ColumnFamily current) throws InvalidRequestException
-        {
-            if (current == null)
-                return conditions.isEmpty();
-
-            for (ColumnCondition.Bound condition : conditions.values())
-                if (!condition.appliesTo(rowPrefix, current, now))
-                    return false;
-            return true;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/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
new file mode 100644
index 0000000..a85c1e5
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.statements;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.config.CFMetaData;
+import org.apache.cassandra.db.*;
+import org.apache.cassandra.db.composites.Composite;
+import org.apache.cassandra.db.filter.*;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.service.CASRequest;
+import org.apache.cassandra.utils.Pair;
+
+/**
+ * Processed CAS conditions and update on potentially multiple rows of the same partition.
+ */
+public class CQL3CasRequest implements CASRequest
+{
+    private final CFMetaData cfm;
+    private final ByteBuffer key;
+    private final long now;
+    private final boolean isBatch;
+
+    // We index RowCondition by the prefix of the row they applied to for 2 reasons:
+    //   1) this allows to keep things sorted to build the ColumnSlice array below
+    //   2) this allows to detect when contradictory conditions are set (not exists with some other conditions on the same row)
+    private final SortedMap<Composite, RowCondition> conditions;
+
+    private final List<RowUpdate> updates = new ArrayList<>();
+
+    public CQL3CasRequest(CFMetaData cfm, ByteBuffer key, boolean isBatch)
+    {
+        this.cfm = cfm;
+        // When checking if conditions apply, we want to use a fixed reference time for a whole request to check
+        // for expired cells. Note that this is unrelated to the cell timestamp.
+        this.now = System.currentTimeMillis();
+        this.key = key;
+        this.conditions = new TreeMap<>(cfm.comparator);
+        this.isBatch = isBatch;
+    }
+
+    public void addRowUpdate(Composite prefix, ModificationStatement stmt, QueryOptions options, long timestamp)
+    {
+        updates.add(new RowUpdate(prefix, stmt, options, timestamp));
+    }
+
+    public void addNotExist(Composite prefix) throws InvalidRequestException
+    {
+        RowCondition previous = conditions.put(prefix, new NotExistCondition(prefix, now));
+        if (previous != null && !(previous instanceof NotExistCondition))
+        {
+            // these should be prevented by the parser, but it doesn't hurt to check
+            if (previous instanceof ExistCondition)
+                throw new InvalidRequestException("Cannot mix IF EXISTS and IF NOT EXISTS conditions for the same row");
+            else
+                throw new InvalidRequestException("Cannot mix IF conditions and IF NOT EXISTS for the same row");
+        }
+    }
+
+    public void addExist(Composite prefix) throws InvalidRequestException
+    {
+        RowCondition previous = conditions.put(prefix, new ExistCondition(prefix, now));
+        // this should be prevented by the parser, but it doesn't hurt to check
+        if (previous != null && previous instanceof NotExistCondition)
+            throw new InvalidRequestException("Cannot mix IF EXISTS and IF NOT EXISTS conditions for the same row");
+    }
+
+    public void addConditions(Composite prefix, Collection<ColumnCondition> conds, QueryOptions options) throws InvalidRequestException
+    {
+        RowCondition condition = conditions.get(prefix);
+        if (condition == null)
+        {
+            condition = new ColumnsConditions(prefix, now);
+            conditions.put(prefix, condition);
+        }
+        else if (!(condition instanceof ColumnsConditions))
+        {
+            throw new InvalidRequestException("Cannot mix IF conditions and IF NOT EXISTS for the same row");
+        }
+        ((ColumnsConditions)condition).addConditions(conds, options);
+    }
+
+    public IDiskAtomFilter readFilter()
+    {
+        assert !conditions.isEmpty();
+        ColumnSlice[] slices = new ColumnSlice[conditions.size()];
+        int i = 0;
+        // We always read CQL rows entirely as on CAS failure we want to be able to distinguish between "row exists
+        // but all values for which there were conditions are null" and "row doesn't exists", and we can't rely on the
+        // row marker for that (see #6623)
+        for (Composite prefix : conditions.keySet())
+            slices[i++] = prefix.slice();
+
+        int toGroup = cfm.comparator.isDense() ? -1 : cfm.clusteringColumns().size();
+        assert ColumnSlice.validateSlices(slices, cfm.comparator, false);
+        return new SliceQueryFilter(slices, false, slices.length, toGroup);
+    }
+
+    public boolean appliesTo(ColumnFamily current) throws InvalidRequestException
+    {
+        for (RowCondition condition : conditions.values())
+        {
+            if (!condition.appliesTo(current))
+                return false;
+        }
+        return true;
+    }
+
+    public ColumnFamily makeUpdates(ColumnFamily current) throws InvalidRequestException
+    {
+        ColumnFamily cf = ArrayBackedSortedColumns.factory.create(cfm);
+        for (RowUpdate upd : updates)
+            upd.applyUpdates(current, cf);
+
+        if (isBatch)
+            BatchStatement.verifyBatchSize(Collections.singleton(cf));
+
+        return cf;
+    }
+
+    /**
+     * Due to some operation on lists, we can't generate the update that a given Modification statement does before
+     * we get the values read by the initial read of Paxos. A RowUpdate thus just store the relevant information
+     * (include the statement iself) to generate those updates. We'll have multiple RowUpdate for a Batch, otherwise
+     * we'll have only one.
+     */
+    private class RowUpdate
+    {
+        private final Composite rowPrefix;
+        private final ModificationStatement stmt;
+        private final QueryOptions options;
+        private final long timestamp;
+
+        private RowUpdate(Composite rowPrefix, ModificationStatement stmt, QueryOptions options, long timestamp)
+        {
+            this.rowPrefix = rowPrefix;
+            this.stmt = stmt;
+            this.options = options;
+            this.timestamp = timestamp;
+        }
+
+        public void applyUpdates(ColumnFamily current, ColumnFamily updates) throws InvalidRequestException
+        {
+            Map<ByteBuffer, CQL3Row> map = null;
+            if (stmt.requiresRead())
+            {
+                // Uses the "current" values read by Paxos for lists operation that requires a read
+                Iterator<CQL3Row> iter = cfm.comparator.CQL3RowBuilder(cfm, now).group(current.iterator(new ColumnSlice[]{ rowPrefix.slice() }));
+                if (iter.hasNext())
+                {
+                    map = Collections.singletonMap(key, iter.next());
+                    assert !iter.hasNext() : "We shoudn't be updating more than one CQL row per-ModificationStatement";
+                }
+            }
+
+            UpdateParameters params = new UpdateParameters(cfm, options, timestamp, stmt.getTimeToLive(options), map);
+            stmt.addUpdateForKey(updates, key, rowPrefix, params);
+        }
+    }
+
+    private static abstract class RowCondition
+    {
+        public final Composite rowPrefix;
+        protected final long now;
+
+        protected RowCondition(Composite rowPrefix, long now)
+        {
+            this.rowPrefix = rowPrefix;
+            this.now = now;
+        }
+
+        public abstract boolean appliesTo(ColumnFamily current) throws InvalidRequestException;
+    }
+
+    private static class NotExistCondition extends RowCondition
+    {
+        private NotExistCondition(Composite rowPrefix, long now)
+        {
+            super(rowPrefix, now);
+        }
+
+        public boolean appliesTo(ColumnFamily current)
+        {
+            if (current == null)
+                return true;
+
+            Iterator<Cell> iter = current.iterator(new ColumnSlice[]{ rowPrefix.slice() });
+            while (iter.hasNext())
+                if (iter.next().isLive(now))
+                    return false;
+            return true;
+        }
+    }
+
+    private static class ExistCondition extends RowCondition
+    {
+        private ExistCondition(Composite rowPrefix, long now)
+        {
+            super (rowPrefix, now);
+        }
+
+        public boolean appliesTo(ColumnFamily current)
+        {
+            if (current == null)
+                return false;
+
+            Iterator<Cell> iter = current.iterator(new ColumnSlice[]{ rowPrefix.slice() });
+            while (iter.hasNext())
+                if (iter.next().isLive(now))
+                    return true;
+            return false;
+        }
+    }
+
+    private static class ColumnsConditions extends RowCondition
+    {
+        private final Map<Pair<ColumnIdentifier, ByteBuffer>, ColumnCondition.Bound> conditions = new HashMap<>();
+
+        private ColumnsConditions(Composite rowPrefix, long now)
+        {
+            super(rowPrefix, now);
+        }
+
+        public void addConditions(Collection<ColumnCondition> conds, QueryOptions options) throws InvalidRequestException
+        {
+            for (ColumnCondition condition : conds)
+            {
+                // We will need the variables in appliesTo but with protocol batches, each condition in this object can have a
+                // different list of variables.
+                ColumnCondition.Bound current = condition.bind(options);
+                ColumnCondition.Bound previous = conditions.put(Pair.create(condition.column.name, current.getCollectionElementValue()), current);
+                // If 2 conditions are actually equal, let it slide
+                if (previous != null && !previous.equals(current))
+                    throw new InvalidRequestException("Duplicate and incompatible conditions for column " + condition.column.name);
+            }
+        }
+
+        public boolean appliesTo(ColumnFamily current) throws InvalidRequestException
+        {
+            if (current == null)
+                return conditions.isEmpty();
+
+            for (ColumnCondition.Bound condition : conditions.values())
+                if (!condition.appliesTo(rowPrefix, current, now))
+                    return false;
+            return true;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index fef0e94..774883d 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@ -147,14 +147,8 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
 
     public void validate(ClientState state) throws InvalidRequestException
     {
-        if (hasConditions())
-        {
-            if (attrs.isTimestampSet())
-                throw new InvalidRequestException("Cannot provide custom timestamp for conditional updates");
-
-            if (requiresRead())
-                throw new InvalidRequestException("Operations using list indexes are not allowed with IF conditions");
-        }
+        if (hasConditions() && attrs.isTimestampSet())
+            throw new InvalidRequestException("Cannot provide custom timestamp for conditional updates");
 
         if (isCounter() && attrs.isTimestampSet())
             throw new InvalidRequestException("Cannot provide custom timestamp for counter updates");
@@ -414,32 +408,20 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
 
     public boolean requiresRead()
     {
+        // Lists SET operation incurs a read.
         for (Operation op : columnOperations)
             if (op.requiresRead())
                 return true;
+
         return false;
     }
 
     protected Map<ByteBuffer, CQL3Row> readRequiredRows(Collection<ByteBuffer> partitionKeys, Composite clusteringPrefix, boolean local, ConsistencyLevel cl)
     throws RequestExecutionException, RequestValidationException
     {
-        // Lists SET operation incurs a read.
-        boolean requiresRead = false;
-        for (Operation op : columnOperations)
-        {
-            if (op.requiresRead())
-            {
-                requiresRead = true;
-                break;
-            }
-        }
-
-        return requiresRead ? readRows(partitionKeys, clusteringPrefix, cfm, local, cl) : null;
-    }
+        if (!requiresRead())
+            return null;
 
-    protected Map<ByteBuffer, CQL3Row> readRows(Collection<ByteBuffer> partitionKeys, Composite rowPrefix, CFMetaData cfm, boolean local, ConsistencyLevel cl)
-    throws RequestExecutionException, RequestValidationException
-    {
         try
         {
             cl.validateForRead(keyspace());
@@ -449,7 +431,7 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
             throw new InvalidRequestException(String.format("Write operation require a read but consistency %s is not supported on reads", cl));
         }
 
-        ColumnSlice[] slices = new ColumnSlice[]{ rowPrefix.slice() };
+        ColumnSlice[] slices = new ColumnSlice[]{ clusteringPrefix.slice() };
         List<ReadCommand> commands = new ArrayList<ReadCommand>(partitionKeys.size());
         long now = System.currentTimeMillis();
         for (ByteBuffer key : partitionKeys)
@@ -527,46 +509,41 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
             throw new InvalidRequestException("IN on the partition key is not supported with conditional updates");
 
         ByteBuffer key = keys.get(0);
-
         long now = options.getTimestamp(queryState);
-        CQL3CasConditions conditions = new CQL3CasConditions(cfm, now);
         Composite prefix = createClusteringPrefix(options);
-        ColumnFamily updates = ArrayBackedSortedColumns.factory.create(cfm);
-        addUpdatesAndConditions(key, prefix, updates, conditions, options, getTimestamp(now, options));
+
+        CQL3CasRequest request = new CQL3CasRequest(cfm, key, false);
+        addConditions(prefix, request, options);
+        request.addRowUpdate(prefix, this, options, now);
 
         ColumnFamily result = StorageProxy.cas(keyspace(),
                                                columnFamily(),
                                                key,
-                                               conditions,
-                                               updates,
+                                               request,
                                                options.getSerialConsistency(),
                                                options.getConsistency());
         return new ResultMessage.Rows(buildCasResultSet(key, result, options));
     }
 
-    public void addUpdatesAndConditions(ByteBuffer key, Composite clusteringPrefix, ColumnFamily updates, CQL3CasConditions conditions, QueryOptions options, long now)
-    throws InvalidRequestException
+    public void addConditions(Composite clusteringPrefix, CQL3CasRequest request, QueryOptions options) throws InvalidRequestException
     {
-        UpdateParameters updParams = new UpdateParameters(cfm, options, now, getTimeToLive(options), null);
-        addUpdateForKey(updates, key, clusteringPrefix, updParams);
-
         if (ifNotExists)
         {
             // If we use ifNotExists, if the statement applies to any non static columns, then the condition is on the row of the non-static
             // columns and the prefix should be the clusteringPrefix. But if only static columns are set, then the ifNotExists apply to the existence
             // of any static columns and we should use the prefix for the "static part" of the partition.
-            conditions.addNotExist(clusteringPrefix);
+            request.addNotExist(clusteringPrefix);
         }
         else if (ifExists)
         {
-            conditions.addExist(clusteringPrefix);
+            request.addExist(clusteringPrefix);
         }
         else
         {
             if (columnConditions != null)
-                conditions.addConditions(clusteringPrefix, columnConditions, options);
+                request.addConditions(clusteringPrefix, columnConditions, options);
             if (staticConditions != null)
-                conditions.addConditions(cfm.comparator.staticPrefix(), staticConditions, options);
+                request.addConditions(cfm.comparator.staticPrefix(), staticConditions, options);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/src/java/org/apache/cassandra/service/CASConditions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/service/CASConditions.java b/src/java/org/apache/cassandra/service/CASConditions.java
deleted file mode 100644
index c0a2111..0000000
--- a/src/java/org/apache/cassandra/service/CASConditions.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.service;
-
-import org.apache.cassandra.db.ColumnFamily;
-import org.apache.cassandra.db.filter.IDiskAtomFilter;
-import org.apache.cassandra.exceptions.InvalidRequestException;
-
-/**
- * Abstract the conditions to be fulfilled by a CAS operation.
- */
-public interface CASConditions
-{
-    /**
-     * The filter to use to fetch the value to compare for the CAS.
-     */
-    public IDiskAtomFilter readFilter();
-
-    /**
-     * Returns whether the provided CF, that represents the values fetched using the
-     * readFilter(), match the CAS conditions this object stands for.
-     */
-    public boolean appliesTo(ColumnFamily current) throws InvalidRequestException;
-}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/src/java/org/apache/cassandra/service/CASRequest.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/service/CASRequest.java b/src/java/org/apache/cassandra/service/CASRequest.java
new file mode 100644
index 0000000..3d86637
--- /dev/null
+++ b/src/java/org/apache/cassandra/service/CASRequest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.service;
+
+import org.apache.cassandra.db.ColumnFamily;
+import org.apache.cassandra.db.filter.IDiskAtomFilter;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+
+/**
+ * Abstract the conditions and updates for a CAS operation.
+ */
+public interface CASRequest
+{
+    /**
+     * The filter to use to fetch the value to compare for the CAS.
+     */
+    public IDiskAtomFilter readFilter();
+
+    /**
+     * Returns whether the provided CF, that represents the values fetched using the
+     * readFilter(), match the CAS conditions this object stands for.
+     */
+    public boolean appliesTo(ColumnFamily current) throws InvalidRequestException;
+
+    /**
+     * The updates to perform of a CAS success. The values fetched using the readFilter()
+     * are passed as argument.
+     */
+    public ColumnFamily makeUpdates(ColumnFamily current) throws InvalidRequestException;
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/src/java/org/apache/cassandra/service/StorageProxy.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/service/StorageProxy.java b/src/java/org/apache/cassandra/service/StorageProxy.java
index 62fc0d0..1c0c482 100644
--- a/src/java/org/apache/cassandra/service/StorageProxy.java
+++ b/src/java/org/apache/cassandra/service/StorageProxy.java
@@ -190,8 +190,7 @@ public class StorageProxy implements StorageProxyMBean
      * @param keyspaceName the keyspace for the CAS
      * @param cfName the column family for the CAS
      * @param key the row key for the row to CAS
-     * @param conditions the conditions for the CAS to apply.
-     * @param updates the value to insert if {@code condtions} matches the current values.
+     * @param request the conditions for the CAS to apply as well as the update to perform if the conditions hold.
      * @param consistencyForPaxos the consistency for the paxos prepare and propose round. This can only be either SERIAL or LOCAL_SERIAL.
      * @param consistencyForCommit the consistency for write done during the commit phase. This can be anything, except SERIAL or LOCAL_SERIAL.
      *
@@ -201,8 +200,7 @@ public class StorageProxy implements StorageProxyMBean
     public static ColumnFamily cas(String keyspaceName,
                                    String cfName,
                                    ByteBuffer key,
-                                   CASConditions conditions,
-                                   ColumnFamily updates,
+                                   CASRequest request,
                                    ConsistencyLevel consistencyForPaxos,
                                    ConsistencyLevel consistencyForCommit)
     throws UnavailableException, IsBootstrappingException, ReadTimeoutException, WriteTimeoutException, InvalidRequestException
@@ -226,18 +224,19 @@ public class StorageProxy implements StorageProxyMBean
             // read the current values and check they validate the conditions
             Tracing.trace("Reading existing values for CAS precondition");
             long timestamp = System.currentTimeMillis();
-            ReadCommand readCommand = ReadCommand.create(keyspaceName, key, cfName, timestamp, conditions.readFilter());
+            ReadCommand readCommand = ReadCommand.create(keyspaceName, key, cfName, timestamp, request.readFilter());
             List<Row> rows = read(Arrays.asList(readCommand), consistencyForPaxos == ConsistencyLevel.LOCAL_SERIAL? ConsistencyLevel.LOCAL_QUORUM : ConsistencyLevel.QUORUM);
             ColumnFamily current = rows.get(0).cf;
-            if (!conditions.appliesTo(current))
+            if (!request.appliesTo(current))
             {
-                Tracing.trace("CAS precondition {} does not match current values {}", conditions, current);
+                Tracing.trace("CAS precondition does not match current values {}", current);
                 // We should not return null as this means success
                 return current == null ? ArrayBackedSortedColumns.factory.create(metadata) : current;
             }
 
             // finish the paxos round w/ the desired updates
             // TODO turn null updates into delete?
+            ColumnFamily updates = request.makeUpdates(current);
 
             // Apply triggers to cas updates. A consideration here is that
             // triggers emit Mutations, and so a given trigger implementation

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5db108c3/src/java/org/apache/cassandra/thrift/CassandraServer.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/thrift/CassandraServer.java b/src/java/org/apache/cassandra/thrift/CassandraServer.java
index 33cd012..de0b125 100644
--- a/src/java/org/apache/cassandra/thrift/CassandraServer.java
+++ b/src/java/org/apache/cassandra/thrift/CassandraServer.java
@@ -63,7 +63,7 @@ import org.apache.cassandra.locator.DynamicEndpointSnitch;
 import org.apache.cassandra.metrics.ClientMetrics;
 import org.apache.cassandra.scheduler.IRequestScheduler;
 import org.apache.cassandra.serializers.MarshalException;
-import org.apache.cassandra.service.CASConditions;
+import org.apache.cassandra.service.CASRequest;
 import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.MigrationManager;
 import org.apache.cassandra.service.StorageProxy;
@@ -784,8 +784,7 @@ public class CassandraServer implements Cassandra.Iface
             ColumnFamily result = StorageProxy.cas(cState.getKeyspace(),
                                                    column_family,
                                                    key,
-                                                   new ThriftCASConditions(cfExpected),
-                                                   cfUpdates,
+                                                   new ThriftCASRequest(cfExpected, cfUpdates),
                                                    ThriftConversion.fromThrift(serial_consistency_level),
                                                    ThriftConversion.fromThrift(commit_consistency_level));
             return result == null
@@ -2249,13 +2248,15 @@ public class CassandraServer implements Cassandra.Iface
         });
     }
 
-    private static class ThriftCASConditions implements CASConditions
+    private static class ThriftCASRequest implements CASRequest
     {
         private final ColumnFamily expected;
+        private final ColumnFamily updates;
 
-        private ThriftCASConditions(ColumnFamily expected)
+        private ThriftCASRequest(ColumnFamily expected, ColumnFamily updates)
         {
             this.expected = expected;
+            this.updates = updates;
         }
 
         public IDiskAtomFilter readFilter()
@@ -2300,10 +2301,9 @@ public class CassandraServer implements Cassandra.Iface
             return cf != null && !cf.hasOnlyTombstones(now);
         }
 
-        @Override
-        public String toString()
+        public ColumnFamily makeUpdates(ColumnFamily current)
         {
-            return expected.toString();
+            return updates;
         }
     }
 }


[3/4] git commit: Merge branch 'cassandra-2.1.0' into cassandra-2.1

Posted by sl...@apache.org.
Merge branch 'cassandra-2.1.0' into cassandra-2.1

Conflicts:
	CHANGES.txt


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/2ea11c16
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/2ea11c16
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/2ea11c16

Branch: refs/heads/cassandra-2.1
Commit: 2ea11c1625b1e7658d6502c79996fc0d3a633c91
Parents: 7035cfc e850785
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Mon Aug 18 10:16:12 2014 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Mon Aug 18 10:16:12 2014 +0200

----------------------------------------------------------------------
 CHANGES.txt                                       |  6 ++++++
 .../cassandra/cql3/statements/BatchStatement.java |  3 +++
 .../cql3/statements/ModificationStatement.java    | 18 ++++++++++++++++--
 3 files changed, 25 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/2ea11c16/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index b8132ba,78ef5df..4fa537b
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,32 -1,8 +1,37 @@@
 +2.1.1
 + * Add max live/tombstoned cells to nodetool cfstats output (CASSANDRA-7731)
 + * Validate IPv6 wildcard addresses properly (CASSANDRA-7680)
 + * (cqlsh) Error when tracing query (CASSANDRA-7613)
 + * Avoid IOOBE when building SyntaxError message snippet (CASSANDRA-7569)
 + * SSTableExport uses correct validator to create string representation of partition
 +   keys (CASSANDRA-7498)
 + * Avoid NPEs when receiving type changes for an unknown keyspace (CASSANDRA-7689)
 + * Add support for custom 2i validation (CASSANDRA-7575)
 + * Pig support for hadoop CqlInputFormat (CASSANDRA-6454)
 + * Add listen_interface and rpc_interface options (CASSANDRA-7417)
 + * Improve schema merge performance (CASSANDRA-7444)
 + * Adjust MT depth based on # of partition validating (CASSANDRA-5263)
 + * Optimise NativeCell comparisons (CASSANDRA-6755)
 + * Configurable client timeout for cqlsh (CASSANDRA-7516)
 + * Include snippet of CQL query near syntax error in messages (CASSANDRA-7111)
 +Merged from 2.0:
 + * (Hadoop) allow ACFRW to limit nodes to local DC (CASSANDRA-7252)
 + * (cqlsh) cqlsh should automatically disable tracing when selecting
 +   from system_traces (CASSANDRA-7641)
 + * (Hadoop) Add CqlOutputFormat (CASSANDRA-6927)
 + * Don't depend on cassandra config for nodetool ring (CASSANDRA-7508)
 + * (cqlsh) Fix failing cqlsh formatting tests (CASSANDRA-7703)
 + * Fix IncompatibleClassChangeError from hadoop2 (CASSANDRA-7229)
 + * Add 'nodetool sethintedhandoffthrottlekb' (CASSANDRA-7635)
 + * (cqlsh) Add tab-completion for CREATE/DROP USER IF [NOT] EXISTS (CASSANDRA-7611)
 + * Catch errors when the JVM pulls the rug out from GCInspector (CASSANDRA-5345)
 + * cqlsh fails when version number parts are not int (CASSANDRA-7524)
 +
+ 2.1.0
+ Merged from 2.0:
+  * Properly reject operations on list index with conditions (CASSANDRA-7499)
+  * (Hadoop) allow ACFRW to limit nodes to local DC (CASSANDRA-7252)
+ 
  
  2.1.0-rc6
   * Fix OOM issue from netty caching over time (CASSANDRA-7743)


[2/4] git commit: Merge branch 'cassandra-2.0' into cassandra-2.1.0

Posted by sl...@apache.org.
Merge branch 'cassandra-2.0' into cassandra-2.1.0

Conflicts:
	CHANGES.txt
	src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
	src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/e8507851
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/e8507851
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/e8507851

Branch: refs/heads/cassandra-2.1
Commit: e85078519c6dd175856b5cf5783ca177bb136d99
Parents: cb772e5 700e816
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Mon Aug 18 10:15:32 2014 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Mon Aug 18 10:15:32 2014 +0200

----------------------------------------------------------------------
 CHANGES.txt                                       |  7 +++++++
 .../cassandra/cql3/statements/BatchStatement.java |  3 +++
 .../cql3/statements/ModificationStatement.java    | 18 ++++++++++++++++--
 3 files changed, 26 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/e8507851/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index eeb115f,f489702..78ef5df
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,24 -1,13 +1,31 @@@
 -2.0.10
++2.1.0
++Merged from 2.0:
+  * Properly reject operations on list index with conditions (CASSANDRA-7499)
+  * (Hadoop) allow ACFRW to limit nodes to local DC (CASSANDRA-7252)
++
++
 +2.1.0-rc6
 + * Fix OOM issue from netty caching over time (CASSANDRA-7743)
 + * json2sstable couldn't import JSON for CQL table (CASSANDRA-7477)
 + * Invalidate all caches on table drop (CASSANDRA-7561)
 + * Skip strict endpoint selection for ranges if RF == nodes (CASSANRA-7765)
 + * Fix Thrift range filtering without 2ary index lookups (CASSANDRA-7741)
 + * Add tracing entries about concurrent range requests (CASSANDRA-7599)
 + * (cqlsh) Fix DESCRIBE for NTS keyspaces (CASSANDRA-7729)
 + * Remove netty buffer ref-counting (CASSANDRA-7735)
 + * Pass mutated cf to index updater for use by PRSI (CASSANDRA-7742)
 + * Include stress yaml example in release and deb (CASSANDRA-7717)
 + * workaround for netty issue causing corrupted data off the wire (CASSANDRA-7695)
 + * cqlsh DESC CLUSTER fails retrieving ring information (CASSANDRA-7687)
 + * Fix binding null values inside UDT (CASSANDRA-7685)
 + * Fix UDT field selection with empty fields (CASSANDRA-7670)
 + * Bogus deserialization of static cells from sstable (CASSANDRA-7684)
 + * Fix NPE on compaction leftover cleanup for dropped table (CASSANDRA-7770)
 +Merged from 2.0:
+  * (cqlsh) Wait up to 10 sec for a tracing session (CASSANDRA-7222)
   * Fix NPE in FileCacheService.sizeInBytes (CASSANDRA-7756)
 - * (cqlsh) cqlsh should automatically disable tracing when selecting
 -   from system_traces (CASSANDRA-7641)
 - * (Hadoop) Add CqlOutputFormat (CASSANDRA-6927)
 - * Don't depend on cassandra config for nodetool ring (CASSANDRA-7508)
 - * (cqlsh) Fix failing cqlsh formatting tests (CASSANDRA-7703)
 + * Remove duplicates from StorageService.getJoiningNodes (CASSANDRA-7478)
 + * Clone token map outside of hot gossip loops (CASSANDRA-7758)
   * Fix MS expiring map timeout for Paxos messages (CASSANDRA-7752)
   * Do not flush on truncate if durable_writes is false (CASSANDRA-7750)
   * Give CRR a default input_cql Statement (CASSANDRA-7226)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/e8507851/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
index 88d23ca,cbe3016..90be914
--- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
@@@ -124,32 -124,10 +124,35 @@@ public class BatchStatement implements 
              if (timestampSet && statement.isTimestampSet())
                  throw new InvalidRequestException("Timestamp must be set either on BATCH or individual statements");
  
 -            statement.validate(state);
 +            if (type == Type.COUNTER && !statement.isCounter())
 +                throw new InvalidRequestException("Cannot include non-counter statement in a counter batch");
 +
 +            if (type == Type.LOGGED && statement.isCounter())
 +                throw new InvalidRequestException("Cannot include a counter statement in a logged batch");
 +
 +            if (statement.isCounter())
 +                hasCounters = true;
 +            else
 +                hasNonCounters = true;
 +        }
 +
 +        if (hasCounters && hasNonCounters)
 +            throw new InvalidRequestException("Counter and non-counter mutations cannot exist in the same batch");
  
 -            if (hasConditions && statement.requiresRead())
 -                throw new InvalidRequestException("Operations using list indexes are not allowed with IF conditions");
 +        if (hasConditions)
 +        {
 +            String ksName = null;
 +            String cfName = null;
 +            for (ModificationStatement stmt : statements)
 +            {
 +                if (ksName != null && (!stmt.keyspace().equals(ksName) || !stmt.columnFamily().equals(cfName)))
 +                    throw new InvalidRequestException("Batch with conditions cannot span multiple tables");
 +                ksName = stmt.keyspace();
 +                cfName = stmt.columnFamily();
++
++                if (stmt.requiresRead())
++                    throw new InvalidRequestException("Operations using list indexes are not allowed with IF conditions");
 +            }
          }
      }
  

http://git-wip-us.apache.org/repos/asf/cassandra/blob/e8507851/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index 478f596,99dd9d9..fef0e94
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@@ -147,14 -151,33 +147,20 @@@ public abstract class ModificationState
  
      public void validate(ClientState state) throws InvalidRequestException
      {
-         if (hasConditions() && attrs.isTimestampSet())
-             throw new InvalidRequestException("Cannot provide custom timestamp for conditional updates");
+         if (hasConditions())
+         {
+             if (attrs.isTimestampSet())
 -                throw new InvalidRequestException("Cannot provide custom timestamp for conditional update");
++                throw new InvalidRequestException("Cannot provide custom timestamp for conditional updates");
+ 
+             if (requiresRead())
+                 throw new InvalidRequestException("Operations using list indexes are not allowed with IF conditions");
+         }
  
 -        if (isCounter())
 -        {
 -            if (attrs.isTimestampSet() && !loggedCounterTimestamp)
 -            {
 -                logger.warn("Detected use of 'USING TIMESTAMP' in a counter UPDATE. This is invalid " +
 -                            "because counters do not use timestamps, and the timestamp has been ignored. " +
 -                            "Such queries will be rejected in Cassandra 2.1+ - please fix your queries before then.");
 -                loggedCounterTimestamp = true;
 -            }
 +        if (isCounter() && attrs.isTimestampSet())
 +            throw new InvalidRequestException("Cannot provide custom timestamp for counter updates");
  
 -            if (attrs.isTimeToLiveSet() && !loggedCounterTTL)
 -            {
 -                logger.warn("Detected use of 'USING TTL' in a counter UPDATE. This is invalid " +
 -                            "because counter tables do not support TTL, and the TTL value has been ignored. " +
 -                            "Such queries will be rejected in Cassandra 2.1+ - please fix your queries before then.");
 -                loggedCounterTTL = true;
 -            }
 -        }
 +        if (isCounter() && attrs.isTimeToLiveSet())
 +            throw new InvalidRequestException("Cannot provide custom TTL for counter updates");
      }
  
      public void addOperation(Operation op)
@@@ -406,7 -445,15 +412,15 @@@
          return null;
      }
  
+     public boolean requiresRead()
+     {
+         for (Operation op : columnOperations)
+             if (op.requiresRead())
+                 return true;
+         return false;
+     }
+ 
 -    protected Map<ByteBuffer, ColumnGroupMap> readRequiredRows(Collection<ByteBuffer> partitionKeys, ColumnNameBuilder clusteringPrefix, boolean local, ConsistencyLevel cl)
 +    protected Map<ByteBuffer, CQL3Row> readRequiredRows(Collection<ByteBuffer> partitionKeys, Composite clusteringPrefix, boolean local, ConsistencyLevel cl)
      throws RequestExecutionException, RequestValidationException
      {
          // Lists SET operation incurs a read.