This is an automated email from the ASF dual-hosted git repository.

jlewandowski pushed a commit to branch cep-15-accord
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cep-15-accord by this push:
     new ef87a5ae22 Improve transaction statement validation
ef87a5ae22 is described below

commit ef87a5ae224b12350a248e469bc3b42471490540
Author: Jacek Lewandowski <lewandowski.ja...@gmail.com>
AuthorDate: Thu Mar 16 18:43:20 2023 +0100

    Improve transaction statement validation
    
    patch by Jacek Lewandowski; reviewed by David Capwell and Caleb Rackliffe 
for CASSANDRA-18302
---
 CHANGES.txt                                        |   1 +
 src/antlr/Parser.g                                 |  38 +++++--
 .../org/apache/cassandra/cql3/StatementSource.java |  76 ++++++++++++++
 .../cassandra/cql3/statements/BatchStatement.java  |  50 +++++++---
 .../cassandra/cql3/statements/DeleteStatement.java |  28 ++++--
 .../cql3/statements/ModificationStatement.java     |  55 ++++++++---
 .../cassandra/cql3/statements/SelectStatement.java | 109 ++++++++++++++++-----
 .../cql3/statements/TransactionStatement.java      |  65 ++++++------
 .../cassandra/cql3/statements/UpdateStatement.java |  47 ++++++---
 src/java/org/apache/cassandra/db/view/View.java    |  22 +++--
 .../apache/cassandra/cql3/StatementSourceTest.java |  61 ++++++++++++
 .../cql3/statements/TransactionStatementTest.java  |  28 +++---
 12 files changed, 451 insertions(+), 129 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 2937ec238a..9ff6764a47 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 accord
+ * Improve transaction statement validation (CASSANDRA-18302)
  * Add support for prepared statements for accord transactions 
(CASSANDRA-18299)
  * Fix statement validation against partition range queries (CASSANDRA-18240)
  * Fix null value handling for static columns (CASSANDRA-18241)
diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g
index d69c7c7fd5..a076b204ff 100644
--- a/src/antlr/Parser.g
+++ b/src/antlr/Parser.g
@@ -34,6 +34,8 @@ options {
 
     protected List<RowDataReference.Raw> references;
 
+    private Token statementBeginMarker;
+
     public static final Set<String> reservedTypeNames = new HashSet<String>()
     {{
         add("byte");
@@ -230,6 +232,19 @@ options {
     {
         // Do nothing.
     }
+
+    public Token stmtBegins()
+    {
+        statementBeginMarker = input.LT(1);
+        return statementBeginMarker;
+    }
+
+    public StatementSource stmtSrc()
+    {
+        StatementSource stmtSrc = StatementSource.create(statementBeginMarker);
+        statementBeginMarker = null;
+        return stmtSrc;
+    }
 }
 
 /** STATEMENTS **/
@@ -302,6 +317,7 @@ selectStatement returns [SelectStatement.RawStatement expr]
         List<Selectable.Raw> groups = new ArrayList<>();
         boolean allowFiltering = false;
         boolean isJson = false;
+        stmtBegins();
     }
     : K_SELECT
         // json is a valid column name. By consequence, we need to resolve the 
ambiguity for "json - json"
@@ -321,7 +337,7 @@ selectStatement returns [SelectStatement.RawStatement expr]
                                                                              
isJson,
                                                                              
null);
           WhereClause where = wclause == null ? WhereClause.empty() : 
wclause.build();
-          $expr = new SelectStatement.RawStatement(cf, params, 
$sclause.selectors, where, limit, perPartitionLimit);
+          $expr = new SelectStatement.RawStatement(cf, params, 
$sclause.selectors, where, limit, perPartitionLimit, stmtSrc());
       }
     ;
     
@@ -334,11 +350,12 @@ letStatement returns [SelectStatement.RawStatement expr]
         Term.Raw limit = null;
     }
     : K_LET txnVar=IDENT '='
-      '(' K_SELECT assignments=letSelectors K_FROM cf=columnFamilyName K_WHERE 
wclause=whereClause ( K_LIMIT rows=intValue { limit = rows; } )? ')'
+      '(' { stmtBegins(); } K_SELECT assignments=letSelectors K_FROM 
cf=columnFamilyName K_WHERE wclause=whereClause ( K_LIMIT rows=intValue { limit 
= rows; } )? ')'
       {
           SelectStatement.Parameters params = new 
SelectStatement.Parameters(Collections.emptyMap(), Collections.emptyList(), 
false, false, false, $txnVar.text);
           WhereClause where = wclause == null ? WhereClause.empty() : 
wclause.build();
-          $expr = new SelectStatement.RawStatement(cf, params, assignments, 
where, limit, null);
+
+          $expr = new SelectStatement.RawStatement(cf, params, assignments, 
where, limit, null, stmtSrc());
       }
     ;
     
@@ -535,6 +552,9 @@ groupByClause[List<Selectable.Raw> groups]
  *
  */
 insertStatement returns [ModificationStatement.Parsed expr]
+    @init {
+        stmtBegins();
+    }
     : K_INSERT K_INTO cf=columnFamilyName
         ( st1=normalInsertStatement[cf] { $expr = st1; }
         | K_JSON st2=jsonInsertStatement[cf] { $expr = st2; })
@@ -553,7 +573,7 @@ normalInsertStatement [QualifiedName qn] returns 
[UpdateStatement.ParsedInsert e
       ( K_IF K_NOT K_EXISTS { ifNotExists = true; } )?
       ( usingClause[attrs] )?
       {
-          $expr = new UpdateStatement.ParsedInsert(qn, attrs, columnNames, 
values, ifNotExists);
+          $expr = new UpdateStatement.ParsedInsert(qn, attrs, columnNames, 
values, ifNotExists, stmtSrc());
       }
     ;
 
@@ -573,7 +593,7 @@ jsonInsertStatement [QualifiedName qn] returns 
[UpdateStatement.ParsedInsertJson
       ( K_IF K_NOT K_EXISTS { ifNotExists = true; } )?
       ( usingClause[attrs] )?
       {
-          $expr = new UpdateStatement.ParsedInsertJson(qn, attrs, val, 
defaultUnset, ifNotExists);
+          $expr = new UpdateStatement.ParsedInsertJson(qn, attrs, val, 
defaultUnset, ifNotExists, stmtSrc());
       }
     ;
 
@@ -604,6 +624,7 @@ updateStatement returns [UpdateStatement.ParsedUpdate expr]
         Attributes.Raw attrs = new Attributes.Raw();
         UpdateStatement.OperationCollector operations = new 
UpdateStatement.OperationCollector();
         boolean ifExists = false;
+        stmtBegins();
     }
     : K_UPDATE cf=columnFamilyName
       ( usingClause[attrs] )?
@@ -617,7 +638,8 @@ updateStatement returns [UpdateStatement.ParsedUpdate expr]
                                                    wclause.build(),
                                                    conditions == null ? 
Collections.<Pair<ColumnIdentifier, ColumnCondition.Raw>>emptyList() : 
conditions,
                                                    ifExists,
-                                                   isParsingTxn);
+                                                   isParsingTxn,
+                                                   stmtSrc());
      }
     ;
 
