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