@sap/cds 5.8.0 → 5.8.3

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 (42) hide show
  1. package/CHANGELOG.md +61 -4
  2. package/app/fiori/routes.js +3 -0
  3. package/bin/cds.js +7 -3
  4. package/bin/serve.js +2 -2
  5. package/lib/deploy.js +1 -1
  6. package/lib/log/format/kibana.js +3 -3
  7. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -3
  8. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +13 -0
  9. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +23 -2
  10. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +1 -1
  11. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -6
  12. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  13. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
  14. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +4 -1
  15. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +40 -5
  16. package/libx/_runtime/common/composition/index.js +1 -2
  17. package/libx/_runtime/common/composition/insert.js +3 -16
  18. package/libx/_runtime/common/composition/tree.js +1 -1
  19. package/libx/_runtime/common/error/frontend.js +2 -3
  20. package/libx/_runtime/common/i18n/index.js +2 -31
  21. package/libx/_runtime/common/utils/csn.js +15 -3
  22. package/libx/_runtime/common/utils/foreignKeyPropagations.js +9 -6
  23. package/libx/_runtime/common/utils/generateOnCond.js +5 -5
  24. package/libx/_runtime/common/utils/structured.js +10 -4
  25. package/libx/_runtime/db/expand/expandCQNToJoin.js +59 -20
  26. package/libx/_runtime/db/query/insert.js +1 -2
  27. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
  28. package/libx/_runtime/db/utils/deep.js +10 -6
  29. package/libx/_runtime/fiori/generic/read.js +1 -3
  30. package/libx/_runtime/fiori/utils/handler.js +1 -11
  31. package/libx/_runtime/hana/conversion.js +2 -1
  32. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
  33. package/libx/_runtime/hana/dynatrace.js +11 -5
  34. package/libx/_runtime/hana/execute.js +105 -8
  35. package/libx/_runtime/hana/search2cqn4sql.js +1 -4
  36. package/libx/_runtime/remote/utils/client.js +13 -4
  37. package/libx/_runtime/remote/utils/data.js +2 -1
  38. package/libx/gql/resolvers/crud/create.js +6 -1
  39. package/libx/gql/resolvers/crud/delete.js +6 -1
  40. package/libx/gql/resolvers/crud/read.js +6 -1
  41. package/libx/gql/resolvers/crud/update.js +11 -5
  42. package/package.json +1 -1
@@ -99,6 +99,7 @@ const _getVal = (data, name) => {
99
99
 
100
100
  const _filterForStructProperty = (structElement, structData, op, prefix = '', nav = []) => {
101
101
  const filterArray = []
102
+ const andOr = op === '!=' ? 'or' : 'and'
102
103
 
103
104
  for (const elementName in structElement.elements) {
104
105
  const element = structElement.elements[elementName]
@@ -123,8 +124,8 @@ const _filterForStructProperty = (structElement, structData, op, prefix = '', na
123
124
  for (const key in assoc._target.keys) {
124
125
  if (element.name === `${assocName}_${key}`) {
125
126
  const ref = [`${prefix}_${assocName}_${key}`]
126
- const val = _getVal(structData[assocName], key)
127
- filterArray.push({ ref }, op, { val }, 'and')
127
+ const val = _getVal(structData && structData[assocName], key)
128
+ filterArray.push({ ref }, op, { val }, andOr)
128
129
  }
129
130
  }
130
131
  }
@@ -134,7 +135,7 @@ const _filterForStructProperty = (structElement, structData, op, prefix = '', na
134
135
  { ref: [...nav, `${prefix}_${element.name}`] },
135
136
  op,
136
137
  { val: _getVal(structData, element.name) },
137
- 'and'
138
+ andOr
138
139
  )
139
140
  }
140
141
  }
@@ -182,7 +183,12 @@ const _transformStructToFlatWhereHaving = ([first, op, second], resArray, struct
182
183
  } else {
183
184
  // transform complex structured to multiple single structured
184
185
  const { nestedElement, prefix } = _nestedStructElement(structProperties, structElement)
185
- resArray.push(..._filterForStructProperty(nestedElement, structData, op, prefix, nav))
186
+ const filterForStructProperty = _filterForStructProperty(nestedElement, structData, op, prefix, nav)
187
+ if (filterForStructProperty.length) {
188
+ filterForStructProperty.pop() // last and/or
189
+ if (op === '!=') resArray.push('(', ...filterForStructProperty, ')')
190
+ else resArray.push(...filterForStructProperty)
191
+ }
186
192
  }
187
193
 
188
194
  if (resArray[resArray.length - 1] === 'and') {
@@ -520,6 +520,17 @@ class JoinCQNFromExpanded {
520
520
 
521
521
  // REVISIT required for other cqn properties as well?
522
522
  this.adjustOrderBy(readToOneCQN.orderBy, mappings, column, tableAlias)
523
+
524
+ // In case active parent entity has orderBy with draft specific columns we need to add them to parent CQN
525
+ if (
526
+ readToOneCQN[IS_ACTIVE] &&
527
+ readToOneCQN.orderBy &&
528
+ column.as &&
529
+ (column.as === 'IsActiveEntity' || column.as === 'HasActiveEntity' || column.as === 'HasDraftEntity')
530
+ ) {
531
+ readToOneCQNCopy.orderBy = readToOneCQN.orderBy
532
+ this._addColumnsInCaseOrderByHasDraft(readToOneCQNCopy, readToOneCQN.columns[readToOneCQN.columns.length - 1])
533
+ }
523
534
  }
524
535
  }
525
536
 
@@ -551,6 +562,14 @@ class JoinCQNFromExpanded {
551
562
  }
552
563
  }
