@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
@@ -1,3 +1,5 @@
1
+ const cds = require('../../cds')
2
+
1
3
  const { getCompositionTree } = require('./tree')
2
4
  const { getDeepInsertCQNs } = require('./insert')
3
5
  const { getDeepDeleteCQNs } = require('./delete')
@@ -13,6 +15,8 @@ const getError = require('../../common/error')
13
15
  */
14
16
 
15
17
  const _serializedKey = (entity, data) => {
18
+ if (data === null) return 'null'
19
+
16
20
  return JSON.stringify(
17
21
  ctUtils
18
22
  .keyElements(entity)
@@ -72,6 +76,10 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, definitions) =>
72
76
  const oldVal = ctUtils.val(oldData[key])
73
77
 
74
78
  if (newVal !== undefined && newVal !== oldVal) {
79
+ if (entity.elements[key]._isStructured && Object.keys(newData[key]).length === 0) {
80
+ // empty structured -> skip
81
+ continue
82
+ }
75
83
  result[key] = newData[key]
76
84
  continue
77
85
  }
@@ -107,6 +115,8 @@ function _addSubDeepUpdateCQNForUpdateInsert({
107
115
  const selectDataByKey = _dataByKey(entity, selectData)
108
116
  const deepUpdateData = []
109
117
  for (const entry of data) {
118
+ if (entry === null) continue
119
+
110
120
  const key = ctUtils.key(entity, entry)
111
121
  const selectEntry = selectDataByKey.get(_serializedKey(entity, entry))
112
122
  _fillLinkFromStructuredData(entity, entry)
@@ -288,7 +298,8 @@ const getDeepUpdateCQNs = (definitions, cqn, selectData) => {
288
298
  definitions,
289
299
  rootEntityName: entityName,
290
300
  checkRoot: false,
291
- resolveViews: !draft
301
+ resolveViews: !draft,
302
+ service: cds.db
292
303
  })
293
304
 
294
305
  const subCQNs = _addSubDeepUpdateCQN({ definitions, compositionTree, data: [entry], selectData, cqns: [], draft })
@@ -9,9 +9,7 @@ const addDraftSuffix = (draft, name) => {
9
9
  const whereKey = key => {
10
10
  const where = []
11
11
  Object.keys(key).forEach(keyPart => {
12
- if (where.length > 0) {
13
- where.push('and')
14
- }
12
+ if (where.length > 0) where.push('and')
15
13
  where.push({ ref: [keyPart] }, '=', { val: key[keyPart] })
16
14
  })
17
15
  return where
@@ -30,5 +30,16 @@ module.exports = {
30
30
  { ref: ['HasActiveEntity'], cast: { type: 'cds.Boolean' } },
31
31
  { ref: ['HasDraftEntity'], cast: { type: 'cds.Boolean' } },
32
32
  { ref: ['DraftAdministrativeData_DraftUUID'] }
33
- ]
33
+ ],
34
+ SCENARIO: {
35
+ ACTIVE: 'ACTIVE',
36
+ ACTIVE_WITHOUT_DRAFT: 'ACTIVE_WITHOUT_DRAFT',
37
+ ALL_ACTIVE: 'ALL_ACTIVE',
38
+ ALL_INACTIVE: 'ALL_INACTIVE',
39
+ DRAFT_ADMIN: 'DRAFT_ADMIN',
40
+ DRAFT_IN_PROCESS: 'DRAFT_IN_PROCESS',
41
+ DRAFT_WHICH_OWNER: 'DRAFT_WHICH_OWNER',
42
+ SIBLING_ENTITY: 'SIBLING_ENTITY',
43
+ UNION: 'UNION'
44
+ }
34
45
  }
@@ -26,9 +26,6 @@ const RESTRICTIONS = {
26
26
  DELETABLE: 'DeleteRestrictions.Deletable'
27
27
  }
28
28
 
29
- // REVISIT: remove everywhere
30
- const SYMBOL_FROM_ANNOTATION = Symbol.for('sap.cds.FROM_ANNOTATION')
31
-
32
29
  const _reject = req => {
33
30
  // unauthorized or forbidden?
34
31
  if (req.user._is_anonymous) {
@@ -46,10 +43,12 @@ const _reject = req => {
46
43
 
47
44
  const _getCurrentSubClause = (next, restrict) => {
48
45
  const escaped = next[0].replace(/\$/g, '\\$').replace(/\./g, '\\.')
49
- const re = new RegExp(`([\\w\\.']*)\\s*=\\s*(${escaped})|(${escaped})\\s*=\\s*([\\w\\.']*)`)
50
- const clause = restrict.where.match(re)
46
+ const re1 = new RegExp(`([\\w\\.']*)\\s*=\\s*(${escaped})|(${escaped})\\s*=\\s*([\\w\\.']*)`)
47
+ const re2 = new RegExp(`([\\w\\.']*)\\s*in\\s*(${escaped})|(${escaped})\\s*in\\s*([\\w\\.']*)`)
48
+ const clause = restrict.where.match(re1) || restrict.where.match(re2)
51
49
  if (!clause) {
52
- throw new Error('user attribute array must be used with operator "="')
50
+ // NOTE: arrayed attr with "=" as operator is some kind of legacy case
51
+ throw new Error('user attribute array must be used with operator "=" or "in"')
53
52
  }
54
53
  return clause
55
54
  }
@@ -57,7 +56,18 @@ const _getCurrentSubClause = (next, restrict) => {
57
56
  const _processUserAttr = (next, restrict, user, attr) => {
58
57
  const clause = _getCurrentSubClause(next, restrict)
59
58
  const valOrRef = clause[1] || clause[4]
60
- if (valOrRef.startsWith("'") && user[attr].includes(valOrRef.split("'")[1])) {
59
+ if (clause[0].match(/ in /)) {
60
+ if (!user[attr] || user[attr].length === 0) {
61
+ restrict.where = restrict.where.replace(clause[0], '1 = 2')
62
+ } else if (user[attr].length === 1) {
63
+ restrict.where = restrict.where.replace(clause[0], `${valOrRef} = '${user[attr][0]}'`)
64
+ } else {
65
+ restrict.where = restrict.where.replace(
66
+ clause[0],
67
+ `${valOrRef} in (${user[attr].map(ele => `'${ele}'`).join(', ')})`
68
+ )
69
+ }
70
+ } else if (valOrRef.startsWith("'") && user[attr].includes(valOrRef.split("'")[1])) {
61
71
  restrict.where = restrict.where.replace(clause[0], `${valOrRef} = ${valOrRef}`)
62
72
  } else {
63
73
  restrict.where = restrict.where.replace(
@@ -164,16 +174,10 @@ const _evalStatic = (op, vals) => {
164
174
  }
165
175
  }
166
176
 
167
- const _addSymbol = element => {
168
- element = typeof element === 'string' ? new String(element) : element
169
- element[SYMBOL_FROM_ANNOTATION] = true
170
- return element
171
- }
172
-
173
177
  const _getMergedWhere = restricts => {
174
178
  const xprs = []
175
179
  restricts.forEach(ele => {
176
- xprs.push('(', ...ele._xpr.map(ele => _addSymbol(ele)), ')', 'or')
180
+ xprs.push('(', ...ele._xpr, ')', 'or')
177
181
  })
178
182
  xprs.pop()
179
183
  return xprs
@@ -229,16 +233,9 @@ const _ensureTableAlias = (ref, aliases, targetFrom, model, hasExpand) => {
229
233
  } else {
230
234
  _adaptTableName(ref, nameObj.refIndex, nameObj.name)
231
235
  }
232
-
233
- if (hasExpand && nameObj.aliasIndex === 0) {
234
- _addSymbol(ref)
235
- }
236
236
  }
237
237
 
238
238
  const _enhanceAnnotationSubSelect = (select, model, targetName, targetFrom, hasExpand) => {
239
- if (select.from && select.from.ref) {
240
- _addSymbol(select.from.ref)
241
- }
242
239
  if (select.where) {
243
240
  for (const v of select.where) {
244
241
  if (v.ref && select.from.ref) {
@@ -368,8 +365,10 @@ const _getRestrictionForTarget = (resolvedApplicables, target) => {
368
365
  }
369
366
 
370
367
  const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
371
- // context.query.where(['(', ...whereClause, ')'])
372
- // REVISIT: better attach where(s) to query as additional filters for later materialization (when?!)
368
+ if (req.target._isDraftEnabled) {
369
+ req.query._draftRestrictions = resolvedApplicables.map(ra => ra._xpr)
370
+ return
371
+ }
373
372
 
374
373
  if (typeof req.query.SELECT.from === 'object')
375
374
  req.query.SELECT.from.ref = _addWheresToRef(req.query.SELECT.from.ref, model, resolvedApplicables)
@@ -377,6 +376,7 @@ const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
377
376
  const restrictionForTarget = _getRestrictionForTarget(resolvedApplicables, req.target)
378
377
  if (restrictionForTarget) {
379
378
  req.query.where(restrictionForTarget)
379
+ // REVISIT: remove with cds^6
380
380
  _enhanceAnnotationWhere(req.query, restrictionForTarget, model)
381
381
  }
382
382
  }
@@ -418,7 +418,7 @@ const _getRestrictedCount = async (req, model, resolvedApplicables) => {
418
418
  const restrictionForTarget = _getRestrictionForTarget(resolvedApplicables, req.target)
419
419
  if (restrictionForTarget) selectRestricted.where(restrictionForTarget)
420
420
 
421
- const { n } = await dbtx.run(selectRestricted)
421
+ const { n } = await dbtx.run(cqn2cqn4sql(selectRestricted, model, { suppressSearch: true }))
422
422
  return n
423
423
  }
424
424
 
@@ -539,27 +539,49 @@ const _addNormalizedRestrictPerGrant = (grant, where, restrict, restricts, defin
539
539
  }
540
540
  }
541
541
 
542
- const _addNormalizedRestrict = (restrict, restricts, definition) => {
543
- const where = restrict.where
542
+ const _addNormalizedRestrict = (restrict, restricts, definition, definitions) => {
543
+ let where = restrict.where
544
544
  ? restrict.where.replace(/\$user/g, '$user.id').replace(/\$user\.id\./g, '$user.')
545
545
  : undefined
546
+
547
+ // NOTE: "exists toMany.toOne[prop = $user]" -> "exists toMany[exists toOne[prop = $user]]"
548
+ try {
549
+ if (where) {
550
+ // operate on a copy
551
+ let _where = where
552
+ const paths = where.match(/ (\w*)\.(\w*)/g) || []
553
+ for (let i = 0; i < paths.length; i++) {
554
+ const parts = paths[i].trim().split('.')
555
+ let current = definition
556
+ while (parts.length) {
557
+ current = current.elements[parts.shift()]
558
+ if (current.is2many) _where = _where.replace(current.name + '.', current.name + '[exists ') + ']'
559
+ if (current.target) current = definitions[current.target]
560
+ }
561
+ }
562
+ where = _where
563
+ }
564
+ } catch (e) {
565
+ // ignore
566
+ }
567
+
546
568
  restrict.grant = Array.isArray(restrict.grant) ? restrict.grant : [restrict.grant || '*']
547
569
  restrict.grant.forEach(grant => _addNormalizedRestrictPerGrant(grant, where, restrict, restricts, definition))
548
570
  }
549
571
 
550
- const _getNormalizedRestricts = definition => {
572
+ const _getNormalizedRestricts = (definition, definitions) => {
551
573
  const restricts = []
552
574
 
553
575
  // own
554
576
  definition['@restrict'] &&
555
- definition['@restrict'].forEach(restrict => _addNormalizedRestrict(restrict, restricts, definition))
577
+ definition['@restrict'].forEach(restrict => _addNormalizedRestrict(restrict, restricts, definition, definitions))
556
578
 
557
579
  // bounds
558
580
  if (definition.actions && Object.keys(definition.actions).some(k => definition.actions[k]['@restrict'])) {
559
581
  for (const k in definition.actions) {
560
582
  const action = definition.actions[k]
561
583
  if (action['@restrict']) {
562
- restricts.push(..._getNormalizedRestricts(action))
584
+ restricts.push(..._getNormalizedRestricts(action, definitions))
563
585
  } else if (!definition['@restrict']) {
564
586
  // > no entity-level restrictions => unrestricted action
565
587
  restricts.push({ grant: action.name, to: ['any'], target: action.parent })
@@ -635,7 +657,7 @@ const _registerEntityRequiresHandlers = (entity, srv, { dependentEntity, interme
635
657
 
636
658
  const _registerEntityRestrictHandlers = (entity, srv, { dependentEntity, intermediateEntities } = {}) => {
637
659
  if (entity['@restrict'] || entity.actions) {
638
- const restricts = _getNormalizedRestricts(entity)
660
+ const restricts = _getNormalizedRestricts(entity, srv.model.definitions)
639
661
  if (restricts.length > 0) {
640
662
  if (dependentEntity)
641
663
  srv.before(
@@ -657,7 +679,7 @@ const _registerOperationRequiresHandlers = (operation, srv) => {
657
679
 
658
680
  const _registerOperationRestrictHandlers = (operation, srv) => {
659
681
  if (operation['@restrict']) {
660
- const restricts = _getNormalizedRestricts(operation)
682
+ const restricts = _getNormalizedRestricts(operation, srv.model.definitions)
661
683
  if (restricts.length > 0) {
662
684
  srv.before(_getLocalName(operation), _getRestrictsHandler(restricts, operation, srv.model))
663
685
  }
@@ -5,16 +5,7 @@ const getTemplate = require('../utils/template')
5
5
  const templateProcessor = require('../utils/templateProcessor')
6
6
  const replaceManagedData = require('../utils/dollar')
7
7
 
8
- const _onlyKeysRemain = req => {
9
- // if custom handlers uses only expression like {col: {'+=': 1}},
10
- // req.data will only contain the keys
11
- if (req.query.UPDATE.with && Object.keys(req.query.UPDATE.with).length > 0) {
12
- return false
13
- }
14
-
15
- const keys = Object.keys(req.target.keys)
16
- return !Object.keys(req.data).some(k => !keys.includes(k))
17
- }
8
+ const onlyKeysRemain = require('../utils/onlyKeysRemain')
18
9
 
19
10
  const _targetEntityDoesNotExist = async req => {
20
11
  const { query } = req
@@ -39,7 +30,7 @@ const _processorFn = req => {
39
30
  const { event, user, timestamp } = req
40
31
  const ts = new Date(timestamp).toISOString()
41
32
 
42
- return (row, key, element, plain, isRoot) => {
33
+ return ({ row, key, plain }) => {
43
34
  const categories = plain.categories
44
35
 
45
36
  for (const category of categories) {
@@ -89,8 +80,8 @@ module.exports = function () {
89
80
 
90
81
  let result
91
82
 
92
- // no changes, no op (otherwise, @cds.on.update gets new values), but we need to check existance
93
- if (req.event === 'UPDATE' && _onlyKeysRemain(req)) {
83
+ // no changes, no op (otherwise, @cds.on.update gets new values), but we need to check existence
84
+ if (req.event === 'UPDATE' && onlyKeysRemain(req)) {
94
85
  if (await _targetEntityDoesNotExist(req)) {
95
86
  req.reject(404)
96
87
  }
@@ -98,6 +89,16 @@ module.exports = function () {
98
89
  result = req.data
99
90
  }
100
91
 
92
+ if (req.event === 'DELETE' && req.target._isSingleton) {
93
+ if (!req.target['@odata.singleton.nullable']) {
94
+ req.reject(400, 'SINGLETON_NOT_NULLABLE')
95
+ }
96
+
97
+ const singleton = await cds.tx(req).run(SELECT.one(req.target))
98
+ if (!singleton) req.reject(404)
99
+ req.query.where(singleton)
100
+ }
101
+
101
102
  if (!result) {
102
103
  result = await cds.tx(req).run(req.query, req.data)
103
104
  }
@@ -16,9 +16,8 @@ const templateProcessor = require('../utils/templateProcessor')
16
16
  const { getDataFromCQN, setDataFromCQN } = require('../utils/data')
17
17
  const { isMandatory, isReadOnly } = require('../aspects/utils')
18
18
 
19
- const shouldSuppressErrorPropagation = ({ event, value, suppress = false }) => {
19
+ const shouldSuppressErrorPropagation = ({ event, value }) => {
20
20
  return (
21
- suppress ||
22
21
  event === 'NEW' ||
23
22
  event === 'PATCH' ||
24
23
  (event === 'UPDATE' && value.val === undefined) ||
@@ -35,7 +34,7 @@ const getSimpleCategory = category => {
35
34
  }
36
35
 
37
36
  const rowKeysGenerator = eventName => {
38
- return ({ keyNames, row, template }) => {
37
+ return (keyNames, row, template) => {
39
38
  if (eventName === 'UPDATE') return
40
39
 
41
40
  for (const keyName of keyNames) {
@@ -96,7 +95,7 @@ const _processCategory = ({ row, key, category, isRoot, event, value, req, eleme
96
95
  const processorFn = (errors, req) => {
97
96
  const { event } = req
98
97
 
99
- return (row, key, element, plain, isRoot, pathSegments, suppressErrorPropagation = false) => {
98
+ return ({ row, key, element, plain, isRoot, pathSegments }) => {
100
99
  const categories = plain.categories
101
100
  // ugly pointer passing for sonar
102
101
  const value = { mandatory: false, val: row && row[key] }
@@ -105,7 +104,7 @@ const processorFn = (errors, req) => {
105
104
  _processCategory({ row, key, category, isRoot, event, value, req, element })
106
105
  }
107
106
 
108
- if (shouldSuppressErrorPropagation({ event, value, suppress: suppressErrorPropagation })) {
107
+ if (shouldSuppressErrorPropagation({ event, value })) {
109
108
  return
110
109
  }
111
110
 
@@ -211,34 +210,30 @@ function _handler(req) {
211
210
  _callError(req, errors)
212
211
  }
213
212
 
214
- const processorFnForActionsFunctions = (errors, opName) => (row, tKey, element) => {
215
- const value = row && row[tKey]
213
+ const processorFnForActionsFunctions =
214
+ (errors, opName) =>
215
+ ({ row, key, element }) => {
216
+ const value = row && row[key]
216
217
 
217
- // REVISIT: Convert checkInputConstraints to template mechanism
218
- checkInputConstraints({ element, value, errors, key: opName })
219
- }
220
-
221
- const KINDS_TO_VALIDATE = {
222
- entity: 1,
223
- type: 1
224
- }
218
+ // REVISIT: Convert checkInputConstraints to template mechanism
219
+ checkInputConstraints({ element, value, errors, key: opName })
220
+ }
225
221
 
226
222
  const _processActionFunctionRow = (row, param, key, errors, event, service) => {
227
223
  const values = Array.isArray(row[key]) ? row[key] : [row[key]]
228
-
229
224
  // unstructured
230
225
  for (const value of values) {
231
226
  checkInputConstraints({ element: param, value, errors, key })
232
227
  }
233
228
 
234
229
  // structured
235
- if (param._type && KINDS_TO_VALIDATE[param._type.kind]) {
236
- const template = getTemplate('app-input-operation', service, param._type, { pick: _pick })
237
- if (template.elements.size) {
238
- for (const value of values) {
239
- const args = { processFn: processorFnForActionsFunctions(errors, key), row: value, template }
240
- templateProcessor(args)
241
- }
230
+ const template = getTemplate('app-input-operation', service, param, {
231
+ pick: _pick
232
+ })
233
+ if (template && template.elements.size) {
234
+ for (const value of values) {
235
+ const args = { processFn: processorFnForActionsFunctions(errors, key), row: value, template }
236
+ templateProcessor(args)
242
237
  }
243
238
  }
244
239
  }
@@ -246,7 +241,8 @@ const _processActionFunctionRow = (row, param, key, errors, event, service) => {
246
241
  const _processActionFunction = (row, eventParams, errors, event, service) => {
247
242
  for (const key in eventParams) {
248
243
  let param = eventParams[key]
249
- if (!param._type && param.items) param = param.items
244
+ const _type = param.type
245
+ if (!_type && param.items) param = param.items
250
246
  _processActionFunctionRow(row, param, key, errors, event, service)
251
247
  }
252
248
  }
@@ -280,8 +276,9 @@ function _actionFunctionHandler(req) {
280
276
  // REVISIT: find better solution, maybe compiler?
281
277
  // resolve enums like format, range, etc.
282
278
  for (const param of Object.values(eventParams)) {
283
- if (param._type) {
284
- param.enum = param._type.enum
279
+ const _type = param.type && this.model && this.model.definitions[param.type]
280
+ if (_type) {
281
+ param.enum = _type.enum
285
282
  }
286
283
  }
287
284
 
@@ -25,7 +25,7 @@ const _fillStructure = (row, parts, element, category, args) => {
25
25
  const processorFn = req => {
26
26
  const REST = req.constructor.name === 'RestRequest'
27
27
 
28
- return (row, key, element, plain) => {
28
+ return ({ row, key, element, plain }) => {
29
29
  if (!row || row[key] !== undefined) return
30
30
 
31
31
  const { category, args } = plain
@@ -5,7 +5,6 @@ const DRAFT_COLUMNS = ['IsActiveEntity', 'HasDraftEntity', 'HasActiveEntity']
5
5
 
6
6
  const _getStaticOrders = req => {
7
7
  const { target: entity, query } = req
8
-
9
8
  const defaultOrders = entity['@cds.default.order'] || entity['@odata.default.order'] || []
10
9
 
11
10
  if (!cds._deprecationWarningForDefaultSort && defaultOrders.length > 0) {
@@ -60,25 +59,26 @@ const _handler = function (req) {
60
59
 
61
60
  // remove defaultOrder if not part of group by
62
61
  if (select.groupBy && select.groupBy.length > 0) {
63
- staticOrders = staticOrders.filter(d => {
64
- return select.groupBy.find(e => e.ref[0] === d.by['='])
65
- })
62
+ staticOrders = staticOrders.filter(d => select.groupBy.find(e => e.ref[0] === d.by['=']))
66
63
  }
67
64
 
65
+ if (!select.orderBy && staticOrders.length === 0) return
68
66
  select.orderBy = select.orderBy || []
67
+
69
68
  for (const defaultOrder of staticOrders) {
70
- if (
71
- !select.orderBy.some(orderBy => {
72
- const managedKey = orderBy.ref && orderBy.ref.length > 1 && orderBy.ref.join('_')
73
- const element = managedKey && req.target.elements[managedKey]
74
- const isManagedKey = element && element.key && !element.is2one
75
- // don't add duplicates
76
- return (
77
- (orderBy.ref && orderBy.ref.length === 1 && orderBy.ref[0] === defaultOrder.by['=']) ||
78
- (isManagedKey && managedKey === defaultOrder.by['='])
79
- )
80
- })
81
- ) {
69
+ const some = select.orderBy.some(orderBy => {
70
+ const managedKey = orderBy.ref && orderBy.ref.length > 1 && orderBy.ref.join('_')
71
+ const element = managedKey && req.target.elements[managedKey]
72
+ const isManagedKey = element && element.key && !element.is2one
73
+
74
+ // don't add duplicates
75
+ return (
76
+ (orderBy.ref && orderBy.ref.length === 1 && orderBy.ref[0] === defaultOrder.by['=']) ||
77
+ (isManagedKey && managedKey === defaultOrder.by['='])
78
+ )
79
+ })
80
+
81
+ if (!some) {
82
82
  const orderByItem = { ref: [defaultOrder.by['=']], sort: defaultOrder.desc ? 'desc' : 'asc' }
83
83
  select.orderBy.push(orderByItem)
84
84
  }
@@ -98,7 +98,7 @@ module.exports = (key, locale = '', args = {}) => {
98
98
 
99
99
  // for locale OR app default OR cds default
100
100
  let text = i18ns[locale][key] || i18ns[''][key] || i18ns.default[key]
101
-
101
+ if (!text) return
102
102
  // best effort replacement
103
103
  try {
104
104
  const matches = text.match(/\{[\w][\w]*\}/g) || []
@@ -71,6 +71,7 @@ ENTITY_IS_READ_ONLY=Entity "{0}" is read-only
71
71
  ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
72
72
  ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via association "{2}"
73
73
  ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitely exposed as part of the service
74
+ EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
74
75
 
75
76
  # rest protocol adapter
76
77
  INVALID_RESOURCE="{0}" is not a valid resource
@@ -87,3 +88,6 @@ CRUD_VIA_NAVIGATION_NOT_SUPPORTED=CRUD via navigations is not yet supported
87
88
  # draft
88
89
  DRAFT_ALREADY_EXISTS=A draft for this entity already exists
89
90
  DRAFT_LOCKED_BY_ANOTHER_USER=The entity is locked by another user
91
+
92
+ # singleton
93
+ SINGLETON_NOT_NULLABLE=The singleton entity is not nullable
@@ -33,15 +33,22 @@ const _getBacklinkNameFromOnCond = element => {
33
33
  }
34
34
  }
35
35
 
36
- const isBacklink = (element, parent, checkContained) => {
36
+ const isBacklink = (element, parent, checkContained, backLinkName) => {
37
37
  if (!element._isAssociationStrict) return false
38
38
  if (!parent || !(element.keys || element.on)) return false
39
39
  if (element.target !== parent.name) return false
40
40
 
41
- for (const parentElement of Object.values(parent.elements)) {
42
- if ((!checkContained || parentElement._isContained) && _getBacklinkNameFromOnCond(parentElement) === element.name) {
43
- return true
44
- }
41
+ const _isBackLink = parentElement =>
42
+ (!checkContained || parentElement._isContained) && _getBacklinkNameFromOnCond(parentElement) === element.name
43
+
44
+ if (backLinkName) {
45
+ const parentElement = parent.elements[backLinkName]
46
+ return parentElement.isAssociation && _isBackLink(parentElement)
47
+ }
48
+ for (const parentElementName in parent.elements) {
49
+ const parentElement = parent.elements[parentElementName]
50
+ if (!parentElement.isAssociation) continue
51
+ if (_isBackLink(parentElement)) return true
45
52
  }
46
53
 
47
54
  return false
@@ -12,6 +12,11 @@ const getEntityNameFromDeleteCQN = cqn => {
12
12
  return from
13
13
  }
14
14
 
15
+ const getEntityNameFromUpdateCQN = cqn => {
16
+ return (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity.name || cqn.UPDATE.entity
17
+ }
18
+
15
19
  module.exports = {
16
- getEntityNameFromDeleteCQN
20
+ getEntityNameFromDeleteCQN,
21
+ getEntityNameFromUpdateCQN
17
22
  }