You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sl...@apache.org on 2015/11/26 10:25:11 UTC

[1/4] cassandra git commit: Fix JSON update with prepared statements

Repository: cassandra
Updated Branches:
  refs/heads/cassandra-3.1 c2ea8c4fc -> 5fd65f3b7


Fix JSON update with prepared statements

patch by slebresne; reviewed by thobbs for CASSANDRA-10631


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

Branch: refs/heads/cassandra-3.1
Commit: 31be903a74864e58980e0e22fd993a280c9138b3
Parents: 96b7603
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Mon Nov 2 13:18:59 2015 +0100
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Thu Nov 26 10:11:55 2015 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 src/java/org/apache/cassandra/cql3/Json.java    | 25 +--------
 .../org/apache/cassandra/cql3/QueryOptions.java | 52 +++++++++++++++++--
 .../apache/cassandra/cql3/QueryProcessor.java   | 54 ++++++++++++++++----
 .../org/apache/cassandra/cql3/CQLTester.java    | 22 +++++++-
 5 files changed, 115 insertions(+), 39 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/31be903a/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index a548c9f..7b32c2b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.2.4
+ * Fix JSON update with prepared statements (CASSANDRA-10631)
  * Don't do anticompaction after subrange repair (CASSANDRA-10422)
  * Fix SimpleDateType type compatibility (CASSANDRA-10027)
  * (Hadoop) fix splits calculation (CASSANDRA-10640)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/31be903a/src/java/org/apache/cassandra/cql3/Json.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Json.java b/src/java/org/apache/cassandra/cql3/Json.java
index e4bce29..5284793 100644
--- a/src/java/org/apache/cassandra/cql3/Json.java
+++ b/src/java/org/apache/cassandra/cql3/Json.java
@@ -156,8 +156,6 @@ public class Json
         private final int bindIndex;
         private final Collection<ColumnDefinition> columns;
 
-        private Map<ColumnIdentifier, Term> columnMap;
-
         public PreparedMarker(String keyspace, int bindIndex, Collection<ColumnDefinition> columns)
         {
             super(keyspace);
@@ -169,24 +167,6 @@ public class Json
         {
             return new DelayedColumnValue(this, def);
         }
-
-        public void bind(QueryOptions options) throws InvalidRequestException
-        {
-            // this will be called once per column, so avoid duplicating work
-            if (columnMap != null)
-                return;
-
-            ByteBuffer value = options.getValues().get(bindIndex);
-            if (value == null)
-                throw new InvalidRequestException("Got null for INSERT JSON values");
-
-            columnMap = parseJson(UTF8Type.instance.getSerializer().deserialize(value), columns);
-        }
-
-        public Term getValue(ColumnDefinition def)
-        {
-            return columnMap.get(def.name);
-        }
     }
 
     /**
@@ -260,8 +240,7 @@ public class Json
         @Override
         public Terminal bind(QueryOptions options) throws InvalidRequestException
         {
-            marker.bind(options);
-            Term term = marker.getValue(column);
+            Term term = options.getJsonColumnValue(marker.bindIndex, column.name, marker.columns);
             return term == null ? null : term.bind(options);
         }
 
@@ -275,7 +254,7 @@ public class Json
     /**
      * Given a JSON string, return a map of columns to their values for the insert.
      */
-    private static Map<ColumnIdentifier, Term> parseJson(String jsonString, Collection<ColumnDefinition> expectedReceivers)
+    public static Map<ColumnIdentifier, Term> parseJson(String jsonString, Collection<ColumnDefinition> expectedReceivers)
     {
         try
         {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/31be903a/src/java/org/apache/cassandra/cql3/QueryOptions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/QueryOptions.java b/src/java/org/apache/cassandra/cql3/QueryOptions.java
index 7fc0997..be773e1 100644
--- a/src/java/org/apache/cassandra/cql3/QueryOptions.java
+++ b/src/java/org/apache/cassandra/cql3/QueryOptions.java
@@ -18,15 +18,15 @@
 package org.apache.cassandra.cql3;
 
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
+import java.util.*;
 
 import com.google.common.collect.ImmutableList;
-
 import io.netty.buffer.ByteBuf;
+
+import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.service.pager.PagingState;
 import org.apache.cassandra.transport.CBCodec;
@@ -48,6 +48,9 @@ public abstract class QueryOptions
 
     public static final CBCodec<QueryOptions> codec = new Codec();
 
+    // A cache of bind values parsed as JSON, see getJsonColumnValue for details.
+    private List<Map<ColumnIdentifier, Term>> jsonValuesCache;
+
     public static QueryOptions fromProtocolV1(ConsistencyLevel consistency, List<ByteBuffer> values)
     {
         return new DefaultQueryOptions(consistency, values, false, SpecificOptions.DEFAULT, Server.VERSION_1);
@@ -93,6 +96,45 @@ public abstract class QueryOptions
     public abstract boolean skipMetadata();
 
     /**
+     * Returns the term corresponding to column {@code columnName} in the JSON value of bind index {@code bindIndex}.
+     *
+     * This is functionally equivalent to:
+     *   {@code Json.parseJson(UTF8Type.instance.getSerializer().deserialize(getValues().get(bindIndex)), expectedReceivers).get(columnName)}
+     * but this cache the result of parsing the JSON so that while this might be called for multiple columns on the same {@code bindIndex}
+     * value, the underlying JSON value is only parsed/processed once.
+     *
+     * Note: this is a bit more involved in CQL specifics than this class generally is but we as we need to cache this per-query and in an object
+     * that is available when we bind values, this is the easier place to have this.
+     *
+     * @param bindIndex the index of the bind value that should be interpreted as a JSON value.
+     * @param columnName the name of the column we want the value of.
+     * @param expectedReceivers the columns expected in the JSON value at index {@code bindIndex}. This is only used when parsing the
+     * json initially and no check is done afterwards. So in practice, any call of this method on the same QueryOptions object and with the same
+     * {@code bindIndx} values should use the same value for this parameter, but this isn't validated in any way.
+     *
+     * @return the value correspong to column {@code columnName} in the (JSON) bind value at index {@code bindIndex}. This may return null if the
+     * JSON value has no value for this column.
+     */
+    public Term getJsonColumnValue(int bindIndex, ColumnIdentifier columnName, Collection<ColumnDefinition> expectedReceivers) throws InvalidRequestException
+    {
+        if (jsonValuesCache == null)
+            jsonValuesCache = new ArrayList<>(Collections.<Map<ColumnIdentifier, Term>>nCopies(getValues().size(), null));
+
+        Map<ColumnIdentifier, Term> jsonValue = jsonValuesCache.get(bindIndex);
+        if (jsonValue == null)
+        {
+            ByteBuffer value = getValues().get(bindIndex);
+            if (value == null)
+                throw new InvalidRequestException("Got null for INSERT JSON values");
+
+            jsonValue = Json.parseJson(UTF8Type.instance.getSerializer().deserialize(value), expectedReceivers);
+            jsonValuesCache.set(bindIndex, jsonValue);
+        }
+
+        return jsonValue.get(columnName);
+    }
+
+    /**
      * Tells whether or not this <code>QueryOptions</code> contains the column specifications for the bound variables.
      * <p>The column specifications will be present only for prepared statements.</p>
      * @return <code>true</code> this <code>QueryOptions</code> contains the column specifications for the bound

http://git-wip-us.apache.org/repos/asf/cassandra/blob/31be903a/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 30a111d..fa82fa7 100644
--- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java
+++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
@@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
@@ -544,6 +545,15 @@ public class QueryProcessor implements QueryHandler
         return meter.measureDeep(key);
     }
 
+    /**
+     * Clear our internal statmeent cache for test purposes.
+     */
+    @VisibleForTesting
+    public static void clearInternalStatementsCache()
+    {
+        internalStatements.clear();
+    }
+
     private static class MigrationSubscriber extends MigrationListener
     {
         private void removeInvalidPreparedStatements(String ksName, String cfName)
@@ -597,22 +607,23 @@ public class QueryProcessor implements QueryHandler
             return ksName.equals(statementKsName) && (cfName == null || cfName.equals(statementCfName));
         }
 
-        public void onCreateFunction(String ksName, String functionName, List<AbstractType<?>> argTypes) {
+        public void onCreateFunction(String ksName, String functionName, List<AbstractType<?>> argTypes)
+        {
             if (Functions.getOverloadCount(new FunctionName(ksName, functionName)) > 1)
             {
                 // in case there are other overloads, we have to remove all overloads since argument type
                 // matching may change (due to type casting)
-                removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, functionName);
-                removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, functionName);
+                removeAllInvalidPreparedStatementsForFunction(ksName, functionName);
             }
         }
-        public void onCreateAggregate(String ksName, String aggregateName, List<AbstractType<?>> argTypes) {
+
+        public void onCreateAggregate(String ksName, String aggregateName, List<AbstractType<?>> argTypes)
+        {
             if (Functions.getOverloadCount(new FunctionName(ksName, aggregateName)) > 1)
             {
                 // in case there are other overloads, we have to remove all overloads since argument type
                 // matching may change (due to type casting)
-                removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, aggregateName);
-                removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, aggregateName);
+                removeAllInvalidPreparedStatementsForFunction(ksName, aggregateName);
             }
         }
 
@@ -623,6 +634,24 @@ public class QueryProcessor implements QueryHandler
                 removeInvalidPreparedStatements(ksName, cfName);
         }
 