553
564
 
565
+ _addColumnsInCaseOrderByHasDraft(readToOneCQNCopy, column) {
566
+ readToOneCQNCopy.orderBy.forEach(order => {
567
+ if (order.as === column.as) {
568
+ readToOneCQNCopy.columns.push(column)
569
+ }
570
+ })
571
+ }
572
+
554
573
  /**
555
574
  * Follow the tree to get to the relevant config object.
556
575
  *
@@ -1132,28 +1151,37 @@ class JoinCQNFromExpanded {
1132
1151
  }
1133
1152
  }
1134
1153
  }
1135
- const ks = Object.keys(expandedEntity.keys).filter(
1136
- c => !expandedEntity.keys[c].isAssociation && !DRAFT_COLUMNS.includes(c)
1137
- )
1138
- const user = (cds.context && cds.context.user && cds.context.user.id) || 'anonymous'
1139
- const unionFrom = getCQNUnionFrom(cols, ref.replace(/_drafts$/, ''), ref, ks, user)
1140
- for (const each of cqn.columns) {
1141
- if (!each.as) continue
1142
- // replace val with ref
1143
- if (each.as === 'IsActiveEntity' || each.as === 'HasActiveEntity') {
1144
- delete each.val
1145
- each.ref = [tableAlias, each.as]
1146
- each.as = tableAlias + '_' + each.as
1147
- }
1148
- // ensure the cast
1149
- if (each.as.match(/IsActiveEntity$/) || each.as.match(/HasActiveEntity$/) || each.as.match(/HasDraftEntity$/)) {
1150
- each.cast = { type: 'cds.Boolean' }
1154
+
1155
+ if (!cqn[IS_ACTIVE]) {
1156
+ const ks = Object.keys(expandedEntity.keys).filter(
1157
+ c => !expandedEntity.keys[c].isAssociation && !DRAFT_COLUMNS.includes(c)
1158
+ )
1159
+ const user = (cds.context && cds.context.user && cds.context.user.id) || 'anonymous'
1160
+ const unionFrom = getCQNUnionFrom(cols, ref.replace(/_drafts$/, ''), ref, ks, user)
1161
+ for (const each of cqn.columns) {
1162
+ if (!each.as) continue
1163
+ // replace val with ref
1164
+ if (each.as === 'IsActiveEntity' || each.as === 'HasActiveEntity') {
1165
+ delete each.val
1166
+ each.ref = [tableAlias, each.as]
1167
+ each.as = tableAlias + '_' + each.as
1168
+ }
1169
+ // ensure the cast
1170
+ if (
1171
+ each.as.match(/IsActiveEntity$/) ||
1172
+ each.as.match(/HasActiveEntity$/) ||
1173
+ each.as.match(/HasDraftEntity$/)
1174
+ ) {
1175
+ each.cast = { type: 'cds.Boolean' }
1176
+ }
1151
1177
  }
1178
+ const cs = cqn.columns
1179
+ .filter(c => !c.expand && c.ref && c.ref[0] === tableAlias)
1180
+ .map(c => ({ ref: [c.ref[1]] }))
1181
+ const unionArgs = cqn.from.args
1182
+ unionArgs[0].SELECT = { columns: cs, from: unionFrom, distinct: true }
1183
+ delete unionArgs[0].ref
1152
1184
  }
1153
- const cs = cqn.columns.filter(c => !c.expand && c.ref && c.ref[0] === tableAlias).map(c => ({ ref: [c.ref[1]] }))
1154
- const unionArgs = cqn.from.args
1155
- unionArgs[0].SELECT = { columns: cs, from: unionFrom, distinct: true }
1156
- delete unionArgs[0].ref
1157
1185
  }
1158
1186
 
1159
1187
  return cqn
@@ -1326,6 +1354,17 @@ class JoinCQNFromExpanded {
1326
1354
  }
1327
1355
  }
1328
1356
 
1357
+ if (readToOneCQN[IS_ACTIVE] && readToOneCQN.columns.length > 0) {
1358
+ readToOneCQN.columns.forEach(column => {
1359
+ if (
1360
+ column.as === `${parentAlias}_IsActiveEntity` ||
1361
+ column.as === `${parentAlias}_HasActiveEntity` ||
1362
+ column.as === `${parentAlias}_HasDraftEntity`
1363
+ )
1364
+ columns.push(column)
1365
+ })
1366
+ }
1367
+
1329
1368
  const subSelect = Object.assign({}, readToOneCQN, { columns })
1330
1369
 
1331
1370
  const SELECT = { from: { SELECT: subSelect }, columns: outerColumns, distinct: true }
@@ -1,4 +1,4 @@
1
- const { hasDeepInsert, getDeepInsertCQNs, cleanEmptyCompositionsOfMany } = require('../../common/composition')
1
+ const { hasDeepInsert, getDeepInsertCQNs } = require('../../common/composition')
2
2
  const { getFlatArray, processCQNs } = require('../utils/deep')
3
3
  const { timestampToISO } = require('../data-conversion/timestamp')
4
4
 
@@ -15,7 +15,6 @@ const insert = executeInsertCQN => async (model, dbc, query, req) => {
15
15
  return getFlatArray(results)
16
16
  }
17
17
 
18
- cleanEmptyCompositionsOfMany(model, query)
19
18
  return executeInsertCQN(model, dbc, query, user, locale, isoTs)
20
19
  }
21
20
 
@@ -96,7 +96,7 @@ class SelectBuilder extends BaseBuilder {
96
96
  }
97
97
 
98
98
  if (this._obj.SELECT.orderBy && this._obj.SELECT.orderBy.length) {
99
- this._orderBy()
99
+ this._orderBy(noQuoting)
100
100
  }
101
101
 
102
102
  if (this._obj.SELECT.limit || this._obj.SELECT.one) {
@@ -373,7 +373,11 @@ class SelectBuilder extends BaseBuilder {
373
373
  this._outputObj.values.push(...values)
374
374
  }
375
375
 
376
- _orderBy() {
376
+ _getOrderByElement(noQuoting, name, element) {
377
+ return (noQuoting ? name : this._quote(name)) + ' ' + (element.sort || 'asc').toUpperCase()
378
+ }
379
+
380
+ _orderBy(noQuoting) {
377
381
  const sqls = []
378
382
  this._outputObj.sql.push('ORDER BY')
379
383
  for (const element of this._obj.SELECT.orderBy) {
@@ -385,7 +389,7 @@ class SelectBuilder extends BaseBuilder {
385
389
  if (!columns.find(c => JSON.stringify(c.ref) === serialized)) {
386
390
  const toMatch = element.as || (element.ref && element.ref.length === 1 && element.ref[0])
387
391
  if (toMatch && columns.find(c => c.as === toMatch)) {
388
- sqls.push(this._quote(toMatch) + ' ' + (element.sort || 'asc').toUpperCase())
392
+ sqls.push(this._getOrderByElement(noQuoting, toMatch, element))
389
393
  continue
390
394
  }
391
395
  }
@@ -1,14 +1,18 @@
1
- function _flattenDeep(arr) {
2
- return arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? _flattenDeep(val) : val), [])
1
+ const _flattenDeep = (arr, res) => {
2
+ if (!Array.isArray(arr)) {
3
+ res.push(arr)
4
+ return res
5
+ }
6
+ for (const a of arr) {
7
+ _flattenDeep(a, res)
8
+ }
9
+ return res
3
10
  }
4
11
 
5
12
  /*
6
13
  * flatten with a dfs approach. this is important!!!
7
14
  */
