@cap-js/db-service 1.11.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 CHANGED
@@ -4,6 +4,32 @@
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
+
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)
19
+
20
+
21
+ ### Fixed
22
+
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))
26
+
27
+
28
+ ### Changed
29
+
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))
31
+
32
+
7
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)
8
34
 
9
35
 
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # CDS base database service
2
2
 
3
- Welcome to the new base database service for [SAP Cloud Application Programming Model](https://cap.cloud.sap) Node.js, based on new, streamlined database architecture.
3
+ Welcome to the base database service for [SAP Cloud Application Programming Model](https://cap.cloud.sap) Node.js. This service forms the core of all supported databases and is the base of our streamlined database architecture.
4
4
 
5
- Find documentation at https://cap.cloud.sap/docs/guides/databases
5
+ Find documentation at <https://cap.cloud.sap/docs/guides/databases>
6
6
 
7
7
  ## Support
8
8
 
@@ -23,4 +23,4 @@ We as members, contributors, and leaders pledge to make participation in our com
23
23
 
24
24
  ## Licensing
25
25
 
26
- Copyright 2023 SAP SE or an SAP affiliate company and cds-dbs contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/cds-dbs).
26
+ Copyright 2024 SAP SE or an SAP affiliate company and cds-dbs contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/cds-dbs).
package/lib/SQLService.js CHANGED
@@ -371,10 +371,8 @@ class SQLService extends DatabaseService {
371
371
  !q.SELECT?.from?.join &&
372
372
  !q.SELECT?.from?.SELECT &&
373
373
  !this.model?.definitions[_target_name4(q)]
374
- ) {
375
- return _unquirked(q)
376
- }
377
- return cqn4sql(q, this.model)
374
+ ) return q
375
+ else return cqn4sql(q, this.model)
378
376
  }
379
377
 
380
378
  /**
@@ -464,18 +462,6 @@ const _target_name4 = q => {
464
462
  return first.id || first
465
463
  }
466
464
 
467
- const _unquirked = !cds.env.ql.quirks_mode ? q => q : q => {
468
- if (!q) return q
469
- else if (typeof q.SELECT?.from === 'string') q.SELECT.from = { ref: [q.SELECT.from] }
470
- else if (typeof q.INSERT?.into === 'string') q.INSERT.into = { ref: [q.INSERT.into] }
471
- else if (typeof q.UPSERT?.into === 'string') q.UPSERT.into = { ref: [q.UPSERT.into] }
472
- else if (typeof q.UPDATE?.entity === 'string') q.UPDATE.entity = { ref: [q.UPDATE.entity] }
473
- else if (typeof q.DELETE?.from === 'string') q.DELETE.from = { ref: [q.DELETE.from] }
474
- else if (typeof q.CREATE?.entity === 'string') q.CREATE.entity = { ref: [q.CREATE.entity] }
475
- else if (typeof q.DROP?.entity === 'string') q.DROP.entity = { ref: [q.DROP.entity] }
476
- return q
477
- }
478
-
479
465
  const sqls = new (class extends SQLService {
480
466
  get factory() {
481
467
  return null
@@ -1,4 +1,4 @@
1
- const { createPool } = require('@sap/cds-foss').pool
1
+ const { createPool } = require('generic-pool')
2
2
 
3
3
  class ConnectionPool {
4
4
  constructor(factory, tenant) {
@@ -23,9 +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
- arg.val = arg.__proto__.val = arg.val.split(' ')[0].replace(/"/g, '')
27
- const refs = ref.list || [ref],
28
- { toString } = ref
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
29
36
  return '(' + refs.map(ref2 => this.contains(this.tolower(toString(ref2)), this.tolower(arg))).join(' or ') + ')'
30
37
  },
31
38
  /**
@@ -158,8 +165,8 @@ const StandardFunctions = {
158
165
  * Generates SQL statement that produces current point in time (date and time with time zone)
159
166
  * @returns {string}
160
167
  */
