You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by al...@apache.org on 2013/07/26 19:37:57 UTC
[1/2] git commit: Allow empty IN relations in SELECT/UPDATE/DELETE
statements
Updated Branches:
refs/heads/trunk 5c7b2e10c -> 1be575da0
Allow empty IN relations in SELECT/UPDATE/DELETE statements
patch by Aleksey Yeschenko; reviewed by Sylvain Lebresne for
CASSANDRA-5626
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/dc457c5e
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/dc457c5e
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/dc457c5e
Branch: refs/heads/trunk
Commit: dc457c5ebca5654c86b4df8a7574e579bae00b7e
Parents: dfa59e3
Author: Aleksey Yeschenko <al...@apache.org>
Authored: Fri Jul 26 20:02:44 2013 +0300
Committer: Aleksey Yeschenko <al...@apache.org>
Committed: Fri Jul 26 20:04:02 2013 +0300
----------------------------------------------------------------------
CHANGES.txt | 1 +
doc/cql3/CQL.textile | 12 ++--
src/java/org/apache/cassandra/cql3/Cql.g | 2 +-
.../apache/cassandra/cql3/QueryProcessor.java | 2 +-
.../cql3/statements/ModificationStatement.java | 6 +-
.../cql3/statements/SelectStatement.java | 69 ++++++++++----------
.../cql3/statements/UpdateStatement.java | 4 +-
7 files changed, 51 insertions(+), 45 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/dc457c5e/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 077a986..fe28eee 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,7 @@
1.2.8
* cqlsh: add collections support to COPY (CASSANDRA-5698)
* retry important messages for any IOException (CASSANDRA-5804)
+ * Allow empty IN relations in SELECT/UPDATE/DELETE statements (CASSANDRA-5626)
1.2.7
http://git-wip-us.apache.org/repos/asf/cassandra/blob/dc457c5e/doc/cql3/CQL.textile
----------------------------------------------------------------------
diff --git a/doc/cql3/CQL.textile b/doc/cql3/CQL.textile
index 44c4dd2..fe64c71 100644
--- a/doc/cql3/CQL.textile
+++ b/doc/cql3/CQL.textile
@@ -1,6 +1,6 @@
<link rel="StyleSheet" href="CQL.css" type="text/css" media="screen">
-h1. Cassandra Query Language (CQL) v3.0.4
+h1. Cassandra Query Language (CQL) v3.0.5
<span id="tableOfContents">
@@ -469,7 +469,7 @@ bc(syntax)..
| <identifier> '[' <term> ']' '=' <term>
<where-clause> ::= <identifier> '=' <term>
- | <identifier> IN '(' <term> ( ',' <term> )* ')'
+ | <identifier> IN '(' ( <term> ( ',' <term> )* )? ')'
<option> ::= TIMESTAMP <integer>
| TTL <integer>
@@ -515,7 +515,7 @@ bc(syntax)..
<selection> ::= <identifier> ( '[' <term> ']' )?
<where-clause> ::= <identifier> '=' <term>
- | <identifier> IN '(' <term> ( ',' <term> )* ')'
+ | <identifier> IN '(' ( <term> ( ',' <term> )* )? ')'
p.
__Sample:__
@@ -597,7 +597,7 @@ bc(syntax)..
<where-clause> ::= <relation> ( "AND" <relation> )*
<relation> ::= <identifier> ("=" | "<" | ">" | "<=" | ">=") <term>
- | <identifier> IN '(' <term> ( ',' <term>)* ')'
+ | <identifier> IN '(' ( <term> ( ',' <term>)* )? ')'
| TOKEN '(' <identifier> ')' ("=" | "<" | ">" | "<=" | ">=") (<term> | TOKEN '( <term> ')' )
<order-by> ::= <ordering> ( ',' <odering> )*
@@ -1052,6 +1052,10 @@ h2(#changes). Changes
The following describes the addition/changes brought for each version of CQL.
+h3. 3.0.5
+
+* SELECT, UPDATE, and DELETE statements now allow empty IN relations (see "CASSANDRA-5626":https://issues.apache.org/jira/browse/CASSANDRA-5626).
+
h3. 3.0.4
* Updated the syntax for custom "secondary indexes":#createIndexStmt.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/dc457c5e/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g
index 3ee30d1..6b31da5 100644
--- a/src/java/org/apache/cassandra/cql3/Cql.g
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -799,7 +799,7 @@ relation[List<Relation> clauses]
$clauses.add(new Relation(id, type, t, true));
}
| name=cident K_IN { Relation rel = Relation.createInRelation($name.id); }
- '(' f1=term { rel.addInValue(f1); } (',' fN=term { rel.addInValue(fN); } )* ')' { $clauses.add(rel); }
+ '(' ( f1=term { rel.addInValue(f1); } (',' fN=term { rel.addInValue(fN); } )* )? ')' { $clauses.add(rel); }
;
comparatorType returns [CQL3Type t]
http://git-wip-us.apache.org/repos/asf/cassandra/blob/dc457c5e/src/java/org/apache/cassandra/cql3/QueryProcessor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
index fa019c0..4ae1e90 100644
--- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java
+++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
@@ -41,7 +41,7 @@ import org.apache.cassandra.utils.SemanticVersion;
public class QueryProcessor
{
- public static final SemanticVersion CQL_VERSION = new SemanticVersion("3.0.4");
+ public static final SemanticVersion CQL_VERSION = new SemanticVersion("3.0.5");
private static final Logger logger = LoggerFactory.getLogger(QueryProcessor.class);
http://git-wip-us.apache.org/repos/asf/cassandra/blob/dc457c5e/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 322ce7f..5ede98f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@ -85,11 +85,13 @@ public abstract class ModificationStatement extends CFStatement implements CQLSt
validateConsistency(cl);
- Collection<? extends IMutation> mutations = getMutations(variables, false, cl, queryState.getTimestamp());
-
// The type should have been set by now or we have a bug
assert type != null;
+ Collection<? extends IMutation> mutations = getMutations(variables, false, cl, queryState.getTimestamp());
+ if (mutations.isEmpty())
+ return null;
+
switch (type)
{
case LOGGED:
http://git-wip-us.apache.org/repos/asf/cassandra/blob/dc457c5e/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 8343d1e..4f559e5 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -147,7 +147,7 @@ public class SelectStatement implements CQLStatement
static List<Row> readLocally(String keyspace, List<ReadCommand> cmds)
{
Table table = Table.open(keyspace);
- List<Row> rows = new ArrayList(cmds.size());
+ List<Row> rows = new ArrayList<Row>(cmds.size());
for (ReadCommand cmd : cmds)
rows.add(cmd.getRow(table));
return rows;
@@ -201,7 +201,11 @@ public class SelectStatement implements CQLStatement
private List<ReadCommand> getSliceCommands(List<ByteBuffer> variables) throws RequestValidationException
{
QueryPath queryPath = new QueryPath(columnFamily());
+
Collection<ByteBuffer> keys = getKeys(variables);
+ if (keys.isEmpty()) // in case of IN () for (the last column of) the partition key.
+ return null;
+
List<ReadCommand> commands = new ArrayList<ReadCommand>(keys.size());
// ...a range (slice) of column names
@@ -227,13 +231,17 @@ public class SelectStatement implements CQLStatement
else
{
// ByNames commands can share the filter
- IDiskAtomFilter filter = makeFilter(variables); // names filter are never null
+ IDiskAtomFilter filter = makeFilter(variables);
+ if (filter == null)
+ return null;
+
for (ByteBuffer key: keys)
{
QueryProcessor.validateKey(key);
commands.add(new SliceByNamesReadCommand(keyspace(), key, queryPath, (NamesQueryFilter)filter));
}
}
+
return commands;
}
@@ -370,6 +378,8 @@ public class SelectStatement implements CQLStatement
else
{
SortedSet<ByteBuffer> columnNames = getRequestedColumns(variables);
+ if (columnNames == null) // in case of IN () for the last column of the key
+ return null;
QueryProcessor.validateColumnNames(columnNames);
return new NamesQueryFilter(columnNames, true);
}
@@ -404,7 +414,7 @@ public class SelectStatement implements CQLStatement
}
else
{
- if (r.eqValues.size() > 1)
+ if (r.isINRestriction())
throw new InvalidRequestException("IN is only supported on the last column of the partition key");
ByteBuffer val = r.eqValues.get(0).bindAndGet(variables);
if (val == null)
@@ -479,11 +489,13 @@ public class SelectStatement implements CQLStatement
{
ColumnIdentifier id = idIter.next();
assert r != null && r.isEquality();
- if (r.eqValues.size() > 1)
+ if (r.isINRestriction())
{
// We have a IN, which we only support for the last column.
// If compact, just add all values and we're done. Otherwise,
// for each value of the IN, creates all the columns corresponding to the selection.
+ if (r.eqValues.isEmpty())
+ return null;
SortedSet<ByteBuffer> columns = new TreeSet<ByteBuffer>(cfDef.cfm.comparator);
Iterator<Term> iter = r.eqValues.iterator();
while (iter.hasNext())
@@ -600,7 +612,7 @@ public class SelectStatement implements CQLStatement
if (r.isEquality())
{
- if (r.eqValues.size() > 1)
+ if (r.isINRestriction())
{
// IN query, we only support it on the clustering column
assert name.position == names.size() - 1;
@@ -659,15 +671,13 @@ public class SelectStatement implements CQLStatement
Restriction restriction = entry.getValue();
if (restriction.isEquality())
{
- for (Term t : restriction.eqValues)
- {
- ByteBuffer value = t.bindAndGet(variables);
- if (value == null)
- throw new InvalidRequestException(String.format("Unsupported null value for indexed column %s", name));
- if (value.remaining() > 0xFFFF)
- throw new InvalidRequestException("Index expression values may not be larger than 64K");
- expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
- }
+ assert restriction.eqValues.size() == 1; // IN is not supported for indexed columns.
+ ByteBuffer value = restriction.eqValues.get(0).bindAndGet(variables);
+ if (value == null)
+ throw new InvalidRequestException(String.format("Unsupported null value for indexed column %s", name));
+ if (value.remaining() > 0xFFFF)
+ throw new InvalidRequestException("Index expression values may not be larger than 64K");
+ expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
}
else
{
@@ -992,7 +1002,6 @@ public class SelectStatement implements CQLStatement
CFDefinition cfDef = cfm.getCfDef();
ColumnSpecification[] names = new ColumnSpecification[getBoundsTerms()];
- IPartitioner partitioner = StorageService.getPartitioner();
// Select clause
if (parameters.isCount && !selectClause.isEmpty())
@@ -1008,7 +1017,7 @@ public class SelectStatement implements CQLStatement
* WHERE clause. For a given entity, rules are:
* - EQ relation conflicts with anything else (including a 2nd EQ)
* - Can't have more than one LT(E) relation (resp. GT(E) relation)
- * - IN relation are restricted to row keys (for now) and conflics with anything else
+ * - IN relation are restricted to row keys (for now) and conflicts with anything else
* (we could allow two IN for the same entity but that doesn't seem very useful)
* - The value_alias cannot be restricted in any way (we don't support wide rows with indexed value in CQL so far)
*/
@@ -1065,7 +1074,7 @@ public class SelectStatement implements CQLStatement
}
// We only support IN for the last name so far
// TODO: #3885 allows us to extend to other parts (cf. #4762)
- else if (restriction.eqValues.size() > 1)
+ else if (restriction.isINRestriction())
{
if (i != stmt.columnRestrictions.length - 1)
throw new InvalidRequestException(String.format("PRIMARY KEY part %s cannot be restricted by IN relation", cname));
@@ -1105,12 +1114,9 @@ public class SelectStatement implements CQLStatement
}
else if (restriction.onToken)
{
- // If this is a query on tokens, it's necessary a range query (there can be more than one key per token), so reject IN queries (as we don't know how to do them)
+ // If this is a query on tokens, it's necessarily a range query (there can be more than one key per token).
stmt.isKeyRange = true;
stmt.onToken = true;
-
- if (restriction.isEquality() && restriction.eqValues.size() > 1)
- throw new InvalidRequestException("Select using the token() function don't support IN clause");
}
else if (stmt.onToken)
{
@@ -1118,7 +1124,7 @@ public class SelectStatement implements CQLStatement
}
else if (restriction.isEquality())
{
- if (restriction.eqValues.size() > 1)
+ if (restriction.isINRestriction())
{
// We only support IN for the last name so far
if (i != stmt.keyRestrictions.length - 1)
@@ -1137,7 +1143,6 @@ public class SelectStatement implements CQLStatement
if (!stmt.metadataRestrictions.isEmpty())
{
stmt.isKeyRange = true;
- boolean hasEq = false;
Set<ByteBuffer> indexedNames = new HashSet<ByteBuffer>();
for (ColumnDefinition cfdef : cfm.getColumn_metadata().values())
{
@@ -1149,6 +1154,7 @@ public class SelectStatement implements CQLStatement
// Note: we cannot use idxManager.indexes() methods because we don't have a complete column name at this point, we only
// have the indexed component.
+ boolean hasEq = false;
for (Map.Entry<CFDefinition.Name, Restriction> entry : stmt.metadataRestrictions.entrySet())
{
Restriction restriction = entry.getValue();
@@ -1156,15 +1162,13 @@ public class SelectStatement implements CQLStatement
continue;
// We don't support IN for indexed values (basically this would require supporting a form of OR)
- if (restriction.eqValues.size() > 1)
+ if (restriction.isINRestriction())
throw new InvalidRequestException("Cannot use IN operator on column not part of the partition key");
if (indexedNames.contains(entry.getKey().name.key))
- {
hasEq = true;
- break;
- }
}
+
if (!hasEq)
throw new InvalidRequestException("No indexed columns present in by-columns clause with Equal operator");
@@ -1270,7 +1274,7 @@ public class SelectStatement implements CQLStatement
receiver = new ColumnSpecification(name.ksName,
name.cfName,
new ColumnIdentifier("partition key token", true),
- StorageService.instance.getPartitioner().getTokenValidator());
+ StorageService.getPartitioner().getTokenValidator());
}
switch (newRel.operator())
@@ -1370,14 +1374,9 @@ public class SelectStatement implements CQLStatement
return eqValues != null;
}
- public void setBound(Bound b, Term t)
- {
- bounds[b.idx] = t;
- }
-
- public void setInclusive(Bound b)
+ boolean isINRestriction()
{
- boundInclusive[b.idx] = true;
+ return isEquality() && (eqValues.isEmpty() || eqValues.size() > 1);
}
public Term bound(Bound b)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/dc457c5e/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
index f31196a..3796173 100644
--- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
@@ -173,7 +173,7 @@ public class UpdateStatement extends ModificationStatement
for (CFDefinition.Name name : cfDef.keys.values())
{
List<Term> values = processed.get(name.name);
- if (values == null || values.isEmpty())
+ if (values == null)
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));
if (keyBuilder.remainingCount() == 1)
@@ -188,7 +188,7 @@ public class UpdateStatement extends ModificationStatement
}
else
{
- if (values.size() > 1)
+ if (values.isEmpty() || values.size() > 1)
throw new InvalidRequestException("IN is only supported on the last column of the partition key");
ByteBuffer val = values.get(0).bindAndGet(variables);
if (val == null)
[2/2] git commit: Merge branch 'cassandra-1.2' into trunk
Posted by al...@apache.org.
Merge branch 'cassandra-1.2' into trunk
Conflicts:
doc/cql3/CQL.textile
src/java/org/apache/cassandra/cql3/QueryProcessor.java
src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/1be575da
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/1be575da
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/1be575da
Branch: refs/heads/trunk
Commit: 1be575da0601fed1c84421d6cd602fcb0a90da08
Parents: 5c7b2e1 dc457c5
Author: Aleksey Yeschenko <al...@apache.org>
Authored: Fri Jul 26 20:37:43 2013 +0300
Committer: Aleksey Yeschenko <al...@apache.org>
Committed: Fri Jul 26 20:37:43 2013 +0300
----------------------------------------------------------------------
CHANGES.txt | 1 +
doc/cql3/CQL.textile | 10 +++--
src/java/org/apache/cassandra/cql3/Cql.g | 2 +-
.../cql3/statements/ModificationStatement.java | 11 +++--
.../cql3/statements/SelectStatement.java | 47 +++++++++++---------
5 files changed, 43 insertions(+), 28 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/1be575da/CHANGES.txt
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/1be575da/doc/cql3/CQL.textile
----------------------------------------------------------------------
diff --cc doc/cql3/CQL.textile
index f63b6b8,fe64c71..dd76228
--- a/doc/cql3/CQL.textile
+++ b/doc/cql3/CQL.textile
@@@ -1077,13 -1052,10 +1077,17 @@@ h2(#changes). Change
The following describes the addition/changes brought for each version of CQL.
+h3. 3.1.0
+
+* "ALTER TABLE":#alterTableStmt @DROP@ option has been reenabled for CQL3 tables and has new semantics now: the space formerly used by dropped columns will now be eventually reclaimed (post-compaction). You should not readd previously dropped columns unless you use timestamps with microsecond precision (see "CASSANDRA-3919":https://issues.apache.org/jira/browse/CASSANDRA-3919 for more details).
+* @SELECT@ statement now supports aliases in select clause. Aliases in WHERE and ORDER BY clauses are not supported. See the "section on select"#selectStmt for details.
+* @CREATE@ statements for @KEYSPACE@, @TABLE@ and @INDEX@ now supports an @IF NOT EXISTS@ condition. Similarly, @DROP@ statements support a @IF EXISTS@ condition.
+* @INSERT@ statements optionally supports a @IF NOT EXISTS@ condition and @UDPATE@ supports @IF@ conditions.
+
+ h3. 3.0.5
+
+ * SELECT, UPDATE, and DELETE statements now allow empty IN relations (see "CASSANDRA-5626":https://issues.apache.org/jira/browse/CASSANDRA-5626).
+
h3. 3.0.4
* Updated the syntax for custom "secondary indexes":#createIndexStmt.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/1be575da/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/1be575da/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index 0aa188d,5ede98f..d08d0ee
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@@ -109,175 -69,76 +109,175 @@@ public abstract class ModificationState
public void validate(ClientState state) throws InvalidRequestException
{
- if (timeToLive < 0)
- throw new InvalidRequestException("A TTL must be greater or equal to 0");
-
- if (timeToLive > ExpiringColumn.MAX_TTL)
- throw new InvalidRequestException(String.format("ttl is too large. requested (%d) maximum (%d)", timeToLive, ExpiringColumn.MAX_TTL));
}
- protected abstract void validateConsistency(ConsistencyLevel cl) throws InvalidRequestException;
+ public void addOperation(Operation op)
+ {
+ columnOperations.add(op);
+ }
- public ResultMessage execute(ConsistencyLevel cl, QueryState queryState, List<ByteBuffer> variables) throws RequestExecutionException, RequestValidationException
+ public List<Operation> getOperations()
{
- if (cl == null)
- throw new InvalidRequestException("Invalid empty consistency level");
+ return columnOperations;
+ }
- validateConsistency(cl);
+ public void addCondition(Operation op)
+ {
+ if (columnConditions == null)
+ columnConditions = new ArrayList<Operation>();
- // The type should have been set by now or we have a bug
- assert type != null;
+ columnConditions.add(op);
+ }
- Collection<? extends IMutation> mutations = getMutations(variables, false, cl, queryState.getTimestamp());
- if (mutations.isEmpty())
- return null;
+ public void setIfNotExistCondition()
+ {
+ ifNotExists = true;
+ }
- switch (type)
- {
- case LOGGED:
- if (mutations.size() > 1)
- StorageProxy.mutateAtomically((Collection<RowMutation>) mutations, cl);
- else
- StorageProxy.mutate(mutations, cl);
- break;
- case UNLOGGED:
- case COUNTER:
- StorageProxy.mutate(mutations, cl);
- break;
- default:
- throw new AssertionError();
- }
+ private void addKeyValues(ColumnIdentifier name, List<Term> values) throws InvalidRequestException
+ {
+ if (processedKeys.put(name, values) != null)
+ throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name));
+ }
- return null;
+ public void addKeyValue(ColumnIdentifier name, Term value) throws InvalidRequestException
+ {
+ addKeyValues(name, Collections.singletonList(value));
}
- public ResultMessage executeInternal(QueryState queryState) throws RequestValidationException, RequestExecutionException
+ public void processWhereClause(List<Relation> whereClause, ColumnSpecification[] names) throws InvalidRequestException
{
- for (IMutation mutation : getMutations(Collections.<ByteBuffer>emptyList(), true, null, queryState.getTimestamp()))
- mutation.apply();
- return null;
+ CFDefinition cfDef = cfm.getCfDef();
+ for (Relation rel : whereClause)
+ {
+ CFDefinition.Name name = cfDef.get(rel.getEntity());
+ if (name == null)
+ throw new InvalidRequestException(String.format("Unknown key identifier %s", rel.getEntity()));
+
+ switch (name.kind)
+ {
+ case KEY_ALIAS:
+ case COLUMN_ALIAS:
+ List<Term.Raw> rawValues;
+ if (rel.operator() == Relation.Type.EQ)
+ rawValues = Collections.singletonList(rel.getValue());
+ else if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS && rel.operator() == Relation.Type.IN)
+ rawValues = rel.getInValues();
+ else
+ throw new InvalidRequestException(String.format("Invalid operator %s for PRIMARY KEY part %s", rel.operator(), name));
+
+ List<Term> values = new ArrayList<Term>(rawValues.size());
+ for (Term.Raw raw : rawValues)
+ {
+ Term t = raw.prepare(name);
+ t.collectMarkerSpecification(names);
+ values.add(t);
+ }
+ addKeyValues(name.name, values);
+ break;
+ case VALUE_ALIAS:
+ case COLUMN_METADATA:
+ throw new InvalidRequestException(String.format("Non PRIMARY KEY %s found in where clause", name));
+ }
+ }
}
- public long getTimestamp(long now)
+ private List<ByteBuffer> buildPartitionKeyNames(List<ByteBuffer> variables)
+ throws InvalidRequestException
{
- return timestamp == null ? now : timestamp;
+ CFDefinition cfDef = cfm.getCfDef();
+ ColumnNameBuilder keyBuilder = cfDef.getKeyNameBuilder();
+ List<ByteBuffer> keys = new ArrayList<ByteBuffer>();
+ for (CFDefinition.Name name : cfDef.keys.values())
+ {
+ List<Term> values = processedKeys.get(name.name);
- if (values == null || values.isEmpty())
++ if (values == null)
+ throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));
+
+ if (keyBuilder.remainingCount() == 1)
+ {
+ for (Term t : values)
+ {
+ ByteBuffer val = t.bindAndGet(variables);
+ if (val == null)
+ throw new InvalidRequestException(String.format("Invalid null value for partition key part %s", name));
+ keys.add(keyBuilder.copy().add(val).build());
+ }
+ }
+ else
+ {
- if (values.size() > 1)
++ if (values.isEmpty() || values.size() > 1)
+ throw new InvalidRequestException("IN is only supported on the last column of the partition key");
+ ByteBuffer val = values.get(0).bindAndGet(variables);
+ if (val == null)
+ throw new InvalidRequestException(String.format("Invalid null value for partition key part %s", name));
+ keyBuilder.add(val);
+ }
+ }
+ return keys;
}
- public void setTimestamp(long timestamp)
+ private ColumnNameBuilder createClusteringPrefixBuilder(List<ByteBuffer> variables)
+ throws InvalidRequestException
{
- this.timestamp = timestamp;
+ CFDefinition cfDef = cfm.getCfDef();
+ ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
+ CFDefinition.Name firstEmptyKey = null;
+ for (CFDefinition.Name name : cfDef.columns.values())
+ {
+ List<Term> values = processedKeys.get(name.name);
- if (values == null || values.isEmpty())
++ if (values == null)
+ {
+ firstEmptyKey = name;
+ if (requireFullClusteringKey() && cfDef.isComposite && !cfDef.isCompact)
+ throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));
+ }
+ else if (firstEmptyKey != null)
+ {
+ throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s since %s is set", firstEmptyKey.name, name.name));
+ }
+ else
+ {
+ assert values.size() == 1; // We only allow IN for row keys so far
+ ByteBuffer val = values.get(0).bindAndGet(variables);
+ if (val == null)
+ throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", name));
+ builder.add(val);
+ }
+ }
+ return builder;
}
- public boolean isSetTimestamp()
+ protected CFDefinition.Name getFirstEmptyKey()
{
- return timestamp != null;
+ for (CFDefinition.Name name : cfm.getCfDef().columns.values())
+ {
+ List<Term> values = processedKeys.get(name.name);
+ if (values == null || values.isEmpty())
+ return name;
+ }
+ return null;
}
- public int getTimeToLive()
+ protected Map<ByteBuffer, ColumnGroupMap> readRequiredRows(List<ByteBuffer> partitionKeys, ColumnNameBuilder clusteringPrefix, boolean local, ConsistencyLevel cl)
+ throws RequestExecutionException, RequestValidationException
{
- return timeToLive;
+ // Lists SET operation incurs a read.
+ Set<ByteBuffer> toRead = null;
+ for (Operation op : columnOperations)
+ {
+ if (op.requiresRead())
+ {
+ if (toRead == null)
+ toRead = new TreeSet<ByteBuffer>(UTF8Type.instance);
+ toRead.add(op.columnName.key);
+ }
+ }
+
+ return toRead == null ? null : readRows(partitionKeys, clusteringPrefix, toRead, (CompositeType)cfm.comparator, local, cl);
}
- protected Map<ByteBuffer, ColumnGroupMap> readRows(List<ByteBuffer> keys, ColumnNameBuilder builder, Set<ByteBuffer> toRead, CompositeType composite, boolean local, ConsistencyLevel cl)
+ private Map<ByteBuffer, ColumnGroupMap> readRows(List<ByteBuffer> partitionKeys, ColumnNameBuilder clusteringPrefix, Set<ByteBuffer> toRead, CompositeType composite, boolean local, ConsistencyLevel cl)
throws RequestExecutionException, RequestValidationException
{
try
@@@ -329,121 -188,6 +329,124 @@@
return map;
}
+ public boolean hasConditions()
+ {
+ return ifNotExists || (columnConditions != null && !columnConditions.isEmpty());
+ }
+
+ public ResultMessage execute(QueryState queryState, QueryOptions options)
+ throws RequestExecutionException, RequestValidationException
+ {
+ if (options.getConsistency() == null)
+ throw new InvalidRequestException("Invalid empty consistency level");
+
+ return hasConditions()
+ ? executeWithCondition(queryState, options)
+ : executeWithoutCondition(queryState, options);
+ }
+
+ private ResultMessage executeWithoutCondition(QueryState queryState, QueryOptions options)
+ throws RequestExecutionException, RequestValidationException
+ {
+ ConsistencyLevel cl = options.getConsistency();
+ if (isCounter())
+ cl.validateCounterForWrite(cfm);
+ else
+ cl.validateForWrite(cfm.ksName);
+
- StorageProxy.mutateWithTriggers(getMutations(options.getValues(), false, cl, queryState.getTimestamp(), false), cl, false);
++ Collection<? extends IMutation> mutations = getMutations(options.getValues(), false, cl, queryState.getTimestamp(), false);
++ if (!mutations.isEmpty())
++ StorageProxy.mutateWithTriggers(mutations, cl, false);
++
+ return null;
+ }
+
+ public ResultMessage executeWithCondition(QueryState queryState, QueryOptions options)
+ throws RequestExecutionException, RequestValidationException
+ {
+ List<ByteBuffer> variables = options.getValues();
+ List<ByteBuffer> keys = buildPartitionKeyNames(variables);
+ // We don't support IN for CAS operation so far
+ if (keys.size() > 1)
+ throw new InvalidRequestException("IN on the partition key is not supported with conditional updates");
+
+ ColumnNameBuilder clusteringPrefix = createClusteringPrefixBuilder(variables);
+ UpdateParameters params = new UpdateParameters(cfm, variables, getTimestamp(queryState.getTimestamp(), variables), getTimeToLive(variables), null);
+
+ ByteBuffer key = keys.get(0);
+ ThriftValidation.validateKey(cfm, key);
+
+ ColumnFamily updates = updateForKey(key, clusteringPrefix, params);
+ ColumnFamily expected = buildConditions(key, clusteringPrefix, params);
+
+ ColumnFamily result = StorageProxy.cas(keyspace(), columnFamily(), key, clusteringPrefix, expected, updates, options.getConsistency(), options.getSerialConsistency());
+ return new ResultMessage.Rows(buildCasResultSet(key, result));
+ }
+
+ private ResultSet buildCasResultSet(ByteBuffer key, ColumnFamily cf) throws InvalidRequestException
+ {
+ boolean success = cf == null;
+
+ ColumnSpecification spec = new ColumnSpecification(keyspace(), columnFamily(), CAS_RESULT_COLUMN, BooleanType.instance);
+ ResultSet.Metadata metadata = new ResultSet.Metadata(Collections.singletonList(spec));
+ List<List<ByteBuffer>> rows = Collections.singletonList(Collections.singletonList(BooleanType.instance.decompose(success)));
+
+ ResultSet rs = new ResultSet(metadata, rows);
+ return success ? rs : merge(rs, buildCasFailureResultSet(key, cf));
+ }
+
+ private static ResultSet merge(ResultSet left, ResultSet right)
+ {
+ if (left.size() == 0)
+ return right;
+ else if (right.size() == 0)
+ return left;
+
+ assert left.size() == 1 && right.size() == 1;
+ int size = left.metadata.names.size() + right.metadata.names.size();
+ List<ColumnSpecification> specs = new ArrayList<ColumnSpecification>(size);
+ specs.addAll(left.metadata.names);
+ specs.addAll(right.metadata.names);
+ List<ByteBuffer> row = new ArrayList<ByteBuffer>(size);
+ row.addAll(left.rows.get(0));
+ row.addAll(right.rows.get(0));
+ return new ResultSet(new ResultSet.Metadata(specs), Collections.singletonList(row));
+ }
+
+ private ResultSet buildCasFailureResultSet(ByteBuffer key, ColumnFamily cf) throws InvalidRequestException
+ {
+ CFDefinition cfDef = cfm.getCfDef();
+
+ Selection selection;
+ if (ifNotExists)
+ {
+ selection = Selection.wildcard(cfDef);
+ }
+ else
+ {
+ List<CFDefinition.Name> names = new ArrayList<CFDefinition.Name>(columnConditions.size());
+ for (Operation condition : columnConditions)
+ names.add(cfDef.get(condition.columnName));
+ selection = Selection.forColumns(names);
+ }
+
+ long now = System.currentTimeMillis();
+ Selection.ResultSetBuilder builder = selection.resultSetBuilder(now);
+ SelectStatement.forSelection(cfDef, selection).processColumnFamily(key, cf, Collections.<ByteBuffer>emptyList(), Integer.MAX_VALUE, now, builder);
+
+ return builder.build();
+ }
+
+ public ResultMessage executeInternal(QueryState queryState) throws RequestValidationException, RequestExecutionException
+ {
+ if (hasConditions())
+ throw new UnsupportedOperationException();
+
+ for (IMutation mutation : getMutations(Collections.<ByteBuffer>emptyList(), true, null, queryState.getTimestamp(), false))
+ mutation.apply();
+ return null;
+ }
+
/**
* Convert statement into a list of mutations to apply on the server
*
http://git-wip-us.apache.org/repos/asf/cassandra/blob/1be575da/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 8b9f7df,4f559e5..fbabf2f
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@@ -268,25 -198,50 +268,29 @@@ public class SelectStatement implement
return cfDef.cfm.cfName;
}
- private List<ReadCommand> getSliceCommands(List<ByteBuffer> variables) throws RequestValidationException
+ private List<ReadCommand> getSliceCommands(List<ByteBuffer> variables, int limit, long now) throws RequestValidationException
{
- QueryPath queryPath = new QueryPath(columnFamily());
-
Collection<ByteBuffer> keys = getKeys(variables);
+ if (keys.isEmpty()) // in case of IN () for (the last column of) the partition key.
+ return null;
+
List<ReadCommand> commands = new ArrayList<ReadCommand>(keys.size());
- // ...a range (slice) of column names
- if (isColumnRange())
- {
- // Note that we use the total limit for every key. This is
- // potentially inefficient, but then again, IN + LIMIT is not a
- // very sensible choice
- for (ByteBuffer key : keys)
- {
- QueryProcessor.validateKey(key);
- // Note that we should not share the slice filter amongst the command, due to SliceQueryFilter not
- // being immutable due to its columnCounter used by the lastCounted() method
- // (this is fairly ugly and we should change that but that's probably not a tiny refactor to do that cleanly)
- SliceQueryFilter filter = (SliceQueryFilter)makeFilter(variables);
- if (filter == null)
- return null;
+ IDiskAtomFilter filter = makeFilter(variables, limit);
+ if (filter == null)
+ return null;
- commands.add(new SliceFromReadCommand(keyspace(), key, queryPath, filter));
- }
- }
- // ...of a list of column names
- else
+ // Note that we use the total limit for every key, which is potentially inefficient.
+ // However, IN + LIMIT is not a very sensible choice.
+ for (ByteBuffer key : keys)
{
- // ByNames commands can share the filter
- IDiskAtomFilter filter = makeFilter(variables);
- if (filter == null)
- return null;
-
- for (ByteBuffer key: keys)
- {
- QueryProcessor.validateKey(key);
- commands.add(new SliceByNamesReadCommand(keyspace(), key, queryPath, (NamesQueryFilter)filter));
- }
+ QueryProcessor.validateKey(key);
+ // We should not share the slice filter amongst the commands (hence the cloneShallow), due to
+ // SliceQueryFilter not being immutable due to its columnCounter used by the lastCounted() method
+ // (this is fairly ugly and we should change that but that's probably not a tiny refactor to do that cleanly)
+ commands.add(ReadCommand.create(keyspace(), key, columnFamily(), now, filter.cloneShallow()));
}
+
return commands;
}
@@@ -714,39 -661,23 +722,37 @@@
private List<IndexExpression> getIndexExpressions(List<ByteBuffer> variables) throws InvalidRequestException
{
- if (metadataRestrictions.isEmpty())
- return Collections.<IndexExpression>emptyList();
+ if (!usesSecondaryIndexing || restrictedNames.isEmpty())
+ return Collections.emptyList();
List<IndexExpression> expressions = new ArrayList<IndexExpression>();
- for (Map.Entry<CFDefinition.Name, Restriction> entry : metadataRestrictions.entrySet())
+ for (CFDefinition.Name name : restrictedNames)
{
- CFDefinition.Name name = entry.getKey();
- Restriction restriction = entry.getValue();
+ Restriction restriction;
+ switch (name.kind)
+ {
+ case KEY_ALIAS:
+ restriction = keyRestrictions[name.position];
+ break;
+ case COLUMN_ALIAS:
+ restriction = columnRestrictions[name.position];
+ break;
+ case COLUMN_METADATA:
+ restriction = metadataRestrictions.get(name);
+ break;
+ default:
+ // We don't allow restricting a VALUE_ALIAS for now in prepare.
+ throw new AssertionError();
+ }
if (restriction.isEquality())
{
- for (Term t : restriction.eqValues)
- {
- ByteBuffer value = t.bindAndGet(variables);
- if (value == null)
- throw new InvalidRequestException(String.format("Unsupported null value for indexed column %s", name));
- if (value.remaining() > 0xFFFF)
- throw new InvalidRequestException("Index expression values may not be larger than 64K");
- expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
- }
+ assert restriction.eqValues.size() == 1; // IN is not supported for indexed columns.
+ ByteBuffer value = restriction.eqValues.get(0).bindAndGet(variables);
+ if (value == null)
+ throw new InvalidRequestException(String.format("Unsupported null value for indexed column %s", name));
+ if (value.remaining() > 0xFFFF)
+ throw new InvalidRequestException("Index expression values may not be larger than 64K");
+ expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
}
else
{
@@@ -1106,12 -1047,7 +1112,11 @@@
* At this point, the select statement if fully constructed, but we still have a few things to validate
*/
- // If a component of the PRIMARY KEY is restricted by a non-EQ relation, all preceding
+ // If there is a queriable index, no special condition are required on the other restrictions.
+ // But we still need to know 2 things:
+ // - If we don't have a queriable index, is the query ok
+ // - Is it queriable without 2ndary index, which is always more efficient
-
+ // If a component of the partition key is restricted by a non-EQ relation, all preceding
// components must have a EQ, and all following must have no restriction
boolean shouldBeDone = false;
CFDefinition.Name previous = null;
@@@ -1186,52 -1139,16 +1188,52 @@@
previous = cname;
}
- // Deal with indexed columns
- if (!stmt.metadataRestrictions.isEmpty())
+ // If a cluster key column is restricted by a non-EQ relation, all preceding
+ // columns must have a EQ, and all following must have no restriction. Unless
+ // the column is indexed that is.
+ shouldBeDone = false;
+ previous = null;
+ iter = cfDef.columns.values().iterator();
+ int lastRestrictedClusteringKey = stmt.columnRestrictions.length - 1;
+ for (int i = 0; i < stmt.columnRestrictions.length; i++)
{
- stmt.isKeyRange = true;
- Set<ByteBuffer> indexedNames = new HashSet<ByteBuffer>();
- for (ColumnDefinition cfdef : cfm.getColumn_metadata().values())
+ CFDefinition.Name cname = iter.next();
+ Restriction restriction = stmt.columnRestrictions[i];
+
+ if (restriction == null)
+ {
+ if (!shouldBeDone)
+ lastRestrictedClusteringKey = i - 1;
+ shouldBeDone = true;
+ }
+ else
{
- if (cfdef.getIndexType() != null)
+ if (shouldBeDone)
+ {
+ if (hasQueriableIndex)
+ {
+ stmt.usesSecondaryIndexing = true;
+ break;
+ }
+ throw new InvalidRequestException(String.format("PRIMARY KEY part %s cannot be restricted (preceding part %s is either not restricted or by a non-EQ relation)", cname, previous));
+ }
+ else if (!restriction.isEquality())
+ {
+ lastRestrictedClusteringKey = i;
+ shouldBeDone = true;
+ // For non-composite slices, we don't support internally the difference between exclusive and
+ // inclusive bounds, so we deal with it manually.
+ if (!cfDef.isComposite && (!restriction.isInclusive(Bound.START) || !restriction.isInclusive(Bound.END)))
+ stmt.sliceRestriction = restriction;
+ }
+ // We only support IN for the last name and for compact storage so far
+ // TODO: #3885 allows us to extend to non compact as well, but that remains to be done
- else if (restriction.eqValues.size() > 1)
++ else if (restriction.isINRestriction())
{
- indexedNames.add(cfdef.name);
+ if (i != stmt.columnRestrictions.length - 1)
+ throw new InvalidRequestException(String.format("PRIMARY KEY part %s cannot be restricted by IN relation", cname));
+ else if (stmt.selectACollection())
+ throw new InvalidRequestException(String.format("Cannot restrict PRIMARY KEY part %s by IN relation as a collection is selected by the query", cname));
}
}