@oino-ts/db 0.13.4 → 0.14.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.
@@ -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|)\((.+)\)$/i;
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
- index_js_1.OINOLog.error("@oino-ts/db", "OINODbSqlFilter", "constructor", "Invalid filter", { filterString: filterString });
100
- throw new Error(index_js_1.OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'"); // invalid filter could be a security risk, stop processing
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 === "")) {
package/dist/cjs/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OINODB_UNDEFINED = exports.OINODB_EMPTY_ROWS = exports.OINODB_EMPTY_ROW = exports.OINODbParser = exports.OINODbSwagger = exports.OINODbFactory = exports.OINODbConfig = exports.OINODbSqlSelect = exports.OINODbSqlAggregateFunctions = exports.OINODbSqlAggregate = exports.OINODbSqlBooleanOperation = exports.OINODbSqlLimit = exports.OINODbSqlComparison = exports.OINODbSqlOrder = exports.OINODbSqlFilter = exports.OINODb = exports.OINODbMemoryDataSet = exports.OINODbDataSet = exports.OINODatetimeDataField = exports.OINOBlobDataField = exports.OINOStringDataField = exports.OINONumberDataField = exports.OINOBooleanDataField = exports.OINODbDataField = exports.OINODbModelSet = exports.OINODbDataModel = exports.OINODbApi = exports.OINODbHtmlTemplate = exports.OINODbApiResult = exports.OINOHtmlTemplate = exports.OINOHttpResult = exports.OINOResult = exports.OINOConsoleLog = exports.OINOLogLevel = exports.OINOLog = exports.OINOMemoryBenchmark = exports.OINOBenchmark = exports.OINOStr = exports.OINO_DEBUG_PREFIX = exports.OINO_INFO_PREFIX = exports.OINO_WARNING_PREFIX = exports.OINO_ERROR_PREFIX = exports.OINOContentType = void 0;
3
+ exports.OINODB_UNDEFINED = exports.OINODB_EMPTY_ROWS = exports.OINODB_EMPTY_ROW = exports.OINODbParser = exports.OINODbSwagger = exports.OINODbFactory = exports.OINODbConfig = exports.OINODbSqlNullCheck = exports.OINODbSqlSelect = exports.OINODbSqlAggregateFunctions = exports.OINODbSqlAggregate = exports.OINODbSqlBooleanOperation = exports.OINODbSqlLimit = exports.OINODbSqlComparison = exports.OINODbSqlOrder = exports.OINODbSqlFilter = exports.OINODb = exports.OINODbMemoryDataSet = exports.OINODbDataSet = exports.OINODatetimeDataField = exports.OINOBlobDataField = exports.OINOStringDataField = exports.OINONumberDataField = exports.OINOBooleanDataField = exports.OINODbDataField = exports.OINODbModelSet = exports.OINODbDataModel = exports.OINODbApi = exports.OINODbHtmlTemplate = exports.OINODbApiResult = exports.OINOHtmlTemplate = exports.OINOHttpResult = exports.OINOResult = exports.OINOConsoleLog = exports.OINOLogLevel = exports.OINOLog = exports.OINOMemoryBenchmark = exports.OINOBenchmark = exports.OINOStr = exports.OINO_DEBUG_PREFIX = exports.OINO_INFO_PREFIX = exports.OINO_WARNING_PREFIX = exports.OINO_ERROR_PREFIX = exports.OINOContentType = void 0;
4
4
  const common_1 = require("@oino-ts/common");
5
5
  Object.defineProperty(exports, "OINOContentType", { enumerable: true, get: function () { return common_1.OINOContentType; } });
6
6
  var common_2 = require("@oino-ts/common");
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "OINODbSqlBooleanOperation", { enumerable: true,
45
45
  Object.defineProperty(exports, "OINODbSqlAggregate", { enumerable: true, get: function () { return OINODbSqlParams_js_1.OINODbSqlAggregate; } });
46
46
  Object.defineProperty(exports, "OINODbSqlAggregateFunctions", { enumerable: true, get: function () { return OINODbSqlParams_js_1.OINODbSqlAggregateFunctions; } });
47
47
  Object.defineProperty(exports, "OINODbSqlSelect", { enumerable: true, get: function () { return OINODbSqlParams_js_1.OINODbSqlSelect; } });
48
+ Object.defineProperty(exports, "OINODbSqlNullCheck", { enumerable: true, get: function () { return OINODbSqlParams_js_1.OINODbSqlNullCheck; } });
48
49
  var OINODbConfig_js_1 = require("./OINODbConfig.js");
49
50
  Object.defineProperty(exports, "OINODbConfig", { enumerable: true, get: function () { return OINODbConfig_js_1.OINODbConfig; } });
50
51
  var OINODbFactory_js_1 = require("./OINODbFactory.js");
@@ -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|)\((.+)\)$/i;
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
- OINOLog.error("@oino-ts/db", "OINODbSqlFilter", "constructor", "Invalid filter", { filterString: filterString });
97
- throw new Error(OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'"); // invalid filter could be a security risk, stop processing
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 === "")) {
package/dist/esm/index.js CHANGED
@@ -6,7 +6,7 @@ export { OINODbDataModel } from "./OINODbDataModel.js";
6
6
  export { OINODbModelSet } from "./OINODbModelSet.js";
7
7
  export { OINODbDataField, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINOBlobDataField, OINODatetimeDataField } from "./OINODbDataField.js";
8
8
  export { OINODbDataSet, OINODbMemoryDataSet, OINODb } from "./OINODb.js";
9
- export { OINODbSqlFilter, OINODbSqlOrder, OINODbSqlComparison, OINODbSqlLimit, OINODbSqlBooleanOperation, OINODbSqlAggregate, OINODbSqlAggregateFunctions, OINODbSqlSelect } from "./OINODbSqlParams.js";
9
+ export { OINODbSqlFilter, OINODbSqlOrder, OINODbSqlComparison, OINODbSqlLimit, OINODbSqlBooleanOperation, OINODbSqlAggregate, OINODbSqlAggregateFunctions, OINODbSqlSelect, OINODbSqlNullCheck } from "./OINODbSqlParams.js";
10
10
  export { OINODbConfig } from "./OINODbConfig.js";
11
11
  export { OINODbFactory } from "./OINODbFactory.js";
12
12
  export { OINODbSwagger } from "./OINODbSwagger.js";
@@ -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";
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  import { OINODbDataModel, OINODataRow, OINODbApiRequestParams } from "./index.js";
4
2
  /**
5
3
  * Static factory class for easily creating things based on data
@@ -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.
@@ -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";
@@ -11,7 +9,7 @@ export { OINODbDataModel } from "./OINODbDataModel.js";
11
9
  export { OINODbModelSet } from "./OINODbModelSet.js";
12
10
  export { OINODbDataField, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINOBlobDataField, OINODatetimeDataField } from "./OINODbDataField.js";
13
11
  export { OINODbDataSet, OINODbMemoryDataSet, OINODb } from "./OINODb.js";
14
- export { OINODbSqlFilter, OINODbSqlOrder, OINODbSqlComparison, OINODbSqlLimit, OINODbSqlBooleanOperation, OINODbSqlAggregate, OINODbSqlAggregateFunctions, OINODbSqlSelect } from "./OINODbSqlParams.js";
12
+ export { OINODbSqlFilter, OINODbSqlOrder, OINODbSqlComparison, OINODbSqlLimit, OINODbSqlBooleanOperation, OINODbSqlAggregate, OINODbSqlAggregateFunctions, OINODbSqlSelect, OINODbSqlNullCheck } from "./OINODbSqlParams.js";
15
13
  export { OINODbConfig } from "./OINODbConfig.js";
16
14
  export { OINODbFactory } from "./OINODbFactory.js";
17
15
  export { OINODbSwagger } from "./OINODbSwagger.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/db",
3
- "version": "0.13.4",
3
+ "version": "0.14.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,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.13.4",
22
+ "@oino-ts/common": "0.14.1",
23
23
  "oino-ts": "file:.."
24
24
  },
25
25
  "devDependencies": {
26
- "@oino-ts/types": "0.13.4",
26
+ "@oino-ts/types": "0.14.1",
27
27
  "@types/bun": "^1.1.14",
28
28
  "@types/node": "^20.14.10",
29
29
  "typescript": "~5.9.0"
@@ -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.parse("(ShipPostalCode)-like(0502%)"), order: OINODbSqlOrder.parse("ShipPostalCode-,Freight+"), limit: OINODbSqlLimit.parse("5 page 2") }
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.parse("(UnitsInStock)-le(5)"), order: OINODbSqlOrder.parse("UnitsInStock,UnitPrice"), limit: OINODbSqlLimit.parse("7") }
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]
@@ -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|)\((.+)\)$/i
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
- OINOLog.error("@oino-ts/db", "OINODbSqlFilter", "constructor", "Invalid filter", {filterString:filterString})
92
- throw new Error(OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'") // invalid filter could be a security risk, stop processing
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 === "")) {
package/src/index.ts CHANGED
@@ -12,7 +12,7 @@ export { OINODbDataModel } from "./OINODbDataModel.js"
12
12
  export { OINODbModelSet } from "./OINODbModelSet.js"
13
13
  export { OINODbDataField, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINOBlobDataField, OINODatetimeDataField } from "./OINODbDataField.js"
14
14
  export { OINODbDataSet, OINODbMemoryDataSet, OINODb } from "./OINODb.js"
15
- export { OINODbSqlFilter, OINODbSqlOrder, OINODbSqlComparison, OINODbSqlLimit, OINODbSqlBooleanOperation, OINODbSqlAggregate, OINODbSqlAggregateFunctions, OINODbSqlSelect } from "./OINODbSqlParams.js"
15
+ export { OINODbSqlFilter, OINODbSqlOrder, OINODbSqlComparison, OINODbSqlLimit, OINODbSqlBooleanOperation, OINODbSqlAggregate, OINODbSqlAggregateFunctions, OINODbSqlSelect, OINODbSqlNullCheck } from "./OINODbSqlParams.js"
16
16
  export { OINODbConfig } from "./OINODbConfig.js"
17
17
  export { OINODbFactory } from "./OINODbFactory.js"
18
18
  export { OINODbSwagger } from "./OINODbSwagger.js"