nodester 0.2.4 → 0.2.5
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/Readme.md +4 -4
- package/lib/factories/responses/rest.js +7 -11
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +57 -10
- package/lib/query/traverse.js +3 -10
- package/lib/tools/nql.tool.js +34 -21
- package/package.json +1 -1
- package/tests/ast.js +4 -2
- package/tests/nql.test.js +164 -15
package/Readme.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/nodester)
|
|
4
4
|
[](https://www.npmjs.com/package/nodester)
|
|
5
5
|
|
|
6
|
-
> **nodester** is a
|
|
6
|
+
> **nodester** is a Node.js framework designed to solve the problem of a complex data querying over HTTP.
|
|
7
7
|
|
|
8
|
-
The main reason of nodester's existence is the [nodester Query Language (NQL)](docs/
|
|
8
|
+
The main reason of nodester's existence is the [nodester Query Language (NQL)](docs/nql/Introduction.md), an extension of standard REST API syntax, it lets you craft complex queries with hierarchical associations.
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
@@ -49,11 +49,11 @@ app.listen(8080, function() {
|
|
|
49
49
|
[Core concepts documentation ➡️](docs/CoreConcepts.md)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
### Queries & Querying -
|
|
52
|
+
### Queries & Querying - nodester Query Language (NQL)
|
|
53
53
|
The true strength of nodester lies in its query language. Serving as an extension of standard REST API syntax, it brings many aspects of SQL into REST requests, providing developers with a simple yet potent tool for expressive and efficient data querying.
|
|
54
54
|
|
|
55
55
|
Read more about it in the documentation:
|
|
56
|
-
[NQL documentaion ➡️](docs/
|
|
56
|
+
[NQL documentaion ➡️](docs/nql/Introduction.md)
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
### Database
|
|
@@ -62,29 +62,27 @@ function _createGenericResponse(res, options) {
|
|
|
62
62
|
|
|
63
63
|
switch(error.name) {
|
|
64
64
|
case 'Unauthorized': {
|
|
65
|
-
|
|
65
|
+
status = 401;
|
|
66
66
|
break;
|
|
67
67
|
}
|
|
68
68
|
case 'NotFound': {
|
|
69
|
-
|
|
69
|
+
status = 404;
|
|
70
70
|
break;
|
|
71
71
|
}
|
|
72
72
|
case 'ValidationError': {
|
|
73
|
-
|
|
73
|
+
status = 422;
|
|
74
74
|
break;
|
|
75
75
|
}
|
|
76
76
|
case 'ConflictError': {
|
|
77
|
-
|
|
77
|
+
status = 409;
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
80
80
|
case 'SequelizeUniqueConstraintError': {
|
|
81
|
-
|
|
81
|
+
status = 409;
|
|
82
82
|
details.errors = error?.errors;
|
|
83
83
|
break;
|
|
84
84
|
}
|
|
85
85
|
default:
|
|
86
|
-
statusCode = status;
|
|
87
|
-
|
|
88
86
|
if (!!error?.errors) {
|
|
89
87
|
details.errors = error?.errors;
|
|
90
88
|
}
|
|
@@ -121,8 +119,7 @@ function _createGenericResponse(res, options) {
|
|
|
121
119
|
* @api public
|
|
122
120
|
*/
|
|
123
121
|
function _createOKResponse(res, options={}) {
|
|
124
|
-
|
|
125
|
-
return this.createGenericResponse(res, {
|
|
122
|
+
return _createGenericResponse(res, {
|
|
126
123
|
...options,
|
|
127
124
|
status: options?.status ?? 200,
|
|
128
125
|
});
|
|
@@ -143,8 +140,7 @@ function _createOKResponse(res, options={}) {
|
|
|
143
140
|
* @api public
|
|
144
141
|
*/
|
|
145
142
|
function _createErrorResponse(res, options) {
|
|
146
|
-
|
|
147
|
-
return this.createGenericResponse(res, {
|
|
143
|
+
return _createGenericResponse(res, {
|
|
148
144
|
...options,
|
|
149
145
|
status: options?.status ?? 500,
|
|
150
146
|
});
|
|
@@ -30,8 +30,14 @@ const OP_TOKENS = new Enum({
|
|
|
30
30
|
XOR: 'xor',
|
|
31
31
|
NOT: 'not',
|
|
32
32
|
NOT_MARK: '!',
|
|
33
|
+
|
|
34
|
+
IN: 'in',
|
|
33
35
|
NOT_IN: 'notIn',
|
|
36
|
+
|
|
34
37
|
LIKE: 'like',
|
|
38
|
+
NOT_LIKE: 'notLike',
|
|
39
|
+
NOT_LIKE_SHORT: '!like',
|
|
40
|
+
|
|
35
41
|
GREATER: 'gt',
|
|
36
42
|
GREATER_OR_EQUAL: 'gte',
|
|
37
43
|
LOWER: 'lt',
|
|
@@ -86,7 +92,7 @@ module.exports = class QueryLexer {
|
|
|
86
92
|
|
|
87
93
|
// If OP token:
|
|
88
94
|
if (OP_TOKENS.asArray.indexOf(token) > -1) {
|
|
89
|
-
// Set
|
|
95
|
+
// Set operator token.
|
|
90
96
|
tree.node.op = this.parseOP(token);
|
|
91
97
|
token = '';
|
|
92
98
|
continue;
|
|
@@ -135,6 +141,7 @@ module.exports = class QueryLexer {
|
|
|
135
141
|
switch (tree.node.op) {
|
|
136
142
|
case OP_TOKENS.NOT:
|
|
137
143
|
case OP_TOKENS.LIKE:
|
|
144
|
+
case OP_TOKENS.NOT_LIKE:
|
|
138
145
|
case OP_TOKENS.GREATER:
|
|
139
146
|
case OP_TOKENS.GREATER_OR_EQUAL:
|
|
140
147
|
case OP_TOKENS.LOWER:
|
|
@@ -214,9 +221,19 @@ module.exports = class QueryLexer {
|
|
|
214
221
|
|
|
215
222
|
// If OP token:
|
|
216
223
|
if (!!tree.node.op) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
224
|
+
switch(tree.node.op) {
|
|
225
|
+
case OP_TOKENS.NOT_IN:
|
|
226
|
+
case OP_TOKENS.IN:
|
|
227
|
+
value.push(token);
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
value.push({
|
|
231
|
+
[tree.node.activeParam]: [token]
|
|
232
|
+
});
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Reset.
|
|
220
237
|
token = '';
|
|
221
238
|
continue;
|
|
222
239
|
}
|
|
@@ -374,27 +391,51 @@ module.exports = class QueryLexer {
|
|
|
374
391
|
throw err;
|
|
375
392
|
}
|
|
376
393
|
|
|
377
|
-
// [ can
|
|
394
|
+
// [ can mean start of 'in'/'notIn',
|
|
395
|
+
// or 'notIn':
|
|
378
396
|
if (char === '[') {
|
|
379
|
-
tree.node.op =
|
|
397
|
+
tree.node.op = OP_TOKENS.IN;
|
|
398
|
+
if (token.length > 0) {
|
|
399
|
+
if (token === '!' || token === 'not') {
|
|
400
|
+
tree.node.op = OP_TOKENS.NOT_IN;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
const err = UnexpectedCharError(i - token.length, token);
|
|
404
|
+
throw err;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Reset:
|
|
409
|
+
token = '';
|
|
380
410
|
continue;
|
|
381
411
|
}
|
|
382
412
|
|
|
383
|
-
// ] can
|
|
413
|
+
// ] can mean end of 'in'/'notIn':
|
|
384
414
|
if (char === ']') {
|
|
385
415
|
// User missed first '[' :
|
|
386
|
-
if (
|
|
416
|
+
if (
|
|
417
|
+
tree.node.op !== OP_TOKENS.IN
|
|
418
|
+
&&
|
|
419
|
+
tree.node.op !== OP_TOKENS.NOT_IN
|
|
420
|
+
) {
|
|
387
421
|
const err = UnexpectedCharError(i, char);
|
|
388
422
|
throw err;
|
|
389
423
|
}
|
|
390
424
|
|
|
425
|
+
// Token is the last element in this array:
|
|
426
|
+
if (token.length > 0) {
|
|
427
|
+
value.push(token);
|
|
428
|
+
}
|
|
429
|
+
|
|
391
430
|
tree.node.addWhere({
|
|
392
431
|
[tree.node.activeParam]: {
|
|
393
432
|
[tree.node.op]: value
|
|
394
433
|
}
|
|
395
434
|
});
|
|
435
|
+
|
|
396
436
|
// Reset:
|
|
397
437
|
tree.node.resetOP();
|
|
438
|
+
tree.node.resetActiveParam();
|
|
398
439
|
value = [];
|
|
399
440
|
token = '';
|
|
400
441
|
continue;
|
|
@@ -521,10 +562,16 @@ module.exports = class QueryLexer {
|
|
|
521
562
|
switch(opToken) {
|
|
522
563
|
case '|':
|
|
523
564
|
case 'or':
|
|
524
|
-
return
|
|
565
|
+
return OP_TOKENS.OR;
|
|
566
|
+
|
|
567
|
+
case '!like':
|
|
568
|
+
case 'notLike':
|
|
569
|
+
return OP_TOKENS.NOT_LIKE;
|
|
570
|
+
|
|
525
571
|
case 'not':
|
|
526
572
|
case '!':
|
|
527
|
-
return
|
|
573
|
+
return OP_TOKENS.NOT;
|
|
574
|
+
|
|
528
575
|
default:
|
|
529
576
|
return opToken;
|
|
530
577
|
}
|
package/lib/query/traverse.js
CHANGED
|
@@ -397,17 +397,10 @@ function _disassembleQueryNode(queryNode) {
|
|
|
397
397
|
function _parseValue(value, attribute) {
|
|
398
398
|
// If value is Object:
|
|
399
399
|
if (typeof value === 'object' && Array.isArray(value) === false) {
|
|
400
|
-
const [opKey, rawValue] = (Object.entries(value))[0];
|
|
400
|
+
const [ opKey, rawValue ] = (Object.entries(value))[0];
|
|
401
401
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
// Unwrap rawValue.
|
|
405
|
-
return rawValue[0][attribute];
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
const op = Op[opKey];
|
|
409
|
-
return { [op]: rawValue };
|
|
410
|
-
}
|
|
402
|
+
const op = Op[opKey];
|
|
403
|
+
return { [op]: rawValue };
|
|
411
404
|
}
|
|
412
405
|
|
|
413
406
|
return value;
|
package/lib/tools/nql.tool.js
CHANGED
|
@@ -5,41 +5,54 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
// Arguments validator.
|
|
9
|
+
const { ensure } = require('nodester/validators/arguments');
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
module.exports = {
|
|
10
|
-
|
|
13
|
+
AST_ModelsTree: _AST_ModelsTree,
|
|
14
|
+
AST_ModelsTreeNode: _AST_ModelsTreeNode
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function _AST_ModelsTree(modelsTree) {
|
|
18
|
+
ensure(modelsTree, 'object,required', 'modelsTree');
|
|
19
|
+
return _AST_ModelsTreeNode(modelsTree.root);
|
|
11
20
|
}
|
|
12
21
|
|
|
13
|
-
function
|
|
14
|
-
|
|
22
|
+
function _AST_ModelsTreeNode(node, spacing=0) {
|
|
23
|
+
ensure(node, 'object,required', 'node');
|
|
24
|
+
ensure(spacing, 'number,required', 'spacing');
|
|
25
|
+
|
|
26
|
+
let spaces = ' ';
|
|
15
27
|
for (let i = 0; i < spacing; i++) {
|
|
16
28
|
spaces += ' ';
|
|
17
29
|
}
|
|
18
30
|
|
|
19
|
-
let ast = `${ spaces }
|
|
20
|
-
|
|
21
|
-
spaces += ' ';
|
|
31
|
+
let ast = `${ spaces }┏ TreeNode\n`;
|
|
32
|
+
ast += `${ spaces }┃\n`;
|
|
22
33
|
|
|
23
|
-
ast += `${ spaces }model: ${ node.model }\n
|
|
34
|
+
ast += `${ spaces }┣ model: ${ node.model }\n`;
|
|
35
|
+
ast += `${ spaces }┃\n`;
|
|
24
36
|
|
|
25
|
-
ast += `${ spaces }fields (${ node.fields.length }): [\n${ node.fields.map(f => ` • ${ f },\n`) }`;
|
|
26
|
-
ast += `${ spaces }]\n
|
|
37
|
+
ast += `${ spaces }┣ fields (${ node.fields.length }): [\n${ node.fields.map(f => ` • ${ f },\n`) }`;
|
|
38
|
+
ast += `${ spaces }┃ ]\n`;
|
|
39
|
+
ast += `${ spaces }┃\n`;
|
|
27
40
|
|
|
28
|
-
ast += `${ spaces }functions (${ node.functions.length }): [\n${ node.functions.map(f => ` • ${ f },\n`) }`;
|
|
29
|
-
ast += `${ spaces }]\n
|
|
41
|
+
ast += `${ spaces }┣ functions (${ node.functions.length }): [\n${ node.functions.map(f => ` • ${ f },\n`) }`;
|
|
42
|
+
ast += `${ spaces }┃ ]\n`;
|
|
43
|
+
ast += `${ spaces }┃\n`;
|
|
30
44
|
|
|
31
|
-
ast += `${ spaces }where: ${ JSON.stringify(node.where) }\n
|
|
45
|
+
ast += `${ spaces }┣ where: ${ JSON.stringify(node.where) }\n`;
|
|
46
|
+
ast += `${ spaces }┃\n`;
|
|
32
47
|
|
|
33
|
-
['skip','limit','order','order_by'].map(
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
['skip','limit','order','order_by'].map(c => {
|
|
49
|
+
ast += `${ spaces }┣ ${ c }: ${ node[c] }\n`;
|
|
50
|
+
ast += `${ spaces }┃\n`;
|
|
51
|
+
});
|
|
36
52
|
|
|
37
|
-
ast += `${ spaces }includes (${ node.includes.length }): [\n`
|
|
38
|
-
node.includes.map(n => ast +=
|
|
39
|
-
ast += `${ spaces }]\n`;
|
|
40
|
-
|
|
41
|
-
spaces.slice(-1);
|
|
42
|
-
ast += `${ spaces }[TreeNode END]\n\n`;
|
|
53
|
+
ast += `${ spaces }┗ includes (${ node.includes.length }): [\n`
|
|
54
|
+
node.includes.map(n => ast += _AST_ModelsTreeNode(n, spacing + 2));
|
|
55
|
+
ast += `${ spaces } ]\n`;
|
|
43
56
|
|
|
44
57
|
return ast;
|
|
45
58
|
}
|
package/package.json
CHANGED
package/tests/ast.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { ModelsTree } = require('../lib/middlewares/ql/sequelize/interpreter/ModelsTree');
|
|
2
|
-
const {
|
|
2
|
+
const { AST_ModelsTree } = require('../lib/tools/nql.tool');
|
|
3
3
|
|
|
4
4
|
const tree = new ModelsTree();
|
|
5
5
|
tree.node.addWhere({ id: ['1000'] });
|
|
@@ -12,5 +12,7 @@ tree.node.order = 'rand';
|
|
|
12
12
|
tree.up();
|
|
13
13
|
tree.include('reposts');
|
|
14
14
|
|
|
15
|
-
console.debug(
|
|
15
|
+
console.debug(
|
|
16
|
+
AST_ModelsTree(tree)
|
|
17
|
+
);
|
|
16
18
|
|
package/tests/nql.test.js
CHANGED
|
@@ -274,22 +274,12 @@ describe('nodester Query Language', () => {
|
|
|
274
274
|
});
|
|
275
275
|
});
|
|
276
276
|
|
|
277
|
-
describe('
|
|
277
|
+
describe('operators:or', () => {
|
|
278
278
|
const queryStrings = [
|
|
279
279
|
// OR simple.
|
|
280
280
|
'or(index=2,position=5)',
|
|
281
281
|
// OR short.
|
|
282
282
|
'|(index=2,position=5)',
|
|
283
|
-
|
|
284
|
-
// Not simple.
|
|
285
|
-
'key=not(main)',
|
|
286
|
-
// Not short.
|
|
287
|
-
'key=!(main)',
|
|
288
|
-
// NOT inside include.
|
|
289
|
-
'includes=comments(id=not(7))',
|
|
290
|
-
|
|
291
|
-
// Like simple.
|
|
292
|
-
'title=like(some_text)',
|
|
293
283
|
];
|
|
294
284
|
|
|
295
285
|
test('"OR" simple', () => {
|
|
@@ -313,9 +303,20 @@ describe('nodester Query Language', () => {
|
|
|
313
303
|
|
|
314
304
|
expect(result).toMatchObject(expected);
|
|
315
305
|
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('operators:not', () => {
|
|
309
|
+
const queryStrings = [
|
|
310
|
+
// Not simple.
|
|
311
|
+
'key=not(main)',
|
|
312
|
+
// Not short.
|
|
313
|
+
'key=!(main)',
|
|
314
|
+
// NOT inside include.
|
|
315
|
+
'includes=comments(id=not(7))'
|
|
316
|
+
];
|
|
316
317
|
|
|
317
318
|
test('"NOT" simple', () => {
|
|
318
|
-
const lexer = new QueryLexer( queryStrings[
|
|
319
|
+
const lexer = new QueryLexer( queryStrings[0] );
|
|
319
320
|
const result = lexer.query;
|
|
320
321
|
|
|
321
322
|
const tree = new ModelsTree();
|
|
@@ -326,7 +327,7 @@ describe('nodester Query Language', () => {
|
|
|
326
327
|
});
|
|
327
328
|
|
|
328
329
|
test('"NOT" short', () => {
|
|
329
|
-
const lexer = new QueryLexer( queryStrings[
|
|
330
|
+
const lexer = new QueryLexer( queryStrings[1] );
|
|
330
331
|
const result = lexer.query;
|
|
331
332
|
|
|
332
333
|
const tree = new ModelsTree();
|
|
@@ -337,7 +338,7 @@ describe('nodester Query Language', () => {
|
|
|
337
338
|
});
|
|
338
339
|
|
|
339
340
|
test('"NOT" inside includes', () => {
|
|
340
|
-
const lexer = new QueryLexer( queryStrings[
|
|
341
|
+
const lexer = new QueryLexer( queryStrings[2] );
|
|
341
342
|
const result = lexer.query;
|
|
342
343
|
|
|
343
344
|
const tree = new ModelsTree();
|
|
@@ -348,8 +349,21 @@ describe('nodester Query Language', () => {
|
|
|
348
349
|
expect(result).toMatchObject(expected);
|
|
349
350
|
});
|
|
350
351
|
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('operators:like', () => {
|
|
355
|
+
const queryStrings = [
|
|
356
|
+
// Like simple.
|
|
357
|
+
'title=like(some_text)',
|
|
358
|
+
|
|
359
|
+
// Not like simple.
|
|
360
|
+
'title=notLike(some_text)',
|
|
361
|
+
// Not like short.
|
|
362
|
+
'title=!like(some_text)',
|
|
363
|
+
];
|
|
364
|
+
|
|
351
365
|
test('"Like" simple', () => {
|
|
352
|
-
const lexer = new QueryLexer( queryStrings[
|
|
366
|
+
const lexer = new QueryLexer( queryStrings[0] );
|
|
353
367
|
const result = lexer.query;
|
|
354
368
|
|
|
355
369
|
const tree = new ModelsTree();
|
|
@@ -358,6 +372,141 @@ describe('nodester Query Language', () => {
|
|
|
358
372
|
|
|
359
373
|
expect(result).toMatchObject(expected);
|
|
360
374
|
});
|
|
375
|
+
|
|
376
|
+
test('"NotLike" simple', () => {
|
|
377
|
+
const lexer = new QueryLexer( queryStrings[1] );
|
|
378
|
+
const result = lexer.query;
|
|
379
|
+
|
|
380
|
+
const tree = new ModelsTree();
|
|
381
|
+
tree.node.addWhere({ title: { notLike: ['some_text'] }});
|
|
382
|
+
const expected = tree.root.toObject();
|
|
383
|
+
|
|
384
|
+
expect(result).toMatchObject(expected);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test('"NotLike" short', () => {
|
|
388
|
+
const lexer = new QueryLexer( queryStrings[2] );
|
|
389
|
+
const result = lexer.query;
|
|
390
|
+
|
|
391
|
+
const tree = new ModelsTree();
|
|
392
|
+
tree.node.addWhere({ title: { notLike: ['some_text'] }});
|
|
393
|
+
const expected = tree.root.toObject();
|
|
394
|
+
|
|
395
|
+
expect(result).toMatchObject(expected);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('operators:in', () => {
|
|
400
|
+
const queryStrings = [
|
|
401
|
+
// IN simple.
|
|
402
|
+
'status=[REVIEWED,ANSWERED]',
|
|
403
|
+
|
|
404
|
+
// IN and limit clause.
|
|
405
|
+
'status=[REVIEWED,ANSWERED]&limit=3',
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
test('"IN" simple', () => {
|
|
409
|
+
const lexer = new QueryLexer( queryStrings[0] );
|
|
410
|
+
const result = lexer.query;
|
|
411
|
+
|
|
412
|
+
const tree = new ModelsTree();
|
|
413
|
+
tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] }});
|
|
414
|
+
const expected = tree.root.toObject();
|
|
415
|
+
|
|
416
|
+
expect(result).toMatchObject(expected);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('"IN" and "limit" clause', () => {
|
|
420
|
+
const lexer = new QueryLexer( queryStrings[1] );
|
|
421
|
+
const result = lexer.query;
|
|
422
|
+
|
|
423
|
+
const tree = new ModelsTree();
|
|
424
|
+
tree.node.limit = 3;
|
|
425
|
+
tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] }});
|
|
426
|
+
const expected = tree.root.toObject();
|
|
427
|
+
|
|
428
|
+
expect(result).toMatchObject(expected);
|
|
429
|
+
});
|
|
361
430
|
});
|
|
362
431
|
|
|
432
|
+
describe('operators:inequality', () => {
|
|
433
|
+
const queryStrings = [
|
|
434
|
+
// Greater than.
|
|
435
|
+
'created_at=gt(2022)',
|
|
436
|
+
|
|
437
|
+
// Greater than or equal to.
|
|
438
|
+
'created_at=gte(2023-12-08)',
|
|
439
|
+
|
|
440
|
+
// Lower than.
|
|
441
|
+
'index=lt(10)',
|
|
442
|
+
|
|
443
|
+
// Lower than or equal to.
|
|
444
|
+
'index=lte(9)',
|
|
445
|
+
|
|
446
|
+
// Greater than in subinclude.
|
|
447
|
+
'in=comments.likes(index=gt(60))'
|
|
448
|
+
];
|
|
449
|
+
|
|
450
|
+
test('Greater than', () => {
|
|
451
|
+
const lexer = new QueryLexer( queryStrings[0] );
|
|
452
|
+
const result = lexer.query;
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
const tree = new ModelsTree();
|
|
456
|
+
tree.node.addWhere({ created_at: { gt: ['2022'] }});
|
|
457
|
+
const expected = tree.root.toObject();
|
|
458
|
+
|
|
459
|
+
expect(result).toMatchObject(expected);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('Greater than or equal to', () => {
|
|
463
|
+
const lexer = new QueryLexer( queryStrings[1] );
|
|
464
|
+
const result = lexer.query;
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
const tree = new ModelsTree();
|
|
468
|
+
tree.node.addWhere({ created_at: { gte: ['2023-12-08'] }});
|
|
469
|
+
const expected = tree.root.toObject();
|
|
470
|
+
|
|
471
|
+
expect(result).toMatchObject(expected);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test('Lower than', () => {
|
|
475
|
+
const lexer = new QueryLexer( queryStrings[2] );
|
|
476
|
+
const result = lexer.query;
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
const tree = new ModelsTree();
|
|
480
|
+
tree.node.addWhere({ index: { lt: ['10'] }});
|
|
481
|
+
const expected = tree.root.toObject();
|
|
482
|
+
|
|
483
|
+
expect(result).toMatchObject(expected);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('Lower than or equal to', () => {
|
|
487
|
+
const lexer = new QueryLexer( queryStrings[3] );
|
|
488
|
+
const result = lexer.query;
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
const tree = new ModelsTree();
|
|
492
|
+
tree.node.addWhere({ index: { lte: ['9'] }});
|
|
493
|
+
const expected = tree.root.toObject();
|
|
494
|
+
|
|
495
|
+
expect(result).toMatchObject(expected);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test('Greater than in subinclude', () => {
|
|
499
|
+
const lexer = new QueryLexer( queryStrings[4] );
|
|
500
|
+
const result = lexer.query;
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
const tree = new ModelsTree();
|
|
504
|
+
tree.include('comments').use('comments');
|
|
505
|
+
tree.include('likes').use('likes');
|
|
506
|
+
tree.node.addWhere({ index: { gt: ['60'] }});
|
|
507
|
+
const expected = tree.root.toObject();
|
|
508
|
+
|
|
509
|
+
expect(result).toMatchObject(expected);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
363
512
|
});
|