@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
@@ -0,0 +1,148 @@
1
+ const cds = require('../../../cds')
2
+ const { ensureDraftsSuffix } = require('../../../common/utils/draft')
3
+
4
+ const { EXT_BACK_PACK } = require('../utils')
5
+
6
+ const _getDraftTable = (view, cds) => {
7
+ return cds.model.definitions[view]._isDraftEnabled ? ensureDraftsSuffix(view) : undefined
8
+ }
9
+
10
+ const _addAnnotation = extension => {
11
+ Object.values(extension.elements).forEach(el => {
12
+ el['@cds.extension'] = true
13
+ })
14
+ }
15
+
16
+ const _isProjection = target => target && target.query && target.query._target
17
+
18
+ const _resolveViews = (target, views_ = []) => {
19
+ if (_isProjection(target)) {
20
+ views_.push(target)
21
+ return _resolveViews(target.query._target, views_)
22
+ }
23
+
24
+ return target
25
+ }
26
+
27
+ const _getCsn = req => {
28
+ const csn = {
29
+ extensions: req.data.extensions.map(ext => JSON.parse(ext))
30
+ }
31
+
32
+ return csn
33
+ }
34
+
35
+ const _addViews = csn => {
36
+ csn.extensions.forEach(extension => {
37
+ const target = cds.model.definitions[extension.extend]
38
+ const views_ = []
39
+ const view = _resolveViews(target, views_)
40
+ extension.extend = view && view.name
41
+ _addAnnotation(extension)
42
+
43
+ // All projection views leading to the db entity are extended with back pack in case view columns are explicitly listed.
44
+ // The views using projections with '*' obtain the back pack automatically.
45
+ views_.forEach(view => {
46
+ if (!view.projection || (view.projection.columns && !view.projection.columns.some(col => col === '*'))) {
47
+ csn.extensions.push({
48
+ extend: view.name,
49
+ columns: Object.keys(extension.elements).map(key => {
50
+ return { ref: [key] }
51
+ })
52
+ })
53
+ }
54
+ })
55
+ })
56
+ }
57
+
58
+ const _handleDefaults = async (extension, dbEntity, req, cds, draftEntity) => {
59
+ const ext = Object.keys(extension.elements)
60
+ .filter(key => extension.elements[key].default)
61
+ .map(key => {
62
+ const element = extension.elements[key]
63
+ const t = cds.model.definitions[element.type] || cds.builtin.types[element.type]
64
+ const value = t && t instanceof cds.builtin.classes.string ? `"${element.default.val}"` : element.default.val
65
+ return `"${key}":${value}`
66
+ })
67
+
68
+ if (ext.length !== 0) {
69
+ const extStr = ext.join(',')
70
+ const changed = `'{${extStr},' || substr(${EXT_BACK_PACK}, 2, length(${EXT_BACK_PACK})-1)`
71
+ const assign = `${EXT_BACK_PACK} = CASE WHEN ${EXT_BACK_PACK} IS NULL THEN '{${extStr}}' ELSE ${changed} END`
72
+ await UPDATE(dbEntity).with(assign)
73
+ if (draftEntity) await UPDATE(draftEntity).with(assign)
74
+ }
75
+ }
76
+
77
+ const _validateCsn = (csn, req) => {
78
+ csn.extensions.forEach(extension => {
79
+ if (!extension.extend || !cds.model.definitions[extension.extend]) {
80
+ req.reject(400, 'Invalid extension. Parameter "extend" missing or malformed')
81
+ }
82
+
83
+ if (!extension.elements) {
84
+ req.reject(400, 'Invalid extension. Missing parameter "elements"')
85
+ }
86
+ })
87
+ }
88
+
89
+ const _validateExtensionFields = async (csn, req) => {
90
+ csn.extensions.forEach(extension => {
91
+ if (extension.elements) {
92
+ Object.keys(extension.elements).forEach(name => {
93
+ if (!/^[A-Za-z]\w*$/.test(name)) {
94
+ req.reject(400, `Invalid extension. Bad element name "${name}"`)
95
+ }
96
+
97
+ if (Object.keys(cds.model.definitions[extension.extend].elements).includes(name)) {
98
+ req.reject(400, `Invalid extension. Element "${name}" already exists`)
99
+ }
100
+ })
101
+ }
102
+ })
103
+ }
104
+
105
+ const _getCompilerError = messages => {
106
+ const defaultMsg = 'Error while compiling extension'
107
+ if (!messages) return defaultMsg
108
+
109
+ for (const msg of messages) {
110
+ if (msg.severity === 'Error') return msg.message
111
+ }
112
+
113
+ return defaultMsg
114
+ }
115
+
116
+ const _validateExtension = async (csn, req) => {
117
+ try {
118
+ const base = await cds.load('*', cds.options)
119
+ const baseCsn = await cds.compile.to.json(base)
120
+ const extCsn = await cds.compile.to.json(csn)
121
+ await cds.compile.to.csn({ 'base.csn': baseCsn, 'ext.csn': extCsn })
122
+ } catch (err) {
123
+ req.reject(400, _getCompilerError(err.messages))
124
+ }
125
+ }
126
+
127
+ module.exports = function () {
128
+ this.on('addExtension', async req => {
129
+ const csn = _getCsn(req, cds)
130
+ _validateCsn(csn, req)
131
+ await _validateExtensionFields(csn, req)
132
+ _addViews(csn, cds)
133
+ await _validateExtension(csn, req)
134
+
135
+ const ID = cds.utils.uuid()
136
+ await INSERT.into('cds_r.Extensions').entries([{ ID, csn: JSON.stringify(csn) }])
137
+
138
+ for (const ext of req.data.extensions) {
139
+ const extension = JSON.parse(ext)
140
+ const draft = _getDraftTable(extension.extend, cds)
141
+ const target = cds.model.definitions[extension.extend]
142
+ const dbEntity = _resolveViews(target).name
143
+ await _handleDefaults(extension, dbEntity, req, cds, draft)
144
+ }
145
+
146
+ setTimeout(() => process.send('restart'), 1111)
147
+ })
148
+ }
@@ -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]) {
@@ -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
  }