EdgeQL supports atomic transactions. The transaction API consists of several commands:
start transaction
Start a transaction, specifying the isolation level, access mode (read
only
vs read write
), and deferrability.
declare savepoint
Establish a new savepoint within the current transaction. A savepoint is a intermediate point in a transaction flow that provides the ability to partially rollback a transaction.
release savepoint
Destroys a savepoint previously defined in the current transaction.
rollback to savepoint
Rollback to the named savepoint. All changes made after the savepoint are discarded. The savepoint remains valid and can be rolled back to again later, if needed.
rollback
Rollback the entire transaction. All updates made within the transaction are discarded.
commit
Commit the transaction. All changes made by the transaction become visible to others and will persist if a crash occurs.
There is rarely a reason to use these commands directly. All EdgeDB client libraries provide dedicated transaction APIs that handle transaction creation under the hood.
Examples below show a transaction that sends 10 cents from the account
of a BankCustomer
called 'Customer1'
to BankCustomer
called
'Customer2'
. The equivalent EdgeDB schema and queries are:
module default {
type BankCustomer {
required name: str;
required balance: int64;
}
}
update BankCustomer
filter .name = 'Customer1'
set { bank_balance := .bank_balance -10 };
update BankCustomer
filter .name = 'Customer2'
set { bank_balance := .bank_balance +10 }
Using an EdgeQL query string:
client.transaction(async tx => {
await tx.execute(`update BankCustomer
filter .name = 'Customer1'
set { bank_balance := .bank_balance -10 }`);
await tx.execute(`update BankCustomer
filter .name = 'Customer2'
set { bank_balance := .bank_balance +10 }`);
});
Using the querybuilder:
const query1 = e.update(e.BankCustomer, () => ({
filter_single: { name: "Customer1" },
set: {
bank_balance: { "-=": 10 }
},
}));
const query2 = e.update(e.BankCustomer, () => ({
filter_single: { name: "Customer2" },
set: {
bank_balance: { "+=": 10 }
},
}));
client.transaction(async (tx) => {
await query1.run(tx);
await query2.run(tx);
});
Full documentation at Client Libraries > TypeScript/JS;
async for tx in client.transaction():
async with tx:
await tx.execute("""update BankCustomer
filter .name = 'Customer1'
set { bank_balance := .bank_balance -10 };""")
await tx.execute("""update BankCustomer
filter .name = 'Customer2'
set { bank_balance := .bank_balance +10 };""")
Full documentation at Client Libraries > Python;
err = client.Tx(ctx, func(ctx context.Context, tx *edgedb.Tx) error {
query1 := `update BankCustomer
filter .name = 'Customer1'
set { bank_balance := .bank_balance -10 };`
if e := tx.Execute(ctx, query1); e != nil {
return e
}
query2 := `update BankCustomer
filter .name = 'Customer2'
set { bank_balance := .bank_balance +10 };`
if e := tx.Execute(ctx, query2); e != nil {
return e
}
return nil
})
if err != nil {
log.Fatal(err)
}
Full documentation at Client Libraries > Go.
let balance_change_query = "update BankCustomer
filter .name = <str>$0
set { bank_balance := .bank_balance + <int32>$1 }";
client
.transaction(|mut conn| async move {
conn.execute(balance_change_query, &("Customer1", -10))
.await
.expect("Execute should have worked");
conn.execute(balance_change_query, &("Customer2", 10))
.await
.expect("Execute should have worked");
Ok(())
})
.await
.expect("Transaction should have worked");
Full documentation at Client Libraries > Rust.