8
- function getFlatArray(arg) {
9
- if (!Array.isArray(arg)) return [arg]
10
- return _flattenDeep(arg)
11
- }
15
+ const getFlatArray = arg => _flattenDeep(arg, [])
12
16
 
13
17
  async function _processChunk(processFn, model, dbc, cqns, user, locale, ts, indexes, results) {
14
18
  const promises = []
@@ -3,10 +3,8 @@ const { SELECT } = cds.ql
3
3
 
4
4
  const { cqn2cqn4sql, convertWhereExists } = require('../../common/utils/cqn2cqn4sql')
5
5
  const { getElementDeep } = require('../../common/utils/csn')
6
-
7
6
  const { DRAFT_COLUMNS, DRAFT_COLUMNS_MAP, SCENARIO } = require('../../common/constants/draft')
8
7
  const {
9
- adaptStreamCQN,
10
8
  addColumnAlias,
11
9
  draftIsLocked,
12
10
  ensureDraftsSuffix,
@@ -18,8 +16,8 @@ const {
18
16
  filterKeys
19
17
  } = require('../utils/handler')
20
18
  const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively } = require('../utils/where')
21
-
22
19
  const { getColumns } = require('../../cds-services/services/utils/columns')
20
+ const { adaptStreamCQN } = require('../../cds-services/adapter/odata-v4/utils/stream')
23
21
 
24
22
  const _findRootSubSelectFor = query => {
25
23
  if (query.SELECT.where) {
@@ -1,6 +1,5 @@
1
1
  const cds = require('../../cds')
2
2
  const { UPDATE, SELECT } = cds.ql
3
- const { removeIsActiveEntityRecursively, isActiveEntityRequested } = require('./where')
4
3
  const { getColumns } = require('../../cds-services/services/utils/columns')
5
4
  const { ensureNoDraftsSuffix, ensureDraftsSuffix, ensureUnlocalized } = require('../../common/utils/draft')
6
5
  const getTemplate = require('../../common/utils/template')
@@ -126,7 +125,7 @@ const getEnrichedCQN = (cqn, select, draftWhere, scenarioAlias, addLimitOrder =
126
125
  }
127
126
 
128
127
  if (select.distinct) {
129
- cqn.distinct()
128
+ cqn.SELECT.distinct = true
130
129
  }
131
130
 
132
131
  const alias = (select.from && select.from.as) || scenarioAlias
@@ -249,14 +248,6 @@ const replaceRefWithDraft = ref => {
249
248
  ref[0] = ensureDraftsSuffix(ref[0])
250
249
  }
251
250
 
252
- const adaptStreamCQN = cqn => {
253
- if (isActiveEntityRequested(cqn.SELECT.where)) {
254
- cqn.SELECT.where = removeIsActiveEntityRecursively(cqn.SELECT.where)
255
- } else {
256
- replaceRefWithDraft(cqn.SELECT.from.ref)
257
- }
258
- }
259
-
260
251
  const draftIsLocked = lastChangedAt => {
261
252
  // default timeout timer is 15 minutes
262
253
  const DRAFT_CANCEL_TIMEOUT_IN_MS = ((cds.env.drafts && cds.env.drafts.cancellationTimeout) || 15) * 60 * 1000
@@ -289,7 +280,6 @@ module.exports = {
289
280
  hasDraft,
290
281
  proxifyToNoDraftsName,
291
282
  addColumnAlias,
292
- adaptStreamCQN,
293
283
  replaceRefWithDraft,
294
284
  getKeyProperty,
295
285
  filterKeys,
@@ -46,7 +46,8 @@ const HANA_TYPE_CONVERSION_MAP = new Map([
46
46
  ['cds.Integer64', convertInt64ToString],
47
47
  ['cds.DateTime', convertToISONoMillis],
48
48
  ['cds.Timestamp', convertToISO],
49
- ['cds.LargeString', convertToString]
49
+ ['cds.LargeString', convertToString],
50
+ ['cds.hana.CLOB', convertToString]
50
51
  ])
51
52
 
52
53
  if (cds.env.features.bigjs) {
@@ -48,7 +48,7 @@ class CustomSelectBuilder extends SelectBuilder {
48
48
  select.from.ref &&
49
49
  select.from.ref.length === 1 &&
50
50
  // REVISIT this does not work with join and draft!
51
- this._csn.definitions[select.from.ref[0]]
51
+ this._csn.definitions[select.from.ref[0].id || select.from.ref[0]]
52
52
  // TODO FIXME
53
53
  skip =
54
54
  !select.orderBy ||
@@ -7,11 +7,12 @@ try {
7
7
  }
8
8
 
9
9
  const isDynatraceEnabled = () => {
10
- return dynatrace.sdk !== undefined
10
+ return dynatrace.sdk !== undefined && !process.env.CDS_SKIP_DYNATRACE
11
11
  }
12
12
 
13
13
  const _dynatraceResultCallback = function (tracer, cb) {
14
- return function (err, results, fields) {
14
+ return function (err, ...args) {
15
+ const results = args.shift()
15
16
  if (err) {
16
17
  tracer.error(err)
17
18
  } else {
@@ -19,7 +20,7 @@ const _dynatraceResultCallback = function (tracer, cb) {
19
20
  rowsReturned: (results && results.length) || results
20
21
  })
21
22
  }
22
- tracer.end(cb, err, results, fields)
23
+ tracer.end(cb, err, results, ...args)
23
24
  }
24
25
  }
25
26
 
@@ -73,9 +74,14 @@ const dynatraceClient = (client, credentials, tenant) => {
73
74
  // hana-client does not like decorating.
74
75
  // because of that, we need to override the fn and pass the original fn for execution
75
76
  const originalExecFn = client.exec
76
- const originalPrepareFn = client.prepare
77
77
  client.exec = _execUsingDynatrace(client, originalExecFn, dbInfo)
78
- client.prepare = _preparedStmtUsingDynatrace(client, originalPrepareFn, dbInfo)
78
+ const originalPrepareFn = client.prepare
79
+ if (client.name === '@sap/hana-client') {
80
+ // client.prepare = ... doesn't work for hana-client
81
+ Object.defineProperty(client, 'prepare', { value: _preparedStmtUsingDynatrace(client, originalPrepareFn, dbInfo) })
82
+ } else {
83
+ client.prepare = _preparedStmtUsingDynatrace(client, originalPrepareFn, dbInfo)
84
+ }
79
85
 
80
86
  return client
81
87
  }
@@ -51,9 +51,8 @@ function _getOutputParameters(stmt) {
51
51
  const BINARY_TYPES = {
52
52
  12: 'BINARY',
53
53
  13: 'VARBINARY',
54
- 25: 'CLOB',
55
- 26: 'NCLOB',
56
- 27: 'BLOB'
54
+ 27: 'BLOB',
55
+ 33: 'BSTRING'
57
56
  }
58
57
 
59
58
  function _getBinaries(stmt) {
@@ -68,15 +67,73 @@ function _getBinaries(stmt) {
68
67
 
69
68
  const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}={2})$/
70
69
 
70
+ function _isProcedureCall(sql) {
71
+ return sql.trim().match(/^call \s*"{0,1}\w*/i)
72
+ }
73
+
74
+ function _getProcedureName(sql) {
75
+ const match = sql.trim().match(/^call \s*"{0,1}(\w*)/i)
76
+ return match && match[1]
77
+ }
78
+
79
+ function _hdbGetResultForProcedure(rows, args, outParameters) {
80
+ // on hdb, rows already contains results for scalar params
81
+ const result = rows || {}
82
+ // merge table output params into scalar params
83
+ if (args && args.length && outParameters) {
84
+ const params = outParameters.filter(md => !(md.PARAMETER_NAME in rows))
85
+ for (let i = 0; i < args.length; i++) {
86
+ result[params[i].PARAMETER_NAME] = args[i]
87
+ }
88
+ }
89
+ return result
90
+ }
91
+
92
+ function _hcGetResultForProcedure(stmt, resultSet, outParameters) {
93
+ const result = {}
94
+ // build result from scalar params
95
+ const paramInfo = stmt.getParameterInfo()
96
+ if (paramInfo.some(p => p.direction > 1)) {
97
+ for (let i = 0; i < paramInfo.length; i++) {
98
+ if (paramInfo[i].direction > 1) {
99
+ result[paramInfo[i].name] = stmt.getParameterValue(i)
100
+ }
101
+ }
102
+ }
103
+ // merge table output params into scalar params
104
+ if (outParameters && outParameters.length) {
105
+ const params = outParameters.filter(md => !(md.PARAMETER_NAME in result))
106
+ let i = 0
107
+ while (resultSet.next()) {
108
+ result[params[i].PARAMETER_NAME] = [resultSet.getValues()]
109
+ resultSet.nextResult()
110
+ i++
111
+ }
112
+ }
113
+ return result
114
+ }
115
+
116
+ function _getProcedureMetadata(procedureName, dbc) {
117
+ return new Promise((resolve, reject) => {
118
+ dbc.exec(
119
+ `SELECT PARAMETER_NAME FROM SYS.PROCEDURE_PARAMETERS WHERE SCHEMA_NAME = CURRENT_SCHEMA AND PROCEDURE_NAME = '${procedureName}' AND PARAMETER_TYPE IN ('OUT', 'INOUT') ORDER BY POSITION`,
120
+ (err, res) => {
121
+ if (err) reject(err)
122
+ else resolve(res)
123
+ }
124
+ )
125
+ })
126
+ }
127
+
71
128
  function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
72
- dbc.prepare(sql, function (err, stmt) {
129
+ dbc.prepare(sql, async function (err, stmt) {
73
130
  if (err) {
74
131
  err.query = sql
75
132
  if (values) err.values = SANITIZE_VALUES ? ['***'] : values
76
133
  return reject(err)
77
134
  }
78
135
 
79
- // convert binary strings to buffers ()
136
+ // convert binary strings to buffers
80
137
  if (cds.env.hana.base64_to_buffer !== false && _hasValues(values)) {
81
138
  const binaries = _getBinaries(stmt)
82
139
  if (binaries.length) {
@@ -91,6 +148,46 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
91
148
  }
92
149
  }
93
150
 
151
+ if (cds.env.features.new_call_prodecure) {
152
+ // procedure call metadata
153
+ let outParameters
154
+ const isProcedureCall = _isProcedureCall(sql)
155
+ if (isProcedureCall) {
156
+ try {
157
+ const procedureName = _getProcedureName(sql)
158
+ outParameters = await _getProcedureMetadata(procedureName, dbc)
159
+ } catch (e) {
160
+ LOG._warn && LOG.warn('Unable to fetch procedure metadata due to error:', e)
161
+ }
162
+ }
163
+
164
+ // on @sap/hana-client, we need to use execQuery in case of calling procedures
165
+ stmt[isProcedureCall && dbc.name !== 'hdb' ? 'execQuery' : 'exec'](values, function (err, rows, ...args) {
166
+ if (err) {
167
+ stmt.drop(() => {})
168
+ err.query = sql
169
+ if (values) err.values = SANITIZE_VALUES ? ['***'] : values
170
+ return reject(err)
171
+ }
172
+
173
+ let result
174
+ if (isProcedureCall) {
175
+ result =
176
+ dbc.name === 'hdb'
177
+ ? _hdbGetResultForProcedure(rows, args, outParameters)
178
+ : _hcGetResultForProcedure(stmt, rows, outParameters)
179
+ } else {
180
+ result = rows
181
+ }
182
+
183
+ stmt.drop(() => {})
184
+
185
+ resolve(result)
186
+ })
187
+
188
+ return
189
+ }
190
+
94
191
  stmt.exec(values, function (err, rows, procedureReturn) {
95
192
  if (err) {
96
193
  stmt.drop(() => {})
@@ -125,15 +222,15 @@ function _executeSimpleSQL(dbc, sql, values) {
125
222
  values = Object.values(values)
126
223
  }
127
224
  // ensure that stored procedure with parameters is always executed as prepared
128
- if (_hasValues(values) || sql.match(/^call.*?\?.*$/i)) {
225
+ if (_hasValues(values) || _isProcedureCall(sql)) {
129
226
  _executeAsPreparedStatement(dbc, sql, values, reject, resolve)
130
227
  } else {
131
- dbc.exec(sql, function (err, result, procedureReturn) {
228
+ dbc.exec(sql, function (err, result) {
132
229
  if (err) {
133
230
  err.query = sql
134
231
  return reject(err)
135
232
  }
136
- resolve(procedureReturn || result)
233
+ resolve(result)
137
234
  })
138
235
  }
139
236
  })
@@ -72,10 +72,7 @@ const search2cqn4sql = (query, entity, options) => {
72
72
  return query
73
73
  }
74
74
 
75
- const _getLocalizedAssociation = entity => {
76
- const associations = entity.associations
77
- return associations && associations.localized
78
- }
75
+ const _getLocalizedAssociation = entity => entity.associations && entity.associations.localized
79
76
 
80
77
  // The inner join modifies the original SELECT ... FROM query and adds ambiguity,
81
78
  // therefore add the table/entity name (as a preceding element) to the columns ref
@@ -67,6 +67,11 @@ const getDestination = (name, credentials) => {
67
67
  throw new Error(`"url" or "destination" property must be configured in "credentials" of "${name}".`)
68
68
  }
69
69
 
70
+ // Cloud SDK wants property "queryParameters" but we have documented "queries"
71
+ if (credentials.queries && !credentials.queryParameters) {
72
+ credentials.queryParameters = credentials.queries
73
+ }
74
+
70
75
  return { name, ...credentials }
71
76
  }
72
77
 
@@ -142,8 +147,8 @@ function _defineProperty(obj, property, value) {
142
147
  }
143
148
 
144
149
  function _normalizeMetadata(prefix, data, results) {
145
- const target = results || data
146
- if (typeof target !== 'object') return target
150
+ const target = results !== undefined ? results : data
151
+ if (typeof target !== 'object' || target === null) return target
147
152
  const metadataKeys = Object.keys(data).filter(k => prefix.test(k))
148
153
  for (const k of metadataKeys) {
149
154
  const $ = k.replace(prefix, '$')
@@ -169,7 +174,7 @@ const _purgeODataV2 = (data, target, reqHeaders) => {
169
174
  data = data.d
170
175
  const ieee754Compatible = reqHeaders.accept && reqHeaders.accept.includes('IEEE754Compatible=true')
171
176
  const exponentialDecimals = ieee754Compatible && reqHeaders.accept.includes('ExponentialDecimals=true')
172
- const purgedResponse = data.results || data
177
+ const purgedResponse = 'results' in data ? data.results : data
173
178
  const convertedResponse = convertV2ResponseData(purgedResponse, target, ieee754Compatible, exponentialDecimals)
174
179
  return _normalizeMetadata(/^__/, data, convertedResponse)
175
180
  }
@@ -177,7 +182,7 @@ const _purgeODataV2 = (data, target, reqHeaders) => {
177
182
  const _purgeODataV4 = data => {
178
183
  if (typeof data !== 'object') return data
179
184
 
180
- const purgedResponse = data.value || data
185
+ const purgedResponse = 'value' in data ? data.value : data
181
186
  return _normalizeMetadata(/^@odata\./, data, purgedResponse)
182
187
  }
183
188
 
@@ -246,6 +251,7 @@ const run = async (
246
251
 
247
252
  LOG._warn && LOG.warn(sanitizedError)
248
253
 
254
+ // REVISIT: switch from innererror to reason in cds^6
249
255
  throw Object.assign(new Error(e.message), { statusCode: 502, innererror: sanitizedError })
250
256
  }
251
257
 
@@ -267,6 +273,7 @@ const run = async (
267
273
 
268
274
  LOG._warn && LOG.warn(sanitizedError)
269
275
 
276
+ // REVISIT: switch from innererror to reason in cds^6
270
277
  throw Object.assign(new Error(`Error during request to remote service: ${e.message}`), {
271
278
  statusCode: 502,
272
279
  innererror: sanitizedError
@@ -294,6 +301,8 @@ const run = async (
294
301
  : 'Request to remote service failed.'
295
302
  const sanitizedError = _getSanitizedError(contentJSON, requestConfig)
296
303
  LOG._warn && LOG.warn(sanitizedError)
304
+
305
+ // REVISIT: switch from innererror to reason in cds^6
297
306
  throw Object.assign(new Error(contentJSON.message), { statusCode: 502, innererror: sanitizedError })
298
307
  }
299
308
  }
@@ -39,7 +39,8 @@ const _getConvertRecordFn = (target, convertValueFn) => record => {
39
39
  if (!element) continue
40
40
 
41
41
  const recordValue = record[key]
42
- const value = (recordValue && recordValue.results) || recordValue
42
+ const value =
43
+ (recordValue && typeof recordValue === 'object' && 'results' in recordValue && recordValue.results) || recordValue
43
44
 
44
45
  if (value && (element.isAssociation || Array.isArray(value))) {
45
46
  record[key] = _convertData(value, element._target, convertValueFn)
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToEntries } = require('../parse/ast2cqn')
3
5
  const { entriesStructureToEntityStructure } = require('./utils')
@@ -9,7 +11,10 @@ module.exports = async (service, entityFQN, selection) => {
9
11
  const entries = entriesStructureToEntityStructure(service, entityFQN, astToEntries(input))
10
12
  query.entries(entries)
11
13
 
12
- const result = await service.tx(tx => tx.run(query))
14
+ const result = await service.tx(tx => {
15
+ cds.context = tx
16
+ return tx.run(query)
17
+ })
13
18
 
14
19
  return Array.isArray(result) ? result : [result]
15
20
  }
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToWhere } = require('../parse/ast2cqn')
3
5
 
@@ -11,7 +13,10 @@ module.exports = async (service, entityFQN, selection) => {
11
13
 
12
14
  let result
13
15
  try {
14
- result = await service.tx(tx => tx.run(query))
16
+ result = await service.tx(tx => {
17
+ cds.context = tx
18
+ return tx.run(query)
19
+ })
15
20
  } catch (e) {
16
21
  if (e.code === 404) {
17
22
  result = 0
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToColumns, astToWhere, astToOrderBy, astToLimit } = require('../parse/ast2cqn')
3
5
 
@@ -21,5 +23,8 @@ module.exports = async (service, entityFQN, selection) => {
21
23
  query.limit(astToLimit(top, skip))
22
24
  }
23
25
 
24
- return await service.tx(tx => tx.run(query))
26
+ return await service.tx(tx => {
27
+ cds.context = tx
28
+ return tx.run(query)
29
+ })
25
30
  }