mirror of https://github.com/apache/cassandra
Allow empty IN relations in SELECT/UPDATE/DELETE statements
patch by Aleksey Yeschenko; reviewed by Sylvain Lebresne for CASSANDRA-5626
This commit is contained in:
parent
dfa59e372e
commit
dc457c5ebc
|
|
@ -1,6 +1,7 @@
|
||||||
1.2.8
|
1.2.8
|
||||||
* cqlsh: add collections support to COPY (CASSANDRA-5698)
|
* cqlsh: add collections support to COPY (CASSANDRA-5698)
|
||||||
* retry important messages for any IOException (CASSANDRA-5804)
|
* retry important messages for any IOException (CASSANDRA-5804)
|
||||||
|
* Allow empty IN relations in SELECT/UPDATE/DELETE statements (CASSANDRA-5626)
|
||||||
|
|
||||||
|
|
||||||
1.2.7
|
1.2.7
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<link rel="StyleSheet" href="CQL.css" type="text/css" media="screen">
|
<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">
|
<span id="tableOfContents">
|
||||||
|
|
@ -469,7 +469,7 @@ bc(syntax)..
|
||||||
| <identifier> '[' <term> ']' '=' <term>
|
| <identifier> '[' <term> ']' '=' <term>
|
||||||
|
|
||||||
<where-clause> ::= <identifier> '=' <term>
|
<where-clause> ::= <identifier> '=' <term>
|
||||||
| <identifier> IN '(' <term> ( ',' <term> )* ')'
|
| <identifier> IN '(' ( <term> ( ',' <term> )* )? ')'
|
||||||
|
|
||||||
<option> ::= TIMESTAMP <integer>
|
<option> ::= TIMESTAMP <integer>
|
||||||
| TTL <integer>
|
| TTL <integer>
|
||||||
|
|
@ -515,7 +515,7 @@ bc(syntax)..
|
||||||
<selection> ::= <identifier> ( '[' <term> ']' )?
|
<selection> ::= <identifier> ( '[' <term> ']' )?
|
||||||
|
|
||||||
<where-clause> ::= <identifier> '=' <term>
|
<where-clause> ::= <identifier> '=' <term>
|
||||||
| <identifier> IN '(' <term> ( ',' <term> )* ')'
|
| <identifier> IN '(' ( <term> ( ',' <term> )* )? ')'
|
||||||
p.
|
p.
|
||||||
__Sample:__
|
__Sample:__
|
||||||
|
|
||||||
|
|
@ -597,7 +597,7 @@ bc(syntax)..
|
||||||
<where-clause> ::= <relation> ( "AND" <relation> )*
|
<where-clause> ::= <relation> ( "AND" <relation> )*
|
||||||
|
|
||||||
<relation> ::= <identifier> ("=" | "<" | ">" | "<=" | ">=") <term>
|
<relation> ::= <identifier> ("=" | "<" | ">" | "<=" | ">=") <term>
|
||||||
| <identifier> IN '(' <term> ( ',' <term>)* ')'
|
| <identifier> IN '(' ( <term> ( ',' <term>)* )? ')'
|
||||||
| TOKEN '(' <identifier> ')' ("=" | "<" | ">" | "<=" | ">=") (<term> | TOKEN '( <term> ')' )
|
| TOKEN '(' <identifier> ')' ("=" | "<" | ">" | "<=" | ">=") (<term> | TOKEN '( <term> ')' )
|
||||||
|
|
||||||
<order-by> ::= <ordering> ( ',' <odering> )*
|
<order-by> ::= <ordering> ( ',' <odering> )*
|
||||||
|
|
@ -1052,6 +1052,10 @@ h2(#changes). Changes
|
||||||
|
|
||||||
The following describes the addition/changes brought for each version of CQL.
|
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
|
h3. 3.0.4
|
||||||
|
|
||||||
* Updated the syntax for custom "secondary indexes":#createIndexStmt.
|
* Updated the syntax for custom "secondary indexes":#createIndexStmt.
|
||||||
|
|
|
||||||
|
|
@ -799,7 +799,7 @@ relation[List<Relation> clauses]
|
||||||
$clauses.add(new Relation(id, type, t, true));
|
$clauses.add(new Relation(id, type, t, true));
|
||||||
}
|
}
|
||||||
| name=cident K_IN { Relation rel = Relation.createInRelation($name.id); }
|
| 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]
|
comparatorType returns [CQL3Type t]
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import org.apache.cassandra.utils.SemanticVersion;
|
||||||
|
|
||||||
public class QueryProcessor
|
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);
|
private static final Logger logger = LoggerFactory.getLogger(QueryProcessor.class);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,11 +85,13 @@ public abstract class ModificationStatement extends CFStatement implements CQLSt
|
||||||
|
|
||||||
validateConsistency(cl);
|
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
|
// The type should have been set by now or we have a bug
|
||||||
assert type != null;
|
assert type != null;
|
||||||
|
|
||||||
|
Collection<? extends IMutation> mutations = getMutations(variables, false, cl, queryState.getTimestamp());
|
||||||
|
if (mutations.isEmpty())
|
||||||
|
return null;
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case LOGGED:
|
case LOGGED:
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ public class SelectStatement implements CQLStatement
|
||||||
static List<Row> readLocally(String keyspace, List<ReadCommand> cmds)
|
static List<Row> readLocally(String keyspace, List<ReadCommand> cmds)
|
||||||
{
|
{
|
||||||
Table table = Table.open(keyspace);
|
Table table = Table.open(keyspace);
|
||||||
List<Row> rows = new ArrayList(cmds.size());
|
List<Row> rows = new ArrayList<Row>(cmds.size());
|
||||||
for (ReadCommand cmd : cmds)
|
for (ReadCommand cmd : cmds)
|
||||||
rows.add(cmd.getRow(table));
|
rows.add(cmd.getRow(table));
|
||||||
return rows;
|
return rows;
|
||||||
|
|
@ -201,7 +201,11 @@ public class SelectStatement implements CQLStatement
|
||||||
private List<ReadCommand> getSliceCommands(List<ByteBuffer> variables) throws RequestValidationException
|
private List<ReadCommand> getSliceCommands(List<ByteBuffer> variables) throws RequestValidationException
|
||||||
{
|
{
|
||||||
QueryPath queryPath = new QueryPath(columnFamily());
|
QueryPath queryPath = new QueryPath(columnFamily());
|
||||||
|
|
||||||
Collection<ByteBuffer> keys = getKeys(variables);
|
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());
|
List<ReadCommand> commands = new ArrayList<ReadCommand>(keys.size());
|
||||||
|
|
||||||
// ...a range (slice) of column names
|
// ...a range (slice) of column names
|
||||||
|
|
@ -227,13 +231,17 @@ public class SelectStatement implements CQLStatement
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// ByNames commands can share the filter
|
// 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)
|
for (ByteBuffer key: keys)
|
||||||
{
|
{
|
||||||
QueryProcessor.validateKey(key);
|
QueryProcessor.validateKey(key);
|
||||||
commands.add(new SliceByNamesReadCommand(keyspace(), key, queryPath, (NamesQueryFilter)filter));
|
commands.add(new SliceByNamesReadCommand(keyspace(), key, queryPath, (NamesQueryFilter)filter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,6 +378,8 @@ public class SelectStatement implements CQLStatement
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SortedSet<ByteBuffer> columnNames = getRequestedColumns(variables);
|
SortedSet<ByteBuffer> columnNames = getRequestedColumns(variables);
|
||||||
|
if (columnNames == null) // in case of IN () for the last column of the key
|
||||||
|
return null;
|
||||||
QueryProcessor.validateColumnNames(columnNames);
|
QueryProcessor.validateColumnNames(columnNames);
|
||||||
return new NamesQueryFilter(columnNames, true);
|
return new NamesQueryFilter(columnNames, true);
|
||||||
}
|
}
|
||||||
|
|
@ -404,7 +414,7 @@ public class SelectStatement implements CQLStatement
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (r.eqValues.size() > 1)
|
if (r.isINRestriction())
|
||||||
throw new InvalidRequestException("IN is only supported on the last column of the partition key");
|
throw new InvalidRequestException("IN is only supported on the last column of the partition key");
|
||||||
ByteBuffer val = r.eqValues.get(0).bindAndGet(variables);
|
ByteBuffer val = r.eqValues.get(0).bindAndGet(variables);
|
||||||
if (val == null)
|
if (val == null)
|
||||||
|
|
@ -479,11 +489,13 @@ public class SelectStatement implements CQLStatement
|
||||||
{
|
{
|
||||||
ColumnIdentifier id = idIter.next();
|
ColumnIdentifier id = idIter.next();
|
||||||
assert r != null && r.isEquality();
|
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.
|
// We have a IN, which we only support for the last column.
|
||||||
// If compact, just add all values and we're done. Otherwise,
|
// 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.
|
// 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);
|
SortedSet<ByteBuffer> columns = new TreeSet<ByteBuffer>(cfDef.cfm.comparator);
|
||||||
Iterator<Term> iter = r.eqValues.iterator();
|
Iterator<Term> iter = r.eqValues.iterator();
|
||||||
while (iter.hasNext())
|
while (iter.hasNext())
|
||||||
|
|
@ -600,7 +612,7 @@ public class SelectStatement implements CQLStatement
|
||||||
|
|
||||||
if (r.isEquality())
|
if (r.isEquality())
|
||||||
{
|
{
|
||||||
if (r.eqValues.size() > 1)
|
if (r.isINRestriction())
|
||||||
{
|
{
|
||||||
// IN query, we only support it on the clustering column
|
// IN query, we only support it on the clustering column
|
||||||
assert name.position == names.size() - 1;
|
assert name.position == names.size() - 1;
|
||||||
|
|
@ -659,15 +671,13 @@ public class SelectStatement implements CQLStatement
|
||||||
Restriction restriction = entry.getValue();
|
Restriction restriction = entry.getValue();
|
||||||
if (restriction.isEquality())
|
if (restriction.isEquality())
|
||||||
{
|
{
|
||||||
for (Term t : restriction.eqValues)
|
assert restriction.eqValues.size() == 1; // IN is not supported for indexed columns.
|
||||||
{
|
ByteBuffer value = restriction.eqValues.get(0).bindAndGet(variables);
|
||||||
ByteBuffer value = t.bindAndGet(variables);
|
if (value == null)
|
||||||
if (value == null)
|
throw new InvalidRequestException(String.format("Unsupported null value for indexed column %s", name));
|
||||||
throw new InvalidRequestException(String.format("Unsupported null value for indexed column %s", name));
|
if (value.remaining() > 0xFFFF)
|
||||||
if (value.remaining() > 0xFFFF)
|
throw new InvalidRequestException("Index expression values may not be larger than 64K");
|
||||||
throw new InvalidRequestException("Index expression values may not be larger than 64K");
|
expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
|
||||||
expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -992,7 +1002,6 @@ public class SelectStatement implements CQLStatement
|
||||||
CFDefinition cfDef = cfm.getCfDef();
|
CFDefinition cfDef = cfm.getCfDef();
|
||||||
|
|
||||||
ColumnSpecification[] names = new ColumnSpecification[getBoundsTerms()];
|
ColumnSpecification[] names = new ColumnSpecification[getBoundsTerms()];
|
||||||
IPartitioner partitioner = StorageService.getPartitioner();
|
|
||||||
|
|
||||||
// Select clause
|
// Select clause
|
||||||
if (parameters.isCount && !selectClause.isEmpty())
|
if (parameters.isCount && !selectClause.isEmpty())
|
||||||
|
|
@ -1008,7 +1017,7 @@ public class SelectStatement implements CQLStatement
|
||||||
* WHERE clause. For a given entity, rules are:
|
* WHERE clause. For a given entity, rules are:
|
||||||
* - EQ relation conflicts with anything else (including a 2nd EQ)
|
* - EQ relation conflicts with anything else (including a 2nd EQ)
|
||||||
* - Can't have more than one LT(E) relation (resp. GT(E) relation)
|
* - 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)
|
* (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)
|
* - 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
|
// We only support IN for the last name so far
|
||||||
// TODO: #3885 allows us to extend to other parts (cf. #4762)
|
// 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)
|
if (i != stmt.columnRestrictions.length - 1)
|
||||||
throw new InvalidRequestException(String.format("PRIMARY KEY part %s cannot be restricted by IN relation", cname));
|
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)
|
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.isKeyRange = true;
|
||||||
stmt.onToken = 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)
|
else if (stmt.onToken)
|
||||||
{
|
{
|
||||||
|
|
@ -1118,7 +1124,7 @@ public class SelectStatement implements CQLStatement
|
||||||
}
|
}
|
||||||
else if (restriction.isEquality())
|
else if (restriction.isEquality())
|
||||||
{
|
{
|
||||||
if (restriction.eqValues.size() > 1)
|
if (restriction.isINRestriction())
|
||||||
{
|
{
|
||||||
// We only support IN for the last name so far
|
// We only support IN for the last name so far
|
||||||
if (i != stmt.keyRestrictions.length - 1)
|
if (i != stmt.keyRestrictions.length - 1)
|
||||||
|
|
@ -1137,7 +1143,6 @@ public class SelectStatement implements CQLStatement
|
||||||
if (!stmt.metadataRestrictions.isEmpty())
|
if (!stmt.metadataRestrictions.isEmpty())
|
||||||
{
|
{
|
||||||
stmt.isKeyRange = true;
|
stmt.isKeyRange = true;
|
||||||
boolean hasEq = false;
|
|
||||||
Set<ByteBuffer> indexedNames = new HashSet<ByteBuffer>();
|
Set<ByteBuffer> indexedNames = new HashSet<ByteBuffer>();
|
||||||
for (ColumnDefinition cfdef : cfm.getColumn_metadata().values())
|
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
|
// 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.
|
// have the indexed component.
|
||||||
|
boolean hasEq = false;
|
||||||
for (Map.Entry<CFDefinition.Name, Restriction> entry : stmt.metadataRestrictions.entrySet())
|
for (Map.Entry<CFDefinition.Name, Restriction> entry : stmt.metadataRestrictions.entrySet())
|
||||||
{
|
{
|
||||||
Restriction restriction = entry.getValue();
|
Restriction restriction = entry.getValue();
|
||||||
|
|
@ -1156,15 +1162,13 @@ public class SelectStatement implements CQLStatement
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// We don't support IN for indexed values (basically this would require supporting a form of OR)
|
// 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");
|
throw new InvalidRequestException("Cannot use IN operator on column not part of the partition key");
|
||||||
|
|
||||||
if (indexedNames.contains(entry.getKey().name.key))
|
if (indexedNames.contains(entry.getKey().name.key))
|
||||||
{
|
|
||||||
hasEq = true;
|
hasEq = true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasEq)
|
if (!hasEq)
|
||||||
throw new InvalidRequestException("No indexed columns present in by-columns clause with Equal operator");
|
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,
|
receiver = new ColumnSpecification(name.ksName,
|
||||||
name.cfName,
|
name.cfName,
|
||||||
new ColumnIdentifier("partition key token", true),
|
new ColumnIdentifier("partition key token", true),
|
||||||
StorageService.instance.getPartitioner().getTokenValidator());
|
StorageService.getPartitioner().getTokenValidator());
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (newRel.operator())
|
switch (newRel.operator())
|
||||||
|
|
@ -1370,14 +1374,9 @@ public class SelectStatement implements CQLStatement
|
||||||
return eqValues != null;
|
return eqValues != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBound(Bound b, Term t)
|
boolean isINRestriction()
|
||||||
{
|
{
|
||||||
bounds[b.idx] = t;
|
return isEquality() && (eqValues.isEmpty() || eqValues.size() > 1);
|
||||||
}
|
|
||||||
|
|
||||||
public void setInclusive(Bound b)
|
|
||||||
{
|
|
||||||
boundInclusive[b.idx] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Term bound(Bound b)
|
public Term bound(Bound b)
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ public class UpdateStatement extends ModificationStatement
|
||||||
for (CFDefinition.Name name : cfDef.keys.values())
|
for (CFDefinition.Name name : cfDef.keys.values())
|
||||||
{
|
{
|
||||||
List<Term> values = processed.get(name.name);
|
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));
|
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));
|
||||||
|
|
||||||
if (keyBuilder.remainingCount() == 1)
|
if (keyBuilder.remainingCount() == 1)
|
||||||
|
|
@ -188,7 +188,7 @@ public class UpdateStatement extends ModificationStatement
|
||||||
}
|
}
|
||||||
else
|
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");
|
throw new InvalidRequestException("IN is only supported on the last column of the partition key");
|
||||||
ByteBuffer val = values.get(0).bindAndGet(variables);
|
ByteBuffer val = values.get(0).bindAndGet(variables);
|
||||||
if (val == null)
|
if (val == null)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue