@ductape/sdk 0.0.4-v42 → 0.0.4-v44

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.
Files changed (85) hide show
  1. package/dist/apps/services/app.service.d.ts +10 -0
  2. package/dist/apps/services/app.service.js +38 -69
  3. package/dist/apps/services/app.service.js.map +1 -1
  4. package/dist/apps/validators/joi-validators/create.appWebhook.validator.d.ts +1 -2
  5. package/dist/apps/validators/joi-validators/create.appWebhook.validator.js +2 -15
  6. package/dist/apps/validators/joi-validators/create.appWebhook.validator.js.map +1 -1
  7. package/dist/apps/validators/joi-validators/update.appWebhook.validator.d.ts +1 -2
  8. package/dist/apps/validators/joi-validators/update.appWebhook.validator.js +2 -14
  9. package/dist/apps/validators/joi-validators/update.appWebhook.validator.js.map +1 -1
  10. package/dist/database/adapters/base.adapter.d.ts +176 -0
  11. package/dist/database/adapters/base.adapter.js +31 -0
  12. package/dist/database/adapters/base.adapter.js.map +1 -0
  13. package/dist/database/adapters/dynamodb.adapter.d.ts +91 -0
  14. package/dist/database/adapters/dynamodb.adapter.js +1469 -0
  15. package/dist/database/adapters/dynamodb.adapter.js.map +1 -0
  16. package/dist/database/adapters/mongodb.adapter.d.ts +71 -0
  17. package/dist/database/adapters/mongodb.adapter.js +882 -0
  18. package/dist/database/adapters/mongodb.adapter.js.map +1 -0
  19. package/dist/database/adapters/mysql.adapter.d.ts +146 -0
  20. package/dist/database/adapters/mysql.adapter.js +1417 -0
  21. package/dist/database/adapters/mysql.adapter.js.map +1 -0
  22. package/dist/database/adapters/postgresql.adapter.d.ts +147 -0
  23. package/dist/database/adapters/postgresql.adapter.js +1472 -0
  24. package/dist/database/adapters/postgresql.adapter.js.map +1 -0
  25. package/dist/database/database.service.d.ts +195 -0
  26. package/dist/database/database.service.js +502 -0
  27. package/dist/database/database.service.js.map +1 -0
  28. package/dist/database/index.d.ts +18 -0
  29. package/dist/database/index.js +98 -0
  30. package/dist/database/index.js.map +1 -0
  31. package/dist/database/types/aggregation.types.d.ts +202 -0
  32. package/dist/database/types/aggregation.types.js +21 -0
  33. package/dist/database/types/aggregation.types.js.map +1 -0
  34. package/dist/database/types/connection.types.d.ts +132 -0
  35. package/dist/database/types/connection.types.js +6 -0
  36. package/dist/database/types/connection.types.js.map +1 -0
  37. package/dist/database/types/database.types.d.ts +174 -0
  38. package/dist/database/types/database.types.js +74 -0
  39. package/dist/database/types/database.types.js.map +1 -0
  40. package/dist/database/types/index.d.ts +12 -0
  41. package/dist/database/types/index.js +37 -0
  42. package/dist/database/types/index.js.map +1 -0
  43. package/dist/database/types/index.types.d.ts +220 -0
  44. package/dist/database/types/index.types.js +27 -0
  45. package/dist/database/types/index.types.js.map +1 -0
  46. package/dist/database/types/migration.types.d.ts +205 -0
  47. package/dist/database/types/migration.types.js +44 -0
  48. package/dist/database/types/migration.types.js.map +1 -0
  49. package/dist/database/types/query.types.d.ts +305 -0
  50. package/dist/database/types/query.types.js +57 -0
  51. package/dist/database/types/query.types.js.map +1 -0
  52. package/dist/database/types/result.types.d.ts +218 -0
  53. package/dist/database/types/result.types.js +6 -0
  54. package/dist/database/types/result.types.js.map +1 -0
  55. package/dist/database/types/schema.types.d.ts +190 -0
  56. package/dist/database/types/schema.types.js +69 -0
  57. package/dist/database/types/schema.types.js.map +1 -0
  58. package/dist/database/utils/helpers.d.ts +66 -0
  59. package/dist/database/utils/helpers.js +501 -0
  60. package/dist/database/utils/helpers.js.map +1 -0
  61. package/dist/database/utils/migration.utils.d.ts +151 -0
  62. package/dist/database/utils/migration.utils.js +476 -0
  63. package/dist/database/utils/migration.utils.js.map +1 -0
  64. package/dist/database/utils/transaction.d.ts +64 -0
  65. package/dist/database/utils/transaction.js +130 -0
  66. package/dist/database/utils/transaction.js.map +1 -0
  67. package/dist/database/validators/connection.validator.d.ts +20 -0
  68. package/dist/database/validators/connection.validator.js +267 -0
  69. package/dist/database/validators/connection.validator.js.map +1 -0
  70. package/dist/database/validators/query.validator.d.ts +31 -0
  71. package/dist/database/validators/query.validator.js +305 -0
  72. package/dist/database/validators/query.validator.js.map +1 -0
  73. package/dist/database/validators/schema.validator.d.ts +31 -0
  74. package/dist/database/validators/schema.validator.js +334 -0
  75. package/dist/database/validators/schema.validator.js.map +1 -0
  76. package/dist/index.d.ts +194 -146
  77. package/dist/index.js +232 -173
  78. package/dist/index.js.map +1 -1
  79. package/dist/processor/services/processor.service.js +61 -43
  80. package/dist/processor/services/processor.service.js.map +1 -1
  81. package/dist/test/test.processor.js +1 -3
  82. package/dist/test/test.processor.js.map +1 -1
  83. package/dist/types/appBuilder.types.d.ts +1 -1
  84. package/dist/types/processor.types.d.ts +2 -2
  85. package/package.json +3 -1
