@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.
@@ -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.16.2",
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.16.2",
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
+ }
@@ -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"