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

dcapwell 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 56ca3032e4 Accord should block currently unsafe operations
56ca3032e4 is described below

commit 56ca3032e4009204356887509a39a56fbcbd9cf7
Author: David Capwell <[email protected]>
AuthorDate: Wed Oct 23 09:56:01 2024 -0700

    Accord should block currently unsafe operations
    
    patch by David Capwell; reviewed by Ariel Weisberg for CASSANDRA-20020
---
 .../cassandra/cql3/statements/SelectStatement.java |   7 +-
 .../cql3/statements/TransactionStatement.java      |  50 ++++---
 .../org/apache/cassandra/cql3/ast/Conditional.java |  37 ++++++
 .../org/apache/cassandra/cql3/ast/Mutation.java    |  53 ++++++++
 .../unit/org/apache/cassandra/cql3/ast/Select.java |   8 ++
 test/unit/org/apache/cassandra/cql3/ast/Txn.java   |  11 ++
 .../cql3/statements/TransactionStatementTest.java  | 143 ++++++++++++++++++++-
 7 files changed, 292 insertions(+), 17 deletions(-)

diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 66a4797637..bfe6589eb4 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -420,6 +420,11 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
         return aggregationSpecFactory == null ? null : 
aggregationSpecFactory.newInstance(options);
     }
 
+    public boolean hasAggregation()
+    {
+        return aggregationSpecFactory != null;
+    }
+
     public ReadQuery getQuery(QueryOptions options, long nowInSec) throws 
