jsforce2 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +74 -0
- package/bin/jsforce +3 -0
- package/bower.json +30 -0
- package/build/jsforce-api-analytics.js +393 -0
- package/build/jsforce-api-analytics.min.js +2 -0
- package/build/jsforce-api-analytics.min.js.map +1 -0
- package/build/jsforce-api-apex.js +183 -0
- package/build/jsforce-api-apex.min.js +2 -0
- package/build/jsforce-api-apex.min.js.map +1 -0
- package/build/jsforce-api-bulk.js +1054 -0
- package/build/jsforce-api-bulk.min.js +2 -0
- package/build/jsforce-api-bulk.min.js.map +1 -0
- package/build/jsforce-api-chatter.js +320 -0
- package/build/jsforce-api-chatter.min.js +2 -0
- package/build/jsforce-api-chatter.min.js.map +1 -0
- package/build/jsforce-api-metadata.js +3020 -0
- package/build/jsforce-api-metadata.min.js +2 -0
- package/build/jsforce-api-metadata.min.js.map +1 -0
- package/build/jsforce-api-soap.js +403 -0
- package/build/jsforce-api-soap.min.js +2 -0
- package/build/jsforce-api-soap.min.js.map +1 -0
- package/build/jsforce-api-streaming.js +3479 -0
- package/build/jsforce-api-streaming.min.js +2 -0
- package/build/jsforce-api-streaming.min.js.map +1 -0
- package/build/jsforce-api-tooling.js +319 -0
- package/build/jsforce-api-tooling.min.js +2 -0
- package/build/jsforce-api-tooling.min.js.map +1 -0
- package/build/jsforce-core.js +25250 -0
- package/build/jsforce-core.min.js +2 -0
- package/build/jsforce-core.min.js.map +1 -0
- package/build/jsforce.js +31637 -0
- package/build/jsforce.min.js +2 -0
- package/build/jsforce.min.js.map +1 -0
- package/core.js +1 -0
- package/index.js +1 -0
- package/lib/VERSION.js +2 -0
- package/lib/_required.js +29 -0
- package/lib/api/analytics.js +387 -0
- package/lib/api/apex.js +177 -0
- package/lib/api/bulk.js +862 -0
- package/lib/api/chatter.js +314 -0
- package/lib/api/index.js +8 -0
- package/lib/api/metadata.js +848 -0
- package/lib/api/soap.js +397 -0
- package/lib/api/streaming-extension.js +136 -0
- package/lib/api/streaming.js +270 -0
- package/lib/api/tooling.js +313 -0
- package/lib/browser/canvas.js +90 -0
- package/lib/browser/client.js +241 -0
- package/lib/browser/core.js +5 -0
- package/lib/browser/jsforce.js +6 -0
- package/lib/browser/jsonp.js +52 -0
- package/lib/browser/request.js +70 -0
- package/lib/cache.js +252 -0
- package/lib/cli/cli.js +431 -0
- package/lib/cli/repl.js +337 -0
- package/lib/connection.js +1881 -0
- package/lib/core.js +16 -0
- package/lib/csv.js +50 -0
- package/lib/date.js +163 -0
- package/lib/http-api.js +300 -0
- package/lib/jsforce.js +10 -0
- package/lib/logger.js +52 -0
- package/lib/oauth2.js +206 -0
- package/lib/process.js +275 -0
- package/lib/promise.js +164 -0
- package/lib/query.js +881 -0
- package/lib/quick-action.js +90 -0
- package/lib/record-stream.js +305 -0
- package/lib/record.js +107 -0
- package/lib/registry/file-registry.js +48 -0
- package/lib/registry/index.js +3 -0
- package/lib/registry/registry.js +111 -0
- package/lib/require.js +14 -0
- package/lib/soap.js +207 -0
- package/lib/sobject.js +558 -0
- package/lib/soql-builder.js +236 -0
- package/lib/transport.js +233 -0
- package/package.json +110 -0
package/lib/query.js
ADDED
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
/*global process*/
|
|
2
|
+
/**
|
|
3
|
+
* @file Manages query for records in Salesforce
|
|
4
|
+
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
var inherits = require('inherits'),
|
|
10
|
+
events = require('events'),
|
|
11
|
+
stream = require('readable-stream'),
|
|
12
|
+
_ = require('lodash/core'),
|
|
13
|
+
Promise = require('./promise'),
|
|
14
|
+
SfDate = require("./date"),
|
|
15
|
+
SOQLBuilder = require("./soql-builder"),
|
|
16
|
+
RecordStream = require("./record-stream");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Query
|
|
20
|
+
*
|
|
21
|
+
* @protected
|
|
22
|
+
* @class
|
|
23
|
+
* @extends {stream.Readable}
|
|
24
|
+
* @implements Promise.<T>
|
|
25
|
+
* @template T
|
|
26
|
+
* @param {Connection} conn - Connection object
|
|
27
|
+
* @param {Object|String} config - Query config object or SOQL string
|
|
28
|
+
* @param {Object} [options] - Default query options
|
|
29
|
+
* @param {Boolean} [options.autoFetch] - Using auto fetch mode or not
|
|
30
|
+
* @param {Number} [options.maxFetch] - Max fetching records in auto fetch mode
|
|
31
|
+
* @param {Boolean} [options.scanAll] - Including deleted records for query target or not
|
|
32
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in query request
|
|
33
|
+
*/
|
|
34
|
+
var Query = module.exports = function(conn, config, options) {
|
|
35
|
+
Query.super_.call(this, { objectMode: true });
|
|
36
|
+
|
|
37
|
+
this._conn = conn;
|
|
38
|
+
if (_.isString(config)) { // if query config is string, it is given in SOQL.
|
|
39
|
+
this._soql = config;
|
|
40
|
+
} else if (config.locator && config.locator.indexOf("/") >= 0) { // if locator given in url for next records
|
|
41
|
+
this._locator = config.locator.split("/").pop();
|
|
42
|
+
} else {
|
|
43
|
+
this._config = config;
|
|
44
|
+
this.select(config.fields);
|
|
45
|
+
if (config.includes) {
|
|
46
|
+
this.include(config.includes);
|
|
47
|
+
}
|
|
48
|
+
if (config.sort) {
|
|
49
|
+
this.sort(config.sort);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this._options = _.defaults(options || {}, {
|
|
53
|
+
maxFetch: 10000,
|
|
54
|
+
autoFetch: false,
|
|
55
|
+
scanAll: false,
|
|
56
|
+
responseTarget: ResponseTargets.QueryResult
|
|
57
|
+
});
|
|
58
|
+
this._executed = false;
|
|
59
|
+
this._finished = false;
|
|
60
|
+
this._chaining = false;
|
|
61
|
+
|
|
62
|
+
this._deferred = Promise.defer();
|
|
63
|
+
|
|
64
|
+
var self = this;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
inherits(Query, stream.Readable);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Select fields to include in the returning result
|
|
71
|
+
*
|
|
72
|
+
* @param {Object|Array.<String>|String} fields - Fields to fetch. Format can be in JSON object (MongoDB-like), array of field names, or comma-separated field names.
|
|
73
|
+
* @returns {Query.<T>}
|
|
74
|
+
*/
|
|
75
|
+
Query.prototype.select = function(fields) {
|
|
76
|
+
if (this._soql) {
|
|
77
|
+
throw Error("Cannot set select fields for the query which has already built SOQL.");
|
|
78
|
+
}
|
|
79
|
+
fields = fields || '*';
|
|
80
|
+
if (_.isString(fields)) {
|
|
81
|
+
fields = fields.split(/\s*,\s*/);
|
|
82
|
+
} else if (_.isObject(fields) && !_.isArray(fields)) {
|
|
83
|
+
var _fields = [];
|
|
84
|
+
for (var k in fields) {
|
|
85
|
+
if (fields[k]) { _fields.push(k); }
|
|
86
|
+
}
|
|
87
|
+
fields = _fields;
|
|
88
|
+
}
|
|
89
|
+
this._config.fields = fields;
|
|
90
|
+
return this;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Set query conditions to filter the result records
|
|
95
|
+
*
|
|
96
|
+
* @param {Object|String} conditions - Conditions in JSON object (MongoDB-like), or raw SOQL WHERE clause string.
|
|
97
|
+
* @returns {Query.<T>}
|
|
98
|
+
*/
|
|
99
|
+
Query.prototype.where = function(conditions) {
|
|
100
|
+
if (this._soql) {
|
|
101
|
+
throw Error("Cannot set where conditions for the query which has already built SOQL.");
|
|
102
|
+
}
|
|
103
|
+
this._config.conditions = conditions;
|
|
104
|
+
return this;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Limit the returning result
|
|
109
|
+
*
|
|
110
|
+
* @param {Number} limit - Maximum number of records the query will return.
|
|
111
|
+
* @returns {Query.<T>}
|
|
112
|
+
*/
|
|
113
|
+
Query.prototype.limit = function(limit) {
|
|
114
|
+
if (this._soql) {
|
|
115
|
+
throw Error("Cannot set limit for the query which has already built SOQL.");
|
|
116
|
+
}
|
|
117
|
+
this._config.limit = limit;
|
|
118
|
+
return this;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Synonym of Query#offset()
|
|
123
|
+
*
|
|
124
|
+
* @method Query#skip
|
|
125
|
+
* @param {Number} offset - Offset number where begins returning results.
|
|
126
|
+
* @returns {Query.<T>}
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* Skip records
|
|
130
|
+
*
|
|
131
|
+
* @method Query#offset
|
|
132
|
+
* @param {Number} offset - Offset number where begins returning results.
|
|
133
|
+
* @returns {Query.<T>}
|
|
134
|
+
*/
|
|
135
|
+
Query.prototype.skip =
|
|
136
|
+
Query.prototype.offset = function(offset) {
|
|
137
|
+
if (this._soql) {
|
|
138
|
+
throw Error("Cannot set skip/offset for the query which has already built SOQL.");
|
|
139
|
+
}
|
|
140
|
+
this._config.offset = offset;
|
|
141
|
+
return this;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Synonym of Query#sort()
|
|
146
|
+
*
|
|
147
|
+
* @memthod Query#orderby
|
|
148
|
+
* @param {String|Object} sort - Sorting field or hash object with field name and sord direction
|
|
149
|
+
* @param {String|Number} [dir] - Sorting direction (ASC|DESC|1|-1)
|
|
150
|
+
* @returns {Query.<T>}
|
|
151
|
+
*/
|
|
152
|
+
/**
|
|
153
|
+
* Set query sort with direction
|
|
154
|
+
*
|
|
155
|
+
* @method Query#sort
|
|
156
|
+
* @param {String|Object} sort - Sorting field or hash object with field name and sord direction
|
|
157
|
+
* @param {String|Number} [dir] - Sorting direction (ASC|DESC|1|-1)
|
|
158
|
+
* @returns {Query.<T>}
|
|
159
|
+
*/
|
|
160
|
+
Query.prototype.sort =
|
|
161
|
+
Query.prototype.orderby = function(sort, dir) {
|
|
162
|
+
if (this._soql) {
|
|
163
|
+
throw Error("Cannot set sort for the query which has already built SOQL.");
|
|
164
|
+
}
|
|
165
|
+
if (_.isString(sort) && _.isString(dir)) {
|
|
166
|
+
sort = [ [ sort, dir ] ];
|
|
167
|
+
}
|
|
168
|
+
this._config.sort = sort;
|
|
169
|
+
return this;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Include child relationship query
|
|
174
|
+
*
|
|
175
|
+
* @param {String} childRelName - Child relationship name to include in query result
|
|
176
|
+
* @param {Object|String} [conditions] - Conditions in JSON object (MongoDB-like), or raw SOQL WHERE clause string.
|
|
177
|
+
* @param {Object|Array.<String>|String} [fields] - Fields to fetch. Format can be in JSON object (MongoDB-like), array of field names, or comma-separated field names.
|
|
178
|
+
* @param {Object} [options] - Optional query configulations.
|
|
179
|
+
* @param {Number} [options.limit] - Maximum number of records the query will return.
|
|
180
|
+
* @param {Number} [options.offset] - Offset number where begins returning results.
|
|
181
|
+
* @param {Number} [options.skip] - Synonym of options.offset.
|
|
182
|
+
* @returns {Query~SubQuery}
|
|
183
|
+
*/
|
|
184
|
+
Query.prototype.include = function(childRelName, conditions, fields, options) {
|
|
185
|
+
if (this._soql) {
|
|
186
|
+
throw Error("Cannot include child relationship into the query which has already built SOQL.");
|
|
187
|
+
}
|
|
188
|
+
if (_.isObject(childRelName)) {
|
|
189
|
+
var includes = childRelName;
|
|
190
|
+
for (var crname in includes) {
|
|
191
|
+
var config = includes[crname];
|
|
192
|
+
this.include(crname, config.conditions, config.fields, config);
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
var childConfig = {
|
|
197
|
+
table: childRelName,
|
|
198
|
+
conditions: conditions,
|
|
199
|
+
fields: fields,
|
|
200
|
+
limit: options && options.limit,
|
|
201
|
+
offset: options && (options.offset || options.skip),
|
|
202
|
+
sort: options && options.sort
|
|
203
|
+
};
|
|
204
|
+
if (!_.isArray(this._config.includes)) this._config.includes = [];
|
|
205
|
+
this._config.includes.push(childConfig);
|
|
206
|
+
var childQuery = new SubQuery(this._conn, this, childConfig);
|
|
207
|
+
this._children = this._children || [];
|
|
208
|
+
this._children.push(childQuery);
|
|
209
|
+
return childQuery;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Setting maxFetch query option
|
|
215
|
+
*
|
|
216
|
+
* @param {Number} maxFetch - Max fetching records in auto fetch mode
|
|
217
|
+
* @returns {Query.<T>}
|
|
218
|
+
*/
|
|
219
|
+
Query.prototype.maxFetch = function(maxFetch) {
|
|
220
|
+
this._options.maxFetch = maxFetch;
|
|
221
|
+
return this;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Switching auto fetch mode
|
|
226
|
+
*
|
|
227
|
+
* @param {Boolean} autoFetch - Using auto fetch mode or not
|
|
228
|
+
* @returns {Query.<T>}
|
|
229
|
+
*/
|
|
230
|
+
Query.prototype.autoFetch = function(autoFetch) {
|
|
231
|
+
this._options.autoFetch = autoFetch;
|
|
232
|
+
return this;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Set flag to scan all records including deleted and archived.
|
|
237
|
+
*
|
|
238
|
+
* @param {Boolean} scanAll - Flag whether include deleted/archived record or not. Default is false.
|
|
239
|
+
* @returns {Query.<T>}
|
|
240
|
+
*/
|
|
241
|
+
Query.prototype.scanAll = function(scanAll) {
|
|
242
|
+
this._options.scanAll = scanAll;
|
|
243
|
+
return this;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @private
|
|
248
|
+
*/
|
|
249
|
+
var ResponseTargets = Query.ResponseTargets = {};
|
|
250
|
+
[ "QueryResult", "Records", "SingleRecord", "Count" ].forEach(function(f) {
|
|
251
|
+
ResponseTargets[f] = f;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @protected
|
|
256
|
+
* @param {String} responseTarget - Query response target
|
|
257
|
+
* @returns {Query.<S>}
|
|
258
|
+
*/
|
|
259
|
+
Query.prototype.setResponseTarget = function(responseTarget) {
|
|
260
|
+
if (responseTarget in ResponseTargets) {
|
|
261
|
+
this._options.responseTarget = responseTarget;
|
|
262
|
+
}
|
|
263
|
+
return this;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Synonym of Query#execute()
|
|
269
|
+
*
|
|
270
|
+
* @method Query#run
|
|
271
|
+
* @param {Object} [options] - Query options
|
|
272
|
+
* @param {Boolean} [options.autoFetch] - Using auto fetch mode or not
|
|
273
|
+
* @param {Number} [options.maxFetch] - Max fetching records in auto fetch mode
|
|
274
|
+
* @param {Boolean} [options.scanAll] - Including deleted records for query target or not
|
|
275
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in query request
|
|
276
|
+
* @param {Callback.<T>} [callback] - Callback function
|
|
277
|
+
* @returns {Query.<T>}
|
|
278
|
+
*/
|
|
279
|
+
Query.prototype.run =
|
|
280
|
+
/**
|
|
281
|
+
* Synonym of Query#execute()
|
|
282
|
+
*
|
|
283
|
+
* @method Query#exec
|
|
284
|
+
* @param {Object} [options] - Query options
|
|
285
|
+
* @param {Boolean} [options.autoFetch] - Using auto fetch mode or not
|
|
286
|
+
* @param {Number} [options.maxFetch] - Max fetching records in auto fetch mode
|
|
287
|
+
* @param {Boolean} [options.scanAll] - Including deleted records for query target or not
|
|
288
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in query request
|
|
289
|
+
* @param {Callback.<T>} [callback] - Callback function
|
|
290
|
+
* @returns {Query.<T>}
|
|
291
|
+
*/
|
|
292
|
+
Query.prototype.exec =
|
|
293
|
+
/**
|
|
294
|
+
* Execute query and fetch records from server.
|
|
295
|
+
*
|
|
296
|
+
* @method Query#execute
|
|
297
|
+
* @param {Object} [options] - Query options
|
|
298
|
+
* @param {Boolean} [options.autoFetch] - Using auto fetch mode or not
|
|
299
|
+
* @param {Number} [options.maxFetch] - Max fetching records in auto fetch mode
|
|
300
|
+
* @param {Boolean} [options.scanAll] - Including deleted records for query target or not
|
|
301
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in query request
|
|
302
|
+
* @param {Callback.<T>} [callback] - Callback function
|
|
303
|
+
* @returns {Query.<T>}
|
|
304
|
+
*/
|
|
305
|
+
Query.prototype.execute = function(options, callback) {
|
|
306
|
+
var self = this;
|
|
307
|
+
var logger = this._conn._logger;
|
|
308
|
+
var deferred = this._deferred;
|
|
309
|
+
|
|
310
|
+
if (this._executed) {
|
|
311
|
+
deferred.reject(new Error("re-executing already executed query"));
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (this._finished) {
|
|
316
|
+
deferred.reject(new Error("executing already closed query"));
|
|
317
|
+
return this;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (typeof options === "function") {
|
|
321
|
+
callback = options;
|
|
322
|
+
options = {};
|
|
323
|
+
}
|
|
324
|
+
options = options || {};
|
|
325
|
+
options = {
|
|
326
|
+
headers: options.headers || self._options.headers,
|
|
327
|
+
responseTarget: options.responseTarget || self._options.responseTarget,
|
|
328
|
+
autoFetch: options.autoFetch || self._options.autoFetch,
|
|
329
|
+
maxFetch: options.maxFetch || self._options.maxFetch,
|
|
330
|
+
scanAll: options.scanAll || self._options.scanAll
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// callback and promise resolution;
|
|
334
|
+
var promiseCallback = function(err, res) {
|
|
335
|
+
if (_.isFunction(callback)) {
|
|
336
|
+
try {
|
|
337
|
+
res = callback(err, res);
|
|
338
|
+
err = null;
|
|
339
|
+
} catch(e) {
|
|
340
|
+
err = e;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (err) {
|
|
344
|
+
deferred.reject(err);
|
|
345
|
+
} else {
|
|
346
|
+
deferred.resolve(res);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
this.once('response', function(res) {
|
|
350
|
+
promiseCallback(null, res);
|
|
351
|
+
});
|
|
352
|
+
this.once('error', function(err) {
|
|
353
|
+
promiseCallback(err);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// collect fetched records in array
|
|
357
|
+
// only when response target is Records and
|
|
358
|
+
// either callback or chaining promises are available to this query.
|
|
359
|
+
this.once('fetch', function() {
|
|
360
|
+
if (options.responseTarget === ResponseTargets.Records && (self._chaining || callback)) {
|
|
361
|
+
logger.debug('--- collecting all fetched records ---');
|
|
362
|
+
var records = [];
|
|
363
|
+
var onRecord = function(record) {
|
|
364
|
+
records.push(record);
|
|
365
|
+
};
|
|
366
|
+
self.on('record', onRecord);
|
|
367
|
+
self.once('end', function() {
|
|
368
|
+
self.removeListener('record', onRecord);
|
|
369
|
+
self.emit('response', records, self);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// flag to prevent re-execution
|
|
375
|
+
this._executed = true;
|
|
376
|
+
|
|
377
|
+
// start actual query
|
|
378
|
+
logger.debug('>>> Query start >>>');
|
|
379
|
+
this._execute(options).then(function() {
|
|
380
|
+
logger.debug('*** Query finished ***');
|
|
381
|
+
}).fail(function(err) {
|
|
382
|
+
logger.debug('--- Query error ---');
|
|
383
|
+
self.emit('error', err);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// return Query instance for chaining
|
|
387
|
+
return this;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
Query.prototype._execute = function(options) {
|
|
394
|
+
var self = this;
|
|
395
|
+
var logger = this._conn._logger;
|
|
396
|
+
var responseTarget = options.responseTarget;
|
|
397
|
+
var autoFetch = options.autoFetch;
|
|
398
|
+
var maxFetch = options.maxFetch;
|
|
399
|
+
var scanAll = options.scanAll;
|
|
400
|
+
|
|
401
|
+
return Promise.resolve(
|
|
402
|
+
self._locator ?
|
|
403
|
+
self._conn._baseUrl() + "/query/" + self._locator :
|
|
404
|
+
self.toSOQL().then(function(soql) {
|
|
405
|
+
self.totalFetched = 0;
|
|
406
|
+
logger.debug("SOQL = " + soql);
|
|
407
|
+
return self._conn._baseUrl() + "/" + (scanAll ? "queryAll" : "query") + "?q=" + encodeURIComponent(soql);
|
|
408
|
+
})
|
|
409
|
+
).then(function(url) {
|
|
410
|
+
return self._conn.request({
|
|
411
|
+
method: 'GET',
|
|
412
|
+
url: url,
|
|
413
|
+
headers: options.headers
|
|
414
|
+
});
|
|
415
|
+
}).then(function(data) {
|
|
416
|
+
self.emit("fetch");
|
|
417
|
+
self.totalSize = data.totalSize;
|
|
418
|
+
var res;
|
|
419
|
+
switch(responseTarget) {
|
|
420
|
+
case ResponseTargets.SingleRecord:
|
|
421
|
+
res = data.records && data.records.length > 0 ? data.records[0] : null;
|
|
422
|
+
break;
|
|
423
|
+
case ResponseTargets.Records:
|
|
424
|
+
res = data.records;
|
|
425
|
+
break;
|
|
426
|
+
case ResponseTargets.Count:
|
|
427
|
+
res = data.totalSize;
|
|
428
|
+
break;
|
|
429
|
+
default:
|
|
430
|
+
res = data;
|
|
431
|
+
}
|
|
432
|
+
// only fire response event when it should be notified per fetch
|
|
433
|
+
if (responseTarget !== ResponseTargets.Records) {
|
|
434
|
+
self.emit("response", res, self);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// streaming record instances
|
|
438
|
+
var numRecords = (data.records && data.records.length) || 0;
|
|
439
|
+
for (var i=0; i<numRecords; i++) {
|
|
440
|
+
if (self.totalFetched >= maxFetch) {
|
|
441
|
+
self._finished = true;
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
var record = data.records[i];
|
|
445
|
+
self.push(record);
|
|
446
|
+
self.emit('record', record, self.totalFetched++, self);
|
|
447
|
+
}
|
|
448
|
+
if (data.nextRecordsUrl) {
|
|
449
|
+
self._locator = data.nextRecordsUrl.split('/').pop();
|
|
450
|
+
}
|
|
451
|
+
self._finished = self._finished || data.done || !autoFetch;
|
|
452
|
+
if (self._finished) {
|
|
453
|
+
self.push(null);
|
|
454
|
+
} else {
|
|
455
|
+
self._execute(options);
|
|
456
|
+
}
|
|
457
|
+
return res;
|
|
458
|
+
});
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Readable stream implementation
|
|
463
|
+
*
|
|
464
|
+
* @override
|
|
465
|
+
* @private
|
|
466
|
+
*/
|
|
467
|
+
Query.prototype._read = function(size) {
|
|
468
|
+
if (!this._finished && !this._executed) {
|
|
469
|
+
this.execute({ autoFetch: true });
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
/** @override **/
|
|
474
|
+
Query.prototype.on = function(e, fn) {
|
|
475
|
+
if (e === 'record') {
|
|
476
|
+
var self = this;
|
|
477
|
+
this.on('readable', function() {
|
|
478
|
+
while(self.read() !== null) {} // discard buffered records
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
return Query.super_.prototype.on.call(this, e, fn);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/** @override **/
|
|
485
|
+
Query.prototype.addListener = Query.prototype.on;
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* @private
|
|
490
|
+
*/
|
|
491
|
+
Query.prototype._expandFields = function() {
|
|
492
|
+
if (this._soql) {
|
|
493
|
+
return Promise.reject(new Error("Cannot expand fields for the query which has already built SOQL."));
|
|
494
|
+
}
|
|
495
|
+
var self = this;
|
|
496
|
+
var logger = self._conn._logger;
|
|
497
|
+
var conn = this._conn;
|
|
498
|
+
var table = this._config.table;
|
|
499
|
+
var fields = this._config.fields || [];
|
|
500
|
+
|
|
501
|
+
logger.debug('_expandFields: table = ' + table + ', fields = ' + fields.join(', '));
|
|
502
|
+
|
|
503
|
+
return Promise.all([
|
|
504
|
+
Promise.resolve(self._parent ? findRelationTable(table) : table)
|
|
505
|
+
.then(function(table) {
|
|
506
|
+
return Promise.all(
|
|
507
|
+
_.map(fields, function(field) { return expandAsteriskField(table, field); })
|
|
508
|
+
).then(function(expandedFields) {
|
|
509
|
+
self._config.fields = _.flatten(expandedFields);
|
|
510
|
+
});
|
|
511
|
+
}),
|
|
512
|
+
Promise.all(
|
|
513
|
+
_.map(self._children || [], function(childQuery) {
|
|
514
|
+
return childQuery._expandFields();
|
|
515
|
+
})
|
|
516
|
+
)
|
|
517
|
+
]);
|
|
518
|
+
|
|
519
|
+
function findRelationTable(rname) {
|
|
520
|
+
var ptable = self._parent._config.table;
|
|
521
|
+
logger.debug('finding table for relation "' + rname + '" in "' + ptable + '"...');
|
|
522
|
+
return describeCache(ptable).then(function(sobject) {
|
|
523
|
+
var upperRname = rname.toUpperCase();
|
|
524
|
+
var childRelation = _.find(sobject.childRelationships, function(cr) {
|
|
525
|
+
return (cr.relationshipName || '').toUpperCase() === upperRname;
|
|
526
|
+
});
|
|
527
|
+
return childRelation ? childRelation.childSObject :
|
|
528
|
+
Promise.reject(new Error("No child relationship found: " + rname ));
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function describeCache(table) {
|
|
533
|
+
logger.debug('describe cache: '+table);
|
|
534
|
+
var deferred = Promise.defer();
|
|
535
|
+
conn.describe$(table, function(err, sobject) {
|
|
536
|
+
logger.debug('... done.');
|
|
537
|
+
if (err) { deferred.reject(err); }
|
|
538
|
+
else { deferred.resolve(sobject); }
|
|
539
|
+
});
|
|
540
|
+
return deferred.promise;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function expandAsteriskField(table, field) {
|
|
544
|
+
logger.debug('expanding field "'+ field + '" in "' + table + '"...');
|
|
545
|
+
var fpath = field.split('.');
|
|
546
|
+
return fpath[fpath.length - 1] === '*' ?
|
|
547
|
+
describeCache(table).then(function(sobject) {
|
|
548
|
+
logger.debug('table '+table+'has been described');
|
|
549
|
+
if (fpath.length > 1) {
|
|
550
|
+
var rname = fpath.shift();
|
|
551
|
+
var rfield = _.find(sobject.fields, function(f) {
|
|
552
|
+
return f.relationshipName &&
|
|
553
|
+
f.relationshipName.toUpperCase() === rname.toUpperCase();
|
|
554
|
+
});
|
|
555
|
+
if (rfield) {
|
|
556
|
+
var rtable = rfield.referenceTo.length === 1 ? rfield.referenceTo[0] : 'Name';
|
|
557
|
+
return expandAsteriskField(rtable, fpath.join('.')).then(function(fpaths) {
|
|
558
|
+
return _.map(fpaths, function(fpath) { return rname + '.' + fpath; });
|
|
559
|
+
});
|
|
560
|
+
} else {
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
return _.map(sobject.fields, function(f) { return f.name; });
|
|
565
|
+
}
|
|
566
|
+
}) :
|
|
567
|
+
Promise.resolve([ field ]);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Explain plan for executing query
|
|
573
|
+
*
|
|
574
|
+
* @param {Callback.<ExplainInfo>} [callback] - Callback function
|
|
575
|
+
* @returns {Promise.<ExplainInfo>}
|
|
576
|
+
*/
|
|
577
|
+
Query.prototype.explain = function(callback) {
|
|
578
|
+
var self = this;
|
|
579
|
+
var logger = this._conn._logger;
|
|
580
|
+
return self.toSOQL().then(function(soql) {
|
|
581
|
+
logger.debug("SOQL = " + soql);
|
|
582
|
+
var url = "/query/?explain=" + encodeURIComponent(soql);
|
|
583
|
+
return self._conn.request(url);
|
|
584
|
+
}).thenCall(callback);
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Return SOQL expression for the query
|
|
589
|
+
*
|
|
590
|
+
* @param {Callback.<String>} [callback] - Callback function
|
|
591
|
+
* @returns {Promise.<String>}
|
|
592
|
+
*/
|
|
593
|
+
Query.prototype.toSOQL = function(callback) {
|
|
594
|
+
var self = this;
|
|
595
|
+
return Promise.resolve(self._soql ||
|
|
596
|
+
self._expandFields().then(function() { return SOQLBuilder.createSOQL(self._config); })
|
|
597
|
+
).thenCall(callback);
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Create data stream of queried records.
|
|
602
|
+
* Automatically resume query if paused.
|
|
603
|
+
*
|
|
604
|
+
* @param {String} [type] - Type of outgoing data format. Currently 'csv' is default value and the only supported.
|
|
605
|
+
* @param {Object} [options] - Options passed to converter
|
|
606
|
+
* @returns {stream.Readable}
|
|
607
|
+
*/
|
|
608
|
+
Query.prototype.stream = RecordStream.Serializable.prototype.stream;
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Get record stream of queried records applying the given mapping function
|
|
612
|
+
*
|
|
613
|
+
* @param {RecordMapFunction} fn - Record mapping function
|
|
614
|
+
* @returns {RecordStream.Serializable}
|
|
615
|
+
*/
|
|
616
|
+
Query.prototype.map = RecordStream.prototype.map;
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Get record stream of queried records, applying the given filter function
|
|
620
|
+
*
|
|
621
|
+
* @param {RecordFilterFunction} fn - Record filtering function
|
|
622
|
+
* @returns {RecordStream.Serializable}
|
|
623
|
+
*/
|
|
624
|
+
Query.prototype.filter = RecordStream.prototype.map;
|
|
625
|
+
|
|
626
|
+
/*
|
|
627
|
+
* Default threshold num of bulk API switching
|
|
628
|
+
*/
|
|
629
|
+
var DEFAULT_BULK_THRESHOLD = 200;
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Synonym of Query#destroy()
|
|
633
|
+
*
|
|
634
|
+
* @method Query#delete
|
|
635
|
+
* @param {String} [type] - SObject type. Required for SOQL based query object.
|
|
636
|
+
* @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
|
|
637
|
+
* @returns {Promise.<Array.<RecordResult>>}
|
|
638
|
+
*/
|
|
639
|
+
/**
|
|
640
|
+
* Synonym of Query#destroy()
|
|
641
|
+
*
|
|
642
|
+
* @method Query#del
|
|
643
|
+
* @param {String} [type] - SObject type. Required for SOQL based query object.
|
|
644
|
+
* @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
|
|
645
|
+
* @returns {Promise.<Array.<RecordResult>>}
|
|
646
|
+
*/
|
|
647
|
+
/**
|
|
648
|
+
* Delete queried records
|
|
649
|
+
*
|
|
650
|
+
* @method Query#destroy
|
|
651
|
+
* @param {String} [type] - SObject type. Required for SOQL based query object.
|
|
652
|
+
* @param {Object} [options] - Mass delete operation options
|
|
653
|
+
* @param {Boolean} [options.allowBulk] - Allow switching to Bulk API when the num of queried records reached to certain threshold. Default is true.
|
|
654
|
+
* @param {Number} [options.bulkThreshold] - Threshold num to switch to use Bulk API instead of usual `SObject#delete()` call. Default value is 200 after API ver 42.0, and 0.5 * `maxRequest` before API ver 42.0.
|
|
655
|
+
* @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
|
|
656
|
+
* @returns {Promise.<Array.<RecordResult>>}
|
|
657
|
+
*/
|
|
658
|
+
Query.prototype["delete"] =
|
|
659
|
+
Query.prototype.del =
|
|
660
|
+
Query.prototype.destroy = function(type, options, callback) {
|
|
661
|
+
if (typeof type === 'function') {
|
|
662
|
+
callback = type;
|
|
663
|
+
options = {};
|
|
664
|
+
type = null;
|
|
665
|
+
} else if (typeof type === 'object' && type !== null) {
|
|
666
|
+
callback = options;
|
|
667
|
+
options = type;
|
|
668
|
+
type = null;
|
|
669
|
+
}
|
|
670
|
+
options = options || {};
|
|
671
|
+
type = type || (this._config && this._config.table);
|
|
672
|
+
if (!type) {
|
|
673
|
+
throw new Error("SOQL based query needs SObject type information to bulk delete.");
|
|
674
|
+
}
|
|
675
|
+
// Set the threshold number to pass to bulk API
|
|
676
|
+
var thresholdNum =
|
|
677
|
+
options.allowBulk === false ?
|
|
678
|
+
-1 :
|
|
679
|
+
typeof options.bulkThreshold === 'number' ?
|
|
680
|
+
options.bulkThreshold :
|
|
681
|
+
// determine threshold if the connection version supports SObject collection API or not
|
|
682
|
+
(this._conn._ensureVersion(42) ? DEFAULT_BULK_THRESHOLD : this._conn.maxRequest / 2);
|
|
683
|
+
var self = this;
|
|
684
|
+
return new Promise(function(resolve, reject) {
|
|
685
|
+
var records = [];
|
|
686
|
+
var batch = null;
|
|
687
|
+
var handleRecord = function(rec) {
|
|
688
|
+
if (!rec.Id) {
|
|
689
|
+
self.emit('error', new Error('Queried record does not include Salesforce record ID.'))
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
var record = { Id: rec.Id };
|
|
693
|
+
if (batch) {
|
|
694
|
+
batch.write(record);
|
|
695
|
+
} else {
|
|
696
|
+
records.push(record);
|
|
697
|
+
if (thresholdNum < 0 || records.length > thresholdNum) {
|
|
698
|
+
// Use bulk delete instead of SObject REST API
|
|
699
|
+
batch =
|
|
700
|
+
self._conn.sobject(type).deleteBulk()
|
|
701
|
+
.on('response', resolve)
|
|
702
|
+
.on('error', reject);
|
|
703
|
+
records.forEach(function(record) {
|
|
704
|
+
batch.write(record);
|
|
705
|
+
});
|
|
706
|
+
records = [];
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
var handleEnd = function() {
|
|
711
|
+
if (batch) {
|
|
712
|
+
batch.end();
|
|
713
|
+
} else {
|
|
714
|
+
var ids = records.map(function (record) { return record.Id; });
|
|
715
|
+
self._conn.sobject(type).destroy(ids, { allowRecursive: true }).then(resolve, reject);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
self.on('data', handleRecord)
|
|
719
|
+
.on('end', handleEnd)
|
|
720
|
+
.on('error', reject);
|
|
721
|
+
}).thenCall(callback);
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Update queried records, using given mapping function/object
|
|
726
|
+
*
|
|
727
|
+
* @param {Record|RecordMapFunction} mapping - Mapping record or record mapping function
|
|
728
|
+
* @param {String} [type] - SObject type. Required for SOQL based query object.
|
|
729
|
+
* @param {Object} [options] - Mass update operation options
|
|
730
|
+
* @param {Boolean} [options.allowBulk] - Allow switching to Bulk API when the num of queried records reached to certain threshold. Default is true.
|
|
731
|
+
* @param {Number} [options.bulkThreshold] - Threshold num to switch to use Bulk API instead of usual `SObject#delete()` call. Default value is 200 after API ver 42.0, and 0.5 * `maxRequest` before API ver 42.0.
|
|
732
|
+
* @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
|
|
733
|
+
* @returns {Promise.<Array.<RecordResult>>}
|
|
734
|
+
*/
|
|
735
|
+
Query.prototype.update = function(mapping, type, options, callback) {
|
|
736
|
+
if (typeof type === 'function') {
|
|
737
|
+
callback = type;
|
|
738
|
+
options = {};
|
|
739
|
+
type = null;
|
|
740
|
+
} else if (typeof type === 'object' && type !== null) {
|
|
741
|
+
callback = options;
|
|
742
|
+
options = type;
|
|
743
|
+
type = null;
|
|
744
|
+
}
|
|
745
|
+
options = options || {};
|
|
746
|
+
type = type || (this._config && this._config.table);
|
|
747
|
+
if (!type) {
|
|
748
|
+
throw new Error("SOQL based query needs SObject type information to bulk update.");
|
|
749
|
+
}
|
|
750
|
+
var updateStream = _.isFunction(mapping) ? RecordStream.map(mapping) : RecordStream.recordMapStream(mapping);
|
|
751
|
+
// Set the threshold number to pass to bulk API
|
|
752
|
+
var thresholdNum =
|
|
753
|
+
options.allowBulk === false ?
|
|
754
|
+
-1 :
|
|
755
|
+
typeof options.bulkThreshold === 'number' ?
|
|
756
|
+
options.bulkThreshold :
|
|
757
|
+
// determine threshold if the connection version supports SObject collection API or not
|
|
758
|
+
(this._conn._ensureVersion(42) ? DEFAULT_BULK_THRESHOLD : this._conn.maxRequest / 2);
|
|
759
|
+
var self = this;
|
|
760
|
+
return new Promise(function(resolve, reject) {
|
|
761
|
+
var records = [];
|
|
762
|
+
var batch = null;
|
|
763
|
+
var handleRecord = function(record) {
|
|
764
|
+
if (batch) {
|
|
765
|
+
batch.write(record);
|
|
766
|
+
} else {
|
|
767
|
+
records.push(record);
|
|
768
|
+
if (thresholdNum < 0 || records.length > thresholdNum) {
|
|
769
|
+
// Use bulk update instead of SObject REST API
|
|
770
|
+
batch =
|
|
771
|
+
self._conn.sobject(type).updateBulk()
|
|
772
|
+
.on('response', resolve)
|
|
773
|
+
.on('error', reject);
|
|
774
|
+
records.forEach(function(record) {
|
|
775
|
+
batch.write(record);
|
|
776
|
+
});
|
|
777
|
+
records = [];
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
var handleEnd = function() {
|
|
782
|
+
if (batch) {
|
|
783
|
+
batch.end();
|
|
784
|
+
} else {
|
|
785
|
+
self._conn.sobject(type).update(records, { allowRecursive: true }).then(resolve, reject);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
self.on('error', reject)
|
|
789
|
+
.pipe(updateStream)
|
|
790
|
+
.on('data', handleRecord)
|
|
791
|
+
.on('end', handleEnd)
|
|
792
|
+
.on('error', reject);
|
|
793
|
+
}).thenCall(callback);
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Promise/A+ interface
|
|
798
|
+
* http://promises-aplus.github.io/promises-spec/
|
|
799
|
+
*
|
|
800
|
+
* Delegate to deferred promise, return promise instance for query result
|
|
801
|
+
*
|
|
802
|
+
* @param {FulfilledCallback.<T, S1>} [onFulfilled]
|
|
803
|
+
* @param {RejectedCallback.<S2>} [onRejected]
|
|
804
|
+
* @returns {Promise.<S1|S2>}
|
|
805
|
+
*/
|
|
806
|
+
Query.prototype.then = function(onResolved, onReject) {
|
|
807
|
+
this._chaining = true;
|
|
808
|
+
if (!this._finished && !this._executed) { this.execute(); }
|
|
809
|
+
return this._deferred.promise.then.apply(this._deferred.promise, arguments);
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Promise/A+ extension
|
|
814
|
+
* Call "then" using given node-style callback function
|
|
815
|
+
*
|
|
816
|
+
* @param {Callback.<T>} [callback] - Callback function
|
|
817
|
+
* @returns {Query}
|
|
818
|
+
*/
|
|
819
|
+
Query.prototype.thenCall = function(callback) {
|
|
820
|
+
if (_.isFunction(callback)) {
|
|
821
|
+
this.then(function(res) {
|
|
822
|
+
process.nextTick(function() {
|
|
823
|
+
callback(null, res);
|
|
824
|
+
});
|
|
825
|
+
}, function(err) {
|
|
826
|
+
process.nextTick(function() {
|
|
827
|
+
callback(err);
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
return this;
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
/*--------------------------------------------*/
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* SubQuery object for representing child relationship query
|
|
838
|
+
*
|
|
839
|
+
* @protected
|
|
840
|
+
* @class Query~SubQuery
|
|
841
|
+
* @extends Query
|
|
842
|
+
* @param {Connection} conn - Connection object
|
|
843
|
+
* @param {Query} parent - Parent query object
|
|
844
|
+
* @param {Object} config - Sub query configuration
|
|
845
|
+
*/
|
|
846
|
+
var SubQuery = function(conn, parent, config) {
|
|
847
|
+
SubQuery.super_.call(this, conn, config);
|
|
848
|
+
this._parent = parent;
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
inherits(SubQuery, Query);
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* @method Query~SubQuery#include
|
|
855
|
+
* @override
|
|
856
|
+
*/
|
|
857
|
+
SubQuery.prototype.include = function() {
|
|
858
|
+
throw new Error("Not allowed to include another subquery in subquery.");
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Back the context to parent query object
|
|
863
|
+
*
|
|
864
|
+
* @method Query~SubQuery#end
|
|
865
|
+
* @returns {Query}
|
|
866
|
+
*/
|
|
867
|
+
SubQuery.prototype.end = function() {
|
|
868
|
+
return this._parent;
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* If execute is called in subquery context, delegate it to parent query object
|
|
873
|
+
*
|
|
874
|
+
* @method Query~SubQuery#execute
|
|
875
|
+
* @override
|
|
876
|
+
*/
|
|
877
|
+
SubQuery.prototype.run =
|
|
878
|
+
SubQuery.prototype.exec =
|
|
879
|
+
SubQuery.prototype.execute = function() {
|
|
880
|
+
return this._parent.execute.apply(this._parent, arguments);
|
|
881
|
+
};
|