You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ty...@apache.org on 2015/09/19 17:07:52 UTC

[3/4] cassandra git commit: Allow MV's SELECT to restrict PK columns

Allow MV's SELECT to restrict PK columns

Patch by Tyler Hobbs; reviewed by Sylvain Lebresne for CASSANDRA-9664


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

Branch: refs/heads/trunk
Commit: 5a4253b6a17de9810fbc4e1c3b8d4980e26adcca
Parents: 41731b8
Author: Tyler Hobbs <ty...@gmail.com>
Authored: Sat Sep 19 10:06:45 2015 -0500
Committer: Tyler Hobbs <ty...@gmail.com>
Committed: Sat Sep 19 10:06:45 2015 -0500

----------------------------------------------------------------------
 CHANGES.txt                                     |    2 +
 .../apache/cassandra/config/ViewDefinition.java |   74 +-
 .../apache/cassandra/cql3/AbstractMarker.java   |   31 +-
 .../apache/cassandra/cql3/ColumnIdentifier.java |   35 +
 .../org/apache/cassandra/cql3/Constants.java    |   26 +-
 src/java/org/apache/cassandra/cql3/Cql.g        |   12 +-
 src/java/org/apache/cassandra/cql3/Json.java    |   42 +-
 src/java/org/apache/cassandra/cql3/Lists.java   |    8 +-
 src/java/org/apache/cassandra/cql3/Maps.java    |   18 +-
 .../cassandra/cql3/MultiColumnRelation.java     |   28 +-
 .../org/apache/cassandra/cql3/Operator.java     |   58 +-
 .../org/apache/cassandra/cql3/Relation.java     |   23 +
 src/java/org/apache/cassandra/cql3/Sets.java    |    8 +-
 .../cassandra/cql3/SingleColumnRelation.java    |   30 +
 src/java/org/apache/cassandra/cql3/Term.java    |   19 +-
 .../apache/cassandra/cql3/TokenRelation.java    |   26 +
 src/java/org/apache/cassandra/cql3/Tuples.java  |   24 +-
 .../org/apache/cassandra/cql3/TypeCast.java     |    5 +-
 .../org/apache/cassandra/cql3/UserTypes.java    |    7 +-
 .../cassandra/cql3/functions/FunctionCall.java  |   16 +-
 .../cql3/restrictions/AbstractRestriction.java  |   14 +-
 .../ForwardingPrimaryKeyRestrictions.java       |    6 +
 .../restrictions/MultiColumnRestriction.java    |   55 +
 .../cql3/restrictions/Restriction.java          |    1 +
 .../restrictions/SingleColumnRestriction.java   |   58 +
 .../restrictions/StatementRestrictions.java     |   69 +-
 .../cql3/statements/AlterTableStatement.java    |    2 +-
 .../cql3/statements/CreateViewStatement.java    |   74 +-
 .../cassandra/cql3/statements/IndexTarget.java  |   14 +-
 .../cql3/statements/ModificationStatement.java  |    2 +-
 .../cql3/statements/ParsedStatement.java        |    5 +
 .../cql3/statements/SelectStatement.java        |   27 +-
 .../cql3/statements/UpdateStatement.java        |    2 +
 .../cassandra/db/PartitionRangeReadCommand.java |   13 +-
 .../org/apache/cassandra/db/ReadCommand.java    |   12 -
 src/java/org/apache/cassandra/db/ReadQuery.java |   24 +-
 .../db/SinglePartitionReadCommand.java          |   25 +-
 .../apache/cassandra/db/filter/RowFilter.java   |   39 +
 .../apache/cassandra/db/view/TemporalRow.java   |   17 +-
 src/java/org/apache/cassandra/db/view/View.java |  156 ++-
 .../apache/cassandra/db/view/ViewBuilder.java   |   15 +-
 .../internal/composites/CompositesSearcher.java |    2 +-
 .../apache/cassandra/schema/SchemaKeyspace.java |   16 +-
 .../org/apache/cassandra/cql3/CQLTester.java    |   78 ++
 .../cassandra/cql3/ViewFilteringTest.java       | 1292 ++++++++++++++++++
 .../org/apache/cassandra/cql3/ViewTest.java     |    4 +-
 .../SelectSingleColumnRelationTest.java         |    4 +
 47 files changed, 2270 insertions(+), 248 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index e55fd0a..e589626 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,6 @@
 3.0.0-rc1
+ * Allow MATERIALIZED VIEW's SELECT statement to restrict primary key
+   columns (CASSANDRA-9664)
  * Move crc_check_chance out of compression options (CASSANDRA-9839)
  * Fix descending iteration past end of BTreeSearchIterator (CASSANDRA-10301)
  * Transfer hints to a different node on decommission (CASSANDRA-10198)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/config/ViewDefinition.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/ViewDefinition.java b/src/java/org/apache/cassandra/config/ViewDefinition.java
index 39695b9..02acc68 100644
--- a/src/java/org/apache/cassandra/config/ViewDefinition.java
+++ b/src/java/org/apache/cassandra/config/ViewDefinition.java
@@ -17,26 +17,35 @@
  */
 package org.apache.cassandra.config;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
+import org.antlr.runtime.*;
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.statements.SelectStatement;
+import org.apache.cassandra.db.view.View;
+import org.apache.cassandra.exceptions.SyntaxException;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 
-import org.apache.cassandra.cql3.ColumnIdentifier;
-
 public class ViewDefinition
 {
     public final String ksName;
     public final String viewName;
     public final UUID baseTableId;
+    public final String baseTableName;
     public final boolean includeAllColumns;
     // The order of partititon columns and clustering columns is important, so we cannot switch these two to sets
     public final CFMetaData metadata;
 
+    public SelectStatement.RawStatement select;
+    public String whereClause;
+
     public ViewDefinition(ViewDefinition def)
     {
-        this(def.ksName, def.viewName, def.baseTableId, def.includeAllColumns, def.metadata);
+        this(def.ksName, def.viewName, def.baseTableId, def.baseTableName, def.includeAllColumns, def.select, def.whereClause, def.metadata);
     }
 
     /**
@@ -44,12 +53,15 @@ public class ViewDefinition
      * @param baseTableId       Internal ID of the table which this view is based off of
      * @param includeAllColumns Whether to include all columns or not
      */
-    public ViewDefinition(String ksName, String viewName, UUID baseTableId, boolean includeAllColumns, CFMetaData metadata)
+    public ViewDefinition(String ksName, String viewName, UUID baseTableId, String baseTableName, boolean includeAllColumns, SelectStatement.RawStatement select, String whereClause, CFMetaData metadata)
     {
         this.ksName = ksName;
         this.viewName = viewName;
         this.baseTableId = baseTableId;
+        this.baseTableName = baseTableName;
         this.includeAllColumns = includeAllColumns;
+        this.select = select;
+        this.whereClause = whereClause;
         this.metadata = metadata;
     }
 
@@ -63,7 +75,7 @@ public class ViewDefinition
 
     public ViewDefinition copy()
     {
-        return new ViewDefinition(ksName, viewName, baseTableId, includeAllColumns, metadata.copy());
+        return new ViewDefinition(ksName, viewName, baseTableId, baseTableName, includeAllColumns, select, whereClause, metadata.copy());
     }
 
     public CFMetaData baseTableMetadata()
@@ -85,6 +97,7 @@ public class ViewDefinition
                && Objects.equals(viewName, other.viewName)
                && Objects.equals(baseTableId, other.baseTableId)
                && Objects.equals(includeAllColumns, other.includeAllColumns)
+               && Objects.equals(whereClause, other.whereClause)
                && Objects.equals(metadata, other.metadata);
     }
 
@@ -96,6 +109,7 @@ public class ViewDefinition
                .append(viewName)
                .append(baseTableId)
                .append(includeAllColumns)
+               .append(whereClause)
                .append(metadata)
                .toHashCode();
     }
@@ -107,8 +121,58 @@ public class ViewDefinition
                .append("ksName", ksName)
                .append("viewName", viewName)
                .append("baseTableId", baseTableId)
+               .append("baseTableName", baseTableName)
                .append("includeAllColumns", includeAllColumns)
+               .append("whereClause", whereClause)
                .append("metadata", metadata)
                .toString();
     }
