@sap/cds 5.4.3 → 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 +239 -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 +66 -63
  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 +12 -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 +53 -31
  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 +123 -108
  127. package/libx/_runtime/common/utils/csn.js +56 -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 +227 -173
  138. package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
  139. package/libx/_runtime/common/utils/structured.js +13 -13
  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 +28 -72
  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 +21 -8
  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 +261 -205
  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 +3 -3
  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 +33 -27
  205. package/libx/_runtime/sqlite/localized.js +12 -7
  206. package/libx/_runtime/types/api.js +10 -0
  207. package/package.json +2 -2
  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,49 +39,50 @@ 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
70
62
  }
71
63
 
72
64
  const _parentKey = (element, key) => {
73
- const parentKey = {}
65
+ return [...element.customBackLinks, ...element.backLinks].reduce((parentKey, customBackLink) => {
66
+ // TODO: why Object.prototype.hasOwnProperty?
67
+ parentKey[customBackLink.entityKey] = Object.prototype.hasOwnProperty.call(key, customBackLink.targetKey)
68
+ ? key[customBackLink.targetKey]
69
+ : customBackLink.targetVal
74
70
 
75
- element.customBackLinks.reduce((parentKey, customBackLink) => {
76
- parentKey[customBackLink.entityKey] = key[customBackLink.targetKey] || customBackLink.targetVal
77
- return parentKey
78
- }, parentKey)
71
+ // nested
72
+ if (!parentKey[customBackLink.entityKey]) {
73
+ const splitted = customBackLink.targetKey.split('_')
74
+ let current
75
+ let joined = ''
76
+ while (splitted.length > 1) {
77
+ if (joined) joined += '_'
78
+ joined += splitted.shift()
79
+ if (Object.prototype.hasOwnProperty.call(key, joined)) current = key[joined]
80
+ }
81
+ if (current) parentKey[customBackLink.entityKey] = current[splitted[0]]
82
+ }
79
83
 
80
- return element.backLinks.reduce((parentKey, backlink) => {
81
- parentKey[backlink.entityKey] = key[backlink.targetKey] || backlink.targetVal
82
84
  return parentKey
83
- }, parentKey)
85
+ }, {})
84
86
  }
85
87
 
86
88
  const _findWhere = (data, where) => {
@@ -98,9 +100,7 @@ const _keys = (entity, data) => {
98
100
  }
99
101
 
100
102
  const _parentKeys = (element, keys) => {
101
- return keys.map(key => {
102
- return _parentKey(element, key)
103
- })
103
+ return keys.map(key => _parentKey(element, key)).filter(ele => Object.keys(ele).length)
104
104
  }
105
105
 
106
106
  const _subData = (data, prop) =>
@@ -113,8 +113,9 @@ const _subData = (data, prop) =>
113
113
  return result
114
114
  }, [])
115
115
 
116
- const _subWhere = (result, links) => {
116
+ const _subWhere = (result, element) => {
117
117
  let where
118
+ const links = [...element.backLinks, ...element.customBackLinks]
118
119
  if (links && links.length > 0) {
119
120
  where = []
120
121
  for (const row of result) {
@@ -122,10 +123,12 @@ const _subWhere = (result, links) => {
122
123
  where.push('or')
123
124
  }
124
125
  const whereObj = links.reduce((res, currentLink) => {
125
- res[currentLink.targetKey] = row[currentLink.entityKey]
126
+ if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey))
127
+ res[currentLink.entityKey] = row[currentLink.targetKey]
126
128
  return res
127
129
  }, {})
128
- where.push('(', ...ctUtils.whereKey(whereObj), ')')
130
+ const whereCQN = ctUtils.whereKey(whereObj)
131
+ if (whereCQN.length) where.push('(', ...whereCQN, ')')
129
132
  }
130
133
  }
131
134
  return where
@@ -143,17 +146,7 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
143
146
  } else if (assoc.is2many) {
144
147
  selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || []
145
148
  }