@@ -639,6 +661,7 @@ deleteStatement returns [DeleteStatement.Parsed expr]
         Attributes.Raw attrs = new Attributes.Raw();
         List<Operation.RawDeletion> columnDeletions = Collections.emptyList();
         boolean ifExists = false;
+        stmtBegins();
     }
     : K_DELETE ( dels=deleteSelection { columnDeletions = dels; } )?
       K_FROM cf=columnFamilyName
@@ -651,7 +674,8 @@ deleteStatement returns [DeleteStatement.Parsed expr]
                                              columnDeletions,
                                              wclause.build(),
                                              conditions == null ? 
Collections.<Pair<ColumnIdentifier, ColumnCondition.Raw>>emptyList() : 
conditions,
-                                             ifExists);
+                                             ifExists,
+                                             stmtSrc());
       }
     ;
 
diff --git a/src/java/org/apache/cassandra/cql3/StatementSource.java 
b/src/java/org/apache/cassandra/cql3/StatementSource.java
new file mode 100644
index 0000000000..2f07ec4f53
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/StatementSource.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.cql3;
+
+import java.util.Objects;
+
+import org.antlr.runtime.Token;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+public class StatementSource
+{
+    public static final StatementSource INTERNAL = new StatementSource(0, 0);
+
+    public final int line;
+    public final int charPositionInLine;
+
+    public StatementSource(int line, int charPositionInLine)
+    {
+        this.line = line;
+        this.charPositionInLine = charPositionInLine;
+    }
+
+    @Override
+    public String toString()
+    {
+        if (this == INTERNAL)
+        {
+            return "<<<internal statement>>>";
+        }
+        else
+        {
+            if (!isEmpty())
+                return String.format("at [%d:%d]", line + 1, 
charPositionInLine + 1);
+            else
+                return "";
+        }
+    }
+
+    public boolean isEmpty()
+    {
+        return line > Character.MAX_VALUE || line == Character.MAX_VALUE && 
charPositionInLine > Character.MAX_VALUE;
+    }
+
+    // note - this can also reproduce the original statement raw text by 
getting TokenStream and calling toString(startToken, endToken)
+    public static StatementSource create(Token startToken)
+    {
+        Objects.requireNonNull(startToken);
+
+        if (startToken.getType() == Token.EOF)
+            return new StatementSource(Character.MAX_VALUE + 1, 0);
+
+        int startLine = min(max(startToken.getLine(), 1) - 1, 
Character.MAX_VALUE);
+        int startChar = min(max(startToken.getCharPositionInLine(), 0), 
Character.MAX_VALUE);
+
+        return new StatementSource(startLine, startChar);
+    }
+
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
index eb77c33127..df173a66cc 100644
--- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
@@ -18,7 +18,16 @@
 package org.apache.cassandra.cql3.statements;
 
 import java.nio.ByteBuffer;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -31,18 +40,36 @@ import org.slf4j.helpers.MessageFormatter;
 
 import org.apache.cassandra.audit.AuditLogContext;
 import org.apache.cassandra.audit.AuditLogEntryType;
-import org.apache.cassandra.db.guardrails.Guardrails;
-import org.apache.cassandra.schema.TableId;
-import org.apache.cassandra.schema.TableMetadata;
-import org.apache.cassandra.schema.ColumnMetadata;
 import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.cql3.*;
-import org.apache.cassandra.db.*;
+import org.apache.cassandra.cql3.Attributes;
+import org.apache.cassandra.cql3.BatchQueryOptions;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.ColumnSpecification;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.ResultSet;
+import org.apache.cassandra.cql3.VariableSpecifications;
+import org.apache.cassandra.db.Clustering;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.IMutation;
+import org.apache.cassandra.db.RegularAndStaticColumns;
+import org.apache.cassandra.db.Slice;
+import org.apache.cassandra.db.Slices;
+import org.apache.cassandra.db.guardrails.Guardrails;
 import org.apache.cassandra.db.partitions.PartitionUpdate;
 import org.apache.cassandra.db.rows.RowIterator;
-import org.apache.cassandra.exceptions.*;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestValidationException;
+import org.apache.cassandra.exceptions.UnauthorizedException;
 import org.apache.cassandra.metrics.BatchMetrics;
-import org.apache.cassandra.service.*;
+import org.apache.cassandra.schema.ColumnMetadata;
+import org.apache.cassandra.schema.TableId;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.ClientWarn;
+import org.apache.cassandra.service.QueryState;
+import org.apache.cassandra.service.StorageProxy;
 import org.apache.cassandra.tracing.Tracing;
 import org.apache.cassandra.transport.messages.ResultMessage;
 import org.apache.cassandra.utils.FBUtilities;
@@ -50,7 +77,6 @@ import org.apache.cassandra.utils.NoSpamLogger;
 import org.apache.cassandra.utils.Pair;
 
 import static java.util.function.Predicate.isEqual;
-
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 import static org.apache.cassandra.utils.Clock.Global.nanoTime;
 
@@ -194,7 +220,7 @@ public class BatchStatement implements 
CQLStatement.CompositeCQLStatement
         for (ModificationStatement statement : statements)
         {
             if (timestampSet && statement.isTimestampSet())
-                throw new InvalidRequestException("Timestamp must be set 
either on BATCH or individual statements");
+                throw new InvalidRequestException("Timestamp must be set 
either on BATCH or individual statements: " + statement.source);
 
             if (statement.isCounter())
                 hasCounters = true;
@@ -235,7 +261,7 @@ public class BatchStatement implements 
CQLStatement.CompositeCQLStatement
             for (ModificationStatement stmt : statements)
             {
                 if (ksName != null && (!stmt.keyspace().equals(ksName) || 
!stmt.table().equals(cfName)))
-                    throw new InvalidRequestException("Batch with conditions 
cannot span multiple tables");
+                    throw new InvalidRequestException("Batch with conditions 
cannot span multiple tables: " + stmt.source);
                 ksName = stmt.keyspace();
                 cfName = stmt.table();
             }
diff --git a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
index a8d99e588b..b37800992c 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
@@ -19,9 +19,20 @@ package org.apache.cassandra.cql3.statements;
 
 import java.util.List;
 
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
 import org.apache.cassandra.audit.AuditLogContext;
 import org.apache.cassandra.audit.AuditLogEntryType;
-import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.Attributes;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.Operation;
+import org.apache.cassandra.cql3.Operations;
+import org.apache.cassandra.cql3.QualifiedName;
+import org.apache.cassandra.cql3.StatementSource;
+import org.apache.cassandra.cql3.UpdateParameters;
+import org.apache.cassandra.cql3.VariableSpecifications;
+import org.apache.cassandra.cql3.WhereClause;
 import org.apache.cassandra.cql3.conditions.ColumnCondition;
 import org.apache.cassandra.cql3.conditions.Conditions;
 import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
@@ -33,8 +44,6 @@ import org.apache.cassandra.schema.ColumnMetadata;
 import org.apache.cassandra.schema.TableMetadata;
 import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.utils.Pair;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
 
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
@@ -49,9 +58,10 @@ public class DeleteStatement extends ModificationStatement
                             Operations operations,
                             StatementRestrictions restrictions,
                             Conditions conditions,
-                            Attributes attrs)
+                            Attributes attrs,
+                            StatementSource source)
     {
-        super(StatementType.DELETE, bindVariables, cfm, operations, 
restrictions, conditions, attrs);
+        super(StatementType.DELETE, bindVariables, cfm, operations, 
restrictions, conditions, attrs, source);
     }
 
     @Override
@@ -132,9 +142,10 @@ public class DeleteStatement extends ModificationStatement
                       List<Operation.RawDeletion> deletions,
                       WhereClause whereClause,
                       List<Pair<ColumnIdentifier, ColumnCondition.Raw>> 
conditions,
-                      boolean ifExists)
+                      boolean ifExists,
+                      StatementSource source)
         {
-            super(name, StatementType.DELETE, attrs, conditions, false, 
ifExists);
+            super(name, StatementType.DELETE, attrs, conditions, false, 
ifExists, source);
             this.deletions = deletions;
             this.whereClause = whereClause;
         }
@@ -174,7 +185,8 @@ public class DeleteStatement extends ModificationStatement
                                                        operations,
                                                        restrictions,
                                                        conditions,
-                                                       attrs);
+                                                       attrs,
+                                                       source);
 
             if (stmt.hasConditions() && 
!restrictions.hasAllPrimaryKeyColumnsRestrictedByEqualities())
             {
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index a62921bf11..5d5dba79d7 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@ -35,7 +35,6 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.HashMultiset;
 import com.google.common.collect.Iterables;
-
 import com.google.common.collect.Lists;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -52,6 +51,7 @@ import org.apache.cassandra.cql3.QualifiedName;
 import org.apache.cassandra.cql3.QueryOptions;
 import org.apache.cassandra.cql3.QueryProcessor;
 import org.apache.cassandra.cql3.ResultSet;
+import org.apache.cassandra.cql3.StatementSource;
 import org.apache.cassandra.cql3.UpdateParameters;
 import org.apache.cassandra.cql3.Validation;
 import org.apache.cassandra.cql3.VariableSpecifications;
@@ -65,16 +65,41 @@ import org.apache.cassandra.cql3.selection.ResultSetBuilder;
 import org.apache.cassandra.cql3.selection.Selection;
 import org.apache.cassandra.cql3.selection.Selection.Selectors;
 import org.apache.cassandra.cql3.transactions.ReferenceOperation;
-import org.apache.cassandra.db.*;
-import org.apache.cassandra.db.filter.*;
+import org.apache.cassandra.db.CBuilder;
+import org.apache.cassandra.db.Clustering;
+import org.apache.cassandra.db.ClusteringBound;
+import org.apache.cassandra.db.ClusteringComparator;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.IMutation;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.db.ReadExecutionController;
+import org.apache.cassandra.db.RegularAndStaticColumns;
+import org.apache.cassandra.db.SinglePartitionReadCommand;
+import org.apache.cassandra.db.SinglePartitionReadQuery;
+import org.apache.cassandra.db.Slice;
+import org.apache.cassandra.db.Slices;
+import org.apache.cassandra.db.filter.ClusteringIndexFilter;
+import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
+import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
+import org.apache.cassandra.db.filter.ColumnFilter;
+import org.apache.cassandra.db.filter.DataLimits;
+import org.apache.cassandra.db.filter.RowFilter;
 import org.apache.cassandra.db.guardrails.Guardrails;
 import org.apache.cassandra.db.marshal.BooleanType;
 import org.apache.cassandra.db.marshal.ValueAccessor;
-import org.apache.cassandra.db.partitions.*;
+import org.apache.cassandra.db.partitions.FilteredPartition;
+import org.apache.cassandra.db.partitions.Partition;
+import org.apache.cassandra.db.partitions.PartitionIterator;
+import org.apache.cassandra.db.partitions.PartitionIterators;
+import org.apache.cassandra.db.partitions.PartitionUpdate;
 import org.apache.cassandra.db.rows.RowIterator;
 import org.apache.cassandra.db.view.View;
 import org.apache.cassandra.dht.Token;
-import org.apache.cassandra.exceptions.*;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestValidationException;
+import org.apache.cassandra.exceptions.UnauthorizedException;
 import org.apache.cassandra.locator.Replica;
 import org.apache.cassandra.locator.ReplicaLayout;
 import org.apache.cassandra.schema.ColumnMetadata;
@@ -84,13 +109,13 @@ import org.apache.cassandra.schema.ViewMetadata;
 import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.service.StorageProxy;
+import org.apache.cassandra.service.accord.txn.TxnReferenceOperation;
+import org.apache.cassandra.service.accord.txn.TxnReferenceOperations;
+import org.apache.cassandra.service.accord.txn.TxnWrite;
 import org.apache.cassandra.service.disk.usage.DiskUsageBroadcaster;
 import org.apache.cassandra.service.paxos.Ballot;
 import org.apache.cassandra.service.paxos.BallotGenerator;
 import org.apache.cassandra.service.paxos.Commit.Proposal;
-import org.apache.cassandra.service.accord.txn.TxnReferenceOperation;
-import org.apache.cassandra.service.accord.txn.TxnReferenceOperations;
-import org.apache.cassandra.service.accord.txn.TxnWrite;
 import org.apache.cassandra.transport.messages.ResultMessage;
 import org.apache.cassandra.triggers.TriggerExecutor;
 import org.apache.cassandra.utils.ByteBufferUtil;
@@ -136,13 +161,16 @@ public abstract class ModificationStatement implements 
CQLStatement.SingleKeyspa
 
     private final RegularAndStaticColumns requiresRead;
 
+    public final StatementSource source;
+
     public ModificationStatement(StatementType type,
                                  VariableSpecifications bindVariables,
                                  TableMetadata metadata,
                                  Operations operations,
                                  StatementRestrictions restrictions,
                                  Conditions conditions,
-                                 Attributes attrs)
+                                 Attributes attrs,
+                                 StatementSource source)
     {
         this.type = type;
         this.bindVariables = bindVariables;
@@ -151,6 +179,7 @@ public abstract class ModificationStatement implements 
CQLStatement.SingleKeyspa
         this.operations = operations;
         this.conditions = conditions;
         this.attrs = attrs;
+        this.source = source;
 
         if (!conditions.isEmpty())
         {
@@ -1019,13 +1048,15 @@ public abstract class ModificationStatement implements 
CQLStatement.SingleKeyspa
         private final List<Pair<ColumnIdentifier, ColumnCondition.Raw>> 
conditions;
         private final boolean ifNotExists;
         private final boolean ifExists;
+        protected final StatementSource source;
 
         protected Parsed(QualifiedName name,
                          StatementType type,
                          Attributes.Raw attrs,
                          List<Pair<ColumnIdentifier, ColumnCondition.Raw>> 
conditions,
                          boolean ifNotExists,
-                         boolean ifExists)
+                         boolean ifExists,
+                         StatementSource source)
         {
             super(name);
             this.type = type;
@@ -1033,6 +1064,7 @@ public abstract class ModificationStatement implements 
CQLStatement.SingleKeyspa
             this.conditions = conditions == null ? Collections.emptyList() : 
conditions;
             this.ifNotExists = ifNotExists;
             this.ifExists = ifExists;
+            this.source = source;
         }
 
         public ModificationStatement prepare(ClientState state)
@@ -1160,6 +1192,7 @@ public abstract class ModificationStatement implements 
CQLStatement.SingleKeyspa
                                    null,
                                    null,
                                    ONE,
-                                   null);
+                                   null,
+                                   StatementSource.INTERNAL);
     }
 }
diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 43f803782d..b54abd3fa1 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -18,27 +18,45 @@
 package org.apache.cassandra.cql3.statements;
 
 import java.nio.ByteBuffer;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeMap;
 import java.util.stream.Collectors;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
-
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.cassandra.audit.AuditLogContext;
 import org.apache.cassandra.audit.AuditLogEntryType;
 import org.apache.cassandra.auth.Permission;
-import org.apache.cassandra.db.guardrails.Guardrails;
-import org.apache.cassandra.schema.ColumnMetadata;
-import org.apache.cassandra.schema.Schema;
-import org.apache.cassandra.schema.SchemaConstants;
-import org.apache.cassandra.schema.TableMetadata;
-import org.apache.cassandra.schema.TableMetadataRef;
-import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.ColumnSpecification;
+import org.apache.cassandra.cql3.Constants;
+import org.apache.cassandra.cql3.QualifiedName;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.ResultSet;
+import org.apache.cassandra.cql3.StatementSource;
+import org.apache.cassandra.cql3.Term;
+import org.apache.cassandra.cql3.VariableSpecifications;
+import org.apache.cassandra.cql3.WhereClause;
 import org.apache.cassandra.cql3.functions.Function;
 import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
 import org.apache.cassandra.cql3.selection.RawSelector;
