@bdkinc/knex-ibmi 0.0.2 → 0.0.4

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
@@ -1,129 +1,197 @@
1
- import * as process from "process";
2
- import { Connection } from "odbc";
3
- import knex from "knex";
4
- import * as odbc from "odbc";
5
- import * as console from "console";
6
-
7
- class DB2Client extends knex.Client {
8
- constructor(config) {
9
- super(config);
10
- this.driverName = "odbc";
11
-
12
- if (this.driverName && config.connection) {
13
- this.initializeDriver();
14
- if (!config.pool || (config.pool && config.pool.max !== 0)) {
15
- this.initializePool(config);
16
- }
17
- }
18
- }
19
-
20
- _driver() {
21
- return odbc;
22
- }
23
-
24
- wrapIdentifierImpl(value: any) {
25
- // override default wrapper ("). we don't want to use it since
26
- // it makes identifiers case-sensitive in DB2
27
- return value;
28
- }
29
-
30
- printDebug(message: string) {
31
- if (process.env.DEBUG === "true") {
32
- // @ts-ignore
33
- this.logger.debug(message);
34
- }
35
- }
36
-
37
- // Get a raw connection, called by the pool manager whenever a new
38
- // connection needs to be added to the pool.
39
- async acquireRawConnection() {
40
- this.printDebug("acquiring raw connection");
41
- const connectionConfig = this.config.connection;
42
- return await this.driver.connect(
43
- this._getConnectionString(connectionConfig),
44
- );
45
- }
46
-
47
- // Used to explicitly close a connection, called internally by the pool manager
48
- // when a connection times out or the pool is shutdown.
49
- async destroyRawConnection(connection: Connection) {
50
- return await connection.close();
51
- }
52
-
53
- _getConnectionString(connectionConfig) {
54
- const connectionStringParams =
55
- connectionConfig.connectionStringParams || {};
56
- const connectionStringExtension = Object.keys(
57
- connectionStringParams,
58
- ).reduce((result, key) => {
59
- const value = connectionStringParams[key];
60
- return `${result}${key}=${value};`;
61
- }, "");
62
-
63
- return `${
64
- `DRIVER=${connectionConfig.driver};SYSTEM=${connectionConfig.host};HOSTNAME=${connectionConfig.host};` +
65
- `PORT=${connectionConfig.port};DATABASE=${connectionConfig.database};` +
66
- `UID=${connectionConfig.user};PWD=${connectionConfig.password};`
67
- }${connectionStringExtension}`;
68
- }
69
-
70
- // Runs the query on the specified connection, providing the bindings
71
- // and any other necessary prep work.
72
- async _query(connection: Connection, obj: any) {
73
- console.log({ obj });
74
- // TODO: verify correctness
75
- if (!obj || typeof obj == "string") obj = { sql: obj };
76
- const method = (
77
- obj.method !== "raw" ? obj.method : obj.sql.split(" ")[0]
78
- ).toLowerCase();
79
- obj.sqlMethod = method;
80
-
81
- // Different functions are used since query() doesn't return # of rows affected,
82
- // which is needed for queries that modify the database
83
- if (method === "select" || method === "first" || method === "pluck") {
84
- const rows: any = await connection.query(obj.sql, obj.bindings);
85
- if (rows) {
86
- obj.response = { rows, rowCount: rows.length };
87
- }
88
- return obj;
89
- }
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();
95
-
96
- return obj;
97
- }
98
-
99
- processResponse(obj: any, runner: any) {
100
- // TODO: verify correctness
101
- if (obj === null) return null;
102
-
103
- const resp = obj.response;
104
- const method = obj.sqlMethod;
105
- const { rows } = resp;
106
-
107
- if (obj.output) return obj.output.call(runner, resp);
108
-
109
- switch (method) {
110
- case "select":
111
- case "pluck":
112
- case "first": {
113
- if (method === "pluck") return rows.map(obj.pluck);
114
- return method === "first" ? rows[0] : rows;
115
- }
116
- case "insert":
117
- case "del":
118
- case "delete":
119
- case "update":
120
- case "counter":
121
- return resp.rowCount;
122
- default:
123
- return resp;
124
- }
125
- }
126
- }
127
-
128
- export const DB2Dialect = DB2Client;
129
- export default DB2Client;
1
+ import * as process from "process";
2
+ import { Connection } from "odbc";
3
+ import knex, { Knex } from "knex";
4
+ import * as odbc from "odbc";
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";
11
+
12
+ class DB2Client extends knex.Client {
13
+ constructor(config) {
14
+ super(config);
15
+
16
+ this.driverName = "odbc";
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
+
36
+ if (this.driverName && config.connection) {
37
+ this.initializeDriver();
38
+ if (!config.pool || (config.pool && config.pool.max !== 0)) {
39
+ this.initializePool(config);
40
+ }
41
+ }
42
+ this.valueForUndefined = this.raw("DEFAULT");
43
+ if (config.useNullAsDefault) {
44
+ this.valueForUndefined = null;
45
+ }
46
+ }
47
+
48
+ _driver() {
49
+ return odbc;
50
+ }
51
+
52
+ wrapIdentifierImpl(value: any) {
53
+ // override default wrapper ("). we don't want to use it since
54
+ // it makes identifiers case-sensitive in DB2
55
+ if (value.includes("knex_migrations")) {
56
+ return value.toUpperCase();
57
+ }
58
+ return value;
59
+ }
60
+
61
+ printDebug(message: string) {
62
+ if (process.env.DEBUG === "true") {
63
+ // @ts-ignore
64
+ this.logger.debug(message);
65
+ }
66
+ }
67
+
68
+ // Get a raw connection, called by the pool manager whenever a new
69
+ // connection needs to be added to the pool.
70
+ async acquireRawConnection() {
71
+ this.printDebug("acquiring raw connection");
72
+ const connectionConfig = this.config.connection;
73
+ console.log(this._getConnectionString(connectionConfig));
74
+ return await this.driver.pool(this._getConnectionString(connectionConfig));
75
+ }
76
+
77
+ // Used to explicitly close a connection, called internally by the pool manager
78
+ // when a connection times out or the pool is shutdown.
79
+ async destroyRawConnection(connection: Connection) {
80
+ console.log("destroy connection");
81
+ return await connection.close();
82
+ }
83
+
84
+ _getConnectionString(connectionConfig) {
85
+ const connectionStringParams =
86
+ connectionConfig.connectionStringParams || {};
87
+ const connectionStringExtension = Object.keys(
88
+ connectionStringParams,
89
+ ).reduce((result, key) => {
90
+ const value = connectionStringParams[key];
91
+ return `${result}${key}=${value};`;
92
+ }, "");
93
+
94
+ return `${
95
+ `DRIVER=${connectionConfig.driver};SYSTEM=${connectionConfig.host};HOSTNAME=${connectionConfig.host};` +
96
+ `PORT=${connectionConfig.port};DATABASE=${connectionConfig.database};` +
97
+ `UID=${connectionConfig.user};PWD=${connectionConfig.password};`
98
+ }${connectionStringExtension}`;
99
+ }
100
+
101
+ // Runs the query on the specified connection, providing the bindings
102
+ // and any other necessary prep work.
103
+ async _query(pool: any, obj: any) {
104
+ // @ts-ignore
105
+ // TODO: verify correctness
106
+ if (!obj || typeof obj == "string") obj = { sql: obj };
107
+ const method = (
108
+ obj.hasOwnProperty("method") && obj.method !== "raw"
109
+ ? obj.method
110
+ : obj.sql.split(" ")[0]
111
+ ).toLowerCase();
112
+ obj.sqlMethod = method;
113
+
114
+ // Different functions are used since query() doesn't return # of rows affected,
115
+ // which is needed for queries that modify the database
116
+
117
+ if (method === "select" || method === "first" || method === "pluck") {
118
+ const rows: any = await pool.query(obj.sql, obj.bindings);
119
+ if (rows) {
120
+ obj.response = { rows, rowCount: rows.length };
121
+ }
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
+ }
136
+ }
137
+ console.log({ obj });
138
+
139
+ return obj;
140
+ }
141
+
142
+ transaction(container: any, config: any, outerTx: any): Knex.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
+
167
+ processResponse(obj: any, runner: any) {
168
+ // TODO: verify correctness
169
+ if (obj === null) return null;
170
+
171
+ const resp = obj.response;
172
+ const method = obj.sqlMethod;
173
+ const { rows } = resp;
174
+
175
+ if (obj.output) return obj.output.call(runner, resp);
176
+
177
+ switch (method) {
178
+ case "select":
179
+ case "pluck":
180
+ case "first": {
181
+ if (method === "pluck") return rows.map(obj.pluck);
182
+ return method === "first" ? rows[0] : rows;
183
+ }
184
+ case "insert":
185
+ case "del":
186
+ case "delete":
187
+ case "update":
188
+ case "counter":
189
+ return resp.rowCount;
190
+ default:
191
+ return resp;
192
+ }
193
+ }
194
+ }
195
+
196
+ export const DB2Dialect = DB2Client;
197
+ export default DB2Client;
@@ -0,0 +1,59 @@
1
+ import QueryCompiler from "knex/lib/query/querycompiler";
2
+ import isObject from "lodash/isObject";
3
+ import { rawOrFn as rawOrFn_ } from "knex/lib/formatter/wrappingFormatter";
4
+ import { format } from "date-fns";
5
+
6
+ class IBMiQueryCompiler extends QueryCompiler {
7
+ _prepInsert(data) {
8
+ if (isObject(data)) {
9
+ if (data.hasOwnProperty("migration_time")) {
10
+ const parsed = new Date(data.migration_time);
11
+ data.migration_time = format(parsed, "yyyy-MM-dd HH:mm:ss");
12
+ }
13
+ }
14
+
15
+ const isRaw = rawOrFn_(
16
+ data,
17
+ undefined,
18
+ // @ts-ignore
19
+ this.builder,
20
+ // @ts-ignore
21
+ this.client,
22
+ // @ts-ignore
23
+ this.bindingsHolder,
24
+ );
25
+ if (isRaw) return isRaw;
26
+ let columns: any[] = [];
27
+ const values: any[] = [];
28
+ if (!Array.isArray(data)) data = data ? [data] : [];
29
+ let i = -1;
30
+ while (++i < data.length) {
31
+ if (data[i] == null) break;
32
+ if (i === 0) columns = Object.keys(data[i]).sort();
33
+ const row = new Array(columns.length);
34
+ const keys = Object.keys(data[i]);
35
+ let j = -1;
36
+ while (++j < keys.length) {
37
+ const key = keys[j];
38
+ let idx = columns.indexOf(key);
39
+ if (idx === -1) {
40
+ columns = columns.concat(key).sort();
41
+ idx = columns.indexOf(key);
42
+ let k = -1;
43
+ while (++k < values.length) {
44
+ values[k].splice(idx, 0, undefined);
45
+ }
46
+ row.splice(idx, 0, undefined);
47
+ }
48
+ row[idx] = data[i][key];
49
+ }
50
+ values.push(row);
51
+ }
52
+ return {
53
+ columns,
54
+ values,
55
+ };
56
+ }
57
+ }
58
+
59
+ export default IBMiQueryCompiler;
@@ -0,0 +1,13 @@
1
+ import ColumnCompiler from "knex/lib/schema/columncompiler";
2
+
3
+ class IBMiColumnCompiler extends ColumnCompiler {
4
+ increments(options = { primaryKey: true }) {
5
+ return (
6
+ "int not null generated always as identity (start with 1, increment by 1)" +
7
+ // @ts-ignore
8
+ (this.tableCompiler._canBeAddPrimaryKey(options) ? " primary key" : "")
9
+ );
10
+ }
11
+ }
12
+
13
+ export default IBMiColumnCompiler;
@@ -0,0 +1,57 @@
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
+ // @ts-ignore
11
+ const formattedTable = this.client.parameter(
12
+ // @ts-ignore
13
+ prefixedTableName(this.schema, tableName),
14
+ // @ts-ignore
15
+ this.builder,
16
+ // @ts-ignore
17
+ this.bindingsHolder,
18
+ );
19
+ const bindings = [tableName.toUpperCase()];
20
+ let sql =
21
+ `SELECT TABLE_NAME FROM QSYS2.SYSTABLES ` +
22
+ `WHERE TYPE = 'T' AND TABLE_NAME = ${formattedTable}`;
23
+ // @ts-ignore
24
+ if (this.schema) {
25
+ sql += " AND TABLE_SCHEMA = ?";
26
+ // @ts-ignore
27
+ bindings.push(this.schema);
28
+ }
29
+
30
+ // @ts-ignore
31
+ this.pushQuery({
32
+ sql,
33
+ bindings,
34
+ output: (resp) => {
35
+ return resp.rowCount > 0;
36
+ },
37
+ });
38
+ }
39
+
40
+ toSQL() {
41
+ // @ts-ignore
42
+ const sequence = this.builder._sequence;
43
+ for (let i = 0, l = sequence.length; i < l; i++) {
44
+ const query = sequence[i];
45
+ console.log(query.method, query);
46
+ this[query.method].apply(this, query.args);
47
+ }
48
+ // @ts-ignore
49
+ return this.sequence;
50
+ }
51
+ }
52
+
53
+ function prefixedTableName(prefix, table) {
54
+ return prefix ? `${prefix}.${table}` : table;
55
+ }
56
+
57
+ export default IBMiSchemaCompiler;
@@ -0,0 +1,70 @@
1
+ import TableCompiler from "knex/lib/schema/tablecompiler";
2
+
3
+ class IBMiTableCompiler extends TableCompiler {
4
+ createQuery(columns, ifNot, like) {
5
+ let createStatement = ifNot
6
+ // @ts-ignore
7
+ ? `if object_id('${this.tableName()}', 'U') is null `
8
+ : "";
9
+
10
+ if (like) {
11
+ // This query copy only columns and not all indexes and keys like other databases.
12
+ // @ts-ignore
13
+ createStatement += `SELECT * INTO ${this.tableName()} FROM ${this.tableNameLike()} WHERE 0=1`;
14
+ } else {
15
+ createStatement +=
16
+ "CREATE TABLE " +
17
+ // @ts-ignore
18
+ this.tableName() +
19
+ // @ts-ignore
20
+ (this._formatting ? " (\n " : " (") +
21
+ // @ts-ignore
22
+ columns.sql.join(this._formatting ? ",\n " : ", ") +
23
+ // @ts-ignore
24
+ this._addChecks() +
25
+ ")";
26
+ }
27
+
28
+ // @ts-ignore
29
+ this.pushQuery(createStatement);
30
+
31
+ // @ts-ignore
32
+ if (this.single.comment) {
33
+ // @ts-ignore
34
+ this.comment(this.single.comment);
35
+ }
36
+ if (like) {
37
+ // @ts-ignore
38
+ this.addColumns(columns, this.addColumnsPrefix);
39
+ }
40
+ }
41
+
42
+ // All of the columns to "add" for the query
43
+ addColumns(columns, prefix) {
44
+ // @ts-ignore
45
+ prefix = prefix || this.addColumnsPrefix;
46
+
47
+ if (columns.sql.length > 0) {
48
+ const columnSql = columns.sql.map((column) => {
49
+ return prefix + column;
50
+ });
51
+ // @ts-ignore
52
+ this.pushQuery({
53
+ sql:
54
+ // @ts-ignore
55
+ (this.lowerCase ? 'alter table ' : 'ALTER TABLE ') +
56
+ // @ts-ignore
57
+ this.tableName() +
58
+ ' ' +
59
+ columnSql.join(' '),
60
+ bindings: columns.bindings,
61
+ });
62
+ }
63
+ }
64
+
65
+ async commit(conn, value) {
66
+ return await conn.commit();
67
+ }
68
+ }
69
+
70
+ export default IBMiTableCompiler