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/package.json +2 -2
- package/.envrc +0 -2
- package/esbuild.js +0 -12
- package/index.ts +0 -22
- package/sample/nested/sample2.sql +0 -26
- package/sample/out.ts +0 -70
- package/sample/sample1.sql +0 -20
- package/school/sql.sql +0 -1055
- package/school/test.ts +0 -27
- package/shell.nix +0 -16
- package/src/builtincasts.ts +0 -277
- package/src/builtinoperators.ts +0 -5144
- package/src/builtinunaryoperators.ts +0 -291
- package/src/cli.ts +0 -144
- package/src/codegen.ts +0 -384
- package/src/readme.md +0 -23
- package/src/typecheck.ts +0 -2143
- package/src/typeparsers.ts +0 -33
- package/template1.sql +0 -43
- package/test/test.ts +0 -1378
- package/tsconfig.json +0 -23
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
|
-
// }
|