nodester 0.6.77 → 0.7.10
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.
|
@@ -14,7 +14,7 @@ const debug = require('debug')('nodester:interpreter:ModelsTree');
|
|
|
14
14
|
* @access public
|
|
15
15
|
*/
|
|
16
16
|
class ModelsTreeNode {
|
|
17
|
-
constructor(model, parent=null, opts={}) {
|
|
17
|
+
constructor(model, parent = null, opts = {}) {
|
|
18
18
|
this.model = model;
|
|
19
19
|
this.parent = parent;
|
|
20
20
|
|
|
@@ -30,11 +30,13 @@ class ModelsTreeNode {
|
|
|
30
30
|
this.group_by = opts.group_by ?? undefined;
|
|
31
31
|
this.skip = 0;
|
|
32
32
|
this.limit = -1; // No limit
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
this.order = opts.order ?? undefined;
|
|
35
35
|
this.order_by = opts.order_by ?? undefined;
|
|
36
36
|
// Clauses\
|
|
37
37
|
|
|
38
|
+
this.required = false; // For root filtering via ^
|
|
39
|
+
|
|
38
40
|
this._includes = opts.includes ?? [];
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -85,14 +87,14 @@ class ModelsTreeNode {
|
|
|
85
87
|
this.fn = null;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
addWhere(condition={}) {
|
|
90
|
+
addWhere(condition = {}) {
|
|
89
91
|
this._where = {
|
|
90
92
|
...this.where,
|
|
91
93
|
...condition
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
addFunction(fnParams={}) {
|
|
97
|
+
addFunction(fnParams = {}) {
|
|
96
98
|
this._functions.push(fnParams);
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -102,6 +104,14 @@ class ModelsTreeNode {
|
|
|
102
104
|
return modelTreeNode;
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
markAsRequired() {
|
|
108
|
+
this.required = true;
|
|
109
|
+
// Propagate up the tree (except for root)
|
|
110
|
+
if (this.parent && this.parent.model !== 'root') {
|
|
111
|
+
this.parent.markAsRequired();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
toObject() {
|
|
106
116
|
return {
|
|
107
117
|
model: this.model,
|
|
@@ -116,7 +126,8 @@ class ModelsTreeNode {
|
|
|
116
126
|
limit: this.limit,
|
|
117
127
|
order: this.order,
|
|
118
128
|
order_by: this.order_by,
|
|
119
|
-
|
|
129
|
+
|
|
130
|
+
required: this.required,
|
|
120
131
|
|
|
121
132
|
includes: this.includes.map(i => i.toObject())
|
|
122
133
|
}
|
|
@@ -134,7 +145,7 @@ class ModelsTree {
|
|
|
134
145
|
this.node = this.root;
|
|
135
146
|
}
|
|
136
147
|
|
|
137
|
-
include(model, opts={}) {
|
|
148
|
+
include(model, opts = {}) {
|
|
138
149
|
debug('include', model);
|
|
139
150
|
|
|
140
151
|
const node = new ModelsTreeNode(model, this.node, opts);
|
|
@@ -153,7 +164,7 @@ class ModelsTree {
|
|
|
153
164
|
}
|
|
154
165
|
|
|
155
166
|
debug('use', model, !!foundOne ? '' : '-> failed.');
|
|
156
|
-
|
|
167
|
+
|
|
157
168
|
return foundOne;
|
|
158
169
|
}
|
|
159
170
|
|
|
@@ -507,6 +507,19 @@ module.exports = class QueryLexer {
|
|
|
507
507
|
continue;
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
|
|
511
|
+
// ^ can mean:
|
|
512
|
+
// • root filtering (INNER JOIN) - attribute in subquery that filters root
|
|
513
|
+
if (char === '^') {
|
|
514
|
+
debug('char', char, { token, node: tree.node });
|
|
515
|
+
|
|
516
|
+
// Mark current node as required (will propagate up)
|
|
517
|
+
tree.node.markAsRequired();
|
|
518
|
+
|
|
519
|
+
// Continue to next char (don't add ^ to token)
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
|
|
510
523
|
// = can only mean the end of a param name:
|
|
511
524
|
if (char === '=') {
|
|
512
525
|
const param = this.parseParamFromToken(token);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* nodester
|
|
3
3
|
* MIT Licensed
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const BOUNDS = require('../../constants/Bounds');
|
|
@@ -44,7 +44,7 @@ module.exports = traverse;
|
|
|
44
44
|
*
|
|
45
45
|
* @access public
|
|
46
46
|
*/
|
|
47
|
-
function traverse(queryNode, filter=null, model=null, association=null) {
|
|
47
|
+
function traverse(queryNode, filter = null, model = null, association = null) {
|
|
48
48
|
const _model = model ?? filter.model;
|
|
49
49
|
|
|
50
50
|
try {
|
|
@@ -55,7 +55,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
55
55
|
throw err;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
catch(error) {
|
|
58
|
+
catch (error) {
|
|
59
59
|
Error.captureStackTrace(error, traverse);
|
|
60
60
|
throw error;
|
|
61
61
|
}
|
|
@@ -78,8 +78,15 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
78
78
|
where,
|
|
79
79
|
|
|
80
80
|
includes,
|
|
81
|
+
required,
|
|
81
82
|
} = disassembleQueryNode(queryNode);
|
|
82
83
|
|
|
84
|
+
// If this include is marked as required (root filtering via ^),
|
|
85
|
+
// set it on the query:
|
|
86
|
+
if (required) {
|
|
87
|
+
newQuery.required = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
83
90
|
|
|
84
91
|
// Attribute:
|
|
85
92
|
//
|
|
@@ -102,7 +109,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
102
109
|
// put them through Filter:
|
|
103
110
|
for (let attribute of filter.attributes) {
|
|
104
111
|
if (attributesAvailable.indexOf(attribute) === -1) {
|
|
105
|
-
const err = new NodesterQueryError(`Field '${
|
|
112
|
+
const err = new NodesterQueryError(`Field '${attribute}' is not present in model.`);
|
|
106
113
|
Error.captureStackTrace(err, traverse);
|
|
107
114
|
throw err;
|
|
108
115
|
}
|
|
@@ -119,7 +126,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
119
126
|
}
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
|
-
|
|
129
|
+
|
|
123
130
|
// At least 1 attribute is mandatory
|
|
124
131
|
// or "functions" must be set:
|
|
125
132
|
if (
|
|
@@ -138,12 +145,12 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
138
145
|
const fnName = fnParams.fn;
|
|
139
146
|
|
|
140
147
|
if (typeof filter.functions[fnName] === 'undefined') {
|
|
141
|
-
const err = new NodesterQueryError(`Function '${
|
|
148
|
+
const err = new NodesterQueryError(`Function '${fnName}' is not allowed.`);
|
|
142
149
|
Error.captureStackTrace(err, traverse);
|
|
143
150
|
throw err;
|
|
144
151
|
}
|
|
145
152
|
|
|
146
|
-
switch(fnName) {
|
|
153
|
+
switch (fnName) {
|
|
147
154
|
// SQL COUNT():
|
|
148
155
|
case 'count': {
|
|
149
156
|
mapCOUNT(
|
|
@@ -157,7 +164,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
157
164
|
}
|
|
158
165
|
// Any other function:
|
|
159
166
|
default:
|
|
160
|
-
consl.warn(`function ${
|
|
167
|
+
consl.warn(`function ${fnName}() is not supported`);
|
|
161
168
|
break;
|
|
162
169
|
}
|
|
163
170
|
}
|
|
@@ -167,7 +174,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
167
174
|
const order = {};
|
|
168
175
|
|
|
169
176
|
const clausesEntries = Object.entries(clauses);
|
|
170
|
-
for (const [
|
|
177
|
+
for (const [clauseName, value] of clausesEntries) {
|
|
171
178
|
|
|
172
179
|
// If clause is not available:
|
|
173
180
|
if (filter != null) {
|
|
@@ -176,7 +183,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
176
183
|
}
|
|
177
184
|
}
|
|
178
185
|
|
|
179
|
-
switch(clauseName) {
|
|
186
|
+
switch (clauseName) {
|
|
180
187
|
case 'group_by': {
|
|
181
188
|
// Check if this value is a valid attribute:
|
|
182
189
|
if (typeof value === 'undefined') {
|
|
@@ -184,7 +191,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
184
191
|
}
|
|
185
192
|
|
|
186
193
|
if (typeof _model.tableAttributes[value] === 'undefined') {
|
|
187
|
-
const err = new NodesterQueryError(`group_by '${
|
|
194
|
+
const err = new NodesterQueryError(`group_by '${value}' is not allowed.`);
|
|
188
195
|
Error.captureStackTrace(err, traverse);
|
|
189
196
|
throw err;
|
|
190
197
|
}
|
|
@@ -197,7 +204,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
197
204
|
const _value = _setValueWithBounds(value, 'number', filter.bounds.clauses.limit);
|
|
198
205
|
|
|
199
206
|
// Do not set if negative:
|
|
200
|
-
if (_value < 0)
|
|
207
|
+
if (_value < 0)
|
|
201
208
|
continue;
|
|
202
209
|
|
|
203
210
|
newQuery.limit = _value;
|
|
@@ -237,9 +244,9 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
237
244
|
const staticClausesEntries = Object.entries(filter.statics.clauses);
|
|
238
245
|
|
|
239
246
|
for (let entry of staticClausesEntries) {
|
|
240
|
-
const [
|
|
247
|
+
const [clauseName, staticClauseValue] = entry;
|
|
241
248
|
|
|
242
|
-
switch(clauseName) {
|
|
249
|
+
switch (clauseName) {
|
|
243
250
|
case 'group_by':
|
|
244
251
|
newQuery.group = staticClauseValue;
|
|
245
252
|
continue;
|
|
@@ -282,7 +289,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
282
289
|
newQuery.order = sequelize.random();
|
|
283
290
|
}
|
|
284
291
|
else {
|
|
285
|
-
const column = sequelize.col(
|
|
292
|
+
const column = sequelize.col(order.by);
|
|
286
293
|
switch (order.order) {
|
|
287
294
|
// MAX/MIN:
|
|
288
295
|
case 'max-asc':
|
|
@@ -293,7 +300,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
293
300
|
case 'min':
|
|
294
301
|
case 'min-asc':
|
|
295
302
|
case 'max-desc':
|
|
296
|
-
newQuery.order = [
|
|
303
|
+
newQuery.order = [sequelize.fn('max', column), 'DESC'];
|
|
297
304
|
break;
|
|
298
305
|
// MAX/MIN\
|
|
299
306
|
|
|
@@ -303,7 +310,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
303
310
|
break;
|
|
304
311
|
|
|
305
312
|
default:
|
|
306
|
-
newQuery.order = [
|
|
313
|
+
newQuery.order = [[order.by, order.order]];
|
|
307
314
|
break;
|
|
308
315
|
}
|
|
309
316
|
}
|
|
@@ -316,7 +323,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
316
323
|
const includeName = include.model;
|
|
317
324
|
|
|
318
325
|
if (rootModelAssociations[includeName] === undefined) {
|
|
319
|
-
const err = new NodesterQueryError(`No include named '${
|
|
326
|
+
const err = new NodesterQueryError(`No include named '${includeName}'`);
|
|
320
327
|
Error.captureStackTrace(err, traverse);
|
|
321
328
|
throw err;
|
|
322
329
|
}
|
|
@@ -330,13 +337,13 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
330
337
|
|
|
331
338
|
// Set aatributes from Query:
|
|
332
339
|
const whereEntries = Object.entries(where);
|
|
333
|
-
for (const [
|
|
340
|
+
for (const [attribute, value] of whereEntries) {
|
|
334
341
|
parseWhereEntry(attribute, value, newQuery.where, _model);
|
|
335
342
|
}
|
|
336
343
|
|
|
337
344
|
// Static attributes override previously set attributes:
|
|
338
345
|
const staticAttributesEntries = Object.entries(filter.statics.attributes);
|
|
339
|
-
for (const [
|
|
346
|
+
for (const [attribute, staticValue] of staticAttributesEntries) {
|
|
340
347
|
newQuery.where[attribute] = parseValue(staticValue, attribute, _model);
|
|
341
348
|
}
|
|
342
349
|
|
|
@@ -369,13 +376,13 @@ function traverse(queryNode, filter=null, model=null, association=null) {
|
|
|
369
376
|
*/
|
|
370
377
|
function _traverseIncludes(includes, rootModel, filter, resultQuery) {
|
|
371
378
|
const filterIncludesEntries = Object.entries(filter.includes);
|
|
372
|
-
for (let [
|
|
379
|
+
for (let [includeName, includeFilter] of filterIncludesEntries) {
|
|
373
380
|
|
|
374
381
|
const association = rootModel.associations[includeName];
|
|
375
382
|
|
|
376
383
|
// If no such association:
|
|
377
384
|
if (!association) {
|
|
378
|
-
const err = new NodesterQueryError(`No include named '${
|
|
385
|
+
const err = new NodesterQueryError(`No include named '${includeName}'`);
|
|
379
386
|
Error.captureStackTrace(err, _traverseIncludes);
|
|
380
387
|
throw err;
|
|
381
388
|
}
|
|
@@ -394,7 +401,7 @@ function _traverseIncludes(includes, rootModel, filter, resultQuery) {
|
|
|
394
401
|
}
|
|
395
402
|
|
|
396
403
|
function _traverseIncludedOrders(resultQuery, rootModel) {
|
|
397
|
-
for (let i=0; i < resultQuery.include.length; i++) {
|
|
404
|
+
for (let i = 0; i < resultQuery.include.length; i++) {
|
|
398
405
|
const include = resultQuery.include[i];
|
|
399
406
|
|
|
400
407
|
if (!include?.order) {
|
|
@@ -411,7 +418,7 @@ function _traverseIncludedOrders(resultQuery, rootModel) {
|
|
|
411
418
|
associationType
|
|
412
419
|
} = getModelAssociationProps(rootModel.associations[association]);
|
|
413
420
|
|
|
414
|
-
switch(associationType) {
|
|
421
|
+
switch (associationType) {
|
|
415
422
|
case 'HasMany': {
|
|
416
423
|
include.separate = true;
|
|
417
424
|
// resultQuery.order.push([
|
|
@@ -434,7 +441,7 @@ function _traverseIncludedOrders(resultQuery, rootModel) {
|
|
|
434
441
|
function _setValueWithBounds(value, type, bounds) {
|
|
435
442
|
if (typeof bounds === 'object') {
|
|
436
443
|
|
|
437
|
-
switch(type) {
|
|
444
|
+
switch (type) {
|
|
438
445
|
case 'number': {
|
|
439
446
|
let _value = value;
|
|
440
447
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* nodester
|
|
3
3
|
* MIT Licensed
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
|
|
@@ -18,6 +18,7 @@ function _disassembleQueryNode(queryNode) {
|
|
|
18
18
|
functions,
|
|
19
19
|
where,
|
|
20
20
|
includes,
|
|
21
|
+
required,
|
|
21
22
|
...clauses
|
|
22
23
|
} = queryNode;
|
|
23
24
|
|
|
@@ -27,6 +28,7 @@ function _disassembleQueryNode(queryNode) {
|
|
|
27
28
|
functions: functions ?? [],
|
|
28
29
|
where: where ?? {},
|
|
29
30
|
includes: includes ?? [],
|
|
31
|
+
required: required ?? false,
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
34
|
|
package/package.json
CHANGED
package/tests/nql.test.js
CHANGED
|
@@ -24,7 +24,7 @@ describe('nodester Query Language', () => {
|
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
it('Simple where', async () => {
|
|
27
|
-
const lexer = new QueryLexer(
|
|
27
|
+
const lexer = new QueryLexer(queryStrings[0]);
|
|
28
28
|
const result = await lexer.parse();
|
|
29
29
|
|
|
30
30
|
const tree = new ModelsTree();
|
|
@@ -35,24 +35,24 @@ describe('nodester Query Language', () => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it('Only certain attributes', async () => {
|
|
38
|
-
const lexer = new QueryLexer(
|
|
38
|
+
const lexer = new QueryLexer(queryStrings[1]);
|
|
39
39
|
const result = await lexer.parse();
|
|
40
40
|
|
|
41
41
|
const tree = new ModelsTree();
|
|
42
|
-
tree.node.attributes = [
|
|
42
|
+
tree.node.attributes = ['id', 'text'];
|
|
43
43
|
const expected = tree.root.toObject();
|
|
44
44
|
|
|
45
45
|
expect(result).toMatchObject(expected);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
test('All possible params', async () => {
|
|
49
|
-
const lexer = new QueryLexer(
|
|
49
|
+
const lexer = new QueryLexer(queryStrings[2]);
|
|
50
50
|
const result = await lexer.parse();
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
const tree = new ModelsTree();
|
|
54
54
|
tree.node.addWhere({ id: ['10'], position: ['4'] });
|
|
55
|
-
tree.node.attributes = [
|
|
55
|
+
tree.node.attributes = ['id', 'content', 'position', 'created_at'];
|
|
56
56
|
tree.node.limit = 3;
|
|
57
57
|
tree.node.skip = 10;
|
|
58
58
|
tree.node.order = 'desc';
|
|
@@ -74,20 +74,20 @@ describe('nodester Query Language', () => {
|
|
|
74
74
|
'2-horizontals': 'includes=comments,users&id=1000',
|
|
75
75
|
// 4 horizontals with subquery.
|
|
76
76
|
'4-horizontals': 'in=categories,replies.users,comments(order_by=position&order=desc),users.avatars',
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
// Horizontals queried.
|
|
79
79
|
'horizontals-queried': 'includes=comments(order=desc),users,likes(order=rand),reposts&id=1000',
|
|
80
80
|
// Horizontals queried №2.
|
|
81
81
|
'horizontals-queried-2': 'in=comments(order_by=index&order=asc).users.karma',
|
|
82
82
|
// Horizontals queried №3.
|
|
83
83
|
'horizontals-queried-3': 'in=reactions,comments(user_id=gte(4)&skip=10&limit=2).users,likes,reposts',
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
// Separated includes.
|
|
86
86
|
'separated-includes': 'includes=comments(order=rand)&id=7&limit=3&includes=users(a=id,content)',
|
|
87
87
|
};
|
|
88
88
|
|
|
89
89
|
test('Simple includes', async () => {
|
|
90
|
-
const lexer = new QueryLexer(
|
|
90
|
+
const lexer = new QueryLexer(queryStrings['simple-includes']);
|
|
91
91
|
const result = await lexer.parse();
|
|
92
92
|
|
|
93
93
|
|
|
@@ -100,13 +100,13 @@ describe('nodester Query Language', () => {
|
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
test('Include with all possible params', async () => {
|
|
103
|
-
const lexer = new QueryLexer(
|
|
103
|
+
const lexer = new QueryLexer(queryStrings['include-with-params']);
|
|
104
104
|
const result = await lexer.parse();
|
|
105
105
|
|
|
106
106
|
const tree = new ModelsTree();
|
|
107
107
|
tree.include('comments').use('comments');
|
|
108
108
|
tree.node.addWhere({ id: ['10'], position: ['4'] });
|
|
109
|
-
tree.node.attributes = [
|
|
109
|
+
tree.node.attributes = ['id', 'content', 'position'];
|
|
110
110
|
tree.node.limit = 3;
|
|
111
111
|
tree.node.skip = 10;
|
|
112
112
|
tree.node.order = 'desc';
|
|
@@ -117,7 +117,7 @@ describe('nodester Query Language', () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
test('2 horizontals', async () => {
|
|
120
|
-
const lexer = new QueryLexer(
|
|
120
|
+
const lexer = new QueryLexer(queryStrings['2-horizontals']);
|
|
121
121
|
const result = await lexer.parse();
|
|
122
122
|
|
|
123
123
|
|
|
@@ -133,7 +133,7 @@ describe('nodester Query Language', () => {
|
|
|
133
133
|
test('4 horizontals', async () => {
|
|
134
134
|
// in=categories,replies.users,comments(order_by=position&order=desc),users.avatars
|
|
135
135
|
|
|
136
|
-
const lexer = new QueryLexer(
|
|
136
|
+
const lexer = new QueryLexer(queryStrings['4-horizontals']);
|
|
137
137
|
const result = await lexer.parse();
|
|
138
138
|
|
|
139
139
|
|
|
@@ -162,7 +162,7 @@ describe('nodester Query Language', () => {
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
test('Horizontals queried', async () => {
|
|
165
|
-
const lexer = new QueryLexer(
|
|
165
|
+
const lexer = new QueryLexer(queryStrings['horizontals-queried']);
|
|
166
166
|
const result = await lexer.parse();
|
|
167
167
|
|
|
168
168
|
|
|
@@ -182,7 +182,7 @@ describe('nodester Query Language', () => {
|
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
test('Horizontals queried №2', async () => {
|
|
185
|
-
const lexer = new QueryLexer(
|
|
185
|
+
const lexer = new QueryLexer(queryStrings['horizontals-queried-2']);
|
|
186
186
|
const result = await lexer.parse();
|
|
187
187
|
|
|
188
188
|
const tree = new ModelsTree();
|
|
@@ -200,7 +200,7 @@ describe('nodester Query Language', () => {
|
|
|
200
200
|
});
|
|
201
201
|
|
|
202
202
|
test('Horizontals queried №3', async () => {
|
|
203
|
-
const lexer = new QueryLexer(
|
|
203
|
+
const lexer = new QueryLexer(queryStrings['horizontals-queried-3']);
|
|
204
204
|
const result = await lexer.parse();
|
|
205
205
|
|
|
206
206
|
const tree = new ModelsTree();
|
|
@@ -208,9 +208,9 @@ describe('nodester Query Language', () => {
|
|
|
208
208
|
|
|
209
209
|
tree.include('comments').use('comments');
|
|
210
210
|
tree.node.addWhere({
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
user_id: {
|
|
212
|
+
gte: ['4']
|
|
213
|
+
}
|
|
214
214
|
});
|
|
215
215
|
tree.node.skip = 10;
|
|
216
216
|
tree.node.limit = 2;
|
|
@@ -227,7 +227,7 @@ describe('nodester Query Language', () => {
|
|
|
227
227
|
});
|
|
228
228
|
|
|
229
229
|
test('Separated includes"', async () => {
|
|
230
|
-
const lexer = new QueryLexer(
|
|
230
|
+
const lexer = new QueryLexer(queryStrings['separated-includes']);
|
|
231
231
|
const result = await lexer.parse();
|
|
232
232
|
|
|
233
233
|
const tree = new ModelsTree();
|
|
@@ -237,7 +237,7 @@ describe('nodester Query Language', () => {
|
|
|
237
237
|
tree.node.order = 'rand';
|
|
238
238
|
tree.up();
|
|
239
239
|
tree.include('users').use('users');
|
|
240
|
-
tree.node.attributes = [
|
|
240
|
+
tree.node.attributes = ['id', 'content'];
|
|
241
241
|
const expected = tree.root.toObject();
|
|
242
242
|
|
|
243
243
|
expect(result).toMatchObject(expected);
|
|
@@ -269,7 +269,7 @@ describe('nodester Query Language', () => {
|
|
|
269
269
|
];
|
|
270
270
|
|
|
271
271
|
test('Simple subinclude', async () => {
|
|
272
|
-
const lexer = new QueryLexer(
|
|
272
|
+
const lexer = new QueryLexer(queryStrings[0]);
|
|
273
273
|
const result = await lexer.parse();
|
|
274
274
|
|
|
275
275
|
|
|
@@ -282,7 +282,7 @@ describe('nodester Query Language', () => {
|
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
test('Deep subincludes', async () => {
|
|
285
|
-
const lexer = new QueryLexer(
|
|
285
|
+
const lexer = new QueryLexer(queryStrings[1]);
|
|
286
286
|
const result = await lexer.parse();
|
|
287
287
|
|
|
288
288
|
|
|
@@ -298,7 +298,7 @@ describe('nodester Query Language', () => {
|
|
|
298
298
|
});
|
|
299
299
|
|
|
300
300
|
test('Simple horizontal subinclude, "+" syntaxis"', async () => {
|
|
301
|
-
const lexer = new QueryLexer(
|
|
301
|
+
const lexer = new QueryLexer(queryStrings[2]);
|
|
302
302
|
const result = await lexer.parse();
|
|
303
303
|
|
|
304
304
|
|
|
@@ -312,7 +312,7 @@ describe('nodester Query Language', () => {
|
|
|
312
312
|
});
|
|
313
313
|
|
|
314
314
|
test('Subinclude query', async () => {
|
|
315
|
-
const lexer = new QueryLexer(
|
|
315
|
+
const lexer = new QueryLexer(queryStrings[3]);
|
|
316
316
|
const result = await lexer.parse();
|
|
317
317
|
|
|
318
318
|
|
|
@@ -396,22 +396,22 @@ describe('nodester Query Language', () => {
|
|
|
396
396
|
];
|
|
397
397
|
|
|
398
398
|
test('"OR" simple', async () => {
|
|
399
|
-
const lexer = new QueryLexer(
|
|
399
|
+
const lexer = new QueryLexer(queryStrings[0]);
|
|
400
400
|
const result = await lexer.parse();
|
|
401
401
|
|
|
402
402
|
const tree = new ModelsTree();
|
|
403
|
-
tree.node.addWhere({ or: [
|
|
403
|
+
tree.node.addWhere({ or: [{ index: ['2'] }, { position: ['5'] }] });
|
|
404
404
|
const expected = tree.root.toObject();
|
|
405
405
|
|
|
406
406
|
expect(result).toMatchObject(expected);
|
|
407
407
|
});
|
|
408
408
|
|
|
409
409
|
test('"OR" short', async () => {
|
|
410
|
-
const lexer = new QueryLexer(
|
|
410
|
+
const lexer = new QueryLexer(queryStrings[1]);
|
|
411
411
|
const result = await lexer.parse();
|
|
412
412
|
|
|
413
413
|
const tree = new ModelsTree();
|
|
414
|
-
tree.node.addWhere({ or: [
|
|
414
|
+
tree.node.addWhere({ or: [{ index: ['2'] }, { position: ['5'] }] });
|
|
415
415
|
const expected = tree.root.toObject();
|
|
416
416
|
|
|
417
417
|
expect(result).toMatchObject(expected);
|
|
@@ -429,7 +429,7 @@ describe('nodester Query Language', () => {
|
|
|
429
429
|
];
|
|
430
430
|
|
|
431
431
|
test('"NOT" simple', async () => {
|
|
432
|
-
const lexer = new QueryLexer(
|
|
432
|
+
const lexer = new QueryLexer(queryStrings[0]);
|
|
433
433
|
const result = await lexer.parse();
|
|
434
434
|
|
|
435
435
|
const tree = new ModelsTree();
|
|
@@ -440,7 +440,7 @@ describe('nodester Query Language', () => {
|
|
|
440
440
|
});
|
|
441
441
|
|
|
442
442
|
test('"NOT" short', async () => {
|
|
443
|
-
const lexer = new QueryLexer(
|
|
443
|
+
const lexer = new QueryLexer(queryStrings[1]);
|
|
444
444
|
const result = await lexer.parse();
|
|
445
445
|
|
|
446
446
|
const tree = new ModelsTree();
|
|
@@ -451,12 +451,12 @@ describe('nodester Query Language', () => {
|
|
|
451
451
|
});
|
|
452
452
|
|
|
453
453
|
test('"NOT" inside includes', async () => {
|
|
454
|
-
const lexer = new QueryLexer(
|
|
454
|
+
const lexer = new QueryLexer(queryStrings[2]);
|
|
455
455
|
const result = await lexer.parse();
|
|
456
456
|
|
|
457
457
|
const tree = new ModelsTree();
|
|
458
458
|
tree.include('comments').use('comments');
|
|
459
|
-
tree.node.addWhere({ id: { not: ['7'] }});
|
|
459
|
+
tree.node.addWhere({ id: { not: ['7'] } });
|
|
460
460
|
const expected = tree.root.toObject();
|
|
461
461
|
|
|
462
462
|
expect(result).toMatchObject(expected);
|
|
@@ -476,33 +476,33 @@ describe('nodester Query Language', () => {
|
|
|
476
476
|
];
|
|
477
477
|
|
|
478
478
|
test('"Like" simple', async () => {
|
|
479
|
-
const lexer = new QueryLexer(
|
|
479
|
+
const lexer = new QueryLexer(queryStrings[0]);
|
|
480
480
|
const result = await lexer.parse();
|
|
481
481
|
|
|
482
482
|
const tree = new ModelsTree();
|
|
483
|
-
tree.node.addWhere({ title: { like: ['some_text'] }});
|
|
483
|
+
tree.node.addWhere({ title: { like: ['some_text'] } });
|
|
484
484
|
const expected = tree.root.toObject();
|
|
485
485
|
|
|
486
486
|
expect(result).toMatchObject(expected);
|
|
487
487
|
});
|
|
488
488
|
|
|
489
489
|
test('"NotLike" simple', async () => {
|
|
490
|
-
const lexer = new QueryLexer(
|
|
490
|
+
const lexer = new QueryLexer(queryStrings[1]);
|
|
491
491
|
const result = await lexer.parse();
|
|
492
492
|
|
|
493
493
|
const tree = new ModelsTree();
|
|
494
|
-
tree.node.addWhere({ title: { notLike: ['some_text'] }});
|
|
494
|
+
tree.node.addWhere({ title: { notLike: ['some_text'] } });
|
|
495
495
|
const expected = tree.root.toObject();
|
|
496
496
|
|
|
497
497
|
expect(result).toMatchObject(expected);
|
|
498
498
|
});
|
|
499
499
|
|
|
500
500
|
test('"NotLike" short', async () => {
|
|
501
|
-
const lexer = new QueryLexer(
|
|
501
|
+
const lexer = new QueryLexer(queryStrings[2]);
|
|
502
502
|
const result = await lexer.parse();
|
|
503
503
|
|
|
504
504
|
const tree = new ModelsTree();
|
|
505
|
-
tree.node.addWhere({ title: { notLike: ['some_text'] }});
|
|
505
|
+
tree.node.addWhere({ title: { notLike: ['some_text'] } });
|
|
506
506
|
const expected = tree.root.toObject();
|
|
507
507
|
|
|
508
508
|
expect(result).toMatchObject(expected);
|
|
@@ -519,23 +519,23 @@ describe('nodester Query Language', () => {
|
|
|
519
519
|
];
|
|
520
520
|
|
|
521
521
|
test('"IN" simple', async () => {
|
|
522
|
-
const lexer = new QueryLexer(
|
|
522
|
+
const lexer = new QueryLexer(queryStrings[0]);
|
|
523
523
|
const result = await lexer.parse();
|
|
524
524
|
|
|
525
525
|
const tree = new ModelsTree();
|
|
526
|
-
tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] }});
|
|
526
|
+
tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] } });
|
|
527
527
|
const expected = tree.root.toObject();
|
|
528
528
|
|
|
529
529
|
expect(result).toMatchObject(expected);
|
|
530
530
|
});
|
|
531
531
|
|
|
532
532
|
test('"IN" and "limit" clause', async () => {
|
|
533
|
-
const lexer = new QueryLexer(
|
|
533
|
+
const lexer = new QueryLexer(queryStrings[1]);
|
|
534
534
|
const result = await lexer.parse();
|
|
535
535
|
|
|
536
536
|
const tree = new ModelsTree();
|
|
537
537
|
tree.node.limit = 3;
|
|
538
|
-
tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] }});
|
|
538
|
+
tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] } });
|
|
539
539
|
const expected = tree.root.toObject();
|
|
540
540
|
|
|
541
541
|
expect(result).toMatchObject(expected);
|
|
@@ -561,62 +561,62 @@ describe('nodester Query Language', () => {
|
|
|
561
561
|
];
|
|
562
562
|
|
|
563
563
|
test('Greater than', async () => {
|
|
564
|
-
const lexer = new QueryLexer(
|
|
564
|
+
const lexer = new QueryLexer(queryStrings[0]);
|
|
565
565
|
const result = await lexer.parse();
|
|
566
566
|
|
|
567
567
|
|
|
568
568
|
const tree = new ModelsTree();
|
|
569
|
-
tree.node.addWhere({ created_at: { gt: ['2022'] }});
|
|
569
|
+
tree.node.addWhere({ created_at: { gt: ['2022'] } });
|
|
570
570
|
const expected = tree.root.toObject();
|
|
571
571
|
|
|
572
572
|
expect(result).toMatchObject(expected);
|
|
573
573
|
});
|
|
574
574
|
|
|
575
575
|
test('Greater than or equal to', async () => {
|
|
576
|
-
const lexer = new QueryLexer(
|
|
576
|
+
const lexer = new QueryLexer(queryStrings[1]);
|
|
577
577
|
const result = await lexer.parse();
|
|
578
578
|
|
|
579
579
|
|
|
580
580
|
const tree = new ModelsTree();
|
|
581
|
-
tree.node.addWhere({ created_at: { gte: ['2023-12-08'] }});
|
|
581
|
+
tree.node.addWhere({ created_at: { gte: ['2023-12-08'] } });
|
|
582
582
|
const expected = tree.root.toObject();
|
|
583
583
|
|
|
584
584
|
expect(result).toMatchObject(expected);
|
|
585
585
|
});
|
|
586
586
|
|
|
587
587
|
test('Lower than', async () => {
|
|
588
|
-
const lexer = new QueryLexer(
|
|
588
|
+
const lexer = new QueryLexer(queryStrings[2]);
|
|
589
589
|
const result = await lexer.parse();
|
|
590
590
|
|
|
591
591
|
|
|
592
592
|
const tree = new ModelsTree();
|
|
593
|
-
tree.node.addWhere({ index: { lt: ['10'] }});
|
|
593
|
+
tree.node.addWhere({ index: { lt: ['10'] } });
|
|
594
594
|
const expected = tree.root.toObject();
|
|
595
595
|
|
|
596
596
|
expect(result).toMatchObject(expected);
|
|
597
597
|
});
|
|
598
598
|
|
|
599
599
|
test('Lower than or equal to', async () => {
|
|
600
|
-
const lexer = new QueryLexer(
|
|
600
|
+
const lexer = new QueryLexer(queryStrings[3]);
|
|
601
601
|
const result = await lexer.parse();
|
|
602
602
|
|
|
603
603
|
|
|
604
604
|
const tree = new ModelsTree();
|
|
605
|
-
tree.node.addWhere({ index: { lte: ['9'] }});
|
|
605
|
+
tree.node.addWhere({ index: { lte: ['9'] } });
|
|
606
606
|
const expected = tree.root.toObject();
|
|
607
607
|
|
|
608
608
|
expect(result).toMatchObject(expected);
|
|
609
609
|
});
|
|
610
610
|
|
|
611
611
|
test('Greater than in subinclude', async () => {
|
|
612
|
-
const lexer = new QueryLexer(
|
|
612
|
+
const lexer = new QueryLexer(queryStrings[4]);
|
|
613
613
|
const result = await lexer.parse();
|
|
614
614
|
|
|
615
615
|
|
|
616
616
|
const tree = new ModelsTree();
|
|
617
617
|
tree.include('comments').use('comments');
|
|
618
618
|
tree.include('likes').use('likes');
|
|
619
|
-
tree.node.addWhere({ index: { gt: ['60'] }});
|
|
619
|
+
tree.node.addWhere({ index: { gt: ['60'] } });
|
|
620
620
|
const expected = tree.root.toObject();
|
|
621
621
|
|
|
622
622
|
expect(result).toMatchObject(expected);
|
|
@@ -634,18 +634,18 @@ describe('nodester Query Language', () => {
|
|
|
634
634
|
}
|
|
635
635
|
|
|
636
636
|
test('AND (simple)', async () => {
|
|
637
|
-
const lexer = new QueryLexer(
|
|
637
|
+
const lexer = new QueryLexer(queryStrings.and_simple);
|
|
638
638
|
const result = await lexer.parse();
|
|
639
639
|
|
|
640
640
|
const tree = new ModelsTree();
|
|
641
|
-
tree.node.addWhere({ id: { gte: ['2'], lt: ['5'] }});
|
|
641
|
+
tree.node.addWhere({ id: { gte: ['2'], lt: ['5'] } });
|
|
642
642
|
const expected = tree.root.toObject();
|
|
643
643
|
|
|
644
644
|
expect(result).toMatchObject(expected);
|
|
645
645
|
});
|
|
646
646
|
|
|
647
647
|
test('AND (more OP)', async () => {
|
|
648
|
-
const lexer = new QueryLexer(
|
|
648
|
+
const lexer = new QueryLexer(queryStrings.and_more_op);
|
|
649
649
|
const result = await lexer.parse();
|
|
650
650
|
|
|
651
651
|
const tree = new ModelsTree();
|
|
@@ -658,7 +658,7 @@ describe('nodester Query Language', () => {
|
|
|
658
658
|
});
|
|
659
659
|
|
|
660
660
|
test('AND (in subincludes #0)', async () => {
|
|
661
|
-
const lexer = new QueryLexer(
|
|
661
|
+
const lexer = new QueryLexer(queryStrings.and_in_subincludes_0);
|
|
662
662
|
const result = await lexer.parse();
|
|
663
663
|
|
|
664
664
|
const tree = new ModelsTree();
|
|
@@ -672,7 +672,7 @@ describe('nodester Query Language', () => {
|
|
|
672
672
|
});
|
|
673
673
|
|
|
674
674
|
test('AND (in subincludes #1)', async () => {
|
|
675
|
-
const lexer = new QueryLexer(
|
|
675
|
+
const lexer = new QueryLexer(queryStrings.and_in_subincludes_1);
|
|
676
676
|
const result = await lexer.parse();
|
|
677
677
|
|
|
678
678
|
const tree = new ModelsTree();
|
|
@@ -689,7 +689,7 @@ describe('nodester Query Language', () => {
|
|
|
689
689
|
});
|
|
690
690
|
|
|
691
691
|
test('AND (in subincludes #2)', async () => {
|
|
692
|
-
const lexer = new QueryLexer(
|
|
692
|
+
const lexer = new QueryLexer(queryStrings.and_in_subincludes_2);
|
|
693
693
|
const result = await lexer.parse();
|
|
694
694
|
|
|
695
695
|
const tree = new ModelsTree();
|
|
@@ -716,7 +716,7 @@ describe('nodester Query Language', () => {
|
|
|
716
716
|
}
|
|
717
717
|
|
|
718
718
|
test('Count (full key name)', async () => {
|
|
719
|
-
const lexer = new QueryLexer(
|
|
719
|
+
const lexer = new QueryLexer(queryStrings.count_long);
|
|
720
720
|
const result = await lexer.parse();
|
|
721
721
|
|
|
722
722
|
|
|
@@ -731,7 +731,7 @@ describe('nodester Query Language', () => {
|
|
|
731
731
|
});
|
|
732
732
|
|
|
733
733
|
test('Count (short key name)', async () => {
|
|
734
|
-
const lexer = new QueryLexer(
|
|
734
|
+
const lexer = new QueryLexer(queryStrings.count_long);
|
|
735
735
|
const result = await lexer.parse();
|
|
736
736
|
|
|
737
737
|
|
|
@@ -746,7 +746,7 @@ describe('nodester Query Language', () => {
|
|
|
746
746
|
});
|
|
747
747
|
|
|
748
748
|
test('Count and includes', async () => {
|
|
749
|
-
const lexer = new QueryLexer(
|
|
749
|
+
const lexer = new QueryLexer(queryStrings.count_and_includes);
|
|
750
750
|
const result = await lexer.parse();
|
|
751
751
|
|
|
752
752
|
|
|
@@ -761,4 +761,46 @@ describe('nodester Query Language', () => {
|
|
|
761
761
|
expect(result).toMatchObject(expected);
|
|
762
762
|
});
|
|
763
763
|
});
|
|
764
|
+
|
|
765
|
+
describe('root-filtering', () => {
|
|
766
|
+
const queryStrings = {
|
|
767
|
+
simple: 'includes=order(^is_filled=1)',
|
|
768
|
+
complex: 'includes=product.order(^is_filled=1)+photos(limit=5)',
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
test('Simple root filtering with ^', async () => {
|
|
772
|
+
const lexer = new QueryLexer(queryStrings.simple);
|
|
773
|
+
const result = await lexer.parse();
|
|
774
|
+
|
|
775
|
+
const tree = new ModelsTree();
|
|
776
|
+
tree.include('order').use('order');
|
|
777
|
+
tree.node.addWhere({ is_filled: ['1'] });
|
|
778
|
+
tree.node.markAsRequired();
|
|
779
|
+
const expected = tree.root.toObject();
|
|
780
|
+
|
|
781
|
+
expect(result).toMatchObject(expected);
|
|
782
|
+
expect(result.includes[0].required).toBe(true);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
test('Complex nested root filtering with ^', async () => {
|
|
786
|
+
const lexer = new QueryLexer(queryStrings.complex);
|
|
787
|
+
const result = await lexer.parse();
|
|
788
|
+
|
|
789
|
+
const tree = new ModelsTree();
|
|
790
|
+
tree.include('product').use('product');
|
|
791
|
+
tree.include('order').use('order');
|
|
792
|
+
tree.node.addWhere({ is_filled: ['1'] });
|
|
793
|
+
tree.node.markAsRequired();
|
|
794
|
+
tree.up();
|
|
795
|
+
tree.up();
|
|
796
|
+
tree.include('photos').use('photos');
|
|
797
|
+
tree.node.limit = 5;
|
|
798
|
+
const expected = tree.root.toObject();
|
|
799
|
+
|
|
800
|
+
expect(result).toMatchObject(expected);
|
|
801
|
+
expect(result.includes[0].required).toBe(true);
|
|
802
|
+
expect(result.includes[0].includes[0].required).toBe(true);
|
|
803
|
+
expect(result.includes[1].required).toBe(false);
|
|
804
|
+
});
|
|
805
|
+
});
|
|
764
806
|
});
|