@sap/cds 5.4.6 → 5.5.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 (228) hide show
  1. package/CHANGELOG.md +208 -2
  2. package/apis/ql.d.ts +17 -15
  3. package/app/index.js +1 -1
  4. package/bin/build/buildTaskEngine.js +26 -42
  5. package/bin/build/buildTaskFactory.js +6 -10
  6. package/bin/build/buildTaskHandler.js +2 -4
  7. package/bin/build/buildTaskProvider.js +3 -1
  8. package/bin/build/buildTaskProviderFactory.js +9 -15
  9. package/bin/build/constants.js +15 -3
  10. package/bin/build/index.js +5 -4
  11. package/bin/build/mtaUtil.js +8 -11
  12. package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
  13. package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
  14. package/bin/build/provider/buildTaskProviderInternal.js +16 -42
  15. package/bin/build/provider/fiori/index.js +13 -24
  16. package/bin/build/provider/hana/2migration.js +17 -15
  17. package/bin/build/provider/hana/2tabledata.js +52 -48
  18. package/bin/build/provider/hana/index.js +27 -25
  19. package/bin/build/provider/hana/migrationtable.js +91 -67
  20. package/bin/build/provider/java-cf/index.js +14 -24
  21. package/bin/build/provider/mtx/index.js +12 -14
  22. package/bin/build/provider/node-cf/index.js +18 -32
  23. package/bin/cds.js +5 -5
  24. package/bin/serve.js +29 -23
  25. package/bin/version.js +0 -1
  26. package/lib/compile/etc/_localized.js +4 -9
  27. package/lib/compile/for/sql.js +5 -2
  28. package/lib/compile/parse.js +25 -17
  29. package/lib/compile/to/srvinfo.js +2 -1
  30. package/lib/connect/bindings.js +2 -1
  31. package/lib/connect/index.js +48 -49
  32. package/lib/core/classes.js +1 -1
  33. package/lib/core/reflect.js +10 -2
  34. package/lib/deploy.js +26 -23
  35. package/lib/env/defaults.js +13 -6
  36. package/lib/env/index.js +73 -78
  37. package/lib/env/requires.js +38 -19
  38. package/lib/index.js +9 -10
  39. package/lib/lazy.js +2 -2
  40. package/lib/log/index.js +33 -45
  41. package/lib/log/service/index.js +2 -2
  42. package/lib/ql/CREATE.js +14 -9
  43. package/lib/ql/DELETE.js +6 -5
  44. package/lib/ql/DROP.js +12 -9
  45. package/lib/ql/INSERT.js +40 -16
  46. package/lib/ql/Query.js +67 -40
  47. package/lib/ql/SELECT.js +162 -127
  48. package/lib/ql/UPDATE.js +74 -42
  49. package/lib/ql/Whereable.js +77 -87
  50. package/lib/ql/index.js +36 -24
  51. package/lib/ql/parse.js +35 -0
  52. package/lib/req/context.js +44 -8
  53. package/lib/req/locale.js +7 -7
  54. package/lib/serve/Service-api.js +21 -14
  55. package/lib/serve/Service-dispatch.js +28 -12
  56. package/lib/serve/Transaction.js +22 -10
  57. package/lib/serve/index.js +16 -11
  58. package/lib/utils/axios.js +23 -16
  59. package/lib/utils/data.js +35 -0
  60. package/lib/utils/tests.js +27 -18
  61. package/libx/_runtime/audit/generic/personal/access.js +81 -0
  62. package/libx/_runtime/audit/generic/personal/constants.js +4 -0
  63. package/libx/_runtime/audit/generic/personal/index.js +50 -0
  64. package/libx/_runtime/audit/generic/personal/modification.js +138 -0
  65. package/libx/_runtime/audit/generic/personal/utils.js +186 -0
  66. package/libx/_runtime/audit/utils/v2.js +10 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
  68. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
  74. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
  75. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
  76. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
  77. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
  78. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
  79. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
  80. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
  81. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  82. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
  84. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
  85. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
  86. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
  87. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
  88. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
  89. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
  90. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
  91. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  92. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
  93. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
  99. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
  101. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
  102. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
  103. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
  104. package/libx/_runtime/cds-services/services/Service.js +40 -5
  105. package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
  106. package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
  107. package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
  108. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
  109. package/libx/_runtime/common/composition/data.js +44 -55
  110. package/libx/_runtime/common/composition/delete.js +97 -71
  111. package/libx/_runtime/common/composition/index.js +2 -1
  112. package/libx/_runtime/common/composition/insert.js +34 -11
  113. package/libx/_runtime/common/composition/tree.js +119 -92
  114. package/libx/_runtime/common/composition/update.js +4 -1
  115. package/libx/_runtime/common/composition/utils.js +1 -3
  116. package/libx/_runtime/common/constants/draft.js +12 -1
  117. package/libx/_runtime/common/generic/auth.js +6 -22
  118. package/libx/_runtime/common/generic/crud.js +14 -13
  119. package/libx/_runtime/common/generic/input.js +23 -26
  120. package/libx/_runtime/common/generic/put.js +1 -1
  121. package/libx/_runtime/common/generic/sorting.js +16 -16
  122. package/libx/_runtime/common/i18n/index.js +1 -1
  123. package/libx/_runtime/common/i18n/messages.properties +4 -0
  124. package/libx/_runtime/common/utils/backlinks.js +12 -5
  125. package/libx/_runtime/common/utils/cqn.js +6 -1
  126. package/libx/_runtime/common/utils/cqn2cqn4sql.js +102 -101
  127. package/libx/_runtime/common/utils/csn.js +47 -4
  128. package/libx/_runtime/common/utils/data.js +0 -37
  129. package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
  130. package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
  131. package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
  132. package/libx/_runtime/common/utils/generateOnCond.js +11 -12
  133. package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
  134. package/libx/_runtime/common/utils/path.js +35 -0
  135. package/libx/_runtime/common/utils/postProcessing.js +86 -0
  136. package/libx/_runtime/common/utils/quotingStyles.js +37 -26
  137. package/libx/_runtime/common/utils/resolveView.js +223 -171
  138. package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
  139. package/libx/_runtime/common/utils/structured.js +6 -12
  140. package/libx/_runtime/common/utils/template.js +10 -5
  141. package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
  142. package/libx/_runtime/common/utils/templateProcessor.js +22 -30
  143. package/libx/_runtime/common/utils/union.js +31 -0
  144. package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
  145. package/libx/_runtime/db/Service.js +1 -1
  146. package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
  147. package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
  148. package/libx/_runtime/db/expand/index.js +3 -3
  149. package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
  150. package/libx/_runtime/db/generic/index.js +1 -1
  151. package/libx/_runtime/db/generic/input.js +5 -7
  152. package/libx/_runtime/db/generic/integrity.js +1 -1
  153. package/libx/_runtime/db/generic/rewrite.js +2 -10
  154. package/libx/_runtime/db/generic/update.js +13 -5
  155. package/libx/_runtime/db/generic/virtual.js +22 -58
  156. package/libx/_runtime/db/query/delete.js +7 -4
  157. package/libx/_runtime/db/query/insert.js +6 -4
  158. package/libx/_runtime/db/query/read.js +13 -20
  159. package/libx/_runtime/db/query/run.js +4 -1
  160. package/libx/_runtime/db/query/update.js +5 -4
  161. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
  162. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
  163. package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
  164. package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
  165. package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
  166. package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
  167. package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
  168. package/libx/_runtime/db/utils/deep.js +8 -0
  169. package/libx/_runtime/db/utils/generateAliases.js +2 -1
  170. package/libx/_runtime/fiori/generic/activate.js +19 -15
  171. package/libx/_runtime/fiori/generic/before.js +3 -11
  172. package/libx/_runtime/fiori/generic/cancel.js +1 -1
  173. package/libx/_runtime/fiori/generic/delete.js +3 -1
  174. package/libx/_runtime/fiori/generic/edit.js +12 -2
  175. package/libx/_runtime/fiori/generic/new.js +5 -5
  176. package/libx/_runtime/fiori/generic/patch.js +0 -18
  177. package/libx/_runtime/fiori/generic/read.js +241 -189
  178. package/libx/_runtime/fiori/utils/delete.js +36 -7
  179. package/libx/_runtime/fiori/utils/handler.js +43 -44
  180. package/libx/_runtime/fiori/utils/where.js +30 -15
  181. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
  182. package/libx/_runtime/hana/execute.js +2 -2
  183. package/libx/_runtime/hana/localized.js +4 -4
  184. package/libx/_runtime/hana/pool.js +29 -14
  185. package/libx/_runtime/hana/search2cqn4sql.js +2 -1
  186. package/libx/_runtime/hana/searchToContains.js +18 -14
  187. package/libx/_runtime/index.js +0 -5
  188. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
  189. package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
  190. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
  191. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
  192. package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
  193. package/libx/_runtime/messaging/service.js +7 -6
  194. package/libx/_runtime/odata/cqn2odata.js +110 -43
  195. package/libx/_runtime/odata/index.js +26 -48
  196. package/libx/_runtime/odata/odata2cqn.js +1 -6154
  197. package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
  198. package/libx/_runtime/odata/readToCqn.js +94 -64
  199. package/libx/_runtime/remote/Service.js +74 -21
  200. package/libx/_runtime/remote/cqn2odata/index.js +1 -5
  201. package/libx/_runtime/remote/utils/client.js +24 -101
  202. package/libx/_runtime/remote/utils/dataConversion.js +27 -12
  203. package/libx/_runtime/sqlite/Service.js +3 -5
  204. package/libx/_runtime/sqlite/execute.js +23 -24
  205. package/libx/_runtime/sqlite/localized.js +12 -7
  206. package/libx/_runtime/types/api.js +10 -0
  207. package/package.json +1 -1
  208. package/server.js +16 -2
  209. package/lib/ql/grammar.pegjs +0 -208
  210. package/lib/ql/parser.js +0 -1
  211. package/lib/ql/rt/DELETE.js +0 -29
  212. package/lib/ql/rt/INSERT.js +0 -23
  213. package/lib/ql/rt/Query.js +0 -84
  214. package/lib/ql/rt/SELECT.js +0 -174
  215. package/lib/ql/rt/UPDATE.js +0 -119
  216. package/lib/ql/rt/_helpers.js +0 -91
  217. package/lib/ql/rt/index.js +0 -32
  218. package/libx/_runtime/audit/generic/personal.js +0 -260
  219. package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
  220. package/libx/_runtime/cds-services/statements/Create.js +0 -57
  221. package/libx/_runtime/cds-services/statements/Delete.js +0 -33
  222. package/libx/_runtime/cds-services/statements/Drop.js +0 -42
  223. package/libx/_runtime/cds-services/statements/Insert.js +0 -201
  224. package/libx/_runtime/cds-services/statements/Select.js +0 -826
  225. package/libx/_runtime/cds-services/statements/Update.js +0 -181
  226. package/libx/_runtime/cds-services/statements/Where.js +0 -726
  227. package/libx/_runtime/cds-services/statements/index.js +0 -25
  228. package/libx/_runtime/common/generic/resolve-mock.js +0 -9
