@builder6/query-mongodb 0.6.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 +201 -0
- package/README.md +37 -0
- package/dist/index.js +272 -0
- package/dist/index.test.js +1941 -0
- package/dist/options.js +420 -0
- package/dist/options.test.js +960 -0
- package/dist/pipelines.js +751 -0
- package/dist/pipelines.test.js +782 -0
- package/dist/utils.js +45 -0
- package/dist/utils.test.js +36 -0
- package/index.js +1 -0
- package/options.js +1 -0
- package/package.json +60 -0
- package/src/index.js +497 -0
- package/src/index.test.js +2333 -0
- package/src/options.js +573 -0
- package/src/options.test.js +1112 -0
- package/src/pipelines.js +760 -0
- package/src/pipelines.test.js +1300 -0
- package/src/utils.js +33 -0
- package/src/utils.test.js +40 -0
package/dist/utils.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
|
4
|
+
|
|
5
|
+
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
|
6
|
+
|
|
7
|
+
var replaceId = function replaceId(item) {
|
|
8
|
+
return item._id ? _extends({}, item, { _id: item._id.toHexString() }) : item;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
var createSummaryQueryExecutor = function createSummaryQueryExecutor(limit) {
|
|
12
|
+
var queriesExecuted = 0;
|
|
13
|
+
|
|
14
|
+
return function (fn) {
|
|
15
|
+
return !limit || ++queriesExecuted <= limit ? fn() : Promise.resolve();
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
var merge = function merge(os) {
|
|
20
|
+
return Object.assign.apply(Object, [{}].concat(_toConsumableArray(os)));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
var debug = function debug(id, f) {
|
|
24
|
+
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
25
|
+
|
|
26
|
+
var output = options.output || console.log;
|
|
27
|
+
var processResult = options.processResult || function (result) {
|
|
28
|
+
return result;
|
|
29
|
+
};
|
|
30
|
+
var processArgs = options.processArgs || function (args) {
|
|
31
|
+
return args;
|
|
32
|
+
};
|
|
33
|
+
return function () {
|
|
34
|
+
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
|
35
|
+
args[_key] = arguments[_key];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
output("DEBUG(" + id + "): ", processArgs(args));
|
|
39
|
+
var result = f.apply(undefined, args);
|
|
40
|
+
output("DEBUG(" + id + "/result): ", processResult(result));
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
module.exports = { replaceId: replaceId, createSummaryQueryExecutor: createSummaryQueryExecutor, merge: merge, debug: debug };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chai = require('chai');
|
|
4
|
+
var assert = chai.assert;
|
|
5
|
+
var sinon = require('sinon');
|
|
6
|
+
|
|
7
|
+
var _require = require('./utils'),
|
|
8
|
+
replaceId = _require.replaceId,
|
|
9
|
+
createSummaryQueryExecutor = _require.createSummaryQueryExecutor;
|
|
10
|
+
|
|
11
|
+
suite('utils', function () {
|
|
12
|
+
suite('replaceId', function () {
|
|
13
|
+
test('works', function () {
|
|
14
|
+
assert.deepEqual(replaceId({ _id: { toHexString: function toHexString() {
|
|
15
|
+
return '42';
|
|
16
|
+
} } }), {
|
|
17
|
+
_id: '42'
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
suite('createSummaryQueryExecutor', function () {
|
|
23
|
+
test('works', function (done) {
|
|
24
|
+
var exec = createSummaryQueryExecutor(3);
|
|
25
|
+
var func = sinon.stub().resolves();
|
|
26
|
+
var promises = [exec(func), exec(func), exec(func), exec(func), exec(func)];
|
|
27
|
+
|
|
28
|
+
Promise.all(promises).then(function (rs) {
|
|
29
|
+
assert.equal(rs.length, 5);
|
|
30
|
+
|
|
31
|
+
assert.equal(func.callCount, 3);
|
|
32
|
+
done();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./dist/index');
|
package/options.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./dist/options');
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@builder6/query-mongodb",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Querying a MongoDB collection using DevExtreme data store load parameters",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"index.js",
|
|
8
|
+
"options.js",
|
|
9
|
+
"dist",
|
|
10
|
+
"src",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"prepare": "babel src --out-dir ./dist",
|
|
16
|
+
"build": "babel src --out-dir ./dist"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/builder6/builder6-server.git"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"DevExtreme",
|
|
24
|
+
"MongoDB"
|
|
25
|
+
],
|
|
26
|
+
"author": "Oliver Sturm <oliver@oliversturm.com>",
|
|
27
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/builder6/builder6-server/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/builder6/builder6-server#readme",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=7.3"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"yup": "^1.1.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"babel-cli": "^6.26.0",
|
|
40
|
+
"babel-core": "^6.26.3",
|
|
41
|
+
"babel-eslint": "^10.1.0",
|
|
42
|
+
"babel-plugin-remove-comments": "^2.0.0",
|
|
43
|
+
"babel-plugin-transform-es2015-modules-umd": "^6.24.1",
|
|
44
|
+
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
|
45
|
+
"babel-polyfill": "^6.26.0",
|
|
46
|
+
"babel-preset-env": "^1.7.0",
|
|
47
|
+
"chai": "^4.3.7",
|
|
48
|
+
"eslint": "^8.38.0",
|
|
49
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
50
|
+
"mocha": "^10.2.0",
|
|
51
|
+
"mongodb": "^5.2.0",
|
|
52
|
+
"nyc": "^15.1.0",
|
|
53
|
+
"qs": "^6.11.1",
|
|
54
|
+
"sinon": "^15.0.3"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"gitHead": "2f816481fc81096a973e75ed27ff9b8bb0638e0c"
|
|
60
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
const {
|
|
2
|
+
createGroupFieldName,
|
|
3
|
+
createGroupKeyPipeline,
|
|
4
|
+
createGroupingPipeline,
|
|
5
|
+
createSkipTakePipeline,
|
|
6
|
+
createCountPipeline,
|
|
7
|
+
createMatchPipeline,
|
|
8
|
+
createSortPipeline,
|
|
9
|
+
createSummaryPipeline,
|
|
10
|
+
createSelectProjectExpression,
|
|
11
|
+
createSelectPipeline,
|
|
12
|
+
createCompleteFilterPipeline,
|
|
13
|
+
createRemoveNestedFieldsPipeline,
|
|
14
|
+
} = require('./pipelines');
|
|
15
|
+
const {
|
|
16
|
+
replaceId,
|
|
17
|
+
createSummaryQueryExecutor,
|
|
18
|
+
merge,
|
|
19
|
+
debug,
|
|
20
|
+
} = require('./utils');
|
|
21
|
+
const { getOptions } = require('./options');
|
|
22
|
+
|
|
23
|
+
function createContext(contextOptions, loadOptions) {
|
|
24
|
+
const aggregateCall = (collection, pipeline, identifier) =>
|
|
25
|
+
((aggregateOptions) => collection.aggregate(pipeline, aggregateOptions))(
|
|
26
|
+
contextOptions.dynamicAggregateOptions
|
|
27
|
+
? filterAggregateOptions(
|
|
28
|
+
contextOptions.dynamicAggregateOptions(
|
|
29
|
+
identifier,
|
|
30
|
+
pipeline,
|
|
31
|
+
collection
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
: contextOptions.aggregateOptions
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const getCount = (collection, pipeline) =>
|
|
38
|
+
aggregateCall(collection, pipeline, 'getCount')
|
|
39
|
+
.toArray()
|
|
40
|
+
// Strangely, the pipeline returns an empty array when the "match" part
|
|
41
|
+
// filters out all rows - I would expect to still see the "count" stage
|
|
42
|
+
// working, but it doesn't. Ask mongo.
|
|
43
|
+
.then((r) => (r.length > 0 ? r[0].count : 0));
|
|
44
|
+
|
|
45
|
+
const populateSummaryResults = (target, summary, summaryResults) => {
|
|
46
|
+
if (summary) {
|
|
47
|
+
target.summary = [];
|
|
48
|
+
|
|
49
|
+
for (const s of summary) {
|
|
50
|
+
switch (s.summaryType) {
|
|
51
|
+
case 'sum':
|
|
52
|
+
target.summary.push(summaryResults['___sum' + s.selector]);
|
|
53
|
+
break;
|
|
54
|
+
case 'avg':
|
|
55
|
+
target.summary.push(summaryResults['___avg' + s.selector]);
|
|
56
|
+
break;
|
|
57
|
+
case 'min':
|
|
58
|
+
target.summary.push(summaryResults['___min' + s.selector]);
|
|
59
|
+
break;
|
|
60
|
+
case 'max':
|
|
61
|
+
target.summary.push(summaryResults['___max' + s.selector]);
|
|
62
|
+
break;
|
|
63
|
+
case 'count':
|
|
64
|
+
target.summary.push(summaryResults.___count);
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
console.error(`Invalid summaryType ${s.summaryType}, ignoring`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return target;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const queryGroupData = (
|
|
75
|
+
collection,
|
|
76
|
+
desc,
|
|
77
|
+
includeDataItems,
|
|
78
|
+
countSeparately,
|
|
79
|
+
itemProjection,
|
|
80
|
+
groupKeyPipeline,
|
|
81
|
+
sortPipeline,
|
|
82
|
+
filterPipelineDetails,
|
|
83
|
+
skipTakePipeline,
|
|
84
|
+
matchPipeline
|
|
85
|
+
) =>
|
|
86
|
+
aggregateCall(
|
|
87
|
+
collection,
|
|
88
|
+
[
|
|
89
|
+
...contextOptions.preProcessingPipeline,
|
|
90
|
+
// sort pipeline first, apparently that enables it to use indexes
|
|
91
|
+
...sortPipeline,
|
|
92
|
+
...filterPipelineDetails.pipeline,
|
|
93
|
+
...matchPipeline,
|
|
94
|
+
...createRemoveNestedFieldsPipeline(
|
|
95
|
+
filterPipelineDetails.nestedFields,
|
|
96
|
+
contextOptions
|
|
97
|
+
),
|
|
98
|
+
...createGroupingPipeline(
|
|
99
|
+
desc,
|
|
100
|
+
includeDataItems,
|
|
101
|
+
countSeparately,
|
|
102
|
+
groupKeyPipeline,
|
|
103
|
+
itemProjection,
|
|
104
|
+
contextOptions
|
|
105
|
+
),
|
|
106
|
+
...skipTakePipeline,
|
|
107
|
+
],
|
|
108
|
+
'queryGroupData'
|
|
109
|
+
)
|
|
110
|
+
.toArray()
|
|
111
|
+
.then((r) =>
|
|
112
|
+
includeDataItems
|
|
113
|
+
? r.map((i) => ({
|
|
114
|
+
...i,
|
|
115
|
+
items: contextOptions.replaceIds
|
|
116
|
+
? i.items.map(replaceId)
|
|
117
|
+
: i.items,
|
|
118
|
+
}))
|
|
119
|
+
: r
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const queryGroup = (
|
|
123
|
+
collection,
|
|
124
|
+
groupIndex,
|
|
125
|
+
runSummaryQuery,
|
|
126
|
+
filterPipelineDetails,
|
|
127
|
+
skipTakePipeline = [],
|
|
128
|
+
summaryPipeline = [],
|
|
129
|
+
matchPipeline = []
|
|
130
|
+
) => {
|
|
131
|
+
const group = loadOptions.group[groupIndex];
|
|
132
|
+
const lastGroup = groupIndex === loadOptions.group.length - 1;
|
|
133
|
+
const itemDataRequired = lastGroup && group.isExpanded;
|
|
134
|
+
const separateCountRequired = !lastGroup;
|
|
135
|
+
|
|
136
|
+
// The current implementation of the dxDataGrid, at least, assumes that sub-group details are
|
|
137
|
+
// always included in the result, whether or not the group is marked isExpanded.
|
|
138
|
+
const subGroupsRequired = !lastGroup; // && group.isExpanded;
|
|
139
|
+
const summariesRequired =
|
|
140
|
+
loadOptions.groupSummary && loadOptions.groupSummary.length > 0;
|
|
141
|
+
|
|
142
|
+
const groupKeyPipeline = createGroupKeyPipeline(
|
|
143
|
+
group.selector,
|
|
144
|
+
group.groupInterval,
|
|
145
|
+
groupIndex,
|
|
146
|
+
contextOptions
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const augmentWithSubGroups = (groupData) =>
|
|
150
|
+
subGroupsRequired
|
|
151
|
+
? groupData.map((item) =>
|
|
152
|
+
queryGroup(
|
|
153
|
+
collection,
|
|
154
|
+
groupIndex + 1,
|
|
155
|
+
runSummaryQuery,
|
|
156
|
+
filterPipelineDetails, // used unchanged in lower levels
|
|
157
|
+
[], // skip/take doesn't apply on lower levels - correct?
|
|
158
|
+
summaryPipeline, // unmodified
|
|
159
|
+
// matchPipeline modified to filter down into group level
|
|
160
|
+
[
|
|
161
|
+
...matchPipeline,
|
|
162
|
+
// not completely clean to include this in the match pipeline, but the field
|
|
163
|
+
// added in the groupKeyPipeline is required specifically for the following match
|
|
164
|
+
...groupKeyPipeline,
|
|
165
|
+
...createMatchPipeline(
|
|
166
|
+
createGroupFieldName(groupIndex),
|
|
167
|
+
item.key,
|
|
168
|
+
contextOptions
|
|
169
|
+
),
|
|
170
|
+
]
|
|
171
|
+
).then((r) => {
|
|
172
|
+
item.items = r;
|
|
173
|
+
item.count = r.length;
|
|
174
|
+
return r;
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
: [];
|
|
178
|
+
|
|
179
|
+
const augmentWithSeparateCount = (groupData) => {
|
|
180
|
+
if (separateCountRequired) {
|
|
181
|
+
// We need to count separately because this is not the lowest level group,
|
|
182
|
+
// but since we didn't query details about our nested group, we can't just go
|
|
183
|
+
// for the length of the result array. An extra query is required in this case.
|
|
184
|
+
// Even though the count is a type of summary for the group, it is special - different
|
|
185
|
+
// from other group level summaries. The difference is that for each group, a summary
|
|
186
|
+
// is usually calculated with its data, even if that data isn't actually visible in the
|
|
187
|
+
// UI at the time. The count on the other hand is meant to represent the number of
|
|
188
|
+
// elements in the group, and in case these elements are sub-groups instead of data
|
|
189
|
+
// items, count represents a value that must not be calculated using the data items.
|
|
190
|
+
|
|
191
|
+
const nextGroup = loadOptions.group[groupIndex + 1];
|
|
192
|
+
const nextGroupKeyPipeline = createGroupKeyPipeline(
|
|
193
|
+
nextGroup.selector,
|
|
194
|
+
nextGroup.groupInterval,
|
|
195
|
+
groupIndex + 1,
|
|
196
|
+
contextOptions
|
|
197
|
+
);
|
|
198
|
+
return groupData.map((item) =>
|
|
199
|
+
getCount(collection, [
|
|
200
|
+
...contextOptions.preProcessingPipeline,
|
|
201
|
+
...filterPipelineDetails.pipeline,
|
|
202
|
+
...groupKeyPipeline,
|
|
203
|
+
|
|
204
|
+
...matchPipeline,
|
|
205
|
+
...createMatchPipeline(
|
|
206
|
+
createGroupFieldName(groupIndex),
|
|
207
|
+
item.key,
|
|
208
|
+
contextOptions
|
|
209
|
+
),
|
|
210
|
+
...createGroupingPipeline(
|
|
211
|
+
nextGroup.desc,
|
|
212
|
+
false,
|
|
213
|
+
true,
|
|
214
|
+
nextGroupKeyPipeline,
|
|
215
|
+
contextOptions
|
|
216
|
+
),
|
|
217
|
+
...createCountPipeline(contextOptions),
|
|
218
|
+
]).then((r) => {
|
|
219
|
+
item.count = r;
|
|
220
|
+
return r;
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
} else return [];
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const augmentWithSummaries = (groupData) =>
|
|
227
|
+
summariesRequired
|
|
228
|
+
? groupData.map((item) =>
|
|
229
|
+
runSummaryQuery(() =>
|
|
230
|
+
aggregateCall(
|
|
231
|
+
collection,
|
|
232
|
+
[
|
|
233
|
+
...contextOptions.preProcessingPipeline,
|
|
234
|
+
...filterPipelineDetails.pipeline,
|
|
235
|
+
...groupKeyPipeline,
|
|
236
|
+
...matchPipeline,
|
|
237
|
+
...createMatchPipeline(
|
|
238
|
+
createGroupFieldName(groupIndex),
|
|
239
|
+
item.key,
|
|
240
|
+
contextOptions
|
|
241
|
+
),
|
|
242
|
+
...summaryPipeline,
|
|
243
|
+
],
|
|
244
|
+
'augmentWithSummaries'
|
|
245
|
+
).toArray()
|
|
246
|
+
).then((r) =>
|
|
247
|
+
populateSummaryResults(item, loadOptions.groupSummary, r[0])
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
: [];
|
|
251
|
+
|
|
252
|
+
return queryGroupData(
|
|
253
|
+
collection,
|
|
254
|
+
group.desc,
|
|
255
|
+
itemDataRequired,
|
|
256
|
+
separateCountRequired,
|
|
257
|
+
createSelectProjectExpression(loadOptions.select, true),
|
|
258
|
+
groupKeyPipeline,
|
|
259
|
+
itemDataRequired
|
|
260
|
+
? createSortPipeline(loadOptions.sort, contextOptions)
|
|
261
|
+
: [],
|
|
262
|
+
filterPipelineDetails,
|
|
263
|
+
skipTakePipeline,
|
|
264
|
+
matchPipeline
|
|
265
|
+
).then(
|
|
266
|
+
(groupData) =>
|
|
267
|
+
/* eslint-disable promise/no-nesting */
|
|
268
|
+
Promise.all([
|
|
269
|
+
...augmentWithSubGroups(groupData),
|
|
270
|
+
...augmentWithSeparateCount(groupData),
|
|
271
|
+
...augmentWithSummaries(groupData),
|
|
272
|
+
]).then(() => groupData)
|
|
273
|
+
/* eslint-enable promise/no-nesting */
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const totalCount = (collection, completeFilterPipelineDetails) =>
|
|
278
|
+
loadOptions.requireTotalCount || loadOptions.totalSummary
|
|
279
|
+
? contextOptions.preferMetadataCount &&
|
|
280
|
+
contextOptions.preProcessingPipeline.length === 0 &&
|
|
281
|
+
completeFilterPipelineDetails.pipeline.length <= 1
|
|
282
|
+
? [
|
|
283
|
+
collection
|
|
284
|
+
.count(
|
|
285
|
+
completeFilterPipelineDetails.pipeline.length === 1
|
|
286
|
+
? completeFilterPipelineDetails.pipeline[0]['$match']
|
|
287
|
+
: undefined
|
|
288
|
+
)
|
|
289
|
+
.then((r) => ({ totalCount: r })),
|
|
290
|
+
]
|
|
291
|
+
: [
|
|
292
|
+
getCount(collection, [
|
|
293
|
+
...contextOptions.preProcessingPipeline,
|
|
294
|
+
...completeFilterPipelineDetails.pipeline,
|
|
295
|
+
...createCountPipeline(contextOptions),
|
|
296
|
+
]).then((r) => ({ totalCount: r })),
|
|
297
|
+
]
|
|
298
|
+
: [];
|
|
299
|
+
|
|
300
|
+
const summary =
|
|
301
|
+
(collection, completeFilterPipelineDetails) => (resultObject) =>
|
|
302
|
+
resultObject.totalCount > 0 && loadOptions.totalSummary
|
|
303
|
+
? aggregateCall(
|
|
304
|
+
collection,
|
|
305
|
+
[
|
|
306
|
+
...contextOptions.preProcessingPipeline,
|
|
307
|
+
...completeFilterPipelineDetails.pipeline,
|
|
308
|
+
...createSummaryPipeline(
|
|
309
|
+
loadOptions.totalSummary,
|
|
310
|
+
contextOptions
|
|
311
|
+
),
|
|
312
|
+
],
|
|
313
|
+
'summary'
|
|
314
|
+
)
|
|
315
|
+
.toArray()
|
|
316
|
+
.then((r) =>
|
|
317
|
+
populateSummaryResults(
|
|
318
|
+
resultObject,
|
|
319
|
+
loadOptions.totalSummary,
|
|
320
|
+
r[0]
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
: Promise.resolve(resultObject);
|
|
324
|
+
|
|
325
|
+
const queryGroups = (collection) => {
|
|
326
|
+
const completeFilterPipelineDetails = createCompleteFilterPipeline(
|
|
327
|
+
loadOptions.searchExpr,
|
|
328
|
+
loadOptions.searchOperation,
|
|
329
|
+
loadOptions.searchValue,
|
|
330
|
+
loadOptions.filter,
|
|
331
|
+
contextOptions
|
|
332
|
+
);
|
|
333
|
+
const summaryPipeline = createSummaryPipeline(
|
|
334
|
+
loadOptions.groupSummary,
|
|
335
|
+
contextOptions
|
|
336
|
+
);
|
|
337
|
+
const skipTakePipeline = createSkipTakePipeline(
|
|
338
|
+
loadOptions.skip,
|
|
339
|
+
loadOptions.take,
|
|
340
|
+
contextOptions
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const mainQueryResult = () =>
|
|
344
|
+
queryGroup(
|
|
345
|
+
collection,
|
|
346
|
+
0,
|
|
347
|
+
createSummaryQueryExecutor(undefined),
|
|
348
|
+
completeFilterPipelineDetails,
|
|
349
|
+
skipTakePipeline,
|
|
350
|
+
summaryPipeline
|
|
351
|
+
).then((r) => ({ data: r }));
|
|
352
|
+
|
|
353
|
+
const groupCount = () => {
|
|
354
|
+
if (loadOptions.requireGroupCount) {
|
|
355
|
+
const group = loadOptions.group[0];
|
|
356
|
+
|
|
357
|
+
return [
|
|
358
|
+
getCount(collection, [
|
|
359
|
+
...contextOptions.preProcessingPipeline,
|
|
360
|
+
...completeFilterPipelineDetails.pipeline,
|
|
361
|
+
...createGroupingPipeline(
|
|
362
|
+
group.desc,
|
|
363
|
+
false,
|
|
364
|
+
true,
|
|
365
|
+
createGroupKeyPipeline(
|
|
366
|
+
group.selector,
|
|
367
|
+
group.groupInterval,
|
|
368
|
+
0,
|
|
369
|
+
contextOptions
|
|
370
|
+
),
|
|
371
|
+
contextOptions
|
|
372
|
+
),
|
|
373
|
+
...createCountPipeline(contextOptions),
|
|
374
|
+
]).then((r) => ({ groupCount: r })),
|
|
375
|
+
];
|
|
376
|
+
} else return [];
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return Promise.all([
|
|
380
|
+
mainQueryResult(),
|
|
381
|
+
...groupCount(),
|
|
382
|
+
...totalCount(collection, completeFilterPipelineDetails),
|
|
383
|
+
])
|
|
384
|
+
.then(merge)
|
|
385
|
+
.then(summary(collection, completeFilterPipelineDetails));
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const querySimple = (collection) => {
|
|
389
|
+
const completeFilterPipelineDetails = createCompleteFilterPipeline(
|
|
390
|
+
loadOptions.searchExpr,
|
|
391
|
+
loadOptions.searchOperation,
|
|
392
|
+
loadOptions.searchValue,
|
|
393
|
+
loadOptions.filter,
|
|
394
|
+
contextOptions
|
|
395
|
+
);
|
|
396
|
+
const sortPipeline = createSortPipeline(loadOptions.sort, contextOptions);
|
|
397
|
+
const skipTakePipeline = createSkipTakePipeline(
|
|
398
|
+
loadOptions.skip,
|
|
399
|
+
loadOptions.take,
|
|
400
|
+
contextOptions
|
|
401
|
+
);
|
|
402
|
+
const selectPipeline = createSelectPipeline(
|
|
403
|
+
loadOptions.select,
|
|
404
|
+
contextOptions
|
|
405
|
+
);
|
|
406
|
+
const removeNestedFieldsPipeline = createRemoveNestedFieldsPipeline(
|
|
407
|
+
completeFilterPipelineDetails.nestedFields,
|
|
408
|
+
contextOptions
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const mainQueryResult = () =>
|
|
412
|
+
aggregateCall(
|
|
413
|
+
collection,
|
|
414
|
+
[
|
|
415
|
+
...contextOptions.preProcessingPipeline,
|
|
416
|
+
...completeFilterPipelineDetails.pipeline,
|
|
417
|
+
...sortPipeline,
|
|
418
|
+
...skipTakePipeline,
|
|
419
|
+
...selectPipeline,
|
|
420
|
+
...removeNestedFieldsPipeline,
|
|
421
|
+
],
|
|
422
|
+
'mainQueryResult'
|
|
423
|
+
)
|
|
424
|
+
.toArray()
|
|
425
|
+
.then((r) => (contextOptions.replaceIds ? r.map(replaceId) : r))
|
|
426
|
+
.then((r) => ({ data: r }));
|
|
427
|
+
|
|
428
|
+
return Promise.all([
|
|
429
|
+
mainQueryResult(),
|
|
430
|
+
...totalCount(collection, completeFilterPipelineDetails),
|
|
431
|
+
])
|
|
432
|
+
.then(merge)
|
|
433
|
+
.then(summary(collection, completeFilterPipelineDetails));
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
return { queryGroups, querySimple };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function filterAggregateOptions(proposedOptions) {
|
|
440
|
+
const acceptableAggregateOptionNames = [
|
|
441
|
+
'allowDiskUse',
|
|
442
|
+
'maxTimeMS',
|
|
443
|
+
'readConcern',
|
|
444
|
+
'collation',
|
|
445
|
+
'hint',
|
|
446
|
+
'comment',
|
|
447
|
+
];
|
|
448
|
+
return Object.keys(proposedOptions).reduce(
|
|
449
|
+
(r, v) =>
|
|
450
|
+
acceptableAggregateOptionNames.includes(v)
|
|
451
|
+
? { ...r, [v]: proposedOptions[v] }
|
|
452
|
+
: r,
|
|
453
|
+
{}
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function query(collection, loadOptions = {}, options = {}) {
|
|
458
|
+
const proposedAggregateOptions = options.aggregateOptions;
|
|
459
|
+
delete options.aggregateOptions;
|
|
460
|
+
|
|
461
|
+
const standardContextOptions = {
|
|
462
|
+
replaceIds: true,
|
|
463
|
+
summaryQueryLimit: 100,
|
|
464
|
+
// timezone offset for the query, in the form returned by
|
|
465
|
+
// Date.getTimezoneOffset
|
|
466
|
+
timezoneOffset: 0,
|
|
467
|
+
preProcessingPipeline: [],
|
|
468
|
+
caseInsensitiveRegex: true,
|
|
469
|
+
};
|
|
470
|
+
const contextOptions = Object.assign(standardContextOptions, options);
|
|
471
|
+
|
|
472
|
+
if (!options.dynamicAggregateOptions && proposedAggregateOptions)
|
|
473
|
+
contextOptions.aggregateOptions = filterAggregateOptions(
|
|
474
|
+
proposedAggregateOptions
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
const context = createContext(contextOptions, loadOptions);
|
|
478
|
+
|
|
479
|
+
return loadOptions.group && loadOptions.group.length > 0
|
|
480
|
+
? context.queryGroups(collection)
|
|
481
|
+
: context.querySimple(collection);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function querySimple(collection, loadOptions = {}, options = {}) {
|
|
485
|
+
delete loadOptions.group;
|
|
486
|
+
return query(collection, loadOptions, options);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function queryGroups(collection, loadOptions = {}, options = {}) {
|
|
490
|
+
return query(collection, loadOptions, options);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
module.exports = {
|
|
494
|
+
queryGroups,
|
|
495
|
+
querySimple,
|
|
496
|
+
getOptions
|
|
497
|
+
};
|