dcapwell commented on PR #4572:
URL: https://github.com/apache/cassandra/pull/4572#issuecomment-3775425277
I used AI to help write these docs, and had it create test cases for each
example to make sure it actually works. While doing this it found a lot of
nice UX or issues; below is the state that it created
# Accord Future Syntax and Semantic Features
This document tracks CQL transaction syntax and semantic features that are
documented or desired but not yet implemented in the current version of
Cassandra. These features may be added in future releases.
## Arithmetic Expressions with Row References
### Issue: Row Reference Arithmetic in UPDATE SET Clauses
**Status:** Not implemented
**Parser Error:** `no viable alternative at input '-'`
**Description:**
Using arithmetic expressions with row references (LET variable fields) in
UPDATE SET clauses is not currently supported by the CQL parser.
**Example of Unsupported Syntax:**
```cql
BEGIN TRANSACTION
LET user_data = (SELECT balance FROM users WHERE id = ?);
UPDATE users SET balance = user_data.balance - 50 WHERE id = ?;
COMMIT TRANSACTION
```
**Error Message:**
```
line 1:X no viable alternative at input '-' (...SET balance =
[user_data].balance...)
```
**Current Workaround:**
Use column self-reference instead of row reference arithmetic:
```cql
BEGIN TRANSACTION
LET user_data = (SELECT balance FROM users WHERE id = ?);
IF user_data.balance > 50 THEN
UPDATE users SET balance = balance - 50 WHERE id = ?;
END IF
COMMIT TRANSACTION
```
**Impact:**
- Cannot compute new values based on LET variable fields in UPDATE SET
clauses
- Must use column self-reference (e.g., `balance = balance - 50`) instead
- Row references can still be used in IF conditions for validation
---
## Comparing Two Row References
### Issue: Comparison Between Two LET Variables
**Status:** Not implemented
**Parser Error:** `IllegalArgumentException`
**Description:**
Comparing values from two different LET variables in an IF condition is not
currently supported.
**Example of Unsupported Syntax:**
```cql
BEGIN TRANSACTION
LET account = (SELECT balance FROM accounts WHERE id = ?);
LET limit = (SELECT max_balance FROM limits WHERE type = 'standard');
IF account.balance < limit.max_balance THEN
UPDATE accounts SET balance = balance + 10 WHERE id = ?;
END IF
COMMIT TRANSACTION
```
**Current Workaround:**
Read the limit in the application layer and pass it as a parameter:
```cql
-- Application code:
-- double maxBalance = 1000.00; // Retrieved from limits table
BEGIN TRANSACTION
LET account = (SELECT balance FROM accounts WHERE id = ?);
IF account.balance < ? THEN -- Pass maxBalance
UPDATE accounts SET balance = balance + 10 WHERE id = ?;
END IF
COMMIT TRANSACTION
```
**Impact:**
- Cannot perform cross-table validations entirely within the database if
they involve comparing values between tables
- Requires application-side logic for such comparisons
---
## Range Updates and Deletes
### Issue: Updates or Deletes without Full Primary Key
**Status:** Not implemented
**Error:** `InvalidRequestException: Some clustering keys are missing`
**Description:**
Performing `UPDATE` or `DELETE` operations that match multiple rows (range
operations) by specifying only the partition key (or a partial primary key) is
not currently supported in transactions. You must specify the full primary key
for every modification.
**Example of Unsupported Syntax:**
```cql
BEGIN TRANSACTION
-- Attempting to update all sessions for a user
UPDATE user_sessions
SET status = 'invalidated'
WHERE user_id = ?; -- Missing session_id (clustering key)
COMMIT TRANSACTION
```
**Current Workaround:**
Perform a read query to identify the specific rows to modify, then issue
individual modification statements for each row within the transaction (or pass
the IDs as parameters).
```cql
-- Step 1: Query active sessions (outside transaction or in previous step)
-- SELECT session_id FROM user_sessions WHERE user_id = ?;
-- Step 2: Transaction with specific updates
BEGIN TRANSACTION
UPDATE users SET status = 'deactivated' WHERE id = ?;
-- Update known sessions individually
UPDATE user_sessions SET status = 'invalidated' WHERE user_id = ? AND
session_id = ?;
UPDATE user_sessions SET status = 'invalidated' WHERE user_id = ? AND
session_id = ?;
COMMIT TRANSACTION
```
**Impact:**
- Cannot atomically "delete all" or "update all" children records without
knowing their IDs
- Requires application to track or query IDs before modifying
---
## Arithmetic Expressions with Row References in SELECT
### Issue: Row Reference Arithmetic in SELECT Return Values
**Status:** Not implemented
**Parser Error:** `no viable alternative at input '-'`
**Description:**
Computing arithmetic expressions with row references in SELECT statements
within transactions is not supported.
**Example of Unsupported Syntax:**
```cql
BEGIN TRANSACTION
LET user_data = (SELECT balance FROM users WHERE id = ?);
UPDATE users SET balance = balance - 50 WHERE id = ?;
SELECT user_data.balance, user_data.balance - 50;
COMMIT TRANSACTION
```
**Error Message:**
```
line 1:X no viable alternative at input '-' (...SELECT user_data.balance,
user_data.balance...)
```
**Current Workaround:**
Return only the row reference fields without arithmetic:
```cql
BEGIN TRANSACTION
LET user_data = (SELECT balance FROM users WHERE id = ?);
UPDATE users SET balance = balance - 50 WHERE id = ?;
SELECT user_data.balance;
COMMIT TRANSACTION
```
**Impact:**
- Cannot return computed values based on LET variables
- Application must perform calculations on returned values
- Row reference fields can be returned individually
---
## SELECT with Row Data References Positioning
### Issue: SELECT with Row References Must Precede Modifications
**Status:** Grammar limitation
**Parser Error:** `no viable alternative at input 'SELECT'`
**Description:**
The CQL grammar requires that `SELECT rowDataReferences` (selecting only row
reference fields without FROM clause) must appear before modification
statements in a transaction. After UPDATE/INSERT/DELETE, only full SELECT
statements with FROM clauses are allowed.
**Example of Unsupported Syntax:**
```cql
BEGIN TRANSACTION
LET user_data = (SELECT balance FROM users WHERE id = ?);
UPDATE users SET balance = balance - 50 WHERE id = ?;
SELECT user_data.balance; -- Error: SELECT with row references after
UPDATE
COMMIT TRANSACTION
```
**Error Message:**
```
line 1:X no viable alternative at input 'SELECT' (... WHERE id = ?;
[SELECT]...)
```
**Current Workaround:**
Place SELECT statements before modifications, or query outside the
transaction:
```cql
-- Option 1: SELECT before UPDATE
BEGIN TRANSACTION
LET user_data = (SELECT balance FROM users WHERE id = ?);
SELECT user_data.balance; -- SELECT before UPDATE
UPDATE users SET balance = balance - 50 WHERE id = ?;
COMMIT TRANSACTION
-- Option 2: Query after transaction commits
BEGIN TRANSACTION
UPDATE users SET balance = balance - 50 WHERE id = ?;
COMMIT TRANSACTION
-- Then query outside transaction:
SELECT balance FROM users WHERE id = ?;
```
**Grammar Structure:**
According to Parser.g, the transaction structure is:
```
BEGIN TRANSACTION
[LET statements]
[SELECT selectStatement | SELECT rowDataReferences] <- Must be before
modifications
[IF condition THEN]
[modifications]
[END IF]
COMMIT TRANSACTION
```
The grammar does NOT allow SELECT statements after modification statements,
even if they are full SELECT statements with FROM clauses. The SELECT position
is fixed in the grammar structure.
**Impact:**
- ALL SELECT statements must appear before any UPDATE/INSERT/DELETE
- Cannot SELECT updated values within the same transaction
- To see results after modifications, query outside the transaction
- Row reference-only SELECTs must also precede modifications
---
## Computed LET Assignments
### Issue: LET Assignments with Computed Values
**Status:** Not implemented
**Parser Error:** Varies based on expression type
**Description:**
LET statements can only assign the result of a SELECT query. They cannot
assign computed values, arithmetic expressions, or CASE expressions.
**Example of Unsupported Syntax:**
```cql
BEGIN TRANSACTION
LET user_data = (SELECT balance FROM users WHERE id = ?);
LET new_balance = user_data.balance - 50; -- Error: computed assignment
LET discount_rate = CASE user_data.tier
WHEN 'gold' THEN 0.10
ELSE 0.0 END; -- Error: CASE expression
UPDATE users SET balance = new_balance WHERE id = ?;
COMMIT TRANSACTION
```
**Current Workaround:**
Perform computations directly in UPDATE statements or in the application
layer:
```cql
BEGIN TRANSACTION
LET user_data = (SELECT balance, tier FROM users WHERE id = ?);
-- Compute in the UPDATE statement instead
IF user_data.balance >= 50 THEN
UPDATE users SET balance = balance - 50 WHERE id = ?;
END IF
COMMIT TRANSACTION
```
**Impact:**
- Cannot store intermediate computed values in LET variables
- All computations must happen in UPDATE/INSERT SET clauses
- Complex calculations must be done in application code
- No support for CASE expressions in LET assignments
---
## SELECT After Modification Statements
### Issue: SELECT Must Precede All Modifications
**Status:** Grammar restriction
**Parser Error:** `no viable alternative at input 'SELECT'` (when after
modifications)
**Description:**
The CQL transaction grammar strictly orders statements: LET, then SELECT,
then modifications. SELECT statements (including those returning computation
results) cannot appear after any UPDATE, INSERT, or DELETE.
**Example of Unsupported Syntax:**
```cql
BEGIN TRANSACTION
LET user_data = (SELECT balance FROM users WHERE id = ?);
UPDATE users SET balance = balance - 50 WHERE id = ?;
-- Error: SELECT after UPDATE not allowed
SELECT user_data.balance, 'Update successful' as message;
COMMIT TRANSACTION
```
**Current Workaround:**
Either move SELECT before modifications or query outside the transaction:
```cql
-- Option 1: SELECT before modifications
BEGIN TRANSACTION
LET user_data = (SELECT balance FROM users WHERE id = ?);
SELECT user_data.balance; -- Before UPDATE
UPDATE users SET balance = balance - 50 WHERE id = ?;
COMMIT TRANSACTION
-- Option 2: Query after transaction
BEGIN TRANSACTION
UPDATE users SET balance = balance - 50 WHERE id = ?;
COMMIT TRANSACTION
-- Then query outside:
SELECT balance FROM users WHERE id = ?;
```
**Impact:**
- Cannot return confirmation messages with results from modifications
- Cannot SELECT updated values within the transaction
- Return values must be queried separately or selected before modifications
- Limits ability to return operation status
---
## IN and OR Operators in IF Conditions
### Issue: IN and OR Not Supported in Transaction Conditions
**Status:** Not implemented
**Parser Error:** `no viable alternative at input 'IN'` or `no viable
alternative at input 'OR'`
**Description:**
The `IN` operator and `OR` logical operator are not currently supported in
transaction `IF` conditions. Only the `AND` logical operator is available for
combining conditions.
**Example of Unsupported Syntax (IN):**
```cql
BEGIN TRANSACTION
LET order = (SELECT status FROM orders WHERE order_id = ?);
IF order.status IN ('PENDING', 'PAID') THEN
UPDATE orders SET status = 'CANCELLED' WHERE order_id = ?;
END IF
COMMIT TRANSACTION
```
**Error Message:**
```
line 1:X no viable alternative at input 'IN' (...IF order.status [IN]...)
```
**Example of Unsupported Syntax (OR):**
```cql
BEGIN TRANSACTION
LET order = (SELECT status FROM orders WHERE order_id = ?);
IF order.status = 'PENDING' OR order.status = 'PAID' THEN
UPDATE orders SET status = 'CANCELLED' WHERE order_id = ?;
END IF
COMMIT TRANSACTION
```
**Error Message:**
```
line 1:X no viable alternative at input 'OR' (...= 'PENDING' [OR]...)
```
**Current Workaround:**
Use numeric status codes and range comparisons to check multiple valid
states:
```cql
-- Schema uses integer status codes: 10=PENDING, 20=PAID, 30=SHIPPED, etc.
BEGIN TRANSACTION
LET order = (SELECT status_code FROM orders WHERE order_id = ?);
-- Use range comparison instead of IN/OR
-- status_code <= 20 means PENDING or PAID
IF order.status_code <= 20 THEN
UPDATE orders SET status_code = 99 WHERE order_id = ?; -- 99=CANCELLED
END IF
COMMIT TRANSACTION
```
**Impact:**
- Cannot use `IN (value1, value2, ...)` syntax to check membership in a set
- Cannot use `OR` to combine multiple conditions
- Only `AND` is supported for combining conditions
- Workaround requires careful design of numeric status codes to enable range
comparisons
**Grammar Reference:**
The `txnConditions` rule in Parser.g only supports `AND`:
```antlr
txnConditions returns [List<ConditionStatement.Raw> conditions]
: txnColumnCondition[conditions] ( K_AND txnColumnCondition[conditions]
)*
;
```
**Code Reference:**
`ConditionStatement.java` contains a TODO for future support:
```java
// TODO: Support for IN, CONTAINS, CONTAINS KEY
```
---
## Summary of Limitations
### Row Reference Arithmetic Limitations
**What Works:**
- Row references in IF conditions: `IF user_data.balance > 100 THEN`
- Row references in comparisons: `IF user_data.balance >= order_total THEN`
- Returning row reference fields: `SELECT user_data.balance`
- Column self-reference arithmetic: `SET balance = balance - 50`
**What Doesn't Work:**
- Arithmetic in UPDATE SET: `SET balance = user_data.balance - 50`
- Arithmetic in SELECT: `SELECT user_data.balance - 50`
- Computed LET values: `LET new_balance = user_data.balance - 50`
**Parser Status:**
The CQL parser (Parser.g) does not currently support arithmetic expressions
involving row references outside of conditional expressions.
---
## Future Enhancements
These features may be added in future Cassandra versions:
1. **Computed LET Assignments:**
```cql
LET new_balance = user_data.balance - 50;
UPDATE users SET balance = new_balance WHERE id = ?;
```
2. **Arithmetic Expressions in SET Clauses:**
```cql
UPDATE users SET balance = user_data.balance - 50 WHERE id = ?;
```
3. **Computed SELECT Returns:**
```cql
SELECT user_data.balance, user_data.balance - 50 AS new_balance;
```
---
## Testing Notes
All examples in the Cassandra documentation
(doc/modules/cassandra/pages/developing/cql/transactions*.adoc) should only
show syntax that currently works. Examples using unsupported syntax have been
removed and documented here for future reference.
Test classes validating documentation examples:
- `BasicTransactionExamplesTest` - Basic transaction patterns
- `BankingExamplesTest` - Banking and financial examples
- `EcommerceExamplesTest` - E-commerce examples
- `UserManagementExamplesTest` - User management examples
- `InOperatorTest` - Validates IN and OR operators are not supported in IF
conditions
- Other example tests in
`test/distributed/org/apache/cassandra/distributed/test/accord/example/`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]