@@ -48,10 +66,29 @@ import 
org.apache.cassandra.cql3.selection.Selectable.WithFunction;
 import org.apache.cassandra.cql3.selection.Selection;
 import org.apache.cassandra.cql3.selection.Selection.Selectors;
 import org.apache.cassandra.cql3.selection.Selector;
-import org.apache.cassandra.db.*;
+import org.apache.cassandra.db.Clustering;
+import org.apache.cassandra.db.ClusteringBound;
+import org.apache.cassandra.db.ColumnFamilyStore;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.DataRange;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.PartitionPosition;
+import org.apache.cassandra.db.PartitionRangeReadQuery;
+import org.apache.cassandra.db.ReadExecutionController;
+import org.apache.cassandra.db.ReadQuery;
+import org.apache.cassandra.db.SinglePartitionReadCommand;
+import org.apache.cassandra.db.SinglePartitionReadQuery;
+import org.apache.cassandra.db.Slice;
+import org.apache.cassandra.db.Slices;
 import org.apache.cassandra.db.aggregation.AggregationSpecification;
 import org.apache.cassandra.db.aggregation.GroupMaker;
-import org.apache.cassandra.db.filter.*;
+import org.apache.cassandra.db.filter.ClusteringIndexFilter;
+import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
+import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
+import org.apache.cassandra.db.filter.ColumnFilter;
+import org.apache.cassandra.db.filter.DataLimits;
+import org.apache.cassandra.db.filter.RowFilter;
+import org.apache.cassandra.db.guardrails.Guardrails;
 import org.apache.cassandra.db.marshal.CompositeType;
 import org.apache.cassandra.db.marshal.Int32Type;
 import org.apache.cassandra.db.partitions.PartitionIterator;
@@ -59,8 +96,18 @@ import org.apache.cassandra.db.rows.Row;
 import org.apache.cassandra.db.rows.RowIterator;
 import org.apache.cassandra.db.view.View;
 import org.apache.cassandra.dht.AbstractBounds;
-import org.apache.cassandra.exceptions.*;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.exceptions.ReadSizeAbortException;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestFailureReason;
+import org.apache.cassandra.exceptions.RequestValidationException;
+import org.apache.cassandra.exceptions.UnauthorizedException;
 import org.apache.cassandra.index.IndexRegistry;
+import org.apache.cassandra.schema.ColumnMetadata;
+import org.apache.cassandra.schema.Schema;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.schema.TableMetadataRef;
 import org.apache.cassandra.serializers.MarshalException;
 import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.ClientWarn;
@@ -74,9 +121,6 @@ import org.apache.cassandra.transport.messages.ResultMessage;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.FBUtilities;
 
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkNull;
@@ -121,6 +165,8 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
      */
     private final Comparator<List<ByteBuffer>> orderingComparator;
 
+    public final StatementSource source;
+
     // Used by forSelection below
     public static final Parameters defaultParameters = new 
Parameters(Collections.emptyMap(),
                                                                        
Collections.emptyList(),
@@ -137,7 +183,8 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
                            AggregationSpecification.Factory 
aggregationSpecFactory,
                            Comparator<List<ByteBuffer>> orderingComparator,
                            Term limit,
-                           Term perPartitionLimit)
+                           Term perPartitionLimit,
+                           StatementSource source)
     {
         this.table = table;
         this.bindVariables = bindVariables;
@@ -149,6 +196,7 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
         this.parameters = parameters;
         this.limit = limit;
         this.perPartitionLimit = perPartitionLimit;
+        this.source = source;
     }
 
     @Override
@@ -209,7 +257,8 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
                                    null,
                                    null,
                                    null,
-                                   null);
+                                   null,
+                                   StatementSource.INTERNAL);
     }
 
     @Override
@@ -306,7 +355,7 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
                               int pageSize,
                               AggregationSpecification aggregationSpec)
     {
-        boolean isPartitionRangeQuery = restrictions.isKeyRange() || 
restrictions.usesSecondaryIndexing();
+        boolean isPartitionRangeQuery = isPartitionRangeQuery();
 
         DataLimits limit = getDataLimits(userLimit, perPartitionLimit, 
pageSize, aggregationSpec);
 
@@ -602,6 +651,11 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
         return restrictions;
     }
 
+    public boolean isPartitionRangeQuery()
+    {
+        return isForPartitionRange(restrictions);
+    }
+
     private ReadQuery getSliceCommands(QueryOptions options, ClientState 
state, ColumnFilter columnFilter,
                                        DataLimits limit, int nowInSec)
     {
@@ -1053,6 +1107,11 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
         Collections.sort(cqlRows.rows, orderingComparator);
     }
 
