@sap/cds 6.8.4 → 7.0.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 (214) hide show
  1. package/CHANGELOG.md +58 -5
  2. package/README.md +0 -1
  3. package/bin/cds-serve.js +50 -3
  4. package/bin/deploy/to-hana.js +1 -0
  5. package/bin/serve.js +16 -20
  6. package/lib/auth/basic-auth.js +6 -4
  7. package/lib/auth/index.js +4 -3
  8. package/lib/auth/jwt-auth.js +2 -5
  9. package/lib/compile/cds-compile.js +34 -89
  10. package/lib/compile/cdsc.js +11 -0
  11. package/lib/compile/etc/properties.js +2 -2
  12. package/lib/compile/for/lean_drafts.js +36 -69
  13. package/lib/compile/for/nodejs.js +2 -1
  14. package/lib/compile/load.js +1 -1
  15. package/lib/compile/minify.js +2 -0
  16. package/lib/compile/to/csn.js +74 -0
  17. package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
  18. package/lib/compile/to/json.js +1 -1
  19. package/lib/compile/to/sql.js +8 -6
  20. package/lib/dbs/cds-deploy.js +174 -114
  21. package/lib/env/cds-env.js +64 -79
  22. package/lib/env/cds-requires.js +11 -28
  23. package/lib/env/defaults.js +13 -3
  24. package/lib/env/plugins.js +1 -12
  25. package/lib/env/presets.js +25 -21
  26. package/lib/index.js +121 -147
  27. package/lib/{core/reflect.js → linked/models.js} +2 -2
  28. package/lib/{core/infer.js → linked/queries.js} +2 -0
  29. package/lib/{core/index.js → linked/types.js} +2 -1
  30. package/lib/log/cds-error.js +13 -7
  31. package/lib/log/format/cf.js +1 -1
  32. package/lib/plugins.js +49 -0
  33. package/lib/ql/Query.js +0 -9
  34. package/lib/ql/STREAM.js +0 -1
  35. package/lib/req/context.js +2 -7
  36. package/lib/req/request.js +6 -2
  37. package/lib/req/response.js +23 -10
  38. package/lib/srv/middlewares/ctx-model.js +1 -1
  39. package/lib/srv/middlewares/errors.js +1 -1
  40. package/lib/srv/protocols/_legacy.js +1 -0
  41. package/lib/srv/protocols/graphql.js +7 -16
  42. package/lib/srv/protocols/index.js +59 -45
  43. package/lib/srv/protocols/odata-v2-proxy.js +2 -70
  44. package/lib/srv/srv-api.js +9 -3
  45. package/lib/srv/srv-dispatch.js +12 -9
  46. package/lib/srv/srv-models.js +4 -21
  47. package/lib/srv/srv-tx.js +15 -12
  48. package/lib/utils/cds-test.js +14 -9
  49. package/lib/utils/cds-utils.js +2 -12
  50. package/lib/utils/check-version.js +17 -0
  51. package/{bin/build → lib/utils}/csv-reader.js +23 -24
  52. package/libx/_runtime/auth/index.js +27 -23
  53. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
  54. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
  56. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
  57. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
  58. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
  59. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
  60. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
  61. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
  62. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
  63. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
  64. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
  66. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
  68. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
  70. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
  71. package/libx/_runtime/cds-services/services/Service.js +79 -107
  72. package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
  73. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
  74. package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
  75. package/libx/_runtime/cds-services/util/assert.js +65 -2
  76. package/libx/_runtime/common/composition/data.js +1 -0
  77. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  78. package/libx/_runtime/common/generic/auth/restrict.js +5 -10
  79. package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
  80. package/libx/_runtime/common/generic/auth/utils.js +1 -2
  81. package/libx/_runtime/common/generic/crud.js +32 -16
  82. package/libx/_runtime/common/generic/etag.js +133 -104
  83. package/libx/_runtime/common/generic/input.js +6 -21
  84. package/libx/_runtime/common/generic/put.js +1 -1
  85. package/libx/_runtime/common/generic/stream.js +52 -0
  86. package/libx/_runtime/common/generic/temporal.js +25 -8
  87. package/libx/_runtime/common/i18n/messages.properties +0 -2
  88. package/libx/_runtime/common/utils/cqn.js +1 -1
  89. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  90. package/libx/_runtime/common/utils/csn.js +0 -51
  91. package/libx/_runtime/common/utils/etag.js +30 -0
  92. package/libx/_runtime/common/utils/keys.js +1 -1
  93. package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
  94. package/libx/_runtime/common/utils/path.js +1 -1
  95. package/libx/_runtime/common/utils/resolveView.js +2 -1
  96. package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
  97. package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
  98. package/libx/_runtime/common/utils/stream.js +140 -0
  99. package/libx/_runtime/common/utils/streamProp.js +29 -12
  100. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
  101. package/libx/_runtime/db/generic/index.js +0 -2
  102. package/libx/_runtime/db/query/delete.js +2 -2
  103. package/libx/_runtime/db/query/insert.js +2 -2
  104. package/libx/_runtime/db/query/read.js +2 -2
  105. package/libx/_runtime/db/query/run.js +2 -2
  106. package/libx/_runtime/db/query/update.js +2 -2
  107. package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
  108. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
  109. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
  110. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
  111. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
  112. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
  113. package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
  114. package/libx/_runtime/fiori/draft.js +2 -0
  115. package/libx/_runtime/fiori/generic/activate.js +8 -9
  116. package/libx/_runtime/fiori/generic/before.js +30 -20
  117. package/libx/_runtime/fiori/generic/cancel.js +5 -3
  118. package/libx/_runtime/fiori/generic/delete.js +5 -3
  119. package/libx/_runtime/fiori/generic/edit.js +7 -7
  120. package/libx/_runtime/fiori/generic/index.js +10 -16
  121. package/libx/_runtime/fiori/generic/new.js +5 -3
  122. package/libx/_runtime/fiori/generic/patch.js +11 -8
  123. package/libx/_runtime/fiori/generic/prepare.js +13 -6
  124. package/libx/_runtime/fiori/generic/read.js +12 -6
  125. package/libx/_runtime/fiori/lean-draft.js +207 -152
  126. package/libx/_runtime/fiori/utils/delete.js +10 -5
  127. package/libx/_runtime/fiori/utils/req.js +17 -5
  128. package/libx/_runtime/fiori/utils/stream.js +36 -0
  129. package/libx/_runtime/hana/Service.js +12 -9
  130. package/libx/_runtime/hana/conversion.js +10 -15
  131. package/libx/_runtime/hana/driver.js +2 -0
  132. package/libx/_runtime/hana/execute.js +28 -6
  133. package/libx/_runtime/hana/pool.js +36 -122
  134. package/libx/_runtime/hana/search2cqn4sql.js +34 -36
  135. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
  136. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
  137. package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
  138. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  139. package/libx/_runtime/remote/Service.js +20 -1
  140. package/libx/_runtime/remote/utils/client.js +3 -5
  141. package/libx/_runtime/sqlite/Service.js +4 -6
  142. package/libx/_runtime/sqlite/conversion.js +3 -13
  143. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
  144. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
  145. package/libx/_runtime/sqlite/execute.js +5 -16
  146. package/libx/odata/afterburner.js +22 -6
  147. package/libx/odata/grammar.pegjs +6 -1
  148. package/libx/odata/parser.js +1 -1
  149. package/libx/rest/RestAdapter.js +16 -9
  150. package/libx/rest/RestRequest.js +1 -1
  151. package/libx/rest/middleware/input.js +2 -1
  152. package/libx/rest/middleware/operation.js +1 -0
  153. package/libx/rest/middleware/parse.js +3 -2
  154. package/libx/rest/middleware/payload.js +9 -8
  155. package/libx/rest/middleware/read.js +1 -0
  156. package/package.json +9 -16
  157. package/app/fiori/preview.js +0 -270
  158. package/app/fiori/routes.js +0 -59
  159. package/bin/build/buildTaskEngine.js +0 -360
  160. package/bin/build/buildTaskFactory.js +0 -283
  161. package/bin/build/buildTaskHandler.js +0 -241
  162. package/bin/build/buildTaskProvider.js +0 -22
  163. package/bin/build/buildTaskProviderFactory.js +0 -175
  164. package/bin/build/cds.js +0 -5
  165. package/bin/build/constants.js +0 -66
  166. package/bin/build/index.js +0 -58
  167. package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
  168. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
  169. package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
  170. package/bin/build/provider/buildTaskProviderInternal.js +0 -383
  171. package/bin/build/provider/fiori/index.js +0 -171
  172. package/bin/build/provider/hana/2migration.js +0 -179
  173. package/bin/build/provider/hana/index.js +0 -505
  174. package/bin/build/provider/hana/migrationtable.js +0 -472
  175. package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
  176. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
  177. package/bin/build/provider/hana/template/.hdinamespace +0 -4
  178. package/bin/build/provider/hana/template/package.json +0 -12
  179. package/bin/build/provider/hana/template/undeploy.json +0 -5
  180. package/bin/build/provider/java/index.js +0 -111
  181. package/bin/build/provider/java-cf/index.js +0 -1
  182. package/bin/build/provider/mtx/index.js +0 -268
  183. package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
  184. package/bin/build/provider/mtx-extension/index.js +0 -131
  185. package/bin/build/provider/mtx-sidecar/index.js +0 -137
  186. package/bin/build/provider/node-cf/index.js +0 -1
  187. package/bin/build/provider/nodejs/index.js +0 -192
  188. package/bin/build/util.js +0 -299
  189. package/bin/cds.js +0 -125
  190. package/bin/deploy/to-hana/cfUtil.js +0 -355
  191. package/bin/deploy/to-hana/gitUtil.js +0 -57
  192. package/bin/deploy/to-hana/hana.js +0 -306
  193. package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
  194. package/bin/deploy/to-hana/index.js +0 -16
  195. package/bin/deploy/to-hana/mtaUtil.js +0 -170
  196. package/bin/mtx/in-cds.js +0 -17
  197. package/bin/plugins.js +0 -32
  198. package/bin/run.js +0 -24
  199. package/bin/utils/log.js +0 -24
  200. package/bin/version.js +0 -178
  201. package/libx/_runtime/audit/Service.js +0 -222
  202. package/libx/_runtime/audit/generic/personal/access.js +0 -61
  203. package/libx/_runtime/audit/generic/personal/index.js +0 -56
  204. package/libx/_runtime/audit/generic/personal/modification.js +0 -132
  205. package/libx/_runtime/audit/generic/personal/utils.js +0 -186
  206. package/libx/_runtime/audit/utils/log.js +0 -23
  207. package/libx/_runtime/audit/utils/v2.js +0 -176
  208. package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
  209. package/libx/_runtime/db/generic/integrity.js +0 -455
  210. package/srv/audit-log.cds +0 -87
  211. package/srv/mtx.cds +0 -2
  212. package/srv/mtx.js +0 -8
  213. /package/lib/{core → linked}/classes.js +0 -0
  214. /package/lib/{core → linked}/entities.js +0 -0
