You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by al...@apache.org on 2013/10/28 11:39:15 UTC

git commit: Provide hooks around CQL2/CQL3 statement execution

Updated Branches:
  refs/heads/cassandra-2.0 d280e970e -> f1c052434


Provide hooks around CQL2/CQL3 statement execution

patch by Sam Tunnicliffe; reviewed by Aleksey Yeschenko for
CASSANDRA-6252


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/f1c05243
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/f1c05243
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/f1c05243

Branch: refs/heads/cassandra-2.0
Commit: f1c0524347190dc28db21c3b27e2c05fb36dae43
Parents: d280e97
Author: Aleksey Yeschenko <al...@apache.org>
Authored: Mon Oct 28 13:38:22 2013 +0300
Committer: Aleksey Yeschenko <al...@apache.org>
Committed: Mon Oct 28 13:38:22 2013 +0300

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 .../apache/cassandra/cql/QueryProcessor.java    | 52 ++++++++++-
 .../cassandra/cql/hooks/ExecutionContext.java   | 48 ++++++++++
 .../cql/hooks/PostPreparationHook.java          | 38 ++++++++
 .../cassandra/cql/hooks/PreExecutionHook.java   | 46 ++++++++++
 .../cassandra/cql/hooks/PreparationContext.java | 39 ++++++++
 .../apache/cassandra/cql3/QueryProcessor.java   | 97 ++++++++++++++++++--
 .../cql3/hooks/BatchExecutionContext.java       | 52 +++++++++++
 .../cassandra/cql3/hooks/ExecutionContext.java  | 47 ++++++++++
 .../cassandra/cql3/hooks/PostExecutionHook.java | 52 +++++++++++
 .../cql3/hooks/PostPreparationHook.java         | 38 ++++++++
 .../cassandra/cql3/hooks/PreExecutionHook.java  | 62 +++++++++++++
 .../cql3/hooks/PreparationContext.java          | 41 +++++++++
 .../transport/messages/BatchMessage.java        |  2 +-
 14 files changed, 603 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 7e6ba95..55da5d3 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -3,6 +3,7 @@
  * cqlsh: fix LIST USERS output (CASSANDRA-6242)
  * Add IRequestSink interface (CASSANDRA-6248)
  * Update memtable size while flushing (CASSANDRA-6249)
+ * Provide hooks around CQL2/CQL3 statement execution (CASSANDRA-6252)
 
 
 2.0.2

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql/QueryProcessor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql/QueryProcessor.java b/src/java/org/apache/cassandra/cql/QueryProcessor.java
index ef804c2..f54f5ef 100644
--- a/src/java/org/apache/cassandra/cql/QueryProcessor.java
+++ b/src/java/org/apache/cassandra/cql/QueryProcessor.java
@@ -20,15 +20,19 @@ package org.apache.cassandra.cql;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeoutException;
 
-import org.apache.cassandra.serializers.MarshalException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.cassandra.auth.Permission;
 import org.apache.cassandra.config.*;
 import org.apache.cassandra.cli.CliUtils;
+import org.apache.cassandra.cql.hooks.ExecutionContext;
+import org.apache.cassandra.cql.hooks.PostPreparationHook;
+import org.apache.cassandra.cql.hooks.PreExecutionHook;
+import org.apache.cassandra.cql.hooks.PreparationContext;
 import org.apache.cassandra.db.CounterColumn;
 import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.context.CounterContext;
@@ -39,6 +43,7 @@ import org.apache.cassandra.db.marshal.AsciiType;
 import org.apache.cassandra.db.marshal.TypeParser;
 import org.apache.cassandra.dht.*;
 import org.apache.cassandra.exceptions.*;
+import org.apache.cassandra.serializers.MarshalException;
 import org.apache.cassandra.service.StorageProxy;
 import org.apache.cassandra.service.StorageService;
 import org.apache.cassandra.service.MigrationManager;
@@ -70,6 +75,29 @@ public class QueryProcessor
 
     public static final String DEFAULT_KEY_NAME = CFMetaData.DEFAULT_KEY_ALIAS.toUpperCase();
 
