@sap/cds 5.7.5 → 5.8.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 (141) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/app/fiori/routes.js +1 -1
  3. package/bin/deploy/to-hana/cfUtil.js +251 -138
  4. package/bin/deploy/to-hana/gitUtil.js +55 -0
  5. package/bin/deploy/to-hana/hana.js +92 -93
  6. package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
  7. package/bin/deploy/to-hana/index.js +14 -13
  8. package/bin/mtx/in-cds.js +1 -0
  9. package/bin/serve.js +1 -1
  10. package/bin/version.js +1 -0
  11. package/lib/compile/cdsc.js +0 -6
  12. package/lib/compile/resolve.js +1 -1
  13. package/lib/compile/to/srvinfo.js +1 -1
  14. package/lib/core/classes.js +21 -1
  15. package/lib/env/index.js +3 -2
  16. package/lib/env/requires.js +4 -0
  17. package/lib/i18n/localize.js +5 -8
  18. package/lib/index.js +1 -0
  19. package/lib/log/errors.js +1 -1
  20. package/lib/ql/SELECT.js +2 -2
  21. package/lib/req/cds-context.js +1 -1
  22. package/lib/req/context.js +1 -1
  23. package/lib/serve/Transaction.js +9 -5
  24. package/lib/serve/index.js +13 -21
  25. package/lib/utils/tests.js +90 -66
  26. package/libx/_runtime/audit/generic/personal/modification.js +0 -8
  27. package/libx/_runtime/auth/index.js +7 -6
  28. package/libx/_runtime/auth/strategies/dwc.js +43 -0
  29. package/libx/_runtime/auth/utils.js +24 -0
  30. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
  31. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
  51. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  53. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  54. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  55. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
  56. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  57. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  58. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
  60. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  61. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  62. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  63. package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
  64. package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
  65. package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
  66. package/libx/_runtime/common/aspects/Association.js +16 -0
  67. package/libx/_runtime/common/composition/data.js +28 -37
  68. package/libx/_runtime/common/composition/delete.js +107 -58
  69. package/libx/_runtime/common/composition/index.js +2 -1
  70. package/libx/_runtime/common/composition/insert.js +13 -13
  71. package/libx/_runtime/common/composition/update.js +39 -34
  72. package/libx/_runtime/common/error/frontend.js +17 -2
  73. package/libx/_runtime/common/generic/auth.js +20 -85
  74. package/libx/_runtime/common/generic/crud.js +22 -1
  75. package/libx/_runtime/common/i18n/messages.properties +2 -1
  76. package/libx/_runtime/common/utils/cqn.js +2 -6
  77. package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
  78. package/libx/_runtime/common/utils/csn.js +14 -3
  79. package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
  80. package/libx/_runtime/common/utils/keys.js +2 -1
  81. package/libx/_runtime/common/utils/path.js +1 -1
  82. package/libx/_runtime/common/utils/resolveView.js +12 -4
  83. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  84. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  85. package/libx/_runtime/common/utils/vcap.js +27 -10
  86. package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
  87. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  88. package/libx/_runtime/db/expand/expandCQNToJoin.js +8 -6
  89. package/libx/_runtime/db/expand/index.js +3 -0
  90. package/libx/_runtime/db/generic/create.js +0 -10
  91. package/libx/_runtime/db/generic/index.js +3 -0
  92. package/libx/_runtime/db/generic/read.js +2 -24
  93. package/libx/_runtime/db/generic/rewrite.js +1 -3
  94. package/libx/_runtime/db/generic/update.js +1 -1
  95. package/libx/_runtime/db/query/delete.js +10 -4
  96. package/libx/_runtime/db/query/insert.js +3 -3
  97. package/libx/_runtime/db/query/read.js +4 -1
  98. package/libx/_runtime/db/query/update.js +5 -5
  99. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  100. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  101. package/libx/_runtime/db/sql-builder/index.js +3 -0
  102. package/libx/_runtime/db/utils/columns.js +5 -2
  103. package/libx/_runtime/db/utils/deep.js +6 -8
  104. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  105. package/libx/_runtime/fiori/generic/before.js +73 -49
  106. package/libx/_runtime/fiori/generic/edit.js +14 -18
  107. package/libx/_runtime/fiori/generic/patch.js +8 -11
  108. package/libx/_runtime/fiori/generic/read.js +19 -16
  109. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  110. package/libx/_runtime/hana/Service.js +1 -1
  111. package/libx/_runtime/hana/conversion.js +10 -0
  112. package/libx/_runtime/hana/execute.js +33 -16
  113. package/libx/_runtime/hana/search.js +3 -3
  114. package/libx/_runtime/hana/search2cqn4sql.js +22 -21
  115. package/libx/_runtime/hana/searchToContains.js +1 -1
  116. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  117. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  118. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  119. package/libx/_runtime/messaging/file-based.js +3 -1
  120. package/libx/_runtime/messaging/service.js +4 -1
  121. package/libx/_runtime/remote/utils/client.js +33 -20
  122. package/libx/_runtime/remote/utils/data.js +52 -11
  123. package/libx/_runtime/sqlite/Service.js +1 -1
  124. package/libx/_runtime/sqlite/conversion.js +10 -0
  125. package/libx/_runtime/types/api.js +2 -2
  126. package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
  127. package/libx/odata/afterburner.js +29 -6
  128. package/libx/odata/cqn2odata.js +9 -0
  129. package/libx/odata/grammar.pegjs +49 -21
  130. package/libx/odata/index.js +2 -2
  131. package/libx/odata/parser.js +1 -1
  132. package/libx/odata/utils.js +2 -2
  133. package/libx/rest/RestAdapter.js +29 -1
  134. package/libx/rest/middleware/auth.js +1 -3
  135. package/libx/rest/middleware/parse.js +1 -0
  136. package/package.json +1 -1
  137. package/server.js +1 -1
  138. package/bin/deploy/to-hana/logger.js +0 -27
  139. package/bin/deploy/to-hana/runCommand.js +0 -113
  140. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
  141. package/libx/_runtime/common/utils/auth.js +0 -16
