@sap/cds 5.5.5 → 5.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.
Files changed (204) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/apis/services.d.ts +27 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +3 -1
  14. package/lib/env/index.js +175 -41
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +31 -4
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/utils/axios.js +7 -0
  25. package/lib/utils/data.js +1 -1
  26. package/lib/utils/tests.js +1 -1
  27. package/libx/_runtime/audit/Service.js +18 -18
  28. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  29. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  30. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  31. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  46. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  50. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  53. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
  54. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  55. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  56. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  57. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  58. package/libx/_runtime/cds-services/util/assert.js +29 -13
  59. package/libx/_runtime/cds.js +2 -1
  60. package/libx/_runtime/common/aspects/Association.js +72 -0
  61. package/libx/_runtime/common/aspects/any.js +8 -45
  62. package/libx/_runtime/common/aspects/entity.js +0 -1
  63. package/libx/_runtime/common/aspects/relation.js +40 -0
  64. package/libx/_runtime/common/aspects/utils.js +73 -1
  65. package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
  66. package/libx/_runtime/common/composition/data.js +3 -2
  67. package/libx/_runtime/common/composition/delete.js +3 -1
  68. package/libx/_runtime/common/composition/tree.js +23 -18
  69. package/libx/_runtime/common/composition/utils.js +34 -8
  70. package/libx/_runtime/common/error/frontend.js +6 -1
  71. package/libx/_runtime/common/generic/auth.js +5 -9
  72. package/libx/_runtime/common/generic/crud.js +2 -2
  73. package/libx/_runtime/common/generic/etag.js +11 -8
  74. package/libx/_runtime/common/generic/input.js +3 -3
  75. package/libx/_runtime/common/generic/paging.js +9 -5
  76. package/libx/_runtime/common/generic/put.js +3 -2
  77. package/libx/_runtime/common/generic/sorting.js +3 -3
  78. package/libx/_runtime/common/generic/temporal.js +3 -3
  79. package/libx/_runtime/common/utils/cqn.js +20 -1
  80. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  81. package/libx/_runtime/common/utils/csn.js +50 -52
  82. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  83. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  84. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  85. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  86. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  87. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  88. package/libx/_runtime/common/utils/resolveView.js +7 -5
  89. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  90. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  91. package/libx/_runtime/common/utils/template.js +54 -46
  92. package/libx/_runtime/db/Service.js +9 -2
  93. package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
  94. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  95. package/libx/_runtime/db/generic/create.js +1 -0
  96. package/libx/_runtime/db/generic/input.js +7 -11
  97. package/libx/_runtime/db/generic/integrity.js +2 -2
  98. package/libx/_runtime/db/generic/rewrite.js +2 -5
  99. package/libx/_runtime/db/generic/update.js +1 -0
  100. package/libx/_runtime/db/query/read.js +9 -4
  101. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  102. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  103. package/libx/_runtime/db/utils/columns.js +14 -43
  104. package/libx/_runtime/fiori/generic/activate.js +3 -2
  105. package/libx/_runtime/fiori/generic/before.js +2 -2
  106. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  107. package/libx/_runtime/fiori/generic/delete.js +3 -2
  108. package/libx/_runtime/fiori/generic/edit.js +2 -2
  109. package/libx/_runtime/fiori/generic/new.js +2 -2
  110. package/libx/_runtime/fiori/generic/patch.js +2 -2
  111. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  112. package/libx/_runtime/fiori/generic/read.js +17 -63
  113. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  114. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  115. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  116. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  117. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  118. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  119. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  120. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  121. package/libx/_runtime/fiori/utils/handler.js +3 -13
  122. package/libx/_runtime/fiori/utils/where.js +6 -1
  123. package/libx/_runtime/hana/pool.js +12 -11
  124. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  125. package/libx/_runtime/hana/searchToContains.js +3 -3
  126. package/libx/_runtime/index.js +5 -2
  127. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  128. package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
  129. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  130. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  131. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  132. package/libx/_runtime/messaging/message-queuing.js +18 -0
  133. package/libx/_runtime/remote/Service.js +14 -2
  134. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  135. package/libx/_runtime/remote/utils/client.js +117 -23
  136. package/libx/_runtime/sqlite/Service.js +2 -2
  137. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  138. package/libx/gql/GraphQLAdapter.js +33 -0
  139. package/libx/gql/constants/adapter.js +69 -0
  140. package/libx/gql/constants/cds.js +18 -0
  141. package/libx/gql/constants/graphql.js +33 -0
  142. package/libx/gql/resolvers/crud/create.js +15 -0
  143. package/libx/gql/resolvers/crud/delete.js +24 -0
  144. package/libx/gql/resolvers/crud/index.js +6 -0
  145. package/libx/gql/resolvers/crud/read.js +25 -0
  146. package/libx/gql/resolvers/crud/update.js +31 -0
  147. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  148. package/libx/gql/resolvers/field.js +5 -0
  149. package/libx/gql/resolvers/index.js +7 -0
  150. package/libx/gql/resolvers/mutation.js +23 -0
  151. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  152. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  153. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  154. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  155. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  156. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  157. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  158. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  159. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  164. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  165. package/libx/gql/resolvers/query.js +13 -0
  166. package/libx/gql/resolvers/root.js +34 -0
  167. package/libx/gql/schema/generate.js +18 -0
  168. package/libx/gql/schema/index.js +5 -0
  169. package/libx/gql/schema/mutation.js +76 -0
  170. package/libx/gql/schema/query.js +108 -0
  171. package/libx/gql/schema/typeDefMap.js +45 -0
  172. package/libx/gql/schema/utils/index.js +54 -0
  173. package/libx/gql/utils/index.js +12 -0
  174. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  175. package/libx/odata/index.js +80 -0
  176. package/libx/odata/odata2cqn/afterburner.js +170 -0
  177. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  178. package/libx/odata/odata2cqn/index.js +3 -0
  179. package/libx/odata/odata2cqn/parser.js +1 -0
  180. package/libx/odata/utils/index.js +64 -0
  181. package/libx/rest/RestAdapter.js +101 -0
  182. package/libx/rest/RestRequest.js +30 -0
  183. package/libx/rest/index.js +3 -0
  184. package/libx/rest/middleware/auth.js +22 -0
  185. package/libx/rest/middleware/content.js +15 -0
  186. package/libx/rest/middleware/create.js +40 -0
  187. package/libx/rest/middleware/delete.js +20 -0
  188. package/libx/rest/middleware/error.js +56 -0
  189. package/libx/rest/middleware/operation.js +39 -0
  190. package/libx/rest/middleware/parse.js +90 -0
  191. package/libx/rest/middleware/read.js +29 -0
  192. package/libx/rest/middleware/update.js +42 -0
  193. package/libx/rest/utils/data.js +65 -0
  194. package/package.json +4 -1
  195. package/server.js +20 -2
  196. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  197. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  198. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  199. package/libx/_runtime/common/utils/backlinks.js +0 -83
  200. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  201. package/libx/_runtime/odata/index.js +0 -55
  202. package/libx/_runtime/odata/odata2cqn.js +0 -1
  203. package/libx/_runtime/odata/readToCqn.js +0 -129
  204. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -3,6 +3,13 @@ const LOG = cds.log('audit-log')
