@cap-js/db-service 1.13.0 → 1.14.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,19 @@
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.14.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.13.0...db-service-v1.14.0) (2024-10-15)
8
+
9
+
10
+ ### Added
11
+
12
+ * assoc-like calc elements after exists predicate ([#831](https://github.com/cap-js/cds-dbs/issues/831)) ([05f7d75](https://github.com/cap-js/cds-dbs/commit/05f7d75837495d58cc4f72ad628077bdebb0acf6))
13
+
14
+
15
+ ### Fixed
16
+
17
+ * Improved behavioral consistency between the database services ([#837](https://github.com/cap-js/cds-dbs/issues/837)) ([b6f7187](https://github.com/cap-js/cds-dbs/commit/b6f718701e48dfb1c4c3d98ee016ec45930f8e7b))
18
+ * Treat assoc-like calculated elements as unmanaged assocs ([#830](https://github.com/cap-js/cds-dbs/issues/830)) ([cbe0df7](https://github.com/cap-js/cds-dbs/commit/cbe0df7a66fec0d421947767adc8621ed8bf236c))
19
+
7
20
  ## [1.13.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.12.1...db-service-v1.13.0) (2024-10-01)
8
21
 
9
22
 
package/lib/SQLService.js CHANGED
@@ -466,6 +466,10 @@ const sqls = new (class extends SQLService {
466
466
  get factory() {
467
467
  return null
468
468
  }
469
+
470
+ get model() {
471
+ return cds.model
472
+ }
469
473
  })()
470
474
  cds.extend(cds.ql.Query).with(
471
475
  class {
@@ -1,3 +1,5 @@
1
+ const cds = require("@sap/cds")
2
+
1
3
  const StandardFunctions = {
2
4
  // OData: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_CanonicalFunctions
3
5
 
@@ -59,7 +61,7 @@ const StandardFunctions = {
59
61
  * @param {string} x
60
62
  * @returns {string}
61
63
  */
62
- countdistinct: x => `count(distinct ${x || '*'})`,
64
+ countdistinct: x => `count(distinct ${x || cds.error`countdistinct requires a ref to be counted`})`,
63
65
  /**
64
66
  * Generates SQL statement that produces the index of the first occurrence of the second string in the first string
65
67
  * @param {string} x
package/lib/cqn2sql.js CHANGED
@@ -18,8 +18,8 @@ const DEBUG = (() => {
18
18
  return cds.debug('sql|sqlite')
19
19
  //if (DEBUG) {
20
20
  // return DEBUG
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
- // FIXME: looses closing ) on INSERT queries
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
+ // FIXME: looses closing ) on INSERT queries
23
23
  //}
24
24
  })()
25
25
 
@@ -88,6 +88,7 @@ class CQN2SQLRenderer {
88
88
  this.values = [] // prepare values, filled in by subroutines
89
89
  this[kind]((this.cqn = q)) // actual sql rendering happens here
90
90
  if (vars?.length && !this.values?.length) this.values = vars
91
+ if (vars && Object.keys(vars).length && !this.values?.length) this.values = vars
91
92
  const sanitize_values = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
92
93
  DEBUG?.(
93
94
  this.sql,
@@ -116,8 +117,13 @@ class CQN2SQLRenderer {
116
117
  * @param {import('./infer/cqn').CREATE} q
117
118
  */
118
119
  CREATE(q) {
119
- const { target } = q,
120
- { query } = target
120
+ let { target } = q
121
+ let query = target?.query || q.CREATE.as
122
+ if (!target || target._unresolved) {
123
+ const entity = q.CREATE.entity
124
+ target = typeof entity === 'string' ? { name: entity } : q.CREATE.entity
125
+ }
126
+
121
127
  const name = this.name(target.name)
122
128
  // Don't allow place holders inside views
123
129
  delete this.values
@@ -203,8 +209,9 @@ class CQN2SQLRenderer {
203
209
  */
204
210
  DROP(q) {
205
211
  const { target } = q
206
- const isView = target.query || target.projection
207
- return (this.sql = `DROP ${isView ? 'VIEW' : 'TABLE'} IF EXISTS ${this.quote(this.name(target.name))}`)
212
+ const isView = target?.query || target?.projection || q.DROP.view
213
+ const name = target?.name || q.DROP.table?.ref?.[0] || q.DROP.view?.ref?.[0]
214
+ return (this.sql = `DROP ${isView ? 'VIEW' : 'TABLE'} IF EXISTS ${this.quote(this.name(name))}`)
208
215
  }
209
216
 
210
217
  // SELECT Statements ------------------------------------------------
@@ -223,7 +230,7 @@ class CQN2SQLRenderer {
223
230
 
224
231
  // REVISIT: When selecting from an entity that is not in the model the from.where are not normalized (as cqn4sql is skipped)
225
232
  if (!where && from?.ref?.length === 1 && from.ref[0]?.where) where = from.ref[0]?.where
226
- let columns = this.SELECT_columns(q)
233
+ const columns = this.SELECT_columns(q)
227
234
  let sql = `SELECT`
228
235
  if (distinct) sql += ` DISTINCT`
229
236
  if (!_empty(columns)) sql += ` ${columns}`
package/lib/cqn4sql.js CHANGED
@@ -4,7 +4,7 @@ const cds = require('@sap/cds')
4
4
 
5
5
  const infer = require('./infer')
6
6
  const { computeColumnsToBeSearched } = require('./search')
7
- const { prettyPrintRef } = require('./utils')
7
+ const { prettyPrintRef, isCalculatedOnRead, isCalculatedElement } = require('./utils')
8
8
 
9
9
  /**
10
10
  * For operators of <eqOps>, this is replaced by comparing all leaf elements with null, combined with and.
@@ -317,10 +317,6 @@ function cqn4sql(originalQuery, model) {
317
317
  }
318
318
  }
319
319
 
320
- function isCalculatedOnRead(def) {
321
- return def?.value && !def.value.stored
322
- }
323
-
324
320
  /**
325
321
  * Walks over a list of columns (ref's, xpr, subqueries, val), applies flattening on structured types and expands wildcards.
326
322
  *
@@ -809,7 +805,7 @@ function cqn4sql(originalQuery, model) {
809
805
  const subqueryBase = {}
810
806
  for (const [key, value] of Object.entries(column)) {
811
807
  if (!(key in { ref: true, expand: true })) {
812
- subqueryBase[key] = value;
808
+ subqueryBase[key] = value
813
809
  }
814
810
  }
815
811
  const subquery = {
@@ -1365,20 +1361,17 @@ function cqn4sql(originalQuery, model) {
1365
1361
 
1366
1362
  const as = getNextAvailableTableAlias(getLastStringSegment(next.alias))
1367
1363
  next.alias = as
1368
- if (next.definition.value) {
1369
- throw new Error(
1370
- `Calculated elements cannot be used in “exists” predicates in: “exists ${tokenStream[i + 1].ref
1371
- .map(idOnly)
1372
- .join('.')}”`,
1373
- )
1374
- }
1375
1364
  if (!next.definition.target) {
1365
+ let type = next.definition.type
1366
+ if (isCalculatedElement(next.definition)) {
1367
+ // try to infer the type at the leaf for better error message
1368
+ const { $refLinks } = next.definition.value
1369
+ type = $refLinks?.at(-1).definition.type || 'expression'
1370
+ }
1376
1371
  throw new Error(
1377
1372
  `Expecting path “${tokenStream[i + 1].ref
1378
1373
  .map(idOnly)
1379
- .join('.')}” following “EXISTS” predicate to end with association/composition, found “${
1380
- next.definition.type
1381
- }”`,
1374
+ .join('.')}” following “EXISTS” predicate to end with association/composition, found “${type}”`,
1382
1375
  )
1383
1376
  }
1384
1377
  const { definition: fkSource } = next
@@ -4,6 +4,7 @@ const cds = require('@sap/cds')
4
4
 
5
5
  const JoinTree = require('./join-tree')
6
6
  const { pseudos } = require('./pseudos')
7
+ const { isCalculatedOnRead } = require('../utils')
7
8
  const cdsTypes = cds.linked({
8
9
  definitions: {
9
10
  Timestamp: { type: 'cds.Timestamp' },
@@ -746,7 +747,7 @@ function infer(originalQuery, model) {
746
747
  joinTree.mergeColumn(colWithBase, originalQuery.outerQueries)
747
748
  }
748
749
  }
749
- if (leafArt.value && !leafArt.value.stored) {
750
+ if (isCalculatedOnRead(leafArt)) {
750
751
  linkCalculatedElement(column, $baseLink, baseColumn)
751
752
  }
752
753
 
@@ -1054,7 +1055,7 @@ function infer(originalQuery, model) {
1054
1055
  if (element.type !== 'cds.LargeBinary') {
1055
1056
  queryElements[k] = element
1056
1057
  }
1057
- if (element.value) {
1058
+ if (isCalculatedOnRead(element)) {
1058
1059
  linkCalculatedElement(element)
1059
1060
  }
1060
1061
  }
@@ -1071,7 +1072,7 @@ function infer(originalQuery, model) {
1071
1072
  if (exclude(name) || name in queryElements) return true
1072
1073
  const element = tableAliases[0].tableAlias.elements[name]
1073
1074
  if (element.type !== 'cds.LargeBinary') queryElements[name] = element
1074
- if (element.value) {
1075
+ if (isCalculatedOnRead(element)) {
1075
1076
  linkCalculatedElement(element)
1076
1077
  }
1077
1078
  })
package/lib/utils.js CHANGED
@@ -21,7 +21,26 @@ function prettyPrintRef(ref, model = null) {
21
21
  }, '')
22
22
  }
23
23
 
24
+ /**
25
+ * Determines if a definition is calculated on read.
26
+ * - Stored calculated elements are not unfolded
27
+ * - Association like calculated elements have been re-written by the compiler
28
+ * they essentially behave like unmanaged associations as their calculations
29
+ * have been incorporated into an on-condition which is handled elsewhere
30
+ *
31
+ * @param {Object} def - The definition to check.
32
+ * @returns {boolean} - Returns true if the definition is calculated on read, otherwise false.
33
+ */
34
+ function isCalculatedOnRead(def) {
35
+ return isCalculatedElement(def) && !def.value.stored && !def.on
36
+ }
37
+ function isCalculatedElement(def) {
38
+ return def?.value
39
+ }
40
+
24
41
  // export the function to be used in other modules
25
42
  module.exports = {
26
43
  prettyPrintRef,
44
+ isCalculatedOnRead,
45
+ isCalculatedElement
27
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/db-service",
3
- "version": "1.13.0",
3
+ "version": "1.14.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": {
@@ -22,7 +22,7 @@
22
22
  "CHANGELOG.md"
23
23
  ],
24
24
  "scripts": {
25
- "test": "jest --silent"
25
+ "test": "cds-test"
26
26
  },
27
27
  "dependencies": {
28
28
  "generic-pool": "^3.9.0"