@@ -90,7 +90,8 @@ const _extractRefs = (from, as) => {
90
90
  return [ref]
91
91
  }
92
92
 
93
- const _addMapperFunction = (elements, toService, key, type, from, includeAlias) => {
93
+ // REVISIT: check why we need includeAlias for some draft cases (check AFC tests)
94
+ const _addMapperFunction = (elements, toService, key, type, alias, includeAlias) => {
94
95
  if (!toService.has(type)) {
95
96
  return
96
97
  }
@@ -99,9 +100,8 @@ const _addMapperFunction = (elements, toService, key, type, from, includeAlias)
99
100
 
100
101
  // ambiguous cases will lead to SQL syntax errors anyway, so no need for a check
101
102
  elements.set(key, convertFunction)
102
-
103
- if (includeAlias) {
104
- elements.set(`${from.as}_${key}`, convertFunction)
103
+ if (includeAlias && alias) {
104
+ elements.set(`${alias}_${key}`, convertFunction)
105
105
  }
106
106
  }
107
107
 
@@ -109,13 +109,13 @@ const _filterUnique = (value, index, arr) => {
109
109
  return arr.indexOf(value) === index
110
110
  }
111
111
 
112
- const _addMapperFunction4struct = (elements, toService, parent, struct, from, includeAlias) => {
112
+ const _addMapperFunction4struct = (elements, toService, parent, struct, alias, includeAlias) => {
113
113
  for (const k in struct.elements) {
114
114
  const current = struct.elements[k]
115
115
  if (current._isStructured) {
116
- _addMapperFunction4struct(elements, toService, `${parent}_${k}`, current, from, includeAlias)
116
+ _addMapperFunction4struct(elements, toService, `${parent}_${k}`, current, alias, includeAlias)
117
117
  } else {
118
- _addMapperFunction(elements, toService, `${parent}_${k}`, current.type, from, includeAlias)
118
+ _addMapperFunction(elements, toService, `${parent}_${k}`, current.type, alias, includeAlias)
119
119
  }
120
120
  }
121
121
  }
@@ -126,7 +126,6 @@ const _addMapperFunction4struct = (elements, toService, parent, struct, from, in
126
126
  * @param {Map} toService - Mapping instructions for data conversions based on CDS data types
127
127
  * @param {object} csn - Reflected CSN
128
128
  * @param {object} cqn - CQN that is used to query the DB.
129
- * @param {boolean} [includeAlias] - Include mapping for aliases. Defaults to false.
130
129
  * @returns {Map<any, any>}
131
130
  * @private
132
131
  */
@@ -142,13 +141,16 @@ const _getElementCombinations = (toService, csn, cqn, includeAlias = false) => {
142
141
  }
143
142
 
144
143
  const entity = csn.definitions[ensureUnlocalized(entityName)]
145
-
146
144
  for (const key in entity.elements) {
147
145
  const element = entity.elements[key]
148
146
  if (element._isStructured) {
149
- _addMapperFunction4struct(elements, toService, key, element, from, includeAlias)
147
+ _addMapperFunction4struct(elements, toService, key, element, from.as, includeAlias)
150
148
  } else {
151
- _addMapperFunction(elements, toService, key, element.type, from, includeAlias)
149
+ if ('SET' in cqn.SELECT.from) {
150
+ _addMapperFunction(elements, toService, key, element.type, cqn.SELECT.from.as, includeAlias)
151
+ } else {
152
+ _addMapperFunction(elements, toService, key, element.type, from.as, includeAlias)
153
+ }
152
154
  }
153
155
  }
154
156
  }
@@ -182,15 +184,17 @@ const _getMapperForListedElements = (toService, csn, cqn) => {
182
184
 
183
185
  for (const element of cqn.SELECT.columns) {
184
186
  if (element.ref) {
185
- const identifier = element.ref[element.ref.length - 1]
187
+ const identifier = element.ref.length === 1 ? element.ref[0] : undefined
186
188
  const name = element.as ? element.as : identifier
187
189
 
188
190
  if (element.cast) {
189
191
  mapper.set(name, _getCastFunction(element.cast))
190
192
  } else if (elements.has(name)) {
191
193
  mapper.set(name, elements.get(name))
192
- } else if (elements.has(identifier) && !cqn.SELECT.from.args) {
194
+ } else if (elements.has(identifier)) {
193
195
  mapper.set(name, elements.get(identifier))
196
+ } else if (elements.has(element.ref.join('_'))) {
197
+ mapper.set(name || element.ref[element.ref.length - 1], elements.get(element.ref.join('_')))
194
198
  }
195
199
  } else if (element.as && element.cast) {
196
200
  mapper.set(element.as, _getCastFunction(element.cast))
@@ -514,6 +518,9 @@ const getPropertyMapper = (csn, cqn) => {
514
518
  return new Map()
515
519
  }
516
520
 
521
+ /*
522
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
523
+ */
517
524
  module.exports = {
518
525
  getPropertyMapper,
519
526
  getPostProcessMapper,
@@ -11,9 +11,10 @@ const _removeParentKeysFromRow = (row, prefix, keys) => {
11
11
  }
12
12
  }
13
13
 
14
- const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth) => {
14
+ const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth, options) => {
15
15
  for (const nav in entity._associations) {
16
16
  const navigation = entity._associations[nav]
17
+ if (options.onlyCompositions && navigation._isAssociationEffective) continue
17
18
 
18
19
  // do not expand backlinks
19
20
  if (navigation._isBacklink) continue
@@ -26,7 +27,9 @@ const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth) =
26
27
  .on(entity._relations[navigation.name].join(childAlias, parentAlias))
27
28
 
28
29
  // set alias for expanded columns already
29
- const childColumns = getColumns(navigation._target).map(c => ({ ref: [childAlias, c.name] }))
30
+ const childColumns = getColumns(navigation._target, { _4db: true, onlyKeys: options.onlyKeys }).map(c => ({
31
+ ref: [childAlias, c.name]
32
+ }))
30
33
  const parentKeys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation)
31
34
  // mark parent key with prefix in alias
32
35
  const parentKeysWithAlias = parentKeys.map(pk => ({ ref: [parentAlias, pk], as: `$$pk_${pk}` }))
@@ -69,16 +72,17 @@ const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth) =
69
72
 
70
73
  // expand next level if needed
71
74
  if (depth - 1 !== 0 && result.length && navigation._target._associations) {
72
- await _autoExpandNavsAndAttachToResult(navigation._target, result, depth - 1)
75
+ await _autoExpandNavsAndAttachToResult(navigation._target, result, depth - 1, options)
73
76
  }
74
77
  }
75
78
 
76
79
  return previousResult
77
80
  }
78
81
 
79
- const _foreignKeysOfTopLevelNavs = entity => {
82
+ const _foreignKeysOfTopLevelNavs = (entity, options) => {
80
83
  const requiredFks = new Set()
81
84
  for (const nav in entity._associations) {
85
+ if (options.onlyCompositions && entity._associations[nav]._isAssociationEffective) continue
82
86
  const onCond = entity._relations[nav].join('child', 'parent')
83
87
  for (const ele of onCond) {
84
88
  if (ele.ref && ele.ref[0] === 'parent') {
@@ -89,6 +93,15 @@ const _foreignKeysOfTopLevelNavs = entity => {
89
93
  return [...requiredFks]
90
94
  }
91
95
 
96
+ const _addForeignKeys = (columns, entity, options) => {
97
+ const fks = _foreignKeysOfTopLevelNavs(entity, options)
98
+ fks.forEach(fk => {
99
+ if (!columns.some(c => c.ref[0] === fk)) {
100
+ columns.push({ ref: [fk] })
101
+ }
102
+ })
103
+ }
104
+
92
105
  /**
93
106
  * 1. Creates flattened SQL statements for each expand layer
94
107
  * 2. Mixes in foreign keys if needed
@@ -98,18 +111,15 @@ const _foreignKeysOfTopLevelNavs = entity => {
98
111
  * @returns object
99
112
  */
100
113
  const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSelectCQN) => {
114
+ const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand === 'string')
115
+ const options = Object.assign({ onlyKeys: false, onlyCompositions: false }, expandColumn._options)
101
116
  // remove expand columns from query without modifying
102
117
  const topLevelSelect = query.clone().columns(query.SELECT.columns.filter(c => !c.expand))
103
118
 
104
119
  const entity = model.definitions[topLevelSelect.SELECT.from.ref[0]]
105
120
 
106
121
  // ensure foreign keys are selected if needed
107
- const fks = _foreignKeysOfTopLevelNavs(entity)
108
- fks.forEach(fk => {
109
- if (!topLevelSelect.SELECT.columns.some(c => c.ref[0] === fk)) {
110
- topLevelSelect.SELECT.columns.push({ ref: [fk] })
111
- }
112
- })
122
+ _addForeignKeys(topLevelSelect.SELECT.columns, entity, options)
113
123
 
114
124
  const result = await executeSelectCQN(model, dbc, topLevelSelect, user, locale, txTimestamp)
115
125
 
@@ -119,9 +129,8 @@ const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSel
119
129
 
120
130
  // _associations contains compositions and associations
121
131
  if (entity._associations) {
122
- const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand === 'string')
123
132
  const depth = expandColumn.expand === '**' ? -1 : Number(expandColumn.expand.replace('*', ''))
124
- await _autoExpandNavsAndAttachToResult(entity, Array.isArray(result) ? result : [result], depth)
133
+ await _autoExpandNavsAndAttachToResult(entity, Array.isArray(result) ? result : [result], depth, options)
125
134
  }
126
135
 
127
136
  return result
@@ -2,10 +2,15 @@ const cds = require('../../cds')
2
2
 
3
3
  const { getAllKeys } = require('../../cds-services/adapter/odata-v4/odata-to-cqn/utils')
4
4
 
5
+ const { deepCopyObject } = require('../../common/utils/copy')
5
6
  const { getNavigationIfStruct } = require('../../common/utils/structured')
6
7
  const { ensureNoDraftsSuffix, ensureDraftsSuffix, ensureUnlocalized } = require('../../common/utils/draft')
7
- const { filterKeys } = require('../../fiori/utils/handler')
8
8
  const { isAsteriskColumn } = require('../../common/utils/rewriteAsterisks')
9
+ const { getCQNUnionFrom } = require('../../common/utils/union')
10
+
11
+ const { DRAFT_COLUMNS } = require('../../common/constants/draft')
12
+
13
+ const { filterKeys } = require('../../fiori/utils/handler')
9
14
 
10
15
  // Symbols are used to add extra information in response structure
11
16
  const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
@@ -16,12 +21,9 @@ const IDENTIFIER = Symbol.for('sap.cds.identifier')
16
21
  const IS_ACTIVE = Symbol.for('sap.cds.isActive')
17
22
  const IS_UNION_DRAFT = Symbol.for('sap.cds.isUnionDraft')
18
23
 
19
- const { DRAFT_COLUMNS } = require('../../common/constants/draft')
20
-
21
- const { getCQNUnionFrom } = require('../../common/utils/union')
22
-
23
24
  function getCqnCopy(readToOneCQN) {
24
- const readToOneCQNCopy = JSON.parse(JSON.stringify(readToOneCQN))
25
+ // REVISIT: Use query.clone() instead
26
+ const readToOneCQNCopy = deepCopyObject(readToOneCQN)
25
27
  if (readToOneCQN[GET_KEY_VALUE] !== undefined) readToOneCQNCopy[GET_KEY_VALUE] = readToOneCQN[GET_KEY_VALUE]
26
28
  if (readToOneCQN[TO_MANY] !== undefined) readToOneCQNCopy[TO_MANY] = readToOneCQN[TO_MANY]
27
29
  if (readToOneCQN[TO_ACTIVE] !== undefined) readToOneCQNCopy[TO_ACTIVE] = readToOneCQN[TO_ACTIVE]
@@ -2,6 +2,9 @@ const { hasExpand, createJoinCQNFromExpanded } = require('./expandCQNToJoin')
2
2
  const rawToExpanded = require('./rawToExpanded')
3
3
  const expandV2 = require('./expand-v2')
4
4
 
5
+ /*
6
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
7
+ */
5
8
  module.exports = {
6
9
  hasExpand,
7
10
  createJoinCQNFromExpanded,
@@ -15,16 +15,6 @@ module.exports = async function (req) {
15
15
  }
16
16
 
17
17
  try {
18
- // REVISIT: should be handled in protocol adapter
19
- // execute validation query first to fail early
20
- if (req.query._validationQuery) {
21
- const validationResult = await this._read(this.model, this.dbc, req.query._validationQuery, req)
22
-
23
- if (validationResult.length === 0) {
24
- // > validation target (e.g., root of navigation) doesn't exist
25
- req.reject(404)
26
- }
27
- }
28
18
  const results = await this._insert(this.model, this.dbc, req.query, req)
29
19
  return new InsertResult(req, results)
30
20
  } catch (err) {
@@ -12,6 +12,9 @@ const DELETE = require('./delete')
12
12
  const structured = require('./structured')
13
13
  const arrayed = require('./arrayed')
14
14
 
15
+ /*
16
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
17
+ */
15
18
  module.exports = {
16
19
  rewrite,
17
20
  virtual,
@@ -7,32 +7,10 @@
7
7
  *
8
8
  * @param req - cds.Request
9
9
  */
10
- module.exports = async function (req) {
10
+ module.exports = function (req) {
11
11
  if (typeof req.query === 'string') {
12
12
  return this._execute.sql(this.dbc, req.query, req.data)
13
13
  }
14
14
 
15
- // REVISIT: should be handled in protocol adapter
16
- // execute validation query first to fail early
17
- if (req.query._validationQuery) {
18
- const validationResult = await this._read(this.model, this.dbc, req.query._validationQuery, req)
19
-
20
- if (validationResult.length === 0) {
21
- // > validation target (e.g., root of navigation) doesn't exist
22
- req.reject(404)
23
- }
24
- }
25
-
26
- const result = await this._read(this.model, this.dbc, req.query, req)
27
-
28
- if (
29
- req.query._validationQuery &&
30
- req.query._validationQuery.__navToManyWithKeys &&
31
- (!result || result.length === 0)
32
- ) {
33
- // > navigation to collection with key specified without result -> 404
34
- req.reject(404)
35
- }
36
-
37
- return result
15
+ return this._read(this.model, this.dbc, req.query, req)
38
16
  }
@@ -1,5 +1,5 @@
1
1
  const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
2
- const generateAliases = require('../utils/generateAliases')
2
+ const { generateAliases } = require('../utils/generateAliases')
3
3
  const { restoreLink } = require('../../common/utils/resolveView')
4
4
 
5
5
  const _isLinked = req => {
@@ -18,7 +18,6 @@ function handler(req) {
18
18
  }
19
19
 
20
20
  const streaming = req.query._streaming
21
- const validationQuery = req.query._validationQuery
22
21
 
23
22
  // for restore link to req.data
24
23
  const linked = _isLinked(req)
@@ -31,7 +30,6 @@ function handler(req) {
31
30
  if (linked) restoreLink(req)
32
31
 
33
32
  if (streaming) req.query._streaming = streaming
34
- if (validationQuery) req.query._validationQuery = validationQuery
35
33
 
36
34
  generateAliases(req.query)
37
35
  }
@@ -70,7 +70,7 @@ module.exports = async function (req) {
70
70
  if (onlyKeysRemain(req)) return
71
71
 
72
72
  try {
73
- const result = await this._update(this.model, this.dbc, req.query, req)
73
+ const result = await this._update(this.model, this.dbc, req)
74
74
  return result
75
75
  } catch (err) {
76
76
  // REVISIT: db specifics
@@ -1,14 +1,14 @@
1
1
  const { getFlatArray, processCQNs } = require('../utils/deep')
2
2
  const { timestampToISO } = require('../data-conversion/timestamp')
3
- const { hasDeepDelete, getDeepDeleteCQNs } = require('../../common/composition')
3
+ const { hasDeepDelete, getDeepDeleteCQNs, getSetNullParentForeignKeyCQNs } = require('../../common/composition')
4
4
 
5
- const deleteFn = executeDeleteCQN => async (model, dbc, query, req) => {
5
+ const deleteFn = (executeDeleteCQN, executeUpdateCQN) => async (model, dbc, query, req) => {
6
6
  const { user, locale, timestamp } = req
7
7
  const isoTs = timestampToISO(timestamp)
8
8
 
9
9
  let result
10
- if (hasDeepDelete(model && model.definitions, query)) {
11
- let cqns = getDeepDeleteCQNs(model && model.definitions, query)
10
+ if (hasDeepDelete(model, query)) {
11
+ let cqns = await getDeepDeleteCQNs(model, req)
12
12
 
13
13
  // the delete chunks, i.e., how many deletes can be processed in parallel
14
14
  const chunks = []
@@ -25,6 +25,12 @@ const deleteFn = executeDeleteCQN => async (model, dbc, query, req) => {
25
25
  result = results[results.length - 1]
26
26
  } else {
27
27
  result = await executeDeleteCQN(model, dbc, query, user, locale, isoTs)
28
+ if (result) {
29
+ const updateCQNs = await getSetNullParentForeignKeyCQNs(model, req)
30
+ for (const cqn of updateCQNs) {
31
+ await executeUpdateCQN(model, dbc, cqn, user, locale, isoTs)
32
+ }
33
+ }
28
34
  }
29
35
 
30
36
  return result
@@ -6,8 +6,8 @@ const insert = executeInsertCQN => async (model, dbc, query, req) => {
6
6
  const { user, locale, timestamp } = req
7
7
  const isoTs = timestampToISO(timestamp)
8
8
 
9
- if (hasDeepInsert(model && model.definitions, query)) {
10
- const cqns = getFlatArray(getDeepInsertCQNs(model && model.definitions, query))
9
+ if (hasDeepInsert(model, query)) {
10
+ const cqns = getFlatArray(getDeepInsertCQNs(model, query))
11
11
 
12
12
  // return array of individual results
13
13
  if (cqns.length === 0) return []
@@ -15,7 +15,7 @@ const insert = executeInsertCQN => async (model, dbc, query, req) => {
15
15
  return getFlatArray(results)
16
16
  }
17
17
 
18
- cleanEmptyCompositionsOfMany(model && model.definitions, query)
18
+ cleanEmptyCompositionsOfMany(model, query)
19
19
  return executeInsertCQN(model, dbc, query, user, locale, isoTs)
20
20
  }
21
21
 
@@ -1,5 +1,7 @@
1
1
  const { timestampToISO } = require('../data-conversion/timestamp')
2
2
 
3
+ const { deepCopyObject } = require('../../common/utils/copy')
4
+
3
5
  function _arrayWithCount(a, count) {
4
6
  const _map = a.map
5
7
  const map = (..._) => _arrayWithCount(_map.call(a, ..._), count)
@@ -10,7 +12,8 @@ function _arrayWithCount(a, count) {
10
12
  }
11
13
 
12
14
  function _createCountQuery(query) {
13
- let _query = JSON.parse(JSON.stringify(query)) // REVISIT: Use query.clone() instead
15
+ // REVISIT: Use query.clone() instead
16
+ let _query = { SELECT: deepCopyObject(query.SELECT) }
14
17
  delete _query.SELECT.orderBy // not necessary to keep that
15
18
  delete _query.SELECT.limit
16
19
  // Also change columns in sub queries
@@ -50,11 +50,11 @@ const _getFilteredCqns = (cqns, model) => {
50
50
  return cqns
51
51
  }
52
52
 
53
- const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query, req) => {
54
- const { user, locale, timestamp } = req
53
+ const update = executeUpdateCQN => async (model, dbc, req) => {
54
+ const { query, user, locale, timestamp } = req
55
55
  const isoTs = timestampToISO(timestamp)
56
56
 
57
- if (hasDeepUpdate(model && model.definitions, query)) {
57
+ if (hasDeepUpdate(model, query)) {
58
58
  // REVISIT: _activeData gets set in case of draftActivate for performance, but this is a layer violation
59
59
  let selectData = req._ && req._.query && req._.query._activeData
60
60
 
@@ -62,10 +62,10 @@ const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query,
62
62
  selectData = [selectData]
63
63
  } else {
64
64
  // REVISIT: avoid additional read
65
- selectData = await selectDeepUpdateData(model && model.definitions, query, req, false, false, cds.db)
65
+ selectData = await selectDeepUpdateData(cds.db, model, req)
66
66
  }
67
67
 
68
- let cqns = getDeepUpdateCQNs(model && model.definitions, query, selectData)
68
+ let cqns = await getDeepUpdateCQNs(model, req, selectData)
69
69
 
70
70
  // the delete chunks, i.e., how many deletes can be processed in parallel
71
71
  const chunks = []
@@ -11,6 +11,10 @@ function _fillAfterDot(val) {
11
11
  return `${beforeDot}.${afterDot.padEnd(3, '0')}`
12
12
  }
13
13
 
14
+ function _valButNoBuffer(arg) {
15
+ return arg && arg.val && typeof arg.val === 'object' && !(arg.val instanceof Buffer)
16
+ }
17
+
14
18
  /**
15
19
  * ExpressionBuilder is used to take a part of a CQN object as an input and to build an object representing an expression
16
20
  * with SQL string and values to be used with a prepared statement.
@@ -87,9 +91,9 @@ class ExpressionBuilder extends BaseBuilder {
87
91
  }
88
92
 
89
93
  _isStructured(op1, comp, op2) {
90
- if (op1.ref && comp === '=' && op2.val && typeof op2.val === 'object' && !(op2.val instanceof Buffer)) return true
94
+ if (op1.ref && comp === '=' && _valButNoBuffer(op2)) return true
91
95
  // also check reverse
92
- if (op1.val && typeof op1.val === 'object' && comp === '=' && op2.ref && !(op1.val instanceof Buffer)) return true
96
+ if (_valButNoBuffer(op1) && comp === '=' && op2.ref) return true
93
97
  }
94
98
 
95
99
  _expressionObjectsToSQL(objects) {
@@ -379,4 +383,7 @@ class ExpressionBuilder extends BaseBuilder {
379
383
  }
380
384
  }
381
385
 
386
+ /*
387
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
388
+ */
382
389
  module.exports = ExpressionBuilder
@@ -214,4 +214,7 @@ class FunctionBuilder extends BaseBuilder {
214
214
  }
215
215
  }
216
216
 
217
+ /*
218
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
219
+ */
217
220
  module.exports = FunctionBuilder
@@ -9,6 +9,9 @@ const ReferenceBuilder = require('./ReferenceBuilder')
9
9
  const FunctionBuilder = require('./FunctionBuilder')
10
10
  const sqlFactory = require('./sqlFactory')
11
11
 
12
+ /*
13
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
14
+ */
12
15
  module.exports = {
13
16
  CreateBuilder,
14
17
  DropBuilder,
@@ -11,7 +11,7 @@ const { DRAFT_COLUMNS } = require('../../common/constants/draft')
11
11
  * @param entity - the csn entity
12
12
  * @returns {Array} - array of columns
13
13
  */
14
- const getColumns = (entity, { db, onlyKeys } = { db: true, onlyKeys: false }) => {
14
+ const getColumns = (entity, { _4db, onlyKeys } = { _4db: true, onlyKeys: false }) => {
15
15
  // REVISIT is this correct or just a problem that occurs because of new structure we do not deal with yet?
16
16
  if (!(entity && entity.elements)) return []
17
17
  const columnNames = []
@@ -21,7 +21,7 @@ const getColumns = (entity, { db, onlyKeys } = { db: true, onlyKeys: false }) =>
21
21
  const element = elements[elementName]
22
22
  if (onlyKeys && !element.key) continue
23
23
  if (element.isAssociation) continue
24
- if (db && entity._isDraftEnabled && DRAFT_COLUMNS.includes(elementName)) continue
24
+ if (_4db && entity._isDraftEnabled && DRAFT_COLUMNS.includes(elementName)) continue
25
25
  if (cds.env.effective.odata.structs && element.elements) {
26
26
  columnNames.push(...resolveStructured({ structName: elementName, structProperties: [] }, element.elements, false))
27
27
  continue
@@ -31,4 +31,7 @@ const getColumns = (entity, { db, onlyKeys } = { db: true, onlyKeys: false }) =>
31
31
  return columnNames.map(name => elements[name] || { name })
32
32
  }
33
33
 
34
+ /*
35
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
36
+ */
34
37
  module.exports = getColumns
@@ -28,20 +28,14 @@ async function processCQNs(processFn, cqns, model, dbc, user, locale, ts, chunks
28
28
  const results = new Array(cqns.length)
29
29
 
30
30
  const deletes = []
31
- const updatesBeforeDelete = []
31
+ const updatesForDeletes = []
32
32
  const others = []
33
33
  for (let i = 0; i < cqns.length; i++) {
34
34
  if (cqns[i].DELETE) deletes.push(i)
35
- else if (cqns[i].UPDATE && cqns[i].UPDATE._beforeDelete) updatesBeforeDelete.push(i)
35
+ else if (cqns[i].__4delete) updatesForDeletes.push(i)
36
36
  else others.push(i)
37
37
  }
38
38
 
39
- // UPDATEs to SET null parent's foreign keys of one compositions
40
- // which are otherwise violate foreign key constraints
41
- if (updatesBeforeDelete.length) {
42
- await _processChunk(processFn, model, dbc, cqns, user, locale, ts, updatesBeforeDelete, results)
43
- }
44
-
45
39
  if (deletes.length > 0) {
46
40
  if (chunks) {
47
41
  let offset = 0
@@ -55,6 +49,10 @@ async function processCQNs(processFn, cqns, model, dbc, user, locale, ts, chunks
55
49
  }
56
50
  }
57
51
 
52
+ if (updatesForDeletes.length > 0) {
53
+ await _processChunk(processFn, model, dbc, cqns, user, locale, ts, updatesForDeletes, results)
54
+ }
55
+
58
56
  if (others.length > 0) {
59
57
  await _processChunk(processFn, model, dbc, cqns, user, locale, ts, others, results)
60
58
  }
@@ -1,10 +1,9 @@
1
1
  const { ensureUnlocalized } = require('../../common/utils/draft')
2
-
3
2
  const ALIAS_PREFIX = 'ALIAS_'
4
-
5
- const cleanUpName = name => {
6
- return ensureUnlocalized(name).replace(/\./g, '_')
7
- }
3
+ const PARENT_ALIAS = '_parent_'
4
+ const FOREIGN_ALIAS = '_foreign_'
5
+ const PARENT_ALIAS_REGEX = new RegExp('^' + PARENT_ALIAS + '\\d*$')
6
+ const cleanUpName = name => ensureUnlocalized(name).replace(/\./g, '_')
8
7
 
9
8
  const _redirectXpr = (xpr, aliasMap) => {
10
9
  if (!xpr) return
@@ -97,4 +96,55 @@ const generateAliases = query => {
97
96
  _generateAliases(query)
98
97
  }
99
98
 
100
- module.exports = generateAliases
99
+ const _addParentAlias = (where, alias) => {
100
+ where.forEach(e => {
101
+ if (e.ref && e.ref[0].match(PARENT_ALIAS_REGEX)) {
102
+ e.ref[0] = alias
103
+ }
104
+ })
105
+ }
106
+
107
+ const _addAliasToElement = (expr, alias) => {
108
+ if (expr.ref) {
109
+ if (typeof alias === 'function') {
110
+ alias = alias(expr.ref)
111
+ }
112
+
113
+ return { ref: [alias, ...expr.ref] }
114
+ }
115
+
116
+ if (expr.list) {
117
+ return { list: expr.list.map(arg => _addAliasToElement(arg, alias)) }
118
+ }
119
+
120
+ if (expr.func) {
121
+ const args = expr.args.map(arg => _addAliasToElement(arg, alias))
122
+ return { ...expr, args }
123
+ }
124
+
125
+ if (expr.SELECT && expr.SELECT.where) {
126
+ // special case in lambda functions
127
+ _addParentAlias(expr.SELECT.where, alias)
128
+ }
129
+
130
+ if (expr.xpr) {
131
+ return { xpr: expr.xpr.map(xpr => _addAliasToElement(xpr, alias)) }
132
+ }
133
+
134
+ return expr
135
+ }
136
+
137
+ const addAliasToExpression = (expression, alias) => {
138
+ if (expression && alias) {
139
+ return expression.map(expr => _addAliasToElement(expr, alias))
140
+ }
141
+
142
+ return expression
143
+ }
144
+
145
+ module.exports = {
146
+ generateAliases,
147
+ addAliasToExpression,
148
+ PARENT_ALIAS,
149
+ FOREIGN_ALIAS
150
+ }