@cap-js/db-service 2.11.0 → 3.0.0

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 CHANGED
@@ -4,6 +4,31 @@
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
+ ## [3.0.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.11.0...db-service-v3.0.0) (2026-06-01)
8
+
9
+
10
+ ### ⚠ BREAKING CHANGES
11
+
12
+ * require cds10 ([#1628](https://github.com/cap-js/cds-dbs/issues/1628))
13
+
14
+ ### Added
15
+
16
+ * own flag for db service results ([#1630](https://github.com/cap-js/cds-dbs/issues/1630)) ([3946e59](https://github.com/cap-js/cds-dbs/commit/3946e59b19ca11afda999d44babeeff835c23817))
17
+ * **pool:** make builtin pool the default ([#1537](https://github.com/cap-js/cds-dbs/issues/1537)) ([919dfe6](https://github.com/cap-js/cds-dbs/commit/919dfe6c5d0cf0ee37a695d2edcc870c1353a93d))
18
+ * require cds10 ([#1628](https://github.com/cap-js/cds-dbs/issues/1628)) ([93e8305](https://github.com/cap-js/cds-dbs/commit/93e83053679f9ef94293caa08917855404db880d))
19
+
20
+
21
+ ### Fixed
22
+
23
+ * **builtin-pool:** ignore released/destroyed resource if already draining ([#1631](https://github.com/cap-js/cds-dbs/issues/1631)) ([de37489](https://github.com/cap-js/cds-dbs/commit/de37489bb80b25be1d292a284119c1c0f6405970))
24
+ * calc element with exists through association ([#1608](https://github.com/cap-js/cds-dbs/issues/1608)) ([0d24b28](https://github.com/cap-js/cds-dbs/commit/0d24b285ae8eb71eeca0c6f4bf811a729a8cff4b))
25
+ * InsertResult for keyless entities ([#1617](https://github.com/cap-js/cds-dbs/issues/1617)) ([2efe922](https://github.com/cap-js/cds-dbs/commit/2efe922e2eb4b0c750eb09a74dfd155e8febc9bc))
26
+ * InsertResult is broken if entity has assoc keys ([#1616](https://github.com/cap-js/cds-dbs/issues/1616)) ([9925d22](https://github.com/cap-js/cds-dbs/commit/9925d22236846081e1013381f79249ab0f8f9447))
27
+ * query results fixed gaps ([#1625](https://github.com/cap-js/cds-dbs/issues/1625)) ([5d60e7f](https://github.com/cap-js/cds-dbs/commit/5d60e7f15b4f0a9e7859b0662e690a1790b350ad))
28
+ * resolve `$self` in infix filters with path expressions and nested exists ([#1604](https://github.com/cap-js/cds-dbs/issues/1604)) ([baa3528](https://github.com/cap-js/cds-dbs/commit/baa3528aa735cc3707c4c2a128cf6b57feaf913b))
29
+ * resolve nested assoc-into-assoc inline path resolution ([#1602](https://github.com/cap-js/cds-dbs/issues/1602)) ([ec6fba0](https://github.com/cap-js/cds-dbs/commit/ec6fba0597c75bb206c859bea4e50ce61be40b40))
30
+ * we can't return keys for `INSERT.from(SELECT.from(...))` ([#1622](https://github.com/cap-js/cds-dbs/issues/1622)) ([ccc9f88](https://github.com/cap-js/cds-dbs/commit/ccc9f881adc62cbc16f1feef13da7c658b27d551))
31
+
7
32
  ## [2.11.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.10.1...db-service-v2.11.0) (2026-04-29)
8
33
 
9
34
 
@@ -1,4 +1,3 @@
1
- const iterator = Symbol.iterator
2
1
 
3
2
  // eslint-disable-next-line no-unused-vars
4
3
  const USAGE_SAMPLE = async () => {
@@ -11,106 +10,113 @@ const USAGE_SAMPLE = async () => {
11
10
  ])
12
11
  }
13
12
 
14
- module.exports = class InsertResult {
13
+ module.exports = class InsertResult extends Array {
14
+
15
15
  /**
16
16
  * @param {import('@sap/cds/apis/cqn').INSERT} query
17
17
  * @param {unknown[]} results
18
18
  */
19
- constructor(query, results) {
20
- // Storing query as non-enumerable property to avoid polluting trace output
21
- Object.defineProperty(this, 'query', { value: query })
22
- this.results = results
19
+ constructor (query, results) {
20
+ Object.defineProperties (super(), { // using non-enumerable properties to avoid polluting trace output
21
+ results: { value: results },
22
+ query: { value: query },
23
+ })
23
24
  }
24
25
 
25
- /**
26
- * Lazy access to auto-generated keys.
26
+ /** Legacy alias for compatibility */
27
+ get affectedRows() { return this.affected }
28
+
29
+ /** The number of affected rows is determined as follows:
30
+ * - For INSERTs with entries/rows/values the number of these entries/rows/values is returned
31
+ * - For other INSERTs the number of affected rows as returned by the database is returned
32
+ * - For other statements the number of affected rows as returned by the database is returned
27
33
  */
28
- get [iterator]() {
29
- // For INSERT.from(SELECT.from(...)) return a dummy iterator with correct length
34
+ get affected() {
30
35
  const { INSERT } = this.query
31
- if (INSERT.from || INSERT.as) {
32
- return (super[iterator] = function* () {
33
- for (let i = 0; i < this.affectedRows; i++) yield {}
34
- })
35
- }
36
+ if (INSERT.from || INSERT.as) return super.affected = this.affectedRows4 (this.results[0] || this.results)
37
+ else return super.affected = INSERT.entries?.length || INSERT.rows?.length || this.results.length || 1
38
+ }
39
+
40
+ [Symbol.iterator]() {
41
+ if (!this.length) this.#materialize() // materialize on first access, e.g. for [...results]
42
+ return super[Symbol.iterator]()
43
+ }
44
+
45
+ toJSON(){
46
+ if (!this.length) this.#materialize() // ensure materialized keys for JSON.stringify
47
+ return this
48
+ }
49
+
50
+ /**
51
+ * Lazy materialization of auto-generated keys.
52
+ */
53
+ #materialize() {
36
54
 
37
55
  const target = this.query._target
38
- if (!target?.keys) return (super[iterator] = this.results[iterator])
39
- const keys = Object.keys(target.keys),
40
- [k1] = keys
56
+ const keys = target?.keys && Object.keys(target.keys).filter(k => !target.keys[k].virtual && !target.keys[k].value && !target.keys[k].isAssociation)
57
+ if (!keys?.length) {
58
+ return this
59
+ }
60
+
61
+ const { INSERT } = this.query
62
+
63
+ // For INSERT.from(SELECT.from(...)), we can't return keys
64
+ if (INSERT.from || INSERT.as) return this
65
+
66
+ const k0 = keys[0]
41
67
 
42
68
  // For INSERT.entries() with generated keys in there return these keys
43
- const { entries } = INSERT
44
- if (entries && k1 in entries[0]) {
45
- return (super[iterator] = function* () {
46
- for (const each of entries)
47
- yield keys.reduce((p, k) => {
48
- p[k] = each[k]
49
- return p
50
- }, {})
51
- })
69
+ if (INSERT.entries && k0 in INSERT.entries[0]) {
70
+ for (const d of INSERT.entries) {
71
+ this.push (keys.reduce((p,k) => (p[k] = d[k], p), {}))
72
+ }
52
73
  }
53
74
 
54
75
  // For INSERT.rows/values() with generated keys in there return these keys
55
- const { columns } = INSERT
56
- if (columns && columns.includes(k1)) {
57
- return (super[iterator] = function* () {
58
- const indices = keys.reduce((p, k) => {
59
- let i = columns.indexOf(k)
60
- if (i >= 0) p[k] = i
61
- return p
62
- }, {})
63
- for (const each of INSERT.rows || [INSERT.values])
64
- yield keys.reduce((p, k) => {
65
- p[k] = each[indices[k]]
66
- return p
67
- }, {})
68
- })
76
+ else if (INSERT.columns && INSERT.columns.includes(k0)) {
77
+ const indices = keys.reduce((p, k) => {
78
+ let i = INSERT.columns.indexOf(k)
79
+ if (i >= 0) p[k] = i
80
+ return p
81
+ }, {})
82
+ for (const d of INSERT.rows || [INSERT.values]) {
83
+ this.push (keys.reduce((p,k) => (p[k] = d[indices[k]], p), {}))
84
+ }
69
85
  }
70
86
 
71
87
  // If no generated keys in entries/rows/values we might have database-generated keys
72
- return (super[iterator] = function* () {
73
- for (const row of this.results) {
74
- const affectedRows = this.affectedRows4(row) - 1
75
- const lastInsertRowid = this.insertedRowId4(row)
76
- for (let i = lastInsertRowid - affectedRows; i<=lastInsertRowid;i++) yield { [k1]: i }
88
+ else for (const row of this.results) {
89
+ const affectedRows = this.affectedRows4(row) - 1
90
+ const lastInsertRowid = this.insertedRowId4(row)
91
+ for (let i = lastInsertRowid - affectedRows; i<=lastInsertRowid;i++) {
92
+ this.push ({ [k0]: i })
77
93
  }
78
- })
79
- }
94
+ }
80
95
 
81
- /**
82
- * the number of inserted (root) entries or the number of affectedRows in case of INSERT into SELECT
83
- * @return {number}
84
- */
85
- get affectedRows() {
86
- const { INSERT: _ } = this.query
87
- if (_.from || _.as) return (super.affectedRows = this.affectedRows4(this.results[0] || this.results))
88
- else return (super.affectedRows = _.entries?.length || _.rows?.length || this.results.length || 1)
96
+ return this
89
97
  }
90
98
 
91
99
  /**
92
- * for checks such as res > 2
93
- * @return {number}
100
+ * Number of affected rows
101
+ * @param {unknown[]} result
102
+ * @returns {number}
94
103
  */
95
- valueOf() {
96
- return this.affectedRows
104
+ affectedRows4(result) {
105
+ return result.changes
97
106
  }
98
107
 
99
108
  /**
100
- * The last id of the auto incremented key column
101
- * @param {unknown[]} result
102
- * @returns {number}
109
+ * The last id of auto-incremented key columns
103
110
  */
104
111
  insertedRowId4(result) {
105
112
  return result.lastInsertRowid
106
113
  }
107
114
 
108
115
  /**
109
- * Number of affected rows
110
- * @param {unknown[]} result
111
- * @returns {number}
116
+ * for checks such as res > 2
117
+ * @return {number}
112
118
  */
113
- affectedRows4(result) {
114
- return result.changes
119
+ valueOf() {
120
+ return this.affected
115
121
  }
116
122
  }
package/lib/SQLService.js CHANGED
@@ -18,7 +18,7 @@ const BINARY_TYPES = {
18
18
  /**
19
19
  * Checks if parameter is an object that at least contains one property.
20
20
  *
21
- * @param {*} obj
21
+ * @param {*} obj
22
22
  * @returns Boolean
23
23
  */
24
24
  const _hasProps = (obj) => {
@@ -199,10 +199,10 @@ class SQLService extends DatabaseService {
199
199
  async onUPSERT({ query, data }) {
200
200
  const { sql, entries } = this.cqn2sql(query, data)
201
201
  if (!sql) return // Do nothing when there is nothing to be done // REVISIT: When does this happen?
202
- const ps = await this.prepare(sql)
203
- const results = entries ? await Promise.all(entries.map(e => ps.run(e))) : await ps.run()
204
- // REVISIT: results isn't an array, when no entries -> how could that work? when do we have no entries?
205
- return results.reduce((total, affectedRows) => total + affectedRows.changes, 0)
202
+ let ps = await this.prepare(sql)
203
+ let results = entries ? await Promise.all(entries.map(e => ps.run(e))) : await ps.run()
204
+ let changes = results.reduce?.((total,r) => total + r.changes, 0) ?? results.changes
205
+ return this._return_affected(changes)
206
206
  }
207
207
 
208
208
  /**
@@ -216,7 +216,7 @@ class SQLService extends DatabaseService {
216
216
  !_hasProps(req.query.UPDATE.with) &&
217
217
  !Object.values(req.target?.elements || {}).some(e => e['@cds.on.update'])
218
218
  )
219
- return 0
219
+ return this._return_affected(0)
220
220
  return this.onSIMPLE(req)
221
221
  }
222
222
 
@@ -227,7 +227,8 @@ class SQLService extends DatabaseService {
227
227
  async onSIMPLE({ query, data }) {
228
228
  const { sql, values } = this.cqn2sql(query, data)
229
229
  let ps = await this.prepare(sql)
230
- return (await ps.run(values)).changes
230
+ let { changes } = await ps.run(values)
231
+ return this._return_affected(changes)
231
232
  }
232
233
 
233
234
  get onDELETE() {
@@ -300,7 +301,7 @@ class SQLService extends DatabaseService {
300
301
  * @type {Handler}
301
302
  */
302
303
  async onEVENT({ event }) {
303
- if(DEBUG._debug) DEBUG.debug(event) // in the other cases above DEBUG happens in cqn2sql
304
+ if (DEBUG._debug) DEBUG.debug(event) // in the other cases above DEBUG happens in cqn2sql
304
305
  return await this.exec(event)
305
306
  }
306
307
 
@@ -310,7 +311,7 @@ class SQLService extends DatabaseService {
310
311
  */
311
312
  async onPlainSQL({ query, data }, next) {
312
313
  if (typeof query === 'string') {
313
- if(DEBUG._debug) DEBUG.debug(query, data)
314
+ if (DEBUG._debug) DEBUG.debug(query, data)
314
315
  const ps = await this.prepare(query)
315
316
  const exec = this.hasResults(query) ? d => ps.all(d) : d => ps.run(d)
316
317
  if (Array.isArray(data) && Array.isArray(data[0])) return await Promise.all(data.map(exec))
@@ -415,7 +416,7 @@ class SQLService extends DatabaseService {
415
416
  * @param {import('@sap/cds/apis/cqn').Query} q
416
417
  * @returns {import('./infer/cqn').Query}
417
418
  */
418
- cqn4sql(q, useTechnicalAlias=true) {
419
+ cqn4sql(q, useTechnicalAlias = true) {
419
420
  if (
420
421
  !cds.env.features.db_strict &&
421
422
  !q.SELECT?.from?.join &&
@@ -517,7 +518,7 @@ const DEBUG_PQL = cds.log('pql')
517
518
  if (DEBUG_PQL._debug || cds.repl) {
518
519
 
519
520
  // Add helper method to convert CQN to PQL, used below...
520
- SQLService.prototype.cqn2pql = function cqn2pql (query, values) {
521
+ SQLService.prototype.cqn2pql = function cqn2pql(query, values) {
521
522
  const CQN2PQL = cqn2pql.renderer ??= require('./cqn2pql')
522
523
  return new CQN2PQL(this).render(query, values)
523
524
  }
@@ -533,7 +534,7 @@ if (DEBUG_PQL._debug || cds.repl) {
533
534
  }
534
535
  }
535
536
 
536
- // If running in the REPL, extend cds.ql.Query with helpers to inspect queries.
537
+ // If running in the REPL, extend cds.ql.Query with helpers to inspect queries.
537
538
  if (cds.repl) {
538
539
 
539
540
  cds.extend(cds.ql.Query).with(
@@ -563,12 +564,12 @@ if (DEBUG_PQL._debug || cds.repl) {
563
564
  }
564
565
  )
565
566
 
566
- /**
567
- * Dummy SQL service used in extensions to cds.ql above,
568
- * if no real SQL service is available yet through cds.db.
567
+ /**
568
+ * Dummy SQL service used in extensions to cds.ql above,
569
+ * if no real SQL service is available yet through cds.db.
569
570
  */
570
571
  class db extends SQLService {
571
- /** @returns {SQLService} */
572
+ /** @returns {SQLService} */
572
573
  static get srv() { return cds.db || (this.singleton ??= new this) }
573
574
  get factory() { return null }
574
575
  get model() { return cds.model }
@@ -167,5 +167,13 @@ class DatabaseService extends cds.Service {
167
167
  }
168
168
  }
169
169
 
170
+ const _as_array = changes => Object.assign ([],{ affected: changes })
171
+ const _as_number = changes => changes
172
+ DatabaseService.prototype._return_affected = _as_number
173
+ DatabaseService._always_return_arrays = on_off => {
174
+ if (on_off === undefined) return DatabaseService.prototype._return_affected === _as_array
175
+ DatabaseService.prototype._return_affected = on_off ? _as_array : _as_number
176
+ }
177
+ DatabaseService._always_return_arrays (cds.env.features.legacy_db_results === false) // TODO: invert default value
170
178
  DatabaseService.prototype.isDatabaseService = true
171
- module.exports = DatabaseService
179
+ module.exports = exports = DatabaseService
@@ -1,7 +1,7 @@
1
1
  const cds = require('@sap/cds')
2
2
  const LOG = cds.log('db')
3
3
 
4
- const use_new_pool = cds.requires.db?.pool?.builtin || cds.env.features.pool === 'builtin'
4
+ const use_new_pool = cds.requires.db?.pool?.builtin === false || cds.env.features.pool === 'generic-pool' ? false : true
5
5
  const createPool = use_new_pool ? (...args) => new Pool(...args) : require('generic-pool').createPool
6
6
 
7
7
  function ConnectionPool (factory, tenant) {
@@ -145,6 +145,10 @@ constructor (factory, options = {}) {
145
145
  }
146
146
 
147
147
  async release(resource) {
148
+ if (this._draining) {
149
+ LOG.debug('Pool is already draining. Resource cannot be returned to pool')
150
+ return
151
+ }
148
152
  const loan = this._loans.get(resource)
149
153
  if (!loan) throw new Error('Resource not currently part of this pool')
150
154
  this._loans.delete(resource)
@@ -155,6 +159,10 @@ constructor (factory, options = {}) {
155
159
  }
156
160
 
157
161
  async destroy(resource) {
162
+ if (this._draining) {
163
+ LOG.debug('Pool is already draining. Resource will be destroyed anyway')
164
+ return
165
+ }
158
166
  const loan = this._loans.get(resource)
159
167
  if (!loan) throw new Error('Resource not currently part of this pool')
160
168
  this._loans.delete(resource)
package/lib/cqn4sql.js CHANGED
@@ -1823,6 +1823,17 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
1823
1823
  continue
1824
1824
  }
1825
1825
  if (token.ref.length > 1 && token.ref[0] === '$self' && !token.$refLinks[0].definition.kind) {
1826
+ if (inferred.outerQueries) {
1827
+ const outerQuery = inferred.outerQueries[0]
1828
+ const stepToFind = token.ref[1]?.id || token.ref[1]
1829
+ const outerAlias = outerQuery.$combinedElements?.[stepToFind]?.[0].index
1830
+ if (outerAlias) {
1831
+ let result = copy(token)
1832
+ result.ref = [outerAlias, token.flatName]
1833
+ transformedTokenStream.push(result)
1834
+ continue
1835
+ }
1836
+ }
1826
1837
  const dollarSelfReplacement = [calculateDollarSelfColumn(token, true)]
1827
1838
  transformedTokenStream.push(...getTransformedTokenStream(dollarSelfReplacement))
1828
1839
  continue
@@ -453,7 +453,7 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
453
453
  arg.$refLinks.push({ definition: pseudos.elements[id], target: pseudos })
454
454
  pseudoPath = true // only first path step must be well defined
455
455
  nameSegments.push(id)
456
- } else if ($baseLink) {
456
+ } else if ($baseLink && !firstStepIsSelf) {
457
457
  const { definition, target } = $baseLink
458
458
  const elements = getDefinition(definition.target)?.elements || definition.elements
459
459
  if (elements && id in elements) {
@@ -484,7 +484,15 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
484
484
  target: getDefinitionFromSources(sources, id),
485
485
  })
486
486
  } else if (firstStepIsSelf) {
487
- arg.$refLinks.push({ definition: { elements: queryElements }, target: { elements: queryElements } })
487
+ const nextStep = arg.ref[1]?.id || arg.ref[1]
488
+ let elements = queryElements
489
+ if (nextStep && (!queryElements || !(nextStep in queryElements)) && inferred.outerQueries) {
490
+ const outerQuery = inferred.outerQueries[0]
491
+ if (outerQuery?.elements && nextStep in outerQuery.elements) {
492
+ elements = outerQuery.elements
493
+ }
494
+ }
495
+ arg.$refLinks.push({ definition: { elements }, target: { elements } })
488
496
  } else if (arg.ref.length > 1 && inferred.outerQueries?.find(outer => id in outer.sources)) {
489
497
  // outer query accessed via alias
490
498
  const outerAlias = inferred.outerQueries.find(outer => id in outer.sources)
@@ -707,7 +715,7 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
707
715
  * d. Otherwise, the corresponding `$refLinks` definition is added to the `elements` object.
708
716
  * 2. Returns the `elements` object.
709
717
  */
710
- function resolveInline(col, namePrefix = col.as || col.flatName) {
718
+ function resolveInline(col, namePrefix = col.as || col.flatName, outerBase = null) {
711
719
  const { inline, $refLinks } = col
712
720
  const $leafLink = $refLinks[$refLinks.length - 1]
713
721
  if (!$leafLink.definition.target && !$leafLink.definition.elements) {
@@ -715,10 +723,13 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
715
723
  `Unexpected “inline” on “${col.ref.map(idOnly)}”; can only be used after a reference to a structure, association or table alias`,
716
724
  )
717
725
  }
726
+ const effectiveBase = outerBase
727
+ ? { ref: [...outerBase.ref, ...col.ref], $refLinks: [...outerBase.$refLinks, ...col.$refLinks] }
728
+ : col
718
729
  let elements = {}
719
730
  let seenWildcard = false
720
731
  inline.forEach(inlineCol => {
721
- inferArg(inlineCol, null, $leafLink, { inXpr: true, baseColumn: col })
732
+ inferArg(inlineCol, null, $leafLink, { inXpr: true, baseColumn: effectiveBase })
722
733
  if (inlineCol === '*') {
723
734
  if (seenWildcard) throw new Error(`Duplicate wildcard "*" in inline of "${col.as || col.ref.map(idOnly).join('_')}"`)
724
735
  seenWildcard = true
@@ -777,7 +788,7 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
777
788
  else if (inlineCol.ref) nameParts.push(...inlineCol.ref.map(idOnly))
778
789
  const name = nameParts.join('_')
779
790
  if (inlineCol.inline) {
780
- const inlineElements = resolveInline(inlineCol, name)
791
+ const inlineElements = resolveInline(inlineCol, name, effectiveBase)
781
792
  elements = { ...elements, ...inlineElements }
782
793
  } else if (inlineCol.expand) {
783
794
  const expandElements = resolveExpand(inlineCol)
@@ -953,19 +964,39 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
953
964
  mergePathIfNecessary(basePath, arg)
954
965
  } else if (arg.xpr || arg.args) {
955
966
  const prop = arg.xpr ? 'xpr' : 'args'
967
+ let inExists = false
956
968
  arg[prop].forEach(step => {
969
+ if (step === 'exists') {
970
+ inExists = true
971
+ return
972
+ }
957
973
  let subPath = { $refLinks: [...basePath.$refLinks], ref: [...basePath.ref] }
958
974
  if (step.ref) {
959
- step.$refLinks.forEach((link, i) => {
960
- const { definition } = link
961
- if (definition.value) {
962
- mergePathsIntoJoinTree(definition.value, subPath)
963
- } else {
964
- subPath.$refLinks.push(link)
965
- subPath.ref.push(step.ref[i])
975
+ if (inExists) {
976
+ // refs following `exists` become subqueries in cqn4sql — only the basePath prefix
977
+ // needs a JOIN (for correlation), the exists-target association itself does not.
978
+ if (subPath.$refLinks.length > 0) {
979
+ inferred.joinTree.mergeColumn(subPath, originalQuery.outerQueries)
980
+ // The exists subquery correlates against the last assoc in basePath,
981
+ // so it's not just a FK access — force a real JOIN.
982
+ const lastLink = subPath.$refLinks[subPath.$refLinks.length - 1]
983
+ if (lastLink.onlyForeignKeyAccess) lastLink.onlyForeignKeyAccess = false
984
+ if (!calcElement.value.isJoinRelevant)
985
+ defineProperty(step, 'isJoinRelevant', true)
966
986
  }
967
- })
968
- mergePathIfNecessary(subPath, step)
987
+ } else {
988
+ step.$refLinks.forEach((link, i) => {
989
+ const { definition } = link
990
+ if (definition.value) {
991
+ mergePathsIntoJoinTree(definition.value, subPath)
992
+ } else {
993
+ subPath.$refLinks.push(link)
994
+ subPath.ref.push(step.ref[i])
995
+ }
996
+ })
997
+ mergePathIfNecessary(subPath, step)
998
+ }
999
+ inExists = false
969
1000
  } else if (step.args || step.xpr) {
970
1001
  const nestedProp = step.xpr ? 'xpr' : 'args'
971
1002
  step[nestedProp].forEach(a => {
@@ -974,6 +1005,9 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
974
1005
  if (!a.ref) subPath = { $refLinks: [...basePath.$refLinks], ref: [...basePath.ref] }
975
1006
  mergePathsIntoJoinTree(a, subPath)
976
1007
  })
1008
+ inExists = false
1009
+ } else {
1010
+ if (step !== 'not') inExists = false
977
1011
  }
978
1012
  })
979
1013
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/db-service",
3
- "version": "2.11.0",
3
+ "version": "3.0.0",
4
4
  "description": "CDS base database service",
5
5
  "homepage": "https://github.com/cap-js/cds-dbs/tree/main/db-service#cds-base-database-service",
6
6
  "repository": {
@@ -23,11 +23,14 @@
23
23
  "scripts": {
24
24
  "test": "cds-test"
25
25
  },
26
- "dependencies": {
26
+ "peerDependencies": {
27
+ "@sap/cds": "^10",
27
28
  "generic-pool": "^3.9.0"
28
29
  },
29
- "peerDependencies": {
30
- "@sap/cds": ">=9.8"
30
+ "peerDependenciesMeta": {
31
+ "generic-pool": {
32
+ "optional": true
33
+ }
31
34
  },
32
35
  "license": "Apache-2.0"
33
36
  }