@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/README.md +73 -32
- package/dist/index.d.mts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +453 -65
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +453 -65
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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
|
|
263
|
-
|
|
264
|
-
|
|
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
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
302
|
-
|
|
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
|
|
334
|
-
return {
|
|
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
|
-
|
|
361
|
-
let i =
|
|
362
|
-
|
|
363
|
-
|
|
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
|
|
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 (
|
|
720
|
-
this.
|
|
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
|
|
736
|
-
|
|
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
|
-
|
|
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
|
-
|
|
832
|
-
|
|
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:
|
|
1141
|
+
fetchSize: optimizedFetchSize
|
|
839
1142
|
},
|
|
840
1143
|
(error, cursor) => {
|
|
841
1144
|
if (error) {
|
|
842
1145
|
this.printError(this.safeStringify(error, 2));
|
|
843
|
-
|
|
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: ${
|
|
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
|
|
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) {
|