@bdkinc/knex-ibmi 0.0.1 → 0.0.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/src/index.ts CHANGED
@@ -3,18 +3,46 @@ import { Connection } from "odbc";
3
3
  import knex from "knex";
4
4
  import * as odbc from "odbc";
5
5
  import * as console from "console";
6
+ import SchemaCompiler from "./schema/ibmi-compiler";
7
+ import TableCompiler from "./schema/ibmi-tablecompiler";
8
+ import ColumnCompiler from "./schema/ibmi-columncompiler";
9
+ import Transaction from "./execution/ibmi-transaction";
10
+ import QueryCompiler from "./query/ibmi-querycompiler";
6
11
 
7
12
  class DB2Client extends knex.Client {
8
13
  constructor(config) {
9
14
  super(config);
15
+
10
16
  this.driverName = "odbc";
11
17
 
18
+ if (this.dialect && !this.config.client) {
19
+ // @ts-ignore
20
+ this.logger.warn(
21
+ `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.`,
22
+ );
23
+ }
24
+
25
+ const dbClient = this.config.client || this.dialect;
26
+ if (!dbClient) {
27
+ throw new Error(
28
+ `knex: Required configuration option 'client' is missing.`,
29
+ );
30
+ }
31
+
32
+ if (config.version) {
33
+ this.version = config.version;
34
+ }
35
+
12
36
  if (this.driverName && config.connection) {
13
37
  this.initializeDriver();
14
38
  if (!config.pool || (config.pool && config.pool.max !== 0)) {
15
39
  this.initializePool(config);
16
40
  }
17
41
  }
42
+ this.valueForUndefined = this.raw("DEFAULT");
43
+ if (config.useNullAsDefault) {
44
+ this.valueForUndefined = null;
45
+ }
18
46
  }
19
47
 
20
48
  _driver() {
@@ -24,6 +52,9 @@ class DB2Client extends knex.Client {
24
52
  wrapIdentifierImpl(value: any) {
25
53
  // override default wrapper ("). we don't want to use it since
26
54
  // it makes identifiers case-sensitive in DB2
55
+ if (value.includes("knex_migrations")) {
56
+ return value.toUpperCase();
57
+ }
27
58
  return value;
28
59
  }
29
60
 
@@ -39,14 +70,14 @@ class DB2Client extends knex.Client {
39
70
  async acquireRawConnection() {
40
71
  this.printDebug("acquiring raw connection");
41
72
  const connectionConfig = this.config.connection;
42
- return await this.driver.connect(
43
- this._getConnectionString(connectionConfig),
44
- );
73
+ console.log(this._getConnectionString(connectionConfig));
74
+ return await this.driver.pool(this._getConnectionString(connectionConfig));
45
75
  }
46
76
 
47
77
  // Used to explicitly close a connection, called internally by the pool manager
48
78
  // when a connection times out or the pool is shutdown.
49
79
  async destroyRawConnection(connection: Connection) {
80
+ console.log("destroy connection");
50
81
  return await connection.close();
51
82
  }
52
83
 
@@ -69,33 +100,70 @@ class DB2Client extends knex.Client {
69
100
 
70
101
  // Runs the query on the specified connection, providing the bindings
71
102
  // and any other necessary prep work.
72
- async _query(connection: Connection, obj: any) {
73
- console.log({ obj });
103
+ async _query(pool: any, obj: any) {
104
+ // @ts-ignore
74
105
  // TODO: verify correctness
75
106
  if (!obj || typeof obj == "string") obj = { sql: obj };
76
107
  const method = (
77
- obj.method !== "raw" ? obj.method : obj.sql.split(" ")[0]
108
+ obj.hasOwnProperty("method") && obj.method !== "raw"
109
+ ? obj.method
110
+ : obj.sql.split(" ")[0]
78
111
  ).toLowerCase();
79
112
  obj.sqlMethod = method;
80
113
 
81
114
  // Different functions are used since query() doesn't return # of rows affected,
82
115
  // which is needed for queries that modify the database
116
+
83
117
  if (method === "select" || method === "first" || method === "pluck") {
84
- const rows: any = await connection.query(obj.sql, obj.bindings);
118
+ const rows: any = await pool.query(obj.sql, obj.bindings);
85
119
  if (rows) {
86
120
  obj.response = { rows, rowCount: rows.length };
87
121
  }
88
- return obj;
122
+ } else {
123
+ try {
124
+ const connection = await pool.connect();
125
+ const statement = await connection.createStatement();
126
+ await statement.prepare(obj.sql);
127
+ if (obj.bindings) {
128
+ await statement.bind(obj.bindings);
129
+ }
130
+ const result = await statement.execute();
131
+ obj.response = { rows: [result.count], rowCount: result.count };
132
+ } catch (err: any) {
133
+ console.error(err);
134
+ throw new Error(err);
135
+ }
89
136
  }
90
-
91
- const statement = await connection.createStatement();
92
- await statement.prepare(obj.sql);
93
- await statement.bind(obj.bindings);
94
- obj.response = await statement.execute();
137
+ console.log({ obj });
95
138
 
96
139
  return obj;
97
140
  }
98
141
 
142
+ transaction() {
143
+ // @ts-ignore
144
+ return new Transaction(this, ...arguments);
145
+ }
146
+
147
+ schemaCompiler() {
148
+ // @ts-ignore
149
+ return new SchemaCompiler(this, ...arguments);
150
+ }
151
+
152
+ tableCompiler() {
153
+ // @ts-ignore
154
+ return new TableCompiler(this, ...arguments);
155
+ }
156
+
157
+ columnCompiler() {
158
+ // @ts-ignore
159
+ return new ColumnCompiler(this, ...arguments);
160
+ }
161
+
162
+ queryCompiler() {
163
+ // @ts-ignore
164
+ return new QueryCompiler(this, ...arguments);
165
+ }
166
+
99
167
  processResponse(obj: any, runner: any) {
100
168
  // TODO: verify correctness
101
169
  if (obj === null) return null;
@@ -0,0 +1,132 @@
1
+ import QueryCompiler from "knex/lib/query/querycompiler";
2
+ import has from "lodash/has";
3
+ import isEmpty from "lodash/isEmpty";
4
+ import omitBy from "lodash/omitBy";
5
+ import isObject from "lodash/isObject";
6
+ import {
7
+ wrap as wrap_,
8
+ rawOrFn as rawOrFn_,
9
+ } from "knex/lib/formatter/wrappingFormatter";
10
+ import { format, parseISO } from "date-fns";
11
+ import * as console from "console";
12
+
13
+ class IBMiQueryCompiler extends QueryCompiler {
14
+ _prepInsert(data) {
15
+ if (isObject(data)) {
16
+ console.log("data is object", data);
17
+ if (data.hasOwnProperty("migration_time")) {
18
+ console.log("data has migration_time", data.migration_time);
19
+ const parsed = new Date(data.migration_time);
20
+ console.log(parsed);
21
+ data.migration_time = format(parsed, "yyyy-MM-dd HH:mm:ss");
22
+ console.log(data.migration_time);
23
+ }
24
+ console.log("data date after change", data);
25
+ }
26
+ const isRaw = rawOrFn_(
27
+ data,
28
+ undefined,
29
+ this.builder,
30
+ this.client,
31
+ this.bindingsHolder,
32
+ );
33
+ if (isRaw) return isRaw;
34
+ let columns: any[] = [];
35
+ const values: any[] = [];
36
+ if (!Array.isArray(data)) data = data ? [data] : [];
37
+ let i = -1;
38
+ while (++i < data.length) {
39
+ if (data[i] == null) break;
40
+ if (i === 0) columns = Object.keys(data[i]).sort();
41
+ const row = new Array(columns.length);
42
+ const keys = Object.keys(data[i]);
43
+ let j = -1;
44
+ while (++j < keys.length) {
45
+ const key = keys[j];
46
+ let idx = columns.indexOf(key);
47
+ if (idx === -1) {
48
+ columns = columns.concat(key).sort();
49
+ idx = columns.indexOf(key);
50
+ let k = -1;
51
+ while (++k < values.length) {
52
+ values[k].splice(idx, 0, undefined);
53
+ }
54
+ row.splice(idx, 0, undefined);
55
+ }
56
+ row[idx] = data[i][key];
57
+ }
58
+ values.push(row);
59
+ }
60
+ return {
61
+ columns,
62
+ values,
63
+ };
64
+ }
65
+
66
+ _prepUpdate(data = {}): any[] {
67
+ const { counter = {} } = this.single;
68
+
69
+ for (const column of Object.keys(counter)) {
70
+ //Skip?
71
+ if (has(data, column)) {
72
+ //Needed?
73
+ this.client.logger.warn(
74
+ `increment/decrement called for a column that has already been specified in main .update() call. Ignoring increment/decrement and using value from .update() call.`,
75
+ );
76
+ continue;
77
+ }
78
+
79
+ let value = counter[column];
80
+
81
+ const symbol = value < 0 ? "-" : "+";
82
+
83
+ if (symbol === "-") {
84
+ value = -value;
85
+ }
86
+
87
+ data[column] = this.client.raw(`?? ${symbol} ?`, [column, value]);
88
+ }
89
+
90
+ data = omitBy(data, (value) => typeof value === "undefined");
91
+
92
+ const vals = [];
93
+ const columns = Object.keys(data);
94
+ let i = -1;
95
+
96
+ while (++i < columns.length) {
97
+ vals.push(
98
+ wrap_(
99
+ columns[i],
100
+ undefined,
101
+ this.builder,
102
+ this.client,
103
+ this.bindingsHolder,
104
+ ) +
105
+ " = " +
106
+ this.client.parameter(
107
+ data[columns[i]],
108
+ this.builder,
109
+ this.bindingsHolder,
110
+ ),
111
+ );
112
+ }
113
+
114
+ if (isEmpty(vals)) {
115
+ throw new Error(
116
+ [
117
+ "Empty .update() call detected!",
118
+ "Update data does not contain any values to update.",
119
+ "This will result in a faulty query.",
120
+ this.single.table ? `Table: ${this.single.table}.` : "",
121
+ this.single.update
122
+ ? `Columns: ${Object.keys(this.single.update)}.`
123
+ : "",
124
+ ].join(" "),
125
+ );
126
+ }
127
+
128
+ return vals;
129
+ }
130
+ }
131
+
132
+ export default IBMiQueryCompiler;
@@ -0,0 +1,34 @@
1
+ import ColumnCompiler from "knex/lib/schema/columncompiler";
2
+ import * as console from "console";
3
+
4
+ class IBMiColumnCompiler extends ColumnCompiler {
5
+ constructor(client, tableCompiler, columnBuilder) {
6
+ super(client, tableCompiler, columnBuilder);
7
+ }
8
+
9
+ increments(options = { primaryKey: true }) {
10
+ return (
11
+ "int not null generated always as identity (start with 1, increment by 1)" +
12
+ (this.tableCompiler._canBeAddPrimaryKey(options) ? " primary key" : "")
13
+ );
14
+ }
15
+
16
+ datetime(withoutTz = false, precision) {
17
+ let useTz;
18
+ if (isObject(withoutTz)) {
19
+ ({ useTz, precision } = withoutTz);
20
+ } else {
21
+ useTz = !withoutTz;
22
+ }
23
+ useTz = typeof useTz === "boolean" ? useTz : true;
24
+ precision =
25
+ precision !== undefined && precision !== null
26
+ ? "(" + precision + ")"
27
+ : "";
28
+
29
+ console.log(useTz, precision);
30
+ return `${useTz ? "timestamptz" : "timestamp"}${precision}`;
31
+ }
32
+ }
33
+
34
+ export default IBMiColumnCompiler;
@@ -0,0 +1,50 @@
1
+ import SchemaCompiler from "knex/lib/schema/compiler";
2
+ import * as console from "console";
3
+
4
+ class IBMiSchemaCompiler extends SchemaCompiler {
5
+ constructor(client, builder) {
6
+ super(client, builder);
7
+ }
8
+
9
+ hasTable(tableName) {
10
+ const formattedTable = this.client.parameter(
11
+ prefixedTableName(this.schema, tableName),
12
+ this.builder,
13
+ this.bindingsHolder,
14
+ );
15
+ const bindings = [tableName.toUpperCase()];
16
+ let sql =
17
+ `SELECT TABLE_NAME FROM QSYS2.SYSTABLES ` +
18
+ `WHERE TYPE = 'T' AND TABLE_NAME = ${formattedTable}`;
19
+ if (this.schema) {
20
+ sql += " AND TABLE_SCHEMA = ?";
21
+ bindings.push(this.schema);
22
+ }
23
+
24
+ this.pushQuery({
25
+ sql,
26
+ bindings,
27
+ output: (resp) => {
28
+ return resp.rowCount > 0;
29
+ },
30
+ });
31
+ }
32
+
33
+ toSQL() {
34
+ // @ts-ignore
35
+ const sequence = this.builder._sequence;
36
+ for (let i = 0, l = sequence.length; i < l; i++) {
37
+ const query = sequence[i];
38
+ console.log(query.method, query);
39
+ this[query.method].apply(this, query.args);
40
+ }
41
+ // @ts-ignore
42
+ return this.sequence;
43
+ }
44
+ }
45
+
46
+ function prefixedTableName(prefix, table) {
47
+ return prefix ? `${prefix}.${table}` : table;
48
+ }
49
+
50
+ export default IBMiSchemaCompiler;
@@ -0,0 +1,109 @@
1
+ import TableCompiler from "knex/lib/schema/tablecompiler";
2
+
3
+ class IBMiTableCompiler extends TableCompiler {
4
+ constructor(client, tableBuilder) {
5
+ super(client, tableBuilder);
6
+ }
7
+
8
+ unique(columns, indexName) {
9
+ /** @type {string | undefined} */
10
+ let deferrable;
11
+ let useConstraint = false;
12
+ let predicate;
13
+ if (typeof indexName === "object") {
14
+ ({ indexName, deferrable, useConstraint, predicate } = indexName);
15
+ }
16
+ if (deferrable && deferrable !== 'not deferrable') {
17
+ this.client.logger.warn(
18
+ `ibmi: unique index [${indexName}] will not be deferrable ${deferrable} because mssql does not support deferred constraints.`
19
+ );
20
+ }
21
+ if (useConstraint && predicate) {
22
+ throw new Error('ibmi cannot create constraint with predicate');
23
+ }
24
+ indexName = indexName
25
+ ? this.formatter.wrap(indexName)
26
+ : this._indexCommand('unique', this.tableNameRaw, columns);
27
+
28
+ if (!Array.isArray(columns)) {
29
+ columns = [columns];
30
+ }
31
+
32
+ if (useConstraint) {
33
+ // mssql supports unique indexes and unique constraints.
34
+ // unique indexes cannot be used with foreign key relationships hence unique constraints are used instead.
35
+ this.pushQuery(
36
+ `ALTER TABLE ${this.tableName()} ADD CONSTRAINT ${indexName} UNIQUE (${this.formatter.columnize(
37
+ columns
38
+ )})`
39
+ );
40
+ } else {
41
+ // default to making unique index that allows null https://stackoverflow.com/a/767702/360060
42
+ // to be more or less compatible with other DBs (if any of the columns is NULL then "duplicates" are allowed)
43
+ const predicateQuery = predicate
44
+ ? ' ' + this.client.queryCompiler(predicate).where()
45
+ : ' WHERE ' +
46
+ columns
47
+ .map((column) => this.formatter.columnize(column) + ' IS NOT NULL')
48
+ .join(' AND ');
49
+ this.pushQuery(
50
+ `CREATE UNIQUE INDEX ${indexName} ON ${this.tableName()} (${this.formatter.columnize(
51
+ columns
52
+ )})${predicateQuery}`
53
+ );
54
+ }
55
+ }
56
+
57
+ createQuery(columns, ifNot, like) {
58
+ let createStatement = ifNot
59
+ ? `if object_id('${this.tableName()}', 'U') is null `
60
+ : "";
61
+
62
+ if (like) {
63
+ // This query copy only columns and not all indexes and keys like other databases.
64
+ createStatement += `SELECT * INTO ${this.tableName()} FROM ${this.tableNameLike()} WHERE 0=1`;
65
+ } else {
66
+ createStatement +=
67
+ "CREATE TABLE " +
68
+ this.tableName() +
69
+ (this._formatting ? " (\n " : " (") +
70
+ columns.sql.join(this._formatting ? ",\n " : ", ") +
71
+ this._addChecks() +
72
+ ")";
73
+ }
74
+
75
+ this.pushQuery(createStatement);
76
+
77
+ if (this.single.comment) {
78
+ this.comment(this.single.comment);
79
+ }
80
+ if (like) {
81
+ this.addColumns(columns, this.addColumnsPrefix);
82
+ }
83
+ }
84
+
85
+ // All of the columns to "add" for the query
86
+ addColumns(columns, prefix) {
87
+ prefix = prefix || this.addColumnsPrefix;
88
+
89
+ if (columns.sql.length > 0) {
90
+ const columnSql = columns.sql.map((column) => {
91
+ return prefix + column;
92
+ });
93
+ this.pushQuery({
94
+ sql:
95
+ (this.lowerCase ? 'alter table ' : 'ALTER TABLE ') +
96
+ this.tableName() +
97
+ ' ' +
98
+ columnSql.join(' '),
99
+ bindings: columns.bindings,
100
+ });
101
+ }
102
+ }
103
+
104
+ async commit(conn, value) {
105
+ return await conn.commit();
106
+ }
107
+ }
108
+
109
+ export default IBMiTableCompiler