patram 0.2.0 → 0.4.0
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/lib/build-graph-identity.js +86 -99
- package/lib/build-graph.js +536 -31
- package/lib/build-graph.types.ts +6 -2
- package/lib/check-directive-metadata.js +534 -0
- package/lib/check-directive-value.js +291 -0
- package/lib/check-graph.js +23 -5
- package/lib/cli-help-metadata.js +56 -16
- package/lib/command-output.js +16 -1
- package/lib/derived-summary.js +10 -8
- package/lib/directive-diagnostics.js +38 -0
- package/lib/directive-type-rules.js +133 -0
- package/lib/discover-fields.js +435 -0
- package/lib/discover-fields.types.ts +52 -0
- package/lib/document-node-identity.js +317 -0
- package/lib/format-node-header.js +9 -7
- package/lib/format-output-metadata.js +15 -23
- package/lib/layout-stored-queries.js +124 -85
- package/lib/load-patram-config.js +433 -96
- package/lib/load-patram-config.types.ts +98 -3
- package/lib/load-project-graph.js +4 -1
- package/lib/output-view.types.ts +14 -6
- package/lib/parse-cli-arguments.types.ts +1 -1
- package/lib/parse-where-clause.js +344 -107
- package/lib/parse-where-clause.types.ts +25 -8
- package/lib/patram-cli.js +68 -4
- package/lib/patram-config.js +31 -31
- package/lib/patram-config.types.ts +10 -4
- package/lib/query-graph.js +269 -40
- package/lib/query-inspection.js +440 -60
- package/lib/render-field-discovery.js +184 -0
- package/lib/render-json-output.js +21 -22
- package/lib/render-output-view.js +301 -34
- package/lib/render-plain-output.js +1 -1
- package/lib/render-rich-output.js +1 -1
- package/lib/render-rich-source.js +245 -14
- package/lib/resolve-patram-graph-config.js +15 -9
- package/lib/show-document.js +66 -9
- package/package.json +5 -5
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* ParsedAggregateComparison,
|
|
7
7
|
* ParsedAggregateName,
|
|
8
8
|
* ParsedAggregateTerm,
|
|
9
|
-
*
|
|
9
|
+
* ParsedExpression,
|
|
10
10
|
* ParsedFieldName,
|
|
11
11
|
* ParsedTerm,
|
|
12
12
|
* ParsedTraversalTerm,
|
|
@@ -17,6 +17,16 @@
|
|
|
17
17
|
* @typedef {{ index: number, where_clause: string }} ParserState
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {{
|
|
22
|
+
* expression: ParsedExpression,
|
|
23
|
+
* success: true,
|
|
24
|
+
* } | {
|
|
25
|
+
* diagnostic: PatramDiagnostic,
|
|
26
|
+
* success: false,
|
|
27
|
+
* }} ParseExpressionResult
|
|
28
|
+
*/
|
|
29
|
+
|
|
20
30
|
/**
|
|
21
31
|
* @typedef {{
|
|
22
32
|
* success: true,
|
|
@@ -28,7 +38,7 @@
|
|
|
28
38
|
*/
|
|
29
39
|
|
|
30
40
|
/**
|
|
31
|
-
* Parse one where clause into structured
|
|
41
|
+
* Parse one where clause into a structured boolean expression.
|
|
32
42
|
*
|
|
33
43
|
* @param {string} where_clause
|
|
34
44
|
* @returns {ParseWhereClauseResult}
|
|
@@ -43,10 +53,10 @@ export function parseWhereClause(where_clause) {
|
|
|
43
53
|
return fail(1, 'Query must not be empty.');
|
|
44
54
|
}
|
|
45
55
|
|
|
46
|
-
const
|
|
56
|
+
const expression_result = parseExpression(parser_state, null);
|
|
47
57
|
|
|
48
|
-
if (!
|
|
49
|
-
return
|
|
58
|
+
if (!expression_result.success) {
|
|
59
|
+
return expression_result;
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
skipWhitespace(parser_state);
|
|
@@ -56,7 +66,7 @@ export function parseWhereClause(where_clause) {
|
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
return {
|
|
59
|
-
|
|
69
|
+
expression: expression_result.expression,
|
|
60
70
|
success: true,
|
|
61
71
|
};
|
|
62
72
|
}
|
|
@@ -64,12 +74,83 @@ export function parseWhereClause(where_clause) {
|
|
|
64
74
|
/**
|
|
65
75
|
* @param {ParserState} parser_state
|
|
66
76
|
* @param {')' | null} stop_character
|
|
67
|
-
* @returns {
|
|
77
|
+
* @returns {ParseExpressionResult}
|
|
78
|
+
*/
|
|
79
|
+
function parseExpression(parser_state, stop_character) {
|
|
80
|
+
return parseOrExpression(parser_state, stop_character);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {ParserState} parser_state
|
|
85
|
+
* @param {')' | null} stop_character
|
|
86
|
+
* @returns {ParseExpressionResult}
|
|
87
|
+
*/
|
|
88
|
+
function parseOrExpression(parser_state, stop_character) {
|
|
89
|
+
const first_expression_result = parseAndExpression(
|
|
90
|
+
parser_state,
|
|
91
|
+
stop_character,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (!first_expression_result.success) {
|
|
95
|
+
return first_expression_result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @type {ParsedExpression[]} */
|
|
99
|
+
const expressions = [first_expression_result.expression];
|
|
100
|
+
|
|
101
|
+
while (true) {
|
|
102
|
+
skipWhitespace(parser_state);
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
currentCharacter(parser_state) === stop_character ||
|
|
106
|
+
isAtEnd(parser_state)
|
|
107
|
+
) {
|
|
108
|
+
return collapseBooleanExpression('or', expressions);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!consumeKeyword(parser_state, 'or')) {
|
|
112
|
+
return collapseBooleanExpression('or', expressions);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
skipWhitespace(parser_state);
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
currentCharacter(parser_state) === stop_character ||
|
|
119
|
+
isAtEnd(parser_state)
|
|
120
|
+
) {
|
|
121
|
+
return failExpectedTerm(parser_state);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const next_expression_result = parseAndExpression(
|
|
125
|
+
parser_state,
|
|
126
|
+
stop_character,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (!next_expression_result.success) {
|
|
130
|
+
return next_expression_result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
expressions.push(next_expression_result.expression);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {ParserState} parser_state
|
|
139
|
+
* @param {')' | null} stop_character
|
|
140
|
+
* @returns {ParseExpressionResult}
|
|
68
141
|
*/
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
142
|
+
function parseAndExpression(parser_state, stop_character) {
|
|
143
|
+
const first_expression_result = parseUnaryExpression(
|
|
144
|
+
parser_state,
|
|
145
|
+
stop_character,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
if (!first_expression_result.success) {
|
|
149
|
+
return first_expression_result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @type {ParsedExpression[]} */
|
|
153
|
+
const expressions = [first_expression_result.expression];
|
|
73
154
|
|
|
74
155
|
while (true) {
|
|
75
156
|
skipWhitespace(parser_state);
|
|
@@ -78,68 +159,131 @@ function parseClauses(parser_state, stop_character) {
|
|
|
78
159
|
currentCharacter(parser_state) === stop_character ||
|
|
79
160
|
isAtEnd(parser_state)
|
|
80
161
|
) {
|
|
81
|
-
return
|
|
82
|
-
? fail(parser_state.where_clause.length + 1, 'Expected a query term.')
|
|
83
|
-
: { clauses, success: true };
|
|
162
|
+
return collapseBooleanExpression('and', expressions);
|
|
84
163
|
}
|
|
85
164
|
|
|
86
|
-
if (!
|
|
87
|
-
|
|
88
|
-
return failToken(parser_state);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
skipWhitespace(parser_state);
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
currentCharacter(parser_state) === stop_character ||
|
|
95
|
-
isAtEnd(parser_state)
|
|
96
|
-
) {
|
|
97
|
-
return fail(
|
|
98
|
-
parser_state.where_clause.length + 1,
|
|
99
|
-
'Expected a query term.',
|
|
100
|
-
);
|
|
101
|
-
}
|
|
165
|
+
if (!consumeKeyword(parser_state, 'and')) {
|
|
166
|
+
return collapseBooleanExpression('and', expressions);
|
|
102
167
|
}
|
|
103
168
|
|
|
104
|
-
|
|
169
|
+
skipWhitespace(parser_state);
|
|
105
170
|
|
|
106
|
-
if (
|
|
107
|
-
|
|
171
|
+
if (
|
|
172
|
+
currentCharacter(parser_state) === stop_character ||
|
|
173
|
+
isAtEnd(parser_state)
|
|
174
|
+
) {
|
|
175
|
+
return failExpectedTerm(parser_state);
|
|
108
176
|
}
|
|
109
177
|
|
|
110
|
-
|
|
111
|
-
|
|
178
|
+
const next_expression_result = parseUnaryExpression(
|
|
179
|
+
parser_state,
|
|
180
|
+
stop_character,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (!next_expression_result.success) {
|
|
184
|
+
return next_expression_result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
expressions.push(next_expression_result.expression);
|
|
112
188
|
}
|
|
113
189
|
}
|
|
114
190
|
|
|
115
191
|
/**
|
|
116
192
|
* @param {ParserState} parser_state
|
|
117
|
-
* @
|
|
193
|
+
* @param {')' | null} stop_character
|
|
194
|
+
* @returns {ParseExpressionResult}
|
|
118
195
|
*/
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
const
|
|
196
|
+
function parseUnaryExpression(parser_state, stop_character) {
|
|
197
|
+
skipWhitespace(parser_state);
|
|
198
|
+
const start_index = parser_state.index;
|
|
199
|
+
|
|
200
|
+
if (!consumeKeyword(parser_state, 'not')) {
|
|
201
|
+
return parsePrimaryExpression(parser_state, stop_character);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const whitespace_length = skipWhitespace(parser_state);
|
|
122
205
|
|
|
123
|
-
if (
|
|
206
|
+
if (whitespace_length === 0 && currentCharacter(parser_state) !== '(') {
|
|
124
207
|
return fail(
|
|
125
|
-
|
|
126
|
-
`Unsupported query token "${readToken(parser_state,
|
|
208
|
+
start_index + 1,
|
|
209
|
+
`Unsupported query token "${readToken(parser_state, start_index)}".`,
|
|
127
210
|
);
|
|
128
211
|
}
|
|
129
212
|
|
|
213
|
+
if (
|
|
214
|
+
currentCharacter(parser_state) === stop_character ||
|
|
215
|
+
isAtEnd(parser_state)
|
|
216
|
+
) {
|
|
217
|
+
return failExpectedTerm(parser_state);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const expression_result = parseUnaryExpression(parser_state, stop_character);
|
|
221
|
+
|
|
222
|
+
if (!expression_result.success) {
|
|
223
|
+
return expression_result;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return successExpression({
|
|
227
|
+
expression: expression_result.expression,
|
|
228
|
+
kind: 'not',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {ParserState} parser_state
|
|
234
|
+
* @param {')' | null} stop_character
|
|
235
|
+
* @returns {ParseExpressionResult}
|
|
236
|
+
*/
|
|
237
|
+
function parsePrimaryExpression(parser_state, stop_character) {
|
|
238
|
+
skipWhitespace(parser_state);
|
|
239
|
+
|
|
240
|
+
if (
|
|
241
|
+
currentCharacter(parser_state) === stop_character ||
|
|
242
|
+
isAtEnd(parser_state)
|
|
243
|
+
) {
|
|
244
|
+
return failExpectedTerm(parser_state);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (consumeOperator(parser_state, '(')) {
|
|
248
|
+
const expression_result = parseExpression(parser_state, ')');
|
|
249
|
+
|
|
250
|
+
if (!expression_result.success) {
|
|
251
|
+
return expression_result;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!consumeOperator(parser_state, ')')) {
|
|
255
|
+
return failToken(parser_state);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return expression_result;
|
|
259
|
+
}
|
|
260
|
+
|
|
130
261
|
const term_result = parseTerm(parser_state);
|
|
131
262
|
|
|
132
263
|
if (!term_result.success) {
|
|
133
264
|
return term_result;
|
|
134
265
|
}
|
|
135
266
|
|
|
136
|
-
return {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
267
|
+
return successExpression({
|
|
268
|
+
kind: 'term',
|
|
269
|
+
term: term_result.term,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @param {'and' | 'or'} kind
|
|
275
|
+
* @param {ParsedExpression[]} expressions
|
|
276
|
+
* @returns {ParseExpressionResult}
|
|
277
|
+
*/
|
|
278
|
+
function collapseBooleanExpression(kind, expressions) {
|
|
279
|
+
if (expressions.length === 1) {
|
|
280
|
+
return successExpression(expressions[0]);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return successExpression({
|
|
284
|
+
expressions,
|
|
285
|
+
kind,
|
|
286
|
+
});
|
|
143
287
|
}
|
|
144
288
|
|
|
145
289
|
/**
|
|
@@ -188,10 +332,10 @@ function parseAggregate(parser_state) {
|
|
|
188
332
|
}
|
|
189
333
|
|
|
190
334
|
skipWhitespace(parser_state);
|
|
191
|
-
const
|
|
335
|
+
const expression_result = parseExpression(parser_state, ')');
|
|
192
336
|
|
|
193
|
-
if (!
|
|
194
|
-
return
|
|
337
|
+
if (!expression_result.success) {
|
|
338
|
+
return expression_result;
|
|
195
339
|
}
|
|
196
340
|
|
|
197
341
|
if (!consumeOperator(parser_state, ')')) {
|
|
@@ -202,7 +346,7 @@ function parseAggregate(parser_state) {
|
|
|
202
346
|
parser_state,
|
|
203
347
|
aggregate_name,
|
|
204
348
|
traversal_result.traversal,
|
|
205
|
-
|
|
349
|
+
expression_result.expression,
|
|
206
350
|
);
|
|
207
351
|
}
|
|
208
352
|
|
|
@@ -218,30 +362,32 @@ function parseAtomicTerm(parser_state) {
|
|
|
218
362
|
return failToken(parser_state);
|
|
219
363
|
}
|
|
220
364
|
|
|
221
|
-
return (
|
|
222
|
-
parseFieldSet(parser_state, field_or_relation_name) ??
|
|
223
|
-
parseOperatorTerm(parser_state, start_index, field_or_relation_name)
|
|
224
|
-
);
|
|
365
|
+
return parseOperatorTerm(parser_state, start_index, field_or_relation_name);
|
|
225
366
|
}
|
|
226
367
|
|
|
227
368
|
/**
|
|
228
369
|
* @param {ParserState} parser_state
|
|
229
370
|
* @param {ParsedAggregateName} aggregate_name
|
|
230
371
|
* @param {ParsedTraversalTerm} traversal
|
|
231
|
-
* @param {
|
|
372
|
+
* @param {ParsedExpression} expression
|
|
232
373
|
* @returns {ParseTermResult}
|
|
233
374
|
*/
|
|
234
|
-
function createAggregateTerm(
|
|
375
|
+
function createAggregateTerm(
|
|
376
|
+
parser_state,
|
|
377
|
+
aggregate_name,
|
|
378
|
+
traversal,
|
|
379
|
+
expression,
|
|
380
|
+
) {
|
|
235
381
|
/** @type {ParsedAggregateTerm} */
|
|
236
382
|
const aggregate_term = {
|
|
237
383
|
aggregate_name,
|
|
238
|
-
|
|
384
|
+
expression,
|
|
239
385
|
kind: 'aggregate',
|
|
240
386
|
traversal,
|
|
241
387
|
};
|
|
242
388
|
|
|
243
389
|
if (aggregate_name !== 'count') {
|
|
244
|
-
return
|
|
390
|
+
return successTerm(aggregate_term);
|
|
245
391
|
}
|
|
246
392
|
|
|
247
393
|
const count_tail = parseCountTail(parser_state);
|
|
@@ -250,7 +396,7 @@ function createAggregateTerm(parser_state, aggregate_name, traversal, clauses) {
|
|
|
250
396
|
return failToken(parser_state);
|
|
251
397
|
}
|
|
252
398
|
|
|
253
|
-
return
|
|
399
|
+
return successTerm({
|
|
254
400
|
...aggregate_term,
|
|
255
401
|
comparison: count_tail.comparison,
|
|
256
402
|
value: count_tail.value,
|
|
@@ -259,15 +405,12 @@ function createAggregateTerm(parser_state, aggregate_name, traversal, clauses) {
|
|
|
259
405
|
|
|
260
406
|
/**
|
|
261
407
|
* @param {ParserState} parser_state
|
|
408
|
+
* @param {number} start_index
|
|
262
409
|
* @param {ParsedFieldName | string} field_name
|
|
263
410
|
* @returns {ParseTermResult | null}
|
|
264
411
|
*/
|
|
265
|
-
function parseFieldSet(parser_state, field_name) {
|
|
266
|
-
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const start_index = parser_state.index;
|
|
412
|
+
function parseFieldSet(parser_state, start_index, field_name) {
|
|
413
|
+
const operator_start_index = parser_state.index;
|
|
271
414
|
|
|
272
415
|
if (!consumeRequiredWhitespace(parser_state)) {
|
|
273
416
|
return null;
|
|
@@ -276,7 +419,7 @@ function parseFieldSet(parser_state, field_name) {
|
|
|
276
419
|
const operator = parseSetOperator(parser_state);
|
|
277
420
|
|
|
278
421
|
if (!operator) {
|
|
279
|
-
parser_state.index =
|
|
422
|
+
parser_state.index = operator_start_index;
|
|
280
423
|
return null;
|
|
281
424
|
}
|
|
282
425
|
|
|
@@ -287,7 +430,13 @@ function parseFieldSet(parser_state, field_name) {
|
|
|
287
430
|
const values = parseList(parser_state);
|
|
288
431
|
|
|
289
432
|
return values
|
|
290
|
-
?
|
|
433
|
+
? successTerm({
|
|
434
|
+
column: start_index + 1,
|
|
435
|
+
field_name,
|
|
436
|
+
kind: 'field_set',
|
|
437
|
+
operator,
|
|
438
|
+
values,
|
|
439
|
+
})
|
|
291
440
|
: failToken(parser_state);
|
|
292
441
|
}
|
|
293
442
|
|
|
@@ -298,18 +447,46 @@ function parseFieldSet(parser_state, field_name) {
|
|
|
298
447
|
* @returns {ParseTermResult}
|
|
299
448
|
*/
|
|
300
449
|
function parseOperatorTerm(parser_state, start_index, field_or_relation_name) {
|
|
301
|
-
const
|
|
450
|
+
const field_set = parseFieldSet(
|
|
451
|
+
parser_state,
|
|
452
|
+
start_index,
|
|
453
|
+
field_or_relation_name,
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
if (field_set) {
|
|
457
|
+
return field_set;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const prefix_term = parsePrefixTerm(
|
|
461
|
+
parser_state,
|
|
462
|
+
start_index,
|
|
463
|
+
field_or_relation_name,
|
|
464
|
+
);
|
|
302
465
|
|
|
303
466
|
if (prefix_term) {
|
|
304
467
|
return prefix_term;
|
|
305
468
|
}
|
|
306
469
|
|
|
307
|
-
const contains_term = parseContainsTerm(
|
|
470
|
+
const contains_term = parseContainsTerm(
|
|
471
|
+
parser_state,
|
|
472
|
+
start_index,
|
|
473
|
+
field_or_relation_name,
|
|
474
|
+
);
|
|
308
475
|
|
|
309
476
|
if (contains_term) {
|
|
310
477
|
return contains_term;
|
|
311
478
|
}
|
|
312
479
|
|
|
480
|
+
const comparison_term = parseFieldComparisonTerm(
|
|
481
|
+
parser_state,
|
|
482
|
+
start_index,
|
|
483
|
+
field_or_relation_name,
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
if (comparison_term) {
|
|
487
|
+
return comparison_term;
|
|
488
|
+
}
|
|
489
|
+
|
|
313
490
|
const equality_term = parseEqualityTerm(
|
|
314
491
|
parser_state,
|
|
315
492
|
start_index,
|
|
@@ -321,7 +498,7 @@ function parseOperatorTerm(parser_state, start_index, field_or_relation_name) {
|
|
|
321
498
|
}
|
|
322
499
|
|
|
323
500
|
if (consumeOperator(parser_state, ':*')) {
|
|
324
|
-
return
|
|
501
|
+
return successTerm({
|
|
325
502
|
column: start_index + 1,
|
|
326
503
|
kind: 'relation',
|
|
327
504
|
relation_name: field_or_relation_name,
|
|
@@ -395,39 +572,51 @@ function parseSetOperator(parser_state) {
|
|
|
395
572
|
|
|
396
573
|
/**
|
|
397
574
|
* @param {ParserState} parser_state
|
|
575
|
+
* @param {number} start_index
|
|
398
576
|
* @param {string} field_name
|
|
399
577
|
* @returns {ParseTermResult | null}
|
|
400
578
|
*/
|
|
401
|
-
function parsePrefixTerm(parser_state, field_name) {
|
|
402
|
-
if (field_name !== 'id' && field_name !== 'path') {
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
|
|
579
|
+
function parsePrefixTerm(parser_state, start_index, field_name) {
|
|
406
580
|
if (!consumeOperator(parser_state, '^=')) {
|
|
407
581
|
return null;
|
|
408
582
|
}
|
|
409
583
|
|
|
584
|
+
skipWhitespace(parser_state);
|
|
410
585
|
const value = parseBareValue(parser_state);
|
|
411
586
|
|
|
412
587
|
return value
|
|
413
|
-
?
|
|
588
|
+
? successTerm({
|
|
589
|
+
column: start_index + 1,
|
|
590
|
+
field_name,
|
|
591
|
+
kind: 'field',
|
|
592
|
+
operator: '^=',
|
|
593
|
+
value,
|
|
594
|
+
})
|
|
414
595
|
: failToken(parser_state);
|
|
415
596
|
}
|
|
416
597
|
|
|
417
598
|
/**
|
|
418
599
|
* @param {ParserState} parser_state
|
|
600
|
+
* @param {number} start_index
|
|
419
601
|
* @param {string} field_name
|
|
420
602
|
* @returns {ParseTermResult | null}
|
|
421
603
|
*/
|
|
422
|
-
function parseContainsTerm(parser_state, field_name) {
|
|
423
|
-
if (
|
|
604
|
+
function parseContainsTerm(parser_state, start_index, field_name) {
|
|
605
|
+
if (!consumeOperator(parser_state, '~')) {
|
|
424
606
|
return null;
|
|
425
607
|
}
|
|
426
608
|
|
|
609
|
+
skipWhitespace(parser_state);
|
|
427
610
|
const value = parseBareValue(parser_state);
|
|
428
611
|
|
|
429
612
|
return value
|
|
430
|
-
?
|
|
613
|
+
? successTerm({
|
|
614
|
+
column: start_index + 1,
|
|
615
|
+
field_name,
|
|
616
|
+
kind: 'field',
|
|
617
|
+
operator: '~',
|
|
618
|
+
value,
|
|
619
|
+
})
|
|
431
620
|
: failToken(parser_state);
|
|
432
621
|
}
|
|
433
622
|
|
|
@@ -442,14 +631,20 @@ function parseEqualityTerm(parser_state, start_index, field_or_relation_name) {
|
|
|
442
631
|
return null;
|
|
443
632
|
}
|
|
444
633
|
|
|
634
|
+
skipWhitespace(parser_state);
|
|
445
635
|
const value = parseBareValue(parser_state);
|
|
446
636
|
|
|
447
637
|
if (!value) {
|
|
448
638
|
return failToken(parser_state);
|
|
449
639
|
}
|
|
450
640
|
|
|
451
|
-
if (
|
|
452
|
-
|
|
641
|
+
if (
|
|
642
|
+
field_or_relation_name.startsWith('$') ||
|
|
643
|
+
field_or_relation_name === 'title' ||
|
|
644
|
+
!value.includes(':')
|
|
645
|
+
) {
|
|
646
|
+
return successTerm({
|
|
647
|
+
column: start_index + 1,
|
|
453
648
|
field_name: field_or_relation_name,
|
|
454
649
|
kind: 'field',
|
|
455
650
|
operator: '=',
|
|
@@ -457,17 +652,54 @@ function parseEqualityTerm(parser_state, start_index, field_or_relation_name) {
|
|
|
457
652
|
});
|
|
458
653
|
}
|
|
459
654
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
655
|
+
return successTerm({
|
|
656
|
+
column: start_index + 1,
|
|
657
|
+
kind: 'relation_target',
|
|
658
|
+
relation_name: field_or_relation_name,
|
|
659
|
+
target_id: value,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* @param {ParserState} parser_state
|
|
665
|
+
* @param {number} start_index
|
|
666
|
+
* @param {string} field_name
|
|
667
|
+
* @returns {ParseTermResult | null}
|
|
668
|
+
*/
|
|
669
|
+
function parseFieldComparisonTerm(parser_state, start_index, field_name) {
|
|
670
|
+
const operator = parseFieldComparisonOperator(parser_state);
|
|
671
|
+
|
|
672
|
+
if (!operator) {
|
|
673
|
+
return null;
|
|
467
674
|
}
|
|
468
675
|
|
|
469
|
-
parser_state
|
|
470
|
-
|
|
676
|
+
skipWhitespace(parser_state);
|
|
677
|
+
const value = parseBareValue(parser_state);
|
|
678
|
+
|
|
679
|
+
if (!value) {
|
|
680
|
+
return failToken(parser_state);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return successTerm({
|
|
684
|
+
column: start_index + 1,
|
|
685
|
+
field_name,
|
|
686
|
+
kind: 'field',
|
|
687
|
+
operator,
|
|
688
|
+
value,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* @param {ParserState} parser_state
|
|
694
|
+
* @returns {'!=' | '<=' | '>=' | '<' | '>' | null}
|
|
695
|
+
*/
|
|
696
|
+
function parseFieldComparisonOperator(parser_state) {
|
|
697
|
+
/** @type {Array<'!=' | '<=' | '>=' | '<' | '>'>} */
|
|
698
|
+
const comparisons = ['!=', '<=', '>=', '<', '>'];
|
|
699
|
+
|
|
700
|
+
return (
|
|
701
|
+
comparisons.find((value) => consumeOperator(parser_state, value)) ?? null
|
|
702
|
+
);
|
|
471
703
|
}
|
|
472
704
|
|
|
473
705
|
/**
|
|
@@ -526,7 +758,7 @@ function parseComparison(parser_state) {
|
|
|
526
758
|
* @returns {string | null}
|
|
527
759
|
*/
|
|
528
760
|
function parseIdentifier(parser_state) {
|
|
529
|
-
return readMatch(parser_state,
|
|
761
|
+
return readMatch(parser_state, /^\$?[a-z_][a-z0-9_]*/u);
|
|
530
762
|
}
|
|
531
763
|
|
|
532
764
|
/**
|
|
@@ -644,26 +876,18 @@ function isAtEnd(parser_state) {
|
|
|
644
876
|
}
|
|
645
877
|
|
|
646
878
|
/**
|
|
647
|
-
* @param {
|
|
648
|
-
* @returns {
|
|
649
|
-
*/
|
|
650
|
-
function isSupportedFieldName(field_name) {
|
|
651
|
-
return ['id', 'kind', 'path', 'status', 'title'].includes(field_name);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* @param {string} field_name
|
|
656
|
-
* @returns {field_name is 'id' | 'kind' | 'path' | 'status'}
|
|
879
|
+
* @param {ParsedExpression} expression
|
|
880
|
+
* @returns {ParseExpressionResult}
|
|
657
881
|
*/
|
|
658
|
-
function
|
|
659
|
-
return
|
|
882
|
+
function successExpression(expression) {
|
|
883
|
+
return { expression, success: true };
|
|
660
884
|
}
|
|
661
885
|
|
|
662
886
|
/**
|
|
663
887
|
* @param {ParsedTerm} term
|
|
664
888
|
* @returns {ParseTermResult}
|
|
665
889
|
*/
|
|
666
|
-
function
|
|
890
|
+
function successTerm(term) {
|
|
667
891
|
return { success: true, term };
|
|
668
892
|
}
|
|
669
893
|
|
|
@@ -678,6 +902,19 @@ function failToken(parser_state) {
|
|
|
678
902
|
);
|
|
679
903
|
}
|
|
680
904
|
|
|
905
|
+
/**
|
|
906
|
+
* @param {ParserState} parser_state
|
|
907
|
+
* @returns {{ diagnostic: PatramDiagnostic, success: false }}
|
|
908
|
+
*/
|
|
909
|
+
function failExpectedTerm(parser_state) {
|
|
910
|
+
return fail(
|
|
911
|
+
isAtEnd(parser_state)
|
|
912
|
+
? parser_state.where_clause.length + 1
|
|
913
|
+
: parser_state.index + 1,
|
|
914
|
+
'Expected a query term.',
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
|
|
681
918
|
/**
|
|
682
919
|
* @param {number} column
|
|
683
920
|
* @param {string} message
|