@bdkinc/knex-ibmi 0.4.0 → 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 CHANGED
@@ -152,6 +152,68 @@ var IBMiColumnCompiler = class extends ColumnCompiler {
152
152
  increments(options = { primaryKey: true }) {
153
153
  return "int not null generated always as identity (start with 1, increment by 1)" + (this.tableCompiler._canBeAddPrimaryKey(options) ? " primary key" : "");
154
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
+ }
155
217
  };
156
218
  var ibmi_columncompiler_default = IBMiColumnCompiler;
157
219
 
@@ -210,6 +272,16 @@ var ibmi_transaction_default = IBMiTransaction;
210
272
  import QueryCompiler from "knex/lib/query/querycompiler.js";
211
273
  import { rawOrFn as rawOrFn_ } from "knex/lib/formatter/wrappingFormatter.js";
212
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
+ }
213
285
  formatTimestampLocal(date) {
214
286
  const pad = (n) => String(n).padStart(2, "0");
215
287
  const y = date.getFullYear();
@@ -229,15 +301,42 @@ var IBMiQueryCompiler = class extends QueryCompiler {
229
301
  }
230
302
  return "";
231
303
  }
232
- const selectColumns = returning ? this.formatter.columnize(returning) : "IDENTITY_VAL_LOCAL()";
233
- const returningSql = returning ? this._returning("insert", returning, void 0) + " " : "";
234
- const insertSql = [
235
- this.with(),
236
- `insert into ${this.tableName}`,
237
- this._buildInsertData(insertValues, returningSql)
238
- ].filter(Boolean).join(" ");
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()";
239
323
  const sql = `select ${selectColumns} from FINAL TABLE(${insertSql})`;
240
- return { sql, returning };
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 };
241
340
  }