+        public void onUpdateFunction(String ksName, String functionName, List<AbstractType<?>> argTypes)
+        {
+            // Updating a function may imply we've changed the body of the function, so we need to invalid statements so that
+            // the new definition is picked (the function is resolved at preparation time).
+            // TODO: if the function has multiple overload, we could invalidate only the statement refering to the overload
+            // that was updated. This requires a few changes however and probably doesn't matter much in practice.
+            removeAllInvalidPreparedStatementsForFunction(ksName, functionName);
+        }
+
+        public void onUpdateAggregate(String ksName, String aggregateName, List<AbstractType<?>> argTypes)
+        {
+            // Updating a function may imply we've changed the body of the function, so we need to invalid statements so that
+            // the new definition is picked (the function is resolved at preparation time).
+            // TODO: if the function has multiple overload, we could invalidate only the statement refering to the overload
+            // that was updated. This requires a few changes however and probably doesn't matter much in practice.
+            removeAllInvalidPreparedStatementsForFunction(ksName, aggregateName);
+        }
+
         public void onDropKeyspace(String ksName)
         {
             logger.trace("Keyspace {} was dropped, invalidating related prepared statements", ksName);
@@ -637,14 +666,19 @@ public class QueryProcessor implements QueryHandler
 
         public void onDropFunction(String ksName, String functionName, List<AbstractType<?>> argTypes)
         {
-            removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, functionName);
-            removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, functionName);
+            removeAllInvalidPreparedStatementsForFunction(ksName, functionName);
         }
 
         public void onDropAggregate(String ksName, String aggregateName, List<AbstractType<?>> argTypes)
         {
-            removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, aggregateName);
-            removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, aggregateName);
+            removeAllInvalidPreparedStatementsForFunction(ksName, aggregateName);
+        }
+
+        private void removeAllInvalidPreparedStatementsForFunction(String ksName, String functionName)
+        {
+            removeInvalidPreparedStatementsForFunction(internalStatements.values().iterator(), ksName, functionName);
+            removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, functionName);
+            removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, functionName);
         }
 
         private static void removeInvalidPreparedStatementsForFunction(Iterator<ParsedStatement.Prepared> statements,

http://git-wip-us.apache.org/repos/asf/cassandra/blob/31be903a/test/unit/org/apache/cassandra/cql3/CQLTester.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java
index 4b4631e..5e17d1b 100644
--- a/test/unit/org/apache/cassandra/cql3/CQLTester.java
+++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java
@@ -136,6 +136,7 @@ public abstract class CQLTester
     // We don't use USE_PREPARED_VALUES in the code below so some test can foce value preparation (if the result
     // is not expected to be the same without preparation)
     private boolean usePrepared = USE_PREPARED_VALUES;
+    private static final boolean reusePrepared = Boolean.valueOf(System.getProperty("cassandra.test.reuse_prepared", "true"));
 
     @BeforeClass
     public static void setUpClass()
@@ -158,6 +159,11 @@ public abstract class CQLTester
 
         if (server != null)
             server.stop();
+
+        // We use queryInternal for CQLTester so prepared statement will populate our internal cache (if reusePrepared is used; otherwise prepared
+        // statements are not cached but re-prepared every time). So we clear the cache between test files to avoid accumulating too much.
+        if (reusePrepared)
+            QueryProcessor.clearInternalStatementsCache();
     }
 
     @Before
