@cap-js/db-service 1.12.0 → 1.12.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 -4
- package/lib/cql-functions.js +12 -6
- package/lib/cqn2sql.js +6 -6
- package/lib/cqn4sql.js +7 -1
- package/lib/deep-queries.js +62 -51
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,19 +4,30 @@
|
|
|
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
|
+
## [1.12.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.12.0...db-service-v1.12.1) (2024-09-03)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* deep `groupby` expand queries ([#768](https://github.com/cap-js/cds-dbs/issues/768)) ([5423cf3](https://github.com/cap-js/cds-dbs/commit/5423cf38574962c09b94febab95f2e3dc118d2c9))
|
|
13
|
+
* **deep:** prevent false unique constraint errors and combine delete queries ([#781](https://github.com/cap-js/cds-dbs/issues/781)) ([01de95f](https://github.com/cap-js/cds-dbs/commit/01de95f5050a1d3325459ccb78a4e9a1e0dbcfde))
|
|
14
|
+
* **logging:** from changes in @sap/cds ([#791](https://github.com/cap-js/cds-dbs/issues/791)) ([1e8bf06](https://github.com/cap-js/cds-dbs/commit/1e8bf06c9ae92ba55d13fe9e3297d6a54c4fc8fe))
|
|
15
|
+
* prepend aliases to refs within function args in on conditions ([#795](https://github.com/cap-js/cds-dbs/issues/795)) ([9b34314](https://github.com/cap-js/cds-dbs/commit/9b34314d1ef8c6fd7e77451fe9bf0abdc12c27ea)), closes [#779](https://github.com/cap-js/cds-dbs/issues/779)
|
|
16
|
+
* prevent $search queries from throwing ([#772](https://github.com/cap-js/cds-dbs/issues/772)) ([cdf4d37](https://github.com/cap-js/cds-dbs/commit/cdf4d37590c2949cdfd6c6533370bc96cd8fd0fc))
|
|
17
|
+
|
|
7
18
|
## [1.12.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.11.0...db-service-v1.12.0) (2024-07-25)
|
|
8
19
|
|
|
9
20
|
|
|
10
21
|
### Fixed
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
* add placeholder for string values ([#733](https://github.com/cap-js/cds-dbs/issues/733)) ([8136a45](https://github.com/cap-js/cds-dbs/commit/8136a4526f596b67932908b8ab1336cb052100f3))
|
|
24
|
+
* for aggregated `expand` always set explicit alias ([#739](https://github.com/cap-js/cds-dbs/issues/739)) ([53a8075](https://github.com/cap-js/cds-dbs/commit/53a8075a609666a896296401a28b6183ff5aa487)), closes [#708](https://github.com/cap-js/cds-dbs/issues/708)
|
|
25
|
+
* quotations in vals ([#754](https://github.com/cap-js/cds-dbs/issues/754)) ([94d8e97](https://github.com/cap-js/cds-dbs/commit/94d8e977ed00776ff494287ce505d6b7e8017d2e))
|
|
15
26
|
|
|
16
27
|
|
|
17
28
|
### Changed
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
* generic-pool as real dep ([#750](https://github.com/cap-js/cds-dbs/issues/750)) ([b50c907](https://github.com/cap-js/cds-dbs/commit/b50c907880455a41a73826a736bc17ca17e5b9ae))
|
|
20
31
|
|
|
21
32
|
|
|
22
33
|
## [1.11.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.10.3...db-service-v1.11.0) (2024-07-08)
|
package/lib/cql-functions.js
CHANGED
|
@@ -23,10 +23,16 @@ const StandardFunctions = {
|
|
|
23
23
|
search: function (ref, arg) {
|
|
24
24
|
if (!('val' in arg)) throw new Error(`Only single value arguments are allowed for $search`)
|
|
25
25
|
// only apply first search term, rest is ignored
|
|
26
|
-
const sub= /("")|("(?:[^"]|\\")*(?:[^\\]|\\\\)")|(\S*)/.exec(arg.val)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const sub = /("")|("(?:[^"]|\\")*(?:[^\\]|\\\\)")|(\S*)/.exec(arg.val)
|
|
27
|
+
let val
|
|
28
|
+
try {
|
|
29
|
+
val = (sub[2] ? JSON.parse(sub[2]) : sub[3]) || ''
|
|
30
|
+
} catch {
|
|
31
|
+
val = sub[2] || sub[3] || ''
|
|
32
|
+
}
|
|
33
|
+
arg.val = arg.__proto__.val = val
|
|
34
|
+
const refs = ref.list || [ref]
|
|
35
|
+
const { toString } = ref
|
|
30
36
|
return '(' + refs.map(ref2 => this.contains(this.tolower(toString(ref2)), this.tolower(arg))).join(' or ') + ')'
|
|
31
37
|
},
|
|
32
38
|
/**
|
|
@@ -159,8 +165,8 @@ const StandardFunctions = {
|
|
|
159
165
|
* Generates SQL statement that produces current point in time (date and time with time zone)
|
|
160
166
|
* @returns {string}
|
|
161
167
|
*/
|
|
162
|
-
|
|
163
|
-
return this.session_context({val: '$now'})
|
|
168
|
+
now: function () {
|
|
169
|
+
return this.session_context({ val: '$now' })
|
|
164
170
|
},
|
|
165
171
|
/**
|
|
166
172
|
* Generates SQL statement that produces the year of a given timestamp
|
package/lib/cqn2sql.js
CHANGED
|
@@ -13,14 +13,14 @@ const BINARY_TYPES = {
|
|
|
13
13
|
const { Readable } = require('stream')
|
|
14
14
|
|
|
15
15
|
const DEBUG = (() => {
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
if (DEBUG) {
|
|
20
|
-
|
|
16
|
+
const LOG = cds.log('sql-json')
|
|
17
|
+
if (LOG._debug) return cds.debug('sql-json')
|
|
18
|
+
return cds.debug('sql|sqlite')
|
|
19
|
+
//if (DEBUG) {
|
|
20
|
+
// return DEBUG
|
|
21
21
|
// (sql, ...more) => DEBUG (sql.replace(/(?:SELECT[\n\r\s]+(json_group_array\()?[\n\r\s]*json_insert\((\n|\r|.)*?\)[\n\r\s]*\)?[\n\r\s]+as[\n\r\s]+_json_[\n\r\s]+FROM[\n\r\s]*\(|\)[\n\r\s]*(\)[\n\r\s]+AS )|\)$)/gim,(a,b,c,d) => d || ''), ...more)
|
|
22
22
|
// FIXME: looses closing ) on INSERT queries
|
|
23
|
-
}
|
|
23
|
+
//}
|
|
24
24
|
})()
|
|
25
25
|
|
|
26
26
|
class CQN2SQLRenderer {
|
package/lib/cqn4sql.js
CHANGED
|
@@ -530,7 +530,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
530
530
|
res = getTransformedTokenStream([value], baseLink)[0]
|
|
531
531
|
} else if (xpr) {
|
|
532
532
|
res = { xpr: getTransformedTokenStream(value.xpr, baseLink) }
|
|
533
|
-
} else if (val) {
|
|
533
|
+
} else if (val !== undefined) {
|
|
534
534
|
res = { val }
|
|
535
535
|
} else if (func) {
|
|
536
536
|
res = { args: getTransformedFunctionArgs(value.args, baseLink), func: value.func }
|
|
@@ -884,6 +884,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
884
884
|
|
|
885
885
|
if (expand.expand) {
|
|
886
886
|
const nested = _subqueryForGroupBy(expand, fullRef, expand.as || expand.ref.map(idOnly).join('_'))
|
|
887
|
+
setElementOnColumns(nested, expand.element)
|
|
887
888
|
elements[expand.as || expand.ref.map(idOnly).join('_')] = nested
|
|
888
889
|
return nested
|
|
889
890
|
}
|
|
@@ -1852,6 +1853,11 @@ function cqn4sql(originalQuery, model) {
|
|
|
1852
1853
|
result[i] = asXpr(xpr)
|
|
1853
1854
|
continue
|
|
1854
1855
|
}
|
|
1856
|
+
if(lhs.args) {
|
|
1857
|
+
const args = calculateOnCondition(lhs.args)
|
|
1858
|
+
result[i] = { ...lhs, args }
|
|
1859
|
+
continue
|
|
1860
|
+
}
|
|
1855
1861
|
const rhs = result[i + 2]
|
|
1856
1862
|
if (rhs?.ref || lhs.ref) {
|
|
1857
1863
|
// if we have refs on each side of the comparison, we might need to perform tuple expansion
|
package/lib/deep-queries.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const cds = require('@sap/cds')
|
|
2
2
|
const { _target_name4 } = require('./SQLService')
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
const ROOT = Symbol('root')
|
|
4
5
|
|
|
5
6
|
// REVISIT: remove old path with cds^8
|
|
6
7
|
let _compareJson
|
|
@@ -45,20 +46,22 @@ async function onDeep(req, next) {
|
|
|
45
46
|
if (query.UPDATE && !beforeData.length) return 0
|
|
46
47
|
|
|
47
48
|
const queries = getDeepQueries(query, beforeData, target)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
49
|
+
|
|
50
|
+
// first delete, then update, then insert because of potential unique constraints:
|
|
51
|
+
// - deletes never trigger unique constraints, but can prevent them -> execute first
|
|
52
|
+
// - updates can trigger and prevent unique constraints -> execute second
|
|
53
|
+
// - inserts can only trigger unique constraints -> execute last
|
|
54
|
+
await Promise.all(Array.from(queries.deletes.values()).map(query => this.onSIMPLE({ query })))
|
|
55
|
+
await Promise.all(queries.updates.map(query => this.onUPDATE({ query })))
|
|
56
|
+
|
|
57
|
+
const rootQuery = queries.inserts.get(ROOT)
|
|
58
|
+
queries.inserts.delete(ROOT)
|
|
59
|
+
const [rootResult] = await Promise.all([
|
|
60
|
+
rootQuery && this.onINSERT({ query: rootQuery }),
|
|
61
|
+
...Array.from(queries.inserts.values()).map(query => this.onINSERT({ query })),
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
return beforeData.length ?? rootResult
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
const hasDeep = (q, target) => {
|
|
@@ -195,7 +198,7 @@ const getDeepQueries = (query, dbData, target) => {
|
|
|
195
198
|
diff = [diff]
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
return _getDeepQueries(diff, target
|
|
201
|
+
return _getDeepQueries(diff, target)
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
const _hasManagedElements = target => {
|
|
@@ -205,16 +208,19 @@ const _hasManagedElements = target => {
|
|
|
205
208
|
/**
|
|
206
209
|
* @param {unknown[]} diff
|
|
207
210
|
* @param {import('@sap/cds/apis/csn').Definition} target
|
|
208
|
-
* @param {
|
|
209
|
-
* @
|
|
211
|
+
* @param {Map<String, Object>} deletes
|
|
212
|
+
* @param {Map<String, Object>} inserts
|
|
213
|
+
* @param {Object[]} updates
|
|
214
|
+
* @param {boolean} [root=true]
|
|
215
|
+
* @returns {Object|Boolean}
|
|
210
216
|
*/
|
|
211
|
-
const _getDeepQueries = (diff, target, root =
|
|
212
|
-
|
|
213
|
-
|
|
217
|
+
const _getDeepQueries = (diff, target, deletes = new Map(), inserts = new Map(), updates = [], root = true) => {
|
|
218
|
+
// flag to determine if queries were created
|
|
219
|
+
let dirty = false
|
|
214
220
|
for (const diffEntry of diff) {
|
|
215
221
|
if (diffEntry === undefined) continue
|
|
216
|
-
const subQueries = []
|
|
217
222
|
|
|
223
|
+
let childrenDirty = false
|
|
218
224
|
for (const prop in diffEntry) {
|
|
219
225
|
// handle deep operations
|
|
220
226
|
|
|
@@ -224,9 +230,12 @@ const _getDeepQueries = (diff, target, root = false) => {
|
|
|
224
230
|
delete diffEntry[prop]
|
|
225
231
|
} else if (target.compositions?.[prop]) {
|
|
226
232
|
const arrayed = Array.isArray(propData) ? propData : [propData]
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
233
|
+
childrenDirty =
|
|
234
|
+
arrayed
|
|
235
|
+
.map(subEntry =>
|
|
236
|
+
_getDeepQueries([subEntry], target.elements[prop]._target, deletes, inserts, updates, false),
|
|
237
|
+
)
|
|
238
|
+
.some(a => a) || childrenDirty
|
|
230
239
|
delete diffEntry[prop]
|
|
231
240
|
} else if (diffEntry[prop] === undefined) {
|
|
232
241
|
// restore current behavior, if property is undefined, not part of payload
|
|
@@ -242,12 +251,32 @@ const _getDeepQueries = (diff, target, root = false) => {
|
|
|
242
251
|
delete diffEntry._old
|
|
243
252
|
}
|
|
244
253
|
|
|
245
|
-
// first calculate subqueries and rm their properties, then build root query
|
|
246
254
|
if (op === 'create') {
|
|
247
|
-
|
|
255
|
+
dirty = true
|
|
256
|
+
const id = root ? ROOT : target.name
|
|
257
|
+
const insert = inserts.get(id)
|
|
258
|
+
if (insert) {
|
|
259
|
+
insert.INSERT.entries.push(diffEntry)
|
|
260
|
+
} else {
|
|
261
|
+
const q = INSERT.into(target).entries(diffEntry)
|
|
262
|
+
inserts.set(id, q)
|
|
263
|
+
}
|
|
248
264
|
} else if (op === 'delete') {
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
dirty = true
|
|
266
|
+
const keys = cds.utils
|
|
267
|
+
.Object_keys(target.keys)
|
|
268
|
+
.filter(key => !target.keys[key].virtual && !target.keys[key].isAssociation)
|
|
269
|
+
|
|
270
|
+
const keyVals = keys.map(k => ({ val: diffEntry[k] }))
|
|
271
|
+
const currDelete = deletes.get(target.name)
|
|
272
|
+
if (currDelete) currDelete.DELETE.where[2].list.push({ list: keyVals })
|
|
273
|
+
else {
|
|
274
|
+
const left = { list: keys.map(k => ({ ref: [k] })) }
|
|
275
|
+
const right = { list: [{ list: keyVals }] }
|
|
276
|
+
deletes.set(target.name, DELETE.from(target).where([left, 'in', right]))
|
|
277
|
+
}
|
|
278
|
+
} else if (op === 'update' || (op === undefined && (root || childrenDirty) && _hasManagedElements(target))) {
|
|
279
|
+
dirty = true
|
|
251
280
|
// TODO do we need the where here?
|
|
252
281
|
const keys = target.keys
|
|
253
282
|
const cqn = UPDATE(target).with(diffEntry)
|
|
@@ -259,34 +288,16 @@ const _getDeepQueries = (diff, target, root = false) => {
|
|
|
259
288
|
delete diffEntry[key]
|
|
260
289
|
}
|
|
261
290
|
cqn.with(diffEntry)
|
|
262
|
-
|
|
291
|
+
updates.push(cqn)
|
|
263
292
|
}
|
|
264
|
-
|
|
265
|
-
for (const q of subQueries) queries.push(q)
|
|
266
293
|
}
|
|
267
294
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return queries.map(q => {
|
|
271
|
-
// Merge all INSERT statements for each target
|
|
272
|
-
if (q.INSERT) {
|
|
273
|
-
const target = q.target
|
|
274
|
-
if (insertQueries.has(target)) {
|
|
275
|
-
insertQueries.get(target).INSERT.entries.push(...q.INSERT.entries)
|
|
276
|
-
return
|
|
277
|
-
} else {
|
|
278
|
-
insertQueries.set(target, q)
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
Object.defineProperty(q, handledDeep, { value: true })
|
|
282
|
-
return q
|
|
283
|
-
})
|
|
284
|
-
.filter(a => a)
|
|
295
|
+
return root ? { updates, inserts, deletes } : dirty
|
|
285
296
|
}
|
|
286
297
|
|
|
287
298
|
module.exports = {
|
|
288
299
|
onDeep,
|
|
289
|
-
getDeepQueries,
|
|
290
|
-
getExpandForDeep,
|
|
291
300
|
hasDeep,
|
|
301
|
+
getDeepQueries, // only for testing
|
|
302
|
+
getExpandForDeep, // only for testing
|
|
292
303
|
}
|
package/package.json
CHANGED