mirror of https://github.com/apache/cassandra
Add range delete support to AST-based fuzz tests
patch by Sunil Ramchandra Pawar; reviewed by Caleb Rackliffe and David Capwell for CASSANDRA-20949
This commit is contained in:
parent
2184d5cc6b
commit
157e8808f9
|
|
@ -392,23 +392,15 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
|
|||
|
||||
|
||||
String annotation;
|
||||
if (rs.nextBoolean())
|
||||
{
|
||||
builder.between(ckSymbol, state.value(rs, low, ckSymbol.type()), state.value(rs, high, ckSymbol.type()));
|
||||
annotation = "clustering BETWEEN";
|
||||
}
|
||||
else if (rs.nextBoolean())
|
||||
{
|
||||
builder.where(ckSymbol, Inequality.GREATER_THAN_EQ, low);
|
||||
builder.where(ckSymbol, Inequality.LESS_THAN_EQ, high);
|
||||
annotation = "clustering >= AND <=";
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.where(ckSymbol, Inequality.GREATER_THAN, low);
|
||||
builder.where(ckSymbol, Inequality.LESS_THAN, high);
|
||||
annotation = "clustering > AND <";
|
||||
}
|
||||
ASTGenerators.RangeType rangeType = rs.pick(ASTGenerators.RangeType.BOUND, ASTGenerators.RangeType.BETWEEN);
|
||||
|
||||
ASTGenerators.applyRangeCondition(builder, rangeType, ckSymbol,
|
||||
state.greaterThanGen.next(rs),
|
||||
state.lessThanGen.next(rs),
|
||||
state.value(rs, low, ckSymbol.type()),
|
||||
state.value(rs, high, ckSymbol.type()));
|
||||
|
||||
annotation = "clustering " + rangeType.name();
|
||||
|
||||
if (rs.nextBoolean())
|
||||
builder.orderByColumn(ckSymbol, rs.pick(Select.OrderBy.Ordering.values()));
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ import org.apache.cassandra.cql3.ast.Value;
|
|||
import org.apache.cassandra.cql3.ast.Visitor;
|
||||
import org.apache.cassandra.db.BufferClustering;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.ClusteringBound;
|
||||
import org.apache.cassandra.db.Slice;
|
||||
import org.apache.cassandra.db.Slices;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.BooleanType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
|
|
@ -621,14 +624,13 @@ public class ASTSingleTableModel
|
|||
}
|
||||
|
||||
private enum DeleteKind
|
||||
{PARTITION, ROW, COLUMN}
|
||||
{PARTITION, ROW, COLUMN, RANGE}
|
||||
private void update(Mutation.Delete delete)
|
||||
{
|
||||
long nowTs = delete.timestampOrDefault(numMutations);
|
||||
//TODO (coverage): range deletes
|
||||
var split = splitOnPartition(delete.where.simplify());
|
||||
List<Clustering<ByteBuffer>> pks = split.left;
|
||||
List<Clustering<ByteBuffer>> clusterings = split.right.isEmpty() ? Collections.emptyList() : clustering(split.right);
|
||||
List<Clustering<ByteBuffer>> clusterings = Collections.emptyList();
|
||||
HashSet<Symbol> columns = delete.columns.isEmpty() ? null : new HashSet<>(delete.columns);
|
||||
for (Clustering<ByteBuffer> pd : pks)
|
||||
{
|
||||
|
|
@ -638,8 +640,15 @@ public class ASTSingleTableModel
|
|||
DeleteKind kind = DeleteKind.PARTITION;
|
||||
if (!delete.columns.isEmpty())
|
||||
kind = DeleteKind.COLUMN;
|
||||
else if (!clusterings.isEmpty())
|
||||
kind = DeleteKind.ROW;
|
||||
else if (containsRangeConditionOnClustering(split.right))
|
||||
kind = DeleteKind.RANGE;
|
||||
|
||||
if (kind != DeleteKind.RANGE)
|
||||
{
|
||||
clusterings = split.right.isEmpty() ? Collections.emptyList() : clustering(split.right);
|
||||
if (kind == DeleteKind.PARTITION && !clusterings.isEmpty())
|
||||
kind = DeleteKind.ROW;
|
||||
}
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
|
|
@ -671,6 +680,19 @@ public class ASTSingleTableModel
|
|||
}
|
||||
}
|
||||
break;
|
||||
case RANGE:
|
||||
assert clusterings.isEmpty();
|
||||
LookupContext ctx = new LookupContext(delete);
|
||||
for (BytesPartitionState.Row value : partition.rows())
|
||||
{
|
||||
if (ctx.include(value))
|
||||
{
|
||||
partition.deleteRow(value.clustering, nowTs);
|
||||
if (partition.shouldDelete())
|
||||
partitions.remove(partition.ref());
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
@ -1016,6 +1038,10 @@ public class ASTSingleTableModel
|
|||
if (factory.clusteringColumns.isEmpty()) return Collections.singletonList(Clustering.EMPTY);
|
||||
throw new IllegalArgumentException("No clustering columns defined in the WHERE clause, but clustering columns exist; expected " + factory.clusteringColumns);
|
||||
}
|
||||
|
||||
if (containsRangeConditionOnClustering(conditionals))
|
||||
return extractClusteringsFromSlices(conditionals);
|
||||
|
||||
var split = splitOnClustering(conditionals);
|
||||
var clusterings = split.left;
|
||||
var remaining = split.right;
|
||||
|
|
@ -1088,6 +1114,131 @@ public class ASTSingleTableModel
|
|||
return Pair.create(partitionKeys, other);
|
||||
}
|
||||
|
||||
private boolean containsRangeConditionOnClustering(List<Conditional> conditionals)
|
||||
{
|
||||
for (Conditional cond : conditionals)
|
||||
{
|
||||
if (cond instanceof Conditional.Where)
|
||||
{
|
||||
Conditional.Where where = (Conditional.Where) cond;
|
||||
if (factory.clusteringColumns.contains(where.lhs))
|
||||
{
|
||||
switch (where.kind)
|
||||
{
|
||||
case GREATER_THAN:
|
||||
case GREATER_THAN_EQ:
|
||||
case LESS_THAN:
|
||||
case LESS_THAN_EQ:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cond instanceof Conditional.Between)
|
||||
{
|
||||
Conditional.Between between = (Conditional.Between) cond;
|
||||
if (between.ref instanceof Symbol && factory.clusteringColumns.contains((Symbol) between.ref))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<Clustering<ByteBuffer>> extractClusteringsFromSlices(List<Conditional> conditionals)
|
||||
{
|
||||
Map<Symbol, List<Conditional.Where>> rangeConditions = new HashMap<>();
|
||||
// Build slices
|
||||
Slices.Builder builder = new Slices.Builder(factory.clusteringComparator);
|
||||
|
||||
for (Conditional cond : conditionals)
|
||||
{
|
||||
if (cond instanceof Conditional.Where)
|
||||
{
|
||||
Conditional.Where where = (Conditional.Where) cond;
|
||||
if (factory.clusteringColumns.contains(where.lhs)
|
||||
&& (where.kind != Inequality.EQUAL)) // skip equality
|
||||
{
|
||||
Symbol col = (Symbol) where.lhs;
|
||||
rangeConditions.computeIfAbsent(col, __ -> new ArrayList<>()).add(where);
|
||||
}
|
||||
}
|
||||
else if (cond instanceof Conditional.Between)
|
||||
{
|
||||
Conditional.Between between = (Conditional.Between) cond;
|
||||
ByteBuffer start = eval(between.start);
|
||||
ByteBuffer end = eval(between.end);
|
||||
|
||||
ClusteringBound<ByteBuffer> first = ClusteringBound.inclusiveStartOf(Clustering.make(start));
|
||||
ClusteringBound<ByteBuffer> last = ClusteringBound.inclusiveEndOf(Clustering.make(end));
|
||||
Slice slice = Slice.make(first, last);
|
||||
// To avoid adding empty slices
|
||||
if (!slice.isEmpty(factory.clusteringComparator))
|
||||
builder.add(slice);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<Symbol, List<Conditional.Where>> entry : rangeConditions.entrySet())
|
||||
{
|
||||
ByteBuffer lower = null;
|
||||
ByteBuffer upper = null;
|
||||
boolean includeLower = false;
|
||||
boolean includeUpper = false;
|
||||
|
||||
for (Conditional.Where cond : entry.getValue())
|
||||
{
|
||||
ByteBuffer val = eval(cond.rhs);
|
||||
switch (cond.kind)
|
||||
{
|
||||
case GREATER_THAN: lower = val; includeLower = false; break;
|
||||
case GREATER_THAN_EQ: lower = val; includeLower = true; break;
|
||||
case LESS_THAN: upper = val; includeUpper = false; break;
|
||||
case LESS_THAN_EQ: upper = val; includeUpper = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
ClusteringBound<?> start = (lower == null)
|
||||
? ClusteringBound.BOTTOM
|
||||
: (includeLower
|
||||
? ClusteringBound.inclusiveStartOf(Clustering.make(lower))
|
||||
: ClusteringBound.exclusiveStartOf(Clustering.make(lower)));
|
||||
|
||||
ClusteringBound<?> end = (upper == null)
|
||||
? ClusteringBound.TOP
|
||||
: (includeUpper
|
||||
? ClusteringBound.inclusiveEndOf(Clustering.make(upper))
|
||||
: ClusteringBound.exclusiveEndOf(Clustering.make(upper)));
|
||||
|
||||
Slice slice = Slice.make(start, end);
|
||||
// To avoid adding empty slices
|
||||
if (!slice.isEmpty(factory.clusteringComparator))
|
||||
builder.add(slice);
|
||||
}
|
||||
List<Clustering<ByteBuffer>> clusterings = new ArrayList<>();
|
||||
for (Slice slice : builder.build())
|
||||
{
|
||||
if (!slice.start().isBottom())
|
||||
clusterings.add(createClusteringBound(slice.start()));
|
||||
if (!slice.end().isTop())
|
||||
clusterings.add(createClusteringBound(slice.end()));
|
||||
}
|
||||
|
||||
return clusterings;
|
||||
}
|
||||
|
||||
|
||||
private static Clustering<ByteBuffer> createClusteringBound(ClusteringBound<?> bound)
|
||||
{
|
||||
if (bound == null)
|
||||
throw new IllegalArgumentException("ClusteringBound should not be null.");
|
||||
|
||||
int size = bound.size();
|
||||
ByteBuffer[] values = new ByteBuffer[size];
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
values[i] = (ByteBuffer) bound.get(i);
|
||||
|
||||
return Clustering.make(values);
|
||||
}
|
||||
|
||||
private static ImmutableUniqueList<Clustering<ByteBuffer>> keys(Collection<Symbol> columns, Map<? extends ReferenceExpression, List<ByteBuffer>> columnValues)
|
||||
{
|
||||
return keys(columns, columnValues, Function.identity());
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ import org.apache.cassandra.schema.ColumnMetadata;
|
|||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
|
||||
public class ASTSingleTableModelTest
|
||||
{
|
||||
public static final ByteBuffer ZERO = ByteBufferUtil.bytes(0);
|
||||
|
|
@ -557,6 +558,116 @@ public class ASTSingleTableModelTest
|
|||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClusteringRangeDelete()
|
||||
{
|
||||
TableMetadata metadata = new Builder().pk(1).ck(1).statics(0).regular(1).build();
|
||||
ASTSingleTableModel model = new ASTSingleTableModel(metadata);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
model.update(Mutation.insert(metadata)
|
||||
.value("pk", 0)
|
||||
.value("ck", i)
|
||||
.value("v", i)
|
||||
.build());
|
||||
}
|
||||
|
||||
model.update(Mutation.delete(metadata)
|
||||
.value("pk", 0)
|
||||
.where("ck", Inequality.GREATER_THAN_EQ, 0)
|
||||
.where("ck", Inequality.LESS_THAN, 7)
|
||||
.build());
|
||||
|
||||
model.validate(rows(row(metadata, 0, 7, 7), row(metadata, 0, 8, 8), row(metadata,0,9,9)),
|
||||
Select.builder(metadata).value("pk", 0).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRangeDeleteWithMultipleClusteringKeys()
|
||||
{
|
||||
TableMetadata metadata = new Builder().pk(1).ck(2).statics(0).regular(1).build();
|
||||
ASTSingleTableModel model = new ASTSingleTableModel(metadata);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
model.update(Mutation.insert(metadata)
|
||||
.value("pk", 0)
|
||||
.value("ck0", i)
|
||||
.value("ck1", i+1)
|
||||
.value("v", i)
|
||||
.build());
|
||||
}
|
||||
|
||||
model.update(Mutation.delete(metadata)
|
||||
.value("pk", 0)
|
||||
.between("ck0", Literal.of(3), Literal.of(7))
|
||||
.build());
|
||||
|
||||
model.validate(rows(row(metadata, 0,0,1,0),
|
||||
row(metadata, 0,1,2,1),
|
||||
row(metadata,0,2,3,2),
|
||||
row(metadata,0,8,9,8),
|
||||
row(metadata,0,9,10,9)),
|
||||
Select.builder(metadata).build());
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRangeDeleteWithStaticColumn()
|
||||
{
|
||||
TableMetadata metadata = new Builder().pk(1).ck(1).statics(1).regular(1).build();
|
||||
ASTSingleTableModel model = new ASTSingleTableModel(metadata);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
model.update(Mutation.insert(metadata)
|
||||
.value("pk", 0)
|
||||
.value("ck", i)
|
||||
.value("s", 0)
|
||||
.value("v", i)
|
||||
.build());
|
||||
}
|
||||
|
||||
model.update(Mutation.delete(metadata)
|
||||
.value("pk", 0)
|
||||
.between("ck", Literal.of(3), Literal.of(7))
|
||||
.build());
|
||||
|
||||
model.validate(rows(row(metadata, 0,0,0,0),
|
||||
row(metadata, 0,1,0,1),
|
||||
row(metadata,0,2,0,2),
|
||||
row(metadata,0,8,0,8),
|
||||
row(metadata,0,9,0,9)),
|
||||
Select.builder(metadata).build());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClusteringRangeDeleteBetween()
|
||||
{
|
||||
TableMetadata metadata = new Builder().pk(1).ck(1).statics(0).regular(1).build();
|
||||
ASTSingleTableModel model = new ASTSingleTableModel(metadata);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
model.update(Mutation.insert(metadata)
|
||||
.value("pk", 0)
|
||||
.value("ck", i)
|
||||
.value("v", i)
|
||||
.build());
|
||||
}
|
||||
|
||||
model.update(Mutation.delete(metadata)
|
||||
.value("pk", 0)
|
||||
.between("ck", Bind.of(0), Literal.of(6))
|
||||
.build());
|
||||
|
||||
model.validate(rows(row(metadata, 0, 7, 7), row(metadata, 0, 8, 8), row(metadata,0,9,9)),
|
||||
Select.builder(metadata).value("pk", 0).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenEqIncludesEmptyPartition()
|
||||
{
|
||||
|
|
@ -753,6 +864,35 @@ public class ASTSingleTableModelTest
|
|||
model.validate(rows(row(metadata, 0, 1, null, List.of(1))), Select.builder(metadata).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRangeDeleteGeneratorWithBetweenClause()
|
||||
{
|
||||
TableMetadata metadata = new Builder().pk(1).ck(1).statics(0).regular(1).build();
|
||||
ASTSingleTableModel model = new ASTSingleTableModel(metadata);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
model.update(Mutation.insert(metadata)
|
||||
.value("pk", 0)
|
||||
.value("ck", i)
|
||||
.value("v", i)
|
||||
.build());
|
||||
}
|
||||
|
||||
model.update(Mutation.delete(metadata)
|
||||
.value("pk", 0)
|
||||
.between("ck", Literal.of(3), Literal.of(7))
|
||||
.build());
|
||||
|
||||
model.validate(rows(row(metadata, 0, 0,0),
|
||||
row(metadata, 0, 1,1),
|
||||
row(metadata,0,2,2),
|
||||
row(metadata,0,8,8),
|
||||
row(metadata,0,9,9)),
|
||||
Select.builder(metadata).build());
|
||||
|
||||
}
|
||||
|
||||
private interface SimpleWrite<T>
|
||||
{
|
||||
void write(String name, T value, long ts);
|
||||
|
|
|
|||
|
|
@ -91,6 +91,36 @@ public class ASTGenerators
|
|||
{
|
||||
public static final EnumSet<KnownIssue> IGNORED_ISSUES = KnownIssue.ignoreAll();
|
||||
|
||||
public enum RangeType {UNBOUND, BOUND , BETWEEN}
|
||||
|
||||
/**
|
||||
* Applies a range condition to a builder for a single column.
|
||||
*/
|
||||
public static void applyRangeCondition(Conditional.ConditionalBuilder<?> builder,
|
||||
RangeType rangeType,
|
||||
Symbol column,
|
||||
Conditional.Where.Inequality lowerKind,
|
||||
Conditional.Where.Inequality upperKind,
|
||||
Expression lowerValue,
|
||||
Expression upperValue)
|
||||
{
|
||||
switch (rangeType)
|
||||
{
|
||||
case UNBOUND:
|
||||
builder.where(column, lowerKind, lowerValue);
|
||||
break;
|
||||
case BOUND:
|
||||
builder.where(column, lowerKind, lowerValue);
|
||||
builder.where(column, upperKind, upperValue);
|
||||
break;
|
||||
case BETWEEN:
|
||||
builder.between(column, lowerValue, upperValue);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported RangeType: " + rangeType);
|
||||
}
|
||||
}
|
||||
|
||||
public static Gen<LinkedHashMap<Symbol, Object>> columnValues(List<Symbol> columns)
|
||||
{
|
||||
List<Gen<?>> gens = new ArrayList<>(columns.size());
|
||||
|
|
@ -403,7 +433,7 @@ public class ASTGenerators
|
|||
|
||||
public static class MutationGenBuilder
|
||||
{
|
||||
public enum DeleteKind { Partition, Row, Column }
|
||||
public enum DeleteKind { Partition, Row, Column, Range }
|
||||
private final TableMetadata metadata;
|
||||
private final LinkedHashSet<Symbol> allColumns;
|
||||
private final LinkedHashSet<Symbol> partitionColumns, clusteringColumns;
|
||||
|
|
@ -845,6 +875,8 @@ public class ASTGenerators
|
|||
// if there are no columns to delete, fallback to row
|
||||
if (deleteKind == DeleteKind.Column && regularAndStaticColumns.isEmpty())
|
||||
deleteKind = DeleteKind.Row;
|
||||
if (deleteKind == DeleteKind.Range && clusteringColumns.isEmpty())
|
||||
deleteKind = DeleteKind.Partition;
|
||||
if (deleteKind == DeleteKind.Row && clusteringColumns.isEmpty())
|
||||
deleteKind = DeleteKind.Partition;
|
||||
|
||||
|
|
@ -907,6 +939,9 @@ public class ASTGenerators
|
|||
}
|
||||
}
|
||||
break;
|
||||
case Range:
|
||||
valueRange(rnd, columnExpressions, builder, clusteringColumns);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
@ -958,6 +993,12 @@ public class ASTGenerators
|
|||
columns = new ArrayList<>(uniq);
|
||||
}
|
||||
break;
|
||||
case Range:
|
||||
{
|
||||
columns = Collections.emptyList();
|
||||
existAllowed = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException(deleteKind.name());
|
||||
}
|
||||
|
|
@ -982,6 +1023,40 @@ public class ASTGenerators
|
|||
};
|
||||
}
|
||||
|
||||
private static final Gen<Conditional.Where.Inequality> RANGE_INEQUALITY_GEN = SourceDSL.arbitrary().pick(Conditional.Where.Inequality.GREATER_THAN_EQ,
|
||||
Conditional.Where.Inequality.GREATER_THAN,
|
||||
Conditional.Where.Inequality.LESS_THAN_EQ,
|
||||
Conditional.Where.Inequality.LESS_THAN);
|
||||
|
||||
private static final Gen<Conditional.Where.Inequality> LOWER_BOUND_GEN = SourceDSL.arbitrary().pick(Conditional.Where.Inequality.GREATER_THAN,
|
||||
Conditional.Where.Inequality.GREATER_THAN_EQ);
|
||||
|
||||
private static final Gen<Conditional.Where.Inequality> UPPER_BOUND_GEN = SourceDSL.arbitrary().pick(Conditional.Where.Inequality.LESS_THAN,
|
||||
Conditional.Where.Inequality.LESS_THAN_EQ);
|
||||
|
||||
|
||||
private void valueRange(RandomnessSource rnd,
|
||||
Map<Symbol, ExpressionBuilder> columnExpressions,
|
||||
Conditional.ConditionalBuilder<?> builder,
|
||||
LinkedHashSet<Symbol> columns)
|
||||
{
|
||||
List<Symbol> columnList = new ArrayList<>(columns);
|
||||
|
||||
assert !columnList.isEmpty();
|
||||
|
||||
Symbol s = columnList.get(0);
|
||||
Gen<Expression> expressionGen = columnExpressions.get(s).build();
|
||||
|
||||
RangeType type = SourceDSL.arbitrary().enumValues(RangeType.class).generate(rnd);
|
||||
applyRangeCondition(builder, type, s,
|
||||
type == RangeType.UNBOUND ? RANGE_INEQUALITY_GEN.generate(rnd) : LOWER_BOUND_GEN.generate(rnd),
|
||||
UPPER_BOUND_GEN.generate(rnd),
|
||||
expressionGen.generate(rnd),
|
||||
expressionGen.generate(rnd));
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void generateRemaining(RandomnessSource rnd,
|
||||
Gen<Boolean> bool,
|
||||
Mutation.Kind kind,
|
||||
|
|
|
|||
Loading…
Reference in New Issue