@sap/cds 7.8.2 → 7.9.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 +45 -0
- package/_i18n/i18n_ar.properties +3 -0
- package/_i18n/i18n_cs.properties +3 -0
- package/_i18n/i18n_da.properties +3 -0
- package/_i18n/i18n_es_MX.properties +3 -0
- package/_i18n/i18n_fi.properties +3 -0
- package/_i18n/i18n_hu.properties +6 -0
- package/_i18n/i18n_ko.properties +3 -0
- package/_i18n/i18n_ms.properties +3 -0
- package/_i18n/i18n_nl.properties +3 -0
- package/_i18n/i18n_no.properties +3 -0
- package/_i18n/i18n_ro.properties +3 -0
- package/_i18n/i18n_sv.properties +3 -0
- package/_i18n/i18n_th.properties +3 -0
- package/_i18n/i18n_tr.properties +6 -0
- package/_i18n/i18n_zh_TW.properties +3 -0
- package/bin/serve.js +5 -5
- package/lib/auth/basic-auth.js +1 -1
- package/lib/compile/cdsc.js +33 -6
- package/lib/compile/etc/_localized.js +14 -7
- package/lib/compile/for/lean_drafts.js +9 -0
- package/lib/compile/to/edm-files.js +116 -0
- package/lib/compile/to/edm.js +8 -1
- package/lib/compile/to/hdbtabledata.js +3 -3
- package/lib/compile/to/sql.js +4 -2
- package/lib/compile/to/srvinfo.js +6 -5
- package/lib/compile/to/yaml.js +22 -21
- package/lib/dbs/cds-deploy.js +5 -6
- package/lib/env/cds-env.js +7 -0
- package/lib/env/cds-requires.js +20 -1
- package/lib/env/defaults.js +21 -5
- package/lib/env/schemas/cds-package.js +1 -1
- package/lib/env/schemas/cds-rc.js +85 -4
- package/lib/index.js +1 -1
- package/lib/linked/entities.js +10 -0
- package/lib/linked/models.js +1 -1
- package/lib/plugins.js +1 -1
- package/lib/ql/INSERT.js +17 -3
- package/lib/ql/Query.js +4 -0
- package/lib/ql/infer.js +1 -1
- package/lib/req/request.js +1 -1
- package/lib/srv/cds-serve.js +1 -0
- package/lib/srv/middlewares/cds-context.js +1 -1
- package/lib/srv/protocols/odata-v4.js +5 -6
- package/lib/srv/srv-models.js +9 -2
- package/lib/utils/cds-test.js +2 -0
- package/lib/utils/cds-utils.js +9 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +3 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +22 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +4 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +4 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +38 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +32 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +2 -274
- package/libx/_runtime/{cds-services/services → common}/Service.js +39 -29
- package/libx/_runtime/common/generic/auth/autoexpose.js +41 -0
- package/libx/_runtime/common/generic/auth/index.js +2 -0
- package/libx/_runtime/common/generic/auth/readOnly.js +0 -11
- package/libx/_runtime/common/generic/auth/restrict.js +6 -5
- package/libx/_runtime/common/generic/auth/utils.js +1 -1
- package/libx/_runtime/common/generic/crud.js +5 -8
- package/libx/_runtime/common/generic/etag.js +8 -6
- package/libx/_runtime/common/generic/sorting.js +2 -2
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/{cds-services/services → common}/utils/columns.js +4 -4
- package/libx/_runtime/common/utils/compareJson.js +274 -0
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/{cds-services/services → common}/utils/differ.js +8 -8
- package/libx/_runtime/common/utils/ensureIEEE754.js +29 -0
- package/libx/_runtime/common/utils/{postProcessing.js → postProcess.js} +1 -3
- package/libx/_runtime/common/utils/resolveView.js +0 -16
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
- package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/streamProp.js +9 -2
- package/libx/_runtime/common/utils/ucsn.js +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +5 -3
- package/libx/_runtime/db/generic/rewrite.js +7 -13
- package/libx/_runtime/fiori/generic/activate.js +1 -1
- package/libx/_runtime/fiori/generic/edit.js +1 -1
- package/libx/_runtime/fiori/generic/prepare.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +151 -46
- package/libx/_runtime/fiori/utils/handler.js +1 -1
- package/libx/_runtime/hana/execute.js +6 -2
- package/libx/_runtime/hana/pool.js +3 -0
- package/libx/_runtime/hana/search2cqn4sql.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
- package/libx/_runtime/messaging/event-broker.js +212 -0
- package/libx/_runtime/remote/Service.js +9 -32
- package/libx/_runtime/remote/utils/client.js +13 -21
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +7 -1
- package/libx/_runtime/sqlite/execute.js +8 -3
- package/libx/_runtime/ucl/Service.js +259 -0
- package/libx/common/assert/index.js +5 -11
- package/libx/common/assert/validation.js +6 -1
- package/libx/odata/index.js +47 -25
- package/libx/odata/middleware/batch.js +8 -7
- package/libx/odata/middleware/create.js +42 -16
- package/libx/odata/middleware/delete.js +18 -11
- package/libx/odata/middleware/metadata.js +15 -14
- package/libx/odata/middleware/operation.js +30 -40
- package/libx/odata/middleware/parse.js +2 -3
- package/libx/odata/middleware/read.js +59 -52
- package/libx/odata/middleware/service-document.js +7 -7
- package/libx/odata/middleware/stream.js +26 -24
- package/libx/odata/middleware/update.js +53 -92
- package/libx/odata/parse/afterburner.js +45 -47
- package/libx/odata/parse/grammar.peggy +3 -3
- package/libx/odata/parse/multipartToJson.js +10 -22
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/etag.js +13 -0
- package/libx/odata/utils/handler.js +120 -0
- package/libx/odata/utils/index.js +15 -2
- package/libx/odata/utils/metaInfo.js +410 -0
- package/libx/odata/utils/path.js +5 -2
- package/libx/odata/utils/readAfterWrite.js +23 -0
- package/libx/odata/utils/result.js +4 -5
- package/libx/rest/RestAdapter.js +4 -13
- package/libx/rest/middleware/parse.js +40 -7
- package/package.json +1 -1
- package/server.js +1 -0
- package/libx/_runtime/cds-services/util/dataProcessUtils.js +0 -93
- package/libx/_runtime/common/utils/thenable.js +0 -51
- package/libx/_runtime/rest/service.js +0 -2
- package/libx/odata/parse/parseToCqn.js +0 -39
- package/libx/rest/middleware/input.js +0 -54
- package/libx/rest/middleware/payload.js +0 -13
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
|
|
2
|
+
|
|
3
|
+
const _deepEqual = (val1, val2) => {
|
|
4
|
+
if (val1 && typeof val1 === 'object' && val2 && typeof val2 === 'object') {
|
|
5
|
+
for (const key in val1) {
|
|
6
|
+
if (!_deepEqual(val1[key], val2[key])) return false
|
|
7
|
+
}
|
|
8
|
+
return true
|
|
9
|
+
}
|
|
10
|
+
return val1 === val2
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const _getCorrespondingEntryWithSameKeys = (source, entry, keys) => {
|
|
14
|
+
const idx = _getIdxCorrespondingEntryWithSameKeys(source, entry, keys)
|
|
15
|
+
return idx !== -1 ? source[idx] : undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const _getIdxCorrespondingEntryWithSameKeys = (source, entry, keys) =>
|
|
19
|
+
source.findIndex(sourceEntry => keys.every(key => _deepEqual(sourceEntry[key], entry[key])))
|
|
20
|
+
|
|
21
|
+
const _getKeysOfEntity = entity =>
|
|
22
|
+
Object.keys(entity.keys).filter(key => !(key in DRAFT_COLUMNS_MAP) && !entity.elements[key].isAssociation)
|
|
23
|
+
|
|
24
|
+
const _getCompositionsOfEntity = entity => Object.keys(entity.elements).filter(e => entity.elements[e].isComposition)
|
|
25
|
+
|
|
26
|
+
const _createToBeDeletedEntries = (oldEntry, entity, keys, compositions) => {
|
|
27
|
+
const toBeDeletedEntry = {
|
|
28
|
+
_op: 'delete'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const prop in oldEntry) {
|
|
32
|
+
if (prop in DRAFT_COLUMNS_MAP) {
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
if (keys.includes(prop)) {
|
|
36
|
+
toBeDeletedEntry[prop] = oldEntry[prop]
|
|
37
|
+
} else if (compositions.includes(prop) && oldEntry[prop]) {
|
|
38
|
+
toBeDeletedEntry[prop] = entity.elements[prop].is2one
|
|
39
|
+
? _createToBeDeletedEntries(
|
|
40
|
+
oldEntry[prop],
|
|
41
|
+
entity.elements[prop]._target,
|
|
42
|
+
_getKeysOfEntity(entity.elements[prop]._target),
|
|
43
|
+
_getCompositionsOfEntity(entity.elements[prop]._target)
|
|
44
|
+
)
|
|
45
|
+
: oldEntry[prop].map(entry =>
|
|
46
|
+
_createToBeDeletedEntries(
|
|
47
|
+
entry,
|
|
48
|
+
entity.elements[prop]._target,
|
|
49
|
+
_getKeysOfEntity(entity.elements[prop]._target),
|
|
50
|
+
_getCompositionsOfEntity(entity.elements[prop]._target)
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
} else {
|
|
54
|
+
toBeDeletedEntry._old = toBeDeletedEntry._old || {}
|
|
55
|
+
toBeDeletedEntry._old[prop] = oldEntry[prop]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return toBeDeletedEntry
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const _hasOpDeep = (entry, element) => {
|
|
63
|
+
const entryArray = Array.isArray(entry) ? entry : [entry]
|
|
64
|
+
for (const entry_ of entryArray) {
|
|
65
|
+
if (entry_._op) return true
|
|
66
|
+
|
|
67
|
+
if (element && element.isComposition) {
|
|
68
|
+
const target = element._target
|
|
69
|
+
for (const prop in entry_) {
|
|
70
|
+
if (_hasOpDeep(entry_[prop], target.elements[prop])) {
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const _addCompositionsToResult = (result, entity, prop, newValue, oldValue, opts) => {
|
|
81
|
+
/*
|
|
82
|
+
* REVISIT: the current impl results in {} instead of keeping null for compo to one.
|
|
83
|
+
* unfortunately, many follow-up errors occur (e.g., prop in null checks) if changed.
|
|
84
|
+
*/
|
|
85
|
+
let composition
|
|
86
|
+
if (
|
|
87
|
+
newValue[prop] &&
|
|
88
|
+
typeof newValue[prop] === 'object' &&
|
|
89
|
+
!Array.isArray(newValue[prop]) &&
|
|
90
|
+
Object.keys(newValue[prop]).length === 0
|
|
91
|
+
) {
|
|
92
|
+
composition = compareJsonDeep(entity.elements[prop]._target, undefined, oldValue && oldValue[prop], opts)
|
|
93
|
+
} else {
|
|
94
|
+
composition = compareJsonDeep(entity.elements[prop]._target, newValue[prop], oldValue && oldValue[prop], opts)
|
|
95
|
+
}
|
|
96
|
+
if (composition.some(c => _hasOpDeep(c, entity.elements[prop]))) {
|
|
97
|
+
result[prop] = entity.elements[prop].is2one ? composition[0] : composition
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const _addPrimitiveValuesAndOperatorToResult = (result, prop, newValue, oldValue) => {
|
|
102
|
+
result[prop] = newValue[prop]
|
|
103
|
+
|
|
104
|
+
if (!result._op) {
|
|
105
|
+
result._op = oldValue ? 'update' : 'create'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (result._op === 'update') {
|
|
109
|
+
result._old = result._old || {}
|
|
110
|
+
result._old[prop] = oldValue[prop]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const _addKeysToResult = (result, prop, newValue, oldValue) => {
|
|
115
|
+
result[prop] = newValue[prop]
|
|
116
|
+
if (!oldValue) {
|
|
117
|
+
result._op = 'create'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const _addToBeDeletedEntriesToResult = (results, entity, keys, newValues, oldValues) => {
|
|
122
|
+
// add to be deleted entries
|
|
123
|
+
for (const oldEntry of oldValues) {
|
|
124
|
+
const entry = _getCorrespondingEntryWithSameKeys(newValues, oldEntry, keys)
|
|
125
|
+
|
|
126
|
+
if (!entry) {
|
|
127
|
+
// prepare to be deleted (deep) entry without manipulating oldData
|
|
128
|
+
const toBeDeletedEntry = _createToBeDeletedEntries(oldEntry, entity, keys, _getCompositionsOfEntity(entity))
|
|
129
|
+
results.push(toBeDeletedEntry)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const _normalizeToArray = value => (Array.isArray(value) ? value : value === null ? [] : [value])
|
|
135
|
+
|
|
136
|
+
const _isUnManaged = element => {
|
|
137
|
+
return element.on && !element._isSelfManaged
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const _skip = (entity, prop) => entity.elements[prop]._target._hasPersistenceSkip
|
|
141
|
+
|
|
142
|
+
const _skipToOne = (entity, prop) => {
|
|
143
|
+
return (
|
|
144
|
+
entity.elements[prop] && entity.elements[prop].is2one && _skip(entity, prop) && _isUnManaged(entity.elements[prop])
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const _skipToMany = (entity, prop) => {
|
|
149
|
+
return entity.elements[prop] && entity.elements[prop].is2many && _skip(entity, prop)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Returns all property names from the new entry and add missing managed elements
|
|
153
|
+
const _propertiesAndManaged = (newEntry, entity) => {
|
|
154
|
+
return [
|
|
155
|
+
...Object.getOwnPropertyNames(newEntry),
|
|
156
|
+
...Object.keys(entity.elements).filter(
|
|
157
|
+
elementName => newEntry[elementName] === undefined && entity.elements[elementName]['@cds.on.update']
|
|
158
|
+
)
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const _iteratePropsInNewEntry = (newEntry, keys, result, oldEntry, entity, opts) => {
|
|
163
|
+
// On app-service layer, generated foreign keys are not enumerable,
|
|
164
|
+
// include them here too.
|
|
165
|
+
for (const prop of _propertiesAndManaged(newEntry, entity)) {
|
|
166
|
+
if (keys.includes(prop)) {
|
|
167
|
+
_addKeysToResult(result, prop, newEntry, oldEntry)
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// if value did not change --> ignored
|
|
172
|
+
if (newEntry[prop] === (oldEntry && oldEntry[prop]) || (opts.ignoreDraftColumns && prop in DRAFT_COLUMNS_MAP)) {
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (_skipToMany(entity, prop)) {
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (_skipToOne(entity, prop)) {
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (entity.elements[prop] && entity.elements[prop].isComposition) {
|
|
185
|
+
_addCompositionsToResult(result, entity, prop, newEntry, oldEntry, opts)
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_addPrimitiveValuesAndOperatorToResult(result, prop, newEntry, oldEntry)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const compareJsonDeep = (entity, newValue = [], oldValue = [], opts) => {
|
|
194
|
+
const resultsArray = []
|
|
195
|
+
const keys = _getKeysOfEntity(entity)
|
|
196
|
+
|
|
197
|
+
// normalize input
|
|
198
|
+
const newValues = _normalizeToArray(newValue)
|
|
199
|
+
const oldValues = _normalizeToArray(oldValue)
|
|
200
|
+
|
|
201
|
+
// add to be created and to be updated entries
|
|
202
|
+
for (const newEntry of newValues) {
|
|
203
|
+
const result = {}
|
|
204
|
+
const oldEntry = _getCorrespondingEntryWithSameKeys(oldValues, newEntry, keys)
|
|
205
|
+
_iteratePropsInNewEntry(newEntry, keys, result, oldEntry, entity, opts)
|
|
206
|
+
resultsArray.push(result)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_addToBeDeletedEntriesToResult(resultsArray, entity, keys, newValues, oldValues)
|
|
210
|
+
|
|
211
|
+
return resultsArray
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Compares newValue with oldValues in a deep fashion.
|
|
216
|
+
* Output format is newValue with additional administrative properties.
|
|
217
|
+
* - "_op" provides info about the CRUD action to perform
|
|
218
|
+
* - "_old" provides info about the current DB state
|
|
219
|
+
*
|
|
220
|
+
* Unchanged values are not part of the result.
|
|
221
|
+
*
|
|
222
|
+
* Output format is:
|
|
223
|
+
* {
|
|
224
|
+
* _op: 'update',
|
|
225
|
+
* _old: { orderedAt: 'DE' },
|
|
226
|
+
* ID: 1,
|
|
227
|
+
* orderedAt: 'EN',
|
|
228
|
+
* items: [
|
|
229
|
+
* {
|
|
230
|
+
* _op: 'update',
|
|
231
|
+
* _old: { amount: 7 },
|
|
232
|
+
* ID: 7,
|
|
233
|
+
* amount: 8
|
|
234
|
+
* },
|
|
235
|
+
* {
|
|
236
|
+
* _op: 'create',
|
|
237
|
+
* ID: 8,
|
|
238
|
+
* amount: 8
|
|
239
|
+
* },
|
|
240
|
+
* {
|
|
241
|
+
* _op: 'delete',
|
|
242
|
+
* _old: {
|
|
243
|
+
* amount: 6
|
|
244
|
+
* },
|
|
245
|
+
* ID: 6
|
|
246
|
+
* }
|
|
247
|
+
* ]
|
|
248
|
+
* }
|
|
249
|
+
*
|
|
250
|
+
*
|
|
251
|
+
* If there is no change in an UPDATE, result is an object containing only the keys of the entity.
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* compareJson(csnEntity, [{ID: 1, col1: 'A'}], [{ID: 1, col1: 'B'}])
|
|
255
|
+
*
|
|
256
|
+
* @param oldValue
|
|
257
|
+
* @param {object} entity
|
|
258
|
+
* @param {Array | object} newValue
|
|
259
|
+
* @param {Array} oldValues
|
|
260
|
+
*
|
|
261
|
+
* @returns {Array}
|
|
262
|
+
*/
|
|
263
|
+
const compareJson = (newValue, oldValue, entity, opts = {}) => {
|
|
264
|
+
const options = Object.assign({ ignoreDraftColumns: false }, opts)
|
|
265
|
+
const result = compareJsonDeep(entity, newValue, oldValue, options)
|
|
266
|
+
|
|
267
|
+
// in case of batch insert, result is an array
|
|
268
|
+
// in all other cases it is an array with just one entry
|
|
269
|
+
return Array.isArray(newValue) ? result : result[0]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = {
|
|
273
|
+
compareJson
|
|
274
|
+
}
|
|
@@ -14,7 +14,7 @@ const { getEntityFromPath } = require('../../common/utils/path')
|
|
|
14
14
|
const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
15
15
|
const { addRefToWhereIfNecessary } = require('../../../odata/parse/afterburner')
|
|
16
16
|
const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
|
|
17
|
-
const { getColumns } = require('
|
|
17
|
+
const { getColumns } = require('./columns')
|
|
18
18
|
|
|
19
19
|
const _elementFromRef = (name, entity) => {
|
|
20
20
|
if (!entity) return
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
const cds = require('
|
|
1
|
+
const cds = require('../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
4
|
const { compareJson } = require('./compareJson')
|
|
5
|
-
const { selectDeepUpdateData } = require('
|
|
6
|
-
const { ensureDraftsSuffix } = require('
|
|
5
|
+
const { selectDeepUpdateData } = require('../composition')
|
|
6
|
+
const { ensureDraftsSuffix } = require('../../fiori/utils/handler')
|
|
7
7
|
|
|
8
|
-
const { DRAFT_COLUMNS_MAP } = require('
|
|
9
|
-
const { cqn2cqn4sql, convertPathExpressionToWhere } = require('
|
|
10
|
-
const { revertData } = require('
|
|
11
|
-
const { removeIsActiveEntityRecursively } = require('
|
|
12
|
-
const { enrichDataWithKeysFromWhere } = require('
|
|
8
|
+
const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
|
|
9
|
+
const { cqn2cqn4sql, convertPathExpressionToWhere } = require('./cqn2cqn4sql')
|
|
10
|
+
const { revertData } = require('./resolveView')
|
|
11
|
+
const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
12
|
+
const { enrichDataWithKeysFromWhere } = require('./keys')
|
|
13
13
|
|
|
14
14
|
module.exports = class Differ {
|
|
15
15
|
constructor(srv) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const getTemplate = require('./template')
|
|
2
|
+
const templateProcessor = require('./templateProcessor')
|
|
3
|
+
|
|
4
|
+
const _pick = element => {
|
|
5
|
+
return element._type in { 'cds.Decimal': 1, 'cds.Integer64': 1 }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const _processorFn = () => elementInfo => {
|
|
9
|
+
const { row, key } = elementInfo
|
|
10
|
+
if (typeof row?.[key] !== 'number') return
|
|
11
|
+
row[key] = `${row[key]}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ensureIEEE754 = (target, service, result) => {
|
|
15
|
+
const { model } = service
|
|
16
|
+
if (!target || !result || !model || !model.definitions[target.name]) return
|
|
17
|
+
|
|
18
|
+
const template = getTemplate('ensureIEEE754', service, target, { pick: _pick })
|
|
19
|
+
if (template.elements.size === 0) return
|
|
20
|
+
|
|
21
|
+
if (typeof result === 'object' && result != null) {
|
|
22
|
+
const processFn = _processorFn()
|
|
23
|
+
for (const row of Array.isArray(result) ? result : [result]) {
|
|
24
|
+
templateProcessor({ processFn, row, template })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = ensureIEEE754
|
|
@@ -714,21 +714,6 @@ const resolveView = (query, model, service) => {
|
|
|
714
714
|
return newQuery
|
|
715
715
|
}
|
|
716
716
|
|
|
717
|
-
/**
|
|
718
|
-
* Restores the link of req.data and req.query in case req.query was overwritten.
|
|
719
|
-
* Only applicable for UPDATEs and INSERTs.
|
|
720
|
-
*
|
|
721
|
-
* @param {*} req
|
|
722
|
-
*/
|
|
723
|
-
const restoreLink = req => {
|
|
724
|
-
if (req.query.INSERT?.entries) {
|
|
725
|
-
if (Array.isArray(req.query.INSERT.entries)) req.data = req.query.INSERT.entries[0]
|
|
726
|
-
else req.data = req.query.INSERT.entries
|
|
727
|
-
} else if (req.query.UPDATE?.data) {
|
|
728
|
-
req.data = req.query.UPDATE.data
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
717
|
/**
|
|
733
718
|
* Retrieves the actual query target by evaluating the created transitions.
|
|
734
719
|
* @param {*} q - the resolved query
|
|
@@ -753,6 +738,5 @@ module.exports = {
|
|
|
753
738
|
getDBTable,
|
|
754
739
|
resolveView,
|
|
755
740
|
getTransition,
|
|
756
|
-
restoreLink,
|
|
757
741
|
revertData
|
|
758
742
|
}
|
|
@@ -42,7 +42,7 @@ const _resolveTarget = (ref, target) => {
|
|
|
42
42
|
const element = target.elements[_ref]
|
|
43
43
|
if (element) return element._target
|
|
44
44
|
|
|
45
|
-
throw cds.error(`Navigation property
|
|
45
|
+
throw cds.error(`Navigation property "${_ref}" is not defined in ${target.name}`, { code: 400 })
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const rewriteExpandAsterisk = (columns, target) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { computeColumnsToBeSearched } = require('
|
|
1
|
+
const { computeColumnsToBeSearched } = require('./columns')
|
|
2
2
|
const searchToLike = require('./searchToLike')
|
|
3
3
|
const { getEntityNameFromCQN } = require('./entityFromCqn')
|
|
4
4
|
|
|
@@ -3,7 +3,9 @@ const { ensureNoDraftsSuffix, ensureUnlocalized } = require('../../fiori/utils/h
|
|
|
3
3
|
const { isDuplicate } = require('./rewriteAsterisks')
|
|
4
4
|
|
|
5
5
|
const _addColumn = (name, type, columns, url) => {
|
|
6
|
+
// we do not want to return these additional odata properties with new adapter
|
|
6
7
|
if (cds.env.features.odata_new_adapter) return
|
|
8
|
+
|
|
7
9
|
if (typeof type === 'object') {
|
|
8
10
|
let mType = type['='].replaceAll(/\./g, '_')
|
|
9
11
|
const ref = {
|
|
@@ -27,6 +29,7 @@ const _addColumn = (name, type, columns, url) => {
|
|
|
27
29
|
|
|
28
30
|
const _addColumns = (target, columns) => {
|
|
29
31
|
if (cds.env.features.odata_new_adapter) return
|
|
32
|
+
|
|
30
33
|
for (const k in target.elements) {
|
|
31
34
|
const el = target.elements[k]
|
|
32
35
|
if (el['@Core.MediaType']) {
|
|
@@ -35,6 +38,7 @@ const _addColumns = (target, columns) => {
|
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
// eslint-disable-next-line complexity
|
|
38
42
|
const handleStreamProperties = (target, columns, model) => {
|
|
39
43
|
if (!target || !model || !columns) return
|
|
40
44
|
|
|
@@ -44,11 +48,14 @@ const handleStreamProperties = (target, columns, model) => {
|
|
|
44
48
|
const name = col.ref && col.ref[col.ref.length - 1]
|
|
45
49
|
const element = name && target.elements[name]
|
|
46
50
|
const type = element && element.type
|
|
47
|
-
const mediaType = element
|
|
51
|
+
const mediaType = element?.['@Core.MediaType']
|
|
52
|
+
|
|
53
|
+
// new adapter handles urls correctly and just returns them as they are, okra handles them wrongly due to wrong EDM generation by compiler
|
|
54
|
+
const ignoreMediaType = cds.env.features.odata_new_adapter && mediaType && element['@Core.IsURL']
|
|
48
55
|
|
|
49
56
|
if (col === '*') {
|
|
50
57
|
_addColumns(target, columns)
|
|
51
|
-
} else if (col.ref && (type === 'cds.LargeBinary' || mediaType)) {
|
|
58
|
+
} else if (col.ref && (type === 'cds.LargeBinary' || (mediaType && !ignoreMediaType))) {
|
|
52
59
|
if (mediaType) {
|
|
53
60
|
_addColumn(name, mediaType, columns, element['@Core.IsURL'])
|
|
54
61
|
columns.splice(index, 1)
|
|
@@ -75,7 +75,7 @@ const _cleanup = (row, definition, cleanupNull, cleanupStruct, errors, prefix =
|
|
|
75
75
|
_cleanup(row[key], definition, cleanupNull, cleanupStruct, errors, [...prefix, key])
|
|
76
76
|
} else {
|
|
77
77
|
if (errors) {
|
|
78
|
-
errors.push(getError(400,
|
|
78
|
+
errors.push(getError(400, `Property "${key}" does not exist in ${definition.name}`))
|
|
79
79
|
}
|
|
80
80
|
delete row[key]
|
|
81
81
|
}
|
|
@@ -1024,7 +1024,7 @@ class JoinCQNFromExpanded {
|
|
|
1024
1024
|
// Add table alias or name to handle cases, where joined tables have same column names
|
|
1025
1025
|
if (this._isElement(column.ref, entity)) {
|
|
1026
1026
|
const alias = tableAlias || ensureNoDraftsSuffix(entity.name)
|
|
1027
|
-
aliasedElement.ref = alias ? [alias, column.ref
|
|
1027
|
+
aliasedElement.ref = alias ? [alias, ...column.ref] : [...column.ref]
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
1030
|
if (skipMapping) return aliasedElement
|
|
@@ -1050,8 +1050,10 @@ class JoinCQNFromExpanded {
|
|
|
1050
1050
|
}
|
|
1051
1051
|
|
|
1052
1052
|
_isElement(ref, entity) {
|
|
1053
|
-
if (!ref
|
|
1054
|
-
|
|
1053
|
+
if (!ref) return false
|
|
1054
|
+
if (ref.length > 1) {
|
|
1055
|
+
return entity.elements[ref[0]]?.isAssociation
|
|
1056
|
+
}
|
|
1055
1057
|
// Normal element
|
|
1056
1058
|
if (entity.elements[ref[0]]) return true
|
|
1057
1059
|
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
|
|
3
3
|
const { generateAliases } = require('../utils/generateAliases')
|
|
4
|
-
const { restoreLink } = require('../../common/utils/resolveView')
|
|
5
4
|
|
|
6
|
-
const
|
|
5
|
+
const _restoreLink = req => {
|
|
7
6
|
if (req.query.INSERT?.entries) {
|
|
8
|
-
|
|
9
|
-
return req.data === req.query.INSERT.entries
|
|
7
|
+
return (req.data = Array.isArray(req.query.INSERT.entries) ? req.query.INSERT.entries[0] : req.query.INSERT.entries)
|
|
10
8
|
}
|
|
11
|
-
|
|
12
9
|
if (req.query.UPDATE?.data) {
|
|
13
|
-
return req.data
|
|
10
|
+
return (req.data = req.query.UPDATE.data)
|
|
14
11
|
}
|
|
15
12
|
}
|
|
16
13
|
|
|
@@ -26,19 +23,16 @@ function handler(req) {
|
|
|
26
23
|
return
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
// for restore link to req.data
|
|
32
|
-
const linked = _isLinked(req)
|
|
26
|
+
const _streaming = cds.env.features.stream_compat && req.query._streaming
|
|
33
27
|
|
|
34
28
|
// convert to sql cqn
|
|
35
29
|
req.query = cqn2cqn4sql(req.query, this.model, { service: this })
|
|
36
30
|
|
|
37
|
-
// REVISIT: should not be necessary
|
|
38
31
|
// restore link to req.data
|
|
39
|
-
|
|
32
|
+
_restoreLink(req)
|
|
33
|
+
|
|
34
|
+
if (_streaming) req.query._streaming = _streaming
|
|
40
35
|
|
|
41
|
-
if (cds.env.features.stream_compat && streaming) req.query._streaming = streaming
|
|
42
36
|
generateAliases(req.query)
|
|
43
37
|
}
|
|
44
38
|
|
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
} = require('../utils/handler')
|
|
11
11
|
const { readAndDeleteKeywords, isActiveEntityRequested, getKeyData } = require('../utils/where')
|
|
12
12
|
const { isDraftRootEntity } = require('../../fiori/utils/csn')
|
|
13
|
-
const { getColumns } = require('../../
|
|
13
|
+
const { getColumns } = require('../../common/utils/columns')
|
|
14
14
|
const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
|
|
15
15
|
|
|
16
16
|
const _getRootCQN = (context, requestActiveData) => {
|
|
@@ -3,7 +3,7 @@ const getLockInfo = require('../utils/lockInfo')
|
|
|
3
3
|
const { INSERT, SELECT, DELETE } = cds.ql
|
|
4
4
|
|
|
5
5
|
const { getCompositionTree } = require('../../common/composition')
|
|
6
|
-
const { getColumns } = require('../../
|
|
6
|
+
const { getColumns } = require('../../common/utils/columns')
|
|
7
7
|
const { draftIsLocked, ensureDraftsSuffix, ensureNoDraftsSuffix, getSubCQNs } = require('../utils/handler')
|
|
8
8
|
const { isActiveEntityRequested } = require('../utils/where')
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ const { SELECT } = cds.ql
|
|
|
3
3
|
|
|
4
4
|
const { isActiveEntityRequested } = require('../utils/where')
|
|
5
5
|
const { ensureDraftsSuffix, ensureNoDraftsSuffix } = require('../utils/handler')
|
|
6
|
-
const { getColumns } = require('../../
|
|
6
|
+
const { getColumns } = require('../../common/utils/columns')
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Generic Handler for PreparationAction requests.
|