@@ -2,6 +2,11 @@ const cds = require('../../cds')
2
2
 
3
3
  const Differ = require('./utils/differ')
4
4
 
5
+ const { resolveView, restoreLink, findQueryTarget } = require('../../common/utils/resolveView')
6
+ const { postProcess } = require('../../common/utils/postProcessing')
7
+
8
+ const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
9
+
5
10
  /**
6
11
  * Generic Service Event Handler.
7
12
  */
@@ -10,8 +15,7 @@ class ApplicationService extends cds.Service {
10
15
  // REVISIT: do we still need that -> likely due to legacy test?
11
16
  // If not we should remove this legacy constructor
12
17
  if (typeof name === 'object') [name, csn, options] = [csn.service, name, csn]
13
- const o = { kind: options.use, ...options, service: name }
14
- super(name, csn, o)
18
+ super(name, csn, options)
15
19
 
16
20
  // REVISIT: umbrella calls srv._calculateDiff
17
21
  this._differ = new Differ(this)
@@ -20,8 +24,7 @@ class ApplicationService extends cds.Service {
20
24
 
21
25
  set model(csn) {
22
26
  const m = csn && 'definitions' in csn ? cds.linked(cds.compile.for.odata(csn)) : csn
23
- // with compiler v2 we always need to localized the csn
24
- cds.alpha_localized(m)
27
+ cds.alpha_localized(m) // with compiler v2 we always need to localized the csn
25
28
  super.model = m
26
29
  }
27
30
 
@@ -36,7 +39,6 @@ class ApplicationService extends cds.Service {
36
39
  require('../../common/generic/temporal').call(this, this)
37
40
  require('../../common/generic/paging').call(this, this) // > paging must be executed before sorting
38
41
  require('../../common/generic/sorting').call(this, this)
39
- require('../../common/generic/resolve-mock').call(this, this)
40
42
 
41
43
  // draft handlers needed?
42
44
  // REVISIT: serve 2 fiori
@@ -111,6 +113,39 @@ class ApplicationService extends cds.Service {
111
113
  })
112
114
  }
113
115
  }
