@syncular/typegen 0.6.0 → 0.7.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/README.md +47 -42
- package/dist/cli.js +1 -4
- package/dist/emit-queries-dart.js +9 -8
- package/dist/emit-queries-kotlin.js +9 -8
- package/dist/emit-queries-swift.js +9 -8
- package/dist/emit-queries.js +10 -13
- package/dist/fmt.js +29 -4
- package/dist/lsp.js +16 -28
- package/dist/query.d.ts +6 -11
- package/dist/query.js +29 -0
- package/dist/syql-ast.d.ts +12 -13
- package/dist/syql-ast.js +26 -20
- package/dist/syql-lowering.js +65 -42
- package/dist/syql-parser.d.ts +19 -18
- package/dist/syql-parser.js +341 -93
- package/dist/syql-semantics.d.ts +4 -7
- package/dist/syql-semantics.js +121 -65
- package/dist/syql-template-parser.d.ts +5 -20
- package/dist/syql-template-parser.js +116 -109
- package/dist/syql-validator.d.ts +2 -2
- package/dist/syql-validator.js +251 -224
- package/package.json +3 -3
- package/src/cli.ts +1 -3
- package/src/emit-queries-dart.ts +7 -7
- package/src/emit-queries-kotlin.ts +7 -7
- package/src/emit-queries-swift.ts +7 -7
- package/src/emit-queries.ts +9 -12
- package/src/fmt.ts +32 -4
- package/src/lsp.ts +16 -28
- package/src/query.ts +42 -12
- package/src/syql-ast.ts +38 -34
- package/src/syql-lowering.ts +69 -43
- package/src/syql-parser.ts +472 -134
- package/src/syql-semantics.ts +158 -87
- package/src/syql-template-parser.ts +119 -163
- package/src/syql-validator.ts +330 -336
package/src/syql-semantics.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
SyqlPredicateDeclaration,
|
|
16
16
|
SyqlQueryDeclaration,
|
|
17
17
|
SyqlQueryParameter,
|
|
18
|
+
SyqlRangeParameter,
|
|
18
19
|
SyqlSyntaxFile,
|
|
19
20
|
SyqlValueType,
|
|
20
21
|
} from './syql-parser';
|
|
@@ -22,7 +23,6 @@ import type {
|
|
|
22
23
|
SyqlEmbeddedNode,
|
|
23
24
|
SyqlEmbeddedTemplate,
|
|
24
25
|
SyqlPredicateCall,
|
|
25
|
-
SyqlReactiveDirective,
|
|
26
26
|
} from './syql-template-parser';
|
|
27
27
|
|
|
28
28
|
export type SyqlSemanticErrorCode =
|
|
@@ -72,22 +72,16 @@ export interface SyqlLogicalPredicateNode {
|
|
|
72
72
|
export interface SyqlLogicalWhenNode {
|
|
73
73
|
readonly kind: 'when';
|
|
74
74
|
readonly controls: readonly string[];
|
|
75
|
+
readonly explicitPresence: readonly boolean[];
|
|
75
76
|
readonly controlSpans: readonly SyqlSourceSpan[];
|
|
76
77
|
readonly body: readonly SyqlLogicalTemplateNode[];
|
|
77
78
|
readonly span: SyqlSourceSpan;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
export interface SyqlLogicalReactiveNode {
|
|
81
|
-
readonly kind: 'scope' | 'cover';
|
|
82
|
-
readonly directive: SyqlReactiveDirective;
|
|
83
|
-
readonly span: SyqlSourceSpan;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
81
|
export type SyqlLogicalTemplateNode =
|
|
87
82
|
| SyqlLogicalSqlNode
|
|
88
83
|
| SyqlLogicalPredicateNode
|
|
89
|
-
| SyqlLogicalWhenNode
|
|
90
|
-
| SyqlLogicalReactiveNode;
|
|
84
|
+
| SyqlLogicalWhenNode;
|
|
91
85
|
|
|
92
86
|
export interface SyqlLogicalInput {
|
|
93
87
|
readonly parameter: SyqlQueryParameter;
|
|
@@ -131,9 +125,18 @@ interface QueryBindSymbol {
|
|
|
131
125
|
|
|
132
126
|
interface ExpansionContext {
|
|
133
127
|
readonly queryBinds?: ReadonlyMap<string, QueryBindSymbol>;
|
|
128
|
+
readonly ranges?: ReadonlyMap<string, SyqlRangeParameter>;
|
|
134
129
|
readonly requirements?: Map<string, SyqlValueType[]>;
|
|
135
130
|
}
|
|
136
131
|
|
|
132
|
+
export function syqlRangeStartBind(name: string): string {
|
|
133
|
+
return `__syqlRangeStart_${name}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function syqlRangeEndBind(name: string): string {
|
|
137
|
+
return `__syqlRangeEnd_${name}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
137
140
|
function predicateId(file: string, name: string): string {
|
|
138
141
|
return `${file}\0${name}`;
|
|
139
142
|
}
|
|
@@ -213,11 +216,8 @@ class SemanticAnalyzer {
|
|
|
213
216
|
const calls: SyqlResolvedPredicate[] = [];
|
|
214
217
|
for (const node of predicate.declaration.body.tree.nodes) {
|
|
215
218
|
if (node.kind !== 'predicate-call') continue;
|
|
216
|
-
const target = this.#
|
|
217
|
-
|
|
218
|
-
node.name,
|
|
219
|
-
node.span,
|
|
220
|
-
);
|
|
219
|
+
const target = this.#lookupCall(predicate.module, node.name);
|
|
220
|
+
if (target === undefined) continue;
|
|
221
221
|
this.#checkArity(node.arguments.length, target, node.span);
|
|
222
222
|
calls.push(target);
|
|
223
223
|
}
|
|
@@ -270,6 +270,7 @@ class SemanticAnalyzer {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
} else if (node.kind === 'predicate-call') {
|
|
273
|
+
const target = this.#lookupCall(predicate.module, node.name);
|
|
273
274
|
for (const argument of node.arguments) {
|
|
274
275
|
if (!declared.has(argument.name)) {
|
|
275
276
|
this.#fail(
|
|
@@ -279,6 +280,7 @@ class SemanticAnalyzer {
|
|
|
279
280
|
);
|
|
280
281
|
}
|
|
281
282
|
}
|
|
283
|
+
if (target === undefined) continue;
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
286
|
}
|
|
@@ -299,7 +301,11 @@ class SemanticAnalyzer {
|
|
|
299
301
|
if (token.kind === 'bind') used.add(bindName(token));
|
|
300
302
|
}
|
|
301
303
|
} else if (node.kind === 'predicate-call') {
|
|
302
|
-
const target = scope.get(node.name)
|
|
304
|
+
const target = scope.get(node.name);
|
|
305
|
+
if (target === undefined) {
|
|
306
|
+
for (const argument of node.arguments) used.add(argument.name);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
303
309
|
const targetUsed = computeUsed(target);
|
|
304
310
|
target.declaration.parameters.forEach((formal, index) => {
|
|
305
311
|
if (targetUsed.has(formal.name)) {
|
|
@@ -350,7 +356,8 @@ class SemanticAnalyzer {
|
|
|
350
356
|
>;
|
|
351
357
|
for (const node of predicate.declaration.body.tree.nodes) {
|
|
352
358
|
if (node.kind !== 'predicate-call') continue;
|
|
353
|
-
const target = scope.get(node.name)
|
|
359
|
+
const target = scope.get(node.name);
|
|
360
|
+
if (target === undefined) continue;
|
|
354
361
|
const targetConstraints = constraintsFor(target);
|
|
355
362
|
target.declaration.parameters.forEach((formal, index) => {
|
|
356
363
|
const actual = node.arguments[index];
|
|
@@ -402,12 +409,19 @@ class SemanticAnalyzer {
|
|
|
402
409
|
query: SyqlQueryDeclaration,
|
|
403
410
|
): SyqlLogicalQuery {
|
|
404
411
|
const bindSymbols = this.#queryBindSymbols(query);
|
|
412
|
+
const ranges = new Map(
|
|
413
|
+
query.parameters.flatMap((parameter) =>
|
|
414
|
+
parameter.kind === 'range'
|
|
415
|
+
? [[parameter.name, parameter] as const]
|
|
416
|
+
: [],
|
|
417
|
+
),
|
|
418
|
+
);
|
|
405
419
|
const requirements = new Map<string, SyqlValueType[]>();
|
|
406
420
|
const template = this.#expandTemplate(
|
|
407
421
|
module,
|
|
408
|
-
query.
|
|
422
|
+
query.statement.tree,
|
|
409
423
|
new Map(),
|
|
410
|
-
{ queryBinds: bindSymbols, requirements },
|
|
424
|
+
{ queryBinds: bindSymbols, ranges, requirements },
|
|
411
425
|
);
|
|
412
426
|
const conditions: SyqlLogicalWhenNode[] = [];
|
|
413
427
|
this.#collectConditions(template, conditions);
|
|
@@ -435,8 +449,20 @@ class SemanticAnalyzer {
|
|
|
435
449
|
): ReadonlyMap<string, QueryBindSymbol> {
|
|
436
450
|
const symbols = new Map<string, QueryBindSymbol>();
|
|
437
451
|
for (const parameter of query.parameters) {
|
|
438
|
-
if (parameter.kind === '
|
|
439
|
-
|
|
452
|
+
if (parameter.kind === 'range') {
|
|
453
|
+
for (const name of [
|
|
454
|
+
syqlRangeStartBind(parameter.name),
|
|
455
|
+
syqlRangeEndBind(parameter.name),
|
|
456
|
+
]) {
|
|
457
|
+
symbols.set(name, {
|
|
458
|
+
name,
|
|
459
|
+
parameter,
|
|
460
|
+
...(parameter.optional ? { controller: parameter.name } : {}),
|
|
461
|
+
...(parameter.type === undefined ? {} : { type: parameter.type }),
|
|
462
|
+
span: parameter.nameSpan,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
} else if (parameter.kind === 'group') {
|
|
440
466
|
for (const member of parameter.members) {
|
|
441
467
|
symbols.set(member.name, {
|
|
442
468
|
name: member.name,
|
|
@@ -477,48 +503,31 @@ class SemanticAnalyzer {
|
|
|
477
503
|
context: ExpansionContext,
|
|
478
504
|
): SyqlLogicalTemplateNode {
|
|
479
505
|
if (node.kind === 'raw') {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
parts[parts.length - 1] = {
|
|
486
|
-
kind: 'text',
|
|
487
|
-
text: previous.text + token.text,
|
|
488
|
-
};
|
|
489
|
-
} else {
|
|
490
|
-
parts.push({ kind: 'text', text: token.text });
|
|
491
|
-
}
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
const authored = bindName(token);
|
|
495
|
-
const substituted = substitution.get(authored);
|
|
496
|
-
parts.push({
|
|
497
|
-
kind: 'bind',
|
|
498
|
-
name: substituted?.name ?? authored,
|
|
499
|
-
span: token.span,
|
|
500
|
-
origins:
|
|
501
|
-
substituted === undefined
|
|
502
|
-
? [token.span]
|
|
503
|
-
: [...substituted.origins, token.span],
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
return { kind: 'sql', parts, span: node.span };
|
|
506
|
+
return {
|
|
507
|
+
kind: 'sql',
|
|
508
|
+
parts: this.#expandSqlTokens(node.tokens, substitution, context),
|
|
509
|
+
span: node.span,
|
|
510
|
+
};
|
|
507
511
|
}
|
|
508
512
|
if (node.kind === 'when') {
|
|
509
513
|
return {
|
|
510
514
|
kind: 'when',
|
|
511
515
|
controls: node.controls,
|
|
516
|
+
explicitPresence: node.explicitPresence,
|
|
512
517
|
controlSpans: node.controlSpans,
|
|
513
518
|
body: this.#expandTemplate(module, node.body, substitution, context),
|
|
514
519
|
span: node.span,
|
|
515
520
|
};
|
|
516
521
|
}
|
|
517
|
-
if (node.kind === 'scope' || node.kind === 'cover') {
|
|
518
|
-
return { kind: node.kind, directive: node, span: node.span };
|
|
519
|
-
}
|
|
520
522
|
const call = node as SyqlPredicateCall;
|
|
521
|
-
const target = this.#
|
|
523
|
+
const target = this.#lookupCall(module, call.name);
|
|
524
|
+
if (target === undefined) {
|
|
525
|
+
return {
|
|
526
|
+
kind: 'sql',
|
|
527
|
+
parts: this.#expandSqlTokens(call.tokens, substitution, context),
|
|
528
|
+
span: call.span,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
522
531
|
this.#checkArity(call.arguments.length, target, call.span);
|
|
523
532
|
const targetSubstitution = new Map<string, ActualBind>();
|
|
524
533
|
target.declaration.parameters.forEach((formal, index) => {
|
|
@@ -549,6 +558,56 @@ class SemanticAnalyzer {
|
|
|
549
558
|
};
|
|
550
559
|
}
|
|
551
560
|
|
|
561
|
+
#expandSqlTokens(
|
|
562
|
+
tokens: readonly SyqlToken[],
|
|
563
|
+
substitution: ReadonlyMap<string, ActualBind>,
|
|
564
|
+
context: ExpansionContext,
|
|
565
|
+
): readonly SyqlSqlPart[] {
|
|
566
|
+
const parts: SyqlSqlPart[] = [];
|
|
567
|
+
const pushText = (text: string): void => {
|
|
568
|
+
const previous = parts[parts.length - 1];
|
|
569
|
+
if (previous?.kind === 'text') {
|
|
570
|
+
parts[parts.length - 1] = { kind: 'text', text: previous.text + text };
|
|
571
|
+
} else parts.push({ kind: 'text', text });
|
|
572
|
+
};
|
|
573
|
+
const pushBind = (
|
|
574
|
+
name: string,
|
|
575
|
+
token: SyqlToken,
|
|
576
|
+
origins: readonly SyqlSourceSpan[],
|
|
577
|
+
): void => {
|
|
578
|
+
parts.push({ kind: 'bind', name, span: token.span, origins });
|
|
579
|
+
};
|
|
580
|
+
for (const token of tokens) {
|
|
581
|
+
if (token.kind !== 'bind') {
|
|
582
|
+
pushText(token.text);
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
const authored = bindName(token);
|
|
586
|
+
const substituted = substitution.get(authored);
|
|
587
|
+
if (substituted !== undefined) {
|
|
588
|
+
const parameter = context.ranges?.get(substituted.name);
|
|
589
|
+
if (parameter !== undefined) {
|
|
590
|
+
this.#fail(
|
|
591
|
+
'SYQL5011_TYPE_CONFLICT',
|
|
592
|
+
token.span,
|
|
593
|
+
`range input ${parameter.name} cannot be passed to a predicate`,
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
pushBind(substituted.name, token, [...substituted.origins, token.span]);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const range = context.ranges?.get(authored);
|
|
600
|
+
if (range !== undefined) {
|
|
601
|
+
pushBind(syqlRangeStartBind(range.name), token, [token.span]);
|
|
602
|
+
pushText(' and ');
|
|
603
|
+
pushBind(syqlRangeEndBind(range.name), token, [token.span]);
|
|
604
|
+
} else {
|
|
605
|
+
pushBind(authored, token, [token.span]);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return parts;
|
|
609
|
+
}
|
|
610
|
+
|
|
552
611
|
#collectConditions(
|
|
553
612
|
nodes: readonly SyqlLogicalTemplateNode[],
|
|
554
613
|
out: SyqlLogicalWhenNode[],
|
|
@@ -575,11 +634,25 @@ class SemanticAnalyzer {
|
|
|
575
634
|
`unknown when control ${JSON.stringify(name)}`,
|
|
576
635
|
);
|
|
577
636
|
}
|
|
578
|
-
if (
|
|
637
|
+
if (condition.explicitPresence[index] && !parameter.optional) {
|
|
638
|
+
this.#fail(
|
|
639
|
+
'SYQL5008_INVALID_CONTROL',
|
|
640
|
+
condition.controlSpans[index] as SyqlSourceSpan,
|
|
641
|
+
`present(${name}) requires an optional input`,
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
if (
|
|
645
|
+
!parameter.optional &&
|
|
646
|
+
!(
|
|
647
|
+
parameter.kind === 'value' &&
|
|
648
|
+
parameter.type?.base === 'boolean' &&
|
|
649
|
+
parameter.default === false
|
|
650
|
+
)
|
|
651
|
+
) {
|
|
579
652
|
this.#fail(
|
|
580
653
|
'SYQL5008_INVALID_CONTROL',
|
|
581
654
|
condition.controlSpans[index] as SyqlSourceSpan,
|
|
582
|
-
`required
|
|
655
|
+
`required input ${name} cannot control when; use an optional input or a bool defaulted to false`,
|
|
583
656
|
);
|
|
584
657
|
}
|
|
585
658
|
});
|
|
@@ -644,20 +717,6 @@ class SemanticAnalyzer {
|
|
|
644
717
|
next.add(control);
|
|
645
718
|
}
|
|
646
719
|
walk(node.body, next);
|
|
647
|
-
} else {
|
|
648
|
-
for (const binding of node.directive.bindings) {
|
|
649
|
-
for (const value of binding.values) {
|
|
650
|
-
record(
|
|
651
|
-
{
|
|
652
|
-
kind: 'bind',
|
|
653
|
-
name: value.name,
|
|
654
|
-
span: value.span,
|
|
655
|
-
origins: [value.span],
|
|
656
|
-
},
|
|
657
|
-
active,
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
720
|
}
|
|
662
721
|
}
|
|
663
722
|
};
|
|
@@ -679,7 +738,11 @@ class SemanticAnalyzer {
|
|
|
679
738
|
const parameter = query.parameters.find(
|
|
680
739
|
(item) => item.name === control,
|
|
681
740
|
);
|
|
682
|
-
if (
|
|
741
|
+
if (
|
|
742
|
+
parameter?.kind === 'value' &&
|
|
743
|
+
parameter.default === undefined &&
|
|
744
|
+
!conditionUses.has(control)
|
|
745
|
+
) {
|
|
683
746
|
this.#fail(
|
|
684
747
|
'SYQL5010_UNUSED_CONTROL',
|
|
685
748
|
condition.controlSpans[index] as SyqlSourceSpan,
|
|
@@ -696,12 +759,27 @@ class SemanticAnalyzer {
|
|
|
696
759
|
`when(${control}) does not use a member of group ${control}`,
|
|
697
760
|
);
|
|
698
761
|
}
|
|
762
|
+
if (
|
|
763
|
+
parameter?.kind === 'range' &&
|
|
764
|
+
!conditionUses.has(syqlRangeStartBind(parameter.name))
|
|
765
|
+
) {
|
|
766
|
+
this.#fail(
|
|
767
|
+
'SYQL5010_UNUSED_CONTROL',
|
|
768
|
+
condition.controlSpans[index] as SyqlSourceSpan,
|
|
769
|
+
`when(${control}) does not use range :${control}`,
|
|
770
|
+
);
|
|
771
|
+
}
|
|
699
772
|
});
|
|
700
773
|
}
|
|
701
774
|
|
|
702
775
|
for (const parameter of query.parameters) {
|
|
703
|
-
if (parameter.kind === '
|
|
704
|
-
if (!usedControls.has(parameter.name)) {
|
|
776
|
+
if (parameter.kind === 'range') {
|
|
777
|
+
if (parameter.optional && !usedControls.has(parameter.name)) {
|
|
778
|
+
this.#unusedInput(parameter.nameSpan, parameter.name);
|
|
779
|
+
}
|
|
780
|
+
const start = syqlRangeStartBind(parameter.name);
|
|
781
|
+
const end = syqlRangeEndBind(parameter.name);
|
|
782
|
+
if (!allUses.has(start) || !allUses.has(end)) {
|
|
705
783
|
this.#unusedInput(parameter.nameSpan, parameter.name);
|
|
706
784
|
}
|
|
707
785
|
} else if (parameter.kind === 'group') {
|
|
@@ -717,7 +795,10 @@ class SemanticAnalyzer {
|
|
|
717
795
|
if (!usedControls.has(parameter.name) || !allUses.has(parameter.name)) {
|
|
718
796
|
this.#unusedInput(parameter.nameSpan, parameter.name);
|
|
719
797
|
}
|
|
720
|
-
} else if (
|
|
798
|
+
} else if (
|
|
799
|
+
!allUses.has(parameter.name) &&
|
|
800
|
+
!(parameter.default !== undefined && usedControls.has(parameter.name))
|
|
801
|
+
) {
|
|
721
802
|
this.#unusedInput(parameter.nameSpan, parameter.name);
|
|
722
803
|
}
|
|
723
804
|
}
|
|
@@ -772,8 +853,7 @@ class SemanticAnalyzer {
|
|
|
772
853
|
parameter: SyqlQueryParameter,
|
|
773
854
|
resolved: ReadonlyMap<string, SyqlValueType>,
|
|
774
855
|
): SyqlValueType | undefined {
|
|
775
|
-
if (parameter.kind === '
|
|
776
|
-
if (parameter.kind === 'group') {
|
|
856
|
+
if (parameter.kind === 'range' || parameter.kind === 'group') {
|
|
777
857
|
// Group members carry their own types; the logical input itself has no
|
|
778
858
|
// scalar type. L3 consumes the per-member resolved map directly.
|
|
779
859
|
return undefined;
|
|
@@ -781,20 +861,11 @@ class SemanticAnalyzer {
|
|
|
781
861
|
return parameter.type ?? resolved.get(parameter.name);
|
|
782
862
|
}
|
|
783
863
|
|
|
784
|
-
#
|
|
864
|
+
#lookupCall(
|
|
785
865
|
module: SyqlSyntaxFile,
|
|
786
866
|
name: string,
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const target = this.#scopes.get(module.file)?.get(name);
|
|
790
|
-
if (target === undefined) {
|
|
791
|
-
this.#fail(
|
|
792
|
-
'SYQL5001_UNKNOWN_PREDICATE',
|
|
793
|
-
span,
|
|
794
|
-
`unknown predicate @${name} in ${module.file}`,
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
return target;
|
|
867
|
+
): SyqlResolvedPredicate | undefined {
|
|
868
|
+
return this.#scopes.get(module.file)?.get(name);
|
|
798
869
|
}
|
|
799
870
|
|
|
800
871
|
#checkArity(
|
|
@@ -807,7 +878,7 @@ class SemanticAnalyzer {
|
|
|
807
878
|
this.#fail(
|
|
808
879
|
'SYQL5003_PREDICATE_ARITY',
|
|
809
880
|
span,
|
|
810
|
-
|
|
881
|
+
`${target.declaration.name} expects ${expected} argument(s), got ${actual}`,
|
|
811
882
|
);
|
|
812
883
|
}
|
|
813
884
|
}
|
|
@@ -853,7 +924,7 @@ export function renderSyqlLogicalTemplate(
|
|
|
853
924
|
if (node.kind === 'when') {
|
|
854
925
|
return `when(${node.controls.join(', ')}) {${renderSyqlLogicalTemplate(node.body)}}`;
|
|
855
926
|
}
|
|
856
|
-
|
|
927
|
+
throw new Error('unknown SYQL logical template node');
|
|
857
928
|
})
|
|
858
929
|
.join('');
|
|
859
930
|
}
|