242
341
  isEmptyInsertValues(insertValues) {
243
342
  return Array.isArray(insertValues) && insertValues.length === 0 || this.isEmptyObject(insertValues);
@@ -259,15 +358,47 @@ var IBMiQueryCompiler = class extends QueryCompiler {
259
358
  _buildInsertData(insertValues, returningSql) {
260
359
  const insertData = this._prepInsert(insertValues);
261
360
  if (insertData.columns.length > 0) {
262
- const columnsSql = `(${this.formatter.columnize(insertData.columns)})`;
263
- const valuesSql = `(${this._buildInsertValues(insertData)})`;
264
- return `${columnsSql} ${returningSql}values ${valuesSql}`;
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("");
265
372
  }
266
373
  if (Array.isArray(insertValues) && insertValues.length === 1 && insertValues[0]) {
267
- return returningSql + this._emptyInsertValue;
374
+ return (returningSql || "") + this._emptyInsertValue;
268
375
  }
269
376
  return "";
270
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
+ }
271
402
  _prepInsert(data) {
272
403
  if (typeof data === "object" && data?.migration_time) {
273
404
  const parsed = new Date(data.migration_time);
@@ -289,25 +420,25 @@ var IBMiQueryCompiler = class extends QueryCompiler {
289
420
  if (dataArray.length === 0) {
290
421
  return { columns: [], values: [] };
291
422
  }
292
- const allColumns = /* @__PURE__ */ new Set();
293
- for (const item of dataArray) {
294
- if (item != null) {
295
- Object.keys(item).forEach((key) => allColumns.add(key));
296
- }
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);
297
435
  }
298
- const columns = Array.from(allColumns).sort();
299
436
  const values = [];
300
437
  for (const item of dataArray) {
301
- if (item == null) {
302
- break;
303
- }
304
- const row = columns.map((column) => item[column] ?? void 0);
305
- values.push(row);
438
+ if (!item || typeof item !== "object") continue;
439
+ values.push(columns.map((c) => item[c] ?? void 0));
306
440
  }
307
- return {
308
- columns,
309
- values
310
- };
441
+ return { columns, values };
311
442
  }
312
443
  update() {
313
444
  const withSQL = this.with();
@@ -316,6 +447,7 @@ var IBMiQueryCompiler = class extends QueryCompiler {
316
447
  const order = this.order();
317
448
  const limit = this.limit();
318
449
  const { returning } = this.single;
450
+ const optimizationHints = "";
319
451
  const baseUpdateSql = [
320
452
  withSQL,
321
453
  `update ${this.single.only ? "only " : ""}${this.tableName}`,
@@ -323,18 +455,46 @@ var IBMiQueryCompiler = class extends QueryCompiler {
323
455
  updates.join(", "),
324
456
  where,
325
457
  order,
326
- limit
458
+ limit,
459
+ optimizationHints
327
460
  ].filter(Boolean).join(" ");
328
461
  if (returning) {
329
- this.client.logger.warn?.(
330
- "IBMi DB2 does not support returning in update statements, only inserts"
331
- );
332
462
  const selectColumns = this.formatter.columnize(this.single.returning);
333
- const sql = `select ${selectColumns} from FINAL TABLE(${baseUpdateSql})`;
334
- return { sql, 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
+ };
335
474
  }
336
475
  return { sql: baseUpdateSql, returning };
337
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
+ }
338
498
  /**
339
499
  * Handle returning clause for IBMi DB2 queries
340
500
  * Note: IBMi DB2 has limited support for RETURNING clauses
@@ -355,15 +515,26 @@ var IBMiQueryCompiler = class extends QueryCompiler {
355
515
  return "";
356
516
  }
357
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
+ }
358
530
  columnizeWithPrefix(prefix, target) {
359
531
  const columns = typeof target === "string" ? [target] : target;
360
- let str = "";
361
- let i = -1;
362
- while (++i < columns.length) {
363
- if (i > 0) str += ", ";
364
- str += prefix + this.wrap(columns[i]);
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]));
365
536
  }
366
- return str;
537
+ return parts.join("");
367
538
  }
368
539
  };
369
540
  var ibmi_querycompiler_default = IBMiQueryCompiler;
@@ -573,7 +744,6 @@ function createIBMiMigrationRunner(knex2, config) {
573
744
  // src/index.ts
574
745
  var DB2Client = class extends knex.Client {
575
746
  constructor(config) {
576
- console.log("\u{1F3AF} DB2Client constructor called!");
577
747
  super(config);
578
748
  this.driverName = "odbc";
579
749
  if (this.dialect && !this.config.client) {
@@ -616,6 +786,7 @@ var DB2Client = class extends knex.Client {
616
786
  return odbc;
617
787
  }
618
788
  wrapIdentifierImpl(value) {
789
+ if (!value) return value;
619
790
  if (value.includes("KNEX_MIGRATIONS") || value.includes("knex_migrations")) {
620
791
  return value.toUpperCase();
621
792
  }
@@ -690,6 +861,15 @@ var DB2Client = class extends knex.Client {
690
861
  const queryObject = this.normalizeQueryObject(obj);
691
862
  const method = this.determineQueryMethod(queryObject);
692
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
+ }
693
873
  if (process.env.DEBUG === "true" && queryObject.sql && (queryObject.sql.toLowerCase().includes("create table") || queryObject.sql.toLowerCase().includes("knex_migrations"))) {
694
874
  this.printDebug(
695
875
  `Executing ${method} query: ${queryObject.sql.substring(0, 200)}...`
@@ -712,31 +892,141 @@ var DB2Client = class extends knex.Client {
712
892
  this.printDebug(`Query completed: ${method} (${endTime - startTime}ms)`);
713
893
  return queryObject;
714
894
  } catch (error) {
895
+ const wrappedError = this.wrapError(error, method, queryObject);
715
896
  if (this.isConnectionError(error)) {
716
897
  this.printError(
717
898
  `Connection error during ${method} query: ${error.message}`
718
899
  );
719
- if (queryObject.sql?.toLowerCase().includes("systables")) {
720
- this.printDebug("Retrying hasTable query due to connection error...");
721
- try {
722
- await new Promise((resolve) => setTimeout(resolve, 1e3));
723
- if (this.isSelectMethod(method)) {
724
- await this.executeSelectQuery(connection, queryObject);
725
- } else {
726
- await this.executeStatementQuery(connection, queryObject);
727
- }
728
- return queryObject;
729
- } catch (retryError) {
730
- this.printError(`Retry failed: ${retryError.message}`);
731
- queryObject.response = { rows: [], rowCount: 0 };
732
- return queryObject;
733
- }
900
+ if (this.shouldRetryQuery(queryObject, method)) {
901
+ return await this.retryQuery(connection, queryObject, method);
734
902
  }
735
- throw new Error(
736
- `Connection closed during ${method} operation - IBM i DB2 DDL operations cause implicit commits`
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"
737
962
  );
738
963
  }
739
- throw error;
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);
740
1030
  }
741
1031
  }
742
1032
  normalizeQueryObject(obj) {
@@ -827,45 +1117,75 @@ var DB2Client = class extends knex.Client {
827
1117
  }
828
1118
  async _stream(connection, obj, stream, options) {
829
1119
  if (!obj.sql) throw new Error("A query is required to stream results");
1120
+ const optimizedFetchSize = options?.fetchSize || this.calculateOptimalFetchSize(obj.sql);
830
1121
  return new Promise((resolve, reject) => {
831
- stream.on("error", reject);
832
- stream.on("end", resolve);
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
+ });
833
1136
  connection.query(
834
1137
  obj.sql,
835
1138
  obj.bindings,
836
1139
  {
837
1140
  cursor: true,
838
- fetchSize: options?.fetchSize || 1
1141
+ fetchSize: optimizedFetchSize
839
1142
  },
840
1143
  (error, cursor) => {
841
1144
  if (error) {
842
1145
  this.printError(this.safeStringify(error, 2));
843
- stream.emit("error", error);
1146
+ cleanup();
844
1147
  reject(error);
845
1148
  return;
846
1149
  }
847
1150
  const readableStream = this._createCursorStream(cursor);
848
1151
  readableStream.on("error", (err) => {
1152
+ cleanup();
849
1153
  reject(err);
850
- stream.emit("error", err);
851
1154
  });
852
1155
  readableStream.pipe(stream);
853
1156
  }
854
1157
  );
855
1158
  });
856
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
+ }
857
1171
  _createCursorStream(cursor) {
858
1172
  const parentThis = this;
1173
+ let isClosed = false;
859
1174
  return new Readable({
860
1175
  objectMode: true,
861
1176
  read() {
1177
+ if (isClosed) return;
862
1178
  cursor.fetch((error, result) => {
863
1179
  if (error) {
864
1180
  parentThis.printError(parentThis.safeStringify(error, 2));
1181
+ isClosed = true;
1182
+ this.emit("error", error);
1183
+ return;
865
1184
  }
866
1185
  if (!cursor.noData) {
867
1186
  this.push(result);
868
1187
  } else {
1188
+ isClosed = true;
869
1189
  cursor.close((closeError) => {
870
1190
  if (closeError) {
871
1191
  parentThis.printError(JSON.stringify(closeError, null, 2));
@@ -877,6 +1197,21 @@ var DB2Client = class extends knex.Client {
877
1197
  });
878
1198
  }
879
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
+ }
880
1215
  }
881
1216
  });
882
1217
  }
@@ -908,15 +1243,16 @@ var DB2Client = class extends knex.Client {
908
1243
  const result = obj.output(runner, response);
909
1244
  return result;
910
1245
  } catch (error) {
1246
+ const wrappedError = this.wrapError(error, "custom_output", obj);
911
1247
  this.printError(
912
- `Custom output function failed: ${error.message || error}`
1248
+ `Custom output function failed: ${wrappedError.message}`
913
1249
  );
914
1250
  if (this.isConnectionError(error)) {
915
1251
  throw new Error(
916
1252
  "Connection closed during query processing - consider using migrations.disableTransactions: true for DDL operations"
917
1253
  );
918
1254
  }
919
- throw error;
1255
+ throw wrappedError;
920
1256
  }
921
1257
  }
922
1258
  const validationResult = this.validateResponse(obj);
@@ -934,10 +1270,62 @@ var DB2Client = class extends knex.Client {
934
1270
  }
935
1271
  return null;
936
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
+ }
937
1317
  isConnectionError(error) {
938
1318
  const errorMessage = (error.message || error.toString || error).toLowerCase();
939
1319
  return errorMessage.includes("connection") && (errorMessage.includes("closed") || errorMessage.includes("invalid") || errorMessage.includes("terminated") || errorMessage.includes("not connected"));
940
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
+ }
941
1329
  processSqlMethod(obj) {
942
1330
  const { rows, rowCount } = obj.response;
943
1331
  switch (obj.sqlMethod) {