This is an automated email from the ASF dual-hosted git repository.
dcapwell pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new ea4ff0e966 Expand TableWalk tests to include collections and add
support for += and -= for these types
ea4ff0e966 is described below
commit ea4ff0e9663f8de01a56ac6b9e9ffc2bc363dc5e
Author: David Capwell <[email protected]>
AuthorDate: Fri Mar 21 11:45:33 2025 -0700
Expand TableWalk tests to include collections and add support for += and -=
for these types
patch by David Capwell; reviewed by Abe Ratnofsky for CASSANDRA-20460
---
.../test/cql3/SingleNodeTableWalkTest.java | 70 ++++++++--
.../cassandra/harry/model/ASTSingleTableModel.java | 125 ++++++++++++++++-
.../harry/model/ASTSingleTableModelTest.java | 128 ++++++++++++++++-
.../cassandra/harry/model/BytesPartitionState.java | 27 +++-
.../unit/org/apache/cassandra/cql3/KnownIssue.java | 2 +
.../cassandra/cql3/ast/AssignmentOperator.java | 31 ++---
.../org/apache/cassandra/cql3/ast/Conditional.java | 2 +-
.../apache/cassandra/cql3/ast/CreateIndexDDL.java | 2 +-
.../cassandra/cql3/ast/ExpressionEvaluator.java | 154 ++++++++++++---------
.../org/apache/cassandra/cql3/ast/Mutation.java | 6 +
.../unit/org/apache/cassandra/cql3/ast/Select.java | 7 +
.../cassandra/cql3/ast/StandardVisitors.java | 2 +-
.../org/apache/cassandra/utils/ASTGenerators.java | 20 ++-
.../cassandra/utils/AbstractTypeGenerators.java | 78 ++++++++---
.../org/apache/cassandra/utils/Generators.java | 31 ++++-
.../cassandra/utils/ImmutableUniqueList.java | 24 +++-
16 files changed, 570 insertions(+), 139 deletions(-)
diff --git
a/test/distributed/org/apache/cassandra/distributed/test/cql3/SingleNodeTableWalkTest.java
b/test/distributed/org/apache/cassandra/distributed/test/cql3/SingleNodeTableWalkTest.java
index 2ba02ae769..755d479e92 100644
---
a/test/distributed/org/apache/cassandra/distributed/test/cql3/SingleNodeTableWalkTest.java
+++
b/test/distributed/org/apache/cassandra/distributed/test/cql3/SingleNodeTableWalkTest.java
@@ -66,10 +66,11 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ASTGenerators;
import org.apache.cassandra.utils.AbstractTypeGenerators;
import org.apache.cassandra.utils.AbstractTypeGenerators.TypeGenBuilder;
+import org.apache.cassandra.utils.AbstractTypeGenerators.TypeKind;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.CassandraGenerators.TableMetadataBuilder;
+import org.apache.cassandra.utils.Generators;
import org.apache.cassandra.utils.ImmutableUniqueList;
-import org.quicktheories.generators.SourceDSL;
import static accord.utils.Property.commands;
import static accord.utils.Property.stateful;
@@ -78,6 +79,17 @@ import static org.apache.cassandra.utils.Generators.toGen;
public class SingleNodeTableWalkTest extends StatefulASTBase
{
+ private static final Gen<Gen<Boolean>> BOOLEAN_DISTRIBUTION =
Gens.bools().mixedDistribution();
+ //TODO (coverage): COMPOSITE, DYNAMIC_COMPOSITE
+ private static final Gen<Gen<TypeKind>> TYPE_KIND_DISTRIBUTION =
Gens.mixedDistribution(TypeKind.PRIMITIVE,
+
TypeKind.SET, TypeKind.LIST, TypeKind.MAP,
+
TypeKind.TUPLE, TypeKind.UDT,
+
TypeKind.VECTOR
+ );
+ private static final Gen<Gen<AbstractType<?>>> PRIMITIVE_DISTRIBUTION =
Gens.mixedDistribution(AbstractTypeGenerators.knownPrimitiveTypes()
+
.stream()
+
.filter(t ->
!AbstractTypeGenerators.isUnsafeEquality(t))
+
.collect(Collectors.toList()));
private static final Logger logger =
LoggerFactory.getLogger(SingleNodeTableWalkTest.class);
protected void preCheck(Cluster cluster, Property.StatefulBuilder builder)
@@ -88,10 +100,20 @@ public class SingleNodeTableWalkTest extends
StatefulASTBase
// CQL_DEBUG_APPLY_OPERATOR = true;
}
- protected TypeGenBuilder supportedTypes()
+ protected TypeGenBuilder supportedTypes(RandomSource rs)
{
- return
AbstractTypeGenerators.withoutUnsafeEquality(AbstractTypeGenerators.builder()
-
.withTypeKinds(AbstractTypeGenerators.TypeKind.PRIMITIVE));
+ return AbstractTypeGenerators.builder()
+
.withTypeKinds(Generators.fromGen(TYPE_KIND_DISTRIBUTION.next(rs)))
+
.withPrimitives(Generators.fromGen(PRIMITIVE_DISTRIBUTION.next(rs)))
+
.withUserTypeFields(AbstractTypeGenerators.UserTypeFieldsGen.simpleNames())
+ .withMaxDepth(1);
+ }
+
+ protected TypeGenBuilder supportedPrimaryColumnTypes(RandomSource rs)
+ {
+ return AbstractTypeGenerators.builder()
+ .withTypeKinds(TypeKind.PRIMITIVE)
+
.withPrimitives(Generators.fromGen(PRIMITIVE_DISTRIBUTION.next(rs)));
}
protected List<CreateIndexDDL.Indexer> supportedIndexers()
@@ -206,7 +228,7 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
builder.value(pk, key.bufferAt(pks.indexOf(pk)));
- List<Symbol> searchableColumns = state.nonPartitionColumns;
+ List<Symbol> searchableColumns = state.searchableNonPartitionColumns;
Symbol symbol = rs.pick(searchableColumns);
TreeMap<ByteBuffer, List<BytesPartitionState.PrimaryKey>> universe =
state.model.index(ref, symbol);
@@ -363,7 +385,8 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
.withCompression())
.withKeyspaceName(ks).withTableName("tbl")
.withSimpleColumnNames()
- .withDefaultTypeGen(supportedTypes())
+ .withDefaultTypeGen(supportedTypes(rs))
+ .withPrimaryColumnTypeGen(supportedPrimaryColumnTypes(rs))
.withPartitioner(Murmur3Partitioner.instance)
.build())
.next(rs);
@@ -393,7 +416,7 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
{
protected final LinkedHashMap<Symbol, IndexedColumn> indexes;
private final Gen<Mutation> mutationGen;
- private final List<Symbol> nonPartitionColumns;
+ private final List<Symbol> searchableNonPartitionColumns;
private final List<Symbol> searchableColumns;
private final List<Symbol> nonPkIndexedColumns;
@@ -424,7 +447,8 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
.withoutTransaction()
.withoutTtl()
.withoutTimestamp()
-
.withPartitions(SourceDSL.arbitrary().pick(uniquePartitions));
+
.withPartitions(Generators.fromGen(Gens.mixedDistribution(uniquePartitions).next(rs)))
+
.withColumnExpressions(e ->
e.withOperators(Generators.fromGen(BOOLEAN_DISTRIBUTION.next(rs))));
if (IGNORED_ISSUES.contains(KnownIssue.SAI_EMPTY_TYPE))
{
model.factory.regularAndStaticColumns.stream()
@@ -438,16 +462,30 @@ public class SingleNodeTableWalkTest extends
StatefulASTBase
}
this.mutationGen = toGen(mutationGenBuilder.build());
- nonPartitionColumns = ImmutableList.<Symbol>builder()
-
.addAll(model.factory.clusteringColumns)
-
.addAll(model.factory.staticColumns)
-
.addAll(model.factory.regularColumns)
- .build();
+ var nonPartitionColumns = ImmutableList.<Symbol>builder()
+
.addAll(model.factory.clusteringColumns)
+
.addAll(model.factory.staticColumns)
+
.addAll(model.factory.regularColumns)
+ .build();
+ searchableNonPartitionColumns = nonPartitionColumns.stream()
+
.filter(this::isSearchable)
+
.collect(Collectors.toList());
nonPkIndexedColumns = nonPartitionColumns.stream()
.filter(indexes::containsKey)
.collect(Collectors.toList());
- searchableColumns = metadata.partitionKeyColumns().size() > 1 ?
model.factory.selectionOrder : nonPartitionColumns;
+ searchableColumns = (metadata.partitionKeyColumns().size() > 1 ?
model.factory.selectionOrder : nonPartitionColumns)
+ .stream()
+ .filter(this::isSearchable)
+ .collect(Collectors.toList());
+ }
+
+ private boolean isSearchable(Symbol symbol)
+ {
+ // See org.apache.cassandra.cql3.Operator.validateFor
+ // multi cell collections can only be searched if you search their
elements, not the collection as a whole
+ //TODO (coverage): can you query for UDT fields? its a single
cell so you "should"?
+ return !(symbol.type().isMultiCell() &&
(symbol.type().isCollection() || symbol.type().isUDT()));
}
@Override
@@ -523,6 +561,8 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
List<Symbol> allowedColumns = searchableColumns;
if (hasMultiNodeMultiColumnAllowFilteringWithLocalWritesIssue())
allowedColumns = nonPkIndexedColumns;
+ if (IGNORED_ISSUES.contains(KnownIssue.SAI_AND_VECTOR_COLUMNS) &&
!indexes.isEmpty())
+ allowedColumns = allowedColumns.stream().filter(s ->
!s.type().isVector()).collect(Collectors.toList());
return allowedColumns;
}
@@ -533,7 +573,7 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
public boolean allowPartitionQuery()
{
- return !(model.isEmpty() || nonPartitionColumns.isEmpty());
+ return !(model.isEmpty() ||
searchableNonPartitionColumns.isEmpty());
}
@Override
diff --git
a/test/harry/main/org/apache/cassandra/harry/model/ASTSingleTableModel.java
b/test/harry/main/org/apache/cassandra/harry/model/ASTSingleTableModel.java
index 4c0a5f5cf5..d2fbb6edcc 100644
--- a/test/harry/main/org/apache/cassandra/harry/model/ASTSingleTableModel.java
+++ b/test/harry/main/org/apache/cassandra/harry/model/ASTSingleTableModel.java
@@ -21,6 +21,7 @@ package org.apache.cassandra.harry.model;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -43,13 +44,16 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import accord.utils.Invariants;
+import org.apache.cassandra.cql3.ast.AssignmentOperator;
import org.apache.cassandra.cql3.ast.Conditional;
import org.apache.cassandra.cql3.ast.Conditional.Where.Inequality;
import org.apache.cassandra.cql3.ast.Element;
import org.apache.cassandra.cql3.ast.Expression;
import org.apache.cassandra.cql3.ast.ExpressionEvaluator;
import org.apache.cassandra.cql3.ast.FunctionCall;
+import org.apache.cassandra.cql3.ast.Literal;
import org.apache.cassandra.cql3.ast.Mutation;
+import org.apache.cassandra.cql3.ast.Operator;
import org.apache.cassandra.cql3.ast.Select;
import org.apache.cassandra.cql3.ast.StandardVisitors;
import org.apache.cassandra.cql3.ast.Symbol;
@@ -244,7 +248,10 @@ public class ASTSingleTableModel
// static columns to add in. If we are doing something like
+= to a row that doesn't exist, we still update statics...
Map<Symbol, ByteBuffer> write = new HashMap<>();
for (Symbol col :
Sets.intersection(factory.staticColumns.asSet(), set.keySet()))
- write.put(col, eval(set.get(col)));
+ {
+ ByteBuffer current = partition.staticRow().get(col);
+ write.put(col, eval(col, current, set.get(col)));
+ }
partition.setStaticColumns(write);
}
// table has clustering but non are in the write, so only
pk/static can be updated
@@ -254,7 +261,10 @@ public class ASTSingleTableModel
{
Map<Symbol, ByteBuffer> write = new HashMap<>();
for (Symbol col :
Sets.intersection(factory.regularColumns.asSet(), set.keySet()))
- write.put(col, eval(set.get(col)));
+ {
+ ByteBuffer current = partition.get(cd, col);
+ write.put(col, eval(col, current, set.get(col)));
+ }
partition.setColumns(cd, write, false);
}
@@ -493,6 +503,45 @@ public class ASTSingleTableModel
if (actual.isEmpty()) sb.append("No rows returned");
else sb.append("Missing rows:\n").append(table(columns, missing));
}
+ if (!unexpected.isEmpty() && unexpected.size() == missing.size())
+ {
+ // good chance a column differs
+ StringBuilder finalSb = sb;
+ Runnable runOnce = new Runnable()
+ {
+ boolean ran = false;
+ @Override
+ public void run()
+ {
+ if (ran) return;
+ finalSb.append("\nPossible column conflicts:");
+ ran = true;
+ }
+ };
+ for (var e : missing)
+ {
+ Row smallest = null;
+ BitSet smallestDiff = null;
+ for (var a : unexpected)
+ {
+ BitSet diff = e.diff(a);
+ if (smallestDiff == null || diff.cardinality() <
smallestDiff.cardinality())
+ {
+ smallest = a;
+ smallestDiff = diff;
+ }
+ }
+ // if every column differs then ignore
+ if (smallestDiff.cardinality() == e.values.length)
+ continue;
+ runOnce.run();
+ sb.append("\n\tExpected: ").append(e);
+ sb.append("\n\tDiff (expected over actual):\n");
+ Row eSmall = e.select(smallestDiff);
+ Row aSmall = smallest.select(smallestDiff);
+ sb.append(table(eSmall.columns, Arrays.asList(eSmall,
aSmall)));
+ }
+ }
if (sb != null)
{
sb.append("\nExpected:\n").append(table(columns, expected));
@@ -731,7 +780,7 @@ public class ASTSingleTableModel
for (Expression e : conditions)
{
ByteBuffer expected = eval(e);
- if (expected.equals(value))
+ if (expected != null && expected.equals(value))
return true;
}
return false;
@@ -893,13 +942,41 @@ public class ASTSingleTableModel
return
current.stream().map(BufferClustering::new).collect(Collectors.toList());
}
+ private static ByteBuffer eval(Symbol col, @Nullable ByteBuffer current,
Expression e)
+ {
+ if (!(e instanceof AssignmentOperator)) return eval(e);
+ // multi cell collections have the property that they do update even
if the current value is null
+ boolean isFancy = col.type().isCollection() &&
col.type().isMultiCell();
+ if (current == null && !isFancy) return null; // null + ? == null
+ var assignment = (AssignmentOperator) e;
+ if (isFancy && current == null)
+ {
+ return assignment.kind == AssignmentOperator.Kind.SUBTRACT
+ // if it doesn't exist, then there is nothing to subtract
+ ? null
+ : eval(assignment.right);
+ }
+ switch (assignment.kind)
+ {
+ case ADD:
+ return eval(new Operator(Operator.Kind.ADD, new
Literal(current, e.type()), assignment.right));
+ case SUBTRACT:
+ return eval(new Operator(Operator.Kind.SUBTRACT, new
Literal(current, e.type()), assignment.right));
+ default:
+ throw new UnsupportedOperationException(assignment.kind + ": "
+ assignment.toCQL());
+ }
+ }
+
+ @Nullable
private static ByteBuffer eval(Expression e)
{
- return ExpressionEvaluator.tryEvalEncoded(e).get();
+ return ExpressionEvaluator.evalEncoded(e);
}
private static class Row
{
+ private static final Row EMPTY = new Row(ImmutableUniqueList.empty(),
ByteBufferUtil.EMPTY_ARRAY);
+
private final ImmutableUniqueList<Symbol> columns;
private final ByteBuffer[] values;
@@ -907,6 +984,8 @@ public class ASTSingleTableModel
{
this.columns = columns;
this.values = values;
+ if (columns.size() != values.length)
+ throw new IllegalArgumentException("Columns " + columns + "
should have the same size as values, but had " + values.length);
}
public String asCQL(Symbol symbol)
@@ -914,7 +993,9 @@ public class ASTSingleTableModel
int offset = columns.indexOf(symbol);
assert offset >= 0;
ByteBuffer b = values[offset];
- return (b == null || ByteBufferUtil.EMPTY_BYTE_BUFFER.equals(b)) ?
"null" : symbol.type().asCQL3Type().toCQLLiteral(b);
+ if (b == null) return "null";
+ if (ByteBufferUtil.EMPTY_BYTE_BUFFER.equals(b)) return "<empty>";
+ return symbol.type().asCQL3Type().toCQLLiteral(b);
}
public List<String> asCQL()
@@ -925,6 +1006,40 @@ public class ASTSingleTableModel
return human;
}
+ public BitSet diff(Row other)
+ {
+ if (!columns.equals(other.columns))
+ throw new UnsupportedOperationException("Columns do not match:
expected " + columns + " but given " + other.columns);
+ int maxLength = Math.max(values.length, other.values.length);
+ int minLength = Math.min(values.length, other.values.length);
+ BitSet set = new BitSet(maxLength);
+ for (int i = 0; i < minLength; i++)
+ {
+ ByteBuffer a = values[i];
+ ByteBuffer b = other.values[i];
+ if (!Objects.equals(a, b))
+ set.set(i);
+ }
+ for (int i = minLength; i < maxLength; i++)
+ set.set(i);
+ return set;
+ }
+
+ public Row select(BitSet selection)
+ {
+ if (selection.isEmpty()) return EMPTY;
+ var names =
ImmutableUniqueList.<Symbol>builder(selection.cardinality());
+ ByteBuffer[] copy = new ByteBuffer[selection.cardinality()];
+ int offset = 0;
+ for (int i = 0; i < this.values.length; i++)
+ {
+ if (!selection.get(i)) continue;
+ names.add(this.columns.get(i));
+ copy[offset++] = this.values[i];
+ }
+ return new Row(names.build(), copy);
+ }
+
@Override
public boolean equals(Object o)
{
diff --git
a/test/harry/main/org/apache/cassandra/harry/model/ASTSingleTableModelTest.java
b/test/harry/main/org/apache/cassandra/harry/model/ASTSingleTableModelTest.java
index 16f4d66819..a04425f827 100644
---
a/test/harry/main/org/apache/cassandra/harry/model/ASTSingleTableModelTest.java
+++
b/test/harry/main/org/apache/cassandra/harry/model/ASTSingleTableModelTest.java
@@ -23,6 +23,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -34,9 +36,11 @@ import java.util.stream.Stream;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
+import org.apache.cassandra.cql3.ast.AssignmentOperator;
import org.apache.cassandra.cql3.ast.Bind;
import org.apache.cassandra.cql3.ast.Conditional.Where.Inequality;
import org.apache.cassandra.cql3.ast.FunctionCall;
+import org.apache.cassandra.cql3.ast.Literal;
import org.apache.cassandra.cql3.ast.Mutation;
import org.apache.cassandra.cql3.ast.Select;
import org.apache.cassandra.cql3.ast.Symbol;
@@ -48,7 +52,10 @@ import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.marshal.InetAddressType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.LexicalUUIDType;
+import org.apache.cassandra.db.marshal.ListType;
+import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.ReversedType;
+import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.marshal.ShortType;
import org.apache.cassandra.db.marshal.TimestampType;
import org.apache.cassandra.dht.Murmur3Partitioner;
@@ -67,6 +74,9 @@ public class ASTSingleTableModelTest
private static final EnumSet<Inequality> RANGE_INEQUALITY =
EnumSet.of(Inequality.LESS_THAN, Inequality.LESS_THAN_EQ,
Inequality.GREATER_THAN, Inequality.GREATER_THAN_EQ);
+ public static final ListType<Integer> LIST_INT =
ListType.getInstance(Int32Type.instance, true);
+ public static final SetType<Integer> SET_INT =
SetType.getInstance(Int32Type.instance, true);
+ public static final MapType<Integer, Integer> MAP_INT =
MapType.getInstance(Int32Type.instance, Int32Type.instance, true);
@Test
public void singlePartition()
@@ -296,12 +306,12 @@ public class ASTSingleTableModelTest
String pk0 = "'e44b:bdaf:aeb:f68b:1cff:ecbd:8b54:2295'";
ByteBuffer pk0BB =
InetAddressType.instance.asCQL3Type().fromCQLLiteral(pk0);
- Short row1 = Short.valueOf((short) -14407);
+ Short row1 = (short) -14407;
ByteBuffer row1BB = ShortType.instance.decompose(row1);
String row1V1 = "0x00000000000049008a00000000000000";
ByteBuffer row1V1BB =
LexicalUUIDType.instance.asCQL3Type().fromCQLLiteral(row1V1);
- Short row2 = Short.valueOf((short) ((short) 18175 - (short) 23847));
+ Short row2 = (short) ((short) 18175 - (short) 23847);
ByteBuffer row2BB = ShortType.instance.decompose(row2);
String row2V0 = "'1989-01-11T15:00:30.950Z'";
ByteBuffer row2V0BB =
TimestampType.instance.asCQL3Type().fromCQLLiteral(row2V0);
@@ -324,7 +334,7 @@ public class ASTSingleTableModelTest
.build());
model.validate(new ByteBuffer[][]{ new ByteBuffer[]{ pk0BB, row1BB,
null, row1V1BB } }, selectPk);
- model.validate(new ByteBuffer[0][], selectColumn);
+ model.validate(EMPTY, selectColumn);
model.update(Mutation.insert(metadata)
@@ -339,7 +349,7 @@ public class ASTSingleTableModelTest
new ByteBuffer[]{ pk0BB, row1BB, null, row1V1BB },
}, selectPk);
- model.validate(new ByteBuffer[0][], selectColumn);
+ model.validate(EMPTY, selectColumn);
}
@Test
@@ -565,6 +575,116 @@ public class ASTSingleTableModelTest
.build());
}
+ @Test
+ public void assignmentOperator()
+ {
+ // not testing if assignment / operators are "corrrect", other tests
can cover that
+ // the goal of this test is to test the plumbing and null handling
within the model
+ TableMetadata metadata = defaultTable()
+ .addPartitionKeyColumn("pk",
Int32Type.instance)
+ .addStaticColumn("s", Int32Type.instance)
+ .addRegularColumn("r", Int32Type.instance)
+ .build();
+ ASTSingleTableModel model = new ASTSingleTableModel(metadata);
+
+ // pk=0 doesn't exist, so s/r are null; so the operation should end
with a null... this shouldn't create the partition
+ model.update(Mutation.update(metadata)
+ .value("pk", 0)
+ .set("s", subtract(42))
+ .set("r", subtract(42))
+ .build());
+
+ model.validate(EMPTY, Select.builder(metadata).build());
+
+ model.update(Mutation.insert(metadata).value("pk", 0).value("s",
40).value("r", 40).build());
+ model.update(Mutation.update(metadata)
+ .value("pk", 0)
+ .set("s", subtract(42))
+ .set("r", subtract(42))
+ .build());
+
+ model.validate(rows(row(metadata, 0, -2, -2)),
Select.builder(metadata).build());
+ }
+
+ @Test
+ public void assignmentOperatorMultiCellCollections()
+ {
+ // not testing if assignment / operators are "corrrect", other tests
can cover that
+ // the goal of this test is to test the plumbing and null handling
within the model
+ TableMetadata metadata = defaultTable()
+ .addPartitionKeyColumn("pk",
Int32Type.instance)
+ .addStaticColumn("s0", LIST_INT)
+ .addStaticColumn("r0", LIST_INT)
+ .addStaticColumn("s1", SET_INT)
+ .addStaticColumn("r1", SET_INT)
+ .addStaticColumn("s2", MAP_INT)
+ .addStaticColumn("r2", MAP_INT)
+ .build();
+ ASTSingleTableModel model = new ASTSingleTableModel(metadata);
+
+ // pk=0 doesn't exist, so s/r are null; but these are multi cell
collections, so the update happens!
+ model.update(Mutation.update(metadata)
+ .value("pk", 0)
+ .set("s0", add(List.of(42)))
+ .set("r0", add(List.of(42)))
+ .set("s1", add(Set.of(42)))
+ .set("r1", add(Set.of(42)))
+ .set("s2", add(Map.of(42, 42)))
+ .set("r2", add(Map.of(42, 42)))
+ .build());
+
+ // Expected:
+ //pk | r0 | r1 | r2 | s0 | s1 | s2
+ //0 | [42] | {42} | {42: 42} | [42] | {42} | {42: 42}
+ model.validate(rows(row(metadata, 0, List.of(42), Set.of(42),
Map.of(42, 42), List.of(42), Set.of(42), Map.of(42, 42))),
Select.builder(metadata).build());
+
+ // add to existing
+ model.update(Mutation.update(metadata)
+ .value("pk", 0)
+ .set("s0", add(List.of(42)))
+ .set("r0", add(List.of(42)))
+ .set("s1", add(Set.of(0)))
+ .set("r1", add(Set.of(0)))
+ .set("s2", add(Map.of(42, 0)))
+ .set("r2", add(Map.of(42, 0)))
+ .build());
+ model.validate(rows(row(metadata, 0, List.of(42, 42), Set.of(0, 42),
Map.of(42, 0), List.of(42, 42), Set.of(0, 42), Map.of(42, 0))),
Select.builder(metadata).build());
+ }
+
+ private static ByteBuffer[][] rows(ByteBuffer[]... rows)
+ {
+ return rows;
+ }
+
+ private static ByteBuffer[] row(TableMetadata metadata, Object... values)
+ {
+ ByteBuffer[] row = new ByteBuffer[values.length];
+ var it = metadata.allColumnsInSelectOrder();
+ for (int i = 0; i < values.length && it.hasNext(); i++)
+ row[i] = it.next().type.decomposeUntyped(values[i]);
+ return row;
+ }
+
+ private static AssignmentOperator subtract(int value)
+ {
+ return new AssignmentOperator(AssignmentOperator.Kind.SUBTRACT,
Literal.of(value));
+ }
+
+ private static AssignmentOperator add(List<Integer> value)
+ {
+ return new AssignmentOperator(AssignmentOperator.Kind.ADD, new
Literal(value, LIST_INT));
+ }
+
+ private static AssignmentOperator add(Set<Integer> value)
+ {
+ return new AssignmentOperator(AssignmentOperator.Kind.ADD, new
Literal(value, SET_INT));
+ }
+
+ private static AssignmentOperator add(Map<Integer, Integer> value)
+ {
+ return new AssignmentOperator(AssignmentOperator.Kind.ADD, new
Literal(value, MAP_INT));
+ }
+
private static TableMetadata.Builder defaultTable()
{
return TableMetadata.builder("ks", "tbl")
diff --git
a/test/harry/main/org/apache/cassandra/harry/model/BytesPartitionState.java
b/test/harry/main/org/apache/cassandra/harry/model/BytesPartitionState.java
index 6b8f61259d..a10524968a 100644
--- a/test/harry/main/org/apache/cassandra/harry/model/BytesPartitionState.java
+++ b/test/harry/main/org/apache/cassandra/harry/model/BytesPartitionState.java
@@ -125,6 +125,12 @@ public class BytesPartitionState
long cd = factory.clusteringCache.deflate(clustering);
long[] vds = toDescriptor(factory.regularColumns, values);
state.writeRegular(cd, vds, MagicConstants.NO_TIMESTAMP,
writePrimaryKeyLiveness);
+
+ // UDT's have the ability to "update" that triggers a delete; this
allows creating an "empty" row.
+ // When an empty row exists without liveness info, then purge the row
+ var row = state.rows.get(cd);
+ if (row.isEmpty() && !row.hasPrimaryKeyLivenessInfo)
+ state.delete(cd, MagicConstants.NO_TIMESTAMP);
}
private long[] toDescriptor(ImmutableUniqueList<Symbol> positions,
Map<Symbol, ByteBuffer> values)
@@ -135,8 +141,14 @@ public class BytesPartitionState
Symbol column = positions.get(i);
if (values.containsKey(column))
{
- long vd = factory.valueCache.deflate(new Value(column.type(),
values.get(column)));
- vds[i] = vd;
+ ByteBuffer value = values.get(column);
+ // user type is the only multi cell type that allows <empty>
so this check should be fine; can expand if we find more cases
+ if (value == null || !value.hasRemaining() &&
(column.type().isUDT() && column.type().isMultiCell()))
+ {
+ vds[i] = MagicConstants.NIL_DESCR;
+ continue;
+ }
+ vds[i] = factory.valueCache.deflate(new Value(column.type(),
value));
}
else
{
@@ -197,6 +209,13 @@ public class BytesPartitionState
return toRow(rowState);
}
+ @Nullable
+ public ByteBuffer get(Clustering<ByteBuffer> clustering, Symbol column)
+ {
+ Row row = get(clustering);
+ return row == null ? null : row.get(column);
+ }
+
private Row toRow(PartitionState.RowState rowState)
{
Clustering<ByteBuffer> clustering;
@@ -548,8 +567,8 @@ public class BytesPartitionState
private Value(AbstractType<?> type, ByteBuffer value)
{
- this.type = type;
- this.value = value;
+ this.type = Objects.requireNonNull(type);
+ this.value = Objects.requireNonNull(value);
}
@Override
diff --git a/test/unit/org/apache/cassandra/cql3/KnownIssue.java
b/test/unit/org/apache/cassandra/cql3/KnownIssue.java
index a1924a91f9..b1c2c09d66 100644
--- a/test/unit/org/apache/cassandra/cql3/KnownIssue.java
+++ b/test/unit/org/apache/cassandra/cql3/KnownIssue.java
@@ -39,6 +39,8 @@ public enum KnownIssue
"Some types allow empty bytes, but define them as
meaningless. AF can be used to query them using <, <=, and =; but SAI can
not"),
AF_MULTI_NODE_MULTI_COLUMN_AND_NODE_LOCAL_WRITES("https://issues.apache.org/jira/browse/CASSANDRA-19007",
"When doing multi
node/multi column queries, AF can miss data when the nodes are not in-sync"),
+
SAI_AND_VECTOR_COLUMNS("https://issues.apache.org/jira/browse/CASSANDRA-20464",
+ "When doing an SAI query, if the where clause also
contains a vector column bad results can be produced")
;
KnownIssue(String url, String description)
diff --git a/test/unit/org/apache/cassandra/cql3/ast/AssignmentOperator.java
b/test/unit/org/apache/cassandra/cql3/ast/AssignmentOperator.java
index f72fceb817..0ffb65411b 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/AssignmentOperator.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/AssignmentOperator.java
@@ -53,31 +53,30 @@ public class AssignmentOperator implements Expression
this.right = right;
}
- public static EnumSet<Kind> supportsOperators(AbstractType<?> type)
+ public static EnumSet<Kind> supportsOperators(AbstractType<?> type,
boolean isTransaction)
{
type = type.unwrap();
EnumSet<Kind> result = EnumSet.noneOf(Kind.class);
+ if (type instanceof CollectionType && type.isMultiCell())
+ {
+ if (type instanceof SetType || type instanceof ListType)
+ return EnumSet.of(Kind.ADD, Kind.SUBTRACT);
+ if (type instanceof MapType)
+ {
+ // map supports subtract, but not map - map; only map - set!
+ // since this is annoying to support, for now dropping -
+ return EnumSet.of(Kind.ADD);
+ }
+ throw new AssertionError("Unexpected collection type: " + type);
+ }
+ if (!isTransaction)
+ return result; // only multi-cell collections can be updated
outside of transactions
for (Operator.Kind supported : Operator.supportsOperators(type))
{
Kind kind = toKind(supported);
if (kind != null)
result.add(kind);
}
- if (result.isEmpty())
- {
- if (type instanceof CollectionType && type.isMultiCell())
- {
- if (type instanceof SetType || type instanceof ListType)
- return EnumSet.of(Kind.ADD, Kind.SUBTRACT);
- if (type instanceof MapType)
- {
- // map supports subtract, but not map - map; only map -
set!
- // since this is annoying to support, for now dropping -
- return EnumSet.of(Kind.ADD);
- }
- throw new AssertionError("Unexpected collection type: " +
type);
- }
- }
return result;
}
diff --git a/test/unit/org/apache/cassandra/cql3/ast/Conditional.java
b/test/unit/org/apache/cassandra/cql3/ast/Conditional.java
index 7fdcd17bea..52f79bb1dc 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/Conditional.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/Conditional.java
@@ -433,7 +433,7 @@ public interface Conditional extends Expression
return sub.isEmpty();
}
- private Builder add(Conditional conditional)
+ public Builder add(Conditional conditional)
{
sub.add(conditional);
return this;
diff --git a/test/unit/org/apache/cassandra/cql3/ast/CreateIndexDDL.java
b/test/unit/org/apache/cassandra/cql3/ast/CreateIndexDDL.java
index 0984ee4620..ce86d6bbb5 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/CreateIndexDDL.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/CreateIndexDDL.java
@@ -141,7 +141,7 @@ public class CreateIndexDDL implements Element
public EnumSet<QueryType> supportedQueries(AbstractType<?> type)
{
type = type.unwrap();
- if (IndexTermType.isEqOnlyType(type))
+ if (IndexTermType.isEqOnlyType(type) || type.isCollection() ||
type.isUDT() || type.isTuple())
return EnumSet.of(QueryType.Eq);
return EnumSet.allOf(QueryType.class);
}
diff --git a/test/unit/org/apache/cassandra/cql3/ast/ExpressionEvaluator.java
b/test/unit/org/apache/cassandra/cql3/ast/ExpressionEvaluator.java
index 8270b438f4..34acb843c7 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/ExpressionEvaluator.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/ExpressionEvaluator.java
@@ -21,107 +21,139 @@ package org.apache.cassandra.cql3.ast;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
-import java.util.Optional;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
-import org.apache.cassandra.db.marshal.AbstractType;
+import javax.annotation.Nullable;
-import static java.util.Optional.of;
+import org.apache.cassandra.db.marshal.AbstractType;
public class ExpressionEvaluator
{
- public static Optional<Object> tryEval(Expression e)
+ @Nullable
+ public static Object eval(Expression e)
{
if (e instanceof Value)
- return of(((Value) e).value());
+ return ((Value) e).value();
if (e instanceof TypeHint)
- return tryEval(((TypeHint) e).e);
+ return eval(((TypeHint) e).e);
if (e instanceof Operator)
- return tryEval((Operator) e);
- return Optional.empty();
+ return eval((Operator) e);
+ throw new UnsupportedOperationException("Unexpected expression type "
+ e.getClass() + ": " + e.toCQL());
}
- public static Optional<Object> tryEval(Operator e)
+ @Nullable
+ public static Object eval(Operator e)
{
+ Object lhs = eval(e.left);
+ if (lhs instanceof ByteBuffer)
+ lhs = e.left.type().compose((ByteBuffer) lhs);
+ Object rhs = eval(e.right);
+ if (rhs instanceof ByteBuffer)
+ rhs = e.right.type().compose((ByteBuffer) rhs);
switch (e.kind)
{
case ADD:
{
- var lhsOpt = tryEval(e.left);
- var rhsOpt = tryEval(e.right);
- if (lhsOpt.isEmpty() || rhsOpt.isEmpty())
- return Optional.empty();
- Object lhs = lhsOpt.get();
- Object rhs = rhsOpt.get();
if (lhs instanceof Byte)
- return of((byte) (((Byte) lhs) + ((Byte) rhs)));
+ return (byte) (((Byte) lhs) + ((Byte) rhs));
if (lhs instanceof Short)
- return of((short) (((Short) lhs) + ((Short) rhs)));
+ return (short) (((Short) lhs) + ((Short) rhs));
if (lhs instanceof Integer)
- return of((int) (((Integer) lhs) + ((Integer) rhs)));
+ return (int) (((Integer) lhs) + ((Integer) rhs));
if (lhs instanceof Long)
- return of((long) (((Long) lhs) + ((Long) rhs)));
+ return (long) (((Long) lhs) + ((Long) rhs));
if (lhs instanceof Float)
- return of((float) (((Float) lhs) + ((Float) rhs)));
+ return (float) (((Float) lhs) + ((Float) rhs));
if (lhs instanceof Double)
- return of((double) (((Double) lhs) + ((Double) rhs)));
+ return (double) (((Double) lhs) + ((Double) rhs));
if (lhs instanceof BigInteger)
- return of(((BigInteger) lhs).add((BigInteger) rhs));
+ return ((BigInteger) lhs).add((BigInteger) rhs);
if (lhs instanceof BigDecimal)
- return of(((BigDecimal) lhs).add((BigDecimal) rhs));
+ return ((BigDecimal) lhs).add((BigDecimal) rhs);
if (lhs instanceof String)
- return of(lhs.toString() + rhs.toString());
+ return lhs.toString() + rhs.toString();
+ if (lhs instanceof Set)
+ {
+ Set<Object> accum = new HashSet<>((Set<Object>) lhs);
+ accum.addAll((Set<Object>) rhs);
+ return accum;
+ }
+ if (lhs instanceof List)
+ {
+ List<Object> accum = new ArrayList<>((List<Object>) lhs);
+ accum.addAll((List<Object>) rhs);
+ return accum;
+ }
+ if (lhs instanceof Map)
+ {
+ Map<Object, Object> accum = new HashMap<>((Map<Object,
Object>) lhs);
+ accum.putAll((Map<Object, Object>) rhs);
+ return accum;
+ }
throw new UnsupportedOperationException("Unexpected type: " +
lhs.getClass());
}
case SUBTRACT:
{
- var lhsOpt = tryEval(e.left);
- var rhsOpt = tryEval(e.right);
- if (lhsOpt.isEmpty() || rhsOpt.isEmpty())
- return Optional.empty();
- Object lhs = lhsOpt.get();
- Object rhs = rhsOpt.get();
if (lhs instanceof Byte)
- return of((byte) (((Byte) lhs) - ((Byte) rhs)));
+ return (byte) (((Byte) lhs) - ((Byte) rhs));
if (lhs instanceof Short)
- return of((short) (((Short) lhs) - ((Short) rhs)));
+ return (short) (((Short) lhs) - ((Short) rhs));
if (lhs instanceof Integer)
- return of((int) (((Integer) lhs) - ((Integer) rhs)));
+ return (int) (((Integer) lhs) - ((Integer) rhs));
if (lhs instanceof Long)
- return of((long) (((Long) lhs) - ((Long) rhs)));
+ return (long) (((Long) lhs) - ((Long) rhs));
if (lhs instanceof Float)
- return of((float) (((Float) lhs) - ((Float) rhs)));
+ return (float) (((Float) lhs) - ((Float) rhs));
if (lhs instanceof Double)
- return of((double) (((Double) lhs) - ((Double) rhs)));
+ return (double) (((Double) lhs) - ((Double) rhs));
if (lhs instanceof BigInteger)
- return of(((BigInteger) lhs).subtract((BigInteger) rhs));
+ return ((BigInteger) lhs).subtract((BigInteger) rhs);
if (lhs instanceof BigDecimal)
- return of(((BigDecimal) lhs).subtract((BigDecimal) rhs));
+ return ((BigDecimal) lhs).subtract((BigDecimal) rhs);
+ if (lhs instanceof Set)
+ {
+ Set<Object> accum = new HashSet<>((Set<Object>) lhs);
+ accum.removeAll((Set<Object>) rhs);
+ return accum.isEmpty() ? null : accum;
+ }
+ if (lhs instanceof List)
+ {
+ List<Object> accum = new ArrayList<>((List<Object>) lhs);
+ accum.removeAll((List<Object>) rhs);
+ return accum.isEmpty() ? null : accum;
+ }
+ if (lhs instanceof Map)
+ {
+ // rhs is a Set<Object> as CQL doesn't allow removing if
the key and value both match
+ Map<Object, Object> accum = new HashMap<>((Map<Object,
Object>) lhs);
+ ((Set<Object>) rhs).forEach(accum::remove);
+ return accum.isEmpty() ? null : accum;
+ }
throw new UnsupportedOperationException("Unexpected type: " +
lhs.getClass());
}
case MULTIPLY:
{
- var lhsOpt = tryEval(e.left);
- var rhsOpt = tryEval(e.right);
- if (lhsOpt.isEmpty() || rhsOpt.isEmpty())
- return Optional.empty();
- Object lhs = lhsOpt.get();
- Object rhs = rhsOpt.get();
if (lhs instanceof Byte)
- return of((byte) (((Byte) lhs) * ((Byte) rhs)));
+ return (byte) (((Byte) lhs) * ((Byte) rhs));
if (lhs instanceof Short)
- return of((short) (((Short) lhs) * ((Short) rhs)));
+ return (short) (((Short) lhs) * ((Short) rhs));
if (lhs instanceof Integer)
- return of((int) (((Integer) lhs) * ((Integer) rhs)));
+ return (int) (((Integer) lhs) * ((Integer) rhs));
if (lhs instanceof Long)
- return of((long) (((Long) lhs) * ((Long) rhs)));
+ return (long) (((Long) lhs) * ((Long) rhs));
if (lhs instanceof Float)
- return of((float) (((Float) lhs) * ((Float) rhs)));
+ return (float) (((Float) lhs) * ((Float) rhs));
if (lhs instanceof Double)
- return of((double) ((Double) lhs) * ((Double) rhs));
+ return (double) ((Double) lhs) * ((Double) rhs);
if (lhs instanceof BigInteger)
- return of(((BigInteger) lhs).multiply((BigInteger) rhs));
+ return ((BigInteger) lhs).multiply((BigInteger) rhs);
if (lhs instanceof BigDecimal)
- return of(((BigDecimal) lhs).multiply((BigDecimal) rhs));
+ return ((BigDecimal) lhs).multiply((BigDecimal) rhs);
throw new UnsupportedOperationException("Unexpected type: " +
lhs.getClass());
}
default:
@@ -129,18 +161,12 @@ public class ExpressionEvaluator
}
}
- public static Optional<ByteBuffer> tryEvalEncoded(Expression e)
+ @Nullable
+ public static ByteBuffer evalEncoded(Expression e)
{
- return tryEval(e).map(v -> {
- if (v instanceof ByteBuffer) return (ByteBuffer) v;
- try
- {
- return ((AbstractType) e.type()).decompose(v);
- }
- catch (Throwable t)
- {
- throw t;
- }
- });
+ Object v = eval(e);
+ if (v == null) return null;
+ if (v instanceof ByteBuffer) return (ByteBuffer) v;
+ return ((AbstractType) e.type()).decompose(v);
}
}
diff --git a/test/unit/org/apache/cassandra/cql3/ast/Mutation.java
b/test/unit/org/apache/cassandra/cql3/ast/Mutation.java
index c21508f4d2..9126d8cbda 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/Mutation.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/Mutation.java
@@ -720,6 +720,12 @@ WHERE PK_column_conditions
return set(new Symbol(column, Int32Type.instance), Bind.of(value));
}
+ public UpdateBuilder set(String column, Expression expression)
+ {
+ Symbol symbol = new Symbol(metadata.getColumn(new
ColumnIdentifier(column, true)));
+ return set(symbol, expression);
+ }
+
public UpdateBuilder set(String column, String value)
{
Symbol symbol = new Symbol(metadata.getColumn(new
ColumnIdentifier(column, true)));
diff --git a/test/unit/org/apache/cassandra/cql3/ast/Select.java
b/test/unit/org/apache/cassandra/cql3/ast/Select.java
index 7fa497e16c..28134dde8e 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/Select.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/Select.java
@@ -378,6 +378,13 @@ FROM [keyspace_name.] table_name
return (T) this;
}
+ public T where(Conditional conditional)
+ {
+ where = new Conditional.Builder();
+ where.add(conditional);
+ return (T) this;
+ }
+
@Override
public T where(Expression ref, Conditional.Where.Inequality kind,
Expression expression)
{
diff --git a/test/unit/org/apache/cassandra/cql3/ast/StandardVisitors.java
b/test/unit/org/apache/cassandra/cql3/ast/StandardVisitors.java
index 854c096c07..4cbf3d989f 100644
--- a/test/unit/org/apache/cassandra/cql3/ast/StandardVisitors.java
+++ b/test/unit/org/apache/cassandra/cql3/ast/StandardVisitors.java
@@ -50,7 +50,7 @@ public class StandardVisitors
public Expression visit(Expression e)
{
if (!(e instanceof Operator)) return e;
- return new Bind(ExpressionEvaluator.tryEval((Operator) e).get(),
e.type());
+ return new Bind(ExpressionEvaluator.eval((Operator) e), e.type());
}
};
diff --git a/test/unit/org/apache/cassandra/utils/ASTGenerators.java
b/test/unit/org/apache/cassandra/utils/ASTGenerators.java
index a02bf65610..47b2267de3 100644
--- a/test/unit/org/apache/cassandra/utils/ASTGenerators.java
+++ b/test/unit/org/apache/cassandra/utils/ASTGenerators.java
@@ -35,6 +35,7 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
@@ -98,7 +99,7 @@ public class ASTGenerators
throw new AssertionError("Unsupported map type: " + map.getClass());
}
- public static Gen<AssignmentOperator>
assignmentOperatorGen(EnumSet<AssignmentOperator.Kind> allowed, Expression
right)
+ private static Gen<AssignmentOperator>
assignmentOperatorGen(EnumSet<AssignmentOperator.Kind> allowed, Expression
right)
{
if (allowed.isEmpty())
throw new IllegalArgumentException("Unable to create a operator
gen for empty set of allowed operators");
@@ -187,6 +188,12 @@ public class ASTGenerators
return this;
}
+ public ExpressionBuilder withOperators(Gen<Boolean> useOperator)
+ {
+ this.useOperator = Objects.requireNonNull(useOperator);
+ return this;
+ }
+
public ExpressionBuilder withoutOperators()
{
useOperator = i -> false;
@@ -375,6 +382,13 @@ public class ASTGenerators
columnExpressions.put(symbol, new
ExpressionBuilder(symbol.type()));
}
+ public MutationGenBuilder
withColumnExpressions(Consumer<ExpressionBuilder> fn)
+ {
+ for (Symbol symbol : allColumns)
+ fn.accept(columnExpressions.get(symbol));
+ return this;
+ }
+
public MutationGenBuilder allowEmpty(Symbol symbol)
{
columnExpressions.get(symbol).allowEmpty();
@@ -770,12 +784,12 @@ public class ASTGenerators
}
}
}
- if (kind == Mutation.Kind.UPDATE && isTransaction)
+ if (kind == Mutation.Kind.UPDATE)
{
for (Symbol c : new ArrayList<>(columnsToGenerate))
{
var useOperator = columnExpressions.get(c).useOperator;
- EnumSet<AssignmentOperator.Kind> additionOperatorAllowed =
AssignmentOperator.supportsOperators(c.type());
+ EnumSet<AssignmentOperator.Kind> additionOperatorAllowed =
AssignmentOperator.supportsOperators(c.type(), isTransaction);
if (!additionOperatorAllowed.isEmpty() &&
useOperator.generate(rnd))
{
Expression expression =
columnExpressions.get(c).build().generate(rnd);
diff --git a/test/unit/org/apache/cassandra/utils/AbstractTypeGenerators.java
b/test/unit/org/apache/cassandra/utils/AbstractTypeGenerators.java
index 902c37e92d..ea9a128233 100644
--- a/test/unit/org/apache/cassandra/utils/AbstractTypeGenerators.java
+++ b/test/unit/org/apache/cassandra/utils/AbstractTypeGenerators.java
@@ -26,7 +26,6 @@ import java.util.Comparator;
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.Objects;
@@ -165,12 +164,13 @@ public final class AbstractTypeGenerators
).collect(Collectors.toMap(t -> t.type, t -> t));
// NOTE not supporting reversed as CQL doesn't allow nested reversed types
// when generating part of the clustering key, it would be good to allow
reversed types as the top level
- private static final Gen<AbstractType<?>> PRIMITIVE_TYPE_GEN;
- static
+ private static final Gen<AbstractType<?>> PRIMITIVE_TYPE_GEN =
SourceDSL.arbitrary().pick(knownPrimitiveTypes());
+
+ public static List<AbstractType<?>> knownPrimitiveTypes()
{
ArrayList<AbstractType<?>> types = new
ArrayList<>(PRIMITIVE_TYPE_DATA_GENS.keySet());
types.sort(Comparator.comparing(a -> a.getClass().getName()));
- PRIMITIVE_TYPE_GEN = SourceDSL.arbitrary().pick(types);
+ return types;
}
private static final Set<Class<? extends AbstractType>>
NON_PRIMITIVE_TYPES = ImmutableSet.<Class<? extends AbstractType>>builder()
@@ -244,6 +244,14 @@ public final class AbstractTypeGenerators
return () -> PRIMITIVE_TYPE_DATA_GENS.put(type, original);
}
+ public static boolean isUnsafeEquality(AbstractType<?> type)
+ {
+ return type == EmptyType.instance
+ || type == DurationType.instance
+ || type == DecimalType.instance
+ || type == CounterColumnType.instance;
+ }
+
public static TypeGenBuilder withoutUnsafeEquality(TypeGenBuilder builder)
{
// make sure to keep UNSAFE_EQUALITY in-sync
@@ -281,6 +289,7 @@ public final class AbstractTypeGenerators
private Predicate<AbstractType<?>> typeFilter = null;
private Gen<String> udtName = null;
private Gen<Boolean> multiCellGen = BOOLEAN_GEN;
+ private UserTypeFieldsGen fieldNamesGen = UserTypeFieldsGen.random();
public TypeGenBuilder()
{
@@ -289,19 +298,27 @@ public final class AbstractTypeGenerators
public TypeGenBuilder(TypeGenBuilder other)
{
maxDepth = other.maxDepth;
- kinds = other.kinds == null ? null : EnumSet.copyOf(other.kinds);
+ kinds = other.kinds;
typeKindGen = other.typeKindGen;
defaultSizeGen = other.defaultSizeGen;
vectorSizeGen = other.vectorSizeGen;
tupleSizeGen = other.tupleSizeGen;
- udtName = other.udtName;
udtSizeGen = other.udtSizeGen;
+ compositeSizeGen = other.compositeSizeGen;
primitiveGen = other.primitiveGen;
+ compositeElementGen = other.compositeElementGen;
userTypeKeyspaceGen = other.userTypeKeyspaceGen;
defaultSetKeyFunc = other.defaultSetKeyFunc;
- compositeElementGen = other.compositeElementGen;
- compositeSizeGen = other.compositeSizeGen;
typeFilter = other.typeFilter;
+ udtName = other.udtName;
+ multiCellGen = other.multiCellGen;
+ fieldNamesGen = other.fieldNamesGen;
+ }
+
+ public TypeGenBuilder withUserTypeFields(UserTypeFieldsGen
fieldNamesGen)
+ {
+ this.fieldNamesGen = fieldNamesGen;
+ return this;
}
public TypeGenBuilder withMultiCell(Gen<Boolean> multiCellGen)
@@ -406,6 +423,13 @@ public final class AbstractTypeGenerators
return this;
}
+ public TypeGenBuilder withPrimitives(Gen<AbstractType<?>> gen)
+ {
+ // any previous filters will be ignored...
+ primitiveGen = Objects.requireNonNull(gen);
+ return this;
+ }
+
public TypeGenBuilder withMaxDepth(int value)
{
this.maxDepth = value;
@@ -509,7 +533,7 @@ public final class AbstractTypeGenerators
case TUPLE:
return tupleTypeGen(atBottom ? primitiveGen :
buildRecursive(maxDepth, level - 1, typeKindGen,
SourceDSL.arbitrary().constant(false)), tupleSizeGen != null ? tupleSizeGen :
defaultSizeGen).generate(rnd);
case UDT:
- return userTypeGen(next.get(), udtSizeGen != null ?
udtSizeGen : defaultSizeGen, userTypeKeyspaceGen, udtName,
multiCellGen).generate(rnd);
+ return userTypeGen(fieldNamesGen, next.get(),
udtSizeGen != null ? udtSizeGen : defaultSizeGen, userTypeKeyspaceGen, udtName,
multiCellGen).generate(rnd);
case VECTOR:
{
Gen<Integer> sizeGen = vectorSizeGen != null ?
vectorSizeGen : defaultSizeGen;
@@ -762,27 +786,47 @@ public final class AbstractTypeGenerators
OVERRIDE_KEYSPACE.remove();
}
+ public interface UserTypeFieldsGen
+ {
+ List<FieldIdentifier> generate(RandomnessSource rnd, int size);
+
+ static UserTypeFieldsGen random()
+ {
+ Gen<FieldIdentifier> fieldNameGen =
IDENTIFIER_GEN.map(FieldIdentifier::forQuoted);
+ return (rnd, size) -> Generators.uniqueList(fieldNameGen, i ->
size).generate(rnd);
+ }
+
+ static UserTypeFieldsGen simpleNames()
+ {
+ return (rnd, size) -> {
+ List<FieldIdentifier> output = new ArrayList<>(size);
+ for (int i = 0; i < size; i++)
+ output.add(FieldIdentifier.forUnquoted("f" + i));
+ return output;
+ };
+ }
+ }
+
public static Gen<UserType> userTypeGen(Gen<AbstractType<?>> elementGen,
Gen<Integer> sizeGen, Gen<String> ksGen, Gen<String> nameGen, Gen<Boolean>
multiCellGen)
{
- Gen<FieldIdentifier> fieldNameGen =
IDENTIFIER_GEN.map(FieldIdentifier::forQuoted);
+ return userTypeGen(UserTypeFieldsGen.random(), elementGen, sizeGen,
ksGen, nameGen, multiCellGen);
+ }
+
+ public static Gen<UserType> userTypeGen(UserTypeFieldsGen fieldNamesGen,
Gen<AbstractType<?>> elementGen, Gen<Integer> sizeGen, Gen<String> ksGen,
Gen<String> nameGen, Gen<Boolean> multiCellGen)
+ {
return rnd -> {
boolean multiCell = multiCellGen.generate(rnd);
int numElements = sizeGen.generate(rnd);
List<AbstractType<?>> fieldTypes = new ArrayList<>(numElements);
- LinkedHashSet<FieldIdentifier> fieldNames = new
LinkedHashSet<>(numElements);
+ List<FieldIdentifier> fieldNames = fieldNamesGen.generate(rnd,
numElements);
String ks = OVERRIDE_KEYSPACE.get();
if (ks == null)
ks = ksGen.generate(rnd);
String name = nameGen.generate(rnd);
ByteBuffer nameBB = AsciiType.instance.decompose(name);
- Gen<FieldIdentifier> distinctNameGen = filter(fieldNameGen, 30, e
-> !fieldNames.contains(e));
- // UDTs don't allow duplicate names, so make sure all names are
unique
for (int i = 0; i < numElements; i++)
{
- FieldIdentifier fieldName = distinctNameGen.generate(rnd);
- fieldNames.add(fieldName);
-
AbstractType<?> element = elementGen.generate(rnd);
element = multiCell ? element.freeze() : element.unfreeze();
// a UDT cannot contain a non-frozen UDT; as defined by
CreateType
@@ -790,7 +834,7 @@ public final class AbstractTypeGenerators
element = element.freeze();
fieldTypes.add(element);
}
- return new UserType(ks, nameBB, new ArrayList<>(fieldNames),
fieldTypes, multiCell);
+ return new UserType(ks, nameBB, fieldNames, fieldTypes, multiCell);
};
}
diff --git a/test/unit/org/apache/cassandra/utils/Generators.java
b/test/unit/org/apache/cassandra/utils/Generators.java
index 5f99421240..6bb7f56a8d 100644
--- a/test/unit/org/apache/cassandra/utils/Generators.java
+++ b/test/unit/org/apache/cassandra/utils/Generators.java
@@ -27,7 +27,6 @@ import java.sql.Timestamp;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
-import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
@@ -38,10 +37,13 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import com.google.common.collect.Range;
+import com.google.common.collect.Sets;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import accord.utils.DefaultRandom;
+import accord.utils.RandomSource;
import org.apache.cassandra.cql3.ReservedKeywords;
import org.quicktheories.core.Gen;
import org.quicktheories.core.RandomnessSource;
@@ -492,13 +494,20 @@ public final class Generators
};
}
- public static <T extends Comparable<? super T>> Gen<List<T>>
uniqueList(Gen<T> gen, Gen<Integer> sizeGen)
+ public static <T> Gen<List<T>> uniqueList(Gen<T> gen, Gen<Integer> sizeGen)
{
- return set(gen, sizeGen).map(t -> {
- List<T> list = new ArrayList<>(t);
- list.sort(Comparator.naturalOrder());
- return list;
- });
+ return rnd -> {
+ int size = sizeGen.generate(rnd);
+ Set<T> set = Sets.newHashSetWithExpectedSize(size);
+ List<T> output = new ArrayList<>(size);
+ for (int i = 0; i < size; i++)
+ {
+ T value;
+ while (!set.add(value = gen.generate(rnd))) {}
+ output.add(value);
+ }
+ return output;
+ };
}
public static <T> Gen<T> cached(Gen<T> gen)
@@ -612,6 +621,14 @@ public final class Generators
};
}
+ public static <T> org.quicktheories.core.Gen<T>
fromGen(accord.utils.Gen<T> accord)
+ {
+ return rnd -> {
+ RandomSource rs = new DefaultRandom(rnd.next(Constraint.none()));
+ return accord.next(rs);
+ };
+ }
+
public static Gen<TimeUUID> timeUUID()
{
ZonedDateTime now = ZonedDateTime.of(2020, 8, 20,
diff --git a/test/unit/org/apache/cassandra/utils/ImmutableUniqueList.java
b/test/unit/org/apache/cassandra/utils/ImmutableUniqueList.java
index 73e7da9a91..00fabea136 100644
--- a/test/unit/org/apache/cassandra/utils/ImmutableUniqueList.java
+++ b/test/unit/org/apache/cassandra/utils/ImmutableUniqueList.java
@@ -32,6 +32,8 @@ import org.agrona.collections.Object2IntHashMap;
public class ImmutableUniqueList<T> extends AbstractList<T> implements
RandomAccess
{
+ private static final ImmutableUniqueList<Object> EMPTY =
ImmutableUniqueList.builder().build();
+
private final T[] values;
private final Object2IntHashMap<T> indexLookup;
private transient AsSet asSet = null;
@@ -46,6 +48,16 @@ public class ImmutableUniqueList<T> extends AbstractList<T>
implements RandomAcc
return new Builder<>();
}
+ public static <T> Builder<T> builder(int expectedSize)
+ {
+ return new Builder<>(expectedSize);
+ }
+
+ public static <T> ImmutableUniqueList<T> empty()
+ {
+ return (ImmutableUniqueList<T>) EMPTY;
+ }
+
public AsSet asSet()
{
if (asSet != null) return asSet;
@@ -85,10 +97,20 @@ public class ImmutableUniqueList<T> extends AbstractList<T>
implements RandomAcc
public static final class Builder<T> extends AbstractSet<T>
{
- private final List<T> values = new ArrayList<>();
+ private final List<T> values;
private final Object2IntHashMap<T> indexLookup = new
Object2IntHashMap<>(-1);
private int idx;
+ public Builder()
+ {
+ this.values = new ArrayList<>();
+ }
+
+ public Builder(int expectedSize)
+ {
+ this.values = new ArrayList<>(expectedSize);
+ }
+
public Builder<T> mayAddAll(Collection<? extends T> values)
{
addAll(values);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]