{
CFMetaData cfm = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
ThriftValidation.validateConsistencyLevel(keyspace(), parameters.consistencyLevel, RequestType.READ);
if (parameters.limit <= 0)
throw new InvalidRequestException("LIMIT must be strictly positive");
CFDefinition cfDef = cfm.getCfDef();
SelectStatement stmt = new SelectStatement(cfDef, getBoundsTerms(), parameters);
AbstractType[] types = new AbstractType[getBoundsTerms()];
// Select clause
if (parameters.isCount)
{
if (selectClause.size() != 1)
throw new InvalidRequestException("Only COUNT(*) and COUNT(1) operations are currently supported.");
String columnName = selectClause.get(0).toString();
if (!columnName.equals("*") && !columnName.equals("1"))
throw new InvalidRequestException("Only COUNT(*) and COUNT(1) operations are currently supported.");
}
else
{
for (ColumnIdentifier t : selectClause)
{
CFDefinition.Name name = cfDef.get(t);
if (name == null)
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", t));
// Keeping the case (as in 'case sensitive') of the input name for the resultSet
stmt.selectedNames.add(Pair.create(name, t));
}
}
/*
* 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));
if (rel.operator() == Relation.Type.IN)
{
for (Term value : rel.getInValues())
if (value.isBindMarker())
types[value.bindIndex] = name.type;
}
else
{
Term value = rel.getValue();
if (value.isBindMarker())
types[value.bindIndex] = name.type;
}
switch (name.kind)
{
case KEY_ALIAS:
if (rel.operator() != Relation.Type.EQ && rel.operator() != Relation.Type.IN && !StorageService.getPartitioner().preservesOrder())
throw new InvalidRequestException("Only EQ and IN relation are supported on first component of the PRIMARY KEY for RandomPartitioner");
stmt.keyRestriction = updateRestriction(name.name, stmt.keyRestriction, rel);
break;
case COLUMN_ALIAS:
stmt.columnRestrictions[name.position] = updateRestriction(name.name, stmt.columnRestrictions[name.position], rel);
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.name, stmt.metadataRestrictions.get(name), rel));
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;
// We only support IN for the last name so far
else if (restriction.eqValues.size() > 1 && i != stmt.columnRestrictions.length - 1)
throw new InvalidRequestException(String.format("PRIMARY KEY part %s cannot be restricted by IN relation (only the first and last parts can)", cname));
previous = cname;
}
// Deal with indexed columns
if (!stmt.metadataRestrictions.isEmpty())
{
boolean hasEq = false;
Set<ByteBuffer> indexed = Table.open(keyspace()).getColumnFamilyStore(columnFamily()).indexManager.getIndexedColumns();
for (Map.Entry<CFDefinition.Name, Restriction> entry : stmt.metadataRestrictions.entrySet())
{
if (entry.getValue().isEquality() && indexed.contains(entry.getKey().name.key))
{
hasEq = true;
break;
}
}
if (!hasEq)
throw new InvalidRequestException("No indexed columns present in by-columns clause with Equal operator");
// If we have indexed columns and the key = X clause, we transform it into a key >= X AND key <= X clause.
// If it's a IN relation however, we reject it.
if (stmt.keyRestriction != null && stmt.keyRestriction.isEquality())
{
if (stmt.keyRestriction.eqValues.size() > 1)
throw new InvalidRequestException("Select on indexed columns and with IN clause for the PRIMARY KEY are not supported");
Restriction newRestriction = new Restriction();
for (Bound b : Bound.values())
{
newRestriction.setBound(b, stmt.keyRestriction.eqValues.get(0));
newRestriction.setInclusive(b);
}
stmt.keyRestriction = newRestriction;
}
}
// Only allow reversed if the row key restriction is an equality,
// since we don't know how to reverse otherwise
if (stmt.parameters.isColumnsReversed)
{
if (stmt.keyRestriction == null || !stmt.keyRestriction.isEquality())
throw new InvalidRequestException("Descending order is only supported is the first part of the PRIMARY KEY is restricted by an Equal or a IN");
}
return new ParsedStatement.Prepared(stmt, Arrays.<AbstractType<?>>asList(types));
}