+    private static boolean isForPartitionRange(StatementRestrictions 
restrictions)
+    {
+        return restrictions.isKeyRange() || 
restrictions.usesSecondaryIndexing();
+    }
+
     public static class RawStatement extends QualifiedStatement
     {
         public final Parameters parameters;
@@ -1061,13 +1120,15 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
         public final Term.Raw limit;
         public final Term.Raw perPartitionLimit;
         private ClientState state;
+        private final StatementSource source;
 
         public RawStatement(QualifiedName cfName,
                             Parameters parameters,
                             List<RawSelector> selectClause,
                             WhereClause whereClause,
                             Term.Raw limit,
-                            Term.Raw perPartitionLimit)
+                            Term.Raw perPartitionLimit,
+                            StatementSource source)
         {
             super(cfName);
             this.parameters = parameters;
@@ -1075,6 +1136,7 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
             this.whereClause = whereClause;
             this.limit = limit;
             this.perPartitionLimit = perPartitionLimit;
+            this.source = source;
         }
 
         public SelectStatement prepare(ClientState state)
@@ -1163,7 +1225,8 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
                                        aggregationSpecFactory,
                                        orderingComparator,
                                        prepareLimit(variableSpecifications, 
limit, keyspace(), limitReceiver()),
-                                       prepareLimit(variableSpecifications, 
perPartitionLimit, keyspace(), perPartitionLimitReceiver()));
+                                       prepareLimit(variableSpecifications, 
perPartitionLimit, keyspace(), perPartitionLimitReceiver()),
+                                       source);
         }
 
         private Selection prepareSelection(TableMetadata table,
@@ -1460,7 +1523,7 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
         private void checkNeedsFiltering(StatementRestrictions restrictions) 
throws InvalidRequestException
         {
             // non-key-range non-indexed queries cannot involve filtering 
underneath
-            if (!parameters.allowFiltering && (restrictions.isKeyRange() || 
restrictions.usesSecondaryIndexing()))
+            if (!parameters.allowFiltering && 
isForPartitionRange(restrictions))
             {
                 // We will potentially filter data if either:
                 //  - Have more than one IndexExpression
@@ -1596,7 +1659,7 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
 
     private String loggableTokens(QueryOptions options, ClientState state)
     {
-        if (restrictions.isKeyRange() || restrictions.usesSecondaryIndexing())
+        if (isPartitionRangeQuery())
         {
             AbstractBounds<PartitionPosition> bounds = 
restrictions.getPartitionKeyBounds(options);
             return "token range: " + (bounds.inclusiveLeft() ? '[' : '(') +
@@ -1633,7 +1696,7 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
 
         sb.append("SELECT ").append(queriedColumns().toCQLString());
         sb.append(" FROM 
").append(table.keyspace).append('.').append(table.name);
-        if (restrictions.isKeyRange() || restrictions.usesSecondaryIndexing())
+        if (isPartitionRangeQuery())
         {
             // partition range
             ClusteringIndexFilter clusteringIndexFilter = 
makeClusteringIndexFilter(options, state, columnFilter);
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java
index 33e5803a96..4ffad56358 100644
--- a/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java
@@ -57,7 +57,6 @@ import 
org.apache.cassandra.cql3.transactions.ConditionStatement;
 import org.apache.cassandra.cql3.transactions.ReferenceOperation;
 import org.apache.cassandra.cql3.transactions.RowDataReference;
 import org.apache.cassandra.cql3.transactions.SelectReferenceSource;
-import org.apache.cassandra.db.ReadQuery;
 import org.apache.cassandra.db.SinglePartitionReadCommand;
 import org.apache.cassandra.db.SinglePartitionReadQuery;
 import org.apache.cassandra.db.marshal.AbstractType;
@@ -77,7 +76,6 @@ import org.apache.cassandra.service.accord.txn.TxnUpdate;
 import org.apache.cassandra.service.accord.txn.TxnWrite;
 import org.apache.cassandra.transport.messages.ResultMessage;
 import org.apache.cassandra.utils.FBUtilities;
-import org.apache.cassandra.utils.LazyToString;
 
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
@@ -89,14 +87,13 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
     private static final Logger logger = 
LoggerFactory.getLogger(TransactionStatement.class);
 
     public static final String DUPLICATE_TUPLE_NAME_MESSAGE = "The name '%s' 
has already been used by a LET assignment.";
-    public static final String INCOMPLETE_PRIMARY_KEY_LET_MESSAGE = "SELECT in 
LET assignment must specify either all primary key elements or all partition 
key elements and LIMIT 1. In both cases partition key elements must be always 
specified with equality operators; CQL %s";
-    public static final String INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE = "Normal 
SELECT must specify either all primary key elements or all partition key 
elements and LIMIT 1. In both cases partition key elements must be always 
specified with equality operators; CQL %s";
-    public static final String NO_CONDITIONS_IN_UPDATES_MESSAGE = "Updates 
within transactions may not specify their own conditions.";
-    public static final String NO_TIMESTAMPS_IN_UPDATES_MESSAGE = "Updates 
within transactions may not specify custom timestamps.";
+    public static final String INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE = "SELECT 
must specify either all primary key elements or all partition key elements and 
LIMIT 1. In both cases partition key elements must be always specified with 
equality operators; %s %s";
+    public static final String NO_CONDITIONS_IN_UPDATES_MESSAGE = "Updates 
within transactions may not specify their own conditions; %s statement %s";
+    public static final String NO_TIMESTAMPS_IN_UPDATES_MESSAGE = "Updates 
within transactions may not specify custom timestamps; %s statement %s";
     public static final String EMPTY_TRANSACTION_MESSAGE = "Transaction 
contains no reads or writes";
     public static final String SELECT_REFS_NEED_COLUMN_MESSAGE = "SELECT 
references must specify a column.";
     public static final String TRANSACTIONS_DISABLED_MESSAGE = "Accord 
transactions are disabled. (See accord_transactions_enabled in cassandra.yaml)";
-    public static final String ILLEGAL_RANGE_QUERY_MESSAGE = "Range queries 
are not allowed for reads within a transaction";
+    public static final String ILLEGAL_RANGE_QUERY_MESSAGE = "Range queries 
are not allowed for reads within a transaction; %s %s";
 
     static class NamedSelect
     {
@@ -207,12 +204,9 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
     TxnNamedRead createNamedRead(NamedSelect namedSelect, QueryOptions 
options, ClientState state)
     {
         SelectStatement select = namedSelect.select;
-        ReadQuery readQuery = select.getQuery(options, 0);
-        checkTrue(readQuery instanceof  SinglePartitionReadQuery.Group, 
ILLEGAL_RANGE_QUERY_MESSAGE, select.asCQL(options, state));
-
         // We reject reads from both LET and SELECT that do not specify a 
single row.
         @SuppressWarnings("unchecked")
-        SinglePartitionReadQuery.Group<SinglePartitionReadCommand> selectQuery 
= (SinglePartitionReadQuery.Group<SinglePartitionReadCommand>) readQuery;
+        SinglePartitionReadQuery.Group<SinglePartitionReadCommand> selectQuery 
= (SinglePartitionReadQuery.Group<SinglePartitionReadCommand>) 
select.getQuery(options, 0);
 
         if (selectQuery.queries.size() != 1)
             throw new IllegalArgumentException("Within a transaction, SELECT 
statements must select a single partition; found " + selectQuery.queries.size() 
+ " partitions");
@@ -223,12 +217,9 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
     List<TxnNamedRead> createNamedReads(NamedSelect namedSelect, QueryOptions 
options, ClientState state)
     {
         SelectStatement select = namedSelect.select;
-        ReadQuery readQuery = select.getQuery(options, 0);
-        checkTrue(readQuery instanceof  SinglePartitionReadQuery.Group, 
ILLEGAL_RANGE_QUERY_MESSAGE, select.asCQL(options, state));
-
         // We reject reads from both LET and SELECT that do not specify a 
single row.
         @SuppressWarnings("unchecked")
-        SinglePartitionReadQuery.Group<SinglePartitionReadCommand> selectQuery 
= (SinglePartitionReadQuery.Group<SinglePartitionReadCommand>) readQuery;
+        SinglePartitionReadQuery.Group<SinglePartitionReadCommand> selectQuery 
= (SinglePartitionReadQuery.Group<SinglePartitionReadCommand>) 
select.getQuery(options, 0);
 
         if (selectQuery.queries.size() == 1)
             return Collections.singletonList(new 
TxnNamedRead(namedSelect.name, Iterables.getOnlyElement(selectQuery.queries)));
@@ -339,24 +330,24 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
         }
     }
 
-    private static void checkAtMostOneRowSpecified(ClientState clientState, 
@Nullable QueryOptions options, SelectStatement select, String failureMessage)
+    /**
+     * Returns {@code true} only if the statement selects multiple clusterings 
in a partition
+     */
+    private static boolean isSelectingMultipleClusterings(SelectStatement 
select, @Nullable QueryOptions options)
     {
         if 
(select.getRestrictions().hasAllPrimaryKeyColumnsRestrictedByEqualities())
-            return;
+            return false;
 
         if (options == null)
         {
-            // If the limit is a non-terminal marker (because we're 
preparing), defer validation until execution.
+            // if the limit is a non-terminal marker (because we're 
preparing), defer validation until execution (when options != null)
             if (select.isLimitMarker())
-                return;
+                return false;
 
-            // The limit is already defined, so proceed with validation...
             options = QueryOptions.DEFAULT;
         }
 
-        int limit = select.getLimit(options);
-        QueryOptions finalOptions = options; // javac thinks this is mutable 
so requires a copy
-        checkTrue(limit == 1 && 
select.getRestrictions().hasAllPartitionKeyColumnsRestrictedByEqualities(), 
failureMessage, LazyToString.lazy(() -> select.asCQL(finalOptions, 
clientState)));
+        return select.getLimit(options) != 1;
     }
 
     @Override
@@ -366,21 +357,19 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
 
         try
         {
+            // check again since now we have query options; note that 
statements are quaranted to be single partition reads at this point
             for (NamedSelect assignment : assignments)
-                checkAtMostOneRowSpecified(state.getClientState(), options, 
assignment.select, INCOMPLETE_PRIMARY_KEY_LET_MESSAGE);
+                checkFalse(isSelectingMultipleClusterings(assignment.select, 
options), INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, "LET assignment", 
assignment.select.source);
 
             if (returningSelect != null)
-                checkAtMostOneRowSpecified(state.getClientState(), options, 
returningSelect.select, INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE);
+                
checkFalse(isSelectingMultipleClusterings(returningSelect.select, options), 
INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, "returning SELECT", 
returningSelect.select.source);
 
             TxnData data = 
AccordService.instance().coordinate(createTxn(state.getClientState(), options), 
options.getConsistency());
 
             if (returningSelect != null)
             {
-                ReadQuery readQuery = returningSelect.select.getQuery(options, 
0);
-                checkTrue(readQuery instanceof  
SinglePartitionReadQuery.Group, ILLEGAL_RANGE_QUERY_MESSAGE, 
returningSelect.select.asCQL(options, state.getClientState()));
-
                 @SuppressWarnings("unchecked")
-                SinglePartitionReadQuery.Group<SinglePartitionReadCommand> 
selectQuery = (SinglePartitionReadQuery.Group<SinglePartitionReadCommand>) 
readQuery;
+                SinglePartitionReadQuery.Group<SinglePartitionReadCommand> 
selectQuery = (SinglePartitionReadQuery.Group<SinglePartitionReadCommand>) 
returningSelect.select.getQuery(options, 0);
                 Selection.Selectors selectors = 
returningSelect.select.getSelection().newSelectors(options);
                 ResultSetBuilder result = new ResultSetBuilder(resultMetadata, 
selectors, null);
                 if (selectQuery.queries.size() == 1)
@@ -505,7 +494,7 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
 
                 SelectStatement prepared = select.prepare(bindVariables);
                 NamedSelect namedSelect = new NamedSelect(name, prepared);
-                checkAtMostOneRowSpecified(state, null, namedSelect.select, 
INCOMPLETE_PRIMARY_KEY_LET_MESSAGE);
+                checkAtMostOneRowSpecified(namedSelect.select, "LET assignment 
" + name.name());
                 preparedAssignments.add(namedSelect);
                 refSources.put(name, new SelectReferenceSource(prepared));
             }
@@ -518,7 +507,7 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
             if (select != null)
             {
                 returningSelect = new NamedSelect(TxnDataName.returning(), 
select.prepare(bindVariables));
-                checkAtMostOneRowSpecified(state, null, 
returningSelect.select, INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE);
+                checkAtMostOneRowSpecified(returningSelect.select, "returning 
select");
             }
 
             List<RowDataReference> returningReferences = null;
@@ -539,8 +528,8 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
                 ModificationStatement.Parsed parsed = updates.get(i);
 
                 ModificationStatement prepared = parsed.prepare(state, 
bindVariables);
-                checkFalse(prepared.hasConditions(), 
NO_CONDITIONS_IN_UPDATES_MESSAGE);
-                checkFalse(prepared.isTimestampSet(), 
NO_TIMESTAMPS_IN_UPDATES_MESSAGE);
+                checkFalse(prepared.hasConditions(), 
NO_CONDITIONS_IN_UPDATES_MESSAGE, prepared.type, prepared.source);
+                checkFalse(prepared.isTimestampSet(), 
NO_TIMESTAMPS_IN_UPDATES_MESSAGE, prepared.type, prepared.source);
 
                 preparedUpdates.add(prepared);
             }
@@ -552,5 +541,15 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
 
             return new TransactionStatement(preparedAssignments, 
returningSelect, returningReferences, preparedUpdates, preparedConditions, 
bindVariables);
         }
+
+        /**
+         * Do not use this method in execution!!! It is only allowed during 
prepare because it outputs a query raw text.
+         * We don't want it print it for a user who provided an identifier of 
someone's else prepared statement.
+         */
+        private static void checkAtMostOneRowSpecified(SelectStatement select, 
String name)
+        {
+            checkFalse(select.isPartitionRangeQuery(), 
ILLEGAL_RANGE_QUERY_MESSAGE, name, select.source);
+            checkFalse(isSelectingMultipleClusterings(select, null), 
INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, name, select.source);
+        }
     }
 }
diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
index 9531679f0f..da25ef7a22 100644
--- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
@@ -23,10 +23,25 @@ import java.util.Collections;
 import java.util.List;
 
 import com.google.common.base.Preconditions;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
 
 import org.apache.cassandra.audit.AuditLogContext;
 import org.apache.cassandra.audit.AuditLogEntryType;
-import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.Attributes;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.Constants;
+import org.apache.cassandra.cql3.Json;
+import org.apache.cassandra.cql3.Operation;
+import org.apache.cassandra.cql3.Operations;
+import org.apache.cassandra.cql3.Operator;
+import org.apache.cassandra.cql3.QualifiedName;
+import org.apache.cassandra.cql3.SingleColumnRelation;
+import org.apache.cassandra.cql3.StatementSource;
+import org.apache.cassandra.cql3.Term;
+import org.apache.cassandra.cql3.UpdateParameters;
+import org.apache.cassandra.cql3.VariableSpecifications;
+import org.apache.cassandra.cql3.WhereClause;
 import org.apache.cassandra.cql3.conditions.ColumnCondition;
 import org.apache.cassandra.cql3.conditions.Conditions;
 import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
@@ -41,8 +56,6 @@ import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.accord.txn.TxnReferenceOperation;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.Pair;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
 
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkContainsNoDuplicates;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
@@ -64,9 +77,10 @@ public class UpdateStatement extends ModificationStatement
                             Operations operations,
                             StatementRestrictions restrictions,
                             Conditions conditions,
-                            Attributes attrs)
+                            Attributes attrs,
+                            StatementSource source)
     {
-        super(type, bindVariables, metadata, operations, restrictions, 
conditions, attrs);
+        super(type, bindVariables, metadata, operations, restrictions, 
conditions, attrs, source);
     }
 
     @Override
@@ -137,9 +151,10 @@ public class UpdateStatement extends ModificationStatement
                             Attributes.Raw attrs,
                             List<ColumnIdentifier> columnNames,
                             List<Term.Raw> columnValues,
-                            boolean ifNotExists)
+                            boolean ifNotExists,
+                            StatementSource source)
         {
-            super(name, StatementType.INSERT, attrs, null, ifNotExists, false);
+            super(name, StatementType.INSERT, attrs, null, ifNotExists, false, 
source);
             this.columnNames = columnNames;
             this.columnValues = columnValues;
         }
@@ -210,7 +225,8 @@ public class UpdateStatement extends ModificationStatement
                                        operations,
                                        restrictions,
                                        conditions,
-                                       attrs);
+                                       attrs,
+                                       source);
         }
     }
 
