@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +56 -3
  2. package/_i18n/i18n.properties +4 -7
  3. package/eslint.config.mjs +1 -1
  4. package/lib/compile/etc/properties.js +12 -9
  5. package/lib/compile/for/java.js +15 -3
  6. package/lib/compile/for/lean_drafts.js +44 -34
  7. package/lib/compile/for/nodejs.js +19 -10
  8. package/lib/compile/minify.js +2 -4
  9. package/lib/compile/parse.js +106 -72
  10. package/lib/compile/to/edm.js +19 -9
  11. package/lib/compile/to/hana.js +25 -21
  12. package/lib/compile/to/json.js +2 -2
  13. package/lib/compile/to/sql.js +15 -8
  14. package/lib/core/linked-csn.js +10 -4
  15. package/lib/dbs/cds-deploy.js +1 -1
  16. package/lib/env/cds-env.js +76 -66
  17. package/lib/env/defaults.js +1 -0
  18. package/lib/i18n/bundles.js +2 -1
  19. package/lib/i18n/files.js +3 -3
  20. package/lib/i18n/localize.js +2 -2
  21. package/lib/index.js +24 -18
  22. package/lib/ql/CREATE.js +11 -6
  23. package/lib/ql/DELETE.js +12 -9
  24. package/lib/ql/DROP.js +15 -8
  25. package/lib/ql/INSERT.js +19 -14
  26. package/lib/ql/SELECT.js +95 -168
  27. package/lib/ql/UPDATE.js +23 -14
  28. package/lib/ql/UPSERT.js +15 -2
  29. package/lib/ql/Whereable.js +44 -118
  30. package/lib/ql/cds-ql.js +222 -28
  31. package/lib/ql/{Query.js → cds.ql-Query.js} +52 -41
  32. package/lib/ql/cds.ql-predicates.js +133 -0
  33. package/lib/ql/cds.ql-projections.js +111 -0
  34. package/lib/ql/cqn.d.ts +146 -0
  35. package/lib/srv/cds-connect.js +3 -3
  36. package/lib/srv/cds-serve.js +2 -2
  37. package/lib/srv/cds.Service.js +132 -0
  38. package/lib/srv/{srv-api.js → cds.ServiceClient.js} +16 -71
  39. package/lib/srv/cds.ServiceProvider.js +20 -0
  40. package/lib/srv/factory.js +20 -8
  41. package/lib/srv/protocols/hcql.js +2 -3
  42. package/lib/srv/protocols/index.js +3 -3
  43. package/lib/srv/srv-dispatch.js +7 -6
  44. package/lib/srv/srv-handlers.js +103 -113
  45. package/lib/srv/srv-methods.js +14 -14
  46. package/lib/srv/srv-tx.js +5 -3
  47. package/lib/utils/cds-utils.js +2 -2
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +3 -3
  49. package/libx/_runtime/cds.js +2 -1
  50. package/libx/_runtime/common/aspects/service.js +25 -0
  51. package/libx/_runtime/common/generic/auth/index.js +5 -0
  52. package/libx/_runtime/common/generic/auth/restrict.js +36 -14
  53. package/libx/_runtime/common/generic/auth/service.js +24 -0
  54. package/libx/_runtime/common/generic/auth/utils.js +14 -6
  55. package/libx/_runtime/common/generic/etag.js +1 -1
  56. package/libx/_runtime/common/utils/cqn.js +1 -2
  57. package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
  58. package/libx/_runtime/common/utils/generateOnCond.js +7 -3
  59. package/libx/_runtime/common/utils/postProcess.js +4 -1
  60. package/libx/_runtime/common/utils/restrictions.js +1 -0
  61. package/libx/_runtime/fiori/lean-draft.js +54 -43
  62. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
  63. package/libx/_runtime/remote/Service.js +2 -0
  64. package/libx/_runtime/remote/utils/client.js +12 -0
  65. package/libx/odata/index.js +5 -3
  66. package/libx/odata/middleware/create.js +2 -2
  67. package/libx/odata/middleware/delete.js +2 -2
  68. package/libx/odata/middleware/operation.js +2 -2
  69. package/libx/odata/middleware/read.js +14 -12
  70. package/libx/odata/middleware/service-document.js +16 -8
  71. package/libx/odata/middleware/update.js +2 -2
  72. package/libx/odata/parse/afterburner.js +63 -29
  73. package/libx/odata/parse/grammar.peggy +95 -0
  74. package/libx/odata/parse/parser.js +1 -1
  75. package/libx/odata/utils/index.js +5 -1
  76. package/libx/odata/utils/metadata.js +69 -75
  77. package/libx/odata/utils/postProcess.js +24 -3
  78. package/package.json +1 -1
  79. package/server.js +1 -1
  80. package/lib/ql/parse.js +0 -36
  81. /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
- if (isCollection && query._target.kind === 'type') edmName = `Collection(${edmName})`
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
- if (ref.length > 1) {
45
- // prepend for relative path
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
- let keys
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
- const _keys = Object.keys(query._target.keys)
76
- let keyString
77
- if (_keys.length === 1) {
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 (isNavToDraftAdmin) {
86
- path += '/' + lastRef
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
- path += '/' + propertyAccess
90
- } else if (isNavToDraftAdmin) {
91
- const lastValidRef = _lastValidRef(ref)
92
- if (lastValidRef.where) {
93
- let keys
94
- const isSibling = lastRef === 'SiblingEntity'
95
- if (lastValidRef.where.length > 3) {
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
- path += '/' + lastRef
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 { columns } = query.SELECT
114
- let containments = []
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 expandSelects = column.expand.map(exp => processColumns(exp)).filter(Boolean)
123
- return `${refName}(${expandSelects.join(',')})`
124
- } else if (column.xpr) {
125
- // Handle xpr cases
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.forEach(column => {
134
- const result = processColumns(column)
135
- if (result) {
136
- containments.push(result)
137
- }
138
- })
139
- if (containments.length) path += `(${containments.join(',')})`
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
- if ((!isCollection && !isSingleton && !propertyAccess) || (isNavToDraftAdmin && !propertyAccess)) {
143
- path += '/$entity'
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
- row.$etag = row[key].startsWith('W/') ? row[key] : `W/"${row[key]}"`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "8.5.1",
3
+ "version": "8.6.1",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
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) .then (cds.minify)
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