@@ -571,7 +577,21 @@ public abstract class CQLTester
         if (usePrepared)
         {
             logger.info("Executing: {} with values {}", query, formatAllValues(values));
-            rs = QueryProcessor.executeOnceInternal(query, transformValues(values));
+            if (reusePrepared)
+            {
+                rs = QueryProcessor.executeInternal(query, transformValues(values));
+
+                // If a test uses a "USE ...", then presumably its statements use relative table. In that case, a USE
+                // change the meaning of the current keyspace, so we don't want a following statement to reuse a previously
+                // prepared statement at this wouldn't use the right keyspace. To avoid that, we drop the previously
+                // prepared statement.
+                if (query.startsWith("USE"))
+                    QueryProcessor.clearInternalStatementsCache();
+            }
+            else
+            {
+                rs = QueryProcessor.executeOnceInternal(query, transformValues(values));
+            }
         }
         else
         {


[4/4] cassandra git commit: Merge branch 'cassandra-3.0' into cassandra-3.1

Posted by sl...@apache.org.
Merge branch 'cassandra-3.0' into cassandra-3.1


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

Branch: refs/heads/cassandra-3.1
Commit: 5fd65f3b7d18bb1fdf93906463bbd657cbea39c6
Parents: c2ea8c4 2cbd776
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Thu Nov 26 10:23:28 2015 +0100
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Thu Nov 26 10:23:28 2015 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 src/java/org/apache/cassandra/cql3/Json.java    | 25 +---------
 .../org/apache/cassandra/cql3/QueryOptions.java | 52 ++++++++++++++++++--
 .../apache/cassandra/cql3/QueryProcessor.java   | 40 ++++++++++++---
 .../cql3/statements/AlterKeyspaceStatement.java |  7 +--
 .../cql3/statements/AlterTableStatement.java    |  9 +---
 .../cql3/statements/AlterTypeStatement.java     |  9 +---
 .../cql3/statements/AlterViewStatement.java     |  9 +---
 .../statements/CreateAggregateStatement.java    | 27 +++-------
 .../statements/CreateFunctionStatement.java     | 23 +++------
 .../cql3/statements/CreateIndexStatement.java   |  8 +--
 .../statements/CreateKeyspaceStatement.java     | 11 ++---
 .../cql3/statements/CreateTableStatement.java   | 11 ++---
 .../cql3/statements/CreateTriggerStatement.java |  9 +---
 .../cql3/statements/CreateTypeStatement.java    | 11 ++---
 .../cql3/statements/CreateViewStatement.java    | 12 ++---
 .../cql3/statements/DropAggregateStatement.java | 19 ++-----
 .../cql3/statements/DropFunctionStatement.java  | 20 ++------
 .../cql3/statements/DropIndexStatement.java     | 41 +++++++--------
 .../cql3/statements/DropKeyspaceStatement.java  | 11 ++---
 .../cql3/statements/DropTableStatement.java     | 11 ++---
 .../cql3/statements/DropTriggerStatement.java   |  9 +---
 .../cql3/statements/DropTypeStatement.java      | 11 ++---
 .../cql3/statements/DropViewStatement.java      | 11 ++---
 .../statements/SchemaAlteringStatement.java     | 26 ++++------
 .../org/apache/cassandra/cql3/CQLTester.java    | 29 ++++++++++-
 .../validation/entities/SecondaryIndexTest.java |  9 ++++
 .../index/internal/CassandraIndexTest.java      |  8 +++
 28 files changed, 220 insertions(+), 249 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/5fd65f3b/CHANGES.txt
----------------------------------------------------------------------


[3/4] cassandra git commit: Fix JSON update with prepared statements (patch for 3.0)

Posted by sl...@apache.org.
Fix JSON update with prepared statements (patch for 3.0)

patch by slebresne; reviewed by thobbs for CASSANDRA-10631


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

Branch: refs/heads/cassandra-3.1
Commit: 2cbd776250553a4e5d3dbb4dc286a3ceb7724158
Parents: b5fef75
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Mon Nov 2 13:18:59 2015 +0100
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Thu Nov 26 10:22:53 2015 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 src/java/org/apache/cassandra/cql3/Json.java    | 25 +---------
 .../org/apache/cassandra/cql3/QueryOptions.java | 52 ++++++++++++++++++--
 .../apache/cassandra/cql3/QueryProcessor.java   | 40 ++++++++++++---
 .../cql3/statements/AlterKeyspaceStatement.java |  7 +--
 .../cql3/statements/AlterTableStatement.java    |  9 +---
 .../cql3/statements/AlterTypeStatement.java     |  9 +---
 .../cql3/statements/AlterViewStatement.java     |  9 +---
 .../statements/CreateAggregateStatement.java    | 27 +++-------
 .../statements/CreateFunctionStatement.java     | 23 +++------
 .../cql3/statements/CreateIndexStatement.java   |  8 +--
 .../statements/CreateKeyspaceStatement.java     | 11 ++---
 .../cql3/statements/CreateTableStatement.java   | 11 ++---
 .../cql3/statements/CreateTriggerStatement.java |  9 +---
 .../cql3/statements/CreateTypeStatement.java    | 11 ++---
 .../cql3/statements/CreateViewStatement.java    | 12 ++---
 .../cql3/statements/DropAggregateStatement.java | 19 ++-----
 .../cql3/statements/DropFunctionStatement.java  | 20 ++------
 .../cql3/statements/DropIndexStatement.java     | 41 +++++++--------
 .../cql3/statements/DropKeyspaceStatement.java  | 11 ++---
 .../cql3/statements/DropTableStatement.java     | 11 ++---
 .../cql3/statements/DropTriggerStatement.java   |  9 +---
 .../cql3/statements/DropTypeStatement.java      | 11 ++---
 .../cql3/statements/DropViewStatement.java      | 11 ++---
 .../statements/SchemaAlteringStatement.java     | 26 ++++------
 .../org/apache/cassandra/cql3/CQLTester.java    | 29 ++++++++++-
 .../validation/entities/SecondaryIndexTest.java |  9 ++++
 .../index/internal/CassandraIndexTest.java      |  8 +++
 28 files changed, 220 insertions(+), 249 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index db6c72f..bd7006e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -9,6 +9,7 @@
  * Keep the file open in trySkipCache (CASSANDRA-10669)
  * Updated trigger example (CASSANDRA-10257)
 Merged from 2.2:
+ * Fix JSON update with prepared statements (CASSANDRA-10631)
  * Don't do anticompaction after subrange repair (CASSANDRA-10422)
  * Fix SimpleDateType type compatibility (CASSANDRA-10027)
  * (Hadoop) fix splits calculation (CASSANDRA-10640)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/Json.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Json.java b/src/java/org/apache/cassandra/cql3/Json.java
index df2d9ab..26e7ff2 100644
--- a/src/java/org/apache/cassandra/cql3/Json.java
+++ b/src/java/org/apache/cassandra/cql3/Json.java
@@ -135,8 +135,6 @@ public class Json
         private final int bindIndex;
         private final Collection<ColumnDefinition> columns;
 
-        private Map<ColumnIdentifier, Term> columnMap;
-
         public PreparedMarker(int bindIndex, Collection<ColumnDefinition> columns)
         {
             this.bindIndex = bindIndex;
@@ -147,24 +145,6 @@ public class Json
         {
             return new RawDelayedColumnValue(this, def);
         }
-
-        public void bind(QueryOptions options) throws InvalidRequestException
-        {
-            // this will be called once per column, so avoid duplicating work
-            if (columnMap != null)
-                return;
-
-            ByteBuffer value = options.getValues().get(bindIndex);
-            if (value == null)
-                throw new InvalidRequestException("Got null for INSERT JSON values");
-
-            columnMap = parseJson(UTF8Type.instance.getSerializer().deserialize(value), columns);
-        }
-
-        public Term getValue(ColumnDefinition def)
-        {
-            return columnMap.get(def.name);
-        }
     }
 
     /**
@@ -261,8 +241,7 @@ public class Json
         @Override
         public Terminal bind(QueryOptions options) throws InvalidRequestException
         {
-            marker.bind(options);
-            Term term = marker.getValue(column);
+            Term term = options.getJsonColumnValue(marker.bindIndex, column.name, marker.columns);
             return term == null ? null : term.bind(options);
         }
 
@@ -277,7 +256,7 @@ public class Json
     /**
      * Given a JSON string, return a map of columns to their values for the insert.
      */
-    private static Map<ColumnIdentifier, Term> parseJson(String jsonString, Collection<ColumnDefinition> expectedReceivers)
+    public static Map<ColumnIdentifier, Term> parseJson(String jsonString, Collection<ColumnDefinition> expectedReceivers)
     {
         try
         {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/QueryOptions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/QueryOptions.java b/src/java/org/apache/cassandra/cql3/QueryOptions.java
index e6e80e3..6324911 100644
--- a/src/java/org/apache/cassandra/cql3/QueryOptions.java
+++ b/src/java/org/apache/cassandra/cql3/QueryOptions.java
@@ -18,15 +18,15 @@
 package org.apache.cassandra.cql3;
 
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
+import java.util.*;
 
 import com.google.common.collect.ImmutableList;
-
 import io.netty.buffer.ByteBuf;
+
+import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.service.pager.PagingState;
 import org.apache.cassandra.transport.CBCodec;
@@ -48,6 +48,9 @@ public abstract class QueryOptions
 
     public static final CBCodec<QueryOptions> codec = new Codec();
 
+    // A cache of bind values parsed as JSON, see getJsonColumnValue for details.
+    private List<Map<ColumnIdentifier, Term>> jsonValuesCache;
+
     public static QueryOptions fromThrift(ConsistencyLevel consistency, List<ByteBuffer> values)
     {
         return new DefaultQueryOptions(consistency, values, false, SpecificOptions.DEFAULT, Server.VERSION_3);
@@ -83,6 +86,45 @@ public abstract class QueryOptions
     public abstract boolean skipMetadata();
 
     /**
+     * Returns the term corresponding to column {@code columnName} in the JSON value of bind index {@code bindIndex}.
+     *
+     * This is functionally equivalent to:
+     *   {@code Json.parseJson(UTF8Type.instance.getSerializer().deserialize(getValues().get(bindIndex)), expectedReceivers).get(columnName)}
+     * but this cache the result of parsing the JSON so that while this might be called for multiple columns on the same {@code bindIndex}
+     * value, the underlying JSON value is only parsed/processed once.
+     *
+     * Note: this is a bit more involved in CQL specifics than this class generally is but we as we need to cache this per-query and in an object
+     * that is available when we bind values, this is the easier place to have this.
+     *
+     * @param bindIndex the index of the bind value that should be interpreted as a JSON value.
+     * @param columnName the name of the column we want the value of.
+     * @param expectedReceivers the columns expected in the JSON value at index {@code bindIndex}. This is only used when parsing the
+     * json initially and no check is done afterwards. So in practice, any call of this method on the same QueryOptions object and with the same
+     * {@code bindIndx} values should use the same value for this parameter, but this isn't validated in any way.
+     *
+     * @return the value correspong to column {@code columnName} in the (JSON) bind value at index {@code bindIndex}. This may return null if the
+     * JSON value has no value for this column.
+     */
+    public Term getJsonColumnValue(int bindIndex, ColumnIdentifier columnName, Collection<ColumnDefinition> expectedReceivers) throws InvalidRequestException
+    {
+        if (jsonValuesCache == null)
+            jsonValuesCache = new ArrayList<>(Collections.<Map<ColumnIdentifier, Term>>nCopies(getValues().size(), null));
+
+        Map<ColumnIdentifier, Term> jsonValue = jsonValuesCache.get(bindIndex);
+        if (jsonValue == null)
+        {
+            ByteBuffer value = getValues().get(bindIndex);
+            if (value == null)
+                throw new InvalidRequestException("Got null for INSERT JSON values");
+
+            jsonValue = Json.parseJson(UTF8Type.instance.getSerializer().deserialize(value), expectedReceivers);
+            jsonValuesCache.set(bindIndex, jsonValue);
+        }
+
+        return jsonValue.get(columnName);
+    }
+
+    /**
      * Tells whether or not this <code>QueryOptions</code> contains the column specifications for the bound variables.
      * <p>The column specifications will be present only for prepared statements.</p>
      * @return <code>true</code> this <code>QueryOptions</code> contains the column specifications for the bound

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/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 59d4148..96e8387 100644
--- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java
+++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
@@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
@@ -533,6 +534,15 @@ public class QueryProcessor implements QueryHandler
         return meter.measureDeep(key);
     }
 
+    /**
+     * Clear our internal statmeent cache for test purposes.
+     */
+    @VisibleForTesting
+    public static void clearInternalStatementsCache()
+    {
+        internalStatements.clear();
+    }
+
     private static class MigrationSubscriber extends MigrationListener
     {
         private void removeInvalidPreparedStatements(String ksName, String cfName)
@@ -601,10 +611,7 @@ public class QueryProcessor implements QueryHandler
             // in case there are other overloads, we have to remove all overloads since argument type
             // matching may change (due to type casting)
             if (Schema.instance.getKSMetaData(ksName).functions.get(new FunctionName(ksName, functionName)).size() > 1)
-            {
-                removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, functionName);
-                removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, functionName);
-            }
+                removeAllInvalidPreparedStatementsForFunction(ksName, functionName);
         }
 
         public void onUpdateColumnFamily(String ksName, String cfName, boolean columnsDidChange)
@@ -614,6 +621,24 @@ public class QueryProcessor implements QueryHandler
                 removeInvalidPreparedStatements(ksName, cfName);
         }
 
+        public void onUpdateFunction(String ksName, String functionName, List<AbstractType<?>> argTypes)
+        {
+            // Updating a function may imply we've changed the body of the function, so we need to invalid statements so that
+            // the new definition is picked (the function is resolved at preparation time).
+            // TODO: if the function has multiple overload, we could invalidate only the statement refering to the overload
+            // that was updated. This requires a few changes however and probably doesn't matter much in practice.
+            removeAllInvalidPreparedStatementsForFunction(ksName, functionName);
+        }
+
+        public void onUpdateAggregate(String ksName, String aggregateName, List<AbstractType<?>> argTypes)
+        {
+            // Updating a function may imply we've changed the body of the function, so we need to invalid statements so that
+            // the new definition is picked (the function is resolved at preparation time).
+            // TODO: if the function has multiple overload, we could invalidate only the statement refering to the overload
+            // that was updated. This requires a few changes however and probably doesn't matter much in practice.
+            removeAllInvalidPreparedStatementsForFunction(ksName, aggregateName);
+        }
+
         public void onDropKeyspace(String ksName)
         {
             logger.trace("Keyspace {} was dropped, invalidating related prepared statements", ksName);
@@ -628,16 +653,17 @@ public class QueryProcessor implements QueryHandler
 
         public void onDropFunction(String ksName, String functionName, List<AbstractType<?>> argTypes)
         {
-            onDropFunctionInternal(ksName, functionName, argTypes);
+            removeAllInvalidPreparedStatementsForFunction(ksName, functionName);
         }
 
         public void onDropAggregate(String ksName, String aggregateName, List<AbstractType<?>> argTypes)
         {
-            onDropFunctionInternal(ksName, aggregateName, argTypes);
+            removeAllInvalidPreparedStatementsForFunction(ksName, aggregateName);
         }
 
-        private static void onDropFunctionInternal(String ksName, String functionName, List<AbstractType<?>> argTypes)
+        private static void removeAllInvalidPreparedStatementsForFunction(String ksName, String functionName)
         {
+            removeInvalidPreparedStatementsForFunction(internalStatements.values().iterator(), ksName, functionName);
             removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, functionName);
             removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, functionName);
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/AlterKeyspaceStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterKeyspaceStatement.java
index b660f52..5642b0d 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterKeyspaceStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterKeyspaceStatement.java
@@ -75,7 +75,7 @@ public class AlterKeyspaceStatement extends SchemaAlteringStatement
         }
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         KeyspaceMetadata oldKsm = Schema.instance.getKSMetaData(name);
         // In the (very) unlikely case the keyspace was dropped since validate()
