@oino-ts/db 0.3.2 → 0.3.4

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.
@@ -1,437 +1,438 @@
1
- /*
2
- * This Source Code Form is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
- */
6
-
7
- import { OINOStr, OINODbDataField, OINODbDataModel, OINO_ERROR_PREFIX, OINOLog } from "./index.js"
8
-
9
- const OINO_FIELD_NAME_CHARS:string = "\\w\\s\\-\\_\\#\\¤"
10
-
11
- /**
12
- * Supported logical conjunctions in filter predicates.
13
- * @enum
14
- */
15
- export enum OINODbSqlBooleanOperation { and = "and", or = "or", not = "not" }
16
-
17
- /**
18
- * Supported logical conjunctions in filter predicates.
19
- * @enum
20
- */
21
- export enum OINODbSqlComparison { lt = "lt", le = "le", eq = "eq", ge = "ge", gt = "gt", like = "like" }
22
-
23
- /**
24
- * Class for recursively parsing of filters and printing them as SQL conditions.
25
- * Supports three types of statements
26
- * - comparison: (field)-lt|le|eq|ge|gt|like(value)
27
- * - negation: -not(filter)
28
- * - conjunction/disjunction: (filter)-and|or(filter)
29
- * Supported conditions are comparisons (<, <=, =, >=, >) and substring match (LIKE).
30
- *
31
- */
32
- export class OINODbSqlFilter {
33
- 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
36
-
37
- private _leftSide: OINODbSqlFilter | string
38
- private _rightSide: OINODbSqlFilter | string
39
- private _operator:OINODbSqlComparison|OINODbSqlBooleanOperation|null
40
-
41
- /**
42
- * Constructor of `OINODbSqlFilter`
43
- * @param leftSide left side of the filter, either another filter or a column name
44
- * @param operation operation of the filter, either `OINODbSqlComparison` or `OINODbSqlBooleanOperation`
45
- * @param rightSide right side of the filter, either another filter or a value
46
- */
47
- constructor(leftSide:OINODbSqlFilter|string, operation:OINODbSqlComparison|OINODbSqlBooleanOperation|null, rightSide:OINODbSqlFilter|string) {
48
- if (!(
49
- ((operation === null) && (leftSide == "") && (rightSide == "")) ||
50
- ((operation !== null) && (Object.values(OINODbSqlComparison).includes(operation as OINODbSqlComparison)) && (typeof(leftSide) == "string") && (leftSide != "") && (typeof(rightSide) == "string") && (rightSide != "")) ||
51
- ((operation == OINODbSqlBooleanOperation.not) && (leftSide == "") && (rightSide instanceof OINODbSqlFilter)) ||
52
- (((operation == OINODbSqlBooleanOperation.and) || (operation == OINODbSqlBooleanOperation.or)) && (leftSide instanceof OINODbSqlFilter) && (rightSide instanceof OINODbSqlFilter))
53
- )) {
54
- OINOLog.debug("Unsupported OINODbSqlFilter format!", {leftSide:leftSide, operation:operation, rightSide:rightSide})
55
- throw new Error(OINO_ERROR_PREFIX + ": Unsupported OINODbSqlFilter format!")
56
- }
57
- this._leftSide = leftSide
58
- this._operator = operation
59
- this._rightSide = rightSide
60
- }
61
-
62
- /**
63
- * Constructor for `OINODbSqlFilter` as parser of http parameter.
64
- *
65
- * @param filterString string representation of filter from HTTP-request
66
- *
67
- */
68
- static parse(filterString: string):OINODbSqlFilter {
69
- // OINOLog_debug("OINODbSqlFilter.constructor", {filterString:filterString})
70
- if (!filterString) {
71
- return new OINODbSqlFilter("", null, "")
72
-
73
- } else {
74
- let match = OINODbSqlFilter._filterComparisonRegex.exec(filterString)
75
- if (match != null) {
76
- return new OINODbSqlFilter(match[1], match[2].toLowerCase() as OINODbSqlComparison, match[3])
77
- } else {
78
- let match = OINODbSqlFilter._negationRegex.exec(filterString)
79
- if (match != null) {
80
- return new OINODbSqlFilter("", OINODbSqlBooleanOperation.not, OINODbSqlFilter.parse(match[3]))
81
- } else {
82
- let boolean_parts = OINOStr.splitByBrackets(filterString, true, false, '(', ')')
83
- // OINOLog_debug("OINODbSqlFilter.constructor", {boolean_parts:boolean_parts})
84
- if (boolean_parts.length == 3 && (boolean_parts[1].match(OINODbSqlFilter._booleanOperationRegex))) {
85
- return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1) as OINODbSqlBooleanOperation, OINODbSqlFilter.parse(boolean_parts[2]))
86
-
87
- } else {
88
- throw new Error(OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'") // invalid filter could be a security risk, stop processing
89
- }
90
- }
91
- }
92
- }
93
- }
94
-
95
- /**
96
- * Construct a new `OINODbSqlFilter` as combination of (boolean and/or) of two filters.
97
- *
98
- * @param leftSide left side to combine
99
- * @param operation boolean operation to use in combination
100
- * @param rightSide right side to combine
101
- *
102
- */
103
- static combine(leftSide:OINODbSqlFilter|undefined, operation:OINODbSqlBooleanOperation, rightSide:OINODbSqlFilter|undefined):OINODbSqlFilter|undefined {
104
- if ((leftSide) && (!leftSide.isEmpty()) && (rightSide) && (!rightSide.isEmpty())) {
105
- return new OINODbSqlFilter(leftSide, operation, rightSide)
106
-
107
- } else if ((leftSide) && (!leftSide.isEmpty())) {
108
- return leftSide
109
-
110
- } else if ((rightSide) && (!rightSide.isEmpty())) {
111
- return rightSide
112
-
113
- } else {
114
- return undefined
115
- }
116
- }
117
-
118
-
119
- private _operatorToSql():string {
120
- switch (this._operator) {
121
- case "and": return " AND "
122
- case "or": return " OR "
123
- case "not": return "NOT "
124
- case "lt": return " < "
125
- case "le": return " <= "
126
- case "eq": return " = "
127
- case "ge": return " >= "
128
- case "gt": return " > "
129
- case "like": return " LIKE "
130
- }
131
- return " "
132
- }
133
-
134
- /**
135
- * Does filter contain any valid conditions.
136
- *
137
- */
138
- isEmpty():boolean {
139
- return (this._leftSide == "") && (this._operator == null) && (this._rightSide == "")
140
- }
141
-
142
- /**
143
- * Print filter as SQL condition based on the datamodel of the API.
144
- *
145
- * @param dataModel data model (and database) to use for formatting of values
146
- *
147
- */
148
- toSql(dataModel:OINODbDataModel):string {
149
- // OINOLog.debug("OINODbSqlFilter.toSql", {_leftSide:this._leftSide, _operator:this._operator, _rightSide:this._rightSide})
150
- if (this.isEmpty()) {
151
- return ""
152
- }
153
- let result:string = ""
154
- let field:OINODbDataField|null = null
155
- if (this._leftSide instanceof OINODbSqlFilter) {
156
- result += this._leftSide.toSql(dataModel)
157
- } else {
158
- field = dataModel.findFieldByName(this._leftSide)
159
- if (!field) {
160
- OINOLog.error("OINODbSqlFilter.toSql: Invalid field!", {field:this._leftSide})
161
- throw new Error(OINO_ERROR_PREFIX + ": OINODbSqlFilter.toSql - Invalid field '" + this._leftSide + "'") // invalid field name could be a security risk, stop processing
162
- }
163
- result += dataModel.api.db.printSqlColumnname(field?.name || this._leftSide)
164
- }
165
- result += this._operatorToSql()
166
- if (this._rightSide instanceof OINODbSqlFilter) {
167
- result += this._rightSide.toSql(dataModel)
168
- } else {
169
- const value = field!.deserializeCell(this._rightSide)
170
- if (!value) {
171
- OINOLog.error("OINODbSqlFilter.toSql: Invalid value!", {value:value})
172
- throw new Error(OINO_ERROR_PREFIX + ": OINODbSqlFilter.toSql - Invalid value '" + value + "'") // invalid value could be a security risk, stop processing
173
- }
174
- result += field!.printCellAsSqlValue(value)
175
- }
176
- // OINOLog.debug("OINODbSqlFilter.toSql", {result:result})
177
- return "(" + result + ")"
178
- }
179
- }
180
-
181
- /**
182
- * Class for ordering select results on a number of columns.
183
- *
184
- */
185
- export class OINODbSqlOrder {
186
- private static _orderColumnRegex = /^\s*(\w+)\s?(ASC|DESC|\+|\-)?\s*?$/i
187
-
188
- private _columns: string[]
189
- private _descending: boolean[]
190
-
191
- /**
192
- * Constructor for `OINODbSqlOrder`.
193
- *
194
- * @param column_or_array single or array of columns to order on
195
- * @param descending_or_array single or array of booleans if ordes is descending
196
- *
197
- */
198
- constructor(column_or_array:string[]|string, descending_or_array:boolean[]|boolean) {
199
- OINOLog.debug("OINODbSqlOrder.constructor", {columns:column_or_array, directions:descending_or_array})
200
- if (Array.isArray(column_or_array)) {
201
- this._columns = column_or_array
202
- } else {
203
- this._columns = [column_or_array]
204
- }
205
- if (Array.isArray(descending_or_array)) {
206
- this._descending = descending_or_array
207
- } else {
208
- this._descending = [descending_or_array]
209
- }
210
- }
211
-
212
- /**
213
- * Constructor for `OINODbSqlOrder` as parser of http parameter.
214
- *
215
- * @param orderString string representation of ordering from HTTP-request
216
- *
217
- */
218
- static parse(orderString: string):OINODbSqlOrder {
219
- let columns:string[] = []
220
- let directions:boolean[] = []
221
-
222
- const column_strings = orderString.split(',')
223
-
224
- for (let i=0; i<column_strings.length; i++) {
225
- let match = OINODbSqlOrder._orderColumnRegex.exec(column_strings[i])
226
- if (match != null) {
227
- columns.push(match[1])
228
- const dir:string = (match[2] || "ASC").toUpperCase()
229
- directions.push((dir == "DESC") || (dir == "-"))
230
- }
231
- }
232
- return new OINODbSqlOrder(columns, directions)
233
- }
234
-
235
- /**
236
- * Does filter contain any valid conditions.
237
- *
238
- */
239
- isEmpty():boolean {
240
- return (this._columns.length == 0)
241
- }
242
-
243
- /**
244
- * Print order as SQL condition based on the datamodel of the API.
245
- *
246
- * @param dataModel data model (and database) to use for formatting of values
247
- *
248
- */
249
- toSql(dataModel:OINODbDataModel):string {
250
- if (this.isEmpty()) {
251
- return ""
252
- }
253
- // OINOLog.debug("OINODbSqlOrder.toSql", {columns:this._columns, directions:this._directions})
254
- let result:string = ""
255
- for (let i=0; i<this._columns.length; i++) {
256
- const field:OINODbDataField|null = dataModel.findFieldByName(this._columns[i])
257
- if (!field) {
258
- OINOLog.error("OINODbSqlOrder.toSql: Invalid field!", {field:this._columns[i]})
259
- throw new Error(OINO_ERROR_PREFIX + ": OINODbSqlOrder.toSql - Invalid field '" + this._columns[i] + "'") // invalid field name could be a security risk, stop processing
260
- }
261
- if (result) {
262
- result += ","
263
- }
264
- result += dataModel.api.db.printSqlColumnname(field.name) + " "
265
- if (this._descending[i]) {
266
- result += "DESC"
267
- } else {
268
- result += "ASC"
269
- }
270
- }
271
- // OINOLog.debug("OINODbSqlOrder.toSql", {result:result})
272
- return result
273
- }
274
- }
275
-
276
- /**
277
- * Class for limiting the number of results.
278
- *
279
- */
280
- export class OINODbSqlLimit {
281
- private static _limitRegex = /^(\d+)(\spage\s)?(\d+)?$/i
282
-
283
- private _limit: number
284
- private _page: number
285
-
286
- /**
287
- * Constructor for `OINODbSqlLimit`.
288
- *
289
- * @param limit maximum number of items to return
290
- * @param page page number to return starting from 1
291
- *
292
- */
293
- constructor(limit: number, page: number = -1) {
294
- this._limit = limit
295
- this._page = page
296
- }
297
- /**
298
- * Constructor for `OINODbSqlLimit` as parser of http parameter.
299
- *
300
- * @param limitString string representation of limit from HTTP-request
301
- *
302
- */
303
- static parse(limitString: string):OINODbSqlLimit {
304
- let match = OINODbSqlLimit._limitRegex.exec(limitString)
305
- if ((match != null) && (match.length == 4)) {
306
- return new OINODbSqlLimit(Number.parseInt(match[1]), Number.parseInt(match[3]))
307
- } else if (match != null) {
308
- return new OINODbSqlLimit(Number.parseInt(match[1]))
309
- } else {
310
- return new OINODbSqlLimit(-1)
311
- }
312
- }
313
-
314
- /**
315
- * Does filter contain any valid conditions.
316
- *
317
- */
318
- isEmpty():boolean {
319
- return (this._limit <= 0)
320
- }
321
-
322
- /**
323
- * Print order as SQL condition based on the datamodel of the API.
324
- *
325
- * @param dataModel data model (and database) to use for formatting of values
326
- *
327
- */
328
- toSql(dataModel:OINODbDataModel):string {
329
- if (this.isEmpty()) {
330
- return ""
331
- }
332
- let result:string = this._limit.toString()
333
- if (this._page > 0) {
334
- result += " OFFSET " + (this._limit * (this._page-1) + 1).toString()
335
- }
336
- return result
337
- }
338
- }
339
-
340
- /**
341
- * Supported aggregation functions in OINODbSqlAggregate.
342
- * @enum
343
- */
344
- export enum OINODbSqlAggregateFunctions { count = "count", sum = "sum", avg = "avg", min = "min", max = "max" }
345
-
346
- /**
347
- * Class for limiting the number of results.
348
- *
349
- */
350
- export class OINODbSqlAggregate {
351
- private static _aggregateRegex:RegExp = new RegExp("^(count|sum|avg|min|max)\\(([" + OINO_FIELD_NAME_CHARS + "]+)\\)$", "mi")
352
-
353
- private _functions: OINODbSqlAggregateFunctions[]
354
- private _fields: string[]
355
-
356
- /**
357
- * Constructor for `OINODbSqlAggregate`.
358
- *
359
- * @param function aggregate function to use
360
- * @param fields fields to aggregate
361
- *
362
- */
363
- constructor(func: OINODbSqlAggregateFunctions[], fields: string[]) {
364
- this._functions = func
365
- this._fields = fields
366
- }
367
- /**
368
- * Constructor for `OINODbSqlAggregate` as parser of http parameter.
369
- *
370
- * @param aggregatorString string representation of limit from HTTP-request
371
- *
372
- */
373
- static parse(aggregatorString: string):OINODbSqlAggregate {
374
- let funtions:OINODbSqlAggregateFunctions[] = []
375
- let fields:string[] = []
376
- const aggregator_parts = aggregatorString.split(',')
377
- for (let i=0; i<aggregator_parts.length; i++) {
378
- let match = OINODbSqlAggregate._aggregateRegex.exec(aggregator_parts[i])
379
- // OINOLog.debug("OINODbSqlAggregate.parse - next aggregator", {aggregator: aggregator_parts[i], match:match})
380
- if ((match != null) && (match.length == 3)) {
381
- funtions.push(match[1] as OINODbSqlAggregateFunctions)
382
- fields.push(match[2])
383
- }
384
- }
385
- return new OINODbSqlAggregate(funtions, fields)
386
- }
387
-
388
- /**
389
- * Does filter contain any valid conditions.
390
- *
391
- */
392
- isEmpty():boolean {
393
- return (this._functions.length <= 0)
394
- }
395
-
396
- /**
397
- * Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
398
- *
399
- * @param dataModel data model (and database) to use for formatting of values
400
- *
401
- */
402
- toSql(dataModel:OINODbDataModel):string {
403
- if (this.isEmpty()) {
404
- return ""
405
- }
406
- let result:string = ""
407
- for (let i=0; i<dataModel.fields.length; i++) {
408
- if (this._fields.includes(dataModel.fields[i].name) == false) {
409
- result += dataModel.fields[i].printSqlColumnName() + ","
410
- }
411
- }
412
- OINOLog.debug("OINODbSqlAggregate.toSql", {result:result})
413
- return result.substring(0, result.length-1)
414
- }
415
-
416
- /**
417
- * Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
418
- *
419
- * @param dataModel data model (and database) to use for formatting of values
420
- *
421
- */
422
- printSqlColumnNames(dataModel:OINODbDataModel):string {
423
- let result:string = ""
424
- for (let i=0; i<dataModel.fields.length; i++) {
425
- const aggregate_index = this._fields.indexOf(dataModel.fields[i].name)
426
- if (aggregate_index >= 0) {
427
- result += this._functions[aggregate_index] + "(" + dataModel.fields[i].printSqlColumnName() + "),"
428
- } else {
429
- result += dataModel.fields[i].printSqlColumnName() + ","
430
- }
431
- }
432
- OINOLog.debug("OINODbSqlAggregate.printSqlColumnNames", {result:result})
433
- return result.substring(0, result.length-1)
434
- }
435
- }
436
-
437
-
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ import { OINOStr, OINODbDataField, OINODbDataModel, OINO_ERROR_PREFIX, OINOLog } from "./index.js"
8
+
9
+ const OINO_FIELD_NAME_CHARS:string = "\\w\\s\\-\\_\\#\\¤"
10
+
11
+ /**
12
+ * Supported logical conjunctions in filter predicates.
13
+ * @enum
14
+ */
15
+ export enum OINODbSqlBooleanOperation { and = "and", or = "or", not = "not" }
16
+
17
+ /**
18
+ * Supported logical conjunctions in filter predicates.
19
+ * @enum
20
+ */
21
+ export enum OINODbSqlComparison { lt = "lt", le = "le", eq = "eq", ge = "ge", gt = "gt", like = "like" }
22
+
23
+ /**
24
+ * Class for recursively parsing of filters and printing them as SQL conditions.
25
+ * Supports three types of statements
26
+ * - comparison: (field)-lt|le|eq|ge|gt|like(value)
27
+ * - negation: -not(filter)
28
+ * - conjunction/disjunction: (filter)-and|or(filter)
29
+ * Supported conditions are comparisons (<, <=, =, >=, >) and substring match (LIKE).
30
+ *
31
+ */
32
+ export class OINODbSqlFilter {
33
+ 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
36
+
37
+ private _leftSide: OINODbSqlFilter | string
38
+ private _rightSide: OINODbSqlFilter | string
39
+ private _operator:OINODbSqlComparison|OINODbSqlBooleanOperation|null
40
+
41
+ /**
42
+ * Constructor of `OINODbSqlFilter`
43
+ * @param leftSide left side of the filter, either another filter or a column name
44
+ * @param operation operation of the filter, either `OINODbSqlComparison` or `OINODbSqlBooleanOperation`
45
+ * @param rightSide right side of the filter, either another filter or a value
46
+ */
47
+ constructor(leftSide:OINODbSqlFilter|string, operation:OINODbSqlComparison|OINODbSqlBooleanOperation|null, rightSide:OINODbSqlFilter|string) {
48
+ if (!(
49
+ ((operation === null) && (leftSide == "") && (rightSide == "")) ||
50
+ ((operation !== null) && (Object.values(OINODbSqlComparison).includes(operation as OINODbSqlComparison)) && (typeof(leftSide) == "string") && (leftSide != "") && (typeof(rightSide) == "string") && (rightSide != "")) ||
51
+ ((operation == OINODbSqlBooleanOperation.not) && (leftSide == "") && (rightSide instanceof OINODbSqlFilter)) ||
52
+ (((operation == OINODbSqlBooleanOperation.and) || (operation == OINODbSqlBooleanOperation.or)) && (leftSide instanceof OINODbSqlFilter) && (rightSide instanceof OINODbSqlFilter))
53
+ )) {
54
+ OINOLog.debug("Unsupported OINODbSqlFilter format!", {leftSide:leftSide, operation:operation, rightSide:rightSide})
55
+ throw new Error(OINO_ERROR_PREFIX + ": Unsupported OINODbSqlFilter format!")
56
+ }
57
+ this._leftSide = leftSide
58
+ this._operator = operation
59
+ this._rightSide = rightSide
60
+ }
61
+
62
+ /**
63
+ * Constructor for `OINODbSqlFilter` as parser of http parameter.
64
+ *
65
+ * @param filterString string representation of filter from HTTP-request
66
+ *
67
+ */
68
+ static parse(filterString: string):OINODbSqlFilter {
69
+ // OINOLog_debug("OINODbSqlFilter.constructor", {filterString:filterString})
70
+ if (!filterString) {
71
+ return new OINODbSqlFilter("", null, "")
72
+
73
+ } else {
74
+ let match = OINODbSqlFilter._filterComparisonRegex.exec(filterString)
75
+ if (match != null) {
76
+ return new OINODbSqlFilter(match[1], match[2].toLowerCase() as OINODbSqlComparison, match[3])
77
+ } else {
78
+ let match = OINODbSqlFilter._negationRegex.exec(filterString)
79
+ if (match != null) {
80
+ return new OINODbSqlFilter("", OINODbSqlBooleanOperation.not, OINODbSqlFilter.parse(match[3]))
81
+ } else {
82
+ let boolean_parts = OINOStr.splitByBrackets(filterString, true, false, '(', ')')
83
+ // OINOLog_debug("OINODbSqlFilter.constructor", {boolean_parts:boolean_parts})
84
+ if (boolean_parts.length == 3 && (boolean_parts[1].match(OINODbSqlFilter._booleanOperationRegex))) {
85
+ return new OINODbSqlFilter(OINODbSqlFilter.parse(boolean_parts[0]), boolean_parts[1].trim().toLowerCase().substring(1) as OINODbSqlBooleanOperation, OINODbSqlFilter.parse(boolean_parts[2]))
86
+
87
+ } else {
88
+ throw new Error(OINO_ERROR_PREFIX + ": Invalid filter '" + filterString + "'") // invalid filter could be a security risk, stop processing
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Construct a new `OINODbSqlFilter` as combination of (boolean and/or) of two filters.
97
+ *
98
+ * @param leftSide left side to combine
99
+ * @param operation boolean operation to use in combination
100
+ * @param rightSide right side to combine
101
+ *
102
+ */
103
+ static combine(leftSide:OINODbSqlFilter|undefined, operation:OINODbSqlBooleanOperation, rightSide:OINODbSqlFilter|undefined):OINODbSqlFilter|undefined {
104
+ if ((leftSide) && (!leftSide.isEmpty()) && (rightSide) && (!rightSide.isEmpty())) {
105
+ return new OINODbSqlFilter(leftSide, operation, rightSide)
106
+
107
+ } else if ((leftSide) && (!leftSide.isEmpty())) {
108
+ return leftSide
109
+
110
+ } else if ((rightSide) && (!rightSide.isEmpty())) {
111
+ return rightSide
112
+
113
+ } else {
114
+ return undefined
115
+ }
116
+ }
117
+
118
+
119
+ private _operatorToSql():string {
120
+ switch (this._operator) {
121
+ case "and": return " AND "
122
+ case "or": return " OR "
123
+ case "not": return "NOT "
124
+ case "lt": return " < "
125
+ case "le": return " <= "
126
+ case "eq": return " = "
127
+ case "ge": return " >= "
128
+ case "gt": return " > "
129
+ case "like": return " LIKE "
130
+ }
131
+ return " "
132
+ }
133
+
134
+ /**
135
+ * Does filter contain any valid conditions.
136
+ *
137
+ */
138
+ isEmpty():boolean {
139
+ return (this._leftSide == "") && (this._operator == null) && (this._rightSide == "")
140
+ }
141
+
142
+ /**
143
+ * Print filter as SQL condition based on the datamodel of the API.
144
+ *
145
+ * @param dataModel data model (and database) to use for formatting of values
146
+ *
147
+ */
148
+ toSql(dataModel:OINODbDataModel):string {
149
+ // OINOLog.debug("OINODbSqlFilter.toSql", {_leftSide:this._leftSide, _operator:this._operator, _rightSide:this._rightSide})
150
+ if (this.isEmpty()) {
151
+ return ""
152
+ }
153
+ let result:string = ""
154
+ let field:OINODbDataField|null = null
155
+ if (this._leftSide instanceof OINODbSqlFilter) {
156
+ result += this._leftSide.toSql(dataModel)
157
+ } else {
158
+ field = dataModel.findFieldByName(this._leftSide)
159
+ if (!field) {
160
+ OINOLog.error("OINODbSqlFilter.toSql: Invalid field!", {field:this._leftSide})
161
+ throw new Error(OINO_ERROR_PREFIX + ": OINODbSqlFilter.toSql - Invalid field '" + this._leftSide + "'") // invalid field name could be a security risk, stop processing
162
+ }
163
+ result += dataModel.api.db.printSqlColumnname(field?.name || this._leftSide)
164
+ }
165
+ result += this._operatorToSql()
166
+ if (this._rightSide instanceof OINODbSqlFilter) {
167
+ result += this._rightSide.toSql(dataModel)
168
+ } else {
169
+ const value = field!.deserializeCell(this._rightSide)
170
+ if (!value) {
171
+ OINOLog.error("OINODbSqlFilter.toSql: Invalid value!", {value:value})
172
+ throw new Error(OINO_ERROR_PREFIX + ": OINODbSqlFilter.toSql - Invalid value '" + value + "'") // invalid value could be a security risk, stop processing
173
+ }
174
+ result += field!.printCellAsSqlValue(value)
175
+ }
176
+ // OINOLog.debug("OINODbSqlFilter.toSql", {result:result})
177
+ return "(" + result + ")"
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Class for ordering select results on a number of columns.
183
+ *
184
+ */
185
+ export class OINODbSqlOrder {
186
+ private static _orderColumnRegex = /^\s*(\w+)\s?(ASC|DESC|\+|\-)?\s*?$/i
187
+
188
+ private _columns: string[]
189
+ private _descending: boolean[]
190
+
191
+ /**
192
+ * Constructor for `OINODbSqlOrder`.
193
+ *
194
+ * @param column_or_array single or array of columns to order on
195
+ * @param descending_or_array single or array of booleans if ordes is descending
196
+ *
197
+ */
198
+ constructor(column_or_array:string[]|string, descending_or_array:boolean[]|boolean) {
199
+ OINOLog.debug("OINODbSqlOrder.constructor", {columns:column_or_array, directions:descending_or_array})
200
+ if (Array.isArray(column_or_array)) {
201
+ this._columns = column_or_array
202
+ } else {
203
+ this._columns = [column_or_array]
204
+ }
205
+ if (Array.isArray(descending_or_array)) {
206
+ this._descending = descending_or_array
207
+ } else {
208
+ this._descending = [descending_or_array]
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Constructor for `OINODbSqlOrder` as parser of http parameter.
214
+ *
215
+ * @param orderString string representation of ordering from HTTP-request
216
+ *
217
+ */
218
+ static parse(orderString: string):OINODbSqlOrder {
219
+ let columns:string[] = []
220
+ let directions:boolean[] = []
221
+
222
+ const column_strings = orderString.split(',')
223
+
224
+ for (let i=0; i<column_strings.length; i++) {
225
+ let match = OINODbSqlOrder._orderColumnRegex.exec(column_strings[i])
226
+ if (match != null) {
227
+ columns.push(match[1])
228
+ const dir:string = (match[2] || "ASC").toUpperCase()
229
+ directions.push((dir == "DESC") || (dir == "-"))
230
+ }
231
+ }
232
+ return new OINODbSqlOrder(columns, directions)
233
+ }
234
+
235
+ /**
236
+ * Does filter contain any valid conditions.
237
+ *
238
+ */
239
+ isEmpty():boolean {
240
+ return (this._columns.length == 0)
241
+ }
242
+
243
+ /**
244
+ * Print order as SQL condition based on the datamodel of the API.
245
+ *
246
+ * @param dataModel data model (and database) to use for formatting of values
247
+ *
248
+ */
249
+ toSql(dataModel:OINODbDataModel):string {
250
+ if (this.isEmpty()) {
251
+ return ""
252
+ }
253
+ // OINOLog.debug("OINODbSqlOrder.toSql", {columns:this._columns, directions:this._directions})
254
+ let result:string = ""
255
+ for (let i=0; i<this._columns.length; i++) {
256
+ const field:OINODbDataField|null = dataModel.findFieldByName(this._columns[i])
257
+ if (!field) {
258
+ OINOLog.error("OINODbSqlOrder.toSql: Invalid field!", {field:this._columns[i]})
259
+ throw new Error(OINO_ERROR_PREFIX + ": OINODbSqlOrder.toSql - Invalid field '" + this._columns[i] + "'") // invalid field name could be a security risk, stop processing
260
+ }
261
+ if (result) {
262
+ result += ","
263
+ }
264
+ result += dataModel.api.db.printSqlColumnname(field.name) + " "
265
+ if (this._descending[i]) {
266
+ result += "DESC"
267
+ } else {
268
+ result += "ASC"
269
+ }
270
+ }
271
+ // OINOLog.debug("OINODbSqlOrder.toSql", {result:result})
272
+ return result
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Class for limiting the number of results.
278
+ *
279
+ */
280
+ export class OINODbSqlLimit {
281
+ private static _limitRegex = /^(\d+)(\spage\s)?(\d+)?$/i
282
+
283
+ private _limit: number
284
+ private _page: number
285
+
286
+ /**
287
+ * Constructor for `OINODbSqlLimit`.
288
+ *
289
+ * @param limit maximum number of items to return
290
+ * @param page page number to return starting from 1
291
+ *
292
+ */
293
+ constructor(limit: number, page: number = -1) {
294
+ this._limit = limit
295
+ this._page = page
296
+ }
297
+ /**
298
+ * Constructor for `OINODbSqlLimit` as parser of http parameter.
299
+ *
300
+ * @param limitString string representation of limit from HTTP-request
301
+ *
302
+ */
303
+ static parse(limitString: string):OINODbSqlLimit {
304
+ let match = OINODbSqlLimit._limitRegex.exec(limitString)
305
+ if ((match != null) && (match.length == 4)) {
306
+ return new OINODbSqlLimit(Number.parseInt(match[1]), Number.parseInt(match[3]))
307
+ } else if (match != null) {
308
+ return new OINODbSqlLimit(Number.parseInt(match[1]))
309
+ } else {
310
+ return new OINODbSqlLimit(-1)
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Does filter contain any valid conditions.
316
+ *
317
+ */
318
+ isEmpty():boolean {
319
+ return (this._limit <= 0)
320
+ }
321
+
322
+ /**
323
+ * Print order as SQL condition based on the datamodel of the API.
324
+ *
325
+ * @param dataModel data model (and database) to use for formatting of values
326
+ *
327
+ */
328
+ toSql(dataModel:OINODbDataModel):string {
329
+ if (this.isEmpty()) {
330
+ return ""
331
+ }
332
+ let result:string = this._limit.toString()
333
+ if (this._page > 0) {
334
+ result += " OFFSET " + (this._limit * (this._page-1) + 1).toString()
335
+ }
336
+ return result
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Supported aggregation functions in OINODbSqlAggregate.
342
+ * @enum
343
+ */
344
+ export enum OINODbSqlAggregateFunctions { count = "count", sum = "sum", avg = "avg", min = "min", max = "max" }
345
+
346
+ /**
347
+ * Class for limiting the number of results.
348
+ *
349
+ */
350
+ export class OINODbSqlAggregate {
351
+ private static _aggregateRegex:RegExp = new RegExp("^(count|sum|avg|min|max)\\(([" + OINO_FIELD_NAME_CHARS + "]+)\\)$", "mi")
352
+
353
+ private _functions: OINODbSqlAggregateFunctions[]
354
+ private _fields: string[]
355
+
356
+ /**
357
+ * Constructor for `OINODbSqlAggregate`.
358
+ *
359
+ * @param functions aggregate function to use
360
+ * @param fields fields to aggregate
361
+ *
362
+ */
363
+ constructor(functions: OINODbSqlAggregateFunctions[], fields: string[]) {
364
+ this._functions = functions
365
+ this._fields = fields
366
+ }
367
+ /**
368
+ * Constructor for `OINODbSqlAggregate` as parser of http parameter.
369
+ *
370
+ * @param aggregatorString string representation of limit from HTTP-request
371
+ *
372
+ */
373
+ static parse(aggregatorString: string):OINODbSqlAggregate {
374
+ let funtions:OINODbSqlAggregateFunctions[] = []
375
+ let fields:string[] = []
376
+ const aggregator_parts = aggregatorString.split(',')
377
+ for (let i=0; i<aggregator_parts.length; i++) {
378
+ let match = OINODbSqlAggregate._aggregateRegex.exec(aggregator_parts[i])
379
+ // OINOLog.debug("OINODbSqlAggregate.parse - next aggregator", {aggregator: aggregator_parts[i], match:match})
380
+ if ((match != null) && (match.length == 3)) {
381
+ funtions.push(match[1] as OINODbSqlAggregateFunctions)
382
+ fields.push(match[2])
383
+ }
384
+ }
385
+ return new OINODbSqlAggregate(funtions, fields)
386
+ }
387
+
388
+ /**
389
+ * Does filter contain any valid conditions.
390
+ *
391
+ */
392
+ isEmpty():boolean {
393
+ return (this._functions.length <= 0)
394
+ }
395
+
396
+ /**
397
+ * Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
398
+ *
399
+ * @param dataModel data model (and database) to use for formatting of values
400
+ *
401
+ */
402
+ toSql(dataModel:OINODbDataModel):string {
403
+ if (this.isEmpty()) {
404
+ return ""
405
+ }
406
+ let result:string = ""
407
+ for (let i=0; i<dataModel.fields.length; i++) {
408
+ if (this._fields.includes(dataModel.fields[i].name) == false) {
409
+ result += dataModel.fields[i].printSqlColumnName() + ","
410
+ }
411
+ }
412
+ OINOLog.debug("OINODbSqlAggregate.toSql", {result:result})
413
+ return result.substring(0, result.length-1)
414
+ }
415
+
416
+ /**
417
+ * Print non-aggregated fields as SQL GROUP BY-condition based on the datamodel of the API.
418
+ *
419
+ * @param dataModel data model (and database) to use for formatting of values
420
+ *
421
+ */
422
+ printSqlColumnNames(dataModel:OINODbDataModel):string {
423
+ let result:string = ""
424
+ for (let i=0; i<dataModel.fields.length; i++) {
425
+ const aggregate_index = this._fields.indexOf(dataModel.fields[i].name)
426
+ const col_name = dataModel.fields[i].printSqlColumnName()
427
+ if (aggregate_index >= 0) {
428
+ result += this._functions[aggregate_index] + "(" + col_name + ") as " + col_name + ","
429
+ } else {
430
+ result += col_name + ","
431
+ }
432
+ }
433
+ OINOLog.debug("OINODbSqlAggregate.printSqlColumnNames", {result:result})
434
+ return result.substring(0, result.length-1)
435
+ }
436
+ }
437
+
438
+