@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
@@ -0,0 +1,119 @@
1
+ const { EXT_BACK_PACK, getExtendedFields, hasExtendedEntity, isExtendedEntity, getTargetRead } = require('../utils')
2
+
3
+ const _addBackPack = (columns, extFields, alias) => {
4
+ if (!columns) return
5
+
6
+ const hasBackPack = columns.some(
7
+ col => col.ref && col.ref[col.ref.length - 1] === EXT_BACK_PACK && _hasAlias(col.ref, alias)
8
+ )
9
+ if (hasBackPack) return // get out early, avoiding overhead of second check
10
+
11
+ const hasExtFields = columns.some(
12
+ col => col.ref && extFields.includes(col.ref[col.ref.length - 1]) && _hasAlias(col.ref, alias)
13
+ )
14
+
15
+ if (hasExtFields) {
16
+ const col = { ref: [EXT_BACK_PACK] }
17
+ if (alias) col.ref.unshift(alias)
18
+ columns.push(col)
19
+ }
20
+
21
+ /*
22
+ Removing backpack if not needed doesn't work. Probably ref copy problem.
23
+ if (hasBackPack && !hasExtFields) remove backpack.
24
+ */
25
+ }
26
+
27
+ const _hasAlias = (ref, alias) => {
28
+ return (ref.length === 1 && !alias) || (ref.length > 1 && ref[0] === alias)
29
+ }
30
+
31
+ const _removeExtendedFields = (columns, extFields, alias) => {
32
+ if (!columns) return
33
+
34
+ let i = columns.length
35
+ while (i--) {
36
+ const col = columns[i]
37
+ if (col.ref && extFields.includes(col.ref[col.ref.length - 1]) && _hasAlias(col.ref, alias)) {
38
+ columns.splice(i, 1)
39
+ }
40
+ }
41
+ }
42
+
43
+ const _transformUnion = (req, model) => {
44
+ // second element is active entity
45
+ const name = req.target.name.SET ? req.target.name.SET.args[1]._target.name : req.target.name
46
+ const extFields = getExtendedFields(name, model)
47
+ _addBackPack(req.query.SELECT.columns, extFields)
48
+ _removeExtendedFields(req.query.SELECT.columns, extFields)
49
+
50
+ _addBackPack(
51
+ req.query.SELECT.from.SET.args[0].SELECT.columns,
52
+ extFields,
53
+ req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
54
+ )
55
+ _addBackPack(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
56
+ _removeExtendedFields(
57
+ req.query.SELECT.from.SET.args[0].SELECT.columns,
58
+ extFields,
59
+ req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
60
+ )
61
+ _removeExtendedFields(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
62
+ }
63
+
64
+ const _getAliasedEntitiesForJoin = (args, model) => {
65
+ const extEntities = []
66
+
67
+ args.forEach(arg => {
68
+ if (arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministativeData' && isExtendedEntity(arg.ref[0], model)) {
69
+ const extFields = getExtendedFields(arg.ref[0], model)
70
+ extEntities.push({ name: arg.ref[0], as: arg.as, extFields })
71
+ }
72
+
73
+ if (arg.join) {
74
+ extEntities.push(..._getAliasedEntitiesForJoin(arg.args, model))
75
+ }
76
+ })
77
+
78
+ return extEntities
79
+ }
80
+
81
+ const _transformJoin = (req, model) => {
82
+ const extEntities = _getAliasedEntitiesForJoin(req.query.SELECT.from.args, model)
83
+
84
+ extEntities.forEach(ext => {
85
+ _addBackPack(req.query.SELECT.columns, ext.extFields, ext.as)
86
+ _removeExtendedFields(req.query.SELECT.columns, ext.extFields, ext.as)
87
+ })
88
+ }
89
+
90
+ const _transformColumns = (columns, targetName, model) => {
91
+ if (!columns) return
92
+
93
+ const extFields = getExtendedFields(targetName, model)
94
+ if (extFields.length !== 0) {
95
+ _addBackPack(columns, extFields)
96
+ _removeExtendedFields(columns, extFields)
97
+ }
98
+
99
+ columns.forEach(col => {
100
+ if (col.ref && col.expand) {
101
+ const expTargetName = model.definitions[targetName].elements[col.ref[0]].target
102
+ _transformColumns(col.expand, expTargetName, model)
103
+ }
104
+ })
105
+ }
106
+
107
+ function transformExtendedFieldsREAD(req) {
108
+ if (!hasExtendedEntity(req, this.model)) return
109
+
110
+ const target = getTargetRead(req)
111
+ _transformColumns(req.query.SELECT.columns, target.name, this.model)
112
+
113
+ if (req.query.SELECT.from.SET) return _transformUnion(req, this.model) // union
114
+ if (req.query.SELECT.from.join) return _transformJoin(req, this.model) // join
115
+ }
116
+
117
+ module.exports = {
118
+ transformExtendedFieldsREAD
119
+ }
@@ -0,0 +1,43 @@
1
+ const { EXT_BACK_PACK, hasExtendedEntity, getTargetRead } = require('../utils')
2
+
3
+ const getTemplate = require('../../../common/utils/template')
4
+ const templateProcessor = require('../../../common/utils/templateProcessor')
5
+
6
+ const _pick = element => {
7
+ return element['@cds.extension']
8
+ }
9
+
10
+ const _processorFn = ({ row, key }) => {
11
+ if (row[EXT_BACK_PACK]) {
12
+ const extensions = JSON.parse(row[EXT_BACK_PACK])
13
+ Object.keys(extensions).forEach(field => {
14
+ row[field] = extensions[field]
15
+ })
16
+
17
+ delete row[EXT_BACK_PACK]
18
+ }
19
+
20
+ if (row[key] === undefined) {
21
+ row[key] = null
22
+ }
23
+ }
24
+
25
+ function transformExtendedFieldsRESULT(result, req) {
26
+ if (!result || !hasExtendedEntity(req, this.model)) return
27
+
28
+ const template = getTemplate('transform-result', this, getTargetRead(req), {
29
+ pick: _pick
30
+ })
31
+
32
+ if (template.elements.size > 0) {
33
+ const result_ = Array.isArray(result) ? result : [result]
34
+ for (const row of result_) {
35
+ const args = { processFn: _processorFn, row, template }
36
+ templateProcessor(args)
37
+ }
38
+ }
39
+ }
40
+
41
+ module.exports = {
42
+ transformExtendedFieldsRESULT
43
+ }
@@ -0,0 +1,62 @@
1
+ const { EXT_BACK_PACK, getTargetWrite, isExtendedEntity } = require('../utils')
2
+
3
+ const getTemplate = require('../../../common/utils/template')
4
+ const templateProcessor = require('../../../common/utils/templateProcessor')
5
+
6
+ const _pick = element => {
7
+ return element['@cds.extension']
8
+ }
9
+
10
+ const _processorFn = ({ row, key }) => {
11
+ if (row[key] === undefined) return
12
+
13
+ if (!row[EXT_BACK_PACK]) {
14
+ row[EXT_BACK_PACK] = '{}'
15
+ }
16
+
17
+ const json = JSON.parse(row[EXT_BACK_PACK])
18
+ json[key] = row[key]
19
+ row[EXT_BACK_PACK] = JSON.stringify(json)
20
+ delete row[key]
21
+ }
22
+
23
+ function transformExtendedFieldsCREATE(req) {
24
+ if (!req.target) return
25
+
26
+ const target = getTargetWrite(req.target, this.model)
27
+ const template = getTemplate('transform-write', this, target, { pick: _pick })
28
+
29
+ if (template && template.elements.size > 0) {
30
+ for (const row of req.query.INSERT.entries) {
31
+ const args = { processFn: _processorFn, row, template }
32
+ templateProcessor(args)
33
+ }
34
+ }
35
+ }
36
+
37
+ async function transformExtendedFieldsUPDATE(req) {
38
+ if (!req.target || !req.query.UPDATE.where) return
39
+
40
+ const target = getTargetWrite(req.target, this.model)
41
+ const template = getTemplate('transform-write', Object.assign(req, { model: this.model }), target, { pick: _pick })
42
+
43
+ if (template && template.elements.size > 0) {
44
+ // In patch case we first should obtain backpack from db.
45
+ // Patch can be only applied to the root.
46
+ if (isExtendedEntity(target.name, this.model)) {
47
+ const current = await SELECT.from(req.query.UPDATE.entity).columns([EXT_BACK_PACK]).where(req.query.UPDATE.where)
48
+
49
+ if (current[0]) {
50
+ req.data[EXT_BACK_PACK] = JSON.stringify(current[0])
51
+ }
52
+ }
53
+
54
+ const args = { processFn: _processorFn, row: req.data, template }
55
+ templateProcessor(args)
56
+ }
57
+ }
58
+
59
+ module.exports = {
60
+ transformExtendedFieldsCREATE,
61
+ transformExtendedFieldsUPDATE
62
+ }
@@ -0,0 +1,35 @@
1
+ module.exports = async () => {
2
+ const cds = require('../../cds')
3
+ if (!cds.requires.db) return
4
+
5
+ const db = await cds.connect.to({ ...cds.requires.db, model: null, silent: true })
6
+ const rs = await db.read('cds_r.Extensions')
7
+ if (rs.length !== 0) {
8
+ const extensions = []
9
+ rs.forEach(row => extensions.push(...JSON.parse(row.csn).extensions))
10
+ cds.once('loaded', csn => {
11
+ if (cds.model) return // extend cds.model only
12
+ const extended = cds.compile({
13
+ 'base.csn': cds.compile.to.json(csn),
14
+ 'ext.csn': cds.compile.to.json({ extensions })
15
+ })
16
+ csn.definitions = extended.definitions
17
+ })
18
+ }
19
+ await db.disconnect()
20
+
21
+ if (cds.db) return // because of tests
22
+ cds.once('served', () => {
23
+ const { transformExtendedFieldsCREATE, transformExtendedFieldsUPDATE } = require('./handler/transformWRITE')
24
+ const { transformExtendedFieldsREAD } = require('./handler/transformREAD')
25
+ const { transformExtendedFieldsRESULT } = require('./handler/transformRESULT')
26
+ cds.db
27
+ .before('CREATE', transformExtendedFieldsCREATE)
28
+ .before('UPDATE', transformExtendedFieldsUPDATE)
29
+ .before('READ', transformExtendedFieldsREAD)
30
+ .after('READ', transformExtendedFieldsRESULT)
31
+ if ('cds_r.ExtensibilityService' in cds.services) return
32
+ const model = require('path').join(__dirname, 'extensibility')
33
+ return cds.serve(model, { silent: true }).to('odata').in(cds.app)
34
+ })
35
+ }
@@ -0,0 +1,78 @@
1
+ const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
2
+ const { ensureUnlocalized } = require('../../fiori/utils/handler')
3
+
4
+ const EXT_BACK_PACK = 'extensions__'
5
+
6
+ const getTargetRead = req => {
7
+ let name = ''
8
+ if (req.query.SELECT.from.join) {
9
+ // join
10
+ name = req.query.SELECT.from.args.find(arg => arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministativeData').ref[0]
11
+ } else if (req.target.name.SET) {
12
+ // union
13
+ name = req.target.name.SET.args[0]._target.name
14
+ } else {
15
+ // simple select
16
+ name = req.target.name
17
+ }
18
+
19
+ return { name: ensureUnlocalized(ensureNoDraftsSuffix(name)) }
20
+ }
21
+
22
+ const getTargetWrite = (target, model) => {
23
+ return model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(target.name))]
24
+ }
25
+
26
+ const isExtendedEntity = (entityName, model) => {
27
+ // REVISIT: Dass alle unsere und auch custom handlers immer die ensureUnlocalized + ensureNoDraftsSuffix schleife drehen müssen, kann nicht sein
28
+ const entity = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))]
29
+ return entity.elements[EXT_BACK_PACK] || Object.values(entity.elements).some(el => el['@cds.extension'])
30
+ }
31
+
32
+ const _hasExtendedEntityArgs = (args, model) => {
33
+ return args.find(arg => {
34
+ if (arg.ref) {
35
+ return arg.ref[0] !== 'DRAFT.DraftAdministativeData' && isExtendedEntity(arg.ref[0], model)
36
+ }
37
+
38
+ if (arg.join) {
39
+ return _hasExtendedEntityArgs(arg.args, model)
40
+ }
41
+ })
42
+ }
43
+
44
+ const hasExtendedEntity = (req, model) => {
45
+ if (!req.query.SELECT) return false
46
+
47
+ if (req.query.SELECT.from.join) {
48
+ // join
49
+ return _hasExtendedEntityArgs(req.query.SELECT.from.args, model)
50
+ } else if (req.target.name.SET) {
51
+ // union
52
+ return isExtendedEntity(req.target.name.SET.args[0]._target.name, model)
53
+ } else {
54
+ // simple select
55
+ return isExtendedEntity(req.target.name, model)
56
+ }
57
+ }
58
+
59
+ const getExtendedFields = (entityName, model) => {
60
+ const elements = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))].elements
61
+
62
+ return Object.values(elements)
63
+ .filter(element => {
64
+ return element['@cds.extension']
65
+ })
66
+ .map(element => {
67
+ return element.name
68
+ })
69
+ }
70
+
71
+ module.exports = {
72
+ EXT_BACK_PACK,
73
+ getTargetRead,
74
+ getTargetWrite,
75
+ isExtendedEntity,
76
+ hasExtendedEntity,
77
+ getExtendedFields
78
+ }
@@ -231,11 +231,12 @@ const addColumnAlias = (columns, alias) => {
231
231
  const getCompositionTargets = (entity, srv) => {
232
232
  if (!entity.own('_deepCompositionTargets')) {
233
233
  const _deepCompositionTargets = []
234
- getTemplate('delete-drafts', srv, entity, {
234
+ getTemplate(undefined, srv, entity, {
235
235
  pick: element => {
236
236
  if (element.isAssociation && !element._isAssociationStrict && srv.model.definitions[element.target].drafts)
237
237
  _deepCompositionTargets.push(element.target)
238
- }
238
+ },
239
+ ignore: element => !element.isAssociation || element._isAssociationStrict
239
240
  })
240
241
  entity.set('_deepCompositionTargets', new Set(_deepCompositionTargets))
241
242
  }
@@ -268,16 +269,6 @@ const getKeyProperty = keys => {
268
269
  })
269
270
  }
270
271
 
271
- const hasKeyInWhere = (where, target) => {
272
- if (!where) {
273
- return false
274
- }
275
-
276
- const key = getKeyProperty(target.keys)
277
-
278
- return where.some(element => (element.ref ? key === element.ref[element.ref.length - 1] : false))
279
- }
280
-
281
272
  const filterKeys = keys => {
282
273
  return Object.keys(keys).filter(key => {
283
274
  return key !== 'IsActiveEntity' && !keys[key]._isAssociationStrict
@@ -301,7 +292,6 @@ module.exports = {
301
292
  adaptStreamCQN,
302
293
  replaceRefWithDraft,
303
294
  getKeyProperty,
304
- hasKeyInWhere,
305
295
  filterKeys,
306
296
  getDeleteDraftAdminCqn,
307
297
  getCompositionTargets
@@ -55,6 +55,9 @@ const _removeIsActiveEntityCondition = where => {
55
55
  i = i + 4
56
56
  } else if (where[i] === 'and' && where[i + 1] === '(' && _isActiveEntity(where[i + 2])) {
57
57
  i = i + 6
58
+ } else if (where[i].xpr) {
59
+ newWhere.push({ xpr: _removeIsActiveEntityCondition(where[i].xpr) })
60
+ i++
58
61
  } else {
59
62
  newWhere.push(where[i])
60
63
  i++
@@ -132,7 +135,7 @@ const readAndDeleteKeywords = (keywords, whereCondition, toDelete = true) => {
132
135
  const removeIsActiveEntityRecursively = where => {
133
136
  for (const entry of where) {
134
137
  if (entry.SELECT && entry.SELECT.where && entry.SELECT.from.ref && !entry.SELECT.from.ref[0].endsWith('_drafts')) {
135
- entry.SELECT.where = _removeIsActiveEntityCondition(entry.SELECT.where)
138
+ entry.SELECT.where = removeIsActiveEntityRecursively(entry.SELECT.where)
136
139
 
137
140
  if (entry.SELECT.where.length === 0) {
138
141
  delete entry.SELECT.where
@@ -144,6 +147,8 @@ const removeIsActiveEntityRecursively = where => {
144
147
  }
145
148
 
146
149
  const isActiveEntityRequested = where => {
150
+ if (!where) return true
151
+
147
152
  let i = 0
148
153
 
149
154
  while (where[i]) {
@@ -130,13 +130,16 @@ class HanaDatabase extends DatabaseService {
130
130
  /*
131
131
  * connection
132
132
  */
133
+ // eslint-disable-next-line complexity
133
134
  async acquire(arg) {
134
- const tenant = (typeof arg === 'string' ? arg : arg.user.tenant) || 'anonymous'
135
+ // REVISIT: remove fallback arg.user.tenant with cds^6
136
+ const tenant = (typeof arg === 'string' ? arg : arg.tenant || (arg.user && arg.user.tenant)) || 'anonymous'
135
137
  const dbc = await pool.acquire(tenant, this.options.credentials)
136
138
 
137
139
  if (typeof arg !== 'string') {
138
140
  _setSessionContext(dbc, 'APPLICATIONUSER', arg.user.id || 'ANONYMOUS')
139
- _setSessionContext(dbc, 'LOCALE', arg.user.locale || 'en')
141
+ // REVISIT: remove fallback arg.user.locale with cds^6
142
+ _setSessionContext(dbc, 'LOCALE', arg.locale || (arg.user && arg.user.locale) || 'en')
140
143
  // REVISIT: stable access
141
144
  const validFrom = (arg.context && arg.context._ && arg.context._['VALID-FROM']) || (arg._ && arg._['VALID-FROM'])
142
145
  const validto = (arg.context && arg.context._ && arg.context._['VALID-TO']) || (arg._ && arg._['VALID-TO'])
@@ -144,7 +144,7 @@ function _processExpand(model, dbc, cqn, user, locale, txTimestamp) {
144
144
  queries.push(_executeSelectSQL(dbc, sql, values, false))
145
145
  }
146
146
 
147
- return rawToExpanded(expandQueries, queries, cqn.SELECT.one, cqn._rootEntity)
147
+ return rawToExpanded(expandQueries, queries, cqn.SELECT.one, cqn._target)
148
148
  }
149
149
 
150
150
  function executeSelectCQN(model, dbc, query, user, locale, txTimestamp) {
@@ -9,19 +9,20 @@ const _require = require('../common/utils/require')
9
9
  let im
10
10
 
11
11
  function multiTenantInstanceManager(db = cds.env.requires.db) {
12
- let creds = db.credentials && (db.credentials.get_managed_instance_url || db.credentials.sm_url) && db.credentials
13
- if (!creds) creds = _require('@sap/xsenv').serviceCredentials(db.vcap || { label: 'managed-hana' })
14
- if (!creds) creds = _require('@sap/xsenv').serviceCredentials({ label: 'service-manager' })
15
-
16
- if (!creds || typeof creds !== 'object' || !(creds.get_managed_instance_url || creds.sm_url)) {
17
- throw Object.assign(new Error('No or malformed Managed HANA credentials'), { credentials: creds })
12
+ const credentials = db.credentials
13
+ if (
14
+ !credentials ||
15
+ typeof credentials !== 'object' ||
16
+ !(credentials.get_managed_instance_url || credentials.sm_url)
17
+ ) {
18
+ throw Object.assign(new Error('No or malformed db credentials'), { credentials: credentials })
18
19
  }
19
20
 
20
21
  // new instance manager
21
22
  return new Promise((resolve, reject) => {
22
23
  // REVISIT: better cache settings? current copied from old cds-hana...
23
24
  // note: may need to be low for mtx tests -> configurable?
24
- const opts = Object.assign(creds, {
25
+ const opts = Object.assign(credentials, {
25
26
  cache_max_items: 1,
26
27
  cache_item_expire_seconds: 1
27
28
  })
@@ -37,16 +38,16 @@ function multiTenantInstanceManager(db = cds.env.requires.db) {
37
38
  }
38
39
 
39
40
  function singleTenantInstanceManager(db = cds.env.requires.db) {
40
- const creds = db.credentials || _require('@sap/xsenv').serviceCredentials(db.vcap || { label: 'hana' })
41
+ const credentials = db.credentials
41
42
 
42
- if (!creds || typeof creds !== 'object' || !creds.host) {
43
- throw Object.assign(new Error('No or malformed HANA credentials'), { credentials: creds })
43
+ if (!credentials || typeof credentials !== 'object' || !credentials.host) {
44
+ throw Object.assign(new Error('No or malformed db credentials'), { credentials: credentials })
44
45
  }
45
46
 
46
47
  // mock instance manager
47
48
  return {
48
49
  get: (_, cb) => {
49
- cb(null, { credentials: creds })
50
+ cb(null, { credentials: credentials })
50
51
  }
51
52
  }
52
53
  }
@@ -1,7 +1,6 @@
1
1
  const { computeColumnsToBeSearched } = require('../cds-services/services/utils/columns')
2
2
  const searchToLike = require('../common/utils/searchToLike')
3
3
  const { isContainsPredicateSupported, searchToContains } = require('./searchToContains')
4
- const { getOnCond } = require('../common/utils/generateOnCond')
5
4
 
6
5
  /**
7
6
  * Computes a CQN expression for a search query.
@@ -15,30 +14,27 @@ const { getOnCond } = require('../common/utils/generateOnCond')
15
14
  * But in contrast to the explicitly written `LIKE ?`, the parameter is already resolved to its concrete value, making
16
15
  * it better optimizable by the HANA optimizer.
17
16
  *
18
- * @param {object} cqn The CQN object
17
+ * @param {object} query The CQN object
19
18
  * @param {import('@sap/cds-compiler/lib/api/main').CSN} entity The target entity for the search query
20
19
  * @param {import('../types/api').search2cqnOptions} [options]
21
20
  * @returns {object} The modified CQN object
22
21
  */
23
- const search2cqn4sql = (cqn, entity, options) => {
24
- const cqnSearchPhrase = cqn.SELECT.search
25
- if (!cqnSearchPhrase) return cqn
22
+ const search2cqn4sql = (query, entity, options) => {
23
+ const cqnSearchPhrase = query.SELECT.search
24
+ if (!cqnSearchPhrase) return query
26
25
 
27
- let { columns = computeColumnsToBeSearched(cqn, entity), locale } = options
26
+ let { columns: columnsToBeSearched = computeColumnsToBeSearched(query, entity), locale } = options
28
27
  const localizedAssociation = _getLocalizedAssociation(entity)
29
28
 
30
29
  // If the localized association is defined for the target entity,
31
30
  // there should be at least one localized element.
32
31
  const resolveLocalizedTextsAtRuntime = !!localizedAssociation
33
32
 
34
- if (resolveLocalizedTextsAtRuntime) {
35
- // suppress the localize handler from modifying the from target
36
- // The `_suppressLocalization` property is:
37
- // enumerable: false (default), writable: false (default)
38
- Object.defineProperty(cqn, '_suppressLocalization', { value: true })
33
+ // suppress the localize handler from redirecting the query's target to the localized view
34
+ Object.defineProperty(query, '_suppressLocalization', { value: true })
39
35
 
40
- const onConditionOptions = _getOnConditionOptions(entity, localizedAssociation)
41
- const onCondition = getOnCond(localizedAssociation, onConditionOptions)
36
+ if (resolveLocalizedTextsAtRuntime) {
37
+ const onCondition = entity._relations[localizedAssociation.name].join(localizedAssociation.target, entity.name)
42
38
 
43
39
  // replace $user_locale placeholder with the user locale or the HANA session context
44
40
  onCondition[onCondition.length - 2] = { val: locale || "SESSION_CONTEXT('LOCALE')" }
@@ -46,31 +42,26 @@ const search2cqn4sql = (cqn, entity, options) => {
46
42
  // inner join the target table with the _texts table (the _texts table contains
47
43
  // the translated texts)
48
44
  const localizedEntityName = localizedAssociation.target
49
- cqn.join(localizedEntityName).on(onCondition)
50
-
51
- // The inner join modifies the original SELECT ... FROM query and adds ambiguity,
52
- // therefore add the table/entity name (as a preceding element) to the columns ref
53
- // to prevent a SQL ambiguity error. E.g., SqlError message: column ambiguously
54
- // defined.
55
- cqn.SELECT.columns = _unshiftEntityNameToColumnRef(entity, cqn.SELECT.columns)
56
- columns = _unshiftEntityNameToColumnRef(entity, columns)
57
- if (cqn.SELECT.groupBy) cqn.SELECT.groupBy = _unshiftEntityNameToColumnRef(entity, cqn.SELECT.groupBy)
45
+ query.join(localizedEntityName).on(onCondition)
46
+
47
+ // prevent SQL ambiguity error for columns with the same name
48
+ columnsToBeSearched = _addAliasToColumns(query, entity, columnsToBeSearched)
58
49
  } // else --> resolve localized texts via localized view (default)
59
50
 
60
- const useContains = resolveLocalizedTextsAtRuntime && isContainsPredicateSupported(cqn)
51
+ const useContains = isContainsPredicateSupported(query)
61
52
  let expression
62
53
 
63
54
  if (useContains) {
64
- expression = searchToContains(cqnSearchPhrase, columns)
55
+ expression = searchToContains(cqnSearchPhrase, columnsToBeSearched)
65
56
  } else {
66
57
  // No CONTAINS optimization possible. The search implementation for localized
67
58
  // texts falls back to the LIKE predicate.
68
- expression = searchToLike(cqnSearchPhrase, columns)
59
+ expression = searchToLike(cqnSearchPhrase, columnsToBeSearched)
69
60
  }
70
61
 
71
62
  // REVISIT: find out here if where or having must be used
72
- cqn._aggregated ? cqn.having(expression) : cqn.where(expression)
73
- return cqn
63
+ query._aggregated ? query.having(expression) : query.where(expression)
64
+ return query
74
65
  }
75
66
 
76
67
  const _getLocalizedAssociation = entity => {
@@ -78,28 +69,28 @@ const _getLocalizedAssociation = entity => {
78
69
  return associations && associations.localized
79
70
  }
80
71
 
81
- const _getOnConditionOptions = (entity, localizedAssociation) => {
82
- return {
83
- associationNames: [localizedAssociation.name],
84
- aliases: {
85
- select: localizedAssociation.target,
86
- join: entity.name
87
- }
88
- }
89
- }
90
-
91
- const _unshiftEntityNameToColumnRef = (entity, columns) => {
72
+ // The inner join modifies the original SELECT ... FROM query and adds ambiguity,
73
+ // therefore add the table/entity name (as a preceding element) to the columns ref
74
+ // to prevent a SQL ambiguity error.
75
+ const _addAliasToColumns = (query, entity, columnsToBeSearched) => {
92
76
  const localizedEntityName = _getLocalizedAssociation(entity).target
93
77
  const elements = entity.elements
94
-
95
- columns = columns.map(column => {
78
+ const entityName = entity.name
79
+ const _addAliasToColumn = (entityName, localizedEntityName, elements) => column => {
96
80
  const columnRef = column.ref
97
81
  if (!columnRef) return column
98
82
  const columnName = columnRef[0]
99
83
  const localizedElement = elements[columnName].localized
100
- const entityName = localizedElement ? localizedEntityName : entity.name
101
- return { ref: [entityName, columnName] }
102
- })
84
+ const targetEntityName = localizedElement ? localizedEntityName : entityName
85
+ return { ref: [targetEntityName, columnName] }
86
+ }
87
+
88
+ query.SELECT.columns = query.SELECT.columns.map(_addAliasToColumn(entityName, localizedEntityName, elements))
89
+ const columns = columnsToBeSearched.map(_addAliasToColumn(entityName, localizedEntityName, elements))
90
+
91
+ if (query.SELECT.groupBy) {
92
+ query.SELECT.groupBy = query.SELECT.groupBy.map(_addAliasToColumn(entityName, localizedEntityName, elements))
93
+ }
103
94
 
104
95
  return columns
105
96
  }
@@ -64,12 +64,12 @@ const searchToContains = (cqnSearchPhrase, columns) => {
64
64
  return expression
65
65
  }
66
66
 
67
- const isContainsPredicateSupported = cqn => {
68
- const cqnSearchPhrase = cqn.SELECT.search
67
+ const isContainsPredicateSupported = query => {
68
+ const cqnSearchPhrase = query.SELECT.search
69
69
 
70
70
  // REVISIT: In the future, to further optimize search queries, you might
71
71
  // want to remove the following condition(s).
72
- if (cqn._aggregated) return false
72
+ if (query._aggregated) return false
73
73
 
74
74
  // REVISIT: search terms starting with whitespace after a `NOT` operator does not
75
75
  // return the expected result on SAP HANA (BCP 2180256508). In addition, double
@@ -5,9 +5,12 @@ module.exports = {
5
5
  return this._odatav4 || (this._odatav4 = require('./cds-services/adapter/odata-v4/to'))
6
6
  },
7
7
 
8
- /** @type {import('./cds-services/adapter/rest/to')} */
9
8
  get rest() {
10
- return this._rest || (this._rest = require('./cds-services/adapter/rest/to'))
9
+ if (!this._rest) {
10
+ if (global.cds.env.features.rest_new_adapter) this._rest = require('../rest')
11
+ else this._rest = require('./cds-services/adapter/rest/to')
12
+ }
13
+ return this._rest
11
14
  }
12
15
  },
13
16
 
@@ -37,7 +37,7 @@ class AMQPWebhookMessaging extends MessagingService {
37
37
  // Some messaging systems don't adhere to the standard that the payload has a `data` property.
38
38
  // For these cases, we interpret the whole payload as `data`.
39
39
  let data, headers
40
- if ('data' in _payload) {
40
+ if (typeof _payload === 'object' && 'data' in _payload) {
41
41
  data = _payload.data
42
42
  headers = { ..._payload }
43
43
  delete headers.data