RequestValidationException
     {
         Selectors selectors = selection.newSelectors(options);
@@ -1226,7 +1231,7 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement,
         public final Term.Raw limit;
         public final Term.Raw perPartitionLimit;
         private ClientState state;
-        private final StatementSource source;
+        public final StatementSource source;
 
         public RawStatement(QualifiedName cfName,
                             Parameters parameters,
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java
index 992d3af0f0..1a6b3ee782 100644
--- a/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java
@@ -57,6 +57,7 @@ import 
org.apache.cassandra.cql3.transactions.RowDataReference;
 import org.apache.cassandra.cql3.transactions.SelectReferenceSource;
 import org.apache.cassandra.db.SinglePartitionReadCommand;
 import org.apache.cassandra.db.SinglePartitionReadQuery;
+import org.apache.cassandra.db.filter.DataLimits;
 import org.apache.cassandra.db.marshal.AbstractType;
 import org.apache.cassandra.db.partitions.FilteredPartition;
 import org.apache.cassandra.exceptions.InvalidRequestException;
@@ -90,6 +91,7 @@ import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
 import static org.apache.cassandra.service.accord.txn.TxnRead.createTxnRead;
 import static 
org.apache.cassandra.service.accord.txn.TxnResult.Kind.retry_new_protocol;
+import static org.apache.cassandra.service.accord.txn.TxnResult.Kind.values;
 
 public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement, CQLStatement.ReturningCQLStatement
 {
@@ -98,14 +100,19 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
     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 NO_TTLS_IN_UPDATES_MESSAGE = "Updates within 
transactions may not specify custom ttls; %s statement %s";
     public static final String TRANSACTIONS_DISABLED_ON_TABLE_MESSAGE = 
"Accord transactions are disabled on table (See transactional_mode in table 
options); %s statement %s";
     public static final String 
TRANSACTIONS_DISABLED_ON_TABLE_BEING_DROPPED_MESSAGE = "Accord transactions are 
disabled on table (table is being dropped); %s statement %s";
     public static final String NO_COUNTERS_IN_TXNS_MESSAGE = "Counter columns 
cannot be accessed within a transaction; %s statement %s";
+    public static final String NO_AGGREGATION_IN_TXNS_MESSAGE = "No 
aggregation functions allowed within a transaction; %s statement %s";
+    public static final String NO_ORDER_BY_IN_TXNS_MESSAGE = "No ORDER BY 
clause allowed within a transaction; %s statement %s";
+    public static final String NO_GROUP_BY_IN_TXNS_MESSAGE = "No GROUP BY 
clause allowed within a transaction; %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.enabled in cassandra.yaml)";
     public static final String ILLEGAL_RANGE_QUERY_MESSAGE = "Range queries 
are not allowed for reads within a transaction; %s %s";
     public static final String UNSUPPORTED_MIGRATION = "Transaction Statement 
is unsupported when migrating away from Accord or before migration to Accord is 
complete for a range";
+    public static final String NO_PARTITION_IN_CLAUSE_WITH_LIMIT = "Partition 
key is present in IN clause and there is a LIMIT... this is currently not 
supported; %s statement %s";
 
     static class NamedSelect
     {
@@ -464,6 +471,29 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
         return false;
     }
 
+    private static void validate(SelectStatement.RawStatement select)
+    {
+        if (select.parameters.orderings != null && 
!select.parameters.orderings.isEmpty())
+            throw invalidRequest(NO_ORDER_BY_IN_TXNS_MESSAGE, "SELECT", 
select.source);
+        if (select.parameters.groups != null && 
!select.parameters.groups.isEmpty())
+            throw invalidRequest(NO_GROUP_BY_IN_TXNS_MESSAGE, "SELECT", 
select.source);
+    }
+
+    private static void validate(SelectStatement prepared)
+    {
+        if (!prepared.table.isAccordEnabled())
+            throw invalidRequest(TRANSACTIONS_DISABLED_ON_TABLE_MESSAGE, 
"SELECT", prepared.source);
+        if (prepared.table.params.pendingDrop)
+            throw 
invalidRequest(TRANSACTIONS_DISABLED_ON_TABLE_BEING_DROPPED_MESSAGE, "SELECT", 
prepared.source);
+        if (prepared.table.isCounter())
+            throw invalidRequest(NO_COUNTERS_IN_TXNS_MESSAGE, "SELECT", 
prepared.source);
+        if (prepared.hasAggregation())
+            throw invalidRequest(NO_AGGREGATION_IN_TXNS_MESSAGE, "SELECT", 
prepared.source);
+
+        if (prepared.getRestrictions().keyIsInRelation())
+            checkTrue(prepared.getLimit(null) == DataLimits.NO_LIMIT, 
NO_PARTITION_IN_CLAUSE_WITH_LIMIT, "SELECT", prepared.source);
+    }
+
     public static class Parsed extends QualifiedStatement.Composite
     {
         private final List<SelectStatement.RawStatement> assignments;
@@ -514,15 +544,10 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
                 checkNotNull(select.parameters.refName, "Assignments must be 
named");
                 TxnDataName name = TxnDataName.user(select.parameters.refName);
                 checkTrue(selectNames.add(name), DUPLICATE_TUPLE_NAME_MESSAGE, 
name.name());
+                validate(select);
 
                 SelectStatement prepared = select.prepare(bindVariables);
-
-                if (!prepared.table.isAccordEnabled())
-                    throw 
invalidRequest(TRANSACTIONS_DISABLED_ON_TABLE_MESSAGE, "SELECT", 
prepared.source);
-                if (prepared.table.params.pendingDrop)
-                    throw 
invalidRequest(TRANSACTIONS_DISABLED_ON_TABLE_BEING_DROPPED_MESSAGE, "SELECT", 
prepared.source);
-                if (prepared.table.isCounter())
-                    throw invalidRequest(NO_COUNTERS_IN_TXNS_MESSAGE, 
"SELECT", prepared.source);
+                validate(prepared);
 
                 NamedSelect namedSelect = new NamedSelect(name, prepared);
                 checkAtMostOneRowSpecified(namedSelect.select, "LET assignment 
" + name.name());
@@ -537,15 +562,9 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
             NamedSelect returningSelect = null;
             if (select != null)
             {
+                validate(select);
                 SelectStatement prepared = select.prepare(bindVariables);
-
-                if (!prepared.table.isAccordEnabled())
-                    throw 
invalidRequest(TRANSACTIONS_DISABLED_ON_TABLE_MESSAGE, "SELECT", 
prepared.source);
-                if (prepared.table.params.pendingDrop)
-                    throw 
invalidRequest(TRANSACTIONS_DISABLED_ON_TABLE_BEING_DROPPED_MESSAGE, "SELECT", 
prepared.source);
-                if (prepared.table.isCounter())
-                    throw invalidRequest(NO_COUNTERS_IN_TXNS_MESSAGE, 
"SELECT", prepared.source);
-
+                validate(prepared);
                 returningSelect = new NamedSelect(TxnDataName.returning(), 
prepared);
                 checkAtMostOnePartitionSpecified(returningSelect.select, 
"returning select");
             }
@@ -572,6 +591,7 @@ public class TransactionStatement implements 
CQLStatement.CompositeCQLStatement,
                 checkFalse(prepared.metadata().params.pendingDrop, 
TRANSACTIONS_DISABLED_ON_TABLE_BEING_DROPPED_MESSAGE, prepared.type, 
prepared.source);
                 checkFalse(prepared.hasConditions(), 
NO_CONDITIONS_IN_UPDATES_MESSAGE, prepared.type, prepared.source);
                 checkFalse(prepared.isTimestampSet(), 
NO_TIMESTAMPS_IN_UPDATES_MESSAGE, prepared.type, prepared.source);
+                checkFalse(prepared.attrs.isTimeToLiveSet(), 
NO_TTLS_IN_UPDATES_MESSAGE, prepared.type, prepared.source);
 
                 if (prepared.metadata().isCounter())
                     throw invalidRequest(NO_COUNTERS_IN_TXNS_MESSAGE, 
prepared.type, prepared.source);
diff --git a/test/unit/org/apache/cassandra/cql3/ast/Conditional.java 
b/test/unit/org/apache/cassandra/cql3/ast/Conditional.java
index ffce162450..4cc0026f97 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/Conditional.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/Conditional.java
@@ -62,6 +62,43 @@ public interface Conditional extends Expression
         }
     }
 
+    class Is implements Conditional
+    {
+        public enum Kind
+        {
+            Null("NULL"),
+            NotNull("NOT NULL");
+
+            private final String cql;
+
+            Kind(String s)
+            {
+                this.cql = s;
+            }
+        }
+
+        public final Kind kind;
+        public final Reference reference;
+
+        public Is(String symbol, Kind kind)
+        {
+            this(Reference.of(Symbol.unknownType(symbol)), kind);
+        }
+
+        public Is(Reference reference, Kind kind)
+        {
+            this.kind = kind;
+            this.reference = reference;
+        }
+
+        @Override
+        public void toCQL(StringBuilder sb, int indent)
+        {
+            reference.toCQL(sb, indent);
+            sb.append(" IS ").append(kind.cql);
+        }
+    }
+
     class Builder
     {
         private final List<Conditional> sub = new ArrayList<>();
diff --git a/test/unit/org/apache/cassandra/cql3/ast/Mutation.java 
b/test/unit/org/apache/cassandra/cql3/ast/Mutation.java
index e9134ddee7..68ed84db49 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/Mutation.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/Mutation.java
@@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -35,6 +36,7 @@ import java.util.stream.StreamSupport;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
+import org.apache.cassandra.db.marshal.Int32Type;
 import org.apache.cassandra.schema.ColumnMetadata;
 import org.apache.cassandra.schema.TableMetadata;
 
@@ -103,6 +105,11 @@ public class Mutation implements Statement
         this.casCondition = casCondition;
     }
 
+    public static Builder builder(TableMetadata table)
+    {
+        return new Builder(table);
+    }
+
     @Override
     public Statement.Kind kind()
     {
@@ -355,4 +362,50 @@ WHERE PK_column_conditions
     {
         return StreamSupport.stream(columns.spliterator(), false).map(m -> new 
Symbol(m)).collect(Collectors.toSet());
     }
+
+    public static class Builder
+    {
+        private final TableMetadata metadata;
+        private Kind kind;
+        private Map<Symbol, Expression> values = new LinkedHashMap<>();
+        private OptionalInt ttl = OptionalInt.empty();
+        private OptionalLong timestamp = OptionalLong.empty();
+        private Optional<? extends CasCondition> casCondition = 
Optional.empty();
+
+        public Builder(TableMetadata metadata)
+        {
+            this.metadata = metadata;
+        }
+
+        public Builder kind(Kind kind)
+        {
+            this.kind = kind;
+            return this;
+        }
+
+        public Builder value(String col, int value)
+        {
+            return value(new Symbol(col, Int32Type.instance), value);
+        }
+
+        public Builder value(Symbol symbol, Object value)
+        {
+            values.put(symbol, new Literal(value, symbol.type()));
+            return this;
+        }
+
+        public Builder ttl(int ttl)
+        {
+            this.ttl = OptionalInt.of(ttl);
+            return this;
+        }
+
+        public Mutation build()
+        {
+            if (kind == null)
+                throw new IllegalStateException("Kind is not defined, must 
call kind method before build");
+            // don't need to check values as Mutation does
+            return new Mutation(kind, metadata, new LinkedHashMap<>(values), 
ttl, timestamp, casCondition);
+        }
+    }
 }
diff --git a/test/unit/org/apache/cassandra/cql3/ast/Select.java 
b/test/unit/org/apache/cassandra/cql3/ast/Select.java
index 9ac541ee74..806f27f187 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/Select.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/Select.java
@@ -23,6 +23,8 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import javax.annotation.Nullable;
@@ -343,6 +345,12 @@ FROM [keyspace_name.] table_name
             return this;
         }
 
+        public Builder withIn(String name, int... values)
+        {
+            where.in(new Symbol(name, Int32Type.instance), 
IntStream.of(values).mapToObj(i -> new Literal(i, 
Int32Type.instance)).collect(Collectors.toList()));
+            return this;
+        }
+
         /**
          * When the column type/value type isn't known, this will fall back to 
byte type
          */
diff --git a/test/unit/org/apache/cassandra/cql3/ast/Txn.java 
b/test/unit/org/apache/cassandra/cql3/ast/Txn.java
index 3ce90f5ce8..1e4097d07a 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/Txn.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/Txn.java
@@ -19,6 +19,7 @@
 package org.apache.cassandra.cql3.ast;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -244,6 +245,16 @@ public class Txn implements Statement
             return this;
         }
 
+        public Builder addIf(Conditional conditional, Mutation... mutations)
+        {
+            return addIf(conditional, Arrays.asList(mutations));
+        }
+
+        public Builder addIf(Conditional conditional, List<Mutation> mutations)
+        {
+            return addIf(new If(conditional, mutations));
+        }
+
         public Builder addUpdate(Mutation mutation)
         {
             this.mutations.add(Objects.requireNonNull(mutation));
diff --git 
a/test/unit/org/apache/cassandra/cql3/statements/TransactionStatementTest.java 
b/test/unit/org/apache/cassandra/cql3/statements/TransactionStatementTest.java
index da7d9213e8..c7df283216 100644
--- 
a/test/unit/org/apache/cassandra/cql3/statements/TransactionStatementTest.java
+++ 
b/test/unit/org/apache/cassandra/cql3/statements/TransactionStatementTest.java
@@ -24,23 +24,37 @@ import org.junit.Test;
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.cql3.CQLStatement;
 import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.ast.Conditional.Is;
+import org.apache.cassandra.cql3.ast.FunctionCall;
+import org.apache.cassandra.cql3.ast.Mutation;
+import org.apache.cassandra.cql3.ast.Select;
+import org.apache.cassandra.cql3.ast.Txn;
+import org.apache.cassandra.cql3.ast.Where;
+import org.apache.cassandra.db.Keyspace;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.exceptions.SyntaxException;
 import org.apache.cassandra.schema.KeyspaceParams;
 import org.apache.cassandra.schema.TableId;
+import org.apache.cassandra.schema.TableMetadata;
 import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.transport.Dispatcher;
 import org.apache.cassandra.transport.messages.ResultMessage;
 import org.assertj.core.api.Assertions;
 
+import static org.apache.cassandra.cql3.ast.Where.Inequalities.EQUAL;
 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.ILLEGAL_RANGE_QUERY_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.INCOMPLETE_PARTITION_KEY_SELECT_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE;
+import static 
org.apache.cassandra.cql3.statements.TransactionStatement.NO_AGGREGATION_IN_TXNS_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.NO_CONDITIONS_IN_UPDATES_MESSAGE;
+import static 
org.apache.cassandra.cql3.statements.TransactionStatement.NO_GROUP_BY_IN_TXNS_MESSAGE;
+import static 
org.apache.cassandra.cql3.statements.TransactionStatement.NO_ORDER_BY_IN_TXNS_MESSAGE;
+import static 
org.apache.cassandra.cql3.statements.TransactionStatement.NO_PARTITION_IN_CLAUSE_WITH_LIMIT;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.NO_TIMESTAMPS_IN_UPDATES_MESSAGE;
+import static 
org.apache.cassandra.cql3.statements.TransactionStatement.NO_TTLS_IN_UPDATES_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.SELECT_REFS_NEED_COLUMN_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.TransactionStatement.TRANSACTIONS_DISABLED_ON_TABLE_MESSAGE;
 import static 
org.apache.cassandra.cql3.statements.UpdateStatement.CANNOT_SET_KEY_WITH_REFERENCE_MESSAGE;
@@ -58,6 +72,7 @@ public class TransactionStatementTest
     private static final TableId TABLE4_ID = 
TableId.fromString("00000000-0000-0000-0000-000000000004");
     private static final TableId TABLE5_ID = 
TableId.fromString("00000000-0000-0000-0000-000000000005");
     private static final TableId TABLE6_ID = 
TableId.fromString("00000000-0000-0000-0000-000000000006");
+    private static final TableId TABLE7_ID = 
TableId.fromString("00000000-0000-0000-0000-000000000007");
 
     @BeforeClass
     public static void beforeClass() throws Exception
@@ -69,7 +84,18 @@ public class TransactionStatementTest
                                     parse("CREATE TABLE tbl3 (k int PRIMARY 
KEY, \"with spaces\" int, \"with\"\"quote\" int, \"MiXeD_CaSe\" int) WITH 
transactional_mode = 'full'", "ks").id(TABLE3_ID),
                                     parse("CREATE TABLE tbl4 (k int PRIMARY 
KEY, int_list list<int>) WITH transactional_mode = 'full'", "ks").id(TABLE4_ID),
                                     parse("CREATE TABLE tbl5 (k int PRIMARY 
KEY, v int) WITH transactional_mode = 'full'", "ks").id(TABLE5_ID),
-                                    parse("CREATE TABLE tbl6 (k int PRIMARY 
KEY, v int) WITH transactional_mode = 'off'", "ks").id(TABLE6_ID));
+                                    parse("CREATE TABLE tbl6 (k int PRIMARY 
KEY, v int) WITH transactional_mode = 'off'", "ks").id(TABLE6_ID),
+                                    parse("CREATE TABLE tbl7 (k int PRIMARY 
KEY, v vector<float, 1>) WITH transactional_mode = 'full'", 
"ks").id(TABLE7_ID));
+    }
+
+    private static TableMetadata tbl(int num)
+    {
+        return Keyspace.open("ks").getColumnFamilyStore("tbl" + 
num).metadata();
+    }
+
+    private static TableMetadata tbl5()
+    {
+        return tbl(5);
     }
 
     @Test
