diff --git a/packages/datastore/src/entity.js b/packages/datastore/src/entity.js index ee9ae89f150..51676e67813 100644 --- a/packages/datastore/src/entity.js +++ b/packages/datastore/src/entity.js @@ -45,7 +45,7 @@ var InvalidKeyError = createErrorClass('InvalidKey', function(opts) { entity.KEY_SYMBOL = Symbol('KEY'); /** - * Build a Datastore Double object. + * Build a Datastore Double object. For long doubles, a string can be provided. * * @constructor * @param {number} value - The double value. @@ -60,16 +60,16 @@ function Double(value) { entity.Double = Double; /** - * Build a Datastore Int object. + * Build a Datastore Int object. For long integers, a string can be provided. * * @constructor - * @param {number} value - The integer value. + * @param {number|string} value - The integer value. * * @example * var anInt = new Int(7); */ function Int(value) { - this.value = value; + this.value = value.toString(); } entity.Int = Int; @@ -116,8 +116,8 @@ function Key(options) { if (options.path.length % 2 === 0) { var identifier = options.path.pop(); - if (is.number(identifier)) { - this.id = identifier; + if (is.number(identifier) || identifier instanceof entity.Int) { + this.id = identifier.value || identifier; } else if (is.string(identifier)) { this.name = identifier; } @@ -467,11 +467,15 @@ function keyFromKeyProto(keyProto) { } keyProto.path.forEach(function(path, index) { - var id = path.name || Number(path.id); - keyOptions.path.push(path.kind); - if (id) { + var id = path[path.id_type]; + + if (path.id_type === 'id') { + id = new entity.Int(id); + } + + if (is.defined(id)) { keyOptions.path.push(id); } else if (index < keyProto.path.length - 1) { throw new InvalidKeyError({ @@ -503,7 +507,7 @@ entity.keyFromKeyProto = keyFromKeyProto; * // } */ function keyToKeyProto(key) { - if (!is.string(key.path[0])) { + if (is.undefined(key.kind)) { throw new InvalidKeyError({ code: 'MISSING_KIND' }); @@ -519,28 +523,31 @@ function keyToKeyProto(key) { }; } - for (var i = 0; i < key.path.length; i += 2) { - var pathElement = { - kind: key.path[i] - }; + var numKeysWalked = 0; - var value = key.path[i + 1]; - - if (value) { - if (is.number(value)) { - pathElement.id = value; - } else { - pathElement.name = value; - } - } else if (i < key.path.length - 2) { + // Reverse-iterate over the Key objects. + do { + if (numKeysWalked > 0 && is.undefined(key.id) && is.undefined(key.name)) { // This isn't just an incomplete key. An ancestor key is incomplete. throw new InvalidKeyError({ code: 'MISSING_ANCESTOR_ID' }); } - keyProto.path.push(pathElement); - } + var pathElement = { + kind: key.kind + }; + + if (is.defined(key.id)) { + pathElement.id = key.id; + } + + if (is.defined(key.name)) { + pathElement.name = key.name; + } + + keyProto.path.unshift(pathElement); + } while ((key = key.parent) && ++numKeysWalked); return keyProto; } diff --git a/packages/datastore/src/index.js b/packages/datastore/src/index.js index 700cb686472..fdf2d943ec0 100644 --- a/packages/datastore/src/index.js +++ b/packages/datastore/src/index.js @@ -360,11 +360,22 @@ Datastore.prototype.geoPoint = Datastore.geoPoint = function(coordindates) { /** * Helper function to get a Datastore Integer object. * + * This is also useful when using an ID outside the bounds of a JavaScript + * Number object. + * * @param {number} value - The integer value. * @return {object} * * @example * var sevenInteger = datastore.int(7); + * + * //- + * // Create an Int to support long Key IDs. + * //- + * var key = datastore.key([ + * 'Kind', + * datastore.int('100000000000001234') + * ]); */ Datastore.prototype.int = Datastore.int = function(value) { return new entity.Int(value); @@ -461,6 +472,15 @@ Datastore.prototype.createQuery = function(namespace, kind) { * var key = datastore.key(['Company', 123]); * * //- + * // If the ID integer is outside the bounds of a JavaScript Number object, + * // create an Int. + * //- + * var key = datastore.key([ + * 'Company', + * datastore.int('100000000000001234') + * ]); + * + * //- * // Create a complete key with a kind value of `Company` and name `Google`. * // Note: `id` is used for numeric identifiers and `name` is used otherwise. * //- diff --git a/packages/datastore/system-test/datastore.js b/packages/datastore/system-test/datastore.js index 85e520e0786..533f6317cc3 100644 --- a/packages/datastore/system-test/datastore.js +++ b/packages/datastore/system-test/datastore.js @@ -181,6 +181,28 @@ describe('Datastore', function() { }); }); + it('should save and get with a string ID', function(done) { + var longIdKey = datastore.key([ + 'Post', + datastore.int('100000000000001234') + ]); + + datastore.save({ + key: longIdKey, + data: { + test: true + } + }, function(err) { + assert.ifError(err); + + datastore.get(longIdKey, function(err, entity) { + assert.ifError(err); + assert.strictEqual(entity.test, true); + done(); + }); + }); + }); + it('should fail explicitly set second insert on save', function(done) { var postKey = datastore.key('Post'); diff --git a/packages/datastore/test/entity.js b/packages/datastore/test/entity.js index a97ace5433f..3c50d19bf1a 100644 --- a/packages/datastore/test/entity.js +++ b/packages/datastore/test/entity.js @@ -48,21 +48,12 @@ describe('entity', function() { }); }); - describe('Double', function() { - it('should store the value', function() { - var value = 8.3; - - var double = new entity.Double(value); - assert.strictEqual(double.value, value); - }); - }); - describe('Int', function() { - it('should store the value', function() { + it('should store the stringified value', function() { var value = 8; var int = new entity.Int(value); - assert.strictEqual(int.value, value); + assert.strictEqual(int.value, value.toString()); }); }); @@ -97,6 +88,12 @@ describe('entity', function() { assert.strictEqual(key.id, id); }); + it('should assign the ID from an Int', function() { + var id = new entity.Int(11); + var key = new entity.Key({ path: ['Kind', id] }); + assert.strictEqual(key.id, id.value); + }); + it('should assign the name', function() { var name = 'name'; var key = new entity.Key({ path: ['Kind', name] }); @@ -607,10 +604,12 @@ describe('entity', function() { }, path: [ { + id_type: 'id', kind: 'Kind', id: '111' }, { + id_type: 'name', kind: 'Kind2', name: 'name' } @@ -632,7 +631,7 @@ describe('entity', function() { namespace: NAMESPACE, path: [ 'Kind', - 111, + new entity.Int(111), 'Kind2', 'name' ] @@ -683,7 +682,7 @@ describe('entity', function() { describe('keyToKeyProto', function() { it('should handle hierarchical key definitions', function() { var key = new entity.Key({ - path: ['Kind1', 1, 'Kind2', 'name'] + path: ['Kind1', 1, 'Kind2', 'name', 'Kind3', new entity.Int(3)] }); var keyProto = entity.keyToKeyProto(key); @@ -697,6 +696,10 @@ describe('entity', function() { assert.strictEqual(keyProto.path[1].kind, 'Kind2'); assert.strictEqual(keyProto.path[1].id, undefined); assert.strictEqual(keyProto.path[1].name, 'name'); + + assert.strictEqual(keyProto.path[2].kind, 'Kind3'); + assert.strictEqual(keyProto.path[2].id, new entity.Int(3).value); + assert.strictEqual(keyProto.path[2].name, undefined); }); it('should detect the namespace of the hierarchical keys', function() { diff --git a/packages/datastore/test/request.js b/packages/datastore/test/request.js index ed891e86ad4..0e9aa38398b 100644 --- a/packages/datastore/test/request.js +++ b/packages/datastore/test/request.js @@ -152,7 +152,7 @@ describe('Request', function() { var incompleteKey; var apiResponse = { keys: [ - { path: [{ kind: 'Kind', id: 123 }] } + { path: [{ id_type: 'id', kind: 'Kind', id: 123 }] } ] }; @@ -173,7 +173,7 @@ describe('Request', function() { request.allocateIds(incompleteKey, 1, function(err, keys) { assert.ifError(err); var generatedKey = keys[0]; - assert.strictEqual(generatedKey.path.pop(), 123); + assert.strictEqual(generatedKey.path.pop(), '123'); done(); }); });