+
+    /**
+     * Replace the column {@param from} with {@param to} in this materialized view definition's partition,
+     * clustering, or included columns.
+     */
+    public void renameColumn(ColumnIdentifier from, ColumnIdentifier to)
+    {
+        metadata.renameColumn(from, to);
+
+        // convert whereClause to Relations, rename ids in Relations, then convert back to whereClause
+        List<Relation> relations = whereClauseToRelations(whereClause);
+        ColumnIdentifier.Raw fromRaw = new ColumnIdentifier.Literal(from.toString(), true);
+        ColumnIdentifier.Raw toRaw = new ColumnIdentifier.Literal(to.toString(), true);
+        List<Relation> newRelations = relations.stream()
+                .map(r -> r.renameIdentifier(fromRaw, toRaw))
+                .collect(Collectors.toList());
+
+        this.whereClause = View.relationsToWhereClause(newRelations);
+        String rawSelect = View.buildSelectStatement(baseTableName, metadata.allColumns(), whereClause);
+        this.select = (SelectStatement.RawStatement) QueryProcessor.parseStatement(rawSelect);
+    }
+
+    private static List<Relation> whereClauseToRelations(String whereClause)
+    {
+        ErrorCollector errorCollector = new ErrorCollector(whereClause);
+        CharStream stream = new ANTLRStringStream(whereClause);
+        CqlLexer lexer = new CqlLexer(stream);
+        lexer.addErrorListener(errorCollector);
+
+        TokenStream tokenStream = new CommonTokenStream(lexer);
+        CqlParser parser = new CqlParser(tokenStream);
+        parser.addErrorListener(errorCollector);
+
+        try
+        {
+            List<Relation> relations = parser.whereClause().build().relations;
+
+            // The errorCollector has queued up any errors that the lexer and parser may have encountered
+            // along the way, if necessary, we turn the last error into exceptions here.
+            errorCollector.throwFirstSyntaxError();
+
+            return relations;
+        }
+        catch (RecognitionException | SyntaxException exc)
+        {
+            throw new RuntimeException("Unexpected error parsing materialized view's where clause while handling column rename: ", exc);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/AbstractMarker.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/AbstractMarker.java b/src/java/org/apache/cassandra/cql3/AbstractMarker.java
index d11b8e2..d2bc022 100644
--- a/src/java/org/apache/cassandra/cql3/AbstractMarker.java
+++ b/src/java/org/apache/cassandra/cql3/AbstractMarker.java
@@ -56,7 +56,7 @@ public abstract class AbstractMarker extends Term.NonTerminal
     /**
      * A parsed, but non prepared, bind marker.
      */
-    public static class Raw implements Term.Raw
+    public static class Raw extends Term.Raw
     {
         protected final int bindIndex;
 
@@ -85,7 +85,34 @@ public abstract class AbstractMarker extends Term.NonTerminal
         }
 
         @Override
-        public String toString()
+        public String getText()
+        {
+            return "?";
+        }
+    }
+
+    /** A MultiColumnRaw version of AbstractMarker.Raw */
+    public static abstract class MultiColumnRaw extends Term.MultiColumnRaw
+    {
+        protected final int bindIndex;
+
+        public MultiColumnRaw(int bindIndex)
+        {
+            this.bindIndex = bindIndex;
+        }
+
+        public NonTerminal prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
+        {
+            throw new AssertionError("MultiColumnRaw..prepare() requires a list of receivers");
+        }
+
+        public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver)
+        {
+            return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE;
+        }
+
+        @Override
+        public String getText()
         {
             return "?";
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
index 4880c60..eb16f93 100644
--- a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
+++ b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.Locale;
 import java.nio.ByteBuffer;
 import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Pattern;
 
 import com.google.common.collect.MapMaker;
 
@@ -55,6 +56,8 @@ public class ColumnIdentifier extends org.apache.cassandra.cql3.selection.Select
     public final long prefixComparison;
     private final boolean interned;
 
+    private static final Pattern UNQUOTED_IDENTIFIER = Pattern.compile("[a-z][a-z0-9_]*");
+
     private static final long EMPTY_SIZE = ObjectSizes.measure(new ColumnIdentifier(ByteBufferUtil.EMPTY_BYTE_BUFFER, "", false));
 
     private static final ConcurrentMap<ByteBuffer, ColumnIdentifier> internedInstances = new MapMaker().weakValues().makeMap();
@@ -150,6 +153,15 @@ public class ColumnIdentifier extends org.apache.cassandra.cql3.selection.Select
         return text;
     }
 
+    /**
+     * Returns a string representation of the identifier that is safe to use directly in CQL queries.
+     * In necessary, the string will be double-quoted, and any quotes inside the string will be escaped.
+     */
+    public String toCQLString()
+    {
+        return maybeQuote(text);
+    }
+
     public long unsharedHeapSize()
     {
         return EMPTY_SIZE
@@ -198,6 +210,12 @@ public class ColumnIdentifier extends org.apache.cassandra.cql3.selection.Select
     {
 
         public ColumnIdentifier prepare(CFMetaData cfm);
+
+        /**
+         * Returns a string representation of the identifier that is safe to use directly in CQL queries.
+         * In necessary, the string will be double-quoted, and any quotes inside the string will be escaped.
+         */
+        public String toCQLString();
     }
 
     public static class Literal implements Raw
@@ -257,6 +275,11 @@ public class ColumnIdentifier extends org.apache.cassandra.cql3.selection.Select
         {
             return text;
         }
+
+        public String toCQLString()
+        {
+            return maybeQuote(text);
+        }
     }
 
     public static class ColumnIdentifierValue implements Raw
@@ -298,5 +321,17 @@ public class ColumnIdentifier extends org.apache.cassandra.cql3.selection.Select
         {
             return identifier.toString();
         }
+
+        public String toCQLString()
+        {
+            return maybeQuote(identifier.text);
+        }
+    }
+
+    private static String maybeQuote(String text)
+    {
+        if (UNQUOTED_IDENTIFIER.matcher(text).matches())
+            return text;
+        return "\"" + text.replace("\"", "\"\"") + "\"";
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/Constants.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Constants.java b/src/java/org/apache/cassandra/cql3/Constants.java
index f10484d..425dd85 100644
--- a/src/java/org/apache/cassandra/cql3/Constants.java
+++ b/src/java/org/apache/cassandra/cql3/Constants.java
@@ -46,7 +46,7 @@ public abstract class Constants
 
     public static final Value UNSET_VALUE = new Value(ByteBufferUtil.UNSET_BYTE_BUFFER);
 
-    public static final Term.Raw NULL_LITERAL = new Term.Raw()
+    private static class NullLiteral extends Term.Raw
     {
         public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
         {
@@ -63,12 +63,13 @@ public abstract class Constants
                  : AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE;
         }
 
-        @Override
-        public String toString()
+        public String getText()
         {
-            return "null";
+            return "NULL";
         }
-    };
+    }
+
+    public static final NullLiteral NULL_LITERAL = new NullLiteral();
 
     public static final Term.Terminal NULL_VALUE = new Value(null)
     {
@@ -86,7 +87,7 @@ public abstract class Constants
         }
     };
 
-    public static class Literal implements Term.Raw
+    public static class Literal extends Term.Raw
     {
         private final Type type;
         private final String text;
@@ -155,11 +156,6 @@ public abstract class Constants
             }
         }
 
-        public String getRawText()
-        {
-            return text;
-        }
-
         public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver)
         {
             CQL3Type receiverType = receiver.type.asCQL3Type();
@@ -238,8 +234,12 @@ public abstract class Constants
             return AssignmentTestable.TestResult.NOT_ASSIGNABLE;
         }
 
-        @Override
-        public String toString()
+        public String getRawText()
+        {
+            return text;
+        }
+
+        public String getText()
         {
             return type == Type.STRING ? String.format("'%s'", text) : text;
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g
index cd52c1c..932ecd6 100644
--- a/src/java/org/apache/cassandra/cql3/Cql.g
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -762,19 +762,18 @@ createMaterializedViewStatement returns [CreateViewStatement expr]
     }
     : K_CREATE K_MATERIALIZED K_VIEW (K_IF K_NOT K_EXISTS { ifNotExists = true; })? cf=columnFamilyName K_AS
         K_SELECT sclause=selectClause K_FROM basecf=columnFamilyName
-        (K_WHERE wclause=mvWhereClause)?
+        (K_WHERE wclause=whereClause)?
         K_PRIMARY K_KEY (
         '(' '(' k1=cident { partitionKeys.add(k1); } ( ',' kn=cident { partitionKeys.add(kn); } )* ')' ( ',' c1=cident { compositeKeys.add(c1); } )* ')'
     |   '(' k1=cident { partitionKeys.add(k1); } ( ',' cn=cident { compositeKeys.add(cn); } )* ')'
         )
-        { $expr = new CreateViewStatement(cf, basecf, sclause, wclause, partitionKeys, compositeKeys, ifNotExists); }
+        {
+             WhereClause where = wclause == null ? WhereClause.empty() : wclause.build();
+             $expr = new CreateViewStatement(cf, basecf, sclause, where, partitionKeys, compositeKeys, ifNotExists);
+        }
         ( K_WITH cfamProperty[expr.properties] ( K_AND cfamProperty[expr.properties] )*)?
     ;
 
