@sap/cds 5.8.1 → 5.8.2
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.
- package/CHANGELOG.md +13 -3
- package/lib/log/format/kibana.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +23 -2
- package/libx/_runtime/common/composition/tree.js +1 -1
- package/libx/_runtime/common/utils/csn.js +14 -2
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +9 -6
- package/libx/_runtime/db/expand/expandCQNToJoin.js +29 -20
- package/libx/_runtime/db/utils/deep.js +10 -6
- package/libx/_runtime/hana/dynatrace.js +11 -5
- package/libx/_runtime/hana/execute.js +103 -5
- package/libx/_runtime/remote/utils/client.js +4 -4
- package/libx/_runtime/remote/utils/data.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## Version 5.8.2 - 2022-02-22
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Crash if error does not have a stack in kibana logging
|
|
12
|
+
- Allow short names for bound operations in odata-server
|
|
13
|
+
- Performance issue during deep operations
|
|
14
|
+
- Resolving views with parameters
|
|
15
|
+
- Expanding association-to-many within draft union scenario
|
|
16
|
+
- Erroneous invalidation of deep `INSERT|UPDATE|DELETE` operations if root entity has managed to-one association to non-writable view
|
|
17
|
+
- Handling of falsy results when sending requests to remote services
|
|
18
|
+
- Resolving foreign key propagations for views with union
|
|
19
|
+
|
|
7
20
|
## Version 5.8.1 - 2022-02-11
|
|
8
21
|
|
|
9
22
|
### Fixed
|
|
@@ -36,9 +49,6 @@
|
|
|
36
49
|
- Restrict access to all services via `cds.env.requires.auth.restrict_all_services = true`
|
|
37
50
|
+ That is, all unrestricted services (i.e., w/o own `@requires`) are treated as having `@requires: 'authenticated-user'`
|
|
38
51
|
- Threshold for automatically sending GET requests as `$batch` (beta, cf. @sap/cds@5.6.0) can be configured per remote service via `cds.env.requires.<srv>.max_get_url_length` (if not configured on service, the global config applies)
|
|
39
|
-
- Alpha out-of-the-box support for DwC
|
|
40
|
-
+ Authentication based on headers set by Jupiter router via `cds.env.requires.auth.kind = 'dwc-auth'`
|
|
41
|
-
+ All DwC headers are forwarded to remote service via `cds.env.requires.<srv>.forward_dwc_headers = true`
|
|
42
52
|
- Limited support for binary data in OData
|
|
43
53
|
+ In payloads, the binary data must be a base64 encoded string
|
|
44
54
|
+ In URLs, the binary data must have the following format: `binary'<url-safe base64 encoded>'`, e.g., `$filter=ID eq binary'Q0FQIE5vZGUuanM='`
|
package/lib/log/format/kibana.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cds = require
|
|
1
|
+
const cds = require('../../')
|
|
2
2
|
const util = require('util')
|
|
3
3
|
|
|
4
4
|
const _l2l = { 1: 'error', 2: 'warn', 3: 'info', 4: 'debug', 5: 'trace' }
|
|
@@ -8,7 +8,7 @@ const _l2l = { 1: 'error', 2: 'warn', 3: 'info', 4: 'debug', 5: 'trace' }
|
|
|
8
8
|
*/
|
|
9
9
|
module.exports = (module, level, ...args) => {
|
|
10
10
|
// config
|
|
11
|
-
const { user: log_user
|
|
11
|
+
const { user: log_user, kibana_custom_fields } = cds.env.log
|
|
12
12
|
|
|
13
13
|
// build the object to log
|
|
14
14
|
const toLog = {
|
|
@@ -36,7 +36,7 @@ module.exports = (module, level, ...args) => {
|
|
|
36
36
|
if (args.length && typeof args[0] === 'object' && args[0].message) {
|
|
37
37
|
const err = args.shift()
|
|
38
38
|
toLog.msg = err.message
|
|
39
|
-
if (err
|
|
39
|
+
if (typeof err.stack === 'string') toLog.stacktrace = err.stack.split(/\s*\r?\n\s*/)
|
|
40
40
|
Object.assign(toLog, err, { level: toLog.level })
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -45,9 +45,7 @@ function _getTarget(service, segments) {
|
|
|
45
45
|
: last.getEdmType().csdlStructuredType.name
|
|
46
46
|
|
|
47
47
|
// autoexposed entities now used . in csn and _ in edm
|
|
48
|
-
const target =
|
|
49
|
-
findCsnTargetFor(name, service.model, namespace) ||
|
|
50
|
-
(name.endsWith('Parameters') && service.model.definitions[namespace + '.' + name.replace(/Parameters$/, '')])
|
|
48
|
+
const target = findCsnTargetFor(name, service.model, namespace)
|
|
51
49
|
|
|
52
50
|
if (target && target.kind === 'entity') {
|
|
53
51
|
return target
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js
CHANGED
|
@@ -396,7 +396,18 @@ class ResourcePathParser {
|
|
|
396
396
|
throw new UriSyntaxError(UriSyntaxError.Message.PREVIOUS_TYPE_HAS_NO_MEDIA, currentType.getName())
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
-
|
|
399
|
+
let uriResources
|
|
400
|
+
try {
|
|
401
|
+
uriResources = this._parsePropertyPath(uriPathSegments, currentResource, tokenizer)
|
|
402
|
+
} catch (e) {
|
|
403
|
+
try {
|
|
404
|
+
uriResources = this._parseBoundOperation(uriPathSegments, currentResource, tokenizer)
|
|
405
|
+
} catch (e1) {
|
|
406
|
+
// throw first error
|
|
407
|
+
throw e
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
400
411
|
return result.concat(uriResources)
|
|
401
412
|
}
|
|
402
413
|
|
|
@@ -409,7 +420,17 @@ class ResourcePathParser {
|
|
|
409
420
|
* @private
|
|
410
421
|
*/
|
|
411
422
|
_parseBoundOperation (uriPathSegments, currentResource, tokenizer) {
|
|
412
|
-
|
|
423
|
+
// allow short names for bound operations
|
|
424
|
+
let name = tokenizer.getText()
|
|
425
|
+
if (typeof name === 'string' && !name.match(/\./)) {
|
|
426
|
+
const namespace = currentResource._entitySet &&
|
|
427
|
+
currentResource._entitySet._target &&
|
|
428
|
+
currentResource._entitySet._target.type &&
|
|
429
|
+
currentResource._entitySet._target.type.namespace
|
|
430
|
+
if (namespace) name = namespace + '.' + name
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const fqn = FullQualifiedName.createFromNameSpaceAndName(name)
|
|
413
434
|
const bindingParamTypeFqn = currentResource.getEdmType().getFullQualifiedName()
|
|
414
435
|
|
|
415
436
|
// parse bound action
|
|
@@ -38,7 +38,7 @@ const _foreignKeysToLinks = (element, inverse) =>
|
|
|
38
38
|
const _resolvedElement = (element, service) => {
|
|
39
39
|
if (!element.target) return element
|
|
40
40
|
// skip forbidden view check if association to view with foreign key in target
|
|
41
|
-
const skipForbiddenViewCheck = element._isAssociationStrict &&
|
|
41
|
+
const skipForbiddenViewCheck = element._isAssociationStrict && !element['@odata.contained']
|
|
42
42
|
const { target, mapping } = getTransition(element._target, service, skipForbiddenViewCheck)
|
|
43
43
|
const newElement = { target: target.name, _target: target }
|
|
44
44
|
Object.setPrototypeOf(newElement, element)
|
|
@@ -79,8 +79,20 @@ const getDataSubject = (entity, model, role) => {
|
|
|
79
79
|
return entity.set(hash, dataSubject)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const
|
|
83
|
-
model.entities(namespace)[name] || model.definitions[`${namespace}.${name}`]
|
|
82
|
+
const _findInModel = (name, model, namespace) => {
|
|
83
|
+
return model.entities(namespace)[name] || model.definitions[`${namespace}.${name}`]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const _resolve = (name, model, namespace) => {
|
|
87
|
+
const resolved = _findInModel(name, model, namespace)
|
|
88
|
+
// the edm name has an additional suffix 'Parameters' in case of views with parameters
|
|
89
|
+
if (!resolved && name.endsWith('Parameters')) {
|
|
90
|
+
const viewWithParam = _findInModel(name.replace(/Parameters$/, ''), model, namespace)
|
|
91
|
+
if (!viewWithParam || !viewWithParam.params) return
|
|
92
|
+
return viewWithParam
|
|
93
|
+
}
|
|
94
|
+
return resolved
|
|
95
|
+
}
|
|
84
96
|
|
|
85
97
|
const _findRootEntity = (model, edmName, namespace) => {
|
|
86
98
|
const parts = edmName.split('_')
|
|
@@ -178,6 +178,12 @@ const _resolveTargetForeignKey = targetKey => {
|
|
|
178
178
|
return { targetName, propagation }
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
const _resolveColumnsFromQuery = query => {
|
|
182
|
+
if (query && query.SET) return _resolveColumnsFromQuery(query.SET.args[0])
|
|
183
|
+
if (query && query.SELECT && query.SELECT.columns) return query.SELECT.columns
|
|
184
|
+
return []
|
|
185
|
+
}
|
|
186
|
+
|
|
181
187
|
const _resolvedKeys = (foreignKeys, targetKeys, fillChild) => {
|
|
182
188
|
const foreignKeyPropagations = []
|
|
183
189
|
|
|
@@ -191,12 +197,9 @@ const _resolvedKeys = (foreignKeys, targetKeys, fillChild) => {
|
|
|
191
197
|
* Once you have the full path, you can find it in the target entity.
|
|
192
198
|
* NOTE: There can be projections upon projections and renamings in every projection. -> not yet covered!!!
|
|
193
199
|
*/
|
|
194
|
-
const tkCol =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
targetKeys[i].parent.query.SELECT.columns.find(
|
|
198
|
-
c => c.ref && `${fk['@odata.foreignKey4']}_${c.ref.join('_')}` === fk.name
|
|
199
|
-
)
|
|
200
|
+
const tkCol = _resolveColumnsFromQuery(targetKeys[i].parent.query).find(
|
|
201
|
+
c => c.ref && `${fk['@odata.foreignKey4']}_${c.ref.join('_')}` === fk.name
|
|
202
|
+
)
|
|
200
203
|
tk = tkCol && targetKeys.find(tk => tk.name === (tkCol.as ? tkCol.as : tkCol.ref.join('_')))
|
|
201
204
|
// with composition of aspects, the lookup fails -> we need this final fallback
|
|
202
205
|
if (!tk) tk = targetKeys[i]
|
|
@@ -1151,28 +1151,37 @@ class JoinCQNFromExpanded {
|
|
|
1151
1151
|
}
|
|
1152
1152
|
}
|
|
1153
1153
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
each.
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
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
|
+
}
|
|
1170
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
|
|
1171
1184
|
}
|
|
1172
|
-
const cs = cqn.columns.filter(c => !c.expand && c.ref && c.ref[0] === tableAlias).map(c => ({ ref: [c.ref[1]] }))
|
|
1173
|
-
const unionArgs = cqn.from.args
|
|
1174
|
-
unionArgs[0].SELECT = { columns: cs, from: unionFrom, distinct: true }
|
|
1175
|
-
delete unionArgs[0].ref
|
|
1176
1185
|
}
|
|
1177
1186
|
|
|
1178
1187
|
return cqn
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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 = []
|
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
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
|
}
|
|
@@ -66,15 +66,73 @@ function _getBinaries(stmt) {
|
|
|
66
66
|
|
|
67
67
|
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}={2})$/
|
|
68
68
|
|
|
69
|
+
function _isProcedureCall(sql) {
|
|
70
|
+
return sql.trim().match(/^call \s*"{0,1}\w*/i)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function _getProcedureName(sql) {
|
|
74
|
+
const match = sql.trim().match(/^call \s*"{0,1}(\w*)/i)
|
|
75
|
+
return match && match[1]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function _hdbGetResultForProcedure(rows, args, outParameters) {
|
|
79
|
+
// on hdb, rows already contains results for scalar params
|
|
80
|
+
const result = rows || {}
|
|
81
|
+
// merge table output params into scalar params
|
|
82
|
+
if (args && args.length && outParameters) {
|
|
83
|
+
const params = outParameters.filter(md => !(md.PARAMETER_NAME in rows))
|
|
84
|
+
for (let i = 0; i < args.length; i++) {
|
|
85
|
+
result[params[i].PARAMETER_NAME] = args[i]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function _hcGetResultForProcedure(stmt, resultSet, outParameters) {
|
|
92
|
+
const result = {}
|
|
93
|
+
// build result from scalar params
|
|
94
|
+
const paramInfo = stmt.getParameterInfo()
|
|
95
|
+
if (paramInfo.some(p => p.direction > 1)) {
|
|
96
|
+
for (let i = 0; i < paramInfo.length; i++) {
|
|
97
|
+
if (paramInfo[i].direction > 1) {
|
|
98
|
+
result[paramInfo[i].name] = stmt.getParameterValue(i)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// merge table output params into scalar params
|
|
103
|
+
if (outParameters && outParameters.length) {
|
|
104
|
+
const params = outParameters.filter(md => !(md.PARAMETER_NAME in result))
|
|
105
|
+
let i = 0
|
|
106
|
+
while (resultSet.next()) {
|
|
107
|
+
result[params[i].PARAMETER_NAME] = [resultSet.getValues()]
|
|
108
|
+
resultSet.nextResult()
|
|
109
|
+
i++
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function _getProcedureMetadata(procedureName, dbc) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
dbc.exec(
|
|
118
|
+
`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`,
|
|
119
|
+
(err, res) => {
|
|
120
|
+
if (err) reject(err)
|
|
121
|
+
else resolve(res)
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
69
127
|
function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
70
|
-
dbc.prepare(sql, function (err, stmt) {
|
|
128
|
+
dbc.prepare(sql, async function (err, stmt) {
|
|
71
129
|
if (err) {
|
|
72
130
|
err.query = sql
|
|
73
131
|
if (values) err.values = SANITIZE_VALUES ? ['***'] : values
|
|
74
132
|
return reject(err)
|
|
75
133
|
}
|
|
76
134
|
|
|
77
|
-
// convert binary strings to buffers
|
|
135
|
+
// convert binary strings to buffers
|
|
78
136
|
if (cds.env.hana.base64_to_buffer !== false && _hasValues(values)) {
|
|
79
137
|
const binaries = _getBinaries(stmt)
|
|
80
138
|
if (binaries.length) {
|
|
@@ -89,6 +147,46 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
|
89
147
|
}
|
|
90
148
|
}
|
|
91
149
|
|
|
150
|
+
if (cds.env.features.new_call_prodecure) {
|
|
151
|
+
// procedure call metadata
|
|
152
|
+
let outParameters
|
|
153
|
+
const isProcedureCall = _isProcedureCall(sql)
|
|
154
|
+
if (isProcedureCall) {
|
|
155
|
+
try {
|
|
156
|
+
const procedureName = _getProcedureName(sql)
|
|
157
|
+
outParameters = await _getProcedureMetadata(procedureName, dbc)
|
|
158
|
+
} catch (e) {
|
|
159
|
+
LOG._warn && LOG.warn('Unable to fetch procedure metadata due to error:', e)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// on @sap/hana-client, we need to use execQuery in case of calling procedures
|
|
164
|
+
stmt[isProcedureCall && dbc.name !== 'hdb' ? 'execQuery' : 'exec'](values, function (err, rows, ...args) {
|
|
165
|
+
if (err) {
|
|
166
|
+
stmt.drop(() => {})
|
|
167
|
+
err.query = sql
|
|
168
|
+
if (values) err.values = SANITIZE_VALUES ? ['***'] : values
|
|
169
|
+
return reject(err)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let result
|
|
173
|
+
if (isProcedureCall) {
|
|
174
|
+
result =
|
|
175
|
+
dbc.name === 'hdb'
|
|
176
|
+
? _hdbGetResultForProcedure(rows, args, outParameters)
|
|
177
|
+
: _hcGetResultForProcedure(stmt, rows, outParameters)
|
|
178
|
+
} else {
|
|
179
|
+
result = rows
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
stmt.drop(() => {})
|
|
183
|
+
|
|
184
|
+
resolve(result)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
92
190
|
stmt.exec(values, function (err, rows, procedureReturn) {
|
|
93
191
|
if (err) {
|
|
94
192
|
stmt.drop(() => {})
|
|
@@ -123,15 +221,15 @@ function _executeSimpleSQL(dbc, sql, values) {
|
|
|
123
221
|
values = Object.values(values)
|
|
124
222
|
}
|
|
125
223
|
// ensure that stored procedure with parameters is always executed as prepared
|
|
126
|
-
if (_hasValues(values) || sql
|
|
224
|
+
if (_hasValues(values) || _isProcedureCall(sql)) {
|
|
127
225
|
_executeAsPreparedStatement(dbc, sql, values, reject, resolve)
|
|
128
226
|
} else {
|
|
129
|
-
dbc.exec(sql, function (err, result
|
|
227
|
+
dbc.exec(sql, function (err, result) {
|
|
130
228
|
if (err) {
|
|
131
229
|
err.query = sql
|
|
132
230
|
return reject(err)
|
|
133
231
|
}
|
|
134
|
-
resolve(
|
|
232
|
+
resolve(result)
|
|
135
233
|
})
|
|
136
234
|
}
|
|
137
235
|
})
|
|
@@ -142,8 +142,8 @@ function _defineProperty(obj, property, value) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
function _normalizeMetadata(prefix, data, results) {
|
|
145
|
-
const target = results
|
|
146
|
-
if (typeof target !== 'object') return target
|
|
145
|
+
const target = results !== undefined ? results : data
|
|
146
|
+
if (typeof target !== 'object' || target === null) return target
|
|
147
147
|
const metadataKeys = Object.keys(data).filter(k => prefix.test(k))
|
|
148
148
|
for (const k of metadataKeys) {
|
|
149
149
|
const $ = k.replace(prefix, '$')
|
|
@@ -169,7 +169,7 @@ const _purgeODataV2 = (data, target, reqHeaders) => {
|
|
|
169
169
|
data = data.d
|
|
170
170
|
const ieee754Compatible = reqHeaders.accept && reqHeaders.accept.includes('IEEE754Compatible=true')
|
|
171
171
|
const exponentialDecimals = ieee754Compatible && reqHeaders.accept.includes('ExponentialDecimals=true')
|
|
172
|
-
const purgedResponse = data.results
|
|
172
|
+
const purgedResponse = 'results' in data ? data.results : data
|
|
173
173
|
const convertedResponse = convertV2ResponseData(purgedResponse, target, ieee754Compatible, exponentialDecimals)
|
|
174
174
|
return _normalizeMetadata(/^__/, data, convertedResponse)
|
|
175
175
|
}
|
|
@@ -177,7 +177,7 @@ const _purgeODataV2 = (data, target, reqHeaders) => {
|
|
|
177
177
|
const _purgeODataV4 = data => {
|
|
178
178
|
if (typeof data !== 'object') return data
|
|
179
179
|
|
|
180
|
-
const purgedResponse = data.value
|
|
180
|
+
const purgedResponse = 'value' in data ? data.value : data
|
|
181
181
|
return _normalizeMetadata(/^@odata\./, data, purgedResponse)
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -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 =
|
|
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)
|