@technicity/data-service-generator 0.22.2 → 0.23.0-next.0
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/dist/src/generation/generate.d.ts +21 -0
- package/dist/src/generation/generate.js +2349 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +11 -0
- package/dist/src/lib/CustomError.d.ts +3 -0
- package/dist/src/lib/CustomError.js +10 -0
- package/dist/src/lib/capitalizeFirstLetter.d.ts +1 -0
- package/dist/src/lib/capitalizeFirstLetter.js +6 -0
- package/dist/src/lib/getDuplicates.d.ts +1 -0
- package/dist/src/lib/getDuplicates.js +9 -0
- package/dist/src/lib/isNotNullOrUndefined.d.ts +1 -0
- package/dist/src/lib/isNotNullOrUndefined.js +7 -0
- package/dist/src/runtime/Cache.d.ts +28 -0
- package/dist/src/runtime/Cache.js +142 -0
- package/dist/src/runtime/IRuntime.d.ts +209 -0
- package/dist/src/runtime/IRuntime.js +12 -0
- package/dist/src/runtime/RuntimeMySQL.d.ts +26 -0
- package/dist/src/runtime/RuntimeMySQL.js +132 -0
- package/dist/src/runtime/RuntimePostgreSQL.d.ts +30 -0
- package/dist/src/runtime/RuntimePostgreSQL.js +73 -0
- package/dist/src/runtime/RuntimeSQLite.d.ts +42 -0
- package/dist/src/runtime/RuntimeSQLite.js +150 -0
- package/dist/src/runtime/Stats.d.ts +8 -0
- package/dist/src/runtime/Stats.js +31 -0
- package/dist/src/runtime/lib/MySQL.d.ts +13 -0
- package/dist/src/runtime/lib/MySQL.js +116 -0
- package/dist/src/runtime/lib/PostgreSQL.d.ts +14 -0
- package/dist/src/runtime/lib/PostgreSQL.js +110 -0
- package/dist/src/runtime/lib/SDKBadWhereError.d.ts +4 -0
- package/dist/src/runtime/lib/SDKBadWhereError.js +10 -0
- package/dist/src/runtime/lib/SDKNotFoundError.d.ts +4 -0
- package/dist/src/runtime/lib/SDKNotFoundError.js +10 -0
- package/dist/src/runtime/lib/addNullFallbacks.d.ts +1 -0
- package/dist/src/runtime/lib/addNullFallbacks.js +32 -0
- package/dist/src/runtime/lib/addNullFallbacks.test.d.ts +1 -0
- package/dist/src/runtime/lib/addNullFallbacks.test.js +206 -0
- package/dist/src/runtime/lib/cursor.d.ts +2 -0
- package/dist/src/runtime/lib/cursor.js +10 -0
- package/dist/src/runtime/lib/getDateTimeStringMySQL.d.ts +1 -0
- package/dist/src/runtime/lib/getDateTimeStringMySQL.js +7 -0
- package/dist/src/runtime/lib/getOrderBy.d.ts +5 -0
- package/dist/src/runtime/lib/getOrderBy.js +52 -0
- package/dist/src/runtime/lib/getSqlAst.d.ts +2 -0
- package/dist/src/runtime/lib/getSqlAst.js +245 -0
- package/dist/src/runtime/lib/getWhere.d.ts +2 -0
- package/dist/src/runtime/lib/getWhere.js +20 -0
- package/dist/src/runtime/lib/shared.d.ts +13 -0
- package/dist/src/runtime/lib/shared.js +1118 -0
- package/dist/src/runtime/lib/stringifyWhere.d.ts +18 -0
- package/dist/src/runtime/lib/stringifyWhere.js +257 -0
- package/dist/src/runtime/lib/stringifyWhere.test.d.ts +1 -0
- package/dist/src/runtime/lib/stringifyWhere.test.js +245 -0
- package/dist/src/runtime/lib/utility.d.ts +5 -0
- package/dist/src/runtime/lib/utility.js +14 -0
- package/dist/src/traverseFieldArgs.d.ts +2 -0
- package/dist/src/traverseFieldArgs.js +17 -0
- package/dist/src/traverseFieldArgs.test.d.ts +1 -0
- package/dist/src/traverseFieldArgs.test.js +56 -0
- package/dist/test/addWhereValidTrue.d.ts +1 -0
- package/dist/test/addWhereValidTrue.js +39 -0
- package/dist/test/globalSetup.d.ts +13 -0
- package/dist/test/globalSetup.js +436 -0
- package/dist/test/postgres/__generated__/sdk-ts/artifacts.d.ts +8425 -0
- package/dist/test/postgres/__generated__/sdk-ts/artifacts.js +10469 -0
- package/dist/test/postgres/__generated__/sdk-ts/index.js +12162 -0
- package/package.json +5 -1
|
@@ -0,0 +1,2349 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.generate = generate;
|
|
30
|
+
const path = __importStar(require("node:path"));
|
|
31
|
+
const fs = __importStar(require("node:fs"));
|
|
32
|
+
const os = __importStar(require("node:os"));
|
|
33
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
34
|
+
const child_process = __importStar(require("node:child_process"));
|
|
35
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
36
|
+
const prettier = __importStar(require("prettier"));
|
|
37
|
+
const changeCase = __importStar(require("change-case"));
|
|
38
|
+
const fse = __importStar(require("fs-extra"));
|
|
39
|
+
const _ = __importStar(require("lodash/fp"));
|
|
40
|
+
const memoize_1 = __importDefault(require("lodash/memoize"));
|
|
41
|
+
const json_schema_to_typescript_1 = require("json-schema-to-typescript");
|
|
42
|
+
const getDuplicates_1 = require("../lib/getDuplicates");
|
|
43
|
+
const isNotNullOrUndefined_1 = require("../lib/isNotNullOrUndefined");
|
|
44
|
+
const pg_1 = require("pg");
|
|
45
|
+
const MySQL_1 = require("../runtime/lib/MySQL");
|
|
46
|
+
const capitalizeFirstLetter_1 = require("../lib/capitalizeFirstLetter");
|
|
47
|
+
const ctxStorage = new node_async_hooks_1.AsyncLocalStorage();
|
|
48
|
+
function getCtx() {
|
|
49
|
+
const c = ctxStorage.getStore();
|
|
50
|
+
if (!c)
|
|
51
|
+
throw new Error("generate() context missing");
|
|
52
|
+
return c;
|
|
53
|
+
}
|
|
54
|
+
const json2TsOpts = {
|
|
55
|
+
bannerComment: ""
|
|
56
|
+
};
|
|
57
|
+
const keyFields = "$fields";
|
|
58
|
+
async function generate(input) {
|
|
59
|
+
if (input.tables != null && input.excludeTables != null) {
|
|
60
|
+
throw new Error("Must specify either `tables` or `excludeTables`, not both");
|
|
61
|
+
}
|
|
62
|
+
if (input.dialect == null) {
|
|
63
|
+
throw new Error("Must specify `dialect`");
|
|
64
|
+
}
|
|
65
|
+
const specialCaseUuidColumn = input.specialCaseUuidColumn ?? true;
|
|
66
|
+
const includeMappedFields = input.includeMappedFields ?? true;
|
|
67
|
+
const supplementClientOpts = input.supplementClientOpts ?? true;
|
|
68
|
+
const ctx = {
|
|
69
|
+
runId: node_crypto_1.default.randomUUID(),
|
|
70
|
+
dialect: input.dialect,
|
|
71
|
+
query: undefined
|
|
72
|
+
};
|
|
73
|
+
return ctxStorage.run(ctx, async () => {
|
|
74
|
+
init(input);
|
|
75
|
+
let tables = await getTableNames();
|
|
76
|
+
if (tables.length === 0) {
|
|
77
|
+
throw new Error("No tables found");
|
|
78
|
+
}
|
|
79
|
+
if (input.tables != null) {
|
|
80
|
+
tables = tables.filter((x) => input.tables?.includes(x));
|
|
81
|
+
}
|
|
82
|
+
if (input.excludeTables != null) {
|
|
83
|
+
tables = tables.filter((x) => !input.excludeTables?.includes(x));
|
|
84
|
+
}
|
|
85
|
+
const data = await Promise.all(tables.flatMap((x) => [
|
|
86
|
+
getGetOneData(x, includeMappedFields),
|
|
87
|
+
getGetListData(x),
|
|
88
|
+
getGetListPaginatedData(x),
|
|
89
|
+
getPostOneData(x, specialCaseUuidColumn, includeMappedFields),
|
|
90
|
+
getPatchOneData(x, specialCaseUuidColumn, includeMappedFields),
|
|
91
|
+
getPatchListData(x),
|
|
92
|
+
getDeleteOneData(x),
|
|
93
|
+
getDeleteListData(x)
|
|
94
|
+
]));
|
|
95
|
+
const artifacts = await getArtifacts(tables, includeMappedFields, specialCaseUuidColumn);
|
|
96
|
+
const artifactsSource = getArtifactsSource(artifacts);
|
|
97
|
+
const sdkSource = await getSDKSource(data, specialCaseUuidColumn, supplementClientOpts, artifacts, input.outputSqliteSchema);
|
|
98
|
+
const sdkFilename = "index.ts";
|
|
99
|
+
const sourceIRuntimeFilePath = fs.existsSync(path.join(__dirname, "../runtime", "IRuntime.ts"))
|
|
100
|
+
? path.join(__dirname, "../runtime", "IRuntime.ts")
|
|
101
|
+
: path.join(__dirname, "../runtime", "IRuntime.js");
|
|
102
|
+
const IRuntimeFilename = path.basename(sourceIRuntimeFilePath);
|
|
103
|
+
const artifactsFilename = "artifacts.ts";
|
|
104
|
+
const tsConfigJSON = {
|
|
105
|
+
compilerOptions: {
|
|
106
|
+
module: "commonjs",
|
|
107
|
+
moduleResolution: "node",
|
|
108
|
+
target: "es2020",
|
|
109
|
+
declaration: true,
|
|
110
|
+
outDir: "./sdk-ts"
|
|
111
|
+
},
|
|
112
|
+
include: [sdkFilename, artifactsFilename, IRuntimeFilename]
|
|
113
|
+
};
|
|
114
|
+
const packageJSON = {
|
|
115
|
+
name: "temp",
|
|
116
|
+
version: "1.0.0",
|
|
117
|
+
// Deps need to be included so that they're inlined by ncc
|
|
118
|
+
dependencies: require("../../package.json").dependencies,
|
|
119
|
+
devDependencies: {
|
|
120
|
+
"@types/node": require("../../package.json").devDependencies["@types/node"],
|
|
121
|
+
typescript: require("../../package.json").devDependencies.typescript
|
|
122
|
+
},
|
|
123
|
+
// Not `resolutions` because npm used for install
|
|
124
|
+
overrides: {
|
|
125
|
+
// Fix for: `Cannot find type definition file for 'glob'`
|
|
126
|
+
glob: ">9.0.0"
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const tmpDirPath = path.join(os.tmpdir(),
|
|
130
|
+
// _ because - in filename is not supported by mysql2sqlite
|
|
131
|
+
`dsg_${node_crypto_1.default.randomUUID()}`.replace(/-/g, "_"));
|
|
132
|
+
fse.mkdirpSync(tmpDirPath);
|
|
133
|
+
fs.writeFileSync(path.join(tmpDirPath, sdkFilename), sdkSource);
|
|
134
|
+
fs.writeFileSync(path.join(tmpDirPath, artifactsFilename), artifactsSource);
|
|
135
|
+
fse.copyFileSync(sourceIRuntimeFilePath, path.join(tmpDirPath, IRuntimeFilename));
|
|
136
|
+
const typesDirPath = path.join(tmpDirPath, "types");
|
|
137
|
+
fse.mkdirpSync(typesDirPath);
|
|
138
|
+
fs.writeFileSync(path.join(typesDirPath, "_shared.ts"), getTypeShared());
|
|
139
|
+
for (let x of data) {
|
|
140
|
+
if (x.kind === "getOne") {
|
|
141
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeFieldsName + ".ts"), x.typeFields);
|
|
142
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeReturnBaseName + ".ts"), x.typeReturnBase);
|
|
143
|
+
}
|
|
144
|
+
if (x.kind === "getList") {
|
|
145
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeWhereName + ".ts"), x.typeWhere);
|
|
146
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeOrderByName + ".ts"), x.typeOrderBy);
|
|
147
|
+
}
|
|
148
|
+
if (x.kind === "postOne") {
|
|
149
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeDataName + ".ts"), x.typeData);
|
|
150
|
+
}
|
|
151
|
+
if (x.kind === "patchOne") {
|
|
152
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeDataName + ".ts"), x.typeData);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
fs.writeFileSync(path.join(typesDirPath, "index.ts"), getTypeTypesIndex(data));
|
|
156
|
+
fs.writeFileSync(path.join(tmpDirPath, "package.json"), JSON.stringify(packageJSON, null, 2));
|
|
157
|
+
fs.writeFileSync(path.join(tmpDirPath, "tsconfig.json"), JSON.stringify(tsConfigJSON, null, 2));
|
|
158
|
+
fse.copySync(__dirname, path.join(tmpDirPath, "src"));
|
|
159
|
+
const tmpBuildOutputPath = path.join(tmpDirPath, "sdk-ts");
|
|
160
|
+
const outdir = path.resolve(input.outdir);
|
|
161
|
+
const sdkOutputPath = path.join(outdir, "sdk-ts");
|
|
162
|
+
const nccVersion = "^0.33.0";
|
|
163
|
+
child_process.execSync("npm i", { cwd: tmpDirPath, stdio: "inherit" });
|
|
164
|
+
child_process.execSync(`npm_config_yes=true npx -p @vercel/ncc@${nccVersion} ncc build ./${sdkFilename} -o ${tmpBuildOutputPath} -e ./artifacts`, { cwd: tmpDirPath, stdio: "inherit" });
|
|
165
|
+
// TODO: workaround for artifacts.js not being output by ncc
|
|
166
|
+
fs.writeFileSync(path.join(tmpBuildOutputPath, "artifacts.js"), artifactsSource
|
|
167
|
+
.replace("export const artifacts: IArtifacts = ", "module.exports.artifacts = ")
|
|
168
|
+
.split("\n")
|
|
169
|
+
// Remove import
|
|
170
|
+
.slice(2)
|
|
171
|
+
.join("\n"));
|
|
172
|
+
// TODO: workaround for IRuntime.d.ts not being included
|
|
173
|
+
// copyFileSync hangs for some reason, so use writeFileSync + readFileSync instead
|
|
174
|
+
fs.writeFileSync(path.join(tmpBuildOutputPath, "IRuntime.d.ts"), fs.existsSync(path.join(__dirname, "../runtime", "IRuntime.d.ts"))
|
|
175
|
+
? fs.readFileSync(path.join(__dirname, "../runtime", "IRuntime.d.ts"), "utf-8")
|
|
176
|
+
: fs.readFileSync(sourceIRuntimeFilePath, "utf-8"));
|
|
177
|
+
if (getCtx().dialect === "mysql" && input.outputSqliteSchema) {
|
|
178
|
+
// Since mysql2sqlite outputs a malformed string if a column
|
|
179
|
+
// has the name `enum`, temporarily change the name to something else,
|
|
180
|
+
// then change it back.
|
|
181
|
+
const enumMarker = "`" + node_crypto_1.default.randomUUID() + "`";
|
|
182
|
+
const schemaMySql = Object.values(artifacts)
|
|
183
|
+
.reduce((acc, x) => {
|
|
184
|
+
let d = x.dump?.schema;
|
|
185
|
+
if (!d) {
|
|
186
|
+
return acc;
|
|
187
|
+
}
|
|
188
|
+
d = d.replace(/`enum`/g, enumMarker);
|
|
189
|
+
d += ";";
|
|
190
|
+
acc.push(d);
|
|
191
|
+
return acc;
|
|
192
|
+
}, [])
|
|
193
|
+
.join("\n\n");
|
|
194
|
+
const mysql2SqliteSrc = getMysql2sqliteSrc();
|
|
195
|
+
const mysql2SqlitePath = path.join(tmpDirPath, "mysql2sqlite");
|
|
196
|
+
fs.writeFileSync(mysql2SqlitePath, mysql2SqliteSrc);
|
|
197
|
+
fs.chmodSync(mysql2SqlitePath, 0o755);
|
|
198
|
+
const tmpMySqlSchemaFilename = "tmp.sql";
|
|
199
|
+
const tmpMySqlSchemaPath = path.join(tmpDirPath, tmpMySqlSchemaFilename);
|
|
200
|
+
fs.writeFileSync(tmpMySqlSchemaPath, schemaMySql);
|
|
201
|
+
let schemaSqlite = child_process
|
|
202
|
+
.execFileSync(mysql2SqlitePath, [tmpMySqlSchemaFilename], { cwd: tmpDirPath })
|
|
203
|
+
.toString();
|
|
204
|
+
schemaSqlite = schemaSqlite.replace(new RegExp(enumMarker, "g"), "`enum`");
|
|
205
|
+
const src = prettier.format(`module.exports = { schema: \`${schemaSqlite.replace(/`/g, "\\`")}\` }`, { parser: "babel" });
|
|
206
|
+
fs.writeFileSync(path.join(tmpBuildOutputPath, "artifacts.sqlite.js"), src);
|
|
207
|
+
}
|
|
208
|
+
if (!fs.existsSync(outdir)) {
|
|
209
|
+
fse.mkdirpSync(outdir);
|
|
210
|
+
}
|
|
211
|
+
fse.emptyDirSync(sdkOutputPath);
|
|
212
|
+
fse.copySync(tmpBuildOutputPath, sdkOutputPath);
|
|
213
|
+
fse.removeSync(tmpDirPath);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function init(input) {
|
|
217
|
+
const ctx = getCtx();
|
|
218
|
+
const { database, user, password, host, port, server } = input;
|
|
219
|
+
if (ctx.dialect === "mysql") {
|
|
220
|
+
const mysql = new MySQL_1.MySQL({
|
|
221
|
+
user,
|
|
222
|
+
password,
|
|
223
|
+
host,
|
|
224
|
+
port,
|
|
225
|
+
database
|
|
226
|
+
});
|
|
227
|
+
ctx.query = mysql.query.bind(mysql);
|
|
228
|
+
}
|
|
229
|
+
if (ctx.dialect === "postgresql") {
|
|
230
|
+
const pool = new pg_1.Pool({
|
|
231
|
+
host: host ?? "localhost",
|
|
232
|
+
port: port ?? 5432,
|
|
233
|
+
user,
|
|
234
|
+
password,
|
|
235
|
+
database
|
|
236
|
+
});
|
|
237
|
+
ctx.query = (q, values) => pool.query(q, values ?? []).then((r) => r.rows);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// It's a bit awkward to put __whereNeedsProcessing, __prepareWhere on the class,
|
|
241
|
+
// but it allows us to share the same database pool, clientOpts, etc.
|
|
242
|
+
async function getSDKSource(input, specialCaseUuidColumn, supplementClientOpts, artifacts, outputSqliteSchema) {
|
|
243
|
+
function getTypeImports() {
|
|
244
|
+
let set = new Set();
|
|
245
|
+
for (let d of input) {
|
|
246
|
+
for (let k of [
|
|
247
|
+
"typeFieldsName",
|
|
248
|
+
"typeReturnBaseName",
|
|
249
|
+
"typeWhereName",
|
|
250
|
+
"typeOrderByName",
|
|
251
|
+
"typeDataName"
|
|
252
|
+
]) {
|
|
253
|
+
const str = d[k];
|
|
254
|
+
if (str) {
|
|
255
|
+
set.add(str);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return `import type { ${["Paginate", "ListPaginated"]
|
|
260
|
+
.concat(Array.from(set).sort())
|
|
261
|
+
.join(",\n")} } from "./types";`;
|
|
262
|
+
}
|
|
263
|
+
const src = `import { AsyncLocalStorage } from "node:async_hooks";
|
|
264
|
+
import type { IRuntime, TMiddleware, TContext, TOnHandler, EventOnHandlerError } from "./IRuntime"
|
|
265
|
+
import { artifacts } from "./artifacts";
|
|
266
|
+
|
|
267
|
+
${getTypeImports()}
|
|
268
|
+
|
|
269
|
+
const asyncLocalStorage = new AsyncLocalStorage<{ isInOnHandler: boolean }>();
|
|
270
|
+
|
|
271
|
+
export class SDK {
|
|
272
|
+
runtime: IRuntime;
|
|
273
|
+
onHandlerMap: Map<string, TOnHandler>;
|
|
274
|
+
eventTarget: EventTarget;
|
|
275
|
+
passBeforeValueToAfterCallback: boolean;
|
|
276
|
+
|
|
277
|
+
constructor(opts: {
|
|
278
|
+
runtime: any;
|
|
279
|
+
clientOpts: { [k: string]: any; },
|
|
280
|
+
otherOpts?: { [k: string]: any; },
|
|
281
|
+
passBeforeValueToAfterCallback: boolean,
|
|
282
|
+
}) {
|
|
283
|
+
let otherOpts = opts.otherOpts ?? {};
|
|
284
|
+
if (opts.clientOpts.filename === ":memory:") {
|
|
285
|
+
${outputSqliteSchema === true
|
|
286
|
+
? `otherOpts = { ...otherOpts, createTablesString: require("./artifacts.sqlite").schema }`
|
|
287
|
+
: `throw new Error("SQLite artifacts not available. Please regenerate the SDK with \`outputSqliteSchema: true\`.")`}
|
|
288
|
+
}
|
|
289
|
+
this.runtime = new opts.runtime(opts.clientOpts, ${supplementClientOpts === true
|
|
290
|
+
? "{ supplementClientOpts: true, ...otherOpts }"
|
|
291
|
+
: "otherOpts"}, artifacts);
|
|
292
|
+
this.onHandlerMap = new Map();
|
|
293
|
+
this.eventTarget = new EventTarget();
|
|
294
|
+
this.passBeforeValueToAfterCallback = opts.passBeforeValueToAfterCallback;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
$use(middleware: TMiddleware) {
|
|
298
|
+
return this.runtime.$use(middleware);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
$whereNeedsProcessing(where: any) {
|
|
302
|
+
return this.runtime.$whereNeedsProcessing(where);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async $prepareWhere(
|
|
306
|
+
table: string,
|
|
307
|
+
where: any
|
|
308
|
+
) {
|
|
309
|
+
return this.runtime.$prepareWhere(artifacts, table, where);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async $queryRaw(sql: string, values?: any[]) {
|
|
313
|
+
return this.runtime.$queryRaw(sql, values);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async $shutdown() {
|
|
317
|
+
return this.runtime.$shutdown();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async $startTransaction(input?: {
|
|
321
|
+
isolationLevel?:
|
|
322
|
+
| "READ UNCOMMITTED"
|
|
323
|
+
| "READ COMMITTED"
|
|
324
|
+
| "REPEATABLE READ"
|
|
325
|
+
| "SERIALIZABLE"
|
|
326
|
+
}) {
|
|
327
|
+
const { dbCall, queryRaw, commit, rollback } = await this.runtime.$startTransaction(input);
|
|
328
|
+
const runtime = this.runtime;
|
|
329
|
+
const eventTarget = this.eventTarget;
|
|
330
|
+
const onHandlerMap = this.onHandlerMap;
|
|
331
|
+
const passBeforeValueToAfterCallback = this.passBeforeValueToAfterCallback;
|
|
332
|
+
return {
|
|
333
|
+
$queryRaw: queryRaw,
|
|
334
|
+
$commit: commit,
|
|
335
|
+
$rollback: rollback,
|
|
336
|
+
${(await Promise.all(input.flatMap(async (x) => {
|
|
337
|
+
if (x.kind === "getOne") {
|
|
338
|
+
const findOnes = await getFindOnes(x, specialCaseUuidColumn);
|
|
339
|
+
return getMethodSourceGetOne(x, findOnes, true);
|
|
340
|
+
}
|
|
341
|
+
if (x.kind === "getList") {
|
|
342
|
+
return getMethodSourceGetList(x, true);
|
|
343
|
+
}
|
|
344
|
+
if (x.kind === "getListPaginated") {
|
|
345
|
+
return getMethodSourceGetListPaginated(x, true);
|
|
346
|
+
}
|
|
347
|
+
if (x.kind === "postOne") {
|
|
348
|
+
return getMethodSourcePostOne(x, specialCaseUuidColumn, true);
|
|
349
|
+
}
|
|
350
|
+
if (x.kind === "patchOne") {
|
|
351
|
+
const findOnes = await getFindOnes(x, specialCaseUuidColumn);
|
|
352
|
+
return getMethodSourcePatchOne(x, findOnes, true);
|
|
353
|
+
}
|
|
354
|
+
if (x.kind === "patchList") {
|
|
355
|
+
return getMethodSourcePatchList(x, true);
|
|
356
|
+
}
|
|
357
|
+
if (x.kind === "deleteOne") {
|
|
358
|
+
const findOnes = await getFindOnes(x, specialCaseUuidColumn);
|
|
359
|
+
return getMethodSourceDeleteOne(x, findOnes, true);
|
|
360
|
+
}
|
|
361
|
+
if (x.kind === "deleteList") {
|
|
362
|
+
return getMethodSourceDeleteList(x, true);
|
|
363
|
+
}
|
|
364
|
+
}))).join(",\n")}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
onOnHandlerError(cb: (event: EventOnHandlerError<unknown>) => void) {
|
|
369
|
+
this.eventTarget.addEventListener("error", cb);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
${(await Promise.all(input.flatMap(async (x) => {
|
|
373
|
+
if (x.kind === "getOne") {
|
|
374
|
+
const findOnes = await getFindOnes(x, specialCaseUuidColumn);
|
|
375
|
+
return getMethodSourceGetOne(x, findOnes, false);
|
|
376
|
+
}
|
|
377
|
+
if (x.kind === "getList") {
|
|
378
|
+
return getMethodSourceGetList(x, false);
|
|
379
|
+
}
|
|
380
|
+
if (x.kind === "getListPaginated") {
|
|
381
|
+
return getMethodSourceGetListPaginated(x, false);
|
|
382
|
+
}
|
|
383
|
+
if (x.kind === "postOne") {
|
|
384
|
+
return getMethodSourcePostOne(x, specialCaseUuidColumn, false);
|
|
385
|
+
}
|
|
386
|
+
if (x.kind === "patchOne") {
|
|
387
|
+
const findOnes = await getFindOnes(x, specialCaseUuidColumn);
|
|
388
|
+
return getMethodSourcePatchOne(x, findOnes, false);
|
|
389
|
+
}
|
|
390
|
+
if (x.kind === "patchList") {
|
|
391
|
+
return getMethodSourcePatchList(x, false);
|
|
392
|
+
}
|
|
393
|
+
if (x.kind === "deleteOne") {
|
|
394
|
+
const findOnes = await getFindOnes(x, specialCaseUuidColumn);
|
|
395
|
+
return getMethodSourceDeleteOne(x, findOnes, false);
|
|
396
|
+
}
|
|
397
|
+
if (x.kind === "deleteList") {
|
|
398
|
+
return getMethodSourceDeleteList(x, false);
|
|
399
|
+
}
|
|
400
|
+
}))).join("\n\n")}
|
|
401
|
+
|
|
402
|
+
${(await Promise.all(input.flatMap(async (x) => {
|
|
403
|
+
if (x.kind === "postOne") {
|
|
404
|
+
return getMethodSourceOnHandlerPostOne(x);
|
|
405
|
+
}
|
|
406
|
+
if (x.kind === "patchOne") {
|
|
407
|
+
const findOnes = await getFindOnes(x, specialCaseUuidColumn);
|
|
408
|
+
return getMethodSourceOnHandlerPatchOne(x, findOnes);
|
|
409
|
+
}
|
|
410
|
+
if (x.kind === "patchList") {
|
|
411
|
+
return getMethodSourceOnHandlerPatchList(x);
|
|
412
|
+
}
|
|
413
|
+
if (x.kind === "deleteOne") {
|
|
414
|
+
const findOnes = await getFindOnes(x, specialCaseUuidColumn);
|
|
415
|
+
return getMethodSourceOnHandlerDeleteOne(x, findOnes);
|
|
416
|
+
}
|
|
417
|
+
if (x.kind === "deleteList") {
|
|
418
|
+
return getMethodSourceOnHandlerDeleteList(x);
|
|
419
|
+
}
|
|
420
|
+
})))
|
|
421
|
+
.filter(Boolean)
|
|
422
|
+
.join("\n\n")}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
${await Promise.all(Object.entries(artifacts).map(async ([table, tableArtifacts]) => {
|
|
426
|
+
const jsonSchema = {
|
|
427
|
+
type: "object",
|
|
428
|
+
properties: tableArtifacts.fields.reduce((acc, x) => {
|
|
429
|
+
if (x.kind === "object") {
|
|
430
|
+
return acc;
|
|
431
|
+
}
|
|
432
|
+
if (x.kind === "scalar" && x.mapped) {
|
|
433
|
+
return acc;
|
|
434
|
+
}
|
|
435
|
+
acc[x.name] =
|
|
436
|
+
x.kind === "enum"
|
|
437
|
+
? { enum: x.values }
|
|
438
|
+
: { type: getJSONTypes(x.type, x.nullable) };
|
|
439
|
+
return acc;
|
|
440
|
+
}, {}),
|
|
441
|
+
required: tableArtifacts.fields
|
|
442
|
+
.filter((x) => x.kind === "scalar" || x.kind === "enum")
|
|
443
|
+
.map((x) => x.name),
|
|
444
|
+
additionalProperties: false
|
|
445
|
+
};
|
|
446
|
+
return (0, json_schema_to_typescript_1.compile)(jsonSchema, getTypeReturnName(table), json2TsOpts);
|
|
447
|
+
})).then((xs) => xs.join(";\n\n"))}
|
|
448
|
+
|
|
449
|
+
${Object.entries(artifacts)
|
|
450
|
+
.map(([table, tableArtifacts]) => {
|
|
451
|
+
// https://pkerschbaum.com/blog/how-prisma-adapts-result-types-based-on-the-actual-arguments-given
|
|
452
|
+
// https://github.com/pkerschbaum/how-prisma-adapts-result-types-based-on-the-actual-arguments-given_example/blob/main/prisma/lib/prisma-client/index.d.ts
|
|
453
|
+
return `export type ${getTypeGetPayloadName(table)}<S extends { ${keyFields}?: ${getTypeFieldsName(table)} } | boolean, U = keyof S> = S extends true ? ${getTypeReturnName(table)} : S extends { ${keyFields}?: ${getTypeFieldsName(table)}, [k: string]: any } ? "${keyFields}" extends U ? S["${keyFields}"] extends undefined ? ${getTypeReturnName(table)} : { [P in TrueKeys<S["${keyFields}"]>]: ${tableArtifacts.fields
|
|
454
|
+
.reduce((acc, x) => {
|
|
455
|
+
if (x.kind !== "object") {
|
|
456
|
+
return acc;
|
|
457
|
+
}
|
|
458
|
+
let str = `P extends "${x.name}" ? ${getTypeGetPayloadName(x.type)}<S["${keyFields}"][P]>`;
|
|
459
|
+
if (x.isList) {
|
|
460
|
+
str += "[]";
|
|
461
|
+
}
|
|
462
|
+
if (x.nullable) {
|
|
463
|
+
str += " | null";
|
|
464
|
+
}
|
|
465
|
+
str += " : ";
|
|
466
|
+
acc.push(str);
|
|
467
|
+
return acc;
|
|
468
|
+
}, [])
|
|
469
|
+
.join("")} ${tableArtifacts.fields
|
|
470
|
+
.reduce((acc, x) => {
|
|
471
|
+
if (x.kind === "scalar" && x.mapped) {
|
|
472
|
+
let type = x.type;
|
|
473
|
+
if (x.nullable) {
|
|
474
|
+
type += " | null";
|
|
475
|
+
}
|
|
476
|
+
const str = `P extends "${x.name}" ? ${type} : `;
|
|
477
|
+
acc.push(str);
|
|
478
|
+
}
|
|
479
|
+
return acc;
|
|
480
|
+
}, [])
|
|
481
|
+
.join("")} P extends keyof ${getTypeReturnName(table)} ? ${getTypeReturnName(table)}[P] : never } : ${getTypeReturnName(table)} : ${getTypeReturnName(table)}`;
|
|
482
|
+
})
|
|
483
|
+
.join(";\n\n")}
|
|
484
|
+
|
|
485
|
+
// Utils
|
|
486
|
+
|
|
487
|
+
type Pick2<T, K extends keyof T> = { [P in K]: T[P]; };
|
|
488
|
+
|
|
489
|
+
type RequiredKeys<T> = {
|
|
490
|
+
[K in keyof T]-?: {} extends Pick2<T, K> ? never : K;
|
|
491
|
+
}[keyof T];
|
|
492
|
+
|
|
493
|
+
type TruthyKeys<T> = {
|
|
494
|
+
[key in keyof T]: T[key] extends false | undefined | null ? never : key;
|
|
495
|
+
}[keyof T];
|
|
496
|
+
|
|
497
|
+
type TrueKeys<T> = TruthyKeys<Pick2<T, RequiredKeys<T>>>;
|
|
498
|
+
|
|
499
|
+
type SelectSubset<T, U> = {
|
|
500
|
+
[key in keyof T]: key extends keyof U ? T[key] : never;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
type HasSelect = { ${keyFields}: any; };
|
|
504
|
+
|
|
505
|
+
type CheckSelect<T, S, U> = T extends HasSelect ? U : S;
|
|
506
|
+
`;
|
|
507
|
+
return prettier.format(src, { parser: "typescript" });
|
|
508
|
+
}
|
|
509
|
+
async function getFindOnes(x, specialCaseUuidColumn) {
|
|
510
|
+
let findOnes = [];
|
|
511
|
+
const primaryColumn = await getPrimaryColumn(x.table);
|
|
512
|
+
const uniqueColumns = await getUniqueColumns(x.table, specialCaseUuidColumn);
|
|
513
|
+
findOnes = findOnes
|
|
514
|
+
.concat([primaryColumn])
|
|
515
|
+
.concat(uniqueColumns)
|
|
516
|
+
.map((x) => ({
|
|
517
|
+
...x,
|
|
518
|
+
type: x.type === "integer" ? "number" : x.type
|
|
519
|
+
}));
|
|
520
|
+
return findOnes;
|
|
521
|
+
}
|
|
522
|
+
function getMethodSourceGetOne(x, findOnes, isTransaction) {
|
|
523
|
+
const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, skipCache?: boolean, context?: TContext }`;
|
|
524
|
+
return `async ${x.methodName}<T extends ${param2}>(
|
|
525
|
+
param1: ${findOnes
|
|
526
|
+
.map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
|
|
527
|
+
.join(" | ")},
|
|
528
|
+
param2?: SelectSubset<T, ${param2}>
|
|
529
|
+
): Promise<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>> {
|
|
530
|
+
return ${isTransaction ? "runtime" : "this.runtime"}.resolve(
|
|
531
|
+
{
|
|
532
|
+
resource: "${x.table}",
|
|
533
|
+
action: "${mapKindToAction(x.kind)}",
|
|
534
|
+
args: { $where: param1 },
|
|
535
|
+
fields: param2?.$fields as any,
|
|
536
|
+
artifacts,
|
|
537
|
+
context: param2?.context,
|
|
538
|
+
skipCache: param2?.skipCache,
|
|
539
|
+
eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
|
|
540
|
+
${isTransaction ? "dbCall," : ""}
|
|
541
|
+
passBeforeValueToAfterCallback: ${isTransaction
|
|
542
|
+
? "passBeforeValueToAfterCallback"
|
|
543
|
+
: "this.passBeforeValueToAfterCallback"},
|
|
544
|
+
}
|
|
545
|
+
);
|
|
546
|
+
}`;
|
|
547
|
+
}
|
|
548
|
+
function getMethodSourceGetList(x, isTransaction) {
|
|
549
|
+
const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, skipCache?: boolean, context?: TContext }`;
|
|
550
|
+
return `async ${x.methodName}<T extends ${param2}>(
|
|
551
|
+
param1: { $where?: ${x.typeWhereName}, $orderBy?: ${x.typeOrderByName}, $limit?: number },
|
|
552
|
+
param2?: SelectSubset<T, ${param2}>
|
|
553
|
+
): Promise<Array<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>>> {
|
|
554
|
+
return ${isTransaction ? "runtime" : "this.runtime"}.resolve(
|
|
555
|
+
{
|
|
556
|
+
resource: "${x.table}",
|
|
557
|
+
action: "${mapKindToAction(x.kind)}",
|
|
558
|
+
args: param1,
|
|
559
|
+
fields: param2?.$fields as any,
|
|
560
|
+
artifacts,
|
|
561
|
+
context: param2?.context,
|
|
562
|
+
skipCache: param2?.skipCache,
|
|
563
|
+
eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
|
|
564
|
+
${isTransaction ? "dbCall," : ""}
|
|
565
|
+
passBeforeValueToAfterCallback: ${isTransaction
|
|
566
|
+
? "passBeforeValueToAfterCallback"
|
|
567
|
+
: "this.passBeforeValueToAfterCallback"},
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
}`;
|
|
571
|
+
}
|
|
572
|
+
function getMethodSourceGetListPaginated(x, isTransaction) {
|
|
573
|
+
const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, skipCache?: boolean, context?: TContext }`;
|
|
574
|
+
return `async ${x.methodName}<T extends ${param2}>(
|
|
575
|
+
param1: { $where?: ${x.typeWhereName}, $orderBy?: ${x.typeOrderByName}, $paginate: Paginate },
|
|
576
|
+
param2?: SelectSubset<T, ${param2}>
|
|
577
|
+
): Promise<ListPaginated<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>>> {
|
|
578
|
+
return ${isTransaction ? "runtime" : "this.runtime"}.resolve(
|
|
579
|
+
{
|
|
580
|
+
resource: "${x.table}",
|
|
581
|
+
action: "${mapKindToAction(x.kind)}",
|
|
582
|
+
args: param1,
|
|
583
|
+
fields: param2?.$fields as any,
|
|
584
|
+
artifacts,
|
|
585
|
+
context: param2?.context,
|
|
586
|
+
skipCache: param2?.skipCache,
|
|
587
|
+
eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
|
|
588
|
+
${isTransaction ? "dbCall," : ""}
|
|
589
|
+
passBeforeValueToAfterCallback: ${isTransaction
|
|
590
|
+
? "passBeforeValueToAfterCallback"
|
|
591
|
+
: "this.passBeforeValueToAfterCallback"},
|
|
592
|
+
}
|
|
593
|
+
);
|
|
594
|
+
}`;
|
|
595
|
+
}
|
|
596
|
+
function getMethodSourcePostOne(x, specialCaseUuidColumn, isTransaction) {
|
|
597
|
+
const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, context?: TContext }`;
|
|
598
|
+
return `async ${x.methodName}<T extends ${param2}>(
|
|
599
|
+
data: ${x.typeDataName},
|
|
600
|
+
param2?: SelectSubset<T, ${param2}>
|
|
601
|
+
): Promise<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>> {
|
|
602
|
+
return ${isTransaction ? "runtime" : "this.runtime"}.resolve({
|
|
603
|
+
resource: "${x.table}",
|
|
604
|
+
action: "${mapKindToAction(x.kind)}",
|
|
605
|
+
data,
|
|
606
|
+
artifacts,
|
|
607
|
+
fields: param2?.$fields as any,
|
|
608
|
+
context: {...param2?.context, specialCaseUuidColumn: ${JSON.stringify(specialCaseUuidColumn)}},
|
|
609
|
+
eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
|
|
610
|
+
onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
|
|
611
|
+
asyncLocalStorage,
|
|
612
|
+
sdk: this,
|
|
613
|
+
${isTransaction ? "dbCall," : ""}
|
|
614
|
+
passBeforeValueToAfterCallback: ${isTransaction
|
|
615
|
+
? "passBeforeValueToAfterCallback"
|
|
616
|
+
: "this.passBeforeValueToAfterCallback"},
|
|
617
|
+
});
|
|
618
|
+
}`;
|
|
619
|
+
}
|
|
620
|
+
function getMethodSourceOnHandlerPostOne(x) {
|
|
621
|
+
return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
|
|
622
|
+
(sdk: InstanceType<typeof SDK>, input: { data: ${x.typeDataName} },
|
|
623
|
+
output: Partial<${getTypeReturnName(x.table)}>,
|
|
624
|
+
context: TContext,
|
|
625
|
+
) => Promise<void>
|
|
626
|
+
): void {
|
|
627
|
+
this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
|
|
628
|
+
}`;
|
|
629
|
+
}
|
|
630
|
+
function getMethodSourcePatchOne(x, findOnes, isTransaction) {
|
|
631
|
+
const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, context?: TContext }`;
|
|
632
|
+
return `async ${x.methodName}<T extends ${param2}>(
|
|
633
|
+
param1: ${findOnes
|
|
634
|
+
.map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
|
|
635
|
+
.join(" | ")},
|
|
636
|
+
data: ${x.typeDataName},
|
|
637
|
+
param2?: SelectSubset<T, ${param2}>
|
|
638
|
+
): Promise<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>> {
|
|
639
|
+
return ${isTransaction ? "runtime" : "this.runtime"}.resolve({
|
|
640
|
+
resource: "${x.table}",
|
|
641
|
+
action: "${mapKindToAction(x.kind)}",
|
|
642
|
+
args: { $where: param1 },
|
|
643
|
+
data,
|
|
644
|
+
artifacts,
|
|
645
|
+
fields: param2?.$fields as any,
|
|
646
|
+
context: param2?.context,
|
|
647
|
+
eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
|
|
648
|
+
onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
|
|
649
|
+
asyncLocalStorage,
|
|
650
|
+
sdk: this,
|
|
651
|
+
${isTransaction ? "dbCall," : ""}
|
|
652
|
+
passBeforeValueToAfterCallback: ${isTransaction
|
|
653
|
+
? "passBeforeValueToAfterCallback"
|
|
654
|
+
: "this.passBeforeValueToAfterCallback"},
|
|
655
|
+
});
|
|
656
|
+
}`;
|
|
657
|
+
}
|
|
658
|
+
function getMethodSourceOnHandlerPatchOne(x, findOnes) {
|
|
659
|
+
return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
|
|
660
|
+
(sdk: InstanceType<typeof SDK>, input: { $where: ${findOnes
|
|
661
|
+
.map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
|
|
662
|
+
.join(" | ")}, data: ${x.typeDataName} },
|
|
663
|
+
output: Partial<${getTypeReturnName(x.table)}>,
|
|
664
|
+
before: ${getTypeReturnName(x.table)} | null,
|
|
665
|
+
context: TContext,
|
|
666
|
+
) => Promise<void>
|
|
667
|
+
): void {
|
|
668
|
+
this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
|
|
669
|
+
}`;
|
|
670
|
+
}
|
|
671
|
+
function getMethodSourcePatchList(x, isTransaction) {
|
|
672
|
+
const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, context?: TContext }`;
|
|
673
|
+
return `async ${x.methodName}<T extends ${param2}>(
|
|
674
|
+
param1: { $where?: ${x.typeWhereName}, $orderBy?: ${x.typeOrderByName} },
|
|
675
|
+
data: ${x.typeDataName},
|
|
676
|
+
param2?: SelectSubset<T, ${param2}>
|
|
677
|
+
): Promise<Array<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>>> {
|
|
678
|
+
return ${isTransaction ? "runtime" : "this.runtime"}.resolve({
|
|
679
|
+
resource: "${x.table}",
|
|
680
|
+
action: "${mapKindToAction(x.kind)}",
|
|
681
|
+
args: param1,
|
|
682
|
+
data,
|
|
683
|
+
artifacts,
|
|
684
|
+
fields: param2?.$fields as any,
|
|
685
|
+
context: param2?.context,
|
|
686
|
+
eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
|
|
687
|
+
onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
|
|
688
|
+
asyncLocalStorage,
|
|
689
|
+
sdk: this,
|
|
690
|
+
${isTransaction ? "dbCall," : ""}
|
|
691
|
+
passBeforeValueToAfterCallback: ${isTransaction
|
|
692
|
+
? "passBeforeValueToAfterCallback"
|
|
693
|
+
: "this.passBeforeValueToAfterCallback"},
|
|
694
|
+
});
|
|
695
|
+
}`;
|
|
696
|
+
}
|
|
697
|
+
function getMethodSourceOnHandlerPatchList(x) {
|
|
698
|
+
return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
|
|
699
|
+
(sdk: InstanceType<typeof SDK>, input: { $where?: ${x.typeWhereName}, $orderBy?: ${x.typeOrderByName}, data: ${x.typeDataName} },
|
|
700
|
+
output: Partial<${getTypeReturnName(x.table)}>[],
|
|
701
|
+
before: ${getTypeReturnName(x.table)}[] | null,
|
|
702
|
+
context: TContext,
|
|
703
|
+
) => Promise<void>
|
|
704
|
+
): void {
|
|
705
|
+
this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
|
|
706
|
+
}`;
|
|
707
|
+
}
|
|
708
|
+
function getMethodSourceDeleteOne(x, findOnes, isTransaction) {
|
|
709
|
+
return `async ${x.methodName}(
|
|
710
|
+
param1: ${findOnes
|
|
711
|
+
.map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
|
|
712
|
+
.join(" | ")},
|
|
713
|
+
param2?: { correlationId?: string, context?: TContext }
|
|
714
|
+
): Promise<void> {
|
|
715
|
+
await ${isTransaction ? "runtime" : "this.runtime"}.resolve({
|
|
716
|
+
resource: "${x.table}",
|
|
717
|
+
action: "${mapKindToAction(x.kind)}",
|
|
718
|
+
args: { $where: param1 },
|
|
719
|
+
artifacts,
|
|
720
|
+
context: param2?.context,
|
|
721
|
+
eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
|
|
722
|
+
onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
|
|
723
|
+
asyncLocalStorage,
|
|
724
|
+
sdk: this,
|
|
725
|
+
${isTransaction ? "dbCall," : ""}
|
|
726
|
+
passBeforeValueToAfterCallback: ${isTransaction
|
|
727
|
+
? "passBeforeValueToAfterCallback"
|
|
728
|
+
: "this.passBeforeValueToAfterCallback"},
|
|
729
|
+
});
|
|
730
|
+
}`;
|
|
731
|
+
}
|
|
732
|
+
function getMethodSourceOnHandlerDeleteOne(x, findOnes) {
|
|
733
|
+
return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
|
|
734
|
+
(sdk: InstanceType<typeof SDK>, input: { $where: ${findOnes
|
|
735
|
+
.map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
|
|
736
|
+
.join(" | ")}, },
|
|
737
|
+
output: void,
|
|
738
|
+
context: TContext,
|
|
739
|
+
) => Promise<void>
|
|
740
|
+
): void {
|
|
741
|
+
this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
|
|
742
|
+
}`;
|
|
743
|
+
}
|
|
744
|
+
function getMethodSourceDeleteList(x, isTransaction) {
|
|
745
|
+
return `async ${x.methodName}(
|
|
746
|
+
param1: { $where?: ${x.typeWhereName} },
|
|
747
|
+
param2?: { correlationId?: string, context?: TContext }
|
|
748
|
+
): Promise<void> {
|
|
749
|
+
await ${isTransaction ? "runtime" : "this.runtime"}.resolve({
|
|
750
|
+
resource: "${x.table}",
|
|
751
|
+
action: "${mapKindToAction(x.kind)}",
|
|
752
|
+
args: param1,
|
|
753
|
+
artifacts,
|
|
754
|
+
context: param2?.context,
|
|
755
|
+
eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
|
|
756
|
+
onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
|
|
757
|
+
asyncLocalStorage,
|
|
758
|
+
sdk: this,
|
|
759
|
+
${isTransaction ? "dbCall," : ""}
|
|
760
|
+
passBeforeValueToAfterCallback: ${isTransaction
|
|
761
|
+
? "passBeforeValueToAfterCallback"
|
|
762
|
+
: "this.passBeforeValueToAfterCallback"},
|
|
763
|
+
});
|
|
764
|
+
}`;
|
|
765
|
+
}
|
|
766
|
+
function getMethodSourceOnHandlerDeleteList(x) {
|
|
767
|
+
return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
|
|
768
|
+
(sdk: InstanceType<typeof SDK>, input: { $where?: ${x.typeWhereName} },
|
|
769
|
+
output: void,
|
|
770
|
+
before: ${getTypeReturnName(x.table)}[] | null,
|
|
771
|
+
context: TContext,
|
|
772
|
+
) => Promise<void>
|
|
773
|
+
): void {
|
|
774
|
+
this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
|
|
775
|
+
}`;
|
|
776
|
+
}
|
|
777
|
+
function mapKindToAction(kind) {
|
|
778
|
+
if (kind === "getOne") {
|
|
779
|
+
return "findUnique";
|
|
780
|
+
}
|
|
781
|
+
if (kind === "getList") {
|
|
782
|
+
return "findMany";
|
|
783
|
+
}
|
|
784
|
+
if (kind === "getListPaginated") {
|
|
785
|
+
return "findManyPaginated";
|
|
786
|
+
}
|
|
787
|
+
if (kind === "postOne") {
|
|
788
|
+
return "create";
|
|
789
|
+
}
|
|
790
|
+
if (kind === "patchOne") {
|
|
791
|
+
return "update";
|
|
792
|
+
}
|
|
793
|
+
if (kind === "patchList") {
|
|
794
|
+
return "updateMany";
|
|
795
|
+
}
|
|
796
|
+
if (kind === "deleteOne") {
|
|
797
|
+
return "delete";
|
|
798
|
+
}
|
|
799
|
+
if (kind === "deleteList") {
|
|
800
|
+
return "deleteMany";
|
|
801
|
+
}
|
|
802
|
+
throw new Error(`Unhandled kind: ${kind}`);
|
|
803
|
+
}
|
|
804
|
+
function getTypeGetPayloadName(table) {
|
|
805
|
+
return "GetPayload" + changeCase.pascalCase(table);
|
|
806
|
+
}
|
|
807
|
+
function getTypeReturnName(table) {
|
|
808
|
+
return changeCase.pascalCase(table);
|
|
809
|
+
}
|
|
810
|
+
function getTypeReturnBaseName(table) {
|
|
811
|
+
return "ReturnBase" + changeCase.pascalCase(table);
|
|
812
|
+
}
|
|
813
|
+
function getTypeFieldsName(table) {
|
|
814
|
+
return "Fields" + changeCase.pascalCase(table);
|
|
815
|
+
}
|
|
816
|
+
function getTypeWhereName(table) {
|
|
817
|
+
return "Where" + changeCase.pascalCase(table);
|
|
818
|
+
}
|
|
819
|
+
function getTypeOrderByName(table) {
|
|
820
|
+
return "OrderBy" + changeCase.pascalCase(table);
|
|
821
|
+
}
|
|
822
|
+
function getTypeDataPostName(table) {
|
|
823
|
+
return "DataPost" + changeCase.pascalCase(table);
|
|
824
|
+
}
|
|
825
|
+
async function getGetOneData(table, includeMappedFields) {
|
|
826
|
+
const typeFieldsName = getTypeFieldsName(table);
|
|
827
|
+
const typeReturnBaseName = getTypeReturnBaseName(table);
|
|
828
|
+
return {
|
|
829
|
+
kind: "getOne",
|
|
830
|
+
table,
|
|
831
|
+
methodName: "get" + changeCase.pascalCase(table),
|
|
832
|
+
typeFields: await getTypeFields(table, typeFieldsName, includeMappedFields),
|
|
833
|
+
typeFieldsName,
|
|
834
|
+
typeReturnBase: await getTypeReturnBase(table, typeReturnBaseName, includeMappedFields),
|
|
835
|
+
typeReturnBaseName
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
async function getGetListData(table) {
|
|
839
|
+
const typeFieldsName = getTypeFieldsName(table);
|
|
840
|
+
const typeReturnBaseName = getTypeReturnBaseName(table);
|
|
841
|
+
const typeWhereName = getTypeWhereName(table);
|
|
842
|
+
const typeWhere = await getTypeWhere(table, typeWhereName);
|
|
843
|
+
const typeOrderByName = getTypeOrderByName(table);
|
|
844
|
+
const typeOrderBy = await getTypeOrderBy(table, typeOrderByName);
|
|
845
|
+
return {
|
|
846
|
+
kind: "getList",
|
|
847
|
+
table,
|
|
848
|
+
methodName: "get" + changeCase.pascalCase(table) + "List",
|
|
849
|
+
typeFieldsName,
|
|
850
|
+
typeReturnBaseName,
|
|
851
|
+
typeWhere,
|
|
852
|
+
typeWhereName,
|
|
853
|
+
typeOrderBy,
|
|
854
|
+
typeOrderByName
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
async function getGetListPaginatedData(table) {
|
|
858
|
+
const typeFieldsName = getTypeFieldsName(table);
|
|
859
|
+
const typeReturnBaseName = getTypeReturnBaseName(table);
|
|
860
|
+
const typeWhereName = getTypeWhereName(table);
|
|
861
|
+
const typeOrderByName = getTypeOrderByName(table);
|
|
862
|
+
return {
|
|
863
|
+
kind: "getListPaginated",
|
|
864
|
+
table,
|
|
865
|
+
methodName: "get" + changeCase.pascalCase(table) + "ListPaginated",
|
|
866
|
+
typeFieldsName,
|
|
867
|
+
typeReturnBaseName,
|
|
868
|
+
typeWhereName,
|
|
869
|
+
typeOrderByName
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
async function getPostOneData(table, specialCaseUuidColumn, includeMappedFields) {
|
|
873
|
+
const typeFieldsName = getTypeFieldsName(table);
|
|
874
|
+
const typeReturnBaseName = getTypeReturnBaseName(table);
|
|
875
|
+
const typeDataName = getTypeDataPostName(table);
|
|
876
|
+
return {
|
|
877
|
+
kind: "postOne",
|
|
878
|
+
table,
|
|
879
|
+
methodName: "post" + changeCase.pascalCase(table),
|
|
880
|
+
typeFieldsName,
|
|
881
|
+
typeReturnBaseName,
|
|
882
|
+
typeData: await getTypeDataPost(table, typeDataName, specialCaseUuidColumn, includeMappedFields),
|
|
883
|
+
typeDataName
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
async function getPatchOneData(table, specialCaseUuidColumn, includeMappedFields) {
|
|
887
|
+
const typeFieldsName = getTypeFieldsName(table);
|
|
888
|
+
const typeReturnBaseName = getTypeReturnBaseName(table);
|
|
889
|
+
const typeDataName = "DataPatch" + changeCase.pascalCase(table);
|
|
890
|
+
return {
|
|
891
|
+
kind: "patchOne",
|
|
892
|
+
table,
|
|
893
|
+
methodName: "patch" + changeCase.pascalCase(table),
|
|
894
|
+
typeFieldsName,
|
|
895
|
+
typeReturnBaseName,
|
|
896
|
+
typeData: await getTypeDataPatch(table, typeDataName, specialCaseUuidColumn, includeMappedFields),
|
|
897
|
+
typeDataName
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
async function getPatchListData(table) {
|
|
901
|
+
const typeFieldsName = getTypeFieldsName(table);
|
|
902
|
+
const typeReturnBaseName = getTypeReturnBaseName(table);
|
|
903
|
+
const typeWhereName = getTypeWhereName(table);
|
|
904
|
+
const typeDataName = "DataPatch" + changeCase.pascalCase(table);
|
|
905
|
+
const typeOrderByName = getTypeOrderByName(table);
|
|
906
|
+
return {
|
|
907
|
+
kind: "patchList",
|
|
908
|
+
table,
|
|
909
|
+
methodName: "patch" + changeCase.pascalCase(table) + "List",
|
|
910
|
+
typeFieldsName,
|
|
911
|
+
typeReturnBaseName,
|
|
912
|
+
typeWhereName,
|
|
913
|
+
typeDataName,
|
|
914
|
+
typeOrderByName
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
function getDeleteOneData(table) {
|
|
918
|
+
return {
|
|
919
|
+
kind: "deleteOne",
|
|
920
|
+
table,
|
|
921
|
+
methodName: "delete" + changeCase.pascalCase(table)
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function getDeleteListData(table) {
|
|
925
|
+
const typeWhereName = getTypeWhereName(table);
|
|
926
|
+
return {
|
|
927
|
+
kind: "deleteList",
|
|
928
|
+
table,
|
|
929
|
+
methodName: "delete" + changeCase.pascalCase(table) + "List",
|
|
930
|
+
typeWhereName
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
async function getTypeWhere(table, name) {
|
|
934
|
+
const jsonSchemaWhere = await getJSONSchemaWhere(table);
|
|
935
|
+
return (0, json_schema_to_typescript_1.compile)(jsonSchemaWhere, name, json2TsOpts);
|
|
936
|
+
}
|
|
937
|
+
async function getTypeDataPost(table, name, specialCaseUuidColumn, includeMappedFields) {
|
|
938
|
+
const primaryColumn = await getPrimaryColumn(table);
|
|
939
|
+
const tableMeta = (await getTableMeta(table)).filter((x) => x.Field !== primaryColumn.name);
|
|
940
|
+
const nullable = tableMeta.reduce((acc, m) => ({
|
|
941
|
+
...acc,
|
|
942
|
+
[m.Field]: m.Null === "YES" ? true : false
|
|
943
|
+
}), {});
|
|
944
|
+
const hasDefault = tableMeta.reduce((acc, m) => ({
|
|
945
|
+
...acc,
|
|
946
|
+
[m.Field]: m.Default == null ? false : true
|
|
947
|
+
}), {});
|
|
948
|
+
let properties = getJSONSchemaObjProperties(tableMeta);
|
|
949
|
+
let notRequiredList = [];
|
|
950
|
+
const oneToManyRelations = (await getRelationInfo(table)).filter((x) => x.type === "one-to-many__many-to-one" && x.kind === "one-to-many");
|
|
951
|
+
const mappedFields = includeMappedFields ? await getMappedFields(table) : [];
|
|
952
|
+
let mappedFieldsMap = new Map();
|
|
953
|
+
if (includeMappedFields) {
|
|
954
|
+
properties = {
|
|
955
|
+
...properties,
|
|
956
|
+
...mappedFields.reduce((acc, v) => {
|
|
957
|
+
acc[v.as] = { type: getJSONTypes(v.type, v.nullable) };
|
|
958
|
+
return acc;
|
|
959
|
+
}, {})
|
|
960
|
+
};
|
|
961
|
+
notRequiredList = mappedFields.flatMap((x) => [x.as, x.foreignKey]);
|
|
962
|
+
for (let r of oneToManyRelations) {
|
|
963
|
+
mappedFieldsMap.set(r.table, await getMappedFields(r.table));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
const uuidColumn = await getUuidColumn(table);
|
|
967
|
+
const jsonSchema = {
|
|
968
|
+
type: "object",
|
|
969
|
+
properties: {
|
|
970
|
+
...properties,
|
|
971
|
+
...oneToManyRelations.reduce((acc, v) => {
|
|
972
|
+
let tsType = getTypeDataPostName(v.table);
|
|
973
|
+
const mappedFields = mappedFieldsMap.get(v.table);
|
|
974
|
+
if (includeMappedFields && mappedFields != null && mappedFields.length > 0) {
|
|
975
|
+
tsType = `Omit<${tsType}, ${mappedFields
|
|
976
|
+
.map((x) => JSON.stringify(x.as))
|
|
977
|
+
.join(" | ")}>`;
|
|
978
|
+
}
|
|
979
|
+
tsType = `{$create: ${tsType}[]}`;
|
|
980
|
+
acc[v.name] = { tsType };
|
|
981
|
+
return acc;
|
|
982
|
+
}, {})
|
|
983
|
+
},
|
|
984
|
+
additionalProperties: false,
|
|
985
|
+
required: Object.keys(properties)
|
|
986
|
+
.filter(
|
|
987
|
+
// `uuid` should not be required
|
|
988
|
+
(x) => !specialCaseUuidColumn || uuidColumn == null ? true : x !== uuidColumn.name)
|
|
989
|
+
.filter(
|
|
990
|
+
// Required if column is non-nullable and has no default.
|
|
991
|
+
(x) => !nullable[x] && !hasDefault[x])
|
|
992
|
+
// Instead of doing a union with all possible permutations of UUID and IDs,
|
|
993
|
+
// for simplicity, just make both not required for now.
|
|
994
|
+
.filter((x) => !notRequiredList.includes(x))
|
|
995
|
+
};
|
|
996
|
+
let type = await (0, json_schema_to_typescript_1.compile)(jsonSchema, name, json2TsOpts);
|
|
997
|
+
const imports = _.uniq(oneToManyRelations
|
|
998
|
+
.filter((r) => r.table !== table)
|
|
999
|
+
.map((x) => getTypeDataPostName(x.table)))
|
|
1000
|
+
.map((x) => `import type { ${x} } from "./${x}";`)
|
|
1001
|
+
.join("\n");
|
|
1002
|
+
if (imports) {
|
|
1003
|
+
type = imports + "\n\n" + type;
|
|
1004
|
+
}
|
|
1005
|
+
return type;
|
|
1006
|
+
}
|
|
1007
|
+
async function getTypeDataPatch(table, name, specialCaseUuidColumn, includeMappedFields) {
|
|
1008
|
+
const primaryColumn = await getPrimaryColumn(table);
|
|
1009
|
+
let tableMeta = (await getTableMeta(table)).filter((x) => x.Field !== primaryColumn.name);
|
|
1010
|
+
if (specialCaseUuidColumn) {
|
|
1011
|
+
const uuidColumn = await getUuidColumn(table);
|
|
1012
|
+
if (uuidColumn != null) {
|
|
1013
|
+
tableMeta = tableMeta.filter((x) => x.Field !== uuidColumn.name);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
let properties = getJSONSchemaObjProperties(tableMeta);
|
|
1017
|
+
let mustImportTUpdateOperationsString = false;
|
|
1018
|
+
let mustImportTUpdateOperationsNumber = false;
|
|
1019
|
+
for (let key in properties) {
|
|
1020
|
+
const type = unwrapJSONType(properties[key].type);
|
|
1021
|
+
if (type === "string") {
|
|
1022
|
+
properties[key] = {
|
|
1023
|
+
oneOf: [properties[key], { tsType: "TUpdateOperationsString" }]
|
|
1024
|
+
};
|
|
1025
|
+
mustImportTUpdateOperationsString = true;
|
|
1026
|
+
}
|
|
1027
|
+
else if (type === "number" || type === "integer") {
|
|
1028
|
+
properties[key] = {
|
|
1029
|
+
oneOf: [properties[key], { tsType: "TUpdateOperationsNumber" }]
|
|
1030
|
+
};
|
|
1031
|
+
mustImportTUpdateOperationsNumber = true;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (includeMappedFields) {
|
|
1035
|
+
const mappedFields = await getMappedFields(table);
|
|
1036
|
+
properties = {
|
|
1037
|
+
...properties,
|
|
1038
|
+
...mappedFields.reduce((acc, v) => {
|
|
1039
|
+
acc[v.as] = { type: getJSONTypes(v.type, v.nullable) };
|
|
1040
|
+
return acc;
|
|
1041
|
+
}, {})
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
const jsonSchema = {
|
|
1045
|
+
type: "object",
|
|
1046
|
+
properties,
|
|
1047
|
+
additionalProperties: false,
|
|
1048
|
+
required: []
|
|
1049
|
+
};
|
|
1050
|
+
let type = await (0, json_schema_to_typescript_1.compile)(jsonSchema, name, json2TsOpts);
|
|
1051
|
+
if (mustImportTUpdateOperationsString || mustImportTUpdateOperationsNumber) {
|
|
1052
|
+
const imports = [];
|
|
1053
|
+
if (mustImportTUpdateOperationsString) {
|
|
1054
|
+
imports.push("TUpdateOperationsString");
|
|
1055
|
+
}
|
|
1056
|
+
if (mustImportTUpdateOperationsNumber) {
|
|
1057
|
+
imports.push("TUpdateOperationsNumber");
|
|
1058
|
+
}
|
|
1059
|
+
const importsStr = imports
|
|
1060
|
+
.map((x) => `import type { ${x} } from "./_shared";`)
|
|
1061
|
+
.join("\n");
|
|
1062
|
+
type = importsStr + "\n\n" + type;
|
|
1063
|
+
}
|
|
1064
|
+
return type;
|
|
1065
|
+
}
|
|
1066
|
+
function unwrapJSONType(type) {
|
|
1067
|
+
if (Array.isArray(type)) {
|
|
1068
|
+
// Gets first non-null type
|
|
1069
|
+
return type.filter((x) => x !== "null")[0];
|
|
1070
|
+
}
|
|
1071
|
+
return type;
|
|
1072
|
+
}
|
|
1073
|
+
async function getMappedFields(table) {
|
|
1074
|
+
const relationsManyToOne = await getRelationsManyToOne(table).then((xs) => xs.filter((x) => x.foreignKey.endsWith("Id")));
|
|
1075
|
+
let out = [];
|
|
1076
|
+
for (let x of relationsManyToOne) {
|
|
1077
|
+
const tableMeta = await getTableMeta(x.referencedTable);
|
|
1078
|
+
const uuidColumn = tableMeta.find((x) => x.Field === "uuid");
|
|
1079
|
+
const idColumn = tableMeta.find((x) => x.Field === "id");
|
|
1080
|
+
if (uuidColumn == null) {
|
|
1081
|
+
throw new Error("No `uuid` column found for table " + x.referencedTable);
|
|
1082
|
+
}
|
|
1083
|
+
if (idColumn == null) {
|
|
1084
|
+
throw new Error("No `id` column found for table " + x.referencedTable);
|
|
1085
|
+
}
|
|
1086
|
+
out.push({
|
|
1087
|
+
...x,
|
|
1088
|
+
name: "uuid",
|
|
1089
|
+
// Replace `Id` with `Uuid`
|
|
1090
|
+
as: x.foreignKey.slice(0, -2) + "Uuid",
|
|
1091
|
+
type: getBaseJSONType(uuidColumn.Type, getCtx().dialect)
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
return out;
|
|
1095
|
+
}
|
|
1096
|
+
async function getJSONSchemaWhere(table) {
|
|
1097
|
+
const whereSchemaName = `_Where${changeCase.pascalCase(table)}`;
|
|
1098
|
+
const defWhere = {
|
|
1099
|
+
oneOf: [
|
|
1100
|
+
{
|
|
1101
|
+
type: "object",
|
|
1102
|
+
properties: Object.entries(getJSONSchemaObjProperties(await getTableMeta(table))).reduce((acc, [k, v]) => ({
|
|
1103
|
+
...acc,
|
|
1104
|
+
[k]: {
|
|
1105
|
+
oneOf: [
|
|
1106
|
+
v,
|
|
1107
|
+
{
|
|
1108
|
+
type: "object",
|
|
1109
|
+
properties: { $eq: v },
|
|
1110
|
+
additionalProperties: false
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
type: "object",
|
|
1114
|
+
properties: { $neq: v },
|
|
1115
|
+
additionalProperties: false
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
type: "object",
|
|
1119
|
+
properties: { $gt: v },
|
|
1120
|
+
additionalProperties: false
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
type: "object",
|
|
1124
|
+
properties: { $gte: v },
|
|
1125
|
+
additionalProperties: false
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
type: "object",
|
|
1129
|
+
properties: { $lt: v },
|
|
1130
|
+
additionalProperties: false
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
type: "object",
|
|
1134
|
+
properties: { $lte: v },
|
|
1135
|
+
additionalProperties: false
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
type: "object",
|
|
1139
|
+
properties: { $like: { type: "string", minLength: 1 } },
|
|
1140
|
+
additionalProperties: false
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
type: "object",
|
|
1144
|
+
properties: { $nlike: { type: "string", minLength: 1 } },
|
|
1145
|
+
additionalProperties: false
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
type: "object",
|
|
1149
|
+
properties: { $in: { type: "array", items: v } },
|
|
1150
|
+
additionalProperties: false
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
type: "object",
|
|
1154
|
+
properties: { $nin: { type: "array", items: v } },
|
|
1155
|
+
additionalProperties: false
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
type: "object",
|
|
1159
|
+
properties: {
|
|
1160
|
+
$btwn: {
|
|
1161
|
+
type: "array",
|
|
1162
|
+
items: v,
|
|
1163
|
+
minItems: 2,
|
|
1164
|
+
maxItems: 2,
|
|
1165
|
+
uniqueItems: true
|
|
1166
|
+
}
|
|
1167
|
+
},
|
|
1168
|
+
additionalProperties: false
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
type: "object",
|
|
1172
|
+
properties: {
|
|
1173
|
+
$nbtwn: {
|
|
1174
|
+
type: "array",
|
|
1175
|
+
items: v,
|
|
1176
|
+
minItems: 2,
|
|
1177
|
+
maxItems: 2,
|
|
1178
|
+
uniqueItems: true
|
|
1179
|
+
}
|
|
1180
|
+
},
|
|
1181
|
+
additionalProperties: false
|
|
1182
|
+
}
|
|
1183
|
+
]
|
|
1184
|
+
}
|
|
1185
|
+
}), {}),
|
|
1186
|
+
additionalProperties: false
|
|
1187
|
+
},
|
|
1188
|
+
{
|
|
1189
|
+
type: "object",
|
|
1190
|
+
properties: {
|
|
1191
|
+
$and: {
|
|
1192
|
+
type: "array",
|
|
1193
|
+
items: {
|
|
1194
|
+
$ref: `#/definitions/${whereSchemaName}`
|
|
1195
|
+
},
|
|
1196
|
+
// While it makes sense conceptually for $and to have
|
|
1197
|
+
// at least 2 items, in practice, $and could be
|
|
1198
|
+
// generated dynamically and could end up having
|
|
1199
|
+
// less than 2 items, so don't enforce minItems.
|
|
1200
|
+
// minItems: 2,
|
|
1201
|
+
additionalProperties: false
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
additionalProperties: false
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
type: "object",
|
|
1208
|
+
properties: {
|
|
1209
|
+
$or: {
|
|
1210
|
+
type: "array",
|
|
1211
|
+
items: {
|
|
1212
|
+
$ref: `#/definitions/${whereSchemaName}`
|
|
1213
|
+
},
|
|
1214
|
+
// While it makes sense conceptually for $and to have
|
|
1215
|
+
// at least 2 items, in practice, $and could be
|
|
1216
|
+
// generated dynamically and could end up having
|
|
1217
|
+
// less than 2 items, so don't enforce minItems.
|
|
1218
|
+
// minItems: 2,
|
|
1219
|
+
additionalProperties: false
|
|
1220
|
+
}
|
|
1221
|
+
},
|
|
1222
|
+
additionalProperties: false
|
|
1223
|
+
}
|
|
1224
|
+
]
|
|
1225
|
+
};
|
|
1226
|
+
return {
|
|
1227
|
+
definitions: { [whereSchemaName]: defWhere },
|
|
1228
|
+
anyOf: [
|
|
1229
|
+
{ $ref: `#/definitions/${whereSchemaName}` },
|
|
1230
|
+
...["$and", "$or"].map((x) => ({
|
|
1231
|
+
type: "object",
|
|
1232
|
+
properties: {
|
|
1233
|
+
[x]: {
|
|
1234
|
+
type: "array",
|
|
1235
|
+
items: { $ref: `#/definitions/${whereSchemaName}` },
|
|
1236
|
+
// While it makes sense conceptually for $and/$or to have
|
|
1237
|
+
// at least 2 items, in practice, they could be
|
|
1238
|
+
// generated dynamically and could end up having
|
|
1239
|
+
// less than 2 items, so don't enforce minItems.
|
|
1240
|
+
// minItems: 2,
|
|
1241
|
+
additionalProperties: false
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
additionalProperties: false
|
|
1245
|
+
}))
|
|
1246
|
+
]
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
async function getTypeOrderBy(table, name) {
|
|
1250
|
+
return (0, json_schema_to_typescript_1.compile)((await getJSONSchemaOrderBy(table, name)), name, json2TsOpts);
|
|
1251
|
+
}
|
|
1252
|
+
async function getJSONSchemaOrderBy(table, name) {
|
|
1253
|
+
const fieldNames = await getTableMeta(table).then((xs) => xs.map((x) => x.Field));
|
|
1254
|
+
const def = {
|
|
1255
|
+
oneOf: fieldNames.map((k) => ({
|
|
1256
|
+
type: "object",
|
|
1257
|
+
properties: { [k]: { enum: ["asc", "desc"] } },
|
|
1258
|
+
required: [k],
|
|
1259
|
+
additionalProperties: false
|
|
1260
|
+
}))
|
|
1261
|
+
};
|
|
1262
|
+
const defName = `_${name}`;
|
|
1263
|
+
const _schema = { $ref: `#/definitions/${defName}` };
|
|
1264
|
+
return {
|
|
1265
|
+
definitions: { [defName]: def },
|
|
1266
|
+
oneOf: [_schema, { type: "array", items: _schema }]
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
function getTypeTypesIndex(data) {
|
|
1270
|
+
function getExport(name) {
|
|
1271
|
+
return `export type { ${name} } from "./${name}";`;
|
|
1272
|
+
}
|
|
1273
|
+
let set = new Set();
|
|
1274
|
+
for (let d of data) {
|
|
1275
|
+
for (let k of [
|
|
1276
|
+
"typeFieldsName",
|
|
1277
|
+
"typeReturnBaseName",
|
|
1278
|
+
"typeWhereName",
|
|
1279
|
+
"typeOrderByName",
|
|
1280
|
+
"typeDataName"
|
|
1281
|
+
]) {
|
|
1282
|
+
const str = d[k];
|
|
1283
|
+
if (str) {
|
|
1284
|
+
set.add(str);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
let src = `export type { ${[
|
|
1289
|
+
"Paginate",
|
|
1290
|
+
"ListPaginated",
|
|
1291
|
+
"TUpdateOperationsString",
|
|
1292
|
+
"TUpdateOperationsNumber"
|
|
1293
|
+
].join(",")} } from "./_shared";\n\n`;
|
|
1294
|
+
let arr = Array.from(set).sort();
|
|
1295
|
+
for (let x of arr) {
|
|
1296
|
+
src += getExport(x);
|
|
1297
|
+
}
|
|
1298
|
+
return prettier.format(src, { parser: "typescript" });
|
|
1299
|
+
}
|
|
1300
|
+
function getTypeShared() {
|
|
1301
|
+
const src = `export type Paginate = {
|
|
1302
|
+
first: number;
|
|
1303
|
+
after?: string;
|
|
1304
|
+
before?: string;
|
|
1305
|
+
} | {
|
|
1306
|
+
last: number;
|
|
1307
|
+
after?: string;
|
|
1308
|
+
before?: string;
|
|
1309
|
+
} | {
|
|
1310
|
+
limit: number;
|
|
1311
|
+
offset?: number;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
export type ListPaginated<T> = {
|
|
1315
|
+
paginationInfo: {
|
|
1316
|
+
hasPreviousPage?: boolean,
|
|
1317
|
+
hasNextPage?: boolean,
|
|
1318
|
+
startCursor?: string,
|
|
1319
|
+
endCursor?: string,
|
|
1320
|
+
totalCount: number,
|
|
1321
|
+
},
|
|
1322
|
+
results: Array<T>,
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
export type TUpdateOperationsString = {$prepend: string} | {$append: string}
|
|
1326
|
+
|
|
1327
|
+
export type TUpdateOperationsNumber = {$increment: number} | {$decrement: number}
|
|
1328
|
+
`;
|
|
1329
|
+
return prettier.format(src, { parser: "typescript" });
|
|
1330
|
+
}
|
|
1331
|
+
async function getTypeFields(table, name, includeMappedFields) {
|
|
1332
|
+
const scalarKeys = Object.keys(getJSONSchemaObjProperties(await getTableMeta(table)));
|
|
1333
|
+
const relations = await getRelationInfo(table);
|
|
1334
|
+
const mappedFields = includeMappedFields ? await getMappedFields(table) : [];
|
|
1335
|
+
const keyWhere = "$where";
|
|
1336
|
+
const keyOrderBy = "$orderBy";
|
|
1337
|
+
let properties = {};
|
|
1338
|
+
for (let x of scalarKeys) {
|
|
1339
|
+
properties[x] = { type: "boolean" };
|
|
1340
|
+
}
|
|
1341
|
+
for (let x of mappedFields) {
|
|
1342
|
+
properties[x.as] = { type: "boolean" };
|
|
1343
|
+
}
|
|
1344
|
+
for (let x of relations) {
|
|
1345
|
+
const argsProperties = x.type === "many-to-many"
|
|
1346
|
+
? {
|
|
1347
|
+
[keyWhere]: {
|
|
1348
|
+
type: "object",
|
|
1349
|
+
properties: {
|
|
1350
|
+
[x.table]: { tsType: getTypeWhereName(x.table) },
|
|
1351
|
+
[x.junctionTable]: {
|
|
1352
|
+
tsType: getTypeWhereName(x.junctionTable)
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
additionalProperties: false
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
: { [keyWhere]: { tsType: getTypeWhereName(x.table) } };
|
|
1359
|
+
// $orderBy only makes sense for a list
|
|
1360
|
+
if (x.grabMany) {
|
|
1361
|
+
argsProperties[keyOrderBy] = {
|
|
1362
|
+
tsType: getTypeOrderByName(x.table)
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
argsProperties[keyFields] = { tsType: getTypeFieldsName(x.table) };
|
|
1366
|
+
const jsonSchemaArgs = {
|
|
1367
|
+
type: "object",
|
|
1368
|
+
properties: argsProperties,
|
|
1369
|
+
additionalProperties: false
|
|
1370
|
+
};
|
|
1371
|
+
properties[x.name] = { enum: [{ type: "boolean" }, jsonSchemaArgs] };
|
|
1372
|
+
}
|
|
1373
|
+
const jsonSchemaFields = {
|
|
1374
|
+
type: "object",
|
|
1375
|
+
additionalProperties: false,
|
|
1376
|
+
properties
|
|
1377
|
+
};
|
|
1378
|
+
let type = await (0, json_schema_to_typescript_1.compile)(jsonSchemaFields, name, json2TsOpts);
|
|
1379
|
+
const fieldImports = _.uniq(relations.filter((x) => x.table !== table).map((x) => getTypeFieldsName(x.table)));
|
|
1380
|
+
const whereImports = _.uniq(relations.flatMap((x) => x.type === "many-to-many"
|
|
1381
|
+
? [getTypeWhereName(x.table), getTypeWhereName(x.junctionTable)]
|
|
1382
|
+
: getTypeWhereName(x.table)));
|
|
1383
|
+
const orderByImports = _.uniq(relations
|
|
1384
|
+
// $orderBy only makes sense for a list
|
|
1385
|
+
.filter((x) => x.grabMany)
|
|
1386
|
+
.map((x) => getTypeOrderByName(x.table)));
|
|
1387
|
+
const imports = _.uniq(fieldImports.concat(whereImports).concat(orderByImports))
|
|
1388
|
+
.map((x) => `import type { ${x} } from "./${x}";`)
|
|
1389
|
+
.join("\n");
|
|
1390
|
+
type = imports + "\n\n" + type;
|
|
1391
|
+
return type;
|
|
1392
|
+
}
|
|
1393
|
+
async function getTypeReturnBase(table, name, includeMappedFields) {
|
|
1394
|
+
const tableMeta = await getTableMeta(table);
|
|
1395
|
+
const scalarProperties = getJSONSchemaObjProperties(tableMeta);
|
|
1396
|
+
const relations = await getRelationInfo(table);
|
|
1397
|
+
const mappedFields = includeMappedFields ? await getMappedFields(table) : [];
|
|
1398
|
+
const jsonSchemaReturn = {
|
|
1399
|
+
type: "object",
|
|
1400
|
+
properties: {
|
|
1401
|
+
...scalarProperties,
|
|
1402
|
+
...mappedFields.reduce((acc, x) => {
|
|
1403
|
+
acc[x.as] = { type: getJSONTypes(x.type, x.nullable) };
|
|
1404
|
+
return acc;
|
|
1405
|
+
}, {}),
|
|
1406
|
+
...relations.reduce((acc, x) => {
|
|
1407
|
+
const key = x.name;
|
|
1408
|
+
const typeName = getTypeReturnBaseName(x.table);
|
|
1409
|
+
const tsType = x.type === "one-to-many__many-to-one"
|
|
1410
|
+
? getMaybeNullableTsType(typeName, x.nullable)
|
|
1411
|
+
: typeName;
|
|
1412
|
+
if (x.grabMany) {
|
|
1413
|
+
acc[key] = { type: "array", items: { tsType } };
|
|
1414
|
+
}
|
|
1415
|
+
else {
|
|
1416
|
+
acc[key] = { tsType };
|
|
1417
|
+
}
|
|
1418
|
+
return acc;
|
|
1419
|
+
}, {})
|
|
1420
|
+
},
|
|
1421
|
+
// Because of aliases
|
|
1422
|
+
additionalProperties: true
|
|
1423
|
+
};
|
|
1424
|
+
let type = await (0, json_schema_to_typescript_1.compile)(jsonSchemaReturn, name, json2TsOpts);
|
|
1425
|
+
const imports = _.uniq(relations.map((x) => getTypeReturnBaseName(x.table)))
|
|
1426
|
+
.filter((x) => x !== name)
|
|
1427
|
+
.map((x) => `import type { ${x} } from "./${x}";`)
|
|
1428
|
+
.join("\n");
|
|
1429
|
+
type = imports + "\n\n" + type;
|
|
1430
|
+
return type;
|
|
1431
|
+
}
|
|
1432
|
+
function getMaybeNullableTsType(type, nullable) {
|
|
1433
|
+
if (nullable) {
|
|
1434
|
+
return `${type} | null`;
|
|
1435
|
+
}
|
|
1436
|
+
return type;
|
|
1437
|
+
}
|
|
1438
|
+
function getArtifactsSource(artifacts) {
|
|
1439
|
+
const src = `
|
|
1440
|
+
import type {IArtifacts} from "./IRuntime";
|
|
1441
|
+
|
|
1442
|
+
export const artifacts: IArtifacts = ${JSON.stringify(artifacts)};
|
|
1443
|
+
`;
|
|
1444
|
+
return prettier.format(src, { parser: "typescript" });
|
|
1445
|
+
}
|
|
1446
|
+
async function getArtifacts(tables, includeMappedFields, specialCaseUuidColumn) {
|
|
1447
|
+
const tableMetaList = await Promise.all(tables.map(async (table) => {
|
|
1448
|
+
const [tableMeta, primaryKey, dumpSchema] = await Promise.all([
|
|
1449
|
+
getTableMeta(table),
|
|
1450
|
+
getPrimaryColumn(table).then((x) => x.name),
|
|
1451
|
+
getShowCreateTable(table)
|
|
1452
|
+
]);
|
|
1453
|
+
const scalarFields = tableMeta.map((x) => x.Field);
|
|
1454
|
+
const relationInfo = await getRelationInfo(table);
|
|
1455
|
+
const relationFields = relationInfo.reduce((acc, x) => {
|
|
1456
|
+
if (x.type === "one-to-many__many-to-one") {
|
|
1457
|
+
acc[x.name] = {
|
|
1458
|
+
name: x.name,
|
|
1459
|
+
type: x.type,
|
|
1460
|
+
kind: x.kind,
|
|
1461
|
+
table: x.table,
|
|
1462
|
+
grabMany: x.grabMany,
|
|
1463
|
+
nullable: x.nullable,
|
|
1464
|
+
relation: x.relation
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
else {
|
|
1468
|
+
acc[x.name] = {
|
|
1469
|
+
name: x.name,
|
|
1470
|
+
type: x.type,
|
|
1471
|
+
table: x.table,
|
|
1472
|
+
junctionTable: x.junctionTable,
|
|
1473
|
+
grabMany: x.grabMany,
|
|
1474
|
+
relations: x.relations
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
return acc;
|
|
1478
|
+
}, {});
|
|
1479
|
+
const _mappedFields = includeMappedFields ? await getMappedFields(table) : [];
|
|
1480
|
+
const mappedFields = _mappedFields.length === 0
|
|
1481
|
+
? null
|
|
1482
|
+
: _mappedFields.reduce((acc, v) => {
|
|
1483
|
+
acc[v.as] = v;
|
|
1484
|
+
return acc;
|
|
1485
|
+
}, {});
|
|
1486
|
+
const dateTimeFields = Object.entries(getJSONSchemaObjProperties(tableMeta)).reduce((acc, x) => {
|
|
1487
|
+
const k = x[0];
|
|
1488
|
+
const v = x[1];
|
|
1489
|
+
if (v.format != null && ["date-time"].includes(v.format)) {
|
|
1490
|
+
acc[k] = true;
|
|
1491
|
+
}
|
|
1492
|
+
return acc;
|
|
1493
|
+
}, {});
|
|
1494
|
+
const uniqueFields = await getUniqueColumns(table, specialCaseUuidColumn);
|
|
1495
|
+
let fields = tableMeta.map((t) => {
|
|
1496
|
+
const nullable = t.Null === "YES";
|
|
1497
|
+
const isEnum = t.Type.startsWith("enum");
|
|
1498
|
+
if (isEnum) {
|
|
1499
|
+
const values = getPropertyEnum(t.Type);
|
|
1500
|
+
if (values && nullable) {
|
|
1501
|
+
values.push(null);
|
|
1502
|
+
}
|
|
1503
|
+
return {
|
|
1504
|
+
kind: "enum",
|
|
1505
|
+
values,
|
|
1506
|
+
name: t.Field,
|
|
1507
|
+
nullable
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
return {
|
|
1511
|
+
kind: "scalar",
|
|
1512
|
+
type: getBaseJSONType(t.Type, getCtx().dialect),
|
|
1513
|
+
name: t.Field,
|
|
1514
|
+
nullable,
|
|
1515
|
+
hasDefaultValue: !!t.Default
|
|
1516
|
+
};
|
|
1517
|
+
});
|
|
1518
|
+
for (let x of _mappedFields) {
|
|
1519
|
+
fields.push({
|
|
1520
|
+
kind: "scalar",
|
|
1521
|
+
type: x.type,
|
|
1522
|
+
name: x.as,
|
|
1523
|
+
nullable: x.nullable,
|
|
1524
|
+
// TODO
|
|
1525
|
+
hasDefaultValue: false,
|
|
1526
|
+
mapped: true
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
for (let x of relationInfo) {
|
|
1530
|
+
fields.push({
|
|
1531
|
+
kind: "object",
|
|
1532
|
+
type: x.table,
|
|
1533
|
+
name: x.name,
|
|
1534
|
+
isList: x.grabMany,
|
|
1535
|
+
nullable: x.type === "one-to-many__many-to-one" ? x.nullable : false
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
return {
|
|
1539
|
+
table,
|
|
1540
|
+
primaryKey,
|
|
1541
|
+
scalarFields,
|
|
1542
|
+
relationFields,
|
|
1543
|
+
mappedFields,
|
|
1544
|
+
uniqueFields,
|
|
1545
|
+
dateTimeFields,
|
|
1546
|
+
dateTimeFieldsCount: Object.keys(dateTimeFields).length,
|
|
1547
|
+
fields,
|
|
1548
|
+
dump: dumpSchema == null ? null : { schema: dumpSchema }
|
|
1549
|
+
};
|
|
1550
|
+
}));
|
|
1551
|
+
const artifacts = tableMetaList.reduce((acc, x) => {
|
|
1552
|
+
acc[x.table] = x;
|
|
1553
|
+
return acc;
|
|
1554
|
+
}, {});
|
|
1555
|
+
return artifacts;
|
|
1556
|
+
}
|
|
1557
|
+
const getRelationInfo = (0, memoize_1.default)(async function getRelationInfo(table) {
|
|
1558
|
+
const relationsManyToOne = await getRelationsManyToOne(table);
|
|
1559
|
+
const relationsOneToMany = await getRelationsOneToMany(table);
|
|
1560
|
+
let out = [];
|
|
1561
|
+
out = out.concat(relationsManyToOne.reduce((acc, x) => {
|
|
1562
|
+
if (!x.foreignKey.endsWith("Id")) {
|
|
1563
|
+
return acc;
|
|
1564
|
+
}
|
|
1565
|
+
const name = getRelationManyToOneFieldName(x);
|
|
1566
|
+
acc.push({
|
|
1567
|
+
type: "one-to-many__many-to-one",
|
|
1568
|
+
kind: "many-to-one",
|
|
1569
|
+
grabMany: false,
|
|
1570
|
+
table: x.referencedTable,
|
|
1571
|
+
name,
|
|
1572
|
+
relation: x,
|
|
1573
|
+
nullable: x.nullable
|
|
1574
|
+
});
|
|
1575
|
+
return acc;
|
|
1576
|
+
}, []));
|
|
1577
|
+
const relationsOneToManyDuplicates = (0, getDuplicates_1.getDuplicates)(relationsOneToMany.map((x) => x.referencedTable));
|
|
1578
|
+
out = out.concat(relationsOneToMany.reduce((acc, x) => {
|
|
1579
|
+
if (!x.referencedKey.endsWith("Id")) {
|
|
1580
|
+
return acc;
|
|
1581
|
+
}
|
|
1582
|
+
let name = changeCase.camelCase(x.referencedTable) + "List";
|
|
1583
|
+
if (relationsOneToManyDuplicates.includes(x.referencedTable)) {
|
|
1584
|
+
// Examples:
|
|
1585
|
+
// `User` would end up with:
|
|
1586
|
+
// * `Invite` + `fromUserId` = `inviteFromUserList`
|
|
1587
|
+
// * `Invite` + `toUserId` = `inviteToUserList`
|
|
1588
|
+
name =
|
|
1589
|
+
changeCase.camelCase(x.referencedTable) +
|
|
1590
|
+
changeCase.pascalCase(x.referencedKey.replace(new RegExp(x.foreignKey + "$", "i"), "")) +
|
|
1591
|
+
"List";
|
|
1592
|
+
}
|
|
1593
|
+
acc.push({
|
|
1594
|
+
type: "one-to-many__many-to-one",
|
|
1595
|
+
kind: "one-to-many",
|
|
1596
|
+
grabMany: true,
|
|
1597
|
+
table: x.referencedTable,
|
|
1598
|
+
name,
|
|
1599
|
+
relation: x,
|
|
1600
|
+
nullable: x.nullable
|
|
1601
|
+
});
|
|
1602
|
+
return acc;
|
|
1603
|
+
}, []));
|
|
1604
|
+
const relationsManyToMany = (await getJunctionTables()).reduce((acc, x) => {
|
|
1605
|
+
const dataForParentTable = x.relations.find((r) => r.referencedTable === table);
|
|
1606
|
+
if (dataForParentTable == null) {
|
|
1607
|
+
return acc;
|
|
1608
|
+
}
|
|
1609
|
+
const dataForChildTable = x.relations.find((r) => r.referencedTable !== dataForParentTable.referencedTable);
|
|
1610
|
+
if (dataForChildTable == null) {
|
|
1611
|
+
throw new Error("Null dataForChildTable for table: " + table);
|
|
1612
|
+
}
|
|
1613
|
+
acc.push({
|
|
1614
|
+
type: "many-to-many",
|
|
1615
|
+
table: dataForChildTable.referencedTable,
|
|
1616
|
+
junctionTable: x.table,
|
|
1617
|
+
name: changeCase.camelCase(dataForChildTable.referencedTable) + "List",
|
|
1618
|
+
// Ensure parent comes before child
|
|
1619
|
+
relations: [dataForParentTable, dataForChildTable],
|
|
1620
|
+
grabMany: true
|
|
1621
|
+
});
|
|
1622
|
+
return acc;
|
|
1623
|
+
}, []);
|
|
1624
|
+
out = out.concat(relationsManyToMany);
|
|
1625
|
+
out = _.sortBy((x) => x.table, out);
|
|
1626
|
+
return out;
|
|
1627
|
+
}, (table) => getCtx().runId + ":" + table);
|
|
1628
|
+
function getRelationManyToOneFieldName(x) {
|
|
1629
|
+
return changeCase.camelCase(x.foreignKey.replace(new RegExp(x.referencedKey + "$", "i"), ""));
|
|
1630
|
+
}
|
|
1631
|
+
// TODO: not sure if this logic is correct
|
|
1632
|
+
async function getJunctionTables() {
|
|
1633
|
+
const tables = await getTableNames();
|
|
1634
|
+
return (await Promise.all(tables.map(async (table) => {
|
|
1635
|
+
const relations = await getRelationsManyToOne(table);
|
|
1636
|
+
if (relations.length === 2 &&
|
|
1637
|
+
relations[0].referencedTable !== relations[1].referencedTable &&
|
|
1638
|
+
// e.g. junction of Foo, Bar must be FooBar or BarFoo
|
|
1639
|
+
[
|
|
1640
|
+
relations[0].referencedTable + relations[1].referencedTable,
|
|
1641
|
+
relations[1].referencedTable + relations[0].referencedTable
|
|
1642
|
+
].includes(table)) {
|
|
1643
|
+
return { table, relations };
|
|
1644
|
+
}
|
|
1645
|
+
return null;
|
|
1646
|
+
}))).filter(isNotNullOrUndefined_1.isNotNullOrUndefined);
|
|
1647
|
+
}
|
|
1648
|
+
// `from` relations
|
|
1649
|
+
// https://stackoverflow.com/a/54732547
|
|
1650
|
+
const getRelationsManyToOne = (0, memoize_1.default)(async function getRelationsManyToOne(table) {
|
|
1651
|
+
const { dialect, query } = getCtx();
|
|
1652
|
+
const tableMeta = await getTableMeta(table);
|
|
1653
|
+
let rs;
|
|
1654
|
+
if (dialect === "mysql") {
|
|
1655
|
+
const sql = `
|
|
1656
|
+
SELECT
|
|
1657
|
+
TABLE_SCHEMA as db,
|
|
1658
|
+
TABLE_NAME as t1,
|
|
1659
|
+
COLUMN_NAME as t1Field,
|
|
1660
|
+
REFERENCED_TABLE_SCHEMA as db2,
|
|
1661
|
+
REFERENCED_TABLE_NAME as t2,
|
|
1662
|
+
REFERENCED_COLUMN_NAME as t2Field
|
|
1663
|
+
FROM
|
|
1664
|
+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
1665
|
+
WHERE
|
|
1666
|
+
TABLE_SCHEMA = SCHEMA()
|
|
1667
|
+
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
1668
|
+
AND (TABLE_NAME = ?);
|
|
1669
|
+
`;
|
|
1670
|
+
rs = await query(sql, [table]);
|
|
1671
|
+
}
|
|
1672
|
+
else if (dialect === "postgresql") {
|
|
1673
|
+
rs = await query(`SELECT kcu.column_name AS "t1Field", ccu.table_name AS t2, ccu.column_name AS "t2Field"
|
|
1674
|
+
FROM information_schema.key_column_usage kcu
|
|
1675
|
+
JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name AND kcu.table_schema = rc.constraint_schema
|
|
1676
|
+
JOIN information_schema.constraint_column_usage ccu ON rc.unique_constraint_name = ccu.constraint_name AND rc.unique_constraint_schema = ccu.table_schema
|
|
1677
|
+
WHERE kcu.table_schema = 'public' AND kcu.table_name = $1`, [table]);
|
|
1678
|
+
}
|
|
1679
|
+
else {
|
|
1680
|
+
throw new Error("Unsupported dialect: " + dialect);
|
|
1681
|
+
}
|
|
1682
|
+
const xs = await Promise.all(_.uniqWith(_.isEqual, rs.map(async (v) => {
|
|
1683
|
+
return {
|
|
1684
|
+
table: table,
|
|
1685
|
+
foreignKey: v.t1Field,
|
|
1686
|
+
referencedTable: v.t2,
|
|
1687
|
+
referencedKey: v.t2Field,
|
|
1688
|
+
nullable: tableMeta.find((m) => m.Field === v.t1Field)?.Null === "YES"
|
|
1689
|
+
};
|
|
1690
|
+
})));
|
|
1691
|
+
return _.sortBy((x) => x.referencedTable, xs);
|
|
1692
|
+
}, (table) => getCtx().runId + ":" + table);
|
|
1693
|
+
// `to` relations
|
|
1694
|
+
const getRelationsOneToMany = (0, memoize_1.default)(async function getRelationsOneToMany(table) {
|
|
1695
|
+
const { dialect, query } = getCtx();
|
|
1696
|
+
let rs;
|
|
1697
|
+
if (dialect === "mysql") {
|
|
1698
|
+
const sql = `
|
|
1699
|
+
SELECT
|
|
1700
|
+
TABLE_SCHEMA as db,
|
|
1701
|
+
TABLE_NAME as t1,
|
|
1702
|
+
COLUMN_NAME as t1Field,
|
|
1703
|
+
REFERENCED_TABLE_SCHEMA as db2,
|
|
1704
|
+
REFERENCED_TABLE_NAME as t2,
|
|
1705
|
+
REFERENCED_COLUMN_NAME as t2Field
|
|
1706
|
+
FROM
|
|
1707
|
+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
1708
|
+
WHERE
|
|
1709
|
+
TABLE_SCHEMA = SCHEMA()
|
|
1710
|
+
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
1711
|
+
AND (REFERENCED_TABLE_NAME = ?);
|
|
1712
|
+
`;
|
|
1713
|
+
rs = await query(sql, [table]);
|
|
1714
|
+
}
|
|
1715
|
+
else if (dialect === "postgresql") {
|
|
1716
|
+
rs = await query(`SELECT kcu.table_name AS t1, kcu.column_name AS "t1Field", ccu.column_name AS "t2Field"
|
|
1717
|
+
FROM information_schema.key_column_usage kcu
|
|
1718
|
+
JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name AND kcu.table_schema = rc.constraint_schema
|
|
1719
|
+
JOIN information_schema.constraint_column_usage ccu ON rc.unique_constraint_name = ccu.constraint_name AND rc.unique_constraint_schema = ccu.table_schema
|
|
1720
|
+
WHERE kcu.table_schema = 'public' AND ccu.table_name = $1`, [table]);
|
|
1721
|
+
}
|
|
1722
|
+
else {
|
|
1723
|
+
throw new Error("Unsupported dialect: " + dialect);
|
|
1724
|
+
}
|
|
1725
|
+
const xs = await Promise.all(_.uniqWith(_.isEqual, rs.map(async (v) => {
|
|
1726
|
+
return {
|
|
1727
|
+
table: table,
|
|
1728
|
+
foreignKey: v.t2Field,
|
|
1729
|
+
referencedTable: v.t1,
|
|
1730
|
+
referencedKey: v.t1Field,
|
|
1731
|
+
nullable: false
|
|
1732
|
+
};
|
|
1733
|
+
})));
|
|
1734
|
+
return _.sortBy((x) => x.referencedKey, _.sortBy((x) => x.referencedTable, xs));
|
|
1735
|
+
}, (table) => getCtx().runId + ":" + table);
|
|
1736
|
+
async function getPrimaryColumn(table) {
|
|
1737
|
+
const tableMeta = await getTableMeta(table);
|
|
1738
|
+
const columns = tableMeta.filter((x) => x.Key === "PRI");
|
|
1739
|
+
if (columns.length !== 1) {
|
|
1740
|
+
throw new Error("Expected to find 1 primary key for table: " + table);
|
|
1741
|
+
}
|
|
1742
|
+
const column = columns[0];
|
|
1743
|
+
return {
|
|
1744
|
+
name: column.Field,
|
|
1745
|
+
type: getBaseJSONType(column.Type, getCtx().dialect),
|
|
1746
|
+
nullable: column.Null === "YES"
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
async function getUniqueColumns(table, specialCaseUuidColumn) {
|
|
1750
|
+
const tableMeta = await getTableMeta(table);
|
|
1751
|
+
return tableMeta
|
|
1752
|
+
.filter((x) => x.Key === "UNI" ||
|
|
1753
|
+
// Because with the _Base pattern, Key isn't UNI for some reason
|
|
1754
|
+
(specialCaseUuidColumn && x.Field === "uuid"))
|
|
1755
|
+
.map((x) => ({
|
|
1756
|
+
name: x.Field,
|
|
1757
|
+
type: getBaseJSONType(x.Type, getCtx().dialect),
|
|
1758
|
+
nullable: x.Null === "YES"
|
|
1759
|
+
}));
|
|
1760
|
+
}
|
|
1761
|
+
async function getUuidColumn(table) {
|
|
1762
|
+
const tableMeta = await getTableMeta(table);
|
|
1763
|
+
const column = tableMeta.find((x) => x.Field === "uuid");
|
|
1764
|
+
if (column == null) {
|
|
1765
|
+
return undefined;
|
|
1766
|
+
}
|
|
1767
|
+
return {
|
|
1768
|
+
name: column.Field,
|
|
1769
|
+
type: column.Type,
|
|
1770
|
+
nullable: column.Null === "YES"
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
const getPgEnumDefinition = (0, memoize_1.default)(async function getPgEnumDefinition(udtName) {
|
|
1774
|
+
const { dialect, query } = getCtx();
|
|
1775
|
+
if (dialect !== "postgresql")
|
|
1776
|
+
return null;
|
|
1777
|
+
const rows = await query(`SELECT e.enumlabel FROM pg_enum e
|
|
1778
|
+
JOIN pg_type t ON e.enumtypid = t.oid
|
|
1779
|
+
JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid
|
|
1780
|
+
WHERE t.typname = $1 AND n.nspname = 'public'
|
|
1781
|
+
ORDER BY e.enumsortorder`, [udtName]);
|
|
1782
|
+
if (rows.length === 0)
|
|
1783
|
+
return null;
|
|
1784
|
+
const labels = rows.map((r) => String(r.enumlabel).replace(/'/g, "''"));
|
|
1785
|
+
return "enum('" + labels.join("', '") + "')";
|
|
1786
|
+
}, (udtName) => getCtx().runId + ":" + udtName);
|
|
1787
|
+
const getTableMeta = (0, memoize_1.default)(async function getTableMeta(table) {
|
|
1788
|
+
const { dialect, query } = getCtx();
|
|
1789
|
+
if (dialect === "mysql") {
|
|
1790
|
+
return query("DESCRIBE ??", [table]).then((xs) => _.sortBy((x) => x.Field, xs));
|
|
1791
|
+
}
|
|
1792
|
+
if (dialect === "postgresql") {
|
|
1793
|
+
const columns = await query(`SELECT column_name AS "Field", data_type, udt_name, character_maximum_length AS char_max, is_nullable, column_default AS "Default"
|
|
1794
|
+
FROM information_schema.columns
|
|
1795
|
+
WHERE table_schema = 'public' AND table_name = $1
|
|
1796
|
+
ORDER BY ordinal_position`, [table]);
|
|
1797
|
+
const keyInfo = await query(`SELECT a.attname AS col, 'PRI' AS key_type
|
|
1798
|
+
FROM pg_index i
|
|
1799
|
+
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) AND NOT a.attisdropped AND a.attnum > 0
|
|
1800
|
+
JOIN pg_class c ON c.oid = i.indrelid
|
|
1801
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1802
|
+
WHERE n.nspname = 'public' AND c.relname = $1 AND i.indisprimary
|
|
1803
|
+
UNION ALL
|
|
1804
|
+
SELECT kcu.column_name AS col, 'UNI' AS key_type
|
|
1805
|
+
FROM information_schema.table_constraints tc
|
|
1806
|
+
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
|
1807
|
+
WHERE tc.table_schema = 'public' AND tc.table_name = $1 AND tc.constraint_type = 'UNIQUE'
|
|
1808
|
+
UNION ALL
|
|
1809
|
+
SELECT kcu.column_name AS col, 'MUL' AS key_type
|
|
1810
|
+
FROM information_schema.table_constraints tc
|
|
1811
|
+
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
|
1812
|
+
WHERE tc.table_schema = 'public' AND tc.table_name = $1 AND tc.constraint_type = 'FOREIGN KEY'`, [table]);
|
|
1813
|
+
const keyMap = new Map();
|
|
1814
|
+
for (const k of keyInfo) {
|
|
1815
|
+
if (!keyMap.has(k.col) || k.key_type === "PRI")
|
|
1816
|
+
keyMap.set(k.col, k.key_type);
|
|
1817
|
+
}
|
|
1818
|
+
const udtNames = [
|
|
1819
|
+
...new Set(columns
|
|
1820
|
+
.filter((c) => c.data_type === "USER-DEFINED" && c.udt_name != null)
|
|
1821
|
+
.map((c) => c.udt_name))
|
|
1822
|
+
];
|
|
1823
|
+
const enumDefs = await Promise.all(udtNames.map((udt) => getPgEnumDefinition(udt)));
|
|
1824
|
+
const enumMap = new Map(udtNames.map((udt, i) => [udt, enumDefs[i] ?? "varchar(255)"]));
|
|
1825
|
+
return columns.map((c) => {
|
|
1826
|
+
let type;
|
|
1827
|
+
if (c.data_type === "USER-DEFINED" && c.udt_name != null) {
|
|
1828
|
+
type = enumMap.get(c.udt_name) ?? "character varying(255)";
|
|
1829
|
+
}
|
|
1830
|
+
else {
|
|
1831
|
+
type = c.data_type;
|
|
1832
|
+
if ((c.data_type === "character varying" || c.data_type === "character") &&
|
|
1833
|
+
c.char_max != null) {
|
|
1834
|
+
type += "(" + c.char_max + ")";
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
return {
|
|
1838
|
+
Field: c.Field,
|
|
1839
|
+
Type: type,
|
|
1840
|
+
Null: c.is_nullable === "YES" ? "YES" : "NO",
|
|
1841
|
+
Key: keyMap.get(c.Field) ?? "",
|
|
1842
|
+
Default: c.Default ?? ""
|
|
1843
|
+
};
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
throw new Error("Unsupported dialect: " + dialect);
|
|
1847
|
+
}, (table) => getCtx().runId + ":" + table);
|
|
1848
|
+
async function getShowCreateTable(table) {
|
|
1849
|
+
const { dialect, query } = getCtx();
|
|
1850
|
+
if (dialect === "mysql") {
|
|
1851
|
+
return query("SHOW CREATE TABLE ??", [table]).then((xs) => xs[0]["Create Table"]
|
|
1852
|
+
// https://github.com/bradzacher/mysqldump/blob/66839a57e572a07c046b0ba98753f30a7026cbd8/src/getSchemaDump.ts#L65
|
|
1853
|
+
.replace(/AUTO_INCREMENT\s*=\s*\d+ /g, ""));
|
|
1854
|
+
}
|
|
1855
|
+
if (dialect === "postgresql") {
|
|
1856
|
+
const [tableMeta, relations] = await Promise.all([
|
|
1857
|
+
getTableMeta(table),
|
|
1858
|
+
getRelationsManyToOne(table)
|
|
1859
|
+
]);
|
|
1860
|
+
const refByFk = new Map(relations.map((r) => [r.foreignKey, r]));
|
|
1861
|
+
const columnDefs = tableMeta.map((c) => {
|
|
1862
|
+
let def = `"${c.Field.replace(/"/g, '""')}" ${c.Type} ${c.Null === "YES" ? "NULL" : "NOT NULL"}`;
|
|
1863
|
+
if (c.Default != null && c.Default !== "") {
|
|
1864
|
+
def += ` DEFAULT ${c.Default}`;
|
|
1865
|
+
}
|
|
1866
|
+
if (c.Key === "PRI")
|
|
1867
|
+
def += " PRIMARY KEY";
|
|
1868
|
+
if (c.Key === "UNI")
|
|
1869
|
+
def += " UNIQUE";
|
|
1870
|
+
const ref = refByFk.get(c.Field);
|
|
1871
|
+
if (ref != null) {
|
|
1872
|
+
def += ` REFERENCES "${ref.referencedTable.replace(/"/g, '""')}" ("${ref.referencedKey.replace(/"/g, '""')}")`;
|
|
1873
|
+
}
|
|
1874
|
+
return def;
|
|
1875
|
+
});
|
|
1876
|
+
return `CREATE TABLE "${table.replace(/"/g, '""')}" (\n ${columnDefs.join(",\n ")}\n)`;
|
|
1877
|
+
}
|
|
1878
|
+
return Promise.resolve(null);
|
|
1879
|
+
}
|
|
1880
|
+
function getJSONSchemaObjProperties(tableMeta) {
|
|
1881
|
+
return tableMeta.reduce((acc, m) => {
|
|
1882
|
+
const baseType = getBaseJSONType(m.Type, getCtx().dialect);
|
|
1883
|
+
const format = getPropertyFormat(m.Type);
|
|
1884
|
+
const nullable = m.Null === "YES";
|
|
1885
|
+
const isEnum = m.Type.startsWith("enum");
|
|
1886
|
+
const _enum = isEnum
|
|
1887
|
+
? getPropertyEnum(m.Type)
|
|
1888
|
+
: undefined;
|
|
1889
|
+
if (_enum && nullable) {
|
|
1890
|
+
_enum.push(null);
|
|
1891
|
+
}
|
|
1892
|
+
acc[m.Field] = {
|
|
1893
|
+
type: getJSONTypes(baseType, nullable)
|
|
1894
|
+
// maxLength:
|
|
1895
|
+
// baseType === "string" && format == null && isEnum == null
|
|
1896
|
+
// ? getPropertyMaxLength(m.Type)
|
|
1897
|
+
// : undefined,
|
|
1898
|
+
};
|
|
1899
|
+
if (_enum !== void 0) {
|
|
1900
|
+
acc[m.Field].enum = _enum;
|
|
1901
|
+
}
|
|
1902
|
+
if (format !== void 0) {
|
|
1903
|
+
acc[m.Field].format = format;
|
|
1904
|
+
}
|
|
1905
|
+
return acc;
|
|
1906
|
+
}, {});
|
|
1907
|
+
}
|
|
1908
|
+
function getJSONTypes(baseType, nullable) {
|
|
1909
|
+
if (nullable) {
|
|
1910
|
+
if (Array.isArray(baseType)) {
|
|
1911
|
+
return baseType.concat("null");
|
|
1912
|
+
}
|
|
1913
|
+
return [baseType, "null"];
|
|
1914
|
+
}
|
|
1915
|
+
return baseType;
|
|
1916
|
+
}
|
|
1917
|
+
// https://github.com/mysqljs/mysql#type-casting
|
|
1918
|
+
function getBaseJSONType(sqlType, dialect) {
|
|
1919
|
+
if (dialect === "postgresql") {
|
|
1920
|
+
if (sqlType === "boolean" || sqlType === "bool")
|
|
1921
|
+
return "boolean";
|
|
1922
|
+
if (["smallint", "int2", "integer", "int4", "bigint", "int8"].includes(sqlType)) {
|
|
1923
|
+
return "integer";
|
|
1924
|
+
}
|
|
1925
|
+
if (["real", "float4", "double precision", "float8"].includes(sqlType) ||
|
|
1926
|
+
sqlType.startsWith("numeric") ||
|
|
1927
|
+
sqlType.startsWith("decimal")) {
|
|
1928
|
+
return "number";
|
|
1929
|
+
}
|
|
1930
|
+
if (["text", "uuid", "json", "jsonb"].includes(sqlType) ||
|
|
1931
|
+
sqlType === "date" ||
|
|
1932
|
+
sqlType === "time" ||
|
|
1933
|
+
sqlType.startsWith("timestamp") ||
|
|
1934
|
+
sqlType.startsWith("character varying") ||
|
|
1935
|
+
sqlType.startsWith("character") ||
|
|
1936
|
+
sqlType.startsWith("enum")) {
|
|
1937
|
+
return "string";
|
|
1938
|
+
}
|
|
1939
|
+
throw new Error("Unable to map to JSON type: " + sqlType);
|
|
1940
|
+
}
|
|
1941
|
+
if (
|
|
1942
|
+
// TODO?
|
|
1943
|
+
sqlType === "tinyint(1)" ||
|
|
1944
|
+
sqlType === "boolean" ||
|
|
1945
|
+
sqlType === "bool" ||
|
|
1946
|
+
sqlType === "bit") {
|
|
1947
|
+
return "boolean";
|
|
1948
|
+
}
|
|
1949
|
+
// TODO?
|
|
1950
|
+
if (sqlType === "timestamp" ||
|
|
1951
|
+
sqlType === "datetime" ||
|
|
1952
|
+
sqlType === "datetime2" ||
|
|
1953
|
+
sqlType === "date") {
|
|
1954
|
+
return "string";
|
|
1955
|
+
}
|
|
1956
|
+
if (sqlType.startsWith("char") ||
|
|
1957
|
+
sqlType.startsWith("varchar") ||
|
|
1958
|
+
sqlType.startsWith("tinytext") ||
|
|
1959
|
+
sqlType.startsWith("mediumtext") ||
|
|
1960
|
+
sqlType.startsWith("longtext") ||
|
|
1961
|
+
sqlType.startsWith("text") ||
|
|
1962
|
+
sqlType.startsWith("enum") ||
|
|
1963
|
+
sqlType.startsWith("set") ||
|
|
1964
|
+
sqlType.startsWith("time") ||
|
|
1965
|
+
sqlType.startsWith("geometry") ||
|
|
1966
|
+
// TODO?
|
|
1967
|
+
// https://github.com/mysqljs/mysql#buffer
|
|
1968
|
+
sqlType.startsWith("tinyblob") ||
|
|
1969
|
+
sqlType.startsWith("mediumblob") ||
|
|
1970
|
+
sqlType.startsWith("longblob") ||
|
|
1971
|
+
sqlType.startsWith("blob") ||
|
|
1972
|
+
sqlType.startsWith("binary") ||
|
|
1973
|
+
sqlType.startsWith("varbinary") ||
|
|
1974
|
+
sqlType === "uniqueidentifier"
|
|
1975
|
+
// sqlType.startsWith("bit")
|
|
1976
|
+
) {
|
|
1977
|
+
return "string";
|
|
1978
|
+
}
|
|
1979
|
+
if (sqlType.startsWith("tinyint") ||
|
|
1980
|
+
sqlType.startsWith("smallint") ||
|
|
1981
|
+
sqlType.startsWith("mediumint") ||
|
|
1982
|
+
sqlType.startsWith("int") ||
|
|
1983
|
+
sqlType.startsWith("year")) {
|
|
1984
|
+
return "integer";
|
|
1985
|
+
}
|
|
1986
|
+
if (sqlType.startsWith("bigint")) {
|
|
1987
|
+
// TODO: will be string if `bigNumberStrings` is used
|
|
1988
|
+
// https://github.com/mysqljs/mysql#connection-options
|
|
1989
|
+
return "integer";
|
|
1990
|
+
}
|
|
1991
|
+
if (sqlType.startsWith("float") || sqlType.startsWith("double")) {
|
|
1992
|
+
return "number";
|
|
1993
|
+
}
|
|
1994
|
+
if (sqlType.startsWith("decimal")) {
|
|
1995
|
+
// TODO: will be string if `bigNumberStrings` is used
|
|
1996
|
+
// https://github.com/mysqljs/mysql#connection-options
|
|
1997
|
+
return "number";
|
|
1998
|
+
}
|
|
1999
|
+
if (sqlType === "json" || sqlType === "nvarchar") {
|
|
2000
|
+
// TODO
|
|
2001
|
+
return "string";
|
|
2002
|
+
// return ["array", "object"];
|
|
2003
|
+
}
|
|
2004
|
+
throw new Error("Unable to map to JSON type: " + sqlType);
|
|
2005
|
+
}
|
|
2006
|
+
// function getPropertyMaxLength(sqlType: string) {
|
|
2007
|
+
// const a = sqlType.includes("(");
|
|
2008
|
+
// if (a) {
|
|
2009
|
+
// const tmp = sqlType.split("(");
|
|
2010
|
+
// const b = tmp[tmp.length - 1];
|
|
2011
|
+
// return parseInt(b.slice(0, b.length - 1));
|
|
2012
|
+
// }
|
|
2013
|
+
// return undefined;
|
|
2014
|
+
// }
|
|
2015
|
+
function getPropertyEnum(sqlType) {
|
|
2016
|
+
const a = sqlType.split("enum(").filter(Boolean)[0];
|
|
2017
|
+
const b = a.slice(0, a.length - 1);
|
|
2018
|
+
const c = b
|
|
2019
|
+
.split(",")
|
|
2020
|
+
.map((x) => x.trim())
|
|
2021
|
+
.map((x) => x.replace(/['"]/g, ""));
|
|
2022
|
+
return c;
|
|
2023
|
+
}
|
|
2024
|
+
function getPropertyFormat(sqlType) {
|
|
2025
|
+
if (sqlType === "datetime" ||
|
|
2026
|
+
sqlType === "datetime2" ||
|
|
2027
|
+
sqlType === "timestamp" ||
|
|
2028
|
+
sqlType.startsWith("timestamp")) {
|
|
2029
|
+
// TODO: not sure this is correct for `timestamp`
|
|
2030
|
+
return "date-time";
|
|
2031
|
+
}
|
|
2032
|
+
if (sqlType === "date") {
|
|
2033
|
+
// https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats
|
|
2034
|
+
// https://github.com/epoberezkin/ajv#formats
|
|
2035
|
+
// `date` isn't a built-in format, but it's supported by `Ajv`.
|
|
2036
|
+
return "date";
|
|
2037
|
+
}
|
|
2038
|
+
if (sqlType === "time") {
|
|
2039
|
+
// https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats
|
|
2040
|
+
// https://github.com/epoberezkin/ajv#formats
|
|
2041
|
+
// `time` isn't a built-in format, but it's supported by `Ajv`.
|
|
2042
|
+
return "time";
|
|
2043
|
+
}
|
|
2044
|
+
return undefined;
|
|
2045
|
+
}
|
|
2046
|
+
async function getTableNames() {
|
|
2047
|
+
const { dialect, query } = getCtx();
|
|
2048
|
+
if (dialect === "mysql") {
|
|
2049
|
+
return query("SHOW TABLES").then((xs) => xs.flatMap((x) => Object.values(x)).sort());
|
|
2050
|
+
}
|
|
2051
|
+
if (dialect === "postgresql") {
|
|
2052
|
+
return query(`SELECT table_name FROM information_schema.tables
|
|
2053
|
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
|
|
2054
|
+
ORDER BY table_name`).then((rows) => rows.map((r) => r.table_name));
|
|
2055
|
+
}
|
|
2056
|
+
throw new Error("Unsupported dialect: " + dialect);
|
|
2057
|
+
}
|
|
2058
|
+
function getMysql2sqliteSrc() {
|
|
2059
|
+
return `#!/usr/bin/awk -f
|
|
2060
|
+
|
|
2061
|
+
# Authors: @esperlu, @artemyk, @gkuenning, @dumblob
|
|
2062
|
+
|
|
2063
|
+
# FIXME detect empty input file and issue a warning
|
|
2064
|
+
|
|
2065
|
+
function printerr( s ){ print s | "cat >&2" }
|
|
2066
|
+
|
|
2067
|
+
BEGIN {
|
|
2068
|
+
if( ARGC != 2 ){
|
|
2069
|
+
printerr( \\
|
|
2070
|
+
"USAGE:\\n"\\
|
|
2071
|
+
" mysql2sqlite dump_mysql.sql > dump_sqlite3.sql\\n" \\
|
|
2072
|
+
" OR\\n" \\
|
|
2073
|
+
" mysql2sqlite dump_mysql.sql | sqlite3 sqlite.db\\n" \\
|
|
2074
|
+
"\\n" \\
|
|
2075
|
+
"NOTES:\\n" \\
|
|
2076
|
+
" Dash in filename is not supported, because dash (-) means stdin." )
|
|
2077
|
+
no_END = 1
|
|
2078
|
+
exit 1
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
# Find INT_MAX supported by both this AWK (usually an ISO C signed int)
|
|
2082
|
+
# and SQlite.
|
|
2083
|
+
# On non-8bit-based architectures, the additional bits are safely ignored.
|
|
2084
|
+
|
|
2085
|
+
# 8bit (lower precision should not exist)
|
|
2086
|
+
s="127"
|
|
2087
|
+
# "63" + 0 avoids potential parser misbehavior
|
|
2088
|
+
if( (s + 0) "" == s ){ INT_MAX_HALF = "63" + 0 }
|
|
2089
|
+
# 16bit
|
|
2090
|
+
s="32767"
|
|
2091
|
+
if( (s + 0) "" == s ){ INT_MAX_HALF = "16383" + 0 }
|
|
2092
|
+
# 32bit
|
|
2093
|
+
s="2147483647"
|
|
2094
|
+
if( (s + 0) "" == s ){ INT_MAX_HALF = "1073741823" + 0 }
|
|
2095
|
+
# 64bit (as INTEGER in SQlite3)
|
|
2096
|
+
s="9223372036854775807"
|
|
2097
|
+
if( (s + 0) "" == s ){ INT_MAX_HALF = "4611686018427387904" + 0 }
|
|
2098
|
+
# # 128bit
|
|
2099
|
+
# s="170141183460469231731687303715884105728"
|
|
2100
|
+
# if( (s + 0) "" == s ){ INT_MAX_HALF = "85070591730234615865843651857942052864" + 0 }
|
|
2101
|
+
# # 256bit
|
|
2102
|
+
# s="57896044618658097711785492504343953926634992332820282019728792003956564819968"
|
|
2103
|
+
# if( (s + 0) "" == s ){ INT_MAX_HALF = "28948022309329048855892746252171976963317496166410141009864396001978282409984" + 0 }
|
|
2104
|
+
# # 512bit
|
|
2105
|
+
# s="6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048"
|
|
2106
|
+
# if( (s + 0) "" == s ){ INT_MAX_HALF = "3351951982485649274893506249551461531869841455148098344430890360930441007518386744200468574541725856922507964546621512713438470702986642486608412251521024" + 0 }
|
|
2107
|
+
# # 1024bit
|
|
2108
|
+
# s="89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119973622969239858152417678164812112068608"
|
|
2109
|
+
# if( (s + 0) "" == s ){ INT_MAX_HALF = "44942328371557897693232629769725618340449424473557664318357520289433168951375240783177119330601884005280028469967848339414697442203604155623211857659868531094441973356216371319075554900311523529863270738021251442209537670585615720368478277635206809290837627671146574559986811484619929076208839082406056034304" + 0 }
|
|
2110
|
+
# # higher precision probably not needed
|
|
2111
|
+
|
|
2112
|
+
FS=",$"
|
|
2113
|
+
print "PRAGMA synchronous = OFF;"
|
|
2114
|
+
print "PRAGMA journal_mode = MEMORY;"
|
|
2115
|
+
print "BEGIN TRANSACTION;"
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
# historically 3 spaces separate non-argument local variables
|
|
2119
|
+
function bit_to_int( str_bit, powtwo, i, res, bit, overflow ){
|
|
2120
|
+
powtwo = 1
|
|
2121
|
+
overflow = 0
|
|
2122
|
+
# 011101 = 1*2^0 + 0*2^1 + 1*2^2 ...
|
|
2123
|
+
for( i = length( str_bit ); i > 0; --i ){
|
|
2124
|
+
bit = substr( str_bit, i, 1 )
|
|
2125
|
+
if( overflow || ( bit == 1 && res > INT_MAX_HALF ) ){
|
|
2126
|
+
printerr( \\
|
|
2127
|
+
NR ": WARN Bit field overflow, number truncated (LSBs saved, MSBs ignored)." )
|
|
2128
|
+
break
|
|
2129
|
+
}
|
|
2130
|
+
res = res + bit * powtwo
|
|
2131
|
+
# no warning here as it might be the last iteration
|
|
2132
|
+
if( powtwo > INT_MAX_HALF ){ overflow = 1; continue }
|
|
2133
|
+
powtwo = powtwo * 2
|
|
2134
|
+
}
|
|
2135
|
+
return res
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
# CREATE TRIGGER statements have funny commenting. Remember we are in trigger.
|
|
2139
|
+
/^\\/\\*.*(CREATE.*TRIGGER|create.*trigger)/ {
|
|
2140
|
+
gsub( /^.*(TRIGGER|trigger)/, "CREATE TRIGGER" )
|
|
2141
|
+
print
|
|
2142
|
+
inTrigger = 1
|
|
2143
|
+
next
|
|
2144
|
+
}
|
|
2145
|
+
# The end of CREATE TRIGGER has a stray comment terminator
|
|
2146
|
+
/(END|end) \\*\\/;;/ { gsub( /\\*\\//, "" ); print; inTrigger = 0; next }
|
|
2147
|
+
# The rest of triggers just get passed through
|
|
2148
|
+
inTrigger != 0 { print; next }
|
|
2149
|
+
|
|
2150
|
+
# CREATE VIEW looks like a TABLE in comments
|
|
2151
|
+
/^\\/\\*.*(CREATE.*TABLE|create.*table)/ {
|
|
2152
|
+
inView = 1
|
|
2153
|
+
next
|
|
2154
|
+
}
|
|
2155
|
+
# end of CREATE VIEW
|
|
2156
|
+
/^(\\).*(ENGINE|engine).*\\*\\/;)/ {
|
|
2157
|
+
inView = 0
|
|
2158
|
+
next
|
|
2159
|
+
}
|
|
2160
|
+
# content of CREATE VIEW
|
|
2161
|
+
inView != 0 { next }
|
|
2162
|
+
|
|
2163
|
+
# skip comments
|
|
2164
|
+
/^\\/\\*/ { next }
|
|
2165
|
+
|
|
2166
|
+
# skip PARTITION statements
|
|
2167
|
+
/^ *[(]?(PARTITION|partition) +[^ ]+/ { next }
|
|
2168
|
+
|
|
2169
|
+
# print all INSERT lines
|
|
2170
|
+
( /^ *\\(/ && /\\) *[,;] *$/ ) || /^(INSERT|insert|REPLACE|replace)/ {
|
|
2171
|
+
prev = ""
|
|
2172
|
+
|
|
2173
|
+
# first replace \\\\ by \\_ that mysqldump never generates to deal with
|
|
2174
|
+
# sequnces like \\\\n that should be translated into \\n, not \\<LF>.
|
|
2175
|
+
# After we convert all escapes we replace \\_ by backslashes.
|
|
2176
|
+
gsub( /\\\\\\\\/, "\\\\_" )
|
|
2177
|
+
|
|
2178
|
+
# single quotes are escaped by another single quote
|
|
2179
|
+
gsub( /\\\\'/, "''" )
|
|
2180
|
+
gsub( /\\\\n/, "\\n" )
|
|
2181
|
+
gsub( /\\\\r/, "\\r" )
|
|
2182
|
+
gsub( /\\\\"/, "\\"" )
|
|
2183
|
+
gsub( /\\\\\\032/, "\\032" ) # substitute char
|
|
2184
|
+
|
|
2185
|
+
gsub( /\\\\_/, "\\\\" )
|
|
2186
|
+
|
|
2187
|
+
# sqlite3 is limited to 16 significant digits of precision
|
|
2188
|
+
while( match( $0, /0x[0-9a-fA-F]{17}/ ) ){
|
|
2189
|
+
hexIssue = 1
|
|
2190
|
+
sub( /0x[0-9a-fA-F]+/, substr( $0, RSTART, RLENGTH-1 ), $0 )
|
|
2191
|
+
}
|
|
2192
|
+
if( hexIssue ){
|
|
2193
|
+
printerr( \\
|
|
2194
|
+
NR ": WARN Hex number trimmed (length longer than 16 chars)." )
|
|
2195
|
+
hexIssue = 0
|
|
2196
|
+
}
|
|
2197
|
+
print
|
|
2198
|
+
next
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
# CREATE DATABASE is not supported
|
|
2202
|
+
/^(CREATE DATABASE|create database)/ { next }
|
|
2203
|
+
|
|
2204
|
+
# print the CREATE line as is and capture the table name
|
|
2205
|
+
/^(CREATE|create)/ {
|
|
2206
|
+
if( $0 ~ /IF NOT EXISTS|if not exists/ || $0 ~ /TEMPORARY|temporary/ ){
|
|
2207
|
+
caseIssue = 1
|
|
2208
|
+
printerr( \\
|
|
2209
|
+
NR ": WARN Potential case sensitivity issues with table/column naming\\n" \\
|
|
2210
|
+
" (see INFO at the end)." )
|
|
2211
|
+
}
|
|
2212
|
+
if( match( $0, /\`[^\`]+/ ) ){
|
|
2213
|
+
tableName = substr( $0, RSTART+1, RLENGTH-1 )
|
|
2214
|
+
}
|
|
2215
|
+
aInc = 0
|
|
2216
|
+
prev = ""
|
|
2217
|
+
firstInTable = 1
|
|
2218
|
+
print
|
|
2219
|
+
next
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
# Replace \`FULLTEXT KEY\` (probably other \`XXXXX KEY\`)
|
|
2223
|
+
/^ (FULLTEXT KEY|fulltext key)/ { gsub( /[A-Za-z ]+(KEY|key)/, " KEY" ) }
|
|
2224
|
+
|
|
2225
|
+
# Get rid of field lengths in KEY lines
|
|
2226
|
+
/ (PRIMARY |primary )?(KEY|key)/ { gsub( /\\([0-9]+\\)/, "" ) }
|
|
2227
|
+
|
|
2228
|
+
aInc == 1 && /PRIMARY KEY|primary key/ { next }
|
|
2229
|
+
|
|
2230
|
+
# Replace COLLATE xxx_xxxx_xx statements with COLLATE BINARY
|
|
2231
|
+
/ (COLLATE|collate) [a-z0-9_]*/ { gsub( /(COLLATE|collate) [a-z0-9_]*/, "COLLATE BINARY" ) }
|
|
2232
|
+
|
|
2233
|
+
# Print all fields definition lines except the \`KEY\` lines.
|
|
2234
|
+
/^ / && !/^( (KEY|key)|\\);)/ {
|
|
2235
|
+
if( match( $0, /[^"\`]AUTO_INCREMENT|auto_increment[^"\`]/) ){
|
|
2236
|
+
aInc = 1
|
|
2237
|
+
gsub( /AUTO_INCREMENT|auto_increment/, "PRIMARY KEY AUTOINCREMENT" )
|
|
2238
|
+
}
|
|
2239
|
+
gsub( /(UNIQUE KEY|unique key) (\`.*\`|".*") /, "UNIQUE " )
|
|
2240
|
+
gsub( /(CHARACTER SET|character set) [^ ]+[ ,]/, "" )
|
|
2241
|
+
# FIXME
|
|
2242
|
+
# CREATE TRIGGER [UpdateLastTime]
|
|
2243
|
+
# AFTER UPDATE
|
|
2244
|
+
# ON Package
|
|
2245
|
+
# FOR EACH ROW
|
|
2246
|
+
# BEGIN
|
|
2247
|
+
# UPDATE Package SET LastUpdate = CURRENT_TIMESTAMP WHERE ActionId = old.ActionId;
|
|
2248
|
+
# END
|
|
2249
|
+
gsub( /(ON|on) (UPDATE|update) (CURRENT_TIMESTAMP|current_timestamp)(\\(\\))?/, "" )
|
|
2250
|
+
gsub( /(DEFAULT|default) (CURRENT_TIMESTAMP|current_timestamp)(\\(\\))?/, "DEFAULT current_timestamp")
|
|
2251
|
+
gsub( /(COLLATE|collate) [^ ]+ /, "" )
|
|
2252
|
+
gsub( /(ENUM|enum)[^)]+\\)/, "text " )
|
|
2253
|
+
gsub( /(SET|set)\\([^)]+\\)/, "text " )
|
|
2254
|
+
gsub( /UNSIGNED|unsigned/, "" )
|
|
2255
|
+
gsub( /_utf8mb3/, "" )
|
|
2256
|
+
gsub( /\` [^ ]*(INT|int|BIT|bit)[^ ]*/, "\` integer" )
|
|
2257
|
+
gsub( /" [^ ]*(INT|int|BIT|bit)[^ ]*/, "\\" integer" )
|
|
2258
|
+
ere_bit_field = "[bB]'[10]+'"
|
|
2259
|
+
if( match($0, ere_bit_field) ){
|
|
2260
|
+
sub( ere_bit_field, bit_to_int( substr( $0, RSTART +2, RLENGTH -2 -1 ) ) )
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
# remove USING BTREE and other suffixes for USING, for example: "UNIQUE KEY
|
|
2264
|
+
# \`hostname_domain\` (\`hostname\`,\`domain\`) USING BTREE,"
|
|
2265
|
+
gsub( / USING [^, ]+/, "" )
|
|
2266
|
+
|
|
2267
|
+
# field comments are not supported
|
|
2268
|
+
gsub( / (COMMENT|comment).+$/, "" )
|
|
2269
|
+
# Get commas off end of line
|
|
2270
|
+
gsub( /,.?$/, "" )
|
|
2271
|
+
if( prev ){
|
|
2272
|
+
if( firstInTable ){
|
|
2273
|
+
print prev
|
|
2274
|
+
firstInTable = 0
|
|
2275
|
+
}
|
|
2276
|
+
else {
|
|
2277
|
+
print "," prev
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
else {
|
|
2281
|
+
# FIXME check if this is correct in all cases
|
|
2282
|
+
if( match( $1,
|
|
2283
|
+
/(CONSTRAINT|constraint) ["].*["] (FOREIGN KEY|foreign key)/ ) ){
|
|
2284
|
+
print ","
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
prev = $1
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
/ ENGINE| engine/ {
|
|
2291
|
+
if( prev ){
|
|
2292
|
+
if( firstInTable ){
|
|
2293
|
+
print prev
|
|
2294
|
+
firstInTable = 0
|
|
2295
|
+
}
|
|
2296
|
+
else {
|
|
2297
|
+
print "," prev
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
prev=""
|
|
2301
|
+
print ");"
|
|
2302
|
+
next
|
|
2303
|
+
}
|
|
2304
|
+
# \`KEY\` lines are extracted from the \`CREATE\` block and stored in array for later print
|
|
2305
|
+
# in a separate \`CREATE KEY\` command. The index name is prefixed by the table name to
|
|
2306
|
+
# avoid a sqlite error for duplicate index name.
|
|
2307
|
+
/^( (KEY|key)|\\);)/ {
|
|
2308
|
+
if( prev ){
|
|
2309
|
+
if( firstInTable ){
|
|
2310
|
+
print prev
|
|
2311
|
+
firstInTable = 0
|
|
2312
|
+
}
|
|
2313
|
+
else {
|
|
2314
|
+
print "," prev
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
prev = ""
|
|
2318
|
+
if( $0 == ");" ){
|
|
2319
|
+
print
|
|
2320
|
+
}
|
|
2321
|
+
else {
|
|
2322
|
+
if( match( $0, /\`[^\`]+/ ) ){
|
|
2323
|
+
indexName = substr( $0, RSTART+1, RLENGTH-1 )
|
|
2324
|
+
}
|
|
2325
|
+
if( match( $0, /\\([^()]+/ ) ){
|
|
2326
|
+
indexKey = substr( $0, RSTART+1, RLENGTH-1 )
|
|
2327
|
+
}
|
|
2328
|
+
# idx_ prefix to avoid name clashes (they really happen!)
|
|
2329
|
+
key[tableName] = key[tableName] "CREATE INDEX \\"idx_" \\
|
|
2330
|
+
tableName "_" indexName "\\" ON \\"" tableName "\\" (" indexKey ");\\n"
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
END {
|
|
2335
|
+
if( no_END ){ exit 1}
|
|
2336
|
+
# print all KEY creation lines.
|
|
2337
|
+
for( table in key ){ printf key[table] }
|
|
2338
|
+
|
|
2339
|
+
print "END TRANSACTION;"
|
|
2340
|
+
|
|
2341
|
+
if( caseIssue ){
|
|
2342
|
+
printerr( \\
|
|
2343
|
+
"INFO Pure sqlite identifiers are case insensitive (even if quoted\\n" \\
|
|
2344
|
+
" or if ASCII) and doesnt cross-check TABLE and TEMPORARY TABLE\\n" \\
|
|
2345
|
+
" identifiers. Thus expect errors like \\"table T has no column named F\\".")
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
`;
|
|
2349
|
+
}
|