3
3
 
4
4
  const v2utils = require('./utils/v2')
5
5
 
6
+ const ANONYMOUS = 'anonymous'
7
+
8
+ const _getTenantAndUser = () => ({
9
+ user: (cds.context && cds.context.user && cds.context.user.id) || ANONYMOUS,
10
+ tenant: (cds.context && cds.context.tenant) || ANONYMOUS
11
+ })
12
+
6
13
  module.exports = class AuditLogService extends cds.MessagingService {
7
14
  async init() {
8
15
  // call MessagingService's init, which handles outboxing
@@ -13,7 +20,8 @@ module.exports = class AuditLogService extends cds.MessagingService {
13
20
  this.ready = !!this.alc
14
21
  }
15
22
 
16
- async emit(event, data) {
23
+ async emit(first, second) {
24
+ const { event, data } = typeof first === 'object' ? first : { event: first, data: second }
17
25
  if (!this.options.outbox) return this.send(event, data)
18
26
 
19
27
  if (this.ready && this[event]) {
@@ -33,10 +41,7 @@ module.exports = class AuditLogService extends cds.MessagingService {
33
41
  async dataAccessLog({ accesses }) {
34
42
  if (!this.ready) throw new Error('AuditLogService not connected')
35
43
 
36
- const {
37
- tenant,
38
- user: { id: user }
39
- } = cds.context
44
+ const { tenant, user } = _getTenantAndUser()
40
45
 
41
46
  // build the logs
42
47
  const { entries, errors } = v2utils.buildDataAccessLogs(this.alc, accesses, tenant, user)
@@ -59,10 +64,7 @@ module.exports = class AuditLogService extends cds.MessagingService {
59
64
  async dataModificationLog({ modifications }) {
60
65
  if (!this.ready) throw new Error('AuditLogService not connected')
61
66
 
62
- const {
63
- tenant,
64
- user: { id: user }
65
- } = cds.context
67
+ const { tenant, user } = _getTenantAndUser()
66
68
 
67
69
  // build the logs
68
70
  const { entries, errors } = v2utils.buildDataModificationLogs(this.alc, modifications, tenant, user)
@@ -87,8 +89,9 @@ module.exports = class AuditLogService extends cds.MessagingService {
87
89
  // cds.context not always set on auth-related errors -> try to extract from data
88
90
  let user, tenant
89
91
  if (cds.context) {
90
- tenant = cds.context.tenant
91
- user = cds.context.user && cds.context.user.id
92
+ const tenantAndUser = _getTenantAndUser()
93
+ tenant = tenantAndUser.tenant
94
+ user = tenantAndUser.user
92
95
  } else {
93
96
  try {
94
97
  const parsed = JSON.parse(data)
@@ -101,10 +104,10 @@ module.exports = class AuditLogService extends cds.MessagingService {
101
104
  delete parsed.user
102
105
  }
103
106
  data = JSON.stringify(parsed)
104
- } catch (e) {
105
- // ignore
106
- }
107
+ } catch (e) {}
107
108
  }
109
+ if (!tenant) tenant = ANONYMOUS
110
+ if (!user) user = ANONYMOUS
108
111
 
109
112
  // build the log
110
113
  const entry = v2utils.buildSecurityLog(this.alc, action, data, tenant, user)
@@ -117,10 +120,7 @@ module.exports = class AuditLogService extends cds.MessagingService {
117
120
  async configChangeLog({ action, success, configurations }) {
118
121
  if (!this.ready) throw new Error('AuditLogService not connected')
119
122
 
120
- const {
121
- tenant,
122
- user: { id: user }
123
- } = cds.context
123
+ const { tenant, user } = _getTenantAndUser()
124
124
 
125
125
  // build the logs
126
126
  const { entries, errors } = v2utils.buildConfigChangeLogs(this.alc, configurations, tenant, user)
@@ -40,7 +40,7 @@ const _processorFnAccess = (accessLogs, model, req) => {
40
40
  element.parent['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' &&
41
41
  accessLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
42
42
  ) {
43
- addDataSubjectForDetailsEntity(row, accessLog, req, entity, model)
43
+ addDataSubjectForDetailsEntity(row, accessLog, req, entity, model, element)
44
44
  }
45
45
  }
46
46
  }
@@ -41,7 +41,8 @@ const _getOldAndNew = (action, row, key) => {
41
41
  const _addAttribute = (log, action, row, key) => {
42
42
  if (!log.attributes.find(ele => ele.name === key)) {
43
43
  const { oldValue, newValue } = _getOldAndNew(action, row, key)
44
- if (oldValue !== newValue) log.attributes.push({ name: key, oldValue, newValue })
44
+ if (oldValue !== newValue)
45
+ log.attributes.push({ name: key, oldValue: String(oldValue), newValue: String(newValue) })
45
46
  }
46
47
  }
47
48
 
@@ -75,7 +76,7 @@ const _processorFnModification = (modificationLogs, model, req, beforeWrite) =>
75
76
  element.parent['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' &&
76
77
  modificationLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
77
78
  ) {
78
- addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model)
79
+ addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model, element)
79
80
  }
80
81
  }
81
82
 
@@ -1,7 +1,6 @@
1
1
  const cds = require('../../../cds')
2
2
 
3
3
  const { getDataSubject } = require('../../../common/utils/csn')
4
- const { getBackLinks, isSelfManaged } = require('../../../common/utils/backlinks')
5
4
 
6
5
  const WRITE = { CREATE: 1, UPDATE: 1, DELETE: 1 }
7
6
  const ASPECTS = { CREATE: '_auditCreate', READ: '_auditRead', UPDATE: '_auditUpdate', DELETE: '_auditDelete' }
@@ -23,8 +22,11 @@ const getPick = event => {
23
22
  const categories = []
24
23
  if (!element.isAssociation && element.key) categories.push('ObjectID')
25
24
  if (
26
- element.parent['@PersonalData.EntitySemantics'] === 'DataSubject' &&
27
- element['@PersonalData.FieldSemantics'] === 'DataSubjectID'
25
+ element['@PersonalData.FieldSemantics'] === 'DataSubjectID' &&
26
+ // item element of arrayed element has no parent, but
27
+ // at the moment annotation on item level is not supported
28
+ element.parent &&
29
+ element.parent['@PersonalData.EntitySemantics'] === 'DataSubject'
28
30
  )
29
31
  categories.push('DataSubjectID')
30
32
  if (event in WRITE && element['@PersonalData.IsPotentiallyPersonal']) categories.push('IsPotentiallyPersonal')
@@ -55,14 +57,15 @@ const createLogEntry = (logs, entity, row) => {
55
57
  }
56
58
 
57
59
  const addObjectID = (log, row, key) => {
58
- if (!log.dataObject.id.find(ele => ele.keyName === key)) log.dataObject.id.push({ keyName: key, value: row[key] })
60
+ if (!log.dataObject.id.find(ele => ele.keyName === key))
61
+ log.dataObject.id.push({ keyName: key, value: String(row[key]) })
59
62
  }
60
63
 
61
64
  const addDataSubject = (log, row, key, entity) => {
62
65
  if (!log.dataSubject.type) log.dataSubject.type = entity.name
63
66
  if (!log.dataSubject.id.find(ele => ele.key === key)) {
64
67
  const value = row[key] || (row._old && row._old[key])
65
- log.dataSubject.id.push({ keyName: key, value })
68
+ log.dataSubject.id.push({ keyName: key, value: String(value) })
66
69
  }
67
70
  }
68
71
 
@@ -77,82 +80,39 @@ const _addKeysToWhere = (child, row) => {
77
80
  return keysWithValue
78
81
  }
79
82
 
80
- const _addForeignKeysToWhere = (parent, child, assoc) => {
81
- const foreignKeys = []
82
- const backlinks = getBackLinks(assoc)
83
- let parentName, childName
84
- if (assoc.isComposition && assoc.is2one && !assoc.on) {
85
- // look for foreign keys in parent
86
- parentName = parent.name
87
- childName = child.name
88
- } else if (assoc.is2many && isSelfManaged(assoc)) {
89
- // look for foreign keys in child
90
- parentName = child.name
91
- childName = parent.name
92
- }
93
-
94
- backlinks.forEach(el => {
95
- if (foreignKeys.length > 0) foreignKeys.push('and')
96
- foreignKeys.push({ ref: [parentName, el.entityKey] }, '=', {
97
- ref: [childName, el.targetKey]
98
- })
99
- })
100
- return foreignKeys
101
- }
102
-
103
- const _buildWhere = (child, dataSubjectInfo, row) => {
104
- const where = []
105
- where.push(
106
- ..._addForeignKeysToWhere(dataSubjectInfo.entity, child, dataSubjectInfo.assoc),
107
- 'and',
108
- ..._addKeysToWhere(child, row)
109
- )
110
- return where
111
- }
112
-
113
- const _buildSubSelect = (child, dataSubjectInfo, row) => {
114
- let previousCqn
83
+ const _buildSubSelect = (model, child, { element, up }, row, previousCqn) => {
84
+ const entity = element.parent
115
85
  const childCqn = SELECT.from(child.name)
116
86
  .columns(Object.keys(child.keys))
117
- .where(_buildWhere(child, dataSubjectInfo.previous[0] ? dataSubjectInfo.previous[0] : dataSubjectInfo, row))
118
- if (dataSubjectInfo.previous.length > 0) {
119
- let currentCQN
120
-
121
- dataSubjectInfo.previous.forEach((el, i) => {
122
- const nextEl = dataSubjectInfo.previous[i + 1]
123
- const args = nextEl
124
- ? [nextEl.entity, el.entity, nextEl.assoc]
125
- : [dataSubjectInfo.entity, el.entity, dataSubjectInfo.assoc]
126
-
127
- currentCQN = SELECT.from(el.entity.name)
128
- .columns(Object.keys(el.entity.keys))
129
- .where([..._addForeignKeysToWhere(...args), 'and', 'exists', previousCqn || childCqn])
130
-
131
- previousCqn = currentCQN
132
- })
87
+ .where(entity._relations[element.name].join(child.name, entity.name))
88
+ if (previousCqn) {
89
+ childCqn.where('exists', previousCqn)
90
+ } else {
91
+ childCqn.where(_addKeysToWhere(child, row))
133
92
  }
134
- return previousCqn || childCqn
93
+ if (up) return _buildSubSelect(model, entity, up, {}, childCqn)
94
+ return childCqn
135
95
  }
136
96
 
137
- const _getDataSubjectIdPromise = (child, dataSubjectInfo, row, req) => {
97
+ const _getDataSubjectIdPromise = (child, dataSubjectInfo, row, req, model) => {
138
98
  const root = dataSubjectInfo.entity
139
99
  const cqn = SELECT.from(root.name)
140
100
  .columns(Object.keys(root.keys))
141
- .where(['exists', _buildSubSelect(child, dataSubjectInfo, row)])
101
+ .where(['exists', _buildSubSelect(model, child, dataSubjectInfo.up, row)])
142
102
  return cds
143
103
  .tx(req)
144
104
  .run(cqn)
145
105
  .then(res => {
146
106
  const id = []
147
- for (const k in res[0]) id.push({ keyName: k, value: res[0][k] })
107
+ for (const k in res[0]) id.push({ keyName: k, value: String(res[0][k]) })
148
108
  return id
149
109
  })
150
110
  }
151
111
 
152
- const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
112
+ const addDataSubjectForDetailsEntity = (row, log, req, entity, model, element) => {
153
113
  const role = entity['@PersonalData.DataSubjectRole']
154
114
 
155
- const dataSubjectInfo = getDataSubject(entity, model, role)
115
+ const dataSubjectInfo = getDataSubject(entity, model, role, element)
156
116
 
157
117
  log.dataSubject.type = dataSubjectInfo.entity.name
158
118
 
@@ -165,7 +125,7 @@ const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
165
125
  if (!req.context._audit.dataSubjects.has(mapKey)) req.context._audit.dataSubjects.set(mapKey, new Map())
166
126
  const map = req.context._audit.dataSubjects.get(mapKey)
167
127
  if (map.has(role)) log.dataSubject.id = map.get(role)
168
- else map.set(role, _getDataSubjectIdPromise(entity, dataSubjectInfo, row, req))
128
+ else map.set(role, _getDataSubjectIdPromise(entity, dataSubjectInfo, row, req, model))
169
129
  }
170
130
 
171
131
  const resolveDataSubjectPromises = async logs => {
@@ -3,6 +3,8 @@ const LOG = cds.log('odata')
3
3
 
4
4
  const OData = require('./OData')
5
5
 
6
+ const { alias2ref } = require('../../../common/utils/csn')
7
+
6
8
  function _createNewService(name, csn, defaultOptions) {
7
9
  const reflectedModel = cds.linked(cds.compile.for.odata(csn))
8
10
  const options = Object.assign({}, defaultOptions, { reflectedModel })
@@ -13,6 +15,8 @@ function _createNewService(name, csn, defaultOptions) {
13
15
  service._isExtended = true
14
16
 
15
17
  const edm = cds.compile.to.edm(csn, { service: name })
18
+ alias2ref(service, edm)
19
+
16
20
  const odataService = new OData(edm, csn, options)
17
21
  odataService.addCDSServiceToChannel(service)
18
22
 
@@ -159,6 +159,7 @@ class OData {
159
159
 
160
160
  this._odataService.on(ATOMICITY_GROUP_START, (odataContext, done) => {
161
161
  const data = odataContext.applicationData
162
+
162
163
  // start tx
163
164
  const txs = (data.txs = data.txs || {})
164
165
  const {
@@ -179,28 +180,14 @@ class OData {
179
180
  this._odataService.on(ATOMICITY_GROUP_END, async (odataErr, odataContext, done) => {
180
181
  const tx = odataContext.applicationData.txs[odataContext.id]
181
182
  let errors = odataErr || odataContext.failedRequests.length > 0
182
- if (!errors) {
183
- try {
184
- await tx.commit(odataContext.applicationData.results[odataContext.id])
185
- done()
186
- } catch (e) {
187
- // tx gets rolled back automatically
188
- // set error on each request of changeset, if commit failed
189
- const changesetResults = odataContext.applicationData.results[odataContext.id]
190
- const failedRequests = changesetResults.reduce((obj, resultEntry) => {
191
- const requestId = resultEntry.req._.odataReq.getOdataRequestId()
192
- const { error, statusCode } = normalizeError(e, resultEntry.req)
193
- obj[requestId] = Object.assign(error, { statusCode })
194
- return obj
195
- }, {})
196
- done(e, { failedRequests })
197
- }
198
- } else {
183
+
184
+ if (errors) {
199
185
  // rollback without errors to not trigger srv.on('error') with array
200
186
  await tx.rollback()
201
187
  // invoke srv.on('error') for each error and build failedRequests that reflects error modifications
202
188
  errors = odataContext.applicationData.errors[odataContext.id]
203
189
  const failedRequests = {}
190
+
204
191
  for (const e of errors) {
205
192
  const { error: err, req } = e
206
193
  for (const each of cdsService._handlers._error) each.handler.call(cdsService, err, req)
@@ -208,7 +195,26 @@ class OData {
208
195
  const { error, statusCode } = normalizeError(err, req)
209
196
  failedRequests[requestId] = Object.assign(error, { statusCode })
210
197
  }
198
+
211
199
  done(new Error(`Atomicity group "${odataContext.id}" failed`), { failedRequests })
200
+ return
201
+ }
202
+
203
+ try {
204
+ await tx.commit(odataContext.applicationData.results[odataContext.id])
205
+ done()
206
+ } catch (e) {
207
+ // tx gets rolled back automatically
208
+ // set error on each request of changeset, if commit failed
209
+ const changesetResults = odataContext.applicationData.results[odataContext.id]
210
+ const failedRequests = changesetResults.reduce((obj, resultEntry) => {
211
+ const requestId = resultEntry.req._.odataReq.getOdataRequestId()
212
+ const { error, statusCode } = normalizeError(e, resultEntry.req)
213
+ obj[requestId] = Object.assign(error, { statusCode })
214
+ return obj
215
+ }, {})
216
+
217
+ done(e, { failedRequests })
212
218
  }
213
219
  })
214
220
 
@@ -233,37 +239,33 @@ class OData {
233
239
  /**
234
240
  * Process request.
235
241
  *
236
- * @param req
237
- * @param res
242
+ * @param {http.IncomingMessage} req
243
+ * @param {http.ServerResponse} res
238
244
  * @private
239
245
  */
240
246
  // REVISIT: Remove this when we replaced Okra
241
247
  process(req, res) {
248
+ const headers = req.headers
249
+ const acceptHeader = headers && headers.accept
250
+
242
251
  // default to combination [...];IEEE754Compatible=true;ExponentialDecimals=true if one is omitted
243
- if (req.headers && req.headers.accept && req.headers.accept.startsWith('application/json')) {
244
- if (
245
- req.headers.accept.includes('IEEE754Compatible=true') &&
246
- !req.headers.accept.includes('ExponentialDecimals')
247
- ) {
252
+ if (acceptHeader && acceptHeader.startsWith('application/json')) {
253
+ if (acceptHeader.includes('IEEE754Compatible=true') && !acceptHeader.includes('ExponentialDecimals')) {
248
254
  req.headers.accept += ';ExponentialDecimals=true'
249
- } else if (
250
- req.headers.accept.includes('ExponentialDecimals=true') &&
251
- !req.headers.accept.includes('IEEE754Compatible')
252
- ) {
255
+ } else if (acceptHeader.includes('ExponentialDecimals=true') && !acceptHeader.includes('IEEE754Compatible')) {
253
256
  req.headers.accept += ';IEEE754Compatible=true'
254
257
  }
255
258
 
259
+ const contentType = headers['content-type']
260
+
256
261
  // add IEEE754Compatible=true if !strict_numbers
257
262
  if (
258
263
  !cds.env.features.strict_numbers &&
259
- req.headers['content-type'] &&
260
- req.headers['content-type'].includes('application/json') &&
261
- !req.headers['content-type'].includes('IEEE754Compatible')
264
+ contentType &&
265
+ contentType.includes('application/json') &&
266
+ !contentType.includes('IEEE754Compatible')
262
267
  ) {
263
- req.headers['content-type'] = req.headers['content-type'].replace(
264
- 'application/json',
265
- 'application/json;IEEE754Compatible=true'
266
- )
268
+ req.headers['content-type'] = contentType.replace('application/json', 'application/json;IEEE754Compatible=true')
267
269
  }
268
270
  }
269
271
 
@@ -9,7 +9,7 @@ const { getSapMessages } = require('../../../../common/error/frontend')
9
9
  const { validateResourcePath } = require('../utils/request')
10
10
  const { isReturnMinimal } = require('../utils/handlerUtils')
11
11
  const readAfterWrite = require('../utils/readAfterWrite')
12
- const { toODataResult, postProcess } = require('../utils/result')
12
+ const { toODataResult, postProcess, postProcessMinimal } = require('../utils/result')
13
13
  const { mergeJson } = require('../../../services/utils/compareJson')
14
14
 
15
15
  /**
@@ -44,6 +44,8 @@ const create = service => {
44
44
  }
45
45
 
46
46
  postProcess(req, odataRes, service, result)
47
+ } else {
48
+ postProcessMinimal(req, result)
47
49
  }
48
50
 
49
51
  if (changeset) {
@@ -93,6 +93,11 @@ const getErrorHandler = (crashOnError = true, srv) => {
93
93
  req = odataReq.getIncomingRequest()
94
94
  }
95
95
 
96
+ if (err.getRootCause && typeof err.getRootCause === 'function') {
97
+ // > an OKRA error
98
+ err = _betterOkraError(err)
99
+ }
100
+
96
101
  // invoke srv.on('error', function (err, req) { ... }) here in special situations
97
102
  // REVISIT: if for compat reasons, remove once cds^5.1
98
103
  if (srv._handlers._error) {
@@ -107,11 +112,6 @@ const getErrorHandler = (crashOnError = true, srv) => {
107
112
  }
108
113
  }
109
114
 
110
- if (err.getRootCause && typeof err.getRootCause === 'function') {
111
- // > an OKRA error
112
- err = _betterOkraError(err)
113
- }
114
-
115
115
  // add content id if not generated by okra ("~...")
116
116
  const contentId = odataReq.getOdataRequestId()
117
117
  if (contentId && !contentId.match(/^~/)) err['@Core.ContentID'] = contentId
@@ -1,6 +1,7 @@
1
1
  const cds = require('../../../../cds')
2
2
  const { SELECT } = cds.ql
3
3
  const ODataRequest = require('../ODataRequest')
4
+ const { rewriteExpandAsterisk } = require('../../../../common/utils/rewriteAsterisks')
4
5
 
5
6
  const {
6
7
  QueryOptions,
@@ -78,7 +79,7 @@ const _getCount = async (tx, readReq) => {
78
79
 
79
80
  // Copy CQN including from and where and changing columns
80
81
  const select = SELECT.from(readReq.query.SELECT.from)
81
- select.SELECT.columns = [{ func: 'count', args: [{ val: '1' }], as: '_counted_' }]
82
+ select.SELECT.columns = [{ func: 'count', args: [{ val: '1' }], as: '$count' }]
82
83
 
83
84
  if (readReq.query.SELECT.where) select.SELECT.where = readReq.query.SELECT.where
84
85
  if (readReq.query.SELECT.search) select.SELECT.search = readReq.query.SELECT.search
@@ -92,13 +93,13 @@ const _getCount = async (tx, readReq) => {
92
93
 
93
94
  // Define new CQN
94
95
  req.query = select
96
+ // todo check limit
97
+ const result = await tx.dispatch(req)
95
98
 
96
- let result = await tx.dispatch(req)
99
+ const count = (result[0] && (result[0].$count || result[0]._counted_)) || 0
97
100
 
98
101
  // Transform into scalar result
99
- result = result[0] && result[0]._counted_ ? result[0]._counted_ : 0
100
-
101
- return toODataResult(result)
102
+ return toODataResult(count)
102
103
  }
103
104
 
104
105
  /**
@@ -318,6 +319,7 @@ const _readStream = async (tx, req, segments) => {
318
319
  if (
319
320
  headers &&
320
321
  headers.accept &&
322
+ contentType &&
321
323
  !headers.accept.includes('*/*') &&
322
324
  !headers.accept.includes(contentType) &&
323
325
  !headers.accept.includes(contentType.split('/')[0] + '/*')
@@ -327,7 +329,7 @@ const _readStream = async (tx, req, segments) => {
327
329
 
328
330
  if (contentType) streamObj['*@odata.mediaContentType'] = contentType
329
331
  if (contentDisposition) {
330
- req._.odataRes.setHeader('Content-Disposition', `attachment; filename="${contentDisposition}"`)
332
+ req._.odataRes.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(contentDisposition)}"`)
331
333
  }
332
334
  return streamObj
333
335
  }
@@ -381,6 +383,7 @@ const _readAndTransform = (tx, req, odataReq) => {
381
383
  break
382
384
  }
383
385
  }
386
+
384
387
  return _readStream(tx, req, segments)
385
388
  }
386
389
 
@@ -431,11 +434,13 @@ const _getTarget = (ref, target, definitions) => {
431
434
  }
432
435
 
433
436
  const _getRestrictedExpand = (columns, target, definitions) => {
434
- if (!columns || !target) return
437
+ if (!columns || !target || columns === '*') return
435
438
 
436
439
  const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
437
440
  const restrictions = annotation && annotation.map(element => element['='])
438
441
 
442
+ rewriteExpandAsterisk(columns, target)
443
+
439
444
  for (const col of columns) {
440
445
  if (col.expand) {
441
446
  if (restrictions && restrictions.length !== 0) {
@@ -473,6 +478,7 @@ const read = service => {
473
478
  return next(e)
474
479
  }
475
480
 
481
+ // REVISIT: this should be in common/generic/auth.js with the rest of the access control stuff
476
482
  const restricted = _getRestrictedExpand(
477
483
  req.query.SELECT && req.query.SELECT.columns,
478
484
  req.target,