patram 0.3.0 → 0.5.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 +171 -19
- package/lib/build-graph.types.ts +1 -0
- package/lib/check-directive-metadata.js +36 -4
- 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 +172 -112
- package/lib/load-patram-config.types.ts +50 -152
- package/lib/parse-claims.js +2 -3
- package/lib/parse-jsdoc-claims.js +3 -3
- 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/patram-config.js +26 -9
- package/lib/patram-config.types.ts +18 -36
- 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 +72 -27
- package/lib/render-rich-source.js +245 -14
- package/lib/resolve-patram-graph-config.js +40 -2
- package/lib/show-document.js +15 -2
- package/package.json +1 -1
package/lib/patram-config.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { PatramConfig } from './patram-config.types.ts';
|
|
3
2
|
* @import { RefinementCtx } from 'zod';
|
|
4
3
|
*/
|
|
5
4
|
|
|
@@ -11,14 +10,20 @@ const CLAIM_TYPE_SCHEMA = z.string().min(1);
|
|
|
11
10
|
const KEY_SOURCE_SCHEMA = z.enum(['path', 'value']);
|
|
12
11
|
const TARGET_SCHEMA = z.enum(['path', 'value']);
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {z.output<typeof class_definition_schema>} ClassDefinition
|
|
15
|
+
*/
|
|
16
|
+
export const class_definition_schema = z
|
|
15
17
|
.object({
|
|
16
18
|
builtin: z.boolean().optional(),
|
|
17
19
|
label: z.string().min(1).optional(),
|
|
18
20
|
})
|
|
19
21
|
.strict();
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {z.output<typeof relation_definition_schema>} RelationDefinition
|
|
25
|
+
*/
|
|
26
|
+
export const relation_definition_schema = z
|
|
22
27
|
.object({
|
|
23
28
|
builtin: z.boolean().optional(),
|
|
24
29
|
from: z.array(CLASS_NAME_SCHEMA).min(1),
|
|
@@ -26,6 +31,9 @@ const relation_definition_schema = z
|
|
|
26
31
|
})
|
|
27
32
|
.strict();
|
|
28
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {z.output<typeof mapping_node_schema>} MappingNodeDefinition
|
|
36
|
+
*/
|
|
29
37
|
const mapping_node_schema = z
|
|
30
38
|
.object({
|
|
31
39
|
class: CLASS_NAME_SCHEMA,
|
|
@@ -34,6 +42,9 @@ const mapping_node_schema = z
|
|
|
34
42
|
})
|
|
35
43
|
.strict();
|
|
36
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {z.output<typeof mapping_emit_schema>} MappingEmitDefinition
|
|
47
|
+
*/
|
|
37
48
|
const mapping_emit_schema = z
|
|
38
49
|
.object({
|
|
39
50
|
relation: RELATION_NAME_SCHEMA,
|
|
@@ -42,7 +53,10 @@ const mapping_emit_schema = z
|
|
|
42
53
|
})
|
|
43
54
|
.strict();
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {z.output<typeof mapping_definition_schema>} MappingDefinition
|
|
58
|
+
*/
|
|
59
|
+
export const mapping_definition_schema = z
|
|
46
60
|
.object({
|
|
47
61
|
emit: mapping_emit_schema.optional(),
|
|
48
62
|
node: mapping_node_schema.optional(),
|
|
@@ -50,6 +64,9 @@ const mapping_definition_schema = z
|
|
|
50
64
|
.strict()
|
|
51
65
|
.superRefine(validateMappingDefinition);
|
|
52
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {z.output<typeof patramConfigSchema>} PatramGraphConfig
|
|
69
|
+
*/
|
|
53
70
|
export const patramConfigSchema = z
|
|
54
71
|
.object({
|
|
55
72
|
$schema: z.url().optional(),
|
|
@@ -64,7 +81,7 @@ export const patramConfigSchema = z
|
|
|
64
81
|
* Parse and validate Patram JSON configuration.
|
|
65
82
|
*
|
|
66
83
|
* @param {unknown} config_json
|
|
67
|
-
* @returns {
|
|
84
|
+
* @returns {PatramGraphConfig}
|
|
68
85
|
*/
|
|
69
86
|
export function parsePatramConfig(config_json) {
|
|
70
87
|
return patramConfigSchema.parse(config_json);
|
|
@@ -86,7 +103,7 @@ function validateMappingDefinition(mapping_definition, refinement_context) {
|
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
/**
|
|
89
|
-
* @param {
|
|
106
|
+
* @param {PatramGraphConfig} config_json
|
|
90
107
|
* @param {RefinementCtx} refinement_context
|
|
91
108
|
*/
|
|
92
109
|
function validatePatramConfigReferences(config_json, refinement_context) {
|
|
@@ -96,7 +113,7 @@ function validatePatramConfigReferences(config_json, refinement_context) {
|
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
/**
|
|
99
|
-
* @param {
|
|
116
|
+
* @param {PatramGraphConfig} config_json
|
|
100
117
|
* @param {RefinementCtx} refinement_context
|
|
101
118
|
*/
|
|
102
119
|
function validateRelationClasses(config_json, refinement_context) {
|
|
@@ -119,7 +136,7 @@ function validateRelationClasses(config_json, refinement_context) {
|
|
|
119
136
|
}
|
|
120
137
|
|
|
121
138
|
/**
|
|
122
|
-
* @param {
|
|
139
|
+
* @param {PatramGraphConfig} config_json
|
|
123
140
|
* @param {RefinementCtx} refinement_context
|
|
124
141
|
*/
|
|
125
142
|
function validateMappingClasses(config_json, refinement_context) {
|
|
@@ -147,7 +164,7 @@ function validateMappingClasses(config_json, refinement_context) {
|
|
|
147
164
|
}
|
|
148
165
|
|
|
149
166
|
/**
|
|
150
|
-
* @param {
|
|
167
|
+
* @param {PatramGraphConfig} config_json
|
|
151
168
|
* @param {RefinementCtx} refinement_context
|
|
152
169
|
*/
|
|
153
170
|
function validateMappingRelations(config_json, refinement_context) {
|
|
@@ -1,40 +1,22 @@
|
|
|
1
|
-
export interface ClassDefinition {
|
|
2
|
-
builtin?: boolean;
|
|
3
|
-
label?: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface RelationDefinition {
|
|
7
|
-
builtin?: boolean;
|
|
8
|
-
from: string[];
|
|
9
|
-
to: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface MappingNodeDefinition {
|
|
13
|
-
class: string;
|
|
14
|
-
field: string;
|
|
15
|
-
key?: 'path' | 'value';
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface MappingEmitDefinition {
|
|
19
|
-
relation: string;
|
|
20
|
-
target: 'path' | 'value';
|
|
21
|
-
target_class: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface MappingDefinition {
|
|
25
|
-
emit?: MappingEmitDefinition;
|
|
26
|
-
node?: MappingNodeDefinition;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface PatramConfig {
|
|
30
|
-
$schema?: string;
|
|
31
|
-
classes: Record<string, ClassDefinition>;
|
|
32
|
-
class_schemas?: Record<string, ClassSchemaConfig>;
|
|
33
|
-
fields?: Record<string, MetadataFieldConfig>;
|
|
34
|
-
mappings: Record<string, MappingDefinition>;
|
|
35
|
-
relations: Record<string, RelationDefinition>;
|
|
36
|
-
}
|
|
37
1
|
import type {
|
|
38
2
|
ClassSchemaConfig,
|
|
39
3
|
MetadataFieldConfig,
|
|
40
4
|
} from './load-patram-config.types.ts';
|
|
5
|
+
|
|
6
|
+
export type ClassDefinition = import('./patram-config.js').ClassDefinition;
|
|
7
|
+
export type RelationDefinition =
|
|
8
|
+
import('./patram-config.js').RelationDefinition;
|
|
9
|
+
export type MappingNodeDefinition =
|
|
10
|
+
import('./patram-config.js').MappingNodeDefinition;
|
|
11
|
+
export type MappingEmitDefinition =
|
|
12
|
+
import('./patram-config.js').MappingEmitDefinition;
|
|
13
|
+
export type MappingDefinition = import('./patram-config.js').MappingDefinition;
|
|
14
|
+
export type PatramGraphConfig = import('./patram-config.js').PatramGraphConfig;
|
|
15
|
+
export type PatramClassConfig = ClassDefinition & {
|
|
16
|
+
schema?: ClassSchemaConfig;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type PatramConfig = Omit<PatramGraphConfig, 'classes'> & {
|
|
20
|
+
classes: Record<string, PatramClassConfig>;
|
|
21
|
+
fields?: Record<string, MetadataFieldConfig>;
|
|
22
|
+
};
|
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') {
|
package/lib/query-inspection.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
/** @import * as $k$$l$parse$j$where$j$clause$k$types$k$ts from './parse-where-clause.types.ts'; */
|
|
2
1
|
/* eslint-disable max-lines */
|
|
3
2
|
/**
|
|
4
3
|
* @import { PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
|
|
5
4
|
* @import { ResolvedOutputMode } from './output-view.types.ts';
|
|
6
5
|
* @import {
|
|
7
|
-
*
|
|
6
|
+
* ParsedExpression,
|
|
8
7
|
* ParsedRelationTargetTerm,
|
|
9
8
|
* ParsedRelationTerm,
|
|
10
9
|
* ParsedTerm,
|
|
@@ -38,10 +37,10 @@ import { parseWhereClause } from './parse-where-clause.js';
|
|
|
38
37
|
* limit: number | null,
|
|
39
38
|
* offset: number,
|
|
40
39
|
* },
|
|
40
|
+
* expression?: ParsedExpression,
|
|
41
41
|
* inspection_mode: 'explain' | 'lint',
|
|
42
42
|
* query_source: QuerySource,
|
|
43
43
|
* where_clause: string,
|
|
44
|
-
* clauses?: ParsedClause[],
|
|
45
44
|
* }} QueryInspectionSuccess
|
|
46
45
|
*/
|
|
47
46
|
|
|
@@ -75,7 +74,7 @@ export function inspectQuery(
|
|
|
75
74
|
const diagnostics = getQuerySemanticDiagnostics(
|
|
76
75
|
repo_config,
|
|
77
76
|
resolved_where_clause.query_source,
|
|
78
|
-
parse_result.
|
|
77
|
+
parse_result.expression,
|
|
79
78
|
);
|
|
80
79
|
|
|
81
80
|
if (diagnostics.length > 0) {
|
|
@@ -99,11 +98,11 @@ export function inspectQuery(
|
|
|
99
98
|
return {
|
|
100
99
|
success: true,
|
|
101
100
|
value: {
|
|
102
|
-
clauses: parse_result.clauses,
|
|
103
101
|
execution: {
|
|
104
102
|
limit: inspection_options.limit,
|
|
105
103
|
offset: inspection_options.offset,
|
|
106
104
|
},
|
|
105
|
+
expression: parse_result.expression,
|
|
107
106
|
inspection_mode: 'explain',
|
|
108
107
|
query_source: resolved_where_clause.query_source,
|
|
109
108
|
where_clause: resolved_where_clause.where_clause,
|
|
@@ -143,20 +142,26 @@ export function renderQueryInspection(query_inspection, output_mode) {
|
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
/**
|
|
145
|
+
* Collect schema-aware diagnostics for one parsed where clause.
|
|
146
|
+
*
|
|
146
147
|
* @param {PatramRepoConfig} repo_config
|
|
147
148
|
* @param {QuerySource} query_source
|
|
148
|
-
* @param {
|
|
149
|
+
* @param {ParsedExpression} expression
|
|
149
150
|
* @returns {PatramDiagnostic[]}
|
|
150
151
|
*/
|
|
151
|
-
function
|
|
152
|
+
export function getQuerySemanticDiagnostics(
|
|
153
|
+
repo_config,
|
|
154
|
+
query_source,
|
|
155
|
+
expression,
|
|
156
|
+
) {
|
|
152
157
|
const known_relation_names = new Set(
|
|
153
158
|
Object.keys(repo_config.relations ?? {}),
|
|
154
159
|
);
|
|
155
160
|
/** @type {PatramDiagnostic[]} */
|
|
156
161
|
const diagnostics = [];
|
|
157
162
|
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
collectExpressionDiagnostics(
|
|
164
|
+
expression,
|
|
160
165
|
diagnostics,
|
|
161
166
|
known_relation_names,
|
|
162
167
|
repo_config.fields ?? {},
|
|
@@ -167,44 +172,58 @@ function collectSemanticDiagnostics(repo_config, query_source, clauses) {
|
|
|
167
172
|
}
|
|
168
173
|
|
|
169
174
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* @param {PatramRepoConfig} repo_config
|
|
173
|
-
* @param {QuerySource} query_source
|
|
174
|
-
* @param {ParsedClause[]} clauses
|
|
175
|
-
* @returns {PatramDiagnostic[]}
|
|
176
|
-
*/
|
|
177
|
-
export function getQuerySemanticDiagnostics(
|
|
178
|
-
repo_config,
|
|
179
|
-
query_source,
|
|
180
|
-
clauses,
|
|
181
|
-
) {
|
|
182
|
-
return collectSemanticDiagnostics(repo_config, query_source, clauses);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* @param {ParsedClause[]} clauses
|
|
175
|
+
* @param {ParsedExpression} expression
|
|
187
176
|
* @param {PatramDiagnostic[]} diagnostics
|
|
188
177
|
* @param {Set<string>} known_relation_names
|
|
189
178
|
* @param {Record<string, import('./load-patram-config.types.ts').MetadataFieldConfig>} known_field_definitions
|
|
190
179
|
* @param {string} diagnostic_path
|
|
191
180
|
*/
|
|
192
|
-
function
|
|
193
|
-
|
|
181
|
+
function collectExpressionDiagnostics(
|
|
182
|
+
expression,
|
|
194
183
|
diagnostics,
|
|
195
184
|
known_relation_names,
|
|
196
185
|
known_field_definitions,
|
|
197
186
|
diagnostic_path,
|
|
198
187
|
) {
|
|
199
|
-
|
|
188
|
+
if (expression.kind === 'and' || expression.kind === 'or') {
|
|
189
|
+
for (const subexpression of expression.expressions) {
|
|
190
|
+
collectExpressionDiagnostics(
|
|
191
|
+
subexpression,
|
|
192
|
+
diagnostics,
|
|
193
|
+
known_relation_names,
|
|
194
|
+
known_field_definitions,
|
|
195
|
+
diagnostic_path,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (expression.kind === 'not') {
|
|
203
|
+
collectExpressionDiagnostics(
|
|
204
|
+
expression.expression,
|
|
205
|
+
diagnostics,
|
|
206
|
+
known_relation_names,
|
|
207
|
+
known_field_definitions,
|
|
208
|
+
diagnostic_path,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (expression.kind === 'term') {
|
|
200
215
|
collectTermDiagnostics(
|
|
201
|
-
|
|
216
|
+
expression.term,
|
|
202
217
|
diagnostics,
|
|
203
218
|
known_relation_names,
|
|
204
219
|
known_field_definitions,
|
|
205
220
|
diagnostic_path,
|
|
206
221
|
);
|
|
222
|
+
|
|
223
|
+
return;
|
|
207
224
|
}
|
|
225
|
+
|
|
226
|
+
throw new Error('Unsupported query inspection expression.');
|
|
208
227
|
}
|
|
209
228
|
|
|
210
229
|
/**
|
|
@@ -228,8 +247,8 @@ function collectTermDiagnostics(
|
|
|
228
247
|
known_relation_names,
|
|
229
248
|
diagnostic_path,
|
|
230
249
|
);
|
|
231
|
-
|
|
232
|
-
term.
|
|
250
|
+
collectExpressionDiagnostics(
|
|
251
|
+
term.expression,
|
|
233
252
|
diagnostics,
|
|
234
253
|
known_relation_names,
|
|
235
254
|
known_field_definitions,
|
|
@@ -577,9 +596,9 @@ function formatJsonQueryInspection(query_inspection) {
|
|
|
577
596
|
}
|
|
578
597
|
|
|
579
598
|
return {
|
|
580
|
-
clauses: query_inspection.clauses,
|
|
581
599
|
diagnostics: [],
|
|
582
600
|
execution: query_inspection.execution,
|
|
601
|
+
expression: query_inspection.expression,
|
|
583
602
|
mode: 'explain',
|
|
584
603
|
source: query_inspection.query_source,
|
|
585
604
|
where: query_inspection.where_clause,
|
|
@@ -625,9 +644,12 @@ function renderTextQueryInspection(query_inspection, render_options) {
|
|
|
625
644
|
: String(query_inspection.execution?.limit ?? ''),
|
|
626
645
|
),
|
|
627
646
|
'',
|
|
628
|
-
`${render_options.label('
|
|
629
|
-
...
|
|
630
|
-
query_inspection.
|
|
647
|
+
`${render_options.label('expression:')}`,
|
|
648
|
+
...formatExplainExpressionBlock(
|
|
649
|
+
query_inspection.expression ?? {
|
|
650
|
+
expressions: [],
|
|
651
|
+
kind: 'and',
|
|
652
|
+
},
|
|
631
653
|
render_options,
|
|
632
654
|
'',
|
|
633
655
|
),
|
|
@@ -637,42 +659,49 @@ function renderTextQueryInspection(query_inspection, render_options) {
|
|
|
637
659
|
}
|
|
638
660
|
|
|
639
661
|
/**
|
|
640
|
-
* @param {
|
|
662
|
+
* @param {ParsedExpression} expression
|
|
641
663
|
* @param {{ header: (value: string) => string, label: (value: string) => string }} render_options
|
|
642
664
|
* @param {string} indentation
|
|
643
665
|
* @returns {string[]}
|
|
644
666
|
*/
|
|
645
|
-
function
|
|
646
|
-
|
|
647
|
-
|
|
667
|
+
function formatExplainExpressionBlock(expression, render_options, indentation) {
|
|
668
|
+
if (expression.kind === 'and') {
|
|
669
|
+
return formatExplainExpressionItems(
|
|
670
|
+
expression.expressions,
|
|
671
|
+
render_options,
|
|
672
|
+
indentation,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
648
675
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
676
|
+
return formatExplainExpressionItems(
|
|
677
|
+
[expression],
|
|
678
|
+
render_options,
|
|
679
|
+
indentation,
|
|
680
|
+
);
|
|
681
|
+
}
|
|
652
682
|
|
|
653
|
-
|
|
683
|
+
/**
|
|
684
|
+
* @param {ParsedExpression[]} expressions
|
|
685
|
+
* @param {{ header: (value: string) => string, label: (value: string) => string }} render_options
|
|
686
|
+
* @param {string} indentation
|
|
687
|
+
* @returns {string[]}
|
|
688
|
+
*/
|
|
689
|
+
function formatExplainExpressionItems(
|
|
690
|
+
expressions,
|
|
691
|
+
render_options,
|
|
692
|
+
indentation,
|
|
693
|
+
) {
|
|
694
|
+
/** @type {string[]} */
|
|
695
|
+
const output_lines = [];
|
|
654
696
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
697
|
+
expressions.forEach((expression, expression_index) => {
|
|
698
|
+
const expression_number = expression_index + 1;
|
|
658
699
|
|
|
659
700
|
output_lines.push(
|
|
660
|
-
`${indentation}
|
|
701
|
+
`${indentation}${expression_number}. ${formatExpressionSummary(expression)}`,
|
|
661
702
|
);
|
|
662
|
-
|
|
663
|
-
if (clause.term.aggregate_name === 'count') {
|
|
664
|
-
output_lines.push(
|
|
665
|
-
`${indentation} ${render_options.label('comparison:')} ${clause.term.comparison} ${clause.term.value}`,
|
|
666
|
-
);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
703
|
output_lines.push(
|
|
670
|
-
|
|
671
|
-
...formatExplainClauseBlock(
|
|
672
|
-
clause.term.clauses,
|
|
673
|
-
render_options,
|
|
674
|
-
`${indentation} `,
|
|
675
|
-
),
|
|
704
|
+
...formatExpressionDetailLines(expression, render_options, indentation),
|
|
676
705
|
);
|
|
677
706
|
});
|
|
678
707
|
|
|
@@ -680,17 +709,89 @@ function formatExplainClauseBlock(clauses, render_options, indentation) {
|
|
|
680
709
|
}
|
|
681
710
|
|
|
682
711
|
/**
|
|
683
|
-
* @param {
|
|
712
|
+
* @param {ParsedExpression} expression
|
|
684
713
|
* @returns {string}
|
|
685
714
|
*/
|
|
686
|
-
function
|
|
687
|
-
|
|
715
|
+
function formatExpressionSummary(expression) {
|
|
716
|
+
if (expression.kind === 'and') {
|
|
717
|
+
return 'all of';
|
|
718
|
+
}
|
|
688
719
|
|
|
689
|
-
if (
|
|
690
|
-
return
|
|
720
|
+
if (expression.kind === 'or') {
|
|
721
|
+
return 'any of';
|
|
691
722
|
}
|
|
692
723
|
|
|
693
|
-
|
|
724
|
+
if (expression.kind === 'not') {
|
|
725
|
+
if (expression.expression.kind === 'term') {
|
|
726
|
+
return `not ${formatTermSummary(expression.expression.term)}`;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return 'not';
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (expression.kind === 'term') {
|
|
733
|
+
return formatTermSummary(expression.term);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
throw new Error('Unsupported explain expression.');
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* @param {ParsedExpression} expression
|
|
741
|
+
* @param {{ header: (value: string) => string, label: (value: string) => string }} render_options
|
|
742
|
+
* @param {string} indentation
|
|
743
|
+
* @returns {string[]}
|
|
744
|
+
*/
|
|
745
|
+
function formatExpressionDetailLines(expression, render_options, indentation) {
|
|
746
|
+
if (expression.kind === 'and' || expression.kind === 'or') {
|
|
747
|
+
return formatExplainExpressionItems(
|
|
748
|
+
expression.expressions,
|
|
749
|
+
render_options,
|
|
750
|
+
`${indentation} `,
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (expression.kind === 'not') {
|
|
755
|
+
if (expression.expression.kind === 'term') {
|
|
756
|
+
return [];
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return formatExplainExpressionBlock(
|
|
760
|
+
expression.expression,
|
|
761
|
+
render_options,
|
|
762
|
+
`${indentation} `,
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (expression.kind !== 'term') {
|
|
767
|
+
throw new Error('Unsupported explain expression details.');
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (expression.term.kind !== 'aggregate') {
|
|
771
|
+
return [];
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/** @type {string[]} */
|
|
775
|
+
const output_lines = [
|
|
776
|
+
`${indentation} ${render_options.label('traversal:')} ${formatTraversal(expression.term.traversal)}`,
|
|
777
|
+
];
|
|
778
|
+
|
|
779
|
+
if (expression.term.aggregate_name === 'count') {
|
|
780
|
+
output_lines.push(
|
|
781
|
+
`${indentation} ${render_options.label('comparison:')} ${expression.term.comparison} ${expression.term.value}`,
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
output_lines.push(
|
|
786
|
+
`${indentation} ${render_options.label('nested expression:')}`,
|
|
787
|
+
...formatExplainExpressionBlock(
|
|
788
|
+
expression.term.expression,
|
|
789
|
+
render_options,
|
|
790
|
+
`${indentation} `,
|
|
791
|
+
),
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
return output_lines;
|
|
694
795
|
}
|
|
695
796
|
|
|
696
797
|
/**
|
|
@@ -698,6 +799,10 @@ function formatClauseSummary(clause) {
|
|
|
698
799
|
* @returns {string}
|
|
699
800
|
*/
|
|
700
801
|
function formatTermSummary(term) {
|
|
802
|
+
if (term.kind === 'aggregate') {
|
|
803
|
+
return `aggregate ${term.aggregate_name}`;
|
|
804
|
+
}
|
|
805
|
+
|
|
701
806
|
if (term.kind === 'field') {
|
|
702
807
|
return `${term.field_name} ${term.operator} ${term.value}`;
|
|
703
808
|
}
|
|
@@ -714,7 +819,7 @@ function formatTermSummary(term) {
|
|
|
714
819
|
return `${term.relation_name} = ${term.target_id}`;
|
|
715
820
|
}
|
|
716
821
|
|
|
717
|
-
throw new Error('Expected a
|
|
822
|
+
throw new Error('Expected a parsed query term.');
|
|
718
823
|
}
|
|
719
824
|
|
|
720
825
|
/**
|