-mvWhereClause returns [List<ColumnIdentifier.Raw> expr]
-    : t1=cident { $expr = new ArrayList<ColumnIdentifier.Raw>(); $expr.add(t1); } K_IS K_NOT K_NULL (K_AND tN=cident { $expr.add(tN); } K_IS K_NOT K_NULL)*
-    ;
-
 /**
  * CREATE TRIGGER triggerName ON columnFamily USING 'triggerClass';
  */
@@ -1423,6 +1422,7 @@ relationType returns [Operator op]
 
 relation[WhereClause.Builder clauses]
     : name=cident type=relationType t=term { $clauses.add(new SingleColumnRelation(name, type, t)); }
+    | name=cident K_IS K_NOT K_NULL { $clauses.add(new SingleColumnRelation(name, Operator.IS_NOT, Constants.NULL_LITERAL)); }
     | K_TOKEN l=tupleOfIdentifiers type=relationType t=term
         { $clauses.add(new TokenRelation(l, type, t)); }
     | name=cident K_IN marker=inMarker

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/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 35c69ed..df2d9ab 100644
--- a/src/java/org/apache/cassandra/cql3/Json.java
+++ b/src/java/org/apache/cassandra/cql3/Json.java
@@ -143,9 +143,9 @@ public class Json
             this.columns = columns;
         }
 
-        public DelayedColumnValue getRawTermForColumn(ColumnDefinition def)
+        public RawDelayedColumnValue getRawTermForColumn(ColumnDefinition def)
         {
-            return new DelayedColumnValue(this, def);
+            return new RawDelayedColumnValue(this, def);
         }
 
         public void bind(QueryOptions options) throws InvalidRequestException
@@ -173,7 +173,7 @@ public class Json
      * Note that this is intrinsically an already prepared term, but this still implements Term.Raw so that we can
      * easily use it to create raw operations.
      */
-    private static class ColumnValue implements Term.Raw
+    private static class ColumnValue extends Term.Raw
     {
         private final Term term;
 
@@ -193,19 +193,22 @@ public class Json
         {
             return TestResult.NOT_ASSIGNABLE;
         }
+
+        public String getText()
+        {
+            return term.toString();
+        }
     }
 
     /**
-     * A NonTerminal for a single column.
-     *
-     * As with {@code ColumnValue}, this is intrinsically a prepared term but implements Terms.Raw for convenience.
+     * A Raw term for a single column. Like ColumnValue, this is intrinsically already prepared.
      */
-    private static class DelayedColumnValue extends Term.NonTerminal implements Term.Raw
+    private static class RawDelayedColumnValue extends Term.Raw
     {
         private final PreparedMarker marker;
         private final ColumnDefinition column;
 
-        public DelayedColumnValue(PreparedMarker prepared, ColumnDefinition column)
+        public RawDelayedColumnValue(PreparedMarker prepared, ColumnDefinition column)
         {
             this.marker = prepared;
             this.column = column;
@@ -214,7 +217,7 @@ public class Json
         @Override
         public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
         {
-            return this;
+            return new DelayedColumnValue(marker, column);
         }
 
         @Override
@@ -223,6 +226,26 @@ public class Json
             return TestResult.WEAKLY_ASSIGNABLE;
         }
 
+        public String getText()
+        {
+            return marker.toString();
+        }
+    }
+
+    /**
+     * A NonTerminal for a single column. As with {@code ColumnValue}, this is intrinsically a prepared.
+     */
+    private static class DelayedColumnValue extends Term.NonTerminal
+    {
+        private final PreparedMarker marker;
+        private final ColumnDefinition column;
+
+        public DelayedColumnValue(PreparedMarker prepared, ColumnDefinition column)
+        {
+            this.marker = prepared;
+            this.column = column;
+        }
+
         @Override
         public void collectMarkerSpecification(VariableSpecifications boundNames)
         {
@@ -248,6 +271,7 @@ public class Json
         {
             return Collections.emptyList();
         }
+
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/Lists.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Lists.java b/src/java/org/apache/cassandra/cql3/Lists.java
index d9dac22..830561e 100644
--- a/src/java/org/apache/cassandra/cql3/Lists.java
+++ b/src/java/org/apache/cassandra/cql3/Lists.java
@@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.cql3.functions.Function;
@@ -55,7 +56,7 @@ public abstract class Lists
         return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((ListType)column.type).getElementsType());
     }
 
-    public static class Literal implements Term.Raw
+    public static class Literal extends Term.Raw
     {
         private final List<Term.Raw> elements;
 
@@ -113,10 +114,9 @@ public abstract class Lists
             return AssignmentTestable.TestResult.testAll(keyspace, valueSpec, elements);
         }
 
-        @Override
-        public String toString()
+        public String getText()
         {
-            return elements.toString();
+            return elements.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "[", "]"));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/Maps.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Maps.java b/src/java/org/apache/cassandra/cql3/Maps.java
index 0f0672f..d5df279 100644
--- a/src/java/org/apache/cassandra/cql3/Maps.java
+++ b/src/java/org/apache/cassandra/cql3/Maps.java
@@ -21,6 +21,7 @@ import static org.apache.cassandra.cql3.Constants.UNSET_VALUE;
 
 import java.nio.ByteBuffer;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import com.google.common.collect.Iterables;
 
@@ -54,7 +55,7 @@ public abstract class Maps
         return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((MapType)column.type).getValuesType());
     }
 
-    public static class Literal implements Term.Raw
+    public static class Literal extends Term.Raw
     {
         public final List<Pair<Term.Raw, Term.Raw>> entries;
 
@@ -129,18 +130,11 @@ public abstract class Maps
             return res;
         }
 
-        @Override
-        public String toString()
+        public String getText()
         {
-            StringBuilder sb = new StringBuilder();
-            sb.append("{");
-            for (int i = 0; i < entries.size(); i++)
-            {
-                if (i > 0) sb.append(", ");
-                sb.append(entries.get(i).left).append(":").append(entries.get(i).right);
-            }
-            sb.append("}");
-            return sb.toString();
+            return entries.stream()
+                    .map(entry -> String.format("%s: %s", entry.left.getText(), entry.right.getText()))
+                    .collect(Collectors.joining(", ", "{", "}"));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java b/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java
index 7735c57..143106d 100644
--- a/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java
+++ b/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java
@@ -19,6 +19,7 @@ package org.apache.cassandra.cql3;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.ColumnDefinition;
@@ -114,11 +115,17 @@ public class MultiColumnRelation extends Relation
      * For non-IN relations, returns the Tuples.Literal or Tuples.Raw marker for a single tuple.
      * @return a Tuples.Literal for non-IN relations or Tuples.Raw marker for a single tuple.
      */
-    private Term.MultiColumnRaw getValue()
+    public Term.MultiColumnRaw getValue()
     {
         return relationType == Operator.IN ? inMarker : valuesOrMarker;
     }
 
+    public List<? extends Term.Raw> getInValues()
+    {
+        assert relationType == Operator.IN;
+        return inValues;
+    }
+
     @Override
     public boolean isMultiColumn()
     {
@@ -164,7 +171,15 @@ public class MultiColumnRelation extends Relation
                                                  VariableSpecifications boundNames,
                                                  boolean isKey) throws InvalidRequestException
     {
-        throw invalidRequest("%s cannot be used for Multi-column relations", operator());
+        throw invalidRequest("%s cannot be used for multi-column relations", operator());
+    }
+
+    @Override
+    protected Restriction newIsNotRestriction(CFMetaData cfm,
+                                              VariableSpecifications boundNames) throws InvalidRequestException
+    {
+        // this is currently disallowed by the grammar
+        throw new AssertionError(String.format("%s cannot be used for multi-column relations", operator()));
     }
 
     @Override
@@ -198,6 +213,15 @@ public class MultiColumnRelation extends Relation
         return names;
     }
 
