@sap/cds-dk 10.0.3 → 10.0.5
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 +28 -0
- package/bin/debug.js +6 -2
- package/bin/up.js +1 -1
- package/bin/watch.js +1 -0
- package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +2 -0
- package/lib/build/plugins/nodejs/index.js +6 -3
- package/lib/build/util.js +1 -1
- package/lib/init/template/ams/files/containerize.yaml.hbs +1 -0
- package/lib/init/template/approuter/files/values.yaml.hbs +9 -1
- package/lib/init/template/data/as-csv.js +4 -2
- package/lib/init/template/data/as-json.js +4 -1
- package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +6 -6
- package/lib/init/template/ias/files/values.yaml.hbs +4 -4
- package/lib/init/template/portal/files/values.yaml.hbs +12 -0
- package/lib/init/template/sample/files/app/browse/fiori-service.cds +2 -0
- package/lib/init/template/typer/files/xsconfig.json +1 -1
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/watch/esm-resolve-registration.js +27 -0
- package/lib/watch/vite-watch.js +1 -1
- package/node_modules/@cap-js/db-service/CHANGELOG.md +768 -0
- package/node_modules/@cap-js/db-service/LICENSE +201 -0
- package/node_modules/@cap-js/db-service/README.md +26 -0
- package/node_modules/@cap-js/db-service/index.js +19 -0
- package/node_modules/@cap-js/db-service/lib/InsertResults.js +122 -0
- package/node_modules/@cap-js/db-service/lib/SQLService.js +581 -0
- package/node_modules/@cap-js/db-service/lib/common/DatabaseService.js +179 -0
- package/node_modules/@cap-js/db-service/lib/common/factory.d.ts +5 -0
- package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +310 -0
- package/node_modules/@cap-js/db-service/lib/common/session-context.js +46 -0
- package/node_modules/@cap-js/db-service/lib/converters.d.ts +24 -0
- package/node_modules/@cap-js/db-service/lib/cql-functions.js +464 -0
- package/node_modules/@cap-js/db-service/lib/cqn2pql.js +116 -0
- package/node_modules/@cap-js/db-service/lib/cqn2sql.js +1612 -0
- package/node_modules/@cap-js/db-service/lib/cqn4sql.js +2895 -0
- package/node_modules/@cap-js/db-service/lib/deep-queries.js +290 -0
- package/node_modules/@cap-js/db-service/lib/fill-in-keys.js +75 -0
- package/node_modules/@cap-js/db-service/lib/infer/cqn.d.ts +42 -0
- package/node_modules/@cap-js/db-service/lib/infer/index.js +1290 -0
- package/node_modules/@cap-js/db-service/lib/infer/join-tree.js +278 -0
- package/node_modules/@cap-js/db-service/lib/infer/pseudos.js +24 -0
- package/node_modules/@cap-js/db-service/lib/search.js +220 -0
- package/node_modules/@cap-js/db-service/lib/utils.js +177 -0
- package/node_modules/@cap-js/db-service/package.json +36 -0
- package/node_modules/@cap-js/sqlite/CHANGELOG.md +404 -0
- package/node_modules/@cap-js/sqlite/LICENSE +201 -0
- package/node_modules/@cap-js/sqlite/README.md +34 -0
- package/node_modules/@cap-js/sqlite/cds-plugin.js +0 -0
- package/node_modules/@cap-js/sqlite/index.js +2 -0
- package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +324 -0
- package/node_modules/@cap-js/sqlite/lib/cql-functions.js +160 -0
- package/node_modules/@cap-js/sqlite/lib/node-sqlite.js +36 -0
- package/node_modules/@cap-js/sqlite/lib/session.json +3 -0
- package/node_modules/@cap-js/sqlite/lib/sql.js.js +99 -0
- package/node_modules/@cap-js/sqlite/package.json +68 -0
- package/node_modules/@sap/cds-compiler/CHANGELOG.md +14 -0
- package/node_modules/@sap/cds-compiler/bin/cdsse.js +1 -1
- package/node_modules/@sap/cds-compiler/bin/cdsv2m.js +1 -1
- package/node_modules/@sap/cds-compiler/doc/IncompatibleChanges_v7.md +20 -13
- package/node_modules/@sap/cds-compiler/lib/base/builtins.js +4 -1
- package/node_modules/@sap/cds-compiler/lib/base/message-registry.js +15 -2
- package/node_modules/@sap/cds-compiler/lib/checks/defaultValues.js +0 -1
- package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +6 -5
- package/node_modules/@sap/cds-compiler/lib/compiler/tweak-assocs.js +27 -6
- package/node_modules/@sap/cds-compiler/lib/edm/annotations/edmJson.js +5 -0
- package/node_modules/@sap/cds-compiler/lib/json/from-csn.js +3 -3
- package/node_modules/@sap/cds-compiler/lib/json/to-csn.js +5 -4
- package/node_modules/@sap/cds-compiler/lib/parsers/AstBuildingParser.js +14 -3
- package/node_modules/@sap/cds-compiler/lib/tool-lib/enrichCsn.js +1 -1
- package/node_modules/@sap/cds-compiler/lib/transform/db/cdsPersistence.js +3 -2
- package/node_modules/@sap/cds-compiler/lib/transform/db/views.js +2 -2
- package/node_modules/@sap/cds-compiler/lib/transform/transformUtils.js +1 -2
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/iconv-lite/encodings/sbcs-data.js +2 -0
- package/node_modules/iconv-lite/encodings/utf32.js +10 -3
- package/node_modules/iconv-lite/package.json +2 -2
- package/node_modules/iconv-lite/types/encodings.d.ts +2 -0
- package/node_modules/ws/lib/receiver.js +15 -32
- package/node_modules/ws/lib/websocket-server.js +4 -4
- package/node_modules/ws/lib/websocket.js +4 -4
- package/node_modules/ws/package.json +5 -1
- package/package.json +5 -3
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
const cds = require('@sap/cds')
|
|
2
|
+
const { _target_name4 } = require('./SQLService')
|
|
3
|
+
|
|
4
|
+
const ROOT = Symbol('root')
|
|
5
|
+
|
|
6
|
+
const compareJson = require('@sap/cds/libx/_runtime/common/utils/compareJson').compareJson
|
|
7
|
+
|
|
8
|
+
const handledDeep = Symbol('handledDeep')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @callback nextCallback
|
|
12
|
+
* @param {Error|undefined} error
|
|
13
|
+
* @returns {Promise<unknown>}
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {import('@sap/cds/apis/services').Request} req
|
|
18
|
+
* @param {nextCallback} next
|
|
19
|
+
* @returns {Promise<number>}
|
|
20
|
+
*/
|
|
21
|
+
async function onDeep(req, next) {
|
|
22
|
+
const { query } = req
|
|
23
|
+
if (handledDeep in query) return next()
|
|
24
|
+
|
|
25
|
+
// REVISIT: req.target does not match the query.INSERT target for path insert
|
|
26
|
+
// const target = query.sources[Object.keys(query.sources)[0]]
|
|
27
|
+
if (!this.model?.definitions[_target_name4(req.query)]) return next()
|
|
28
|
+
|
|
29
|
+
if (!hasDeep(query)) return next()
|
|
30
|
+
|
|
31
|
+
const target = this.infer(query)._target
|
|
32
|
+
const beforeData = query.INSERT ? [] : await this.run(getExpandForDeep(query, target, true))
|
|
33
|
+
if (query.UPDATE && !beforeData.length) return 0
|
|
34
|
+
|
|
35
|
+
const queries = getDeepQueries(query, beforeData, target)
|
|
36
|
+
|
|
37
|
+
// first delete, then update, then insert because of potential unique constraints:
|
|
38
|
+
// - deletes never trigger unique constraints, but can prevent them -> execute first
|
|
39
|
+
// - updates can trigger and prevent unique constraints -> execute second
|
|
40
|
+
// - inserts can only trigger unique constraints -> execute last
|
|
41
|
+
await Promise.all(Array.from(queries.deletes.values()).map(query => this.onDELETE({ query, target: query._target })))
|
|
42
|
+
await Promise.all(queries.updates.map(query => this.onUPDATE({ query })))
|
|
43
|
+
|
|
44
|
+
const rootQuery = queries.inserts.get(ROOT)
|
|
45
|
+
queries.inserts.delete(ROOT)
|
|
46
|
+
const [rootResult] = await Promise.all([
|
|
47
|
+
rootQuery && this.onINSERT({ query: rootQuery }),
|
|
48
|
+
...Array.from(queries.inserts.values()).map(query => this.onINSERT({ query })),
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
return rootResult ?? beforeData.length
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const hasDeep = (q) => {
|
|
55
|
+
const data = q.INSERT?.entries || (q.UPDATE?.data && [q.UPDATE.data]) || (q.UPDATE?.with && [q.UPDATE.with])
|
|
56
|
+
if (data)
|
|
57
|
+
for (const c in q._target.compositions) {
|
|
58
|
+
for (const row of data) if (row[c] !== undefined) return true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// unofficial config!
|
|
63
|
+
const DEEP_DELETE_MAX_RECURSION_DEPTH =
|
|
64
|
+
(cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 4 // we use 4 here as our test data has a max depth of 3
|
|
65
|
+
|
|
66
|
+
// IMPORTANT: Skip only if @cds.persistence.skip is `true` → e.g. this skips skipping targets marked with @cds.persistence.skip: 'if-unused'
|
|
67
|
+
const _hasPersistenceSkip = target => target?.['@cds.persistence.skip'] === true
|
|
68
|
+
|
|
69
|
+
const getColumnsFromDataOrKeys = (data, target) => {
|
|
70
|
+
if (Array.isArray(data)) {
|
|
71
|
+
// loop and get all columns from current level
|
|
72
|
+
const columns = new Set()
|
|
73
|
+
data.forEach(row =>
|
|
74
|
+
Object.keys(row || target.keys)
|
|
75
|
+
.filter(propName => !target.elements[propName]?.isAssociation)
|
|
76
|
+
.forEach(entry => {
|
|
77
|
+
columns.add(entry)
|
|
78
|
+
}),
|
|
79
|
+
)
|
|
80
|
+
return Array.from(columns).map(c => ({ ref: [c] }))
|
|
81
|
+
} else {
|
|
82
|
+
// get all columns from current level
|
|
83
|
+
return Object.keys(data || target.keys)
|
|
84
|
+
.filter(propName => target.elements[propName] && !target.elements[propName].isAssociation)
|
|
85
|
+
.map(c => ({ ref: [c] }))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const _calculateExpandColumns = (target, data, expandColumns = [], elementMap = new Map()) => {
|
|
90
|
+
const compositions = target.compositions || {}
|
|
91
|
+
|
|
92
|
+
if (expandColumns.length === 0) {
|
|
93
|
+
// REVISIT: ensure that all keys are included in the expand columns
|
|
94
|
+
expandColumns.push(...getColumnsFromDataOrKeys(data, target))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const compName in compositions) {
|
|
98
|
+
let compositionData
|
|
99
|
+
if (data === null || (Array.isArray(data) && !data.length)) {
|
|
100
|
+
compositionData = null
|
|
101
|
+
} else {
|
|
102
|
+
compositionData = data[compName]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ignore not provided compositions as nothing happens with them (expect deep delete)
|
|
106
|
+
if (compositionData === undefined) {
|
|
107
|
+
// fill columns in case
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const composition = compositions[compName]
|
|
112
|
+
|
|
113
|
+
const fqn = composition.parent.name + ':' + composition.name
|
|
114
|
+
const seen = elementMap.get(fqn)
|
|
115
|
+
if (seen && seen >= DEEP_DELETE_MAX_RECURSION_DEPTH) {
|
|
116
|
+
// recursion -> abort
|
|
117
|
+
return expandColumns
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let expandColumn = expandColumns.find(expandColumn => expandColumn.ref[0] === composition.name)
|
|
121
|
+
if (!expandColumn) {
|
|
122
|
+
expandColumn = {
|
|
123
|
+
ref: [composition.name],
|
|
124
|
+
expand: getColumnsFromDataOrKeys(compositionData, composition._target),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
expandColumns.push(expandColumn)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// expand deep
|
|
131
|
+
// Make a copy and do not share the same map among brother compositions
|
|
132
|
+
// as we're only interested in deep recursions, not wide recursions.
|
|
133
|
+
const newElementMap = new Map(elementMap)
|
|
134
|
+
newElementMap.set(fqn, (seen && seen + 1) || 1)
|
|
135
|
+
|
|
136
|
+
if (composition.is2many) {
|
|
137
|
+
// expandColumn.expand = getColumnsFromDataOrKeys(compositionData, composition._target)
|
|
138
|
+
if (compositionData === null || compositionData.length === 0) {
|
|
139
|
+
// deep delete, get all subitems until recursion depth
|
|
140
|
+
_calculateExpandColumns(composition._target, null, expandColumn.expand, newElementMap)
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const row of compositionData) {
|
|
145
|
+
_calculateExpandColumns(composition._target, row, expandColumn.expand, newElementMap)
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
// to one
|
|
149
|
+
_calculateExpandColumns(composition._target, compositionData, expandColumn.expand, newElementMap)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return expandColumns
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {import('@sap/cds/apis/cqn').Query} query
|
|
157
|
+
* @param {import('@sap/cds/apis/csn').Definition} target
|
|
158
|
+
*/
|
|
159
|
+
const getExpandForDeep = (query, target) => {
|
|
160
|
+
const { entity, data = null, where } = query.UPDATE
|
|
161
|
+
const columns = _calculateExpandColumns(target, data)
|
|
162
|
+
return SELECT(columns).from(entity).where(where)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param {import('@sap/cds/apis/cqn').Query} query
|
|
167
|
+
* @param {unknown[]} dbData
|
|
168
|
+
* @param {import('@sap/cds/apis/csn').Definition} target
|
|
169
|
+
* @returns
|
|
170
|
+
*/
|
|
171
|
+
const getDeepQueries = (query, dbData, target) => {
|
|
172
|
+
let queryData
|
|
173
|
+
if (query.INSERT) {
|
|
174
|
+
queryData = query.INSERT.entries
|
|
175
|
+
}
|
|
176
|
+
if (query.DELETE) {
|
|
177
|
+
queryData = []
|
|
178
|
+
}
|
|
179
|
+
if (query.UPDATE) {
|
|
180
|
+
queryData = [query.UPDATE.data]
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let diff = compareJson(queryData, dbData, target)
|
|
184
|
+
if (!Array.isArray(diff)) {
|
|
185
|
+
diff = [diff]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return _getDeepQueries(diff, target)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const _hasManagedElements = target => {
|
|
192
|
+
return Object.keys(target.elements).filter(elementName => target.elements[elementName]['@cds.on.update']).length > 0
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {unknown[]} diff
|
|
197
|
+
* @param {import('@sap/cds/apis/csn').Definition} target
|
|
198
|
+
* @param {Map<String, Object>} deletes
|
|
199
|
+
* @param {Map<String, Object>} inserts
|
|
200
|
+
* @param {Object[]} updates
|
|
201
|
+
* @param {boolean} [root=true]
|
|
202
|
+
* @returns {Object|Boolean}
|
|
203
|
+
*/
|
|
204
|
+
const _getDeepQueries = (diff, target, deletes = new Map(), inserts = new Map(), updates = [], root = true) => {
|
|
205
|
+
// flag to determine if queries were created
|
|
206
|
+
let dirty = false
|
|
207
|
+
for (const diffEntry of diff) {
|
|
208
|
+
if (diffEntry === undefined) continue
|
|
209
|
+
|
|
210
|
+
let childrenDirty = false
|
|
211
|
+
for (const prop in diffEntry) {
|
|
212
|
+
// handle deep operations
|
|
213
|
+
|
|
214
|
+
const propData = diffEntry[prop]
|
|
215
|
+
|
|
216
|
+
if (target.elements[prop] && _hasPersistenceSkip(target.elements[prop]._target)) {
|
|
217
|
+
delete diffEntry[prop]
|
|
218
|
+
} else if (target.compositions?.[prop]) {
|
|
219
|
+
const arrayed = Array.isArray(propData) ? propData : [propData]
|
|
220
|
+
childrenDirty =
|
|
221
|
+
arrayed
|
|
222
|
+
.map(subEntry =>
|
|
223
|
+
_getDeepQueries([subEntry], target.elements[prop]._target, deletes, inserts, updates, false),
|
|
224
|
+
)
|
|
225
|
+
.some(a => a) || childrenDirty
|
|
226
|
+
delete diffEntry[prop]
|
|
227
|
+
} else if (diffEntry[prop] === undefined) {
|
|
228
|
+
// restore current behavior, if property is undefined, not part of payload
|
|
229
|
+
delete diffEntry[prop]
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// handle current entity level
|
|
234
|
+
const op = diffEntry._op
|
|
235
|
+
delete diffEntry._op
|
|
236
|
+
|
|
237
|
+
if (diffEntry._old != null) {
|
|
238
|
+
delete diffEntry._old
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (op === 'create') {
|
|
242
|
+
dirty = true
|
|
243
|
+
const id = root ? ROOT : target.name
|
|
244
|
+
const insert = inserts.get(id)
|
|
245
|
+
if (insert) {
|
|
246
|
+
insert.INSERT.entries.push(diffEntry)
|
|
247
|
+
} else {
|
|
248
|
+
const q = INSERT.into(target).entries(diffEntry)
|
|
249
|
+
inserts.set(id, q)
|
|
250
|
+
}
|
|
251
|
+
} else if (op === 'delete') {
|
|
252
|
+
dirty = true
|
|
253
|
+
const keys = cds.utils
|
|
254
|
+
.Object_keys(target.keys)
|
|
255
|
+
.filter(key => !target.keys[key].virtual && !target.keys[key].isAssociation)
|
|
256
|
+
|
|
257
|
+
const keyVals = keys.map(k => ({ val: diffEntry[k] }))
|
|
258
|
+
const currDelete = deletes.get(target.name)
|
|
259
|
+
if (currDelete) currDelete.DELETE.where[2].list.push({ list: keyVals })
|
|
260
|
+
else {
|
|
261
|
+
const left = { list: keys.map(k => ({ ref: [k] })) }
|
|
262
|
+
const right = { list: [{ list: keyVals }] }
|
|
263
|
+
deletes.set(target.name, DELETE.from(target).where([left, 'in', right]))
|
|
264
|
+
}
|
|
265
|
+
} else if (op === 'update' || (op === undefined && (root || childrenDirty) && _hasManagedElements(target))) {
|
|
266
|
+
dirty = true
|
|
267
|
+
// TODO do we need the where here?
|
|
268
|
+
const keys = target.keys
|
|
269
|
+
const cqn = UPDATE(target).with(diffEntry)
|
|
270
|
+
for (const key in keys) {
|
|
271
|
+
if (keys[key].virtual) continue
|
|
272
|
+
if (!keys[key].isAssociation) {
|
|
273
|
+
cqn.where(key + '=', diffEntry[key])
|
|
274
|
+
}
|
|
275
|
+
delete diffEntry[key]
|
|
276
|
+
}
|
|
277
|
+
cqn.with(diffEntry)
|
|
278
|
+
updates.push(cqn)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return root ? { updates, inserts, deletes } : dirty
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
module.exports = {
|
|
286
|
+
onDeep,
|
|
287
|
+
hasDeep,
|
|
288
|
+
getDeepQueries, // only for testing
|
|
289
|
+
getExpandForDeep, // only for testing
|
|
290
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const cds = require('@sap/cds')
|
|
2
|
+
const { hasDeep } = require('./deep-queries')
|
|
3
|
+
|
|
4
|
+
// REVISIT: very deep & fragile dependencies to internal modules -> copy these into here
|
|
5
|
+
const propagateForeignKeys = require('@sap/cds/libx/_runtime/common/utils/propagateForeignKeys')
|
|
6
|
+
const { enrichDataWithKeysFromWhere } = require('@sap/cds/libx/_runtime/common/utils/keys')
|
|
7
|
+
|
|
8
|
+
const assoc4 = (e) => e.own('$fk4', ()=> {
|
|
9
|
+
const old = e['@odata.foreignKey4']; if (old) return old
|
|
10
|
+
if (!e.parent || !e.name || !e.name.includes('_')) return
|
|
11
|
+
if (e.name === 'ID_texts') return
|
|
12
|
+
if (e.name === 'DraftAdministrativeData_DraftUUID') return 'DraftAdministrativeData'
|
|
13
|
+
if (e.name.startsWith('up__')) return 'up_' // assumes up_ is a reserved name
|
|
14
|
+
const {elements} = e.parent, path = e.name.split('_')
|
|
15
|
+
for (let [p]=path, a, i=1; i < path.length; p += '_'+path[i++]) {
|
|
16
|
+
if ((a = elements[p])) {
|
|
17
|
+
if (a.keys) {
|
|
18
|
+
const tail = path.slice(i)
|
|
19
|
+
if (a.keys.some (k => k.ref.every((r,i) => r === tail[i]))) {
|
|
20
|
+
// process.stdout.write('> resolved assoc: ' + a.name + ' for: ' + this.name + '\n')
|
|
21
|
+
return a.name
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return // not an assoc, or not the one we're looking for
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const fkeys4 = (e) => {
|
|
30
|
+
let fkeys = e._foreignKeys
|
|
31
|
+
return typeof fkeys === 'function' ? fkeys.call(e) : fkeys
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const generateUUIDandPropagateKeys = (entity, data, event) => {
|
|
35
|
+
if (event === 'CREATE') {
|
|
36
|
+
const keys = entity.keys
|
|
37
|
+
for (const k in keys)
|
|
38
|
+
if (keys[k].isUUID && !data[k] && !assoc4(keys[k]) && !keys[k].default) //> skip key assocs, and foreign keys thereof
|
|
39
|
+
data[k] = cds.utils.uuid()
|
|
40
|
+
}
|
|
41
|
+
for (const each in entity.elements) {
|
|
42
|
+
const e = entity.elements[each]
|
|
43
|
+
// if assoc keys are structured, do not ignore them, as they need to be flattened in propagateForeignKeys
|
|
44
|
+
if (!e.isAssociation || e.key && (e.isComposition || e.is2many || !(each in data))) continue
|
|
45
|
+
// propagate own foreign keys to propagate further to sub data
|
|
46
|
+
propagateForeignKeys (each, data, fkeys4(e), e.isComposition, { deleteAssocs: true, })
|
|
47
|
+
|
|
48
|
+
let subData = data[each]; if (!subData) continue
|
|
49
|
+
if (!Array.isArray(subData)) subData = [subData]
|
|
50
|
+
for (const sub of subData) {
|
|
51
|
+
// For subData the event is set to 'CREATE' as require UUID generation
|
|
52
|
+
generateUUIDandPropagateKeys (e._target, sub, 'CREATE')
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
module.exports = async function fill_in_keys(req, next) {
|
|
59
|
+
// REVISIT dummy handler until we have input processing
|
|
60
|
+
if (!req.target || !this.model || req.target._unresolved) return next()
|
|
61
|
+
// only for deep update
|
|
62
|
+
if (req.event === 'UPDATE' && hasDeep(req.query)) {
|
|
63
|
+
// REVISIT for deep update we need to inject the keys first
|
|
64
|
+
enrichDataWithKeysFromWhere(req.data, req, this)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// REVISIT no input processing for INPUT with rows/values
|
|
68
|
+
if (!(req.query.INSERT?.rows || req.query.INSERT?.values)) {
|
|
69
|
+
let {data} = req; if (!Array.isArray(data)) data = [data]
|
|
70
|
+
for (const d of data) {
|
|
71
|
+
generateUUIDandPropagateKeys (req.target, d, req.event)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return next()
|
|
75
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as cqn from '@sap/cds/apis/cqn'
|
|
2
|
+
import * as csn from '@sap/cds/apis/csn'
|
|
3
|
+
|
|
4
|
+
type linkedQuery = {
|
|
5
|
+
target: csn.Definition
|
|
6
|
+
elements: elements
|
|
7
|
+
}
|
|
8
|
+
export type SELECT = cqn.SELECT & linkedQuery
|
|
9
|
+
export type INSERT = cqn.INSERT & linkedQuery
|
|
10
|
+
export type UPSERT = cqn.UPSERT & linkedQuery
|
|
11
|
+
export type UPDATE = cqn.UPDATE & linkedQuery
|
|
12
|
+
export type DELETE = cqn.DELETE & linkedQuery
|
|
13
|
+
export type CREATE = cqn.CREATE & linkedQuery
|
|
14
|
+
export type DROP = cqn.DROP & linkedQuery
|
|
15
|
+
|
|
16
|
+
export type Query = SELECT | INSERT | UPSERT | UPDATE | DELETE | CREATE | DROP
|
|
17
|
+
|
|
18
|
+
export type element = csn.Element & {
|
|
19
|
+
key?: boolean
|
|
20
|
+
virtual?: boolean
|
|
21
|
+
unique?: boolean
|
|
22
|
+
notNull?: boolean
|
|
23
|
+
}
|
|
24
|
+
export type elements = {
|
|
25
|
+
[name: string]: element
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type col = cqn.column_expr & { element: element }
|
|
29
|
+
|
|
30
|
+
export type list = {
|
|
31
|
+
list: cqn.expr[]
|
|
32
|
+
}
|
|
33
|
+
// Passthrough
|
|
34
|
+
export type source = cqn.source
|
|
35
|
+
export type ref = cqn.ref
|
|
36
|
+
export type val = cqn.val
|
|
37
|
+
export type xpr = cqn.xpr
|
|
38
|
+
export type expr = cqn.expr
|
|
39
|
+
export type func = cqn.function_call
|
|
40
|
+
export type predicate = cqn.predicate
|
|
41
|
+
export type ordering_term = cqn.ordering_term
|
|
42
|
+
export type limit = { rows: val; offset: val }
|