@sap/cds 6.8.4 → 7.0.1

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 (217) hide show
  1. package/CHANGELOG.md +66 -4
  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 +3 -3
  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 +2 -2
  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/protocols/odata-v4.js +9 -4
  45. package/lib/srv/srv-api.js +9 -3
  46. package/lib/srv/srv-dispatch.js +12 -9
  47. package/lib/srv/srv-models.js +4 -21
  48. package/lib/srv/srv-tx.js +15 -12
  49. package/lib/utils/cds-test.js +14 -9
  50. package/lib/utils/cds-utils.js +2 -12
  51. package/lib/utils/check-version.js +17 -0
  52. package/{bin/build → lib/utils}/csv-reader.js +23 -24
  53. package/libx/_runtime/auth/index.js +27 -23
  54. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  56. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
  57. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
  58. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
  59. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
  60. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
  61. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
  62. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
  63. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
  64. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
  66. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
  68. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
  70. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
  71. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
  73. package/libx/_runtime/cds-services/services/Service.js +79 -107
  74. package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
  75. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
  76. package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
  77. package/libx/_runtime/cds-services/util/assert.js +65 -2
  78. package/libx/_runtime/common/composition/data.js +1 -0
  79. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  80. package/libx/_runtime/common/generic/auth/restrict.js +5 -10
  81. package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
  82. package/libx/_runtime/common/generic/auth/utils.js +1 -2
  83. package/libx/_runtime/common/generic/crud.js +32 -16
  84. package/libx/_runtime/common/generic/etag.js +133 -104
  85. package/libx/_runtime/common/generic/input.js +6 -21
  86. package/libx/_runtime/common/generic/put.js +1 -1
  87. package/libx/_runtime/common/generic/stream.js +52 -0
  88. package/libx/_runtime/common/generic/temporal.js +25 -8
  89. package/libx/_runtime/common/i18n/messages.properties +0 -2
  90. package/libx/_runtime/common/utils/cqn.js +1 -1
  91. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  92. package/libx/_runtime/common/utils/csn.js +0 -51
  93. package/libx/_runtime/common/utils/etag.js +30 -0
  94. package/libx/_runtime/common/utils/keys.js +1 -1
  95. package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
  96. package/libx/_runtime/common/utils/path.js +1 -1
  97. package/libx/_runtime/common/utils/resolveView.js +2 -1
  98. package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
  99. package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
  100. package/libx/_runtime/common/utils/stream.js +140 -0
  101. package/libx/_runtime/common/utils/streamProp.js +29 -12
  102. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
  103. package/libx/_runtime/db/generic/index.js +0 -2
  104. package/libx/_runtime/db/query/delete.js +2 -2
  105. package/libx/_runtime/db/query/insert.js +2 -2
  106. package/libx/_runtime/db/query/read.js +2 -2
  107. package/libx/_runtime/db/query/run.js +2 -2
  108. package/libx/_runtime/db/query/update.js +2 -2
  109. package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
  110. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
  111. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
  112. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
  113. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
  114. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
  115. package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
  116. package/libx/_runtime/fiori/draft.js +2 -0
  117. package/libx/_runtime/fiori/generic/activate.js +8 -9
  118. package/libx/_runtime/fiori/generic/before.js +30 -20
  119. package/libx/_runtime/fiori/generic/cancel.js +5 -3
  120. package/libx/_runtime/fiori/generic/delete.js +5 -3
  121. package/libx/_runtime/fiori/generic/edit.js +7 -7
  122. package/libx/_runtime/fiori/generic/index.js +10 -16
  123. package/libx/_runtime/fiori/generic/new.js +5 -3
  124. package/libx/_runtime/fiori/generic/patch.js +11 -8
  125. package/libx/_runtime/fiori/generic/prepare.js +13 -6
  126. package/libx/_runtime/fiori/generic/read.js +12 -6
  127. package/libx/_runtime/fiori/lean-draft.js +207 -152
  128. package/libx/_runtime/fiori/utils/delete.js +10 -5
  129. package/libx/_runtime/fiori/utils/req.js +17 -5
  130. package/libx/_runtime/fiori/utils/stream.js +36 -0
  131. package/libx/_runtime/hana/Service.js +12 -9
  132. package/libx/_runtime/hana/conversion.js +10 -15
  133. package/libx/_runtime/hana/driver.js +2 -0
  134. package/libx/_runtime/hana/execute.js +28 -6
  135. package/libx/_runtime/hana/pool.js +36 -122
  136. package/libx/_runtime/hana/search2cqn4sql.js +34 -36
  137. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
  138. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
  139. package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
  140. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  141. package/libx/_runtime/remote/Service.js +20 -1
  142. package/libx/_runtime/remote/utils/client.js +3 -5
  143. package/libx/_runtime/sqlite/Service.js +4 -6
  144. package/libx/_runtime/sqlite/conversion.js +3 -13
  145. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
  146. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
  147. package/libx/_runtime/sqlite/execute.js +5 -16
  148. package/libx/odata/afterburner.js +22 -6
  149. package/libx/odata/grammar.pegjs +6 -1
  150. package/libx/odata/parser.js +1 -1
  151. package/libx/rest/RestAdapter.js +16 -9
  152. package/libx/rest/RestRequest.js +1 -1
  153. package/libx/rest/middleware/input.js +2 -1
  154. package/libx/rest/middleware/operation.js +1 -0
  155. package/libx/rest/middleware/parse.js +3 -2
  156. package/libx/rest/middleware/payload.js +9 -8
  157. package/libx/rest/middleware/read.js +1 -0
  158. package/package.json +9 -16
  159. package/server.js +1 -1
  160. package/app/fiori/preview.js +0 -270
  161. package/app/fiori/routes.js +0 -59
  162. package/bin/build/buildTaskEngine.js +0 -360
  163. package/bin/build/buildTaskFactory.js +0 -283
  164. package/bin/build/buildTaskHandler.js +0 -241
  165. package/bin/build/buildTaskProvider.js +0 -22
  166. package/bin/build/buildTaskProviderFactory.js +0 -175
  167. package/bin/build/cds.js +0 -5
  168. package/bin/build/constants.js +0 -66
  169. package/bin/build/index.js +0 -58
  170. package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
  171. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
  172. package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
  173. package/bin/build/provider/buildTaskProviderInternal.js +0 -383
  174. package/bin/build/provider/fiori/index.js +0 -171
  175. package/bin/build/provider/hana/2migration.js +0 -179
  176. package/bin/build/provider/hana/index.js +0 -505
  177. package/bin/build/provider/hana/migrationtable.js +0 -472
  178. package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
  179. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
  180. package/bin/build/provider/hana/template/.hdinamespace +0 -4
  181. package/bin/build/provider/hana/template/package.json +0 -12
  182. package/bin/build/provider/hana/template/undeploy.json +0 -5
  183. package/bin/build/provider/java/index.js +0 -111
  184. package/bin/build/provider/java-cf/index.js +0 -1
  185. package/bin/build/provider/mtx/index.js +0 -268
  186. package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
  187. package/bin/build/provider/mtx-extension/index.js +0 -131
  188. package/bin/build/provider/mtx-sidecar/index.js +0 -137
  189. package/bin/build/provider/node-cf/index.js +0 -1
  190. package/bin/build/provider/nodejs/index.js +0 -192
  191. package/bin/build/util.js +0 -299
  192. package/bin/cds.js +0 -125
  193. package/bin/deploy/to-hana/cfUtil.js +0 -355
  194. package/bin/deploy/to-hana/gitUtil.js +0 -57
  195. package/bin/deploy/to-hana/hana.js +0 -306
  196. package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
  197. package/bin/deploy/to-hana/index.js +0 -16
  198. package/bin/deploy/to-hana/mtaUtil.js +0 -170
  199. package/bin/mtx/in-cds.js +0 -17
  200. package/bin/plugins.js +0 -32
  201. package/bin/run.js +0 -24
  202. package/bin/utils/log.js +0 -24
  203. package/bin/version.js +0 -178
  204. package/libx/_runtime/audit/Service.js +0 -222
  205. package/libx/_runtime/audit/generic/personal/access.js +0 -61
  206. package/libx/_runtime/audit/generic/personal/index.js +0 -56
  207. package/libx/_runtime/audit/generic/personal/modification.js +0 -132
  208. package/libx/_runtime/audit/generic/personal/utils.js +0 -186
  209. package/libx/_runtime/audit/utils/log.js +0 -23
  210. package/libx/_runtime/audit/utils/v2.js +0 -176
  211. package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
  212. package/libx/_runtime/db/generic/integrity.js +0 -455
  213. package/srv/audit-log.cds +0 -87
  214. package/srv/mtx.cds +0 -2
  215. package/srv/mtx.js +0 -8
  216. /package/lib/{core → linked}/classes.js +0 -0
  217. /package/lib/{core → linked}/entities.js +0 -0
