From 8f71808a68aa4d38de4a115dd453bcd9277901e5 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 6 Dec 2017 11:43:40 -0500 Subject: [PATCH] Support TransactionOptions. --- src/index.js | 14 ++++-- src/transaction.js | 45 ++++++++++++++---- system-test/datastore.js | 30 ++++++++++++ test/index.js | 6 +++ test/transaction.js | 100 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 182 insertions(+), 13 deletions(-) diff --git a/src/index.js b/src/index.js index da1547b26..261ae9bda 100644 --- a/src/index.js +++ b/src/index.js @@ -674,11 +674,19 @@ Datastore.prototype.isKey = Datastore.isKey = function(value) { /** * Create a new Transaction object. * + * @param {object} [options] Configuration object. + * @param {string} [options.id] The ID of a previously run transaction. + * @param {boolean} [options.readOnly=false] A read-only transaction cannot + * modify entities. * @returns {Transaction} - * @private + * + * @example + * const Datastore = require('@google-cloud/datastore'); + * const datastore = new Datastore(); + * const transaction = datastore.transaction(); */ -Datastore.prototype.transaction = function() { - return new Transaction(this); +Datastore.prototype.transaction = function(options) { + return new Transaction(this, options); }; /** diff --git a/src/transaction.js b/src/transaction.js index 54c97ae1a..ab86dc690 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -44,7 +44,7 @@ var Request = require('./request.js'); * const datastore = new Datastore(); * const transaction = datastore.transaction(); */ -function Transaction(datastore) { +function Transaction(datastore, options) { /** * @name Transaction#datastore * @type {Datastore} @@ -62,6 +62,11 @@ function Transaction(datastore) { */ this.namespace = datastore.namespace; + options = options || {}; + + this.id = options.id; + this.readOnly = options.readOnly === true; + this.request = datastore.request_.bind(datastore); // A queue for entity modifications made during the transaction. @@ -378,8 +383,12 @@ Transaction.prototype.rollback = function(gaxOptions, callback) { * Begin a remote transaction. In the callback provided, run your transactional * commands. * - * @param {object} [gaxOptions] Request configuration options, outlined here: - * https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {object} [options] Configuration object. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {boolean} [options.readOnly=false] A read-only transaction cannot + * modify entities. + * @param {string} [options.transactionId] The ID of a previous transaction. * @param {function} callback The function to execute within the context of * a transaction. * @param {?error} callback.err An error returned while making this request. @@ -420,21 +429,41 @@ Transaction.prototype.rollback = function(gaxOptions, callback) { * var apiResponse = data[1]; * }); */ -Transaction.prototype.run = function(gaxOptions, callback) { +Transaction.prototype.run = function(options, callback) { var self = this; - if (is.fn(gaxOptions)) { - callback = gaxOptions; - gaxOptions = {}; + if (is.fn(options)) { + callback = options; + options = {}; } + options = options || {}; callback = callback || common.util.noop; + var reqOpts = { + transactionOptions: {} + }; + + if (options.readOnly || this.readOnly) { + reqOpts.transactionOptions.readOnly = {}; + } + + if (options.transactionId || this.id) { + reqOpts.transactionOptions.readWrite = { + previousTransaction: options.transactionId || this.id + }; + } + + if (options.transactionOptions) { + reqOpts.transactionOptions = options.transactionOptions; + } + this.request_( { client: 'DatastoreClient', method: 'beginTransaction', - gaxOpts: gaxOptions, + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, }, function(err, resp) { if (err) { diff --git a/system-test/datastore.js b/system-test/datastore.js index b2a9b2075..d08b702dc 100644 --- a/system-test/datastore.js +++ b/system-test/datastore.js @@ -979,5 +979,35 @@ describe('Datastore', function() { }); }); }); + + it('should read in a readOnly transaction', function(done) { + var transaction = datastore.transaction({ readOnly: true }); + var key = datastore.key(['Company', 'Google']); + + transaction.run(function(err) { + assert.ifError(err); + transaction.get(key, done); + }); + }); + + it('should not write in a readOnly transaction', function(done) { + var transaction = datastore.transaction({ readOnly: true }); + var key = datastore.key(['Company', 'Google']); + + transaction.run(function(err) { + assert.ifError(err); + + transaction.get(key, function(err) { + assert.ifError(err); + + transaction.save({ key: key, data: {} }); + + transaction.commit(function(err) { + assert(err instanceof Error); + done(); + }); + }); + }); + }); }); }); diff --git a/test/index.js b/test/index.js index 1444469a0..9f142b0fb 100644 --- a/test/index.js +++ b/test/index.js @@ -397,6 +397,12 @@ describe('Datastore', function() { var transaction = datastore.transaction(); assert.strictEqual(transaction.calledWith_[0], datastore); }); + + it('should pass options to the Transaction constructor', function() { + var options = {}; + var transaction = datastore.transaction(options); + assert.strictEqual(transaction.calledWith_[1], options); + }); }); describe('determineBaseUrl_', function() { diff --git a/test/transaction.js b/test/transaction.js index 7aeda9c2b..1c26cf103 100644 --- a/test/transaction.js +++ b/test/transaction.js @@ -105,6 +105,24 @@ describe('Transaction', function() { assert.strictEqual(transaction.namespace, NAMESPACE); }); + it('should localize the transaction ID', function() { + var options = { + id: 'transaction-id' + }; + + var transaction = new Transaction(DATASTORE, options); + assert.strictEqual(transaction.id, options.id); + }); + + it('should localize readOnly', function() { + var options = { + readOnly: true + }; + + var transaction = new Transaction(DATASTORE, options); + assert.strictEqual(transaction.readOnly, true); + }); + it('should localize request function', function(done) { var transaction; @@ -440,7 +458,8 @@ describe('Transaction', function() { transaction.request_ = function(config) { assert.strictEqual(config.client, 'DatastoreClient'); assert.strictEqual(config.method, 'beginTransaction'); - assert.deepEqual(config.gaxOpts, {}); + assert.deepEqual(config.reqOpts, { transactionOptions: {} }); + assert.strictEqual(config.gaxOpts, undefined); done(); }; @@ -455,7 +474,84 @@ describe('Transaction', function() { done(); }; - transaction.run(gaxOptions); + transaction.run({ gaxOptions: gaxOptions }); + }); + + describe('options.readOnly', function() { + it('should respect the readOnly option', function(done) { + var options = { + readOnly: true + }; + + transaction.request_ = function(config) { + assert.deepEqual(config.reqOpts.transactionOptions.readOnly, {}); + done(); + }; + + transaction.run(options, assert.ifError); + }); + + it('should respect the global readOnly option', function(done) { + transaction.readOnly = true; + + transaction.request_ = function(config) { + assert.deepEqual(config.reqOpts.transactionOptions.readOnly, {}); + done(); + }; + + transaction.run(assert.ifError); + }); + }); + + describe('options.transactionId', function() { + it('should respect the transactionId option', function(done) { + var options = { + transactionId: 'transaction-id' + }; + + transaction.request_ = function(config) { + assert.deepEqual(config.reqOpts.transactionOptions.readWrite, { + previousTransaction: options.transactionId + }); + done(); + }; + + transaction.run(options, assert.ifError); + }); + + it('should respect the global transactionId option', function(done) { + transaction.id = 'transaction-id'; + + transaction.request_ = function(config) { + assert.deepEqual(config.reqOpts.transactionOptions.readWrite, { + previousTransaction: transaction.id + }); + done(); + }; + + transaction.run(assert.ifError); + }); + }); + + describe('options.transactionOptions', function() { + it('should allow full override of transactionOptions', function(done) { + transaction.readOnly = true; + + var options = { + transactionOptions: { + readWrite: { + previousTransaction: 'transaction-id' + } + } + }; + + transaction.request_ = function(config) { + assert.deepEqual(config.reqOpts, options); + done(); + }; + + transaction.run(options, assert.ifError); + }); }); describe('error', function() {