@oino-ts/db-postgresql 0.0.16 → 0.0.18

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
@@ -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. Also supports standard HTML form file submission to blob fields and returning them data url images.
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).
@@ -131,11 +100,11 @@
131
100
  - Mariadb / Mysql-support through [mariadb](https://www.npmjs.com/package/mariadb)-package
132
101
  - Sql Server through [mssql](https://www.npmjs.com/package/mssql)-package
133
102
 
134
- ## Complex Keys
103
+ ## Composite Keys
135
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`.
136
105
 
137
106
  ## Power Of SQL
138
- Since OINO is just generating 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 [`OINOSqlLimit`](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.
139
108
 
140
109
  ## Swagger Support
141
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.
@@ -152,7 +121,7 @@
152
121
  ## HTMX support
153
122
  OINO is [htmx.org](https://htmx.org) friendly, allowing easy translation of [`OINODataRow`](https://pragmatta.github.io/oino-ts/types/db_src.OINODataRow.html) to HTML output using templates (cf. the [htmx sample app](https://github.com/pragmatta/oino-ts/tree/main/samples/htmxApp)).
154
123
 
155
- ### Hashids
124
+ ## Hashids
156
125
  Autoinc numeric id's are very pragmatic and fit well with OINO (e.g. using a form without primary key fields to insert new rows with database assigned ids). However it's not always sensible to share information about the sequence. Hashids solve this by masking the original values by encrypting the ids using AES-128 and some randomness. Length of the hashid can be chosen from 12-32 characters where longer ids provide more security. However this should not be considereded a cryptographic solution for keeping ids secret but rather making it infeasible to iterate all ids.
157
126
 
158
127
 
@@ -164,9 +133,6 @@
164
133
 
165
134
  ### Realistic app
166
135
  There needs to be a realistic app built on top of OINO to get a better grasp of the edge cases.
167
-
168
- ### Security review
169
- Handling of SQL-injection attacks needs a thorough review, what are the relevant attack vectors are for OINO and what protections are still needed.
170
136
 
171
137
  ## Roadmap
172
138
  Things that need to happen in some order before beta-status are at least following:
@@ -179,8 +145,8 @@
179
145
  ### Batch updates
180
146
  Supporting batch updates similar to batch inserts is slightly bending the RESTfull principles but would still be a useful optional feature.
181
147
 
182
- ### Aggregation and limits
183
- 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.
148
+ ### Aggregation
149
+ 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.
184
150
 
185
151
  ### Streaming
186
152
  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.
@@ -222,4 +188,3 @@
222
188
 
223
189
  ## SQL Scripts
224
190
  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).
225
-
@@ -273,22 +273,23 @@ class OINODbPostgresql extends db_1.OINODb {
273
273
  col.data_type,
274
274
  col.character_maximum_length,
275
275
  col.is_nullable,
276
- pk.primary_key,
276
+ con.constraint_type,
277
277
  col.numeric_precision,
278
278
  col.numeric_scale,
279
279
  col.column_default
280
280
  FROM information_schema.columns col
281
281
  LEFT JOIN LATERAL
282
- (select kcu.column_name, 'YES' as primary_key
282
+ (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
283
283
  from
284
284
  information_schema.table_constraints tco,
285
285
  information_schema.key_column_usage kcu
286
286
  where
287
287
  kcu.constraint_name = tco.constraint_name
288
288
  and kcu.constraint_schema = tco.constraint_schema
289
- and tco.table_name = col.table_name
290
- and tco.constraint_type = 'PRIMARY KEY'
291
- ) pk on col.column_name = pk.column_name
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
292
293
  WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`;
293
294
  return sql;
294
295
  }
@@ -308,11 +309,13 @@ WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`;
308
309
  const field_name = row[0]?.toString() || "";
309
310
  const sql_type = row[1]?.toString() || "";
310
311
  const field_length = this._parseFieldLength(row[2]);
312
+ const constraints = row[4]?.toString() || "";
311
313
  const numeric_precision = this._parseFieldLength(row[5]);
312
314
  const numeric_scale = this._parseFieldLength(row[6]);
313
315
  const default_val = row[7]?.toString() || "";
314
316
  const field_params = {
315
- isPrimaryKey: row[4] == "YES",
317
+ isPrimaryKey: constraints.indexOf('PRIMARY KEY') >= 0 || false,
318
+ isForeignKey: constraints.indexOf('FOREIGN KEY') >= 0 || false,
316
319
  isNotNull: row[3] == "NO",
317
320
  isAutoInc: default_val.startsWith("nextval(")
318
321
  };
@@ -270,22 +270,23 @@ export class OINODbPostgresql extends OINODb {
270
270
  col.data_type,
271
271
  col.character_maximum_length,
272
272
  col.is_nullable,
273
- pk.primary_key,
273
+ con.constraint_type,
274
274
  col.numeric_precision,
275
275
  col.numeric_scale,
276
276
  col.column_default
277
277
  FROM information_schema.columns col
278
278
  LEFT JOIN LATERAL
279
- (select kcu.column_name, 'YES' as primary_key
279
+ (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
280
280
  from
281
281
  information_schema.table_constraints tco,
282
282
  information_schema.key_column_usage kcu
283
283
  where
284
284
  kcu.constraint_name = tco.constraint_name
285
285
  and kcu.constraint_schema = tco.constraint_schema
286
- and tco.table_name = col.table_name
287
- and tco.constraint_type = 'PRIMARY KEY'
288
- ) pk on col.column_name = pk.column_name
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
289
290
  WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`;
290
291
  return sql;
291
292
  }
