Skip to content

feat: add transaction() context manager to CoordinodeClient #23

Description

@polaz

Feature

Explicit multi-statement transactions in the Python SDK, so that a group of
writes commits or rolls back as one.

Server support already shipped

The gate this issue originally named, Bolt BEGIN / COMMIT / ROLLBACK, is
the wrong one. The SDK speaks gRPC, and the gRPC API has carried interactive
transactions since server v0.5.5, which is the version this repository already
pins by digest in CI and in the demo stack:

  • BeginTransaction / CommitTransaction / RollbackTransaction on CypherService
  • ExecuteCypherRequest.transaction_id: a non-zero value runs that statement
    inside the transaction, reading at the snapshot pinned at begin, with writes
    buffered until commit

The pinned proto submodule already carries these and the generated stubs already
expose them, so nothing has to move on the server or the schema for this work.
Bolt (port 7082) is a stub with no listener and is unrelated.

API design

Context manager (preferred):

with client.transaction() as tx:
    tx.cypher("CREATE (n:Person {name: $name})", params={"name": "Alice"})
    tx.cypher("CREATE (n:Person {name: $name})", params={"name": "Bob"})
    # commit on clean exit, rollback on exception

Explicit:

tx = client.begin_transaction()
try:
    tx.cypher("MERGE (n:Entity {name: $name})", params={"name": "Alice"})
    tx.commit()
except Exception:
    tx.rollback()
    raise

Both the sync and the async client get the same surface, since the async client
is the one the sync client wraps.

Semantics to honour

Read out of the server implementation rather than assumed:

  • Snapshot pinned at begin. Every statement reads the snapshot taken when
    the transaction began, so the transaction sees a stable view plus its own
    buffered writes. Conflicts surface at commit, which can fail because another
    transaction wrote the same data in the meantime.
  • A failed statement aborts the whole transaction. The server drops the
    buffered state on any statement error and consumes the handle, so a later
    commit or rollback on that handle answers "unknown transaction id". The SDK
    has to treat a server-side statement error as terminal and must not bury the
    original exception under a second one from its own cleanup.
  • Per-statement consistency options do not apply. The in-transaction path
    ignores read concern, write concern, read preference and causal index,
    because the snapshot was fixed at begin. Transaction.cypher() should decline
    those arguments rather than accept and silently drop them.
  • The handle is node-local. It lives in the memory of the process that
    served the begin. Every statement and the commit must reach that same node, so
    against a load balancer or a multi-replica deployment a transaction has to
    hold one connection for its lifetime.
  • Idle transactions are reaped. The default idle timeout is 30 seconds, and
    reaping runs when some other transaction begins rather than on a timer, so a
    long pause can lose the handle without a wall-clock guarantee of when.
    Buffered writes are capped as well (256 MiB by default) and a breach aborts
    the transaction.

Acceptance criteria

  • client.transaction() context manager commits on clean exit
  • Rollback on exception is automatic, and the original exception is what propagates
  • Transaction.cypher() runs inside the transaction rather than auto-committing
  • client.begin_transaction() / tx.commit() / tx.rollback() explicit API
  • Using a transaction after commit or rollback raises rather than silently reopening one
  • Same surface on AsyncCoordinodeClient
  • Integration test: a partial write is not visible after a rollback
  • Integration test: a statement error leaves nothing behind and reports the statement's own error
  • Documented in the README, including the node affinity and idle-timeout constraints
  • Demonstrated in the notebook that exercises the server-only surface

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions