メインコンテンツまでスキップ

$transaction(トランザクション)

$transaction は、複数の操作をまとめて 1 つのトランザクションとして実行するための機能です。Prisma のインタラクティブトランザクション(コールバック形の $transaction)に相当します。

コールバック内の書き込み操作は即座にはシートへ反映されず、コールバックが正常終了した時点でまとめてシートに書き込まれます(コミット)。コールバックが throw した場合、シートには 1 セルも書き込まれません

基本的な使い方

gassma.$transaction((tx) => {...}) の形で呼び出します。コールバックにはトランザクション用クライアント tx が渡され、コールバックの戻り値がそのまま $transaction の戻り値として返ります。

const user = gassma.$transaction((tx) => {
const created = tx.Users.create({
data: { id: 1, name: "Tanaka" },
});
tx.Posts.create({
data: { id: 10, title: "Hello", authorId: created.id },
});
return created;
});
// コールバックが正常終了した時点で Users と Posts にまとめて書き込まれる

GAS 上で動作するため、Prisma と異なり $transaction同期的に実行されます。コールバックも $transaction の戻り値も Promise ではありません。async / await は不要です。

throw で全キャンセル

コールバックの途中でエラーが throw されると、それまでの書き込みはすべて破棄され、シートには何も反映されません。

try {
gassma.$transaction((tx) => {
tx.Users.create({ data: { id: 1, name: "Tanaka" } });
tx.Posts.create({ data: { id: 10, title: "Hello", authorId: 1 } });
throw new Error("cancel");
});
} catch (e) {
// Users にも Posts にも 1 行も書き込まれていない
}

tx クライアント

tx では通常のクライアントと同じモデル群(シート)が使えます。$extends も使え、トランザクション内だけに適用される拡張クライアントを作れます。

gassma.$transaction((tx) => {
const extended = tx.$extends({
query: {
Users: {
findMany({ args, query }) {
return query(args);
},
},
},
});
return extended.Users.findMany();
});

一方、tx$transaction はありません(型上も存在しません)。トランザクションのネストは非対応で、トランザクション内で $transaction を呼ぶと GassmaNestedTransactionError が throw されます。

トランザクション内の読み取り(read-your-writes)

トランザクション内の読み取り(findMany / include / リレーションフィルタなど)には、まだコミットされていない変更が見えます。一方、トランザクションの外のクライアントからは、コミットされるまで変更は見えません。

gassma.$transaction((tx) => {
tx.Users.create({ data: { id: 1, name: "Tanaka" } });

// tx 内の読み取りには未コミットの変更が見える
const found = tx.Users.findFirst({ where: { id: 1 } }); // 見つかる

// tx の外のクライアントからはコミットまで見えない
const outside = gassma.Users.findFirst({ where: { id: 1 } }); // null
});

オプション

第 2 引数でオプションを指定できます。

gassma.$transaction(
(tx) => {
// ...
},
{ maxWait: 10000, timeout: 120000, rollback: false },
);
オプション既定値説明
maxWait20000(ms)トランザクション開始(ロック取得)を待つ時間の上限。超過すると GassmaTransactionLockTimeoutError
timeout60000(ms)トランザクション全体の実行時間の上限。超過すると GassmaTransactionTimeoutError
rollbacktrueコミット時のバックアップと失敗時の自動復元を有効にするか(詳細は後述

maxWait

トランザクションはスクリプトロックを取得してから開始されます。別の実行が同じロックを保持している場合(別の $transaction の実行中など)、最大 maxWait ミリ秒までロックの解放を待ち、それでも取得できなければ GassmaTransactionLockTimeoutError を throw します。

timeout

トランザクション開始からの経過時間が timeout ミリ秒を超えると GassmaTransactionTimeoutError を throw します。チェックは協調式で、各 tx 操作の呼び出し時コミット直前に経過時間を確認します。

注記

協調式のため、tx のメソッドを呼ばない処理(長い計算ループなど)の途中では超過を検知できません。その場合も、次の tx 操作またはコミット直前のチェックで検知されます。

rollback

rollback: true(既定)のとき、コミットは次の流れで行われます。

  1. 書き込み対象のシートを一時バックアップ(_gassma_tx_... という名前の隠しシート)として複製する
  2. シートへ書き込む
  3. 成功したらバックアップを自動削除する

書き込みの途中で失敗した場合は、バックアップから自動復元したうえで元のエラーを再 throw します。シートはその場で復元され、数式セルも保たれます。復元まで失敗した場合はバックアップシートを残して GassmaTransactionRollbackError を throw します。このエラーの backupSheetNames プロパティに残されたバックアップシート名の一覧が入っており、そこから手動で復旧できます。

{ rollback: false } を指定すると、この仕組みを丸ごと省略します(高速)。ただし、書き込みの途中で失敗した場合はシートが部分的に書き込まれた状態になり得ます。

注意

rollback 有効時は、バックアップ複製(copyTo)のコストが書き込み対象シートのサイズに比例して掛かり、GAS の 6 分実行制限も消費します。大きいシートへのトランザクションや高頻度のトランザクションでは rollback: false を検討してください。

制限事項

  • ロックが直列化するのは、GASsma を経由する処理同士だけです。スプレッドシートの手動編集や、GASsma を使わない別スクリプトからの変更は止められません。同時実行で何が起こりうるかは書き込みの原子性と同時実行を参照してください。
  • ロックは GASsma ライブラリのものです。 GASsma はライブラリとして動くため、$transaction が取るロックはあなたのスクリプトプロジェクトのものではなく、GASsma 自身のものです。同じライブラリを使う他のスクリプトプロジェクトとも共有されるため、別のプロジェクトのトランザクションが実行中であれば、そちらの完了を待つことがあります。
  • 実行が強制終了された場合(GAS の 6 分実行制限など)は、バックアップシートが残ることがあります。次回の $transaction 実行時に警告ログが出力されます。残った _gassma_tx_... シートは、中身を確認のうえ手動で削除して構いません。
  • 復元されるのはセルの値と数式のみです(書式などは対象外)。
  • changeSettings で実行時に変更した設定は、トランザクション内に引き継がれません。
  • 操作の配列を渡す形(Prisma の sequential operations)は非対応です。コールバック形のみ使えます。
  • isolationLevel は非対応です(常にロックによる直列実行)。

関連エラー

GassmaTransactionLockTimeoutError / GassmaTransactionTimeoutError / GassmaNestedTransactionError / GassmaTransactionRollbackError の詳細はエラー一覧を参照してください。