@bdkinc/knex-ibmi 0.3.21 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,971 @@
1
+ import { createRequire } from 'module';
2
+ const require = createRequire(import.meta.url);
3
+ var __defProp = Object.defineProperty;
4
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+
7
+ // src/index.ts
8
+ import process from "process";
9
+ import knex from "knex";
10
+ import odbc from "odbc";
11
+
12
+ // src/schema/ibmi-compiler.ts
13
+ import SchemaCompiler from "knex/lib/schema/compiler.js";
14
+ var IBMiSchemaCompiler = class extends SchemaCompiler {
15
+ hasTable(tableName) {
16
+ const upperName = String(tableName).toUpperCase();
17
+ let schemaName = null;
18
+ let actualTableName = upperName;
19
+ if (upperName.includes(".")) {
20
+ const parts = upperName.split(".");
21
+ schemaName = parts[0];
22
+ actualTableName = parts[1];
23
+ }
24
+ const builderSchema = this.builder._schema;
25
+ if (builderSchema) {
26
+ schemaName = builderSchema.toUpperCase();
27
+ }
28
+ let sql;
29
+ let bindings;
30
+ if (schemaName) {
31
+ sql = `select count(*) as table_count from QSYS2.SYSTABLES where UPPER(TABLE_NAME) = ? AND UPPER(TABLE_SCHEMA) = ?`;
32
+ bindings = [actualTableName, schemaName];
33
+ } else {
34
+ sql = `select count(*) as table_count from QSYS2.SYSTABLES where UPPER(TABLE_NAME) = ?`;
35
+ bindings = [actualTableName];
36
+ }
37
+ this.pushQuery({
38
+ sql,
39
+ bindings,
40
+ output: (runner, resp) => {
41
+ if (!resp) {
42
+ return false;
43
+ }
44
+ if (Array.isArray(resp) && resp.length > 0) {
45
+ const firstRow = resp[0];
46
+ if (firstRow && typeof firstRow === "object") {
47
+ const count = firstRow.table_count || firstRow.TABLE_COUNT || firstRow.count || firstRow.COUNT || 0;
48
+ return count > 0;
49
+ }
50
+ }
51
+ if (typeof resp === "object" && resp !== null) {
52
+ const keys = Object.keys(resp);
53
+ for (const key of keys) {
54
+ if (!isNaN(parseInt(key))) {
55
+ const row = resp[key];
56
+ if (row && typeof row === "object") {
57
+ const count = row.table_count || row.TABLE_COUNT || row.count || row.COUNT || 0;
58
+ return count > 0;
59
+ }
60
+ }
61
+ }
62
+ if (resp.rows && Array.isArray(resp.rows) && resp.rows.length > 0) {
63
+ const firstRow = resp.rows[0];
64
+ if (firstRow && typeof firstRow === "object") {
65
+ const count = firstRow.table_count || firstRow.TABLE_COUNT || firstRow.count || firstRow.COUNT || 0;
66
+ return count > 0;
67
+ }
68
+ }
69
+ }
70
+ return false;
71
+ }
72
+ });
73
+ }
74
+ toSQL() {
75
+ const sequence = this.builder._sequence;
76
+ for (let i = 0, l = sequence.length; i < l; i++) {
77
+ const query = sequence[i];
78
+ this[query.method].apply(this, query.args);
79
+ }
80
+ return this.sequence;
81
+ }
82
+ };
83
+ var ibmi_compiler_default = IBMiSchemaCompiler;
84
+
85
+ // src/schema/ibmi-tablecompiler.ts
86
+ import TableCompiler from "knex/lib/schema/tablecompiler.js";
87
+ var IBMiTableCompiler = class extends TableCompiler {
88
+ createQuery(columns, ifNot, like) {
89
+ let createStatement = ifNot ? `if object_id('${this.tableName()}', 'U') is null ` : "";
90
+ if (like) {
91
+ createStatement += `select * into ${this.tableName()} from ${this.tableNameLike()} WHERE 0=1`;
92
+ } else {
93
+ createStatement += "create table " + this.tableName() + (this._formatting ? " (\n " : " (") + columns.sql.join(this._formatting ? ",\n " : ", ") + this._addChecks() + ")";
94
+ }
95
+ this.pushQuery(createStatement);
96
+ if (this.single.comment) {
97
+ this.comment(this.single.comment);
98
+ }
99
+ if (like) {
100
+ this.addColumns(columns, this.addColumnsPrefix);
101
+ }
102
+ }
103
+ dropUnique(columns, indexName) {
104
+ indexName = indexName ? this.formatter.wrap(indexName) : this._indexCommand("unique", this.tableNameRaw, columns);
105
+ this.pushQuery(`drop index ${indexName}`);
106
+ }
107
+ unique(columns, indexName) {
108
+ let deferrable = "";
109
+ let predicate;
110
+ let finalIndexName;
111
+ if (typeof indexName === "object" && indexName !== null) {
112
+ deferrable = indexName.deferrable || "";
113
+ predicate = indexName.predicate;
114
+ finalIndexName = indexName.indexName;
115
+ } else {
116
+ finalIndexName = indexName;
117
+ }
118
+ if (deferrable && deferrable !== "not deferrable") {
119
+ this.client.logger.warn?.(
120
+ `IBMi: unique index \`${finalIndexName}\` will not be deferrable ${deferrable}.`
121
+ );
122
+ }
123
+ const wrappedIndexName = finalIndexName ? this.formatter.wrap(finalIndexName) : this._indexCommand("unique", this.tableNameRaw, columns);
124
+ columns = this.formatter.columnize(columns);
125
+ const predicateQuery = predicate ? " " + this.client.queryCompiler(predicate).where() : "";
126
+ this.pushQuery(
127
+ `create unique index ${wrappedIndexName} on ${this.tableName()} (${columns})${predicateQuery}`
128
+ );
129
+ }
130
+ // All of the columns to "add" for the query
131
+ addColumns(columns, prefix) {
132
+ prefix = prefix || this.addColumnsPrefix;
133
+ if (columns.sql.length > 0) {
134
+ const columnSql = columns.sql.map((column) => {
135
+ return prefix + column;
136
+ });
137
+ this.pushQuery({
138
+ sql: (this.lowerCase ? "alter table " : "ALTER TABLE ") + this.tableName() + " " + columnSql.join(" "),
139
+ bindings: columns.bindings
140
+ });
141
+ }
142
+ }
143
+ async commit(connection) {
144
+ return await connection.commit();
145
+ }
146
+ };
147
+ var ibmi_tablecompiler_default = IBMiTableCompiler;
148
+
149
+ // src/schema/ibmi-columncompiler.ts
150
+ import ColumnCompiler from "knex/lib/schema/columncompiler.js";
151
+ var IBMiColumnCompiler = class extends ColumnCompiler {
152
+ increments(options = { primaryKey: true }) {
153
+ return "int not null generated always as identity (start with 1, increment by 1)" + (this.tableCompiler._canBeAddPrimaryKey(options) ? " primary key" : "");
154
+ }
155
+ };
156
+ var ibmi_columncompiler_default = IBMiColumnCompiler;
157
+
158
+ // src/execution/ibmi-transaction.ts
159
+ import Transaction from "knex/lib/execution/transaction.js";
160
+ var IBMiTransaction = class extends Transaction {
161
+ begin(connection) {
162
+ try {
163
+ return connection.beginTransaction();
164
+ } catch (error) {
165
+ if (this.isConnectionClosed(error)) {
166
+ console.warn(
167
+ "IBM i DB2: Connection closed during transaction begin, DDL operations may have caused implicit commit"
168
+ );
169
+ throw new Error(
170
+ "Connection closed during transaction begin - consider using migrations.disableTransactions: true"
171
+ );
172
+ }
173
+ throw error;
174
+ }
175
+ }
176
+ rollback(connection) {
177
+ try {
178
+ return connection.rollback();
179
+ } catch (error) {
180
+ console.warn(
181
+ "IBM i DB2: Rollback encountered an error (likely closed connection):",
182
+ error?.message || error
183
+ );
184
+ return Promise.resolve();
185
+ }
186
+ }
187
+ commit(connection) {
188
+ try {
189
+ return connection.commit();
190
+ } catch (error) {
191
+ if (this.isConnectionClosed(error)) {
192
+ console.warn(
193
+ "IBM i DB2: Connection closed during commit - DDL operations cause implicit commits"
194
+ );
195
+ throw new Error(
196
+ "Connection closed during commit - this is expected with DDL operations on IBM i DB2"
197
+ );
198
+ }
199
+ throw error;
200
+ }
201
+ }
202
+ isConnectionClosed(error) {
203
+ const message = String(error?.message || error || "").toLowerCase();
204
+ return message.includes("connection") && (message.includes("closed") || message.includes("invalid") || message.includes("terminated") || message.includes("not connected"));
205
+ }
206
+ };
207
+ var ibmi_transaction_default = IBMiTransaction;
208
+
209
+ // src/query/ibmi-querycompiler.ts
210
+ import QueryCompiler from "knex/lib/query/querycompiler.js";
211
+ import { rawOrFn as rawOrFn_ } from "knex/lib/formatter/wrappingFormatter.js";
212
+ var IBMiQueryCompiler = class extends QueryCompiler {
213
+ formatTimestampLocal(date) {
214
+ const pad = (n) => String(n).padStart(2, "0");
215
+ const y = date.getFullYear();
216
+ const m = pad(date.getMonth() + 1);
217
+ const d = pad(date.getDate());
218
+ const hh = pad(date.getHours());
219
+ const mm = pad(date.getMinutes());
220
+ const ss = pad(date.getSeconds());
221
+ return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
222
+ }
223
+ insert() {
224
+ const insertValues = this.single.insert || [];
225
+ const { returning } = this.single;
226
+ if (this.isEmptyInsertValues(insertValues)) {
227
+ if (this.isEmptyObject(insertValues)) {
228
+ return this.buildEmptyInsertResult(returning);
229
+ }
230
+ return "";
231
+ }
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(" ");
239
+ const sql = `select ${selectColumns} from FINAL TABLE(${insertSql})`;
240
+ return { sql, returning };
241
+ }
242
+ isEmptyInsertValues(insertValues) {
243
+ return Array.isArray(insertValues) && insertValues.length === 0 || this.isEmptyObject(insertValues);
244
+ }
245
+ isEmptyObject(insertValues) {
246
+ return insertValues !== null && typeof insertValues === "object" && !Array.isArray(insertValues) && Object.keys(insertValues).length === 0;
247
+ }
248
+ buildEmptyInsertResult(returning) {
249
+ const selectColumns = returning ? this.formatter.columnize(returning) : "IDENTITY_VAL_LOCAL()";
250
+ const returningSql = returning ? this._returning("insert", returning, void 0) + " " : "";
251
+ const insertSql = [
252
+ this.with(),
253
+ `insert into ${this.tableName}`,
254
+ returningSql + this._emptyInsertValue
255
+ ].filter(Boolean).join(" ");
256
+ const sql = `select ${selectColumns} from FINAL TABLE(${insertSql})`;
257
+ return { sql, returning };
258
+ }
259
+ _buildInsertData(insertValues, returningSql) {
260
+ const insertData = this._prepInsert(insertValues);
261
+ 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}`;
265
+ }
266
+ if (Array.isArray(insertValues) && insertValues.length === 1 && insertValues[0]) {
267
+ return returningSql + this._emptyInsertValue;
268
+ }
269
+ return "";
270
+ }
271
+ _prepInsert(data) {
272
+ if (typeof data === "object" && data?.migration_time) {
273
+ const parsed = new Date(data.migration_time);
274
+ if (!isNaN(parsed.getTime())) {
275
+ data.migration_time = this.formatTimestampLocal(parsed);
276
+ }
277
+ }
278
+ const isRaw = rawOrFn_(
279
+ data,
280
+ void 0,
281
+ this.builder,
282
+ this.client,
283
+ this.bindingsHolder
284
+ );
285
+ if (isRaw) {
286
+ return isRaw;
287
+ }
288
+ const dataArray = Array.isArray(data) ? data : data ? [data] : [];
289
+ if (dataArray.length === 0) {
290
+ return { columns: [], values: [] };
291
+ }
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
+ }
297
+ }
298
+ const columns = Array.from(allColumns).sort();
299
+ const values = [];
300
+ 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);
306
+ }
307
+ return {
308
+ columns,
309
+ values
310
+ };
311
+ }
312
+ update() {
313
+ const withSQL = this.with();
314
+ const updates = this._prepUpdate(this.single.update);
315
+ const where = this.where();
316
+ const order = this.order();
317
+ const limit = this.limit();
318
+ const { returning } = this.single;
319
+ const baseUpdateSql = [
320
+ withSQL,
321
+ `update ${this.single.only ? "only " : ""}${this.tableName}`,
322
+ "set",
323
+ updates.join(", "),
324
+ where,
325
+ order,
326
+ limit
327
+ ].filter(Boolean).join(" ");
328
+ if (returning) {
329
+ this.client.logger.warn?.(
330
+ "IBMi DB2 does not support returning in update statements, only inserts"
331
+ );
332
+ const selectColumns = this.formatter.columnize(this.single.returning);
333
+ const sql = `select ${selectColumns} from FINAL TABLE(${baseUpdateSql})`;
334
+ return { sql, returning };
335
+ }
336
+ return { sql: baseUpdateSql, returning };
337
+ }
338
+ /**
339
+ * Handle returning clause for IBMi DB2 queries
340
+ * Note: IBMi DB2 has limited support for RETURNING clauses
341
+ * @param method - The SQL method (insert, update, delete)
342
+ * @param value - The returning value
343
+ * @param withTrigger - Trigger support (currently unused)
344
+ */
345
+ _returning(method, value, withTrigger) {
346
+ switch (method) {
347
+ case "update":
348
+ case "insert":
349
+ return value ? `${withTrigger ? " into #out" : ""}` : "";
350
+ case "del":
351
+ return value ? `${withTrigger ? " into #out" : ""}` : "";
352
+ case "rowcount":
353
+ return value ? "select @@rowcount" : "";
354
+ default:
355
+ return "";
356
+ }
357
+ }
358
+ columnizeWithPrefix(prefix, target) {
359
+ 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]);
365
+ }
366
+ return str;
367
+ }
368
+ };
369
+ var ibmi_querycompiler_default = IBMiQueryCompiler;
370
+
371
+ // src/index.ts
372
+ import { Readable } from "stream";
373
+
374
+ // src/migrations/ibmi-migration-runner.ts
375
+ import fs from "fs";
376
+ import path from "path";
377
+ var IBMiMigrationRunner = class {
378
+ constructor(knex2, config) {
379
+ __publicField(this, "knex");
380
+ __publicField(this, "config");
381
+ this.knex = knex2;
382
+ this.config = {
383
+ directory: "./migrations",
384
+ tableName: "KNEX_MIGRATIONS",
385
+ schemaName: void 0,
386
+ extension: "js",
387
+ ...config
388
+ };
389
+ }
390
+ getFullTableName() {
391
+ return this.config.schemaName ? `${this.config.schemaName}.${this.config.tableName}` : this.config.tableName;
392
+ }
393
+ async latest() {
394
+ try {
395
+ console.log(
396
+ "\u{1F680} IBM i DB2 Migration Runner - bypassing Knex locking system"
397
+ );
398
+ const tableName = this.getFullTableName();
399
+ const migrationTableExists = await this.knex.schema.hasTable(
400
+ tableName
401
+ );
402
+ if (!migrationTableExists) {
403
+ console.log(`\u{1F4DD} Creating migration table: ${tableName}`);
404
+ await this.knex.schema.createTable(tableName, (table) => {
405
+ table.increments("id").primary();
406
+ table.string("name");
407
+ table.integer("batch");
408
+ table.timestamp("migration_time");
409
+ });
410
+ console.log("\u2705 Migration table created");
411
+ }
412
+ const completed = await this.knex(tableName).select("NAME").orderBy("ID");
413
+ const completedNames = completed.map((c) => c.NAME);
414
+ console.log(`\u{1F4CB} Found ${completedNames.length} completed migrations`);
415
+ const migrationFiles = this.getMigrationFiles();
416
+ console.log(`\u{1F4C1} Found ${migrationFiles.length} migration files`);
417
+ const newMigrations = migrationFiles.filter(
418
+ (file) => !completedNames.includes(file)
419
+ );
420
+ if (newMigrations.length === 0) {
421
+ console.log("\u2705 No new migrations to run");
422
+ return;
423
+ }
424
+ console.log(`\u{1F3AF} Running ${newMigrations.length} new migrations:`);
425
+ newMigrations.forEach((file) => console.log(` - ${file}`));
426
+ const batchResult = await this.knex(tableName).max("BATCH as max_batch");
427
+ const nextBatch = (batchResult[0]?.max_batch || 0) + 1;
428
+ console.log(`\u{1F4CA} Using batch number: ${nextBatch}`);
429
+ for (const migrationFile of newMigrations) {
430
+ console.log(`
431
+ \u{1F504} Running migration: ${migrationFile}`);
432
+ try {
433
+ const migrationPath = this.getMigrationPath(migrationFile);
434
+ const migration = await import(migrationPath);
435
+ if (!migration.up || typeof migration.up !== "function") {
436
+ throw new Error(`Migration ${migrationFile} has no 'up' function`);
437
+ }
438
+ console.log(` \u26A1 Executing migration...`);
439
+ await migration.up(this.knex);
440
+ await this.knex(tableName).insert({
441
+ name: migrationFile,
442
+ batch: nextBatch,
443
+ migration_time: /* @__PURE__ */ new Date()
444
+ });
445
+ console.log(` \u2705 Migration ${migrationFile} completed successfully`);
446
+ } catch (error) {
447
+ console.error(
448
+ ` \u274C Migration ${migrationFile} failed:`,
449
+ error.message
450
+ );
451
+ throw error;
452
+ }
453
+ }
454
+ console.log(`
455
+ \u{1F389} All migrations completed successfully!`);
456
+ } catch (error) {
457
+ console.error("\u274C Migration runner failed:", error.message);
458
+ throw error;
459
+ }
460
+ }
461
+ async rollback(steps = 1) {
462
+ try {
463
+ console.log(`\u{1F504} Rolling back ${steps} migration batch(es)...`);
464
+ const tableName = this.getFullTableName();
465
+ const batchesToRollback = await this.knex(tableName).distinct("BATCH").orderBy("BATCH", "desc").limit(steps);
466
+ if (batchesToRollback.length === 0) {
467
+ console.log("\u2705 No migrations to rollback");
468
+ return;
469
+ }
470
+ const batchNumbers = batchesToRollback.map((b) => b.BATCH);
471
+ console.log(`\u{1F4CA} Rolling back batches: ${batchNumbers.join(", ")}`);
472
+ const migrationsToRollback = await this.knex(tableName).select("NAME").whereIn("BATCH", batchNumbers).orderBy("ID", "desc");
473
+ console.log(`\u{1F3AF} Rolling back ${migrationsToRollback.length} migrations:`);
474
+ migrationsToRollback.forEach((m) => console.log(` - ${m.NAME}`));
475
+ for (const migrationRecord of migrationsToRollback) {
476
+ const migrationFile = migrationRecord.NAME;
477
+ console.log(`
478
+ \u{1F504} Rolling back migration: ${migrationFile}`);
479
+ try {
480
+ const migrationPath = this.getMigrationPath(migrationFile);
481
+ const migration = await import(migrationPath);
482
+ if (migration.down && typeof migration.down === "function") {
483
+ console.log(` \u26A1 Executing rollback...`);
484
+ await migration.down(this.knex);
485
+ } else {
486
+ console.log(
487
+ ` \u26A0\uFE0F Migration ${migrationFile} has no 'down' function, skipping rollback`
488
+ );
489
+ }
490
+ await this.knex(tableName).where("NAME", migrationFile).del();
491
+ console.log(
492
+ ` \u2705 Migration ${migrationFile} rolled back successfully`
493
+ );
494
+ } catch (error) {
495
+ console.error(
496
+ ` \u274C Migration ${migrationFile} rollback failed:`,
497
+ error.message
498
+ );
499
+ throw error;
500
+ }
501
+ }
502
+ console.log(`
503
+ \u{1F389} Rollback completed successfully!`);
504
+ } catch (error) {
505
+ console.error("\u274C Rollback failed:", error.message);
506
+ throw error;
507
+ }
508
+ }
509
+ async currentVersion() {
510
+ try {
511
+ const tableName = this.getFullTableName();
512
+ const migrationTableExists = await this.knex.schema.hasTable(
513
+ tableName
514
+ );
515
+ if (!migrationTableExists) {
516
+ return null;
517
+ }
518
+ const result = await this.knex(tableName).select("NAME").orderBy("ID", "desc").first();
519
+ return result?.NAME || null;
520
+ } catch (error) {
521
+ console.error("\u274C Error getting current version:", error.message);
522
+ return null;
523
+ }
524
+ }
525
+ async listExecuted() {
526
+ try {
527
+ const tableName = this.getFullTableName();
528
+ const migrationTableExists = await this.knex.schema.hasTable(
529
+ tableName
530
+ );
531
+ if (!migrationTableExists) {
532
+ return [];
533
+ }
534
+ const completed = await this.knex(tableName).select("NAME").orderBy("ID");
535
+ return completed.map((c) => c.NAME);
536
+ } catch (error) {
537
+ console.error("\u274C Error listing executed migrations:", error.message);
538
+ return [];
539
+ }
540
+ }
541
+ async listPending() {
542
+ try {
543
+ const allFiles = this.getMigrationFiles();
544
+ const executed = await this.listExecuted();
545
+ return allFiles.filter((file) => !executed.includes(file));
546
+ } catch (error) {
547
+ console.error("\u274C Error listing pending migrations:", error.message);
548
+ return [];
549
+ }
550
+ }
551
+ getMigrationFiles() {
552
+ const { directory, extension } = this.config;
553
+ if (!fs.existsSync(directory)) {
554
+ throw new Error(`Migration directory does not exist: ${directory}`);
555
+ }
556
+ const validExtensions = ["js", "ts", "mjs", "cjs"];
557
+ const extensionToCheck = extension || "js";
558
+ return fs.readdirSync(directory).filter((file) => {
559
+ if (extension && extension !== "js") {
560
+ return file.endsWith(`.${extension}`);
561
+ }
562
+ return validExtensions.some((ext) => file.endsWith(`.${ext}`));
563
+ }).sort();
564
+ }
565
+ getMigrationPath(filename) {
566
+ return path.resolve(this.config.directory, filename);
567
+ }
568
+ };
569
+ function createIBMiMigrationRunner(knex2, config) {
570
+ return new IBMiMigrationRunner(knex2, config);
571
+ }
572
+
573
+ // src/index.ts
574
+ var DB2Client = class extends knex.Client {
575
+ constructor(config) {
576
+ console.log("\u{1F3AF} DB2Client constructor called!");
577
+ super(config);
578
+ this.driverName = "odbc";
579
+ if (this.dialect && !this.config.client) {
580
+ this.printWarn(
581
+ `Using 'this.dialect' to identify the client is deprecated and support for it will be removed in the future. Please use configuration option 'client' instead.`
582
+ );
583
+ }
584
+ const dbClient = this.config.client || this.dialect;
585
+ if (!dbClient) {
586
+ throw new Error(
587
+ `knex: Required configuration option 'client' is missing.`
588
+ );
589
+ }
590
+ if (config.version) {
591
+ this.version = config.version;
592
+ }
593
+ if (this.driverName && config.connection) {
594
+ this.initializeDriver();
595
+ if (!config.pool || config.pool && config.pool.max !== 0) {
596
+ this.initializePool(config);
597
+ }
598
+ }
599
+ this.valueForUndefined = this.raw("DEFAULT");
600
+ if (config.useNullAsDefault) {
601
+ this.valueForUndefined = null;
602
+ }
603
+ }
604
+ // Helper method to safely stringify objects that might have circular references
605
+ safeStringify(obj, indent = 0) {
606
+ try {
607
+ return JSON.stringify(obj, null, indent);
608
+ } catch (error) {
609
+ if (error instanceof Error && error.message.includes("circular")) {
610
+ return `[Circular structure - ${typeof obj}]`;
611
+ }
612
+ return `[Stringify error - ${typeof obj}]`;
613
+ }
614
+ }
615
+ _driver() {
616
+ return odbc;
617
+ }
618
+ wrapIdentifierImpl(value) {
619
+ if (value.includes("KNEX_MIGRATIONS") || value.includes("knex_migrations")) {
620
+ return value.toUpperCase();
621
+ }
622
+ return value;
623
+ }
624
+ printDebug(message) {
625
+ if (process.env.DEBUG === "true") {
626
+ if (this.logger.debug) {
627
+ this.logger.debug("knex-ibmi: " + message);
628
+ }
629
+ }
630
+ }
631
+ printError(message) {
632
+ if (this.logger.error) {
633
+ this.logger.error("knex-ibmi: " + message);
634
+ }
635
+ }
636
+ printWarn(message) {
637
+ if (process.env.DEBUG === "true") {
638
+ if (this.logger.warn) {
639
+ this.logger.warn("knex-ibmi: " + message);
640
+ }
641
+ }
642
+ }
643
+ // Get a raw connection, called by the pool manager whenever a new
644
+ // connection needs to be added to the pool.
645
+ async acquireRawConnection() {
646
+ this.printDebug("acquiring raw connection");
647
+ const connectionConfig = this.config.connection;
648
+ if (!connectionConfig) {
649
+ return this.printError("There is no connection config defined");
650
+ }
651
+ this.printDebug(
652
+ "connection config: " + this._getConnectionString(connectionConfig)
653
+ );
654
+ let connection;
655
+ if (this.config?.pool) {
656
+ const poolConfig = {
657
+ connectionString: this._getConnectionString(connectionConfig),
658
+ connectionTimeout: this.config?.acquireConnectionTimeout || 6e4,
659
+ initialSize: this.config?.pool?.min || 2,
660
+ maxSize: this.config?.pool?.max || 10,
661
+ reuseConnection: true
662
+ };
663
+ const pool = await this.driver.pool(poolConfig);
664
+ connection = await pool.connect();
665
+ } else {
666
+ connection = await this.driver.connect(
667
+ this._getConnectionString(connectionConfig)
668
+ );
669
+ }
670
+ return connection;
671
+ }
672
+ // Used to explicitly close a connection, called internally by the pool manager
673
+ // when a connection times out or the pool is shutdown.
674
+ async destroyRawConnection(connection) {
675
+ this.printDebug("destroy connection");
676
+ return await connection.close();
677
+ }
678
+ _getConnectionString(connectionConfig) {
679
+ const connectionStringParams = connectionConfig.connectionStringParams || {};
680
+ const connectionStringExtension = Object.keys(
681
+ connectionStringParams
682
+ ).reduce((result, key) => {
683
+ const value = connectionStringParams[key];
684
+ return `${result}${key}=${value};`;
685
+ }, "");
686
+ return `DRIVER=${connectionConfig.driver};SYSTEM=${connectionConfig.host};HOSTNAME=${connectionConfig.host};PORT=${connectionConfig.port};DATABASE=${connectionConfig.database};UID=${connectionConfig.user};PWD=${connectionConfig.password};` + connectionStringExtension;
687
+ }
688
+ // Runs the query on the specified connection, providing the bindings
689
+ async _query(connection, obj) {
690
+ const queryObject = this.normalizeQueryObject(obj);
691
+ const method = this.determineQueryMethod(queryObject);
692
+ queryObject.sqlMethod = method;
693
+ if (process.env.DEBUG === "true" && queryObject.sql && (queryObject.sql.toLowerCase().includes("create table") || queryObject.sql.toLowerCase().includes("knex_migrations"))) {
694
+ this.printDebug(
695
+ `Executing ${method} query: ${queryObject.sql.substring(0, 200)}...`
696
+ );
697
+ if (queryObject.bindings?.length) {
698
+ this.printDebug(`Bindings: ${JSON.stringify(queryObject.bindings)}`);
699
+ }
700
+ }
701
+ try {
702
+ const startTime = Date.now();
703
+ if (this.isSelectMethod(method)) {
704
+ await this.executeSelectQuery(connection, queryObject);
705
+ } else {
706
+ await this.executeStatementQuery(connection, queryObject);
707
+ }
708
+ const endTime = Date.now();
709
+ if (process.env.DEBUG === "true" && queryObject.sql && (queryObject.sql.toLowerCase().includes("create table") || queryObject.sql.toLowerCase().includes("knex_migrations"))) {
710
+ this.printDebug(`${method} completed in ${endTime - startTime}ms`);
711
+ }
712
+ this.printDebug(`Query completed: ${method} (${endTime - startTime}ms)`);
713
+ return queryObject;
714
+ } catch (error) {
715
+ if (this.isConnectionError(error)) {
716
+ this.printError(
717
+ `Connection error during ${method} query: ${error.message}`
718
+ );
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
+ }
734
+ }
735
+ throw new Error(
736
+ `Connection closed during ${method} operation - IBM i DB2 DDL operations cause implicit commits`
737
+ );
738
+ }
739
+ throw error;
740
+ }
741
+ }
742
+ normalizeQueryObject(obj) {
743
+ if (!obj || typeof obj === "string") {
744
+ return { sql: obj };
745
+ }
746
+ return obj;
747
+ }
748
+ determineQueryMethod(obj) {
749
+ return (obj.hasOwnProperty("method") && obj.method !== "raw" ? obj.method : obj.sql.split(" ")[0]).toLowerCase();
750
+ }
751
+ isSelectMethod(method) {
752
+ return method === "select" || method === "first" || method === "pluck";
753
+ }
754
+ async executeSelectQuery(connection, obj) {
755
+ const rows = await connection.query(
756
+ obj.sql,
757
+ obj.bindings
758
+ );
759
+ if (rows) {
760
+ obj.response = { rows, rowCount: rows.length };
761
+ }
762
+ }
763
+ async executeStatementQuery(connection, obj) {
764
+ let statement;
765
+ try {
766
+ statement = await connection.createStatement();
767
+ await statement.prepare(obj.sql);
768
+ if (obj.bindings) {
769
+ await statement.bind(obj.bindings);
770
+ }
771
+ const result = await statement.execute();
772
+ this.printDebug(String(result));
773
+ obj.response = this.formatStatementResponse(result);
774
+ } catch (err) {
775
+ const sql = (obj.sql || "").toLowerCase();
776
+ const isDml = obj.sqlMethod === "update" /* UPDATE */ || sql.includes(" update ") || sql.startsWith("update") || obj.sqlMethod === "del" /* DELETE */ || sql.includes(" delete ") || sql.startsWith("delete");
777
+ const odbcErrors = err?.odbcErrors;
778
+ const isEmptyOdbcError = Array.isArray(odbcErrors) && odbcErrors.length === 0;
779
+ const hasNoDataState = Array.isArray(odbcErrors) ? odbcErrors.some(
780
+ (e) => String(e?.state || e?.SQLSTATE || "").toUpperCase() === "02000"
781
+ ) : false;
782
+ if (isDml && (isEmptyOdbcError || hasNoDataState || this.isNoDataError(err))) {
783
+ this.printWarn(
784
+ `ODBC signaled no-data for ${sql.includes("update") ? "UPDATE" : "DELETE"}; treating as 0 rows affected`
785
+ );
786
+ obj.response = { rows: [], rowCount: 0 };
787
+ return;
788
+ }
789
+ this.printError(this.safeStringify(err));
790
+ throw err;
791
+ } finally {
792
+ if (statement && typeof statement.close === "function") {
793
+ try {
794
+ await statement.close();
795
+ } catch (closeErr) {
796
+ this.printDebug(
797
+ `Error closing statement: ${this.safeStringify(closeErr, 2)}`
798
+ );
799
+ }
800
+ }
801
+ }
802
+ }
803
+ isNoDataError(error) {
804
+ if (!error) return false;
805
+ const msg = String(error?.message || error || "").toLowerCase();
806
+ return msg.includes("02000") || msg.includes("no data") || msg.includes("no rows") || msg.includes("0 rows");
807
+ }
808
+ /**
809
+ * Format statement response from ODBC driver
810
+ * Handles special case for IDENTITY_VAL_LOCAL() function
811
+ */
812
+ formatStatementResponse(result) {
813
+ const isIdentityQuery = result.statement?.includes("IDENTITY_VAL_LOCAL()");
814
+ if (isIdentityQuery && result.columns?.length > 0) {
815
+ return {
816
+ rows: result.map(
817
+ (row) => row[result.columns[0].name]
818
+ ),
819
+ rowCount: result.count
820
+ };
821
+ }
822
+ const rowCount = typeof result?.count === "number" ? result.count : 0;
823
+ return {
824
+ rows: result,
825
+ rowCount
826
+ };
827
+ }
828
+ async _stream(connection, obj, stream, options) {
829
+ if (!obj.sql) throw new Error("A query is required to stream results");
830
+ return new Promise((resolve, reject) => {
831
+ stream.on("error", reject);
832
+ stream.on("end", resolve);
833
+ connection.query(
834
+ obj.sql,
835
+ obj.bindings,
836
+ {
837
+ cursor: true,
838
+ fetchSize: options?.fetchSize || 1
839
+ },
840
+ (error, cursor) => {
841
+ if (error) {
842
+ this.printError(this.safeStringify(error, 2));
843
+ stream.emit("error", error);
844
+ reject(error);
845
+ return;
846
+ }
847
+ const readableStream = this._createCursorStream(cursor);
848
+ readableStream.on("error", (err) => {
849
+ reject(err);
850
+ stream.emit("error", err);
851
+ });
852
+ readableStream.pipe(stream);
853
+ }
854
+ );
855
+ });
856
+ }
857
+ _createCursorStream(cursor) {
858
+ const parentThis = this;
859
+ return new Readable({
860
+ objectMode: true,
861
+ read() {
862
+ cursor.fetch((error, result) => {
863
+ if (error) {
864
+ parentThis.printError(parentThis.safeStringify(error, 2));
865
+ }
866
+ if (!cursor.noData) {
867
+ this.push(result);
868
+ } else {
869
+ cursor.close((closeError) => {
870
+ if (closeError) {
871
+ parentThis.printError(JSON.stringify(closeError, null, 2));
872
+ }
873
+ if (result) {
874
+ this.push(result);
875
+ }
876
+ this.push(null);
877
+ });
878
+ }
879
+ });
880
+ }
881
+ });
882
+ }
883
+ transaction(container, config, outerTx) {
884
+ return new ibmi_transaction_default(this, container, config, outerTx);
885
+ }
886
+ schemaCompiler(tableBuilder) {
887
+ return new ibmi_compiler_default(this, tableBuilder);
888
+ }
889
+ tableCompiler(tableBuilder) {
890
+ return new ibmi_tablecompiler_default(this, tableBuilder);
891
+ }
892
+ columnCompiler(tableCompiler, columnCompiler) {
893
+ return new ibmi_columncompiler_default(this, tableCompiler, columnCompiler);
894
+ }
895
+ queryCompiler(builder, bindings) {
896
+ return new ibmi_querycompiler_default(this, builder, bindings);
897
+ }
898
+ // Create IBM i-specific migration runner that bypasses Knex's problematic locking system
899
+ createMigrationRunner(config) {
900
+ const knexInstance = this.context || this;
901
+ return createIBMiMigrationRunner(knexInstance, config);
902
+ }
903
+ processResponse(obj, runner) {
904
+ if (obj === null) return null;
905
+ const { response } = obj;
906
+ if (obj.output) {
907
+ try {
908
+ const result = obj.output(runner, response);
909
+ return result;
910
+ } catch (error) {
911
+ this.printError(
912
+ `Custom output function failed: ${error.message || error}`
913
+ );
914
+ if (this.isConnectionError(error)) {
915
+ throw new Error(
916
+ "Connection closed during query processing - consider using migrations.disableTransactions: true for DDL operations"
917
+ );
918
+ }
919
+ throw error;
920
+ }
921
+ }
922
+ const validationResult = this.validateResponse(obj);
923
+ if (validationResult !== null) return validationResult;
924
+ return this.processSqlMethod(obj);
925
+ }
926
+ validateResponse(obj) {
927
+ if (!obj.response) {
928
+ this.printDebug("response undefined" + JSON.stringify(obj));
929
+ return null;
930
+ }
931
+ if (!obj.response.rows) {
932
+ this.printError("rows undefined" + JSON.stringify(obj));
933
+ return null;
934
+ }
935
+ return null;
936
+ }
937
+ isConnectionError(error) {
938
+ const errorMessage = (error.message || error.toString || error).toLowerCase();
939
+ return errorMessage.includes("connection") && (errorMessage.includes("closed") || errorMessage.includes("invalid") || errorMessage.includes("terminated") || errorMessage.includes("not connected"));
940
+ }
941
+ processSqlMethod(obj) {
942
+ const { rows, rowCount } = obj.response;
943
+ switch (obj.sqlMethod) {
944
+ case "select" /* SELECT */:
945
+ return rows;
946
+ case "pluck" /* PLUCK */:
947
+ return rows.map(obj.pluck);
948
+ case "first" /* FIRST */:
949
+ return rows[0];
950
+ case "insert" /* INSERT */:
951
+ return rows;
952
+ case "del" /* DELETE */:
953
+ case "delete" /* DELETE_ALT */:
954
+ case "update" /* UPDATE */:
955
+ return obj.select ? rows : rowCount ?? 0;
956
+ case "counter" /* COUNTER */:
957
+ return rowCount;
958
+ default:
959
+ return rows;
960
+ }
961
+ }
962
+ };
963
+ var DB2Dialect = DB2Client;
964
+ var index_default = DB2Client;
965
+ export {
966
+ DB2Dialect,
967
+ IBMiMigrationRunner,
968
+ createIBMiMigrationRunner,
969
+ index_default as default
970
+ };
971
+ //# sourceMappingURL=index.mjs.map