Skip to main content
Deno 2 is finally here 🎉️
Learn more
Module

x/aether/deps.ts>Transaction

A Deno library to interface with the Discord API
Latest
class Transaction
import { Transaction } from "https://deno.land/x/aether@v0.0.3/deps.ts";

Constructors

new
Transaction(
name: string,
options: TransactionOptions | undefined,
client: QueryClient,
execute_query_callback: (query: Query<ResultType>) => Promise<QueryResult>,
update_client_lock_callback: (name: string | null) => void,
)

Properties

readonly
isolation_level
readonly
savepoints

Methods

The begin method will officially begin the transaction, and it must be called before any query or transaction operation is executed in order to lock the session

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction_name");

await transaction.begin(); // Session is locked, transaction operations are now safe
// Important operations
await transaction.commit(); // Session is unlocked, external operations can now take place

https://www.postgresql.org/docs/14/sql-begin.html

commit(options?: { chain?: boolean; })

The commit method will make permanent all changes made to the database in the current transaction and end the current transaction

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

await transaction.begin();
// Important operations
await transaction.commit(); // Will terminate the transaction and save all changes

The commit method allows you to specify a "chain" option, that allows you to both commit the current changes and start a new with the same transaction parameters in a single statement

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

// Transaction operations I want to commit
await transaction.commit({ chain: true }); // All changes are saved, following statements will be executed inside a transaction
await transaction.queryArray`DELETE SOMETHING FROM SOMEWHERE`; // Still inside the transaction
await transaction.commit(); // The transaction finishes for good

https://www.postgresql.org/docs/14/sql-commit.html

getSavepoint(name: string): Savepoint | undefined

This method will search for the provided savepoint name and return a reference to the requested savepoint, otherwise it will return undefined

getSavepoints(): string[]

This method will list you all of the active savepoints in this transaction

getSnapshot(): Promise<string>

This method returns the snapshot id of the on going transaction, allowing you to share the snapshot state between two transactions

import { Client } from "../client.ts";

const client_1 = new Client();
const client_2 = new Client();
const transaction_1 = client_1.createTransaction("transaction");

const snapshot = await transaction_1.getSnapshot();
const transaction_2 = client_2.createTransaction("new_transaction", { isolation_level: "repeatable_read", snapshot });
// transaction_2 now shares the same starting state that transaction_1 had

https://www.postgresql.org/docs/14/functions-admin.html#FUNCTIONS-SNAPSHOT-SYNCHRONIZATION

queryArray<T extends Array<unknown>>(query: string, args?: QueryArguments): Promise<QueryArrayResult<T>>

This method allows executed queries to be retrieved as array entries. It supports a generic interface in order to type the entries retrieved by the query

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

const {rows} = await transaction.queryArray(
 "SELECT ID, NAME FROM CLIENTS"
); // Array<unknown[]>

You can pass type arguments to the query in order to hint TypeScript what the return value will be

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

const { rows } = await transaction.queryArray<[number, string]>(
 "SELECT ID, NAME FROM CLIENTS"
); // Array<[number, string]>

It also allows you to execute prepared stamements with template strings

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

const id = 12;
// Array<[number, string]>
const { rows } = await transaction.queryArray<[number, string]>`SELECT ID, NAME FROM CLIENTS WHERE ID = ${id}`;
queryArray<T extends Array<unknown>>(config: QueryOptions): Promise<QueryArrayResult<T>>
queryArray<T extends Array<unknown>>(strings: TemplateStringsArray, ...args: unknown[]): Promise<QueryArrayResult<T>>
queryObject<T>(query: string, args?: QueryArguments): Promise<QueryObjectResult<T>>

This method allows executed queries to be retrieved as object entries. It supports a generic interface in order to type the entries retrieved by the query

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

{
  const { rows } = await transaction.queryObject(
    "SELECT ID, NAME FROM CLIENTS"
  ); // Record<string, unknown>
}

{
  const { rows } = await transaction.queryObject<{id: number, name: string}>(
    "SELECT ID, NAME FROM CLIENTS"
  ); // Array<{id: number, name: string}>
}

You can also map the expected results to object fields using the configuration interface. This will be assigned in the order they were provided

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

{
  const { rows } = await transaction.queryObject(
    "SELECT ID, NAME FROM CLIENTS"
  );

  console.log(rows); // [{id: 78, name: "Frank"}, {id: 15, name: "Sarah"}]
}

