Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@cap-js/db-service

Package Overview
Dependencies
Maintainers
2
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cap-js/db-service - npm Package Compare versions

Comparing version
2.8.2
to
2.9.0
+19
-0
CHANGELOG.md

@@ -7,2 +7,21 @@ # Changelog

## [2.9.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.8.2...db-service-v2.9.0) (2026-03-09)
### Added
* runtime views ([#1410](https://github.com/cap-js/cds-dbs/issues/1410)) ([5242675](https://github.com/cap-js/cds-dbs/commit/5242675c97472b86b81b3dc5fe0906141d276b02))
* support calculated elements in hierarchies ([#1456](https://github.com/cap-js/cds-dbs/issues/1456)) ([97c6f66](https://github.com/cap-js/cds-dbs/commit/97c6f6661f0ac4043245e021f2bf182f4e5d406f))
### Fixed
* **`exists`:** detect join relevant path after exists ([#1412](https://github.com/cap-js/cds-dbs/issues/1412)) ([c5bad06](https://github.com/cap-js/cds-dbs/commit/c5bad06724ce6761379f91748490c6caac84153a)), closes [#1407](https://github.com/cap-js/cds-dbs/issues/1407)
* **cqn2sql:** Relied on inconstistent behavior of cds.ql.cloned queries ([#1500](https://github.com/cap-js/cds-dbs/issues/1500)) ([f9cb201](https://github.com/cap-js/cds-dbs/commit/f9cb2011219a86ae22f22fcc105e597b23209adf))
* enable expressions for `inline` ([#1512](https://github.com/cap-js/cds-dbs/issues/1512)) ([65f78e1](https://github.com/cap-js/cds-dbs/commit/65f78e1f3af83188462e9d44db67daa5d743ceb0))
* path expressions for scoped queries ([#1507](https://github.com/cap-js/cds-dbs/issues/1507)) ([0f1e234](https://github.com/cap-js/cds-dbs/commit/0f1e234b373f26a6244c715c9ca9d4a207a0faed))
* reject duplicated wildcards ([#1511](https://github.com/cap-js/cds-dbs/issues/1511)) ([b483062](https://github.com/cap-js/cds-dbs/commit/b483062e2ff5a8d0960dc2e7b71880af87ee8f78))
* the combination of `iterator` and `SELECT.one` ([#1514](https://github.com/cap-js/cds-dbs/issues/1514)) ([4b28579](https://github.com/cap-js/cds-dbs/commit/4b2857920a7a57bcfc09a9b5fb765283cf8bd70b))
* wildcard on inlined assoc ([#1513](https://github.com/cap-js/cds-dbs/issues/1513)) ([e520b97](https://github.com/cap-js/cds-dbs/commit/e520b97fd30394825b937b3613370c32c36c24a4))
## [2.8.2](https://github.com/cap-js/cds-dbs/compare/db-service-v2.8.1...db-service-v2.8.2) (2026-02-03)

@@ -9,0 +28,0 @@

+124
-42
const cds = require('@sap/cds')
const cds_infer = require('./infer')
const cqn4sql = require('./cqn4sql')
const { resolveTable } = require('./utils')
const _simple_queries = cds.env.features.sql_simple_queries

@@ -29,3 +31,4 @@ const _strict_booleans = _simple_queries < 2

const e = name.id || name
return (query?._target || this.model?.definitions[e])?.['@cds.persistence.name'] || e
const entity = query?._target || this.model?.definitions[e]
return (!entity?.['@cds.persistence.skip'] && entity?.['@cds.persistence.name']) || e
}

@@ -80,2 +83,3 @@ this.class.prototype.quote = (s) => `"${String(s).replace(/"/g, '""')}"`

const kind = q.kind || Object.keys(q)[0] // SELECT, INSERT, ...
if (q._with) this._with = q._with
/**

@@ -95,3 +99,2 @@ * @type {string} the rendered SQL string

if (DEBUG && (LOG_SQL._debug || LOG_SQLITE._debug)) {

@@ -263,9 +266,11 @@ let values = sanitize_values && (this.entries || this.values?.length > 0) ? ['***'] : this.entries || this.values || []

if (!where && from?.ref?.length === 1 && from.ref[0]?.where) where = from.ref[0]?.where
const columns = this.SELECT_columns(q)
let sql = `SELECT`
if (distinct) sql += ` DISTINCT`
if (!_empty(columns)) sql += ` ${columns}`
if (recurse) sql += ` FROM ${this.SELECT_recurse(q)}`
else if (!_empty(from)) sql += ` FROM ${this.from(from, q)}`
else sql += this.from_dummy()
if (recurse) sql += this.SELECT_recurse(q)
else {
sql += ` ${this.SELECT_columns(q)}`
if (!_empty(from)) sql += ` FROM ${this.from(from, q)}`
else sql += this.from_dummy()
}
if (!recurse && !_empty(where)) sql += ` WHERE ${this.where(where)}`

@@ -303,3 +308,3 @@ if (!recurse && !_empty(groupBy)) sql += ` GROUP BY ${this.groupBy(groupBy)}`

const clone = q.clone()
clone.columns(keys)
clone.SELECT.columns = keys
clone.SELECT.recurse = undefined

@@ -355,6 +360,12 @@ clone.SELECT.limit = undefined

const element = target.elements[name]
if (element.virtual || element.value || element.isAssociation) continue
if (element['@Core.Computed'] && name in availableComputedColumns) continue
if (element.virtual || element.isAssociation) continue
if (name in availableComputedColumns) continue
if (name.toUpperCase() in reservedColumnNames) ref.as = `$$${name}$$`
columnsIn.push(ref)
// This only supports calculated elements within the scope of the own entity
if ('value' in element) {
const requested = columnsFiltered.find(c => this.column_name(c) === element.name)
if (requested) columnsIn.push(requested)
else continue
}
else columnsIn.push(ref)
const foreignkey4 = element._foreignKey4

@@ -389,3 +400,3 @@ if (

if (orderBy) {
orderBy = orderBy.map(r => {
orderBy = orderBy.filter(o => o.ref).map(r => {
let col = r.ref.at(-1)

@@ -507,9 +518,15 @@ if (col.toUpperCase() in reservedColumnNames) col = `$$${col}$$`

const columnsQuery = cds.ql(q).clone()
columnsQuery.SELECT.columns = columns.map(x => {
if (x.element && 'value' in x.element) return { element: x.element, ref: [this.column_name(x)] }
return x
})
const recurseColumns = this.SELECT_columns(columnsQuery)
// Only apply result join if the columns contain a references which doesn't start with the source alias
if (from.args && columns.find(c => c.ref?.[0] === alias)) {
graph.as = alias
return this.from(setStableFrom(from, graph))
return ` ${recurseColumns} FROM ${this.from(setStableFrom(from, graph))}`
}
return `(${this.SELECT(graph)})${alias ? ` AS ${this.quote(alias)}` : ''} `
return ` ${recurseColumns} FROM (${this.SELECT(graph)})${alias ? ` AS ${this.quote(alias)}` : ''} `

@@ -742,2 +759,34 @@ function collectDistanceTo(where, innot = false) {

/**
* Renders a transformed where clause that maps the query target view to the source table
* @param {import('./infer/cqn').source} alias
* @param {import('./infer/cqn').predicate} where
* @param {import('./infer/cqn').query} q
* @returns SQL
*/
where_resolved(alias, where, q) {
const transitions = this.srv.resolve.transitions(q)
if (transitions.target === transitions.queryTarget) return this.where(where)
// view and table column refs to be matched
const viewCols = []
const tableCols = []
// Only match key columns when possible
const elements = q._target.keys || q._target.elements
for (const c in elements) {
if (
c in elements
&& transitions.mapping.has(c)
&& this.physical_column(elements, c)
) {
viewCols.push({ ref: [c] })
tableCols.push(transitions.mapping.get(c))
}
}
return tableCols.length > 0
? this.where([{ list: tableCols }, 'in', SELECT.from(q._target).alias(alias).columns(viewCols).where(where)])
: this.where(where)
}
/**
* Renders a HAVING clause into generic SQL

@@ -847,4 +896,9 @@ * @param {import('./infer/cqn').predicate} xpr

}
const transitions = this.srv.resolve.transitions(q)
const columns = elements
? ObjectKeys(elements).filter(c => c in elements && !elements[c].virtual && !elements[c].value && !elements[c].isAssociation)
? ObjectKeys(elements).filter(c => this.physical_column(elements, c)
&& (c = transitions.mapping.get(c)?.ref?.[0] || c)
&& c in transitions.target.elements
&& this.physical_column(transitions.target.elements, c)
)
: ObjectKeys(INSERT.entries[0])

@@ -856,3 +910,3 @@

const alias = INSERT.into.as
const entity = this.name(q._target?.name || INSERT.into.ref[0], q)
const entity = q._target ? this.table_name(q) : INSERT.into.ref[0]
if (!elements) {

@@ -879,4 +933,4 @@ this.entries = INSERT.entries.map(e => columns.map(c => e[c]))

const extractions = this._managed = this.managed(columns.map(c => ({ name: c })), elements)
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(c))
}) SELECT ${extractions.map(c => c.insert)} FROM json_each(?)`)
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))
}) SELECT ${extractions.slice(0, columns.length).map(c => c.insert)} FROM json_each(?)`)
}

@@ -987,3 +1041,3 @@

const { INSERT } = q
const entity = this.name(q._target?.name || INSERT.into.ref[0], q)
const entity = q._target ? this.table_name(q) : INSERT.into.ref[0]
const alias = INSERT.into.as

@@ -1013,3 +1067,4 @@ const elements = q.elements || q._target?.elements

return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(c))
const transitions = this.srv.resolve.transitions(q)
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))
}) SELECT ${extraction} FROM json_each(?)`)

@@ -1035,10 +1090,14 @@ }

const { INSERT } = q
const entity = this.name(q._target.name, q)
const entity = q._target ? this.table_name(q) : INSERT.into.ref[0]
const alias = INSERT.into.as
const src = this.cqn4sql(INSERT.from)
const elements = q.elements || q._target?.elements || {}
let columns = (this.columns = (INSERT.columns || ObjectKeys(elements)).filter(
c => c in elements && !elements[c].virtual && !elements[c].isAssociation,
))
const transitions = this.srv.resolve.transitions(q)
let columns = (this.columns = (INSERT.columns || src.SELECT.columns?.map(c => this.column_name(c)) || ObjectKeys(src.elements) || ObjectKeys(elements))
.filter(c => this.physical_column(elements, c)
&& (c = transitions.mapping.get(c)?.ref?.[0] || c)
&& c in transitions.target.elements
&& this.physical_column(transitions.target.elements, c)
))
const src = this.cqn4sql(INSERT.from)
const extractions = this._managed = this.managed(columns.map(c => ({ name: c, sql: `NEW.${this.quote(c)}` })), elements)

@@ -1049,3 +1108,3 @@ const sql = extractions.length > columns.length

if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns.map(c => this.quote(c))}) ${sql}`
this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))}) ${sql}`
this.entries = [this.values]

@@ -1104,3 +1163,3 @@ return this.sql

let columns = this.columns // this.columns is computed as part of this.INSERT
const entity = this.name(q._target?.name || UPSERT.into.ref[0], q)
const entity = q._target ? this.table_name(q) : this.name(UPSERT.into.ref[0], q)
if (UPSERT.entries || UPSERT.rows || UPSERT.values) {

@@ -1141,3 +1200,4 @@ const managed = this._managed.slice(0, columns.length)

return (this.sql = `INSERT INTO ${this.quote(entity)} (${columns.map(c => this.quote(c))}) ${sql
const transitions = this.srv.resolve.transitions(q)
return (this.sql = `INSERT INTO ${this.quote(entity)} (${columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))}) ${sql
} WHERE TRUE ON CONFLICT(${keys.map(c => this.quote(c))}) DO ${updateColumns.length ? `UPDATE SET ${updateColumns}` : 'NOTHING'}`)

@@ -1155,15 +1215,16 @@ }

const { entity, with: _with, data, where } = q.UPDATE
const transitions = this.srv.resolve.transitions(q)
const elements = q._target?.elements
let sql = `UPDATE ${this.quote(this.name(entity.ref?.[0] || entity, q))}`
let sql = `UPDATE ${this.quote(this.table_name(q))}`
if (entity.as) sql += ` AS ${this.quote(entity.as)}`
let columns = []
if (data) _add(data, val => this.val({ val }))
if (_with) _add(_with, x => this.expr(x))
function _add(data, sql4) {
for (let c in data) {
const columnExistsInDatabase =
elements && c in elements && !elements[c].virtual && !elements[c].isAssociation && !elements[c].value
const _add = (data, sql4) => {
for (let col in data) {
const c = transitions.mapping.get(col)?.ref?.[0] || col
const columnExistsInDatabase = elements
&& this.physical_column(elements, col)
&& c in transitions.target.elements
&& this.physical_column(transitions.target.elements, c)
if (!elements || columnExistsInDatabase) {
columns.push({ name: c, sql: sql4(data[c]) })
columns.push({ name: c, sql: sql4(data[col], col) })
}

@@ -1173,8 +1234,14 @@ }

let columns = []
if (data) _add(data, val => this.val({ val }))
if (_with) _add(_with, x => this.expr(x))
const extraction = this.managed(columns, elements)
.filter((c, i) => columns[i] || c.onUpdate)
.map((c, i) => `${this.quote(c.name)}=${!columns[i] ? c.onUpdate : c.sql}`)
.filter((c, i) => {
if (transitions.mapping.get(c.name)?.ref?.length > 1) return false
return columns[i] || c.onUpdate
}).map((c, i) => `${this.quote(transitions.mapping.get(c.name)?.ref?.[0] || c.name)}=${!columns[i] ? c.onUpdate : c.sql}`)
sql += ` SET ${extraction}`
if (where) sql += ` WHERE ${this.where(where)}`
if (where) sql += ` WHERE ${this.where_resolved(entity.as, where, q)}`
return (this.sql = sql)

@@ -1191,4 +1258,5 @@ }

DELETE(q) {
const { DELETE: { from, where } } = q
let sql = `DELETE FROM ${this.from(from, q)}`
const { DELETE: { where, from } } = q
let sql = `DELETE FROM ${this.quote(this.table_name(q))}`
if (from.as) sql += ` AS ${this.quote(from.as)}`
if (where) sql += ` WHERE ${this.where(where)}`

@@ -1407,2 +1475,12 @@ return (this.sql = sql)

/**
* Calculates the Database table name of the query target
* @param {import('./infer/cqn').Query} query
* @returns {string} Database table name
*/
table_name(q) {
const table = resolveTable(q._target)
return this.name(table.name, { _target: table })
}
/**
* Calculates the Database name of the given name

@@ -1514,2 +1592,6 @@ * @param {string|import('./infer/cqn').ref} name

physical_column(elements, c) {
return elements[c] && !elements[c].virtual && !elements[c].value && !elements[c].isAssociation
}
managed_extract(name, element, converter) {

@@ -1516,0 +1598,0 @@ const { UPSERT, INSERT } = this.cqn

@@ -7,3 +7,3 @@ 'use strict'

const { pseudos } = require('./pseudos')
const { isCalculatedOnRead, getImplicitAlias, getModelUtils, defineProperty, hasOwnSkip } = require('../utils')
const { isCalculatedOnRead, getImplicitAlias, getModelUtils, defineProperty, hasOwnSkip, isRuntimeView } = require('../utils')
const cdsTypes = cds.builtin.types

@@ -195,2 +195,3 @@ /**

if (col === '*') {
if (wildcardSelect) throw new Error('Duplicate wildcard "*" in column list')
wildcardSelect = true

@@ -464,6 +465,6 @@ } else if (col.val !== undefined || col.xpr || col.SELECT || col.func || col.param) {

const nextStep = arg.ref[1]?.id || arg.ref[1]
if (isNonForeignKeyNavigation(element, nextStep)) {
if (isNonForeignKeyNavigation(element, nextStep) || arg.ref[0]?.where) {
if (inExists) {
defineProperty($baseLink, 'pathExpressionInsideFilter', true)
} else {
} else if (!inFrom) {
rejectNonFkNavigation(element, element.on ? $baseLink.definition.name : nextStep)

@@ -529,6 +530,6 @@ }

const nextStep = arg.ref[i + 1]?.id || arg.ref[i + 1]
if (isNonForeignKeyNavigation(element, nextStep)) {
if (isNonForeignKeyNavigation(element, nextStep) || arg.ref[i-1]?.where) {
if (inExists) {
defineProperty($baseLink, 'pathExpressionInsideFilter', true)
} else {
} else if (!inFrom) {
rejectNonFkNavigation(element, element.on ? $baseLink.definition.name : nextStep)

@@ -572,3 +573,3 @@ }

const definition = arg.$refLinks[i].definition
if ((!definition.target && definition.kind !== 'entity') || (!inFrom && danglingFilter))
if ((!definition.target && definition.kind !== 'entity') || (!inFrom && !inCalcElement && danglingFilter))
throw new Error('A filter can only be provided when navigating along associations')

@@ -582,5 +583,7 @@ if (!inFrom && !arg.expand)defineProperty(arg, 'isJoinRelevant', true)

} else if (token.ref || token.xpr || token.list) {
// For scoped queries (non-dangling filters in FROM), treat filter contents as EXISTS context
// because they will become part of an EXISTS subquery
inferArg(token, false, arg.$refLinks[i], {
...context,
inExists: skipJoinsForFilter || inExists,
inExists: skipJoinsForFilter || inExists || (inFrom && !danglingFilter),
inXpr: !!token.xpr,

@@ -595,3 +598,3 @@ inInfixFilter: true,

arg.$refLinks[i],
{ inExists: skipJoinsForFilter || inExists, inXpr: true, inInfixFilter: true, inFrom },
{ inExists: skipJoinsForFilter || inExists || (inFrom && !danglingFilter), inXpr: true, inInfixFilter: true, inFrom },
])

@@ -605,3 +608,4 @@ }

arg.$refLinks[i].alias = !arg.ref[i + 1] && arg.as ? arg.as : id.split('.').pop()
if (hasOwnSkip(getDefinition(arg.$refLinks[i].definition.target))) isPersisted = false
const def = getDefinition(arg.$refLinks[i].definition.target)
if (hasOwnSkip(def) && !isRuntimeView(def)) isPersisted = false
if (!arg.ref[i + 1]) {

@@ -666,3 +670,7 @@ const flatName = nameSegments.join('_')

const { $refLinks } = arg
const skip = $refLinks.some(link => hasOwnSkip(getDefinition(link.definition.target)))
const skip = $refLinks.some(link => {
const def = getDefinition(link.definition.target)
return hasOwnSkip(def) && !isRuntimeView(def)
})
if (skip) {

@@ -720,8 +728,15 @@ $refLinks[$refLinks.length - 1].skipExpand = true

let elements = {}
let seenWildcard = false
inline.forEach(inlineCol => {
inferArg(inlineCol, null, $leafLink, { inXpr: true, baseColumn: col })
if (inlineCol === '*') {
if (seenWildcard) throw new Error(`Duplicate wildcard "*" in inline of "${col.as || col.ref.map(idOnly).join('_')}"`)
seenWildcard = true
const wildCardElements = {}
// either the `.elements´ of the struct or the `.elements` of the assoc target
const leafLinkElements = getDefinition($leafLink.definition.target)?.elements || $leafLink.definition.elements
const targetDef = getDefinition($leafLink.definition.target)
const leafLinkElements = targetDef?.elements || $leafLink.definition.elements
const isAssociation = !!$leafLink.definition.target
const deferredCalcElements = []
Object.entries(leafLinkElements).forEach(([k, v]) => {

@@ -732,4 +747,38 @@ const name = namePrefix ? `${namePrefix}_${k}` : k

// in excluding, the elements are addressed without the prefix
if (!(name in elements || col.excluding?.includes(k))) wildCardElements[name] = v
if (!(name in elements || col.excluding?.includes(k))) {
wildCardElements[name] = v
if(v.value) {
// defer linkCalculatedElement calls until after all association joins are registered
// so that the join tree order is correct
deferredCalcElements.push({ k, v })
}
else if (isAssociation && !v.virtual && v.type !== 'cds.LargeBinary' && !(v.on && !v.keys)) {
// Check if this element is a foreign key (FK elements don't need join)
const isFK = $leafLink.definition.keys?.some(key => key.ref[0] === k)
if (!isFK) {
// Create a fake column with ref [<inlined assoc>, <element name>] and proper $refLinks
const fakeCol = {
ref: [...col.ref, k],
}
// Copy $refLinks and add new link for the target element with proper alias
const fakeRefLinks = [
...$refLinks,
{ definition: v, target: targetDef, alias: k }
]
defineProperty(fakeCol, '$refLinks', fakeRefLinks)
defineProperty(fakeCol, 'isJoinRelevant', true)
// Merge into join tree
inferred.joinTree.mergeColumn(fakeCol, originalQuery.outerQueries)
}
}
}
})
// link calculated elements after association joins are registered in the join tree
for (const { k, v } of deferredCalcElements) {
linkCalculatedElement(
{ ref: [k], $refLinks: [{ definition: v, target: targetDef }] },
$leafLink,
)
}
elements = { ...elements, ...wildCardElements }

@@ -739,3 +788,3 @@ } else {

if (inlineCol.as) nameParts.push(inlineCol.as)
else nameParts.push(...inlineCol.ref.map(idOnly))
else if (inlineCol.ref) nameParts.push(...inlineCol.ref.map(idOnly))
const name = nameParts.join('_')

@@ -752,2 +801,4 @@ if (inlineCol.inline) {

elements[name] = {}
} else if (inlineCol.xpr) {
elements[name] = {}
} else {

@@ -780,2 +831,12 @@ elements[name] = inlineCol.$refLinks[inlineCol.$refLinks.length - 1].definition

}
// Check for duplicate wildcards before creating the subquery
let seenWildcard = false
for (const e of expand) {
if (e === '*') {
if (seenWildcard) {
throw new Error(`Duplicate wildcard "*" in expand of "${col.as || col.ref.map(idOnly).join('_')}"`)
}
seenWildcard = true
}
}
const target = getDefinition($leafLink.definition.target)

@@ -782,0 +843,0 @@ if (target) {

@@ -136,16 +136,18 @@ 'use strict'

* @param {unknown[]} outerQueries - An array of outer queries.
* @param {string} key - The key to be used for storing the alias in the map. If not provided, the upper-case version of the alias will be used as the key.
* @returns {string} - The next unambiguous table alias.
*/
addNextAvailableTableAlias(alias, outerQueries) {
addNextAvailableTableAlias(alias, outerQueries, key) {
const upperAlias = alias.toUpperCase()
if (this._queryAliases.get(upperAlias) || outerQueries?.some(outer => outerHasAlias(outer))) {
if (this._queryAliases.get(upperAlias) || outerQueries?.some(outer => outerHasAlias(outer, key))) {
let j = 2
while (this._queryAliases.get(upperAlias + j) || outerQueries?.some(outer => outerHasAlias(outer, j))) j += 1
while (this._queryAliases.get(upperAlias + j) || outerQueries?.some(outer => outerHasAlias(outer, key, j))) j += 1
alias += j
}
this._queryAliases.set(alias.toUpperCase(), alias)
this._queryAliases.set(key || alias.toUpperCase(), alias)
return alias
function outerHasAlias(outer, number) {
return outer.joinTree._queryAliases.get(number ? upperAlias + number : upperAlias)
function outerHasAlias(outer, searchInValues = false, number) {
const currAlias = number ? upperAlias + number : upperAlias
return searchInValues ? Array.from(outer.joinTree._queryAliases.values()).includes(currAlias) : outer.joinTree._queryAliases.get(currAlias)
}

@@ -152,0 +154,0 @@ }

@@ -5,5 +5,5 @@ const cds = require('@sap/cds'),

const { pipeline } = require('stream/promises')
const { resolveView, getDBTable, getTransition } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
const DatabaseService = require('./common/DatabaseService')
const cqn4sql = require('./cqn4sql')
const { resolveTable } = require('./utils')

@@ -172,3 +172,3 @@ const BINARY_TYPES = {

return iterator !== false && isOne ? rows[0] : rows
return !iterator && isOne ? rows[0] : rows
} catch (err) {

@@ -235,3 +235,4 @@ // Ensure that iterators receive pre stream errors

async function deep_delete(/** @type {Request} */ req) {
const transitions = getTransition(req.target, this, false, req.query.cmd || 'DELETE')
const resolve = this.resolve
const transitions = resolve.transitions(req.query)
if (transitions.target !== transitions.queryTarget) {

@@ -259,3 +260,3 @@ const keys = []

}
const table = getDBTable(req.target)
const table = resolveTable(req.target)
const { compositions } = table

@@ -411,6 +412,2 @@ if (compositions) {

let q = this.cqn4sql(query)
let kind = q.kind || Object.keys(q)[0]
if (kind in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 }) {
q = resolveView(q, this.model, this) // REVISIT: before resolveView was called on flat cqn obtained from cqn4sql -> is it correct to call on original q instead?
}
let cqn2sql = new this.class.CQN2SQL(this)

@@ -417,0 +414,0 @@ return cqn2sql.render(q, values)

'use strict'
const cds = require('@sap/cds')
/**

@@ -30,2 +32,21 @@ * Formats a ref array into a string representation.

function isRuntimeView(definition) {
if (!definition || !cds.env.features.runtime_views) return false
if (definition['_isRuntimeView']) return true
if (!definition['@cds.persistence.skip']) {
Object.defineProperty(definition, '_isRuntimeView', {
value: true,
writable: false,
configurable: true,
enumerable: false
})
return true
}
// views with "as select from" variant are also runtime views, even if they are annotated with persistence skip
if (definition.query && !definition.query._target) return true
if (definition.query) return isRuntimeView(definition.query._target)
return false
}
/**

@@ -140,2 +161,8 @@ * Determines if a definition is calculated on read.

function resolveTable(target) {
if (target.query?._target && !Object.prototype.hasOwnProperty.call(target, '@cds.persistence.table'))
return resolveTable(target.query._target)
return target
}
// export the function to be used in other modules

@@ -150,2 +177,4 @@ module.exports = {

hasOwnSkip,
isRuntimeView,
resolveTable
}
{
"name": "@cap-js/db-service",
"version": "2.8.2",
"version": "2.9.0",
"description": "CDS base database service",

@@ -30,5 +30,5 @@ "homepage": "https://github.com/cap-js/cds-dbs/tree/main/db-service#cds-base-database-service",

"peerDependencies": {
"@sap/cds": ">=9.4.5"
"@sap/cds": ">=9.8"
},
"license": "Apache-2.0"
}

Sorry, the diff of this file is too big to display