@cap-js/db-service 1.6.3 → 1.6.4
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/SQLService.js +3 -0
- package/lib/cqn2sql.js +7 -7
- package/lib/cqn4sql.js +47 -26
- package/lib/infer/join-tree.js +2 -1
- package/package.json +1 -1
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.6.4](https://github.com/cap-js/cds-dbs/compare/db-service-v1.6.3...db-service-v1.6.4) (2024-02-28)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* **`cqn2sql`:** smart quoting also for update statements ([#475](https://github.com/cap-js/cds-dbs/issues/475)) ([1688f77](https://github.com/cap-js/cds-dbs/commit/1688f77158c2df37673e969074f1b6d210267336))
|
|
13
|
+
* `INSERT` with first `undefined` value ([#484](https://github.com/cap-js/cds-dbs/issues/484)) ([c21e3c4](https://github.com/cap-js/cds-dbs/commit/c21e3c44140c44ff6378d1fdac32869d9c1c988c))
|
|
14
|
+
* Allow SELECT.join queries again with full infer call ([#469](https://github.com/cap-js/cds-dbs/issues/469)) ([5329ec0](https://github.com/cap-js/cds-dbs/commit/5329ec0a25036a1e42513e8bb9347b0ff8c7aa2d))
|
|
15
|
+
* optimize foreign key access in a join relevant path ([#481](https://github.com/cap-js/cds-dbs/issues/481)) ([5e30de4](https://github.com/cap-js/cds-dbs/commit/5e30de439b62167c4b6d487c4d5cda4f2f0a806d)), closes [#479](https://github.com/cap-js/cds-dbs/issues/479)
|
|
16
|
+
|
|
7
17
|
## [1.6.3](https://github.com/cap-js/cds-dbs/compare/db-service-v1.6.2...db-service-v1.6.3) (2024-02-20)
|
|
8
18
|
|
|
9
19
|
|
package/lib/SQLService.js
CHANGED
|
@@ -114,6 +114,9 @@ class SQLService extends DatabaseService {
|
|
|
114
114
|
* @type {Handler}
|
|
115
115
|
*/
|
|
116
116
|
async onSELECT({ query, data }) {
|
|
117
|
+
if (!query.target) {
|
|
118
|
+
try { this.infer(query) } catch (e) { /**/ }
|
|
119
|
+
}
|
|
117
120
|
if (query.target && !query.target._unresolved) {
|
|
118
121
|
// Will return multiple rows with objects inside
|
|
119
122
|
query.SELECT.expand = 'root'
|
package/lib/cqn2sql.js
CHANGED
|
@@ -113,8 +113,8 @@ class CQN2SQLRenderer {
|
|
|
113
113
|
delete this.values
|
|
114
114
|
this.sql =
|
|
115
115
|
!query || target['@cds.persistence.table']
|
|
116
|
-
? `CREATE TABLE ${name} ( ${this.CREATE_elements(target.elements)} )`
|
|
117
|
-
: `CREATE VIEW ${name} AS ${this.SELECT(this.cqn4sql(query))}`
|
|
116
|
+
? `CREATE TABLE ${this.quote(name)} ( ${this.CREATE_elements(target.elements)} )`
|
|
117
|
+
: `CREATE VIEW ${this.quote(name)} AS ${this.SELECT(this.cqn4sql(query))}`
|
|
118
118
|
this.values = []
|
|
119
119
|
return
|
|
120
120
|
}
|
|
@@ -465,10 +465,11 @@ class CQN2SQLRenderer {
|
|
|
465
465
|
|
|
466
466
|
let sepsub = ''
|
|
467
467
|
for (const key in row) {
|
|
468
|
+
let val = row[key]
|
|
469
|
+
if (val === undefined) continue
|
|
468
470
|
const keyJSON = `${sepsub}${JSON.stringify(key)}:`
|
|
469
471
|
if (!sepsub) sepsub = ','
|
|
470
472
|
|
|
471
|
-
let val = row[key]
|
|
472
473
|
if (val instanceof Readable) {
|
|
473
474
|
buffer += `${keyJSON}"`
|
|
474
475
|
|
|
@@ -484,7 +485,6 @@ class CQN2SQLRenderer {
|
|
|
484
485
|
|
|
485
486
|
buffer += '"'
|
|
486
487
|
} else {
|
|
487
|
-
if (val === undefined) continue
|
|
488
488
|
if (elements[key]?.type in BINARY_TYPES) {
|
|
489
489
|
val = transformBase64(val)
|
|
490
490
|
}
|
|
@@ -617,7 +617,7 @@ class CQN2SQLRenderer {
|
|
|
617
617
|
const columns = (this.columns = (INSERT.columns || ObjectKeys(elements)).filter(
|
|
618
618
|
c => c in elements && !elements[c].virtual && !elements[c].isAssociation,
|
|
619
619
|
))
|
|
620
|
-
this.sql = `INSERT INTO ${entity}${alias ? ' as ' + this.quote(alias) : ''} (${columns}) ${this.SELECT(
|
|
620
|
+
this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns}) ${this.SELECT(
|
|
621
621
|
this.cqn4sql(INSERT.as),
|
|
622
622
|
)}`
|
|
623
623
|
this.entries = [this.values]
|
|
@@ -689,8 +689,8 @@ class CQN2SQLRenderer {
|
|
|
689
689
|
UPDATE(q) {
|
|
690
690
|
const { entity, with: _with, data, where } = q.UPDATE
|
|
691
691
|
const elements = q.target?.elements
|
|
692
|
-
let sql = `UPDATE ${this.name(entity.ref?.[0] || entity)}`
|
|
693
|
-
if (entity.as) sql += ` AS ${entity.as}`
|
|
692
|
+
let sql = `UPDATE ${this.quote(this.name(entity.ref?.[0] || entity))}`
|
|
693
|
+
if (entity.as) sql += ` AS ${this.quote(entity.as)}`
|
|
694
694
|
|
|
695
695
|
let columns = []
|
|
696
696
|
if (data) _add(data, val => this.val({ val }))
|
package/lib/cqn4sql.js
CHANGED
|
@@ -838,9 +838,10 @@ function cqn4sql(originalQuery, model) {
|
|
|
838
838
|
const calcElement = resolveCalculatedElement(col, true)
|
|
839
839
|
res.push(calcElement)
|
|
840
840
|
} else if (col.isJoinRelevant) {
|
|
841
|
-
const tableAlias
|
|
841
|
+
const tableAlias = getQuerySourceName(col)
|
|
842
|
+
const name = calculateElementName(col)
|
|
842
843
|
const transformedColumn = {
|
|
843
|
-
ref: [tableAlias
|
|
844
|
+
ref: [tableAlias, name],
|
|
844
845
|
}
|
|
845
846
|
if (col.sort) transformedColumn.sort = col.sort
|
|
846
847
|
if (col.nulls) transformedColumn.nulls = col.nulls
|
|
@@ -1046,10 +1047,10 @@ function cqn4sql(originalQuery, model) {
|
|
|
1046
1047
|
leafAssoc = [...column.$refLinks].reverse().find(link => link.definition.isAssociation)
|
|
1047
1048
|
let elements
|
|
1048
1049
|
elements = leafAssoc.definition.elements || leafAssoc.definition.foreignKeys
|
|
1049
|
-
if (elements && leaf.
|
|
1050
|
+
if (elements && leaf.definition.name in elements) {
|
|
1050
1051
|
element = leafAssoc.definition
|
|
1051
1052
|
baseName = getFullName(leafAssoc.definition)
|
|
1052
|
-
columnAlias = column.ref.slice(0, -1).map(idOnly).join('_')
|
|
1053
|
+
columnAlias = column.as || column.ref.slice(0, -1).map(idOnly).join('_')
|
|
1053
1054
|
} else baseName = getFullName(column.$refLinks[column.$refLinks.length - 1].definition)
|
|
1054
1055
|
} else if (!baseName && structsAreUnfoldedAlready) {
|
|
1055
1056
|
baseName = element.name // name is already fully constructed
|
|
@@ -1127,8 +1128,16 @@ function cqn4sql(originalQuery, model) {
|
|
|
1127
1128
|
} else {
|
|
1128
1129
|
// leaf reached
|
|
1129
1130
|
let flatColumn
|
|
1130
|
-
if (columnAlias)
|
|
1131
|
-
|
|
1131
|
+
if (columnAlias) {
|
|
1132
|
+
// if the column has an explicit alias AND the orignal ref
|
|
1133
|
+
// directly resolves to the foreign key, we must not append the fk name to the column alias
|
|
1134
|
+
// e.g. `assoc.fk as FOO` => columns.alias = FOO
|
|
1135
|
+
// `assoc as FOO` => columns.alias = FOO_fk
|
|
1136
|
+
let columnAliasWithFlatFk
|
|
1137
|
+
if (!(column.as && fkElement === column.$refLinks?.at(-1).definition))
|
|
1138
|
+
columnAliasWithFlatFk = `${columnAlias}_${fk.as || fk.ref.join('_')}`
|
|
1139
|
+
flatColumn = { ref: [fkBaseName], as: columnAliasWithFlatFk || columnAlias }
|
|
1140
|
+
} else flatColumn = { ref: [fkBaseName] }
|
|
1132
1141
|
if (tableAlias) flatColumn.ref.unshift(tableAlias)
|
|
1133
1142
|
|
|
1134
1143
|
// in a flat model, we must assign the foreign key rather than the key in the target
|
|
@@ -1353,11 +1362,12 @@ function cqn4sql(originalQuery, model) {
|
|
|
1353
1362
|
// in that case, we have a baseLink `books` which we need to resolve the following steps
|
|
1354
1363
|
// however, the correct table alias has been assigned to the `author` step
|
|
1355
1364
|
// hence we need to ignore the alias of the `$baseLink`
|
|
1356
|
-
const
|
|
1365
|
+
const lastAssoc =
|
|
1357
1366
|
token.isJoinRelevant && [...token.$refLinks].reverse().find(l => l.definition.isAssociation)
|
|
1358
|
-
const tableAlias = getQuerySourceName(token,
|
|
1359
|
-
if ((!$baseLink ||
|
|
1360
|
-
|
|
1367
|
+
const tableAlias = getQuerySourceName(token, (!lastAssoc?.onlyForeignKeyAccess && lastAssoc) || $baseLink)
|
|
1368
|
+
if ((!$baseLink || lastAssoc) && token.isJoinRelevant) {
|
|
1369
|
+
let name = calculateElementName(token, getFullName)
|
|
1370
|
+
result.ref = [tableAlias, name]
|
|
1361
1371
|
} else if (tableAlias) {
|
|
1362
1372
|
result.ref = [tableAlias, token.flatName]
|
|
1363
1373
|
} else {
|
|
@@ -1814,7 +1824,8 @@ function cqn4sql(originalQuery, model) {
|
|
|
1814
1824
|
result.splice(i, 3, ...(wrapInXpr ? [asXpr(backlinkOnCondition)] : backlinkOnCondition))
|
|
1815
1825
|
i += wrapInXpr ? 1 : backlinkOnCondition.length // skip inserted tokens
|
|
1816
1826
|
} else if (lhs.ref) {
|
|
1817
|
-
if (lhs.ref[0] === '$self') {
|
|
1827
|
+
if (lhs.ref[0] === '$self') {
|
|
1828
|
+
// $self in ref of length > 1
|
|
1818
1829
|
// if $self is followed by association, the alias of the association must be used
|
|
1819
1830
|
if (lhs.$refLinks[1].definition.isAssociation) result[i].ref.splice(0, 1)
|
|
1820
1831
|
// otherwise $self is replaced by the alias of the entity
|
|
@@ -1997,21 +2008,6 @@ function cqn4sql(originalQuery, model) {
|
|
|
1997
2008
|
return model.definitions[assoc.target] || null
|
|
1998
2009
|
}
|
|
1999
2010
|
|
|
2000
|
-
/**
|
|
2001
|
-
* Calculate the flat name for a deeply nested element:
|
|
2002
|
-
* @example `entity E { struct: { foo: String} }` => `getFullName(foo)` => `struct_foo`
|
|
2003
|
-
*
|
|
2004
|
-
* @param {CSN.element} node an element
|
|
2005
|
-
* @param {object} name the last part of the name, e.g. the name of the deeply nested element
|
|
2006
|
-
* @returns the flat name of the element
|
|
2007
|
-
*/
|
|
2008
|
-
function getFullName(node, name = node.name) {
|
|
2009
|
-
// REVISIT: this is an unfortunate implementation
|
|
2010
|
-
if (!node.parent || node.parent.kind === 'entity') return name
|
|
2011
|
-
|
|
2012
|
-
return getFullName(node.parent, `${node.parent.name}_${name}`)
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
2011
|
/**
|
|
2016
2012
|
* Calculates the name of the source which can be used to address the given node.
|
|
2017
2013
|
*
|
|
@@ -2080,6 +2076,31 @@ module.exports = Object.assign(cqn4sql, {
|
|
|
2080
2076
|
notSupportedOps,
|
|
2081
2077
|
})
|
|
2082
2078
|
|
|
2079
|
+
function calculateElementName(token) {
|
|
2080
|
+
const nonJoinRelevantAssoc = [...token.$refLinks].findIndex(l => l.definition.isAssociation && l.onlyForeignKeyAccess)
|
|
2081
|
+
let name
|
|
2082
|
+
if (nonJoinRelevantAssoc)
|
|
2083
|
+
// calculate fk name
|
|
2084
|
+
name = token.ref.slice(nonJoinRelevantAssoc).join('_')
|
|
2085
|
+
else name = token.$refLinks[token.$refLinks.length - 1].definition.name
|
|
2086
|
+
return name
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
/**
|
|
2090
|
+
* Calculate the flat name for a deeply nested element:
|
|
2091
|
+
* @example `entity E { struct: { foo: String} }` => `getFullName(foo)` => `struct_foo`
|
|
2092
|
+
*
|
|
2093
|
+
* @param {CSN.element} node an element
|
|
2094
|
+
* @param {object} name the last part of the name, e.g. the name of the deeply nested element
|
|
2095
|
+
* @returns the flat name of the element
|
|
2096
|
+
*/
|
|
2097
|
+
function getFullName(node, name = node.name) {
|
|
2098
|
+
// REVISIT: this is an unfortunate implementation
|
|
2099
|
+
if (!node.parent || node.parent.kind === 'entity') return name
|
|
2100
|
+
|
|
2101
|
+
return getFullName(node.parent, `${node.parent.name}_${name}`)
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2083
2104
|
function copy(obj) {
|
|
2084
2105
|
const walk = function (par, prop) {
|
|
2085
2106
|
const val = prop ? par[prop] : par
|
package/lib/infer/join-tree.js
CHANGED
|
@@ -181,6 +181,7 @@ class JoinTree {
|
|
|
181
181
|
col.$refLinks[i].alias = node.$refLink.alias
|
|
182
182
|
col.$refLinks[i].definition = node.$refLink.definition
|
|
183
183
|
col.$refLinks[i].target = node.$refLink.target
|
|
184
|
+
col.$refLinks[i].onlyForeignKeyAccess = node.$refLink.onlyForeignKeyAccess
|
|
184
185
|
} else {
|
|
185
186
|
if (col.expand && !col.ref[i + 1]) {
|
|
186
187
|
node.$refLink.onlyForeignKeyAccess = false
|
|
@@ -201,7 +202,7 @@ class JoinTree {
|
|
|
201
202
|
const elements =
|
|
202
203
|
node.$refLink?.definition.isAssociation &&
|
|
203
204
|
(node.$refLink.definition.elements || node.$refLink.definition.foreignKeys)
|
|
204
|
-
if (node.$refLink && (!elements || !(child.$refLink.
|
|
205
|
+
if (node.$refLink && (!elements || !(child.$refLink.definition.name in elements)))
|
|
205
206
|
// foreign key access
|
|
206
207
|
node.$refLink.onlyForeignKeyAccess = false
|
|
207
208
|
|
package/package.json
CHANGED