@oino-ts/db 0.0.16 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -42
- package/dist/cjs/OINODb.js +26 -0
- package/dist/cjs/OINODbApi.js +18 -12
- package/dist/cjs/OINODbDataField.js +11 -2
- package/dist/cjs/OINODbDataModel.js +8 -13
- package/dist/cjs/OINODbFactory.js +0 -356
- package/dist/cjs/OINODbModelSet.js +6 -4
- package/dist/cjs/OINODbRequestParams.js +38 -23
- package/dist/cjs/OINODbSqlParams.js +336 -0
- package/dist/cjs/index.js +6 -6
- package/dist/esm/OINODb.js +26 -0
- package/dist/esm/OINODbApi.js +19 -13
- package/dist/esm/OINODbDataField.js +11 -2
- package/dist/esm/OINODbDataModel.js +8 -13
- package/dist/esm/OINODbFactory.js +1 -357
- package/dist/esm/OINODbModelSet.js +6 -4
- package/dist/esm/OINODbRequestParams.js +38 -23
- package/dist/esm/OINODbSqlParams.js +330 -0
- package/dist/esm/index.js +1 -1
- package/dist/types/OINODb.d.ts +11 -0
- package/dist/types/OINODbApi.d.ts +2 -2
- package/dist/types/OINODbFactory.d.ts +1 -29
- package/dist/types/OINODbRequestParams.d.ts +3 -2
- package/dist/types/OINODbSqlParams.d.ts +147 -0
- package/dist/types/index.d.ts +4 -2
- package/package.json +2 -2
- package/src/OINODb.ts +27 -0
- package/src/OINODbApi.test.ts +184 -46
- package/src/OINODbApi.ts +20 -15
- package/src/OINODbDataField.ts +11 -3
- package/src/OINODbDataModel.ts +8 -13
- package/src/OINODbFactory.ts +1 -358
- package/src/OINODbModelSet.ts +6 -4
- package/src/{OINODbRequestParams.ts → OINODbSqlParams.ts} +39 -21
- package/src/index.ts +4 -2
|
@@ -34,11 +34,13 @@ class OINODbModelSet {
|
|
|
34
34
|
}
|
|
35
35
|
_encodeAndHashFieldValue(field, value, contentType, primaryKeyValues, rowIdSeed) {
|
|
36
36
|
let result;
|
|
37
|
-
if (field.fieldParams.isPrimaryKey) {
|
|
37
|
+
if (field.fieldParams.isPrimaryKey || field.fieldParams.isForeignKey) {
|
|
38
38
|
if (value && (field instanceof index_js_1.OINONumberDataField) && (this.datamodel.api.hashid)) {
|
|
39
39
|
value = this.datamodel.api.hashid.encode(value, rowIdSeed);
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
if (field.fieldParams.isPrimaryKey) {
|
|
42
|
+
primaryKeyValues.push(value || "");
|
|
43
|
+
}
|
|
42
44
|
}
|
|
43
45
|
result = index_js_1.OINOStr.encode(value, contentType);
|
|
44
46
|
return result;
|
|
@@ -54,13 +56,13 @@ class OINODbModelSet {
|
|
|
54
56
|
const f = fields[i];
|
|
55
57
|
let value = f.serializeCell(row[i]);
|
|
56
58
|
if (value === undefined) {
|
|
57
|
-
|
|
59
|
+
// OINOLog.info("OINODbModelSet._writeRowJson: undefined value skipped", {field_name:f.name})
|
|
58
60
|
}
|
|
59
61
|
else if (value === null) {
|
|
60
62
|
json_row += "," + index_js_1.OINOStr.encode(f.name, index_js_1.OINOContentType.json) + ":null";
|
|
61
63
|
}
|
|
62
64
|
else {
|
|
63
|
-
let is_hashed = f.fieldParams.isPrimaryKey && (f instanceof index_js_1.OINONumberDataField) && (this.datamodel.api.hashid != null);
|
|
65
|
+
let is_hashed = (f.fieldParams.isPrimaryKey || f.fieldParams.isForeignKey) && (f instanceof index_js_1.OINONumberDataField) && (this.datamodel.api.hashid != null);
|
|
64
66
|
let is_value = (f instanceof index_js_1.OINOBooleanDataField) || ((f instanceof index_js_1.OINONumberDataField) && !is_hashed);
|
|
65
67
|
value = this._encodeAndHashFieldValue(f, value, index_js_1.OINOContentType.json, primary_key_values, f.name + " " + row_id_seed);
|
|
66
68
|
if (is_value) {
|
|
@@ -92,7 +92,7 @@ class OINODbSqlFilter {
|
|
|
92
92
|
return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1), OINODbSqlFilter.parse(boolean_parts[2]));
|
|
93
93
|
}
|
|
94
94
|
else {
|
|
95
|
-
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'");
|
|
95
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'"); // invalid filter could be a security risk, stop processing
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -158,20 +158,24 @@ class OINODbSqlFilter {
|
|
|
158
158
|
result += this._leftSide.toSql(dataModel);
|
|
159
159
|
}
|
|
160
160
|
else {
|
|
161
|
-
result += dataModel.api.db.printSqlColumnname(this._leftSide);
|
|
162
161
|
field = dataModel.findFieldByName(this._leftSide);
|
|
162
|
+
if (!field) {
|
|
163
|
+
index_js_1.OINOLog.error("OINODbSqlFilter.toSql: Invalid field!", { field: this._leftSide });
|
|
164
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": OINODbSqlFilter.toSql - Invalid field '" + this._leftSide + "'"); // invalid field name could be a security risk, stop processing
|
|
165
|
+
}
|
|
166
|
+
result += dataModel.api.db.printSqlColumnname(field?.name || this._leftSide);
|
|
163
167
|
}
|
|
164
168
|
result += this._operatorToSql();
|
|
165
169
|
if (this._rightSide instanceof OINODbSqlFilter) {
|
|
166
170
|
result += this._rightSide.toSql(dataModel);
|
|
167
171
|
}
|
|
168
172
|
else {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
result += this._rightSide;
|
|
173
|
+
const value = field.deserializeCell(this._rightSide);
|
|
174
|
+
if (!value) {
|
|
175
|
+
index_js_1.OINOLog.error("OINODbSqlFilter.toSql: Invalid value!", { value: value });
|
|
176
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": OINODbSqlFilter.toSql - Invalid value '" + value + "'"); // invalid value could be a security risk, stop processing
|
|
174
177
|
}
|
|
178
|
+
result += field.printCellAsSqlValue(value);
|
|
175
179
|
}
|
|
176
180
|
// OINOLog.debug("OINOFilter.toSql", {result:result})
|
|
177
181
|
return "(" + result + ")";
|
|
@@ -248,17 +252,19 @@ class OINODbSqlOrder {
|
|
|
248
252
|
let result = "";
|
|
249
253
|
for (let i = 0; i < this._columns.length; i++) {
|
|
250
254
|
const field = dataModel.findFieldByName(this._columns[i]);
|
|
251
|
-
if (field) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
if (!field) {
|
|
256
|
+
index_js_1.OINOLog.error("OINODbSqlOrder.toSql: Invalid field!", { field: this._columns[i] });
|
|
257
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": OINODbSqlOrder.toSql - Invalid field '" + this._columns[i] + "'"); // invalid field name could be a security risk, stop processing
|
|
258
|
+
}
|
|
259
|
+
if (result) {
|
|
260
|
+
result += ",";
|
|
261
|
+
}
|
|
262
|
+
result += dataModel.api.db.printSqlColumnname(field.name) + " ";
|
|
263
|
+
if (this._descending[i]) {
|
|
264
|
+
result += "DESC";
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
result += "ASC";
|
|
262
268
|
}
|
|
263
269
|
}
|
|
264
270
|
// OINOLog.debug("OINODbSqlOrder.toSql", {result:result})
|
|
@@ -271,16 +277,18 @@ exports.OINODbSqlOrder = OINODbSqlOrder;
|
|
|
271
277
|
*
|
|
272
278
|
*/
|
|
273
279
|
class OINODbSqlLimit {
|
|
274
|
-
static
|
|
280
|
+
static _limitRegex = /^(\d+)(\spage\s)?(\d+)?$/i;
|
|
275
281
|
_limit;
|
|
282
|
+
_page;
|
|
276
283
|
/**
|
|
277
284
|
* Constructor for `OINODbSqlLimit`.
|
|
278
285
|
*
|
|
279
286
|
* @param limit maximum number of items to return
|
|
280
287
|
*
|
|
281
288
|
*/
|
|
282
|
-
constructor(limit) {
|
|
289
|
+
constructor(limit, page = -1) {
|
|
283
290
|
this._limit = limit;
|
|
291
|
+
this._page = page;
|
|
284
292
|
}
|
|
285
293
|
/**
|
|
286
294
|
* Constructor for `OINODbSqlLimit` as parser of http parameter.
|
|
@@ -289,10 +297,14 @@ class OINODbSqlLimit {
|
|
|
289
297
|
*
|
|
290
298
|
*/
|
|
291
299
|
static parse(limitString) {
|
|
292
|
-
|
|
293
|
-
|
|
300
|
+
let match = OINODbSqlLimit._limitRegex.exec(limitString);
|
|
301
|
+
if ((match != null) && (match.length == 4)) {
|
|
302
|
+
return new OINODbSqlLimit(Number.parseInt(match[1]), Number.parseInt(match[3]));
|
|
294
303
|
}
|
|
295
|
-
|
|
304
|
+
else if (match != null) {
|
|
305
|
+
return new OINODbSqlLimit(Number.parseInt(match[1]));
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
296
308
|
return new OINODbSqlLimit(-1);
|
|
297
309
|
}
|
|
298
310
|
}
|
|
@@ -314,6 +326,9 @@ class OINODbSqlLimit {
|
|
|
314
326
|
return "";
|
|
315
327
|
}
|
|
316
328
|
let result = this._limit.toString();
|
|
329
|
+
if (this._page > 0) {
|
|
330
|
+
result += " OFFSET " + (this._limit * (this._page - 1) + 1).toString();
|
|
331
|
+
}
|
|
317
332
|
return result;
|
|
318
333
|
}
|
|
319
334
|
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
4
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
5
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.OINODbSqlLimit = exports.OINODbSqlOrder = exports.OINODbSqlFilter = exports.OINODbSqlComparison = exports.OINODbSqlBooleanOperation = void 0;
|
|
9
|
+
const index_js_1 = require("./index.js");
|
|
10
|
+
/**
|
|
11
|
+
* Supported logical conjunctions in filter predicates.
|
|
12
|
+
* @enum
|
|
13
|
+
*/
|
|
14
|
+
var OINODbSqlBooleanOperation;
|
|
15
|
+
(function (OINODbSqlBooleanOperation) {
|
|
16
|
+
OINODbSqlBooleanOperation["and"] = "and";
|
|
17
|
+
OINODbSqlBooleanOperation["or"] = "or";
|
|
18
|
+
OINODbSqlBooleanOperation["not"] = "not";
|
|
19
|
+
})(OINODbSqlBooleanOperation || (exports.OINODbSqlBooleanOperation = OINODbSqlBooleanOperation = {}));
|
|
20
|
+
/**
|
|
21
|
+
* Supported logical conjunctions in filter predicates.
|
|
22
|
+
* @enum
|
|
23
|
+
*/
|
|
24
|
+
var OINODbSqlComparison;
|
|
25
|
+
(function (OINODbSqlComparison) {
|
|
26
|
+
OINODbSqlComparison["lt"] = "lt";
|
|
27
|
+
OINODbSqlComparison["le"] = "le";
|
|
28
|
+
OINODbSqlComparison["eq"] = "eq";
|
|
29
|
+
OINODbSqlComparison["ge"] = "ge";
|
|
30
|
+
OINODbSqlComparison["gt"] = "gt";
|
|
31
|
+
OINODbSqlComparison["like"] = "like";
|
|
32
|
+
})(OINODbSqlComparison || (exports.OINODbSqlComparison = OINODbSqlComparison = {}));
|
|
33
|
+
/**
|
|
34
|
+
* Class for recursively parsing of filters and printing them as SQL conditions.
|
|
35
|
+
* Supports three types of statements
|
|
36
|
+
* - comparison: (field)-lt|le|eq|ge|gt|like(value)
|
|
37
|
+
* - negation: -not(filter)
|
|
38
|
+
* - conjunction/disjunction: (filter)-and|or(filter)
|
|
39
|
+
* Supported conditions are comparisons (<, <=, =, >=, >) and substring match (LIKE).
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
42
|
+
class OINODbSqlFilter {
|
|
43
|
+
static _booleanOperationRegex = /^\s?\-(and|or)\s?$/i;
|
|
44
|
+
static _negationRegex = /^-(not|)\((.+)\)$/i;
|
|
45
|
+
static _filterComparisonRegex = /^\(([^'"\(\)]+)\)\s?\-(lt|le|eq|ge|gt|like)\s?\(([^'"\(\)]+)\)$/i;
|
|
46
|
+
_leftSide;
|
|
47
|
+
_rightSide;
|
|
48
|
+
_operator;
|
|
49
|
+
/**
|
|
50
|
+
* Constructor of `OINODbSqlFilter`
|
|
51
|
+
* @param leftSide left side of the filter, either another filter or a column name
|
|
52
|
+
* @param operation operation of the filter, either `OINODbSqlComparison` or `OINODbSqlBooleanOperation`
|
|
53
|
+
* @param rightSide right side of the filter, either another filter or a value
|
|
54
|
+
*/
|
|
55
|
+
constructor(leftSide, operation, rightSide) {
|
|
56
|
+
if (!(((operation === null) && (leftSide == "") && (rightSide == "")) ||
|
|
57
|
+
((operation !== null) && (Object.values(OINODbSqlComparison).includes(operation)) && (typeof (leftSide) == "string") && (leftSide != "") && (typeof (rightSide) == "string") && (rightSide != "")) ||
|
|
58
|
+
((operation == OINODbSqlBooleanOperation.not) && (leftSide == "") && (rightSide instanceof OINODbSqlFilter)) ||
|
|
59
|
+
(((operation == OINODbSqlBooleanOperation.and) || (operation == OINODbSqlBooleanOperation.or)) && (leftSide instanceof OINODbSqlFilter) && (rightSide instanceof OINODbSqlFilter)))) {
|
|
60
|
+
index_js_1.OINOLog.debug("Unsupported OINODbSqlFilter format!", { leftSide: leftSide, operation: operation, rightSide: rightSide });
|
|
61
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": Unsupported OINODbSqlFilter format!");
|
|
62
|
+
}
|
|
63
|
+
this._leftSide = leftSide;
|
|
64
|
+
this._operator = operation;
|
|
65
|
+
this._rightSide = rightSide;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Constructor for `OINOFilter` as parser of http parameter.
|
|
69
|
+
*
|
|
70
|
+
* @param filterString string representation of filter from HTTP-request
|
|
71
|
+
*
|
|
72
|
+
*/
|
|
73
|
+
static parse(filterString) {
|
|
74
|
+
// OINOLog_debug("OINOFilter.constructor", {filterString:filterString})
|
|
75
|
+
if (!filterString) {
|
|
76
|
+
return new OINODbSqlFilter("", null, "");
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
let match = OINODbSqlFilter._filterComparisonRegex.exec(filterString);
|
|
80
|
+
if (match != null) {
|
|
81
|
+
return new OINODbSqlFilter(match[1], match[2].toLowerCase(), match[3]);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
let match = OINODbSqlFilter._negationRegex.exec(filterString);
|
|
85
|
+
if (match != null) {
|
|
86
|
+
return new OINODbSqlFilter("", OINODbSqlBooleanOperation.not, OINODbSqlFilter.parse(match[3]));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
let boolean_parts = index_js_1.OINOStr.splitByBrackets(filterString, true, false, '(', ')');
|
|
90
|
+
// OINOLog_debug("OINOFilter.constructor", {boolean_parts:boolean_parts})
|
|
91
|
+
if (boolean_parts.length == 3 && (boolean_parts[1].match(OINODbSqlFilter._booleanOperationRegex))) {
|
|
92
|
+
return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1), OINODbSqlFilter.parse(boolean_parts[2]));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'"); // invalid filter could be a security risk, stop processing
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Construct a new `OINOFilter` as combination of (boolean and/or) of two filters.
|
|
103
|
+
*
|
|
104
|
+
* @param leftSide left side to combine
|
|
105
|
+
* @param operation boolean operation to use in combination
|
|
106
|
+
* @param rightSide right side to combine
|
|
107
|
+
*
|
|
108
|
+
*/
|
|
109
|
+
static combine(leftSide, operation, rightSide) {
|
|
110
|
+
if ((leftSide) && (!leftSide.isEmpty()) && (rightSide) && (!rightSide.isEmpty())) {
|
|
111
|
+
return new OINODbSqlFilter(leftSide, operation, rightSide);
|
|
112
|
+
}
|
|
113
|
+
else if ((leftSide) && (!leftSide.isEmpty())) {
|
|
114
|
+
return leftSide;
|
|
115
|
+
}
|
|
116
|
+
else if ((rightSide) && (!rightSide.isEmpty())) {
|
|
117
|
+
return rightSide;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
_operatorToSql() {
|
|
124
|
+
switch (this._operator) {
|
|
125
|
+
case "and": return " AND ";
|
|
126
|
+
case "or": return " OR ";
|
|
127
|
+
case "not": return "NOT ";
|
|
128
|
+
case "lt": return " < ";
|
|
129
|
+
case "le": return " <= ";
|
|
130
|
+
case "eq": return " = ";
|
|
131
|
+
case "ge": return " >= ";
|
|
132
|
+
case "gt": return " > ";
|
|
133
|
+
case "like": return " LIKE ";
|
|
134
|
+
}
|
|
135
|
+
return " ";
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Does filter contain any valid conditions.
|
|
139
|
+
*
|
|
140
|
+
*/
|
|
141
|
+
isEmpty() {
|
|
142
|
+
return (this._leftSide == "") && (this._operator == null) && (this._rightSide == "");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Print filter as SQL condition based on the datamodel of the API.
|
|
146
|
+
*
|
|
147
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
148
|
+
*
|
|
149
|
+
*/
|
|
150
|
+
toSql(dataModel) {
|
|
151
|
+
// OINOLog.debug("OINOFilter.toSql", {_leftSide:this._leftSide, _operator:this._operator, _rightSide:this._rightSide})
|
|
152
|
+
if (this.isEmpty()) {
|
|
153
|
+
return "";
|
|
154
|
+
}
|
|
155
|
+
let result = "";
|
|
156
|
+
let field = null;
|
|
157
|
+
if (this._leftSide instanceof OINODbSqlFilter) {
|
|
158
|
+
result += this._leftSide.toSql(dataModel);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
field = dataModel.findFieldByName(this._leftSide);
|
|
162
|
+
if (!field) {
|
|
163
|
+
index_js_1.OINOLog.error("OINODbSqlFilter.toSql: Invalid field!", { field: this._leftSide });
|
|
164
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": OINODbSqlFilter.toSql - Invalid field '" + this._leftSide + "'"); // invalid field name could be a security risk, stop processing
|
|
165
|
+
}
|
|
166
|
+
result += dataModel.api.db.printSqlColumnname(field?.name || this._leftSide);
|
|
167
|
+
}
|
|
168
|
+
result += this._operatorToSql();
|
|
169
|
+
if (this._rightSide instanceof OINODbSqlFilter) {
|
|
170
|
+
result += this._rightSide.toSql(dataModel);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const value = field.deserializeCell(this._rightSide);
|
|
174
|
+
if (!value) {
|
|
175
|
+
index_js_1.OINOLog.error("OINODbSqlFilter.toSql: Invalid value!", { value: value });
|
|
176
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": OINODbSqlFilter.toSql - Invalid value '" + value + "'"); // invalid value could be a security risk, stop processing
|
|
177
|
+
}
|
|
178
|
+
result += field.printCellAsSqlValue(value);
|
|
179
|
+
}
|
|
180
|
+
// OINOLog.debug("OINOFilter.toSql", {result:result})
|
|
181
|
+
return "(" + result + ")";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
exports.OINODbSqlFilter = OINODbSqlFilter;
|
|
185
|
+
/**
|
|
186
|
+
* Class for ordering select results on a number of columns.
|
|
187
|
+
*
|
|
188
|
+
*/
|
|
189
|
+
class OINODbSqlOrder {
|
|
190
|
+
static _orderColumnRegex = /^\s*(\w+)\s?(ASC|DESC)?\s*?$/i;
|
|
191
|
+
_columns;
|
|
192
|
+
_descending;
|
|
193
|
+
/**
|
|
194
|
+
* Constructor for `OINODbSqlOrder`.
|
|
195
|
+
*
|
|
196
|
+
* @param column_or_array single or array of columns to order on
|
|
197
|
+
* @param descending_or_array single or array of booleans if ordes is descending
|
|
198
|
+
*
|
|
199
|
+
*/
|
|
200
|
+
constructor(column_or_array, descending_or_array) {
|
|
201
|
+
index_js_1.OINOLog.debug("OINODbSqlOrder.constructor", { columns: column_or_array, directions: descending_or_array });
|
|
202
|
+
if (Array.isArray(column_or_array)) {
|
|
203
|
+
this._columns = column_or_array;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this._columns = [column_or_array];
|
|
207
|
+
}
|
|
208
|
+
if (Array.isArray(descending_or_array)) {
|
|
209
|
+
this._descending = descending_or_array;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
this._descending = [descending_or_array];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Constructor for `OINODbSqlOrder` as parser of http parameter.
|
|
217
|
+
*
|
|
218
|
+
* @param orderString string representation of ordering from HTTP-request
|
|
219
|
+
*
|
|
220
|
+
*/
|
|
221
|
+
static parse(orderString) {
|
|
222
|
+
let columns = [];
|
|
223
|
+
let directions = [];
|
|
224
|
+
const column_strings = orderString.split(',');
|
|
225
|
+
for (let i = 0; i < column_strings.length; i++) {
|
|
226
|
+
let match = OINODbSqlOrder._orderColumnRegex.exec(column_strings[i]);
|
|
227
|
+
if (match != null) {
|
|
228
|
+
columns.push(match[1]);
|
|
229
|
+
directions.push((match[2] || "DESC").toUpperCase() == "DESC");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return new OINODbSqlOrder(columns, directions);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Does filter contain any valid conditions.
|
|
236
|
+
*
|
|
237
|
+
*/
|
|
238
|
+
isEmpty() {
|
|
239
|
+
return (this._columns.length == 0);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Print order as SQL condition based on the datamodel of the API.
|
|
243
|
+
*
|
|
244
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
245
|
+
*
|
|
246
|
+
*/
|
|
247
|
+
toSql(dataModel) {
|
|
248
|
+
if (this.isEmpty()) {
|
|
249
|
+
return "";
|
|
250
|
+
}
|
|
251
|
+
// OINOLog.debug("OINODbSqlOrder.toSql", {columns:this._columns, directions:this._directions})
|
|
252
|
+
let result = "";
|
|
253
|
+
for (let i = 0; i < this._columns.length; i++) {
|
|
254
|
+
const field = dataModel.findFieldByName(this._columns[i]);
|
|
255
|
+
if (!field) {
|
|
256
|
+
index_js_1.OINOLog.error("OINODbSqlOrder.toSql: Invalid field!", { field: this._columns[i] });
|
|
257
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": OINODbSqlOrder.toSql - Invalid field '" + this._columns[i] + "'"); // invalid field name could be a security risk, stop processing
|
|
258
|
+
}
|
|
259
|
+
if (result) {
|
|
260
|
+
result += ",";
|
|
261
|
+
}
|
|
262
|
+
result += dataModel.api.db.printSqlColumnname(field.name) + " ";
|
|
263
|
+
if (this._descending[i]) {
|
|
264
|
+
result += "DESC";
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
result += "ASC";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// OINOLog.debug("OINODbSqlOrder.toSql", {result:result})
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
exports.OINODbSqlOrder = OINODbSqlOrder;
|
|
275
|
+
/**
|
|
276
|
+
* Class for limiting the number of results.
|
|
277
|
+
*
|
|
278
|
+
*/
|
|
279
|
+
class OINODbSqlLimit {
|
|
280
|
+
static _limitRegex = /^(\d+)(\spage\s)?(\d+)?$/i;
|
|
281
|
+
_limit;
|
|
282
|
+
_page;
|
|
283
|
+
/**
|
|
284
|
+
* Constructor for `OINODbSqlLimit`.
|
|
285
|
+
*
|
|
286
|
+
* @param limit maximum number of items to return
|
|
287
|
+
* @param page page number to return starting from 1
|
|
288
|
+
*
|
|
289
|
+
*/
|
|
290
|
+
constructor(limit, page = -1) {
|
|
291
|
+
this._limit = limit;
|
|
292
|
+
this._page = page;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Constructor for `OINODbSqlLimit` as parser of http parameter.
|
|
296
|
+
*
|
|
297
|
+
* @param limitString string representation of limit from HTTP-request
|
|
298
|
+
*
|
|
299
|
+
*/
|
|
300
|
+
static parse(limitString) {
|
|
301
|
+
let match = OINODbSqlLimit._limitRegex.exec(limitString);
|
|
302
|
+
if ((match != null) && (match.length == 4)) {
|
|
303
|
+
return new OINODbSqlLimit(Number.parseInt(match[1]), Number.parseInt(match[3]));
|
|
304
|
+
}
|
|
305
|
+
else if (match != null) {
|
|
306
|
+
return new OINODbSqlLimit(Number.parseInt(match[1]));
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
return new OINODbSqlLimit(-1);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Does filter contain any valid conditions.
|
|
314
|
+
*
|
|
315
|
+
*/
|
|
316
|
+
isEmpty() {
|
|
317
|
+
return (this._limit <= 0);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Print order as SQL condition based on the datamodel of the API.
|
|
321
|
+
*
|
|
322
|
+
* @param dataModel data model (and database) to use for formatting of values
|
|
323
|
+
*
|
|
324
|
+
*/
|
|
325
|
+
toSql(dataModel) {
|
|
326
|
+
if (this.isEmpty()) {
|
|
327
|
+
return "";
|
|
328
|
+
}
|
|
329
|
+
let result = this._limit.toString();
|
|
330
|
+
if (this._page > 0) {
|
|
331
|
+
result += " OFFSET " + (this._limit * (this._page - 1) + 1).toString();
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
exports.OINODbSqlLimit = OINODbSqlLimit;
|
package/dist/cjs/index.js
CHANGED
|
@@ -36,12 +36,12 @@ Object.defineProperty(exports, "OINODb", { enumerable: true, get: function () {
|
|
|
36
36
|
var OINODbDataSet_js_1 = require("./OINODbDataSet.js");
|
|
37
37
|
Object.defineProperty(exports, "OINODbDataSet", { enumerable: true, get: function () { return OINODbDataSet_js_1.OINODbDataSet; } });
|
|
38
38
|
Object.defineProperty(exports, "OINODbMemoryDataSet", { enumerable: true, get: function () { return OINODbDataSet_js_1.OINODbMemoryDataSet; } });
|
|
39
|
-
var
|
|
40
|
-
Object.defineProperty(exports, "OINODbSqlFilter", { enumerable: true, get: function () { return
|
|
41
|
-
Object.defineProperty(exports, "OINODbSqlOrder", { enumerable: true, get: function () { return
|
|
42
|
-
Object.defineProperty(exports, "OINODbSqlComparison", { enumerable: true, get: function () { return
|
|
43
|
-
Object.defineProperty(exports, "OINODbSqlLimit", { enumerable: true, get: function () { return
|
|
44
|
-
Object.defineProperty(exports, "OINODbSqlBooleanOperation", { enumerable: true, get: function () { return
|
|
39
|
+
var OINODbSqlParams_js_1 = require("./OINODbSqlParams.js");
|
|
40
|
+
Object.defineProperty(exports, "OINODbSqlFilter", { enumerable: true, get: function () { return OINODbSqlParams_js_1.OINODbSqlFilter; } });
|
|
41
|
+
Object.defineProperty(exports, "OINODbSqlOrder", { enumerable: true, get: function () { return OINODbSqlParams_js_1.OINODbSqlOrder; } });
|
|
42
|
+
Object.defineProperty(exports, "OINODbSqlComparison", { enumerable: true, get: function () { return OINODbSqlParams_js_1.OINODbSqlComparison; } });
|
|
43
|
+
Object.defineProperty(exports, "OINODbSqlLimit", { enumerable: true, get: function () { return OINODbSqlParams_js_1.OINODbSqlLimit; } });
|
|
44
|
+
Object.defineProperty(exports, "OINODbSqlBooleanOperation", { enumerable: true, get: function () { return OINODbSqlParams_js_1.OINODbSqlBooleanOperation; } });
|
|
45
45
|
var OINODbConfig_js_1 = require("./OINODbConfig.js");
|
|
46
46
|
Object.defineProperty(exports, "OINODbConfig", { enumerable: true, get: function () { return OINODbConfig_js_1.OINODbConfig; } });
|
|
47
47
|
var OINODbFactory_js_1 = require("./OINODbFactory.js");
|
package/dist/esm/OINODb.js
CHANGED
|
@@ -20,4 +20,30 @@ export class OINODb {
|
|
|
20
20
|
this._params = params;
|
|
21
21
|
this.name = params.database;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Print SQL select statement with DB specific formatting.
|
|
25
|
+
*
|
|
26
|
+
* @param tableName - The name of the table to select from.
|
|
27
|
+
* @param columnNames - The columns to be selected.
|
|
28
|
+
* @param whereCondition - The WHERE clause to filter the results.
|
|
29
|
+
* @param orderCondition - The ORDER BY clause to sort the results.
|
|
30
|
+
* @param limitCondition - The LIMIT clause to limit the number of results.
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
printSqlSelect(tableName, columnNames, whereCondition, orderCondition, limitCondition) {
|
|
34
|
+
let result = "SELECT " + columnNames + " FROM " + tableName;
|
|
35
|
+
// OINOLog.debug("OINODb.printSqlSelect", {tableName:tableName, columnNames:columnNames, whereCondition:whereCondition, orderCondition:orderCondition, limitCondition:limitCondition })
|
|
36
|
+
if (whereCondition != "") {
|
|
37
|
+
result += " WHERE " + whereCondition;
|
|
38
|
+
}
|
|
39
|
+
if (orderCondition != "") {
|
|
40
|
+
result += " ORDER BY " + orderCondition;
|
|
41
|
+
}
|
|
42
|
+
if (limitCondition != "") {
|
|
43
|
+
result += " LIMIT " + limitCondition;
|
|
44
|
+
}
|
|
45
|
+
result += ";";
|
|
46
|
+
// OINOLog.debug("OINODb.printSqlSelect", {result:result})
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
23
49
|
}
|
package/dist/esm/OINODbApi.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
4
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
|
-
import { OINODbDataModel, OINOStringDataField, OINO_ERROR_PREFIX, OINODbModelSet, OINOBenchmark,
|
|
6
|
+
import { OINODbDataModel, OINOStringDataField, OINO_ERROR_PREFIX, OINODbModelSet, OINOBenchmark, OINODbConfig, OINOHtmlTemplate, OINONumberDataField } from "./index.js";
|
|
7
7
|
import { OINOResult } from "@oino-ts/types";
|
|
8
8
|
import { OINOHashid } from "@oino-ts/hashid";
|
|
9
|
+
import { OINOParser } from "@oino-ts/types";
|
|
9
10
|
const API_EMPTY_PARAMS = { sqlParams: {} };
|
|
10
11
|
/**
|
|
11
12
|
* OINO API request result object with returned data and/or http status code/message and
|
|
@@ -35,7 +36,7 @@ export class OINODbApiResult extends OINOResult {
|
|
|
35
36
|
* @param headers Headers to include in the response
|
|
36
37
|
*
|
|
37
38
|
*/
|
|
38
|
-
async
|
|
39
|
+
async getResponse(headers = {}) {
|
|
39
40
|
let response = null;
|
|
40
41
|
if (this.success && this.data) {
|
|
41
42
|
const body = await this.data.writeString(this.params.responseType);
|
|
@@ -85,11 +86,13 @@ export class OINODbHtmlTemplate extends OINOHtmlTemplate {
|
|
|
85
86
|
for (let i = 0; i < datamodel.fields.length; i++) {
|
|
86
87
|
const f = datamodel.fields[i];
|
|
87
88
|
let value = f.serializeCell(row[i]);
|
|
88
|
-
if (f.fieldParams.isPrimaryKey) {
|
|
89
|
+
if (f.fieldParams.isPrimaryKey || f.fieldParams.isForeignKey) {
|
|
89
90
|
if (value && (f instanceof OINONumberDataField) && (datamodel.api.hashid)) {
|
|
90
91
|
value = datamodel.api.hashid.encode(value, f.name + " " + row_id_seed);
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
+
if (f.fieldParams.isPrimaryKey) {
|
|
94
|
+
primary_key_values.push(value || "");
|
|
95
|
+
}
|
|
93
96
|
}
|
|
94
97
|
// OINOLog.debug("renderFromDbData replace field value", {field:f.name, value:value })
|
|
95
98
|
this.setVariableFromValue(f.name, value || "");
|
|
@@ -177,9 +180,10 @@ export class OINODbApi {
|
|
|
177
180
|
//logDebug("OINODbApi.validateHttpValues", {result:result})
|
|
178
181
|
}
|
|
179
182
|
async _doGet(result, id, params) {
|
|
180
|
-
|
|
181
|
-
// OINOLog.debug("OINODbApi.doGet sql", {sql:sql})
|
|
183
|
+
let sql = "";
|
|
182
184
|
try {
|
|
185
|
+
sql = this.datamodel.printSqlSelect(id, params.sqlParams || {});
|
|
186
|
+
// OINOLog.debug("OINODbApi.doGet sql", {sql:sql})
|
|
183
187
|
const sql_res = await this.db.sqlSelect(sql);
|
|
184
188
|
// OINOLog.debug("OINODbApi.doGet sql_res", {sql_res:sql_res})
|
|
185
189
|
if (sql_res.hasErrors()) {
|
|
@@ -282,14 +286,16 @@ export class OINODbApi {
|
|
|
282
286
|
let result = new OINODbApiResult(params);
|
|
283
287
|
let rows = [];
|
|
284
288
|
if ((method == "POST") || (method == "PUT")) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
289
|
+
try {
|
|
290
|
+
if (Array.isArray(body)) {
|
|
291
|
+
rows = body;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
rows = OINOParser.createRows(this.datamodel, body, params);
|
|
295
|
+
}
|
|
290
296
|
}
|
|
291
|
-
|
|
292
|
-
|
|
297
|
+
catch (e) {
|
|
298
|
+
result.setError(400, "Invalid data: " + e.message, "DoRequest");
|
|
293
299
|
}
|
|
294
300
|
// OINOLog.debug("OINODbApi.doRequest - OINODataRow rows", {rows:rows})
|
|
295
301
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
4
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
|
+
import { OINOLog, OINO_ERROR_PREFIX } from "./index.js";
|
|
6
7
|
/**
|
|
7
8
|
* Base class for a column of data responsible for appropriatelly serializing/deserializing the data.
|
|
8
9
|
*
|
|
@@ -51,6 +52,9 @@ export class OINODbDataField {
|
|
|
51
52
|
if (this.fieldParams.isPrimaryKey) {
|
|
52
53
|
params += "PK ";
|
|
53
54
|
}
|
|
55
|
+
if (this.fieldParams.isForeignKey) {
|
|
56
|
+
params += "FK ";
|
|
57
|
+
}
|
|
54
58
|
if (this.fieldParams.isAutoInc) {
|
|
55
59
|
params += "AUTOINC ";
|
|
56
60
|
}
|
|
@@ -236,7 +240,12 @@ export class OINONumberDataField extends OINODbDataField {
|
|
|
236
240
|
return null;
|
|
237
241
|
}
|
|
238
242
|
else {
|
|
239
|
-
|
|
243
|
+
const result = parseFloat(value);
|
|
244
|
+
if (isNaN(result)) {
|
|
245
|
+
OINOLog.error("OINODbSqlFilter.toSql: Invalid value!", { value: value });
|
|
246
|
+
throw new Error(OINO_ERROR_PREFIX + ": OINONumberDataField.deserializeCell - Invalid value '" + value + "'"); // incorrectly formatted data could be a security risk, abort processing
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
240
249
|
}
|
|
241
250
|
}
|
|
242
251
|
}
|
|
@@ -284,7 +293,7 @@ export class OINOBlobDataField extends OINODbDataField {
|
|
|
284
293
|
*/
|
|
285
294
|
deserializeCell(value) {
|
|
286
295
|
if (value == null) {
|
|
287
|
-
return
|
|
296
|
+
return Buffer.alloc(0);
|
|
288
297
|
}
|
|
289
298
|
else {
|
|
290
299
|
return Buffer.from(value, 'base64'); // Blob-field data is base64 encoded and converted internally to UInt8Array / Buffer
|