diff --git a/packages/bigtable/package.json b/packages/bigtable/package.json
index e1d9ded8aa6..0576b56c545 100644
--- a/packages/bigtable/package.json
+++ b/packages/bigtable/package.json
@@ -50,13 +50,13 @@
"bigtable"
],
"dependencies": {
- "@google-cloud/common": "^0.1.0",
"arrify": "^1.0.0",
"concat-stream": "^1.5.0",
"create-error-class": "^2.0.1",
"dot-prop": "^2.4.0",
"extend": "^3.0.0",
- "google-proto-files": "^0.2.1",
+ "@google-cloud/common": "^0.3.0",
+ "google-proto-files": "^0.7.0",
"is": "^3.0.1",
"lodash.flatten": "^4.2.0",
"node-int64": "^0.4.0",
diff --git a/packages/bigtable/src/cluster.js b/packages/bigtable/src/cluster.js
new file mode 100644
index 00000000000..ddd7595b94a
--- /dev/null
+++ b/packages/bigtable/src/cluster.js
@@ -0,0 +1,275 @@
+/*!
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*!
+ * @module bigtable/instance
+ */
+
+'use strict';
+
+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.
+ *
+ * @constructor
+ * @alias module:bigtable/cluster
+ *
+ * @param {string} name - Name of the cluster.
+ *
+ * @example
+ * var instance = bigtable.instance('my-instance');
+ * var cluster = instance.cluster('my-cluster');
+ */
+function Cluster(instance, name) {
+ var id = name;
+
+ if (id.indexOf('/') === -1) {
+ id = instance.id + '/clusters/' + name;
+ }
+
+ var methods = {
+
+ /**
+ * Create a cluster.
+ *
+ * @param {object} options - See {module:bigtable/instance#createCluster}
+ *
+ * @example
+ * 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.
+ * });
+ * });
+ */
+ create: true,
+
+ /**
+ * Delete the cluster.
+ *
+ * @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) {});
+ */
+ delete: {
+ protoOpts: {
+ service: 'BigtableInstanceAdmin',
+ method: 'deleteCluster'
+ },
+ reqOpts: {
+ name: id
+ }
+ },
+
+ /**
+ * Check if a 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) {});
+ */
+ exists: true,
+
+ /**
+ * Get a cluster if it exists.
+ *
+ * @example
+ * cluster.get(function(err, cluster, apiResponse) {
+ * // The `cluster` data has been populated.
+ * });
+ */
+ get: true,
+
+ /**
+ * 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) {});
+ */
+ getMetadata: {
+ protoOpts: {
+ service: 'BigtableInstanceAdmin',
+ method: 'getCluster'
+ },
+ reqOpts: {
+ name: id
+ }
+ }
+ };
+
+ var config = {
+ parent: instance,
+ id: id,
+ methods: methods,
+ createMethod: function(_, options, callback) {
+ instance.createCluster(name, options, callback);
+ }
+ };
+
+ common.GrpcServiceObject.call(this, config);
+}
+
+util.inherits(Cluster, common.GrpcServiceObject);
+
+/**
+ * Formats zone location.
+ *
+ * @private
+ *
+ * @param {string} project - The project.
+ * @param {string} location - The zone location.
+ * @return {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).
+ * @return {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.
+ *
+ * See {module:bigtable/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 {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
+ * to check the status of the request.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * var callback = function(err, operation, apiResponse) {
+ * if (err) {
+ * // Error handling omitted.
+ * }
+ *
+ * operation
+ * .on('error', console.error)
+ * .on('complete', function() {
+ * // The cluster was updated successfully.
+ * });
+ * };
+ *
+ * cluster.setMetadata({
+ * location: 'us-central1-b',
+ * nodes: 3,
+ * storage: 'ssd'
+ * }, callback);
+ */
+Cluster.prototype.setMetadata = function(options, callback) {
+ var protoOpts = {
+ service: 'BigtableInstanceAdmin',
+ method: 'updateCluster'
+ };
+
+ var reqOpts = {
+ name: this.id
+ };
+
+ var bigtable = this.parent.parent;
+
+ if (options.location) {
+ reqOpts.location = Cluster.getLocation_(
+ bigtable.projectId,
+ options.location
+ );
+ }
+
+ if (options.nodes) {
+ reqOpts.serveNodes = options.nodes;
+ }
+
+ if (options.storage) {
+ reqOpts.defaultStorageType = Cluster.getStorageType_(options.storage);
+ }
+
+ this.request(protoOpts, reqOpts, function(err, resp) {
+ if (err) {
+ callback(err, null, resp);
+ return;
+ }
+
+ var operation = bigtable.operation(resp.name);
+ operation.metadata = resp;
+
+ callback(null, operation, resp);
+ });
+};
+
+module.exports = Cluster;
diff --git a/packages/bigtable/src/family.js b/packages/bigtable/src/family.js
index c83c27e26fd..15e37aa2467 100644
--- a/packages/bigtable/src/family.js
+++ b/packages/bigtable/src/family.js
@@ -22,7 +22,6 @@
var common = require('@google-cloud/common');
var createErrorClass = require('create-error-class');
-var is = require('is');
var util = require('util');
/**
@@ -40,11 +39,13 @@ var FamilyError = createErrorClass('FamilyError', function(name) {
* @alias module:bigtable/family
*
* @example
- * var table = bigtable.table('prezzy');
+ * var instance = bigtable.instance('my-instance');
+ * var table = instance.table('prezzy');
* var family = table.family('follows');
*/
function Family(table, name) {
var id = Family.formatName_(table.id, name);
+ this.familyName = name.split('/').pop();
var methods = {
@@ -73,11 +74,15 @@ function Family(table, name) {
*/
delete: {
protoOpts: {
- service: 'BigtableTableService',
- method: 'deleteColumnFamily'
+ service: 'BigtableTableAdmin',
+ method: 'modifyColumnFamilies'
},
reqOpts: {
- name: id
+ name: table.id,
+ modifications: [{
+ id: this.familyName,
+ drop: true
+ }]
}
},
@@ -256,8 +261,7 @@ Family.prototype.getMetadata = function(callback) {
* @resource [Garbage Collection Proto Docs]{@link https://github.com/googleapis/googleapis/blob/3592a7339da5a31a3565870989beb86e9235476e/google/bigtable/admin/table/v1/bigtable_table_data.proto#L59}
*
* @param {object} metadata - Metadata object.
- * @param {object|string=} metadata.rule - Garbage collection rule.
- * @param {string=} metadata.name - The updated column family name.
+ * @param {object=} metadata.rule - Garbage collection rule.
* @param {function} callback - The callback function.
* @param {?error} callback.err - An error returned while making this
* request.
@@ -265,33 +269,43 @@ Family.prototype.getMetadata = function(callback) {
*
* @example
* family.setMetadata({
- * name: 'updated-name',
- * rule: 'version() > 3 || (age() > 3d && version() > 1)'
+ * rule: {
+ * versions: 2,
+ * union: true
+ * }
* }, function(err, apiResponse) {});
*/
Family.prototype.setMetadata = function(metadata, callback) {
+ var self = this;
+
var grpcOpts = {
- service: 'BigtableTableService',
- method: 'updateColumnFamily'
+ service: 'BigtableTableAdmin',
+ method: 'modifyColumnFamilies'
};
- var reqOpts = {
- name: this.id
+ var mod = {
+ id: this.familyName,
+ update: {}
};
if (metadata.rule) {
- if (is.string(metadata.rule)) {
- reqOpts.gcExpression = metadata.rule;
- } else if (is.object(metadata.rule)) {
- reqOpts.gcRule = Family.formatRule_(metadata.rule);
- }
+ mod.update.gcRule = Family.formatRule_(metadata.rule);
}
- if (metadata.name) {
- reqOpts.name = Family.formatName_(this.parent.id, metadata.name);
- }
+ var reqOpts = {
+ name: this.parent.id,
+ modifications: [mod]
+ };
- this.request(grpcOpts, reqOpts, callback);
+ this.request(grpcOpts, reqOpts, function(err, resp) {
+ if (err) {
+ callback(err, null, resp);
+ return;
+ }
+
+ self.metadata = resp.columnFamilies[self.familyName];
+ callback(null, self.metadata, resp);
+ });
};
module.exports = Family;
diff --git a/packages/bigtable/src/filter.js b/packages/bigtable/src/filter.js
index 08340bbf6f3..e5a511d57f8 100644
--- a/packages/bigtable/src/filter.js
+++ b/packages/bigtable/src/filter.js
@@ -170,7 +170,7 @@ Filter.createRange = function(start, end, key) {
function createBound(boundName, boundData, key) {
var isInclusive = boundData.inclusive !== false;
- var boundKey = boundName + key + (isInclusive ? 'Inclusive' : 'Exclusive');
+ var boundKey = boundName + key + (isInclusive ? 'Closed' : 'Open');
var bound = {};
bound[boundKey] = Mutation.convertToBytes(boundData.value || boundData);
diff --git a/packages/bigtable/src/index.js b/packages/bigtable/src/index.js
index 276fc0e0f35..943bdd12bc7 100644
--- a/packages/bigtable/src/index.js
+++ b/packages/bigtable/src/index.js
@@ -20,24 +20,24 @@
'use strict';
+var arrify = require('arrify');
var common = require('@google-cloud/common');
var extend = require('extend');
-var format = require('string-format-obj');
var googleProtoFiles = require('google-proto-files');
var is = require('is');
var util = require('util');
/**
- * @type {module:bigtable/family}
* @private
+ * @type {module:bigtable/instance}
*/
-var Family = require('./family.js');
+var Instance = require('./instance.js');
/**
- * @type {module:bigtable/table}
* @private
+ * @type {module:bigtable/cluster}
*/
-var Table = require('./table.js');
+var Cluster = require('./cluster.js');
var PKG = require('../package.json');
@@ -48,31 +48,49 @@ var PKG = require('../package.json');
* @resource [Creating a Cloud Bigtable Cluster]{@link https://cloud.google.com/bigtable/docs/creating-compute-instance}
* @resource [Google Cloud Bigtable Concepts Overview]{@link https://cloud.google.com/bigtable/docs/concepts}
*
- * @throws {error} If a cluster is not provided.
- * @throws {error} If a zone is not provided.
- *
* @param {object=} options - [Configuration object](#/docs).
- * @param {string} options.cluster - The cluster name that hosts your tables.
- * @param {string|module:compute/zone} options.zone - The zone in which your
- * cluster resides.
*
+ * @example
* //-
- * //
Creating a Cluster
+ * // Creating a Compute Instance
* //
- * // Before you create your table, you first need to create a Bigtable Cluster
- * // for the table to be served from. This can be done from either the
- * // Google Cloud Platform Console or the `gcloud` cli tool. Please refer to
- * // the
+ * // Before you create your table, you first need to create a Compute Instance
+ * // for the table to be served from.
+ * //-
+ * var callback = function(err, instance, operation) {
+ * operation
+ * .on('error', console.log)
+ * .on('complete', function() {
+ * // `instance` is your newly created Instance object.
+ * });
+ * };
+ *
+ * var instance = bigtable.instance('my-instance');
+ *
+ * instance.create({
+ * clusters: [
+ * {
+ * name: 'my-cluster',
+ * location: 'us-central1-b',
+ * nodes: 3
+ * }
+ * ]
+ * }, callback);
+ *
+ * //-
+ * // This can also be done from either the Google Cloud Platform Console or the
+ * // `gcloud` cli tool. Please refer to the
+ * //
* // official Bigtable documentation for more information.
* //-
*
* //-
* // Creating Tables
* //
- * // After creating your cluster and enabling the Bigtable APIs, you are now
- * // ready to create your table with {module:bigtable#createTable}.
+ * // After creating your instance and enabling the Bigtable APIs, you are now
+ * // ready to create your table with {module:bigtable/instance#createTable}.
* //-
- * bigtable.createTable('prezzy', function(err, table) {
+ * instance.createTable('prezzy', function(err, table) {
* // `table` is your newly created Table object.
* });
*
@@ -85,13 +103,23 @@ var PKG = require('../package.json');
* //
* // We can create a column family with {module:bigtable/table#createFamily}.
* //-
- * var table = bigtable.table('prezzy');
+ * var table = instance.table('prezzy');
*
* table.createFamily('follows', function(err, family) {
* // `family` is your newly created Family object.
* });
*
* //-
+ * // It is also possible to create your column families when creating a new
+ * // table.
+ * //-
+ * var options = {
+ * families: ['follows']
+ * };
+ *
+ * instance.createTable('prezzy', options, function(err, table) {});
+ *
+ * //-
* // Creating Rows
* //
* // New rows can be created within your table using
@@ -173,7 +201,6 @@ var PKG = require('../package.json');
* // with all previous versions of the data. So your `row.data` object could
* // resemble the following.
* //-
- * console.log(row.data);
* // {
* // follows: {
* // wmckinley: [
@@ -262,267 +289,262 @@ function Bigtable(options) {
return new Bigtable(options);
}
- if (!options.cluster) {
- throw new Error('A cluster must be provided to interact with Bigtable.');
- }
-
- if (!options.zone) {
- throw new Error('A zone must be provided to interact with Bigtable.');
- }
-
- options = extend({}, options, {
- zone: options.zone.name || options.zone
- });
-
- this.clusterName = format(
- 'projects/{projectId}/zones/{zone}/clusters/{cluster}',
- options
- );
+ var adminBaseUrl = 'bigtableadmin.googleapis.com';
var config = {
baseUrl: 'bigtable.googleapis.com',
service: 'bigtable',
- apiVersion: 'v1',
+ apiVersion: 'v2',
protoServices: {
- BigtableService: googleProtoFiles.bigtable.v1,
- BigtableTableService: {
- path: googleProtoFiles.bigtable.admin,
- service: 'bigtable.admin.table'
+ Bigtable: googleProtoFiles.bigtable.v2,
+ BigtableTableAdmin: {
+ baseUrl: adminBaseUrl,
+ path: googleProtoFiles.bigtable.admin.v2.table,
+ service: 'bigtable.admin'
+ },
+ BigtableInstanceAdmin: {
+ baseUrl: adminBaseUrl,
+ path: googleProtoFiles.bigtable.admin.v2.instance,
+ service: 'bigtable.admin'
+ },
+ Operations: {
+ baseUrl: adminBaseUrl,
+ path: googleProtoFiles('longrunning/operations.proto'),
+ service: 'longrunning',
+ apiVersion: 'v1'
}
},
scopes: [
'https://www.googleapis.com/auth/bigtable.admin',
- 'https://www.googleapis.com/auth/bigtable.data'
+ 'https://www.googleapis.com/auth/bigtable.data',
+ 'https://www.googleapis.com/auth/cloud-platform'
],
userAgent: PKG.name + '/' + PKG.version
};
common.GrpcService.call(this, config, options);
+
+ this.projectName = 'projects/' + this.projectId;
}
util.inherits(Bigtable, common.GrpcService);
/**
- * Formats the full table name into a user friendly version.
+ * Create a Compute instance.
*
- * @private
- *
- * @param {string} name - The formatted Table name.
- * @return {string}
+ * @resource [Creating a Compute Instance]{@link https://cloud.google.com/bigtable/docs/creating-compute-instance}
*
- * @example
- * Bigtable.formatTableName_('projects/p/zones/z/clusters/c/tables/my-table');
- * // => 'my-table'
- */
-Bigtable.formatTableName_ = function(name) {
- if (name.indexOf('/') === -1) {
- return name;
- }
-
- var parts = name.split('/');
- return parts[parts.length - 1];
-};
-
-/**
- * Create a table on your Bigtable cluster.
- *
- * @resource [Designing Your Schema]{@link https://cloud.google.com/bigtable/docs/schema-design}
- * @resource [Splitting Keys]{@link https://cloud.google.com/bigtable/docs/managing-tables#splits}
- *
- * @throws {error} If a name is not provided.
- *
- * @param {string} name - The name of the table.
- * @param {object=} options - Table creation options.
- * @param {object|string[]} options.families - Column families to be created
- * within the table.
- * @param {string} options.operation - Operation used for table that has already
- * been queued to be created.
- * @param {string[]} options.splits - Initial
- * [split keys](https://cloud.google.com/bigtable/docs/managing-tables#splits).
+ * @param {string} name - The unique name of the instance.
+ * @param {object=} options - Instance creation options.
+ * @param {object[]} options.clusters - The clusters to be created within the
+ * instance.
+ * @param {string} options.displayName - The descriptive name for this instance
+ * as it appears in UIs.
* @param {function} callback - The callback function.
* @param {?error} callback.err - An error returned while making this request.
- * @param {module:bigtable/table} callback.table - The newly created table.
+ * @param {module:bigtable/instance} callback.instance - The newly created
+ * instance.
+ * @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
- * var callback = function(err, table, apiResponse) {
- * // `table` is a Table object.
- * };
- *
- * bigtable.createTable('prezzy', callback);
+ * var callback = function(err, instance, operation, apiResponse) {
+ * if (err) {
+ * // Error handling omitted.
+ * }
*
- * //-
- * // Optionally specify column families to be created within the table.
- * //-
- * var options = {
- * families: ['follows']
+ * operation
+ * .on('error', console.log)
+ * .on('complete', function() {
+ * // The instance was created successfully.
+ * });
* };
*
- * bigtable.createTable('prezzy', options, callback);
- *
- * //-
- * // You can also specify garbage collection rules for your column families.
- * // See {module:bigtable/table#createFamily} for more information about
- * // column families and garbage collection rules.
- * //-
* var options = {
- * families: [
+ * displayName: 'my-sweet-instance',
+ * clusters: [
* {
- * name: 'follows',
- * rule: {
- * age: {
- * seconds: 0,
- * nanos: 5000
- * },
- * versions: 3,
- * union: true
- * }
+ * name: 'my-sweet-cluster',
+ * nodes: 3,
+ * location: 'us-central1-b',
+ * storage: 'ssd'
* }
* ]
* };
*
- * bigtable.createTable('prezzy', options, callback);
- *
- * //-
- * // Pre-split the table based on the row key to spread the load across
- * // multiple Cloud Bigtable nodes.
- * //-
- * var options = {
- * splits: ['10', '20']
- * };
- *
- * bigtable.createTable('prezzy', options, callback);
+ * bigtable.createInstance('my-instance', options, callback);
*/
-Bigtable.prototype.createTable = function(name, options, callback) {
+Bigtable.prototype.createInstance = function(name, options, callback) {
var self = this;
- options = options || {};
-
if (is.function(options)) {
callback = options;
options = {};
}
- if (!name) {
- throw new Error('A name is required to create a table.');
- }
-
var protoOpts = {
- service: 'BigtableTableService',
- method: 'createTable'
+ service: 'BigtableInstanceAdmin',
+ method: 'createInstance'
};
var reqOpts = {
- name: this.clusterName,
- tableId: name,
- table: {
- // The granularity at which timestamps are stored in the table.
- // Currently only milliseconds is supported, so it's not configurable.
- granularity: 0
+ parent: this.projectName,
+ instanceId: name,
+ instance: {
+ displayName: options.displayName || name
}
};
- if (options.operation) {
- reqOpts.table.currentOperation = options.operation;
- }
-
- if (options.splits) {
- reqOpts.initialSplitKeys = options.splits;
- }
-
- if (options.families) {
- var columnFamilies = options.families.reduce(function(families, family) {
- if (is.string(family)) {
- family = {
- name: family
- };
- }
-
- var columnFamily = families[family.name] = {};
+ reqOpts.clusters = arrify(options.clusters)
+ .reduce(function(clusters, cluster) {
+ clusters[cluster.name] = {
+ location: Cluster.getLocation_(self.projectId, cluster.location),
+ serveNodes: cluster.nodes,
+ defaultStorageType: Cluster.getStorageType_(cluster.storage)
+ };
- if (is.string(family.rule)) {
- columnFamily.gcExpression = family.rule;
- } else if (is.object(family.rule)) {
- columnFamily.gcRule = Family.formatRule_(family.rule);
- }
-
- return families;
+ return clusters;
}, {});
- reqOpts.table.columnFamilies = columnFamilies;
- }
-
this.request(protoOpts, reqOpts, function(err, resp) {
if (err) {
- callback(err, null, resp);
+ callback(err, null, null, resp);
return;
}
- var table = self.table(resp.name);
- table.metadata = resp;
+ var instance = self.instance(name);
+ var operation = self.operation(resp.name);
+ operation.metadata = resp;
- callback(null, table, resp);
+ callback(null, instance, operation, resp);
});
};
/**
- * Get Table objects for all the tables in your Bigtable cluster.
- *
+ * 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 {function} callback - The callback function.
- * @param {?error} callback.err - An error returned while making this request.
- * @param {module:bigtable/table[]} callback.tables - List of all Tables.
+ * @param {?error} callback.error - An error returned while making this request.
+ * @param {module:bigtable/instance[]} callback.instances - List of all
+ * instances.
+ * @param {object} callback.nextQuery - If present, query with this object to
+ * check for more results.
* @param {object} callback.apiResponse - The full API response.
*
* @example
- * bigtable.getTables(function(err, tables) {
+ * bigtable.getInstances(function(err, instances) {
* if (!err) {
- * // `tables` is an array of Table objects.
+ * // `instances` is an array of Instance objects.
* }
* });
+ *
+ * //-
+ * // To control how many API requests are made and page through the results
+ * // manually, set `autoPaginate` to false.
+ * //-
+ * var callback = function(err, instances, nextQuery, apiResponse) {
+ * if (nextQuery) {
+ * // More results exist.
+ * bigtable.getInstances(nextQuery, calback);
+ * }
+ * };
+ *
+ * bigtable.getInstances({
+ * autoPaginate: false
+ * }, callback);
+ *
+ * //-
+ * // Get the instances from your project as a readable object stream.
+ * //-
+ * bigtable.getInstances()
+ * .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.getInstances()
+ * .on('data', function(instance) {
+ * this.end();
+ * });
*/
-Bigtable.prototype.getTables = function(callback) {
+Bigtable.prototype.getInstances = function(query, callback) {
var self = this;
+ if (is.function(query)) {
+ callback = query;
+ query = {};
+ }
+
var protoOpts = {
- service: 'BigtableTableService',
- method: 'listTables'
+ service: 'BigtableInstanceAdmin',
+ method: 'listInstances'
};
- var reqOpts = {
- name: this.clusterName
- };
+ var reqOpts = extend({}, query, {
+ parent: this.projectName
+ });
this.request(protoOpts, reqOpts, function(err, resp) {
if (err) {
- callback(err, null, resp);
+ callback(err, null, null, resp);
return;
}
- var tables = resp.tables.map(function(metadata) {
- var name = Bigtable.formatTableName_(metadata.name);
- var table = self.table(name);
-
- table.metadata = metadata;
- return table;
+ var instances = resp.instances.map(function(instanceData) {
+ var instance = self.instance(instanceData.name);
+ instance.metadata = instanceData;
+ return instance;
});
- callback(null, tables, resp);
+ var nextQuery = null;
+ if (resp.nextPageToken) {
+ nextQuery = extend({}, query, { pageToken: resp.nextPageToken });
+ }
+
+ callback(null, instances, nextQuery, resp);
});
};
/**
- * Get a reference to a Bigtable table.
+ * Get a reference to a Compute instance.
*
- * @param {string} name - The name of the table.
- * @return {module:bigtable/table}
+ * @param {string} name - The name of the instance.
+ * @return {module:bigtable/instance}
+ */
+Bigtable.prototype.instance = function(name) {
+ return new Instance(this, name);
+};
+
+/**
+ * Get a reference to an Operation.
*
- * @example
- * var table = bigtable.table('presidents');
+ * @param {string} name - The name of the instance.
+ * @return {Operation}
*/
-Bigtable.prototype.table = function(name) {
- return new Table(this, name);
+Bigtable.prototype.operation = function(name) {
+ return new common.GrpcOperation(this, name);
};
-Bigtable.Table = Table;
+/*! Developer Documentation
+ *
+ * These methods can be used with either a callback or as a readable object
+ * stream. `streamRouter` is used to add this dual behavior.
+ */
+common.streamRouter.extend(Bigtable, ['getInstances']);
module.exports = Bigtable;
diff --git a/packages/bigtable/src/instance.js b/packages/bigtable/src/instance.js
new file mode 100644
index 00000000000..579ba608296
--- /dev/null
+++ b/packages/bigtable/src/instance.js
@@ -0,0 +1,667 @@
+/*!
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*!
+ * @module bigtable/instance
+ */
+
+'use strict';
+
+var common = require('@google-cloud/common');
+var extend = require('extend');
+var is = require('is');
+var util = require('util');
+
+/**
+ * @private
+ * @type {module:bigtable/cluster}
+ */
+var Cluster = require('./cluster.js');
+
+/**
+ * @private
+ * @type {module:bigtable/family}
+ */
+var Family = require('./family.js');
+
+/**
+ * @private
+ * @type {module:bigtable/table}
+ */
+var Table = require('./table.js');
+
+/**
+ * Create an Instance object to interact with a Compute instance.
+ *
+ * @constructor
+ * @alias module:bigtable/instance
+ *
+ * @param {string} name - Name of the instance.
+ *
+ * @example
+ * var instance = bigtable.instance('my-instance');
+ */
+function Instance(bigtable, name) {
+ var id = name;
+
+ if (id.indexOf('/') === -1) {
+ id = bigtable.projectName + '/instances/' + name;
+ }
+
+ var methods = {
+
+ /**
+ * Create an instance.
+ *
+ * @param {object=} options - See {module:bigtable#createInstance}.
+ *
+ * @example
+ * 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.
+ * });
+ * });
+ */
+ create: true,
+
+ /**
+ * Delete the instance.
+ *
+ * @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
+ * instance.delete(function(err, apiResponse) {});
+ */
+ delete: {
+ protoOpts: {
+ service: 'BigtableInstanceAdmin',
+ method: 'deleteInstance'
+ },
+ reqOpts: {
+ name: id
+ }
+ },
+
+ /**
+ * Check if an 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
+ * instance.exists(function(err, exists) {});
+ */
+ exists: true,
+
+ /**
+ * Get an instance if it exists.
+ *
+ * @example
+ * instance.get(function(err, instance, apiResponse) {
+ * // The `instance` data has been populated.
+ * });
+ */
+ get: true,
+
+ /**
+ * Get the instance 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
+ * instance.getMetadata(function(err, metadata, apiResponse) {});
+ */
+ getMetadata: {
+ protoOpts: {
+ service: 'BigtableInstanceAdmin',
+ method: 'getInstance'
+ },
+ reqOpts: {
+ name: id
+ }
+ },
+
+ /**
+ * 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 {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
+ * instance.setMetadata({
+ * displayName: 'updated-name'
+ * }, function(err, apiResponse) {});
+ */
+ 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);
+ }
+ };
+
+ common.GrpcServiceObject.call(this, config);
+}
+
+util.inherits(Instance, common.GrpcServiceObject);
+
+/**
+ * Create a cluster.
+ *
+ * @param {string} name - The name to be used when referring to the new
+ * cluster within its instance.
+ * @param {object=} options - Cluster creation options.
+ * @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
+ * supported.
+ * @param {number} options.nodes - The number of nodes allocated to this
+ * cluster. More nodes enable higher throughput and more consistent
+ * performance.
+ * @param {string} options.storage - The type of storage used by this cluster
+ * to serve its parent instance's tables. Options are 'hdd' or 'ssd'.
+ * @param {function} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {module:bigtable/cluster} callback.cluster - The newly created
+ * 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
+ * var callback = function(err, cluster, operation, apiResponse) {
+ * if (err) {
+ * // Error handling omitted.
+ * }
+ *
+ * operation
+ * .on('error', console.log)
+ * .on('complete', function() {
+ * // The cluster was created successfully.
+ * });
+ * };
+ *
+ * var options = {
+ * location: 'us-central1-b',
+ * nodes: 3,
+ * storage: 'ssd'
+ * };
+ *
+ * instance.createCluster('my-cluster', options, callback);
+ */
+Instance.prototype.createCluster = function(name, options, callback) {
+ var self = this;
+
+ if (is.function(options)) {
+ callback = options;
+ options = {};
+ }
+
+ var protoOpts = {
+ service: 'BigtableInstanceAdmin',
+ method: 'createCluster'
+ };
+
+ var reqOpts = {
+ parent: this.id,
+ clusterId: name
+ };
+
+ if (!is.empty(options)) {
+ reqOpts.cluster = {};
+ }
+
+ if (options.location) {
+ reqOpts.cluster.location = Cluster.getLocation_(
+ this.parent.projectName,
+ options.location
+ );
+ }
+
+ if (options.nodes) {
+ reqOpts.cluster.serveNodes = options.nodes;
+ }
+
+ if (options.storage) {
+ var storageType = Cluster.getStorageType_(options.storage);
+ reqOpts.cluster.defaultStorageType = storageType;
+ }
+
+ this.request(protoOpts, reqOpts, function(err, resp) {
+ if (err) {
+ callback(err, null, null, resp);
+ return;
+ }
+
+ var bigtable = self.parent;
+
+ var cluster = self.cluster(name);
+ var operation = bigtable.operation(resp.name);
+ operation.metadata = resp;
+
+ callback(null, cluster, operation, resp);
+ });
+};
+
+/**
+ * Create a table on your Bigtable instance.
+ *
+ * @resource [Designing Your Schema]{@link https://cloud.google.com/bigtable/docs/schema-design}
+ * @resource [Splitting Keys]{@link https://cloud.google.com/bigtable/docs/managing-tables#splits}
+ *
+ * @throws {error} If a name is not provided.
+ *
+ * @param {string} name - The name of the table.
+ * @param {object=} options - Table creation options.
+ * @param {object|string[]} options.families - Column families to be created
+ * within the table.
+ * @param {string[]} options.splits - Initial
+ * [split keys](https://cloud.google.com/bigtable/docs/managing-tables#splits).
+ * @param {function} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {module:bigtable/table} callback.table - The newly created table.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * var callback = function(err, table, apiResponse) {
+ * // `table` is a Table object.
+ * };
+ *
+ * instance.createTable('prezzy', callback);
+ *
+ * //-
+ * // Optionally specify column families to be created within the table.
+ * //-
+ * var options = {
+ * families: ['follows']
+ * };
+ *
+ * instance.createTable('prezzy', options, callback);
+ *
+ * //-
+ * // You can also specify garbage collection rules for your column families.
+ * // See {module:bigtable/table#createFamily} for more information about
+ * // column families and garbage collection rules.
+ * //-
+ * var options = {
+ * families: [
+ * {
+ * name: 'follows',
+ * rule: {
+ * age: {
+ * seconds: 0,
+ * nanos: 5000
+ * },
+ * versions: 3,
+ * union: true
+ * }
+ * }
+ * ]
+ * };
+ *
+ * instance.createTable('prezzy', options, callback);
+ *
+ * //-
+ * // Pre-split the table based on the row key to spread the load across
+ * // multiple Cloud Bigtable nodes.
+ * //-
+ * var options = {
+ * splits: ['10', '20']
+ * };
+ *
+ * instance.createTable('prezzy', options, callback);
+ */
+Instance.prototype.createTable = function(name, options, callback) {
+ var self = this;
+
+ if (!name) {
+ throw new Error('A name is required to create a table.');
+ }
+
+ options = options || {};
+
+ if (is.function(options)) {
+ callback = options;
+ options = {};
+ }
+
+ var protoOpts = {
+ service: 'BigtableTableAdmin',
+ method: 'createTable'
+ };
+
+ var reqOpts = {
+ parent: this.id,
+ tableId: name,
+ table: {
+ // The granularity at which timestamps are stored in the table.
+ // Currently only milliseconds is supported, so it's not configurable.
+ granularity: 0
+ }
+ };
+
+ if (options.splits) {
+ reqOpts.initialSplits = options.splits.map(function(key) {
+ return {
+ key: key
+ };
+ });
+ }
+
+ if (options.families) {
+ var columnFamilies = options.families.reduce(function(families, family) {
+ if (is.string(family)) {
+ family = {
+ name: family
+ };
+ }
+
+ var columnFamily = families[family.name] = {};
+
+ if (family.rule) {
+ columnFamily.gcRule = Family.formatRule_(family.rule);
+ }
+
+ return families;
+ }, {});
+
+ reqOpts.table.columnFamilies = columnFamilies;
+ }
+
+ this.request(protoOpts, reqOpts, function(err, resp) {
+ if (err) {
+ callback(err, null, resp);
+ return;
+ }
+
+ var table = self.table(resp.name);
+ table.metadata = resp;
+
+ callback(null, table, resp);
+ });
+};
+
+/**
+ * Get a reference to a Bigtable Cluster.
+ *
+ * @param {string} name - The name of the cluster.
+ * @return {module:bigtable/cluster}
+ */
+Instance.prototype.cluster = function(name) {
+ return new Cluster(this, name);
+};
+
+/**
+ * Get Cluster objects for all of your clusters.
+ *
+ * @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 {function} callback - The callback function.
+ * @param {?error} callback.error - An error returned while making this request.
+ * @param {module:bigtable/cluster[]} callback.clusters - List of all
+ * Clusters.
+ * @param {object} callback.nextQuery - If present, query with this object to
+ * check for more results.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * instance.getClusters(function(err, clusters) {
+ * if (!err) {
+ * // `clusters` is an array of Cluster objects.
+ * }
+ * });
+ *
+ * //-
+ * // To control how many API requests are made and page through the results
+ * // manually, set `autoPaginate` to false.
+ * //-
+ * var callback = function(err, clusters, nextQuery, apiResponse) {
+ * if (nextQuery) {
+ * // More results exist.
+ * instance.getClusters(nextQuery, calback);
+ * }
+ * };
+ *
+ * instance.getClusters({
+ * autoPaginate: false
+ * }, callback);
+ *
+ * //-
+ * // Get the clusters from your project as a readable object stream.
+ * //-
+ * instance.getClusters()
+ * .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.getClusters()
+ * .on('data', function(cluster) {
+ * this.end();
+ * });
+ */
+Instance.prototype.getClusters = function(query, callback) {
+ var self = this;
+
+ 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) {
+ 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;
+
+ if (resp.nextPageToken) {
+ nextQuery = extend({}, query, {
+ pageToken: resp.nextPageToken
+ });
+ }
+
+ callback(null, clusters, nextQuery, resp);
+ });
+};
+
+/**
+ * Get Table objects for all the tables in your Compute instance.
+ *
+ * @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 items to return.
+ * @param {string} query.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
+ * are 'name', 'schema' or 'full'. Default: 'name'.
+ * @param {function} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {module:bigtable/table[]} callback.tables - List of all Tables.
+ * @param {object} callback.nextQuery - If present, query with this object to
+ * check for more results.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * instance.getTables(function(err, tables) {
+ * if (!err) {
+ * // `tables` is an array of Table objects.
+ * }
+ * });
+ *
+ * //-
+ * // To control how many API requests are made and page through the results
+ * // manually, set `autoPaginate` to false.
+ * //-
+ * var callback = function(err, tables, nextQuery, apiResponse) {
+ * if (nextQuery) {
+ * // More results exist.
+ * instance.getTables(nextQuery, calback);
+ * }
+ * };
+ *
+ * instance.getTables({
+ * autoPaginate: false
+ * }, callback);
+ *
+ * //-
+ * // Get the tables from your project as a readable object stream.
+ * //-
+ * instance.getTables()
+ * .on('error', console.error)
+ * .on('data', function(table) {
+ * // table is a Table object.
+ * })
+ * .on('end', function() {
+ * // All tables retrieved.
+ * });
+ *
+ * //-
+ * // If you anticipate many results, you can end a stream early to prevent
+ * // unnecessary processing and API requests.
+ * //-
+ * instance.getTables()
+ * .on('data', function(table) {
+ * this.end();
+ * });
+ */
+Instance.prototype.getTables = function(query, callback) {
+ var self = this;
+
+ if (is.function(query)) {
+ callback = query;
+ query = {};
+ }
+
+ var protoOpts = {
+ service: 'BigtableTableAdmin',
+ method: 'listTables'
+ };
+
+ var reqOpts = extend({}, query, {
+ parent: this.id,
+ view: Table.VIEWS[query.view || 'unspecified']
+ });
+
+ this.request(protoOpts, reqOpts, 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;
+ });
+
+ var nextQuery = null;
+ if (resp.nextPageToken) {
+ nextQuery = extend({}, query, {
+ pageToken: resp.nextPageToken
+ });
+ }
+
+ callback(null, tables, nextQuery, resp);
+ });
+};
+
+/**
+ * Get a reference to a Bigtable table.
+ *
+ * @param {string} name - The name of the table.
+ * @return {module:bigtable/table}
+ *
+ * @example
+ * var table = instance.table('presidents');
+ */
+Instance.prototype.table = function(name) {
+ return new Table(this, name);
+};
+
+/*! Developer Documentation
+ *
+ * These methods can be used with either a callback or as a readable object
+ * stream. `streamRouter` is used to add this dual behavior.
+ */
+common.streamRouter.extend(Instance, ['getClusters', 'getTables']);
+
+module.exports = Instance;
diff --git a/packages/bigtable/src/mutation.js b/packages/bigtable/src/mutation.js
index 5ca993cc5fe..a829dc4eff6 100644
--- a/packages/bigtable/src/mutation.js
+++ b/packages/bigtable/src/mutation.js
@@ -80,6 +80,10 @@ Mutation.convertFromBytes = function(bytes) {
* @return {buffer}
*/
Mutation.convertToBytes = function(data) {
+ if (data instanceof Buffer) {
+ return data;
+ }
+
if (is.number(data)) {
return new Int64(data).toBuffer();
}
@@ -152,7 +156,7 @@ Mutation.encodeSetCell = function(data) {
Object.keys(family).forEach(function(cellName) {
var cell = family[cellName];
- if (!is.object(cell)) {
+ if (!is.object(cell) || cell instanceof Buffer) {
cell = {
value: cell
};
diff --git a/packages/bigtable/src/row.js b/packages/bigtable/src/row.js
index 44d78ddf0f9..64dad9c3ce4 100644
--- a/packages/bigtable/src/row.js
+++ b/packages/bigtable/src/row.js
@@ -60,7 +60,8 @@ var RowError = createErrorClass('RowError', function(row) {
* @alias module:bigtable/row
*
* @example
- * var table = bigtable.table('prezzy');
+ * var instance = bigtable.instance('my-instance');
+ * var table = instance.table('prezzy');
* var row = table.row('gwashington');
*/
function Row(table, key) {
@@ -108,44 +109,9 @@ util.inherits(Row, common.GrpcServiceObject);
* @private
*
* @param {chunk[]} chunks - The list of chunks.
+ * @param {object=} options - Formatting options.
*
* @example
- * var chunks = [
- * {
- * rowContents: {
- * name: 'follows',
- * columns: [
- * {
- * qualifier: 'gwashington',
- * cells: [
- * {
- * value: 1
- * }
- * ]
- * }
- * ]
- * }
- * }, {
- * resetRow: true
- * }, {
- * rowContents: {
- * name: 'follows',
- * columns: [
- * {
- * qualifier: 'gwashington',
- * cells: [
- * {
- * value: 2
- * }
- * ]
- * }
- * ]
- * }
- * }, {
- * commitRow: true
- * }
- * ];
- *
* Row.formatChunks_(chunks);
* // {
* // follows: {
@@ -157,26 +123,67 @@ util.inherits(Row, common.GrpcServiceObject);
* // }
* // }
*/
-Row.formatChunks_ = function(chunks) {
- var families = [];
- var chunkList = [];
+Row.formatChunks_ = function(chunks, options) {
+ var rows = [];
+ var familyName;
+ var qualifierName;
+
+ options = options || {};
+
+ chunks.reduce(function(row, chunk) {
+ var family;
+ var qualifier;
- chunks.forEach(function(chunk) {
- if (chunk.resetRow) {
- chunkList = [];
+ row.data = row.data || {};
+
+ if (chunk.rowKey) {
+ row.key = Mutation.convertFromBytes(chunk.rowKey);
+ }
+
+ if (chunk.familyName) {
+ familyName = chunk.familyName.value;
+ }
+
+ if (familyName) {
+ family = row.data[familyName] = row.data[familyName] || {};
}
- if (chunk.rowContents) {
- chunkList.push(chunk.rowContents);
+ if (chunk.qualifier) {
+ qualifierName = Mutation.convertFromBytes(chunk.qualifier.value);
+ }
+
+ if (family && qualifierName) {
+ qualifier = family[qualifierName] = family[qualifierName] || [];
+ }
+
+ if (qualifier && chunk.value) {
+ var value = chunk.value;
+
+ if (options.decode !== false) {
+ value = Mutation.convertFromBytes(value);
+ }
+
+ qualifier.push({
+ value: value,
+ labels: chunk.labels,
+ timestamp: chunk.timestampMicros,
+ size: chunk.valueSize
+ });
}
if (chunk.commitRow) {
- families = families.concat(chunkList);
- chunkList = [];
+ rows.push(row);
+ }
+
+ if (chunk.commitRow || chunk.resetRow) {
+ familyName = qualifierName = null;
+ return {};
}
- });
- return Row.formatFamilies_(families);
+ return row;
+ }, {});
+
+ return rows;
};
/**
@@ -185,6 +192,7 @@ Row.formatChunks_ = function(chunks) {
* @private
*
* @param {object[]} families - The row families.
+ * @param {object=} options - Formatting options.
*
* @example
* var families = [
@@ -214,9 +222,11 @@ Row.formatChunks_ = function(chunks) {
* // }
* // }
*/
-Row.formatFamilies_ = function(families) {
+Row.formatFamilies_ = function(families, options) {
var data = {};
+ options = options || {};
+
families.forEach(function(family) {
var familyData = data[family.name] = {};
@@ -224,8 +234,14 @@ Row.formatFamilies_ = function(families) {
var qualifier = Mutation.convertFromBytes(column.qualifier);
familyData[qualifier] = column.cells.map(function(cell) {
+ var value = cell.value;
+
+ if (options.decode !== false) {
+ value = Mutation.convertFromBytes(value);
+ }
+
return {
- value: Mutation.convertFromBytes(cell.value),
+ value: value,
timestamp: cell.timestampMicros,
labels: cell.labels
};
@@ -355,7 +371,7 @@ Row.prototype.createRules = function(rules, callback) {
});
var grpcOpts = {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'readModifyWriteRow'
};
@@ -421,7 +437,7 @@ Row.prototype.createRules = function(rules, callback) {
*/
Row.prototype.filter = function(filter, onMatch, onNoMatch, callback) {
var grpcOpts = {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'checkAndMutateRow'
};
@@ -520,6 +536,9 @@ Row.prototype.deleteCells = function(columns, callback) {
* Get the row data. See {module:bigtable/table#getRows}.
*
* @param {string[]=} columns - List of specific columns to retrieve.
+ * @param {object} options - Configuration object.
+ * @param {boolean} options.decode - If set to `false` it will not decode Buffer
+ * values returned from Bigtable. Default: true.
* @param {function} callback - The callback function.
* @param {?error} callback.err - An error returned while making this
* request.
@@ -547,14 +566,20 @@ Row.prototype.deleteCells = function(columns, callback) {
* 'follows:alincoln'
* ], callback);
*/
-Row.prototype.get = function(columns, callback) {
+Row.prototype.get = function(columns, options, callback) {
var self = this;
- if (is.function(columns)) {
- callback = columns;
+ if (!is.array(columns)) {
+ callback = options;
+ options = columns;
columns = [];
}
+ if (is.function(options)) {
+ callback = options;
+ options = {};
+ }
+
var filter;
columns = arrify(columns);
@@ -581,10 +606,10 @@ Row.prototype.get = function(columns, callback) {
}
}
- var reqOpts = {
- key: this.id,
+ var reqOpts = extend({}, options, {
+ keys: [this.id],
filter: filter
- };
+ });
this.parent.getRows(reqOpts, function(err, rows, apiResponse) {
if (err) {
@@ -612,6 +637,9 @@ Row.prototype.get = function(columns, callback) {
/**
* Get the row's metadata.
*
+ * @param {object=} options - Configuration object.
+ * @param {boolean} options.decode - If set to `false` it will not decode Buffer
+ * values returned from Bigtable. Default: true.
* @param {function} callback - The callback function.
* @param {?error} callback.err - An error returned while making this
* request.
@@ -621,8 +649,13 @@ Row.prototype.get = function(columns, callback) {
* @example
* row.getMetadata(function(err, metadata, apiResponse) {});
*/
-Row.prototype.getMetadata = function(callback) {
- this.get(function(err, row, resp) {
+Row.prototype.getMetadata = function(options, callback) {
+ if (is.function(options)) {
+ callback = options;
+ options = {};
+ }
+
+ this.get(options, function(err, row, resp) {
if (err) {
callback(err, null, resp);
return;
@@ -674,16 +707,16 @@ Row.prototype.increment = function(column, value, callback) {
increment: value
};
- this.createRules(reqOpts, function(err, apiResponse) {
+ this.createRules(reqOpts, function(err, resp) {
if (err) {
- callback(err, null, apiResponse);
+ callback(err, null, resp);
return;
}
- var data = Row.formatFamilies_(apiResponse.families);
+ var data = Row.formatFamilies_(resp.row.families);
var value = dotProp.get(data, column.replace(':', '.'))[0].value;
- callback(null, value, apiResponse);
+ callback(null, value, resp);
});
};
@@ -693,6 +726,8 @@ Row.prototype.increment = function(column, value, callback) {
* @param {string|object} key - Either a column name or an entry
* object to be inserted into the row. See {module:bigtable/table#insert}.
* @param {*=} value - This can be omitted if using entry object.
+ * @param {object=} options - Configuration options. See
+ * {module:bigtable/table#mutate}.
* @param {function} callback - The callback function.
* @param {?error} callback.err - An error returned while making this
* request.
diff --git a/packages/bigtable/src/table.js b/packages/bigtable/src/table.js
index e68a373e238..5df17d1e6a6 100644
--- a/packages/bigtable/src/table.js
+++ b/packages/bigtable/src/table.js
@@ -23,6 +23,7 @@
var arrify = require('arrify');
var common = require('@google-cloud/common');
var concat = require('concat-stream');
+var events = require('events');
var flatten = require('lodash.flatten');
var is = require('is');
var propAssign = require('prop-assign');
@@ -63,17 +64,18 @@ var Row = require('./row.js');
* @param {string} name - Name of the table.
*
* @example
- * var table = bigtable.table('prezzy');
+ * var instance = bigtable.instance('my-instance');
+ * var table = instance.table('prezzy');
*/
-function Table(bigtable, name) {
- var id = Table.formatName_(bigtable.clusterName, name);
+function Table(instance, name) {
+ var id = Table.formatName_(instance.id, name);
var methods = {
/**
* Create a table.
*
- * @param {object=} options - See {module:bigtable#createTable}.
+ * @param {object=} options - See {module:bigtable/instance#createTable}.
*
* @example
* table.create(function(err, table, apiResponse) {
@@ -97,7 +99,7 @@ function Table(bigtable, name) {
*/
delete: {
protoOpts: {
- service: 'BigtableTableService',
+ service: 'BigtableTableAdmin',
method: 'deleteTable'
},
reqOpts: {
@@ -129,43 +131,23 @@ function Table(bigtable, name) {
* @param {options=} options - Configuration object.
* @param {boolean} options.autoCreate - Automatically create the object if
* it does not exist. Default: `false`
+ * @param {string} options.view - The view to be applied to the table
+ * fields. See {module:bigtable/table#getMetadata}.
*
* @example
* table.get(function(err, table, apiResponse) {
* // The `table` data has been populated.
* });
*/
- get: true,
-
- /**
- * Get the table's metadata.
- *
- * @param {function=} callback - The callback function.
- * @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
- * table.getMetadata(function(err, metadata, apiResponse) {});
- */
- getMetadata: {
- protoOpts: {
- service: 'BigtableTableService',
- method: 'getTable'
- },
- reqOpts: {
- name: id
- }
- }
+ get: true
};
var config = {
- parent: bigtable,
+ parent: instance,
id: id,
methods: methods,
createMethod: function(_, options, callback) {
- bigtable.createTable(name, options, callback);
+ instance.createTable(name, options, callback);
}
};
@@ -174,78 +156,57 @@ function Table(bigtable, name) {
util.inherits(Table, common.GrpcServiceObject);
+/**
+ * The view to be applied to the returned table's fields.
+ * Defaults to schema if unspecified.
+ *
+ * @private
+ */
+Table.VIEWS = {
+ unspecified: 0,
+ name: 1,
+ schema: 2,
+ full: 4
+};
+
/**
* Formats the table name to include the Bigtable cluster.
*
* @private
*
- * @param {string} clusterName - The formatted cluster name.
+ * @param {string} instanceName - The formatted instance name.
* @param {string} name - The table name.
*
* @example
* Table.formatName_(
- * 'projects/my-project/zones/my-zone/clusters/my-cluster',
+ * 'projects/my-project/zones/my-zone/instances/my-instance',
* 'my-table'
* );
- * // 'projects/my-project/zones/my-zone/clusters/my-cluster/tables/my-table'
+ * // 'projects/my-project/zones/my-zone/instances/my-instance/tables/my-table'
*/
-Table.formatName_ = function(clusterName, name) {
+Table.formatName_ = function(instanceName, name) {
if (name.indexOf('/') > -1) {
return name;
}
- return clusterName + '/tables/' + name;
-};
-
-/**
- * Formats a row range into the desired proto format.
- *
- * @private
- *
- * @param {object} range - The range object.
- * @param {string} range.start - The lower bound for the range.
- * @param {string} range.end - The upper bound for the range.
- * @return {object}
- *
- * @example
- * Table.formatRowRange_({
- * start: 'gwashington',
- * end: 'alincoln'
- * });
- * // {
- * // startKey: new Buffer('gwashington'),
- * // endKey: new Buffer('alincoln')
- * // }
- */
-Table.formatRowRange_ = function(range) {
- var rowRange = {};
-
- if (range.start) {
- rowRange.startKey = Mutation.convertToBytes(range.start);
- }
-
- if (range.end) {
- rowRange.endKey = Mutation.convertToBytes(range.end);
- }
-
- return rowRange;
+ return instanceName + '/tables/' + name;
};
/**
* Create a column family.
*
- * Optionally you can send garbage collection rules and expressions when
- * creating a family. Garbage collection executes opportunistically in the
- * background, so it's possible for reads to return a cell even if it
- * matches the active expression for its family.
+ * Optionally you can send garbage collection rules and when creating a family.
+ * Garbage collection executes opportunistically in the background, so it's
+ * possible for reads to return a cell even if it matches the active expression
+ * for its family.
*
* @resource [Garbage Collection Proto Docs]{@link https://github.com/googleapis/googleapis/blob/master/google/bigtable/admin/table/v1/bigtable_table_data.proto#L59}
*
* @throws {error} If a name is not provided.
*
* @param {string} name - The name of column family.
- * @param {string|object=} rule - Garbage collection rule.
- * @param {object=} rule.age - Delete cells in a column older than the given
+ * @param {object=} rule - Garbage collection rule.
+ * @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.
@@ -271,13 +232,6 @@ Table.formatRowRange_ = function(range) {
* };
*
* table.createFamily('follows', rule, callback);
- *
- * //-
- * // Alternatively you can send a garbage collection expression.
- * //-
- * var expression = 'version() > 3 || (age() > 3d && version() > 1)';
- *
- * table.createFamily('follows', expression, callback);
*/
Table.prototype.createFamily = function(name, rule, callback) {
var self = this;
@@ -292,25 +246,24 @@ Table.prototype.createFamily = function(name, rule, callback) {
}
var grpcOpts = {
- service: 'BigtableTableService',
- method: 'createColumnFamily'
+ service: 'BigtableTableAdmin',
+ method: 'modifyColumnFamilies'
};
- var reqOpts = {
- name: this.id,
- columnFamilyId: name
+ var mod = {
+ id: name,
+ create: {}
};
- if (is.string(rule)) {
- reqOpts.columnFamily = {
- gcExpression: rule
- };
- } else if (is.object(rule)) {
- reqOpts.columnFamily = {
- gcRule: Family.formatRule_(rule)
- };
+ if (rule) {
+ mod.create.gcRule = Family.formatRule_(rule);
}
+ var reqOpts = {
+ name: this.id,
+ modifications: [mod]
+ };
+
this.request(grpcOpts, reqOpts, function(err, resp) {
if (err) {
callback(err, null, resp);
@@ -360,12 +313,12 @@ Table.prototype.deleteRows = function(options, callback) {
}
var grpcOpts = {
- service: 'BigtableTableService',
- method: 'bulkDeleteRows'
+ service: 'BigtableTableAdmin',
+ method: 'dropRowRange'
};
var reqOpts = {
- tableName: this.id
+ name: this.id
};
if (options.prefix) {
@@ -428,11 +381,55 @@ Table.prototype.getFamilies = function(callback) {
});
};
+/**
+ * Get the table's metadata.
+ *
+ * @param {object=} options - Table request options.
+ * @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
+ * request.
+ * @param {object} callback.metadata - The table's metadata.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * table.getMetadata(function(err, metadata, apiResponse) {});
+ */
+Table.prototype.getMetadata = function(options, callback) {
+ var self = this;
+
+ if (is.function(options)) {
+ callback = options;
+ 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) {
+ if (err) {
+ callback(err, null, resp);
+ return;
+ }
+
+ self.metadata = resp;
+ callback(null, self.metadata, resp);
+ });
+};
+
/**
* Get Row objects for the rows currently in your table.
*
* @param {options=} options - Configuration object.
- * @param {string} options.key - An individual row key.
+ * @param {boolean} options.decode - If set to `false` it will not decode Buffer
+ * values returned from Bigtable. Default: true.
* @param {string[]} options.keys - A list of row keys.
* @param {string} options.start - Start value for key range.
* @param {string} options.end - End value for key range.
@@ -461,13 +458,6 @@ Table.prototype.getFamilies = function(callback) {
* table.getRows(callback);
*
* //-
- * // Specify a single row to be returned.
- * //-
- * table.getRows({
- * key: 'alincoln'
- * }, callback);
- *
- * //-
* // Specify arbitrary keys for a non-contiguous set of rows.
* // The total size of the keys must remain under 1MB, after encoding.
* //-
@@ -557,9 +547,10 @@ Table.prototype.getRows = function(options, callback) {
}
options = options || {};
+ options.ranges = options.ranges || [];
var grpcOpts = {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'readRows'
};
@@ -568,19 +559,24 @@ Table.prototype.getRows = function(options, callback) {
objectMode: true
};
- if (options.key) {
- reqOpts.rowKey = Mutation.convertToBytes(options.key);
- } else if (options.start || options.end) {
- reqOpts.rowRange = Table.formatRowRange_(options);
- } else if (options.keys || options.ranges) {
- reqOpts.rowSet = {};
+ if (options.start || options.end) {
+ options.ranges.push({
+ start: options.start,
+ end: options.end
+ });
+ }
+
+ if (options.keys || options.ranges.length) {
+ reqOpts.rows = {};
if (options.keys) {
- reqOpts.rowSet.rowKeys = options.keys.map(Mutation.convertToBytes);
+ reqOpts.rows.rowKeys = options.keys.map(Mutation.convertToBytes);
}
- if (options.ranges) {
- reqOpts.rowSet.rowRanges = options.ranges.map(Table.formatRowRange_);
+ if (options.ranges.length) {
+ reqOpts.rows.rowRanges = options.ranges.map(function(range) {
+ return Filter.createRange(range.start, range.end, 'key');
+ });
}
}
@@ -588,21 +584,26 @@ Table.prototype.getRows = function(options, callback) {
reqOpts.filter = Filter.parse(options.filter);
}
- if (options.interleave) {
- reqOpts.allowRowInterleaving = options.interleave;
- }
-
if (options.limit) {
reqOpts.numRowsLimit = options.limit;
}
var stream = pumpify.obj([
this.requestStream(grpcOpts, reqOpts),
- through.obj(function(rowData, enc, next) {
- var row = self.row(Mutation.convertFromBytes(rowData.rowKey));
+ through.obj(function(data, enc, next) {
+ var throughStream = this;
+ var rows = Row.formatChunks_(data.chunks, {
+ decode: options.decode
+ });
- row.data = Row.formatChunks_(rowData.chunks);
- next(null, row);
+ rows.forEach(function(rowData) {
+ var row = self.row(rowData.key);
+
+ row.data = rowData.data;
+ throughStream.push(row);
+ });
+
+ next();
})
]);
@@ -624,9 +625,32 @@ Table.prototype.getRows = function(options, callback) {
* See {module:bigtable/table#mutate}.
* @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.
+ * @param {object[]} callback.insertErrors - A status object for each failed
+ * insert.
*
* @example
+ * var callback = function(err, insertErrors) {
+ * if (err) {
+ * // Error handling omitted.
+ * }
+ *
+ * // insertErrors = [
+ * // {
+ * // code: 500,
+ * // message: 'Internal Server Error',
+ * // entry: {
+ * // key: 'gwashington',
+ * // data: {
+ * // follows: {
+ * // jadams: 1
+ * // }
+ * // }
+ * // }
+ * // },
+ * // ...
+ * // ]
+ * };
+ *
* var entries = [
* {
* key: 'alincoln',
@@ -638,7 +662,7 @@ Table.prototype.getRows = function(options, callback) {
* }
* ];
*
- * table.insert(entries, function(err, apiResponse) {});
+ * table.insert(entries, callback);
*
* //-
* // By default whenever you insert new data, the server will capture a
@@ -659,7 +683,18 @@ Table.prototype.getRows = function(options, callback) {
* }
* ];
*
- * table.insert(entries, function(err, apiResponse) {});
+ * table.insert(entries, callback);
+ *
+ * //-
+ * // If you don't provide a callback, an EventEmitter is returned. Listen for
+ * // the error event to catch API and insert errors, and complete for when
+ * // the API request has completed.
+ * //-
+ * table.insert(entries)
+ * .on('error', console.error)
+ * .on('complete', function() {
+ * // All requested inserts have been processed.
+ * });
*/
Table.prototype.insert = function(entries, callback) {
entries = arrify(entries).map(propAssign('method', Mutation.methods.INSERT));
@@ -676,16 +711,34 @@ Table.prototype.insert = function(entries, callback) {
* 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.
+ * @param {object[]} callback.mutationErrors - A status object for each failed
+ * mutation.
*
* @example
* //-
* // Insert entities. See {module:bigtable/table#insert}
* //-
- * var callback = function(err, apiResponse) {
- * if (!err) {
- * // Mutations were successful.
+ * var callback = function(err, mutationErrors) {
+ * if (err) {
+ * // Error handling omitted.
* }
+ *
+ * // mutationErrors = [
+ * // {
+ * // code: 500,
+ * // message: 'Internal Server Error',
+ * // entry: {
+ * // method: 'insert',
+ * // key: 'gwashington',
+ * // data: {
+ * // follows: {
+ * // jadams: 1
+ * // }
+ * // }
+ * // }
+ * // },
+ * // ...
+ * // ]
* };
*
* var entries = [
@@ -752,21 +805,77 @@ Table.prototype.insert = function(entries, callback) {
* ];
*
* table.mutate(entries, callback);
+ *
+ * //-
+ * // If you don't provide a callback, an EventEmitter is returned. Listen for
+ * // the error event to catch API and mutation errors, and complete for when
+ * // the API request has completed.
+ * //-
+ * table.mutate(entries)
+ * .on('error', console.error)
+ * .on('complete', function() {
+ * // All requested mutations have been processed.
+ * });
*/
Table.prototype.mutate = function(entries, callback) {
- entries = flatten(arrify(entries)).map(Mutation.parse);
+ entries = flatten(arrify(entries));
var grpcOpts = {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'mutateRows'
};
var reqOpts = {
+ objectMode: true,
tableName: this.id,
- entries: entries
+ entries: entries.map(Mutation.parse)
};
- this.request(grpcOpts, reqOpts, callback);
+ var isCallbackMode = is.function(callback);
+ var emitter = null;
+
+ if (!isCallbackMode) {
+ emitter = new events.EventEmitter();
+ }
+
+ var stream = pumpify.obj([
+ this.requestStream(grpcOpts, reqOpts),
+ through.obj(function(data, enc, next) {
+ var throughStream = this;
+
+ data.entries.forEach(function(entry) {
+ // mutation was successful, no need to notify the user
+ if (entry.status.code === 0) {
+ return;
+ }
+
+ var status = common.GrpcService.decorateStatus_(entry.status);
+ status.entry = entries[entry.index];
+
+
+ if (!isCallbackMode) {
+ emitter.emit('error', status);
+ return;
+ }
+
+ throughStream.push(status);
+ });
+
+ next();
+ })
+ ]);
+
+ if (!isCallbackMode) {
+ stream.on('error', emitter.emit.bind(emitter, 'error'));
+ stream.on('finish', emitter.emit.bind(emitter, 'complete'));
+ return emitter;
+ }
+
+ stream
+ .on('error', callback)
+ .pipe(concat(function(mutationErrors) {
+ callback(null, mutationErrors);
+ }));
};
/**
@@ -828,7 +937,7 @@ Table.prototype.row = function(key) {
*/
Table.prototype.sampleRowKeys = function(callback) {
var grpcOpts = {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'sampleRowKeys'
};
diff --git a/packages/bigtable/system-test/bigtable.js b/packages/bigtable/system-test/bigtable.js
index fbb9421d871..819b2514b07 100644
--- a/packages/bigtable/system-test/bigtable.js
+++ b/packages/bigtable/system-test/bigtable.js
@@ -18,70 +18,230 @@
var assert = require('assert');
var async = require('async');
-var exec = require('methmeth');
-var extend = require('extend');
var uuid = require('node-uuid');
-var Bigtable = require('../');
var env = require('../../../system-test/env.js');
+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');
var Row = require('../src/row.js');
-var Table = require('../src/table.js');
-var clusterName = process.env.GCLOUD_TESTS_BIGTABLE_CLUSTER;
-var zoneName = process.env.GCLOUD_TESTS_BIGTABLE_ZONE;
-
-var isTestable = clusterName && zoneName;
-
-function generateTableName() {
- return 'test-table-' + uuid.v4();
+function generateName(obj) {
+ return ['test', obj, uuid.v4()].join('-');
}
-(isTestable ? describe : describe.skip)('Bigtable', function() {
+describe('Bigtable', function() {
var bigtable;
- var TABLE_NAME = generateTableName();
+ var INSTANCE_NAME = 'test-bigtable-instance';
+ var INSTANCE;
+
+ var TABLE_NAME = generateName('table');
var TABLE;
+ var CLUSTER_NAME = 'test-bigtable-cluster';
+
before(function(done) {
- bigtable = new Bigtable(extend({
- cluster: clusterName,
- zone: zoneName
- }, env));
+ bigtable = new Bigtable(env);
+
+ INSTANCE = bigtable.instance(INSTANCE_NAME);
- TABLE = bigtable.table(TABLE_NAME);
+ var options = {
+ clusters: [{
+ name: CLUSTER_NAME,
+ location: 'us-central1-b',
+ nodes: 3
+ }]
+ };
- bigtable.getTables(function(err, tables) {
+ INSTANCE.create(options, function(err, instance, operation) {
if (err) {
done(err);
return;
}
- async.each(tables, exec('delete'), function(err) {
- if (err) {
- done(err);
- return;
- }
+ operation
+ .on('error', done)
+ .on('complete', function() {
+ TABLE = INSTANCE.table(TABLE_NAME);
+
+ TABLE.create({
+ families: ['follows', 'traits']
+ }, done);
+ });
+ });
+ });
+
+ after(function(done) {
+ INSTANCE.delete(done);
+ });
+
+ describe('instances', function() {
+ it('should get a list of instances', function(done) {
+ bigtable.getInstances(function(err, instances) {
+ assert.ifError(err);
+ assert(instances.length > 0);
+ done();
+ });
+ });
+
+ it('should get a list of instances in stream mode', function(done) {
+ var instances = [];
+
+ bigtable.getInstances()
+ .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);
+ assert.strictEqual(exists, true);
+ done();
+ });
+ });
+
+ it('should check if an instance does not exist', function(done) {
+ var instance = bigtable.instance('fake-instance');
- TABLE.create(done);
+ instance.exists(function(err, exists) {
+ assert.ifError(err);
+ assert.strictEqual(exists, false);
+ done();
+ });
+ });
+
+ it('should get a single instance', function(done) {
+ var instance = bigtable.instance(INSTANCE_NAME);
+
+ instance.get(done);
+ });
+
+ it('should update an instance', function(done) {
+ var metadata = {
+ displayName: 'metadata-test'
+ };
+
+ INSTANCE.setMetadata(metadata, function(err) {
+ assert.ifError(err);
+
+ INSTANCE.getMetadata(function(err, metadata_) {
+ assert.ifError(err);
+ assert.strictEqual(metadata.displayName, metadata_.displayName);
+ done();
+ });
});
});
});
- after(function() {
- TABLE.delete();
+ describe('clusters', function() {
+ var CLUSTER;
+
+ beforeEach(function() {
+ CLUSTER = INSTANCE.cluster(CLUSTER_NAME);
+ });
+
+ it('should retrieve a list of clusters', function(done) {
+ INSTANCE.getClusters(function(err, clusters) {
+ assert.ifError(err);
+ assert(clusters[0] instanceof Cluster);
+ done();
+ });
+ });
+
+ it('should retrieve a list of clusters in stream mode', function(done) {
+ var clusters = [];
+
+ INSTANCE.getClusters()
+ .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);
+ assert.strictEqual(exists, true);
+ done();
+ });
+ });
+
+ it('should check if a cluster does not exist', function(done) {
+ var cluster = INSTANCE.cluster('fake-cluster');
+
+ cluster.exists(function(err, exists) {
+ assert.ifError(err);
+ assert.strictEqual(exists, false);
+ done();
+ });
+ });
+
+ it('should get a cluster', function(done) {
+ CLUSTER.get(done);
+ });
+
+ it('should update a cluster', function(done) {
+ var metadata = {
+ nodes: 4
+ };
+
+ CLUSTER.setMetadata(metadata, function(err, operation) {
+ assert.ifError(err);
+
+ operation
+ .on('error', done)
+ .on('complete', function() {
+ CLUSTER.getMetadata(function(err, _metadata) {
+ assert.ifError(err);
+ assert.strictEqual(metadata.nodes, _metadata.serveNodes);
+ done();
+ });
+ });
+ });
+ });
+
});
describe('tables', function() {
it('should retrieve a list of tables', function(done) {
- bigtable.getTables(function(err, tables) {
+ INSTANCE.getTables(function(err, tables) {
assert.ifError(err);
assert(tables[0] instanceof Table);
done();
});
});
+ it('should retrieve a list of tables in stream mode', function(done) {
+ var tables = [];
+
+ INSTANCE.getTables()
+ .on('error', done)
+ .on('data', function(table) {
+ assert(table instanceof Table);
+ tables.push(table);
+ })
+ .on('end', function() {
+ assert(tables.length > 0);
+ done();
+ });
+ });
+
it('should check if a table exists', function(done) {
TABLE.exists(function(err, exists) {
assert.ifError(err);
@@ -91,7 +251,7 @@ function generateTableName() {
});
it('should check if a table does not exist', function(done) {
- var table = bigtable.table('should-not-exist');
+ var table = INSTANCE.table('should-not-exist');
table.exists(function(err, exists) {
assert.ifError(err);
@@ -101,7 +261,7 @@ function generateTableName() {
});
it('should get a table', function(done) {
- var table = bigtable.table(TABLE_NAME);
+ var table = INSTANCE.table(TABLE_NAME);
table.get(function(err, table_) {
assert.ifError(err);
@@ -111,7 +271,7 @@ function generateTableName() {
});
it('should delete a table', function(done) {
- var table = bigtable.table(generateTableName());
+ var table = INSTANCE.table(generateName('table'));
async.series([
table.create.bind(table),
@@ -127,12 +287,12 @@ function generateTableName() {
});
it('should create a table with column family data', function(done) {
- var name = generateTableName();
+ var name = generateName('table');
var options = {
families: ['test']
};
- bigtable.createTable(name, options, function(err, table) {
+ INSTANCE.createTable(name, options, function(err, table) {
assert.ifError(err);
assert(table.metadata.columnFamilies.test);
done();
@@ -153,7 +313,7 @@ function generateTableName() {
it('should get a list of families', function(done) {
TABLE.getFamilies(function(err, families) {
assert.ifError(err);
- assert.strictEqual(families.length, 1);
+ assert.strictEqual(families.length, 3);
assert(families[0] instanceof Family);
assert.strictEqual(families[0].name, FAMILY.name);
done();
@@ -206,13 +366,12 @@ function generateTableName() {
union: true
};
- FAMILY.setMetadata({ rule: rule }, function(err, metadata_) {
+ FAMILY.setMetadata({ rule: rule }, function(err, metadata) {
assert.ifError(err);
+ var maxAge = metadata.gcRule.maxAge;
- var maxAge_ = metadata_.gcRule.maxAge;
-
- assert.equal(maxAge_.seconds, rule.age.seconds);
- assert.strictEqual(maxAge_.nanas, rule.age.nanas);
+ assert.equal(maxAge.seconds, rule.age.seconds);
+ assert.strictEqual(maxAge.nanas, rule.age.nanas);
done();
});
});
@@ -225,12 +384,6 @@ function generateTableName() {
describe('rows', function() {
- before(function(done) {
- async.each(['follows', 'traits'], function(family, callback) {
- TABLE.createFamily(family, callback);
- }, done);
- });
-
describe('inserting data', function() {
it('should insert rows', function(done) {
@@ -259,8 +412,9 @@ function generateTableName() {
}
}];
- TABLE.insert(rows, function(err) {
+ TABLE.insert(rows, function(err, insertErrors) {
assert.ifError(err);
+ assert.strictEqual(insertErrors.length, 0);
done();
});
});
@@ -323,15 +477,15 @@ function generateTableName() {
append: '-wood'
};
- row.createRules(rule, function(err) {
+ row.save('traits:teeth', 'shiny', function(err) {
assert.ifError(err);
- row.save('traits:teeth', 'shiny', function(err) {
+ row.createRules(rule, function(err) {
assert.ifError(err);
row.get(['traits:teeth'], function(err, data) {
assert.ifError(err);
- assert(data.traits.teeth[0].value, 'shiny-wood');
+ assert.strictEqual(data.traits.teeth[0].value, 'shiny-wood');
done();
});
});
@@ -345,12 +499,12 @@ function generateTableName() {
value: 'alincoln'
};
- var batch = [{
+ var mutations = [{
method: 'delete',
- data: ['follows:lincoln']
+ data: ['follows:alincoln']
}];
- row.filter(filter, null, batch, function(err, matched) {
+ row.filter(filter, mutations, function(err, matched) {
assert.ifError(err);
assert(matched);
done();
@@ -405,6 +559,29 @@ function generateTableName() {
});
});
+ it('should not decode the values', function(done) {
+ var row = TABLE.row('alincoln');
+ var options = {
+ decode: false
+ };
+
+ row.get(options, function(err) {
+ assert.ifError(err);
+
+ var presidents = Object.keys(row.data.follows);
+
+ assert(presidents.length > 0);
+
+ presidents.forEach(function(prez) {
+ var follower = row.data.follows[prez];
+
+ assert.strictEqual(follower[0].value, 'AAAAAAAAAAE=');
+ });
+
+ done();
+ });
+ });
+
it('should get sample row keys', function(done) {
TABLE.sampleRowKeys(function(err, keys) {
assert.ifError(err);
diff --git a/packages/bigtable/test/cluster.js b/packages/bigtable/test/cluster.js
new file mode 100644
index 00000000000..0dc7c04d623
--- /dev/null
+++ b/packages/bigtable/test/cluster.js
@@ -0,0 +1,300 @@
+/**
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var assert = require('assert');
+var format = require('string-format-obj');
+var proxyquire = require('proxyquire');
+var util = require('util');
+
+var GrpcServiceObject = require('@google-cloud/common').GrpcServiceObject;
+
+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 }
+ };
+
+ var CLUSTER_ID = format('{instance}/clusters/{cluster}', {
+ instance: INSTANCE.id,
+ cluster: CLUSTER_NAME
+ });
+
+ var Cluster;
+ var cluster;
+
+ before(function() {
+ Cluster = proxyquire('../src/cluster.js', {
+ '@google-cloud/common': {
+ GrpcServiceObject: FakeGrpcServiceObject
+ }
+ });
+ });
+
+ beforeEach(function() {
+ cluster = new Cluster(INSTANCE, CLUSTER_NAME);
+ });
+
+ 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 Instance#createCluster to create the cluster', function(done) {
+ var config = cluster.calledWith_[0];
+ var fakeOptions = {};
+
+ INSTANCE.createCluster = function(name, options, callback) {
+ assert.strictEqual(name, CLUSTER_NAME);
+ assert.strictEqual(options, fakeOptions);
+ callback();
+ };
+
+ config.createMethod(null, fakeOptions, done);
+ });
+
+ 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
+ }
+ }
+ });
+ });
+ });
+
+ describe('getLocation_', function() {
+ var LOCATION = 'us-centralb-1';
+
+ it('should format the location name', function() {
+ var expected = format('projects/{project}/locations/{location}', {
+ project: PROJECT_ID,
+ location: LOCATION
+ });
+
+ var formatted = Cluster.getLocation_(PROJECT_ID, LOCATION);
+ assert.strictEqual(formatted, expected);
+ });
+
+ it('should not re-format a complete location', function() {
+ var complete = format('projects/p/locations/{location}', {
+ location: LOCATION
+ });
+
+ var formatted = Cluster.getLocation_(PROJECT_ID, complete);
+ assert.strictEqual(formatted, complete);
+ });
+ });
+
+ describe('getStorageType_', function() {
+ var types = {
+ unspecified: 0,
+ ssd: 1,
+ hdd: 2
+ };
+
+ it('should default to unspecified', function() {
+ assert.strictEqual(Cluster.getStorageType_(), types.unspecified);
+ });
+
+ it('should lowercase a type', function() {
+ assert.strictEqual(Cluster.getStorageType_('SSD'), types.ssd);
+ });
+
+ Object.keys(types).forEach(function(type) {
+ it('should get the storage type for "' + type + '"', function() {
+ assert.strictEqual(Cluster.getStorageType_(type), types[type]);
+ });
+ });
+ });
+
+ describe('setMetadata', function() {
+ it('should provide the proper request options', function(done) {
+ cluster.request = function(grpcOpts, reqOpts) {
+ assert.deepEqual(grpcOpts, {
+ service: 'BigtableInstanceAdmin',
+ method: 'updateCluster'
+ });
+
+ assert.strictEqual(reqOpts.name, CLUSTER_ID);
+ done();
+ };
+
+ cluster.setMetadata({}, assert.ifError);
+ });
+
+ it('should respect the location option', function(done) {
+ var options = {
+ location: 'us-centralb-1'
+ };
+
+ var getLocation = Cluster.getLocation_;
+ var fakeLocation = 'a/b/c/d';
+
+ Cluster.getLocation_ = function(project, location) {
+ assert.strictEqual(project, PROJECT_ID);
+ assert.strictEqual(location, options.location);
+ return fakeLocation;
+ };
+
+ cluster.request = function(grpcOpts, reqOpts) {
+ assert.strictEqual(reqOpts.location, fakeLocation);
+ Cluster.getLocation_ = getLocation;
+ done();
+ };
+
+ cluster.setMetadata(options, assert.ifError);
+ });
+
+ it('should respect the nodes option', function(done) {
+ var options = {
+ nodes: 3
+ };
+
+ cluster.request = function(grpcOpts, reqOpts) {
+ assert.strictEqual(reqOpts.serveNodes, options.nodes);
+ done();
+ };
+
+ cluster.setMetadata(options, assert.ifError);
+ });
+
+ it('should respect the storage option', function(done) {
+ var options = {
+ storage: 'ssd'
+ };
+
+ var getStorageType = Cluster.getStorageType_;
+ var fakeStorageType = 'a';
+
+ Cluster.getStorageType_ = function(storage) {
+ assert.strictEqual(storage, options.storage);
+ return fakeStorageType;
+ };
+
+ cluster.request = function(grpcOpts, reqOpts) {
+ assert.strictEqual(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/packages/bigtable/test/family.js b/packages/bigtable/test/family.js
index b8ad445c479..89489ac2301 100644
--- a/packages/bigtable/test/family.js
+++ b/packages/bigtable/test/family.js
@@ -74,15 +74,20 @@ describe('Bigtable/Family', function() {
get: true,
delete: {
protoOpts: {
- service: 'BigtableTableService',
- method: 'deleteColumnFamily'
+ service: 'BigtableTableAdmin',
+ method: 'modifyColumnFamilies'
},
reqOpts: {
- name: FAMILY_ID
+ name: TABLE.id,
+ modifications: [{
+ drop: true,
+ id: FAMILY_NAME
+ }]
}
}
});
assert.strictEqual(typeof config.createMethod, 'function');
+ assert.strictEqual(family.familyName, FAMILY_NAME);
});
it('should call Table#createFamily for the create method', function(done) {
@@ -96,6 +101,11 @@ describe('Bigtable/Family', function() {
family.create(fakeOptions, done);
});
+
+ it('should extract the family name', function() {
+ var family = new Family(TABLE, FAMILY_ID);
+ assert.strictEqual(family.familyName, FAMILY_NAME);
+ });
});
describe('formatName_', function() {
@@ -229,30 +239,21 @@ describe('Bigtable/Family', function() {
describe('setMetadata', function() {
it('should provide the proper request options', function(done) {
- family.request = function(protoOpts, reqOpts, callback) {
+ family.request = function(protoOpts, reqOpts) {
assert.deepEqual(protoOpts, {
- service: 'BigtableTableService',
- method: 'updateColumnFamily'
+ service: 'BigtableTableAdmin',
+ method: 'modifyColumnFamilies'
});
- assert.strictEqual(reqOpts.name, FAMILY_ID);
- callback();
- };
-
- family.setMetadata({}, done);
- });
-
- it('should respect the gc expression option', function(done) {
- var metadata = {
- rule: 'a b c'
- };
-
- family.request = function(p, reqOpts) {
- assert.strictEqual(reqOpts.gcExpression, metadata.rule);
+ assert.strictEqual(reqOpts.name, TABLE.id);
+ assert.deepEqual(reqOpts.modifications, [{
+ id: FAMILY_NAME,
+ update: {}
+ }]);
done();
};
- family.setMetadata(metadata, assert.ifError);
+ family.setMetadata({}, assert.ifError);
});
it('should respect the gc rule option', function(done) {
@@ -276,7 +277,15 @@ describe('Bigtable/Family', function() {
};
family.request = function(p, reqOpts) {
- assert.strictEqual(reqOpts.gcRule, formattedRule);
+ assert.deepEqual(reqOpts, {
+ name: TABLE.id,
+ modifications: [{
+ id: family.familyName,
+ update: {
+ gcRule: formattedRule
+ }
+ }]
+ });
Family.formatRule_ = formatRule;
done();
};
@@ -284,27 +293,41 @@ describe('Bigtable/Family', function() {
family.setMetadata(metadata, assert.ifError);
});
- it('should respect the updated name option', function(done) {
- var formatName = Family.formatName_;
- var fakeName = 'a/b/c';
+ it('should return an error to the callback', function(done) {
+ var error = new Error('err');
+ var response = {};
- var metadata = {
- name: 'new-name'
+ family.request = function(protoOpts, reqOpts, callback) {
+ callback(error, response);
};
- Family.formatName_ = function(parent, newName) {
- assert.strictEqual(parent, TABLE.id);
- assert.strictEqual(newName, metadata.name);
- return fakeName;
+ family.setMetadata({}, function(err, metadata, apiResponse) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(metadata, null);
+ assert.strictEqual(apiResponse, response);
+ done();
+ });
+ });
+
+ it('should update the metadata property', function(done) {
+ var fakeMetadata = {};
+ var response = {
+ columnFamilies: {
+ 'family-test': fakeMetadata
+ }
};
- family.request = function(p, reqOpts) {
- assert.strictEqual(reqOpts.name, fakeName);
- Family.formatName_ = formatName;
- done();
+ family.request = function(protoOpts, reqOpts, callback) {
+ callback(null, response);
};
- family.setMetadata(metadata, assert.ifError);
+ family.setMetadata({}, function(err, metadata, apiResponse) {
+ assert.ifError(err);
+ assert.strictEqual(metadata, fakeMetadata);
+ assert.strictEqual(family.metadata, fakeMetadata);
+ assert.strictEqual(apiResponse, response);
+ done();
+ });
});
});
diff --git a/packages/bigtable/test/filter.js b/packages/bigtable/test/filter.js
index 717b840ae9a..c41d02bc462 100644
--- a/packages/bigtable/test/filter.js
+++ b/packages/bigtable/test/filter.js
@@ -80,9 +80,11 @@ describe('Bigtable/Filter', function() {
});
it('should throw an error for unknown types', function() {
+ var errorReg = /Can\'t convert to RegExp String from unknown type\./;
+
assert.throws(function() {
Filter.convertToRegExpString(true);
- }, /Can\'t convert to RegExp String from unknown type\./);
+ }, errorReg);
});
});
@@ -95,8 +97,8 @@ describe('Bigtable/Filter', function() {
var range = Filter.createRange(start, end, key);
assert.deepEqual(range, {
- startKeyInclusive: start,
- endKeyInclusive: end
+ startKeyClosed: start,
+ endKeyClosed: end
});
});
@@ -108,7 +110,7 @@ describe('Bigtable/Filter', function() {
assert(FakeMutation.convertToBytes.calledWithExactly(start));
assert.deepEqual(range, {
- startKeyInclusive: start
+ startKeyClosed: start
});
});
@@ -120,7 +122,7 @@ describe('Bigtable/Filter', function() {
assert(FakeMutation.convertToBytes.calledWithExactly(end));
assert.deepEqual(range, {
- endKeyInclusive: end
+ endKeyClosed: end
});
});
@@ -140,8 +142,8 @@ describe('Bigtable/Filter', function() {
var range = Filter.createRange(start, end, key);
assert.deepEqual(range, {
- startKeyExclusive: start.value,
- endKeyExclusive: end.value
+ startKeyOpen: start.value,
+ endKeyOpen: end.value
});
});
});
diff --git a/packages/bigtable/test/index.js b/packages/bigtable/test/index.js
index 82af59c9d85..6b719f21525 100644
--- a/packages/bigtable/test/index.js
+++ b/packages/bigtable/test/index.js
@@ -18,43 +18,39 @@
var assert = require('assert');
var extend = require('extend');
-var format = require('string-format-obj');
var googleProtoFiles = require('google-proto-files');
var nodeutil = require('util');
var proxyquire = require('proxyquire');
var sinon = require('sinon').sandbox.create();
-var GrpcService = require('@google-cloud/common').GrpcService;
+var common = require('@google-cloud/common');
+var Cluster = require('../src/cluster.js');
+var Instance = require('../src/instance.js');
var PKG = require('../package.json');
-var Table = require('../src/table.js');
-var util = require('@google-cloud/common').util;
-var fakeUtil = extend({}, util);
-
-function FakeGrpcService() {
- this.calledWith_ = arguments;
- GrpcService.apply(this, arguments);
-}
-
-nodeutil.inherits(FakeGrpcService, GrpcService);
-
-function FakeTable() {
- this.calledWith_ = arguments;
- Table.apply(this, arguments);
+var fakeUtil = extend({}, common.util);
+var fakeStreamRouter = {
+ extend: function() {
+ this.calledWith_ = arguments;
+ }
+};
+
+function createFake(Class) {
+ function Fake() {
+ this.calledWith_ = arguments;
+ Class.apply(this, arguments);
+ }
+ nodeutil.inherits(Fake, Class);
+ return Fake;
}
-function FakeFamily() {}
+var FakeGrpcService = createFake(common.GrpcService);
+var FakeCluster = createFake(Cluster);
+var FakeInstance = createFake(Instance);
+var FakeGrpcOperation = createFake(function() {});
describe('Bigtable', function() {
var PROJECT_ID = 'test-project';
- var ZONE = 'test-zone';
- var CLUSTER = 'test-cluster';
-
- var CLUSTER_NAME = format('projects/{p}/zones/{z}/clusters/{c}', {
- p: PROJECT_ID,
- z: ZONE,
- c: CLUSTER
- });
var Bigtable;
var bigtable;
@@ -63,10 +59,12 @@ describe('Bigtable', function() {
Bigtable = proxyquire('../', {
'@google-cloud/common': {
GrpcService: FakeGrpcService,
+ GrpcOperation: FakeGrpcOperation,
+ streamRouter: fakeStreamRouter,
util: fakeUtil
},
- './family.js': FakeFamily,
- './table.js': FakeTable
+ './cluster.js': FakeCluster,
+ './instance.js': FakeInstance
});
});
@@ -75,21 +73,22 @@ describe('Bigtable', function() {
});
beforeEach(function() {
- bigtable = new Bigtable({
- projectId: PROJECT_ID,
- zone: ZONE,
- cluster: CLUSTER
- });
+ bigtable = new Bigtable({ projectId: PROJECT_ID });
});
describe('instantiation', function() {
+ it('should streamify the correct methods', function() {
+ var args = fakeStreamRouter.calledWith_;
+
+ assert.strictEqual(args[0], Bigtable);
+ assert.deepEqual(args[1], ['getInstances']);
+ });
+
it('should normalize the arguments', function() {
var normalizeArguments = fakeUtil.normalizeArguments;
var normalizeArgumentsCalled = false;
var fakeOptions = {
- projectId: PROJECT_ID,
- zone: ZONE,
- cluster: CLUSTER
+ projectId: PROJECT_ID
};
var fakeContext = {};
@@ -106,325 +105,315 @@ describe('Bigtable', function() {
fakeUtil.normalizeArguments = normalizeArguments;
});
- it('should throw if a cluster is not provided', function() {
- assert.throws(function() {
- new Bigtable({});
- }, /A cluster must be provided to interact with Bigtable\./);
- });
-
- it('should throw if a zone is not provided', function() {
- assert.throws(function() {
- new Bigtable({
- cluster: CLUSTER
- });
- }, /A zone must be provided to interact with Bigtable\./);
- });
-
- it('should leave the original options unaltered', function() {
- var fakeOptions = {
- a: 'a',
- b: 'b',
- c: 'c',
- cluster: CLUSTER,
- zone: ZONE
- };
-
- var bigtable = new Bigtable(fakeOptions);
- var options = bigtable.calledWith_[1];
-
- for (var option in fakeOptions) {
- assert.strictEqual(fakeOptions[option], options[option]);
- }
-
- assert.notStrictEqual(fakeOptions, options);
- });
-
- it('should localize the cluster name', function() {
- assert.strictEqual(bigtable.clusterName, CLUSTER_NAME);
- });
-
it('should inherit from GrpcService', function() {
- assert(bigtable instanceof GrpcService);
+ assert(bigtable instanceof FakeGrpcService);
var calledWith = bigtable.calledWith_[0];
assert.strictEqual(calledWith.baseUrl, 'bigtable.googleapis.com');
assert.strictEqual(calledWith.service, 'bigtable');
- assert.strictEqual(calledWith.apiVersion, 'v1');
+ assert.strictEqual(calledWith.apiVersion, 'v2');
assert.deepEqual(calledWith.protoServices, {
- BigtableService: googleProtoFiles.bigtable.v1,
- BigtableTableService: {
- path: googleProtoFiles.bigtable.admin,
- service: 'bigtable.admin.table'
+ Bigtable: googleProtoFiles('bigtable/v2/bigtable.proto'),
+ BigtableTableAdmin: {
+ baseUrl: 'bigtableadmin.googleapis.com',
+ path: googleProtoFiles(
+ 'bigtable/admin/v2/bigtable_table_admin.proto'),
+ service: 'bigtable.admin'
+ },
+ BigtableInstanceAdmin: {
+ baseUrl: 'bigtableadmin.googleapis.com',
+ path: googleProtoFiles(
+ 'bigtable/admin/v2/bigtable_instance_admin.proto'
+ ),
+ service: 'bigtable.admin'
+ },
+ Operations: {
+ baseUrl: 'bigtableadmin.googleapis.com',
+ path: googleProtoFiles('longrunning/operations.proto'),
+ service: 'longrunning',
+ apiVersion: 'v1'
}
});
assert.deepEqual(calledWith.scopes, [
'https://www.googleapis.com/auth/bigtable.admin',
- 'https://www.googleapis.com/auth/bigtable.data'
+ 'https://www.googleapis.com/auth/bigtable.data',
+ 'https://www.googleapis.com/auth/cloud-platform'
]);
- assert.strictEqual(calledWith.userAgent, PKG.name + '/' + PKG.version);
- });
- });
- describe('formatTableName_', function() {
- it('should return the last section of a formatted table name', function() {
- var fakeTableName = 'projects/p/zones/z/clusters/c/tables/my-table';
- var formatted = Bigtable.formatTableName_(fakeTableName);
-
- assert.strictEqual(formatted, 'my-table');
+ assert.strictEqual(calledWith.userAgent, PKG.name + '/' + PKG.version);
});
- it('should do nothing if the table is name is not formatted', function() {
- var fakeTableName = 'my-table';
- var formatted = Bigtable.formatTableName_(fakeTableName);
-
- assert.strictEqual(formatted, fakeTableName);
+ it('should set the projectName', function() {
+ assert.strictEqual(bigtable.projectName, 'projects/' + PROJECT_ID);
});
});
- describe('createTable', function() {
- var TABLE_ID = 'my-table';
-
- it('should throw if a name is not provided', function() {
- assert.throws(function() {
- bigtable.createTable();
- }, /A name is required to create a table\./);
- });
+ describe('createInstance', function() {
+ var INSTANCE_NAME = 'my-instance';
it('should provide the proper request options', function(done) {
bigtable.request = function(protoOpts, reqOpts) {
assert.deepEqual(protoOpts, {
- service: 'BigtableTableService',
- method: 'createTable'
+ service: 'BigtableInstanceAdmin',
+ method: 'createInstance'
});
- assert.strictEqual(reqOpts.name, CLUSTER_NAME);
- assert.strictEqual(reqOpts.tableId, TABLE_ID);
- assert.deepEqual(reqOpts.table, {
- granularity: 0
- });
+ assert.strictEqual(reqOpts.parent, bigtable.projectName);
+ assert.strictEqual(reqOpts.instanceId, INSTANCE_NAME);
+ assert.strictEqual(reqOpts.instance.displayName, INSTANCE_NAME);
done();
};
- bigtable.createTable(TABLE_ID, assert.ifError);
+ bigtable.createInstance(INSTANCE_NAME, assert.ifError);
});
- it('should set the current operation', function(done) {
+ it('should respect the displayName option', function(done) {
var options = {
- operation: 'abc'
+ displayName: 'robocop'
};
bigtable.request = function(protoOpts, reqOpts) {
- assert.strictEqual(reqOpts.table.currentOperation, options.operation);
+ assert.strictEqual(reqOpts.instance.displayName, options.displayName);
done();
};
- bigtable.createTable(TABLE_ID, options, assert.ifError);
+ bigtable.createInstance(INSTANCE_NAME, options, assert.ifError);
});
- it('should set the initial split keys', function(done) {
- var options = {
- splits: ['a', 'b']
+ it('should respect the clusters option', function(done) {
+ var cluster = {
+ name: 'my-cluster',
+ location: 'us-central1-b',
+ nodes: 3,
+ storage: 'ssd'
};
- bigtable.request = function(protoOpts, reqOpts) {
- assert.strictEqual(reqOpts.initialSplitKeys, options.splits);
- done();
+ var options = {
+ clusters: [cluster]
};
- bigtable.createTable(TABLE_ID, options, assert.ifError);
- });
-
- describe('creating column families', function() {
- it('should accept a family name', function(done) {
- var options = {
- families: ['a', 'b']
- };
-
- bigtable.request = function(protoOpts, reqOpts) {
- assert.deepEqual(reqOpts.table.columnFamilies, {
- a: {},
- b: {}
- });
+ var fakeLocation = 'a/b/c/d';
+ FakeCluster.getLocation_ = function(project, location) {
+ assert.strictEqual(project, PROJECT_ID);
+ assert.strictEqual(location, cluster.location);
+ return fakeLocation;
+ };
- done();
- };
+ var fakeStorage = 20;
+ FakeCluster.getStorageType_ = function(storage) {
+ assert.strictEqual(storage, cluster.storage);
+ return fakeStorage;
+ };
- bigtable.createTable(TABLE_ID, options, assert.ifError);
- });
+ bigtable.request = function(protoOpts, reqOpts) {
+ assert.deepEqual(reqOpts.clusters, {
+ 'my-cluster': {
+ location: fakeLocation,
+ serveNodes: cluster.nodes,
+ defaultStorageType: fakeStorage
+ }
+ });
- it('should accept a garbage collection expression', function(done) {
- var options = {
- families: [
- {
- name: 'c',
- rule: 'd'
- }
- ]
- };
-
- bigtable.request = function(protoOpts, reqOpts) {
- assert.deepEqual(reqOpts.table.columnFamilies, {
- c: {
- gcExpression: 'd'
- }
- });
- done();
- };
-
- bigtable.createTable(TABLE_ID, options, assert.ifError);
- });
+ done();
+ };
- it('should accept a garbage collection object', function(done) {
- var options = {
- families: [
- {
- name: 'e',
- rule: {}
- }
- ]
- };
-
- var fakeRule = { a: 'b' };
-
- FakeFamily.formatRule_ = function(rule) {
- assert.strictEqual(rule, options.families[0].rule);
- return fakeRule;
- };
-
- bigtable.request = function(protoOpts, reqOpts) {
- assert.deepEqual(reqOpts.table.columnFamilies, {
- e: {
- gcRule: fakeRule
- }
- });
- done();
- };
-
- bigtable.createTable(TABLE_ID, options, assert.ifError);
- });
+ bigtable.createInstance(INSTANCE_NAME, options, assert.ifError);
});
it('should return an error to the callback', function(done) {
- var err = new Error('err');
+ var error = new Error('err');
var response = {};
bigtable.request = function(protoOpts, reqOpts, callback) {
- callback(err, response);
+ callback(error, response);
};
- bigtable.createTable(TABLE_ID, function(err_, table, apiResponse) {
- assert.strictEqual(err, err_);
- assert.strictEqual(table, null);
+ var callback = function(err, instance, operation, apiResponse) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(instance, null);
+ assert.strictEqual(operation, null);
assert.strictEqual(apiResponse, response);
done();
- });
+ };
+
+ bigtable.createInstance(INSTANCE_NAME, callback);
});
- it('should return a Table object', function(done) {
+ it('should pass an operation and instance to the callback', function(done) {
var response = {
- name: TABLE_ID
+ name: 'my-operation'
};
- var fakeTable = {};
+ var fakeInstance = {};
+ bigtable.instance = function(name) {
+ assert.strictEqual(name, INSTANCE_NAME);
+ return fakeInstance;
+ };
- var tableSpy = sinon.stub(bigtable, 'table', function() {
- return fakeTable;
- });
+ var fakeOperation = {};
+ bigtable.operation = function(name) {
+ assert.strictEqual(name, response.name);
+ return fakeOperation;
+ };
- bigtable.request = function(p, r, callback) {
+ bigtable.request = function(protoOpts, reqOpts, callback) {
callback(null, response);
};
- bigtable.createTable(TABLE_ID, function(err, table, apiResponse) {
+ var callback = function(err, instance, operation, apiResponse) {
assert.ifError(err);
- assert.strictEqual(table, fakeTable);
- assert(tableSpy.calledWithExactly(response.name));
- assert.strictEqual(table.metadata, response);
- assert.strictEqual(response, apiResponse);
+ assert.strictEqual(instance, fakeInstance);
+ assert.strictEqual(operation, fakeOperation);
+ assert.strictEqual(operation.metadata, response);
+ assert.strictEqual(apiResponse, response);
done();
- });
+ };
+
+ bigtable.createInstance(INSTANCE_NAME, callback);
});
});
- describe('getTables', function() {
+ describe('getInstances', function() {
it('should provide the proper request options', function(done) {
- bigtable.request = function(protoOpts, reqOpts) {
- assert.deepEqual(protoOpts, {
- service: 'BigtableTableService',
- method: 'listTables'
+ bigtable.request = function(grpcOpts, reqOpts) {
+ assert.deepEqual(grpcOpts, {
+ service: 'BigtableInstanceAdmin',
+ method: 'listInstances'
+ });
+
+ assert.strictEqual(reqOpts.parent, bigtable.projectName);
+ done();
+ };
+
+ bigtable.getInstances(assert.ifError);
+ });
+
+ it('should copy all query options', function(done) {
+ var fakeOptions = {
+ a: 'a',
+ b: 'b'
+ };
+
+ bigtable.request = function(grpcOpts, reqOpts) {
+ Object.keys(fakeOptions).forEach(function(key) {
+ assert.strictEqual(reqOpts[key], fakeOptions[key]);
});
- assert.strictEqual(reqOpts.name, CLUSTER_NAME);
+
+ assert.notStrictEqual(reqOpts, fakeOptions);
done();
};
- bigtable.getTables(assert.ifError);
+ bigtable.getInstances(fakeOptions, assert.ifError);
});
it('should return an error to the callback', function(done) {
- var err = new Error('err');
+ var error = new Error('err');
var response = {};
- bigtable.request = function(p, r, callback) {
- callback(err, response);
+ bigtable.request = function(grpcOpts, reqOpts, callback) {
+ callback(error, response);
};
- bigtable.getTables(function(err_, tables, apiResponse) {
- assert.strictEqual(err, err_);
- assert.strictEqual(tables, null);
+ bigtable.getInstances(function(err, instances, nextQuery, apiResponse) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(instances, null);
+ assert.strictEqual(nextQuery, null);
assert.strictEqual(apiResponse, response);
done();
});
});
- it('should return a list of Table objects', function(done) {
- var fakeFormattedName = 'abcd';
+ it('should return an array of instance objects', function(done) {
var response = {
- tables: [{
- name: 'projects/p/zones/z/clusters/c/tables/my-table'
+ instances: [{
+ name: 'a'
+ }, {
+ name: 'b'
}]
};
- var fakeTable = {};
- bigtable.request = function(p, r, callback) {
+ var fakeInstances = [
+ {},
+ {}
+ ];
+
+ bigtable.request = function(grpcOpts, reqOpts, callback) {
callback(null, response);
};
- var tableSpy = sinon.stub(bigtable, 'table', function() {
- return fakeTable;
- });
+ var instanceCount = 0;
- var formatSpy = sinon.stub(Bigtable, 'formatTableName_', function() {
- return fakeFormattedName;
- });
+ bigtable.instance = function(name) {
+ assert.strictEqual(name, response.instances[instanceCount].name);
+ return fakeInstances[instanceCount++];
+ };
- bigtable.getTables(function(err, tables, apiResponse) {
+ bigtable.getInstances(function(err, instances, nextQuery, 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();
+ });
+ });
- var table = tables[0];
+ it('should provide a nextQuery object', function(done) {
+ var response = {
+ instances: [],
+ nextPageToken: 'a'
+ };
+
+ var options = {
+ a: 'b'
+ };
+
+ bigtable.request = function(grpcOpts, reqOpts, callback) {
+ callback(null, response);
+ };
- assert.strictEqual(table, fakeTable);
- assert(formatSpy.calledWithExactly(response.tables[0].name));
- assert(tableSpy.calledWithExactly(fakeFormattedName));
- assert.strictEqual(table.metadata, response.tables[0]);
- assert.strictEqual(response, apiResponse);
+ var callback = function(err, instances, nextQuery) {
+ var expectedQuery = extend({}, options, {
+ pageToken: response.nextPageToken
+ });
+
+ assert.ifError(err);
+ assert.deepEqual(nextQuery, expectedQuery);
done();
- });
+ };
+
+ bigtable.getInstances(options, callback);
+ });
+ });
+
+ describe('instance', function() {
+ var INSTANCE_NAME = 'my-instance';
+
+ it('should return an Instance object', function() {
+ var instance = bigtable.instance(INSTANCE_NAME);
+ var args = instance.calledWith_;
+
+ assert(instance instanceof FakeInstance);
+ assert.strictEqual(args[0], bigtable);
+ assert.strictEqual(args[1], INSTANCE_NAME);
});
});
- describe('table', function() {
- var TABLE_ID = 'table-id';
+ describe('operation', function() {
+ var OPERATION_NAME = 'my-operation';
- it('should return a table instance', function() {
- var table = bigtable.table(TABLE_ID);
- var args = table.calledWith_;
+ it('should return a GrpcOperation object', function() {
+ var operation = bigtable.operation(OPERATION_NAME);
+ var args = operation.calledWith_;
- assert(table instanceof FakeTable);
+ assert(operation instanceof FakeGrpcOperation);
assert.strictEqual(args[0], bigtable);
- assert.strictEqual(args[1], TABLE_ID);
+ assert.strictEqual(args[1], OPERATION_NAME);
});
});
diff --git a/packages/bigtable/test/instance.js b/packages/bigtable/test/instance.js
new file mode 100644
index 00000000000..5aa689d929f
--- /dev/null
+++ b/packages/bigtable/test/instance.js
@@ -0,0 +1,696 @@
+/**
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var assert = require('assert');
+var proxyquire = require('proxyquire');
+var util = require('util');
+var format = require('string-format-obj');
+var extend = require('extend');
+
+var GrpcServiceObject = require('@google-cloud/common').GrpcServiceObject;
+var Cluster = require('../src/cluster.js');
+var Family = require('../src/family.js');
+var Table = require('../src/table.js');
+
+var fakeStreamRouter = {
+ extend: function() {
+ this.calledWith_ = arguments;
+ }
+};
+
+function createFake(Class) {
+ function Fake() {
+ this.calledWith_ = arguments;
+ Class.apply(this, arguments);
+ }
+
+ util.inherits(Fake, Class);
+ return Fake;
+}
+
+var FakeGrpcServiceObject = createFake(GrpcServiceObject);
+var FakeCluster = createFake(Cluster);
+var FakeFamily = createFake(Family);
+var FakeTable = createFake(Table);
+
+describe('Bigtable/Instance', function() {
+ var INSTANCE_NAME = 'my-instance';
+ var BIGTABLE = { projectName: 'projects/my-project' };
+
+ var INSTANCE_ID = format('{project}/instances/{instance}', {
+ project: BIGTABLE.projectName,
+ instance: INSTANCE_NAME
+ });
+
+ var CLUSTER_NAME = 'my-cluster';
+
+ var Instance;
+ var instance;
+
+ before(function() {
+ Instance = proxyquire('../src/instance.js', {
+ '@google-cloud/common': {
+ GrpcServiceObject: FakeGrpcServiceObject,
+ streamRouter: fakeStreamRouter
+ },
+ './cluster.js': FakeCluster,
+ './family.js': FakeFamily,
+ './table.js': FakeTable
+ });
+ });
+
+ beforeEach(function() {
+ instance = new Instance(BIGTABLE, INSTANCE_NAME);
+ });
+
+ describe('instantiation', function() {
+ it('should streamify the correct methods', function() {
+ var args = fakeStreamRouter.calledWith_;
+
+ assert.strictEqual(args[0], Instance);
+ assert.deepEqual(args[1], ['getClusters', 'getTables']);
+ });
+
+ it('should inherit from GrpcServiceObject', function() {
+ var config = instance.calledWith_[0];
+
+ assert(instance instanceof FakeGrpcServiceObject);
+ assert.strictEqual(config.parent, BIGTABLE);
+ assert.strictEqual(config.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 Bigtable#createInstance to create the table', function(done) {
+ var fakeOptions = {};
+ var config = instance.calledWith_[0];
+
+ BIGTABLE.createInstance = function(name, options, callback) {
+ assert.strictEqual(name, INSTANCE_NAME);
+ assert.strictEqual(options, fakeOptions);
+ callback();
+ };
+
+ config.createMethod(null, fakeOptions, 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
+ }
+ }
+ });
+ });
+ });
+
+ describe('createCluster', function() {
+ it('should provide the proper request options', function(done) {
+ instance.request = function(grpcOpts, reqOpts) {
+ assert.deepEqual(grpcOpts, {
+ service: 'BigtableInstanceAdmin',
+ method: 'createCluster'
+ });
+
+ assert.strictEqual(reqOpts.parent, INSTANCE_ID);
+ assert.strictEqual(reqOpts.clusterId, CLUSTER_NAME);
+ done();
+ };
+
+ instance.createCluster(CLUSTER_NAME, assert.ifError);
+ });
+
+ it('should respect the location option', function(done) {
+ var options = {
+ location: 'us-central1-b'
+ };
+
+ var fakeLocation = 'a/b/c/d';
+
+ FakeCluster.getLocation_ = function(project, location) {
+ assert.strictEqual(project, BIGTABLE.projectName);
+ assert.strictEqual(location, options.location);
+ return fakeLocation;
+ };
+
+ instance.request = function(grpcOpts, reqOpts) {
+ assert.strictEqual(reqOpts.cluster.location, fakeLocation);
+ done();
+ };
+
+ instance.createCluster(CLUSTER_NAME, options, assert.ifError);
+ });
+
+ it('should respect the nodes option', function(done) {
+ var options = {
+ nodes: 3
+ };
+
+ instance.request = function(grpcOpts, reqOpts) {
+ assert.strictEqual(reqOpts.cluster.serveNodes, options.nodes);
+ done();
+ };
+
+ instance.createCluster(CLUSTER_NAME, options, assert.ifError);
+ });
+
+ it('should respect the storage option', function(done) {
+ var options = {
+ storage: 'ssd'
+ };
+
+ var fakeStorageType = 2;
+
+ FakeCluster.getStorageType_ = function(type) {
+ assert.strictEqual(type, options.storage);
+ return fakeStorageType;
+ };
+
+ instance.request = function(grpcOpts, reqOpts) {
+ assert.strictEqual(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');
+ 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) {
+ callback(null, response);
+ };
+
+ BIGTABLE.operation = function(name) {
+ assert.strictEqual(name, response.name);
+ return fakeOperation;
+ };
+
+ 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);
+ assert.strictEqual(apiResponse, response);
+ done();
+ };
+
+ instance.createCluster(CLUSTER_NAME, callback);
+ });
+ });
+
+ describe('createTable', function() {
+ var TABLE_ID = 'my-table';
+
+ it('should throw if a name is not provided', function() {
+ assert.throws(function() {
+ instance.createTable();
+ }, /A name is required to create a table\./);
+ });
+
+ it('should provide the proper request options', function(done) {
+ instance.request = function(protoOpts, reqOpts) {
+ assert.deepEqual(protoOpts, {
+ service: 'BigtableTableAdmin',
+ method: 'createTable'
+ });
+
+ 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 set the initial split keys', function(done) {
+ var options = {
+ splits: ['a', 'b']
+ };
+
+ var expectedSplits = [
+ { key: 'a' },
+ { key: 'b' }
+ ];
+
+ instance.request = function(protoOpts, reqOpts) {
+ assert.deepEqual(reqOpts.initialSplits, expectedSplits);
+ done();
+ };
+
+ instance.createTable(TABLE_ID, options, assert.ifError);
+ });
+
+ describe('creating column families', function() {
+ it('should accept a family name', function(done) {
+ var options = {
+ families: ['a', 'b']
+ };
+
+ instance.request = function(protoOpts, reqOpts) {
+ assert.deepEqual(reqOpts.table.columnFamilies, {
+ a: {},
+ b: {}
+ });
+
+ done();
+ };
+
+ instance.createTable(TABLE_ID, options, assert.ifError);
+ });
+
+ it('should accept a garbage collection object', function(done) {
+ var options = {
+ families: [
+ {
+ name: 'e',
+ rule: {}
+ }
+ ]
+ };
+
+ var fakeRule = { a: 'b' };
+
+ FakeFamily.formatRule_ = function(rule) {
+ assert.strictEqual(rule, options.families[0].rule);
+ return fakeRule;
+ };
+
+ instance.request = function(protoOpts, reqOpts) {
+ assert.deepEqual(reqOpts.table.columnFamilies, {
+ e: {
+ gcRule: fakeRule
+ }
+ });
+ done();
+ };
+
+ instance.createTable(TABLE_ID, options, assert.ifError);
+ });
+ });
+
+ 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
+ };
+
+ var fakeTable = {};
+
+ instance.table = function(name) {
+ assert.strictEqual(name, response.name);
+ return fakeTable;
+ };
+
+ instance.request = function(p, r, callback) {
+ callback(null, response);
+ };
+
+ instance.createTable(TABLE_ID, function(err, table, apiResponse) {
+ assert.ifError(err);
+ assert.strictEqual(table, fakeTable);
+ assert.strictEqual(table.metadata, response);
+ assert.strictEqual(response, apiResponse);
+ done();
+ });
+ });
+ });
+
+ describe('cluster', function() {
+ it('should return a Cluster object', function() {
+ var cluster = instance.cluster(CLUSTER_NAME);
+
+ assert(cluster instanceof FakeCluster);
+
+ var args = cluster.calledWith_;
+
+ assert.strictEqual(args[0], instance);
+ assert.strictEqual(args[1], CLUSTER_NAME);
+ });
+ });
+
+ describe('getClusters', function() {
+ it('should provide the proper request options', function(done) {
+ instance.request = function(grpcOpts, reqOpts) {
+ assert.deepEqual(grpcOpts, {
+ service: 'BigtableInstanceAdmin',
+ method: 'listClusters'
+ });
+
+ assert.strictEqual(reqOpts.parent, INSTANCE_ID);
+ done();
+ };
+
+ instance.getClusters(assert.ifError);
+ });
+
+ it('should copy all query options', function(done) {
+ var fakeOptions = {
+ a: 'a',
+ b: 'b'
+ };
+
+ instance.request = function(grpcOpts, reqOpts) {
+ Object.keys(fakeOptions).forEach(function(key) {
+ assert.strictEqual(reqOpts[key], fakeOptions[key]);
+ });
+
+ assert.notStrictEqual(reqOpts, fakeOptions);
+ done();
+ };
+
+ instance.getClusters(fakeOptions, 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);
+ };
+
+ instance.getClusters(function(err, clusters, nextQuery, apiResponse) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(clusters, null);
+ assert.strictEqual(nextQuery, null);
+ assert.strictEqual(apiResponse, response);
+ done();
+ });
+ });
+
+ it('should return an array of cluster objects', function(done) {
+ var response = {
+ clusters: [{
+ name: 'a'
+ }, {
+ name: 'b'
+ }]
+ };
+
+ var fakeClusters = [
+ {},
+ {}
+ ];
+
+ instance.request = function(grpcOpts, reqOpts, callback) {
+ callback(null, response);
+ };
+
+ var clusterCount = 0;
+
+ instance.cluster = function(name) {
+ assert.strictEqual(name, response.clusters[clusterCount].name);
+ return fakeClusters[clusterCount++];
+ };
+
+ instance.getClusters(function(err, clusters, nextQuery, apiResponse) {
+ assert.ifError(err);
+ assert.strictEqual(clusters[0], fakeClusters[0]);
+ assert.strictEqual(clusters[0].metadata, response.clusters[0]);
+ assert.strictEqual(clusters[1], fakeClusters[1]);
+ assert.strictEqual(clusters[1].metadata, response.clusters[1]);
+ assert.strictEqual(nextQuery, null);
+ assert.strictEqual(apiResponse, response);
+ done();
+ });
+ });
+
+ it('should provide a nextQuery object', function(done) {
+ var response = {
+ clusters: [],
+ nextPageToken: 'a'
+ };
+
+ var options = {
+ a: 'b'
+ };
+
+ instance.request = function(grpcOpts, reqOpts, callback) {
+ callback(null, response);
+ };
+
+ instance.getClusters(options, function(err, clusters, nextQuery) {
+ var expectedQuery = extend({}, options, {
+ pageToken: response.nextPageToken
+ });
+
+ assert.ifError(err);
+ assert.deepEqual(nextQuery, expectedQuery);
+ done();
+ });
+ });
+ });
+
+ describe('getTables', function() {
+ var views = FakeTable.VIEWS = {
+ unspecified: 0,
+ name: 1,
+ schema: 2,
+ full: 4
+ };
+
+ 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);
+ done();
+ };
+
+ instance.getTables(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]);
+ done();
+ };
+
+ instance.getTables(options, assert.ifError);
+ });
+ });
+
+ 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 = {};
+
+ var response = {
+ tables: [{
+ name: tableName
+ }]
+ };
+
+ instance.request = function(p, r, callback) {
+ callback(null, response);
+ };
+
+ instance.table = function(name) {
+ assert.strictEqual(name, fakeFormattedName);
+ return fakeTable;
+ };
+
+ instance.getTables(function(err, tables, nextQuery, apiResponse) {
+ assert.ifError(err);
+
+ var table = tables[0];
+
+ assert.strictEqual(table, fakeTable);
+ assert.strictEqual(table.metadata, response.tables[0]);
+ assert.strictEqual(nextQuery, null);
+ assert.strictEqual(response, apiResponse);
+ done();
+ });
+ });
+
+ it('should create a nextQuery object', function(done) {
+ var response = {
+ tables: [],
+ nextPageToken: 'a'
+ };
+
+ var options = {
+ a: 'b'
+ };
+
+ instance.request = function(protoOpts, reqOpts, callback) {
+ callback(null, response);
+ };
+
+ instance.getTables(options, function(err, tables, nextQuery) {
+ assert.ifError(err);
+
+ var expectedQuery = extend({}, options, {
+ pageToken: response.nextPageToken
+ });
+
+ assert.deepEqual(nextQuery, expectedQuery);
+ done();
+ });
+ });
+ });
+
+ describe('table', function() {
+ var TABLE_ID = 'table-id';
+
+ it('should return a table instance', function() {
+ var table = instance.table(TABLE_ID);
+ var args = table.calledWith_;
+
+ assert(table instanceof FakeTable);
+ assert.strictEqual(args[0], instance);
+ assert.strictEqual(args[1], TABLE_ID);
+ });
+ });
+
+});
diff --git a/packages/bigtable/test/mutation.js b/packages/bigtable/test/mutation.js
index 36024b0961b..eff4369192b 100644
--- a/packages/bigtable/test/mutation.js
+++ b/packages/bigtable/test/mutation.js
@@ -28,13 +28,13 @@ describe('Bigtable/Mutation', function() {
});
describe('instantiation', function() {
- it('should localize all the mutation properties', function() {
- var fakeData = {
- key: 'a',
- method: 'b',
- data: 'c'
- };
+ var fakeData = {
+ key: 'a',
+ method: 'b',
+ data: 'c'
+ };
+ it('should localize all the mutation properties', function() {
var mutation = new Mutation(fakeData);
assert.strictEqual(mutation.key, fakeData.key);
@@ -62,6 +62,13 @@ describe('Bigtable/Mutation', function() {
});
describe('convertToBytes', function() {
+ it('should not re-wrap buffers', function() {
+ var buf = new Buffer('hello');
+ var encoded = Mutation.convertToBytes(buf);
+
+ assert.strictEqual(buf, encoded);
+ });
+
it('should pack numbers into int64 values', function() {
var num = 10;
var encoded = Mutation.convertToBytes(num);
@@ -175,6 +182,29 @@ describe('Bigtable/Mutation', function() {
assert.strictEqual(convertCalls.length, 2);
assert.deepEqual(convertCalls, ['gwashington', 1]);
});
+
+ it('should accept buffers', function() {
+ var val = new Buffer('hello');
+ var fakeMutation = {
+ follows: {
+ gwashington: val
+ }
+ };
+
+ var cells = Mutation.encodeSetCell(fakeMutation);
+
+ assert.deepEqual(cells, [{
+ setCell: {
+ familyName: 'follows',
+ columnQualifier: 'gwashington',
+ timestampMicros: -1,
+ value: val
+ }
+ }]);
+
+ assert.strictEqual(convertCalls.length, 2);
+ assert.deepEqual(convertCalls, ['gwashington', val]);
+ });
});
describe('encodeDelete', function() {
@@ -376,15 +406,17 @@ describe('Bigtable/Mutation', function() {
data: []
};
+ var mutation = new Mutation(data);
+
Mutation.encodeSetCell = function(_data) {
assert.strictEqual(_data, data.data);
return fakeEncoded;
};
- var mutation = new Mutation(data).toProto();
+ var mutationProto = mutation.toProto();
- assert.strictEqual(mutation.mutations, fakeEncoded);
- assert.strictEqual(mutation.rowKey, data.key);
+ assert.strictEqual(mutationProto.mutations, fakeEncoded);
+ assert.strictEqual(mutationProto.rowKey, data.key);
assert.strictEqual(convertCalls[0], data.key);
});
diff --git a/packages/bigtable/test/row.js b/packages/bigtable/test/row.js
index 49c507be125..0c4e05c3d80 100644
--- a/packages/bigtable/test/row.js
+++ b/packages/bigtable/test/row.js
@@ -110,50 +110,265 @@ describe('Bigtable/Row', function() {
});
describe('formatChunks_', function() {
- var formatFamiliesSpy;
+ var convert;
beforeEach(function() {
- formatFamiliesSpy = sinon.stub(Row, 'formatFamilies_');
+ convert = FakeMutation.convertFromBytes;
+ FakeMutation.convertFromBytes = sinon.spy(function(val) {
+ return val.replace('unconverted', 'converted');
+ });
+ });
+
+ afterEach(function() {
+ FakeMutation.convertFromBytes = convert;
+ });
+
+ it('should format the chunks', function() {
+ var timestamp = Date.now();
+ var chunks = [{
+ rowKey: 'unconvertedKey',
+ familyName: {
+ value: 'familyName'
+ },
+ qualifier: {
+ value: 'unconvertedQualifier'
+ },
+ value: 'unconvertedValue',
+ labels: ['label'],
+ timestampMicros: timestamp,
+ valueSize: 0,
+ commitRow: false,
+ resetRow: false
+ }, {
+ commitRow: true
+ }];
+
+ var rows = Row.formatChunks_(chunks);
+
+ assert.deepEqual(rows, [{
+ key: 'convertedKey',
+ data: {
+ familyName: {
+ convertedQualifier: [{
+ value: 'convertedValue',
+ labels: ['label'],
+ timestamp: timestamp,
+ size: 0
+ }]
+ }
+ }
+ }]);
});
- it('should not include chunks without commitRow', function() {
+ it('should inherit the row key', function() {
var chunks = [{
- rowConents: {}
+ rowKey: 'unconvertedKey'
+ }, {
+ rowKey: null,
+ familyName: {
+ value: 'familyName'
+ },
+ commitRow: true
+ }, {
+ rowKey: 'unconvertedKey2'
+ }, {
+ rowKey: null,
+ familyName: {
+ value: 'familyName2'
+ },
+ commitRow: true
}];
- var fakeFamilies = [];
- formatFamiliesSpy.returns(fakeFamilies);
+ var rows = Row.formatChunks_(chunks);
- var formatted = Row.formatChunks_(chunks);
+ assert.deepEqual(rows, [{
+ key: 'convertedKey',
+ data: {
+ familyName: {}
+ }
+ }, {
+ key: 'convertedKey2',
+ data: {
+ familyName2: {}
+ }
+ }]);
+ });
+
+ it('should inherit the family name', function() {
+ var chunks = [{
+ rowKey: 'unconvertedKey',
+ familyName: {
+ value: 'familyName'
+ }
+ }, {
+ qualifier: {
+ value: 'unconvertedQualifier'
+ }
+ }, {
+ qualifier: {
+ value: 'unconvertedQualifier2'
+ }
+ }, {
+ commitRow: true
+ }];
- assert.strictEqual(formatted, fakeFamilies);
- assert.strictEqual(formatFamiliesSpy.callCount, 1);
- assert.deepEqual(formatFamiliesSpy.getCall(0).args[0], []);
+ var rows = Row.formatChunks_(chunks);
+
+ assert.deepEqual(rows, [{
+ key: 'convertedKey',
+ data: {
+ familyName: {
+ convertedQualifier: [],
+ convertedQualifier2: []
+ }
+ }
+ }]);
});
- it('should ignore any chunks previous to a resetRow', function() {
- var badData = {};
- var goodData = {};
- var fakeFamilies = [goodData, {}];
+ it('should inherit the qualifier', function() {
+ var timestamp1 = 123;
+ var timestamp2 = 345;
var chunks = [{
- rowContents: badData,
+ rowKey: 'unconvertedKey',
+ familyName: {
+ value: 'familyName'
+ },
+ qualifier: {
+ value: 'unconvertedQualifier'
+ }
}, {
- resetRow: true
+ value: 'unconvertedValue',
+ labels: ['label'],
+ timestampMicros: timestamp1,
+ valueSize: 0
}, {
- rowContents: goodData
+ value: 'unconvertedValue2',
+ labels: ['label2'],
+ timestampMicros: timestamp2,
+ valueSize: 2
}, {
commitRow: true
}];
- formatFamiliesSpy.returns(fakeFamilies);
+ var rows = Row.formatChunks_(chunks);
+
+ assert.deepEqual(rows, [{
+ key: 'convertedKey',
+ data: {
+ familyName: {
+ convertedQualifier: [{
+ value: 'convertedValue',
+ labels: ['label'],
+ timestamp: timestamp1,
+ size: 0
+ }, {
+ value: 'convertedValue2',
+ labels: ['label2'],
+ timestamp: timestamp2,
+ size: 2
+ }]
+ }
+ }
+ }]);
+ });
+
+ it('should not decode values when applicable', function() {
+ var timestamp1 = 123;
+ var timestamp2 = 345;
+
+ var chunks = [{
+ rowKey: 'unconvertedKey',
+ familyName: {
+ value: 'familyName'
+ },
+ qualifier: {
+ value: 'unconvertedQualifier'
+ }
+ }, {
+ value: 'unconvertedValue',
+ labels: ['label'],
+ timestampMicros: timestamp1,
+ valueSize: 0
+ }, {
+ value: 'unconvertedValue2',
+ labels: ['label2'],
+ timestampMicros: timestamp2,
+ valueSize: 2
+ }, {
+ commitRow: true
+ }];
- var formatted = Row.formatChunks_(chunks);
+ var rows = Row.formatChunks_(chunks, {
+ decode: false
+ });
- assert.strictEqual(formatted, fakeFamilies);
- assert.strictEqual(formatted.indexOf(badData), -1);
- assert.strictEqual(formatFamiliesSpy.callCount, 1);
- assert.deepEqual(formatFamiliesSpy.getCall(0).args[0], [goodData]);
+ assert.deepEqual(rows, [{
+ key: 'convertedKey',
+ data: {
+ familyName: {
+ convertedQualifier: [{
+ value: 'unconvertedValue',
+ labels: ['label'],
+ timestamp: timestamp1,
+ size: 0
+ }, {
+ value: 'unconvertedValue2',
+ labels: ['label2'],
+ timestamp: timestamp2,
+ size: 2
+ }]
+ }
+ }
+ }]);
+ });
+
+ it('should discard old data when reset row is found', function() {
+ var chunks = [{
+ rowKey: 'unconvertedKey',
+ familyName: {
+ value: 'familyName'
+ },
+ qualifier: {
+ value: 'unconvertedQualifier'
+ },
+ value: 'unconvertedValue',
+ labels: ['label'],
+ valueSize: 0,
+ timestampMicros: 123
+ }, {
+ resetRow: true
+ }, {
+ rowKey: 'unconvertedKey2',
+ familyName: {
+ value: 'familyName2'
+ },
+ qualifier: {
+ value: 'unconvertedQualifier2'
+ },
+ value: 'unconvertedValue2',
+ labels: ['label2'],
+ valueSize: 2,
+ timestampMicros: 345
+ }, {
+ commitRow: true
+ }];
+
+ var rows = Row.formatChunks_(chunks);
+
+ assert.deepEqual(rows, [{
+ key: 'convertedKey2',
+ data: {
+ familyName2: {
+ convertedQualifier2: [{
+ value: 'convertedValue2',
+ labels: ['label2'],
+ size: 2,
+ timestamp: 345
+ }]
+ }
+ }
+ }]);
});
});
@@ -191,6 +406,18 @@ describe('Bigtable/Row', function() {
assert.strictEqual(convertStpy.getCall(0).args[0], 'test-column');
assert.strictEqual(convertStpy.getCall(1).args[0], 'test-value');
});
+
+ it('should optionally not decode the value', function() {
+ var formatted = Row.formatFamilies_(families, {
+ decode: false
+ });
+
+ assert.deepEqual(formatted, formattedRowData);
+
+ var convertStpy = FakeMutation.convertFromBytes;
+ assert.strictEqual(convertStpy.callCount, 1);
+ assert.strictEqual(convertStpy.getCall(0).args[0], 'test-column');
+ });
});
describe('create', function() {
@@ -219,6 +446,20 @@ describe('Bigtable/Row', function() {
row.create(data, assert.ifError);
});
+ it('should accept options when inserting data', function(done) {
+ var data = {
+ a: 'a',
+ b: 'b'
+ };
+
+ row.parent.mutate = function(entry) {
+ assert.strictEqual(entry.data, data);
+ done();
+ };
+
+ row.create(data, assert.ifError);
+ });
+
it('should return an error to the callback', function(done) {
var err = new Error('err');
var response = {};
@@ -267,7 +508,7 @@ describe('Bigtable/Row', function() {
it('should read/modify/write rules', function(done) {
row.request = function(grpcOpts, reqOpts, callback) {
assert.deepEqual(grpcOpts, {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'readModifyWriteRow'
});
@@ -345,7 +586,7 @@ describe('Bigtable/Row', function() {
row.request = function(grpcOpts, reqOpts) {
assert.deepEqual(grpcOpts, {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'checkAndMutateRow'
});
@@ -456,7 +697,7 @@ describe('Bigtable/Row', function() {
describe('get', function() {
it('should provide the proper request options', function(done) {
row.parent.getRows = function(reqOpts) {
- assert.strictEqual(reqOpts.key, ROW_ID);
+ assert.strictEqual(reqOpts.keys[0], ROW_ID);
assert.strictEqual(reqOpts.filter, undefined);
assert.strictEqual(FakeMutation.parseColumnName.callCount, 0);
done();
@@ -539,6 +780,44 @@ describe('Bigtable/Row', function() {
row.get(keys, assert.ifError);
});
+ it('should respect the options object', function(done) {
+ var keys = [
+ 'a'
+ ];
+
+ var options = {
+ decode: false
+ };
+
+ var expectedFilter = [{
+ family: 'a'
+ }];
+
+ row.parent.getRows = function(reqOpts) {
+ assert.deepEqual(reqOpts.filter, expectedFilter);
+ assert.strictEqual(FakeMutation.parseColumnName.callCount, 1);
+ assert(FakeMutation.parseColumnName.calledWith(keys[0]));
+ assert.strictEqual(reqOpts.decode, options.decode);
+ done();
+ };
+
+ row.get(keys, options, assert.ifError);
+ });
+
+ it('should accept options without keys', function(done) {
+ var options = {
+ decode: false
+ };
+
+ row.parent.getRows = function(reqOpts) {
+ assert.strictEqual(reqOpts.decode, options.decode);
+ assert(!reqOpts.filter);
+ done();
+ };
+
+ row.get(options, assert.ifError);
+ });
+
it('should return an error to the callback', function(done) {
var error = new Error('err');
var response = {};
@@ -626,7 +905,7 @@ describe('Bigtable/Row', function() {
var error = new Error('err');
var response = {};
- row.get = function(callback) {
+ row.get = function(options, callback) {
callback(error, null, response);
};
@@ -640,19 +919,42 @@ describe('Bigtable/Row', function() {
it('should return metadata to the callback', function(done) {
var response = {};
- var metadata = {
+ var fakeMetadata = {
a: 'a',
b: 'b'
};
- row.get = function(callback) {
- row.metadata = metadata;
+ row.get = function(options, callback) {
callback(null, row, response);
};
+ row.metadata = fakeMetadata;
+
row.getMetadata(function(err, metadata, apiResponse) {
assert.ifError(err);
- assert.strictEqual(metadata, metadata);
+ 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
+ };
+
+ row.get = function(options, callback) {
+ assert.strictEqual(options, fakeOptions);
+ callback(null, row, response);
+ };
+
+ row.metadata = fakeMetadata;
+
+ row.getMetadata(fakeOptions, function(err, metadata, apiResponse) {
+ assert.ifError(err);
+ assert.strictEqual(metadata, fakeMetadata);
assert.strictEqual(response, apiResponse);
done();
});
@@ -714,18 +1016,19 @@ describe('Bigtable/Row', function() {
it('should pass back the updated value to the callback', function(done) {
var fakeValue = 10;
var response = {
- key: 'fakeKey',
- families: [{
- name: 'a',
- columns: [{
- qualifier: 'b',
- cells: [{
- timestampMicros: Date.now(),
- value: fakeValue,
- labels: []
+ row: {
+ families: [{
+ name: 'a',
+ columns: [{
+ qualifier: 'b',
+ cells: [{
+ timestampMicros: Date.now(),
+ value: fakeValue,
+ labels: []
+ }]
}]
}]
- }]
+ }
};
row.createRules = function(r, callback) {
@@ -737,14 +1040,14 @@ describe('Bigtable/Row', function() {
assert.strictEqual(value, fakeValue);
assert.strictEqual(apiResponse, response);
assert.strictEqual(formatFamiliesSpy.callCount, 1);
- assert(formatFamiliesSpy.calledWithExactly(response.families));
+ assert(formatFamiliesSpy.calledWithExactly(response.row.families));
done();
});
});
});
describe('save', function() {
- it('should insert a key value pair', function(done) {
+ describe('key value pair', function() {
var key = 'a:b';
var value = 'c';
@@ -754,37 +1057,45 @@ describe('Bigtable/Row', function() {
}
};
- var parseSpy = Mutation.parseColumnName = sinon.spy(function() {
- return {
- family: 'd',
- qualifier: 'e'
- };
+ var parseSpy;
+
+ beforeEach(function() {
+ parseSpy = Mutation.parseColumnName = sinon.spy(function() {
+ return {
+ family: 'd',
+ qualifier: 'e'
+ };
+ });
});
- 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();
- };
+ 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(key, value, done);
+ });
});
- it('should insert an object', function(done) {
+ describe('object mode', function() {
var data = {
a: {
b: 'c'
}
};
- row.parent.mutate = function(entry) {
- assert.strictEqual(entry.data, data);
- done();
- };
+ it('should insert an object', function(done) {
+ row.parent.mutate = function(entry) {
+ assert.strictEqual(entry.data, data);
+ done();
+ };
- row.save(data, assert.ifError);
+ row.save(data, assert.ifError);
+ });
});
});
diff --git a/packages/bigtable/test/table.js b/packages/bigtable/test/table.js
index 27986f2424b..b159450f560 100644
--- a/packages/bigtable/test/table.js
+++ b/packages/bigtable/test/table.js
@@ -17,6 +17,7 @@
'use strict';
var assert = require('assert');
+var events = require('events');
var nodeutil = require('util');
var proxyquire = require('proxyquire');
var pumpify = require('pumpify');
@@ -24,40 +25,34 @@ var sinon = require('sinon').sandbox.create();
var Stream = require('stream').PassThrough;
var through = require('through2');
+var common = require('@google-cloud/common');
var Family = require('../src/family.js');
-var GrpcServiceObject = require('@google-cloud/common').GrpcServiceObject;
var Mutation = require('../src/mutation.js');
var Row = require('../src/row.js');
-function FakeGrpcServiceObject() {
- this.calledWith_ = arguments;
- GrpcServiceObject.apply(this, arguments);
+function createFake(Class) {
+ function Fake() {
+ this.calledWith_ = arguments;
+ Class.apply(this, arguments);
+ }
+ nodeutil.inherits(Fake, Class);
+ return Fake;
}
-nodeutil.inherits(FakeGrpcServiceObject, GrpcServiceObject);
-
-function FakeFamily() {
- this.calledWith_ = arguments;
- Family.apply(this, arguments);
-}
+var FakeGrpcService = createFake(common.GrpcService);
+var FakeGrpcServiceObject = createFake(common.GrpcServiceObject);
+var FakeFamily = createFake(Family);
FakeFamily.formatRule_ = sinon.spy(function(rule) {
return rule;
});
-nodeutil.inherits(FakeFamily, Family);
-
-function FakeRow() {
- this.calledWith_ = arguments;
- Row.apply(this, arguments);
-}
+var FakeRow = createFake(Row);
FakeRow.formatChunks_ = sinon.spy(function(chunks) {
return chunks;
});
-nodeutil.inherits(FakeRow, Row);
-
var FakeMutation = {
methods: Mutation.methods,
convertToBytes: sinon.spy(function(value) {
@@ -79,10 +74,11 @@ var FakeFilter = {
describe('Bigtable/Table', function() {
var TABLE_ID = 'my-table';
- var BIGTABLE = {
- clusterName: 'a/b/c/d'
+ var INSTANCE = {
+ id: 'a/b/c/d'
};
- var TABLE_NAME = BIGTABLE.clusterName + '/tables/' + TABLE_ID;
+
+ var TABLE_NAME = INSTANCE.id + '/tables/' + TABLE_ID;
var Table;
var table;
@@ -90,6 +86,7 @@ describe('Bigtable/Table', function() {
before(function() {
Table = proxyquire('../src/table.js', {
'@google-cloud/common': {
+ GrpcService: FakeGrpcService,
GrpcServiceObject: FakeGrpcServiceObject
},
'./family.js': FakeFamily,
@@ -101,7 +98,7 @@ describe('Bigtable/Table', function() {
});
beforeEach(function() {
- table = new Table(BIGTABLE, TABLE_ID);
+ table = new Table(INSTANCE, TABLE_ID);
});
afterEach(function() {
@@ -124,18 +121,18 @@ describe('Bigtable/Table', function() {
return FAKE_TABLE_NAME;
});
- var table = new Table(BIGTABLE, TABLE_ID);
+ var table = new Table(INSTANCE, TABLE_ID);
var config = table.calledWith_[0];
assert(table instanceof FakeGrpcServiceObject);
- assert.strictEqual(config.parent, BIGTABLE);
+ assert.strictEqual(config.parent, INSTANCE);
assert.strictEqual(config.id, FAKE_TABLE_NAME);
assert.deepEqual(config.methods, {
create: true,
delete: {
protoOpts: {
- service: 'BigtableTableService',
+ service: 'BigtableTableAdmin',
method: 'deleteTable'
},
reqOpts: {
@@ -143,25 +140,16 @@ describe('Bigtable/Table', function() {
}
},
exists: true,
- get: true,
- getMetadata: {
- protoOpts: {
- service: 'BigtableTableService',
- method: 'getTable'
- },
- reqOpts: {
- name: FAKE_TABLE_NAME
- }
- }
+ get: true
});
- assert(Table.formatName_.calledWith(BIGTABLE.clusterName, TABLE_ID));
+ assert(Table.formatName_.calledWith(INSTANCE.id, TABLE_ID));
});
- it('should use Bigtable#createTable to create the table', function(done) {
+ it('should use Instance#createTable to create the table', function(done) {
var fakeOptions = {};
- BIGTABLE.createTable = function(name, options, callback) {
+ INSTANCE.createTable = function(name, options, callback) {
assert.strictEqual(name, TABLE_ID);
assert.strictEqual(options, fakeOptions);
callback();
@@ -171,49 +159,31 @@ describe('Bigtable/Table', function() {
});
});
+ describe('VIEWS', function() {
+ var views = {
+ unspecified: 0,
+ name: 1,
+ schema: 2,
+ full: 4
+ };
+
+ it('should export the table views', function() {
+ assert.deepEqual(views, Table.VIEWS);
+ });
+ });
+
describe('formatName_', function() {
it('should format the table name to include the cluster name', function() {
- var tableName = Table.formatName_(BIGTABLE.clusterName, TABLE_ID);
+ var tableName = Table.formatName_(INSTANCE.id, TABLE_ID);
assert.strictEqual(tableName, TABLE_NAME);
});
it('should not re-format the table name', function() {
- var tableName = Table.formatName_(BIGTABLE.clusterName, TABLE_NAME);
+ var tableName = Table.formatName_(INSTANCE.id, TABLE_NAME);
assert.strictEqual(tableName, TABLE_NAME);
});
});
- describe('formatRowRange_', function() {
- it('should create a row range object', function() {
- var fakeRange = {
- start: 'a',
- end: 'b'
- };
- var convertedFakeRange = {
- start: 'c',
- end: 'd'
- };
-
- var spy = FakeMutation.convertToBytes = sinon.spy(function(value) {
- if (value === fakeRange.start) {
- return convertedFakeRange.start;
- }
- return convertedFakeRange.end;
- });
-
- var range = Table.formatRowRange_(fakeRange);
-
- assert.deepEqual(range, {
- startKey: convertedFakeRange.start,
- endKey: convertedFakeRange.end
- });
-
- assert.strictEqual(spy.callCount, 2);
- assert.strictEqual(spy.getCall(0).args[0], 'a');
- assert.strictEqual(spy.getCall(1).args[0], 'b');
- });
- });
-
describe('createFamily', function() {
var COLUMN_ID = 'my-column';
@@ -226,27 +196,20 @@ describe('Bigtable/Table', function() {
it('should provide the proper request options', function(done) {
table.request = function(grpcOpts, reqOpts) {
assert.deepEqual(grpcOpts, {
- service: 'BigtableTableService',
- method: 'createColumnFamily'
+ service: 'BigtableTableAdmin',
+ method: 'modifyColumnFamilies'
});
assert.strictEqual(reqOpts.name, TABLE_NAME);
- assert.strictEqual(reqOpts.columnFamilyId, COLUMN_ID);
- done();
- };
+ assert.deepEqual(reqOpts.modifications, [{
+ id: COLUMN_ID,
+ create: {}
+ }]);
- table.createFamily(COLUMN_ID, assert.ifError);
- });
-
- it('should respect the gc expression option', function(done) {
- var expression = 'a && b';
-
- table.request = function(g, reqOpts) {
- assert.strictEqual(reqOpts.columnFamily.gcExpression, expression);
done();
};
- table.createFamily(COLUMN_ID, expression, assert.ifError);
+ table.createFamily(COLUMN_ID, assert.ifError);
});
it('should respect the gc rule option', function(done) {
@@ -264,7 +227,9 @@ describe('Bigtable/Table', function() {
});
table.request = function(g, reqOpts) {
- assert.strictEqual(reqOpts.columnFamily.gcRule, convertedRule);
+ var modification = reqOpts.modifications[0];
+
+ assert.strictEqual(modification.create.gcRule, convertedRule);
assert.strictEqual(spy.callCount, 1);
assert.strictEqual(spy.getCall(0).args[0], rule);
done();
@@ -318,11 +283,11 @@ describe('Bigtable/Table', function() {
it('should provide the proper request options', function(done) {
table.request = function(grpcOpts, reqOpts, callback) {
assert.deepEqual(grpcOpts, {
- service: 'BigtableTableService',
- method: 'bulkDeleteRows'
+ service: 'BigtableTableAdmin',
+ method: 'dropRowRange'
});
- assert.strictEqual(reqOpts.tableName, TABLE_NAME);
+ assert.strictEqual(reqOpts.name, TABLE_NAME);
callback();
};
@@ -429,25 +394,94 @@ describe('Bigtable/Table', function() {
});
});
+ describe('getMetadata', function() {
+ var views = {
+ unspecified: 0,
+ name: 1,
+ schema: 2,
+ full: 4
+ };
+ beforeEach(function() {
+ Table.VIEWS = views;
+ });
+
+ it('should provide the proper request options', function(done) {
+ table.request = function(grpcOpts, reqOpts) {
+ assert.deepEqual(grpcOpts, {
+ service: 'BigtableTableAdmin',
+ method: 'getTable'
+ });
+
+ assert.strictEqual(reqOpts.name, table.id);
+ assert.strictEqual(reqOpts.view, views.unspecified);
+ done();
+ };
+
+ table.getMetadata(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]);
+ done();
+ };
+
+ table.getMetadata(options, assert.ifError);
+ });
+ });
+
+ it('should return an error to the callback', function(done) {
+ var error = new Error('err');
+ var response = {};
+
+ table.request = function(grpcOpts, reqOpts, callback) {
+ callback(error, response);
+ };
+
+ table.getMetadata(function(err, metadata, apiResponse) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(metadata, null);
+ assert.strictEqual(apiResponse, response);
+ done();
+ });
+ });
+
+ it('should update the metadata', function(done) {
+ var response = {};
+
+ table.request = function(grpcOpts, reqOpts, callback) {
+ callback(null, response);
+ };
+
+ table.getMetadata(function(err, metadata, apiResponse) {
+ assert.ifError(err);
+ assert.strictEqual(metadata, response);
+ assert.strictEqual(apiResponse, response);
+ assert.strictEqual(table.metadata, response);
+ done();
+ });
+ });
+ });
+
describe('getRows', function() {
describe('options', function() {
var pumpSpy;
- var formatSpy;
beforeEach(function() {
pumpSpy = sinon.stub(pumpify, 'obj', function() {
return through.obj();
});
-
- formatSpy = sinon.stub(Table, 'formatRowRange_', function(value) {
- return value;
- });
});
it('should provide the proper request options', function(done) {
table.requestStream = function(grpcOpts, reqOpts) {
assert.deepEqual(grpcOpts, {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'readRows'
});
@@ -459,26 +493,6 @@ describe('Bigtable/Table', function() {
table.getRows();
});
- it('should retrieve an individual row', function(done) {
- var options = {
- key: 'gwashington'
- };
- var fakeKey = 'a';
-
- var convertSpy = FakeMutation.convertToBytes = sinon.spy(function() {
- return fakeKey;
- });
-
- table.requestStream = function(g, reqOpts) {
- assert.strictEqual(reqOpts.rowKey, fakeKey);
- assert.strictEqual(convertSpy.callCount, 1);
- assert.strictEqual(convertSpy.getCall(0).args[0], options.key);
- done();
- };
-
- table.getRows(options);
- });
-
it('should retrieve a range of rows', function(done) {
var options = {
start: 'gwashington',
@@ -490,14 +504,18 @@ describe('Bigtable/Table', function() {
end: 'b'
};
- var formatSpy = Table.formatRowRange_ = sinon.spy(function() {
+ var formatSpy = FakeFilter.createRange = sinon.spy(function() {
return fakeRange;
});
table.requestStream = function(g, reqOpts) {
- assert.strictEqual(reqOpts.rowRange, fakeRange);
+ assert.deepEqual(reqOpts.rows.rowRanges[0], fakeRange);
assert.strictEqual(formatSpy.callCount, 1);
- assert.strictEqual(formatSpy.getCall(0).args[0], options);
+ assert.deepEqual(formatSpy.getCall(0).args, [
+ options.start,
+ options.end,
+ 'key'
+ ]);
done();
};
@@ -522,7 +540,7 @@ describe('Bigtable/Table', function() {
});
table.requestStream = function(g, reqOpts) {
- assert.deepEqual(reqOpts.rowSet.rowKeys, convertedKeys);
+ assert.deepEqual(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]);
@@ -551,16 +569,23 @@ describe('Bigtable/Table', function() {
end: 'h'
}];
- var formatSpy = Table.formatRowRange_ = sinon.spy(function(range) {
- var rangeIndex = options.ranges.indexOf(range);
- return fakeRanges[rangeIndex];
+ var formatSpy = FakeFilter.createRange = sinon.spy(function() {
+ return fakeRanges[formatSpy.callCount - 1];
});
table.requestStream = function(g, reqOpts) {
- assert.deepEqual(reqOpts.rowSet.rowRanges, fakeRanges);
+ assert.deepEqual(reqOpts.rows.rowRanges, fakeRanges);
assert.strictEqual(formatSpy.callCount, 2);
- assert.strictEqual(formatSpy.getCall(0).args[0], options.ranges[0]);
- assert.strictEqual(formatSpy.getCall(1).args[0], options.ranges[1]);
+ assert.deepEqual(formatSpy.getCall(0).args, [
+ options.ranges[0].start,
+ options.ranges[0].end,
+ 'key'
+ ]);
+ assert.deepEqual(formatSpy.getCall(1).args, [
+ options.ranges[1].start,
+ options.ranges[1].end,
+ 'key'
+ ]);
done();
};
@@ -588,19 +613,6 @@ describe('Bigtable/Table', function() {
table.getRows(options);
});
- it('should allow row interleaving', function(done) {
- var options = {
- interleave: true
- };
-
- table.requestStream = function(g, reqOpts) {
- assert.strictEqual(reqOpts.allowRowInterleaving, options.interleave);
- done();
- };
-
- table.getRows(options);
- });
-
it('should allow setting a row limit', function(done) {
var options = {
limit: 10
@@ -616,20 +628,21 @@ describe('Bigtable/Table', function() {
});
describe('success', function() {
- var fakeRows = [{
- rowKey: 'a',
- chunks: []
- }, {
- rowKey: 'b',
- chunks: []
- }];
- var convertedKeys = [
- 'c',
- 'd'
- ];
- var formattedChunks = [
- [{ a: 'a' }],
- [{ b: 'b' }]
+ var fakeChunks = {
+ chunks: [{
+ rowKey: 'a',
+ }, {
+ commitRow: true
+ }, {
+ rowKey: 'b',
+ }, {
+ commitRow: true
+ }]
+ };
+
+ var formattedRows = [
+ { key: 'c', data: {} },
+ { key: 'd', data: {} }
];
beforeEach(function() {
@@ -637,18 +650,8 @@ describe('Bigtable/Table', function() {
return {};
});
- FakeMutation.convertFromBytes = sinon.spy(function(value) {
- if (value === fakeRows[0].rowKey) {
- return convertedKeys[0];
- }
- return convertedKeys[1];
- });
-
- FakeRow.formatChunks_ = sinon.spy(function(value) {
- if (value === fakeRows[0].chunks) {
- return formattedChunks[0];
- }
- return formattedChunks[1];
+ FakeRow.formatChunks_ = sinon.spy(function() {
+ return formattedRows;
});
table.requestStream = function() {
@@ -657,10 +660,7 @@ describe('Bigtable/Table', function() {
});
setImmediate(function() {
- fakeRows.forEach(function(data) {
- stream.push(data);
- });
-
+ stream.push(fakeChunks);
stream.push(null);
});
@@ -668,6 +668,19 @@ describe('Bigtable/Table', function() {
};
});
+ it('should pass the decode option', function(done) {
+ var options = {
+ decode: false
+ };
+
+ table.getRows(options, function(err) {
+ assert.ifError(err);
+ var formatArgs = FakeRow.formatChunks_.getCall(0).args[1];
+ assert.strictEqual(formatArgs.decode, options.decode);
+ done();
+ });
+ });
+
it('should stream Row objects', function(done) {
var rows = [];
@@ -680,18 +693,16 @@ describe('Bigtable/Table', function() {
var rowSpy = table.row;
var formatSpy = FakeRow.formatChunks_;
- assert.strictEqual(rows.length, fakeRows.length);
- assert.strictEqual(rowSpy.callCount, fakeRows.length);
+ assert.strictEqual(rows.length, formattedRows.length);
+ assert.strictEqual(rowSpy.callCount, formattedRows.length);
- assert.strictEqual(rowSpy.getCall(0).args[0], convertedKeys[0]);
- assert.strictEqual(rows[0].data, formattedChunks[0]);
- assert.strictEqual(
- formatSpy.getCall(0).args[0], fakeRows[0].chunks);
+ assert.strictEqual(formatSpy.getCall(0).args[0], fakeChunks.chunks);
- assert.strictEqual(rowSpy.getCall(1).args[0], convertedKeys[1]);
- assert.strictEqual(rows[1].data, formattedChunks[1]);
- assert.strictEqual(
- formatSpy.getCall(1).args[0], fakeRows[1].chunks);
+ assert.strictEqual(rowSpy.getCall(0).args[0], formattedRows[0].key);
+ assert.strictEqual(rows[0].data, formattedRows[0].data);
+
+ assert.strictEqual(rowSpy.getCall(1).args[0], formattedRows[1].key);
+ assert.strictEqual(rows[1].data, formattedRows[1].data);
done();
});
@@ -704,18 +715,16 @@ describe('Bigtable/Table', function() {
var rowSpy = table.row;
var formatSpy = FakeRow.formatChunks_;
- assert.strictEqual(rows.length, fakeRows.length);
- assert.strictEqual(rowSpy.callCount, fakeRows.length);
+ assert.strictEqual(rows.length, formattedRows.length);
+ assert.strictEqual(rowSpy.callCount, formattedRows.length);
+
+ assert.strictEqual(formatSpy.getCall(0).args[0], fakeChunks.chunks);
- assert.strictEqual(rowSpy.getCall(0).args[0], convertedKeys[0]);
- assert.strictEqual(rows[0].data, formattedChunks[0]);
- assert.strictEqual(
- formatSpy.getCall(0).args[0], fakeRows[0].chunks);
+ assert.strictEqual(rowSpy.getCall(0).args[0], formattedRows[0].key);
+ assert.strictEqual(rows[0].data, formattedRows[0].data);
- assert.strictEqual(rowSpy.getCall(1).args[0], convertedKeys[1]);
- assert.strictEqual(rows[1].data, formattedChunks[1]);
- assert.strictEqual(
- formatSpy.getCall(1).args[0], fakeRows[1].chunks);
+ assert.strictEqual(rowSpy.getCall(1).args[0], formattedRows[1].key);
+ assert.strictEqual(rows[1].data, formattedRows[1].data);
done();
});
@@ -786,21 +795,37 @@ describe('Bigtable/Table', function() {
table.insert(fakeEntries, done);
});
+
+ it('should return the mutate stream', function() {
+ var fakeStream = {};
+
+ table.mutate = function() {
+ return fakeStream;
+ };
+
+ var stream = table.insert([]);
+ assert.strictEqual(stream, fakeStream);
+ });
});
describe('mutate', function() {
- it('should provide the proper request options', function(done) {
- var entries = [{}, {}];
- var fakeEntries = [{}, {}];
+ var entries = [{}, {}];
+ var fakeEntries = [{}, {}];
+ var parseSpy;
- var parseSpy = FakeMutation.parse = sinon.spy(function(value) {
+ beforeEach(function() {
+ parseSpy = FakeMutation.parse = sinon.spy(function(value) {
var entryIndex = entries.indexOf(value);
return fakeEntries[entryIndex];
});
+ });
- table.request = function(grpcOpts, reqOpts, callback) {
+ it('should provide the proper request options', function(done) {
+ var stream = through.obj();
+
+ table.requestStream = function(grpcOpts, reqOpts) {
assert.deepEqual(grpcOpts, {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'mutateRows'
});
@@ -810,10 +835,165 @@ describe('Bigtable/Table', function() {
assert.strictEqual(parseSpy.callCount, 2);
assert.strictEqual(parseSpy.getCall(0).args[0], entries[0]);
assert.strictEqual(parseSpy.getCall(1).args[0], entries[1]);
- callback();
+
+ setImmediate(done);
+
+ return stream;
};
- table.mutate(entries, done);
+ table.mutate(entries, assert.ifError);
+ });
+
+ describe('error', function() {
+ describe('API errors', function() {
+ var error = new Error('err');
+
+ beforeEach(function() {
+ table.requestStream = function() {
+ var stream = new Stream({
+ objectMode: true
+ });
+
+ setImmediate(function() {
+ stream.emit('error', error);
+ });
+
+ return stream;
+ };
+ });
+
+ it('should return the error to the callback', function(done) {
+ table.mutate(entries, function(err) {
+ assert.strictEqual(err, error);
+ done();
+ });
+ });
+
+ it('should emit the error via error event', function(done) {
+ table.mutate(entries).on('error', function(err) {
+ assert.strictEqual(err, error);
+ done();
+ });
+ });
+ });
+
+ describe('mutation errors', function() {
+ var fakeStatuses = [{
+ index: 0,
+ status: {
+ code: 1
+ }
+ }, {
+ index: 1,
+ status: {
+ code: 1
+ }
+ }];
+
+ var parsedStatuses = [{}, {}];
+
+ beforeEach(function() {
+ table.requestStream = function() {
+ var stream = through.obj();
+
+ stream.push({ entries: fakeStatuses });
+
+ setImmediate(function() {
+ stream.end();
+ });
+
+ return stream;
+ };
+
+ var statusCount = 0;
+ FakeGrpcService.decorateStatus_ = function(status) {
+ assert.strictEqual(status, fakeStatuses[statusCount].status);
+ return parsedStatuses[statusCount++];
+ };
+ });
+
+ it('should populate the mutationErrors array', function(done) {
+ table.mutate(entries, function(err, mutationErrors) {
+ assert.ifError(err);
+ assert.strictEqual(mutationErrors[0], parsedStatuses[0]);
+ assert.strictEqual(mutationErrors[0].entry, entries[0]);
+ assert.strictEqual(mutationErrors[1], parsedStatuses[1]);
+ assert.strictEqual(mutationErrors[1].entry, entries[1]);
+ done();
+ });
+ });
+
+ it('should emit a mutation error as an error event', function(done) {
+ var mutationErrors = [];
+ var emitter = table.mutate(entries);
+
+ assert(emitter instanceof events.EventEmitter);
+
+ emitter
+ .on('error', function(err) {
+ mutationErrors.push(err);
+ })
+ .on('complete', function() {
+ assert.strictEqual(mutationErrors[0], parsedStatuses[0]);
+ assert.strictEqual(mutationErrors[0].entry, entries[0]);
+ assert.strictEqual(mutationErrors[1], parsedStatuses[1]);
+ assert.strictEqual(mutationErrors[1].entry, entries[1]);
+ done();
+ });
+ });
+ });
+ });
+
+ describe('success', function() {
+ var fakeStatuses = [{
+ index: 0,
+ status: {
+ code: 0
+ }
+ }, {
+ index: 1,
+ status: {
+ code: 0
+ }
+ }];
+
+ beforeEach(function() {
+ table.requestStream = function() {
+ var stream = through.obj();
+
+ stream.push({ entries: fakeStatuses });
+
+ setImmediate(function() {
+ stream.end();
+ });
+
+ return stream;
+ };
+
+ FakeGrpcServiceObject.decorateStatus_ = function() {
+ throw new Error('Should not be called');
+ };
+ });
+
+ it('should return an empty array to the callback', function(done) {
+ table.mutate(entries, function(err, mutateErrors) {
+ assert.ifError(err);
+ assert.strictEqual(mutateErrors.length, 0);
+ done();
+ });
+ });
+
+ it('should emit the appropriate stream events', function(done) {
+ var emitter = table.mutate(entries);
+
+ assert(emitter instanceof events.EventEmitter);
+
+ emitter
+ .on('error', done) // should not be emitted
+ .on('complete', function() {
+ done();
+ });
+ });
});
});
@@ -839,7 +1019,7 @@ describe('Bigtable/Table', function() {
it('should provide the proper request options', function(done) {
table.requestStream = function(grpcOpts, reqOpts) {
assert.deepEqual(grpcOpts, {
- service: 'BigtableService',
+ service: 'Bigtable',
method: 'sampleRowKeys'
});