@oino-ts/db 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -55,6 +55,23 @@ class OINODb {
55
55
  result += ";";
56
56
  return result;
57
57
  }
58
+ /**
59
+ * Print SQL select statement with DB specific formatting.
60
+ *
61
+ * @param tableName - The name of the table to select from.
62
+ * @param columns - The columns to be selected.
63
+ * @param values - The values to be inserted.
64
+ * @param returnIdFields - the id fields to return if returnIds is true (if supported by the database)
65
+ *
66
+ */
67
+ printSqlInsert(tableName, columns, values, returnIdFields) {
68
+ let result = "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + values + ")";
69
+ if (returnIdFields) {
70
+ result += " RETURNING " + returnIdFields.join(",");
71
+ }
72
+ result += ";";
73
+ return result;
74
+ }
58
75
  }
59
76
  exports.OINODb = OINODb;
60
77
  /**
@@ -65,7 +82,7 @@ exports.OINODb = OINODb;
65
82
  * `OINODbDataSet` will serve it out consistently.
66
83
  *
67
84
  */
68
- class OINODbDataSet {
85
+ class OINODbDataSet extends common_1.OINOResult {
69
86
  _data;
70
87
  /** Error messages */
71
88
  messages;
@@ -77,6 +94,7 @@ class OINODbDataSet {
77
94
  *
78
95
  */
79
96
  constructor(data, messages = []) {
97
+ super();
80
98
  this._data = data;
81
99
  this.messages = messages;
82
100
  }
@@ -17,7 +17,7 @@ class OINODbApiRequest extends common_1.OINOHttpRequest {
17
17
  constructor(init) {
18
18
  super(init);
19
19
  this.rowId = init?.rowId || "";
20
- this.rowData = init?.rowData || null;
20
+ this.rowData = init?.rowData || null; // rowData is not compatible with OINOHttpRequest body so it's not automatically set, caller can set both if needed
21
21
  this.sqlParams = init?.sqlParams || {};
22
22
  if (init?.filter) {
23
23
  if (init.filter instanceof index_js_1.OINODbSqlFilter) {
@@ -28,9 +28,15 @@ class OINODbApiRequest extends common_1.OINOHttpRequest {
28
28
  }
29
29
  }
30
30
  if (!this.sqlParams.filter) {
31
- const filter_param = this.url?.searchParams.get(index_js_1.OINODbConfig.OINODB_SQL_FILTER_PARAM);
32
- if (filter_param) {
33
- this.sqlParams.filter = index_js_1.OINODbSqlFilter.parse(filter_param);
31
+ const filter_params = this.url?.searchParams.getAll(index_js_1.OINODbConfig.OINODB_SQL_FILTER_PARAM) || [];
32
+ for (let i = 0; i < filter_params.length; i++) {
33
+ const f = index_js_1.OINODbSqlFilter.parse(filter_params[i]);
34
+ if (i > 0) {
35
+ this.sqlParams.filter = index_js_1.OINODbSqlFilter.combine(this.sqlParams.filter, index_js_1.OINODbSqlBooleanOperation.and, f);
36
+ }
37
+ else {
38
+ this.sqlParams.filter = f;
39
+ }
34
40
  }
35
41
  }
36
42
  if (init?.order) {
@@ -348,8 +354,8 @@ class OINODbApi {
348
354
  sql = this.datamodel.printSqlSelect(rowId, request.sqlParams || {});
349
355
  common_1.OINOLog.debug("@oino-ts/db", "OINODbApi", "_doGet", "Print SQL", { sql: sql });
350
356
  const sql_res = await this.db.sqlSelect(sql);
351
- if (sql_res.hasErrors()) {
352
- result.setError(500, sql_res.getFirstError(), "DoGet");
357
+ if (sql_res.success == false) {
358
+ result.setError(500, sql_res.statusText, "DoGet");
353
359
  if (this._debugOnError) {
354
360
  result.addDebug("OINO GET SQL [" + sql + "]", "DoPut");
355
361
  }
@@ -366,7 +372,7 @@ class OINODbApi {
366
372
  }
367
373
  }
368
374
  }
369
- async _doPost(result, rows) {
375
+ async _doPost(result, rows, request) {
370
376
  let sql = "";
371
377
  try {
372
378
  for (let i = 0; i < rows.length; i++) {
@@ -384,13 +390,16 @@ class OINODbApi {
384
390
  else if (result.success) {
385
391
  common_1.OINOLog.debug("@oino-ts/db", "OINODbApi", "_doPost", "Print SQL", { sql: sql });
386
392
  const sql_res = await this.db.sqlExec(sql);
387
- if (sql_res.hasErrors()) {
388
- result.setError(500, sql_res.getFirstError(), "DoPost");
393
+ if (sql_res.success == false) {
394
+ result.setError(500, sql_res.statusText, "DoPost");
389
395
  if (this._debugOnError) {
390
- result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost");
396
+ result.addDebug("OINO POST MESSAGES [" + sql_res.statusText + "]", "DoPost");
391
397
  result.addDebug("OINO POST SQL [" + sql + "]", "DoPost");
392
398
  }
393
399
  }
400
+ else if (this.params.returnInsertedIds) {
401
+ result.data = new index_js_1.OINODbModelSet(this.datamodel, sql_res, request.sqlParams); // return the inserted ids as data
402
+ }
394
403
  }
395
404
  }
396
405
  catch (e) {
@@ -421,10 +430,10 @@ class OINODbApi {
421
430
  else if (result.success) {
422
431
  common_1.OINOLog.debug("@oino-ts/db", "OINODbApi", "_doPut", "Print SQL", { sql: sql });
423
432
  const sql_res = await this.db.sqlExec(sql);
424
- if (sql_res.hasErrors()) {
425
- result.setError(500, sql_res.getFirstError(), "DoPut");
433
+ if (sql_res.success == false) {
434
+ result.setError(500, sql_res.statusText, "DoPut");
426
435
  if (this._debugOnError) {
427
- result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut");
436
+ result.addDebug("OINO PUT MESSAGES [" + sql_res.statusText + "]", "DoPut");
428
437
  result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut");
429
438
  }
430
439
  }
@@ -434,7 +443,7 @@ class OINODbApi {
434
443
  result.setError(500, "Unhandled exception: " + e.message, "DoPut");
435
444
  common_1.OINOLog.exception("@oino-ts/db", "OINODbApi", "_doPut", "exception in put request", { message: e.message, stack: e.stack });
436
445
  if (this._debugOnError) {
437
- result.addDebug("OINO POST SQL [" + sql + "]", "DoPut");
446
+ result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut");
438
447
  }
439
448
  }
440
449
  }
@@ -461,10 +470,10 @@ class OINODbApi {
461
470
  else if (result.success) {
462
471
  common_1.OINOLog.debug("@oino-ts/db", "OINODbApi", "_doDelete", "Print SQL", { sql: sql });
463
472
  const sql_res = await this.db.sqlExec(sql);
464
- if (sql_res.hasErrors()) {
465
- result.setError(500, sql_res.getFirstError(), "DoDelete");
473
+ if (sql_res.success == false) {
474
+ result.setError(500, sql_res.statusText, "DoDelete");
466
475
  if (this._debugOnError) {
467
- result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete");
476
+ result.addDebug("OINO DELETE MESSAGES [" + sql_res.statusText + "]", "DoDelete");
468
477
  result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete");
469
478
  }
470
479
  }
@@ -550,7 +559,7 @@ class OINODbApi {
550
559
  }
551
560
  else {
552
561
  try {
553
- await this._doPost(result, rows);
562
+ await this._doPost(result, rows, request);
554
563
  }
555
564
  catch (e) {
556
565
  result.setError(500, "Unhandled exception in HTTP POST doRequest: " + e.message, "DoRequest");
@@ -67,7 +67,7 @@ class OINODbDataModel {
67
67
  }
68
68
  }
69
69
  // console.log("_printSqlInsertColumnsAndValues: columns=" + columns + ", values=" + values)
70
- return "(" + columns + ") VALUES (" + values + ")";
70
+ return [columns, values];
71
71
  }
72
72
  _printSqlUpdateValues(row) {
73
73
  let result = "";
@@ -109,6 +109,15 @@ class OINODbDataModel {
109
109
  }
110
110
  return "(" + result + ")";
111
111
  }
112
+ _printSqlPrimaryKeyColumns() {
113
+ let result = [];
114
+ for (let f of this.fields) {
115
+ if (f.fieldParams.isPrimaryKey) {
116
+ result.push(this.api.db.printSqlColumnname(f.name));
117
+ }
118
+ }
119
+ return result;
120
+ }
112
121
  /**
113
122
  * Add a field to the datamodel.
114
123
  *
@@ -255,8 +264,10 @@ class OINODbDataModel {
255
264
  *
256
265
  */
257
266
  printSqlInsert(row) {
258
- let result = "INSERT INTO " + this.api.db.printSqlTablename(this.api.params.tableName) + " " + this._printSqlInsertColumnsAndValues(row) + ";";
259
- return result;
267
+ const table_name = this.api.db.printSqlTablename(this.api.params.tableName);
268
+ const [columns, values] = this._printSqlInsertColumnsAndValues(row);
269
+ const return_fields = this.api.params.returnInsertedIds ? this._printSqlPrimaryKeyColumns() : undefined;
270
+ return this.api.db.printSqlInsert(table_name, columns, values, return_fields);
260
271
  }
261
272
  /**
262
273
  * Print SQL insert statement from one data row.
package/dist/cjs/index.js CHANGED
@@ -42,6 +42,6 @@ Object.defineProperty(exports, "OINODbParser", { enumerable: true, get: function
42
42
  /** Empty row instance */
43
43
  exports.OINODB_EMPTY_ROW = [];
44
44
  /** Empty row array instance */
45
- exports.OINODB_EMPTY_ROWS = [exports.OINODB_EMPTY_ROW];
45
+ exports.OINODB_EMPTY_ROWS = [];
46
46
  /** Constant for undefined values */
47
47
  exports.OINODB_UNDEFINED = ""; // original idea was to have a defined literal that get's swapped back to undefined, but current implementation just leaves it out at serialization (so value does not matter)
@@ -3,7 +3,7 @@
3
3
  * License, v. 2.0. If a copy of the MPL was not distributed with this
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
- import { OINO_ERROR_PREFIX } from "@oino-ts/common";
6
+ import { OINO_ERROR_PREFIX, OINOResult } from "@oino-ts/common";
7
7
  import { OINODB_EMPTY_ROW } from "./index.js";
8
8
  /**
9
9
  * Base class for database abstraction, implementing methods for connecting, making queries and parsing/formatting data
@@ -52,6 +52,23 @@ export class OINODb {
52
52
  result += ";";
53
53
  return result;
54
54
  }
55
+ /**
56
+ * Print SQL select statement with DB specific formatting.
57
+ *
58
+ * @param tableName - The name of the table to select from.
59
+ * @param columns - The columns to be selected.
60
+ * @param values - The values to be inserted.
61
+ * @param returnIdFields - the id fields to return if returnIds is true (if supported by the database)
62
+ *
63
+ */
64
+ printSqlInsert(tableName, columns, values, returnIdFields) {
65
+ let result = "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + values + ")";
66
+ if (returnIdFields) {
67
+ result += " RETURNING " + returnIdFields.join(",");
68
+ }
69
+ result += ";";
70
+ return result;
71
+ }
55
72
  }
56
73
  /**
57
74
  * Base class for SQL results that can be asynchronously iterated (but
@@ -61,7 +78,7 @@ export class OINODb {
61
78
  * `OINODbDataSet` will serve it out consistently.
62
79
  *
63
80
  */
64
- export class OINODbDataSet {
81
+ export class OINODbDataSet extends OINOResult {
65
82
  _data;
66
83
  /** Error messages */
67
84
  messages;
@@ -73,6 +90,7 @@ export class OINODbDataSet {
73
90
  *
74
91
  */
75
92
  constructor(data, messages = []) {
93
+ super();
76
94
  this._data = data;
77
95
  this.messages = messages;
78
96
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { Buffer } from "node:buffer";
7
7
  import { OINOLog, OINOResult, OINOHttpRequest, OINOBenchmark, OINOHtmlTemplate, OINOContentType, OINO_ERROR_PREFIX } from "@oino-ts/common";
8
- import { OINODbDataModel, OINOStringDataField, OINODbModelSet, OINODbConfig, OINONumberDataField, OINODbParser, OINODatetimeDataField, OINODbSqlAggregate, OINODbSqlSelect, OINODbSqlFilter, OINODbSqlOrder, OINODbSqlLimit } from "./index.js";
8
+ import { OINODbDataModel, OINOStringDataField, OINODbModelSet, OINODbConfig, OINONumberDataField, OINODbParser, OINODatetimeDataField, OINODbSqlAggregate, OINODbSqlSelect, OINODbSqlFilter, OINODbSqlOrder, OINODbSqlLimit, OINODbSqlBooleanOperation } from "./index.js";
9
9
  import { OINOHashid } from "@oino-ts/hashid";
10
10
  export class OINODbApiRequest extends OINOHttpRequest {
11
11
  rowId;
@@ -14,7 +14,7 @@ export class OINODbApiRequest extends OINOHttpRequest {
14
14
  constructor(init) {
15
15
  super(init);
16
16
  this.rowId = init?.rowId || "";
17
- this.rowData = init?.rowData || null;
17
+ this.rowData = init?.rowData || null; // rowData is not compatible with OINOHttpRequest body so it's not automatically set, caller can set both if needed
18
18
  this.sqlParams = init?.sqlParams || {};
19
19
  if (init?.filter) {
20
20
  if (init.filter instanceof OINODbSqlFilter) {
@@ -25,9 +25,15 @@ export class OINODbApiRequest extends OINOHttpRequest {
25
25
  }
26
26
  }
27
27
  if (!this.sqlParams.filter) {
28
- const filter_param = this.url?.searchParams.get(OINODbConfig.OINODB_SQL_FILTER_PARAM);
29
- if (filter_param) {
30
- this.sqlParams.filter = OINODbSqlFilter.parse(filter_param);
28
+ const filter_params = this.url?.searchParams.getAll(OINODbConfig.OINODB_SQL_FILTER_PARAM) || [];
29
+ for (let i = 0; i < filter_params.length; i++) {
30
+ const f = OINODbSqlFilter.parse(filter_params[i]);
31
+ if (i > 0) {
32
+ this.sqlParams.filter = OINODbSqlFilter.combine(this.sqlParams.filter, OINODbSqlBooleanOperation.and, f);
33
+ }
34
+ else {
35
+ this.sqlParams.filter = f;
36
+ }
31
37
  }
32
38
  }
33
39
  if (init?.order) {
@@ -342,8 +348,8 @@ export class OINODbApi {
342
348
  sql = this.datamodel.printSqlSelect(rowId, request.sqlParams || {});
343
349
  OINOLog.debug("@oino-ts/db", "OINODbApi", "_doGet", "Print SQL", { sql: sql });
344
350
  const sql_res = await this.db.sqlSelect(sql);
345
- if (sql_res.hasErrors()) {
346
- result.setError(500, sql_res.getFirstError(), "DoGet");
351
+ if (sql_res.success == false) {
352
+ result.setError(500, sql_res.statusText, "DoGet");
347
353
  if (this._debugOnError) {
348
354
  result.addDebug("OINO GET SQL [" + sql + "]", "DoPut");
349
355
  }
@@ -360,7 +366,7 @@ export class OINODbApi {
360
366
  }
361
367
  }
362
368
  }
363
- async _doPost(result, rows) {
369
+ async _doPost(result, rows, request) {
364
370
  let sql = "";
365
371
  try {
366
372
  for (let i = 0; i < rows.length; i++) {
@@ -378,13 +384,16 @@ export class OINODbApi {
378
384
  else if (result.success) {
379
385
  OINOLog.debug("@oino-ts/db", "OINODbApi", "_doPost", "Print SQL", { sql: sql });
380
386
  const sql_res = await this.db.sqlExec(sql);
381
- if (sql_res.hasErrors()) {
382
- result.setError(500, sql_res.getFirstError(), "DoPost");
387
+ if (sql_res.success == false) {
388
+ result.setError(500, sql_res.statusText, "DoPost");
383
389
  if (this._debugOnError) {
384
- result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost");
390
+ result.addDebug("OINO POST MESSAGES [" + sql_res.statusText + "]", "DoPost");
385
391
  result.addDebug("OINO POST SQL [" + sql + "]", "DoPost");
386
392
  }
387
393
  }
394
+ else if (this.params.returnInsertedIds) {
395
+ result.data = new OINODbModelSet(this.datamodel, sql_res, request.sqlParams); // return the inserted ids as data
396
+ }
388
397
  }
389
398
  }
390
399
  catch (e) {
@@ -415,10 +424,10 @@ export class OINODbApi {
415
424
  else if (result.success) {
416
425
  OINOLog.debug("@oino-ts/db", "OINODbApi", "_doPut", "Print SQL", { sql: sql });
417
426
  const sql_res = await this.db.sqlExec(sql);
418
- if (sql_res.hasErrors()) {
419
- result.setError(500, sql_res.getFirstError(), "DoPut");
427
+ if (sql_res.success == false) {
428
+ result.setError(500, sql_res.statusText, "DoPut");
420
429
  if (this._debugOnError) {
421
- result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut");
430
+ result.addDebug("OINO PUT MESSAGES [" + sql_res.statusText + "]", "DoPut");
422
431
  result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut");
423
432
  }
424
433
  }
@@ -428,7 +437,7 @@ export class OINODbApi {
428
437
  result.setError(500, "Unhandled exception: " + e.message, "DoPut");
429
438
  OINOLog.exception("@oino-ts/db", "OINODbApi", "_doPut", "exception in put request", { message: e.message, stack: e.stack });
430
439
  if (this._debugOnError) {
431
- result.addDebug("OINO POST SQL [" + sql + "]", "DoPut");
440
+ result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut");
432
441
  }
433
442
  }
434
443
  }
@@ -455,10 +464,10 @@ export class OINODbApi {
455
464
  else if (result.success) {
456
465
  OINOLog.debug("@oino-ts/db", "OINODbApi", "_doDelete", "Print SQL", { sql: sql });
457
466
  const sql_res = await this.db.sqlExec(sql);
458
- if (sql_res.hasErrors()) {
459
- result.setError(500, sql_res.getFirstError(), "DoDelete");
467
+ if (sql_res.success == false) {
468
+ result.setError(500, sql_res.statusText, "DoDelete");
460
469
  if (this._debugOnError) {
461
- result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete");
470
+ result.addDebug("OINO DELETE MESSAGES [" + sql_res.statusText + "]", "DoDelete");
462
471
  result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete");
463
472
  }
464
473
  }
@@ -544,7 +553,7 @@ export class OINODbApi {
544
553
  }
545
554
  else {
546
555
  try {
547
- await this._doPost(result, rows);
556
+ await this._doPost(result, rows, request);
548
557
  }
549
558
  catch (e) {
550
559
  result.setError(500, "Unhandled exception in HTTP POST doRequest: " + e.message, "DoRequest");
@@ -64,7 +64,7 @@ export class OINODbDataModel {
64
64
  }
65
65
  }
66
66
  // console.log("_printSqlInsertColumnsAndValues: columns=" + columns + ", values=" + values)
67
- return "(" + columns + ") VALUES (" + values + ")";
67
+ return [columns, values];
68
68
  }
69
69
  _printSqlUpdateValues(row) {
70
70
  let result = "";
@@ -106,6 +106,15 @@ export class OINODbDataModel {
106
106
  }
107
107
  return "(" + result + ")";
108
108
  }
109
+ _printSqlPrimaryKeyColumns() {
110
+ let result = [];
111
+ for (let f of this.fields) {
112
+ if (f.fieldParams.isPrimaryKey) {
113
+ result.push(this.api.db.printSqlColumnname(f.name));
114
+ }
115
+ }
116
+ return result;
117
+ }
109
118
  /**
110
119
  * Add a field to the datamodel.
111
120
  *
@@ -252,8 +261,10 @@ export class OINODbDataModel {
252
261
  *
253
262
  */
254
263
  printSqlInsert(row) {
255
- let result = "INSERT INTO " + this.api.db.printSqlTablename(this.api.params.tableName) + " " + this._printSqlInsertColumnsAndValues(row) + ";";
256
- return result;
264
+ const table_name = this.api.db.printSqlTablename(this.api.params.tableName);
265
+ const [columns, values] = this._printSqlInsertColumnsAndValues(row);
266
+ const return_fields = this.api.params.returnInsertedIds ? this._printSqlPrimaryKeyColumns() : undefined;
267
+ return this.api.db.printSqlInsert(table_name, columns, values, return_fields);
257
268
  }
258
269
  /**
259
270
  * Print SQL insert statement from one data row.
package/dist/esm/index.js CHANGED
@@ -11,6 +11,6 @@ export { OINODbParser } from "./OINODbParser.js";
11
11
  /** Empty row instance */
12
12
  export const OINODB_EMPTY_ROW = [];
13
13
  /** Empty row array instance */
14
- export const OINODB_EMPTY_ROWS = [OINODB_EMPTY_ROW];
14
+ export const OINODB_EMPTY_ROWS = [];
15
15
  /** Constant for undefined values */
16
16
  export const OINODB_UNDEFINED = ""; // original idea was to have a defined literal that get's swapped back to undefined, but current implementation just leaves it out at serialization (so value does not matter)
@@ -26,6 +26,11 @@ export declare abstract class OINODb {
26
26
  *
27
27
  */
28
28
  abstract validate(): Promise<OINOResult>;
29
+ /**
30
+ * Disconnect from database.
31
+ *
32
+ */
33
+ abstract disconnect(): Promise<void>;
29
34
  /**
30
35
  * Print a table name using database specific SQL escaping.
31
36
  *
@@ -99,6 +104,16 @@ export declare abstract class OINODb {
99
104
  *
100
105
  */
101
106
  printSqlSelect(tableName: string, columnNames: string, whereCondition: string, orderCondition: string, limitCondition: string, groupByCondition: string): string;
107
+ /**
108
+ * Print SQL select statement with DB specific formatting.
109
+ *
110
+ * @param tableName - The name of the table to select from.
111
+ * @param columns - The columns to be selected.
112
+ * @param values - The values to be inserted.
113
+ * @param returnIdFields - the id fields to return if returnIds is true (if supported by the database)
114
+ *
115
+ */
116
+ printSqlInsert(tableName: string, columns: string, values: string, returnIdFields?: string[]): string;
102
117
  }
103
118
  /**
104
119
  * Base class for SQL results that can be asynchronously iterated (but
@@ -108,7 +123,7 @@ export declare abstract class OINODb {
108
123
  * `OINODbDataSet` will serve it out consistently.
109
124
  *
110
125
  */
111
- export declare abstract class OINODbDataSet {
126
+ export declare abstract class OINODbDataSet extends OINOResult {
112
127
  private _data;
113
128
  /** Error messages */
114
129
  readonly messages: string[];
@@ -26,6 +26,7 @@ export declare class OINODbDataModel {
26
26
  private _printSqlInsertColumnsAndValues;
27
27
  private _printSqlUpdateValues;
28
28
  private _printSqlPrimaryKeyCondition;
29
+ private _printSqlPrimaryKeyColumns;
29
30
  /**
30
31
  * Add a field to the datamodel.
31
32
  *
@@ -44,6 +44,8 @@ export type OINODbApiParams = {
44
44
  hashidStaticIds?: boolean;
45
45
  /** Name of field that has the modified field */
46
46
  cacheModifiedField?: string;
47
+ /** Return inserted id values */
48
+ returnInsertedIds?: boolean;
47
49
  };
48
50
  /**
49
51
  * Database class (constructor) type
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/db",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "description": "OINO TS library package for publishing an SQL database tables as a REST API.",
5
5
  "author": "Matias Kiviniemi (pragmatta)",
6
6
  "license": "MPL-2.0",
@@ -19,13 +19,13 @@
19
19
  "module": "./dist/esm/index.js",
20
20
  "types": "./dist/types/index.d.ts",
21
21
  "dependencies": {
22
- "@oino-ts/common": "0.19.0",
22
+ "@oino-ts/common": "0.20.1",
23
23
  "oino-ts": "file:.."
24
24
  },
25
25
  "devDependencies": {
26
- "@oino-ts/types": "0.19.0",
26
+ "@oino-ts/types": "0.20.1",
27
27
  "@types/bun": "^1.1.14",
28
- "@types/node": "^20.19.00",
28
+ "@types/node": "^20.20.10",
29
29
  "typescript": "~5.9.0"
30
30
  },
31
31
  "files": [
package/src/OINODb.ts CHANGED
@@ -43,6 +43,12 @@ export abstract class OINODb {
43
43
  */
44
44
  abstract validate(): Promise<OINOResult>
45
45
 
46
+ /**
47
+ * Disconnect from database.
48
+ *
49
+ */
50
+ abstract disconnect(): Promise<void>
51
+
46
52
  /**
47
53
  * Print a table name using database specific SQL escaping.
48
54
  *
@@ -140,6 +146,25 @@ export abstract class OINODb {
140
146
  result += ";"
141
147
  return result;
142
148
  }
149
+
150
+ /**
151
+ * Print SQL select statement with DB specific formatting.
152
+ *
153
+ * @param tableName - The name of the table to select from.
154
+ * @param columns - The columns to be selected.
155
+ * @param values - The values to be inserted.
156
+ * @param returnIdFields - the id fields to return if returnIds is true (if supported by the database)
157
+ *
158
+ */
159
+ printSqlInsert(tableName:string, columns:string, values:string, returnIdFields?:string[]): string {
160
+ let result = "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + values + ")"
161
+ if (returnIdFields) {
162
+ result += " RETURNING " + returnIdFields.join(",")
163
+ }
164
+ result += ";"
165
+ return result;
166
+ }
167
+
143
168
  }
144
169
 
145
170
  /**
@@ -151,7 +176,7 @@ export abstract class OINODb {
151
176
  *
152
177
  */
153
178
 
154
- export abstract class OINODbDataSet {
179
+ export abstract class OINODbDataSet extends OINOResult {
155
180
  private _data: unknown;
156
181
 
157
182
  /** Error messages */
@@ -165,6 +190,7 @@ export abstract class OINODbDataSet {
165
190
  *
166
191
  */
167
192
  constructor(data: unknown, messages: string[] = []) {
193
+ super();
168
194
  this._data = data;
169
195
  this.messages = messages;
170
196
  }
@@ -65,7 +65,7 @@ const API_TESTS:OINOTestParams[] = [
65
65
  },
66
66
  {
67
67
  name: "API 3",
68
- apiParams: { apiName: "Employees", tableName: "Employees", hashidKey: "12345678901234567890123456789012", hashidStaticIds:true },
68
+ apiParams: { apiName: "Employees", tableName: "Employees", hashidKey: "12345678901234567890123456789012", hashidStaticIds:true, returnInsertedIds:true },
69
69
  sqlParams: { filter: OINODbSqlFilter.parse("(TitleOfCourtesy)-eq(Ms.)"), order: OINODbSqlOrder.parse("LastName asc"), limit: OINODbSqlLimit.parse("5") },
70
70
  postRow: [99, "LastName", "FirstName", "Title", "TitleOfCourtesy", new Date("2024-04-06"), new Date("2024-04-07"), "Address", "City", "Region", 12345, "EU", "123 456 7890", "9876", Buffer.from("0001020304", "hex"), "Line1\nLine2", 1, "http://accweb/emmployees/lastnamefirstname.bmp"],
71
71
  putRow: [99, "LastName2", "FirstName2", null, "TitleOfCourtesy2", new Date("2023-04-06"), new Date("2023-04-07"), "Address2", "City2", "Region2", 54321, "EU2", "234 567 8901", "8765", Buffer.from("0506070809", "hex"), "Line3\nLine4", 1, "http://accweb/emmployees/lastnamefirstname.bmp"],
@@ -260,7 +260,12 @@ export async function OINOTestApi(dbParams:OINODbParams, testParams: OINOTestPar
260
260
  expect(encodeResult((await api.doApiRequest(post_request_with_id)))).toMatchSnapshot("POST")
261
261
  })
262
262
  await test(target_name + target_db + target_table + target_group + " insert", async () => {
263
- expect(encodeResult((await api.doApiRequest(post_request)))).toMatchSnapshot("POST")
263
+ const post_res = await api.doApiRequest(post_request)
264
+ if (testParams.apiParams.returnInsertedIds) {
265
+ expect(encodeData(await post_res.data?.writeString())).toMatchSnapshot("POST RETURN ID")
266
+ } else {
267
+ expect(encodeResult(post_res)).toMatchSnapshot("POST")
268
+ }
264
269
  expect(encodeData(await (await api.doApiRequest(get_request_with_rowid)).data?.writeString())).toMatchSnapshot("GET JSON")
265
270
  expect(encodeData(await (await api.doApiRequest(get_request_with_rowid)).data?.writeString(OINOContentType.csv))).toMatchSnapshot("GET CSV")
266
271
  })
package/src/OINODbApi.ts CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { Buffer } from "node:buffer";
8
8
  import { OINOLog, OINOResult, OINOHttpRequest, type OINOHttpRequestInit, OINOBenchmark, OINOHttpResult, OINOHtmlTemplate, OINOContentType, OINO_ERROR_PREFIX } from "@oino-ts/common";
9
- import { OINODbApiParams, OINODb, OINODbDataSet, OINODbDataModel, OINODbDataField, OINOStringDataField, OINODataRow, OINODataCell, OINODbModelSet, OINODbConfig, OINONumberDataField, OINODbParser, OINODatetimeDataField, OINODbSqlParams, OINODbSqlAggregate, OINODbSqlSelect, OINODbSqlFilter, OINODbSqlOrder, OINODbSqlLimit } from "./index.js"
9
+ import { OINODbApiParams, OINODb, OINODbDataSet, OINODbDataModel, OINODbDataField, OINOStringDataField, OINODataRow, OINODataCell, OINODbModelSet, OINODbConfig, OINONumberDataField, OINODbParser, OINODatetimeDataField, OINODbSqlParams, OINODbSqlAggregate, OINODbSqlSelect, OINODbSqlFilter, OINODbSqlOrder, OINODbSqlLimit, OINODbSqlBooleanOperation } from "./index.js"
10
10
  import { OINOHashid } from "@oino-ts/hashid"
11
11
 
12
12
  export type OINODbApiData = string|OINODataRow[]|Buffer|Uint8Array|object|null
@@ -30,7 +30,7 @@ export class OINODbApiRequest extends OINOHttpRequest {
30
30
  constructor (init: OINODbApiRequestInit) {
31
31
  super(init)
32
32
  this.rowId = init?.rowId || ""
33
- this.rowData = init?.rowData || null
33
+ this.rowData = init?.rowData || null // rowData is not compatible with OINOHttpRequest body so it's not automatically set, caller can set both if needed
34
34
  this.sqlParams = init?.sqlParams || {}
35
35
 
36
36
  if (init?.filter) {
@@ -41,9 +41,14 @@ export class OINODbApiRequest extends OINOHttpRequest {
41
41
  }
42
42
  }
43
43
  if (!this.sqlParams.filter) {
44
- const filter_param = this.url?.searchParams.get(OINODbConfig.OINODB_SQL_FILTER_PARAM)
45
- if (filter_param) {
46
- this.sqlParams.filter = OINODbSqlFilter.parse(filter_param)
44
+ const filter_params = this.url?.searchParams.getAll(OINODbConfig.OINODB_SQL_FILTER_PARAM) || []
45
+ for (let i=0; i<filter_params.length; i++) {
46
+ const f = OINODbSqlFilter.parse(filter_params[i])
47
+ if (i > 0) {
48
+ this.sqlParams.filter = OINODbSqlFilter.combine(this.sqlParams.filter, OINODbSqlBooleanOperation.and, f)
49
+ } else {
50
+ this.sqlParams.filter = f
51
+ }
47
52
  }
48
53
  }
49
54
  if (init?.order) {
@@ -369,8 +374,8 @@ export class OINODbApi {
369
374
  sql = this.datamodel.printSqlSelect(rowId, request.sqlParams || {})
370
375
  OINOLog.debug("@oino-ts/db", "OINODbApi", "_doGet", "Print SQL", {sql:sql})
371
376
  const sql_res:OINODbDataSet = await this.db.sqlSelect(sql)
372
- if (sql_res.hasErrors()) {
373
- result.setError(500, sql_res.getFirstError(), "DoGet")
377
+ if (sql_res.success == false) {
378
+ result.setError(500, sql_res.statusText, "DoGet")
374
379
  if (this._debugOnError) {
375
380
  result.addDebug("OINO GET SQL [" + sql + "]", "DoPut")
376
381
  }
@@ -386,7 +391,7 @@ export class OINODbApi {
386
391
  }
387
392
  }
388
393
 
389
- private async _doPost(result:OINODbApiResult, rows:OINODataRow[]):Promise<void> {
394
+ private async _doPost(result:OINODbApiResult, rows:OINODataRow[], request:OINODbApiRequest):Promise<void> {
390
395
  let sql:string = ""
391
396
  try {
392
397
  for (let i=0; i<rows.length; i++) {
@@ -404,12 +409,14 @@ export class OINODbApi {
404
409
  } else if (result.success) {
405
410
  OINOLog.debug("@oino-ts/db", "OINODbApi", "_doPost", "Print SQL", {sql:sql})
406
411
  const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
407
- if (sql_res.hasErrors()) {
408
- result.setError(500, sql_res.getFirstError(), "DoPost")
412
+ if (sql_res.success == false) {
413
+ result.setError(500, sql_res.statusText, "DoPost")
409
414
  if (this._debugOnError) {
410
- result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost")
415
+ result.addDebug("OINO POST MESSAGES [" + sql_res.statusText + "]", "DoPost")
411
416
  result.addDebug("OINO POST SQL [" + sql + "]", "DoPost")
412
417
  }
418
+ } else if (this.params.returnInsertedIds) {
419
+ result.data = new OINODbModelSet(this.datamodel, sql_res, request.sqlParams) // return the inserted ids as data
413
420
  }
414
421
  }
415
422
  } catch (e:any) {
@@ -441,10 +448,10 @@ export class OINODbApi {
441
448
  } else if (result.success) {
442
449
  OINOLog.debug("@oino-ts/db", "OINODbApi", "_doPut", "Print SQL", {sql:sql})
443
450
  const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
444
- if (sql_res.hasErrors()) {
445
- result.setError(500, sql_res.getFirstError(), "DoPut")
451
+ if (sql_res.success == false) {
452
+ result.setError(500, sql_res.statusText, "DoPut")
446
453
  if (this._debugOnError) {
447
- result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut")
454
+ result.addDebug("OINO PUT MESSAGES [" + sql_res.statusText + "]", "DoPut")
448
455
  result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut")
449
456
  }
450
457
  }
@@ -453,7 +460,7 @@ export class OINODbApi {
453
460
  result.setError(500, "Unhandled exception: " + e.message, "DoPut")
454
461
  OINOLog.exception("@oino-ts/db", "OINODbApi", "_doPut", "exception in put request", {message:e.message, stack:e.stack})
455
462
  if (this._debugOnError) {
456
- result.addDebug("OINO POST SQL [" + sql + "]", "DoPut")
463
+ result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut")
457
464
  }
458
465
  }
459
466
  }
@@ -480,10 +487,10 @@ export class OINODbApi {
480
487
 
481
488
  OINOLog.debug("@oino-ts/db", "OINODbApi", "_doDelete", "Print SQL", {sql:sql})
482
489
  const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
483
- if (sql_res.hasErrors()) {
484
- result.setError(500, sql_res.getFirstError(), "DoDelete")
490
+ if (sql_res.success == false) {
491
+ result.setError(500, sql_res.statusText, "DoDelete")
485
492
  if (this._debugOnError) {
486
- result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete")
493
+ result.addDebug("OINO DELETE MESSAGES [" + sql_res.statusText + "]", "DoDelete")
487
494
  result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete")
488
495
  }
489
496
  }
@@ -570,7 +577,7 @@ export class OINODbApi {
570
577
 
571
578
  } else {
572
579
  try {
573
- await this._doPost(result, rows)
580
+ await this._doPost(result, rows, request)
574
581
 
575
582
  } catch (e:any) {
576
583
  result.setError(500, "Unhandled exception in HTTP POST doRequest: " + e.message, "DoRequest")
@@ -53,7 +53,7 @@ export class OINODbDataModel {
53
53
  return result.substring(0, result.length-1)
54
54
  }
55
55
 
56
- private _printSqlInsertColumnsAndValues(row: OINODataRow): string {
56
+ private _printSqlInsertColumnsAndValues(row: OINODataRow): [string, string] {
57
57
  let columns: string = "";
58
58
  let values: string = "";
59
59
  for (let i=0; i< this.fields.length; i++) {
@@ -70,7 +70,7 @@ export class OINODbDataModel {
70
70
  }
71
71
  }
72
72
  // console.log("_printSqlInsertColumnsAndValues: columns=" + columns + ", values=" + values)
73
- return "(" + columns + ") VALUES (" + values + ")";
73
+ return [ columns, values ]
74
74
  }
75
75
 
76
76
  private _printSqlUpdateValues(row: OINODataRow): string {
@@ -115,6 +115,16 @@ export class OINODbDataModel {
115
115
  return "(" + result + ")";
116
116
  }
117
117
 
118
+ private _printSqlPrimaryKeyColumns(): string[] {
119
+ let result: string[] = []
120
+ for (let f of this.fields) {
121
+ if (f.fieldParams.isPrimaryKey) {
122
+ result.push(this.api.db.printSqlColumnname(f.name))
123
+ }
124
+ }
125
+ return result
126
+ }
127
+
118
128
  /**
119
129
  * Add a field to the datamodel.
120
130
  *
@@ -263,8 +273,10 @@ export class OINODbDataModel {
263
273
  *
264
274
  */
265
275
  printSqlInsert(row: OINODataRow): string {
266
- let result: string = "INSERT INTO " + this.api.db.printSqlTablename(this.api.params.tableName) + " " + this._printSqlInsertColumnsAndValues(row) + ";";
267
- return result;
276
+ const table_name = this.api.db.printSqlTablename(this.api.params.tableName)
277
+ const [columns, values] = this._printSqlInsertColumnsAndValues(row)
278
+ const return_fields = this.api.params.returnInsertedIds ? this._printSqlPrimaryKeyColumns() : undefined
279
+ return this.api.db.printSqlInsert(table_name, columns, values, return_fields);
268
280
  }
269
281
 
270
282
  /**
package/src/index.ts CHANGED
@@ -43,7 +43,9 @@ export type OINODbApiParams = {
43
43
  /** Make hashids static per row/table */
44
44
  hashidStaticIds?: boolean,
45
45
  /** Name of field that has the modified field */
46
- cacheModifiedField?:string
46
+ cacheModifiedField?:string,
47
+ /** Return inserted id values */
48
+ returnInsertedIds?: boolean
47
49
  }
48
50
 
49
51
  /**
@@ -107,7 +109,7 @@ export type OINODataRow = Array<OINODataCell>
107
109
  /** Empty row instance */
108
110
  export const OINODB_EMPTY_ROW:OINODataRow = []
109
111
  /** Empty row array instance */
110
- export const OINODB_EMPTY_ROWS:OINODataRow[] = [OINODB_EMPTY_ROW]
112
+ export const OINODB_EMPTY_ROWS:OINODataRow[] = []
111
113
  /** Constant for undefined values */
112
114
  export const OINODB_UNDEFINED = "" // original idea was to have a defined literal that get's swapped back to undefined, but current implementation just leaves it out at serialization (so value does not matter)
113
115