diff --git a/NEWS.txt b/NEWS.txt index 4b35f71fb8..b6730a1535 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -118,6 +118,12 @@ Changed Defaults of the total space of the commitlog volume. (Before: always used 8192) +New features +------------ + - Custom QueryHandlers can retrieve the column specifications for the bound + variables from QueryOptions by using the hasColumnSpecifications() + and getColumnSpecifications() methods. + 2.2.1 ===== diff --git a/src/java/org/apache/cassandra/cql3/BatchQueryOptions.java b/src/java/org/apache/cassandra/cql3/BatchQueryOptions.java index 2fcee5b2a0..db7fa395b7 100644 --- a/src/java/org/apache/cassandra/cql3/BatchQueryOptions.java +++ b/src/java/org/apache/cassandra/cql3/BatchQueryOptions.java @@ -22,6 +22,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.apache.cassandra.utils.MD5Digest; + import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.service.QueryState; @@ -50,6 +52,11 @@ public abstract class BatchQueryOptions public abstract QueryOptions forStatement(int i); + public void prepareStatement(int i, List boundNames) + { + forStatement(i).prepare(boundNames); + } + public ConsistencyLevel getConsistency() { return wrapped.getConsistency(); @@ -107,5 +114,26 @@ public abstract class BatchQueryOptions { return perStatementOptions.get(i); } + + @Override + public void prepareStatement(int i, List boundNames) + { + if (isPreparedStatement(i)) + { + QueryOptions options = perStatementOptions.get(i); + options.prepare(boundNames); + options = QueryOptions.addColumnSpecifications(options, boundNames); + perStatementOptions.set(i, options); + } + else + { + super.prepareStatement(i, boundNames); + } + } + + private boolean isPreparedStatement(int i) + { + return getQueryOrIdList().get(i) instanceof MD5Digest; + } } } diff --git a/src/java/org/apache/cassandra/cql3/ColumnSpecification.java b/src/java/org/apache/cassandra/cql3/ColumnSpecification.java index e12a57e798..e64f5f920e 100644 --- a/src/java/org/apache/cassandra/cql3/ColumnSpecification.java +++ b/src/java/org/apache/cassandra/cql3/ColumnSpecification.java @@ -18,6 +18,7 @@ package org.apache.cassandra.cql3; import com.google.common.base.Objects; + import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.ReversedType; @@ -91,4 +92,13 @@ public class ColumnSpecification { return Objects.hashCode(ksName, cfName, name, type); } + + @Override + public String toString() + { + return Objects.toStringHelper(this) + .add("name", name) + .add("type", type) + .toString(); + } } diff --git a/src/java/org/apache/cassandra/cql3/QueryOptions.java b/src/java/org/apache/cassandra/cql3/QueryOptions.java index 672f8ea3e2..ad554edb2c 100644 --- a/src/java/org/apache/cassandra/cql3/QueryOptions.java +++ b/src/java/org/apache/cassandra/cql3/QueryOptions.java @@ -23,8 +23,9 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; -import io.netty.buffer.ByteBuf; +import com.google.common.collect.ImmutableList; +import io.netty.buffer.ByteBuf; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.pager.PagingState; @@ -72,10 +73,43 @@ public abstract class QueryOptions return new DefaultQueryOptions(consistency, values, skipMetadata, new SpecificOptions(pageSize, pagingState, serialConsistency, -1L), 0); } + public static QueryOptions addColumnSpecifications(QueryOptions options, List columnSpecs) + { + return new OptionsWithColumnSpecifications(options, columnSpecs); + } + public abstract ConsistencyLevel getConsistency(); public abstract List getValues(); public abstract boolean skipMetadata(); + /** + * Tells whether or not this QueryOptions contains the column specifications for the bound variables. + *

The column specifications will be present only for prepared statements.

+ * @return true this QueryOptions contains the column specifications for the bound + * variables, false otherwise. + */ + public boolean hasColumnSpecifications() + { + return false; + } + + /** + * Returns the column specifications for the bound variables (optional operation). + * + *

The column specifications will be present only for prepared statements.

+ * + *

Invoke the {@link hasColumnSpecifications} method before invoking this method in order to ensure that this + * QueryOptions contains the column specifications.

+ * + * @return the option names + * @throws UnsupportedOperationException If this QueryOptions does not contains the column + * specifications. + */ + public ImmutableList getColumnSpecifications() + { + throw new UnsupportedOperationException(); + } + /** The pageSize for this query. Will be <= 0 if not relevant for the query. */ public int getPageSize() { @@ -159,7 +193,7 @@ public abstract class QueryOptions } } - static abstract class QueryOptionsWrapper extends QueryOptions + static class QueryOptionsWrapper extends QueryOptions { protected final QueryOptions wrapped; @@ -168,6 +202,11 @@ public abstract class QueryOptions this.wrapped = wrapped; } + public List getValues() + { + return this.wrapped.getValues(); + } + public ConsistencyLevel getConsistency() { return wrapped.getConsistency(); @@ -196,6 +235,32 @@ public abstract class QueryOptions } } + /** + * QueryOptions decorator that provides access to the column specifications. + */ + static class OptionsWithColumnSpecifications extends QueryOptionsWrapper + { + private final ImmutableList columnSpecs; + + OptionsWithColumnSpecifications(QueryOptions wrapped, List columnSpecs) + { + super(wrapped); + this.columnSpecs = ImmutableList.copyOf(columnSpecs); + } + + @Override + public boolean hasColumnSpecifications() + { + return true; + } + + @Override + public ImmutableList getColumnSpecifications() + { + return columnSpecs; + } + } + static class OptionsWithNames extends QueryOptionsWrapper { private final List names; @@ -228,6 +293,7 @@ public abstract class QueryOptions return this; } + @Override public List getValues() { assert orderedValues != null; // We should have called prepare first! diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java index 4a92ec1a97..0b3e1ba491 100644 --- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java @@ -519,7 +519,7 @@ public class BatchStatement implements CQLStatement // Use the CFMetadata of the first statement for partition key bind indexes. If the statements affect // multiple tables, we won't send partition key bind indexes. - Short[] partitionKeyBindIndexes = haveMultipleCFs ? null + Short[] partitionKeyBindIndexes = (haveMultipleCFs || batchStatement.statements.isEmpty())? null : boundNames.getPartitionKeyBindIndexes(batchStatement.statements.get(0).cfm); return new ParsedStatement.Prepared(batchStatement, boundNames, partitionKeyBindIndexes); diff --git a/src/java/org/apache/cassandra/transport/messages/BatchMessage.java b/src/java/org/apache/cassandra/transport/messages/BatchMessage.java index 5baf1a6b77..bd2423e040 100644 --- a/src/java/org/apache/cassandra/transport/messages/BatchMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/BatchMessage.java @@ -203,7 +203,7 @@ public class BatchMessage extends Message.Request for (int i = 0; i < prepared.size(); i++) { ParsedStatement.Prepared p = prepared.get(i); - batchOptions.forStatement(i).prepare(p.boundNames); + batchOptions.prepareStatement(i, p.boundNames); if (!(p.statement instanceof ModificationStatement)) throw new InvalidRequestException("Invalid statement in batch: only UPDATE, INSERT and DELETE statements are allowed."); diff --git a/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java b/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java index 940a0fcf74..e9923b4ece 100644 --- a/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java @@ -124,7 +124,10 @@ public class ExecuteMessage extends Message.Request Tracing.instance.begin("Execute CQL3 prepared query", state.getClientAddress(), builder.build()); } - Message.Response response = handler.processPrepared(statement, state, options, getCustomPayload()); + // Some custom QueryHandlers are interested by the bound names. We provide them this information + // by wrapping the QueryOptions. + QueryOptions queryOptions = QueryOptions.addColumnSpecifications(options, prepared.boundNames); + Message.Response response = handler.processPrepared(statement, state, queryOptions, getCustomPayload()); if (options.skipMetadata() && response instanceof ResultMessage.Rows) ((ResultMessage.Rows)response).result.metadata.setSkipMetadata(); diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/BatchTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/BatchTest.java index c0d1df5011..43e3a303b6 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/BatchTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/BatchTest.java @@ -18,12 +18,8 @@ package org.apache.cassandra.cql3.validation.operations; -import java.util.Arrays; - import org.junit.Test; -import static org.apache.commons.lang3.StringUtils.isEmpty; - import org.apache.cassandra.cql3.CQLTester; public class BatchTest extends CQLTester @@ -172,4 +168,10 @@ public class BatchTest extends CQLTester row(0, 5, 20), row(0, 6, 20)); } + + @Test + public void testBatchEmpty() throws Throwable + { + assertEmpty(execute("BEGIN BATCH APPLY BATCH;")); + } }