sql-typechecker 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,291 @@
1
+ import { unaryOp } from "./typecheck";
2
+
3
+ const builtinunaryoperatorsFromSyntax: unaryOp[] = [
4
+ // {
5
+ // name: { name: "IS NULL" },
6
+ // operand: { kind: "nullable", typevar: { kind: "anyscalar" } },
7
+ // result: { kind: "scalar", name: { name: "boolean" } },
8
+ // description: "is NULL check",
9
+ // },
10
+ ];
11
+
12
+ const builtinunaryoperatorsFromSchema: unaryOp[] = [
13
+ {
14
+ name: { name: "!" },
15
+ operand: { kind: "scalar", name: { name: "bigint" } },
16
+ result: { kind: "scalar", name: { name: "numeric" } },
17
+ description: "factorial",
18
+ },
19
+ {
20
+ name: { name: "!!" },
21
+ operand: { kind: "scalar", name: { name: "bigint" } },
22
+ result: { kind: "scalar", name: { name: "numeric" } },
23
+ description: "deprecated, use ! instead",
24
+ },
25
+ {
26
+ name: { name: "!!" },
27
+ operand: { kind: "scalar", name: { name: "tsquery" } },
28
+ result: { kind: "scalar", name: { name: "tsquery" } },
29
+ description: "NOT tsquery",
30
+ },
31
+ {
32
+ name: { name: "#" },
33
+ operand: { kind: "scalar", name: { name: "path" } },
34
+ result: { kind: "scalar", name: { name: "integer" } },
35
+ description: "number of points",
36
+ },
37
+ {
38
+ name: { name: "#" },
39
+ operand: { kind: "scalar", name: { name: "polygon" } },
40
+ result: { kind: "scalar", name: { name: "integer" } },
41
+ description: "number of points",
42
+ },
43
+
44
+ {
45
+ name: { name: "+" },
46
+ operand: { kind: "scalar", name: { name: "bigint" } },
47
+ result: { kind: "scalar", name: { name: "bigint" } },
48
+ description: "unary plus",
49
+ },
50
+ {
51
+ name: { name: "+" },
52
+ operand: { kind: "scalar", name: { name: "double precision" } },
53
+ result: { kind: "scalar", name: { name: "double precision" } },
54
+ description: "unary plus",
55
+ },
56
+ {
57
+ name: { name: "+" },
58
+ operand: { kind: "scalar", name: { name: "integer" } },
59
+ result: { kind: "scalar", name: { name: "integer" } },
60
+ description: "unary plus",
61
+ },
62
+ {
63
+ name: { name: "+" },
64
+ operand: { kind: "scalar", name: { name: "numeric" } },
65
+ result: { kind: "scalar", name: { name: "numeric" } },
66
+ description: "unary plus",
67
+ },
68
+ {
69
+ name: { name: "+" },
70
+ operand: { kind: "scalar", name: { name: "real" } },
71
+ result: { kind: "scalar", name: { name: "real" } },
72
+ description: "unary plus",
73
+ },
74
+ {
75
+ name: { name: "+" },
76
+ operand: { kind: "scalar", name: { name: "smallint" } },
77
+ result: { kind: "scalar", name: { name: "smallint" } },
78
+ description: "unary plus",
79
+ },
80
+
81
+ {
82
+ name: { name: "-" },
83
+ operand: { kind: "scalar", name: { name: "bigint" } },
84
+ result: { kind: "scalar", name: { name: "bigint" } },
85
+ description: "negate",
86
+ },
87
+ {
88
+ name: { name: "-" },
89
+ operand: { kind: "scalar", name: { name: "double precision" } },
90
+ result: { kind: "scalar", name: { name: "double precision" } },
91
+ description: "negate",
92
+ },
93
+ {
94
+ name: { name: "-" },
95
+ operand: { kind: "scalar", name: { name: "integer" } },
96
+ result: { kind: "scalar", name: { name: "integer" } },
97
+ description: "negate",
98
+ },
99
+ {
100
+ name: { name: "-" },
101
+ operand: { kind: "scalar", name: { name: "interval" } },
102
+ result: { kind: "scalar", name: { name: "interval" } },
103
+ description: "negate",
104
+ },
105
+ {
106
+ name: { name: "-" },
107
+ operand: { kind: "scalar", name: { name: "numeric" } },
108
+ result: { kind: "scalar", name: { name: "numeric" } },
109
+ description: "negate",
110
+ },
111
+ {
112
+ name: { name: "-" },
113
+ operand: { kind: "scalar", name: { name: "real" } },
114
+ result: { kind: "scalar", name: { name: "real" } },
115
+ description: "negate",
116
+ },
117
+ {
118
+ name: { name: "-" },
119
+ operand: { kind: "scalar", name: { name: "smallint" } },
120
+ result: { kind: "scalar", name: { name: "smallint" } },
121
+ description: "negate",
122
+ },
123
+
124
+ {
125
+ name: { name: "?-" },
126
+ operand: { kind: "scalar", name: { name: "line" } },
127
+ result: { kind: "scalar", name: { name: "boolean" } },
128
+ description: "horizontal",
129
+ },
130
+ {
131
+ name: { name: "?-" },
132
+ operand: { kind: "scalar", name: { name: "lseg" } },
133
+ result: { kind: "scalar", name: { name: "boolean" } },
134
+ description: "horizontal",
135
+ },
136
+
137
+ {
138
+ name: { name: "?|" },
139
+ operand: { kind: "scalar", name: { name: "line" } },
140
+ result: { kind: "scalar", name: { name: "boolean" } },
141
+ description: "vertical",
142
+ },
143
+ {
144
+ name: { name: "?|" },
145
+ operand: { kind: "scalar", name: { name: "lseg" } },
146
+ result: { kind: "scalar", name: { name: "boolean" } },
147
+ description: "vertical",
148
+ },
149
+
150
+ {
151
+ name: { name: "@" },
152
+ operand: { kind: "scalar", name: { name: "bigint" } },
153
+ result: { kind: "scalar", name: { name: "bigint" } },
154
+ description: "absolute value",
155
+ },
156
+ {
157
+ name: { name: "@" },
158
+ operand: { kind: "scalar", name: { name: "double precision" } },
159
+ result: { kind: "scalar", name: { name: "double precision" } },
160
+ description: "absolute value",
161
+ },
162
+ {
163
+ name: { name: "@" },
164
+ operand: { kind: "scalar", name: { name: "integer" } },
165
+ result: { kind: "scalar", name: { name: "integer" } },
166
+ description: "absolute value",
167
+ },
168
+ {
169
+ name: { name: "@" },
170
+ operand: { kind: "scalar", name: { name: "numeric" } },
171
+ result: { kind: "scalar", name: { name: "numeric" } },
172
+ description: "absolute value",
173
+ },
174
+ {
175
+ name: { name: "@" },
176
+ operand: { kind: "scalar", name: { name: "real" } },
177
+ result: { kind: "scalar", name: { name: "real" } },
178
+ description: "absolute value",
179
+ },
180
+ {
181
+ name: { name: "@" },
182
+ operand: { kind: "scalar", name: { name: "smallint" } },
183
+ result: { kind: "scalar", name: { name: "smallint" } },
184
+ description: "absolute value",
185
+ },
186
+ {
187
+ name: { name: "@-@" },
188
+ operand: { kind: "scalar", name: { name: "lseg" } },
189
+ result: { kind: "scalar", name: { name: "double precision" } },
190
+ description: "distance between endpoints",
191
+ },
192
+ {
193
+ name: { name: "@-@" },
194
+ operand: { kind: "scalar", name: { name: "path" } },
195
+ result: { kind: "scalar", name: { name: "double precision" } },
196
+ description: "sum of path segment lengths",
197
+ },
198
+
199
+ {
200
+ name: { name: "@@" },
201
+ operand: { kind: "scalar", name: { name: "box" } },
202
+ result: { kind: "scalar", name: { name: "point" } },
203
+ description: "center of",
204
+ },
205
+ {
206
+ name: { name: "@@" },
207
+ operand: { kind: "scalar", name: { name: "circle" } },
208
+ result: { kind: "scalar", name: { name: "point" } },
209
+ description: "center of",
210
+ },
211
+ {
212
+ name: { name: "@@" },
213
+ operand: { kind: "scalar", name: { name: "lseg" } },
214
+ result: { kind: "scalar", name: { name: "point" } },
215
+ description: "center of",
216
+ },
217
+ {
218
+ name: { name: "@@" },
219
+ operand: { kind: "scalar", name: { name: "path" } },
220
+ result: { kind: "scalar", name: { name: "point" } },
221
+ description: "center of",
222
+ },
223
+ {
224
+ name: { name: "@@" },
225
+ operand: { kind: "scalar", name: { name: "polygon" } },
226
+ result: { kind: "scalar", name: { name: "point" } },
227
+ description: "center of",
228
+ },
229
+
230
+ {
231
+ name: { name: "|" },
232
+ operand: { kind: "scalar", name: { name: "tinterval" } },
233
+ result: { kind: "scalar", name: { name: "abstime" } },
234
+ description: "start of interval",
235
+ },
236
+
237
+ {
238
+ name: { name: "|/" },
239
+ operand: { kind: "scalar", name: { name: "double precision" } },
240
+ result: { kind: "scalar", name: { name: "double precision" } },
241
+ description: "square root",
242
+ },
243
+
244
+ {
245
+ name: { name: "||/" },
246
+ operand: { kind: "scalar", name: { name: "double precision" } },
247
+ result: { kind: "scalar", name: { name: "double precision" } },
248
+ description: "cube root",
249
+ },
250
+
251
+ {
252
+ name: { name: "~" },
253
+ operand: { kind: "scalar", name: { name: "bigint" } },
254
+ result: { kind: "scalar", name: { name: "bigint" } },
255
+ description: "bitwise not",
256
+ },
257
+ {
258
+ name: { name: "~" },
259
+ operand: { kind: "scalar", name: { name: "bit" } },
260
+ result: { kind: "scalar", name: { name: "bit" } },
261
+ description: "bitwise not",
262
+ },
263
+ {
264
+ name: { name: "~" },
265
+ operand: { kind: "scalar", name: { name: "inet" } },
266
+ result: { kind: "scalar", name: { name: "inet" } },
267
+ description: "bitwise not",
268
+ },
269
+ {
270
+ name: { name: "~" },
271
+ operand: { kind: "scalar", name: { name: "integer" } },
272
+ result: { kind: "scalar", name: { name: "integer" } },
273
+ description: "bitwise not",
274
+ },
275
+ {
276
+ name: { name: "~" },
277
+ operand: { kind: "scalar", name: { name: "macaddr" } },
278
+ result: { kind: "scalar", name: { name: "macaddr" } },
279
+ description: "bitwise not",
280
+ },
281
+ {
282
+ name: { name: "~" },
283
+ operand: { kind: "scalar", name: { name: "smallint" } },
284
+ result: { kind: "scalar", name: { name: "smallint" } },
285
+ description: "bitwise not",
286
+ },
287
+ ];
288
+
289
+ export const builtinUnaryOperators = builtinunaryoperatorsFromSyntax.concat(
290
+ builtinunaryoperatorsFromSchema
291
+ );
package/src/cli.ts ADDED
@@ -0,0 +1,187 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import { CreateFunctionStatement, parse, Statement } from "pgsql-ast-parser";
4
+ import {
5
+ checkAllCasesHandled,
6
+ doCreateFunction,
7
+ functionType,
8
+ parseSetupScripts,
9
+ showSqlType,
10
+ showType,
11
+ Type,
12
+ } from "./typecheck";
13
+ import * as prettier from "prettier";
14
+
15
+ go();
16
+
17
+ async function findSqlFiles(dir: string): Promise<string[]> {
18
+ const inThisDir = await fs.readdir(dir);
19
+ const res: string[] = [];
20
+ for (let p of inThisDir) {
21
+ const fullP = path.join(dir, p);
22
+ if (fullP.endsWith(".sql")) {
23
+ res.push(fullP);
24
+ } else {
25
+ const stat = await fs.stat(fullP);
26
+ if (stat.isDirectory()) {
27
+ const inSubFolder = await findSqlFiles(fullP);
28
+ res.push(...inSubFolder);
29
+ } else {
30
+ // not a sql file, not a directory
31
+ }
32
+ }
33
+ }
34
+ return res;
35
+ }
36
+
37
+ function isCreateFunctionStatement(
38
+ st: Statement
39
+ ): st is CreateFunctionStatement {
40
+ return st.type === "create function";
41
+ }
42
+
43
+ export function showTypeAsTypescriptType(t: Type): string {
44
+ if (t.kind === "set") {
45
+ return (
46
+ "{" +
47
+ t.fields
48
+ .map(
49
+ (f) =>
50
+ (f.name === null ? `"?": ` : `"${f.name.name}": `) +
51
+ showTypeAsTypescriptType(f.type)
52
+ )
53
+ .join(", ") +
54
+ "}"
55
+ );
56
+ } else {
57
+ if (t.kind === "array") {
58
+ return "(" + showTypeAsTypescriptType(t.typevar) + ")" + "[]";
59
+ } else if (t.kind === "nullable") {
60
+ return showTypeAsTypescriptType(t.typevar) + " | null";
61
+ } else if (t.kind === "scalar") {
62
+ if (
63
+ ["numeric", "bigint", "smallint", "integer", "real", "double"].includes(
64
+ t.name.name
65
+ )
66
+ ) {
67
+ return "number";
68
+ } else if (
69
+ ["text", "name", "char", "character", "varchar", "nvarchar"].includes(
70
+ t.name.name
71
+ )
72
+ ) {
73
+ return "string";
74
+ } else if (["bytea"].includes(t.name.name)) {
75
+ return "Buffer";
76
+ } else {
77
+ return t.name.name;
78
+ }
79
+ } else if (t.kind === "anyscalar") {
80
+ return "anyscalar";
81
+ } else {
82
+ return checkAllCasesHandled(t);
83
+ }
84
+ }
85
+ }
86
+
87
+ function functionToTypescript(f: functionType): string {
88
+ const returnTypeAsString =
89
+ f.returns.kind === "void"
90
+ ? "void"
91
+ : showTypeAsTypescriptType(f.returns) + "[]";
92
+
93
+ const argsType =
94
+ "{" +
95
+ f.inputs
96
+ .map((k) => {
97
+ const paramTypeAsString = showTypeAsTypescriptType(k.type);
98
+
99
+ // console.log(`Param \$${k.name.name}:\n`, paramTypeAsString, "\n");
100
+ return k.name.name + ": " + paramTypeAsString;
101
+ })
102
+ .join(", ") +
103
+ "}";
104
+
105
+ const argsAsList = f.inputs
106
+ .map((i) => "${args." + i.name.name + "}")
107
+ .join(", ");
108
+
109
+ const argsForCreateFunction = f.inputs
110
+ .map((k) => k.name.name + showSqlType(k.type))
111
+ .join(", ");
112
+
113
+ return `
114
+ export function ${
115
+ f.name.name
116
+ }(pg: postgres.Sql<any>, args: ${argsType}): Promise<${returnTypeAsString}>{
117
+ return pg\`select ${f.name.name}(${argsAsList})\`;
118
+ /*
119
+ CREATE FUNCTION ${f.name.name}(${argsForCreateFunction}) RETURNS ${
120
+ f.multipleRows ? "SETOF " : ""
121
+ }__todo__ AS
122
+ $$${f.code}$$ LANGUAGE ${f.language};
123
+ */
124
+ }
125
+ `;
126
+ }
127
+
128
+ async function go() {
129
+ const dir = process.argv[2];
130
+ if (!dir) {
131
+ throw new Error("Please provide directory with SQL files");
132
+ }
133
+
134
+ const outArg = findOutArg(process.argv);
135
+ if (!outArg) {
136
+ throw new Error("Please provide -o/--out parameter");
137
+ }
138
+ const allSqlFiles = await findSqlFiles(path.resolve(process.cwd(), dir));
139
+
140
+ // console.log(`Processing files: ${allSqlFiles.join(", ")}`);
141
+
142
+ const allStatements: Statement[] = [];
143
+ for (let sqlFile of allSqlFiles) {
144
+ console.log("Processing file ${sqlFile}");
145
+ const fileContents = await fs.readFile(sqlFile, "utf-8");
146
+ const statements: Statement[] = parse(fileContents);
147
+ allStatements.push(...statements);
148
+ }
149
+
150
+ console.log(`Processing ${allStatements.length} statements`);
151
+
152
+ const g = parseSetupScripts(allStatements);
153
+
154
+ // console.log("Global:\n", JSON.stringify(g, null, 2), "\n");
155
+
156
+ const createFunctionStatements = allStatements.filter(
157
+ isCreateFunctionStatement
158
+ );
159
+
160
+ const outfile = await prepOutFile(path.resolve(process.cwd(), outArg));
161
+
162
+ for (let st of createFunctionStatements) {
163
+ const res = doCreateFunction(g, { decls: [], froms: [] }, st);
164
+ const writing = prettier.format(functionToTypescript(res), {
165
+ parser: "typescript",
166
+ });
167
+ // console.log(`Writing: ${writing}`);
168
+ await fs.appendFile(outfile, writing, "utf-8");
169
+ }
170
+ }
171
+
172
+ async function prepOutFile(path: string): Promise<string> {
173
+ // const stat = await fs.stat(path);
174
+ // if (!stat.isFile)
175
+ // await fs.truncate(path);
176
+ await fs.writeFile(path, `import postgres from "postgres";\n`, "utf-8");
177
+ return path;
178
+ }
179
+
180
+ function findOutArg(args: string[]): string | null {
181
+ const flagIndex = args.findIndex((arg) => arg === "-o" || arg === "--out");
182
+ if (!flagIndex) {
183
+ return null;
184
+ } else {
185
+ return args[flagIndex + 1] || null;
186
+ }
187
+ }
package/src/readme.md ADDED
@@ -0,0 +1,23 @@
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?