@bdkinc/knex-ibmi 0.0.2 → 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
@@ -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 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() {
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,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