@oino-ts/db-mariadb 0.0.11

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.
@@ -0,0 +1,358 @@
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
+ import { OINODb, OINODbDataSet, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINO_ERROR_PREFIX, OINOBenchmark, OINODatetimeDataField, OINOBlobDataField, OINO_INFO_PREFIX, OINODB_EMPTY_ROW, OINODB_EMPTY_ROWS, OINOLog } from "@oino-ts/db";
7
+ import mariadb from "mariadb";
8
+ /**
9
+ * Implmentation of OINODbDataSet for MariaDb.
10
+ *
11
+ */
12
+ class OINOMariadbData extends OINODbDataSet {
13
+ _rows = OINODB_EMPTY_ROWS;
14
+ /**
15
+ * OINOMariadbData constructor
16
+ * @param params database parameters
17
+ */
18
+ constructor(data, messages = []) {
19
+ super(data, messages);
20
+ if (data == null) {
21
+ this.messages.push(OINO_INFO_PREFIX + "SQL result is empty");
22
+ }
23
+ else if (Array.isArray(data)) {
24
+ this._rows = data;
25
+ }
26
+ // OINOLog.debug("OINOMariadbData.constructor", {_rows:this._rows})
27
+ if (this.isEmpty()) {
28
+ this._currentRow = -1;
29
+ this._eof = true;
30
+ }
31
+ else {
32
+ this._currentRow = 0;
33
+ this._eof = false;
34
+ }
35
+ }
36
+ _currentRow;
37
+ _eof;
38
+ isEmpty() {
39
+ return (this._rows.length == 0);
40
+ }
41
+ // EOF means "there is no more content", i.e. either dataset is empty or we have moved beyond last line
42
+ isEof() {
43
+ return (this._eof);
44
+ }
45
+ next() {
46
+ // OINOLog.debug("OINODbDataSet.next", {currentRow:this._currentRow, length:this.sqlResult.data.length})
47
+ if (this._currentRow < this._rows.length - 1) {
48
+ this._currentRow = this._currentRow + 1;
49
+ }
50
+ else {
51
+ this._eof = true;
52
+ }
53
+ return !this._eof;
54
+ }
55
+ getRow() {
56
+ if ((this._currentRow >= 0) && (this._currentRow < this._rows.length)) {
57
+ return this._rows[this._currentRow];
58
+ }
59
+ else {
60
+ return OINODB_EMPTY_ROW;
61
+ }
62
+ }
63
+ }
64
+ /**
65
+ * Implementation of MariaDb/MySql-database.
66
+ *
67
+ */
68
+ export class OINODbMariadb extends OINODb {
69
+ static _fieldLengthRegex = /([^\(\)]+)(\s?\((\d+)\s?\,?\s?(\d*)?\))?/i;
70
+ static _exceptionMessageRegex = /\(([^\)]*)\) (.*)\nsql\:(.*)?/i;
71
+ static _tableSchemaSql = `SHOW COLUMNS from `;
72
+ _pool;
73
+ /**
74
+ * Constructor of `OINODbMariadb`
75
+ * @param params database parameters
76
+ */
77
+ constructor(params) {
78
+ super(params);
79
+ // OINOLog.debug("OINODbMariadb.constructor", {params:params})
80
+ if (this._params.type !== "OINODbMariadb") {
81
+ throw new Error(OINO_ERROR_PREFIX + ": Not OINODbMariadb-type: " + this._params.type);
82
+ }
83
+ this._pool = mariadb.createPool({ host: params.url, database: params.database, port: params.port, user: params.user, password: params.password, acquireTimeout: 2000, debug: false, rowsAsArray: true });
84
+ // this._pool.on("acquire", (conn: mariadb.Connection) => {
85
+ // OINOLog.info("OINODbMariadb acquire", {conn:conn})
86
+ // })
87
+ // this._pool.on("connection", (conn: mariadb.Connection) => {
88
+ // OINOLog.info("OINODbMariadb connection", {conn:conn})
89
+ // })
90
+ // this._pool.on("release", (conn: mariadb.Connection) => {
91
+ // OINOLog.info("OINODbMariadb release", {conn:conn})
92
+ // })
93
+ // this._pool.on("enqueue", () => {
94
+ // OINOLog.info("OINODbMariadb enqueue", {})
95
+ // })
96
+ }
97
+ _parseFieldLength(fieldLengthStr) {
98
+ let result = parseInt(fieldLengthStr);
99
+ if (Number.isNaN(result)) {
100
+ result = 0;
101
+ }
102
+ return result;
103
+ }
104
+ async _query(sql) {
105
+ // OINOLog.debug("OINODbMariadb._query", {sql:sql})
106
+ let connection = null;
107
+ try {
108
+ connection = await this._pool.getConnection();
109
+ const result = await connection.query(sql);
110
+ // console.log("OINODbMariadb._query rows="+result)
111
+ return Promise.resolve(result);
112
+ }
113
+ catch (err) {
114
+ // console.log("OINODbMariadb._query err=" + err);
115
+ throw err;
116
+ }
117
+ finally {
118
+ if (connection) {
119
+ await connection.end();
120
+ }
121
+ }
122
+ // OINOLog.debug("OINODbMariadb._query", {result:query_result})
123
+ }
124
+ async _exec(sql) {
125
+ // OINOLog.debug("OINODbMariadb._exec", {sql:sql})
126
+ let connection = null;
127
+ try {
128
+ connection = await this._pool.getConnection();
129
+ const result = await connection.query(sql);
130
+ // console.log(result);
131
+ return Promise.resolve(result);
132
+ }
133
+ catch (err) {
134
+ const msg_parts = err.message.match(OINODbMariadb._exceptionMessageRegex) || [];
135
+ // OINOLog.debug("OINODbMariadb._exec exception", {connection: msg_parts[1], message:msg_parts[2], sql:msg_parts[3]}) // print connection info just to log so tests don't break on runtime output
136
+ throw new Error(msg_parts[2]);
137
+ }
138
+ finally {
139
+ if (connection) {
140
+ await connection.end();
141
+ }
142
+ }
143
+ // OINOLog.debug("OINODbMariadb._query", {result:query_result})
144
+ }
145
+ /**
146
+ * Print a table name using database specific SQL escaping.
147
+ *
148
+ * @param sqlTable name of the table
149
+ *
150
+ */
151
+ printSqlTablename(sqlTable) {
152
+ return "`" + sqlTable + "`";
153
+ }
154
+ /**
155
+ * Print a column name with correct SQL escaping.
156
+ *
157
+ * @param sqlColumn name of the column
158
+ *
159
+ */
160
+ printSqlColumnname(sqlColumn) {
161
+ return "`" + sqlColumn + "`";
162
+ }
163
+ /**
164
+ * Print a single data value from serialization using the context of the native data
165
+ * type with the correct SQL escaping.
166
+ *
167
+ * @param cellValue data from sql results
168
+ * @param sqlType native type name for table column
169
+ *
170
+ */
171
+ printCellAsSqlValue(cellValue, sqlType) {
172
+ // OINOLog.debug("OINODbMariadb.printCellAsSqlValue", {cellValue:cellValue, sqlType:sqlType})
173
+ if (cellValue === null) {
174
+ return "NULL";
175
+ }
176
+ else if (cellValue === undefined) {
177
+ return "UNDEFINED";
178
+ }
179
+ else if ((sqlType == "int") || (sqlType == "smallint") || (sqlType == "float")) {
180
+ return cellValue.toString();
181
+ }
182
+ else if ((sqlType == "longblob") || (sqlType == "binary") || (sqlType == "varbinary")) {
183
+ return "\"" + cellValue + "\"";
184
+ }
185
+ else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
186
+ return "\"" + cellValue.toISOString().replace('T', ' ').substring(0, 23) + "\"";
187
+ }
188
+ else if ((sqlType == "bit")) {
189
+ if ((cellValue === false) || (cellValue == null) || (cellValue == "") || (cellValue.toString().toLowerCase() == "false") || (cellValue == "0")) {
190
+ return "b'0'";
191
+ }
192
+ else if ((cellValue === true) || (cellValue.toString().toLowerCase() == "true")) {
193
+ return "b'1'";
194
+ }
195
+ else {
196
+ return "b'" + cellValue.toString() + "'"; // rest is assumed to be a valid bitstring
197
+ }
198
+ }
199
+ else {
200
+ return "\"" + cellValue?.toString().replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\r", "\\r").replaceAll("\n", "\\n").replaceAll("\t", "\\t") + "\"";
201
+ }
202
+ }
203
+ /**
204
+ * Parse a single SQL result value for serialization using the context of the native data
205
+ * type.
206
+ *
207
+ * @param sqlValue data from serialization
208
+ * @param sqlType native type name for table column
209
+ *
210
+ */
211
+ parseSqlValueAsCell(sqlValue, sqlType) {
212
+ // OINOLog.debug("OINODbMariadb.parseSqlValueAsCell", {sqlValue:sqlValue, sqlType:sqlType})
213
+ if ((sqlValue === null) || (sqlValue == "NULL")) {
214
+ return null;
215
+ }
216
+ else if (sqlValue === undefined) {
217
+ return undefined;
218
+ }
219
+ else if (((sqlType == "date")) && (typeof (sqlValue) == "string")) {
220
+ return new Date(sqlValue);
221
+ }
222
+ else if ((sqlType == "bit") && (sqlValue instanceof Buffer)) { // mariadb returns a buffer for bit-fields
223
+ const buf = sqlValue;
224
+ let result = "";
225
+ for (let i = 0; i < buf.length; i++) {
226
+ result += buf[i].toString(2).padStart(8, '0');
227
+ }
228
+ return result;
229
+ }
230
+ else {
231
+ return sqlValue;
232
+ }
233
+ }
234
+ /**
235
+ * Connect to database.
236
+ *
237
+ */
238
+ async connect() {
239
+ try {
240
+ // make sure that any items are correctly URL encoded in the connection string
241
+ // OINOLog.debug("OINODbMariadb.connect")
242
+ await this._pool.on;
243
+ // await this._client.connect()
244
+ return Promise.resolve(true);
245
+ }
246
+ catch (err) {
247
+ // ... error checks
248
+ throw new Error(OINO_ERROR_PREFIX + ": Error connecting to Postgresql server: " + err);
249
+ }
250
+ }
251
+ /**
252
+ * Execute a select operation.
253
+ *
254
+ * @param sql SQL statement.
255
+ *
256
+ */
257
+ async sqlSelect(sql) {
258
+ OINOBenchmark.start("sqlSelect");
259
+ let result;
260
+ try {
261
+ const sql_res = await this._query(sql);
262
+ // OINOLog.debug("OINODbMariadb.sqlSelect", {sql_res:sql_res})
263
+ result = new OINOMariadbData(sql_res, []);
264
+ }
265
+ catch (e) {
266
+ result = new OINOMariadbData([[]], [OINO_ERROR_PREFIX + " (sqlSelect): OINODbMariadb.sqlSelect exception in _db.query: " + e.message]);
267
+ }
268
+ OINOBenchmark.end("sqlSelect");
269
+ return result;
270
+ }
271
+ /**
272
+ * Execute other sql operations.
273
+ *
274
+ * @param sql SQL statement.
275
+ *
276
+ */
277
+ async sqlExec(sql) {
278
+ OINOBenchmark.start("sqlExec");
279
+ let result;
280
+ try {
281
+ const sql_res = await this._exec(sql);
282
+ // OINOLog.debug("OINODbMariadb.sqlExec", {sql_res:sql_res})
283
+ result = new OINOMariadbData(sql_res, []);
284
+ }
285
+ catch (e) {
286
+ result = new OINOMariadbData([[]], [OINO_ERROR_PREFIX + " (sqlExec): exception in _db.exec [" + e.message + "]"]);
287
+ }
288
+ OINOBenchmark.end("sqlExec");
289
+ return result;
290
+ }
291
+ /**
292
+ * Initialize a data model by getting the SQL schema and populating OINODbDataFields of
293
+ * the model.
294
+ *
295
+ * @param api api which data model to initialize.
296
+ *
297
+ */
298
+ async initializeApiDatamodel(api) {
299
+ const res = await this.sqlSelect(OINODbMariadb._tableSchemaSql + this._params.database + "." + api.params.tableName + ";");
300
+ while (!res.isEof()) {
301
+ const row = res.getRow();
302
+ // OINOLog.debug("OINODbMariadb.initializeApiDatamodel", { description:row })
303
+ const field_name = row[0]?.toString() || "";
304
+ const field_matches = OINODbMariadb._fieldLengthRegex.exec(row[1]?.toString() || "") || [];
305
+ // OINOLog.debug("OINODbMariadb.initializeApiDatamodel", { field_matches:field_matches })
306
+ const sql_type = field_matches[1] || "";
307
+ const field_length1 = this._parseFieldLength(field_matches[3] || "0");
308
+ const field_length2 = this._parseFieldLength(field_matches[4] || "0");
309
+ const extra = row[5]?.toString() || "";
310
+ const field_params = {
311
+ isPrimaryKey: row[3] == "PRI",
312
+ isAutoInc: extra.indexOf('auto_increment') >= 0,
313
+ isNotNull: row[2] == "NO"
314
+ };
315
+ if (((api.params.excludeFieldPrefix) && field_name.startsWith(api.params.excludeFieldPrefix)) || ((api.params.excludeFields) && (api.params.excludeFields.indexOf(field_name) < 0))) {
316
+ OINOLog.info("OINODbMariadb.initializeApiDatamodel: field excluded in API parameters.", { field: field_name });
317
+ }
318
+ else {
319
+ // OINOLog.debug("OINODbMariadb.initializeApiDatamodel: next field ", {field_name: field_name, sql_type:sql_type, field_length1:field_length1, field_length2:field_length2, field_params:field_params })
320
+ if ((sql_type == "int") || (sql_type == "smallint") || (sql_type == "float") || (sql_type == "double")) {
321
+ api.datamodel.addField(new OINONumberDataField(this, field_name, sql_type, field_params));
322
+ }
323
+ else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "timestamp")) {
324
+ if (api.params.useDatesAsString) {
325
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
326
+ }
327
+ else {
328
+ api.datamodel.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params));
329
+ }
330
+ }
331
+ else if ((sql_type == "char") || (sql_type == "varchar") || (sql_type == "tinytext") || (sql_type == "tinytext") || (sql_type == "mediumtext") || (sql_type == "longtext")) {
332
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1));
333
+ }
334
+ else if ((sql_type == "longblob") || (sql_type == "binary") || (sql_type == "varbinary")) {
335
+ api.datamodel.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length1));
336
+ }
337
+ else if ((sql_type == "decimal")) {
338
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 + field_length2 + 1));
339
+ }
340
+ else if ((sql_type == "bit")) {
341
+ if (field_length1 == 1) {
342
+ api.datamodel.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params));
343
+ }
344
+ else {
345
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 * 8));
346
+ }
347
+ }
348
+ else {
349
+ OINOLog.info("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 });
350
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
351
+ }
352
+ }
353
+ res.next();
354
+ }
355
+ OINOLog.debug("OINODbMariadb.initializeDatasetModel:\n" + api.datamodel.printDebug("\n"));
356
+ return Promise.resolve();
357
+ }
358
+ }
@@ -0,0 +1 @@
1
+ export { OINODbMariadb } from "./OINODbMariadb.js";
@@ -0,0 +1,78 @@
1
+ import { OINODb, OINODbParams, OINODbDataSet, OINODbApi, OINODataCell } from "@oino-ts/db";
2
+ /**
3
+ * Implementation of MariaDb/MySql-database.
4
+ *
5
+ */
6
+ export declare class OINODbMariadb extends OINODb {
7
+ private static _fieldLengthRegex;
8
+ private static _exceptionMessageRegex;
9
+ private static _tableSchemaSql;
10
+ private _pool;
11
+ /**
12
+ * Constructor of `OINODbMariadb`
13
+ * @param params database parameters
14
+ */
15
+ constructor(params: OINODbParams);
16
+ private _parseFieldLength;
17
+ private _query;
18
+ private _exec;
19
+ /**
20
+ * Print a table name using database specific SQL escaping.
21
+ *
22
+ * @param sqlTable name of the table
23
+ *
24
+ */
25
+ printSqlTablename(sqlTable: string): string;
26
+ /**
27
+ * Print a column name with correct SQL escaping.
28
+ *
29
+ * @param sqlColumn name of the column
30
+ *
31
+ */
32
+ printSqlColumnname(sqlColumn: string): string;
33
+ /**
34
+ * Print a single data value from serialization using the context of the native data
35
+ * type with the correct SQL escaping.
36
+ *
37
+ * @param cellValue data from sql results
38
+ * @param sqlType native type name for table column
39
+ *
40
+ */
41
+ printCellAsSqlValue(cellValue: OINODataCell, sqlType: string): string;
42
+ /**
43
+ * Parse a single SQL result value for serialization using the context of the native data
44
+ * type.
45
+ *
46
+ * @param sqlValue data from serialization
47
+ * @param sqlType native type name for table column
48
+ *
49
+ */
50
+ parseSqlValueAsCell(sqlValue: OINODataCell, sqlType: string): OINODataCell;
51
+ /**
52
+ * Connect to database.
53
+ *
54
+ */
55
+ connect(): Promise<boolean>;
56
+ /**
57
+ * Execute a select operation.
58
+ *
59
+ * @param sql SQL statement.
60
+ *
61
+ */
62
+ sqlSelect(sql: string): Promise<OINODbDataSet>;
63
+ /**
64
+ * Execute other sql operations.
65
+ *
66
+ * @param sql SQL statement.
67
+ *
68
+ */
69
+ sqlExec(sql: string): Promise<OINODbDataSet>;
70
+ /**
71
+ * Initialize a data model by getting the SQL schema and populating OINODbDataFields of
72
+ * the model.
73
+ *
74
+ * @param api api which data model to initialize.
75
+ *
76
+ */
77
+ initializeApiDatamodel(api: OINODbApi): Promise<void>;
78
+ }
@@ -0,0 +1 @@
1
+ export { OINODbMariadb } from "./OINODbMariadb.js";
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@oino-ts/db-mariadb",
3
+ "version": "0.0.11",
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.0.11",
25
+ "mariadb": "3.2.3"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.12.7",
29
+ "@types/bun": "latest"
30
+ },
31
+ "files": [
32
+ "src/*.ts",
33
+ "dist/cjs/*.js",
34
+ "dist/esm/*.js",
35
+ "dist/types/*.d.ts"
36
+ ]
37
+ }