@@ -222,9 +238,9 @@ public class UpdateStatement extends ModificationStatement
         private final Json.Raw jsonValue;
         private final boolean defaultUnset;
 
-        public ParsedInsertJson(QualifiedName name, Attributes.Raw attrs, 
Json.Raw jsonValue, boolean defaultUnset, boolean ifNotExists)
+        public ParsedInsertJson(QualifiedName name, Attributes.Raw attrs, 
Json.Raw jsonValue, boolean defaultUnset, boolean ifNotExists, StatementSource 
source)
         {
-            super(name, StatementType.INSERT, attrs, null, ifNotExists, false);
+            super(name, StatementType.INSERT, attrs, null, ifNotExists, false, 
source);
             this.jsonValue = jsonValue;
             this.defaultUnset = defaultUnset;
         }
@@ -280,7 +296,8 @@ public class UpdateStatement extends ModificationStatement
                                        operations,
                                        restrictions,
                                        conditions,
-                                       attrs);
+                                       attrs,
+                                       source);
         }
     }
 
@@ -354,9 +371,10 @@ public class UpdateStatement extends ModificationStatement
                             WhereClause whereClause,
                             List<Pair<ColumnIdentifier, ColumnCondition.Raw>> 
conditions,
                             boolean ifExists,
-                            boolean isForTxn)
+                            boolean isForTxn,
+                            StatementSource source)
         {
-            super(name, StatementType.UPDATE, attrs, conditions, false, 
ifExists);
+            super(name, StatementType.UPDATE, attrs, conditions, false, 
ifExists, source);
             this.updates = updates;
             this.whereClause = whereClause;
             this.isForTxn = isForTxn;
@@ -402,7 +420,8 @@ public class UpdateStatement extends ModificationStatement
                                        operations,
                                        restrictions,
                                        conditions,
-                                       attrs);
+                                       attrs,
+                                       source);
         }
     }
     