+    public Relation renameIdentifier(ColumnIdentifier.Raw from, ColumnIdentifier.Raw to)
+    {
+        if (!entities.contains(from))
+            return this;
+
+        List<ColumnIdentifier.Raw> newEntities = entities.stream().map(e -> e.equals(from) ? to : e).collect(Collectors.toList());
+        return new MultiColumnRelation(newEntities, operator(), valuesOrMarker, inValues, inMarker);
+    }
+
     @Override
     public String toString()
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/Operator.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Operator.java b/src/java/org/apache/cassandra/cql3/Operator.java
index 5ae9885..7b28a30 100644
--- a/src/java/org/apache/cassandra/cql3/Operator.java
+++ b/src/java/org/apache/cassandra/cql3/Operator.java
@@ -21,8 +21,14 @@ import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.ListType;
+import org.apache.cassandra.db.marshal.MapType;
+import org.apache.cassandra.db.marshal.SetType;
 
 public enum Operator
 {
@@ -87,6 +93,14 @@ public enum Operator
         {
             return "!=";
         }
+    },
+    IS_NOT(9)
+    {
+        @Override
+        public String toString()
+        {
+            return "IS NOT";
+        }
     };
 
     /**
@@ -114,6 +128,11 @@ public enum Operator
         output.writeInt(b);
     }
 
+    public int getValue()
+    {
+        return b;
+    }
+
     /**
      * Deserializes a <code>Operator</code> instance from the specified input.
      *
@@ -134,27 +153,48 @@ public enum Operator
     /**
      * Whether 2 values satisfy this operator (given the type they should be compared with).
      *
-     * @throws AssertionError for IN, CONTAINS and CONTAINS_KEY as this doesn't make sense for this function.
+     * @throws AssertionError for CONTAINS and CONTAINS_KEY as this doesn't support those operators yet
      */
     public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
     {
-        int comparison = type.compareForCQL(leftOperand, rightOperand);
         switch (this)
         {
             case EQ:
-                return comparison == 0;
+                return type.compareForCQL(leftOperand, rightOperand) == 0;
             case LT:
-                return comparison < 0;
+                return type.compareForCQL(leftOperand, rightOperand) < 0;
             case LTE:
-                return comparison <= 0;
+                return type.compareForCQL(leftOperand, rightOperand) <= 0;
             case GT:
-                return comparison > 0;
+                return type.compareForCQL(leftOperand, rightOperand) > 0;
             case GTE:
-                return comparison >= 0;
+                return type.compareForCQL(leftOperand, rightOperand) >= 0;
             case NEQ:
-                return comparison != 0;
+                return type.compareForCQL(leftOperand, rightOperand) != 0;
+            case IN:
+                List inValues = ((List) ListType.getInstance(type, false).getSerializer().deserialize(rightOperand));
+                return inValues.contains(type.getSerializer().deserialize(leftOperand));
+            case CONTAINS:
+                if (type instanceof ListType)
+                {
+                    List list = (List) type.getSerializer().deserialize(leftOperand);
+                    return list.contains(((ListType) type).getElementsType().getSerializer().deserialize(rightOperand));
+                }
+                else if (type instanceof SetType)
+                {
+                    Set set = (Set) type.getSerializer().deserialize(leftOperand);
+                    return set.contains(((SetType) type).getElementsType().getSerializer().deserialize(rightOperand));
+                }
+                else  // MapType
+                {
+                    Map map = (Map) type.getSerializer().deserialize(leftOperand);
+                    return map.containsValue(((MapType) type).getValuesType().getSerializer().deserialize(rightOperand));
+                }
+            case CONTAINS_KEY:
+                Map map = (Map) type.getSerializer().deserialize(leftOperand);
+                return map.containsKey(((MapType) type).getKeysType().getSerializer().deserialize(rightOperand));
             default:
-                // we shouldn't get IN, CONTAINS, or CONTAINS KEY here
+                // we shouldn't get CONTAINS, CONTAINS KEY, or IS NOT here
                 throw new AssertionError();
         }
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/Relation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Relation.java b/src/java/org/apache/cassandra/cql3/Relation.java
index 1337096..334464f 100644
--- a/src/java/org/apache/cassandra/cql3/Relation.java
+++ b/src/java/org/apache/cassandra/cql3/Relation.java
@@ -39,6 +39,16 @@ public abstract class Relation {
     }
 
     /**
+     * Returns the raw value for this relation, or null if this is an IN relation.
+     */
+    public abstract Term.Raw getValue();
+
+    /**
+     * Returns the list of raw IN values for this relation, or null if this is not an IN relation.
+     */
+    public abstract List<? extends Term.Raw> getInValues();
+
+    /**
      * Checks if this relation apply to multiple columns.
      *
      * @return <code>true</code> if this relation apply to multiple columns, <code>false</code> otherwise.
@@ -132,6 +142,7 @@ public abstract class Relation {
             case IN: return newINRestriction(cfm, boundNames);
             case CONTAINS: return newContainsRestriction(cfm, boundNames, false);
             case CONTAINS_KEY: return newContainsRestriction(cfm, boundNames, true);
+            case IS_NOT: return newIsNotRestriction(cfm, boundNames);
             default: throw invalidRequest("Unsupported \"!=\" relation: %s", this);
         }
     }
@@ -186,6 +197,9 @@ public abstract class Relation {
                                                           VariableSpecifications boundNames,
                                                           boolean isKey) throws InvalidRequestException;
 
+    protected abstract Restriction newIsNotRestriction(CFMetaData cfm,
+                                                       VariableSpecifications boundNames) throws InvalidRequestException;
+
     /**
      * Converts the specified <code>Raw</code> into a <code>Term</code>.
      * @param receivers the columns to which the values must be associated at
@@ -246,4 +260,13 @@ public abstract class Relation {
 
         return def;
     }
+
+    /**
+     * Renames an identifier in this Relation, if applicable.
+     * @param from the old identifier
+     * @param to the new identifier
+     * @return this object, if the old identifier is not in the set of entities that this relation covers; otherwise
+     *         a new Relation with "from" replaced by "to" is returned.
+     */
+    public abstract Relation renameIdentifier(ColumnIdentifier.Raw from, ColumnIdentifier.Raw to);
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/Sets.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Sets.java b/src/java/org/apache/cassandra/cql3/Sets.java
index 7ff3815..010abaa 100644
--- a/src/java/org/apache/cassandra/cql3/Sets.java
+++ b/src/java/org/apache/cassandra/cql3/Sets.java
@@ -21,6 +21,7 @@ import static org.apache.cassandra.cql3.Constants.UNSET_VALUE;
 
 import java.nio.ByteBuffer;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import com.google.common.base.Joiner;
 
@@ -48,7 +49,7 @@ public abstract class Sets
         return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((SetType)column.type).getElementsType());
     }
 
-    public static class Literal implements Term.Raw
+    public static class Literal extends Term.Raw
     {
         private final List<Term.Raw> elements;
 
@@ -124,10 +125,9 @@ public abstract class Sets
             return AssignmentTestable.TestResult.testAll(keyspace, valueSpec, elements);
         }
 
-        @Override
-        public String toString()
+        public String getText()
         {
-            return "{" + Joiner.on(", ").join(elements) + "}";
+            return elements.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "{", "}"));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
index 84e6274..e2c0b79 100644
--- a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
+++ b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
@@ -54,6 +54,9 @@ public final class SingleColumnRelation extends Relation
         this.relationType = type;
         this.value = value;
         this.inValues = inValues;
+
+        if (type == Operator.IS_NOT)
+            assert value == Constants.NULL_LITERAL;
     }
 
     /**
@@ -81,6 +84,16 @@ public final class SingleColumnRelation extends Relation
         this(entity, null, type, value);
     }
 
+    public Term.Raw getValue()
+    {
+        return value;
+    }
+
+    public List<? extends Term.Raw> getInValues()
+    {
+        return inValues;
+    }
+
     public static SingleColumnRelation createInRelation(ColumnIdentifier.Raw entity, List<Term.Raw> inValues)
     {
         return new SingleColumnRelation(entity, null, Operator.IN, null, inValues);
@@ -120,6 +133,13 @@ public final class SingleColumnRelation extends Relation
         }
     }
 
+    public Relation renameIdentifier(ColumnIdentifier.Raw from, ColumnIdentifier.Raw to)
+    {
+        return entity.equals(from)
+               ? new SingleColumnRelation(to, mapKey, operator(), value, inValues)
+               : this;
+    }
+
     @Override
     public String toString()
     {
@@ -185,6 +205,16 @@ public final class SingleColumnRelation extends Relation
         return new SingleColumnRestriction.ContainsRestriction(columnDef, term, isKey);
     }
 
+    @Override
+    protected Restriction newIsNotRestriction(CFMetaData cfm,
+                                              VariableSpecifications boundNames) throws InvalidRequestException
+    {
+        ColumnDefinition columnDef = toColumnDefinition(cfm, entity);
+        // currently enforced by the grammar
+        assert value == Constants.NULL_LITERAL : "Expected null literal for IS NOT relation: " + this.toString();
+        return new SingleColumnRestriction.IsNotNullRestriction(columnDef);
+    }
+
     /**
      * Returns the receivers for this relation.
      * @param columnDef the column definition

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/Term.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Term.java b/src/java/org/apache/cassandra/cql3/Term.java
index 6fa0c76..1f6bc62 100644
--- a/src/java/org/apache/cassandra/cql3/Term.java
+++ b/src/java/org/apache/cassandra/cql3/Term.java
@@ -81,7 +81,7 @@ public interface Term
      *   - a function call
      *   - a marker
      */
