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 998f42984f Add user docs for Accord focused on how to use, what CQL is
and isnt valid, and use cases
998f42984f is described below
commit 998f42984fb2cbf5c4bd142fb530ec7ed128559b
Author: David Capwell <[email protected]>
AuthorDate: Tue Jan 20 15:24:33 2026 -0800
Add user docs for Accord focused on how to use, what CQL is and isnt valid,
and use cases
patch by David Capwell; reviewed by Caleb Rackliffe for CASSANDRA-21127
---
doc/modules/cassandra/nav.adoc | 3 +
.../cassandra/pages/developing/cql/dml.adoc | 94 ++++
.../cassandra/pages/developing/cql/index.adoc | 3 +
.../developing/cql/transactions-examples.adoc | 496 ++++++++++++++++++++
.../developing/cql/transactions-migration.adoc | 289 ++++++++++++
.../pages/developing/cql/transactions.adoc | 500 +++++++++++++++++++++
6 files changed, 1385 insertions(+)
diff --git a/doc/modules/cassandra/nav.adoc b/doc/modules/cassandra/nav.adoc
index 52f8677c97..061b9ecb4b 100644
--- a/doc/modules/cassandra/nav.adoc
+++ b/doc/modules/cassandra/nav.adoc
@@ -43,6 +43,9 @@
*** xref:cassandra:developing/cql/types.adoc[Data types]
*** xref:cassandra:developing/cql/ddl.adoc[Data definition (DDL)]
*** xref:cassandra:developing/cql/dml.adoc[Data manipulation (DML)]
+*** xref:cassandra:developing/cql/transactions.adoc[Transactions]
+*** xref:cassandra:developing/cql/transactions-examples.adoc[Transaction
patterns]
+*** xref:cassandra:developing/cql/transactions-migration.adoc[Transaction
migration]
*** xref:cassandra:developing/cql/dynamic-data-masking.adoc[]
*** xref:cassandra:developing/cql/operators.adoc[Operators]
*** xref:cassandra:developing/cql/indexing/indexing-concepts.adoc[]
diff --git a/doc/modules/cassandra/pages/developing/cql/dml.adoc
b/doc/modules/cassandra/pages/developing/cql/dml.adoc
index 4f13ba2b0c..e1b751d708 100644
--- a/doc/modules/cassandra/pages/developing/cql/dml.adoc
+++ b/doc/modules/cassandra/pages/developing/cql/dml.adoc
@@ -474,3 +474,97 @@ partly applied.
Use the `COUNTER` option for batched counter updates. Unlike other
updates in Cassandra, counter updates are not idempotent.
+
+=== BATCH vs Transactions
+
+BATCH statements have limitations compared to Accord transactions.
Understanding the differences helps you choose the right approach for your use
case.
+
+==== BATCH Statement Capabilities
+
+BATCH statements offer:
+
+* **Single-partition atomicity**: All operations within a single partition
succeed or fail together atomically
+* **Multi-partition batching**: LOGGED batches can span multiple partitions,
but are not atomic. The batch log ensures all operations eventually complete,
but there can be a window where only some operations are visible.
+* **Single partition isolation**: Operations on the same partition are isolated
+* **Network efficiency**: Multiple operations in one round-trip
+* **Automatic timestamps**: All operations use the same timestamp by default
+* **Conditional updates**: Batches support `IF` clauses for lightweight
transactions (LWT/CAS) on a single partition. If any condition fails, the
entire batch is rejected.
+* **Basic business rule enforcement**: `IF` clauses can validate conditions
before applying changes, though limited to what CAS syntax supports
+
+==== BATCH Statement Limitations
+
+However, BATCH statements cannot provide:
+
+* **Cross-partition atomicity**: Multi-partition LOGGED batches are not truly
atomic (see above)
+* **Read-before-write patterns**: Cannot read data within the batch to make
decisions based on current values
+* **Cross-partition conditional updates**: Conditional batches (using `IF`
clauses) must operate on a single partition and table
+* **Complex conditional logic**: `IF` clauses are limited to simple
equality/inequality checks on existing column values
+
+==== When to Use Accord Transactions Instead
+
+Consider using xref:developing/cql/transactions.adoc[Accord transactions] when
you need:
+
+**Read-Modify-Write Patterns:**
+[source,cql]
+----
+-- BATCH cannot check balance before transfer
+BEGIN BATCH
+ UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
+ UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
+APPLY BATCH;
+
+-- Transaction can validate before transfer
+BEGIN TRANSACTION
+ LET sender = (SELECT balance FROM accounts WHERE user_id = 1);
+
+ IF sender.balance >= 100 THEN
+ UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
+ UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
+ END IF
+COMMIT TRANSACTION
+----
+
+**Complex Business Logic:**
+
+NOTE: Row reference arithmetic in SET clauses and comparing two row references
are not currently supported. Pass values as parameters instead.
+
+[source,cql]
+----
+-- Application code:
+-- double productPrice = 50.00; // Retrieved from products table
+
+-- Transaction can enforce multi-step business rules
+BEGIN TRANSACTION
+ LET user_account = (SELECT balance, status FROM accounts WHERE user_id = ?);
+ LET product_info = (SELECT quantity FROM products WHERE id = ?);
+
+ IF user_account.status = 'active'
+ AND user_account.balance >= ? -- Pass product_price as parameter
+ AND product_info.quantity > 0 THEN
+
+ UPDATE accounts SET balance = balance - ? WHERE user_id = ?; -- Pass
product_price
+ UPDATE products SET quantity = quantity - 1 WHERE id = ?;
+ INSERT INTO orders (id, user_id, product_id, amount) VALUES (?, ?, ?, ?);
-- Pass product_price
+ END IF
+COMMIT TRANSACTION
+----
+
+**Error Prevention:**
+Transactions prevent invalid operations like:
+
+* Overdrawing accounts
+* Selling out-of-stock items
+* Creating duplicate records
+* Violating business constraints
+
+==== When to Continue Using BATCH
+
+BATCH statements remain appropriate for:
+
+* **Simple multi-table updates** without conditional logic
+* **Same-partition operations** where isolation is sufficient
+* **High-throughput scenarios** where transaction overhead isn't justified
+* **Counter updates** (transactions don't support counters)
+* **Backward compatibility** with existing applications
+
+For detailed transaction syntax and examples, see the
xref:developing/cql/transactions.adoc[Transactions] and
xref:developing/cql/transactions-examples.adoc[Transaction Examples]
documentation.
diff --git a/doc/modules/cassandra/pages/developing/cql/index.adoc
b/doc/modules/cassandra/pages/developing/cql/index.adoc
index 97204ce568..5af9f814c7 100644
--- a/doc/modules/cassandra/pages/developing/cql/index.adoc
+++ b/doc/modules/cassandra/pages/developing/cql/index.adoc
@@ -13,6 +13,9 @@ For that reason, when used in this document, these terms
(tables, rows and colum
* xref:developing/cql/types.adoc[Data types]
* xref:developing/cql/ddl.adoc[Data definition language]
* xref:developing/cql/dml.adoc[Data manipulation language]
+* xref:developing/cql/transactions.adoc[Transactions]
+* xref:developing/cql/transactions-examples.adoc[Transaction patterns]
+* xref:developing/cql/transactions-migration.adoc[Transaction migration]
* xref:developing/cql/dynamic-data-masking.adoc[Dynamic data masking]
* xref:developing/cql/operators.adoc[Operators]
* xref:developing/cql/indexing/indexing-concepts.adoc[Indexing]
diff --git
a/doc/modules/cassandra/pages/developing/cql/transactions-examples.adoc
b/doc/modules/cassandra/pages/developing/cql/transactions-examples.adoc
new file mode 100644
index 0000000000..be8cab501f
--- /dev/null
+++ b/doc/modules/cassandra/pages/developing/cql/transactions-examples.adoc
@@ -0,0 +1,496 @@
+= Accord Transaction Design Patterns
+:page-nav-title: Transaction Patterns
+
+This page provides common patterns and advanced design patterns for Accord
transactions. These patterns solve common distributed system challenges that
were difficult or impossible to address with eventual consistency.
+
+For basic syntax and getting started, see
xref:developing/cql/transactions.adoc[Accord Transactions]. For migration from
LWT/BATCH, see xref:developing/cql/transactions-migration.adoc[Transaction
Migration].
+
+== Common Patterns
+
+=== Read-Modify-Write
+
+The fundamental transaction pattern: read current state, check a condition,
then write. This covers conditional updates, conditional inserts, and any
operation that must validate preconditions.
+
+**Conditional update (e.g., sufficient balance):**
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET sender_account = (SELECT balance FROM accounts WHERE id = ?);
+
+ IF sender_account.balance >= ? THEN
+ UPDATE accounts SET balance = balance - ? WHERE id = ?;
+ UPDATE accounts SET balance = balance + ? WHERE id = ?;
+
+ INSERT INTO transactions (id, from_account, to_account, amount, timestamp)
+ VALUES (?, ?, ?, ?, toTimestamp(now()));
+ END IF
+COMMIT TRANSACTION
+----
+
+**Conditional insert (e.g., prevent duplicates):**
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET existing_user = (SELECT user_id FROM email_index WHERE email = ? LIMIT
1);
+
+ IF existing_user IS NULL THEN
+ INSERT INTO users (id, email, created_at, status)
+ VALUES (?, ?, toTimestamp(now()), 'active');
+
+ INSERT INTO email_index (email, user_id) VALUES (?, ?);
+
+ INSERT INTO user_profiles (user_id, display_name)
+ VALUES (?, ?);
+ END IF
+COMMIT TRANSACTION
+----
+
+=== Multi-Table Updates
+
+Maintain referential integrity across tables:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ UPDATE users SET status = 'suspended', suspended_at = toTimestamp(now())
+ WHERE id = ?;
+
+ UPDATE orders SET status = 'cancelled'
+ WHERE order_id = ?;
+
+ INSERT INTO audit_log (id, user_id, action, timestamp)
+ VALUES (?, ?, 'user_suspended', toTimestamp(now()));
+COMMIT TRANSACTION
+----
+
+=== Cross-Partition Transactions
+
+Coordinate updates across different partitions (see
xref:developing/cql/transactions.adoc#row-reference-limitations[row reference
limitations] for parameter passing):
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET user_data = (SELECT balance, status FROM users WHERE id = ?);
+
+ IF user_data.status = 'active' AND user_data.balance >= ? THEN
+ UPDATE users SET balance = balance - ? WHERE id = ?;
+
+ INSERT INTO orders (id, user_id, total, status, created_at)
+ VALUES (?, ?, ?, 'confirmed', toTimestamp(now()));
+
+ UPDATE inventory SET quantity = quantity - ? WHERE product_id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+== Pattern: Synchronous Unique Constraints
+
+Cassandra's primary key enforces uniqueness, but what if you need uniqueness
on a non-primary-key column like `email` or `username`? This pattern uses
user-maintained lookup tables to enforce multiple unique constraints atomically.
+
+=== The Challenge
+
+You have a `users` table keyed by `user_id`, but you also need:
+
+* Unique `email` addresses
+* Unique `username` values
+* The ability to **change** email or username while maintaining uniqueness
+
+=== Schema
+
+[source,cql]
+----
+CREATE TABLE users (
+ user_id uuid PRIMARY KEY,
+ username text,
+ email text,
+ display_name text,
+ created_at timestamp
+) WITH transactional_mode = 'full';
+
+-- Lookup tables for uniqueness enforcement
+CREATE TABLE username_index (
+ username text PRIMARY KEY,
+ user_id uuid
+) WITH transactional_mode = 'full';
+
+CREATE TABLE email_index (
+ email text PRIMARY KEY,
+ user_id uuid
+) WITH transactional_mode = 'full';
+----
+
+=== Creating a User with Unique Constraints
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET existing_username = (SELECT user_id FROM username_index WHERE username =
? LIMIT 1);
+ LET existing_email = (SELECT user_id FROM email_index WHERE email = ? LIMIT
1);
+
+ IF existing_username IS NULL AND existing_email IS NULL THEN
+ INSERT INTO users (user_id, username, email, display_name, created_at)
+ VALUES (?, ?, ?, ?, toTimestamp(now()));
+
+ INSERT INTO username_index (username, user_id) VALUES (?, ?);
+ INSERT INTO email_index (email, user_id) VALUES (?, ?);
+ END IF
+COMMIT TRANSACTION
+----
+
+=== Changing a Username
+
+Renaming requires atomically: (1) verifying the new name is available, (2)
deleting the old index entry, and (3) inserting the new one. Without
transactions, a crash between steps could leave orphaned index entries or allow
duplicates.
+
+[source,cql]
+----
+-- Application provides: user_id, old_username, new_username
+BEGIN TRANSACTION
+ LET current_user = (SELECT username FROM users WHERE user_id = ?);
+ LET new_name_owner = (SELECT user_id FROM username_index WHERE username = ?
LIMIT 1);
+
+ -- Verify: user exists, old username matches, new username is available
+ IF current_user IS NOT NULL
+ AND current_user.username = ? -- old_username parameter
+ AND new_name_owner IS NULL THEN
+
+ -- Update the user record
+ UPDATE users SET username = ? WHERE user_id = ?; -- new_username
+
+ -- Atomically swap index entries
+ DELETE FROM username_index WHERE username = ?; -- old_username
+ INSERT INTO username_index (username, user_id) VALUES (?, ?); --
new_username, user_id
+ END IF
+COMMIT TRANSACTION
+----
+
+This pattern ensures the index tables are always consistent with the `users`
table, even under concurrent modifications or partial failures.
+
+== Pattern: Distributed State Machine
+
+Many business objects follow a lifecycle with strict state transitions. An
order might be `PENDING` -> `PAID` -> `SHIPPED` -> `DELIVERED`. Without
transactions, concurrent operations (e.g., "cancel" and "ship") could both
succeed, leaving the system in an invalid state.
+
+=== The Challenge
+
+* Ensure state transitions follow valid paths
+* Prevent race conditions between competing operations
+* Maintain audit trail of transitions
+
+NOTE: The `IN` and `OR` operators are not currently supported in transaction
`IF` conditions. To check multiple valid states, use **numeric status codes**
with range comparisons (e.g., `status_code < 30` to mean "any state before
SHIPPED").
+
+=== Schema
+
+[source,cql]
+----
+CREATE TABLE orders (
+ order_id uuid PRIMARY KEY,
+ customer_id uuid,
+ status_code int, -- 10=PENDING, 20=PAID, 30=SHIPPED, 40=DELIVERED,
99=CANCELLED
+ status_name text,
+ total_amount decimal,
+ updated_at timestamp
+) WITH transactional_mode = 'full';
+
+CREATE TABLE order_status_history (
+ order_id uuid,
+ transition_time timestamp,
+ from_status int,
+ to_status int,
+ actor_id uuid,
+ PRIMARY KEY (order_id, transition_time)
+) WITH transactional_mode = 'full';
+----
+
+=== Valid Transition: PAID -> SHIPPED
+
+[source,cql]
+----
+-- Application provides: order_id, actor_id
+BEGIN TRANSACTION
+ LET current_order = (SELECT status_code FROM orders WHERE order_id = ?);
+
+ -- Only allow transition from PAID (20) state
+ IF current_order IS NOT NULL AND current_order.status_code = 20 THEN
+ UPDATE orders
+ SET status_code = 30, status_name = 'SHIPPED', updated_at =
toTimestamp(now())
+ WHERE order_id = ?;
+
+ INSERT INTO order_status_history (order_id, transition_time, from_status,
to_status, actor_id)
+ VALUES (?, toTimestamp(now()), 20, 30, ?);
+ END IF
+COMMIT TRANSACTION
+----
+
+=== Handling Cancellation (Competing Transition)
+
+Cancellation is only valid from certain states. If a "ship" and "cancel"
operation race, exactly one will succeed.
+
+[source,cql]
+----
+-- Application provides: order_id, actor_id
+BEGIN TRANSACTION
+ LET current_order = (SELECT status_code, customer_id, total_amount FROM
orders WHERE order_id = ?);
+
+ -- Cancellation allowed only from PENDING (10) or PAID (20) states.
+ -- Use range comparison (status_code <= 20) since IN/OR are not supported.
+ IF current_order IS NOT NULL
+ AND current_order.status_code <= 20 THEN
+
+ UPDATE orders
+ SET status_code = 99, status_name = 'CANCELLED', updated_at =
toTimestamp(now())
+ WHERE order_id = ?;
+
+ INSERT INTO order_status_history (order_id, transition_time, from_status,
to_status, actor_id)
+ VALUES (?, toTimestamp(now()), current_order.status_code, 99, ?);
+ END IF
+COMMIT TRANSACTION
+----
+
+If a concurrent "ship" operation already moved the order to `SHIPPED` (30),
the `status_code <= 20` condition fails and the cancellation is rejected. The
application can check the result and inform the user.
+
+== Pattern: Synchronous Denormalization
+
+Cassandra best practices often involve denormalizing data for read
performance. Keeping summary tables in sync with detail tables has
traditionally been eventually consistent. Accord enables **synchronous
denormalization** where aggregates are always accurate.
+
+=== The Challenge
+
+You have a `posts` table and want to maintain accurate counts per author
without using counters (which aren't supported in transactions) and without
eventual consistency lag.
+
+=== Schema
+
+[source,cql]
+----
+CREATE TABLE posts (
+ post_id uuid PRIMARY KEY,
+ author_id uuid,
+ title text,
+ content text,
+ status text, -- draft, published, archived
+ created_at timestamp
+) WITH transactional_mode = 'full';
+
+CREATE TABLE author_stats (
+ author_id uuid PRIMARY KEY,
+ draft_count bigint,
+ published_count bigint,
+ archived_count bigint,
+ last_post_at timestamp
+) WITH transactional_mode = 'full';
+----
+
+=== Publishing a New Post (Increment Count)
+
+This example demonstrates **idempotent creation**: if the application retries
a failed publish request, the post won't be duplicated and the counter won't be
incremented twice.
+
+[source,cql]
+----
+-- Application provides: post_id, author_id, title, content
+BEGIN TRANSACTION
+ -- Check if this post already exists (idempotency guard)
+ LET existing_post = (SELECT status FROM posts WHERE post_id = ?);
+
+ IF existing_post IS NULL THEN
+ -- Create the post
+ INSERT INTO posts (post_id, author_id, title, content, status, created_at)
+ VALUES (?, ?, ?, ?, 'published', toTimestamp(now()));
+
+ -- Synchronously update the count exactly once
+ UPDATE author_stats
+ SET published_count = published_count + 1,
+ last_post_at = toTimestamp(now())
+ WHERE author_id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+=== Changing Post Status (Transfer Between Counts)
+
+When a post moves from `published` to `archived`, both counts must update
atomically.
+
+[source,cql]
+----
+-- Application provides: post_id, author_id
+BEGIN TRANSACTION
+ LET current_post = (SELECT status, author_id FROM posts WHERE post_id = ?);
+
+ IF current_post IS NOT NULL AND current_post.status = 'published' THEN
+ -- Update post status
+ UPDATE posts SET status = 'archived' WHERE post_id = ?;
+
+ -- Atomically transfer between counts
+ UPDATE author_stats
+ SET published_count = published_count - 1,
+ archived_count = archived_count + 1
+ WHERE author_id = ?; -- author_id passed as parameter
+ END IF
+COMMIT TRANSACTION
+----
+
+=== Deleting a Post (Decrement Count)
+
+[source,cql]
+----
+-- Application provides: post_id, author_id, current_status
+-- Application must query the post first to get status and author_id
+BEGIN TRANSACTION
+ LET current_post = (SELECT status FROM posts WHERE post_id = ?);
+
+ IF current_post IS NOT NULL AND current_post.status = ? THEN --
current_status parameter
+ -- Delete the post
+ DELETE FROM posts WHERE post_id = ?;
+
+ -- Decrement the appropriate counter based on status
+ -- Application passes which counter to decrement based on current_status
+ UPDATE author_stats
+ SET published_count = published_count - ? -- pass 1 if published, 0
otherwise
+ WHERE author_id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+== Pattern: Multi-Entity Referential Integrity
+
+Relational databases use foreign keys to prevent orphaned records. In
Cassandra, you can achieve similar guarantees with transactions.
+
+=== The Challenge
+
+* A `Task` must belong to an existing `Project`
+* When a `Project` is deleted, handle its `Tasks` appropriately
+* Prevent creating tasks for non-existent projects
+
+=== Schema
+
+[source,cql]
+----
+CREATE TABLE projects (
+ project_id uuid PRIMARY KEY,
+ name text,
+ owner_id uuid,
+ status text, -- active, completed, deleted
+ task_count bigint,
+ created_at timestamp
+) WITH transactional_mode = 'full';
+
+CREATE TABLE tasks (
+ task_id uuid PRIMARY KEY,
+ project_id uuid,
+ title text,
+ status text, -- open, in_progress, done
+ assignee_id uuid,
+ created_at timestamp
+) WITH transactional_mode = 'full';
+
+-- Index for finding tasks by project (for cleanup operations)
+CREATE TABLE tasks_by_project (
+ project_id uuid,
+ task_id uuid,
+ title text,
+ status text,
+ PRIMARY KEY (project_id, task_id)
+) WITH transactional_mode = 'full';
+----
+
+=== Creating a Task (Enforce Parent Exists)
+
+[source,cql]
+----
+-- Application provides: task_id, project_id, title, assignee_id
+BEGIN TRANSACTION
+ LET project = (SELECT status FROM projects WHERE project_id = ?);
+
+ -- Only create task if project exists and is active
+ IF project IS NOT NULL AND project.status = 'active' THEN
+ INSERT INTO tasks (task_id, project_id, title, status, assignee_id,
created_at)
+ VALUES (?, ?, ?, 'open', ?, toTimestamp(now()));
+
+ INSERT INTO tasks_by_project (project_id, task_id, title, status)
+ VALUES (?, ?, ?, 'open');
+
+ UPDATE projects SET task_count = task_count + 1 WHERE project_id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+=== Soft-Deleting a Project
+
+Rather than cascading deletes (which would require iterating over all tasks),
mark the project as deleted. Tasks can be cleaned up asynchronously or remain
for audit purposes.
+
+[source,cql]
+----
+-- Application provides: project_id
+BEGIN TRANSACTION
+ LET project = (SELECT status FROM projects WHERE project_id = ?);
+
+ IF project IS NOT NULL AND project.status = 'active' THEN
+ UPDATE projects
+ SET status = 'deleted'
+ WHERE project_id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+Future task operations will fail the `project.status = 'active'` check,
preventing modifications to a deleted project's tasks.
+
+=== Moving a Task Between Projects
+
+This pattern ensures both projects exist and are active, and maintains
accurate task counts.
+
+[source,cql]
+----
+-- Application provides: task_id, old_project_id, new_project_id, task_title,
task_status
+BEGIN TRANSACTION
+ LET task = (SELECT project_id, title, status FROM tasks WHERE task_id = ?);
+ LET old_project = (SELECT status FROM projects WHERE project_id = ?); --
old_project_id
+ LET new_project = (SELECT status FROM projects WHERE project_id = ?); --
new_project_id
+
+ IF task IS NOT NULL
+ AND task.project_id = ? -- verify task belongs to old_project_id
+ AND old_project.status = 'active'
+ AND new_project.status = 'active' THEN
+
+ -- Update task's project reference
+ UPDATE tasks SET project_id = ? WHERE task_id = ?; -- new_project_id
+
+ -- Update denormalized index: remove from old, add to new
+ DELETE FROM tasks_by_project WHERE project_id = ? AND task_id = ?; --
old_project_id
+ INSERT INTO tasks_by_project (project_id, task_id, title, status)
+ VALUES (?, ?, ?, ?); -- new_project_id, task_id, task_title, task_status
+
+ -- Update counts on both projects
+ UPDATE projects SET task_count = task_count - 1 WHERE project_id = ?; --
old_project_id
+ UPDATE projects SET task_count = task_count + 1 WHERE project_id = ?; --
new_project_id
+ END IF
+COMMIT TRANSACTION
+----
+
+== Summary
+
+These patterns demonstrate how Accord transactions solve problems that were
previously difficult in Cassandra:
+
+|===
+| Pattern | Problem Solved | Key Technique
+
+| Synchronous Unique Constraints
+| Non-primary-key uniqueness
+| Lookup index tables with atomic swap
+
+| Distributed State Machine
+| Race conditions in status changes
+| IF condition guards valid transitions
+
+| Synchronous Denormalization
+| Stale aggregate counts, duplicate increments on retry
+| Idempotent creation with atomic detail + summary updates
+
+| Multi-Entity Referential Integrity
+| Orphaned child records
+| Parent existence check before child operations
+|===
+
+All patterns share common principles:
+
+* **Read what you need**: Use LET to capture current state
+* **Guard with IF**: Validate preconditions before modifications
+* **Atomic updates**: All changes succeed or fail together
+* **Pass computed values as parameters**: Row-reference arithmetic in
SET/VALUES is not supported
diff --git
a/doc/modules/cassandra/pages/developing/cql/transactions-migration.adoc
b/doc/modules/cassandra/pages/developing/cql/transactions-migration.adoc
new file mode 100644
index 0000000000..6b237a4971
--- /dev/null
+++ b/doc/modules/cassandra/pages/developing/cql/transactions-migration.adoc
@@ -0,0 +1,289 @@
+= Migrating to Accord Transactions
+:page-nav-title: Transaction Migration
+
+This page covers migrating existing CQL patterns to Accord transactions,
including LWT (Paxos), BATCH statements, and multi-statement workflows.
+
+For transaction syntax and usage, see
xref:developing/cql/transactions.adoc[Accord Transactions]. For design
patterns, see xref:developing/cql/transactions-examples.adoc[Transaction
Patterns].
+
+== From Light Weight Transactions (LWT)
+
+=== IF EXISTS
+
+**Before (LWT):**
+[source,cql]
+----
+UPDATE accounts SET balance = balance - 100
+WHERE user_id = 12345
+IF EXISTS;
+----
+
+**After (Accord):**
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET account_data = (SELECT balance FROM accounts WHERE user_id = 12345);
+
+ IF account_data IS NOT NULL THEN
+ UPDATE accounts SET balance = balance - 100 WHERE user_id = 12345;
+ END IF
+COMMIT TRANSACTION
+----
+
+=== IF NOT EXISTS
+
+**Before (LWT):**
+[source,cql]
+----
+INSERT INTO users (id, email, status)
+VALUES (?, ?, 'active')
+IF NOT EXISTS;
+----
+
+**After (Accord):**
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET existing_user = (SELECT id FROM users WHERE id = ? LIMIT 1);
+
+ IF existing_user IS NULL THEN
+ INSERT INTO users (id, email, status) VALUES (?, ?, 'active');
+ END IF
+COMMIT TRANSACTION
+----
+
+=== IF column = null (NULL Comparison)
+
+[IMPORTANT]
+====
+LWT and Accord handle `IF column = null` differently. See <<Null Handling>>
for complete details.
+====
+
+LWT treats `column = null` as "column is null," but Accord follows SQL
semantics where `column = null` is always FALSE.
+
+**Before (LWT):**
+[source,cql]
+----
+-- LWT: Matches when email is null/missing
+UPDATE users SET email = '[email protected]'
+WHERE id = ?
+IF email = null;
+----
+
+**After (Accord):**
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET user_data = (SELECT email FROM users WHERE id = ?);
+
+ -- Must use IS NULL, not = null
+ IF user_data IS NOT NULL AND user_data.email IS NULL THEN
+ UPDATE users SET email = '[email protected]' WHERE id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+=== Complex CAS with Multiple Operations
+
+**Before (Multiple LWT operations with race condition risk):**
+[source,cql]
+----
+UPDATE accounts SET balance = balance - 50
+WHERE user_id = 12345 AND balance >= 50
+IF balance >= 50;
+
+-- Separate operation (not atomic with above)
+INSERT INTO transaction_log (id, user_id, amount, timestamp)
+VALUES (?, 12345, -50, toTimestamp(now()));
+----
+
+**After (Single atomic transaction):**
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET account = (SELECT balance FROM accounts WHERE user_id = 12345);
+
+ IF account.balance >= 50 THEN
+ UPDATE accounts SET balance = balance - 50 WHERE user_id = 12345;
+
+ INSERT INTO transaction_log (id, user_id, amount, timestamp)
+ VALUES (?, 12345, -50, toTimestamp(now()));
+ END IF
+COMMIT TRANSACTION
+----
+
+== From BATCH Statements
+
+Single-partition BATCH provides atomicity (all mutations apply or none do).
Multi-partition (LOGGED) BATCH does not guarantee atomicity — it uses a
batchlog for best-effort delivery. CAS can be used with BATCH for conditional
checks, but only on a single partition, and if the condition fails the entire
batch is rejected.
+
+Accord transactions provide atomicity across multiple partitions with richer
conditional logic:
+
+**Before (Multi-partition BATCH - no atomicity guarantee, no condition
checking):**
+[source,cql]
+----
+BEGIN BATCH
+ UPDATE users SET balance = 400 WHERE id = ?; -- Application pre-computes
new balance
+ INSERT INTO orders (id, user_id, amount) VALUES (?, ?, 100);
+APPLY BATCH;
+-- Problem: No atomicity across partitions, can't check if balance is
sufficient!
+-- Application had to read balance separately, race condition possible.
+----
+
+**After (Transaction with condition and cross-partition atomicity):**
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+ IF user_data.balance >= 100 THEN
+ UPDATE users SET balance = balance - 100 WHERE id = ?;
+ INSERT INTO orders (id, user_id, amount) VALUES (?, ?, 100);
+ END IF
+COMMIT TRANSACTION
+----
+
+For single-partition operations that already use CAS BATCH, transactions
provide equivalent guarantees with more flexible conditions:
+
+**Before (CAS BATCH - single partition, single table only; condition fails =
nothing applies):**
+[source,cql]
+----
+BEGIN BATCH
+ UPDATE users SET balance = balance - 100 WHERE id = ?
+ IF balance >= 100;
+ UPDATE users SET last_action = 'debit' WHERE id = ?;
+APPLY BATCH;
+----
+
+**After (Transaction - same guarantees, works across partitions and tables):**
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+ IF user_data.balance >= 100 THEN
+ UPDATE users SET balance = balance - 100 WHERE id = ?;
+ INSERT INTO user_history (user_id, action) VALUES (?, 'debit');
+ END IF
+COMMIT TRANSACTION
+----
+
+== Null Handling
+
+[IMPORTANT]
+====
+Accord transactions follow SQL standard null semantics, which differ from LWT
(Paxos) behavior. This is a critical difference when migrating from LWT to
Accord.
+====
+
+=== LWT vs Accord: NULL Comparison Behavior
+
+|===
+| Condition | Column Value | LWT (Paxos) Result | Accord Result
+
+| `IF column = null`
+| Column is NULL/missing
+| **TRUE** (matches)
+| **FALSE** (never matches)
+
+| `IF column = null`
+| Column has a value
+| FALSE
+| FALSE
+
+| `IF column IS NULL`
+| Column is NULL/missing
+| TRUE
+| TRUE
+
+| `IF column != null`
+| Column is NULL/missing
+| FALSE
+| **FALSE** (null comparisons always false)
+
+| `IF column != null`
+| Column has a value
+| TRUE
+| TRUE
+|===
+
+=== Why the Difference?
+
+**LWT (Paxos) behavior:** LWT treats `column = null` as a special case meaning
"column is null or missing." This is a Cassandra-specific convenience that
deviates from SQL standards.
+
+**Accord behavior:** Accord follows SQL standard semantics where `NULL`
represents an unknown value. Comparing anything to `NULL` using `=`, `!=`, `<`,
`>`, etc. always returns `FALSE` (or more precisely, `UNKNOWN`, which is
treated as `FALSE` in conditionals). This is because you cannot know if an
unknown value equals another value.
+
+=== Migration from LWT
+
+If your LWT code uses `IF column = null`, you must update it when migrating to
Accord transaction syntax.
+
+NOTE: This does not affect LWT statements executed on Accord-enabled tables.
Those statements retain their original LWT null semantics.
+
+**Before (LWT):**
+[source,cql]
+----
+-- LWT: This returns TRUE when balance is null/missing
+UPDATE users SET balance = 100 WHERE id = ? IF balance = null;
+----
+
+**After (Accord):**
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+ -- Use IS NULL for null checks
+ IF user_data IS NOT NULL AND user_data.balance IS NULL THEN
+ UPDATE users SET balance = 100 WHERE id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+TIP: Null checks are recursive. If `user_data` is null (row doesn't exist),
then `user_data.balance` is also null. So `IF user_data.balance IS NULL` will
be true both when the row doesn't exist AND when the row exists but the column
is null. Use `user_data IS NOT NULL` first only if you need to distinguish
between these cases.
+
+=== Common Null Patterns
+
+**Check if a column is null (row missing OR column unset):**
+[source,cql]
+----
+IF user_data.balance IS NULL THEN
+ -- Either row doesn't exist, OR row exists but balance is null
+END IF
+----
+
+**Check if a row exists (regardless of column value):**
+[source,cql]
+----
+IF user_data IS NOT NULL THEN
+ -- Row exists (balance could be null or have a value)
+END IF
+----
+
+**Check if a column is null but row exists:**
+[source,cql]
+----
+IF user_data IS NOT NULL AND user_data.balance IS NULL THEN
+ -- Row exists AND balance is null (distinguishes from missing row)
+END IF
+----
+
+**Check if a column has a specific value:**
+[source,cql]
+----
+IF user_data IS NOT NULL AND user_data.balance = 100 THEN
+ -- Row exists AND balance equals 100
+END IF
+----
+
+**Check if a row does not exist:**
+[source,cql]
+----
+IF user_data IS NULL THEN
+ -- Row does not exist
+END IF
+----
+
+=== Null Propagation Rules
+
+* Any comparison with null using `=`, `!=`, `<`, `>`, `<=`, `>=` returns
**FALSE**
+* Only `IS NULL` and `IS NOT NULL` can meaningfully test for null
+* Arithmetic operations with null return null
+* Null columns in row references return null (not errors)
+* Accessing a field on a null row reference (e.g., `missing_row.column`)
returns null
diff --git a/doc/modules/cassandra/pages/developing/cql/transactions.adoc
b/doc/modules/cassandra/pages/developing/cql/transactions.adoc
new file mode 100644
index 0000000000..8e30fbdd68
--- /dev/null
+++ b/doc/modules/cassandra/pages/developing/cql/transactions.adoc
@@ -0,0 +1,500 @@
+= Accord Transactions
+:page-nav-title: Transactions
+
+Accord provides strong consistency and ACID guarantees for Cassandra
operations.
+When enabled on a table, **all CQL operations automatically execute through
Accord** - no code changes required.
+For complex multi-step operations, explicit transaction syntax (`BEGIN
TRANSACTION ... COMMIT TRANSACTION`) allows you to read, apply conditions, and
write atomically across multiple partitions and tables.
+
+== Overview
+
+=== Key Benefits
+
+* **Automatic Strong Consistency**: Normal CQL reads and writes become
linearizable when `transactional_mode='full'`
+* **ACID Guarantees**: Atomicity, Consistency, Isolation, and Durability
across multiple operations
+* **Multi-Partition Consistency**: Coordinate updates across different
partition keys
+* **Multi-Table Support**: Update multiple tables atomically within a single
transaction
+* **Complex Business Logic**: Support for conditional operations with multiple
steps
+
+=== When to Use Explicit Transactions
+
+While normal CQL operations are automatically transactional with
`transactional_mode='full'`, use explicit `BEGIN TRANSACTION ... COMMIT
TRANSACTION` syntax when you need:
+
+* **Read-Modify-Write Patterns**: Check a condition before making changes
+* **Complex Business Logic**: Multi-step operations that must be atomic
+* **Cross-Partition Operations**: Updates that span multiple partition keys
+* **Multi-Table Atomicity**: Ensure related changes across tables succeed or
fail together
+
+=== Safety & Consistency
+
+Accord ensures data integrity through:
+
+* **Strict Serializability**: Transactions execute as if in a single, total
order that respects real-time ordering
+* **Conflict Detection**: Automatic handling of concurrent access to the same
data
+* **Atomic Commitment**: All changes commit together or none at all
+* **Durable Writes**: Committed transactions survive node failures
+
+== Getting Started
+
+=== Prerequisites
+
+Before using transactions:
+
+. **Enable Accord globally** in `cassandra.yaml`:
++
+[source,yaml]
+----
+accord:
+ enabled: true
+----
+
+. **Enable transactional mode on tables**:
++
+[source,cql]
+----
+CREATE TABLE users (
+ id UUID PRIMARY KEY,
+ email text,
+ balance decimal
+) WITH transactional_mode = 'full';
+----
+
+See <<transactional-modes>> for detailed mode explanations.
+
+=== Normal CQL Operations Are Transactional
+
+When a table has `transactional_mode='full'`, your existing CQL statements are
automatically executed through Accord. **You do not need to rewrite your
application code.**
+
+[source,cql]
+----
+-- These normal CQL operations are automatically transactional:
+
+-- Reads are executed through Accord
+SELECT id, email, balance FROM users WHERE id =
123e4567-e89b-12d3-a456-426614174000;
+
+-- Writes are executed through Accord
+INSERT INTO users (id, email, balance) VALUES
(123e4567-e89b-12d3-a456-426614174000, '[email protected]', 100.00);
+
+UPDATE users SET balance = 50.00 WHERE id =
123e4567-e89b-12d3-a456-426614174000;
+
+DELETE FROM users WHERE id = 123e4567-e89b-12d3-a456-426614174000;
+----
+
+Each statement executes as an individual Accord transaction, providing
linearizability, consistency, and durability. Migrating to Accord can be as
simple as enabling `transactional_mode='full'` on your tables. See
<<transactional-modes>> for all available modes.
+
+[[transactional-modes]]
+== Transactional Modes
+
+Tables must be configured with one of these transactional modes:
+
+=== transactional_mode='off' (Default)
+
+* No Accord transaction support
+* Uses traditional Cassandra behavior
+* Lightweight transactions use Paxos protocol
+* Cannot participate in Accord transactions
+
+**When to Use:**
+
+* Tables not ready for transaction migration
+* High-throughput tables where transaction overhead isn't justified
+* Tables with existing Paxos-based logic that works well
+
+[source,cql]
+----
+CREATE TABLE tbl (...)
+WITH transactional_mode = 'off';
+----
+
+[[transactional_mode_mixed_reads]]
+=== transactional_mode='mixed_reads'
+
+NOTE: Most users should migrate directly from `off` to `full`. This mode is
only needed for specific scenarios where you must mix transactional and
non-transactional access on the same table during a transition period.
+
+* Non-SERIAL writes are routed through Accord but committed at the supplied
consistency level
+* Allows non-SERIAL reads to see transactionally written data
+* Blocking read repair is routed through Accord to avoid exposing uncommitted
data
+
+**When to Use:**
+
+* Applications that **require** mixed transactional and non-transactional
access on the same table simultaneously
+
+**Trade-offs:**
+
+* **Slower transactions**: Accord cannot perform single-replica read
optimization because it must ensure data is readable at the non-SERIAL
consistency level
+* **Read repair overhead**: Accord must repair stale replicas during reads
+* **Not recommended for most users**: Direct migration from `off` to `full` is
simpler and provides better performance
+
+[source,cql]
+----
+ALTER TABLE tbl WITH transactional_mode = 'mixed_reads';
+----
+
+=== transactional_mode='full' (Recommended)
+
+* All reads and writes must occur through Accord transactions
+* Enables single-replica reads since non-transactional readers don't exist
+* Best transaction performance
+
+**When to Use:**
+
+* New tables that will use transactions
+* Tables migrating from `off` mode (recommended for most users)
+* Maximum transaction performance required
+
+**Why this mode is faster:**
+
+* **Single-replica reads**: Accord can read from a single replica and still
provide correct results
+* **No read repair overhead**: Accord doesn't need to repair data for
non-transactional readers
+
+[source,cql]
+----
+CREATE TABLE tbl (...)
+WITH transactional_mode = 'full';
+----
+
+== Transaction Syntax
+
+=== Basic Structure
+
+All transactions follow this pattern:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ [LET assignments]
+ [SELECT statements]
+ [IF conditions THEN]
+ [modification statements]
+ [END IF]
+COMMIT TRANSACTION
+----
+
+=== LET Assignments
+
+LET statements read data and bind it to variables for use later in the
transaction:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET user_data = (SELECT id, balance FROM users WHERE id = ?);
+ LET account_data = (SELECT account_type FROM accounts WHERE user_id = ?);
+
+ IF user_data.balance > 100 AND account_data.account_type = 'premium' THEN
+ UPDATE users SET balance = balance - 50 WHERE id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+**LET Requirements:**
+
+* Each LET must specify a unique variable name
+* SELECT must return exactly one row (use `LIMIT 1` if needed)
+* All partition key columns must be specified with equality operators
+* Cannot use `ORDER BY`, `GROUP BY`, or aggregation functions
+* Cannot use range queries or multi-partition operations
+
+**Valid LET Examples:**
+[source,cql]
+----
+LET user_data = (SELECT balance, status FROM users WHERE id = ?);
+LET order_info = (SELECT total, shipping_fee FROM orders WHERE id = ? LIMIT 1);
+LET static_config = (SELECT max_attempts FROM config WHERE setting_type =
'retry');
+----
+
+**Invalid LET Examples:**
+[source,cql]
+----
+-- Missing LIMIT 1 with potential multiple results
+LET users = (SELECT * FROM users WHERE status = 'active');
+
+-- Range query not allowed
+LET recent = (SELECT * FROM events WHERE id > ? AND id < ?);
+
+-- Aggregation not supported
+LET total = (SELECT COUNT(*) FROM orders WHERE user_id = ?);
+----
+
+=== Row References
+
+Access fields from LET variables using dot notation:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET current_user = (SELECT balance, status FROM users WHERE id = ?);
+
+ -- Access fields with dot notation
+ SELECT current_user.balance, current_user.status;
+COMMIT TRANSACTION
+----
+
+[[row-reference-limitations]]
+**Row Reference Limitations:**
+
+Row reference arithmetic in SET clauses and row references in VALUES are not
currently supported. Pass values as parameters instead.
+
+[source,cql]
+----
+-- Application code computes values, passes as parameters
+BEGIN TRANSACTION
+ LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+ IF user_data.balance >= ? THEN -- Pass order_total as parameter
+ UPDATE users SET balance = balance - ? WHERE id = ?; -- Pass order_total
+ END IF
+COMMIT TRANSACTION
+----
+
+=== Returning Results
+
+Return data from transactions using SELECT statements that appear before any
modifications. You can use either row reference values or a normal
single-partition SELECT.
+
+NOTE: If no SELECT statement is present, the transaction returns an empty
result set regardless of whether the IF condition was satisfied. Use a SELECT
before modifications to observe transaction state.
+
+==== Using Row References
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET user_data = (SELECT balance, status FROM users WHERE id = ?);
+
+ -- Return row reference values (must come before UPDATE)
+ SELECT user_data.balance, user_data.status;
+
+ UPDATE users SET balance = balance - 50 WHERE id = ?;
+COMMIT TRANSACTION
+----
+
+==== Using a Normal SELECT
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+ -- Return data directly from table (must come before UPDATE)
+ SELECT balance, status, email FROM users WHERE id = ?;
+
+ IF user_data.balance >= 50 THEN
+ UPDATE users SET balance = balance - 50 WHERE id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+**Important:** SELECT statements must appear before any UPDATE, INSERT, or
DELETE statements. To retrieve updated values, query outside the transaction
after it commits.
+
+=== Conditional Logic
+
+Add conditional logic to transactions with IF blocks:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET sender = (SELECT balance FROM accounts WHERE user_id = ?);
+
+ IF sender.balance >= 100 THEN
+ UPDATE accounts SET balance = balance - 100 WHERE user_id = ?;
+ UPDATE accounts SET balance = balance + 100 WHERE user_id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+**Supported Operators:**
+
+* Comparison: `=`, `<`, `<=`, `>`, `>=`, `!=`
+* Null checks: `IS NULL`, `IS NOT NULL`
+* Logical: `AND` only
+
+**Complex Condition Examples:**
+[source,cql]
+----
+-- Multiple conditions with AND
+IF user_data.balance >= 100 AND user_data.status = 'active'
+ AND user_data.credit_limit > 150 THEN
+ -- statements
+END IF
+
+-- Null checking
+IF account_info IS NOT NULL AND account_info.balance > 0 THEN
+ -- statements
+END IF
+----
+
+**Important Notes:**
+
+* Null handling is strict (any null comparison returns false)
+* All modification statements must be inside the IF block when using conditions
+
+== Restrictions
+
+The following features cannot be used within `BEGIN TRANSACTION ... COMMIT
TRANSACTION` blocks:
+
+* **Counter tables** and counter operations
+* **Aggregation functions** (COUNT, SUM, AVG, etc.)
+* **ORDER BY** and **GROUP BY** clauses
+* **Custom TTL** and **timestamp** specifications (transaction manages
timestamps)
+* **Range DELETE** operations (must specify complete primary key)
+* **Non-equality partition key** restrictions in LET/SELECT
+* **Individual statement conditions** (use transaction-level IF blocks instead)
+
+=== Schema Requirements
+
+* Accord must be enabled in `cassandra.yaml`
+* Tables must have a `transactional_mode` other than `'off'`
+* Tables being dropped cannot participate in transactions
+
+== Syntax Reference
+
+=== Complete Grammar
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ [LET letStatements]
+ [SELECT selectStatement | SELECT rowDataReferences]
+ [IF conditionalExpression THEN]
+ [modificationStatements]
+ [END IF]
+COMMIT TRANSACTION
+----
+
+=== Modification Statements
+
+**INSERT:**
+[source,cql]
+----
+INSERT INTO table (columns) VALUES (values);
+----
+
+**UPDATE:**
+[source,cql]
+----
+UPDATE table SET assignments WHERE conditions;
+----
+
+**DELETE:**
+[source,cql]
+----
+DELETE [columns] FROM table WHERE conditions;
+----
+
+== Multi-Partition Coordination
+
+Cross-partition transactions add coordination overhead:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ LET user1 = (SELECT balance FROM accounts WHERE user_id = ?);
+ LET user2 = (SELECT balance FROM accounts WHERE user_id = ?);
+
+ IF user1.balance >= 100 THEN
+ UPDATE accounts SET balance = balance - 100 WHERE user_id = ?;
+ UPDATE accounts SET balance = balance + 100 WHERE user_id = ?;
+ END IF
+COMMIT TRANSACTION
+----
+
+**Performance Impact:**
+
+* Same-partition transactions: Low overhead
+* Cross-partition transactions: Moderate overhead
+* More partitions = higher coordination cost
+
+**Optimization:** Minimize cross-partition operations; consider data model
changes to reduce cross-partition transactions.
+
+== Automatic Read Generation
+
+When UPDATE statements reference current column values, Accord automatically
reads the current state:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+ -- This UPDATE automatically reads current balance
+ UPDATE users SET balance = balance + ? WHERE id = ?;
+COMMIT TRANSACTION
+----
+
+**Triggers:**
+
+* SET clauses that reference current column values
+
+== Troubleshooting
+
+=== Configuration Errors
+
+**Accord transactions are disabled**
+
+Enable in cassandra.yaml:
+[source,yaml]
+----
+accord:
+ enabled: true
+----
+
+**Accord transactions are disabled on table**
+
+Enable on table:
+[source,cql]
+----
+ALTER TABLE tbl WITH transactional_mode = 'full';
+----
+
+=== Syntax Errors
+
+**Duplicate variable names**
+
+Use unique variable names:
+[source,cql]
+----
+-- Bad
+LET user_data = (SELECT balance FROM accounts WHERE user_id = 1);
+LET user_data = (SELECT balance FROM accounts WHERE user_id = 2);
+
+-- Good
+LET sender_data = (SELECT balance FROM accounts WHERE user_id = 1);
+LET receiver_data = (SELECT balance FROM accounts WHERE user_id = 2);
+----
+
+=== Performance Issues
+
+**High latency investigation:**
+
+. Check transaction complexity (LET count, partition count)footnote:[Keep LET
statements under 5 and partitions under 3 as a starting point. These are
suggested guidelines, not empirically derived limits.]
+. Monitor cross-datacenter operations
+. Check for high contention on specific keys
+
+**Large result sets:**
+
+Select only needed columns:
+[source,cql]
+----
+-- Bad: Reading entire row
+LET user_history = (SELECT * FROM large_history_table WHERE user_id = ? LIMIT
1);
+
+-- Good: Only read necessary data
+LET user_status = (SELECT status, last_login FROM users WHERE id = ?);
+----
+
+=== Counter Table Alternative
+
+Counters cannot be used in transactions. Use regular columns:
+
+[source,cql]
+----
+-- Bad: Counter in transaction
+CREATE TABLE page_views (
+ page_id uuid PRIMARY KEY,
+ views counter
+) WITH transactional_mode = 'full';
+
+-- Good: Regular column
+CREATE TABLE page_views (
+ page_id uuid PRIMARY KEY,
+ views bigint
+) WITH transactional_mode = 'full';
+
+BEGIN TRANSACTION
+ UPDATE page_views SET views = views + 1 WHERE page_id = ?;
+COMMIT TRANSACTION
+----
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]