@@ -342,6 +368,121 @@ public class TransactionStatementTest
                   
.hasMessageContaining(String.format(ILLEGAL_RANGE_QUERY_MESSAGE, "LET 
assignment row1", "at [2:15]"));
     }
 
+    @Test
+    public void shouldRejectTTL()
+    {
+        Mutation.Builder builder = Mutation.builder(tbl5())
+                                           .value("k", 1)
+                                           .value("v", 2)
+                                           .ttl(42);
+        for (Mutation.Kind kind : Mutation.Kind.values())
+        {
+            if (kind == Mutation.Kind.DELETE) continue; // deletes don't 
support TTL
+            Mutation mutation = builder.kind(kind).build();
+            String query = Txn.wrap(mutation).toCQL();
+            Assertions.assertThatThrownBy(() -> prepare(query))
+                      .isInstanceOf(InvalidRequestException.class)
+                      
.hasMessageContaining(String.format(NO_TTLS_IN_UPDATES_MESSAGE, kind.name(), 
"at"));
+
+            var txn = Txn.builder()
+                         .addLet("a", Select.builder()
+                                            .withTable(tbl5())
+                                            .withWhere("k", 
Where.Inequalities.EQUAL, 1)
+                                            .build())
+                         .addIf(new Is("a", Is.Kind.Null), mutation)
+                         .build();
+            Assertions.assertThatThrownBy(() -> prepare(txn.toCQL()))
+                      .isInstanceOf(InvalidRequestException.class)
+                      
.hasMessageContaining(String.format(NO_TTLS_IN_UPDATES_MESSAGE, kind.name(), 
"at"));
+        }
+    }
+
+    @Test
+    public void shouldRejectAggFunctions()
+    {
+        var select = Select.builder()
+                           .withSelection(FunctionCall.count("v"))
+                           .withTable(tbl5())
+                           .withWhere("k", EQUAL, 0)
+                           .build();
+
+        Assertions.assertThatThrownBy(() -> prepare(Txn.wrap(select).toCQL()))
+                  .isInstanceOf(InvalidRequestException.class)
+                  
.hasMessageContaining(String.format(NO_AGGREGATION_IN_TXNS_MESSAGE, "SELECT", 
"at"));
+
+        var txn = Txn.builder()
+                     .addLet("a", select)
+                     .addReturnReferences("a.count")
+                     .build();
+
+        Assertions.assertThatThrownBy(() -> prepare(txn.toCQL()))
+                  .isInstanceOf(InvalidRequestException.class)
+                  
.hasMessageContaining(String.format(NO_AGGREGATION_IN_TXNS_MESSAGE, "SELECT", 
"at"));
+    }
+
+    @Test
+    public void shouldRejectOrderBy()
+    {
+        String query = "BEGIN TRANSACTION\n" +
+                       "  SELECT * FROM ks.tbl7 WHERE k=0 ORDER BY v ANN OF 
[42] LIMIT 1;" +
+                       "COMMIT TRANSACTION;";
+        Assertions.assertThatThrownBy(() -> prepare(query))
+                  .isInstanceOf(InvalidRequestException.class)
+                  
.hasMessageContaining(String.format(NO_ORDER_BY_IN_TXNS_MESSAGE, "SELECT", 
"at"));
+
+        // The below code is left commented out as a reminder to think about 
this case... As of this writing ORDER BY does not parse in a LET clause... if 
that is ever fixed we should block it right away!
+//        String query2 = "BEGIN TRANSACTION\n" +
+//                        "  LET a = (SELECT * FROM ks.tbl7 WHERE k=0 ORDER BY 
v ANN OF [42] LIMIT 1;)" +
+//                        "  SELECT a.v" +
+//                        "COMMIT TRANSACTION;";
+//        Assertions.assertThatThrownBy(() -> prepare(query2))
+//                  .isInstanceOf(InvalidRequestException.class)
+//                  
.hasMessageContaining(String.format(NO_ORDER_BY_IN_TXNS_MESSAGE, "SELECT", 
"at"));
+    }
+
+    @Test
+    public void shouldRejectGroupBy()
+    {
+        String query = "BEGIN TRANSACTION\n" +
+                       "  SELECT * FROM ks.tbl1 WHERE k=0 GROUP BY c LIMIT 1;" 
+
+                       "COMMIT TRANSACTION;";
+        Assertions.assertThatThrownBy(() -> prepare(query))
+                  .isInstanceOf(InvalidRequestException.class)
+                  
.hasMessageContaining(String.format(NO_GROUP_BY_IN_TXNS_MESSAGE, "SELECT", 
"at"));
+
+        // The below code is left commented out as a reminder to think about 
this case... As of this writing GROUP BY does not parse in a LET clause... if 
that is ever fixed we should block it right away!
+//        String query2 = "BEGIN TRANSACTION\n" +
+//                        "  LET a = (SELECT * FROM ks.tbl1 WHERE k=0 GROUP BY 
c LIMIT 1;)" +
+//                        "  SELECT a.v" +
+//                        "COMMIT TRANSACTION;";
+//        Assertions.assertThatThrownBy(() -> prepare(query2))
+//                  .isInstanceOf(InvalidRequestException.class)
+//                  
.hasMessageContaining(String.format(NO_GROUP_BY_IN_TXNS_MESSAGE, "SELECT", 
"at"));
+    }
+
+    @Test
+    public void shouldRejectInClauseInLet()
+    {
+        // this is blocked not because this isn't safe, but that the logic to 
handle this is currently in the read coordinator, which Accord doesn't call.
+        // So rather than return bad results to users, IN w/ LIMIT is 
blocked... until we can fix
+        var select = Select.builder()
+                .withTable(tbl(1))
+                .withIn("k", 0, 1)
+                .withLimit(1)
+                .build();
+
+        Assertions.assertThatThrownBy(() -> prepare(Txn.wrap(select).toCQL()))
+                .isInstanceOf(InvalidRequestException.class)
+                
.hasMessageContaining(String.format(NO_PARTITION_IN_CLAUSE_WITH_LIMIT, 
"SELECT", "at"));
+
+        Assertions.assertThatThrownBy(() -> prepare(Txn.builder()
+                        .addLet("a", select)
+                        .addReturnReferences("a.k")
+                        .build().toCQL()))
+                .isInstanceOf(InvalidRequestException.class)
+                
.hasMessageContaining(String.format(NO_PARTITION_IN_CLAUSE_WITH_LIMIT, 
"SELECT", "at"));
+    }
+
     @Test
     public void shouldRejectLetSelectOnNonTransactionalTable()
     {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to