@@ -84,11 +84,6 @@ public class AlterKeyspaceStatement extends SchemaAlteringStatement
 
         KeyspaceMetadata newKsm = oldKsm.withSwapped(attrs.asAlteredKeyspaceParams(oldKsm.params));
         MigrationManager.announceKeyspaceUpdate(newKsm, isLocalOnly);
-        return true;
-    }
-
-    public Event.SchemaChange changeEvent()
-    {
         return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, keyspace());
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
index a9b9d37..3515c6b 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
@@ -85,7 +85,7 @@ public class AlterTableStatement extends SchemaAlteringStatement
         // validated in announceMigration()
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         CFMetaData meta = validateColumnFamily(keyspace(), columnFamily());
         if (meta.isView())
@@ -329,7 +329,7 @@ public class AlterTableStatement extends SchemaAlteringStatement
             for (ViewDefinition viewUpdate : viewUpdates)
                 MigrationManager.announceViewUpdate(viewUpdate, isLocalOnly);
         }
-        return true;
+        return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
     }
 
     private static void validateAlter(CFMetaData cfm, ColumnDefinition def, AbstractType<?> validatorType)
@@ -387,9 +387,4 @@ public class AlterTableStatement extends SchemaAlteringStatement
                              rawColumnName,
                              validator);
     }
