@ductape/sdk 0.0.4-v41 → 0.0.4-v43
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/apps/services/app.service.d.ts +10 -0
- package/dist/apps/services/app.service.js +22 -0
- package/dist/apps/services/app.service.js.map +1 -1
- package/dist/database/adapters/base.adapter.d.ts +176 -0
- package/dist/database/adapters/base.adapter.js +31 -0
- package/dist/database/adapters/base.adapter.js.map +1 -0
- package/dist/database/adapters/dynamodb.adapter.d.ts +83 -0
- package/dist/database/adapters/dynamodb.adapter.js +1237 -0
- package/dist/database/adapters/dynamodb.adapter.js.map +1 -0
- package/dist/database/adapters/mongodb.adapter.d.ts +70 -0
- package/dist/database/adapters/mongodb.adapter.js +717 -0
- package/dist/database/adapters/mongodb.adapter.js.map +1 -0
- package/dist/database/adapters/mysql.adapter.d.ts +141 -0
- package/dist/database/adapters/mysql.adapter.js +1221 -0
- package/dist/database/adapters/mysql.adapter.js.map +1 -0
- package/dist/database/adapters/postgresql.adapter.d.ts +142 -0
- package/dist/database/adapters/postgresql.adapter.js +1288 -0
- package/dist/database/adapters/postgresql.adapter.js.map +1 -0
- package/dist/database/database.service.d.ts +190 -0
- package/dist/database/database.service.js +552 -0
- package/dist/database/database.service.js.map +1 -0
- package/dist/database/index.d.ts +18 -0
- package/dist/database/index.js +98 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/types/aggregation.types.d.ts +202 -0
- package/dist/database/types/aggregation.types.js +21 -0
- package/dist/database/types/aggregation.types.js.map +1 -0
- package/dist/database/types/connection.types.d.ts +132 -0
- package/dist/database/types/connection.types.js +6 -0
- package/dist/database/types/connection.types.js.map +1 -0
- package/dist/database/types/database.types.d.ts +173 -0
- package/dist/database/types/database.types.js +73 -0
- package/dist/database/types/database.types.js.map +1 -0
- package/dist/database/types/index.d.ts +12 -0
- package/dist/database/types/index.js +37 -0
- package/dist/database/types/index.js.map +1 -0
- package/dist/database/types/index.types.d.ts +220 -0
- package/dist/database/types/index.types.js +27 -0
- package/dist/database/types/index.types.js.map +1 -0
- package/dist/database/types/migration.types.d.ts +205 -0
- package/dist/database/types/migration.types.js +44 -0
- package/dist/database/types/migration.types.js.map +1 -0
- package/dist/database/types/query.types.d.ts +274 -0
- package/dist/database/types/query.types.js +57 -0
- package/dist/database/types/query.types.js.map +1 -0
- package/dist/database/types/result.types.d.ts +218 -0
- package/dist/database/types/result.types.js +6 -0
- package/dist/database/types/result.types.js.map +1 -0
- package/dist/database/types/schema.types.d.ts +190 -0
- package/dist/database/types/schema.types.js +69 -0
- package/dist/database/types/schema.types.js.map +1 -0
- package/dist/database/utils/helpers.d.ts +66 -0
- package/dist/database/utils/helpers.js +501 -0
- package/dist/database/utils/helpers.js.map +1 -0
- package/dist/database/utils/migration.utils.d.ts +151 -0
- package/dist/database/utils/migration.utils.js +476 -0
- package/dist/database/utils/migration.utils.js.map +1 -0
- package/dist/database/utils/transaction.d.ts +64 -0
- package/dist/database/utils/transaction.js +130 -0
- package/dist/database/utils/transaction.js.map +1 -0
- package/dist/database/validators/connection.validator.d.ts +20 -0
- package/dist/database/validators/connection.validator.js +267 -0
- package/dist/database/validators/connection.validator.js.map +1 -0
- package/dist/database/validators/query.validator.d.ts +31 -0
- package/dist/database/validators/query.validator.js +305 -0
- package/dist/database/validators/query.validator.js.map +1 -0
- package/dist/database/validators/schema.validator.d.ts +31 -0
- package/dist/database/validators/schema.validator.js +334 -0
- package/dist/database/validators/schema.validator.js.map +1 -0
- package/dist/index.d.ts +25 -4
- package/dist/index.js +36 -4
- package/dist/index.js.map +1 -1
- package/dist/processor/services/processor.service.js +10 -8
- package/dist/processor/services/processor.service.js.map +1 -1
- package/dist/products/services/utils/functions.utils.d.ts +1 -1
- package/dist/products/services/utils/functions.utils.js +1 -6
- package/dist/products/services/utils/functions.utils.js.map +1 -1
- package/dist/types/processor.types.d.ts +2 -2
- package/package.json +3 -1
|
@@ -0,0 +1,1288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PostgreSQL Database Adapter
|
|
4
|
+
* Implements database operations for PostgreSQL
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.PostgreSQLAdapter = void 0;
|
|
8
|
+
const base_adapter_1 = require("./base.adapter");
|
|
9
|
+
const database_types_1 = require("../types/database.types");
|
|
10
|
+
const schema_types_1 = require("../types/schema.types");
|
|
11
|
+
const migration_types_1 = require("../types/migration.types");
|
|
12
|
+
/**
|
|
13
|
+
* PostgreSQL Connection wrapper
|
|
14
|
+
*/
|
|
15
|
+
class PostgreSQLConnection {
|
|
16
|
+
constructor(id, pool) {
|
|
17
|
+
this.id = id;
|
|
18
|
+
this.type = database_types_1.DatabaseType.POSTGRESQL;
|
|
19
|
+
this.status = database_types_1.ConnectionStatus.CONNECTED;
|
|
20
|
+
this.pool = pool;
|
|
21
|
+
}
|
|
22
|
+
async connect() {
|
|
23
|
+
this.status = database_types_1.ConnectionStatus.CONNECTED;
|
|
24
|
+
}
|
|
25
|
+
async disconnect() {
|
|
26
|
+
if (this.pool) {
|
|
27
|
+
await this.pool.end();
|
|
28
|
+
this.status = database_types_1.ConnectionStatus.DISCONNECTED;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
isConnected() {
|
|
32
|
+
return this.status === database_types_1.ConnectionStatus.CONNECTED;
|
|
33
|
+
}
|
|
34
|
+
getClient() {
|
|
35
|
+
return this.pool;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* PostgreSQL Adapter
|
|
40
|
+
*/
|
|
41
|
+
class PostgreSQLAdapter extends base_adapter_1.BaseDatabaseAdapter {
|
|
42
|
+
constructor() {
|
|
43
|
+
super(...arguments);
|
|
44
|
+
this.type = database_types_1.DatabaseType.POSTGRESQL;
|
|
45
|
+
this.connectionPool = new Map();
|
|
46
|
+
}
|
|
47
|
+
// ==================== Connection Methods ====================
|
|
48
|
+
async connect(config) {
|
|
49
|
+
try {
|
|
50
|
+
// TODO: Import pg library dynamically
|
|
51
|
+
// const { Pool } = await import('pg');
|
|
52
|
+
// TODO: Create connection pool
|
|
53
|
+
// const pool = new Pool({
|
|
54
|
+
// host: config.host,
|
|
55
|
+
// port: config.port || 5432,
|
|
56
|
+
// database: config.database,
|
|
57
|
+
// user: config.user,
|
|
58
|
+
// password: config.password,
|
|
59
|
+
// ssl: config.ssl,
|
|
60
|
+
// max: config.poolSize || 10,
|
|
61
|
+
// connectionTimeoutMillis: config.connectionTimeout || 5000,
|
|
62
|
+
// });
|
|
63
|
+
// TODO: Test connection
|
|
64
|
+
// await pool.query('SELECT 1');
|
|
65
|
+
// TODO: Create connection wrapper
|
|
66
|
+
// const connectionId = `postgresql-${config.database}-${Date.now()}`;
|
|
67
|
+
// const connection = new PostgreSQLConnection(connectionId, pool);
|
|
68
|
+
// return connection;
|
|
69
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.CONNECTION_ERROR, 'PostgreSQL adapter not fully implemented yet - connection pending pg library integration');
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.CONNECTION_ERROR, `Failed to connect to PostgreSQL: ${error.message}`, error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async disconnect(connection) {
|
|
76
|
+
await connection.disconnect();
|
|
77
|
+
this.connectionPool.delete(connection.id);
|
|
78
|
+
}
|
|
79
|
+
async testConnection(connection) {
|
|
80
|
+
var _a;
|
|
81
|
+
const startTime = Date.now();
|
|
82
|
+
try {
|
|
83
|
+
const pool = connection.getClient();
|
|
84
|
+
const result = await pool.query('SELECT version()');
|
|
85
|
+
const responseTime = Date.now() - startTime;
|
|
86
|
+
return {
|
|
87
|
+
connected: true,
|
|
88
|
+
message: 'Successfully connected to PostgreSQL',
|
|
89
|
+
databaseType: 'PostgreSQL',
|
|
90
|
+
version: (_a = result.rows[0]) === null || _a === void 0 ? void 0 : _a.version,
|
|
91
|
+
responseTime,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
connected: false,
|
|
97
|
+
message: 'Failed to connect to PostgreSQL',
|
|
98
|
+
databaseType: 'PostgreSQL',
|
|
99
|
+
responseTime: Date.now() - startTime,
|
|
100
|
+
error: error.message,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// ==================== Transaction Methods ====================
|
|
105
|
+
async beginTransaction(connection) {
|
|
106
|
+
const client = connection.getClient();
|
|
107
|
+
await client.query('BEGIN');
|
|
108
|
+
return { client };
|
|
109
|
+
}
|
|
110
|
+
async commitTransaction(connection, transaction) {
|
|
111
|
+
await transaction.client.query('COMMIT');
|
|
112
|
+
}
|
|
113
|
+
async rollbackTransaction(connection, transaction) {
|
|
114
|
+
await transaction.client.query('ROLLBACK');
|
|
115
|
+
}
|
|
116
|
+
async createSavepoint(connection, transaction, savepointName) {
|
|
117
|
+
await transaction.client.query(`SAVEPOINT ${savepointName}`);
|
|
118
|
+
}
|
|
119
|
+
async rollbackToSavepoint(connection, transaction, savepointName) {
|
|
120
|
+
await transaction.client.query(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
|
121
|
+
}
|
|
122
|
+
async releaseSavepoint(connection, transaction, savepointName) {
|
|
123
|
+
await transaction.client.query(`RELEASE SAVEPOINT ${savepointName}`);
|
|
124
|
+
}
|
|
125
|
+
// ==================== Query Methods ====================
|
|
126
|
+
async query(connection, options) {
|
|
127
|
+
const startTime = Date.now();
|
|
128
|
+
try {
|
|
129
|
+
const { query, params } = this.buildSelectQuery(options);
|
|
130
|
+
const client = connection.getClient();
|
|
131
|
+
const result = await client.query(query, params);
|
|
132
|
+
const executionTime = Date.now() - startTime;
|
|
133
|
+
return {
|
|
134
|
+
data: result.rows,
|
|
135
|
+
count: result.rowCount,
|
|
136
|
+
executionTime,
|
|
137
|
+
databaseType: 'PostgreSQL',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL query failed: ${error.message}`, error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async insert(connection, options) {
|
|
145
|
+
const startTime = Date.now();
|
|
146
|
+
try {
|
|
147
|
+
const { query, params } = this.buildInsertQuery(options);
|
|
148
|
+
const client = connection.getClient();
|
|
149
|
+
const result = await client.query(query, params);
|
|
150
|
+
const executionTime = Date.now() - startTime;
|
|
151
|
+
return {
|
|
152
|
+
insertedCount: result.rowCount,
|
|
153
|
+
insertedIds: result.rows.map((row) => row.id || row[Object.keys(row)[0]]),
|
|
154
|
+
data: options.returning ? result.rows : undefined,
|
|
155
|
+
executionTime,
|
|
156
|
+
success: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL insert failed: ${error.message}`, error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async update(connection, options) {
|
|
164
|
+
const startTime = Date.now();
|
|
165
|
+
try {
|
|
166
|
+
const { query, params } = this.buildUpdateQuery(options);
|
|
167
|
+
const client = connection.getClient();
|
|
168
|
+
const result = await client.query(query, params);
|
|
169
|
+
const executionTime = Date.now() - startTime;
|
|
170
|
+
return {
|
|
171
|
+
updatedCount: result.rowCount,
|
|
172
|
+
matchedCount: result.rowCount,
|
|
173
|
+
data: options.returning ? result.rows : undefined,
|
|
174
|
+
executionTime,
|
|
175
|
+
success: true,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL update failed: ${error.message}`, error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async delete(connection, options) {
|
|
183
|
+
const startTime = Date.now();
|
|
184
|
+
try {
|
|
185
|
+
const { query, params } = this.buildDeleteQuery(options);
|
|
186
|
+
const client = connection.getClient();
|
|
187
|
+
const result = await client.query(query, params);
|
|
188
|
+
const executionTime = Date.now() - startTime;
|
|
189
|
+
return {
|
|
190
|
+
deletedCount: result.rowCount,
|
|
191
|
+
data: options.returning ? result.rows : undefined,
|
|
192
|
+
executionTime,
|
|
193
|
+
success: true,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL delete failed: ${error.message}`, error);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async upsert(connection, options) {
|
|
201
|
+
const startTime = Date.now();
|
|
202
|
+
try {
|
|
203
|
+
const { query, params } = this.buildUpsertQuery(options);
|
|
204
|
+
const client = connection.getClient();
|
|
205
|
+
const result = await client.query(query, params);
|
|
206
|
+
const executionTime = Date.now() - startTime;
|
|
207
|
+
return {
|
|
208
|
+
insertedCount: 0, // PostgreSQL doesn't distinguish in upsert
|
|
209
|
+
updatedCount: 0,
|
|
210
|
+
affectedCount: result.rowCount,
|
|
211
|
+
data: options.returning ? result.rows : undefined,
|
|
212
|
+
executionTime,
|
|
213
|
+
success: true,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL upsert failed: ${error.message}`, error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async executeRaw(connection, options) {
|
|
221
|
+
var _a;
|
|
222
|
+
const startTime = Date.now();
|
|
223
|
+
try {
|
|
224
|
+
const client = connection.getClient();
|
|
225
|
+
const result = await client.query(options.query, options.params || []);
|
|
226
|
+
const executionTime = Date.now() - startTime;
|
|
227
|
+
return {
|
|
228
|
+
rows: result.rows,
|
|
229
|
+
rowCount: result.rowCount,
|
|
230
|
+
fields: (_a = result.fields) === null || _a === void 0 ? void 0 : _a.map((f) => ({
|
|
231
|
+
name: f.name,
|
|
232
|
+
type: f.dataTypeID,
|
|
233
|
+
})),
|
|
234
|
+
executionTime,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL raw query failed: ${error.message}`, error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// ==================== Aggregation Methods ====================
|
|
242
|
+
async count(connection, options) {
|
|
243
|
+
try {
|
|
244
|
+
const { query, params } = this.buildCountQuery(options);
|
|
245
|
+
const client = connection.getClient();
|
|
246
|
+
const result = await client.query(query, params);
|
|
247
|
+
return parseInt(result.rows[0].count, 10);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL count failed: ${error.message}`, error);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async sum(connection, options) {
|
|
254
|
+
try {
|
|
255
|
+
const { query, params } = this.buildAggregateQuery('SUM', options.column, options);
|
|
256
|
+
const client = connection.getClient();
|
|
257
|
+
const result = await client.query(query, params);
|
|
258
|
+
return parseFloat(result.rows[0].result) || 0;
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL sum failed: ${error.message}`, error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async avg(connection, options) {
|
|
265
|
+
try {
|
|
266
|
+
const { query, params } = this.buildAggregateQuery('AVG', options.column, options);
|
|
267
|
+
const client = connection.getClient();
|
|
268
|
+
const result = await client.query(query, params);
|
|
269
|
+
return parseFloat(result.rows[0].result) || 0;
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL avg failed: ${error.message}`, error);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async min(connection, options) {
|
|
276
|
+
try {
|
|
277
|
+
const { query, params } = this.buildAggregateQuery('MIN', options.column, options);
|
|
278
|
+
const client = connection.getClient();
|
|
279
|
+
const result = await client.query(query, params);
|
|
280
|
+
return result.rows[0].result;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL min failed: ${error.message}`, error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async max(connection, options) {
|
|
287
|
+
try {
|
|
288
|
+
const { query, params } = this.buildAggregateQuery('MAX', options.column, options);
|
|
289
|
+
const client = connection.getClient();
|
|
290
|
+
const result = await client.query(query, params);
|
|
291
|
+
return result.rows[0].result;
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL max failed: ${error.message}`, error);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async groupBy(connection, options) {
|
|
298
|
+
try {
|
|
299
|
+
const { query, params } = this.buildGroupByQuery(options);
|
|
300
|
+
const client = connection.getClient();
|
|
301
|
+
const result = await client.query(query, params);
|
|
302
|
+
return result.rows;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL groupBy failed: ${error.message}`, error);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async aggregate(connection, options) {
|
|
309
|
+
try {
|
|
310
|
+
const { query, params } = this.buildMultiAggregateQuery(options);
|
|
311
|
+
const client = connection.getClient();
|
|
312
|
+
const result = await client.query(query, params);
|
|
313
|
+
return result.rows[0] || {};
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `PostgreSQL aggregate failed: ${error.message}`, error);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// ==================== Schema Methods ====================
|
|
320
|
+
async createTable(connection, schema, options) {
|
|
321
|
+
const startTime = Date.now();
|
|
322
|
+
try {
|
|
323
|
+
const query = this.buildCreateTableQuery(schema, options);
|
|
324
|
+
const client = connection.getClient();
|
|
325
|
+
await client.query(query);
|
|
326
|
+
const executionTime = Date.now() - startTime;
|
|
327
|
+
return {
|
|
328
|
+
success: true,
|
|
329
|
+
operation: 'create',
|
|
330
|
+
table: schema.name,
|
|
331
|
+
executionTime,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
return {
|
|
336
|
+
success: false,
|
|
337
|
+
operation: 'create',
|
|
338
|
+
table: schema.name,
|
|
339
|
+
error: error.message,
|
|
340
|
+
executionTime: Date.now() - startTime,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async dropTable(connection, tableName) {
|
|
345
|
+
const startTime = Date.now();
|
|
346
|
+
try {
|
|
347
|
+
const client = connection.getClient();
|
|
348
|
+
await client.query(`DROP TABLE IF EXISTS "${tableName}"`);
|
|
349
|
+
return {
|
|
350
|
+
success: true,
|
|
351
|
+
operation: 'drop',
|
|
352
|
+
table: tableName,
|
|
353
|
+
executionTime: Date.now() - startTime,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
return {
|
|
358
|
+
success: false,
|
|
359
|
+
operation: 'drop',
|
|
360
|
+
table: tableName,
|
|
361
|
+
error: error.message,
|
|
362
|
+
executionTime: Date.now() - startTime,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async alterTable(connection, tableName, alterations, options) {
|
|
367
|
+
const startTime = Date.now();
|
|
368
|
+
try {
|
|
369
|
+
const queries = this.buildAlterTableQueries(tableName, alterations);
|
|
370
|
+
const client = connection.getClient();
|
|
371
|
+
for (const query of queries) {
|
|
372
|
+
await client.query(query);
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
success: true,
|
|
376
|
+
operation: 'alter',
|
|
377
|
+
table: tableName,
|
|
378
|
+
executionTime: Date.now() - startTime,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
return {
|
|
383
|
+
success: false,
|
|
384
|
+
operation: 'alter',
|
|
385
|
+
table: tableName,
|
|
386
|
+
error: error.message,
|
|
387
|
+
executionTime: Date.now() - startTime,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async getTableSchema(connection, tableName) {
|
|
392
|
+
try {
|
|
393
|
+
const client = connection.getClient();
|
|
394
|
+
const query = `
|
|
395
|
+
SELECT
|
|
396
|
+
column_name,
|
|
397
|
+
data_type,
|
|
398
|
+
is_nullable,
|
|
399
|
+
column_default,
|
|
400
|
+
character_maximum_length,
|
|
401
|
+
numeric_precision,
|
|
402
|
+
numeric_scale
|
|
403
|
+
FROM information_schema.columns
|
|
404
|
+
WHERE table_name = $1
|
|
405
|
+
ORDER BY ordinal_position
|
|
406
|
+
`;
|
|
407
|
+
const result = await client.query(query, [tableName]);
|
|
408
|
+
const columns = result.rows.map((row) => ({
|
|
409
|
+
name: row.column_name,
|
|
410
|
+
type: this.mapPostgreSQLTypeToColumnType(row.data_type),
|
|
411
|
+
nullable: row.is_nullable === 'YES',
|
|
412
|
+
defaultValue: row.column_default,
|
|
413
|
+
length: row.character_maximum_length,
|
|
414
|
+
precision: row.numeric_precision,
|
|
415
|
+
scale: row.numeric_scale,
|
|
416
|
+
}));
|
|
417
|
+
return {
|
|
418
|
+
name: tableName,
|
|
419
|
+
columns,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.SCHEMA_ERROR, `Failed to get PostgreSQL table schema: ${error.message}`, error);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async listTables(connection) {
|
|
427
|
+
try {
|
|
428
|
+
const client = connection.getClient();
|
|
429
|
+
const query = `
|
|
430
|
+
SELECT tablename
|
|
431
|
+
FROM pg_tables
|
|
432
|
+
WHERE schemaname = 'public'
|
|
433
|
+
ORDER BY tablename
|
|
434
|
+
`;
|
|
435
|
+
const result = await client.query(query);
|
|
436
|
+
return result.rows.map((row) => row.tablename);
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `Failed to list PostgreSQL tables: ${error.message}`, error);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async tableExists(connection, tableName) {
|
|
443
|
+
try {
|
|
444
|
+
const client = connection.getClient();
|
|
445
|
+
const query = `
|
|
446
|
+
SELECT EXISTS (
|
|
447
|
+
SELECT FROM information_schema.tables
|
|
448
|
+
WHERE table_schema = 'public'
|
|
449
|
+
AND table_name = $1
|
|
450
|
+
)
|
|
451
|
+
`;
|
|
452
|
+
const result = await client.query(query, [tableName]);
|
|
453
|
+
return result.rows[0].exists;
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// ==================== Index Methods ====================
|
|
460
|
+
async createIndex(connection, options) {
|
|
461
|
+
const startTime = Date.now();
|
|
462
|
+
try {
|
|
463
|
+
const query = this.buildCreateIndexQuery(options);
|
|
464
|
+
const client = connection.getClient();
|
|
465
|
+
await client.query(query);
|
|
466
|
+
return {
|
|
467
|
+
success: true,
|
|
468
|
+
operation: 'create',
|
|
469
|
+
indexName: options.index.name,
|
|
470
|
+
table: options.table,
|
|
471
|
+
executionTime: Date.now() - startTime,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
return {
|
|
476
|
+
success: false,
|
|
477
|
+
operation: 'create',
|
|
478
|
+
indexName: options.index.name,
|
|
479
|
+
table: options.table,
|
|
480
|
+
error: error.message,
|
|
481
|
+
executionTime: Date.now() - startTime,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async dropIndex(connection, options) {
|
|
486
|
+
const startTime = Date.now();
|
|
487
|
+
try {
|
|
488
|
+
const client = connection.getClient();
|
|
489
|
+
await client.query(`DROP INDEX IF EXISTS "${options.indexName}"`);
|
|
490
|
+
return {
|
|
491
|
+
success: true,
|
|
492
|
+
operation: 'drop',
|
|
493
|
+
indexName: options.indexName,
|
|
494
|
+
table: options.table,
|
|
495
|
+
executionTime: Date.now() - startTime,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
return {
|
|
500
|
+
success: false,
|
|
501
|
+
operation: 'drop',
|
|
502
|
+
indexName: options.indexName,
|
|
503
|
+
table: options.table,
|
|
504
|
+
error: error.message,
|
|
505
|
+
executionTime: Date.now() - startTime,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async listIndexes(connection, options) {
|
|
510
|
+
try {
|
|
511
|
+
const client = connection.getClient();
|
|
512
|
+
const query = `
|
|
513
|
+
SELECT
|
|
514
|
+
i.relname as index_name,
|
|
515
|
+
a.attname as column_name,
|
|
516
|
+
ix.indisunique as is_unique,
|
|
517
|
+
am.amname as index_type
|
|
518
|
+
FROM
|
|
519
|
+
pg_class t,
|
|
520
|
+
pg_class i,
|
|
521
|
+
pg_index ix,
|
|
522
|
+
pg_attribute a,
|
|
523
|
+
pg_am am
|
|
524
|
+
WHERE
|
|
525
|
+
t.oid = ix.indrelid
|
|
526
|
+
AND i.oid = ix.indexrelid
|
|
527
|
+
AND a.attrelid = t.oid
|
|
528
|
+
AND a.attnum = ANY(ix.indkey)
|
|
529
|
+
AND t.relkind = 'r'
|
|
530
|
+
AND t.relname = $1
|
|
531
|
+
AND i.relam = am.oid
|
|
532
|
+
ORDER BY
|
|
533
|
+
i.relname,
|
|
534
|
+
a.attnum
|
|
535
|
+
`;
|
|
536
|
+
const result = await client.query(query, [options.table]);
|
|
537
|
+
// Group by index name
|
|
538
|
+
const indexMap = new Map();
|
|
539
|
+
result.rows.forEach((row) => {
|
|
540
|
+
if (!indexMap.has(row.index_name)) {
|
|
541
|
+
indexMap.set(row.index_name, {
|
|
542
|
+
name: row.index_name,
|
|
543
|
+
table: options.table,
|
|
544
|
+
columns: [],
|
|
545
|
+
columnDetails: [],
|
|
546
|
+
unique: row.is_unique,
|
|
547
|
+
primaryKey: false,
|
|
548
|
+
type: row.index_type,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const indexInfo = indexMap.get(row.index_name);
|
|
552
|
+
indexInfo.columns.push(row.column_name);
|
|
553
|
+
indexInfo.columnDetails.push({
|
|
554
|
+
name: row.column_name,
|
|
555
|
+
order: 'ASC',
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
return Array.from(indexMap.values());
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.INDEX_ERROR, `Failed to list PostgreSQL indexes: ${error.message}`, error);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
async getIndexStatistics(connection, tableName, indexName) {
|
|
565
|
+
try {
|
|
566
|
+
const client = connection.getClient();
|
|
567
|
+
let query = `
|
|
568
|
+
SELECT
|
|
569
|
+
schemaname,
|
|
570
|
+
tablename,
|
|
571
|
+
indexname,
|
|
572
|
+
idx_scan as scans,
|
|
573
|
+
idx_tup_read as tuples_read,
|
|
574
|
+
idx_tup_fetch as tuples_fetched
|
|
575
|
+
FROM
|
|
576
|
+
pg_stat_user_indexes
|
|
577
|
+
WHERE
|
|
578
|
+
tablename = $1
|
|
579
|
+
`;
|
|
580
|
+
const params = [tableName];
|
|
581
|
+
if (indexName) {
|
|
582
|
+
query += ` AND indexname = $2`;
|
|
583
|
+
params.push(indexName);
|
|
584
|
+
}
|
|
585
|
+
const result = await client.query(query, params);
|
|
586
|
+
return result.rows.map((row) => ({
|
|
587
|
+
indexName: row.indexname,
|
|
588
|
+
tableName: row.tablename,
|
|
589
|
+
scans: row.scans,
|
|
590
|
+
tuplesRead: row.tuples_read,
|
|
591
|
+
tuplesFetched: row.tuples_fetched,
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
catch (error) {
|
|
595
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.INDEX_ERROR, `Failed to get PostgreSQL index statistics: ${error.message}`, error);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// ==================== Migration Methods ====================
|
|
599
|
+
/**
|
|
600
|
+
* Build SQL for a migration operation
|
|
601
|
+
*/
|
|
602
|
+
async buildMigrationOperationSQL(operation) {
|
|
603
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
604
|
+
switch (operation.type) {
|
|
605
|
+
case migration_types_1.MigrationOperationType.RAW_SQL:
|
|
606
|
+
return operation.sql || null;
|
|
607
|
+
case migration_types_1.MigrationOperationType.CREATE_TABLE:
|
|
608
|
+
if ((_a = operation.params) === null || _a === void 0 ? void 0 : _a.schema) {
|
|
609
|
+
const schema = operation.params.schema;
|
|
610
|
+
const columns = schema.columns
|
|
611
|
+
.map((col) => {
|
|
612
|
+
let def = `"${col.name}" ${this.mapColumnTypeToPostgreSQL(col.type)}`;
|
|
613
|
+
if (col.primaryKey)
|
|
614
|
+
def += ' PRIMARY KEY';
|
|
615
|
+
if (!col.nullable)
|
|
616
|
+
def += ' NOT NULL';
|
|
617
|
+
if (col.unique)
|
|
618
|
+
def += ' UNIQUE';
|
|
619
|
+
if (col.defaultValue !== undefined) {
|
|
620
|
+
def += ` DEFAULT ${this.escapeValue(col.defaultValue)}`;
|
|
621
|
+
}
|
|
622
|
+
return def;
|
|
623
|
+
})
|
|
624
|
+
.join(', ');
|
|
625
|
+
return `CREATE TABLE "${schema.name}" (${columns})`;
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
case migration_types_1.MigrationOperationType.DROP_TABLE:
|
|
629
|
+
return operation.table ? `DROP TABLE IF EXISTS "${operation.table}"` : null;
|
|
630
|
+
case migration_types_1.MigrationOperationType.ADD_COLUMN:
|
|
631
|
+
if (operation.table && ((_b = operation.params) === null || _b === void 0 ? void 0 : _b.column)) {
|
|
632
|
+
const col = operation.params.column;
|
|
633
|
+
let def = `ALTER TABLE "${operation.table}" ADD COLUMN "${col.name}" ${this.mapColumnTypeToPostgreSQL(col.type)}`;
|
|
634
|
+
if (!col.nullable)
|
|
635
|
+
def += ' NOT NULL';
|
|
636
|
+
if (col.defaultValue !== undefined) {
|
|
637
|
+
def += ` DEFAULT ${this.escapeValue(col.defaultValue)}`;
|
|
638
|
+
}
|
|
639
|
+
return def;
|
|
640
|
+
}
|
|
641
|
+
return null;
|
|
642
|
+
case migration_types_1.MigrationOperationType.DROP_COLUMN:
|
|
643
|
+
return operation.table && ((_c = operation.params) === null || _c === void 0 ? void 0 : _c.columnName)
|
|
644
|
+
? `ALTER TABLE "${operation.table}" DROP COLUMN "${operation.params.columnName}"`
|
|
645
|
+
: null;
|
|
646
|
+
case migration_types_1.MigrationOperationType.MODIFY_COLUMN:
|
|
647
|
+
if (operation.table && ((_d = operation.params) === null || _d === void 0 ? void 0 : _d.column)) {
|
|
648
|
+
const col = operation.params.column;
|
|
649
|
+
return `ALTER TABLE "${operation.table}" ALTER COLUMN "${col.name}" TYPE ${this.mapColumnTypeToPostgreSQL(col.type)}`;
|
|
650
|
+
}
|
|
651
|
+
return null;
|
|
652
|
+
case migration_types_1.MigrationOperationType.RENAME_COLUMN:
|
|
653
|
+
return operation.table && ((_e = operation.params) === null || _e === void 0 ? void 0 : _e.oldName) && ((_f = operation.params) === null || _f === void 0 ? void 0 : _f.newName)
|
|
654
|
+
? `ALTER TABLE "${operation.table}" RENAME COLUMN "${operation.params.oldName}" TO "${operation.params.newName}"`
|
|
655
|
+
: null;
|
|
656
|
+
case migration_types_1.MigrationOperationType.ADD_INDEX:
|
|
657
|
+
if (operation.table && ((_g = operation.params) === null || _g === void 0 ? void 0 : _g.indexName) && ((_h = operation.params) === null || _h === void 0 ? void 0 : _h.columns)) {
|
|
658
|
+
const unique = operation.params.unique ? 'UNIQUE ' : '';
|
|
659
|
+
const columns = operation.params.columns.map((col) => `"${col}"`).join(', ');
|
|
660
|
+
return `CREATE ${unique}INDEX "${operation.params.indexName}" ON "${operation.table}" (${columns})`;
|
|
661
|
+
}
|
|
662
|
+
return null;
|
|
663
|
+
case migration_types_1.MigrationOperationType.DROP_INDEX:
|
|
664
|
+
return ((_j = operation.params) === null || _j === void 0 ? void 0 : _j.indexName)
|
|
665
|
+
? `DROP INDEX IF EXISTS "${operation.params.indexName}"`
|
|
666
|
+
: null;
|
|
667
|
+
case migration_types_1.MigrationOperationType.ADD_CONSTRAINT:
|
|
668
|
+
if (operation.table && ((_k = operation.params) === null || _k === void 0 ? void 0 : _k.constraintName) && ((_l = operation.params) === null || _l === void 0 ? void 0 : _l.definition)) {
|
|
669
|
+
return `ALTER TABLE "${operation.table}" ADD CONSTRAINT "${operation.params.constraintName}" ${operation.params.definition}`;
|
|
670
|
+
}
|
|
671
|
+
return null;
|
|
672
|
+
case migration_types_1.MigrationOperationType.DROP_CONSTRAINT:
|
|
673
|
+
return operation.table && ((_m = operation.params) === null || _m === void 0 ? void 0 : _m.constraintName)
|
|
674
|
+
? `ALTER TABLE "${operation.table}" DROP CONSTRAINT IF EXISTS "${operation.params.constraintName}"`
|
|
675
|
+
: null;
|
|
676
|
+
default:
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
async runMigration(connection, migration, options) {
|
|
681
|
+
const startTime = Date.now();
|
|
682
|
+
const statements = [];
|
|
683
|
+
try {
|
|
684
|
+
const client = connection.getClient();
|
|
685
|
+
await client.query('BEGIN');
|
|
686
|
+
try {
|
|
687
|
+
// Execute migration operations
|
|
688
|
+
for (const operation of migration.up) {
|
|
689
|
+
const sql = await this.buildMigrationOperationSQL(operation);
|
|
690
|
+
if (sql) {
|
|
691
|
+
statements.push(sql);
|
|
692
|
+
await client.query(sql);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
// Record migration in history table
|
|
696
|
+
await this.recordMigration(client, migration, migration_types_1.MigrationDirection.UP);
|
|
697
|
+
await client.query('COMMIT');
|
|
698
|
+
return {
|
|
699
|
+
tag: migration.tag,
|
|
700
|
+
status: migration_types_1.MigrationStatus.COMPLETED,
|
|
701
|
+
direction: migration_types_1.MigrationDirection.UP,
|
|
702
|
+
executedAt: new Date(),
|
|
703
|
+
duration: Date.now() - startTime,
|
|
704
|
+
statements,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
catch (error) {
|
|
708
|
+
await client.query('ROLLBACK');
|
|
709
|
+
throw error;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
return {
|
|
714
|
+
tag: migration.tag,
|
|
715
|
+
status: migration_types_1.MigrationStatus.FAILED,
|
|
716
|
+
direction: migration_types_1.MigrationDirection.UP,
|
|
717
|
+
executedAt: new Date(),
|
|
718
|
+
duration: Date.now() - startTime,
|
|
719
|
+
error: error.message,
|
|
720
|
+
statements,
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async rollbackMigration(connection, migration, options) {
|
|
725
|
+
const startTime = Date.now();
|
|
726
|
+
const statements = [];
|
|
727
|
+
try {
|
|
728
|
+
const client = connection.getClient();
|
|
729
|
+
await client.query('BEGIN');
|
|
730
|
+
try {
|
|
731
|
+
// Execute rollback operations
|
|
732
|
+
for (const operation of migration.down) {
|
|
733
|
+
const sql = await this.buildMigrationOperationSQL(operation);
|
|
734
|
+
if (sql) {
|
|
735
|
+
statements.push(sql);
|
|
736
|
+
await client.query(sql);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// Remove migration from history table
|
|
740
|
+
await this.removeMigration(client, migration.tag);
|
|
741
|
+
await client.query('COMMIT');
|
|
742
|
+
return {
|
|
743
|
+
tag: migration.tag,
|
|
744
|
+
status: migration_types_1.MigrationStatus.ROLLED_BACK,
|
|
745
|
+
direction: migration_types_1.MigrationDirection.DOWN,
|
|
746
|
+
executedAt: new Date(),
|
|
747
|
+
duration: Date.now() - startTime,
|
|
748
|
+
statements,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
await client.query('ROLLBACK');
|
|
753
|
+
throw error;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
return {
|
|
758
|
+
tag: migration.tag,
|
|
759
|
+
status: migration_types_1.MigrationStatus.FAILED,
|
|
760
|
+
direction: migration_types_1.MigrationDirection.DOWN,
|
|
761
|
+
executedAt: new Date(),
|
|
762
|
+
duration: Date.now() - startTime,
|
|
763
|
+
error: error.message,
|
|
764
|
+
statements,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async getMigrationHistory(connection, options) {
|
|
769
|
+
try {
|
|
770
|
+
const client = connection.getClient();
|
|
771
|
+
// Ensure migration table exists
|
|
772
|
+
await this.ensureMigrationTable(client);
|
|
773
|
+
const query = `
|
|
774
|
+
SELECT tag, name, executed_at
|
|
775
|
+
FROM _ductape_migrations
|
|
776
|
+
ORDER BY executed_at DESC
|
|
777
|
+
`;
|
|
778
|
+
const result = await client.query(query);
|
|
779
|
+
return result.rows.map((row) => ({
|
|
780
|
+
tag: row.tag,
|
|
781
|
+
name: row.name,
|
|
782
|
+
executedAt: row.executed_at,
|
|
783
|
+
}));
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.MIGRATION_ERROR, `Failed to get migration history: ${error.message}`, error);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// ==================== Helper Methods ====================
|
|
790
|
+
/**
|
|
791
|
+
* Build SELECT query from options
|
|
792
|
+
*/
|
|
793
|
+
buildSelectQuery(options) {
|
|
794
|
+
const params = [];
|
|
795
|
+
let paramIndex = 1;
|
|
796
|
+
// SELECT clause
|
|
797
|
+
const selectColumns = options.select && options.select.length > 0
|
|
798
|
+
? options.select.map((col) => `"${col}"`).join(', ')
|
|
799
|
+
: '*';
|
|
800
|
+
let query = `SELECT ${options.distinct ? 'DISTINCT ' : ''}${selectColumns} FROM "${options.table}"`;
|
|
801
|
+
// WHERE clause
|
|
802
|
+
if (options.where) {
|
|
803
|
+
const { clause, values } = this.buildWhereClause(options.where, paramIndex);
|
|
804
|
+
query += ` WHERE ${clause}`;
|
|
805
|
+
params.push(...values);
|
|
806
|
+
paramIndex += values.length;
|
|
807
|
+
}
|
|
808
|
+
// GROUP BY clause
|
|
809
|
+
if (options.groupBy && options.groupBy.length > 0) {
|
|
810
|
+
query += ` GROUP BY ${options.groupBy.map((col) => `"${col}"`).join(', ')}`;
|
|
811
|
+
}
|
|
812
|
+
// HAVING clause
|
|
813
|
+
if (options.having) {
|
|
814
|
+
const { clause, values } = this.buildWhereClause(options.having, paramIndex);
|
|
815
|
+
query += ` HAVING ${clause}`;
|
|
816
|
+
params.push(...values);
|
|
817
|
+
paramIndex += values.length;
|
|
818
|
+
}
|
|
819
|
+
// ORDER BY clause
|
|
820
|
+
if (options.orderBy) {
|
|
821
|
+
const orderByArray = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
|
|
822
|
+
const orderClauses = orderByArray.map((order) => `"${order.column}" ${order.order || 'ASC'}`);
|
|
823
|
+
query += ` ORDER BY ${orderClauses.join(', ')}`;
|
|
824
|
+
}
|
|
825
|
+
// LIMIT clause
|
|
826
|
+
if (options.limit) {
|
|
827
|
+
query += ` LIMIT $${paramIndex++}`;
|
|
828
|
+
params.push(options.limit);
|
|
829
|
+
}
|
|
830
|
+
// OFFSET clause
|
|
831
|
+
if (options.offset) {
|
|
832
|
+
query += ` OFFSET $${paramIndex++}`;
|
|
833
|
+
params.push(options.offset);
|
|
834
|
+
}
|
|
835
|
+
return { query, params };
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Build INSERT query from options
|
|
839
|
+
*/
|
|
840
|
+
buildInsertQuery(options) {
|
|
841
|
+
const dataArray = Array.isArray(options.data) ? options.data : [options.data];
|
|
842
|
+
const columns = Object.keys(dataArray[0]);
|
|
843
|
+
const params = [];
|
|
844
|
+
let paramIndex = 1;
|
|
845
|
+
let query = `INSERT INTO "${options.table}" (${columns.map((col) => `"${col}"`).join(', ')}) VALUES `;
|
|
846
|
+
const valuesClauses = [];
|
|
847
|
+
dataArray.forEach((data) => {
|
|
848
|
+
const valuePlaceholders = columns.map(() => `$${paramIndex++}`);
|
|
849
|
+
valuesClauses.push(`(${valuePlaceholders.join(', ')})`);
|
|
850
|
+
params.push(...columns.map((col) => data[col]));
|
|
851
|
+
});
|
|
852
|
+
query += valuesClauses.join(', ');
|
|
853
|
+
if (options.returning) {
|
|
854
|
+
const returningColumns = Array.isArray(options.returning)
|
|
855
|
+
? options.returning.map((col) => `"${col}"`).join(', ')
|
|
856
|
+
: '*';
|
|
857
|
+
query += ` RETURNING ${returningColumns}`;
|
|
858
|
+
}
|
|
859
|
+
return { query, params };
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Build UPDATE query from options
|
|
863
|
+
*/
|
|
864
|
+
buildUpdateQuery(options) {
|
|
865
|
+
const params = [];
|
|
866
|
+
let paramIndex = 1;
|
|
867
|
+
const setClauses = Object.keys(options.data).map((key) => {
|
|
868
|
+
params.push(options.data[key]);
|
|
869
|
+
return `"${key}" = $${paramIndex++}`;
|
|
870
|
+
});
|
|
871
|
+
let query = `UPDATE "${options.table}" SET ${setClauses.join(', ')}`;
|
|
872
|
+
if (options.where) {
|
|
873
|
+
const { clause, values } = this.buildWhereClause(options.where, paramIndex);
|
|
874
|
+
query += ` WHERE ${clause}`;
|
|
875
|
+
params.push(...values);
|
|
876
|
+
}
|
|
877
|
+
if (options.returning) {
|
|
878
|
+
const returningColumns = Array.isArray(options.returning)
|
|
879
|
+
? options.returning.map((col) => `"${col}"`).join(', ')
|
|
880
|
+
: '*';
|
|
881
|
+
query += ` RETURNING ${returningColumns}`;
|
|
882
|
+
}
|
|
883
|
+
return { query, params };
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Build DELETE query from options
|
|
887
|
+
*/
|
|
888
|
+
buildDeleteQuery(options) {
|
|
889
|
+
const params = [];
|
|
890
|
+
let paramIndex = 1;
|
|
891
|
+
let query = `DELETE FROM "${options.table}"`;
|
|
892
|
+
if (options.where) {
|
|
893
|
+
const { clause, values } = this.buildWhereClause(options.where, paramIndex);
|
|
894
|
+
query += ` WHERE ${clause}`;
|
|
895
|
+
params.push(...values);
|
|
896
|
+
}
|
|
897
|
+
if (options.returning) {
|
|
898
|
+
const returningColumns = Array.isArray(options.returning)
|
|
899
|
+
? options.returning.map((col) => `"${col}"`).join(', ')
|
|
900
|
+
: '*';
|
|
901
|
+
query += ` RETURNING ${returningColumns}`;
|
|
902
|
+
}
|
|
903
|
+
return { query, params };
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Build UPSERT (INSERT ... ON CONFLICT) query from options
|
|
907
|
+
*/
|
|
908
|
+
buildUpsertQuery(options) {
|
|
909
|
+
// Upsert only works with single records
|
|
910
|
+
const data = Array.isArray(options.data) ? options.data[0] : options.data;
|
|
911
|
+
const columns = Object.keys(data);
|
|
912
|
+
const params = [];
|
|
913
|
+
let paramIndex = 1;
|
|
914
|
+
let query = `INSERT INTO "${options.table}" (${columns.map((col) => `"${col}"`).join(', ')}) VALUES (`;
|
|
915
|
+
query += columns.map(() => `$${paramIndex++}`).join(', ');
|
|
916
|
+
params.push(...columns.map((col) => data[col]));
|
|
917
|
+
query += `) ON CONFLICT (${options.uniqueColumns.map((col) => `"${col}"`).join(', ')}) DO UPDATE SET `;
|
|
918
|
+
const updateClauses = columns
|
|
919
|
+
.filter((col) => !options.uniqueColumns.includes(col))
|
|
920
|
+
.map((col) => `"${col}" = EXCLUDED."${col}"`);
|
|
921
|
+
query += updateClauses.join(', ');
|
|
922
|
+
if (options.returning) {
|
|
923
|
+
const returningColumns = Array.isArray(options.returning)
|
|
924
|
+
? options.returning.map((col) => `"${col}"`).join(', ')
|
|
925
|
+
: '*';
|
|
926
|
+
query += ` RETURNING ${returningColumns}`;
|
|
927
|
+
}
|
|
928
|
+
return { query, params };
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Build COUNT query from options
|
|
932
|
+
*/
|
|
933
|
+
buildCountQuery(options) {
|
|
934
|
+
const params = [];
|
|
935
|
+
let paramIndex = 1;
|
|
936
|
+
const column = options.column || '*';
|
|
937
|
+
let query = `SELECT COUNT(${column === '*' ? '*' : `"${column}"`}) as count FROM "${options.table}"`;
|
|
938
|
+
if (options.where) {
|
|
939
|
+
const { clause, values } = this.buildWhereClause(options.where, paramIndex);
|
|
940
|
+
query += ` WHERE ${clause}`;
|
|
941
|
+
params.push(...values);
|
|
942
|
+
}
|
|
943
|
+
return { query, params };
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Build aggregate query (SUM, AVG, MIN, MAX)
|
|
947
|
+
*/
|
|
948
|
+
buildAggregateQuery(func, column, options) {
|
|
949
|
+
const params = [];
|
|
950
|
+
let paramIndex = 1;
|
|
951
|
+
let query = `SELECT ${func}("${column}") as result FROM "${options.table}"`;
|
|
952
|
+
if (options.where) {
|
|
953
|
+
const { clause, values } = this.buildWhereClause(options.where, paramIndex);
|
|
954
|
+
query += ` WHERE ${clause}`;
|
|
955
|
+
params.push(...values);
|
|
956
|
+
}
|
|
957
|
+
return { query, params };
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Build GROUP BY query
|
|
961
|
+
*/
|
|
962
|
+
buildGroupByQuery(options) {
|
|
963
|
+
const params = [];
|
|
964
|
+
let paramIndex = 1;
|
|
965
|
+
const groupColumns = options.groupBy.map((col) => `"${col}"`);
|
|
966
|
+
const selectColumns = [...groupColumns];
|
|
967
|
+
// Build aggregations
|
|
968
|
+
if (options.aggregate) {
|
|
969
|
+
Object.entries(options.aggregate).forEach(([alias, agg]) => {
|
|
970
|
+
selectColumns.push(`${agg.function}("${agg.column}") as "${alias}"`);
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
let query = `SELECT ${selectColumns.join(', ')} FROM "${options.table}"`;
|
|
974
|
+
if (options.where) {
|
|
975
|
+
const { clause, values } = this.buildWhereClause(options.where, paramIndex);
|
|
976
|
+
query += ` WHERE ${clause}`;
|
|
977
|
+
params.push(...values);
|
|
978
|
+
paramIndex += values.length;
|
|
979
|
+
}
|
|
980
|
+
query += ` GROUP BY ${groupColumns.join(', ')}`;
|
|
981
|
+
if (options.having) {
|
|
982
|
+
const { clause, values } = this.buildWhereClause(options.having, paramIndex);
|
|
983
|
+
query += ` HAVING ${clause}`;
|
|
984
|
+
params.push(...values);
|
|
985
|
+
}
|
|
986
|
+
return { query, params };
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Build multi-aggregate query
|
|
990
|
+
*/
|
|
991
|
+
buildMultiAggregateQuery(options) {
|
|
992
|
+
const params = [];
|
|
993
|
+
let paramIndex = 1;
|
|
994
|
+
const aggregations = Object.entries(options.operations).map(([alias, agg]) => {
|
|
995
|
+
return `${agg.function}("${agg.column}") as "${alias}"`;
|
|
996
|
+
});
|
|
997
|
+
let query = `SELECT ${aggregations.join(', ')} FROM "${options.table}"`;
|
|
998
|
+
if (options.where) {
|
|
999
|
+
const { clause, values } = this.buildWhereClause(options.where, paramIndex);
|
|
1000
|
+
query += ` WHERE ${clause}`;
|
|
1001
|
+
params.push(...values);
|
|
1002
|
+
}
|
|
1003
|
+
return { query, params };
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Build WHERE clause from WhereClause object
|
|
1007
|
+
*/
|
|
1008
|
+
buildWhereClause(where, startIndex = 1) {
|
|
1009
|
+
const values = [];
|
|
1010
|
+
let paramIndex = startIndex;
|
|
1011
|
+
const buildCondition = (condition) => {
|
|
1012
|
+
if (typeof condition !== 'object' || condition === null) {
|
|
1013
|
+
values.push(condition);
|
|
1014
|
+
return `$${paramIndex++}`;
|
|
1015
|
+
}
|
|
1016
|
+
// Handle operators like $and, $or
|
|
1017
|
+
if (condition.$and) {
|
|
1018
|
+
const subConditions = condition.$and.map(buildCondition);
|
|
1019
|
+
return `(${subConditions.join(' AND ')})`;
|
|
1020
|
+
}
|
|
1021
|
+
if (condition.$or) {
|
|
1022
|
+
const subConditions = condition.$or.map(buildCondition);
|
|
1023
|
+
return `(${subConditions.join(' OR ')})`;
|
|
1024
|
+
}
|
|
1025
|
+
// Handle column conditions
|
|
1026
|
+
const conditions = [];
|
|
1027
|
+
for (const [key, value] of Object.entries(condition)) {
|
|
1028
|
+
if (typeof value === 'object' && value !== null) {
|
|
1029
|
+
// Handle operators like $gt, $lt, $gte, $lte, $ne, $in, $like
|
|
1030
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
1031
|
+
switch (op) {
|
|
1032
|
+
case '$gt':
|
|
1033
|
+
values.push(opValue);
|
|
1034
|
+
conditions.push(`"${key}" > $${paramIndex++}`);
|
|
1035
|
+
break;
|
|
1036
|
+
case '$gte':
|
|
1037
|
+
values.push(opValue);
|
|
1038
|
+
conditions.push(`"${key}" >= $${paramIndex++}`);
|
|
1039
|
+
break;
|
|
1040
|
+
case '$lt':
|
|
1041
|
+
values.push(opValue);
|
|
1042
|
+
conditions.push(`"${key}" < $${paramIndex++}`);
|
|
1043
|
+
break;
|
|
1044
|
+
case '$lte':
|
|
1045
|
+
values.push(opValue);
|
|
1046
|
+
conditions.push(`"${key}" <= $${paramIndex++}`);
|
|
1047
|
+
break;
|
|
1048
|
+
case '$ne':
|
|
1049
|
+
values.push(opValue);
|
|
1050
|
+
conditions.push(`"${key}" != $${paramIndex++}`);
|
|
1051
|
+
break;
|
|
1052
|
+
case '$in':
|
|
1053
|
+
values.push(opValue);
|
|
1054
|
+
conditions.push(`"${key}" = ANY($${paramIndex++})`);
|
|
1055
|
+
break;
|
|
1056
|
+
case '$like':
|
|
1057
|
+
values.push(opValue);
|
|
1058
|
+
conditions.push(`"${key}" LIKE $${paramIndex++}`);
|
|
1059
|
+
break;
|
|
1060
|
+
case '$ilike':
|
|
1061
|
+
values.push(opValue);
|
|
1062
|
+
conditions.push(`"${key}" ILIKE $${paramIndex++}`);
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
else {
|
|
1068
|
+
values.push(value);
|
|
1069
|
+
conditions.push(`"${key}" = $${paramIndex++}`);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return conditions.join(' AND ');
|
|
1073
|
+
};
|
|
1074
|
+
const clause = buildCondition(where);
|
|
1075
|
+
return { clause, values };
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Build CREATE TABLE query
|
|
1079
|
+
*/
|
|
1080
|
+
buildCreateTableQuery(schema, options) {
|
|
1081
|
+
const columnDefs = schema.columns.map((col) => {
|
|
1082
|
+
let def = `"${col.name}" ${this.mapColumnTypeToPostgreSQL(col.type)}`;
|
|
1083
|
+
if (col.length) {
|
|
1084
|
+
def += `(${col.length})`;
|
|
1085
|
+
}
|
|
1086
|
+
else if (col.precision && col.scale) {
|
|
1087
|
+
def += `(${col.precision}, ${col.scale})`;
|
|
1088
|
+
}
|
|
1089
|
+
else if (col.precision) {
|
|
1090
|
+
def += `(${col.precision})`;
|
|
1091
|
+
}
|
|
1092
|
+
if (col.primaryKey) {
|
|
1093
|
+
def += ' PRIMARY KEY';
|
|
1094
|
+
}
|
|
1095
|
+
if (col.unique) {
|
|
1096
|
+
def += ' UNIQUE';
|
|
1097
|
+
}
|
|
1098
|
+
if (!col.nullable && !col.primaryKey) {
|
|
1099
|
+
def += ' NOT NULL';
|
|
1100
|
+
}
|
|
1101
|
+
if (col.defaultValue !== undefined) {
|
|
1102
|
+
def += ` DEFAULT ${col.defaultValue}`;
|
|
1103
|
+
}
|
|
1104
|
+
if (col.autoIncrement) {
|
|
1105
|
+
def = `"${col.name}" SERIAL PRIMARY KEY`;
|
|
1106
|
+
}
|
|
1107
|
+
return def;
|
|
1108
|
+
});
|
|
1109
|
+
let query = `CREATE TABLE ${(options === null || options === void 0 ? void 0 : options.ifNotExists) ? 'IF NOT EXISTS ' : ''}"${schema.name}" (${columnDefs.join(', ')})`;
|
|
1110
|
+
return query;
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Build ALTER TABLE queries
|
|
1114
|
+
*/
|
|
1115
|
+
buildAlterTableQueries(tableName, alterations) {
|
|
1116
|
+
return alterations.map((alt) => {
|
|
1117
|
+
let query = `ALTER TABLE "${tableName}" `;
|
|
1118
|
+
switch (alt.type) {
|
|
1119
|
+
case schema_types_1.ColumnAlterationType.ADD:
|
|
1120
|
+
query += `ADD COLUMN "${alt.column.name}" ${this.mapColumnTypeToPostgreSQL(alt.column.type)}`;
|
|
1121
|
+
if (!alt.column.nullable) {
|
|
1122
|
+
query += ' NOT NULL';
|
|
1123
|
+
}
|
|
1124
|
+
break;
|
|
1125
|
+
case schema_types_1.ColumnAlterationType.DROP:
|
|
1126
|
+
query += `DROP COLUMN "${alt.oldName}"`;
|
|
1127
|
+
break;
|
|
1128
|
+
case schema_types_1.ColumnAlterationType.MODIFY:
|
|
1129
|
+
query += `ALTER COLUMN "${alt.column.name}" TYPE ${this.mapColumnTypeToPostgreSQL(alt.column.type)}`;
|
|
1130
|
+
break;
|
|
1131
|
+
case schema_types_1.ColumnAlterationType.RENAME:
|
|
1132
|
+
query += `RENAME COLUMN "${alt.oldName}" TO "${alt.newName}"`;
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
return query;
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Build CREATE INDEX query
|
|
1140
|
+
*/
|
|
1141
|
+
buildCreateIndexQuery(options) {
|
|
1142
|
+
const unique = options.index.unique ? 'UNIQUE ' : '';
|
|
1143
|
+
const columns = options.index.columns.map((col) => `"${col.name}"`).join(', ');
|
|
1144
|
+
return `CREATE ${unique}INDEX ${options.ifNotExists ? 'IF NOT EXISTS ' : ''}"${options.index.name}" ON "${options.table}" (${columns})`;
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Map ColumnType to PostgreSQL type
|
|
1148
|
+
*/
|
|
1149
|
+
mapColumnTypeToPostgreSQL(type) {
|
|
1150
|
+
const typeMap = {
|
|
1151
|
+
[schema_types_1.ColumnType.STRING]: 'VARCHAR',
|
|
1152
|
+
[schema_types_1.ColumnType.TEXT]: 'TEXT',
|
|
1153
|
+
[schema_types_1.ColumnType.VARCHAR]: 'VARCHAR',
|
|
1154
|
+
[schema_types_1.ColumnType.CHAR]: 'CHAR',
|
|
1155
|
+
[schema_types_1.ColumnType.INTEGER]: 'INTEGER',
|
|
1156
|
+
[schema_types_1.ColumnType.BIGINT]: 'BIGINT',
|
|
1157
|
+
[schema_types_1.ColumnType.SMALLINT]: 'SMALLINT',
|
|
1158
|
+
[schema_types_1.ColumnType.DECIMAL]: 'DECIMAL',
|
|
1159
|
+
[schema_types_1.ColumnType.NUMERIC]: 'NUMERIC',
|
|
1160
|
+
[schema_types_1.ColumnType.FLOAT]: 'REAL',
|
|
1161
|
+
[schema_types_1.ColumnType.DOUBLE]: 'DOUBLE PRECISION',
|
|
1162
|
+
[schema_types_1.ColumnType.REAL]: 'REAL',
|
|
1163
|
+
[schema_types_1.ColumnType.BOOLEAN]: 'BOOLEAN',
|
|
1164
|
+
[schema_types_1.ColumnType.DATE]: 'DATE',
|
|
1165
|
+
[schema_types_1.ColumnType.TIME]: 'TIME',
|
|
1166
|
+
[schema_types_1.ColumnType.DATETIME]: 'TIMESTAMP',
|
|
1167
|
+
[schema_types_1.ColumnType.TIMESTAMP]: 'TIMESTAMP',
|
|
1168
|
+
[schema_types_1.ColumnType.JSON]: 'JSONB',
|
|
1169
|
+
[schema_types_1.ColumnType.JSONB]: 'JSONB',
|
|
1170
|
+
[schema_types_1.ColumnType.UUID]: 'UUID',
|
|
1171
|
+
[schema_types_1.ColumnType.BLOB]: 'BYTEA',
|
|
1172
|
+
[schema_types_1.ColumnType.BINARY]: 'BYTEA',
|
|
1173
|
+
[schema_types_1.ColumnType.ARRAY]: 'ARRAY',
|
|
1174
|
+
[schema_types_1.ColumnType.OBJECT]: 'JSONB',
|
|
1175
|
+
[schema_types_1.ColumnType.ENUM]: 'VARCHAR',
|
|
1176
|
+
};
|
|
1177
|
+
return typeMap[type] || 'TEXT';
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Map PostgreSQL type to ColumnType
|
|
1181
|
+
*/
|
|
1182
|
+
mapPostgreSQLTypeToColumnType(pgType) {
|
|
1183
|
+
const typeMap = {
|
|
1184
|
+
'character varying': schema_types_1.ColumnType.VARCHAR,
|
|
1185
|
+
varchar: schema_types_1.ColumnType.VARCHAR,
|
|
1186
|
+
text: schema_types_1.ColumnType.TEXT,
|
|
1187
|
+
character: schema_types_1.ColumnType.CHAR,
|
|
1188
|
+
char: schema_types_1.ColumnType.CHAR,
|
|
1189
|
+
integer: schema_types_1.ColumnType.INTEGER,
|
|
1190
|
+
bigint: schema_types_1.ColumnType.BIGINT,
|
|
1191
|
+
smallint: schema_types_1.ColumnType.SMALLINT,
|
|
1192
|
+
decimal: schema_types_1.ColumnType.DECIMAL,
|
|
1193
|
+
numeric: schema_types_1.ColumnType.NUMERIC,
|
|
1194
|
+
real: schema_types_1.ColumnType.FLOAT,
|
|
1195
|
+
'double precision': schema_types_1.ColumnType.DOUBLE,
|
|
1196
|
+
boolean: schema_types_1.ColumnType.BOOLEAN,
|
|
1197
|
+
date: schema_types_1.ColumnType.DATE,
|
|
1198
|
+
time: schema_types_1.ColumnType.TIME,
|
|
1199
|
+
timestamp: schema_types_1.ColumnType.TIMESTAMP,
|
|
1200
|
+
'timestamp without time zone': schema_types_1.ColumnType.TIMESTAMP,
|
|
1201
|
+
jsonb: schema_types_1.ColumnType.JSONB,
|
|
1202
|
+
json: schema_types_1.ColumnType.JSON,
|
|
1203
|
+
uuid: schema_types_1.ColumnType.UUID,
|
|
1204
|
+
bytea: schema_types_1.ColumnType.BINARY,
|
|
1205
|
+
};
|
|
1206
|
+
return typeMap[pgType.toLowerCase()] || schema_types_1.ColumnType.TEXT;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Ensure migration table exists
|
|
1210
|
+
*/
|
|
1211
|
+
async ensureMigrationTable(client) {
|
|
1212
|
+
const query = `
|
|
1213
|
+
CREATE TABLE IF NOT EXISTS _ductape_migrations (
|
|
1214
|
+
tag VARCHAR(255) PRIMARY KEY,
|
|
1215
|
+
name VARCHAR(255) NOT NULL,
|
|
1216
|
+
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
1217
|
+
)
|
|
1218
|
+
`;
|
|
1219
|
+
await client.query(query);
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Record migration execution
|
|
1223
|
+
*/
|
|
1224
|
+
async recordMigration(client, migration, direction) {
|
|
1225
|
+
await this.ensureMigrationTable(client);
|
|
1226
|
+
const query = `
|
|
1227
|
+
INSERT INTO _ductape_migrations (tag, name, executed_at)
|
|
1228
|
+
VALUES ($1, $2, CURRENT_TIMESTAMP)
|
|
1229
|
+
ON CONFLICT (tag) DO UPDATE SET executed_at = CURRENT_TIMESTAMP
|
|
1230
|
+
`;
|
|
1231
|
+
await client.query(query, [migration.tag, migration.name]);
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Remove migration from history
|
|
1235
|
+
*/
|
|
1236
|
+
async removeMigration(client, tag) {
|
|
1237
|
+
await this.ensureMigrationTable(client);
|
|
1238
|
+
const query = `DELETE FROM _ductape_migrations WHERE tag = $1`;
|
|
1239
|
+
await client.query(query, [tag]);
|
|
1240
|
+
}
|
|
1241
|
+
// ==================== Utility Methods ====================
|
|
1242
|
+
/**
|
|
1243
|
+
* Escape identifier (table/column name)
|
|
1244
|
+
*/
|
|
1245
|
+
escapeIdentifier(identifier) {
|
|
1246
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Escape value for SQL
|
|
1250
|
+
*/
|
|
1251
|
+
escapeValue(value) {
|
|
1252
|
+
if (value === null || value === undefined) {
|
|
1253
|
+
return 'NULL';
|
|
1254
|
+
}
|
|
1255
|
+
if (typeof value === 'string') {
|
|
1256
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1257
|
+
}
|
|
1258
|
+
if (typeof value === 'boolean') {
|
|
1259
|
+
return value ? 'TRUE' : 'FALSE';
|
|
1260
|
+
}
|
|
1261
|
+
if (value instanceof Date) {
|
|
1262
|
+
return `'${value.toISOString()}'`;
|
|
1263
|
+
}
|
|
1264
|
+
if (Array.isArray(value)) {
|
|
1265
|
+
return `ARRAY[${value.map((v) => this.escapeValue(v)).join(', ')}]`;
|
|
1266
|
+
}
|
|
1267
|
+
if (typeof value === 'object') {
|
|
1268
|
+
return `'${JSON.stringify(value)}'::jsonb`;
|
|
1269
|
+
}
|
|
1270
|
+
return String(value);
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Get database version
|
|
1274
|
+
*/
|
|
1275
|
+
async getDatabaseVersion(connection) {
|
|
1276
|
+
var _a;
|
|
1277
|
+
try {
|
|
1278
|
+
const pool = connection.getClient();
|
|
1279
|
+
const result = await pool.query('SELECT version()');
|
|
1280
|
+
return ((_a = result.rows[0]) === null || _a === void 0 ? void 0 : _a.version) || 'Unknown';
|
|
1281
|
+
}
|
|
1282
|
+
catch (error) {
|
|
1283
|
+
return 'Unknown';
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
exports.PostgreSQLAdapter = PostgreSQLAdapter;
|
|
1288
|
+
//# sourceMappingURL=postgresql.adapter.js.map
|