this.whereClause = whereClause == null ? Collections.<Relation>emptyList() : whereClause;
}
public ParsedStatement.Prepared prepare() throws InvalidRequestException
{
CFMetaData cfm = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
if (parameters.limit <= 0)
throw new InvalidRequestException("LIMIT must be strictly positive");
CFDefinition cfDef = cfm.getCfDef();
ColumnSpecification[] names = new ColumnSpecification[getBoundsTerms()];
IPartitioner partitioner = StorageService.getPartitioner();
// Select clause
if (parameters.isCount && !selectClause.isEmpty())
throw new InvalidRequestException("Only COUNT(*) and COUNT(1) operations are currently supported.");
Selection selection = selectClause.isEmpty()
? Selection.wildcard(cfDef)
: Selection.fromSelectors(cfDef, selectClause);
SelectStatement stmt = new SelectStatement(cfDef, getBoundsTerms(), parameters, selection);
/*
* 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
* (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)
*/
for (Relation rel : whereClause)
{
CFDefinition.Name name = cfDef.get(rel.getEntity());
if (name == null)
throw new InvalidRequestException(String.format("Undefined name %s in where clause ('%s')", rel.getEntity(), rel));
switch (name.kind)
{
case KEY_ALIAS:
stmt.keyRestrictions[name.position] = updateRestriction(name, stmt.keyRestrictions[name.position], rel, names);
break;
case COLUMN_ALIAS:
stmt.columnRestrictions[name.position] = updateRestriction(name, stmt.columnRestrictions[name.position], rel, names);
break;
case VALUE_ALIAS:
throw new InvalidRequestException(String.format("Restricting the value of a compact CF (%s) is not supported", name.name));
case COLUMN_METADATA:
stmt.metadataRestrictions.put(name, updateRestriction(name, stmt.metadataRestrictions.get(name), rel, names));
break;
}
}
/*
* 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
// components must have a EQ, and all following must have no restriction
boolean shouldBeDone = false;
CFDefinition.Name previous = null;
Iterator<CFDefinition.Name> iter = cfDef.columns.values().iterator();
for (int i = 0; i < stmt.columnRestrictions.length; i++)
{
CFDefinition.Name cname = iter.next();
Restriction restriction = stmt.columnRestrictions[i];
if (restriction == null)
{
shouldBeDone = true;
}
else if (shouldBeDone)
{
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())
{
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 so far
// TODO: #3885 allows us to extend to other parts (cf. #4762)
else if (restriction.eqValues.size() > 1)
{
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));
}
previous = cname;
}
// 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
shouldBeDone = false;
previous = null;
stmt.keyIsInRelation = false;
iter = cfDef.keys.values().iterator();
for (int i = 0; i < stmt.keyRestrictions.length; i++)
{
CFDefinition.Name cname = iter.next();
Restriction restriction = stmt.keyRestrictions[i];
if (restriction == null)
{
if (stmt.onToken)
throw new InvalidRequestException("The token() function must be applied to all partition key components or none of them");
// Under a non order perserving partitioner, the only time not restricting a key part is allowed is if none are restricted
if (!partitioner.preservesOrder() && i > 0 && stmt.keyRestrictions[i-1] != null)
throw new InvalidRequestException(String.format("Partition key part %s must be restricted since preceding part is", cname));
stmt.isKeyRange = true;
shouldBeDone = true;
}
else if (shouldBeDone)
{
throw new InvalidRequestException(String.format("partition key part %s cannot be restricted (preceding part %s is either not restricted or by a non-EQ relation)", cname, previous));
}
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)
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)
{
throw new InvalidRequestException(String.format("The token() function must be applied to all partition key components or none of them"));
}
else if (restriction.isEquality())
{
if (restriction.eqValues.size() > 1)
{
// We only support IN for the last name so far
if (i != stmt.keyRestrictions.length - 1)
throw new InvalidRequestException(String.format("Partition KEY part %s cannot be restricted by IN relation (only the last part of the partition key can)", cname));
stmt.keyIsInRelation = true;
}
}
else
{
throw new InvalidRequestException("Only EQ and IN relation are supported on the partition key (you will need to use the token() function for non equality based relation)");
}
previous = cname;
}
// Deal with indexed columns
if (!stmt.metadataRestrictions.isEmpty())
{
stmt.isKeyRange = true;
boolean hasEq = false;
Set<ByteBuffer> indexedNames = new HashSet<ByteBuffer>();
for (ColumnDefinition cfdef : cfm.getColumn_metadata().values())
{
if (cfdef.getIndexType() != null)
{
indexedNames.add(cfdef.name);
}