@sap/cds 5.5.3 → 5.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 (227) hide show
  1. package/CHANGELOG.md +134 -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/index.js +1 -1
  12. package/lib/compile/to/sql.js +22 -2
  13. package/lib/connect/bindings.js +2 -1
  14. package/lib/connect/index.js +1 -1
  15. package/lib/core/infer.js +1 -1
  16. package/lib/core/reflect.js +3 -1
  17. package/lib/env/index.js +175 -41
  18. package/lib/env/requires.js +24 -3
  19. package/lib/i18n/localize.js +33 -5
  20. package/lib/index.js +7 -6
  21. package/lib/log/format/kibana.js +6 -2
  22. package/lib/ql/DELETE.js +1 -1
  23. package/lib/ql/INSERT.js +1 -1
  24. package/lib/ql/Query.js +13 -10
  25. package/lib/ql/SELECT.js +15 -8
  26. package/lib/ql/UPDATE.js +1 -1
  27. package/lib/ql/Whereable.js +5 -0
  28. package/lib/req/context.js +87 -37
  29. package/lib/req/{impl.js → request.js} +1 -1
  30. package/lib/req/{res.js → response.js} +0 -0
  31. package/lib/serve/Service-api.js +1 -1
  32. package/lib/serve/Service-dispatch.js +12 -2
  33. package/lib/serve/Service-handlers.js +21 -7
  34. package/lib/serve/Service-methods.js +1 -1
  35. package/lib/serve/Transaction.js +7 -6
  36. package/lib/serve/index.js +1 -1
  37. package/lib/utils/axios.js +7 -0
  38. package/lib/utils/data.js +1 -1
  39. package/libx/_runtime/audit/Service.js +18 -18
  40. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  41. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  42. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  43. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +6 -0
  44. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  45. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  51. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  52. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  53. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  54. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  57. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  58. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  59. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  64. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  67. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +9 -2
  68. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  69. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  70. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  71. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  72. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  73. package/libx/_runtime/cds-services/util/assert.js +29 -13
  74. package/libx/_runtime/cds.js +2 -1
  75. package/libx/_runtime/common/aspects/Association.js +72 -0
  76. package/libx/_runtime/common/aspects/any.js +8 -45
  77. package/libx/_runtime/common/aspects/entity.js +0 -1
  78. package/libx/_runtime/common/aspects/relation.js +40 -0
  79. package/libx/_runtime/common/aspects/utils.js +73 -1
  80. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  81. package/libx/_runtime/common/composition/data.js +3 -2
  82. package/libx/_runtime/common/composition/delete.js +3 -1
  83. package/libx/_runtime/common/composition/tree.js +23 -18
  84. package/libx/_runtime/common/composition/utils.js +34 -8
  85. package/libx/_runtime/common/error/frontend.js +6 -1
  86. package/libx/_runtime/common/generic/auth.js +15 -13
  87. package/libx/_runtime/common/generic/crud.js +2 -2
  88. package/libx/_runtime/common/generic/etag.js +11 -8
  89. package/libx/_runtime/common/generic/input.js +3 -3
  90. package/libx/_runtime/common/generic/paging.js +9 -5
  91. package/libx/_runtime/common/generic/put.js +3 -2
  92. package/libx/_runtime/common/generic/sorting.js +3 -3
  93. package/libx/_runtime/common/generic/temporal.js +3 -3
  94. package/libx/_runtime/common/toggles/alpha.js +1 -1
  95. package/libx/_runtime/common/utils/cqn.js +20 -1
  96. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  97. package/libx/_runtime/common/utils/csn.js +50 -52
  98. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  99. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  100. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  101. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  102. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  103. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  104. package/libx/_runtime/common/utils/resolveView.js +19 -9
  105. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  106. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  107. package/libx/_runtime/common/utils/template.js +54 -46
  108. package/libx/_runtime/db/Service.js +9 -2
  109. package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
  110. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  111. package/libx/_runtime/db/generic/create.js +1 -0
  112. package/libx/_runtime/db/generic/input.js +7 -11
  113. package/libx/_runtime/db/generic/integrity.js +2 -2
  114. package/libx/_runtime/db/generic/rewrite.js +2 -5
  115. package/libx/_runtime/db/generic/update.js +1 -0
  116. package/libx/_runtime/db/query/read.js +10 -5
  117. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  118. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  119. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  120. package/libx/_runtime/db/utils/columns.js +14 -43
  121. package/libx/_runtime/db/utils/deep.js +5 -7
  122. package/libx/_runtime/fiori/generic/activate.js +3 -2
  123. package/libx/_runtime/fiori/generic/before.js +2 -2
  124. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  125. package/libx/_runtime/fiori/generic/delete.js +3 -2
  126. package/libx/_runtime/fiori/generic/edit.js +2 -2
  127. package/libx/_runtime/fiori/generic/new.js +2 -2
  128. package/libx/_runtime/fiori/generic/patch.js +2 -2
  129. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  130. package/libx/_runtime/fiori/generic/read.js +17 -63
  131. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  132. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  133. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  134. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  135. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  136. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  137. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  138. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  139. package/libx/_runtime/fiori/utils/handler.js +3 -13
  140. package/libx/_runtime/fiori/utils/where.js +6 -1
  141. package/libx/_runtime/hana/Service.js +5 -2
  142. package/libx/_runtime/hana/execute.js +1 -1
  143. package/libx/_runtime/hana/pool.js +12 -11
  144. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  145. package/libx/_runtime/hana/searchToContains.js +3 -3
  146. package/libx/_runtime/index.js +5 -2
  147. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  148. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  149. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  150. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  151. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  152. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  153. package/libx/_runtime/messaging/message-queuing.js +18 -0
  154. package/libx/_runtime/remote/Service.js +14 -2
  155. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  156. package/libx/_runtime/remote/utils/client.js +117 -23
  157. package/libx/_runtime/sqlite/Service.js +4 -3
  158. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  159. package/libx/_runtime/sqlite/execute.js +1 -1
  160. package/libx/gql/GraphQLAdapter.js +33 -0
  161. package/libx/gql/constants/adapter.js +69 -0
  162. package/libx/gql/constants/cds.js +18 -0
  163. package/libx/gql/constants/graphql.js +33 -0
  164. package/libx/gql/resolvers/crud/create.js +15 -0
  165. package/libx/gql/resolvers/crud/delete.js +24 -0
  166. package/libx/gql/resolvers/crud/index.js +6 -0
  167. package/libx/gql/resolvers/crud/read.js +25 -0
  168. package/libx/gql/resolvers/crud/update.js +31 -0
  169. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  170. package/libx/gql/resolvers/field.js +5 -0
  171. package/libx/gql/resolvers/index.js +7 -0
  172. package/libx/gql/resolvers/mutation.js +23 -0
  173. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  174. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  175. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  176. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  177. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  178. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  179. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  180. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  181. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  182. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  183. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  184. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  185. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  186. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  187. package/libx/gql/resolvers/query.js +13 -0
  188. package/libx/gql/resolvers/root.js +34 -0
  189. package/libx/gql/schema/generate.js +18 -0
  190. package/libx/gql/schema/index.js +5 -0
  191. package/libx/gql/schema/mutation.js +76 -0
  192. package/libx/gql/schema/query.js +108 -0
  193. package/libx/gql/schema/typeDefMap.js +45 -0
  194. package/libx/gql/schema/utils/index.js +54 -0
  195. package/libx/gql/utils/index.js +12 -0
  196. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  197. package/libx/odata/index.js +80 -0
  198. package/libx/odata/odata2cqn/afterburner.js +170 -0
  199. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  200. package/libx/odata/odata2cqn/index.js +3 -0
  201. package/libx/odata/odata2cqn/parser.js +1 -0
  202. package/libx/odata/utils/index.js +64 -0
  203. package/libx/rest/RestAdapter.js +101 -0
  204. package/libx/rest/RestRequest.js +30 -0
  205. package/libx/rest/index.js +3 -0
  206. package/libx/rest/middleware/auth.js +22 -0
  207. package/libx/rest/middleware/content.js +15 -0
  208. package/libx/rest/middleware/create.js +40 -0
  209. package/libx/rest/middleware/delete.js +20 -0
  210. package/libx/rest/middleware/error.js +56 -0
  211. package/libx/rest/middleware/operation.js +39 -0
  212. package/libx/rest/middleware/parse.js +90 -0
  213. package/libx/rest/middleware/read.js +29 -0
  214. package/libx/rest/middleware/update.js +42 -0
  215. package/libx/rest/utils/data.js +65 -0
  216. package/package.json +4 -1
  217. package/server.js +42 -29
  218. package/lib/req/cls.js +0 -39
  219. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  220. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  221. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  222. package/libx/_runtime/common/utils/backlinks.js +0 -83
  223. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  224. package/libx/_runtime/odata/index.js +0 -55
  225. package/libx/_runtime/odata/odata2cqn.js +0 -1
  226. package/libx/_runtime/odata/readToCqn.js +0 -129
  227. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -39,25 +39,18 @@ const validationChecks = (event, data, target) => {
39
39
  const _enrichErrorDetails = (isPrimitive, error) => {
40
40
  const element = error.target ? ` '${error.target}' ` : ' '
41
41
  const typeDetails = isPrimitive ? '.' : ` according to type definition '${error.type}'.`
42
- return `Value '${error.value}' of element${element}is invalid${typeDetails}`
42
+ const value = typeof error.value === 'string' ? `'${error.value}'` : error.value
43
+ if (element && element.match(/\w/)) return `Value ${value} of element${element}is invalid${typeDetails}`
44
+ return `Value ${value} is invalid${typeDetails}`
43
45
  }
44
46
 
45
47
  // REVISIT: use i18n
46
- const _buildErrorMessage = (context, operation, type, typeErrors) => {
47
- return `Failed to validate return value ${type ? `of type '${type}' ` : ''}for custom ${operation.kind} '${
48
- context.event
48
+ const _getTypeError = (operation, type, errorDetails) => {
49
+ const typeErrors = errorDetails.map(error => _enrichErrorDetails(cds.builtin.types[type], error))
50
+ const msg = `Failed to validate return value ${type ? `of type '${type}' ` : ''}for custom ${operation.kind} '${
51
+ operation.name
49
52
  }': ${typeErrors.join(' ')}`
50
- }
51
-
52
- const _getTypeError = (context, operation, type, errorDetails) => {
53
- return getError(
54
- _buildErrorMessage(
55
- context,
56
- operation,
57
- type,
58
- errorDetails.map(error => _enrichErrorDetails(cds.builtin.types[type], error))
59
- )
60
- )
53
+ return getError(msg)
61
54
  }
