@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
@@ -1,93 +1,123 @@
1
1
  const cds = require('../cds')
2
+ const { SELECT } = cds.ql
3
+
4
+ const getError = require('../common/error')
2
5
  const { findCsnTargetFor } = require('../common/utils/csn')
3
- // REVISIT: should be cds.ql
4
- const { SELECT } = require('../cds-services/statements')
6
+ const { getAllKeys } = require('../cds-services/adapter/odata-v4/odata-to-cqn/utils')
5
7
  const rewriteAsterisk = require('../common/utils/rewriteAsterisk')
6
-
7
8
  const { getMaxPageSize } = require('../common/utils/page')
8
9
 
9
- const _isEntityWithKeys = (entity, ref) => {
10
- if (!ref || !ref.id || !ref.where) return
11
- const keys = ref.where.reduce((res, part) => {
12
- if (Array.isArray(part.ref)) res.push(part.ref[0])
13
- return res
14
- }, [])
15
- for (const key in entity.keys) {
16
- if (entity.elements[key].target) continue
17
- if (keys.indexOf(key) === -1) return
18
- keys.splice(keys.indexOf(key))
10
+ const _hasAllEntityKeysInWhere = (keys, where) => {
11
+ if (!(where && keys && keys.length)) return
12
+ keys = keys.map(key => (Array.isArray(key) ? key.join('_') : key))
13
+ for (const part of where) {
14
+ if (part.ref && keys.indexOf(part.ref.join('_')) > -1) {
15
+ keys.splice(keys.indexOf(part.ref.join('_')))
16
+ }
19
17
  }
18
+
20
19
  return keys.length === 0
21
20
  }
22
21
 
23
- const _isCollection = (ref, root, model) =>
24
- ref.length > 1
25
- ? ref.slice(1).reduce((csn, _ref, i, arr) => {
26
- if (!csn) return false
27
- const seg = _ref.id || _ref
28
- let target = csn.elements && csn.elements[seg]
29
- if (!target) {
30
- if (csn.target) target = model.definitions[csn.target].elements[seg]
31
- else if (!csn.items && csn._isStructured) {
32
- // REVISIT: this is not tested with x4!!!
33
- if (csn.elements) target = csn.elements[seg]
34
- else if (csn.type) target = model.definitions[csn.type].elements[seg]
35
- }
36
- }
37
- if (i === arr.length - 1) {
38
- if (target && target.target) return !_isEntityWithKeys(model.definitions[target.target], _ref)
39
- if (target && target.items) return true
40
- } else return target
41
- }, root)
42
- : !_isEntityWithKeys(root, ref[0])
22
+ const _setLimits = (cqn, parsed, target) => {
23
+ // adjust SELECT.limit based on @cds.query.limit annotations
24
+ const rows = parsed.SELECT.limit.rows ? parsed.SELECT.limit.rows.val : Number.MAX_SAFE_INTEGER
25
+ const offset = parsed.SELECT.limit.offset ? parsed.SELECT.limit.offset.val : 0
26
+ cqn.limit(Math.min(rows, getMaxPageSize(target)), offset)
27
+ }
43
28
 
44
- /*
45
- * uses new odata2cqn
46
- */
47
- const _newReadToCQN = ({ model, namespace }, target, req) => {
48
- const odata = !!req.getUrlObject
29
+ const _setRootInFrom = (cqn, target, model, namespace) => {
30
+ const root = _findRoot(cqn, target, model, namespace)
31
+ const from = cqn.SELECT.from
32
+ if (from.ref[0].id) from.ref[0].id = root.name
33
+ else from.ref[0] = root.name
34
+ return cqn
35
+ }
49
36
 
50
- const url = odata ? req.getUrlObject().path : req.url
37
+ const _findRoot = (cqn, target, model, namespace) => {
38
+ const parsedTargetName = cqn.SELECT.from.ref[0].id || cqn.SELECT.from.ref[0]
39
+ return target.name === parsedTargetName ? target : findCsnTargetFor(parsedTargetName, model, namespace)
40
+ }
51
41
 
52
- const parsed = cds.odata.parse.url(url)
42
+ const _resolveRef = (ref, model, parent) => {
43
+ ref = ref.id || ref
44
+ const element = parent && parent.elements && parent.elements[ref]
45
+ let target = element || parent || model.definitions[ref]
46
+ let aspect
47
+ if (target && !target.elements) {
48
+ if (target.target) {
49
+ if (target.targetAspect) aspect = model.definitions[target.targetAspect]
50
+ target = model.definitions[target.target]
51
+ } else if (target.kind === 'type') {
52
+ target = model.definitions[target.type]
53
+ }
54
+ }
55
+ const keys = getAllKeys(aspect || target, false)
56
+ return { target, keys, element }
57
+ }
53
58
 
54
- const parsedTargetName = parsed.SELECT.from.ref[0].id || parsed.SELECT.from.ref[0]
55
- let root = target
56
- if (parsedTargetName !== target.name) {
57
- root = findCsnTargetFor(parsedTargetName, model, namespace)
58
- if (parsed.SELECT.from.ref[0].id) {
59
- parsed.SELECT.from.ref[0].id = root.name
60
- } else {
61
- parsed.SELECT.from.ref[0] = root.name
59
+ const _resolveStructKeys = (where, keys) => {
60
+ for (const el of where) {
61
+ if (!el.ref) continue
62
+ const keyIdx = keys.findIndex(key => Array.isArray(key) && key[key.length - 1] === el.ref[0])
63
+ if (keyIdx > -1) {
64
+ el.ref = [keys[keyIdx].join('_')]
65
+ keys.splice(keyIdx, 1)
62
66
  }
63
67
  }
64
- const whereKeys = parsed.SELECT.from.ref[0].where
65
- if (whereKeys && whereKeys.length === 1) {
66
- whereKeys.push('=', whereKeys[0])
67
- for (const key in root.keys) {
68
- whereKeys.splice(0, 1, { ref: [key] })
68
+ }
69
+
70
+ const _postProcess = (cqn, target, model, namespace) => {
71
+ _setRootInFrom(cqn, target, model, namespace)
72
+ let prev
73
+ for (let i = 0; i < cqn.SELECT.from.ref.length; i++) {
74
+ const ref = cqn.SELECT.from.ref[i]
75
+ const { target, keys, element } = _resolveRef(ref, model, prev)
76
+ if (ref.where) {
77
+ // cases like `GET /Foo(1)/bar(2)` => `where` contains single `val`s
78
+ if (ref.where.length === 1 && keys.length === 1) {
79
+ ref.where = [{ ref: [Array.isArray(keys[0]) ? keys[0].join('_') : keys[0]] }, '=', ref.where[0]]
80
+ } else {
81
+ _resolveStructKeys(ref.where, keys)
82
+ }
69
83
  }
84
+ // $value never comes, $count is 2many, $ref is to be checked
85
+ if (i === cqn.SELECT.from.ref.length - 1) {
86
+ if (target && !target.elements) delete cqn.SELECT.columns
87
+ if ((element && element.is2one) || (target && target._isSingleton) || _hasAllEntityKeysInWhere(keys, ref.where)) {
88
+ cqn.SELECT.one = true
89
+ }
90
+ }
91
+ prev = target
92
+ }
93
+ return cqn
94
+ }
95
+
96
+ /*
97
+ * uses new odata2cqn
98
+ */
99
+ const _newReadToCQN = ({ model, namespace }, target, req) => {
100
+ let parsed
101
+
102
+ try {
103
+ parsed = cds.odata.parse.url(req.url || req._inRequestUrl)
104
+ } catch (err) {
105
+ throw getError(400, err.message)
70
106
  }
71
107
 
72
- const cqn = SELECT.from(target.name)
108
+ _postProcess(parsed, target, model, namespace)
73
109
 
74
- cqn.SELECT.from = parsed.SELECT.from
75
- if (parsed.SELECT.columns) cqn.SELECT.columns = parsed.SELECT.columns
110
+ const cqn = SELECT.from(parsed.SELECT.from, parsed.SELECT.columns)
76
111
  if (parsed.SELECT.search) cqn.SELECT.search = parsed.SELECT.search
77
112
  if (parsed.SELECT.where) cqn.SELECT.where = parsed.SELECT.where
78
113
  if (parsed.SELECT.orderBy) cqn.SELECT.orderBy = parsed.SELECT.orderBy
79
114
  if (parsed.SELECT.groupBy) cqn.SELECT.groupBy = parsed.SELECT.groupBy
80
115
  if (parsed.SELECT.count) cqn.SELECT.count = parsed.SELECT.count
81
-
82
- if (!_isCollection(parsed.SELECT.from.ref, root, model)) {
83
- cqn.SELECT.one = true
116
+ if (parsed.SELECT.one) {
117
+ cqn.SELECT.one = parsed.SELECT.one
84
118
  } else if (parsed.SELECT.limit) {
85
- // adjust SELECT.limit based on @cds.query.limit annotations
86
- const rows = parsed.SELECT.limit.rows ? parsed.SELECT.limit.rows.val : Number.MAX_SAFE_INTEGER
87
- const offset = parsed.SELECT.limit.offset ? parsed.SELECT.limit.offset.val : 0
88
- cqn.limit(Math.min(rows, getMaxPageSize(target)), offset)
119
+ _setLimits(cqn, parsed, target, model)
89
120
  }
90
-
91
121
  if (!cds.env.features.rest_new_parser) {
92
122
  // REVISIT not needed for rest parser?
93
123
  rewriteAsterisk({ query: cqn, target })
@@ -9,15 +9,12 @@ if (!LOG._debug) {
9
9
  sdkUtils.setGlobalLogLevel('error')
10
10
  }
11
11
 
12
- const { resolveView, getTransition } = require('../common/utils/resolveView')
13
- const { getKind, run, getDestination, getAdditionalOptions, getReqOptions, postProcess } = require('./utils/client')
12
+ const { resolveView, getTransition, restoreLink, findQueryTarget } = require('../common/utils/resolveView')
13
+ const { postProcess } = require('../common/utils/postProcessing')
14
+ const { getKind, run, getDestination, getAdditionalOptions, getReqOptions } = require('./utils/client')
14
15
  const { formatVal } = require('../odata/cqn2odata')
15
16
 
16
- const _checkProduction = destination => {
17
- if (!destination && process.env.NODE_ENV === 'production') {
18
- throw new Error('In production mode it is required to set `options.destination`')
19
- }
20
- }
17
+ const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
21
18
 
22
19
  const _setHeaders = (defaultHeaders, req) => {
23
20
  return Object.assign(
@@ -112,17 +109,30 @@ const _addHandlerActionFunction = (srv, def, target) => {
112
109
  }
113
110
  }
114
111
 
112
+ const _selectOnlyWithAlias = q => {
113
+ return q && q.SELECT && !q.SELECT._transitions && q.SELECT.columns && q.SELECT.columns.some(c => c.as)
114
+ }
115
+
116
+ const resolvedTargetOfQuery = q => {
117
+ const transitions = (typeof q === 'object' && (q.SELECT || q.INSERT || q.UPDATE || q.DELETE)._transitions) || []
118
+ return transitions.length && [transitions.length - 1].target
119
+ }
120
+
115
121
  class RemoteService extends cds.Service {
116
122
  init() {
117
- this.destination = this.options.credentials && this.options.credentials.destination
118
- this.requestTimeout = this.options.credentials && this.options.credentials.requestTimeout
119
- if (this.requestTimeout === null || this.requestTimeout === undefined) this.requestTimeout = 60000
120
- this.path = this.options.credentials && this.options.credentials.path
123
+ if (!this.options.credentials) {
124
+ throw new Error(`No credentials configured for "${this.name}".`)
125
+ }
126
+
121
127
  this.datasource = this.options.datasource
128
+ this.destination =
129
+ this.options.credentials.destination ||
130
+ getDestination((this.definition && this.definition.name) || this.datasource, this.options.credentials)
131
+ this.requestTimeout = this.options.credentials.requestTimeout
132
+ if (this.requestTimeout == null) this.requestTimeout = 60000
133
+ this.path = this.options.credentials.path
122
134
  this.kind = getKind(this.options) // TODO: Simplify
123
135
 
124
- _checkProduction(this.destination)
125
-
126
136
  for (const each of this.entities) {
127
137
  for (const a in each.actions) {
128
138
  _addHandlerActionFunction(this, each.actions[a], each)
@@ -136,20 +146,63 @@ class RemoteService extends cds.Service {
136
146
  this.on('*', async (req, next) => {
137
147
  let { query } = req
138
148
  if (!query && !(typeof req.path === 'string')) return next()
139
- if (typeof query === 'object' && this.model) query = resolveView(query, this.model, 'remote', true)
140
- const transitions =
141
- (typeof query === 'object' && (query.SELECT || query.INSERT || query.UPDATE || query.DELETE)._transitions) || []
142
- const resolvedTarget =
143
- (transitions.length && [transitions.length - 1].target) || getTransition(req.target, true).target
144
- if (!this.destination) this.destination = getDestination(this.model, this.datasource, this.options)
149
+ if (cds.env.features.resolve_views === false && typeof query === 'object' && this.model) {
150
+ query = resolveView(query, this.model, this)
151
+ }
152
+
153
+ const resolvedTarget = resolvedTargetOfQuery(query) || getTransition(req.target, this).target
145
154
  const reqOptions = getReqOptions(req, query, this)
146
155
  reqOptions.headers = _setHeaders(reqOptions.headers, req)
147
156
  const additionalOptions = getAdditionalOptions(req, this.destination, this.kind, resolvedTarget)
148
157
 
149
- const result = await run(reqOptions, additionalOptions)
150
- return typeof query === 'object' ? postProcess(query, result) : result
158
+ // hidden compat flag in order to suppress logging response body of failed request
159
+ if (req._suppressRemoteResponseBody) {
160
+ additionalOptions.suppressRemoteResponseBody = req._suppressRemoteResponseBody
161
+ }
162
+
163
+ let result = await run(reqOptions, additionalOptions)
164
+
165
+ result =
166
+ typeof query === 'object' && query.SELECT && query.SELECT.one && Array.isArray(result) ? result[0] : result
167
+
168
+ return cds.env.features.resolve_views === false && typeof query === 'object' ? postProcess(query, result) : result
151
169
  })
152
170
  }
171
+
172
+ // Overload .handle in order to resolve projections up to a definition that is known by the remote service instance.
173
+ // Result is post processed according to the inverse projection in order to reflect the correct result of the original query.
174
+ async handle(req) {
175
+ // compat mode
176
+ if (req._resolved || cds.env.features.resolve_views === false) return super.handle(req)
177
+
178
+ if (req.target && req.target.name && this.definition && req.target.name.startsWith(this.definition.name + '.')) {
179
+ const result = await super.handle(req)
180
+ // only post process if alias was explicitely set in query
181
+ if (_selectOnlyWithAlias(req.query)) {
182
+ return postProcess(req.query, result, true)
183
+ }
184
+ return result
185
+ }
186
+
187
+ // req.query can be:
188
+ // - empty object in case of unbound action/function
189
+ // - undefined/null in case of plain string queries
190
+ if (_isSimpleCqnQuery(req.query) && this.model) {
191
+ const q = resolveView(req.query, this.model, this)
192
+ const t = findQueryTarget(q) || req.target
193
+
194
+ // compat
195
+ restoreLink(req)
196
+
197
+ // REVISIT: We need to provide target explicitly because it's cached already within ensure_target
198
+ const newReq = new cds.Request({ query: q, target: t, headers: req.headers, _resolved: true })
199
+ const result = await super.dispatch(newReq)
200
+
201
+ return postProcess(q, result, true)
202
+ }
203
+
204
+ return super.handle(req)
205
+ }
153
206
  }
154
207
 
155
208
  module.exports = RemoteService
@@ -1,6 +1,2 @@
1
1
  const cds = require('../../cds')
2
-
3
- module.exports = (cqn, type, model) => {
4
- if (!cds.odata) cds.odata = require('../../odata')
5
- return cds.odata.parse.cqn(cqn, type, model)
6
- }
2
+ module.exports = cds.odata.urlify
@@ -1,7 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
  const LOG = cds.log('remote')
3
+ const cdsLocale = require('../../../../lib/req/locale.js')
3
4
 
4
- const { revertData } = require('../../common/utils/resolveView')
5
5
  const generateQuery = require('../cqn2odata')
6
6
  const { convertV2ResponseData } = require('./dataConversion')
7
7
 
@@ -26,27 +26,9 @@ const _executeHttpRequest = (...args) => {
26
26
  return _cloudSdkCore.executeHttpRequest(...args)
27
27
  }
28
28
 
29
- const findServiceName = (model, ds, options) => {
30
- const modelServices = Object.values(model.services)
31
-
32
- if (options.credentials && options.credentials.service) {
33
- if (!modelServices.find(srv => srv.name === options.credentials.service)) {
34
- throw new Error(`Service "${options.credentials.service}" not found in provided model`)
35
- }
36
-
37
- return options.credentials.service
38
- }
39
-
40
- return ds
41
- }
42
-
43
- const createDestinationObject = (name, credentials) => {
44
- if (!credentials) {
45
- throw new Error(`No credentials configured for "${name}"`)
46
- }
47
-
29
+ const getDestination = (name, credentials) => {
48
30
  if (!credentials.url) {
49
- throw new Error(`No url configured in credentials for "${name}"`)
31
+ throw new Error(`"url" or "destination" property must be configured in "credentials" of "${name}".`)
50
32
  }
51
33
 
52
34
  return { name, ...credentials }
@@ -88,62 +70,6 @@ const formatPath = path => {
88
70
 
89
71
  return formattedPath
90
72
  }
91
- // creates a map with key "remote origin name" and value { as: "projection name"}
92
- // if it is an expand, it contains an additional property .expand with classic ref/as syntax
93
- // ref/as syntax is kept in order to reuse handleAliasInResult
94
- const _createAliasMap = columns => {
95
- if (columns) {
96
- let aliasMap
97
- for (const col of columns) {
98
- const processor = {}
99
- if (col.as) {
100
- processor.as = col.as
101
- ;(aliasMap || (aliasMap = new Map())) && aliasMap.set(col.ref[col.ref.length - 1], processor)
102
- }
103
- if (col.expand) {
104
- processor.expand = col.expand
105
- }
106
- }
107
-
108
- return aliasMap
109
- }
110
- }
111
-
112
- // Transforms the result of the remote service according to the provided aliases
113
- const handleAliasInResult = (columns, result) => {
114
- const postProcessor = _createAliasMap(columns)
115
- const resultArray = Array.isArray(result) ? result : [result]
116
- if (postProcessor) {
117
- for (const row of resultArray) {
118
- // we need to use a cache because of cross renamings
119
- // e. g. column a is renamed to b and column b is renamed to a
120
- const tempCache = new Map()
121
-
122
- for (const col in row) {
123
- const processor = postProcessor.get(col)
124
- if (processor && processor.as !== col) {
125
- // if a value for the alias is already present, add it to the cache
126
- if (row[processor.as]) {
127
- tempCache.set(processor.as, row[processor.as])
128
- }
129
-
130
- // get the value from cache if present
131
- row[processor.as] = tempCache.get(col) || row[col]
132
-
133
- // if it was not overridden because of a renaming,
134
- // delete it from the row
135
- if (!tempCache.has(processor.as)) {
136
- delete row[col]
137
- }
138
- }
139
-
140
- if (processor && processor.expand) {
141
- handleAliasInResult(processor.expand, row[processor.as || col])
142
- }
143
- }
144
- }
145
- }
146
- }
147
73
 
148
74
  function _defineProperty(obj, property, value) {
149
75
  const props = {}
@@ -202,7 +128,7 @@ const _purgeODataV4 = data => {
202
128
  const TYPES_TO_REMOVE = { function: 1, object: 1 }
203
129
  const PROPS_TO_IGNORE = { cause: 1, name: 1 }
204
130
 
205
- const _getSanitizedError = (e, reqOptions) => {
131
+ const _getSanitizedError = (e, reqOptions, suppressRemoteResponseBody) => {
206
132
  e.request = {
207
133
  method: reqOptions.method,
208
134
  url: e.config ? e.config.baseURL + e.config.url : reqOptions.url,
@@ -215,7 +141,9 @@ const _getSanitizedError = (e, reqOptions) => {
215
141
  statusText: e.response.statusText,
216
142
  headers: e.response.headers
217
143
  }
218
- if (e.response.data && e.response.data.error) response.body = e.response.data
144
+ if (e.response.data && !suppressRemoteResponseBody) {
145
+ response.body = e.response.data
146
+ }
219
147
  e.response = response
220
148
  }
221
149
 
@@ -247,7 +175,7 @@ const _getSanitizedError = (e, reqOptions) => {
247
175
  return e
248
176
  }
249
177
 
250
- const run = async (reqOptions, { destination, jwt, kind, resolvedTarget }) => {
178
+ const run = async (reqOptions, { destination, jwt, kind, resolvedTarget, suppressRemoteResponseBody }) => {
251
179
  const dest = typeof destination === 'string' ? { destinationName: destination, jwt } : destination
252
180
 
253
181
  let response
@@ -257,7 +185,7 @@ const run = async (reqOptions, { destination, jwt, kind, resolvedTarget }) => {
257
185
  // > axios received status >= 400 -> gateway error
258
186
  e.message = e.message ? 'Error during request to remote service: ' + e.message : 'Request to remote service failed.'
259
187
 
260
- const sanitizedError = _getSanitizedError(e, reqOptions)
188
+ const sanitizedError = _getSanitizedError(e, reqOptions, suppressRemoteResponseBody)
261
189
 
262
190
  LOG._warn && LOG.warn(sanitizedError)
263
191
 
@@ -278,7 +206,7 @@ const run = async (reqOptions, { destination, jwt, kind, resolvedTarget }) => {
278
206
  const e = new Error("Received content-type 'text/html' which is not part of accepted content types")
279
207
  e.response = response
280
208
 
281
- const sanitizedError = _getSanitizedError(e, reqOptions)
209
+ const sanitizedError = _getSanitizedError(e, reqOptions, suppressRemoteResponseBody)
282
210
 
283
211
  LOG._warn && LOG.warn(sanitizedError)
284
212
 
@@ -353,6 +281,11 @@ const _pathToReqOptions = (method, path, data) => {
353
281
  return reqOptions
354
282
  }
355
283
 
284
+ const _hasHeader = (headers, header) =>
285
+ Object.keys(headers || [])
286
+ .map(k => k.toLowerCase())
287
+ .includes(header)
288
+
356
289
  const getReqOptions = (req, query, service) => {
357
290
  const reqOptions =
358
291
  typeof query === 'object'
@@ -364,11 +297,19 @@ const getReqOptions = (req, query, service) => {
364
297
  reqOptions.headers = { accept: 'application/json,text/plain' }
365
298
  reqOptions.timeout = service.requestTimeout
366
299
 
300
+ if (!_hasHeader(req.headers, 'accept-language')) {
301
+ // Forward the locale properties from the original request (including region variants or weight factors),
302
+ // if not given, it's taken from the user's locale (normalized and simplified)
303
+ const locale =
304
+ (req.context && req.context._ && req.context._.req && cdsLocale.from_req(req.context._.req)) ||
305
+ (req.user && req.user.locale)
306
+ if (locale) reqOptions.headers['accept-language'] = locale
307
+ }
308
+
367
309
  if (reqOptions.data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
368
310
  reqOptions.headers['content-type'] = 'application/json'
369
311
  reqOptions.headers['content-length'] = Buffer.byteLength(JSON.stringify(reqOptions.data))
370
312
  }
371
-
372
313
  reqOptions.url = formatPath(reqOptions.url)
373
314
 
374
315
  if (service.path) reqOptions.url = `${encodeURI(service.path)}${reqOptions.url}`
@@ -376,20 +317,6 @@ const getReqOptions = (req, query, service) => {
376
317
  return reqOptions
377
318
  }
378
319
 
379
- // REVISIT: todo renaming for expanded entities
380
- // REVISIT: todo renaming for deep operations
381
- const postProcess = (query, result) => {
382
- if (query.SELECT) {
383
- handleAliasInResult(query.SELECT.columns, result)
384
- return typeof query === 'object' && query.SELECT.one && Array.isArray(result) ? result[0] : result
385
- }
386
- if (query.DELETE) return result
387
- let transition
388
- if (query.INSERT) transition = query.INSERT._transitions[query.INSERT._transitions.length - 1]
389
- if (query.UPDATE) transition = query.UPDATE._transitions[query.UPDATE._transitions.length - 1]
390
- return revertData(result, transition)
391
- }
392
-
393
320
  const getAdditionalOptions = (req, destination, kind, resolvedTarget) => {
394
321
  const jwt = getJwt(req)
395
322
  const additionalOptions = { destination, kind, resolvedTarget }
@@ -397,14 +324,10 @@ const getAdditionalOptions = (req, destination, kind, resolvedTarget) => {
397
324
  return additionalOptions
398
325
  }
399
326
 
400
- const getDestination = (model, datasource, options) =>
401
- createDestinationObject(findServiceName(model, datasource, options), options.credentials)
402
-
403
327
  module.exports = {
404
328
  getKind,
405
329
  run,
406
330
  getReqOptions,
407
- postProcess,
408
331
  getDestination,
409
332
  getAdditionalOptions
410
333
  }
@@ -21,33 +21,40 @@ const DataTypeOData = {
21
21
  }
22
22
 
23
23
  const _convertData = (data, target, ieee754Compatible) => {
24
- if (!Array.isArray(data)) {
25
- return _convertRecord(target, ieee754Compatible)(data)
24
+ if (Array.isArray(data)) {
25
+ return data.map(record => _getConvertRecordFn(target, ieee754Compatible)(record))
26
26
  }
27
- return data.map(_convertRecord(target, ieee754Compatible))
27
+
28
+ return _getConvertRecordFn(target, ieee754Compatible)(data)
28
29
  }
29
30
 
30
- const _convertRecord = (target, ieee754Compatible) => record => {
31
+ const _getConvertRecordFn = (target, ieee754Compatible) => record => {
31
32
  for (const key in record) {
32
- const value = record[key]
33
+ if (key === '__metadata') continue
34
+
33
35
  const element = target.elements[key]
36
+ if (!element) continue
37
+
38
+ const recordValue = record[key]
34
39
  const type = _elementType(element)
35
- if (element) {
36
- if (value && Array.isArray(value.results || value)) {
37
- record[key] = _convertData(value.results || value, element._target, ieee754Compatible)
38
- } else {
39
- record[key] = _convertValue(value, type, ieee754Compatible)
40
- }
40
+ const value = (recordValue && recordValue.results) || recordValue
41
+
42
+ if (value && (element.isAssociation || Array.isArray(value))) {
43
+ record[key] = _convertData(value, element._target, ieee754Compatible)
44
+ } else {
45
+ record[key] = _convertValue(value, type, ieee754Compatible)
41
46
  }
42
47
  }
48
+
43
49
  return record
44
50
  }
45
51
 
46
52
  // eslint-disable-next-line complexity
47
53
  const _convertValue = (value, type, ieee754Compatible) => {
48
- if (value === null || value === undefined) {
54
+ if (value == null) {
49
55
  return value
50
56
  }
57
+
51
58
  if (['cds.Boolean'].includes(type)) {
52
59
  if (value === 'true') {
53
60
  value = true
@@ -62,21 +69,25 @@ const _convertValue = (value, type, ieee754Compatible) => {
62
69
  value = parseFloat(value)
63
70
  } else if (['cds.Time'].includes(type)) {
64
71
  const match = value.match(DurationRegex)
72
+
65
73
  if (match) {
66
74
  value = `${match[4] || '00'}:${match[5] || '00'}:${match[6] || '00'}`
67
75
  }
68
76
  } else if (['cds.Date', 'cds.DateTime', 'cds.Timestamp'].includes(type)) {
69
77
  const match = value.match(/\/Date\((.*)\)\//)
70
78
  const ticksAndOffset = match && match.pop()
79
+
71
80
  if (ticksAndOffset) {
72
81
  value = new Date(_calculateTicksOffsetSum(ticksAndOffset)).toISOString() // always UTC
73
82
  }
83
+
74
84
  if (['cds.DateTime'].includes(type)) {
75
85
  value = value.slice(0, 19) + 'Z' // Cut millis
76
86
  } else if (['cds.Date'].includes(type)) {
77
87
  value = value.slice(0, 10) // Cut time
78
88
  }
79
89
  }
90
+
80
91
  return value
81
92
  }
82
93
 
@@ -88,16 +99,20 @@ const _calculateTicksOffsetSum = text => {
88
99
 
89
100
  const _elementType = element => {
90
101
  let type
102
+
91
103
  if (element) {
92
104
  type = element.type
105
+
93
106
  if (element['@odata.Type']) {
94
107
  const odataType = element['@odata.Type'].match(/\w+$/)
95
108
  type = (odataType && DataTypeOData[odataType[0]]) || type
96
109
  }
110
+
97
111
  if (!type && element.items && element.items.type) {
98
112
  type = element.items.type
99
113
  }
100
114
  }
115
+
101
116
  return type
102
117
  }
103
118
 
@@ -16,13 +16,11 @@ const convertAssocToOneManaged = require('./convertAssocToOneManaged')
16
16
  const execute = require('./execute')
17
17
 
18
18
  const _new = url => {
19
+ if (url && url !== ':memory:') url = cds.utils.path.resolve(cds.root, url)
20
+ if (!_sqlite) _sqlite = require('sqlite3')
19
21
  return new Promise((resolve, reject) => {
20
- if (!_sqlite) {
21
- _sqlite = require('sqlite3')
22
- }
23
22
  const dbc = new _sqlite.Database(url, err => {
24
- if (err) return reject(err)
25
- resolve(dbc)
23
+ err ? reject(err) : resolve(dbc)
26
24
  })
27
25
  })
28
26
  }