-
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
-    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
index 24bf4cb..068b996 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
@@ -78,18 +78,13 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
         // It doesn't really change anything anyway.
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName());
-    }
-
     @Override
     public String keyspace()
     {
         return name.getKeyspace();
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
     {
         KeyspaceMetadata ksm = Schema.instance.getKSMetaData(name.getKeyspace());
         if (ksm == null)
@@ -140,7 +135,7 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
             if (upd != null)
                 MigrationManager.announceTypeUpdate((UserType) upd, isLocalOnly);
         }
-        return true;
+        return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName());
     }
 
     private static int getIdxOfField(UserType type, ColumnIdentifier field)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/AlterViewStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterViewStatement.java
index e12ebd7..5b1699b 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterViewStatement.java
@@ -55,7 +55,7 @@ public class AlterViewStatement extends SchemaAlteringStatement
         // validated in announceMigration()
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         CFMetaData meta = validateColumnFamily(keyspace(), columnFamily());
         if (!meta.isView())
@@ -78,16 +78,11 @@ public class AlterViewStatement extends SchemaAlteringStatement
         viewCopy.metadata.params(params);
 
         MigrationManager.announceViewUpdate(viewCopy, isLocalOnly);
-        return true;
+        return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
     }
 
     public String toString()
     {
         return String.format("AlterViewStatement(name=%s)", cfName);
     }
-
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
-    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java
index 6fd0334..50f4f12 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java
@@ -50,9 +50,6 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement
     private final List<CQL3Type.Raw> argRawTypes;
     private final Term.Raw ival;
 
-    private UDAggregate udAggregate;
-    private boolean replaced;
-
     private List<AbstractType<?>> argTypes;
     private AbstractType<?> returnType;
     private ScalarFunction stateFunction;
@@ -192,20 +189,14 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement
             throw new InvalidRequestException(String.format("Cannot add aggregate '%s' to non existing keyspace '%s'.", functionName.name, functionName.keyspace));
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(replaced ? Event.SchemaChange.Change.UPDATED : Event.SchemaChange.Change.CREATED,
-                                      Event.SchemaChange.Target.AGGREGATE,
-                                      udAggregate.name().keyspace, udAggregate.name().name, AbstractType.asCQLTypeStringList(udAggregate.argTypes()));
-    }
-
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         Function old = Schema.instance.findFunction(functionName, argTypes).orElse(null);
-        if (old != null)
+        boolean replaced = old != null;
+        if (replaced)
         {
             if (ifNotExists)
-                return false;
+                return null;
             if (!orReplace)
                 throw new InvalidRequestException(String.format("Function %s already exists", old));
             if (!(old instanceof AggregateFunction))
@@ -223,15 +214,13 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement
         if (!stateFunction.isCalledOnNullInput() && initcond == null)
             throw new InvalidRequestException(String.format("Cannot create aggregate %s without INITCOND because state function %s does not accept 'null' arguments", functionName, stateFunc));
 
-        udAggregate = new UDAggregate(functionName, argTypes, returnType,
-                                                  stateFunction,
-                                                  finalFunction,
-                                                  initcond);
-        replaced = old != null;
+        UDAggregate udAggregate = new UDAggregate(functionName, argTypes, returnType, stateFunction, finalFunction, initcond);
 
         MigrationManager.announceNewAggregate(udAggregate, isLocalOnly);
 
-        return true;
+        return new Event.SchemaChange(replaced ? Event.SchemaChange.Change.UPDATED : Event.SchemaChange.Change.CREATED,
+                                      Event.SchemaChange.Target.AGGREGATE,
+                                      udAggregate.name().keyspace, udAggregate.name().name, AbstractType.asCQLTypeStringList(udAggregate.argTypes()));
     }
 
     private static String stateFuncSig(FunctionName stateFuncName, CQL3Type.Raw stateTypeRaw, List<CQL3Type.Raw> argRawTypes)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java
index bd632bb..a54c49e 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java
@@ -54,8 +54,6 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement
 
     private List<AbstractType<?>> argTypes;
     private AbstractType<?> returnType;
-    private UDFunction udFunction;
-    private boolean replaced;
 
     public CreateFunctionStatement(FunctionName functionName,
                                    String language,
@@ -140,20 +138,14 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement
             throw new InvalidRequestException(String.format("Cannot add function '%s' to non existing keyspace '%s'.", functionName.name, functionName.keyspace));
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(replaced ? Event.SchemaChange.Change.UPDATED : Event.SchemaChange.Change.CREATED,
-                                      Event.SchemaChange.Target.FUNCTION,
-                                      udFunction.name().keyspace, udFunction.name().name, AbstractType.asCQLTypeStringList(udFunction.argTypes()));
-    }
-
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         Function old = Schema.instance.findFunction(functionName, argTypes).orElse(null);
-        if (old != null)
+        boolean replaced = old != null;
+        if (replaced)
         {
             if (ifNotExists)
-                return false;
+                return null;
             if (!orReplace)
                 throw new InvalidRequestException(String.format("Function %s already exists", old));
             if (!(old instanceof ScalarFunction))
@@ -167,12 +159,13 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement
                                                                 functionName, returnType.asCQL3Type(), old.returnType().asCQL3Type()));
         }
 
-        this.udFunction = UDFunction.create(functionName, argNames, argTypes, returnType, calledOnNullInput, language, body);
-        this.replaced = old != null;
+        UDFunction udFunction = UDFunction.create(functionName, argNames, argTypes, returnType, calledOnNullInput, language, body);
 
         MigrationManager.announceNewFunction(udFunction, isLocalOnly);
 
-        return true;
+        return new Event.SchemaChange(replaced ? Event.SchemaChange.Change.UPDATED : Event.SchemaChange.Change.CREATED,
+                                      Event.SchemaChange.Target.FUNCTION,
+                                      udFunction.name().keyspace, udFunction.name().name, AbstractType.asCQLTypeStringList(udFunction.argTypes()));
     }
 
     private AbstractType<?> prepareType(String typeName, CQL3Type.Raw rawType)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
index e26a1eb..b2a6fd5 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
@@ -188,7 +188,7 @@ public class CreateIndexStatement extends SchemaAlteringStatement
                 throw new InvalidRequestException("Duplicate column " + target.column + " in index target list");
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()).copy();
         List<IndexTarget> targets = new ArrayList<>(rawTargets.size());
@@ -206,7 +206,7 @@ public class CreateIndexStatement extends SchemaAlteringStatement
         if (Schema.instance.getKSMetaData(keyspace()).existingIndexNames(null).contains(acceptedName))
         {
             if (ifNotExists)
-                return false;
+                return null;
             else
                 throw new InvalidRequestException(String.format("Index %s already exists", acceptedName));
         }
@@ -237,11 +237,7 @@ public class CreateIndexStatement extends SchemaAlteringStatement
         cfm.indexes(cfm.getIndexes().with(index));
 
         MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly);
-        return true;
-    }
 
