@bdkinc/knex-ibmi 0.3.22 → 0.4.3

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/index.mjs ADDED
@@ -0,0 +1,1359 @@
1
+ import { createRequire } from 'module';
2
+ const require = createRequire(import.meta.url);
3
+ var __defProp = Object.defineProperty;
4
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+
7
+ // src/index.ts
8
+ import process from "process";
9
+ import knex from "knex";
10
+ import odbc from "odbc";
11
+
12
+ // src/schema/ibmi-compiler.ts
13
+ import SchemaCompiler from "knex/lib/schema/compiler.js";
14
+ var IBMiSchemaCompiler = class extends SchemaCompiler {
15
+ hasTable(tableName) {
16
+ const upperName = String(tableName).toUpperCase();
17
+ let schemaName = null;
18
+ let actualTableName = upperName;
19
+ if (upperName.includes(".")) {
20
+ const parts = upperName.split(".");
21
+ schemaName = parts[0];
22
+ actualTableName = parts[1];
23
+ }
24
+ const builderSchema = this.builder._schema;
25
+ if (builderSchema) {
26
+ schemaName = builderSchema.toUpperCase();
27
+ }
28
+ let sql;
29
+ let bindings;
30
+ if (schemaName) {
31
+ sql = `select count(*) as table_count from QSYS2.SYSTABLES where UPPER(TABLE_NAME) = ? AND UPPER(TABLE_SCHEMA) = ?`;
32
+ bindings = [actualTableName, schemaName];
33
+ } else {
34
+ sql = `select count(*) as table_count from QSYS2.SYSTABLES where UPPER(TABLE_NAME) = ?`;
35
+ bindings = [actualTableName];
36
+ }
37
+ this.pushQuery({
38
+ sql,
39
+ bindings,
40
+ output: (runner, resp) => {
41
+ if (!resp) {
42
+ return false;
43
+ }
44
+ if (Array.isArray(resp) && resp.length > 0) {
45
+ const firstRow = resp[0];
46
+ if (firstRow && typeof firstRow === "object") {
47
+ const count = firstRow.table_count || firstRow.TABLE_COUNT || firstRow.count || firstRow.COUNT || 0;
48
+ return count > 0;
49
+ }
50
+ }
51
+ if (typeof resp === "object" && resp !== null) {
52
+ const keys = Object.keys(resp);
53
+ for (const key of keys) {
54
+ if (!isNaN(parseInt(key))) {
55
+ const row = resp[key];
56
+ if (row && typeof row === "object") {
57
+ const count = row.table_count || row.TABLE_COUNT || row.count || row.COUNT || 0;
58
+ return count > 0;
59
+ }
60
+ }
61
+ }
62
+ if (resp.rows && Array.isArray(resp.rows) && resp.rows.length > 0) {
63
+ const firstRow = resp.rows[0];
64
+ if (firstRow && typeof firstRow === "object") {
65
+ const count = firstRow.table_count || firstRow.TABLE_COUNT || firstRow.count || firstRow.COUNT || 0;
66
+ return count > 0;
67
+ }
68
+ }
69
+ }
70
+ return false;
71
+ }
72
+ });
73
+ }
74
+ toSQL() {
75
+ const sequence = this.builder._sequence;
76
+ for (let i = 0, l = sequence.length; i < l; i++) {
77
+ const query = sequence[i];
78
+ this[query.method].apply(this, query.args);
79
+ }
80
+ return this.sequence;
81
+ }
82
+ };
83
+ var ibmi_compiler_default = IBMiSchemaCompiler;
84
+
85
+ // src/schema/ibmi-tablecompiler.ts
86
+ import TableCompiler from "knex/lib/schema/tablecompiler.js";
87
+ var IBMiTableCompiler = class extends TableCompiler {
88
+ createQuery(columns, ifNot, like) {
89
+ let createStatement = ifNot ? `if object_id('${this.tableName()}', 'U') is null ` : "";
90
+ if (like) {
91
+ createStatement += `select * into ${this.tableName()} from ${this.tableNameLike()} WHERE 0=1`;
92
+ } else {
93
+ createStatement += "create table " + this.tableName() + (this._formatting ? " (\n " : " (") + columns.sql.join(this._formatting ? ",\n " : ", ") + this._addChecks() + ")";
94
+ }
95
+ this.pushQuery(createStatement);
96
+ if (this.single.comment) {
97
+ this.comment(this.single.comment);
98
+ }
99
+ if (like) {
100
+ this.addColumns(columns, this.addColumnsPrefix);
101
+ }
102
+ }
103
+ dropUnique(columns, indexName) {
104
+ indexName = indexName ? this.formatter.wrap(indexName) : this._indexCommand("unique", this.tableNameRaw, columns);
105
+ this.pushQuery(`drop index ${indexName}`);
106
+ }
107
+ unique(columns, indexName) {
108
+ let deferrable = "";
109
+ let predicate;
110
+ let finalIndexName;
111
+ if (typeof indexName === "object" && indexName !== null) {
112
+ deferrable = indexName.deferrable || "";
113
+ predicate = indexName.predicate;
114
+ finalIndexName = indexName.indexName;
115
+ } else {
116
+ finalIndexName = indexName;
117
+ }
118
+ if (deferrable && deferrable !== "not deferrable") {
119
+ this.client.logger.warn?.(
120
+ `IBMi: unique index \`${finalIndexName}\` will not be deferrable ${deferrable}.`
121
+ );
122
+ }
123
+ const wrappedIndexName = finalIndexName ? this.formatter.wrap(finalIndexName) : this._indexCommand("unique", this.tableNameRaw, columns);
124
+ columns = this.formatter.columnize(columns);
125
+ const predicateQuery = predicate ? " " + this.client.queryCompiler(predicate).where() : "";
126
+ this.pushQuery(
127
+ `create unique index ${wrappedIndexName} on ${this.tableName()} (${columns})${predicateQuery}`
128
+ );
129
+ }
130
+ // All of the columns to "add" for the query
131
+ addColumns(columns, prefix) {
132
+ prefix = prefix || this.addColumnsPrefix;
133
+ if (columns.sql.length > 0) {
134
+ const columnSql = columns.sql.map((column) => {
135
+ return prefix + column;
136
+ });
137
+ this.pushQuery({
138
+ sql: (this.lowerCase ? "alter table " : "ALTER TABLE ") + this.tableName() + " " + columnSql.join(" "),
139
+ bindings: columns.bindings
140
+ });
141
+ }
142
+ }
143
+ async commit(connection) {
144
+ return await connection.commit();
145
+ }
146
+ };
147
+ var ibmi_tablecompiler_default = IBMiTableCompiler;
148
+
149
+ // src/schema/ibmi-columncompiler.ts
150
+ import ColumnCompiler from "knex/lib/schema/columncompiler.js";
151
+ var IBMiColumnCompiler = class extends ColumnCompiler {
152
+ increments(options = { primaryKey: true }) {
153
+ return "int not null generated always as identity (start with 1, increment by 1)" + (this.tableCompiler._canBeAddPrimaryKey(options) ? " primary key" : "");
154
+ }
155
+ // Add more IBM i DB2 specific column types for better support
156
+ bigIncrements(options = { primaryKey: true }) {
157
+ return "bigint not null generated always as identity (start with 1, increment by 1)" + (this.tableCompiler._canBeAddPrimaryKey(options) ? " primary key" : "");
158
+ }
159
+ varchar(length) {
160
+ return length ? `varchar(${length})` : "varchar(255)";
161
+ }
162
+ char(length) {
163
+ return length ? `char(${length})` : "char(1)";
164
+ }
165
+ text() {
166
+ return "clob(1M)";
167
+ }
168
+ mediumtext() {
169
+ return "clob(16M)";
170
+ }
171
+ longtext() {
172
+ return "clob(2G)";
173
+ }
174
+ binary(length) {
175
+ return length ? `binary(${length})` : "binary(1)";
176
+ }
177
+ varbinary(length) {
178
+ return length ? `varbinary(${length})` : "varbinary(255)";
179
+ }
180
+ // IBM i DB2 decimal with precision/scale
181
+ decimal(precision, scale) {
182
+ if (precision && scale) {
183
+ return `decimal(${precision}, ${scale})`;
184
+ } else if (precision) {
185
+ return `decimal(${precision})`;
186
+ }
187
+ return "decimal(10, 2)";
188
+ }
189
+ // IBM i DB2 timestamp
190
+ timestamp(options) {
191
+ if (options?.useTz) {
192
+ return "timestamp with time zone";
193
+ }
194
+ return "timestamp";
195
+ }
196
+ datetime(options) {
197
+ return this.timestamp(options);
198
+ }
199
+ // IBM i DB2 date and time types
200
+ date() {
201
+ return "date";
202
+ }
203
+ time() {
204
+ return "time";
205
+ }
206
+ // JSON support (IBM i 7.3+)
207
+ json() {
208
+ return "clob(16M) check (json_valid(json_column))";
209
+ }
210
+ jsonb() {
211
+ return "clob(16M) check (json_valid(jsonb_column))";
212
+ }
213
+ // UUID support using CHAR(36)
214
+ uuid() {
215
+ return "char(36)";
216
+ }
217
+ };
218
+ var ibmi_columncompiler_default = IBMiColumnCompiler;
219
+
220
+ // src/execution/ibmi-transaction.ts
221
+ import Transaction from "knex/lib/execution/transaction.js";
222
+ var IBMiTransaction = class extends Transaction {
223
+ begin(connection) {
224
+ try {
225
+ return connection.beginTransaction();
226
+ } catch (error) {
227
+ if (this.isConnectionClosed(error)) {
228
+ console.warn(
229
+ "IBM i DB2: Connection closed during transaction begin, DDL operations may have caused implicit commit"
230
+ );
231
+ throw new Error(
232
+ "Connection closed during transaction begin - consider using migrations.disableTransactions: true"
233
+ );
234
+ }
235
+ throw error;
236
+ }
237
+ }
238
+ rollback(connection) {
239
+ try {
240
+ return connection.rollback();
241
+ } catch (error) {
242
+ console.warn(
243
+ "IBM i DB2: Rollback encountered an error (likely closed connection):",
244
+ error?.message || error
245
+ );
246
+ return Promise.resolve();
247
+ }
248
+ }
249
+ commit(connection) {
250
+ try {
251
+ return connection.commit();
252
+ } catch (error) {
253
+ if (this.isConnectionClosed(error)) {
254
+ console.warn(
255
+ "IBM i DB2: Connection closed during commit - DDL operations cause implicit commits"
256
+ );
257
+ throw new Error(
258
+ "Connection closed during commit - this is expected with DDL operations on IBM i DB2"
259
+ );
260
+ }
261
+ throw error;
262
+ }
263
+ }
264
+ isConnectionClosed(error) {
265
+ const message = String(error?.message || error || "").toLowerCase();
266
+ return message.includes("connection") && (message.includes("closed") || message.includes("invalid") || message.includes("terminated") || message.includes("not connected"));
267
+ }
268
+ };
269
+ var ibmi_transaction_default = IBMiTransaction;
270
+
271
+ // src/query/ibmi-querycompiler.ts
272
+ import QueryCompiler from "knex/lib/query/querycompiler.js";
273
+ import { rawOrFn as rawOrFn_ } from "knex/lib/formatter/wrappingFormatter.js";
274
+ var IBMiQueryCompiler = class extends QueryCompiler {
275
+ constructor() {
276
+ super(...arguments);
277
+ // Cache for column metadata to improve performance with repeated operations
278
+ __publicField(this, "columnCache", /* @__PURE__ */ new Map());
279
+ }
280
+ // Override select method to add IBM i optimization hints
281
+ select() {
282
+ const originalResult = super.select.call(this);
283
+ return originalResult;
284
+ }
285
+ formatTimestampLocal(date) {
286
+ const pad = (n) => String(n).padStart(2, "0");
287
+ const y = date.getFullYear();
288
+ const m = pad(date.getMonth() + 1);
289
+ const d = pad(date.getDate());
290
+ const hh = pad(date.getHours());
291
+ const mm = pad(date.getMinutes());
292
+ const ss = pad(date.getSeconds());
293
+ return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
294
+ }
295
+ insert() {
296
+ const insertValues = this.single.insert || [];
297
+ const { returning } = this.single;
298
+ if (this.isEmptyInsertValues(insertValues)) {
299
+ if (this.isEmptyObject(insertValues)) {
300
+ return this.buildEmptyInsertResult(returning);
301
+ }
302
+ return "";
303
+ }
304
+ const ibmiConfig = this.client?.config?.ibmi || {};
305
+ const multiRowStrategy = ibmiConfig.multiRowInsert || "auto";
306
+ const isArrayInsert = Array.isArray(insertValues) && insertValues.length > 1;
307
+ const originalValues = isArrayInsert ? insertValues.slice() : insertValues;
308
+ const forceSingleRow = multiRowStrategy === "disabled" || multiRowStrategy === "sequential" && isArrayInsert;
309
+ let workingValues = insertValues;
310
+ if (forceSingleRow && isArrayInsert) {
311
+ workingValues = [insertValues[0]];
312
+ this.single.insert = workingValues;
313
+ }
314
+ const standardInsert = super.insert();
315
+ const insertSql = typeof standardInsert === "object" && standardInsert.sql ? standardInsert.sql : standardInsert;
316
+ const multiRow = isArrayInsert && !forceSingleRow;
317
+ if (multiRow && returning === "*") {
318
+ if (this.client?.printWarn) {
319
+ this.client.printWarn("multi-row insert with returning * may be large");
320
+ }
321
+ }
322
+ const selectColumns = returning ? this.formatter.columnize(returning) : multiRow ? "*" : "IDENTITY_VAL_LOCAL()";
323
+ const sql = `select ${selectColumns} from FINAL TABLE(${insertSql})`;
324
+ if (multiRowStrategy === "sequential" && isArrayInsert) {
325
+ const first = originalValues[0];
326
+ const columns = Object.keys(first).sort();
327
+ return {
328
+ sql,
329
+ returning: void 0,
330
+ _ibmiSequentialInsert: {
331
+ columns,
332
+ rows: originalValues,
333
+ tableName: this.tableName,
334
+ returning: returning || null,
335
+ identityOnly: !returning
336
+ }
337
+ };
338
+ }
339
+ return { sql, returning: void 0 };
340
+ }
341
+ isEmptyInsertValues(insertValues) {
342
+ return Array.isArray(insertValues) && insertValues.length === 0 || this.isEmptyObject(insertValues);
343
+ }
344
+ isEmptyObject(insertValues) {
345
+ return insertValues !== null && typeof insertValues === "object" && !Array.isArray(insertValues) && Object.keys(insertValues).length === 0;
346
+ }
347
+ buildEmptyInsertResult(returning) {
348
+ const selectColumns = returning ? this.formatter.columnize(returning) : "IDENTITY_VAL_LOCAL()";
349
+ const returningSql = returning ? this._returning("insert", returning, void 0) + " " : "";
350
+ const insertSql = [
351
+ this.with(),
352
+ `insert into ${this.tableName}`,
353
+ returningSql + this._emptyInsertValue
354
+ ].filter(Boolean).join(" ");
355
+ const sql = `select ${selectColumns} from FINAL TABLE(${insertSql})`;
356
+ return { sql, returning };
357
+ }
358
+ _buildInsertData(insertValues, returningSql) {
359
+ const insertData = this._prepInsert(insertValues);
360
+ if (insertData.columns.length > 0) {
361
+ const parts = [];
362
+ parts.push("(" + this.formatter.columnize(insertData.columns) + ") ");
363
+ if (returningSql) parts.push(returningSql);
364
+ parts.push("values ");
365
+ const rowsSql = [];
366
+ for (const row of insertData.values) {
367
+ const placeholders = row.map(() => "?").join(", ");
368
+ rowsSql.push("(" + placeholders + ")");
369
+ }
370
+ parts.push(rowsSql.join(", "));
371
+ return parts.join("");
372
+ }
373
+ if (Array.isArray(insertValues) && insertValues.length === 1 && insertValues[0]) {
374
+ return (returningSql || "") + this._emptyInsertValue;
375
+ }
376
+ return "";
377
+ }
378
+ generateCacheKey(data) {
379
+ if (Array.isArray(data) && data.length > 0) {
380
+ return Object.keys(data[0] || {}).sort().join("|");
381
+ }
382
+ if (data && typeof data === "object") {
383
+ return Object.keys(data).sort().join("|");
384
+ }
385
+ return "";
386
+ }
387
+ buildFromCache(data, cachedColumns) {
388
+ const dataArray = Array.isArray(data) ? data : data ? [data] : [];
389
+ const values = [];
390
+ for (const item of dataArray) {
391
+ if (item == null) {
392
+ break;
393
+ }
394
+ const row = cachedColumns.map((column) => item[column] ?? void 0);
395
+ values.push(row);
396
+ }
397
+ return {
398
+ columns: cachedColumns,
399
+ values
400
+ };
401
+ }
402
+ _prepInsert(data) {
403
+ if (typeof data === "object" && data?.migration_time) {
404
+ const parsed = new Date(data.migration_time);
405
+ if (!isNaN(parsed.getTime())) {
406
+ data.migration_time = this.formatTimestampLocal(parsed);
407
+ }
408
+ }
409
+ const isRaw = rawOrFn_(
410
+ data,
411
+ void 0,
412
+ this.builder,
413
+ this.client,
414
+ this.bindingsHolder
415
+ );
416
+ if (isRaw) {
417
+ return isRaw;
418
+ }
419
+ const dataArray = Array.isArray(data) ? data : data ? [data] : [];
420
+ if (dataArray.length === 0) {
421
+ return { columns: [], values: [] };
422
+ }
423
+ const firstItem = dataArray[0];
424
+ if (!firstItem || typeof firstItem !== "object") {
425
+ return { columns: [], values: [] };
426
+ }
427
+ const cacheKey = this.generateCacheKey(firstItem);
428
+ let columns;
429
+ if (cacheKey && this.columnCache.has(cacheKey)) {
430
+ columns = this.columnCache.get(cacheKey);
431
+ } else {
432
+ columns = Object.keys(firstItem).sort();
433
+ if (cacheKey && columns.length > 0)
434
+ this.columnCache.set(cacheKey, columns);
435
+ }
436
+ const values = [];
437
+ for (const item of dataArray) {
438
+ if (!item || typeof item !== "object") continue;
439
+ values.push(columns.map((c) => item[c] ?? void 0));
440
+ }
441
+ return { columns, values };
442
+ }
443
+ update() {
444
+ const withSQL = this.with();
445
+ const updates = this._prepUpdate(this.single.update);
446
+ const where = this.where();
447
+ const order = this.order();
448
+ const limit = this.limit();
449
+ const { returning } = this.single;
450
+ const optimizationHints = "";
451
+ const baseUpdateSql = [
452
+ withSQL,
453
+ `update ${this.single.only ? "only " : ""}${this.tableName}`,
454
+ "set",
455
+ updates.join(", "),
456
+ where,
457
+ order,
458
+ limit,
459
+ optimizationHints
460
+ ].filter(Boolean).join(" ");
461
+ if (returning) {
462
+ const selectColumns = this.formatter.columnize(this.single.returning);
463
+ const expectedSql = `select ${selectColumns} from FINAL TABLE(${baseUpdateSql})`;
464
+ return {
465
+ sql: expectedSql,
466
+ returning,
467
+ _ibmiUpdateReturning: {
468
+ updateSql: baseUpdateSql,
469
+ selectColumns,
470
+ whereClause: where,
471
+ tableName: this.tableName
472
+ }
473
+ };
474
+ }
475
+ return { sql: baseUpdateSql, returning };
476
+ }
477
+ // Emulate DELETE ... RETURNING by compiling a FINAL TABLE wrapper for display and attaching metadata
478
+ del() {
479
+ const baseDelete = super.del();
480
+ const { returning } = this.single;
481
+ if (!returning) {
482
+ return { sql: baseDelete, returning: void 0 };
483
+ }
484
+ const deleteSql = typeof baseDelete === "object" && baseDelete.sql ? baseDelete.sql : baseDelete;
485
+ const selectColumns = this.formatter.columnize(returning);
486
+ const expectedSql = `select ${selectColumns} from FINAL TABLE(${deleteSql})`;
487
+ return {
488
+ sql: expectedSql,
489
+ returning,
490
+ _ibmiDeleteReturning: {
491
+ deleteSql,
492
+ selectColumns,
493
+ whereClause: this.where(),
494
+ tableName: this.tableName
495
+ }
496
+ };
497
+ }
498
+ /**
499
+ * Handle returning clause for IBMi DB2 queries
500
+ * Note: IBMi DB2 has limited support for RETURNING clauses
501
+ * @param method - The SQL method (insert, update, delete)
502
+ * @param value - The returning value
503
+ * @param withTrigger - Trigger support (currently unused)
504
+ */
505
+ _returning(method, value, withTrigger) {
506
+ switch (method) {
507
+ case "update":
508
+ case "insert":
509
+ return value ? `${withTrigger ? " into #out" : ""}` : "";
510
+ case "del":
511
+ return value ? `${withTrigger ? " into #out" : ""}` : "";
512
+ case "rowcount":
513
+ return value ? "select @@rowcount" : "";
514
+ default:
515
+ return "";
516
+ }
517
+ }
518
+ getOptimizationHints(queryType, data) {
519
+ const hints = [];
520
+ if (queryType === "select") {
521
+ hints.push("WITH UR");
522
+ }
523
+ return hints.length > 0 ? " " + hints.join(" ") : "";
524
+ }
525
+ getSelectOptimizationHints(sql) {
526
+ const hints = [];
527
+ hints.push("WITH UR");
528
+ return hints.length > 0 ? " " + hints.join(" ") : "";
529
+ }
530
+ columnizeWithPrefix(prefix, target) {
531
+ const columns = typeof target === "string" ? [target] : target;
532
+ const parts = [];
533
+ for (let i = 0; i < columns.length; i++) {
534
+ if (i > 0) parts.push(", ");
535
+ parts.push(prefix + this.wrap(columns[i]));
536
+ }
537
+ return parts.join("");
538
+ }
539
+ };
540
+ var ibmi_querycompiler_default = IBMiQueryCompiler;
541
+
542
+ // src/index.ts
543
+ import { Readable } from "stream";
544
+
545
+ // src/migrations/ibmi-migration-runner.ts
546
+ import fs from "fs";
547
+ import path from "path";
548
+ var IBMiMigrationRunner = class {
549
+ constructor(knex2, config) {
550
+ __publicField(this, "knex");
551
+ __publicField(this, "config");
552
+ this.knex = knex2;
553
+ this.config = {
554
+ directory: "./migrations",
555
+ tableName: "KNEX_MIGRATIONS",
556
+ schemaName: void 0,
557
+ extension: "js",
558
+ ...config
559
+ };
560
+ }
561
+ getFullTableName() {
562
+ return this.config.schemaName ? `${this.config.schemaName}.${this.config.tableName}` : this.config.tableName;
563
+ }
564
+ async latest() {
565
+ try {
566
+ console.log(
567
+ "\u{1F680} IBM i DB2 Migration Runner - bypassing Knex locking system"
568
+ );
569
+ const tableName = this.getFullTableName();
570
+ const migrationTableExists = await this.knex.schema.hasTable(
571
+ tableName
572
+ );
573
+ if (!migrationTableExists) {
574
+ console.log(`\u{1F4DD} Creating migration table: ${tableName}`);
575
+ await this.knex.schema.createTable(tableName, (table) => {
576
+ table.increments("id").primary();
577
+ table.string("name");
578
+ table.integer("batch");
579
+ table.timestamp("migration_time");
580
+ });
581
+ console.log("\u2705 Migration table created");
582
+ }
583
+ const completed = await this.knex(tableName).select("NAME").orderBy("ID");
584
+ const completedNames = completed.map((c) => c.NAME);
585
+ console.log(`\u{1F4CB} Found ${completedNames.length} completed migrations`);
586
+ const migrationFiles = this.getMigrationFiles();
587
+ console.log(`\u{1F4C1} Found ${migrationFiles.length} migration files`);
588
+ const newMigrations = migrationFiles.filter(
589
+ (file) => !completedNames.includes(file)
590
+ );
591
+ if (newMigrations.length === 0) {
592
+ console.log("\u2705 No new migrations to run");
593
+ return;
594
+ }
595
+ console.log(`\u{1F3AF} Running ${newMigrations.length} new migrations:`);
596
+ newMigrations.forEach((file) => console.log(` - ${file}`));
597
+ const batchResult = await this.knex(tableName).max("BATCH as max_batch");
598
+ const nextBatch = (batchResult[0]?.max_batch || 0) + 1;
599
+ console.log(`\u{1F4CA} Using batch number: ${nextBatch}`);
600
+ for (const migrationFile of newMigrations) {
601
+ console.log(`
602
+ \u{1F504} Running migration: ${migrationFile}`);
603
+ try {
604
+ const migrationPath = this.getMigrationPath(migrationFile);
605
+ const migration = await import(migrationPath);
606
+ if (!migration.up || typeof migration.up !== "function") {
607
+ throw new Error(`Migration ${migrationFile} has no 'up' function`);
608
+ }
609
+ console.log(` \u26A1 Executing migration...`);
610
+ await migration.up(this.knex);
611
+ await this.knex(tableName).insert({
612
+ name: migrationFile,
613
+ batch: nextBatch,
614
+ migration_time: /* @__PURE__ */ new Date()
615
+ });
616
+ console.log(` \u2705 Migration ${migrationFile} completed successfully`);
617
+ } catch (error) {
618
+ console.error(
619
+ ` \u274C Migration ${migrationFile} failed:`,
620
+ error.message
621
+ );
622
+ throw error;
623
+ }
624
+ }
625
+ console.log(`
626
+ \u{1F389} All migrations completed successfully!`);
627
+ } catch (error) {
628
+ console.error("\u274C Migration runner failed:", error.message);
629
+ throw error;
630
+ }
631
+ }
632
+ async rollback(steps = 1) {
633
+ try {
634
+ console.log(`\u{1F504} Rolling back ${steps} migration batch(es)...`);
635
+ const tableName = this.getFullTableName();
636
+ const batchesToRollback = await this.knex(tableName).distinct("BATCH").orderBy("BATCH", "desc").limit(steps);
637
+ if (batchesToRollback.length === 0) {
638
+ console.log("\u2705 No migrations to rollback");
639
+ return;
640
+ }
641
+ const batchNumbers = batchesToRollback.map((b) => b.BATCH);
642
+ console.log(`\u{1F4CA} Rolling back batches: ${batchNumbers.join(", ")}`);
643
+ const migrationsToRollback = await this.knex(tableName).select("NAME").whereIn("BATCH", batchNumbers).orderBy("ID", "desc");
644
+ console.log(`\u{1F3AF} Rolling back ${migrationsToRollback.length} migrations:`);
645
+ migrationsToRollback.forEach((m) => console.log(` - ${m.NAME}`));
646
+ for (const migrationRecord of migrationsToRollback) {
647
+ const migrationFile = migrationRecord.NAME;
648
+ console.log(`
649
+ \u{1F504} Rolling back migration: ${migrationFile}`);
650
+ try {
651
+ const migrationPath = this.getMigrationPath(migrationFile);
652
+ const migration = await import(migrationPath);
653
+ if (migration.down && typeof migration.down === "function") {
654
+ console.log(` \u26A1 Executing rollback...`);
655
+ await migration.down(this.knex);
656
+ } else {
657
+ console.log(
658
+ ` \u26A0\uFE0F Migration ${migrationFile} has no 'down' function, skipping rollback`
659
+ );
660
+ }
661
+ await this.knex(tableName).where("NAME", migrationFile).del();
662
+ console.log(
663
+ ` \u2705 Migration ${migrationFile} rolled back successfully`
664
+ );
665
+ } catch (error) {
666
+ console.error(
667
+ ` \u274C Migration ${migrationFile} rollback failed:`,
668
+ error.message
669
+ );
670
+ throw error;
671
+ }
672
+ }
673
+ console.log(`
674
+ \u{1F389} Rollback completed successfully!`);
675
+ } catch (error) {
676
+ console.error("\u274C Rollback failed:", error.message);
677
+ throw error;
678
+ }
679
+ }
680
+ async currentVersion() {
681
+ try {
682
+ const tableName = this.getFullTableName();
683
+ const migrationTableExists = await this.knex.schema.hasTable(
684
+ tableName
685
+ );
686
+ if (!migrationTableExists) {
687
+ return null;
688
+ }
689
+ const result = await this.knex(tableName).select("NAME").orderBy("ID", "desc").first();
690
+ return result?.NAME || null;
691
+ } catch (error) {
692
+ console.error("\u274C Error getting current version:", error.message);
693
+ return null;
694
+ }
695
+ }
696
+ async listExecuted() {
697
+ try {
698
+ const tableName = this.getFullTableName();
699
+ const migrationTableExists = await this.knex.schema.hasTable(
700
+ tableName
701
+ );
702
+ if (!migrationTableExists) {
703
+ return [];
704
+ }
705
+ const completed = await this.knex(tableName).select("NAME").orderBy("ID");
706
+ return completed.map((c) => c.NAME);
707
+ } catch (error) {
708
+ console.error("\u274C Error listing executed migrations:", error.message);
709
+ return [];
710
+ }
711
+ }
712
+ async listPending() {
713
+ try {
714
+ const allFiles = this.getMigrationFiles();
715
+ const executed = await this.listExecuted();
716
+ return allFiles.filter((file) => !executed.includes(file));
717
+ } catch (error) {
718
+ console.error("\u274C Error listing pending migrations:", error.message);
719
+ return [];
720
+ }
721
+ }
722
+ getMigrationFiles() {
723
+ const { directory, extension } = this.config;
724
+ if (!fs.existsSync(directory)) {
725
+ throw new Error(`Migration directory does not exist: ${directory}`);
726
+ }
727
+ const validExtensions = ["js", "ts", "mjs", "cjs"];
728
+ const extensionToCheck = extension || "js";
729
+ return fs.readdirSync(directory).filter((file) => {
730
+ if (extension && extension !== "js") {
731
+ return file.endsWith(`.${extension}`);
732
+ }
733
+ return validExtensions.some((ext) => file.endsWith(`.${ext}`));
734
+ }).sort();
735
+ }
736
+ getMigrationPath(filename) {
737
+ return path.resolve(this.config.directory, filename);
738
+ }
739
+ };
740
+ function createIBMiMigrationRunner(knex2, config) {
741
+ return new IBMiMigrationRunner(knex2, config);
742
+ }
743
+
744
+ // src/index.ts
745
+ var DB2Client = class extends knex.Client {
746
+ constructor(config) {
747
+ super(config);
748
+ this.driverName = "odbc";
749
+ if (this.dialect && !this.config.client) {
750
+ this.printWarn(
751
+ `Using 'this.dialect' to identify the client is deprecated and support for it will be removed in the future. Please use configuration option 'client' instead.`
752
+ );
753
+ }
754
+ const dbClient = this.config.client || this.dialect;
755
+ if (!dbClient) {
756
+ throw new Error(
757
+ `knex: Required configuration option 'client' is missing.`
758
+ );
759
+ }
760
+ if (config.version) {
761
+ this.version = config.version;
762
+ }
763
+ if (this.driverName && config.connection) {
764
+ this.initializeDriver();
765
+ if (!config.pool || config.pool && config.pool.max !== 0) {
766
+ this.initializePool(config);
767
+ }
768
+ }
769
+ this.valueForUndefined = this.raw("DEFAULT");
770
+ if (config.useNullAsDefault) {
771
+ this.valueForUndefined = null;
772
+ }
773
+ }
774
+ // Helper method to safely stringify objects that might have circular references
775
+ safeStringify(obj, indent = 0) {
776
+ try {
777
+ return JSON.stringify(obj, null, indent);
778
+ } catch (error) {
779
+ if (error instanceof Error && error.message.includes("circular")) {
780
+ return `[Circular structure - ${typeof obj}]`;
781
+ }
782
+ return `[Stringify error - ${typeof obj}]`;
783
+ }
784
+ }
785
+ _driver() {
786
+ return odbc;
787
+ }
788
+ wrapIdentifierImpl(value) {
789
+ if (!value) return value;
790
+ if (value.includes("KNEX_MIGRATIONS") || value.includes("knex_migrations")) {
791
+ return value.toUpperCase();
792
+ }
793
+ return value;
794
+ }
795
+ printDebug(message) {
796
+ if (process.env.DEBUG === "true") {
797
+ if (this.logger.debug) {
798
+ this.logger.debug("knex-ibmi: " + message);
799
+ }
800
+ }
801
+ }
802
+ printError(message) {
803
+ if (this.logger.error) {
804
+ this.logger.error("knex-ibmi: " + message);
805
+ }
806
+ }
807
+ printWarn(message) {
808
+ if (process.env.DEBUG === "true") {
809
+ if (this.logger.warn) {
810
+ this.logger.warn("knex-ibmi: " + message);
811
+ }
812
+ }
813
+ }
814
+ // Get a raw connection, called by the pool manager whenever a new
815
+ // connection needs to be added to the pool.
816
+ async acquireRawConnection() {
817
+ this.printDebug("acquiring raw connection");
818
+ const connectionConfig = this.config.connection;
819
+ if (!connectionConfig) {
820
+ return this.printError("There is no connection config defined");
821
+ }
822
+ this.printDebug(
823
+ "connection config: " + this._getConnectionString(connectionConfig)
824
+ );
825
+ let connection;
826
+ if (this.config?.pool) {
827
+ const poolConfig = {
828
+ connectionString: this._getConnectionString(connectionConfig),
829
+ connectionTimeout: this.config?.acquireConnectionTimeout || 6e4,
830
+ initialSize: this.config?.pool?.min || 2,
831
+ maxSize: this.config?.pool?.max || 10,
832
+ reuseConnection: true
833
+ };
834
+ const pool = await this.driver.pool(poolConfig);
835
+ connection = await pool.connect();
836
+ } else {
837
+ connection = await this.driver.connect(
838
+ this._getConnectionString(connectionConfig)
839
+ );
840
+ }
841
+ return connection;
842
+ }
843
+ // Used to explicitly close a connection, called internally by the pool manager
844
+ // when a connection times out or the pool is shutdown.
845
+ async destroyRawConnection(connection) {
846
+ this.printDebug("destroy connection");
847
+ return await connection.close();
848
+ }
849
+ _getConnectionString(connectionConfig) {
850
+ const connectionStringParams = connectionConfig.connectionStringParams || {};
851
+ const connectionStringExtension = Object.keys(
852
+ connectionStringParams
853
+ ).reduce((result, key) => {
854
+ const value = connectionStringParams[key];
855
+ return `${result}${key}=${value};`;
856
+ }, "");
857
+ return `DRIVER=${connectionConfig.driver};SYSTEM=${connectionConfig.host};HOSTNAME=${connectionConfig.host};PORT=${connectionConfig.port};DATABASE=${connectionConfig.database};UID=${connectionConfig.user};PWD=${connectionConfig.password};` + connectionStringExtension;
858
+ }
859
+ // Runs the query on the specified connection, providing the bindings
860
+ async _query(connection, obj) {
861
+ const queryObject = this.normalizeQueryObject(obj);
862
+ const method = this.determineQueryMethod(queryObject);
863
+ queryObject.sqlMethod = method;
864
+ if (queryObject._ibmiUpdateReturning) {
865
+ return await this.executeUpdateReturning(connection, queryObject);
866
+ }
867
+ if (queryObject._ibmiSequentialInsert) {
868
+ return await this.executeSequentialInsert(connection, queryObject);
869
+ }
870
+ if (queryObject._ibmiDeleteReturning) {
871
+ return await this.executeDeleteReturning(connection, queryObject);
872
+ }
873
+ if (process.env.DEBUG === "true" && queryObject.sql && (queryObject.sql.toLowerCase().includes("create table") || queryObject.sql.toLowerCase().includes("knex_migrations"))) {
874
+ this.printDebug(
875
+ `Executing ${method} query: ${queryObject.sql.substring(0, 200)}...`
876
+ );
877
+ if (queryObject.bindings?.length) {
878
+ this.printDebug(`Bindings: ${JSON.stringify(queryObject.bindings)}`);
879
+ }
880
+ }
881
+ try {
882
+ const startTime = Date.now();
883
+ if (this.isSelectMethod(method)) {
884
+ await this.executeSelectQuery(connection, queryObject);
885
+ } else {
886
+ await this.executeStatementQuery(connection, queryObject);
887
+ }
888
+ const endTime = Date.now();
889
+ if (process.env.DEBUG === "true" && queryObject.sql && (queryObject.sql.toLowerCase().includes("create table") || queryObject.sql.toLowerCase().includes("knex_migrations"))) {
890
+ this.printDebug(`${method} completed in ${endTime - startTime}ms`);
891
+ }
892
+ this.printDebug(`Query completed: ${method} (${endTime - startTime}ms)`);
893
+ return queryObject;
894
+ } catch (error) {
895
+ const wrappedError = this.wrapError(error, method, queryObject);
896
+ if (this.isConnectionError(error)) {
897
+ this.printError(
898
+ `Connection error during ${method} query: ${error.message}`
899
+ );
900
+ if (this.shouldRetryQuery(queryObject, method)) {
901
+ return await this.retryQuery(connection, queryObject, method);
902
+ }
903
+ throw wrappedError;
904
+ }
905
+ throw wrappedError;
906
+ }
907
+ }
908
+ /**
909
+ * Execute UPDATE with returning clause using transaction + SELECT approach
910
+ * Since IBM i DB2 doesn't support FINAL TABLE with UPDATE, we:
911
+ * 1. Execute the UPDATE statement
912
+ * 2. Execute a SELECT to get the updated values using the same WHERE clause
913
+ */
914
+ async executeUpdateReturning(connection, obj) {
915
+ const { _ibmiUpdateReturning } = obj;
916
+ const { updateSql, selectColumns, whereClause, tableName } = _ibmiUpdateReturning;
917
+ this.printDebug(
918
+ "Executing UPDATE with returning using transaction approach"
919
+ );
920
+ try {
921
+ const updateObj = {
922
+ sql: updateSql,
923
+ bindings: obj.bindings,
924
+ sqlMethod: "update"
925
+ };
926
+ await this.executeStatementQuery(connection, updateObj);
927
+ const selectSql = whereClause ? `select ${selectColumns} from ${tableName} ${whereClause}` : `select ${selectColumns} from ${tableName}`;
928
+ const updateSqlParts = updateSql.split(" where ");
929
+ const setClausePart = updateSqlParts[0];
930
+ const setBindingCount = (setClausePart.match(/\?/g) || []).length;
931
+ const whereBindings = obj.bindings ? obj.bindings.slice(setBindingCount) : [];
932
+ const selectObj = {
933
+ sql: selectSql,
934
+ bindings: whereBindings,
935
+ sqlMethod: "select",
936
+ response: void 0
937
+ };
938
+ await this.executeSelectQuery(connection, selectObj);
939
+ obj.response = selectObj.response;
940
+ obj.sqlMethod = "update";
941
+ obj.select = true;
942
+ return obj;
943
+ } catch (error) {
944
+ this.printError(`UPDATE with returning failed: ${error.message}`);
945
+ throw this.wrapError(error, "update_returning", obj);
946
+ }
947
+ }
948
+ async executeSequentialInsert(connection, obj) {
949
+ const meta = obj._ibmiSequentialInsert;
950
+ const { rows, columns, tableName, returning, identityOnly } = meta;
951
+ this.printDebug("Executing sequential multi-row insert");
952
+ const insertedRows = [];
953
+ const transactional = this.config?.ibmi?.sequentialInsertTransactional === true;
954
+ let beganTx = false;
955
+ if (transactional) {
956
+ try {
957
+ await connection.query("BEGIN");
958
+ beganTx = true;
959
+ } catch (e) {
960
+ this.printWarn(
961
+ "Could not begin transaction for sequential insert; proceeding without"
962
+ );
963
+ }
964
+ }
965
+ for (let i = 0; i < rows.length; i++) {
966
+ const row = rows[i];
967
+ const colList = columns.join(", ");
968
+ const placeholders = columns.map(() => "?").join(", ");
969
+ const singleValues = columns.map((c) => row[c]);
970
+ const baseInsert = `insert into ${tableName} (${colList}) values (${placeholders})`;
971
+ const selectCols = returning ? this.queryCompiler({}).formatter.columnize(returning) : "IDENTITY_VAL_LOCAL()";
972
+ const wrapped = `select ${selectCols} from FINAL TABLE(${baseInsert})`;
973
+ const singleObj = {
974
+ sql: wrapped,
975
+ bindings: singleValues,
976
+ sqlMethod: "insert",
977
+ response: void 0
978
+ };
979
+ await this.executeStatementQuery(connection, singleObj);
980
+ const resp = singleObj.response?.rows;
981
+ if (resp) insertedRows.push(...resp);
982
+ }
983
+ if (transactional && beganTx) {
984
+ try {
985
+ await connection.query("COMMIT");
986
+ } catch (commitErr) {
987
+ this.printError(
988
+ "Commit failed for sequential insert, attempting rollback: " + commitErr?.message
989
+ );
990
+ try {
991
+ await connection.query("ROLLBACK");
992
+ } catch {
993
+ }
994
+ throw commitErr;
995
+ }
996
+ }
997
+ obj.response = { rows: insertedRows, rowCount: insertedRows.length };
998
+ obj.sqlMethod = "insert";
999
+ obj.select = true;
1000
+ return obj;
1001
+ }
1002
+ async executeDeleteReturning(connection, obj) {
1003
+ const meta = obj._ibmiDeleteReturning;
1004
+ const { deleteSql, selectColumns, whereClause, tableName } = meta;
1005
+ this.printDebug("Executing DELETE with returning emulation");
1006
+ try {
1007
+ const selectSql = whereClause ? `select ${selectColumns} from ${tableName} ${whereClause}` : `select ${selectColumns} from ${tableName}`;
1008
+ const selectObj = {
1009
+ sql: selectSql,
1010
+ bindings: obj.bindings,
1011
+ sqlMethod: "select",
1012
+ response: void 0
1013
+ };
1014
+ await this.executeSelectQuery(connection, selectObj);
1015
+ const rowsToReturn = selectObj.response?.rows || [];
1016
+ const deleteObj = {
1017
+ sql: deleteSql,
1018
+ bindings: obj.bindings,
1019
+ sqlMethod: "del",
1020
+ response: void 0
1021
+ };
1022
+ await this.executeStatementQuery(connection, deleteObj);
1023
+ obj.response = { rows: rowsToReturn, rowCount: rowsToReturn.length };
1024
+ obj.sqlMethod = "del";
1025
+ obj.select = true;
1026
+ return obj;
1027
+ } catch (error) {
1028
+ this.printError(`DELETE with returning failed: ${error.message}`);
1029
+ throw this.wrapError(error, "delete_returning", obj);
1030
+ }
1031
+ }
1032
+ normalizeQueryObject(obj) {
1033
+ if (!obj || typeof obj === "string") {
1034
+ return { sql: obj };
1035
+ }
1036
+ return obj;
1037
+ }
1038
+ determineQueryMethod(obj) {
1039
+ return (obj.hasOwnProperty("method") && obj.method !== "raw" ? obj.method : obj.sql.split(" ")[0]).toLowerCase();
1040
+ }
1041
+ isSelectMethod(method) {
1042
+ return method === "select" || method === "first" || method === "pluck";
1043
+ }
1044
+ async executeSelectQuery(connection, obj) {
1045
+ const rows = await connection.query(
1046
+ obj.sql,
1047
+ obj.bindings
1048
+ );
1049
+ if (rows) {
1050
+ obj.response = { rows, rowCount: rows.length };
1051
+ }
1052
+ }
1053
+ async executeStatementQuery(connection, obj) {
1054
+ let statement;
1055
+ try {
1056
+ statement = await connection.createStatement();
1057
+ await statement.prepare(obj.sql);
1058
+ if (obj.bindings) {
1059
+ await statement.bind(obj.bindings);
1060
+ }
1061
+ const result = await statement.execute();
1062
+ this.printDebug(String(result));
1063
+ obj.response = this.formatStatementResponse(result);
1064
+ } catch (err) {
1065
+ const sql = (obj.sql || "").toLowerCase();
1066
+ const isDml = obj.sqlMethod === "update" /* UPDATE */ || sql.includes(" update ") || sql.startsWith("update") || obj.sqlMethod === "del" /* DELETE */ || sql.includes(" delete ") || sql.startsWith("delete");
1067
+ const odbcErrors = err?.odbcErrors;
1068
+ const isEmptyOdbcError = Array.isArray(odbcErrors) && odbcErrors.length === 0;
1069
+ const hasNoDataState = Array.isArray(odbcErrors) ? odbcErrors.some(
1070
+ (e) => String(e?.state || e?.SQLSTATE || "").toUpperCase() === "02000"
1071
+ ) : false;
1072
+ if (isDml && (isEmptyOdbcError || hasNoDataState || this.isNoDataError(err))) {
1073
+ this.printWarn(
1074
+ `ODBC signaled no-data for ${sql.includes("update") ? "UPDATE" : "DELETE"}; treating as 0 rows affected`
1075
+ );
1076
+ obj.response = { rows: [], rowCount: 0 };
1077
+ return;
1078
+ }
1079
+ this.printError(this.safeStringify(err));
1080
+ throw err;
1081
+ } finally {
1082
+ if (statement && typeof statement.close === "function") {
1083
+ try {
1084
+ await statement.close();
1085
+ } catch (closeErr) {
1086
+ this.printDebug(
1087
+ `Error closing statement: ${this.safeStringify(closeErr, 2)}`
1088
+ );
1089
+ }
1090
+ }
1091
+ }
1092
+ }
1093
+ isNoDataError(error) {
1094
+ if (!error) return false;
1095
+ const msg = String(error?.message || error || "").toLowerCase();
1096
+ return msg.includes("02000") || msg.includes("no data") || msg.includes("no rows") || msg.includes("0 rows");
1097
+ }
1098
+ /**
1099
+ * Format statement response from ODBC driver
1100
+ * Handles special case for IDENTITY_VAL_LOCAL() function
1101
+ */
1102
+ formatStatementResponse(result) {
1103
+ const isIdentityQuery = result.statement?.includes("IDENTITY_VAL_LOCAL()");
1104
+ if (isIdentityQuery && result.columns?.length > 0) {
1105
+ return {
1106
+ rows: result.map(
1107
+ (row) => row[result.columns[0].name]
1108
+ ),
1109
+ rowCount: result.count
1110
+ };
1111
+ }
1112
+ const rowCount = typeof result?.count === "number" ? result.count : 0;
1113
+ return {
1114
+ rows: result,
1115
+ rowCount
1116
+ };
1117
+ }
1118
+ async _stream(connection, obj, stream, options) {
1119
+ if (!obj.sql) throw new Error("A query is required to stream results");
1120
+ const optimizedFetchSize = options?.fetchSize || this.calculateOptimalFetchSize(obj.sql);
1121
+ return new Promise((resolve, reject) => {
1122
+ let isResolved = false;
1123
+ const cleanup = () => {
1124
+ if (!isResolved) {
1125
+ isResolved = true;
1126
+ }
1127
+ };
1128
+ stream.on("error", (err) => {
1129
+ cleanup();
1130
+ reject(err);
1131
+ });
1132
+ stream.on("end", () => {
1133
+ cleanup();
1134
+ resolve(void 0);
1135
+ });
1136
+ connection.query(
1137
+ obj.sql,
1138
+ obj.bindings,
1139
+ {
1140
+ cursor: true,
1141
+ fetchSize: optimizedFetchSize
1142
+ },
1143
+ (error, cursor) => {
1144
+ if (error) {
1145
+ this.printError(this.safeStringify(error, 2));
1146
+ cleanup();
1147
+ reject(error);
1148
+ return;
1149
+ }
1150
+ const readableStream = this._createCursorStream(cursor);
1151
+ readableStream.on("error", (err) => {
1152
+ cleanup();
1153
+ reject(err);
1154
+ });
1155
+ readableStream.pipe(stream);
1156
+ }
1157
+ );
1158
+ });
1159
+ }
1160
+ calculateOptimalFetchSize(sql) {
1161
+ const sqlLower = sql.toLowerCase();
1162
+ const hasJoins = /\s+join\s+/i.test(sql);
1163
+ const hasAggregates = /\s+(count|sum|avg|max|min)\s*\(/i.test(sql);
1164
+ const hasOrderBy = /\s+order\s+by\s+/i.test(sql);
1165
+ const hasGroupBy = /\s+group\s+by\s+/i.test(sql);
1166
+ if (hasJoins || hasAggregates || hasOrderBy || hasGroupBy) {
1167
+ return 500;
1168
+ }
1169
+ return 100;
1170
+ }
1171
+ _createCursorStream(cursor) {
1172
+ const parentThis = this;
1173
+ let isClosed = false;
1174
+ return new Readable({
1175
+ objectMode: true,
1176
+ read() {
1177
+ if (isClosed) return;
1178
+ cursor.fetch((error, result) => {
1179
+ if (error) {
1180
+ parentThis.printError(parentThis.safeStringify(error, 2));
1181
+ isClosed = true;
1182
+ this.emit("error", error);
1183
+ return;
1184
+ }
1185
+ if (!cursor.noData) {
1186
+ this.push(result);
1187
+ } else {
1188
+ isClosed = true;
1189
+ cursor.close((closeError) => {
1190
+ if (closeError) {
1191
+ parentThis.printError(JSON.stringify(closeError, null, 2));
1192
+ }
1193
+ if (result) {
1194
+ this.push(result);
1195
+ }
1196
+ this.push(null);
1197
+ });
1198
+ }
1199
+ });
1200
+ },
1201
+ destroy(err, callback) {
1202
+ if (!isClosed) {
1203
+ isClosed = true;
1204
+ cursor.close((closeError) => {
1205
+ if (closeError) {
1206
+ parentThis.printDebug(
1207
+ "Error closing cursor during destroy: " + parentThis.safeStringify(closeError)
1208
+ );
1209
+ }
1210
+ callback(err);
1211
+ });
1212
+ } else {
1213
+ callback(err);
1214
+ }
1215
+ }
1216
+ });
1217
+ }
1218
+ transaction(container, config, outerTx) {
1219
+ return new ibmi_transaction_default(this, container, config, outerTx);
1220
+ }
1221
+ schemaCompiler(tableBuilder) {
1222
+ return new ibmi_compiler_default(this, tableBuilder);
1223
+ }
1224
+ tableCompiler(tableBuilder) {
1225
+ return new ibmi_tablecompiler_default(this, tableBuilder);
1226
+ }
1227
+ columnCompiler(tableCompiler, columnCompiler) {
1228
+ return new ibmi_columncompiler_default(this, tableCompiler, columnCompiler);
1229
+ }
1230
+ queryCompiler(builder, bindings) {
1231
+ return new ibmi_querycompiler_default(this, builder, bindings);
1232
+ }
1233
+ // Create IBM i-specific migration runner that bypasses Knex's problematic locking system
1234
+ createMigrationRunner(config) {
1235
+ const knexInstance = this.context || this;
1236
+ return createIBMiMigrationRunner(knexInstance, config);
1237
+ }
1238
+ processResponse(obj, runner) {
1239
+ if (obj === null) return null;
1240
+ const { response } = obj;
1241
+ if (obj.output) {
1242
+ try {
1243
+ const result = obj.output(runner, response);
1244
+ return result;
1245
+ } catch (error) {
1246
+ const wrappedError = this.wrapError(error, "custom_output", obj);
1247
+ this.printError(
1248
+ `Custom output function failed: ${wrappedError.message}`
1249
+ );
1250
+ if (this.isConnectionError(error)) {
1251
+ throw new Error(
1252
+ "Connection closed during query processing - consider using migrations.disableTransactions: true for DDL operations"
1253
+ );
1254
+ }
1255
+ throw wrappedError;
1256
+ }
1257
+ }
1258
+ const validationResult = this.validateResponse(obj);
1259
+ if (validationResult !== null) return validationResult;
1260
+ return this.processSqlMethod(obj);
1261
+ }
1262
+ validateResponse(obj) {
1263
+ if (!obj.response) {
1264
+ this.printDebug("response undefined" + JSON.stringify(obj));
1265
+ return null;
1266
+ }
1267
+ if (!obj.response.rows) {
1268
+ this.printError("rows undefined" + JSON.stringify(obj));
1269
+ return null;
1270
+ }
1271
+ return null;
1272
+ }
1273
+ wrapError(error, method, queryObject) {
1274
+ const context = {
1275
+ method,
1276
+ sql: queryObject.sql ? queryObject.sql.substring(0, 100) + "..." : "unknown",
1277
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1278
+ };
1279
+ if (this.isConnectionError(error)) {
1280
+ return new Error(
1281
+ `IBM i DB2 connection error during ${method}: ${error.message} | Context: ${JSON.stringify(context)}`
1282
+ );
1283
+ }
1284
+ if (this.isTimeoutError(error)) {
1285
+ return new Error(
1286
+ `IBM i DB2 timeout during ${method}: ${error.message} | Context: ${JSON.stringify(context)}`
1287
+ );
1288
+ }
1289
+ if (this.isSQLError(error)) {
1290
+ return new Error(
1291
+ `IBM i DB2 SQL error during ${method}: ${error.message} | Context: ${JSON.stringify(context)}`
1292
+ );
1293
+ }
1294
+ return new Error(
1295
+ `IBM i DB2 error during ${method}: ${error.message} | Context: ${JSON.stringify(context)}`
1296
+ );
1297
+ }
1298
+ shouldRetryQuery(queryObject, method) {
1299
+ return queryObject.sql?.toLowerCase().includes("systables") || queryObject.sql?.toLowerCase().includes("knex_migrations");
1300
+ }
1301
+ async retryQuery(connection, queryObject, method) {
1302
+ this.printDebug(`Retrying ${method} query due to connection error...`);
1303
+ try {
1304
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
1305
+ if (this.isSelectMethod(method)) {
1306
+ await this.executeSelectQuery(connection, queryObject);
1307
+ } else {
1308
+ await this.executeStatementQuery(connection, queryObject);
1309
+ }
1310
+ return queryObject;
1311
+ } catch (retryError) {
1312
+ this.printError(`Retry failed: ${retryError.message}`);
1313
+ queryObject.response = { rows: [], rowCount: 0 };
1314
+ return queryObject;
1315
+ }
1316
+ }
1317
+ isConnectionError(error) {
1318
+ const errorMessage = (error.message || error.toString || error).toLowerCase();
1319
+ return errorMessage.includes("connection") && (errorMessage.includes("closed") || errorMessage.includes("invalid") || errorMessage.includes("terminated") || errorMessage.includes("not connected"));
1320
+ }
1321
+ isTimeoutError(error) {
1322
+ const errorMessage = (error.message || error.toString || error).toLowerCase();
1323
+ return errorMessage.includes("timeout") || errorMessage.includes("timed out");
1324
+ }
1325
+ isSQLError(error) {
1326
+ const errorMessage = (error.message || error.toString || error).toLowerCase();
1327
+ return errorMessage.includes("sql") || errorMessage.includes("syntax") || errorMessage.includes("table") || errorMessage.includes("column");
1328
+ }
1329
+ processSqlMethod(obj) {
1330
+ const { rows, rowCount } = obj.response;
1331
+ switch (obj.sqlMethod) {
1332
+ case "select" /* SELECT */:
1333
+ return rows;
1334
+ case "pluck" /* PLUCK */:
1335
+ return rows.map(obj.pluck);
1336
+ case "first" /* FIRST */:
1337
+ return rows[0];
1338
+ case "insert" /* INSERT */:
1339
+ return rows;
1340
+ case "del" /* DELETE */:
1341
+ case "delete" /* DELETE_ALT */:
1342
+ case "update" /* UPDATE */:
1343
+ return obj.select ? rows : rowCount ?? 0;
1344
+ case "counter" /* COUNTER */:
1345
+ return rowCount;
1346
+ default:
1347
+ return rows;
1348
+ }
1349
+ }
1350
+ };
1351
+ var DB2Dialect = DB2Client;
1352
+ var index_default = DB2Client;
1353
+ export {
1354
+ DB2Dialect,
1355
+ IBMiMigrationRunner,
1356
+ createIBMiMigrationRunner,
1357
+ index_default as default
1358
+ };
1359
+ //# sourceMappingURL=index.mjs.map