sql-typechecker 0.0.1 → 0.0.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sql-typechecker",
3
- "version": "0.0.1",
3
+ "version": "0.0.5",
4
4
  "main": "out/cli.js",
5
5
  "bin": "out/cli.js",
6
6
  "dependencies": {
@@ -12,8 +12,8 @@
12
12
  "build": "node esbuild.js",
13
13
  "install": "npm run build",
14
14
  "test": "alsatian ./test/**/*.ts",
15
- "start": "npm run build && node --enable-source-maps ./out/cli.js ./sample -o ./sample/out.ts",
16
- "start_school": "npm run build && node --enable-source-maps ./out/cli.js ./school -o ./school/out.ts",
15
+ "start": "npm run build && node --enable-source-maps ./out/cli.js -d ./sample -o ./sample/out.ts",
16
+ "start_school": "npm run build && node --enable-source-maps ./out/cli.js -d ./school -o ./school/out.ts",
17
17
  "debug": "npm run build && node --inspect-brk --enable-source-maps ./out/cli.js ",
18
18
  "test_debug": "node --inspect-brk ./node_modules/alsatian/dist/cli/alsatian-cli.js ./test/**/*.ts -t 20000000"
19
19
  },
package/school/out.ts CHANGED
@@ -4,7 +4,7 @@ export function getemailstosend(
4
4
  args: {}
5
5
  ): Promise<
