Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "modules/accord"]
path = modules/accord
url = https://github.com/apache/cassandra-accord.git
branch = trunk
url = https://github.com/alanwang67/cassandra-accord.git
branch = linearIntersectionBug
1 change: 1 addition & 0 deletions src/antlr/Lexer.g
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ K_ALLOW: A L L O W;
K_FILTERING: F I L T E R I N G;
K_IF: I F;
K_THEN: T H E N;
K_ELSE: E L S E;
K_END: E N D;
K_IS: I S;
K_CONTAINS: C O N T A I N S;
Expand Down
23 changes: 17 additions & 6 deletions src/antlr/Parser.g
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ options {

// enables parsing txn specific syntax when true
protected boolean isParsingTxn = false;
// tracks whether a txn has conditional updates
protected boolean isTxnConditional = false;

protected List<RowDataReference.Raw> references;

Expand Down Expand Up @@ -783,20 +781,33 @@ batchTxnStatement returns [TransactionStatement.Parsed expr]
List<SelectStatement.RawStatement> assignments = new ArrayList<>();
SelectStatement.RawStatement select = null;
List<RowDataReference.Raw> returning = null;
List<ModificationStatement.Parsed> updates = new ArrayList<>();
List<List<ConditionStatement.Raw>> conditions = new ArrayList<>();
List<List<ModificationStatement.Parsed>> updates = new ArrayList<>();
}
: K_BEGIN K_TRANSACTION
( let=letStatement ';' { assignments.add(let); })*
( ( (selectStatement) => s=selectStatement ';' { select = s; }) | ( K_SELECT drs=rowDataReferences ';' { returning = drs; }) )?
( K_IF conditions=txnConditions K_THEN { isTxnConditional = true; } )?
( upd=batchStatementObjective ';' { updates.add(upd); } )*
( {!isTxnConditional}? (K_COMMIT K_TRANSACTION) | {isTxnConditional}? (K_END K_IF K_COMMIT K_TRANSACTION))
(
K_IF c=txnConditions K_THEN u=updateStatements { conditions.add(c); updates.add(u); }
( K_ELSE K_IF c=txnConditions K_THEN u=updateStatements { conditions.add(c); updates.add(u); } )*
( K_ELSE u=updateStatements { conditions.add(Collections.singletonList(new ConditionStatement.Raw(null, ConditionStatement.Kind.ELSE, null))); updates.add(u); } )?
K_END K_IF
)?
( (updateStatements) => u=updateStatements { updates.add(u); } )?
(K_COMMIT K_TRANSACTION)
{
$expr = new TransactionStatement.Parsed(assignments, select, returning, updates, conditions, references);
}
;
finally { isParsingTxn = false; }

updateStatements returns [List<ModificationStatement.Parsed> updates]
@init {
updates = new ArrayList<ModificationStatement.Parsed>();
}
: (upd=batchStatementObjective ';' { updates.add(upd); })*
;

rowDataReferences returns [List<RowDataReference.Raw> refs]
: r1=rowDataReference { refs = new ArrayList<RowDataReference.Raw>(); refs.add(r1); } (',' rN=rowDataReference { refs.add(rN); })*
;
Expand Down
175 changes: 123 additions & 52 deletions src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ public enum Kind
GT(TxnCondition.Kind.GREATER_THAN, TxnCondition.Kind.LESS_THAN),
GTE(TxnCondition.Kind.GREATER_THAN_OR_EQUAL, TxnCondition.Kind.LESS_THAN_OR_EQUAL),
LT(TxnCondition.Kind.LESS_THAN, TxnCondition.Kind.GREATER_THAN),
LTE(TxnCondition.Kind.LESS_THAN_OR_EQUAL, TxnCondition.Kind.GREATER_THAN_OR_EQUAL);

LTE(TxnCondition.Kind.LESS_THAN_OR_EQUAL, TxnCondition.Kind.GREATER_THAN_OR_EQUAL),
ELSE(TxnCondition.Kind.ELSE, null);