-    public Event.SchemaChange changeEvent()
-    {
         // Creating an index is akin to updating the CF
         return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java
index 9577af8..3eb0ac9 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java
@@ -92,27 +92,22 @@ public class CreateKeyspaceStatement extends SchemaAlteringStatement
             throw new ConfigurationException("Unable to use given strategy class: LocalStrategy is reserved for internal use.");
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         KeyspaceMetadata ksm = KeyspaceMetadata.create(name, attrs.asNewKeyspaceParams());
         try
         {
             MigrationManager.announceNewKeyspace(ksm, isLocalOnly);
-            return true;
+            return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, keyspace());
         }
         catch (AlreadyExistsException e)
         {
             if (ifNotExists)
-                return false;
+                return null;
             throw e;
         }
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, keyspace());
-    }
-
     protected void grantPermissionsToCreator(QueryState state)
     {
         try

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
index a1947df..1363bee 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
@@ -78,26 +78,21 @@ public class CreateTableStatement extends SchemaAlteringStatement
         // validated in announceMigration()
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         try
         {
             MigrationManager.announceNewColumnFamily(getCFMetaData(), isLocalOnly);
-            return true;
+            return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
         }
         catch (AlreadyExistsException e)
         {
             if (ifNotExists)
-                return false;
+                return null;
             throw e;
         }
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
-    }
-
     protected void grantPermissionsToCreator(QueryState state)
     {
         try

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java
index 2589622..2720749 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java
@@ -72,7 +72,7 @@ public class CreateTriggerStatement extends SchemaAlteringStatement
         }
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws ConfigurationException, InvalidRequestException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws ConfigurationException, InvalidRequestException
     {
         CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()).copy();
         Triggers triggers = cfm.getTriggers();
@@ -80,7 +80,7 @@ public class CreateTriggerStatement extends SchemaAlteringStatement
         if (triggers.get(triggerName).isPresent())
         {
             if (ifNotExists)
-                return false;
+                return null;
             else
                 throw new InvalidRequestException(String.format("Trigger %s already exists", triggerName));
         }
@@ -88,11 +88,6 @@ public class CreateTriggerStatement extends SchemaAlteringStatement
         cfm.triggers(triggers.with(TriggerMetadata.create(triggerName, triggerClass)));
         logger.info("Adding trigger with name {} and class {}", triggerName, triggerClass);
         MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly);
-        return true;
-    }
-
-    public Event.SchemaChange changeEvent()
-    {
         return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java
index 465f0f1..f62b9ea 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java
@@ -93,11 +93,6 @@ public class CreateTypeStatement extends SchemaAlteringStatement
         }
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName());
-    }
-
     @Override
     public String keyspace()
     {
@@ -117,18 +112,18 @@ public class CreateTypeStatement extends SchemaAlteringStatement
         return new UserType(name.getKeyspace(), name.getUserTypeName(), names, types);
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
     {
         KeyspaceMetadata ksm = Schema.instance.getKSMetaData(name.getKeyspace());
         assert ksm != null; // should haven't validate otherwise
 
         // Can happen with ifNotExists
         if (ksm.types.get(name.getUserTypeName()).isPresent())
-            return false;
+            return null;
 
         UserType type = createType();
         checkForDuplicateNames(type);
         MigrationManager.announceNewType(type, isLocalOnly);
-        return true;
+        return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName());
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java
index 586b09b..5d1fd45 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java
@@ -111,7 +111,7 @@ public class CreateViewStatement extends SchemaAlteringStatement
         }
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         // We need to make sure that:
         //  - primary key includes all columns in base table's primary key
@@ -292,15 +292,14 @@ public class CreateViewStatement extends SchemaAlteringStatement
         try
         {
             MigrationManager.announceNewView(definition, isLocalOnly);
+            return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
         }
         catch (AlreadyExistsException e)
         {
             if (ifNotExists)
-                return false;
+                return null;
             throw e;
         }
-
-        return true;
     }
 
     private static boolean getColumnIdentifier(CFMetaData cfm,
@@ -327,9 +326,4 @@ public class CreateViewStatement extends SchemaAlteringStatement
         columns.add(identifier);
         return !isPk;
     }
-
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
-    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/DropAggregateStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropAggregateStatement.java
index 3aa176e..bef9e74 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropAggregateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropAggregateStatement.java
@@ -44,8 +44,6 @@ public final class DropAggregateStatement extends SchemaAlteringStatement
     private final List<CQL3Type.Raw> argRawTypes;
     private final boolean argsPresent;
 
-    private Function old;
-
     public DropAggregateStatement(FunctionName functionName,
                                   List<CQL3Type.Raw> argRawTypes,
                                   boolean argsPresent,
@@ -79,13 +77,7 @@ public final class DropAggregateStatement extends SchemaAlteringStatement
     {
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.AGGREGATE,
-                                      old.name().keyspace, old.name().name, AbstractType.asCQLTypeStringList(old.argTypes()));
-    }
-
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
         Collection<Function> olds = Schema.instance.getFunctions(functionName);
 
@@ -107,7 +99,7 @@ public final class DropAggregateStatement extends SchemaAlteringStatement
             if (old == null || !(old instanceof AggregateFunction))
             {
                 if (ifExists)
-                    return false;
+                    return null;
                 // just build a nicer error message
                 StringBuilder sb = new StringBuilder();
                 for (CQL3Type.Raw rawType : argRawTypes)
@@ -125,7 +117,7 @@ public final class DropAggregateStatement extends SchemaAlteringStatement
             if (olds == null || olds.isEmpty() || !(olds.iterator().next() instanceof AggregateFunction))
             {
                 if (ifExists)
-                    return false;
+                    return null;
                 throw new InvalidRequestException(String.format("Cannot drop non existing aggregate '%s'", functionName));
             }
             old = olds.iterator().next();
@@ -135,11 +127,10 @@ public final class DropAggregateStatement extends SchemaAlteringStatement
             throw new InvalidRequestException(String.format("Cannot drop aggregate '%s' because it is a " +
                                                             "native (built-in) function", functionName));
 
-        this.old = old;
-
         MigrationManager.announceAggregateDrop((UDAggregate)old, isLocalOnly);
+        return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.AGGREGATE,
+                                      old.name().keyspace, old.name().name, AbstractType.asCQLTypeStringList(old.argTypes()));
 
-        return true;
     }
 
     private AbstractType<?> prepareType(String typeName, CQL3Type.Raw rawType)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java
index 59864df..3cef2da 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java
@@ -48,7 +48,6 @@ public final class DropFunctionStatement extends SchemaAlteringStatement
     private final List<CQL3Type.Raw> argRawTypes;
     private final boolean argsPresent;
 
-    private Function old;
     private List<AbstractType<?>> argTypes;
 
     public DropFunctionStatement(FunctionName functionName,
@@ -93,7 +92,6 @@ public final class DropFunctionStatement extends SchemaAlteringStatement
         ThriftValidation.validateKeyspaceNotSystem(functionName.keyspace);
     }
 
-    @Override
     public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException
     {
         Function function = findFunction();
@@ -113,7 +111,6 @@ public final class DropFunctionStatement extends SchemaAlteringStatement
         }
     }
 
-    @Override
     public void validate(ClientState state)
     {
         Collection<Function> olds = Schema.instance.getFunctions(functionName);
@@ -126,21 +123,13 @@ public final class DropFunctionStatement extends SchemaAlteringStatement
                                                             functionName, functionName, functionName));
     }
 
-    @Override
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.FUNCTION,
-                                      old.name().keyspace, old.name().name, AbstractType.asCQLTypeStringList(old.argTypes()));
-    }
-
-    @Override
-    public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException
     {
-        old = findFunction();
+        Function old = findFunction();
         if (old == null)
         {
             if (ifExists)
-                return false;
+                return null;
             else
                 throw new InvalidRequestException(getMissingFunctionError());
         }
@@ -152,7 +141,8 @@ public final class DropFunctionStatement extends SchemaAlteringStatement
 
         MigrationManager.announceFunctionDrop((UDFunction) old, isLocalOnly);
 
-        return true;
+        return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.FUNCTION,
+                                      old.name().keyspace, old.name().name, AbstractType.asCQLTypeStringList(old.argTypes()));
     }
 
     private String getMissingFunctionError()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java
index 63a1200..eaf755f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java
@@ -36,9 +36,6 @@ public class DropIndexStatement extends SchemaAlteringStatement
     public final String indexName;
     public final boolean ifExists;
 
