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/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?