diff --git a/src/java/org/apache/cassandra/db/view/View.java 
b/src/java/org/apache/cassandra/db/view/View.java
index a3ecc33d79..5603ede19a 100644
--- a/src/java/org/apache/cassandra/db/view/View.java
+++ b/src/java/org/apache/cassandra/db/view/View.java
@@ -17,19 +17,26 @@
  */
 package org.apache.cassandra.db.view;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.stream.Collectors;
-
 import javax.annotation.Nullable;
 
 import com.google.common.collect.Iterables;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.QualifiedName;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.StatementSource;
 import org.apache.cassandra.cql3.selection.RawSelector;
 import org.apache.cassandra.cql3.selection.Selectable;
 import org.apache.cassandra.cql3.statements.SelectStatement;
-import org.apache.cassandra.db.*;
-import org.apache.cassandra.db.rows.*;
+import org.apache.cassandra.db.ColumnFamilyStore;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.ReadQuery;
+import org.apache.cassandra.db.rows.Row;
 import org.apache.cassandra.schema.ColumnMetadata;
 import org.apache.cassandra.schema.KeyspaceMetadata;
 import org.apache.cassandra.schema.Schema;
@@ -37,8 +44,6 @@ import org.apache.cassandra.schema.TableMetadataRef;
 import org.apache.cassandra.schema.ViewMetadata;
 import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.utils.FBUtilities;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * A View copies data from a base table into a view table which can be queried 
independently from the
@@ -174,7 +179,8 @@ public class View
                                                  selectClause(),
                                                  definition.whereClause,
                                                  null,
-                                                 null);
+                                                 null,
+                                                 StatementSource.INTERNAL);
 
             rawSelect.setBindVariables(Collections.emptyList());
 