+    private static final List<PreExecutionHook> preExecutionHooks = new CopyOnWriteArrayList<>();
+    private static final List<PostPreparationHook> postPreparationHooks = new CopyOnWriteArrayList<>();
+
+    public static void addPreExecutionHook(PreExecutionHook hook)
+    {
+        preExecutionHooks.add(hook);
+    }
+
+    public static void removePreExecutionHook(PreExecutionHook hook)
+    {
+        preExecutionHooks.remove(hook);
+    }
+
+    public static void addPostPreparationHook(PostPreparationHook hook)
+    {
+        postPreparationHooks.add(hook);
+    }
+
+    public static void removePostPreparationHook(PostPreparationHook hook)
+    {
+        postPreparationHooks.remove(hook);
+    }
+
     private static List<org.apache.cassandra.db.Row> getSlice(CFMetaData metadata, SelectStatement select, List<ByteBuffer> variables, long now)
     throws InvalidRequestException, ReadTimeoutException, UnavailableException, IsBootstrappingException
     {
@@ -337,10 +365,12 @@ public class QueryProcessor
             throw new InvalidRequestException("range finish must come after start in traversal order");
     }
 
-    public static CqlResult processStatement(CQLStatement statement,ThriftClientState clientState, List<ByteBuffer> variables )
+    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))
@@ -348,6 +378,10 @@ public class QueryProcessor
 
         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)
@@ -761,11 +795,12 @@ public class QueryProcessor
     throws RequestValidationException, RequestExecutionException
     {
         logger.trace("CQL QUERY: {}", queryString);
-        return processStatement(getStatement(queryString), clientState, new ArrayList<ByteBuffer>(0));
+        return processStatement(getStatement(queryString),
+                                new ExecutionContext(clientState, queryString, Collections.<ByteBuffer>emptyList()));
     }
 
     public static CqlPreparedResult prepare(String queryString, ThriftClientState clientState)
-    throws SyntaxException
+    throws RequestValidationException
     {
         logger.trace("CQL QUERY: {}", queryString);
 
@@ -778,6 +813,13 @@ public class QueryProcessor
                                    statementId,
                                    statement.boundTerms));
 
+        if (!postPreparationHooks.isEmpty())
+        {
+            PreparationContext context = new PreparationContext(clientState, queryString, statement);
+            for (PostPreparationHook hook : postPreparationHooks)
+                hook.processStatement(statement, context);
+        }
+
         return new CqlPreparedResult(statementId, statement.boundTerms);
     }
 
@@ -799,7 +841,7 @@ public class QueryProcessor
                     logger.trace("[{}] '{}'", i+1, variables.get(i));
         }
 
-        return processStatement(statement, clientState, variables);
+        return processStatement(statement, new ExecutionContext(clientState, null, variables));
     }
 
     private static final int makeStatementId(String cql)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql/hooks/ExecutionContext.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql/hooks/ExecutionContext.java b/src/java/org/apache/cassandra/cql/hooks/ExecutionContext.java
