patram 0.3.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 +70 -84
- package/lib/build-graph.js +170 -18
- package/lib/build-graph.types.ts +1 -0
- package/lib/check-directive-metadata.js +20 -2
- package/lib/check-directive-value.js +9 -0
- package/lib/check-graph.js +1 -2
- package/lib/cli-help-metadata.js +12 -0
- package/lib/command-output.js +16 -1
- package/lib/discover-fields.js +9 -1
- package/lib/document-node-identity.js +317 -0
- package/lib/layout-stored-queries.js +122 -29
- package/lib/load-patram-config.js +1 -1
- package/lib/parse-where-clause.js +237 -66
- package/lib/parse-where-clause.types.ts +21 -6
- package/lib/patram-cli.js +34 -2
- package/lib/query-graph.js +29 -19
- package/lib/query-inspection.js +173 -68
- package/lib/render-field-discovery.js +44 -8
- package/lib/render-output-view.js +65 -19
- package/lib/render-rich-source.js +245 -14
- package/lib/show-document.js +15 -2
- package/package.json +1 -1
|
@@ -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,29 @@ export function parseWhereClause(where_clause) {
|
|
|
64
74
|
/**
|
|
65
75
|
* @param {ParserState} parser_state
|
|
66
76
|
* @param {')' | null} stop_character
|
|
67
|
-
* @returns {
|
|
77
|
+
* @returns {ParseExpressionResult}
|
|
68
78
|
*/
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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];
|
|
73
100
|
|
|
74
101
|
while (true) {
|
|
75
102
|
skipWhitespace(parser_state);
|
|
@@ -78,68 +105,185 @@ function parseClauses(parser_state, stop_character) {
|
|
|
78
105
|
currentCharacter(parser_state) === stop_character ||
|
|
79
106
|
isAtEnd(parser_state)
|
|
80
107
|
) {
|
|
81
|
-
return
|
|
82
|
-
? fail(parser_state.where_clause.length + 1, 'Expected a query term.')
|
|
83
|
-
: { clauses, success: true };
|
|
108
|
+
return collapseBooleanExpression('or', expressions);
|
|
84
109
|
}
|
|
85
110
|
|
|
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
|
-
}
|
|
111
|
+
if (!consumeKeyword(parser_state, 'or')) {
|
|
112
|
+
return collapseBooleanExpression('or', expressions);
|
|
102
113
|
}
|
|
103
114
|
|
|
104
|
-
|
|
115
|
+
skipWhitespace(parser_state);
|
|
105
116
|
|
|
106
|
-
if (
|
|
107
|
-
|
|
117
|
+
if (
|
|
118
|
+
currentCharacter(parser_state) === stop_character ||
|
|
119
|
+
isAtEnd(parser_state)
|
|
120
|
+
) {
|
|
121
|
+
return failExpectedTerm(parser_state);
|
|
108
122
|
}
|
|
109
123
|
|
|
110
|
-
|
|
111
|
-
|
|
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);
|
|
112
134
|
}
|
|
113
135
|
}
|
|
114
136
|
|
|
115
137
|
/**
|
|
116
138
|
* @param {ParserState} parser_state
|
|
117
|
-
* @
|
|
139
|
+
* @param {')' | null} stop_character
|
|
140
|
+
* @returns {ParseExpressionResult}
|
|
118
141
|
*/
|
|
119
|
-
function
|
|
120
|
-
const
|
|
121
|
-
|
|
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];
|
|
154
|
+
|
|
155
|
+
while (true) {
|
|
156
|
+
skipWhitespace(parser_state);
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
currentCharacter(parser_state) === stop_character ||
|
|
160
|
+
isAtEnd(parser_state)
|
|
161
|
+
) {
|
|
162
|
+
return collapseBooleanExpression('and', expressions);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!consumeKeyword(parser_state, 'and')) {
|
|
166
|
+
return collapseBooleanExpression('and', expressions);
|
|
167
|
+
}
|
|
122
168
|
|
|
123
|
-
|
|
169
|
+
skipWhitespace(parser_state);
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
currentCharacter(parser_state) === stop_character ||
|
|
173
|
+
isAtEnd(parser_state)
|
|
174
|
+
) {
|
|
175
|
+
return failExpectedTerm(parser_state);
|
|
176
|
+
}
|
|
177
|
+
|
|
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);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @param {ParserState} parser_state
|
|
193
|
+
* @param {')' | null} stop_character
|
|
194
|
+
* @returns {ParseExpressionResult}
|
|
195
|
+
*/
|
|
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);
|
|
205
|
+
|
|
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
|
|
|
@@ -225,20 +369,25 @@ function parseAtomicTerm(parser_state) {
|
|
|
225
369
|
* @param {ParserState} parser_state
|
|
226
370
|
* @param {ParsedAggregateName} aggregate_name
|
|
227
371
|
* @param {ParsedTraversalTerm} traversal
|
|
228
|
-
* @param {
|
|
372
|
+
* @param {ParsedExpression} expression
|
|
229
373
|
* @returns {ParseTermResult}
|
|
230
374
|
*/
|
|
231
|
-
function createAggregateTerm(
|
|
375
|
+
function createAggregateTerm(
|
|
376
|
+
parser_state,
|
|
377
|
+
aggregate_name,
|
|
378
|
+
traversal,
|
|
379
|
+
expression,
|
|
380
|
+
) {
|
|
232
381
|
/** @type {ParsedAggregateTerm} */
|
|
233
382
|
const aggregate_term = {
|
|
234
383
|
aggregate_name,
|
|
235
|
-
|
|
384
|
+
expression,
|
|
236
385
|
kind: 'aggregate',
|
|
237
386
|
traversal,
|
|
238
387
|
};
|
|
239
388
|
|
|
240
389
|
if (aggregate_name !== 'count') {
|
|
241
|
-
return
|
|
390
|
+
return successTerm(aggregate_term);
|
|
242
391
|
}
|
|
243
392
|
|
|
244
393
|
const count_tail = parseCountTail(parser_state);
|
|
@@ -247,7 +396,7 @@ function createAggregateTerm(parser_state, aggregate_name, traversal, clauses) {
|
|
|
247
396
|
return failToken(parser_state);
|
|
248
397
|
}
|
|
249
398
|
|
|
250
|
-
return
|
|
399
|
+
return successTerm({
|
|
251
400
|
...aggregate_term,
|
|
252
401
|
comparison: count_tail.comparison,
|
|
253
402
|
value: count_tail.value,
|
|
@@ -262,6 +411,7 @@ function createAggregateTerm(parser_state, aggregate_name, traversal, clauses) {
|
|
|
262
411
|
*/
|
|
263
412
|
function parseFieldSet(parser_state, start_index, field_name) {
|
|
264
413
|
const operator_start_index = parser_state.index;
|
|
414
|
+
|
|
265
415
|
if (!consumeRequiredWhitespace(parser_state)) {
|
|
266
416
|
return null;
|
|
267
417
|
}
|
|
@@ -280,7 +430,7 @@ function parseFieldSet(parser_state, start_index, field_name) {
|
|
|
280
430
|
const values = parseList(parser_state);
|
|
281
431
|
|
|
282
432
|
return values
|
|
283
|
-
?
|
|
433
|
+
? successTerm({
|
|
284
434
|
column: start_index + 1,
|
|
285
435
|
field_name,
|
|
286
436
|
kind: 'field_set',
|
|
@@ -348,7 +498,7 @@ function parseOperatorTerm(parser_state, start_index, field_or_relation_name) {
|
|
|
348
498
|
}
|
|
349
499
|
|
|
350
500
|
if (consumeOperator(parser_state, ':*')) {
|
|
351
|
-
return
|
|
501
|
+
return successTerm({
|
|
352
502
|
column: start_index + 1,
|
|
353
503
|
kind: 'relation',
|
|
354
504
|
relation_name: field_or_relation_name,
|
|
@@ -435,7 +585,7 @@ function parsePrefixTerm(parser_state, start_index, field_name) {
|
|
|
435
585
|
const value = parseBareValue(parser_state);
|
|
436
586
|
|
|
437
587
|
return value
|
|
438
|
-
?
|
|
588
|
+
? successTerm({
|
|
439
589
|
column: start_index + 1,
|
|
440
590
|
field_name,
|
|
441
591
|
kind: 'field',
|
|
@@ -460,7 +610,7 @@ function parseContainsTerm(parser_state, start_index, field_name) {
|
|
|
460
610
|
const value = parseBareValue(parser_state);
|
|
461
611
|
|
|
462
612
|
return value
|
|
463
|
-
?
|
|
613
|
+
? successTerm({
|
|
464
614
|
column: start_index + 1,
|
|
465
615
|
field_name,
|
|
466
616
|
kind: 'field',
|
|
@@ -493,7 +643,7 @@ function parseEqualityTerm(parser_state, start_index, field_or_relation_name) {
|
|
|
493
643
|
field_or_relation_name === 'title' ||
|
|
494
644
|
!value.includes(':')
|
|
495
645
|
) {
|
|
496
|
-
return
|
|
646
|
+
return successTerm({
|
|
497
647
|
column: start_index + 1,
|
|
498
648
|
field_name: field_or_relation_name,
|
|
499
649
|
kind: 'field',
|
|
@@ -502,7 +652,7 @@ function parseEqualityTerm(parser_state, start_index, field_or_relation_name) {
|
|
|
502
652
|
});
|
|
503
653
|
}
|
|
504
654
|
|
|
505
|
-
return
|
|
655
|
+
return successTerm({
|
|
506
656
|
column: start_index + 1,
|
|
507
657
|
kind: 'relation_target',
|
|
508
658
|
relation_name: field_or_relation_name,
|
|
@@ -530,7 +680,7 @@ function parseFieldComparisonTerm(parser_state, start_index, field_name) {
|
|
|
530
680
|
return failToken(parser_state);
|
|
531
681
|
}
|
|
532
682
|
|
|
533
|
-
return
|
|
683
|
+
return successTerm({
|
|
534
684
|
column: start_index + 1,
|
|
535
685
|
field_name,
|
|
536
686
|
kind: 'field',
|
|
@@ -725,11 +875,19 @@ function isAtEnd(parser_state) {
|
|
|
725
875
|
return parser_state.index >= parser_state.where_clause.length;
|
|
726
876
|
}
|
|
727
877
|
|
|
878
|
+
/**
|
|
879
|
+
* @param {ParsedExpression} expression
|
|
880
|
+
* @returns {ParseExpressionResult}
|
|
881
|
+
*/
|
|
882
|
+
function successExpression(expression) {
|
|
883
|
+
return { expression, success: true };
|
|
884
|
+
}
|
|
885
|
+
|
|
728
886
|
/**
|
|
729
887
|
* @param {ParsedTerm} term
|
|
730
888
|
* @returns {ParseTermResult}
|
|
731
889
|
*/
|
|
732
|
-
function
|
|
890
|
+
function successTerm(term) {
|
|
733
891
|
return { success: true, term };
|
|
734
892
|
}
|
|
735
893
|
|
|
@@ -744,6 +902,19 @@ function failToken(parser_state) {
|
|
|
744
902
|
);
|
|
745
903
|
}
|
|
746
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
|
+
|
|
747
918
|
/**
|
|
748
919
|
* @param {number} column
|
|
749
920
|
* @param {string} message
|
|
@@ -42,13 +42,28 @@ export type ParsedAggregateName = 'any' | 'count' | 'none';
|
|
|
42
42
|
|
|
43
43
|
export interface ParsedAggregateTerm {
|
|
44
44
|
aggregate_name: ParsedAggregateName;
|
|
45
|
-
clauses: ParsedClause[];
|
|
46
45
|
comparison?: ParsedAggregateComparison;
|
|
46
|
+
expression: ParsedExpression;
|
|
47
47
|
kind: 'aggregate';
|
|
48
48
|
traversal: ParsedTraversalTerm;
|
|
49
49
|
value?: number;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export interface ParsedTermExpression {
|
|
53
|
+
kind: 'term';
|
|
54
|
+
term: ParsedTerm;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ParsedNotExpression {
|
|
58
|
+
expression: ParsedExpression;
|
|
59
|
+
kind: 'not';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ParsedBooleanExpression {
|
|
63
|
+
expressions: ParsedExpression[];
|
|
64
|
+
kind: 'and' | 'or';
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
export type ParsedTerm =
|
|
53
68
|
| ParsedAggregateTerm
|
|
54
69
|
| ParsedFieldSetTerm
|
|
@@ -56,14 +71,14 @@ export type ParsedTerm =
|
|
|
56
71
|
| ParsedRelationTargetTerm
|
|
57
72
|
| ParsedRelationTerm;
|
|
58
73
|
|
|
59
|
-
export
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
export type ParsedExpression =
|
|
75
|
+
| ParsedBooleanExpression
|
|
76
|
+
| ParsedNotExpression
|
|
77
|
+
| ParsedTermExpression;
|
|
63
78
|
|
|
64
79
|
export type ParseWhereClauseResult =
|
|
65
80
|
| {
|
|
66
|
-
|
|
81
|
+
expression: ParsedExpression;
|
|
67
82
|
success: true;
|
|
68
83
|
}
|
|
69
84
|
| {
|
package/lib/patram-cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { checkGraph } from './check-graph.js';
|
|
|
9
9
|
import {
|
|
10
10
|
shouldPageCommandOutput,
|
|
11
11
|
writeCommandOutput,
|
|
12
|
+
writeRenderedCommandOutput,
|
|
12
13
|
} from './command-output.js';
|
|
13
14
|
import { discoverFields } from './discover-fields.js';
|
|
14
15
|
import { listRepoFiles } from './list-source-files.js';
|
|
@@ -257,9 +258,20 @@ async function runFieldsCommand(parsed_command, io_context) {
|
|
|
257
258
|
no_color: process.env.NO_COLOR !== undefined,
|
|
258
259
|
term: process.env.TERM,
|
|
259
260
|
});
|
|
260
|
-
const
|
|
261
|
+
const load_result = await loadPatramConfig(process.cwd());
|
|
262
|
+
const defined_field_names =
|
|
263
|
+
load_result.diagnostics.length === 0
|
|
264
|
+
? collectDefinedDiscoveryNames(load_result.config)
|
|
265
|
+
: new Set();
|
|
266
|
+
const discovery_result = await discoverFields(process.cwd(), {
|
|
267
|
+
defined_field_names,
|
|
268
|
+
});
|
|
261
269
|
|
|
262
|
-
|
|
270
|
+
await writeRenderedCommandOutput(
|
|
271
|
+
io_context,
|
|
272
|
+
parsed_command,
|
|
273
|
+
renderFieldDiscovery(discovery_result, output_mode),
|
|
274
|
+
);
|
|
263
275
|
|
|
264
276
|
return 0;
|
|
265
277
|
}
|
|
@@ -395,6 +407,7 @@ async function runShowCommand(parsed_command, io_context) {
|
|
|
395
407
|
parsed_command,
|
|
396
408
|
createShowOutputView(show_output.value, {
|
|
397
409
|
derived_summary_evaluator,
|
|
410
|
+
document_node_ids: project_graph_result.graph.document_node_ids,
|
|
398
411
|
graph_nodes: project_graph_result.graph.nodes,
|
|
399
412
|
repo_config: project_graph_result.config,
|
|
400
413
|
}),
|
|
@@ -494,3 +507,22 @@ function createQueryExecutionOptions(parsed_command, use_pager) {
|
|
|
494
507
|
function formatDiagnostic(diagnostic) {
|
|
495
508
|
return `${diagnostic.path}:${diagnostic.line}:${diagnostic.column} ${diagnostic.level} ${diagnostic.code} ${diagnostic.message}\n`;
|
|
496
509
|
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* @param {import('./load-patram-config.types.ts').PatramRepoConfig | null} repo_config
|
|
513
|
+
* @returns {Set<string>}
|
|
514
|
+
*/
|
|
515
|
+
function collectDefinedDiscoveryNames(repo_config) {
|
|
516
|
+
/** @type {Set<string>} */
|
|
517
|
+
const defined_field_names = new Set();
|
|
518
|
+
|
|
519
|
+
for (const field_name of Object.keys(repo_config?.fields ?? {})) {
|
|
520
|
+
defined_field_names.add(field_name);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
for (const relation_name of Object.keys(repo_config?.relations ?? {})) {
|
|
524
|
+
defined_field_names.add(relation_name);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return defined_field_names;
|
|
528
|
+
}
|
package/lib/query-graph.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @import { PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
|
|
5
5
|
* @import {
|
|
6
6
|
* ParsedAggregateTerm,
|
|
7
|
-
*
|
|
7
|
+
* ParsedExpression,
|
|
8
8
|
* ParsedFieldSetTerm,
|
|
9
9
|
* ParsedFieldTerm,
|
|
10
10
|
* ParsedRelationTargetTerm,
|
|
@@ -82,7 +82,7 @@ export function queryGraph(
|
|
|
82
82
|
const diagnostics = getQuerySemanticDiagnostics(
|
|
83
83
|
repo_config,
|
|
84
84
|
{ kind: 'ad_hoc' },
|
|
85
|
-
parse_result.
|
|
85
|
+
parse_result.expression,
|
|
86
86
|
);
|
|
87
87
|
|
|
88
88
|
if (diagnostics.length > 0) {
|
|
@@ -100,7 +100,7 @@ export function queryGraph(
|
|
|
100
100
|
};
|
|
101
101
|
const graph_nodes = Object.values(graph.nodes).sort(compareGraphNodes);
|
|
102
102
|
const matching_nodes = graph_nodes.filter((graph_node) =>
|
|
103
|
-
|
|
103
|
+
matchesExpression(graph_node, parse_result.expression, evaluation_context),
|
|
104
104
|
);
|
|
105
105
|
const paginated_nodes = paginateNodes(
|
|
106
106
|
matching_nodes,
|
|
@@ -116,26 +116,36 @@ export function queryGraph(
|
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
118
|
* @param {GraphNode} graph_node
|
|
119
|
-
* @param {
|
|
119
|
+
* @param {ParsedExpression} expression
|
|
120
120
|
* @param {EvaluationContext} evaluation_context
|
|
121
121
|
* @returns {boolean}
|
|
122
122
|
*/
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
function matchesExpression(graph_node, expression, evaluation_context) {
|
|
124
|
+
if (expression.kind === 'and') {
|
|
125
|
+
return expression.expressions.every((subexpression) =>
|
|
126
|
+
matchesExpression(graph_node, subexpression, evaluation_context),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
if (expression.kind === 'or') {
|
|
131
|
+
return expression.expressions.some((subexpression) =>
|
|
132
|
+
matchesExpression(graph_node, subexpression, evaluation_context),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (expression.kind === 'not') {
|
|
137
|
+
return !matchesExpression(
|
|
138
|
+
graph_node,
|
|
139
|
+
expression.expression,
|
|
140
|
+
evaluation_context,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (expression.kind === 'term') {
|
|
145
|
+
return matchesTerm(graph_node, expression.term, evaluation_context);
|
|
146
|
+
}
|
|
137
147
|
|
|
138
|
-
|
|
148
|
+
throw new Error('Unsupported parsed boolean expression.');
|
|
139
149
|
}
|
|
140
150
|
|
|
141
151
|
/**
|
|
@@ -181,7 +191,7 @@ function matchesAggregateTerm(graph_node, term, evaluation_context) {
|
|
|
181
191
|
evaluation_context,
|
|
182
192
|
);
|
|
183
193
|
const matching_count = related_nodes.filter((related_node) =>
|
|
184
|
-
|
|
194
|
+
matchesExpression(related_node, term.expression, evaluation_context),
|
|
185
195
|
).length;
|
|
186
196
|
|
|
187
197
|
if (term.aggregate_name === 'any') {
|