@oino-ts/db-postgresql 0.0.15 → 0.0.17
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/README.md +18 -47
- package/dist/cjs/OINODbPostgresql.js +49 -29
- package/dist/esm/OINODbPostgresql.js +49 -29
- package/dist/types/OINODbPostgresql.d.ts +1 -1
- package/package.json +2 -2
- package/src/OINODbPostgresql.ts +52 -31
package/README.md
CHANGED
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
# GETTING STARTED
|
|
11
11
|
|
|
12
12
|
### Setup
|
|
13
|
-
Install the `@oino-ts/
|
|
13
|
+
Install the `@oino-ts/db` npm package and necessary database packages and import them in your code.
|
|
14
14
|
```
|
|
15
|
-
bun install @oino-ts/
|
|
16
|
-
bun install @oino-ts/bunsqlite
|
|
15
|
+
bun install @oino-ts/db
|
|
16
|
+
bun install @oino-ts/db-bunsqlite
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
import { OINODb, OINOApi, OINOFactory } from "@oino-ts/
|
|
21
|
-
import { OINODbBunSqlite } from "@oino-ts/bunsqlite"
|
|
20
|
+
import { OINODb, OINOApi, OINOFactory } from "@oino-ts/db";
|
|
21
|
+
import { OINODbBunSqlite } from "@oino-ts/db-bunsqlite"
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
### Register database and logger
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
### Write results back to HTTP Response
|
|
52
52
|
The results for a GET request will contain [`OINOModelSet`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbModelSet.html) data that can be written out as JSON or CSV as needed. For other requests result is just success or error with messages.
|
|
53
53
|
```
|
|
54
|
-
return new Response(result.
|
|
54
|
+
return new Response(result.data.writeString(OINOContentType.json))
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
|
|
@@ -59,20 +59,8 @@
|
|
|
59
59
|
|
|
60
60
|
## RESTfull
|
|
61
61
|
OINO maps HTTP methods GET/POST/PUT/DELETE to SQL operations SELECT/INSERT/UPDATE/DELETE. The GET/POST requests can be made without URL ID to get all rows or insert new ones and others target a single row using URL ID.
|
|
62
|
-
|
|
63
|
-
### HTTP GET
|
|
64
|
-
```
|
|
65
|
-
Request and response:
|
|
66
|
-
> curl.exe -X GET http://localhost:3001/orderdetails/11077:77
|
|
67
|
-
[
|
|
68
|
-
{"_OINOID_":"11077:77","OrderID":11077,"ProductID":77,"UnitPrice":13,"Quantity":2,"Discount":0}
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
SQL:
|
|
72
|
-
SELECT "OrderID","ProductID","UnitPrice","Quantity","Discount" FROM [OrderDetails] WHERE ("OrderID"=11077 AND "ProductID"=77);
|
|
73
|
-
```
|
|
74
62
|
|
|
75
|
-
|
|
63
|
+
For example HTTP POST
|
|
76
64
|
```
|
|
77
65
|
Request and response:
|
|
78
66
|
> curl.exe -X POST http://localhost:3001/orderdetails -H "Content-Type: application/json" --data '[{\"OrderID\":11077,\"ProductID\":99,\"UnitPrice\":19,\"Quantity\":1,\"Discount\":0}]'
|
|
@@ -82,31 +70,12 @@
|
|
|
82
70
|
INSERT INTO [OrderDetails] ("OrderID","ProductID","UnitPrice","Quantity","Discount") VALUES (11077,99,19,1,0);
|
|
83
71
|
```
|
|
84
72
|
|
|
85
|
-
### HTTP PUT
|
|
86
|
-
```
|
|
87
|
-
Request and response:
|
|
88
|
-
> curl.exe -X PUT http://localhost:3001/orderdetails/11077:99 -H "Content-Type: application/json" --data '[{\"UnitPrice\":20}]'
|
|
89
|
-
{"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
|
|
90
73
|
|
|
91
|
-
SQL:
|
|
92
|
-
UPDATE [OrderDetails] SET "UnitPrice"=20 WHERE ("OrderID"=11077 AND "ProductID"=99);
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### HTTP DELETE
|
|
96
|
-
```
|
|
97
|
-
Request and response:
|
|
98
|
-
> curl.exe -X DELETE http://localhost:3001/orderdetails/11077:99
|
|
99
|
-
{"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
|
|
100
|
-
|
|
101
|
-
SQL:
|
|
102
|
-
DELETE FROM [OrderDetails] WHERE ("OrderID"=11077 AND "ProductID"=99);
|
|
103
|
-
```
|
|
104
|
-
|
|
105
74
|
## Universal Serialization
|
|
106
75
|
OINO handles serialization of data to JSON/CSV/etc. and back based on the data model. It knows what columns exist, what is their data type and how to convert each to JSON/CSV and back. This allows also partial data to be sent, i.e. you can send only columns that need updating or even send extra columns and have them ignored.
|
|
107
76
|
|
|
108
77
|
### Features
|
|
109
|
-
- Files can be sent to BLOB fields using BASE64 encoding.
|
|
78
|
+
- Files can be sent to BLOB fields using BASE64 or MIME multipart encoding.
|
|
110
79
|
- Datetimes are (optionally) normalized to ISO 8601 format.
|
|
111
80
|
- Extended JSON-encoding
|
|
112
81
|
- Unquoted literal `undefined` can be used to represent non-existent values (leaving property out works too but preserving structure might be easier e.g. when translating data).
|
|
@@ -129,12 +98,13 @@
|
|
|
129
98
|
- Bun Sqlite through Bun native implementation
|
|
130
99
|
- Postgresql through [pg](https://www.npmjs.com/package/pg)-package
|
|
131
100
|
- Mariadb / Mysql-support through [mariadb](https://www.npmjs.com/package/mariadb)-package
|
|
101
|
+
- Sql Server through [mssql](https://www.npmjs.com/package/mssql)-package
|
|
132
102
|
|
|
133
103
|
## Complex Keys
|
|
134
104
|
To support tables with multipart primary keys OINO generates a composite key `_OINOID_` that is included in the result and can be used as the REST ID. For example in the example above table `OrderDetails` has two primary keys `OrderID` and `ProductID` making the `_OINOID_` of form `11077:99`.
|
|
135
105
|
|
|
136
106
|
## Power Of SQL
|
|
137
|
-
Since OINO
|
|
107
|
+
Since OINO is just generating SQL, WHERE-conditions can be defined with [`OINOSqlFilter`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlFilter.html), order with [`OINOSqlOrder`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlOrder.html) and limits/paging with [`OINOSqlOrder`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlLimit.html) that are passed as HTTP request parameters. No more API development where you make unique API endpoints for each filter that fetch all data with original API and filter in backend code. Every API can be filtered when and as needed without unnessecary data tranfer and utilizing SQL indexing when available.
|
|
138
108
|
|
|
139
109
|
## Swagger Support
|
|
140
110
|
Swagger is great as long as the definitions are updated and with OINO you can automatically get a Swagger definition including a data model schema.
|
|
@@ -158,7 +128,7 @@
|
|
|
158
128
|
# STATUS
|
|
159
129
|
OINO is currently a hobby project which should and should considered in alpha status. That also means compatibility breaking changes can be made without prior notice when architectual issues are discovered.
|
|
160
130
|
|
|
161
|
-
## Beta
|
|
131
|
+
## Beta
|
|
162
132
|
For a beta status following milestones are planned:
|
|
163
133
|
|
|
164
134
|
### Realistic app
|
|
@@ -178,8 +148,8 @@
|
|
|
178
148
|
### Batch updates
|
|
179
149
|
Supporting batch updates similar to batch inserts is slightly bending the RESTfull principles but would still be a useful optional feature.
|
|
180
150
|
|
|
181
|
-
### Aggregation
|
|
182
|
-
Similar to filtering and
|
|
151
|
+
### Aggregation
|
|
152
|
+
Similar to filtering, ordering and limits, aggregation could be implemented as HTTP request parameters telling what column is aggregated or used for ordering or how many results to return.
|
|
183
153
|
|
|
184
154
|
### Streaming
|
|
185
155
|
One core idea is to be efficient in not making unnecessary copies of the data and minimizing garbage collection debt. This can be taken further by implementing streaming, allowing large dataset to be written to HTTP response as SQL result rows are received.
|
|
@@ -205,18 +175,19 @@
|
|
|
205
175
|
# LINKS
|
|
206
176
|
- [Github repository](https://github.com/pragmatta/oino-ts)
|
|
207
177
|
- [NPM repository](https://www.npmjs.com/org/oino-ts)
|
|
208
|
-
|
|
178
|
+
|
|
209
179
|
|
|
210
180
|
# ACKNOWLEDGEMENTS
|
|
211
181
|
|
|
212
182
|
## Libraries
|
|
213
183
|
OINO uses the following open source libraries and npm packages and I would like to thank everyone for their contributions:
|
|
214
|
-
- Postgresql
|
|
215
|
-
- Mariadb / Mysql
|
|
184
|
+
- Postgresql [node-postgres package](https://www.npmjs.com/package/pg)
|
|
185
|
+
- Mariadb / Mysql [mariadb package](https://www.npmjs.com/package/mariadb)
|
|
186
|
+
- Sql Server [mssql package](https://www.npmjs.com/package/mssql)
|
|
187
|
+
- Custom base encoding [base-x package](https://www.npmjs.com/package/base-x)
|
|
216
188
|
|
|
217
189
|
## Bun
|
|
218
190
|
OINO has been developed using the Bun runtime, not because of the speed improvements but for the first class Typescript support and integrated developper experience. Kudos on the bun team for making Typescript work more exiting again.
|
|
219
191
|
|
|
220
192
|
## SQL Scripts
|
|
221
193
|
The SQL scripts for creating the sample Northwind database are based on [Google Code archive](https://code.google.com/archive/p/northwindextended/downloads) and have been further customized to ensure they would have identical data (in the scope of the automated testing).
|
|
222
|
-
|
|
@@ -36,14 +36,25 @@ class OINOPostgresqlData extends db_1.OINODbDataSet {
|
|
|
36
36
|
}
|
|
37
37
|
_currentRow;
|
|
38
38
|
_eof;
|
|
39
|
+
/**
|
|
40
|
+
* Is data set empty.
|
|
41
|
+
*
|
|
42
|
+
*/
|
|
39
43
|
isEmpty() {
|
|
40
44
|
return (this._rows.length == 0);
|
|
41
45
|
}
|
|
42
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Is there no more content, i.e. either dataset is empty or we have moved beyond last line
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
43
50
|
isEof() {
|
|
44
51
|
return (this._eof);
|
|
45
52
|
}
|
|
46
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Attempts to moves dataset to the next row, possibly waiting for more data to become available. Returns !isEof().
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
async next() {
|
|
47
58
|
// OINOLog.debug("OINODbDataSet.next", {currentRow:this._currentRow, length:this.sqlResult.data.length})
|
|
48
59
|
if (this._currentRow < this._rows.length - 1) {
|
|
49
60
|
this._currentRow = this._currentRow + 1;
|
|
@@ -51,8 +62,12 @@ class OINOPostgresqlData extends db_1.OINODbDataSet {
|
|
|
51
62
|
else {
|
|
52
63
|
this._eof = true;
|
|
53
64
|
}
|
|
54
|
-
return !this._eof;
|
|
65
|
+
return Promise.resolve(!this._eof);
|
|
55
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Gets current row of data.
|
|
69
|
+
*
|
|
70
|
+
*/
|
|
56
71
|
getRow() {
|
|
57
72
|
if ((this._currentRow >= 0) && (this._currentRow < this._rows.length)) {
|
|
58
73
|
return this._rows[this._currentRow];
|
|
@@ -67,29 +82,6 @@ class OINOPostgresqlData extends db_1.OINODbDataSet {
|
|
|
67
82
|
*
|
|
68
83
|
*/
|
|
69
84
|
class OINODbPostgresql extends db_1.OINODb {
|
|
70
|
-
static table_schema_sql = `SELECT
|
|
71
|
-
col.column_name,
|
|
72
|
-
col.data_type,
|
|
73
|
-
col.character_maximum_length,
|
|
74
|
-
col.is_nullable,
|
|
75
|
-
pk.primary_key,
|
|
76
|
-
col.numeric_precision,
|
|
77
|
-
col.numeric_scale,
|
|
78
|
-
col.column_default
|
|
79
|
-
FROM information_schema.columns col
|
|
80
|
-
LEFT JOIN LATERAL
|
|
81
|
-
(select kcu.column_name, 'YES' as primary_key
|
|
82
|
-
from
|
|
83
|
-
information_schema.table_constraints tco,
|
|
84
|
-
information_schema.key_column_usage kcu
|
|
85
|
-
where
|
|
86
|
-
kcu.constraint_name = tco.constraint_name
|
|
87
|
-
and kcu.constraint_schema = tco.constraint_schema
|
|
88
|
-
and tco.table_name = col.table_name
|
|
89
|
-
and tco.constraint_type = 'PRIMARY KEY'
|
|
90
|
-
) pk on col.column_name = pk.column_name
|
|
91
|
-
WHERE table_name = `;
|
|
92
|
-
// private _client:Client
|
|
93
85
|
_pool;
|
|
94
86
|
/**
|
|
95
87
|
* Constructor of `OINODbPostgresql`
|
|
@@ -275,6 +267,32 @@ WHERE table_name = `;
|
|
|
275
267
|
db_1.OINOBenchmark.end("OINODb", "sqlExec");
|
|
276
268
|
return result;
|
|
277
269
|
}
|
|
270
|
+
_getSchemaSql(dbName, tableName) {
|
|
271
|
+
const sql = `SELECT
|
|
272
|
+
col.column_name,
|
|
273
|
+
col.data_type,
|
|
274
|
+
col.character_maximum_length,
|
|
275
|
+
col.is_nullable,
|
|
276
|
+
con.constraint_type,
|
|
277
|
+
col.numeric_precision,
|
|
278
|
+
col.numeric_scale,
|
|
279
|
+
col.column_default
|
|
280
|
+
FROM information_schema.columns col
|
|
281
|
+
LEFT JOIN LATERAL
|
|
282
|
+
(select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
|
|
283
|
+
from
|
|
284
|
+
information_schema.table_constraints tco,
|
|
285
|
+
information_schema.key_column_usage kcu
|
|
286
|
+
where
|
|
287
|
+
kcu.constraint_name = tco.constraint_name
|
|
288
|
+
and kcu.constraint_schema = tco.constraint_schema
|
|
289
|
+
and tco.table_catalog = col.table_catalog
|
|
290
|
+
and tco.table_name = col.table_name
|
|
291
|
+
and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
|
|
292
|
+
group by kcu.column_name) con on col.column_name = con.column_name
|
|
293
|
+
WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`;
|
|
294
|
+
return sql;
|
|
295
|
+
}
|
|
278
296
|
/**
|
|
279
297
|
* Initialize a data model by getting the SQL schema and populating OINODbDataFields of
|
|
280
298
|
* the model.
|
|
@@ -283,7 +301,7 @@ WHERE table_name = `;
|
|
|
283
301
|
*
|
|
284
302
|
*/
|
|
285
303
|
async initializeApiDatamodel(api) {
|
|
286
|
-
const res = await this.sqlSelect(
|
|
304
|
+
const res = await this.sqlSelect(this._getSchemaSql(this._params.database, api.params.tableName.toLowerCase()));
|
|
287
305
|
// OINOLog.debug("OINODbPostgresql.initializeApiDatamodel: table description ", {res: res })
|
|
288
306
|
while (!res.isEof()) {
|
|
289
307
|
const row = res.getRow();
|
|
@@ -291,11 +309,13 @@ WHERE table_name = `;
|
|
|
291
309
|
const field_name = row[0]?.toString() || "";
|
|
292
310
|
const sql_type = row[1]?.toString() || "";
|
|
293
311
|
const field_length = this._parseFieldLength(row[2]);
|
|
312
|
+
const constraints = row[4]?.toString() || "";
|
|
294
313
|
const numeric_precision = this._parseFieldLength(row[5]);
|
|
295
314
|
const numeric_scale = this._parseFieldLength(row[6]);
|
|
296
315
|
const default_val = row[7]?.toString() || "";
|
|
297
316
|
const field_params = {
|
|
298
|
-
isPrimaryKey:
|
|
317
|
+
isPrimaryKey: constraints.indexOf('PRIMARY KEY') >= 0 || false,
|
|
318
|
+
isForeignKey: constraints.indexOf('FOREIGN KEY') >= 0 || false,
|
|
299
319
|
isNotNull: row[3] == "NO",
|
|
300
320
|
isAutoInc: default_val.startsWith("nextval(")
|
|
301
321
|
};
|
|
@@ -329,7 +349,7 @@ WHERE table_name = `;
|
|
|
329
349
|
api.datamodel.addField(new db_1.OINOStringDataField(this, field_name, sql_type, field_params, 0));
|
|
330
350
|
}
|
|
331
351
|
}
|
|
332
|
-
res.next();
|
|
352
|
+
await res.next();
|
|
333
353
|
}
|
|
334
354
|
db_1.OINOLog.debug("OINODbPostgresql.initializeDatasetModel:\n" + api.datamodel.printDebug("\n"));
|
|
335
355
|
return Promise.resolve();
|
|
@@ -33,14 +33,25 @@ class OINOPostgresqlData extends OINODbDataSet {
|
|
|
33
33
|
}
|
|
34
34
|
_currentRow;
|
|
35
35
|
_eof;
|
|
36
|
+
/**
|
|
37
|
+
* Is data set empty.
|
|
38
|
+
*
|
|
39
|
+
*/
|
|
36
40
|
isEmpty() {
|
|
37
41
|
return (this._rows.length == 0);
|
|
38
42
|
}
|
|
39
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Is there no more content, i.e. either dataset is empty or we have moved beyond last line
|
|
45
|
+
*
|
|
46
|
+
*/
|
|
40
47
|
isEof() {
|
|
41
48
|
return (this._eof);
|
|
42
49
|
}
|
|
43
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Attempts to moves dataset to the next row, possibly waiting for more data to become available. Returns !isEof().
|
|
52
|
+
*
|
|
53
|
+
*/
|
|
54
|
+
async next() {
|
|
44
55
|
// OINOLog.debug("OINODbDataSet.next", {currentRow:this._currentRow, length:this.sqlResult.data.length})
|
|
45
56
|
if (this._currentRow < this._rows.length - 1) {
|
|
46
57
|
this._currentRow = this._currentRow + 1;
|
|
@@ -48,8 +59,12 @@ class OINOPostgresqlData extends OINODbDataSet {
|
|
|
48
59
|
else {
|
|
49
60
|
this._eof = true;
|
|
50
61
|
}
|
|
51
|
-
return !this._eof;
|
|
62
|
+
return Promise.resolve(!this._eof);
|
|
52
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Gets current row of data.
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
53
68
|
getRow() {
|
|
54
69
|
if ((this._currentRow >= 0) && (this._currentRow < this._rows.length)) {
|
|
55
70
|
return this._rows[this._currentRow];
|
|
@@ -64,29 +79,6 @@ class OINOPostgresqlData extends OINODbDataSet {
|
|
|
64
79
|
*
|
|
65
80
|
*/
|
|
66
81
|
export class OINODbPostgresql extends OINODb {
|
|
67
|
-
static table_schema_sql = `SELECT
|
|
68
|
-
col.column_name,
|
|
69
|
-
col.data_type,
|
|
70
|
-
col.character_maximum_length,
|
|
71
|
-
col.is_nullable,
|
|
72
|
-
pk.primary_key,
|
|
73
|
-
col.numeric_precision,
|
|
74
|
-
col.numeric_scale,
|
|
75
|
-
col.column_default
|
|
76
|
-
FROM information_schema.columns col
|
|
77
|
-
LEFT JOIN LATERAL
|
|
78
|
-
(select kcu.column_name, 'YES' as primary_key
|
|
79
|
-
from
|
|
80
|
-
information_schema.table_constraints tco,
|
|
81
|
-
information_schema.key_column_usage kcu
|
|
82
|
-
where
|
|
83
|
-
kcu.constraint_name = tco.constraint_name
|
|
84
|
-
and kcu.constraint_schema = tco.constraint_schema
|
|
85
|
-
and tco.table_name = col.table_name
|
|
86
|
-
and tco.constraint_type = 'PRIMARY KEY'
|
|
87
|
-
) pk on col.column_name = pk.column_name
|
|
88
|
-
WHERE table_name = `;
|
|
89
|
-
// private _client:Client
|
|
90
82
|
_pool;
|
|
91
83
|
/**
|
|
92
84
|
* Constructor of `OINODbPostgresql`
|
|
@@ -272,6 +264,32 @@ WHERE table_name = `;
|
|
|
272
264
|
OINOBenchmark.end("OINODb", "sqlExec");
|
|
273
265
|
return result;
|
|
274
266
|
}
|
|
267
|
+
_getSchemaSql(dbName, tableName) {
|
|
268
|
+
const sql = `SELECT
|
|
269
|
+
col.column_name,
|
|
270
|
+
col.data_type,
|
|
271
|
+
col.character_maximum_length,
|
|
272
|
+
col.is_nullable,
|
|
273
|
+
con.constraint_type,
|
|
274
|
+
col.numeric_precision,
|
|
275
|
+
col.numeric_scale,
|
|
276
|
+
col.column_default
|
|
277
|
+
FROM information_schema.columns col
|
|
278
|
+
LEFT JOIN LATERAL
|
|
279
|
+
(select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
|
|
280
|
+
from
|
|
281
|
+
information_schema.table_constraints tco,
|
|
282
|
+
information_schema.key_column_usage kcu
|
|
283
|
+
where
|
|
284
|
+
kcu.constraint_name = tco.constraint_name
|
|
285
|
+
and kcu.constraint_schema = tco.constraint_schema
|
|
286
|
+
and tco.table_catalog = col.table_catalog
|
|
287
|
+
and tco.table_name = col.table_name
|
|
288
|
+
and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
|
|
289
|
+
group by kcu.column_name) con on col.column_name = con.column_name
|
|
290
|
+
WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`;
|
|
291
|
+
return sql;
|
|
292
|
+
}
|
|
275
293
|
/**
|
|
276
294
|
* Initialize a data model by getting the SQL schema and populating OINODbDataFields of
|
|
277
295
|
* the model.
|
|
@@ -280,7 +298,7 @@ WHERE table_name = `;
|
|
|
280
298
|
*
|
|
281
299
|
*/
|
|
282
300
|
async initializeApiDatamodel(api) {
|
|
283
|
-
const res = await this.sqlSelect(
|
|
301
|
+
const res = await this.sqlSelect(this._getSchemaSql(this._params.database, api.params.tableName.toLowerCase()));
|
|
284
302
|
// OINOLog.debug("OINODbPostgresql.initializeApiDatamodel: table description ", {res: res })
|
|
285
303
|
while (!res.isEof()) {
|
|
286
304
|
const row = res.getRow();
|
|
@@ -288,11 +306,13 @@ WHERE table_name = `;
|
|
|
288
306
|
const field_name = row[0]?.toString() || "";
|
|
289
307
|
const sql_type = row[1]?.toString() || "";
|
|
290
308
|
const field_length = this._parseFieldLength(row[2]);
|
|
309
|
+
const constraints = row[4]?.toString() || "";
|
|
291
310
|
const numeric_precision = this._parseFieldLength(row[5]);
|
|
292
311
|
const numeric_scale = this._parseFieldLength(row[6]);
|
|
293
312
|
const default_val = row[7]?.toString() || "";
|
|
294
313
|
const field_params = {
|
|
295
|
-
isPrimaryKey:
|
|
314
|
+
isPrimaryKey: constraints.indexOf('PRIMARY KEY') >= 0 || false,
|
|
315
|
+
isForeignKey: constraints.indexOf('FOREIGN KEY') >= 0 || false,
|
|
296
316
|
isNotNull: row[3] == "NO",
|
|
297
317
|
isAutoInc: default_val.startsWith("nextval(")
|
|
298
318
|
};
|
|
@@ -326,7 +346,7 @@ WHERE table_name = `;
|
|
|
326
346
|
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
|
|
327
347
|
}
|
|
328
348
|
}
|
|
329
|
-
res.next();
|
|
349
|
+
await res.next();
|
|
330
350
|
}
|
|
331
351
|
OINOLog.debug("OINODbPostgresql.initializeDatasetModel:\n" + api.datamodel.printDebug("\n"));
|
|
332
352
|
return Promise.resolve();
|
|
@@ -4,7 +4,6 @@ import { OINODb, OINODbParams, OINODbDataSet, OINODbApi, OINODataCell } from "@o
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
export declare class OINODbPostgresql extends OINODb {
|
|
7
|
-
private static table_schema_sql;
|
|
8
7
|
private _pool;
|
|
9
8
|
/**
|
|
10
9
|
* Constructor of `OINODbPostgresql`
|
|
@@ -65,6 +64,7 @@ export declare class OINODbPostgresql extends OINODb {
|
|
|
65
64
|
*
|
|
66
65
|
*/
|
|
67
66
|
sqlExec(sql: string): Promise<OINODbDataSet>;
|
|
67
|
+
private _getSchemaSql;
|
|
68
68
|
/**
|
|
69
69
|
* Initialize a data model by getting the SQL schema and populating OINODbDataFields of
|
|
70
70
|
* the model.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oino-ts/db-postgresql",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "OINO TS package for using Postgresql databases.",
|
|
5
5
|
"author": "Matias Kiviniemi (pragmatta)",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"module": "./dist/esm/index.js",
|
|
21
21
|
"types": "./dist/types/index.d.ts",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@oino-ts/db": "^0.0.
|
|
23
|
+
"@oino-ts/db": "^0.0.17",
|
|
24
24
|
"pg": "^8.11.3"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
package/src/OINODbPostgresql.ts
CHANGED
|
@@ -38,25 +38,41 @@ class OINOPostgresqlData extends OINODbDataSet {
|
|
|
38
38
|
}
|
|
39
39
|
private _currentRow: number
|
|
40
40
|
private _eof: boolean
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Is data set empty.
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
41
46
|
isEmpty():boolean {
|
|
42
47
|
return (this._rows.length == 0)
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Is there no more content, i.e. either dataset is empty or we have moved beyond last line
|
|
52
|
+
*
|
|
53
|
+
*/
|
|
46
54
|
isEof():boolean {
|
|
47
55
|
return (this._eof)
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Attempts to moves dataset to the next row, possibly waiting for more data to become available. Returns !isEof().
|
|
60
|
+
*
|
|
61
|
+
*/
|
|
62
|
+
async next():Promise<boolean> {
|
|
51
63
|
// OINOLog.debug("OINODbDataSet.next", {currentRow:this._currentRow, length:this.sqlResult.data.length})
|
|
52
64
|
if (this._currentRow < this._rows.length-1) {
|
|
53
65
|
this._currentRow = this._currentRow + 1
|
|
54
66
|
} else {
|
|
55
67
|
this._eof = true
|
|
56
68
|
}
|
|
57
|
-
return !this._eof
|
|
69
|
+
return Promise.resolve(!this._eof)
|
|
58
70
|
}
|
|
59
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Gets current row of data.
|
|
74
|
+
*
|
|
75
|
+
*/
|
|
60
76
|
getRow(): OINODataRow {
|
|
61
77
|
if ((this._currentRow >=0) && (this._currentRow < this._rows.length)) {
|
|
62
78
|
return this._rows[this._currentRow]
|
|
@@ -72,31 +88,6 @@ class OINOPostgresqlData extends OINODbDataSet {
|
|
|
72
88
|
*/
|
|
73
89
|
export class OINODbPostgresql extends OINODb {
|
|
74
90
|
|
|
75
|
-
private static table_schema_sql:string =
|
|
76
|
-
`SELECT
|
|
77
|
-
col.column_name,
|
|
78
|
-
col.data_type,
|
|
79
|
-
col.character_maximum_length,
|
|
80
|
-
col.is_nullable,
|
|
81
|
-
pk.primary_key,
|
|
82
|
-
col.numeric_precision,
|
|
83
|
-
col.numeric_scale,
|
|
84
|
-
col.column_default
|
|
85
|
-
FROM information_schema.columns col
|
|
86
|
-
LEFT JOIN LATERAL
|
|
87
|
-
(select kcu.column_name, 'YES' as primary_key
|
|
88
|
-
from
|
|
89
|
-
information_schema.table_constraints tco,
|
|
90
|
-
information_schema.key_column_usage kcu
|
|
91
|
-
where
|
|
92
|
-
kcu.constraint_name = tco.constraint_name
|
|
93
|
-
and kcu.constraint_schema = tco.constraint_schema
|
|
94
|
-
and tco.table_name = col.table_name
|
|
95
|
-
and tco.constraint_type = 'PRIMARY KEY'
|
|
96
|
-
) pk on col.column_name = pk.column_name
|
|
97
|
-
WHERE table_name = `
|
|
98
|
-
|
|
99
|
-
// private _client:Client
|
|
100
91
|
private _pool:Pool
|
|
101
92
|
|
|
102
93
|
/**
|
|
@@ -292,6 +283,34 @@ WHERE table_name = `
|
|
|
292
283
|
return result
|
|
293
284
|
}
|
|
294
285
|
|
|
286
|
+
private _getSchemaSql(dbName:string, tableName:string):string {
|
|
287
|
+
const sql =
|
|
288
|
+
`SELECT
|
|
289
|
+
col.column_name,
|
|
290
|
+
col.data_type,
|
|
291
|
+
col.character_maximum_length,
|
|
292
|
+
col.is_nullable,
|
|
293
|
+
con.constraint_type,
|
|
294
|
+
col.numeric_precision,
|
|
295
|
+
col.numeric_scale,
|
|
296
|
+
col.column_default
|
|
297
|
+
FROM information_schema.columns col
|
|
298
|
+
LEFT JOIN LATERAL
|
|
299
|
+
(select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
|
|
300
|
+
from
|
|
301
|
+
information_schema.table_constraints tco,
|
|
302
|
+
information_schema.key_column_usage kcu
|
|
303
|
+
where
|
|
304
|
+
kcu.constraint_name = tco.constraint_name
|
|
305
|
+
and kcu.constraint_schema = tco.constraint_schema
|
|
306
|
+
and tco.table_catalog = col.table_catalog
|
|
307
|
+
and tco.table_name = col.table_name
|
|
308
|
+
and (tco.constraint_type = 'PRIMARY KEY' OR tco.constraint_type = 'FOREIGN KEY')
|
|
309
|
+
group by kcu.column_name) con on col.column_name = con.column_name
|
|
310
|
+
WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`
|
|
311
|
+
return sql
|
|
312
|
+
}
|
|
313
|
+
|
|
295
314
|
/**
|
|
296
315
|
* Initialize a data model by getting the SQL schema and populating OINODbDataFields of
|
|
297
316
|
* the model.
|
|
@@ -301,7 +320,7 @@ WHERE table_name = `
|
|
|
301
320
|
*/
|
|
302
321
|
async initializeApiDatamodel(api:OINODbApi): Promise<void> {
|
|
303
322
|
|
|
304
|
-
const res:OINODbDataSet = await this.sqlSelect(
|
|
323
|
+
const res:OINODbDataSet = await this.sqlSelect(this._getSchemaSql(this._params.database, api.params.tableName.toLowerCase()))
|
|
305
324
|
// OINOLog.debug("OINODbPostgresql.initializeApiDatamodel: table description ", {res: res })
|
|
306
325
|
while (!res.isEof()) {
|
|
307
326
|
const row:OINODataRow = res.getRow()
|
|
@@ -309,11 +328,13 @@ WHERE table_name = `
|
|
|
309
328
|
const field_name:string = row[0]?.toString() || ""
|
|
310
329
|
const sql_type:string = row[1]?.toString() || ""
|
|
311
330
|
const field_length:number = this._parseFieldLength(row[2])
|
|
331
|
+
const constraints = row[4]?.toString() || ""
|
|
312
332
|
const numeric_precision:number = this._parseFieldLength(row[5])
|
|
313
333
|
const numeric_scale:number = this._parseFieldLength(row[6])
|
|
314
334
|
const default_val:string = row[7]?.toString() || ""
|
|
315
335
|
const field_params:OINODbDataFieldParams = {
|
|
316
|
-
isPrimaryKey:
|
|
336
|
+
isPrimaryKey: constraints.indexOf('PRIMARY KEY') >= 0 || false,
|
|
337
|
+
isForeignKey: constraints.indexOf('FOREIGN KEY') >= 0 || false,
|
|
317
338
|
isNotNull: row[3] == "NO",
|
|
318
339
|
isAutoInc: default_val.startsWith("nextval(")
|
|
319
340
|
}
|
|
@@ -346,7 +367,7 @@ WHERE table_name = `
|
|
|
346
367
|
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
|
|
347
368
|
}
|
|
348
369
|
}
|
|
349
|
-
res.next()
|
|
370
|
+
await res.next()
|
|
350
371
|
}
|
|
351
372
|
OINOLog.debug("OINODbPostgresql.initializeDatasetModel:\n" + api.datamodel.printDebug("\n"))
|
|
352
373
|
return Promise.resolve()
|