{
  const { rows } = await transaction.queryObject({
    text: "SELECT ID, NAME FROM CLIENTS",
    fields: ["personal_id", "complete_name"],
  });

  console.log(rows); // [{personal_id: 78, complete_name: "Frank"}, {personal_id: 15, complete_name: "Sarah"}]
}

It also allows you to execute prepared stamements with template strings

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

const id = 12;
// Array<{id: number, name: string}>
const {rows} = await transaction.queryObject<{id: number, name: string}>`SELECT ID, NAME FROM CLIENTS WHERE ID = ${id}`;
queryObject<T>(config: QueryObjectOptions): Promise<QueryObjectResult<T>>
queryObject<T>(query: TemplateStringsArray, ...args: unknown[]): Promise<QueryObjectResult<T>>
rollback(savepoint?: string | Savepoint): Promise<void>

Rollbacks are a mechanism to undo transaction operations without compromising the data that was modified during the transaction

A rollback can be executed the following way

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

// Very very important operations that went very, very wrong
await transaction.rollback(); // Like nothing ever happened

Calling a rollback without arguments will terminate the current transaction and undo all changes, but it can be used in conjuction with the savepoint feature to rollback specific changes like the following

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

// Important operations I don't want to rollback
const savepoint = await transaction.savepoint("before_disaster");
await transaction.queryArray`UPDATE MY_TABLE SET X = 0`; // Oops, update without where
await transaction.rollback(savepoint); // "before_disaster" would work as well
// Everything that happened between the savepoint and the rollback gets undone
await transaction.commit(); // Commits all other changes

The rollback method allows you to specify a "chain" option, that allows you to not only undo the current transaction but to restart it with the same parameters in a single statement

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

// Transaction operations I want to undo
await transaction.rollback({ chain: true }); // All changes are undone, but the following statements will be executed inside a transaction as well
await transaction.queryArray`DELETE SOMETHING FROM SOMEWHERE`; // Still inside the transaction
await transaction.commit(); // The transaction finishes for good

However, the "chain" option can't be used alongside a savepoint, even though they are similar

A savepoint is meant to reset progress up to a certain point, while a chained rollback is meant to reset all progress and start from scratch

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

// @ts-expect-error
await transaction.rollback({ chain: true, savepoint: "my_savepoint" }); // Error, can't both return to savepoint and reset transaction

https://www.postgresql.org/docs/14/sql-rollback.html

rollback(options?: { savepoint?: string | Savepoint; }): Promise<void>
rollback(options?: { chain?: boolean; }): Promise<void>
savepoint(name: string): Promise<Savepoint>

This method will generate a savepoint, which will allow you to reset transaction states to a previous point of time

Each savepoint has a unique name used to identify it, and it must abide the following rules

  • Savepoint names must start with a letter or an underscore
  • Savepoint names are case insensitive
  • Savepoint names can't be longer than 63 characters
  • Savepoint names can only have alphanumeric characters

A savepoint can be easily created like this

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

const savepoint = await transaction.savepoint("MY_savepoint"); // returns a `Savepoint` with name "my_savepoint"
await transaction.rollback(savepoint);
await savepoint.release(); // The savepoint will be removed

All savepoints can have multiple positions in a transaction, and you can change or update this positions by using the update and release methods

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

const savepoint = await transaction.savepoint("n1");
await transaction.queryArray`INSERT INTO MY_TABLE VALUES (${'A'}, ${2})`;
await savepoint.update(); // The savepoint will continue from here
await transaction.queryArray`DELETE FROM MY_TABLE`;
await transaction.rollback(savepoint); // The transaction will rollback before the delete, but after the insert
await savepoint.release(); // The last savepoint will be removed, the original one will remain
await transaction.rollback(savepoint); // It rolls back before the insert
await savepoint.release(); // All savepoints are released

Creating a new savepoint with an already used name will return you a reference to the original savepoint

import { Client } from "../client.ts";

const client = new Client();
const transaction = client.createTransaction("transaction");

const savepoint_a = await transaction.savepoint("a");
await transaction.queryArray`DELETE FROM MY_TABLE`;
const savepoint_b = await transaction.savepoint("a"); // They will be the same savepoint, but the savepoint will be updated to this position
await transaction.rollback(savepoint_a); // Rolls back to savepoint_b

https://www.postgresql.org/docs/14/sql-savepoint.html