-    // initialized in announceMigration()
-    private CFMetaData indexedTable;
-
     public DropIndexStatement(IndexName indexName, boolean ifExists)
     {
         super(indexName.getCfName());
@@ -48,11 +45,8 @@ public class DropIndexStatement extends SchemaAlteringStatement
 
     public String columnFamily()
     {
-        if (indexedTable != null)
-            return indexedTable.cfName;
-
-        indexedTable = lookupIndexedTable();
-        return indexedTable == null ? null : indexedTable.cfName;
+        CFMetaData cfm = lookupIndexedTable();
+        return cfm == null ? null : cfm.cfName;
     }
 
     public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException
@@ -69,36 +63,39 @@ public class DropIndexStatement extends SchemaAlteringStatement
         // validated in lookupIndexedTable()
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        // Dropping an index is akin to updating the CF
-        return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
-    }
-
     @Override
     public ResultMessage execute(QueryState state, QueryOptions options) throws RequestValidationException
     {
-        return announceMigration(false) ? new ResultMessage.SchemaChange(changeEvent()) : null;
+        Event.SchemaChange ce = announceMigration(false);
+        return ce == null ? null : new ResultMessage.SchemaChange(ce);
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
     {
         CFMetaData cfm = lookupIndexedTable();
         if (cfm == null)
-            return false;
+            return null;
 
-        indexedTable = cfm;
         CFMetaData updatedCfm = cfm.copy();
         updatedCfm.indexes(updatedCfm.getIndexes().without(indexName));
         MigrationManager.announceColumnFamilyUpdate(updatedCfm, false, isLocalOnly);
-        return true;
+        // Dropping an index is akin to updating the CF
+        // Note that we shouldn't call columnFamily() at this point because the index has been dropped and the call to lookupIndexedTable()
+        // in that method would now throw.
+        return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, cfm.ksName, cfm.cfName);
     }
 
+    /**
+     * The table for which the index should be dropped, or null if the index doesn't exist
+     *
+     * @return the metadata for the table containing the dropped index, or {@code null}
+     * if the index to drop cannot be found but "IF EXISTS" is set on the statement.
+     *
+     * @throws InvalidRequestException if the index cannot be found and "IF EXISTS" is not
+     * set on the statement.
+     */
     private CFMetaData lookupIndexedTable()
     {
-        if (indexedTable != null)
-            return indexedTable;
-
         KeyspaceMetadata ksm = Schema.instance.getKSMetaData(keyspace());
         if (ksm == null)
             throw new KeyspaceNotDefinedException("Keyspace " + keyspace() + " does not exist");

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java
index ba6b917..513ff1b 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java
@@ -55,23 +55,18 @@ public class DropKeyspaceStatement extends SchemaAlteringStatement
         return keyspace;
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws ConfigurationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws ConfigurationException
     {
         try
         {
             MigrationManager.announceKeyspaceDrop(keyspace, isLocalOnly);
-            return true;
+            return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, keyspace());
         }
         catch(ConfigurationException e)
         {
             if (ifExists)
-                return false;
+                return null;
             throw e;
         }
     }
-
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, keyspace());
-    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java
index 1f61020..79c46f5 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java
@@ -58,7 +58,7 @@ public class DropTableStatement extends SchemaAlteringStatement
         // validated in announceMigration()
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws ConfigurationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws ConfigurationException
     {
         try
         {
@@ -89,18 +89,13 @@ public class DropTableStatement extends SchemaAlteringStatement
                 }
             }
             MigrationManager.announceColumnFamilyDrop(keyspace(), columnFamily(), isLocalOnly);
-            return true;
+            return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
         }
         catch (ConfigurationException e)
         {
             if (ifExists)
-                return false;
+                return null;
             throw e;
         }
     }
-
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
-    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/DropTriggerStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropTriggerStatement.java
index 54711de..562b4e8 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropTriggerStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropTriggerStatement.java
@@ -58,7 +58,7 @@ public class DropTriggerStatement extends SchemaAlteringStatement
         ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws ConfigurationException, InvalidRequestException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws ConfigurationException, InvalidRequestException
     {
         CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()).copy();
         Triggers triggers = cfm.getTriggers();
@@ -66,7 +66,7 @@ public class DropTriggerStatement extends SchemaAlteringStatement
         if (!triggers.get(triggerName).isPresent())
         {
             if (ifExists)
-                return false;
+                return null;
             else
                 throw new InvalidRequestException(String.format("Trigger %s was not found", triggerName));
         }
@@ -74,11 +74,6 @@ public class DropTriggerStatement extends SchemaAlteringStatement
         logger.info("Dropping trigger with name {}", triggerName);
         cfm.triggers(triggers.without(triggerName));
         MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly);
-        return true;
-    }
-
-    public Event.SchemaChange changeEvent()
-    {
         return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
index d104b73..58abde9 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
@@ -124,18 +124,13 @@ public class DropTypeStatement extends SchemaAlteringStatement
         return false;
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName());
-    }
-
     @Override
     public String keyspace()
     {
         return name.getKeyspace();
     }
 
-    public boolean announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
     {
         KeyspaceMetadata ksm = Schema.instance.getKSMetaData(name.getKeyspace());
         assert ksm != null;
@@ -143,9 +138,9 @@ public class DropTypeStatement extends SchemaAlteringStatement
         UserType toDrop = ksm.types.getNullable(name.getUserTypeName());
         // Can be null with ifExists
         if (toDrop == null)
-            return false;
+            return null;
 
         MigrationManager.announceTypeDrop(toDrop, isLocalOnly);
-        return true;
+        return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName());
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/DropViewStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropViewStatement.java
index f2be370..1f53ac4 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropViewStatement.java
@@ -53,12 +53,7 @@ public class DropViewStatement extends SchemaAlteringStatement
         // validated in findIndexedCf()
     }
 
-    public Event.SchemaChange changeEvent()
-    {
-        return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
-    }
-
-    public boolean announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
+    public Event.SchemaChange announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException
     {
         try
         {
@@ -81,12 +76,12 @@ public class DropViewStatement extends SchemaAlteringStatement
 //            }
 
             MigrationManager.announceViewDrop(keyspace(), columnFamily(), isLocalOnly);
-            return true;
+            return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
         }
         catch (ConfigurationException e)
         {
             if (ifExists)
-                return false;
+                return null;
             throw e;
         }
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java b/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java
index a477df6..10c004c 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java
@@ -65,8 +65,6 @@ public abstract class SchemaAlteringStatement extends CFStatement implements CQL
         return new Prepared(this);
     }
 
-    public abstract Event.SchemaChange changeEvent();
-
     /**
      * Schema alteration may result in a new database object (keyspace, table, role, function) being created capable of
      * having permissions GRANTed on it. The creator of the object (the primary role assigned to the AuthenticatedUser
@@ -80,29 +78,29 @@ public abstract class SchemaAlteringStatement extends CFStatement implements CQL
 
     /**
      * Announces the migration to other nodes in the cluster.
-     * @return true if the execution of this statement resulted in a schema change, false otherwise (when IF NOT EXISTS
-     * is used, for example)
+     *
+     * @return the schema change event corresponding to the execution of this statement, or {@code null} if no schema change
+     * has occurred (when IF NOT EXISTS is used, for example)
+     *
      * @throws RequestValidationException
      */
