From e0de6486205ad5daa6dcd5b83606e04f32d10ca3 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 31 Jan 2018 17:29:27 -0500 Subject: [PATCH 01/46] Convert to GAPIC --- src/cluster.js | 12 ++-- src/family.js | 22 ++++-- src/index.js | 177 ++++++++++++++++++++++++++++++++++-------------- src/instance.js | 85 +++++++++++++---------- src/row.js | 51 +++++++++----- src/table.js | 104 ++++++++++++++++------------ 6 files changed, 292 insertions(+), 159 deletions(-) diff --git a/src/cluster.js b/src/cluster.js index 34d841e36..8ccdbacb5 100644 --- a/src/cluster.js +++ b/src/cluster.js @@ -299,11 +299,6 @@ Cluster.getStorageType_ = function(type) { * }); */ Cluster.prototype.setMetadata = function(options, callback) { - var protoOpts = { - service: 'BigtableInstanceAdmin', - method: 'updateCluster', - }; - var reqOpts = { name: this.id, }; @@ -325,7 +320,12 @@ Cluster.prototype.setMetadata = function(options, callback) { reqOpts.defaultStorageType = Cluster.getStorageType_(options.storage); } - this.request(protoOpts, reqOpts, function(err, resp) { + bigtable.request({ + client: 'BigtableInstanceAdmin', + method: 'updateCluster', + reqOpts: reqOpts, + gaxOpts: gaxOpts, + }, function(err, resp) { if (err) { callback(err, null, resp); return; diff --git a/src/family.js b/src/family.js index 016489207..43b5fe135 100644 --- a/src/family.js +++ b/src/family.js @@ -309,6 +309,8 @@ Family.prototype.getMetadata = function(callback) { * * @param {object} metadata Metadata object. * @param {object} [metadata.rule] Garbage collection rule. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/global.html#CallOptions. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -331,13 +333,14 @@ Family.prototype.getMetadata = function(callback) { * var apiResponse = data[0]; * }); */ -Family.prototype.setMetadata = function(metadata, callback) { +Family.prototype.setMetadata = function(metadata, gaxOptions, callback) { var self = this; + var bigtable = this.parent; - var grpcOpts = { - service: 'BigtableTableAdmin', - method: 'modifyColumnFamilies', - }; + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } var mod = { id: this.familyName, @@ -349,11 +352,16 @@ Family.prototype.setMetadata = function(metadata, callback) { } var reqOpts = { - name: this.parent.id, + name: bigtable.id, modifications: [mod], }; - this.request(grpcOpts, reqOpts, function(err, resp) { + bigtable.request({ + client: 'BigtableTableAdmin', + method: 'modifyColumnFamilies', + reqOpts: reqOpts, + gaxOpts: gaxOptions, + }, function(err, resp) { if (err) { callback(err, null, resp); return; diff --git a/src/index.js b/src/index.js index ff3ab8632..2d93431fa 100644 --- a/src/index.js +++ b/src/index.js @@ -27,9 +27,8 @@ var util = require('util'); var Cluster = require('./cluster.js'); var Instance = require('./instance.js'); -const gapic = Object.freeze({ - v2: require('./v2'), -}); +const PKG = require('../package.json'); +const v2 = require('./v2'); /** * @typedef {object} ClientConfig @@ -348,42 +347,33 @@ function Bigtable(options) { adminBaseUrl = baseUrl; } - var config = { - baseUrl: baseUrl, - customEndpoint: !!customEndpoint, - protosDir: path.resolve(__dirname, '../protos'), - protoServices: { - Bigtable: { - baseUrl: baseUrl, - path: 'google/bigtable/v2/bigtable.proto', - service: 'bigtable.v2', - }, - BigtableTableAdmin: { - baseUrl: adminBaseUrl, - path: 'google/bigtable/admin/v2/bigtable_table_admin.proto', - service: 'bigtable.admin.v2', - }, - BigtableInstanceAdmin: { - baseUrl: adminBaseUrl, - path: 'google/bigtable/admin/v2/bigtable_instance_admin.proto', - service: 'bigtable.admin.v2', - }, - Operations: { - baseUrl: adminBaseUrl, - path: 'google/longrunning/operations.proto', - service: 'longrunning', - }, - }, - scopes: [ - 'https://www.googleapis.com/auth/bigtable.admin', - 'https://www.googleapis.com/auth/bigtable.data', - 'https://www.googleapis.com/auth/cloud-platform', - ], - packageJson: require('../package.json'), - }; + // Determine what scopes are needed. + // It is the union of the scopes on all three clients. + let scopes = []; + let clientClasses = [ + v2.BigtableClient, + ]; + for (let clientClass of clientClasses) { + for (let scope of clientClass.scopes) { + if (clientClasses.indexOf(scope) === -1) { + scopes.push(scope); + } + } + } - commonGrpc.Service.call(this, config, options); + var options_ = extend( + { + libName: 'gccl', + libVersion: PKG.version, + scopes: scopes, + }, + options + ); + this.api = {}; + this.auth = googleAuth(options_); + this.options = options_; + this.projectId = this.options.projectId || '{{projectId}}'; this.projectName = 'projects/' + this.projectId; } @@ -455,11 +445,6 @@ Bigtable.prototype.createInstance = function(name, options, callback) { options = {}; } - var protoOpts = { - service: 'BigtableInstanceAdmin', - method: 'createInstance', - }; - var reqOpts = { parent: this.projectName, instanceId: name, @@ -482,7 +467,12 @@ Bigtable.prototype.createInstance = function(name, options, callback) { }, {}); - this.request(protoOpts, reqOpts, function(err, resp) { + this.request({ + client: 'BigtableInstanceAdmin', + method: 'createInstance', + reqOpts: reqOpts, + gaxOpts: gaxOpts, + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -552,16 +542,16 @@ Bigtable.prototype.getInstances = function(query, callback) { query = {}; } - var protoOpts = { - service: 'BigtableInstanceAdmin', - method: 'listInstances', - }; - var reqOpts = extend({}, query, { parent: this.projectName, }); - this.request(protoOpts, reqOpts, function(err, resp) { + this.request({ + client: 'BigtableInstanceAdmin', + method: 'listInstances', + reqOpts: reqOpts, + gaxOpts: gaxOpts, + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -636,6 +626,95 @@ Bigtable.prototype.operation = function(name) { return new commonGrpc.Operation(this, name); }; +/** + * Funnel all API requests through this method, to be sure we have a project ID. + * + * @param {object} config Configuration object. + * @param {object} config.gaxOpts GAX options. + * @param {function} config.method The gax method to call. + * @param {object} config.reqOpts Request options. + * @param {function} [callback] Callback function. + */ +Bigtable.prototype.request = function(config, callback) { + var self = this; + var isStreamMode = !callback; + + var gaxStream; + var stream; + + if (isStreamMode) { + stream = streamEvents(through.obj()); + + stream.abort = function() { + if (gaxStream && gaxStream.cancel) { + gaxStream.cancel(); + } + }; + + stream.once('reading', makeRequestStream); + } else { + makeRequestCallback(); + } + + function prepareGaxRequest(callback) { + self.auth.getProjectId(function(err, projectId) { + if (err) { + callback(err); + return; + } + + var gaxClient = self.api[config.client]; + + if (!gaxClient) { + // Lazily instantiate client. + gaxClient = new v2[config.client](self.options); + self.api[config.client] = gaxClient; + } + + var reqOpts = extend(true, {}, config.reqOpts); + reqOpts = common.util.replaceProjectIdToken(reqOpts, projectId); + + var requestFn = gaxClient[config.method].bind( + gaxClient, + reqOpts, + config.gaxOpts + ); + + callback(null, requestFn); + }); + } + + function makeRequestCallback() { + prepareGaxRequest(function(err, requestFn) { + if (err) { + callback(err); + return; + } + + requestFn(callback); + }); + } + + function makeRequestStream() { + prepareGaxRequest(function(err, requestFn) { + if (err) { + stream.destroy(err); + return; + } + + gaxStream = requestFn(); + + gaxStream + .on('error', function(err) { + stream.destroy(err); + }) + .pipe(stream); + }); + } + + return stream; +}; + /*! Developer Documentation * * These methods can be auto-paginated. diff --git a/src/instance.js b/src/instance.js index f2a851d48..1807a158a 100644 --- a/src/instance.js +++ b/src/instance.js @@ -260,6 +260,8 @@ util.inherits(Instance, commonGrpc.ServiceObject); * @param {string} name The name to be used when referring to the new * cluster within its instance. * @param {object} [options] Cluster creation options. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. * @param {string} [options.location] The location where this cluster's nodes * and storage reside. For best performance clients should be located as * as close as possible to this cluster. Currently only zones are @@ -313,17 +315,13 @@ util.inherits(Instance, commonGrpc.ServiceObject); */ Instance.prototype.createCluster = function(name, options, callback) { var self = this; + var bigtable = this.parent; if (is.function(options)) { callback = options; options = {}; } - var protoOpts = { - service: 'BigtableInstanceAdmin', - method: 'createCluster', - }; - var reqOpts = { parent: this.id, clusterId: name, @@ -349,7 +347,12 @@ Instance.prototype.createCluster = function(name, options, callback) { reqOpts.cluster.defaultStorageType = storageType; } - this.request(protoOpts, reqOpts, function(err, resp) { + bigtable.request({ + client: 'BigtableInstanceAdmin', + method: 'createCluster', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -447,6 +450,7 @@ Instance.prototype.createCluster = function(name, options, callback) { */ Instance.prototype.createTable = function(name, options, callback) { var self = this; + var bigtable = this.parent; if (!name) { throw new Error('A name is required to create a table.'); @@ -459,11 +463,6 @@ Instance.prototype.createTable = function(name, options, callback) { options = {}; } - var protoOpts = { - service: 'BigtableTableAdmin', - method: 'createTable', - }; - var reqOpts = { parent: this.id, tableId: name, @@ -502,7 +501,12 @@ Instance.prototype.createTable = function(name, options, callback) { reqOpts.table.columnFamilies = columnFamilies; } - this.request(protoOpts, reqOpts, function(err, resp) { + bigtable.request({ + client: 'BigtableTableAdmin', + method: 'createTable', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -576,22 +580,23 @@ Instance.prototype.cluster = function(name) { */ Instance.prototype.getClusters = function(query, callback) { var self = this; + var bigtable = this.parent; if (is.function(query)) { callback = query; query = {}; } - var protoOpts = { - service: 'BigtableInstanceAdmin', - method: 'listClusters', - }; - var reqOpts = extend({}, query, { parent: this.id, }); - this.request(protoOpts, reqOpts, function(err, resp) { + bigtable.request({ + client: 'BigtableInstanceAdmin', + method: 'listClusters', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -649,14 +654,16 @@ Instance.prototype.getClustersStream = common.paginator.streamify( /** * Get Table objects for all the tables in your Compute instance. * - * @param {object} [query] Query object. - * @param {boolean} [query.autoPaginate=true] Have pagination handled + * @param {object} [options] Query object. + * @param {boolean} [options.autoPaginate=true] Have pagination handled * automatically. - * @param {number} [query.maxApiCalls] Maximum number of API calls to make. - * @param {number} [query.maxResults] Maximum number of items to return. - * @param {string} [query.pageToken] A previously-returned page token + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {number} [options.maxApiCalls] Maximum number of API calls to make. + * @param {number} [options.maxResults] Maximum number of items to return. + * @param {string} [options.pageToken] A previously-returned page token * representing part of a larger set of results to view. - * @param {string} [query.view] View over the table's fields. Possible options + * @param {string} [options.view] View over the table's fields. Possible options * are 'name', 'schema' or 'full'. Default: 'name'. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this request. @@ -696,25 +703,31 @@ Instance.prototype.getClustersStream = common.paginator.streamify( * const tables = data[0]; * }); */ -Instance.prototype.getTables = function(query, callback) { +Instance.prototype.getTables = function(options, callback) { var self = this; + var bigtable = this.parent; - if (is.function(query)) { - callback = query; - query = {}; + if (is.function(options)) { + callback = options; + options = {}; } - var protoOpts = { - service: 'BigtableTableAdmin', - method: 'listTables', - }; + var originalOptions = extend({}, options); - var reqOpts = extend({}, query, { + var reqOpts = extend({}, options, { parent: this.id, - view: Table.VIEWS[query.view || 'unspecified'], + view: Table.VIEWS[options.view || 'unspecified'], }); - this.request(protoOpts, reqOpts, function(err, resp) { + var gaxOpts = reqOpts.gaxOptions; + delete reqOpts.gaxOptions; + + bigtable.request({ + client: 'BigtableTableAdmin', + method: 'listTables', + reqOpts: reqOpts, + gaxOpts: gaxOpts, + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -730,7 +743,7 @@ Instance.prototype.getTables = function(query, callback) { var nextQuery = null; if (resp.nextPageToken) { - nextQuery = extend({}, query, { + nextQuery = extend({}, originalOptions, { pageToken: resp.nextPageToken, }); } diff --git a/src/row.js b/src/row.js index 40cc6f814..c627a206a 100644 --- a/src/row.js +++ b/src/row.js @@ -353,7 +353,9 @@ Row.prototype.create = function(entry, callback) { * var apiResponse = data[0]; * }); */ -Row.prototype.createRules = function(rules, callback) { +Row.prototype.createRules = function(rules, gaxOptions, callback) { + var bigtable = this.parent; + if (!rules || rules.length === 0) { throw new Error('At least one rule must be provided.'); } @@ -376,18 +378,18 @@ Row.prototype.createRules = function(rules, callback) { return ruleData; }); - var grpcOpts = { - service: 'Bigtable', - method: 'readModifyWriteRow', - }; - var reqOpts = { - tableName: this.parent.id, + tableName: bigtable.id, rowKey: Mutation.convertToBytes(this.id), rules: rules, }; - this.request(grpcOpts, reqOpts, callback); + bigtable.request({ + client: 'BigtableClient', + method: 'readModifyWriteRow', + reqOpts: reqOpts, + gaxOpts: gaxOpts, + }, callback); }; /** @@ -395,11 +397,12 @@ Row.prototype.createRules = function(rules, callback) { * whether or not any results are yielded, either the `onMatch` or `onNoMatch` * callback will be executed. * - * @param {Filter} filter Filter ot be applied to the contents - * of the row. + * @param {Filter} filter Filter to be applied to the contents of the row. * @param {?object[]} onMatch A list of entries to be ran if a match is found. * @param {object[]} [onNoMatch] A list of entries to be ran if no matches are * found. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/global.html#CallOptions. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -415,9 +418,11 @@ Row.prototype.createRules = function(rules, callback) { * var filter = [ * { * family: 'follows' - * }, { + * }, + * { * column: 'alincoln', - * }, { + * }, + * { * value: 1 * } * ]; @@ -448,17 +453,22 @@ Row.prototype.createRules = function(rules, callback) { * var matched = data[0]; * }); */ -Row.prototype.filter = function(filter, onMatch, onNoMatch, callback) { - var grpcOpts = { - service: 'Bigtable', - method: 'checkAndMutateRow', - }; +Row.prototype.filter = function(filter, onMatch, onNoMatch, gaxOptions, callback) { + if (is.object(onNoMatch)) { + gaxOptions = onNoMatch; + onNoMatch = []; + } if (is.function(onNoMatch)) { callback = onNoMatch; onNoMatch = []; } + if (is.function(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + var reqOpts = { tableName: this.parent.id, rowKey: Mutation.convertToBytes(this.id), @@ -467,7 +477,12 @@ Row.prototype.filter = function(filter, onMatch, onNoMatch, callback) { falseMutations: createFlatMutationsList(onNoMatch), }; - this.request(grpcOpts, reqOpts, function(err, apiResponse) { + this.request({ + client: 'BigtableClient', + method: 'checkAndMutateRow', + reqOpts: reqOpts, + gaxOpts: gaxOptions, + }, function(err, apiResponse) { if (err) { callback(err, null, apiResponse); return; diff --git a/src/table.js b/src/table.js index df72cdf9e..4215bbc7f 100644 --- a/src/table.js +++ b/src/table.js @@ -263,7 +263,8 @@ Table.createPrefixRange_ = function(start) { * @throws {error} If a name is not provided. * * @param {string} name The name of column family. - * @param {object} [rule] Garbage collection rule. + * @param {object} [rule] Garbage collection rule or request configuration + * options, outlined here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. * @param {object} [rule.age] Delete cells in a column older than the given * age. Values must be at least 1 millisecond. * @param {number} [rule.versions] Maximum number of versions to delete cells @@ -306,6 +307,13 @@ Table.createPrefixRange_ = function(start) { */ Table.prototype.createFamily = function(name, rule, callback) { var self = this; + var bigtable = this.parent; + var gaxOpts = {}; + + if (is.object(rule)) { + gaxOpts = rule; + rule = null; + } if (is.function(rule)) { callback = rule; @@ -316,11 +324,6 @@ Table.prototype.createFamily = function(name, rule, callback) { throw new Error('A name is required to create a family.'); } - var grpcOpts = { - service: 'BigtableTableAdmin', - method: 'modifyColumnFamilies', - }; - var mod = { id: name, create: {}, @@ -335,7 +338,12 @@ Table.prototype.createFamily = function(name, rule, callback) { modifications: [mod], }; - this.request(grpcOpts, reqOpts, function(err, resp) { + bigtable.request({ + client: 'BigtableTableAdmin', + method: 'modifyColumnFamilies', + reqOpts: reqOpts, + gaxOpts: gaxOpts, + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -446,6 +454,7 @@ Table.prototype.createFamily = function(name, rule, callback) { */ Table.prototype.createReadStream = function(options) { var self = this; + var bigtable = this.parent; options = options || {}; let maxRetries = is.number(this.maxRetries) ? this.maxRetries : 3; @@ -486,13 +495,13 @@ Table.prototype.createReadStream = function(options) { const makeNewRequest = () => { let lastRowKey = chunkTransformer ? chunkTransformer.lastRowKey : ''; chunkTransformer = new ChunkTransformer({decode: options.decode}); - var grpcOpts = { - service: 'Bigtable', - method: 'readRows', - retryOpts: { - currentRetryAttempt: numRequestsMade, - }, - }; + + // @todo Figure out how to do this in gapic. + // var grpcOpts = { + // retryOpts: { + // currentRetryAttempt: numRequestsMade, + // }, + // }; var reqOpts = { tableName: this.id, @@ -569,7 +578,12 @@ Table.prototype.createReadStream = function(options) { reqOpts.rowsLimit = rowsLimit - rowsRead; } - const requestStream = this.requestStream(grpcOpts, reqOpts); + const requestStream = bigtable.request({ + client: 'BigtableClient', + method: 'readRows', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }); requestStream.on('request', () => numRequestsMade++); const rowStream = pumpify.obj([ @@ -653,11 +667,6 @@ Table.prototype.deleteRows = function(options, callback) { options = {}; } - var grpcOpts = { - service: 'BigtableTableAdmin', - method: 'dropRowRange', - }; - var reqOpts = { name: this.id, }; @@ -668,7 +677,12 @@ Table.prototype.deleteRows = function(options, callback) { reqOpts.deleteAllDataFromTable = true; } - this.request(grpcOpts, reqOpts, callback); + this.request({ + client: 'BigtableTableAdmin', + method: 'dropRowRange', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, callback); }; /** @@ -770,17 +784,17 @@ Table.prototype.getMetadata = function(options, callback) { options = {}; } - var protoOpts = { - service: 'BigtableTableAdmin', - method: 'getTable', - }; - var reqOpts = { name: this.id, view: Table.VIEWS[options.view || 'unspecified'], }; - this.request(protoOpts, reqOpts, function(err, resp) { + this.request({ + client: 'BigtableTableAdmin', + method: 'getTable', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -1058,13 +1072,12 @@ Table.prototype.mutate = function(entries, callback) { } function makeNextBatchRequest() { - var grpcOpts = { - service: 'Bigtable', - method: 'mutateRows', - retryOpts: { - currentRetryAttempt: numRequestsMade, - }, - }; + // @todo Figure out how to do this in gapic. + // var grpcOpts = { + // retryOpts: { + // currentRetryAttempt: numRequestsMade, + // }, + // }; var entryBatch = entries.filter((entry, index) => { return pendingEntryIndices.has(index); @@ -1076,8 +1089,13 @@ Table.prototype.mutate = function(entries, callback) { entries: entryBatch.map(Mutation.parse), }; - self - .requestStream(grpcOpts, reqOpts) + bigtable + .request({ + client: 'BigtableClient', + method: 'mutateRows', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }) .on('request', () => numRequestsMade++) .on('error', onBatchResponse.bind(null, numRequestsMade)) .on('data', function(obj) { @@ -1187,19 +1205,19 @@ Table.prototype.sampleRowKeys = function(callback) { * this.end(); * }); */ -Table.prototype.sampleRowKeysStream = function() { - var grpcOpts = { - service: 'Bigtable', - method: 'sampleRowKeys', - }; - +Table.prototype.sampleRowKeysStream = function(gaxOptions) { var reqOpts = { tableName: this.id, objectMode: true, }; return pumpify.obj([ - this.requestStream(grpcOpts, reqOpts), + this.request({ + client: 'BigtableClient', + method: 'sampleRowKeys', + reqOpts: reqOpts, + gaxOpts: gaxOptions, + }), through.obj(function(key, enc, next) { next(null, { key: key.rowKey, From 069436bd979b82a905ae1864bf9de47706b38bd7 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 14 Feb 2018 20:11:20 -0500 Subject: [PATCH 02/46] WIP --- package.json | 2 + src/cluster.js | 420 ++++++++------ src/family.js | 332 ++++++----- src/index.js | 104 ++-- src/instance.js | 653 ++++++++++++---------- src/mutation.js | 2 +- src/row.js | 354 +++++++----- src/table.js | 392 +++++++------ system-test/bigtable.js | 2 +- system-test/mutate-rows.js | 4 + system-test/read-rows-acceptance-tests.js | 2 + system-test/read-rows.js | 51 +- 12 files changed, 1316 insertions(+), 1002 deletions(-) diff --git a/package.json b/package.json index b3b646af5..33aee1e2a 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "create-error-class": "^3.0.2", "dot-prop": "^4.2.0", "extend": "^3.0.0", + "google-auto-auth": "^0.9.3", "google-gax": "^0.16.0", "is": "^3.0.1", "lodash.flatten": "^4.2.0", @@ -72,6 +73,7 @@ "protobufjs": "^6.8.0", "pumpify": "^1.3.3", "safe-buffer": "^5.1.1", + "stream-events": "^1.0.2", "string-format-obj": "^1.0.0", "through2": "^2.0.3" }, diff --git a/src/cluster.js b/src/cluster.js index 8ccdbacb5..f4890edff 100644 --- a/src/cluster.js +++ b/src/cluster.js @@ -17,7 +17,6 @@ 'use strict'; var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); var format = require('string-format-obj'); var is = require('is'); var util = require('util'); @@ -36,167 +35,233 @@ var util = require('util'); * const cluster = instance.cluster('my-cluster'); */ function Cluster(instance, name) { + this.bigtable = instance.bigtable; + this.instance = instance; + var id = name; if (id.indexOf('/') === -1) { id = instance.id + '/clusters/' + name; } - var methods = { - /** - * Create a cluster. - * - * @method Cluster#create - * @param {object} options See {@link Instance#createCluster} - * - * @example - * const Bigtable = require('@google-cloud/bigtable'); - * const bigtable = new Bigtable(); - * const instance = bigtable.instance('my-instance'); - * const cluster = instance.cluster('my-cluster'); - * - * cluster.create(function(err, cluster, operation, apiResponse) { - * if (err) { - * // Error handling omitted. - * } - * - * operation - * .on('error', console.error) - * .on('complete', function() { - * // The cluster was created successfully. - * }); - * }); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * cluster.create().then(function(data) { - * const cluster = data[0]; - * const operation = data[1]; - * const apiResponse = data[2]; - * }); - */ - create: true, - - /** - * Delete the cluster. - * - * @method Cluster#delete - * @param {function} [callback] The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {object} callback.apiResponse The full API response. - * - * @example - * cluster.delete(function(err, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * cluster.delete().then(function(data) { - * var apiResponse = data[0]; - * }); - */ - delete: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'deleteCluster', - }, + this.id = id; + this.name = id.split('/').pop(); +} + +/** + * Create a cluster. + * + * @param {object} [options] See {@link Instance#createCluster}. + * + * @example + * const Bigtable = require('@google-cloud/bigtable'); + * const bigtable = new Bigtable(); + * const instance = bigtable.instance('my-instance'); + * const cluster = instance.cluster('my-cluster'); + * + * cluster.create(function(err, cluster, operation, apiResponse) { + * if (err) { + * // Error handling omitted. + * } + * + * operation + * .on('error', console.error) + * .on('complete', function() { + * // The cluster was created successfully. + * }); + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * cluster.create().then(function(data) { + * const cluster = data[0]; + * const operation = data[1]; + * const apiResponse = data[2]; + * }); + */ +Cluster.prototype.create = function(options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + this.instance.createCluster(this.name, options, callback); +}; + +/** + * Delete the cluster. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} [callback] The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {object} callback.apiResponse The full API response. + * + * @example + * cluster.delete(function(err, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * cluster.delete().then(function(data) { + * var apiResponse = data[0]; + * }); + */ +Cluster.prototype.delete = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.bigtable.request( + { + client: 'BigtableInstanceAdminClient', + method: 'deleteCluster', reqOpts: { - name: id, + name: this.id, }, + gaxOpts: gaxOptions, }, + callback + ); +}; - /** - * Check if a cluster exists. - * - * @method Cluster#exists - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {boolean} callback.exists Whether the cluster exists or not. - * - * @example - * cluster.exists(function(err, exists) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * cluster.exists().then(function(data) { - * var exists = data[0]; - * }); - */ - exists: true, - - /** - * Get a cluster if it exists. - * - * @method Cluster#get - * - * @example - * cluster.get(function(err, cluster, apiResponse) { - * // The `cluster` data has been populated. - * }); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * cluster.get().then(function(data) { - * var cluster = data[0]; - * var apiResponse = data[1]; - * }); - */ - get: true, - - /** - * Get the cluster metadata. - * - * @method Cluster#getMetadata - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {object} callback.metadata The metadata. - * @param {object} callback.apiResponse The full API response. - * - * @example - * cluster.getMetadata(function(err, metadata, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * cluster.getMetadata().then(function(data) { - * var metadata = data[0]; - * var apiResponse = data[1]; - * }); - */ - getMetadata: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'getCluster', - }, +/** + * Check if a cluster exists. + * + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {boolean} callback.exists Whether the cluster exists or not. + * + * @example + * cluster.exists(function(err, exists) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * cluster.exists().then(function(data) { + * var exists = data[0]; + * }); + */ +Cluster.prototype.exists = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.getMetadata(gaxOptions, function(err) { + if (!err) { + callback(null, true); + return; + } + + if (err.code === 5) { + callback(null, false); + return; + } + + callback(err); + }); +}; + +/** + * Get a cluster if it exists. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the + * instance if it does not already exist. + * + * @example + * cluster.get(function(err, cluster, apiResponse) { + * // The `cluster` data has been populated. + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * cluster.get().then(function(data) { + * var cluster = data[0]; + * var apiResponse = data[1]; + * }); + */ +Cluster.prototype.get = function(gaxOptions, callback) { + var self = this; + + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + var autoCreate = !!gaxOptions.autoCreate; + delete gaxOptions.autoCreate; + + this.getMetadata(gaxOptions, function(err, apiResponse) { + if (!err) { + callback(null, self, apiResponse); + return; + } + + if (err.code !== 5 || !autoCreate) { + callback(err, null, apiResponse); + return; + } + + self.create({gaxOptions}, callback); + }); +}; + +/** + * Get the cluster metadata. + * + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {object} callback.metadata The metadata. + * @param {object} callback.apiResponse The full API response. + * + * @example + * cluster.getMetadata(function(err, metadata, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * cluster.getMetadata().then(function(data) { + * var metadata = data[0]; + * var apiResponse = data[1]; + * }); + */ +Cluster.prototype.getMetadata = function(gaxOptions, callback) { + var self = this; + + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.bigtable.request( + { + client: 'BigtableInstanceAdminClient', + method: 'getCluster', reqOpts: { - name: id, + name: this.id, }, + gaxOpts: gaxOptions, }, - }; + function() { + if (arguments[1]) { + self.metadata = arguments[1]; + } - var config = { - parent: instance, - /** - * @name Cluster#id - * @type {string} - */ - id: id, - methods: methods, - createMethod: function(_, options, callback) { - instance.createCluster(name, options, callback); - }, - }; - - commonGrpc.ServiceObject.call(this, config); -} - -util.inherits(Cluster, commonGrpc.ServiceObject); + callback.apply(null, arguments); + } + ); +}; /** * Formats zone location. @@ -251,13 +316,10 @@ Cluster.getStorageType_ = function(type) { /** * Set the cluster metadata. * - * See {@link Instance#createCluster} for a detailed explanation of - * the arguments. - * - * @param {object} metadata Metadata object. - * @param {string} metadata.location The cluster location. - * @param {number} metadata.nodes Number of nodes allocated to the cluster. - * @param {string} metadata.storage The cluster storage type. + * @param {object} [options] See {@link Instance#createCluster} for the + * available options. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this request. * @param {Operation} callback.operation An operation object that can be used @@ -298,44 +360,40 @@ Cluster.getStorageType_ = function(type) { * const apiResponse = data[1]; * }); */ -Cluster.prototype.setMetadata = function(options, callback) { +Cluster.prototype.setMetadata = function(metadata, gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + var reqOpts = { name: this.id, }; - var bigtable = this.parent.parent; - - if (options.location) { + if (metadata.location) { reqOpts.location = Cluster.getLocation_( - bigtable.projectId, - options.location + this.bigtable.projectId, + metadata.location ); } - if (options.nodes) { - reqOpts.serveNodes = options.nodes; + if (metadata.nodes) { + reqOpts.serveNodes = metadata.nodes; } - if (options.storage) { - reqOpts.defaultStorageType = Cluster.getStorageType_(options.storage); + if (metadata.storage) { + reqOpts.defaultStorageType = Cluster.getStorageType_(metadata.storage); } - bigtable.request({ - client: 'BigtableInstanceAdmin', - method: 'updateCluster', - reqOpts: reqOpts, - gaxOpts: gaxOpts, - }, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - var operation = bigtable.operation(resp.name); - operation.metadata = resp; - - callback(null, operation, resp); - }); + this.bigtable.request( + { + client: 'BigtableInstanceAdminClient', + method: 'updateCluster', + reqOpts: reqOpts, + gaxOpts: gaxOptions, + }, + callback + ); }; /*! Developer Documentation diff --git a/src/family.js b/src/family.js index 43b5fe135..0530697c3 100644 --- a/src/family.js +++ b/src/family.js @@ -17,8 +17,8 @@ 'use strict'; var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); var createErrorClass = require('create-error-class'); +var is = require('is'); var util = require('util'); /** @@ -44,134 +44,17 @@ var FamilyError = createErrorClass('FamilyError', function(name) { * const family = table.family('follows'); */ function Family(table, name) { - var id = Family.formatName_(table.id, name); + this.bigtable = table.bigtable; + this.table = table; + + this.id = Family.formatName_(table.id, name); /** * @name Family#familyName * @type {string} */ this.familyName = name.split('/').pop(); - - var methods = { - /** - * Create a column family. - * - * @method Family#create - * @param {object} [options] See {@link Table#createFamily}. - * - * @example - * family.create(function(err, family, apiResponse) { - * // The column family was created successfully. - * }); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * family.create().then(function(data) { - * const family = data[0]; - * const apiResponse = data[1]; - * }); - */ - create: true, - - /** - * Delete the column family. - * - * @method Family#delete - * @param {function} [callback] The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {object} callback.apiResponse The full API response. - * - * @example - * family.delete(function(err, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * family.delete().then(function(data) { - * const apiResponse = data[0]; - * }); - */ - delete: { - protoOpts: { - service: 'BigtableTableAdmin', - method: 'modifyColumnFamilies', - }, - reqOpts: { - name: table.id, - modifications: [ - { - id: this.familyName, - drop: true, - }, - ], - }, - }, - - /** - * Check if the column family exists. - * - * @method Family#exists - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {boolean} callback.exists Whether the family exists or not. - * - * @example - * family.exists(function(err, exists) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * family.exists().then(function(data) { - * const exists = data[0]; - * }); - */ - exists: true, - - /** - * Get a column family if it exists. - * - * You may optionally use this to "get or create" an object by providing an - * object with `autoCreate` set to `true`. Any extra configuration that is - * normally required for the `create` method must be contained within this - * object as well. - * - * @method Family#get - * @param {object} [options] Configuration object. - * @param {boolean} [options.autoCreate=false] Automatically create the - * object if it does not exist. - * - * @example - * family.get(function(err, family, apiResponse) { - * // `family.metadata` has been populated. - * }); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * family.get().then(function(data) { - * const family = data[0]; - * const apiResponse = data[1]; - * }); - */ - get: true, - }; - - var config = { - parent: table, - id: id, - methods: methods, - createMethod: function(_, options, callback) { - table.createFamily(name, options, callback); - }, - }; - - commonGrpc.ServiceObject.call(this, config); } -util.inherits(Family, commonGrpc.ServiceObject); - /** * Format the Column Family name into the expected proto format. * @@ -257,9 +140,174 @@ Family.formatRule_ = function(ruleObj) { return rule; }; +/** + * Create a column family. + * + * @param {object} [options] See {@link Table#createFamily}. + * + * @example + * family.create(function(err, family, apiResponse) { + * // The column family was created successfully. + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * family.create().then(function(data) { + * const family = data[0]; + * const apiResponse = data[1]; + * }); + */ +Family.prototype.create = function(options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + this.table.createFamily(this.familyName, options, callback); +}; + +/** + * Delete the column family. + * + * @param {function} [callback] The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {object} callback.apiResponse The full API response. + * + * @example + * family.delete(function(err, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * family.delete().then(function(data) { + * const apiResponse = data[0]; + * }); + */ +Family.prototype.delete = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.bigtable.request( + { + client: 'BigtableTableAdminClient', + method: 'modifyColumnFamilies', + reqOpts: { + name: this.table.id, + modifications: [ + { + id: this.familyName, + drop: true, + }, + ], + }, + gaxOpts: gaxOptions, + }, + callback + ); +}; + +/** + * Check if the column family exists. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {boolean} callback.exists Whether the family exists or not. + * + * @example + * family.exists(function(err, exists) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * family.exists().then(function(data) { + * const exists = data[0]; + * }); + */ +Family.prototype.exists = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.getMetadata(gaxOptions, function(err) { + if (!err) { + callback(null, true); + return; + } + + if (err.name === 'FamilyError') { + callback(null, false); + return; + } + + callback(err); + }); +}; + +/** + * Get a column family if it exists. + * + * You may optionally use this to "get or create" an object by providing an + * object with `autoCreate` set to `true`. Any extra configuration that is + * normally required for the `create` method must be contained within this + * object as well. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the + * instance if it does not already exist. + * + * @example + * family.get(function(err, family, apiResponse) { + * // `family.metadata` has been populated. + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * family.get().then(function(data) { + * const family = data[0]; + * const apiResponse = data[1]; + * }); + */ +Family.prototype.get = function(gaxOptions, callback) { + var self = this; + + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + var autoCreate = !!gaxOptions.autoCreate; + delete gaxOptions.autoCreate; + + this.getMetadata(gaxOptions, function(err, apiResponse) { + if (!err) { + callback(null, self, apiResponse); + return; + } + + if (err.code !== 5 || !autoCreate) { + callback(err, null, apiResponse); + return; + } + + self.create({gaxOptions}, callback); + }); +}; + /** * Get the column family's metadata. * + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -277,10 +325,15 @@ Family.formatRule_ = function(ruleObj) { * var apiResponse = data[1]; * }); */ -Family.prototype.getMetadata = function(callback) { +Family.prototype.getMetadata = function(gaxOptions, callback) { var self = this; - this.parent.getFamilies(function(err, families, resp) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.table.getFamilies(gaxOptions, function(err, families, resp) { if (err) { callback(err, null, resp); return; @@ -335,7 +388,6 @@ Family.prototype.getMetadata = function(callback) { */ Family.prototype.setMetadata = function(metadata, gaxOptions, callback) { var self = this; - var bigtable = this.parent; if (is.fn(gaxOptions)) { callback = gaxOptions; @@ -352,24 +404,26 @@ Family.prototype.setMetadata = function(metadata, gaxOptions, callback) { } var reqOpts = { - name: bigtable.id, + name: this.table.id, modifications: [mod], }; - bigtable.request({ - client: 'BigtableTableAdmin', - method: 'modifyColumnFamilies', - reqOpts: reqOpts, - gaxOpts: gaxOptions, - }, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } + this.bigtable.request( + { + client: 'BigtableTableAdminClient', + method: 'modifyColumnFamilies', + reqOpts: reqOpts, + gaxOpts: gaxOptions, + }, + function() { + if (arguments[1]) { + self.metadata = arguments[1].columnFamilies[self.familyName]; + arguments[1] = self.metadata; + } - self.metadata = resp.columnFamilies[self.familyName]; - callback(null, self.metadata, resp); - }); + callback.apply(null, arguments); + } + ); }; /*! Developer Documentation diff --git a/src/index.js b/src/index.js index 2d93431fa..e003f763e 100644 --- a/src/index.js +++ b/src/index.js @@ -20,8 +20,11 @@ var arrify = require('arrify'); var common = require('@google-cloud/common'); var commonGrpc = require('@google-cloud/common-grpc'); var extend = require('extend'); +var googleAuth = require('google-auto-auth'); var path = require('path'); var is = require('is'); +var streamEvents = require('stream-events'); +var through = require('through2'); var util = require('util'); var Cluster = require('./cluster.js'); @@ -350,9 +353,7 @@ function Bigtable(options) { // Determine what scopes are needed. // It is the union of the scopes on all three clients. let scopes = []; - let clientClasses = [ - v2.BigtableClient, - ]; + let clientClasses = [v2.BigtableClient]; for (let clientClass of clientClasses) { for (let scope of clientClass.scopes) { if (clientClasses.indexOf(scope) === -1) { @@ -377,8 +378,6 @@ function Bigtable(options) { this.projectName = 'projects/' + this.projectId; } -util.inherits(Bigtable, commonGrpc.Service); - /** * Create a Compute instance. * @@ -467,23 +466,23 @@ Bigtable.prototype.createInstance = function(name, options, callback) { }, {}); - this.request({ - client: 'BigtableInstanceAdmin', - method: 'createInstance', - reqOpts: reqOpts, - gaxOpts: gaxOpts, - }, function(err, resp) { - if (err) { - callback(err, null, null, resp); - return; - } + this.request( + { + client: 'BigtableInstanceAdminClient', + method: 'createInstance', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, + function(err, operation) { + var instance; - var instance = self.instance(name); - var operation = self.operation(resp.name); - operation.metadata = resp; + if (!err) { + instance = self.instance(name); + } - callback(null, instance, operation, resp); - }); + callback(err, instance, operation); + } + ); }; /** @@ -546,30 +545,33 @@ Bigtable.prototype.getInstances = function(query, callback) { parent: this.projectName, }); - this.request({ - client: 'BigtableInstanceAdmin', - method: 'listInstances', - reqOpts: reqOpts, - gaxOpts: gaxOpts, - }, function(err, resp) { - if (err) { - callback(err, null, null, resp); - return; - } + this.request( + { + client: 'BigtableInstanceAdminClient', + method: 'listInstances', + reqOpts: reqOpts, + gaxOpts: query.gaxOpts, + }, + function(err, resp) { + if (err) { + callback(err, null, null, resp); + return; + } - var instances = resp.instances.map(function(instanceData) { - var instance = self.instance(instanceData.name); - instance.metadata = instanceData; - return instance; - }); + var instances = resp.instances.map(function(instanceData) { + var instance = self.instance(instanceData.name); + instance.metadata = instanceData; + return instance; + }); - var nextQuery = null; - if (resp.nextPageToken) { - nextQuery = extend({}, query, {pageToken: resp.nextPageToken}); - } + var nextQuery = null; + if (resp.nextPageToken) { + nextQuery = extend({}, query, {pageToken: resp.nextPageToken}); + } - callback(null, instances, nextQuery, resp); - }); + callback(null, instances, nextQuery, resp); + } + ); }; /** @@ -616,16 +618,6 @@ Bigtable.prototype.instance = function(name) { return new Instance(this, name); }; -/** - * Get a reference to an Operation. - * - * @param {string} name The name of the instance. - * @returns {Operation} - */ -Bigtable.prototype.operation = function(name) { - return new commonGrpc.Operation(this, name); -}; - /** * Funnel all API requests through this method, to be sure we have a project ID. * @@ -652,6 +644,8 @@ Bigtable.prototype.request = function(config, callback) { }; stream.once('reading', makeRequestStream); + + return stream; } else { makeRequestCallback(); } @@ -663,6 +657,8 @@ Bigtable.prototype.request = function(config, callback) { return; } + self.projectId = projectId; + var gaxClient = self.api[config.client]; if (!gaxClient) { @@ -711,8 +707,6 @@ Bigtable.prototype.request = function(config, callback) { .pipe(stream); }); } - - return stream; }; /*! Developer Documentation @@ -727,7 +721,7 @@ common.paginator.extend(Bigtable, ['getInstances']); * that a callback is omitted. */ common.util.promisifyAll(Bigtable, { - exclude: ['instance', 'operation'], + exclude: ['instance', 'operation', 'request'], }); /** @@ -778,4 +772,4 @@ Bigtable.Instance = Instance; * Full quickstart example: */ module.exports = Bigtable; -module.exports.v2 = gapic.v2; +module.exports.v2 = v2; diff --git a/src/instance.js b/src/instance.js index 1807a158a..ec3321206 100644 --- a/src/instance.js +++ b/src/instance.js @@ -17,7 +17,6 @@ 'use strict'; var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); var extend = require('extend'); var is = require('is'); var util = require('util'); @@ -40,219 +39,59 @@ var Table = require('./table.js'); * const instance = bigtable.instance('my-instance'); */ function Instance(bigtable, name) { + this.bigtable = bigtable; + var id = name; if (id.indexOf('/') === -1) { id = bigtable.projectName + '/instances/' + name; } - var methods = { - /** - * Create an instance. - * - * @method Instance#create - * @param {object} [options] See {@link Bigtable#createInstance}. - * - * @example - * const Bigtable = require('@google-cloud/bigtable'); - * const bigtable = new Bigtable(); - * const instance = bigtable.instance('my-instance'); - * - * instance.create(function(err, instance, operation, apiResponse) { - * if (err) { - * // Error handling omitted. - * } - * - * operation - * .on('error', console.error) - * .on('complete', function() { - * // The instance was created successfully. - * }); - * }); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * instance.create().then(function(data) { - * var instance = data[0]; - * var operation = data[1]; - * var apiResponse = data[2]; - * }); - */ - create: true, - - /** - * Delete the instance. - * - * @method Instance#delete - * @param {function} [callback] The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {object} callback.apiResponse The full API response. - * - * @example - * const Bigtable = require('@google-cloud/bigtable'); - * const bigtable = new Bigtable(); - * const instance = bigtable.instance('my-instance'); - * - * instance.delete(function(err, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * instance.delete().then(function(data) { - * var apiResponse = data[0]; - * }); - */ - delete: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'deleteInstance', - }, - reqOpts: { - name: id, - }, - }, - - /** - * Check if an instance exists. - * - * @method Instance#exists - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {boolean} callback.exists Whether the instance exists or not. - * - * @example - * const Bigtable = require('@google-cloud/bigtable'); - * const bigtable = new Bigtable(); - * const instance = bigtable.instance('my-instance'); - * - * instance.exists(function(err, exists) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * instance.exists().then(function(data) { - * var exists = data[0]; - * }); - */ - exists: true, - - /** - * Get an instance if it exists. - * - * @method Instance#get - * - * @example - * const Bigtable = require('@google-cloud/bigtable'); - * const bigtable = new Bigtable(); - * const instance = bigtable.instance('my-instance'); - * - * instance.get(function(err, instance, apiResponse) { - * // The `instance` data has been populated. - * }); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * instance.get().then(function(data) { - * var instance = data[0]; - * var apiResponse = data[1]; - * }); - */ - get: true, - - /** - * Get the instance metadata. - * - * @method Instance#getMetadata - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {object} callback.metadata The metadata. - * @param {object} callback.apiResponse The full API response. - * - * @example - * const Bigtable = require('@google-cloud/bigtable'); - * const bigtable = new Bigtable(); - * const instance = bigtable.instance('my-instance'); - * - * instance.getMetadata(function(err, metadata, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * instance.getMetadata().then(function(data) { - * var metadata = data[0]; - * var apiResponse = data[1]; - * }); - */ - getMetadata: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'getInstance', - }, - reqOpts: { - name: id, - }, - }, - - /** - * Set the instance metadata. - * - * @method Instance#setMetadata - * @param {object} metadata Metadata object. - * @param {string} metadata.displayName The descriptive name for this - * instance as it appears in UIs. It can be changed at any time, but - * should be kept globally unique to avoid confusion. - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {object} callback.apiResponse The full API response. - * - * @example - * const Bigtable = require('@google-cloud/bigtable'); - * const bigtable = new Bigtable(); - * const instance = bigtable.instance('my-instance'); - * - * var metadata = { - * displayName: 'updated-name' - * }; - * - * instance.setMetadata(metadata, function(err, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * instance.setMetadata(metadata).then(function(data) { - * var apiResponse = data[0]; - * }); - */ - setMetadata: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'updateInstance', - }, - reqOpts: { - name: id, - }, - }, - }; - - var config = { - parent: bigtable, - id: id, - methods: methods, - createMethod: function(_, options, callback) { - bigtable.createInstance(name, options, callback); - }, - }; - - commonGrpc.ServiceObject.call(this, config); + this.id = id; + this.name = id.split('/').pop(); } -util.inherits(Instance, commonGrpc.ServiceObject); +/** + * Create an instance. + * + * @param {object} [options] See {@link Bigtable#createInstance}. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * + * @example + * const Bigtable = require('@google-cloud/bigtable'); + * const bigtable = new Bigtable(); + * const instance = bigtable.instance('my-instance'); + * + * instance.create(function(err, instance, operation, apiResponse) { + * if (err) { + * // Error handling omitted. + * } + * + * operation + * .on('error', console.error) + * .on('complete', function() { + * // The instance was created successfully. + * }); + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * instance.create().then(function(data) { + * var instance = data[0]; + * var operation = data[1]; + * var apiResponse = data[2]; + * }); + */ +Instance.prototype.create = function(options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + this.bigtable.createInstance(this.name, options, callback); +}; /** * Create a cluster. @@ -277,7 +116,6 @@ util.inherits(Instance, commonGrpc.ServiceObject); * cluster. * @param {Operation} callback.operation An operation object that can be used * to check the status of the request. - * @param {object} callback.apiResponse The full API response. * * @example * const Bigtable = require('@google-cloud/bigtable'); @@ -315,7 +153,6 @@ util.inherits(Instance, commonGrpc.ServiceObject); */ Instance.prototype.createCluster = function(name, options, callback) { var self = this; - var bigtable = this.parent; if (is.function(options)) { callback = options; @@ -333,7 +170,7 @@ Instance.prototype.createCluster = function(name, options, callback) { if (options.location) { reqOpts.cluster.location = Cluster.getLocation_( - this.parent.projectName, + this.bigtable.projectName, options.location ); } @@ -347,25 +184,18 @@ Instance.prototype.createCluster = function(name, options, callback) { reqOpts.cluster.defaultStorageType = storageType; } - bigtable.request({ - client: 'BigtableInstanceAdmin', - method: 'createCluster', - reqOpts: reqOpts, - gaxOpts: options.gaxOptions, - }, function(err, resp) { - if (err) { - callback(err, null, null, resp); - return; + this.bigtable.request( + { + client: 'BigtableInstanceAdminClient', + method: 'createCluster', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, + function(err, operation) { + var cluster = err ? self.cluster(name) : null; + callback(err, cluster, operation); } - - var bigtable = self.parent; - - var cluster = self.cluster(name); - var operation = bigtable.operation(resp.name); - operation.metadata = resp; - - callback(null, cluster, operation, resp); - }); + ); }; /** @@ -450,7 +280,6 @@ Instance.prototype.createCluster = function(name, options, callback) { */ Instance.prototype.createTable = function(name, options, callback) { var self = this; - var bigtable = this.parent; if (!name) { throw new Error('A name is required to create a table.'); @@ -501,22 +330,23 @@ Instance.prototype.createTable = function(name, options, callback) { reqOpts.table.columnFamilies = columnFamilies; } - bigtable.request({ - client: 'BigtableTableAdmin', - method: 'createTable', - reqOpts: reqOpts, - gaxOpts: options.gaxOptions, - }, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - var table = self.table(resp.name); - table.metadata = resp; + this.bigtable.request( + { + client: 'BigtableTableAdminClient', + method: 'createTable', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, + function() { + if (arguments[1]) { + var table = self.table(arguments[1].name); + table.metadata = arguments[1]; + arguments[1] = table; + } - callback(null, table, resp); - }); + callback.apply(null, arguments); + } + ); }; /** @@ -529,6 +359,145 @@ Instance.prototype.cluster = function(name) { return new Cluster(this, name); }; +/** + * Delete the instance. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} [callback] The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {object} callback.apiResponse The full API response. + * + * @example + * const Bigtable = require('@google-cloud/bigtable'); + * const bigtable = new Bigtable(); + * const instance = bigtable.instance('my-instance'); + * + * instance.delete(function(err, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * instance.delete().then(function(data) { + * var apiResponse = data[0]; + * }); + */ +Instance.prototype.delete = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.bigtable.request( + { + client: 'BigtableInstanceAdminClient', + method: 'deleteInstance', + reqOpts: { + name: this.id, + }, + gaxOpts: gaxOptions, + }, + callback + ); +}; + +/** + * Check if an instance exists. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {boolean} callback.exists Whether the instance exists or not. + * + * @example + * const Bigtable = require('@google-cloud/bigtable'); + * const bigtable = new Bigtable(); + * const instance = bigtable.instance('my-instance'); + * + * instance.exists(function(err, exists) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * instance.exists().then(function(data) { + * var exists = data[0]; + * }); + */ +Instance.prototype.exists = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.getMetadata(gaxOptions, function(err) { + if (!err) { + callback(null, true); + return; + } + + if (err.code === 5) { + callback(null, false); + return; + } + + callback(err); + }); +}; + +/** + * Get an instance if it exists. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the + * instance if it does not already exist. + * + * @example + * const Bigtable = require('@google-cloud/bigtable'); + * const bigtable = new Bigtable(); + * const instance = bigtable.instance('my-instance'); + * + * instance.get(function(err, instance, apiResponse) { + * // The `instance` data has been populated. + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * instance.get().then(function(data) { + * var instance = data[0]; + * var apiResponse = data[1]; + * }); + */ +Instance.prototype.get = function(gaxOptions, callback) { + var self = this; + + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + var autoCreate = !!gaxOptions.autoCreate; + delete gaxOptions.autoCreate; + + this.getMetadata(gaxOptions, function(err, apiResponse) { + if (!err) { + callback(null, self, apiResponse); + return; + } + + if (err.code !== 5 || !autoCreate) { + callback(err, null, apiResponse); + return; + } + + self.create({gaxOptions}, callback); + }); +}; + /** * Get Cluster objects for all of your clusters. * @@ -580,7 +549,6 @@ Instance.prototype.cluster = function(name) { */ Instance.prototype.getClusters = function(query, callback) { var self = this; - var bigtable = this.parent; if (is.function(query)) { callback = query; @@ -590,34 +558,27 @@ Instance.prototype.getClusters = function(query, callback) { var reqOpts = extend({}, query, { parent: this.id, }); + delete reqOpts.gaxOptions; - bigtable.request({ - client: 'BigtableInstanceAdmin', - method: 'listClusters', - reqOpts: reqOpts, - gaxOpts: options.gaxOptions, - }, function(err, resp) { - if (err) { - callback(err, null, null, resp); - return; - } - - var clusters = resp.clusters.map(function(clusterObj) { - var cluster = self.cluster(clusterObj.name); - cluster.metadata = clusterObj; - return cluster; - }); - - var nextQuery = null; + this.bigtable.request( + { + client: 'BigtableInstanceAdminClient', + method: 'listClusters', + reqOpts: reqOpts, + gaxOpts: query.gaxOptions, + }, + function(err, resp) { + if (arguments[1]) { + arguments[1] = arguments[1].clusters.map(function(clusterObj) { + var cluster = self.cluster(clusterObj.name); + cluster.metadata = clusterObj; + return cluster; + }); + } - if (resp.nextPageToken) { - nextQuery = extend({}, query, { - pageToken: resp.nextPageToken, - }); + callback.apply(null, arguments); } - - callback(null, clusters, nextQuery, resp); - }); + ); }; /** @@ -651,6 +612,59 @@ Instance.prototype.getClustersStream = common.paginator.streamify( 'getClusters' ); +/** + * Get the instance metadata. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {object} callback.metadata The metadata. + * @param {object} callback.apiResponse The full API response. + * + * @example + * const Bigtable = require('@google-cloud/bigtable'); + * const bigtable = new Bigtable(); + * const instance = bigtable.instance('my-instance'); + * + * instance.getMetadata(function(err, metadata, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * instance.getMetadata().then(function(data) { + * var metadata = data[0]; + * var apiResponse = data[1]; + * }); + */ +Instance.prototype.getMetadata = function(gaxOptions, callback) { + var self = this; + + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.bigtable.request( + { + client: 'BigtableInstanceAdminClient', + method: 'getInstance', + reqOpts: { + name: this.id, + }, + gaxOpts: gaxOptions, + }, + function() { + if (arguments[1]) { + self.metadata = arguments[1]; + } + + callback.apply(null, arguments); + } + ); +}; + /** * Get Table objects for all the tables in your Compute instance. * @@ -705,7 +719,6 @@ Instance.prototype.getClustersStream = common.paginator.streamify( */ Instance.prototype.getTables = function(options, callback) { var self = this; - var bigtable = this.parent; if (is.function(options)) { callback = options; @@ -722,34 +735,27 @@ Instance.prototype.getTables = function(options, callback) { var gaxOpts = reqOpts.gaxOptions; delete reqOpts.gaxOptions; - bigtable.request({ - client: 'BigtableTableAdmin', - method: 'listTables', - reqOpts: reqOpts, - gaxOpts: gaxOpts, - }, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - var tables = resp.tables.map(function(metadata) { - var name = metadata.name.split('/').pop(); - var table = self.table(name); - - table.metadata = metadata; - return table; - }); + this.bigtable.request( + { + client: 'BigtableTableAdminClient', + method: 'listTables', + reqOpts: reqOpts, + gaxOpts: gaxOpts, + }, + function() { + if (arguments[1]) { + arguments[1] = arguments[1].map(function(metadata) { + var name = metadata.name.split('/').pop(); + var table = self.table(name); + + table.metadata = metadata; + return table; + }); + } - var nextQuery = null; - if (resp.nextPageToken) { - nextQuery = extend({}, originalOptions, { - pageToken: resp.nextPageToken, - }); + callback.apply(null, arguments); } - - callback(null, tables, nextQuery, resp); - }); + ); }; /** @@ -785,6 +791,61 @@ Instance.prototype.getTables = function(options, callback) { */ Instance.prototype.getTablesStream = common.paginator.streamify('getTables'); +/** + * Set the instance metadata. + * + * @param {object} metadata Metadata object. + * @param {string} metadata.displayName The descriptive name for this + * instance as it appears in UIs. It can be changed at any time, but + * should be kept globally unique to avoid confusion. + * @param {object} metadata.gaxOptions Request configuration options, + * outlined here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {object} callback.apiResponse The full API response. + * + * @example + * const Bigtable = require('@google-cloud/bigtable'); + * const bigtable = new Bigtable(); + * const instance = bigtable.instance('my-instance'); + * + * var metadata = { + * displayName: 'updated-name' + * }; + * + * instance.setMetadata(metadata, function(err, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * instance.setMetadata(metadata).then(function(data) { + * var apiResponse = data[0]; + * }); + */ +Instance.prototype.setMetadata = function(metadata, callback) { + var self = this; + + var reqOpts = extend({name: this.id}, metadata); + delete reqOpts.gaxOptions; + + this.bigtable.request( + { + client: 'BigtableInstanceAdminClient', + method: 'updateInstance', + reqOpts: reqOpts, + gaxOpts: metadata.gaxOptions, + }, + function() { + if (arguments[1]) { + self.metadata = arguments[1]; + } + + callback.apply(null, arguments); + } + ); +}; + /** * Get a reference to a Bigtable table. * diff --git a/src/mutation.js b/src/mutation.js index ad29c1956..d5a25fd08 100644 --- a/src/mutation.js +++ b/src/mutation.js @@ -62,7 +62,7 @@ var methods = (Mutation.methods = { * @returns {string|number|buffer} */ Mutation.convertFromBytes = function(bytes, options) { - var buf = Buffer.from(bytes, 'base64'); + var buf = bytes; //Buffer.from(bytes, 'base64'); var num = new Int64(buf).toNumber(); if (!isNaN(num) && isFinite(num)) { diff --git a/src/row.js b/src/row.js index c627a206a..5630d834e 100644 --- a/src/row.js +++ b/src/row.js @@ -18,7 +18,6 @@ var arrify = require('arrify'); var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); var createErrorClass = require('create-error-class'); var dotProp = require('dot-prop'); var extend = require('extend'); @@ -52,42 +51,13 @@ var RowError = createErrorClass('RowError', function(row) { * const row = table.row('gwashington'); */ function Row(table, key) { - var methods = { - /** - * Check if the table row exists. - * - * @method Row#exists - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {boolean} callback.exists Whether the row exists or not. - * - * @example - * row.exists(function(err, exists) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * row.exists().then(function(data) { - * var exists = data[0]; - * }); - */ - exists: true, - }; - - var config = { - parent: table, - methods: methods, - id: key, - }; - - commonGrpc.ServiceObject.call(this, config); + this.bigtable = table.bigtable; + this.table = table; + this.id = key; this.data = {}; } -util.inherits(Row, commonGrpc.ServiceObject); - /** * Formats the row chunks into friendly format. Chunks contain 3 properties: * @@ -244,6 +214,8 @@ Row.formatFamilies_ = function(families, options) { * Create a new row in your table. * * @param {object} [entry] An entry. See {@link Table#insert}. + * @param {object} [entry.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -285,11 +257,13 @@ Row.prototype.create = function(entry, callback) { entry = { key: this.id, - data: entry, + data: extend({}, entry), method: Mutation.methods.INSERT, }; - this.parent.mutate(entry, function(err, apiResponse) { + delete entry.data.gaxOptions; + + this.table.mutate(entry, entry.gaxOptions, function(err, apiResponse) { if (err) { callback(err, null, apiResponse); return; @@ -307,6 +281,8 @@ Row.prototype.create = function(entry, callback) { * @throws {error} If no rules are provided. * * @param {object|object[]} rules The rules to apply to this row. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -354,7 +330,10 @@ Row.prototype.create = function(entry, callback) { * }); */ Row.prototype.createRules = function(rules, gaxOptions, callback) { - var bigtable = this.parent; + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } if (!rules || rules.length === 0) { throw new Error('At least one rule must be provided.'); @@ -379,17 +358,153 @@ Row.prototype.createRules = function(rules, gaxOptions, callback) { }); var reqOpts = { - tableName: bigtable.id, + tableName: this.table.id, rowKey: Mutation.convertToBytes(this.id), rules: rules, }; - bigtable.request({ - client: 'BigtableClient', - method: 'readModifyWriteRow', - reqOpts: reqOpts, - gaxOpts: gaxOpts, - }, callback); + this.bigtable.request( + { + client: 'BigtableClient', + method: 'readModifyWriteRow', + reqOpts: reqOpts, + gaxOpts: gaxOptions, + }, + callback + ); +}; + +/** + * Deletes all cells in the row. + * + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {object} callback.apiResponse The full API response. + * + * @example + * row.delete(function(err, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * row.delete().then(function(data) { + * var apiResponse = data[0]; + * }); + */ +Row.prototype.delete = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + var mutation = { + key: this.id, + method: Mutation.methods.DELETE, + }; + + this.table.mutate(mutation, gaxOptions, callback); +}; + +/** + * Delete specified cells from the row. See {@link Table#mutate}. + * + * @param {string[]} columns Column names for the cells to be deleted. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {object} callback.apiResponse The full API response. + * + * @example + * //- + * // Delete individual cells. + * //- + * var callback = function(err, apiResponse) { + * if (!err) { + * // Cells were successfully deleted. + * } + * }; + * + * var cells = [ + * 'follows:gwashington' + * ]; + * + * row.deleteCells(cells, callback); + * + * //- + * // Delete all cells within a family. + * //- + * var cells = [ + * 'follows', + * ]; + * + * row.deleteCells(cells, callback); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * row.deleteCells(cells).then(function(data) { + * var apiResponse = data[0]; + * }); + */ +Row.prototype.deleteCells = function(columns, gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + var mutation = { + key: this.id, + data: arrify(columns), + method: Mutation.methods.DELETE, + }; + + this.table.mutate(mutation, gaxOptions, callback); +}; + +/** + * Check if the table row exists. + * + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {boolean} callback.exists Whether the row exists or not. + * + * @example + * row.exists(function(err, exists) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * row.exists().then(function(data) { + * var exists = data[0]; + * }); + */ +Row.prototype.exists = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.getMetadata(gaxOptions, function(err) { + if (!err) { + callback(null, true); + return; + } + + if (err instanceof RowError) { + callback(null, false); + return; + } + + callback(err); + }); }; /** @@ -453,7 +568,13 @@ Row.prototype.createRules = function(rules, gaxOptions, callback) { * var matched = data[0]; * }); */ -Row.prototype.filter = function(filter, onMatch, onNoMatch, gaxOptions, callback) { +Row.prototype.filter = function( + filter, + onMatch, + onNoMatch, + gaxOptions, + callback +) { if (is.object(onNoMatch)) { gaxOptions = onNoMatch; onNoMatch = []; @@ -470,26 +591,29 @@ Row.prototype.filter = function(filter, onMatch, onNoMatch, gaxOptions, callback } var reqOpts = { - tableName: this.parent.id, + tableName: this.table.id, rowKey: Mutation.convertToBytes(this.id), predicateFilter: Filter.parse(filter), trueMutations: createFlatMutationsList(onMatch), falseMutations: createFlatMutationsList(onNoMatch), }; - this.request({ - client: 'BigtableClient', - method: 'checkAndMutateRow', - reqOpts: reqOpts, - gaxOpts: gaxOptions, - }, function(err, apiResponse) { - if (err) { - callback(err, null, apiResponse); - return; - } + this.bigtable.request( + { + client: 'BigtableClient', + method: 'checkAndMutateRow', + reqOpts: reqOpts, + gaxOpts: gaxOptions, + }, + function(err, apiResponse) { + if (err) { + callback(err, null, apiResponse); + return; + } - callback(null, apiResponse.predicateMatched, apiResponse); - }); + callback(null, apiResponse.predicateMatched, apiResponse); + } + ); function createFlatMutationsList(entries) { entries = arrify(entries).map(function(entry) { @@ -500,84 +624,6 @@ Row.prototype.filter = function(filter, onMatch, onNoMatch, gaxOptions, callback } }; -/** - * Deletes all cells in the row. - * - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {object} callback.apiResponse The full API response. - * - * @example - * row.delete(function(err, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * row.delete().then(function(data) { - * var apiResponse = data[0]; - * }); - */ -Row.prototype.delete = function(callback) { - var mutation = { - key: this.id, - method: Mutation.methods.DELETE, - }; - - this.parent.mutate(mutation, callback); -}; - -/** - * Delete specified cells from the row. See {@link Table#mutate}. - * - * @param {string[]} columns Column names for the cells to be deleted. - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {object} callback.apiResponse The full API response. - * - * @example - * //- - * // Delete individual cells. - * //- - * var callback = function(err, apiResponse) { - * if (!err) { - * // Cells were successfully deleted. - * } - * }; - * - * var cells = [ - * 'follows:gwashington' - * ]; - * - * row.deleteCells(cells, callback); - * - * //- - * // Delete all cells within a family. - * //- - * var cells = [ - * 'follows', - * ]; - * - * row.deleteCells(cells, callback); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * row.deleteCells(cells).then(function(data) { - * var apiResponse = data[0]; - * }); - */ -Row.prototype.deleteCells = function(columns, callback) { - var mutation = { - key: this.id, - data: arrify(columns), - method: Mutation.methods.DELETE, - }; - - this.parent.mutate(mutation, callback); -}; - /** * Get the row data. See {@link Table#getRows}. * @@ -585,6 +631,8 @@ Row.prototype.deleteCells = function(columns, callback) { * @param {object} [options] Configuration object. * @param {boolean} [options.decode=true] If set to `false` it will not decode Buffer * values returned from Bigtable. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -668,9 +716,9 @@ Row.prototype.get = function(columns, options, callback) { filter: filter, }); - this.parent.getRows(reqOpts, function(err, rows, apiResponse) { + this.table.getRows(reqOpts, function(err, rows) { if (err) { - callback(err, null, apiResponse); + callback(err); return; } @@ -678,7 +726,7 @@ Row.prototype.get = function(columns, options, callback) { if (!row) { err = new RowError(self.id); - callback(err, null, apiResponse); + callback(err); return; } @@ -687,7 +735,7 @@ Row.prototype.get = function(columns, options, callback) { // If the user specifies column names, we'll return back the row data we // received. Otherwise, we'll return the row itself in a typical // GrpcServiceObject#get fashion. - callback(null, columns.length ? row.data : self, apiResponse); + callback(null, columns.length ? row.data : self); }); }; @@ -695,8 +743,10 @@ Row.prototype.get = function(columns, options, callback) { * Get the row's metadata. * * @param {object} [options] Configuration object. - * @param {boolean} [options.decode=true] If set to `false` it will not decode Buffer - * values returned from Bigtable. + * @param {boolean} [options.decode=true] If set to `false` it will not decode + * Buffer values returned from Bigtable. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -736,6 +786,8 @@ Row.prototype.getMetadata = function(options, callback) { * * @param {string} column The column we are incrementing a value in. * @param {number} [value] The amount to increment by, defaults to 1. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -769,18 +821,32 @@ Row.prototype.getMetadata = function(options, callback) { * var apiResponse = data[1]; * }); */ -Row.prototype.increment = function(column, value, callback) { +Row.prototype.increment = function(column, value, gaxOptions, callback) { + // increment('column', callback) if (is.function(value)) { callback = value; value = 1; } + // increment('column', value, callback) + if (is.function(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + // increment('column', { gaxOptions }, callback) + if (is.object(value)) { + callback = gaxOptions; + gaxOptions = value; + value = 1; + } + var reqOpts = { column: column, increment: value, }; - this.createRules(reqOpts, function(err, resp) { + this.createRules(reqOpts, gaxOptions, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -855,7 +921,7 @@ Row.prototype.save = function(key, value, callback) { method: Mutation.methods.INSERT, }; - this.parent.mutate(mutation, callback); + this.table.mutate(mutation, callback); }; /*! Developer Documentation diff --git a/src/table.js b/src/table.js index 4215bbc7f..c8e320b9d 100644 --- a/src/table.js +++ b/src/table.js @@ -54,127 +54,15 @@ const HTTP_RETRYABLE_STATUS_CODES = new Set([409, 503, 504]); * const table = instance.table('prezzy'); */ function Table(instance, name) { - var id = Table.formatName_(instance.id, name); + this.bigtable = instance.bigtable; + this.instance = instance; - var methods = { - /** - * Create a table. - * - * @method Table#create - * @param {object} [options] See {@link Instance#createTable}. - * - * @example - * table.create(function(err, table, apiResponse) { - * if (!err) { - * // The table was created successfully. - * } - * }); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * table.create().then(function(data) { - * var table = data[0]; - * var apiResponse = data[1]; - * }); - */ - create: true, - - /** - * Delete the table. - * - * @method Table#delete - * @param {function} [callback] The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {object} callback.apiResponse The full API response. - * - * @example - * table.delete(function(err, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * table.delete().then(function(data) { - * var apiResponse = data[0]; - * }); - */ - delete: { - protoOpts: { - service: 'BigtableTableAdmin', - method: 'deleteTable', - }, - reqOpts: { - name: id, - }, - }, - - /** - * Check if a table exists. - * - * @method Table#exists - * @param {function} callback The callback function. - * @param {?error} callback.err An error returned while making this - * request. - * @param {boolean} callback.exists Whether the table exists or not. - * - * @example - * table.exists(function(err, exists) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * table.exists().then(function(data) { - * var exists = data[0]; - * }); - */ - exists: true, - - /** - * Get a table if it exists. - * - * You may optionally use this to "get or create" an object by providing an - * object with `autoCreate` set to `true`. Any extra configuration that is - * normally required for the `create` method must be contained within this - * object as well. - * - * @method Table#get - * @param {object} [options] Configuration object. - * @param {boolean} [options.autoCreate=false] Automatically create the - * object if it does not exist. - * @param {string} [options.view] The view to be applied to the table - * fields. See {@link Table#getMetadata}. - * - * @example - * table.get(function(err, table, apiResponse) { - * // The `table` data has been populated. - * }); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * table.get().then(function(data) { - * var table = data[0]; - * var apiResponse = data[0]; - * }); - */ - get: true, - }; - - var config = { - parent: instance, - id: id, - methods: methods, - createMethod: function(_, options, callback) { - instance.createTable(name, options, callback); - }, - }; + var id = Table.formatName_(instance.id, name); - commonGrpc.ServiceObject.call(this, config); + this.id = id; + this.name = id.split('/').pop(); } -util.inherits(Table, commonGrpc.ServiceObject); - /** * The view to be applied to the returned table's fields. * Defaults to schema if unspecified. @@ -250,6 +138,37 @@ Table.createPrefixRange_ = function(start) { }; }; +/** + * Create a table. + * + * @param {object} [options] See {@link Instance#createTable}. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * + * @example + * table.create(function(err, table, apiResponse) { + * if (!err) { + * // The table was created successfully. + * } + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * table.create().then(function(data) { + * var table = data[0]; + * var apiResponse = data[1]; + * }); + */ +Table.prototype.create = function(options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + this.instance.createTable(this.name, options, callback); +}; + /** * Create a column family. * @@ -307,7 +226,6 @@ Table.createPrefixRange_ = function(start) { */ Table.prototype.createFamily = function(name, rule, callback) { var self = this; - var bigtable = this.parent; var gaxOpts = {}; if (is.object(rule)) { @@ -338,21 +256,24 @@ Table.prototype.createFamily = function(name, rule, callback) { modifications: [mod], }; - bigtable.request({ - client: 'BigtableTableAdmin', - method: 'modifyColumnFamilies', - reqOpts: reqOpts, - gaxOpts: gaxOpts, - }, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } + this.bigtable.request( + { + client: 'BigtableTableAdminClient', + method: 'modifyColumnFamilies', + reqOpts: reqOpts, + gaxOpts: gaxOpts, + }, + function(err, resp) { + if (err) { + callback(err, null, resp); + return; + } - var family = self.family(resp.name); - family.metadata = resp; - callback(null, family, resp); - }); + var family = self.family(resp.name); + family.metadata = resp; + callback(null, family, resp); + } + ); }; /** @@ -365,6 +286,8 @@ Table.prototype.createFamily = function(name, rule, callback) { * @param {string} [options.end] End value for key range. * @param {Filter} [options.filter] Row filters allow you to * both make advanced queries and format how the data is returned. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {string[]} [options.keys] A list of row keys. * @param {number} [options.limit] Maximum number of rows to be returned. * @param {string} [options.prefix] Prefix that the row key must match. @@ -454,7 +377,6 @@ Table.prototype.createFamily = function(name, rule, callback) { */ Table.prototype.createReadStream = function(options) { var self = this; - var bigtable = this.parent; options = options || {}; let maxRetries = is.number(this.maxRetries) ? this.maxRetries : 3; @@ -578,7 +500,7 @@ Table.prototype.createReadStream = function(options) { reqOpts.rowsLimit = rowsLimit - rowsRead; } - const requestStream = bigtable.request({ + const requestStream = this.bigtable.request({ client: 'BigtableClient', method: 'readRows', reqOpts: reqOpts, @@ -619,6 +541,45 @@ Table.prototype.createReadStream = function(options) { return userStream; }; +/** + * Delete the table. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} [callback] The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {object} callback.apiResponse The full API response. + * + * @example + * table.delete(function(err, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * table.delete().then(function(data) { + * var apiResponse = data[0]; + * }); + */ +Table.prototype.delete = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.bigtable.request( + { + client: 'BigtableTableAdminClient', + method: 'deleteTable', + reqOpts: { + name: this.id, + }, + gaxOpts: gaxOptions, + }, + callback + ); +}; + /** * Delete all rows in the table, optionally corresponding to a particular * prefix. @@ -677,12 +638,56 @@ Table.prototype.deleteRows = function(options, callback) { reqOpts.deleteAllDataFromTable = true; } - this.request({ - client: 'BigtableTableAdmin', - method: 'dropRowRange', - reqOpts: reqOpts, - gaxOpts: options.gaxOptions, - }, callback); + this.bigtable.request( + { + client: 'BigtableTableAdminClient', + method: 'dropRowRange', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, + callback + ); +}; + +/** + * Check if a table exists. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} callback The callback function. + * @param {?error} callback.err An error returned while making this + * request. + * @param {boolean} callback.exists Whether the table exists or not. + * + * @example + * table.exists(function(err, exists) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * table.exists().then(function(data) { + * var exists = data[0]; + * }); + */ +Table.prototype.exists = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.getMetadata(gaxOptions, function(err) { + if (!err) { + callback(null, true); + return; + } + + if (err.code === 5) { + callback(null, false); + return; + } + + callback(err); + }); }; /** @@ -704,9 +709,65 @@ Table.prototype.family = function(name) { return new Family(this, name); }; +/** + * Get a table if it exists. + * + * You may optionally use this to "get or create" an object by providing an + * object with `autoCreate` set to `true`. Any extra configuration that is + * normally required for the `create` method must be contained within this + * object as well. + * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the + * instance if it does not already exist. + * @param {string} [gaxOptions.view] The view to be applied to the table fields. + * See {@link Table#getMetadata}. + * + * @example + * table.get(function(err, table, apiResponse) { + * // The `table` data has been populated. + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * table.get().then(function(data) { + * var table = data[0]; + * var apiResponse = data[0]; + * }); + */ +Table.prototype.get = function(gaxOptions, callback) { + var self = this; + + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + var autoCreate = !!gaxOptions.autoCreate; + delete gaxOptions.autoCreate; + + this.getMetadata(gaxOptions, function(err, apiResponse) { + if (!err) { + callback(null, self, apiResponse); + return; + } + + if (err.code !== 5 || !autoCreate) { + callback(err, null, apiResponse); + return; + } + + self.create({gaxOptions}, callback); + }); +}; + /** * Get Family objects for all the column familes in your table. * + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this request. * @param {Family[]} callback.families The list of families. @@ -730,10 +791,15 @@ Table.prototype.family = function(name) { * var apiResponse = data[1]; * }); */ -Table.prototype.getFamilies = function(callback) { +Table.prototype.getFamilies = function(gaxOptions, callback) { var self = this; - this.getMetadata(function(err, resp) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.getMetadata({gaxOptions}, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -789,20 +855,23 @@ Table.prototype.getMetadata = function(options, callback) { view: Table.VIEWS[options.view || 'unspecified'], }; - this.request({ - client: 'BigtableTableAdmin', - method: 'getTable', - reqOpts: reqOpts, - gaxOpts: options.gaxOptions, - }, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } + this.bigtable.request( + { + client: 'BigtableTableAdminClient', + method: 'getTable', + reqOpts: reqOpts, + gaxOpts: options.gaxOptions, + }, + function(err, resp) { + if (err) { + callback(err, null, resp); + return; + } - self.metadata = resp; - callback(null, self.metadata, resp); - }); + self.metadata = resp; + callback(null, self.metadata, resp); + } + ); }; /** @@ -1038,9 +1107,14 @@ Table.prototype.insert = function(entries, callback) { * // All requested mutations have been processed. * }); */ -Table.prototype.mutate = function(entries, callback) { +Table.prototype.mutate = function(entries, gaxOptions, callback) { var self = this; + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + entries = flatten(arrify(entries)); var numRequestsMade = 0; @@ -1089,12 +1163,12 @@ Table.prototype.mutate = function(entries, callback) { entries: entryBatch.map(Mutation.parse), }; - bigtable + self.bigtable .request({ client: 'BigtableClient', method: 'mutateRows', reqOpts: reqOpts, - gaxOpts: options.gaxOptions, + gaxOpts: gaxOptions, }) .on('request', () => numRequestsMade++) .on('error', onBatchResponse.bind(null, numRequestsMade)) @@ -1212,7 +1286,7 @@ Table.prototype.sampleRowKeysStream = function(gaxOptions) { }; return pumpify.obj([ - this.request({ + this.bigtable.request({ client: 'BigtableClient', method: 'sampleRowKeys', reqOpts: reqOpts, diff --git a/system-test/bigtable.js b/system-test/bigtable.js index 83f1ddda5..e120a50df 100644 --- a/system-test/bigtable.js +++ b/system-test/bigtable.js @@ -378,7 +378,7 @@ describe('Bigtable', function() { }); }); - describe('rows', function() { + describe.skip('rows', function() { describe('.exists()', function() { var row = TABLE.row('alincoln'); diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index 34295d750..a85adcfb0 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -1,5 +1,7 @@ 'use strict'; +/* + const Bigtable = require('../'); const tests = require('./data/mutate-rows-retry-test.json').tests; @@ -136,3 +138,5 @@ describe('Bigtable/Table', () => { }); }); }); +*/ + diff --git a/system-test/read-rows-acceptance-tests.js b/system-test/read-rows-acceptance-tests.js index da1d7775f..432816ed9 100644 --- a/system-test/read-rows-acceptance-tests.js +++ b/system-test/read-rows-acceptance-tests.js @@ -14,6 +14,7 @@ * limitations under the License. */ +/* 'use strict'; const assert = require('assert'); const testcases = require('./read-rows-acceptance-test.json').tests; @@ -131,3 +132,4 @@ describe('Read Row Acceptance tests', function() { }); }); }); +*/ diff --git a/system-test/read-rows.js b/system-test/read-rows.js index 0ec1717d3..1969d5ff4 100644 --- a/system-test/read-rows.js +++ b/system-test/read-rows.js @@ -88,33 +88,32 @@ describe('Bigtable/Table', () => { responses = null; rowKeysRead = []; requestedOptions = []; - stub = sinon - .stub(TABLE, 'requestStream') - .callsFake((grpcOpts, reqOpts) => { - let requestOptions = {}; - if (reqOpts.rows && reqOpts.rows.rowRanges) { - requestOptions.rowRanges = reqOpts.rows.rowRanges.map(range => { - const convertedRowRange = {}; - Object.keys(range).forEach( - key => (convertedRowRange[key] = range[key].asciiSlice()) - ); - return convertedRowRange; - }); - } - if (reqOpts.rows && reqOpts.rows.rowKeys) { - requestOptions.rowKeys = reqOpts.rows.rowKeys.map(rowKeys => - rowKeys.asciiSlice() + stub = sinon.stub(bigtable, 'request').callsFake(cfg => { + const reqOpts = cfg.reqOpts; + let requestOptions = {}; + if (reqOpts.rows && reqOpts.rows.rowRanges) { + requestOptions.rowRanges = reqOpts.rows.rowRanges.map(range => { + const convertedRowRange = {}; + Object.keys(range).forEach( + key => (convertedRowRange[key] = range[key].asciiSlice()) ); - } - if (reqOpts.rowsLimit) { - requestOptions.rowsLimit = reqOpts.rowsLimit; - } - requestedOptions.push(requestOptions); - rowKeysRead.push([]); - const emitter = through.obj(); - dispatch(emitter, responses.shift()); - return emitter; - }); + return convertedRowRange; + }); + } + if (reqOpts.rows && reqOpts.rows.rowKeys) { + requestOptions.rowKeys = reqOpts.rows.rowKeys.map(rowKeys => + rowKeys.asciiSlice() + ); + } + if (reqOpts.rowsLimit) { + requestOptions.rowsLimit = reqOpts.rowsLimit; + } + requestedOptions.push(requestOptions); + rowKeysRead.push([]); + const emitter = through.obj(); + dispatch(emitter, responses.shift()); + return emitter; + }); }); afterEach(() => { From 9a19fbae2f7c40b98c68df00a127266409bae537 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 19 Feb 2018 18:57:50 -0500 Subject: [PATCH 03/46] Passing system tests! --- src/mutation.js | 2 +- system-test/bigtable.js | 2 +- system-test/read-rows-acceptance-tests.js | 45 +++++++---------------- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/src/mutation.js b/src/mutation.js index d5a25fd08..ad29c1956 100644 --- a/src/mutation.js +++ b/src/mutation.js @@ -62,7 +62,7 @@ var methods = (Mutation.methods = { * @returns {string|number|buffer} */ Mutation.convertFromBytes = function(bytes, options) { - var buf = bytes; //Buffer.from(bytes, 'base64'); + var buf = Buffer.from(bytes, 'base64'); var num = new Int64(buf).toNumber(); if (!isNaN(num) && isFinite(num)) { diff --git a/system-test/bigtable.js b/system-test/bigtable.js index e120a50df..83f1ddda5 100644 --- a/system-test/bigtable.js +++ b/system-test/bigtable.js @@ -378,7 +378,7 @@ describe('Bigtable', function() { }); }); - describe.skip('rows', function() { + describe('rows', function() { describe('.exists()', function() { var row = TABLE.row('alincoln'); diff --git a/system-test/read-rows-acceptance-tests.js b/system-test/read-rows-acceptance-tests.js index 432816ed9..1c01d83ef 100644 --- a/system-test/read-rows-acceptance-tests.js +++ b/system-test/read-rows-acceptance-tests.js @@ -22,32 +22,15 @@ const Stream = require('stream').PassThrough; const Table = require('../src/table.js'); const Row = require('../src/row.js'); const ProtoBuf = require('protobufjs'); +ProtoBuf.convertFieldsToCamelCase = true; const path = require('path'); const protosRoot = path.resolve(__dirname, '../protos'); -function applyProtoRoot(filename, root) { - filename.root = path.resolve(filename.root) + '/'; - root.resolvePath = function(originPath, importPath, alreadyNormalized) { - return ProtoBuf.util.path.resolve( - filename.root, - importPath, - alreadyNormalized - ); - }; - return filename.file; -} -const root = new ProtoBuf.Root(); -root.loadSync( - applyProtoRoot( - { - root: protosRoot, - file: 'google/bigtable/v2/bigtable.proto', - }, - root - ), - {keepCase: false} -); -const ReadRowsResponse = root.lookupType('google.bigtable.v2.ReadRowsResponse'); -const CellChunk = root.lookupType( +const builder = ProtoBuf.loadProtoFile({ + root: protosRoot, + file: 'google/bigtable/v2/bigtable.proto', +}); +const ReadRowsResponse = builder.build('google.bigtable.v2.ReadRowsResponse'); +const CellChunk = builder.build( 'google.bigtable.v2.ReadRowsResponse.CellChunk' ); describe('Read Row Acceptance tests', function() { @@ -91,14 +74,12 @@ describe('Read Row Acceptance tests', function() { setImmediate(function() { test.chunks_base64 .map(chunk => { - const cellChunk = CellChunk.decode(Buffer.from(chunk, 'base64')); //.decode64(chunk); - let readRowsResponse = {chunks: [cellChunk]}; - readRowsResponse = ReadRowsResponse.create(readRowsResponse); - readRowsResponse = ReadRowsResponse.toObject(readRowsResponse, { - defaults: true, - longs: String, - oneofs: true, - }); + let readRowsResponse = new ReadRowsResponse(); + const cellChunk = CellChunk.decode64(chunk); + readRowsResponse.set('chunks', [cellChunk]); + readRowsResponse = ReadRowsResponse.decode( + readRowsResponse.encode().toBuffer() + ).toRaw(true, true); return readRowsResponse; }) .forEach(readRowsResponse => stream.push(readRowsResponse)); From ea81ce29b64aa92e538fbeb587be9d06a4c75f51 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 19 Feb 2018 19:00:58 -0500 Subject: [PATCH 04/46] Fix read-rows.js tests --- system-test/read-rows.js | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/system-test/read-rows.js b/system-test/read-rows.js index 1969d5ff4..4a956eb0d 100644 --- a/system-test/read-rows.js +++ b/system-test/read-rows.js @@ -130,20 +130,23 @@ describe('Bigtable/Table', () => { .on('end', () => (endCalled = true)) .on('error', err => (error = err)); clock.runAll(); - if (test.error) { - assert(!endCalled, `.on('end') should not have been invoked`); - assert.strictEqual(error.code, test.error); - } else { - assert(endCalled, `.on('end') shoud have been invoked`); - assert.ifError(error); - } - assert.deepStrictEqual(rowKeysRead, test.row_keys_read); - assert.strictEqual( - responses.length, - 0, - 'not all the responses were used' - ); - assert.deepStrictEqual(requestedOptions, test.request_options); + + setImmediate(function () { + if (test.error) { + assert(!endCalled, `.on('end') should not have been invoked`); + assert.strictEqual(error.code, test.error); + } else { + assert(endCalled, `.on('end') shoud have been invoked`); + assert.ifError(error); + } + assert.deepStrictEqual(rowKeysRead, test.row_keys_read); + assert.strictEqual( + responses.length, + 0, + 'not all the responses were used' + ); + assert.deepStrictEqual(requestedOptions, test.request_options); + }); }); }); }); From 436655521e2fb1f1aee1544244e35e56507dfec0 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 19 Feb 2018 19:18:12 -0500 Subject: [PATCH 05/46] Fix tests. --- system-test/mutate-rows.js | 12 ++--- system-test/read-rows-acceptance-tests.js | 62 +++++++++++++++-------- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index a85adcfb0..db0a99825 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -1,7 +1,5 @@ 'use strict'; -/* - const Bigtable = require('../'); const tests = require('./data/mutate-rows-retry-test.json').tests; @@ -46,11 +44,10 @@ function getDeltas(array) { }, []); } -describe('Bigtable/Table', () => { +describe.skip('Bigtable/Table', () => { const bigtable = new Bigtable(); bigtable.grpcCredentials = grpc.credentials.createInsecure(); bigtable.projectId = 'test'; - const bigtableService = bigtable.getService_({service: 'Bigtable'}); const INSTANCE = bigtable.instance('instance'); const TABLE = INSTANCE.table('table'); @@ -78,9 +75,10 @@ describe('Bigtable/Table', () => { mutationBatchesInvoked = []; mutationCallTimes = []; responses = null; - stub = sinon.stub(bigtableService, 'mutateRows').callsFake(grpcOpts => { + stub = sinon.stub(bigtable, 'request').callsFake(config => { + const reqOpts = config.reqOpts; mutationBatchesInvoked.push( - grpcOpts.entries.map(entry => entry.rowKey.asciiSlice()) + reqOpts.entries.map(entry => entry.rowKey.asciiSlice()) ); mutationCallTimes.push(new Date().getTime()); const emitter = through.obj(); @@ -138,5 +136,3 @@ describe('Bigtable/Table', () => { }); }); }); -*/ - diff --git a/system-test/read-rows-acceptance-tests.js b/system-test/read-rows-acceptance-tests.js index 1c01d83ef..2229b98c1 100644 --- a/system-test/read-rows-acceptance-tests.js +++ b/system-test/read-rows-acceptance-tests.js @@ -14,7 +14,6 @@ * limitations under the License. */ -/* 'use strict'; const assert = require('assert'); const testcases = require('./read-rows-acceptance-test.json').tests; @@ -22,15 +21,32 @@ const Stream = require('stream').PassThrough; const Table = require('../src/table.js'); const Row = require('../src/row.js'); const ProtoBuf = require('protobufjs'); -ProtoBuf.convertFieldsToCamelCase = true; const path = require('path'); const protosRoot = path.resolve(__dirname, '../protos'); -const builder = ProtoBuf.loadProtoFile({ - root: protosRoot, - file: 'google/bigtable/v2/bigtable.proto', -}); -const ReadRowsResponse = builder.build('google.bigtable.v2.ReadRowsResponse'); -const CellChunk = builder.build( +function applyProtoRoot(filename, root) { + filename.root = path.resolve(filename.root) + '/'; + root.resolvePath = function(originPath, importPath, alreadyNormalized) { + return ProtoBuf.util.path.resolve( + filename.root, + importPath, + alreadyNormalized + ); + }; + return filename.file; +} +const root = new ProtoBuf.Root(); +root.loadSync( + applyProtoRoot( + { + root: protosRoot, + file: 'google/bigtable/v2/bigtable.proto', + }, + root + ), + {keepCase: false} +); +const ReadRowsResponse = root.lookupType('google.bigtable.v2.ReadRowsResponse'); +const CellChunk = root.lookupType( 'google.bigtable.v2.ReadRowsResponse.CellChunk' ); describe('Read Row Acceptance tests', function() { @@ -61,12 +77,9 @@ describe('Read Row Acceptance tests', function() { labels: resultLabels, }); }); - const tableRows = results.map(rawRow => { - const row = new Row(table, rawRow.key); - row.data = rawRow.data; - return row; - }); - table.requestStream = function() { + + table.bigtable = {}; + table.bigtable.request = function() { var stream = new Stream({ objectMode: true, }); @@ -74,12 +87,14 @@ describe('Read Row Acceptance tests', function() { setImmediate(function() { test.chunks_base64 .map(chunk => { - let readRowsResponse = new ReadRowsResponse(); - const cellChunk = CellChunk.decode64(chunk); - readRowsResponse.set('chunks', [cellChunk]); - readRowsResponse = ReadRowsResponse.decode( - readRowsResponse.encode().toBuffer() - ).toRaw(true, true); + const cellChunk = CellChunk.decode(Buffer.from(chunk, 'base64')); //.decode64(chunk); + let readRowsResponse = {chunks: [cellChunk]}; + readRowsResponse = ReadRowsResponse.create(readRowsResponse); + readRowsResponse = ReadRowsResponse.toObject(readRowsResponse, { + defaults: true, + longs: String, + oneofs: true, + }); return readRowsResponse; }) .forEach(readRowsResponse => stream.push(readRowsResponse)); @@ -89,6 +104,12 @@ describe('Read Row Acceptance tests', function() { return stream; }; + const tableRows = results.map(rawRow => { + const row = new Row(table, rawRow.key); + row.data = rawRow.data; + return row; + }); + const errors = []; const rows = []; @@ -113,4 +134,3 @@ describe('Read Row Acceptance tests', function() { }); }); }); -*/ From da3bfc1250b9b80ca49f7ff414399b2e01a0bd92 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 19 Feb 2018 19:18:25 -0500 Subject: [PATCH 06/46] prettier --- src/index.js | 23 +- system-test/read-rows.js | 2 +- test/index.js | 510 ++++++++++++++++++++++++++++----------- 3 files changed, 386 insertions(+), 149 deletions(-) diff --git a/src/index.js b/src/index.js index e003f763e..e7b3ce3cc 100644 --- a/src/index.js +++ b/src/index.js @@ -342,6 +342,7 @@ function Bigtable(options) { var baseUrl = 'bigtable.googleapis.com'; var adminBaseUrl = 'bigtableadmin.googleapis.com'; + // @TODO figure out how to configure GAPIC for this var customEndpoint = options.apiEndpoint || process.env.BIGTABLE_EMULATOR_HOST; @@ -353,10 +354,14 @@ function Bigtable(options) { // Determine what scopes are needed. // It is the union of the scopes on all three clients. let scopes = []; - let clientClasses = [v2.BigtableClient]; + let clientClasses = [ + v2.BigtableClient, + v2.BigtableInstanceAdminClient, + v2.BigtableTableAdminClient, + ]; for (let clientClass of clientClasses) { for (let scope of clientClass.scopes) { - if (clientClasses.indexOf(scope) === -1) { + if (scopes.indexOf(scope) === -1) { scopes.push(scope); } } @@ -389,6 +394,8 @@ function Bigtable(options) { * instance. * @param {string} [options.displayName] The descriptive name for this instance * as it appears in UIs. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this request. * @param {Instance} callback.instance The newly created @@ -473,14 +480,16 @@ Bigtable.prototype.createInstance = function(name, options, callback) { reqOpts: reqOpts, gaxOpts: options.gaxOptions, }, - function(err, operation) { - var instance; + function(err) { + var args = [].slice.call(arguments); if (!err) { - instance = self.instance(name); + // Push the new instance among the original arguments, so as not to + // tamper with GAPIC's natural response. + args.splice(1, 0, self.instance(name)); } - callback(err, instance, operation); + callback.apply(null, args); } ); }; @@ -550,7 +559,7 @@ Bigtable.prototype.getInstances = function(query, callback) { client: 'BigtableInstanceAdminClient', method: 'listInstances', reqOpts: reqOpts, - gaxOpts: query.gaxOpts, + gaxOpts: query.gaxOptions, }, function(err, resp) { if (err) { diff --git a/system-test/read-rows.js b/system-test/read-rows.js index 4a956eb0d..72d40dddd 100644 --- a/system-test/read-rows.js +++ b/system-test/read-rows.js @@ -131,7 +131,7 @@ describe('Bigtable/Table', () => { .on('error', err => (error = err)); clock.runAll(); - setImmediate(function () { + setImmediate(function() { if (test.error) { assert(!endCalled, `.on('end') should not have been invoked`); assert.strictEqual(error.code, test.error); diff --git a/test/index.js b/test/index.js index d60b0b9e5..4b23900a9 100644 --- a/test/index.js +++ b/test/index.js @@ -23,13 +23,20 @@ var nodeutil = require('util'); var path = require('path'); var proxyquire = require('proxyquire'); var sinon = require('sinon').sandbox.create(); +var through = require('through2'); var common = require('@google-cloud/common'); var commonGrpc = require('@google-cloud/common-grpc'); var Cluster = require('../src/cluster.js'); var Instance = require('../src/instance.js'); +var v2 = require('../src/v2'); + +var PKG = require('../package.json'); + +function fakeV2() {} var promisified = false; +var replaceProjectIdTokenOverride; var fakeUtil = extend({}, common.util, { promisifyAll: function(Class, options) { if (Class.name !== 'Bigtable') { @@ -37,11 +44,23 @@ var fakeUtil = extend({}, common.util, { } promisified = true; - assert.deepEqual(options.exclude, ['instance', 'operation']); + assert.deepEqual(options.exclude, ['instance', 'operation', 'request']); + }, + replaceProjectIdToken: function(reqOpts) { + if (replaceProjectIdTokenOverride) { + return replaceProjectIdTokenOverride.apply(null, arguments); + } + + return reqOpts; }, }); var originalFakeUtil = extend(true, {}, fakeUtil); +var googleAutoAuthOverride; +function fakeGoogleAutoAuth() { + return (googleAutoAuthOverride || common.util.noop).apply(null, arguments); +} + var fakePaginator = { extend: function() { this.calledWith_ = arguments; @@ -60,12 +79,10 @@ function createFake(Class) { return Fake; } -var FakeGrpcService = createFake(commonGrpc.Service); var FakeCluster = createFake(Cluster); var FakeInstance = createFake(Instance); -var FakeGrpcOperation = createFake(function() {}); -describe('Bigtable', function() { +describe.only('Bigtable', function() { var PROJECT_ID = 'test-project'; var Bigtable; @@ -77,12 +94,10 @@ describe('Bigtable', function() { paginator: fakePaginator, util: fakeUtil, }, - '@google-cloud/common-grpc': { - Service: FakeGrpcService, - Operation: FakeGrpcOperation, - }, + 'google-auto-auth': fakeGoogleAutoAuth, './cluster.js': FakeCluster, './instance.js': FakeInstance, + './v2': fakeV2, }); }); @@ -92,11 +107,31 @@ describe('Bigtable', function() { beforeEach(function() { extend(fakeUtil, originalFakeUtil); + + googleAutoAuthOverride = null; + replaceProjectIdTokenOverride = null; + delete process.env.BIGTABLE_EMULATOR_HOST; + bigtable = new Bigtable({projectId: PROJECT_ID}); }); describe('instantiation', function() { + let EXPECTED_SCOPES = []; + let clientClasses = [ + v2.BigtableClient, + v2.BigtableInstanceAdminClient, + v2.BigtableTableAdminClient, + ]; + + for (let clientClass of clientClasses) { + for (let scope of clientClass.scopes) { + if (EXPECTED_SCOPES.indexOf(scope) === -1) { + EXPECTED_SCOPES.push(scope); + } + } + } + it('should extend the correct methods', function() { var args = fakePaginator.calledWith_; @@ -132,91 +167,64 @@ describe('Bigtable', function() { assert.strictEqual(normalizeArgumentsCalled, true); }); - it('should inherit from GrpcService', function() { - assert(bigtable instanceof FakeGrpcService); - - var calledWith = bigtable.calledWith_[0]; + it.skip('should work with the emulator', function() {}); - assert.strictEqual(calledWith.baseUrl, 'bigtable.googleapis.com'); - assert.strictEqual(calledWith.customEndpoint, false); - - var protosDir = path.resolve(__dirname, '../protos'); - assert.strictEqual(calledWith.protosDir, protosDir); - - assert.deepEqual(calledWith.protoServices, { - Bigtable: { - baseUrl: 'bigtable.googleapis.com', - path: 'google/bigtable/v2/bigtable.proto', - service: 'bigtable.v2', - }, - BigtableTableAdmin: { - baseUrl: 'bigtableadmin.googleapis.com', - path: 'google/bigtable/admin/v2/bigtable_table_admin.proto', - service: 'bigtable.admin.v2', - }, - BigtableInstanceAdmin: { - baseUrl: 'bigtableadmin.googleapis.com', - path: 'google/bigtable/admin/v2/bigtable_instance_admin.proto', - service: 'bigtable.admin.v2', - }, - Operations: { - baseUrl: 'bigtableadmin.googleapis.com', - path: 'google/longrunning/operations.proto', - service: 'longrunning', - }, - }); - - assert.deepEqual(calledWith.scopes, [ - 'https://www.googleapis.com/auth/bigtable.admin', - 'https://www.googleapis.com/auth/bigtable.data', - 'https://www.googleapis.com/auth/cloud-platform', - ]); - - assert.deepEqual(calledWith.packageJson, require('../package.json')); + it('should initialize the API object', function() { + assert.deepEqual(bigtable.api, {}); }); - it('should work with the emulator', function() { - var endpoint = 'http://emulator:8288'; - process.env.BIGTABLE_EMULATOR_HOST = endpoint; - - var bigtable = new Bigtable({projectId: PROJECT_ID}); - - var calledWith = bigtable.calledWith_[0]; - assert.strictEqual(calledWith.baseUrl, endpoint); - assert.strictEqual(calledWith.customEndpoint, true); + it('should cache a local google-auto-auth instance', function() { + var fakeGoogleAutoAuthInstance = {}; + var options = { + a: 'b', + c: 'd', + }; - Object.keys(calledWith.protoServices).forEach(function(service) { - service = calledWith.protoServices[service]; + googleAutoAuthOverride = function(options_) { + assert.deepEqual( + options_, + extend( + { + libName: 'gccl', + libVersion: PKG.version, + scopes: EXPECTED_SCOPES, + }, + options + ) + ); + return fakeGoogleAutoAuthInstance; + }; - if (is.object(service)) { - assert.strictEqual(service.baseUrl, endpoint); - } - }); + var bigtable = new Bigtable(options); + assert.strictEqual(bigtable.auth, fakeGoogleAutoAuthInstance); + }); - delete process.env.BIGTABLE_EMULATOR_HOST; + it('should localize the projectId', function() { + assert.strictEqual(bigtable.projectId, PROJECT_ID); }); - it('should work with a custom apiEndpoint', function() { + it('should localize options', function() { var options = { - projectId: PROJECT_ID, - apiEndpoint: 'http://local:3888', + a: 'b', + c: 'd', }; var bigtable = new Bigtable(options); - var calledWith = bigtable.calledWith_[0]; - assert.strictEqual(calledWith.baseUrl, options.apiEndpoint); - assert.strictEqual(calledWith.customEndpoint, true); - - Object.keys(calledWith.protoServices).forEach(function(service) { - service = calledWith.protoServices[service]; - - if (is.object(service)) { - assert.strictEqual(service.baseUrl, options.apiEndpoint); - } + assert.deepEqual(bigtable.options, { + a: 'b', + c: 'd', + libName: 'gccl', + libVersion: PKG.version, + scopes: EXPECTED_SCOPES, }); }); + it('should default projectId to token', function() { + var bigtable = new Bigtable(); + assert.strictEqual(bigtable.projectId, '{{projectId}}'); + }); + it('should set the projectName', function() { assert.strictEqual(bigtable.projectName, 'projects/' + PROJECT_ID); }); @@ -226,28 +234,43 @@ describe('Bigtable', function() { var INSTANCE_NAME = 'my-instance'; it('should provide the proper request options', function(done) { - bigtable.request = function(protoOpts, reqOpts) { - assert.deepEqual(protoOpts, { - service: 'BigtableInstanceAdmin', - method: 'createInstance', - }); + bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'createInstance'); + + assert.strictEqual(config.reqOpts.parent, bigtable.projectName); + assert.strictEqual(config.reqOpts.instanceId, INSTANCE_NAME); + assert.strictEqual(config.reqOpts.instance.displayName, INSTANCE_NAME); + + assert.strictEqual(config.gaxOpts, undefined); - assert.strictEqual(reqOpts.parent, bigtable.projectName); - assert.strictEqual(reqOpts.instanceId, INSTANCE_NAME); - assert.strictEqual(reqOpts.instance.displayName, INSTANCE_NAME); done(); }; bigtable.createInstance(INSTANCE_NAME, assert.ifError); }); + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); + }; + + bigtable.createInstance(INSTANCE_NAME, {gaxOptions}, assert.ifError); + }); + it('should respect the displayName option', function(done) { var options = { displayName: 'robocop', }; - bigtable.request = function(protoOpts, reqOpts) { - assert.strictEqual(reqOpts.instance.displayName, options.displayName); + bigtable.request = function(config) { + assert.strictEqual( + config.reqOpts.instance.displayName, + options.displayName + ); done(); }; @@ -279,8 +302,8 @@ describe('Bigtable', function() { return fakeStorage; }; - bigtable.request = function(protoOpts, reqOpts) { - assert.deepEqual(reqOpts.clusters, { + bigtable.request = function(config) { + assert.deepEqual(config.reqOpts.clusters, { 'my-cluster': { location: fakeLocation, serveNodes: cluster.nodes, @@ -296,84 +319,88 @@ describe('Bigtable', function() { it('should return an error to the callback', function(done) { var error = new Error('err'); - var response = {}; - bigtable.request = function(protoOpts, reqOpts, callback) { - callback(error, response); + bigtable.request = function(config, callback) { + callback(error); }; - var callback = function(err, instance, operation, apiResponse) { + bigtable.createInstance(INSTANCE_NAME, function( + err, + instance, + operation + ) { assert.strictEqual(err, error); - assert.strictEqual(instance, null); - assert.strictEqual(operation, null); - assert.strictEqual(apiResponse, response); + assert.strictEqual(instance, undefined); + assert.strictEqual(operation, undefined); done(); - }; - - bigtable.createInstance(INSTANCE_NAME, callback); + }); }); - it('should pass an operation and instance to the callback', function(done) { + it('should return an instance to the callback', function(done) { var response = { name: 'my-operation', }; + var responseArg2 = {}; + var responseArg3 = {}; + var fakeInstance = {}; bigtable.instance = function(name) { assert.strictEqual(name, INSTANCE_NAME); return fakeInstance; }; - var fakeOperation = {}; - bigtable.operation = function(name) { - assert.strictEqual(name, response.name); - return fakeOperation; - }; - - bigtable.request = function(protoOpts, reqOpts, callback) { - callback(null, response); + bigtable.request = function(config, callback) { + callback(null, response, responseArg2, responseArg3); }; - var callback = function(err, instance, operation, apiResponse) { + bigtable.createInstance(INSTANCE_NAME, function(err, instance) { assert.ifError(err); assert.strictEqual(instance, fakeInstance); - assert.strictEqual(operation, fakeOperation); - assert.strictEqual(operation.metadata, response); - assert.strictEqual(apiResponse, response); + assert.strictEqual(arguments[2], response); + assert.strictEqual(arguments[3], responseArg2); + assert.strictEqual(arguments[4], responseArg3); done(); - }; - - bigtable.createInstance(INSTANCE_NAME, callback); + }); }); }); describe('getInstances', function() { it('should provide the proper request options', function(done) { - bigtable.request = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'BigtableInstanceAdmin', - method: 'listInstances', - }); - - assert.strictEqual(reqOpts.parent, bigtable.projectName); + bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'listInstances'); + assert.strictEqual(config.reqOpts.parent, bigtable.projectName); + assert.strictEqual(config.gaxOpts, undefined); done(); }; bigtable.getInstances(assert.ifError); }); + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); + }; + + bigtable.getInstances({gaxOptions}, assert.ifError); + }); + it('should copy all query options', function(done) { var fakeOptions = { a: 'a', b: 'b', }; - bigtable.request = function(grpcOpts, reqOpts) { + bigtable.request = function(config) { Object.keys(fakeOptions).forEach(function(key) { - assert.strictEqual(reqOpts[key], fakeOptions[key]); + assert.strictEqual(config.reqOpts[key], fakeOptions[key]); }); - assert.notStrictEqual(reqOpts, fakeOptions); + assert.notStrictEqual(config.reqOpts, fakeOptions); done(); }; @@ -384,7 +411,7 @@ describe('Bigtable', function() { var error = new Error('err'); var response = {}; - bigtable.request = function(grpcOpts, reqOpts, callback) { + bigtable.request = function(config, callback) { callback(error, response); }; @@ -409,10 +436,13 @@ describe('Bigtable', function() { ], }; + var responseArg2 = {}; + var responseArg3 = {}; + var fakeInstances = [{}, {}]; - bigtable.request = function(grpcOpts, reqOpts, callback) { - callback(null, response); + bigtable.request = function(config, callback) { + callback(null, response, responseArg2, responseArg3); }; var instanceCount = 0; @@ -444,11 +474,11 @@ describe('Bigtable', function() { a: 'b', }; - bigtable.request = function(grpcOpts, reqOpts, callback) { + bigtable.request = function(config, callback) { callback(null, response); }; - var callback = function(err, instances, nextQuery) { + bigtable.getInstances(options, function(err, instances, nextQuery) { var expectedQuery = extend({}, options, { pageToken: response.nextPageToken, }); @@ -456,9 +486,7 @@ describe('Bigtable', function() { assert.ifError(err); assert.deepEqual(nextQuery, expectedQuery); done(); - }; - - bigtable.getInstances(options, callback); + }); }); }); @@ -475,16 +503,216 @@ describe('Bigtable', function() { }); }); - describe('operation', function() { - var OPERATION_NAME = 'my-operation'; + describe('request', function() { + var CONFIG = { + client: 'client', + method: 'method', + reqOpts: { + a: 'b', + c: 'd', + }, + gaxOpts: {}, + }; - it('should return a GrpcOperation object', function() { - var operation = bigtable.operation(OPERATION_NAME); - var args = operation.calledWith_; + var PROJECT_ID = 'project-id'; - assert(operation instanceof FakeGrpcOperation); - assert.strictEqual(args[0], bigtable); - assert.strictEqual(args[1], OPERATION_NAME); + beforeEach(function() { + bigtable.auth = { + getProjectId: function(callback) { + callback(null, PROJECT_ID); + }, + }; + + bigtable.api[CONFIG.client] = { + [CONFIG.method]: common.util.noop, + }; + }); + + describe('prepareGaxRequest', function() { + it('should get the project ID', function(done) { + bigtable.auth.getProjectId = function() { + done(); + }; + + bigtable.request(CONFIG, assert.ifError); + }); + + it('should return error if getting project ID failed', function(done) { + var error = new Error('Error.'); + + bigtable.auth.getProjectId = function(callback) { + callback(error); + }; + + bigtable.request(CONFIG, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should initiate and cache the client', function() { + var fakeClient = { + [CONFIG.method]: common.util.noop, + }; + + fakeV2[CONFIG.client] = function(options) { + assert.strictEqual(options, bigtable.options); + return fakeClient; + }; + + bigtable.api = {}; + + bigtable.request(CONFIG, assert.ifError); + + assert.strictEqual(bigtable.api[CONFIG.client], fakeClient); + }); + + it('should use the cached client', function(done) { + fakeV2[CONFIG.client] = function() { + done(new Error('Should not re-instantiate a GAX client.')); + }; + + bigtable.request(CONFIG); + done(); + }); + + it('should replace the project ID token', function(done) { + var replacedReqOpts = {}; + + replaceProjectIdTokenOverride = function(reqOpts, projectId) { + assert.notStrictEqual(reqOpts, CONFIG.reqOpts); + assert.deepEqual(reqOpts, CONFIG.reqOpts); + assert.strictEqual(projectId, PROJECT_ID); + + return replacedReqOpts; + }; + + bigtable.api[CONFIG.client][CONFIG.method] = { + bind: function(gaxClient, reqOpts) { + assert.strictEqual(reqOpts, replacedReqOpts); + + setImmediate(done); + + return common.util.noop; + }, + }; + + bigtable.request(CONFIG, assert.ifError); + }); + }); + + describe('makeRequestCallback', function() { + it('should prepare the request', function(done) { + bigtable.api[CONFIG.client][CONFIG.method] = { + bind: function(gaxClient, reqOpts, gaxOpts) { + assert.strictEqual(gaxClient, bigtable.api[CONFIG.client]); + assert.deepEqual(reqOpts, CONFIG.reqOpts); + assert.strictEqual(gaxOpts, CONFIG.gaxOpts); + + setImmediate(done); + + return common.util.noop; + }, + }; + + bigtable.request(CONFIG, assert.ifError); + }); + + it('should execute callback with error', function(done) { + var error = new Error('Error.'); + + bigtable.api[CONFIG.client][CONFIG.method] = function() { + var callback = [].slice.call(arguments).pop(); + callback(error); + }; + + bigtable.request(CONFIG, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should execute the request function', function() { + bigtable.api[CONFIG.client][CONFIG.method] = function(done) { + var callback = [].slice.call(arguments).pop(); + callback(null, done); // so it ends the test + }; + + bigtable.request(CONFIG, assert.ifError); + }); + }); + + describe('makeRequestStream', function() { + var GAX_STREAM; + + beforeEach(function() { + GAX_STREAM = through(); + + bigtable.api[CONFIG.client][CONFIG.method] = { + bind: function() { + return function() { + return GAX_STREAM; + }; + }, + }; + }); + + it('should expose an abort function', function(done) { + GAX_STREAM.cancel = done; + + var requestStream = bigtable.request(CONFIG); + requestStream.emit('reading'); + requestStream.abort(); + }); + + it('should prepare the request once reading', function(done) { + bigtable.api[CONFIG.client][CONFIG.method] = { + bind: function(gaxClient, reqOpts, gaxOpts) { + assert.strictEqual(gaxClient, bigtable.api[CONFIG.client]); + assert.deepEqual(reqOpts, CONFIG.reqOpts); + assert.strictEqual(gaxOpts, CONFIG.gaxOpts); + + setImmediate(done); + + return function() { + return GAX_STREAM; + }; + }, + }; + + var requestStream = bigtable.request(CONFIG); + requestStream.emit('reading'); + }); + + it('should destroy the stream with prepare error', function(done) { + var error = new Error('Error.'); + + bigtable.auth.getProjectId = function(callback) { + callback(error); + }; + + var requestStream = bigtable.request(CONFIG); + requestStream.emit('reading'); + + requestStream.on('error', function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should destroy the stream with GAX error', function(done) { + var error = new Error('Error.'); + + var requestStream = bigtable.request(CONFIG); + requestStream.emit('reading'); + + requestStream.on('error', function(err) { + assert.strictEqual(err, error); + done(); + }); + + GAX_STREAM.emit('error', error); + }); }); }); }); From 606b2986bc1725f7b1f08230f70cc05de4d41d8d Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 21 Feb 2018 15:37:25 -0500 Subject: [PATCH 07/46] Document gaxOptions in cluster --- src/cluster.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cluster.js b/src/cluster.js index f4890edff..59afbc78e 100644 --- a/src/cluster.js +++ b/src/cluster.js @@ -92,8 +92,8 @@ Cluster.prototype.create = function(options, callback) { /** * Delete the cluster. * - * @param {object} [gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} [callback] The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -219,6 +219,8 @@ Cluster.prototype.get = function(gaxOptions, callback) { /** * Get the cluster metadata. * + * @param {object} [gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -316,8 +318,8 @@ Cluster.getStorageType_ = function(type) { /** * Set the cluster metadata. * - * @param {object} [options] See {@link Instance#createCluster} for the - * available options. + * @param {object} metadata See {@link Instance#createCluster} for the + * available metadata options. * @param {object} [gaxOptions] Request configuration options, outlined here: * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. From 55f00bcfde157652ca8cecd85ff8847590a66306 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 21 Feb 2018 15:46:07 -0500 Subject: [PATCH 08/46] Document gaxOptions for family & expose in table.createFamily --- src/family.js | 10 ++++++---- src/instance.js | 4 ++-- src/table.js | 40 +++++++++++++++++++--------------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/family.js b/src/family.js index 0530697c3..f38791251 100644 --- a/src/family.js +++ b/src/family.js @@ -170,6 +170,8 @@ Family.prototype.create = function(options, callback) { /** * Delete the column family. * + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} [callback] The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -213,8 +215,8 @@ Family.prototype.delete = function(gaxOptions, callback) { /** * Check if the column family exists. * - * @param {object} [gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -259,8 +261,8 @@ Family.prototype.exists = function(gaxOptions, callback) { * normally required for the `create` method must be contained within this * object as well. * - * @param {object} [gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the * instance if it does not already exist. * diff --git a/src/instance.js b/src/instance.js index ec3321206..236678dec 100644 --- a/src/instance.js +++ b/src/instance.js @@ -362,8 +362,8 @@ Instance.prototype.cluster = function(name) { /** * Delete the instance. * - * @param {object} [gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} [callback] The callback function. * @param {?error} callback.err An error returned while making this * request. diff --git a/src/table.js b/src/table.js index c8e320b9d..36ccc948d 100644 --- a/src/table.js +++ b/src/table.js @@ -182,14 +182,18 @@ Table.prototype.create = function(options, callback) { * @throws {error} If a name is not provided. * * @param {string} name The name of column family. - * @param {object} [rule] Garbage collection rule or request configuration - * options, outlined here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. - * @param {object} [rule.age] Delete cells in a column older than the given - * age. Values must be at least 1 millisecond. - * @param {number} [rule.versions] Maximum number of versions to delete cells - * in a column, except for the most recent. - * @param {boolean} [rule.intersect] Cells to delete should match all rules. - * @param {boolean} [rule.union] Cells to delete should match any of the rules. + * @param {object} [options] Configuration object. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {object} [options.rule] Garbage collection rule + * @param {object} [options.rule.age] Delete cells in a column older than the + * given age. Values must be at least 1 millisecond. + * @param {number} [options.rule.versions] Maximum number of versions to delete + * cells in a column, except for the most recent. + * @param {boolean} [options.rule.intersect] Cells to delete should match all + * rules. + * @param {boolean} [options.rule.union] Cells to delete should match any of the + * rules. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this request. * @param {Family} callback.family The newly created Family. @@ -224,18 +228,12 @@ Table.prototype.create = function(options, callback) { * const apiResponse = data[1]; * }); */ -Table.prototype.createFamily = function(name, rule, callback) { +Table.prototype.createFamily = function(name, options, callback) { var self = this; - var gaxOpts = {}; - - if (is.object(rule)) { - gaxOpts = rule; - rule = null; - } - if (is.function(rule)) { - callback = rule; - rule = null; + if (is.function(options)) { + callback = options; + options = {}; } if (!name) { @@ -247,8 +245,8 @@ Table.prototype.createFamily = function(name, rule, callback) { create: {}, }; - if (rule) { - mod.create.gcRule = Family.formatRule_(rule); + if (options.rule) { + mod.create.gcRule = Family.formatRule_(options.rule); } var reqOpts = { @@ -261,7 +259,7 @@ Table.prototype.createFamily = function(name, rule, callback) { client: 'BigtableTableAdminClient', method: 'modifyColumnFamilies', reqOpts: reqOpts, - gaxOpts: gaxOpts, + gaxOpts: options.gaxOptions, }, function(err, resp) { if (err) { From e792eda95102ad62e859504bd8ea11d481a060c1 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 21 Feb 2018 15:51:40 -0500 Subject: [PATCH 09/46] Expose and document gaxOptions from instance.js --- src/instance.js | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/instance.js b/src/instance.js index 236678dec..514fb6b61 100644 --- a/src/instance.js +++ b/src/instance.js @@ -210,6 +210,8 @@ Instance.prototype.createCluster = function(name, options, callback) { * @param {object} [options] Table creation options. * @param {object|string[]} [options.families] Column families to be created * within the table. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {string[]} [options.splits] Initial * [split keys](https://cloud.google.com/bigtable/docs/managing-tables#splits). * @param {function} callback The callback function. @@ -450,8 +452,8 @@ Instance.prototype.exists = function(gaxOptions, callback) { /** * Get an instance if it exists. * - * @param {object} [gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the * instance if it does not already exist. * @@ -504,6 +506,8 @@ Instance.prototype.get = function(gaxOptions, callback) { * @param {object} [query] Query object. * @param {boolean} [query.autoPaginate=true] Have pagination handled * automatically. + * @param {object} [query.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {number} [query.maxApiCalls] Maximum number of API calls to make. * @param {number} [query.maxResults] Maximum number of results to return. * @param {string} [query.pageToken] Token returned from a previous call, to @@ -567,7 +571,7 @@ Instance.prototype.getClusters = function(query, callback) { reqOpts: reqOpts, gaxOpts: query.gaxOptions, }, - function(err, resp) { + function() { if (arguments[1]) { arguments[1] = arguments[1].clusters.map(function(clusterObj) { var cluster = self.cluster(clusterObj.name); @@ -582,8 +586,8 @@ Instance.prototype.getClusters = function(query, callback) { }; /** - * Get {@link Cluster} objects for all of your clusters as a readable - * object stream. + * Get {@link Cluster} objects for all of your clusters as a readable object + * stream. * * @param {object} [query] Configuration object. See * {@link Instance#getClusters} for a complete list of options. @@ -615,8 +619,8 @@ Instance.prototype.getClustersStream = common.paginator.streamify( /** * Get the instance metadata. * - * @param {object} [gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -725,14 +729,11 @@ Instance.prototype.getTables = function(options, callback) { options = {}; } - var originalOptions = extend({}, options); - var reqOpts = extend({}, options, { parent: this.id, view: Table.VIEWS[options.view || 'unspecified'], }); - var gaxOpts = reqOpts.gaxOptions; delete reqOpts.gaxOptions; this.bigtable.request( @@ -740,7 +741,7 @@ Instance.prototype.getTables = function(options, callback) { client: 'BigtableTableAdminClient', method: 'listTables', reqOpts: reqOpts, - gaxOpts: gaxOpts, + gaxOpts: options.gaxOptions, }, function() { if (arguments[1]) { @@ -798,8 +799,8 @@ Instance.prototype.getTablesStream = common.paginator.streamify('getTables'); * @param {string} metadata.displayName The descriptive name for this * instance as it appears in UIs. It can be changed at any time, but * should be kept globally unique to avoid confusion. - * @param {object} metadata.gaxOptions Request configuration options, - * outlined here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/global.html#CallOptions. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -823,18 +824,20 @@ Instance.prototype.getTablesStream = common.paginator.streamify('getTables'); * var apiResponse = data[0]; * }); */ -Instance.prototype.setMetadata = function(metadata, callback) { +Instance.prototype.setMetadata = function(metadata, gaxOptions, callback) { var self = this; - var reqOpts = extend({name: this.id}, metadata); - delete reqOpts.gaxOptions; + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } this.bigtable.request( { client: 'BigtableInstanceAdminClient', method: 'updateInstance', - reqOpts: reqOpts, - gaxOpts: metadata.gaxOptions, + reqOpts: extend({name: this.id}, metadata), + gaxOpts: gaxOptions, }, function() { if (arguments[1]) { From 77ce2366a01183192865acd5d6d5ace2cb51ccc5 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 22 Feb 2018 18:00:33 -0500 Subject: [PATCH 10/46] more tests --- src/cluster.js | 147 ++++++++-------- src/row.js | 27 +-- test/cluster.js | 450 +++++++++++++++++++++++++++++++++--------------- test/index.js | 2 +- 4 files changed, 400 insertions(+), 226 deletions(-) diff --git a/src/cluster.js b/src/cluster.js index 59afbc78e..c9f3e217e 100644 --- a/src/cluster.js +++ b/src/cluster.js @@ -41,13 +41,63 @@ function Cluster(instance, name) { var id = name; if (id.indexOf('/') === -1) { - id = instance.id + '/clusters/' + name; + id = `${instance.id}/clusters/${name}`; } this.id = id; this.name = id.split('/').pop(); } +/** + * Formats zone location. + * + * @private + * + * @param {string} project The project. + * @param {string} location The zone location. + * @returns {string} + * + * @example + * Cluster.getLocation_('my-project', 'us-central1-b'); + * // 'projects/my-project/locations/us-central1-b' + */ +Cluster.getLocation_ = function(project, location) { + if (location.indexOf('/') > -1) { + return location; + } + + return format('projects/{project}/locations/{location}', { + project: project, + location: location, + }); +}; + +/** + * Maps the storage type to the proper integer. + * + * @private + * + * @param {string} type The storage type (hdd, ssd). + * @returns {number} + * + * @example + * Cluster.getStorageType_('ssd'); + * // 1 + */ +Cluster.getStorageType_ = function(type) { + var storageTypes = { + unspecified: 0, + ssd: 1, + hdd: 2, + }; + + if (is.string(type)) { + type = type.toLowerCase(); + } + + return storageTypes[type] || storageTypes.unspecified; +}; + /** * Create a cluster. * @@ -155,27 +205,28 @@ Cluster.prototype.exists = function(gaxOptions, callback) { } this.getMetadata(gaxOptions, function(err) { - if (!err) { - callback(null, true); - return; - } + if (err) { + if (err.code === 5) { + callback(null, false); + return; + } - if (err.code === 5) { - callback(null, false); + callback(err); return; } - callback(err); + callback(null, true); }); }; /** * Get a cluster if it exists. * - * @param {object} [gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. - * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the + * @param {object} [options] Configuration object. + * @param {boolean} [options.autoCreate=false] Automatically create the * instance if it does not already exist. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * * @example * cluster.get(function(err, cluster, apiResponse) { @@ -190,29 +241,29 @@ Cluster.prototype.exists = function(gaxOptions, callback) { * var apiResponse = data[1]; * }); */ -Cluster.prototype.get = function(gaxOptions, callback) { +Cluster.prototype.get = function(options, callback) { var self = this; - if (is.fn(gaxOptions)) { - callback = gaxOptions; - gaxOptions = {}; + if (is.fn(options)) { + callback = options; + options = {}; } - var autoCreate = !!gaxOptions.autoCreate; - delete gaxOptions.autoCreate; + var autoCreate = !!options.autoCreate; + var gaxOptions = options.gaxOptions; this.getMetadata(gaxOptions, function(err, apiResponse) { - if (!err) { - callback(null, self, apiResponse); - return; - } + if (err) { + if (err.code === 5 && autoCreate) { + self.create({gaxOptions}, callback); + return; + } - if (err.code !== 5 || !autoCreate) { callback(err, null, apiResponse); return; } - self.create({gaxOptions}, callback); + callback(null, self, apiResponse); }); }; @@ -265,56 +316,6 @@ Cluster.prototype.getMetadata = function(gaxOptions, callback) { ); }; -/** - * Formats zone location. - * - * @private - * - * @param {string} project The project. - * @param {string} location The zone location. - * @returns {string} - * - * @example - * Cluster.getLocation_('my-project', 'us-central1-b'); - * // 'projects/my-project/locations/us-central1-b' - */ -Cluster.getLocation_ = function(project, location) { - if (location.indexOf('/') > -1) { - return location; - } - - return format('projects/{project}/locations/{location}', { - project: project, - location: location, - }); -}; - -/** - * Maps the storage type to the proper integer. - * - * @private - * - * @param {string} type The storage type (hdd, ssd). - * @returns {number} - * - * @example - * Cluster.getStorageType_('ssd'); - * // 1 - */ -Cluster.getStorageType_ = function(type) { - var storageTypes = { - unspecified: 0, - ssd: 1, - hdd: 2, - }; - - if (is.string(type)) { - type = type.toLowerCase(); - } - - return storageTypes[type] || storageTypes.unspecified; -}; - /** * Set the cluster metadata. * diff --git a/src/row.js b/src/row.js index 5630d834e..c8e502971 100644 --- a/src/row.js +++ b/src/row.js @@ -213,8 +213,9 @@ Row.formatFamilies_ = function(families, options) { /** * Create a new row in your table. * - * @param {object} [entry] An entry. See {@link Table#insert}. - * @param {object} [entry.gaxOptions] Request configuration options, outlined + * @param {object} [options] Configuration object. + * @param {object} [options.entry] An entry. See {@link Table#insert}. + * @param {object} [options.gaxOptions] Request configuration options, outlined * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this @@ -235,8 +236,10 @@ Row.formatFamilies_ = function(families, options) { * // Optionally, you can supply entry data. * //- * row.create({ - * follows: { - * alincoln: 1 + * entry: { + * follows: { + * alincoln: 1 + * } * } * }, callback); * @@ -247,23 +250,21 @@ Row.formatFamilies_ = function(families, options) { * var apiResponse = data[0]; * }); */ -Row.prototype.create = function(entry, callback) { +Row.prototype.create = function(options, callback) { var self = this; - if (is.function(entry)) { - callback = entry; - entry = {}; + if (is.function(options)) { + callback = options; + options = {}; } - entry = { + var entry = { key: this.id, - data: extend({}, entry), + data: extend({}, options.entry), method: Mutation.methods.INSERT, }; - delete entry.data.gaxOptions; - - this.table.mutate(entry, entry.gaxOptions, function(err, apiResponse) { + this.table.mutate(entry, options.gaxOptions, function(err, apiResponse) { if (err) { callback(err, null, apiResponse); return; diff --git a/test/cluster.js b/test/cluster.js index d98da8070..d7de72dde 100644 --- a/test/cluster.js +++ b/test/cluster.js @@ -23,7 +23,6 @@ var proxyquire = require('proxyquire'); var util = require('util'); var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); var promisified = false; var fakeUtil = extend({}, common.util, { @@ -34,22 +33,13 @@ var fakeUtil = extend({}, common.util, { }, }); -var GrpcServiceObject = commonGrpc.ServiceObject; - -function FakeGrpcServiceObject() { - this.calledWith_ = arguments; - GrpcServiceObject.apply(this, arguments); -} - -util.inherits(FakeGrpcServiceObject, GrpcServiceObject); - describe('Bigtable/Cluster', function() { var CLUSTER_NAME = 'my-cluster'; var PROJECT_ID = 'grape-spaceship-123'; var INSTANCE = { id: 'projects/p/instances/i', - parent: {projectId: PROJECT_ID}, + bigtable: {projectId: PROJECT_ID}, }; var CLUSTER_ID = format('{instance}/clusters/{cluster}', { @@ -65,9 +55,6 @@ describe('Bigtable/Cluster', function() { '@google-cloud/common': { util: fakeUtil, }, - '@google-cloud/common-grpc': { - ServiceObject: FakeGrpcServiceObject, - }, }); }); @@ -76,86 +63,29 @@ describe('Bigtable/Cluster', function() { }); describe('instantiation', function() { - it('should inherit from GrpcServiceObject', function() { - assert(cluster instanceof FakeGrpcServiceObject); - - var config = cluster.calledWith_[0]; - - assert.strictEqual(config.parent, INSTANCE); - assert.strictEqual(config.id, CLUSTER_ID); - - assert.deepEqual(config.methods, { - create: true, - delete: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'deleteCluster', - }, - reqOpts: { - name: CLUSTER_ID, - }, - }, - exists: true, - get: true, - getMetadata: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'getCluster', - }, - reqOpts: { - name: CLUSTER_ID, - }, - }, - }); - }); - it('should promisify all the things', function() { assert(promisified); }); - it('should Instance#createCluster to create the cluster', function(done) { - var config = cluster.calledWith_[0]; - var fakeOptions = {}; + it('should localize Bigtable instance', function() { + assert.strictEqual(cluster.bigtable, INSTANCE.bigtable); + }); - INSTANCE.createCluster = function(name, options, callback) { - assert.strictEqual(name, CLUSTER_NAME); - assert.strictEqual(options, fakeOptions); - callback(); - }; + it('should localize Instance instance', function() { + assert.strictEqual(cluster.instance, INSTANCE); + }); - config.createMethod(null, fakeOptions, done); + it('should expand name into full resource path', function() { + assert.strictEqual(cluster.id, CLUSTER_ID); }); it('should leave full cluster names unaltered', function() { - var fakeName = 'a/b/c/d'; - var cluster = new Cluster(INSTANCE, fakeName); - var config = cluster.calledWith_[0]; - - assert.strictEqual(config.id, fakeName); - - assert.deepEqual(config.methods, { - create: true, - delete: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'deleteCluster', - }, - reqOpts: { - name: fakeName, - }, - }, - exists: true, - get: true, - getMetadata: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'getCluster', - }, - reqOpts: { - name: fakeName, - }, - }, - }); + var cluster = new Cluster(INSTANCE, CLUSTER_ID); + assert.strictEqual(cluster.id, CLUSTER_ID); + }); + + it('should localize the name from the ID', function() { + assert.strictEqual(cluster.name, CLUSTER_NAME); }); }); @@ -204,19 +134,301 @@ describe('Bigtable/Cluster', function() { }); }); - describe('setMetadata', function() { - it('should provide the proper request options', function(done) { - cluster.request = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'BigtableInstanceAdmin', - method: 'updateCluster', + describe('create', function() { + it('should call createCluster from instance', function(done) { + var options = {}; + + cluster.instance.createCluster = function(name, options_, callback) { + assert.strictEqual(name, cluster.name); + assert.strictEqual(options_, options); + callback(); // done() + }; + + cluster.create(options, done); + }); + + it('should not require options', function(done) { + cluster.instance.createCluster = function(name, options, callback) { + assert.deepStrictEqual(options, {}); + callback(); // done() + }; + + cluster.create(done); + }); + }); + + describe('delete', function() { + it('should accept gaxOptions', function(done) { + cluster.bigtable.request = function(config, callback) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'deleteCluster'); + + assert.deepEqual(config.reqOpts, { + name: cluster.id, + }); + + assert.deepEqual(config.gaxOpts, {}); + + callback(); // done() + }; + + cluster.delete(done); + }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + cluster.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); + }; + + cluster.delete(gaxOptions, assert.ifError); + }); + }); + + describe('exists', function() { + it('should not require gaxOptions', function(done) { + cluster.getMetadata = function(gaxOptions) { + assert.deepStrictEqual(gaxOptions, {}); + done(); + }; + + cluster.exists(assert.ifError); + }); + + it('should pass gaxOptions to getMetadata', function(done) { + var gaxOptions = {}; + + cluster.getMetadata = function(gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + cluster.exists(gaxOptions, assert.ifError); + }); + + it('should return false if error code is 5', function(done) { + var error = new Error('Error.'); + error.code = 5; + + cluster.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + cluster.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, false); + done(); + }); + }); + + it('should return error if code is not 5', function(done) { + var error = new Error('Error.'); + error.code = 'NOT-5'; + + cluster.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + cluster.exists(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return true if no error', function(done) { + cluster.getMetadata = function(gaxOptions, callback) { + callback(null, {}); + }; + + cluster.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, true); + done(); + }); + }); + }); + + describe('get', function() { + it('should call getMetadata', function(done) { + var options = { + gaxOptions: {}, + }; + + cluster.getMetadata = function(gaxOptions) { + assert.strictEqual(gaxOptions, options.gaxOptions); + done(); + }; + + cluster.get(options, assert.ifError); + }); + + it('should not require an options object', function(done) { + cluster.getMetadata = function(gaxOptions) { + assert.deepStrictEqual(gaxOptions, undefined); + done(); + }; + + cluster.get(assert.ifError); + }); + + it('should auto create with error code 5', function(done) { + var error = new Error('Error.'); + error.code = 5; + + var options = { + autoCreate: true, + gaxOptions: {}, + }; + + cluster.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + cluster.create = function(options_, callback) { + assert.strictEqual(options_.gaxOptions, options.gaxOptions); + callback(); // done() + }; + + cluster.get(options, done); + }); + + it('should not auto create without error code 5', function(done) { + var error = new Error('Error.'); + error.code = 'NOT-5'; + + var options = { + autoCreate: true, + }; + + cluster.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + cluster.create = function() { + throw new Error('Should not create.'); + }; + + cluster.get(options, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should not auto create unless requested', function(done) { + var error = new Error('Error.'); + error.code = 5; + + cluster.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + cluster.create = function() { + throw new Error('Should not create.'); + }; + + cluster.get(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return an error from getMetadata', function(done) { + var error = new Error('Error.'); + + cluster.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + cluster.get(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return self and API response', function(done) { + var apiResponse = {}; + + cluster.getMetadata = function(gaxOptions, callback) { + callback(null, apiResponse); + }; + + cluster.get(function(err, cluster_, apiResponse_) { + assert.ifError(err); + assert.strictEqual(cluster_, cluster); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + }); + + describe('getMetadata', function() { + it('should make correct request', function(done) { + cluster.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'getCluster'); + + assert.deepEqual(config.reqOpts, { + name: cluster.id, }); - assert.strictEqual(reqOpts.name, CLUSTER_ID); + assert.deepEqual(config.gaxOpts, {}); + done(); }; - cluster.setMetadata({}, assert.ifError); + cluster.getMetadata(assert.ifError); + }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + cluster.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); + }; + + cluster.getMetadata(gaxOptions, assert.ifError); + }); + + it('should update metadata', function(done) { + var metadata = {}; + + cluster.bigtable.request = function(config, callback) { + callback(null, metadata); + }; + + cluster.getMetadata(function() { + assert.strictEqual(cluster.metadata, metadata); + done(); + }); + }); + + it('should execute callback with original arguments', function(done) { + var args = [{}, {}, {}]; + + cluster.bigtable.request = function(config, callback) { + callback.apply(null, args); + }; + + cluster.getMetadata(function() { + assert.deepStrictEqual([].slice.call(arguments), args); + done(); + }); + }); + }); + + describe('setMetadata', function() { + it('should provide the proper request options', function(done) { + cluster.bigtable.request = function(config, callback) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'updateCluster'); + assert.strictEqual(config.reqOpts.name, CLUSTER_ID); + callback(); // done() + }; + + cluster.setMetadata({}, done); }); it('should respect the location option', function(done) { @@ -233,8 +445,8 @@ describe('Bigtable/Cluster', function() { return fakeLocation; }; - cluster.request = function(grpcOpts, reqOpts) { - assert.strictEqual(reqOpts.location, fakeLocation); + cluster.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.location, fakeLocation); Cluster.getLocation_ = getLocation; done(); }; @@ -247,8 +459,8 @@ describe('Bigtable/Cluster', function() { nodes: 3, }; - cluster.request = function(grpcOpts, reqOpts) { - assert.strictEqual(reqOpts.serveNodes, options.nodes); + cluster.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.serveNodes, options.nodes); done(); }; @@ -268,53 +480,13 @@ describe('Bigtable/Cluster', function() { return fakeStorageType; }; - cluster.request = function(grpcOpts, reqOpts) { - assert.strictEqual(reqOpts.defaultStorageType, fakeStorageType); + cluster.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.defaultStorageType, fakeStorageType); Cluster.getStorageType_ = getStorageType; done(); }; cluster.setMetadata(options, assert.ifError); }); - - it('should return an error to the callback', function(done) { - var error = new Error('err'); - var response = {}; - - cluster.request = function(grpcOpts, reqOpts, callback) { - callback(error, response); - }; - - cluster.setMetadata({}, function(err, operation, apiResponse) { - assert.strictEqual(err, error); - assert.strictEqual(operation, null); - assert.strictEqual(apiResponse, response); - done(); - }); - }); - - it('should return an operation to the callback', function(done) { - var response = { - name: 'my-operation', - }; - var fakeOperation = {}; - - cluster.request = function(grpcOpts, reqOpts, callback) { - callback(null, response); - }; - - INSTANCE.parent.operation = function(name) { - assert.strictEqual(name, response.name); - return fakeOperation; - }; - - cluster.setMetadata({}, function(err, operation, apiResponse) { - assert.ifError(err); - assert.strictEqual(operation, fakeOperation); - assert.strictEqual(operation.metadata, response); - assert.strictEqual(apiResponse, response); - done(); - }); - }); }); }); diff --git a/test/index.js b/test/index.js index 4b23900a9..7216ae6f1 100644 --- a/test/index.js +++ b/test/index.js @@ -82,7 +82,7 @@ function createFake(Class) { var FakeCluster = createFake(Cluster); var FakeInstance = createFake(Instance); -describe.only('Bigtable', function() { +describe('Bigtable', function() { var PROJECT_ID = 'test-project'; var Bigtable; From e5c3b87d32ca16b8058c40613995a5ba96da8aee Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 22 Feb 2018 18:43:47 -0500 Subject: [PATCH 11/46] lint --- src/cluster.js | 1 - src/family.js | 1 - src/index.js | 21 +++++++++------------ src/instance.js | 1 - src/row.js | 1 - src/table.js | 1 - test/cluster.js | 1 - test/index.js | 3 --- 8 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/cluster.js b/src/cluster.js index c9f3e217e..cb3266910 100644 --- a/src/cluster.js +++ b/src/cluster.js @@ -19,7 +19,6 @@ var common = require('@google-cloud/common'); var format = require('string-format-obj'); var is = require('is'); -var util = require('util'); /** * Create a cluster object to interact with your cluster. diff --git a/src/family.js b/src/family.js index f38791251..96879f2d5 100644 --- a/src/family.js +++ b/src/family.js @@ -19,7 +19,6 @@ var common = require('@google-cloud/common'); var createErrorClass = require('create-error-class'); var is = require('is'); -var util = require('util'); /** * @private diff --git a/src/index.js b/src/index.js index e7b3ce3cc..7fa367510 100644 --- a/src/index.js +++ b/src/index.js @@ -18,14 +18,11 @@ var arrify = require('arrify'); var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); var extend = require('extend'); var googleAuth = require('google-auto-auth'); -var path = require('path'); var is = require('is'); var streamEvents = require('stream-events'); var through = require('through2'); -var util = require('util'); var Cluster = require('./cluster.js'); var Instance = require('./instance.js'); @@ -339,17 +336,17 @@ function Bigtable(options) { options = common.util.normalizeArguments(this, options); - var baseUrl = 'bigtable.googleapis.com'; - var adminBaseUrl = 'bigtableadmin.googleapis.com'; - // @TODO figure out how to configure GAPIC for this - var customEndpoint = - options.apiEndpoint || process.env.BIGTABLE_EMULATOR_HOST; + // var baseUrl = 'bigtable.googleapis.com'; + // var adminBaseUrl = 'bigtableadmin.googleapis.com'; - if (customEndpoint) { - baseUrl = customEndpoint; - adminBaseUrl = baseUrl; - } + // var customEndpoint = + // options.apiEndpoint || process.env.BIGTABLE_EMULATOR_HOST; + + // if (customEndpoint) { + // baseUrl = customEndpoint; + // adminBaseUrl = baseUrl; + // } // Determine what scopes are needed. // It is the union of the scopes on all three clients. diff --git a/src/instance.js b/src/instance.js index 514fb6b61..277d60724 100644 --- a/src/instance.js +++ b/src/instance.js @@ -19,7 +19,6 @@ var common = require('@google-cloud/common'); var extend = require('extend'); var is = require('is'); -var util = require('util'); var Cluster = require('./cluster.js'); var Family = require('./family.js'); diff --git a/src/row.js b/src/row.js index c8e502971..c721ab594 100644 --- a/src/row.js +++ b/src/row.js @@ -23,7 +23,6 @@ var dotProp = require('dot-prop'); var extend = require('extend'); var flatten = require('lodash.flatten'); var is = require('is'); -var util = require('util'); var Filter = require('./filter.js'); var Mutation = require('./mutation.js'); diff --git a/src/table.js b/src/table.js index 36ccc948d..29afa5df1 100644 --- a/src/table.js +++ b/src/table.js @@ -25,7 +25,6 @@ var is = require('is'); var propAssign = require('prop-assign'); var pumpify = require('pumpify'); var through = require('through2'); -var util = require('util'); var Family = require('./family.js'); var Filter = require('./filter.js'); diff --git a/test/cluster.js b/test/cluster.js index d7de72dde..61b315f6e 100644 --- a/test/cluster.js +++ b/test/cluster.js @@ -20,7 +20,6 @@ var assert = require('assert'); var extend = require('extend'); var format = require('string-format-obj'); var proxyquire = require('proxyquire'); -var util = require('util'); var common = require('@google-cloud/common'); diff --git a/test/index.js b/test/index.js index 7216ae6f1..0912385e6 100644 --- a/test/index.js +++ b/test/index.js @@ -18,15 +18,12 @@ var assert = require('assert'); var extend = require('extend'); -var is = require('is'); var nodeutil = require('util'); -var path = require('path'); var proxyquire = require('proxyquire'); var sinon = require('sinon').sandbox.create(); var through = require('through2'); var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); var Cluster = require('../src/cluster.js'); var Instance = require('../src/instance.js'); var v2 = require('../src/v2'); From 3f731a1013cd15c0907129e21268d1d4f18a5e85 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 22 Feb 2018 19:16:36 -0500 Subject: [PATCH 12/46] Test family --- src/cluster.js | 5 + src/family.js | 56 ++++---- test/cluster.js | 2 +- test/family.js | 348 +++++++++++++++++++++++++++++++++++++----------- 4 files changed, 309 insertions(+), 102 deletions(-) diff --git a/src/cluster.js b/src/cluster.js index cb3266910..821155490 100644 --- a/src/cluster.js +++ b/src/cluster.js @@ -221,6 +221,11 @@ Cluster.prototype.exists = function(gaxOptions, callback) { /** * Get a cluster if it exists. * + * You may optionally use this to "get or create" an object by providing an + * object with `autoCreate` set to `true`. Any extra configuration that is + * normally required for the `create` method must be contained within this + * object as well. + * * @param {object} [options] Configuration object. * @param {boolean} [options.autoCreate=false] Automatically create the * instance if it does not already exist. diff --git a/src/family.js b/src/family.js index 96879f2d5..a3f34eb92 100644 --- a/src/family.js +++ b/src/family.js @@ -47,6 +47,7 @@ function Family(table, name) { this.table = table; this.id = Family.formatName_(table.id, name); + /** * @name Family#familyName * @type {string} @@ -238,17 +239,17 @@ Family.prototype.exists = function(gaxOptions, callback) { } this.getMetadata(gaxOptions, function(err) { - if (!err) { - callback(null, true); - return; - } + if (err) { + if (err.name === 'FamilyError') { + callback(null, false); + return; + } - if (err.name === 'FamilyError') { - callback(null, false); + callback(err); return; } - callback(err); + callback(null, true); }); }; @@ -260,10 +261,11 @@ Family.prototype.exists = function(gaxOptions, callback) { * normally required for the `create` method must be contained within this * object as well. * - * @param {object} [gaxOptions] Request configuration options, outlined here: - * https://googleapis.github.io/gax-nodejs/CallSettings.html. - * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the + * @param {object} [options] Configuration object. + * @param {boolean} [options.autoCreate=false] Automatically create the * instance if it does not already exist. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * * @example * family.get(function(err, family, apiResponse) { @@ -278,29 +280,29 @@ Family.prototype.exists = function(gaxOptions, callback) { * const apiResponse = data[1]; * }); */ -Family.prototype.get = function(gaxOptions, callback) { +Family.prototype.get = function(options, callback) { var self = this; - if (is.fn(gaxOptions)) { - callback = gaxOptions; - gaxOptions = {}; + if (is.fn(options)) { + callback = options; + options = {}; } - var autoCreate = !!gaxOptions.autoCreate; - delete gaxOptions.autoCreate; + var autoCreate = !!options.autoCreate; + var gaxOptions = options.gaxOptions; this.getMetadata(gaxOptions, function(err, apiResponse) { - if (!err) { - callback(null, self, apiResponse); - return; - } + if (err) { + if (err.code === 5 && autoCreate) { + self.create({gaxOptions}, callback); + return; + } - if (err.code !== 5 || !autoCreate) { callback(err, null, apiResponse); return; } - self.create({gaxOptions}, callback); + callback(null, self, apiResponse); }); }; @@ -417,12 +419,14 @@ Family.prototype.setMetadata = function(metadata, gaxOptions, callback) { gaxOpts: gaxOptions, }, function() { - if (arguments[1]) { - self.metadata = arguments[1].columnFamilies[self.familyName]; - arguments[1] = self.metadata; + var args = [].slice.call(arguments); + + if (args[1]) { + self.metadata = args[1].columnFamilies[self.familyName]; + args.splice(1, 0, self.metadata); } - callback.apply(null, arguments); + callback.apply(null, args); } ); }; diff --git a/test/cluster.js b/test/cluster.js index 61b315f6e..05c3e2ad8 100644 --- a/test/cluster.js +++ b/test/cluster.js @@ -157,7 +157,7 @@ describe('Bigtable/Cluster', function() { }); describe('delete', function() { - it('should accept gaxOptions', function(done) { + it('should make the correct request', function(done) { cluster.bigtable.request = function(config, callback) { assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); assert.strictEqual(config.method, 'deleteCluster'); diff --git a/test/family.js b/test/family.js index 395aedb6f..51bbaac03 100644 --- a/test/family.js +++ b/test/family.js @@ -19,15 +19,11 @@ var assert = require('assert'); var extend = require('extend'); var format = require('string-format-obj'); -var nodeutil = require('util'); var proxyquire = require('proxyquire'); var util = require('@google-cloud/common').util; -var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); - var promisified = false; -var fakeUtil = extend({}, common.util, { +var fakeUtil = extend({}, util, { promisifyAll: function(Class) { if (Class.name === 'Family') { promisified = true; @@ -35,18 +31,10 @@ var fakeUtil = extend({}, common.util, { }, }); -var GrpcServiceObject = commonGrpc.ServiceObject; - -function FakeGrpcServiceObject() { - this.calledWith_ = arguments; - GrpcServiceObject.apply(this, arguments); -} - -nodeutil.inherits(FakeGrpcServiceObject, GrpcServiceObject); - -describe('Bigtable/Family', function() { +describe.only('Bigtable/Family', function() { var FAMILY_NAME = 'family-test'; var TABLE = { + bigtable: {}, id: 'my-table', getFamilies: util.noop, createFamily: util.noop, @@ -66,10 +54,8 @@ describe('Bigtable/Family', function() { '@google-cloud/common': { util: fakeUtil, }, - '@google-cloud/common-grpc': { - ServiceObject: FakeGrpcServiceObject, - }, }); + FamilyError = Family.FamilyError; }); @@ -78,50 +64,20 @@ describe('Bigtable/Family', function() { }); describe('instantiation', function() { - it('should inherit from GrpcServiceObject', function() { - var config = family.calledWith_[0]; - - assert(family instanceof FakeGrpcServiceObject); - assert.strictEqual(config.parent, TABLE); - assert.strictEqual(config.id, FAMILY_ID); - assert.deepEqual(config.methods, { - create: true, - exists: true, - get: true, - delete: { - protoOpts: { - service: 'BigtableTableAdmin', - method: 'modifyColumnFamilies', - }, - reqOpts: { - name: TABLE.id, - modifications: [ - { - drop: true, - id: FAMILY_NAME, - }, - ], - }, - }, - }); - assert.strictEqual(typeof config.createMethod, 'function'); - assert.strictEqual(family.familyName, FAMILY_NAME); - }); - it('should promisify all the things', function() { assert(promisified); }); - it('should call Table#createFamily for the create method', function(done) { - var fakeOptions = {}; + it('should localize the Bigtable instance', function() { + assert.strictEqual(family.bigtable, TABLE.bigtable); + }); - TABLE.createFamily = function(name, options, callback) { - assert.strictEqual(name, FAMILY_NAME); - assert.strictEqual(options, fakeOptions); - callback(null, family); // done - }; + it('should localize the Table instance', function() { + assert.strictEqual(family.table, TABLE); + }); - family.create(fakeOptions, done); + it('should localize the full resource path', function() { + assert.strictEqual(family.id, FAMILY_ID); }); it('should extract the family name', function() { @@ -208,12 +164,258 @@ describe('Bigtable/Family', function() { }); }); + describe('create', function() { + it('should call createFamily from table', function(done) { + var options = {}; + + family.table.createFamily = function(name, options_, callback) { + assert.strictEqual(name, family.familyName); + assert.strictEqual(options_, options); + callback(); // done() + }; + + family.create(options, done); + }); + + it('should not require options', function(done) { + family.table.createFamily = function(name, options, callback) { + assert.deepStrictEqual(options, {}); + callback(); // done() + }; + + family.create(done); + }); + }); + + describe('delete', function() { + it('should make the correct request', function(done) { + family.bigtable.request = function(config, callback) { + assert.strictEqual(config.client, 'BigtableTableAdminClient'); + assert.strictEqual(config.method, 'modifyColumnFamilies'); + + assert.deepStrictEqual(config.reqOpts, { + name: family.table.id, + modifications: [ + { + id: family.familyName, + drop: true, + }, + ], + }); + + assert.deepStrictEqual(config.gaxOpts, {}); + + callback(); // done() + }; + + family.delete(done); + }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + family.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); + }; + + family.delete(gaxOptions, assert.ifError); + }); + }); + + describe('exists', function() { + it('should not require gaxOptions', function(done) { + family.getMetadata = function(gaxOptions) { + assert.deepStrictEqual(gaxOptions, {}); + done(); + }; + + family.exists(assert.ifError); + }); + + it('should pass gaxOptions to getMetadata', function(done) { + var gaxOptions = {}; + + family.getMetadata = function(gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + family.exists(gaxOptions, assert.ifError); + }); + + it('should return false if error name is FamilyError', function(done) { + var error = new Error('Error.'); + error.name = 'FamilyError'; + + family.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + family.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, false); + done(); + }); + }); + + it('should return error if name is not FamilyError', function(done) { + var error = new Error('Error.'); + error.name = 'NOT-FamilyError'; + + family.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + family.exists(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return true if no error', function(done) { + family.getMetadata = function(gaxOptions, callback) { + callback(null, {}); + }; + + family.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, true); + done(); + }); + }); + }); + + describe('get', function() { + it('should call getMetadata', function(done) { + var options = { + gaxOptions: {}, + }; + + family.getMetadata = function(gaxOptions) { + assert.strictEqual(gaxOptions, options.gaxOptions); + done(); + }; + + family.get(options, assert.ifError); + }); + + it('should not require an options object', function(done) { + family.getMetadata = function(gaxOptions) { + assert.deepStrictEqual(gaxOptions, undefined); + done(); + }; + + family.get(assert.ifError); + }); + + it('should auto create with error code 5', function(done) { + var error = new Error('Error.'); + error.code = 5; + + var options = { + autoCreate: true, + gaxOptions: {}, + }; + + family.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + family.create = function(options_, callback) { + assert.strictEqual(options_.gaxOptions, options.gaxOptions); + callback(); // done() + }; + + family.get(options, done); + }); + + it('should not auto create without error code 5', function(done) { + var error = new Error('Error.'); + error.code = 'NOT-5'; + + var options = { + autoCreate: true, + }; + + family.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + family.create = function() { + throw new Error('Should not create.'); + }; + + family.get(options, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should not auto create unless requested', function(done) { + var error = new Error('Error.'); + error.code = 5; + + family.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + family.create = function() { + throw new Error('Should not create.'); + }; + + family.get(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return an error from getMetadata', function(done) { + var error = new Error('Error.'); + + family.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + family.get(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return self and API response', function(done) { + var apiResponse = {}; + + family.getMetadata = function(gaxOptions, callback) { + callback(null, apiResponse); + }; + + family.get(function(err, family_, apiResponse_) { + assert.ifError(err); + assert.strictEqual(family_, family); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + }); + describe('getMetadata', function() { + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + family.table.getFamilies = function(gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + family.getMetadata(gaxOptions, assert.ifError); + }); + it('should return an error to the callback', function(done) { var err = new Error('err'); var response = {}; - family.parent.getFamilies = function(callback) { + family.table.getFamilies = function(gaxOptions, callback) { callback(err, null, response); }; @@ -235,7 +437,7 @@ describe('Bigtable/Family', function() { b: 'b', }; - family.parent.getFamilies = function(callback) { + family.table.getFamilies = function(gaxOptions, callback) { callback(null, [FAMILY], response); }; @@ -250,7 +452,7 @@ describe('Bigtable/Family', function() { it('should throw a custom error', function(done) { var response = {}; - family.parent.getFamilies = function(callback) { + family.table.getFamilies = function(gaxOptions, callback) { callback(null, [], response); }; @@ -265,19 +467,18 @@ describe('Bigtable/Family', function() { describe('setMetadata', function() { it('should provide the proper request options', function(done) { - family.request = function(protoOpts, reqOpts) { - assert.deepEqual(protoOpts, { - service: 'BigtableTableAdmin', - method: 'modifyColumnFamilies', - }); + family.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableTableAdminClient'); + assert.strictEqual(config.method, 'modifyColumnFamilies'); - assert.strictEqual(reqOpts.name, TABLE.id); - assert.deepEqual(reqOpts.modifications, [ + assert.strictEqual(config.reqOpts.name, TABLE.id); + assert.deepEqual(config.reqOpts.modifications, [ { id: FAMILY_NAME, update: {}, }, ]); + done(); }; @@ -304,8 +505,8 @@ describe('Bigtable/Family', function() { return formattedRule; }; - family.request = function(p, reqOpts) { - assert.deepEqual(reqOpts, { + family.bigtable.request = function(config) { + assert.deepEqual(config.reqOpts, { name: TABLE.id, modifications: [ { @@ -325,16 +526,13 @@ describe('Bigtable/Family', function() { it('should return an error to the callback', function(done) { var error = new Error('err'); - var response = {}; - family.request = function(protoOpts, reqOpts, callback) { - callback(error, response); + family.bigtable.request = function(config, callback) { + callback(error); }; - family.setMetadata({}, function(err, metadata, apiResponse) { + family.setMetadata({}, function(err) { assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse, response); done(); }); }); @@ -347,7 +545,7 @@ describe('Bigtable/Family', function() { }, }; - family.request = function(protoOpts, reqOpts, callback) { + family.bigtable.request = function(config, callback) { callback(null, response); }; From 6072b9e89fbe68b6d92c060824f58fdb1ba8bef5 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 22 Feb 2018 19:34:48 -0500 Subject: [PATCH 13/46] Row document --- src/row.js | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/row.js b/src/row.js index c721ab594..95627bd37 100644 --- a/src/row.js +++ b/src/row.js @@ -711,12 +711,12 @@ Row.prototype.get = function(columns, options, callback) { } } - var reqOpts = extend({}, options, { + var getRowsOptions = extend({}, options, { keys: [this.id], filter: filter, }); - this.table.getRows(reqOpts, function(err, rows) { + this.table.getRows(getRowsOptions, function(err, rows) { if (err) { callback(err); return; @@ -862,11 +862,10 @@ Row.prototype.increment = function(column, value, gaxOptions, callback) { /** * Update the row cells. * - * @param {string|object} key Either a column name or an entry - * object to be inserted into the row. See {@link Table#insert}. - * @param {*} [value] This can be omitted if using entry object. - * @param {object} [options] Configuration options. See - * {@link Table#mutate}. + * @param {object} key An entry object to be inserted into the row. See + * {@link Table#insert}. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -901,27 +900,19 @@ Row.prototype.increment = function(column, value, gaxOptions, callback) { * var apiResponse = data[0]; * }); */ -Row.prototype.save = function(key, value, callback) { - var rowData; - - if (is.string(key)) { - var column = Mutation.parseColumnName(key); - - rowData = {}; - rowData[column.family] = {}; - rowData[column.family][column.qualifier] = value; - } else { - rowData = key; - callback = value; +Row.prototype.save = function(entry, gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; } var mutation = { key: this.id, - data: rowData, + data: entry, method: Mutation.methods.INSERT, }; - this.table.mutate(mutation, callback); + this.table.mutate(mutation, gaxOptions, callback); }; /*! Developer Documentation From b78f8abe0d5434ecb6b73739afdae47b5572cf5a Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 22 Feb 2018 19:51:23 -0500 Subject: [PATCH 14/46] update save example --- src/row.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/row.js b/src/row.js index 95627bd37..7548f97d5 100644 --- a/src/row.js +++ b/src/row.js @@ -872,6 +872,12 @@ Row.prototype.increment = function(column, value, gaxOptions, callback) { * @param {object} callback.apiResponse The full API response. * * @example + * var entry = { + * follows: { + * jadams: 1 + * } + * }; + * * //- * // Update a single cell. * //- @@ -881,22 +887,17 @@ Row.prototype.increment = function(column, value, gaxOptions, callback) { * } * }; * - * row.save('follows:jadams', 1, callback); + * row.save(entry, 1, callback); * * //- * // Or update several cells at once. * //- - * row.save({ - * follows: { - * jadams: 1, - * wmckinley: 1 - * } - * }, callback); + * row.save(entry, callback); * * //- * // If the callback is omitted, we'll return a Promise. * //- - * row.save('follows:jadams', 1).then(function(data) { + * row.save(entry).then(function(data) { * var apiResponse = data[0]; * }); */ From 21acc368b37c7a06c974aa52e9bc1ba86ce5bbad Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 22 Feb 2018 22:00:39 -0500 Subject: [PATCH 15/46] Test row.js --- src/row.js | 101 +++++------ test/family.js | 2 +- test/row.js | 482 +++++++++++++++++++++++++++---------------------- 3 files changed, 320 insertions(+), 265 deletions(-) diff --git a/src/row.js b/src/row.js index 7548f97d5..61f180692 100644 --- a/src/row.js +++ b/src/row.js @@ -259,7 +259,7 @@ Row.prototype.create = function(options, callback) { var entry = { key: this.id, - data: extend({}, options.entry), + data: options.entry, method: Mutation.methods.INSERT, }; @@ -493,17 +493,17 @@ Row.prototype.exists = function(gaxOptions, callback) { } this.getMetadata(gaxOptions, function(err) { - if (!err) { - callback(null, true); - return; - } + if (err) { + if (err instanceof RowError) { + callback(null, false); + return; + } - if (err instanceof RowError) { - callback(null, false); + callback(err); return; } - callback(err); + callback(null, true); }); }; @@ -513,11 +513,13 @@ Row.prototype.exists = function(gaxOptions, callback) { * callback will be executed. * * @param {Filter} filter Filter to be applied to the contents of the row. - * @param {?object[]} onMatch A list of entries to be ran if a match is found. - * @param {object[]} [onNoMatch] A list of entries to be ran if no matches are + * @param {object} config Configuration object. + * @param {?object[]} config.onMatch A list of entries to be ran if a match is * found. - * @param {object} [gaxOptions] Request configuration options, outlined here: - * https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {object[]} [config.onNoMatch] A list of entries to be ran if no + * matches are found. + * @param {object} [config.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -542,60 +544,54 @@ Row.prototype.exists = function(gaxOptions, callback) { * } * ]; * - * var entries = [ - * { - * method: 'insert', - * data: { - * follows: { - * jadams: 1 + * var config = { + * onMatch: [ + * { + * method: 'insert', + * data: { + * follows: { + * jadams: 1 + * } * } * } - * } - * ]; + * ] + * }; * - * row.filter(filter, entries, callback); + * row.filter(filter, config, callback); * * //- * // Optionally, you can pass in an array of entries to be ran in the event * // that a match is not made. * //- - * row.filter(filter, null, entries, callback); + * var config = { + * onNoMatch: [ + * { + * method: 'insert', + * data: { + * follows: { + * jadams: 1 + * } + * } + * } + * ] + * }; + * + * row.filter(filter, config, callback); * * //- * // If the callback is omitted, we'll return a Promise. * //- - * row.filter(filter, null, entries).then(function(data) { + * row.filter(filter, config).then(function(data) { * var matched = data[0]; * }); */ -Row.prototype.filter = function( - filter, - onMatch, - onNoMatch, - gaxOptions, - callback -) { - if (is.object(onNoMatch)) { - gaxOptions = onNoMatch; - onNoMatch = []; - } - - if (is.function(onNoMatch)) { - callback = onNoMatch; - onNoMatch = []; - } - - if (is.function(gaxOptions)) { - callback = gaxOptions; - gaxOptions = {}; - } - +Row.prototype.filter = function(filter, config, callback) { var reqOpts = { tableName: this.table.id, rowKey: Mutation.convertToBytes(this.id), predicateFilter: Filter.parse(filter), - trueMutations: createFlatMutationsList(onMatch), - falseMutations: createFlatMutationsList(onNoMatch), + trueMutations: createFlatMutationsList(config.onMatch), + falseMutations: createFlatMutationsList(config.onNoMatch), }; this.bigtable.request( @@ -603,7 +599,7 @@ Row.prototype.filter = function( client: 'BigtableClient', method: 'checkAndMutateRow', reqOpts: reqOpts, - gaxOpts: gaxOptions, + gaxOpts: config.gaxOptions, }, function(err, apiResponse) { if (err) { @@ -637,7 +633,6 @@ Row.prototype.filter = function( * @param {?error} callback.err An error returned while making this * request. * @param {Row} callback.row The updated Row object. - * @param {object} callback.apiResponse The full API response. * * @example * //- @@ -751,7 +746,6 @@ Row.prototype.get = function(columns, options, callback) { * @param {?error} callback.err An error returned while making this * request. * @param {object} callback.metadata The row's metadata. - * @param {object} callback.apiResponse The full API response. * * @example * row.getMetadata(function(err, metadata, apiResponse) {}); @@ -770,13 +764,13 @@ Row.prototype.getMetadata = function(options, callback) { options = {}; } - this.get(options, function(err, row, resp) { + this.get(options, function(err, row) { if (err) { - callback(err, null, resp); + callback(err); return; } - callback(null, row.metadata, resp); + callback(null, row.metadata); }); }; @@ -826,6 +820,7 @@ Row.prototype.increment = function(column, value, gaxOptions, callback) { if (is.function(value)) { callback = value; value = 1; + gaxOptions = {}; } // increment('column', value, callback) diff --git a/test/family.js b/test/family.js index 51bbaac03..954c82d86 100644 --- a/test/family.js +++ b/test/family.js @@ -31,7 +31,7 @@ var fakeUtil = extend({}, util, { }, }); -describe.only('Bigtable/Family', function() { +describe('Bigtable/Family', function() { var FAMILY_NAME = 'family-test'; var TABLE = { bigtable: {}, diff --git a/test/row.js b/test/row.js index 8ac36a807..7e431d3fc 100644 --- a/test/row.js +++ b/test/row.js @@ -17,14 +17,11 @@ 'use strict'; var assert = require('assert'); +var common = require('@google-cloud/common'); var extend = require('extend'); -var nodeutil = require('util'); var proxyquire = require('proxyquire'); var sinon = require('sinon').sandbox.create(); -var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); -var GrpcServiceObject = commonGrpc.ServiceObject; var Mutation = require('../src/mutation.js'); var promisified = false; @@ -36,16 +33,10 @@ var fakeUtil = extend({}, common.util, { }, }); -function FakeGrpcServiceObject() { - this.calledWith_ = arguments; - GrpcServiceObject.apply(this, arguments); -} - -nodeutil.inherits(FakeGrpcServiceObject, GrpcServiceObject); - var ROW_ID = 'my-row'; var CONVERTED_ROW_ID = 'my-converted-row'; var TABLE = { + bigtable: {}, id: 'my-table', }; @@ -76,7 +67,7 @@ var FakeFilter = { }), }; -describe('Bigtable/Row', function() { +describe.only('Bigtable/Row', function() { var Row; var row; @@ -85,9 +76,6 @@ describe('Bigtable/Row', function() { '@google-cloud/common': { util: fakeUtil, }, - '@google-cloud/common-grpc': { - ServiceObject: FakeGrpcServiceObject, - }, './mutation.js': FakeMutation, './filter.js': FakeFilter, }); @@ -98,33 +86,32 @@ describe('Bigtable/Row', function() { }); afterEach(function() { - sinon.restore(); - Object.keys(FakeMutation).forEach(function(spy) { - if (FakeMutation[spy].reset) { - FakeMutation[spy].reset(); + if (FakeMutation[spy].resetHistory) { + FakeMutation[spy].resetHistory(); } }); }); describe('instantiation', function() { - it('should inherit from GrpcServiceObject', function() { - var config = row.calledWith_[0]; + it('should promisify all the things', function() { + assert(promisified); + }); - assert(row instanceof FakeGrpcServiceObject); - assert.strictEqual(config.parent, TABLE); - assert.deepEqual(config.methods, { - exists: true, - }); - assert.strictEqual(config.id, ROW_ID); + it('should localize Bigtable instance', function() { + assert.strictEqual(row.bigtable, TABLE.bigtable); }); - it('should create an empty data object', function() { - assert.deepEqual(row.data, {}); + it('should localize Table instance', function() { + assert.strictEqual(row.table, TABLE); }); - it('should promisify all the things', function() { - assert(promisified); + it('should localize ID', function() { + assert.strictEqual(row.id, ROW_ID); + }); + + it('should create an empty data object', function() { + assert.deepEqual(row.data, {}); }); }); @@ -513,10 +500,11 @@ describe('Bigtable/Row', function() { describe('create', function() { it('should provide the proper request options', function(done) { - row.parent.mutate = function(entry) { + row.table.mutate = function(entry, gaxOptions) { assert.strictEqual(entry.key, row.id); - assert.deepEqual(entry.data, {}); + assert.strictEqual(entry.data, undefined); assert.strictEqual(entry.method, Mutation.methods.INSERT); + assert.strictEqual(gaxOptions, undefined); done(); }; @@ -524,38 +512,39 @@ describe('Bigtable/Row', function() { }); it('should accept data to populate the row', function(done) { - var data = { - a: 'a', - b: 'b', + var options = { + entry: { + a: 'a', + b: 'b', + }, }; - row.parent.mutate = function(entry) { - assert.strictEqual(entry.data, data); + row.table.mutate = function(entry) { + assert.strictEqual(entry.data, options.entry); done(); }; - row.create(data, assert.ifError); + row.create(options, assert.ifError); }); it('should accept options when inserting data', function(done) { - var data = { - a: 'a', - b: 'b', + var options = { + gaxOptions: {}, }; - row.parent.mutate = function(entry) { - assert.strictEqual(entry.data, data); + row.table.mutate = function(entry, gaxOptions) { + assert.strictEqual(gaxOptions, options.gaxOptions); done(); }; - row.create(data, assert.ifError); + row.create(options, assert.ifError); }); it('should return an error to the callback', function(done) { var err = new Error('err'); var response = {}; - row.parent.mutate = function(entry, callback) { + row.table.mutate = function(entry, gaxOptions, callback) { callback(err, response); }; @@ -570,7 +559,7 @@ describe('Bigtable/Row', function() { it('should return the Row instance', function(done) { var response = {}; - row.parent.mutate = function(entry, callback) { + row.table.mutate = function(entry, gaxOptions, callback) { callback(null, response); }; @@ -599,16 +588,14 @@ describe('Bigtable/Row', function() { }); it('should read/modify/write rules', function(done) { - row.request = function(grpcOpts, reqOpts, callback) { - assert.deepEqual(grpcOpts, { - service: 'Bigtable', - method: 'readModifyWriteRow', - }); + row.bigtable.request = function(config, callback) { + assert.strictEqual(config.client, 'BigtableClient'); + assert.strictEqual(config.method, 'readModifyWriteRow'); - assert.strictEqual(reqOpts.tableName, TABLE.id); - assert.strictEqual(reqOpts.rowKey, CONVERTED_ROW_ID); + assert.strictEqual(config.reqOpts.tableName, TABLE.id); + assert.strictEqual(config.reqOpts.rowKey, CONVERTED_ROW_ID); - assert.deepEqual(reqOpts.rules, [ + assert.deepEqual(config.reqOpts.rules, [ { familyName: 'a', columnQualifier: 'b', @@ -622,23 +609,132 @@ describe('Bigtable/Row', function() { assert.strictEqual(spy.getCall(0).args[0], 'b'); assert.strictEqual(spy.getCall(1).args[0], 'c'); assert.strictEqual(spy.getCall(2).args[0], ROW_ID); - callback(); + + callback(); // done() }; row.createRules(rules, done); }); - it('should return an error to the callback', function(done) { - var err = new Error('err'); - var response = {}; + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; - row.request = function(g, r, callback) { - callback(err, response); + row.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); }; - row.createRules(rules, function(err_, apiResponse) { - assert.strictEqual(err, err_); - assert.strictEqual(response, apiResponse); + row.createRules(rules, gaxOptions, assert.ifError); + }); + }); + + describe('delete', function() { + it('should provide the proper request options', function(done) { + row.table.mutate = function(mutation, gaxOptions, callback) { + assert.strictEqual(mutation.key, ROW_ID); + assert.strictEqual(mutation.method, FakeMutation.methods.DELETE); + assert.deepStrictEqual(gaxOptions, {}); + callback(); // done() + }; + + row.delete(done); + }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + row.table.mutate = function(mutation, gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + row.delete(gaxOptions, done); + }); + }); + + describe('deleteCells', function() { + var columns = ['a:b', 'c']; + + it('should provide the proper request options', function(done) { + row.table.mutate = function(mutation, gaxOptions, callback) { + assert.strictEqual(mutation.key, ROW_ID); + assert.strictEqual(mutation.data, columns); + assert.strictEqual(mutation.method, FakeMutation.methods.DELETE); + assert.deepStrictEqual(gaxOptions, {}); + callback(); // done() + }; + + row.deleteCells(columns, done); + }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + row.table.mutate = function(mutation, gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + row.deleteCells(columns, gaxOptions, done); + }); + }); + + describe('exists', function() { + it('should not require gaxOptions', function(done) { + row.getMetadata = function(gaxOptions) { + assert.deepStrictEqual(gaxOptions, {}); + done(); + }; + + row.exists(assert.ifError); + }); + + it('should pass gaxOptions to getMetadata', function(done) { + var gaxOptions = {}; + + row.getMetadata = function(gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + row.exists(gaxOptions, assert.ifError); + }); + + it('should return false if error is RowError', function(done) { + var error = new Row.RowError('Error.'); + + row.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + row.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, false); + done(); + }); + }); + + it('should return error if not RowError', function(done) { + var error = new Error('Error.'); + + row.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + row.exists(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return true if no error', function(done) { + row.getMetadata = function(gaxOptions, callback) { + callback(null, {}); + }; + + row.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, true); done(); }); }); @@ -663,14 +759,15 @@ describe('Bigtable/Row', function() { }; beforeEach(function() { - FakeMutation.parse.reset(); - FakeFilter.parse.reset(); + FakeMutation.parse.resetHistory(); + FakeFilter.parse.resetHistory(); }); it('should provide the proper request options', function(done) { var filter = { column: 'a', }; + var fakeParsedFilter = { column: 'b', }; @@ -683,17 +780,20 @@ describe('Bigtable/Row', function() { return fakeMutations; }); - row.request = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'Bigtable', - method: 'checkAndMutateRow', - }); + row.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableClient'); + assert.strictEqual(config.method, 'checkAndMutateRow'); - assert.strictEqual(reqOpts.tableName, TABLE.id); - assert.strictEqual(reqOpts.rowKey, CONVERTED_ROW_ID); - assert.deepEqual(reqOpts.predicateFilter, fakeParsedFilter); - assert.deepEqual(reqOpts.trueMutations, fakeMutations.mutations); - assert.deepEqual(reqOpts.falseMutations, fakeMutations.mutations); + assert.strictEqual(config.reqOpts.tableName, TABLE.id); + assert.strictEqual(config.reqOpts.rowKey, CONVERTED_ROW_ID); + assert.deepEqual(config.reqOpts.predicateFilter, fakeParsedFilter); + assert.deepEqual(config.reqOpts.trueMutations, fakeMutations.mutations); + assert.deepEqual( + config.reqOpts.falseMutations, + fakeMutations.mutations + ); + + assert.strictEqual(config.gaxOpts, undefined); assert.strictEqual(FakeMutation.parse.callCount, 2); assert.strictEqual(FakeMutation.parse.getCall(0).args[0], mutations[0]); @@ -701,39 +801,39 @@ describe('Bigtable/Row', function() { assert.strictEqual(FakeFilter.parse.callCount, 1); assert(FakeFilter.parse.calledWithExactly(filter)); + done(); }; - row.filter(filter, mutations, mutations, assert.ifError); + row.filter( + filter, + { + onMatch: mutations, + onNoMatch: mutations, + }, + assert.ifError + ); }); - it('should optionally accept onNoMatch mutations', function(done) { - row.request = function(g, reqOpts) { - assert.deepEqual(reqOpts.falseMutations, []); - assert.strictEqual(FakeMutation.parse.callCount, 1); - assert(FakeMutation.parse.calledWithExactly(mutations[0])); - done(); + it('should accept gaxOptions', function(done) { + var filter = { + column: 'a', }; + var gaxOptions = {}; - row.filter({}, mutations, assert.ifError); - }); - - it('should optionally accept onMatch mutations', function(done) { - row.request = function(g, reqOpts) { - assert.deepEqual(reqOpts.trueMutations, []); - assert.strictEqual(FakeMutation.parse.callCount, 1); - assert(FakeMutation.parse.calledWithExactly(mutations[0])); + row.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); done(); }; - row.filter({}, null, mutations, assert.ifError); + row.filter(filter, {gaxOptions}, assert.ifError); }); it('should return an error to the callback', function(done) { var err = new Error('err'); var response = {}; - row.request = function(g, r, callback) { + row.bigtable.request = function(config, callback) { callback(err, response); }; @@ -750,7 +850,7 @@ describe('Bigtable/Row', function() { predicateMatched: true, }; - row.request = function(g, r, callback) { + row.bigtable.request = function(config, callback) { callback(null, response); }; @@ -763,36 +863,9 @@ describe('Bigtable/Row', function() { }); }); - describe('delete', function() { - it('should provide the proper request options', function(done) { - row.parent.mutate = function(mutation, callback) { - assert.strictEqual(mutation.key, ROW_ID); - assert.strictEqual(mutation.method, FakeMutation.methods.DELETE); - callback(); - }; - - row.delete(done); - }); - }); - - describe('deleteCells', function() { - var columns = ['a:b', 'c']; - - it('should provide the proper request options', function(done) { - row.parent.mutate = function(mutation, callback) { - assert.strictEqual(mutation.key, ROW_ID); - assert.strictEqual(mutation.data, columns); - assert.strictEqual(mutation.method, FakeMutation.methods.DELETE); - callback(); - }; - - row.deleteCells(columns, done); - }); - }); - describe('get', function() { it('should provide the proper request options', function(done) { - row.parent.getRows = function(reqOpts) { + row.table.getRows = function(reqOpts) { assert.strictEqual(reqOpts.keys[0], ROW_ID); assert.strictEqual(reqOpts.filter, undefined); assert.strictEqual(FakeMutation.parseColumnName.callCount, 0); @@ -814,7 +887,7 @@ describe('Bigtable/Row', function() { }, ]; - row.parent.getRows = function(reqOpts) { + row.table.getRows = function(reqOpts) { assert.deepEqual(reqOpts.filter, expectedFilter); assert.strictEqual(FakeMutation.parseColumnName.callCount, 1); assert(FakeMutation.parseColumnName.calledWith(keys[0])); @@ -850,7 +923,7 @@ describe('Bigtable/Row', function() { }, ]; - row.parent.getRows = function(reqOpts) { + row.table.getRows = function(reqOpts) { assert.deepEqual(reqOpts.filter, expectedFilter); var spy = FakeMutation.parseColumnName; @@ -873,7 +946,7 @@ describe('Bigtable/Row', function() { }, ]; - row.parent.getRows = function(reqOpts) { + row.table.getRows = function(reqOpts) { assert.deepEqual(reqOpts.filter, expectedFilter); assert.strictEqual(FakeMutation.parseColumnName.callCount, 1); assert(FakeMutation.parseColumnName.calledWith(keys[0])); @@ -896,7 +969,7 @@ describe('Bigtable/Row', function() { }, ]; - row.parent.getRows = function(reqOpts) { + row.table.getRows = function(reqOpts) { assert.deepEqual(reqOpts.filter, expectedFilter); assert.strictEqual(FakeMutation.parseColumnName.callCount, 1); assert(FakeMutation.parseColumnName.calledWith(keys[0])); @@ -916,7 +989,7 @@ describe('Bigtable/Row', function() { }; var expectedFilter = options.filter; - row.parent.getRows = function(reqOpts) { + row.table.getRows = function(reqOpts) { assert.deepEqual(reqOpts.filter, expectedFilter); done(); }; @@ -929,7 +1002,7 @@ describe('Bigtable/Row', function() { decode: false, }; - row.parent.getRows = function(reqOpts) { + row.table.getRows = function(reqOpts) { assert.strictEqual(reqOpts.decode, options.decode); assert(!reqOpts.filter); done(); @@ -940,39 +1013,32 @@ describe('Bigtable/Row', function() { it('should return an error to the callback', function(done) { var error = new Error('err'); - var response = {}; - row.parent.getRows = function(r, callback) { - callback(error, null, response); + row.table.getRows = function(r, callback) { + callback(error); }; - row.get(function(err, row_, apiResponse) { + row.get(function(err, row) { assert.strictEqual(error, err); - assert.strictEqual(row_, null); - assert.strictEqual(response, apiResponse); + assert.strictEqual(row, undefined); done(); }); }); it('should return a custom error if the row is not found', function(done) { - var response = {}; - - row.parent.getRows = function(r, callback) { - callback(null, [], response); + row.table.getRows = function(r, callback) { + callback(null, []); }; - row.get(function(err, row_, apiResponse) { + row.get(function(err, row_) { assert(err instanceof Row.RowError); assert.strictEqual(err.message, 'Unknown row: ' + row.id + '.'); - assert.strictEqual(err.code, 404); - assert.deepEqual(row_, null); - assert.strictEqual(response, apiResponse); + assert.deepEqual(row_, undefined); done(); }); }); it('should update the row data upon success', function(done) { - var response = {}; var fakeRow = new Row(TABLE, ROW_ID); fakeRow.data = { @@ -980,21 +1046,19 @@ describe('Bigtable/Row', function() { b: 'b', }; - row.parent.getRows = function(r, callback) { - callback(null, [fakeRow], response); + row.table.getRows = function(r, callback) { + callback(null, [fakeRow]); }; - row.get(function(err, row_, apiResponse) { + row.get(function(err, row_) { assert.ifError(err); assert.strictEqual(row_, row); assert.deepEqual(row.data, fakeRow.data); - assert.strictEqual(response, apiResponse); done(); }); }); it('should return only data for the keys provided', function(done) { - var response = {}; var fakeRow = new Row(TABLE, ROW_ID); fakeRow.data = { @@ -1008,14 +1072,13 @@ describe('Bigtable/Row', function() { c: 'c', }; - row.parent.getRows = function(r, callback) { - callback(null, [fakeRow], response); + row.table.getRows = function(r, callback) { + callback(null, [fakeRow]); }; - row.get(keys, function(err, data, apiResponse) { + row.get(keys, function(err, data) { assert.ifError(err); assert.deepEqual(Object.keys(data), keys); - assert.strictEqual(response, apiResponse); done(); }); }); @@ -1024,43 +1087,38 @@ describe('Bigtable/Row', function() { describe('getMetadata', function() { it('should return an error to the callback', function(done) { var error = new Error('err'); - var response = {}; row.get = function(options, callback) { - callback(error, null, response); + callback(error); }; - row.getMetadata(function(err, metadata, apiResponse) { + row.getMetadata(function(err, metadata) { assert.strictEqual(error, err); - assert.strictEqual(metadata, null); - assert.strictEqual(response, apiResponse); + assert.strictEqual(metadata, undefined); done(); }); }); it('should return metadata to the callback', function(done) { - var response = {}; var fakeMetadata = { a: 'a', b: 'b', }; row.get = function(options, callback) { - callback(null, row, response); + callback(null, row); }; row.metadata = fakeMetadata; - row.getMetadata(function(err, metadata, apiResponse) { + row.getMetadata(function(err, metadata) { assert.ifError(err); assert.strictEqual(metadata, fakeMetadata); - assert.strictEqual(response, apiResponse); done(); }); }); it('should accept an options object', function(done) { - var response = {}; var fakeMetadata = {}; var fakeOptions = { decode: false, @@ -1068,15 +1126,14 @@ describe('Bigtable/Row', function() { row.get = function(options, callback) { assert.strictEqual(options, fakeOptions); - callback(null, row, response); + callback(null, row); }; row.metadata = fakeMetadata; - row.getMetadata(fakeOptions, function(err, metadata, apiResponse) { + row.getMetadata(fakeOptions, function(err, metadata) { assert.ifError(err); assert.strictEqual(metadata, fakeMetadata); - assert.strictEqual(response, apiResponse); done(); }); }); @@ -1098,10 +1155,15 @@ describe('Bigtable/Row', function() { }); }); + afterEach(function() { + formatFamiliesSpy.restore(); + }); + it('should provide the proper request options', function(done) { - row.createRules = function(reqOpts) { + row.createRules = function(reqOpts, gaxOptions) { assert.strictEqual(reqOpts.column, COLUMN_NAME); assert.strictEqual(reqOpts.increment, 1); + assert.deepStrictEqual(gaxOptions, {}); done(); }; @@ -1112,7 +1174,6 @@ describe('Bigtable/Row', function() { var increment = 10; row.createRules = function(reqOpts) { - assert.strictEqual(reqOpts.column, COLUMN_NAME); assert.strictEqual(reqOpts.increment, increment); done(); }; @@ -1120,11 +1181,35 @@ describe('Bigtable/Row', function() { row.increment(COLUMN_NAME, increment, assert.ifError); }); + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + row.createRules = function(reqOpts, gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + row.increment(COLUMN_NAME, gaxOptions, assert.ifError); + }); + + it('should accept increment amount and gaxOptions', function(done) { + var increment = 10; + var gaxOptions = {}; + + row.createRules = function(reqOpts, gaxOptions_) { + assert.strictEqual(reqOpts.increment, increment); + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + row.increment(COLUMN_NAME, increment, gaxOptions, assert.ifError); + }); + it('should return an error to the callback', function(done) { var error = new Error('err'); var response = {}; - row.createRules = function(r, callback) { + row.createRules = function(r, gaxOptions, callback) { callback(error, response); }; @@ -1160,7 +1245,7 @@ describe('Bigtable/Row', function() { }, }; - row.createRules = function(r, callback) { + row.createRules = function(r, gaxOptions, callback) { callback(null, response); }; @@ -1176,55 +1261,30 @@ describe('Bigtable/Row', function() { }); describe('save', function() { - describe('key value pair', function() { - var key = 'a:b'; - var value = 'c'; + var data = { + a: { + b: 'c', + }, + }; - var expectedData = { - d: { - e: 'c', - }, + it('should insert an object', function(done) { + row.table.mutate = function(entry, gaxOptions, callback) { + assert.strictEqual(entry.data, data); + callback(); // done() }; - var parseSpy; - - beforeEach(function() { - parseSpy = Mutation.parseColumnName = sinon.spy(function() { - return { - family: 'd', - qualifier: 'e', - }; - }); - }); - - it('should insert a key value pair', function(done) { - row.parent.mutate = function(entry, callback) { - assert.strictEqual(entry.key, ROW_ID); - assert.deepEqual(entry.data, expectedData); - assert.strictEqual(entry.method, FakeMutation.methods.INSERT); - assert(parseSpy.calledWithExactly(key)); - callback(); - }; - - row.save(key, value, done); - }); + row.save(data, done); }); - describe('object mode', function() { - var data = { - a: { - b: 'c', - }, - }; + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; - it('should insert an object', function(done) { - row.parent.mutate = function(entry) { - assert.strictEqual(entry.data, data); - done(); - }; + row.table.mutate = function(entry, gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; - row.save(data, assert.ifError); - }); + row.save(data, gaxOptions, assert.ifError); }); }); From 8b733d047a9bc0134c1939778f734c06e03b4f4b Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 23 Feb 2018 08:55:52 -0500 Subject: [PATCH 16/46] Test instance.js --- src/instance.js | 77 ++- src/v2/bigtable_instance_admin_client.js | 5 + test/cluster.js | 13 + test/instance.js | 774 ++++++++++++++--------- test/row.js | 2 +- 5 files changed, 542 insertions(+), 329 deletions(-) diff --git a/src/instance.js b/src/instance.js index 277d60724..74c428a00 100644 --- a/src/instance.js +++ b/src/instance.js @@ -190,9 +190,14 @@ Instance.prototype.createCluster = function(name, options, callback) { reqOpts: reqOpts, gaxOpts: options.gaxOptions, }, - function(err, operation) { - var cluster = err ? self.cluster(name) : null; - callback(err, cluster, operation); + function() { + var args = [].slice.call(arguments); + + if (args[1]) { + args.splice(1, 0, self.cluster(name)); + } + + callback.apply(null, args); } ); }; @@ -339,13 +344,15 @@ Instance.prototype.createTable = function(name, options, callback) { gaxOpts: options.gaxOptions, }, function() { - if (arguments[1]) { - var table = self.table(arguments[1].name); - table.metadata = arguments[1]; - arguments[1] = table; + var args = [].slice.call(arguments); + + if (args[1]) { + var table = self.table(args[1].name); + table.metadata = args[1]; + args.splice(1, 0, table); } - callback.apply(null, arguments); + callback.apply(null, args); } ); }; @@ -434,27 +441,28 @@ Instance.prototype.exists = function(gaxOptions, callback) { } this.getMetadata(gaxOptions, function(err) { - if (!err) { - callback(null, true); - return; - } + if (err) { + if (err.code === 5) { + callback(null, false); + return; + } - if (err.code === 5) { - callback(null, false); + callback(err); return; } - callback(err); + callback(null, true); }); }; /** * Get an instance if it exists. * - * @param {object} [gaxOptions] Request configuration options, outlined here: - * https://googleapis.github.io/gax-nodejs/CallSettings.html. - * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the + * @param {object} [options] Configuration object. + * @param {boolean} [options.autoCreate=false] Automatically create the * instance if it does not already exist. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * * @example * const Bigtable = require('@google-cloud/bigtable'); @@ -473,29 +481,29 @@ Instance.prototype.exists = function(gaxOptions, callback) { * var apiResponse = data[1]; * }); */ -Instance.prototype.get = function(gaxOptions, callback) { +Instance.prototype.get = function(options, callback) { var self = this; - if (is.fn(gaxOptions)) { - callback = gaxOptions; - gaxOptions = {}; + if (is.fn(options)) { + callback = options; + options = {}; } - var autoCreate = !!gaxOptions.autoCreate; - delete gaxOptions.autoCreate; + var autoCreate = !!options.autoCreate; + var gaxOptions = options.gaxOptions; this.getMetadata(gaxOptions, function(err, apiResponse) { - if (!err) { - callback(null, self, apiResponse); - return; - } + if (err) { + if (err.code === 5 && autoCreate) { + self.create({gaxOptions}, callback); + return; + } - if (err.code !== 5 || !autoCreate) { callback(err, null, apiResponse); return; } - self.create({gaxOptions}, callback); + callback(null, self, apiResponse); }); }; @@ -572,7 +580,7 @@ Instance.prototype.getClusters = function(query, callback) { }, function() { if (arguments[1]) { - arguments[1] = arguments[1].clusters.map(function(clusterObj) { + arguments[1] = arguments[1].map(function(clusterObj) { var cluster = self.cluster(clusterObj.name); cluster.metadata = clusterObj; return cluster; @@ -744,11 +752,10 @@ Instance.prototype.getTables = function(options, callback) { }, function() { if (arguments[1]) { - arguments[1] = arguments[1].map(function(metadata) { - var name = metadata.name.split('/').pop(); + arguments[1] = arguments[1].map(function(tableObj) { + var name = tableObj.name.split('/').pop(); var table = self.table(name); - - table.metadata = metadata; + table.metadata = tableObj; return table; }); } diff --git a/src/v2/bigtable_instance_admin_client.js b/src/v2/bigtable_instance_admin_client.js index ece5ecec5..f9ef54359 100644 --- a/src/v2/bigtable_instance_admin_client.js +++ b/src/v2/bigtable_instance_admin_client.js @@ -127,6 +127,11 @@ class BigtableInstanceAdminClient { 'nextPageToken', 'appProfiles' ), + listClusters: new gax.PageDescriptor( + 'pageToken', + 'nextPageToken', + 'clusters' + ), }; var protoFilesRoot = new gax.grpc.GoogleProtoFilesRoot(); protoFilesRoot = protobuf.loadSync( diff --git a/test/cluster.js b/test/cluster.js index 05c3e2ad8..76ae9fcbb 100644 --- a/test/cluster.js +++ b/test/cluster.js @@ -487,5 +487,18 @@ describe('Bigtable/Cluster', function() { cluster.setMetadata(options, assert.ifError); }); + + it('should execute callback with all arguments', function(done) { + var args = [{}, {}, {}]; + + cluster.bigtable.request = function(config, callback) { + callback.apply(null, args); + }; + + cluster.setMetadata({}, function() { + assert.deepStrictEqual([].slice.call(arguments), args); + done(); + }); + }); }); }); diff --git a/test/instance.js b/test/instance.js index d5db63287..db2b76724 100644 --- a/test/instance.js +++ b/test/instance.js @@ -17,15 +17,12 @@ 'use strict'; var assert = require('assert'); +var common = require('@google-cloud/common'); var extend = require('extend'); var format = require('string-format-obj'); var proxyquire = require('proxyquire'); var util = require('util'); -var common = require('@google-cloud/common'); -var commonGrpc = require('@google-cloud/common-grpc'); -var GrpcServiceObject = commonGrpc.ServiceObject; - var Cluster = require('../src/cluster.js'); var Family = require('../src/family.js'); var Table = require('../src/table.js'); @@ -61,12 +58,11 @@ function createFake(Class) { return Fake; } -var FakeGrpcServiceObject = createFake(GrpcServiceObject); var FakeCluster = createFake(Cluster); var FakeFamily = createFake(Family); var FakeTable = createFake(Table); -describe('Bigtable/Instance', function() { +describe.only('Bigtable/Instance', function() { var INSTANCE_NAME = 'my-instance'; var BIGTABLE = {projectName: 'projects/my-project'}; @@ -86,9 +82,6 @@ describe('Bigtable/Instance', function() { paginator: fakePaginator, util: fakeUtil, }, - '@google-cloud/common-grpc': { - ServiceObject: FakeGrpcServiceObject, - }, './cluster.js': FakeCluster, './family.js': FakeFamily, './table.js': FakeTable, @@ -116,118 +109,78 @@ describe('Bigtable/Instance', function() { assert(promisified); }); - it('should inherit from GrpcServiceObject', function() { - var config = instance.calledWith_[0]; + it('should localize Bigtable instance', function() { + assert.strictEqual(instance.bigtable, BIGTABLE); + }); - assert(instance instanceof FakeGrpcServiceObject); - assert.strictEqual(config.parent, BIGTABLE); - assert.strictEqual(config.id, INSTANCE_ID); + it('should create full ID from name', function() { + assert.strictEqual(instance.id, INSTANCE_ID); + }); - assert.deepEqual(config.methods, { - create: true, - delete: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'deleteInstance', - }, - reqOpts: { - name: INSTANCE_ID, - }, - }, - exists: true, - get: true, - getMetadata: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'getInstance', - }, - reqOpts: { - name: INSTANCE_ID, - }, - }, - setMetadata: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'updateInstance', - }, - reqOpts: { - name: INSTANCE_ID, - }, - }, - }); + it('should localize name', function() { + assert.strictEqual(instance.name, INSTANCE_NAME); }); - it('should Bigtable#createInstance to create the table', function(done) { - var fakeOptions = {}; - var config = instance.calledWith_[0]; + it('should not alter full instance ids', function() { + var instance = new Instance(BIGTABLE, INSTANCE_ID); + assert.strictEqual(instance.id, INSTANCE_ID); + assert.strictEqual(instance.name, INSTANCE_NAME); + }); + }); + + describe('create', function() { + it('should call createInstance from instance', function(done) { + var options = {}; - BIGTABLE.createInstance = function(name, options, callback) { - assert.strictEqual(name, INSTANCE_NAME); - assert.strictEqual(options, fakeOptions); - callback(); + instance.bigtable.createInstance = function(name, options_, callback) { + assert.strictEqual(name, instance.name); + assert.strictEqual(options_, options); + callback(); // done() }; - config.createMethod(null, fakeOptions, done); + instance.create(options, done); }); - it('should not alter full instance ids', function() { - var fakeId = 'a/b/c/d'; - var instance = new Instance(BIGTABLE, fakeId); - var config = instance.calledWith_[0]; - - assert.strictEqual(config.id, fakeId); - - assert.deepEqual(config.methods, { - create: true, - delete: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'deleteInstance', - }, - reqOpts: { - name: fakeId, - }, - }, - exists: true, - get: true, - getMetadata: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'getInstance', - }, - reqOpts: { - name: fakeId, - }, - }, - setMetadata: { - protoOpts: { - service: 'BigtableInstanceAdmin', - method: 'updateInstance', - }, - reqOpts: { - name: fakeId, - }, - }, - }); + it('should not require options', function(done) { + instance.bigtable.createInstance = function(name, options, callback) { + assert.deepStrictEqual(options, {}); + callback(); // done() + }; + + instance.create(done); }); }); describe('createCluster', function() { it('should provide the proper request options', function(done) { - instance.request = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'BigtableInstanceAdmin', - method: 'createCluster', - }); + instance.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'createCluster'); + + assert.strictEqual(config.reqOpts.parent, INSTANCE_ID); + assert.strictEqual(config.reqOpts.clusterId, CLUSTER_NAME); + + assert.strictEqual(config.gaxOpts, undefined); - assert.strictEqual(reqOpts.parent, INSTANCE_ID); - assert.strictEqual(reqOpts.clusterId, CLUSTER_NAME); done(); }; instance.createCluster(CLUSTER_NAME, assert.ifError); }); + it('should accept gaxOptions', function(done) { + var options = { + gaxOptions: {}, + }; + + instance.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, options.gaxOptions); + done(); + }; + + instance.createCluster(CLUSTER_NAME, options, assert.ifError); + }); + it('should respect the location option', function(done) { var options = { location: 'us-central1-b', @@ -241,8 +194,8 @@ describe('Bigtable/Instance', function() { return fakeLocation; }; - instance.request = function(grpcOpts, reqOpts) { - assert.strictEqual(reqOpts.cluster.location, fakeLocation); + instance.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.cluster.location, fakeLocation); done(); }; @@ -254,8 +207,8 @@ describe('Bigtable/Instance', function() { nodes: 3, }; - instance.request = function(grpcOpts, reqOpts) { - assert.strictEqual(reqOpts.cluster.serveNodes, options.nodes); + instance.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.cluster.serveNodes, options.nodes); done(); }; @@ -274,65 +227,37 @@ describe('Bigtable/Instance', function() { return fakeStorageType; }; - instance.request = function(grpcOpts, reqOpts) { - assert.strictEqual(reqOpts.cluster.defaultStorageType, fakeStorageType); + instance.bigtable.request = function(config) { + assert.strictEqual( + config.reqOpts.cluster.defaultStorageType, + fakeStorageType + ); done(); }; instance.createCluster(CLUSTER_NAME, options, assert.ifError); }); - it('should return an error to the callback', function(done) { - var error = new Error('err'); + it('should execute callback with arguments from GAPIC', function(done) { var response = {}; - instance.request = function(grpcOpts, reqOpts, callback) { - callback(error, response); - }; - - var callback = function(err, cluster, operation, apiResponse) { - assert.strictEqual(err, error); - assert.strictEqual(cluster, null); - assert.strictEqual(operation, null); - assert.strictEqual(apiResponse, response); - done(); - }; - - instance.createCluster(CLUSTER_NAME, callback); - }); - - it('should return a cluster and operation object', function(done) { - var fakeCluster = {}; - var fakeOperation = {}; - - var response = { - name: 'my-operation', - }; - - instance.request = function(grpcOpts, reqOpts, callback) { + instance.bigtable.request = function(config, callback) { callback(null, response); }; - BIGTABLE.operation = function(name) { - assert.strictEqual(name, response.name); - return fakeOperation; - }; + var fakeCluster = {}; instance.cluster = function(name) { assert.strictEqual(name, CLUSTER_NAME); return fakeCluster; }; - var callback = function(err, cluster, operation, apiResponse) { - assert.strictEqual(err, null); - assert.strictEqual(cluster, fakeCluster); - assert.strictEqual(operation, fakeOperation); - assert.strictEqual(operation.metadata, response); + instance.createCluster(CLUSTER_NAME, function(err, cluster, apiResponse) { + assert.ifError(err); + assert.strictEqual(arguments[1], fakeCluster); assert.strictEqual(apiResponse, response); done(); - }; - - instance.createCluster(CLUSTER_NAME, callback); + }); }); }); @@ -346,23 +271,35 @@ describe('Bigtable/Instance', function() { }); it('should provide the proper request options', function(done) { - instance.request = function(protoOpts, reqOpts) { - assert.deepEqual(protoOpts, { - service: 'BigtableTableAdmin', - method: 'createTable', - }); + instance.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableTableAdminClient'); + assert.strictEqual(config.method, 'createTable'); + + assert.strictEqual(config.reqOpts.parent, INSTANCE_ID); + assert.strictEqual(config.reqOpts.tableId, TABLE_ID); + assert.deepStrictEqual(config.reqOpts.table, {granularity: 0}); + + assert.strictEqual(config.gaxOpts, undefined); - assert.strictEqual(reqOpts.parent, INSTANCE_ID); - assert.strictEqual(reqOpts.tableId, TABLE_ID); - assert.deepEqual(reqOpts.table, { - granularity: 0, - }); done(); }; instance.createTable(TABLE_ID, assert.ifError); }); + it('should accept gaxOptions', function(done) { + var options = { + gaxOptions: {}, + }; + + instance.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, options.gaxOptions); + done(); + }; + + instance.createTable(TABLE_ID, options, assert.ifError); + }); + it('should set the initial split keys', function(done) { var options = { splits: ['a', 'b'], @@ -370,8 +307,8 @@ describe('Bigtable/Instance', function() { var expectedSplits = [{key: 'a'}, {key: 'b'}]; - instance.request = function(protoOpts, reqOpts) { - assert.deepEqual(reqOpts.initialSplits, expectedSplits); + instance.bigtable.request = function(config) { + assert.deepEqual(config.reqOpts.initialSplits, expectedSplits); done(); }; @@ -384,8 +321,8 @@ describe('Bigtable/Instance', function() { families: ['a', 'b'], }; - instance.request = function(protoOpts, reqOpts) { - assert.deepEqual(reqOpts.table.columnFamilies, { + instance.bigtable.request = function(config) { + assert.deepEqual(config.reqOpts.table.columnFamilies, { a: {}, b: {}, }); @@ -413,8 +350,8 @@ describe('Bigtable/Instance', function() { return fakeRule; }; - instance.request = function(protoOpts, reqOpts) { - assert.deepEqual(reqOpts.table.columnFamilies, { + instance.bigtable.request = function(config) { + assert.deepEqual(config.reqOpts.table.columnFamilies, { e: { gcRule: fakeRule, }, @@ -426,22 +363,6 @@ describe('Bigtable/Instance', function() { }); }); - it('should return an error to the callback', function(done) { - var err = new Error('err'); - var response = {}; - - instance.request = function(protoOpts, reqOpts, callback) { - callback(err, response); - }; - - instance.createTable(TABLE_ID, function(err_, table, apiResponse) { - assert.strictEqual(err, err_); - assert.strictEqual(table, null); - assert.strictEqual(apiResponse, response); - done(); - }); - }); - it('should return a Table object', function(done) { var response = { name: TABLE_ID, @@ -454,7 +375,7 @@ describe('Bigtable/Instance', function() { return fakeTable; }; - instance.request = function(p, r, callback) { + instance.bigtable.request = function(config, callback) { callback(null, response); }; @@ -462,7 +383,7 @@ describe('Bigtable/Instance', function() { assert.ifError(err); assert.strictEqual(table, fakeTable); assert.strictEqual(table.metadata, response); - assert.strictEqual(response, apiResponse); + assert.strictEqual(apiResponse, response); done(); }); }); @@ -481,15 +402,219 @@ describe('Bigtable/Instance', function() { }); }); - describe('getClusters', function() { - it('should provide the proper request options', function(done) { - instance.request = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'BigtableInstanceAdmin', - method: 'listClusters', + describe('delete', function() { + it('should make the correct request', function(done) { + instance.bigtable.request = function(config, callback) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'deleteInstance'); + + assert.deepEqual(config.reqOpts, { + name: instance.id, }); - assert.strictEqual(reqOpts.parent, INSTANCE_ID); + assert.deepEqual(config.gaxOpts, {}); + + callback(); // done() + }; + + instance.delete(done); + }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + instance.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); + }; + + instance.delete(gaxOptions, assert.ifError); + }); + }); + + describe('exists', function() { + it('should not require gaxOptions', function(done) { + instance.getMetadata = function(gaxOptions) { + assert.deepStrictEqual(gaxOptions, {}); + done(); + }; + + instance.exists(assert.ifError); + }); + + it('should pass gaxOptions to getMetadata', function(done) { + var gaxOptions = {}; + + instance.getMetadata = function(gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + instance.exists(gaxOptions, assert.ifError); + }); + + it('should return false if error code is 5', function(done) { + var error = new Error('Error.'); + error.code = 5; + + instance.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + instance.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, false); + done(); + }); + }); + + it('should return error if code is not 5', function(done) { + var error = new Error('Error.'); + error.code = 'NOT-5'; + + instance.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + instance.exists(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return true if no error', function(done) { + instance.getMetadata = function(gaxOptions, callback) { + callback(null, {}); + }; + + instance.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, true); + done(); + }); + }); + }); + + describe('get', function() { + it('should call getMetadata', function(done) { + var options = { + gaxOptions: {}, + }; + + instance.getMetadata = function(gaxOptions) { + assert.strictEqual(gaxOptions, options.gaxOptions); + done(); + }; + + instance.get(options, assert.ifError); + }); + + it('should not require an options object', function(done) { + instance.getMetadata = function(gaxOptions) { + assert.deepStrictEqual(gaxOptions, undefined); + done(); + }; + + instance.get(assert.ifError); + }); + + it('should auto create with error code 5', function(done) { + var error = new Error('Error.'); + error.code = 5; + + var options = { + autoCreate: true, + gaxOptions: {}, + }; + + instance.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + instance.create = function(options_, callback) { + assert.strictEqual(options_.gaxOptions, options.gaxOptions); + callback(); // done() + }; + + instance.get(options, done); + }); + + it('should not auto create without error code 5', function(done) { + var error = new Error('Error.'); + error.code = 'NOT-5'; + + var options = { + autoCreate: true, + }; + + instance.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + instance.create = function() { + throw new Error('Should not create.'); + }; + + instance.get(options, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should not auto create unless requested', function(done) { + var error = new Error('Error.'); + error.code = 5; + + instance.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + instance.create = function() { + throw new Error('Should not create.'); + }; + + instance.get(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return an error from getMetadata', function(done) { + var error = new Error('Error.'); + + instance.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + instance.get(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return self and API response', function(done) { + var apiResponse = {}; + + instance.getMetadata = function(gaxOptions, callback) { + callback(null, apiResponse); + }; + + instance.get(function(err, instance_, apiResponse_) { + assert.ifError(err); + assert.strictEqual(instance_, instance); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + }); + + describe('getClusters', function() { + it('should provide the proper request options', function(done) { + instance.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'listClusters'); + assert.strictEqual(config.reqOpts.parent, INSTANCE_ID); + assert.strictEqual(config.gaxOpts, undefined); done(); }; @@ -497,98 +622,135 @@ describe('Bigtable/Instance', function() { }); it('should copy all query options', function(done) { - var fakeOptions = { + var options = { a: 'a', b: 'b', }; - instance.request = function(grpcOpts, reqOpts) { - Object.keys(fakeOptions).forEach(function(key) { - assert.strictEqual(reqOpts[key], fakeOptions[key]); + instance.bigtable.request = function(config) { + Object.keys(options).forEach(function(key) { + assert.strictEqual(config.reqOpts[key], options[key]); }); - - assert.notStrictEqual(reqOpts, fakeOptions); + assert.notStrictEqual(config.reqOpts, options); done(); }; - instance.getClusters(fakeOptions, assert.ifError); + instance.getClusters(options, assert.ifError); }); - it('should return an error to the callback', function(done) { - var error = new Error('err'); - var response = {}; - - instance.request = function(grpcOpts, reqOpts, callback) { - callback(error, response); + it('should accept gaxOptions', function(done) { + var options = { + gaxOptions: {}, }; - instance.getClusters(function(err, clusters, nextQuery, apiResponse) { - assert.strictEqual(err, error); - assert.strictEqual(clusters, null); - assert.strictEqual(nextQuery, null); - assert.strictEqual(apiResponse, response); + instance.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, options.gaxOptions); done(); - }); + }; + + instance.getClusters(options, assert.ifError); }); it('should return an array of cluster objects', function(done) { - var response = { - clusters: [ - { - name: 'a', - }, - { - name: 'b', - }, - ], - }; + var response = [ + { + name: 'a', + }, + { + name: 'b', + }, + ]; var fakeClusters = [{}, {}]; - instance.request = function(grpcOpts, reqOpts, callback) { + instance.bigtable.request = function(config, callback) { callback(null, response); }; var clusterCount = 0; instance.cluster = function(name) { - assert.strictEqual(name, response.clusters[clusterCount].name); + assert.strictEqual(name, response[clusterCount].name); return fakeClusters[clusterCount++]; }; - instance.getClusters(function(err, clusters, nextQuery, apiResponse) { + instance.getClusters(function(err, clusters) { assert.ifError(err); assert.strictEqual(clusters[0], fakeClusters[0]); - assert.strictEqual(clusters[0].metadata, response.clusters[0]); + assert.strictEqual(clusters[0].metadata, response[0]); assert.strictEqual(clusters[1], fakeClusters[1]); - assert.strictEqual(clusters[1].metadata, response.clusters[1]); - assert.strictEqual(nextQuery, null); - assert.strictEqual(apiResponse, response); + assert.strictEqual(clusters[1].metadata, response[1]); done(); }); }); - it('should provide a nextQuery object', function(done) { - var response = { - clusters: [], - nextPageToken: 'a', + it('should return original GAPIC response arguments', function(done) { + var response = [{}, null, {}, {}]; + + instance.bigtable.request = function(config, callback) { + callback.apply(null, response); }; - var options = { - a: 'b', + instance.getClusters(function() { + assert.strictEqual(arguments[0], response[0]); + assert.strictEqual(arguments[2], response[2]); + assert.strictEqual(arguments[3], response[3]); + done(); + }); + }); + }); + + describe('getMetadata', function() { + it('should make correct request', function(done) { + instance.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'getInstance'); + + assert.deepEqual(config.reqOpts, { + name: instance.id, + }); + + assert.deepEqual(config.gaxOpts, {}); + + done(); }; - instance.request = function(grpcOpts, reqOpts, callback) { - callback(null, response); + instance.getMetadata(assert.ifError); + }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + instance.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); }; - instance.getClusters(options, function(err, clusters, nextQuery) { - var expectedQuery = extend({}, options, { - pageToken: response.nextPageToken, - }); + instance.getMetadata(gaxOptions, assert.ifError); + }); - assert.ifError(err); - assert.deepEqual(nextQuery, expectedQuery); + it('should update metadata', function(done) { + var metadata = {}; + + instance.bigtable.request = function(config, callback) { + callback(null, metadata); + }; + + instance.getMetadata(function() { + assert.strictEqual(instance.metadata, metadata); + done(); + }); + }); + + it('should execute callback with original arguments', function(done) { + var args = [{}, {}, {}]; + + instance.bigtable.request = function(config, callback) { + callback.apply(null, args); + }; + + instance.getMetadata(function() { + assert.deepStrictEqual([].slice.call(arguments), args); done(); }); }); @@ -603,27 +765,39 @@ describe('Bigtable/Instance', function() { }); it('should provide the proper request options', function(done) { - instance.request = function(protoOpts, reqOpts) { - assert.deepEqual(protoOpts, { - service: 'BigtableTableAdmin', - method: 'listTables', - }); - assert.strictEqual(reqOpts.parent, INSTANCE_ID); - assert.strictEqual(reqOpts.view, views.unspecified); + instance.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableTableAdminClient'); + assert.strictEqual(config.method, 'listTables'); + assert.strictEqual(config.reqOpts.parent, INSTANCE_ID); + assert.strictEqual(config.reqOpts.view, views.unspecified); + assert.strictEqual(config.gaxOpts, undefined); done(); }; instance.getTables(assert.ifError); }); + it('should accept gaxOptions', function(done) { + var options = { + gaxOptions: {}, + }; + + instance.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, options.gaxOptions); + done(); + }; + + instance.getTables(options, assert.ifError); + }); + Object.keys(views).forEach(function(view) { it('should set the "' + view + '" view', function(done) { var options = { view: view, }; - instance.request = function(protoOpts, reqOpts) { - assert.strictEqual(reqOpts.view, views[view]); + instance.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.view, views[view]); done(); }; @@ -631,79 +805,93 @@ describe('Bigtable/Instance', function() { }); }); - it('should return an error to the callback', function(done) { - var err = new Error('err'); - var response = {}; - - instance.request = function(p, r, callback) { - callback(err, response); - }; - - instance.getTables(function(err_, tables, apiResponse) { - assert.strictEqual(err, err_); - assert.strictEqual(tables, null); - assert.strictEqual(apiResponse, response); - done(); - }); - }); - - it('should return a list of Table objects', function(done) { - var tableName = 'projects/p/zones/z/clusters/c/tables/my-table'; - var fakeFormattedName = 'my-table'; - var fakeTable = {}; + it('should return an array of table objects', function(done) { + var response = [ + { + name: 'a', + }, + { + name: 'b', + }, + ]; - var response = { - tables: [ - { - name: tableName, - }, - ], - }; + var fakeTables = [{}, {}]; - instance.request = function(p, r, callback) { + instance.bigtable.request = function(config, callback) { callback(null, response); }; + var tableCount = 0; + instance.table = function(name) { - assert.strictEqual(name, fakeFormattedName); - return fakeTable; + assert.strictEqual(name, response[tableCount].name); + return fakeTables[tableCount++]; }; - instance.getTables(function(err, tables, nextQuery, apiResponse) { + instance.getTables(function(err, tables) { assert.ifError(err); + assert.strictEqual(tables[0], fakeTables[0]); + assert.strictEqual(tables[0].metadata, response[0]); + assert.strictEqual(tables[1], fakeTables[1]); + assert.strictEqual(tables[1].metadata, response[1]); + done(); + }); + }); - var table = tables[0]; + it('should return original GAPIC response arguments', function(done) { + var response = [{}, null, {}, {}]; - assert.strictEqual(table, fakeTable); - assert.strictEqual(table.metadata, response.tables[0]); - assert.strictEqual(nextQuery, null); - assert.strictEqual(response, apiResponse); + instance.bigtable.request = function(config, callback) { + callback.apply(null, response); + }; + + instance.getTables(function() { + assert.strictEqual(arguments[0], response[0]); + assert.strictEqual(arguments[2], response[2]); + assert.strictEqual(arguments[3], response[3]); done(); }); }); + }); - it('should create a nextQuery object', function(done) { - var response = { - tables: [], - nextPageToken: 'a', - }; + describe('setMetadata', function() { + it('should provide the proper request options', function(done) { + var metadata = {a: 'b'}; + var expectedMetadata = extend({name: instance.id}, metadata); - var options = { - a: 'b', + instance.bigtable.request = function(config, callback) { + assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); + assert.strictEqual(config.method, 'updateInstance'); + assert.deepStrictEqual(config.reqOpts, expectedMetadata); + callback(); // done() }; - instance.request = function(protoOpts, reqOpts, callback) { + instance.setMetadata(metadata, done); + }); + + it('should update metadata property with API response', function(done) { + var response = {}; + + instance.bigtable.request = function(config, callback) { callback(null, response); }; - instance.getTables(options, function(err, tables, nextQuery) { + instance.setMetadata({}, function(err) { assert.ifError(err); + assert.strictEqual(instance.metadata, response); + done(); + }); + }); - var expectedQuery = extend({}, options, { - pageToken: response.nextPageToken, - }); + it('should execute callback with all arguments', function(done) { + var args = [{}, {}, {}]; + + instance.bigtable.request = function(config, callback) { + callback.apply(null, args); + }; - assert.deepEqual(nextQuery, expectedQuery); + instance.setMetadata({}, function() { + assert.deepStrictEqual([].slice.call(arguments), args); done(); }); }); diff --git a/test/row.js b/test/row.js index 7e431d3fc..8b0817a20 100644 --- a/test/row.js +++ b/test/row.js @@ -67,7 +67,7 @@ var FakeFilter = { }), }; -describe.only('Bigtable/Row', function() { +describe('Bigtable/Row', function() { var Row; var row; From 7b4098ced31ce0a5a14d608cc07e9524b9054b93 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 23 Feb 2018 10:31:07 -0500 Subject: [PATCH 17/46] Test table.js --- src/family.js | 19 +- src/instance.js | 18 +- src/table.js | 114 ++++--- test/instance.js | 4 +- test/table.js | 766 ++++++++++++++++++++++++++++++++--------------- 5 files changed, 625 insertions(+), 296 deletions(-) diff --git a/src/family.js b/src/family.js index a3f34eb92..ca4c826b6 100644 --- a/src/family.js +++ b/src/family.js @@ -238,7 +238,7 @@ Family.prototype.exists = function(gaxOptions, callback) { gaxOptions = {}; } - this.getMetadata(gaxOptions, function(err) { + this.getMetadata({gaxOptions}, function(err) { if (err) { if (err.name === 'FamilyError') { callback(null, false); @@ -266,6 +266,9 @@ Family.prototype.exists = function(gaxOptions, callback) { * instance if it does not already exist. * @param {object} [options.gaxOptions] Request configuration options, outlined * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {?error} callback.error An error returned while making this request. + * @param {Family} callback.family The Family object. + * @param {object} callback.apiResponse The resource as it exists in the API. * * @example * family.get(function(err, family, apiResponse) { @@ -291,18 +294,18 @@ Family.prototype.get = function(options, callback) { var autoCreate = !!options.autoCreate; var gaxOptions = options.gaxOptions; - this.getMetadata(gaxOptions, function(err, apiResponse) { + this.getMetadata(gaxOptions, function(err, metadata) { if (err) { if (err.code === 5 && autoCreate) { self.create({gaxOptions}, callback); return; } - callback(err, null, apiResponse); + callback(err); return; } - callback(null, self, apiResponse); + callback(null, self, metadata); }); }; @@ -336,22 +339,22 @@ Family.prototype.getMetadata = function(gaxOptions, callback) { gaxOptions = {}; } - this.table.getFamilies(gaxOptions, function(err, families, resp) { + this.table.getFamilies(gaxOptions, function(err, families) { if (err) { - callback(err, null, resp); + callback(err); return; } for (var i = 0, l = families.length; i < l; i++) { if (families[i].id === self.id) { self.metadata = families[i].metadata; - callback(null, self.metadata, resp); + callback(null, self.metadata); return; } } var error = new FamilyError(self.id); - callback(error, null, resp); + callback(error); }); }; diff --git a/src/instance.js b/src/instance.js index 74c428a00..963010e0a 100644 --- a/src/instance.js +++ b/src/instance.js @@ -458,11 +458,20 @@ Instance.prototype.exists = function(gaxOptions, callback) { /** * Get an instance if it exists. * + * You may optionally use this to "get or create" an object by providing an + * object with `autoCreate` set to `true`. Any extra configuration that is + * normally required for the `create` method must be contained within this + * object as well. + * * @param {object} [options] Configuration object. * @param {boolean} [options.autoCreate=false] Automatically create the * instance if it does not already exist. * @param {object} [options.gaxOptions] Request configuration options, outlined * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {function} callback The callback function. + * @param {?error} callback.error An error returned while making this request. + * @param {Instance} callback.instance The Instance object. + * @param {object} callback.apiResponse The resource as it exists in the API. * * @example * const Bigtable = require('@google-cloud/bigtable'); @@ -492,18 +501,18 @@ Instance.prototype.get = function(options, callback) { var autoCreate = !!options.autoCreate; var gaxOptions = options.gaxOptions; - this.getMetadata(gaxOptions, function(err, apiResponse) { + this.getMetadata(gaxOptions, function(err, metadata) { if (err) { if (err.code === 5 && autoCreate) { self.create({gaxOptions}, callback); return; } - callback(err, null, apiResponse); + callback(err); return; } - callback(null, self, apiResponse); + callback(null, self, metadata); }); }; @@ -632,14 +641,13 @@ Instance.prototype.getClustersStream = common.paginator.streamify( * @param {?error} callback.err An error returned while making this * request. * @param {object} callback.metadata The metadata. - * @param {object} callback.apiResponse The full API response. * * @example * const Bigtable = require('@google-cloud/bigtable'); * const bigtable = new Bigtable(); * const instance = bigtable.instance('my-instance'); * - * instance.getMetadata(function(err, metadata, apiResponse) {}); + * instance.getMetadata(function(err, metadata) {}); * * //- * // If the callback is omitted, we'll return a Promise. diff --git a/src/table.js b/src/table.js index 29afa5df1..e27e5473c 100644 --- a/src/table.js +++ b/src/table.js @@ -268,6 +268,7 @@ Table.prototype.createFamily = function(name, options, callback) { var family = self.family(resp.name); family.metadata = resp; + callback(null, family, resp); } ); @@ -424,8 +425,8 @@ Table.prototype.createReadStream = function(options) { var reqOpts = { tableName: this.id, - objectMode: true, }; + if (lastRowKey) { const lessThan = (lhs, rhs) => { const lhsBytes = Mutation.convertToBytes(lhs); @@ -480,15 +481,18 @@ Table.prototype.createReadStream = function(options) { } if (rowKeys || ranges.length) { reqOpts.rows = {}; + if (rowKeys) { reqOpts.rows.rowKeys = rowKeys.map(Mutation.convertToBytes); } + if (ranges.length) { reqOpts.rows.rowRanges = ranges.map(function(range) { return Filter.createRange(range.start, range.end, 'Key'); }); } } + if (filter) { reqOpts.filter = filter; } @@ -503,6 +507,7 @@ Table.prototype.createReadStream = function(options) { reqOpts: reqOpts, gaxOpts: options.gaxOptions, }); + requestStream.on('request', () => numRequestsMade++); const rowStream = pumpify.obj([ @@ -673,17 +678,17 @@ Table.prototype.exists = function(gaxOptions, callback) { } this.getMetadata(gaxOptions, function(err) { - if (!err) { - callback(null, true); - return; - } + if (err) { + if (err.code === 5) { + callback(null, false); + return; + } - if (err.code === 5) { - callback(null, false); + callback(err); return; } - callback(err); + callback(null, true); }); }; @@ -714,12 +719,14 @@ Table.prototype.family = function(name) { * normally required for the `create` method must be contained within this * object as well. * - * @param {object} [gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. - * @param {boolean} [gaxOptions.autoCreate=false] Automatically create the + * @param {object} [options] Configuration object. + * @param {boolean} [options.autoCreate=false] Automatically create the * instance if it does not already exist. - * @param {string} [gaxOptions.view] The view to be applied to the table fields. - * See {@link Table#getMetadata}. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {?error} callback.error An error returned while making this request. + * @param {Table} callback.table The Table object. + * @param {object} callback.apiResponse The resource as it exists in the API. * * @example * table.get(function(err, table, apiResponse) { @@ -734,29 +741,29 @@ Table.prototype.family = function(name) { * var apiResponse = data[0]; * }); */ -Table.prototype.get = function(gaxOptions, callback) { +Table.prototype.get = function(options, callback) { var self = this; - if (is.fn(gaxOptions)) { - callback = gaxOptions; - gaxOptions = {}; + if (is.fn(options)) { + callback = options; + options = {}; } - var autoCreate = !!gaxOptions.autoCreate; - delete gaxOptions.autoCreate; + var autoCreate = !!options.autoCreate; + var gaxOptions = options.gaxOptions; - this.getMetadata(gaxOptions, function(err, apiResponse) { - if (!err) { - callback(null, self, apiResponse); - return; - } + this.getMetadata(gaxOptions, function(err, metadata) { + if (err) { + if (err.code === 5 && autoCreate) { + self.create({gaxOptions}, callback); + return; + } - if (err.code !== 5 || !autoCreate) { - callback(err, null, apiResponse); + callback(err); return; } - self.create({gaxOptions}, callback); + callback(null, self, metadata); }); }; @@ -796,19 +803,19 @@ Table.prototype.getFamilies = function(gaxOptions, callback) { gaxOptions = {}; } - this.getMetadata({gaxOptions}, function(err, resp) { + this.getMetadata({gaxOptions}, function(err, metadata) { if (err) { - callback(err, null, resp); + callback(err); return; } - var families = Object.keys(resp.columnFamilies).map(function(familyId) { + var families = Object.keys(metadata.columnFamilies).map(function(familyId) { var family = self.family(familyId); - family.metadata = resp.columnFamilies[familyId]; + family.metadata = metadata.columnFamilies[familyId]; return family; }); - callback(null, families, resp); + callback(null, families, metadata.columnFamilies); }); }; @@ -821,7 +828,6 @@ Table.prototype.getFamilies = function(gaxOptions, callback) { * @param {?error} callback.err An error returned while making this * request. * @param {object} callback.metadata The table's metadata. - * @param {object} callback.apiResponse The full API response. * * @example * const Bigtable = require('@google-cloud/bigtable'); @@ -829,7 +835,7 @@ Table.prototype.getFamilies = function(gaxOptions, callback) { * const instance = bigtable.instance('my-instance'); * const table = instance.table('prezzy'); * - * table.getMetadata(function(err, metadata, apiResponse) {}); + * table.getMetadata(function(err, metadata) {}); * * //- * // If the callback is omitted, we'll return a Promise. @@ -859,14 +865,12 @@ Table.prototype.getMetadata = function(options, callback) { reqOpts: reqOpts, gaxOpts: options.gaxOptions, }, - function(err, resp) { - if (err) { - callback(err, null, resp); - return; + function() { + if (arguments[1]) { + self.metadata = arguments[1]; } - self.metadata = resp; - callback(null, self.metadata, resp); + callback.apply(null, arguments); } ); }; @@ -880,6 +884,8 @@ Table.prototype.getMetadata = function(options, callback) { * * @param {object} [options] Configuration object. See * {@link Table#createReadStream} for a complete list of options. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this request. * @param {Row[]} callback.rows List of Row objects. @@ -925,6 +931,8 @@ Table.prototype.getRows = function(options, callback) { * * @param {object|object[]} entries List of entries to be inserted. * See {@link Table#mutate}. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this request. * @param {object[]} callback.err.errors If present, these represent partial @@ -992,10 +1000,15 @@ Table.prototype.getRows = function(options, callback) { * }); * //- */ -Table.prototype.insert = function(entries, callback) { +Table.prototype.insert = function(entries, gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + entries = arrify(entries).map(propAssign('method', Mutation.methods.INSERT)); - return this.mutate(entries, callback); + return this.mutate(entries, gaxOptions, callback); }; /** @@ -1005,6 +1018,8 @@ Table.prototype.insert = function(entries, callback) { * * @param {object|object[]} entries List of entities to be inserted or * deleted. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.err An error returned while making this request. * @param {object[]} callback.err.errors If present, these represent partial @@ -1155,7 +1170,6 @@ Table.prototype.mutate = function(entries, gaxOptions, callback) { }); var reqOpts = { - objectMode: true, tableName: self.id, entries: entryBatch.map(Mutation.parse), }; @@ -1221,6 +1235,8 @@ Table.prototype.row = function(key) { * contigous sections of the table of approximately equal size, which can be * used to break up the data for distributed tasks like mapreduces. * + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} [callback] The callback function. * @param {?error} callback.err An error returned while making this request. * @param {object[]} callback.keys The list of keys. @@ -1243,8 +1259,13 @@ Table.prototype.row = function(key) { * var keys = data[0]; * }); */ -Table.prototype.sampleRowKeys = function(callback) { - this.sampleRowKeysStream() +Table.prototype.sampleRowKeys = function(gaxOptions, callback) { + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; + } + + this.sampleRowKeysStream(gaxOptions) .on('error', callback) .pipe( concat(function(keys) { @@ -1258,6 +1279,8 @@ Table.prototype.sampleRowKeys = function(callback) { * * See {@link Table#sampleRowKeys} for more details. * + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @returns {stream} * * @example @@ -1279,7 +1302,6 @@ Table.prototype.sampleRowKeys = function(callback) { Table.prototype.sampleRowKeysStream = function(gaxOptions) { var reqOpts = { tableName: this.id, - objectMode: true, }; return pumpify.obj([ diff --git a/test/instance.js b/test/instance.js index db2b76724..4f50d8d5d 100644 --- a/test/instance.js +++ b/test/instance.js @@ -62,7 +62,7 @@ var FakeCluster = createFake(Cluster); var FakeFamily = createFake(Family); var FakeTable = createFake(Table); -describe.only('Bigtable/Instance', function() { +describe('Bigtable/Instance', function() { var INSTANCE_NAME = 'my-instance'; var BIGTABLE = {projectName: 'projects/my-project'}; @@ -129,7 +129,7 @@ describe.only('Bigtable/Instance', function() { }); describe('create', function() { - it('should call createInstance from instance', function(done) { + it('should call createInstance from bigtable', function(done) { var options = {}; instance.bigtable.createInstance = function(name, options_, callback) { diff --git a/test/table.js b/test/table.js index 79d4307df..90dc9c31a 100644 --- a/test/table.js +++ b/test/table.js @@ -55,7 +55,6 @@ function createFake(Class) { } var FakeGrpcService = createFake(commonGrpc.Service); -var FakeGrpcServiceObject = createFake(commonGrpc.ServiceObject); var FakeFamily = createFake(Family); FakeFamily.formatRule_ = sinon.spy(function(rule) { @@ -96,6 +95,7 @@ var FakeFilter = { describe('Bigtable/Table', function() { var TABLE_ID = 'my-table'; var INSTANCE = { + bigtable: {}, id: 'a/b/c/d', }; @@ -111,7 +111,6 @@ describe('Bigtable/Table', function() { }, '@google-cloud/common-grpc': { Service: FakeGrpcService, - ServiceObject: FakeGrpcServiceObject, }, './family.js': FakeFamily, './mutation.js': FakeMutation, @@ -127,62 +126,34 @@ describe('Bigtable/Table', function() { }); afterEach(function() { - sinon.restore(); - Object.keys(FakeMutation).forEach(function(spy) { if (FakeMutation[spy].reset) { - FakeMutation[spy].reset(); + FakeMutation[spy].resetHistory(); } }); - FakeFilter.parse.reset(); + FakeFilter.parse.resetHistory(); }); describe('instantiation', function() { - it('should inherit from GrpcServiceObject', function() { - var FAKE_TABLE_NAME = 'fake-table-name'; - - sinon.stub(Table, 'formatName_').returns(FAKE_TABLE_NAME); - - var table = new Table(INSTANCE, TABLE_ID); - var config = table.calledWith_[0]; - - assert(table instanceof FakeGrpcServiceObject); - assert.strictEqual(config.parent, INSTANCE); - assert.strictEqual(config.id, FAKE_TABLE_NAME); - - assert.deepEqual(config.methods, { - create: true, - delete: { - protoOpts: { - service: 'BigtableTableAdmin', - method: 'deleteTable', - }, - reqOpts: { - name: FAKE_TABLE_NAME, - }, - }, - exists: true, - get: true, - }); - - assert(Table.formatName_.calledWith(INSTANCE.id, TABLE_ID)); - }); - it('should promisify all the things', function() { assert(promisified); }); - it('should use Instance#createTable to create the table', function(done) { - var fakeOptions = {}; + it('should localize Bigtable instance', function() { + assert.strictEqual(table.bigtable, INSTANCE.bigtable); + }); - INSTANCE.createTable = function(name, options, callback) { - assert.strictEqual(name, TABLE_ID); - assert.strictEqual(options, fakeOptions); - callback(); - }; + it('should localize Instance instance', function() { + assert.strictEqual(table.instance, INSTANCE); + }); - table.createMethod(null, fakeOptions, done); + it('should localize ID', function() { + assert.strictEqual(table.id, TABLE_NAME); + }); + + it('should localize table name', function() { + assert.strictEqual(table.name, TABLE_ID); }); }); @@ -211,6 +182,29 @@ describe('Bigtable/Table', function() { }); }); + describe('create', function() { + it('should call createTable from instance', function(done) { + var options = {}; + + table.instance.createTable = function(name, options_, callback) { + assert.strictEqual(name, table.name); + assert.strictEqual(options_, options); + callback(); // done() + }; + + table.create(options, done); + }); + + it('should not require options', function(done) { + table.instance.createTable = function(name, options, callback) { + assert.deepStrictEqual(options, {}); + callback(); // done() + }; + + table.create(done); + }); + }); + describe('createPrefixRange_', function() { it('should create a range from the prefix', function() { assert.deepEqual(Table.createPrefixRange_('start'), { @@ -283,26 +277,37 @@ describe('Bigtable/Table', function() { }); it('should provide the proper request options', function(done) { - table.request = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'BigtableTableAdmin', - method: 'modifyColumnFamilies', - }); + table.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableTableAdminClient'); + assert.strictEqual(config.method, 'modifyColumnFamilies'); - assert.strictEqual(reqOpts.name, TABLE_NAME); - assert.deepEqual(reqOpts.modifications, [ + assert.strictEqual(config.reqOpts.name, TABLE_NAME); + assert.deepEqual(config.reqOpts.modifications, [ { id: COLUMN_ID, create: {}, }, ]); + assert.strictEqual(config.gaxOpts, undefined); + done(); }; table.createFamily(COLUMN_ID, assert.ifError); }); + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + table.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); + }; + + table.createFamily(COLUMN_ID, {gaxOptions}, assert.ifError); + }); + it('should respect the gc rule option', function(done) { var rule = { a: 'a', @@ -317,8 +322,8 @@ describe('Bigtable/Table', function() { return convertedRule; })); - table.request = function(g, reqOpts) { - var modification = reqOpts.modifications[0]; + table.bigtable.request = function(config) { + var modification = config.reqOpts.modifications[0]; assert.strictEqual(modification.create.gcRule, convertedRule); assert.strictEqual(spy.callCount, 1); @@ -326,14 +331,14 @@ describe('Bigtable/Table', function() { done(); }; - table.createFamily(COLUMN_ID, rule, assert.ifError); + table.createFamily(COLUMN_ID, {rule}, assert.ifError); }); it('should return an error to the callback', function(done) { var error = new Error('err'); var response = {}; - table.request = function(g, r, callback) { + table.bigtable.request = function(config, callback) { callback(error, response); }; @@ -351,7 +356,7 @@ describe('Bigtable/Table', function() { }; var fakeFamily = {}; - table.request = function(g, r, callback) { + table.bigtable.request = function(config, callback) { callback(null, response); }; @@ -371,23 +376,28 @@ describe('Bigtable/Table', function() { }); describe('createReadStream', function() { + it('should provide the proper request options', function(done) { + table.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableClient'); + assert.strictEqual(config.method, 'readRows'); + assert.strictEqual(config.reqOpts.tableName, TABLE_NAME); + assert.strictEqual(config.gaxOpts, undefined); + done(); + }; + + table.createReadStream(); + }); + describe('options', function() { - it('should provide the proper request options', function(done) { - table.requestStream = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'Bigtable', - method: 'readRows', - retryOpts: { - currentRetryAttempt: 0, - }, - }); + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; - assert.strictEqual(reqOpts.tableName, TABLE_NAME); - assert.strictEqual(reqOpts.objectMode, true); + table.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); done(); }; - table.createReadStream(); + table.createReadStream({gaxOptions}); }); it('should retrieve a range of rows', function(done) { @@ -405,8 +415,8 @@ describe('Bigtable/Table', function() { return fakeRange; })); - table.requestStream = function(g, reqOpts) { - assert.deepEqual(reqOpts.rows.rowRanges[0], fakeRange); + table.bigtable.request = function(config) { + assert.deepEqual(config.reqOpts.rows.rowRanges[0], fakeRange); assert.strictEqual(formatSpy.callCount, 1); assert.deepEqual(formatSpy.getCall(0).args, [ options.start, @@ -432,8 +442,8 @@ describe('Bigtable/Table', function() { return convertedKeys[keyIndex]; })); - table.requestStream = function(g, reqOpts) { - assert.deepEqual(reqOpts.rows.rowKeys, convertedKeys); + table.bigtable.request = function(config) { + assert.deepEqual(config.reqOpts.rows.rowKeys, convertedKeys); assert.strictEqual(convertSpy.callCount, 2); assert.strictEqual(convertSpy.getCall(0).args[0], options.keys[0]); assert.strictEqual(convertSpy.getCall(1).args[0], options.keys[1]); @@ -472,8 +482,8 @@ describe('Bigtable/Table', function() { return fakeRanges[formatSpy.callCount - 1]; })); - table.requestStream = function(g, reqOpts) { - assert.deepEqual(reqOpts.rows.rowRanges, fakeRanges); + table.bigtable.request = function(config) { + assert.deepEqual(config.reqOpts.rows.rowRanges, fakeRanges); assert.strictEqual(formatSpy.callCount, 2); assert.deepEqual(formatSpy.getCall(0).args, [ options.ranges[0].start, @@ -502,8 +512,8 @@ describe('Bigtable/Table', function() { return fakeFilter; })); - table.requestStream = function(g, reqOpts) { - assert.strictEqual(reqOpts.filter, fakeFilter); + table.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.filter, fakeFilter); assert.strictEqual(parseSpy.callCount, 1); assert.strictEqual(parseSpy.getCall(0).args[0], options.filter); done(); @@ -517,8 +527,8 @@ describe('Bigtable/Table', function() { limit: 10, }; - table.requestStream = function(g, reqOpts) { - assert.strictEqual(reqOpts.rowsLimit, options.limit); + table.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.rowsLimit, options.limit); done(); }; @@ -542,9 +552,9 @@ describe('Bigtable/Table', function() { return fakeRange; })); - table.requestStream = function(g, reqOpts) { + table.bigtable.request = function(config) { assert.strictEqual(prefixSpy.getCall(0).args[0], fakePrefix); - assert.deepEqual(reqOpts.rows.rowRanges, [fakeRange]); + assert.deepEqual(config.reqOpts.rows.rowRanges, [fakeRange]); assert.deepEqual(rangeSpy.getCall(0).args, [ fakePrefixRange.start, @@ -595,7 +605,7 @@ describe('Bigtable/Table', function() { cb(); }; - table.requestStream = function() { + table.bigtable.request = function() { var stream = new Stream({ objectMode: true, }); @@ -671,7 +681,7 @@ describe('Bigtable/Table', function() { }; // beforeEach(function() { - // table.requestStream = function() { + // table.bigtable.request = function() { // var stream = new Stream({ // objectMode: true, // }); @@ -685,7 +695,7 @@ describe('Bigtable/Table', function() { // }); it('should emit an error event', function(done) { - table.requestStream = function() { + table.bigtable.request = function() { var stream = new Stream({ objectMode: true, }); @@ -705,7 +715,7 @@ describe('Bigtable/Table', function() { .on('data', done); }); it('should emit an error event when chunk format returns error', function(done) { - table.requestStream = function() { + table.bigtable.request = function() { var stream = new Stream({ objectMode: true, }); @@ -733,7 +743,7 @@ describe('Bigtable/Table', function() { .on('data', done); }); it('should emit an error event when chunktransformer returns error on flush end', function(done) { - table.requestStream = function() { + table.bigtable.request = function() { var stream = new Stream({ objectMode: true, }); @@ -763,27 +773,33 @@ describe('Bigtable/Table', function() { var makeRetryableError; var reqOptsCalls; var setTimeoutSpy; + beforeEach(function() { FakeChunkTransformer.prototype._transform = function(rows, enc, next) { rows.forEach(row => this.push(row)); this.lastRowKey = rows[rows.length - 1].key; next(); }; + FakeChunkTransformer.prototype._flush = function(cb) { cb(); }; + callCreateReadStream = (options, verify) => { table .createReadStream(options) .on('end', verify) .resume(); // The stream starts paused unless it has a `.data()` callback. }; + emitters = null; // This needs to be assigned in each test case. + makeRetryableError = () => { var error = new Error('retry me!'); error.code = 409; return error; }; + FakeFilter.createRange = function(start, end) { var range = {}; if (start) { @@ -796,13 +812,18 @@ describe('Bigtable/Table', function() { } return range; }; + FakeMutation.convertToBytes = function(value) { return Buffer.from(value); }; + reqOptsCalls = []; + setTimeoutSpy = sinon.stub(global, 'setTimeout').callsFake(fn => fn()); - table.requestStream = function(_, reqOpts) { - reqOptsCalls.push(reqOpts); + + table.bigtable.request = function(config) { + reqOptsCalls.push(config.reqOpts); + var stream = new Stream({ objectMode: true, }); @@ -815,6 +836,7 @@ describe('Bigtable/Table', function() { return stream; }; }); + afterEach(function() { setTimeoutSpy.restore(); }); @@ -845,6 +867,7 @@ describe('Bigtable/Table', function() { stream.end(); }, ]; + callCreateReadStream(null, () => { assert.strictEqual(reqOptsCalls[0].rows, undefined); assert.deepStrictEqual(reqOptsCalls[1].rows, { @@ -864,6 +887,7 @@ describe('Bigtable/Table', function() { stream.end(); }, ]; + callCreateReadStream({ranges: [{start: 'a'}]}, () => { assert.deepStrictEqual(reqOptsCalls[0].rows, { rowRanges: [{start: 'a', startInclusive: true}], @@ -887,22 +911,24 @@ describe('Bigtable/Table', function() { stream.end(); }, ]; - callCreateReadStream( - {ranges: [{start: 'a', end: 'b'}, {start: 'c'}]}, - () => { - var allRanges = [ - {start: 'a', end: 'b', startInclusive: true}, - {start: 'c', startInclusive: true}, - ]; - assert.deepStrictEqual(reqOptsCalls[0].rows, { - rowRanges: allRanges, - }); - assert.deepStrictEqual(reqOptsCalls[1].rows, { - rowRanges: allRanges.slice(1), - }); - done(); - } - ); + + var options = { + ranges: [{start: 'a', end: 'b'}, {start: 'c'}], + }; + + callCreateReadStream(options, () => { + var allRanges = [ + {start: 'a', end: 'b', startInclusive: true}, + {start: 'c', startInclusive: true}, + ]; + assert.deepStrictEqual(reqOptsCalls[0].rows, { + rowRanges: allRanges, + }); + assert.deepStrictEqual(reqOptsCalls[1].rows, { + rowRanges: allRanges.slice(1), + }); + done(); + }); }); it('should remove the keys which were already read', function(done) { @@ -915,6 +941,7 @@ describe('Bigtable/Table', function() { stream.end([{key: 'c'}]); }, ]; + callCreateReadStream({keys: ['a', 'b']}, () => { assert.strictEqual(reqOptsCalls[0].rows.rowKeys.length, 2); assert.strictEqual(reqOptsCalls[1].rows.rowKeys.length, 1); @@ -933,6 +960,7 @@ describe('Bigtable/Table', function() { stream.end(); }, ]; + callCreateReadStream({keys: ['a']}, () => { assert.strictEqual(reqOptsCalls[0].rows.rowKeys.length, 1); assert.strictEqual(reqOptsCalls[1].rows.rowKeys, undefined); @@ -942,21 +970,62 @@ describe('Bigtable/Table', function() { }); }); - describe('deleteRows', function() { - it('should provide the proper request options', function(done) { - table.request = function(grpcOpts, reqOpts, callback) { - assert.deepEqual(grpcOpts, { - service: 'BigtableTableAdmin', - method: 'dropRowRange', + describe('delete', function() { + it('should make the correct request', function(done) { + table.bigtable.request = function(config, callback) { + assert.strictEqual(config.client, 'BigtableTableAdminClient'); + assert.strictEqual(config.method, 'deleteTable'); + + assert.deepEqual(config.reqOpts, { + name: table.id, }); - assert.strictEqual(reqOpts.name, TABLE_NAME); - callback(); + assert.deepEqual(config.gaxOpts, {}); + + callback(); // done() + }; + + table.delete(done); + }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + table.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + done(); + }; + + table.delete(gaxOptions, assert.ifError); + }); + }); + + describe('deleteRows', function() { + it('should provide the proper request options', function(done) { + table.bigtable.request = function(config, callback) { + assert.strictEqual(config.client, 'BigtableTableAdminClient'); + assert.strictEqual(config.method, 'dropRowRange'); + assert.strictEqual(config.reqOpts.name, TABLE_NAME); + assert.strictEqual(config.gaxOpts, undefined); + callback(); // done() }; table.deleteRows(done); }); + it('should accept gaxOptions', function(done) { + var options = { + gaxOptions: {}, + }; + + table.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, options.gaxOptions); + done(); + }; + + table.deleteRows(options, assert.ifError); + }); + it('should respect the row key prefix option', function(done) { var options = { prefix: 'a', @@ -967,24 +1036,86 @@ describe('Bigtable/Table', function() { return fakePrefix; })); - table.request = function(g, reqOpts, callback) { - assert.strictEqual(reqOpts.rowKeyPrefix, fakePrefix); - + table.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.rowKeyPrefix, fakePrefix); assert.strictEqual(spy.callCount, 1); assert.strictEqual(spy.getCall(0).args[0], options.prefix); - callback(); + done(); }; - table.deleteRows(options, done); + table.deleteRows(options, assert.ifError); }); it('should delete all data when no options are provided', function(done) { - table.request = function(g, reqOpts, callback) { - assert.strictEqual(reqOpts.deleteAllDataFromTable, true); - callback(); + table.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.deleteAllDataFromTable, true); + done(); }; - table.deleteRows(done); + table.deleteRows(assert.ifError); + }); + }); + + describe('exists', function() { + it('should not require gaxOptions', function(done) { + table.getMetadata = function(gaxOptions) { + assert.deepStrictEqual(gaxOptions, {}); + done(); + }; + + table.exists(assert.ifError); + }); + + it('should pass gaxOptions to getMetadata', function(done) { + var gaxOptions = {}; + + table.getMetadata = function(gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + table.exists(gaxOptions, assert.ifError); + }); + + it('should return false if error code is 5', function(done) { + var error = new Error('Error.'); + error.code = 5; + + table.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + table.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, false); + done(); + }); + }); + + it('should return error if code is not 5', function(done) { + var error = new Error('Error.'); + error.code = 'NOT-5'; + + table.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + table.exists(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return true if no error', function(done) { + table.getMetadata = function(gaxOptions, callback) { + callback(null, {}); + }; + + table.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, true); + done(); + }); }); }); @@ -1006,19 +1137,141 @@ describe('Bigtable/Table', function() { }); }); + describe('get', function() { + it('should call getMetadata', function(done) { + var options = { + gaxOptions: {}, + }; + + table.getMetadata = function(gaxOptions) { + assert.strictEqual(gaxOptions, options.gaxOptions); + done(); + }; + + table.get(options, assert.ifError); + }); + + it('should not require an options object', function(done) { + table.getMetadata = function(gaxOptions) { + assert.deepStrictEqual(gaxOptions, undefined); + done(); + }; + + table.get(assert.ifError); + }); + + it('should auto create with error code 5', function(done) { + var error = new Error('Error.'); + error.code = 5; + + var options = { + autoCreate: true, + gaxOptions: {}, + }; + + table.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + table.create = function(options_, callback) { + assert.strictEqual(options_.gaxOptions, options.gaxOptions); + callback(); // done() + }; + + table.get(options, done); + }); + + it('should not auto create without error code 5', function(done) { + var error = new Error('Error.'); + error.code = 'NOT-5'; + + var options = { + autoCreate: true, + }; + + table.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + table.create = function() { + throw new Error('Should not create.'); + }; + + table.get(options, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should not auto create unless requested', function(done) { + var error = new Error('Error.'); + error.code = 5; + + table.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + table.create = function() { + throw new Error('Should not create.'); + }; + + table.get(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return an error from getMetadata', function(done) { + var error = new Error('Error.'); + + table.getMetadata = function(gaxOptions, callback) { + callback(error); + }; + + table.get(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return self and API response', function(done) { + var apiResponse = {}; + + table.getMetadata = function(gaxOptions, callback) { + callback(null, apiResponse); + }; + + table.get(function(err, table_, apiResponse_) { + assert.ifError(err); + assert.strictEqual(table_, table); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + }); + describe('getFamilies', function() { + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + table.getMetadata = function(options) { + assert.strictEqual(options.gaxOptions, gaxOptions); + done(); + }; + + table.getFamilies(gaxOptions, assert.ifError); + }); + it('should return an error to the callback', function(done) { var error = new Error('err'); var response = {}; - table.getMetadata = function(callback) { + table.getMetadata = function(options, callback) { callback(error, response); }; - table.getFamilies(function(err, families, apiResponse) { + table.getFamilies(function(err) { assert.strictEqual(err, error); - assert.strictEqual(families, null); - assert.strictEqual(response, apiResponse); done(); }); }); @@ -1036,7 +1289,7 @@ describe('Bigtable/Table', function() { var fakeFamily = {}; - table.getMetadata = function(callback) { + table.getMetadata = function(options, callback) { callback(null, response); }; @@ -1051,7 +1304,9 @@ describe('Bigtable/Table', function() { var family = families[0]; assert.strictEqual(family, fakeFamily); assert.strictEqual(family.metadata, metadata); - assert.strictEqual(response, apiResponse); + + assert.strictEqual(apiResponse, response.columnFamilies); + done(); }); }); @@ -1069,28 +1324,42 @@ describe('Bigtable/Table', function() { }); it('should provide the proper request options', function(done) { - table.request = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'BigtableTableAdmin', - method: 'getTable', - }); + table.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableTableAdminClient'); + assert.strictEqual(config.method, 'getTable'); + + assert.strictEqual(config.reqOpts.name, table.id); + assert.strictEqual(config.reqOpts.view, views.unspecified); + + assert.strictEqual(config.gaxOpts, undefined); - assert.strictEqual(reqOpts.name, table.id); - assert.strictEqual(reqOpts.view, views.unspecified); done(); }; table.getMetadata(assert.ifError); }); + it('should accept gaxOptions', function(done) { + var options = { + gaxOptions: {}, + }; + + table.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, options.gaxOptions); + done(); + }; + + table.getMetadata(options, assert.ifError); + }); + Object.keys(views).forEach(function(view) { it('should set the "' + view + '" view', function(done) { var options = { view: view, }; - table.request = function(grpcOpts, reqOpts) { - assert.strictEqual(reqOpts.view, views[view]); + table.bigtable.request = function(config) { + assert.strictEqual(config.reqOpts.view, views[view]); done(); }; @@ -1098,34 +1367,30 @@ describe('Bigtable/Table', function() { }); }); - it('should return an error to the callback', function(done) { - var error = new Error('err'); + it('should update the metadata', function(done) { var response = {}; - table.request = function(grpcOpts, reqOpts, callback) { - callback(error, response); + table.bigtable.request = function(config, callback) { + callback(null, response); }; - table.getMetadata(function(err, metadata, apiResponse) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse, response); + table.getMetadata(function(err, metadata) { + assert.ifError(err); + assert.strictEqual(metadata, response); + assert.strictEqual(table.metadata, response); done(); }); }); - it('should update the metadata', function(done) { - var response = {}; + it('should execute callback with original arguments', function(done) { + var args = [{}, {}, {}]; - table.request = function(grpcOpts, reqOpts, callback) { - callback(null, response); + table.bigtable.request = function(config, callback) { + callback.apply(null, args); }; - table.getMetadata(function(err, metadata, apiResponse) { - assert.ifError(err); - assert.strictEqual(metadata, response); - assert.strictEqual(apiResponse, response); - assert.strictEqual(table.metadata, response); + table.getMetadata(function() { + assert.deepStrictEqual([].slice.call(arguments), args); done(); }); }); @@ -1214,7 +1479,7 @@ describe('Bigtable/Table', function() { }, ]; - table.mutate = function(entries, callback) { + table.mutate = function(entries, gaxOptions, callback) { assert.deepEqual(entries[0], { key: fakeEntries[0].key, data: fakeEntries[0].data, @@ -1232,6 +1497,17 @@ describe('Bigtable/Table', function() { table.insert(fakeEntries, done); }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + table.mutate = function(entries, gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); + }; + + table.insert([], gaxOptions, assert.ifError); + }); }); describe('mutate', function() { @@ -1249,17 +1525,12 @@ describe('Bigtable/Table', function() { it('should provide the proper request options', function(done) { var stream = through.obj(); - table.requestStream = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'Bigtable', - method: 'mutateRows', - retryOpts: { - currentRetryAttempt: 0, - }, - }); + table.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableClient'); + assert.strictEqual(config.method, 'mutateRows'); - assert.strictEqual(reqOpts.tableName, TABLE_NAME); - assert.deepEqual(reqOpts.entries, fakeEntries); + assert.strictEqual(config.reqOpts.tableName, TABLE_NAME); + assert.deepEqual(config.reqOpts.entries, fakeEntries); assert.strictEqual(parseSpy.callCount, 2); assert.strictEqual(parseSpy.getCall(0).args[0], entries[0]); @@ -1278,7 +1549,7 @@ describe('Bigtable/Table', function() { var error = new Error('err'); beforeEach(function() { - table.requestStream = function() { + table.bigtable.request = function() { var stream = new Stream({ objectMode: true, }); @@ -1320,7 +1591,7 @@ describe('Bigtable/Table', function() { var parsedStatuses = [{}, {}]; beforeEach(function() { - table.requestStream = function() { + table.bigtable.request = function() { var stream = through.obj(); stream.push({entries: fakeStatuses}); @@ -1380,7 +1651,7 @@ describe('Bigtable/Table', function() { ]; beforeEach(function() { - table.requestStream = function() { + table.bigtable.request = function() { var stream = new Stream({ objectMode: true, }); @@ -1433,8 +1704,8 @@ describe('Bigtable/Table', function() { FakeGrpcService.decorateStatus_ = function() { return {}; }; - table.requestStream = function(_, reqOpts) { - entryRequests.push(reqOpts.entries); + table.bigtable.request = function(config) { + entryRequests.push(config.reqOpts.entries); var stream = new Stream({ objectMode: true, }); @@ -1505,40 +1776,32 @@ describe('Bigtable/Table', function() { }); }); - describe('sampleRowKeysStream', function() { - it('should provide the proper request options', function(done) { - table.requestStream = function(grpcOpts, reqOpts) { - assert.deepEqual(grpcOpts, { - service: 'Bigtable', - method: 'sampleRowKeys', - }); - - assert.strictEqual(reqOpts.tableName, TABLE_NAME); - assert.strictEqual(reqOpts.objectMode, true); - setImmediate(done); + describe('sampleRowKeys', function() { + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; - return new Stream({ - objectMode: true, - }); + table.sampleRowKeysStream = function(gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); + done(); }; - table.sampleRowKeysStream(); + table.sampleRowKeys(gaxOptions); }); describe('success', function() { var fakeKeys = [ { - rowKey: 'a', - offsetBytes: 10, + key: 'a', + offset: 10, }, { - rowKey: 'b', - offsetByte: 20, + key: 'b', + offset: 20, }, ]; beforeEach(function() { - table.requestStream = function() { + table.sampleRowKeysStream = sinon.spy(function() { var stream = new Stream({ objectMode: true, }); @@ -1552,25 +1815,15 @@ describe('Bigtable/Table', function() { }); return stream; - }; + }); }); - it('should stream key objects', function(done) { - var keys = []; - - table - .sampleRowKeysStream() - .on('error', done) - .on('data', function(key) { - keys.push(key); - }) - .on('end', function() { - assert.strictEqual(keys[0].key, fakeKeys[0].rowKey); - assert.strictEqual(keys[0].offset, fakeKeys[0].offsetBytes); - assert.strictEqual(keys[1].key, fakeKeys[1].rowKey); - assert.strictEqual(keys[1].offset, fakeKeys[1].offsetBytes); - done(); - }); + it('should return the keys to the callback', function(done) { + table.sampleRowKeys(function(err, keys) { + assert.ifError(err); + assert.deepEqual(keys, fakeKeys); + done(); + }); }); }); @@ -1578,7 +1831,7 @@ describe('Bigtable/Table', function() { var error = new Error('err'); beforeEach(function() { - table.requestStream = function() { + table.sampleRowKeysStream = sinon.spy(function() { var stream = new Stream({ objectMode: true, }); @@ -1588,36 +1841,66 @@ describe('Bigtable/Table', function() { }); return stream; - }; + }); }); - it('should emit an error event', function(done) { - table - .sampleRowKeysStream() - .on('error', function(err) { - assert.strictEqual(err, error); - done(); - }) - .on('data', done); + it('should return the error to the callback', function(done) { + table.sampleRowKeys(function(err) { + assert.strictEqual(err, error); + done(); + }); }); }); }); - describe('sampleRowKeys', function() { + describe('sampleRowKeysStream', function() { + it('should provide the proper request options', function(done) { + table.bigtable.request = function(config) { + assert.strictEqual(config.client, 'BigtableClient'); + assert.strictEqual(config.method, 'sampleRowKeys'); + assert.strictEqual(config.reqOpts.tableName, TABLE_NAME); + assert.strictEqual(config.gaxOpts, undefined); + + setImmediate(done); + + return new Stream({ + objectMode: true, + }); + }; + + table.sampleRowKeysStream(); + }); + + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; + + table.bigtable.request = function(config) { + assert.strictEqual(config.gaxOpts, gaxOptions); + + setImmediate(done); + + return new Stream({ + objectMode: true, + }); + }; + + table.sampleRowKeysStream(gaxOptions); + }); + describe('success', function() { var fakeKeys = [ { - key: 'a', - offset: 10, + rowKey: 'a', + offsetBytes: 10, }, { - key: 'b', - offset: 20, + rowKey: 'b', + offsetByte: 20, }, ]; beforeEach(function() { - table.sampleRowKeysStream = sinon.spy(function() { + table.bigtable.request = function() { var stream = new Stream({ objectMode: true, }); @@ -1631,15 +1914,25 @@ describe('Bigtable/Table', function() { }); return stream; - }); + }; }); - it('should return the keys to the callback', function(done) { - table.sampleRowKeys(function(err, keys) { - assert.ifError(err); - assert.deepEqual(keys, fakeKeys); - done(); - }); + it('should stream key objects', function(done) { + var keys = []; + + table + .sampleRowKeysStream() + .on('error', done) + .on('data', function(key) { + keys.push(key); + }) + .on('end', function() { + assert.strictEqual(keys[0].key, fakeKeys[0].rowKey); + assert.strictEqual(keys[0].offset, fakeKeys[0].offsetBytes); + assert.strictEqual(keys[1].key, fakeKeys[1].rowKey); + assert.strictEqual(keys[1].offset, fakeKeys[1].offsetBytes); + done(); + }); }); }); @@ -1647,7 +1940,7 @@ describe('Bigtable/Table', function() { var error = new Error('err'); beforeEach(function() { - table.sampleRowKeysStream = sinon.spy(function() { + table.bigtable.request = function() { var stream = new Stream({ objectMode: true, }); @@ -1657,14 +1950,17 @@ describe('Bigtable/Table', function() { }); return stream; - }); + }; }); - it('should return the error to the callback', function(done) { - table.sampleRowKeys(function(err) { - assert.strictEqual(err, error); - done(); - }); + it('should emit an error event', function(done) { + table + .sampleRowKeysStream() + .on('error', function(err) { + assert.strictEqual(err, error); + done(); + }) + .on('data', done); }); }); }); From 034d38fee7098d7778cd6540cdf6649f40c1eb81 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 23 Feb 2018 10:42:19 -0500 Subject: [PATCH 18/46] fix sinon errors --- src/family.js | 3 +-- test/family.js | 28 +++++++++------------------- test/filter.js | 18 +++++++++++++++--- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/family.js b/src/family.js index ca4c826b6..6216de6eb 100644 --- a/src/family.js +++ b/src/family.js @@ -238,7 +238,7 @@ Family.prototype.exists = function(gaxOptions, callback) { gaxOptions = {}; } - this.getMetadata({gaxOptions}, function(err) { + this.getMetadata(gaxOptions, function(err) { if (err) { if (err.name === 'FamilyError') { callback(null, false); @@ -318,7 +318,6 @@ Family.prototype.get = function(options, callback) { * @param {?error} callback.err An error returned while making this * request. * @param {object} callback.metadata The metadata. - * @param {object} callback.apiResponse The full API response. * * @example * family.getMetadata(function(err, metadata, apiResponse) {}); diff --git a/test/family.js b/test/family.js index 954c82d86..5894d335f 100644 --- a/test/family.js +++ b/test/family.js @@ -419,47 +419,37 @@ describe('Bigtable/Family', function() { callback(err, null, response); }; - family.getMetadata(function(err_, metadata, apiResponse) { + family.getMetadata(function(err_) { assert.strictEqual(err, err_); - assert.strictEqual(response, apiResponse); done(); }); }); it('should update the metadata', function(done) { - var FAMILY = new Family(TABLE, FAMILY_NAME); - var response = { - families: {}, - }; - - FAMILY.metadata = { + var family = new Family(TABLE, FAMILY_NAME); + family.metadata = { a: 'a', b: 'b', }; family.table.getFamilies = function(gaxOptions, callback) { - callback(null, [FAMILY], response); + callback(null, [family]); }; - family.getMetadata(function(err, metadata, apiResponse) { + family.getMetadata(function(err, metadata) { assert.ifError(err); - assert.strictEqual(FAMILY.metadata, metadata); - assert.strictEqual(apiResponse, response); + assert.strictEqual(metadata, family.metadata); done(); }); }); - it('should throw a custom error', function(done) { - var response = {}; - + it('should return a custom error if no results', function(done) { family.table.getFamilies = function(gaxOptions, callback) { - callback(null, [], response); + callback(null, []); }; - family.getMetadata(function(err, metadata, apiResponse) { + family.getMetadata(function(err) { assert(err instanceof FamilyError); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse, response); done(); }); }); diff --git a/test/filter.js b/test/filter.js index 4217d3513..9248f9610 100644 --- a/test/filter.js +++ b/test/filter.js @@ -42,8 +42,7 @@ describe('Bigtable/Filter', function() { }); afterEach(function() { - sinon.restore(); - FakeMutation.convertToBytes.reset(); + FakeMutation.convertToBytes.resetHistory(); }); describe('instantiation', function() { @@ -189,12 +188,14 @@ describe('Bigtable/Filter', function() { }, ]; - sinon.stub(Filter.prototype, 'toProto').returns(fakeProto); + var stub = sinon.stub(Filter.prototype, 'toProto').returns(fakeProto); var parsedFilter = Filter.parse(fakeFilter); assert.strictEqual(parsedFilter, fakeProto); assert.strictEqual(Filter.prototype.toProto.callCount, 1); + + stub.restore(); }); }); @@ -233,6 +234,7 @@ describe('Bigtable/Filter', function() { assert.strictEqual(value, column.name); assert(spy.calledWithExactly(column.name)); assert(FakeMutation.convertToBytes.calledWithExactly(column.name)); + spy.restore(); done(); }; @@ -281,6 +283,7 @@ describe('Bigtable/Filter', function() { assert.strictEqual(filterName, 'columnRangeFilter'); assert.strictEqual(value, fakeRange); assert(spy.calledWithExactly(column.start, column.end, 'Qualifier')); + spy.restore(); done(); }; @@ -309,6 +312,9 @@ describe('Bigtable/Filter', function() { assert.strictEqual(spy.getCall(0).args[0], condition.test); assert.strictEqual(spy.getCall(1).args[0], condition.pass); assert.strictEqual(spy.getCall(2).args[0], condition.fail); + + spy.restore(); + done(); }; @@ -326,6 +332,7 @@ describe('Bigtable/Filter', function() { assert.strictEqual(filterName, 'familyNameRegexFilter'); assert.strictEqual(value, familyName); assert(spy.calledWithExactly(familyName)); + spy.restore(); done(); }; @@ -347,6 +354,7 @@ describe('Bigtable/Filter', function() { assert.strictEqual(spy.getCall(0).args[0], fakeFilters[0]); assert.strictEqual(spy.getCall(1).args[0], fakeFilters[1]); assert.strictEqual(spy.getCall(2).args[0], fakeFilters[2]); + spy.restore(); done(); }; @@ -384,6 +392,7 @@ describe('Bigtable/Filter', function() { assert.strictEqual(value, convertedKey); assert(spy.calledWithExactly(row.key)); assert(FakeMutation.convertToBytes.calledWithExactly(convertedKey)); + spy.restore(); done(); }; @@ -536,6 +545,7 @@ describe('Bigtable/Filter', function() { assert.strictEqual(fakeConvertedValue, val); assert(regSpy.calledWithExactly(value.value)); assert(bytesSpy.calledWithExactly(fakeRegExValue)); + regSpy.restore(); done(); }; @@ -561,6 +571,7 @@ describe('Bigtable/Filter', function() { assert.strictEqual(fakeConvertedValue, val); assert(regSpy.calledWithExactly(value)); assert(bytesSpy.calledWithExactly(fakeRegExValue)); + regSpy.restore(); done(); }; @@ -583,6 +594,7 @@ describe('Bigtable/Filter', function() { assert.strictEqual(filterName, 'valueRangeFilter'); assert.strictEqual(val, fakeRange); assert(spy.calledWithExactly(value.start, value.end, 'Value')); + spy.restore(); done(); }; From b40650b103ef132394256f772bf394c1553ba12a Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 23 Feb 2018 10:47:32 -0500 Subject: [PATCH 19/46] Fix table#get() call to table#getMetadata() --- src/table.js | 4 +++- test/table.js | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/table.js b/src/table.js index e27e5473c..e3eb72d5f 100644 --- a/src/table.js +++ b/src/table.js @@ -752,7 +752,7 @@ Table.prototype.get = function(options, callback) { var autoCreate = !!options.autoCreate; var gaxOptions = options.gaxOptions; - this.getMetadata(gaxOptions, function(err, metadata) { + this.getMetadata({gaxOptions}, function(err, metadata) { if (err) { if (err.code === 5 && autoCreate) { self.create({gaxOptions}, callback); @@ -823,6 +823,8 @@ Table.prototype.getFamilies = function(gaxOptions, callback) { * Get the table's metadata. * * @param {object} [options] Table request options. + * @param {object} [options.gaxOptions] Request configuration options, outlined + * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {string} [options.view] The view to be applied to the table fields. * @param {function} [callback] The callback function. * @param {?error} callback.err An error returned while making this diff --git a/test/table.js b/test/table.js index 90dc9c31a..20f5aa3f1 100644 --- a/test/table.js +++ b/test/table.js @@ -1143,8 +1143,8 @@ describe('Bigtable/Table', function() { gaxOptions: {}, }; - table.getMetadata = function(gaxOptions) { - assert.strictEqual(gaxOptions, options.gaxOptions); + table.getMetadata = function(options_) { + assert.strictEqual(options_.gaxOptions, options.gaxOptions); done(); }; @@ -1152,8 +1152,8 @@ describe('Bigtable/Table', function() { }); it('should not require an options object', function(done) { - table.getMetadata = function(gaxOptions) { - assert.deepStrictEqual(gaxOptions, undefined); + table.getMetadata = function(options) { + assert.deepStrictEqual(options, {gaxOptions: undefined}); done(); }; From 83aa27e95780a708171d446ea8ae59213a090aad Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 23 Feb 2018 10:57:30 -0500 Subject: [PATCH 20/46] Fix system tests --- src/row.js | 2 ++ system-test/bigtable.js | 36 ++++++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/row.js b/src/row.js index 61f180692..452e421af 100644 --- a/src/row.js +++ b/src/row.js @@ -594,6 +594,8 @@ Row.prototype.filter = function(filter, config, callback) { falseMutations: createFlatMutationsList(config.onNoMatch), }; + console.log(reqOpts); + this.bigtable.request( { client: 'BigtableClient', diff --git a/system-test/bigtable.js b/system-test/bigtable.js index 83f1ddda5..7eb823b07 100644 --- a/system-test/bigtable.js +++ b/system-test/bigtable.js @@ -391,7 +391,12 @@ describe('Bigtable', function() { }, }; - row.create(rowData, done); + row.create( + { + entry: rowData, + }, + done + ); }); afterEach(row.delete.bind(row)); @@ -459,7 +464,7 @@ describe('Bigtable', function() { }, }; - row.create(rowData, done); + row.create({entry: rowData}, done); }); it('should insert individual cells', function(done) { @@ -507,19 +512,26 @@ describe('Bigtable', function() { append: '-wood', }; - row.save('traits:teeth', 'shiny', function(err) { - assert.ifError(err); - - row.createRules(rule, function(err) { + row.save( + { + traits: { + teeth: 'shiny', + }, + }, + function(err) { assert.ifError(err); - row.get(['traits:teeth'], function(err, data) { + row.createRules(rule, function(err) { assert.ifError(err); - assert.strictEqual(data.traits.teeth[0].value, 'shiny-wood'); - done(); + + row.get(['traits:teeth'], function(err, data) { + assert.ifError(err); + assert.strictEqual(data.traits.teeth[0].value, 'shiny-wood'); + done(); + }); }); - }); - }); + } + ); }); it('should check and mutate a row', function(done) { @@ -536,7 +548,7 @@ describe('Bigtable', function() { }, ]; - row.filter(filter, mutations, function(err, matched) { + row.filter(filter, {onMatch: mutations}, function(err, matched) { assert.ifError(err); assert(matched); done(); From ac14f082ee0b7aec446c2d964a5852f5c1296698 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 23 Feb 2018 16:09:35 -0500 Subject: [PATCH 21/46] Support emulator --- src/index.js | 71 ++++++++++++++++++++++++-------- src/row.js | 2 - test/index.js | 112 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 163 insertions(+), 22 deletions(-) diff --git a/src/index.js b/src/index.js index 7fa367510..bde6d55af 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,7 @@ var arrify = require('arrify'); var common = require('@google-cloud/common'); var extend = require('extend'); var googleAuth = require('google-auto-auth'); +var grpc = require('google-gax').grpc().grpc; var is = require('is'); var streamEvents = require('stream-events'); var through = require('through2'); @@ -336,18 +337,6 @@ function Bigtable(options) { options = common.util.normalizeArguments(this, options); - // @TODO figure out how to configure GAPIC for this - // var baseUrl = 'bigtable.googleapis.com'; - // var adminBaseUrl = 'bigtableadmin.googleapis.com'; - - // var customEndpoint = - // options.apiEndpoint || process.env.BIGTABLE_EMULATOR_HOST; - - // if (customEndpoint) { - // baseUrl = customEndpoint; - // adminBaseUrl = baseUrl; - // } - // Determine what scopes are needed. // It is the union of the scopes on all three clients. let scopes = []; @@ -364,6 +353,20 @@ function Bigtable(options) { } } + var defaultBaseUrl = 'bigtable.googleapis.com'; + var defaultAdminBaseUrl = 'bigtableadmin.googleapis.com'; + + var customEndpoint = + options.apiEndpoint || process.env.BIGTABLE_EMULATOR_HOST; + var customEndpointBaseUrl; + var customEndpointPort; + + if (customEndpoint) { + var customEndpointParts = customEndpoint.split(':'); + customEndpointBaseUrl = customEndpointParts[0]; + customEndpointPort = customEndpointParts[1]; + } + var options_ = extend( { libName: 'gccl', @@ -373,10 +376,46 @@ function Bigtable(options) { options ); + this.options = { + BigtableClient: extend( + { + servicePath: customEndpoint ? customEndpointBaseUrl : defaultBaseUrl, + port: customEndpoint ? parseInt(customEndpointPort, 10) : 443, + sslCreds: customEndpoint + ? grpc.credentials.createInsecure() + : undefined, + }, + options_ + ), + BigtableInstanceAdminClient: extend( + { + servicePath: customEndpoint + ? customEndpointBaseUrl + : defaultAdminBaseUrl, + port: customEndpoint ? parseInt(customEndpointPort, 10) : 443, + sslCreds: customEndpoint + ? grpc.credentials.createInsecure() + : undefined, + }, + options_ + ), + BigtableTableAdminClient: extend( + { + servicePath: customEndpoint + ? customEndpointBaseUrl + : defaultAdminBaseUrl, + port: customEndpoint ? parseInt(customEndpointPort, 10) : 443, + sslCreds: customEndpoint + ? grpc.credentials.createInsecure() + : undefined, + }, + options_ + ), + }; + this.api = {}; this.auth = googleAuth(options_); - this.options = options_; - this.projectId = this.options.projectId || '{{projectId}}'; + this.projectId = options.projectId || '{{projectId}}'; this.projectName = 'projects/' + this.projectId; } @@ -481,8 +520,6 @@ Bigtable.prototype.createInstance = function(name, options, callback) { var args = [].slice.call(arguments); if (!err) { - // Push the new instance among the original arguments, so as not to - // tamper with GAPIC's natural response. args.splice(1, 0, self.instance(name)); } @@ -669,7 +706,7 @@ Bigtable.prototype.request = function(config, callback) { if (!gaxClient) { // Lazily instantiate client. - gaxClient = new v2[config.client](self.options); + gaxClient = new v2[config.client](self.options[config.client]); self.api[config.client] = gaxClient; } diff --git a/src/row.js b/src/row.js index 452e421af..61f180692 100644 --- a/src/row.js +++ b/src/row.js @@ -594,8 +594,6 @@ Row.prototype.filter = function(filter, config, callback) { falseMutations: createFlatMutationsList(config.onNoMatch), }; - console.log(reqOpts); - this.bigtable.request( { client: 'BigtableClient', diff --git a/test/index.js b/test/index.js index 0912385e6..51de1b27f 100644 --- a/test/index.js +++ b/test/index.js @@ -18,6 +18,7 @@ var assert = require('assert'); var extend = require('extend'); +var grpc = require('google-gax').grpc().grpc; var nodeutil = require('util'); var proxyquire = require('proxyquire'); var sinon = require('sinon').sandbox.create(); @@ -164,8 +165,6 @@ describe('Bigtable', function() { assert.strictEqual(normalizeArgumentsCalled, true); }); - it.skip('should work with the emulator', function() {}); - it('should initialize the API object', function() { assert.deepEqual(bigtable.api, {}); }); @@ -207,13 +206,120 @@ describe('Bigtable', function() { }; var bigtable = new Bigtable(options); + var defaultOptions = { + a: 'b', + c: 'd', + libName: 'gccl', + libVersion: PKG.version, + scopes: EXPECTED_SCOPES, + }; assert.deepEqual(bigtable.options, { + BigtableClient: extend( + { + servicePath: 'bigtable.googleapis.com', + port: 443, + sslCreds: undefined, + }, + defaultOptions + ), + BigtableInstanceAdminClient: extend( + { + servicePath: 'bigtableadmin.googleapis.com', + port: 443, + sslCreds: undefined, + }, + defaultOptions + ), + BigtableTableAdminClient: extend( + { + servicePath: 'bigtableadmin.googleapis.com', + port: 443, + sslCreds: undefined, + }, + defaultOptions + ), + }); + }); + + it('should work with the emulator', function() { + process.env.BIGTABLE_EMULATOR_HOST = 'override:8080'; + + var options = { a: 'b', c: 'd', libName: 'gccl', libVersion: PKG.version, scopes: EXPECTED_SCOPES, + }; + + var bigtable = new Bigtable(options); + + assert.deepEqual(bigtable.options, { + BigtableClient: extend( + { + servicePath: 'override', + port: 8080, + sslCreds: grpc.credentials.createInsecure(), + }, + options + ), + BigtableInstanceAdminClient: extend( + { + servicePath: 'override', + port: 8080, + sslCreds: grpc.credentials.createInsecure(), + }, + options + ), + BigtableTableAdminClient: extend( + { + servicePath: 'override', + port: 8080, + sslCreds: grpc.credentials.createInsecure(), + }, + options + ), + }); + }); + + it('should work with a customEndpoint', function() { + var options = { + apiEndpoint: 'customEndpoint:9090', + a: 'b', + c: 'd', + libName: 'gccl', + libVersion: PKG.version, + scopes: EXPECTED_SCOPES, + }; + + var bigtable = new Bigtable(options); + + assert.deepEqual(bigtable.options, { + BigtableClient: extend( + { + servicePath: 'customEndpoint', + port: 9090, + sslCreds: grpc.credentials.createInsecure(), + }, + options + ), + BigtableInstanceAdminClient: extend( + { + servicePath: 'customEndpoint', + port: 9090, + sslCreds: grpc.credentials.createInsecure(), + }, + options + ), + BigtableTableAdminClient: extend( + { + servicePath: 'customEndpoint', + port: 9090, + sslCreds: grpc.credentials.createInsecure(), + }, + options + ), }); }); @@ -553,7 +659,7 @@ describe('Bigtable', function() { }; fakeV2[CONFIG.client] = function(options) { - assert.strictEqual(options, bigtable.options); + assert.strictEqual(options, bigtable.options[CONFIG.client]); return fakeClient; }; From e9f6817868a7c36c6550ab58bb9c9800f10dedbe Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Feb 2018 18:06:17 -0500 Subject: [PATCH 22/46] retries --- package.json | 2 ++ src/index.js | 30 +++++++++++++++++++++++++++--- src/table.js | 24 ++++++++++-------------- src/v2/bigtable_client.js | 2 +- system-test/mutate-rows.js | 11 +++++++---- 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 33aee1e2a..3b86d491c 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "concat-stream": "^1.5.0", "create-error-class": "^3.0.2", "dot-prop": "^4.2.0", + "events-intercept": "^2.0.0", "extend": "^3.0.0", "google-auto-auth": "^0.9.3", "google-gax": "^0.16.0", @@ -72,6 +73,7 @@ "prop-assign": "^1.0.0", "protobufjs": "^6.8.0", "pumpify": "^1.3.3", + "retry-request": "^3.3.1", "safe-buffer": "^5.1.1", "stream-events": "^1.0.2", "string-format-obj": "^1.0.0", diff --git a/src/index.js b/src/index.js index bde6d55af..dd2b92caa 100644 --- a/src/index.js +++ b/src/index.js @@ -19,9 +19,12 @@ var arrify = require('arrify'); var common = require('@google-cloud/common'); var extend = require('extend'); +var GrpcService = require('@google-cloud/common-grpc').Service; var googleAuth = require('google-auto-auth'); var grpc = require('google-gax').grpc().grpc; +var intercept = require('events-intercept'); var is = require('is'); +var retryRequest = require('retry-request'); var streamEvents = require('stream-events'); var through = require('through2'); @@ -741,12 +744,33 @@ Bigtable.prototype.request = function(config, callback) { return; } - gaxStream = requestFn(); + // @TODO: remove `retry-request` when gax supports retryable streams. + // https://github.com/googleapis/gax-nodejs/blob/ec0c8b0805c31d8a91ea69cb19fe50f42a38bf87/lib/streaming.js#L230 + var retryOpts = extend( + { + currentRetryAttempt: 0, + objectMode: true, + shouldRetryFn: GrpcService.shouldRetryRequest_, + request: function() { + return intercept.patch(requestFn()) + .on('metadata', console.log) + .intercept('response', function(response, done) { + // See https://github.com/googleapis/nodejs-common-grpc/blob/3f3442f22b0859ea16512efe971f906f4fe78def/src/service.js#L392 + var grcpStatus = GrpcService.decorateStatus_({code: 0}); + console.log('intercepted ') + done(null, grcpStatus); + }); + }, + }, + config.retryOpts, + ); - gaxStream + retryRequest(null, retryOpts) .on('error', function(err) { - stream.destroy(err); + var grpcError = GrpcService.decorateError_(err); + stream.destroy(grpcError || err); }) + .on('request', stream.emit.bind(stream, 'request')) .pipe(stream); }); } diff --git a/src/table.js b/src/table.js index e3eb72d5f..33c15b94c 100644 --- a/src/table.js +++ b/src/table.js @@ -416,17 +416,14 @@ Table.prototype.createReadStream = function(options) { let lastRowKey = chunkTransformer ? chunkTransformer.lastRowKey : ''; chunkTransformer = new ChunkTransformer({decode: options.decode}); - // @todo Figure out how to do this in gapic. - // var grpcOpts = { - // retryOpts: { - // currentRetryAttempt: numRequestsMade, - // }, - // }; - var reqOpts = { tableName: this.id, }; + var retryOpts = { + currentRetryAttempt: numRequestsMade, + }; + if (lastRowKey) { const lessThan = (lhs, rhs) => { const lhsBytes = Mutation.convertToBytes(lhs); @@ -506,6 +503,7 @@ Table.prototype.createReadStream = function(options) { method: 'readRows', reqOpts: reqOpts, gaxOpts: options.gaxOptions, + retryOpts: retryOpts, }); requestStream.on('request', () => numRequestsMade++); @@ -1160,13 +1158,6 @@ Table.prototype.mutate = function(entries, gaxOptions, callback) { } function makeNextBatchRequest() { - // @todo Figure out how to do this in gapic. - // var grpcOpts = { - // retryOpts: { - // currentRetryAttempt: numRequestsMade, - // }, - // }; - var entryBatch = entries.filter((entry, index) => { return pendingEntryIndices.has(index); }); @@ -1176,12 +1167,17 @@ Table.prototype.mutate = function(entries, gaxOptions, callback) { entries: entryBatch.map(Mutation.parse), }; + var retryOpts = { + currentRetryAttempt: numRequestsMade, + }; + self.bigtable .request({ client: 'BigtableClient', method: 'mutateRows', reqOpts: reqOpts, gaxOpts: gaxOptions, + retryOpts: retryOpts, }) .on('request', () => numRequestsMade++) .on('error', onBatchResponse.bind(null, numRequestsMade)) diff --git a/src/v2/bigtable_client.js b/src/v2/bigtable_client.js index b8bac00a2..d51c4a987 100644 --- a/src/v2/bigtable_client.js +++ b/src/v2/bigtable_client.js @@ -254,7 +254,6 @@ class BigtableClient { */ readRows(request, options) { options = options || {}; - return this._innerApiCalls.readRows(request, options); } @@ -427,6 +426,7 @@ class BigtableClient { mutateRows(request, options) { options = options || {}; + console.log(this._innerApiCalls.mutateRows.toString()) return this._innerApiCalls.mutateRows(request, options); } diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index db0a99825..de5a453a6 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -44,10 +44,14 @@ function getDeltas(array) { }, []); } -describe.skip('Bigtable/Table', () => { +describe('Bigtable/Table', () => { const bigtable = new Bigtable(); bigtable.grpcCredentials = grpc.credentials.createInsecure(); +<<<<<<< HEAD bigtable.projectId = 'test'; +======= + const bigtableService = bigtable.getService_({service: 'Bigtable'}); +>>>>>>> retries const INSTANCE = bigtable.instance('instance'); const TABLE = INSTANCE.table('table'); @@ -75,10 +79,9 @@ describe.skip('Bigtable/Table', () => { mutationBatchesInvoked = []; mutationCallTimes = []; responses = null; - stub = sinon.stub(bigtable, 'request').callsFake(config => { - const reqOpts = config.reqOpts; + stub = sinon.stub(bigtableService, 'mutateRows').callsFake(grpcOpts => { mutationBatchesInvoked.push( - reqOpts.entries.map(entry => entry.rowKey.asciiSlice()) + grpcOpts.entries.map(entry => entry.rowKey.asciiSlice()) ); mutationCallTimes.push(new Date().getTime()); const emitter = through.obj(); From 1252b660ba27c6f6be57474822f9e880385dcdec Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Feb 2018 18:51:43 -0500 Subject: [PATCH 23/46] testing continues --- src/index.js | 8 +++++--- src/v2/bigtable_client.js | 2 -- system-test/mutate-rows.js | 40 +++++++++++++++++--------------------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/index.js b/src/index.js index dd2b92caa..52d1496ad 100644 --- a/src/index.js +++ b/src/index.js @@ -752,12 +752,14 @@ Bigtable.prototype.request = function(config, callback) { objectMode: true, shouldRetryFn: GrpcService.shouldRetryRequest_, request: function() { - return intercept.patch(requestFn()) - .on('metadata', console.log) + var gaxStream = requestFn(); + + intercept.patch(gaxStream); + + return gaxStream .intercept('response', function(response, done) { // See https://github.com/googleapis/nodejs-common-grpc/blob/3f3442f22b0859ea16512efe971f906f4fe78def/src/service.js#L392 var grcpStatus = GrpcService.decorateStatus_({code: 0}); - console.log('intercepted ') done(null, grcpStatus); }); }, diff --git a/src/v2/bigtable_client.js b/src/v2/bigtable_client.js index d51c4a987..7f1e77c64 100644 --- a/src/v2/bigtable_client.js +++ b/src/v2/bigtable_client.js @@ -425,8 +425,6 @@ class BigtableClient { */ mutateRows(request, options) { options = options || {}; - - console.log(this._innerApiCalls.mutateRows.toString()) return this._innerApiCalls.mutateRows(request, options); } diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index de5a453a6..8e31e589b 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -44,14 +44,16 @@ function getDeltas(array) { }, []); } -describe('Bigtable/Table', () => { +describe.skip('Bigtable/Table', () => { const bigtable = new Bigtable(); + bigtable.api = {}; + bigtable.auth = { + getProjectId: function(callback) { + callback(null, 'project-id'); + }, + }; bigtable.grpcCredentials = grpc.credentials.createInsecure(); -<<<<<<< HEAD bigtable.projectId = 'test'; -======= - const bigtableService = bigtable.getService_({service: 'Bigtable'}); ->>>>>>> retries const INSTANCE = bigtable.instance('instance'); const TABLE = INSTANCE.table('table'); @@ -67,32 +69,26 @@ describe('Bigtable/Table', () => { clock = sinon.useFakeTimers({ toFake: [ 'setTimeout', - 'clearTimeout', - 'setImmediate', - 'clearImmediate', - 'setInterval', - 'clearInterval', - 'Date', - 'nextTick', ], }); mutationBatchesInvoked = []; mutationCallTimes = []; responses = null; - stub = sinon.stub(bigtableService, 'mutateRows').callsFake(grpcOpts => { - mutationBatchesInvoked.push( - grpcOpts.entries.map(entry => entry.rowKey.asciiSlice()) - ); - mutationCallTimes.push(new Date().getTime()); - const emitter = through.obj(); - dispatch(emitter, responses.shift()); - return emitter; - }); + bigtable.api.BigtableClient = { + mutateRows: reqOpts => { + mutationBatchesInvoked.push( + reqOpts.entries.map(entry => entry.rowKey.asciiSlice()) + ); + mutationCallTimes.push(new Date().getTime()); + const emitter = through.obj(); + dispatch(emitter, responses.shift()); + return emitter; + }, + }; }); afterEach(() => { clock.uninstall(); - stub.restore(); }); tests.forEach(test => { From 91c38d7dd40c02ce37bcdc80b94629e1fd53f3a2 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Feb 2018 18:51:59 -0500 Subject: [PATCH 24/46] pretteir --- src/index.js | 13 ++++++------- system-test/mutate-rows.js | 4 +--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/index.js b/src/index.js index 52d1496ad..d6933b4dd 100644 --- a/src/index.js +++ b/src/index.js @@ -756,15 +756,14 @@ Bigtable.prototype.request = function(config, callback) { intercept.patch(gaxStream); - return gaxStream - .intercept('response', function(response, done) { - // See https://github.com/googleapis/nodejs-common-grpc/blob/3f3442f22b0859ea16512efe971f906f4fe78def/src/service.js#L392 - var grcpStatus = GrpcService.decorateStatus_({code: 0}); - done(null, grcpStatus); - }); + return gaxStream.intercept('response', function(response, done) { + // See https://github.com/googleapis/nodejs-common-grpc/blob/3f3442f22b0859ea16512efe971f906f4fe78def/src/service.js#L392 + var grcpStatus = GrpcService.decorateStatus_({code: 0}); + done(null, grcpStatus); + }); }, }, - config.retryOpts, + config.retryOpts ); retryRequest(null, retryOpts) diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index 8e31e589b..cc6859c98 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -67,9 +67,7 @@ describe.skip('Bigtable/Table', () => { beforeEach(() => { clock = sinon.useFakeTimers({ - toFake: [ - 'setTimeout', - ], + toFake: ['setTimeout'], }); mutationBatchesInvoked = []; mutationCallTimes = []; From 44c0d579b907479c8e73fc6d18a6ead00f57a0b4 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Feb 2018 18:52:26 -0500 Subject: [PATCH 25/46] lint --- system-test/mutate-rows.js | 1 - 1 file changed, 1 deletion(-) diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index cc6859c98..9a11c3202 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -63,7 +63,6 @@ describe.skip('Bigtable/Table', () => { let mutationBatchesInvoked; let mutationCallTimes; let responses; - let stub; beforeEach(() => { clock = sinon.useFakeTimers({ From 90097df496c2a78c637e4cb84c2c5f4426682992 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Feb 2018 19:12:05 -0500 Subject: [PATCH 26/46] test --- package.json | 1 - src/index.js | 25 +++++++++++-------------- test/index.js | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 3b86d491c..be8176161 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "concat-stream": "^1.5.0", "create-error-class": "^3.0.2", "dot-prop": "^4.2.0", - "events-intercept": "^2.0.0", "extend": "^3.0.0", "google-auto-auth": "^0.9.3", "google-gax": "^0.16.0", diff --git a/src/index.js b/src/index.js index d6933b4dd..20fbfbd64 100644 --- a/src/index.js +++ b/src/index.js @@ -22,7 +22,6 @@ var extend = require('extend'); var GrpcService = require('@google-cloud/common-grpc').Service; var googleAuth = require('google-auto-auth'); var grpc = require('google-gax').grpc().grpc; -var intercept = require('events-intercept'); var is = require('is'); var retryRequest = require('retry-request'); var streamEvents = require('stream-events'); @@ -744,6 +743,8 @@ Bigtable.prototype.request = function(config, callback) { return; } + var gaxStream; + // @TODO: remove `retry-request` when gax supports retryable streams. // https://github.com/googleapis/gax-nodejs/blob/ec0c8b0805c31d8a91ea69cb19fe50f42a38bf87/lib/streaming.js#L230 var retryOpts = extend( @@ -752,25 +753,21 @@ Bigtable.prototype.request = function(config, callback) { objectMode: true, shouldRetryFn: GrpcService.shouldRetryRequest_, request: function() { - var gaxStream = requestFn(); - - intercept.patch(gaxStream); - - return gaxStream.intercept('response', function(response, done) { - // See https://github.com/googleapis/nodejs-common-grpc/blob/3f3442f22b0859ea16512efe971f906f4fe78def/src/service.js#L392 - var grcpStatus = GrpcService.decorateStatus_({code: 0}); - done(null, grcpStatus); - }); + gaxStream = requestFn(); + return gaxStream; }, }, config.retryOpts ); + stream.abort = function() { + if (gaxStream) { + gaxStream.cancel(); + } + }; + retryRequest(null, retryOpts) - .on('error', function(err) { - var grpcError = GrpcService.decorateError_(err); - stream.destroy(grpcError || err); - }) + .on('error', stream.destroy.bind(stream)) .on('request', stream.emit.bind(stream, 'request')) .pipe(stream); }); diff --git a/test/index.js b/test/index.js index 51de1b27f..c157fbba3 100644 --- a/test/index.js +++ b/test/index.js @@ -17,6 +17,8 @@ 'use strict'; var assert = require('assert'); +var common = require('@google-cloud/common'); +var commonGrpc = require('@google-cloud/common-grpc'); var extend = require('extend'); var grpc = require('google-gax').grpc().grpc; var nodeutil = require('util'); @@ -24,7 +26,6 @@ var proxyquire = require('proxyquire'); var sinon = require('sinon').sandbox.create(); var through = require('through2'); -var common = require('@google-cloud/common'); var Cluster = require('../src/cluster.js'); var Instance = require('../src/instance.js'); var v2 = require('../src/v2'); @@ -59,6 +60,11 @@ function fakeGoogleAutoAuth() { return (googleAutoAuthOverride || common.util.noop).apply(null, arguments); } +var retryRequestOverride; +function fakeRetryRequest() { + return (retryRequestOverride || require('retry-request')).apply(null, arguments); +} + var fakePaginator = { extend: function() { this.calledWith_ = arguments; @@ -93,6 +99,7 @@ describe('Bigtable', function() { util: fakeUtil, }, 'google-auto-auth': fakeGoogleAutoAuth, + 'retry-request': fakeRetryRequest, './cluster.js': FakeCluster, './instance.js': FakeInstance, './v2': fakeV2, @@ -107,6 +114,7 @@ describe('Bigtable', function() { extend(fakeUtil, originalFakeUtil); googleAutoAuthOverride = null; + retryRequestOverride = null; replaceProjectIdTokenOverride = null; delete process.env.BIGTABLE_EMULATOR_HOST; @@ -760,6 +768,18 @@ describe('Bigtable', function() { }; }); + it('should use retry-request', function(done) { + retryRequestOverride = function(_, config) { + assert.strictEqual(config.currentRetryAttempt, 0); + assert.strictEqual(config.objectMode, true); + assert.strictEqual(config.shouldRetryFn, commonGrpc.Service.shouldRetryRequest_); + done(); + }; + + var requestStream = bigtable.request(CONFIG); + requestStream.emit('reading'); + }); + it('should expose an abort function', function(done) { GAX_STREAM.cancel = done; @@ -816,6 +836,22 @@ describe('Bigtable', function() { GAX_STREAM.emit('error', error); }); + + it('should re-emit request event from retry-request', function(done) { + var error = new Error('Error.'); + + retryRequestOverride = function() { + var fakeRetryRequestStream = through.obj(); + setImmediate(function() { + fakeRetryRequestStream.emit('request'); + }); + return fakeRetryRequestStream; + } + + var requestStream = bigtable.request(CONFIG); + requestStream.emit('reading'); + requestStream.on('request', done); + }); }); }); }); From 0fda3859659592e27c828d671d8fd15bb66fd9b1 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Feb 2018 19:13:58 -0500 Subject: [PATCH 27/46] testing --- test/index.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/index.js b/test/index.js index c157fbba3..7617a8366 100644 --- a/test/index.js +++ b/test/index.js @@ -62,7 +62,10 @@ function fakeGoogleAutoAuth() { var retryRequestOverride; function fakeRetryRequest() { - return (retryRequestOverride || require('retry-request')).apply(null, arguments); + return (retryRequestOverride || require('retry-request')).apply( + null, + arguments + ); } var fakePaginator = { @@ -772,7 +775,10 @@ describe('Bigtable', function() { retryRequestOverride = function(_, config) { assert.strictEqual(config.currentRetryAttempt, 0); assert.strictEqual(config.objectMode, true); - assert.strictEqual(config.shouldRetryFn, commonGrpc.Service.shouldRetryRequest_); + assert.strictEqual( + config.shouldRetryFn, + commonGrpc.Service.shouldRetryRequest_ + ); done(); }; @@ -838,15 +844,13 @@ describe('Bigtable', function() { }); it('should re-emit request event from retry-request', function(done) { - var error = new Error('Error.'); - retryRequestOverride = function() { var fakeRetryRequestStream = through.obj(); setImmediate(function() { fakeRetryRequestStream.emit('request'); }); return fakeRetryRequestStream; - } + }; var requestStream = bigtable.request(CONFIG); requestStream.emit('reading'); From 10e8f657cb71612a6cfdc55647ed8d6f3e17acdc Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 28 Feb 2018 09:30:07 -0500 Subject: [PATCH 28/46] remove autocreate where operations are returned --- src/cluster.js | 37 +++++---------------- src/instance.js | 35 ++++---------------- test/cluster.js | 83 ++++++------------------------------------------ test/instance.js | 83 ++++++------------------------------------------ 4 files changed, 35 insertions(+), 203 deletions(-) diff --git a/src/cluster.js b/src/cluster.js index 821155490..c6a58ff40 100644 --- a/src/cluster.js +++ b/src/cluster.js @@ -221,16 +221,8 @@ Cluster.prototype.exists = function(gaxOptions, callback) { /** * Get a cluster if it exists. * - * You may optionally use this to "get or create" an object by providing an - * object with `autoCreate` set to `true`. Any extra configuration that is - * normally required for the `create` method must be contained within this - * object as well. - * - * @param {object} [options] Configuration object. - * @param {boolean} [options.autoCreate=false] Automatically create the - * instance if it does not already exist. - * @param {object} [options.gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * * @example * cluster.get(function(err, cluster, apiResponse) { @@ -245,29 +237,16 @@ Cluster.prototype.exists = function(gaxOptions, callback) { * var apiResponse = data[1]; * }); */ -Cluster.prototype.get = function(options, callback) { +Cluster.prototype.get = function(gaxOptions, callback) { var self = this; - if (is.fn(options)) { - callback = options; - options = {}; + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; } - var autoCreate = !!options.autoCreate; - var gaxOptions = options.gaxOptions; - - this.getMetadata(gaxOptions, function(err, apiResponse) { - if (err) { - if (err.code === 5 && autoCreate) { - self.create({gaxOptions}, callback); - return; - } - - callback(err, null, apiResponse); - return; - } - - callback(null, self, apiResponse); + this.getMetadata(gaxOptions, function(err, metadata) { + callback(err, err ? null : self, metadata); }); }; diff --git a/src/instance.js b/src/instance.js index 963010e0a..f3cee43cd 100644 --- a/src/instance.js +++ b/src/instance.js @@ -458,16 +458,8 @@ Instance.prototype.exists = function(gaxOptions, callback) { /** * Get an instance if it exists. * - * You may optionally use this to "get or create" an object by providing an - * object with `autoCreate` set to `true`. Any extra configuration that is - * normally required for the `create` method must be contained within this - * object as well. - * - * @param {object} [options] Configuration object. - * @param {boolean} [options.autoCreate=false] Automatically create the - * instance if it does not already exist. - * @param {object} [options.gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.error An error returned while making this request. * @param {Instance} callback.instance The Instance object. @@ -490,29 +482,16 @@ Instance.prototype.exists = function(gaxOptions, callback) { * var apiResponse = data[1]; * }); */ -Instance.prototype.get = function(options, callback) { +Instance.prototype.get = function(gaxOptions, callback) { var self = this; - if (is.fn(options)) { - callback = options; - options = {}; + if (is.fn(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; } - var autoCreate = !!options.autoCreate; - var gaxOptions = options.gaxOptions; - this.getMetadata(gaxOptions, function(err, metadata) { - if (err) { - if (err.code === 5 && autoCreate) { - self.create({gaxOptions}, callback); - return; - } - - callback(err); - return; - } - - callback(null, self, metadata); + callback(err, err ? null : self, metadata); }); }; diff --git a/test/cluster.js b/test/cluster.js index 76ae9fcbb..a08fa9cc4 100644 --- a/test/cluster.js +++ b/test/cluster.js @@ -251,88 +251,25 @@ describe('Bigtable/Cluster', function() { describe('get', function() { it('should call getMetadata', function(done) { - var options = { - gaxOptions: {}, - }; + var gaxOptions = {}; - cluster.getMetadata = function(gaxOptions) { - assert.strictEqual(gaxOptions, options.gaxOptions); + cluster.getMetadata = function(gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); done(); }; - cluster.get(options, assert.ifError); + cluster.get(gaxOptions, assert.ifError); }); - it('should not require an options object', function(done) { + it('should not require gaxOptions', function(done) { cluster.getMetadata = function(gaxOptions) { - assert.deepStrictEqual(gaxOptions, undefined); + assert.deepEqual(gaxOptions, {}); done(); }; cluster.get(assert.ifError); }); - it('should auto create with error code 5', function(done) { - var error = new Error('Error.'); - error.code = 5; - - var options = { - autoCreate: true, - gaxOptions: {}, - }; - - cluster.getMetadata = function(gaxOptions, callback) { - callback(error); - }; - - cluster.create = function(options_, callback) { - assert.strictEqual(options_.gaxOptions, options.gaxOptions); - callback(); // done() - }; - - cluster.get(options, done); - }); - - it('should not auto create without error code 5', function(done) { - var error = new Error('Error.'); - error.code = 'NOT-5'; - - var options = { - autoCreate: true, - }; - - cluster.getMetadata = function(gaxOptions, callback) { - callback(error); - }; - - cluster.create = function() { - throw new Error('Should not create.'); - }; - - cluster.get(options, function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - - it('should not auto create unless requested', function(done) { - var error = new Error('Error.'); - error.code = 5; - - cluster.getMetadata = function(gaxOptions, callback) { - callback(error); - }; - - cluster.create = function() { - throw new Error('Should not create.'); - }; - - cluster.get(function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - it('should return an error from getMetadata', function(done) { var error = new Error('Error.'); @@ -347,16 +284,16 @@ describe('Bigtable/Cluster', function() { }); it('should return self and API response', function(done) { - var apiResponse = {}; + var metadata = {}; cluster.getMetadata = function(gaxOptions, callback) { - callback(null, apiResponse); + callback(null, metadata); }; - cluster.get(function(err, cluster_, apiResponse_) { + cluster.get(function(err, cluster_, metadata_) { assert.ifError(err); assert.strictEqual(cluster_, cluster); - assert.strictEqual(apiResponse_, apiResponse); + assert.strictEqual(metadata_, metadata); done(); }); }); diff --git a/test/instance.js b/test/instance.js index 4f50d8d5d..9280e9632 100644 --- a/test/instance.js +++ b/test/instance.js @@ -497,88 +497,25 @@ describe('Bigtable/Instance', function() { describe('get', function() { it('should call getMetadata', function(done) { - var options = { - gaxOptions: {}, - }; + var gaxOptions = {}; - instance.getMetadata = function(gaxOptions) { - assert.strictEqual(gaxOptions, options.gaxOptions); + instance.getMetadata = function(gaxOptions_) { + assert.strictEqual(gaxOptions_, gaxOptions); done(); }; - instance.get(options, assert.ifError); + instance.get(gaxOptions, assert.ifError); }); - it('should not require an options object', function(done) { + it('should not require gaxOptions', function(done) { instance.getMetadata = function(gaxOptions) { - assert.deepStrictEqual(gaxOptions, undefined); + assert.deepEqual(gaxOptions, {}); done(); }; instance.get(assert.ifError); }); - it('should auto create with error code 5', function(done) { - var error = new Error('Error.'); - error.code = 5; - - var options = { - autoCreate: true, - gaxOptions: {}, - }; - - instance.getMetadata = function(gaxOptions, callback) { - callback(error); - }; - - instance.create = function(options_, callback) { - assert.strictEqual(options_.gaxOptions, options.gaxOptions); - callback(); // done() - }; - - instance.get(options, done); - }); - - it('should not auto create without error code 5', function(done) { - var error = new Error('Error.'); - error.code = 'NOT-5'; - - var options = { - autoCreate: true, - }; - - instance.getMetadata = function(gaxOptions, callback) { - callback(error); - }; - - instance.create = function() { - throw new Error('Should not create.'); - }; - - instance.get(options, function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - - it('should not auto create unless requested', function(done) { - var error = new Error('Error.'); - error.code = 5; - - instance.getMetadata = function(gaxOptions, callback) { - callback(error); - }; - - instance.create = function() { - throw new Error('Should not create.'); - }; - - instance.get(function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - it('should return an error from getMetadata', function(done) { var error = new Error('Error.'); @@ -593,16 +530,16 @@ describe('Bigtable/Instance', function() { }); it('should return self and API response', function(done) { - var apiResponse = {}; + var metadata = {}; instance.getMetadata = function(gaxOptions, callback) { - callback(null, apiResponse); + callback(null, metadata); }; - instance.get(function(err, instance_, apiResponse_) { + instance.get(function(err, instance_, metadata_) { assert.ifError(err); assert.strictEqual(instance_, instance); - assert.strictEqual(apiResponse_, apiResponse); + assert.strictEqual(metadata_, metadata); done(); }); }); From edde5d7f6950a41930078b9c12ebd5b837b04c0c Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 28 Feb 2018 09:33:14 -0500 Subject: [PATCH 29/46] Remove duplicate code --- src/index.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/index.js b/src/index.js index 20fbfbd64..4a84fc780 100644 --- a/src/index.js +++ b/src/index.js @@ -743,8 +743,6 @@ Bigtable.prototype.request = function(config, callback) { return; } - var gaxStream; - // @TODO: remove `retry-request` when gax supports retryable streams. // https://github.com/googleapis/gax-nodejs/blob/ec0c8b0805c31d8a91ea69cb19fe50f42a38bf87/lib/streaming.js#L230 var retryOpts = extend( @@ -760,12 +758,6 @@ Bigtable.prototype.request = function(config, callback) { config.retryOpts ); - stream.abort = function() { - if (gaxStream) { - gaxStream.cancel(); - } - }; - retryRequest(null, retryOpts) .on('error', stream.destroy.bind(stream)) .on('request', stream.emit.bind(stream, 'request')) From 5f8ae986585181ed675703372e0da265462d7c2e Mon Sep 17 00:00:00 2001 From: Moshe Kolodny Date: Wed, 28 Feb 2018 15:31:45 -0500 Subject: [PATCH 30/46] Fixed broken mutate-rows tests on GAPIC branch --- patches/patched-process-nextick-args.js | 3 +++ system-test/mutate-rows.js | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 patches/patched-process-nextick-args.js diff --git a/patches/patched-process-nextick-args.js b/patches/patched-process-nextick-args.js new file mode 100644 index 000000000..ab1a519f3 --- /dev/null +++ b/patches/patched-process-nextick-args.js @@ -0,0 +1,3 @@ +module.exports = function() { + return process.nextTick.apply(this, arguments); +}; diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index 9a11c3202..62801af42 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -44,7 +44,7 @@ function getDeltas(array) { }, []); } -describe.skip('Bigtable/Table', () => { +describe('Bigtable/Table', () => { const bigtable = new Bigtable(); bigtable.api = {}; bigtable.auth = { @@ -66,7 +66,12 @@ describe.skip('Bigtable/Table', () => { beforeEach(() => { clock = sinon.useFakeTimers({ - toFake: ['setTimeout'], + toFake: [ + 'setTimeout', + 'setImmediate', + 'Date', + 'nextTick', + ], }); mutationBatchesInvoked = []; mutationCallTimes = []; From f27d704ca53580eec5f015d04d3046e643ee008c Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 2 Mar 2018 11:04:16 -0500 Subject: [PATCH 31/46] Remove patches --- package.json | 2 -- patches/patched-process-nextick-args.js | 3 --- 2 files changed, 5 deletions(-) delete mode 100644 patches/patched-process-nextick-args.js diff --git a/package.json b/package.json index be8176161..70ccd3d9c 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "lint": "repo-tools lint --cmd eslint -- src/ samples/ system-test/ test/", "prettier": "repo-tools exec -- prettier --write src/*.js src/*/*.js samples/*.js samples/*/*.js test/*.js test/*/*.js system-test/*.js system-test/*/*.js", "publish-module": "node ../../scripts/publish.js bigtable", - "presystem-test": "node patches/patch", "system-test": "repo-tools test run --cmd mocha -- system-test/*.js --no-timeouts" }, "dependencies": { @@ -86,7 +85,6 @@ "eslint-config-prettier": "^2.6.0", "eslint-plugin-node": "^6.0.0", "eslint-plugin-prettier": "^2.3.1", - "glob": "^7.1.2", "ink-docstrap": "^1.3.0", "intelli-espower-loader": "^1.0.1", "jsdoc": "^3.5.5", diff --git a/patches/patched-process-nextick-args.js b/patches/patched-process-nextick-args.js deleted file mode 100644 index ab1a519f3..000000000 --- a/patches/patched-process-nextick-args.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function() { - return process.nextTick.apply(this, arguments); -}; From 5e58cd180c595d739df7a2f4f8a18378dd0491c4 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 2 Mar 2018 11:17:38 -0500 Subject: [PATCH 32/46] Revert "Remove patches" This reverts commit a6a3c8c5bc2b79b02d1ba24d5ab2b6024609341f. --- package.json | 2 ++ patches/patched-process-nextick-args.js | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 patches/patched-process-nextick-args.js diff --git a/package.json b/package.json index 70ccd3d9c..be8176161 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "lint": "repo-tools lint --cmd eslint -- src/ samples/ system-test/ test/", "prettier": "repo-tools exec -- prettier --write src/*.js src/*/*.js samples/*.js samples/*/*.js test/*.js test/*/*.js system-test/*.js system-test/*/*.js", "publish-module": "node ../../scripts/publish.js bigtable", + "presystem-test": "node patches/patch", "system-test": "repo-tools test run --cmd mocha -- system-test/*.js --no-timeouts" }, "dependencies": { @@ -85,6 +86,7 @@ "eslint-config-prettier": "^2.6.0", "eslint-plugin-node": "^6.0.0", "eslint-plugin-prettier": "^2.3.1", + "glob": "^7.1.2", "ink-docstrap": "^1.3.0", "intelli-espower-loader": "^1.0.1", "jsdoc": "^3.5.5", diff --git a/patches/patched-process-nextick-args.js b/patches/patched-process-nextick-args.js new file mode 100644 index 000000000..ab1a519f3 --- /dev/null +++ b/patches/patched-process-nextick-args.js @@ -0,0 +1,3 @@ +module.exports = function() { + return process.nextTick.apply(this, arguments); +}; From 5139d7e1421ffaa97202f55f1c762751cd4615e7 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 2 Mar 2018 13:12:37 -0500 Subject: [PATCH 33/46] Fix patch & use new test --- system-test/mutate-rows.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index 62801af42..991c22673 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -19,7 +19,10 @@ function dispatch(emitter, response) { let index = 0; setImmediate(next); + console.log('Executes') function next() { + console.log('Never executes') + if (index < emits.length) { const emit = emits[index]; index++; @@ -44,7 +47,7 @@ function getDeltas(array) { }, []); } -describe('Bigtable/Table', () => { +describe.only('Bigtable/Table', () => { const bigtable = new Bigtable(); bigtable.api = {}; bigtable.auth = { From 404b5a46b4b9b42b1d72178ffdd7afbbbe7b3891 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 2 Mar 2018 13:29:05 -0500 Subject: [PATCH 34/46] Get windows to run test --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index be8176161..e7eac0cb7 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "docs": "repo-tools exec -- jsdoc -c .jsdoc.js", "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", "test-no-cover": "repo-tools test run --cmd mocha -- test/*.js --no-timeouts", - "test": "repo-tools test run --cmd npm -- run cover", + "pretest": "npm run presystem-test", + "test": "mocha system-test/mutate-rows.js --timeout 0", "generate-scaffolding": "repo-tools generate all && repo-tools generate lib_samples_readme -l samples/ --config ../.cloud-repo-tools.json", "lint": "repo-tools lint --cmd eslint -- src/ samples/ system-test/ test/", "prettier": "repo-tools exec -- prettier --write src/*.js src/*/*.js samples/*.js samples/*/*.js test/*.js test/*/*.js system-test/*.js system-test/*/*.js", From e2d4429aa096fb73489755250cddc768f7fd8085 Mon Sep 17 00:00:00 2001 From: Moshe Kolodny Date: Fri, 2 Mar 2018 14:06:20 -0500 Subject: [PATCH 35/46] Fixed broken mutate-rows tests on GAPIC branch nextickv2 --- patches/patched-process-nextick-args.js | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 patches/patched-process-nextick-args.js diff --git a/patches/patched-process-nextick-args.js b/patches/patched-process-nextick-args.js deleted file mode 100644 index ab1a519f3..000000000 --- a/patches/patched-process-nextick-args.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function() { - return process.nextTick.apply(this, arguments); -}; From ed965f0f0558e63aac5986f96f7628d27d920192 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 2 Mar 2018 14:09:19 -0500 Subject: [PATCH 36/46] Revert "Get windows to run test" This reverts commit d34724c0aaa20af24d4ee7ad16fceea5941154c4. --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index e7eac0cb7..be8176161 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,7 @@ "docs": "repo-tools exec -- jsdoc -c .jsdoc.js", "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", "test-no-cover": "repo-tools test run --cmd mocha -- test/*.js --no-timeouts", - "pretest": "npm run presystem-test", - "test": "mocha system-test/mutate-rows.js --timeout 0", + "test": "repo-tools test run --cmd npm -- run cover", "generate-scaffolding": "repo-tools generate all && repo-tools generate lib_samples_readme -l samples/ --config ../.cloud-repo-tools.json", "lint": "repo-tools lint --cmd eslint -- src/ samples/ system-test/ test/", "prettier": "repo-tools exec -- prettier --write src/*.js src/*/*.js samples/*.js samples/*/*.js test/*.js test/*/*.js system-test/*.js system-test/*/*.js", From 7fe2fa0e304de95ddc9fc37d3baa811cfcd9bd26 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 2 Mar 2018 14:21:28 -0500 Subject: [PATCH 37/46] Tests work! --- package.json | 2 +- system-test/mutate-rows.js | 36 ++++++++++++++---------------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index be8176161..2c23d0db2 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "test": "repo-tools test run --cmd npm -- run cover", "generate-scaffolding": "repo-tools generate all && repo-tools generate lib_samples_readme -l samples/ --config ../.cloud-repo-tools.json", "lint": "repo-tools lint --cmd eslint -- src/ samples/ system-test/ test/", - "prettier": "repo-tools exec -- prettier --write src/*.js src/*/*.js samples/*.js samples/*/*.js test/*.js test/*/*.js system-test/*.js system-test/*/*.js", + "prettier": "repo-tools exec -- prettier --write patches/*.js src/*.js src/*/*.js samples/*.js samples/*/*.js test/*.js test/*/*.js system-test/*.js system-test/*/*.js", "publish-module": "node ../../scripts/publish.js bigtable", "presystem-test": "node patches/patch", "system-test": "repo-tools test run --cmd mocha -- system-test/*.js --no-timeouts" diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index 991c22673..38c9974db 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -19,10 +19,7 @@ function dispatch(emitter, response) { let index = 0; setImmediate(next); - console.log('Executes') function next() { - console.log('Never executes') - if (index < emits.length) { const emit = emits[index]; index++; @@ -47,7 +44,7 @@ function getDeltas(array) { }, []); } -describe.only('Bigtable/Table', () => { +describe('Bigtable/Table', () => { const bigtable = new Bigtable(); bigtable.api = {}; bigtable.auth = { @@ -69,12 +66,7 @@ describe.only('Bigtable/Table', () => { beforeEach(() => { clock = sinon.useFakeTimers({ - toFake: [ - 'setTimeout', - 'setImmediate', - 'Date', - 'nextTick', - ], + toFake: ['setTimeout', 'setImmediate', 'Date', 'nextTick'], }); mutationBatchesInvoked = []; mutationCallTimes = []; @@ -101,18 +93,6 @@ describe.only('Bigtable/Table', () => { responses = test.responses; TABLE.maxRetries = test.max_retries; TABLE.mutate(test.mutations_request, error => { - if (test.errors) { - const expectedIndices = test.errors.map(error => { - return error.index_in_mutations_request; - }); - assert.deepEqual(error.name, 'PartialFailureError'); - const actualIndices = error.errors.map(error => { - return test.mutations_request.indexOf(error.entry); - }); - assert.deepEqual(expectedIndices, actualIndices); - } else { - assert.ifError(error); - } assert.deepEqual( mutationBatchesInvoked, test.mutation_batches_invoked @@ -133,6 +113,18 @@ describe.only('Bigtable/Table', () => { assert(delta > minBackoff, message); assert(delta < maxBackoff, message); }); + if (test.errors) { + const expectedIndices = test.errors.map(error => { + return error.index_in_mutations_request; + }); + assert.deepEqual(error.name, 'PartialFailureError'); + const actualIndices = error.errors.map(error => { + return test.mutations_request.indexOf(error.entry); + }); + assert.deepEqual(expectedIndices, actualIndices); + } else { + assert.ifError(error); + } done(); }); clock.runAll(); From 9168f5d5a03ae84d14d5fa3138be14d6be5416dc Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 12 Mar 2018 15:20:07 -0400 Subject: [PATCH 38/46] revert v2 changes --- src/index.js | 55 ++++++-------------- src/instance.js | 66 +++++++++--------------- src/v2/bigtable_client.js | 2 + src/v2/bigtable_instance_admin_client.js | 5 -- system-test/bigtable.js | 2 +- 5 files changed, 45 insertions(+), 85 deletions(-) diff --git a/src/index.js b/src/index.js index 4a84fc780..898102820 100644 --- a/src/index.js +++ b/src/index.js @@ -533,13 +533,8 @@ Bigtable.prototype.createInstance = function(name, options, callback) { /** * Get Instance objects for all of your Compute instances. * - * @param {object} query Query object. - * @param {boolean} query.autoPaginate Have pagination handled - * automatically. Default: true. - * @param {number} query.maxApiCalls Maximum number of API calls to make. - * @param {number} query.maxResults Maximum number of results to return. - * @param {string} query.pageToken Token returned from a previous call, to - * request the next page of results. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.error An error returned while making this request. * @param {Instance[]} callback.instances List of all @@ -557,49 +552,38 @@ Bigtable.prototype.createInstance = function(name, options, callback) { * }); * * //- - * // To control how many API requests are made and page through the results - * // manually, set `autoPaginate` to false. - * //- - * const callback = function(err, instances, nextQuery, apiResponse) { - * if (nextQuery) { - * // More results exist. - * bigtable.getInstances(nextQuery, calback); - * } - * }; - * - * bigtable.getInstances({ - * autoPaginate: false - * }, callback); - * - * //- * // If the callback is omitted, we'll return a Promise. * //- * bigtable.getInstances().then(function(data) { * const instances = data[0]; * }); */ -Bigtable.prototype.getInstances = function(query, callback) { +Bigtable.prototype.getInstances = function(gaxOptions, callback) { var self = this; - if (is.function(query)) { - callback = query; - query = {}; + if (is.function(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; } - var reqOpts = extend({}, query, { + var reqOpts = { parent: this.projectName, - }); + }; + + // @TODO this option shouldn't exist in the GAPIC client. + // Ref: https://github.com/googleapis/nodejs-bigtable/pull/35/files#r173892576 + // gaxOptions.autoPaginate = false; this.request( { client: 'BigtableInstanceAdminClient', method: 'listInstances', reqOpts: reqOpts, - gaxOpts: query.gaxOptions, + gaxOpts: gaxOptions, }, function(err, resp) { if (err) { - callback(err, null, null, resp); + callback(err); return; } @@ -609,19 +593,14 @@ Bigtable.prototype.getInstances = function(query, callback) { return instance; }); - var nextQuery = null; - if (resp.nextPageToken) { - nextQuery = extend({}, query, {pageToken: resp.nextPageToken}); - } - - callback(null, instances, nextQuery, resp); + callback(null, instances, resp); } ); }; /** - * Get {@link Iinstance} objects for all of your Compute instances as a - * readable object stream. + * Get {@link Instance} objects for all of your Compute instances as a readable + * object stream. * * @param {object} [query] Configuration object. See * {@link Bigtable#getInstances} for a complete list of options. diff --git a/src/instance.js b/src/instance.js index f3cee43cd..4332fe60f 100644 --- a/src/instance.js +++ b/src/instance.js @@ -498,15 +498,8 @@ Instance.prototype.get = function(gaxOptions, callback) { /** * Get Cluster objects for all of your clusters. * - * @param {object} [query] Query object. - * @param {boolean} [query.autoPaginate=true] Have pagination handled - * automatically. - * @param {object} [query.gaxOptions] Request configuration options, outlined - * here: https://googleapis.github.io/gax-nodejs/CallSettings.html. - * @param {number} [query.maxApiCalls] Maximum number of API calls to make. - * @param {number} [query.maxResults] Maximum number of results to return. - * @param {string} [query.pageToken] Token returned from a previous call, to - * request the next page of results. + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/CallSettings.html. * @param {function} callback The callback function. * @param {?error} callback.error An error returned while making this request. * @param {Cluster[]} callback.clusters List of all @@ -525,57 +518,48 @@ Instance.prototype.get = function(gaxOptions, callback) { * }); * * //- - * // To control how many API requests are made and page through the results - * // manually, set `autoPaginate` to false. - * //- - * const callback = function(err, clusters, nextQuery, apiResponse) { - * if (nextQuery) { - * // More results exist. - * instance.getClusters(nextQuery, calback); - * } - * }; - * - * instance.getClusters({ - * autoPaginate: false - * }, callback); - * - * //- * // If the callback is omitted, we'll return a Promise. * //- * instance.getClusters().then(function(data) { * const clusters = data[0]; * }); */ -Instance.prototype.getClusters = function(query, callback) { +Instance.prototype.getClusters = function(gaxOptions, callback) { var self = this; - if (is.function(query)) { - callback = query; - query = {}; + if (is.function(gaxOptions)) { + callback = gaxOptions; + gaxOptions = {}; } - var reqOpts = extend({}, query, { - parent: this.id, - }); - delete reqOpts.gaxOptions; + var reqOpts = { + parent: this.projectName, + }; + + // @TODO this option shouldn't exist in the GAPIC client. + // Ref: https://github.com/googleapis/nodejs-bigtable/pull/35/files#r173892576 + gaxOptions.autoPaginate = false; this.bigtable.request( { client: 'BigtableInstanceAdminClient', method: 'listClusters', reqOpts: reqOpts, - gaxOpts: query.gaxOptions, + gaxOpts: gaxOptions, }, - function() { - if (arguments[1]) { - arguments[1] = arguments[1].map(function(clusterObj) { - var cluster = self.cluster(clusterObj.name); - cluster.metadata = clusterObj; - return cluster; - }); + function(err, resp) { + if (err) { + callback(err); + return; } - callback.apply(null, arguments); + var clusters = resp.clusters.map(function(instanceData) { + var cluster = self.cluster(clusterObj.name); + cluster.metadata = clusterObj; + return cluster; + }); + + callback(null, clusters, resp); } ); }; diff --git a/src/v2/bigtable_client.js b/src/v2/bigtable_client.js index 7f1e77c64..b8bac00a2 100644 --- a/src/v2/bigtable_client.js +++ b/src/v2/bigtable_client.js @@ -254,6 +254,7 @@ class BigtableClient { */ readRows(request, options) { options = options || {}; + return this._innerApiCalls.readRows(request, options); } @@ -425,6 +426,7 @@ class BigtableClient { */ mutateRows(request, options) { options = options || {}; + return this._innerApiCalls.mutateRows(request, options); } diff --git a/src/v2/bigtable_instance_admin_client.js b/src/v2/bigtable_instance_admin_client.js index f9ef54359..ece5ecec5 100644 --- a/src/v2/bigtable_instance_admin_client.js +++ b/src/v2/bigtable_instance_admin_client.js @@ -127,11 +127,6 @@ class BigtableInstanceAdminClient { 'nextPageToken', 'appProfiles' ), - listClusters: new gax.PageDescriptor( - 'pageToken', - 'nextPageToken', - 'clusters' - ), }; var protoFilesRoot = new gax.grpc.GoogleProtoFilesRoot(); protoFilesRoot = protobuf.loadSync( diff --git a/system-test/bigtable.js b/system-test/bigtable.js index 7eb823b07..e4b5a73fe 100644 --- a/system-test/bigtable.js +++ b/system-test/bigtable.js @@ -87,7 +87,7 @@ describe('Bigtable', function() { }); }); - describe('instances', function() { + describe.only('instances', function() { it('should get a list of instances', function(done) { bigtable.getInstances(function(err, instances) { assert.ifError(err); From 6ee22f5afd87dc13eef9156d175bd17ba5cd9e83 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 12 Mar 2018 16:07:42 -0400 Subject: [PATCH 39/46] remove non-paginated methods --- src/index.js | 44 --------------------- src/instance.js | 41 ++----------------- system-test/bigtable.js | 34 +--------------- test/index.js | 88 +++++------------------------------------ test/instance.js | 81 +++++++++++++++---------------------- 5 files changed, 45 insertions(+), 243 deletions(-) diff --git a/src/index.js b/src/index.js index 898102820..555777c3b 100644 --- a/src/index.js +++ b/src/index.js @@ -570,10 +570,6 @@ Bigtable.prototype.getInstances = function(gaxOptions, callback) { parent: this.projectName, }; - // @TODO this option shouldn't exist in the GAPIC client. - // Ref: https://github.com/googleapis/nodejs-bigtable/pull/35/files#r173892576 - // gaxOptions.autoPaginate = false; - this.request( { client: 'BigtableInstanceAdminClient', @@ -598,40 +594,6 @@ Bigtable.prototype.getInstances = function(gaxOptions, callback) { ); }; -/** - * Get {@link Instance} objects for all of your Compute instances as a readable - * object stream. - * - * @param {object} [query] Configuration object. See - * {@link Bigtable#getInstances} for a complete list of options. - * @returns {stream} - * - * @example - * const Bigtable = require('@google-cloud/bigtable'); - * const bigtable = new Bigtable(); - * - * bigtable.getInstancesStream() - * .on('error', console.error) - * .on('data', function(instance) { - * // `instance` is an Instance object. - * }) - * .on('end', function() { - * // All instances retrieved. - * }); - * - * //- - * // If you anticipate many results, you can end a stream early to prevent - * // unnecessary processing and API requests. - * //- - * bigtable.getInstancesStream() - * .on('data', function(instance) { - * this.end(); - * }); - */ -Bigtable.prototype.getInstancesStream = common.paginator.streamify( - 'getInstances' -); - /** * Get a reference to a Compute instance. * @@ -745,12 +707,6 @@ Bigtable.prototype.request = function(config, callback) { } }; -/*! Developer Documentation - * - * These methods can be auto-paginated. - */ -common.paginator.extend(Bigtable, ['getInstances']); - /*! Developer Documentation * * All async methods (except for streams) will return a Promise in the event diff --git a/src/instance.js b/src/instance.js index 4332fe60f..2a82e01ab 100644 --- a/src/instance.js +++ b/src/instance.js @@ -533,13 +533,9 @@ Instance.prototype.getClusters = function(gaxOptions, callback) { } var reqOpts = { - parent: this.projectName, + parent: this.id, }; - // @TODO this option shouldn't exist in the GAPIC client. - // Ref: https://github.com/googleapis/nodejs-bigtable/pull/35/files#r173892576 - gaxOptions.autoPaginate = false; - this.bigtable.request( { client: 'BigtableInstanceAdminClient', @@ -553,7 +549,7 @@ Instance.prototype.getClusters = function(gaxOptions, callback) { return; } - var clusters = resp.clusters.map(function(instanceData) { + var clusters = resp.clusters.map(function(clusterObj) { var cluster = self.cluster(clusterObj.name); cluster.metadata = clusterObj; return cluster; @@ -564,37 +560,6 @@ Instance.prototype.getClusters = function(gaxOptions, callback) { ); }; -/** - * Get {@link Cluster} objects for all of your clusters as a readable object - * stream. - * - * @param {object} [query] Configuration object. See - * {@link Instance#getClusters} for a complete list of options. - * @returns {stream} - * - * @example - * instance.getClustersStream() - * .on('error', console.error) - * .on('data', function(cluster) { - * // `cluster` is a Cluster object. - * }) - * .on('end', function() { - * // All clusters retrieved. - * }); - * - * //- - * // If you anticipate many results, you can end a stream early to prevent - * // unnecessary processing and API requests. - * //- - * instance.getClustersStream() - * .on('data', function(cluster) { - * this.end(); - * }); - */ -Instance.prototype.getClustersStream = common.paginator.streamify( - 'getClusters' -); - /** * Get the instance metadata. * @@ -846,7 +811,7 @@ Instance.prototype.table = function(name) { * * These methods can be auto-paginated. */ -common.paginator.extend(Instance, ['getClusters', 'getTables']); +common.paginator.extend(Instance, ['getTables']); /*! Developer Documentation * diff --git a/system-test/bigtable.js b/system-test/bigtable.js index e4b5a73fe..4b8cafa09 100644 --- a/system-test/bigtable.js +++ b/system-test/bigtable.js @@ -21,7 +21,6 @@ var async = require('async'); var uuid = require('uuid'); var Bigtable = require('../'); -var Instance = require('../src/instance.js'); var Cluster = require('../src/cluster.js'); var Table = require('../src/table.js'); var Family = require('../src/family.js'); @@ -87,7 +86,7 @@ describe('Bigtable', function() { }); }); - describe.only('instances', function() { + describe('instances', function() { it('should get a list of instances', function(done) { bigtable.getInstances(function(err, instances) { assert.ifError(err); @@ -96,22 +95,6 @@ describe('Bigtable', function() { }); }); - it('should get a list of instances in stream mode', function(done) { - var instances = []; - - bigtable - .getInstancesStream() - .on('error', done) - .on('data', function(instance) { - assert(instance instanceof Instance); - instances.push(instance); - }) - .on('end', function() { - assert(instances.length > 0); - done(); - }); - }); - it('should check if an instance exists', function(done) { INSTANCE.exists(function(err, exists) { assert.ifError(err); @@ -166,21 +149,6 @@ describe('Bigtable', function() { }); }); - it('should retrieve a list of clusters in stream mode', function(done) { - var clusters = []; - - INSTANCE.getClustersStream() - .on('error', done) - .on('data', function(cluster) { - assert(cluster instanceof Cluster); - clusters.push(cluster); - }) - .on('end', function() { - assert(clusters.length > 0); - done(); - }); - }); - it('should check if a cluster exists', function(done) { CLUSTER.exists(function(err, exists) { assert.ifError(err); diff --git a/test/index.js b/test/index.js index 7617a8366..3446a873d 100644 --- a/test/index.js +++ b/test/index.js @@ -68,15 +68,6 @@ function fakeRetryRequest() { ); } -var fakePaginator = { - extend: function() { - this.calledWith_ = arguments; - }, - streamify: function(methodName) { - return methodName; - }, -}; - function createFake(Class) { function Fake() { this.calledWith_ = arguments; @@ -98,7 +89,6 @@ describe('Bigtable', function() { before(function() { Bigtable = proxyquire('../', { '@google-cloud/common': { - paginator: fakePaginator, util: fakeUtil, }, 'google-auto-auth': fakeGoogleAutoAuth, @@ -141,17 +131,6 @@ describe('Bigtable', function() { } } - it('should extend the correct methods', function() { - var args = fakePaginator.calledWith_; - - assert.strictEqual(args[0], Bigtable); - assert.deepEqual(args[1], ['getInstances']); - }); - - it('should streamify the correct methods', function() { - assert.strictEqual(bigtable.getInstancesStream, 'getInstances'); - }); - it('should promisify all the things', function() { assert(promisified); }); @@ -484,8 +463,10 @@ describe('Bigtable', function() { bigtable.request = function(config) { assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); assert.strictEqual(config.method, 'listInstances'); - assert.strictEqual(config.reqOpts.parent, bigtable.projectName); - assert.strictEqual(config.gaxOpts, undefined); + assert.deepStrictEqual(config.reqOpts, { + parent: bigtable.projectName, + }); + assert.deepEqual(config.gaxOpts, {}); done(); }; @@ -500,40 +481,18 @@ describe('Bigtable', function() { done(); }; - bigtable.getInstances({gaxOptions}, assert.ifError); - }); - - it('should copy all query options', function(done) { - var fakeOptions = { - a: 'a', - b: 'b', - }; - - bigtable.request = function(config) { - Object.keys(fakeOptions).forEach(function(key) { - assert.strictEqual(config.reqOpts[key], fakeOptions[key]); - }); - - assert.notStrictEqual(config.reqOpts, fakeOptions); - done(); - }; - - bigtable.getInstances(fakeOptions, assert.ifError); + bigtable.getInstances(gaxOptions, assert.ifError); }); it('should return an error to the callback', function(done) { var error = new Error('err'); - var response = {}; bigtable.request = function(config, callback) { - callback(error, response); + callback(error); }; - bigtable.getInstances(function(err, instances, nextQuery, apiResponse) { + bigtable.getInstances(function(err) { assert.strictEqual(err, error); - assert.strictEqual(instances, null); - assert.strictEqual(nextQuery, null); - assert.strictEqual(apiResponse, response); done(); }); }); @@ -550,13 +509,10 @@ describe('Bigtable', function() { ], }; - var responseArg2 = {}; - var responseArg3 = {}; - var fakeInstances = [{}, {}]; bigtable.request = function(config, callback) { - callback(null, response, responseArg2, responseArg3); + callback(null, response); }; var instanceCount = 0; @@ -566,42 +522,16 @@ describe('Bigtable', function() { return fakeInstances[instanceCount++]; }; - bigtable.getInstances(function(err, instances, nextQuery, apiResponse) { + bigtable.getInstances(function(err, instances, apiResponse) { assert.ifError(err); assert.strictEqual(instances[0], fakeInstances[0]); assert.strictEqual(instances[0].metadata, response.instances[0]); assert.strictEqual(instances[1], fakeInstances[1]); assert.strictEqual(instances[1].metadata, response.instances[1]); - assert.strictEqual(nextQuery, null); assert.strictEqual(apiResponse, response); done(); }); }); - - it('should provide a nextQuery object', function(done) { - var response = { - instances: [], - nextPageToken: 'a', - }; - - var options = { - a: 'b', - }; - - bigtable.request = function(config, callback) { - callback(null, response); - }; - - bigtable.getInstances(options, function(err, instances, nextQuery) { - var expectedQuery = extend({}, options, { - pageToken: response.nextPageToken, - }); - - assert.ifError(err); - assert.deepEqual(nextQuery, expectedQuery); - done(); - }); - }); }); describe('instance', function() { diff --git a/test/instance.js b/test/instance.js index 9280e9632..1e855d87c 100644 --- a/test/instance.js +++ b/test/instance.js @@ -97,11 +97,10 @@ describe('Bigtable/Instance', function() { var args = fakePaginator.calledWith_; assert.strictEqual(args[0], Instance); - assert.deepEqual(args[1], ['getClusters', 'getTables']); + assert.deepEqual(args[1], ['getTables']); }); it('should streamify the correct methods', function() { - assert.strictEqual(instance.getClustersStream, 'getClusters'); assert.strictEqual(instance.getTablesStream, 'getTables'); }); @@ -550,53 +549,51 @@ describe('Bigtable/Instance', function() { instance.bigtable.request = function(config) { assert.strictEqual(config.client, 'BigtableInstanceAdminClient'); assert.strictEqual(config.method, 'listClusters'); - assert.strictEqual(config.reqOpts.parent, INSTANCE_ID); - assert.strictEqual(config.gaxOpts, undefined); + assert.deepStrictEqual(config.reqOpts, { + parent: INSTANCE_ID, + }); + assert.deepEqual(config.gaxOpts, {}); done(); }; instance.getClusters(assert.ifError); }); - it('should copy all query options', function(done) { - var options = { - a: 'a', - b: 'b', - }; + it('should accept gaxOptions', function(done) { + var gaxOptions = {}; instance.bigtable.request = function(config) { - Object.keys(options).forEach(function(key) { - assert.strictEqual(config.reqOpts[key], options[key]); - }); - assert.notStrictEqual(config.reqOpts, options); + assert.strictEqual(config.gaxOpts, gaxOptions); done(); }; - instance.getClusters(options, assert.ifError); + instance.getClusters(gaxOptions, assert.ifError); }); - it('should accept gaxOptions', function(done) { - var options = { - gaxOptions: {}, - }; + it('should return error from gapic', function(done) { + var error = new Error('Error.'); - instance.bigtable.request = function(config) { - assert.strictEqual(config.gaxOpts, options.gaxOptions); - done(); + instance.bigtable.request = function(config, callback) { + callback(error); }; - instance.getClusters(options, assert.ifError); + instance.getClusters(function(err) { + assert.strictEqual(err, error); + done(); + }); }); it('should return an array of cluster objects', function(done) { - var response = [ - { - name: 'a', - }, - { - name: 'b', - }, - ]; + var response = { + clusters: [ + { + name: 'a', + }, + { + name: 'b', + }, + ], + }; var fakeClusters = [{}, {}]; @@ -607,31 +604,17 @@ describe('Bigtable/Instance', function() { var clusterCount = 0; instance.cluster = function(name) { - assert.strictEqual(name, response[clusterCount].name); + assert.strictEqual(name, response.clusters[clusterCount].name); return fakeClusters[clusterCount++]; }; - instance.getClusters(function(err, clusters) { + instance.getClusters(function(err, clusters, apiResponse) { assert.ifError(err); assert.strictEqual(clusters[0], fakeClusters[0]); - assert.strictEqual(clusters[0].metadata, response[0]); + assert.strictEqual(clusters[0].metadata, response.clusters[0]); assert.strictEqual(clusters[1], fakeClusters[1]); - assert.strictEqual(clusters[1].metadata, response[1]); - done(); - }); - }); - - it('should return original GAPIC response arguments', function(done) { - var response = [{}, null, {}, {}]; - - instance.bigtable.request = function(config, callback) { - callback.apply(null, response); - }; - - instance.getClusters(function() { - assert.strictEqual(arguments[0], response[0]); - assert.strictEqual(arguments[2], response[2]); - assert.strictEqual(arguments[3], response[3]); + assert.strictEqual(clusters[1].metadata, response.clusters[1]); + assert.strictEqual(apiResponse, response); done(); }); }); From 10db88819a3fac89c5fbdea3fc1b5c67fffd1cf9 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 12 Mar 2018 16:18:39 -0400 Subject: [PATCH 40/46] remove old test stuff --- src/table.js | 6 +++--- system-test/mutate-rows.js | 1 - test/table.js | 22 ---------------------- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/table.js b/src/table.js index 33c15b94c..2bab42fd6 100644 --- a/src/table.js +++ b/src/table.js @@ -1136,8 +1136,8 @@ Table.prototype.mutate = function(entries, gaxOptions, callback) { var entryToIndex = new Map(entries.map((entry, index) => [entry, index])); var mutationErrorsByEntryIndex = new Map(); - function onBatchResponse(previousNumRequestsMade, err) { - if (previousNumRequestsMade === numRequestsMade && err) { + function onBatchResponse(err) { + if (err) { // The error happened before a request was even made, don't retry. callback(err); return; @@ -1180,7 +1180,7 @@ Table.prototype.mutate = function(entries, gaxOptions, callback) { retryOpts: retryOpts, }) .on('request', () => numRequestsMade++) - .on('error', onBatchResponse.bind(null, numRequestsMade)) + .on('error', onBatchResponse) .on('data', function(obj) { obj.entries.forEach(function(entry) { var originalEntry = entryBatch[entry.index]; diff --git a/system-test/mutate-rows.js b/system-test/mutate-rows.js index 38c9974db..e86c352a2 100644 --- a/system-test/mutate-rows.js +++ b/system-test/mutate-rows.js @@ -53,7 +53,6 @@ describe('Bigtable/Table', () => { }, }; bigtable.grpcCredentials = grpc.credentials.createInsecure(); - bigtable.projectId = 'test'; const INSTANCE = bigtable.instance('instance'); const TABLE = INSTANCE.table('table'); diff --git a/test/table.js b/test/table.js index 20f5aa3f1..3a55baccb 100644 --- a/test/table.js +++ b/test/table.js @@ -1733,28 +1733,6 @@ describe('Bigtable/Table', function() { done(); }); }); - - it('should not retry a pre request error', function(done) { - var calls = 0; - var error = new Error('err'); - table.requestStream = function() { - calls++; - var stream = new Stream({ - objectMode: true, - }); - - setImmediate(function() { - stream.emit('error', error); - }); - - return stream; - }; - table.mutate(entries, function(err) { - assert.strictEqual(calls, 1); - assert.strictEqual(err, error); - done(); - }); - }); }); }); From 6bbc8bd6fe9c82bf98ef867514c782f6d9734645 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 13 Mar 2018 09:58:11 -0400 Subject: [PATCH 41/46] Use `instanceof` FamilyError check. --- src/family.js | 2 +- test/family.js | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/family.js b/src/family.js index 6216de6eb..648f92e08 100644 --- a/src/family.js +++ b/src/family.js @@ -240,7 +240,7 @@ Family.prototype.exists = function(gaxOptions, callback) { this.getMetadata(gaxOptions, function(err) { if (err) { - if (err.name === 'FamilyError') { + if (err instanceof FamilyError) { callback(null, false); return; } diff --git a/test/family.js b/test/family.js index 5894d335f..1479e0c76 100644 --- a/test/family.js +++ b/test/family.js @@ -244,9 +244,8 @@ describe('Bigtable/Family', function() { family.exists(gaxOptions, assert.ifError); }); - it('should return false if error name is FamilyError', function(done) { - var error = new Error('Error.'); - error.name = 'FamilyError'; + it('should return false if FamilyError', function(done) { + var error = new FamilyError('Error.'); family.getMetadata = function(gaxOptions, callback) { callback(error); @@ -259,9 +258,8 @@ describe('Bigtable/Family', function() { }); }); - it('should return error if name is not FamilyError', function(done) { + it('should return error if not FamilyError', function(done) { var error = new Error('Error.'); - error.name = 'NOT-FamilyError'; family.getMetadata = function(gaxOptions, callback) { callback(error); From e0eda5157107248ae4bd60813f8458f42c4ee37b Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 13 Mar 2018 10:29:50 -0400 Subject: [PATCH 42/46] rename options --- src/index.js | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/index.js b/src/index.js index 555777c3b..88160909d 100644 --- a/src/index.js +++ b/src/index.js @@ -355,6 +355,12 @@ function Bigtable(options) { } } + options = extend({ + libName: 'gccl', + libVersion: PKG.version, + scopes: scopes, + }, options); + var defaultBaseUrl = 'bigtable.googleapis.com'; var defaultAdminBaseUrl = 'bigtableadmin.googleapis.com'; @@ -369,15 +375,6 @@ function Bigtable(options) { customEndpointPort = customEndpointParts[1]; } - var options_ = extend( - { - libName: 'gccl', - libVersion: PKG.version, - scopes: scopes, - }, - options - ); - this.options = { BigtableClient: extend( { @@ -387,7 +384,7 @@ function Bigtable(options) { ? grpc.credentials.createInsecure() : undefined, }, - options_ + options ), BigtableInstanceAdminClient: extend( { @@ -399,7 +396,7 @@ function Bigtable(options) { ? grpc.credentials.createInsecure() : undefined, }, - options_ + options ), BigtableTableAdminClient: extend( { @@ -411,12 +408,12 @@ function Bigtable(options) { ? grpc.credentials.createInsecure() : undefined, }, - options_ + options ), }; this.api = {}; - this.auth = googleAuth(options_); + this.auth = googleAuth(options); this.projectId = options.projectId || '{{projectId}}'; this.projectName = 'projects/' + this.projectId; } From bbcbe2586dbb909f599892e766fbf613c06332e0 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 13 Mar 2018 10:31:14 -0400 Subject: [PATCH 43/46] Restore `sinon.restore()` call. --- test/filter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/filter.js b/test/filter.js index 9248f9610..637d4754c 100644 --- a/test/filter.js +++ b/test/filter.js @@ -43,6 +43,7 @@ describe('Bigtable/Filter', function() { afterEach(function() { FakeMutation.convertToBytes.resetHistory(); + sinon.restore(); }); describe('instantiation', function() { From a7ccc12708793bf2c6e0587ef945bbb8414cead7 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 13 Mar 2018 10:43:41 -0400 Subject: [PATCH 44/46] Put back pre-request error handler --- src/table.js | 9 ++++++++- test/table.js | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/table.js b/src/table.js index 2bab42fd6..0c7b8c98d 100644 --- a/src/table.js +++ b/src/table.js @@ -1180,7 +1180,14 @@ Table.prototype.mutate = function(entries, gaxOptions, callback) { retryOpts: retryOpts, }) .on('request', () => numRequestsMade++) - .on('error', onBatchResponse) + .on('error', err => { + if (numRequestsMade === 0) { + callback(err); // Likely a "projectId not detected" error. + return; + } + + onBatchResponse(err); + }) .on('data', function(obj) { obj.entries.forEach(function(entry) { var originalEntry = entryBatch[entry.index]; diff --git a/test/table.js b/test/table.js index 3a55baccb..be9b9f3ad 100644 --- a/test/table.js +++ b/test/table.js @@ -1545,6 +1545,31 @@ describe('Bigtable/Table', function() { }); describe('error', function() { + describe('pre-request errors', function() { + var error = new Error('Error.'); + + beforeEach(function() { + table.bigtable.request = function() { + var stream = new Stream({ + objectMode: true, + }); + + setImmediate(function() { + stream.emit('error', error); + }); + + return stream; + }; + }); + + it('should return error', function(done) { + table.mutate(entries, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + }); + describe('API errors', function() { var error = new Error('err'); From 4e5ca66b0f7c2e627cb6bde145885cdddb9edc74 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 13 Mar 2018 10:44:03 -0400 Subject: [PATCH 45/46] prettier --- src/index.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index 88160909d..bbc5476ae 100644 --- a/src/index.js +++ b/src/index.js @@ -355,11 +355,14 @@ function Bigtable(options) { } } - options = extend({ - libName: 'gccl', - libVersion: PKG.version, - scopes: scopes, - }, options); + options = extend( + { + libName: 'gccl', + libVersion: PKG.version, + scopes: scopes, + }, + options + ); var defaultBaseUrl = 'bigtable.googleapis.com'; var defaultAdminBaseUrl = 'bigtableadmin.googleapis.com'; From b236b42f168a4119f60e74d5e526656602c2f429 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 13 Mar 2018 11:12:16 -0400 Subject: [PATCH 46/46] remove unnecessary resetHistory call --- test/filter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/filter.js b/test/filter.js index 637d4754c..f3f308d5c 100644 --- a/test/filter.js +++ b/test/filter.js @@ -42,7 +42,6 @@ describe('Bigtable/Filter', function() { }); afterEach(function() { - FakeMutation.convertToBytes.resetHistory(); sinon.restore(); });