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.