@technicity/data-service-generator 0.23.0-next.0 → 0.23.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generation/generate.d.ts +4 -0
- package/dist/generation/generate.js +450 -183
- package/dist/generation/pg2sqliteSchema.d.ts +6 -0
- package/dist/generation/pg2sqliteSchema.js +121 -0
- package/dist/runtime/IRuntime.d.ts +2 -1
- package/dist/runtime/lib/getSqlAst.js +8 -6
- package/dist/runtime/lib/shared.js +57 -13
- package/dist/runtime/lib/stringifyWhere.js +7 -0
- package/package.json +3 -1
- package/dist/src/generation/generate.d.ts +0 -21
- package/dist/src/generation/generate.js +0 -2349
- package/dist/src/index.d.ts +0 -4
- package/dist/src/index.js +0 -11
- package/dist/src/lib/CustomError.d.ts +0 -3
- package/dist/src/lib/CustomError.js +0 -10
- package/dist/src/lib/capitalizeFirstLetter.d.ts +0 -1
- package/dist/src/lib/capitalizeFirstLetter.js +0 -6
- package/dist/src/lib/getDuplicates.d.ts +0 -1
- package/dist/src/lib/getDuplicates.js +0 -9
- package/dist/src/lib/isNotNullOrUndefined.d.ts +0 -1
- package/dist/src/lib/isNotNullOrUndefined.js +0 -7
- package/dist/src/runtime/Cache.d.ts +0 -28
- package/dist/src/runtime/Cache.js +0 -142
- package/dist/src/runtime/IRuntime.d.ts +0 -209
- package/dist/src/runtime/IRuntime.js +0 -12
- package/dist/src/runtime/RuntimeMySQL.d.ts +0 -26
- package/dist/src/runtime/RuntimeMySQL.js +0 -132
- package/dist/src/runtime/RuntimeSQLite.d.ts +0 -42
- package/dist/src/runtime/RuntimeSQLite.js +0 -150
- package/dist/src/runtime/Stats.d.ts +0 -8
- package/dist/src/runtime/Stats.js +0 -31
- package/dist/src/runtime/lib/MySQL.d.ts +0 -13
- package/dist/src/runtime/lib/MySQL.js +0 -116
- package/dist/src/runtime/lib/SDKBadWhereError.d.ts +0 -4
- package/dist/src/runtime/lib/SDKBadWhereError.js +0 -10
- package/dist/src/runtime/lib/SDKNotFoundError.d.ts +0 -4
- package/dist/src/runtime/lib/SDKNotFoundError.js +0 -10
- package/dist/src/runtime/lib/addNullFallbacks.d.ts +0 -1
- package/dist/src/runtime/lib/addNullFallbacks.js +0 -32
- package/dist/src/runtime/lib/addNullFallbacks.test.d.ts +0 -1
- package/dist/src/runtime/lib/addNullFallbacks.test.js +0 -206
- package/dist/src/runtime/lib/cursor.d.ts +0 -2
- package/dist/src/runtime/lib/cursor.js +0 -10
- package/dist/src/runtime/lib/getDateTimeStringMySQL.d.ts +0 -1
- package/dist/src/runtime/lib/getDateTimeStringMySQL.js +0 -7
- package/dist/src/runtime/lib/getOrderBy.d.ts +0 -5
- package/dist/src/runtime/lib/getOrderBy.js +0 -52
- package/dist/src/runtime/lib/getSqlAst.d.ts +0 -2
- package/dist/src/runtime/lib/getSqlAst.js +0 -245
- package/dist/src/runtime/lib/getWhere.d.ts +0 -2
- package/dist/src/runtime/lib/getWhere.js +0 -20
- package/dist/src/runtime/lib/shared.d.ts +0 -13
- package/dist/src/runtime/lib/shared.js +0 -1118
- package/dist/src/runtime/lib/stringifyWhere.d.ts +0 -18
- package/dist/src/runtime/lib/stringifyWhere.js +0 -257
- package/dist/src/runtime/lib/stringifyWhere.test.d.ts +0 -1
- package/dist/src/runtime/lib/stringifyWhere.test.js +0 -245
- package/dist/src/runtime/lib/utility.d.ts +0 -5
- package/dist/src/runtime/lib/utility.js +0 -14
- package/dist/src/traverseFieldArgs.d.ts +0 -2
- package/dist/src/traverseFieldArgs.js +0 -17
- package/dist/src/traverseFieldArgs.test.d.ts +0 -1
- package/dist/src/traverseFieldArgs.test.js +0 -56
- package/dist/test/addWhereValidTrue.d.ts +0 -1
- package/dist/test/addWhereValidTrue.js +0 -39
- package/dist/test/globalSetup.d.ts +0 -13
- package/dist/test/globalSetup.js +0 -436
- package/dist/test/postgres/__generated__/sdk-ts/artifacts.d.ts +0 -8425
- package/dist/test/postgres/__generated__/sdk-ts/artifacts.js +0 -10469
- package/dist/test/postgres/__generated__/sdk-ts/index.js +0 -12162
- /package/dist/{src/runtime → runtime}/RuntimePostgreSQL.d.ts +0 -0
- /package/dist/{src/runtime → runtime}/RuntimePostgreSQL.js +0 -0
- /package/dist/{src/runtime → runtime}/lib/PostgreSQL.d.ts +0 -0
- /package/dist/{src/runtime → runtime}/lib/PostgreSQL.js +0 -0
|
@@ -30,24 +30,29 @@ exports.generate = generate;
|
|
|
30
30
|
const path = __importStar(require("node:path"));
|
|
31
31
|
const fs = __importStar(require("node:fs"));
|
|
32
32
|
const os = __importStar(require("node:os"));
|
|
33
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
33
34
|
const child_process = __importStar(require("node:child_process"));
|
|
34
35
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
36
|
+
const pino_1 = require("pino");
|
|
35
37
|
const prettier = __importStar(require("prettier"));
|
|
36
38
|
const changeCase = __importStar(require("change-case"));
|
|
37
39
|
const fse = __importStar(require("fs-extra"));
|
|
38
40
|
const _ = __importStar(require("lodash/fp"));
|
|
41
|
+
const memoize_1 = __importDefault(require("lodash/memoize"));
|
|
39
42
|
const json_schema_to_typescript_1 = require("json-schema-to-typescript");
|
|
40
43
|
const getDuplicates_1 = require("../lib/getDuplicates");
|
|
41
44
|
const isNotNullOrUndefined_1 = require("../lib/isNotNullOrUndefined");
|
|
45
|
+
const pg_1 = require("pg");
|
|
42
46
|
const MySQL_1 = require("../runtime/lib/MySQL");
|
|
43
47
|
const capitalizeFirstLetter_1 = require("../lib/capitalizeFirstLetter");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
const pg2sqliteSchema_1 = require("./pg2sqliteSchema");
|
|
49
|
+
const ctxStorage = new node_async_hooks_1.AsyncLocalStorage();
|
|
50
|
+
function getCtx() {
|
|
51
|
+
const c = ctxStorage.getStore();
|
|
52
|
+
if (!c)
|
|
53
|
+
throw new Error("generate() context missing");
|
|
54
|
+
return c;
|
|
55
|
+
}
|
|
51
56
|
const json2TsOpts = {
|
|
52
57
|
bannerComment: ""
|
|
53
58
|
};
|
|
@@ -59,162 +64,237 @@ async function generate(input) {
|
|
|
59
64
|
if (input.dialect == null) {
|
|
60
65
|
throw new Error("Must specify `dialect`");
|
|
61
66
|
}
|
|
67
|
+
const log = (0, pino_1.pino)({
|
|
68
|
+
name: "generate",
|
|
69
|
+
level: input.logLevel ?? process.env.LOG_LEVEL ?? "info",
|
|
70
|
+
transport: {
|
|
71
|
+
target: "pino-pretty",
|
|
72
|
+
options: {
|
|
73
|
+
colorize: true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
62
77
|
const specialCaseUuidColumn = input.specialCaseUuidColumn ?? true;
|
|
63
78
|
const includeMappedFields = input.includeMappedFields ?? true;
|
|
64
79
|
const supplementClientOpts = input.supplementClientOpts ?? true;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
tables = tables.filter((x) => input.tables?.includes(x));
|
|
73
|
-
}
|
|
74
|
-
if (input.excludeTables != null) {
|
|
75
|
-
tables = tables.filter((x) => !input.excludeTables?.includes(x));
|
|
76
|
-
}
|
|
77
|
-
const data = await Promise.all(tables.flatMap((x) => [
|
|
78
|
-
getGetOneData(x, includeMappedFields),
|
|
79
|
-
getGetListData(x),
|
|
80
|
-
getGetListPaginatedData(x),
|
|
81
|
-
getPostOneData(x, specialCaseUuidColumn, includeMappedFields),
|
|
82
|
-
getPatchOneData(x, specialCaseUuidColumn, includeMappedFields),
|
|
83
|
-
getPatchListData(x),
|
|
84
|
-
getDeleteOneData(x),
|
|
85
|
-
getDeleteListData(x)
|
|
86
|
-
]));
|
|
87
|
-
const artifacts = await getArtifacts(tables, includeMappedFields, specialCaseUuidColumn);
|
|
88
|
-
const artifactsSource = getArtifactsSource(artifacts);
|
|
89
|
-
const sdkSource = await getSDKSource(data, specialCaseUuidColumn, supplementClientOpts, artifacts, input.outputSqliteSchema);
|
|
90
|
-
const sdkFilename = "index.ts";
|
|
91
|
-
const sourceIRuntimeFilePath = fs.existsSync(path.join(__dirname, "../runtime", "IRuntime.ts"))
|
|
92
|
-
? path.join(__dirname, "../runtime", "IRuntime.ts")
|
|
93
|
-
: path.join(__dirname, "../runtime", "IRuntime.js");
|
|
94
|
-
const IRuntimeFilename = path.basename(sourceIRuntimeFilePath);
|
|
95
|
-
const artifactsFilename = "artifacts.ts";
|
|
96
|
-
const tsConfigJSON = {
|
|
97
|
-
compilerOptions: {
|
|
98
|
-
module: "commonjs",
|
|
99
|
-
moduleResolution: "node",
|
|
100
|
-
target: "es2020",
|
|
101
|
-
declaration: true,
|
|
102
|
-
outDir: "./sdk-ts"
|
|
103
|
-
},
|
|
104
|
-
include: [sdkFilename, artifactsFilename, IRuntimeFilename]
|
|
80
|
+
const runId = node_crypto_1.default.randomUUID();
|
|
81
|
+
const ctx = {
|
|
82
|
+
runId,
|
|
83
|
+
log: log.child({}),
|
|
84
|
+
dialect: input.dialect,
|
|
85
|
+
query: undefined,
|
|
86
|
+
pool: undefined
|
|
105
87
|
};
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
88
|
+
log.debug({
|
|
89
|
+
runId: ctx.runId,
|
|
90
|
+
dialect: input.dialect,
|
|
91
|
+
database: input.database,
|
|
92
|
+
outdir: input.outdir
|
|
93
|
+
}, "generate() started");
|
|
94
|
+
return ctxStorage.run(ctx, async () => {
|
|
95
|
+
init(input);
|
|
96
|
+
ctx.log.debug("init() completed");
|
|
97
|
+
let tables = await getTableNames();
|
|
98
|
+
ctx.log.debug({ tableCount: tables.length, tables }, "getTableNames() completed");
|
|
99
|
+
if (tables.length === 0) {
|
|
100
|
+
throw new Error("No tables found");
|
|
119
101
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// _ because - in filename is not supported by mysql2sqlite
|
|
123
|
-
`dsg_${node_crypto_1.default.randomUUID()}`.replace(/-/g, "_"));
|
|
124
|
-
fse.mkdirpSync(tmpDirPath);
|
|
125
|
-
fs.writeFileSync(path.join(tmpDirPath, sdkFilename), sdkSource);
|
|
126
|
-
fs.writeFileSync(path.join(tmpDirPath, artifactsFilename), artifactsSource);
|
|
127
|
-
fse.copyFileSync(sourceIRuntimeFilePath, path.join(tmpDirPath, IRuntimeFilename));
|
|
128
|
-
const typesDirPath = path.join(tmpDirPath, "types");
|
|
129
|
-
fse.mkdirpSync(typesDirPath);
|
|
130
|
-
fs.writeFileSync(path.join(typesDirPath, "_shared.ts"), getTypeShared());
|
|
131
|
-
for (let x of data) {
|
|
132
|
-
if (x.kind === "getOne") {
|
|
133
|
-
fs.writeFileSync(path.join(typesDirPath, x.typeFieldsName + ".ts"), x.typeFields);
|
|
134
|
-
fs.writeFileSync(path.join(typesDirPath, x.typeReturnBaseName + ".ts"), x.typeReturnBase);
|
|
102
|
+
if (input.tables != null) {
|
|
103
|
+
tables = tables.filter((x) => input.tables?.includes(x));
|
|
135
104
|
}
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
fs.writeFileSync(path.join(typesDirPath, x.typeOrderByName + ".ts"), x.typeOrderBy);
|
|
105
|
+
if (input.excludeTables != null) {
|
|
106
|
+
tables = tables.filter((x) => !input.excludeTables?.includes(x));
|
|
139
107
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
108
|
+
ctx.log.debug({ tableCount: tables.length, tables }, "tables after filter");
|
|
109
|
+
const data = await Promise.all(tables.flatMap((x) => [
|
|
110
|
+
getGetOneData(x, includeMappedFields),
|
|
111
|
+
getGetListData(x),
|
|
112
|
+
getGetListPaginatedData(x),
|
|
113
|
+
getPostOneData(x, specialCaseUuidColumn, includeMappedFields),
|
|
114
|
+
getPatchOneData(x, specialCaseUuidColumn, includeMappedFields),
|
|
115
|
+
getPatchListData(x),
|
|
116
|
+
getDeleteOneData(x),
|
|
117
|
+
getDeleteListData(x)
|
|
118
|
+
]));
|
|
119
|
+
ctx.log.debug({ inputLength: data.length }, "SDK input data collected");
|
|
120
|
+
const artifacts = await getArtifacts(tables, includeMappedFields, specialCaseUuidColumn);
|
|
121
|
+
ctx.log.debug("getArtifacts() completed");
|
|
122
|
+
const artifactsSource = getArtifactsSource(artifacts);
|
|
123
|
+
const sdkSource = await getSDKSource(data, specialCaseUuidColumn, supplementClientOpts, artifacts, input.outputSqliteSchema);
|
|
124
|
+
ctx.log.debug("getSDKSource() completed");
|
|
125
|
+
const sdkFilename = "index.ts";
|
|
126
|
+
const sourceIRuntimeFilePath = fs.existsSync(path.join(__dirname, "../runtime", "IRuntime.ts"))
|
|
127
|
+
? path.join(__dirname, "../runtime", "IRuntime.ts")
|
|
128
|
+
: path.join(__dirname, "../runtime", "IRuntime.js");
|
|
129
|
+
const IRuntimeFilename = path.basename(sourceIRuntimeFilePath);
|
|
130
|
+
const artifactsFilename = "artifacts.ts";
|
|
131
|
+
const tsConfigJSON = {
|
|
132
|
+
compilerOptions: {
|
|
133
|
+
module: "commonjs",
|
|
134
|
+
moduleResolution: "node",
|
|
135
|
+
target: "es2020",
|
|
136
|
+
declaration: true,
|
|
137
|
+
outDir: "./sdk-ts"
|
|
138
|
+
},
|
|
139
|
+
include: [sdkFilename, artifactsFilename, IRuntimeFilename]
|
|
140
|
+
};
|
|
141
|
+
const packageJSON = {
|
|
142
|
+
name: "temp",
|
|
143
|
+
version: "1.0.0",
|
|
144
|
+
// Deps need to be included so that they're inlined by ncc
|
|
145
|
+
dependencies: require("../../package.json").dependencies,
|
|
146
|
+
devDependencies: {
|
|
147
|
+
"@types/node": require("../../package.json").devDependencies["@types/node"],
|
|
148
|
+
typescript: require("../../package.json").devDependencies.typescript
|
|
149
|
+
},
|
|
150
|
+
// Not `resolutions` because npm used for install
|
|
151
|
+
overrides: {
|
|
152
|
+
// Fix for: `Cannot find type definition file for 'glob'`
|
|
153
|
+
glob: ">9.0.0"
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const tmpDirPath = path.join(os.tmpdir(),
|
|
157
|
+
// _ because - in filename is not supported by mysql2sqlite
|
|
158
|
+
`dsg_${node_crypto_1.default.randomUUID()}`.replace(/-/g, "_"));
|
|
159
|
+
ctx.log.debug({ tmpDirPath }, "writing SDK and artifacts to tmp dir");
|
|
160
|
+
fse.mkdirpSync(tmpDirPath);
|
|
161
|
+
fs.writeFileSync(path.join(tmpDirPath, sdkFilename), sdkSource);
|
|
162
|
+
fs.writeFileSync(path.join(tmpDirPath, artifactsFilename), artifactsSource);
|
|
163
|
+
fse.copyFileSync(sourceIRuntimeFilePath, path.join(tmpDirPath, IRuntimeFilename));
|
|
164
|
+
const typesDirPath = path.join(tmpDirPath, "types");
|
|
165
|
+
fse.mkdirpSync(typesDirPath);
|
|
166
|
+
fs.writeFileSync(path.join(typesDirPath, "_shared.ts"), getTypeShared());
|
|
167
|
+
for (let x of data) {
|
|
168
|
+
if (x.kind === "getOne") {
|
|
169
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeFieldsName + ".ts"), x.typeFields);
|
|
170
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeReturnBaseName + ".ts"), x.typeReturnBase);
|
|
171
|
+
}
|
|
172
|
+
if (x.kind === "getList") {
|
|
173
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeWhereName + ".ts"), x.typeWhere);
|
|
174
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeOrderByName + ".ts"), x.typeOrderBy);
|
|
175
|
+
}
|
|
176
|
+
if (x.kind === "postOne") {
|
|
177
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeDataName + ".ts"), x.typeData);
|
|
178
|
+
}
|
|
179
|
+
if (x.kind === "patchOne") {
|
|
180
|
+
fs.writeFileSync(path.join(typesDirPath, x.typeDataName + ".ts"), x.typeData);
|
|
181
|
+
}
|
|
145
182
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
fs.writeFileSync(path.join(typesDirPath, "index.ts"), getTypeTypesIndex(data));
|
|
184
|
+
fs.writeFileSync(path.join(tmpDirPath, "package.json"), JSON.stringify(packageJSON, null, 2));
|
|
185
|
+
fs.writeFileSync(path.join(tmpDirPath, "tsconfig.json"), JSON.stringify(tsConfigJSON, null, 2));
|
|
186
|
+
fse.copySync(__dirname, path.join(tmpDirPath, "src"));
|
|
187
|
+
const tmpBuildOutputPath = path.join(tmpDirPath, "sdk-ts");
|
|
188
|
+
const outdir = path.resolve(input.outdir);
|
|
189
|
+
const sdkOutputPath = path.join(outdir, "sdk-ts");
|
|
190
|
+
const nccVersion = "^0.33.0";
|
|
191
|
+
child_process.execSync("npm i", { cwd: tmpDirPath, stdio: "inherit" });
|
|
192
|
+
child_process.execSync(`npm_config_yes=true npx -p @vercel/ncc@${nccVersion} ncc build ./${sdkFilename} -o ${tmpBuildOutputPath} -e ./artifacts`, { cwd: tmpDirPath, stdio: "inherit" });
|
|
193
|
+
// TODO: workaround for artifacts.js not being output by ncc
|
|
194
|
+
fs.writeFileSync(path.join(tmpBuildOutputPath, "artifacts.js"), artifactsSource
|
|
195
|
+
.replace("export const artifacts: IArtifacts = ", "module.exports.artifacts = ")
|
|
196
|
+
.split("\n")
|
|
197
|
+
// Remove import
|
|
198
|
+
.slice(2)
|
|
199
|
+
.join("\n"));
|
|
200
|
+
// TODO: workaround for IRuntime.d.ts not being included
|
|
201
|
+
// copyFileSync hangs for some reason, so use writeFileSync + readFileSync instead
|
|
202
|
+
fs.writeFileSync(path.join(tmpBuildOutputPath, "IRuntime.d.ts"), fs.existsSync(path.join(__dirname, "../runtime", "IRuntime.d.ts"))
|
|
203
|
+
? fs.readFileSync(path.join(__dirname, "../runtime", "IRuntime.d.ts"), "utf-8")
|
|
204
|
+
: fs.readFileSync(sourceIRuntimeFilePath, "utf-8"));
|
|
205
|
+
if (input.outputSqliteSchema) {
|
|
206
|
+
let schemaSqlite = null;
|
|
207
|
+
if (ctx.dialect === "mysql") {
|
|
208
|
+
// Since mysql2sqlite outputs a malformed string if a column
|
|
209
|
+
// has the name `enum`, temporarily change the name to something else,
|
|
210
|
+
// then change it back.
|
|
211
|
+
const enumMarker = "`" + node_crypto_1.default.randomUUID() + "`";
|
|
212
|
+
const schemaMySql = Object.values(artifacts)
|
|
213
|
+
.reduce((acc, x) => {
|
|
214
|
+
let d = x.dump?.schema;
|
|
215
|
+
if (!d) {
|
|
216
|
+
return acc;
|
|
217
|
+
}
|
|
218
|
+
d = d.replace(/`enum`/g, enumMarker);
|
|
219
|
+
d += ";";
|
|
220
|
+
acc.push(d);
|
|
221
|
+
return acc;
|
|
222
|
+
}, [])
|
|
223
|
+
.join("\n\n");
|
|
224
|
+
const mysql2SqliteSrc = getMysql2sqliteSrc();
|
|
225
|
+
const mysql2SqlitePath = path.join(tmpDirPath, "mysql2sqlite");
|
|
226
|
+
fs.writeFileSync(mysql2SqlitePath, mysql2SqliteSrc);
|
|
227
|
+
fs.chmodSync(mysql2SqlitePath, 0o755);
|
|
228
|
+
const tmpMySqlSchemaFilename = "tmp.sql";
|
|
229
|
+
const tmpMySqlSchemaPath = path.join(tmpDirPath, tmpMySqlSchemaFilename);
|
|
230
|
+
fs.writeFileSync(tmpMySqlSchemaPath, schemaMySql);
|
|
231
|
+
schemaSqlite = child_process
|
|
232
|
+
.execFileSync(mysql2SqlitePath, [tmpMySqlSchemaFilename], {
|
|
233
|
+
cwd: tmpDirPath
|
|
234
|
+
})
|
|
235
|
+
.toString();
|
|
236
|
+
schemaSqlite = schemaSqlite.replace(new RegExp(enumMarker, "g"), "`enum`");
|
|
179
237
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
fs.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const src = prettier.format(`module.exports = { schema: \`${schemaSqlite.replace(/`/g, "\\`")}\` }`, { parser: "babel" });
|
|
198
|
-
fs.writeFileSync(path.join(tmpBuildOutputPath, "artifacts.sqlite.js"), src);
|
|
199
|
-
}
|
|
200
|
-
if (!fs.existsSync(outdir)) {
|
|
201
|
-
fse.mkdirpSync(outdir);
|
|
202
|
-
}
|
|
203
|
-
fse.emptyDirSync(sdkOutputPath);
|
|
204
|
-
fse.copySync(tmpBuildOutputPath, sdkOutputPath);
|
|
205
|
-
fse.removeSync(tmpDirPath);
|
|
238
|
+
else if (ctx.dialect === "postgresql") {
|
|
239
|
+
schemaSqlite = await (0, pg2sqliteSchema_1.convertPgSchemaToSqlite)(ctx.pool);
|
|
240
|
+
}
|
|
241
|
+
if (schemaSqlite) {
|
|
242
|
+
const src = prettier.format(`module.exports = { schema: \`${schemaSqlite.replace(/`/g, "\\`")}\` }`, { parser: "babel" });
|
|
243
|
+
fs.writeFileSync(path.join(tmpBuildOutputPath, "artifacts.sqlite.js"), src);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (!fs.existsSync(outdir)) {
|
|
247
|
+
fse.mkdirpSync(outdir);
|
|
248
|
+
}
|
|
249
|
+
fse.emptyDirSync(sdkOutputPath);
|
|
250
|
+
fse.copySync(tmpBuildOutputPath, sdkOutputPath);
|
|
251
|
+
ctx.log.debug({ outdir, sdkOutputPath }, "copy to output dir completed");
|
|
252
|
+
fse.removeSync(tmpDirPath);
|
|
253
|
+
ctx.log.debug("generate() completed");
|
|
254
|
+
});
|
|
206
255
|
}
|
|
207
256
|
function init(input) {
|
|
257
|
+
const ctx = getCtx();
|
|
208
258
|
const { database, user, password, host, port, server } = input;
|
|
209
|
-
if (dialect === "mysql") {
|
|
210
|
-
|
|
259
|
+
if (ctx.dialect === "mysql") {
|
|
260
|
+
const connectionOpts = {
|
|
211
261
|
user,
|
|
212
262
|
password,
|
|
213
|
-
host,
|
|
214
|
-
port,
|
|
215
|
-
database
|
|
216
|
-
|
|
217
|
-
|
|
263
|
+
host: host ?? "localhost",
|
|
264
|
+
port: port ?? 3306,
|
|
265
|
+
database,
|
|
266
|
+
...input.clientOpts
|
|
267
|
+
};
|
|
268
|
+
ctx.log.debug({
|
|
269
|
+
dialect: "mysql",
|
|
270
|
+
host: connectionOpts.host,
|
|
271
|
+
port: connectionOpts.port,
|
|
272
|
+
database,
|
|
273
|
+
user
|
|
274
|
+
}, "connecting to MySQL");
|
|
275
|
+
const mysql = new MySQL_1.MySQL(connectionOpts);
|
|
276
|
+
ctx.query = mysql.query.bind(mysql);
|
|
277
|
+
ctx.pool = mysql;
|
|
278
|
+
}
|
|
279
|
+
if (ctx.dialect === "postgresql") {
|
|
280
|
+
const connectionOpts = {
|
|
281
|
+
host: host ?? "localhost",
|
|
282
|
+
port: port ?? 5432,
|
|
283
|
+
user,
|
|
284
|
+
password,
|
|
285
|
+
database,
|
|
286
|
+
...input.clientOpts
|
|
287
|
+
};
|
|
288
|
+
ctx.log.debug({
|
|
289
|
+
dialect: "postgresql",
|
|
290
|
+
host: connectionOpts.host,
|
|
291
|
+
port: connectionOpts.port,
|
|
292
|
+
database,
|
|
293
|
+
user
|
|
294
|
+
}, "connecting to PostgreSQL");
|
|
295
|
+
const pool = new pg_1.Pool(connectionOpts);
|
|
296
|
+
ctx.query = (q, values) => pool.query(q, values ?? []).then((r) => r.rows);
|
|
297
|
+
ctx.pool = pool;
|
|
218
298
|
}
|
|
219
299
|
}
|
|
220
300
|
// It's a bit awkward to put __whereNeedsProcessing, __prepareWhere on the class,
|
|
@@ -258,7 +338,7 @@ async function getSDKSource(input, specialCaseUuidColumn, supplementClientOpts,
|
|
|
258
338
|
runtime: any;
|
|
259
339
|
clientOpts: { [k: string]: any; },
|
|
260
340
|
otherOpts?: { [k: string]: any; },
|
|
261
|
-
passBeforeValueToAfterCallback
|
|
341
|
+
passBeforeValueToAfterCallback?: boolean,
|
|
262
342
|
}) {
|
|
263
343
|
let otherOpts = opts.otherOpts ?? {};
|
|
264
344
|
if (opts.clientOpts.filename === ":memory:") {
|
|
@@ -271,7 +351,7 @@ async function getSDKSource(input, specialCaseUuidColumn, supplementClientOpts,
|
|
|
271
351
|
: "otherOpts"}, artifacts);
|
|
272
352
|
this.onHandlerMap = new Map();
|
|
273
353
|
this.eventTarget = new EventTarget();
|
|
274
|
-
this.passBeforeValueToAfterCallback = opts.passBeforeValueToAfterCallback;
|
|
354
|
+
this.passBeforeValueToAfterCallback = opts.passBeforeValueToAfterCallback ?? false;
|
|
275
355
|
}
|
|
276
356
|
|
|
277
357
|
$use(middleware: TMiddleware) {
|
|
@@ -601,6 +681,7 @@ function getMethodSourceOnHandlerPostOne(x) {
|
|
|
601
681
|
return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
|
|
602
682
|
(sdk: InstanceType<typeof SDK>, input: { data: ${x.typeDataName} },
|
|
603
683
|
output: Partial<${getTypeReturnName(x.table)}>,
|
|
684
|
+
before: null,
|
|
604
685
|
context: TContext,
|
|
605
686
|
) => Promise<void>
|
|
606
687
|
): void {
|
|
@@ -715,6 +796,7 @@ function getMethodSourceOnHandlerDeleteOne(x, findOnes) {
|
|
|
715
796
|
.map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
|
|
716
797
|
.join(" | ")}, },
|
|
717
798
|
output: void,
|
|
799
|
+
before: ${getTypeReturnName(x.table)} | null,
|
|
718
800
|
context: TContext,
|
|
719
801
|
) => Promise<void>
|
|
720
802
|
): void {
|
|
@@ -1068,7 +1150,7 @@ async function getMappedFields(table) {
|
|
|
1068
1150
|
name: "uuid",
|
|
1069
1151
|
// Replace `Id` with `Uuid`
|
|
1070
1152
|
as: x.foreignKey.slice(0, -2) + "Uuid",
|
|
1071
|
-
type: getBaseJSONType(uuidColumn.Type)
|
|
1153
|
+
type: getBaseJSONType(uuidColumn.Type, getCtx().dialect)
|
|
1072
1154
|
});
|
|
1073
1155
|
}
|
|
1074
1156
|
return out;
|
|
@@ -1424,6 +1506,7 @@ function getArtifactsSource(artifacts) {
|
|
|
1424
1506
|
return prettier.format(src, { parser: "typescript" });
|
|
1425
1507
|
}
|
|
1426
1508
|
async function getArtifacts(tables, includeMappedFields, specialCaseUuidColumn) {
|
|
1509
|
+
const ctx = getCtx();
|
|
1427
1510
|
const tableMetaList = await Promise.all(tables.map(async (table) => {
|
|
1428
1511
|
const [tableMeta, primaryKey, dumpSchema] = await Promise.all([
|
|
1429
1512
|
getTableMeta(table),
|
|
@@ -1489,7 +1572,7 @@ async function getArtifacts(tables, includeMappedFields, specialCaseUuidColumn)
|
|
|
1489
1572
|
}
|
|
1490
1573
|
return {
|
|
1491
1574
|
kind: "scalar",
|
|
1492
|
-
type: getBaseJSONType(t.Type),
|
|
1575
|
+
type: getBaseJSONType(t.Type, ctx.dialect),
|
|
1493
1576
|
name: t.Field,
|
|
1494
1577
|
nullable,
|
|
1495
1578
|
hasDefaultValue: !!t.Default
|
|
@@ -1534,7 +1617,7 @@ async function getArtifacts(tables, includeMappedFields, specialCaseUuidColumn)
|
|
|
1534
1617
|
}, {});
|
|
1535
1618
|
return artifacts;
|
|
1536
1619
|
}
|
|
1537
|
-
const getRelationInfo =
|
|
1620
|
+
const getRelationInfo = (0, memoize_1.default)(async function getRelationInfo(table) {
|
|
1538
1621
|
const relationsManyToOne = await getRelationsManyToOne(table);
|
|
1539
1622
|
const relationsOneToMany = await getRelationsOneToMany(table);
|
|
1540
1623
|
let out = [];
|
|
@@ -1602,14 +1685,14 @@ const getRelationInfo = _.memoize(async function getRelationInfo(table) {
|
|
|
1602
1685
|
return acc;
|
|
1603
1686
|
}, []);
|
|
1604
1687
|
out = out.concat(relationsManyToMany);
|
|
1605
|
-
out = _.sortBy((x) => x.table, out);
|
|
1688
|
+
out = _.sortBy([(x) => x.table, (x) => x.name], out);
|
|
1606
1689
|
return out;
|
|
1607
|
-
});
|
|
1690
|
+
}, (table) => getCtx().runId + ":" + table);
|
|
1608
1691
|
function getRelationManyToOneFieldName(x) {
|
|
1609
1692
|
return changeCase.camelCase(x.foreignKey.replace(new RegExp(x.referencedKey + "$", "i"), ""));
|
|
1610
1693
|
}
|
|
1611
1694
|
// TODO: not sure if this logic is correct
|
|
1612
|
-
async function getJunctionTables() {
|
|
1695
|
+
const getJunctionTables = (0, memoize_1.default)(async function getJunctionTables() {
|
|
1613
1696
|
const tables = await getTableNames();
|
|
1614
1697
|
return (await Promise.all(tables.map(async (table) => {
|
|
1615
1698
|
const relations = await getRelationsManyToOne(table);
|
|
@@ -1624,14 +1707,15 @@ async function getJunctionTables() {
|
|
|
1624
1707
|
}
|
|
1625
1708
|
return null;
|
|
1626
1709
|
}))).filter(isNotNullOrUndefined_1.isNotNullOrUndefined);
|
|
1627
|
-
}
|
|
1710
|
+
}, () => getCtx().runId);
|
|
1628
1711
|
// `from` relations
|
|
1629
1712
|
// https://stackoverflow.com/a/54732547
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1713
|
+
const getRelationsManyToOne = (0, memoize_1.default)(async function getRelationsManyToOne(table) {
|
|
1714
|
+
const { dialect, query } = getCtx();
|
|
1715
|
+
const tableMeta = await getTableMeta(table);
|
|
1716
|
+
let rs;
|
|
1717
|
+
if (dialect === "mysql") {
|
|
1718
|
+
const sql = `
|
|
1635
1719
|
SELECT
|
|
1636
1720
|
TABLE_SCHEMA as db,
|
|
1637
1721
|
TABLE_NAME as t1,
|
|
@@ -1646,8 +1730,19 @@ const getRelationsManyToOne = _.memoize(async function getRelationsManyToOne(tab
|
|
|
1646
1730
|
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
1647
1731
|
AND (TABLE_NAME = ?);
|
|
1648
1732
|
`;
|
|
1649
|
-
|
|
1650
|
-
|
|
1733
|
+
rs = await query(sql, [table]);
|
|
1734
|
+
}
|
|
1735
|
+
else if (dialect === "postgresql") {
|
|
1736
|
+
rs = await query(`SELECT kcu.column_name AS "t1Field", ccu.table_name AS t2, ccu.column_name AS "t2Field"
|
|
1737
|
+
FROM information_schema.key_column_usage kcu
|
|
1738
|
+
JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name AND kcu.table_schema = rc.constraint_schema
|
|
1739
|
+
JOIN information_schema.constraint_column_usage ccu ON rc.unique_constraint_name = ccu.constraint_name AND rc.unique_constraint_schema = ccu.table_schema
|
|
1740
|
+
WHERE kcu.table_schema = 'public' AND kcu.table_name = $1
|
|
1741
|
+
ORDER BY ccu.table_name, ccu.column_name`, [table]);
|
|
1742
|
+
}
|
|
1743
|
+
else {
|
|
1744
|
+
throw new Error("Unsupported dialect: " + dialect);
|
|
1745
|
+
}
|
|
1651
1746
|
const xs = await Promise.all(_.uniqWith(_.isEqual, rs.map(async (v) => {
|
|
1652
1747
|
return {
|
|
1653
1748
|
table: table,
|
|
@@ -1657,11 +1752,14 @@ const getRelationsManyToOne = _.memoize(async function getRelationsManyToOne(tab
|
|
|
1657
1752
|
nullable: tableMeta.find((m) => m.Field === v.t1Field)?.Null === "YES"
|
|
1658
1753
|
};
|
|
1659
1754
|
})));
|
|
1660
|
-
return _.sortBy((x) => x.referencedTable, xs);
|
|
1661
|
-
});
|
|
1755
|
+
return _.sortBy([(x) => x.referencedTable, (x) => x.referencedKey, (x) => x.foreignKey], xs);
|
|
1756
|
+
}, (table) => getCtx().runId + ":" + table);
|
|
1662
1757
|
// `to` relations
|
|
1663
|
-
const getRelationsOneToMany =
|
|
1664
|
-
const
|
|
1758
|
+
const getRelationsOneToMany = (0, memoize_1.default)(async function getRelationsOneToMany(table) {
|
|
1759
|
+
const { dialect, query } = getCtx();
|
|
1760
|
+
let rs;
|
|
1761
|
+
if (dialect === "mysql") {
|
|
1762
|
+
const sql = `
|
|
1665
1763
|
SELECT
|
|
1666
1764
|
TABLE_SCHEMA as db,
|
|
1667
1765
|
TABLE_NAME as t1,
|
|
@@ -1676,19 +1774,30 @@ const getRelationsOneToMany = _.memoize(async function getRelationsOneToMany(tab
|
|
|
1676
1774
|
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
1677
1775
|
AND (REFERENCED_TABLE_NAME = ?);
|
|
1678
1776
|
`;
|
|
1679
|
-
|
|
1777
|
+
rs = await query(sql, [table]);
|
|
1778
|
+
}
|
|
1779
|
+
else if (dialect === "postgresql") {
|
|
1780
|
+
rs = await query(`SELECT kcu.table_name AS t1, kcu.column_name AS "t1Field", ccu.column_name AS "t2Field"
|
|
1781
|
+
FROM information_schema.key_column_usage kcu
|
|
1782
|
+
JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name AND kcu.table_schema = rc.constraint_schema
|
|
1783
|
+
JOIN information_schema.constraint_column_usage ccu ON rc.unique_constraint_name = ccu.constraint_name AND rc.unique_constraint_schema = ccu.table_schema
|
|
1784
|
+
WHERE kcu.table_schema = 'public' AND ccu.table_name = $1
|
|
1785
|
+
ORDER BY kcu.table_name, kcu.column_name`, [table]);
|
|
1786
|
+
}
|
|
1787
|
+
else {
|
|
1788
|
+
throw new Error("Unsupported dialect: " + dialect);
|
|
1789
|
+
}
|
|
1680
1790
|
const xs = await Promise.all(_.uniqWith(_.isEqual, rs.map(async (v) => {
|
|
1681
1791
|
return {
|
|
1682
1792
|
table: table,
|
|
1683
1793
|
foreignKey: v.t2Field,
|
|
1684
1794
|
referencedTable: v.t1,
|
|
1685
1795
|
referencedKey: v.t1Field,
|
|
1686
|
-
// TODO? I think this is right, since it's one-to-many, so a list
|
|
1687
1796
|
nullable: false
|
|
1688
1797
|
};
|
|
1689
1798
|
})));
|
|
1690
|
-
return _.sortBy((x) => x.referencedKey,
|
|
1691
|
-
});
|
|
1799
|
+
return _.sortBy([(x) => x.referencedTable, (x) => x.referencedKey, (x) => x.foreignKey], xs);
|
|
1800
|
+
}, (table) => getCtx().runId + ":" + table);
|
|
1692
1801
|
async function getPrimaryColumn(table) {
|
|
1693
1802
|
const tableMeta = await getTableMeta(table);
|
|
1694
1803
|
const columns = tableMeta.filter((x) => x.Key === "PRI");
|
|
@@ -1698,7 +1807,7 @@ async function getPrimaryColumn(table) {
|
|
|
1698
1807
|
const column = columns[0];
|
|
1699
1808
|
return {
|
|
1700
1809
|
name: column.Field,
|
|
1701
|
-
type: getBaseJSONType(column.Type),
|
|
1810
|
+
type: getBaseJSONType(column.Type, getCtx().dialect),
|
|
1702
1811
|
nullable: column.Null === "YES"
|
|
1703
1812
|
};
|
|
1704
1813
|
}
|
|
@@ -1710,7 +1819,7 @@ async function getUniqueColumns(table, specialCaseUuidColumn) {
|
|
|
1710
1819
|
(specialCaseUuidColumn && x.Field === "uuid"))
|
|
1711
1820
|
.map((x) => ({
|
|
1712
1821
|
name: x.Field,
|
|
1713
|
-
type: getBaseJSONType(x.Type),
|
|
1822
|
+
type: getBaseJSONType(x.Type, getCtx().dialect),
|
|
1714
1823
|
nullable: x.Null === "YES"
|
|
1715
1824
|
}));
|
|
1716
1825
|
}
|
|
@@ -1726,23 +1835,150 @@ async function getUuidColumn(table) {
|
|
|
1726
1835
|
nullable: column.Null === "YES"
|
|
1727
1836
|
};
|
|
1728
1837
|
}
|
|
1729
|
-
const
|
|
1838
|
+
const getPgEnumDefinition = (0, memoize_1.default)(async function getPgEnumDefinition(udtSchema, udtName) {
|
|
1839
|
+
const { dialect, query } = getCtx();
|
|
1840
|
+
if (dialect !== "postgresql")
|
|
1841
|
+
return null;
|
|
1842
|
+
const rows = await query(`SELECT e.enumlabel FROM pg_enum e
|
|
1843
|
+
JOIN pg_type t ON e.enumtypid = t.oid
|
|
1844
|
+
JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid
|
|
1845
|
+
WHERE t.typname = $2 AND n.nspname = $1
|
|
1846
|
+
ORDER BY e.enumsortorder`, [udtSchema, udtName]);
|
|
1847
|
+
if (rows.length === 0)
|
|
1848
|
+
return null;
|
|
1849
|
+
const labels = rows.map((r) => String(r.enumlabel).replace(/'/g, "''"));
|
|
1850
|
+
return "enum('" + labels.join("', '") + "')";
|
|
1851
|
+
}, (udtSchema, udtName) => getCtx().runId + ":" + udtSchema + ":" + udtName);
|
|
1852
|
+
const getTableMeta = (0, memoize_1.default)(async function getTableMeta(table) {
|
|
1853
|
+
const ctx = getCtx();
|
|
1854
|
+
const { dialect, query } = ctx;
|
|
1855
|
+
ctx.log.debug({ table }, "getTableMeta() fetching");
|
|
1730
1856
|
if (dialect === "mysql") {
|
|
1731
1857
|
return query("DESCRIBE ??", [table]).then((xs) => _.sortBy((x) => x.Field, xs));
|
|
1732
1858
|
}
|
|
1859
|
+
if (dialect === "postgresql") {
|
|
1860
|
+
const columns = await query(`SELECT column_name AS "Field", data_type, udt_schema, udt_name, character_maximum_length AS char_max, is_nullable, column_default AS "Default"
|
|
1861
|
+
FROM information_schema.columns
|
|
1862
|
+
WHERE table_schema = 'public' AND table_name = $1
|
|
1863
|
+
ORDER BY ordinal_position`, [table]);
|
|
1864
|
+
const keyInfo = await query(`SELECT a.attname AS col, 'PRI' AS key_type
|
|
1865
|
+
FROM pg_index i
|
|
1866
|
+
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) AND NOT a.attisdropped AND a.attnum > 0
|
|
1867
|
+
JOIN pg_class c ON c.oid = i.indrelid
|
|
1868
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1869
|
+
WHERE n.nspname = 'public' AND c.relname = $1 AND i.indisprimary
|
|
1870
|
+
UNION ALL
|
|
1871
|
+
SELECT kcu.column_name AS col, 'UNI' AS key_type
|
|
1872
|
+
FROM information_schema.table_constraints tc
|
|
1873
|
+
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
|
1874
|
+
WHERE tc.table_schema = 'public' AND tc.table_name = $1 AND tc.constraint_type = 'UNIQUE'
|
|
1875
|
+
UNION ALL
|
|
1876
|
+
SELECT kcu.column_name AS col, 'MUL' AS key_type
|
|
1877
|
+
FROM information_schema.table_constraints tc
|
|
1878
|
+
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
|
1879
|
+
WHERE tc.table_schema = 'public' AND tc.table_name = $1 AND tc.constraint_type = 'FOREIGN KEY'`, [table]);
|
|
1880
|
+
const keyMap = new Map();
|
|
1881
|
+
for (const k of keyInfo) {
|
|
1882
|
+
if (!keyMap.has(k.col) || k.key_type === "PRI")
|
|
1883
|
+
keyMap.set(k.col, k.key_type);
|
|
1884
|
+
}
|
|
1885
|
+
const udtKeys = [
|
|
1886
|
+
...new Map(columns
|
|
1887
|
+
.filter((c) => c.data_type === "USER-DEFINED" &&
|
|
1888
|
+
c.udt_schema != null &&
|
|
1889
|
+
c.udt_name != null)
|
|
1890
|
+
.map((c) => [`${c.udt_schema}.${c.udt_name}`, c])).keys()
|
|
1891
|
+
];
|
|
1892
|
+
const udtPairs = udtKeys.map((k) => {
|
|
1893
|
+
const [s, n] = k.split(".", 2);
|
|
1894
|
+
return [s, n];
|
|
1895
|
+
});
|
|
1896
|
+
const enumDefs = await Promise.all(udtPairs.map(([schema, name]) => getPgEnumDefinition(schema, name)));
|
|
1897
|
+
const enumMap = new Map(udtKeys.map((k, i) => [k, enumDefs[i] ?? "varchar(255)"]));
|
|
1898
|
+
const cols = columns.map((c) => {
|
|
1899
|
+
let type;
|
|
1900
|
+
if (c.data_type === "USER-DEFINED" &&
|
|
1901
|
+
c.udt_schema != null &&
|
|
1902
|
+
c.udt_name != null) {
|
|
1903
|
+
const enumKey = `${c.udt_schema}.${c.udt_name}`;
|
|
1904
|
+
type = enumMap.get(enumKey) ?? "character varying(255)";
|
|
1905
|
+
}
|
|
1906
|
+
else {
|
|
1907
|
+
type = c.data_type;
|
|
1908
|
+
if ((c.data_type === "character varying" || c.data_type === "character") &&
|
|
1909
|
+
c.char_max != null) {
|
|
1910
|
+
type += "(" + c.char_max + ")";
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
return {
|
|
1914
|
+
Field: c.Field,
|
|
1915
|
+
Type: type,
|
|
1916
|
+
Null: c.is_nullable === "YES" ? "YES" : "NO",
|
|
1917
|
+
Key: keyMap.get(c.Field) ?? "",
|
|
1918
|
+
// Preserve `null` when there is no default so that
|
|
1919
|
+
// required-field detection (via `hasDefault`) works
|
|
1920
|
+
// consistently with the MySQL `DESCRIBE` output.
|
|
1921
|
+
Default: c.Default,
|
|
1922
|
+
...(c.data_type === "USER-DEFINED" &&
|
|
1923
|
+
c.udt_schema != null &&
|
|
1924
|
+
c.udt_name != null
|
|
1925
|
+
? { PgType: c.udt_name }
|
|
1926
|
+
: {})
|
|
1927
|
+
};
|
|
1928
|
+
});
|
|
1929
|
+
return _.sortBy((c) => c.Field, cols);
|
|
1930
|
+
}
|
|
1733
1931
|
throw new Error("Unsupported dialect: " + dialect);
|
|
1734
|
-
});
|
|
1735
|
-
function getShowCreateTable(table) {
|
|
1932
|
+
}, (table) => getCtx().runId + ":" + table);
|
|
1933
|
+
async function getShowCreateTable(table) {
|
|
1934
|
+
const { dialect, query } = getCtx();
|
|
1736
1935
|
if (dialect === "mysql") {
|
|
1737
1936
|
return query("SHOW CREATE TABLE ??", [table]).then((xs) => xs[0]["Create Table"]
|
|
1738
1937
|
// https://github.com/bradzacher/mysqldump/blob/66839a57e572a07c046b0ba98753f30a7026cbd8/src/getSchemaDump.ts#L65
|
|
1739
1938
|
.replace(/AUTO_INCREMENT\s*=\s*\d+ /g, ""));
|
|
1740
1939
|
}
|
|
1940
|
+
if (dialect === "postgresql") {
|
|
1941
|
+
const [tableMeta, relations] = await Promise.all([
|
|
1942
|
+
getTableMeta(table),
|
|
1943
|
+
getRelationsManyToOne(table)
|
|
1944
|
+
]);
|
|
1945
|
+
const refByFk = new Map(relations.map((r) => [r.foreignKey, r]));
|
|
1946
|
+
const columnDefs = tableMeta.map((c) => {
|
|
1947
|
+
const isSerialPk = c.Key === "PRI" &&
|
|
1948
|
+
c.Default != null &&
|
|
1949
|
+
c.Default !== "" &&
|
|
1950
|
+
/nextval\s*\(/i.test(c.Default);
|
|
1951
|
+
if (isSerialPk) {
|
|
1952
|
+
const baseType = (c.PgType ?? c.Type).toLowerCase();
|
|
1953
|
+
const serialType = baseType === "bigint" || baseType === "int8"
|
|
1954
|
+
? "BIGSERIAL"
|
|
1955
|
+
: baseType === "smallint" || baseType === "int2"
|
|
1956
|
+
? "SMALLSERIAL"
|
|
1957
|
+
: "SERIAL";
|
|
1958
|
+
return `"${c.Field.replace(/"/g, '""')}" ${serialType} PRIMARY KEY`;
|
|
1959
|
+
}
|
|
1960
|
+
const pgType = c.PgType ?? c.Type;
|
|
1961
|
+
let def = `"${c.Field.replace(/"/g, '""')}" ${pgType} ${c.Null === "YES" ? "NULL" : "NOT NULL"}`;
|
|
1962
|
+
if (c.Default != null && c.Default !== "") {
|
|
1963
|
+
def += ` DEFAULT ${c.Default}`;
|
|
1964
|
+
}
|
|
1965
|
+
if (c.Key === "PRI")
|
|
1966
|
+
def += " PRIMARY KEY";
|
|
1967
|
+
if (c.Key === "UNI")
|
|
1968
|
+
def += " UNIQUE";
|
|
1969
|
+
const ref = refByFk.get(c.Field);
|
|
1970
|
+
if (ref != null) {
|
|
1971
|
+
def += ` REFERENCES "${ref.referencedTable.replace(/"/g, '""')}" ("${ref.referencedKey.replace(/"/g, '""')}")`;
|
|
1972
|
+
}
|
|
1973
|
+
return def;
|
|
1974
|
+
});
|
|
1975
|
+
return `CREATE TABLE "${table.replace(/"/g, '""')}" (\n ${columnDefs.join(",\n ")}\n);`;
|
|
1976
|
+
}
|
|
1741
1977
|
return Promise.resolve(null);
|
|
1742
1978
|
}
|
|
1743
1979
|
function getJSONSchemaObjProperties(tableMeta) {
|
|
1744
1980
|
return tableMeta.reduce((acc, m) => {
|
|
1745
|
-
const baseType = getBaseJSONType(m.Type);
|
|
1981
|
+
const baseType = getBaseJSONType(m.Type, getCtx().dialect);
|
|
1746
1982
|
const format = getPropertyFormat(m.Type);
|
|
1747
1983
|
const nullable = m.Null === "YES";
|
|
1748
1984
|
const isEnum = m.Type.startsWith("enum");
|
|
@@ -1778,7 +2014,29 @@ function getJSONTypes(baseType, nullable) {
|
|
|
1778
2014
|
return baseType;
|
|
1779
2015
|
}
|
|
1780
2016
|
// https://github.com/mysqljs/mysql#type-casting
|
|
1781
|
-
function getBaseJSONType(sqlType) {
|
|
2017
|
+
function getBaseJSONType(sqlType, dialect) {
|
|
2018
|
+
if (dialect === "postgresql") {
|
|
2019
|
+
if (sqlType === "boolean" || sqlType === "bool")
|
|
2020
|
+
return "boolean";
|
|
2021
|
+
if (["smallint", "int2", "integer", "int4", "bigint", "int8"].includes(sqlType)) {
|
|
2022
|
+
return "integer";
|
|
2023
|
+
}
|
|
2024
|
+
if (["real", "float4", "double precision", "float8"].includes(sqlType) ||
|
|
2025
|
+
sqlType.startsWith("numeric") ||
|
|
2026
|
+
sqlType.startsWith("decimal")) {
|
|
2027
|
+
return "number";
|
|
2028
|
+
}
|
|
2029
|
+
if (["text", "uuid", "json", "jsonb"].includes(sqlType) ||
|
|
2030
|
+
sqlType === "date" ||
|
|
2031
|
+
sqlType === "time" ||
|
|
2032
|
+
sqlType.startsWith("timestamp") ||
|
|
2033
|
+
sqlType.startsWith("character varying") ||
|
|
2034
|
+
sqlType.startsWith("character") ||
|
|
2035
|
+
sqlType.startsWith("enum")) {
|
|
2036
|
+
return "string";
|
|
2037
|
+
}
|
|
2038
|
+
throw new Error("Unable to map to JSON type: " + sqlType);
|
|
2039
|
+
}
|
|
1782
2040
|
if (
|
|
1783
2041
|
// TODO?
|
|
1784
2042
|
sqlType === "tinyint(1)" ||
|
|
@@ -1863,7 +2121,10 @@ function getPropertyEnum(sqlType) {
|
|
|
1863
2121
|
return c;
|
|
1864
2122
|
}
|
|
1865
2123
|
function getPropertyFormat(sqlType) {
|
|
1866
|
-
if (sqlType === "datetime" ||
|
|
2124
|
+
if (sqlType === "datetime" ||
|
|
2125
|
+
sqlType === "datetime2" ||
|
|
2126
|
+
sqlType === "timestamp" ||
|
|
2127
|
+
sqlType.startsWith("timestamp")) {
|
|
1867
2128
|
// TODO: not sure this is correct for `timestamp`
|
|
1868
2129
|
return "date-time";
|
|
1869
2130
|
}
|
|
@@ -1881,12 +2142,18 @@ function getPropertyFormat(sqlType) {
|
|
|
1881
2142
|
}
|
|
1882
2143
|
return undefined;
|
|
1883
2144
|
}
|
|
1884
|
-
async function getTableNames() {
|
|
2145
|
+
const getTableNames = (0, memoize_1.default)(async function getTableNames() {
|
|
2146
|
+
const { dialect, query } = getCtx();
|
|
1885
2147
|
if (dialect === "mysql") {
|
|
1886
2148
|
return query("SHOW TABLES").then((xs) => xs.flatMap((x) => Object.values(x)).sort());
|
|
1887
2149
|
}
|
|
2150
|
+
if (dialect === "postgresql") {
|
|
2151
|
+
return query(`SELECT table_name FROM information_schema.tables
|
|
2152
|
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
|
|
2153
|
+
ORDER BY table_name`).then((rows) => rows.map((r) => r.table_name));
|
|
2154
|
+
}
|
|
1888
2155
|
throw new Error("Unsupported dialect: " + dialect);
|
|
1889
|
-
}
|
|
2156
|
+
}, () => getCtx().runId);
|
|
1890
2157
|
function getMysql2sqliteSrc() {
|
|
1891
2158
|
return `#!/usr/bin/awk -f
|
|
1892
2159
|
|