116
+
117
+ // Overload .handle in order to resolve projections up to a definition that is known by the remote service instance.
118
+ // Result is post processed according to the inverse projection in order to reflect the correct result of the original query.
119
+ async handle(req) {
120
+ // compat mode
121
+ if (req._resolved || cds.env.features.resolve_views === false) return super.handle(req)
122
+
123
+ if (req.target && req.target.name && this.definition && req.target.name.startsWith(this.definition.name + '.')) {
124
+ return super.handle(req)
125
+ }
126
+
127
+ // req.query can be:
128
+ // - empty object in case of unbound action/function
129
+ // - undefined/null in case of plain string queries
130
+ if (_isSimpleCqnQuery(req.query) && this.model) {
131
+ const q = resolveView(req.query, this.model, this)
132
+ const t = findQueryTarget(q) || req.target
133
+
134
+ // compat
135
+ restoreLink(req)
136
+ if (req.query.SELECT && req.query.SELECT._4odata) {
137
+ q.SELECT._4odata = req.query.SELECT._4odata
138
+ }
139
+
140
+ // REVISIT: We need to provide target explicitly because it's cached already within ensure_target
141
+ const newReq = new cds.Request({ query: q, target: t, _resolved: true })
142
+ const result = await super.dispatch(newReq)
143
+
144
+ return postProcess(q, result)
145
+ }
146
+
147
+ return super.handle(req)
148
+ }
114
149
  }
