@andymic/pigeon 1.2.0 → 1.4.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/bin/pigeon.js +10 -10
- package/package.json +3 -3
- package/src/cli.js +54 -12
- package/src/config.js +4 -16
- package/src/index.js +283 -194
- package/src/maps.js +60 -5
- package/src/pgAdmin.js +117 -0
- package/src/utils.js +20 -10
package/bin/pigeon.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/*
|
|
3
|
-
* Copyright (c) 2024 Andreas Michael
|
|
4
|
-
* This software is under the Apache 2.0 License
|
|
5
|
-
*/
|
|
6
2
|
import { cli, run } from "../src/cli.js";
|
|
3
|
+
import { PigeonError } from "../src/index.js";
|
|
7
4
|
try {
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
10
|
-
|
|
5
|
+
const result = await run(cli.flags);
|
|
6
|
+
if (result instanceof PigeonError) {
|
|
7
|
+
if (result.message !== "")
|
|
8
|
+
console.log(result.message);
|
|
9
|
+
console.error(result.error);
|
|
10
|
+
process.exit(result.exitCode);
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
console.
|
|
12
|
+
else {
|
|
13
|
+
console.log("Generation Completed Successfully");
|
|
14
|
+
process.exit(0);
|
|
14
15
|
}
|
|
15
|
-
process.exit(exitCode);
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
18
18
|
console.error(error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andymic/pigeon",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"author": "Andreas Michael <ateasm03@gmail.com>",
|
|
5
5
|
"description": "Pigeon is a TypeScript-based tool for generating TypeScript classes and methods from PostgreSQL database schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"meow": "^13.2.0",
|
|
35
|
-
"pg": "^8.
|
|
35
|
+
"pg": "^8.15.6",
|
|
36
36
|
"prompt-sync": "^4.2.0"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "ec33791a735792a340bfa41c1f66e9a08534ffa6"
|
|
39
39
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2024 Andreas Michael
|
|
3
|
-
* This software is under the Apache 2.0 License
|
|
4
|
-
*/
|
|
5
1
|
import meow from "meow";
|
|
6
2
|
import { createConfig } from "./config.js";
|
|
3
|
+
import { Database, deleteDir, enumsQuery, guided, PigeonError, queryDB, runGeneration } from "./index.js";
|
|
7
4
|
import * as path from "node:path";
|
|
8
|
-
import { deleteDir, guided, runPigeon } from "./index.js";
|
|
9
5
|
import fs from "node:fs";
|
|
6
|
+
import { getRelationshipsAndTables, tableProcessing } from "./pgAdmin.js";
|
|
10
7
|
export const cli = meow(`
|
|
11
8
|
Usage
|
|
12
9
|
$ pigeon [options]
|
|
@@ -17,12 +14,15 @@ export const cli = meow(`
|
|
|
17
14
|
--force overwrites already existing files
|
|
18
15
|
--output [path:String] output directory for the generated files.
|
|
19
16
|
--config [path:String] path to .pigeon.json config file.
|
|
17
|
+
--pgAdmin [path:String] path to the pgAdmin ERD file.
|
|
18
|
+
--offline (only with pgAdmin) does not contact the database
|
|
20
19
|
|
|
21
20
|
Examples
|
|
22
21
|
$ pigeon --init
|
|
23
22
|
$ pigeon --output C:/Users/User/Documents/Project
|
|
24
23
|
$ pigeon --output ./generatedFiles --force
|
|
25
24
|
$ pigeon --config ./customPigeonConfig.json
|
|
25
|
+
$ pigeon --pgAdmin C:/Users/User/Documents/Project/ERD.json --offline
|
|
26
26
|
|
|
27
27
|
Exit Status
|
|
28
28
|
Pigeon returns the following codes:
|
|
@@ -57,6 +57,13 @@ export const cli = meow(`
|
|
|
57
57
|
type: "string",
|
|
58
58
|
default: process.cwd(),
|
|
59
59
|
},
|
|
60
|
+
pgAdmin: {
|
|
61
|
+
type: "string",
|
|
62
|
+
},
|
|
63
|
+
offline: {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
default: false,
|
|
66
|
+
}
|
|
60
67
|
},
|
|
61
68
|
autoHelp: true,
|
|
62
69
|
autoVersion: true,
|
|
@@ -67,16 +74,51 @@ export async function run(flags) {
|
|
|
67
74
|
return createConfig(flags.cwd);
|
|
68
75
|
if (flags.force)
|
|
69
76
|
deleteDir(flags.output);
|
|
77
|
+
if (flags.pgAdmin) {
|
|
78
|
+
let enums;
|
|
79
|
+
let database;
|
|
80
|
+
if (!flags.offline) {
|
|
81
|
+
if (!fs.existsSync(path.join(process.cwd(), ".pigeon.json")))
|
|
82
|
+
return new PigeonError(1, "", new Error("The configuration file does not exist. Generate one using the \"pigeon --init\" command"));
|
|
83
|
+
const params = JSON.parse(fs.readFileSync(flags.config).toString());
|
|
84
|
+
database = new Database(params.host, params.port, params.database, params.username, params.password);
|
|
85
|
+
enums = await enumsQuery(database);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
enums = [];
|
|
89
|
+
database = new Database("localhost", "5432", "database", "username", "password");
|
|
90
|
+
}
|
|
91
|
+
if (enums instanceof PigeonError)
|
|
92
|
+
return enums;
|
|
93
|
+
if (!fs.existsSync(flags.pgAdmin))
|
|
94
|
+
return new PigeonError(1, "", new Error("The pgAdmin ERD file specified does not exist."));
|
|
95
|
+
const file = fs.readFileSync(flags.pgAdmin).toString();
|
|
96
|
+
let tables;
|
|
97
|
+
try {
|
|
98
|
+
tables = getRelationshipsAndTables(file).tables;
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return e;
|
|
102
|
+
}
|
|
103
|
+
const generationResult = runGeneration(flags.output, database, tableProcessing(tables), enums);
|
|
104
|
+
if (generationResult instanceof PigeonError)
|
|
105
|
+
return generationResult;
|
|
106
|
+
}
|
|
70
107
|
if (flags.guided) {
|
|
71
108
|
const params = guided();
|
|
72
|
-
|
|
109
|
+
const database = new Database(params.host, String(params.port), params.db, params.user, params.pass);
|
|
110
|
+
const result = await queryDB(database);
|
|
111
|
+
if (result instanceof PigeonError)
|
|
112
|
+
return result;
|
|
73
113
|
}
|
|
74
114
|
if (!fs.existsSync(path.join(process.cwd(), ".pigeon.json")))
|
|
75
|
-
return
|
|
76
|
-
exitCode: 1,
|
|
77
|
-
message: null,
|
|
78
|
-
error: new Error("The configuration file does not exist. Generate one using the \"pigeon --init\" command"),
|
|
79
|
-
};
|
|
115
|
+
return new PigeonError(1, "", new Error("The configuration file does not exist. Generate one using the \"pigeon --init\" command"));
|
|
80
116
|
const params = JSON.parse(fs.readFileSync(flags.config).toString());
|
|
81
|
-
|
|
117
|
+
const database = new Database(params.host, params.port, params.database, params.username, params.password);
|
|
118
|
+
const queryResult = await queryDB(database);
|
|
119
|
+
if (queryResult instanceof PigeonError)
|
|
120
|
+
return queryResult;
|
|
121
|
+
const generationResult = runGeneration(flags.output, database, queryResult.tables, queryResult.enums);
|
|
122
|
+
if (generationResult instanceof PigeonError)
|
|
123
|
+
return generationResult;
|
|
82
124
|
}
|
package/src/config.js
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
* Copyright (c) 2024 Andreas Michael
|
|
3
|
-
* This software is under the Apache 2.0 License
|
|
4
|
-
*/
|
|
1
|
+
import { PigeonError } from "./index.js";
|
|
5
2
|
import fs from "node:fs";
|
|
6
3
|
import * as path from "node:path";
|
|
7
4
|
export function createConfig(dir) {
|
|
8
|
-
if (fs.existsSync(path.join(dir, ".pigeon.json")))
|
|
9
|
-
return
|
|
10
|
-
exitCode: 1,
|
|
11
|
-
message: null,
|
|
12
|
-
error: new Error("A Pigeon configuration file already exists."),
|
|
13
|
-
};
|
|
14
|
-
}
|
|
5
|
+
if (fs.existsSync(path.join(dir, ".pigeon.json")))
|
|
6
|
+
return new PigeonError(1, "", new Error("A Pigeon configuration file already exists."));
|
|
15
7
|
fs.writeFileSync(path.join(dir, ".pigeon.json"), "{\n\t\"host\": \"localhost\",\n\t\"port\": 5432,\n\t\"database\": \"postgres\",\n\t\"username\": \"postgres\",\n\t\"password\": \"xxx\"\n}");
|
|
16
|
-
return
|
|
17
|
-
exitCode: 0,
|
|
18
|
-
message: "Configuration file successfully created!",
|
|
19
|
-
error: null,
|
|
20
|
-
};
|
|
8
|
+
return new PigeonError(0, "Configuration file successfully created!", null);
|
|
21
9
|
}
|
package/src/index.js
CHANGED
|
@@ -1,33 +1,108 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* This software is under the Apache 2.0 License
|
|
4
|
-
*/
|
|
5
|
-
import { arrayMaker, getCombinations, nameBeautifier, queryMaker, runQuery, singularize, sleep, tabsInserter } from "./utils.js";
|
|
6
|
-
import { types } from "./maps.js";
|
|
1
|
+
import { arrayMaker, consoleMessage, getCombinations, getType, nameBeautifier, queryMaker, runQuery, singularize, sleep, tabsInserter } from "./utils.js";
|
|
2
|
+
import prompt from "prompt-sync";
|
|
7
3
|
import fs from "node:fs";
|
|
8
4
|
import * as path from "node:path";
|
|
9
|
-
|
|
5
|
+
export class PigeonError {
|
|
6
|
+
exitCode;
|
|
7
|
+
message;
|
|
8
|
+
error;
|
|
9
|
+
constructor(exitCode, message, error) {
|
|
10
|
+
this.exitCode = exitCode;
|
|
11
|
+
this.message = message;
|
|
12
|
+
this.error = error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class Database {
|
|
16
|
+
host;
|
|
17
|
+
port;
|
|
18
|
+
db;
|
|
19
|
+
user;
|
|
20
|
+
pass;
|
|
21
|
+
constructor(host, port, db, user, pass) {
|
|
22
|
+
this.host = host;
|
|
23
|
+
this.port = port;
|
|
24
|
+
this.db = db;
|
|
25
|
+
this.user = user;
|
|
26
|
+
this.pass = pass;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class Table {
|
|
30
|
+
table_schema;
|
|
31
|
+
table_name;
|
|
32
|
+
columns = [];
|
|
33
|
+
primaryKey;
|
|
34
|
+
foreignKeys;
|
|
35
|
+
unique;
|
|
36
|
+
constructor(table_schema, table_name, columns, primaryKey, foreignKeys, unique) {
|
|
37
|
+
this.table_schema = table_schema;
|
|
38
|
+
this.table_name = table_name;
|
|
39
|
+
this.columns = columns;
|
|
40
|
+
this.primaryKey = primaryKey;
|
|
41
|
+
this.foreignKeys = foreignKeys;
|
|
42
|
+
this.unique = unique;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export class Enum {
|
|
46
|
+
name;
|
|
47
|
+
labels;
|
|
48
|
+
constructor(name, labels) {
|
|
49
|
+
this.name = name;
|
|
50
|
+
this.labels = labels;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class ColumnQueryRow {
|
|
54
|
+
column_name;
|
|
55
|
+
ordinal_position;
|
|
56
|
+
column_default;
|
|
57
|
+
is_nullable;
|
|
58
|
+
data_type;
|
|
59
|
+
udt_name;
|
|
60
|
+
is_identity;
|
|
61
|
+
identity_generation;
|
|
62
|
+
constructor(column_name, ordinal_position, column_default, is_nullable, data_type, udt_name, is_identity, identity_generation) {
|
|
63
|
+
this.column_name = column_name;
|
|
64
|
+
this.ordinal_position = ordinal_position;
|
|
65
|
+
this.column_default = column_default;
|
|
66
|
+
this.is_nullable = is_nullable;
|
|
67
|
+
this.data_type = data_type;
|
|
68
|
+
this.udt_name = udt_name;
|
|
69
|
+
this.is_identity = is_identity;
|
|
70
|
+
this.identity_generation = identity_generation;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export class PrimaryKeyQueryRow {
|
|
74
|
+
column_name;
|
|
75
|
+
constructor(column_name) {
|
|
76
|
+
this.column_name = column_name;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export class ForeignKeyQueryRow {
|
|
80
|
+
local_schema;
|
|
81
|
+
local_table;
|
|
82
|
+
local_column;
|
|
83
|
+
foreign_schema;
|
|
84
|
+
foreign_table;
|
|
85
|
+
foreign_column;
|
|
86
|
+
constructor(local_schema, local_table, local_column, foreign_schema, foreign_table, foreign_column) {
|
|
87
|
+
this.local_schema = local_schema;
|
|
88
|
+
this.local_table = local_table;
|
|
89
|
+
this.local_column = local_column;
|
|
90
|
+
this.foreign_schema = foreign_schema;
|
|
91
|
+
this.foreign_table = foreign_table;
|
|
92
|
+
this.foreign_column = foreign_column;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export class UniqueQueryRow {
|
|
96
|
+
columns;
|
|
97
|
+
constructor(columns) {
|
|
98
|
+
this.columns = columns;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
10
101
|
function createDir(dirPath) {
|
|
11
102
|
if (fs.existsSync(dirPath))
|
|
12
|
-
return
|
|
13
|
-
exitCode: 1,
|
|
14
|
-
message: null,
|
|
15
|
-
error: new Error("Generation directory already exists. Add the --force flag if you want to overwrite it.")
|
|
16
|
-
};
|
|
103
|
+
return new PigeonError(1, "", new Error("Generation directory already exists. Add the --force flag if you want to overwrite it."));
|
|
17
104
|
else
|
|
18
|
-
fs.
|
|
19
|
-
if (err)
|
|
20
|
-
return {
|
|
21
|
-
exitCode: 1,
|
|
22
|
-
message: null,
|
|
23
|
-
error: err
|
|
24
|
-
};
|
|
25
|
-
});
|
|
26
|
-
return {
|
|
27
|
-
exitCode: 0,
|
|
28
|
-
message: null,
|
|
29
|
-
error: null
|
|
30
|
-
};
|
|
105
|
+
fs.mkdirSync(dirPath);
|
|
31
106
|
}
|
|
32
107
|
export function deleteDir(dirPath) {
|
|
33
108
|
if (fs.existsSync(dirPath)) {
|
|
@@ -75,94 +150,71 @@ export function guided() {
|
|
|
75
150
|
const pass = input("Database Password: ");
|
|
76
151
|
return { host, port, db, user, pass };
|
|
77
152
|
}
|
|
78
|
-
export async function
|
|
79
|
-
const dirResult = createDir(dir);
|
|
80
|
-
if (dirResult.exitCode !== 0)
|
|
81
|
-
return dirResult;
|
|
82
|
-
const tableQuery = await runQuery(`SELECT table_schema, table_name
|
|
83
|
-
FROM information_schema.tables
|
|
84
|
-
WHERE table_type = 'BASE TABLE'
|
|
85
|
-
AND table_schema NOT IN
|
|
86
|
-
('pg_catalog', 'information_schema');`, [], host, port, db, user, pass);
|
|
87
|
-
if (typeof tableQuery === "undefined")
|
|
88
|
-
return {
|
|
89
|
-
exitCode: 1,
|
|
90
|
-
message: null,
|
|
91
|
-
error: new Error("An SQL error has occurred.")
|
|
92
|
-
};
|
|
93
|
-
let schemas = [];
|
|
94
|
-
for (const table of tableQuery.rows) {
|
|
95
|
-
if (schemas.includes(table.table_schema))
|
|
96
|
-
continue;
|
|
97
|
-
schemas.push(table.table_schema);
|
|
98
|
-
}
|
|
99
|
-
for (const schema of schemas)
|
|
100
|
-
createDir(path.join(dir, schema));
|
|
153
|
+
export async function enumsQuery(db) {
|
|
101
154
|
const customTypeQuery = await runQuery(`SELECT t.oid, t.typname
|
|
102
155
|
FROM pg_type t
|
|
103
156
|
WHERE (t.typrelid = 0 OR t.typrelid IN (SELECT oid FROM pg_class WHERE relkind = 'c'))
|
|
104
157
|
AND t.typelem = 0
|
|
105
158
|
AND t.typnamespace NOT IN
|
|
106
|
-
(SELECT oid FROM pg_namespace WHERE nspname IN ('pg_catalog', 'information_schema'));`, [],
|
|
159
|
+
(SELECT oid FROM pg_namespace WHERE nspname IN ('pg_catalog', 'information_schema'));`, [], db);
|
|
107
160
|
if (typeof customTypeQuery === "undefined")
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
message: null,
|
|
111
|
-
error: new Error("An SQL error has occurred.")
|
|
112
|
-
};
|
|
113
|
-
const customTypes = [];
|
|
161
|
+
return new PigeonError(1, "", new Error("An SQL error has occurred."));
|
|
162
|
+
const enums = [];
|
|
114
163
|
for (const type of customTypeQuery.rows) {
|
|
115
164
|
const enumQuery = await runQuery(`SELECT enumlabel
|
|
116
165
|
FROM pg_enum
|
|
117
166
|
WHERE enumtypid = $1::oid
|
|
118
|
-
ORDER BY enumsortorder;`, [type.oid],
|
|
167
|
+
ORDER BY enumsortorder;`, [type.oid], db);
|
|
119
168
|
if (typeof enumQuery === "undefined")
|
|
120
|
-
return
|
|
121
|
-
exitCode: 1,
|
|
122
|
-
message: null,
|
|
123
|
-
error: new Error("An SQL error has occurred.")
|
|
124
|
-
};
|
|
169
|
+
return new PigeonError(1, "", new Error("An SQL error has occurred."));
|
|
125
170
|
let labels = [];
|
|
126
171
|
for (const enumLabel of enumQuery.rows)
|
|
127
172
|
labels.push(enumLabel.enumlabel);
|
|
128
|
-
|
|
129
|
-
name: type.typname,
|
|
130
|
-
labels: labels
|
|
131
|
-
});
|
|
173
|
+
enums.push(new Enum(type.typname, labels));
|
|
132
174
|
}
|
|
175
|
+
return enums;
|
|
176
|
+
}
|
|
177
|
+
export async function queryDB(db) {
|
|
178
|
+
const tableQuery = await runQuery(`SELECT table_schema, table_name
|
|
179
|
+
FROM information_schema.tables
|
|
180
|
+
WHERE table_type = 'BASE TABLE'
|
|
181
|
+
AND table_schema NOT IN
|
|
182
|
+
('pg_catalog', 'information_schema');`, [], db);
|
|
183
|
+
if (typeof tableQuery === "undefined")
|
|
184
|
+
return new PigeonError(1, "", new Error("An SQL error has occurred."));
|
|
185
|
+
const enums = await enumsQuery(db);
|
|
186
|
+
if (enums instanceof PigeonError)
|
|
187
|
+
return enums;
|
|
188
|
+
const tables = [];
|
|
133
189
|
for (const table of tableQuery.rows) {
|
|
134
|
-
const columnQuery = await runQuery(`SELECT
|
|
190
|
+
const columnQuery = await runQuery(`SELECT column_name,
|
|
191
|
+
ordinal_position,
|
|
192
|
+
column_default,
|
|
193
|
+
is_nullable,
|
|
194
|
+
data_type,
|
|
195
|
+
udt_name,
|
|
196
|
+
is_identity,
|
|
197
|
+
identity_generation
|
|
135
198
|
FROM information_schema.columns
|
|
136
199
|
WHERE table_name = $1::varchar
|
|
137
|
-
AND table_schema = $2::varchar;`, [table.table_name, table.table_schema],
|
|
200
|
+
AND table_schema = $2::varchar;`, [table.table_name, table.table_schema], db);
|
|
138
201
|
if (typeof columnQuery === "undefined")
|
|
139
|
-
return
|
|
140
|
-
exitCode: 1,
|
|
141
|
-
message: null,
|
|
142
|
-
error: new Error("An SQL error has occurred.")
|
|
143
|
-
};
|
|
202
|
+
return new PigeonError(1, "", new Error("An SQL error has occurred."));
|
|
144
203
|
const pKeyQuery = await runQuery(`SELECT ku.column_name
|
|
145
204
|
FROM information_schema.table_constraints AS tc
|
|
146
205
|
INNER JOIN information_schema.key_column_usage AS ku
|
|
147
206
|
ON tc.constraint_type = 'PRIMARY KEY'
|
|
148
207
|
AND tc.constraint_name = ku.constraint_name
|
|
149
208
|
WHERE tc.table_schema = $1::varchar
|
|
150
|
-
AND tc.table_name = $2::varchar;`, [table.table_schema, table.table_name],
|
|
209
|
+
AND tc.table_name = $2::varchar;`, [table.table_schema, table.table_name], db);
|
|
151
210
|
if (typeof pKeyQuery === "undefined")
|
|
152
|
-
return
|
|
153
|
-
exitCode: 1,
|
|
154
|
-
message: null,
|
|
155
|
-
error: new Error("An SQL error has occurred.")
|
|
156
|
-
};
|
|
157
|
-
let pKeys = [];
|
|
158
|
-
for (let pKey of pKeyQuery.rows)
|
|
159
|
-
pKeys.push(pKey.column_name);
|
|
211
|
+
return new PigeonError(1, "", new Error("An SQL error has occurred."));
|
|
160
212
|
const fKeyQuery = await runQuery(`SELECT kcu1.table_schema AS local_schema,
|
|
161
213
|
kcu1.table_name AS local_table,
|
|
162
214
|
kcu1.column_name AS local_column,
|
|
163
|
-
kcu2.table_schema AS
|
|
164
|
-
kcu2.table_name AS
|
|
165
|
-
kcu2.column_name AS
|
|
215
|
+
kcu2.table_schema AS foreign_schema,
|
|
216
|
+
kcu2.table_name AS foreign_table,
|
|
217
|
+
kcu2.column_name AS foreign_column
|
|
166
218
|
FROM information_schema.referential_constraints AS rc
|
|
167
219
|
INNER JOIN information_schema.key_column_usage AS kcu1
|
|
168
220
|
ON kcu1.constraint_catalog = rc.constraint_catalog
|
|
@@ -175,13 +227,9 @@ export async function runPigeon(dir, host, port, db, user, pass) {
|
|
|
175
227
|
AND kcu2.constraint_name = rc.unique_constraint_name
|
|
176
228
|
AND kcu2.ordinal_position = kcu1.ordinal_position
|
|
177
229
|
WHERE kcu1.table_schema = $1::varchar
|
|
178
|
-
AND kcu1.table_name = $2::varchar;`, [table.table_schema, table.table_name],
|
|
230
|
+
AND kcu1.table_name = $2::varchar;`, [table.table_schema, table.table_name], db);
|
|
179
231
|
if (typeof fKeyQuery === "undefined")
|
|
180
|
-
return
|
|
181
|
-
exitCode: 1,
|
|
182
|
-
message: null,
|
|
183
|
-
error: new Error("An SQL error has occurred.")
|
|
184
|
-
};
|
|
232
|
+
return new PigeonError(1, "", new Error("An SQL error has occurred."));
|
|
185
233
|
const uniqueQuery = await runQuery(`SELECT array_agg(a.attname) AS columns
|
|
186
234
|
FROM pg_constraint AS c
|
|
187
235
|
CROSS JOIN LATERAL unnest(c.conkey) AS k(c)
|
|
@@ -190,61 +238,88 @@ export async function runPigeon(dir, host, port, db, user, pass) {
|
|
|
190
238
|
AND c.connamespace = $1::regnamespace
|
|
191
239
|
AND c.conrelid = $2::regclass
|
|
192
240
|
GROUP BY c.conrelid;
|
|
193
|
-
`, [table.table_schema, table.table_name],
|
|
241
|
+
`, [table.table_schema, table.table_name], db);
|
|
194
242
|
if (typeof uniqueQuery === "undefined")
|
|
195
|
-
return
|
|
196
|
-
exitCode: 1,
|
|
197
|
-
message: null,
|
|
198
|
-
error: new Error("An SQL error has occurred.")
|
|
199
|
-
};
|
|
243
|
+
return new PigeonError(1, "", new Error("An SQL error has occurred."));
|
|
200
244
|
let uniques = [];
|
|
201
245
|
if (uniqueQuery.rowCount > 0)
|
|
202
246
|
uniques = uniqueQuery.rows[0].columns.slice(1, -1).split(",");
|
|
203
|
-
|
|
247
|
+
tables.push(new Table(table.table_schema, table.table_name, columnQuery.rows, pKeyQuery.rows[0], fKeyQuery.rows, { columns: uniques }));
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
tables: tables,
|
|
251
|
+
enums: enums
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
export function runGeneration(dir, db, tables, enums) {
|
|
255
|
+
if (!tables)
|
|
256
|
+
return new PigeonError(1, "", new Error("No tables were found."));
|
|
257
|
+
const dirResult = createDir(dir);
|
|
258
|
+
if (dirResult instanceof PigeonError)
|
|
259
|
+
return dirResult;
|
|
260
|
+
let schemas = [];
|
|
261
|
+
for (const table of tables) {
|
|
262
|
+
if (schemas.includes(table.table_schema))
|
|
263
|
+
continue;
|
|
264
|
+
schemas.push(table.table_schema);
|
|
265
|
+
}
|
|
266
|
+
for (const schema of schemas) {
|
|
267
|
+
const dirResult = createDir(path.join(dir, schema));
|
|
268
|
+
if (dirResult instanceof PigeonError)
|
|
269
|
+
return dirResult;
|
|
270
|
+
}
|
|
271
|
+
for (const table of tables) {
|
|
272
|
+
let ts = clientMaker(0, db);
|
|
204
273
|
ts += "\n\n";
|
|
205
|
-
|
|
206
|
-
for (const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
274
|
+
if (enums) {
|
|
275
|
+
for (const cEnum of enums) {
|
|
276
|
+
for (const column of table.columns) {
|
|
277
|
+
if (cEnum.name === column.udt_name) {
|
|
278
|
+
const enumName = nameBeautifier(cEnum.name).replaceAll(" ", "");
|
|
279
|
+
ts += "/**\n An Enum representing the " + nameBeautifier(cEnum.name).toLowerCase() + ".\n * @readonly\n * @enum {string}\n */\n";
|
|
280
|
+
ts += "class " + enumName + " {\n";
|
|
281
|
+
let longestLabel = 0;
|
|
282
|
+
for (const label of cEnum.labels)
|
|
283
|
+
if (label.length > longestLabel)
|
|
284
|
+
longestLabel = label.length;
|
|
285
|
+
for (const label of cEnum.labels)
|
|
286
|
+
ts += "\tstatic " + label.toUpperCase().replaceAll(/[^a-zA-Z0-9$]/g, "_") + ": string" + " ".repeat(longestLabel - label.length + 1) + "= \"" + label + "\";\n";
|
|
287
|
+
ts += "}\n\n";
|
|
288
|
+
}
|
|
218
289
|
}
|
|
219
290
|
}
|
|
220
291
|
}
|
|
221
|
-
ts += createClass(table.table_name,
|
|
292
|
+
ts += createClass(table.table_name, table.columns, table.primaryKey?.column_name, table.foreignKeys);
|
|
222
293
|
ts += "\n\n";
|
|
223
|
-
ts += createGetAll(table.table_schema, table.table_name,
|
|
294
|
+
ts += createGetAll(table.table_schema, table.table_name, table.columns);
|
|
224
295
|
ts += "\n\n";
|
|
225
|
-
let keys = [
|
|
226
|
-
|
|
227
|
-
keys.push(
|
|
228
|
-
|
|
296
|
+
let keys = [];
|
|
297
|
+
if (table.primaryKey)
|
|
298
|
+
keys.push(table.primaryKey.column_name);
|
|
299
|
+
if (table.foreignKeys)
|
|
300
|
+
for (const fKey of table.foreignKeys)
|
|
301
|
+
keys.push(fKey.local_column.replaceAll(" ", ""));
|
|
302
|
+
if (table.unique)
|
|
303
|
+
keys = keys.concat(table.unique.columns);
|
|
229
304
|
keys = [...new Set(keys)];
|
|
230
305
|
for (const keyCombination of getCombinations(keys)) {
|
|
231
|
-
ts += createGet(table.table_schema, table.table_name,
|
|
306
|
+
ts += createGet(table.table_schema, table.table_name, table.columns, keyCombination);
|
|
232
307
|
ts += "\n\n";
|
|
233
308
|
}
|
|
234
309
|
let nonDefaults = [];
|
|
235
310
|
let softDefaults = [];
|
|
236
311
|
let hardDefaults = [];
|
|
237
|
-
for (const column of
|
|
312
|
+
for (const column of table.columns) {
|
|
238
313
|
if (column.column_default === null && column.is_identity === "NO")
|
|
239
314
|
nonDefaults.push(column);
|
|
240
|
-
if ((column.column_default !== null && !column.column_default.includes("nextval")) || (column.is_identity === "YES" && column.identity_generation === "BY DEFAULT"))
|
|
315
|
+
else if ((column.column_default !== null && !column.column_default.includes("nextval")) || (column.is_identity === "YES" && column.identity_generation === "BY DEFAULT"))
|
|
241
316
|
softDefaults.push(column);
|
|
242
|
-
if ((column.column_default !== null && column.column_default.includes("nextval")) || (column.is_identity === "YES" && column.identity_generation === "ALWAYS"))
|
|
317
|
+
else if ((column.column_default !== null && column.column_default.includes("nextval")) || (column.is_identity === "YES" && column.identity_generation === "ALWAYS"))
|
|
243
318
|
hardDefaults.push(column);
|
|
244
319
|
}
|
|
245
|
-
ts += createAdd(table.table_schema, table.table_name, nonDefaults, [], hardDefaults.concat(softDefaults),
|
|
320
|
+
ts += createAdd(table.table_schema, table.table_name, nonDefaults, [], hardDefaults.concat(softDefaults), table.foreignKeys) + "\n\n";
|
|
246
321
|
for (const softCombination of getCombinations(softDefaults))
|
|
247
|
-
ts += createAdd(table.table_schema, table.table_name, nonDefaults, softCombination, hardDefaults.concat(softDefaults.filter(n => !getCombinations(softDefaults).includes(n))),
|
|
322
|
+
ts += createAdd(table.table_schema, table.table_name, nonDefaults, softCombination, hardDefaults.concat(softDefaults.filter(n => !getCombinations(softDefaults).includes([n]))), table.foreignKeys) + "\n\n";
|
|
248
323
|
ts = ts.slice(0, -2);
|
|
249
324
|
const regex = /import ({?.*?}?) from "(.*?)";\n/g;
|
|
250
325
|
let importObjects = [];
|
|
@@ -291,34 +366,29 @@ export async function runPigeon(dir, host, port, db, user, pass) {
|
|
|
291
366
|
ts = importString + ts;
|
|
292
367
|
fs.writeFileSync(path.join(dir, table.table_schema, table.table_name + ".ts"), ts);
|
|
293
368
|
}
|
|
294
|
-
return {
|
|
295
|
-
exitCode: 0,
|
|
296
|
-
message: "Generation Completed Successfully",
|
|
297
|
-
error: null
|
|
298
|
-
};
|
|
299
369
|
}
|
|
300
|
-
function createClass(tableName, columns,
|
|
370
|
+
function createClass(tableName, columns, primaryKey, foreignKeys) {
|
|
301
371
|
let text = "";
|
|
302
372
|
text += "export class " + singularize(nameBeautifier(tableName)).replaceAll(" ", "") + " {\n";
|
|
303
373
|
for (const column of columns) {
|
|
304
|
-
let dataType =
|
|
305
|
-
if (
|
|
306
|
-
dataType = nameBeautifier(column.udt_name).replaceAll(" ", "");
|
|
307
|
-
if (column.is_nullable == "YES")
|
|
374
|
+
let dataType = getType(column.data_type, column.udt_name).replaceAll(" ", "");
|
|
375
|
+
if (column.is_nullable === "YES")
|
|
308
376
|
dataType += " | undefined";
|
|
309
377
|
let isPrimaryKey = false;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
378
|
+
if (column.column_name === primaryKey)
|
|
379
|
+
isPrimaryKey = true;
|
|
380
|
+
let foreignKeyIndex;
|
|
381
|
+
if (foreignKeys)
|
|
382
|
+
for (let i = 0; i < foreignKeys.length; i++)
|
|
383
|
+
if (foreignKeys[i].local_column === column.column_name)
|
|
384
|
+
foreignKeyIndex = i;
|
|
317
385
|
text += "\t/**\n";
|
|
318
386
|
if (isPrimaryKey)
|
|
319
387
|
text += "\t * A primary key representing the " + nameBeautifier(column.column_name) + " for the " + nameBeautifier(tableName) + " table.\n";
|
|
320
|
-
else if (
|
|
321
|
-
text += "\t * A foreign key representing the " + nameBeautifier(column.column_name) + " for the " + nameBeautifier(tableName) + " table and referencing the " + nameBeautifier(foreignKeys[foreignKeyIndex].
|
|
388
|
+
else if (foreignKeys && foreignKeyIndex)
|
|
389
|
+
text += "\t * A foreign key representing the " + nameBeautifier(column.column_name) + " for the " + nameBeautifier(tableName) + " table and referencing the " + nameBeautifier(foreignKeys[foreignKeyIndex].foreign_column) + " in the " + nameBeautifier(foreignKeys[foreignKeyIndex].foreign_table) + " table in the " + nameBeautifier(foreignKeys[foreignKeyIndex].foreign_schema) + " schema.\n";
|
|
390
|
+
else if (column.column_name.toLowerCase().startsWith("is_"))
|
|
391
|
+
text += "\t * Indicates whether this record in the table " + nameBeautifier(tableName) + " is currently " + nameBeautifier(column.column_name.slice(3)).toLowerCase() + ".\n";
|
|
322
392
|
else
|
|
323
393
|
text += "\t * The " + nameBeautifier(column.column_name) + " for the " + nameBeautifier(tableName) + " table.\n";
|
|
324
394
|
text += "\t * @type {" + dataType + "}\n";
|
|
@@ -330,9 +400,9 @@ function createClass(tableName, columns, primaryKeys, foreignKeys) {
|
|
|
330
400
|
if (column.column_default)
|
|
331
401
|
text += " = new Date()";
|
|
332
402
|
else
|
|
333
|
-
text += " = new Date(" + column.column_default.replace(
|
|
403
|
+
text += " = new Date(" + column.column_default.replace(" ", "T") + ")";
|
|
334
404
|
}
|
|
335
|
-
else if (dataType === "number")
|
|
405
|
+
else if (dataType === "number" || dataType === "boolean")
|
|
336
406
|
text += " = " + column.column_default;
|
|
337
407
|
else
|
|
338
408
|
text += " = \"" + column.column_default + "\"";
|
|
@@ -345,24 +415,23 @@ function createClass(tableName, columns, primaryKeys, foreignKeys) {
|
|
|
345
415
|
text += "\t * Creates a new object for the " + nameBeautifier(tableName) + " table.\n";
|
|
346
416
|
text += "\t * \n";
|
|
347
417
|
for (const column of columns) {
|
|
348
|
-
let dataType =
|
|
349
|
-
if (dataType === undefined)
|
|
350
|
-
dataType = nameBeautifier(column.udt_name).replaceAll(" ", "");
|
|
418
|
+
let dataType = getType(column.data_type, column.udt_name).replaceAll(" ", "");
|
|
351
419
|
text += "\t * ";
|
|
352
420
|
text += "@param {" + dataType;
|
|
353
|
-
if (column.is_nullable
|
|
421
|
+
if (column.is_nullable === "YES")
|
|
354
422
|
text += " | undefined";
|
|
355
423
|
text += "} " + column.column_name;
|
|
356
|
-
|
|
424
|
+
if (!column.column_name.toLowerCase().startsWith("is_"))
|
|
425
|
+
text += " - The " + nameBeautifier(column.column_name) + " of the " + nameBeautifier(tableName) + " table. \n";
|
|
426
|
+
else
|
|
427
|
+
text += " - Indicates whether this record in the table " + nameBeautifier(tableName) + " is currently " + nameBeautifier(column.column_name.slice(3)).toLowerCase() + ".\n";
|
|
357
428
|
}
|
|
358
429
|
text += "\t */\n";
|
|
359
430
|
text += "\tconstructor(";
|
|
360
431
|
for (const column of columns) {
|
|
361
|
-
let dataType =
|
|
362
|
-
if (dataType === undefined)
|
|
363
|
-
dataType = nameBeautifier(column.udt_name).replaceAll(" ", "");
|
|
432
|
+
let dataType = getType(column.data_type, column.udt_name).replaceAll(" ", "");
|
|
364
433
|
text += column.column_name + ": " + dataType;
|
|
365
|
-
if (column.is_nullable
|
|
434
|
+
if (column.is_nullable === "YES")
|
|
366
435
|
text += " | undefined";
|
|
367
436
|
text += ", ";
|
|
368
437
|
}
|
|
@@ -374,14 +443,14 @@ function createClass(tableName, columns, primaryKeys, foreignKeys) {
|
|
|
374
443
|
text += "}";
|
|
375
444
|
return text;
|
|
376
445
|
}
|
|
377
|
-
export function clientMaker(baseTabs,
|
|
446
|
+
export function clientMaker(baseTabs, db) {
|
|
378
447
|
let text = "";
|
|
379
448
|
text += tabsInserter(baseTabs) + "const client = new Client({\n";
|
|
380
|
-
text += tabsInserter(baseTabs + 1) + "host: \"" + host + "\",\n";
|
|
381
|
-
text += tabsInserter(baseTabs + 1) + "port: " + port + ",\n";
|
|
382
|
-
text += tabsInserter(baseTabs + 1) + "database: \"" + db + "\",\n";
|
|
383
|
-
text += tabsInserter(baseTabs + 1) + "user: \"" + user + "\",\n";
|
|
384
|
-
text += tabsInserter(baseTabs + 1) + "password: \"" + pass + "\"\n";
|
|
449
|
+
text += tabsInserter(baseTabs + 1) + "host: \"" + db.host + "\",\n";
|
|
450
|
+
text += tabsInserter(baseTabs + 1) + "port: " + db.port + ",\n";
|
|
451
|
+
text += tabsInserter(baseTabs + 1) + "database: \"" + db.db + "\",\n";
|
|
452
|
+
text += tabsInserter(baseTabs + 1) + "user: \"" + db.user + "\",\n";
|
|
453
|
+
text += tabsInserter(baseTabs + 1) + "password: \"" + db.pass + "\"\n";
|
|
385
454
|
text += tabsInserter(baseTabs) + "});";
|
|
386
455
|
return text;
|
|
387
456
|
}
|
|
@@ -413,10 +482,12 @@ function createGet(tableSchema, tableName, columns, keys) {
|
|
|
413
482
|
text = text.slice(0, -5) + ".\n";
|
|
414
483
|
text += " *\n";
|
|
415
484
|
for (const key of keys) {
|
|
416
|
-
const column = columns.find(column => column.column_name
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
485
|
+
const column = columns.find(column => column.column_name === key);
|
|
486
|
+
if (!column) {
|
|
487
|
+
consoleMessage("WRN", `Key ${key} was not found in the columns of table ${tableName}.`);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
let dataType = getType(column.data_type, column.udt_name).replaceAll(" ", "");
|
|
420
491
|
text += " * ";
|
|
421
492
|
text += "@param {" + dataType;
|
|
422
493
|
text += "} " + column.column_name;
|
|
@@ -429,8 +500,14 @@ function createGet(tableSchema, tableName, columns, keys) {
|
|
|
429
500
|
text += nameBeautifier(key).replaceAll(" ", "") + "And";
|
|
430
501
|
text = text.slice(0, -3);
|
|
431
502
|
text += "(";
|
|
432
|
-
for (const key of keys)
|
|
433
|
-
|
|
503
|
+
for (const key of keys) {
|
|
504
|
+
const column = columns.find(column => column.column_name === key);
|
|
505
|
+
if (!column) {
|
|
506
|
+
consoleMessage("WRN", `Key ${key} was not found in the columns of table ${tableName}.`);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
text += key + ": " + getType(column.data_type, column.udt_name) + ", ";
|
|
510
|
+
}
|
|
434
511
|
text = text.slice(0, -2);
|
|
435
512
|
text += "): Promise<" + className + "[]> {\n";
|
|
436
513
|
text += "\tif (";
|
|
@@ -441,7 +518,12 @@ function createGet(tableSchema, tableName, columns, keys) {
|
|
|
441
518
|
let query = "SELECT * FROM " + tableSchema + "." + tableName + " WHERE ";
|
|
442
519
|
let parameters = "";
|
|
443
520
|
for (let i = 0; i < keys.length; i++) {
|
|
444
|
-
|
|
521
|
+
const column = columns.find(column => column.column_name === keys[i]);
|
|
522
|
+
if (!column) {
|
|
523
|
+
consoleMessage("WRN", `Key ${keys[i]} was not found in the columns of table ${tableName}.`);
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
query += keys[i] + " = " + "$" + (i + 1) + "::" + (column.data_type || column.udt_name) + " AND ";
|
|
445
527
|
parameters += keys[i] + ", ";
|
|
446
528
|
}
|
|
447
529
|
query = query.slice(0, -5) + ";";
|
|
@@ -456,13 +538,15 @@ function createGet(tableSchema, tableName, columns, keys) {
|
|
|
456
538
|
function createAdd(tableSchema, tableName, nonDefaults, softDefaults, hardDefaults, foreignKeys) {
|
|
457
539
|
let text = "";
|
|
458
540
|
const className = singularize(nameBeautifier(tableName)).replaceAll(" ", "");
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
541
|
+
if (foreignKeys) {
|
|
542
|
+
for (const foreignKey of foreignKeys) {
|
|
543
|
+
if ((tableSchema === foreignKey.foreign_schema) && (tableName === foreignKey.foreign_table))
|
|
544
|
+
continue;
|
|
545
|
+
text += "import {get" + nameBeautifier(foreignKey.foreign_table).replaceAll(" ", "") + "By" + nameBeautifier(foreignKey.foreign_column).replaceAll(" ", "") + "} from \".";
|
|
546
|
+
if (tableSchema !== foreignKey.foreign_schema)
|
|
547
|
+
text += "./" + foreignKey.foreign_schema;
|
|
548
|
+
text += "/" + foreignKey.foreign_table + ".js\";\n";
|
|
549
|
+
}
|
|
466
550
|
}
|
|
467
551
|
text += "/**\n";
|
|
468
552
|
text += " * Adds the provided " + className + " object to the database.\n";
|
|
@@ -470,9 +554,7 @@ function createAdd(tableSchema, tableName, nonDefaults, softDefaults, hardDefaul
|
|
|
470
554
|
let columns = nonDefaults.concat(softDefaults);
|
|
471
555
|
columns.sort((a, b) => a.ordinal_position - b.ordinal_position);
|
|
472
556
|
for (const column of columns) {
|
|
473
|
-
let dataType =
|
|
474
|
-
if (dataType === undefined)
|
|
475
|
-
dataType = nameBeautifier(column.udt_name).replaceAll(" ", "");
|
|
557
|
+
let dataType = getType(column.data_type, column.udt_name).replaceAll(" ", "");
|
|
476
558
|
text += " * ";
|
|
477
559
|
text += "@param {" + dataType;
|
|
478
560
|
if (column.is_nullable === "YES")
|
|
@@ -481,7 +563,7 @@ function createAdd(tableSchema, tableName, nonDefaults, softDefaults, hardDefaul
|
|
|
481
563
|
text += " - The " + nameBeautifier(column.column_name) + " to be inserted into the " + nameBeautifier(tableName) + " table.\n";
|
|
482
564
|
}
|
|
483
565
|
text += " * @returns {Promise<" + className + ">} - A Promise object returning the inserted " + nameBeautifier(tableName) + ".\n";
|
|
484
|
-
if (foreignKeys.length > 0) {
|
|
566
|
+
if (foreignKeys && foreignKeys.length > 0) {
|
|
485
567
|
text += " * @throws string An exception in the case of the ";
|
|
486
568
|
for (const foreignKey of foreignKeys)
|
|
487
569
|
text += nameBeautifier(foreignKey.local_column) + " or the ";
|
|
@@ -498,9 +580,7 @@ function createAdd(tableSchema, tableName, nonDefaults, softDefaults, hardDefaul
|
|
|
498
580
|
}
|
|
499
581
|
text += "(";
|
|
500
582
|
for (const column of columns) {
|
|
501
|
-
let dataType =
|
|
502
|
-
if (dataType === undefined)
|
|
503
|
-
dataType = nameBeautifier(column.udt_name).replaceAll(" ", "");
|
|
583
|
+
let dataType = getType(column.data_type, column.udt_name);
|
|
504
584
|
text += column.column_name + ": " + dataType;
|
|
505
585
|
if (column.is_nullable === "YES")
|
|
506
586
|
text += " | undefined";
|
|
@@ -508,18 +588,25 @@ function createAdd(tableSchema, tableName, nonDefaults, softDefaults, hardDefaul
|
|
|
508
588
|
}
|
|
509
589
|
text = text.slice(0, -2);
|
|
510
590
|
text += "): Promise<" + className + "> {\n";
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
591
|
+
if (foreignKeys) {
|
|
592
|
+
for (const foreignKey of foreignKeys) {
|
|
593
|
+
const column = columns.find(column => column.column_name === foreignKey.local_column);
|
|
594
|
+
if (!column) {
|
|
595
|
+
consoleMessage("WRN", `Key ${foreignKey} was not found in the columns of table ${tableName}.`);
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
if (column.is_nullable === "YES") {
|
|
599
|
+
text += "\tif (" + foreignKey.local_column + ") {\n";
|
|
600
|
+
text += "\t\tconst verify" + nameBeautifier(foreignKey.local_column).replaceAll(" ", "") + " = await get" + nameBeautifier(foreignKey.foreign_table).replaceAll(" ", "") + "By" + nameBeautifier(foreignKey.foreign_column).replaceAll(" ", "") + "(" + foreignKey.local_column + ");\n";
|
|
601
|
+
text += "\t\tif (verify" + nameBeautifier(foreignKey.local_column).replaceAll(" ", "") + ".length === 0)\n";
|
|
602
|
+
text += "\t\t\tthrow \"The " + nameBeautifier(foreignKey.local_column) + " provided does not exist.\";\n";
|
|
603
|
+
text += "\t}\n\n";
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
text += "\tconst verify" + nameBeautifier(foreignKey.local_column).replaceAll(" ", "") + " = await get" + nameBeautifier(foreignKey.foreign_table).replaceAll(" ", "") + "By" + nameBeautifier(foreignKey.foreign_column).replaceAll(" ", "") + "(" + foreignKey.local_column + ");\n";
|
|
607
|
+
text += "\tif (verify" + nameBeautifier(foreignKey.local_column).replaceAll(" ", "") + ".length === 0)\n";
|
|
608
|
+
text += "\t\tthrow \"The " + nameBeautifier(foreignKey.local_column) + " provided does not exist.\";\n\n";
|
|
609
|
+
}
|
|
523
610
|
}
|
|
524
611
|
}
|
|
525
612
|
let query = "INSERT INTO " + tableSchema + "." + tableName + " (";
|
|
@@ -529,9 +616,11 @@ function createAdd(tableSchema, tableName, nonDefaults, softDefaults, hardDefaul
|
|
|
529
616
|
query += ") VALUES (";
|
|
530
617
|
let parameters = "";
|
|
531
618
|
for (let i = 0; i < columns.length; i++) {
|
|
532
|
-
let dataType = columns[i].
|
|
533
|
-
if (dataType === "
|
|
534
|
-
dataType =
|
|
619
|
+
let dataType = columns[i].udt_name;
|
|
620
|
+
if (dataType[0] === "_")
|
|
621
|
+
dataType = dataType.slice(1) + "[]";
|
|
622
|
+
else if (columns[i].data_type !== "USER-DEFINED")
|
|
623
|
+
dataType = columns[i].data_type;
|
|
535
624
|
query += "$" + (i + 1) + "::" + dataType + ", ";
|
|
536
625
|
parameters += columns[i].column_name + ", ";
|
|
537
626
|
}
|
package/src/maps.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Copyright (c) 2024 Andreas Michael
|
|
3
|
-
* This software is under the Apache 2.0 License
|
|
4
|
-
*/
|
|
5
|
-
export const types = new Map([
|
|
1
|
+
export const jsTypes = new Map([
|
|
6
2
|
["bigint", "number"],
|
|
7
3
|
["int8", "number"],
|
|
8
4
|
["bigserial", "number"],
|
|
@@ -65,3 +61,62 @@ export const types = new Map([
|
|
|
65
61
|
["uuid", "string"],
|
|
66
62
|
["xml", "string"],
|
|
67
63
|
]);
|
|
64
|
+
export const udtTypes = new Map([
|
|
65
|
+
["smallint", "int2"],
|
|
66
|
+
["int2", "int2"],
|
|
67
|
+
["integer", "int4"],
|
|
68
|
+
["int", "int4"],
|
|
69
|
+
["bigint", "int8"],
|
|
70
|
+
["int8", "int8"],
|
|
71
|
+
["real", "float4"],
|
|
72
|
+
["float4", "float4"],
|
|
73
|
+
["double precision", "float8"],
|
|
74
|
+
["float8", "float8"],
|
|
75
|
+
["numeric", "numeric"],
|
|
76
|
+
["decimal", "numeric"],
|
|
77
|
+
["money", "money"],
|
|
78
|
+
["smallserial", "int2"],
|
|
79
|
+
["serial", "int4"],
|
|
80
|
+
["bigserial", "int8"],
|
|
81
|
+
["character", "bpchar"],
|
|
82
|
+
["char", "bpchar"],
|
|
83
|
+
["character varying", "varchar"],
|
|
84
|
+
["varchar", "varchar"],
|
|
85
|
+
["text", "text"],
|
|
86
|
+
["bytea", "bytea"],
|
|
87
|
+
["bit", "bit"],
|
|
88
|
+
["bit varying", "varbit"],
|
|
89
|
+
["varbit", "varbit"],
|
|
90
|
+
["boolean", "bool"],
|
|
91
|
+
["bool", "bool"],
|
|
92
|
+
["date", "date"],
|
|
93
|
+
["time without time zone", "time"],
|
|
94
|
+
["time", "time"],
|
|
95
|
+
["time with time zone", "timetz"],
|
|
96
|
+
["timetz", "timetz"],
|
|
97
|
+
["timestamp without time zone", "timestamp"],
|
|
98
|
+
["timestamp", "timestamp"],
|
|
99
|
+
["timestamp with time zone", "timestamptz"],
|
|
100
|
+
["timestamptz", "timestamptz"],
|
|
101
|
+
["interval", "interval"],
|
|
102
|
+
["box", "box"],
|
|
103
|
+
["circle", "circle"],
|
|
104
|
+
["line", "line"],
|
|
105
|
+
["lseg", "lseg"],
|
|
106
|
+
["path", "path"],
|
|
107
|
+
["point", "point"],
|
|
108
|
+
["polygon", "polygon"],
|
|
109
|
+
["cidr", "cidr"],
|
|
110
|
+
["inet", "inet"],
|
|
111
|
+
["macaddr", "macaddr"],
|
|
112
|
+
["macaddr8", "macaddr8"],
|
|
113
|
+
["json", "json"],
|
|
114
|
+
["jsonb", "jsonb"],
|
|
115
|
+
["uuid", "uuid"],
|
|
116
|
+
["tsquery", "tsquery"],
|
|
117
|
+
["tsvector", "tsvector"],
|
|
118
|
+
["pg_lsn", "pg_lsn"],
|
|
119
|
+
["pg_snapshot", "pg_snapshot"],
|
|
120
|
+
["txid_snapshot", "txid_snapshot"],
|
|
121
|
+
["xml", "xml"],
|
|
122
|
+
]);
|
package/src/pgAdmin.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ColumnQueryRow, ForeignKeyQueryRow, PigeonError, PrimaryKeyQueryRow, Table, UniqueQueryRow } from "./index.js";
|
|
2
|
+
import { udtTypes } from "./maps.js";
|
|
3
|
+
function objectToArray(json) {
|
|
4
|
+
let arrayJSON = "";
|
|
5
|
+
let enteredObject = false;
|
|
6
|
+
let brackets = 0;
|
|
7
|
+
for (let i = 0; i < json.length; i++) {
|
|
8
|
+
if (json[i] === "{") {
|
|
9
|
+
if (!enteredObject) {
|
|
10
|
+
enteredObject = true;
|
|
11
|
+
arrayJSON += "[";
|
|
12
|
+
brackets++;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
brackets++;
|
|
16
|
+
arrayJSON += json[i];
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
else if (json[i] === "}") {
|
|
20
|
+
brackets--;
|
|
21
|
+
if (brackets === 0)
|
|
22
|
+
return JSON.parse(arrayJSON.slice(0, -1) + "]");
|
|
23
|
+
arrayJSON += json[i];
|
|
24
|
+
if (brackets === 1)
|
|
25
|
+
arrayJSON += ",";
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (enteredObject && brackets !== 1)
|
|
29
|
+
arrayJSON += json[i];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function getRelationshipsAndTables(json) {
|
|
33
|
+
if (!json.startsWith("{\"version\":"))
|
|
34
|
+
throw new PigeonError(1, "", new Error("The file specified is not an pgAdmin ERD file."));
|
|
35
|
+
let relationships = [];
|
|
36
|
+
let tables = [];
|
|
37
|
+
const relationshipsIndex = json.match(/"type":"diagram-links"/)?.index;
|
|
38
|
+
if (relationshipsIndex)
|
|
39
|
+
relationships = objectToArray(json.slice(relationshipsIndex));
|
|
40
|
+
const tablesIndex = json.match(/"type":"diagram-nodes"/)?.index;
|
|
41
|
+
if (tablesIndex)
|
|
42
|
+
tables = objectToArray(json.slice(tablesIndex));
|
|
43
|
+
return {
|
|
44
|
+
relationships: relationships,
|
|
45
|
+
tables: tables,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function tableProcessing(tables) {
|
|
49
|
+
const pigeonTables = [];
|
|
50
|
+
for (const table of tables) {
|
|
51
|
+
const data = table.otherInfo.data;
|
|
52
|
+
const columns = [];
|
|
53
|
+
let ordinalPossition = 1;
|
|
54
|
+
for (const column of data.columns) {
|
|
55
|
+
let dataType = column.cltype;
|
|
56
|
+
let udtType;
|
|
57
|
+
udtType = udtTypes.get(dataType);
|
|
58
|
+
if (!udtType) {
|
|
59
|
+
if (dataType.endsWith("[]")) {
|
|
60
|
+
udtType = "_" + udtTypes.get(dataType.slice(0, -2));
|
|
61
|
+
dataType = "ARRAY";
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
udtType = dataType;
|
|
65
|
+
dataType = "USER-DEFINED";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (dataType === "smallserial" || dataType === "serial" || dataType === "bigserial") {
|
|
69
|
+
dataType = dataType.replace("serial", "int");
|
|
70
|
+
if (dataType === "int")
|
|
71
|
+
dataType = "integer";
|
|
72
|
+
column.defval = "nextval('" + data.name + "_" + column.name + "_seq'::regclass)";
|
|
73
|
+
}
|
|
74
|
+
let isNullable;
|
|
75
|
+
if (column.attnotnull)
|
|
76
|
+
isNullable = "NO";
|
|
77
|
+
else
|
|
78
|
+
isNullable = "YES";
|
|
79
|
+
let columnDefault;
|
|
80
|
+
if (column.defval !== "" && column.defval !== undefined)
|
|
81
|
+
columnDefault = column.defval;
|
|
82
|
+
else
|
|
83
|
+
columnDefault = null;
|
|
84
|
+
let identity;
|
|
85
|
+
if (column.colconstype === "i")
|
|
86
|
+
identity = "YES";
|
|
87
|
+
else
|
|
88
|
+
identity = "NO";
|
|
89
|
+
let identityGeneration = null;
|
|
90
|
+
if (identity === "YES") {
|
|
91
|
+
if (column.attidentity === "a")
|
|
92
|
+
identityGeneration = "ALWAYS";
|
|
93
|
+
if (column.attidentity === "b")
|
|
94
|
+
identityGeneration = "BY DEFAULT";
|
|
95
|
+
}
|
|
96
|
+
columns.push(new ColumnQueryRow(column.name, ordinalPossition, columnDefault, isNullable, dataType, udtType, identity, identityGeneration));
|
|
97
|
+
ordinalPossition++;
|
|
98
|
+
}
|
|
99
|
+
let primaryKey = undefined;
|
|
100
|
+
if (data.primary_key[0]?.columns[0]?.column)
|
|
101
|
+
primaryKey = new PrimaryKeyQueryRow(data.primary_key[0].columns[0].column);
|
|
102
|
+
const foreignKeys = [];
|
|
103
|
+
for (const foreignKey of data.foreign_key) {
|
|
104
|
+
for (const column of foreignKey.columns) {
|
|
105
|
+
const match = column.references_table_name.match(/(?:\((.*?)\))? ?(.*)/);
|
|
106
|
+
foreignKeys.push(new ForeignKeyQueryRow(data.schema, data.name, column.local_column, match[1] || data.schema, match[2], column.referenced));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const uniqueConstraints = [];
|
|
110
|
+
if (data.unique_constraint)
|
|
111
|
+
for (const uniqueConstraint of data.unique_constraint)
|
|
112
|
+
for (const column of uniqueConstraint.columns)
|
|
113
|
+
uniqueConstraints.push(column.column);
|
|
114
|
+
pigeonTables.push(new Table(data.schema, data.name, columns, primaryKey, foreignKeys, new UniqueQueryRow(uniqueConstraints)));
|
|
115
|
+
}
|
|
116
|
+
return pigeonTables;
|
|
117
|
+
}
|
package/src/utils.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2024 Andreas Michael
|
|
3
|
-
* This software is under the Apache 2.0 License
|
|
4
|
-
*/
|
|
5
1
|
import pg from "pg";
|
|
2
|
+
import { jsTypes } from "./maps.js";
|
|
6
3
|
const { Client } = pg;
|
|
7
|
-
export async function runQuery(command, parameters,
|
|
4
|
+
export async function runQuery(command, parameters, db) {
|
|
8
5
|
const client = new Client({
|
|
9
|
-
host: host,
|
|
10
|
-
port: port,
|
|
11
|
-
database: db,
|
|
12
|
-
user:
|
|
13
|
-
password:
|
|
6
|
+
host: db.host,
|
|
7
|
+
port: Number(db.port),
|
|
8
|
+
database: db.db,
|
|
9
|
+
user: db.user,
|
|
10
|
+
password: db.pass,
|
|
14
11
|
});
|
|
15
12
|
try {
|
|
16
13
|
await client.connect();
|
|
@@ -149,3 +146,16 @@ export function getCombinations(valuesArray) {
|
|
|
149
146
|
combinations.sort((a, b) => a.length - b.length);
|
|
150
147
|
return combinations;
|
|
151
148
|
}
|
|
149
|
+
export function getType(dataType, udtName) {
|
|
150
|
+
let isArray = false;
|
|
151
|
+
if (dataType === "ARRAY") {
|
|
152
|
+
dataType = udtName.slice(1);
|
|
153
|
+
isArray = true;
|
|
154
|
+
}
|
|
155
|
+
let foundDataType = jsTypes.get(dataType);
|
|
156
|
+
if (foundDataType === undefined)
|
|
157
|
+
foundDataType = nameBeautifier(udtName).replaceAll(" ", "");
|
|
158
|
+
if (isArray)
|
|
159
|
+
foundDataType += "[]";
|
|
160
|
+
return foundDataType;
|
|
161
|
+
}
|