@oino-ts/db-postgresql 0.16.2 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/OINODbPostgresql.js +37 -37
- package/dist/esm/OINODbPostgresql.js +37 -37
- package/package.json +38 -38
- package/src/OINODbPostgresql.ts +443 -443
- package/src/index.ts +1 -1
|
@@ -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.
|
|
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.
|
|
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
|
+
}
|
package/src/OINODbPostgresql.ts
CHANGED
|
@@ -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"
|