Skip to content

Commit 46ac928

Browse files
datastore: support streaming gets
1 parent b18e5e2 commit 46ac928

File tree

3 files changed

+271
-80
lines changed

3 files changed

+271
-80
lines changed

lib/datastore/request.js

+73-29
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var request = require('request').defaults({
2525
maxSockets: Infinity
2626
}
2727
});
28+
var through = require('through2');
2829

2930
/**
3031
* @type {module:datastore/entity}
@@ -92,11 +93,13 @@ function DatastoreRequest() {}
9293
* transaction. Get operations require a valid key to retrieve the
9394
* key-identified entity from Datastore.
9495
*
96+
* @throws {Error} If at least one Key object is not provided.
97+
*
9598
* @param {Key|Key[]} keys - Datastore key object(s).
9699
* @param {function} callback - The callback function.
97100
* @param {?error} callback.err - An error returned while making this request
98101
* @param {module:datastore/entity|module:datastore/entity[]} callback.entity -
99-
* Will return either a single Entity or a list of Entities
102+
* Will return either a single Entity or a list of Entities.
100103
* @param {object} callback.apiResponse - The full API response.
101104
*
102105
* @example
@@ -105,57 +108,98 @@ function DatastoreRequest() {}
105108
* // your use, whether that be a Dataset or Transaction object.
106109
* //-
107110
*
111+
* //-
108112
* // Get a single entity.
113+
* //-
109114
* var key = dataset.key(['Company', 123]);
115+
*
110116
* transaction.get(key, function(err, entity, apiResponse) {});
111117
*
112-
* // Get multiple entities at once.
113-
* transaction.get([
118+
* //-
119+
* // Get multiple entities at once with a callback.
120+
* //-
121+
* var keys = [
114122
* dataset.key(['Company', 123]),
115123
* dataset.key(['Product', 'Computer'])
116-
* ], function(err, entities, apiResponse) {});
124+
* ];
125+
*
126+
* transaction.get(keys, function(err, entities, apiResponse) {});
127+
*
128+
* //-
129+
* // Or, get the entities as a readable object stream.
130+
* //-
131+
* transaction.get(keys)
132+
* .on('error', function(err, apiResponse) {})
133+
* .on('data', function(entity) {
134+
* // entity is an entity object.
135+
* })
136+
* .on('end', function() {
137+
* // All entities retrieved.
138+
* });
117139
*/
118140
DatastoreRequest.prototype.get = function(keys, callback) {
119-
var that = this;
141+
var self = this;
120142

121-
var isMultipleRequest = Array.isArray(keys);
122-
keys = isMultipleRequest ? keys : [keys];
143+
var isStreamMode = !callback;
144+
var stream;
123145

124-
callback = callback || util.noop;
146+
if (isStreamMode) {
147+
stream = through.obj();
148+
}
125149

126-
var req = {
127-
key: keys.map(entity.keyToKeyProto)
150+
var isSingleLookup = !util.is(keys, 'array');
151+
keys = util.arrayize(keys).map(entity.keyToKeyProto);
152+
153+
if (keys.length === 0) {
154+
throw new Error('At least one Key object is required.');
155+
}
156+
157+
var request = {
158+
key: keys
128159
};
129160

130-
this.makeReq_('lookup', req, function(err, resp) {
161+
var entities = [];
162+
this.makeReq_('lookup', request, onApiResponse);
163+
164+
function onApiResponse(err, resp) {
131165
if (err) {
132-
callback(err, null, resp);
166+
if (isStreamMode) {
167+
stream.emit('error', err, resp);
168+
stream.end();
169+
} else {
170+
callback(err, null, resp);
171+
}
133172
return;
134173
}
135174

136-
var found = entity.formatArray(resp.found);
137-
138-
if (isMultipleRequest && resp.deferred && resp.deferred.length) {
139-
// There may be more results. Call `.get` again, and append the results.
140-
that.get(
141-
resp.deferred.map(entity.keyFromKeyProto), function(err, entities) {
142-
if (err) {
143-
callback(err, null, resp);
144-
return;
145-
}
146-
147-
if (resp) {
148-
found = (found || []).concat(entities);
149-
}
175+
var results = entity.formatArray(resp.found);
150176

151-
callback(null, found, resp);
177+
if (isStreamMode) {
178+
results.forEach(function(entity) {
179+
stream.push(entity);
152180
});
181+
} else {
182+
entities = entities.concat(results);
183+
}
184+
185+
var nextKeys = (resp.deferred || []).map(entity.keyFromKeyProto);
153186

187+
if (nextKeys.length > 0) {
188+
self.get(nextKeys, onApiResponse);
154189
return;
155190
}
156191

157-
callback(null, isMultipleRequest ? found : found[0], resp);
158-
});
192+
if (isStreamMode) {
193+
stream.push(null);
194+
stream.end();
195+
} else {
196+
callback(null, isSingleLookup ? entities[0] : entities, resp);
197+
}
198+
}
199+
200+
if (isStreamMode) {
201+
return stream;
202+
}
159203
};
160204

161205
/**

system-test/datastore.js

+27
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,33 @@ describe('datastore', function() {
183183
});
184184
});
185185

186+
it('should get multiple entities in a stream', function(done) {
187+
var key1 = ds.key('Post');
188+
var key2 = ds.key('Post');
189+
190+
ds.save([
191+
{ key: key1, data: post },
192+
{ key: key2, data: post }
193+
], function(err) {
194+
assert.ifError(err);
195+
196+
var firstKey = ds.key(['Post', key1.path[1]]);
197+
var secondKey = ds.key(['Post', key2.path[1]]);
198+
199+
var numEntitiesEmitted = 0;
200+
201+
ds.get([firstKey, secondKey])
202+
.on('error', done)
203+
.on('data', function() {
204+
numEntitiesEmitted++;
205+
})
206+
.on('end', function() {
207+
assert.strictEqual(numEntitiesEmitted, 2);
208+
209+
ds.delete([firstKey, secondKey], done);
210+
});
211+
});
212+
});
186213
});
187214

188215
it('should save keys as a part of entity and query by key', function(done) {

0 commit comments

Comments
 (0)