@bdkinc/knex-ibmi 0.0.6 → 0.0.8

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 CHANGED
@@ -17,8 +17,8 @@ This is an external dialect for [knex](https://github.com/tgriesser/knex). This
17
17
  Currently, this dialect has limited functionality compared to the Knex built-in dialects. Below are some of the limitations:
18
18
 
19
19
  - No streaming support
20
+ - Updates return the value of the first column in that row. Make sure your identifier is the first column in the table. The returning option does not work on updates.
20
21
  - Possibly other missing functionality
21
- - Uses a pool for all connections
22
22
  - Journaling must be handled separately. After a migration is ran journaling can be configured on the newly created tables. I recommend using the schema utility in the i access client solutions software.
23
23
 
24
24
  ## Installing
package/dist/index.js CHANGED
@@ -183,7 +183,57 @@ var import_querycompiler = __toESM(require("knex/lib/query/querycompiler"));
183
183
  var import_isObject2 = __toESM(require("lodash/isObject"));
184
184
  var import_wrappingFormatter = require("knex/lib/formatter/wrappingFormatter");
185
185
  var import_date_fns = require("date-fns");
186
+ var import_isEmpty = __toESM(require("lodash/isEmpty"));
186
187
  var IBMiQueryCompiler = class extends import_querycompiler.default {
188
+ insert() {
189
+ const insertValues = this.single.insert || [];
190
+ let sql = `SELECT ${// @ts-ignore
191
+ this.single.returning ? (
192
+ // @ts-ignore
193
+ this.formatter.columnize(this.single.returning)
194
+ ) : "IDENTITY_VAL_LOCAL()"} FROM FINAL TABLE(`;
195
+ sql += this.with() + `insert into ${this.tableName} `;
196
+ const { returning } = this.single;
197
+ const returningSql = returning ? (
198
+ // @ts-ignore
199
+ this._returning("insert", returning) + " "
200
+ ) : "";
201
+ if (Array.isArray(insertValues)) {
202
+ if (insertValues.length === 0) {
203
+ return "";
204
+ }
205
+ } else if (typeof insertValues === "object" && (0, import_isEmpty.default)(insertValues)) {
206
+ return {
207
+ // @ts-ignore
208
+ sql: sql + returningSql + this._emptyInsertValue,
209
+ returning
210
+ };
211
+ }
212
+ sql += this._buildInsertData(insertValues, returningSql);
213
+ sql += ")";
214
+ return {
215
+ sql,
216
+ returning
217
+ };
218
+ }
219
+ _buildInsertData(insertValues, returningSql) {
220
+ let sql = "";
221
+ const insertData = this._prepInsert(insertValues);
222
+ if (typeof insertData === "string") {
223
+ sql += insertData;
224
+ } else {
225
+ if (insertData.columns.length) {
226
+ sql += `(${this.formatter.columnize(insertData.columns)}`;
227
+ sql += `) ${returningSql}values (` + // @ts-ignore
228
+ this._buildInsertValues(insertData) + ")";
229
+ } else if (insertValues.length === 1 && insertValues[0]) {
230
+ sql += returningSql + this._emptyInsertValue;
231
+ } else {
232
+ return "";
233
+ }
234
+ }
235
+ return sql;
236
+ }
187
237
  _prepInsert(data) {
188
238
  if ((0, import_isObject2.default)(data)) {
189
239
  if (data.hasOwnProperty("migration_time")) {
@@ -237,6 +287,33 @@ var IBMiQueryCompiler = class extends import_querycompiler.default {
237
287
  values
238
288
  };
239
289
  }
290
+ _returning(method, value, withTrigger) {
291
+ switch (method) {
292
+ case "update":
293
+ case "insert":
294
+ return value ? (
295
+ // @ts-ignore
296
+ `${withTrigger ? " into #out" : ""}`
297
+ ) : "";
298
+ case "del":
299
+ return value ? (
300
+ // @ts-ignore
301
+ `${withTrigger ? " into #out" : ""}`
302
+ ) : "";
303
+ case "rowcount":
304
+ return value ? ";select @@rowcount" : "";
305
+ }
306
+ }
307
+ columnizeWithPrefix(prefix, target) {
308
+ const columns = typeof target === "string" ? [target] : target;
309
+ let str = "", i = -1;
310
+ while (++i < columns.length) {
311
+ if (i > 0)
312
+ str += ", ";
313
+ str += prefix + this.wrap(columns[i]);
314
+ }
315
+ return str;
316
+ }
240
317
  };
241
318
  var ibmi_querycompiler_default = IBMiQueryCompiler;
242
319
 
@@ -315,22 +392,62 @@ var DB2Client = class extends import_knex.default.Client {
315
392
  obj.response = { rows, rowCount: rows.length };
316
393
  }
317
394
  } else {
395
+ const connection = await pool.connect();
396
+ await connection.beginTransaction();
318
397
  try {
319
- const connection = await pool.connect();
320
398
  const statement = await connection.createStatement();
321
399
  await statement.prepare(obj.sql);
400
+ console.log({ obj });
322
401
  if (obj.bindings) {
323
402
  await statement.bind(obj.bindings);
324
403
  }
325
404
  const result = await statement.execute();
326
- obj.response = { rows: [result.count], rowCount: result.count };
405
+ if (result.statement.includes("IDENTITY_VAL_LOCAL()")) {
406
+ obj.response = {
407
+ rows: result.map(
408
+ (row) => result.columns.length > 0 ? row[result.columns[0].name] : row
409
+ ),
410
+ rowCount: result.length
411
+ };
412
+ } else if (method === "update") {
413
+ let returningSelect = obj.sql.replace("update", "select * from ");
414
+ returningSelect = returningSelect.replace("where", "and");
415
+ returningSelect = returningSelect.replace("set", "where");
416
+ returningSelect = returningSelect.replace(this.tableName, "where");
417
+ const selectStatement = await connection.createStatement();
418
+ await selectStatement.prepare(returningSelect);
419
+ if (obj.bindings) {
420
+ await selectStatement.bind(obj.bindings);
421
+ }
422
+ const selected = await selectStatement.execute();
423
+ obj.response = { rows: selected.map(
424
+ (row) => selected.columns.length > 0 ? row[selected.columns[0].name] : row
425
+ ), rowCount: selected.length };
426
+ } else {
427
+ obj.response = { rows: result, rowCount: result.length };
428
+ }
327
429
  } catch (err) {
328
430
  console.error(err);
431
+ await connection.rollback();
329
432
  throw new Error(err);
433
+ } finally {
434
+ await connection.commit();
330
435
  }
331
436
  }
332
437
  return obj;
333
438
  }
439
+ _selectAfterUpdate() {
440
+ const returnSelect = `; SELECT ${this.single.returning ? (
441
+ // @ts-ignore
442
+ this.formatter.columnize(this.single.returning)
443
+ ) : "*"} from ${this.tableName} `;
444
+ let whereStatement = [this.where()];
445
+ console.log({ whereStatement });
446
+ for (const [key, value] of Object.entries(this.single.update)) {
447
+ whereStatement.push(`WHERE ${key} = ${value}`);
448
+ }
449
+ return returnSelect + whereStatement.join(" and ");
450
+ }
334
451
  transaction(container, config, outerTx) {
335
452
  return new ibmi_transaction_default(this, ...arguments);
336
453
  }
@@ -366,6 +483,7 @@ var DB2Client = class extends import_knex.default.Client {
366
483
  case "del":
367
484
  case "delete":
368
485
  case "update":
486
+ return rows;
369
487
  case "counter":
370
488
  return resp.rowCount;
371
489
  default:
package/dist/index.mjs CHANGED
@@ -148,7 +148,57 @@ import QueryCompiler from "knex/lib/query/querycompiler";
148
148
  import isObject2 from "lodash/isObject";
149
149
  import { rawOrFn as rawOrFn_ } from "knex/lib/formatter/wrappingFormatter";
150
150
  import { format } from "date-fns";
151
+ import isEmpty from "lodash/isEmpty";
151
152
  var IBMiQueryCompiler = class extends QueryCompiler {
153
+ insert() {
154
+ const insertValues = this.single.insert || [];
155
+ let sql = `SELECT ${// @ts-ignore
156
+ this.single.returning ? (
157
+ // @ts-ignore
158
+ this.formatter.columnize(this.single.returning)
159
+ ) : "IDENTITY_VAL_LOCAL()"} FROM FINAL TABLE(`;
160
+ sql += this.with() + `insert into ${this.tableName} `;
161
+ const { returning } = this.single;
162
+ const returningSql = returning ? (
163
+ // @ts-ignore
164
+ this._returning("insert", returning) + " "
165
+ ) : "";
166
+ if (Array.isArray(insertValues)) {
167
+ if (insertValues.length === 0) {
168
+ return "";
169
+ }
170
+ } else if (typeof insertValues === "object" && isEmpty(insertValues)) {
171
+ return {
172
+ // @ts-ignore
173
+ sql: sql + returningSql + this._emptyInsertValue,
174
+ returning
175
+ };
176
+ }
177
+ sql += this._buildInsertData(insertValues, returningSql);
178
+ sql += ")";
179
+ return {
180
+ sql,
181
+ returning
182
+ };
183
+ }
184
+ _buildInsertData(insertValues, returningSql) {
185
+ let sql = "";
186
+ const insertData = this._prepInsert(insertValues);
187
+ if (typeof insertData === "string") {
188
+ sql += insertData;
189
+ } else {
190
+ if (insertData.columns.length) {
191
+ sql += `(${this.formatter.columnize(insertData.columns)}`;
192
+ sql += `) ${returningSql}values (` + // @ts-ignore
193
+ this._buildInsertValues(insertData) + ")";
194
+ } else if (insertValues.length === 1 && insertValues[0]) {
195
+ sql += returningSql + this._emptyInsertValue;
196
+ } else {
197
+ return "";
198
+ }
199
+ }
200
+ return sql;
201
+ }
152
202
  _prepInsert(data) {
153
203
  if (isObject2(data)) {
154
204
  if (data.hasOwnProperty("migration_time")) {
@@ -202,6 +252,33 @@ var IBMiQueryCompiler = class extends QueryCompiler {
202
252
  values
203
253
  };
204
254
  }
255
+ _returning(method, value, withTrigger) {
256
+ switch (method) {
257
+ case "update":
258
+ case "insert":
259
+ return value ? (
260
+ // @ts-ignore
261
+ `${withTrigger ? " into #out" : ""}`
262
+ ) : "";
263
+ case "del":
264
+ return value ? (
265
+ // @ts-ignore
266
+ `${withTrigger ? " into #out" : ""}`
267
+ ) : "";
268
+ case "rowcount":
269
+ return value ? ";select @@rowcount" : "";
270
+ }
271
+ }
272
+ columnizeWithPrefix(prefix, target) {
273
+ const columns = typeof target === "string" ? [target] : target;
274
+ let str = "", i = -1;
275
+ while (++i < columns.length) {
276
+ if (i > 0)
277
+ str += ", ";
278
+ str += prefix + this.wrap(columns[i]);
279
+ }
280
+ return str;
281
+ }
205
282
  };
206
283
  var ibmi_querycompiler_default = IBMiQueryCompiler;
207
284
 
@@ -280,22 +357,62 @@ var DB2Client = class extends knex.Client {
280
357
  obj.response = { rows, rowCount: rows.length };
281
358
  }
282
359
  } else {
360
+ const connection = await pool.connect();
361
+ await connection.beginTransaction();
283
362
  try {
284
- const connection = await pool.connect();
285
363
  const statement = await connection.createStatement();
286
364
  await statement.prepare(obj.sql);
365
+ console.log({ obj });
287
366
  if (obj.bindings) {
288
367
  await statement.bind(obj.bindings);
289
368
  }
290
369
  const result = await statement.execute();
291
- obj.response = { rows: [result.count], rowCount: result.count };
370
+ if (result.statement.includes("IDENTITY_VAL_LOCAL()")) {
371
+ obj.response = {
372
+ rows: result.map(
373
+ (row) => result.columns.length > 0 ? row[result.columns[0].name] : row
374
+ ),
375
+ rowCount: result.length
376
+ };
377
+ } else if (method === "update") {
378
+ let returningSelect = obj.sql.replace("update", "select * from ");
379
+ returningSelect = returningSelect.replace("where", "and");
380
+ returningSelect = returningSelect.replace("set", "where");
381
+ returningSelect = returningSelect.replace(this.tableName, "where");
382
+ const selectStatement = await connection.createStatement();
383
+ await selectStatement.prepare(returningSelect);
384
+ if (obj.bindings) {
385
+ await selectStatement.bind(obj.bindings);
386
+ }
387
+ const selected = await selectStatement.execute();
388
+ obj.response = { rows: selected.map(
389
+ (row) => selected.columns.length > 0 ? row[selected.columns[0].name] : row
390
+ ), rowCount: selected.length };
391
+ } else {
392
+ obj.response = { rows: result, rowCount: result.length };
393
+ }
292
394
  } catch (err) {
293
395
  console.error(err);
396
+ await connection.rollback();
294
397
  throw new Error(err);
398
+ } finally {
399
+ await connection.commit();
295
400
  }
296
401
  }
297
402
  return obj;
298
403
  }
404
+ _selectAfterUpdate() {
405
+ const returnSelect = `; SELECT ${this.single.returning ? (
406
+ // @ts-ignore
407
+ this.formatter.columnize(this.single.returning)
408
+ ) : "*"} from ${this.tableName} `;
409
+ let whereStatement = [this.where()];
410
+ console.log({ whereStatement });
411
+ for (const [key, value] of Object.entries(this.single.update)) {
412
+ whereStatement.push(`WHERE ${key} = ${value}`);
413
+ }
414
+ return returnSelect + whereStatement.join(" and ");
415
+ }
299
416
  transaction(container, config, outerTx) {
300
417
  return new ibmi_transaction_default(this, ...arguments);
301
418
  }
@@ -331,6 +448,7 @@ var DB2Client = class extends knex.Client {
331
448
  case "del":
332
449
  case "delete":
333
450
  case "update":
451
+ return rows;
334
452
  case "counter":
335
453
  return resp.rowCount;
336
454
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bdkinc/knex-ibmi",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Knex dialect for IBMi",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/src/index.ts CHANGED
@@ -110,24 +110,78 @@ class DB2Client extends knex.Client {
110
110
  obj.response = { rows, rowCount: rows.length };
111
111
  }
112
112
  } else {
113
+ const connection = await pool.connect();
114
+ await connection.beginTransaction();
113
115
  try {
114
- const connection = await pool.connect();
115
116
  const statement = await connection.createStatement();
116
117
  await statement.prepare(obj.sql);
118
+ console.log({ obj });
117
119
  if (obj.bindings) {
118
120
  await statement.bind(obj.bindings);
119
121
  }
120
122
  const result = await statement.execute();
121
- obj.response = { rows: [result.count], rowCount: result.count };
123
+ // this is hacky we check the SQL for the ID column
124
+ // most dialects return the ID of the inserted
125
+ // we check for the IDENTITY scalar function
126
+ // if that function is present, then we just return the value of the
127
+ // IDENTITY column
128
+ if (result.statement.includes("IDENTITY_VAL_LOCAL()")) {
129
+ obj.response = {
130
+ rows: result.map((row) =>
131
+ result.columns.length > 0 ? row[result.columns[0].name] : row,
132
+ ),
133
+ rowCount: result.length,
134
+ };
135
+ } else if (method === "update") {
136
+ // if is in update we need to run a separate select query
137
+ // this also feels hacky and should be cleaned up
138
+ // it would be a lot easier if the table-reference function
139
+ // worked the same for updates as it does inserts
140
+ // on DB2 LUW it does work so if they ever add it we need to fix
141
+ let returningSelect = obj.sql.replace("update", "select * from ");
142
+ returningSelect = returningSelect.replace("where", "and");
143
+ returningSelect = returningSelect.replace("set", "where");
144
+ returningSelect = returningSelect.replace(this.tableName, "where");
145
+ const selectStatement = await connection.createStatement();
146
+ await selectStatement.prepare(returningSelect);
147
+ if (obj.bindings) {
148
+ await selectStatement.bind(obj.bindings);
149
+ }
150
+ const selected = await selectStatement.execute();
151
+ obj.response = {rows: selected.map((row) =>
152
+ selected.columns.length > 0 ? row[selected.columns[0].name] : row,
153
+ ), rowCount: selected.length}
154
+ } else {
155
+ obj.response = { rows: result, rowCount: result.length };
156
+ }
122
157
  } catch (err: any) {
123
158
  console.error(err);
159
+ await connection.rollback()
124
160
  throw new Error(err);
161
+ } finally {
162
+ await connection.commit()
125
163
  }
164
+
126
165
  }
127
166
 
128
167
  return obj;
129
168
  }
130
169
 
170
+ _selectAfterUpdate() {
171
+ const returnSelect = `; SELECT ${
172
+ this.single.returning
173
+ ? // @ts-ignore
174
+ this.formatter.columnize(this.single.returning)
175
+ : "*"
176
+ } from ${this.tableName} `;
177
+ let whereStatement = [this.where()];
178
+ console.log({ whereStatement });
179
+ for (const [key, value] of Object.entries(this.single.update)) {
180
+ whereStatement.push(`WHERE ${key} = ${value}`);
181
+ }
182
+ return returnSelect + whereStatement.join(" and ");
183
+ }
184
+
131
185
  transaction(container: any, config: any, outerTx: any): Knex.Transaction {
132
186
  // @ts-ignore
133
187
  return new Transaction(this, ...arguments);
@@ -174,6 +228,7 @@ class DB2Client extends knex.Client {
174
228
  case "del":
175
229
  case "delete":
176
230
  case "update":
231
+ return rows;
177
232
  case "counter":
178
233
  return resp.rowCount;
179
234
  default:
@@ -2,8 +2,80 @@ import QueryCompiler from "knex/lib/query/querycompiler";
2
2
  import isObject from "lodash/isObject";
3
3
  import { rawOrFn as rawOrFn_ } from "knex/lib/formatter/wrappingFormatter";
4
4
  import { format } from "date-fns";
5
+ import * as console from "console";
6
+ import isEmpty from "lodash/isEmpty";
5
7
 
6
8
  class IBMiQueryCompiler extends QueryCompiler {
9
+ insert() {
10
+ // @ts-ignore
11
+ const insertValues = this.single.insert || [];
12
+ // we need to return a value
13
+ // we need to wrap the insert statement in a select statement
14
+ // we use the "IDENTITY_VAL_LOCAL()" function to return the IDENTITY
15
+ // unless specified in a return
16
+ // @ts-ignore
17
+ let sql = `SELECT ${
18
+ // @ts-ignore
19
+ this.single.returning
20
+ ? // @ts-ignore
21
+ this.formatter.columnize(this.single.returning)
22
+ : "IDENTITY_VAL_LOCAL()"
23
+ } FROM FINAL TABLE(`;
24
+ // @ts-ignore
25
+ sql += this.with() + `insert into ${this.tableName} `;
26
+ // @ts-ignore
27
+ const { returning } = this.single;
28
+ const returningSql = returning
29
+ ? // @ts-ignore
30
+ this._returning("insert", returning) + " "
31
+ : "";
32
+
33
+ if (Array.isArray(insertValues)) {
34
+ if (insertValues.length === 0) {
35
+ return "";
36
+ }
37
+ } else if (typeof insertValues === "object" && isEmpty(insertValues)) {
38
+ return {
39
+ // @ts-ignore
40
+ sql: sql + returningSql + this._emptyInsertValue,
41
+ returning,
42
+ };
43
+ }
44
+
45
+ // @ts-ignore
46
+ sql += this._buildInsertData(insertValues, returningSql);
47
+ sql += ")";
48
+
49
+ return {
50
+ sql,
51
+ returning,
52
+ };
53
+ }
54
+
55
+ _buildInsertData(insertValues, returningSql) {
56
+ let sql = "";
57
+ const insertData = this._prepInsert(insertValues);
58
+ if (typeof insertData === "string") {
59
+ sql += insertData;
60
+ } else {
61
+ if (insertData.columns.length) {
62
+ // @ts-ignore
63
+ sql += `(${this.formatter.columnize(insertData.columns)}`;
64
+ sql +=
65
+ `) ${returningSql}values (` +
66
+ // @ts-ignore
67
+ this._buildInsertValues(insertData) +
68
+ ")";
69
+ } else if (insertValues.length === 1 && insertValues[0]) {
70
+ // @ts-ignore
71
+ sql += returningSql + this._emptyInsertValue;
72
+ } else {
73
+ return "";
74
+ }
75
+ }
76
+ return sql;
77
+ }
78
+
7
79
  _prepInsert(data) {
8
80
  if (isObject(data)) {
9
81
  if (data.hasOwnProperty("migration_time")) {
@@ -54,6 +126,36 @@ class IBMiQueryCompiler extends QueryCompiler {
54
126
  values,
55
127
  };
56
128
  }
129
+
130
+ _returning(method, value, withTrigger) {
131
+ // currently a placeholder in case I need to update return values
132
+ switch (method) {
133
+ case "update":
134
+ case "insert":
135
+ return value
136
+ ? // @ts-ignore
137
+ `${withTrigger ? " into #out" : ""}`
138
+ : "";
139
+ case "del":
140
+ return value
141
+ ? // @ts-ignore
142
+ `${withTrigger ? " into #out" : ""}`
143
+ : "";
144
+ case "rowcount":
145
+ return value ? ";select @@rowcount" : "";
146
+ }
147
+ }
148
+
149
+ columnizeWithPrefix(prefix, target) {
150
+ const columns = typeof target === "string" ? [target] : target;
151
+ let str = "",
152
+ i = -1;
153
+ while (++i < columns.length) {
154
+ if (i > 0) str += ", ";
155
+ str += prefix + this.wrap(columns[i]);
156
+ }
157
+ return str;
158
+ }
57
159
  }
58
160
 
59
161
  export default IBMiQueryCompiler;