@oino-ts/db-postgresql 0.16.1 → 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.
@@ -306,47 +306,47 @@ class OINODbPostgresql extends db_1.OINODb {
306
306
  return result;
307
307
  }
308
308
  _getSchemaSql(dbName, tableName) {
309
- const sql = `SELECT
310
- col.column_name,
311
- col.data_type,
312
- col.character_maximum_length,
313
- col.is_nullable,
314
- con.constraint_type,
315
- col.numeric_precision,
316
- col.numeric_scale,
317
- col.column_default
318
- FROM information_schema.columns col
319
- LEFT JOIN LATERAL
320
- (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
321
- from
322
- information_schema.table_constraints tco,
323
- information_schema.key_column_usage kcu
324
- where
325
- kcu.constraint_name = tco.constraint_name
326
- and kcu.constraint_schema = tco.constraint_schema
327
- and tco.table_catalog = col.table_catalog
328
- and tco.table_name = col.table_name
329
- and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
330
- group by kcu.column_name) con on col.column_name = con.column_name
309
+ const sql = `SELECT
310
+ col.column_name,
311
+ col.data_type,
312
+ col.character_maximum_length,
313
+ col.is_nullable,
314
+ con.constraint_type,
315
+ col.numeric_precision,
316
+ col.numeric_scale,
317
+ col.column_default
318
+ FROM information_schema.columns col
319
+ LEFT JOIN LATERAL
320
+ (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
321
+ from
322
+ information_schema.table_constraints tco,
323
+ information_schema.key_column_usage kcu
324
+ where
325
+ kcu.constraint_name = tco.constraint_name
326
+ and kcu.constraint_schema = tco.constraint_schema
327
+ and tco.table_catalog = col.table_catalog
328
+ and tco.table_name = col.table_name
329
+ and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
330
+ group by kcu.column_name) con on col.column_name = con.column_name
331
331
  WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`;
332
332
  return sql;
333
333
  }
334
334
  _getValidateSql(dbName) {
335
- const sql = `SELECT
336
- count(col.column_name) AS column_count
337
- FROM information_schema.columns col
338
- LEFT JOIN LATERAL
339
- (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
340
- from
341
- information_schema.table_constraints tco,
342
- information_schema.key_column_usage kcu
343
- where
344
- kcu.constraint_name = tco.constraint_name
345
- and kcu.constraint_schema = tco.constraint_schema
346
- and tco.table_catalog = col.table_catalog
347
- and tco.table_name = col.table_name
348
- and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
349
- group by kcu.column_name) con on col.column_name = con.column_name
335
+ const sql = `SELECT
336
+ count(col.column_name) AS column_count
337
+ FROM information_schema.columns col
338
+ LEFT JOIN LATERAL
339
+ (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
340
+ from
341
+ information_schema.table_constraints tco,
342
+ information_schema.key_column_usage kcu
343
+ where
344
+ kcu.constraint_name = tco.constraint_name
345
+ and kcu.constraint_schema = tco.constraint_schema
346
+ and tco.table_catalog = col.table_catalog
347
+ and tco.table_name = col.table_name
348
+ and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
349
+ group by kcu.column_name) con on col.column_name = con.column_name
350
350
  WHERE col.table_catalog = '${dbName}'`;
351
351
  return sql;
352
352
  }
@@ -303,47 +303,47 @@ export class OINODbPostgresql extends OINODb {
303
303
  return result;
304
304
  }
305
305
  _getSchemaSql(dbName, tableName) {
306
- const sql = `SELECT
307
- col.column_name,
308
- col.data_type,
309
- col.character_maximum_length,
310
- col.is_nullable,
311
- con.constraint_type,
312
- col.numeric_precision,
313
- col.numeric_scale,
314
- col.column_default
315
- FROM information_schema.columns col
316
- LEFT JOIN LATERAL
317
- (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
318
- from
319
- information_schema.table_constraints tco,
320
- information_schema.key_column_usage kcu
321
- where
322
- kcu.constraint_name = tco.constraint_name
323
- and kcu.constraint_schema = tco.constraint_schema
324
- and tco.table_catalog = col.table_catalog
325
- and tco.table_name = col.table_name
326
- and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
327
- group by kcu.column_name) con on col.column_name = con.column_name
306
+ const sql = `SELECT
307
+ col.column_name,
308
+ col.data_type,
309
+ col.character_maximum_length,
310
+ col.is_nullable,
311
+ con.constraint_type,
312
+ col.numeric_precision,
313
+ col.numeric_scale,
314
+ col.column_default
315
+ FROM information_schema.columns col
316
+ LEFT JOIN LATERAL
317
+ (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
318
+ from
319
+ information_schema.table_constraints tco,
320
+ information_schema.key_column_usage kcu
321
+ where
322
+ kcu.constraint_name = tco.constraint_name
323
+ and kcu.constraint_schema = tco.constraint_schema
324
+ and tco.table_catalog = col.table_catalog
325
+ and tco.table_name = col.table_name
326
+ and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
327
+ group by kcu.column_name) con on col.column_name = con.column_name
328
328
  WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`;
329
329
  return sql;
330
330
  }
331
331
  _getValidateSql(dbName) {
332
- const sql = `SELECT
333
- count(col.column_name) AS column_count
334
- FROM information_schema.columns col
335
- LEFT JOIN LATERAL
336
- (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
337
- from
338
- information_schema.table_constraints tco,
339
- information_schema.key_column_usage kcu
340
- where
341
- kcu.constraint_name = tco.constraint_name
342
- and kcu.constraint_schema = tco.constraint_schema
343
- and tco.table_catalog = col.table_catalog
344
- and tco.table_name = col.table_name
345
- and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
346
- group by kcu.column_name) con on col.column_name = con.column_name
332
+ const sql = `SELECT
333
+ count(col.column_name) AS column_count
334
+ FROM information_schema.columns col
335
+ LEFT JOIN LATERAL
336
+ (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
337
+ from
338
+ information_schema.table_constraints tco,
339
+ information_schema.key_column_usage kcu
340
+ where
341
+ kcu.constraint_name = tco.constraint_name
342
+ and kcu.constraint_schema = tco.constraint_schema
343
+ and tco.table_catalog = col.table_catalog
344
+ and tco.table_name = col.table_name
345
+ and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
346
+ group by kcu.column_name) con on col.column_name = con.column_name
347
347
  WHERE col.table_catalog = '${dbName}'`;
348
348
  return sql;
349
349
  }