6
6
  {
7
- uw_id: bigint;
7
+ uw_id: number;
8
8
  uw_from: string;
9
9
  uw_replyto: string | null;
10
10
  uw_address: string;
@@ -17,8 +17,8 @@ export function getemailstosend(
17
17
  uw_content: Buffer | null;
18
18
  }[]
19
19
  > {
20
- return pg`select getemailstosend()`;
21
- /*
20
+ return pg`select getemailstosend() AS getemailstosend(uw_id bigint, uw_from text, uw_replyto text, uw_address text, uw_addressee text, uw_subject text, uw_text text, uw_html text, uw_externalid text, uw_filename text, uw_content bytea)`;
21
+ /* -- ORIGINAL --
22
22
  CREATE FUNCTION getemailstosend() RETURNS SETOF __todo__ AS
23
23
  $$
24
24
  SELECT
@@ -44,10 +44,10 @@ $$ LANGUAGE sql;
44
44
  export function insertnewemailstatus(
45
45
  pg: postgres.Sql<any>,
46
46
  args: { uw_emailid: number; version: number; uw_status: string }
47
- ): Promise<{ uw_emailid: bigint }[]> {
48
- return pg`select insertnewemailstatus(${args.uw_emailid}, ${args.version}, ${args.uw_status})`;
49
- /*
50
- CREATE FUNCTION insertnewemailstatus(uw_emailidinteger, versioninteger, uw_statustext) RETURNS SETOF __todo__ AS
47
+ ): Promise<{ uw_emailid: number } | undefined> {
48
+ return pg`select insertnewemailstatus(${args.uw_emailid}, ${args.version}, ${args.uw_status}) AS insertnewemailstatus(uw_emailid bigint)`[0];
49
+ /* -- ORIGINAL --
50
+ CREATE FUNCTION insertnewemailstatus(uw_emailidinteger, versioninteger, uw_statustext) RETURNS __todo__ AS
51
51
  $$
52
52
  INSERT INTO uw_email_statusses
53
53
  (uw_emailid, uw_version, uw_status, uw_stamp, uw_islastversion)
package/school/sql.sql CHANGED
@@ -956,7 +956,7 @@ CREATE TABLE uw_Payenrollment_onlinepayments(
956
956
  CONSTRAINT uw_Payenrollment_onlinepayments_EnrollmentOrRequest
957
957
  CHECK (((uw_enrollmentId IS NULL) AND (NOT (uw_enrollmentrequestId IS NULL))) OR ((uw_enrollmentrequestId IS NULL) AND (NOT (uw_enrollmentId IS NULL)))));
958
958
 
959
- CREATE OR REPLACE FUNCTION getEmailsToSend() RETURNS SETOF AS $$
959
+ CREATE OR REPLACE FUNCTION getEmailsToSend() RETURNS SETOF RECORD AS $$
960
960
  SELECT
961
961
  e.uw_id,
962
962
  e.uw_from,
package/src/cli.ts CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  import * as fs from "fs/promises";
2
3
  import * as path from "path";
3
4
  import { CreateFunctionStatement, parse, Statement } from "pgsql-ast-parser";
@@ -9,12 +10,13 @@ import {
9
10
  showSqlType,
10
11
  showType,
11
12
  Type,
13
+ SimpleT,
12
14
  } from "./typecheck";
13
15
  import * as prettier from "prettier";
14
16
 
15
17
  go();
16
18
 
17
- async function findSqlFiles(dir: string): Promise<string[]> {
19
+ async function findSqlFilesInDir(dir: string): Promise<string[]> {
18
20
  const inThisDir = await fs.readdir(dir);
19
21
  const res: string[] = [];
20
22
  for (let p of inThisDir) {
@@ -24,7 +26,7 @@ async function findSqlFiles(dir: string): Promise<string[]> {
24
26
  } else {
25
27
  const stat = await fs.stat(fullP);
26
28
  if (stat.isDirectory()) {
27
- const inSubFolder = await findSqlFiles(fullP);
29
+ const inSubFolder = await findSqlFilesInDir(fullP);
28
30
  res.push(...inSubFolder);
29
31
  } else {
30
32
  // not a sql file, not a directory
@@ -88,7 +90,8 @@ function functionToTypescript(f: functionType): string {
88
90
  const returnTypeAsString =
89
91
  f.returns.kind === "void"
90
92
  ? "void"
91
- : showTypeAsTypescriptType(f.returns) + "[]";
93
+ : showTypeAsTypescriptType(f.returns) +
94
+ (f.multipleRows ? "[]" : " | undefined");
92
95
 
93
96
  const argsType =
94
97
  "{" +
@@ -110,12 +113,37 @@ function functionToTypescript(f: functionType): string {
110
113
  .map((k) => k.name.name + showSqlType(k.type))
111
114
  .join(", ");
112
115
 
116
+ function showTypeDroppingNullable(t: SimpleT): string {
117
+ if (t.kind === "nullable") {
118
+ return showTypeDroppingNullable(t.typevar);
119
+ } else if (t.kind === "array") {
120
+ return showTypeDroppingNullable(t.typevar) + "[]";
121
+ } else if (t.kind === "anyscalar") {
122
+ return "anyscalar";
123
+ } else if (t.kind === "scalar") {
124
+ return t.name.name;
125
+ } else {
126
+ return "";
127
+ }
128
+ }
129
+
130
+ const asExpression =
131
+ f.returns.kind === "set"
132
+ ? ` AS ${f.name.name}(${f.returns.fields
133
+ .map(
134
+ (f) => (f.name?.name || "") + " " + showTypeDroppingNullable(f.type)
135
+ )
136
+ .join(", ")})`
137
+ : "";
138
+
113
139
  return `
114
140
  export function ${
115
141
  f.name.name
116
142
  }(pg: postgres.Sql<any>, args: ${argsType}): Promise<${returnTypeAsString}>{
117
- return pg\`select ${f.name.name}(${argsAsList})\`;
118
- /*
143
+ return pg\`select ${f.name.name}(${argsAsList})${asExpression}\`${
144
+ f.multipleRows ? "" : "[0]"
145
+ };
146
+ /* -- ORIGINAL --
119
147
  CREATE FUNCTION ${f.name.name}(${argsForCreateFunction}) RETURNS ${
120
148
  f.multipleRows ? "SETOF " : ""
121
149
  }__todo__ AS
@@ -126,18 +154,28 @@ $$${f.code}$$ LANGUAGE ${f.language};
126
154
  }
127
155
 
128
156
  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);
157
+ const outArgs = findInArgs({ argv: process.argv, flags: ["-o", "--out"] });
158
+ const outArg = outArgs[0];
135
159
  if (!outArg) {
136
160
  throw new Error("Please provide -o/--out parameter");
137
161
  }
138
- const allSqlFiles = await findSqlFiles(path.resolve(process.cwd(), dir));
139
162
 
140
- // console.log(`Processing files: ${allSqlFiles.join(", ")}`);
163
+ const dirs = findInArgs({ argv: process.argv, flags: ["-d", "--dir"] });
164
+ const files = findInArgs({ argv: process.argv, flags: ["-f", "--file"] });
165
+
166
+ const allSqlFiles = (
167
+ await Promise.all(
168
+ dirs.map((dir) => findSqlFilesInDir(path.resolve(process.cwd(), dir)))
169
+ )
170
+ )
171
+ .flat()
172
+ .concat(files);
173
+
174
+ if (allSqlFiles.length === 0) {
175
+ throw new Error(
176
+ "Please provide at least one SQL file with flags -f/--file or -d/--dir"
177
+ );
178
+ }
141
179
 
142
180
  const allStatements: Statement[] = [];
143
181
  for (let sqlFile of allSqlFiles) {
@@ -177,11 +215,14 @@ async function prepOutFile(path: string): Promise<string> {
177
215
  return path;
178
216
  }
179
217
 
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;
218
+ function findInArgs(opts: { argv: string[]; flags: string[] }): string[] {
219
+ let i = 0;
220
+ let res = [];
221
+ for (let arg of opts.argv) {
222
+ if (opts.flags.includes(arg) && opts.argv[i + 1]) {
223
+ res.push(opts.argv[i + 1]);
224
+ }
225
+ i = i + 1;
186
226
  }
227
+ return res;
187
228
  }
package/src/typecheck.ts CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  SelectStatement,
25
25
  Statement,
26
26
  toSql,
27
+ UpdateStatement,
27
28
  } from "pgsql-ast-parser";
28
29
  import { builtincasts } from "./builtincasts";
29
30
  import { builtinoperators } from "./builtinoperators";
@@ -740,21 +741,26 @@ function elabInsert(g: Global, c: Context, s: InsertStatement): VoidT | SetT {
740
741
  // TODO: typecheck s.onConflict
741
742
  }
742
743
 
743
- function elabDelete(g: Global, c: Context, s: DeleteStatement): VoidT | SetT {
744
- const deletingFrom: null | {
744
+ function elabDeleteOrUpdate(
745
+ g: Global,
746
+ c: Context,
747
+ s: DeleteStatement | UpdateStatement
748
+ ): VoidT | SetT {
749
+ const tableName = s.type === "delete" ? s.from : s.table;
750
+ const tableDef: null | {
745
751
  readonly name: QName;
746
752
  readonly rel: SetT;
747
- } = g.tables.find((t) => eqQNames(t.name, s.from)) || null;
753
+ } = g.tables.find((t) => eqQNames(t.name, tableName)) || null;
748
754
 
749
- if (!deletingFrom) {
750
- throw new UnknownIdentifier(s, s.from);
755
+ if (!tableDef) {
756
+ throw new UnknownIdentifier(s, tableName);
751
757
  }
752
- const nameToAddInContext = s.from.alias || s.from.name;
758
+ const nameToAddInContext = tableName.alias || tableName.name;
753
759
  const newContext = {
754
760
  ...c,
755
761
  froms: c.froms.concat({
756
762
  name: { name: nameToAddInContext },
757
- type: deletingFrom.rel,
763
+ type: tableDef.rel,
758
764
  }),
759
765
  };
760
766
 
@@ -850,6 +856,7 @@ export function doCreateFunction(
850
856
  const body = parse(s.code);
851
857
 
852
858
  if (body.length === 0) {
859
+ // empty function body
853
860
  return {
854
861
  name,
855
862
  inputs,
@@ -867,7 +874,7 @@ export function doCreateFunction(
867
874
  name,
868
875
  inputs,
869
876
  returns: returnType,
870
- multipleRows: true, // todo
877
+ multipleRows: (s.returns && s.returns.setof) || false,
871
878
  code: s.code,
872
879
  language: s.language.name,
873
880
  };
@@ -1770,8 +1777,8 @@ function elabStatement(g: Global, c: Context, s: Statement): VoidT | Type {
1770
1777
  return elabExpr(g, c, s);
1771
1778
  } else if (s.type === "insert") {
1772
1779
  return elabInsert(g, c, s);
1773
- } else if (s.type === "delete") {
1774
- return elabDelete(g, c, s);
1780
+ } else if (s.type === "delete" || s.type === "update") {
1781
+ return elabDeleteOrUpdate(g, c, s);
1775
1782
  } else {
1776
1783
  return notImplementedYet(s);
1777
1784
  }