@cap-js/db-service 2.1.2 → 2.3.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 +23 -0
- package/lib/SQLService.js +5 -3
- package/lib/cqn2sql.js +21 -1
- package/lib/cqn4sql.js +102 -86
- package/lib/fill-in-keys.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@
|
|
|
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
|
+
## [2.3.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.2.0...db-service-v2.3.0) (2025-07-28)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* **`exists`:** support additional query modifiers ([#1261](https://github.com/cap-js/cds-dbs/issues/1261)) ([1394b46](https://github.com/cap-js/cds-dbs/commit/1394b46b10f628748dd1f945095ab4ce7f3963f6))
|
|
13
|
+
* **hierarchy:** LimitedRank ([#1268](https://github.com/cap-js/cds-dbs/issues/1268)) ([52e16db](https://github.com/cap-js/cds-dbs/commit/52e16db2c83d06e318ea05947a3c3c3153bd3ab2))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* `count` subquery for queries with only `expand` columns ([#1264](https://github.com/cap-js/cds-dbs/issues/1264)) ([097a332](https://github.com/cap-js/cds-dbs/commit/097a332f78156526823b2088bdc35278aea09854))
|
|
19
|
+
* **cqn4sql:** multiple path expressions in where clause ([#1272](https://github.com/cap-js/cds-dbs/issues/1272)) ([9b35366](https://github.com/cap-js/cds-dbs/commit/9b353660f4c2568176f57baa642ab2b052cfcff9))
|
|
20
|
+
* **hierarchy:** only modify where if existent ([#1265](https://github.com/cap-js/cds-dbs/issues/1265)) ([eaca855](https://github.com/cap-js/cds-dbs/commit/eaca855ec06087e22bf780fac5e4010ef1b5ff4f))
|
|
21
|
+
* TypeError for empty `list` ([#1269](https://github.com/cap-js/cds-dbs/issues/1269)) ([f262718](https://github.com/cap-js/cds-dbs/commit/f26271813e161c9f7c05fbc82b10c4d5f14916a7))
|
|
22
|
+
|
|
23
|
+
## [2.2.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.1.2...db-service-v2.2.0) (2025-06-30)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
* **recurse:** object-page hierarchies ([#1247](https://github.com/cap-js/cds-dbs/issues/1247)) ([6fe81f2](https://github.com/cap-js/cds-dbs/commit/6fe81f27bc1aee0b8edebe3d9251928ebea8474a))
|
|
29
|
+
|
|
7
30
|
## [2.1.2](https://github.com/cap-js/cds-dbs/compare/db-service-v2.1.1...db-service-v2.1.2) (2025-06-12)
|
|
8
31
|
|
|
9
32
|
|
package/lib/SQLService.js
CHANGED
|
@@ -337,9 +337,11 @@ class SQLService extends DatabaseService {
|
|
|
337
337
|
|
|
338
338
|
// Keep original query columns when potentially used insde conditions
|
|
339
339
|
const { having, groupBy } = query.SELECT
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
let columns = []
|
|
341
|
+
if((having?.length || groupBy?.length)) {
|
|
342
|
+
columns = query.SELECT.columns.filter(c => !c.expand)
|
|
343
|
+
}
|
|
344
|
+
if (columns.length === 0) columns.push({ val: 1 })
|
|
343
345
|
const cq = SELECT.one([{ func: 'count' }]).from(
|
|
344
346
|
cds.ql.clone(query, {
|
|
345
347
|
columns,
|
package/lib/cqn2sql.js
CHANGED
|
@@ -282,6 +282,25 @@ class CQN2SQLRenderer {
|
|
|
282
282
|
SELECT_recurse(q) {
|
|
283
283
|
let { from, columns, where, orderBy, recurse, _internal } = q.SELECT
|
|
284
284
|
|
|
285
|
+
const _target = q._target
|
|
286
|
+
|
|
287
|
+
if (_target && where) {
|
|
288
|
+
const keys = []
|
|
289
|
+
for (const _key in _target.keys) {
|
|
290
|
+
const k = _target.keys[_key]
|
|
291
|
+
if (!k.virtual && !k.isAssociation && !k.value) {
|
|
292
|
+
keys.push({ ref: [_key] })
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// `where` needs to be wrapped to also support `where == ['exists', { SELECT }]` which is not allowed in `START WHERE`
|
|
297
|
+
const clone = q.clone()
|
|
298
|
+
clone.columns(keys)
|
|
299
|
+
clone.SELECT.recurse = undefined
|
|
300
|
+
clone.SELECT.expand = undefined // omits JSON
|
|
301
|
+
where = [{ list: keys }, 'in', clone]
|
|
302
|
+
}
|
|
303
|
+
|
|
285
304
|
const requiredComputedColumns = { PARENT_ID: true, NODE_ID: true }
|
|
286
305
|
if (!_internal) requiredComputedColumns.RANK = true
|
|
287
306
|
const addComputedColumn = (name) => {
|
|
@@ -305,6 +324,7 @@ class CQN2SQLRenderer {
|
|
|
305
324
|
DistanceFromRoot: { xpr: [{ ref: ['HIERARCHY_LEVEL'] }, '-', { val: 1, param: false }], as: 'DistanceFromRoot' },
|
|
306
325
|
DrillState: false,
|
|
307
326
|
LimitedDescendantCount: { xpr: [{ ref: ['HIERARCHY_TREE_SIZE'] }, '-', { val: 1, param: false }], as: 'LimitedDescendantCount' },
|
|
327
|
+
LimitedRank: { xpr: [{ func: 'row_number', args: [] }, 'OVER', { xpr: [] }, '-', { val: 1, param: false }], as: 'LimitedRank' }
|
|
308
328
|
}
|
|
309
329
|
|
|
310
330
|
const columnsFiltered = columns
|
|
@@ -1157,7 +1177,7 @@ class CQN2SQLRenderer {
|
|
|
1157
1177
|
.map((x, i) => {
|
|
1158
1178
|
if (x in { LIKE: 1, like: 1 } && is_regexp(xpr[i + 1]?.val)) return this.operator('regexp')
|
|
1159
1179
|
if (typeof x === 'string') return this.operator(x, i, xpr)
|
|
1160
|
-
if (x.xpr) return `(${this.xpr(x)})`
|
|
1180
|
+
if (x.xpr && !x.func) return `(${this.xpr(x)})`
|
|
1161
1181
|
else return this.expr(x)
|
|
1162
1182
|
})
|
|
1163
1183
|
.join(' ')
|
package/lib/cqn4sql.js
CHANGED
|
@@ -5,7 +5,14 @@ cds.infer.target ??= q => q._target || q.target // instanceof cds.entity ? q._ta
|
|
|
5
5
|
|
|
6
6
|
const infer = require('./infer')
|
|
7
7
|
const { computeColumnsToBeSearched } = require('./search')
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
prettyPrintRef,
|
|
10
|
+
isCalculatedOnRead,
|
|
11
|
+
isCalculatedElement,
|
|
12
|
+
getImplicitAlias,
|
|
13
|
+
defineProperty,
|
|
14
|
+
getModelUtils,
|
|
15
|
+
} = require('./utils')
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* For operators of <eqOps>, this is replaced by comparing all leaf elements with null, combined with and.
|
|
@@ -63,19 +70,8 @@ function cqn4sql(originalQuery, model) {
|
|
|
63
70
|
}
|
|
64
71
|
// query modifiers can also be defined in from ref leaf infix filter
|
|
65
72
|
// > SELECT from bookshop.Books[order by price] {ID}
|
|
66
|
-
if (inferred.SELECT?.from.ref) {
|
|
67
|
-
|
|
68
|
-
if (key in { orderBy: 1, groupBy: 1 }) {
|
|
69
|
-
if (inferred.SELECT[key]) inferred.SELECT[key].push(...val)
|
|
70
|
-
else inferred.SELECT[key] = val
|
|
71
|
-
} else if (key === 'limit') {
|
|
72
|
-
// limit defined on the query has precedence
|
|
73
|
-
if (!inferred.SELECT.limit) inferred.SELECT.limit = val
|
|
74
|
-
} else if (key === 'having') {
|
|
75
|
-
if (!inferred.SELECT.having) inferred.SELECT.having = val
|
|
76
|
-
else inferred.SELECT.having.push('and', ...val)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
73
|
+
if (inferred.SELECT?.from.ref?.at(-1).id) {
|
|
74
|
+
assignQueryModifiers(inferred.SELECT, inferred.SELECT.from.ref.at(-1))
|
|
79
75
|
}
|
|
80
76
|
inferred = infer(inferred, model)
|
|
81
77
|
const { getLocalizedName, isLocalized, getDefinition } = getModelUtils(model, originalQuery) // TODO: pass model to getModelUtils
|
|
@@ -95,6 +91,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
95
91
|
const from = queryProp.from
|
|
96
92
|
|
|
97
93
|
const transformedProp = { __proto__: queryProp } // IMPORTANT: don't lose anything you might not know of
|
|
94
|
+
const queryNeedsJoins = inferred.joinTree && !inferred.joinTree.isInitial
|
|
98
95
|
|
|
99
96
|
// Transform the existing where, prepend table aliases, and so on...
|
|
100
97
|
if (where) {
|
|
@@ -104,7 +101,47 @@ function cqn4sql(originalQuery, model) {
|
|
|
104
101
|
// Transform the from clause: association path steps turn into `WHERE EXISTS` subqueries.
|
|
105
102
|
// The already transformed `where` clause is then glued together with the resulting subqueries.
|
|
106
103
|
const { transformedWhere, transformedFrom } = getTransformedFrom(from || entity, transformedProp.where)
|
|
107
|
-
|
|
104
|
+
|
|
105
|
+
// build a subquery for DELETE / UPDATE queries with path expressions and match the primary keys
|
|
106
|
+
if (queryNeedsJoins && (inferred.UPDATE || inferred.DELETE)) {
|
|
107
|
+
const prop = inferred.UPDATE ? 'UPDATE' : 'DELETE'
|
|
108
|
+
const subquery = {
|
|
109
|
+
SELECT: {
|
|
110
|
+
from: { ...(from || entity) },
|
|
111
|
+
columns: [], // primary keys of the query target will be added later
|
|
112
|
+
where: [...where],
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
// The alias of the original query is now the alias for the subquery
|
|
116
|
+
// so that potential references in the where clause to the alias match.
|
|
117
|
+
// Hence, replace the alias of the original query with the next
|
|
118
|
+
// available alias, so that each alias is unique.
|
|
119
|
+
const uniqueSubqueryAlias = getNextAvailableTableAlias(transformedFrom.as)
|
|
120
|
+
transformedFrom.as = uniqueSubqueryAlias
|
|
121
|
+
|
|
122
|
+
// calculate the primary keys of the target entity, there is always exactly
|
|
123
|
+
// one query source for UPDATE / DELETE
|
|
124
|
+
const queryTarget = Object.values(inferred.sources)[0].definition
|
|
125
|
+
const primaryKey = { list: [] }
|
|
126
|
+
for (const k of Object.keys(queryTarget.elements)) {
|
|
127
|
+
const e = queryTarget.elements[k]
|
|
128
|
+
if (e.key === true && !e.virtual && e.isAssociation !== true) {
|
|
129
|
+
subquery.SELECT.columns.push({ ref: [e.name] })
|
|
130
|
+
primaryKey.list.push({ ref: [transformedFrom.as, e.name] })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const transformedSubquery = cqn4sql(subquery, model)
|
|
135
|
+
|
|
136
|
+
// replace where condition of original query with the transformed subquery
|
|
137
|
+
// correlate UPDATE / DELETE query with subquery by primary key matches
|
|
138
|
+
transformedQuery[prop].where = [primaryKey, 'in', transformedSubquery]
|
|
139
|
+
|
|
140
|
+
if (prop === 'UPDATE') transformedQuery.UPDATE.entity = transformedFrom
|
|
141
|
+
else transformedQuery.DELETE.from = transformedFrom
|
|
142
|
+
|
|
143
|
+
return transformedQuery
|
|
144
|
+
}
|
|
108
145
|
|
|
109
146
|
if (inferred.SELECT) {
|
|
110
147
|
transformedQuery = transformSelectQuery(queryProp, transformedFrom, transformedWhere, transformedQuery)
|
|
@@ -130,45 +167,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
130
167
|
}
|
|
131
168
|
|
|
132
169
|
if (queryNeedsJoins) {
|
|
133
|
-
|
|
134
|
-
const prop = inferred.UPDATE ? 'UPDATE' : 'DELETE'
|
|
135
|
-
const subquery = {
|
|
136
|
-
SELECT: {
|
|
137
|
-
from: { ...transformedFrom },
|
|
138
|
-
columns: [], // primary keys of the query target will be added later
|
|
139
|
-
where: [...transformedProp.where],
|
|
140
|
-
},
|
|
141
|
-
}
|
|
142
|
-
// The alias of the original query is now the alias for the subquery
|
|
143
|
-
// so that potential references in the where clause to the alias match.
|
|
144
|
-
// Hence, replace the alias of the original query with the next
|
|
145
|
-
// available alias, so that each alias is unique.
|
|
146
|
-
const uniqueSubqueryAlias = getNextAvailableTableAlias(transformedFrom.as)
|
|
147
|
-
transformedFrom.as = uniqueSubqueryAlias
|
|
148
|
-
|
|
149
|
-
// calculate the primary keys of the target entity, there is always exactly
|
|
150
|
-
// one query source for UPDATE / DELETE
|
|
151
|
-
const queryTarget = Object.values(inferred.sources)[0].definition
|
|
152
|
-
const primaryKey = { list: [] }
|
|
153
|
-
for (const k of Object.keys(queryTarget.elements)) {
|
|
154
|
-
const e = queryTarget.elements[k]
|
|
155
|
-
if (e.key === true && !e.virtual && e.isAssociation !== true) {
|
|
156
|
-
subquery.SELECT.columns.push({ ref: [e.name] })
|
|
157
|
-
primaryKey.list.push({ ref: [transformedFrom.as, e.name] })
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const transformedSubquery = cqn4sql(subquery, model)
|
|
162
|
-
|
|
163
|
-
// replace where condition of original query with the transformed subquery
|
|
164
|
-
// correlate UPDATE / DELETE query with subquery by primary key matches
|
|
165
|
-
transformedQuery[prop].where = [primaryKey, 'in', transformedSubquery]
|
|
166
|
-
|
|
167
|
-
if (prop === 'UPDATE') transformedQuery.UPDATE.entity = transformedFrom
|
|
168
|
-
else transformedQuery.DELETE.from = transformedFrom
|
|
169
|
-
} else {
|
|
170
|
-
transformedQuery[kind].from = translateAssocsToJoins(transformedQuery[kind].from)
|
|
171
|
-
}
|
|
170
|
+
transformedQuery[kind].from = translateAssocsToJoins(transformedQuery[kind].from)
|
|
172
171
|
}
|
|
173
172
|
}
|
|
174
173
|
|
|
@@ -1420,7 +1419,8 @@ function cqn4sql(originalQuery, model) {
|
|
|
1420
1419
|
}
|
|
1421
1420
|
const { definition: fkSource } = next
|
|
1422
1421
|
ensureValidForeignKeys(fkSource, ref)
|
|
1423
|
-
|
|
1422
|
+
const { where, ...args } = step
|
|
1423
|
+
whereExistsSubSelects.push(getWhereExistsSubquery(current, next, where, true, args))
|
|
1424
1424
|
}
|
|
1425
1425
|
|
|
1426
1426
|
const whereExists = { SELECT: whereExistsSubqueries(whereExistsSubSelects) }
|
|
@@ -1668,8 +1668,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1668
1668
|
function getTransformedFrom(from, existingWhere = []) {
|
|
1669
1669
|
const transformedWhere = []
|
|
1670
1670
|
let transformedFrom = copy(from) // REVISIT: too expensive!
|
|
1671
|
-
if (from.$refLinks)
|
|
1672
|
-
defineProperty(transformedFrom, '$refLinks', [...from.$refLinks])
|
|
1671
|
+
if (from.$refLinks) defineProperty(transformedFrom, '$refLinks', [...from.$refLinks])
|
|
1673
1672
|
if (from.args) {
|
|
1674
1673
|
transformedFrom.args = []
|
|
1675
1674
|
from.args.forEach(arg => {
|
|
@@ -1716,7 +1715,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1716
1715
|
const nextStep = refReverse[i + 1] // only because we want the filter condition
|
|
1717
1716
|
|
|
1718
1717
|
if (current.definition.target && next) {
|
|
1719
|
-
const { where, args } = nextStep
|
|
1718
|
+
const { where, ...args } = nextStep
|
|
1720
1719
|
if (isStructured(next.definition)) {
|
|
1721
1720
|
// find next association / entity in the ref because this is actually our real nextStep
|
|
1722
1721
|
const nextStepIndex =
|
|
@@ -1777,12 +1776,10 @@ function cqn4sql(originalQuery, model) {
|
|
|
1777
1776
|
// adjust ref & $refLinks after associations have turned into where exists subqueries
|
|
1778
1777
|
transformedFrom.$refLinks.splice(0, transformedFrom.$refLinks.length - 1)
|
|
1779
1778
|
|
|
1780
|
-
let args = from.ref.at(-1).args
|
|
1781
1779
|
const subquerySource =
|
|
1782
1780
|
getDefinition(transformedFrom.$refLinks[0].definition.target) || transformedFrom.$refLinks[0].target
|
|
1783
|
-
if (subquerySource.params && !args) args = {}
|
|
1784
1781
|
const id = getLocalizedName(subquerySource)
|
|
1785
|
-
transformedFrom.ref = [
|
|
1782
|
+
transformedFrom.ref = [subquerySource.params ? { id, args: from.ref.at(-1).args || {} } : id]
|
|
1786
1783
|
|
|
1787
1784
|
return { transformedWhere, transformedFrom }
|
|
1788
1785
|
}
|
|
@@ -1814,10 +1811,6 @@ function cqn4sql(originalQuery, model) {
|
|
|
1814
1811
|
return inferred.joinTree.addNextAvailableTableAlias(id, inferred.outerQueries)
|
|
1815
1812
|
}
|
|
1816
1813
|
|
|
1817
|
-
function asXpr(thing) {
|
|
1818
|
-
return { xpr: thing }
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
1814
|
/**
|
|
1822
1815
|
* @param {CSN.Element} elt
|
|
1823
1816
|
* @returns {boolean}
|
|
@@ -2133,9 +2126,11 @@ function cqn4sql(originalQuery, model) {
|
|
|
2133
2126
|
* @param {object[]} customWhere infix filter which must be part of the where exists subquery on condition
|
|
2134
2127
|
* @param {boolean} inWhere whether or not the path is part of the queries where clause
|
|
2135
2128
|
* -> if it is, target and source side are flipped in the where exists subquery
|
|
2129
|
+
* @param {object} queryModifier optional query modifiers: group by, order by, limit, offset
|
|
2130
|
+
*
|
|
2136
2131
|
* @returns {CQN.SELECT}
|
|
2137
2132
|
*/
|
|
2138
|
-
function getWhereExistsSubquery(current, next, customWhere = null, inWhere = false,
|
|
2133
|
+
function getWhereExistsSubquery(current, next, customWhere = null, inWhere = false, queryModifier = null) {
|
|
2139
2134
|
const { definition } = current
|
|
2140
2135
|
const { definition: nextDefinition } = next
|
|
2141
2136
|
const on = []
|
|
@@ -2156,10 +2151,9 @@ function cqn4sql(originalQuery, model) {
|
|
|
2156
2151
|
|
|
2157
2152
|
const subquerySource = getDefinition(nextDefinition.target) || nextDefinition
|
|
2158
2153
|
const id = getLocalizedName(subquerySource)
|
|
2159
|
-
if (subquerySource.params && !customArgs) customArgs = {}
|
|
2160
2154
|
const SELECT = {
|
|
2161
2155
|
from: {
|
|
2162
|
-
ref: [
|
|
2156
|
+
ref: [subquerySource.params ? { id, args: queryModifier.args || {} } : id],
|
|
2163
2157
|
as: next.alias,
|
|
2164
2158
|
},
|
|
2165
2159
|
columns: [
|
|
@@ -2170,25 +2164,27 @@ function cqn4sql(originalQuery, model) {
|
|
|
2170
2164
|
],
|
|
2171
2165
|
where: on,
|
|
2172
2166
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
)
|
|
2167
|
+
// this requires sub-sequent transformation of the subquery
|
|
2168
|
+
if (next.pathExpressionInsideFilter || (queryModifier && ['orderBy', 'groupBy', 'having', 'limit', 'offset'].some(key => key in queryModifier))) {
|
|
2169
|
+
SELECT.where = next.pathExpressionInsideFilter ? customWhere : [];
|
|
2170
|
+
if (queryModifier) assignQueryModifiers(SELECT, queryModifier);
|
|
2171
|
+
|
|
2172
|
+
const transformedExists = transformSubquery({ SELECT });
|
|
2173
|
+
if (transformedExists.SELECT.where?.length) {
|
|
2174
|
+
const wrappedWhere = hasLogicalOr(transformedExists.SELECT.where)
|
|
2175
|
+
? [asXpr(transformedExists.SELECT.where)]
|
|
2176
|
+
: transformedExists.SELECT.where;
|
|
2177
|
+
|
|
2178
|
+
on.push('and', ...wrappedWhere);
|
|
2186
2179
|
}
|
|
2187
|
-
transformedExists.SELECT.where = on
|
|
2188
|
-
return transformedExists.SELECT
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2180
|
+
transformedExists.SELECT.where = on;
|
|
2181
|
+
return transformedExists.SELECT;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
if (customWhere) {
|
|
2185
|
+
const filter = getTransformedTokenStream(customWhere, next);
|
|
2186
|
+
const wrappedFilter = hasLogicalOr(filter) ? [asXpr(filter)] : filter;
|
|
2187
|
+
on.push('and', ...wrappedFilter);
|
|
2192
2188
|
}
|
|
2193
2189
|
return SELECT
|
|
2194
2190
|
}
|
|
@@ -2353,6 +2349,26 @@ function getParentEntity(element) {
|
|
|
2353
2349
|
else return getParentEntity(element.parent)
|
|
2354
2350
|
}
|
|
2355
2351
|
|
|
2352
|
+
|
|
2353
|
+
function asXpr(thing) {
|
|
2354
|
+
return { xpr: thing }
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
function assignQueryModifiers(SELECT, modifiers) {
|
|
2358
|
+
for (const [key, val] of Object.entries(modifiers)) {
|
|
2359
|
+
if (key in { orderBy: 1, groupBy: 1 }) {
|
|
2360
|
+
if (SELECT[key]) SELECT[key].push(...val)
|
|
2361
|
+
else SELECT[key] = val
|
|
2362
|
+
} else if (key === 'limit') {
|
|
2363
|
+
// limit defined on the query has precedence
|
|
2364
|
+
if (!SELECT.limit) SELECT.limit = val
|
|
2365
|
+
} else if (key === 'having') {
|
|
2366
|
+
if (!SELECT.having) SELECT.having = val
|
|
2367
|
+
else SELECT.having.push('and', ...val)
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2356
2372
|
/**
|
|
2357
2373
|
* Assigns the given `element` as non-enumerable property 'element' onto `col`.
|
|
2358
2374
|
*
|
package/lib/fill-in-keys.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = require('@sap/cds')
|
|
2
|
-
const { hasDeep } = require('
|
|
2
|
+
const { hasDeep } = require('./deep-queries')
|
|
3
3
|
|
|
4
4
|
// REVISIT: very deep & fragile dependencies to internal modules -> copy these into here
|
|
5
5
|
const propagateForeignKeys = require('@sap/cds/libx/_runtime/common/utils/propagateForeignKeys')
|
package/package.json
CHANGED