package/package.json CHANGED
@@ -1,38 +1,38 @@
1
- {
2
- "name": "@oino-ts/db-postgresql",
3
- "version": "0.16.1",
4
- "description": "OINO TS package for using Postgresql 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
- "postgresql"
18
- ],
19
- "main": "./dist/cjs/index.js",
20
- "module": "./dist/esm/index.js",
21
- "types": "./dist/types/index.d.ts",
22
- "dependencies": {
23
- "@oino-ts/db": "0.16.1",
24
- "pg": "^8.11.3"
25
- },
26
- "devDependencies": {
27
- "@types/bun": "latest",
28
- "@types/node": "^20.12.7",
29
- "@types/pg": "^8.11.5",
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-postgresql",
3
+ "version": "0.17.1",
4
+ "description": "OINO TS package for using Postgresql 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
+ "postgresql"
18
+ ],
19
+ "main": "./dist/cjs/index.js",
20
+ "module": "./dist/esm/index.js",
21
+ "types": "./dist/types/index.d.ts",
22
+ "dependencies": {
23
+ "@oino-ts/db": "0.17.1",
24
+ "pg": "^8.11.3"
25
+ },
26
+ "devDependencies": {
27
+ "@types/bun": "latest",
28
+ "@types/node": "^20.12.7",
29
+ "@types/pg": "^8.11.5",
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,443 +1,443 @@
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, OINOLog, OINOResult, OINODB_EMPTY_ROW, OINODB_EMPTY_ROWS } from "@oino-ts/db";
8
-
9
- import { Pool, QueryResult } from "pg";
10
-
11
-
12
- /**
13
- * Implmentation of OINODbDataSet for Postgresql.
14
- *
15
- */
16
- class OINOPostgresqlData extends OINODbDataSet {
17
- private _rows:OINODataRow[]
18
-
19
- /**
20
- * OINOPostgresqlData constructor
21
- * @param params database parameters
22
- */
23
- constructor(data: unknown, messages:string[]=[]) {
24
- super(data, messages)
25
-
26
- if ((data != null) && !(Array.isArray(data))) {
27
- throw new Error(OINO_ERROR_PREFIX + ": Invalid Posgresql data type!") // TODO: maybe check all rows
28
- }
29
- this._rows = data as OINODataRow[]
30
- if (this.isEmpty()) {
31
- this._currentRow = -1
32
- this._eof = true
33
- } else {
34
- this._currentRow = 0
35
- this._eof = false
36
- }
37
- }
38
- private _currentRow: number
39
- private _eof: boolean
40
-
41
- /**
42
- * Is data set empty.
43
- *
44
- */
45
- isEmpty():boolean {
46
- return (this._rows.length == 0)
47
- }
48
-
49
- /**
50
- * Is there no more content, i.e. either dataset is empty or we have moved beyond last line
51
- *
52
- */
53
- isEof():boolean {
54
- return (this._eof)
55
- }
56
-
57
- /**
58
- * Attempts to moves dataset to the next row, possibly waiting for more data to become available. Returns !isEof().
59
- *
60
- */
61
- async next():Promise<boolean> {
62
- if (this._currentRow < this._rows.length-1) {
63
- this._currentRow = this._currentRow + 1
64
- } else {
65
- this._eof = true
66
- }
67
- return Promise.resolve(!this._eof)
68
- }
69
-
70
- /**
71
- * Gets current row of data.
72
- *
73
- */
74
- getRow(): OINODataRow {
75
- if ((this._currentRow >=0) && (this._currentRow < this._rows.length)) {
76
- return this._rows[this._currentRow]
77
- } else {
78
- return OINODB_EMPTY_ROW
79
- }
80
- }
81
-
82
- /**
83
- * Gets all rows of data.
84
- *
85
- */
86
- async getAllRows(): Promise<OINODataRow[]> {
87
- return this._rows // at the moment theres no result streaming, so we can just return the rows
88
- }
89
- }
90
-
91
- /**
92
- * Implementation of Postgresql-database.
93
- *
94
- */
95
- export class OINODbPostgresql extends OINODb {
96
-
97
- private _pool:Pool
98
-
99
- /**
100
- * Constructor of `OINODbPostgresql`
101
- * @param params database paraneters
102
- */
103
- constructor(params:OINODbParams) {
104
- super(params)
105
-
106
- if (this._params.type !== "OINODbPostgresql") {
107
- throw new Error(OINO_ERROR_PREFIX + ": Not OINODbPostgresql-type: " + this._params.type)
108
- }
109
- const ssl_enabled:boolean = !(this._params.url == "localhost" || this._params.url == "127.0.0.1")
110
- this._pool = new Pool({ host: this._params.url, database: this._params.database, port: this._params.port, user: this._params.user, password: this._params.password, ssl: ssl_enabled })
111
- delete this._params.password
112
-
113
- this._pool.on("error", (err: any) => {
114
- OINOLog.error("@oino-ts/db-postgresql", "OINODbPostgresql", ".on(error)", "Error-event", {err:err})
115
- })
116
- }
117
-
118
- private _parseFieldLength(fieldLength:OINODataCell):number {
119
- let result:number = parseInt((fieldLength || "0").toString())
120
- if (Number.isNaN(result)) {
121
- result = 0
122
- }
123
- return result
124
- }
125
-
126
- private async _query(sql:string):Promise<OINODataRow[]> {
127
- const query_result:QueryResult = await this._pool.query({rowMode: "array", text: sql})
128
- return Promise.resolve(query_result.rows)
129
- }
130
-
131
- private async _exec(sql:string):Promise<OINODataRow[]> {
132
- const query_result:QueryResult = await this._pool.query({rowMode: "array", text: sql})
133
- if (Array.isArray(query_result) == true) {
134
- return Promise.resolve(query_result.flatMap((q) => q.rows))
135
- } else if (query_result.rows) {
136
- return Promise.resolve(query_result.rows)
137
- } else {
138
- return Promise.resolve(OINODB_EMPTY_ROWS) // return empty row if no rows returned
139
- }
140
- }
141
-
142
- /**
143
- * Print a table name using database specific SQL escaping.
144
- *
145
- * @param sqlTable name of the table
146
- *
147
- */
148
- printSqlTablename(sqlTable:string): string {
149
- return "\""+sqlTable.toLowerCase()+"\""
150
- }
151
-
152
- /**
153
- * Print a column name with correct SQL escaping.
154
- *
155
- * @param sqlColumn name of the column
156
- *
157
- */
158
- printSqlColumnname(sqlColumn:string): string {
159
- return "\""+sqlColumn+"\""
160
- }
161
-
162
- /**
163
- * Print a single data value from serialization using the context of the native data
164
- * type with the correct SQL escaping.
165
- *
166
- * @param cellValue data from sql results
167
- * @param sqlType native type name for table column
168
- *
169
- */
170
- printCellAsSqlValue(cellValue:OINODataCell, sqlType: string): string {
171
- if (cellValue === null) {
172
- return "NULL"
173
-
174
- } else if (cellValue === undefined) {
175
- return "UNDEFINED"
176
-
177
- } else if ((sqlType == "integer") || (sqlType == "smallint") || (sqlType == "real")) {
178
- return cellValue.toString()
179
-
180
- } else if (sqlType == "bytea") {
181
- if (cellValue instanceof Buffer) {
182
- return "'\\x" + (cellValue as Buffer).toString("hex") + "'"
183
- } else if (cellValue instanceof Uint8Array) {
184
- return "'\\x" + Buffer.from(cellValue as Uint8Array).toString("hex") + "'"
185
- } else {
186
- return "\'" + cellValue?.toString() + "\'"
187
- }
188
-
189
- } else if (sqlType == "boolean") {
190
- if (cellValue == null || cellValue == "" || cellValue.toString().toLowerCase() == "false" || cellValue == "0") {
191
- return "false"
192
- } else {
193
- return "true"
194
- }
195
-
196
- } else if ((sqlType == "date") && (cellValue instanceof Date)) {
197
- return "\'" + cellValue.toISOString() + "\'"
198
-
199
- } else {
200
- return this.printSqlString(cellValue.toString())
201
- }
202
- }
203
-
204
- /**
205
- * Print a single string value as valid sql literal
206
- *
207
- * @param sqlString string value
208
- *
209
- */
210
- printSqlString(sqlString:string): string {
211
- return "\'" + sqlString.replaceAll("'", "''") + "\'"
212
- }
213
-
214
-
215
- /**
216
- * Parse a single SQL result value for serialization using the context of the native data
217
- * type.
218
- *
219
- * @param sqlValue data from serialization
220
- * @param sqlType native type name for table column
221
- *
222
- */
223
- parseSqlValueAsCell(sqlValue:OINODataCell, sqlType: string): OINODataCell {
224
- if ((sqlValue === null) || (sqlValue == "NULL")) {
225
- return null
226
-
227
- } else if (sqlValue === undefined) {
228
- return undefined
229
-
230
- } else if (((sqlType == "date")) && (typeof(sqlValue) == "string") && (sqlValue != "")) {
231
- return new Date(sqlValue)
232
-
233
- } else {
234
- return sqlValue
235
- }
236
-
237
- }
238
-
239
- /**
240
- * Connect to database.
241
- *
242
- */
243
- async connect(): Promise<OINOResult> {
244
- let result:OINOResult = new OINOResult()
245
- try {
246
- // make sure that any items are correctly URL encoded in the connection string
247
- await this._pool.connect()
248
- this.isConnected = true
249
-
250
- } catch (e:any) {
251
- result.setError(500, "Exception connecting to database: " + e.message, "OINODbPostgresql.connect")
252
- OINOLog.exception("@oino-ts/db-postgresql", "OINODbMsSql", "connect", "exception in connect", {message:e.message, stack:e.stack})
253
- }
254
- return result
255
- }
256
-
257
- /**
258
- * Validate connection to database is working.
259
- *
260
- */
261
- async validate(): Promise<OINOResult> {
262
- OINOBenchmark.startMetric("OINODb", "validate")
263
- let result:OINOResult = new OINOResult()
264
- try {
265
- const sql = this._getValidateSql(this._params.database)
266
- const sql_res:OINODbDataSet = await this.sqlSelect(sql)
267
- if (sql_res.isEmpty()) {
268
- result.setError(400, "DB returned no rows for select!", "OINODbPostgresql.validate")
269
-
270
- } else if (sql_res.getRow().length == 0) {
271
- result.setError(400, "DB returned no values for database!", "OINODbPostgresql.validate")
272
-
273
- } else if (sql_res.getRow()[0] == "0") {
274
- result.setError(400, "DB returned no schema for database!", "OINODbPostgresql.validate")
275
-
276
- } else {
277
- this.isValidated = true
278
- }
279
- } catch (e:any) {
280
- result.setError(500, "Exception validating connection: " + e.message, "OINODbPostgresql.validate")
281
- OINOLog.exception("@oino-ts/db-postgresql", "OINODbMsSql", "validate", "exception in validate", {message:e.message, stack:e.stack})
282
- }
283
- OINOBenchmark.endMetric("OINODb", "validate")
284
- return result
285
- }
286
-
287
- /**
288
- * Execute a select operation.
289
- *
290
- * @param sql SQL statement.
291
- *
292
- */
293
- async sqlSelect(sql:string): Promise<OINODbDataSet> {
294
- OINOBenchmark.startMetric("OINODb", "sqlSelect")
295
- let result:OINODbDataSet
296
- try {
297
- const rows:OINODataRow[] = await this._query(sql)
298
- result = new OINOPostgresqlData(rows, [])
299
-
300
- } catch (e:any) {
301
- result = new OINOPostgresqlData(OINODB_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (sqlSelect): exception in _db.query [" + e.message + "]"])
302
- }
303
- OINOBenchmark.endMetric("OINODb", "sqlSelect")
304
- return result
305
- }
306
-
307
- /**
308
- * Execute other sql operations.
309
- *
310
- * @param sql SQL statement.
311
- *
312
- */
313
- async sqlExec(sql:string): Promise<OINODbDataSet> {
314
- OINOBenchmark.startMetric("OINODb", "sqlExec")
315
- let result:OINODbDataSet
316
- try {
317
- const rows:OINODataRow[] = await this._exec(sql)
318
- result = new OINOPostgresqlData(rows, [])
319
-
320
- } catch (e:any) {
321
- result = new OINOPostgresqlData(OINODB_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (sqlExec): exception in _db.exec [" + e.message + "]"])
322
- }
323
- OINOBenchmark.endMetric("OINODb", "sqlExec")
324
- return result
325
- }
326
-
327
- private _getSchemaSql(dbName:string, tableName:string):string {
328
- const sql =
329
- `SELECT
330
- col.column_name,
331
- col.data_type,
332
- col.character_maximum_length,
333
- col.is_nullable,
334
- con.constraint_type,
335
- col.numeric_precision,
336
- col.numeric_scale,
337
- col.column_default
338
- FROM information_schema.columns col
339
- LEFT JOIN LATERAL
340
- (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
341
- from
342
- information_schema.table_constraints tco,
343
- information_schema.key_column_usage kcu
344
- where
345
- kcu.constraint_name = tco.constraint_name
346
- and kcu.constraint_schema = tco.constraint_schema
347
- and tco.table_catalog = col.table_catalog
348
- and tco.table_name = col.table_name
349
- and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
350
- group by kcu.column_name) con on col.column_name = con.column_name
351
- WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`
352
- return sql
353
- }
354
-
355
- private _getValidateSql(dbName:string):string {
356
- const sql =
357
- `SELECT
358
- count(col.column_name) AS column_count
359
- FROM information_schema.columns col
360
- LEFT JOIN LATERAL
361
- (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
362
- from
363
- information_schema.table_constraints tco,
364
- information_schema.key_column_usage kcu
365
- where
366
- kcu.constraint_name = tco.constraint_name
367
- and kcu.constraint_schema = tco.constraint_schema
368
- and tco.table_catalog = col.table_catalog
369
- and tco.table_name = col.table_name
370
- and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
371
- group by kcu.column_name) con on col.column_name = con.column_name
372
- WHERE col.table_catalog = '${dbName}'`
373
- return sql
374
- }
375
-
376
- /**
377
- * Initialize a data model by getting the SQL schema and populating OINODbDataFields of
378
- * the model.
379
- *
380
- * @param api api which data model to initialize.
381
- *
382
- */
383
- async initializeApiDatamodel(api:OINODbApi): Promise<void> {
384
-
385
- const schema_res:OINODbDataSet = await this.sqlSelect(this._getSchemaSql(this._params.database, api.params.tableName.toLowerCase()))
386
- while (!schema_res.isEof()) {
387
- const row:OINODataRow = schema_res.getRow()
388
- const field_name:string = row[0]?.toString() || ""
389
- const sql_type:string = row[1]?.toString() || ""
390
- const field_length:number = this._parseFieldLength(row[2])
391
- const constraints = row[4]?.toString() || ""
392
- const numeric_precision:number = this._parseFieldLength(row[5])
393
- const numeric_scale:number = this._parseFieldLength(row[6])
394
- const default_val:string = row[7]?.toString() || ""
395
- const field_params:OINODbDataFieldParams = {
396
- isPrimaryKey: constraints.indexOf('PRIMARY KEY') >= 0 || false,
397
- isForeignKey: constraints.indexOf('FOREIGN KEY') >= 0 || false,
398
- isNotNull: row[3] == "NO",
399
- isAutoInc: default_val.startsWith("nextval(")
400
- }
401
- if (api.isFieldIncluded(field_name) == false) {
402
- OINOLog.info("@oino-ts/db-postgresql", "OINODbPostgresql", "initializeApiDatamodel", "Field excluded in API parameters.", {field:field_name})
403
- if (field_params.isPrimaryKey) {
404
- throw new Error(OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + field_name)
405
- }
406
-
407
- } else {
408
- if ((sql_type == "integer") || (sql_type == "smallint") || (sql_type == "real")) {
409
- api.datamodel.addField(new OINONumberDataField(this, field_name, sql_type, field_params ))
410
-
411
- } else if ((sql_type == "date")) {
412
- if (api.params.useDatesAsString) {
413
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
414
- } else {
415
- api.datamodel.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params))
416
- }
417
-
418
- } else if ((sql_type == "character") || (sql_type == "character varying") || (sql_type == "varchar") || (sql_type == "text")) {
419
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length))
420
-
421
- } else if ((sql_type == "bytea")) {
422
- api.datamodel.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length))
423
-
424
- } else if ((sql_type == "boolean")) {
425
- api.datamodel.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params))
426
-
427
- } else if ((sql_type == "decimal") || (sql_type == "numeric")) {
428
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, numeric_precision + numeric_scale + 1))
429
-
430
- } else {
431
- OINOLog.info("@oino-ts/db-postgresql", "OINODbPostgresql", "initializeApiDatamodel", "Unrecognized field type treated as string", {field_name: field_name, sql_type:sql_type, field_length:field_length, field_params:field_params })
432
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
433
- }
434
- }
435
- await schema_res.next()
436
- }
437
- OINOLog.info("@oino-ts/db-postgresql", "OINODbPostgresql", "initializeApiDatamodel", "\n" + api.datamodel.printDebug("\n"))
438
- return Promise.resolve()
439
- }
440
- }
441
-
442
-
443
-
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, OINOLog, OINOResult, OINODB_EMPTY_ROW, OINODB_EMPTY_ROWS } from "@oino-ts/db";
8
+
9
+ import { Pool, QueryResult } from "pg";
10
+
11
+
12
+ /**
13
+ * Implmentation of OINODbDataSet for Postgresql.
14
+ *
15
+ */
16
+ class OINOPostgresqlData extends OINODbDataSet {
17
+ private _rows:OINODataRow[]
18
+
19
+ /**
20
+ * OINOPostgresqlData constructor
21
+ * @param params database parameters
22
+ */
23
+ constructor(data: unknown, messages:string[]=[]) {
24
+ super(data, messages)
25
+
26
+ if ((data != null) && !(Array.isArray(data))) {
27
+ throw new Error(OINO_ERROR_PREFIX + ": Invalid Posgresql data type!") // TODO: maybe check all rows
28
+ }
29
+ this._rows = data as OINODataRow[]
30
+ if (this.isEmpty()) {
31
+ this._currentRow = -1
32
+ this._eof = true
33
+ } else {
34
+ this._currentRow = 0
35
+ this._eof = false
36
+ }
37
+ }
38
+ private _currentRow: number
39
+ private _eof: boolean
40
+
41
+ /**
42
+ * Is data set empty.
43
+ *
44
+ */
45
+ isEmpty():boolean {
46
+ return (this._rows.length == 0)
47
+ }
48
+
49
+ /**
50
+ * Is there no more content, i.e. either dataset is empty or we have moved beyond last line
51
+ *
52
+ */
53
+ isEof():boolean {
54
+ return (this._eof)
55
+ }
56
+
57
+ /**
58
+ * Attempts to moves dataset to the next row, possibly waiting for more data to become available. Returns !isEof().
59
+ *
60
+ */
61
+ async next():Promise<boolean> {
62
+ if (this._currentRow < this._rows.length-1) {
63
+ this._currentRow = this._currentRow + 1
64
+ } else {
65
+ this._eof = true
66
+ }
67
+ return Promise.resolve(!this._eof)
68
+ }
69
+
70
+ /**
71
+ * Gets current row of data.
72
+ *
73
+ */
74
+ getRow(): OINODataRow {
75
+ if ((this._currentRow >=0) && (this._currentRow < this._rows.length)) {
76
+ return this._rows[this._currentRow]
77
+ } else {
78
+ return OINODB_EMPTY_ROW
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Gets all rows of data.
84
+ *
85
+ */
86
+ async getAllRows(): Promise<OINODataRow[]> {
87
+ return this._rows // at the moment theres no result streaming, so we can just return the rows
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Implementation of Postgresql-database.
93
+ *
94
+ */
95
+ export class OINODbPostgresql extends OINODb {
96
+
97
+ private _pool:Pool
98
+
99
+ /**
100
+ * Constructor of `OINODbPostgresql`
101
+ * @param params database paraneters
102
+ */
103
+ constructor(params:OINODbParams) {
104
+ super(params)
105
+
106
+ if (this._params.type !== "OINODbPostgresql") {
107
+ throw new Error(OINO_ERROR_PREFIX + ": Not OINODbPostgresql-type: " + this._params.type)
108
+ }
109
+ const ssl_enabled:boolean = !(this._params.url == "localhost" || this._params.url == "127.0.0.1")
110
+ this._pool = new Pool({ host: this._params.url, database: this._params.database, port: this._params.port, user: this._params.user, password: this._params.password, ssl: ssl_enabled })
111
+ delete this._params.password
112
+
113
+ this._pool.on("error", (err: any) => {
114
+ OINOLog.error("@oino-ts/db-postgresql", "OINODbPostgresql", ".on(error)", "Error-event", {err:err})
115
+ })
116
+ }
117
+
118
+ private _parseFieldLength(fieldLength:OINODataCell):number {
119
+ let result:number = parseInt((fieldLength || "0").toString())
120
+ if (Number.isNaN(result)) {
121
+ result = 0
122
+ }
123
+ return result
124
+ }
125
+
126
+ private async _query(sql:string):Promise<OINODataRow[]> {
127
+ const query_result:QueryResult = await this._pool.query({rowMode: "array", text: sql})
128
+ return Promise.resolve(query_result.rows)
129
+ }
130
+
131
+ private async _exec(sql:string):Promise<OINODataRow[]> {
132
+ const query_result:QueryResult = await this._pool.query({rowMode: "array", text: sql})
133
+ if (Array.isArray(query_result) == true) {
134
+ return Promise.resolve(query_result.flatMap((q) => q.rows))
135
+ } else if (query_result.rows) {
136
+ return Promise.resolve(query_result.rows)
137
+ } else {
138
+ return Promise.resolve(OINODB_EMPTY_ROWS) // return empty row if no rows returned
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Print a table name using database specific SQL escaping.
144
+ *
145
+ * @param sqlTable name of the table
146
+ *
147
+ */
148
+ printSqlTablename(sqlTable:string): string {
149
+ return "\""+sqlTable.toLowerCase()+"\""
150
+ }
151
+
152
+ /**
153
+ * Print a column name with correct SQL escaping.
154
+ *
155
+ * @param sqlColumn name of the column
156
+ *
157
+ */
158
+ printSqlColumnname(sqlColumn:string): string {
159
+ return "\""+sqlColumn+"\""
160
+ }
161
+
162
+ /**
163
+ * Print a single data value from serialization using the context of the native data
164
+ * type with the correct SQL escaping.
165
+ *
166
+ * @param cellValue data from sql results
167
+ * @param sqlType native type name for table column
168
+ *
169
+ */
170
+ printCellAsSqlValue(cellValue:OINODataCell, sqlType: string): string {
171
+ if (cellValue === null) {
172
+ return "NULL"
173
+
174
+ } else if (cellValue === undefined) {
175
+ return "UNDEFINED"
176
+
177
+ } else if ((sqlType == "integer") || (sqlType == "smallint") || (sqlType == "real")) {
178
+ return cellValue.toString()
179
+
180
+ } else if (sqlType == "bytea") {
181
+ if (cellValue instanceof Buffer) {
182
+ return "'\\x" + (cellValue as Buffer).toString("hex") + "'"
183
+ } else if (cellValue instanceof Uint8Array) {
184
+ return "'\\x" + Buffer.from(cellValue as Uint8Array).toString("hex") + "'"
185
+ } else {
186
+ return "\'" + cellValue?.toString() + "\'"
187
+ }
188
+
189
+ } else if (sqlType == "boolean") {
190
+ if (cellValue == null || cellValue == "" || cellValue.toString().toLowerCase() == "false" || cellValue == "0") {
191
+ return "false"
192
+ } else {
193
+ return "true"
194
+ }
195
+
196
+ } else if ((sqlType == "date") && (cellValue instanceof Date)) {
197
+ return "\'" + cellValue.toISOString() + "\'"
198
+
199
+ } else {
200
+ return this.printSqlString(cellValue.toString())
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Print a single string value as valid sql literal
206
+ *
207
+ * @param sqlString string value
208
+ *
209
+ */
210
+ printSqlString(sqlString:string): string {
211
+ return "\'" + sqlString.replaceAll("'", "''") + "\'"
212
+ }
213
+
214
+
215
+ /**
216
+ * Parse a single SQL result value for serialization using the context of the native data
217
+ * type.
218
+ *
219
+ * @param sqlValue data from serialization
220
+ * @param sqlType native type name for table column
221
+ *
222
+ */
223
+ parseSqlValueAsCell(sqlValue:OINODataCell, sqlType: string): OINODataCell {
224
+ if ((sqlValue === null) || (sqlValue == "NULL")) {
225
+ return null
226
+
227
+ } else if (sqlValue === undefined) {
228
+ return undefined
229
+
230
+ } else if (((sqlType == "date")) && (typeof(sqlValue) == "string") && (sqlValue != "")) {
231
+ return new Date(sqlValue)
232
+
233
+ } else {
234
+ return sqlValue
235
+ }
236
+
237
+ }
238
+
239
+ /**
240
+ * Connect to database.
241
+ *
242
+ */
243
+ async connect(): Promise<OINOResult> {
244
+ let result:OINOResult = new OINOResult()
245
+ try {
246
+ // make sure that any items are correctly URL encoded in the connection string
247
+ await this._pool.connect()
248
+ this.isConnected = true
249
+
250
+ } catch (e:any) {
251
+ result.setError(500, "Exception connecting to database: " + e.message, "OINODbPostgresql.connect")
252
+ OINOLog.exception("@oino-ts/db-postgresql", "OINODbMsSql", "connect", "exception in connect", {message:e.message, stack:e.stack})
253
+ }
254
+ return result
255
+ }
256
+
257
+ /**
258
+ * Validate connection to database is working.
259
+ *
260
+ */
261
+ async validate(): Promise<OINOResult> {
262
+ OINOBenchmark.startMetric("OINODb", "validate")
263
+ let result:OINOResult = new OINOResult()
264
+ try {
265
+ const sql = this._getValidateSql(this._params.database)
266
+ const sql_res:OINODbDataSet = await this.sqlSelect(sql)
267
+ if (sql_res.isEmpty()) {
268
+ result.setError(400, "DB returned no rows for select!", "OINODbPostgresql.validate")
269
+
270
+ } else if (sql_res.getRow().length == 0) {
271
+ result.setError(400, "DB returned no values for database!", "OINODbPostgresql.validate")
272
+
273
+ } else if (sql_res.getRow()[0] == "0") {
274
+ result.setError(400, "DB returned no schema for database!", "OINODbPostgresql.validate")
275
+
276
+ } else {
277
+ this.isValidated = true
278
+ }
279
+ } catch (e:any) {
280
+ result.setError(500, "Exception validating connection: " + e.message, "OINODbPostgresql.validate")
281
+ OINOLog.exception("@oino-ts/db-postgresql", "OINODbMsSql", "validate", "exception in validate", {message:e.message, stack:e.stack})
282
+ }
283
+ OINOBenchmark.endMetric("OINODb", "validate")
284
+ return result
285
+ }
286
+
287
+ /**
288
+ * Execute a select operation.
289
+ *
290
+ * @param sql SQL statement.
291
+ *
292
+ */
293
+ async sqlSelect(sql:string): Promise<OINODbDataSet> {
294
+ OINOBenchmark.startMetric("OINODb", "sqlSelect")
295
+ let result:OINODbDataSet
296
+ try {
297
+ const rows:OINODataRow[] = await this._query(sql)
298
+ result = new OINOPostgresqlData(rows, [])
299
+
300
+ } catch (e:any) {
301
+ result = new OINOPostgresqlData(OINODB_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (sqlSelect): exception in _db.query [" + e.message + "]"])
302
+ }
303
+ OINOBenchmark.endMetric("OINODb", "sqlSelect")
304
+ return result
305
+ }
306
+
307
+ /**
308
+ * Execute other sql operations.
309
+ *
310
+ * @param sql SQL statement.
311
+ *
312
+ */
313
+ async sqlExec(sql:string): Promise<OINODbDataSet> {
314
+ OINOBenchmark.startMetric("OINODb", "sqlExec")
315
+ let result:OINODbDataSet
316
+ try {
317
+ const rows:OINODataRow[] = await this._exec(sql)
318
+ result = new OINOPostgresqlData(rows, [])
319
+
320
+ } catch (e:any) {
321
+ result = new OINOPostgresqlData(OINODB_EMPTY_ROWS, [OINO_ERROR_PREFIX + " (sqlExec): exception in _db.exec [" + e.message + "]"])
322
+ }
323
+ OINOBenchmark.endMetric("OINODb", "sqlExec")
324
+ return result
325
+ }
326
+
327
+ private _getSchemaSql(dbName:string, tableName:string):string {
328
+ const sql =
329
+ `SELECT
330
+ col.column_name,
331
+ col.data_type,
332
+ col.character_maximum_length,
333
+ col.is_nullable,
334
+ con.constraint_type,
335
+ col.numeric_precision,
336
+ col.numeric_scale,
337
+ col.column_default
338
+ FROM information_schema.columns col
339
+ LEFT JOIN LATERAL
340
+ (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
341
+ from
342
+ information_schema.table_constraints tco,
343
+ information_schema.key_column_usage kcu
344
+ where
345
+ kcu.constraint_name = tco.constraint_name
346
+ and kcu.constraint_schema = tco.constraint_schema
347
+ and tco.table_catalog = col.table_catalog
348
+ and tco.table_name = col.table_name
349
+ and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
350
+ group by kcu.column_name) con on col.column_name = con.column_name
351
+ WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`
352
+ return sql
353
+ }
354
+
355
+ private _getValidateSql(dbName:string):string {
356
+ const sql =
357
+ `SELECT
358
+ count(col.column_name) AS column_count
359
+ FROM information_schema.columns col
360
+ LEFT JOIN LATERAL
361
+ (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
362
+ from
363
+ information_schema.table_constraints tco,
364
+ information_schema.key_column_usage kcu
365
+ where
366
+ kcu.constraint_name = tco.constraint_name
367
+ and kcu.constraint_schema = tco.constraint_schema
368
+ and tco.table_catalog = col.table_catalog
369
+ and tco.table_name = col.table_name
370
+ and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
371
+ group by kcu.column_name) con on col.column_name = con.column_name
372
+ WHERE col.table_catalog = '${dbName}'`
373
+ return sql
374
+ }
375
+
376
+ /**
377
+ * Initialize a data model by getting the SQL schema and populating OINODbDataFields of
378
+ * the model.
379
+ *
380
+ * @param api api which data model to initialize.
381
+ *
382
+ */
383
+ async initializeApiDatamodel(api:OINODbApi): Promise<void> {
384
+
385
+ const schema_res:OINODbDataSet = await this.sqlSelect(this._getSchemaSql(this._params.database, api.params.tableName.toLowerCase()))
386
+ while (!schema_res.isEof()) {
387
+ const row:OINODataRow = schema_res.getRow()
388
+ const field_name:string = row[0]?.toString() || ""
389
+ const sql_type:string = row[1]?.toString() || ""
390
+ const field_length:number = this._parseFieldLength(row[2])
391
+ const constraints = row[4]?.toString() || ""
392
+ const numeric_precision:number = this._parseFieldLength(row[5])
393
+ const numeric_scale:number = this._parseFieldLength(row[6])
394
+ const default_val:string = row[7]?.toString() || ""
395
+ const field_params:OINODbDataFieldParams = {
396
+ isPrimaryKey: constraints.indexOf('PRIMARY KEY') >= 0 || false,
397
+ isForeignKey: constraints.indexOf('FOREIGN KEY') >= 0 || false,
398
+ isNotNull: row[3] == "NO",
399
+ isAutoInc: default_val.startsWith("nextval(")
400
+ }
401
+ if (api.isFieldIncluded(field_name) == false) {
402
+ OINOLog.info("@oino-ts/db-postgresql", "OINODbPostgresql", "initializeApiDatamodel", "Field excluded in API parameters.", {field:field_name})
403
+ if (field_params.isPrimaryKey) {
404
+ throw new Error(OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + field_name)
405
+ }
406
+
407
+ } else {
408
+ if ((sql_type == "integer") || (sql_type == "smallint") || (sql_type == "real")) {
409
+ api.datamodel.addField(new OINONumberDataField(this, field_name, sql_type, field_params ))
410
+
411
+ } else if ((sql_type == "date")) {
412
+ if (api.params.useDatesAsString) {
413
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
414
+ } else {
415
+ api.datamodel.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params))
416
+ }
417
+
418
+ } else if ((sql_type == "character") || (sql_type == "character varying") || (sql_type == "varchar") || (sql_type == "text")) {
419
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length))
420
+
421
+ } else if ((sql_type == "bytea")) {
422
+ api.datamodel.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length))
423
+
424
+ } else if ((sql_type == "boolean")) {
425
+ api.datamodel.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params))
426
+
427
+ } else if ((sql_type == "decimal") || (sql_type == "numeric")) {
428
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, numeric_precision + numeric_scale + 1))
429
+
430
+ } else {
431
+ OINOLog.info("@oino-ts/db-postgresql", "OINODbPostgresql", "initializeApiDatamodel", "Unrecognized field type treated as string", {field_name: field_name, sql_type:sql_type, field_length:field_length, field_params:field_params })
432
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
433
+ }
434
+ }
435
+ await schema_res.next()
436
+ }
437
+ OINOLog.info("@oino-ts/db-postgresql", "OINODbPostgresql", "initializeApiDatamodel", "\n" + api.datamodel.printDebug("\n"))
438
+ return Promise.resolve()
439
+ }
440
+ }
441
+
442
+
443
+
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export { OINODbPostgresql } from "./OINODbPostgresql.js"
1
+ export { OINODbPostgresql } from "./OINODbPostgresql.js"