@sap/cds 9.0.4 → 9.2.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/CHANGELOG.md +68 -0
- package/bin/deploy.js +29 -0
- package/bin/serve.js +1 -5
- package/lib/compile/etc/csv.js +11 -6
- package/lib/compile/for/lean_drafts.js +29 -7
- package/lib/compile/load.js +8 -5
- package/lib/compile/to/hdbtabledata.js +1 -1
- package/lib/dbs/cds-deploy.js +5 -34
- package/lib/env/cds-env.js +2 -1
- package/lib/env/cds-requires.js +4 -1
- package/lib/env/defaults.js +0 -11
- package/lib/env/schemas/cds-rc.js +218 -6
- package/lib/index.js +38 -38
- package/lib/log/cds-error.js +12 -11
- package/lib/log/format/json.js +1 -1
- package/lib/ql/SELECT.js +31 -0
- package/lib/ql/resolve.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/req/request.js +1 -1
- package/lib/req/validate.js +17 -19
- package/lib/srv/cds.Service.js +18 -28
- package/lib/srv/middlewares/auth/ias-auth.js +29 -2
- package/lib/srv/middlewares/auth/jwt-auth.js +11 -1
- package/lib/srv/middlewares/auth/xssec.js +1 -1
- package/lib/srv/srv-models.js +1 -1
- package/lib/srv/srv-tx.js +2 -2
- package/lib/utils/cds-utils.js +35 -2
- package/lib/utils/csv-reader.js +1 -1
- package/lib/utils/inflect.js +2 -2
- package/lib/utils/tar.js +60 -23
- package/lib/utils/version.js +18 -0
- package/libx/_runtime/cds.js +1 -1
- package/libx/_runtime/common/aspects/any.js +1 -23
- package/libx/_runtime/common/generic/crud.js +1 -3
- package/libx/_runtime/common/generic/input.js +113 -52
- package/libx/_runtime/common/generic/sorting.js +1 -1
- package/libx/_runtime/common/generic/temporal.js +0 -6
- package/libx/_runtime/common/utils/draft.js +1 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +1 -1
- package/libx/_runtime/common/utils/propagateForeignKeys.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -2
- package/libx/_runtime/common/utils/structured.js +2 -2
- package/libx/_runtime/common/utils/templateProcessor.js +0 -5
- package/libx/_runtime/common/utils/vcap.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +529 -143
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -2
- package/libx/_runtime/messaging/service.js +1 -1
- package/libx/_runtime/remote/utils/client.js +2 -1
- package/libx/common/assert/utils.js +2 -12
- package/libx/common/utils/streaming.js +4 -9
- package/libx/http/location.js +1 -0
- package/libx/odata/ODataAdapter.js +47 -43
- package/libx/odata/index.js +1 -1
- package/libx/odata/middleware/batch.js +6 -2
- package/libx/odata/middleware/create.js +1 -1
- package/libx/odata/middleware/error.js +27 -17
- package/libx/odata/middleware/operation.js +15 -21
- package/libx/odata/middleware/stream.js +1 -1
- package/libx/odata/parse/afterburner.js +22 -8
- package/libx/odata/parse/cqn2odata.js +16 -10
- package/libx/odata/parse/grammar.peggy +185 -134
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +1 -36
- package/libx/odata/utils/metadata.js +34 -1
- package/libx/odata/utils/odataBind.js +2 -1
- package/libx/odata/utils/result.js +22 -20
- package/libx/queue/index.js +7 -4
- package/libx/rest/RestAdapter.js +1 -2
- package/libx/rest/middleware/create.js +5 -2
- package/package.json +2 -2
- package/server.js +1 -1
- package/bin/deploy/to-hana.js +0 -1
- package/lib/utils/check-version.js +0 -9
- package/lib/utils/unit.js +0 -19
- package/libx/_runtime/cds-services/util/assert.js +0 -181
- package/libx/_runtime/types/api.js +0 -129
- package/libx/common/assert/validation.js +0 -109
|
@@ -177,7 +177,9 @@
|
|
|
177
177
|
|
|
178
178
|
const _handleApply = (cqn, apply) => {
|
|
179
179
|
let newCqn = _convertApply({ from: cqn.from }, apply)
|
|
180
|
-
|
|
180
|
+
//Delete apply as it was successfully converted & to not spill into final query
|
|
181
|
+
delete cqn.apply
|
|
182
|
+
//Normal query options have to be applied after convert, as _convertApply calls itself recursively and else normal options might be redundant in nested SELECTs
|
|
181
183
|
if (Array.isArray(newCqn)) {
|
|
182
184
|
for (let i = 0; i < newCqn.length; i++) {
|
|
183
185
|
newCqn[i] = _addNormalQueryOptions(newCqn[i], cqn)
|
|
@@ -185,99 +187,104 @@
|
|
|
185
187
|
} else {
|
|
186
188
|
newCqn = _addNormalQueryOptions(newCqn, cqn)
|
|
187
189
|
}
|
|
188
|
-
|
|
190
|
+
if (newCqn.SELECT?.recurse && cqn.where) {
|
|
191
|
+
const where = cqn.where
|
|
192
|
+
delete cqn.where
|
|
193
|
+
const columns = newCqn.SELECT.columns
|
|
194
|
+
delete newCqn.SELECT.columns
|
|
195
|
+
newCqn = { SELECT: { from: newCqn, where, columns } }
|
|
196
|
+
}
|
|
189
197
|
return newCqn
|
|
190
198
|
}
|
|
191
199
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
function isQueryWithAggregation(cqn) {
|
|
201
|
+
return cqn.SELECT.groupBy ||
|
|
202
|
+
cqn.SELECT.columns?.some(col => col && typeof col === 'object' && 'func' in col)
|
|
203
|
+
}
|
|
195
204
|
|
|
196
|
-
|
|
205
|
+
// topCqn is not allowed to be mutated as that would spill in case of concat transformations
|
|
206
|
+
const _addNormalQueryOptions = (cqn, topCqn) => {
|
|
207
|
+
const QUERY_WITH_AGGREGATION = isQueryWithAggregation(cqn);
|
|
197
208
|
if (
|
|
198
209
|
(topCqn.columns && topCqn.columns[0].as === '$count') ||
|
|
199
|
-
|
|
210
|
+
//In QUERY_WITH_AGGREGATION topCqn.where is a having and thus no further nesting needed
|
|
211
|
+
(!QUERY_WITH_AGGREGATION && topCqn.where && (cqn.SELECT.where || cqn.SELECT.limit)) ||
|
|
200
212
|
(cqn.SELECT.limit && topCqn.limit) ||
|
|
201
213
|
(cqn.SELECT.orderBy && topCqn.orderBy) ||
|
|
202
214
|
(cqn.SELECT.search && topCqn.search)
|
|
203
215
|
) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
for (const queryOption in topCqn) {
|
|
232
|
-
if (queryOption === 'columns') {
|
|
233
|
-
const mergedColumns = []
|
|
234
|
-
|
|
235
|
-
// Add aggregation columns
|
|
236
|
-
if (newCqn.columns.length) mergedColumns.push(...newCqn.columns)
|
|
237
|
-
|
|
238
|
-
// Use columns from parsed CQN if there's no grouping
|
|
239
|
-
if (!newCqn.groupBy || !newCqn.groupBy.length) {
|
|
240
|
-
for (const column of topCqn.columns)
|
|
241
|
-
if (!mergedColumns.some(_compareRefs(column))) mergedColumns.push(column)
|
|
216
|
+
//Have topCqn as new cqn but assign existing cqn as SELECT.from.SELECT
|
|
217
|
+
const newCqn = Object.assign({}, topCqn)
|
|
218
|
+
newCqn.from = cqn;
|
|
219
|
+
cqn = {SELECT: newCqn}
|
|
220
|
+
} else {
|
|
221
|
+
let from = cqn.SELECT.from;
|
|
222
|
+
let columns = cqn.SELECT.columns;
|
|
223
|
+
//Needed in case topCqn.where is a having and not a where
|
|
224
|
+
let originalWhere = cqn.SELECT.where;
|
|
225
|
+
// cqn.SELECT must be the base as else concat queries would be overridden
|
|
226
|
+
cqn.SELECT = Object.assign(cqn.SELECT, topCqn)
|
|
227
|
+
//Reapplied to ensure SELECT.from.SELECT is not lost
|
|
228
|
+
cqn.SELECT.from = from;
|
|
229
|
+
//Reapplied as topCqn.columns have special handling further down below to be merged into existing columns
|
|
230
|
+
cqn.SELECT.columns = columns;
|
|
231
|
+
|
|
232
|
+
// When a group by already exists or a column is aggregated and a where follows it is a having
|
|
233
|
+
// In the scenario where a topCqn.where exists and the query is not aggregating, topCqn.where is used as the cqn.SELECT.where.
|
|
234
|
+
// Those cannot conflict as the top if statement ensures that in this case a SELECT.from.SELECT is created
|
|
235
|
+
if (QUERY_WITH_AGGREGATION && topCqn.where) {
|
|
236
|
+
const newHaving = topCqn.where
|
|
237
|
+
.map(_remapFunc(cqn.SELECT.columns))
|
|
238
|
+
//In a query with only aggregation groupBy would be undefined causing a crash
|
|
239
|
+
.map(_replaceNullRef(cqn.SELECT.groupBy ?? []));
|
|
240
|
+
if (!cqn.SELECT.having) {
|
|
241
|
+
cqn.SELECT.having = newHaving
|
|
242
242
|
} else {
|
|
243
|
-
|
|
243
|
+
//The existing cqn.SELECT.having was already mapped in _convertApply
|
|
244
|
+
cqn.SELECT.having = [
|
|
245
|
+
{xpr: cqn.SELECT.having},
|
|
246
|
+
'and',
|
|
247
|
+
{xpr: newHaving}
|
|
248
|
+
];
|
|
244
249
|
}
|
|
250
|
+
//Correct back to original where
|
|
251
|
+
cqn.SELECT.where = originalWhere;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
245
254
|
|
|
246
|
-
|
|
255
|
+
if (topCqn.columns) {
|
|
256
|
+
const mergedColumns = [];
|
|
257
|
+
if (!cqn.SELECT.columns) {
|
|
258
|
+
cqn.SELECT.columns = [];
|
|
259
|
+
}
|
|
260
|
+
// Add aggregation columns
|
|
261
|
+
if (cqn.SELECT.columns.length) {
|
|
262
|
+
mergedColumns.push(...cqn.SELECT.columns)
|
|
263
|
+
}
|
|
247
264
|
|
|
248
|
-
|
|
249
|
-
|
|
265
|
+
// Use columns from parsed CQN if there's no grouping
|
|
266
|
+
if (!cqn.SELECT.groupBy || !cqn.SELECT.groupBy.length) {
|
|
267
|
+
for (const column of topCqn.columns) {
|
|
268
|
+
if (!mergedColumns.some(_compareRefs(column))) {
|
|
269
|
+
mergedColumns.push(column)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
_mergeColumnForests(mergedColumns, topCqn.columns)
|
|
250
274
|
}
|
|
275
|
+
// topCqn is later applied to cqn.SELECT and thus the merged columns have to be assigned to them
|
|
276
|
+
cqn.SELECT.columns = mergedColumns
|
|
251
277
|
}
|
|
252
278
|
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
newCqn.having = newCqn.where
|
|
257
|
-
.map(_remapFunc(newCqn.columns))
|
|
258
|
-
.map(_replaceNullRef(newCqn.groupBy))
|
|
259
|
-
delete newCqn.where
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (!newCqn.columns.length) delete newCqn.columns
|
|
263
|
-
return { SELECT: newCqn }
|
|
279
|
+
if (!cqn.SELECT.columns?.length) delete cqn.SELECT.columns
|
|
280
|
+
return cqn
|
|
264
281
|
}
|
|
265
282
|
|
|
266
283
|
const _convertApply = (cqn, apply) => {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (cqn.apply) delete cqn.apply
|
|
284
|
+
delete cqn.apply
|
|
270
285
|
if (apply.identity && cqn.from.SELECT) cqn = cqn.from.SELECT
|
|
271
|
-
if (
|
|
272
|
-
|
|
273
|
-
(apply.where && cqn.where) ||
|
|
274
|
-
(apply.search && cqn.search) ||
|
|
275
|
-
(apply.limit && cqn.limit) ||
|
|
276
|
-
(apply.orderBy && cqn.orderBy)
|
|
277
|
-
) {
|
|
278
|
-
cqn.from = { SELECT: { ...cqn } }
|
|
279
|
-
}
|
|
280
|
-
|
|
286
|
+
if (apply.apply) cqn.from = { SELECT: { ...cqn } }
|
|
287
|
+
|
|
281
288
|
const _toplevels = cqn => {
|
|
282
289
|
cqn.recurse = { ref: ['parent'] }
|
|
283
290
|
|
|
@@ -370,36 +377,60 @@
|
|
|
370
377
|
if (apply.search) cqn.search = apply.search
|
|
371
378
|
if (apply.limit) cqn.limit = apply.limit
|
|
372
379
|
if (apply.orderBy) cqn.orderBy = apply.orderBy
|
|
380
|
+
|
|
373
381
|
if (apply.groupBy)
|
|
374
382
|
cqn.groupBy = apply.groupBy.reduce((groupBy, column) => {
|
|
375
383
|
if (!groupBy.some(_compareRefs(column))) groupBy.push(column)
|
|
376
384
|
return groupBy
|
|
377
385
|
}, [])
|
|
378
386
|
|
|
379
|
-
if (apply.aggregate && apply.aggregate.length)
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (
|
|
387
|
+
if (apply.aggregate && apply.aggregate.length) {
|
|
388
|
+
cqn.columns = (cqn.columns || []).concat(apply.aggregate);
|
|
389
|
+
}
|
|
390
|
+
if (cqn.groupBy && cqn.groupBy.length) {
|
|
391
|
+
const groupByColumns = cqn.groupBy.reduce((expandedGroupByColumns, column) => {
|
|
392
|
+
if (column.ref && column.ref.length > 1) {
|
|
393
|
+
const columnToExpand = { ref: [...column.ref] }
|
|
394
|
+
if (column.expand && column.expand.length) columnToExpand.expand = [...column.expand]
|
|
395
|
+
if (column.as) columnToExpand.as = column.as
|
|
396
|
+
_expand(expandedGroupByColumns, columnToExpand, true)
|
|
397
|
+
} else expandedGroupByColumns.push({ ...column })
|
|
398
|
+
return expandedGroupByColumns
|
|
399
|
+
}, [])
|
|
400
|
+
cqn.columns = (cqn.columns || []).concat(groupByColumns);
|
|
401
|
+
}
|
|
402
|
+
if (apply.having) {
|
|
403
|
+
cqn.having = apply.having
|
|
404
|
+
.map(_remapFunc(cqn.columns))
|
|
405
|
+
.map(_replaceNullRef(cqn.groupBy ?? []))
|
|
406
|
+
}
|
|
383
407
|
|
|
384
|
-
if (apply.
|
|
385
|
-
|
|
386
|
-
|
|
408
|
+
if (apply.apply) {
|
|
409
|
+
_convertApply(cqn.from.SELECT, apply.apply)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (Array.isArray(apply.concat)) {
|
|
413
|
+
const concatenatedQueries = []
|
|
414
|
+
// Refrain from nesting if no property other than 'from' was transferred from apply to cqn
|
|
415
|
+
// With concat the current level SELECT is the base for multiple queries, and is used as the FROM clause in those,
|
|
416
|
+
// thus it has to be wrapped in a SELECT for a proper CQN structure, like SELECT.from.SELECT.<cqn-props>
|
|
417
|
+
// However if concat is the first transformation the SELECT just has the from and thus SELECT.from.SELECT nesting can be avoided
|
|
418
|
+
const newFrom = Object.keys(cqn).length === 1
|
|
419
|
+
? cqn.from
|
|
420
|
+
: { SELECT: cqn }
|
|
387
421
|
for (let select of apply.concat) {
|
|
388
|
-
select.from =
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
422
|
+
select.from = newFrom
|
|
423
|
+
if (select.apply) {
|
|
424
|
+
const nextCqn = _convertApply(select, select.apply)
|
|
425
|
+
if (Array.isArray(nextCqn)) {
|
|
426
|
+
concatenatedQueries.push(...nextCqn)
|
|
427
|
+
} else {
|
|
428
|
+
concatenatedQueries.push(nextCqn)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
397
431
|
}
|
|
398
|
-
|
|
399
|
-
cqn = additionalQueries
|
|
432
|
+
return concatenatedQueries
|
|
400
433
|
}
|
|
401
|
-
|
|
402
|
-
if (Array.isArray(cqn)) return cqn
|
|
403
434
|
return { SELECT: cqn }
|
|
404
435
|
}
|
|
405
436
|
|
|
@@ -527,7 +558,7 @@
|
|
|
527
558
|
= "$count" {count = true}
|
|
528
559
|
/ rv:$("$ref"/"$value") {return !TECHNICAL_OPTS.includes(rv) && {from: {ref: [rv]}}}
|
|
529
560
|
/ head:(
|
|
530
|
-
(identifier filter:(OPEN CLOSE/OPEN
|
|
561
|
+
(identifier filter:(OPEN CLOSE/OPEN guardedargs CLOSE)? !segment) / val:segment{return [val]}
|
|
531
562
|
)? tail:((s:"/" {return s;}) path?)? {
|
|
532
563
|
tail = tail && tail[1]
|
|
533
564
|
if (!head && !tail) {
|
|
@@ -575,6 +606,14 @@
|
|
|
575
606
|
return args
|
|
576
607
|
}
|
|
577
608
|
|
|
609
|
+
guardedargs
|
|
610
|
+
= ref:ref o"="o val:guardedval more:( COMMA guardedargs )? {
|
|
611
|
+
const args = [ ref, '=', val ]
|
|
612
|
+
if (more) args.push ('and', ...more[1])
|
|
613
|
+
return args
|
|
614
|
+
}
|
|
615
|
+
/ val:guardedval {return [val]}
|
|
616
|
+
|
|
578
617
|
// `path` cannot be used in hierarchy functions, hence use a simpler definition
|
|
579
618
|
simplePath
|
|
580
619
|
= i:identifier filter:(OPEN CLOSE/OPEN a:args CLOSE{ return a })? tail:("/" s:simplePath{ return s })*{
|
|
@@ -798,38 +837,6 @@
|
|
|
798
837
|
count
|
|
799
838
|
= val:bool { if(val) SELECT.count = true }
|
|
800
839
|
|
|
801
|
-
transformations
|
|
802
|
-
= mainTransformation:trafo additionalTransformation:("/" t2:trafo {
|
|
803
|
-
return t2
|
|
804
|
-
})* {
|
|
805
|
-
if(mainTransformation === undefined) return
|
|
806
|
-
additionalTransformation = (Array.isArray(additionalTransformation)) ? additionalTransformation : [additionalTransformation]
|
|
807
|
-
// Loop through additionalTransformation
|
|
808
|
-
// Loop through each element, add it to current level, if element is already part of result, increase level
|
|
809
|
-
for(let trafos of additionalTransformation) {
|
|
810
|
-
for(const trafo in trafos) {
|
|
811
|
-
if (trafo === 'limit' && trafos.limit && mainTransformation.limit && mainTransformation.limit.offset && trafos.limit.rows)
|
|
812
|
-
mainTransformation.limit.rows = trafos.limit.rows
|
|
813
|
-
else if(
|
|
814
|
-
mainTransformation[trafo] ||
|
|
815
|
-
(trafo === 'groupBy' && (mainTransformation.where || mainTransformation.search)) ||
|
|
816
|
-
(trafo === 'aggregate' && 'groupBy' in mainTransformation && !('groupBy' in trafos))
|
|
817
|
-
) {
|
|
818
|
-
let _apply = mainTransformation
|
|
819
|
-
mainTransformation = { apply: _apply }
|
|
820
|
-
if (trafo === 'limit' && trafos[trafo].offset && _apply.limit && _apply.limit.offset && _apply.limit.offset.val)
|
|
821
|
-
trafos[trafo].offset += _apply.limit.offset.val
|
|
822
|
-
|
|
823
|
-
_apply = mainTransformation
|
|
824
|
-
_apply[trafo] = trafos[trafo]
|
|
825
|
-
} else {
|
|
826
|
-
mainTransformation[trafo] = trafos[trafo]
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
return {apply: mainTransformation}
|
|
831
|
-
}
|
|
832
|
-
|
|
833
840
|
deltaToken = "$deltatoken=" o token:([^&]*) { return token }
|
|
834
841
|
|
|
835
842
|
valList "value with double-quoted string"
|
|
@@ -840,8 +847,8 @@
|
|
|
840
847
|
|
|
841
848
|
aliasedParamVal = val / jsonObject / jsonArray / "[" list:innerListParam "]" { return { list } }
|
|
842
849
|
|
|
843
|
-
custom = k:$([
|
|
844
|
-
_custom(k, v)
|
|
850
|
+
custom = k:$([a-zA-Z0-9_.~!\[\]\-]+) "="? v:$([^&]*)? {
|
|
851
|
+
return _custom(k, v)
|
|
845
852
|
}
|
|
846
853
|
|
|
847
854
|
aliasedParam "an aliased parameter (@param)" = "@" i:identifier { return "@" + i }
|
|
@@ -904,7 +911,7 @@
|
|
|
904
911
|
}
|
|
905
912
|
return { ref:[ head, ...tail ] }
|
|
906
913
|
}
|
|
907
|
-
|
|
914
|
+
|
|
908
915
|
val
|
|
909
916
|
= val:bool {return {val}}
|
|
910
917
|
/ val:date {return {val}}
|
|
@@ -916,6 +923,10 @@
|
|
|
916
923
|
/ val:aliasedParam {return {val}}
|
|
917
924
|
/ null
|
|
918
925
|
|
|
926
|
+
guardedval
|
|
927
|
+
= val:val {return val}
|
|
928
|
+
/ val:simpleword {throw Object.assign(new Error(`Invalid value: ${val}`), { statusCode: 400 })};
|
|
929
|
+
|
|
919
930
|
null "null" = "null" {return {val: null }}
|
|
920
931
|
|
|
921
932
|
// REVISIT why not JSON.parse() and return JS object?
|
|
@@ -965,8 +976,45 @@
|
|
|
965
976
|
//
|
|
966
977
|
// ---------- Transformations ------------
|
|
967
978
|
// Odata spec: http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/odata-data-aggregation-ext-v4.0.html
|
|
968
|
-
|
|
969
|
-
|
|
979
|
+
|
|
980
|
+
transformations
|
|
981
|
+
= mainTransformation:transformation additionalTransformation:("/" t2:transformation {
|
|
982
|
+
return t2
|
|
983
|
+
})* {
|
|
984
|
+
if(mainTransformation === undefined) return
|
|
985
|
+
additionalTransformation = Array.isArray(additionalTransformation) ? additionalTransformation : [additionalTransformation]
|
|
986
|
+
// Loop through each element, add it to current level, if element is already part of result, increase level
|
|
987
|
+
for(let trafos of additionalTransformation) {
|
|
988
|
+
for(const transformation in trafos) {
|
|
989
|
+
if (transformation === 'where' && (mainTransformation.groupBy || mainTransformation.aggregate) && !mainTransformation.having) {
|
|
990
|
+
//When a group by or aggregate preceed a where, the where is a having
|
|
991
|
+
mainTransformation.having = trafos[transformation]
|
|
992
|
+
}
|
|
993
|
+
else if (transformation === 'limit' && mainTransformation.limit?.offset && trafos.limit.rows) {
|
|
994
|
+
//To avoid nesting in case first trafo is skip second is top, than they should be merged
|
|
995
|
+
//Inverse case first top than skip should not be nested as second skip will redcue the amount of returned items
|
|
996
|
+
mainTransformation.limit.rows = trafos.limit.rows
|
|
997
|
+
}
|
|
998
|
+
else if (transformation === 'limit' && mainTransformation.limit?.offset?.val && trafos.limit.offset?.val) {
|
|
999
|
+
//To avoid nesting in case of two subsequent skips as they can be merged
|
|
1000
|
+
mainTransformation.limit.offset.val += trafos.limit.offset.val
|
|
1001
|
+
}
|
|
1002
|
+
else if(
|
|
1003
|
+
mainTransformation[transformation] ||
|
|
1004
|
+
//Case like aggregation follows with a / after groupBy - if they should be on the same level the aggregation is part of groupBy
|
|
1005
|
+
(transformation === 'aggregate' && mainTransformation.groupBy && !trafos.groupBy)
|
|
1006
|
+
) {
|
|
1007
|
+
mainTransformation = { apply: mainTransformation }
|
|
1008
|
+
mainTransformation[transformation] = trafos[transformation]
|
|
1009
|
+
} else {
|
|
1010
|
+
mainTransformation[transformation] = trafos[transformation]
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return {apply: mainTransformation}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
transformation
|
|
970
1018
|
= (
|
|
971
1019
|
"aggregate" agg:aggregateTrafo{return agg} /
|
|
972
1020
|
"groupby" group:groupbyTrafo{return group} /
|
|
@@ -1170,10 +1218,13 @@
|
|
|
1170
1218
|
|
|
1171
1219
|
doubleQuotedString "a doubled quoted string"
|
|
1172
1220
|
= '"' s:$('\\"' / '\\\\' / [^"])* '"'
|
|
1173
|
-
{return s.replace(
|
|
1221
|
+
{return s.replace(/\\(["\\])/g, '$1')}
|
|
1174
1222
|
|
|
1175
1223
|
word "a string"
|
|
1176
1224
|
= $([^ \t\n()"&;]+)
|
|
1225
|
+
|
|
1226
|
+
simpleword "a string and/or number"
|
|
1227
|
+
= $([a-zA-Z0-9]+)
|
|
1177
1228
|
|
|
1178
1229
|
time "a time"
|
|
1179
1230
|
= $([0-9][0-9]":"[0-9][0-9]":"[0-9][0-9])
|
|
@@ -1199,7 +1250,7 @@
|
|
|
1199
1250
|
= s:$( [+-]? [0-9]+ ) { return parseInt(s) }
|
|
1200
1251
|
|
|
1201
1252
|
identifier "an identifier"
|
|
1202
|
-
= !bool !guid s:$([_a-zA-Z][_a-zA-Z0-9"."\
|
|
1253
|
+
= !bool !guid s:$([_a-zA-Z][_a-zA-Z0-9"."\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]*) { return s }
|
|
1203
1254
|
|
|
1204
1255
|
guid "a guid"
|
|
1205
1256
|
= $( hex16 hex16 "-"? hex16 "-"? hex16 "-"? hex16 "-"? hex16 hex16 hex16 )
|
|
@@ -1214,7 +1265,7 @@
|
|
|
1214
1265
|
= $( [a-zA-Z0-9-"."_~!$'()*+,;=:@"/""?"]+ )
|
|
1215
1266
|
|
|
1216
1267
|
binary "a binary" // > url-safe base64
|
|
1217
|
-
= "binary'" s:$([a-zA-Z0-
|
|
1268
|
+
= "binary'" s:$([a-zA-Z0-9_\-]+ ("=="/"=")?) "'" { return cds.env.features.base64_binaries ? standardBase64(s) : Buffer.from(s, 'base64') }
|
|
1218
1269
|
|
|
1219
1270
|
//
|
|
1220
1271
|
// ---------- Punctuation ----------
|