146
- let pk = _parentKey(compositionTree, selectEntry)
147
- // adjust pk for nested composition of one
148
- if (
149
- (!pk || Object.keys(pk).length === 0) &&
150
- assoc.isComposition &&
151
- assoc.is2one &&
152
- compositionTree.links.length === 1
153
- ) {
154
- pk = { [compositionTree.links[0].targetKey]: selectEntry[compositionTree.links[0].entityKey] }
155
- }
156
- const newData = _findWhere(result, pk)
149
+ const newData = _findWhere(result, _parentKey(compositionTree, selectEntry))
157
150
  if (assoc.is2one && newData[0]) {
158
151
  selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
159
152
  } else if (assoc.is2many) {
@@ -165,23 +158,20 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
165
158
  }
166
159
 
167
160
  const _columns = (entity, data, compositionTree, selectAll) => {
168
- const links = _getLinksOfCompTree(compositionTree)
169
- const backLinkKeys = [
170
- ...compositionTree.backLinks.map(backLink => backLink.entityKey),
171
- ...compositionTree.customBackLinks.map(customBackLink => customBackLink.entityKey)
172
- ]
173
- const columns = _dataElements(entity)
174
- .filter(
175
- element =>
176
- selectAll ||
177
- element.key ||
178
- links.includes(element.name) ||
179
- backLinkKeys.includes(element.name) ||
180
- (Array.isArray(data) && data.find(entry => element.name in entry))
181
- )
182
- .map(element => {
183
- return { ref: [element.name] }
184
- })
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
+ }
185
175
  return columns
186
176
  }
187
177
 
@@ -230,12 +220,16 @@ const _selectDeepUpdateData = async args => {
230
220
  compositionTree: element,
231
221
  entityName: element.source,
232
222
  data: _subData(data, element.name),
233
- where: _subWhere(result, element.links),
223
+ where: _subWhere(result, element),
234
224
  selectData: result,
235
225
  parentKeys: _parentKeys(element, keys),
236
226
  orderBy: false,
237
227
  root: false
238
228
  }
229
+
230
+ // REVISIT: remove null elements
231
+ subs.data = subs.data.filter(d => d)
232
+
239
233
  return _selectDeepUpdateData({ ...args, ...subs })
240
234
  })
241
235
  )
@@ -247,7 +241,14 @@ const _selectDeepUpdateData = async args => {
247
241
  * exports
248
242
  */
249
243
 
250
- 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
+ ) => {
251
252
  // REVISIT this should be done somewhere before, so it is not done twice for deep updates
252
253
  const sqlQuery = cqn2cqn4sql(cqn, { definitions })
253
254
 
@@ -255,7 +256,7 @@ const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = fal
255
256
  return Promise.resolve(req._.partialPersistentState)
256
257
  }
257
258
 
258
- const from = _from(sqlQuery)
259
+ const from = getEntityNameFromUpdateCQN(sqlQuery)
259
260
  const alias = sqlQuery.UPDATE.entity.as
260
261
  const where = sqlQuery.UPDATE.where || []
261
262
  const entityName = ensureNoDraftsSuffix(from)
@@ -266,7 +267,8 @@ const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = fal
266
267
  definitions,
267
268
  rootEntityName: entityName, // REVISIT: drafts are resolved too eagerly
268
269
  checkRoot: false,
269
- resolveViews: !draft
270
+ resolveViews: !draft,
271
+ service
270
272
  })
271
273
 
272
274
  return _selectDeepUpdateData({
@@ -281,8 +283,9 @@ const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = fal
281
283
  includeAllRootColumns,
282
284
  singleton: req && req.target && req.target._isSingleton,
283
285
  alias,
284
- includeAllColumns: cqn._selectAll,
285
- root: true
286
+ includeAllColumns: cqn._selectAll || includeAllColumns,
287
+ root: true,
288
+ service
286
289
  })
287
290
  }
288
291