115
150
 
116
151
  module.exports = ApplicationService
@@ -99,10 +99,14 @@ const getSearchableColumns = entity => {
99
99
  const defaultSearchFilteredColumns = searchableColumns.filter(column => column[defaultSearchElementTerm])
100
100
 
101
101
  if (defaultSearchFilteredColumns.length > 0) {
102
- LOG._warn &&
103
- LOG.warn(
104
- 'Annotation "@Search.defaultSearchElement" is deprecated and will be removed in an upcoming release. Use "@cds.search" instead.'
105
- )
102
+ if (!cds._deprecationWarningForDefaultSearchElement) {
103
+ LOG._warn &&
104
+ LOG.warn(
105
+ 'Annotation "@Search.defaultSearchElement" is deprecated and will be removed in an upcoming release. Use "@cds.search" instead.'
106
+ )
107
+ cds._deprecationWarningForDefaultSearchElement = true
108
+ }
109
+
106
110
  return defaultSearchFilteredColumns.map(column => column.name)
107
111
  }
108
112
 
@@ -129,9 +133,11 @@ const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }) =>
129
133
  }
130
134
 
131
135
  const columnRef = column.ref
132
- const columnName = columnRef[columnRef.length - 1]
133
- const csnColumn = entity.elements[columnName]
134
- if (!csnColumn) toBeSearched.push({ ref: [columnName] })
136
+ if (columnRef) {
137
+ const columnName = columnRef[columnRef.length - 1]
138
+ const csnColumn = entity.elements[columnName]
139
+ if (!csnColumn) toBeSearched.push({ ref: [columnName] })
140
+ }
135
141
  })
136
142
 
137
143
  return toBeSearched
@@ -1,7 +1,22 @@
1
1
  const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