new file mode 100644
index 0000000..deb785c
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql/hooks/ExecutionContext.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql.hooks;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import com.google.common.base.Optional;
+
+import org.apache.cassandra.thrift.ThriftClientState;
+
+/**
+ * Contextual information about the execution of a CQLStatement.
+ * Used by {@link org.apache.cassandra.cql.hooks.PreExecutionHook}
+ *
+ * The CQL string representing the statement being executed is optional
+ * and is not present for prepared statements. Contexts created for the
+ * execution of regular (i.e. non-prepared) statements will always
+ * contain a CQL string.
+ */
+public class ExecutionContext
+{
+    public final ThriftClientState clientState;
+    public final Optional<String> queryString;
+    public final List<ByteBuffer> variables;
+
+    public ExecutionContext(ThriftClientState clientState, String queryString, List<ByteBuffer> variables)
+    {
+        this.clientState = clientState;
+        this.queryString = Optional.fromNullable(queryString);
+        this.variables = variables;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql/hooks/PostPreparationHook.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql/hooks/PostPreparationHook.java b/src/java/org/apache/cassandra/cql/hooks/PostPreparationHook.java
new file mode 100644
index 0000000..1de9c70
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql/hooks/PostPreparationHook.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql.hooks;
+
+import org.apache.cassandra.cql.CQLStatement;
+import org.apache.cassandra.exceptions.RequestValidationException;
+
+/**
+ * Run directly after a CQL Statement is prepared in
+ * {@link org.apache.cassandra.cql.QueryProcessor}.
+ */
+public interface PostPreparationHook
+{
+    /**
+     * Called in QueryProcessor, once a CQL statement has been prepared.
+     *
+     * @param statement the statement to perform additional processing on
+     * @param context preparation context containing additional info
+     *                about the operation and statement
+     * @throws RequestValidationException
+     */
+    void processStatement(CQLStatement statement, PreparationContext context) throws RequestValidationException;
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql/hooks/PreExecutionHook.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql/hooks/PreExecutionHook.java b/src/java/org/apache/cassandra/cql/hooks/PreExecutionHook.java
new file mode 100644
index 0000000..29ed38e
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql/hooks/PreExecutionHook.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql.hooks;
+
+import org.apache.cassandra.cql.CQLStatement;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestValidationException;
+
+/**
+ * Run before the CQL Statement is executed in
+ * {@link org.apache.cassandra.cql.QueryProcessor}. The CQLStatement
+ * returned from the processStatement method is what is actually executed
+ * by the QueryProcessor.
+ */
+public interface PreExecutionHook
+{
+    /**
+     * Perform pre-processing on a CQL statement prior to it being
+     * executed by the QueryProcessor. If required, implementations
+     * may modify the statement as the returned instance is what
+     * is actually executed.
+     *
+     * @param statement the statement to perform pre-processing on
+     * @param context execution context containing additional info
+     *                about the operation and statement
+     * @return the actual statement that will be executed, possibly
+     *         a modification of the initial statement
+     * @throws RequestExecutionException, RequestValidationException
+     */
+    CQLStatement processStatement(CQLStatement statement, ExecutionContext context) throws RequestExecutionException, RequestValidationException;
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql/hooks/PreparationContext.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql/hooks/PreparationContext.java b/src/java/org/apache/cassandra/cql/hooks/PreparationContext.java
new file mode 100644
index 0000000..00cce78
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql/hooks/PreparationContext.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql.hooks;
+
+import org.apache.cassandra.cql.CQLStatement;
+import org.apache.cassandra.thrift.ThriftClientState;
+
+/**
+ * Contextual information about the preparation of a CQLStatement.
+ * Used by {@link org.apache.cassandra.cql.hooks.PostPreparationHook}
+ */
+public class PreparationContext
+{
+    public final ThriftClientState clientState;
+    public final String queryString;
+    public final CQLStatement statement;
+
+    public PreparationContext(ThriftClientState clientState, String queryString, CQLStatement statement)
+    {
+        this.clientState = clientState;
+        this.queryString = queryString;
+        this.statement = statement;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql3/QueryProcessor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
index 7ddff96..ec8b379 100644
--- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java
+++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
@@ -19,6 +19,7 @@ package org.apache.cassandra.cql3;
 
 import java.nio.ByteBuffer;
 import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import com.google.common.primitives.Ints;
 
@@ -29,6 +30,7 @@ import org.github.jamm.MemoryMeter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.cassandra.cql3.hooks.*;
 import org.apache.cassandra.cql3.statements.*;
 import org.apache.cassandra.transport.messages.ResultMessage;
 import org.apache.cassandra.db.*;
@@ -76,6 +78,39 @@ public class QueryProcessor
                                                                                                    .weigher(thriftMemoryUsageWeigher)
                                                                                                    .build();
 
+    private static final List<PreExecutionHook> preExecutionHooks = new CopyOnWriteArrayList<>();
+    private static final List<PostExecutionHook> postExecutionHooks = new CopyOnWriteArrayList<>();
+    private static final List<PostPreparationHook> postPreparationHooks = new CopyOnWriteArrayList<>();
+
+    public static void addPreExecutionHook(PreExecutionHook hook)
+    {
+        preExecutionHooks.add(hook);
+    }
+
+    public static void removePreExecutionHook(PreExecutionHook hook)
+    {
+        preExecutionHooks.remove(hook);
+    }
+
+    public static void addPostExecutionHook(PostExecutionHook hook)
+    {
+        postExecutionHooks.add(hook);
+    }
+
+    public static void removePostExecutionHook(PostExecutionHook hook)
+    {
+        postExecutionHooks.remove(hook);
+    }
+
+    public static void addPostPreparationHook(PostPreparationHook hook)
+    {
+        postPreparationHooks.add(hook);
+    }
+
+    public static void removePostPreparationHook(PostPreparationHook hook)
+    {
+        postPreparationHooks.remove(hook);
+    }
 
     public static CQLStatement getPrepared(MD5Digest id)
     {
@@ -119,17 +154,38 @@ public class QueryProcessor
             throw new InvalidRequestException("Invalid empty value for clustering column of COMPACT TABLE");
     }
 
-    private static ResultMessage processStatement(CQLStatement statement, QueryState queryState, QueryOptions options)
+    private static ResultMessage processStatement(CQLStatement statement,
+                                                  QueryState queryState,
+                                                  QueryOptions options,
+                                                  String queryString)
     throws RequestExecutionException, RequestValidationException
     {
         logger.trace("Process {} @CL.{}", statement, options.getConsistency());
         ClientState clientState = queryState.getClientState();
         statement.checkAccess(clientState);
         statement.validate(clientState);
-        ResultMessage result = statement.execute(queryState, options);
+
+        ResultMessage result = preExecutionHooks.isEmpty() && postExecutionHooks.isEmpty()
+                             ? statement.execute(queryState, options)
+                             : executeWithHooks(statement, new ExecutionContext(queryState, queryString, options));
+
         return result == null ? new ResultMessage.Void() : result;
     }
 
+    private static ResultMessage executeWithHooks(CQLStatement statement, ExecutionContext context)
+    throws RequestExecutionException, RequestValidationException
+    {
+        for (PreExecutionHook hook : preExecutionHooks)
+           statement = hook.processStatement(statement, context);
+
+        ResultMessage result = statement.execute(context.queryState, context.queryOptions);
+
+        for (PostExecutionHook hook : postExecutionHooks)
+            hook.processStatement(statement, context);
+
+        return result;
+    }
+
     public static ResultMessage process(String queryString, ConsistencyLevel cl, QueryState queryState)
     throws RequestExecutionException, RequestValidationException
     {
@@ -142,7 +198,8 @@ public class QueryProcessor
         CQLStatement prepared = getStatement(queryString, queryState.getClientState()).statement;
         if (prepared.getBoundsTerms() != options.getValues().size())
             throw new InvalidRequestException("Invalid amount of bind variables");
-        return processStatement(prepared, queryState, options);
+
+        return processStatement(prepared, queryState, options, queryString);
     }
 
     public static CQLStatement parseStatement(String queryStr, QueryState queryState) throws RequestValidationException
@@ -211,6 +268,13 @@ public class QueryProcessor
         ParsedStatement.Prepared prepared = getStatement(queryString, clientState);
         ResultMessage.Prepared msg = storePreparedStatement(queryString, clientState.getRawKeyspace(), prepared, forThrift);
 
+        if (!postPreparationHooks.isEmpty())
+        {
+            PreparationContext context = new PreparationContext(clientState, queryString, prepared.boundNames);
+            for (PostPreparationHook hook : postPreparationHooks)
+                hook.processStatement(prepared.statement, context);
+        }
+
         assert prepared.statement.getBoundsTerms() == prepared.boundNames.size();
         return msg;
     }
@@ -267,19 +331,40 @@ public class QueryProcessor
                     logger.trace("[{}] '{}'", i+1, variables.get(i));
         }
 
-        return processStatement(statement, queryState, options);
+        return processStatement(statement, queryState, options, null);
     }
 
-    public static ResultMessage processBatch(BatchStatement batch, ConsistencyLevel cl, QueryState queryState, List<List<ByteBuffer>> variables)
+    public static ResultMessage processBatch(BatchStatement batch,
+                                             ConsistencyLevel cl,
+                                             QueryState queryState,
+                                             List<List<ByteBuffer>> variables,
+                                             List<Object> queryOrIdList)
     throws RequestExecutionException, RequestValidationException
     {
         ClientState clientState = queryState.getClientState();
         batch.checkAccess(clientState);
         batch.validate(clientState);
-        batch.executeWithPerStatementVariables(cl, queryState, variables);
+
+        if (preExecutionHooks.isEmpty() && postExecutionHooks.isEmpty())
+            batch.executeWithPerStatementVariables(cl, queryState, variables);
+        else
+            executeBatchWithHooks(batch, cl, new BatchExecutionContext(queryState, queryOrIdList, variables));
+
         return new ResultMessage.Void();
     }
 
+    private static void executeBatchWithHooks(BatchStatement batch, ConsistencyLevel cl, BatchExecutionContext context)
+    throws RequestExecutionException, RequestValidationException
+    {
+        for (PreExecutionHook hook : preExecutionHooks)
+            batch = hook.processBatch(batch, context);
+
+        batch.executeWithPerStatementVariables(cl, context.queryState, context.variables);
+
+        for (PostExecutionHook hook : postExecutionHooks)
+            hook.processBatch(batch, context);
+    }
+
     private static ParsedStatement.Prepared getStatement(String queryStr, ClientState clientState)
     throws RequestValidationException
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql3/hooks/BatchExecutionContext.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/hooks/BatchExecutionContext.java b/src/java/org/apache/cassandra/cql3/hooks/BatchExecutionContext.java
new file mode 100644
index 0000000..8c81bea
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/hooks/BatchExecutionContext.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3.hooks;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.cassandra.service.QueryState;
+
+/**
+ * Contextual information about the execution of a CQL Batch.
+ * Used by {@link org.apache.cassandra.cql3.hooks.PreExecutionHook} and
+ * {@link org.apache.cassandra.cql3.hooks.PostExecutionHook}
+ *
+ * The {@code queryOrIdList} field, provides a list of objects which
+ * may be used to identify the individual statements in the batch.
+ * Currently, these objects will be one of two types (and the list may
+ * contain a mixture of the two). A {@code String} indicates the statement is
+ * a regular (i.e. non-prepared) statement, and is in fact the CQL
+ * string for the statement. An {@code MD5Digest} object indicates a prepared
+ * statement & may be used to retrieve the corresponding CQLStatement
+ * using {@link org.apache.cassandra.cql3.QueryProcessor#getPrepared(org.apache.cassandra.utils.MD5Digest) QueryProcessor.getPrepared()}
+ *
+ */
+public class BatchExecutionContext
+{
+    public final QueryState queryState;
+    public final List<Object> queryOrIdList;
+    public final List<List<ByteBuffer>> variables;
+
+    public BatchExecutionContext(QueryState queryState, List<Object> queryOrIdList, List<List<ByteBuffer>> variables)
+    {
+        this.queryState = queryState;
+        this.queryOrIdList = queryOrIdList;
+        this.variables = variables;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql3/hooks/ExecutionContext.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/hooks/ExecutionContext.java b/src/java/org/apache/cassandra/cql3/hooks/ExecutionContext.java
new file mode 100644
index 0000000..56d56c8
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/hooks/ExecutionContext.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3.hooks;
+
+import com.google.common.base.Optional;
+
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.service.QueryState;
+
+/**
+ * Contextual information about the execution of a CQLStatement.
+ * Used by {@link org.apache.cassandra.cql3.hooks.PreExecutionHook} and
+ * {@link org.apache.cassandra.cql3.hooks.PostExecutionHook}
+ *
+ * The CQL string representing the statement being executed is optional
+ * and is not present for prepared statements. Contexts created for the
+ * execution of regular (i.e. non-prepared) statements will always
+ * contain a CQL string.
+ */
+public class ExecutionContext
+{
+    public final QueryState queryState;
+    public final Optional<String> queryString;
+    public final QueryOptions queryOptions;
+
+    public ExecutionContext(QueryState queryState, String queryString, QueryOptions queryOptions)
+    {
+        this.queryState = queryState;
+        this.queryString = Optional.fromNullable(queryString);
+        this.queryOptions = queryOptions;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql3/hooks/PostExecutionHook.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/hooks/PostExecutionHook.java b/src/java/org/apache/cassandra/cql3/hooks/PostExecutionHook.java
new file mode 100644
index 0000000..96c742f
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/hooks/PostExecutionHook.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3.hooks;
+
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.statements.BatchStatement;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestValidationException;
+
+/**
+ * Run after the CQL Statement is executed in
+ * {@link org.apache.cassandra.cql3.QueryProcessor}.
+ */
+public interface PostExecutionHook
+{
+    /**
+     * Perform post-processing on a CQL statement directly after
+     * it being executed by the QueryProcessor.
+     *
+     * @param statement the statement to perform post-processing on
+     * @param context execution context containing additional info
+     *                about the operation and statement
+     * @throws RequestExecutionException, RequestValidationException
+     */
+    void processStatement(CQLStatement statement, ExecutionContext context) throws RequestExecutionException, RequestValidationException;
+
+    /**
+     * Perform post-processing on a CQL batch directly after
+     * it being executed by the QueryProcessor.
+     *
+     * @param batch the CQL batch to perform post-processing on
+     * @param context execution context containing additional info
+     *                about the operation and batch
+     * @throws RequestExecutionException, RequestValidationException
+     */
+    void processBatch(BatchStatement batch, BatchExecutionContext context) throws RequestExecutionException, RequestValidationException;
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql3/hooks/PostPreparationHook.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/hooks/PostPreparationHook.java b/src/java/org/apache/cassandra/cql3/hooks/PostPreparationHook.java
new file mode 100644
index 0000000..c2cf88a
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/hooks/PostPreparationHook.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3.hooks;
+
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.exceptions.RequestValidationException;
+
+/**
+ * Run directly after a CQL Statement is prepared in
+ * {@link org.apache.cassandra.cql3.QueryProcessor}.
+ */
+public interface PostPreparationHook
+{
+    /**
+     * Called in QueryProcessor, once a CQL statement has been prepared.
+     *
+     * @param statement the statement to perform additional processing on
+     * @param context preparation context containing additional info
+     *                about the operation and statement
+     * @throws RequestValidationException
+     */
+    void processStatement(CQLStatement statement, PreparationContext context) throws RequestValidationException;
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql3/hooks/PreExecutionHook.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/hooks/PreExecutionHook.java b/src/java/org/apache/cassandra/cql3/hooks/PreExecutionHook.java
new file mode 100644
index 0000000..3a8182f
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/hooks/PreExecutionHook.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3.hooks;
+
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.statements.BatchStatement;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestValidationException;
+
+/**
+ * Run before the CQL Statement is executed in
+ * {@link org.apache.cassandra.cql3.QueryProcessor}. The CQLStatement
+ * returned from the process* methods is what is actually executed
+ * by the QueryProcessor.
+ */
+public interface PreExecutionHook
+{
+    /**
+     * Perform pre-processing on a CQL statement prior to it being
+     * executed by the QueryProcessor. If required, implementations
+     * may modify the statement as the returned instance is what
+     * is actually executed.
+     *
+     * @param statement the statement to perform pre-processing on
+     * @param context execution context containing additional info
+     *                about the operation and statement
+     * @return the actual statement that will be executed, potentially
+     *         a modification of the initial statement
+     * @throws RequestExecutionException, RequestValidationException
+     */
+    CQLStatement processStatement(CQLStatement statement, ExecutionContext context) throws RequestExecutionException, RequestValidationException;
+
+    /**
+     * Perform pre-processing on a CQL batch prior to it being
+     * executed by the QueryProcessor. If required, implementations
+     * may modify the batch & its component statements as the returned
+     * instance is what is actually executed.
+     *
+     * @param batch the CQL batch to perform pre-processing on
+     * @param context execution context containing additional info
+     *                about the operation and batch
+     * @return the actual batch that will be executed, potentially
+     *         a modification of the initial batch
+     * @throws RequestExecutionException, RequestValidationException
+     */
+    BatchStatement processBatch(BatchStatement batch, BatchExecutionContext context) throws RequestExecutionException, RequestValidationException;
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/cql3/hooks/PreparationContext.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/hooks/PreparationContext.java b/src/java/org/apache/cassandra/cql3/hooks/PreparationContext.java
new file mode 100644
index 0000000..fda0b7d
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/hooks/PreparationContext.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3.hooks;
+
+import java.util.List;
+
+import org.apache.cassandra.cql3.ColumnSpecification;
+import org.apache.cassandra.service.ClientState;
+
+/**
+ * Contextual information about the preparation of a CQLStatement.
+ * Used by {@link org.apache.cassandra.cql3.hooks.PostPreparationHook}
+ */
+public class PreparationContext
+{
+    public final ClientState clientState;
+    public final String queryString;
+    public final List<ColumnSpecification> boundNames;
+
+    public PreparationContext(ClientState clientState, String queryString, List<ColumnSpecification> boundNames)
+    {
+        this.clientState = clientState;
+        this.queryString = queryString;
+        this.boundNames = boundNames;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1c05243/src/java/org/apache/cassandra/transport/messages/BatchMessage.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/transport/messages/BatchMessage.java b/src/java/org/apache/cassandra/transport/messages/BatchMessage.java
index 0f63018..bd95ef3 100644
--- a/src/java/org/apache/cassandra/transport/messages/BatchMessage.java
+++ b/src/java/org/apache/cassandra/transport/messages/BatchMessage.java
@@ -204,7 +204,7 @@ public class BatchMessage extends Message.Request
             // Note: It's ok at this point to pass a bogus value for the number of bound terms in the BatchState ctor
             // (and no value would be really correct, so we prefer passing a clearly wrong one).
             BatchStatement batch = new BatchStatement(-1, type, statements, Attributes.none());
-            Message.Response response = QueryProcessor.processBatch(batch, consistency, state, values);
+            Message.Response response = QueryProcessor.processBatch(batch, consistency, state, values, queryOrIdList);
 
             if (tracingId != null)
                 response.setTracingId(tracingId);