-    public interface Raw extends AssignmentTestable
+    public abstract class Raw implements AssignmentTestable
     {
         /**
          * This method validates this RawTerm is valid for provided column
@@ -93,12 +93,23 @@ public interface Term
          * case this RawTerm describe a list index or a map key, etc...
          * @return the prepared term.
          */
-        public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException;
+        public abstract Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException;
+
+        /**
+         * @return a String representation of the raw term that can be used when reconstructing a CQL query string.
+         */
+        public abstract String getText();
+
+        @Override
+        public String toString()
+        {
+            return getText();
+        }
     }
 
-    public interface MultiColumnRaw extends Raw
+    public abstract class MultiColumnRaw extends Term.Raw
     {
-        public Term prepare(String keyspace, List<? extends ColumnSpecification> receiver) throws InvalidRequestException;
+        public abstract Term prepare(String keyspace, List<? extends ColumnSpecification> receiver) throws InvalidRequestException;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/TokenRelation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/TokenRelation.java b/src/java/org/apache/cassandra/cql3/TokenRelation.java
index 6b487ef..2c13b19 100644
--- a/src/java/org/apache/cassandra/cql3/TokenRelation.java
+++ b/src/java/org/apache/cassandra/cql3/TokenRelation.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.cql3;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import com.google.common.base.Joiner;
 
@@ -63,6 +64,16 @@ public final class TokenRelation extends Relation
         return true;
     }
 
+    public Term.Raw getValue()
+    {
+        return value;
+    }
+
+    public List<? extends Term.Raw> getInValues()
+    {
+        return null;
+    }
+
     @Override
     protected Restriction newEQRestriction(CFMetaData cfm, VariableSpecifications boundNames) throws InvalidRequestException
     {
@@ -95,6 +106,12 @@ public final class TokenRelation extends Relation
     }
 
     @Override
+    protected Restriction newIsNotRestriction(CFMetaData cfm, VariableSpecifications boundNames) throws InvalidRequestException
+    {
+        throw invalidRequest("%s cannot be used with the token function", operator());
+    }
+
+    @Override
     protected Term toTerm(List<? extends ColumnSpecification> receivers,
                           Raw raw,
                           String keyspace,
@@ -105,6 +122,15 @@ public final class TokenRelation extends Relation
         return term;
     }
 
+    public Relation renameIdentifier(ColumnIdentifier.Raw from, ColumnIdentifier.Raw to)
+    {
+        if (!entities.contains(from))
+            return this;
+
+        List<ColumnIdentifier.Raw> newEntities = entities.stream().map(e -> e.equals(from) ? to : e).collect(Collectors.toList());
+        return new TokenRelation(newEntities, operator(), value);
+    }
+
     @Override
     public String toString()
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/Tuples.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Tuples.java b/src/java/org/apache/cassandra/cql3/Tuples.java
index 933088f..6c7df47 100644
--- a/src/java/org/apache/cassandra/cql3/Tuples.java
+++ b/src/java/org/apache/cassandra/cql3/Tuples.java
@@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -53,7 +54,7 @@ public class Tuples
      * A raw, literal tuple.  When prepared, this will become a Tuples.Value or Tuples.DelayedValue, depending
      * on whether the tuple holds NonTerminals.
      */
-    public static class Literal implements Term.MultiColumnRaw
+    public static class Literal extends Term.MultiColumnRaw
     {
         private final List<Term.Raw> elements;
 
@@ -133,10 +134,9 @@ public class Tuples
             }
         }
 
-        @Override
-        public String toString()
+        public String getText()
         {
-            return tupleToString(elements);
+            return elements.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "(", ")"));
         }
     }
 
@@ -287,7 +287,7 @@ public class Tuples
      * For example, "SELECT ... WHERE (col1, col2) > ?".
      * }
      */
-    public static class Raw extends AbstractMarker.Raw implements Term.MultiColumnRaw
+    public static class Raw extends AbstractMarker.MultiColumnRaw
     {
         public Raw(int bindIndex)
         {
@@ -317,18 +317,12 @@ public class Tuples
         {
             return new Tuples.Marker(bindIndex, makeReceiver(receivers));
         }
-
-        @Override
-        public AbstractMarker prepare(String keyspace, ColumnSpecification receiver)
-        {
-            throw new AssertionError("Tuples.Raw.prepare() requires a list of receivers");
-        }
     }
 
     /**
      * A raw marker for an IN list of tuples, like "SELECT ... WHERE (a, b, c) IN ?"
      */
-    public static class INRaw extends AbstractMarker.Raw implements MultiColumnRaw
+    public static class INRaw extends AbstractMarker.MultiColumnRaw
     {
         public INRaw(int bindIndex)
         {
@@ -362,12 +356,6 @@ public class Tuples
         {
             return new InMarker(bindIndex, makeInReceiver(receivers));
         }
-
-        @Override
-        public AbstractMarker prepare(String keyspace, ColumnSpecification receiver)
-        {
-            throw new AssertionError("Tuples.INRaw.prepare() requires a list of receivers");
-        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/TypeCast.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/TypeCast.java b/src/java/org/apache/cassandra/cql3/TypeCast.java
index 561a158..890b34f 100644
--- a/src/java/org/apache/cassandra/cql3/TypeCast.java
+++ b/src/java/org/apache/cassandra/cql3/TypeCast.java
@@ -20,7 +20,7 @@ package org.apache.cassandra.cql3;
 import org.apache.cassandra.db.marshal.AbstractType;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 
-public class TypeCast implements Term.Raw
+public class TypeCast extends Term.Raw
 {
     private final CQL3Type.Raw type;
     private final Term.Raw term;
@@ -58,8 +58,7 @@ public class TypeCast implements Term.Raw
             return AssignmentTestable.TestResult.NOT_ASSIGNABLE;
     }
 
-    @Override
-    public String toString()
+    public String getText()
     {
         return "(" + type + ")" + term;
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/UserTypes.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java b/src/java/org/apache/cassandra/cql3/UserTypes.java
index 22c7987..0beff06 100644
--- a/src/java/org/apache/cassandra/cql3/UserTypes.java
+++ b/src/java/org/apache/cassandra/cql3/UserTypes.java
@@ -42,7 +42,7 @@ public abstract class UserTypes
                                        ut.fieldType(field));
     }
 
-    public static class Literal implements Term.Raw
+    public static class Literal extends Term.Raw
     {
         public final Map<ColumnIdentifier, Term.Raw> entries;
 
@@ -118,8 +118,7 @@ public abstract class UserTypes
             }
         }
 
-        @Override
-        public String toString()
+        public String getText()
         {
             StringBuilder sb = new StringBuilder();
             sb.append("{");
@@ -127,7 +126,7 @@ public abstract class UserTypes
             while (iter.hasNext())
             {
                 Map.Entry<ColumnIdentifier, Term.Raw> entry = iter.next();
-                sb.append(entry.getKey()).append(":").append(entry.getValue());
+                sb.append(entry.getKey()).append(": ").append(entry.getValue().getText());
                 if (iter.hasNext())
                     sb.append(", ");
             }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java
index b25d079..1766a79 100644
--- a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java
+++ b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.cql3.functions;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import com.google.common.collect.Iterables;
 
@@ -110,7 +111,7 @@ public class FunctionCall extends Term.NonTerminal
         throw new AssertionError();
     }
 
-    public static class Raw implements Term.Raw
+    public static class Raw extends Term.Raw
     {
         private FunctionName name;
         private final List<Term.Raw> terms;
@@ -181,18 +182,9 @@ public class FunctionCall extends Term.NonTerminal
             }
         }
 
-        @Override
-        public String toString()
+        public String getText()
         {
-            StringBuilder sb = new StringBuilder();
-            sb.append(name).append("(");
-            for (int i = 0; i < terms.size(); i++)
-            {
-                if (i > 0)
-                    sb.append(", ");
-                sb.append(terms.get(i));
-            }
-            return sb.append(")").toString();
+            return name + terms.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "(", ")"));
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
index 0f56fd9..023c2ac 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
@@ -17,17 +17,9 @@
  */
 package org.apache.cassandra.cql3.restrictions;
 
-import java.nio.ByteBuffer;
-
-import org.apache.cassandra.cql3.ColumnSpecification;
 import org.apache.cassandra.cql3.QueryOptions;
 import org.apache.cassandra.cql3.statements.Bound;
 import org.apache.cassandra.db.MultiCBuilder;
