@oino-ts/db 0.6.1 → 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.
package/src/OINODbApi.ts CHANGED
@@ -162,6 +162,9 @@ export class OINODbHtmlTemplate extends OINOHtmlTemplate {
162
162
  *
163
163
  */
164
164
  export class OINODbApi {
165
+ /** Enable debug output on errors */
166
+ private _debugOnError:boolean = false
167
+
165
168
  /** API database reference */
166
169
  readonly db: OINODb
167
170
 
@@ -197,7 +200,19 @@ export class OINODbApi {
197
200
  }
198
201
  }
199
202
 
200
- private _validateRowValues(httpResult:OINODbApiResult, row:OINODataRow, requirePrimaryKey:boolean):void {
203
+ private _printSql(result: OINODbApiResult, rows: OINODataRow[], requirePrimaryKey: boolean, printer: (row: OINODataRow) => string): string {
204
+ let sql = "";
205
+ for (let i = 0; i < rows.length; i++) {
206
+ this._validateRow(result, rows[i], requirePrimaryKey);
207
+ if (result.success) {
208
+ sql += printer(rows[i]);
209
+ } else if (this.params.failOnAnyInvalidRows === false) {
210
+ result.setOk(); // individual rows may fail and will just be messages in response similar to executing multiple sql statements
211
+ }
212
+ }
213
+ return sql;
214
+ }
215
+ private _validateRow(result:OINODbApiResult, row:OINODataRow, requirePrimaryKey:boolean):void {
201
216
  let field:OINODbDataField
202
217
  for (let i=0; i<this.datamodel.fields.length; i++) {
203
218
  field = this.datamodel.fields[i]
@@ -205,13 +220,13 @@ export class OINODbApi {
205
220
  const val:OINODataCell = row[i]
206
221
  // OINOLog.debug("OINODbApi.validateHttpValues", {val:val})
207
222
  if ((val === null) && ((field.fieldParams.isNotNull)||(field.fieldParams.isPrimaryKey))) { // null is a valid SQL value except if it's not allowed
208
- httpResult.setError(405, "Field '" + field.name + "' is not allowed to be NULL!", "ValidateRowValues")
223
+ result.setError(405, "Field '" + field.name + "' is not allowed to be NULL!", "ValidateRowValues")
209
224
 
210
225
  } else if ((val === undefined) && (requirePrimaryKey) && (field.fieldParams.isPrimaryKey) && (!field.fieldParams.isAutoInc)) {
211
- httpResult.setError(405, "Primary key '" + field.name + "' is not autoinc and missing from the data!", "ValidateRowValues")
226
+ result.setError(405, "Primary key '" + field.name + "' is not autoinc and missing from the data!", "ValidateRowValues")
212
227
 
213
228
  } else if ((val !== undefined) && (this.params.failOnUpdateOnAutoinc) && (field.fieldParams.isAutoInc)) {
214
- httpResult.setError(405, "Autoinc field '" + field.name + "' can't be updated!", "ValidateRowValues")
229
+ result.setError(405, "Autoinc field '" + field.name + "' can't be updated!", "ValidateRowValues")
215
230
 
216
231
  } else {
217
232
  if ((field instanceof OINOStringDataField) && ((field.maxLength > 0))){
@@ -219,9 +234,9 @@ export class OINODbApi {
219
234
  // OINOLog.debug("OINODbApi.validateHttpValues", {f:str_field, val:val})
220
235
  if (str_val.length > field.maxLength) {
221
236
  if (this.params.failOnOversizedValues) {
222
- httpResult.setError(405, "Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and can't be set!", "ValidateRowValues")
237
+ result.setError(405, "Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and can't be set!", "ValidateRowValues")
223
238
  } else {
224
- httpResult.addWarning("Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and might truncate or fail.", "ValidateRowValues")
239
+ result.addWarning("Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and might truncate or fail.", "ValidateRowValues")
225
240
  }
226
241
  }
227
242
  }
@@ -231,6 +246,21 @@ export class OINODbApi {
231
246
  //logDebug("OINODbApi.validateHttpValues", {result:result})
232
247
  }
233
248
 
249
+ private _parseData(httpResult:OINODbApiResult, body:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams):OINODataRow[] {
250
+ let rows:OINODataRow[] = []
251
+ try {
252
+ if (Array.isArray(body)) {
253
+ rows = body as OINODataRow[]
254
+ } else {
255
+ rows = OINODbParser.createRows(this.datamodel, body, params)
256
+ }
257
+
258
+ } catch (e:any) {
259
+ httpResult.setError(400, "Invalid data: " + e.message, "DoRequest")
260
+ }
261
+ return rows
262
+ }
263
+
234
264
  private async _doGet(result:OINODbApiResult, id:string, params:OINODbApiRequestParams):Promise<void> {
235
265
  let sql:string = ""
236
266
  try {
@@ -253,27 +283,28 @@ export class OINODbApi {
253
283
  private async _doPost(result:OINODbApiResult, rows:OINODataRow[]):Promise<void> {
254
284
  let sql:string = ""
255
285
  try {
256
- let i:number = 0
257
- while (i<rows.length) {
258
- this._validateRowValues(result, rows[i], this.params.failOnInsertWithoutKey||false)
286
+ for (let i=0; i<rows.length; i++) {
287
+ this._validateRow(result, rows[i], this.params.failOnInsertWithoutKey||false)
259
288
  if (result.success) {
260
289
  sql += this.datamodel.printSqlInsert(rows[i])
290
+
291
+ } else if (this.params.failOnAnyInvalidRows == false) {
292
+ result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
261
293
  }
262
- result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
263
- i++
264
294
  }
265
- if (sql == "") {
295
+ if ((sql == "") && result.success) {
266
296
  result.setError(405, "No valid rows for POST!", "DoPost")
267
- result.addDebug("OINO POST DATA [" + rows.join("|") + "]", "DoPost")
268
297
 
269
- } else {
298
+ } else if (result.success) {
270
299
  // OINOLog.debug("OINODbApi.doPost sql", {sql:sql})
271
300
  const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
272
301
  // OINOLog.debug("OINODbApi.doPost sql_res", {sql_res:sql_res})
273
302
  if (sql_res.hasErrors()) {
274
303
  result.setError(500, sql_res.getFirstError(), "DoPost")
275
- result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost")
276
- result.addDebug("OINO POST SQL [" + sql + "]", "DoPost")
304
+ if (this._debugOnError) {
305
+ result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost")
306
+ result.addDebug("OINO POST SQL [" + sql + "]", "DoPost")
307
+ }
277
308
  }
278
309
  }
279
310
  } catch (e:any) {
@@ -282,19 +313,33 @@ export class OINODbApi {
282
313
  }
283
314
  }
284
315
 
285
- private async _doPut(result:OINODbApiResult, id:string, row:OINODataRow):Promise<void> {
316
+ private async _doPut(result:OINODbApiResult, id:string|null, rows:OINODataRow[]):Promise<void> {
286
317
  let sql:string = ""
287
318
  try {
288
- this._validateRowValues(result, row, false)
289
- if (result.success) {
290
- sql = this.datamodel.printSqlUpdate(id, row)
319
+ // this._validateRowValues(result, row, false)
320
+ for (let i=0; i<rows.length; i++) {
321
+ const row_id = id || OINODbConfig.printOINOId(this.datamodel.getRowPrimarykeyValues(rows[i], this.hashid != null))
322
+ this._validateRow(result, rows[i], this.params.failOnInsertWithoutKey||false)
323
+ if (result.success) {
324
+ sql += this.datamodel.printSqlUpdate(row_id, rows[i])
325
+
326
+ } else if (this.params.failOnAnyInvalidRows == false) {
327
+ result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
328
+ }
329
+ }
330
+ if ((sql == "") && result.success) {
331
+ result.setError(405, "No valid rows for PUT!", "DoPut") // only set error if there are multiple rows and no valid sql was created
332
+
333
+ } else if (result.success) {
291
334
  // OINOLog.debug("OINODbApi.doPut sql", {sql:sql})
292
335
  const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
293
336
  // OINOLog.debug("OINODbApi.doPut sql_res", {sql_res:sql_res})
294
337
  if (sql_res.hasErrors()) {
295
338
  result.setError(500, sql_res.getFirstError(), "DoPut")
296
- result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut")
297
- result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut")
339
+ if (this._debugOnError) {
340
+ result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut")
341
+ result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut")
342
+ }
298
343
  }
299
344
  }
300
345
  } catch (e:any) {
@@ -303,17 +348,36 @@ export class OINODbApi {
303
348
  }
304
349
  }
305
350
 
306
- private async _doDelete(result:OINODbApiResult, id:string):Promise<void> {
351
+ private async _doDelete(result:OINODbApiResult, id:string|null, rows:OINODataRow[]|null):Promise<void> {
307
352
  let sql:string = ""
308
353
  try {
309
- sql = this.datamodel.printSqlDelete(id)
310
- // OINOLog.debug("OINODbApi.doDelete sql", {sql:sql})
311
- const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
312
- // OINOLog.debug("OINODbApi.doDelete sql_res", {sql_res:sql_res})
313
- if (sql_res.hasErrors()) {
314
- result.setError(500, sql_res.getFirstError(), "DoDelete")
315
- result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete")
316
- result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete")
354
+ if (rows != null) {
355
+ for (let i=0; i<rows.length; i++) {
356
+ const row_id = OINODbConfig.printOINOId(this.datamodel.getRowPrimarykeyValues(rows[i], this.hashid != null))
357
+ if (row_id) {
358
+ sql += this.datamodel.printSqlDelete(row_id)
359
+ } else if (this.params.failOnAnyInvalidRows == false) {
360
+ result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
361
+ }
362
+ }
363
+ } else if (id) {
364
+ sql = this.datamodel.printSqlDelete(id)
365
+ }
366
+ if ((sql == "") && result.success) {
367
+ result.setError(405, "No valid rows for DELETE!", "DoDelete") // only set error if there are multiple rows and no valid sql was created
368
+
369
+ } else if (result.success) {
370
+
371
+ // OINOLog.debug("OINODbApi.doDelete sql", {sql:sql})
372
+ const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
373
+ // OINOLog.debug("OINODbApi.doDelete sql_res", {sql_res:sql_res})
374
+ if (sql_res.hasErrors()) {
375
+ result.setError(500, sql_res.getFirstError(), "DoDelete")
376
+ if (this._debugOnError) {
377
+ result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete")
378
+ result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete")
379
+ }
380
+ }
317
381
  }
318
382
  } catch (e:any) {
319
383
  result.setError(500, "Unhandled exception: " + e.message, "DoDelete")
@@ -321,33 +385,32 @@ export class OINODbApi {
321
385
  }
322
386
  }
323
387
 
388
+ /**
389
+ * Enable or disable debug output on errors.
390
+ *
391
+ * @param debugOnError true to enable debug output on errors, false to disable
392
+ */
393
+ setDebugOnError(debugOnError:boolean) {
394
+ this._debugOnError = debugOnError
395
+ }
396
+
324
397
  /**
325
398
  * Method for handlind a HTTP REST request with GET, POST, PUT, DELETE corresponding to
326
399
  * SQL select, insert, update and delete.
327
400
  *
328
401
  * @param method HTTP verb (uppercase)
329
402
  * @param id URL id of the REST request
330
- * @param body HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
403
+ * @param data HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
331
404
  * @param params HTTP URL parameters as key-value-pairs
332
405
  *
333
406
  */
334
- async doRequest(method:string, id: string, body:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams = API_EMPTY_PARAMS):Promise<OINODbApiResult> {
407
+ async doRequest(method:string, id: string, data:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams = API_EMPTY_PARAMS):Promise<OINODbApiResult> {
335
408
  OINOBenchmark.start("OINODbApi", "doRequest")
336
409
  // OINOLog.debug("OINODbApi.doRequest enter", {method:method, id:id, body:body, params:params})
337
410
  let result:OINODbApiResult = new OINODbApiResult(params)
338
411
  let rows:OINODataRow[] = []
339
412
  if ((method == "POST") || (method == "PUT")) {
340
- try {
341
- if (Array.isArray(body)) {
342
- rows = body as OINODataRow[]
343
- } else {
344
- rows = OINODbParser.createRows(this.datamodel, body, params)
345
- }
346
-
347
- } catch (e:any) {
348
- result.setError(400, "Invalid data: " + e.message, "DoRequest")
349
- }
350
- // OINOLog.debug("OINODbApi.doRequest - OINODataRow rows", {rows:rows})
413
+ rows = this._parseData(result, data, params)
351
414
  }
352
415
  if (method == "GET") {
353
416
  await this._doGet(result, id, params)
@@ -361,7 +424,7 @@ export class OINODbApi {
361
424
 
362
425
  } else {
363
426
  try {
364
- await this._doPut(result, id, rows[0])
427
+ await this._doPut(result, id, rows)
365
428
 
366
429
  } catch (e:any) {
367
430
  result.setError(500, "Unhandled exception in HTTP PUT doRequest: " + e.message, "DoRequest")
@@ -389,19 +452,59 @@ export class OINODbApi {
389
452
 
390
453
  } else {
391
454
  try {
392
- await this._doDelete(result, id)
455
+ await this._doDelete(result, id, null)
393
456
 
394
457
  } catch (e:any) {
395
458
  result.setError(500, "Unhandled exception in HTTP DELETE doRequest: " + e.message, "DoRequest")
396
459
  }
397
460
  }
398
461
  } else {
399
- result.setError(405, "Unsupported HTTP method '" + method + "'", "DoRequest")
462
+ result.setError(405, "Unsupported HTTP method '" + method + "' for REST request", "DoRequest")
400
463
  }
401
464
  OINOBenchmark.end("OINODbApi", "doRequest", method)
402
465
  return Promise.resolve(result)
403
466
  }
404
467
 
468
+ /**
469
+ * Method for handlind a HTTP REST request with GET, POST, PUT, DELETE corresponding to
470
+ * SQL select, insert, update and delete.
471
+ *
472
+ * @param method HTTP verb (uppercase)
473
+ * @param data HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
474
+ * @param params HTTP URL parameters as key-value-pairs
475
+ *
476
+ */
477
+ async doBatchUpdate(method:string, data:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams = API_EMPTY_PARAMS):Promise<OINODbApiResult> {
478
+ OINOBenchmark.start("OINODbApi", "doBatchUpdate")
479
+ // OINOLog.debug("OINODbApi.doRequest enter", {method:method, id:id, body:body, params:params})
480
+ let result:OINODbApiResult = new OINODbApiResult(params)
481
+ let rows:OINODataRow[] = []
482
+ if ((method == "PUT")) {
483
+ rows = this._parseData(result, data, params)
484
+ }
485
+ if (method == "PUT") {
486
+
487
+ try {
488
+ await this._doPut(result, null, rows)
489
+
490
+ } catch (e:any) {
491
+ result.setError(500, "Unhandled exception in HTTP PUT doRequest: " + e.message, "DoBatchUpdate")
492
+ }
493
+
494
+ } else if (method == "DELETE") {
495
+ try {
496
+ await this._doDelete(result, null, rows)
497
+
498
+ } catch (e:any) {
499
+ result.setError(500, "Unhandled exception in HTTP DELETE doRequest: " + e.message, "DoBatchUpdate")
500
+ }
501
+ } else {
502
+ result.setError(405, "Unsupported HTTP method '" + method + "' for batch update", "DoBatchUpdate")
503
+ }
504
+ OINOBenchmark.end("OINODbApi", "doBatchUpdate", method)
505
+ return Promise.resolve(result)
506
+ }
507
+
405
508
  /**
406
509
  * Method to check if a field is included in the API params.
407
510
  *
@@ -103,6 +103,7 @@ export class OINODbDataModel {
103
103
  if ((f instanceof OINONumberDataField) && (this.api.hashid)) {
104
104
  value = this.api.hashid.decode(value)
105
105
  }
106
+ // OINOLog.debug("OINODbDataModel._printSqlPrimaryKeyCondition", {field:f.name, value:value, id_value:id_value})
106
107
  result += f.printSqlColumnName() + "=" + f.printCellAsSqlValue(value);
107
108
  i = i + 1
108
109
  }
@@ -279,6 +280,7 @@ export class OINODbDataModel {
279
280
  */
280
281
  printSqlUpdate(id: string, row: OINODataRow): string {
281
282
  let result: string = "UPDATE " + this.api.db.printSqlTablename(this.api.params.tableName) + " SET " + this._printSqlUpdateValues(row) + " WHERE " + this._printSqlPrimaryKeyCondition(id) + ";";
283
+ // OINOLog.debug("OINODbDataModel.printSqlUpdate", {result:result, id:id, row:row})
282
284
  return result;
283
285
  }
284
286
 
package/src/index.ts CHANGED
@@ -30,6 +30,8 @@ export type OINODbApiParams = {
30
30
  failOnUpdateOnAutoinc?: boolean
31
31
  /** Reject POST-requests without primary key value (can work if DB-side ) */
32
32
  failOnInsertWithoutKey?: boolean
33
+ /** Reject POST-requests without primary key value (can work if DB-side ) */
34
+ failOnAnyInvalidRows?: boolean
33
35
  /** Treat date type fields as just strings and use the native formatting instead of the ISO 8601 format */
34
36
  useDatesAsString?: Boolean
35
37
  /** Include given fields from the API and exclude rest (if defined) */