sql-typechecker 0.0.7 → 0.0.10
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/out/cli.js +133 -88
- package/out/cli.js.map +2 -2
- package/out/typeparsers.js +1 -1
- package/out/typeparsers.js.map +2 -2
- package/package.json +5 -5
- 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/codegen.ts
DELETED
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
import { Name, QName } from "pgsql-ast-parser";
|
|
2
|
-
import {
|
|
3
|
-
checkAllCasesHandled,
|
|
4
|
-
functionType,
|
|
5
|
-
JsonKnownT,
|
|
6
|
-
RecordT,
|
|
7
|
-
showQName,
|
|
8
|
-
showSqlType,
|
|
9
|
-
SimpleT,
|
|
10
|
-
Type,
|
|
11
|
-
VoidT,
|
|
12
|
-
} from "./typecheck";
|
|
13
|
-
|
|
14
|
-
export function showTypeAsTypescriptType(t: Type): string {
|
|
15
|
-
if (t.kind === "record") {
|
|
16
|
-
return (
|
|
17
|
-
"{" +
|
|
18
|
-
t.fields
|
|
19
|
-
.map(
|
|
20
|
-
(f) =>
|
|
21
|
-
(f.name === null ? `"?": ` : `"${f.name.name}": `) +
|
|
22
|
-
showTypeAsTypescriptType(f.type)
|
|
23
|
-
)
|
|
24
|
-
.join(", ") +
|
|
25
|
-
"}"
|
|
26
|
-
);
|
|
27
|
-
} else {
|
|
28
|
-
if (t.kind === "array") {
|
|
29
|
-
return "(" + showTypeAsTypescriptType(t.typevar) + ")" + "[]";
|
|
30
|
-
} else if (t.kind === "nullable") {
|
|
31
|
-
return showTypeAsTypescriptType(t.typevar) + " | null";
|
|
32
|
-
} else if (t.kind === "scalar") {
|
|
33
|
-
if (
|
|
34
|
-
["numeric", "bigint", "smallint", "integer", "real", "double"].includes(
|
|
35
|
-
t.name.name
|
|
36
|
-
)
|
|
37
|
-
) {
|
|
38
|
-
return "number";
|
|
39
|
-
} else if (
|
|
40
|
-
["text", "name", "char", "character", "varchar", "nvarchar"].includes(
|
|
41
|
-
t.name.name
|
|
42
|
-
)
|
|
43
|
-
) {
|
|
44
|
-
return "string";
|
|
45
|
-
} else if (["bytea"].includes(t.name.name)) {
|
|
46
|
-
return "Buffer";
|
|
47
|
-
} else if (t.name.name === "date") {
|
|
48
|
-
return "LocalDate";
|
|
49
|
-
} else if (t.name.name === "time") {
|
|
50
|
-
return "LocalTime";
|
|
51
|
-
} else if (
|
|
52
|
-
t.name.name === "timestamp without time zone" ||
|
|
53
|
-
t.name.name === "timestamp"
|
|
54
|
-
) {
|
|
55
|
-
return "LocalDateTime";
|
|
56
|
-
} else {
|
|
57
|
-
return t.name.name;
|
|
58
|
-
}
|
|
59
|
-
} else if (t.kind === "jsonknown") {
|
|
60
|
-
return (
|
|
61
|
-
"{\n" +
|
|
62
|
-
t.record.fields
|
|
63
|
-
.map((f) => ` ${f.name?.name}: ${showTypeAsTypescriptType(f.type)}`)
|
|
64
|
-
.join(",\n") +
|
|
65
|
-
"\n}"
|
|
66
|
-
);
|
|
67
|
-
} else if (t.kind === "anyscalar") {
|
|
68
|
-
return "anyscalar";
|
|
69
|
-
} else {
|
|
70
|
-
return checkAllCasesHandled(t);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function genDeserializeSimpleT(t: SimpleT, literalVar: string): string {
|
|
76
|
-
if (t.kind === "array") {
|
|
77
|
-
return `${literalVar}.map((el: any) => ${genDeserializeSimpleT(
|
|
78
|
-
t.typevar as SimpleT,
|
|
79
|
-
"el"
|
|
80
|
-
)})`;
|
|
81
|
-
} else if (t.kind === "nullable") {
|
|
82
|
-
const inner = genDeserializeSimpleT(t.typevar as SimpleT, literalVar);
|
|
83
|
-
if (inner === literalVar) {
|
|
84
|
-
return inner;
|
|
85
|
-
} else {
|
|
86
|
-
return `${literalVar} === null ? (${inner}) : null`;
|
|
87
|
-
}
|
|
88
|
-
} else if (t.kind === "anyscalar") {
|
|
89
|
-
return literalVar;
|
|
90
|
-
} else if (t.kind === "jsonknown") {
|
|
91
|
-
return (
|
|
92
|
-
"({" +
|
|
93
|
-
t.record.fields
|
|
94
|
-
.map(
|
|
95
|
-
(f) =>
|
|
96
|
-
`${f.name?.name || "?"}: ${genDeserializeSimpleT(
|
|
97
|
-
f.type,
|
|
98
|
-
literalVar + '["' + f.name?.name + '"]'
|
|
99
|
-
)}`
|
|
100
|
-
)
|
|
101
|
-
.join(",\n") +
|
|
102
|
-
"})"
|
|
103
|
-
);
|
|
104
|
-
} else if (t.kind === "scalar") {
|
|
105
|
-
if (t.name.name === "date") {
|
|
106
|
-
return `LocalDate.parse(${literalVar})`;
|
|
107
|
-
} else if (t.name.name === "time") {
|
|
108
|
-
return `LocalTime.parse(${literalVar})`;
|
|
109
|
-
} else if (t.name.name === "timestamp with time zone") {
|
|
110
|
-
return `Instant.parse(${literalVar})`;
|
|
111
|
-
} else if (
|
|
112
|
-
t.name.name === "timestamp without time zone" ||
|
|
113
|
-
t.name.name === "timestamp"
|
|
114
|
-
) {
|
|
115
|
-
return `LocalDateTime.parse(${literalVar})`;
|
|
116
|
-
} else {
|
|
117
|
-
return literalVar;
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
return checkAllCasesHandled(t);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function genDeserialization(
|
|
125
|
-
returnType: SimpleT | RecordT | VoidT,
|
|
126
|
-
literalVar: string
|
|
127
|
-
) {
|
|
128
|
-
if (returnType.kind === "void") {
|
|
129
|
-
return literalVar;
|
|
130
|
-
} else if (returnType.kind === "record") {
|
|
131
|
-
return (
|
|
132
|
-
"({" +
|
|
133
|
-
returnType.fields
|
|
134
|
-
.map(
|
|
135
|
-
(f, i) =>
|
|
136
|
-
`${f.name?.name || "?"}: ${genDeserializeSimpleT(
|
|
137
|
-
f.type,
|
|
138
|
-
literalVar + "[" + i + "]"
|
|
139
|
-
)}`
|
|
140
|
-
)
|
|
141
|
-
.join(",\n") +
|
|
142
|
-
"})"
|
|
143
|
-
);
|
|
144
|
-
} else {
|
|
145
|
-
`function deserialize(cells: unknown[]): any{
|
|
146
|
-
return ${genDeserializeSimpleT(returnType, "cells[0]")};
|
|
147
|
-
}`;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function functionToTypescript(f: functionType): string {
|
|
152
|
-
const returnTypeAsString =
|
|
153
|
-
f.returns.kind === "void"
|
|
154
|
-
? "void"
|
|
155
|
-
: showTypeAsTypescriptType(f.returns) +
|
|
156
|
-
(f.multipleRows ? "[]" : " | undefined");
|
|
157
|
-
|
|
158
|
-
const argsType =
|
|
159
|
-
"{" +
|
|
160
|
-
f.inputs
|
|
161
|
-
.map((k) => {
|
|
162
|
-
const paramTypeAsString = showTypeAsTypescriptType(k.type);
|
|
163
|
-
|
|
164
|
-
// console.log(`Param \$${k.name.name}:\n`, paramTypeAsString, "\n");
|
|
165
|
-
return k.name.name + ": " + paramTypeAsString;
|
|
166
|
-
})
|
|
167
|
-
.join(", ") +
|
|
168
|
-
"}";
|
|
169
|
-
|
|
170
|
-
const argsAsList = f.inputs.map((i) => "args." + i.name.name).join(", ");
|
|
171
|
-
|
|
172
|
-
const argsForCreateFunction = f.inputs
|
|
173
|
-
.map((k) => k.name.name + " " + showSqlType(k.type))
|
|
174
|
-
.join(", ");
|
|
175
|
-
|
|
176
|
-
function showTypeDroppingNullable(t: SimpleT | JsonKnownT): string {
|
|
177
|
-
if (t.kind === "nullable") {
|
|
178
|
-
return showTypeDroppingNullable(t.typevar);
|
|
179
|
-
} else if (t.kind === "array") {
|
|
180
|
-
return showTypeDroppingNullable(t.typevar) + "[]";
|
|
181
|
-
} else if (t.kind === "anyscalar") {
|
|
182
|
-
return "anyscalar";
|
|
183
|
-
} else if (t.kind === "scalar") {
|
|
184
|
-
return t.name.name;
|
|
185
|
-
} else if (t.kind === "jsonknown") {
|
|
186
|
-
return "json";
|
|
187
|
-
} else {
|
|
188
|
-
return "";
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const asExpression =
|
|
193
|
-
f.returns.kind === "record"
|
|
194
|
-
? ` AS ${f.name.name}(${f.returns.fields
|
|
195
|
-
.map(
|
|
196
|
-
(f) => (f.name?.name || "") + " " + showTypeDroppingNullable(f.type)
|
|
197
|
-
)
|
|
198
|
-
.join(", ")})`
|
|
199
|
-
: "";
|
|
200
|
-
|
|
201
|
-
const recreatedSqlFunctionStatement = `
|
|
202
|
-
CREATE FUNCTION ${f.name.name}(${argsForCreateFunction}) RETURNS ${
|
|
203
|
-
f.multipleRows ? "SETOF " : ""
|
|
204
|
-
}${
|
|
205
|
-
f.returns.kind === "record"
|
|
206
|
-
? "RECORD"
|
|
207
|
-
: f.returns.kind === "void"
|
|
208
|
-
? "void"
|
|
209
|
-
: showTypeDroppingNullable(f.returns)
|
|
210
|
-
} AS
|
|
211
|
-
$$${f.code}$$ LANGUAGE ${f.language};
|
|
212
|
-
`;
|
|
213
|
-
|
|
214
|
-
const funcInvocation = `${f.name.name}(${f.inputs.map(
|
|
215
|
-
(inp, i) => "$" + (i + 1) + "::" + showTypeDroppingNullable(inp.type)
|
|
216
|
-
)})${asExpression}`;
|
|
217
|
-
|
|
218
|
-
return `
|
|
219
|
-
export async function ${f.name.name}(pool: Pool, args: ${argsType})
|
|
220
|
-
: Promise<${returnTypeAsString}>{
|
|
221
|
-
|
|
222
|
-
const res = await pool.query({
|
|
223
|
-
text: "SELECT * FROM ${funcInvocation}",
|
|
224
|
-
values: [${argsAsList}],
|
|
225
|
-
rowMode: "array",
|
|
226
|
-
});
|
|
227
|
-
const rows = res.rows.map(row => ${genDeserialization(f.returns, "row")});
|
|
228
|
-
debugger;
|
|
229
|
-
return rows${f.multipleRows ? "" : "[0]"};
|
|
230
|
-
}
|
|
231
|
-
`;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export function genDomain(dom: {
|
|
235
|
-
readonly name: QName;
|
|
236
|
-
readonly type: SimpleT;
|
|
237
|
-
}): string {
|
|
238
|
-
return `export type ${dom.name.name} = ${showTypeAsTypescriptType(
|
|
239
|
-
dom.type
|
|
240
|
-
)} & { readonly __tag: "${dom.name.name}" };`;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function getImports() {
|
|
244
|
-
return `
|
|
245
|
-
import type { Pool } from "pg";
|
|
246
|
-
import { Instant, LocalDate, LocalTime, LocalDateTime} from "@js-joda/core";
|
|
247
|
-
`;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export function genCrudOperations(table: {
|
|
251
|
-
readonly name: QName;
|
|
252
|
-
readonly rel: RecordT;
|
|
253
|
-
readonly primaryKey: Name[];
|
|
254
|
-
readonly defaults: Name[];
|
|
255
|
-
}): string {
|
|
256
|
-
const selectAll = `
|
|
257
|
-
export async function getAll(pool: Pool): Promise<${showTypeAsTypescriptType(
|
|
258
|
-
table.rel
|
|
259
|
-
)}[]>{
|
|
260
|
-
|
|
261
|
-
const res = await pool.query({
|
|
262
|
-
text: "SELECT * FROM ${showQName(table.name)}",
|
|
263
|
-
values: [],
|
|
264
|
-
rowMode: "array",
|
|
265
|
-
});
|
|
266
|
-
const rows = res.rows.map(row => ${genDeserialization(table.rel, "row")});
|
|
267
|
-
return rows;
|
|
268
|
-
}`;
|
|
269
|
-
|
|
270
|
-
const primaryKeySingleCol: null | { name: Name; type: SimpleT } =
|
|
271
|
-
(function getPrimaryKey() {
|
|
272
|
-
if (table.primaryKey.length === 1) {
|
|
273
|
-
return {
|
|
274
|
-
name: table.primaryKey[0],
|
|
275
|
-
type: table.rel.fields.find(
|
|
276
|
-
(f) => f.name?.name === table.primaryKey[0].name
|
|
277
|
-
)?.type!,
|
|
278
|
-
};
|
|
279
|
-
} else {
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
})();
|
|
283
|
-
|
|
284
|
-
if (!primaryKeySingleCol) {
|
|
285
|
-
return selectAll;
|
|
286
|
-
} else {
|
|
287
|
-
const relWithoutPrim = table.rel.fields.filter(
|
|
288
|
-
(f) => f.name?.name !== primaryKeySingleCol.name.name
|
|
289
|
-
);
|
|
290
|
-
const mandatoryFields = table.rel.fields.filter(
|
|
291
|
-
(c) => !table.defaults.some((def) => def.name === c.name?.name)
|
|
292
|
-
);
|
|
293
|
-
const optionalFields = table.rel.fields.filter((c) =>
|
|
294
|
-
table.defaults.some((def) => def.name === c.name?.name)
|
|
295
|
-
);
|
|
296
|
-
const inputRow =
|
|
297
|
-
mandatoryFields
|
|
298
|
-
.map(
|
|
299
|
-
(f) => `
|
|
300
|
-
${f.name?.name}: ${showTypeAsTypescriptType(f.type)}`
|
|
301
|
-
)
|
|
302
|
-
.join(",") +
|
|
303
|
-
optionalFields
|
|
304
|
-
.map(
|
|
305
|
-
(f) => `
|
|
306
|
-
${f.name?.name}?: ${showTypeAsTypescriptType(f.type)}`
|
|
307
|
-
)
|
|
308
|
-
.join(",");
|
|
309
|
-
const insert = `
|
|
310
|
-
export async function insert(pool: Pool, row: {${inputRow}}): Promise<{${
|
|
311
|
-
primaryKeySingleCol.name.name
|
|
312
|
-
}: ${showTypeAsTypescriptType(primaryKeySingleCol.type)}} | null>{
|
|
313
|
-
|
|
314
|
-
const providedFields = Object.keys(row);
|
|
315
|
-
|
|
316
|
-
const res = await pool.query({
|
|
317
|
-
text: "INSERT INTO ${showQName(
|
|
318
|
-
table.name
|
|
319
|
-
)} (" + (providedFields.join(", ")) + ") VALUES (" + providedFields.map((_, i) => "$" + (i + 1)).join(", ") +") RETURNING ${
|
|
320
|
-
primaryKeySingleCol.name.name
|
|
321
|
-
}",
|
|
322
|
-
values: providedFields.map(f => row[f]),
|
|
323
|
-
rowMode: "array",
|
|
324
|
-
});
|
|
325
|
-
if (res && res[0]){
|
|
326
|
-
return {${primaryKeySingleCol.name.name}: res[0][0]};
|
|
327
|
-
} else {
|
|
328
|
-
return null;
|
|
329
|
-
}
|
|
330
|
-
}`;
|
|
331
|
-
|
|
332
|
-
const inputRowForUpdate = relWithoutPrim
|
|
333
|
-
.map(
|
|
334
|
-
(f) => `
|
|
335
|
-
${f.name?.name}?: ${showTypeAsTypescriptType(f.type)}`
|
|
336
|
-
)
|
|
337
|
-
.join(",");
|
|
338
|
-
const update = `
|
|
339
|
-
export async function update(pool: Pool, pk: {${
|
|
340
|
-
primaryKeySingleCol.name.name
|
|
341
|
-
}: ${showTypeAsTypescriptType(
|
|
342
|
-
primaryKeySingleCol.type
|
|
343
|
-
)}}, row: {${inputRowForUpdate}}): Promise<null>{
|
|
344
|
-
|
|
345
|
-
const providedFields = Object.keys(row);
|
|
346
|
-
if (providedFields.length === 0){ return null; }
|
|
347
|
-
|
|
348
|
-
await pool.query({
|
|
349
|
-
text: "UPDATE ${showQName(
|
|
350
|
-
table.name
|
|
351
|
-
)} SET " + providedFields.map((f, i) => f + " = $" + (i + 2)).join(", ") + " WHERE ${
|
|
352
|
-
primaryKeySingleCol.name.name
|
|
353
|
-
} = $1",
|
|
354
|
-
values: [pk.${
|
|
355
|
-
primaryKeySingleCol.name.name
|
|
356
|
-
}].concat(providedFields.map(f => row[f])),
|
|
357
|
-
rowMode: "array",
|
|
358
|
-
});
|
|
359
|
-
return null;
|
|
360
|
-
}`;
|
|
361
|
-
|
|
362
|
-
const del = `
|
|
363
|
-
export async function del(pool: Pool, pk: {${
|
|
364
|
-
primaryKeySingleCol.name.name
|
|
365
|
-
}: ${showTypeAsTypescriptType(primaryKeySingleCol.type)}}): Promise<null>{
|
|
366
|
-
|
|
367
|
-
await pool.query({
|
|
368
|
-
text: "DELETE FROM ${showQName(table.name)} WHERE ${
|
|
369
|
-
primaryKeySingleCol.name.name
|
|
370
|
-
} = $1",
|
|
371
|
-
values: [pk.${primaryKeySingleCol.name.name}],
|
|
372
|
-
rowMode: "array",
|
|
373
|
-
});
|
|
374
|
-
return null;
|
|
375
|
-
}`;
|
|
376
|
-
|
|
377
|
-
return `
|
|
378
|
-
${selectAll}
|
|
379
|
-
${insert}
|
|
380
|
-
${update}
|
|
381
|
-
${del}
|
|
382
|
-
`;
|
|
383
|
-
}
|
|
384
|
-
}
|
package/src/readme.md
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
Huidige plan:
|
|
2
|
-
|
|
3
|
-
We werken niet meer met positionele parameters (da's toch maar een lapmiddel, niet echt deel van Postgres) maar we gaan enkel functies declareren en die typechecken en daar (typescript) bindings van maken.
|
|
4
|
-
|
|
5
|
-
Daarmee weet je dus op voorhand welke parameters je hebt. Let bindings, from's etc, kunnen ook wel dingen in de context steken, maar da's wel beter afgelijnd. Die blocks zorgen er wel voor dat immutable Context's wel een goed idee zijn, zo volgen die de flow meteen mee.
|
|
6
|
-
|
|
7
|
-
Idealiter zouden we LANGUAGE plpgsql en LANGUAGE sql moeten ondersteunen!
|
|
8
|
-
-> Als we beginnen met LANGUAGE sql, dan moeten we (nog) geen parser schrijven!
|
|
9
|
-
|
|
10
|
-
Type inference:
|
|
11
|
-
* Type is mandatory in DECLARE blocks
|
|
12
|
-
* Type is mandatory in CREATE FUNCTION parameters
|
|
13
|
-
* We kunnen dus aan alle Context.decls meteen een type geven. Enkel nullability heb je niet.
|
|
14
|
-
* Of wat als we gewoon alle (user-defined) functie parameters not-nullable by default zetten, en je moet "default NULL" meegeven om ze nullable te definiëren? -> Dit is volgens mij eigenlijk het beste, maar het staat redelijk ver af van hoe Postgres het zelf doet. Op zich voor Aperi zou dit wel goed werken... YUP WE GAAN DIT DOEN
|
|
15
|
-
* Oude discussie:
|
|
16
|
-
* Als we dat immutable willen bijhouden, dan moeten we dus ook weer heel de context de hele tijd meesleuren
|
|
17
|
-
* Als we dat mutable maken, volg je niet meer de volledige control flow, maar da's ook niet echt nodig denk ik want die moeten altijd "kloppen"
|
|
18
|
-
* Oude discussie: Moeten we parameters eerste als nullable, of als not-nullable beschouwen?
|
|
19
|
-
* Indien CALLED ON NULL INPUT (=default): Alles is nullable. (NOPE ZIE VERDER)
|
|
20
|
-
* Indien RETURNS NULL ON NULL INPUT of STRICT: Niks is nullable? . (NOPE ZIE VERDER)
|
|
21
|
-
* Eigenlijk betekent dit: Als je mij ergens een NULL geeft, geef ik NULL terug. De body wordt niet uitgevoerd. Dus inderdaad, je gaat altijd in de function body zelf not-nullable zijn! Als je zo'n functie aanroept kan je echter wel NULL terugkrijgen, als een van de argumenten NULL is. Je kan dat als speciale modifier op die functie bijhouden, en als een van de argumenten nullable is, dan is het return type ook nullable.. (NOPE ZIE VERDER)
|
|
22
|
-
* Optional parameters kunnen we wel nog ondersteunen met "myvar int default NULL". NOPE toch niet, eens STRICT kan je nooit nullable parameters binnenkrijgen. . (NOPE ZIE VERDER)
|
|
23
|
-
* Moeten we een optie voorzien (of gewoon default zo doen) dat input params aan STRICT functions nooit NULL mogen zijn?
|