@@ -0,0 +1,1417 @@
1
+ "use strict";
2
+ /**
3
+ * MySQL Database Adapter
4
+ * Implements database operations for MySQL/MariaDB
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.MySQLAdapter = void 0;
41
+ const base_adapter_1 = require("./base.adapter");
42
+ const database_types_1 = require("../types/database.types");
43
+ const schema_types_1 = require("../types/schema.types");
44
+ const migration_types_1 = require("../types/migration.types");
45
+ /**
46
+ * MySQL Connection wrapper
47
+ */
48
+ class MySQLConnection {
49
+ constructor(id, connection) {
50
+ this.id = id;
51
+ this.type = database_types_1.DatabaseType.MYSQL;
52
+ this.status = database_types_1.ConnectionStatus.CONNECTED;
53
+ this.connection = connection;
54
+ }
55
+ async connect() {
56
+ this.status = database_types_1.ConnectionStatus.CONNECTED;
57
+ }
58
+ async disconnect() {
59
+ if (this.connection) {
60
+ await this.connection.end();
61
+ this.status = database_types_1.ConnectionStatus.DISCONNECTED;
62
+ }
63
+ }
64
+ isConnected() {
65
+ return this.status === database_types_1.ConnectionStatus.CONNECTED;
66
+ }
67
+ getClient() {
68
+ return this.connection;
69
+ }
70
+ getType() {
71
+ return this.type;
72
+ }
73
+ getConfig() {
74
+ throw new Error('Config not stored in connection');
75
+ }
76
+ }
77
+ /**
78
+ * MySQL Database Adapter
79
+ */
80
+ class MySQLAdapter extends base_adapter_1.BaseDatabaseAdapter {
81
+ constructor() {
82
+ super(...arguments);
83
+ this.type = database_types_1.DatabaseType.MYSQL;
84
+ }
85
+ // ==================== Connection Methods ====================
86
+ async connect(config) {
87
+ var _a, _b, _c, _d, _e, _f;
88
+ try {
89
+ // Dynamic import of mysql2/promise
90
+ // @ts-ignore - mysql2 is an optional peer dependency
91
+ const mysql = await Promise.resolve().then(() => __importStar(require('mysql2/promise')));
92
+ const connection = await mysql.createConnection(Object.assign({ host: (_a = config.options) === null || _a === void 0 ? void 0 : _a.host, port: ((_b = config.options) === null || _b === void 0 ? void 0 : _b.port) || 3306, user: ((_c = config.options) === null || _c === void 0 ? void 0 : _c.username) || ((_d = config.options) === null || _d === void 0 ? void 0 : _d.user), password: (_e = config.options) === null || _e === void 0 ? void 0 : _e.password, database: config.database, ssl: (_f = config.options) === null || _f === void 0 ? void 0 : _f.ssl }, config.options));
93
+ return new MySQLConnection(`mysql-${Date.now()}`, connection);
94
+ }
95
+ catch (error) {
96
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.CONNECTION_ERROR, `Failed to connect to MySQL: ${error.message}`, error);
97
+ }
98
+ }
99
+ async disconnect(connection) {
100
+ await connection.disconnect();
101
+ }
102
+ async testConnection(connection) {
103
+ const startTime = Date.now();
104
+ try {
105
+ const conn = connection.getClient();
106
+ await conn.query('SELECT 1');
107
+ return {
108
+ connected: true,
109
+ message: 'MySQL connection successful',
110
+ databaseType: 'MySQL',
111
+ responseTime: Date.now() - startTime,
112
+ };
113
+ }
114
+ catch (error) {
115
+ return {
116
+ connected: false,
117
+ message: `MySQL connection failed: ${error.message}`,
118
+ databaseType: 'MySQL',
119
+ responseTime: Date.now() - startTime,
120
+ error: error.message,
121
+ };
122
+ }
123
+ }
124
+ // ==================== Transaction Methods ====================
125
+ async beginTransaction(connection, options) {
126
+ const conn = connection.getClient();
127
+ await conn.beginTransaction();
128
+ return { client: conn };
129
+ }
130
+ async commitTransaction(connection, transaction) {
131
+ await transaction.client.commit();
132
+ }
133
+ async rollbackTransaction(connection, transaction) {
134
+ await transaction.client.rollback();
135
+ }
136
+ async createSavepoint(connection, transaction, savepointName) {
137
+ await transaction.client.query(`SAVEPOINT ${this.escapeIdentifier(savepointName)}`);
138
+ }
139
+ async rollbackToSavepoint(connection, transaction, savepointName) {
140
+ await transaction.client.query(`ROLLBACK TO SAVEPOINT ${this.escapeIdentifier(savepointName)}`);
141
+ }
142
+ async releaseSavepoint(connection, transaction, savepointName) {
143
+ await transaction.client.query(`RELEASE SAVEPOINT ${this.escapeIdentifier(savepointName)}`);
144
+ }
145
+ // ==================== Query Methods ====================
146
+ async query(connection, options) {
147
+ const startTime = Date.now();
148
+ try {
149
+ const { query, params } = this.buildSelectQuery(options);
150
+ const conn = connection.getClient();
151
+ const [rows] = await conn.query(query, params);
152
+ const executionTime = Date.now() - startTime;
153
+ return {
154
+ data: rows,
155
+ count: Array.isArray(rows) ? rows.length : 0,
156
+ executionTime,
157
+ };
158
+ }
159
+ catch (error) {
160
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL query failed: ${error.message}`, error);
161
+ }
162
+ }
163
+ async insert(connection, options) {
164
+ const startTime = Date.now();
165
+ try {
166
+ const { query, params } = this.buildInsertQuery(options);
167
+ const conn = connection.getClient();
168
+ const [result] = await conn.query(query, params);
169
+ const executionTime = Date.now() - startTime;
170
+ return {
171
+ insertedCount: result.affectedRows,
172
+ insertedIds: [result.insertId],
173
+ data: undefined,
174
+ executionTime,
175
+ success: true,
176
+ };
177
+ }
178
+ catch (error) {
179
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL insert failed: ${error.message}`, error);
180
+ }
181
+ }
182
+ async update(connection, options) {
183
+ const startTime = Date.now();
184
+ try {
185
+ const { query, params } = this.buildUpdateQuery(options);
186
+ const conn = connection.getClient();
187
+ const [result] = await conn.query(query, params);
188
+ const executionTime = Date.now() - startTime;
189
+ return {
190
+ updatedCount: result.affectedRows,
191
+ matchedCount: result.affectedRows,
192
+ data: undefined,
193
+ executionTime,
194
+ success: true,
195
+ };
196
+ }
197
+ catch (error) {
198
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL update failed: ${error.message}`, error);
199
+ }
200
+ }
201
+ async delete(connection, options) {
202
+ const startTime = Date.now();
203
+ try {
204
+ const { query, params } = this.buildDeleteQuery(options);
205
+ const conn = connection.getClient();
206
+ const [result] = await conn.query(query, params);
207
+ const executionTime = Date.now() - startTime;
208
+ return {
209
+ deletedCount: result.affectedRows,
210
+ data: undefined,
211
+ executionTime,
212
+ success: true,
213
+ };
214
+ }
215
+ catch (error) {
216
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL delete failed: ${error.message}`, error);
217
+ }
218
+ }
219
+ async upsert(connection, options) {
220
+ const startTime = Date.now();
221
+ try {
222
+ const { query, params } = this.buildUpsertQuery(options);
223
+ const conn = connection.getClient();
224
+ const [result] = await conn.query(query, params);
225
+ const executionTime = Date.now() - startTime;
226
+ return {
227
+ insertedCount: 0,
228
+ updatedCount: 0,
229
+ affectedCount: result.affectedRows,
230
+ data: undefined,
231
+ executionTime,
232
+ success: true,
233
+ };
234
+ }
235
+ catch (error) {
236
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL upsert failed: ${error.message}`, error);
237
+ }
238
+ }
239
+ async executeRaw(connection, options) {
240
+ const startTime = Date.now();
241
+ try {
242
+ const conn = connection.getClient();
243
+ const [rows, fields] = await conn.query(options.query, options.params || []);
244
+ const executionTime = Date.now() - startTime;
245
+ return {
246
+ rows: rows,
247
+ rowCount: Array.isArray(rows) ? rows.length : 0,
248
+ fields: fields === null || fields === void 0 ? void 0 : fields.map((f) => ({
249
+ name: f.name,
250
+ type: f.type,
251
+ })),
252
+ executionTime,
253
+ };
254
+ }
255
+ catch (error) {
256
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL raw query failed: ${error.message}`, error);
257
+ }
258
+ }
259
+ // ==================== Aggregation Methods ====================
260
+ async count(connection, options) {
261
+ try {
262
+ const { query, params } = this.buildCountQuery(options);
263
+ const conn = connection.getClient();
264
+ const [rows] = await conn.query(query, params);
265
+ return parseInt(rows[0].count, 10);
266
+ }
267
+ catch (error) {
268
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL count failed: ${error.message}`, error);
269
+ }
270
+ }
271
+ async sum(connection, options) {
272
+ try {
273
+ const { query, params } = this.buildAggregateQuery('SUM', options.column, options);
274
+ const conn = connection.getClient();
275
+ const [rows] = await conn.query(query, params);
276
+ return parseFloat(rows[0].result) || 0;
277
+ }
278
+ catch (error) {
279
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL sum failed: ${error.message}`, error);
280
+ }
281
+ }
282
+ async avg(connection, options) {
283
+ try {
284
+ const { query, params } = this.buildAggregateQuery('AVG', options.column, options);
285
+ const conn = connection.getClient();
286
+ const [rows] = await conn.query(query, params);
287
+ return parseFloat(rows[0].result) || 0;
288
+ }
289
+ catch (error) {
290
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL avg failed: ${error.message}`, error);
291
+ }
292
+ }
293
+ async min(connection, options) {
294
+ try {
295
+ const { query, params } = this.buildAggregateQuery('MIN', options.column, options);
296
+ const conn = connection.getClient();
297
+ const [rows] = await conn.query(query, params);
298
+ return rows[0].result;
299
+ }
300
+ catch (error) {
301
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL min failed: ${error.message}`, error);
302
+ }
303
+ }
304
+ async max(connection, options) {
305
+ try {
306
+ const { query, params } = this.buildAggregateQuery('MAX', options.column, options);
307
+ const conn = connection.getClient();
308
+ const [rows] = await conn.query(query, params);
309
+ return rows[0].result;
310
+ }
311
+ catch (error) {
312
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL max failed: ${error.message}`, error);
313
+ }
314
+ }
315
+ async groupBy(connection, options) {
316
+ try {
317
+ const { query, params } = this.buildGroupByQuery(options);
318
+ const conn = connection.getClient();
319
+ const [rows] = await conn.query(query, params);
320
+ return rows;
321
+ }
322
+ catch (error) {
323
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL groupBy failed: ${error.message}`, error);
324
+ }
325
+ }
326
+ async aggregate(connection, options) {
327
+ try {
328
+ const { query, params } = this.buildMultiAggregateQuery(options);
329
+ const conn = connection.getClient();
330
+ const [rows] = await conn.query(query, params);
331
+ return rows[0] || {};
332
+ }
333
+ catch (error) {
334
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `MySQL aggregate failed: ${error.message}`, error);
335
+ }
336
+ }
337
+ // ==================== Schema Methods ====================
338
+ async createTable(connection, schema, options) {
339
+ const startTime = Date.now();
340
+ try {
341
+ const query = this.buildCreateTableQuery(schema, options);
342
+ const conn = connection.getClient();
343
+ await conn.query(query);
344
+ const executionTime = Date.now() - startTime;
345
+ return {
346
+ success: true,
347
+ operation: 'create',
348
+ table: schema.name,
349
+ executionTime,
350
+ };
351
+ }
352
+ catch (error) {
353
+ return {
354
+ success: false,
355
+ operation: 'create',
356
+ table: schema.name,
357
+ error: error.message,
358
+ executionTime: Date.now() - startTime,
359
+ };
360
+ }
361
+ }
362
+ async dropTable(connection, tableName) {
363
+ const startTime = Date.now();
364
+ try {
365
+ const conn = connection.getClient();
366
+ await conn.query(`DROP TABLE IF EXISTS \`${tableName}\``);
367
+ return {
368
+ success: true,
369
+ operation: 'drop',
370
+ table: tableName,
371
+ executionTime: Date.now() - startTime,
372
+ };
373
+ }
374
+ catch (error) {
375
+ return {
376
+ success: false,
377
+ operation: 'drop',
378
+ table: tableName,
379
+ error: error.message,
380
+ executionTime: Date.now() - startTime,
381
+ };
382
+ }
383
+ }
384
+ async alterTable(connection, tableName, alterations, options) {
385
+ const startTime = Date.now();
386
+ try {
387
+ const conn = connection.getClient();
388
+ const queries = this.buildAlterTableQueries(tableName, alterations);
389
+ for (const query of queries) {
390
+ await conn.query(query);
391
+ }
392
+ return {
393
+ success: true,
394
+ operation: 'alter',
395
+ table: tableName,
396
+ executionTime: Date.now() - startTime,
397
+ };
398
+ }
399
+ catch (error) {
400
+ return {
401
+ success: false,
402
+ operation: 'alter',
403
+ table: tableName,
404
+ error: error.message,
405
+ executionTime: Date.now() - startTime,
406
+ };
407
+ }
408
+ }
409
+ async getTableSchema(connection, tableName) {
410
+ try {
411
+ const conn = connection.getClient();
412
+ const query = `
413
+ SELECT
414
+ COLUMN_NAME as column_name,
415
+ DATA_TYPE as data_type,
416
+ IS_NULLABLE as is_nullable,
417
+ COLUMN_DEFAULT as column_default,
418
+ CHARACTER_MAXIMUM_LENGTH as character_maximum_length,
419
+ NUMERIC_PRECISION as numeric_precision,
420
+ NUMERIC_SCALE as numeric_scale,
421
+ COLUMN_KEY as column_key
422
+ FROM INFORMATION_SCHEMA.COLUMNS
423
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
424
+ ORDER BY ORDINAL_POSITION
425
+ `;
426
+ const [rows] = await conn.query(query, [tableName]);
427
+ const columns = rows.map((row) => ({
428
+ name: row.column_name,
429
+ type: this.mapMySQLTypeToColumnType(row.data_type),
430
+ nullable: row.is_nullable === 'YES',
431
+ primaryKey: row.column_key === 'PRI',
432
+ unique: row.column_key === 'UNI',
433
+ defaultValue: row.column_default,
434
+ length: row.character_maximum_length,
435
+ precision: row.numeric_precision,
436
+ scale: row.numeric_scale,
437
+ }));
438
+ return {
439
+ name: tableName,
440
+ columns,
441
+ };
442
+ }
443
+ catch (error) {
444
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.SCHEMA_ERROR, `Failed to get MySQL table schema: ${error.message}`, error);
445
+ }
446
+ }
447
+ async listTables(connection) {
448
+ try {
449
+ const conn = connection.getClient();
450
+ const [rows] = await conn.query('SHOW TABLES');
451
+ return rows.map((row) => Object.values(row)[0]);
452
+ }
453
+ catch (error) {
454
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.SCHEMA_ERROR, `Failed to list MySQL tables: ${error.message}`, error);
455
+ }
456
+ }
457
+ async tableExists(connection, tableName) {
458
+ try {
459
+ const conn = connection.getClient();
460
+ const query = `
461
+ SELECT COUNT(*) as count
462
+ FROM INFORMATION_SCHEMA.TABLES
463
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
464
+ `;
465
+ const [rows] = await conn.query(query, [tableName]);
466
+ return rows[0].count > 0;
467
+ }
468
+ catch (error) {
469
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.SCHEMA_ERROR, `Failed to check MySQL table existence: ${error.message}`, error);
470
+ }
471
+ }
472
+ // ==================== Index Methods ====================
473
+ async createIndex(connection, options) {
474
+ const startTime = Date.now();
475
+ try {
476
+ const query = this.buildCreateIndexQuery(options);
477
+ const conn = connection.getClient();
478
+ await conn.query(query);
479
+ return {
480
+ success: true,
481
+ operation: 'create',
482
+ indexName: options.index.name,
483
+ table: options.table,
484
+ executionTime: Date.now() - startTime,
485
+ };
486
+ }
487
+ catch (error) {
488
+ return {
489
+ success: false,
490
+ operation: 'create',
491
+ indexName: options.index.name,
492
+ table: options.table,
493
+ error: error.message,
494
+ executionTime: Date.now() - startTime,
495
+ };
496
+ }
497
+ }
498
+ async dropIndex(connection, options) {
499
+ const startTime = Date.now();
500
+ try {
501
+ const conn = connection.getClient();
502
+ await conn.query(`DROP INDEX \`${options.indexName}\` ON \`${options.table}\``);
503
+ return {
504
+ success: true,
505
+ operation: 'drop',
506
+ indexName: options.indexName,
507
+ table: options.table,
508
+ executionTime: Date.now() - startTime,
509
+ };
510
+ }
511
+ catch (error) {
512
+ return {
513
+ success: false,
514
+ operation: 'drop',
515
+ indexName: options.indexName,
516
+ table: options.table,
517
+ error: error.message,
518
+ executionTime: Date.now() - startTime,
519
+ };
520
+ }
521
+ }
522
+ async listIndexes(connection, options) {
523
+ try {
524
+ const conn = connection.getClient();
525
+ const query = `
526
+ SELECT
527
+ INDEX_NAME as index_name,
528
+ COLUMN_NAME as column_name,
529
+ NON_UNIQUE as non_unique,
530
+ INDEX_TYPE as index_type
531
+ FROM INFORMATION_SCHEMA.STATISTICS
532
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
533
+ ORDER BY INDEX_NAME, SEQ_IN_INDEX
534
+ `;
535
+ const [rows] = await conn.query(query, [options.table]);
536
+ const indexMap = new Map();
537
+ rows.forEach((row) => {
538
+ if (!indexMap.has(row.index_name)) {
539
+ indexMap.set(row.index_name, {
540
+ name: row.index_name,
541
+ table: options.table,
542
+ columns: [],
543
+ columnDetails: [],
544
+ unique: row.non_unique === 0,
545
+ primaryKey: row.index_name === 'PRIMARY',
546
+ type: row.index_type,
547
+ });
548
+ }
549
+ const indexInfo = indexMap.get(row.index_name);
550
+ indexInfo.columns.push(row.column_name);
551
+ indexInfo.columnDetails.push({
552
+ name: row.column_name,
553
+ order: 'ASC',
554
+ });
555
+ });
556
+ return Array.from(indexMap.values());
557
+ }
558
+ catch (error) {
559
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.INDEX_ERROR, `Failed to list MySQL indexes: ${error.message}`, error);
560
+ }
561
+ }
562
+ async getIndexStatistics(connection, tableName, indexName) {
563
+ try {
564
+ const conn = connection.getClient();
565
+ let query = `
566
+ SELECT
567
+ INDEX_NAME as index_name,
568
+ CARDINALITY as cardinality
569
+ FROM INFORMATION_SCHEMA.STATISTICS
570
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
571
+ `;
572
+ const params = [tableName];
573
+ if (indexName) {
574
+ query += ' AND INDEX_NAME = ?';
575
+ params.push(indexName);
576
+ }
577
+ const [rows] = await conn.query(query, params);
578
+ return rows.map((row) => ({
579
+ indexName: row.index_name,
580
+ table: tableName,
581
+ size: 0, // MySQL doesn't provide this easily
582
+ tuplesRead: row.cardinality || 0,
583
+ }));
584
+ }
585
+ catch (error) {
586
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.INDEX_ERROR, `Failed to get MySQL index statistics: ${error.message}`, error);
587
+ }
588
+ }
589
+ // ==================== Migration Methods ====================
590
+ /**
591
+ * Build SQL for a migration operation
592
+ */
593
+ async buildMigrationOperationSQL(operation) {
594
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
595
+ switch (operation.type) {
596
+ case migration_types_1.MigrationOperationType.RAW_SQL:
597
+ return operation.sql || null;
598
+ case migration_types_1.MigrationOperationType.CREATE_TABLE:
599
+ if ((_a = operation.params) === null || _a === void 0 ? void 0 : _a.schema) {
600
+ const schema = operation.params.schema;
601
+ const columns = schema.columns
602
+ .map((col) => {
603
+ let def = `\`${col.name}\` ${this.mapColumnTypeToMySQL(col.type)}`;
604
+ if (col.primaryKey)
605
+ def += ' PRIMARY KEY';
606
+ if (col.autoIncrement)
607
+ def += ' AUTO_INCREMENT';
608
+ if (!col.nullable)
609
+ def += ' NOT NULL';
610
+ if (col.unique)
611
+ def += ' UNIQUE';
612
+ if (col.defaultValue !== undefined) {
613
+ def += ` DEFAULT ${this.escapeValue(col.defaultValue)}`;
614
+ }
615
+ return def;
616
+ })
617
+ .join(', ');
618
+ return `CREATE TABLE \`${schema.name}\` (${columns})`;
619
+ }
620
+ return null;
621
+ case migration_types_1.MigrationOperationType.DROP_TABLE:
622
+ return operation.table ? `DROP TABLE IF EXISTS \`${operation.table}\`` : null;
623
+ case migration_types_1.MigrationOperationType.ADD_COLUMN:
624
+ if (operation.table && ((_b = operation.params) === null || _b === void 0 ? void 0 : _b.column)) {
625
+ const col = operation.params.column;
626
+ let def = `ALTER TABLE \`${operation.table}\` ADD COLUMN \`${col.name}\` ${this.mapColumnTypeToMySQL(col.type)}`;
627
+ if (!col.nullable)
628
+ def += ' NOT NULL';
629
+ if (col.defaultValue !== undefined) {
630
+ def += ` DEFAULT ${this.escapeValue(col.defaultValue)}`;
631
+ }
632
+ return def;
633
+ }
634
+ return null;
635
+ case migration_types_1.MigrationOperationType.DROP_COLUMN:
636
+ return operation.table && ((_c = operation.params) === null || _c === void 0 ? void 0 : _c.columnName)
637
+ ? `ALTER TABLE \`${operation.table}\` DROP COLUMN \`${operation.params.columnName}\``
638
+ : null;
639
+ case migration_types_1.MigrationOperationType.MODIFY_COLUMN:
640
+ if (operation.table && ((_d = operation.params) === null || _d === void 0 ? void 0 : _d.column)) {
641
+ const col = operation.params.column;
642
+ return `ALTER TABLE \`${operation.table}\` MODIFY COLUMN \`${col.name}\` ${this.mapColumnTypeToMySQL(col.type)}`;
643
+ }
644
+ return null;
645
+ case migration_types_1.MigrationOperationType.RENAME_COLUMN:
646
+ 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)
647
+ ? `ALTER TABLE \`${operation.table}\` RENAME COLUMN \`${operation.params.oldName}\` TO \`${operation.params.newName}\``
648
+ : null;
649
+ case migration_types_1.MigrationOperationType.ADD_INDEX:
650
+ 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)) {
651
+ const unique = operation.params.unique ? 'UNIQUE ' : '';
652
+ const columns = operation.params.columns.map((col) => `\`${col}\``).join(', ');
653
+ return `CREATE ${unique}INDEX \`${operation.params.indexName}\` ON \`${operation.table}\` (${columns})`;
654
+ }
655
+ return null;
656
+ case migration_types_1.MigrationOperationType.DROP_INDEX:
657
+ return ((_j = operation.params) === null || _j === void 0 ? void 0 : _j.indexName) && operation.table
658
+ ? `DROP INDEX \`${operation.params.indexName}\` ON \`${operation.table}\``
659
+ : null;
660
+ case migration_types_1.MigrationOperationType.ADD_CONSTRAINT:
661
+ 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)) {
662
+ return `ALTER TABLE \`${operation.table}\` ADD CONSTRAINT \`${operation.params.constraintName}\` ${operation.params.definition}`;
663
+ }
664
+ return null;
665
+ case migration_types_1.MigrationOperationType.DROP_CONSTRAINT:
666
+ return operation.table && ((_m = operation.params) === null || _m === void 0 ? void 0 : _m.constraintName)
667
+ ? `ALTER TABLE \`${operation.table}\` DROP CONSTRAINT \`${operation.params.constraintName}\``
668
+ : null;
669
+ default:
670
+ return null;
671
+ }
672
+ }
673
+ async runMigration(connection, migration, options) {
674
+ const startTime = Date.now();
675
+ const statements = [];
676
+ try {
677
+ const conn = connection.getClient();
678
+ await conn.query('START TRANSACTION');
679
+ try {
680
+ // Execute migration operations
681
+ for (const operation of migration.up) {
682
+ const sql = await this.buildMigrationOperationSQL(operation);
683
+ if (sql) {
684
+ statements.push(sql);
685
+ await conn.query(sql);
686
+ }
687
+ }
688
+ // Record migration in history table
689
+ await this.recordMigration(conn, migration, migration_types_1.MigrationDirection.UP);
690
+ await conn.query('COMMIT');
691
+ return {
692
+ tag: migration.tag,
693
+ status: migration_types_1.MigrationStatus.COMPLETED,
694
+ direction: migration_types_1.MigrationDirection.UP,
695
+ executedAt: new Date(),
696
+ duration: Date.now() - startTime,
697
+ statements,
698
+ };
699
+ }
700
+ catch (error) {
701
+ await conn.query('ROLLBACK');
702
+ throw error;
703
+ }
704
+ }
705
+ catch (error) {
706
+ return {
707
+ tag: migration.tag,
708
+ status: migration_types_1.MigrationStatus.FAILED,
709
+ direction: migration_types_1.MigrationDirection.UP,
710
+ executedAt: new Date(),
711
+ duration: Date.now() - startTime,
712
+ error: error.message,
713
+ statements,
714
+ };
715
+ }
716
+ }
717
+ async rollbackMigration(connection, migration, options) {
718
+ const startTime = Date.now();
719
+ const statements = [];
720
+ try {
721
+ const conn = connection.getClient();
722
+ await conn.query('START TRANSACTION');
723
+ try {
724
+ // Execute rollback operations
725
+ for (const operation of migration.down) {
726
+ const sql = await this.buildMigrationOperationSQL(operation);
727
+ if (sql) {
728
+ statements.push(sql);
729
+ await conn.query(sql);
730
+ }
731
+ }
732
+ // Remove migration from history table
733
+ await this.removeMigration(conn, migration.tag);
734
+ await conn.query('COMMIT');
735
+ return {
736
+ tag: migration.tag,
737
+ status: migration_types_1.MigrationStatus.ROLLED_BACK,
738
+ direction: migration_types_1.MigrationDirection.DOWN,
739
+ executedAt: new Date(),
740
+ duration: Date.now() - startTime,
741
+ statements,
742
+ };
743
+ }
744
+ catch (error) {
745
+ await conn.query('ROLLBACK');
746
+ throw error;
747
+ }
748
+ }
749
+ catch (error) {
750
+ return {
751
+ tag: migration.tag,
752
+ status: migration_types_1.MigrationStatus.FAILED,
753
+ direction: migration_types_1.MigrationDirection.DOWN,
754
+ executedAt: new Date(),
755
+ duration: Date.now() - startTime,
756
+ error: error.message,
757
+ statements,
758
+ };
759
+ }
760
+ }
761
+ async getMigrationHistory(connection, options) {
762
+ try {
763
+ const conn = connection.getClient();
764
+ // Ensure migration table exists
765
+ await this.ensureMigrationTable(conn);
766
+ const query = `
767
+ SELECT tag, name, executed_at
768
+ FROM _ductape_migrations
769
+ ORDER BY executed_at DESC
770
+ `;
771
+ const [rows] = await conn.query(query);
772
+ return rows.map((row) => ({
773
+ tag: row.tag,
774
+ name: row.name,
775
+ env: options.env,
776
+ product: options.product,
777
+ database: options.database,
778
+ status: migration_types_1.MigrationStatus.COMPLETED,
779
+ direction: migration_types_1.MigrationDirection.UP,
780
+ appliedAt: row.executed_at,
781
+ }));
782
+ }
783
+ catch (error) {
784
+ throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.MIGRATION_ERROR, `Failed to get MySQL migration history: ${error.message}`, error);
785
+ }
786
+ }
787
+ // ==================== Query Builder Helpers ====================
788
+ /**
789
+ * Build SELECT query from options
790
+ */
791
+ buildSelectQuery(options) {
792
+ const params = [];
793
+ // Handle includes
794
+ const hasIncludes = options.include && (Array.isArray(options.include) ? options.include.length > 0 : true);
795
+ // SELECT clause
796
+ let selectColumns;
797
+ if (options.select) {
798
+ const cols = Array.isArray(options.select) ? options.select : [options.select];
799
+ // Prefix main table columns with table name
800
+ selectColumns = cols.map((col) => `\`${options.table}\`.\`${col}\``).join(', ');
801
+ }
802
+ else {
803
+ // Select all from main table
804
+ selectColumns = hasIncludes ? `\`${options.table}\`.*` : '*';
805
+ }
806
+ // Add included relation columns
807
+ if (hasIncludes) {
808
+ const includes = Array.isArray(options.include) ? options.include : [options.include];
809
+ includes.forEach((include) => {
810
+ const relationAlias = include.relation;
811
+ if (include.select && include.select.length > 0) {
812
+ const relationCols = include.select.map((col) => `\`${relationAlias}\`.\`${col}\` as \`${relationAlias}_${col}\``).join(', ');
813
+ selectColumns += `, ${relationCols}`;
814
+ }
815
+ else {
816
+ // Use JSON aggregation for one-to-many
817
+ if (include.type === 'one-to-many') {
818
+ selectColumns += `, JSON_ARRAYAGG(JSON_OBJECT('*', \`${relationAlias}\`.*)) as \`${relationAlias}\``;
819
+ }
820
+ else {
821
+ selectColumns += `, JSON_OBJECT('*', \`${relationAlias}\`.*) as \`${relationAlias}\``;
822
+ }
823
+ }
824
+ });
825
+ }
826
+ let query = `SELECT ${options.distinct ? 'DISTINCT ' : ''}${selectColumns} FROM \`${options.table}\``;
827
+ // JOIN clauses for includes
828
+ if (hasIncludes) {
829
+ const includes = Array.isArray(options.include) ? options.include : [options.include];
830
+ includes.forEach((include) => {
831
+ const { joinClause } = this.buildIncludeJoin(options.table, include);
832
+ query += ` ${joinClause}`;
833
+ });
834
+ }
835
+ // WHERE clause
836
+ if (options.where) {
837
+ const { clause, values } = this.buildWhereClause(options.where);
838
+ query += ` WHERE ${clause}`;
839
+ params.push(...values);
840
+ }
841
+ // GROUP BY clause
842
+ if (options.groupBy && options.groupBy.length > 0) {
843
+ query += ` GROUP BY ${options.groupBy.map((col) => `\`${col}\``).join(', ')}`;
844
+ }
845
+ else if (hasIncludes) {
846
+ // Auto group by main table primary key for one-to-many relations
847
+ const includes = Array.isArray(options.include) ? options.include : [options.include];
848
+ const hasOneToMany = includes.some(inc => inc.type === 'one-to-many');
849
+ if (hasOneToMany) {
850
+ query += ` GROUP BY \`${options.table}\`.\`id\``;
851
+ }
852
+ }
853
+ // HAVING clause
854
+ if (options.having) {
855
+ const { clause, values } = this.buildWhereClause(options.having);
856
+ query += ` HAVING ${clause}`;
857
+ params.push(...values);
858
+ }
859
+ // ORDER BY clause
860
+ if (options.orderBy) {
861
+ const orderByArray = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
862
+ const orderClauses = orderByArray.map((order) => `\`${options.table}\`.\`${order.column}\` ${order.order || 'ASC'}`);
863
+ query += ` ORDER BY ${orderClauses.join(', ')}`;
864
+ }
865
+ // LIMIT clause
866
+ if (options.limit) {
867
+ query += ` LIMIT ?`;
868
+ params.push(options.limit);
869
+ }
870
+ // OFFSET clause
871
+ if (options.offset) {
872
+ query += ` OFFSET ?`;
873
+ params.push(options.offset);
874
+ }
875
+ return { query, params };
876
+ }
877
+ /**
878
+ * Build JOIN clause for include relationships
879
+ */
880
+ buildIncludeJoin(mainTable, include) {
881
+ const relationTable = include.relation;
882
+ const foreignKey = include.foreignKey || `${relationTable}_id`;
883
+ const primaryKey = include.primaryKey || 'id';
884
+ const joinType = include.type === 'one-to-many' || include.type === 'one-to-one' ? 'LEFT' : 'INNER';
885
+ let joinClause;
886
+ if (include.type === 'many-to-many' && include.through) {
887
+ // Many-to-many with junction table
888
+ const throughTable = include.through;
889
+ const throughForeignKey = include.throughForeignKey || `${mainTable}_id`;
890
+ const throughRelatedKey = include.throughRelatedKey || `${relationTable}_id`;
891
+ joinClause = `${joinType} JOIN \`${throughTable}\` ON \`${mainTable}\`.\`${primaryKey}\` = \`${throughTable}\`.\`${throughForeignKey}\` `;
892
+ joinClause += `${joinType} JOIN \`${relationTable}\` ON \`${throughTable}\`.\`${throughRelatedKey}\` = \`${relationTable}\`.\`${primaryKey}\``;
893
+ }
894
+ else if (include.type === 'one-to-many') {
895
+ // One-to-many: foreign key is in the related table
896
+ joinClause = `${joinType} JOIN \`${relationTable}\` ON \`${mainTable}\`.\`${primaryKey}\` = \`${relationTable}\`.\`${foreignKey}\``;
897
+ }
898
+ else {
899
+ // Many-to-one or one-to-one: foreign key is in the main table
900
+ joinClause = `${joinType} JOIN \`${relationTable}\` ON \`${mainTable}\`.\`${foreignKey}\` = \`${relationTable}\`.\`${primaryKey}\``;
901
+ }
902
+ // Add where clause for the relation if specified
903
+ if (include.where) {
904
+ const { clause } = this.buildWhereClause(include.where);
905
+ joinClause += ` AND ${clause}`;
906
+ }
907
+ return { joinClause };
908
+ }
909
+ /**
910
+ * Build WHERE clause from options
911
+ * Supports both simplified syntax (GT, LT, AND, OR) and legacy syntax ($gt, $lt, $and, $or)
912
+ */
913
+ buildWhereClause(where) {
914
+ const values = [];
915
+ const buildCondition = (condition) => {
916
+ if (typeof condition !== 'object' || condition === null) {
917
+ values.push(condition);
918
+ return '?';
919
+ }
920
+ // Handle logical operators (supports $AND, AND, and legacy $and)
921
+ if (condition.$AND || condition.AND || condition.$and) {
922
+ const andConditions = condition.$AND || condition.AND || condition.$and;
923
+ if (typeof andConditions === 'object' && !Array.isArray(andConditions)) {
924
+ // New syntax: { $AND: { col1: val1, col2: val2 } }
925
+ return buildCondition(andConditions);
926
+ }
927
+ else {
928
+ // Legacy syntax: { $and: [cond1, cond2] }
929
+ const subConditions = andConditions.map(buildCondition);
930
+ return `(${subConditions.join(' AND ')})`;
931
+ }
932
+ }
933
+ if (condition.$OR || condition.OR || condition.$or) {
934
+ const orConditions = condition.$OR || condition.OR || condition.$or;
935
+ if (typeof orConditions === 'object' && !Array.isArray(orConditions)) {
936
+ // New syntax: { $OR: { col1: val1, col2: val2 } }
937
+ const subClauses = [];
938
+ for (const [key, value] of Object.entries(orConditions)) {
939
+ if (key === '$AND' || key === 'AND' || key === '$and' || key === '$OR' || key === 'OR' || key === '$or') {
940
+ subClauses.push(buildCondition({ [key]: value }));
941
+ }
942
+ else {
943
+ subClauses.push(buildCondition({ [key]: value }));
944
+ }
945
+ }
946
+ return `(${subClauses.join(' OR ')})`;
947
+ }
948
+ else {
949
+ // Legacy syntax: { $or: [cond1, cond2] }
950
+ const subConditions = orConditions.map(buildCondition);
951
+ return `(${subConditions.join(' OR ')})`;
952
+ }
953
+ }
954
+ // Handle column conditions
955
+ const conditions = [];
956
+ for (const [key, value] of Object.entries(condition)) {
957
+ // Skip logical operators
958
+ if (key === '$AND' || key === 'AND' || key === '$and' || key === '$OR' || key === 'OR' || key === '$or') {
959
+ continue;
960
+ }
961
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
962
+ // Handle comparison operators
963
+ for (const [op, opValue] of Object.entries(value)) {
964
+ switch (op) {
965
+ // Preferred $-prefixed syntax
966
+ case '$GT':
967
+ case 'GT':
968
+ case '$gt':
969
+ values.push(opValue);
970
+ conditions.push(`\`${key}\` > ?`);
971
+ break;
972
+ case '$GTE':
973
+ case 'GTE':
974
+ case '$gte':
975
+ values.push(opValue);
976
+ conditions.push(`\`${key}\` >= ?`);
977
+ break;
978
+ case '$LT':
979
+ case 'LT':
980
+ case '$lt':
981
+ values.push(opValue);
982
+ conditions.push(`\`${key}\` < ?`);
983
+ break;
984
+ case '$LTE':
985
+ case 'LTE':
986
+ case '$lte':
987
+ values.push(opValue);
988
+ conditions.push(`\`${key}\` <= ?`);
989
+ break;
990
+ case '$NE':
991
+ case '$NOT':
992
+ case 'NE':
993
+ case 'NOT':
994
+ case '$ne':
995
+ values.push(opValue);
996
+ conditions.push(`\`${key}\` != ?`);
997
+ break;
998
+ case '$IN':
999
+ case 'IN':
1000
+ case '$in':
1001
+ if (Array.isArray(opValue)) {
1002
+ const placeholders = opValue.map(() => '?').join(', ');
1003
+ values.push(...opValue);
1004
+ conditions.push(`\`${key}\` IN (${placeholders})`);
1005
+ }
1006
+ break;
1007
+ case '$NOT_IN':
1008
+ case 'NOT_IN':
1009
+ case '$nin':
1010
+ if (Array.isArray(opValue)) {
1011
+ const placeholders = opValue.map(() => '?').join(', ');
1012
+ values.push(...opValue);
1013
+ conditions.push(`\`${key}\` NOT IN (${placeholders})`);
1014
+ }
1015
+ break;
1016
+ case '$LIKE':
1017
+ case 'LIKE':
1018
+ case '$like':
1019
+ values.push(opValue);
1020
+ conditions.push(`\`${key}\` LIKE ?`);
1021
+ break;
1022
+ case '$IS_NULL':
1023
+ case 'IS_NULL':
1024
+ case '$null':
1025
+ conditions.push(`\`${key}\` IS NULL`);
1026
+ break;
1027
+ case '$IS_NOT_NULL':
1028
+ case 'IS_NOT_NULL':
1029
+ case '$notNull':
1030
+ conditions.push(`\`${key}\` IS NOT NULL`);
1031
+ break;
1032
+ case '$BETWEEN':
1033
+ case 'BETWEEN':
1034
+ case '$between':
1035
+ if (Array.isArray(opValue) && opValue.length === 2) {
1036
+ values.push(opValue[0], opValue[1]);
1037
+ conditions.push(`\`${key}\` BETWEEN ? AND ?`);
1038
+ }
1039
+ break;
1040
+ }
1041
+ }
1042
+ }
1043
+ else {
1044
+ // Simple equality
1045
+ values.push(value);
1046
+ conditions.push(`\`${key}\` = ?`);
1047
+ }
1048
+ }
1049
+ return conditions.join(' AND ');
1050
+ };
1051
+ const clause = buildCondition(where);
1052
+ return { clause, values };
1053
+ }
1054
+ /**
1055
+ * Build COUNT query
1056
+ */
1057
+ buildCountQuery(options) {
1058
+ const params = [];
1059
+ const column = options.column ? `\`${options.column}\`` : '*';
1060
+ let query = `SELECT COUNT(${column}) as count FROM \`${options.table}\``;
1061
+ if (options.where) {
1062
+ const { clause, values } = this.buildWhereClause(options.where);
1063
+ query += ` WHERE ${clause}`;
1064
+ params.push(...values);
1065
+ }
1066
+ return { query, params };
1067
+ }
1068
+ /**
1069
+ * Build aggregate query
1070
+ */
1071
+ buildAggregateQuery(fn, column, options) {
1072
+ const params = [];
1073
+ let query = `SELECT ${fn}(\`${column}\`) as result FROM \`${options.table}\``;
1074
+ if (options.where) {
1075
+ const { clause, values } = this.buildWhereClause(options.where);
1076
+ query += ` WHERE ${clause}`;
1077
+ params.push(...values);
1078
+ }
1079
+ return { query, params };
1080
+ }
1081
+ /**
1082
+ * Build INSERT query
1083
+ */
1084
+ buildInsertQuery(options) {
1085
+ const dataArray = Array.isArray(options.data) ? options.data : [options.data];
1086
+ const columns = Object.keys(dataArray[0]);
1087
+ const params = [];
1088
+ let query = `INSERT INTO \`${options.table}\` (${columns.map((col) => `\`${col}\``).join(', ')}) VALUES `;
1089
+ const valuesClauses = dataArray.map((data) => {
1090
+ const placeholders = columns.map(() => '?');
1091
+ params.push(...columns.map((col) => data[col]));
1092
+ return `(${placeholders.join(', ')})`;
1093
+ });
1094
+ query += valuesClauses.join(', ');
1095
+ return { query, params };
1096
+ }
1097
+ /**
1098
+ * Build UPDATE query
1099
+ */
1100
+ buildUpdateQuery(options) {
1101
+ const params = [];
1102
+ const setClauses = Object.keys(options.data).map((key) => {
1103
+ params.push(options.data[key]);
1104
+ return `\`${key}\` = ?`;
1105
+ });
1106
+ let query = `UPDATE \`${options.table}\` SET ${setClauses.join(', ')}`;
1107
+ if (options.where) {
1108
+ const { clause, values } = this.buildWhereClause(options.where);
1109
+ query += ` WHERE ${clause}`;
1110
+ params.push(...values);
1111
+ }
1112
+ return { query, params };
1113
+ }
1114
+ /**
1115
+ * Build DELETE query
1116
+ */
1117
+ buildDeleteQuery(options) {
1118
+ const params = [];
1119
+ let query = `DELETE FROM \`${options.table}\``;
1120
+ if (options.where) {
1121
+ const { clause, values } = this.buildWhereClause(options.where);
1122
+ query += ` WHERE ${clause}`;
1123
+ params.push(...values);
1124
+ }
1125
+ return { query, params };
1126
+ }
1127
+ /**
1128
+ * Build UPSERT query (INSERT ... ON DUPLICATE KEY UPDATE)
1129
+ */
1130
+ buildUpsertQuery(options) {
1131
+ const data = Array.isArray(options.data) ? options.data[0] : options.data;
1132
+ const columns = Object.keys(data);
1133
+ const params = [];
1134
+ let query = `INSERT INTO \`${options.table}\` (${columns.map((col) => `\`${col}\``).join(', ')}) VALUES (`;
1135
+ query += columns.map(() => '?').join(', ');
1136
+ params.push(...columns.map((col) => data[col]));
1137
+ query += ') ON DUPLICATE KEY UPDATE ';
1138
+ const updateClauses = columns
1139
+ .filter((col) => !options.uniqueColumns.includes(col))
1140
+ .map((col) => `\`${col}\` = VALUES(\`${col}\`)`);
1141
+ query += updateClauses.join(', ');
1142
+ return { query, params };
1143
+ }
1144
+ /**
1145
+ * Build GROUP BY query
1146
+ */
1147
+ buildGroupByQuery(options) {
1148
+ const params = [];
1149
+ const groupColumns = options.groupBy.map((col) => `\`${col}\``);
1150
+ const selectColumns = [...groupColumns];
1151
+ // Build aggregations
1152
+ if (options.aggregate) {
1153
+ Object.entries(options.aggregate).forEach(([alias, agg]) => {
1154
+ // Support both old format { function: 'AVG', column: 'price' }
1155
+ // and new simplified format { $AVG: 'price' }
1156
+ if (agg.function && agg.column) {
1157
+ selectColumns.push(`${agg.function}(\`${agg.column}\`) as \`${alias}\``);
1158
+ }
1159
+ else {
1160
+ // Check for $-prefixed aggregation functions
1161
+ const aggObj = agg;
1162
+ const aggFunctions = ['$COUNT', '$AVG', '$SUM', '$MIN', '$MAX', 'COUNT', 'AVG', 'SUM', 'MIN', 'MAX'];
1163
+ for (const func of aggFunctions) {
1164
+ if (aggObj[func] !== undefined) {
1165
+ const funcName = func.replace('$', '');
1166
+ const column = aggObj[func];
1167
+ selectColumns.push(`${funcName}(\`${column}\`) as \`${alias}\``);
1168
+ break;
1169
+ }
1170
+ }
1171
+ }
1172
+ });
1173
+ }
1174
+ let query = `SELECT ${selectColumns.join(', ')} FROM \`${options.table}\``;
1175
+ if (options.where) {
1176
+ const { clause, values } = this.buildWhereClause(options.where);
1177
+ query += ` WHERE ${clause}`;
1178
+ params.push(...values);
1179
+ }
1180
+ query += ` GROUP BY ${groupColumns.join(', ')}`;
1181
+ return { query, params };
1182
+ }
1183
+ /**
1184
+ * Build multi-aggregate query
1185
+ */
1186
+ buildMultiAggregateQuery(options) {
1187
+ const params = [];
1188
+ const aggregations = Object.entries(options.operations).map(([alias, agg]) => {
1189
+ // Support both old format { function: 'AVG', column: 'price' }
1190
+ // and new simplified format { $AVG: 'price' }
1191
+ if (agg.function && agg.column) {
1192
+ return `${agg.function}(\`${agg.column}\`) as \`${alias}\``;
1193
+ }
1194
+ else {
1195
+ // Check for $-prefixed aggregation functions
1196
+ const aggObj = agg;
1197
+ const aggFunctions = ['$COUNT', '$AVG', '$SUM', '$MIN', '$MAX', 'COUNT', 'AVG', 'SUM', 'MIN', 'MAX'];
1198
+ for (const func of aggFunctions) {
1199
+ if (aggObj[func] !== undefined) {
1200
+ const funcName = func.replace('$', '');
1201
+ const column = aggObj[func];
1202
+ return `${funcName}(\`${column}\`) as \`${alias}\``;
1203
+ }
1204
+ }
1205
+ return `${agg.function}(\`${agg.column}\`) as \`${alias}\``; // fallback
1206
+ }
1207
+ });
1208
+ let query = `SELECT ${aggregations.join(', ')} FROM \`${options.table}\``;
1209
+ if (options.where) {
1210
+ const { clause, values } = this.buildWhereClause(options.where);
1211
+ query += ` WHERE ${clause}`;
1212
+ params.push(...values);
1213
+ }
1214
+ return { query, params };
1215
+ }
1216
+ /**
1217
+ * Build CREATE TABLE query
1218
+ */
1219
+ buildCreateTableQuery(schema, options) {
1220
+ const columnDefs = schema.columns.map((col) => {
1221
+ let def = `\`${col.name}\` ${this.mapColumnTypeToMySQL(col.type)}`;
1222
+ if (col.length) {
1223
+ def += `(${col.length})`;
1224
+ }
1225
+ if (col.primaryKey) {
1226
+ def += ' PRIMARY KEY';
1227
+ }
1228
+ if (col.autoIncrement) {
1229
+ def += ' AUTO_INCREMENT';
1230
+ }
1231
+ if (!col.nullable) {
1232
+ def += ' NOT NULL';
1233
+ }
1234
+ if (col.unique) {
1235
+ def += ' UNIQUE';
1236
+ }
1237
+ if (col.defaultValue !== undefined) {
1238
+ def += ` DEFAULT ${this.escapeValue(col.defaultValue)}`;
1239
+ }
1240
+ return def;
1241
+ });
1242
+ let query = `CREATE TABLE ${(options === null || options === void 0 ? void 0 : options.ifNotExists) ? 'IF NOT EXISTS ' : ''}\`${schema.name}\` (${columnDefs.join(', ')})`;
1243
+ return query;
1244
+ }
1245
+ /**
1246
+ * Build ALTER TABLE queries
1247
+ */
1248
+ buildAlterTableQueries(tableName, alterations) {
1249
+ return alterations.map((alt) => {
1250
+ let query = `ALTER TABLE \`${tableName}\` `;
1251
+ switch (alt.type) {
1252
+ case schema_types_1.ColumnAlterationType.ADD:
1253
+ query += `ADD COLUMN \`${alt.column.name}\` ${this.mapColumnTypeToMySQL(alt.column.type)}`;
1254
+ if (!alt.column.nullable) {
1255
+ query += ' NOT NULL';
1256
+ }
1257
+ break;
1258
+ case schema_types_1.ColumnAlterationType.DROP:
1259
+ query += `DROP COLUMN \`${alt.oldName}\``;
1260
+ break;
1261
+ case schema_types_1.ColumnAlterationType.MODIFY:
1262
+ query += `MODIFY COLUMN \`${alt.column.name}\` ${this.mapColumnTypeToMySQL(alt.column.type)}`;
1263
+ break;
1264
+ case schema_types_1.ColumnAlterationType.RENAME:
1265
+ query += `RENAME COLUMN \`${alt.oldName}\` TO \`${alt.newName}\``;
1266
+ break;
1267
+ }
1268
+ return query;
1269
+ });
1270
+ }
1271
+ /**
1272
+ * Build CREATE INDEX query
1273
+ */
1274
+ buildCreateIndexQuery(options) {
1275
+ const unique = options.index.unique ? 'UNIQUE ' : '';
1276
+ const columns = options.index.columns.map((col) => `\`${col.name}\``).join(', ');
1277
+ return `CREATE ${unique}INDEX \`${options.index.name}\` ON \`${options.table}\` (${columns})`;
1278
+ }
1279
+ /**
1280
+ * Map ColumnType to MySQL type
1281
+ */
1282
+ mapColumnTypeToMySQL(type) {
1283
+ const typeMap = {
1284
+ [schema_types_1.ColumnType.STRING]: 'VARCHAR',
1285
+ [schema_types_1.ColumnType.TEXT]: 'TEXT',
1286
+ [schema_types_1.ColumnType.VARCHAR]: 'VARCHAR',
1287
+ [schema_types_1.ColumnType.CHAR]: 'CHAR',
1288
+ [schema_types_1.ColumnType.INTEGER]: 'INT',
1289
+ [schema_types_1.ColumnType.BIGINT]: 'BIGINT',
1290
+ [schema_types_1.ColumnType.SMALLINT]: 'SMALLINT',
1291
+ [schema_types_1.ColumnType.DECIMAL]: 'DECIMAL',
1292
+ [schema_types_1.ColumnType.NUMERIC]: 'DECIMAL',
1293
+ [schema_types_1.ColumnType.FLOAT]: 'FLOAT',
1294
+ [schema_types_1.ColumnType.DOUBLE]: 'DOUBLE',
1295
+ [schema_types_1.ColumnType.REAL]: 'FLOAT',
1296
+ [schema_types_1.ColumnType.BOOLEAN]: 'TINYINT',
1297
+ [schema_types_1.ColumnType.DATE]: 'DATE',
1298
+ [schema_types_1.ColumnType.TIME]: 'TIME',
1299
+ [schema_types_1.ColumnType.DATETIME]: 'DATETIME',
1300
+ [schema_types_1.ColumnType.TIMESTAMP]: 'TIMESTAMP',
1301
+ [schema_types_1.ColumnType.JSON]: 'JSON',
1302
+ [schema_types_1.ColumnType.JSONB]: 'JSON',
1303
+ [schema_types_1.ColumnType.UUID]: 'CHAR(36)',
1304
+ [schema_types_1.ColumnType.BLOB]: 'BLOB',
1305
+ [schema_types_1.ColumnType.BINARY]: 'BINARY',
1306
+ [schema_types_1.ColumnType.ARRAY]: 'JSON',
1307
+ [schema_types_1.ColumnType.OBJECT]: 'JSON',
1308
+ [schema_types_1.ColumnType.ENUM]: 'VARCHAR',
1309
+ };
1310
+ return typeMap[type] || 'TEXT';
1311
+ }
1312
+ /**
1313
+ * Map MySQL type to ColumnType
1314
+ */
1315
+ mapMySQLTypeToColumnType(mysqlType) {
1316
+ const typeMap = {
1317
+ varchar: schema_types_1.ColumnType.VARCHAR,
1318
+ text: schema_types_1.ColumnType.TEXT,
1319
+ char: schema_types_1.ColumnType.CHAR,
1320
+ int: schema_types_1.ColumnType.INTEGER,
1321
+ bigint: schema_types_1.ColumnType.BIGINT,
1322
+ smallint: schema_types_1.ColumnType.SMALLINT,
1323
+ decimal: schema_types_1.ColumnType.DECIMAL,
1324
+ float: schema_types_1.ColumnType.FLOAT,
1325
+ double: schema_types_1.ColumnType.DOUBLE,
1326
+ tinyint: schema_types_1.ColumnType.BOOLEAN,
1327
+ date: schema_types_1.ColumnType.DATE,
1328
+ time: schema_types_1.ColumnType.TIME,
1329
+ datetime: schema_types_1.ColumnType.DATETIME,
1330
+ timestamp: schema_types_1.ColumnType.TIMESTAMP,
1331
+ json: schema_types_1.ColumnType.JSON,
1332
+ blob: schema_types_1.ColumnType.BLOB,
1333
+ binary: schema_types_1.ColumnType.BINARY,
1334
+ };
1335
+ return typeMap[mysqlType.toLowerCase()] || schema_types_1.ColumnType.TEXT;
1336
+ }
1337
+ /**
1338
+ * Ensure migration table exists
1339
+ */
1340
+ async ensureMigrationTable(conn) {
1341
+ const query = `
1342
+ CREATE TABLE IF NOT EXISTS _ductape_migrations (
1343
+ tag VARCHAR(255) PRIMARY KEY,
1344
+ name VARCHAR(255) NOT NULL,
1345
+ executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1346
+ )
1347
+ `;
1348
+ await conn.query(query);
1349
+ }
1350
+ /**
1351
+ * Record migration execution
1352
+ */
1353
+ async recordMigration(conn, migration, direction) {
1354
+ await this.ensureMigrationTable(conn);
1355
+ const query = `
1356
+ INSERT INTO _ductape_migrations (tag, name, executed_at)
1357
+ VALUES (?, ?, CURRENT_TIMESTAMP)
1358
+ ON DUPLICATE KEY UPDATE executed_at = CURRENT_TIMESTAMP
1359
+ `;
1360
+ await conn.query(query, [migration.tag, migration.name]);
1361
+ }
1362
+ /**
1363
+ * Remove migration from history
1364
+ */
1365
+ async removeMigration(conn, tag) {
1366
+ await this.ensureMigrationTable(conn);
1367
+ const query = `DELETE FROM _ductape_migrations WHERE tag = ?`;
1368
+ await conn.query(query, [tag]);
1369
+ }
1370
+ // ==================== Utility Methods ====================
1371
+ /**
1372
+ * Escape identifier (table/column name)
1373
+ */
1374
+ escapeIdentifier(identifier) {
1375
+ return `\`${identifier.replace(/`/g, '``')}\``;
1376
+ }
1377
+ /**
1378
+ * Escape value for SQL
1379
+ */
1380
+ escapeValue(value) {
1381
+ if (value === null || value === undefined) {
1382
+ return 'NULL';
1383
+ }
1384
+ if (typeof value === 'string') {
1385
+ return `'${value.replace(/'/g, "''")}'`;
1386
+ }
1387
+ if (typeof value === 'boolean') {
1388
+ return value ? '1' : '0';
1389
+ }
1390
+ if (value instanceof Date) {
1391
+ return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
1392
+ }
1393
+ if (Array.isArray(value)) {
1394
+ return `'${JSON.stringify(value)}'`;
1395
+ }
1396
+ if (typeof value === 'object') {
1397
+ return `'${JSON.stringify(value)}'`;
1398
+ }
1399
+ return String(value);
1400
+ }
1401
+ /**
1402
+ * Get database version
1403
+ */
1404
+ async getDatabaseVersion(connection) {
1405
+ var _a;
1406
+ try {
1407
+ const conn = connection.getClient();
1408
+ const [rows] = await conn.query('SELECT VERSION() as version');
1409
+ return ((_a = rows[0]) === null || _a === void 0 ? void 0 : _a.version) || 'Unknown';
1410
+ }
1411
+ catch (error) {
1412
+ return 'Unknown';
1413
+ }
1414
+ }
1415
+ }
1416
+ exports.MySQLAdapter = MySQLAdapter;
1417
+ //# sourceMappingURL=mysql.adapter.js.map