From 3f742eaf8946e0c42a40f148fc19ae39fa88265f Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 20 Aug 2014 18:55:57 -0400 Subject: [PATCH 1/7] datastore: scope Key creation to include NS. fixes #116. --- README.md | 39 ++++++++++++--------------------- lib/datastore/dataset.js | 45 +++++++++++++++++++++++--------------- lib/datastore/entity.js | 39 ++++++++++++++++----------------- lib/datastore/index.js | 12 ---------- lib/datastore/query.js | 2 +- regression/datastore.js | 42 +++++++++++++++++------------------ test/datastore/dataset.js | 28 ++++++++++++++---------- test/datastore/entity.js | 46 ++++++++++++++++++++++++++++----------- test/datastore/query.js | 8 +++++-- 9 files changed, 139 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 850cbe66360..954a8a9ec36 100644 --- a/README.md +++ b/README.md @@ -99,12 +99,12 @@ TODO Get operations require a valid key to retrieve the key identified entity from Datastore. Skip to the "Querying" section if you'd like to learn more about querying against Datastore. ~~~~ js -ds.get(datastore.key('Company', 123), function(err, entity) {}); +ds.get(ds.key('Company', 123), function(err, entity) {}); // alternatively, you can retrieve multiple entities at once. ds.get([ - datastore.key('Company', 123), - datastore.key('Product', 'Computer') + ds.key('Company', 123), + ds.key('Product', 'Computer') ], function(err, entities) {}); ~~~~ @@ -114,15 +114,15 @@ To learn more about keys and incomplete keys, skip to the Keys section. ~~~~ js ds.save({ - key: datastore.key('Company', null), data: {/*...*/} + key: ds.key('Company', null), data: {/*...*/} }, function(err, key) { // First arg is an incomplete key for Company kind. // console.log(key) will output ['Company', 599900452312]. }); // alternatively, you can save multiple entities at once. ds.save([ - { key: datastore.key('Company', 123), data: {/*...*/} }, - { key: datastore.key('Product', 'Computer'), data: {/*...*/} } + { key: ds.key('Company', 123), data: {/*...*/} }, + { key: ds.key('Product', 'Computer'), data: {/*...*/} } ], function(err, keys) { // if the first key was incomplete, keys[0] will return the generated key. }); @@ -136,10 +136,10 @@ ds.delete(['Company', 599900452312], function(err) {}); // alternatively, you can delete multiple entities of different // kinds at once. ds.delete([ - datastore.key('Company', 599900452312), - datastore.key('Company', 599900452315), - datastore.key('Office', 'mtv'), - datastore.key('Company', 123, 'Employee', 'jbd') + ds.key('Company', 599900452312), + ds.key('Company', 599900452315), + ds.key('Office', 'mtv'), + ds.key('Company', 123, 'Employee', 'jbd') ], function(err) {}); ~~~~ @@ -178,14 +178,14 @@ stored as properties is not currently supported. ~~~~ js var q = ds.createQuery('Company') - .filter('__key__ =', datastore.key('Company', 'Google')) + .filter('__key__ =', ds.key('Company', 'Google')) ~~~~ In order to filter by ancestors, use `hasAncestor` helper. ~~~ js var q = ds.createQuery('Child') - .hasAncestor(datastore.key('Parent', 123)); + .hasAncestor(ds.key('Parent', 123)); ~~~ ##### Sorting @@ -223,25 +223,14 @@ var q = ds.createQuery('Company') #### Allocating IDs (ID generation) You can generate IDs without creating entities. The following call will create -100 new IDs from the Company kind which exists under the default namespace. +100 new IDs from the Company kind which exists under the dataset's namespace. ~~~~ js -ds.allocateIds(datastore.key('Company', null), 100, function(err, keys) { +ds.allocateIds(ds.key('Company', null), 100, function(err, keys) { }); ~~~~ -You may prefer to create IDs from a non-default namespace by providing -an incomplete key with a namespace. Similar to the previous example, the -call below will create 100 new IDs, but from the Company kind that exists -under the "ns-test" namespace. - -~~~~ js -var incompleteKey = datastore.key('ns-test', 'Company', null); -ds.allocateIds(incompleteKey, 100, function(err, keys) { -}); -~~~~ - #### Transactions Datastore has support for transactions. Transactions allow you to perform diff --git a/lib/datastore/dataset.js b/lib/datastore/dataset.js index 4a8027df1e9..b5a514181ed 100644 --- a/lib/datastore/dataset.js +++ b/lib/datastore/dataset.js @@ -54,32 +54,49 @@ var SCOPES = [ * @constructor * @alias module:datastore/dataset * - * @param {object} options - * @param {string} options.id - Dataset ID. This is your project ID from the - * Google Developers Console. + * @param {object=} options + * @param {string} options.projectId - Dataset ID. This is your project ID from + * the Google Developers Console. * @param {string} options.keyFileName - Full path to the JSON key downloaded * from the Google Developers Console. + * @param {string} options.namespace - Namespace to isolate transactions to. * * @example * ```js * var dataset = new Dataset({ - * id: 'my-project', + * projectId: 'my-project', * keyFileName: '/path/to/keyfile.json' * }); * ``` */ -function Dataset(opts) { - opts = opts || {}; - var id = opts.projectId; +function Dataset(options) { + options = options || {}; this.connection = new conn.Connection({ - keyFilename: opts.keyFilename, + keyFilename: options.keyFilename, scopes: SCOPES }); - this.id = id; + this.id = options.projectId; + this.ns = options.namespace; this.transaction = this.createTransaction_(); } +/** + * Helper to create a Key object, scoped to the namespace if present. + * + * @example + * ```js + * var key = dataset.key('Company', 123); + * ``` + */ +Dataset.prototype.key = function() { + return new entity.Key({ + namespace: this.ns, + path: [].slice.call(arguments) + }); +}; + + /** * Create a query from the current dataset to query the specified kinds. * @@ -88,17 +105,11 @@ function Dataset(opts) { * @example * ```js * var query = dataset.createQuery(['Lion', 'Chimp']); - * var zooQuery = dataset.createQuery('zoo', ['Lion', 'Chimp']); * ``` * @return {module:datastore/query} */ -Dataset.prototype.createQuery = function(ns, kinds) { - if (!kinds) { - kinds = ns; - ns = ''; - } - kinds = util.arrayize(kinds); - return new Query(ns, kinds); +Dataset.prototype.createQuery = function(kinds) { + return new Query(this.ns, util.arrayize(kinds)); }; /** diff --git a/lib/datastore/entity.js b/lib/datastore/entity.js index 87804994ed6..3a645e05bd0 100644 --- a/lib/datastore/entity.js +++ b/lib/datastore/entity.js @@ -89,18 +89,22 @@ var SIGN_TO_ORDER = { * Build a Datastore Key object. * * @constructor - * @param {...*} - Key descriptors. + * @param {object} - Configuration object. + * @param {...*} options.path - Key path. + * @param {string=} options.namespace - Optional namespace. * * @example * ```js * var key = new Key('Company', 123); * ``` */ -function Key() { - if (arguments.length > 1) { - this.path_ = [].slice.call(arguments); +function Key(options) { + this.namespace_ = options.namespace; + + if (options.path.length > 1) { + this.path_ = [].slice.call(options.path); } else { - this.path_ = arguments[0]; + this.path_ = options.path[0]; } } @@ -220,15 +224,17 @@ module.exports.entityFromEntityProto = entityFromEntityProto; * ``` */ function keyFromKeyProto(proto) { - var keyPath = []; + var keyOptions = { + path: [] + }; if (proto.partition_id && proto.partition_id.namespace) { - keyPath.push(proto.partition_id.namespace); + keyOptions.namespace = proto.partition_id.namespace; } proto.path_element.forEach(function(path) { - keyPath.push(path.kind); - keyPath.push(Number(path.id) || path.name || null); + keyOptions.path.push(path.kind); + keyOptions.path.push(Number(path.id) || path.name || null); }); - return new Key(keyPath); + return new Key(keyOptions); } module.exports.keyFromKeyProto = keyFromKeyProto; @@ -261,15 +267,8 @@ function keyToKeyProto(key) { if (keyPath.length < 2) { throw new Error('A key should contain at least a kind and an identifier.'); } - var namespace = null; - var start = 0; - if (keyPath.length % 2 === 1) { - // the first item is the namespace - namespace = keyPath[0]; - start = 1; - } var path = []; - for (var i = start; i < (keyPath.length - start); i += 2) { + for (var i = 0; i < keyPath.length; i += 2) { var p = { kind: keyPath[i] }; var val = keyPath[i+1]; if (val) { @@ -285,9 +284,9 @@ function keyToKeyProto(key) { var proto = { path_element: path }; - if (namespace) { + if (key.namespace_) { proto.partition_id = { - namespace: namespace + namespace: key.namespace_ }; } return proto; diff --git a/lib/datastore/index.js b/lib/datastore/index.js index 08b7ebea6e7..d69c9e3d6bd 100644 --- a/lib/datastore/index.js +++ b/lib/datastore/index.js @@ -36,18 +36,6 @@ var datastore = {}; */ datastore.Dataset = require('./dataset'); -/** - * @borrows {module:datastore/entity~Key} as key - * - * @example - * ```js - * var key = dataset.key('Company', 123); - * ``` - */ -datastore.key = function() { - return new entity.Key([].slice.call(arguments)); -}; - /** * @borrows {module:datastore/entity~Int} as int * diff --git a/lib/datastore/query.js b/lib/datastore/query.js index 96d1d061eac..913bc979c72 100644 --- a/lib/datastore/query.js +++ b/lib/datastore/query.js @@ -38,7 +38,7 @@ var util = require('../common/util.js'); * ``` */ function Query(namespace, kinds) { - this.namespace = namespace; + this.namespace = namespace || ''; this.kinds = kinds; this.filters = []; diff --git a/regression/datastore.js b/regression/datastore.js index 845a12a4327..9e36c65f8ad 100644 --- a/regression/datastore.js +++ b/regression/datastore.js @@ -28,7 +28,7 @@ var entity = require('../lib/datastore/entity.js'); describe('datastore', function() { it('should allocate IDs', function(done) { - ds.allocateIds(datastore.key('Kind', null), 10, function(err, keys) { + ds.allocateIds(ds.key('Kind', null), 10, function(err, keys) { assert.ifError(err); assert.equal(keys.length, 10); assert.equal(entity.isKeyComplete(keys[0]), true); @@ -48,7 +48,7 @@ describe('datastore', function() { }; it('should save/get/delete with a key name', function(done) { - var postKey = datastore.key('Post', 'post1'); + var postKey = ds.key('Post', 'post1'); ds.save({ key: postKey, data: post }, function(err, key) { assert.ifError(err); assert.equal(key.path_[1], 'post1'); @@ -64,7 +64,7 @@ describe('datastore', function() { }); it('should save/get/delete with a numeric key id', function(done) { - var postKey = datastore.key('Post', 123456789); + var postKey = ds.key('Post', 123456789); ds.save({ key: postKey, data: post @@ -84,16 +84,16 @@ describe('datastore', function() { it('should save/get/delete with a generated key id', function(done) { ds.save({ - key: datastore.key('Post', null), + key: ds.key('Post', null), data: post }, function(err, key) { assert.ifError(err); var assignedId = key.path_[1]; assert(assignedId); - ds.get(datastore.key('Post', assignedId), function(err, entity) { + ds.get(ds.key('Post', assignedId), function(err, entity) { assert.ifError(err); assert.deepEqual(entity.data, post); - ds.delete(datastore.key('Post', assignedId), function(err) { + ds.delete(ds.key('Post', assignedId), function(err) { assert.ifError(err); done(); }); @@ -111,15 +111,15 @@ describe('datastore', function() { wordCount: 450, rating: 4.5, }; - var key = datastore.key('Post', null); + var key = ds.key('Post', null); ds.save([ { key: key, data: post }, { key: key, data: post2 } ], function(err, keys) { assert.ifError(err); assert.equal(keys.length,2); - var firstKey = datastore.key('Post', keys[0].path_[1]); - var secondKey = datastore.key('Post', keys[1].path_[1]); + var firstKey = ds.key('Post', keys[0].path_[1]); + var secondKey = ds.key('Post', keys[1].path_[1]); ds.get([firstKey, secondKey], function(err, entities) { assert.ifError(err); assert.equal(entities.length, 2); @@ -135,7 +135,7 @@ describe('datastore', function() { it('should be able to save keys as a part of entity and query by key', function(done) { - var personKey = datastore.key('Person', 'name'); + var personKey = ds.key('Person', 'name'); ds.save({ key: personKey, data: { @@ -158,14 +158,14 @@ describe('datastore', function() { describe('querying the datastore', function() { var keys = [ - datastore.key('Character', 'Rickard'), - datastore.key('Character', 'Rickard', 'Character', 'Eddard'), - datastore.key('Character', 'Catelyn'), - datastore.key('Character', 'Eddard', 'Character', 'Arya'), - datastore.key('Character', 'Eddard', 'Character', 'Sansa'), - datastore.key('Character', 'Eddard', 'Character', 'Robb'), - datastore.key('Character', 'Eddard', 'Character', 'Bran'), - datastore.key('Character', 'Eddard', 'Character', 'Jon Snow') + ds.key('Character', 'Rickard'), + ds.key('Character', 'Rickard', 'Character', 'Eddard'), + ds.key('Character', 'Catelyn'), + ds.key('Character', 'Eddard', 'Character', 'Arya'), + ds.key('Character', 'Eddard', 'Character', 'Sansa'), + ds.key('Character', 'Eddard', 'Character', 'Robb'), + ds.key('Character', 'Eddard', 'Character', 'Bran'), + ds.key('Character', 'Eddard', 'Character', 'Jon Snow') ]; var characters = [{ @@ -265,7 +265,7 @@ describe('datastore', function() { it('should filter by ancestor', function(done) { var q = ds.createQuery('Character') - .hasAncestor(datastore.key('Character', 'Eddard')); + .hasAncestor(ds.key('Character', 'Eddard')); ds.runQuery(q, function(err, entities) { assert.ifError(err); assert.equal(entities.length, 5); @@ -275,7 +275,7 @@ describe('datastore', function() { it('should filter by key', function(done) { var q = ds.createQuery('Character') - .filter('__key__ =', datastore.key('Character', 'Rickard')); + .filter('__key__ =', ds.key('Character', 'Rickard')); ds.runQuery(q, function(err, entities) { assert.ifError(err); assert.equal(entities.length, 1); @@ -370,7 +370,7 @@ describe('datastore', function() { describe('transactions', function() { it('should run in a transaction', function(done) { - var key = datastore.key('Company', 'Google'); + var key = ds.key('Company', 'Google'); var obj = { url: 'www.google.com' }; diff --git a/test/datastore/dataset.js b/test/datastore/dataset.js index 4f78500e15b..b6071aeac89 100644 --- a/test/datastore/dataset.js +++ b/test/datastore/dataset.js @@ -25,6 +25,12 @@ var mockRespGet = require('../testdata/response_get.json'); var Transaction = require('../../lib/datastore/transaction.js'); describe('Dataset', function() { + it('should return a key scoped by namespace', function() { + var ds = new datastore.Dataset({ projectId: 'test', namespace: 'my-ns' }); + var key = ds.key('Company', 1); + assert.equal(key.namespace_, 'my-ns'); + }); + it('should get by key', function(done) { var ds = new datastore.Dataset({ projectId: 'test' }); ds.transaction.makeReq = function(method, proto, typ, callback) { @@ -32,7 +38,7 @@ describe('Dataset', function() { assert.equal(proto.key.length, 1); callback(null, mockRespGet); }; - ds.get(datastore.key('Kind', 123), function(err, entity) { + ds.get(ds.key('Kind', 123), function(err, entity) { var data = entity.data; assert.deepEqual(entity.key.path_, ['Kind', 5732568548769792]); assert.strictEqual(data.author, 'Silvano'); @@ -49,7 +55,7 @@ describe('Dataset', function() { assert.equal(proto.key.length, 1); callback(null, mockRespGet); }; - var key = datastore.key('Kind', 5732568548769792); + var key = ds.key('Kind', 5732568548769792); ds.get([key], function(err, entities) { var entity = entities[0]; var data = entity.data; @@ -68,7 +74,7 @@ describe('Dataset', function() { assert.equal(!!proto.mutation.delete, true); callback(); }; - ds.delete(datastore.key('Kind', 123), done); + ds.delete(ds.key('Kind', 123), done); }); it('should multi delete by keys', function(done) { @@ -79,8 +85,8 @@ describe('Dataset', function() { callback(); }; ds.delete([ - datastore.key('Kind', 123), - datastore.key('Kind', 345) + ds.key('Kind', 123), + ds.key('Kind', 345) ], done); }); @@ -91,7 +97,7 @@ describe('Dataset', function() { assert.equal(proto.mutation.insert_auto_id.length, 1); callback(); }; - var key = datastore.key('Kind', null); + var key = ds.key('Kind', null); ds.save({ key: key, data: {} }, done); }); @@ -106,8 +112,8 @@ describe('Dataset', function() { callback(); }; ds.save([ - { key: datastore.key('Kind', 123), data: { k: 'v' } }, - { key: datastore.key('Kind', 456), data: { k: 'v' } } + { key: ds.key('Kind', 123), data: { k: 'v' } }, + { key: ds.key('Kind', 456), data: { k: 'v' } } ], done); }); @@ -126,8 +132,8 @@ describe('Dataset', function() { ] }); }; - ds.allocateIds(datastore.key('Kind', null), 1, function(err, ids) { - assert.deepEqual(ids[0], datastore.key('Kind', 123)); + ds.allocateIds(ds.key('Kind', null), 1, function(err, ids) { + assert.deepEqual(ids[0], ds.key('Kind', 123)); done(); }); }); @@ -135,7 +141,7 @@ describe('Dataset', function() { it('should throw if trying to allocate IDs with complete keys', function() { var ds = new datastore.Dataset({ projectId: 'test' }); assert.throws(function() { - ds.allocateIds(datastore.key('Kind', 123)); + ds.allocateIds(ds.key('Kind', 123)); }); }); diff --git a/test/datastore/entity.js b/test/datastore/entity.js index 708aaf30815..f3a789b3677 100644 --- a/test/datastore/entity.js +++ b/test/datastore/entity.js @@ -159,26 +159,32 @@ describe('keyFromKeyProto', function() { it('should handle keys hierarchically', function(done) { var key = entity.keyFromKeyProto(protoH); - assert.deepEqual(key, datastore.key('Test', 'Kind', 111, 'Kind2', 'name')); + assert.deepEqual(key, new entity.Key({ + namespace: 'Test', + path: [ 'Kind', 111, 'Kind2', 'name' ] + })); done(); }); it('should handle incomplete keys hierarchically', function(done) { var key = entity.keyFromKeyProto(protoHIncomplete); - assert.deepEqual(key, datastore.key('Test', 'Kind', null, 'Kind2', null)); + assert.deepEqual(key, new entity.Key({ + namespace: 'Test', + path: [ 'Kind', null, 'Kind2', null ] + })); done(); }); it('should not set namespace if default', function(done) { var key = entity.keyFromKeyProto(proto); - assert.deepEqual(key, datastore.key('Kind', 'Name')); + assert.deepEqual(key, new entity.Key({ path: [ 'Kind', 'Name' ] })); done(); }); }); describe('keyToKeyProto', function() { it('should handle hierarchical key definitions', function(done) { - var key = datastore.key('Kind1', 1, 'Kind2', 'name'); + var key = new entity.Key({ path: [ 'Kind1', 1, 'Kind2', 'name' ] }); var proto = entity.keyToKeyProto(key); assert.strictEqual(proto.partition_id, undefined); assert.strictEqual(proto.path_element[0].kind, 'Kind1'); @@ -191,7 +197,10 @@ describe('keyToKeyProto', function() { }); it('should detect the namespace of the hierarchical keys', function(done) { - var key = datastore.key('Namespace', 'Kind1', 1, 'Kind2', 'name'); + var key = new entity.Key({ + namespace: 'Namespace', + path: [ 'Kind1', 1, 'Kind2', 'name' ] + }); var proto = entity.keyToKeyProto(key); assert.strictEqual(proto.partition_id.namespace, 'Namespace'); assert.strictEqual(proto.path_element[0].kind, 'Kind1'); @@ -204,8 +213,11 @@ describe('keyToKeyProto', function() { }); it('should handle incomplete keys with & without namespaces', function(done) { - var key = datastore.key('Kind1', null); - var keyWithNS = datastore.key('Namespace', 'Kind1', null); + var key = new entity.Key({ path: [ 'Kind1', null ] }); + var keyWithNS = new entity.Key({ + namespace: 'Namespace', + path: [ 'Kind1', null ] + }); var proto = entity.keyToKeyProto(key); var protoWithNS = entity.keyToKeyProto(keyWithNS); @@ -232,10 +244,16 @@ describe('keyToKeyProto', function() { describe('isKeyComplete', function() { it('should ret true if kind and an identifier have !0 vals', function(done) { [ - { key: datastore.key('Kind1', null), expected: false }, - { key: datastore.key('Kind1', 3), expected: true }, - { key: datastore.key('Namespace', 'Kind1', null), expected: false }, - { key: datastore.key('Namespace', 'Kind1', 'name'), expected: true } + { key: new entity.Key({ path: [ 'Kind1', null ] }), expected: false }, + { key: new entity.Key({ path: [ 'Kind1', 3 ] }), expected: true }, + { key: new entity.Key({ + namespace: 'Namespace', + path: [ 'Kind1', null ] + }), expected: false }, + { key: new entity.Key({ + namespace: 'Namespace', + path: [ 'Kind1', 'name' ] + }), expected: true } ].forEach(function(test) { assert.strictEqual(entity.isKeyComplete(test.key), test.expected); }); @@ -247,7 +265,9 @@ describe('entityFromEntityProto', function() { it('should support boolean, integer, double, string, entity and list values', function(done) { var obj = entity.entityFromEntityProto(entityProto); - assert.deepEqual(obj.linkedTo, datastore.key('Kind', 'another')); + assert.deepEqual(obj.linkedTo, new entity.Key({ + path: [ 'Kind', 'another' ] + })); assert.strictEqual(obj.name, 'Some name'); assert.strictEqual(obj.flagged, false); assert.strictEqual(obj.count, 5); @@ -305,7 +325,7 @@ describe('queryToQueryProto', function() { var ds = new datastore.Dataset({ projectId: 'project-id' }); var q = ds.createQuery('Kind1') .filter('name =', 'John') - .hasAncestor(datastore.key('Kind2', 'somename')); + .hasAncestor(new entity.Key({ path: [ 'Kind2', 'somename' ] })); var proto = entity.queryToQueryProto(q); assert.deepEqual(proto, queryFilterProto); done(); diff --git a/test/datastore/query.js b/test/datastore/query.js index 7f2efa59507..2c4f620842f 100644 --- a/test/datastore/query.js +++ b/test/datastore/query.js @@ -25,6 +25,10 @@ var queryProto = require('../testdata/proto_query.json'); describe('Query', function() { var ds = new datastore.Dataset({ projectId: 'my-project-id' }); + var dsWithNs = new datastore.Dataset({ + projectId: 'my-project-id', + namespace: 'ns' + }); it('should use default namespace if none is specified', function(done) { var q = ds.createQuery(['kind1']); @@ -33,14 +37,14 @@ describe('Query', function() { }); it('should use support custom namespaces', function(done) { - var q = ds.createQuery('ns', ['kind1']); + var q = dsWithNs.createQuery(['kind1']); assert.equal(q.namespace, 'ns'); done(); }); it('should support querying multiple kinds', function(done) { var q = ds.createQuery(['kind1', 'kind2']); - var qNS = ds.createQuery('ns', ['kind1', 'kind2']); + var qNS = dsWithNs.createQuery(['kind1', 'kind2']); assert.strictEqual(q.namespace, ''); assert.equal(q.kinds[0], 'kind1'); From 8f356c97ee27e3d30fd97b3c644fc7833ed23441 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 21 Aug 2014 18:30:43 -0400 Subject: [PATCH 2/7] datastore: support overriding namespace. --- lib/common/util.js | 52 +++++++++++++++++++++++++++++++++++++++ lib/datastore/dataset.js | 28 +++++++++++++++------ lib/datastore/entity.js | 7 +----- test/datastore/dataset.js | 11 +++++++++ 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/lib/common/util.js b/lib/common/util.js index 287c085dbd6..4dd479bacde 100644 --- a/lib/common/util.js +++ b/lib/common/util.js @@ -163,3 +163,55 @@ function handleResp(err, resp, body, callback) { } module.exports.handleResp = handleResp; + +/** + * Get the type of a value. + * + * @private + * + * @return {string} + */ +function getType(value) { + return Object.prototype.toString.call(value).match(/\s(\w+)\]/)[1]; +} + +/** + * Check if an object is of the given type. + * + * @param {*} value - Value to compare to. + * @param {string} type - Type to check against object's value. + * @return {boolean} + * + * @example + * ```js + * is([1, 2, 3], 'array'); + * // true + * ``` + */ +function is(value, type) { + return getType(value).toLowerCase() === type.toLowerCase(); +} + +module.exports.is = is; + +/** + * Convert an object into an array. + * + * @param {object} object - Object to convert to an array. + * @return {array} + * + * @example + * ```js + * function aFunction() { + * return toArray(arguments); + * } + * + * aFunction(1, 2, 3); + * // [1, 2, 3] + * ``` + */ +function toArray(object) { + return [].slice.call(object); +} + +module.exports.toArray = toArray; diff --git a/lib/datastore/dataset.js b/lib/datastore/dataset.js index b5a514181ed..bf1f12e5c3f 100644 --- a/lib/datastore/dataset.js +++ b/lib/datastore/dataset.js @@ -82,18 +82,32 @@ function Dataset(options) { } /** - * Helper to create a Key object, scoped to the namespace if present. + * Helper to create a Key object, scoped to the dataset's namespace by default. + * + * You may also specify a configuration object to define a namespace and path. * * @example * ```js - * var key = dataset.key('Company', 123); + * var key; + * + * // Create a key from the dataset's namespace. + * key = dataset.key('Company', 123); + * + * // Create a key from a provided namespace and path. + * key = dataset.key({ + * namespace: 'My-NS', + * path: ['Company', 123] + * }); * ``` */ -Dataset.prototype.key = function() { - return new entity.Key({ - namespace: this.ns, - path: [].slice.call(arguments) - }); +Dataset.prototype.key = function(keyConfig) { + if (!util.is(keyConfig, 'object')) { + keyConfig = { + namespace: this.ns, + path: util.toArray(arguments) + }; + } + return new entity.Key(keyConfig); }; diff --git a/lib/datastore/entity.js b/lib/datastore/entity.js index 3a645e05bd0..adffa39fb5b 100644 --- a/lib/datastore/entity.js +++ b/lib/datastore/entity.js @@ -100,12 +100,7 @@ var SIGN_TO_ORDER = { */ function Key(options) { this.namespace_ = options.namespace; - - if (options.path.length > 1) { - this.path_ = [].slice.call(options.path); - } else { - this.path_ = options.path[0]; - } + this.path_ = options.path; } module.exports.Key = Key; diff --git a/test/datastore/dataset.js b/test/datastore/dataset.js index b6071aeac89..0659cdcc570 100644 --- a/test/datastore/dataset.js +++ b/test/datastore/dataset.js @@ -29,6 +29,17 @@ describe('Dataset', function() { var ds = new datastore.Dataset({ projectId: 'test', namespace: 'my-ns' }); var key = ds.key('Company', 1); assert.equal(key.namespace_, 'my-ns'); + assert.deepEqual(key.path_, ['Company', 1]); + }); + + it('should allow namespace specification when creating a key', function() { + var ds = new datastore.Dataset({ projectId: 'test' }); + var key = ds.key({ + namespace: 'custom-ns', + path: ['Company', 1] + }); + assert.equal(key.namespace_, 'custom-ns'); + assert.deepEqual(key.path_, ['Company', 1]) }); it('should get by key', function(done) { From 1e3b795a8b79294c2a1961562c990c72dfa9cb68 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 21 Aug 2014 19:02:28 -0400 Subject: [PATCH 3/7] datastore: support namespace overrides & removal. --- lib/datastore/dataset.js | 26 ++++++++++++++++++++---- lib/datastore/query.js | 4 ++++ lib/datastore/transaction.js | 2 +- test/datastore/dataset.js | 38 +++++++++++++++++++++++++++++++++++- 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/lib/datastore/dataset.js b/lib/datastore/dataset.js index bf1f12e5c3f..daed8ae1133 100644 --- a/lib/datastore/dataset.js +++ b/lib/datastore/dataset.js @@ -112,18 +112,36 @@ Dataset.prototype.key = function(keyConfig) { /** - * Create a query from the current dataset to query the specified kinds. + * Create a query from the current dataset to query the specified kinds, scoped + * to the namespace provided at the initialization of the dataset. * * @borrows {module:datastore/query} as createQuery * + * @param {string=} ns - Optional namespace. + * @param {string|array} kinds - Kinds to query. + * * @example * ```js - * var query = dataset.createQuery(['Lion', 'Chimp']); + * var query; + * + * // If your dataset was scoped to a namespace at initialization, your query + * // will likewise be scoped to that namespace. + * query = dataset.createQuery(['Lion', 'Chimp']); + * + * // However, you may override the namespace per query. + * query = dataset.createQuery('AnimalNamespace', ['Lion', 'Chimp']); + * + * // You may also remove the namespace altogether. + * query = dataset.createQuery(null, ['Lion', 'Chimp']); * ``` * @return {module:datastore/query} */ -Dataset.prototype.createQuery = function(kinds) { - return new Query(this.ns, util.arrayize(kinds)); +Dataset.prototype.createQuery = function(ns, kinds) { + if (arguments.length === 1) { + kinds = util.arrayize(ns); + ns = this.ns; + } + return new Query(ns, util.arrayize(kinds)); }; /** diff --git a/lib/datastore/query.js b/lib/datastore/query.js index 913bc979c72..a51ec190bcd 100644 --- a/lib/datastore/query.js +++ b/lib/datastore/query.js @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * @module datastore/query + */ + 'use strict'; var util = require('../common/util.js'); diff --git a/lib/datastore/transaction.js b/lib/datastore/transaction.js index 7876d7be6a2..e88a2c1c4db 100644 --- a/lib/datastore/transaction.js +++ b/lib/datastore/transaction.js @@ -357,7 +357,7 @@ Transaction.prototype.delete = function(keys, callback) { * supported. If more results are available, a query to retrieve the next page * is provided to the callback function. * - * @param {Query} query - Query object. + * @param {module:datastore/query} query - Query object. * @param {function} callback - The callback function. * * @example diff --git a/test/datastore/dataset.js b/test/datastore/dataset.js index 0659cdcc570..c91808be075 100644 --- a/test/datastore/dataset.js +++ b/test/datastore/dataset.js @@ -39,7 +39,7 @@ describe('Dataset', function() { path: ['Company', 1] }); assert.equal(key.namespace_, 'custom-ns'); - assert.deepEqual(key.path_, ['Company', 1]) + assert.deepEqual(key.path_, ['Company', 1]); }); it('should get by key', function(done) { @@ -192,6 +192,42 @@ describe('Dataset', function() { }); }); + describe('createQuery', function() { + var ds; + var dsWithNs; + + beforeEach(function() { + ds = new datastore.Dataset({ projectId: 'test' }); + dsWithNs = new datastore.Dataset({ + projectId: 'test', + namespace: 'my-ns' + }); + }); + + it('should not include a namespace on a ns-less dataset', function() { + var query = ds.createQuery('Kind'); + assert.equal(query.namespace, false); + }); + + it('should scope query to namespace', function() { + var query = dsWithNs.createQuery('Kind'); + assert.equal(query.namespace, 'my-ns'); + }); + + it('should allow control over namespace and kinds', function() { + var queryFromDs = ds.createQuery('my-ns', 'Kind'); + assert.equal(queryFromDs.namespace, 'my-ns'); + + var queryFromDsWithNs = dsWithNs.createQuery('Kind'); + assert.equal(queryFromDsWithNs.namespace, 'my-ns'); + }); + + it('should allow removal of namespace', function() { + var query = dsWithNs.createQuery(null, 'Kind'); + assert.equal(query.namespace, false); + }); + }); + describe('runQuery', function() { var ds; var query; From f7d7020c582ac32a095377aed2959ceb401c563e Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 21 Aug 2014 19:32:26 -0400 Subject: [PATCH 4/7] datastore: tighten up falsy namespace values. --- lib/datastore/query.js | 2 +- test/datastore/dataset.js | 6 +++--- test/datastore/query.js | 15 +++++++++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/datastore/query.js b/lib/datastore/query.js index a51ec190bcd..c16481fdc34 100644 --- a/lib/datastore/query.js +++ b/lib/datastore/query.js @@ -42,7 +42,7 @@ var util = require('../common/util.js'); * ``` */ function Query(namespace, kinds) { - this.namespace = namespace || ''; + this.namespace = namespace || undefined; this.kinds = kinds; this.filters = []; diff --git a/test/datastore/dataset.js b/test/datastore/dataset.js index c91808be075..5626843275e 100644 --- a/test/datastore/dataset.js +++ b/test/datastore/dataset.js @@ -206,7 +206,7 @@ describe('Dataset', function() { it('should not include a namespace on a ns-less dataset', function() { var query = ds.createQuery('Kind'); - assert.equal(query.namespace, false); + assert.equal(query.namespace, undefined); }); it('should scope query to namespace', function() { @@ -223,8 +223,8 @@ describe('Dataset', function() { }); it('should allow removal of namespace', function() { - var query = dsWithNs.createQuery(null, 'Kind'); - assert.equal(query.namespace, false); + var query = dsWithNs.createQuery(undefined, 'Kind'); + assert.strictEqual(query.namespace, undefined); }); }); diff --git a/test/datastore/query.js b/test/datastore/query.js index 2c4f620842f..ab6fe044566 100644 --- a/test/datastore/query.js +++ b/test/datastore/query.js @@ -30,9 +30,20 @@ describe('Query', function() { namespace: 'ns' }); + it('should use undefined for all falsy namespace values', function() { + [ + ds.createQuery('', 'Kind'), + ds.createQuery(null, 'Kind'), + ds.createQuery(undefined, 'Kind'), + ds.createQuery(0, 'Kind') + ].forEach(function(query) { + assert.strictEqual(query.namespace, undefined); + }); + }); + it('should use default namespace if none is specified', function(done) { var q = ds.createQuery(['kind1']); - assert.equal(q.namespace, ''); + assert.strictEqual(q.namespace, undefined); done(); }); @@ -46,7 +57,7 @@ describe('Query', function() { var q = ds.createQuery(['kind1', 'kind2']); var qNS = dsWithNs.createQuery(['kind1', 'kind2']); - assert.strictEqual(q.namespace, ''); + assert.strictEqual(q.namespace, undefined); assert.equal(q.kinds[0], 'kind1'); assert.equal(q.kinds[1], 'kind2'); From dc6dd17464a1bce147c7b36fc22c7198b31b9cb8 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 21 Aug 2014 19:36:28 -0400 Subject: [PATCH 5/7] datastore: prefer `namespace` over `ns` naming convention. --- lib/datastore/dataset.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/datastore/dataset.js b/lib/datastore/dataset.js index daed8ae1133..926fd95ed0f 100644 --- a/lib/datastore/dataset.js +++ b/lib/datastore/dataset.js @@ -77,7 +77,7 @@ function Dataset(options) { scopes: SCOPES }); this.id = options.projectId; - this.ns = options.namespace; + this.namespace = options.namespace; this.transaction = this.createTransaction_(); } @@ -103,7 +103,7 @@ function Dataset(options) { Dataset.prototype.key = function(keyConfig) { if (!util.is(keyConfig, 'object')) { keyConfig = { - namespace: this.ns, + namespace: this.namespace, path: util.toArray(arguments) }; } @@ -117,7 +117,7 @@ Dataset.prototype.key = function(keyConfig) { * * @borrows {module:datastore/query} as createQuery * - * @param {string=} ns - Optional namespace. + * @param {string=} namespace - Optional namespace. * @param {string|array} kinds - Kinds to query. * * @example @@ -136,12 +136,12 @@ Dataset.prototype.key = function(keyConfig) { * ``` * @return {module:datastore/query} */ -Dataset.prototype.createQuery = function(ns, kinds) { +Dataset.prototype.createQuery = function(namespace, kinds) { if (arguments.length === 1) { - kinds = util.arrayize(ns); - ns = this.ns; + kinds = util.arrayize(namespace); + namespace = this.namespace; } - return new Query(ns, util.arrayize(kinds)); + return new Query(namespace, util.arrayize(kinds)); }; /** From b1f916f3968b3b2ebd670196f3dc1f061e5a7550 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 21 Aug 2014 19:37:48 -0400 Subject: [PATCH 6/7] datastore: readme: mention default namespace scoping. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 954a8a9ec36..7764795dfef 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,9 @@ var q = ds.createQuery('Company') #### Allocating IDs (ID generation) You can generate IDs without creating entities. The following call will create -100 new IDs from the Company kind which exists under the dataset's namespace. +100 new IDs from the Company kind which exists under the dataset's namespace. If +no namespace was provided when the dataset was created, the default namespace +will be used. ~~~~ js ds.allocateIds(ds.key('Company', null), 100, function(err, keys) { From ea60e50b568a7fc205c683400ab11dcb0e401afe Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 21 Aug 2014 21:39:12 -0400 Subject: [PATCH 7/7] datastore: undefined -> null. --- lib/datastore/query.js | 2 +- test/datastore/dataset.js | 4 ++-- test/datastore/query.js | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/datastore/query.js b/lib/datastore/query.js index c16481fdc34..4845e753ae0 100644 --- a/lib/datastore/query.js +++ b/lib/datastore/query.js @@ -42,7 +42,7 @@ var util = require('../common/util.js'); * ``` */ function Query(namespace, kinds) { - this.namespace = namespace || undefined; + this.namespace = namespace || null; this.kinds = kinds; this.filters = []; diff --git a/test/datastore/dataset.js b/test/datastore/dataset.js index 5626843275e..3af056d9a05 100644 --- a/test/datastore/dataset.js +++ b/test/datastore/dataset.js @@ -223,8 +223,8 @@ describe('Dataset', function() { }); it('should allow removal of namespace', function() { - var query = dsWithNs.createQuery(undefined, 'Kind'); - assert.strictEqual(query.namespace, undefined); + var query = dsWithNs.createQuery(null, 'Kind'); + assert.strictEqual(query.namespace, null); }); }); diff --git a/test/datastore/query.js b/test/datastore/query.js index ab6fe044566..ffbdb490f0d 100644 --- a/test/datastore/query.js +++ b/test/datastore/query.js @@ -37,13 +37,13 @@ describe('Query', function() { ds.createQuery(undefined, 'Kind'), ds.createQuery(0, 'Kind') ].forEach(function(query) { - assert.strictEqual(query.namespace, undefined); + assert.strictEqual(query.namespace, null); }); }); it('should use default namespace if none is specified', function(done) { var q = ds.createQuery(['kind1']); - assert.strictEqual(q.namespace, undefined); + assert.strictEqual(q.namespace, null); done(); }); @@ -57,7 +57,7 @@ describe('Query', function() { var q = ds.createQuery(['kind1', 'kind2']); var qNS = dsWithNs.createQuery(['kind1', 'kind2']); - assert.strictEqual(q.namespace, undefined); + assert.strictEqual(q.namespace, null); assert.equal(q.kinds[0], 'kind1'); assert.equal(q.kinds[1], 'kind2');