@oino-ts/db-mariadb 0.16.2 → 0.17.1
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/dist/cjs/OINODbMariadb.js +15 -15
- package/dist/esm/OINODbMariadb.js +15 -15
- package/package.json +38 -38
- package/src/OINODbMariadb.ts +465 -465
- package/src/index.ts +1 -1
|
@@ -344,25 +344,25 @@ class OINODbMariadb extends db_1.OINODb {
|
|
|
344
344
|
return result;
|
|
345
345
|
}
|
|
346
346
|
_getSchemaSql(dbName, tableName) {
|
|
347
|
-
const sql = `SELECT
|
|
348
|
-
c.COLUMN_NAME,
|
|
349
|
-
c.COLUMN_TYPE,
|
|
350
|
-
c.IS_NULLABLE,
|
|
351
|
-
c.COLUMN_KEY,
|
|
352
|
-
c.COLUMN_DEFAULT,
|
|
353
|
-
c.EXTRA,
|
|
354
|
-
KCU.CONSTRAINT_NAME AS ForeignKeyName
|
|
355
|
-
FROM information_schema.COLUMNS C
|
|
356
|
-
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
357
|
-
WHERE C.TABLE_SCHEMA = '${dbName}' AND C.TABLE_NAME = '${tableName}'
|
|
347
|
+
const sql = `SELECT
|
|
348
|
+
c.COLUMN_NAME,
|
|
349
|
+
c.COLUMN_TYPE,
|
|
350
|
+
c.IS_NULLABLE,
|
|
351
|
+
c.COLUMN_KEY,
|
|
352
|
+
c.COLUMN_DEFAULT,
|
|
353
|
+
c.EXTRA,
|
|
354
|
+
KCU.CONSTRAINT_NAME AS ForeignKeyName
|
|
355
|
+
FROM information_schema.COLUMNS C
|
|
356
|
+
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
357
|
+
WHERE C.TABLE_SCHEMA = '${dbName}' AND C.TABLE_NAME = '${tableName}'
|
|
358
358
|
ORDER BY C.ORDINAL_POSITION;`;
|
|
359
359
|
return sql;
|
|
360
360
|
}
|
|
361
361
|
_getValidateSql(dbName) {
|
|
362
|
-
const sql = `SELECT
|
|
363
|
-
Count(c.COLUMN_NAME) AS COLUMN_COUNT
|
|
364
|
-
FROM information_schema.COLUMNS C
|
|
365
|
-
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
362
|
+
const sql = `SELECT
|
|
363
|
+
Count(c.COLUMN_NAME) AS COLUMN_COUNT
|
|
364
|
+
FROM information_schema.COLUMNS C
|
|
365
|
+
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
366
366
|
WHERE C.TABLE_SCHEMA = '${dbName}';`;
|
|
367
367
|
return sql;
|
|
368
368
|
}
|
|
@@ -341,25 +341,25 @@ export class OINODbMariadb extends OINODb {
|
|
|
341
341
|
return result;
|
|
342
342
|
}
|
|
343
343
|
_getSchemaSql(dbName, tableName) {
|
|
344
|
-
const sql = `SELECT
|
|
345
|
-
c.COLUMN_NAME,
|
|
346
|
-
c.COLUMN_TYPE,
|
|
347
|
-
c.IS_NULLABLE,
|
|
348
|
-
c.COLUMN_KEY,
|
|
349
|
-
c.COLUMN_DEFAULT,
|
|
350
|
-
c.EXTRA,
|
|
351
|
-
KCU.CONSTRAINT_NAME AS ForeignKeyName
|
|
352
|
-
FROM information_schema.COLUMNS C
|
|
353
|
-
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
354
|
-
WHERE C.TABLE_SCHEMA = '${dbName}' AND C.TABLE_NAME = '${tableName}'
|
|
344
|
+
const sql = `SELECT
|
|
345
|
+
c.COLUMN_NAME,
|
|
346
|
+
c.COLUMN_TYPE,
|
|
347
|
+
c.IS_NULLABLE,
|
|
348
|
+
c.COLUMN_KEY,
|
|
349
|
+
c.COLUMN_DEFAULT,
|
|
350
|
+
c.EXTRA,
|
|
351
|
+
KCU.CONSTRAINT_NAME AS ForeignKeyName
|
|
352
|
+
FROM information_schema.COLUMNS C
|
|
353
|
+
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
354
|
+
WHERE C.TABLE_SCHEMA = '${dbName}' AND C.TABLE_NAME = '${tableName}'
|
|
355
355
|
ORDER BY C.ORDINAL_POSITION;`;
|
|
356
356
|
return sql;
|
|
357
357
|
}
|
|
358
358
|
_getValidateSql(dbName) {
|
|
359
|
-
const sql = `SELECT
|
|
360
|
-
Count(c.COLUMN_NAME) AS COLUMN_COUNT
|
|
361
|
-
FROM information_schema.COLUMNS C
|
|
362
|
-
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
359
|
+
const sql = `SELECT
|
|
360
|
+
Count(c.COLUMN_NAME) AS COLUMN_COUNT
|
|
361
|
+
FROM information_schema.COLUMNS C
|
|
362
|
+
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
363
363
|
WHERE C.TABLE_SCHEMA = '${dbName}';`;
|
|
364
364
|
return sql;
|
|
365
365
|
}
|
package/package.json
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@oino-ts/db-mariadb",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OINO TS package for using Mariadb databases.",
|
|
5
|
-
"author": "Matias Kiviniemi (pragmatta)",
|
|
6
|
-
"license": "MPL-2.0",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/pragmatta/oino-ts.git"
|
|
10
|
-
},
|
|
11
|
-
"keywords": [
|
|
12
|
-
"sql",
|
|
13
|
-
"database",
|
|
14
|
-
"rest-api",
|
|
15
|
-
"typescript",
|
|
16
|
-
"library",
|
|
17
|
-
"mariadb",
|
|
18
|
-
"mysql"
|
|
19
|
-
],
|
|
20
|
-
"main": "./dist/cjs/index.js",
|
|
21
|
-
"module": "./dist/esm/index.js",
|
|
22
|
-
"types": "./dist/types/index.d.ts",
|
|
23
|
-
"dependencies": {
|
|
24
|
-
"@oino-ts/db": "^0.
|
|
25
|
-
"mariadb": "^3.2.3"
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@types/bun": "^1.1.14",
|
|
29
|
-
"@types/node": "^20.12.7",
|
|
30
|
-
"typescript": "~5.9.0"
|
|
31
|
-
},
|
|
32
|
-
"files": [
|
|
33
|
-
"src/*.ts",
|
|
34
|
-
"dist/cjs/*.js",
|
|
35
|
-
"dist/esm/*.js",
|
|
36
|
-
"dist/types/*.d.ts"
|
|
37
|
-
]
|
|
38
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@oino-ts/db-mariadb",
|
|
3
|
+
"version": "0.17.1",
|
|
4
|
+
"description": "OINO TS package for using Mariadb databases.",
|
|
5
|
+
"author": "Matias Kiviniemi (pragmatta)",
|
|
6
|
+
"license": "MPL-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/pragmatta/oino-ts.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"sql",
|
|
13
|
+
"database",
|
|
14
|
+
"rest-api",
|
|
15
|
+
"typescript",
|
|
16
|
+
"library",
|
|
17
|
+
"mariadb",
|
|
18
|
+
"mysql"
|
|
19
|
+
],
|
|
20
|
+
"main": "./dist/cjs/index.js",
|
|
21
|
+
"module": "./dist/esm/index.js",
|
|
22
|
+
"types": "./dist/types/index.d.ts",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@oino-ts/db": "^0.17.1",
|
|
25
|
+
"mariadb": "^3.2.3"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/bun": "^1.1.14",
|
|
29
|
+
"@types/node": "^20.12.7",
|
|
30
|
+
"typescript": "~5.9.0"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"src/*.ts",
|
|
34
|
+
"dist/cjs/*.js",
|
|
35
|
+
"dist/esm/*.js",
|
|
36
|
+
"dist/types/*.d.ts"
|
|
37
|
+
]
|
|
38
|
+
}
|
package/src/OINODbMariadb.ts
CHANGED
|
@@ -1,465 +1,465 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { OINODb, OINODbParams, OINODbDataSet, OINODbApi, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODbDataFieldParams, OINO_ERROR_PREFIX, OINODataRow, OINODataCell, OINOBenchmark, OINODatetimeDataField, OINOBlobDataField, OINO_INFO_PREFIX, OINODB_EMPTY_ROW, OINODB_EMPTY_ROWS, OINOLog, OINOResult } from "@oino-ts/db";
|
|
8
|
-
|
|
9
|
-
import mariadb from "mariadb";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Implmentation of OINODbDataSet for MariaDb.
|
|
13
|
-
*
|
|
14
|
-
*/
|
|
15
|
-
class OINOMariadbData extends OINODbDataSet {
|
|
16
|
-
private _rows:OINODataRow[] = OINODB_EMPTY_ROWS
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* OINOMariadbData constructor
|
|
20
|
-
* @param params database parameters
|
|
21
|
-
*/
|
|
22
|
-
constructor(data: any, messages:string[]=[]) {
|
|
23
|
-
super(data, messages)
|
|
24
|
-
|
|
25
|
-
if (data == null) {
|
|
26
|
-
this.messages.push(OINO_INFO_PREFIX + "SQL result is empty")
|
|
27
|
-
|
|
28
|
-
} else if (Array.isArray(data)) {
|
|
29
|
-
this._rows = data as OINODataRow[]
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
if (this.isEmpty()) {
|
|
33
|
-
this._currentRow = -1
|
|
34
|
-
this._eof = true
|
|
35
|
-
} else {
|
|
36
|
-
this._currentRow = 0
|
|
37
|
-
this._eof = false
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
private _currentRow: number
|
|
41
|
-
private _eof: boolean
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Is data set empty.
|
|
45
|
-
*
|
|
46
|
-
*/
|
|
47
|
-
isEmpty():boolean {
|
|
48
|
-
return (this._rows.length == 0)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Is there no more content, i.e. either dataset is empty or we have moved beyond last line
|
|
53
|
-
*
|
|
54
|
-
*/
|
|
55
|
-
isEof():boolean {
|
|
56
|
-
return (this._eof)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Attempts to moves dataset to the next row, possibly waiting for more data to become available. Returns !isEof().
|
|
61
|
-
*
|
|
62
|
-
*/
|
|
63
|
-
async next():Promise<boolean> {
|
|
64
|
-
if (this._currentRow < this._rows.length-1) {
|
|
65
|
-
this._currentRow = this._currentRow + 1
|
|
66
|
-
} else {
|
|
67
|
-
this._eof = true
|
|
68
|
-
}
|
|
69
|
-
return Promise.resolve(!this._eof)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Gets current row of data.
|
|
74
|
-
*
|
|
75
|
-
*/
|
|
76
|
-
getRow(): OINODataRow {
|
|
77
|
-
if ((this._currentRow >=0) && (this._currentRow < this._rows.length)) {
|
|
78
|
-
return this._rows[this._currentRow]
|
|
79
|
-
} else {
|
|
80
|
-
return OINODB_EMPTY_ROW
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Gets all rows of data.
|
|
86
|
-
*
|
|
87
|
-
*/
|
|
88
|
-
async getAllRows(): Promise<OINODataRow[]> {
|
|
89
|
-
return this._rows // at the moment theres no result streaming, so we can just return the rows
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Implementation of MariaDb/MySql-database.
|
|
95
|
-
*
|
|
96
|
-
*/
|
|
97
|
-
export class OINODbMariadb extends OINODb {
|
|
98
|
-
|
|
99
|
-
private static _fieldLengthRegex = /([^\(\)]+)(\s?\((\d+)\s?\,?\s?(\d*)?\))?/i
|
|
100
|
-
private static _connectionExceptionMessageRegex = /\(([^\)]*)\) (.*)/i
|
|
101
|
-
private static _sqlExceptionMessageRegex = /\(([^\)]*)\) (.*)\nsql\:(.*)?/i
|
|
102
|
-
|
|
103
|
-
private _pool:mariadb.Pool
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Constructor of `OINODbMariadb`
|
|
107
|
-
* @param params database parameters
|
|
108
|
-
*/
|
|
109
|
-
constructor(params:OINODbParams) {
|
|
110
|
-
super(params)
|
|
111
|
-
|
|
112
|
-
if (this._params.type !== "OINODbMariadb") {
|
|
113
|
-
throw new Error(OINO_ERROR_PREFIX + ": Not OINODbMariadb-type: " + this._params.type)
|
|
114
|
-
}
|
|
115
|
-
this._pool = mariadb.createPool({ host: this._params.url, database: this._params.database, port: this._params.port, user: this._params.user, password: this._params.password, acquireTimeout: 2000, debug:false, rowsAsArray: true, multipleStatements: true })
|
|
116
|
-
delete this._params.password // do not store password in db object
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private _parseFieldLength(fieldLengthStr:string):number {
|
|
120
|
-
let result:number = parseInt(fieldLengthStr)
|
|
121
|
-
if (Number.isNaN(result)) {
|
|
122
|
-
result = 0
|
|
123
|
-
}
|
|
124
|
-
return result
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private async _query(sql:string):Promise<OINODataRow[]> {
|
|
128
|
-
let connection:mariadb.Connection|null = null
|
|
129
|
-
try {
|
|
130
|
-
connection = await this._pool.getConnection();
|
|
131
|
-
const result = await connection.query(sql);
|
|
132
|
-
return Promise.resolve(result)
|
|
133
|
-
|
|
134
|
-
} finally {
|
|
135
|
-
if (connection) {
|
|
136
|
-
await connection.end()
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private async _exec(sql:string):Promise<any> {
|
|
142
|
-
let connection:mariadb.Connection|null = null
|
|
143
|
-
try {
|
|
144
|
-
connection = await this._pool.getConnection();
|
|
145
|
-
const result = await connection.query(sql);
|
|
146
|
-
// console.log(result);
|
|
147
|
-
return Promise.resolve(result)
|
|
148
|
-
|
|
149
|
-
} finally {
|
|
150
|
-
if (connection) {
|
|
151
|
-
await connection.end()
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Print a table name using database specific SQL escaping.
|
|
158
|
-
*
|
|
159
|
-
* @param sqlTable name of the table
|
|
160
|
-
*
|
|
161
|
-
*/
|
|
162
|
-
printSqlTablename(sqlTable:string): string {
|
|
163
|
-
return "`"+sqlTable+"`"
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Print a column name with correct SQL escaping.
|
|
168
|
-
*
|
|
169
|
-
* @param sqlColumn name of the column
|
|
170
|
-
*
|
|
171
|
-
*/
|
|
172
|
-
printSqlColumnname(sqlColumn:string): string {
|
|
173
|
-
return "`"+sqlColumn+"`"
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Print a single data value from serialization using the context of the native data
|
|
179
|
-
* type with the correct SQL escaping.
|
|
180
|
-
*
|
|
181
|
-
* @param cellValue data from sql results
|
|
182
|
-
* @param sqlType native type name for table column
|
|
183
|
-
*
|
|
184
|
-
*/
|
|
185
|
-
printCellAsSqlValue(cellValue:OINODataCell, sqlType: string): string {
|
|
186
|
-
if (cellValue === null) {
|
|
187
|
-
return "NULL"
|
|
188
|
-
|
|
189
|
-
} else if (cellValue === undefined) {
|
|
190
|
-
return "UNDEFINED"
|
|
191
|
-
|
|
192
|
-
} else if ((sqlType == "int") || (sqlType == "smallint") || (sqlType == "float") || (sqlType == "double")) {
|
|
193
|
-
return cellValue.toString()
|
|
194
|
-
|
|
195
|
-
} else if ((sqlType == "longblob") || (sqlType == "binary") || (sqlType == "varbinary")) {
|
|
196
|
-
if (cellValue instanceof Buffer) {
|
|
197
|
-
return "x'" + (cellValue as Buffer).toString("hex") + "'"
|
|
198
|
-
} else if (cellValue instanceof Uint8Array) {
|
|
199
|
-
return "x'" + Buffer.from(cellValue as Uint8Array).toString("hex") + "'"
|
|
200
|
-
} else {
|
|
201
|
-
return "\"" + cellValue?.toString() + "\""
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
} else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
|
|
205
|
-
return "\"" + cellValue.toISOString().replace('T', ' ').substring(0, 23) + "\""
|
|
206
|
-
|
|
207
|
-
} else if ((sqlType == "bit")) {
|
|
208
|
-
if ((cellValue === false) || (cellValue == null) || (cellValue == "") || (cellValue.toString().toLowerCase() == "false") || (cellValue == "0")) {
|
|
209
|
-
return "b'0'"
|
|
210
|
-
} else if ((cellValue === true) || (cellValue.toString().toLowerCase() == "true")) {
|
|
211
|
-
return "b'1'"
|
|
212
|
-
} else {
|
|
213
|
-
return "b'" + cellValue.toString() + "'" // rest is assumed to be a valid bitstring
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
} else {
|
|
217
|
-
return this.printSqlString(cellValue?.toString())
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Print a single string value as valid sql literal
|
|
223
|
-
*
|
|
224
|
-
* @param sqlString string value
|
|
225
|
-
*
|
|
226
|
-
*/
|
|
227
|
-
printSqlString(sqlString:string): string {
|
|
228
|
-
return "\"" + sqlString.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\r", "\\r").replaceAll("\n", "\\n").replaceAll("\t", "\\t") + "\""
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Parse a single SQL result value for serialization using the context of the native data
|
|
234
|
-
* type.
|
|
235
|
-
*
|
|
236
|
-
* @param sqlValue data from serialization
|
|
237
|
-
* @param sqlType native type name for table column
|
|
238
|
-
*
|
|
239
|
-
*/
|
|
240
|
-
parseSqlValueAsCell(sqlValue:OINODataCell, sqlType: string): OINODataCell {
|
|
241
|
-
if ((sqlValue === null) || (sqlValue == "NULL")) {
|
|
242
|
-
return null
|
|
243
|
-
|
|
244
|
-
} else if (sqlValue === undefined) {
|
|
245
|
-
return undefined
|
|
246
|
-
|
|
247
|
-
} else if (((sqlType == "date")) && (typeof(sqlValue) == "string") && (sqlValue != "")) {
|
|
248
|
-
return new Date(sqlValue)
|
|
249
|
-
|
|
250
|
-
} else if ((sqlType == "bit") && (sqlValue instanceof Buffer)) { // mariadb returns a buffer for bit-fields
|
|
251
|
-
const buf:Buffer = sqlValue as Buffer
|
|
252
|
-
if (buf.length == 1) {
|
|
253
|
-
return buf.readUInt8(0) === 1
|
|
254
|
-
} else {
|
|
255
|
-
let result:string = ""
|
|
256
|
-
for (let i=0; i<buf.length; i++) {
|
|
257
|
-
result += buf[i].toString(2).padStart(8, '0')
|
|
258
|
-
}
|
|
259
|
-
return result
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
} else {
|
|
263
|
-
return sqlValue
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Connect to database.
|
|
270
|
-
*
|
|
271
|
-
*/
|
|
272
|
-
async connect(): Promise<OINOResult> {
|
|
273
|
-
const result:OINOResult = new OINOResult()
|
|
274
|
-
let connection:mariadb.Connection|null = null
|
|
275
|
-
try {
|
|
276
|
-
// make sure that any items are correctly URL encoded in the connection string
|
|
277
|
-
connection = await this._pool.getConnection()
|
|
278
|
-
this.isConnected = true
|
|
279
|
-
|
|
280
|
-
} catch (e:any) {
|
|
281
|
-
const msg_parts = (e as Error).message.match(OINODbMariadb._connectionExceptionMessageRegex) || []
|
|
282
|
-
result.setError(500, "Error connecting to server: " + msg_parts[2], "OINODbMariadb.connect")
|
|
283
|
-
OINOLog.exception("@oino-ts/db-mariadb", "OINODbMariadb", "connect", "exception in connect", {message:e.message, stack:e.stack})
|
|
284
|
-
} finally {
|
|
285
|
-
if (connection) {
|
|
286
|
-
await connection.end()
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return Promise.resolve(result)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Validate connection to database is working.
|
|
295
|
-
*
|
|
296
|
-
*/
|
|
297
|
-
async validate(): Promise<OINOResult> {
|
|
298
|
-
OINOBenchmark.startMetric("OINODb", "validate")
|
|
299
|
-
let result:OINOResult = new OINOResult()
|
|
300
|
-
try {
|
|
301
|
-
const sql = this._getValidateSql(this._params.database)
|
|
302
|
-
const sql_res:OINODbDataSet = await this.sqlSelect(sql)
|
|
303
|
-
if (sql_res.isEmpty()) {
|
|
304
|
-
result.setError(400, "DB returned no rows for select!", "OINODbMariadb.validate")
|
|
305
|
-
|
|
306
|
-
} else if (sql_res.getRow().length == 0) {
|
|
307
|
-
result.setError(400, "DB returned no values for database!", "OINODbMariadb.validate")
|
|
308
|
-
|
|
309
|
-
} else if (sql_res.getRow()[0] == "0") {
|
|
310
|
-
result.setError(400, "DB returned no schema for database!", "OINODbMariadb.validate")
|
|
311
|
-
|
|
312
|
-
} else {
|
|
313
|
-
this.isValidated = true
|
|
314
|
-
}
|
|
315
|
-
} catch (e:any) {
|
|
316
|
-
result.setError(500, "Exception validating connection: " + e.message, "OINODbMariadb.validate")
|
|
317
|
-
OINOLog.exception("@oino-ts/db-mariadb", "OINODbMariadb", "validate", "exception in validate", {message:e.message, stack:e.stack})
|
|
318
|
-
}
|
|
319
|
-
OINOBenchmark.endMetric("OINODb", "validate")
|
|
320
|
-
return result
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Execute a select operation.
|
|
325
|
-
*
|
|
326
|
-
* @param sql SQL statement.
|
|
327
|
-
*
|
|
328
|
-
*/
|
|
329
|
-
async sqlSelect(sql:string): Promise<OINODbDataSet> {
|
|
330
|
-
OINOBenchmark.startMetric("OINODb", "sqlSelect")
|
|
331
|
-
let result:OINODbDataSet
|
|
332
|
-
try {
|
|
333
|
-
const rows:OINODataRow[] = await this._query(sql)
|
|
334
|
-
result = new OINOMariadbData(rows, [])
|
|
335
|
-
|
|
336
|
-
} catch (e:any) {
|
|
337
|
-
OINOLog.exception("@oino-ts/db-mariadb", "OINODbMariadb", "sqlSelect", "exception in SQL select", {message:e.message, stack:e.stack})
|
|
338
|
-
result = new OINOMariadbData(OINODB_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (sqlSelect): OINODbMariadb.sqlSelect exception in _db.query: " + e.message])
|
|
339
|
-
}
|
|
340
|
-
OINOBenchmark.endMetric("OINODb", "sqlSelect")
|
|
341
|
-
return result
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Execute other sql operations.
|
|
346
|
-
*
|
|
347
|
-
* @param sql SQL statement.
|
|
348
|
-
*
|
|
349
|
-
*/
|
|
350
|
-
async sqlExec(sql:string): Promise<OINODbDataSet> {
|
|
351
|
-
OINOBenchmark.startMetric("OINODb", "sqlExec")
|
|
352
|
-
let result:OINODbDataSet
|
|
353
|
-
try {
|
|
354
|
-
const sql_res:OINODataRow[] = await this._exec(sql)
|
|
355
|
-
result = new OINOMariadbData(sql_res, [])
|
|
356
|
-
|
|
357
|
-
} catch (e:any) {
|
|
358
|
-
const msg_parts = e.message.match(OINODbMariadb._sqlExceptionMessageRegex) || []
|
|
359
|
-
OINOLog.exception("@oino-ts/db-mariadb", "OINODbMariadb", "sqlExec", "exception in SQL exec", {message:msg_parts[2], stack:e.stack})
|
|
360
|
-
result = new OINOMariadbData(OINODB_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (sqlExec): exception in _db.exec [" + msg_parts[2] + "]"])
|
|
361
|
-
}
|
|
362
|
-
OINOBenchmark.endMetric("OINODb", "sqlExec")
|
|
363
|
-
return result
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
private _getSchemaSql(dbName:string, tableName:string):string {
|
|
367
|
-
const sql =
|
|
368
|
-
`SELECT
|
|
369
|
-
c.COLUMN_NAME,
|
|
370
|
-
c.COLUMN_TYPE,
|
|
371
|
-
c.IS_NULLABLE,
|
|
372
|
-
c.COLUMN_KEY,
|
|
373
|
-
c.COLUMN_DEFAULT,
|
|
374
|
-
c.EXTRA,
|
|
375
|
-
KCU.CONSTRAINT_NAME AS ForeignKeyName
|
|
376
|
-
FROM information_schema.COLUMNS C
|
|
377
|
-
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
378
|
-
WHERE C.TABLE_SCHEMA = '${dbName}' AND C.TABLE_NAME = '${tableName}'
|
|
379
|
-
ORDER BY C.ORDINAL_POSITION;`
|
|
380
|
-
return sql
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
private _getValidateSql(dbName:string):string {
|
|
384
|
-
const sql =
|
|
385
|
-
`SELECT
|
|
386
|
-
Count(c.COLUMN_NAME) AS COLUMN_COUNT
|
|
387
|
-
FROM information_schema.COLUMNS C
|
|
388
|
-
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
389
|
-
WHERE C.TABLE_SCHEMA = '${dbName}';`
|
|
390
|
-
return sql
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Initialize a data model by getting the SQL schema and populating OINODbDataFields of
|
|
396
|
-
* the model.
|
|
397
|
-
*
|
|
398
|
-
* @param api api which data model to initialize.
|
|
399
|
-
*
|
|
400
|
-
*/
|
|
401
|
-
async initializeApiDatamodel(api:OINODbApi): Promise<void> {
|
|
402
|
-
|
|
403
|
-
const schema_res:OINODbDataSet = await this.sqlSelect(this._getSchemaSql(this._params.database, api.params.tableName))
|
|
404
|
-
while (!schema_res.isEof()) {
|
|
405
|
-
const row:OINODataRow = schema_res.getRow()
|
|
406
|
-
// console.log("OINODbMariadb.initializeApiDatamodel row", row)
|
|
407
|
-
const field_name:string = row[0]?.toString() || ""
|
|
408
|
-
const field_matches = OINODbMariadb._fieldLengthRegex.exec(row[1]?.toString() || "") || []
|
|
409
|
-
const sql_type:string = field_matches[1] || ""
|
|
410
|
-
const field_length1:number = this._parseFieldLength(field_matches[3] || "0")
|
|
411
|
-
const field_length2:number = this._parseFieldLength(field_matches[4] || "0")
|
|
412
|
-
const extra:string = row[5]?.toString() || ""
|
|
413
|
-
const field_params:OINODbDataFieldParams = {
|
|
414
|
-
isPrimaryKey: row[3] == "PRI",
|
|
415
|
-
isForeignKey: row[6] != null,
|
|
416
|
-
isAutoInc: extra.indexOf('auto_increment') >= 0,
|
|
417
|
-
isNotNull: row[2] == "NO"
|
|
418
|
-
}
|
|
419
|
-
if (api.isFieldIncluded(field_name)==false) {
|
|
420
|
-
OINOLog.info("@oino-ts/db-mariadb", "OINODbMariadb", ".initializeApiDatamodel", "Field excluded in API parameters", {field:field_name})
|
|
421
|
-
if (field_params.isPrimaryKey) {
|
|
422
|
-
throw new Error(OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + field_name)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
} else {
|
|
426
|
-
if ((sql_type == "int") || (sql_type == "smallint") || (sql_type == "float") || (sql_type == "double")) {
|
|
427
|
-
api.datamodel.addField(new OINONumberDataField(this, field_name, sql_type, field_params ))
|
|
428
|
-
|
|
429
|
-
} else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "timestamp")) {
|
|
430
|
-
if (api.params.useDatesAsString) {
|
|
431
|
-
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
|
|
432
|
-
} else {
|
|
433
|
-
api.datamodel.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params))
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
} else if ((sql_type == "char") || (sql_type == "varchar") || (sql_type == "tinytext") || (sql_type == "tinytext") || (sql_type == "mediumtext") || (sql_type == "longtext")) {
|
|
437
|
-
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1))
|
|
438
|
-
|
|
439
|
-
} else if ((sql_type == "longblob") || (sql_type == "binary") || (sql_type == "varbinary")) {
|
|
440
|
-
api.datamodel.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length1))
|
|
441
|
-
|
|
442
|
-
} else if ((sql_type == "decimal")) {
|
|
443
|
-
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 + field_length2 + 1))
|
|
444
|
-
|
|
445
|
-
} else if ((sql_type == "bit")) {
|
|
446
|
-
if (field_length1 == 1) {
|
|
447
|
-
api.datamodel.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params))
|
|
448
|
-
} else {
|
|
449
|
-
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1*8))
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
} else {
|
|
453
|
-
OINOLog.info("@oino-ts/db-mariadb", "OINODbMariadb", "initializeApiDatamodel", "Unrecognized field type treated as string", {field_name: field_name, sql_type:sql_type, field_length1:field_length1, field_length2:field_length2, field_params:field_params })
|
|
454
|
-
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
await schema_res.next()
|
|
458
|
-
}
|
|
459
|
-
OINOLog.info("@oino-ts/db-mariadb", "OINODbMariadb", "initializeApiDatamodel", "\n" + api.datamodel.printDebug("\n"))
|
|
460
|
-
return Promise.resolve()
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { OINODb, OINODbParams, OINODbDataSet, OINODbApi, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODbDataFieldParams, OINO_ERROR_PREFIX, OINODataRow, OINODataCell, OINOBenchmark, OINODatetimeDataField, OINOBlobDataField, OINO_INFO_PREFIX, OINODB_EMPTY_ROW, OINODB_EMPTY_ROWS, OINOLog, OINOResult } from "@oino-ts/db";
|
|
8
|
+
|
|
9
|
+
import mariadb from "mariadb";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Implmentation of OINODbDataSet for MariaDb.
|
|
13
|
+
*
|
|
14
|
+
*/
|
|
15
|
+
class OINOMariadbData extends OINODbDataSet {
|
|
16
|
+
private _rows:OINODataRow[] = OINODB_EMPTY_ROWS
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* OINOMariadbData constructor
|
|
20
|
+
* @param params database parameters
|
|
21
|
+
*/
|
|
22
|
+
constructor(data: any, messages:string[]=[]) {
|
|
23
|
+
super(data, messages)
|
|
24
|
+
|
|
25
|
+
if (data == null) {
|
|
26
|
+
this.messages.push(OINO_INFO_PREFIX + "SQL result is empty")
|
|
27
|
+
|
|
28
|
+
} else if (Array.isArray(data)) {
|
|
29
|
+
this._rows = data as OINODataRow[]
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
if (this.isEmpty()) {
|
|
33
|
+
this._currentRow = -1
|
|
34
|
+
this._eof = true
|
|
35
|
+
} else {
|
|
36
|
+
this._currentRow = 0
|
|
37
|
+
this._eof = false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
private _currentRow: number
|
|
41
|
+
private _eof: boolean
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Is data set empty.
|
|
45
|
+
*
|
|
46
|
+
*/
|
|
47
|
+
isEmpty():boolean {
|
|
48
|
+
return (this._rows.length == 0)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Is there no more content, i.e. either dataset is empty or we have moved beyond last line
|
|
53
|
+
*
|
|
54
|
+
*/
|
|
55
|
+
isEof():boolean {
|
|
56
|
+
return (this._eof)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Attempts to moves dataset to the next row, possibly waiting for more data to become available. Returns !isEof().
|
|
61
|
+
*
|
|
62
|
+
*/
|
|
63
|
+
async next():Promise<boolean> {
|
|
64
|
+
if (this._currentRow < this._rows.length-1) {
|
|
65
|
+
this._currentRow = this._currentRow + 1
|
|
66
|
+
} else {
|
|
67
|
+
this._eof = true
|
|
68
|
+
}
|
|
69
|
+
return Promise.resolve(!this._eof)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gets current row of data.
|
|
74
|
+
*
|
|
75
|
+
*/
|
|
76
|
+
getRow(): OINODataRow {
|
|
77
|
+
if ((this._currentRow >=0) && (this._currentRow < this._rows.length)) {
|
|
78
|
+
return this._rows[this._currentRow]
|
|
79
|
+
} else {
|
|
80
|
+
return OINODB_EMPTY_ROW
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Gets all rows of data.
|
|
86
|
+
*
|
|
87
|
+
*/
|
|
88
|
+
async getAllRows(): Promise<OINODataRow[]> {
|
|
89
|
+
return this._rows // at the moment theres no result streaming, so we can just return the rows
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Implementation of MariaDb/MySql-database.
|
|
95
|
+
*
|
|
96
|
+
*/
|
|
97
|
+
export class OINODbMariadb extends OINODb {
|
|
98
|
+
|
|
99
|
+
private static _fieldLengthRegex = /([^\(\)]+)(\s?\((\d+)\s?\,?\s?(\d*)?\))?/i
|
|
100
|
+
private static _connectionExceptionMessageRegex = /\(([^\)]*)\) (.*)/i
|
|
101
|
+
private static _sqlExceptionMessageRegex = /\(([^\)]*)\) (.*)\nsql\:(.*)?/i
|
|
102
|
+
|
|
103
|
+
private _pool:mariadb.Pool
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Constructor of `OINODbMariadb`
|
|
107
|
+
* @param params database parameters
|
|
108
|
+
*/
|
|
109
|
+
constructor(params:OINODbParams) {
|
|
110
|
+
super(params)
|
|
111
|
+
|
|
112
|
+
if (this._params.type !== "OINODbMariadb") {
|
|
113
|
+
throw new Error(OINO_ERROR_PREFIX + ": Not OINODbMariadb-type: " + this._params.type)
|
|
114
|
+
}
|
|
115
|
+
this._pool = mariadb.createPool({ host: this._params.url, database: this._params.database, port: this._params.port, user: this._params.user, password: this._params.password, acquireTimeout: 2000, debug:false, rowsAsArray: true, multipleStatements: true })
|
|
116
|
+
delete this._params.password // do not store password in db object
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private _parseFieldLength(fieldLengthStr:string):number {
|
|
120
|
+
let result:number = parseInt(fieldLengthStr)
|
|
121
|
+
if (Number.isNaN(result)) {
|
|
122
|
+
result = 0
|
|
123
|
+
}
|
|
124
|
+
return result
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private async _query(sql:string):Promise<OINODataRow[]> {
|
|
128
|
+
let connection:mariadb.Connection|null = null
|
|
129
|
+
try {
|
|
130
|
+
connection = await this._pool.getConnection();
|
|
131
|
+
const result = await connection.query(sql);
|
|
132
|
+
return Promise.resolve(result)
|
|
133
|
+
|
|
134
|
+
} finally {
|
|
135
|
+
if (connection) {
|
|
136
|
+
await connection.end()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private async _exec(sql:string):Promise<any> {
|
|
142
|
+
let connection:mariadb.Connection|null = null
|
|
143
|
+
try {
|
|
144
|
+
connection = await this._pool.getConnection();
|
|
145
|
+
const result = await connection.query(sql);
|
|
146
|
+
// console.log(result);
|
|
147
|
+
return Promise.resolve(result)
|
|
148
|
+
|
|
149
|
+
} finally {
|
|
150
|
+
if (connection) {
|
|
151
|
+
await connection.end()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Print a table name using database specific SQL escaping.
|
|
158
|
+
*
|
|
159
|
+
* @param sqlTable name of the table
|
|
160
|
+
*
|
|
161
|
+
*/
|
|
162
|
+
printSqlTablename(sqlTable:string): string {
|
|
163
|
+
return "`"+sqlTable+"`"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Print a column name with correct SQL escaping.
|
|
168
|
+
*
|
|
169
|
+
* @param sqlColumn name of the column
|
|
170
|
+
*
|
|
171
|
+
*/
|
|
172
|
+
printSqlColumnname(sqlColumn:string): string {
|
|
173
|
+
return "`"+sqlColumn+"`"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Print a single data value from serialization using the context of the native data
|
|
179
|
+
* type with the correct SQL escaping.
|
|
180
|
+
*
|
|
181
|
+
* @param cellValue data from sql results
|
|
182
|
+
* @param sqlType native type name for table column
|
|
183
|
+
*
|
|
184
|
+
*/
|
|
185
|
+
printCellAsSqlValue(cellValue:OINODataCell, sqlType: string): string {
|
|
186
|
+
if (cellValue === null) {
|
|
187
|
+
return "NULL"
|
|
188
|
+
|
|
189
|
+
} else if (cellValue === undefined) {
|
|
190
|
+
return "UNDEFINED"
|
|
191
|
+
|
|
192
|
+
} else if ((sqlType == "int") || (sqlType == "smallint") || (sqlType == "float") || (sqlType == "double")) {
|
|
193
|
+
return cellValue.toString()
|
|
194
|
+
|
|
195
|
+
} else if ((sqlType == "longblob") || (sqlType == "binary") || (sqlType == "varbinary")) {
|
|
196
|
+
if (cellValue instanceof Buffer) {
|
|
197
|
+
return "x'" + (cellValue as Buffer).toString("hex") + "'"
|
|
198
|
+
} else if (cellValue instanceof Uint8Array) {
|
|
199
|
+
return "x'" + Buffer.from(cellValue as Uint8Array).toString("hex") + "'"
|
|
200
|
+
} else {
|
|
201
|
+
return "\"" + cellValue?.toString() + "\""
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
} else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
|
|
205
|
+
return "\"" + cellValue.toISOString().replace('T', ' ').substring(0, 23) + "\""
|
|
206
|
+
|
|
207
|
+
} else if ((sqlType == "bit")) {
|
|
208
|
+
if ((cellValue === false) || (cellValue == null) || (cellValue == "") || (cellValue.toString().toLowerCase() == "false") || (cellValue == "0")) {
|
|
209
|
+
return "b'0'"
|
|
210
|
+
} else if ((cellValue === true) || (cellValue.toString().toLowerCase() == "true")) {
|
|
211
|
+
return "b'1'"
|
|
212
|
+
} else {
|
|
213
|
+
return "b'" + cellValue.toString() + "'" // rest is assumed to be a valid bitstring
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
} else {
|
|
217
|
+
return this.printSqlString(cellValue?.toString())
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Print a single string value as valid sql literal
|
|
223
|
+
*
|
|
224
|
+
* @param sqlString string value
|
|
225
|
+
*
|
|
226
|
+
*/
|
|
227
|
+
printSqlString(sqlString:string): string {
|
|
228
|
+
return "\"" + sqlString.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\r", "\\r").replaceAll("\n", "\\n").replaceAll("\t", "\\t") + "\""
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Parse a single SQL result value for serialization using the context of the native data
|
|
234
|
+
* type.
|
|
235
|
+
*
|
|
236
|
+
* @param sqlValue data from serialization
|
|
237
|
+
* @param sqlType native type name for table column
|
|
238
|
+
*
|
|
239
|
+
*/
|
|
240
|
+
parseSqlValueAsCell(sqlValue:OINODataCell, sqlType: string): OINODataCell {
|
|
241
|
+
if ((sqlValue === null) || (sqlValue == "NULL")) {
|
|
242
|
+
return null
|
|
243
|
+
|
|
244
|
+
} else if (sqlValue === undefined) {
|
|
245
|
+
return undefined
|
|
246
|
+
|
|
247
|
+
} else if (((sqlType == "date")) && (typeof(sqlValue) == "string") && (sqlValue != "")) {
|
|
248
|
+
return new Date(sqlValue)
|
|
249
|
+
|
|
250
|
+
} else if ((sqlType == "bit") && (sqlValue instanceof Buffer)) { // mariadb returns a buffer for bit-fields
|
|
251
|
+
const buf:Buffer = sqlValue as Buffer
|
|
252
|
+
if (buf.length == 1) {
|
|
253
|
+
return buf.readUInt8(0) === 1
|
|
254
|
+
} else {
|
|
255
|
+
let result:string = ""
|
|
256
|
+
for (let i=0; i<buf.length; i++) {
|
|
257
|
+
result += buf[i].toString(2).padStart(8, '0')
|
|
258
|
+
}
|
|
259
|
+
return result
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
} else {
|
|
263
|
+
return sqlValue
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Connect to database.
|
|
270
|
+
*
|
|
271
|
+
*/
|
|
272
|
+
async connect(): Promise<OINOResult> {
|
|
273
|
+
const result:OINOResult = new OINOResult()
|
|
274
|
+
let connection:mariadb.Connection|null = null
|
|
275
|
+
try {
|
|
276
|
+
// make sure that any items are correctly URL encoded in the connection string
|
|
277
|
+
connection = await this._pool.getConnection()
|
|
278
|
+
this.isConnected = true
|
|
279
|
+
|
|
280
|
+
} catch (e:any) {
|
|
281
|
+
const msg_parts = (e as Error).message.match(OINODbMariadb._connectionExceptionMessageRegex) || []
|
|
282
|
+
result.setError(500, "Error connecting to server: " + msg_parts[2], "OINODbMariadb.connect")
|
|
283
|
+
OINOLog.exception("@oino-ts/db-mariadb", "OINODbMariadb", "connect", "exception in connect", {message:e.message, stack:e.stack})
|
|
284
|
+
} finally {
|
|
285
|
+
if (connection) {
|
|
286
|
+
await connection.end()
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return Promise.resolve(result)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Validate connection to database is working.
|
|
295
|
+
*
|
|
296
|
+
*/
|
|
297
|
+
async validate(): Promise<OINOResult> {
|
|
298
|
+
OINOBenchmark.startMetric("OINODb", "validate")
|
|
299
|
+
let result:OINOResult = new OINOResult()
|
|
300
|
+
try {
|
|
301
|
+
const sql = this._getValidateSql(this._params.database)
|
|
302
|
+
const sql_res:OINODbDataSet = await this.sqlSelect(sql)
|
|
303
|
+
if (sql_res.isEmpty()) {
|
|
304
|
+
result.setError(400, "DB returned no rows for select!", "OINODbMariadb.validate")
|
|
305
|
+
|
|
306
|
+
} else if (sql_res.getRow().length == 0) {
|
|
307
|
+
result.setError(400, "DB returned no values for database!", "OINODbMariadb.validate")
|
|
308
|
+
|
|
309
|
+
} else if (sql_res.getRow()[0] == "0") {
|
|
310
|
+
result.setError(400, "DB returned no schema for database!", "OINODbMariadb.validate")
|
|
311
|
+
|
|
312
|
+
} else {
|
|
313
|
+
this.isValidated = true
|
|
314
|
+
}
|
|
315
|
+
} catch (e:any) {
|
|
316
|
+
result.setError(500, "Exception validating connection: " + e.message, "OINODbMariadb.validate")
|
|
317
|
+
OINOLog.exception("@oino-ts/db-mariadb", "OINODbMariadb", "validate", "exception in validate", {message:e.message, stack:e.stack})
|
|
318
|
+
}
|
|
319
|
+
OINOBenchmark.endMetric("OINODb", "validate")
|
|
320
|
+
return result
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Execute a select operation.
|
|
325
|
+
*
|
|
326
|
+
* @param sql SQL statement.
|
|
327
|
+
*
|
|
328
|
+
*/
|
|
329
|
+
async sqlSelect(sql:string): Promise<OINODbDataSet> {
|
|
330
|
+
OINOBenchmark.startMetric("OINODb", "sqlSelect")
|
|
331
|
+
let result:OINODbDataSet
|
|
332
|
+
try {
|
|
333
|
+
const rows:OINODataRow[] = await this._query(sql)
|
|
334
|
+
result = new OINOMariadbData(rows, [])
|
|
335
|
+
|
|
336
|
+
} catch (e:any) {
|
|
337
|
+
OINOLog.exception("@oino-ts/db-mariadb", "OINODbMariadb", "sqlSelect", "exception in SQL select", {message:e.message, stack:e.stack})
|
|
338
|
+
result = new OINOMariadbData(OINODB_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (sqlSelect): OINODbMariadb.sqlSelect exception in _db.query: " + e.message])
|
|
339
|
+
}
|
|
340
|
+
OINOBenchmark.endMetric("OINODb", "sqlSelect")
|
|
341
|
+
return result
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Execute other sql operations.
|
|
346
|
+
*
|
|
347
|
+
* @param sql SQL statement.
|
|
348
|
+
*
|
|
349
|
+
*/
|
|
350
|
+
async sqlExec(sql:string): Promise<OINODbDataSet> {
|
|
351
|
+
OINOBenchmark.startMetric("OINODb", "sqlExec")
|
|
352
|
+
let result:OINODbDataSet
|
|
353
|
+
try {
|
|
354
|
+
const sql_res:OINODataRow[] = await this._exec(sql)
|
|
355
|
+
result = new OINOMariadbData(sql_res, [])
|
|
356
|
+
|
|
357
|
+
} catch (e:any) {
|
|
358
|
+
const msg_parts = e.message.match(OINODbMariadb._sqlExceptionMessageRegex) || []
|
|
359
|
+
OINOLog.exception("@oino-ts/db-mariadb", "OINODbMariadb", "sqlExec", "exception in SQL exec", {message:msg_parts[2], stack:e.stack})
|
|
360
|
+
result = new OINOMariadbData(OINODB_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (sqlExec): exception in _db.exec [" + msg_parts[2] + "]"])
|
|
361
|
+
}
|
|
362
|
+
OINOBenchmark.endMetric("OINODb", "sqlExec")
|
|
363
|
+
return result
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private _getSchemaSql(dbName:string, tableName:string):string {
|
|
367
|
+
const sql =
|
|
368
|
+
`SELECT
|
|
369
|
+
c.COLUMN_NAME,
|
|
370
|
+
c.COLUMN_TYPE,
|
|
371
|
+
c.IS_NULLABLE,
|
|
372
|
+
c.COLUMN_KEY,
|
|
373
|
+
c.COLUMN_DEFAULT,
|
|
374
|
+
c.EXTRA,
|
|
375
|
+
KCU.CONSTRAINT_NAME AS ForeignKeyName
|
|
376
|
+
FROM information_schema.COLUMNS C
|
|
377
|
+
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
378
|
+
WHERE C.TABLE_SCHEMA = '${dbName}' AND C.TABLE_NAME = '${tableName}'
|
|
379
|
+
ORDER BY C.ORDINAL_POSITION;`
|
|
380
|
+
return sql
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private _getValidateSql(dbName:string):string {
|
|
384
|
+
const sql =
|
|
385
|
+
`SELECT
|
|
386
|
+
Count(c.COLUMN_NAME) AS COLUMN_COUNT
|
|
387
|
+
FROM information_schema.COLUMNS C
|
|
388
|
+
LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
|
|
389
|
+
WHERE C.TABLE_SCHEMA = '${dbName}';`
|
|
390
|
+
return sql
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Initialize a data model by getting the SQL schema and populating OINODbDataFields of
|
|
396
|
+
* the model.
|
|
397
|
+
*
|
|
398
|
+
* @param api api which data model to initialize.
|
|
399
|
+
*
|
|
400
|
+
*/
|
|
401
|
+
async initializeApiDatamodel(api:OINODbApi): Promise<void> {
|
|
402
|
+
|
|
403
|
+
const schema_res:OINODbDataSet = await this.sqlSelect(this._getSchemaSql(this._params.database, api.params.tableName))
|
|
404
|
+
while (!schema_res.isEof()) {
|
|
405
|
+
const row:OINODataRow = schema_res.getRow()
|
|
406
|
+
// console.log("OINODbMariadb.initializeApiDatamodel row", row)
|
|
407
|
+
const field_name:string = row[0]?.toString() || ""
|
|
408
|
+
const field_matches = OINODbMariadb._fieldLengthRegex.exec(row[1]?.toString() || "") || []
|
|
409
|
+
const sql_type:string = field_matches[1] || ""
|
|
410
|
+
const field_length1:number = this._parseFieldLength(field_matches[3] || "0")
|
|
411
|
+
const field_length2:number = this._parseFieldLength(field_matches[4] || "0")
|
|
412
|
+
const extra:string = row[5]?.toString() || ""
|
|
413
|
+
const field_params:OINODbDataFieldParams = {
|
|
414
|
+
isPrimaryKey: row[3] == "PRI",
|
|
415
|
+
isForeignKey: row[6] != null,
|
|
416
|
+
isAutoInc: extra.indexOf('auto_increment') >= 0,
|
|
417
|
+
isNotNull: row[2] == "NO"
|
|
418
|
+
}
|
|
419
|
+
if (api.isFieldIncluded(field_name)==false) {
|
|
420
|
+
OINOLog.info("@oino-ts/db-mariadb", "OINODbMariadb", ".initializeApiDatamodel", "Field excluded in API parameters", {field:field_name})
|
|
421
|
+
if (field_params.isPrimaryKey) {
|
|
422
|
+
throw new Error(OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + field_name)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
} else {
|
|
426
|
+
if ((sql_type == "int") || (sql_type == "smallint") || (sql_type == "float") || (sql_type == "double")) {
|
|
427
|
+
api.datamodel.addField(new OINONumberDataField(this, field_name, sql_type, field_params ))
|
|
428
|
+
|
|
429
|
+
} else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "timestamp")) {
|
|
430
|
+
if (api.params.useDatesAsString) {
|
|
431
|
+
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
|
|
432
|
+
} else {
|
|
433
|
+
api.datamodel.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params))
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
} else if ((sql_type == "char") || (sql_type == "varchar") || (sql_type == "tinytext") || (sql_type == "tinytext") || (sql_type == "mediumtext") || (sql_type == "longtext")) {
|
|
437
|
+
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1))
|
|
438
|
+
|
|
439
|
+
} else if ((sql_type == "longblob") || (sql_type == "binary") || (sql_type == "varbinary")) {
|
|
440
|
+
api.datamodel.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length1))
|
|
441
|
+
|
|
442
|
+
} else if ((sql_type == "decimal")) {
|
|
443
|
+
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 + field_length2 + 1))
|
|
444
|
+
|
|
445
|
+
} else if ((sql_type == "bit")) {
|
|
446
|
+
if (field_length1 == 1) {
|
|
447
|
+
api.datamodel.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params))
|
|
448
|
+
} else {
|
|
449
|
+
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1*8))
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
} else {
|
|
453
|
+
OINOLog.info("@oino-ts/db-mariadb", "OINODbMariadb", "initializeApiDatamodel", "Unrecognized field type treated as string", {field_name: field_name, sql_type:sql_type, field_length1:field_length1, field_length2:field_length2, field_params:field_params })
|
|
454
|
+
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
await schema_res.next()
|
|
458
|
+
}
|
|
459
|
+
OINOLog.info("@oino-ts/db-mariadb", "OINODbMariadb", "initializeApiDatamodel", "\n" + api.datamodel.printDebug("\n"))
|
|
460
|
+
return Promise.resolve()
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { OINODbMariadb } from "./OINODbMariadb.js"
|
|
1
|
+
export { OINODbMariadb } from "./OINODbMariadb.js"
|