@@ -0,0 +1,30 @@
1
+ const { isAsteriskColumn } = require('./rewriteAsterisks')
2
+
3
+ /**
4
+ * Recursively adds etag columns if a manual list of columns is specified.
5
+ * If asterisk columns or no columns are given, the database layer will
6
+ * add the etag columns anyway.
7
+ */
8
+ const addEtagColumns = (columns, entity) => {
9
+ if (!columns || !Array.isArray(columns)) return
10
+ if (
11
+ entity._etag &&
12
+ !columns.some(c => isAsteriskColumn(c)) &&
13
+ !(columns.length === 1 && columns[0].func === 'count') &&
14
+ !columns.some(c => c.ref && c.ref[c.ref.length - 1] === entity._etag.name)
15
+ ) {
16
+ columns.push({ ref: [entity._etag.name] })
17
+ }
18
+ const expands = columns.filter(c => c.expand)
19
+ for (const expand of expands) {
20
+ const refName = expand.ref[expand.ref.length - 1]
21
+ const targetEntity = refName && entity.elements[refName] && entity.elements[refName]._target
22
+ if (targetEntity) {
23
+ addEtagColumns(expand.expand, targetEntity)
24
+ }
25
+ }
26
+ }
27
+
28
+ module.exports = {
29
+ addEtagColumns
30
+ }
@@ -92,7 +92,7 @@ function _getWhereFromUpdate(query, target, model) {
92
92
 
93
93
  const where = query.UPDATE.where || []
94
94
  if (query.UPDATE.entity.ref?.length === 1 && query.UPDATE.entity.ref[0].where)
95
- return _mergeWhere(query.UPDATE.entity.ref[0].where, where)
95
+ return _mergeWhere(where.length ? [...query.UPDATE.entity.ref[0].where] : query.UPDATE.entity.ref[0].where, where)
96
96
  return where
97
97
  }