diff --git a/test/unit/org/apache/cassandra/cql3/StatementSourceTest.java 
b/test/unit/org/apache/cassandra/cql3/StatementSourceTest.java
new file mode 100644
index 0000000000..b6362747d5
--- /dev/null
+++ b/test/unit/org/apache/cassandra/cql3/StatementSourceTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.cql3;
+
+import org.junit.Test;
+
+import org.antlr.runtime.Token;
+import org.mockito.Mockito;
+
+import static org.apache.cassandra.cql3.StatementSource.create;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+public class StatementSourceTest
+{
+    private static Token token(int line, int pos)
+    {
+        Token token = Mockito.mock(Token.class);
+        when(token.getLine()).thenReturn(line);
+        when(token.getCharPositionInLine()).thenReturn(pos);
+        when(token.getType()).thenReturn(1);
+        return token;
+    }
+
+    private static Token eof()
+    {
+        Token token = Mockito.mock(Token.class);
+        when(token.getLine()).thenThrow(UnsupportedOperationException.class);
+        
when(token.getCharPositionInLine()).thenThrow(UnsupportedOperationException.class);
+        when(token.getType()).thenReturn(Token.EOF);
+        return token;
+    }
+
+    @Test
+    public void test()
+    {
+        assertThat(create(token(1, 4))).hasToString("at [1:5]");
+        assertThat(create(token(3, 8))).hasToString("at [3:9]");
+        assertThat(create(token(6, 8))).hasToString("at [6:9]");
+        assertThat(create(token(1, 0))).hasToString("at [1:1]");
+        assertThat(create(eof()).toString()).isEmpty();
+
+        assertThat(StatementSource.INTERNAL).hasToString("<<<internal 
statement>>>");
+    }
+}
diff --git 
a/test/unit/org/apache/cassandra/cql3/statements/TransactionStatementTest.java 
b/test/unit/org/apache/cassandra/cql3/statements/TransactionStatementTest.java
index d8062a39ec..afdc91cb17 100644
--- 
a/test/unit/org/apache/cassandra/cql3/statements/TransactionStatementTest.java
+++ 
b/test/unit/org/apache/cassandra/cql3/statements/TransactionStatementTest.java
@@ -18,7 +18,6 @@
 
 package org.apache.cassandra.cql3.statements;
 
-import org.assertj.core.api.Assertions;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -32,10 +31,11 @@ import org.apache.cassandra.schema.TableId;
 import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.transport.messages.ResultMessage;
+import org.assertj.core.api.Assertions;
 
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.DUPLICATE_TUPLE_NAME_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.EMPTY_TRANSACTION_MESSAGE;
-import static 
org.apache.cassandra.cql3.statements.TransactionStatement.INCOMPLETE_PRIMARY_KEY_LET_MESSAGE;
+import static 
org.apache.cassandra.cql3.statements.TransactionStatement.ILLEGAL_RANGE_QUERY_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.NO_CONDITIONS_IN_UPDATES_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.NO_TIMESTAMPS_IN_UPDATES_MESSAGE;
@@ -161,7 +161,7 @@ public class TransactionStatementTest
 
         Assertions.assertThatThrownBy(() -> prepare(query))
                   .isInstanceOf(InvalidRequestException.class)
-                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_LET_MESSAGE, 
letSelect));
+                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, "LET 
assignment row1", "at [2:15]"));
     }
 
     @Test
@@ -175,7 +175,7 @@ public class TransactionStatementTest
 
         Assertions.assertThatThrownBy(() -> execute(query, 2))
                   .isInstanceOf(InvalidRequestException.class)
-                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_LET_MESSAGE, 
letSelect.replace("?", "2")));
+                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, "LET 
assignment", "at [2:15]"));
     }
 
     @Test
@@ -189,7 +189,7 @@ public class TransactionStatementTest
 
         Assertions.assertThatThrownBy(() -> prepare(query))
                   .isInstanceOf(InvalidRequestException.class)
-                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_LET_MESSAGE, 
letSelect));
+                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, "LET 
assignment row1", "at [2:15]"));
     }
 
     @Test
@@ -200,7 +200,7 @@ public class TransactionStatementTest
 
         Assertions.assertThatThrownBy(() -> prepare(query))
                   .isInstanceOf(InvalidRequestException.class)
-                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, 
select));
+                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, 
"returning select", "at [2:1]"));
     }
 
     @Test
@@ -211,7 +211,7 @@ public class TransactionStatementTest
 
         Assertions.assertThatThrownBy(() -> prepare(query))
                   .isInstanceOf(InvalidRequestException.class)
-                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, 
select));
+                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, 
"returning select", "at [2:1]"));
     }
 
     @Test
@@ -223,7 +223,7 @@ public class TransactionStatementTest
 
         Assertions.assertThatThrownBy(() -> prepare(query))
                   .isInstanceOf(InvalidRequestException.class)
-                  .hasMessageContaining(NO_CONDITIONS_IN_UPDATES_MESSAGE);
+                  .hasMessageContaining(NO_CONDITIONS_IN_UPDATES_MESSAGE, 
"INSERT", "at [2:3]");
     }
 
     @Test
@@ -235,7 +235,7 @@ public class TransactionStatementTest
 
         Assertions.assertThatThrownBy(() -> prepare(query))
                   .isInstanceOf(InvalidRequestException.class)
-                  .hasMessageContaining(NO_TIMESTAMPS_IN_UPDATES_MESSAGE);
+                  .hasMessageContaining(NO_TIMESTAMPS_IN_UPDATES_MESSAGE, 
"INSERT", "at [2:3]");
     }
 
     @Test
@@ -335,26 +335,28 @@ public class TransactionStatementTest
     @Test
     public void shouldRejectNormalSelectWithIncompletePartitionKey()
     {
+        String select = "SELECT k, v FROM ks.tbl5 LIMIT 1";
         String query = "BEGIN TRANSACTION\n" +
-                       "  SELECT k, v FROM ks.tbl5 LIMIT 1;\n" +
+                       select + ";\n" +
                        "COMMIT TRANSACTION;\n";
 
         Assertions.assertThatThrownBy(() -> prepare(query))
                   .isInstanceOf(InvalidRequestException.class)
-                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE, 
"SELECT v FROM ks.tbl5 LIMIT 1"));
+                  
.hasMessageContaining(String.format(ILLEGAL_RANGE_QUERY_MESSAGE, "returning 
select", "at [2:1]"));
     }
 
     @Test
     public void shouldRejectLetSelectWithIncompletePartitionKey()
     {
+        String select = "SELECT k, v FROM ks.tbl5 WHERE token(k) > token(123) 
LIMIT 1";
         String query = "BEGIN TRANSACTION\n" +
-                       "  LET row1 = (SELECT k, v FROM ks.tbl5 WHERE token(k) 
> token(123) LIMIT 1); \n" +
+                       "  LET row1 = (" + select + "); \n" +
                        "  SELECT row1.k, row1.v;\n" +
                        "COMMIT TRANSACTION;\n";
 
         Assertions.assertThatThrownBy(() -> prepare(query))
                   .isInstanceOf(InvalidRequestException.class)
-                  
.hasMessageContaining(String.format(INCOMPLETE_PRIMARY_KEY_LET_MESSAGE, "SELECT 
v FROM ks.tbl5 WHERE token(k) > 0000007b LIMIT 1"));
+                  
.hasMessageContaining(String.format(ILLEGAL_RANGE_QUERY_MESSAGE, "LET 
assignment row1", "at [2:15]"));
     }
 
     private static CQLStatement prepare(String query)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to