@oino-ts/db 0.2.0 → 0.3.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.
- package/dist/cjs/OINODb.js +5 -1
- package/dist/cjs/OINODbApi.js +13 -0
- package/dist/cjs/OINODbConfig.js +2 -0
- package/dist/cjs/OINODbDataField.js +1 -1
- package/dist/cjs/OINODbDataModel.js +12 -8
- package/dist/cjs/OINODbFactory.js +5 -0
- package/dist/cjs/OINODbSqlParams.js +110 -9
- package/dist/esm/OINODb.js +5 -1
- package/dist/esm/OINODbApi.js +13 -0
- package/dist/esm/OINODbConfig.js +2 -0
- package/dist/esm/OINODbDataField.js +1 -1
- package/dist/esm/OINODbDataModel.js +12 -8
- package/dist/esm/OINODbFactory.js +5 -0
- package/dist/esm/OINODbSqlParams.js +108 -8
- package/dist/types/OINODb.d.ts +2 -1
- package/dist/types/OINODbApi.d.ts +7 -0
- package/dist/types/OINODbConfig.d.ts +2 -0
- package/dist/types/OINODbSqlParams.d.ts +56 -2
- package/dist/types/index.d.ts +8 -2
- package/package.json +3 -3
- package/src/OINODb.ts +5 -1
- package/src/OINODbApi.test.ts +14 -12
- package/src/OINODbApi.ts +19 -1
- package/src/OINODbConfig.ts +3 -0
- package/src/OINODbDataField.ts +1 -1
- package/src/OINODbDataModel.ts +12 -8
- package/src/OINODbFactory.ts +5 -0
- package/src/OINODbSqlParams.ts +105 -6
- package/src/index.ts +8 -2
package/dist/cjs/OINODb.js
CHANGED
|
@@ -32,9 +32,10 @@ class OINODb {
|
|
|
32
32
|
* @param whereCondition - The WHERE clause to filter the results.
|
|
33
33
|
* @param orderCondition - The ORDER BY clause to sort the results.
|
|
34
34
|
* @param limitCondition - The LIMIT clause to limit the number of results.
|
|
35
|
+
* @param groupByCondition - The GROUP BY clause to group the results.
|
|
35
36
|
*
|
|
36
37
|
*/
|
|
37
|
-
printSqlSelect(tableName, columnNames, whereCondition, orderCondition, limitCondition) {
|
|
38
|
+
printSqlSelect(tableName, columnNames, whereCondition, orderCondition, limitCondition, groupByCondition) {
|
|
38
39
|
let result = "SELECT " + columnNames + " FROM " + tableName;
|
|
39
40
|
// OINOLog.debug("OINODb.printSqlSelect", {tableName:tableName, columnNames:columnNames, whereCondition:whereCondition, orderCondition:orderCondition, limitCondition:limitCondition })
|
|
40
41
|
if (whereCondition != "") {
|
|
@@ -46,6 +47,9 @@ class OINODb {
|
|
|
46
47
|
if (limitCondition != "") {
|
|
47
48
|
result += " LIMIT " + limitCondition;
|
|
48
49
|
}
|
|
50
|
+
if (groupByCondition != "") {
|
|
51
|
+
result += " GROUP BY " + groupByCondition;
|
|
52
|
+
}
|
|
49
53
|
result += ";";
|
|
50
54
|
// OINOLog.debug("OINODb.printSqlSelect", {result:result})
|
|
51
55
|
return result;
|
package/dist/cjs/OINODbApi.js
CHANGED
|
@@ -358,5 +358,18 @@ class OINODbApi {
|
|
|
358
358
|
index_js_1.OINOBenchmark.end("OINODbApi", "doRequest", method);
|
|
359
359
|
return Promise.resolve(result);
|
|
360
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Method to check if a field is included in the API params.
|
|
363
|
+
*
|
|
364
|
+
* @param fieldName name of the field
|
|
365
|
+
*
|
|
366
|
+
*/
|
|
367
|
+
isFieldIncluded(fieldName) {
|
|
368
|
+
// OINOLog.debug("OINODbApi.isFieldIncluded", {fieldName:fieldName, included:this.params.includeFields})
|
|
369
|
+
const params = this.params;
|
|
370
|
+
return (((params.excludeFieldPrefix == undefined) || (params.excludeFieldPrefix == "") || fieldName.startsWith(params.excludeFieldPrefix)) &&
|
|
371
|
+
((params.excludeFields == undefined) || (params.excludeFields.length == 0) || (params.excludeFields.indexOf(fieldName) < 0)) &&
|
|
372
|
+
((params.includeFields == undefined) || (params.includeFields.length == 0) || (params.includeFields.indexOf(fieldName) >= 0)));
|
|
373
|
+
}
|
|
361
374
|
}
|
|
362
375
|
exports.OINODbApi = OINODbApi;
|
package/dist/cjs/OINODbConfig.js
CHANGED
|
@@ -14,6 +14,8 @@ class OINODbConfig {
|
|
|
14
14
|
static OINODB_SQL_ORDER_PARAM = "oinosqlorder";
|
|
15
15
|
/** Name of the OINODbSqlLimit-parameter in request */
|
|
16
16
|
static OINODB_SQL_LIMIT_PARAM = "oinosqllimit";
|
|
17
|
+
/** Name of the OINODbSqlAggregate-parameter in request */
|
|
18
|
+
static OINODB_SQL_AGGREGATE_PARAM = "oinosqlaggregate";
|
|
17
19
|
/**
|
|
18
20
|
* Set the name of the OINO ID field
|
|
19
21
|
* @param idField name of the OINO ID field
|
|
@@ -248,7 +248,7 @@ class OINONumberDataField extends OINODbDataField {
|
|
|
248
248
|
else {
|
|
249
249
|
const result = parseFloat(value);
|
|
250
250
|
if (isNaN(result)) {
|
|
251
|
-
index_js_1.OINOLog.error("
|
|
251
|
+
index_js_1.OINOLog.error("OINONumberDataField.toSql: Invalid value!", { value: value });
|
|
252
252
|
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": OINONumberDataField.deserializeCell - Invalid value '" + value + "'"); // incorrectly formatted data could be a security risk, abort processing
|
|
253
253
|
}
|
|
254
254
|
return result;
|
|
@@ -39,13 +39,10 @@ class OINODbDataModel {
|
|
|
39
39
|
}
|
|
40
40
|
_printSqlColumnNames() {
|
|
41
41
|
let result = "";
|
|
42
|
-
for (let
|
|
43
|
-
|
|
44
|
-
result += ",";
|
|
45
|
-
}
|
|
46
|
-
result += f.printSqlColumnName();
|
|
42
|
+
for (let i = 0; i < this.fields.length; i++) {
|
|
43
|
+
result += this.fields[i].printSqlColumnName() + ",";
|
|
47
44
|
}
|
|
48
|
-
return result;
|
|
45
|
+
return result.substring(0, result.length - 1);
|
|
49
46
|
}
|
|
50
47
|
_printSqlInsertColumnsAndValues(row) {
|
|
51
48
|
let columns = "";
|
|
@@ -220,10 +217,17 @@ class OINODbDataModel {
|
|
|
220
217
|
*
|
|
221
218
|
*/
|
|
222
219
|
printSqlSelect(id, params) {
|
|
223
|
-
|
|
220
|
+
let column_names = "";
|
|
221
|
+
if (params.aggregate) {
|
|
222
|
+
column_names = params.aggregate.printSqlColumnNames(this);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
column_names = this._printSqlColumnNames();
|
|
226
|
+
}
|
|
224
227
|
const order_sql = params.order?.toSql(this) || "";
|
|
225
228
|
const limit_sql = params.limit?.toSql(this) || "";
|
|
226
229
|
const filter_sql = params.filter?.toSql(this) || "";
|
|
230
|
+
const aggregate_sql = params.aggregate?.toSql(this) || "";
|
|
227
231
|
let where_sql = "";
|
|
228
232
|
// OINOLog.debug("OINODbDataModel.printSqlSelect", {id:id, select_sql:result, filter_sql:filter_sql, order_sql:order_sql})
|
|
229
233
|
if ((id != null) && (id != "") && (filter_sql != "")) {
|
|
@@ -235,7 +239,7 @@ class OINODbDataModel {
|
|
|
235
239
|
else if (filter_sql != "") {
|
|
236
240
|
where_sql = filter_sql;
|
|
237
241
|
}
|
|
238
|
-
const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql);
|
|
242
|
+
const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql, aggregate_sql);
|
|
239
243
|
// OINOLog.debug("OINODbDataModel.printSqlSelect", {result:result})
|
|
240
244
|
return result;
|
|
241
245
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.OINODbFactory = void 0;
|
|
9
9
|
const index_js_1 = require("./index.js");
|
|
10
|
+
const OINODbSqlParams_js_1 = require("./OINODbSqlParams.js");
|
|
10
11
|
/**
|
|
11
12
|
* Static factory class for easily creating things based on data
|
|
12
13
|
*
|
|
@@ -72,6 +73,10 @@ class OINODbFactory {
|
|
|
72
73
|
if (limit) {
|
|
73
74
|
sql_params.limit = index_js_1.OINODbSqlLimit.parse(limit);
|
|
74
75
|
}
|
|
76
|
+
const aggregate = url.searchParams.get(index_js_1.OINODbConfig.OINODB_SQL_AGGREGATE_PARAM);
|
|
77
|
+
if (aggregate) {
|
|
78
|
+
sql_params.aggregate = OINODbSqlParams_js_1.OINODbSqlAggregate.parse(aggregate);
|
|
79
|
+
}
|
|
75
80
|
let result = { sqlParams: sql_params };
|
|
76
81
|
const content_type = request.headers.get("content-type");
|
|
77
82
|
if (content_type == index_js_1.OINOContentType.csv) {
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.OINODbSqlLimit = exports.OINODbSqlOrder = exports.OINODbSqlFilter = exports.OINODbSqlComparison = exports.OINODbSqlBooleanOperation = void 0;
|
|
8
|
+
exports.OINODbSqlAggregate = exports.OINODbSqlAggregateFunctions = exports.OINODbSqlLimit = exports.OINODbSqlOrder = exports.OINODbSqlFilter = exports.OINODbSqlComparison = exports.OINODbSqlBooleanOperation = void 0;
|
|
9
9
|
const index_js_1 = require("./index.js");
|
|
10
|
+
const OINO_FIELD_NAME_CHARS = "\\w\\s\\-\\_\\#\\¤";
|
|
10
11
|
/**
|
|
11
12
|
* Supported logical conjunctions in filter predicates.
|
|
12
13
|
* @enum
|
|
@@ -65,13 +66,13 @@ class OINODbSqlFilter {
|
|
|
65
66
|
this._rightSide = rightSide;
|
|
66
67
|
}
|
|
67
68
|
/**
|
|
68
|
-
* Constructor for `
|
|
69
|
+
* Constructor for `OINODbSqlFilter` as parser of http parameter.
|
|
69
70
|
*
|
|
70
71
|
* @param filterString string representation of filter from HTTP-request
|
|
71
72
|
*
|
|
72
73
|
*/
|
|
73
74
|
static parse(filterString) {
|
|
74
|
-
// OINOLog_debug("
|
|
75
|
+
// OINOLog_debug("OINODbSqlFilter.constructor", {filterString:filterString})
|
|
75
76
|
if (!filterString) {
|
|
76
77
|
return new OINODbSqlFilter("", null, "");
|
|
77
78
|
}
|
|
@@ -87,7 +88,7 @@ class OINODbSqlFilter {
|
|
|
87
88
|
}
|
|
88
89
|
else {
|
|
89
90
|
let boolean_parts = index_js_1.OINOStr.splitByBrackets(filterString, true, false, '(', ')');
|
|
90
|
-
// OINOLog_debug("
|
|
91
|
+
// OINOLog_debug("OINODbSqlFilter.constructor", {boolean_parts:boolean_parts})
|
|
91
92
|
if (boolean_parts.length == 3 && (boolean_parts[1].match(OINODbSqlFilter._booleanOperationRegex))) {
|
|
92
93
|
return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1), OINODbSqlFilter.parse(boolean_parts[2]));
|
|
93
94
|
}
|
|
@@ -99,7 +100,7 @@ class OINODbSqlFilter {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
/**
|
|
102
|
-
* Construct a new `
|
|
103
|
+
* Construct a new `OINODbSqlFilter` as combination of (boolean and/or) of two filters.
|
|
103
104
|
*
|
|
104
105
|
* @param leftSide left side to combine
|
|
105
106
|
* @param operation boolean operation to use in combination
|
|
@@ -148,7 +149,7 @@ class OINODbSqlFilter {
|
|
|
148
149
|
*
|
|
149
150
|
*/
|
|
150
151
|
toSql(dataModel) {
|
|
151
|
-
// OINOLog.debug("
|
|
152
|
+
// OINOLog.debug("OINODbSqlFilter.toSql", {_leftSide:this._leftSide, _operator:this._operator, _rightSide:this._rightSide})
|
|
152
153
|
if (this.isEmpty()) {
|
|
153
154
|
return "";
|
|
154
155
|
}
|
|
@@ -177,7 +178,7 @@ class OINODbSqlFilter {
|
|
|
177
178
|
}
|
|
178
179
|
result += field.printCellAsSqlValue(value);
|
|
179
180
|
}
|
|
180
|
-
// OINOLog.debug("
|
|
181
|
+
// OINOLog.debug("OINODbSqlFilter.toSql", {result:result})
|
|
181
182
|
return "(" + result + ")";
|
|
182
183
|
}
|
|
183
184
|
}
|
|
@@ -187,7 +188,7 @@ exports.OINODbSqlFilter = OINODbSqlFilter;
|
|
|
187
188
|
*
|
|
188
189
|
*/
|
|
189
190
|
class OINODbSqlOrder {
|
|
190
|
-
static _orderColumnRegex = /^\s*(\w+)\s?(ASC|DESC)?\s*?$/i;
|
|
191
|
+
static _orderColumnRegex = /^\s*(\w+)\s?(ASC|DESC|\+|\-)?\s*?$/i;
|
|
191
192
|
_columns;
|
|
192
193
|
_descending;
|
|
193
194
|
/**
|
|
@@ -226,7 +227,8 @@ class OINODbSqlOrder {
|
|
|
226
227
|
let match = OINODbSqlOrder._orderColumnRegex.exec(column_strings[i]);
|
|
227
228
|
if (match != null) {
|
|
228
229
|
columns.push(match[1]);
|
|
229
|
-
|
|
230
|
+
const dir = (match[2] || "ASC").toUpperCase();
|
|
231
|
+
directions.push((dir == "DESC") || (dir == "-"));
|
|
230
232
|
}
|
|
231
233
|
}
|
|
232
234
|
return new OINODbSqlOrder(columns, directions);
|
|
@@ -334,3 +336,102 @@ class OINODbSqlLimit {
|
|
|
334
336
|
}
|
|
335
337
|
}
|
|
336
338
|
exports.OINODbSqlLimit = OINODbSqlLimit;
|
|
339
|
+
/**
|
|
340
|
+
* Supported aggregation functions in OINODbSqlAggregate.
|
|
341
|
+
* @enum
|
|
342
|
+
*/
|
|
343
|
+
var OINODbSqlAggregateFunctions;
|
|
344
|
+
(function (OINODbSqlAggregateFunctions) {
|
|
345
|
+
OINODbSqlAggregateFunctions["count"] = "count";
|
|
346
|
+
OINODbSqlAggregateFunctions["sum"] = "sum";
|
|
347
|
+
OINODbSqlAggregateFunctions["avg"] = "avg";
|
|
348
|
+
OINODbSqlAggregateFunctions["min"] = "min";
|
|
349
|
+
OINODbSqlAggregateFunctions["max"] = "max";
|
|
350
|
+
})(OINODbSqlAggregateFunctions || (exports.OINODbSqlAggregateFunctions = OINODbSqlAggregateFunctions = {}));
|
|
351
|
+
/**
|
|
352
|
+
* Class for limiting the number of results.
|
|
353
|
+
*
|
|
354
|
+
*/
|
|
355
|
+
class OINODbSqlAggregate {
|
|
356
|
+
static _aggregateRegex = new RegExp("^(count|sum|avg|min|max)\\(([" + OINO_FIELD_NAME_CHARS + "]+)\\)$", "mi");
|
|
357
|
+
_functions;
|
|
358
|
+
_fields;
|
|
359
|
+
/**
|
|
360
|
+
* Constructor for `OINODbSqlAggregate`.
|
|
361
|
+
*
|
|
362
|
+
* @param function aggregate function to use
|
|
363
|
+
* @param fields fields to aggregate
|
|
364
|
+
*
|
|
365
|
+
*/
|
|
366
|
+
constructor(func, fields) {
|
|
367
|
+
this._functions = func;
|
|
368
|
+
this._fields = fields;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Constructor for `OINODbSqlAggregate` as parser of http parameter.
|
|
372
|
+
*
|
|
373
|
+
* @param aggregatorString string representation of limit from HTTP-request
|
|
374
|
+
*
|
|
375
|
+
*/
|
|
376
|
+
static parse(aggregatorString) {
|
|
377
|
+
let funtions = [];
|
|
378
|
+
let fields = [];
|
|
379
|
+
const aggregator_parts = aggregatorString.split(',');
|
|
380
|
+
for (let i = 0; i < aggregator_parts.length; i++) {
|
|
381
|
+
let match = OINODbSqlAggregate._aggregateRegex.exec(aggregator_parts[i]);
|
|
382
|
+
// OINOLog.debug("OINODbSqlAggregate.parse - next aggregator", {aggregator: aggregator_parts[i], match:match})
|
|
383
|
+
if ((match != null) && (match.length == 3)) {
|
|
384
|
+
funtions.push(match[1]);
|
|
385
|
+
fields.push(match[2]);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return new OINODbSqlAggregate(funtions, fields);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Does filter contain any valid conditions.
|
|
392
|
+
*
|
|
393
|
+
*/
|
|
394
|
+
isEmpty() {
|
|
395
|
+
return (this._functions.length <= 0);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
|
|
399
|
+
*
|
|
400
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
401
|
+
*
|
|
402
|
+
*/
|
|
403
|
+
toSql(dataModel) {
|
|
404
|
+
if (this.isEmpty()) {
|
|
405
|
+
return "";
|
|
406
|
+
}
|
|
407
|
+
let result = "";
|
|
408
|
+
for (let i = 0; i < dataModel.fields.length; i++) {
|
|
409
|
+
if (this._fields.includes(dataModel.fields[i].name) == false) {
|
|
410
|
+
result += dataModel.fields[i].printSqlColumnName() + ",";
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
index_js_1.OINOLog.debug("OINODbSqlAggregate.toSql", { result: result });
|
|
414
|
+
return result.substring(0, result.length - 1);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
|
|
418
|
+
*
|
|
419
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
420
|
+
*
|
|
421
|
+
*/
|
|
422
|
+
printSqlColumnNames(dataModel) {
|
|
423
|
+
let result = "";
|
|
424
|
+
for (let i = 0; i < dataModel.fields.length; i++) {
|
|
425
|
+
const aggregate_index = this._fields.indexOf(dataModel.fields[i].name);
|
|
426
|
+
if (aggregate_index >= 0) {
|
|
427
|
+
result += this._functions[aggregate_index] + "(" + dataModel.fields[i].printSqlColumnName() + "),";
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
result += dataModel.fields[i].printSqlColumnName() + ",";
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
index_js_1.OINOLog.debug("OINODbSqlAggregate.printSqlColumnNames", { result: result });
|
|
434
|
+
return result.substring(0, result.length - 1);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
exports.OINODbSqlAggregate = OINODbSqlAggregate;
|
package/dist/esm/OINODb.js
CHANGED
|
@@ -29,9 +29,10 @@ export class OINODb {
|
|
|
29
29
|
* @param whereCondition - The WHERE clause to filter the results.
|
|
30
30
|
* @param orderCondition - The ORDER BY clause to sort the results.
|
|
31
31
|
* @param limitCondition - The LIMIT clause to limit the number of results.
|
|
32
|
+
* @param groupByCondition - The GROUP BY clause to group the results.
|
|
32
33
|
*
|
|
33
34
|
*/
|
|
34
|
-
printSqlSelect(tableName, columnNames, whereCondition, orderCondition, limitCondition) {
|
|
35
|
+
printSqlSelect(tableName, columnNames, whereCondition, orderCondition, limitCondition, groupByCondition) {
|
|
35
36
|
let result = "SELECT " + columnNames + " FROM " + tableName;
|
|
36
37
|
// OINOLog.debug("OINODb.printSqlSelect", {tableName:tableName, columnNames:columnNames, whereCondition:whereCondition, orderCondition:orderCondition, limitCondition:limitCondition })
|
|
37
38
|
if (whereCondition != "") {
|
|
@@ -43,6 +44,9 @@ export class OINODb {
|
|
|
43
44
|
if (limitCondition != "") {
|
|
44
45
|
result += " LIMIT " + limitCondition;
|
|
45
46
|
}
|
|
47
|
+
if (groupByCondition != "") {
|
|
48
|
+
result += " GROUP BY " + groupByCondition;
|
|
49
|
+
}
|
|
46
50
|
result += ";";
|
|
47
51
|
// OINOLog.debug("OINODb.printSqlSelect", {result:result})
|
|
48
52
|
return result;
|
package/dist/esm/OINODbApi.js
CHANGED
|
@@ -353,4 +353,17 @@ export class OINODbApi {
|
|
|
353
353
|
OINOBenchmark.end("OINODbApi", "doRequest", method);
|
|
354
354
|
return Promise.resolve(result);
|
|
355
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Method to check if a field is included in the API params.
|
|
358
|
+
*
|
|
359
|
+
* @param fieldName name of the field
|
|
360
|
+
*
|
|
361
|
+
*/
|
|
362
|
+
isFieldIncluded(fieldName) {
|
|
363
|
+
// OINOLog.debug("OINODbApi.isFieldIncluded", {fieldName:fieldName, included:this.params.includeFields})
|
|
364
|
+
const params = this.params;
|
|
365
|
+
return (((params.excludeFieldPrefix == undefined) || (params.excludeFieldPrefix == "") || fieldName.startsWith(params.excludeFieldPrefix)) &&
|
|
366
|
+
((params.excludeFields == undefined) || (params.excludeFields.length == 0) || (params.excludeFields.indexOf(fieldName) < 0)) &&
|
|
367
|
+
((params.includeFields == undefined) || (params.includeFields.length == 0) || (params.includeFields.indexOf(fieldName) >= 0)));
|
|
368
|
+
}
|
|
356
369
|
}
|
package/dist/esm/OINODbConfig.js
CHANGED
|
@@ -11,6 +11,8 @@ export class OINODbConfig {
|
|
|
11
11
|
static OINODB_SQL_ORDER_PARAM = "oinosqlorder";
|
|
12
12
|
/** Name of the OINODbSqlLimit-parameter in request */
|
|
13
13
|
static OINODB_SQL_LIMIT_PARAM = "oinosqllimit";
|
|
14
|
+
/** Name of the OINODbSqlAggregate-parameter in request */
|
|
15
|
+
static OINODB_SQL_AGGREGATE_PARAM = "oinosqlaggregate";
|
|
14
16
|
/**
|
|
15
17
|
* Set the name of the OINO ID field
|
|
16
18
|
* @param idField name of the OINO ID field
|
|
@@ -242,7 +242,7 @@ export class OINONumberDataField extends OINODbDataField {
|
|
|
242
242
|
else {
|
|
243
243
|
const result = parseFloat(value);
|
|
244
244
|
if (isNaN(result)) {
|
|
245
|
-
OINOLog.error("
|
|
245
|
+
OINOLog.error("OINONumberDataField.toSql: Invalid value!", { value: value });
|
|
246
246
|
throw new Error(OINO_ERROR_PREFIX + ": OINONumberDataField.deserializeCell - Invalid value '" + value + "'"); // incorrectly formatted data could be a security risk, abort processing
|
|
247
247
|
}
|
|
248
248
|
return result;
|
|
@@ -36,13 +36,10 @@ export class OINODbDataModel {
|
|
|
36
36
|
}
|
|
37
37
|
_printSqlColumnNames() {
|
|
38
38
|
let result = "";
|
|
39
|
-
for (let
|
|
40
|
-
|
|
41
|
-
result += ",";
|
|
42
|
-
}
|
|
43
|
-
result += f.printSqlColumnName();
|
|
39
|
+
for (let i = 0; i < this.fields.length; i++) {
|
|
40
|
+
result += this.fields[i].printSqlColumnName() + ",";
|
|
44
41
|
}
|
|
45
|
-
return result;
|
|
42
|
+
return result.substring(0, result.length - 1);
|
|
46
43
|
}
|
|
47
44
|
_printSqlInsertColumnsAndValues(row) {
|
|
48
45
|
let columns = "";
|
|
@@ -217,10 +214,17 @@ export class OINODbDataModel {
|
|
|
217
214
|
*
|
|
218
215
|
*/
|
|
219
216
|
printSqlSelect(id, params) {
|
|
220
|
-
|
|
217
|
+
let column_names = "";
|
|
218
|
+
if (params.aggregate) {
|
|
219
|
+
column_names = params.aggregate.printSqlColumnNames(this);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
column_names = this._printSqlColumnNames();
|
|
223
|
+
}
|
|
221
224
|
const order_sql = params.order?.toSql(this) || "";
|
|
222
225
|
const limit_sql = params.limit?.toSql(this) || "";
|
|
223
226
|
const filter_sql = params.filter?.toSql(this) || "";
|
|
227
|
+
const aggregate_sql = params.aggregate?.toSql(this) || "";
|
|
224
228
|
let where_sql = "";
|
|
225
229
|
// OINOLog.debug("OINODbDataModel.printSqlSelect", {id:id, select_sql:result, filter_sql:filter_sql, order_sql:order_sql})
|
|
226
230
|
if ((id != null) && (id != "") && (filter_sql != "")) {
|
|
@@ -232,7 +236,7 @@ export class OINODbDataModel {
|
|
|
232
236
|
else if (filter_sql != "") {
|
|
233
237
|
where_sql = filter_sql;
|
|
234
238
|
}
|
|
235
|
-
const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql);
|
|
239
|
+
const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql, aggregate_sql);
|
|
236
240
|
// OINOLog.debug("OINODbDataModel.printSqlSelect", {result:result})
|
|
237
241
|
return result;
|
|
238
242
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
6
|
import { OINODbApi, OINOContentType, OINODbSqlFilter, OINODbConfig, OINODbSqlOrder, OINODbSqlLimit } from "./index.js";
|
|
7
|
+
import { OINODbSqlAggregate } from "./OINODbSqlParams.js";
|
|
7
8
|
/**
|
|
8
9
|
* Static factory class for easily creating things based on data
|
|
9
10
|
*
|
|
@@ -69,6 +70,10 @@ export class OINODbFactory {
|
|
|
69
70
|
if (limit) {
|
|
70
71
|
sql_params.limit = OINODbSqlLimit.parse(limit);
|
|
71
72
|
}
|
|
73
|
+
const aggregate = url.searchParams.get(OINODbConfig.OINODB_SQL_AGGREGATE_PARAM);
|
|
74
|
+
if (aggregate) {
|
|
75
|
+
sql_params.aggregate = OINODbSqlAggregate.parse(aggregate);
|
|
76
|
+
}
|
|
72
77
|
let result = { sqlParams: sql_params };
|
|
73
78
|
const content_type = request.headers.get("content-type");
|
|
74
79
|
if (content_type == OINOContentType.csv) {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
6
|
import { OINOStr, OINO_ERROR_PREFIX, OINOLog } from "./index.js";
|
|
7
|
+
const OINO_FIELD_NAME_CHARS = "\\w\\s\\-\\_\\#\\¤";
|
|
7
8
|
/**
|
|
8
9
|
* Supported logical conjunctions in filter predicates.
|
|
9
10
|
* @enum
|
|
@@ -62,13 +63,13 @@ export class OINODbSqlFilter {
|
|
|
62
63
|
this._rightSide = rightSide;
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
65
|
-
* Constructor for `
|
|
66
|
+
* Constructor for `OINODbSqlFilter` as parser of http parameter.
|
|
66
67
|
*
|
|
67
68
|
* @param filterString string representation of filter from HTTP-request
|
|
68
69
|
*
|
|
69
70
|
*/
|
|
70
71
|
static parse(filterString) {
|
|
71
|
-
// OINOLog_debug("
|
|
72
|
+
// OINOLog_debug("OINODbSqlFilter.constructor", {filterString:filterString})
|
|
72
73
|
if (!filterString) {
|
|
73
74
|
return new OINODbSqlFilter("", null, "");
|
|
74
75
|
}
|
|
@@ -84,7 +85,7 @@ export class OINODbSqlFilter {
|
|
|
84
85
|
}
|
|
85
86
|
else {
|
|
86
87
|
let boolean_parts = OINOStr.splitByBrackets(filterString, true, false, '(', ')');
|
|
87
|
-
// OINOLog_debug("
|
|
88
|
+
// OINOLog_debug("OINODbSqlFilter.constructor", {boolean_parts:boolean_parts})
|
|
88
89
|
if (boolean_parts.length == 3 && (boolean_parts[1].match(OINODbSqlFilter._booleanOperationRegex))) {
|
|
89
90
|
return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1), OINODbSqlFilter.parse(boolean_parts[2]));
|
|
90
91
|
}
|
|
@@ -96,7 +97,7 @@ export class OINODbSqlFilter {
|
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
/**
|
|
99
|
-
* Construct a new `
|
|
100
|
+
* Construct a new `OINODbSqlFilter` as combination of (boolean and/or) of two filters.
|
|
100
101
|
*
|
|
101
102
|
* @param leftSide left side to combine
|
|
102
103
|
* @param operation boolean operation to use in combination
|
|
@@ -145,7 +146,7 @@ export class OINODbSqlFilter {
|
|
|
145
146
|
*
|
|
146
147
|
*/
|
|
147
148
|
toSql(dataModel) {
|
|
148
|
-
// OINOLog.debug("
|
|
149
|
+
// OINOLog.debug("OINODbSqlFilter.toSql", {_leftSide:this._leftSide, _operator:this._operator, _rightSide:this._rightSide})
|
|
149
150
|
if (this.isEmpty()) {
|
|
150
151
|
return "";
|
|
151
152
|
}
|
|
@@ -174,7 +175,7 @@ export class OINODbSqlFilter {
|
|
|
174
175
|
}
|
|
175
176
|
result += field.printCellAsSqlValue(value);
|
|
176
177
|
}
|
|
177
|
-
// OINOLog.debug("
|
|
178
|
+
// OINOLog.debug("OINODbSqlFilter.toSql", {result:result})
|
|
178
179
|
return "(" + result + ")";
|
|
179
180
|
}
|
|
180
181
|
}
|
|
@@ -183,7 +184,7 @@ export class OINODbSqlFilter {
|
|
|
183
184
|
*
|
|
184
185
|
*/
|
|
185
186
|
export class OINODbSqlOrder {
|
|
186
|
-
static _orderColumnRegex = /^\s*(\w+)\s?(ASC|DESC)?\s*?$/i;
|
|
187
|
+
static _orderColumnRegex = /^\s*(\w+)\s?(ASC|DESC|\+|\-)?\s*?$/i;
|
|
187
188
|
_columns;
|
|
188
189
|
_descending;
|
|
189
190
|
/**
|
|
@@ -222,7 +223,8 @@ export class OINODbSqlOrder {
|
|
|
222
223
|
let match = OINODbSqlOrder._orderColumnRegex.exec(column_strings[i]);
|
|
223
224
|
if (match != null) {
|
|
224
225
|
columns.push(match[1]);
|
|
225
|
-
|
|
226
|
+
const dir = (match[2] || "ASC").toUpperCase();
|
|
227
|
+
directions.push((dir == "DESC") || (dir == "-"));
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
return new OINODbSqlOrder(columns, directions);
|
|
@@ -328,3 +330,101 @@ export class OINODbSqlLimit {
|
|
|
328
330
|
return result;
|
|
329
331
|
}
|
|
330
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Supported aggregation functions in OINODbSqlAggregate.
|
|
335
|
+
* @enum
|
|
336
|
+
*/
|
|
337
|
+
export var OINODbSqlAggregateFunctions;
|
|
338
|
+
(function (OINODbSqlAggregateFunctions) {
|
|
339
|
+
OINODbSqlAggregateFunctions["count"] = "count";
|
|
340
|
+
OINODbSqlAggregateFunctions["sum"] = "sum";
|
|
341
|
+
OINODbSqlAggregateFunctions["avg"] = "avg";
|
|
342
|
+
OINODbSqlAggregateFunctions["min"] = "min";
|
|
343
|
+
OINODbSqlAggregateFunctions["max"] = "max";
|
|
344
|
+
})(OINODbSqlAggregateFunctions || (OINODbSqlAggregateFunctions = {}));
|
|
345
|
+
/**
|
|
346
|
+
* Class for limiting the number of results.
|
|
347
|
+
*
|
|
348
|
+
*/
|
|
349
|
+
export class OINODbSqlAggregate {
|
|
350
|
+
static _aggregateRegex = new RegExp("^(count|sum|avg|min|max)\\(([" + OINO_FIELD_NAME_CHARS + "]+)\\)$", "mi");
|
|
351
|
+
_functions;
|
|
352
|
+
_fields;
|
|
353
|
+
/**
|
|
354
|
+
* Constructor for `OINODbSqlAggregate`.
|
|
355
|
+
*
|
|
356
|
+
* @param function aggregate function to use
|
|
357
|
+
* @param fields fields to aggregate
|
|
358
|
+
*
|
|
359
|
+
*/
|
|
360
|
+
constructor(func, fields) {
|
|
361
|
+
this._functions = func;
|
|
362
|
+
this._fields = fields;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Constructor for `OINODbSqlAggregate` as parser of http parameter.
|
|
366
|
+
*
|
|
367
|
+
* @param aggregatorString string representation of limit from HTTP-request
|
|
368
|
+
*
|
|
369
|
+
*/
|
|
370
|
+
static parse(aggregatorString) {
|
|
371
|
+
let funtions = [];
|
|
372
|
+
let fields = [];
|
|
373
|
+
const aggregator_parts = aggregatorString.split(',');
|
|
374
|
+
for (let i = 0; i < aggregator_parts.length; i++) {
|
|
375
|
+
let match = OINODbSqlAggregate._aggregateRegex.exec(aggregator_parts[i]);
|
|
376
|
+
// OINOLog.debug("OINODbSqlAggregate.parse - next aggregator", {aggregator: aggregator_parts[i], match:match})
|
|
377
|
+
if ((match != null) && (match.length == 3)) {
|
|
378
|
+
funtions.push(match[1]);
|
|
379
|
+
fields.push(match[2]);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return new OINODbSqlAggregate(funtions, fields);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Does filter contain any valid conditions.
|
|
386
|
+
*
|
|
387
|
+
*/
|
|
388
|
+
isEmpty() {
|
|
389
|
+
return (this._functions.length <= 0);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
|
|
393
|
+
*
|
|
394
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
395
|
+
*
|
|
396
|
+
*/
|
|
397
|
+
toSql(dataModel) {
|
|
398
|
+
if (this.isEmpty()) {
|
|
399
|
+
return "";
|
|
400
|
+
}
|
|
401
|
+
let result = "";
|
|
402
|
+
for (let i = 0; i < dataModel.fields.length; i++) {
|
|
403
|
+
if (this._fields.includes(dataModel.fields[i].name) == false) {
|
|
404
|
+
result += dataModel.fields[i].printSqlColumnName() + ",";
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
OINOLog.debug("OINODbSqlAggregate.toSql", { result: result });
|
|
408
|
+
return result.substring(0, result.length - 1);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
|
|
412
|
+
*
|
|
413
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
414
|
+
*
|
|
415
|
+
*/
|
|
416
|
+
printSqlColumnNames(dataModel) {
|
|
417
|
+
let result = "";
|
|
418
|
+
for (let i = 0; i < dataModel.fields.length; i++) {
|
|
419
|
+
const aggregate_index = this._fields.indexOf(dataModel.fields[i].name);
|
|
420
|
+
if (aggregate_index >= 0) {
|
|
421
|
+
result += this._functions[aggregate_index] + "(" + dataModel.fields[i].printSqlColumnName() + "),";
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
result += dataModel.fields[i].printSqlColumnName() + ",";
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
OINOLog.debug("OINODbSqlAggregate.printSqlColumnNames", { result: result });
|
|
428
|
+
return result.substring(0, result.length - 1);
|
|
429
|
+
}
|
|
430
|
+
}
|
package/dist/types/OINODb.d.ts
CHANGED
|
@@ -80,9 +80,10 @@ export declare abstract class OINODb {
|
|
|
80
80
|
* @param whereCondition - The WHERE clause to filter the results.
|
|
81
81
|
* @param orderCondition - The ORDER BY clause to sort the results.
|
|
82
82
|
* @param limitCondition - The LIMIT clause to limit the number of results.
|
|
83
|
+
* @param groupByCondition - The GROUP BY clause to group the results.
|
|
83
84
|
*
|
|
84
85
|
*/
|
|
85
|
-
printSqlSelect(tableName: string, columnNames: string, whereCondition: string, orderCondition: string, limitCondition: string): string;
|
|
86
|
+
printSqlSelect(tableName: string, columnNames: string, whereCondition: string, orderCondition: string, limitCondition: string, groupByCondition: string): string;
|
|
86
87
|
}
|
|
87
88
|
/**
|
|
88
89
|
* Base class for SQL results that can be asynchronously iterated (but
|
|
@@ -79,4 +79,11 @@ export declare class OINODbApi {
|
|
|
79
79
|
*
|
|
80
80
|
*/
|
|
81
81
|
doRequest(method: string, id: string, body: string | OINODataRow[] | Buffer | any, params?: OINODbApiRequestParams): Promise<OINODbApiResult>;
|
|
82
|
+
/**
|
|
83
|
+
* Method to check if a field is included in the API params.
|
|
84
|
+
*
|
|
85
|
+
* @param fieldName name of the field
|
|
86
|
+
*
|
|
87
|
+
*/
|
|
88
|
+
isFieldIncluded(fieldName: string): boolean;
|
|
82
89
|
}
|
|
@@ -11,6 +11,8 @@ export declare class OINODbConfig {
|
|
|
11
11
|
static OINODB_SQL_ORDER_PARAM: string;
|
|
12
12
|
/** Name of the OINODbSqlLimit-parameter in request */
|
|
13
13
|
static OINODB_SQL_LIMIT_PARAM: string;
|
|
14
|
+
/** Name of the OINODbSqlAggregate-parameter in request */
|
|
15
|
+
static OINODB_SQL_AGGREGATE_PARAM: string;
|
|
14
16
|
/**
|
|
15
17
|
* Set the name of the OINO ID field
|
|
16
18
|
* @param idField name of the OINO ID field
|
|
@@ -44,14 +44,14 @@ export declare class OINODbSqlFilter {
|
|
|
44
44
|
*/
|
|
45
45
|
constructor(leftSide: OINODbSqlFilter | string, operation: OINODbSqlComparison | OINODbSqlBooleanOperation | null, rightSide: OINODbSqlFilter | string);
|
|
46
46
|
/**
|
|
47
|
-
* Constructor for `
|
|
47
|
+
* Constructor for `OINODbSqlFilter` as parser of http parameter.
|
|
48
48
|
*
|
|
49
49
|
* @param filterString string representation of filter from HTTP-request
|
|
50
50
|
*
|
|
51
51
|
*/
|
|
52
52
|
static parse(filterString: string): OINODbSqlFilter;
|
|
53
53
|
/**
|
|
54
|
-
* Construct a new `
|
|
54
|
+
* Construct a new `OINODbSqlFilter` as combination of (boolean and/or) of two filters.
|
|
55
55
|
*
|
|
56
56
|
* @param leftSide left side to combine
|
|
57
57
|
* @param operation boolean operation to use in combination
|
|
@@ -145,3 +145,57 @@ export declare class OINODbSqlLimit {
|
|
|
145
145
|
*/
|
|
146
146
|
toSql(dataModel: OINODbDataModel): string;
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Supported aggregation functions in OINODbSqlAggregate.
|
|
150
|
+
* @enum
|
|
151
|
+
*/
|
|
152
|
+
export declare enum OINODbSqlAggregateFunctions {
|
|
153
|
+
count = "count",
|
|
154
|
+
sum = "sum",
|
|
155
|
+
avg = "avg",
|
|
156
|
+
min = "min",
|
|
157
|
+
max = "max"
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Class for limiting the number of results.
|
|
161
|
+
*
|
|
162
|
+
*/
|
|
163
|
+
export declare class OINODbSqlAggregate {
|
|
164
|
+
private static _aggregateRegex;
|
|
165
|
+
private _functions;
|
|
166
|
+
private _fields;
|
|
167
|
+
/**
|
|
168
|
+
* Constructor for `OINODbSqlAggregate`.
|
|
169
|
+
*
|
|
170
|
+
* @param function aggregate function to use
|
|
171
|
+
* @param fields fields to aggregate
|
|
172
|
+
*
|
|
173
|
+
*/
|
|
174
|
+
constructor(func: OINODbSqlAggregateFunctions[], fields: string[]);
|
|
175
|
+
/**
|
|
176
|
+
* Constructor for `OINODbSqlAggregate` as parser of http parameter.
|
|
177
|
+
*
|
|
178
|
+
* @param aggregatorString string representation of limit from HTTP-request
|
|
179
|
+
*
|
|
180
|
+
*/
|
|
181
|
+
static parse(aggregatorString: string): OINODbSqlAggregate;
|
|
182
|
+
/**
|
|
183
|
+
* Does filter contain any valid conditions.
|
|
184
|
+
*
|
|
185
|
+
*/
|
|
186
|
+
isEmpty(): boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
|
|
189
|
+
*
|
|
190
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
191
|
+
*
|
|
192
|
+
*/
|
|
193
|
+
toSql(dataModel: OINODbDataModel): string;
|
|
194
|
+
/**
|
|
195
|
+
* Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
|
|
196
|
+
*
|
|
197
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
198
|
+
*
|
|
199
|
+
*/
|
|
200
|
+
printSqlColumnNames(dataModel: OINODbDataModel): string;
|
|
201
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { OINOContentType };
|
|
|
3
3
|
export { OINO_ERROR_PREFIX, OINO_WARNING_PREFIX, OINO_INFO_PREFIX, OINO_DEBUG_PREFIX, OINOStr, OINOBenchmark, OINOLog, OINOLogLevel, OINOConsoleLog, OINOResult, OINOHttpResult, OINOHtmlTemplate } from "@oino-ts/common";
|
|
4
4
|
import { OINODb } from "./OINODb.js";
|
|
5
5
|
import { OINODbDataField } from "./OINODbDataField.js";
|
|
6
|
-
import { OINODbSqlFilter, OINODbSqlLimit, OINODbSqlOrder } from "./OINODbSqlParams.js";
|
|
6
|
+
import { OINODbSqlAggregate, OINODbSqlFilter, OINODbSqlLimit, OINODbSqlOrder } from "./OINODbSqlParams.js";
|
|
7
7
|
export { OINODbApiResult, OINODbHtmlTemplate, OINODbApi } from "./OINODbApi.js";
|
|
8
8
|
export { OINODbDataModel } from "./OINODbDataModel.js";
|
|
9
9
|
export { OINODbModelSet } from "./OINODbModelSet.js";
|
|
@@ -16,6 +16,8 @@ export { OINODbSwagger } from "./OINODbSwagger.js";
|
|
|
16
16
|
export { OINODbParser } from "./OINODbParser.js";
|
|
17
17
|
/** API parameters */
|
|
18
18
|
export type OINODbApiParams = {
|
|
19
|
+
/** Name of the api */
|
|
20
|
+
apiName: string;
|
|
19
21
|
/** Name of the database table */
|
|
20
22
|
tableName: string;
|
|
21
23
|
/** Reject values that exceed field max length (behaviour on such is platform dependent) */
|
|
@@ -26,9 +28,11 @@ export type OINODbApiParams = {
|
|
|
26
28
|
failOnInsertWithoutKey?: boolean;
|
|
27
29
|
/** Treat date type fields as just strings and use the native formatting instead of the ISO 8601 format */
|
|
28
30
|
useDatesAsString?: Boolean;
|
|
31
|
+
/** Include given fields from the API and exclude rest (if defined) */
|
|
32
|
+
includeFields?: string[];
|
|
29
33
|
/** Exclude all fields with this prefix from the API */
|
|
30
34
|
excludeFieldPrefix?: string;
|
|
31
|
-
/** Exclude given fields from the API */
|
|
35
|
+
/** Exclude given fields from the API and include rest (if defined) */
|
|
32
36
|
excludeFields?: string[];
|
|
33
37
|
/** Enable hashids for numeric primarykeys by adding a 32 char key */
|
|
34
38
|
hashidKey?: string;
|
|
@@ -83,6 +87,8 @@ export type OINODbSqlParams = {
|
|
|
83
87
|
order?: OINODbSqlOrder;
|
|
84
88
|
/** SQL result limit condition */
|
|
85
89
|
limit?: OINODbSqlLimit;
|
|
90
|
+
/** SQL aggregation functions */
|
|
91
|
+
aggregate?: OINODbSqlAggregate;
|
|
86
92
|
};
|
|
87
93
|
/** Request options */
|
|
88
94
|
export type OINODbApiRequestParams = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oino-ts/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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,12 +19,12 @@
|
|
|
19
19
|
"module": "./dist/esm/index.js",
|
|
20
20
|
"types": "./dist/types/index.d.ts",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@oino-ts/common": "0.
|
|
22
|
+
"@oino-ts/common": "0.3.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^20.14.10",
|
|
26
26
|
"@types/bun": "^1.1.14",
|
|
27
|
-
"@oino-ts/types": "0.
|
|
27
|
+
"@oino-ts/types": "0.3.1",
|
|
28
28
|
"typedoc": "^0.25.13"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
package/src/OINODb.ts
CHANGED
|
@@ -102,9 +102,10 @@ export abstract class OINODb {
|
|
|
102
102
|
* @param whereCondition - The WHERE clause to filter the results.
|
|
103
103
|
* @param orderCondition - The ORDER BY clause to sort the results.
|
|
104
104
|
* @param limitCondition - The LIMIT clause to limit the number of results.
|
|
105
|
+
* @param groupByCondition - The GROUP BY clause to group the results.
|
|
105
106
|
*
|
|
106
107
|
*/
|
|
107
|
-
printSqlSelect(tableName:string, columnNames:string, whereCondition:string, orderCondition:string, limitCondition:string): string {
|
|
108
|
+
printSqlSelect(tableName:string, columnNames:string, whereCondition:string, orderCondition:string, limitCondition:string, groupByCondition: string): string {
|
|
108
109
|
let result:string = "SELECT " + columnNames + " FROM " + tableName;
|
|
109
110
|
// OINOLog.debug("OINODb.printSqlSelect", {tableName:tableName, columnNames:columnNames, whereCondition:whereCondition, orderCondition:orderCondition, limitCondition:limitCondition })
|
|
110
111
|
if (whereCondition != "") {
|
|
@@ -116,6 +117,9 @@ export abstract class OINODb {
|
|
|
116
117
|
if (limitCondition != "") {
|
|
117
118
|
result += " LIMIT " + limitCondition
|
|
118
119
|
}
|
|
120
|
+
if (groupByCondition != "") {
|
|
121
|
+
result += " GROUP BY " + groupByCondition
|
|
122
|
+
}
|
|
119
123
|
result += ";"
|
|
120
124
|
// OINOLog.debug("OINODb.printSqlSelect", {result:result})
|
|
121
125
|
return result;
|
package/src/OINODbApi.test.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { OINODbBunSqlite } from "@oino-ts/db-bunsqlite"
|
|
|
12
12
|
import { OINODbPostgresql } from "@oino-ts/db-postgresql"
|
|
13
13
|
import { OINODbMariadb } from "@oino-ts/db-mariadb"
|
|
14
14
|
import { OINODbMsSql } from "@oino-ts/db-mssql"
|
|
15
|
+
import { OINODbSqlAggregate } from "./OINODbSqlParams.js";
|
|
15
16
|
|
|
16
17
|
const OINODB_POSTGRESQL_TOKEN = process.env.OINODB_POSTGRESQL_TOKEN || console.error("OINODB_POSTGRESQL_TOKEN not set") || ""
|
|
17
18
|
const OINODB_MARIADB_TOKEN = process.env.OINODB_MARIADB_TOKEN || console.error("OINODB_MARIADB_TOKEN not set") || ""
|
|
@@ -36,8 +37,8 @@ const dbs:OINODbParams[] = [
|
|
|
36
37
|
|
|
37
38
|
const api_tests:OINOTestParams[] = [
|
|
38
39
|
{
|
|
39
|
-
name: "API",
|
|
40
|
-
apiParams: { tableName: "Orders" },
|
|
40
|
+
name: "API 1",
|
|
41
|
+
apiParams: { apiName: "Orders", tableName: "Orders" },
|
|
41
42
|
requestParams: {
|
|
42
43
|
sqlParams: { filter: OINODbSqlFilter.parse("(ShipPostalCode)-like(0502%)"), order: OINODbSqlOrder.parse("ShipPostalCode-,Freight+"), limit: OINODbSqlLimit.parse("5 page 2") }
|
|
43
44
|
},
|
|
@@ -45,8 +46,8 @@ const api_tests:OINOTestParams[] = [
|
|
|
45
46
|
putRow: [30000,"CACTU",1,new Date("2023-04-05"),new Date("2023-04-06"),new Date("2023-04-07"),2,"847.51","k'l\"m%n_o\tp\rq\nr\\s","59 rue de l'Abbaye","Cowes2","Western Europe","PO31 8PJ","UK"]
|
|
46
47
|
},
|
|
47
48
|
{
|
|
48
|
-
name: "API",
|
|
49
|
-
apiParams: { tableName: "Products", failOnOversizedValues: true },
|
|
49
|
+
name: "API 2",
|
|
50
|
+
apiParams: { apiName: "Products", tableName: "Products", failOnOversizedValues: true },
|
|
50
51
|
requestParams: {
|
|
51
52
|
sqlParams: { filter: OINODbSqlFilter.parse("(UnitsInStock)-le(5)"), order: OINODbSqlOrder.parse("UnitsInStock,UnitPrice"), limit: OINODbSqlLimit.parse("7") }
|
|
52
53
|
},
|
|
@@ -54,8 +55,8 @@ const api_tests:OINOTestParams[] = [
|
|
|
54
55
|
putRow: [99, "Umeshu", 1, 1, undefined, 24.99, 3, 0, 20, 0]
|
|
55
56
|
},
|
|
56
57
|
{
|
|
57
|
-
name: "API",
|
|
58
|
-
apiParams: { tableName: "Employees", hashidKey: "12345678901234567890123456789012", hashidStaticIds:true },
|
|
58
|
+
name: "API 3",
|
|
59
|
+
apiParams: { apiName: "Employees", tableName: "Employees", hashidKey: "12345678901234567890123456789012", hashidStaticIds:true },
|
|
59
60
|
requestParams: {
|
|
60
61
|
sqlParams: { filter: OINODbSqlFilter.parse("(TitleOfCourtesy)-eq(Ms.)"), order: OINODbSqlOrder.parse("LastName asc"), limit: OINODbSqlLimit.parse("5") }
|
|
61
62
|
},
|
|
@@ -63,20 +64,21 @@ const api_tests:OINOTestParams[] = [
|
|
|
63
64
|
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"],
|
|
64
65
|
},
|
|
65
66
|
{
|
|
66
|
-
name: "API",
|
|
67
|
-
apiParams: { tableName: "OrderDetails" },
|
|
67
|
+
name: "API 4",
|
|
68
|
+
apiParams: { apiName: "OrderDetails", tableName: "OrderDetails" },
|
|
68
69
|
requestParams: {
|
|
69
|
-
sqlParams: {
|
|
70
|
+
sqlParams: { aggregate: OINODbSqlAggregate.parse("count(ProductID),avg(UnitPrice),sum(Quantity),max(Discount)") }
|
|
70
71
|
},
|
|
71
72
|
postRow: [10249,77,12.34,56,0],
|
|
72
73
|
putRow: [10249,77,23.45,67,0]
|
|
73
74
|
}
|
|
75
|
+
|
|
74
76
|
]
|
|
75
77
|
|
|
76
78
|
const owasp_tests:OINOTestParams[] = [
|
|
77
79
|
{
|
|
78
80
|
name: "OWASP 1",
|
|
79
|
-
apiParams: { tableName: "Products", failOnOversizedValues: true },
|
|
81
|
+
apiParams: { apiName: "Products", tableName: "Products", failOnOversizedValues: true },
|
|
80
82
|
requestParams: {
|
|
81
83
|
sqlParams: { filter: OINODbSqlFilter.parse("(1)-eq(1)") }
|
|
82
84
|
},
|
|
@@ -85,7 +87,7 @@ const owasp_tests:OINOTestParams[] = [
|
|
|
85
87
|
},
|
|
86
88
|
{
|
|
87
89
|
name: "OWASP 2",
|
|
88
|
-
apiParams: { tableName: "Products", failOnOversizedValues: true },
|
|
90
|
+
apiParams: { apiName: "Products", tableName: "Products", failOnOversizedValues: true },
|
|
89
91
|
requestParams: {
|
|
90
92
|
sqlParams: { order: OINODbSqlOrder.parse("1 asc") }
|
|
91
93
|
},
|
|
@@ -94,7 +96,7 @@ const owasp_tests:OINOTestParams[] = [
|
|
|
94
96
|
},
|
|
95
97
|
{
|
|
96
98
|
name: "OWASP 3",
|
|
97
|
-
apiParams: { tableName: "Products", failOnOversizedValues: true },
|
|
99
|
+
apiParams: { apiName: "Products", tableName: "Products", failOnOversizedValues: true },
|
|
98
100
|
requestParams: {
|
|
99
101
|
sqlParams: { filter: OINODbSqlFilter.parse("(ProductID)-eq(FOO)") }
|
|
100
102
|
},
|
package/src/OINODbApi.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { OINODbApiParams, OINODb, OINODbDataSet, OINODbDataModel, OINODbDataField, OINOStringDataField, OINO_ERROR_PREFIX, OINODataRow, OINODataCell, OINODbModelSet, OINOBenchmark, OINODbApiRequestParams, OINODbConfig, OINOHttpResult, OINOHtmlTemplate, OINONumberDataField, OINODbParser } from "./index.js"
|
|
8
|
-
import { OINOResult } from "@oino-ts/common";
|
|
8
|
+
import { OINOLog, OINOResult } from "@oino-ts/common";
|
|
9
9
|
import { OINOHashid } from "@oino-ts/hashid"
|
|
10
10
|
|
|
11
11
|
const API_EMPTY_PARAMS:OINODbApiRequestParams = { sqlParams: {} }
|
|
@@ -364,4 +364,22 @@ export class OINODbApi {
|
|
|
364
364
|
OINOBenchmark.end("OINODbApi", "doRequest", method)
|
|
365
365
|
return Promise.resolve(result)
|
|
366
366
|
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Method to check if a field is included in the API params.
|
|
370
|
+
*
|
|
371
|
+
* @param fieldName name of the field
|
|
372
|
+
*
|
|
373
|
+
*/
|
|
374
|
+
|
|
375
|
+
public isFieldIncluded(fieldName:string):boolean {
|
|
376
|
+
// OINOLog.debug("OINODbApi.isFieldIncluded", {fieldName:fieldName, included:this.params.includeFields})
|
|
377
|
+
const params = this.params
|
|
378
|
+
return (
|
|
379
|
+
((params.excludeFieldPrefix == undefined) || (params.excludeFieldPrefix == "") || (fieldName.startsWith(params.excludeFieldPrefix) == false)) &&
|
|
380
|
+
((params.excludeFields == undefined) || (params.excludeFields.length == 0) || (params.excludeFields.indexOf(fieldName) < 0)) &&
|
|
381
|
+
((params.includeFields == undefined) || (params.includeFields.length == 0) || (params.includeFields.indexOf(fieldName) >= 0))
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
|
|
367
385
|
}
|
package/src/OINODbConfig.ts
CHANGED
|
@@ -16,6 +16,9 @@ export class OINODbConfig {
|
|
|
16
16
|
/** Name of the OINODbSqlLimit-parameter in request */
|
|
17
17
|
static OINODB_SQL_LIMIT_PARAM:string = "oinosqllimit"
|
|
18
18
|
|
|
19
|
+
/** Name of the OINODbSqlAggregate-parameter in request */
|
|
20
|
+
static OINODB_SQL_AGGREGATE_PARAM:string = "oinosqlaggregate"
|
|
21
|
+
|
|
19
22
|
/**
|
|
20
23
|
* Set the name of the OINO ID field
|
|
21
24
|
* @param idField name of the OINO ID field
|
package/src/OINODbDataField.ts
CHANGED
|
@@ -258,7 +258,7 @@ export class OINONumberDataField extends OINODbDataField {
|
|
|
258
258
|
} else {
|
|
259
259
|
const result:number = parseFloat(value)
|
|
260
260
|
if (isNaN(result)) {
|
|
261
|
-
OINOLog.error("
|
|
261
|
+
OINOLog.error("OINONumberDataField.toSql: Invalid value!", {value:value})
|
|
262
262
|
throw new Error(OINO_ERROR_PREFIX + ": OINONumberDataField.deserializeCell - Invalid value '" + value + "'") // incorrectly formatted data could be a security risk, abort processing
|
|
263
263
|
}
|
|
264
264
|
return result
|
package/src/OINODbDataModel.ts
CHANGED
|
@@ -43,13 +43,10 @@ export class OINODbDataModel {
|
|
|
43
43
|
|
|
44
44
|
private _printSqlColumnNames(): string {
|
|
45
45
|
let result: string = "";
|
|
46
|
-
for (let
|
|
47
|
-
|
|
48
|
-
result += ",";
|
|
49
|
-
}
|
|
50
|
-
result += f.printSqlColumnName();
|
|
46
|
+
for (let i=0; i < this.fields.length; i++) {
|
|
47
|
+
result += this.fields[i].printSqlColumnName()+","
|
|
51
48
|
}
|
|
52
|
-
return result
|
|
49
|
+
return result.substring(0, result.length-1)
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
private _printSqlInsertColumnsAndValues(row: OINODataRow): string {
|
|
@@ -231,10 +228,17 @@ export class OINODbDataModel {
|
|
|
231
228
|
*
|
|
232
229
|
*/
|
|
233
230
|
printSqlSelect(id: string, params:OINODbSqlParams): string {
|
|
234
|
-
|
|
231
|
+
let column_names = ""
|
|
232
|
+
if (params.aggregate) {
|
|
233
|
+
column_names = params.aggregate.printSqlColumnNames(this)
|
|
234
|
+
} else {
|
|
235
|
+
column_names = this._printSqlColumnNames()
|
|
236
|
+
}
|
|
235
237
|
const order_sql = params.order?.toSql(this) || ""
|
|
236
238
|
const limit_sql = params.limit?.toSql(this) || ""
|
|
237
239
|
const filter_sql = params.filter?.toSql(this) || ""
|
|
240
|
+
const aggregate_sql = params.aggregate?.toSql(this) || ""
|
|
241
|
+
|
|
238
242
|
let where_sql = ""
|
|
239
243
|
// OINOLog.debug("OINODbDataModel.printSqlSelect", {id:id, select_sql:result, filter_sql:filter_sql, order_sql:order_sql})
|
|
240
244
|
if ((id != null) && (id != "") && (filter_sql != "")) {
|
|
@@ -244,7 +248,7 @@ export class OINODbDataModel {
|
|
|
244
248
|
} else if (filter_sql != "") {
|
|
245
249
|
where_sql = filter_sql
|
|
246
250
|
}
|
|
247
|
-
const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql)
|
|
251
|
+
const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql, aggregate_sql)
|
|
248
252
|
// OINOLog.debug("OINODbDataModel.printSqlSelect", {result:result})
|
|
249
253
|
return result;
|
|
250
254
|
}
|
package/src/OINODbFactory.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { OINODbApi, OINODbApiParams, OINODbParams, OINOContentType, OINODb, OINODbConstructor, OINODbApiRequestParams, OINODbSqlFilter, OINODbConfig, OINODbSqlOrder, OINODbSqlLimit, OINODbSqlParams } from "./index.js"
|
|
8
|
+
import { OINODbSqlAggregate } from "./OINODbSqlParams.js"
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Static factory class for easily creating things based on data
|
|
@@ -75,6 +76,10 @@ export class OINODbFactory {
|
|
|
75
76
|
if (limit) {
|
|
76
77
|
sql_params.limit = OINODbSqlLimit.parse(limit)
|
|
77
78
|
}
|
|
79
|
+
const aggregate = url.searchParams.get(OINODbConfig.OINODB_SQL_AGGREGATE_PARAM)
|
|
80
|
+
if (aggregate) {
|
|
81
|
+
sql_params.aggregate = OINODbSqlAggregate.parse(aggregate)
|
|
82
|
+
}
|
|
78
83
|
|
|
79
84
|
let result:OINODbApiRequestParams = { sqlParams: sql_params }
|
|
80
85
|
|
package/src/OINODbSqlParams.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import { OINOStr, OINODbDataField, OINODbDataModel, OINO_ERROR_PREFIX, OINOLog } from "./index.js"
|
|
8
8
|
|
|
9
|
+
const OINO_FIELD_NAME_CHARS:string = "\\w\\s\\-\\_\\#\\¤"
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Supported logical conjunctions in filter predicates.
|
|
11
13
|
* @enum
|
|
@@ -58,13 +60,13 @@ export class OINODbSqlFilter {
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
|
-
* Constructor for `
|
|
63
|
+
* Constructor for `OINODbSqlFilter` as parser of http parameter.
|
|
62
64
|
*
|
|
63
65
|
* @param filterString string representation of filter from HTTP-request
|
|
64
66
|
*
|
|
65
67
|
*/
|
|
66
68
|
static parse(filterString: string):OINODbSqlFilter {
|
|
67
|
-
// OINOLog_debug("
|
|
69
|
+
// OINOLog_debug("OINODbSqlFilter.constructor", {filterString:filterString})
|
|
68
70
|
if (!filterString) {
|
|
69
71
|
return new OINODbSqlFilter("", null, "")
|
|
70
72
|
|
|
@@ -78,7 +80,7 @@ export class OINODbSqlFilter {
|
|
|
78
80
|
return new OINODbSqlFilter("", OINODbSqlBooleanOperation.not, OINODbSqlFilter.parse(match[3]))
|
|
79
81
|
} else {
|
|
80
82
|
let boolean_parts = OINOStr.splitByBrackets(filterString, true, false, '(', ')')
|
|
81
|
-
// OINOLog_debug("
|
|
83
|
+
// OINOLog_debug("OINODbSqlFilter.constructor", {boolean_parts:boolean_parts})
|
|
82
84
|
if (boolean_parts.length == 3 && (boolean_parts[1].match(OINODbSqlFilter._booleanOperationRegex))) {
|
|
83
85
|
return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1) as OINODbSqlBooleanOperation, OINODbSqlFilter.parse(boolean_parts[2]))
|
|
84
86
|
|
|
@@ -91,7 +93,7 @@ export class OINODbSqlFilter {
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
/**
|
|
94
|
-
* Construct a new `
|
|
96
|
+
* Construct a new `OINODbSqlFilter` as combination of (boolean and/or) of two filters.
|
|
95
97
|
*
|
|
96
98
|
* @param leftSide left side to combine
|
|
97
99
|
* @param operation boolean operation to use in combination
|
|
@@ -144,7 +146,7 @@ export class OINODbSqlFilter {
|
|
|
144
146
|
*
|
|
145
147
|
*/
|
|
146
148
|
toSql(dataModel:OINODbDataModel):string {
|
|
147
|
-
// OINOLog.debug("
|
|
149
|
+
// OINOLog.debug("OINODbSqlFilter.toSql", {_leftSide:this._leftSide, _operator:this._operator, _rightSide:this._rightSide})
|
|
148
150
|
if (this.isEmpty()) {
|
|
149
151
|
return ""
|
|
150
152
|
}
|
|
@@ -171,7 +173,7 @@ export class OINODbSqlFilter {
|
|
|
171
173
|
}
|
|
172
174
|
result += field!.printCellAsSqlValue(value)
|
|
173
175
|
}
|
|
174
|
-
// OINOLog.debug("
|
|
176
|
+
// OINOLog.debug("OINODbSqlFilter.toSql", {result:result})
|
|
175
177
|
return "(" + result + ")"
|
|
176
178
|
}
|
|
177
179
|
}
|
|
@@ -335,4 +337,101 @@ export class OINODbSqlLimit {
|
|
|
335
337
|
}
|
|
336
338
|
}
|
|
337
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Supported aggregation functions in OINODbSqlAggregate.
|
|
342
|
+
* @enum
|
|
343
|
+
*/
|
|
344
|
+
export enum OINODbSqlAggregateFunctions { count = "count", sum = "sum", avg = "avg", min = "min", max = "max" }
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Class for limiting the number of results.
|
|
348
|
+
*
|
|
349
|
+
*/
|
|
350
|
+
export class OINODbSqlAggregate {
|
|
351
|
+
private static _aggregateRegex:RegExp = new RegExp("^(count|sum|avg|min|max)\\(([" + OINO_FIELD_NAME_CHARS + "]+)\\)$", "mi")
|
|
352
|
+
|
|
353
|
+
private _functions: OINODbSqlAggregateFunctions[]
|
|
354
|
+
private _fields: string[]
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Constructor for `OINODbSqlAggregate`.
|
|
358
|
+
*
|
|
359
|
+
* @param function aggregate function to use
|
|
360
|
+
* @param fields fields to aggregate
|
|
361
|
+
*
|
|
362
|
+
*/
|
|
363
|
+
constructor(func: OINODbSqlAggregateFunctions[], fields: string[]) {
|
|
364
|
+
this._functions = func
|
|
365
|
+
this._fields = fields
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Constructor for `OINODbSqlAggregate` as parser of http parameter.
|
|
369
|
+
*
|
|
370
|
+
* @param aggregatorString string representation of limit from HTTP-request
|
|
371
|
+
*
|
|
372
|
+
*/
|
|
373
|
+
static parse(aggregatorString: string):OINODbSqlAggregate {
|
|
374
|
+
let funtions:OINODbSqlAggregateFunctions[] = []
|
|
375
|
+
let fields:string[] = []
|
|
376
|
+
const aggregator_parts = aggregatorString.split(',')
|
|
377
|
+
for (let i=0; i<aggregator_parts.length; i++) {
|
|
378
|
+
let match = OINODbSqlAggregate._aggregateRegex.exec(aggregator_parts[i])
|
|
379
|
+
// OINOLog.debug("OINODbSqlAggregate.parse - next aggregator", {aggregator: aggregator_parts[i], match:match})
|
|
380
|
+
if ((match != null) && (match.length == 3)) {
|
|
381
|
+
funtions.push(match[1] as OINODbSqlAggregateFunctions)
|
|
382
|
+
fields.push(match[2])
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return new OINODbSqlAggregate(funtions, fields)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Does filter contain any valid conditions.
|
|
390
|
+
*
|
|
391
|
+
*/
|
|
392
|
+
isEmpty():boolean {
|
|
393
|
+
return (this._functions.length <= 0)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
|
|
398
|
+
*
|
|
399
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
400
|
+
*
|
|
401
|
+
*/
|
|
402
|
+
toSql(dataModel:OINODbDataModel):string {
|
|
403
|
+
if (this.isEmpty()) {
|
|
404
|
+
return ""
|
|
405
|
+
}
|
|
406
|
+
let result:string = ""
|
|
407
|
+
for (let i=0; i<dataModel.fields.length; i++) {
|
|
408
|
+
if (this._fields.includes(dataModel.fields[i].name) == false) {
|
|
409
|
+
result += dataModel.fields[i].printSqlColumnName() + ","
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
OINOLog.debug("OINODbSqlAggregate.toSql", {result:result})
|
|
413
|
+
return result.substring(0, result.length-1)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
|
|
418
|
+
*
|
|
419
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
420
|
+
*
|
|
421
|
+
*/
|
|
422
|
+
printSqlColumnNames(dataModel:OINODbDataModel):string {
|
|
423
|
+
let result:string = ""
|
|
424
|
+
for (let i=0; i<dataModel.fields.length; i++) {
|
|
425
|
+
const aggregate_index = this._fields.indexOf(dataModel.fields[i].name)
|
|
426
|
+
if (aggregate_index >= 0) {
|
|
427
|
+
result += this._functions[aggregate_index] + "(" + dataModel.fields[i].printSqlColumnName() + "),"
|
|
428
|
+
} else {
|
|
429
|
+
result += dataModel.fields[i].printSqlColumnName() + ","
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
OINOLog.debug("OINODbSqlAggregate.printSqlColumnNames", {result:result})
|
|
433
|
+
return result.substring(0, result.length-1)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
338
437
|
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { OINO_ERROR_PREFIX, OINO_WARNING_PREFIX, OINO_INFO_PREFIX, OINO_DEBUG_PR
|
|
|
5
5
|
|
|
6
6
|
import { OINODb } from "./OINODb.js"
|
|
7
7
|
import { OINODbDataField } from "./OINODbDataField.js"
|
|
8
|
-
import { OINODbSqlFilter, OINODbSqlLimit, OINODbSqlOrder } from "./OINODbSqlParams.js"
|
|
8
|
+
import { OINODbSqlAggregate, OINODbSqlFilter, OINODbSqlLimit, OINODbSqlOrder } from "./OINODbSqlParams.js"
|
|
9
9
|
|
|
10
10
|
export { OINODbApiResult, OINODbHtmlTemplate, OINODbApi } from "./OINODbApi.js"
|
|
11
11
|
export { OINODbDataModel } from "./OINODbDataModel.js"
|
|
@@ -20,6 +20,8 @@ export { OINODbParser } from "./OINODbParser.js"
|
|
|
20
20
|
|
|
21
21
|
/** API parameters */
|
|
22
22
|
export type OINODbApiParams = {
|
|
23
|
+
/** Name of the api */
|
|
24
|
+
apiName: string
|
|
23
25
|
/** Name of the database table */
|
|
24
26
|
tableName: string
|
|
25
27
|
/** Reject values that exceed field max length (behaviour on such is platform dependent) */
|
|
@@ -30,9 +32,11 @@ export type OINODbApiParams = {
|
|
|
30
32
|
failOnInsertWithoutKey?: boolean
|
|
31
33
|
/** Treat date type fields as just strings and use the native formatting instead of the ISO 8601 format */
|
|
32
34
|
useDatesAsString?: Boolean
|
|
35
|
+
/** Include given fields from the API and exclude rest (if defined) */
|
|
36
|
+
includeFields?:string[],
|
|
33
37
|
/** Exclude all fields with this prefix from the API */
|
|
34
38
|
excludeFieldPrefix?:string
|
|
35
|
-
/** Exclude given fields from the API */
|
|
39
|
+
/** Exclude given fields from the API and include rest (if defined) */
|
|
36
40
|
excludeFields?:string[],
|
|
37
41
|
/** Enable hashids for numeric primarykeys by adding a 32 char key */
|
|
38
42
|
hashidKey?:string,
|
|
@@ -92,6 +96,8 @@ export type OINODbSqlParams = {
|
|
|
92
96
|
order?:OINODbSqlOrder
|
|
93
97
|
/** SQL result limit condition */
|
|
94
98
|
limit?:OINODbSqlLimit
|
|
99
|
+
/** SQL aggregation functions */
|
|
100
|
+
aggregate?:OINODbSqlAggregate
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
/** Request options */
|