Tasks and Transactions

Tasks let you reuse the same connection for multiple statements, avoiding the overhead of acquiring and releasing the connection in between. The task connection is only released once the task has completed, returning a resolved promise, or failed and returned a rejected promise. However, in the latter case, anything executed before the failure will still have been applied to the database state. If you're not careful, this could lead to inconsistent data.

Database transactions are similar to tasks, except they're all-or-nothing: if any individual statement fails to complete, the whole set is rolled back -- as if none of them had ever executed in the first place. Only when the final statement has completed can the transaction be committed into the database. Script files and functions naturally execute in a single transaction, but the Massive API allows complex workflows to be broken apart and processed safely.

Begin a task with the db.withConnection method, or a transaction with db.withTransaction. Each takes as its first argument an async or promise-returning function which presumably involves Massive API calls. This function's first and only argument is a copy of the db object with all your tables, views, functions, and scripts attached, but which routes all database calls through the task or transaction.

db.withConnection and db.withTransaction may each take a second options argument, and find queries in a transaction context gain options for locking records.

db.withTransaction(async tx => {
  const user = await tx.users.findOne({}, {
    lock: {
      strength: 'UPDATE'
    }
  });

  const test = await tx.tests.insert({
    user_id: user.id,
    name: 'sample test'
  });

  const issues = await tx.issues.insert([{
    summary: 'sample issue 1'
  }, {
    summary: 'sample issue 2'
  }]);

  return tx.users.save({
    id: txUser.id,
    tests_started: txUser.tests_started + 1
  });
}, {
  tag: 'my transaction',
  mode: new db.pgp.txMode.TransactionMode({
    tiLevel: db.pgp.txMode.isolationLevel.serializable
  })
});

Since this example uses withTransaction, the transaction will be rolled back if an error occurs anywhere in the async transaction callback. If all statements are successful, a new test is started, issues are created, and the user is updated!