admindb 1.2.0 → 2.0.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/README.md +250 -352
- package/dist/app.d.ts +25 -4
- package/dist/app.js +79 -15
- package/dist/app.js.map +1 -1
- package/dist/auth/config.d.ts +19 -0
- package/dist/auth/config.js +147 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/crypto.d.ts +27 -0
- package/dist/auth/crypto.js +133 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.js +22 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +14 -0
- package/dist/auth/middleware.js +100 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/routes.d.ts +7 -0
- package/dist/auth/routes.js +78 -0
- package/dist/auth/routes.js.map +1 -0
- package/dist/auth/types.d.ts +35 -0
- package/dist/auth/types.js +11 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli/args.d.ts +45 -0
- package/dist/cli/args.js +328 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/config.d.ts +56 -0
- package/dist/cli/config.js +133 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.js +21 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/runner.d.ts +1 -0
- package/dist/cli/runner.js +222 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli.js +2 -127
- package/dist/cli.js.map +1 -1
- package/dist/data/datasets.d.ts +17 -0
- package/dist/data/datasets.js +133 -0
- package/dist/data/datasets.js.map +1 -0
- package/dist/data/detector.d.ts +30 -0
- package/dist/data/detector.js +472 -0
- package/dist/data/detector.js.map +1 -0
- package/dist/data/engine.d.ts +10 -0
- package/dist/data/engine.js +211 -0
- package/dist/data/engine.js.map +1 -0
- package/dist/data/index.d.ts +5 -0
- package/dist/data/index.js +22 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/strategies.d.ts +30 -0
- package/dist/data/strategies.js +343 -0
- package/dist/data/strategies.js.map +1 -0
- package/dist/data/types.d.ts +73 -0
- package/dist/data/types.js +8 -0
- package/dist/data/types.js.map +1 -0
- package/dist/db/database.d.ts +14 -142
- package/dist/db/database.js +209 -435
- package/dist/db/database.js.map +1 -1
- package/dist/db/export.d.ts +2 -2
- package/dist/db/export.js +7 -2
- package/dist/db/export.js.map +1 -1
- package/dist/db/filters.d.ts +22 -0
- package/dist/db/filters.js +158 -0
- package/dist/db/filters.js.map +1 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.js +25 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/introspection.d.ts +6 -0
- package/dist/db/introspection.js +172 -0
- package/dist/db/introspection.js.map +1 -0
- package/dist/db/manager.d.ts +32 -19
- package/dist/db/manager.js +203 -44
- package/dist/db/manager.js.map +1 -1
- package/dist/db/migrations.d.ts +5 -0
- package/dist/db/migrations.js +110 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/postgres.d.ts +85 -0
- package/dist/db/postgres.js +745 -0
- package/dist/db/postgres.js.map +1 -0
- package/dist/db/types.d.ts +206 -0
- package/dist/db/types.js +7 -0
- package/dist/db/types.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +46 -4
- package/dist/index.js.map +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/css/input.css +83 -13
- package/dist/public/js/app.js +142 -0
- package/dist/public/js/browse.js +591 -104
- package/dist/public/js/databases.js +105 -13
- package/dist/public/js/designer.js +40 -2
- package/dist/public/js/forms.js +610 -28
- package/dist/public/js/icons.js +51 -0
- package/dist/public/js/inspector.js +638 -0
- package/dist/public/js/query.js +93 -3
- package/dist/public/js/schema.js +170 -52
- package/dist/public/js/seed.js +1090 -0
- package/dist/routes/api/helpers.d.ts +42 -0
- package/dist/routes/api/helpers.js +133 -0
- package/dist/routes/api/helpers.js.map +1 -0
- package/dist/routes/api/import-export.d.ts +3 -0
- package/dist/routes/api/import-export.js +85 -0
- package/dist/routes/api/import-export.js.map +1 -0
- package/dist/routes/api/index.d.ts +4 -0
- package/dist/routes/api/index.js +37 -0
- package/dist/routes/api/index.js.map +1 -0
- package/dist/routes/api/query.d.ts +3 -0
- package/dist/routes/api/query.js +76 -0
- package/dist/routes/api/query.js.map +1 -0
- package/dist/routes/api/rows.d.ts +3 -0
- package/dist/routes/api/rows.js +373 -0
- package/dist/routes/api/rows.js.map +1 -0
- package/dist/routes/api/seed.d.ts +3 -0
- package/dist/routes/api/seed.js +82 -0
- package/dist/routes/api/seed.js.map +1 -0
- package/dist/routes/api/tables.d.ts +3 -0
- package/dist/routes/api/tables.js +160 -0
- package/dist/routes/api/tables.js.map +1 -0
- package/dist/routes/databases.d.ts +8 -11
- package/dist/routes/databases.js +100 -66
- package/dist/routes/databases.js.map +1 -1
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.js +20 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/pages.d.ts +3 -3
- package/dist/routes/pages.js +163 -54
- package/dist/routes/pages.js.map +1 -1
- package/dist/serverless.d.ts +40 -0
- package/dist/serverless.js +210 -0
- package/dist/serverless.js.map +1 -0
- package/dist/sql/generator.d.ts +4 -3
- package/dist/sql/generator.js +103 -6
- package/dist/sql/generator.js.map +1 -1
- package/dist/sql/index.d.ts +2 -0
- package/dist/sql/index.js +19 -0
- package/dist/sql/index.js.map +1 -0
- package/dist/types/api.d.ts +412 -0
- package/dist/types/api.js +9 -0
- package/dist/types/api.js.map +1 -0
- package/dist/utils/colors.d.ts +40 -0
- package/dist/utils/colors.js +64 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/{util.d.ts → utils/common.d.ts} +10 -6
- package/dist/{util.js → utils/common.js} +80 -13
- package/dist/utils/common.js.map +1 -0
- package/dist/utils/csv.js.map +1 -0
- package/dist/utils/datatype.d.ts +49 -0
- package/dist/utils/datatype.js +222 -0
- package/dist/utils/datatype.js.map +1 -0
- package/dist/utils/icons.d.ts +11 -0
- package/dist/utils/icons.js +72 -0
- package/dist/utils/icons.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/{logger.d.ts → utils/logger.d.ts} +1 -1
- package/dist/utils/logger.js +40 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/views/layouts/main.hbs +33 -11
- package/dist/views/pages/databases.hbs +182 -74
- package/dist/views/pages/designer.hbs +9 -6
- package/dist/views/pages/error.hbs +3 -3
- package/dist/views/pages/form.hbs +5 -3
- package/dist/views/pages/home.hbs +22 -13
- package/dist/views/pages/login.hbs +111 -0
- package/dist/views/pages/query.hbs +10 -8
- package/dist/views/pages/schema.hbs +41 -13
- package/dist/views/pages/seed.hbs +232 -0
- package/dist/views/pages/table.hbs +155 -70
- package/dist/views/partials/icon.hbs +2 -0
- package/dist/views/partials/navbar.hbs +70 -31
- package/dist/views/partials/sidebar.hbs +108 -99
- package/package.json +12 -5
- package/dist/args.d.ts +0 -28
- package/dist/args.js +0 -187
- package/dist/args.js.map +0 -1
- package/dist/config.d.ts +0 -17
- package/dist/config.js +0 -23
- package/dist/config.js.map +0 -1
- package/dist/csv.js.map +0 -1
- package/dist/logger.js +0 -25
- package/dist/logger.js.map +0 -1
- package/dist/routes/api.d.ts +0 -9
- package/dist/routes/api.js +0 -786
- package/dist/routes/api.js.map +0 -1
- package/dist/test/classifier.test.d.ts +0 -1
- package/dist/test/classifier.test.js +0 -44
- package/dist/test/classifier.test.js.map +0 -1
- package/dist/test/csv.test.d.ts +0 -1
- package/dist/test/csv.test.js +0 -64
- package/dist/test/csv.test.js.map +0 -1
- package/dist/test/database.test.d.ts +0 -1
- package/dist/test/database.test.js +0 -402
- package/dist/test/database.test.js.map +0 -1
- package/dist/test/generator.test.d.ts +0 -1
- package/dist/test/generator.test.js +0 -132
- package/dist/test/generator.test.js.map +0 -1
- package/dist/test/manager.test.d.ts +0 -1
- package/dist/test/manager.test.js +0 -146
- package/dist/test/manager.test.js.map +0 -1
- package/dist/util.js.map +0 -1
- /package/dist/{csv.d.ts → utils/csv.d.ts} +0 -0
- /package/dist/{csv.js → utils/csv.js} +0 -0
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresDatabase = void 0;
|
|
4
|
+
const pg_1 = require("pg");
|
|
5
|
+
const generator_1 = require("../sql/generator");
|
|
6
|
+
const common_1 = require("../utils/common");
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
const filters_1 = require("./filters");
|
|
9
|
+
class PostgresDatabase {
|
|
10
|
+
pool;
|
|
11
|
+
logger;
|
|
12
|
+
path;
|
|
13
|
+
isReadOnly;
|
|
14
|
+
dialect = 'postgres';
|
|
15
|
+
schema;
|
|
16
|
+
constructor(connection, logger, options = {}) {
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
const opts = typeof connection === 'string'
|
|
19
|
+
? { connectionString: connection, ...options }
|
|
20
|
+
: { ...connection, ...options };
|
|
21
|
+
this.isReadOnly = Boolean(opts.readonly);
|
|
22
|
+
this.schema = opts.schema || 'public';
|
|
23
|
+
let sslConfig = opts.ssl;
|
|
24
|
+
if (opts.connectionString) {
|
|
25
|
+
try {
|
|
26
|
+
const u = new URL(opts.connectionString);
|
|
27
|
+
const sslMode = (u.searchParams.get('sslmode') || u.searchParams.get('ssl') || '').toLowerCase();
|
|
28
|
+
const host = u.hostname.toLowerCase();
|
|
29
|
+
const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '0.0.0.0';
|
|
30
|
+
if (sslMode === 'disable') {
|
|
31
|
+
sslConfig = false;
|
|
32
|
+
}
|
|
33
|
+
else if (sslConfig === undefined) {
|
|
34
|
+
// Auto-enable SSL for remote/cloud hosts (e.g. Neon, Supabase, Render, Railway, AWS RDS, etc.)
|
|
35
|
+
// or whenever sslmode is specified or not explicitly local
|
|
36
|
+
if (sslMode === 'require' || sslMode === 'prefer' || sslMode === 'no-verify' || !isLocal) {
|
|
37
|
+
sslConfig = { rejectUnauthorized: false };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* ignore URL parse error */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else if (opts.host && sslConfig === undefined) {
|
|
46
|
+
const host = opts.host.toLowerCase();
|
|
47
|
+
const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '0.0.0.0';
|
|
48
|
+
if (!isLocal) {
|
|
49
|
+
sslConfig = { rejectUnauthorized: false };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const poolConfig = {
|
|
53
|
+
connectionString: opts.connectionString,
|
|
54
|
+
host: opts.host,
|
|
55
|
+
port: opts.port,
|
|
56
|
+
database: opts.database,
|
|
57
|
+
user: opts.user,
|
|
58
|
+
password: opts.password,
|
|
59
|
+
ssl: sslConfig,
|
|
60
|
+
max: opts.max ?? 10,
|
|
61
|
+
connectionTimeoutMillis: 10000,
|
|
62
|
+
idleTimeoutMillis: 30000,
|
|
63
|
+
keepAlive: true,
|
|
64
|
+
};
|
|
65
|
+
this.path = opts.connectionString
|
|
66
|
+
? this.sanitizeConnectionString(opts.connectionString)
|
|
67
|
+
: `${opts.user || 'postgres'}@${opts.host || 'localhost'}:${opts.port || 5432}/${opts.database || 'postgres'}`;
|
|
68
|
+
this.pool = new pg_1.Pool(poolConfig);
|
|
69
|
+
this.pool.on('error', (err) => {
|
|
70
|
+
this.logger.error(`Unexpected PostgreSQL client error: ${(0, common_1.errorMessage)(err)}`);
|
|
71
|
+
});
|
|
72
|
+
if (!this.isReadOnly) {
|
|
73
|
+
this.initSchema().catch((err) => {
|
|
74
|
+
this.logger.warn(`Failed to initialize Postgres schema: ${(0, common_1.errorMessage)(err)}`);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
this.logger.info(`Initialized PostgreSQL pool for ${this.path}${this.isReadOnly ? ' (read-only)' : ''}`);
|
|
78
|
+
}
|
|
79
|
+
sanitizeConnectionString(conn) {
|
|
80
|
+
try {
|
|
81
|
+
const url = new URL(conn);
|
|
82
|
+
if (url.password)
|
|
83
|
+
url.password = '****';
|
|
84
|
+
return url.toString();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return conn.replace(/:([^@]+)@/, ':****@');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
readonlyBlocked() {
|
|
91
|
+
return { success: false, error: 'Database is open in read-only mode — write operations are disabled.' };
|
|
92
|
+
}
|
|
93
|
+
async close() {
|
|
94
|
+
try {
|
|
95
|
+
await this.pool.end();
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
this.logger.warn(`Failed to close PostgreSQL pool: ${(0, common_1.errorMessage)(err)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async tryRun(fn) {
|
|
102
|
+
try {
|
|
103
|
+
const data = await fn();
|
|
104
|
+
return { success: true, data };
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
const msg = (0, common_1.errorMessage)(err);
|
|
108
|
+
this.logger.error(msg);
|
|
109
|
+
return { success: false, error: msg };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Run a callback inside an atomic transaction on a dedicated client. */
|
|
113
|
+
async transaction(fn) {
|
|
114
|
+
if (this.isReadOnly)
|
|
115
|
+
return this.readonlyBlocked();
|
|
116
|
+
const client = await this.pool.connect();
|
|
117
|
+
try {
|
|
118
|
+
await client.query('BEGIN');
|
|
119
|
+
const res = await fn(client);
|
|
120
|
+
await client.query('COMMIT');
|
|
121
|
+
return { success: true, data: res };
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
try {
|
|
125
|
+
await client.query('ROLLBACK');
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
/* ignore */
|
|
129
|
+
}
|
|
130
|
+
const msg = (0, common_1.errorMessage)(err);
|
|
131
|
+
this.logger.error(msg);
|
|
132
|
+
return { success: false, error: msg };
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
client.release();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async initSchema() {
|
|
139
|
+
const sql = `
|
|
140
|
+
CREATE TABLE IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (
|
|
141
|
+
id SERIAL PRIMARY KEY,
|
|
142
|
+
name VARCHAR(255) NOT NULL UNIQUE,
|
|
143
|
+
sql TEXT NOT NULL,
|
|
144
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
145
|
+
);
|
|
146
|
+
`;
|
|
147
|
+
await this.pool.query(sql);
|
|
148
|
+
}
|
|
149
|
+
hasMultipleStatements(sql) {
|
|
150
|
+
const s = String(sql ?? '').trim();
|
|
151
|
+
if (!s)
|
|
152
|
+
return false;
|
|
153
|
+
let count = 0;
|
|
154
|
+
let inString = false;
|
|
155
|
+
for (let i = 0; i < s.length; i++) {
|
|
156
|
+
const ch = s[i];
|
|
157
|
+
if (ch === "'") {
|
|
158
|
+
if (inString && s[i + 1] === "'") {
|
|
159
|
+
i++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
inString = !inString;
|
|
163
|
+
}
|
|
164
|
+
else if (ch === ';' && !inString) {
|
|
165
|
+
// Only count if there are non-whitespace characters following
|
|
166
|
+
const rest = s.slice(i + 1).trim();
|
|
167
|
+
if (rest.length > 0)
|
|
168
|
+
count++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return count > 0;
|
|
172
|
+
}
|
|
173
|
+
async all(sql, params = []) {
|
|
174
|
+
return this.tryRun(async () => {
|
|
175
|
+
const pgSql = (0, filters_1.convertPlaceholdersToPostgres)(sql);
|
|
176
|
+
const res = await this.pool.query(pgSql, params);
|
|
177
|
+
return res.rows;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async run(sql, params = []) {
|
|
181
|
+
if (this.isReadOnly)
|
|
182
|
+
return this.readonlyBlocked();
|
|
183
|
+
return this.tryRun(async () => {
|
|
184
|
+
const pgSql = (0, filters_1.convertPlaceholdersToPostgres)(sql);
|
|
185
|
+
const res = await this.pool.query(pgSql, params);
|
|
186
|
+
return {
|
|
187
|
+
changes: res.rowCount ?? 0,
|
|
188
|
+
lastInsertRowid: null,
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async execResult(sql) {
|
|
193
|
+
if (this.isReadOnly)
|
|
194
|
+
return this.readonlyBlocked();
|
|
195
|
+
return this.tryRun(async () => {
|
|
196
|
+
const res = await this.pool.query(sql);
|
|
197
|
+
const changes = Array.isArray(res)
|
|
198
|
+
? res.reduce((acc, r) => acc + (r.rowCount ?? 0), 0)
|
|
199
|
+
: res.rowCount ?? undefined;
|
|
200
|
+
return { changes };
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async runWrite(sql) {
|
|
204
|
+
if (this.isReadOnly)
|
|
205
|
+
return this.readonlyBlocked();
|
|
206
|
+
return this.tryRun(async () => {
|
|
207
|
+
const res = await this.pool.query(sql);
|
|
208
|
+
const changes = Array.isArray(res)
|
|
209
|
+
? res.reduce((acc, r) => acc + (r.rowCount ?? 0), 0)
|
|
210
|
+
: res.rowCount ?? undefined;
|
|
211
|
+
return { changes };
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
async listTables() {
|
|
215
|
+
return this.tryRun(async () => {
|
|
216
|
+
const res = await this.pool.query(`SELECT table_name AS name
|
|
217
|
+
FROM information_schema.tables
|
|
218
|
+
WHERE table_schema = $1 AND table_type = 'BASE TABLE'
|
|
219
|
+
ORDER BY table_name;`, [this.schema]);
|
|
220
|
+
return res.rows.map((r) => ({ name: r.name }));
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async getTableInfo(table) {
|
|
224
|
+
return this.tryRun(async () => {
|
|
225
|
+
// Execute all 4 metadata queries concurrently in parallel
|
|
226
|
+
const [colRes, pkRes, fkRes, idxRes] = await Promise.all([
|
|
227
|
+
// 1. Columns
|
|
228
|
+
this.pool.query(`SELECT
|
|
229
|
+
ordinal_position AS cid,
|
|
230
|
+
column_name AS name,
|
|
231
|
+
data_type AS type,
|
|
232
|
+
udt_name,
|
|
233
|
+
CASE WHEN is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,
|
|
234
|
+
column_default AS dflt_value
|
|
235
|
+
FROM information_schema.columns
|
|
236
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
237
|
+
ORDER BY ordinal_position;`, [this.schema, table]),
|
|
238
|
+
// 2. Primary Keys
|
|
239
|
+
this.pool.query(`SELECT kcu.column_name
|
|
240
|
+
FROM information_schema.table_constraints tc
|
|
241
|
+
JOIN information_schema.key_column_usage kcu
|
|
242
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
243
|
+
AND tc.table_schema = kcu.table_schema
|
|
244
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
245
|
+
AND tc.table_schema = $1
|
|
246
|
+
AND tc.table_name = $2
|
|
247
|
+
ORDER BY kcu.ordinal_position;`, [this.schema, table]),
|
|
248
|
+
// 3. Foreign Keys
|
|
249
|
+
this.pool.query(`SELECT
|
|
250
|
+
row_number() OVER () AS id,
|
|
251
|
+
kcu.position_in_unique_constraint AS seq,
|
|
252
|
+
ccu.table_name AS table,
|
|
253
|
+
kcu.column_name AS "from",
|
|
254
|
+
ccu.column_name AS "to",
|
|
255
|
+
rc.update_rule AS on_update,
|
|
256
|
+
rc.delete_rule AS on_delete
|
|
257
|
+
FROM information_schema.table_constraints AS tc
|
|
258
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
259
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
260
|
+
AND tc.table_schema = kcu.table_schema
|
|
261
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
262
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
263
|
+
AND ccu.table_schema = tc.table_schema
|
|
264
|
+
JOIN information_schema.referential_constraints AS rc
|
|
265
|
+
ON rc.constraint_name = tc.constraint_name
|
|
266
|
+
AND rc.constraint_schema = tc.table_schema
|
|
267
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
268
|
+
AND tc.table_schema = $1
|
|
269
|
+
AND tc.table_name = $2;`, [this.schema, table]),
|
|
270
|
+
// 4. Indexes
|
|
271
|
+
this.pool.query(`SELECT
|
|
272
|
+
i.relname AS name,
|
|
273
|
+
ix.indisunique AS unique,
|
|
274
|
+
pg_get_indexdef(ix.indexrelid) AS sql,
|
|
275
|
+
ARRAY(
|
|
276
|
+
SELECT pg_get_indexdef(ix.indexrelid, k + 1, true)
|
|
277
|
+
FROM generate_subscripts(ix.indkey, 1) as k
|
|
278
|
+
ORDER BY k
|
|
279
|
+
) AS columns
|
|
280
|
+
FROM pg_index ix
|
|
281
|
+
JOIN pg_class t ON t.oid = ix.indrelid
|
|
282
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
283
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
284
|
+
WHERE n.nspname = $1 AND t.relname = $2;`, [this.schema, table]),
|
|
285
|
+
]);
|
|
286
|
+
const primaryKey = pkRes.rows.map((r) => r.column_name);
|
|
287
|
+
const columns = colRes.rows.map((c) => {
|
|
288
|
+
const isPk = primaryKey.includes(c.name);
|
|
289
|
+
const displayType = c.type === 'USER-DEFINED' ? c.udt_name : c.type;
|
|
290
|
+
return {
|
|
291
|
+
cid: c.cid,
|
|
292
|
+
name: c.name,
|
|
293
|
+
type: displayType.toUpperCase(),
|
|
294
|
+
notnull: c.notnull,
|
|
295
|
+
dflt_value: c.dflt_value,
|
|
296
|
+
pk: isPk ? primaryKey.indexOf(c.name) + 1 : 0,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
const indexes = idxRes.rows.map((ix) => ({
|
|
300
|
+
name: ix.name,
|
|
301
|
+
unique: Boolean(ix.unique),
|
|
302
|
+
partial: 0,
|
|
303
|
+
origin: 'c',
|
|
304
|
+
columns: Array.isArray(ix.columns) ? ix.columns : [],
|
|
305
|
+
sql: ix.sql,
|
|
306
|
+
}));
|
|
307
|
+
// Approximate DDL for display
|
|
308
|
+
const colDefs = columns.map((c) => {
|
|
309
|
+
let def = ` ${(0, generator_1.quoteIdentifier)(c.name)} ${c.type}`;
|
|
310
|
+
if (c.notnull)
|
|
311
|
+
def += ' NOT NULL';
|
|
312
|
+
if (c.dflt_value)
|
|
313
|
+
def += ` DEFAULT ${c.dflt_value}`;
|
|
314
|
+
return def;
|
|
315
|
+
});
|
|
316
|
+
if (primaryKey.length) {
|
|
317
|
+
colDefs.push(` PRIMARY KEY (${primaryKey.map(generator_1.quoteIdentifier).join(', ')})`);
|
|
318
|
+
}
|
|
319
|
+
const synthesizedSql = `CREATE TABLE ${(0, generator_1.quoteIdentifier)(table)} (\n${colDefs.join(',\n')}\n);`;
|
|
320
|
+
return {
|
|
321
|
+
table,
|
|
322
|
+
columns,
|
|
323
|
+
foreignKeys: fkRes.rows.map((f) => ({
|
|
324
|
+
id: Number(f.id),
|
|
325
|
+
seq: Number(f.seq ?? 1),
|
|
326
|
+
table: f.table,
|
|
327
|
+
from: f.from,
|
|
328
|
+
to: f.to,
|
|
329
|
+
on_update: f.on_update,
|
|
330
|
+
on_delete: f.on_delete,
|
|
331
|
+
})),
|
|
332
|
+
primaryKey,
|
|
333
|
+
indexes,
|
|
334
|
+
sql: synthesizedSql,
|
|
335
|
+
};
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
async getRowCount(table, filters) {
|
|
339
|
+
return this.tryRun(async () => {
|
|
340
|
+
let pgWhere = '';
|
|
341
|
+
let params = [];
|
|
342
|
+
if (filters && Object.keys(filters).length > 0) {
|
|
343
|
+
const { where, params: p } = (0, filters_1.buildFilterClause)(filters, Object.keys(filters));
|
|
344
|
+
pgWhere = (0, filters_1.convertPlaceholdersToPostgres)(where);
|
|
345
|
+
params = p;
|
|
346
|
+
}
|
|
347
|
+
const sql = `SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(table)}${pgWhere}`;
|
|
348
|
+
const res = await this.pool.query(sql, params);
|
|
349
|
+
return Number(res.rows[0]?.c ?? 0);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async getRows(table, opts = {}) {
|
|
353
|
+
return this.tryRun(async () => {
|
|
354
|
+
const orderCol = opts.orderBy;
|
|
355
|
+
const orderDir = opts.orderDir === 'desc' ? 'DESC' : 'ASC';
|
|
356
|
+
const limit = opts.limit && opts.limit > 0 ? opts.limit : 200;
|
|
357
|
+
const page = opts.page && opts.page > 0 ? opts.page : 1;
|
|
358
|
+
const offset = (page - 1) * limit;
|
|
359
|
+
let pgWhere = '';
|
|
360
|
+
let filterParams = [];
|
|
361
|
+
if (opts.filters && Object.keys(opts.filters).length > 0) {
|
|
362
|
+
const { where, params } = (0, filters_1.buildFilterClause)(opts.filters, Object.keys(opts.filters));
|
|
363
|
+
pgWhere = (0, filters_1.convertPlaceholdersToPostgres)(where);
|
|
364
|
+
filterParams = params;
|
|
365
|
+
}
|
|
366
|
+
const nextParamIdx = filterParams.length + 1;
|
|
367
|
+
let sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)}${pgWhere}`;
|
|
368
|
+
if (orderCol) {
|
|
369
|
+
sql += ` ORDER BY ${(0, generator_1.quoteIdentifier)(orderCol)} ${orderDir}`;
|
|
370
|
+
}
|
|
371
|
+
sql += ` LIMIT $${nextParamIdx} OFFSET $${nextParamIdx + 1}`;
|
|
372
|
+
const allParams = [...filterParams, limit, offset];
|
|
373
|
+
const res = await this.pool.query(sql, allParams);
|
|
374
|
+
return res.rows;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
async getAllRows(table) {
|
|
378
|
+
return this.tryRun(async () => {
|
|
379
|
+
const res = await this.pool.query(`SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)}`);
|
|
380
|
+
return res.rows;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async getRow(table, where) {
|
|
384
|
+
return this.tryRun(async () => {
|
|
385
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${idx + 1}`);
|
|
386
|
+
const sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')} LIMIT 1`;
|
|
387
|
+
const res = await this.pool.query(sql, where.map((w) => w.value));
|
|
388
|
+
return res.rows[0] ?? null;
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
async insertRow(table, fields) {
|
|
392
|
+
if (this.isReadOnly)
|
|
393
|
+
return this.readonlyBlocked();
|
|
394
|
+
return this.tryRun(async () => {
|
|
395
|
+
const cols = fields.map((f) => (0, generator_1.quoteIdentifier)(f.column));
|
|
396
|
+
const placeholders = fields.map((_, idx) => `$${idx + 1}`);
|
|
397
|
+
const sql = `INSERT INTO ${(0, generator_1.quoteIdentifier)(table)} (${cols.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING *`;
|
|
398
|
+
const res = await this.pool.query(sql, fields.map((f) => f.value));
|
|
399
|
+
return {
|
|
400
|
+
changes: res.rowCount ?? 1,
|
|
401
|
+
lastInsertRowid: null,
|
|
402
|
+
};
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
async insertRows(table, rows) {
|
|
406
|
+
return this.transaction(async (client) => {
|
|
407
|
+
let inserted = 0;
|
|
408
|
+
for (const fields of rows) {
|
|
409
|
+
if (!fields.length)
|
|
410
|
+
continue;
|
|
411
|
+
const cols = fields.map((f) => (0, generator_1.quoteIdentifier)(f.column));
|
|
412
|
+
const placeholders = fields.map((_, idx) => `$${idx + 1}`);
|
|
413
|
+
const sql = `INSERT INTO ${(0, generator_1.quoteIdentifier)(table)} (${cols.join(', ')}) VALUES (${placeholders.join(', ')})`;
|
|
414
|
+
await client.query(sql, fields.map((f) => f.value));
|
|
415
|
+
inserted++;
|
|
416
|
+
}
|
|
417
|
+
return { inserted, skipped: rows.length - inserted };
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
async updateRow(table, fields, where) {
|
|
421
|
+
if (this.isReadOnly)
|
|
422
|
+
return this.readonlyBlocked();
|
|
423
|
+
return this.tryRun(async () => {
|
|
424
|
+
const sets = fields.map((f, idx) => `${(0, generator_1.quoteIdentifier)(f.column)} = $${idx + 1}`);
|
|
425
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${fields.length + idx + 1}`);
|
|
426
|
+
const sql = `UPDATE ${(0, generator_1.quoteIdentifier)(table)} SET ${sets.join(', ')} WHERE ${conds.join(' AND ')}`;
|
|
427
|
+
const res = await this.pool.query(sql, [...fields.map((f) => f.value), ...where.map((w) => w.value)]);
|
|
428
|
+
return {
|
|
429
|
+
changes: res.rowCount ?? 0,
|
|
430
|
+
lastInsertRowid: null,
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
async updateRows(table, rows) {
|
|
435
|
+
return this.transaction(async (client) => {
|
|
436
|
+
let updated = 0;
|
|
437
|
+
for (const { fields, where } of rows) {
|
|
438
|
+
if (!fields.length || !where.length)
|
|
439
|
+
continue;
|
|
440
|
+
const sets = fields.map((f, idx) => `${(0, generator_1.quoteIdentifier)(f.column)} = $${idx + 1}`);
|
|
441
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${fields.length + idx + 1}`);
|
|
442
|
+
const sql = `UPDATE ${(0, generator_1.quoteIdentifier)(table)} SET ${sets.join(', ')} WHERE ${conds.join(' AND ')}`;
|
|
443
|
+
const res = await client.query(sql, [...fields.map((f) => f.value), ...where.map((w) => w.value)]);
|
|
444
|
+
updated += res.rowCount ?? 0;
|
|
445
|
+
}
|
|
446
|
+
return { updated };
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
async deleteRow(table, where) {
|
|
450
|
+
if (this.isReadOnly)
|
|
451
|
+
return this.readonlyBlocked();
|
|
452
|
+
return this.tryRun(async () => {
|
|
453
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${idx + 1}`);
|
|
454
|
+
const sql = `DELETE FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')}`;
|
|
455
|
+
const res = await this.pool.query(sql, where.map((w) => w.value));
|
|
456
|
+
return {
|
|
457
|
+
changes: res.rowCount ?? 0,
|
|
458
|
+
lastInsertRowid: null,
|
|
459
|
+
};
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
async deleteRows(table, rows) {
|
|
463
|
+
return this.transaction(async (client) => {
|
|
464
|
+
let deleted = 0;
|
|
465
|
+
for (const where of rows) {
|
|
466
|
+
if (!where.length)
|
|
467
|
+
continue;
|
|
468
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${idx + 1}`);
|
|
469
|
+
const sql = `DELETE FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')}`;
|
|
470
|
+
const res = await client.query(sql, where.map((w) => w.value));
|
|
471
|
+
deleted += res.rowCount ?? 0;
|
|
472
|
+
}
|
|
473
|
+
return { deleted };
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
async getRowsByPks(table, pks) {
|
|
477
|
+
return this.tryRun(async () => {
|
|
478
|
+
const groups = [];
|
|
479
|
+
const params = [];
|
|
480
|
+
let paramIdx = 1;
|
|
481
|
+
for (const where of pks) {
|
|
482
|
+
if (!where.length)
|
|
483
|
+
continue;
|
|
484
|
+
const conds = where.map((w) => {
|
|
485
|
+
params.push(w.value);
|
|
486
|
+
return `${(0, generator_1.quoteIdentifier)(w.column)} = $${paramIdx++}`;
|
|
487
|
+
});
|
|
488
|
+
groups.push(conds.join(' AND '));
|
|
489
|
+
}
|
|
490
|
+
if (!groups.length)
|
|
491
|
+
return [];
|
|
492
|
+
const sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${groups.map((g) => `(${g})`).join(' OR ')}`;
|
|
493
|
+
const res = await this.pool.query(sql, params);
|
|
494
|
+
return res.rows;
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
async getRowsByFk(table, column, value, limit = 50) {
|
|
498
|
+
return this.tryRun(async () => {
|
|
499
|
+
const n = Math.max(1, Math.min(500, Number(limit) || 50));
|
|
500
|
+
const countRes = await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${(0, generator_1.quoteIdentifier)(column)} = $1`, [value]);
|
|
501
|
+
const rowsRes = await this.pool.query(`SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${(0, generator_1.quoteIdentifier)(column)} = $1 LIMIT $2`, [value, n]);
|
|
502
|
+
return { rows: rowsRes.rows, total: Number(countRes.rows[0]?.c ?? 0) };
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
async getCreateStatement(table) {
|
|
506
|
+
const info = await this.getTableInfo(table);
|
|
507
|
+
if (!info.success || !info.data)
|
|
508
|
+
return { success: false, error: info.error };
|
|
509
|
+
return { success: true, data: info.data.sql };
|
|
510
|
+
}
|
|
511
|
+
async listSavedQueries() {
|
|
512
|
+
return this.tryRun(async () => {
|
|
513
|
+
const res = await this.pool.query(`SELECT id, name, sql, created_at FROM ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} ORDER BY name`);
|
|
514
|
+
return res.rows;
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
async saveQuery(name, sql) {
|
|
518
|
+
return this.tryRun(async () => {
|
|
519
|
+
const res = await this.pool.query(`INSERT INTO ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (name, sql) VALUES ($1, $2) RETURNING id`, [name, sql]);
|
|
520
|
+
return {
|
|
521
|
+
changes: res.rowCount ?? 1,
|
|
522
|
+
lastInsertRowid: Number(res.rows[0]?.id ?? null),
|
|
523
|
+
};
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
async deleteSavedQuery(id) {
|
|
527
|
+
return this.tryRun(async () => {
|
|
528
|
+
const res = await this.pool.query(`DELETE FROM ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} WHERE id = $1`, [Number(id)]);
|
|
529
|
+
return {
|
|
530
|
+
changes: res.rowCount ?? 0,
|
|
531
|
+
lastInsertRowid: null,
|
|
532
|
+
};
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
async getSchema(table) {
|
|
536
|
+
return this.tryRun(async () => {
|
|
537
|
+
const infoR = await this.getTableInfo(table);
|
|
538
|
+
if (!infoR.success || !infoR.data)
|
|
539
|
+
throw new Error(infoR.error || `Table ${table} not found.`);
|
|
540
|
+
const { columns, foreignKeys, indexes, primaryKey } = infoR.data;
|
|
541
|
+
// Find references in other tables that point to this table
|
|
542
|
+
const refRes = await this.pool.query(`SELECT
|
|
543
|
+
tc.table_name AS table,
|
|
544
|
+
kcu.column_name AS "from",
|
|
545
|
+
ccu.column_name AS "to"
|
|
546
|
+
FROM information_schema.table_constraints AS tc
|
|
547
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
548
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
549
|
+
AND tc.table_schema = kcu.table_schema
|
|
550
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
551
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
552
|
+
AND ccu.table_schema = tc.table_schema
|
|
553
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
554
|
+
AND tc.table_schema = $1
|
|
555
|
+
AND ccu.table_name = $2;`, [this.schema, table]);
|
|
556
|
+
const references = refRes.rows;
|
|
557
|
+
const columnDetails = columns.map((col) => {
|
|
558
|
+
const isPk = primaryKey.includes(col.name);
|
|
559
|
+
const fk = foreignKeys.find((f) => f.from === col.name) ?? null;
|
|
560
|
+
const matchingIndexes = indexes.filter((i) => i.columns.includes(col.name));
|
|
561
|
+
const unique = matchingIndexes.some((i) => i.unique && i.columns.length === 1 && i.columns[0] === col.name);
|
|
562
|
+
const indexed = matchingIndexes.length > 0;
|
|
563
|
+
const referencedBy = references
|
|
564
|
+
.filter((r) => r.to === col.name)
|
|
565
|
+
.map((r) => ({ table: r.table, from: r.from }));
|
|
566
|
+
const dropBlockers = [];
|
|
567
|
+
if (isPk && primaryKey.length === 1)
|
|
568
|
+
dropBlockers.push('Primary key');
|
|
569
|
+
if (referencedBy.length > 0) {
|
|
570
|
+
dropBlockers.push(`Referenced by ${referencedBy.map((r) => `${r.table}.${r.from}`).join(', ')}`);
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
...col,
|
|
574
|
+
unique,
|
|
575
|
+
indexed,
|
|
576
|
+
referencedBy,
|
|
577
|
+
fk,
|
|
578
|
+
canDrop: dropBlockers.length === 0,
|
|
579
|
+
dropBlockers,
|
|
580
|
+
};
|
|
581
|
+
});
|
|
582
|
+
return {
|
|
583
|
+
table,
|
|
584
|
+
columns: columnDetails,
|
|
585
|
+
foreignKeys,
|
|
586
|
+
indexes,
|
|
587
|
+
references,
|
|
588
|
+
};
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
async getReferencingTables(table) {
|
|
592
|
+
return this.tryRun(async () => {
|
|
593
|
+
const refRes = await this.pool.query(`SELECT
|
|
594
|
+
tc.table_name AS table,
|
|
595
|
+
kcu.column_name AS "from",
|
|
596
|
+
ccu.column_name AS "to"
|
|
597
|
+
FROM information_schema.table_constraints AS tc
|
|
598
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
599
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
600
|
+
AND tc.table_schema = kcu.table_schema
|
|
601
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
602
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
603
|
+
AND ccu.table_schema = tc.table_schema
|
|
604
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
605
|
+
AND tc.table_schema = $1
|
|
606
|
+
AND ccu.table_name = $2;`, [this.schema, table]);
|
|
607
|
+
const byTable = new Map();
|
|
608
|
+
for (const r of refRes.rows) {
|
|
609
|
+
if (!byTable.has(r.table))
|
|
610
|
+
byTable.set(r.table, []);
|
|
611
|
+
byTable.get(r.table).push({ from: r.from, to: r.to });
|
|
612
|
+
}
|
|
613
|
+
const results = [];
|
|
614
|
+
for (const [tbl, refs] of byTable.entries()) {
|
|
615
|
+
try {
|
|
616
|
+
const countRes = await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(tbl)} WHERE ${refs.map((r) => `${(0, generator_1.quoteIdentifier)(r.from)} IS NOT NULL`).join(' OR ')}`);
|
|
617
|
+
results.push({
|
|
618
|
+
table: tbl,
|
|
619
|
+
refs,
|
|
620
|
+
refCount: Number(countRes.rows[0]?.c ?? 0),
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
results.push({ table: tbl, refs, refCount: 0 });
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return results;
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
async renameTable(oldName, newName) {
|
|
631
|
+
if (this.isReadOnly)
|
|
632
|
+
return this.readonlyBlocked();
|
|
633
|
+
return this.tryRun(async () => {
|
|
634
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(oldName)} RENAME TO ${(0, generator_1.quoteIdentifier)(newName)};`);
|
|
635
|
+
return { changes: 1 };
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
async addColumn(table, col) {
|
|
639
|
+
if (this.isReadOnly)
|
|
640
|
+
return this.readonlyBlocked();
|
|
641
|
+
return this.tryRun(async () => {
|
|
642
|
+
let sql = `ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ADD COLUMN ${(0, generator_1.quoteIdentifier)(col.name)} ${col.type}`;
|
|
643
|
+
if (col.notNull)
|
|
644
|
+
sql += ' NOT NULL';
|
|
645
|
+
if (col.defaultValue !== undefined && col.defaultValue !== null && col.defaultValue !== '') {
|
|
646
|
+
sql += ` DEFAULT ${col.defaultValue}`;
|
|
647
|
+
}
|
|
648
|
+
if (col.foreignKey) {
|
|
649
|
+
sql += ` REFERENCES ${(0, generator_1.quoteIdentifier)(col.foreignKey.table)} (${(0, generator_1.quoteIdentifier)(col.foreignKey.column)})`;
|
|
650
|
+
}
|
|
651
|
+
await this.pool.query(sql);
|
|
652
|
+
if (col.unique) {
|
|
653
|
+
const idxName = `idx_${table}_${col.name}_unique`;
|
|
654
|
+
await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(idxName)} ON ${(0, generator_1.quoteIdentifier)(table)} (${(0, generator_1.quoteIdentifier)(col.name)});`);
|
|
655
|
+
}
|
|
656
|
+
return { changes: 1 };
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
async modifyColumn(table, oldColName, newColDef) {
|
|
660
|
+
if (this.isReadOnly)
|
|
661
|
+
return this.readonlyBlocked();
|
|
662
|
+
return this.tryRun(async () => {
|
|
663
|
+
const newName = String(newColDef.name || oldColName).trim();
|
|
664
|
+
// Rename column first if name changed
|
|
665
|
+
if (oldColName !== newName) {
|
|
666
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0, generator_1.quoteIdentifier)(oldColName)} TO ${(0, generator_1.quoteIdentifier)(newName)};`);
|
|
667
|
+
}
|
|
668
|
+
const colToModify = newName;
|
|
669
|
+
// Alter Type if provided
|
|
670
|
+
if (newColDef.type) {
|
|
671
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} TYPE ${newColDef.type} USING ${(0, generator_1.quoteIdentifier)(colToModify)}::${newColDef.type};`);
|
|
672
|
+
}
|
|
673
|
+
// Alter NOT NULL
|
|
674
|
+
if (newColDef.notNull !== undefined) {
|
|
675
|
+
const action = newColDef.notNull ? 'SET NOT NULL' : 'DROP NOT NULL';
|
|
676
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} ${action};`);
|
|
677
|
+
}
|
|
678
|
+
// Alter DEFAULT
|
|
679
|
+
if (newColDef.defaultValue !== undefined) {
|
|
680
|
+
if (newColDef.defaultValue === null || newColDef.defaultValue === '') {
|
|
681
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} DROP DEFAULT;`);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} SET DEFAULT ${newColDef.defaultValue};`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Handle unique index
|
|
688
|
+
if (newColDef.unique !== undefined) {
|
|
689
|
+
const idxName = `idx_${table}_${colToModify}_unique`;
|
|
690
|
+
if (newColDef.unique) {
|
|
691
|
+
await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(idxName)} ON ${(0, generator_1.quoteIdentifier)(table)} (${(0, generator_1.quoteIdentifier)(colToModify)});`);
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
await this.pool.query(`DROP INDEX IF EXISTS ${(0, generator_1.quoteIdentifier)(idxName)};`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return { changes: 1 };
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
async renameColumn(table, oldName, newName) {
|
|
701
|
+
if (this.isReadOnly)
|
|
702
|
+
return this.readonlyBlocked();
|
|
703
|
+
return this.tryRun(async () => {
|
|
704
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0, generator_1.quoteIdentifier)(oldName)} TO ${(0, generator_1.quoteIdentifier)(newName)};`);
|
|
705
|
+
return { changes: 1 };
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
async dropColumn(table, column) {
|
|
709
|
+
if (this.isReadOnly)
|
|
710
|
+
return this.readonlyBlocked();
|
|
711
|
+
return this.tryRun(async () => {
|
|
712
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} DROP COLUMN ${(0, generator_1.quoteIdentifier)(column)} CASCADE;`);
|
|
713
|
+
return { changes: 1 };
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
async dropTable(table) {
|
|
717
|
+
if (this.isReadOnly)
|
|
718
|
+
return this.readonlyBlocked();
|
|
719
|
+
return this.tryRun(async () => {
|
|
720
|
+
await this.pool.query(`DROP TABLE ${(0, generator_1.quoteIdentifier)(table)} CASCADE;`);
|
|
721
|
+
return { changes: 1 };
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
async createIndex(table, index) {
|
|
725
|
+
if (this.isReadOnly)
|
|
726
|
+
return this.readonlyBlocked();
|
|
727
|
+
return this.tryRun(async () => {
|
|
728
|
+
const unique = index.unique ? 'UNIQUE ' : '';
|
|
729
|
+
const cols = index.columns.map(generator_1.quoteIdentifier).join(', ');
|
|
730
|
+
const name = index.name || `idx_${table}_${index.columns.join('_')}`;
|
|
731
|
+
await this.pool.query(`CREATE ${unique}INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(name)} ON ${(0, generator_1.quoteIdentifier)(table)} (${cols});`);
|
|
732
|
+
return { changes: 1 };
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
async dropIndex(indexName) {
|
|
736
|
+
if (this.isReadOnly)
|
|
737
|
+
return this.readonlyBlocked();
|
|
738
|
+
return this.tryRun(async () => {
|
|
739
|
+
await this.pool.query(`DROP INDEX IF EXISTS ${(0, generator_1.quoteIdentifier)(indexName)} CASCADE;`);
|
|
740
|
+
return { changes: 1 };
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
exports.PostgresDatabase = PostgresDatabase;
|
|
745
|
+
//# sourceMappingURL=postgres.js.map
|