-import org.apache.cassandra.exceptions.InvalidRequestException;
-
-import static org.apache.cassandra.cql3.statements.RequestValidations.checkBindValueSet;
-import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
-import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
 
 /**
  * Base class for <code>Restriction</code>s
@@ -71,6 +63,12 @@ abstract class AbstractRestriction  implements Restriction
     }
 
     @Override
+    public boolean isNotNull()
+    {
+        return false;
+    }
+
+    @Override
     public boolean hasBound(Bound b)
     {
         return true;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
index f82cb11..18e7105 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
@@ -167,6 +167,12 @@ abstract class ForwardingPrimaryKeyRestrictions implements PrimaryKeyRestriction
     }
 
     @Override
+    public boolean isNotNull()
+    {
+        return getDelegate().isNotNull();
+    }
+
+    @Override
     public boolean isMultiColumn()
     {
         return getDelegate().isMultiColumn();

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
index b60930e..069a01b 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
@@ -459,4 +459,59 @@ public abstract class MultiColumnRestriction extends AbstractRestriction
             return Collections.singletonList(terminal.get(options.getProtocolVersion()));
         }
     }
+
+    public static class NotNullRestriction extends MultiColumnRestriction
+    {
+        public NotNullRestriction(List<ColumnDefinition> columnDefs)
+        {
+            super(columnDefs);
+            assert columnDefs.size() == 1;
+        }
+
+        @Override
+        public Iterable<Function> getFunctions()
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public boolean isNotNull()
+        {
+            return true;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "IS NOT NULL";
+        }
+
+        @Override
+        public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
+        {
+            throw invalidRequest("%s cannot be restricted by a relation if it includes an IS NOT NULL clause",
+                                 getColumnsInCommons(otherRestriction));
+        }
+
+        @Override
+        protected boolean isSupportedBy(Index index)
+        {
+            for(ColumnDefinition column : columnDefs)
+                if (index.supportsExpression(column, Operator.IS_NOT))
+                    return true;
+            return false;
+        }
+
+        @Override
+        public MultiCBuilder appendTo(MultiCBuilder builder, QueryOptions options)
+        {
+            throw new UnsupportedOperationException("Cannot use IS NOT NULL restriction for slicing");
+        }
+
+        @Override
+        public final void addRowFilterTo(RowFilter filter, SecondaryIndexManager indexMananger, QueryOptions options) throws InvalidRequestException
+        {
+            throw new UnsupportedOperationException("Secondary indexes do not support IS NOT NULL restrictions");
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java b/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
index a21087e..a84ebc4 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
@@ -41,6 +41,7 @@ public interface Restriction
     public boolean isEQ();
     public boolean isIN();
     public boolean isContains();
+    public boolean isNotNull();
     public boolean isMultiColumn();
 
     /**

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
index 25146e5..d851253 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
@@ -587,4 +587,62 @@ public abstract class SingleColumnRestriction extends AbstractRestriction
             super(columnDef);
         }
     }
+
+    public static final class IsNotNullRestriction extends SingleColumnRestriction
+    {
+        public IsNotNullRestriction(ColumnDefinition columnDef)
+        {
+            super(columnDef);
+        }
+
+        @Override
+        public Iterable<Function> getFunctions()
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public boolean isNotNull()
+        {
+            return true;
+        }
+
+        @Override
+        MultiColumnRestriction toMultiColumnRestriction()
+        {
+            return new MultiColumnRestriction.NotNullRestriction(Collections.singletonList(columnDef));
+        }
+
+        @Override
+        public void addRowFilterTo(RowFilter filter,
+                                   SecondaryIndexManager indexManager,
+                                   QueryOptions options)
+        {
+            throw new UnsupportedOperationException("Secondary indexes do not support IS NOT NULL restrictions");
+        }
+
+        @Override
+        public MultiCBuilder appendTo(MultiCBuilder builder, QueryOptions options)
+        {
+            throw new UnsupportedOperationException("Cannot use IS NOT NULL restriction for slicing");
+        }
+
+        @Override
+        public String toString()
+        {
+            return "IS NOT NULL";
+        }
+
+        @Override
+        public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
+        {
+            throw invalidRequest("%s cannot be restricted by a relation if it includes an IS NOT NULL", columnDef.name);
+        }
+
+        @Override
+        protected boolean isSupportedBy(Index index)
+        {
+            return index.supportsExpression(columnDef, Operator.IS_NOT);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
index b1c7aff..1bd4218 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@ -77,6 +77,8 @@ public final class StatementRestrictions
      */
     private RestrictionSet nonPrimaryKeyRestrictions;
 
+    private Set<ColumnDefinition> notNullColumns;
+
     /**
      * The restrictions used to build the row filter
      */
@@ -111,6 +113,7 @@ public final class StatementRestrictions
         this.partitionKeyRestrictions = new PrimaryKeyRestrictionSet(cfm.getKeyValidatorAsClusteringComparator(), true);
         this.clusteringColumnsRestrictions = new PrimaryKeyRestrictionSet(cfm.comparator, false);
         this.nonPrimaryKeyRestrictions = new RestrictionSet();
+        this.notNullColumns = new HashSet<>();
     }
 
     public StatementRestrictions(StatementType type,
@@ -119,7 +122,8 @@ public final class StatementRestrictions
                                  VariableSpecifications boundNames,
                                  boolean selectsOnlyStaticColumns,
                                  boolean selectACollection,
-                                 boolean useFiltering)
+                                 boolean useFiltering,
+                                 boolean forView) throws InvalidRequestException
     {
         this(type, cfm);
 
@@ -133,7 +137,20 @@ public final class StatementRestrictions
          *     in CQL so far)
          */
         for (Relation relation : whereClause.relations)
-            addRestriction(relation.toRestriction(cfm, boundNames));
+        {
+            if (relation.operator() == Operator.IS_NOT)
+            {
+                if (!forView)
+                    throw new InvalidRequestException("Unsupported restriction: " + relation);
+
+                for (ColumnDefinition def : relation.toRestriction(cfm, boundNames).getColumnDefs())
+                    this.notNullColumns.add(def);
+            }
+            else
+            {
+                addRestriction(relation.toRestriction(cfm, boundNames));
+            }
+        }
 
         boolean hasQueriableClusteringColumnIndex = false;
         boolean hasQueriableIndex = false;
@@ -180,7 +197,7 @@ public final class StatementRestrictions
                 throw invalidRequest("Cannot restrict clustering columns when selecting only static columns");
         }
 
-        processClusteringColumnsRestrictions(hasQueriableIndex, selectsOnlyStaticColumns, selectACollection);
+        processClusteringColumnsRestrictions(hasQueriableIndex, selectsOnlyStaticColumns, selectACollection, forView);
 
         // Covers indexes on the first clustering column (among others).
         if (isKeyRange && hasQueriableClusteringColumnIndex)
@@ -244,19 +261,56 @@ public final class StatementRestrictions
     }
 
     /**
-     * Returns the non-PK column that are restricted.
+     * Returns the non-PK column that are restricted.  If includeNotNullRestrictions is true, columns that are restricted
+     * by an IS NOT NULL restriction will be included, otherwise they will not be included (unless another restriction
+     * applies to them).
      */
