diff --git a/README.md b/README.md index 850cbe663603..954a8a9ec36c 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 4a8027df1e97..65a255049f2b 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. * diff --git a/lib/datastore/entity.js b/lib/datastore/entity.js index 87804994ed6d..3a645e05bd0a 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 08b7ebea6e79..d69c9e3d6bd4 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/regression/datastore.js b/regression/datastore.js index 845a12a43277..9e36c65f8ad7 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 4f78500e15b2..b6071aeac894 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 708aaf308151..f3a789b36777 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();