@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.
Files changed (82) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/bin/debug.js +6 -2
  3. package/bin/up.js +1 -1
  4. package/bin/watch.js +1 -0
  5. package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +2 -0
  6. package/lib/build/plugins/nodejs/index.js +6 -3
  7. package/lib/build/util.js +1 -1
  8. package/lib/init/template/ams/files/containerize.yaml.hbs +1 -0
  9. package/lib/init/template/approuter/files/values.yaml.hbs +9 -1
  10. package/lib/init/template/data/as-csv.js +4 -2
  11. package/lib/init/template/data/as-json.js +4 -1
  12. package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +6 -6
  13. package/lib/init/template/ias/files/values.yaml.hbs +4 -4
  14. package/lib/init/template/portal/files/values.yaml.hbs +12 -0
  15. package/lib/init/template/sample/files/app/browse/fiori-service.cds +2 -0
  16. package/lib/init/template/typer/files/xsconfig.json +1 -1
  17. package/lib/init/template/typescript/files/package.json +1 -1
  18. package/lib/lint/eslintHelp.json +1 -1
  19. package/lib/watch/esm-resolve-registration.js +27 -0
  20. package/lib/watch/vite-watch.js +1 -1
  21. package/node_modules/@cap-js/db-service/CHANGELOG.md +768 -0
  22. package/node_modules/@cap-js/db-service/LICENSE +201 -0
  23. package/node_modules/@cap-js/db-service/README.md +26 -0
  24. package/node_modules/@cap-js/db-service/index.js +19 -0
  25. package/node_modules/@cap-js/db-service/lib/InsertResults.js +122 -0
  26. package/node_modules/@cap-js/db-service/lib/SQLService.js +581 -0
  27. package/node_modules/@cap-js/db-service/lib/common/DatabaseService.js +179 -0
  28. package/node_modules/@cap-js/db-service/lib/common/factory.d.ts +5 -0
  29. package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +310 -0
  30. package/node_modules/@cap-js/db-service/lib/common/session-context.js +46 -0
  31. package/node_modules/@cap-js/db-service/lib/converters.d.ts +24 -0
  32. package/node_modules/@cap-js/db-service/lib/cql-functions.js +464 -0
  33. package/node_modules/@cap-js/db-service/lib/cqn2pql.js +116 -0
  34. package/node_modules/@cap-js/db-service/lib/cqn2sql.js +1612 -0
  35. package/node_modules/@cap-js/db-service/lib/cqn4sql.js +2895 -0
  36. package/node_modules/@cap-js/db-service/lib/deep-queries.js +290 -0
  37. package/node_modules/@cap-js/db-service/lib/fill-in-keys.js +75 -0
  38. package/node_modules/@cap-js/db-service/lib/infer/cqn.d.ts +42 -0
  39. package/node_modules/@cap-js/db-service/lib/infer/index.js +1290 -0
  40. package/node_modules/@cap-js/db-service/lib/infer/join-tree.js +278 -0
  41. package/node_modules/@cap-js/db-service/lib/infer/pseudos.js +24 -0
  42. package/node_modules/@cap-js/db-service/lib/search.js +220 -0
  43. package/node_modules/@cap-js/db-service/lib/utils.js +177 -0
  44. package/node_modules/@cap-js/db-service/package.json +36 -0
  45. package/node_modules/@cap-js/sqlite/CHANGELOG.md +404 -0
  46. package/node_modules/@cap-js/sqlite/LICENSE +201 -0
  47. package/node_modules/@cap-js/sqlite/README.md +34 -0
  48. package/node_modules/@cap-js/sqlite/cds-plugin.js +0 -0
  49. package/node_modules/@cap-js/sqlite/index.js +2 -0
  50. package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +324 -0
  51. package/node_modules/@cap-js/sqlite/lib/cql-functions.js +160 -0
  52. package/node_modules/@cap-js/sqlite/lib/node-sqlite.js +36 -0
  53. package/node_modules/@cap-js/sqlite/lib/session.json +3 -0
  54. package/node_modules/@cap-js/sqlite/lib/sql.js.js +99 -0
  55. package/node_modules/@cap-js/sqlite/package.json +68 -0
  56. package/node_modules/@sap/cds-compiler/CHANGELOG.md +14 -0
  57. package/node_modules/@sap/cds-compiler/bin/cdsse.js +1 -1
  58. package/node_modules/@sap/cds-compiler/bin/cdsv2m.js +1 -1
  59. package/node_modules/@sap/cds-compiler/doc/IncompatibleChanges_v7.md +20 -13
  60. package/node_modules/@sap/cds-compiler/lib/base/builtins.js +4 -1
  61. package/node_modules/@sap/cds-compiler/lib/base/message-registry.js +15 -2
  62. package/node_modules/@sap/cds-compiler/lib/checks/defaultValues.js +0 -1
  63. package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +6 -5
  64. package/node_modules/@sap/cds-compiler/lib/compiler/tweak-assocs.js +27 -6
  65. package/node_modules/@sap/cds-compiler/lib/edm/annotations/edmJson.js +5 -0
  66. package/node_modules/@sap/cds-compiler/lib/json/from-csn.js +3 -3
  67. package/node_modules/@sap/cds-compiler/lib/json/to-csn.js +5 -4
  68. package/node_modules/@sap/cds-compiler/lib/parsers/AstBuildingParser.js +14 -3
  69. package/node_modules/@sap/cds-compiler/lib/tool-lib/enrichCsn.js +1 -1
  70. package/node_modules/@sap/cds-compiler/lib/transform/db/cdsPersistence.js +3 -2
  71. package/node_modules/@sap/cds-compiler/lib/transform/db/views.js +2 -2
  72. package/node_modules/@sap/cds-compiler/lib/transform/transformUtils.js +1 -2
  73. package/node_modules/@sap/cds-compiler/package.json +1 -1
  74. package/node_modules/iconv-lite/encodings/sbcs-data.js +2 -0
  75. package/node_modules/iconv-lite/encodings/utf32.js +10 -3
  76. package/node_modules/iconv-lite/package.json +2 -2
  77. package/node_modules/iconv-lite/types/encodings.d.ts +2 -0
  78. package/node_modules/ws/lib/receiver.js +15 -32
  79. package/node_modules/ws/lib/websocket-server.js +4 -4
  80. package/node_modules/ws/lib/websocket.js +4 -4
  81. package/node_modules/ws/package.json +5 -1
  82. 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 }