98
98
 
@@ -0,0 +1,25 @@
1
+ const cds = require('../../cds')
2
+ const PRECISION = cds.env.features.precise_timestamps ? 7 : 3
3
+
4
+ const TZ_REGEX = new RegExp(/(Z|[+-][01]\d:?[0-5]\d)$/)
5
+ const NON_DIGIT_REGEX = new RegExp(/\D/, 'g')
6
+
7
+ const _lengthIfNotFoundIndex = (index, length) => (index > -1 ? index : length)
8
+
9
+ module.exports = value => {
10
+ if (value instanceof Date) value = value.toISOString()
11
+ if (typeof value === 'number') value = new Date(value).toISOString()
12
+
13
+ const decimalPointIndex = _lengthIfNotFoundIndex(value.lastIndexOf('.'), value.length)
14
+ const tzRegexMatch = TZ_REGEX.exec(value)
15
+ const tz = tzRegexMatch?.[0] || ''
16
+ const tzIndex = _lengthIfNotFoundIndex(tzRegexMatch?.index, value.length)
17
+ const dateEndIndex = Math.min(decimalPointIndex, tzIndex)
18
+ const dateNoMillisNoTZ = new Date(value.slice(0, dateEndIndex) + tz).toISOString().slice(0, 19)
19
+ const normalizedFractionalDigits = value
20
+ .slice(dateEndIndex + 1, tzIndex)
21
+ .replace(NON_DIGIT_REGEX, '')
22
+ .padEnd(PRECISION, '0')
23
+ .slice(0, PRECISION)
24
+ return dateNoMillisNoTZ + (normalizedFractionalDigits ? '.' + normalizedFractionalDigits : '') + 'Z'
25
+ }
@@ -9,7 +9,7 @@ const getEntityFromPath = (path, def) => {
9
9
  let id
10
10
  for (const segment of path.ref) {
11
11
  id = ensureNoDraftsSuffix(segment.id || segment)
12
- current = current.elements[id]
12
+ current = current?.elements[id] || current
13
13
  if (current && current.target) current = current._target
14
14
  }
15
15
  return current
@@ -217,6 +217,7 @@ const _newInsertColumns = (columns = [], transition) => {
217
217
  return newColumns
218
218
  }
219
219
 
220
+ // REVISIT: this hard-coding on ref indexes does not support path expressions
220
221
  const _newWhereRef = (newWhereElement, transition, alias, tableName, isSubSelect) => {
221
222
  const newRef = Array.isArray(newWhereElement.ref) ? [...newWhereElement.ref] : [newWhereElement.ref]
222
223
  if (newRef[0] === alias) {
@@ -249,7 +250,7 @@ const _newWhere = (where = [], transition, tableName, alias, isSubselect = false
249
250
 
250
251
  const newWhereElement = { ...whereElement }
251
252
  if (!whereElement.ref && !whereElement.SELECT && !whereElement.func) return whereElement
252
- if (whereElement.SELECT && whereElement.SELECT.where) {
253
+ if (whereElement.SELECT && whereElement.SELECT.where && !whereElement._doNotResolve) {
253
254
  newWhereElement.SELECT.where = _newWhere(whereElement.SELECT.where, transition, tableName, alias, true)
254
255
  return newWhereElement
255
256
  } else {
@@ -6,8 +6,9 @@ const cds = require('../../cds')
6
6
 
7
7
  const isAsteriskColumn = col => col === '*' || (col.ref && col.ref[0] === '*' && !col.expand)
8
8
 
9
- const _isDuplicate = newColumn => column => {
10
- if (newColumn.as) return column.as && column.as === newColumn.as
9
+ const isDuplicate = newColumn => column => {
10
+ if (newColumn.as && column.as) return column.as === newColumn.as
11
+ if ((newColumn.as && !column.as) || (!newColumn.as && column.as)) return
11
12
  if (!column.ref) return
12
13
  if (Array.isArray(newColumn)) newColumn = { ref: newColumn }
13
14
  return newColumn.ref ? newColumn.ref.join('_') === column.ref.join('_') : newColumn === column.ref.join('_')
@@ -50,7 +51,7 @@ const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
50
51
  1,
51
52
  ...getColumns(target, { _4db })
52
53
  .map(c => ({ ref: [c.name] }))
53
- .filter(c => !columns.find(_isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
54
+ .filter(c => !columns.find(isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
54
55
  )
55
56
  }
56
57
  }
@@ -136,5 +137,6 @@ const rewriteAsterisks = (query, model, options) => {
136
137
  module.exports = {
137
138
  rewriteAsterisks,
138
139
  isAsteriskColumn,
139
- rewriteExpandAsterisk
140
+ rewriteExpandAsterisk,
141
+ isDuplicate
140
142
  }
@@ -12,30 +12,26 @@ const _targetFrom = (cqn, options) => {
12
12
  const search2cqn4sql = (query, model, options = {}) => {
13
13
  const cqnSearchPhrase = query.SELECT.search
14
14
  if (!cqnSearchPhrase) return
15
+
15
16
  const { search2cqn4sql } = options
16
17
  const { entityName, alias } = _targetFrom(query.SELECT.from, options)
17
18
  const entity = model.definitions[entityName]
18
- const localizedAssociation = entity.associations?.localized
19
+ const aggregated = query._aggregated || /* new parser */ query.SELECT.groupBy
20
+
19
21
  // Call custom (optimized search to cqn for sql implementation) that tries
20
22
  // to optimize the search behavior for a specific database service.
21
- // REVISIT: $search query option combined with $count is not currently optimized
22
- if (
23
- typeof search2cqn4sql === 'function' &&
24
- !query.SELECT.count &&
25
- localizedAssociation &&
26
- !(query._aggregated || /* new parser */ query.SELECT.groupBy)
27
- ) {
23
+ if (typeof search2cqn4sql === 'function' && entity.associations?.localized && !aggregated) {
28
24
  const search2cqnOptions = { columns: computeColumnsToBeSearched(query, entity), locale: options.locale }
29
25
  return search2cqn4sql(query, entity, search2cqnOptions)
30
- } else {
31
- const columnsToBeSearched = computeColumnsToBeSearched(query, entity, alias)
32
- const expression = columnsToBeSearched?.length
33
- ? searchToLike(cqnSearchPhrase, columnsToBeSearched)
34
- : [{ val: 0 }, '=', { val: 1 }]
35
-
36
- // REVISIT: find out here if where or having must be used
37
- query._aggregated || /* if new parser */ query.SELECT.groupBy ? query.having(expression) : query.where(expression)
38
26
  }
27
+
28
+ const columnsToBeSearched = computeColumnsToBeSearched(query, entity, alias)
29
+ const expression = columnsToBeSearched?.length
30
+ ? searchToLike(cqnSearchPhrase, columnsToBeSearched)
31
+ : [{ val: 0 }, '=', { val: 1 }]
32
+
33
+ // REVISIT: find out here if where or having must be used
34
+ aggregated ? query.having(expression) : query.where(expression)
39
35
  }
40
36
 
41
37
  module.exports = search2cqn4sql
@@ -0,0 +1,140 @@
1
+ const cds = require('../../cds')
2
+ const LOG = cds.log('odata')
3
+ const { SELECT } = cds.ql
4
+ const { deepCopyArray } = require('./copy')
5
+ const { getTransition } = require('./resolveView')
6
+ const { cqn2cqn4sql } = require('./cqn2cqn4sql')
7
+ const getTemplate = require('./template')
8
+ const templateProcessor = require('./templateProcessor')
9
+ const { adaptStreamCQN } = require('../../fiori/utils/stream.js')
10
+ const { isPathToDraft } = require('./cqn')
11
+
12
+ // eslint-disable-next-line complexity
13
+ const _getStreamProperties = (req, query, model) => {
14
+ // new odata parser sets streaming property in SELECT.from
15
+ const ref = query.SELECT
16
+ ? (query.SELECT.columns && query.SELECT.columns[0].ref) || query.SELECT.from.ref
17
+ : [query.STREAM.column]
18
+ const propertyName = ref[ref.length - 1]
19
+ let mediaTypeProperty
20
+ for (let key in req.target.elements) {
21
+ const val = req.target.elements[key]
22
+ if (val['@Core.MediaType'] && val.name === propertyName) {
23
+ mediaTypeProperty = val
24
+ break
25
+ }
26
+ }
27
+
28
+ let contentType, contentDispositionFilename
29
+ const columns = []
30
+ if (typeof mediaTypeProperty['@Core.MediaType'] === 'object') {
31
+ let contentTypeProperty = mediaTypeProperty['@Core.MediaType']['=']
32
+ if (!req.target.elements[contentTypeProperty]) {
33
+ LOG._warn &&
34
+ LOG.warn(
35
+ `@Core.MediaType in entity "${req.target.name}" points to property "${contentTypeProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
36
+ )
37
+ const mapping = getTransition(req.target, cds.db).mapping
38
+ const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === contentTypeProperty)
39
+ contentTypeProperty = key && key.length && key[0]
40
+ }
41
+ if (!req.target.elements[contentTypeProperty]) {
42
+ LOG._warn && LOG.warn(`MediaType ${contentTypeProperty} not found in entity "${req.target.name}".`)
43
+ } else {
44
+ columns.push({ ref: [contentTypeProperty], as: 'contentType' })
45
+ }
46
+ } else {
47
+ contentType = mediaTypeProperty['@Core.MediaType']
48
+ }
49
+ if (mediaTypeProperty['@Core.ContentDisposition.Filename']) {
50
+ if (typeof mediaTypeProperty['@Core.ContentDisposition.Filename'] === 'object') {
51
+ let contentDispositionProperty = mediaTypeProperty['@Core.ContentDisposition.Filename']['=']
52
+ if (!req.target.elements[contentDispositionProperty]) {
53
+ LOG._warn &&
54
+ LOG.warn(
55
+ `@Core.ContentDisposition.Filename in entity "${req.target.name}" points to property "${contentDispositionProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
56
+ )
57
+ const mapping = getTransition(req.target, cds.db).mapping
58
+ const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === contentDispositionProperty)
59
+ contentDispositionProperty = key && key.length && key[0]
60
+ }
61
+ if (!req.target.elements[contentDispositionProperty]) {
62
+ LOG._warn &&
63
+ LOG.warn(`ContentDisposition ${contentDispositionProperty} not found in entity "${req.target.name}".`)
64
+ } else {
65
+ columns.push({ ref: [contentDispositionProperty], as: 'contentDispositionFilename' })
66
+ }
67
+ } else {
68
+ contentDispositionFilename = mediaTypeProperty['@Core.ContentDisposition.Filename']
69
+ }
70
+ }
71
+ const contentDispositionType = mediaTypeProperty['@Core.ContentDisposition.Type']
72
+
73
+ if (columns.length && cds.db && !req.target._hasPersistenceSkip) {
74
+ // used cloned path
75
+ const ref = query.SELECT ? query.SELECT.from.ref : query.STREAM.from.ref
76
+ const as = query.SELECT ? query.SELECT.from.as : query.STREAM.from.as
77
+ let select = SELECT.one.from({ ref: deepCopyArray(ref), as }).columns(columns)
78
+ const where = query.SELECT ? query.SELECT.where : query.STREAM.where
79
+ if (where?.length) select.SELECT.where = where
80
+ if (!(isNewStream() || cds.env.fiori.lean_draft)) {
81
+ const draft = req.target._isDraftEnabled && isPathToDraft(select.SELECT.from.ref, model)
82
+ if (draft) {
83
+ select = cqn2cqn4sql(select, model)
84
+ adaptStreamCQN(select, draft)
85
+ }
86
+ }
87
+
88
+ return cds
89
+ .tx(req)
90
+ .run(select)
91
+ .then(res => ({
92
+ contentType: (res && res.contentType) || contentType,
93
+ contentDispositionFilename: (res && res.contentDispositionFilename) || contentDispositionFilename,
94
+ contentDispositionType
95
+ }))
96
+ }
97
+
98
+ return Promise.resolve({ contentType, contentDispositionFilename, contentDispositionType })
99
+ }
100
+
101
+ const enhanceStreamResult = async (req, query, result, model) => {
102
+ if (!result) return
103
+ if (result.$mediaContentType || result.$mediaContentDispositionFilename || result.$mediaContentDispositionType) return
104
+
105
+ const { contentType, contentDispositionFilename, contentDispositionType } = await _getStreamProperties(
106
+ req,
107
+ query,
108
+ model
109
+ )
110
+ if (contentType) result.$mediaContentType = contentType
111
+ if (contentDispositionFilename) {
112
+ result.$mediaContentDispositionFilename = contentDispositionFilename
113
+ if (contentDispositionType) result.$mediaContentDispositionType = contentDispositionType
114
+ }
115
+ }
116
+
117
+ const pick = element => {
118
+ return element['@Core.IsURL']
119
+ }
120
+
121
+ const processFn = ({ row, key }) => {
122
+ row[`${key}@odata.mediaReadLink`] = row[key]
123
+ delete row[key]
124
+ }
125
+
126
+ function transformRedirectProperties(req, service, result) {
127
+ if (!Array.isArray(result)) result = [result]
128
+ if (result.length === 0) return
129
+
130
+ const template = getTemplate('redirect-properties', service, req.target, { pick })
131
+ if (template && template.elements.size) {
132
+ for (const row of result) {
133
+ templateProcessor({ processFn, row, template })
134
+ }
135
+ }
136
+ }
137
+
138
+ const isNewStream = () => cds.env.features.new_stream && (!cds.db || !!cds.db.stream('foo').STREAM)
139
+
140
+ module.exports = { isNewStream, enhanceStreamResult, transformRedirectProperties }
@@ -1,21 +1,38 @@
1
1
  const { ensureNoDraftsSuffix, ensureUnlocalized } = require('../../fiori/utils/handler')
2
+ const { isDuplicate } = require('./rewriteAsterisks')
3
+
4
+ const _addColumn = (name, type, columns) => {
5
+ if (typeof type === 'object') {
6
+ const ref = {
7
+ ref: [type['=']],
8
+ as: `${name}@odata.mediaContentType`
9
+ }
10
+ if (!columns.find(isDuplicate(ref))) columns.push(ref)
11
+ } else {
12
+ const val = { val: type, as: `${name}@odata.mediaContentType` }
13
+ if (!columns.find(isDuplicate(val))) columns.push(val)
14
+ }
15
+ }
2
16
 
3
17
  const _changeStreamProperties = (target, columns, model) => {
4
- for (let index = 0; index < columns.length; index++) {
18
+ let index = columns.length
19
+ while (index--) {
5
20
  const col = columns[index]
6
21
  const name = col.ref && col.ref[col.ref.length - 1]
7
22
  const element = name && target.elements[name]
8
- const type = element && !element['@Core.IsURL'] && element['@Core.MediaType']
23
+ const type = element && element['@Core.MediaType']
9
24
 
10
- if (col.ref && type) {
11
- if (typeof type === 'object') {
12
- columns[index] = {
13
- ref: [...col.ref.slice(0, -1), type['=']],
14
- as: `${name}@odata.mediaContentType`
25
+ if (col === '*') {
26
+ for (const k in target.elements) {
27
+ const el = target.elements[k]
28
+ if (el['@Core.MediaType']) {
29
+ const type = el['@Core.MediaType']
30
+ _addColumn(el.name, type, columns)
15
31
  }
16
- } else {
17
- columns[index] = { val: type, as: `${name}@odata.mediaContentType` }
18
32
  }
33
+ } else if (col.ref && type) {
34
+ _addColumn(name, type, columns)
35
+ if (!element['@Core.IsURL']) columns.splice(index, 1)
19
36
  } else if (col.expand && col.ref) {
20
37
  const tgt = target.elements[col.ref] && target.elements[col.ref].target
21
38
  tgt && _changeStreamProperties(model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(tgt))], col.expand, model)
@@ -23,10 +40,10 @@ const _changeStreamProperties = (target, columns, model) => {
23
40
  }
24
41
  }
25
42
 
26
- const handleStreamProperties = (target, select, model) => {
27
- const columns = select.SELECT.columns
43
+ const handleStreamProperties = (target, select, model, _4odata) => {
44
+ const columns = select.SELECT?.columns
28
45
  if (!columns || !target || !model) return
29
- if (!select.SELECT._4odata) return
46
+ if (!_4odata && !select.SELECT._4odata) return
30
47
  if (select._streaming) return
31
48
 
32
49
  _changeStreamProperties(target, columns, model)
@@ -1,5 +1,3 @@
1
- const cds = require('../../cds')
2
-
3
1
  const segmentSerializer = pathSegmentInfo => {
4
2
  const { key: tKey, row, elements, draftKeys } = pathSegmentInfo
5
3
  let keyNames = pathSegmentInfo.keyNames
@@ -1,7 +1,6 @@
1
1
  // before
2
2
  const rewrite = require('./rewrite')
3
3
  const input = require('./input')
4
- const integrity = require('./integrity')
5
4
  const { convertVirtuals: virtual } = require('./virtual')
6
5
  // on
7
6
  const CREATE = require('./create')
@@ -18,7 +17,6 @@ module.exports = {
18
17
  rewrite,
19
18
  virtual,
20
19
  input,
21
- integrity,
22
20
  CREATE,
23
21
  READ,
24
22
  UPDATE,
@@ -1,10 +1,10 @@
1
1
  const { getFlatArray, processCQNs } = require('../utils/deep')
2
- const { timestampToISO } = require('../data-conversion/timestamp')
2
+ const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
3
3
  const { hasDeepDelete, getDeepDeleteCQNs, getSetNullParentForeignKeyCQNs } = require('../../common/composition')
4
4
 
5
5
  const deleteFn = (executeDeleteCQN, executeUpdateCQN) => async (model, dbc, query, req) => {
6
6
  const { user, locale, timestamp } = req
7
- const isoTs = timestampToISO(timestamp)
7
+ const isoTs = normalizeTimestamp(timestamp)
8
8
 
9
9
  let result
10
10
  if (model && hasDeepDelete(model, query)) {
@@ -1,10 +1,10 @@
1
1
  const { hasDeepInsert, getDeepInsertCQNs } = require('../../common/composition')
2
2
  const { getFlatArray, processCQNs } = require('../utils/deep')
3
- const { timestampToISO } = require('../data-conversion/timestamp')
3
+ const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
4
4
 
5
5
  const insert = executeInsertCQN => async (model, dbc, query, req) => {
6
6
  const { user, locale, timestamp } = req
7
- const isoTs = timestampToISO(timestamp)
7
+ const isoTs = normalizeTimestamp(timestamp)
8
8
 
9
9
  if (model && hasDeepInsert(model, query)) {
10
10
  const cqns = getFlatArray(getDeepInsertCQNs(model, query))
@@ -1,4 +1,4 @@
1
- const { timestampToISO } = require('../data-conversion/timestamp')
1
+ const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
2
2
  const { deepCopyObject } = require('../../common/utils/copy')
3
3
  const getError = require('../../common/error')
4
4
 
@@ -39,7 +39,7 @@ const countValue = countResults => {
39
39
 
40
40
  const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) => {
41
41
  const { user, locale, timestamp } = req
42
- const isoTs = timestampToISO(timestamp)
42
+ const isoTs = normalizeTimestamp(timestamp)
43
43
 
44
44
  if (query._streaming) {
45
45
  if (!query.SELECT || (query.SELECT && !query.SELECT.columns)) {
@@ -1,4 +1,4 @@
1
- const { timestampToISO } = require('../data-conversion/timestamp')
1
+ const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
2
2
 
3
3
  const run = (insert, read, update, deleet, cqn, sql) => (model, dbc, query, req, values) => {
4
4
  if (typeof query === 'string') {
@@ -22,7 +22,7 @@ const run = (insert, read, update, deleet, cqn, sql) => (model, dbc, query, req,
22
22
  }
23
23
 
24
24
  const { user, locale, timestamp } = req
25
- const isoTs = timestampToISO(timestamp)
25
+ const isoTs = normalizeTimestamp(timestamp)
26
26
 
27
27
  return cqn(model, dbc, query, user, locale, isoTs)
28
28
  }
@@ -2,7 +2,7 @@ const cds = require('../../cds')
2
2
 
3
3
  const { hasDeepUpdate, getDeepUpdateCQNs, selectDeepUpdateData } = require('../../common/composition')
4
4
  const { getFlatArray, processCQNs } = require('../utils/deep')
5
- const { timestampToISO } = require('../data-conversion/timestamp')
5
+ const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
6
6
 
7
7
  const _includesCompositionTarget = (cqns, target) => {
8
8
  return cqns.find(cqn => {
@@ -47,7 +47,7 @@ const _getFilteredCqns = (cqns, model) => {
47
47
 
48
48
  const update = executeUpdateCQN => async (model, dbc, req) => {
49
49
  const { query, user, locale, timestamp } = req
50
- const isoTs = timestampToISO(timestamp)
50
+ const isoTs = normalizeTimestamp(timestamp)
51
51
 
52
52
  if (model && hasDeepUpdate(model, query)) {
53
53
  // REVISIT: _activeData gets set in case of draftActivate for performance, but this is a layer violation
@@ -35,12 +35,6 @@ class BaseBuilder {
35
35
  this._quotingStyle = cds.env.sql.names || 'plain'
36
36
  this._quoteElement = quotingStyles[this._quotingStyle]
37
37
  this._validateQuotingStyle()
38
-
39
- // NOTE: unofficial feature flag!
40
- this._parameterizedNumbers =
41
- 'parameterized_numbers' in this._options
42
- ? this._options.parameterized_numbers
43
- : cds.env && cds.env.features && cds.env.features.parameterized_numbers
44
38
  }
45
39
 
46
40
  getDefaultOptions() {
@@ -4,6 +4,7 @@ const BaseBuilder = require('./BaseBuilder')
4
4
  const { flattenStructuredWhereHaving } = require('../../common/utils/structured')
5
5
 
6
6
  const SQLITE_DATETIME_FUNCTIONS = new Set(['year', 'month', 'day', 'second', 'hour', 'minute'])
7
+ const HANA_DATETIME_FUNCTIONS = new Set(['year', 'month', 'dayofmonth', 'second', 'hour', 'minute'])
7
8
  const OPERATORS = new Set(['=', '!=', '<>', '<', '>', '<=', '>='])
8
9
 
9
10
  function _fillAfterDot(val) {
@@ -96,6 +97,11 @@ class ExpressionBuilder extends BaseBuilder {
96
97
  if (_valButNoBuffer(op1) && comp === '=' && op2.ref) return true
97
98
  }
98
99
 
100
+ _hasValToValComparision(op1, comp, op2) {
101
+ const _operations = ['=', '!=']
102
+ return typeof op1.val === 'number' && _operations.includes(comp) && typeof op2.val === 'number'
103
+ }
104
+
99
105
  _expressionObjectsToSQL(objects) {
100
106
  const length = objects.length
101
107
  let i = 0
@@ -117,6 +123,14 @@ class ExpressionBuilder extends BaseBuilder {
117
123
  this._expressionObjectsToSQL(flattenedStructExpression)
118
124
  i += 3
119
125
  continue
126
+ } else if (this._hasValToValComparision(objects[i], objects[i + 1], objects[i + 2])) {
127
+ // Avoid utilizing placeholders to represent numerical values being (not) equal to other numbers due to an issue related to HDB.
128
+ const _outputObj = this._outputObj
129
+ _outputObj.sql.push(objects[i].val)
130
+ _outputObj.sql.push(objects[i + 1])
131
+ _outputObj.sql.push(objects[i + 2].val)
132
+ i += 3
133
+ continue
120
134
  }
121
135
 
122
136
  this._expressionElementToSQL(objects[i])
@@ -147,17 +161,17 @@ class ExpressionBuilder extends BaseBuilder {
147
161
  // sqlite requires leading 0 for numbers in datetime functions
148
162
  const f = objects[i].func ? i : OPERATORS.has(objects[i + 1]) ? i + 2 : i - 2
149
163
  const v = objects[i].val ? i : OPERATORS.has(objects[i + 1]) ? i + 2 : i - 2
150
- if (objects[f] && SQLITE_DATETIME_FUNCTIONS.has(objects[f].func) && cds.db && cds.db.kind === 'sqlite') {
164
+ if (
165
+ objects[f] &&
166
+ cds.db &&
167
+ ((SQLITE_DATETIME_FUNCTIONS.has(objects[f].func) && cds.db.kind === 'sqlite') ||
168
+ (HANA_DATETIME_FUNCTIONS.has(objects[f].func) && cds.db.kind === 'hana'))
169
+ ) {
151
170
  if (objects[v] && objects[v].val !== undefined && typeof objects[v].val === 'number') {
152
171
  objects[v] = { val: `${objects[v].val < 10 ? 0 : ''}${objects[v].val}` }
153
172
  if (objects[f].func === 'second') objects[v].val = _fillAfterDot(objects[v].val)
154
173
  }
155
174
  }
156
- // odata indexof function returns the zero-based character position of the first occurrence
157
- if (this._options._4odata && objects[i].func && objects[i].func === 'indexof') {
158
- if (objects[i + 2] && objects[i + 2].val !== undefined) objects[i + 2].val++
159
- else if (objects[i - 2] && objects[i - 2].val !== undefined) this._outputObj.sql[i - 2]++
160
- }
161
175
  return 0
162
176
  }
163
177
 
@@ -343,12 +357,9 @@ class ExpressionBuilder extends BaseBuilder {
343
357
  * @private
344
358
  */
345
359
  _valOutputFromElement(element) {
346
- if (typeof element.val === 'number' && !this._parameterizedNumbers) {
347
- this._outputObj.sql.push(element.val)
348
- } else {
349
- this._outputObj.sql.push(this._options.placeholder)
350
- this._outputObj.values.push(element.val)
351
- }
360
+ const _outputObj = this._outputObj
361
+ _outputObj.sql.push(this._options.placeholder)
362
+ _outputObj.values.push(element.val)
352
363
  }
353
364
 
354
365
  _addToOutputObj({ sql, values }, addBrackets) {
@@ -69,6 +69,11 @@ class FunctionBuilder extends BaseBuilder {
69
69
  const functionName = this._functionName()
70
70
  const args = this._functionArgs()
71
71
 
72
+ if (this._obj.func === 'indexof') {
73
+ this._handleIndexof(args, functionName)
74
+ return
75
+ }
76
+
72
77
  if (!args) {
73
78
  // > arg-less func such as current_date
74
79
  this._outputObj.sql.push(functionName)
@@ -114,6 +119,17 @@ class FunctionBuilder extends BaseBuilder {
114
119
  this._handleLikewiseFunc(args)
115
120
  }
116
121
 
122
+ _handleIndexof(args, funcName) {
123
+ this._outputObj.sql.push('(')
124
+ this._outputObj.sql.push(funcName, '(')
125
+ if (typeof args === 'string') this._outputObj.sql.push(args)
126
+ else this._addFunctionArgs(args)
127
+ this._outputObj.sql.push(')')
128
+ this._outputObj.sql.push('-')
129
+ this._outputObj.sql.push('1')
130
+ this._outputObj.sql.push(')')
131
+ }
132
+
117
133
  _handleLikewiseFunc(args) {
118
134
  const functionName = this._functionName()
119
135
  const not = functionName.startsWith('not') ? 'NOT ' : ''
@@ -193,12 +209,8 @@ class FunctionBuilder extends BaseBuilder {
193
209
  res.push(sql)
194
210
  this._outputObj.values.push(...values)
195
211
  } else if (Object.prototype.hasOwnProperty.call(arg, 'val')) {
196
- if (typeof arg.val === 'number' && !this._parameterizedNumbers) {
197
- res.push(arg.val)
198
- } else {
199
- res.push(this._options.placeholder)
200
- this._outputObj.values.push(arg.val)
201
- }
212
+ res.push(this._options.placeholder)
213
+ this._outputObj.values.push(arg.val)
202
214
  } else if (arg.list) {
203
215
  this._addFunctionArgs(arg.list, true)
204
216
  // _addFunctionArgs adds the arguments list already to the output object
@@ -259,6 +259,7 @@ class InsertBuilder extends BaseBuilder {
259
259
  _getValue(column, { entry, flattenColumn, insertAnnotatedColumns }) {
260
260
  let val = entry
261
261
  if (!flattenColumn && this.uuidKeys.includes(column)) {
262
+ if (this._UPSERT) return undefined
262
263
  val = cds.utils.uuid()
263
264
  } else {
264
265
  for (const key of flattenColumn) {
@@ -413,7 +413,7 @@ class SelectBuilder extends BaseBuilder {
413
413
  _addRows() {
414
414
  if (this._obj.SELECT.limit) {
415
415
  if (this._obj.SELECT.limit.rows !== undefined) {
416
- // limit (no placeholder for statement caching)
416
+ // limit (no placeholder for statement caching) - hana does not know how to optimize
417
417
  this._outputObj.sql.push('LIMIT', this._obj.SELECT.limit.rows.val)
418
418
  } else {
419
419
  // rows parameter is mandatory for SQL
@@ -429,12 +429,8 @@ class SelectBuilder extends BaseBuilder {
429
429
  _addOffset() {
430
430
  // offset
431
431
  if (this._obj.SELECT.limit && this._obj.SELECT.limit.offset !== undefined) {
432
- if (typeof this._obj.SELECT.limit.offset.val === 'number' && !this._parameterizedNumbers) {
433
- this._outputObj.sql.push('OFFSET', this._obj.SELECT.limit.offset.val)
434
- } else {
435
- this._outputObj.sql.push('OFFSET', '?')
436
- this._outputObj.values.push(this._obj.SELECT.limit.offset.val)
437
- }
432
+ this._outputObj.sql.push('OFFSET', '?')
433
+ this._outputObj.values.push(this._obj.SELECT.limit.offset.val)
438
434
  }
439
435
  }
440
436
 
@@ -4,6 +4,7 @@ const getAnnotatedColumns = require('./annotations')
4
4
  class UpsertBuilder extends InsertBuilder {
5
5
  constructor(obj, options, csn) {
6
6
  super(obj, options, csn)
7
+ this._UPSERT = true
7
8
  }
8
9
 
9
10
  annotatedColumns(entityName, csn) {