62
55
 
63
56
  const _buildTypeErrorObject = (type, value) => {
@@ -79,14 +72,13 @@ const _checkSingle = (type, check, data) => {
79
72
  * Validate the return type values of custom operations (actions and functions) for primitive or complex values as
80
73
  * single values or arrays.
81
74
  *
82
- * @param {Context} context
83
75
  * @param {Operation} operation
84
76
  * @param {object} data
85
77
  * @throws Will throw an error with error code 500 if the validation fails. Contains a detailed error message of the
86
78
  * type and name of the custom operation, the invalid values, their names and their expected types.
87
79
  * @returns {boolean} Returns true if return type validation has passed.
88
80
  */
89
- const validateReturnType = (context, operation, data) => {
81
+ const validateReturnType = (operation, data) => {
90
82
  // array of or single return type
91
83
  // in case of modeled return type: { type: 'bookModel.Books', _type: csnDefinition }
92
84
  // in case of inline return type: { elements: ... } and no explicit name of return type
@@ -114,14 +106,21 @@ const validateReturnType = (context, operation, data) => {
114
106
  // Determine entity from bound or unbound action/function
115
107
  const returnTypeCsnDefinition = returnType._type || returnType
116
108
 
117
- checkResult = checkStatic(returnTypeCsnDefinition, data, true)
109
+ // REVISIT: remove exception with cds^6
110
+ // mtx returns object instead of string (as in modell) -> skip validation
111
+ if (returnTypeCsnDefinition.type !== 'cds.String') {
112
+ checkResult = checkStatic(returnTypeCsnDefinition, data, true)
113
+ }
118
114
  }
119
115
 
120
- if (checkResult.length !== 0) {
121
- throw _getTypeError(context, operation, returnType.type, checkResult)
116
+ if (checkResult && checkResult.length !== 0) {
117
+ throw _getTypeError(operation, returnType.type, checkResult)
122
118
  }
123
119
 
124
120
  return true
125
121
  }
126
122
 
127
- module.exports = { validationChecks, validateReturnType }
123
+ module.exports = {
124
+ validationChecks,
125
+ validateReturnType
126
+ }
@@ -127,7 +127,12 @@ const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }) =>
127
127
  cqn.SELECT.columns.forEach(column => {
128
128
  if (column.func) {
129
129
  // exclude $count by SELECT of number of Items in a Collection
130
- if (cqn.SELECT.columns.length === 1 && column.func === 'count' && column.as === '_counted_') return
130
+ if (
131
+ cqn.SELECT.columns.length === 1 &&
132
+ column.func === 'count' &&
133
+ (column.as === '_counted_' || column.as === '$count')
134
+ )
135
+ return
131
136
  toBeSearched.push(column)
132
137
  return
133
138
  }
@@ -141,15 +141,8 @@ const _addKeysToEntryIfNotExists = (keys, newEntry) => {
141
141
  }
142
142
  }
143
143
 
144
- const isSelfManaged = element => {
145
- if (element.on && element.on.length > 2) {
146
- return element.on[0].ref[0] === '$self' || element.on[2].ref[0] === '$self'
147
- }
148
- return false
149
- }
150
-
151
144
  const _isUnManaged = element => {
152
- return element.on && !isSelfManaged(element)
145
+ return element.on && !element._isSelfManaged
153
146
  }
154
147
 
155
148
  const _skip = (entity, prop) => entity.elements[prop]._target._hasPersistenceSkip
@@ -7,8 +7,9 @@ const { selectDeepUpdateData } = require('../../../common/composition')
7
7
  const { ensureDraftsSuffix } = require('../../../fiori/utils/handler')
8
8
 
9
9
  const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
10
- const cqn2cqn4sql = require('../../../common/utils/cqn2cqn4sql')
10
+ const { cqn2cqn4sql, convertPathExpressionToWhere } = require('../../../common/utils/cqn2cqn4sql')
11
11
  const { revertData } = require('../../../common/utils/resolveView')
12
+ const { removeIsActiveEntityRecursively } = require('../../../fiori/utils/where')
12
13
 
13
14
  module.exports = class {
14
15
  constructor(srv) {
@@ -32,31 +33,11 @@ module.exports = class {
32
33
  return columns
33
34
  }
34
35
 
35
- _createWhereCondition(entity, data) {
36
- return Object.keys(entity.keys).reduce((prev, curr) => {
37
- if (!DRAFT_COLUMNS.includes(curr)) {
38
- prev[curr] = data[curr]
39
- }
40
-
41
- return prev
42
- }, {})
43
- }
44
-
45
36
  _diffDelete(req) {
46
37
  const { DELETE } = (req._ && req._.query) || req.query
47
38
  const query = SELECT.from(DELETE.from).columns(this._createSelectColumnsForDelete(req.target))
48
39
  if (DELETE.where) query.where(...DELETE.where)
49
40
 
50
- // REVISIT: should be done in cqn2cqn4sql
51
- if (req.target._isDraftEnabled && query.SELECT.from.ref.some(r => r.where)) {
52
- query.SELECT.from.ref.forEach((r, i) => {
53
- if (!r.where) return
54
- const j = r.where.findIndex(w => w.ref && w.ref.some(r => r === 'IsActiveEntity'))
55
- if (j === -1) return
56
- r.where.splice(Math.max(j - 1, 0), 4)
57
- })
58
- }
59
-
60
41
  return cds
61
42
  .tx(req)
62
43
  .run(query)
@@ -92,14 +73,14 @@ module.exports = class {
92
73
 
93
74
  async _diffPatch(req, providedData) {
94
75
  if (cds.db) {
76
+ const { target, alias, where = [] } = convertPathExpressionToWhere(req.query.UPDATE.entity, this._srv.model, {})
77
+
78
+ const draftRef = { ref: [ensureDraftsSuffix(target)], as: alias }
79
+
95
80
  // SELECT because req.query in custom handler does not have access to _drafts
96
81
  req._.partialPersistentState = await cds
97
82
  .tx(req)
98
- .run(
99
- SELECT.from(ensureDraftsSuffix(req.target.name))
100
- .where(this._createWhereCondition(req.target, req.data))
101
- .limit(1)
102
- )
83
+ .run(SELECT.from(draftRef).where(removeIsActiveEntityRecursively(where)).limit(1))
103
84
 
104
85
  return compareJson(providedData || req.data, req._.partialPersistentState, req.target)
105
86
  }
@@ -2,7 +2,6 @@ const cds = require('../../../cds')
2
2
 
3
3
  const { SELECT } = cds.ql
4
4
 
5
- const { foreignKeyPropagations } = require('../../../common/utils/foreignKeyPropagations')
6
5
  const { checkReferenceIntegrity } = require('../../util/assert')
7
6
  const { processDeep, processDeepAsync } = require('../../util/dataProcessUtils')
8
7
 
@@ -74,9 +73,8 @@ const _getSelectCQN = (req, columns) => {
74
73
  }
75
74
 
76
75
  function _fillForeignKeysWithNull(managedAssocToOneElement, row) {
77
- const keys = foreignKeyPropagations(managedAssocToOneElement)
78
- for (const key of keys) {
79
- row[key.parentFieldName] = null
76
+ for (const key of managedAssocToOneElement._foreignKeys) {
77
+ if (key.parentElement) row[key.parentElement.name] = null
80
78
  }
81
79
  }
82
80
 
@@ -1,8 +1,10 @@
1
+ const cds = require('../../cds')
1
2
  const { all, resolve } = require('../../common/utils/thenable')
2
3
  const { getDependents } = require('../../common/utils/csn')
3
4
 
4
5
  // REVISIT: replace with cds.Request
5
6
  const getEntry = require('../../common/error/entry')
7
+ const crypto = require('crypto')
6
8
 
7
9
  const ISO_DATE_PART1 =
8
10
  '[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)'
@@ -174,11 +176,12 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
174
176
  return found || ignoreNonModelledData
175
177
  }
176
178
 
177
- const _checkStaticElementByKey = (entity, key, value, result, ignoreNonModelledData) => {
178
- const element = entity.elements && entity.elements[key]
179
+ const _checkStaticElementByKey = (definition, key, value, result, ignoreNonModelledData) => {
180
+ const elementsOrParameters = definition.elements || definition.params
181
+ const elementOrParameter = elementsOrParameters[key]
179
182
 
180
- if (!element) {
181
- if (!checkComplexType([key, value], entity.elements, ignoreNonModelledData)) {
183
+ if (!elementOrParameter) {
184
+ if (!checkComplexType([key, value], elementsOrParameters, ignoreNonModelledData)) {
182
185
  result.push(assertError(ASSERT_VALID_ELEMENT, { name: key }))
183
186
  }
184
187
 
@@ -186,17 +189,17 @@ const _checkStaticElementByKey = (entity, key, value, result, ignoreNonModelledD
186
189
  }
187
190
 
188
191
  let check
189
- if (element.type === 'cds.UUID' && entity.name === 'ProvisioningService.tenant') {
192
+ if (elementOrParameter.type === 'cds.UUID' && definition.name === 'ProvisioningService.tenant') {
190
193
  // > old SCP accounts don't have UUID ids
191
194
  check = CDS_TYPE_CHECKS['cds.String']
192
195
  } else {
193
- check = CDS_TYPE_CHECKS[element.type]
196
+ check = CDS_TYPE_CHECKS[elementOrParameter.type]
194
197
  }
195
198
 
196
- if (check && !check(value, element)) {
199
+ if (check && !check(value, elementOrParameter)) {
197
200
  // code, entity, element, value
198
- const args = [typeof value === 'string' ? '"' + value + '"' : value, element.type]
199
- result.push(assertError({ code: ASSERT_DATA_TYPE, args }, element, value, key))
201
+ const args = [typeof value === 'string' ? '"' + value + '"' : value, elementOrParameter.type]
202
+ result.push(assertError({ code: ASSERT_DATA_TYPE, args }, elementOrParameter, value, key))
200
203
  }
201
204
 
202
205
  return result
@@ -294,14 +297,14 @@ const checkInputConstraints = ({ element, value, errors, key, pathSegments, even
294
297
  return errors
295
298
  }
296
299
 
297
- const checkStatic = (entity, data, ignoreNonModelledData = false) => {
300
+ const checkStatic = (definition, data, ignoreNonModelledData = false) => {
298
301
  if (!Array.isArray(data)) data = [data]
299
302
 
300
303
  return data.reduce((result, row) => {
301
304
  return Object.entries(row)
302
305
  .filter(([key, value]) => value !== null && value !== undefined)
303
306
  .reduce((result, [key, value]) => {
304
- return _checkStaticElementByKey(entity, key, value, result, ignoreNonModelledData)
307
+ return _checkStaticElementByKey(definition, key, value, result, ignoreNonModelledData)
305
308
  }, result)
306
309
  }, [])
307
310
  }
@@ -320,6 +323,20 @@ const _checkExistsWhere = (entity, whereList, run) => {
320
323
  }
321
324
  }
322
325
 
326
+ if (cds.context) {
327
+ const hash = crypto.createHash('sha1').update(JSON.stringify(cqn)).digest('base64') // fastest hash
328
+ if (!cds.context.__alreadyExecutedIntegrityChecks) cds.context.__alreadyExecutedIntegrityChecks = new Map()
329
+ if (cds.context.__alreadyExecutedIntegrityChecks.has(hash)) {
330
+ return cds.context.__alreadyExecutedIntegrityChecks.get(hash)
331
+ } else {
332
+ const promise = run(cqn).then(exists => {
333
+ return exists.length !== 0
334
+ })
335
+ // we store the promise object in the map, it won't get executed twice when calling await Promise.all([promise, promise])
336
+ cds.context.__alreadyExecutedIntegrityChecks.set(hash, promise)
337
+ return promise
338
+ }
339
+ }
323
340
  return run(cqn).then(exists => {
324
341
  return exists.length !== 0
325
342
  })
@@ -553,9 +570,8 @@ const checkKeys = (entity, data) => {
553
570
  const entityKeys = Object.keys(entity.keys)
554
571
  return data.reduce((result, row) => {
555
572
  for (const key of entityKeys) {
556
- if (entityKeys.some(key => row[key] === undefined)) {
573
+ if (row[key] === undefined && entity.elements[key].type !== 'cds.Association')
557
574
  result.push(assertError(ASSERT_NOT_NULL, entity.elements[key]))
558
- }
559
575
  }
560
576
  return result
561
577
  }, [])
@@ -5,8 +5,9 @@ module.exports = cds
5
5
  /*
6
6
  * csn aspects
7
7
  */
8
- const { any, entity } = cds.builtin.classes
8
+ const { any, entity, Association } = cds.builtin.classes
9
9
  cds.extend(any).with(require('./common/aspects/any'))
10
+ cds.extend(Association).with(require('./common/aspects/Association'))
10
11
  cds.extend(entity).with(require('./common/aspects/entity'))
11
12
 
12
13
  /*
@@ -0,0 +1,72 @@
1
+ // global.cds is used on purpose here!
2
+ const cds = global.cds
3
+
4
+ const ODATA_CONTAINED = '@odata.contained'
5
+
6
+ const { isSelfManaged, isBacklink, getAnchor, getBacklink } = require('./utils')
7
+ const { foreignKeyPropagations } = require('../utils/foreignKeyPropagations')
8
+
9
+ module.exports = class {
10
+ get _isAssociationStrict() {
11
+ return (
12
+ this.own('__isAssociationStrict') ||
13
+ this.set('__isAssociationStrict', !!(this.isAssociation && !this.isComposition))
14
+ )
15
+ }
16
+
17
+ get _isAssociationEffective() {
18
+ return (
19
+ this.own('__isAssociationEffective') ||
20
+ this.set(
21
+ '__isAssociationEffective',
22
+ this._isAssociationStrict && (!this[ODATA_CONTAINED] || this.name === 'DraftAdministrativeData')
23
+ )
24
+ )
25
+ }
26
+
27
+ get _isCompositionEffective() {
28
+ return (
29
+ this.own('__isCompositionEffective') ||
30
+ this.set(
31
+ '__isCompositionEffective',
32
+ this.isComposition ||
33
+ (this._isAssociationStrict && this[ODATA_CONTAINED] && this.name !== 'DraftAdministrativeData')
34
+ )
35
+ )
36
+ }
37
+
38
+ get _isContained() {
39
+ return (
40
+ this.own('__isContained') ||
41
+ this.set(
42
+ '__isContained',
43
+ this.name !== 'DraftAdministrativeData_DraftUUID' &&
44
+ ((this.isAssociation && this[ODATA_CONTAINED]) || (this.isComposition && cds.env.effective.odata.containment))
45
+ )
46
+ )
47
+ }
48
+
49
+ get _isSelfManaged() {
50
+ return this.own('__isSelfManaged') || this.set('__isSelfManaged', isSelfManaged(this))
51
+ }
52
+
53
+ get _isBacklink() {
54
+ return this.own('__isBacklink') || this.set('__isBacklink', isBacklink(this))
55
+ }
56
+
57
+ get _isCompositionBacklink() {
58
+ return this.own('__isCompositionBacklink') || this.set('__isCompositionBacklink', isBacklink(this, true))
59
+ }
60
+
61
+ get _anchor() {
62
+ return this.own('__anchor') || this.set('__anchor', getAnchor(this))
63
+ }
64
+
65
+ get _backlink() {
66
+ return this.own('__backlink') || this.set('__backlink', getBacklink(this))
67
+ }
68
+
69
+ get _foreignKeys() {
70
+ return this.own('__foreignKeys') || this.set('__foreignKeys', foreignKeyPropagations(this))
71
+ }
72
+ }
@@ -1,54 +1,12 @@
1
- // global.cds is used on purpose here!
2
- const cds = global.cds
3
-
4
- const ODATA_CONTAINED = '@odata.contained'
5
-
6
- const { isMandatory, isReadOnly } = require('./utils')
1
+ const { getRelations, isMandatory, isReadOnly } = require('./utils')
7
2
 
3
+ // NOTE: Please only add things which are relevant to _any_ type,
4
+ // use specialized types otherwise (entity, Association, ...).
8
5
  module.exports = class {
9
- get _isAssociationStrict() {
10
- return (
11
- this.own('__isAssociationStrict') ||
12
- this.set('__isAssociationStrict', !!(this.isAssociation && !this.isComposition))
13
- )
14
- }
15
-
16
- get _isAssociationEffective() {
17
- return (
18
- this.own('__isAssociationEffective') ||
19
- this.set(
20
- '__isAssociationEffective',
21
- this._isAssociationStrict && (!this[ODATA_CONTAINED] || this.name === 'DraftAdministrativeData')
22
- )
23
- )
24
- }
25
-
26
- get _isCompositionEffective() {
27
- return (
28
- this.own('__isCompositionEffective') ||
29
- this.set(
30
- '__isCompositionEffective',
31
- this.isComposition ||
32
- (this._isAssociationStrict && this[ODATA_CONTAINED] && this.name !== 'DraftAdministrativeData')
33
- )
34
- )
35
- }
36
-
37
6
  get _isStructured() {
38
7
  return this.own('__isStructured') || this.set('__isStructured', !!this.elements && this.kind !== 'entity')
39
8
  }
40
9
 
41
- get _isContained() {
42
- return (
43
- this.own('__isContained') ||
44
- this.set(
45
- '__isContained',
46
- this.name !== 'DraftAdministrativeData_DraftUUID' &&
47
- ((this.isAssociation && this[ODATA_CONTAINED]) || (this.isComposition && cds.env.effective.odata.containment))
48
- )
49
- )
50
- }
51
-
52
10
  get _isMandatory() {
53
11
  return this.own('__isMandatory') || this.set('__isMandatory', !this.isAssociation && isMandatory(this))
54
12
  }
@@ -56,4 +14,9 @@ module.exports = class {
56
14
  get _isReadOnly() {
57
15
  return this.own('__isReadOnly') || this.set('__isReadOnly', !this.key && isReadOnly(this))
58
16
  }
17
+
18
+ // REVISIT: Where to put?
19
+ get _relations() {
20
+ return this.own('__relations') || this.set('__relations', getRelations(this))
21
+ }
59
22
  }
@@ -44,7 +44,6 @@ module.exports = class {
44
44
  // lazily require on first use
45
45
  getSearchableColumns =
46
46
  getSearchableColumns || require('../../cds-services/services/utils/columns').getSearchableColumns
47
-
48
47
  return this.own('__searchableColumns') || this.set('__searchableColumns', getSearchableColumns(this))
49
48
  }
50
49
 
@@ -0,0 +1,40 @@
1
+ const { getOnCond } = require('../utils/generateOnCond')
2
+
3
+ let initializing = false
4
+
5
+ module.exports = class Relation {
6
+ constructor(csn, path = []) {
7
+ if (!initializing) throw new Error(`Do not new a relation, use 'Relation.to()' instead`)
8
+ Object.defineProperty(this, 'csn', { get: () => csn })
9
+ Object.defineProperty(this, 'path', {
10
+ get: () => path,
11
+ set: _ => {
12
+ path = _
13
+ }
14
+ })
15
+ if (csn.target) Object.defineProperty(this, 'target', { get: () => csn.target })
16
+ initializing = false
17
+ }
18
+
19
+ static to(from, name) {
20
+ initializing = true
21
+ if (!name) return new Relation(from)
22
+ return from._elements[name] && new Relation(from._elements[name], [...from.path, name])
23
+ }
24
+
25
+ _has(prop) {
26
+ return Reflect.has(this, prop) && !this._elements[prop]
27
+ }
28
+
29
+ get _elements() {
30
+ if (this.csn.elements) return this.csn.elements
31
+ if (this.csn._target && this.csn._target.elements) return this.csn._target.elements
32
+ // if (csn.targetAspect) relation.elements = model.definitions[csn.targetAspect].elements
33
+ // if (csn.kind = 'type') relation.elements = model.definitions[csn.type].element
34
+ return {}
35
+ }
36
+
37
+ join(fromAlias = '', toAlias = '') {
38
+ return getOnCond(this.csn, this.path, { select: fromAlias, join: toAlias })
39
+ }
40
+ }
@@ -1,3 +1,5 @@
1
+ const Relation = require('./relation')
2
+
1
3
  const CommonFieldControl = e => {
2
4
  const cfr = e['@Common.FieldControl']
3
5
  return cfr && cfr['#']
@@ -65,10 +67,80 @@ const hasSensitiveData = entity => {
65
67
  return val
66
68
  }
67
69
 
70
+ const _exposeRelation = relation => Object.defineProperty({}, '_', { get: () => relation })
71
+
72
+ const _relationHandler = relation => ({
73
+ get: (target, name) => {
74
+ const path = name.split(',')
75
+ const prop = path.join('_')
76
+ if (!target[prop]) {
77
+ if (path.length === 1) {
78
+ // REVISIT: property 'join' must not be used in CSN to make this working
79
+ if (relation._has(prop)) return relation[prop]
80
+ const newRelation = Relation.to(relation, prop)
81
+ if (newRelation) {
82
+ target[prop] = new Proxy(_exposeRelation(newRelation), _relationHandler(newRelation))
83
+ }
84
+ return target[prop]
85
+ }
86
+ target[prop] = path.reduce((r, p) => r[p] || r.csn._relations[p], relation)
87
+ target[prop].path = path
88
+ }
89
+ return target[prop]
90
+ }
91
+ })
92
+
93
+ const getRelations = e => {
94
+ const newRelation = Relation.to(e)
95
+ return new Proxy(_exposeRelation(newRelation), _relationHandler(newRelation))
96
+ }
97
+
98
+ const _hasJoinCondition = e => e.isAssociation && e.on && e.on.length > 2
99
+
100
+ const _isSelfRef = e => e.ref && e.ref[0] === '$self'
101
+
102
+ const _getBacklinkName = on => {
103
+ const i = on.findIndex(_isSelfRef)
104
+ if (i === -1) return
105
+ let ref
106
+ if (on[i + 1] && on[i + 1] === '=') ref = on[i + 2].ref
107
+ if (on[i - 1] && on[i - 1] === '=') ref = on[i - 2].ref
108
+ return ref && ref[ref.length - 1]
109
+ }
110
+
111
+ const isSelfManaged = e => {
112
+ if (!_hasJoinCondition(e)) return
113
+ return !!e.on.find(_isSelfRef)
114
+ }
115
+
116
+ const isBacklink = (e, checkComposition) => getAnchor(e, checkComposition) && true
117
+
118
+ const _isUnManagedAssociation = (e, checkComposition) =>
119
+ e.isAssociation && (!checkComposition || e._isCompositionEffective) && _hasJoinCondition(e)
120
+
121
+ const getAnchor = (e, checkComposition) => {
122
+ if (!(e._isAssociationStrict && (e.keys || e.on))) return
123
+ for (const anchor of Object.values(e._target.associations || {})) {
124
+ if (!_isUnManagedAssociation(anchor, checkComposition)) continue
125
+ if (_getBacklinkName(anchor.on) === e.name && anchor.target === e.parent.name) return anchor
126
+ }
127
+ }
128
+
129
+ const getBacklink = (e, checkComposition) => {
130
+ if (!_isUnManagedAssociation(e, checkComposition)) return
131
+ const backlinkName = _getBacklinkName(e.on)
132
+ if (backlinkName) return e._target && e._target.elements && e._target.elements[backlinkName]
133
+ }
134
+
68
135
  module.exports = {
69
136
  isMandatory,
70
137
  isReadOnly,
71
138
  getETag,
72
139
  hasPersonalData,
73
- hasSensitiveData
140
+ hasSensitiveData,
141
+ getRelations,
142
+ isSelfManaged,
143
+ isBacklink,
144
+ getAnchor,
145
+ getBacklink
74
146
  }
@@ -1,21 +1,17 @@
1
1
  const cds = require('../../../../cds')
2
2
 
3
- const _require = require('../../../utils/require')
4
-
5
3
  const getCredentials = uaa => {
6
- uaa = uaa || cds.env.requires.uaa || {}
4
+ uaa =
5
+ uaa && uaa.credentials
6
+ ? uaa
7
+ : cds.env.requires.uaa && cds.env.requires.uaa.credentials
8
+ ? cds.env.requires.uaa
9
+ : cds.env.requires.xsuaa && cds.env.requires.xsuaa.credentials
10
+ ? cds.env.requires.xsuaa
11
+ : {}
7
12
 
8
- if (!uaa.credentials) {
9
- try {
10
- const vcap = cds.env.requires.uaa && cds.env.requires.uaa.vcap
11
- uaa.credentials = _require('@sap/xsenv').serviceCredentials(vcap || { label: 'xsuaa' })
12
- } catch (e) {
13
- const msg =
14
- 'Unable to get xsuaa credentials. Please make sure your app is bound to a single xsuaa service instance or that you provide vcap information in the requires.uaa section.'
15
- // REVISIT: switch to verror
16
- throw Object.assign(new Error(msg), { original: e })
17
- }
18
- }
13
+ if (!uaa.credentials)
14
+ throw Object.assign(new Error('No or malformed uaa credentials'), { credentials: uaa.credentials })
19
15
 
20
16
  return uaa.credentials
21
17
  }
@@ -4,8 +4,9 @@ const { getEntityNameFromUpdateCQN } = require('../utils/cqn')
4
4
 
5
5
  const { ensureNoDraftsSuffix } = require('../utils/draft')
6
6
  const { getDBTable } = require('../utils/resolveView')
7
- const cqn2cqn4sql = require('../utils/cqn2cqn4sql')
7
+ const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
8
8
  const cds = require('../../cds')
9
+ const { SELECT } = cds.ql
9
10
 
10
11
  /*
11
12
  * own utils
@@ -192,7 +193,7 @@ const _select = ({
192
193
  }) => {
193
194
  const entity = definitions && definitions[entityName]
194
195
  const from = ctUtils.addDraftSuffix(draft, entity.name)
195
- const selectCQN = { SELECT: { from: { ref: [from] } } }
196
+ const selectCQN = SELECT.from(from)
196
197
  if (alias) selectCQN.SELECT.from.as = alias
197
198
  const selectAll = includeAllColumns || (includeAllRootColumns && root)
198
199
  selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAll)
@@ -20,8 +20,10 @@ const _recursivelyAliasRefs = (something, newAlias, oldAlias, subselect = false)
20
20
  if (oldAlias && something.ref[0] === oldAlias) something.ref[0] = newAlias
21
21
  else if (!subselect) something.ref.unshift(newAlias)
22
22
  } else {
23
- for (const key in something)
23
+ for (const key in something) {
24
+ if (key === 'from') continue // Workaround: Deep delete to be rewritten
24
25
  _recursivelyAliasRefs(something[key], newAlias, oldAlias, subselect || key === 'SELECT')
26
+ }
25
27
  }
26
28
  }
27
29
  }