@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 CHANGED
@@ -10,15 +10,15 @@
10
10
  # GETTING STARTED
11
11
 
12
12
  ### Setup
13
- Install the `@oino-ts/core` npm package and necessary database packages and import them in your code.
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/core
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/core";
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.modelset.writeString(OINOContentType.json))
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
- ### HTTP POST
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 controls the SQL, WHERE-conditions can be defined with [`OINOSqlFilter`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlFilter.html) and order with [`OINOSqlOrder`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlOrder.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.
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 and limits
182
- Similar to filtering and ordering, aggregation and limits can be implemented as HTTP request parameters telling what column is aggregated or used for ordering or how many results to return.
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 support by [node-postgres package](https://www.npmjs.com/package/pg)
215
- - Mariadb / Mysql-support by [mariadb package](https://www.npmjs.com/package/mariadb)
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
- // EOF means "there is no more content", i.e. either dataset is empty or we have moved beyond last line
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
- next() {
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(OINODbPostgresql.table_schema_sql + "'" + api.params.tableName.toLowerCase() + "';");
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: row[4] == "YES",
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
- // EOF means "there is no more content", i.e. either dataset is empty or we have moved beyond last line
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
- next() {
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(OINODbPostgresql.table_schema_sql + "'" + api.params.tableName.toLowerCase() + "';");
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: row[4] == "YES",
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.15",
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.15",
23
+ "@oino-ts/db": "^0.0.17",
24
24
  "pg": "^8.11.3"
25
25
  },
26
26
  "devDependencies": {
@@ -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
- // EOF means "there is no more content", i.e. either dataset is empty or we have moved beyond last line
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
- next():boolean {
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(OINODbPostgresql.table_schema_sql + "'" + api.params.tableName.toLowerCase() + "';")
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: row[4] == "YES",
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()