@oino-ts/db 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,13 +16,15 @@ class OINODb {
16
16
  _params;
17
17
  /** Name of the database */
18
18
  name;
19
+ isConnected = false;
20
+ isValidated = false;
19
21
  /**
20
22
  * Constructor for `OINODb`.
21
23
  * @param params database parameters
22
24
  */
23
25
  constructor(params) {
24
- this._params = params;
25
- this.name = params.database;
26
+ this._params = { ...params }; // make a shallow copy of params so that changes to them do not affect the original object
27
+ this.name = this._params.database;
26
28
  }
27
29
  /**
28
30
  * Print SQL select statement with DB specific formatting.
@@ -175,6 +177,13 @@ class OINODbMemoryDataSet extends OINODbDataSet {
175
177
  return index_js_1.OINODB_EMPTY_ROW;
176
178
  }
177
179
  }
180
+ /**
181
+ * Gets all rows of data.
182
+ *
183
+ */
184
+ async getAllRows() {
185
+ return this._rows; // at the moment theres no result streaming, so we can just return the rows
186
+ }
178
187
  /**
179
188
  * Rewinds data set to the first row, returns !isEof().
180
189
  *
@@ -158,6 +158,8 @@ exports.OINODbHtmlTemplate = OINODbHtmlTemplate;
158
158
  *
159
159
  */
160
160
  class OINODbApi {
161
+ /** Enable debug output on errors */
162
+ _debugOnError = false;
161
163
  /** API database reference */
162
164
  db;
163
165
  /** API datamodel */
@@ -189,7 +191,20 @@ class OINODbApi {
189
191
  this.hashid = null;
190
192
  }
191
193
  }
192
- _validateRowValues(httpResult, row, requirePrimaryKey) {
194
+ _printSql(result, rows, requirePrimaryKey, printer) {
195
+ let sql = "";
196
+ for (let i = 0; i < rows.length; i++) {
197
+ this._validateRow(result, rows[i], requirePrimaryKey);
198
+ if (result.success) {
199
+ sql += printer(rows[i]);
200
+ }
201
+ else if (this.params.failOnAnyInvalidRows === false) {
202
+ result.setOk(); // individual rows may fail and will just be messages in response similar to executing multiple sql statements
203
+ }
204
+ }
205
+ return sql;
206
+ }
207
+ _validateRow(result, row, requirePrimaryKey) {
193
208
  let field;
194
209
  for (let i = 0; i < this.datamodel.fields.length; i++) {
195
210
  field = this.datamodel.fields[i];
@@ -197,13 +212,13 @@ class OINODbApi {
197
212
  const val = row[i];
198
213
  // OINOLog.debug("OINODbApi.validateHttpValues", {val:val})
199
214
  if ((val === null) && ((field.fieldParams.isNotNull) || (field.fieldParams.isPrimaryKey))) { // null is a valid SQL value except if it's not allowed
200
- httpResult.setError(405, "Field '" + field.name + "' is not allowed to be NULL!", "ValidateRowValues");
215
+ result.setError(405, "Field '" + field.name + "' is not allowed to be NULL!", "ValidateRowValues");
201
216
  }
202
217
  else if ((val === undefined) && (requirePrimaryKey) && (field.fieldParams.isPrimaryKey) && (!field.fieldParams.isAutoInc)) {
203
- httpResult.setError(405, "Primary key '" + field.name + "' is not autoinc and missing from the data!", "ValidateRowValues");
218
+ result.setError(405, "Primary key '" + field.name + "' is not autoinc and missing from the data!", "ValidateRowValues");
204
219
  }
205
220
  else if ((val !== undefined) && (this.params.failOnUpdateOnAutoinc) && (field.fieldParams.isAutoInc)) {
206
- httpResult.setError(405, "Autoinc field '" + field.name + "' can't be updated!", "ValidateRowValues");
221
+ result.setError(405, "Autoinc field '" + field.name + "' can't be updated!", "ValidateRowValues");
207
222
  }
208
223
  else {
209
224
  if ((field instanceof index_js_1.OINOStringDataField) && ((field.maxLength > 0))) {
@@ -211,10 +226,10 @@ class OINODbApi {
211
226
  // OINOLog.debug("OINODbApi.validateHttpValues", {f:str_field, val:val})
212
227
  if (str_val.length > field.maxLength) {
213
228
  if (this.params.failOnOversizedValues) {
214
- httpResult.setError(405, "Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and can't be set!", "ValidateRowValues");
229
+ result.setError(405, "Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and can't be set!", "ValidateRowValues");
215
230
  }
216
231
  else {
217
- httpResult.addWarning("Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and might truncate or fail.", "ValidateRowValues");
232
+ result.addWarning("Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and might truncate or fail.", "ValidateRowValues");
218
233
  }
219
234
  }
220
235
  }
@@ -222,6 +237,21 @@ class OINODbApi {
222
237
  }
223
238
  //logDebug("OINODbApi.validateHttpValues", {result:result})
224
239
  }
240
+ _parseData(httpResult, body, params) {
241
+ let rows = [];
242
+ try {
243
+ if (Array.isArray(body)) {
244
+ rows = body;
245
+ }
246
+ else {
247
+ rows = index_js_1.OINODbParser.createRows(this.datamodel, body, params);
248
+ }
249
+ }
250
+ catch (e) {
251
+ httpResult.setError(400, "Invalid data: " + e.message, "DoRequest");
252
+ }
253
+ return rows;
254
+ }
225
255
  async _doGet(result, id, params) {
226
256
  let sql = "";
227
257
  try {
@@ -245,27 +275,28 @@ class OINODbApi {
245
275
  async _doPost(result, rows) {
246
276
  let sql = "";
247
277
  try {
248
- let i = 0;
249
- while (i < rows.length) {
250
- this._validateRowValues(result, rows[i], this.params.failOnInsertWithoutKey || false);
278
+ for (let i = 0; i < rows.length; i++) {
279
+ this._validateRow(result, rows[i], this.params.failOnInsertWithoutKey || false);
251
280
  if (result.success) {
252
281
  sql += this.datamodel.printSqlInsert(rows[i]);
253
282
  }
254
- result.setOk(); // individual rows may fail and will just be messages in response similar to executing multiple sql statements
255
- i++;
283
+ else if (this.params.failOnAnyInvalidRows == false) {
284
+ result.setOk(); // individual rows may fail and will just be messages in response similar to executing multiple sql statements
285
+ }
256
286
  }
257
- if (sql == "") {
287
+ if ((sql == "") && result.success) {
258
288
  result.setError(405, "No valid rows for POST!", "DoPost");
259
- result.addDebug("OINO POST DATA [" + rows.join("|") + "]", "DoPost");
260
289
  }
261
- else {
290
+ else if (result.success) {
262
291
  // OINOLog.debug("OINODbApi.doPost sql", {sql:sql})
263
292
  const sql_res = await this.db.sqlExec(sql);
264
293
  // OINOLog.debug("OINODbApi.doPost sql_res", {sql_res:sql_res})
265
294
  if (sql_res.hasErrors()) {
266
295
  result.setError(500, sql_res.getFirstError(), "DoPost");
267
- result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost");
268
- result.addDebug("OINO POST SQL [" + sql + "]", "DoPost");
296
+ if (this._debugOnError) {
297
+ result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost");
298
+ result.addDebug("OINO POST SQL [" + sql + "]", "DoPost");
299
+ }
269
300
  }
270
301
  }
271
302
  }
@@ -274,19 +305,33 @@ class OINODbApi {
274
305
  result.addDebug("OINO POST SQL [" + sql + "]", "DoPost");
275
306
  }
276
307
  }
277
- async _doPut(result, id, row) {
308
+ async _doPut(result, id, rows) {
278
309
  let sql = "";
279
310
  try {
280
- this._validateRowValues(result, row, false);
281
- if (result.success) {
282
- sql = this.datamodel.printSqlUpdate(id, row);
311
+ // this._validateRowValues(result, row, false)
312
+ for (let i = 0; i < rows.length; i++) {
313
+ const row_id = id || index_js_1.OINODbConfig.printOINOId(this.datamodel.getRowPrimarykeyValues(rows[i], this.hashid != null));
314
+ this._validateRow(result, rows[i], this.params.failOnInsertWithoutKey || false);
315
+ if (result.success) {
316
+ sql += this.datamodel.printSqlUpdate(row_id, rows[i]);
317
+ }
318
+ else if (this.params.failOnAnyInvalidRows == false) {
319
+ result.setOk(); // individual rows may fail and will just be messages in response similar to executing multiple sql statements
320
+ }
321
+ }
322
+ if ((sql == "") && result.success) {
323
+ result.setError(405, "No valid rows for PUT!", "DoPut"); // only set error if there are multiple rows and no valid sql was created
324
+ }
325
+ else if (result.success) {
283
326
  // OINOLog.debug("OINODbApi.doPut sql", {sql:sql})
284
327
  const sql_res = await this.db.sqlExec(sql);
285
328
  // OINOLog.debug("OINODbApi.doPut sql_res", {sql_res:sql_res})
286
329
  if (sql_res.hasErrors()) {
287
330
  result.setError(500, sql_res.getFirstError(), "DoPut");
288
- result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut");
289
- result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut");
331
+ if (this._debugOnError) {
332
+ result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut");
333
+ result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut");
334
+ }
290
335
  }
291
336
  }
292
337
  }
@@ -295,17 +340,37 @@ class OINODbApi {
295
340
  result.addDebug("OINO POST SQL [" + sql + "]", "DoPut");
296
341
  }
297
342
  }
298
- async _doDelete(result, id) {
343
+ async _doDelete(result, id, rows) {
299
344
  let sql = "";
300
345
  try {
301
- sql = this.datamodel.printSqlDelete(id);
302
- // OINOLog.debug("OINODbApi.doDelete sql", {sql:sql})
303
- const sql_res = await this.db.sqlExec(sql);
304
- // OINOLog.debug("OINODbApi.doDelete sql_res", {sql_res:sql_res})
305
- if (sql_res.hasErrors()) {
306
- result.setError(500, sql_res.getFirstError(), "DoDelete");
307
- result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete");
308
- result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete");
346
+ if (rows != null) {
347
+ for (let i = 0; i < rows.length; i++) {
348
+ const row_id = index_js_1.OINODbConfig.printOINOId(this.datamodel.getRowPrimarykeyValues(rows[i], this.hashid != null));
349
+ if (row_id) {
350
+ sql += this.datamodel.printSqlDelete(row_id);
351
+ }
352
+ else if (this.params.failOnAnyInvalidRows == false) {
353
+ result.setOk(); // individual rows may fail and will just be messages in response similar to executing multiple sql statements
354
+ }
355
+ }
356
+ }
357
+ else if (id) {
358
+ sql = this.datamodel.printSqlDelete(id);
359
+ }
360
+ if ((sql == "") && result.success) {
361
+ result.setError(405, "No valid rows for DELETE!", "DoDelete"); // only set error if there are multiple rows and no valid sql was created
362
+ }
363
+ else if (result.success) {
364
+ // OINOLog.debug("OINODbApi.doDelete sql", {sql:sql})
365
+ const sql_res = await this.db.sqlExec(sql);
366
+ // OINOLog.debug("OINODbApi.doDelete sql_res", {sql_res:sql_res})
367
+ if (sql_res.hasErrors()) {
368
+ result.setError(500, sql_res.getFirstError(), "DoDelete");
369
+ if (this._debugOnError) {
370
+ result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete");
371
+ result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete");
372
+ }
373
+ }
309
374
  }
310
375
  }
311
376
  catch (e) {
@@ -313,34 +378,31 @@ class OINODbApi {
313
378
  result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete");
314
379
  }
315
380
  }
381
+ /**
382
+ * Enable or disable debug output on errors.
383
+ *
384
+ * @param debugOnError true to enable debug output on errors, false to disable
385
+ */
386
+ setDebugOnError(debugOnError) {
387
+ this._debugOnError = debugOnError;
388
+ }
316
389
  /**
317
390
  * Method for handlind a HTTP REST request with GET, POST, PUT, DELETE corresponding to
318
391
  * SQL select, insert, update and delete.
319
392
  *
320
393
  * @param method HTTP verb (uppercase)
321
394
  * @param id URL id of the REST request
322
- * @param body HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
395
+ * @param data HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
323
396
  * @param params HTTP URL parameters as key-value-pairs
324
397
  *
325
398
  */
326
- async doRequest(method, id, body, params = API_EMPTY_PARAMS) {
399
+ async doRequest(method, id, data, params = API_EMPTY_PARAMS) {
327
400
  index_js_1.OINOBenchmark.start("OINODbApi", "doRequest");
328
401
  // OINOLog.debug("OINODbApi.doRequest enter", {method:method, id:id, body:body, params:params})
329
402
  let result = new OINODbApiResult(params);
330
403
  let rows = [];
331
404
  if ((method == "POST") || (method == "PUT")) {
332
- try {
333
- if (Array.isArray(body)) {
334
- rows = body;
335
- }
336
- else {
337
- rows = index_js_1.OINODbParser.createRows(this.datamodel, body, params);
338
- }
339
- }
340
- catch (e) {
341
- result.setError(400, "Invalid data: " + e.message, "DoRequest");
342
- }
343
- // OINOLog.debug("OINODbApi.doRequest - OINODataRow rows", {rows:rows})
405
+ rows = this._parseData(result, data, params);
344
406
  }
345
407
  if (method == "GET") {
346
408
  await this._doGet(result, id, params);
@@ -354,7 +416,7 @@ class OINODbApi {
354
416
  }
355
417
  else {
356
418
  try {
357
- await this._doPut(result, id, rows[0]);
419
+ await this._doPut(result, id, rows);
358
420
  }
359
421
  catch (e) {
360
422
  result.setError(500, "Unhandled exception in HTTP PUT doRequest: " + e.message, "DoRequest");
@@ -384,7 +446,7 @@ class OINODbApi {
384
446
  }
385
447
  else {
386
448
  try {
387
- await this._doDelete(result, id);
449
+ await this._doDelete(result, id, null);
388
450
  }
389
451
  catch (e) {
390
452
  result.setError(500, "Unhandled exception in HTTP DELETE doRequest: " + e.message, "DoRequest");
@@ -392,11 +454,50 @@ class OINODbApi {
392
454
  }
393
455
  }
394
456
  else {
395
- result.setError(405, "Unsupported HTTP method '" + method + "'", "DoRequest");
457
+ result.setError(405, "Unsupported HTTP method '" + method + "' for REST request", "DoRequest");
396
458
  }
397
459
  index_js_1.OINOBenchmark.end("OINODbApi", "doRequest", method);
398
460
  return Promise.resolve(result);
399
461
  }
462
+ /**
463
+ * Method for handlind a HTTP REST request with GET, POST, PUT, DELETE corresponding to
464
+ * SQL select, insert, update and delete.
465
+ *
466
+ * @param method HTTP verb (uppercase)
467
+ * @param data HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
468
+ * @param params HTTP URL parameters as key-value-pairs
469
+ *
470
+ */
471
+ async doBatchUpdate(method, data, params = API_EMPTY_PARAMS) {
472
+ index_js_1.OINOBenchmark.start("OINODbApi", "doBatchUpdate");
473
+ // OINOLog.debug("OINODbApi.doRequest enter", {method:method, id:id, body:body, params:params})
474
+ let result = new OINODbApiResult(params);
475
+ let rows = [];
476
+ if ((method == "PUT")) {
477
+ rows = this._parseData(result, data, params);
478
+ }
479
+ if (method == "PUT") {
480
+ try {
481
+ await this._doPut(result, null, rows);
482
+ }
483
+ catch (e) {
484
+ result.setError(500, "Unhandled exception in HTTP PUT doRequest: " + e.message, "DoBatchUpdate");
485
+ }
486
+ }
487
+ else if (method == "DELETE") {
488
+ try {
489
+ await this._doDelete(result, null, rows);
490
+ }
491
+ catch (e) {
492
+ result.setError(500, "Unhandled exception in HTTP DELETE doRequest: " + e.message, "DoBatchUpdate");
493
+ }
494
+ }
495
+ else {
496
+ result.setError(405, "Unsupported HTTP method '" + method + "' for batch update", "DoBatchUpdate");
497
+ }
498
+ index_js_1.OINOBenchmark.end("OINODbApi", "doBatchUpdate", method);
499
+ return Promise.resolve(result);
500
+ }
400
501
  /**
401
502
  * Method to check if a field is included in the API params.
402
503
  *
@@ -97,6 +97,7 @@ class OINODbDataModel {
97
97
  if ((f instanceof index_js_1.OINONumberDataField) && (this.api.hashid)) {
98
98
  value = this.api.hashid.decode(value);
99
99
  }
100
+ // OINOLog.debug("OINODbDataModel._printSqlPrimaryKeyCondition", {field:f.name, value:value, id_value:id_value})
100
101
  result += f.printSqlColumnName() + "=" + f.printCellAsSqlValue(value);
101
102
  i = i + 1;
102
103
  }
@@ -269,6 +270,7 @@ class OINODbDataModel {
269
270
  */
270
271
  printSqlUpdate(id, row) {
271
272
  let result = "UPDATE " + this.api.db.printSqlTablename(this.api.params.tableName) + " SET " + this._printSqlUpdateValues(row) + " WHERE " + this._printSqlPrimaryKeyCondition(id) + ";";
273
+ // OINOLog.debug("OINODbDataModel.printSqlUpdate", {result:result, id:id, row:row})
272
274
  return result;
273
275
  }
274
276
  /**
@@ -29,7 +29,7 @@ class OINODbFactory {
29
29
  *
30
30
  * @param params database connection parameters
31
31
  */
32
- static async createDb(params) {
32
+ static async createDb(params, connect = true, validate = true) {
33
33
  let result;
34
34
  let db_type = this._dbRegistry[params.type];
35
35
  if (db_type) {
@@ -38,10 +38,17 @@ class OINODbFactory {
38
38
  else {
39
39
  throw new Error("Unsupported database type: " + params.type);
40
40
  }
41
- await result.connect();
42
- const validate_res = await result.validate();
43
- if (validate_res.success == false) {
44
- throw new Error("Database connection validation failed: " + validate_res.statusMessage);
41
+ if (connect) {
42
+ const connect_res = await result.connect();
43
+ if (connect_res.success == false) {
44
+ throw new Error("Database connection failed: " + connect_res.statusMessage);
45
+ }
46
+ }
47
+ if (validate) {
48
+ const validate_res = await result.validate();
49
+ if (validate_res.success == false) {
50
+ throw new Error("Database validation failed: " + validate_res.statusMessage);
51
+ }
45
52
  }
46
53
  return result;
47
54
  }
@@ -13,13 +13,15 @@ export class OINODb {
13
13
  _params;
14
14
  /** Name of the database */
15
15
  name;
16
+ isConnected = false;
17
+ isValidated = false;
16
18
  /**
17
19
  * Constructor for `OINODb`.
18
20
  * @param params database parameters
19
21
  */
20
22
  constructor(params) {
21
- this._params = params;
22
- this.name = params.database;
23
+ this._params = { ...params }; // make a shallow copy of params so that changes to them do not affect the original object
24
+ this.name = this._params.database;
23
25
  }
24
26
  /**
25
27
  * Print SQL select statement with DB specific formatting.
@@ -170,6 +172,13 @@ export class OINODbMemoryDataSet extends OINODbDataSet {
170
172
  return OINODB_EMPTY_ROW;
171
173
  }
172
174
  }
175
+ /**
176
+ * Gets all rows of data.
177
+ *
178
+ */
179
+ async getAllRows() {
180
+ return this._rows; // at the moment theres no result streaming, so we can just return the rows
181
+ }
173
182
  /**
174
183
  * Rewinds data set to the first row, returns !isEof().
175
184
  *