nodester 0.2.3 → 0.2.4
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.
|
@@ -11,6 +11,15 @@ const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
|
|
|
11
11
|
const util = require('util');
|
|
12
12
|
const debug = require('debug')('nodester:interpreter:QueryLexer');
|
|
13
13
|
|
|
14
|
+
const PARAM_TOKENS = new Enum({
|
|
15
|
+
FIELDS: Symbol('fields'),
|
|
16
|
+
INCLUDES: Symbol('includes'),
|
|
17
|
+
LIMIT: Symbol('limit'),
|
|
18
|
+
ORDER: Symbol('order'),
|
|
19
|
+
ORDER_BY: Symbol('order_by'),
|
|
20
|
+
SKIP: Symbol('skip'),
|
|
21
|
+
});
|
|
22
|
+
|
|
14
23
|
const OP_TOKENS = new Enum({
|
|
15
24
|
AND: 'and',
|
|
16
25
|
BETWEEN: 'between',
|
|
@@ -59,13 +68,12 @@ module.exports = class QueryLexer {
|
|
|
59
68
|
|
|
60
69
|
parseIsolatedQuery(queryString='', startAt=0, tree) {
|
|
61
70
|
const isSubQuery = tree.node.model !== 'root';
|
|
62
|
-
debug({ isSubQuery, startAt });
|
|
63
71
|
|
|
64
|
-
// Token is String, accumulated char-by-char.
|
|
72
|
+
// Token is a String, accumulated char-by-char.
|
|
65
73
|
let token = '';
|
|
66
74
|
// Value of param ('id=10' OR 'fields=id,text').
|
|
67
75
|
let value = [];
|
|
68
|
-
// Model, that was active before cursor went up in the tree.
|
|
76
|
+
// Model, that was active before a cursor went up in the tree.
|
|
69
77
|
let previousActive = null;
|
|
70
78
|
|
|
71
79
|
for (let i=startAt; i < queryString.length; i++) {
|
|
@@ -143,7 +151,7 @@ module.exports = class QueryLexer {
|
|
|
143
151
|
|
|
144
152
|
// Reset:
|
|
145
153
|
tree.node.resetOP();
|
|
146
|
-
tree.node.activeParam =
|
|
154
|
+
tree.node.activeParam = PARAM_TOKENS.INCLUDES;
|
|
147
155
|
token = '';
|
|
148
156
|
value = [];
|
|
149
157
|
continue;
|
|
@@ -177,26 +185,29 @@ module.exports = class QueryLexer {
|
|
|
177
185
|
|
|
178
186
|
// Reset:
|
|
179
187
|
tree.node.resetFN();
|
|
180
|
-
tree.node.activeParam =
|
|
188
|
+
tree.node.activeParam = PARAM_TOKENS.INCLUDES;
|
|
181
189
|
token = '';
|
|
182
190
|
value = [];
|
|
183
191
|
continue;
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
// If end of subquery:
|
|
187
|
-
if (!!tree.node.activeParam && tree.node.activeParam !==
|
|
195
|
+
if (!!tree.node.activeParam && tree.node.activeParam !== PARAM_TOKENS.INCLUDES) {
|
|
188
196
|
// Set value.
|
|
189
197
|
this.setNodeParam(tree.node, token, value);
|
|
198
|
+
|
|
190
199
|
// Reset:
|
|
191
200
|
tree.node.resetActiveParam();
|
|
192
201
|
tree.node.resetOP();
|
|
202
|
+
|
|
203
|
+
// Lift from subquery.
|
|
204
|
+
tree.up();
|
|
193
205
|
}
|
|
194
206
|
const numberOfProcessedChars = i - startAt;
|
|
195
207
|
return [ numberOfProcessedChars ];
|
|
196
208
|
}
|
|
197
209
|
|
|
198
210
|
// , can mean n-th value in value array,
|
|
199
|
-
// or it can be n-th key-value pair in subquery,
|
|
200
211
|
// or horizontal include:
|
|
201
212
|
if (char === ',') {
|
|
202
213
|
debug('char', char, { token, node: tree.node });
|
|
@@ -211,7 +222,7 @@ module.exports = class QueryLexer {
|
|
|
211
222
|
}
|
|
212
223
|
|
|
213
224
|
// If param value:
|
|
214
|
-
if (tree.node.activeParam !==
|
|
225
|
+
if (tree.node.activeParam !== PARAM_TOKENS.INCLUDES) {
|
|
215
226
|
value.push(token);
|
|
216
227
|
token = '';
|
|
217
228
|
continue;
|
|
@@ -223,7 +234,7 @@ module.exports = class QueryLexer {
|
|
|
223
234
|
}
|
|
224
235
|
|
|
225
236
|
// Horizontal include:
|
|
226
|
-
if (tree.node.activeParam ===
|
|
237
|
+
if (tree.node.activeParam === PARAM_TOKENS.INCLUDES) {
|
|
227
238
|
const model = token;
|
|
228
239
|
tree.use(model) ?? tree.include(model);
|
|
229
240
|
|
|
@@ -232,7 +243,7 @@ module.exports = class QueryLexer {
|
|
|
232
243
|
tree.node.resetActiveParam();
|
|
233
244
|
tree.upToRoot();
|
|
234
245
|
|
|
235
|
-
tree.node.activeParam =
|
|
246
|
+
tree.node.activeParam = PARAM_TOKENS.INCLUDES;
|
|
236
247
|
|
|
237
248
|
token = '';
|
|
238
249
|
continue;
|
|
@@ -250,7 +261,7 @@ module.exports = class QueryLexer {
|
|
|
250
261
|
// Vertical include:
|
|
251
262
|
if (!!previousActive) {
|
|
252
263
|
tree.use(previousActive);
|
|
253
|
-
tree.node.activeParam =
|
|
264
|
+
tree.node.activeParam = PARAM_TOKENS.INCLUDES;
|
|
254
265
|
token = '';
|
|
255
266
|
continue;
|
|
256
267
|
}
|
|
@@ -261,7 +272,7 @@ module.exports = class QueryLexer {
|
|
|
261
272
|
tree.use(model) ?? tree.include(model).use(model);
|
|
262
273
|
|
|
263
274
|
// Prepare for more includes:
|
|
264
|
-
tree.node.activeParam =
|
|
275
|
+
tree.node.activeParam = PARAM_TOKENS.INCLUDES;
|
|
265
276
|
|
|
266
277
|
token = '';
|
|
267
278
|
continue;
|
|
@@ -283,7 +294,7 @@ module.exports = class QueryLexer {
|
|
|
283
294
|
tree.up();
|
|
284
295
|
|
|
285
296
|
// Prepare for more includes:
|
|
286
|
-
tree.node.activeParam =
|
|
297
|
+
tree.node.activeParam = PARAM_TOKENS.INCLUDES;
|
|
287
298
|
|
|
288
299
|
token = '';
|
|
289
300
|
continue;
|
|
@@ -295,12 +306,12 @@ module.exports = class QueryLexer {
|
|
|
295
306
|
}
|
|
296
307
|
|
|
297
308
|
tree.up();
|
|
298
|
-
tree.node.activeParam =
|
|
309
|
+
tree.node.activeParam = PARAM_TOKENS.INCLUDES;
|
|
299
310
|
|
|
300
311
|
continue;
|
|
301
312
|
}
|
|
302
313
|
|
|
303
|
-
// & can mean the end of key=value pair,
|
|
314
|
+
// & can mean the end of key=value pair in root and sub query,
|
|
304
315
|
// or the end of subincludes:
|
|
305
316
|
if (char === '&') {
|
|
306
317
|
debug('char', char, { token, node: tree.node });
|
|
@@ -312,7 +323,7 @@ module.exports = class QueryLexer {
|
|
|
312
323
|
}
|
|
313
324
|
|
|
314
325
|
// If end of key=value pair:
|
|
315
|
-
if (!!tree.node.activeParam && tree.node.activeParam !==
|
|
326
|
+
if (!!tree.node.activeParam && tree.node.activeParam !== PARAM_TOKENS.INCLUDES) {
|
|
316
327
|
// Set value.
|
|
317
328
|
this.setNodeParam(tree.node, token, value);
|
|
318
329
|
// Reset:
|
|
@@ -321,13 +332,19 @@ module.exports = class QueryLexer {
|
|
|
321
332
|
value = [];
|
|
322
333
|
continue;
|
|
323
334
|
}
|
|
324
|
-
else if (tree.node.activeParam ===
|
|
325
|
-
// If
|
|
335
|
+
else if (tree.node.activeParam === PARAM_TOKENS.INCLUDES) {
|
|
336
|
+
// If token has some chars,
|
|
337
|
+
// then it's include of a new model:
|
|
326
338
|
if (token.length > 0) {
|
|
327
339
|
const model = token;
|
|
328
340
|
// Just include, no use.
|
|
329
341
|
tree.include(model);
|
|
330
342
|
}
|
|
343
|
+
// If token is empty,
|
|
344
|
+
// it's most possibly a subquery
|
|
345
|
+
else {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
331
348
|
|
|
332
349
|
// Then jump to root.
|
|
333
350
|
tree.upToRoot();
|
|
@@ -387,7 +404,7 @@ module.exports = class QueryLexer {
|
|
|
387
404
|
if (char === '=') {
|
|
388
405
|
const param = this.parseParamFromToken(token);
|
|
389
406
|
|
|
390
|
-
if (isSubQuery === true && param ===
|
|
407
|
+
if (isSubQuery === true && param === PARAM_TOKENS.INCLUDES) {
|
|
391
408
|
const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.submodel' or 'model.submodel1+submodel2'.`);
|
|
392
409
|
throw err;
|
|
393
410
|
}
|
|
@@ -433,23 +450,29 @@ module.exports = class QueryLexer {
|
|
|
433
450
|
switch(token) {
|
|
434
451
|
case 'limit':
|
|
435
452
|
case 'l':
|
|
436
|
-
return
|
|
453
|
+
return PARAM_TOKENS.LIMIT;
|
|
454
|
+
|
|
437
455
|
case 'skip':
|
|
438
456
|
case 's':
|
|
439
457
|
case 'offset':
|
|
440
|
-
return
|
|
458
|
+
return PARAM_TOKENS.SKIP;
|
|
459
|
+
|
|
441
460
|
case 'order':
|
|
442
461
|
case 'o':
|
|
443
|
-
return
|
|
462
|
+
return PARAM_TOKENS.ORDER;
|
|
463
|
+
|
|
444
464
|
case 'order_by':
|
|
445
465
|
case 'o_by':
|
|
446
|
-
return
|
|
466
|
+
return PARAM_TOKENS.ORDER_BY;
|
|
467
|
+
|
|
447
468
|
case 'fields':
|
|
448
469
|
case 'f':
|
|
449
|
-
return
|
|
470
|
+
return PARAM_TOKENS.FIELDS;
|
|
471
|
+
|
|
450
472
|
case 'includes':
|
|
451
473
|
case 'in':
|
|
452
|
-
return
|
|
474
|
+
return PARAM_TOKENS.INCLUDES;
|
|
475
|
+
|
|
453
476
|
default:
|
|
454
477
|
return token;
|
|
455
478
|
}
|
|
@@ -458,30 +481,35 @@ module.exports = class QueryLexer {
|
|
|
458
481
|
setNodeParam(treeNode, token, value) {
|
|
459
482
|
const param = treeNode.activeParam;
|
|
460
483
|
|
|
461
|
-
debug(`set param
|
|
484
|
+
debug(`set param`, { param, token, value });
|
|
462
485
|
|
|
463
486
|
switch(param) {
|
|
464
|
-
case
|
|
487
|
+
case PARAM_TOKENS.LIMIT:
|
|
465
488
|
treeNode.limit = parseInt(token);
|
|
466
489
|
break;
|
|
467
|
-
|
|
468
|
-
case
|
|
490
|
+
|
|
491
|
+
case PARAM_TOKENS.SKIP:
|
|
469
492
|
treeNode.skip = parseInt(token);
|
|
470
493
|
break;
|
|
471
|
-
|
|
494
|
+
|
|
495
|
+
case PARAM_TOKENS.ORDER:
|
|
472
496
|
treeNode.order = token;
|
|
473
497
|
break;
|
|
474
|
-
|
|
498
|
+
|
|
499
|
+
case PARAM_TOKENS.ORDER_BY:
|
|
475
500
|
treeNode.order_by = token;
|
|
476
501
|
break;
|
|
477
|
-
|
|
502
|
+
|
|
503
|
+
case PARAM_TOKENS.FIELDS:
|
|
478
504
|
if (token) value.push(token);
|
|
479
505
|
treeNode.fields = value;
|
|
480
506
|
break;
|
|
481
|
-
|
|
507
|
+
|
|
508
|
+
case PARAM_TOKENS.INCLUDES:
|
|
482
509
|
const node = new ModelsTreeNode(token);
|
|
483
510
|
treeNode.include(node);
|
|
484
511
|
break;
|
|
512
|
+
|
|
485
513
|
default:
|
|
486
514
|
if (token) value.push(token);
|
|
487
515
|
treeNode.addWhere({ [param]: value });
|
package/lib/query/traverse.js
CHANGED
|
@@ -102,49 +102,57 @@ function traverse(queryNode, filter=null, model=null) {
|
|
|
102
102
|
// Functions:
|
|
103
103
|
for (const fnParams of functions) {
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
105
|
+
switch(fnParams.fn) {
|
|
106
|
+
// SQL COUNT():
|
|
107
|
+
case 'count': {
|
|
108
|
+
const countParams = fnParams.args;
|
|
109
|
+
|
|
110
|
+
const [ countTarget ] = countParams;
|
|
111
|
+
// Count can be requested for this model,
|
|
112
|
+
// or for any of the available uncludes.
|
|
113
|
+
const isForRootModel = countTarget === rootModelName.plural.toLowerCase();
|
|
114
|
+
|
|
115
|
+
// Compile request:
|
|
116
|
+
// Example of desired SQL:
|
|
117
|
+
// `(SELECT COUNT(*) FROM comments WHERE comments.post_id=Post.id)`
|
|
118
|
+
//
|
|
119
|
+
let rawSQL = '(SELECT COUNT(*) FROM ';
|
|
120
|
+
let countAttribute = 'count';
|
|
121
|
+
|
|
122
|
+
// If request to count one of the includes:
|
|
123
|
+
if (!isForRootModel) {
|
|
124
|
+
// Check if it's available:
|
|
125
|
+
if (
|
|
126
126
|
!filter
|
|
127
127
|
||
|
|
128
128
|
!filter?.includes[countTarget]
|
|
129
129
|
||
|
|
130
130
|
rootModelAssociations[countTarget] === undefined
|
|
131
131
|
) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
const err = new NodesterQueryError(`Count for '${ countTarget }' is not available.`);
|
|
133
|
+
Error.captureStackTrace(err, traverse);
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const {
|
|
138
|
+
as,
|
|
139
|
+
target,
|
|
140
|
+
foreignKey,
|
|
141
|
+
sourceKey
|
|
142
|
+
} = rootModelAssociations[countTarget];
|
|
143
|
+
const { tableName } = target;
|
|
144
|
+
|
|
145
|
+
rawSQL += `${ tableName } where ${ tableName }.${ foreignKey }=${ rootModelName.singular }.${ sourceKey })`;
|
|
146
|
+
countAttribute = `${ as }_count`;
|
|
135
147
|
}
|
|
136
148
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
} = rootModelAssociations[countTarget];
|
|
141
|
-
rawSQL += `${ countTarget } where ${ countTarget }.${ foreignKey }=${ rootModelName.singular }.${ sourceKey })`;
|
|
142
|
-
countAttribute = `${ countTarget }_count`;
|
|
149
|
+
newQuery.attributes.push(
|
|
150
|
+
[sequelize.literal(rawSQL), countAttribute]
|
|
151
|
+
);
|
|
143
152
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
);
|
|
153
|
+
// Unknow function:
|
|
154
|
+
default:
|
|
155
|
+
break;
|
|
148
156
|
}
|
|
149
157
|
}
|
|
150
158
|
// Functions\
|
|
@@ -194,7 +202,7 @@ function traverse(queryNode, filter=null, model=null) {
|
|
|
194
202
|
}
|
|
195
203
|
}
|
|
196
204
|
|
|
197
|
-
// "statics"
|
|
205
|
+
// Override clauses with "statics":
|
|
198
206
|
if (filter !== null) {
|
|
199
207
|
const staticClausesEntries = Object.entries(filter.statics.clauses);
|
|
200
208
|
|
|
@@ -228,7 +236,11 @@ function traverse(queryNode, filter=null, model=null) {
|
|
|
228
236
|
|
|
229
237
|
|
|
230
238
|
// Order:
|
|
231
|
-
if (
|
|
239
|
+
if (
|
|
240
|
+
order.order === 'rand'
|
|
241
|
+
||
|
|
242
|
+
order.order === 'random'
|
|
243
|
+
) {
|
|
232
244
|
newQuery.order = sequelize.random();
|
|
233
245
|
}
|
|
234
246
|
else {
|
|
@@ -292,11 +304,21 @@ function traverse(queryNode, filter=null, model=null) {
|
|
|
292
304
|
}
|
|
293
305
|
|
|
294
306
|
|
|
295
|
-
|
|
307
|
+
/**
|
|
308
|
+
* Traverses each include in the array.
|
|
309
|
+
*
|
|
310
|
+
* @param {Array} includes
|
|
311
|
+
* @param {Model} rootModel
|
|
312
|
+
* @param {NodesterFilter} filter
|
|
313
|
+
* @param {Object} resultQuery
|
|
314
|
+
*
|
|
315
|
+
* @api private
|
|
316
|
+
*/
|
|
317
|
+
function _traverseIncludes(includes, rootModel, filter, resultQuery) {
|
|
296
318
|
const filterIncludesEntries = Object.entries(filter.includes);
|
|
297
319
|
for (let [ includeName, includeFilter ] of filterIncludesEntries) {
|
|
298
320
|
|
|
299
|
-
const association =
|
|
321
|
+
const association = rootModel.associations[includeName];
|
|
300
322
|
|
|
301
323
|
// If no such association:
|
|
302
324
|
if (!association) {
|
package/package.json
CHANGED
package/tests/nql.test.js
CHANGED
|
@@ -10,285 +10,354 @@ const {
|
|
|
10
10
|
const { ModelsTree } = require('../lib/middlewares/ql/sequelize/interpreter/ModelsTree');
|
|
11
11
|
const QueryLexer = require('../lib/middlewares/ql/sequelize/interpreter/QueryLexer');
|
|
12
12
|
|
|
13
|
-
describe('nodester Query Language', () => {
|
|
14
|
-
const queryStrings = [
|
|
15
|
-
// Simple where.
|
|
16
|
-
'id=10',
|
|
17
|
-
// All possible params.
|
|
18
|
-
'id=10&position=4&limit=3&skip=10&order=desc&order_by=index&fields=id,content,position,created_at',
|
|
19
|
-
// Simple includes.
|
|
20
|
-
'includes=comments&id=7',
|
|
21
|
-
// Include with All possible params.
|
|
22
|
-
'includes=comments(id=10&position=4&limit=3&skip=10&order=desc&order_by=index&fields=id,content,position)',
|
|
23
|
-
|
|
24
|
-
// Subinclude horizontal.
|
|
25
|
-
'includes=comments,users&id=1000',
|
|
26
|
-
// Subinclude horizontal (more entries).
|
|
27
|
-
'includes=comments(order=desc),users,likes(order=rand),reposts&id=1000',
|
|
28
|
-
// Subinclude horizontal (+ syntaxis).
|
|
29
|
-
'includes=comments(order=desc).users+likes(order=rand&order_by=position)&id=1000',
|
|
30
|
-
|
|
31
|
-
// Subinclude vertical.
|
|
32
|
-
'includes=comments.users&id=1000',
|
|
33
|
-
// Subinclude vertical (more entries).
|
|
34
|
-
'in=comments.users.avatars.sizes&position=200',
|
|
35
|
-
|
|
36
|
-
// Complex includes.
|
|
37
|
-
'includes=comments.users.avatars(fields=id,content&order=rand)&id=7&limit=3',
|
|
38
|
-
|
|
39
|
-
// Broken includes.
|
|
40
|
-
'includes=comments(order=rand)&id=7&limit=3&includes=users(fields=id,content)',
|
|
41
|
-
|
|
42
|
-
// OR simple.
|
|
43
|
-
'or(index=2,position=5)',
|
|
44
|
-
// OR shortened.
|
|
45
|
-
'|(index=2,position=5)',
|
|
46
|
-
|
|
47
|
-
// NOT inside include.
|
|
48
|
-
'includes=comments(id=not(7))',
|
|
49
|
-
|
|
50
|
-
// Like simple.
|
|
51
|
-
'title=like(some_text)',
|
|
52
|
-
|
|
53
|
-
// Subinclude and isolated Horizontal.
|
|
54
|
-
'in=comments.user,likes',
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
it('query "Simple where"', () => {
|
|
58
|
-
const lexer = new QueryLexer( queryStrings[0] );
|
|
59
|
-
const result = lexer.query;
|
|
60
|
-
|
|
61
|
-
const tree = new ModelsTree();
|
|
62
|
-
tree.node.addWhere({ id: ['10'] });
|
|
63
|
-
const expected = tree.root.toObject();
|
|
64
|
-
|
|
65
|
-
expect(result).toMatchObject(expected);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('query "All possible params"', () => {
|
|
69
|
-
const lexer = new QueryLexer( queryStrings[1] );
|
|
70
|
-
const result = lexer.query;
|
|
71
13
|
|
|
72
14
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
15
|
+
describe('nodester Query Language', () => {
|
|
16
|
+
describe('flat', () => {
|
|
17
|
+
const queryStrings = [
|
|
18
|
+
// Simple where.
|
|
19
|
+
'id=10',
|
|
20
|
+
// All possible params.
|
|
21
|
+
'id=10&position=4&limit=3&skip=10&order=desc&order_by=index&fields=id,content,position,created_at',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
it('Simple where', () => {
|
|
25
|
+
const lexer = new QueryLexer( queryStrings[0] );
|
|
26
|
+
const result = lexer.query;
|
|
27
|
+
|
|
28
|
+
const tree = new ModelsTree();
|
|
29
|
+
tree.node.addWhere({ id: ['10'] });
|
|
30
|
+
const expected = tree.root.toObject();
|
|
31
|
+
|
|
32
|
+
expect(result).toMatchObject(expected);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('All possible params', () => {
|
|
36
|
+
const lexer = new QueryLexer( queryStrings[1] );
|
|
37
|
+
const result = lexer.query;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
const tree = new ModelsTree();
|
|
41
|
+
tree.node.addWhere({ id: ['10'], position: ['4'] });
|
|
42
|
+
tree.node.fields = [ 'id', 'content', 'position', 'created_at' ];
|
|
43
|
+
tree.node.limit = 3;
|
|
44
|
+
tree.node.skip = 10;
|
|
45
|
+
tree.node.order = 'desc';
|
|
46
|
+
tree.node.order_by = 'index';
|
|
47
|
+
const expected = tree.root.toObject();
|
|
48
|
+
|
|
49
|
+
expect(result).toMatchObject(expected);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
81
52
|
|
|
82
|
-
|
|
53
|
+
describe('includes', () => {
|
|
54
|
+
const queryStrings = [
|
|
55
|
+
// Simple includes.
|
|
56
|
+
'includes=comments&id=7',
|
|
57
|
+
// Include with All possible params.
|
|
58
|
+
'includes=comments(id=10&position=4&limit=3&skip=10&order=desc&order_by=index&fields=id,content,position)',
|
|
59
|
+
|
|
60
|
+
// 2 horizontals
|
|
61
|
+
'includes=comments,users&id=1000',
|
|
62
|
+
|
|
63
|
+
// Horizontals queried.
|
|
64
|
+
'includes=comments(order=desc),users,likes(order=rand),reposts&id=1000',
|
|
65
|
+
// Horizontals queried №2.
|
|
66
|
+
'in=reactions,comments(user_id=gte(4)&skip=10&limit=2).users,likes,reposts',
|
|
67
|
+
|
|
68
|
+
// Separated includes.
|
|
69
|
+
'includes=comments(order=rand)&id=7&limit=3&includes=users(fields=id,content)',
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
test('Simple includes', () => {
|
|
73
|
+
const lexer = new QueryLexer( queryStrings[0] );
|
|
74
|
+
const result = lexer.query;
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
const tree = new ModelsTree();
|
|
78
|
+
tree.node.addWhere({ id: ['7'] });
|
|
79
|
+
tree.include('comments');
|
|
80
|
+
const expected = tree.root.toObject();
|
|
81
|
+
|
|
82
|
+
expect(result).toMatchObject(expected);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('Include with all possible params', () => {
|
|
86
|
+
const lexer = new QueryLexer( queryStrings[1] );
|
|
87
|
+
const result = lexer.query;
|
|
88
|
+
|
|
89
|
+
const tree = new ModelsTree();
|
|
90
|
+
tree.include('comments').use('comments');
|
|
91
|
+
tree.node.addWhere({ id: ['10'], position: ['4'] });
|
|
92
|
+
tree.node.fields = [ 'id', 'content', 'position' ];
|
|
93
|
+
tree.node.limit = 3;
|
|
94
|
+
tree.node.skip = 10;
|
|
95
|
+
tree.node.order = 'desc';
|
|
96
|
+
tree.node.order_by = 'index';
|
|
97
|
+
const expected = tree.root.toObject();
|
|
98
|
+
|
|
99
|
+
expect(result).toMatchObject(expected);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('2 horizontals', () => {
|
|
103
|
+
const lexer = new QueryLexer( queryStrings[2] );
|
|
104
|
+
const result = lexer.query;
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
const tree = new ModelsTree();
|
|
108
|
+
tree.node.addWhere({ id: ['1000'] });
|
|
109
|
+
tree.include('comments');
|
|
110
|
+
tree.include('users');
|
|
111
|
+
const expected = tree.root.toObject();
|
|
112
|
+
|
|
113
|
+
expect(result).toMatchObject(expected);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('Horizontals queried', () => {
|
|
117
|
+
const lexer = new QueryLexer( queryStrings[3] );
|
|
118
|
+
const result = lexer.query;
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
const tree = new ModelsTree();
|
|
122
|
+
tree.node.addWhere({ id: ['1000'] });
|
|
123
|
+
tree.include('comments').use('comments');
|
|
124
|
+
tree.node.order = 'desc';
|
|
125
|
+
tree.up();
|
|
126
|
+
tree.include('users');
|
|
127
|
+
tree.include('likes') && tree.use('likes');
|
|
128
|
+
tree.node.order = 'rand';
|
|
129
|
+
tree.up();
|
|
130
|
+
tree.include('reposts');
|
|
131
|
+
const expected = tree.root.toObject();
|
|
132
|
+
|
|
133
|
+
expect(result).toMatchObject(expected);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('Horizontals queried №2', () => {
|
|
137
|
+
const lexer = new QueryLexer( queryStrings[4] );
|
|
138
|
+
const result = lexer.query;
|
|
139
|
+
|
|
140
|
+
const tree = new ModelsTree();
|
|
141
|
+
tree.include('reactions');
|
|
142
|
+
|
|
143
|
+
tree.include('comments').use('comments');
|
|
144
|
+
tree.node.addWhere({
|
|
145
|
+
user_id: {
|
|
146
|
+
gte: ['4']
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
tree.node.skip = 10;
|
|
150
|
+
tree.node.limit = 2;
|
|
151
|
+
|
|
152
|
+
tree.include('users');
|
|
153
|
+
tree.up();
|
|
154
|
+
|
|
155
|
+
tree.include('likes');
|
|
156
|
+
tree.include('reposts');
|
|
157
|
+
|
|
158
|
+
const expected = tree.root.toObject();
|
|
159
|
+
|
|
160
|
+
expect(result).toMatchObject(expected);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('Separated includes"', () => {
|
|
164
|
+
const lexer = new QueryLexer( queryStrings[5] );
|
|
165
|
+
const result = lexer.query;
|
|
166
|
+
|
|
167
|
+
const tree = new ModelsTree();
|
|
168
|
+
tree.node.addWhere({ id: ['7'] });
|
|
169
|
+
tree.node.limit = 3;
|
|
170
|
+
tree.include('comments').use('comments');
|
|
171
|
+
tree.node.order = 'rand';
|
|
172
|
+
tree.up();
|
|
173
|
+
tree.include('users').use('users');
|
|
174
|
+
tree.node.fields = [ 'id', 'content' ];
|
|
175
|
+
const expected = tree.root.toObject();
|
|
176
|
+
|
|
177
|
+
expect(result).toMatchObject(expected);
|
|
178
|
+
});
|
|
83
179
|
});
|
|
84
180
|
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
181
|
+
describe('subincludes', () => {
|
|
182
|
+
const queryStrings = [
|
|
183
|
+
// Simple subinclude.
|
|
184
|
+
'includes=comments.users',
|
|
88
185
|
|
|
186
|
+
// Deep subincludes.
|
|
187
|
+
'in=posts.comments.users.avatars.sizes&position=200',
|
|
89
188
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
tree.include('comments');
|
|
93
|
-
const expected = tree.root.toObject();
|
|
189
|
+
// Simple horizontal subinclude, "+" syntaxis.
|
|
190
|
+
'includes=comments.users+likes',
|
|
94
191
|
|
|
95
|
-
|
|
96
|
-
|
|
192
|
+
// Subinclude query.
|
|
193
|
+
'includes=comments.users(order=rand&order_by=position)',
|
|
97
194
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const tree = new ModelsTree();
|
|
103
|
-
tree.include('comments').use('comments');
|
|
104
|
-
tree.node.addWhere({ id: ['10'], position: ['4'] });
|
|
105
|
-
tree.node.fields = [ 'id', 'content', 'position' ];
|
|
106
|
-
tree.node.limit = 3;
|
|
107
|
-
tree.node.skip = 10;
|
|
108
|
-
tree.node.order = 'desc';
|
|
109
|
-
tree.node.order_by = 'index';
|
|
110
|
-
const expected = tree.root.toObject();
|
|
111
|
-
|
|
112
|
-
expect(result).toMatchObject(expected);
|
|
113
|
-
});
|
|
195
|
+
// Complex subincludes query, "+" syntaxis.
|
|
196
|
+
'includes=comments(order=desc).users+likes(order=rand&order_by=position)&id=1000',
|
|
197
|
+
];
|
|
114
198
|
|
|
199
|
+
test('Simple subinclude', () => {
|
|
200
|
+
const lexer = new QueryLexer( queryStrings[0] );
|
|
201
|
+
const result = lexer.query;
|
|
115
202
|
|
|
116
|
-
test('query "Subinclude horizontal"', () => {
|
|
117
|
-
const lexer = new QueryLexer( queryStrings[4] );
|
|
118
|
-
const result = lexer.query;
|
|
119
203
|
|
|
204
|
+
const tree = new ModelsTree();
|
|
205
|
+
tree.include('comments').use('comments');
|
|
206
|
+
tree.include('users');
|
|
207
|
+
const expected = tree.root.toObject();
|
|
120
208
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
tree.include('comments');
|
|
124
|
-
tree.include('users');
|
|
125
|
-
const expected = tree.root.toObject();
|
|
209
|
+
expect(result).toMatchObject(expected);
|
|
210
|
+
});
|
|
126
211
|
|
|
127
|
-
|
|
128
|
-
|
|
212
|
+
test('Deep subincludes', () => {
|
|
213
|
+
const lexer = new QueryLexer( queryStrings[1] );
|
|
214
|
+
const result = lexer.query;
|
|
129
215
|
|
|
130
|
-
test('query "Subinclude horizontal (complex)"', () => {
|
|
131
|
-
const lexer = new QueryLexer( queryStrings[5] );
|
|
132
|
-
const result = lexer.query;
|
|
133
216
|
|
|
217
|
+
const tree = new ModelsTree();
|
|
218
|
+
tree.include('posts').use('posts');
|
|
219
|
+
tree.include('comments').use('comments');
|
|
220
|
+
tree.include('users').use('users');
|
|
221
|
+
tree.include('avatars').use('avatars');
|
|
222
|
+
tree.include('sizes').use('sizes');
|
|
223
|
+
const expected = tree.root.toObject();
|
|
134
224
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
tree.include('comments').use('comments');
|
|
138
|
-
tree.node.order = 'desc';
|
|
139
|
-
tree.up();
|
|
140
|
-
tree.include('users');
|
|
141
|
-
tree.include('likes') && tree.use('likes');
|
|
142
|
-
tree.node.order = 'rand';
|
|
143
|
-
tree.up();
|
|
144
|
-
tree.include('reposts');
|
|
145
|
-
const expected = tree.root.toObject();
|
|
225
|
+
expect(result).toMatchObject(expected);
|
|
226
|
+
});
|
|
146
227
|
|
|
147
|
-
|
|
148
|
-
|
|
228
|
+
test('Simple horizontal subinclude, "+" syntaxis"', () => {
|
|
229
|
+
const lexer = new QueryLexer( queryStrings[2] );
|
|
230
|
+
const result = lexer.query;
|
|
149
231
|
|
|
150
232
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
233
|
+
const tree = new ModelsTree();
|
|
234
|
+
tree.include('comments').use('comments');
|
|
235
|
+
tree.include('users');
|
|
236
|
+
tree.include('likes');
|
|
237
|
+
const expected = tree.root.toObject();
|
|
154
238
|
|
|
239
|
+
expect(result).toMatchObject(expected);
|
|
240
|
+
});
|
|
155
241
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
tree.node.order = 'desc';
|
|
160
|
-
tree.include('users');
|
|
161
|
-
tree.include('likes') && tree.use('likes');
|
|
162
|
-
tree.node.order = 'rand';
|
|
163
|
-
tree.node.order_by = 'position';
|
|
164
|
-
tree.up();
|
|
165
|
-
const expected = tree.root.toObject();
|
|
242
|
+
test('Subinclude query', () => {
|
|
243
|
+
const lexer = new QueryLexer( queryStrings[3] );
|
|
244
|
+
const result = lexer.query;
|
|
166
245
|
|
|
167
|
-
expect(result).toMatchObject(expected);
|
|
168
|
-
});
|
|
169
246
|
|
|
247
|
+
const tree = new ModelsTree();
|
|
248
|
+
tree.include('comments').use('comments');
|
|
249
|
+
tree.include('users').use('users');
|
|
250
|
+
tree.node.order = 'rand';
|
|
251
|
+
tree.node.order_by = 'position';
|
|
252
|
+
const expected = tree.root.toObject();
|
|
253
|
+
|
|
254
|
+
expect(result).toMatchObject(expected);
|
|
255
|
+
});
|
|
170
256
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
257
|
+
test('Complex subincludes query, "+" syntaxis', () => {
|
|
258
|
+
const lexer = new QueryLexer( queryStrings[4] );
|
|
259
|
+
const result = lexer.query;
|
|
174
260
|
|
|
175
261
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
262
|
+
const tree = new ModelsTree();
|
|
263
|
+
tree.node.addWhere({ id: ['1000'] });
|
|
264
|
+
tree.include('comments').use('comments');
|
|
265
|
+
tree.node.order = 'desc';
|
|
266
|
+
tree.include('users');
|
|
267
|
+
tree.include('likes') && tree.use('likes');
|
|
268
|
+
tree.node.order = 'rand';
|
|
269
|
+
tree.node.order_by = 'position';
|
|
270
|
+
tree.up();
|
|
271
|
+
const expected = tree.root.toObject();
|
|
181
272
|
|
|
182
|
-
|
|
273
|
+
expect(result).toMatchObject(expected);
|
|
274
|
+
});
|
|
183
275
|
});
|
|
184
276
|
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
277
|
+
describe('operations', () => {
|
|
278
|
+
const queryStrings = [
|
|
279
|
+
// OR simple.
|
|
280
|
+
'or(index=2,position=5)',
|
|
281
|
+
// OR short.
|
|
282
|
+
'|(index=2,position=5)',
|
|
188
283
|
|
|
284
|
+
// Not simple.
|
|
285
|
+
'key=not(main)',
|
|
286
|
+
// Not short.
|
|
287
|
+
'key=!(main)',
|
|
288
|
+
// NOT inside include.
|
|
289
|
+
'includes=comments(id=not(7))',
|
|
189
290
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
tree.include('users').use('users');
|
|
194
|
-
tree.include('avatars').use('avatars');
|
|
195
|
-
tree.include('sizes').use('sizes');
|
|
196
|
-
const expected = tree.root.toObject();
|
|
291
|
+
// Like simple.
|
|
292
|
+
'title=like(some_text)',
|
|
293
|
+
];
|
|
197
294
|
|
|
198
|
-
|
|
199
|
-
|
|
295
|
+
test('"OR" simple', () => {
|
|
296
|
+
const lexer = new QueryLexer( queryStrings[0] );
|
|
297
|
+
const result = lexer.query;
|
|
200
298
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const tree = new ModelsTree();
|
|
206
|
-
tree.node.addWhere({ id: ['7'] });
|
|
207
|
-
tree.node.limit = 3;
|
|
208
|
-
tree.include('comments').use('comments');
|
|
209
|
-
tree.include('users').use('users');
|
|
210
|
-
tree.include('avatars').use('avatars');
|
|
211
|
-
tree.node.fields = [ 'id', 'content' ];
|
|
212
|
-
tree.node.order = 'rand';
|
|
213
|
-
const expected = tree.root.toObject();
|
|
214
|
-
|
|
215
|
-
expect(result).toMatchObject(expected);
|
|
216
|
-
});
|
|
299
|
+
const tree = new ModelsTree();
|
|
300
|
+
tree.node.addWhere({ or: [ { index: ['2'] }, { position: ['5'] } ] });
|
|
301
|
+
const expected = tree.root.toObject();
|
|
217
302
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const result = lexer.query;
|
|
221
|
-
|
|
222
|
-
const tree = new ModelsTree();
|
|
223
|
-
tree.node.addWhere({ id: ['7'] });
|
|
224
|
-
tree.node.limit = 3;
|
|
225
|
-
tree.include('comments').use('comments');
|
|
226
|
-
tree.node.order = 'rand';
|
|
227
|
-
tree.up();
|
|
228
|
-
tree.include('users').use('users');
|
|
229
|
-
tree.node.fields = [ 'id', 'content' ];
|
|
230
|
-
const expected = tree.root.toObject();
|
|
231
|
-
|
|
232
|
-
expect(result).toMatchObject(expected);
|
|
233
|
-
});
|
|
303
|
+
expect(result).toMatchObject(expected);
|
|
304
|
+
});
|
|
234
305
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
306
|
+
test('"OR" short', () => {
|
|
307
|
+
const lexer = new QueryLexer( queryStrings[1] );
|
|
308
|
+
const result = lexer.query;
|
|
238
309
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
310
|
+
const tree = new ModelsTree();
|
|
311
|
+
tree.node.addWhere({ or: [ { index: ['2'] }, { position: ['5'] } ] });
|
|
312
|
+
const expected = tree.root.toObject();
|
|
242
313
|
|
|
243
|
-
|
|
244
|
-
|
|
314
|
+
expect(result).toMatchObject(expected);
|
|
315
|
+
});
|
|
245
316
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
317
|
+
test('"NOT" simple', () => {
|
|
318
|
+
const lexer = new QueryLexer( queryStrings[2] );
|
|
319
|
+
const result = lexer.query;
|
|
249
320
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
321
|
+
const tree = new ModelsTree();
|
|
322
|
+
tree.node.addWhere({ key: { not: ['main'] } });
|
|
323
|
+
const expected = tree.root.toObject();
|
|
253
324
|
|
|
254
|
-
|
|
255
|
-
|
|
325
|
+
expect(result).toMatchObject(expected);
|
|
326
|
+
});
|
|
256
327
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
328
|
+
test('"NOT" short', () => {
|
|
329
|
+
const lexer = new QueryLexer( queryStrings[3] );
|
|
330
|
+
const result = lexer.query;
|
|
260
331
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const expected = tree.root.toObject();
|
|
332
|
+
const tree = new ModelsTree();
|
|
333
|
+
tree.node.addWhere({ key: { not: ['main'] } });
|
|
334
|
+
const expected = tree.root.toObject();
|
|
265
335
|
|
|
266
|
-
|
|
267
|
-
|
|
336
|
+
expect(result).toMatchObject(expected);
|
|
337
|
+
});
|
|
268
338
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
339
|
+
test('"NOT" inside includes', () => {
|
|
340
|
+
const lexer = new QueryLexer( queryStrings[4] );
|
|
341
|
+
const result = lexer.query;
|
|
272
342
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
343
|
+
const tree = new ModelsTree();
|
|
344
|
+
tree.include('comments').use('comments');
|
|
345
|
+
tree.node.addWhere({ id: { not: ['7'] }});
|
|
346
|
+
const expected = tree.root.toObject();
|
|
276
347
|
|
|
277
|
-
|
|
278
|
-
|
|
348
|
+
expect(result).toMatchObject(expected);
|
|
349
|
+
});
|
|
279
350
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
351
|
+
test('"Like" simple', () => {
|
|
352
|
+
const lexer = new QueryLexer( queryStrings[5] );
|
|
353
|
+
const result = lexer.query;
|
|
283
354
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
tree.up();
|
|
288
|
-
tree.include('likes');
|
|
289
|
-
const expected = tree.root.toObject();
|
|
355
|
+
const tree = new ModelsTree();
|
|
356
|
+
tree.node.addWhere({ title: { like: ['some_text'] }});
|
|
357
|
+
const expected = tree.root.toObject();
|
|
290
358
|
|
|
291
|
-
|
|
359
|
+
expect(result).toMatchObject(expected);
|
|
360
|
+
});
|
|
292
361
|
});
|
|
293
362
|
|
|
294
363
|
});
|
|
File without changes
|