@@ -305,11 +306,13 @@ WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`;
305
306
  const field_name = row[0]?.toString() || "";
306
307
  const sql_type = row[1]?.toString() || "";
307
308
  const field_length = this._parseFieldLength(row[2]);
309
+ const constraints = row[4]?.toString() || "";
308
310
  const numeric_precision = this._parseFieldLength(row[5]);
309
311
  const numeric_scale = this._parseFieldLength(row[6]);
310
312
  const default_val = row[7]?.toString() || "";
311
313
  const field_params = {
312
- isPrimaryKey: row[4] == "YES",
314
+ isPrimaryKey: constraints.indexOf('PRIMARY KEY') >= 0 || false,
315
+ isForeignKey: constraints.indexOf('FOREIGN KEY') >= 0 || false,
313
316
  isNotNull: row[3] == "NO",
314
317
  isAutoInc: default_val.startsWith("nextval(")
315
318
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/db-postgresql",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
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.16",
23
+ "@oino-ts/db": "^0.0.18",
24
24
  "pg": "^8.11.3"
25
25
  },
26
26
  "devDependencies": {
@@ -290,22 +290,23 @@ export class OINODbPostgresql extends OINODb {
290
290
  col.data_type,
291
291
  col.character_maximum_length,
292
292
  col.is_nullable,
293
- pk.primary_key,
293
+ con.constraint_type,
294
294
  col.numeric_precision,
295
295
  col.numeric_scale,
296
296
  col.column_default
297
297
  FROM information_schema.columns col
298
298
  LEFT JOIN LATERAL
299
- (select kcu.column_name, 'YES' as primary_key
299
+ (select kcu.column_name, STRING_AGG(tco.constraint_type,',') as constraint_type
300
300
  from
301
301
  information_schema.table_constraints tco,
302
302
  information_schema.key_column_usage kcu
303
303
  where
304
304
  kcu.constraint_name = tco.constraint_name
305
305
  and kcu.constraint_schema = tco.constraint_schema
306
- and tco.table_name = col.table_name
307
- and tco.constraint_type = 'PRIMARY KEY'
308
- ) pk on col.column_name = pk.column_name
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
309
310
  WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`
310
311
  return sql
311
312
  }
@@ -327,11 +328,13 @@ WHERE col.table_catalog = '${dbName}' AND col.table_name = '${tableName}'`
327
328
  const field_name:string = row[0]?.toString() || ""
328
329
  const sql_type:string = row[1]?.toString() || ""
329
330
  const field_length:number = this._parseFieldLength(row[2])
331
+ const constraints = row[4]?.toString() || ""
330
332
  const numeric_precision:number = this._parseFieldLength(row[5])
331
333
  const numeric_scale:number = this._parseFieldLength(row[6])
332
334
  const default_val:string = row[7]?.toString() || ""
333
335
  const field_params:OINODbDataFieldParams = {
334
- isPrimaryKey: row[4] == "YES",
336
+ isPrimaryKey: constraints.indexOf('PRIMARY KEY') >= 0 || false,
337
+ isForeignKey: constraints.indexOf('FOREIGN KEY') >= 0 || false,
335
338
  isNotNull: row[3] == "NO",
336
339
  isAutoInc: default_val.startsWith("nextval(")
337
340
  }