public static CqlResult processStatement(CQLStatement statement, ExecutionContext context)
throws RequestExecutionException, RequestValidationException
{
String keyspace = null;
ThriftClientState clientState = context.clientState;
List<ByteBuffer> variables = context.variables;
// Some statements won't have (or don't need) a keyspace (think USE, or CREATE).
if (statement.type != StatementType.SELECT && StatementType.REQUIRES_KEYSPACE.contains(statement.type))
keyspace = clientState.getKeyspace();
CqlResult result = new CqlResult();
if (!preExecutionHooks.isEmpty())
for (PreExecutionHook hook : preExecutionHooks)
statement = hook.processStatement(statement, context);
if (logger.isDebugEnabled()) logger.debug("CQL statement type: {}", statement.type.toString());
CFMetaData metadata;
switch (statement.type)
{
case SELECT:
SelectStatement select = (SelectStatement)statement.statement;
final String oldKeyspace = clientState.getRawKeyspace();
if (select.isSetKeyspace())
{
keyspace = CliUtils.unescapeSQLString(select.getKeyspace());
ThriftValidation.validateKeyspace(keyspace);
}
else if (oldKeyspace == null)
throw new InvalidRequestException("no keyspace has been specified");
else
keyspace = oldKeyspace;
clientState.hasColumnFamilyAccess(keyspace, select.getColumnFamily(), Permission.SELECT);
metadata = validateColumnFamily(keyspace, select.getColumnFamily());
// need to do this in here because we need a CFMD.getKeyName()
select.extractKeyAliasFromColumns(metadata);
if (select.getKeys().size() > 0)
validateKeyAlias(metadata, select.getKeyAlias());
validateSelect(keyspace, select, variables);
List<org.apache.cassandra.db.Row> rows;
long now = System.currentTimeMillis();
// By-key
if (!select.isKeyRange() && (select.getKeys().size() > 0))
{
rows = getSlice(metadata, select, variables, now);
}
else
{
rows = multiRangeSlice(metadata, select, variables, now);
}
// count resultset is a single column named "count"
result.type = CqlResultType.ROWS;
if (select.isCountOperation())
{
validateCountOperation(select);
ByteBuffer countBytes = ByteBufferUtil.bytes("count");
result.schema = new CqlMetadata(Collections.<ByteBuffer, String>emptyMap(),
Collections.<ByteBuffer, String>emptyMap(),
"AsciiType",
"LongType");
List<Column> columns = Collections.singletonList(new Column(countBytes).setValue(ByteBufferUtil.bytes((long) rows.size())));
result.rows = Collections.singletonList(new CqlRow(countBytes, columns));
return result;
}
// otherwise create resultset from query results
result.schema = new CqlMetadata(new HashMap<ByteBuffer, String>(),
new HashMap<ByteBuffer, String>(),
TypeParser.getShortName(metadata.comparator.asAbstractType()),
TypeParser.getShortName(metadata.getDefaultValidator()));
List<CqlRow> cqlRows = new ArrayList<CqlRow>(rows.size());
for (org.apache.cassandra.db.Row row : rows)
{
List<Column> thriftColumns = new ArrayList<Column>();
if (select.isColumnRange())
{
if (select.isFullWildcard())
{
// prepend key
ByteBuffer keyName = ByteBufferUtil.bytes(metadata.getCQL2KeyName());
thriftColumns.add(new Column(keyName).setValue(row.key.key).setTimestamp(-1));
result.schema.name_types.put(keyName, TypeParser.getShortName(AsciiType.instance));
result.schema.value_types.put(keyName, TypeParser.getShortName(metadata.getKeyValidator()));
}
// preserve comparator order
if (row.cf != null)
{
for (org.apache.cassandra.db.Cell c : row.cf.getSortedColumns())
{
if (c.isMarkedForDelete(now))
continue;
ColumnDefinition cd = metadata.getColumnDefinition(c.name());
if (cd != null)
result.schema.value_types.put(c.name().toByteBuffer(), TypeParser.getShortName(cd.type));
thriftColumns.add(thriftify(c));
}
}
}
else
{
String keyString = metadata.getCQL2KeyName();
// order columns in the order they were asked for
for (Term term : select.getColumnNames())
{
if (term.getText().equalsIgnoreCase(keyString))
{
// preserve case of key as it was requested
ByteBuffer requestedKey = ByteBufferUtil.bytes(term.getText());
thriftColumns.add(new Column(requestedKey).setValue(row.key.key).setTimestamp(-1));
result.schema.name_types.put(requestedKey, TypeParser.getShortName(AsciiType.instance));
result.schema.value_types.put(requestedKey, TypeParser.getShortName(metadata.getKeyValidator()));
continue;
}
if (row.cf == null)
continue;
ByteBuffer nameBytes;
try
{
nameBytes = term.getByteBuffer(metadata.comparator.asAbstractType(), variables);
}
catch (InvalidRequestException e)
{
throw new AssertionError(e);
}
CellName name = metadata.comparator.cellFromByteBuffer(nameBytes);
ColumnDefinition cd = metadata.getColumnDefinition(name);
if (cd != null)
result.schema.value_types.put(nameBytes, TypeParser.getShortName(cd.type));
org.apache.cassandra.db.Cell c = row.cf.getColumn(name);
if (c == null || c.isMarkedForDelete(System.currentTimeMillis()))
thriftColumns.add(new Column().setName(nameBytes));
else
thriftColumns.add(thriftify(c));
}
}
// Create a new row, add the columns to it, and then add it to the list of rows
CqlRow cqlRow = new CqlRow();
cqlRow.key = row.key.key;
cqlRow.columns = thriftColumns;
if (select.isColumnsReversed())
Collections.reverse(cqlRow.columns);
cqlRows.add(cqlRow);
}
result.rows = cqlRows;
return result;
case INSERT: // insert uses UpdateStatement
case UPDATE:
UpdateStatement update = (UpdateStatement)statement.statement;
update.getConsistencyLevel().validateForWrite(keyspace);
keyspace = update.keyspace == null ? clientState.getKeyspace() : update.keyspace;
// permission is checked in prepareRowMutations()
List<IMutation> rowMutations = update.prepareRowMutations(keyspace, clientState, variables);
for (IMutation mutation : rowMutations)
{
validateKey(mutation.key());
}
StorageProxy.mutateWithTriggers(rowMutations, update.getConsistencyLevel(), false);
result.type = CqlResultType.VOID;
return result;
case BATCH:
BatchStatement batch = (BatchStatement) statement.statement;
batch.getConsistencyLevel().validateForWrite(keyspace);
if (batch.getTimeToLive() != 0)
throw new InvalidRequestException("Global TTL on the BATCH statement is not supported.");
for (AbstractModification up : batch.getStatements())
{
if (up.isSetConsistencyLevel())
throw new InvalidRequestException(
"Consistency level must be set on the BATCH, not individual statements");
if (batch.isSetTimestamp() && up.isSetTimestamp())
throw new InvalidRequestException(
"Timestamp must be set either on BATCH or individual statements");
}
List<IMutation> mutations = batch.getMutations(keyspace, clientState, variables);
for (IMutation mutation : mutations)
{
validateKey(mutation.key());
}
StorageProxy.mutateWithTriggers(mutations, batch.getConsistencyLevel(), false);
result.type = CqlResultType.VOID;
return result;
case USE:
clientState.validateLogin();
clientState.setKeyspace(CliUtils.unescapeSQLString((String) statement.statement));
result.type = CqlResultType.VOID;
return result;
case TRUNCATE:
Pair<String, String> columnFamily = (Pair<String, String>)statement.statement;
keyspace = columnFamily.left == null ? clientState.getKeyspace() : columnFamily.left;
validateColumnFamily(keyspace, columnFamily.right);
clientState.hasColumnFamilyAccess(keyspace, columnFamily.right, Permission.MODIFY);
try
{
StorageProxy.truncateBlocking(keyspace, columnFamily.right);
}
catch (TimeoutException e)
{
throw new TruncateException(e);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
result.type = CqlResultType.VOID;
return result;
case DELETE:
DeleteStatement delete = (DeleteStatement)statement.statement;
keyspace = delete.keyspace == null ? clientState.getKeyspace() : delete.keyspace;
// permission is checked in prepareRowMutations()
List<IMutation> deletions = delete.prepareRowMutations(keyspace, clientState, variables);
for (IMutation deletion : deletions)
{
validateKey(deletion.key());
}
StorageProxy.mutateWithTriggers(deletions, delete.getConsistencyLevel(), false);
result.type = CqlResultType.VOID;
return result;
case CREATE_KEYSPACE:
CreateKeyspaceStatement create = (CreateKeyspaceStatement)statement.statement;
create.validate();
ThriftValidation.validateKeyspaceNotSystem(create.getName());
clientState.hasAllKeyspacesAccess(Permission.CREATE);
try
{
KSMetaData ksm = KSMetaData.newKeyspace(create.getName(),
create.getStrategyClass(),
create.getStrategyOptions(),
true);
ThriftValidation.validateKeyspaceNotYetExisting(ksm.name);
MigrationManager.announceNewKeyspace(ksm);
}
catch (ConfigurationException e)
{
InvalidRequestException ex = new InvalidRequestException(e.getMessage());
ex.initCause(e);
throw ex;
}
result.type = CqlResultType.VOID;
return result;
case CREATE_COLUMNFAMILY:
CreateColumnFamilyStatement createCf = (CreateColumnFamilyStatement)statement.statement;
clientState.hasKeyspaceAccess(keyspace, Permission.CREATE);
try
{
MigrationManager.announceNewColumnFamily(createCf.getCFMetaData(keyspace, variables));
}
catch (ConfigurationException e)
{
InvalidRequestException ex = new InvalidRequestException(e.toString());
ex.initCause(e);
throw ex;
}
result.type = CqlResultType.VOID;
return result;
case CREATE_INDEX:
CreateIndexStatement createIdx = (CreateIndexStatement)statement.statement;
clientState.hasColumnFamilyAccess(keyspace, createIdx.getColumnFamily(), Permission.ALTER);
CFMetaData oldCfm = Schema.instance.getCFMetaData(keyspace, createIdx.getColumnFamily());
if (oldCfm == null)
throw new InvalidRequestException("No such column family: " + createIdx.getColumnFamily());
boolean columnExists = false;
ByteBuffer columnName = createIdx.getColumnName().getByteBuffer();
// mutating oldCfm directly would be bad, but mutating a copy is fine.
CFMetaData cfm = oldCfm.clone();
for (ColumnDefinition cd : cfm.regularColumns())
{
if (cd.name.bytes.equals(columnName))
{
if (cd.getIndexType() != null)
throw new InvalidRequestException("Index already exists");
if (logger.isDebugEnabled())
logger.debug("Updating column {} definition for index {}", cfm.comparator.getString(cfm.comparator.fromByteBuffer(columnName)), createIdx.getIndexName());
cd.setIndexType(IndexType.KEYS, Collections.<String, String>emptyMap());
cd.setIndexName(createIdx.getIndexName());
columnExists = true;
break;
}
}
if (!columnExists)
throw new InvalidRequestException("No column definition found for column " + oldCfm.comparator.getString(cfm.comparator.fromByteBuffer(columnName)));
try
{
cfm.addDefaultIndexNames();
MigrationManager.announceColumnFamilyUpdate(cfm, true); // As far as metadata are concerned, CQL2 == thrift
}
catch (ConfigurationException e)
{
InvalidRequestException ex = new InvalidRequestException(e.toString());
ex.initCause(e);
throw ex;
}
result.type = CqlResultType.VOID;
return result;
case DROP_INDEX:
DropIndexStatement dropIdx = (DropIndexStatement)statement.statement;
keyspace = clientState.getKeyspace();
dropIdx.setKeyspace(keyspace);
clientState.hasColumnFamilyAccess(keyspace, dropIdx.getColumnFamily(), Permission.ALTER);
try
{
CFMetaData updatedCF = dropIdx.generateCFMetadataUpdate();
MigrationManager.announceColumnFamilyUpdate(updatedCF, true); // As far as metadata are concerned, CQL2 == thrift
}
catch (ConfigurationException e)
{
InvalidRequestException ex = new InvalidRequestException(e.toString());
ex.initCause(e);
throw ex;
}
result.type = CqlResultType.VOID;
return result;
case DROP_KEYSPACE:
String deleteKeyspace = (String)statement.statement;
ThriftValidation.validateKeyspaceNotSystem(deleteKeyspace);
clientState.hasKeyspaceAccess(deleteKeyspace, Permission.DROP);
try
{
MigrationManager.announceKeyspaceDrop(deleteKeyspace);
}
catch (ConfigurationException e)
{
InvalidRequestException ex = new InvalidRequestException(e.getMessage());
ex.initCause(e);
throw ex;
}
result.type = CqlResultType.VOID;
return result;
case DROP_COLUMNFAMILY:
String deleteColumnFamily = (String)statement.statement;
clientState.hasColumnFamilyAccess(keyspace, deleteColumnFamily, Permission.DROP);
try
{
MigrationManager.announceColumnFamilyDrop(keyspace, deleteColumnFamily);
}
catch (ConfigurationException e)
{
InvalidRequestException ex = new InvalidRequestException(e.getMessage());
ex.initCause(e);
throw ex;
}
result.type = CqlResultType.VOID;
return result;
case ALTER_TABLE:
AlterTableStatement alterTable = (AlterTableStatement) statement.statement;
validateColumnFamily(keyspace, alterTable.columnFamily);
clientState.hasColumnFamilyAccess(keyspace, alterTable.columnFamily, Permission.ALTER);
try
{
MigrationManager.announceColumnFamilyUpdate(alterTable.getCFMetaData(keyspace), true); // As far as metadata are concerned, CQL2 == thrift
}