@oino-ts/db-bunsqlite 0.21.2 → 1.0.0

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.
@@ -1,391 +1,395 @@
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 { OINO_ERROR_PREFIX, OINOBenchmark, OINOStr, OINOLog, OINOResult } from "@oino-ts/common";
8
- import { OINODb, OINODbParams, OINODbDataSet, OINODbApi, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODbDataFieldParams, OINODbMemoryDataSet, OINODataCell, OINOBlobDataField, OINODatetimeDataField, OINODB_EMPTY_ROWS } from "@oino-ts/db";
9
-
10
- import { Database as BunSqliteDb } from "bun:sqlite";
11
-
12
- /**
13
- * Implmentation of OINODbDataSet for BunSqlite.
14
- *
15
- */
16
- class OINOBunSqliteDataset extends OINODbMemoryDataSet {
17
- constructor(data: unknown, messages:string[]=[]) {
18
- super(data, messages)
19
- }
20
- }
21
-
22
- /**
23
- * Implementation of BunSqlite-database.
24
- *
25
- */
26
- export class OINODbBunSqlite extends OINODb {
27
- private static _tableDescriptionRegex = /^CREATE TABLE\s*[\"\[]?\w+[\"\]]?\s*\(\s*(.*)\s*\)\s*(WITHOUT ROWID)?$/msi
28
- private static _tablePrimarykeyRegex = /PRIMARY KEY \(([^\)]+)\)/i
29
- private static _tableForeignkeyRegex = /FOREIGN KEY \(\[([^\)]+)\]\)/i
30
- private static _tableFieldTypeRegex = /[\"\[\s]?(\w+)[\"\]\s]\s?(INTEGER|REAL|DOUBLE|NUMERIC|DECIMAL|TEXT|BLOB|VARCHAR|DATETIME|DATE|BOOLEAN)(\s?\((\d+)\s?\,?\s?(\d*)?\))?/i
31
-
32
- private _db:BunSqliteDb|null
33
-
34
- /**
35
- * OINODbBunSqlite constructor
36
- * @param params database parameters
37
- */
38
- constructor(params:OINODbParams) {
39
- super(params)
40
- this._db = null
41
- if (!this._params.url.startsWith("file://")) {
42
- throw new Error(OINO_ERROR_PREFIX + ": OINODbBunSqlite url must be a file://-url!")
43
- }
44
-
45
- if (this._params.type !== "OINODbBunSqlite") {
46
- throw new Error(OINO_ERROR_PREFIX + ": Not OINODbBunSqlite-type: " + this._params.type)
47
- }
48
- }
49
-
50
- private _parseDbFieldParams(fieldStr:string): OINODbDataFieldParams {
51
- const result:OINODbDataFieldParams = {
52
- isPrimaryKey: fieldStr.indexOf("PRIMARY KEY") >= 0,
53
- isForeignKey: false,
54
- isAutoInc: fieldStr.indexOf("AUTOINCREMENT") >= 0,
55
- isNotNull: fieldStr.indexOf("NOT NULL") >= 0
56
- }
57
- return result
58
- }
59
-
60
- /**
61
- * Print a table name using database specific SQL escaping.
62
- *
63
- * @param sqlTable name of the table
64
- *
65
- */
66
- printSqlTablename(sqlTable:string): string {
67
- return "["+sqlTable+"]"
68
- }
69
-
70
- /**
71
- * Print a column name with correct SQL escaping.
72
- *
73
- * @param sqlColumn name of the column
74
- *
75
- */
76
- printSqlColumnname(sqlColumn:string): string {
77
- return "\""+sqlColumn+"\""
78
- }
79
-
80
- /**
81
- * Print a single data value from serialization using the context of the native data
82
- * type with the correct SQL escaping.
83
- *
84
- * @param cellValue data from sql results
85
- * @param sqlType native type name for table column
86
- *
87
- */
88
- printCellAsSqlValue(cellValue:OINODataCell, sqlType: string): string {
89
- if (cellValue === null) {
90
- return "NULL"
91
-
92
- } else if (cellValue === undefined) {
93
- return "UNDEFINED"
94
-
95
- } else if ((sqlType == "INTEGER") || (sqlType == "REAL") || (sqlType == "DOUBLE" || (sqlType == "NUMERIC") || (sqlType == "DECIMAL"))) {
96
- return cellValue.toString()
97
-
98
- } else if (sqlType == "BLOB") {
99
- if (cellValue instanceof Buffer) {
100
- return "X'" + (cellValue as Buffer).toString("hex") + "'"
101
- } else if (cellValue instanceof Uint8Array) {
102
- return "X'" + Buffer.from(cellValue as Uint8Array).toString("hex") + "'"
103
- } else {
104
- return "'" + cellValue?.toString() + "'"
105
- }
106
-
107
- } else if (((sqlType == "DATETIME") || (sqlType == "DATE")) && (cellValue instanceof Date)) {
108
- return "\'" + cellValue.toISOString() + "\'"
109
-
110
- } else {
111
- return this.printSqlString(cellValue.toString())
112
- }
113
- }
114
-
115
- /**
116
- * Print a single string value as valid sql literal
117
- *
118
- * @param sqlString string value
119
- *
120
- */
121
- printSqlString(sqlString:string): string {
122
- return "\"" + sqlString.replaceAll("\"", "\"\"") + "\""
123
- }
124
-
125
- /**
126
- * Parse a single SQL result value for serialization using the context of the native data
127
- * type.
128
- *
129
- * @param sqlValue data from serialization
130
- * @param sqlType native type name for table column
131
- *
132
- */
133
- parseSqlValueAsCell(sqlValue:OINODataCell, sqlType: string): OINODataCell {
134
- if ((sqlValue === null) || (sqlValue == "NULL")) {
135
- return null
136
-
137
- } else if (sqlValue === undefined) {
138
- return undefined
139
-
140
- } else if (((sqlType == "DATETIME") || (sqlType == "DATE")) && (typeof(sqlValue) == "string") && (sqlValue != "")) {
141
- return new Date(sqlValue)
142
-
143
- } else if ((sqlType == "BOOLEAN")) {
144
- return sqlValue == 1
145
-
146
- } else if ((sqlType == "BLOB")) {
147
- if (sqlValue instanceof Uint8Array) {
148
- return Buffer.from(sqlValue)
149
- } else {
150
- return sqlValue
151
- }
152
- } else {
153
- return sqlValue
154
- }
155
-
156
- }
157
-
158
-
159
- /**
160
- * Connect to database.
161
- *
162
- */
163
- async connect(): Promise<OINOResult> {
164
- OINOBenchmark.startMetric("OINODb", "connect")
165
- let result:OINOResult = new OINOResult()
166
- if (this.isConnected) {
167
- return result
168
- }
169
- const filepath:string = this._params.url.substring(7)
170
- try {
171
- this._db = BunSqliteDb.open(filepath, { create: true, readonly: false, readwrite: true })
172
- this.isConnected = true
173
- } catch (e:any) {
174
- result.setError(500, "Exception connecting to database: " + e.message, "OINODbBunSqlite.connect")
175
- OINOLog.exception("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "connect", "exception in connect", {message:e.message, stack:e.stack})
176
- }
177
- OINOBenchmark.endMetric("OINODb", "connect", result.status != 500)
178
- return result
179
- }
180
-
181
- /**
182
- * Validate connection to database is working.
183
- *
184
- */
185
- async validate(): Promise<OINOResult> {
186
- if (!this.isConnected) {
187
- return new OINOResult().setError(400, "Database not connected!", "OINODbBunSqlite.validate")
188
- }
189
- OINOBenchmark.startMetric("OINODb", "validate")
190
- let result:OINOResult = new OINOResult()
191
- try {
192
- this.isValidated = false
193
- const sql = this._getValidateSql(this._params.database)
194
- const sql_res:OINODbDataSet = await this._query(sql)
195
- if (sql_res.isEmpty()) {
196
- result.setError(400, "DB returned no rows for select!", "OINODbBunSqlite.validate")
197
-
198
- } else if (sql_res.getRow().length == 0) {
199
- result.setError(400, "DB returned no values for database!", "OINODbBunSqlite.validate")
200
-
201
- } else if (sql_res.getRow()[0] == "0") {
202
- result.setError(400, "DB returned no schema for database!", "OINODbBunSqlite.validate")
203
-
204
- } else {
205
- this.isValidated = true
206
- }
207
- } catch (e:any) {
208
- result.setError(500, OINO_ERROR_PREFIX + " (OINODbBunSqlite.validate): Exception in db query: " + e.message, "OINODbBunSqlite.validate")
209
- }
210
- OINOBenchmark.endMetric("OINODb", "validate", result.status != 500)
211
- return result
212
- }
213
-
214
- /**
215
- * Connect to database.
216
- *
217
- */
218
- async disconnect(): Promise<void> {
219
- this.isConnected = false
220
- this.isValidated = false
221
- }
222
-
223
-
224
- private async _query(sql:string): Promise<OINODbDataSet> {
225
- let result:OINODbDataSet
226
- try {
227
- const sql_res = this._db?.query(sql).values()
228
- if (sql_res) {
229
- // console.log("OINODbBunSqlite._query: res", sql_res)
230
- result = new OINOBunSqliteDataset(sql_res, [])
231
- } else {
232
- result = new OINOBunSqliteDataset(OINODB_EMPTY_ROWS, [])
233
- }
234
-
235
- } catch (e:any) {
236
- result = new OINOBunSqliteDataset(OINODB_EMPTY_ROWS, []).setError(500, OINO_ERROR_PREFIX + " (OINODbBunSqlite._query): Exception in db query: " + e.message, "OINODbBunSqlite._query") as OINOBunSqliteDataset
237
- }
238
- return result
239
- }
240
- private async _exec(sql:string): Promise<OINODbDataSet> {
241
- let result:OINODbDataSet
242
- try {
243
- const sql_res = this._db?.query(sql).values()
244
- if (sql_res) {
245
- // console.log("OINODbBunSqlite._exec: res", sql_res)
246
- result = new OINOBunSqliteDataset(sql_res, [])
247
- } else {
248
- result = new OINOBunSqliteDataset(OINODB_EMPTY_ROWS, [])
249
- }
250
-
251
- } catch (e:any) {
252
- result = new OINOBunSqliteDataset(OINODB_EMPTY_ROWS, []).setError(500, OINO_ERROR_PREFIX + ": Exception in db exec: " + e.message, "OINODbBunSqlite._exec") as OINOBunSqliteDataset
253
- }
254
- return result
255
- }
256
-
257
- /**
258
- * Execute a select operation.
259
- *
260
- * @param sql SQL statement.
261
- *
262
- */
263
- async sqlSelect(sql:string): Promise<OINODbDataSet> {
264
- if (!this.isValidated) {
265
- throw new Error(OINO_ERROR_PREFIX + ": Database connection not validated!")
266
- }
267
- OINOBenchmark.startMetric("OINODb", "sqlSelect")
268
- let result:OINODbDataSet = await this._query(sql)
269
- OINOBenchmark.endMetric("OINODb", "sqlSelect", result.status != 500)
270
- return result
271
- }
272
-
273
- /**
274
- * Execute other sql operations.
275
- *
276
- * @param sql SQL statement.
277
- *
278
- */
279
- async sqlExec(sql:string): Promise<OINODbDataSet> {
280
- if (!this.isValidated) {
281
- return new OINOBunSqliteDataset(OINODB_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (OINODbBunSqlite.sqlExec): Database connection not validated!"])
282
- }
283
- OINOBenchmark.startMetric("OINODb", "sqlExec")
284
- let result:OINODbDataSet = await this._exec(sql)
285
- OINOBenchmark.endMetric("OINODb", "sqlExec", result.status != 500)
286
- return result
287
- }
288
-
289
- private _getSchemaSql(dbName:string, tableName:string):string {
290
- const sql = "SELECT sql from sqlite_schema WHERE name='" + tableName + "'"
291
- return sql
292
- }
293
-
294
- private _getValidateSql(dbName:string):string {
295
- const sql = "SELECT count(*) as COLUMN_COUNT from sqlite_schema"
296
- return sql
297
- }
298
-
299
- /**
300
- * Initialize a data model by getting the SQL schema and populating OINODbDataFields of
301
- * the model.
302
- *
303
- * @param api api which data model to initialize.
304
- *
305
- */
306
- async initializeApiDatamodel(api:OINODbApi): Promise<void> {
307
- const schema_sql:string = this._getSchemaSql(this._params.database, api.params.tableName)
308
- const res:OINODbDataSet|null = await this._query(schema_sql)
309
- const sql_desc:string = (res?.getRow()[0]) as string
310
- const excluded_fields:string[] = []
311
- let table_matches = OINODbBunSqlite._tableDescriptionRegex.exec(sql_desc)
312
- if (!table_matches || table_matches?.length < 2) {
313
- throw new Error("Table " + api.params.tableName + " not recognized as a valid Sqlite table!")
314
-
315
- } else {
316
- let field_strings:string[] = OINOStr.splitExcludingBrackets(table_matches[1], ',', '(', ')')
317
- for (let field_str of field_strings) {
318
- field_str = field_str.trim()
319
- let field_params = this._parseDbFieldParams(field_str)
320
- let field_match = OINODbBunSqlite._tableFieldTypeRegex.exec(field_str)
321
- // console.log("OINODbBunSqlite.initializeApiDatamodel: field_match", field_match)
322
- if ((!field_match) || (field_match.length < 3)) {
323
- let primarykey_match = OINODbBunSqlite._tablePrimarykeyRegex.exec(field_str)
324
- let foreignkey_match = OINODbBunSqlite._tableForeignkeyRegex.exec(field_str)
325
- if (primarykey_match && primarykey_match.length >= 2) {
326
- const primary_keys:string[] = primarykey_match[1].replaceAll("\"", "").split(',') // not sure if will have space or not so split by comma and trim later
327
- for (let i:number=0; i<primary_keys.length; i++) {
328
- const pk:string = primary_keys[i].trim() //..the trim
329
- if (excluded_fields.indexOf(pk) >= 0) {
330
- throw new Error(OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + pk)
331
- }
332
- for (let j:number=0; j<api.datamodel.fields.length; j++) {
333
- if (api.datamodel.fields[j].name == pk) {
334
- api.datamodel.fields[j].fieldParams.isPrimaryKey = true
335
- }
336
- }
337
- }
338
-
339
- } else if (foreignkey_match && foreignkey_match.length >= 2) {
340
- const fk:string = foreignkey_match[1].trim()
341
- for (let j:number=0; j<api.datamodel.fields.length; j++) {
342
- if (api.datamodel.fields[j].name == fk) {
343
- api.datamodel.fields[j].fieldParams.isForeignKey = true
344
- }
345
- }
346
-
347
- } else {
348
- OINOLog.info("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "initializeApiDatamodel", "Unsupported field definition skipped.", { field: field_str })
349
- }
350
-
351
- } else {
352
- // field_str = "NAME TYPE (M, N)" -> 1:NAME, 2:TYPE, 4:M, 5:N
353
- const field_name:string = field_match[1]
354
- const sql_type:string = field_match[2]
355
- const field_length:number = parseInt(field_match[4]) || 0
356
- if (api.isFieldIncluded(field_name) == false) {
357
- excluded_fields.push(field_name)
358
- OINOLog.info("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "initializeApiDatamodel", "Field excluded in API parameters.", {field:field_name})
359
-
360
- } else {
361
- if ((sql_type == "INTEGER") || (sql_type == "REAL") || (sql_type == "DOUBLE") || (sql_type == "NUMERIC") || (sql_type == "DECIMAL")) {
362
- api.datamodel.addField(new OINONumberDataField(this, field_name, sql_type, field_params ))
363
-
364
- } else if ((sql_type == "BLOB") ) {
365
- api.datamodel.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length))
366
-
367
- } else if ((sql_type == "TEXT")) {
368
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length))
369
-
370
- } else if ((sql_type == "DATETIME") || (sql_type == "DATE")) {
371
- if (api.params.useDatesAsString) {
372
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
373
- } else {
374
- api.datamodel.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params))
375
- }
376
-
377
- } else if ((sql_type == "BOOLEAN")) {
378
- api.datamodel.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params))
379
-
380
- } else {
381
- OINOLog.info("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "initializeApiDatamodel", "Unrecognized field type treated as string", {field_name: field_name, sql_type:sql_type, field_length:field_length, field_params:field_params })
382
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
383
- }
384
- }
385
- }
386
- };
387
- OINOLog.info("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "initializeApiDatamodel", "\n" + api.datamodel.printDebug("\n"))
388
- return Promise.resolve()
389
- }
390
- }
391
- }
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 { Buffer } from "node:buffer"
8
+
9
+ import { OINO_ERROR_PREFIX, OINOBenchmark, OINOStr, OINOLog, OINOResult, OINODataSet, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODataFieldParams, OINOMemoryDataset, OINODataCell, OINOBlobDataField, OINODatetimeDataField, OINO_EMPTY_ROWS } from "@oino-ts/common";
10
+
11
+ import { OINODb, OINODbApi, OINODbParams, OINODbDataModel } from "@oino-ts/db";
12
+
13
+ import { Database as BunSqliteDb } from "bun:sqlite";
14
+
15
+ /**
16
+ * Implmentation of OINODataSet for BunSqlite.
17
+ *
18
+ */
19
+ class OINOBunSqliteDataset extends OINOMemoryDataset {
20
+ constructor(data: unknown, messages:string[]=[]) {
21
+ super(data, messages)
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Implementation of BunSqlite-database.
27
+ *
28
+ */
29
+ export class OINODbBunSqlite extends OINODb {
30
+ private static _tableDescriptionRegex = /^CREATE TABLE\s*[\"\[]?\w+[\"\]]?\s*\(\s*(.*)\s*\)\s*(WITHOUT ROWID)?$/msi
31
+ private static _tablePrimarykeyRegex = /PRIMARY KEY \(([^\)]+)\)/i
32
+ private static _tableForeignkeyRegex = /FOREIGN KEY \(\[([^\)]+)\]\)/i
33
+ private static _tableFieldTypeRegex = /[\"\[\s]?(\w+)[\"\]\s]\s?(INTEGER|REAL|DOUBLE|NUMERIC|DECIMAL|TEXT|BLOB|VARCHAR|DATETIME|DATE|BOOLEAN)(\s?\((\d+)\s?\,?\s?(\d*)?\))?/i
34
+
35
+ private _db:BunSqliteDb|null
36
+
37
+ /**
38
+ * OINODbBunSqlite constructor
39
+ * @param params database parameters
40
+ */
41
+ constructor(params:OINODbParams) {
42
+ super(params)
43
+ this._db = null
44
+ if (!this.dbParams.url.startsWith("file://")) {
45
+ throw new Error(OINO_ERROR_PREFIX + ": OINODbBunSqlite url must be a file://-url!")
46
+ }
47
+
48
+ if (this.dbParams.type !== "OINODbBunSqlite") {
49
+ throw new Error(OINO_ERROR_PREFIX + ": Not OINODbBunSqlite-type: " + this.dbParams.type)
50
+ }
51
+ }
52
+
53
+ private _parseDbFieldParams(fieldStr:string): OINODataFieldParams {
54
+ const result:OINODataFieldParams = {
55
+ isPrimaryKey: fieldStr.indexOf("PRIMARY KEY") >= 0,
56
+ isForeignKey: false,
57
+ isAutoInc: fieldStr.indexOf("AUTOINCREMENT") >= 0,
58
+ isNotNull: fieldStr.indexOf("NOT NULL") >= 0
59
+ }
60
+ return result
61
+ }
62
+
63
+ /**
64
+ * Print a table name using database specific SQL escaping.
65
+ *
66
+ * @param sqlTable name of the table
67
+ *
68
+ */
69
+ printTableName(sqlTable:string): string {
70
+ return "["+sqlTable+"]"
71
+ }
72
+
73
+ /**
74
+ * Print a column name with correct SQL escaping.
75
+ *
76
+ * @param sqlColumn name of the column
77
+ *
78
+ */
79
+ printColumnName(sqlColumn:string): string {
80
+ return "\""+sqlColumn+"\""
81
+ }
82
+
83
+ /**
84
+ * Print a single data value from serialization using the context of the native data
85
+ * type with the correct SQL escaping.
86
+ *
87
+ * @param cellValue data from sql results
88
+ * @param nativeType native type name for table column
89
+ *
90
+ */
91
+ printCellAsValue(cellValue:OINODataCell, nativeType: string): string {
92
+ if (cellValue === null) {
93
+ return "NULL"
94
+
95
+ } else if (cellValue === undefined) {
96
+ return "UNDEFINED"
97
+
98
+ } else if ((nativeType == "INTEGER") || (nativeType == "REAL") || (nativeType == "DOUBLE" || (nativeType == "NUMERIC") || (nativeType == "DECIMAL"))) {
99
+ return cellValue.toString()
100
+
101
+ } else if (nativeType == "BLOB") {
102
+ if (cellValue instanceof Buffer) {
103
+ return "X'" + (cellValue as Buffer).toString("hex") + "'"
104
+ } else if (cellValue instanceof Uint8Array) {
105
+ return "X'" + Buffer.from(cellValue as Uint8Array).toString("hex") + "'"
106
+ } else {
107
+ return "'" + cellValue?.toString() + "'"
108
+ }
109
+
110
+ } else if (((nativeType == "DATETIME") || (nativeType == "DATE")) && (cellValue instanceof Date)) {
111
+ return "\'" + cellValue.toISOString() + "\'"
112
+
113
+ } else {
114
+ return this.printStringValue(cellValue.toString())
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Print a single string value as valid sql literal
120
+ *
121
+ * @param sqlString string value
122
+ *
123
+ */
124
+ printStringValue(sqlString:string): string {
125
+ return "\"" + sqlString.replaceAll("\"", "\"\"") + "\""
126
+ }
127
+
128
+ /**
129
+ * Parse a single SQL result value for serialization using the context of the native data
130
+ * type.
131
+ *
132
+ * @param sqlValue data from serialization
133
+ * @param nativeType native type name for table column
134
+ *
135
+ */
136
+ parseValueAsCell(sqlValue:OINODataCell, nativeType: string): OINODataCell {
137
+ if ((sqlValue === null) || (sqlValue == "NULL")) {
138
+ return null
139
+
140
+ } else if (sqlValue === undefined) {
141
+ return undefined
142
+
143
+ } else if (((nativeType == "DATETIME") || (nativeType == "DATE")) && (typeof(sqlValue) == "string") && (sqlValue != "")) {
144
+ return new Date(sqlValue)
145
+
146
+ } else if ((nativeType == "BOOLEAN")) {
147
+ return sqlValue == 1
148
+
149
+ } else if ((nativeType == "BLOB")) {
150
+ if (sqlValue instanceof Uint8Array) {
151
+ return Buffer.from(sqlValue)
152
+ } else {
153
+ return sqlValue
154
+ }
155
+ } else {
156
+ return sqlValue
157
+ }
158
+
159
+ }
160
+
161
+
162
+ /**
163
+ * Connect to database.
164
+ *
165
+ */
166
+ async connect(): Promise<OINOResult> {
167
+ OINOBenchmark.startMetric("OINODb", "connect")
168
+ let result:OINOResult = new OINOResult()
169
+ if (this.isConnected) {
170
+ return result
171
+ }
172
+ const filepath:string = this.dbParams.url.substring(7)
173
+ try {
174
+ this._db = BunSqliteDb.open(filepath, { create: true, readonly: false, readwrite: true })
175
+ this.isConnected = true
176
+ } catch (e:any) {
177
+ result.setError(500, "Exception connecting to database: " + e.message, "OINODbBunSqlite.connect")
178
+ OINOLog.exception("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "connect", "exception in connect", {message:e.message, stack:e.stack})
179
+ }
180
+ OINOBenchmark.endMetric("OINODb", "connect", result.status != 500)
181
+ return result
182
+ }
183
+
184
+ /**
185
+ * Validate connection to database is working.
186
+ *
187
+ */
188
+ async validate(): Promise<OINOResult> {
189
+ if (!this.isConnected) {
190
+ return new OINOResult().setError(400, "Database not connected!", "OINODbBunSqlite.validate")
191
+ }
192
+ OINOBenchmark.startMetric("OINODb", "validate")
193
+ let result:OINOResult = new OINOResult()
194
+ try {
195
+ this.isValidated = false
196
+ const sql = this._getValidateSql(this.dbParams.database)
197
+ const sql_res:OINODataSet = await this._query(sql)
198
+ if (sql_res.isEmpty()) {
199
+ result.setError(400, "DB returned no rows for select!", "OINODbBunSqlite.validate")
200
+
201
+ } else if (sql_res.getRow().length == 0) {
202
+ result.setError(400, "DB returned no values for database!", "OINODbBunSqlite.validate")
203
+
204
+ } else if (sql_res.getRow()[0] == "0") {
205
+ result.setError(400, "DB returned no schema for database!", "OINODbBunSqlite.validate")
206
+
207
+ } else {
208
+ this.isValidated = true
209
+ }
210
+ } catch (e:any) {
211
+ result.setError(500, OINO_ERROR_PREFIX + " (OINODbBunSqlite.validate): Exception in db query: " + e.message, "OINODbBunSqlite.validate")
212
+ }
213
+ OINOBenchmark.endMetric("OINODb", "validate", result.status != 500)
214
+ return result
215
+ }
216
+
217
+ /**
218
+ * Connect to database.
219
+ *
220
+ */
221
+ async disconnect(): Promise<void> {
222
+ this.isConnected = false
223
+ this.isValidated = false
224
+ }
225
+
226
+
227
+ private async _query(sql:string): Promise<OINODataSet> {
228
+ let result:OINODataSet
229
+ try {
230
+ const sql_res = this._db?.query(sql).values()
231
+ if (sql_res) {
232
+ // console.log("OINODbBunSqlite._query: res", sql_res)
233
+ result = new OINOBunSqliteDataset(sql_res, [])
234
+ } else {
235
+ result = new OINOBunSqliteDataset(OINO_EMPTY_ROWS, [])
236
+ }
237
+
238
+ } catch (e:any) {
239
+ result = new OINOBunSqliteDataset(OINO_EMPTY_ROWS, []).setError(500, OINO_ERROR_PREFIX + " (OINODbBunSqlite._query): Exception in db query: " + e.message, "OINODbBunSqlite._query") as OINOBunSqliteDataset
240
+ }
241
+ return result
242
+ }
243
+ private async _exec(sql:string): Promise<OINODataSet> {
244
+ let result:OINODataSet
245
+ try {
246
+ const sql_res = this._db?.query(sql).values()
247
+ if (sql_res) {
248
+ // console.log("OINODbBunSqlite._exec: res", sql_res)
249
+ result = new OINOBunSqliteDataset(sql_res, [])
250
+ } else {
251
+ result = new OINOBunSqliteDataset(OINO_EMPTY_ROWS, [])
252
+ }
253
+
254
+ } catch (e:any) {
255
+ result = new OINOBunSqliteDataset(OINO_EMPTY_ROWS, []).setError(500, OINO_ERROR_PREFIX + ": Exception in db exec: " + e.message, "OINODbBunSqlite._exec") as OINOBunSqliteDataset
256
+ }
257
+ return result
258
+ }
259
+
260
+ /**
261
+ * Execute a select operation.
262
+ *
263
+ * @param sql SQL statement.
264
+ *
265
+ */
266
+ async sqlSelect(sql:string): Promise<OINODataSet> {
267
+ if (!this.isValidated) {
268
+ throw new Error(OINO_ERROR_PREFIX + ": Database connection not validated!")
269
+ }
270
+ OINOBenchmark.startMetric("OINODb", "sqlSelect")
271
+ let result:OINODataSet = await this._query(sql)
272
+ OINOBenchmark.endMetric("OINODb", "sqlSelect", result.status != 500)
273
+ return result
274
+ }
275
+
276
+ /**
277
+ * Execute other sql operations.
278
+ *
279
+ * @param sql SQL statement.
280
+ *
281
+ */
282
+ async sqlExec(sql:string): Promise<OINODataSet> {
283
+ if (!this.isValidated) {
284
+ return new OINOBunSqliteDataset(OINO_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (OINODbBunSqlite.sqlExec): Database connection not validated!"])
285
+ }
286
+ OINOBenchmark.startMetric("OINODb", "sqlExec")
287
+ let result:OINODataSet = await this._exec(sql)
288
+ OINOBenchmark.endMetric("OINODb", "sqlExec", result.status != 500)
289
+ return result
290
+ }
291
+
292
+ private _getSchemaSql(dbName:string, tableName:string):string {
293
+ const sql = "SELECT sql from sqlite_schema WHERE name='" + tableName + "'"
294
+ return sql
295
+ }
296
+
297
+ private _getValidateSql(dbName:string):string {
298
+ const sql = "SELECT count(*) as COLUMN_COUNT from sqlite_schema"
299
+ return sql
300
+ }
301
+
302
+ /**
303
+ * Initialize a data model by getting the SQL schema and populating OINODataFields of
304
+ * the model.
305
+ *
306
+ * @param api api which data model to initialize.
307
+ *
308
+ */
309
+ async initializeApiDatamodel(api:OINODbApi): Promise<void> {
310
+ api.initializeDatamodel(new OINODbDataModel(api))
311
+ const schema_sql:string = this._getSchemaSql(this.dbParams.database, api.params.tableName)
312
+ const res:OINODataSet|null = await this._query(schema_sql)
313
+ const sql_desc:string = (res?.getRow()[0]) as string
314
+ const excluded_fields:string[] = []
315
+ let table_matches = OINODbBunSqlite._tableDescriptionRegex.exec(sql_desc)
316
+ if (!table_matches || table_matches?.length < 2) {
317
+ throw new Error("Table " + api.params.tableName + " not recognized as a valid Sqlite table!")
318
+
319
+ } else {
320
+ let field_strings:string[] = OINOStr.splitExcludingBrackets(table_matches[1], ',', '(', ')')
321
+ for (let field_str of field_strings) {
322
+ field_str = field_str.trim()
323
+ let field_params = this._parseDbFieldParams(field_str)
324
+ let field_match = OINODbBunSqlite._tableFieldTypeRegex.exec(field_str)
325
+ // console.log("OINODbBunSqlite.initializeApiDatamodel: field_match", field_match)
326
+ if ((!field_match) || (field_match.length < 3)) {
327
+ let primarykey_match = OINODbBunSqlite._tablePrimarykeyRegex.exec(field_str)
328
+ let foreignkey_match = OINODbBunSqlite._tableForeignkeyRegex.exec(field_str)
329
+ if (primarykey_match && primarykey_match.length >= 2) {
330
+ const primary_keys:string[] = primarykey_match[1].replaceAll("\"", "").split(',') // not sure if will have space or not so split by comma and trim later
331
+ for (let i:number=0; i<primary_keys.length; i++) {
332
+ const pk:string = primary_keys[i].trim() //..the trim
333
+ if (excluded_fields.indexOf(pk) >= 0) {
334
+ throw new Error(OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + pk)
335
+ }
336
+ for (let j:number=0; j<api.datamodel!.fields.length; j++) {
337
+ if (api.datamodel!.fields[j].name == pk) {
338
+ api.datamodel!.fields[j].fieldParams.isPrimaryKey = true
339
+ }
340
+ }
341
+ }
342
+
343
+ } else if (foreignkey_match && foreignkey_match.length >= 2) {
344
+ const fk:string = foreignkey_match[1].trim()
345
+ for (let j:number=0; j<api.datamodel!.fields.length; j++) {
346
+ if (api.datamodel!.fields[j].name == fk) {
347
+ api.datamodel!.fields[j].fieldParams.isForeignKey = true
348
+ }
349
+ }
350
+
351
+ } else {
352
+ OINOLog.info("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "initializeApiDatamodel", "Unsupported field definition skipped.", { field: field_str })
353
+ }
354
+
355
+ } else {
356
+ // field_str = "NAME TYPE (M, N)" -> 1:NAME, 2:TYPE, 4:M, 5:N
357
+ const field_name:string = field_match[1]
358
+ const sql_type:string = field_match[2]
359
+ const field_length:number = parseInt(field_match[4]) || 0
360
+ if (api.isFieldIncluded(field_name) == false) {
361
+ excluded_fields.push(field_name)
362
+ OINOLog.info("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "initializeApiDatamodel", "Field excluded in API parameters.", {field:field_name})
363
+
364
+ } else {
365
+ if ((sql_type == "INTEGER") || (sql_type == "REAL") || (sql_type == "DOUBLE") || (sql_type == "NUMERIC") || (sql_type == "DECIMAL")) {
366
+ api.datamodel!.addField(new OINONumberDataField(this, field_name, sql_type, field_params ))
367
+
368
+ } else if ((sql_type == "BLOB") ) {
369
+ api.datamodel!.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length))
370
+
371
+ } else if ((sql_type == "TEXT")) {
372
+ api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length))
373
+
374
+ } else if ((sql_type == "DATETIME") || (sql_type == "DATE")) {
375
+ if (api.params.useDatesAsString) {
376
+ api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
377
+ } else {
378
+ api.datamodel!.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params))
379
+ }
380
+
381
+ } else if ((sql_type == "BOOLEAN")) {
382
+ api.datamodel!.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params))
383
+
384
+ } else {
385
+ OINOLog.info("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "initializeApiDatamodel", "Unrecognized field type treated as string", {field_name: field_name, sql_type:sql_type, field_length:field_length, field_params:field_params })
386
+ api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
387
+ }
388
+ }
389
+ }
390
+ };
391
+ OINOLog.info("@oino-ts/db-bunsqlite", "OINODbBunSqlite", "initializeApiDatamodel", "\n" + api.datamodel!.printDebug("\n"))
392
+ return Promise.resolve()
393
+ }
394
+ }
395
+ }