// TODO: Support for IN, CONTAINS, CONTAINS KEY

private final TxnCondition.Kind kind;
private final TxnCondition.Kind reversedKind;

Kind(TxnCondition.Kind kind, TxnCondition.Kind reversedKind)
{
this.kind = kind;
Expand Down Expand Up @@ -80,15 +81,18 @@ public static class Raw

public Raw(Term.Raw lhs, Kind kind, Term.Raw rhs)
{
Preconditions.checkArgument(lhs != null);
Preconditions.checkArgument((rhs == null) == (kind == Kind.IS_NOT_NULL || kind == Kind.IS_NULL));
Preconditions.checkArgument(lhs != null || kind == Kind.ELSE);
Preconditions.checkArgument((rhs == null) == (kind == Kind.IS_NOT_NULL || kind == Kind.IS_NULL || kind == Kind.ELSE));
this.lhs = lhs;
this.kind = kind;
this.rhs = rhs;
}

public ConditionStatement prepare(String keyspace, VariableSpecifications bindVariables)
{
if (kind == Kind.ELSE)
return new ConditionStatement(null, kind, null, false);

if (rhs == null)
{
// In the IS NULL/IS NOT NULL case, the reference will always be on the LHS
Expand Down Expand Up @@ -134,6 +138,8 @@ public TxnCondition createCondition(QueryOptions options)
{
switch (kind)
{
case ELSE:
return TxnCondition.Else.instance;
case IS_NOT_NULL:
case IS_NULL:
return new TxnCondition.Exists(reference.toTxnReference(options), kind.toTxnKind(reversed));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ public enum Kind
GREATER_THAN_OR_EQUAL(">=", Operator.GTE),
LESS_THAN("<", Operator.LT),
LESS_THAN_OR_EQUAL("<=", Operator.LTE),
COLUMN_CONDITIONS("COLUMN_CONDITIONS", null);
COLUMN_CONDITIONS("COLUMN_CONDITIONS", null),
ELSE("ELSE", null);

@Nonnull
private final String symbol;
Expand Down Expand Up @@ -152,6 +153,8 @@ private ConditionSerializer serializer()
return None.serializer;
case COLUMN_CONDITIONS:
return ColumnConditionsAdapter.serializer;
case ELSE:
return Else.serializer;
default:
throw new IllegalArgumentException("No serializer exists for kind " + this);
}
Expand Down Expand Up @@ -231,6 +234,43 @@ public static TxnCondition none()
return None.instance;
}

public static class Else extends TxnCondition
{
public static final Else instance = new Else();

private Else()
{
super(Kind.ELSE);
}

@Override
public String toString()
{
return kind.toString();
}

@Override
public void collect(TableMetadatas.Collector collector)
{
}

@Override
public boolean applies(TxnData data)
{
return true;
}

private static final ConditionSerializer<Else> serializer = new ConditionSerializer<>()
{
@Override
public void serialize(Else condition, TableMetadatas tables, DataOutputPlus out) {}
@Override
public Else deserialize(TableMetadatas tables, DataInputPlus in, Kind kind) { return instance; }
@Override
public long serializedSize(Else condition, TableMetadatas tables) { return 0; }
};
}

public static class Exists extends TxnCondition
{
private static final Set<Kind> KINDS = ImmutableSet.of(Kind.IS_NOT_NULL, Kind.IS_NULL);
Expand Down
148 changes: 132 additions & 16 deletions src/java/org/apache/cassandra/service/accord/txn/TxnUpdate.java
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ public long estimatedSizeOnHeap()
}
}

static class Block
public static class Block
{
private static final long EMPTY_SIZE = ObjectSizes.measure(new Block(null, null));
public static final ParameterisedUnversionedSerializer<Block, TableMetadatasAndKeys> serializer = new ParameterisedUnversionedSerializer<>()
Expand Down Expand Up @@ -293,6 +293,11 @@ public long serializedSize(Block t, TableMetadatasAndKeys p)
final BlockFragment[] fragments;
final ConditionalBlock[] conditionalBlocks;

// Must ensure the following invariants when constructing a Block
// (1) Every fragment in fragments is sorted by key order
// (2) conditionalBlocks occurs in the same order as the IF/ELSE IF/ELSE statements because the order of the array
// determines the order in which the conditions get evaluated
// (3) Within each ConditionalBlock, fragmentIds is in sorted order
Block(BlockFragment[] fragments, ConditionalBlock[] conditionalBlocks)
{
this.fragments = fragments;
Expand Down Expand Up @@ -372,26 +377,16 @@ public Block select(Keys keys)

ConditionalBlock[] outConditions;
{
List<ConditionalBlock> collect = null;
List<ConditionalBlock> collect = new ArrayList<>(conditionalBlocks.length - 1);
for (int i = 0 ; i < conditionalBlocks.length ; ++i)
{
ConditionalBlock cb = conditionalBlocks[i];
int[] cbOutFragmentIds = SortedArrays.linearIntersection(cb.fragmentIds, 0, cb.fragmentIds.length, outFragmentIds, 0, count, cachedInts());
//noinspection ArrayEquality
if (cbOutFragmentIds != cb.fragmentIds) // when arrays are equal the cb.fragmentIds gets returned unchanged, so can do a pointer check to detect a change
{
if (collect == null)
{
collect = new ArrayList<>(conditionalBlocks.length - 1);
for (int j = 0 ; j < i ; ++j) //TODO (review): why do we include the previous blocks that "should" have empty fragments, but we provide them without empty fragments?
collect.add(conditionalBlocks[j]);
}
if (cbOutFragmentIds.length > 0)
collect.add(new ConditionalBlock(cb.id, cb.condition, cbOutFragmentIds));
}

if (cbOutFragmentIds.length > 0)
collect.add(new ConditionalBlock(cb.id, cb.condition, cbOutFragmentIds));
}
if (collect == null) outConditions = conditionalBlocks;
else if (collect.isEmpty()) outConditions = NO_CONDITIONAL_BLOCKS;
if (collect.isEmpty()) outConditions = NO_CONDITIONAL_BLOCKS;
else outConditions = collect.toArray(ConditionalBlock[]::new);
}

Expand Down Expand Up @@ -576,6 +571,127 @@ public TxnUpdate(TableMetadatas tables, List<Fragment> fragments, TxnCondition c
this.preserveTimestamps = preserveTimestamps;
}

public TxnUpdate(TableMetadatas tables, List<PreTransformedBlock> preTransformedBlocks, @Nullable ConsistencyLevel cassandraCommitCL, PreserveTimestamp preserveTimestamps)
{
requireArgument(cassandraCommitCL == null || IAccordService.SUPPORTED_COMMIT_CONSISTENCY_LEVELS.contains(cassandraCommitCL));
this.tables = tables;
this.keys = sortedFragmentKeys(preTransformedBlocks);

List<Block> blocks = new ArrayList<>();
int nextConditionIndex = 0;
for (PreTransformedBlock preTransformedBlock : preTransformedBlocks)
{
Pair<Integer, Block> pair = preTransformedBlock.generateBlock(nextConditionIndex, tables);
nextConditionIndex = pair.left();
blocks.add(pair.right());
}

this.blocks = blocks;
this.cassandraCommitCL = cassandraCommitCL;
this.preserveTimestamps = preserveTimestamps;
}

public Keys sortedFragmentKeys(List<PreTransformedBlock> preTransformedBlocks)
{
List<Key> keys = new ArrayList<>();
for (PreTransformedBlock preTransformedBlock : preTransformedBlocks)
keys.addAll(preTransformedBlock.getKeys());

keys.sort(RoutableKey::compareTo);
return Keys.of(keys, k -> k);
}

public static class PreTransformedBlock
{
public TxnCondition[] conditions;
@Nullable
public List<Pair<TxnWrite.Fragment, Integer>> fragmentConditionIndexPair;
@Nullable
public List<TxnWrite.Fragment> noneConditions;

public PreTransformedBlock(TxnCondition[] conditions, List<Pair<TxnWrite.Fragment, Integer>> fragmentConditionIndexPair, List<TxnWrite.Fragment> noneConditions)
{
Invariants.require((fragmentConditionIndexPair == null && noneConditions != null) || (fragmentConditionIndexPair != null && noneConditions == null));
this.conditions = conditions;
if (fragmentConditionIndexPair != null)
fragmentConditionIndexPair.sort((l, r) -> TxnWrite.Fragment.compareKeys(l.left(), r.left()));
this.fragmentConditionIndexPair = fragmentConditionIndexPair;
if (noneConditions != null)
noneConditions.sort(Fragment::compareKeys);
this.noneConditions = noneConditions;
}

private boolean isTrailingUpdate()
{
return noneConditions != null;
}

public List<Key> getKeys()
{
List<Key> keys = new ArrayList<>();
if (isTrailingUpdate())
{
for (int i = 0; i < noneConditions.size(); i++)
keys.add(noneConditions.get(i).key);
}
else {
for (int i = 0; i < fragmentConditionIndexPair.size(); i++)
keys.add(fragmentConditionIndexPair.get(i).left().key);
}
return keys;
}

public Pair<Integer, Block> generateBlock(int conditionalBlockIndex, TableMetadatas tables)
{
if (isTrailingUpdate())
{
BlockFragment[] blockFragments = new BlockFragment[noneConditions.size()];
int[] fragmentIds = new int[noneConditions.size()];
for (int i = 0 ; i < noneConditions.size() ; ++i)
{
Fragment fragment = noneConditions.get(i);
blockFragments[i] = new BlockFragment(i, fragment.key, Fragment.FragmentSerializer.serialize(fragment, tables, Version.LATEST));
fragmentIds[i] = i;
}

SerializedTxnCondition serializedCondition = new SerializedTxnCondition(TxnCondition.none(), tables);
ConditionalBlock[] conditionalBlock = new ConditionalBlock[] { new ConditionalBlock(conditionalBlockIndex++, serializedCondition, fragmentIds) };
return Pair.create(conditionalBlockIndex, new Block(blockFragments, conditionalBlock));
}
else
{
List<List<Integer>> conditionsIndexToFragmentId = new ArrayList<>(conditions.length);
for (int i = 0; i < conditions.length; i++)
conditionsIndexToFragmentId.add(new ArrayList<>());

BlockFragment[] blockFragments = new BlockFragment[fragmentConditionIndexPair.size()];
ConditionalBlock[] conditionalBlocks = new ConditionalBlock[conditions.length];

int blockFragmentIdx = 0;
for (Pair<Fragment, Integer> pair : fragmentConditionIndexPair)
{
Fragment fragment = pair.left;
int conditionsIndex = pair.right;
blockFragments[blockFragmentIdx] = new BlockFragment(blockFragmentIdx, fragment.key, Fragment.FragmentSerializer.serialize(fragment, tables, Version.LATEST));
conditionsIndexToFragmentId.get(conditionsIndex).add(blockFragmentIdx);
blockFragmentIdx++;
}

int conditionalBlocksIdx = 0;

for (int i = 0; i < conditions.length; i++)
{
int[] fragmentIds = conditionsIndexToFragmentId.get(i).stream().mapToInt(x -> x).toArray();
SerializedTxnCondition serializedCondition = new SerializedTxnCondition(conditions[i], tables);
conditionalBlocks[conditionalBlocksIdx] = new ConditionalBlock(conditionalBlocksIdx, serializedCondition, fragmentIds);
conditionalBlocksIdx++;
}

return Pair.create(conditionalBlocksIdx, new Block(blockFragments, conditionalBlocks));
}
}
}

private TxnUpdate(TableMetadatas tables, Keys keys, List<Block> blocks, ConsistencyLevel cassandraCommitCL, PreserveTimestamp preserveTimestamps)
{
this.tables = tables;
Expand Down
Loading