sql-typechecker 0.0.1

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.
@@ -0,0 +1,1869 @@
1
+ import {
2
+ AlterTableStatement,
3
+ ColumnConstraint,
4
+ CreateFunctionStatement,
5
+ CreateMaterializedViewStatement,
6
+ CreateTableStatement,
7
+ CreateViewStatement,
8
+ DataTypeDef,
9
+ DeleteStatement,
10
+ Expr,
11
+ ExprBinary,
12
+ ExprCall,
13
+ ExprRef,
14
+ ExprUnary,
15
+ From,
16
+ InsertStatement,
17
+ Name,
18
+ // astVisitor,
19
+ NodeLocation,
20
+ parse,
21
+ PGNode,
22
+ QName,
23
+ SelectedColumn,
24
+ SelectStatement,
25
+ Statement,
26
+ toSql,
27
+ } from "pgsql-ast-parser";
28
+ import { builtincasts } from "./builtincasts";
29
+ import { builtinoperators } from "./builtinoperators";
30
+ import { builtinUnaryOperators } from "./builtinunaryoperators";
31
+
32
+ export type Type = SimpleT | SetT;
33
+ export type AnyScalarT = {
34
+ kind: "anyscalar";
35
+ };
36
+ export type NullableT<T> = {
37
+ kind: "nullable";
38
+ typevar: T;
39
+ };
40
+ export type ArrayT<T> = {
41
+ kind: "array";
42
+ subtype: "array" | "list";
43
+ typevar: T;
44
+ };
45
+ export type ScalarT = {
46
+ kind: "scalar";
47
+ name: QName;
48
+ };
49
+ export type VoidT = {
50
+ // represents nothing, so zero rows, like when doing an INSERT without RETURNING
51
+ kind: "void";
52
+ };
53
+ export type SimpleT =
54
+ | AnyScalarT
55
+ | ScalarT
56
+ | NullableT<any>
57
+ | ArrayT<any>
58
+ | NullableT<ArrayT<any>>;
59
+
60
+ type Field = {
61
+ name: Name | null;
62
+ type: SimpleT;
63
+ };
64
+ export type SetT = {
65
+ kind: "set";
66
+ fields: Field[];
67
+ };
68
+
69
+ export const BuiltinTypes = {
70
+ Boolean: {
71
+ kind: "scalar",
72
+ name: { name: "boolean" },
73
+ },
74
+ Smallint: {
75
+ kind: "scalar",
76
+ name: { name: "smallint" },
77
+ },
78
+ Integer: {
79
+ kind: "scalar",
80
+ name: { name: "integer" },
81
+ },
82
+ Numeric: {
83
+ kind: "scalar",
84
+ name: { name: "numeric" },
85
+ },
86
+ Bigint: {
87
+ kind: "scalar",
88
+ name: { name: "bigint" },
89
+ },
90
+ Text: {
91
+ kind: "scalar",
92
+ name: { name: "text" },
93
+ },
94
+ AnyScalar: {
95
+ kind: "anyscalar",
96
+ },
97
+ Date: {
98
+ kind: "scalar",
99
+ name: { name: "date" },
100
+ },
101
+ Time: {
102
+ kind: "scalar",
103
+ name: { name: "time" },
104
+ },
105
+ Timestamp: {
106
+ kind: "scalar",
107
+ name: { name: "timestamp" },
108
+ },
109
+ Interval: {
110
+ kind: "scalar",
111
+ name: { name: "interval" },
112
+ },
113
+ Json: {
114
+ kind: "scalar",
115
+ name: { name: "json" },
116
+ },
117
+ Jsonb: {
118
+ kind: "scalar",
119
+ name: { name: "jsonb" },
120
+ },
121
+ } as const;
122
+
123
+ function requireBoolean(e: Expr, t: Type): void {
124
+ if (
125
+ (t.kind === "scalar" && eqQNames(t.name, BuiltinTypes.Boolean.name)) ||
126
+ (t.kind === "nullable" &&
127
+ t.typevar.kind === "scalar" &&
128
+ eqQNames(t.typevar.name, BuiltinTypes.Boolean.name))
129
+ ) {
130
+ return;
131
+ } else {
132
+ throw new TypeMismatch(e, {
133
+ expected: BuiltinTypes.Boolean,
134
+ actual: t,
135
+ });
136
+ }
137
+ }
138
+
139
+ export const BuiltinTypeConstructors = {
140
+ Nullable: <T>(t: T): NullableT<T> => ({
141
+ kind: "nullable",
142
+ typevar: t,
143
+ }),
144
+ Array: <T>(t: T): ArrayT<T> => ({
145
+ kind: "array",
146
+ subtype: "array",
147
+ typevar: t,
148
+ }),
149
+ List: <T>(t: T): ArrayT<T> => ({
150
+ kind: "array",
151
+ subtype: "list",
152
+ typevar: t,
153
+ }),
154
+ } as const;
155
+
156
+ function isNullable(t: Type) {
157
+ return t.kind === "nullable";
158
+ }
159
+
160
+ export type Global = {
161
+ readonly tables: ReadonlyArray<{
162
+ readonly name: QName;
163
+ readonly rel: SetT;
164
+ // readonly defaults: Name[];
165
+ }>;
166
+ readonly views: ReadonlyArray<{
167
+ readonly name: QName;
168
+ readonly rel: SetT;
169
+ }>;
170
+ };
171
+
172
+ function eqType(t1: Type, t2: Type): boolean {
173
+ if (t1.kind === "anyscalar") {
174
+ return t2.kind === "anyscalar";
175
+ } else if (t1.kind === "nullable") {
176
+ return t2.kind === "nullable" && eqType(t1.typevar, t2.typevar);
177
+ } else if (t1.kind === "array") {
178
+ return (
179
+ t2.kind === "array" &&
180
+ t1.subtype === t2.subtype &&
181
+ eqType(t1.typevar, t2.typevar)
182
+ );
183
+ } else if (t1.kind === "scalar") {
184
+ return t2.kind === "scalar" && eqQNames(t1.name, t2.name);
185
+ } else if (t1.kind === "set") {
186
+ return (
187
+ t2.kind === "set" &&
188
+ t1.fields.length == t2.fields.length &&
189
+ t1.fields.reduce(
190
+ (acc: boolean, field, i) =>
191
+ acc &&
192
+ field.name?.name === t2.fields[i].name?.name &&
193
+ eqType(field.type, t2.fields[i].type),
194
+ true
195
+ )
196
+ );
197
+ } else {
198
+ return checkAllCasesHandled(t1);
199
+ }
200
+ }
201
+
202
+ function unify(e: Expr, source: Type, target: Type): Type {
203
+ if (source.kind === "set") {
204
+ if (target.kind === "set") {
205
+ return unifySets(e, source, target);
206
+ } else {
207
+ return unifySetWithSimple(e, source, target);
208
+ }
209
+ } else {
210
+ if (target.kind === "set") {
211
+ return unifySetWithSimple(e, target, source);
212
+ } else {
213
+ return unifySimples(e, source, target);
214
+ }
215
+ }
216
+ }
217
+
218
+ function cast(e: Expr, source: Type, target: Type, casttype: CastType): void {
219
+ if (source.kind === "set") {
220
+ if (target.kind === "set") {
221
+ castSets(e, source, target, casttype);
222
+ } else {
223
+ castSetToSimple(e, source, target, casttype);
224
+ }
225
+ } else {
226
+ if (target.kind === "set") {
227
+ castSimpleToSet(e, source, target, casttype);
228
+ } else {
229
+ castSimples(e, source, target, casttype);
230
+ }
231
+ }
232
+ }
233
+
234
+ function castSets(
235
+ e: Expr,
236
+ source: SetT,
237
+ target: SetT,
238
+ casttype: CastType
239
+ ): void {
240
+ if (source.fields.length !== target.fields.length) {
241
+ throw new TypeMismatch(e, { expected: source, actual: target });
242
+ }
243
+ source.fields.forEach((sf, i) => {
244
+ const tf = target.fields[i];
245
+ castSimples(e, sf.type, tf.type, casttype);
246
+ });
247
+ }
248
+
249
+ function unifySets(e: Expr, source: SetT, target: SetT): SetT {
250
+ if (source.fields.length !== target.fields.length) {
251
+ throw new TypeMismatch(e, { expected: source, actual: target });
252
+ }
253
+ const newFields = source.fields.map((sf, i) => {
254
+ const tf = target.fields[i];
255
+ const t = unifySimples(e, sf.type, tf.type);
256
+ return {
257
+ name: sf.name || tf.name,
258
+ type: t,
259
+ };
260
+ });
261
+ return {
262
+ kind: "set",
263
+ fields: newFields,
264
+ };
265
+ }
266
+
267
+ function castSetToSimple(
268
+ e: Expr,
269
+ source: SetT,
270
+ target: SimpleT,
271
+ casttype: CastType
272
+ ): void {
273
+ // TODO add warning if no LIMIT 1
274
+ if (source.fields.length === 0) {
275
+ throw new TypeMismatch(
276
+ e,
277
+ { expected: source, actual: target },
278
+ "Set has no fields"
279
+ );
280
+ }
281
+ if (source.fields.length > 1) {
282
+ throw new TypeMismatch(
283
+ e,
284
+ { expected: source, actual: target },
285
+ "More than one row returned by a subquery used as an expression"
286
+ );
287
+ }
288
+ castSimples(e, source.fields[0].type, target, casttype);
289
+ }
290
+
291
+ function castSimpleToSet(
292
+ e: Expr,
293
+ source: SimpleT,
294
+ target: SetT,
295
+ casttype: CastType
296
+ ): void {
297
+ // TODO add warning if no LIMIT 1
298
+ if (target.fields.length === 0) {
299
+ throw new TypeMismatch(
300
+ e,
301
+ { expected: source, actual: target },
302
+ "Set has no fields"
303
+ );
304
+ }
305
+ if (target.fields.length > 1) {
306
+ throw new TypeMismatch(
307
+ e,
308
+ { expected: source, actual: target },
309
+ "More than one row returned by a subquery used as an expression"
310
+ );
311
+ }
312
+ castSimples(e, source, target.fields[0].type, casttype);
313
+ }
314
+
315
+ function unifySetWithSimple(e: Expr, source: SetT, target: SimpleT): SimpleT {
316
+ // TODO add warning if no LIMIT 1
317
+ if (source.fields.length === 0) {
318
+ throw new TypeMismatch(
319
+ e,
320
+ { expected: source, actual: target },
321
+ "Set has no fields"
322
+ );
323
+ }
324
+ if (source.fields.length > 1) {
325
+ throw new TypeMismatch(
326
+ e,
327
+ { expected: source, actual: target },
328
+ "More than one row returned by a subquery used as an expression"
329
+ );
330
+ }
331
+ return unifySimples(e, source.fields[0].type, target);
332
+ }
333
+
334
+ function castSimples(
335
+ e: Expr,
336
+ source: SimpleT,
337
+ target: SimpleT,
338
+ type: CastType
339
+ ): void {
340
+ // T -> Nullable<T> is a universal cast
341
+ if (target.kind === "nullable" && source.kind === "scalar") {
342
+ return castSimples(e, source, target.typevar, type);
343
+ }
344
+
345
+ if (source.kind === "anyscalar") {
346
+ // ok
347
+ return;
348
+ } else if (source.kind === "nullable") {
349
+ if (target.kind === "nullable") {
350
+ return castSimples(e, source.typevar, target.typevar, type);
351
+ } else {
352
+ throw new TypeMismatch(e, { expected: source, actual: target });
353
+ }
354
+ } else if (source.kind === "array") {
355
+ if (target.kind === "array" && source.subtype === target.subtype) {
356
+ return castSimples(e, source.typevar, target.typevar, type);
357
+ } else {
358
+ throw new TypeMismatch(e, { expected: source, actual: target });
359
+ }
360
+ } else if (source.kind === "scalar") {
361
+ if (target.kind === "scalar") {
362
+ return castScalars(e, source, target, type);
363
+ } else {
364
+ // simple - parametrized
365
+ throw new TypeMismatch(e, { expected: source, actual: target });
366
+ }
367
+ } else {
368
+ return checkAllCasesHandled(source);
369
+ }
370
+ }
371
+
372
+ // Get the "biggest" type back, if implicit casting is possible
373
+ function unifySimples(e: Expr, source: SimpleT, target: SimpleT): SimpleT {
374
+ try {
375
+ castSimples(e, source, target, "implicit");
376
+ return target;
377
+ } catch {
378
+ castSimples(e, target, source, "implicit");
379
+ return source;
380
+ }
381
+ }
382
+
383
+ //www.postgresql.org/docs/current/sql-createcast.html
384
+ // If they "fit into" eachother, you get the "biggest" type back
385
+ // eg: smallint fits into integer
386
+ // otherwise, this function will return null = does not unify
387
+ //
388
+ // https://www.postgresql.org/docs/7.3/typeconv.html
389
+ // https://www.postgresql.org/docs/current/sql-createcast.html
390
+ // 3 kinds of casts:
391
+ // * Implicit: can happen anywhere
392
+ // * In Assignment: (Only) in insert/update statements, when trying to "fit" data into table columns
393
+ // * Explicit: can happen when explicitely calling the CAST function
394
+ //
395
+ export type CastType = "implicit" | "assignment" | "explicit";
396
+ function castScalars(
397
+ e: Expr,
398
+ source: ScalarT,
399
+ target: ScalarT,
400
+ type: CastType
401
+ ): void {
402
+ // list casts = \dC+
403
+
404
+ const casts = builtincasts;
405
+
406
+ if (eqQNames(source.name, target.name)) {
407
+ return;
408
+ } else {
409
+ const matchingCast = casts.find(
410
+ (c) =>
411
+ eqQNames(c.source.name, source.name) &&
412
+ eqQNames(c.target.name, target.name) &&
413
+ // Implicit casts can always be done explicitly as well
414
+ // Not the other way around: some casts are dangerous so you need to ASK for them
415
+ (c.type === type ||
416
+ (c.type === "implicit" && type === "assignment") ||
417
+ (c.type === "implicit" && type === "explicit"))
418
+ );
419
+ if (matchingCast) {
420
+ // ok
421
+ return;
422
+ } else {
423
+ throw new TypeMismatch(e, { expected: target, actual: source });
424
+ }
425
+ }
426
+ }
427
+
428
+ // Lexical scoping
429
+ export type Context = {
430
+ readonly froms: ReadonlyArray<{
431
+ // used in INSERT as well, name is not great
432
+ readonly name: Name;
433
+ readonly type: SetT;
434
+ }>;
435
+ readonly decls: ReadonlyArray<{
436
+ readonly name: Name;
437
+ readonly type:
438
+ | Type
439
+ | VoidT /* with statement can return bindings of type void */;
440
+ // | ScalarT // declare bindings and function parameters
441
+ // | ParametrizedT<ScalarT> // declare bindings and function parameters
442
+ // | SetT; // with, (temp tables?)
443
+ }>;
444
+ };
445
+
446
+ export function notImplementedYet(node: PGNode | null): any {
447
+ throw new NotImplementedYet(node);
448
+ }
449
+
450
+ function normalizeTypeName(s: string): string {
451
+ if (s === "int8") {
452
+ return "bigint";
453
+ }
454
+ if (s === "int" || s === "int4") {
455
+ return "integer";
456
+ }
457
+ if (s === "int2") {
458
+ return "smallint";
459
+ }
460
+ if (s === "decimal") {
461
+ return "numeric";
462
+ }
463
+ if (s === "bool") {
464
+ return "boolean";
465
+ }
466
+ return s;
467
+ }
468
+
469
+ function mkType(t: DataTypeDef, cs: ColumnConstraint[]): SimpleT {
470
+ if (t.kind === "array") {
471
+ if (t.arrayOf.kind === "array") {
472
+ throw new Error("Array or array not supported");
473
+ } else {
474
+ return BuiltinTypeConstructors.Array({
475
+ kind: "scalar",
476
+ name: { ...t.arrayOf, name: normalizeTypeName(t.arrayOf.name) },
477
+ });
478
+ }
479
+ } else {
480
+ const t_: ScalarT = {
481
+ kind: "scalar",
482
+ name: { ...t, name: normalizeTypeName(t.name) },
483
+ };
484
+
485
+ const notnullable = cs.some(
486
+ (c) => c.type === "not null" || c.type === "primary key"
487
+ );
488
+ return notnullable ? t_ : nullify(t_);
489
+ }
490
+ }
491
+
492
+ function doCreateTable(g: Global, s: CreateTableStatement): Global {
493
+ if ((s.inherits || []).length !== 0) {
494
+ // Reusing the columns is not hard (see LIKE TABLE)
495
+ // but subsequent alters to the parent table(s) also alter the children
496
+ // so that's a bit more work. Not a huge amount though, just didnt do it yet
497
+ return notImplementedYet(s);
498
+ }
499
+ const fields = s.columns.reduce(function (acc: Field[], c) {
500
+ if (c.kind === "like table") {
501
+ const targetTable = c.like;
502
+ const found = g.tables.find((t) => eqQNames(t.name, targetTable));
503
+ if (!found) {
504
+ throw new UnknownIdentifier(c.like, targetTable);
505
+ }
506
+ return acc.concat(found.rel.fields);
507
+ } else {
508
+ return acc.concat({
509
+ name: c.name,
510
+ type: mkType(c.dataType, c.constraints || []),
511
+ });
512
+ }
513
+ }, []);
514
+ return {
515
+ ...g,
516
+ tables: g.tables.concat({
517
+ name: s.name,
518
+ rel: {
519
+ kind: "set",
520
+ fields,
521
+ },
522
+ }),
523
+ };
524
+ }
525
+ function doCreateView(
526
+ _g: Global,
527
+ s: CreateViewStatement | CreateMaterializedViewStatement
528
+ ): Global {
529
+ return _g;
530
+ // return notImplementedYet(s);
531
+ }
532
+ function doAlterTable(_g: Global, s: AlterTableStatement): Global {
533
+ return notImplementedYet(s);
534
+ }
535
+
536
+ function deriveNameFromExpr(expr: Expr): Name | null {
537
+ if (expr.type === "ref") {
538
+ return { name: expr.name };
539
+ } else if (expr.type === "parameter") {
540
+ return null;
541
+ } else {
542
+ // return notImplementedYet(expr);
543
+ return null;
544
+ }
545
+ }
546
+
547
+ // WITH ... INSERT is also a SelectStatement. So this will return SetT or VoidT I think...
548
+ export function elabSelect(
549
+ g: Global,
550
+ c: Context,
551
+ s: SelectStatement
552
+ ): SetT | VoidT {
553
+ if (s.type === "select") {
554
+ const newC: Context = addFromsToScope(g, c, s, s.from || []);
555
+
556
+ if (s.where) {
557
+ const t = elabExpr(g, newC, s.where);
558
+ requireBoolean(s.where, t);
559
+ }
560
+
561
+ const fields = (s.columns || []).flatMap((c: SelectedColumn): Field[] => {
562
+ const n = c.alias ? c.alias : deriveNameFromExpr(c.expr);
563
+
564
+ const t = elabExpr(g, newC, c.expr);
565
+
566
+ if (t.kind === "set") {
567
+ if (t.fields.length === 0) {
568
+ throw new KindMismatch(c.expr, t, "Set with no fields");
569
+ } else if (t.fields.length === 1) {
570
+ if (c.expr.type === "ref" && c.expr.name === "*") {
571
+ return t.fields;
572
+ } else {
573
+ return [{ name: n, type: t.fields[0].type }];
574
+ }
575
+ } else {
576
+ // AFAIK, * is the only way to introduce multiple fields with one expression
577
+ if (c.expr.type === "ref" && c.expr.name === "*") {
578
+ return t.fields;
579
+ } else {
580
+ throw new KindMismatch(c.expr, t, "Set with more than one field");
581
+ }
582
+ }
583
+ }
584
+
585
+ return [{ name: n, type: t }];
586
+ });
587
+ return {
588
+ kind: "set",
589
+ fields,
590
+ };
591
+ } else if (s.type === "union" || s.type === "union all") {
592
+ const typeL = elabSelect(g, c, s.left);
593
+ const typeR = elabSelect(g, c, s.right);
594
+ if (typeL.kind === "void") {
595
+ throw new KindMismatch(
596
+ s.left,
597
+ typeL,
598
+ "Can't union a statement that returns nothing"
599
+ );
600
+ }
601
+ if (typeR.kind === "void") {
602
+ throw new KindMismatch(
603
+ s.right,
604
+ typeR,
605
+ "Can't union a statement that returns nothing"
606
+ );
607
+ }
608
+ return unifySets(s, typeL, typeR);
609
+ } else if (s.type === "values") {
610
+ const typesPerRow: SetT[] = s.values.map((exprs) => {
611
+ const fields = exprs.map((exp) => {
612
+ const t_ = elabExpr(g, c, exp);
613
+ const t = toSimpleT(t_);
614
+ if (t === null) {
615
+ throw new CantReduceToSimpleT(exp, t_);
616
+ } else {
617
+ return { name: null, type: t };
618
+ }
619
+ });
620
+ return {
621
+ kind: "set",
622
+ fields: fields,
623
+ };
624
+ });
625
+ return typesPerRow.reduce(
626
+ (acc: SetT, t: SetT) => unifySets(s, acc, t),
627
+ typesPerRow[0]
628
+ );
629
+ } else if (s.type === "with") {
630
+ const resultingContext = s.bind.reduce((c, bind) => {
631
+ const t = elabStatement(g, c, bind.statement);
632
+ return {
633
+ ...c,
634
+ decls: c.decls.concat({
635
+ name: bind.alias,
636
+ type: t || { kind: "void" },
637
+ }),
638
+ };
639
+ }, c);
640
+ const res = elabStatement(g, resultingContext, s.in);
641
+ if (res.kind !== "void" && res.kind !== "set") {
642
+ return {
643
+ kind: "set",
644
+ fields: [
645
+ {
646
+ name: null,
647
+ type: res,
648
+ },
649
+ ],
650
+ };
651
+ } else {
652
+ return res;
653
+ }
654
+ } else if (s.type === "with recursive") {
655
+ return notImplementedYet(s);
656
+ } else {
657
+ return checkAllCasesHandled(s.type);
658
+ }
659
+ }
660
+
661
+ function elabInsert(g: Global, c: Context, s: InsertStatement): VoidT | SetT {
662
+ const insertingInto: null | {
663
+ readonly name: QName;
664
+ readonly rel: SetT;
665
+ } = g.tables.find((t) => eqQNames(t.name, s.into)) || null;
666
+ if (!insertingInto) {
667
+ throw new UnknownIdentifier(s, s.into);
668
+ }
669
+
670
+ const nameToAddInContext = s.into.alias || s.into.name;
671
+ const newContext = {
672
+ ...c,
673
+ froms: c.froms.concat({
674
+ name: { name: nameToAddInContext },
675
+ type: insertingInto.rel,
676
+ }),
677
+ };
678
+
679
+ const columns: Field[] = s.columns
680
+ ? s.columns.map((c) => {
681
+ const foundField = insertingInto.rel.fields.find((f) => {
682
+ if (!f.name) {
683
+ throw new Error("Assertion error: Table field without name");
684
+ }
685
+ return eqQNames(c, f.name);
686
+ });
687
+ if (!foundField) {
688
+ throw new UnknownIdentifier(s, c);
689
+ }
690
+ return foundField;
691
+ })
692
+ : insertingInto.rel.fields;
693
+
694
+ const insertT = elabSelect(g, newContext, s.insert);
695
+
696
+ if (insertT.kind === "void") {
697
+ throw new ColumnsMismatch(s.insert, {
698
+ expected: columns.length,
699
+ actual: 0,
700
+ });
701
+ }
702
+
703
+ if (insertT.fields.length !== columns.length) {
704
+ throw new ColumnsMismatch(s.insert, {
705
+ expected: columns.length,
706
+ actual: insertT.fields.length,
707
+ });
708
+ }
709
+
710
+ insertT.fields.forEach((insertField, i) => {
711
+ const col = columns[i];
712
+
713
+ cast(s.insert, insertField.type, col.type, "assignment");
714
+ });
715
+
716
+ if (s.returning) {
717
+ return {
718
+ kind: "set",
719
+ fields: s.returning.map((selectedCol) => {
720
+ const t_ = elabExpr(g, newContext, selectedCol.expr);
721
+ const t = toSimpleT(t_);
722
+ if (!t) {
723
+ throw new KindMismatch(
724
+ selectedCol.expr,
725
+ t_,
726
+ "Need simple type here, not a set"
727
+ );
728
+ } else {
729
+ return {
730
+ name: selectedCol.alias || deriveNameFromExpr(selectedCol.expr),
731
+ type: t,
732
+ };
733
+ }
734
+ }),
735
+ };
736
+ } else {
737
+ return { kind: "void" };
738
+ }
739
+
740
+ // TODO: typecheck s.onConflict
741
+ }
742
+
743
+ function elabDelete(g: Global, c: Context, s: DeleteStatement): VoidT | SetT {
744
+ const deletingFrom: null | {
745
+ readonly name: QName;
746
+ readonly rel: SetT;
747
+ } = g.tables.find((t) => eqQNames(t.name, s.from)) || null;
748
+
749
+ if (!deletingFrom) {
750
+ throw new UnknownIdentifier(s, s.from);
751
+ }
752
+ const nameToAddInContext = s.from.alias || s.from.name;
753
+ const newContext = {
754
+ ...c,
755
+ froms: c.froms.concat({
756
+ name: { name: nameToAddInContext },
757
+ type: deletingFrom.rel,
758
+ }),
759
+ };
760
+
761
+ if (s.where) {
762
+ const whereT = elabExpr(g, newContext, s.where);
763
+ cast(s.where, whereT, BuiltinTypes.Boolean, "implicit");
764
+ }
765
+
766
+ if (s.returning) {
767
+ return {
768
+ kind: "set",
769
+ fields: s.returning.map((selectedCol) => {
770
+ const t_ = elabExpr(g, newContext, selectedCol.expr);
771
+ const t = toSimpleT(t_);
772
+ if (!t) {
773
+ throw new KindMismatch(
774
+ selectedCol.expr,
775
+ t_,
776
+ "Need simple type here, not a set"
777
+ );
778
+ } else {
779
+ return {
780
+ name: selectedCol.alias || deriveNameFromExpr(selectedCol.expr),
781
+ type: t,
782
+ };
783
+ }
784
+ }),
785
+ };
786
+ } else {
787
+ return { kind: "void" };
788
+ }
789
+ }
790
+
791
+ function toSimpleT(t: Type): SimpleT | null {
792
+ if (t.kind === "set") {
793
+ if (t.fields.length === 1) {
794
+ return t.fields[0].type;
795
+ } else {
796
+ return null;
797
+ }
798
+ } else {
799
+ return t;
800
+ }
801
+ }
802
+
803
+ export type functionType = {
804
+ name: QName;
805
+ inputs: { name: Name; type: SimpleT }[];
806
+ returns: Type | VoidT;
807
+ multipleRows: boolean;
808
+ code: string;
809
+ language: string;
810
+ };
811
+
812
+ export function doCreateFunction(
813
+ g: Global,
814
+ c: Context,
815
+ s: CreateFunctionStatement
816
+ ): functionType {
817
+ const name = s.name;
818
+ if (!s.language) {
819
+ throw new Error(
820
+ "Please provide language for function at " + showLocation(s._location)
821
+ );
822
+ }
823
+ if (s.language && s.language.name.toLowerCase() === "sql") {
824
+ const inputs = s.arguments.map((arg) => {
825
+ if (!arg.name) {
826
+ throw new Error(
827
+ "Please provide name for all function arguments at " +
828
+ showLocation(s._location)
829
+ );
830
+ }
831
+ return {
832
+ name: arg.name,
833
+ type: mkType(
834
+ arg.type,
835
+ // !!!!!!!!!!!!
836
+ // Default rule of THIS typechecker: params are NOT NULL
837
+ // , unless defined as eg: (myname int default null)
838
+ // !!!!!!!!!!!!
839
+ arg.default && arg.default.type === "null"
840
+ ? []
841
+ : [{ type: "not null" }]
842
+ ),
843
+ };
844
+ });
845
+ const contextForBody: Context = {
846
+ froms: c.froms,
847
+ decls: c.decls.concat(inputs),
848
+ };
849
+
850
+ const body = parse(s.code);
851
+
852
+ if (body.length === 0) {
853
+ return {
854
+ name,
855
+ inputs,
856
+ returns: { kind: "void" },
857
+ multipleRows: false,
858
+ code: s.code,
859
+ language: s.language.name,
860
+ };
861
+ } else {
862
+ // TODO check rest of body for type errors
863
+ const lastStatement = body[body.length - 1];
864
+ const returnType = elabStatement(g, contextForBody, lastStatement);
865
+
866
+ return {
867
+ name,
868
+ inputs,
869
+ returns: returnType,
870
+ multipleRows: true, // todo
871
+ code: s.code,
872
+ language: s.language.name,
873
+ };
874
+ }
875
+ } else {
876
+ return notImplementedYet(s);
877
+ }
878
+ }
879
+
880
+ type HandledFrom = { name: QName; rel: SetT };
881
+ type Nullable<T> = T | null;
882
+
883
+ function findRel(g: Global, c: Context, e: Expr, n: QName): Nullable<SetT> {
884
+ debugger;
885
+ const d = c.decls.find((d) => eqQNames(d.name, n));
886
+ if (d) {
887
+ if (d.type.kind === "set") {
888
+ return d.type;
889
+ } else {
890
+ throw new KindMismatch(e, d.type, "Expecting a set or table");
891
+ }
892
+ } else {
893
+ const t = g.tables.find((t) => eqQNames(t.name, n));
894
+ if (t) {
895
+ return t.rel;
896
+ } else {
897
+ const v = g.views.find((v) => eqQNames(v.name, n));
898
+ if (v) {
899
+ return v.rel;
900
+ } else {
901
+ return null;
902
+ }
903
+ }
904
+ }
905
+ }
906
+
907
+ function showLocation(loc: NodeLocation | undefined): string {
908
+ if (!loc) {
909
+ return "??";
910
+ } else {
911
+ return loc.start + " - " + loc.end;
912
+ }
913
+ }
914
+
915
+ class ErrorWithLocation extends Error {
916
+ constructor(l: NodeLocation | undefined, m: string) {
917
+ super(`${showLocation(l)}: ${m}`);
918
+ }
919
+ }
920
+
921
+ class NotImplementedYet extends ErrorWithLocation {
922
+ constructor(node: PGNode | null) {
923
+ const m = node
924
+ ? `: \n
925
+ ${JSON.stringify(node)} @ ${node._location}`
926
+ : "";
927
+ super(node?._location, `NotImplementedYet: ${m}`);
928
+ }
929
+ }
930
+
931
+ class UnknownField extends ErrorWithLocation {
932
+ constructor(e: Expr, _s: SetT, n: Name) {
933
+ super(e._location, `UnknownField ${n.name}`);
934
+ }
935
+ }
936
+ export class UnknownIdentifier extends ErrorWithLocation {
937
+ constructor(e: PGNode, m: QName) {
938
+ super(e._location, `UnknownIdentifier ${showQName(m)}`);
939
+ }
940
+ }
941
+ export class CantReduceToSimpleT extends ErrorWithLocation {
942
+ constructor(e: PGNode, m: Type) {
943
+ super(e._location, `Can't reduce to simple type: ${showType(m)}`);
944
+ }
945
+ }
946
+
947
+ export function showType(t: Type): string {
948
+ if (t.kind === "set") {
949
+ return (
950
+ "{" +
951
+ t.fields
952
+ .map(
953
+ (f) =>
954
+ (f.name === null ? `"?": ` : `"${f.name.name}": `) +
955
+ showType(f.type)
956
+ )
957
+ .join(", ") +
958
+ "}"
959
+ );
960
+ } else {
961
+ if (t.kind === "array") {
962
+ return "(" + showType(t.typevar) + ")" + "[]";
963
+ } else if (t.kind === "nullable") {
964
+ return showType(t.typevar) + " | null";
965
+ } else if (t.kind === "scalar") {
966
+ return t.name.name;
967
+ } else if (t.kind === "anyscalar") {
968
+ return "anyscalar";
969
+ } else {
970
+ return checkAllCasesHandled(t);
971
+ }
972
+ }
973
+ }
974
+ export function showSqlType(t: Type): string {
975
+ if (t.kind === "set") {
976
+ return (
977
+ "{" +
978
+ t.fields
979
+ .map(
980
+ (f) =>
981
+ (f.name === null ? `"?" ` : `"${f.name.name}" `) + showType(f.type)
982
+ )
983
+ .join(", ") +
984
+ "}"
985
+ );
986
+ } else {
987
+ if (t.kind === "array") {
988
+ return "(" + showSqlType(t.typevar) + ")" + "[]";
989
+ } else if (t.kind === "nullable") {
990
+ return showSqlType(t.typevar) + " DEFAULT NULL";
991
+ } else if (t.kind === "scalar") {
992
+ return t.name.name;
993
+ } else if (t.kind === "anyscalar") {
994
+ return "anyscalar";
995
+ } else {
996
+ return checkAllCasesHandled(t);
997
+ }
998
+ }
999
+ }
1000
+ export class UnknownUnaryOp extends ErrorWithLocation {
1001
+ constructor(e: Expr, n: QName, t1: Type) {
1002
+ super(
1003
+ e._location,
1004
+ `Can't apply unary operator "${showQName(n)}" to ${showType(t1)}`
1005
+ );
1006
+ }
1007
+ }
1008
+ export class UnknownBinaryOp extends ErrorWithLocation {
1009
+ constructor(e: Expr, n: QName, t1: Type, t2: Type) {
1010
+ super(
1011
+ e._location,
1012
+ `Can't apply operator "${showQName(n)}" to ${showType(t1)} and ${showType(
1013
+ t2
1014
+ )}`
1015
+ );
1016
+ }
1017
+ }
1018
+ export class UnknownFunction extends ErrorWithLocation {
1019
+ constructor(e: Expr, n: QName) {
1020
+ super(e._location, `Unknown function "${showQName(n)}"`);
1021
+ }
1022
+ }
1023
+ export class InvalidArguments extends ErrorWithLocation {
1024
+ constructor(e: Expr, n: QName, argTs: Type[]) {
1025
+ const argsString = argTs.map((t) => showType(t)).join(", ");
1026
+ super(
1027
+ e._location,
1028
+ `Can't apply function "${showQName(n)}" to arguments: ${argsString}`
1029
+ );
1030
+ }
1031
+ }
1032
+ export class TypecheckerError extends ErrorWithLocation {
1033
+ constructor(e: Expr, m: string) {
1034
+ super(e._location, `Typechecker error: ${m}`);
1035
+ }
1036
+ }
1037
+ class AmbiguousIdentifier extends ErrorWithLocation {
1038
+ constructor(e: Expr, m: QName, sets: QName[]) {
1039
+ super(
1040
+ e._location,
1041
+ `AmbiguousIdentifier ${showQName(m)} @ ${showLocation(
1042
+ m._location
1043
+ )} present in ${JSON.stringify(sets)}`
1044
+ );
1045
+ }
1046
+ }
1047
+ class ColumnsMismatch extends ErrorWithLocation {
1048
+ constructor(e: Expr, opts: { expected: number; actual: number }) {
1049
+ super(
1050
+ e._location,
1051
+ `ColumnsMismatch: Expecting ${opts.expected} columns, got ${opts.actual} columns`
1052
+ );
1053
+ }
1054
+ }
1055
+ class KindMismatch extends ErrorWithLocation {
1056
+ constructor(e: Expr, type: Type | VoidT, errormsg: string) {
1057
+ super(e._location, `KindMismatch: ${e}: ${type}: ${errormsg}}`);
1058
+ }
1059
+ }
1060
+ export class TypeMismatch extends ErrorWithLocation {
1061
+ constructor(
1062
+ e: Expr,
1063
+ ts: {
1064
+ expected: Type;
1065
+ actual: Type;
1066
+ },
1067
+ mess?: string
1068
+ ) {
1069
+ super(
1070
+ e._location,
1071
+ `
1072
+ TypeMismatch:
1073
+ ${toSql.expr(e)} ${mess ? ": " + mess : ""}
1074
+
1075
+ Expected:
1076
+ ${JSON.stringify(ts.expected)}
1077
+
1078
+ Actual:
1079
+ ${JSON.stringify(ts.actual)}}
1080
+ `
1081
+ );
1082
+ }
1083
+ }
1084
+
1085
+ const warnings: [Expr, string][] = [];
1086
+ function registerWarning(e: Expr, message: string) {
1087
+ warnings.push([e, message]);
1088
+ }
1089
+
1090
+ function mergeHandledFroms(c: Context, handledFroms: HandledFrom[]): Context {
1091
+ return {
1092
+ ...c,
1093
+ froms: c.froms.concat(
1094
+ handledFroms.map(function (f) {
1095
+ return {
1096
+ name: f.name,
1097
+ type: f.rel,
1098
+ };
1099
+ })
1100
+ ),
1101
+ };
1102
+ }
1103
+
1104
+ function doSingleFrom(
1105
+ g: Global,
1106
+ c: Context,
1107
+ e: Expr,
1108
+ handledFroms: HandledFrom[],
1109
+ f: From
1110
+ ): HandledFrom[] {
1111
+ function getHandledFrom(f: From): HandledFrom {
1112
+ if (f.type === "statement") {
1113
+ const t = elabSelect(g, c, f.statement);
1114
+ if (t.kind === "void") {
1115
+ throw new KindMismatch(
1116
+ f.statement,
1117
+ t,
1118
+ "Can't bind a statement that returns void in a FROM statement"
1119
+ );
1120
+ }
1121
+ return {
1122
+ name: {
1123
+ name: f.alias,
1124
+ _location: f._location,
1125
+ },
1126
+ rel: t,
1127
+ };
1128
+ } else if (f.type === "call") {
1129
+ return notImplementedYet(f);
1130
+ } else if (f.type === "table") {
1131
+ if ((f.name.columnNames || []).length > 0) {
1132
+ notImplementedYet(f);
1133
+ }
1134
+ const foundRel = findRel(g, c, e, f.name);
1135
+ if (!foundRel) {
1136
+ throw new UnknownIdentifier(f, f.name);
1137
+ }
1138
+ return {
1139
+ name: {
1140
+ name: f.name.alias || f.name.name,
1141
+ _location: f.name._location,
1142
+ },
1143
+ rel: foundRel,
1144
+ };
1145
+ } else {
1146
+ return checkAllCasesHandled(f);
1147
+ }
1148
+ }
1149
+
1150
+ const newHandledFrom_ = getHandledFrom(f);
1151
+
1152
+ const newHandledFrom =
1153
+ f.join && (f.join.type === "FULL JOIN" || f.join.type === "LEFT JOIN")
1154
+ ? { ...newHandledFrom_, rel: nullifySet(newHandledFrom_.rel) }
1155
+ : newHandledFrom_;
1156
+
1157
+ const newHandledFroms_ =
1158
+ f.join && (f.join.type === "FULL JOIN" || f.join.type === "RIGHT JOIN")
1159
+ ? handledFroms.map((fr) => ({ ...fr, rel: nullifySet(fr.rel) }))
1160
+ : handledFroms;
1161
+
1162
+ const newHandledFroms = newHandledFroms_.concat(newHandledFrom);
1163
+
1164
+ if (f.join?.on) {
1165
+ const t = elabExpr(g, mergeHandledFroms(c, newHandledFroms), f.join.on);
1166
+ requireBoolean(f.join.on, t);
1167
+ }
1168
+
1169
+ return newHandledFroms;
1170
+ }
1171
+ function addFromsToScope(
1172
+ g: Global,
1173
+ c: Context,
1174
+ e: Expr,
1175
+ froms: From[]
1176
+ ): Context {
1177
+ const inFroms: HandledFrom[] = froms.reduce(function (
1178
+ acc: HandledFrom[],
1179
+ f: From
1180
+ ) {
1181
+ return doSingleFrom(g, c, e, acc, f);
1182
+ },
1183
+ []);
1184
+ return mergeHandledFroms(c, inFroms);
1185
+ }
1186
+
1187
+ function lookupInSet(s: SetT, name: Name): SimpleT | null {
1188
+ const found = s.fields.find((f) => f.name && f.name.name === name.name);
1189
+ if (found) {
1190
+ return found.type;
1191
+ } else {
1192
+ return null;
1193
+ }
1194
+ }
1195
+
1196
+ function elabRef(c: Context, e: ExprRef): Type {
1197
+ if (e.name === "*") {
1198
+ const tab = e.table;
1199
+ if (tab !== undefined) {
1200
+ const found = c.froms.find((f) => eqQNames(f.name, tab));
1201
+ if (!found) {
1202
+ throw new UnknownIdentifier(e, tab);
1203
+ } else {
1204
+ return found.type;
1205
+ }
1206
+ } else {
1207
+ return {
1208
+ kind: "set",
1209
+ fields: c.froms.reduce(
1210
+ (acc: Field[], from) => acc.concat(from.type.fields),
1211
+ []
1212
+ ),
1213
+ };
1214
+ }
1215
+ } else {
1216
+ const tableName = e.table;
1217
+ if (tableName) {
1218
+ const table = c.froms.find((d) => eqQNames(d.name, tableName));
1219
+ if (!table) {
1220
+ throw new UnknownIdentifier(e, tableName);
1221
+ }
1222
+ if (!(table.type.kind === "set")) {
1223
+ throw new KindMismatch(e, table.type, "Expecting Set");
1224
+ }
1225
+ const field = lookupInSet(table.type, e);
1226
+ if (!field) {
1227
+ throw new UnknownField(e, table.type, e);
1228
+ }
1229
+ return field;
1230
+ } else {
1231
+ const foundFields: {
1232
+ set: QName;
1233
+ field: Name;
1234
+ type: SimpleT;
1235
+ }[] = mapPartial(c.froms, (t) => {
1236
+ const foundfield = lookupInSet(t.type, e);
1237
+ return foundfield ? { set: t.name, field: e, type: foundfield } : null;
1238
+ });
1239
+
1240
+ const foundIdentifiers = mapPartial(c.decls, (t) => {
1241
+ if (t.type.kind === "set" || t.type.kind === "void") {
1242
+ return null;
1243
+ } else {
1244
+ return t.name.name === e.name
1245
+ ? { name: t.name.name, type: t.type }
1246
+ : null;
1247
+ }
1248
+ });
1249
+
1250
+ // Fields seem to have precedence over eg: function params in postgres?
1251
+ if (foundFields.length === 0) {
1252
+ if (foundIdentifiers.length === 0) {
1253
+ throw new UnknownIdentifier(e, e);
1254
+ } else if (foundIdentifiers.length === 1) {
1255
+ return foundIdentifiers[0].type;
1256
+ } else {
1257
+ throw new AmbiguousIdentifier(e, e, []);
1258
+ }
1259
+ } else if (foundFields.length === 1) {
1260
+ return foundFields[0].type;
1261
+ } else {
1262
+ throw new AmbiguousIdentifier(
1263
+ e,
1264
+ e,
1265
+ foundFields.map((f) => f.set)
1266
+ );
1267
+ }
1268
+ }
1269
+ }
1270
+ }
1271
+
1272
+ export type binaryOp = {
1273
+ left: SimpleT;
1274
+ right: SimpleT;
1275
+ result: SimpleT;
1276
+ name: QName;
1277
+ description: string;
1278
+ };
1279
+
1280
+ export type unaryOp = {
1281
+ operand: SimpleT;
1282
+ result: SimpleT;
1283
+ name: QName;
1284
+ description: string;
1285
+ };
1286
+
1287
+ function isNotEmpty<A>(a: A | null | undefined): a is A {
1288
+ return a !== null && a !== undefined;
1289
+ }
1290
+
1291
+ function elabAnyCall(
1292
+ e: Expr,
1293
+ name: QName,
1294
+ nullPolicy: "CALLED ON NULL INPUT" | "STRICT", // RETURNS NULL ON NULL INPUT = STRICT
1295
+ sourceTypes: Type[],
1296
+ targetTypes: Type[]
1297
+ ): {
1298
+ nullifyResultType: boolean;
1299
+ score: number; // amount of coersions we had to do gets you into minus, so higher score is better
1300
+ } {
1301
+ if (sourceTypes.length !== targetTypes.length) {
1302
+ throw new InvalidArguments(e, name, sourceTypes);
1303
+ }
1304
+ const { score, anySourceIsNullable } = sourceTypes.reduce(
1305
+ function (acc, sourceT, i) {
1306
+ const targetT = targetTypes[i];
1307
+ if (sourceT.kind === "nullable") {
1308
+ cast(e, sourceT.typevar, targetT, "implicit");
1309
+ const score = eqType(sourceT.typevar, targetT) ? 0 : -1;
1310
+ return {
1311
+ score: acc.score + score,
1312
+ anySourceIsNullable: true,
1313
+ };
1314
+ } else {
1315
+ cast(e, sourceT, targetT, "implicit");
1316
+ const score = eqType(sourceT, targetT) ? 0 : -1;
1317
+ return {
1318
+ score: acc.score + score,
1319
+ anySourceIsNullable: acc.anySourceIsNullable || false,
1320
+ };
1321
+ }
1322
+ },
1323
+ {
1324
+ score: 0 as number,
1325
+ anySourceIsNullable: false as boolean,
1326
+ }
1327
+ );
1328
+ return {
1329
+ score,
1330
+ nullifyResultType: nullPolicy === "STRICT" && anySourceIsNullable,
1331
+ };
1332
+ }
1333
+
1334
+ function elabUnaryOp(g: Global, c: Context, e: ExprUnary): Type {
1335
+ const t1_ = elabExpr(g, c, e.operand);
1336
+
1337
+ const t1 = toSimpleT(t1_);
1338
+ if (t1 === null) {
1339
+ throw new CantReduceToSimpleT(e, t1_);
1340
+ }
1341
+
1342
+ if (e.op === "IS NULL" || e.op === "IS NOT NULL") {
1343
+ if (!isNullable(t1)) {
1344
+ registerWarning(e, "IS (NOT) NULL check but operand is not nullable");
1345
+ }
1346
+ return BuiltinTypes.Boolean;
1347
+ }
1348
+
1349
+ const found = builtinUnaryOperators
1350
+ .filter(function (op) {
1351
+ return eqQNames(
1352
+ {
1353
+ name: e.op,
1354
+ schema: e.opSchema,
1355
+ },
1356
+ op.name
1357
+ );
1358
+ })
1359
+ .map(function (op) {
1360
+ try {
1361
+ const { nullifyResultType, score } = elabAnyCall(
1362
+ e,
1363
+ op.name,
1364
+ "STRICT", // TODO?
1365
+ [t1],
1366
+ [op.operand]
1367
+ );
1368
+ return [score, op, nullifyResultType] as const;
1369
+ } catch {
1370
+ return null;
1371
+ }
1372
+ })
1373
+ .filter(isNotEmpty)
1374
+ .sort((m1, m2) => (m1[0] > m2[0] ? -1 : 1))[0];
1375
+
1376
+ if (!found) {
1377
+ throw new UnknownUnaryOp(e, { name: e.op, schema: e.opSchema }, t1);
1378
+ } else {
1379
+ const op = found[1];
1380
+ return found[2] ? nullify(op.result) : op.result;
1381
+ }
1382
+ }
1383
+ function elabBinaryOp(g: Global, c: Context, e: ExprBinary): Type {
1384
+ const t1_ = elabExpr(g, c, e.left);
1385
+ const t2_ = elabExpr(g, c, e.right);
1386
+
1387
+ const t1 = toSimpleT(t1_);
1388
+ const t2 = toSimpleT(t2_);
1389
+
1390
+ if (t1 === null) {
1391
+ throw new CantReduceToSimpleT(e, t1_);
1392
+ }
1393
+ if (t2 === null) {
1394
+ throw new CantReduceToSimpleT(e, t2_);
1395
+ }
1396
+
1397
+ // Specific test on = NULL, because it's always False (I think?) and is a cause of a lot of bugs
1398
+ if (e.op === "=" && (e.left.type === "null" || e.right.type === "null")) {
1399
+ throw new Error(
1400
+ `Don't use \"= NULL\", use "IS NULL" instead @ ${showLocation(
1401
+ e._location
1402
+ )}`
1403
+ );
1404
+ }
1405
+
1406
+ // TODO use elabAnyCall?
1407
+ if (e.op === "IN" || e.op === "NOT IN") {
1408
+ // No generics, so special casing this operator
1409
+ castSimples(e, t2, BuiltinTypeConstructors.List(t1), "implicit");
1410
+ return BuiltinTypes.Boolean;
1411
+ }
1412
+
1413
+ const found = builtinoperators
1414
+ .filter(function (op) {
1415
+ return eqQNames(
1416
+ {
1417
+ name: e.op,
1418
+ schema: e.opSchema,
1419
+ },
1420
+ op.name
1421
+ );
1422
+ })
1423
+ // TODO do this only once?
1424
+ .sort(function (op) {
1425
+ // prefer operators on same type
1426
+ // mostly (only?) if one of the operators is "anyscalar" (= NULL expr)
1427
+ if (eqType(op.left, op.right)) {
1428
+ return -1;
1429
+ } else {
1430
+ return 0;
1431
+ }
1432
+ })
1433
+ .map(function (op) {
1434
+ try {
1435
+ const res = elabAnyCall(
1436
+ e,
1437
+ op.name,
1438
+ "STRICT" /* TODO ? */,
1439
+ [t1, t2],
1440
+ [op.left, op.right]
1441
+ );
1442
+ return {
1443
+ ...res,
1444
+ op,
1445
+ };
1446
+ } catch {
1447
+ return null;
1448
+ }
1449
+ })
1450
+ .filter(isNotEmpty)
1451
+ .sort((m1, m2) => (m1.score > m2.score ? -1 : 1));
1452
+
1453
+ if (found.length === 0) {
1454
+ throw new UnknownBinaryOp(e, { name: e.op, schema: e.opSchema }, t1, t2);
1455
+ } else {
1456
+ const best = found[0];
1457
+ return best.nullifyResultType ? nullify(best.op.result) : best.op.result;
1458
+ }
1459
+ }
1460
+
1461
+ function elabCall(g: Global, c: Context, e: ExprCall): Type {
1462
+ const argTypes = e.args.map((arg) => elabExpr(g, c, arg));
1463
+
1464
+ if (
1465
+ eqQNames(e.function, { name: "any" }) ||
1466
+ eqQNames(e.function, { name: "some" }) ||
1467
+ eqQNames(e.function, { name: "all" })
1468
+ ) {
1469
+ if (e.args.length !== 1) {
1470
+ throw new InvalidArguments(e, e.function, argTypes);
1471
+ }
1472
+ const t_ = argTypes[0];
1473
+ const t = toSimpleT(t_);
1474
+ if (t === null) {
1475
+ throw new CantReduceToSimpleT(e.args[0], argTypes[0]);
1476
+ }
1477
+ const unifiedT = unifySimples(
1478
+ e,
1479
+ t,
1480
+ BuiltinTypeConstructors.Array(BuiltinTypes.AnyScalar)
1481
+ );
1482
+ if (unifiedT.kind !== "array") {
1483
+ throw new TypecheckerError(e, "Expecting array type");
1484
+ } else {
1485
+ return unifiedT.typevar;
1486
+ }
1487
+ }
1488
+ if (
1489
+ eqQNames(e.function, { name: "coalesce" }) ||
1490
+ eqQNames(e.function, { name: "nullif" })
1491
+ ) {
1492
+ if (e.args.length === 0) {
1493
+ throw new InvalidArguments(e, e.function, []);
1494
+ }
1495
+ const types: [Expr, SimpleT][] = e.args
1496
+ .map((arg) => [arg, elabExpr(g, c, arg)] as const)
1497
+ .map(([arg, t_]) => {
1498
+ const t = toSimpleT(t_);
1499
+ if (t === null) {
1500
+ throw new CantReduceToSimpleT(arg, t_);
1501
+ } else {
1502
+ return [arg, t];
1503
+ }
1504
+ });
1505
+ const unifiedType = types.reduce(
1506
+ (acc, [arg, t]) => unifySimples(arg, acc, t),
1507
+ types[0][1]
1508
+ );
1509
+ if (eqQNames(e.function, { name: "coalesce" })) {
1510
+ if (types.some(([_arg, t]) => !isNullable(t))) {
1511
+ return unnullify(unifiedType);
1512
+ } else {
1513
+ return unifiedType;
1514
+ }
1515
+ } else {
1516
+ // nullable types already "win" unification, so nullif doesn't need special logic
1517
+ return unifiedType;
1518
+ }
1519
+ }
1520
+
1521
+ throw new UnknownFunction(e, e.function);
1522
+ }
1523
+
1524
+ function elabExpr(g: Global, c: Context, e: Expr): Type {
1525
+ if (e.type === "ref") {
1526
+ const t = elabRef(c, e);
1527
+ return t;
1528
+ } else if (e.type === "parameter") {
1529
+ return notImplementedYet(e);
1530
+ } else if (e.type === "integer") {
1531
+ return BuiltinTypes.Integer;
1532
+ } else if (e.type === "boolean") {
1533
+ return BuiltinTypes.Boolean;
1534
+ } else if (e.type === "string") {
1535
+ return BuiltinTypes.Text;
1536
+ } else if (e.type === "unary") {
1537
+ return elabUnaryOp(g, c, e);
1538
+ } else if (e.type === "binary") {
1539
+ return elabBinaryOp(g, c, e);
1540
+ } else if (e.type === "null") {
1541
+ return BuiltinTypeConstructors.Nullable(BuiltinTypes.AnyScalar);
1542
+ } else if (e.type === "numeric") {
1543
+ return BuiltinTypes.Numeric;
1544
+ } else if (e.type === "list" || e.type === "array") {
1545
+ const typevar = e.expressions.reduce((acc: SimpleT, subexpr: Expr) => {
1546
+ const t_ = elabExpr(g, c, subexpr);
1547
+ const t = toSimpleT(t_);
1548
+ if (t === null) {
1549
+ throw new CantReduceToSimpleT(e, t_);
1550
+ } else {
1551
+ return unifySimples(e, t, acc);
1552
+ }
1553
+ }, BuiltinTypes.AnyScalar);
1554
+ return e.type === "list"
1555
+ ? BuiltinTypeConstructors.List(typevar)
1556
+ : BuiltinTypeConstructors.Array(typevar);
1557
+ } else if (e.type === "call") {
1558
+ return elabCall(g, c, e);
1559
+ } else if (e.type === "array select") {
1560
+ const selectType = elabSelect(g, c, e.select);
1561
+ if (selectType.kind === "void") {
1562
+ throw new KindMismatch(
1563
+ e.select,
1564
+ selectType,
1565
+ "Select in array select can't return void"
1566
+ );
1567
+ }
1568
+ const t = unifySetWithSimple(e, selectType, BuiltinTypes.AnyScalar);
1569
+ return BuiltinTypeConstructors.Array(t);
1570
+ } else if (e.type === "default") {
1571
+ // ??
1572
+ return BuiltinTypes.AnyScalar;
1573
+ } else if (e.type === "extract") {
1574
+ function timeIsValid(s: string) {
1575
+ return [
1576
+ "hour",
1577
+ "minute",
1578
+ "second",
1579
+ "microseconds",
1580
+ "milliseconds",
1581
+ ].includes(s.toLowerCase());
1582
+ }
1583
+ function intervalIsValid(s: string) {
1584
+ return (
1585
+ timeIsValid(s) ||
1586
+ ["century", "epoch", "decade", "year", "month", "day"].includes(
1587
+ s.toLowerCase()
1588
+ )
1589
+ );
1590
+ }
1591
+ // TODO use elabAnyCall?
1592
+ const t = elabExpr(g, c, e.from);
1593
+ try {
1594
+ cast(e.from, t, BuiltinTypes.Timestamp, "implicit");
1595
+ } catch (err) {
1596
+ try {
1597
+ if (intervalIsValid(e.field.name)) {
1598
+ cast(e.from, t, BuiltinTypes.Interval, "implicit");
1599
+ } else {
1600
+ throw err;
1601
+ }
1602
+ } catch (err) {
1603
+ if (timeIsValid(e.field.name)) {
1604
+ cast(e.from, t, BuiltinTypes.Time, "implicit");
1605
+ } else {
1606
+ throw err;
1607
+ }
1608
+ }
1609
+ }
1610
+ return BuiltinTypes.Numeric;
1611
+ } else if (e.type === "member") {
1612
+ const t = elabExpr(g, c, e.operand);
1613
+ try {
1614
+ cast(
1615
+ e.operand,
1616
+ t,
1617
+ BuiltinTypeConstructors.Nullable(BuiltinTypes.Json),
1618
+ "implicit"
1619
+ );
1620
+ } catch {
1621
+ cast(
1622
+ e.operand,
1623
+ t,
1624
+ BuiltinTypeConstructors.Nullable(BuiltinTypes.Jsonb),
1625
+ "implicit"
1626
+ );
1627
+ }
1628
+ return BuiltinTypes.AnyScalar;
1629
+ } else if (e.type === "keyword") {
1630
+ if (e.keyword === "current_time") {
1631
+ return BuiltinTypes.Time;
1632
+ } else if (e.keyword === "current_date") {
1633
+ return BuiltinTypes.Date;
1634
+ } else if (
1635
+ e.keyword === "current_role" ||
1636
+ e.keyword === "current_timestamp" ||
1637
+ e.keyword === "localtimestamp" ||
1638
+ e.keyword === "localtime"
1639
+ ) {
1640
+ return BuiltinTypes.Timestamp;
1641
+ } else if (
1642
+ e.keyword === "current_catalog" ||
1643
+ e.keyword === "current_schema" ||
1644
+ e.keyword === "session_user" ||
1645
+ e.keyword === "user" ||
1646
+ e.keyword === "current_user"
1647
+ ) {
1648
+ return BuiltinTypes.Text;
1649
+ } else if (e.keyword === "distinct") {
1650
+ throw new Error("Don't know what to do with distinct keyword");
1651
+ } else {
1652
+ return checkAllCasesHandled(e.keyword);
1653
+ }
1654
+ } else if (e.type === "arrayIndex") {
1655
+ const arrayT = elabExpr(g, c, e.array);
1656
+ const indexT = elabExpr(g, c, e.index);
1657
+ const unifiedArrayT_ = unify(
1658
+ e.array,
1659
+ arrayT,
1660
+ BuiltinTypeConstructors.Array(BuiltinTypes.AnyScalar)
1661
+ );
1662
+ cast(e.array, indexT, BuiltinTypes.Integer, "implicit");
1663
+ const unifiedArrayT = toSimpleT(unifiedArrayT_);
1664
+
1665
+ if (unifiedArrayT === null) {
1666
+ throw new CantReduceToSimpleT(e.array, unifiedArrayT_);
1667
+ } else {
1668
+ const unnulified = unnullify(unifiedArrayT);
1669
+ if (unnulified.kind !== "array") {
1670
+ throw new TypeMismatch(e.array, {
1671
+ expected: arrayT,
1672
+ actual: BuiltinTypeConstructors.Array(BuiltinTypes.AnyScalar),
1673
+ });
1674
+ } else {
1675
+ return nullify(unnulified.typevar);
1676
+ }
1677
+ }
1678
+ } else if (e.type === "case") {
1679
+ if (e.value) {
1680
+ const valueT = elabExpr(g, c, e.value);
1681
+ const conditionTs: [Expr, Type][] = e.whens.map((whenExp) => [
1682
+ whenExp.when,
1683
+ elabExpr(g, c, whenExp.when),
1684
+ ]);
1685
+ conditionTs.reduce(
1686
+ (acc, [exp, conditionT]) => unify(exp, acc, conditionT),
1687
+ valueT
1688
+ );
1689
+ } else {
1690
+ const conditionTs: [Expr, Type][] = e.whens.map((whenExp) => [
1691
+ whenExp.when,
1692
+ elabExpr(g, c, whenExp.when),
1693
+ ]);
1694
+ conditionTs.forEach(([exp, conditionT]) =>
1695
+ requireBoolean(exp, conditionT)
1696
+ );
1697
+ }
1698
+ if (e.whens.length === 0) {
1699
+ throw new Error("Not expecting CASE statement without when");
1700
+ }
1701
+ const whensT = e.whens.reduce(
1702
+ (acc: Type, whenExp) =>
1703
+ unify(whenExp.value, acc, elabExpr(g, c, whenExp.value)),
1704
+ elabExpr(g, c, e.whens[0].value)
1705
+ );
1706
+ return e.else ? unify(e.else, whensT, elabExpr(g, c, e.else)) : whensT;
1707
+ } else if (
1708
+ e.type === "select" ||
1709
+ e.type === "union" ||
1710
+ e.type === "union all" ||
1711
+ e.type === "values" ||
1712
+ e.type === "with" ||
1713
+ e.type === "with recursive"
1714
+ ) {
1715
+ const t = elabSelect(g, c, e);
1716
+ if (t.kind === "void") {
1717
+ throw new KindMismatch(
1718
+ e,
1719
+ t,
1720
+ "Select as an expression needs to return something"
1721
+ );
1722
+ }
1723
+ return t;
1724
+ } else if (e.type === "ternary") {
1725
+ const valueT = elabExpr(g, c, e.value);
1726
+ const hiT = elabExpr(g, c, e.hi);
1727
+ const loT = elabExpr(g, c, e.lo);
1728
+ cast(e, valueT, loT, "implicit");
1729
+ cast(e, valueT, hiT, "implicit");
1730
+ return BuiltinTypes.Boolean;
1731
+ } else if (e.type === "substring" || e.type === "overlay") {
1732
+ const valueT = elabExpr(g, c, e.value);
1733
+ const fromT = e.from ? elabExpr(g, c, e.from) : BuiltinTypes.Integer;
1734
+ const forT = e.for ? elabExpr(g, c, e.for) : BuiltinTypes.Integer;
1735
+ const res = elabAnyCall(
1736
+ e,
1737
+ { name: e.type },
1738
+ "STRICT",
1739
+ [valueT, fromT, forT],
1740
+ [BuiltinTypes.Text, BuiltinTypes.Integer, BuiltinTypes.Integer]
1741
+ );
1742
+ return res.nullifyResultType
1743
+ ? nullify(BuiltinTypes.Text)
1744
+ : BuiltinTypes.Text;
1745
+ } else if (e.type === "constant") {
1746
+ throw new Error("Haven't been able to simulate this yet");
1747
+ } else if (e.type === "cast") {
1748
+ const operandT = elabExpr(g, c, e.operand);
1749
+ const toT = mkType(e.to, []);
1750
+ cast(e, operandT, nullify(toT), "explicit");
1751
+ if (isNullable(operandT)) {
1752
+ return toT;
1753
+ } else {
1754
+ return unnullify(toT);
1755
+ }
1756
+ } else {
1757
+ return checkAllCasesHandled(e.type);
1758
+ }
1759
+ }
1760
+
1761
+ function elabStatement(g: Global, c: Context, s: Statement): VoidT | Type {
1762
+ if (
1763
+ s.type === "select" ||
1764
+ s.type === "union" ||
1765
+ s.type === "union all" ||
1766
+ s.type === "with" ||
1767
+ s.type === "with recursive" ||
1768
+ s.type === "values"
1769
+ ) {
1770
+ return elabExpr(g, c, s);
1771
+ } else if (s.type === "insert") {
1772
+ return elabInsert(g, c, s);
1773
+ } else if (s.type === "delete") {
1774
+ return elabDelete(g, c, s);
1775
+ } else {
1776
+ return notImplementedYet(s);
1777
+ }
1778
+ }
1779
+
1780
+ export function parseSetupScripts(ast: Statement[]): Global {
1781
+ return ast.reduce(
1782
+ (acc: Global, a): Global => {
1783
+ if (a.type === "create table" && !a.temporary) {
1784
+ return doCreateTable(acc, a);
1785
+ } else if (
1786
+ a.type === "create view" ||
1787
+ a.type === "create materialized view"
1788
+ ) {
1789
+ return doCreateView(acc, a);
1790
+ } else if (a.type === "alter table") {
1791
+ return doAlterTable(acc, a);
1792
+ } else {
1793
+ return acc;
1794
+ }
1795
+ },
1796
+ { tables: [], views: [] }
1797
+ );
1798
+ }
1799
+
1800
+ function nullifySet(s: SetT): SetT {
1801
+ return {
1802
+ kind: "set",
1803
+ fields: s.fields.map((c) => ({
1804
+ name: c.name,
1805
+ type: nullify(c.type),
1806
+ })),
1807
+ };
1808
+ }
1809
+
1810
+ function nullify(s: SimpleT): SimpleT {
1811
+ if (s.kind === "nullable") {
1812
+ return s;
1813
+ } else {
1814
+ return BuiltinTypeConstructors.Nullable(s);
1815
+ }
1816
+ }
1817
+
1818
+ function unnullify(s: SimpleT): SimpleT {
1819
+ if (s.kind === "nullable") {
1820
+ return s.typevar;
1821
+ } else {
1822
+ return s;
1823
+ }
1824
+ }
1825
+
1826
+ export function checkAllCasesHandled(_: never): any {
1827
+ throw new Error("Oops didn't expect that");
1828
+ }
1829
+
1830
+ function showQName(n: QName): string {
1831
+ return n.schema ? n.schema + "." + n.name : n.name;
1832
+ }
1833
+
1834
+ function eqQNames<U extends QName, V extends QName>(u: U, v: V): boolean {
1835
+ return (
1836
+ u.name.toLowerCase() === v.name.toLowerCase() &&
1837
+ ((!u.schema && (v.schema === "dbo" || v.schema === "pg_catalog")) ||
1838
+ ((u.schema === "dbo" || u.schema === "pg_catalog") && !v.schema) ||
1839
+ (!u.schema && !v.schema) ||
1840
+ u.schema?.toLowerCase() === v.schema?.toLowerCase())
1841
+ );
1842
+ }
1843
+
1844
+ function mapPartial<T, U>(
1845
+ a: Array<T> | ReadonlyArray<T>,
1846
+ f: (t: T, i: number) => U | null
1847
+ ): U[] {
1848
+ const newA: U[] = [];
1849
+ a.forEach(function (a, i) {
1850
+ const res = f(a, i);
1851
+ if (res === null) {
1852
+ } else {
1853
+ newA.push(res);
1854
+ }
1855
+ });
1856
+ return newA.reverse();
1857
+ }
1858
+
1859
+ // function flatMapPartial<T, U>(a: T[], f: (t: T, i: number) => U[] | null): U[] {
1860
+ // const newA: U[] = [];
1861
+ // a.forEach(function (a, i) {
1862
+ // const res = f(a, i);
1863
+ // if (res === null) {
1864
+ // } else {
1865
+ // newA.push(...res);
1866
+ // }
1867
+ // });
1868
+ // return newA.reverse();
1869
+ // }