2
2
 
3
- const _getCorrespondingEntryWithSameKeys = (source, entry, keys) =>
4
- source.find(sourceEntry => keys.every(key => sourceEntry[key] === entry[key]))
3
+ const _deepEqual = (val1, val2) => {
4
+ if (val1 && typeof val1 === 'object' && val2 && typeof val2 === 'object') {
5
+ for (const key in val1) {
6
+ if (!_deepEqual(val1[key], val2[key])) return false
7
+ }
8
+ return true
9
+ }
10
+ return val1 === val2
11
+ }
12
+
13
+ const _getCorrespondingEntryWithSameKeys = (source, entry, keys) => {
14
+ const idx = _getIdxCorrespondingEntryWithSameKeys(source, entry, keys)
15
+ return idx !== -1 ? source[idx] : undefined
16
+ }
17
+
18
+ const _getIdxCorrespondingEntryWithSameKeys = (source, entry, keys) =>
19
+ source.findIndex(sourceEntry => keys.every(key => _deepEqual(sourceEntry[key], entry[key])))
5
20
 
6
21
  const _getKeysOfEntity = entity =>
7
22
  Object.keys(entity.keys).filter(key => !DRAFT_COLUMNS.includes(key) && !entity.elements[key].isAssociation)
@@ -63,7 +78,21 @@ const _hasOpDeep = (entry, element) => {
63
78
  }
64
79
 
