Skip to content

Commit bab982f

Browse files
datastore: support long entity IDs (#1413)
1 parent 55524d3 commit bab982f

5 files changed

Lines changed: 92 additions & 40 deletions

File tree

packages/datastore/src/entity.js

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ var InvalidKeyError = createErrorClass('InvalidKey', function(opts) {
4545
entity.KEY_SYMBOL = Symbol('KEY');
4646

4747
/**
48-
* Build a Datastore Double object.
48+
* Build a Datastore Double object. For long doubles, a string can be provided.
4949
*
5050
* @constructor
5151
* @param {number} value - The double value.
@@ -60,16 +60,16 @@ function Double(value) {
6060
entity.Double = Double;
6161

6262
/**
63-
* Build a Datastore Int object.
63+
* Build a Datastore Int object. For long integers, a string can be provided.
6464
*
6565
* @constructor
66-
* @param {number} value - The integer value.
66+
* @param {number|string} value - The integer value.
6767
*
6868
* @example
6969
* var anInt = new Int(7);
7070
*/
7171
function Int(value) {
72-
this.value = value;
72+
this.value = value.toString();
7373
}
7474

7575
entity.Int = Int;
@@ -116,8 +116,8 @@ function Key(options) {
116116
if (options.path.length % 2 === 0) {
117117
var identifier = options.path.pop();
118118

119-
if (is.number(identifier)) {
120-
this.id = identifier;
119+
if (is.number(identifier) || identifier instanceof entity.Int) {
120+
this.id = identifier.value || identifier;
121121
} else if (is.string(identifier)) {
122122
this.name = identifier;
123123
}
@@ -467,11 +467,15 @@ function keyFromKeyProto(keyProto) {
467467
}
468468

469469
keyProto.path.forEach(function(path, index) {
470-
var id = path.name || Number(path.id);
471-
472470
keyOptions.path.push(path.kind);
473471

474-
if (id) {
472+
var id = path[path.id_type];
473+
474+
if (path.id_type === 'id') {
475+
id = new entity.Int(id);
476+
}
477+
478+
if (is.defined(id)) {
475479
keyOptions.path.push(id);
476480
} else if (index < keyProto.path.length - 1) {
477481
throw new InvalidKeyError({
@@ -503,7 +507,7 @@ entity.keyFromKeyProto = keyFromKeyProto;
503507
* // }
504508
*/
505509
function keyToKeyProto(key) {
506-
if (!is.string(key.path[0])) {
510+
if (is.undefined(key.kind)) {
507511
throw new InvalidKeyError({
508512
code: 'MISSING_KIND'
509513
});
@@ -519,28 +523,31 @@ function keyToKeyProto(key) {
519523
};
520524
}
521525

522-
for (var i = 0; i < key.path.length; i += 2) {
523-
var pathElement = {
524-
kind: key.path[i]
525-
};
526+
var numKeysWalked = 0;
526527

527-
var value = key.path[i + 1];
528-
529-
if (value) {
530-
if (is.number(value)) {
531-
pathElement.id = value;
532-
} else {
533-
pathElement.name = value;
534-
}
535-
} else if (i < key.path.length - 2) {
528+
// Reverse-iterate over the Key objects.
529+
do {
530+
if (numKeysWalked > 0 && is.undefined(key.id) && is.undefined(key.name)) {
536531
// This isn't just an incomplete key. An ancestor key is incomplete.
537532
throw new InvalidKeyError({
538533
code: 'MISSING_ANCESTOR_ID'
539534
});
540535
}
541536

542-
keyProto.path.push(pathElement);
543-
}
537+
var pathElement = {
538+
kind: key.kind
539+
};
540+
541+
if (is.defined(key.id)) {
542+
pathElement.id = key.id;
543+
}
544+
545+
if (is.defined(key.name)) {
546+
pathElement.name = key.name;
547+
}
548+
549+
keyProto.path.unshift(pathElement);
550+
} while ((key = key.parent) && ++numKeysWalked);
544551

545552
return keyProto;
546553
}

packages/datastore/src/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,22 @@ Datastore.prototype.geoPoint = Datastore.geoPoint = function(coordindates) {
360360
/**
361361
* Helper function to get a Datastore Integer object.
362362
*
363+
* This is also useful when using an ID outside the bounds of a JavaScript
364+
* Number object.
365+
*
363366
* @param {number} value - The integer value.
364367
* @return {object}
365368
*
366369
* @example
367370
* var sevenInteger = datastore.int(7);
371+
*
372+
* //-
373+
* // Create an Int to support long Key IDs.
374+
* //-
375+
* var key = datastore.key([
376+
* 'Kind',
377+
* datastore.int('100000000000001234')
378+
* ]);
368379
*/
369380
Datastore.prototype.int = Datastore.int = function(value) {
370381
return new entity.Int(value);
@@ -461,6 +472,15 @@ Datastore.prototype.createQuery = function(namespace, kind) {
461472
* var key = datastore.key(['Company', 123]);
462473
*
463474
* //-
475+
* // If the ID integer is outside the bounds of a JavaScript Number object,
476+
* // create an Int.
477+
* //-
478+
* var key = datastore.key([
479+
* 'Company',
480+
* datastore.int('100000000000001234')
481+
* ]);
482+
*
483+
* //-
464484
* // Create a complete key with a kind value of `Company` and name `Google`.
465485
* // Note: `id` is used for numeric identifiers and `name` is used otherwise.
466486
* //-

packages/datastore/system-test/datastore.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,28 @@ describe('Datastore', function() {
181181
});
182182
});
183183

184+
it('should save and get with a string ID', function(done) {
185+
var longIdKey = datastore.key([
186+
'Post',
187+
datastore.int('100000000000001234')
188+
]);
189+
190+
datastore.save({
191+
key: longIdKey,
192+
data: {
193+
test: true
194+
}
195+
}, function(err) {
196+
assert.ifError(err);
197+
198+
datastore.get(longIdKey, function(err, entity) {
199+
assert.ifError(err);
200+
assert.strictEqual(entity.test, true);
201+
done();
202+
});
203+
});
204+
});
205+
184206
it('should fail explicitly set second insert on save', function(done) {
185207
var postKey = datastore.key('Post');
186208

packages/datastore/test/entity.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,12 @@ describe('entity', function() {
4848
});
4949
});
5050

51-
describe('Double', function() {
52-
it('should store the value', function() {
53-
var value = 8.3;
54-
55-
var double = new entity.Double(value);
56-
assert.strictEqual(double.value, value);
57-
});
58-
});
59-
6051
describe('Int', function() {
61-
it('should store the value', function() {
52+
it('should store the stringified value', function() {
6253
var value = 8;
6354

6455
var int = new entity.Int(value);
65-
assert.strictEqual(int.value, value);
56+
assert.strictEqual(int.value, value.toString());
6657
});
6758
});
6859

@@ -97,6 +88,12 @@ describe('entity', function() {
9788
assert.strictEqual(key.id, id);
9889
});
9990

91+
it('should assign the ID from an Int', function() {
92+
var id = new entity.Int(11);
93+
var key = new entity.Key({ path: ['Kind', id] });
94+
assert.strictEqual(key.id, id.value);
95+
});
96+
10097
it('should assign the name', function() {
10198
var name = 'name';
10299
var key = new entity.Key({ path: ['Kind', name] });
@@ -607,10 +604,12 @@ describe('entity', function() {
607604
},
608605
path: [
609606
{
607+
id_type: 'id',
610608
kind: 'Kind',
611609
id: '111'
612610
},
613611
{
612+
id_type: 'name',
614613
kind: 'Kind2',
615614
name: 'name'
616615
}
@@ -632,7 +631,7 @@ describe('entity', function() {
632631
namespace: NAMESPACE,
633632
path: [
634633
'Kind',
635-
111,
634+
new entity.Int(111),
636635
'Kind2',
637636
'name'
638637
]
@@ -683,7 +682,7 @@ describe('entity', function() {
683682
describe('keyToKeyProto', function() {
684683
it('should handle hierarchical key definitions', function() {
685684
var key = new entity.Key({
686-
path: ['Kind1', 1, 'Kind2', 'name']
685+
path: ['Kind1', 1, 'Kind2', 'name', 'Kind3', new entity.Int(3)]
687686
});
688687

689688
var keyProto = entity.keyToKeyProto(key);
@@ -697,6 +696,10 @@ describe('entity', function() {
697696
assert.strictEqual(keyProto.path[1].kind, 'Kind2');
698697
assert.strictEqual(keyProto.path[1].id, undefined);
699698
assert.strictEqual(keyProto.path[1].name, 'name');
699+
700+
assert.strictEqual(keyProto.path[2].kind, 'Kind3');
701+
assert.strictEqual(keyProto.path[2].id, new entity.Int(3).value);
702+
assert.strictEqual(keyProto.path[2].name, undefined);
700703
});
701704

702705
it('should detect the namespace of the hierarchical keys', function() {

packages/datastore/test/request.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ describe('Request', function() {
152152
var incompleteKey;
153153
var apiResponse = {
154154
keys: [
155-
{ path: [{ kind: 'Kind', id: 123 }] }
155+
{ path: [{ id_type: 'id', kind: 'Kind', id: 123 }] }
156156
]
157157
};
158158

@@ -173,7 +173,7 @@ describe('Request', function() {
173173
request.allocateIds(incompleteKey, 1, function(err, keys) {
174174
assert.ifError(err);
175175
var generatedKey = keys[0];
176-
assert.strictEqual(generatedKey.path.pop(), 123);
176+
assert.strictEqual(generatedKey.path.pop(), '123');
177177
done();
178178
});
179179
});

0 commit comments

Comments
 (0)