@sap/cds 8.5.0 → 8.6.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 +54 -3
- package/_i18n/i18n.properties +4 -7
- package/eslint.config.mjs +1 -1
- package/lib/compile/etc/properties.js +2 -2
- package/lib/compile/for/java.js +15 -3
- package/lib/compile/for/lean_drafts.js +44 -34
- package/lib/compile/for/nodejs.js +19 -10
- package/lib/compile/minify.js +2 -4
- package/lib/compile/parse.js +106 -72
- package/lib/compile/to/edm.js +19 -9
- package/lib/compile/to/hana.js +25 -21
- package/lib/compile/to/sql.js +15 -8
- package/lib/core/linked-csn.js +10 -4
- package/lib/dbs/cds-deploy.js +2 -2
- package/lib/env/cds-env.js +76 -66
- package/lib/env/defaults.js +1 -0
- package/lib/i18n/bundles.js +2 -1
- package/lib/i18n/localize.js +2 -2
- package/lib/index.js +24 -18
- package/lib/ql/CREATE.js +11 -6
- package/lib/ql/DELETE.js +12 -9
- package/lib/ql/DROP.js +15 -8
- package/lib/ql/INSERT.js +19 -14
- package/lib/ql/SELECT.js +95 -168
- package/lib/ql/UPDATE.js +23 -14
- package/lib/ql/UPSERT.js +15 -2
- package/lib/ql/Whereable.js +44 -118
- package/lib/ql/cds-ql.js +222 -28
- package/lib/ql/{Query.js → cds.ql-Query.js} +52 -41
- package/lib/ql/cds.ql-predicates.js +133 -0
- package/lib/ql/cds.ql-projections.js +111 -0
- package/lib/ql/cqn.d.ts +146 -0
- package/lib/srv/cds-connect.js +3 -3
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/cds.Service.js +132 -0
- package/lib/srv/{srv-api.js → cds.ServiceClient.js} +16 -71
- package/lib/srv/cds.ServiceProvider.js +20 -0
- package/lib/srv/factory.js +20 -8
- package/lib/srv/protocols/hcql.js +2 -3
- package/lib/srv/protocols/index.js +3 -3
- package/lib/srv/srv-dispatch.js +7 -6
- package/lib/srv/srv-handlers.js +103 -113
- package/lib/srv/srv-methods.js +14 -14
- package/lib/srv/srv-tx.js +5 -3
- package/lib/utils/cds-utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +3 -3
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/service.js +25 -0
- package/libx/_runtime/common/generic/auth/index.js +5 -0
- package/libx/_runtime/common/generic/auth/restrict.js +36 -14
- package/libx/_runtime/common/generic/auth/service.js +24 -0
- package/libx/_runtime/common/generic/auth/utils.js +14 -6
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/utils/cqn.js +1 -2
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/generateOnCond.js +7 -3
- package/libx/_runtime/common/utils/postProcess.js +4 -1
- package/libx/_runtime/common/utils/restrictions.js +1 -0
- package/libx/_runtime/fiori/lean-draft.js +53 -42
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
- package/libx/_runtime/remote/Service.js +2 -0
- package/libx/_runtime/remote/utils/client.js +12 -0
- package/libx/odata/ODataAdapter.js +2 -1
- package/libx/odata/index.js +5 -3
- package/libx/odata/middleware/batch.js +4 -0
- package/libx/odata/middleware/create.js +2 -2
- package/libx/odata/middleware/delete.js +2 -2
- package/libx/odata/middleware/operation.js +2 -2
- package/libx/odata/middleware/read.js +14 -12
- package/libx/odata/middleware/service-document.js +16 -8
- package/libx/odata/middleware/update.js +2 -2
- package/libx/odata/parse/afterburner.js +64 -30
- package/libx/odata/parse/grammar.peggy +95 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +6 -1
- package/libx/odata/utils/metadata.js +69 -75
- package/libx/odata/utils/postProcess.js +24 -3
- package/package.json +1 -1
- package/server.js +1 -1
- package/lib/ql/parse.js +0 -36
- /package/lib/ql/{infer.js → cds.ql-infer.js} +0 -0
|
@@ -19,7 +19,7 @@ function _getDefinition(definition, name, namespace) {
|
|
|
19
19
|
|
|
20
20
|
function _resolveAliasesInRef(ref, target) {
|
|
21
21
|
if (ref.length === 1) {
|
|
22
|
-
if (target.keys[ref[0]]) return ref
|
|
22
|
+
if (target.keys?.[ref[0]]) return ref
|
|
23
23
|
|
|
24
24
|
// resolve multi-part refs for innermost ref in url
|
|
25
25
|
if (target._flattenedKeys === undefined) {
|
|
@@ -266,6 +266,23 @@ function _handleCollectionBoundActions(current, ref, i, namespace, one) {
|
|
|
266
266
|
return incompleteKeys
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
function _resolveImplicitFunctionParameters(args) {
|
|
270
|
+
Object.entries(args).forEach(([key, value]) => {
|
|
271
|
+
if (typeof value !== 'boolean' && !!Number(value)) {
|
|
272
|
+
args[key] = Number(value)
|
|
273
|
+
} else if (typeof value === 'string') {
|
|
274
|
+
let result
|
|
275
|
+
result = value.match(/^'(\w*)'$/)?.[1]
|
|
276
|
+
if (result) {
|
|
277
|
+
args[key] = result
|
|
278
|
+
} else {
|
|
279
|
+
result = value.match(/^binary'([^']+)'$/)?.[1]
|
|
280
|
+
if (result) args[key] = Buffer.from(result, 'base64')
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
269
286
|
function _processSegments(from, model, namespace, cqn, protocol) {
|
|
270
287
|
const { ref } = from
|
|
271
288
|
|
|
@@ -386,16 +403,19 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
386
403
|
|
|
387
404
|
ref[i] = { operation: current.name }
|
|
388
405
|
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
406
|
+
if (current.kind === 'function') {
|
|
407
|
+
if (params) ref[i].args = _getDataFromParams(params, current)
|
|
408
|
+
// REVISIT: SELECT.from._params is a temporary hack
|
|
409
|
+
else if (from._params) {
|
|
410
|
+
// only take known params to allow additional instructions like sap-language, etc.
|
|
411
|
+
ref[i].args = current['@open']
|
|
412
|
+
? Object.assign({}, from._params)
|
|
413
|
+
: Object.keys(from._params).reduce((acc, cur) => {
|
|
414
|
+
if (current.params && cur in current.params) acc[cur] = from._params[cur]
|
|
415
|
+
return acc
|
|
416
|
+
}, {})
|
|
417
|
+
_resolveImplicitFunctionParameters(ref[i].args)
|
|
418
|
+
}
|
|
399
419
|
}
|
|
400
420
|
if (current.returns && current.returns._type) one = true
|
|
401
421
|
|
|
@@ -494,12 +514,11 @@ function _addKeys(columns, target) {
|
|
|
494
514
|
let hasAggregatedColumn = false,
|
|
495
515
|
hasStarColumn = false
|
|
496
516
|
|
|
497
|
-
for (
|
|
498
|
-
if (
|
|
499
|
-
else if (
|
|
517
|
+
for (const column of columns) {
|
|
518
|
+
if (column === '*') hasStarColumn = true
|
|
519
|
+
else if (column.func || column.func === null) hasAggregatedColumn = true
|
|
500
520
|
// Add keys to (sub-)expands
|
|
501
|
-
else if (
|
|
502
|
-
_addKeys(columns[k].expand, target.elements[columns[k].ref]._target)
|
|
521
|
+
else if (column.expand && column.ref) _addKeys(column.expand, target.elements[column.ref]._target)
|
|
503
522
|
}
|
|
504
523
|
|
|
505
524
|
// Don't add keys to queries with calculated properties, especially aggregations
|
|
@@ -515,15 +534,27 @@ function _addKeys(columns, target) {
|
|
|
515
534
|
}
|
|
516
535
|
}
|
|
517
536
|
|
|
518
|
-
|
|
519
|
-
|
|
537
|
+
/**
|
|
538
|
+
* Recursively, for each depth, remove all other select columns if a select star is present
|
|
539
|
+
* (including duplicates) and remove duplicate expand stars.
|
|
540
|
+
*
|
|
541
|
+
* @param {*} columns CQN `SELECT` columns array.
|
|
542
|
+
*/
|
|
543
|
+
function _removeUnneededColumnsIfHasAsterisk(columns) {
|
|
544
|
+
// We need to know if column contains a select * before we can remove other selected columns below
|
|
545
|
+
const hasSelectStar = columns.some(column => column === '*')
|
|
520
546
|
let hasExpandStar = false
|
|
521
547
|
|
|
522
548
|
columns.forEach((column, i) => {
|
|
549
|
+
// Remove other select columns if we have a select star
|
|
550
|
+
if (hasSelectStar && column.ref && !column.expand) columns.splice(i, 1)
|
|
551
|
+
// Remove duplicate expand stars
|
|
523
552
|
if (!column.ref && column.expand?.[0] === '*') {
|
|
524
553
|
if (hasExpandStar) columns.splice(i, 1)
|
|
525
554
|
hasExpandStar = true
|
|
526
555
|
}
|
|
556
|
+
// Recursively remove unneeded columns in expand
|
|
557
|
+
if (column.expand) _removeUnneededColumnsIfHasAsterisk(column.expand)
|
|
527
558
|
})
|
|
528
559
|
}
|
|
529
560
|
|
|
@@ -548,7 +579,7 @@ function _processColumns(cqn, target, protocol) {
|
|
|
548
579
|
else if (target.kind === 'action' && target.returns?.kind === 'entity') entity = target.returns
|
|
549
580
|
if (!entity) return
|
|
550
581
|
|
|
551
|
-
|
|
582
|
+
_removeUnneededColumnsIfHasAsterisk(columns)
|
|
552
583
|
rewriteExpandAsterisk(columns, entity)
|
|
553
584
|
if (protocol !== 'rest') _addKeys(columns, entity)
|
|
554
585
|
}
|
|
@@ -720,24 +751,27 @@ module.exports = (cqn, model, namespace, protocol) => {
|
|
|
720
751
|
const from = resolveFromSelect(cqn)
|
|
721
752
|
const { ref } = from
|
|
722
753
|
|
|
754
|
+
let edmName = ref[0].id || ref[0]
|
|
723
755
|
// REVISIT: shouldn't be necessary
|
|
724
|
-
|
|
725
|
-
|
|
756
|
+
if (edmName.split('.').length > 1)
|
|
757
|
+
//required for concat query, where the root is already identified with the first query and subsequent queries already have correct root
|
|
758
|
+
edmName = edmName.split('.')[edmName.split('.').length - 1]
|
|
759
|
+
|
|
726
760
|
/*
|
|
727
761
|
* make first path segment fully qualified
|
|
728
762
|
*/
|
|
729
|
-
const root =
|
|
730
|
-
|
|
731
|
-
findCsnTargetFor(
|
|
732
|
-
ref[0].id?.split('.')[ref[0].id?.split('.').length - 1] || ref[0].split('.')[ref[0].split('.').length - 1],
|
|
733
|
-
model,
|
|
734
|
-
namespace
|
|
735
|
-
)
|
|
736
|
-
|
|
737
|
-
// REVISIT: 404 or 400?
|
|
763
|
+
const root = findCsnTargetFor(edmName, model, namespace)
|
|
764
|
+
|
|
738
765
|
if (!root) {
|
|
766
|
+
//404 else we would expose knowledge to potential attackers
|
|
739
767
|
cds.error(`Invalid resource path "${namespace}.${ref[0].id || ref[0]}"`, { code: '404', statusCode: 404 })
|
|
740
768
|
}
|
|
769
|
+
if (cds.env.effective.odata.containment && model.definitions[namespace]._containedEntities.has(root.name)) {
|
|
770
|
+
cds.error(`Invalid resource path "${namespace}.${ref[0].id || ref[0]}"! It is not an entity set nor a singleton.`, {
|
|
771
|
+
code: '404',
|
|
772
|
+
statusCode: 404
|
|
773
|
+
})
|
|
774
|
+
}
|
|
741
775
|
if (ref[0].id) ref[0].id = root.name
|
|
742
776
|
else ref[0] = root.name
|
|
743
777
|
|
|
@@ -204,6 +204,9 @@
|
|
|
204
204
|
) {
|
|
205
205
|
cqn.from = { SELECT: { ...cqn } }
|
|
206
206
|
}
|
|
207
|
+
if (apply.topLevels) cqn.__topLevels = apply.topLevels
|
|
208
|
+
if (apply.ancestors) cqn.__ancestors = apply.ancestors
|
|
209
|
+
if (apply.descendants) cqn.__descendants = apply.descendants
|
|
207
210
|
if (apply.where) cqn.where = apply.where
|
|
208
211
|
if (apply.search) cqn.search = apply.search
|
|
209
212
|
if (apply.groupBy) {
|
|
@@ -379,6 +382,19 @@
|
|
|
379
382
|
return args
|
|
380
383
|
}
|
|
381
384
|
|
|
385
|
+
// `path` cannot be used in hierarchy functions, hence use a simpler definition
|
|
386
|
+
simplePath
|
|
387
|
+
= i:identifier filter:(OPEN CLOSE/OPEN a:args CLOSE{ return a })? tail:("/" s:simplePath{ return s })*{
|
|
388
|
+
const ref = [filter ? { id: i, where: filter } : i]
|
|
389
|
+
if (tail.length) ref.push(...tail.map(t => t.from.ref[0]))
|
|
390
|
+
return {
|
|
391
|
+
from: {
|
|
392
|
+
ref
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
382
398
|
//
|
|
383
399
|
// ---------- Query Options ------------
|
|
384
400
|
|
|
@@ -778,6 +794,9 @@
|
|
|
778
794
|
"top" top:topTrafo{return top} /
|
|
779
795
|
"skip" skip:skipTrafo{return skip} /
|
|
780
796
|
"orderby" order:orderbyTrafo{return order} /
|
|
797
|
+
"com.sap.vocabularies.Hierarchy.v1.TopLevels" toplevels:topLevelsTrafo{return toplevels} /
|
|
798
|
+
"ancestors" ancestors:ansDescTrafo{return { ancestors: ancestors }} /
|
|
799
|
+
"descendants" descendants:ansDescTrafo{return { descendants: descendants }} /
|
|
781
800
|
func:("topcount"i/"bottomcount"i/"topsum"i/"bottomsum"i/"toppercent"i/"bottompercent"i) args:commonFuncTrafo {
|
|
782
801
|
func = func.toLowerCase()
|
|
783
802
|
if (!SUPPORTED_APPLY_TRANSFORMATIONS[func]) {
|
|
@@ -873,6 +892,82 @@
|
|
|
873
892
|
return {orderBy: [o,...o2]}
|
|
874
893
|
}
|
|
875
894
|
|
|
895
|
+
topLevelsTrafo
|
|
896
|
+
= OPEN o t:topLevelsArg ts:(o COMMA o t2:topLevelsArg{ return t2 })* o CLOSE {
|
|
897
|
+
ts.forEach(_t => Object.assign(t, _t))
|
|
898
|
+
return {
|
|
899
|
+
topLevels: t
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
topLevelsArg
|
|
904
|
+
= hierarchyNodes /
|
|
905
|
+
hierarchyQualifier /
|
|
906
|
+
nodeProperty /
|
|
907
|
+
levels /
|
|
908
|
+
expandLevels
|
|
909
|
+
|
|
910
|
+
hierarchyNodes
|
|
911
|
+
= o "HierarchyNodes" o "=" o "$root/" p:simplePath o {
|
|
912
|
+
return { hierarchyNodes: p }
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
hierarchyQualifier
|
|
916
|
+
= o "HierarchyQualifier" o "=" o s:string o {
|
|
917
|
+
return { hierarchyQualifier: s }
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
nodeProperty
|
|
921
|
+
= o "NodeProperty" o "=" o s:string o {
|
|
922
|
+
return { nodeProperty: s }
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
levels
|
|
926
|
+
= o "Levels" o "=" o i:integer o {
|
|
927
|
+
return { levels: i}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
expandLevels
|
|
931
|
+
= o "ExpandLevels" o "=" o "[" o e:expandItem es:(o COMMA o e2:expandItem{ return e2 })* "]" {
|
|
932
|
+
return { expandLevels: [e, ...es] }
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
expandItem
|
|
936
|
+
= o "{" o e1:expandItemProp o "," o e2:expandItemProp o "}" {
|
|
937
|
+
return Object.assign(e1,e2)
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
expandItemProp
|
|
941
|
+
= "\"NodeID\"" o ":" o s:doubleQuotedString{return { nodeID: s } } /
|
|
942
|
+
"\"Levels\"" o ":" o i:integer{return { levels: i } }
|
|
943
|
+
|
|
944
|
+
ansDescTrafo
|
|
945
|
+
= OPEN o "$root/" p:simplePath o COMMA o h:identifier o COMMA o e:identifier o COMMA? t:ansDescGetNodes? o COMMA? d:integer? o COMMA? k:ansDescKeepStart? CLOSE {
|
|
946
|
+
return {
|
|
947
|
+
path: p,
|
|
948
|
+
hierarchy: h,
|
|
949
|
+
id: e,
|
|
950
|
+
nodes: t,
|
|
951
|
+
keepStart: k?.keepStart || false
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
ansDescGetNodes
|
|
956
|
+
= g:ansDescGetNode gs:( o "/" o g2:ansDescGetNode{return g2} )*{ return [g, ...gs] }
|
|
957
|
+
|
|
958
|
+
ansDescGetNode
|
|
959
|
+
= ansDescSearchNode /
|
|
960
|
+
ansDescFilterNode
|
|
961
|
+
|
|
962
|
+
ansDescFilterNode
|
|
963
|
+
= "filter(" f:filter ")"{ return { filter: f } }
|
|
964
|
+
|
|
965
|
+
ansDescSearchNode
|
|
966
|
+
= "search(" s:search_expand ")"{ return { search: s } }
|
|
967
|
+
|
|
968
|
+
ansDescKeepStart
|
|
969
|
+
= "keep start"{ return { keepStart: true } }
|
|
970
|
+
|
|
876
971
|
//
|
|
877
972
|
// ---------- Literals -----------
|
|
878
973
|
|