-    public abstract boolean announceMigration(boolean isLocalOnly) throws RequestValidationException;
+    public abstract Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException;
 
     public ResultMessage execute(QueryState state, QueryOptions options) throws RequestValidationException
     {
         // If an IF [NOT] EXISTS clause was used, this may not result in an actual schema change.  To avoid doing
         // extra work in the drivers to handle schema changes, we return an empty message in this case. (CASSANDRA-7600)
-        boolean didChangeSchema = announceMigration(false);
-        if (!didChangeSchema)
+        Event.SchemaChange ce = announceMigration(false);
+        if (ce == null)
             return new ResultMessage.Void();
 
-        Event.SchemaChange ce = changeEvent();
-
         // when a schema alteration results in a new db object being created, we grant permissions on the new
         // object to the user performing the request if:
         // * the user is not anonymous
         // * the configured IAuthorizer supports granting of permissions (not all do, AllowAllAuthorizer doesn't and
         //   custom external implementations may not)
         AuthenticatedUser user = state.getClientState().getUser();
-        if (user != null && !user.isAnonymous() && ce != null && ce.change == Event.SchemaChange.Change.CREATED)
+        if (user != null && !user.isAnonymous() && ce.change == Event.SchemaChange.Change.CREATED)
         {
             try
             {
@@ -114,16 +112,12 @@ public abstract class SchemaAlteringStatement extends CFStatement implements CQL
             }
         }
 
-        return ce == null ? new ResultMessage.Void() : new ResultMessage.SchemaChange(ce);
+        return new ResultMessage.SchemaChange(ce);
     }
 
     public ResultMessage executeInternal(QueryState state, QueryOptions options)
     {
-        boolean didChangeSchema = announceMigration(true);
-        if (!didChangeSchema)
-            return new ResultMessage.Void();
-
-        Event.SchemaChange ce = changeEvent();
+        Event.SchemaChange ce = announceMigration(true);
         return ce == null ? new ResultMessage.Void() : new ResultMessage.SchemaChange(ce);
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/test/unit/org/apache/cassandra/cql3/CQLTester.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java
index fd0b086..fd9bb28 100644
--- a/test/unit/org/apache/cassandra/cql3/CQLTester.java
+++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java
@@ -78,6 +78,7 @@ public abstract class CQLTester
     public static final String KEYSPACE = "cql_test_keyspace";
     public static final String KEYSPACE_PER_TEST = "cql_test_keyspace_alt";
     protected static final boolean USE_PREPARED_VALUES = Boolean.valueOf(System.getProperty("cassandra.test.use_prepared", "true"));
+    protected static final boolean REUSE_PREPARED = Boolean.valueOf(System.getProperty("cassandra.test.reuse_prepared", "true"));
     protected static final long ROW_CACHE_SIZE_IN_MB = Integer.valueOf(System.getProperty("cassandra.test.row_cache_size_in_mb", "0"));
     private static final AtomicInteger seqNumber = new AtomicInteger();
 
@@ -137,6 +138,7 @@ public abstract class CQLTester
     // We don't use USE_PREPARED_VALUES in the code below so some test can foce value preparation (if the result
     // is not expected to be the same without preparation)
     private boolean usePrepared = USE_PREPARED_VALUES;
+    private static boolean reusePrepared = REUSE_PREPARED;
 
     public static void prepareServer()
     {
@@ -236,6 +238,11 @@ public abstract class CQLTester
 
         if (server != null)
             server.stop();
+
+        // We use queryInternal for CQLTester so prepared statement will populate our internal cache (if reusePrepared is used; otherwise prepared
+        // statements are not cached but re-prepared every time). So we clear the cache between test files to avoid accumulating too much.
+        if (reusePrepared)
+            QueryProcessor.clearInternalStatementsCache();
     }
 
     @Before
@@ -252,6 +259,7 @@ public abstract class CQLTester
 
         // Restore standard behavior in case it was changed
         usePrepared = USE_PREPARED_VALUES;
+        reusePrepared = REUSE_PREPARED;
 
         final List<String> tablesToDrop = copy(tables);
         final List<String> typesToDrop = copy(types);
@@ -447,6 +455,11 @@ public abstract class CQLTester
         this.usePrepared = USE_PREPARED_VALUES;
     }
 
+    protected void disablePreparedReuseForTest()
+    {
+        this.reusePrepared = false;
+    }
+
     protected String createType(String query)
     {
         String typeName = "type_" + seqNumber.getAndIncrement();
@@ -655,7 +668,21 @@ public abstract class CQLTester
         {
             if (logger.isDebugEnabled())
                 logger.debug("Executing: {} with values {}", query, formatAllValues(values));
-            rs = QueryProcessor.executeOnceInternal(query, transformValues(values));
+            if (reusePrepared)
+            {
+                rs = QueryProcessor.executeInternal(query, transformValues(values));
+
+                // If a test uses a "USE ...", then presumably its statements use relative table. In that case, a USE
+                // change the meaning of the current keyspace, so we don't want a following statement to reuse a previously
+                // prepared statement at this wouldn't use the right keyspace. To avoid that, we drop the previously
+                // prepared statement.
+                if (query.startsWith("USE"))
+                    QueryProcessor.clearInternalStatementsCache();
+            }
+            else
+            {
+                rs = QueryProcessor.executeOnceInternal(query, transformValues(values));
+            }
         }
         else
         {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
index 225e197..d30937f 100644
--- a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
@@ -23,6 +23,7 @@ import java.util.*;
 import org.apache.cassandra.db.DeletionTime;
 import org.apache.cassandra.utils.Pair;
 import org.apache.commons.lang3.StringUtils;
+import org.junit.Before;
 import org.junit.Test;
 
 import org.apache.cassandra.config.CFMetaData;
@@ -53,6 +54,14 @@ public class SecondaryIndexTest extends CQLTester
 {
     private static final int TOO_BIG = 1024 * 65;
 
+    @Before
+    public void disablePreparedReuse() throws Throwable
+    {
+        // TODO: this shouldn't be needed but is due to #10758. As such, this should be removed on that
+        // ticket is fixed.
+        disablePreparedReuseForTest();
+    }
+
     @Test
     public void testCreateAndDropIndex() throws Throwable
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2cbd7762/test/unit/org/apache/cassandra/index/internal/CassandraIndexTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/index/internal/CassandraIndexTest.java b/test/unit/org/apache/cassandra/index/internal/CassandraIndexTest.java
index 934e551..c72b4ec 100644
--- a/test/unit/org/apache/cassandra/index/internal/CassandraIndexTest.java
+++ b/test/unit/org/apache/cassandra/index/internal/CassandraIndexTest.java
@@ -24,6 +24,7 @@ import java.util.stream.StreamSupport;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.*;
+import org.junit.Before;
 import org.junit.Test;
 
 import org.apache.cassandra.config.CFMetaData;
@@ -55,6 +56,13 @@ import static org.junit.Assert.fail;
  */
 public class CassandraIndexTest extends CQLTester
 {
+    @Before
+    public void disablePreparedReuse() throws Throwable
+    {
+        // TODO: this shouldn't be needed but is due to #10758. As such, this should be removed on that
+        // ticket is fixed.
+        disablePreparedReuseForTest();
+    }
 
     @Test
     public void indexOnRegularColumn() throws Throwable


[2/4] cassandra git commit: Merge commit '31be903a74864e58980e0e22fd993a280c9138b3' into cassandra-3.0

Posted by sl...@apache.org.
Merge commit '31be903a74864e58980e0e22fd993a280c9138b3' into cassandra-3.0


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

Branch: refs/heads/cassandra-3.1
Commit: b5fef7522e59b9930598ffd2a58c96f27f6a2423
Parents: 71bca78 31be903
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Thu Nov 26 10:13:50 2015 +0100
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Thu Nov 26 10:13:50 2015 +0100

----------------------------------------------------------------------

----------------------------------------------------------------------