161
- now: function() {
162
- return this.session_context({val: '$now'})
168
+ now: function () {
169
+ return this.session_context({ val: '$now' })
163
170
  },
164
171
  /**
165
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
- let DEBUG = cds.debug('sql-json')
17
- if (DEBUG) return DEBUG
18
- else DEBUG = cds.debug('sql|sqlite')
19
- if (DEBUG) {
20
- return DEBUG
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 {
@@ -85,7 +85,7 @@ class CQN2SQLRenderer {
85
85
  const sanitize_values = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
86
86
  DEBUG?.(
87
87
  this.sql,
88
- sanitize_values && (this.entries || this.values?.length > 0) ? ['***'] : this.entries || this.values,
88
+ ...(sanitize_values && (this.entries || this.values?.length > 0) ? ['***'] : this.entries || this.values || []),
89
89
  )
90
90
  return this
91
91
  }
@@ -311,8 +311,8 @@ class CQN2SQLRenderer {
311
311
  */
312
312
  column_expr(x, q) {
313
313
  if (x === '*') return '*'
314
-
315
- let sql = this.expr({ param: false, __proto__: x })
314
+
315
+ let sql = x.param !== true && typeof x.val === 'number' ? this.expr({ param: false, __proto__: x }): this.expr(x)
316
316
  let alias = this.column_alias4(x, q)
317
317
  if (alias) sql += ' as ' + this.quote(alias)
318
318
  return sql
@@ -508,6 +508,7 @@ class CQN2SQLRenderer {
508
508
  } else {
509
509
  const stream = Readable.from(this.INSERT_entries_stream(INSERT.entries), { objectMode: false })
510
510
  stream.type = 'json'
511
+ stream._raw = INSERT.entries
511
512
  this.entries = [[...this.values, stream]]
512
513
  }
513
514
 
@@ -652,6 +653,7 @@ class CQN2SQLRenderer {
652
653
  } else {
653
654
  const stream = Readable.from(this.INSERT_rows_stream(INSERT.rows), { objectMode: false })
654
655
  stream.type = 'json'
656
+ stream._raw = INSERT.rows
655
657
  this.entries = [[...this.values, stream]]
656
658
  }
657
659
 
@@ -1080,6 +1082,8 @@ Buffer.prototype.toJSON = function () {
1080
1082
  return this.toString('base64')
1081
1083
  }
1082
1084
 
1085
+ Readable.prototype[require('node:util').inspect.custom] = Readable.prototype.toJSON = function () { return this._raw || `[object ${this.constructor.name}]` }
1086
+
1083
1087
  const ObjectKeys = o => (o && [...ObjectKeys(o.__proto__), ...Object.keys(o)]) || []
1084
1088
  const _managed = {
1085
1089
  '$user.id': '$user.id',
package/lib/cqn4sql.js CHANGED
@@ -432,7 +432,7 @@ function cqn4sql(originalQuery, model) {
432
432
  return
433
433
  }
434
434
 
435
- const tableAlias = getQuerySourceName(col)
435
+ const tableAlias = getTableAlias(col)
436
436
  // re-adjust usage of implicit alias in subquery
437
437
  if (col.$refLinks[0].definition.kind === 'entity' && col.ref[0] !== tableAlias) {
438
438
  col.ref[0] = tableAlias
@@ -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 }
@@ -725,7 +725,7 @@ function cqn4sql(originalQuery, model) {
725
725
  res.push(...getColumnsForWildcard(exclude, replace, col.as))
726
726
  } else
727
727
  res.push(
728
- ...getFlatColumnsFor(col, { columnAlias: col.as, tableAlias: getQuerySourceName(col) }, [], {
728
+ ...getFlatColumnsFor(col, { columnAlias: col.as, tableAlias: getTableAlias(col) }, [], {
729
729
  exclude,
730
730
  replace,
731
731
  }),
@@ -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
  }
@@ -895,11 +896,12 @@ function cqn4sql(originalQuery, model) {
895
896
  )
896
897
  }
897
898
 
898
- const columnCopy = Object.create(groupByRef)
899
- if (expand.as) {
900
- columnCopy.as = expand.as
901
- }
902
- const res = getFlatColumnsFor(columnCopy, { tableAlias: getQuerySourceName(columnCopy) })
899
+ const copy = Object.create(groupByRef)
900
+ // always alias for this special case, so that they nested element names match the expected result structure
901
+ // otherwise we'd get `author { <outer>.author_ID }`, but we need `author { <outer>.author_ID as ID }`
902
+ copy.as = expand.as || expand.ref.at(-1)
903
+ const tableAlias = getTableAlias(copy)
904
+ const res = getFlatColumnsFor(copy, { tableAlias })
903
905
  res.forEach(c => {
904
906
  elements[c.as || c.ref.at(-1)] = c.element
905
907
  })
@@ -961,7 +963,7 @@ function cqn4sql(originalQuery, model) {
961
963
  referredCol.nulls = col.nulls
962
964
  col = referredCol
963
965
  if (definition.kind === 'element') {
964
- tableAlias = getQuerySourceName(col)
966
+ tableAlias = getTableAlias(col)
965
967
  } else {
966
968
  // we must replace the reference with the underlying expression
967
969
  const { val, func, args, xpr } = col
@@ -973,7 +975,7 @@ function cqn4sql(originalQuery, model) {
973
975
  }
974
976
  }
975
977
  } else {
976
- tableAlias = getQuerySourceName(col) // do not prepend TA if orderBy column addresses element of query
978
+ tableAlias = getTableAlias(col) // do not prepend TA if orderBy column addresses element of query
977
979
  }
978
980
  const leaf = col.$refLinks[col.$refLinks.length - 1].definition
979
981
  if (leaf.virtual === true) continue // already in getFlatColumnForElement
@@ -992,12 +994,9 @@ function cqn4sql(originalQuery, model) {
992
994
  if (inOrderBy && flatColumns.length > 1)
993
995
  throw new Error(`"${getFullName(leaf)}" can't be used in order by as it expands to multiple fields`)
994
996
  flatColumns.forEach(fc => {
995
- if (col.nulls)
996
- fc.nulls = col.nulls
997
- if (col.sort)
998
- fc.sort = col.sort
999
- if (fc.as)
1000
- delete fc.as
997
+ if (col.nulls) fc.nulls = col.nulls
998
+ if (col.sort) fc.sort = col.sort
999
+ if (fc.as) delete fc.as
1001
1000
  })
1002
1001
  res.push(...flatColumns)
1003
1002
  } else {
@@ -1156,7 +1155,7 @@ function cqn4sql(originalQuery, model) {
1156
1155
  if (column.val || column.func || column.SELECT) return [column]
1157
1156
 
1158
1157
  const structsAreUnfoldedAlready = model.meta.unfolded?.includes('structs')
1159
- let { baseName, columnAlias, tableAlias } = names
1158
+ let { baseName, columnAlias = column.as, tableAlias } = names
1160
1159
  const { exclude, replace } = excludeAndReplace || {}
1161
1160
  const { $refLinks, flatName, isJoinRelevant } = column
1162
1161
  let leafAssoc
@@ -1199,7 +1198,7 @@ function cqn4sql(originalQuery, model) {
1199
1198
  baseName = getFullName(replacedBy.$refLinks?.[replacedBy.$refLinks.length - 2].definition)
1200
1199
  if (replacedBy.isJoinRelevant)
1201
1200
  // we need to provide the correct table alias
1202
- tableAlias = getQuerySourceName(replacedBy)
1201
+ tableAlias = getTableAlias(replacedBy)
1203
1202
 
1204
1203
  if (replacedBy.expand) return [{ as: baseName }]
1205
1204
 
@@ -1488,7 +1487,7 @@ function cqn4sql(originalQuery, model) {
1488
1487
  // hence we need to ignore the alias of the `$baseLink`
1489
1488
  const lastAssoc =
1490
1489
  token.isJoinRelevant && [...token.$refLinks].reverse().find(l => l.definition.isAssociation)
1491
- const tableAlias = getQuerySourceName(token, (!lastAssoc?.onlyForeignKeyAccess && lastAssoc) || $baseLink)
1490
+ const tableAlias = getTableAlias(token, (!lastAssoc?.onlyForeignKeyAccess && lastAssoc) || $baseLink)
1492
1491
  if ((!$baseLink || lastAssoc) && token.isJoinRelevant) {
1493
1492
  let name = calculateElementName(token, getFullName)
1494
1493
  result.ref = [tableAlias, name]
@@ -1585,7 +1584,7 @@ function cqn4sql(originalQuery, model) {
1585
1584
  if (!def.$refLinks) return def
1586
1585
  const leaf = def.$refLinks[def.$refLinks.length - 1]
1587
1586
  const first = def.$refLinks[0]
1588
- const tableAlias = getQuerySourceName(
1587
+ const tableAlias = getTableAlias(
1589
1588
  def,
1590
1589
  def.ref.length > 1 && first.definition.isAssociation ? first : $baseLink,
1591
1590
  )
@@ -1854,6 +1853,11 @@ function cqn4sql(originalQuery, model) {
1854
1853
  result[i] = asXpr(xpr)
1855
1854
  continue
1856
1855
  }
1856
+ if(lhs.args) {
1857
+ const args = calculateOnCondition(lhs.args)
1858
+ result[i] = { ...lhs, args }
1859
+ continue
1860
+ }
1857
1861
  const rhs = result[i + 2]
1858
1862
  if (rhs?.ref || lhs.ref) {
1859
1863
  // if we have refs on each side of the comparison, we might need to perform tuple expansion
@@ -2194,7 +2198,7 @@ function cqn4sql(originalQuery, model) {
2194
2198
  * the combined elements of the query
2195
2199
  * @returns the source name which can be used to address the node
2196
2200
  */
2197
- function getQuerySourceName(node, $baseLink = null) {
2201
+ function getTableAlias(node, $baseLink = null) {
2198
2202
  if (!node || !node.$refLinks || !node.ref) {
2199
2203
  throw new Error('Invalid node')
2200
2204
  }
@@ -1,6 +1,7 @@
1
1
  const cds = require('@sap/cds')
2
2
  const { _target_name4 } = require('./SQLService')
3
- const InsertResult = require('../lib/InsertResults')
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
- const res = await Promise.all(queries.map(query => {
49
- if (query.INSERT) return this.onINSERT({ query })
50
- if (query.UPDATE) return this.onUPDATE({ query })
51
- if (query.DELETE) return this.onSIMPLE({ query })
52
- }))
53
- return (
54
- beforeData.length ||
55
- new InsertResult(query, [
56
- {
57
- changes: Array.isArray(req.data) ? req.data.length : 1,
58
- ...(res[0]?.results[0]?.lastInsertRowid ? { lastInsertRowid: res[0].results[0].lastInsertRowid } : {}),
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, true)
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 {boolean} [root=false]
209
- * @returns {import('@sap/cds/apis/cqn').Query[]}
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 = false) => {
212
- const queries = []
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
- arrayed.forEach(subEntry => {
228
- subQueries.push(..._getDeepQueries([subEntry], target.elements[prop]._target))
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
- queries.push(INSERT.into(target).entries(diffEntry))
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
- queries.push(DELETE.from(target).where(diffEntry))
250
- } else if (op === 'update' || (op === undefined && (root || subQueries.length) && _hasManagedElements(target))) {
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
- queries.push(cqn)
291
+ updates.push(cqn)
263
292
  }
264
-
265
- for (const q of subQueries) queries.push(q)
266
293
  }
267
294
 
268
- const insertQueries = new Map()
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/db-service",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
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": {
@@ -24,6 +24,9 @@
24
24
  "scripts": {
25
25
  "test": "jest --silent"
26
26
  },
27
+ "dependencies": {
28
+ "generic-pool": "^3.9.0"
29
+ },
27
30
  "peerDependencies": {
28
31
  "@sap/cds": ">=7.9"
29
32
  },