Basics of Transactions
In this section, we will go through the fundamentals of transactions in NimbusDB. We’ll use this schema and the items model and catalog as an example:
var schema = { id: { type: NIMBUSDB_DATA_TYPE.INTEGER, const: NIMBUSDB_CONSTRAINT.PRIMARY_KEY }, name: NIMBUSDB_DATA_TYPE.STRING, price: { type: NIMBUSDB_DATA_TYPE.NUMBER, validator: function(data, value) { return value >= 0; }, default_value: 0 }, is_locked: { type: NIMBUSDB_DATA_TYPE.BOOLEAN, const: NIMBUSDB_CONSTRAINT.OPTIONAL, default_value: false }};
// `items` modelitems = new NimbusDBModel("global", "items", schema, [ { id: 1, name: "Apple", price: 5 }, { id: 2, name: "Banana", price: 7.2 }, { id: 3, name: "Cherry", price: 15 }, { id: 4, name: "Date", price: 12.5 }, { id: 5, name: "Elderberry", price: 8 }, { id: 6, name: "Fig", price: 10 }, { id: 7, name: "Grape", price: 6 }, { id: 8, name: "Honeydew", price: 9 }, { id: 9, name: "Kiwi", price: 4 }, { id: 10, name: "Lemon", price: 3 }]);
// `items` catalogctg_items = new NimbusDBCatalog("items", { model: items});ACID Properties in Transactions
Section titled “ACID Properties in Transactions”ACID (Atomicity, Consistency, Isolation, Durability) properties are mandatory for transactions in traditional relational databases to ensure data integrity and reliability. But, transactions in NimbusDB are not the same as ACID in relational databases due its design philosophy (in-memory and client-side first).
Atomicity Supported
Section titled “Atomicity ”Atomicity means that all operations in a transaction are treated as a single unit of work. If any operation fails, the entire transaction will be rolled back, and the original model will remain unchanged.
NimbusDB transactions support atomicity by default, so if any operation fails during the transaction, it will automatically rollback to the original state.
Consistency Supported
Section titled “Consistency ”Consistency means that a transaction must bring the database from one valid state to another valid state, maintaining the integrity of the data.
In NimbusDB, the schema supports constraint and validator for each column/field. Data entering the model must satisfy defined rules, or it gets rejected. This ensures that the data remains consistent and valid according to the defined schema.
Isolation Not Supported
Section titled “Isolation ”Isolation means that the operations in a transaction are isolated from other transactions, preventing concurrent transactions from interfering with each other.
But, since NimbusDB is currently designed to be single-threaded and client-side first, it does not support true isolation between transactions.
Durability Partially Supported
Section titled “Durability ”Durability means that once a transaction is committed, its changes are permanent and will survive any subsequent failures.
But, since NimbusDB is an in-memory database, it does not provide durability by default. You need to manually save and load your data back from persistent storage to achieve data persistence (e.g using Model.export()/Catalog.backup() and Model.import()/Catalog.restore() methods).
Creating a Transaction
Section titled “Creating a Transaction”Transaction means to create a perform isolated operations on a database (model or catalog) with the partial ACID properties.
Use .transaction() method on model or catalog instance to create and begin a transaction.
// start a transaction on `items` modelitems.transaction(function(tx) { // inside the transaction context, use `tx` instead of `items` // all CRUD operations are done through `tx` object // for example: // tx.insert({ /* some data here */ });});
// you can also start a transaction on catalogctg_items.transaction("items", function(tx) { // first parameter is the model name or custom id // transaction operations here // ...});Buffered Transaction
Section titled “Buffered Transaction”By default, NimbusDB performs operations in a buffered mode, meaning that all operations are performed on temporary model copy first before applying it to the original model once committed.
items.transaction(function(tx) { // transaction operations here // ...}, { buffered: false, // ... other options});
ctg_items.transaction("items", function(tx) { // transaction operations here // ...}, { buffered: false, // ... other options});Buffered vs Unbuffered Transaction
Section titled “Buffered vs Unbuffered Transaction”-
On buffered transaction:
- All operations are performed on the copied model, so it’s safer to use.
- The original model will remain unchanged until the transaction is committed:
- On success, it will apply the operations to the original model.
- On failure, nothing happens to the original model.
- It is the default behavior.
-
On unbuffered transaction:
- All operations are performed directly on the original model.
- The original model will be modified immediately after each operation:
- On success, nothing happens because the original model is already updated.
- On failure, the original model will be rolled back one-by-one in reverse order of the performed operations.
- It is only available if you set
buffered: falsein the options.
Staged Commit
Section titled “Staged Commit”The .commit() method can only be used once per transaction, and it will commit all the operations performed during the transaction. But you can change this behavior, so .commit() can be used multiple times.
items.transaction(function(tx) { // transaction operations here // ...}, { staged_commit: true, // ... other options});
ctg_items.transaction("items", function(tx) { // transaction operations here // ...}, { staged_commit: true, // ... other options});Commit and Rollback
Section titled “Commit and Rollback”Auto-rollback on Discard
Section titled “Auto-rollback on Discard”By default, the transaction will automatically rollback if it’s discarded (not calling any .commit() before the end of the callback function).
items.transaction(function(tx) { // transaction operations here, without calling any `.commit()` // ...});// `items` rolled back automatically hereSo, don’t forget to call .commit() before the end of the callback function if you want to commit the transaction.
items.transaction(function(tx) { // transaction operations here, without calling any `.commit()` // ... tx.commit();});// `items` committedYou can also set the auto_rollback option to false to prevent the transaction from being automatically rolled back when it’s discarded.
items.transaction(function(tx) { // transaction operations here, without calling any `.commit()` // ...}, { auto_rollback: false});// `items` committed automatically hereEarly Commit
Section titled “Early Commit”You can also call .commit() method before the end of the callback function to commit the transaction early.
On Single-use Commit
Section titled “On Single-use Commit”The remaining operations after .commit() will be ignored.
items.transaction(function(tx) { // transaction operations here // ... tx.commit(); // <-- early commit here // ... other tx operations // these operations will be skipped because the transaction is already committed});// `items` committedOn Staged Commit
Section titled “On Staged Commit”If staged_commit is true, you can call .commit() multiple times. Each call will commit all the operations performed since the last commit.
items.transaction(function(tx) { // first batch operations // ... tx.commit(); // <-- first commit here, apply operations to the original model // ... other tx operations (second batch) tx.commit(); // <-- second commit here, apply second batch operations to the original model // ... and so on // and if the transaction fail, it will rollback to the previous committed state // so, if it's fail here (after second commit), it will commit from the first batch until second batch tx.commit();}, { staged_commit: true});// `items` committedRollback
Section titled “Rollback”Used to rollback the transaction manually, like the return keyword in a normal function.
items.transaction(function(tx) { // transaction operations here // ... tx.rollback(); // <-- rollback here // ... other tx operations // these operations will be skipped because the transaction is already rolled back});// `items` rolled backReferences
Section titled “References”NimbusDBTransaction Internal
Section titled “NimbusDBTransaction ”Represents an isolated transaction context over a NimbusDB model, supporting atomic CRUD operations with commit and rollback capabilities.
class NimbusDBTransaction { constructor( _original_model: NimbusDBModel, _options?: NimbusDBTransactionOptions );
id: int;
static __id: int; __active: boolean; // whether this transaction is valid or not __auto_rollback: boolean; __buffered: boolean; __is_commited: boolean; __is_rolled_back: boolean; __model: NimbusDBModel; // temp model if buffered __original_model: NimbusDBModel; __operations: NimbusDBTransactionOps[]; // return result for each operation __staged_commit: boolean; __sys_temp: Struct | null; __temp_model: boolean;
// ...public and internal methods}Model.transaction()
Section titled “Model.transaction()”Runs a transaction against the model.
Signature
Section titled “Signature”class NimbusDBModel { // ... other methods and properties ... static transaction( _func: (tx: NimbusDBTransaction) => void, _options?: NimbusDBTransactionOptions ): void;}Parameters
Section titled “Parameters”- Type:
(tx: NimbusDBTransaction) => void - Callback function that performs operations on the transaction.
_options
Section titled “_options”- Type:
NimbusDBTransactionOptions - Default:
undefined - Optional configuration for the transaction.
Catalog.transaction()
Section titled “Catalog.transaction()”Runs a transaction against the catalog.
Signature
Section titled “Signature”class NimbusDBCatalog { // ... other methods and properties ... static transaction( _name: string, _func: (tx: NimbusDBTransaction) => void, _options?: NimbusDBTransactionOptions ): void;}Parameters
Section titled “Parameters”- Type:
string - The name of the model to run the transaction against.
- Type:
(tx: NimbusDBTransaction) => void - Callback function that performs operations on the transaction.
_options
Section titled “_options”- Type:
NimbusDBTransactionOptions - Default:
undefined - Optional configuration for the transaction.
Signature
Section titled “Signature”class NimbusDBCatalog { // ... other methods and properties ... static transaction( _custom_id: int, _func: (tx: NimbusDBTransaction) => void, _options?: NimbusDBTransactionOptions ): void;}Parameters
Section titled “Parameters”_custom_id
Section titled “_custom_id”- Type:
int - The custom ID of the model to run the transaction against.
- Type:
(tx: NimbusDBTransaction) => void - Callback function that performs operations on the transaction.
_options
Section titled “_options”- Type:
NimbusDBTransactionOptions - Default:
undefined - Optional configuration for the transaction.
Transaction.commit()
Section titled “Transaction.commit()”Manually commits the transaction early (when staged_commit is false), or applies all operations performed before this .commit() is called (when staged_commit is true).
Signature
Section titled “Signature”class NimbusDBTransaction { // ... other methods and properties ... commit(): void;}Transaction.print()
Section titled “Transaction.print()”Prints the current state of the transaction model’s data to the debug console.
Signature
Section titled “Signature”class NimbusDBTransaction { // ... other methods and properties ... print(): void;}Transaction.rollback()
Section titled “Transaction.rollback()”Rolls back all changes made during this transaction, restoring the model to its pre-transaction state.
Signature
Section titled “Signature”class NimbusDBTransaction { // ... other methods and properties ... rollback(): void;}NimbusDBTransactionOptions
Section titled “NimbusDBTransactionOptions”Optional configuration for creating a transaction.
type NimbusDBTransactionOptions = Partial<{ auto_rollback: boolean; // rollback automatically if the transaction is discarded (default = true) buffered: boolean; // isolate the transaction from the original model (default = true) staged_commit: boolean; // enable `.commit` to be called multiple times (default = false) temp_model: NimbusDBModel; // [INTERNAL] temp model if buffered}>;NimbusDBTransactionOps Internal
Section titled “NimbusDBTransactionOps ”Represents the result of a single operation within a transaction.
type NimbusDBTransactionOps = | { type: NIMBUSDB_TRANSACTION.GET; selector: (NimbusDBGetByIndex | NimbusDBGetByPrimary | NimbusDBGetByValue | NimbusDBGetByFunction); result: any; } | { type: NIMBUSDB_TRANSACTION.INSERT; data_id: int; data: Struct[]; options: NimbusDBInsertOptions; result: NimbusDBInsertResult; } | { type: NIMBUSDB_TRANSACTION.REMOVE; selector: (NimbusDBRemoveByIndex | NimbusDBRemoveByPrimary | NimbusDBRemoveByValue | NimbusDBRemoveByFunction); result: NimbusDBRemoveResult; } | { type: NIMBUSDB_TRANSACTION.UPDATE; selector: (NimbusDBUpdateByIndex | NimbusDBUpdateByPrimary | NimbusDBUpdateByValue | NimbusDBUpdateByFunction); update_data: any | StructExt; result: NimbusDBUpdateResult; } | { type: NIMBUSDB_TRANSACTION.UPSERT; data_id: int; data: Struct[]; options: NimbusDBUpsertOptions; result: NimbusDBUpsertResult; };NIMBUSDB_TRANSACTION Internal
Section titled “NIMBUSDB_TRANSACTION ”The type of operation performed in a transaction.
export const enum NIMBUSDB_TRANSACTION { GET, INSERT, REMOVE, UPDATE, UPSERT}