@sap/cds 8.8.3 → 8.9.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.
- package/CHANGELOG.md +48 -4
- package/_i18n/i18n_en_US_saptrc.properties +3 -0
- package/bin/colors.js +2 -0
- package/bin/test.js +103 -75
- package/eslint.config.mjs +16 -4
- package/lib/compile/for/lean_drafts.js +4 -0
- package/lib/compile/parse.js +26 -6
- package/lib/env/cds-env.js +3 -1
- package/lib/env/cds-requires.js +0 -3
- package/lib/env/schemas/cds-rc.js +11 -0
- package/lib/log/format/aspects/cls.js +2 -1
- package/lib/log/format/json.js +1 -1
- package/lib/plugins.js +2 -3
- package/lib/ql/SELECT.js +2 -1
- package/lib/ql/cds-ql.js +2 -0
- package/lib/ql/cds.ql-predicates.js +6 -4
- package/lib/ql/resolve.js +46 -0
- package/lib/req/validate.js +1 -0
- package/lib/srv/bindings.js +64 -43
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/middlewares/auth/ias-auth.js +2 -0
- package/lib/srv/protocols/http.js +2 -2
- package/lib/srv/protocols/index.js +1 -1
- package/lib/srv/protocols/odata-v4.js +0 -1
- package/lib/srv/srv-tx.js +1 -1
- package/lib/test/cds-test.js +3 -4
- package/lib/utils/cds-utils.js +19 -19
- package/lib/utils/colors.js +46 -45
- package/lib/utils/csv-reader.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
- package/libx/_runtime/common/Service.js +4 -2
- package/libx/_runtime/common/composition/data.js +1 -2
- package/libx/_runtime/common/composition/tree.js +6 -4
- package/libx/_runtime/common/generic/sorting.js +6 -2
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +6 -7
- package/libx/_runtime/common/utils/differ.js +1 -1
- package/libx/_runtime/common/utils/draft.js +1 -1
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +6 -2
- package/libx/_runtime/common/utils/keys.js +13 -84
- package/libx/_runtime/common/utils/propagateForeignKeys.js +4 -3
- package/libx/_runtime/common/utils/resolveView.js +96 -102
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
- package/libx/_runtime/common/utils/stream.js +2 -3
- package/libx/_runtime/db/utils/columns.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +11 -7
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -2
- package/libx/_runtime/messaging/file-based.js +6 -6
- package/libx/_runtime/messaging/kafka.js +5 -7
- package/libx/_runtime/messaging/redis-messaging.js +1 -1
- package/libx/_runtime/messaging/service.js +11 -4
- package/libx/_runtime/remote/Service.js +13 -5
- package/libx/_runtime/remote/utils/client.js +1 -0
- package/libx/_runtime/ucl/Service.js +135 -126
- package/libx/common/utils/path.js +34 -22
- package/libx/odata/middleware/create.js +2 -0
- package/libx/odata/middleware/operation.js +8 -2
- package/libx/odata/middleware/parse.js +1 -1
- package/libx/odata/middleware/stream.js +1 -2
- package/libx/odata/middleware/update.js +2 -0
- package/libx/odata/parse/afterburner.js +17 -9
- package/libx/odata/parse/cqn2odata.js +43 -22
- package/libx/odata/parse/grammar.peggy +21 -19
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/metadata.js +8 -2
- package/libx/odata/utils/odataBind.js +36 -0
- package/libx/outbox/index.js +1 -0
- package/libx/rest/middleware/operation.js +9 -8
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +3 -3
- package/lib/i18n/resources.js +0 -150
|
@@ -74,7 +74,7 @@ function hasValidProps(obj, ...names) {
|
|
|
74
74
|
return true
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function _args(args, func) {
|
|
77
|
+
function _args(args, func, navPrefix) {
|
|
78
78
|
const res = []
|
|
79
79
|
|
|
80
80
|
for (const cur of args) {
|
|
@@ -84,9 +84,9 @@ function _args(args, func) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
if (hasValidProps(cur, 'func', 'args')) {
|
|
87
|
-
res.push(`${cur.func}(${_args(cur.args, cur.func)})`)
|
|
87
|
+
res.push(`${cur.func}(${_args(cur.args, cur.func, navPrefix)})`)
|
|
88
88
|
} else if (hasValidProps(cur, 'ref')) {
|
|
89
|
-
res.push(_format(cur))
|
|
89
|
+
res.push(_format(cur, null, null, null, null, null, navPrefix))
|
|
90
90
|
} else if (hasValidProps(cur, 'val')) {
|
|
91
91
|
res.push(_format(cur, null, null, null, null, func))
|
|
92
92
|
}
|
|
@@ -95,38 +95,44 @@ function _args(args, func) {
|
|
|
95
95
|
return res.join(',')
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
const _in = (column, /* in */ collection, target, kind, isLambda) => {
|
|
99
|
-
const ref = _format(column, null, target, kind, isLambda)
|
|
98
|
+
const _in = (column, /* in */ collection, target, kind, isLambda, navPrefix) => {
|
|
99
|
+
const ref = _format(column, null, target, kind, isLambda, null, navPrefix)
|
|
100
100
|
// { list: [ { val: 1}, { val: 2}, { val: 3} ] }
|
|
101
101
|
const values = collection.list
|
|
102
102
|
if (values && values.length) {
|
|
103
103
|
// REVISIT: what about OData `in` operator?
|
|
104
|
-
const expressions = values.map(
|
|
104
|
+
const expressions = values.map(
|
|
105
|
+
value => `${ref}%20eq%20${_format(value, ref, target, kind, isLambda, null, navPrefix)}`
|
|
106
|
+
)
|
|
105
107
|
return expressions.join('%20or%20')
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
110
|
|
|
109
|
-
const _odataV2Func = (func, args) => {
|
|
111
|
+
const _odataV2Func = (func, args, navPrefix) => {
|
|
110
112
|
switch (func) {
|
|
111
113
|
case 'contains':
|
|
112
114
|
// this doesn't support the contains signature with two collections as args, introduced in odata v4.01
|
|
113
|
-
return `substringof(${_args([args[1], args[0]])})`
|
|
115
|
+
return `substringof(${_args([args[1], args[0]], null, navPrefix)})`
|
|
114
116
|
default:
|
|
115
|
-
return `${func}(${_args(args, func)})`
|
|
117
|
+
return `${func}(${_args(args, func, navPrefix)})`
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
const _format = (cur, elementName, target, kind, isLambda, func) => {
|
|
121
|
+
const _format = (cur, elementName, target, kind, isLambda, func, navPrefix = []) => {
|
|
120
122
|
if (typeof cur !== 'object') return encodeURIComponent(formatVal(cur, elementName, target, kind))
|
|
121
123
|
if (hasValidProps(cur, 'ref'))
|
|
122
|
-
return encodeURIComponent(
|
|
124
|
+
return encodeURIComponent(
|
|
125
|
+
isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref[0].id || [...navPrefix, ...cur.ref].join('/')
|
|
126
|
+
)
|
|
123
127
|
if (hasValidProps(cur, 'val'))
|
|
124
128
|
return encodeURIComponent(formatVal(cur.val, elementName, target, kind, func, cur.literal))
|
|
125
|
-
if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
|
|
129
|
+
if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda, navPrefix)})`
|
|
126
130
|
// REVISIT: How to detect the types for all functions?
|
|
127
131
|
if (hasValidProps(cur, 'func')) {
|
|
128
132
|
if (cur.args?.length) {
|
|
129
|
-
return kind === 'odata-v2'
|
|
133
|
+
return kind === 'odata-v2'
|
|
134
|
+
? _odataV2Func(cur.func, cur.args, navPrefix)
|
|
135
|
+
: `${cur.func}(${_args(cur.args, cur.func)})`
|
|
130
136
|
}
|
|
131
137
|
return `${cur.func}()`
|
|
132
138
|
}
|
|
@@ -138,7 +144,7 @@ const _isLambda = (cur, next) => {
|
|
|
138
144
|
return last && hasValidProps(last, 'id')
|
|
139
145
|
}
|
|
140
146
|
|
|
141
|
-
function _xpr(expr, target, kind, isLambda) {
|
|
147
|
+
function _xpr(expr, target, kind, isLambda, navPrefix = []) {
|
|
142
148
|
const res = []
|
|
143
149
|
const openBrackets = []
|
|
144
150
|
|
|
@@ -165,10 +171,10 @@ function _xpr(expr, target, kind, isLambda) {
|
|
|
165
171
|
const between = [expr[i - 1], 'gt', expr[i + 1], 'and', expr[i - 1], 'lt', expr[i + 3]]
|
|
166
172
|
// cleanup previous ref
|
|
167
173
|
res.pop()
|
|
168
|
-
res.push(`(${_xpr(between, target, kind, isLambda)})`)
|
|
174
|
+
res.push(`(${_xpr(between, target, kind, isLambda, navPrefix)})`)
|
|
169
175
|
i += 3
|
|
170
176
|
} else if (cur === 'in') {
|
|
171
|
-
const inExpr = _in(expr[i - 1], expr[i + 1], target, kind, isLambda)
|
|
177
|
+
const inExpr = _in(expr[i - 1], expr[i + 1], target, kind, isLambda, navPrefix)
|
|
172
178
|
// cleanup previous ref
|
|
173
179
|
res.pop()
|
|
174
180
|
// when sending a where clause with "col in []" we currently ignore the where clause
|
|
@@ -179,20 +185,33 @@ function _xpr(expr, target, kind, isLambda) {
|
|
|
179
185
|
} else if (_isLambda(cur, expr[i + 1])) {
|
|
180
186
|
const { where } = expr[i + 1].ref.at(-1)
|
|
181
187
|
const nav = expr[i + 1].ref.map(ref => ref?.id ?? ref).join('/')
|
|
182
|
-
|
|
188
|
+
|
|
183
189
|
if (kind === 'odata-v2') {
|
|
190
|
+
// odata-v2 does not support lambda expressions but successfactors allows filter like for to-one assocs
|
|
184
191
|
cds.log('remote').info(`OData V2 does not support lambda expressions. Using path expression as best effort.`)
|
|
185
192
|
isLambda = false
|
|
186
|
-
res.push(
|
|
187
|
-
} else if (!where)
|
|
188
|
-
|
|
193
|
+
res.push(_xpr(where, target, kind, isLambda, [...navPrefix, nav]))
|
|
194
|
+
} else if (!where) {
|
|
195
|
+
res.push(`${nav}/any()`)
|
|
196
|
+
} else {
|
|
197
|
+
res.push(`${nav}/any(${LAMBDA_VARIABLE}:${_xpr(where, target, kind, true, navPrefix)})`)
|
|
198
|
+
}
|
|
199
|
+
|
|
189
200
|
i++
|
|
190
201
|
} else {
|
|
191
202
|
res.push(OPERATORS[cur] || cur.toLowerCase())
|
|
192
203
|
}
|
|
193
204
|
} else {
|
|
194
205
|
const ref = expr[i - 2]
|
|
195
|
-
const formatted = _format(
|
|
206
|
+
const formatted = _format(
|
|
207
|
+
cur,
|
|
208
|
+
ref?.ref && (ref.ref.length ? ref.ref : ref.ref[0]),
|
|
209
|
+
target,
|
|
210
|
+
kind,
|
|
211
|
+
isLambda,
|
|
212
|
+
null,
|
|
213
|
+
navPrefix
|
|
214
|
+
)
|
|
196
215
|
if (formatted !== undefined) res.push(formatted)
|
|
197
216
|
}
|
|
198
217
|
}
|
|
@@ -393,6 +412,8 @@ function $orderBy(orderBy) {
|
|
|
393
412
|
const res = []
|
|
394
413
|
|
|
395
414
|
for (const cur of orderBy) {
|
|
415
|
+
if (cur.implicit) continue
|
|
416
|
+
|
|
396
417
|
if (hasValidProps(cur, 'ref', 'sort')) {
|
|
397
418
|
res.push(_format(cur) + '%20' + cur.sort)
|
|
398
419
|
continue
|
|
@@ -412,7 +433,7 @@ function $orderBy(orderBy) {
|
|
|
412
433
|
}
|
|
413
434
|
}
|
|
414
435
|
|
|
415
|
-
return '$orderby=' + res.join(',')
|
|
436
|
+
if (res.length) return '$orderby=' + res.join(',')
|
|
416
437
|
}
|
|
417
438
|
|
|
418
439
|
function parseSearch(search) {
|
|
@@ -210,13 +210,18 @@
|
|
|
210
210
|
|
|
211
211
|
}
|
|
212
212
|
if (apply.topLevels.levels) {
|
|
213
|
-
cqn.recurse.where = [{ ref: ['DistanceFromRoot'] }, '<=', { val: apply.topLevels.levels }]
|
|
213
|
+
cqn.recurse.where = [{ ref: ['DistanceFromRoot'] }, '<=', { val: apply.topLevels.levels - 1 }]
|
|
214
214
|
}
|
|
215
215
|
if (apply.topLevels.expandLevels) {
|
|
216
216
|
if (!cqn.recurse.where) cqn.recurse.where = []
|
|
217
217
|
for (const expandLevel of apply.topLevels.expandLevels) {
|
|
218
218
|
if (cqn.recurse.where.length !== 0) cqn.recurse.where.push('or')
|
|
219
|
-
cqn.recurse.where.push({ ref: [apply.topLevels.nodeProperty] }, '=', { val: expandLevel.nodeID }
|
|
219
|
+
cqn.recurse.where.push({ ref: [apply.topLevels.nodeProperty] }, '=', { val: expandLevel.nodeID })
|
|
220
|
+
if (Number.isInteger(expandLevel.levels)) {
|
|
221
|
+
cqn.recurse.where.push('and', { ref: ['Distance'] })
|
|
222
|
+
if (expandLevel.levels === 1) cqn.recurse.where.push('=', { val: 1 })
|
|
223
|
+
else cqn.recurse.where.push('between', { val: 1 }, 'and', { val: expandLevel.levels } )
|
|
224
|
+
}
|
|
220
225
|
}
|
|
221
226
|
}
|
|
222
227
|
}
|
|
@@ -244,23 +249,16 @@
|
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
if (apply.ancestors && apply.topLevels) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
_ancestors(inner.SELECT)
|
|
253
|
-
cqn.from = inner
|
|
254
|
-
_toplevels(cqn)
|
|
252
|
+
_ancestors(cqn)
|
|
253
|
+
const extra = {...cqn}
|
|
254
|
+
_toplevels(extra)
|
|
255
|
+
cqn.recurse = extra.recurse
|
|
255
256
|
} else if (apply.ancestors && apply.descendants) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
_ancestors(inner.SELECT)
|
|
262
|
-
cqn.from = inner
|
|
263
|
-
_descendants(cqn)
|
|
257
|
+
_ancestors(cqn)
|
|
258
|
+
const extra = {...cqn}
|
|
259
|
+
_descendants(extra)
|
|
260
|
+
cqn.where = extra.where
|
|
261
|
+
cqn.recurse = extra.recurse
|
|
264
262
|
} else if (apply.topLevels) {
|
|
265
263
|
_toplevels(cqn)
|
|
266
264
|
} else if (apply.ancestors) {
|
|
@@ -1019,7 +1017,11 @@
|
|
|
1019
1017
|
|
|
1020
1018
|
expandItemProp
|
|
1021
1019
|
= "\"NodeID\"" o ":" o s:doubleQuotedString{return { nodeID: s } } /
|
|
1022
|
-
"\"Levels\"" o ":" o
|
|
1020
|
+
"\"Levels\"" o ":" o l:expandItemLevel{return { levels: l } }
|
|
1021
|
+
|
|
1022
|
+
expandItemLevel
|
|
1023
|
+
= i:integer{ return i } /
|
|
1024
|
+
null{ return null }
|
|
1023
1025
|
|
|
1024
1026
|
ansDescTrafo
|
|
1025
1027
|
= 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 {
|