65
80
  const _addCompositionsToResult = (result, entity, prop, newValue, oldValue) => {
66
- const composition = compareJsonDeep(entity.elements[prop]._target, newValue[prop], oldValue && oldValue[prop])
81
+ /*
82
+ * REVISIT: the current impl results in {} instead of keeping null for compo to one.
83
+ * unfortunately, many follow-up errors occur (e.g., prop in null checks) if changed.
84
+ */
85
+ let composition
86
+ if (
87
+ newValue[prop] &&
88
+ typeof newValue[prop] === 'object' &&
89
+ !Array.isArray(newValue[prop]) &&
90
+ Object.keys(newValue[prop]).length === 0
91
+ ) {
92
+ composition = compareJsonDeep(entity.elements[prop]._target, undefined, oldValue && oldValue[prop])
93
+ } else {
94
+ composition = compareJsonDeep(entity.elements[prop]._target, newValue[prop], oldValue && oldValue[prop])
95
+ }
67
96
  if (composition.some(c => _hasOpDeep(c, entity.elements[prop]))) {
68
97
  result[prop] = entity.elements[prop].is2one ? composition[0] : composition
69
98
  }
@@ -246,4 +275,59 @@ const compareJson = (newValue, oldValue, entity) => {
246
275
  return Array.isArray(newValue) ? result : result[0]
247
276
  }
248
277
 
249
- module.exports = compareJson
278
+ const _isObject = item => item && typeof item === 'object' && !Array.isArray(item)
279
+
280
+ const _mergeArrays = (entity, oldValue, newValue) => {
281
+ const merged = []
282
+ const foundIdxNew = []
283
+ const keys = _getKeysOfEntity(entity)
284
+ for (const entry of oldValue) {
285
+ const idxNew = _getIdxCorrespondingEntryWithSameKeys(newValue, entry, keys)
286
+ if (idxNew === -1) merged.push(entry)
287
+ else {
288
+ foundIdxNew.push(idxNew)
289
+ merged.push(mergeJsonDeep(entity, entry, newValue[idxNew]))
290
+ }
291
+ }
292
+ for (let i = 0; i < newValue.length; i++) {
293
+ if (!foundIdxNew.includes(i)) merged.push(newValue[i])
294
+ }
295
+ return merged
296
+ }
297
+
298
+ const mergeJsonDeep = (entity, oldValue, newValue) => {
299
+ if (_isObject(oldValue) && _isObject(newValue)) {
300
+ Object.keys(newValue).forEach(key => {
301
+ if (_isObject(newValue[key])) {
302
+ if (!(key in oldValue)) Object.assign(oldValue, { [key]: newValue[key] })
303
+ else {
304
+ const target = entity && entity.elements[key] && entity.elements[key]._target
305
+ oldValue[key] = mergeJsonDeep(target, oldValue[key], newValue[key])
306
+ }
307
+ } else if (Array.isArray(newValue[key])) {
308
+ if (!(key in oldValue)) Object.assign(oldValue, { [key]: newValue[key] })
309
+ else {
310
+ const target = entity && entity.elements[key] && entity.elements[key]._target
311
+ if (target) {
312
+ oldValue[key] = _mergeArrays(target, oldValue[key], newValue[key])
313
+ }
314
+ // Can't merge items without target
315
+ }
316
+ } else {
317
+ Object.assign(oldValue, { [key]: newValue[key] })
318
+ }
319
+ })
320
+ }
321
+ return oldValue
322
+ }
323
+
324
+ // Signature similar to Object.assign(oldValue, newValue)
325
+ const mergeJson = (oldValue, newValue, entity) => {
326
+ const result = mergeJsonDeep(entity, oldValue, newValue)
327
+ return result
328
+ }
329
+
330
+ module.exports = {
331
+ compareJson,
332
+ mergeJson
333
+ }
@@ -2,7 +2,7 @@ const cds = require('../../../cds')
2
2
  const LOG = cds.log('app')
3
3
  const { SELECT } = cds.ql
4
4
 
5
- const compareJson = require('./compareJson')
5
+ const { compareJson } = require('./compareJson')
6
6
  const { selectDeepUpdateData } = require('../../../common/composition')
7
7
  const { ensureDraftsSuffix } = require('../../../fiori/utils/handler')
8
8
 
@@ -43,9 +43,20 @@ module.exports = class {
43
43
  }
44
44
 
45
45
  _diffDelete(req) {
46
- const query = SELECT.from(req.target)
47
- .columns(this._createSelectColumnsForDelete(req.target))
48
- .where(this._createWhereCondition(req.target, req.data))
46
+ const { DELETE } = (req._ && req._.query) || req.query
47
+ const query = SELECT.from(DELETE.from).columns(this._createSelectColumnsForDelete(req.target))
48
+ if (DELETE.where) query.where(...DELETE.where)
49
+
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
+
49
60
  return cds
50
61
  .tx(req)
51
62
  .run(query)
@@ -53,7 +64,14 @@ module.exports = class {
53
64
  }
54
65
 
55
66
  async _addPartialPersistentState(req) {
56
- const deepUpdateData = await selectDeepUpdateData(this._srv.model.definitions, req.query, req, true)
67
+ const deepUpdateData = await selectDeepUpdateData(
68
+ this._srv.model.definitions,
69
+ req.query,
70
+ req,
71
+ true,
72
+ true,
73
+ this._srv
74
+ )
57
75
  req._.partialPersistentState = deepUpdateData
58
76
  }
59
77
 
@@ -68,7 +86,7 @@ module.exports = class {
68
86
  const newQuery = cqn2cqn4sql(req.query, this._srv.model)
69
87
  const combinedData = providedData || Object.assign({}, req.query.UPDATE.data || {}, req.query.UPDATE.with || {})
70
88
  const lastTransition = newQuery.UPDATE._transitions[newQuery.UPDATE._transitions.length - 1]
71
- const revertedPersistent = revertData(req._.partialPersistentState, lastTransition)
89
+ const revertedPersistent = revertData(req._.partialPersistentState, lastTransition, this._srv)
72
90
  return compareJson(combinedData, revertedPersistent, req.target)
73
91
  }
74
92
 
@@ -20,7 +20,7 @@ const _getWheres = (key, data) => {
20
20
 
21
21
  const allKeysAreProvided = req => {
22
22
  const data = req.data && (Array.isArray(req.data) ? req.data : [req.data])
23
- for (const key of Object.values(req.target.keys)) {
23
+ for (const key of Object.values(req.target.keys || {})) {
24
24
  if (key._isAssociationStrict || DRAFT_COLUMNS.includes(key.name)) {
25
25
  continue
26
26
  }
@@ -55,7 +55,7 @@ const _getSelectCQN = (req, columns) => {
55
55
 
56
56
  const data = req.data && (Array.isArray(req.data) ? req.data : [req.data])
57
57
 
58
- for (const key of Object.values(req.target.keys)) {
58
+ for (const key of Object.values(req.target.keys || {})) {
59
59
  if (key._isAssociationStrict || DRAFT_COLUMNS.includes(key.name)) {
60
60
  continue
61
61
  }
@@ -1,5 +1,6 @@
1
1
  const { getCompositionTree } = require('./tree')
2
2
  const ctUtils = require('./utils')
3
+ const { getEntityNameFromUpdateCQN } = require('../utils/cqn')
3
4
 
4
5
  const { ensureNoDraftsSuffix } = require('../utils/draft')
5
6
  const { getDBTable } = require('../utils/resolveView')
@@ -38,32 +39,23 @@ const _isSameEntity = (cqn, req) => {
38
39
  return true
39
40
  }
40
41
 
41
- const _from = query =>
42
- (query.UPDATE.entity.ref && query.UPDATE.entity.ref[0]) || query.UPDATE.entity.name || query.UPDATE.entity
43
-
44
42
  const _getLinksOfCompTree = compositionTree => {
45
43
  const links = []
44
+ for (const link of [...compositionTree.backLinks, ...compositionTree.customBackLinks]) {
45
+ links.push(link.entityKey)
46
+ }
46
47
  for (const compElement of compositionTree.compositionElements || []) {
47
- for (const link of compElement.links || []) {
48
- links.push(link.entityKey)
48
+ for (const link of [...compElement.backLinks, ...compElement.customBackLinks]) {
49
+ links.push(link.targetKey)
49
50
  }
50
51
  }
51
52
  return links
52
53
  }
53
54
 
54
- const _dataElements = entity => {
55
- // REVISIT: this is expensive
56
- return Object.keys(entity.elements)
57
- .map(key => entity.elements[key])
58
- .filter(e => !e.virtual && !e.isAssociation)
59
- }
60
-
61
55
  const _whereKeys = keys => {
62
56
  const where = []
63
57
  keys.forEach(key => {
64
- if (where.length > 0) {
65
- where.push('or')
66
- }
58
+ if (where.length) where.push('or')
67
59
  where.push('(', ...ctUtils.whereKey(key), ')')
68
60
  })
69
61
  return where
@@ -108,9 +100,7 @@ const _keys = (entity, data) => {
108
100
  }
109
101
 
110
102
  const _parentKeys = (element, keys) => {
111
- return keys.map(key => {
112
- return _parentKey(element, key)
113
- })
103
+ return keys.map(key => _parentKey(element, key)).filter(ele => Object.keys(ele).length)
114
104
  }
115
105
 
116
106
  const _subData = (data, prop) =>
@@ -123,8 +113,9 @@ const _subData = (data, prop) =>
123
113
  return result
124
114
  }, [])
125
115
 
126
- const _subWhere = (result, links) => {
116
+ const _subWhere = (result, element) => {
127
117
  let where
118
+ const links = [...element.backLinks, ...element.customBackLinks]
128
119
  if (links && links.length > 0) {
129
120
  where = []
130
121
  for (const row of result) {
@@ -132,10 +123,12 @@ const _subWhere = (result, links) => {
132
123
  where.push('or')
133
124
  }
134
125
  const whereObj = links.reduce((res, currentLink) => {
135
- res[currentLink.targetKey] = row[currentLink.entityKey]
126
+ if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey))
127
+ res[currentLink.entityKey] = row[currentLink.targetKey]
136
128
  return res
137
129
  }, {})
138
- where.push('(', ...ctUtils.whereKey(whereObj), ')')
130
+ const whereCQN = ctUtils.whereKey(whereObj)
131
+ if (whereCQN.length) where.push('(', ...whereCQN, ')')
139
132
  }
140
133
  }
141
134
  return where
@@ -153,17 +146,7 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
153
146
  } else if (assoc.is2many) {
154
147
  selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || []
155
148
  }
156
- let pk = _parentKey(compositionTree, selectEntry)
157
- // adjust pk for nested composition of one
158
- if (
159
- (!pk || Object.keys(pk).length === 0) &&
160
- assoc.isComposition &&
161
- assoc.is2one &&
162
- compositionTree.links.length === 1
163
- ) {
164
- pk = { [compositionTree.links[0].targetKey]: selectEntry[compositionTree.links[0].entityKey] }
165
- }
166
- const newData = _findWhere(result, pk)
149
+ const newData = _findWhere(result, _parentKey(compositionTree, selectEntry))
167
150
  if (assoc.is2one && newData[0]) {
168
151
  selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
169
152
  } else if (assoc.is2many) {
@@ -175,23 +158,20 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
175
158
  }
176
159
 
177
160
  const _columns = (entity, data, compositionTree, selectAll) => {
178
- const links = _getLinksOfCompTree(compositionTree)
179
- const backLinkKeys = [
180
- ...compositionTree.backLinks.map(backLink => backLink.entityKey),
181
- ...compositionTree.customBackLinks.map(customBackLink => customBackLink.entityKey)
182
- ]
183
- const columns = _dataElements(entity)
184
- .filter(
185
- element =>
186
- selectAll ||
187
- element.key ||
188
- links.includes(element.name) ||
189
- backLinkKeys.includes(element.name) ||
190
- (Array.isArray(data) && data.find(entry => element.name in entry))
191
- )
192
- .map(element => {
193
- return { ref: [element.name] }
194
- })
161
+ const backLinkKeys = _getLinksOfCompTree(compositionTree)
162
+ const columns = []
163
+ for (const elementName in entity.elements) {
164
+ const element = entity.elements[elementName]
165
+ if (element.virtual || element.isAssociation) continue
166
+ if (
167
+ selectAll ||
168
+ element.key ||
169
+ backLinkKeys.includes(element.name) ||
170
+ (Array.isArray(data) && data.find(entry => element.name in entry))
171
+ ) {
172
+ columns.push({ ref: [element.name] })
173
+ }
174
+ }
195
175
  return columns
196
176
  }
197
177
 
@@ -240,7 +220,7 @@ const _selectDeepUpdateData = async args => {
240
220
  compositionTree: element,
241
221
  entityName: element.source,
242
222
  data: _subData(data, element.name),
243
- where: _subWhere(result, element.links),
223
+ where: _subWhere(result, element),
244
224
  selectData: result,
245
225
  parentKeys: _parentKeys(element, keys),
246
226
  orderBy: false,
@@ -261,7 +241,14 @@ const _selectDeepUpdateData = async args => {
261
241
  * exports
262
242
  */
263
243
 
264
- const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = false) => {
244
+ const selectDeepUpdateData = (
245
+ definitions,
246
+ cqn,
247
+ req,
248
+ includeAllRootColumns = false,
249
+ includeAllColumns = false,
250
+ service
251
+ ) => {
265
252
  // REVISIT this should be done somewhere before, so it is not done twice for deep updates
266
253
  const sqlQuery = cqn2cqn4sql(cqn, { definitions })
267
254
 
@@ -269,7 +256,7 @@ const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = fal
269
256
  return Promise.resolve(req._.partialPersistentState)
270
257
  }
271
258
 
272
- const from = _from(sqlQuery)
259
+ const from = getEntityNameFromUpdateCQN(sqlQuery)
273
260
  const alias = sqlQuery.UPDATE.entity.as
274
261
  const where = sqlQuery.UPDATE.where || []
275
262
  const entityName = ensureNoDraftsSuffix(from)
@@ -280,7 +267,8 @@ const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = fal
280
267
  definitions,
281
268
  rootEntityName: entityName, // REVISIT: drafts are resolved too eagerly
282
269
  checkRoot: false,
283
- resolveViews: !draft
270
+ resolveViews: !draft,
271
+ service
284
272
  })
285
273
 
286
274
  return _selectDeepUpdateData({
@@ -295,8 +283,9 @@ const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = fal
295
283
  includeAllRootColumns,
296
284
  singleton: req && req.target && req.target._isSingleton,
297
285
  alias,
298
- includeAllColumns: cqn._selectAll,
299
- root: true
286
+ includeAllColumns: cqn._selectAll || includeAllColumns,
287
+ root: true,
288
+ service
300
289
  })
301
290
  }
302
291