@cap-js/db-service 1.20.0 → 1.20.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 +10 -0
- package/lib/cqn2sql.js +1 -1
- package/lib/cqn4sql.js +9 -18
- package/lib/infer/index.js +32 -33
- package/lib/search.js +25 -18
- package/lib/utils.js +9 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
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.20.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.20.0...db-service-v1.20.1) (2025-05-27)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* **`search`:** do not search on non-projected elements ([#1198](https://github.com/cap-js/cds-dbs/issues/1198)) ([1461673](https://github.com/cap-js/cds-dbs/commit/14616730ba8c27e8ffa30c5962b881badfab991c))
|
|
13
|
+
* current_utctimestamp as default ([#1161](https://github.com/cap-js/cds-dbs/issues/1161)) ([c0cccad](https://github.com/cap-js/cds-dbs/commit/c0cccad921c45db96e14f0e2afeced6af69da4a2))
|
|
14
|
+
* exists within expression is properly detected ([#1156](https://github.com/cap-js/cds-dbs/issues/1156)) ([febe175](https://github.com/cap-js/cds-dbs/commit/febe1755186d291b92edbdf69cebbab68a53d0af))
|
|
15
|
+
* resilience for query re-use scenarios ([#1175](https://github.com/cap-js/cds-dbs/issues/1175)) ([fe9abd5](https://github.com/cap-js/cds-dbs/commit/fe9abd5e4c5e9153c0afad9164f240ae2eadf581))
|
|
16
|
+
|
|
7
17
|
## [1.20.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.19.1...db-service-v1.20.0) (2025-04-17)
|
|
8
18
|
|
|
9
19
|
|
package/lib/cqn2sql.js
CHANGED
|
@@ -1102,7 +1102,7 @@ class CQN2SQLRenderer {
|
|
|
1102
1102
|
|
|
1103
1103
|
let onInsert = this.managed_session_context(element[cdsOnInsert]?.['='])
|
|
1104
1104
|
|| this.managed_session_context(element.default?.ref?.[0])
|
|
1105
|
-
|| (element.default
|
|
1105
|
+
|| (element.default && { __proto__: element.default, param: false })
|
|
1106
1106
|
let onUpdate = this.managed_session_context(element[cdsOnUpdate]?.['='])
|
|
1107
1107
|
|
|
1108
1108
|
if (onInsert) onInsert = this.expr(onInsert)
|
package/lib/cqn4sql.js
CHANGED
|
@@ -5,7 +5,7 @@ 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 { prettyPrintRef, isCalculatedOnRead, isCalculatedElement, getImplicitAlias, getModelUtils } = require('./utils')
|
|
8
|
+
const { prettyPrintRef, isCalculatedOnRead, isCalculatedElement, getImplicitAlias, defineProperty, getModelUtils } = require('./utils')
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* For operators of <eqOps>, this is replaced by comparing all leaf elements with null, combined with and.
|
|
@@ -845,10 +845,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
845
845
|
}
|
|
846
846
|
const expanded = transformSubquery(subquery)
|
|
847
847
|
const correlated = _correlate({ ...expanded, as: columnAlias }, outerAlias)
|
|
848
|
-
|
|
849
|
-
value: expanded.elements,
|
|
850
|
-
writable: true,
|
|
851
|
-
})
|
|
848
|
+
defineProperty(correlated, 'elements', expanded.elements)
|
|
852
849
|
return correlated
|
|
853
850
|
|
|
854
851
|
function _correlate(subq, outer) {
|
|
@@ -1070,7 +1067,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1070
1067
|
else {
|
|
1071
1068
|
const outerQueries = inferred.outerQueries || []
|
|
1072
1069
|
outerQueries.push(inferred)
|
|
1073
|
-
|
|
1070
|
+
defineProperty(q, 'outerQueries', outerQueries)
|
|
1074
1071
|
}
|
|
1075
1072
|
const target = cds.infer.target(inferred) // REVISIT: we should reliably use inferred._target instead
|
|
1076
1073
|
if (isLocalized(target)) q.SELECT.localized = true
|
|
@@ -1084,7 +1081,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1084
1081
|
getImplicitAlias(last.id || last),
|
|
1085
1082
|
inferred.outerQueries,
|
|
1086
1083
|
)
|
|
1087
|
-
|
|
1084
|
+
defineProperty(q.SELECT.from, 'uniqueSubqueryAlias', uniqueSubqueryAlias)
|
|
1088
1085
|
}
|
|
1089
1086
|
}
|
|
1090
1087
|
|
|
@@ -1311,7 +1308,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1311
1308
|
const flatForeignKey = getDefinition(element.parent.name)?.elements[fkBaseName]
|
|
1312
1309
|
|
|
1313
1310
|
setElementOnColumns(flatColumn, flatForeignKey || fkElement)
|
|
1314
|
-
|
|
1311
|
+
defineProperty(flatColumn, '_csnPath', csnPath)
|
|
1315
1312
|
flatColumns.push(flatColumn)
|
|
1316
1313
|
}
|
|
1317
1314
|
}
|
|
@@ -1343,7 +1340,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1343
1340
|
if (column.sort) flatRef.sort = column.sort
|
|
1344
1341
|
if (columnAlias) flatRef.as = columnAlias
|
|
1345
1342
|
setElementOnColumns(flatRef, element)
|
|
1346
|
-
|
|
1343
|
+
defineProperty(flatRef, '_csnPath', csnPath)
|
|
1347
1344
|
return [flatRef]
|
|
1348
1345
|
|
|
1349
1346
|
function getReplacement(from) {
|
|
@@ -1676,7 +1673,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1676
1673
|
const transformedWhere = []
|
|
1677
1674
|
let transformedFrom = copy(from) // REVISIT: too expensive!
|
|
1678
1675
|
if (from.$refLinks)
|
|
1679
|
-
|
|
1676
|
+
defineProperty(transformedFrom, '$refLinks', [...from.$refLinks])
|
|
1680
1677
|
if (from.args) {
|
|
1681
1678
|
transformedFrom.args = []
|
|
1682
1679
|
from.args.forEach(arg => {
|
|
@@ -1924,10 +1921,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1924
1921
|
const refLinkFaker = thing => {
|
|
1925
1922
|
const { ref } = thing
|
|
1926
1923
|
const assocHost = getParentEntity(assocRefLink.definition)
|
|
1927
|
-
|
|
1928
|
-
value: [],
|
|
1929
|
-
writable: true,
|
|
1930
|
-
})
|
|
1924
|
+
defineProperty(thing, '$refLinks', [])
|
|
1931
1925
|
let pseudoPath = false
|
|
1932
1926
|
ref.reduce((prev, res, i) => {
|
|
1933
1927
|
if (res === '$self') {
|
|
@@ -2370,10 +2364,7 @@ function getParentEntity(element) {
|
|
|
2370
2364
|
* @param {csn.Element} element
|
|
2371
2365
|
*/
|
|
2372
2366
|
function setElementOnColumns(col, element) {
|
|
2373
|
-
|
|
2374
|
-
value: element,
|
|
2375
|
-
writable: true,
|
|
2376
|
-
})
|
|
2367
|
+
defineProperty(col, 'element', element)
|
|
2377
2368
|
}
|
|
2378
2369
|
|
|
2379
2370
|
const getName = col => col.as || col.ref?.at(-1)
|
package/lib/infer/index.js
CHANGED
|
@@ -4,7 +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, getImplicitAlias, getModelUtils } = require('../utils')
|
|
7
|
+
const { isCalculatedOnRead, getImplicitAlias, getModelUtils, defineProperty } = require('../utils')
|
|
8
8
|
const cdsTypes = cds.linked({
|
|
9
9
|
definitions: {
|
|
10
10
|
Timestamp: { type: 'cds.Timestamp' },
|
|
@@ -75,7 +75,7 @@ function infer(originalQuery, model) {
|
|
|
75
75
|
joinTree: { value: joinTree, writable: true, configurable: true }, // REVISIT: eliminate
|
|
76
76
|
})
|
|
77
77
|
// also enrich original query -> writable because it may be inferred again
|
|
78
|
-
|
|
78
|
+
defineProperty(originalQuery, 'elements', elements)
|
|
79
79
|
}
|
|
80
80
|
return inferred
|
|
81
81
|
|
|
@@ -171,10 +171,7 @@ function infer(originalQuery, model) {
|
|
|
171
171
|
* @param {csn.Element} element
|
|
172
172
|
*/
|
|
173
173
|
function setElementOnColumns(col, element) {
|
|
174
|
-
|
|
175
|
-
value: element,
|
|
176
|
-
writable: true,
|
|
177
|
-
})
|
|
174
|
+
defineProperty(col, 'element', element)
|
|
178
175
|
}
|
|
179
176
|
|
|
180
177
|
/**
|
|
@@ -245,7 +242,7 @@ function infer(originalQuery, model) {
|
|
|
245
242
|
// link $refLinks -> special name resolution rules for orderBy
|
|
246
243
|
orderBy.forEach(token => {
|
|
247
244
|
let $baseLink
|
|
248
|
-
let
|
|
245
|
+
let needsElementsOfQueryAsBase
|
|
249
246
|
// first check if token ref is resolvable in query elements
|
|
250
247
|
if (columns) {
|
|
251
248
|
const firstStep = token.ref?.[0].id || token.ref?.[0]
|
|
@@ -253,14 +250,11 @@ function infer(originalQuery, model) {
|
|
|
253
250
|
const columnName = c.as || c.flatName || c.ref?.at(-1).id || c.ref?.at(-1) || c.func
|
|
254
251
|
return columnName === firstStep
|
|
255
252
|
})
|
|
256
|
-
|
|
253
|
+
needsElementsOfQueryAsBase =
|
|
257
254
|
tokenPointsToQueryElements &&
|
|
258
|
-
queryElements[
|
|
259
|
-
/* expand on structure can be addressed */ !queryElements[
|
|
255
|
+
queryElements[firstStep] &&
|
|
256
|
+
/* expand on structure can be addressed */ !queryElements[firstStep].$assocExpand
|
|
260
257
|
|
|
261
|
-
// if the ref points into the query itself and follows an exposed association
|
|
262
|
-
// to a non-fk column, we must reject the ref, as we can't join with the queries own results
|
|
263
|
-
rejectJoinRelevantPath = needsElementsOfQueryAsBase
|
|
264
258
|
if (needsElementsOfQueryAsBase) $baseLink = { definition: { elements: queryElements }, target: inferred }
|
|
265
259
|
} else {
|
|
266
260
|
// fallback to elements of query source
|
|
@@ -268,7 +262,9 @@ function infer(originalQuery, model) {
|
|
|
268
262
|
}
|
|
269
263
|
|
|
270
264
|
inferArg(token, queryElements, $baseLink, { inQueryModifier: true })
|
|
271
|
-
if
|
|
265
|
+
// if the ref points into the query itself and follows an exposed association
|
|
266
|
+
// to a non-fk column, we must reject the ref, as we can't join with the queries own results
|
|
267
|
+
if (token.isJoinRelevant && needsElementsOfQueryAsBase) {
|
|
272
268
|
// reverse the array, find the last association and calculate the index of the association in non-reversed order
|
|
273
269
|
const assocIndex =
|
|
274
270
|
token.$refLinks.length - 1 - token.$refLinks.reverse().findIndex(link => link.definition.isAssociation)
|
|
@@ -406,18 +402,22 @@ function infer(originalQuery, model) {
|
|
|
406
402
|
if (arg.param || arg.SELECT) return // parameter references are only resolved into values on execution e.g. :val, :1 or ?
|
|
407
403
|
if (arg.args) applyToFunctionArgs(arg.args, inferArg, [null, $baseLink, context])
|
|
408
404
|
if (arg.list) arg.list.forEach(arg => inferArg(arg, null, $baseLink, context))
|
|
409
|
-
if (arg.xpr)
|
|
405
|
+
if (arg.xpr)
|
|
406
|
+
arg.xpr.forEach((token, i) =>
|
|
407
|
+
inferArg(token, queryElements, $baseLink, { ...context, inXpr: true, inExists: arg.xpr[i - 1] === 'exists' }),
|
|
408
|
+
) // e.g. function in expression
|
|
410
409
|
|
|
411
410
|
if (!arg.ref) {
|
|
412
411
|
if (arg.expand && queryElements) queryElements[arg.as] = resolveExpand(arg)
|
|
413
412
|
return
|
|
414
413
|
}
|
|
415
414
|
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
415
|
+
// Before the arg is linked, it's meta information should be cleaned up.
|
|
416
|
+
// This may be important if one manipulates the arg object
|
|
417
|
+
// __after__ a query has been fired and re-uses the manipulated query
|
|
418
|
+
defineProperty(arg, '$refLinks', [])
|
|
419
|
+
defineProperty(arg, 'isJoinRelevant', false)
|
|
420
|
+
|
|
421
421
|
// if any path step points to an artifact with `@cds.persistence.skip`
|
|
422
422
|
// we must ignore the element from the queries elements
|
|
423
423
|
let isPersisted = true
|
|
@@ -428,7 +428,7 @@ function infer(originalQuery, model) {
|
|
|
428
428
|
expandOnTableAlias = arg.ref.length === 1 && arg.ref[0] in sources && (arg.expand || arg.inline)
|
|
429
429
|
}
|
|
430
430
|
if (dollarSelfRefs && firstStepIsSelf) {
|
|
431
|
-
|
|
431
|
+
defineProperty(arg, 'inXpr', true)
|
|
432
432
|
dollarSelfRefs.push(arg)
|
|
433
433
|
return
|
|
434
434
|
}
|
|
@@ -455,7 +455,7 @@ function infer(originalQuery, model) {
|
|
|
455
455
|
const nextStep = arg.ref[1]?.id || arg.ref[1]
|
|
456
456
|
if (isNonForeignKeyNavigation(element, nextStep)) {
|
|
457
457
|
if (inExists) {
|
|
458
|
-
|
|
458
|
+
defineProperty($baseLink, 'pathExpressionInsideFilter', true)
|
|
459
459
|
} else {
|
|
460
460
|
rejectNonFkNavigation(element, element.on ? $baseLink.definition.name : nextStep)
|
|
461
461
|
}
|
|
@@ -519,7 +519,7 @@ function infer(originalQuery, model) {
|
|
|
519
519
|
const nextStep = arg.ref[i + 1]?.id || arg.ref[i + 1]
|
|
520
520
|
if (isNonForeignKeyNavigation(element, nextStep)) {
|
|
521
521
|
if (inExists) {
|
|
522
|
-
|
|
522
|
+
defineProperty($baseLink, 'pathExpressionInsideFilter', true)
|
|
523
523
|
} else {
|
|
524
524
|
rejectNonFkNavigation(element, element.on ? $baseLink.definition.name : nextStep)
|
|
525
525
|
}
|
|
@@ -535,7 +535,7 @@ function infer(originalQuery, model) {
|
|
|
535
535
|
} else if (id === '$dummy') {
|
|
536
536
|
// `some.known.element.$dummy` -> no error; used by cds.ql to simulate joins
|
|
537
537
|
arg.$refLinks.push({ definition: { name: '$dummy', parent: arg.$refLinks[i - 1].target } })
|
|
538
|
-
|
|
538
|
+
defineProperty(arg, 'isJoinRelevant', true)
|
|
539
539
|
} else {
|
|
540
540
|
const notFoundIn = pseudoPath ? arg.ref[i - 1] : getFullPathForLinkedArg(arg)
|
|
541
541
|
stepNotFoundInPredecessor(id, notFoundIn)
|
|
@@ -561,7 +561,7 @@ function infer(originalQuery, model) {
|
|
|
561
561
|
const definition = arg.$refLinks[i].definition
|
|
562
562
|
if ((!definition.target && definition.kind !== 'entity') || (!inFrom && danglingFilter))
|
|
563
563
|
throw new Error('A filter can only be provided when navigating along associations')
|
|
564
|
-
if (!inFrom && !arg.expand)
|
|
564
|
+
if (!inFrom && !arg.expand)defineProperty(arg, 'isJoinRelevant', true)
|
|
565
565
|
let skipJoinsForFilter = false
|
|
566
566
|
step.where.forEach(token => {
|
|
567
567
|
if (token === 'exists') {
|
|
@@ -590,7 +590,7 @@ function infer(originalQuery, model) {
|
|
|
590
590
|
if (getDefinition(arg.$refLinks[i].definition.target)?.['@cds.persistence.skip'] === true) isPersisted = false
|
|
591
591
|
if (!arg.ref[i + 1]) {
|
|
592
592
|
const flatName = nameSegments.join('_')
|
|
593
|
-
|
|
593
|
+
defineProperty(arg, 'flatName', flatName)
|
|
594
594
|
// if column is casted, we overwrite it's origin with the new type
|
|
595
595
|
if (arg.cast) {
|
|
596
596
|
const base = getElementForCast(arg)
|
|
@@ -635,7 +635,7 @@ function infer(originalQuery, model) {
|
|
|
635
635
|
})
|
|
636
636
|
|
|
637
637
|
// we need inner joins for the path expressions inside filter expressions after exists predicate
|
|
638
|
-
if ($baseLink?.pathExpressionInsideFilter)
|
|
638
|
+
if ($baseLink?.pathExpressionInsideFilter) defineProperty(arg, 'join', 'inner')
|
|
639
639
|
|
|
640
640
|
// ignore whole expand if target of assoc along path has ”@cds.persistence.skip”
|
|
641
641
|
if (arg.expand) {
|
|
@@ -655,7 +655,7 @@ function infer(originalQuery, model) {
|
|
|
655
655
|
? { ref: [...baseColumn.ref, ...arg.ref], $refLinks: [...baseColumn.$refLinks, ...arg.$refLinks] }
|
|
656
656
|
: arg
|
|
657
657
|
if (isColumnJoinRelevant(colWithBase)) {
|
|
658
|
-
|
|
658
|
+
defineProperty(arg, 'isJoinRelevant', true)
|
|
659
659
|
joinTree.mergeColumn(colWithBase, originalQuery.outerQueries)
|
|
660
660
|
}
|
|
661
661
|
}
|
|
@@ -761,7 +761,7 @@ function infer(originalQuery, model) {
|
|
|
761
761
|
const res = $leafLink.definition.is2one
|
|
762
762
|
? new cds.struct({ elements: inferredExpandSubquery.elements })
|
|
763
763
|
: new cds.array({ items: new cds.struct({ elements: inferredExpandSubquery.elements }) })
|
|
764
|
-
return
|
|
764
|
+
return defineProperty(res, '$assocExpand', true)
|
|
765
765
|
} else if ($leafLink.definition.elements) {
|
|
766
766
|
let elements = {}
|
|
767
767
|
expand.forEach(e => {
|
|
@@ -900,13 +900,13 @@ function infer(originalQuery, model) {
|
|
|
900
900
|
const calcElementIsJoinRelevant = isColumnJoinRelevant(p)
|
|
901
901
|
if (calcElementIsJoinRelevant) {
|
|
902
902
|
if (!calcElement.value.isJoinRelevant)
|
|
903
|
-
|
|
903
|
+
defineProperty(step, 'isJoinRelevant',true)
|
|
904
904
|
joinTree.mergeColumn(p, originalQuery.outerQueries)
|
|
905
905
|
} else {
|
|
906
906
|
// we need to explicitly set the value to false in this case,
|
|
907
907
|
// e.g. `SELECT from booksCalc.Books { ID, author.{name }, author {name } }`
|
|
908
908
|
// --> for the inline column, the name is join relevant, while for the expand, it is not
|
|
909
|
-
|
|
909
|
+
defineProperty(step, 'isJoinRelevant', false)
|
|
910
910
|
}
|
|
911
911
|
}
|
|
912
912
|
}
|
|
@@ -1064,7 +1064,6 @@ function infer(originalQuery, model) {
|
|
|
1064
1064
|
* @returns {object} a copy of @param base with all annotations of @param from
|
|
1065
1065
|
* @TODO prototype based
|
|
1066
1066
|
*/
|
|
1067
|
-
// REVISIT: TODO: inferred.elements should be linked
|
|
1068
1067
|
function getCopyWithAnnos(from, base) {
|
|
1069
1068
|
const result = { ...base }
|
|
1070
1069
|
// REVISIT: we don't need to and hence should not handle annotations at runtime
|
|
@@ -1072,7 +1071,7 @@ function infer(originalQuery, model) {
|
|
|
1072
1071
|
if (prop.startsWith('@')) result[prop] = from[prop]
|
|
1073
1072
|
}
|
|
1074
1073
|
|
|
1075
|
-
if (from.as && base.name !== from.as)
|
|
1074
|
+
if (from.as && base.name !== from.as) defineProperty(result, 'name', from.as) // TODO double check if this is needed
|
|
1076
1075
|
// in subqueries we need the linked element if an outer query accesses it
|
|
1077
1076
|
return Object.setPrototypeOf(result, base)
|
|
1078
1077
|
}
|
package/lib/search.js
CHANGED
|
@@ -79,7 +79,7 @@ const _getSearchableColumns = entity => {
|
|
|
79
79
|
const column = entity.elements[columnName]
|
|
80
80
|
if (column?.isAssociation || columnName.includes('.')) {
|
|
81
81
|
deepSearchCandidates.push({ ref: columnName.split('.') })
|
|
82
|
-
continue
|
|
82
|
+
continue
|
|
83
83
|
}
|
|
84
84
|
cdsSearchColumnMap.set(columnName, annotationValue)
|
|
85
85
|
}
|
|
@@ -93,8 +93,8 @@ const _getSearchableColumns = entity => {
|
|
|
93
93
|
// `@cds.search { element1: true }` or `@cds.search { element1 }`
|
|
94
94
|
if (annotatedColumnValue) return true
|
|
95
95
|
|
|
96
|
-
// calculated elements are only searchable if requested through `@cds.search`
|
|
97
|
-
if(column.value) return false
|
|
96
|
+
// calculated elements are only searchable if requested through `@cds.search`
|
|
97
|
+
if (column.value) return false
|
|
98
98
|
|
|
99
99
|
// if at least one element is explicitly annotated as searchable, e.g.:
|
|
100
100
|
// `@cds.search { element1: true }` or `@cds.search { element1 }`
|
|
@@ -112,16 +112,23 @@ const _getSearchableColumns = entity => {
|
|
|
112
112
|
|
|
113
113
|
if (deepSearchCandidates.length) {
|
|
114
114
|
deepSearchCandidates.forEach(c => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
let element = entity
|
|
116
|
+
for (let i = 0; i < c.ref.length; ++i) {
|
|
117
|
+
const curr = c.ref[i]
|
|
118
|
+
const next = element.elements?.[curr] ?? element._target?.elements?.[curr]
|
|
119
|
+
|
|
120
|
+
if (!next) { // e.g. if a search element is not part of a projection
|
|
121
|
+
element = undefined
|
|
122
|
+
break
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
|
|
125
|
+
if (next.isAssociation && i === c.ref.length - 1) {
|
|
126
|
+
_getSearchableColumns(next._target).forEach(r => searchableColumns.push({ ref: c.ref.concat(...r.ref) }))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
element = next
|
|
130
|
+
}
|
|
131
|
+
|
|
125
132
|
if (element?.type === DEFAULT_SEARCHABLE_TYPE) {
|
|
126
133
|
searchableColumns.push({ ref: c.ref })
|
|
127
134
|
}
|
|
@@ -129,9 +136,8 @@ const _getSearchableColumns = entity => {
|
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
return searchableColumns.map(column => {
|
|
132
|
-
if(column.ref)
|
|
133
|
-
|
|
134
|
-
return { ref: [ column.name ] }
|
|
139
|
+
if (column.ref) return column
|
|
140
|
+
return { ref: [column.name] }
|
|
135
141
|
})
|
|
136
142
|
}
|
|
137
143
|
|
|
@@ -183,15 +189,16 @@ const computeColumnsToBeSearched = (cqn, entity = { __searchableColumns: [] }) =
|
|
|
183
189
|
}
|
|
184
190
|
})
|
|
185
191
|
} else {
|
|
186
|
-
if(entity.kind === 'entity') {
|
|
192
|
+
if (entity.kind === 'entity') {
|
|
187
193
|
// first check cache
|
|
188
|
-
toBeSearched =
|
|
194
|
+
toBeSearched =
|
|
195
|
+
entity.own('__searchableColumns') || entity.set('__searchableColumns', _getSearchableColumns(entity))
|
|
189
196
|
} else {
|
|
190
197
|
// if we search on a subquery, we don't have a cache
|
|
191
198
|
toBeSearched = _getSearchableColumns(entity)
|
|
192
199
|
}
|
|
193
200
|
toBeSearched = toBeSearched.map(c => {
|
|
194
|
-
const column = {ref: [...c.ref]}
|
|
201
|
+
const column = { ref: [...c.ref] }
|
|
195
202
|
return column
|
|
196
203
|
})
|
|
197
204
|
}
|
package/lib/utils.js
CHANGED
|
@@ -62,6 +62,14 @@ function getImplicitAlias(str, useTechnicalAlias = true) {
|
|
|
62
62
|
return index != -1 ? str.substring(index + 1) : str
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function defineProperty(obj, prop, value) {
|
|
66
|
+
return Object.defineProperty(obj, prop, {
|
|
67
|
+
value,
|
|
68
|
+
writable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
/**
|
|
66
74
|
* Shared utility functions which operate dynamically on the model / query.
|
|
67
75
|
*
|
|
@@ -129,5 +137,6 @@ module.exports = {
|
|
|
129
137
|
isCalculatedOnRead,
|
|
130
138
|
isCalculatedElement,
|
|
131
139
|
getImplicitAlias,
|
|
140
|
+
defineProperty,
|
|
132
141
|
getModelUtils,
|
|
133
142
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/db-service",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.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": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/cap-js/cds-dbs"
|
|
8
|
+
"url": "git+https://github.com/cap-js/cds-dbs.git"
|
|
9
9
|
},
|
|
10
10
|
"bugs": {
|
|
11
11
|
"url": "https://github.com/cap-js/cds-dbs/issues"
|