@@ -67,8 +67,8 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
67
67
  const currentEntityName = draft ? entityName && ensureDraftsSuffix(entityName) : entityName
68
68
  if (!currentEntityName) continue
69
69
  if (!draft && currentEntityName.endsWith('_drafts')) draft = true
70
- const tableAlias = `T${i}`
71
- const currentSelect = SELECT.from(`${currentEntityName} as ${tableAlias}`)
70
+ const tableAlias = options.tableAliasPrefix ? `${options.tableAliasPrefix}${i}` : `T${i}`
71
+ const currentSelect = SELECT.from({ ref: [currentEntityName], as: tableAlias })
72
72
 
73
73
  if (fromClause.ref[i].where) {
74
74
  currentSelect.where(addAliasToExpression(fromClause.ref[i].where, tableAlias))
@@ -969,13 +969,16 @@ const _convertUpdate = (query, model, options) => {
969
969
 
970
970
  if (alias) update.UPDATE.entity = { ref: [target], as: alias }
971
971
  if (where) update.where(where)
972
+
972
973
  const targetEntity = model.definitions[target]
974
+
973
975
  if (query.UPDATE.where) {
974
976
  update.where(addAliasToExpression(query.UPDATE.where, alias))
975
977
  _convertToOneEqNullInFilter(update.UPDATE, targetEntity)
976
978
  }
977
979
 
978
980
  if (!targetEntity) return update
981
+
979
982
  return resolveView(update, model, cds.db)
980
983
  }
981
984
 
@@ -39,56 +39,6 @@ const _getUps = (entity, model) => {
39
39
  return entity.set('__parents', ups)
40
40
  }
41
41
 
42
- const _ifDataSubject = (entity, role) => {
43
- return entity['@PersonalData.EntitySemantics'] === 'DataSubject' && entity['@PersonalData.DataSubjectRole'] === role
44
- }
45
-
46
- const _getDataSubjectUp = (role, model, entity, prev, next, result) => {
47
- for (const element of _getUps(entity, model)) {
48
- const me = { entity, relative: element.parent, element }
49
- if (prev) prev.next = me
50
- if (_ifDataSubject(element.parent, role)) {
51
- if (!result) result = { dataSubjectEntity: element.parent, subs: [] }
52
- result.subs.push(next || me)
53
- return result
54
- } else {
55
- // dfs is a must here
56
- result = _getDataSubjectUp(role, model, element.parent, me, next || me, result)
57
- }
58
- }
59
- return result
60
- }
61
-
62
- const _getDataSubjectDown = (role, entity, prev, next) => {
63
- const associations = Object.values(entity.associations || {}).filter(e => !e._isBacklink)
64
- for (const element of associations) {
65
- const me = { entity, relative: entity, element }
66
- if (_ifDataSubject(element._target, role)) {
67
- if (prev) prev.next = me
68
- return { dataSubjectEntity: element._target, subs: [next || me] }
69
- }
70
- }
71
- // bfs makes more sense here
72
- for (const element of associations) {
73
- const me = { entity, relative: entity, element }
74
- if (prev) prev.next = me
75
- const dataSubject = _getDataSubjectDown(role, element._target, me, next || me)
76
- if (dataSubject) return dataSubject
77
- }
78
- }
79
-
80
- const getDataSubject = (entity, model, role) => {
81
- const hash = '__dataSubject4' + role
82
- if (entity.own(hash)) return entity[hash]
83
- // entities with EntitySemantics 'DataSubjectDetails' or 'Other' must not necessarily
84
- // be always below or always above 'DataSubject' entity in CSN tree
85
- let dataSubject = _getDataSubjectUp(role, model, entity)
86
- if (!dataSubject) {
87
- dataSubject = _getDataSubjectDown(role, entity)
88
- }
89
- return entity.set(hash, dataSubject)
90
- }
91
-
92
42
  const _resolve = (edmName, model, namespace) => {
93
43
  const resolved = model._edmToCSNNameMap[namespace][edmName.replace(/\./g, '_')]
94
44
  // the edm name has an additional suffix 'Parameters' in case of views with parameters
@@ -249,7 +199,6 @@ module.exports = {
249
199
  getEtagElement,
250
200
  findCsnTargetFor,
251
201
  getElementDeep,
252
- getDataSubject,
253
202
  alias2ref,
254
203
  getComp2oneParents,
255
204
  prefixForStruct,
@@ -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) {