Module Rowtype_sqlite.Transaction

One transaction on one connection, and its own failures told apart from the work's.

The work answers a result: Ok commits and Error rolls back. A failure of the transaction itself joins the work's error type as an open polymorphic variant, so a caller whose errors are polymorphic variants, as every statement's are, writes nothing, and one whose errors are ordinary variants maps once, in its own bracket.

SQLite's transactions are serializable, so there is no level to choose: what is chosen is when a transaction takes the write lock. A statement that fails inside one undoes itself alone, as SQLite does, and the transaction goes on; SQLite rolls back the whole of it only where it cannot go on -- a full disk, an I/O error -- and then its COMMIT finds nothing to commit.

type behaviour =
  1. | Deferred
  2. | Immediate
  3. | Exclusive

When the transaction takes the database's write lock, as SQLite's begin names it: Deferred at its first write, Immediate as it begins, and Exclusive as it begins, keeping other connections from reading too outside WAL mode. A transaction that reads and then writes begins Immediate, or its first write may find that another connection wrote since it read, which is `Not_serializable however long it waits.

type failure = [
  1. | `Not_committed of string
    (*

    the transaction could not begin, or its COMMIT failed or found that SQLite had rolled it back: in words for the log. Whether it committed is known: it did not.

    *)
  2. | `Conflict of string option
    (*

    its COMMIT found a deferred foreign key refusing the write, as a statement's Rowtype.error names one checked at once, and it was rolled back

    *)
  3. | `Not_serializable of string
    (*

    it could not take a lock another connection held -- to begin, or to commit -- and its retries did not get past it

    *)
]

The transaction's own failures, which within adds to the work's: a caller tells them from its own as #Transaction.failure, and names them in an error type of its own as [ my_error | Transaction.failure ].

val within : ?keep:('e -> bool) -> ?behaviour:behaviour -> ?retries:int -> conn -> (conn -> ('a, [> failure ] as 'e) Stdlib.result) -> ('a, 'e) Stdlib.result

within db work: begin, run work, and commit what it answered Ok. A refusal rolls back unless keep says to keep what it wrote -- a wrong sign-in code that has to count its attempt -- and still answers the refusal. keep keeps nothing unless given.

behaviour is how it begins, Deferred unless given, as SQLite begins one.

retries is how many times the whole transaction runs again when the work answers `Not_serializable, or its begin or COMMIT does -- none unless given, so a retry is always asked for. Each is an info line, and the last answer stands. Work that runs again does everything again, so work given retries does nothing but its statements.

`Not_committed replaces the work's answer when the transaction could not begin -- a transaction already open on the connection, a within inside another, among the reasons, and the outer one goes on -- when its COMMIT failed, or when SQLite had rolled it back. `Conflict replaces it when a deferred foreign key refused the write at COMMIT, and `Not_serializable when COMMIT could not take its lock; either way the transaction is rolled back. Each `Not_committed is an error line on rowtype-sqlite. A raise inside work, a cancellation among them, rolls back and passes. Its COMMIT and each rollback run to their end whatever cancels the fiber, so a cancellation never leaves the transaction, or its lock, open.

type savepoint

A point in the transaction open on a connection that work after it can be released into, made by savepoint.

val savepoint : conn -> (savepoint, [> Rowtype.error ]) Stdlib.result

A savepoint, in the transaction open on conn; with none open, it begins one, which its release commits.

val release : conn -> savepoint -> ([ `Released | `Gone ], [> Rowtype.error ]) Stdlib.result

The work since savepoint kept in the transaction, `Released; or `Gone where that work ended the transaction the savepoint was made in -- a commit, a rollback, or one and a begin after it -- so nothing of it stands to release. For a script a caller did not write, run between the two, that has to be told apart from one that kept its transaction.