@oino-ts/db 0.13.2 → 0.14.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/dist/cjs/OINODbDataModel.js +5 -5
- package/dist/cjs/OINODbModelSet.js +5 -2
- package/dist/cjs/OINODbSqlParams.js +75 -6
- package/dist/esm/OINODbDataModel.js +5 -5
- package/dist/esm/OINODbModelSet.js +5 -2
- package/dist/esm/OINODbSqlParams.js +74 -5
- package/dist/types/OINODbApi.d.ts +0 -2
- package/dist/types/OINODbDataModel.d.ts +1 -1
- package/dist/types/OINODbModelSet.d.ts +2 -1
- package/dist/types/OINODbParser.d.ts +0 -2
- package/dist/types/OINODbSqlParams.d.ts +35 -1
- package/dist/types/index.d.ts +0 -2
- package/package.json +3 -3
- package/src/OINODbApi.test.ts +15 -3
- package/src/OINODbDataModel.ts +5 -5
- package/src/OINODbModelSet.ts +6 -3
- package/src/OINODbSqlParams.ts +79 -9
|
@@ -12,7 +12,7 @@ const index_js_1 = require("./index.js");
|
|
|
12
12
|
*
|
|
13
13
|
*/
|
|
14
14
|
class OINODbDataModel {
|
|
15
|
-
|
|
15
|
+
_fieldIndexLookup;
|
|
16
16
|
/** Database refererence of the table */
|
|
17
17
|
api;
|
|
18
18
|
/** Field refererences of the API */
|
|
@@ -25,7 +25,7 @@ class OINODbDataModel {
|
|
|
25
25
|
*
|
|
26
26
|
*/
|
|
27
27
|
constructor(api) {
|
|
28
|
-
this.
|
|
28
|
+
this._fieldIndexLookup = {};
|
|
29
29
|
this.api = api;
|
|
30
30
|
this.fields = [];
|
|
31
31
|
}
|
|
@@ -116,7 +116,7 @@ class OINODbDataModel {
|
|
|
116
116
|
*/
|
|
117
117
|
addField(field) {
|
|
118
118
|
this.fields.push(field);
|
|
119
|
-
this.
|
|
119
|
+
this._fieldIndexLookup[field.name] = this.fields.length - 1;
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
122
|
* Find a field of a given name if any.
|
|
@@ -125,7 +125,7 @@ class OINODbDataModel {
|
|
|
125
125
|
*
|
|
126
126
|
*/
|
|
127
127
|
findFieldByName(name) {
|
|
128
|
-
const i = this.
|
|
128
|
+
const i = this._fieldIndexLookup[name];
|
|
129
129
|
if (i >= 0) {
|
|
130
130
|
return this.fields[i];
|
|
131
131
|
}
|
|
@@ -140,7 +140,7 @@ class OINODbDataModel {
|
|
|
140
140
|
*
|
|
141
141
|
*/
|
|
142
142
|
findFieldIndexByName(name) {
|
|
143
|
-
const i = this.
|
|
143
|
+
const i = this._fieldIndexLookup[name];
|
|
144
144
|
if (i >= 0) {
|
|
145
145
|
return i;
|
|
146
146
|
}
|
|
@@ -308,13 +308,16 @@ class OINODbModelSet {
|
|
|
308
308
|
/**
|
|
309
309
|
* Export all rows as a record with OINOId as key and object with row cells as values.
|
|
310
310
|
*
|
|
311
|
+
* @param idFieldName optional field name to use as key instead of OINOId
|
|
311
312
|
*/
|
|
312
|
-
async exportAsRecord() {
|
|
313
|
+
async exportAsRecord(idFieldName) {
|
|
313
314
|
const result = {};
|
|
315
|
+
const row_id_field = idFieldName || index_js_1.OINODbConfig.OINODB_ID_FIELD;
|
|
314
316
|
while (!this.dataset.isEof()) {
|
|
315
317
|
const row_data = this.dataset.getRow();
|
|
316
318
|
const row_export = this._exportRow(row_data);
|
|
317
|
-
|
|
319
|
+
const row_id = row_export[row_id_field];
|
|
320
|
+
result[row_id] = row_export;
|
|
318
321
|
await this.dataset.next();
|
|
319
322
|
}
|
|
320
323
|
return result;
|
|
@@ -5,7 +5,7 @@
|
|
|
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.OINODbSqlSelect = exports.OINODbSqlAggregate = exports.OINODbSqlAggregateFunctions = exports.OINODbSqlLimit = exports.OINODbSqlOrder = exports.OINODbSqlFilter = exports.OINODbSqlComparison = exports.OINODbSqlBooleanOperation = void 0;
|
|
8
|
+
exports.OINODbSqlSelect = exports.OINODbSqlAggregate = exports.OINODbSqlAggregateFunctions = exports.OINODbSqlLimit = exports.OINODbSqlOrder = exports.OINODbSqlFilter = exports.OINODbSqlNullCheck = exports.OINODbSqlComparison = exports.OINODbSqlBooleanOperation = void 0;
|
|
9
9
|
const index_js_1 = require("./index.js");
|
|
10
10
|
const OINO_FIELD_NAME_CHARS = "\\w\\s\\-\\_\\#\\¤";
|
|
11
11
|
/**
|
|
@@ -27,10 +27,20 @@ var OINODbSqlComparison;
|
|
|
27
27
|
OINODbSqlComparison["lt"] = "lt";
|
|
28
28
|
OINODbSqlComparison["le"] = "le";
|
|
29
29
|
OINODbSqlComparison["eq"] = "eq";
|
|
30
|
+
OINODbSqlComparison["ne"] = "ne";
|
|
30
31
|
OINODbSqlComparison["ge"] = "ge";
|
|
31
32
|
OINODbSqlComparison["gt"] = "gt";
|
|
32
33
|
OINODbSqlComparison["like"] = "like";
|
|
33
34
|
})(OINODbSqlComparison || (exports.OINODbSqlComparison = OINODbSqlComparison = {}));
|
|
35
|
+
/**
|
|
36
|
+
* Supported logical conjunctions in filter predicates.
|
|
37
|
+
* @enum
|
|
38
|
+
*/
|
|
39
|
+
var OINODbSqlNullCheck;
|
|
40
|
+
(function (OINODbSqlNullCheck) {
|
|
41
|
+
OINODbSqlNullCheck["isnull"] = "isnull";
|
|
42
|
+
OINODbSqlNullCheck["isnotnull"] = "isnotnull";
|
|
43
|
+
})(OINODbSqlNullCheck || (exports.OINODbSqlNullCheck = OINODbSqlNullCheck = {}));
|
|
34
44
|
/**
|
|
35
45
|
* Class for recursively parsing of filters and printing them as SQL conditions.
|
|
36
46
|
* Supports three types of statements
|
|
@@ -42,8 +52,9 @@ var OINODbSqlComparison;
|
|
|
42
52
|
*/
|
|
43
53
|
class OINODbSqlFilter {
|
|
44
54
|
static _booleanOperationRegex = /^\s?\-(and|or)\s?$/i;
|
|
45
|
-
static _negationRegex = /^-(not
|
|
46
|
-
static _filterComparisonRegex = /^\(([^'"\(\)]+)\)\s?\-(lt|le|eq|ge|gt|like)\s?\(([^'"\(\)]+)\)$/i;
|
|
55
|
+
static _negationRegex = /^-(not)\((.+)\)$/i;
|
|
56
|
+
static _filterComparisonRegex = /^\(([^'"\(\)]+)\)\s?\-(lt|le|eq|ne|ge|gt|like)\s?\(([^'"\(\)]+)\)$/i;
|
|
57
|
+
static _filterNullCheckRegex = /^-(isnull|isnotnull)\((.+)\)$/i;
|
|
47
58
|
_leftSide;
|
|
48
59
|
_rightSide;
|
|
49
60
|
_operator;
|
|
@@ -57,6 +68,7 @@ class OINODbSqlFilter {
|
|
|
57
68
|
if (!(((operation === null) && (leftSide == "") && (rightSide == "")) ||
|
|
58
69
|
((operation !== null) && (Object.values(OINODbSqlComparison).includes(operation)) && (typeof (leftSide) == "string") && (leftSide != "") && (typeof (rightSide) == "string") && (rightSide != "")) ||
|
|
59
70
|
((operation == OINODbSqlBooleanOperation.not) && (leftSide == "") && (rightSide instanceof OINODbSqlFilter)) ||
|
|
71
|
+
(((operation == OINODbSqlNullCheck.isnull) || (operation == OINODbSqlNullCheck.isnotnull)) && (typeof (leftSide) == "string") && (rightSide == "")) ||
|
|
60
72
|
(((operation == OINODbSqlBooleanOperation.and) || (operation == OINODbSqlBooleanOperation.or)) && (leftSide instanceof OINODbSqlFilter) && (rightSide instanceof OINODbSqlFilter)))) {
|
|
61
73
|
index_js_1.OINOLog.error("@oino-ts/db", "OINODbSqlFilter", "constructor", "Unsupported OINODbSqlFilter format", { leftSide: leftSide, operation: operation, rightSide: rightSide });
|
|
62
74
|
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": Unsupported OINODbSqlFilter format!");
|
|
@@ -72,6 +84,7 @@ class OINODbSqlFilter {
|
|
|
72
84
|
* - comparison: (field)-lt|le|eq|ge|gt|like(value)
|
|
73
85
|
* - negation: -not(filter)
|
|
74
86
|
* - conjunction/disjunction: (filter)-and|or(filter)
|
|
87
|
+
* - null check: -isnull(field) or -isnotnull(field)
|
|
75
88
|
*
|
|
76
89
|
* @param filterString string representation of filter from HTTP-request
|
|
77
90
|
*
|
|
@@ -82,7 +95,7 @@ class OINODbSqlFilter {
|
|
|
82
95
|
}
|
|
83
96
|
else {
|
|
84
97
|
let match = OINODbSqlFilter._filterComparisonRegex.exec(filterString);
|
|
85
|
-
if (match != null) {
|
|
98
|
+
if ((match != null) && (match.length == 4)) {
|
|
86
99
|
return new OINODbSqlFilter(match[1], match[2].toLowerCase(), match[3]);
|
|
87
100
|
}
|
|
88
101
|
else {
|
|
@@ -96,8 +109,14 @@ class OINODbSqlFilter {
|
|
|
96
109
|
return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1), OINODbSqlFilter.parse(boolean_parts[2]));
|
|
97
110
|
}
|
|
98
111
|
else {
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
let match = OINODbSqlFilter._filterNullCheckRegex.exec(filterString);
|
|
113
|
+
if ((match != null)) {
|
|
114
|
+
return new OINODbSqlFilter(match[2], match[1].toLowerCase(), "");
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
index_js_1.OINOLog.error("@oino-ts/db", "OINODbSqlFilter", "constructor", "Invalid filter", { filterString: filterString });
|
|
118
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'"); // invalid filter could be a security risk, stop processing
|
|
119
|
+
}
|
|
101
120
|
}
|
|
102
121
|
}
|
|
103
122
|
}
|
|
@@ -125,6 +144,50 @@ class OINODbSqlFilter {
|
|
|
125
144
|
return undefined;
|
|
126
145
|
}
|
|
127
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Combine two filters with an AND operation.
|
|
149
|
+
*
|
|
150
|
+
* @param leftSide left side filter
|
|
151
|
+
* @param rightSide right side filter
|
|
152
|
+
*
|
|
153
|
+
*/
|
|
154
|
+
static and(leftSide, rightSide) {
|
|
155
|
+
if ((leftSide) && (!leftSide.isEmpty()) && (rightSide) && (!rightSide.isEmpty())) {
|
|
156
|
+
return new OINODbSqlFilter(leftSide, OINODbSqlBooleanOperation.and, rightSide);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Combine two filters with an OR operation.
|
|
164
|
+
*
|
|
165
|
+
* @param leftSide left side filter
|
|
166
|
+
* @param rightSide right side filter
|
|
167
|
+
*
|
|
168
|
+
*/
|
|
169
|
+
static or(leftSide, rightSide) {
|
|
170
|
+
if ((leftSide) && (!leftSide.isEmpty()) && (rightSide) && (!rightSide.isEmpty())) {
|
|
171
|
+
return new OINODbSqlFilter(leftSide, OINODbSqlBooleanOperation.or, rightSide);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Negate a filter with a NOT operation.
|
|
179
|
+
*
|
|
180
|
+
* @param leftSide left side filter
|
|
181
|
+
*
|
|
182
|
+
*/
|
|
183
|
+
static not(leftSide) {
|
|
184
|
+
if ((leftSide) && (!leftSide.isEmpty())) {
|
|
185
|
+
return new OINODbSqlFilter(leftSide, OINODbSqlBooleanOperation.not, "");
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
128
191
|
_operatorToSql() {
|
|
129
192
|
switch (this._operator) {
|
|
130
193
|
case "and": return " AND ";
|
|
@@ -133,9 +196,12 @@ class OINODbSqlFilter {
|
|
|
133
196
|
case "lt": return " < ";
|
|
134
197
|
case "le": return " <= ";
|
|
135
198
|
case "eq": return " = ";
|
|
199
|
+
case "ne": return " != ";
|
|
136
200
|
case "ge": return " >= ";
|
|
137
201
|
case "gt": return " > ";
|
|
138
202
|
case "like": return " LIKE ";
|
|
203
|
+
case "isnull": return " IS NULL";
|
|
204
|
+
case "isnotnull": return " IS NOT NULL";
|
|
139
205
|
}
|
|
140
206
|
return " ";
|
|
141
207
|
}
|
|
@@ -173,6 +239,9 @@ class OINODbSqlFilter {
|
|
|
173
239
|
if (this._rightSide instanceof OINODbSqlFilter) {
|
|
174
240
|
result += this._rightSide.toSql(dataModel);
|
|
175
241
|
}
|
|
242
|
+
else if (this._operator == OINODbSqlNullCheck.isnull || this._operator == OINODbSqlNullCheck.isnotnull) {
|
|
243
|
+
// nothing to do, IS NULL and IS NOT NULL do not have a right side
|
|
244
|
+
}
|
|
176
245
|
else {
|
|
177
246
|
const value = field.deserializeCell(this._rightSide);
|
|
178
247
|
if ((value == null) || (value === "")) {
|
|
@@ -9,7 +9,7 @@ import { OINO_ERROR_PREFIX, OINODbConfig, OINONumberDataField, OINODB_UNDEFINED
|
|
|
9
9
|
*
|
|
10
10
|
*/
|
|
11
11
|
export class OINODbDataModel {
|
|
12
|
-
|
|
12
|
+
_fieldIndexLookup;
|
|
13
13
|
/** Database refererence of the table */
|
|
14
14
|
api;
|
|
15
15
|
/** Field refererences of the API */
|
|
@@ -22,7 +22,7 @@ export class OINODbDataModel {
|
|
|
22
22
|
*
|
|
23
23
|
*/
|
|
24
24
|
constructor(api) {
|
|
25
|
-
this.
|
|
25
|
+
this._fieldIndexLookup = {};
|
|
26
26
|
this.api = api;
|
|
27
27
|
this.fields = [];
|
|
28
28
|
}
|
|
@@ -113,7 +113,7 @@ export class OINODbDataModel {
|
|
|
113
113
|
*/
|
|
114
114
|
addField(field) {
|
|
115
115
|
this.fields.push(field);
|
|
116
|
-
this.
|
|
116
|
+
this._fieldIndexLookup[field.name] = this.fields.length - 1;
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
119
|
* Find a field of a given name if any.
|
|
@@ -122,7 +122,7 @@ export class OINODbDataModel {
|
|
|
122
122
|
*
|
|
123
123
|
*/
|
|
124
124
|
findFieldByName(name) {
|
|
125
|
-
const i = this.
|
|
125
|
+
const i = this._fieldIndexLookup[name];
|
|
126
126
|
if (i >= 0) {
|
|
127
127
|
return this.fields[i];
|
|
128
128
|
}
|
|
@@ -137,7 +137,7 @@ export class OINODbDataModel {
|
|
|
137
137
|
*
|
|
138
138
|
*/
|
|
139
139
|
findFieldIndexByName(name) {
|
|
140
|
-
const i = this.
|
|
140
|
+
const i = this._fieldIndexLookup[name];
|
|
141
141
|
if (i >= 0) {
|
|
142
142
|
return i;
|
|
143
143
|
}
|
|
@@ -305,13 +305,16 @@ export class OINODbModelSet {
|
|
|
305
305
|
/**
|
|
306
306
|
* Export all rows as a record with OINOId as key and object with row cells as values.
|
|
307
307
|
*
|
|
308
|
+
* @param idFieldName optional field name to use as key instead of OINOId
|
|
308
309
|
*/
|
|
309
|
-
async exportAsRecord() {
|
|
310
|
+
async exportAsRecord(idFieldName) {
|
|
310
311
|
const result = {};
|
|
312
|
+
const row_id_field = idFieldName || OINODbConfig.OINODB_ID_FIELD;
|
|
311
313
|
while (!this.dataset.isEof()) {
|
|
312
314
|
const row_data = this.dataset.getRow();
|
|
313
315
|
const row_export = this._exportRow(row_data);
|
|
314
|
-
|
|
316
|
+
const row_id = row_export[row_id_field];
|
|
317
|
+
result[row_id] = row_export;
|
|
315
318
|
await this.dataset.next();
|
|
316
319
|
}
|
|
317
320
|
return result;
|
|
@@ -24,10 +24,20 @@ export var OINODbSqlComparison;
|
|
|
24
24
|
OINODbSqlComparison["lt"] = "lt";
|
|
25
25
|
OINODbSqlComparison["le"] = "le";
|
|
26
26
|
OINODbSqlComparison["eq"] = "eq";
|
|
27
|
+
OINODbSqlComparison["ne"] = "ne";
|
|
27
28
|
OINODbSqlComparison["ge"] = "ge";
|
|
28
29
|
OINODbSqlComparison["gt"] = "gt";
|
|
29
30
|
OINODbSqlComparison["like"] = "like";
|
|
30
31
|
})(OINODbSqlComparison || (OINODbSqlComparison = {}));
|
|
32
|
+
/**
|
|
33
|
+
* Supported logical conjunctions in filter predicates.
|
|
34
|
+
* @enum
|
|
35
|
+
*/
|
|
36
|
+
export var OINODbSqlNullCheck;
|
|
37
|
+
(function (OINODbSqlNullCheck) {
|
|
38
|
+
OINODbSqlNullCheck["isnull"] = "isnull";
|
|
39
|
+
OINODbSqlNullCheck["isnotnull"] = "isnotnull";
|
|
40
|
+
})(OINODbSqlNullCheck || (OINODbSqlNullCheck = {}));
|
|
31
41
|
/**
|
|
32
42
|
* Class for recursively parsing of filters and printing them as SQL conditions.
|
|
33
43
|
* Supports three types of statements
|
|
@@ -39,8 +49,9 @@ export var OINODbSqlComparison;
|
|
|
39
49
|
*/
|
|
40
50
|
export class OINODbSqlFilter {
|
|
41
51
|
static _booleanOperationRegex = /^\s?\-(and|or)\s?$/i;
|
|
42
|
-
static _negationRegex = /^-(not
|
|
43
|
-
static _filterComparisonRegex = /^\(([^'"\(\)]+)\)\s?\-(lt|le|eq|ge|gt|like)\s?\(([^'"\(\)]+)\)$/i;
|
|
52
|
+
static _negationRegex = /^-(not)\((.+)\)$/i;
|
|
53
|
+
static _filterComparisonRegex = /^\(([^'"\(\)]+)\)\s?\-(lt|le|eq|ne|ge|gt|like)\s?\(([^'"\(\)]+)\)$/i;
|
|
54
|
+
static _filterNullCheckRegex = /^-(isnull|isnotnull)\((.+)\)$/i;
|
|
44
55
|
_leftSide;
|
|
45
56
|
_rightSide;
|
|
46
57
|
_operator;
|
|
@@ -54,6 +65,7 @@ export class OINODbSqlFilter {
|
|
|
54
65
|
if (!(((operation === null) && (leftSide == "") && (rightSide == "")) ||
|
|
55
66
|
((operation !== null) && (Object.values(OINODbSqlComparison).includes(operation)) && (typeof (leftSide) == "string") && (leftSide != "") && (typeof (rightSide) == "string") && (rightSide != "")) ||
|
|
56
67
|
((operation == OINODbSqlBooleanOperation.not) && (leftSide == "") && (rightSide instanceof OINODbSqlFilter)) ||
|
|
68
|
+
(((operation == OINODbSqlNullCheck.isnull) || (operation == OINODbSqlNullCheck.isnotnull)) && (typeof (leftSide) == "string") && (rightSide == "")) ||
|
|
57
69
|
(((operation == OINODbSqlBooleanOperation.and) || (operation == OINODbSqlBooleanOperation.or)) && (leftSide instanceof OINODbSqlFilter) && (rightSide instanceof OINODbSqlFilter)))) {
|
|
58
70
|
OINOLog.error("@oino-ts/db", "OINODbSqlFilter", "constructor", "Unsupported OINODbSqlFilter format", { leftSide: leftSide, operation: operation, rightSide: rightSide });
|
|
59
71
|
throw new Error(OINO_ERROR_PREFIX + ": Unsupported OINODbSqlFilter format!");
|
|
@@ -69,6 +81,7 @@ export class OINODbSqlFilter {
|
|
|
69
81
|
* - comparison: (field)-lt|le|eq|ge|gt|like(value)
|
|
70
82
|
* - negation: -not(filter)
|
|
71
83
|
* - conjunction/disjunction: (filter)-and|or(filter)
|
|
84
|
+
* - null check: -isnull(field) or -isnotnull(field)
|
|
72
85
|
*
|
|
73
86
|
* @param filterString string representation of filter from HTTP-request
|
|
74
87
|
*
|
|
@@ -79,7 +92,7 @@ export class OINODbSqlFilter {
|
|
|
79
92
|
}
|
|
80
93
|
else {
|
|
81
94
|
let match = OINODbSqlFilter._filterComparisonRegex.exec(filterString);
|
|
82
|
-
if (match != null) {
|
|
95
|
+
if ((match != null) && (match.length == 4)) {
|
|
83
96
|
return new OINODbSqlFilter(match[1], match[2].toLowerCase(), match[3]);
|
|
84
97
|
}
|
|
85
98
|
else {
|
|
@@ -93,8 +106,14 @@ export class OINODbSqlFilter {
|
|
|
93
106
|
return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1), OINODbSqlFilter.parse(boolean_parts[2]));
|
|
94
107
|
}
|
|
95
108
|
else {
|
|
96
|
-
|
|
97
|
-
|
|
109
|
+
let match = OINODbSqlFilter._filterNullCheckRegex.exec(filterString);
|
|
110
|
+
if ((match != null)) {
|
|
111
|
+
return new OINODbSqlFilter(match[2], match[1].toLowerCase(), "");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
OINOLog.error("@oino-ts/db", "OINODbSqlFilter", "constructor", "Invalid filter", { filterString: filterString });
|
|
115
|
+
throw new Error(OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'"); // invalid filter could be a security risk, stop processing
|
|
116
|
+
}
|
|
98
117
|
}
|
|
99
118
|
}
|
|
100
119
|
}
|
|
@@ -122,6 +141,50 @@ export class OINODbSqlFilter {
|
|
|
122
141
|
return undefined;
|
|
123
142
|
}
|
|
124
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Combine two filters with an AND operation.
|
|
146
|
+
*
|
|
147
|
+
* @param leftSide left side filter
|
|
148
|
+
* @param rightSide right side filter
|
|
149
|
+
*
|
|
150
|
+
*/
|
|
151
|
+
static and(leftSide, rightSide) {
|
|
152
|
+
if ((leftSide) && (!leftSide.isEmpty()) && (rightSide) && (!rightSide.isEmpty())) {
|
|
153
|
+
return new OINODbSqlFilter(leftSide, OINODbSqlBooleanOperation.and, rightSide);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Combine two filters with an OR operation.
|
|
161
|
+
*
|
|
162
|
+
* @param leftSide left side filter
|
|
163
|
+
* @param rightSide right side filter
|
|
164
|
+
*
|
|
165
|
+
*/
|
|
166
|
+
static or(leftSide, rightSide) {
|
|
167
|
+
if ((leftSide) && (!leftSide.isEmpty()) && (rightSide) && (!rightSide.isEmpty())) {
|
|
168
|
+
return new OINODbSqlFilter(leftSide, OINODbSqlBooleanOperation.or, rightSide);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Negate a filter with a NOT operation.
|
|
176
|
+
*
|
|
177
|
+
* @param leftSide left side filter
|
|
178
|
+
*
|
|
179
|
+
*/
|
|
180
|
+
static not(leftSide) {
|
|
181
|
+
if ((leftSide) && (!leftSide.isEmpty())) {
|
|
182
|
+
return new OINODbSqlFilter(leftSide, OINODbSqlBooleanOperation.not, "");
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
125
188
|
_operatorToSql() {
|
|
126
189
|
switch (this._operator) {
|
|
127
190
|
case "and": return " AND ";
|
|
@@ -130,9 +193,12 @@ export class OINODbSqlFilter {
|
|
|
130
193
|
case "lt": return " < ";
|
|
131
194
|
case "le": return " <= ";
|
|
132
195
|
case "eq": return " = ";
|
|
196
|
+
case "ne": return " != ";
|
|
133
197
|
case "ge": return " >= ";
|
|
134
198
|
case "gt": return " > ";
|
|
135
199
|
case "like": return " LIKE ";
|
|
200
|
+
case "isnull": return " IS NULL";
|
|
201
|
+
case "isnotnull": return " IS NOT NULL";
|
|
136
202
|
}
|
|
137
203
|
return " ";
|
|
138
204
|
}
|
|
@@ -170,6 +236,9 @@ export class OINODbSqlFilter {
|
|
|
170
236
|
if (this._rightSide instanceof OINODbSqlFilter) {
|
|
171
237
|
result += this._rightSide.toSql(dataModel);
|
|
172
238
|
}
|
|
239
|
+
else if (this._operator == OINODbSqlNullCheck.isnull || this._operator == OINODbSqlNullCheck.isnotnull) {
|
|
240
|
+
// nothing to do, IS NULL and IS NOT NULL do not have a right side
|
|
241
|
+
}
|
|
173
242
|
else {
|
|
174
243
|
const value = field.deserializeCell(this._rightSide);
|
|
175
244
|
if ((value == null) || (value === "")) {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
1
|
import { OINODbApiParams, OINODb, OINODbDataModel, OINODataRow, OINODbModelSet, OINODbApiRequestParams, OINOHttpResult, OINOHtmlTemplate } from "./index.js";
|
|
4
2
|
import { OINOResult } from "@oino-ts/common";
|
|
5
3
|
import { OINOHashid } from "@oino-ts/hashid";
|
|
@@ -4,7 +4,7 @@ import { OINODbDataField, OINODbApi, OINODataRow, OINODbDataFieldFilter, OINODbS
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
export declare class OINODbDataModel {
|
|
7
|
-
private
|
|
7
|
+
private _fieldIndexLookup;
|
|
8
8
|
/** Database refererence of the table */
|
|
9
9
|
readonly api: OINODbApi;
|
|
10
10
|
/** Field refererences of the API */
|
|
@@ -55,6 +55,7 @@ export declare class OINODbModelSet {
|
|
|
55
55
|
/**
|
|
56
56
|
* Export all rows as a record with OINOId as key and object with row cells as values.
|
|
57
57
|
*
|
|
58
|
+
* @param idFieldName optional field name to use as key instead of OINOId
|
|
58
59
|
*/
|
|
59
|
-
exportAsRecord(): Promise<Record<string, any>>;
|
|
60
|
+
exportAsRecord(idFieldName?: string): Promise<Record<string, any>>;
|
|
60
61
|
}
|
|
@@ -16,10 +16,19 @@ export declare enum OINODbSqlComparison {
|
|
|
16
16
|
lt = "lt",
|
|
17
17
|
le = "le",
|
|
18
18
|
eq = "eq",
|
|
19
|
+
ne = "ne",
|
|
19
20
|
ge = "ge",
|
|
20
21
|
gt = "gt",
|
|
21
22
|
like = "like"
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Supported logical conjunctions in filter predicates.
|
|
26
|
+
* @enum
|
|
27
|
+
*/
|
|
28
|
+
export declare enum OINODbSqlNullCheck {
|
|
29
|
+
isnull = "isnull",
|
|
30
|
+
isnotnull = "isnotnull"
|
|
31
|
+
}
|
|
23
32
|
/**
|
|
24
33
|
* Class for recursively parsing of filters and printing them as SQL conditions.
|
|
25
34
|
* Supports three types of statements
|
|
@@ -33,6 +42,7 @@ export declare class OINODbSqlFilter {
|
|
|
33
42
|
private static _booleanOperationRegex;
|
|
34
43
|
private static _negationRegex;
|
|
35
44
|
private static _filterComparisonRegex;
|
|
45
|
+
private static _filterNullCheckRegex;
|
|
36
46
|
private _leftSide;
|
|
37
47
|
private _rightSide;
|
|
38
48
|
private _operator;
|
|
@@ -42,7 +52,7 @@ export declare class OINODbSqlFilter {
|
|
|
42
52
|
* @param operation operation of the filter, either `OINODbSqlComparison` or `OINODbSqlBooleanOperation`
|
|
43
53
|
* @param rightSide right side of the filter, either another filter or a value
|
|
44
54
|
*/
|
|
45
|
-
constructor(leftSide: OINODbSqlFilter | string, operation: OINODbSqlComparison | OINODbSqlBooleanOperation | null, rightSide: OINODbSqlFilter | string);
|
|
55
|
+
constructor(leftSide: OINODbSqlFilter | string, operation: OINODbSqlComparison | OINODbSqlBooleanOperation | OINODbSqlNullCheck | null, rightSide: OINODbSqlFilter | string);
|
|
46
56
|
/**
|
|
47
57
|
* Constructor for `OINODbSqlFilter` as parser of http parameter.
|
|
48
58
|
*
|
|
@@ -50,6 +60,7 @@ export declare class OINODbSqlFilter {
|
|
|
50
60
|
* - comparison: (field)-lt|le|eq|ge|gt|like(value)
|
|
51
61
|
* - negation: -not(filter)
|
|
52
62
|
* - conjunction/disjunction: (filter)-and|or(filter)
|
|
63
|
+
* - null check: -isnull(field) or -isnotnull(field)
|
|
53
64
|
*
|
|
54
65
|
* @param filterString string representation of filter from HTTP-request
|
|
55
66
|
*
|
|
@@ -64,6 +75,29 @@ export declare class OINODbSqlFilter {
|
|
|
64
75
|
*
|
|
65
76
|
*/
|
|
66
77
|
static combine(leftSide: OINODbSqlFilter | undefined, operation: OINODbSqlBooleanOperation, rightSide: OINODbSqlFilter | undefined): OINODbSqlFilter | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Combine two filters with an AND operation.
|
|
80
|
+
*
|
|
81
|
+
* @param leftSide left side filter
|
|
82
|
+
* @param rightSide right side filter
|
|
83
|
+
*
|
|
84
|
+
*/
|
|
85
|
+
static and(leftSide: OINODbSqlFilter, rightSide: OINODbSqlFilter): OINODbSqlFilter | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Combine two filters with an OR operation.
|
|
88
|
+
*
|
|
89
|
+
* @param leftSide left side filter
|
|
90
|
+
* @param rightSide right side filter
|
|
91
|
+
*
|
|
92
|
+
*/
|
|
93
|
+
static or(leftSide: OINODbSqlFilter, rightSide: OINODbSqlFilter): OINODbSqlFilter | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Negate a filter with a NOT operation.
|
|
96
|
+
*
|
|
97
|
+
* @param leftSide left side filter
|
|
98
|
+
*
|
|
99
|
+
*/
|
|
100
|
+
static not(leftSide: OINODbSqlFilter): OINODbSqlFilter | undefined;
|
|
67
101
|
private _operatorToSql;
|
|
68
102
|
/**
|
|
69
103
|
* Does filter contain any valid conditions.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
1
|
import { OINOContentType } from "@oino-ts/common";
|
|
4
2
|
export { OINOContentType };
|
|
5
3
|
export { OINO_ERROR_PREFIX, OINO_WARNING_PREFIX, OINO_INFO_PREFIX, OINO_DEBUG_PREFIX, OINOStr, OINOBenchmark, OINOMemoryBenchmark, OINOLog, OINOLogLevel, OINOConsoleLog, OINOResult, OINOHttpResult, OINOHtmlTemplate } from "@oino-ts/common";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oino-ts/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
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,11 +19,11 @@
|
|
|
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.14.0",
|
|
23
23
|
"oino-ts": "file:.."
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@oino-ts/types": "0.
|
|
26
|
+
"@oino-ts/types": "0.14.0",
|
|
27
27
|
"@types/bun": "^1.1.14",
|
|
28
28
|
"@types/node": "^20.14.10",
|
|
29
29
|
"typescript": "~5.9.0"
|
package/src/OINODbApi.test.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { expect, test } from "bun:test";
|
|
8
8
|
|
|
9
|
-
import { OINODbApi, OINODbApiParams, OINOContentType, OINODataRow, OINODbDataField, OINOStringDataField, OINODb, OINODbFactory, OINODbParams, OINODbMemoryDataSet, OINODbModelSet, OINOBenchmark, OINOConsoleLog, OINODbSqlFilter, OINODbConfig, OINODbSqlOrder, OINOLogLevel, OINOLog, OINODbSqlLimit, OINODbApiResult, OINODbSqlComparison, OINONumberDataField, OINODatetimeDataField, OINODbApiRequestParams, OINODbHtmlTemplate, OINODbParser } from "./index.js";
|
|
9
|
+
import { OINODbApi, OINODbApiParams, OINOContentType, OINODataRow, OINODbDataField, OINOStringDataField, OINODb, OINODbFactory, OINODbParams, OINODbMemoryDataSet, OINODbModelSet, OINOBenchmark, OINOConsoleLog, OINODbSqlFilter, OINODbConfig, OINODbSqlOrder, OINOLogLevel, OINOLog, OINODbSqlLimit, OINODbApiResult, OINODbSqlComparison, OINONumberDataField, OINODatetimeDataField, OINODbApiRequestParams, OINODbHtmlTemplate, OINODbParser, OINODbSqlBooleanOperation } from "./index.js";
|
|
10
10
|
|
|
11
11
|
import { OINODbBunSqlite } from "@oino-ts/db-bunsqlite"
|
|
12
12
|
import { OINODbPostgresql } from "@oino-ts/db-postgresql"
|
|
@@ -40,7 +40,13 @@ const API_TESTS:OINOTestParams[] = [
|
|
|
40
40
|
name: "API 1",
|
|
41
41
|
apiParams: { apiName: "Orders", tableName: "Orders" },
|
|
42
42
|
requestParams: {
|
|
43
|
-
sqlParams: { filter: OINODbSqlFilter.
|
|
43
|
+
sqlParams: { filter: OINODbSqlFilter.and(
|
|
44
|
+
OINODbSqlFilter.parse("(ShipPostalCode)-like(0502%)"),
|
|
45
|
+
OINODbSqlFilter.parse("-isnull(ShipRegion)")
|
|
46
|
+
),
|
|
47
|
+
order: OINODbSqlOrder.parse("ShipPostalCode-,Freight+"),
|
|
48
|
+
limit: OINODbSqlLimit.parse("5 page 2")
|
|
49
|
+
}
|
|
44
50
|
},
|
|
45
51
|
postRow: [30000,"CACTU",1,new Date("2024-04-05"),new Date("2024-04-06"),new Date("2024-04-07"),2,"184.75","a'b\"c%d_e\tf\rg\nh\\i","Garden House Crowther Way","Cowes","British Isles","PO31 7PJ","UK"],
|
|
46
52
|
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"]
|
|
@@ -49,7 +55,13 @@ const API_TESTS:OINOTestParams[] = [
|
|
|
49
55
|
name: "API 2",
|
|
50
56
|
apiParams: { apiName: "Products", tableName: "Products", failOnOversizedValues: true },
|
|
51
57
|
requestParams: {
|
|
52
|
-
sqlParams: { filter: OINODbSqlFilter.
|
|
58
|
+
sqlParams: { filter: OINODbSqlFilter.and(
|
|
59
|
+
OINODbSqlFilter.parse("(UnitsInStock)-le(5)"),
|
|
60
|
+
OINODbSqlFilter.parse("(UnitsInStock)-ne(4)"),
|
|
61
|
+
),
|
|
62
|
+
order: OINODbSqlOrder.parse("UnitsInStock,UnitPrice"),
|
|
63
|
+
limit: OINODbSqlLimit.parse("7")
|
|
64
|
+
}
|
|
53
65
|
},
|
|
54
66
|
postRow: [99, "Umeshu", 1, 1, "500 ml", 12.99, 2, 0, 20, 0],
|
|
55
67
|
putRow: [99, "Umeshu", 1, 1, undefined, 24.99, 3, 0, 20, 0]
|
package/src/OINODbDataModel.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { OINODbDataField, OINODbApi, OINODataRow, OINO_ERROR_PREFIX, OINODbDataF
|
|
|
11
11
|
*
|
|
12
12
|
*/
|
|
13
13
|
export class OINODbDataModel {
|
|
14
|
-
private
|
|
14
|
+
private _fieldIndexLookup:Record<string, number>;
|
|
15
15
|
|
|
16
16
|
/** Database refererence of the table */
|
|
17
17
|
readonly api:OINODbApi
|
|
@@ -27,7 +27,7 @@ export class OINODbDataModel {
|
|
|
27
27
|
*
|
|
28
28
|
*/
|
|
29
29
|
constructor(api:OINODbApi) {
|
|
30
|
-
this.
|
|
30
|
+
this._fieldIndexLookup = {}
|
|
31
31
|
this.api = api
|
|
32
32
|
this.fields = []
|
|
33
33
|
}
|
|
@@ -122,7 +122,7 @@ export class OINODbDataModel {
|
|
|
122
122
|
*/
|
|
123
123
|
addField(field:OINODbDataField) {
|
|
124
124
|
this.fields.push(field)
|
|
125
|
-
this.
|
|
125
|
+
this._fieldIndexLookup[field.name] = this.fields.length-1
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
@@ -132,7 +132,7 @@ export class OINODbDataModel {
|
|
|
132
132
|
*
|
|
133
133
|
*/
|
|
134
134
|
findFieldByName(name:string):OINODbDataField|null {
|
|
135
|
-
const i:number = this.
|
|
135
|
+
const i:number = this._fieldIndexLookup[name]
|
|
136
136
|
if (i >= 0) {
|
|
137
137
|
return this.fields[i]
|
|
138
138
|
} else {
|
|
@@ -147,7 +147,7 @@ export class OINODbDataModel {
|
|
|
147
147
|
*
|
|
148
148
|
*/
|
|
149
149
|
findFieldIndexByName(name:string):number {
|
|
150
|
-
const i:number = this.
|
|
150
|
+
const i:number = this._fieldIndexLookup[name]
|
|
151
151
|
if (i >= 0) {
|
|
152
152
|
return i
|
|
153
153
|
} else {
|
package/src/OINODbModelSet.ts
CHANGED
|
@@ -329,15 +329,18 @@ export class OINODbModelSet {
|
|
|
329
329
|
|
|
330
330
|
/**
|
|
331
331
|
* Export all rows as a record with OINOId as key and object with row cells as values.
|
|
332
|
-
*
|
|
332
|
+
*
|
|
333
|
+
* @param idFieldName optional field name to use as key instead of OINOId
|
|
333
334
|
*/
|
|
334
335
|
|
|
335
|
-
async exportAsRecord():Promise<Record<string, any>> {
|
|
336
|
+
async exportAsRecord(idFieldName?:string):Promise<Record<string, any>> {
|
|
336
337
|
const result:Record<string, any> = {}
|
|
338
|
+
const row_id_field = idFieldName || OINODbConfig.OINODB_ID_FIELD
|
|
337
339
|
while (!this.dataset.isEof()) {
|
|
338
340
|
const row_data:OINODataRow = this.dataset.getRow()
|
|
339
341
|
const row_export = this._exportRow(row_data)
|
|
340
|
-
|
|
342
|
+
const row_id = row_export[row_id_field]
|
|
343
|
+
result[row_id] = row_export
|
|
341
344
|
await this.dataset.next()
|
|
342
345
|
}
|
|
343
346
|
return result
|
package/src/OINODbSqlParams.ts
CHANGED
|
@@ -18,7 +18,14 @@ export enum OINODbSqlBooleanOperation { and = "and", or = "or", not = "not" }
|
|
|
18
18
|
* Supported logical conjunctions in filter predicates.
|
|
19
19
|
* @enum
|
|
20
20
|
*/
|
|
21
|
-
export enum OINODbSqlComparison { lt = "lt", le = "le", eq = "eq", ge = "ge", gt = "gt", like = "like" }
|
|
21
|
+
export enum OINODbSqlComparison { lt = "lt", le = "le", eq = "eq", ne = "ne", ge = "ge", gt = "gt", like = "like" }
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Supported logical conjunctions in filter predicates.
|
|
25
|
+
* @enum
|
|
26
|
+
*/
|
|
27
|
+
export enum OINODbSqlNullCheck { isnull = "isnull", isnotnull = "isnotnull" }
|
|
28
|
+
|
|
22
29
|
|
|
23
30
|
/**
|
|
24
31
|
* Class for recursively parsing of filters and printing them as SQL conditions.
|
|
@@ -31,12 +38,13 @@ export enum OINODbSqlComparison { lt = "lt", le = "le", eq = "eq", ge = "ge", gt
|
|
|
31
38
|
*/
|
|
32
39
|
export class OINODbSqlFilter {
|
|
33
40
|
private static _booleanOperationRegex = /^\s?\-(and|or)\s?$/i
|
|
34
|
-
private static _negationRegex = /^-(not
|
|
35
|
-
private static _filterComparisonRegex = /^\(([^'"\(\)]+)\)\s?\-(lt|le|eq|ge|gt|like)\s?\(([^'"\(\)]+)\)$/i
|
|
41
|
+
private static _negationRegex = /^-(not)\((.+)\)$/i
|
|
42
|
+
private static _filterComparisonRegex = /^\(([^'"\(\)]+)\)\s?\-(lt|le|eq|ne|ge|gt|like)\s?\(([^'"\(\)]+)\)$/i
|
|
43
|
+
private static _filterNullCheckRegex = /^-(isnull|isnotnull)\((.+)\)$/i
|
|
36
44
|
|
|
37
45
|
private _leftSide: OINODbSqlFilter | string
|
|
38
46
|
private _rightSide: OINODbSqlFilter | string
|
|
39
|
-
private _operator:OINODbSqlComparison|OINODbSqlBooleanOperation|null
|
|
47
|
+
private _operator:OINODbSqlComparison|OINODbSqlBooleanOperation|OINODbSqlNullCheck|null
|
|
40
48
|
|
|
41
49
|
/**
|
|
42
50
|
* Constructor of `OINODbSqlFilter`
|
|
@@ -44,11 +52,12 @@ export class OINODbSqlFilter {
|
|
|
44
52
|
* @param operation operation of the filter, either `OINODbSqlComparison` or `OINODbSqlBooleanOperation`
|
|
45
53
|
* @param rightSide right side of the filter, either another filter or a value
|
|
46
54
|
*/
|
|
47
|
-
constructor(leftSide:OINODbSqlFilter|string, operation:OINODbSqlComparison|OINODbSqlBooleanOperation|null, rightSide:OINODbSqlFilter|string) {
|
|
55
|
+
constructor(leftSide:OINODbSqlFilter|string, operation:OINODbSqlComparison|OINODbSqlBooleanOperation|OINODbSqlNullCheck|null, rightSide:OINODbSqlFilter|string) {
|
|
48
56
|
if (!(
|
|
49
57
|
((operation === null) && (leftSide == "") && (rightSide == "")) ||
|
|
50
58
|
((operation !== null) && (Object.values(OINODbSqlComparison).includes(operation as OINODbSqlComparison)) && (typeof(leftSide) == "string") && (leftSide != "") && (typeof(rightSide) == "string") && (rightSide != "")) ||
|
|
51
59
|
((operation == OINODbSqlBooleanOperation.not) && (leftSide == "") && (rightSide instanceof OINODbSqlFilter)) ||
|
|
60
|
+
(((operation == OINODbSqlNullCheck.isnull) || (operation == OINODbSqlNullCheck.isnotnull)) && (typeof(leftSide) == "string") && (rightSide == "")) ||
|
|
52
61
|
(((operation == OINODbSqlBooleanOperation.and) || (operation == OINODbSqlBooleanOperation.or)) && (leftSide instanceof OINODbSqlFilter) && (rightSide instanceof OINODbSqlFilter))
|
|
53
62
|
)) {
|
|
54
63
|
OINOLog.error("@oino-ts/db", "OINODbSqlFilter", "constructor", "Unsupported OINODbSqlFilter format", {leftSide:leftSide, operation:operation, rightSide:rightSide})
|
|
@@ -66,6 +75,7 @@ export class OINODbSqlFilter {
|
|
|
66
75
|
* - comparison: (field)-lt|le|eq|ge|gt|like(value)
|
|
67
76
|
* - negation: -not(filter)
|
|
68
77
|
* - conjunction/disjunction: (filter)-and|or(filter)
|
|
78
|
+
* - null check: -isnull(field) or -isnotnull(field)
|
|
69
79
|
*
|
|
70
80
|
* @param filterString string representation of filter from HTTP-request
|
|
71
81
|
*
|
|
@@ -76,20 +86,28 @@ export class OINODbSqlFilter {
|
|
|
76
86
|
|
|
77
87
|
} else {
|
|
78
88
|
let match = OINODbSqlFilter._filterComparisonRegex.exec(filterString)
|
|
79
|
-
if (match != null) {
|
|
89
|
+
if ((match != null) && (match.length == 4)) {
|
|
80
90
|
return new OINODbSqlFilter(match[1], match[2].toLowerCase() as OINODbSqlComparison, match[3])
|
|
91
|
+
|
|
81
92
|
} else {
|
|
82
93
|
let match = OINODbSqlFilter._negationRegex.exec(filterString)
|
|
83
94
|
if (match != null) {
|
|
84
95
|
return new OINODbSqlFilter("", OINODbSqlBooleanOperation.not, OINODbSqlFilter.parse(match[3]))
|
|
96
|
+
|
|
85
97
|
} else {
|
|
86
98
|
let boolean_parts = OINOStr.splitByBrackets(filterString, true, false, '(', ')')
|
|
87
99
|
if (boolean_parts.length == 3 && (boolean_parts[1].match(OINODbSqlFilter._booleanOperationRegex))) {
|
|
88
100
|
return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1) as OINODbSqlBooleanOperation, OINODbSqlFilter.parse(boolean_parts[2]))
|
|
89
|
-
|
|
101
|
+
|
|
90
102
|
} else {
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
let match = OINODbSqlFilter._filterNullCheckRegex.exec(filterString)
|
|
104
|
+
if ((match != null)) {
|
|
105
|
+
return new OINODbSqlFilter(match[2], match[1].toLowerCase() as OINODbSqlComparison, "")
|
|
106
|
+
|
|
107
|
+
} else {
|
|
108
|
+
OINOLog.error("@oino-ts/db", "OINODbSqlFilter", "constructor", "Invalid filter", {filterString:filterString})
|
|
109
|
+
throw new Error(OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'") // invalid filter could be a security risk, stop processing
|
|
110
|
+
}
|
|
93
111
|
}
|
|
94
112
|
}
|
|
95
113
|
}
|
|
@@ -119,6 +137,53 @@ export class OINODbSqlFilter {
|
|
|
119
137
|
}
|
|
120
138
|
}
|
|
121
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Combine two filters with an AND operation.
|
|
142
|
+
*
|
|
143
|
+
* @param leftSide left side filter
|
|
144
|
+
* @param rightSide right side filter
|
|
145
|
+
*
|
|
146
|
+
*/
|
|
147
|
+
static and(leftSide:OINODbSqlFilter, rightSide:OINODbSqlFilter):OINODbSqlFilter|undefined {
|
|
148
|
+
if ((leftSide) && (!leftSide.isEmpty()) && (rightSide) && (!rightSide.isEmpty())) {
|
|
149
|
+
return new OINODbSqlFilter(leftSide, OINODbSqlBooleanOperation.and, rightSide)
|
|
150
|
+
|
|
151
|
+
} else {
|
|
152
|
+
return undefined
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Combine two filters with an OR operation.
|
|
158
|
+
*
|
|
159
|
+
* @param leftSide left side filter
|
|
160
|
+
* @param rightSide right side filter
|
|
161
|
+
*
|
|
162
|
+
*/
|
|
163
|
+
static or(leftSide:OINODbSqlFilter, rightSide:OINODbSqlFilter):OINODbSqlFilter|undefined {
|
|
164
|
+
if ((leftSide) && (!leftSide.isEmpty()) && (rightSide) && (!rightSide.isEmpty())) {
|
|
165
|
+
return new OINODbSqlFilter(leftSide, OINODbSqlBooleanOperation.or, rightSide)
|
|
166
|
+
|
|
167
|
+
} else {
|
|
168
|
+
return undefined
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Negate a filter with a NOT operation.
|
|
174
|
+
*
|
|
175
|
+
* @param leftSide left side filter
|
|
176
|
+
*
|
|
177
|
+
*/
|
|
178
|
+
static not(leftSide:OINODbSqlFilter):OINODbSqlFilter|undefined {
|
|
179
|
+
if ((leftSide) && (!leftSide.isEmpty())) {
|
|
180
|
+
return new OINODbSqlFilter(leftSide, OINODbSqlBooleanOperation.not, "")
|
|
181
|
+
|
|
182
|
+
} else {
|
|
183
|
+
return undefined
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
122
187
|
|
|
123
188
|
private _operatorToSql():string {
|
|
124
189
|
switch (this._operator) {
|
|
@@ -128,9 +193,12 @@ export class OINODbSqlFilter {
|
|
|
128
193
|
case "lt": return " < "
|
|
129
194
|
case "le": return " <= "
|
|
130
195
|
case "eq": return " = "
|
|
196
|
+
case "ne": return " != "
|
|
131
197
|
case "ge": return " >= "
|
|
132
198
|
case "gt": return " > "
|
|
133
199
|
case "like": return " LIKE "
|
|
200
|
+
case "isnull": return " IS NULL"
|
|
201
|
+
case "isnotnull": return " IS NOT NULL"
|
|
134
202
|
}
|
|
135
203
|
return " "
|
|
136
204
|
}
|
|
@@ -168,6 +236,8 @@ export class OINODbSqlFilter {
|
|
|
168
236
|
result += this._operatorToSql()
|
|
169
237
|
if (this._rightSide instanceof OINODbSqlFilter) {
|
|
170
238
|
result += this._rightSide.toSql(dataModel)
|
|
239
|
+
} else if (this._operator == OINODbSqlNullCheck.isnull || this._operator == OINODbSqlNullCheck.isnotnull) {
|
|
240
|
+
// nothing to do, IS NULL and IS NOT NULL do not have a right side
|
|
171
241
|
} else {
|
|
172
242
|
const value = field!.deserializeCell(this._rightSide)
|
|
173
243
|
if ((value == null) || (value === "")) {
|