@sap/cds 8.5.1 → 8.6.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 +56 -3
- package/_i18n/i18n.properties +4 -7
- package/eslint.config.mjs +1 -1
- package/lib/compile/etc/properties.js +12 -9
- 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/json.js +2 -2
- package/lib/compile/to/sql.js +15 -8
- package/lib/core/linked-csn.js +10 -4
- package/lib/dbs/cds-deploy.js +1 -1
- 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/files.js +3 -3
- 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 +54 -43
- 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/index.js +5 -3
- 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 +63 -29
- package/libx/odata/parse/grammar.peggy +95 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +5 -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
|
@@ -11,6 +11,8 @@ const _lastValidRef = ref => {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
const _toBinaryKeyValue = value => `binary'${value.toString('base64')}'`
|
|
15
|
+
|
|
14
16
|
const _odataContext = (query, options) => {
|
|
15
17
|
let path = '$metadata'
|
|
16
18
|
if (query._target.kind === 'service') return path
|
|
@@ -36,113 +38,105 @@ const _odataContext = (query, options) => {
|
|
|
36
38
|
else if (cds2edm[query._target.type]) edmName = cds2edm[query._target.type]
|
|
37
39
|
else edmName = query._target.name
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
const isType = query._target.kind === 'type'
|
|
42
|
+
if (isCollection && isType) edmName = `Collection(${edmName})`
|
|
40
43
|
|
|
41
44
|
const serviceName = query._target._service?.name
|
|
42
|
-
if (serviceName) edmName = edmName.replace(serviceName + '.', '').replace(/\./g, '_')
|
|
45
|
+
if (serviceName && !isType) edmName = edmName.replace(serviceName + '.', '').replace(/\./g, '_')
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
path = '../'.repeat(ref.length - 1) + path
|
|
47
|
-
}
|
|
47
|
+
// prepend "../" parent segments for relative path
|
|
48
|
+
if (ref.length > 1) path = '../'.repeat(ref.length - 1) + path
|
|
48
49
|
|
|
49
50
|
path += edmName
|
|
50
51
|
|
|
51
52
|
const lastRef = ref.at(-1)
|
|
52
53
|
|
|
53
|
-
if (propertyAccess) {
|
|
54
|
-
path = '../' + path
|
|
54
|
+
if (propertyAccess || isNavToDraftAdmin) {
|
|
55
|
+
if (propertyAccess) path = '../' + path
|
|
56
|
+
|
|
57
|
+
const keyValuePairs = []
|
|
55
58
|
|
|
56
59
|
const lastValidRef = _lastValidRef(ref)
|
|
60
|
+
const isSibling = lastRef === 'SiblingEntity'
|
|
61
|
+
let _keyValuePairs
|
|
57
62
|
if (lastValidRef.where) {
|
|
58
|
-
|
|
59
|
-
const isSibling = lastRef === 'SiblingEntity'
|
|
60
|
-
if (lastValidRef.where.length > 3) {
|
|
61
|
-
// multiple keys should contain key name
|
|
62
|
-
const _keys = where2obj(lastValidRef.where)
|
|
63
|
-
keys = Object.keys(_keys).map(k => {
|
|
64
|
-
if (k === 'IsActiveEntity' && isSibling) return k + '=' + !_keys[k]
|
|
65
|
-
return k + '=' + _keys[k]
|
|
66
|
-
})
|
|
67
|
-
} else {
|
|
68
|
-
// single keys can just contain value
|
|
69
|
-
keys = [lastValidRef.where.at(-1).val]
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
path += '(' + keys.join(',') + ')'
|
|
63
|
+
_keyValuePairs = Object.entries(where2obj(lastValidRef.where))
|
|
73
64
|
} else if (!isSingleton) {
|
|
74
65
|
// use keys from result if not in query
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
keyString = result[_keys[0]]
|
|
79
|
-
} else {
|
|
80
|
-
keyString = _keys.map(k => k.name + '=' + result[k.name]).join(',')
|
|
81
|
-
}
|
|
82
|
-
path += '(' + keyString + ')'
|
|
66
|
+
_keyValuePairs = Object.entries(query._target.keys)
|
|
67
|
+
.filter(([, v]) => !v._isBacklink)
|
|
68
|
+
.map(([k]) => [k, result[k]])
|
|
83
69
|
}
|
|
84
70
|
|
|
85
|
-
if (
|
|
86
|
-
|
|
71
|
+
if (Array.isArray(_keyValuePairs)) {
|
|
72
|
+
_keyValuePairs.forEach(([k, _v]) => {
|
|
73
|
+
const v = (() => {
|
|
74
|
+
if (k === 'IsActiveEntity' && isSibling) return !_v
|
|
75
|
+
if (Buffer.isBuffer(_v)) return _toBinaryKeyValue(_v)
|
|
76
|
+
return _v
|
|
77
|
+
})()
|
|
78
|
+
if (v !== undefined) keyValuePairs.push([k, v])
|
|
79
|
+
})
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// multiple keys should contain key name
|
|
97
|
-
const _keys = where2obj(lastValidRef.where)
|
|
98
|
-
keys = Object.keys(_keys).map(k => {
|
|
99
|
-
if (k === 'IsActiveEntity' && isSibling) return k + '=' + !_keys[k]
|
|
100
|
-
return k + '=' + _keys[k]
|
|
101
|
-
})
|
|
102
|
-
} else {
|
|
103
|
-
// single keys can just contain value
|
|
104
|
-
keys = [lastValidRef.where.at(-1).val]
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
path += '(' + keys.join(',') + ')'
|
|
82
|
+
if (keyValuePairs.length) {
|
|
83
|
+
let keyString
|
|
84
|
+
// single keys just contain the value
|
|
85
|
+
if (keyValuePairs.length === 1) keyString = String(keyValuePairs[0][1])
|
|
86
|
+
// multiple keys should contain key value pairs
|
|
87
|
+
else keyString = keyValuePairs.map(([k, v]) => `${k}=${v}`).join(',')
|
|
88
|
+
path += '(' + keyString + ')'
|
|
108
89
|
}
|
|
109
|
-
|
|
90
|
+
|
|
91
|
+
if (isNavToDraftAdmin) path += '/' + lastRef
|
|
92
|
+
if (propertyAccess) path += '/' + propertyAccess
|
|
110
93
|
}
|
|
111
94
|
|
|
112
95
|
if (cds.env.odata.context_with_columns && query.SELECT && !isSingleton && !propertyAccess) {
|
|
113
|
-
const
|
|
114
|
-
|
|
96
|
+
const _calculateStringFromColumn = column => {
|
|
97
|
+
if (column === '*') return
|
|
115
98
|
|
|
116
|
-
function processColumns(column) {
|
|
117
99
|
const refName = column.ref?.[0]
|
|
118
|
-
if (!refName) return ''
|
|
119
100
|
|
|
120
101
|
if (column.expand) {
|
|
121
102
|
// Process nested expands recursively
|
|
122
|
-
const
|
|
123
|
-
return `${refName}
|
|
124
|
-
} else if (column.
|
|
125
|
-
|
|
126
|
-
const xprRefName = column.xpr.find(item => item.ref?.[0])?.ref?.[0]
|
|
127
|
-
return xprRefName || ''
|
|
128
|
-
} else if (refName !== '*') {
|
|
129
|
-
return refName
|
|
103
|
+
const expands = _calculateColumnsString(column.expand)
|
|
104
|
+
return `${refName}${expands ? expands : '()'}`
|
|
105
|
+
} else if (column.func) {
|
|
106
|
+
return column.as
|
|
130
107
|
}
|
|
108
|
+
|
|
109
|
+
return refName
|
|
131
110
|
}
|
|
132
111
|
|
|
133
|
-
columns
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
112
|
+
const _calculateColumnsString = columns => {
|
|
113
|
+
const selects = []
|
|
114
|
+
const expands = []
|
|
115
|
+
columns.forEach(column => {
|
|
116
|
+
if (column.expand) expands.push(column)
|
|
117
|
+
else selects.push(column)
|
|
118
|
+
})
|
|
141
119
|
|
|
142
|
-
|
|
143
|
-
|
|
120
|
+
const columnStrings = []
|
|
121
|
+
|
|
122
|
+
// First process selects, then expands
|
|
123
|
+
selects.concat(expands).forEach(column => {
|
|
124
|
+
const stringFromColumn = _calculateStringFromColumn(column)
|
|
125
|
+
if (stringFromColumn) columnStrings.push(stringFromColumn)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if (columnStrings.length) return `(${columnStrings.join(',')})`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const columns = query.SELECT.columns || query.SELECT.from?.SELECT?.columns
|
|
132
|
+
if (columns) {
|
|
133
|
+
const columnsString = _calculateColumnsString(columns)
|
|
134
|
+
if (columnsString) path += columnsString
|
|
135
|
+
}
|
|
144
136
|
}
|
|
145
137
|
|
|
138
|
+
if (!isCollection && !isSingleton && !propertyAccess && !isType) path += '/$entity'
|
|
139
|
+
|
|
146
140
|
return path
|
|
147
141
|
}
|
|
148
142
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
const cds = require('../../../lib')
|
|
2
|
+
|
|
1
3
|
const getTemplate = require('../../_runtime/common/utils/template')
|
|
2
4
|
const { toBase64url } = require('../../_runtime/common/utils/binary')
|
|
3
5
|
|
|
4
6
|
const _addEtags = (row, key) => {
|
|
5
7
|
if (!row[key]) return
|
|
6
|
-
|
|
8
|
+
// if provided as js date, take the iso string
|
|
9
|
+
const value = row[key] instanceof Date ? row[key].toISOString() : row[key]
|
|
10
|
+
row.$etag = value.startsWith?.('W/') ? value : `W/"${value}"`
|
|
7
11
|
}
|
|
8
|
-
|
|
9
12
|
const _processorFn = elementInfo => {
|
|
10
13
|
const { row, plain } = elementInfo
|
|
11
14
|
if (typeof row !== 'object') return
|
|
@@ -29,7 +32,10 @@ const _processorFn = elementInfo => {
|
|
|
29
32
|
}
|
|
30
33
|
break
|
|
31
34
|
case 'array':
|
|
32
|
-
row[key]
|
|
35
|
+
if (row[key] === null) row[key] = []
|
|
36
|
+
break
|
|
37
|
+
case '@cleanup':
|
|
38
|
+
if (key !== 'DraftAdministrativeData_DraftUUID') delete row[key]
|
|
33
39
|
break
|
|
34
40
|
// no default
|
|
35
41
|
}
|
|
@@ -42,6 +48,21 @@ const _pick = element => {
|
|
|
42
48
|
if (element['@cds.api.ignore'] && !element.isAssociation) categories.push('@cds.api.ignore')
|
|
43
49
|
if (element._type === 'cds.Binary') categories.push('binary')
|
|
44
50
|
if (element.items) categories.push('array')
|
|
51
|
+
|
|
52
|
+
// in case of containment managed composition (& assoc backlinks) keys are not exposed and have to be removed from the result
|
|
53
|
+
if (cds.env.effective.odata.containment) {
|
|
54
|
+
const _isContainedOrBackLink = element =>
|
|
55
|
+
element &&
|
|
56
|
+
element.isAssociation &&
|
|
57
|
+
element.keys &&
|
|
58
|
+
(element._isContained || (element._anchor && element._anchor._isContained))
|
|
59
|
+
|
|
60
|
+
const assocName = element._foreignKey4
|
|
61
|
+
const assoc = assocName && element.parent.elements[assocName]
|
|
62
|
+
|
|
63
|
+
if (_isContainedOrBackLink(assoc)) categories.push('@cleanup')
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
if (categories.length) return { categories }
|
|
46
67
|
}
|
|
47
68
|
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -46,7 +46,7 @@ module.exports = async function cds_server (options) {
|
|
|
46
46
|
if (o.index) app.get ('/',o.index) //> if none in ./app
|
|
47
47
|
|
|
48
48
|
// load and prepare models
|
|
49
|
-
const csn = await cds.load(o.from||'*',o)
|
|
49
|
+
const csn = await cds.load(o.from||'*',o)
|
|
50
50
|
cds.edmxs = cds.compile.to.edmx.files (csn)
|
|
51
51
|
cds.model = cds.compile.for.nodejs (csn)
|
|
52
52
|
|
package/lib/ql/parse.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// please keep all comments!
|
|
2
|
-
|
|
3
|
-
const cds = require('../index')
|
|
4
|
-
module.exports = {
|
|
5
|
-
column:(x) => _simple(x) /* || _parse('column',x) */ || cds.parse.column(x),
|
|
6
|
-
expr:(x) => _simple(x) /* || _parse('expr',x) */ || cds.parse.expr(x),
|
|
7
|
-
CQL: (..._) => cds.parse.CQL (..._),
|
|
8
|
-
CXL: (..._) => cds.parse.CXL (..._),
|
|
9
|
-
cql: (..._) => cds.parse.cql (..._),
|
|
10
|
-
path: (..._) => cds.parse.path (..._),
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const _simple = (x) => {
|
|
14
|
-
if (typeof x !== 'string') return {val:x}
|
|
15
|
-
const t = /^\s*([\w.'?]+)(?:\s*([!?\\/:=\-+<~>]+|like)\s*([\w.'?]+))?\s*$/.exec(x); if (!t) return
|
|
16
|
-
const [,lhs,op,rhs] = t
|
|
17
|
-
return op ? {xpr:[_rv(lhs),op,_rv(rhs)]} : _rv(lhs)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const _rv = (x) => {
|
|
21
|
-
if (x[0] === '?') return { param: true, ref: x }
|
|
22
|
-
if (x[0] === "'") return { val: x.slice(1,-1).replace(/''/g, "'") }
|
|
23
|
-
if (x === 'null') return { val: null }
|
|
24
|
-
if (x === 'true') return { val: true }
|
|
25
|
-
if (x === 'false') return { val: false }
|
|
26
|
-
if (!isNaN(x)) return { val: Number(x) }
|
|
27
|
-
else return { ref: x.split('.') }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// const _parse = (startRule,x) => {
|
|
31
|
-
// try {
|
|
32
|
-
// return parser.parse(x,{startRule})
|
|
33
|
-
// // } catch (e) { e.message += ' in: \n' + x; throw e }
|
|
34
|
-
// } catch {/* ignored */}
|
|
35
|
-
// }
|
|
36
|
-
// const parser = require('./parser')
|
|
File without changes
|