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.
- package/.envrc +2 -0
- package/esbuild.js +12 -0
- package/package.json +28 -0
- package/sample/nested/sample2.sql +7 -0
- package/sample/out.ts +13 -0
- package/sample/sample1.sql +10 -0
- package/school/out.ts +59 -0
- package/school/sql.sql +985 -0
- package/shell.nix +16 -0
- package/src/builtincasts.ts +277 -0
- package/src/builtinoperators.ts +5144 -0
- package/src/builtinunaryoperators.ts +291 -0
- package/src/cli.ts +187 -0
- package/src/readme.md +23 -0
- package/src/typecheck.ts +1869 -0
- package/template1.sql +43 -0
- package/test/test.ts +1378 -0
- package/tsconfig.json +20 -0
package/src/typecheck.ts
ADDED
|
@@ -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
|
+
// }
|