-    public Set<ColumnDefinition> nonPKRestrictedColumns()
+    public Set<ColumnDefinition> nonPKRestrictedColumns(boolean includeNotNullRestrictions)
     {
         Set<ColumnDefinition> columns = new HashSet<>();
         for (Restrictions r : indexRestrictions.getRestrictions())
+        {
             for (ColumnDefinition def : r.getColumnDefs())
                 if (!def.isPrimaryKeyColumn())
                     columns.add(def);
+        }
+
+        if (includeNotNullRestrictions)
+        {
+            for (ColumnDefinition def : notNullColumns)
+            {
+                if (!def.isPrimaryKeyColumn())
+                    columns.add(def);
+            }
+        }
+
         return columns;
     }
 
     /**
+     * @return the set of columns that have an IS NOT NULL restriction on them
+     */
+    public Set<ColumnDefinition> notNullColumns()
+    {
+        return notNullColumns;
+    }
+
+    /**
+     * @return true if column is restricted by some restriction, false otherwise
+     */
+    public boolean isRestricted(ColumnDefinition column)
+    {
+        if (notNullColumns.contains(column))
+            return true;
+        else if (column.isPartitionKey())
+            return partitionKeyRestrictions.getColumnDefs().contains(column);
+        else if (column.isClusteringColumn())
+            return clusteringColumnsRestrictions.getColumnDefs().contains(column);
+        else
+            return nonPrimaryKeyRestrictions.getColumnDefs().contains(column);
+    }
+
+    /**
      * Checks if the restrictions on the partition key is an IN restriction.
      *
      * @return <code>true</code> the restrictions on the partition key is an IN restriction, <code>false</code>
@@ -370,7 +424,8 @@ public final class StatementRestrictions
      */
     private void processClusteringColumnsRestrictions(boolean hasQueriableIndex,
                                                       boolean selectsOnlyStaticColumns,
-                                                      boolean selectACollection)
+                                                      boolean selectACollection,
+                                                      boolean forView) throws InvalidRequestException
     {
         checkFalse(!type.allowClusteringColumnSlices() && clusteringColumnsRestrictions.isSlice(),
                    "Slice restrictions are not supported on the clustering columns in %s statements", type);
@@ -401,7 +456,7 @@ public final class StatementRestrictions
 
                     if (!clusteringColumn.equals(restrictedColumn))
                     {
-                        checkTrue(hasQueriableIndex,
+                        checkTrue(hasQueriableIndex || forView,
                                   "PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted",
                                   restrictedColumn.name,
                                   clusteringColumn.name);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/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 0d2011b..c410f10 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
@@ -341,7 +341,7 @@ public class AlterTableStatement extends SchemaAlteringStatement
                         ViewDefinition viewCopy = view.copy();
                         ColumnIdentifier viewFrom = entry.getKey().prepare(viewCopy.metadata);
                         ColumnIdentifier viewTo = entry.getValue().prepare(viewCopy.metadata);
-                        viewCopy.metadata.renameColumn(viewFrom, viewTo);
+                        viewCopy.renameColumn(viewFrom, viewTo);
 
                         if (viewUpdates == null)
                             viewUpdates = new ArrayList<>();

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/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 1a020ce..586b09b 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java
@@ -18,10 +18,8 @@
 
 package org.apache.cassandra.cql3.statements;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import com.google.common.collect.Iterables;
 
@@ -30,8 +28,8 @@ import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.config.Schema;
 import org.apache.cassandra.config.ViewDefinition;
-import org.apache.cassandra.cql3.CFName;
-import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
 import org.apache.cassandra.cql3.selection.RawSelector;
 import org.apache.cassandra.cql3.selection.Selectable;
 import org.apache.cassandra.db.marshal.AbstractType;
@@ -52,7 +50,7 @@ public class CreateViewStatement extends SchemaAlteringStatement
 {
     private final CFName baseName;
     private final List<RawSelector> selectClause;
-    private final List<ColumnIdentifier.Raw> notNullWhereClause;
+    private final WhereClause whereClause;
     private final List<ColumnIdentifier.Raw> partitionKeys;
     private final List<ColumnIdentifier.Raw> clusteringKeys;
     public final CFProperties properties = new CFProperties();
@@ -61,7 +59,7 @@ public class CreateViewStatement extends SchemaAlteringStatement
     public CreateViewStatement(CFName viewName,
                                CFName baseName,
                                List<RawSelector> selectClause,
-                               List<ColumnIdentifier.Raw> notNullWhereClause,
+                               WhereClause whereClause,
                                List<ColumnIdentifier.Raw> partitionKeys,
                                List<ColumnIdentifier.Raw> clusteringKeys,
                                boolean ifNotExists)
@@ -69,7 +67,7 @@ public class CreateViewStatement extends SchemaAlteringStatement
         super(viewName);
         this.baseName = baseName;
         this.selectClause = selectClause;
-        this.notNullWhereClause = notNullWhereClause;
+        this.whereClause = whereClause;
         this.partitionKeys = partitionKeys;
         this.clusteringKeys = clusteringKeys;
         this.ifNotExists = ifNotExists;
@@ -194,34 +192,50 @@ public class CreateViewStatement extends SchemaAlteringStatement
                 throw new InvalidRequestException(String.format("Cannot use Static column '%s' in PRIMARY KEY of materialized view", identifier));
         }
 
+        // build the select statement
+        Map<ColumnIdentifier.Raw, Boolean> orderings = Collections.emptyMap();
+        SelectStatement.Parameters parameters = new SelectStatement.Parameters(orderings, false, true, false);
+        SelectStatement.RawStatement rawSelect = new SelectStatement.RawStatement(baseName, parameters, selectClause, whereClause, null);
+
+        ClientState state = ClientState.forInternalCalls();
+        state.setKeyspace(keyspace());
+
+        rawSelect.prepareKeyspace(state);
+        rawSelect.setBoundVariables(getBoundVariables());
+
+        ParsedStatement.Prepared prepared = rawSelect.prepare(true);
+        SelectStatement select = (SelectStatement) prepared.statement;
+        StatementRestrictions restrictions = select.getRestrictions();
+
+        if (!prepared.boundNames.isEmpty())
+            throw new InvalidRequestException("Cannot use query parameters in CREATE MATERIALIZED VIEW statements");
+
+        if (!restrictions.nonPKRestrictedColumns(false).isEmpty())
+        {
+            throw new InvalidRequestException(String.format(
+                    "Non-primary key columns cannot be restricted in the SELECT statement used for materialized view " +
+                    "creation (got restrictions on: %s)",
+                    restrictions.nonPKRestrictedColumns(false).stream().map(def -> def.name.toString()).collect(Collectors.joining(", "))));
+        }
+
+        String whereClauseText = View.relationsToWhereClause(whereClause.relations);
+
         Set<ColumnIdentifier> basePrimaryKeyCols = new HashSet<>();
         for (ColumnDefinition definition : Iterables.concat(cfm.partitionKeyColumns(), cfm.clusteringColumns()))
             basePrimaryKeyCols.add(definition.name);
 
         List<ColumnIdentifier> targetClusteringColumns = new ArrayList<>();
         List<ColumnIdentifier> targetPartitionKeys = new ArrayList<>();
-        Set<ColumnIdentifier> notNullColumns = new HashSet<>();
-        if (notNullWhereClause != null)
-        {
-            for (ColumnIdentifier.Raw raw : notNullWhereClause)
-            {
-                notNullColumns.add(raw.prepare(cfm));
-            }
-        }
 
         // This is only used as an intermediate state; this is to catch whether multiple non-PK columns are used
         boolean hasNonPKColumn = false;
         for (ColumnIdentifier.Raw raw : partitionKeys)
-        {
-            hasNonPKColumn = getColumnIdentifier(cfm, basePrimaryKeyCols, hasNonPKColumn, raw, targetPartitionKeys, notNullColumns);
-        }
+            hasNonPKColumn = getColumnIdentifier(cfm, basePrimaryKeyCols, hasNonPKColumn, raw, targetPartitionKeys, restrictions);
 
         for (ColumnIdentifier.Raw raw : clusteringKeys)
-        {
-            hasNonPKColumn = getColumnIdentifier(cfm, basePrimaryKeyCols, hasNonPKColumn, raw, targetClusteringColumns, notNullColumns);
-        }
+            hasNonPKColumn = getColumnIdentifier(cfm, basePrimaryKeyCols, hasNonPKColumn, raw, targetClusteringColumns, restrictions);
 
-        // We need to include all of the primary key colums from the base table in order to make sure that we do not
+        // We need to include all of the primary key columns from the base table in order to make sure that we do not
         // overwrite values in the view. We cannot support "collapsing" the base table into a smaller number of rows in
         // the view because if we need to generate a tombstone, we have no way of knowing which value is currently being
         // used in the view and whether or not to generate a tombstone. In order to not surprise our users, we require
@@ -269,7 +283,10 @@ public class CreateViewStatement extends SchemaAlteringStatement
         ViewDefinition definition = new ViewDefinition(keyspace(),
                                                        columnFamily(),
                                                        Schema.instance.getId(keyspace(), baseName.getColumnFamily()),
+                                                       baseName.getColumnFamily(),
                                                        included.isEmpty(),
+                                                       rawSelect,
+                                                       whereClauseText,
                                                        viewCfm);
 
         try
@@ -291,24 +308,21 @@ public class CreateViewStatement extends SchemaAlteringStatement
                                                boolean hasNonPKColumn,
                                                ColumnIdentifier.Raw raw,
                                                List<ColumnIdentifier> columns,
-                                               Set<ColumnIdentifier> allowedPKColumns)
+                                               StatementRestrictions restrictions)
     {
         ColumnIdentifier identifier = raw.prepare(cfm);
+        ColumnDefinition def = cfm.getColumnDefinition(identifier);
 
         boolean isPk = basePK.contains(identifier);
         if (!isPk && hasNonPKColumn)
-        {
             throw new InvalidRequestException(String.format("Cannot include more than one non-primary key column '%s' in materialized view partition key", identifier));
-        }
 
         // We don't need to include the "IS NOT NULL" filter on a non-composite partition key
         // because we will never allow a single partition key to be NULL
         boolean isSinglePartitionKey = cfm.getColumnDefinition(identifier).isPartitionKey()
                                        && cfm.partitionKeyColumns().size() == 1;
-        if (!allowedPKColumns.remove(identifier) && !isSinglePartitionKey)
-        {
+        if (!isSinglePartitionKey && !restrictions.isRestricted(def))
             throw new InvalidRequestException(String.format("Primary key column '%s' is required to be filtered by 'IS NOT NULL'", identifier));
-        }
 
         columns.add(identifier);
         return !isPk;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java b/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java
index 6210a86..8cdf2c8 100644
--- a/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java
+++ b/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java
@@ -60,18 +60,10 @@ public class IndexTarget
 
     public String asCqlString(CFMetaData cfm)
     {
-        if (! cfm.getColumnDefinition(column).type.isCollection())
-            return maybeEscapeQuotedName(column.toString());
+        if (!cfm.getColumnDefinition(column).type.isCollection())
+            return column.toCQLString();
 
-        return String.format("%s(%s)", type.toString(), maybeEscapeQuotedName(column.toString()));
-    }
-
-    // Quoted column names may themselves contain quotes, these need
-    // to be escaped with a preceding quote when written out as cql.
-    // Of course, the escaped name also needs to be wrapped in quotes.
-    private String maybeEscapeQuotedName(String name)
-    {
-        return quoteName ? '\"' + name.replace("\"", "\"\"") + '\"' : name;
+        return String.format("%s(%s)", type.toString(), column.toCQLString());
     }
 
     public static class Raw

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index 54a4f28..23a26d0 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@ -855,7 +855,7 @@ public abstract class ModificationStatement implements CQLStatement
                 throw new InvalidRequestException(CUSTOM_EXPRESSIONS_NOT_ALLOWED);
 
             boolean applyOnlyToStaticColumns = appliesOnlyToStaticColumns(operations, conditions);
-            return new StatementRestrictions(type, cfm, where, boundNames, applyOnlyToStaticColumns, false, false);
+            return new StatementRestrictions(type, cfm, where, boundNames, applyOnlyToStaticColumns, false, false, false);
         }
 
         /**

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java b/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java
index 539a957..4c3f8a9 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java
@@ -39,6 +39,11 @@ public abstract class ParsedStatement
         this.variables = new VariableSpecifications(boundNames);
     }
 
+    public void setBoundVariables(VariableSpecifications variables)
+    {
+        this.variables = variables;
+    }
+
     public abstract Prepared prepare() throws RequestValidationException;
 
     public static class Prepared

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 170bfdf..7848556 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -143,7 +143,7 @@ public class SelectStatement implements CQLStatement
             if (!def.isPrimaryKeyColumn())
                 builder.add(def);
         // as well as any restricted column (so we can actually apply the restriction)
-        builder.addAll(restrictions.nonPKRestrictedColumns());
+        builder.addAll(restrictions.nonPKRestrictedColumns(true));
         return builder.build();
     }
 
@@ -451,6 +451,17 @@ public class SelectStatement implements CQLStatement
         return new SinglePartitionReadCommand.Group(commands, limit);
     }
 
+    /**
+     * Returns a read command that can be used internally to filter individual rows for materialized views.
+     */
+    public SinglePartitionReadCommand internalReadForView(DecoratedKey key, int nowInSec)
+    {
+        QueryOptions options = QueryOptions.forInternalCalls(Collections.emptyList());
+        ClusteringIndexFilter filter = makeClusteringIndexFilter(options);
+        RowFilter rowFilter = getRowFilter(options);
+        return SinglePartitionReadCommand.create(cfm, nowInSec, queriedColumns, rowFilter, DataLimits.NONE, key, filter);
+    }
+
     private ReadQuery getRangeCommand(QueryOptions options, DataLimits limit, int nowInSec) throws RequestValidationException
     {
         ClusteringIndexFilter clusteringIndexFilter = makeClusteringIndexFilter(options);
@@ -738,6 +749,11 @@ public class SelectStatement implements CQLStatement
 
         public ParsedStatement.Prepared prepare() throws InvalidRequestException
         {
+            return prepare(false);
+        }
+
+        public ParsedStatement.Prepared prepare(boolean forView) throws InvalidRequestException
+        {
             CFMetaData cfm = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
             VariableSpecifications boundNames = getBoundVariables();
 
@@ -745,7 +761,7 @@ public class SelectStatement implements CQLStatement
                                   ? Selection.wildcard(cfm)
                                   : Selection.fromSelectors(cfm, selectClause);
 
-            StatementRestrictions restrictions = prepareRestrictions(cfm, boundNames, selection);
+            StatementRestrictions restrictions = prepareRestrictions(cfm, boundNames, selection, forView);
 
             if (parameters.isDistinct)
                 validateDistinctSelection(cfm, selection, restrictions);
@@ -755,6 +771,7 @@ public class SelectStatement implements CQLStatement
 
             if (!parameters.orderings.isEmpty())
             {
+                assert !forView;
                 verifyOrderingIsAllowed(restrictions);
                 orderingComparator = getOrderingComparator(cfm, selection, restrictions);
                 isReversed = isReversed(cfm);
@@ -787,7 +804,8 @@ public class SelectStatement implements CQLStatement
          */
         private StatementRestrictions prepareRestrictions(CFMetaData cfm,
                                                           VariableSpecifications boundNames,
-                                                          Selection selection) throws InvalidRequestException
+                                                          Selection selection,
+                                                          boolean forView) throws InvalidRequestException
         {
             try
             {
@@ -797,7 +815,8 @@ public class SelectStatement implements CQLStatement
                                                  boundNames,
                                                  selection.containsOnlyStaticColumns(),
                                                  selection.containsACollection(),
-                                                 parameters.allowFiltering);
+                                                 parameters.allowFiltering,
+                                                 forView);
             }
             catch (UnrecognizedEntityException e)
             {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
index f8435eb..ce9aaee 100644
--- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
@@ -186,6 +186,7 @@ public class UpdateStatement extends ModificationStatement
                                                                            boundNames,
                                                                            applyOnlyToStaticColumns,
                                                                            false,
+                                                                           false,
                                                                            false);
 
             return new UpdateStatement(StatementType.INSERT,
@@ -254,6 +255,7 @@ public class UpdateStatement extends ModificationStatement
                                                                            boundNames,
                                                                            applyOnlyToStaticColumns,
                                                                            false,
+                                                                           false,
                                                                            false);
 
             return new UpdateStatement(StatementType.INSERT,

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
index 4e96d81..f17f3e3 100644
--- a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
+++ b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
@@ -139,15 +139,22 @@ public class PartitionRangeReadCommand extends ReadCommand
         return DatabaseDescriptor.getRangeRpcTimeout();
     }
 
-    public boolean selects(DecoratedKey partitionKey, Clustering clustering)
+    public boolean selectsKey(DecoratedKey key)
     {
-        if (!dataRange().contains(partitionKey))
+        if (!dataRange().contains(key))
             return false;
 
+        return rowFilter().partitionKeyRestrictionsAreSatisfiedBy(key, metadata().getKeyValidator());
+    }
+
+    public boolean selectsClustering(DecoratedKey key, Clustering clustering)
+    {
         if (clustering == Clustering.STATIC_CLUSTERING)
             return !columnFilter().fetchedColumns().statics.isEmpty();
 
-        return dataRange().clusteringIndexFilter(partitionKey).selects(clustering);
+        if (!dataRange().clusteringIndexFilter(key).selects(clustering))
+            return false;
+        return rowFilter().clusteringKeyRestrictionsAreSatisfiedBy(clustering);
     }
 
     public PartitionIterator execute(ConsistencyLevel consistency, ClientState clientState) throws RequestExecutionException

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5a4253b6/src/java/org/apache/cassandra/db/ReadCommand.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/ReadCommand.java b/src/java/org/apache/cassandra/db/ReadCommand.java
index 3d08f17..1c1da42 100644
--- a/src/java/org/apache/cassandra/db/ReadCommand.java
+++ b/src/java/org/apache/cassandra/db/ReadCommand.java
@@ -276,18 +276,6 @@ public abstract class ReadCommand implements ReadQuery
      */
     public abstract ReadCommand copy();
 
-    /**
-     * Whether the provided row, identified by its primary key components, is selected by
-     * this read command.
-     *
-     * @param partitionKey the partition key for the row to test.
-     * @param clustering the clustering for the row to test.
-     *
-     * @return whether the row of partition key {@code partitionKey} and clustering
-     * {@code clustering} is selected by this command.
-     */
-    public abstract boolean selects(DecoratedKey partitionKey, Clustering clustering);
-
     protected abstract UnfilteredPartitionIterator queryStorage(ColumnFamilyStore cfs, ReadOrderGroup orderGroup);
 
     protected abstract int oldestUnrepairedTombstone();