sql-typechecker 0.0.7 → 0.0.8

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