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