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 = 'includes';
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 = 'includes';
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 !== 'includes') {
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 !== 'includes') {
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 === 'includes') {
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 = 'includes';
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 = 'includes';
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 = 'includes';
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 = 'includes';
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 = 'includes';
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 !== 'includes') {
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 === 'includes') {
325
- // If include of new model:
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 === 'includes') {
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 'limit';
453
+ return PARAM_TOKENS.LIMIT;
454
+
437
455
  case 'skip':
438
456
  case 's':
439
457
  case 'offset':
440
- return 'skip';
458
+ return PARAM_TOKENS.SKIP;
459
+
441
460
  case 'order':
442
461
  case 'o':
443
- return 'order';
462
+ return PARAM_TOKENS.ORDER;
463
+
444
464
  case 'order_by':
445
465
  case 'o_by':
446
- return 'order_by';
466
+ return PARAM_TOKENS.ORDER_BY;
467
+
447
468
  case 'fields':
448
469
  case 'f':
449
- return 'fields';
470
+ return PARAM_TOKENS.FIELDS;
471
+
450
472
  case 'includes':
451
473
  case 'in':
452
- return 'includes';
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 ${ param }`, { token, value });
484
+ debug(`set param`, { param, token, value });
462
485
 
463
486
  switch(param) {
464
- case 'limit':
487
+ case PARAM_TOKENS.LIMIT:
465
488
  treeNode.limit = parseInt(token);
466
489
  break;
467
- case 'skip':
468
- case 'offset':
490
+
491
+ case PARAM_TOKENS.SKIP:
469
492
  treeNode.skip = parseInt(token);
470
493
  break;
471
- case 'order':
494
+
495
+ case PARAM_TOKENS.ORDER:
472
496
  treeNode.order = token;
473
497
  break;
474
- case 'order_by':
498
+
499
+ case PARAM_TOKENS.ORDER_BY:
475
500
  treeNode.order_by = token;
476
501
  break;
477
- case 'fields':
502
+
503
+ case PARAM_TOKENS.FIELDS:
478
504
  if (token) value.push(token);
479
505
  treeNode.fields = value;
480
506
  break;
481
- case 'includes':
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 });
@@ -102,49 +102,57 @@ function traverse(queryNode, filter=null, model=null) {
102
102
  // Functions:
103
103
  for (const fnParams of functions) {
104
104
 
105
- // If COUNT() is requested:
106
- if (fnParams.fn === 'count') {
107
- const countParams = fnParams.args;
108
-
109
- const [ countTarget ] = countParams;
110
- // Count can be requested for this model,
111
- // or for any of the available uncludes.
112
- const isForRootModel = countTarget === rootModelName.plural.toLowerCase();
113
-
114
- // Compile request:
115
- // Example:
116
- // `(SELECT COUNT(*) FROM comments WHERE comments.morph_id=Morph.id)`
117
-
118
- // Params for attribute:
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 (
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
- const err = new NodesterQueryError(`Count for '${ countTarget }' is not available.`);
133
- Error.captureStackTrace(err, traverse);
134
- throw err;
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
- const {
138
- foreignKey,
139
- sourceKey
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
- newQuery.attributes.push(
146
- [sequelize.literal(rawSQL), countAttribute]
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" override or set any query in clauses:
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 ( ['rand', 'random'].indexOf(order.order) > -1) {
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
- function _traverseIncludes(includes, model, filter, resultQuery) {
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 = model.associations[includeName];
321
+ const association = rootModel.associations[includeName];
300
322
 
301
323
  // If no such association:
302
324
  if (!association) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodester",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "A versatile REST framework for Node.js",
5
5
  "exports": {
6
6
  ".": "./lib/application/index.js",
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
- const tree = new ModelsTree();
74
- tree.node.addWhere({ id: ['10'], position: ['4'] });
75
- tree.node.fields = [ 'id', 'content', 'position', 'created_at' ];
76
- tree.node.limit = 3;
77
- tree.node.skip = 10;
78
- tree.node.order = 'desc';
79
- tree.node.order_by = 'index';
80
- const expected = tree.root.toObject();
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
- expect(result).toMatchObject(expected);
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
- test('query "Simple includes"', () => {
86
- const lexer = new QueryLexer( queryStrings[2] );
87
- const result = lexer.query;
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
- const tree = new ModelsTree();
91
- tree.node.addWhere({ id: ['7'] });
92
- tree.include('comments');
93
- const expected = tree.root.toObject();
189
+ // Simple horizontal subinclude, "+" syntaxis.
190
+ 'includes=comments.users+likes',
94
191
 
95
- expect(result).toMatchObject(expected);
96
- });
192
+ // Subinclude query.
193
+ 'includes=comments.users(order=rand&order_by=position)',
97
194
 
98
- test('query "Include with all possible params"', () => {
99
- const lexer = new QueryLexer( queryStrings[3] );
100
- const result = lexer.query;
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
- const tree = new ModelsTree();
122
- tree.node.addWhere({ id: ['1000'] });
123
- tree.include('comments');
124
- tree.include('users');
125
- const expected = tree.root.toObject();
209
+ expect(result).toMatchObject(expected);
210
+ });
126
211
 
127
- expect(result).toMatchObject(expected);
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
- const tree = new ModelsTree();
136
- tree.node.addWhere({ id: ['1000'] });
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
- expect(result).toMatchObject(expected);
148
- });
228
+ test('Simple horizontal subinclude, "+" syntaxis"', () => {
229
+ const lexer = new QueryLexer( queryStrings[2] );
230
+ const result = lexer.query;
149
231
 
150
232
 
151
- test('query "Subinclude horizontal (+ syntaxis)"', () => {
152
- const lexer = new QueryLexer( queryStrings[6] );
153
- const result = lexer.query;
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
- const tree = new ModelsTree();
157
- tree.node.addWhere({ id: ['1000'] });
158
- tree.include('comments').use('comments');
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
- test('query "Subinclude vertical"', () => {
172
- const lexer = new QueryLexer( queryStrings[7] );
173
- const result = lexer.query;
257
+ test('Complex subincludes query, "+" syntaxis', () => {
258
+ const lexer = new QueryLexer( queryStrings[4] );
259
+ const result = lexer.query;
174
260
 
175
261
 
176
- const tree = new ModelsTree();
177
- tree.node.addWhere({ id: ['1000'] });
178
- tree.include('comments').use('comments');
179
- tree.include('users');
180
- const expected = tree.root.toObject();
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
- expect(result).toMatchObject(expected);
273
+ expect(result).toMatchObject(expected);
274
+ });
183
275
  });
184
276
 
185
- test('query "Subinclude vertical (complex)"', () => {
186
- const lexer = new QueryLexer( queryStrings[8] );
187
- const result = lexer.query;
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
- const tree = new ModelsTree();
191
- tree.node.addWhere({ position: ['200'] });
192
- tree.include('comments').use('comments');
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
- expect(result).toMatchObject(expected);
199
- });
295
+ test('"OR" simple', () => {
296
+ const lexer = new QueryLexer( queryStrings[0] );
297
+ const result = lexer.query;
200
298
 
201
- test('query "Complex includes"', () => {
202
- const lexer = new QueryLexer( queryStrings[9] );
203
- const result = lexer.query;
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
- test('query "Broken includes"', () => {
219
- const lexer = new QueryLexer( queryStrings[10] );
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
- test('Token "OR" simple', () => {
236
- const lexer = new QueryLexer( queryStrings[11] );
237
- const result = lexer.query;
306
+ test('"OR" short', () => {
307
+ const lexer = new QueryLexer( queryStrings[1] );
308
+ const result = lexer.query;
238
309
 
239
- const tree = new ModelsTree();
240
- tree.node.addWhere({ or: [ { index: ['2'] }, { position: ['5'] } ] });
241
- const expected = tree.root.toObject();
310
+ const tree = new ModelsTree();
311
+ tree.node.addWhere({ or: [ { index: ['2'] }, { position: ['5'] } ] });
312
+ const expected = tree.root.toObject();
242
313
 
243
- expect(result).toMatchObject(expected);
244
- });
314
+ expect(result).toMatchObject(expected);
315
+ });
245
316
 
246
- test('Token "OR" shortened', () => {
247
- const lexer = new QueryLexer( queryStrings[12] );
248
- const result = lexer.query;
317
+ test('"NOT" simple', () => {
318
+ const lexer = new QueryLexer( queryStrings[2] );
319
+ const result = lexer.query;
249
320
 
250
- const tree = new ModelsTree();
251
- tree.node.addWhere({ or: [ { index: ['2'] }, { position: ['5'] } ] });
252
- const expected = tree.root.toObject();
321
+ const tree = new ModelsTree();
322
+ tree.node.addWhere({ key: { not: ['main'] } });
323
+ const expected = tree.root.toObject();
253
324
 
254
- expect(result).toMatchObject(expected);
255
- });
325
+ expect(result).toMatchObject(expected);
326
+ });
256
327
 
257
- test('Token "NOT"', () => {
258
- const lexer = new QueryLexer( queryStrings[13] );
259
- const result = lexer.query;
328
+ test('"NOT" short', () => {
329
+ const lexer = new QueryLexer( queryStrings[3] );
330
+ const result = lexer.query;
260
331
 
261
- const tree = new ModelsTree();
262
- tree.include('comments').use('comments');
263
- tree.node.addWhere({ id: { not: ['7'] }});
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
- expect(result).toMatchObject(expected);
267
- });
336
+ expect(result).toMatchObject(expected);
337
+ });
268
338
 
269
- test('Token "Like" simple', () => {
270
- const lexer = new QueryLexer( queryStrings[14] );
271
- const result = lexer.query;
339
+ test('"NOT" inside includes', () => {
340
+ const lexer = new QueryLexer( queryStrings[4] );
341
+ const result = lexer.query;
272
342
 
273
- const tree = new ModelsTree();
274
- tree.node.addWhere({ title: { like: ['some_text'] }});
275
- const expected = tree.root.toObject();
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
- expect(result).toMatchObject(expected);
278
- });
348
+ expect(result).toMatchObject(expected);
349
+ });
279
350
 
280
- it('query "Subinclude and isolated Horizontal"', () => {
281
- const lexer = new QueryLexer( queryStrings[15] );
282
- result = lexer.query;
351
+ test('"Like" simple', () => {
352
+ const lexer = new QueryLexer( queryStrings[5] );
353
+ const result = lexer.query;
283
354
 
284
- const tree = new ModelsTree();
285
- tree.include('comments').use('comments');
286
- tree.include('user');
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
- expect(result).toMatchObject(expected);
359
+ expect(result).toMatchObject(expected);
360
+ });
292
361
  });
293
362
 
294
363
  });
File without changes