@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/README.md +22 -11
- package/dist/index.js +371 -138
- package/dist/index.mjs +336 -103
- package/package.json +3 -1
- package/src/execution/ibmi-transaction.ts +24 -0
- package/src/index.ts +197 -129
- package/src/query/ibmi-querycompiler.ts +59 -0
- package/src/schema/ibmi-columncompiler.ts +13 -0
- package/src/schema/ibmi-compiler.ts +57 -0
- package/src/schema/ibmi-tablecompiler.ts +70 -0
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
console.log(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|