@sap/cds 5.8.0 → 5.8.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.
- package/CHANGELOG.md +15 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +40 -5
- package/libx/_runtime/common/composition/index.js +1 -2
- package/libx/_runtime/common/composition/insert.js +3 -16
- package/libx/_runtime/common/error/frontend.js +2 -3
- package/libx/_runtime/common/utils/csn.js +1 -1
- package/libx/_runtime/common/utils/structured.js +10 -4
- package/libx/_runtime/db/expand/expandCQNToJoin.js +30 -0
- package/libx/_runtime/db/query/insert.js +1 -2
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
- package/libx/_runtime/fiori/generic/read.js +1 -3
- package/libx/_runtime/fiori/utils/handler.js +1 -11
- package/libx/_runtime/hana/conversion.js +2 -1
- package/libx/_runtime/hana/execute.js +0 -2
- package/libx/_runtime/hana/search2cqn4sql.js +1 -4
- package/libx/_runtime/remote/utils/client.js +4 -0
- package/libx/gql/resolvers/crud/update.js +8 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
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.1 - 2022-02-11
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Use single transaction for update mutations in GraphQL adapter
|
|
12
|
+
- ODATA to CQN parser returned not selected keys in `@odata.context`
|
|
13
|
+
- Draft: `$expand` with special draft columns in `$orderBy` for active entities
|
|
14
|
+
- Reading distinct values of draft enabled entity
|
|
15
|
+
- Handling of LOB data on HANA
|
|
16
|
+
- Fix streaming draft by navigation
|
|
17
|
+
- Empty to-many arrays are not removed from req.data for inserts
|
|
18
|
+
- `$filter` query option in structured mode (OData flavors `w4` and `x4`)
|
|
19
|
+
+ Using JSON-stringified objects no longer occasionally crashes an application
|
|
20
|
+
+ Filtering on a structured element with `ne null` condition also selects data having some `null` properties within
|
|
21
|
+
|
|
7
22
|
## Version 5.8.0 - 2022-01-27
|
|
8
23
|
|
|
9
24
|
### Added
|
|
@@ -281,7 +281,7 @@ class ContextURLFactory {
|
|
|
281
281
|
// If there is one '*' selected, the context URL contains only '*'.
|
|
282
282
|
if (isAll) {
|
|
283
283
|
value = ['*']
|
|
284
|
-
} else if (type && type.getKind() === EdmTypeKind.ENTITY && isKeyRequired) {
|
|
284
|
+
} else if (type && type.getKind() === EdmTypeKind.ENTITY && isKeyRequired && !cds.env.features.odata_new_parser) {
|
|
285
285
|
for (const keyName of type.getKeyPropertyRefs().keys()) {
|
|
286
286
|
if (!value.includes(keyName)) value.push(keyName)
|
|
287
287
|
}
|
|
@@ -5,6 +5,9 @@ const { SELECT } = cds.ql
|
|
|
5
5
|
|
|
6
6
|
const { targetFromPath, isPathToDraft } = require('../../../../common/utils/cqn')
|
|
7
7
|
const { deepCopyArray } = require('../../../../common/utils/copy')
|
|
8
|
+
const { cqn2cqn4sql } = require('../../../../common/utils/cqn2cqn4sql')
|
|
9
|
+
const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
|
|
10
|
+
const { removeIsActiveEntityRecursively, isActiveEntityRequested } = require('../../../../fiori/utils/where')
|
|
8
11
|
|
|
9
12
|
const isStreaming = segments => {
|
|
10
13
|
const lastSegment = segments[segments.length - 1]
|
|
@@ -15,6 +18,34 @@ const isStreaming = segments => {
|
|
|
15
18
|
)
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
const _adaptSubSelectsDraft = select => {
|
|
22
|
+
if (select.SELECT.from.ref) {
|
|
23
|
+
const index = select.SELECT.from.ref.length - 1
|
|
24
|
+
select.SELECT.from.ref[index] = ensureDraftsSuffix(select.SELECT.from.ref[index])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (select.SELECT.where) {
|
|
28
|
+
for (let i = 0; i < select.SELECT.where.length; i++) {
|
|
29
|
+
const element = select.SELECT.where[i]
|
|
30
|
+
if (element.SELECT) {
|
|
31
|
+
_adaptSubSelectsDraft(element)
|
|
32
|
+
} else if (element.xpr) {
|
|
33
|
+
for (const ele of element.xpr.filter(e => e.SELECT)) {
|
|
34
|
+
_adaptSubSelectsDraft(ele)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const adaptStreamCQN = (cqn, isDraft = false) => {
|
|
42
|
+
if (isDraft || !isActiveEntityRequested(cqn.SELECT.where)) {
|
|
43
|
+
_adaptSubSelectsDraft(cqn)
|
|
44
|
+
} else {
|
|
45
|
+
cqn.SELECT.where = removeIsActiveEntityRecursively(cqn.SELECT.where)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
18
49
|
const getStreamProperties = (req, model) => {
|
|
19
50
|
const mediaTypeProperty = Object.values(req.target.elements).find(val => val['@Core.MediaType'])
|
|
20
51
|
|
|
@@ -56,14 +87,17 @@ const getStreamProperties = (req, model) => {
|
|
|
56
87
|
|
|
57
88
|
if (columns.length && cds.db && !req.target._hasPersistenceSkip) {
|
|
58
89
|
// used cloned path
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (req.target._isDraftEnabled && isPathToDraft(select.SELECT.from.ref, model))
|
|
62
|
-
select.SELECT.from.ref[0].id = select.SELECT.from.ref[0].id + '_drafts'
|
|
90
|
+
let select = SELECT.one.from({ ref: deepCopyArray(req.query.SELECT.from.ref) }).columns(columns)
|
|
63
91
|
|
|
64
92
|
// new parser has media property as last ref element -> remove
|
|
65
93
|
if (targetFromPath(select.SELECT.from.ref, model).kind === 'element') select.SELECT.from.ref.pop()
|
|
66
94
|
|
|
95
|
+
const pathToDraft = isPathToDraft(select.SELECT.from.ref, model)
|
|
96
|
+
if (req.target._isDraftEnabled && pathToDraft) {
|
|
97
|
+
select = cqn2cqn4sql(select, model)
|
|
98
|
+
adaptStreamCQN(select, pathToDraft)
|
|
99
|
+
}
|
|
100
|
+
|
|
67
101
|
return cds
|
|
68
102
|
.tx(req)
|
|
69
103
|
.run(select)
|
|
@@ -79,5 +113,6 @@ const getStreamProperties = (req, model) => {
|
|
|
79
113
|
|
|
80
114
|
module.exports = {
|
|
81
115
|
isStreaming,
|
|
82
|
-
getStreamProperties
|
|
116
|
+
getStreamProperties,
|
|
117
|
+
adaptStreamCQN
|
|
83
118
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { getCompositionTree, getCompositionRoot } = require('./tree')
|
|
2
|
-
const { hasDeepInsert, getDeepInsertCQNs
|
|
2
|
+
const { hasDeepInsert, getDeepInsertCQNs } = require('./insert')
|
|
3
3
|
const { hasDeepUpdate, getDeepUpdateCQNs } = require('./update')
|
|
4
4
|
const { hasDeepDelete, getDeepDeleteCQNs, getSetNullParentForeignKeyCQNs } = require('./delete')
|
|
5
5
|
const { selectDeepUpdateData } = require('./data')
|
|
@@ -11,7 +11,6 @@ module.exports = {
|
|
|
11
11
|
// insert
|
|
12
12
|
hasDeepInsert,
|
|
13
13
|
getDeepInsertCQNs,
|
|
14
|
-
cleanEmptyCompositionsOfMany,
|
|
15
14
|
// update
|
|
16
15
|
hasDeepUpdate,
|
|
17
16
|
getDeepUpdateCQNs,
|
|
@@ -10,9 +10,9 @@ const { deepCopyArray } = require('../utils/copy')
|
|
|
10
10
|
* own utils
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
function
|
|
13
|
+
function _hasCompOrAssoc(entity, k) {
|
|
14
14
|
// TODO once REST also uses same logic as odata structured check if we can omit 'entity.elements[k] &&'
|
|
15
|
-
return entity.elements[k] && (entity.elements[k].is2one ||
|
|
15
|
+
return entity.elements[k] && (entity.elements[k].is2one || entity.elements[k].is2many)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const _addSubDeepInsertCQN = (model, compositionTree, data, cqns, draft) => {
|
|
@@ -60,25 +60,13 @@ const _entityFromINSERT = (model, INSERT) => {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const cleanEmptyCompositionsOfMany = (model, cqn) => {
|
|
64
|
-
const entity = _entityFromINSERT(model, cqn.INSERT)
|
|
65
|
-
if (!entity) return
|
|
66
|
-
for (const entry of cqn.INSERT.entries || []) {
|
|
67
|
-
for (const elName in entry || {}) {
|
|
68
|
-
const el = entity.elements[elName]
|
|
69
|
-
if (!el) continue
|
|
70
|
-
if (el.is2many && !entry[elName].length) delete entry[elName]
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
63
|
const hasDeepInsert = (model, cqn) => {
|
|
76
64
|
if (cqn.INSERT.entries) {
|
|
77
65
|
const entity = _entityFromINSERT(model, cqn.INSERT)
|
|
78
66
|
if (entity) {
|
|
79
67
|
return !!cqn.INSERT.entries.find(entry => {
|
|
80
68
|
return !!Object.keys(entry || {}).find(k => {
|
|
81
|
-
return
|
|
69
|
+
return _hasCompOrAssoc(entity, k)
|
|
82
70
|
})
|
|
83
71
|
})
|
|
84
72
|
}
|
|
@@ -111,7 +99,6 @@ const getDeepInsertCQNs = (model, cqn) => {
|
|
|
111
99
|
}
|
|
112
100
|
|
|
113
101
|
module.exports = {
|
|
114
|
-
cleanEmptyCompositionsOfMany,
|
|
115
102
|
hasDeepInsert,
|
|
116
103
|
getDeepInsertCQNs
|
|
117
104
|
}
|
|
@@ -30,9 +30,8 @@ const _getFiltered = err => {
|
|
|
30
30
|
Object.keys(err)
|
|
31
31
|
.concat(['message'])
|
|
32
32
|
.forEach(k => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
33
|
+
// REVISIT: do not remove innererror with cds^6
|
|
34
|
+
if (k === 'innererror' && process.env.NODE_ENV === 'production' && !err[SKIP_SANITIZATION]) return
|
|
36
35
|
if (ALLOWED_PROPERTIES.includes(k) || k.startsWith('@')) {
|
|
37
36
|
error[k] = err[k]
|
|
38
37
|
} else if (k === 'numericSeverity') {
|
|
@@ -50,7 +50,7 @@ const _getDataSubjectUp = (role, model, entity, prev, next, result) => {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const _getDataSubjectDown = (role, entity, prev, next) => {
|
|
53
|
-
const associations = Object.values(entity.associations).filter(e => !e._isBacklink)
|
|
53
|
+
const associations = Object.values(entity.associations || {}).filter(e => !e._isBacklink)
|
|
54
54
|
for (const element of associations) {
|
|
55
55
|
const me = { entity, relative: entity, element }
|
|
56
56
|
if (_ifDataSubject(element._target, role)) {
|
|
@@ -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 },
|
|
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
|
-
|
|
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
|
-
|
|
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
|
*
|
|
@@ -1326,6 +1345,17 @@ class JoinCQNFromExpanded {
|
|
|
1326
1345
|
}
|
|
1327
1346
|
}
|
|
1328
1347
|
|
|
1348
|
+
if (readToOneCQN[IS_ACTIVE] && readToOneCQN.columns.length > 0) {
|
|
1349
|
+
readToOneCQN.columns.forEach(column => {
|
|
1350
|
+
if (
|
|
1351
|
+
column.as === `${parentAlias}_IsActiveEntity` ||
|
|
1352
|
+
column.as === `${parentAlias}_HasActiveEntity` ||
|
|
1353
|
+
column.as === `${parentAlias}_HasDraftEntity`
|
|
1354
|
+
)
|
|
1355
|
+
columns.push(column)
|
|
1356
|
+
})
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1329
1359
|
const subSelect = Object.assign({}, readToOneCQN, { columns })
|
|
1330
1360
|
|
|
1331
1361
|
const SELECT = { from: { SELECT: subSelect }, columns: outerColumns, distinct: true }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { hasDeepInsert, getDeepInsertCQNs
|
|
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
|
-
|
|
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.
|
|
392
|
+
sqls.push(this._getOrderByElement(noQuoting, toMatch, element))
|
|
389
393
|
continue
|
|
390
394
|
}
|
|
391
395
|
}
|
|
@@ -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) {
|
|
@@ -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
|
|
@@ -246,6 +246,7 @@ const run = async (
|
|
|
246
246
|
|
|
247
247
|
LOG._warn && LOG.warn(sanitizedError)
|
|
248
248
|
|
|
249
|
+
// REVISIT: switch from innererror to reason in cds^6
|
|
249
250
|
throw Object.assign(new Error(e.message), { statusCode: 502, innererror: sanitizedError })
|
|
250
251
|
}
|
|
251
252
|
|
|
@@ -267,6 +268,7 @@ const run = async (
|
|
|
267
268
|
|
|
268
269
|
LOG._warn && LOG.warn(sanitizedError)
|
|
269
270
|
|
|
271
|
+
// REVISIT: switch from innererror to reason in cds^6
|
|
270
272
|
throw Object.assign(new Error(`Error during request to remote service: ${e.message}`), {
|
|
271
273
|
statusCode: 502,
|
|
272
274
|
innererror: sanitizedError
|
|
@@ -294,6 +296,8 @@ const run = async (
|
|
|
294
296
|
: 'Request to remote service failed.'
|
|
295
297
|
const sanitizedError = _getSanitizedError(contentJSON, requestConfig)
|
|
296
298
|
LOG._warn && LOG.warn(sanitizedError)
|
|
299
|
+
|
|
300
|
+
// REVISIT: switch from innererror to reason in cds^6
|
|
297
301
|
throw Object.assign(new Error(contentJSON.message), { statusCode: 502, innererror: sanitizedError })
|
|
298
302
|
}
|
|
299
303
|
}
|
|
@@ -5,16 +5,14 @@ const { entriesStructureToEntityStructure } = require('./utils')
|
|
|
5
5
|
module.exports = async (service, entityFQN, selection) => {
|
|
6
6
|
const filter = getArgumentByName(selection.arguments, ARGUMENT.FILTER)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const queryBeforeUpdate = service.read(entityFQN)
|
|
9
9
|
queryBeforeUpdate.columns(astToColumns(selection.selectionSet.selections))
|
|
10
10
|
|
|
11
11
|
if (filter) {
|
|
12
12
|
queryBeforeUpdate.where(astToWhere(filter))
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
let query = service.update(entityFQN)
|
|
15
|
+
const query = service.update(entityFQN)
|
|
18
16
|
|
|
19
17
|
if (filter) {
|
|
20
18
|
query.where(astToWhere(filter))
|
|
@@ -24,7 +22,12 @@ module.exports = async (service, entityFQN, selection) => {
|
|
|
24
22
|
const entries = entriesStructureToEntityStructure(service, entityFQN, astToEntries(input))
|
|
25
23
|
query.with(entries)
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
let resultBeforeUpdate
|
|
26
|
+
const result = await service.tx(async tx => {
|
|
27
|
+
// read needs to be done before the update, otherwise the where clause might become invalid (case that properties in where clause are updated by the mutation)
|
|
28
|
+
resultBeforeUpdate = await service.tx(tx => tx.run(queryBeforeUpdate))
|
|
29
|
+
return tx.run(query)
|
|
30
|
+
})
|
|
28
31
|
|
|
29
32
|
// Merge selected fields with updated data
|
|
30
33
|
return resultBeforeUpdate.map(original => ({ ...original, ...result }))
|