@cap-js/db-service 1.4.0 → 1.5.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 +26 -0
- package/lib/SQLService.js +13 -13
- package/lib/common/DatabaseService.js +14 -1
- package/lib/cqn2sql.js +53 -55
- package/lib/cqn4sql.js +2 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@
|
|
|
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.5.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.5.0...db-service-v1.5.1) (2023-12-20)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* **cqn2sql:** supporting calculated elements ([#387](https://github.com/cap-js/cds-dbs/issues/387)) ([2153fb9](https://github.com/cap-js/cds-dbs/commit/2153fb9a3910cd4afa3a91918e6cf682646492b7))
|
|
13
|
+
* do not rely on db constraints for deep delete ([#390](https://github.com/cap-js/cds-dbs/issues/390)) ([9623af6](https://github.com/cap-js/cds-dbs/commit/9623af64db97cfe15ef07b659635850fc908f77c))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Performance Improvements
|
|
17
|
+
|
|
18
|
+
* HANA list placeholder ([#380](https://github.com/cap-js/cds-dbs/issues/380)) ([3eadfea](https://github.com/cap-js/cds-dbs/commit/3eadfea7b94f485030cc8bd0bd298ce088586422))
|
|
19
|
+
|
|
20
|
+
## [1.5.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.4.0...db-service-v1.5.0) (2023-12-06)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
* Improved connection pool for HANAService ([#349](https://github.com/cap-js/cds-dbs/issues/349)) ([1c284e6](https://github.com/cap-js/cds-dbs/commit/1c284e69cccd76daad52249c0462bc62aa4d11a8))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
* **localized:** `ref`s in subqueries in `from` are translated ([#366](https://github.com/cap-js/cds-dbs/issues/366)) ([cfe4897](https://github.com/cap-js/cds-dbs/commit/cfe489715db0854d30b90b7f13c024e6e90be497))
|
|
31
|
+
* wrong odata count in filter with groupby ([#352](https://github.com/cap-js/cds-dbs/issues/352)) ([70690a1](https://github.com/cap-js/cds-dbs/commit/70690a1a13e72bfbb66f03bf315d3f2d48672bf6))
|
|
32
|
+
|
|
7
33
|
## [1.4.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.3.2...db-service-v1.4.0) (2023-11-20)
|
|
8
34
|
|
|
9
35
|
|
package/lib/SQLService.js
CHANGED
|
@@ -153,7 +153,8 @@ class SQLService extends DatabaseService {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
get onDELETE() {
|
|
156
|
-
|
|
156
|
+
// REVISIT: It's not yet 100 % clear under which circumstances we can rely on db constraints
|
|
157
|
+
return super.onDELETE = /* cds.env.features.assert_integrity === 'db' ? this.onSIMPLE : */ deep_delete
|
|
157
158
|
async function deep_delete(/** @type {Request} */ req) {
|
|
158
159
|
let { compositions } = req.target
|
|
159
160
|
if (compositions) {
|
|
@@ -230,16 +231,15 @@ class SQLService extends DatabaseService {
|
|
|
230
231
|
if (max === undefined || (n < max && (n || !offset))) return n + offset
|
|
231
232
|
}
|
|
232
233
|
// REVISIT: made uppercase count because of HANA reserved word quoting
|
|
233
|
-
const cq =
|
|
234
|
-
|
|
234
|
+
const cq = SELECT.one([{ func: 'count', as: 'COUNT' }]).from(
|
|
235
|
+
cds.ql.clone(query, {
|
|
235
236
|
localized: false,
|
|
236
237
|
expand: false,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
const { count, COUNT } = await ps.get(values)
|
|
238
|
+
limit: undefined,
|
|
239
|
+
orderBy: undefined,
|
|
240
|
+
}),
|
|
241
|
+
)
|
|
242
|
+
const { count, COUNT } = await this.onSELECT({ query: cq })
|
|
243
243
|
return count ?? COUNT
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -280,12 +280,12 @@ class SQLService extends DatabaseService {
|
|
|
280
280
|
*/
|
|
281
281
|
cqn2sql(query, values) {
|
|
282
282
|
let q = this.cqn4sql(query)
|
|
283
|
-
if (q.SELECT &&
|
|
283
|
+
if (q.SELECT && 'elements' in q) q.SELECT.expand ??= 'root'
|
|
284
284
|
|
|
285
|
-
let
|
|
286
|
-
if (
|
|
285
|
+
let kind = q.kind || Object.keys(q)[0]
|
|
286
|
+
if (kind in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 } || q.STREAM?.into) {
|
|
287
287
|
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?
|
|
288
|
-
let target = q[
|
|
288
|
+
let target = q[kind]._transitions?.[0].target
|
|
289
289
|
if (target) q.target = target // REVISIT: Why isn't that done in resolveView?
|
|
290
290
|
}
|
|
291
291
|
let cqn2sql = new this.class.CQN2SQL(this)
|
|
@@ -63,6 +63,7 @@ class DatabaseService extends cds.Service {
|
|
|
63
63
|
|
|
64
64
|
// Acquire a pooled connection
|
|
65
65
|
this.dbc = await this.acquire()
|
|
66
|
+
this.dbc.destroy = this.destroy.bind(this)
|
|
66
67
|
|
|
67
68
|
// Begin a session...
|
|
68
69
|
try {
|
|
@@ -110,8 +111,20 @@ class DatabaseService extends cds.Service {
|
|
|
110
111
|
*/
|
|
111
112
|
async release() {
|
|
112
113
|
if (!this.dbc) return
|
|
113
|
-
|
|
114
|
+
const dbc = this.dbc
|
|
114
115
|
this.dbc = undefined
|
|
116
|
+
await this.pool.release(dbc)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Destroys own connection, i.e. tix.dbc, from this.pool
|
|
121
|
+
* This is for subclasses to intercept, if required.
|
|
122
|
+
*/
|
|
123
|
+
async destroy() {
|
|
124
|
+
if (!this.dbc) return
|
|
125
|
+
const dbc = this.dbc
|
|
126
|
+
this.dbc = undefined
|
|
127
|
+
await this.pool.destroy(dbc)
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
// REVISIT: should happen automatically after a configurable time
|
package/lib/cqn2sql.js
CHANGED
|
@@ -27,7 +27,7 @@ class CQN2SQLRenderer {
|
|
|
27
27
|
this.class._init() // is a noop for subsequent calls
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
static _add_mixins
|
|
30
|
+
static _add_mixins(aspect, mixins) {
|
|
31
31
|
const fqn = this.name + aspect
|
|
32
32
|
const types = cds.builtin.types
|
|
33
33
|
for (let each in mixins) {
|
|
@@ -52,7 +52,7 @@ class CQN2SQLRenderer {
|
|
|
52
52
|
this.ReservedWords[each[0] + each.slice(1).toLowerCase()] = 1 // Order
|
|
53
53
|
this.ReservedWords[each.toLowerCase()] = 1 // order
|
|
54
54
|
}
|
|
55
|
-
this._init = () => {} // makes this a noop for subsequent calls
|
|
55
|
+
this._init = () => { } // makes this a noop for subsequent calls
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -62,14 +62,14 @@ class CQN2SQLRenderer {
|
|
|
62
62
|
* @returns {CQN2SQLRenderer|unknown}
|
|
63
63
|
*/
|
|
64
64
|
render(q, vars) {
|
|
65
|
-
const
|
|
65
|
+
const kind = q.kind || Object.keys(q)[0] // SELECT, INSERT, ...
|
|
66
66
|
/**
|
|
67
67
|
* @type {string} the rendered SQL string
|
|
68
68
|
*/
|
|
69
69
|
this.sql = '' // to have it as first property for debugging
|
|
70
70
|
/** @type {unknown[]} */
|
|
71
71
|
this.values = [] // prepare values, filled in by subroutines
|
|
72
|
-
this[
|
|
72
|
+
this[kind]((this.cqn = q)) // actual sql rendering happens here
|
|
73
73
|
if (vars?.length && !this.values?.length) this.values = vars
|
|
74
74
|
const sanitize_values = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
|
|
75
75
|
DEBUG?.(
|
|
@@ -208,9 +208,8 @@ class CQN2SQLRenderer {
|
|
|
208
208
|
if (limit) sql += ` LIMIT ${this.limit(limit)}`
|
|
209
209
|
// Expand cannot work without an inferred query
|
|
210
210
|
if (expand) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
sql = this.SELECT_expand(q, sql)
|
|
211
|
+
if ('elements' in q) sql = this.SELECT_expand(q, sql)
|
|
212
|
+
else cds.error`Query was not inferred and includes expand. For which the metadata is missing.`
|
|
214
213
|
}
|
|
215
214
|
return (this.sql = sql)
|
|
216
215
|
}
|
|
@@ -230,10 +229,12 @@ class CQN2SQLRenderer {
|
|
|
230
229
|
* @param {string} sql
|
|
231
230
|
* @returns {string} SQL
|
|
232
231
|
*/
|
|
233
|
-
SELECT_expand(
|
|
232
|
+
SELECT_expand(q, sql) {
|
|
233
|
+
if (!('elements' in q)) return sql
|
|
234
|
+
|
|
235
|
+
const SELECT = q.SELECT
|
|
234
236
|
if (!SELECT.columns) return sql
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
|
|
237
238
|
let cols = SELECT.columns.map(x => {
|
|
238
239
|
const name = this.column_name(x)
|
|
239
240
|
let col = `'${name}',${this.output_converter4(x.element, this.quote(name))}`
|
|
@@ -248,7 +249,7 @@ class CQN2SQLRenderer {
|
|
|
248
249
|
// Prevent SQLite from hitting function argument limit of 100
|
|
249
250
|
let obj = ''
|
|
250
251
|
|
|
251
|
-
if(cols.length < 50) obj =
|
|
252
|
+
if (cols.length < 50) obj = `json_object(${cols.slice(0, 50)})`
|
|
252
253
|
else {
|
|
253
254
|
const chunks = []
|
|
254
255
|
for (let i = 0; i < cols.length; i += 50) {
|
|
@@ -256,7 +257,7 @@ class CQN2SQLRenderer {
|
|
|
256
257
|
}
|
|
257
258
|
// REVISIT: json_merge is a user defined function, bad performance!
|
|
258
259
|
obj = `json_merge(${chunks})`
|
|
259
|
-
}
|
|
260
|
+
}
|
|
260
261
|
|
|
261
262
|
|
|
262
263
|
return `SELECT ${SELECT.one || SELECT.expand === 'root' ? obj : `json_group_array(${obj.includes('json_merge') ? `json_insert(${obj})` : obj})`} as _json_ FROM (${sql})`
|
|
@@ -341,9 +342,9 @@ class CQN2SQLRenderer {
|
|
|
341
342
|
return orderBy.map(
|
|
342
343
|
localized
|
|
343
344
|
? c =>
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
345
|
+
this.expr(c) +
|
|
346
|
+
(c.element?.[this.class._localized] ? ' COLLATE NOCASE' : '') +
|
|
347
|
+
(c.sort === 'desc' || c.sort === -1 ? ' DESC' : ' ASC')
|
|
347
348
|
: c => this.expr(c) + (c.sort === 'desc' || c.sort === -1 ? ' DESC' : ' ASC'),
|
|
348
349
|
)
|
|
349
350
|
}
|
|
@@ -371,12 +372,12 @@ class CQN2SQLRenderer {
|
|
|
371
372
|
return INSERT.entries
|
|
372
373
|
? this.INSERT_entries(q)
|
|
373
374
|
: INSERT.rows
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
375
|
+
? this.INSERT_rows(q)
|
|
376
|
+
: INSERT.values
|
|
377
|
+
? this.INSERT_values(q)
|
|
378
|
+
: INSERT.as
|
|
379
|
+
? this.INSERT_select(q)
|
|
380
|
+
: cds.error`Missing .entries, .rows, or .values in ${q}`
|
|
380
381
|
}
|
|
381
382
|
|
|
382
383
|
/**
|
|
@@ -393,7 +394,7 @@ class CQN2SQLRenderer {
|
|
|
393
394
|
return // REVISIT: mtx sends an insert statement without entries and no reference entity
|
|
394
395
|
}
|
|
395
396
|
const columns = elements
|
|
396
|
-
? ObjectKeys(elements).filter(c => c in elements && !elements[c].virtual && !elements[c].isAssociation)
|
|
397
|
+
? ObjectKeys(elements).filter(c => c in elements && !elements[c].virtual && !elements[c].value && !elements[c].isAssociation)
|
|
397
398
|
: ObjectKeys(INSERT.entries[0])
|
|
398
399
|
|
|
399
400
|
/** @type {string[]} */
|
|
@@ -426,9 +427,8 @@ class CQN2SQLRenderer {
|
|
|
426
427
|
// Include this.values for placeholders
|
|
427
428
|
/** @type {unknown[][]} */
|
|
428
429
|
this.entries = [[...this.values, JSON.stringify(INSERT.entries)]]
|
|
429
|
-
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${
|
|
430
|
-
|
|
431
|
-
}) SELECT ${extraction} FROM json_each(?)`)
|
|
430
|
+
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns
|
|
431
|
+
}) SELECT ${extraction} FROM json_each(?)`)
|
|
432
432
|
}
|
|
433
433
|
|
|
434
434
|
/**
|
|
@@ -442,21 +442,20 @@ class CQN2SQLRenderer {
|
|
|
442
442
|
const alias = INSERT.into.as
|
|
443
443
|
const elements = q.elements || q.target?.elements
|
|
444
444
|
const columns = INSERT.columns
|
|
445
|
-
|
|
445
|
+
|| cds.error`Cannot insert rows without columns or elements`
|
|
446
446
|
|
|
447
447
|
const inputConverter = this.class._convertInput
|
|
448
|
-
const extraction = columns.map((c,i) => {
|
|
448
|
+
const extraction = columns.map((c, i) => {
|
|
449
449
|
const extract = `value->>'$[${i}]'`
|
|
450
450
|
const element = elements?.[c]
|
|
451
451
|
const converter = element?.[inputConverter]
|
|
452
|
-
return converter?.(extract,element) || extract
|
|
452
|
+
return converter?.(extract, element) || extract
|
|
453
453
|
})
|
|
454
454
|
|
|
455
455
|
this.columns = columns.map(c => this.quote(c))
|
|
456
456
|
this.entries = [[JSON.stringify(INSERT.rows)]]
|
|
457
|
-
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${
|
|
458
|
-
|
|
459
|
-
}) SELECT ${extraction} FROM json_each(?)`)
|
|
457
|
+
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns
|
|
458
|
+
}) SELECT ${extraction} FROM json_each(?)`)
|
|
460
459
|
}
|
|
461
460
|
|
|
462
461
|
/**
|
|
@@ -551,9 +550,9 @@ class CQN2SQLRenderer {
|
|
|
551
550
|
if (entity.as) sql += ` AS ${entity.as}`
|
|
552
551
|
|
|
553
552
|
let columns = []
|
|
554
|
-
if (data) _add
|
|
555
|
-
if (_with) _add
|
|
556
|
-
function _add
|
|
553
|
+
if (data) _add(data, val => this.val({ val }))
|
|
554
|
+
if (_with) _add(_with, x => this.expr(x))
|
|
555
|
+
function _add(data, sql4) {
|
|
557
556
|
for (let c in data) {
|
|
558
557
|
if (!elements || (c in elements && !elements[c].virtual)) {
|
|
559
558
|
columns.push({ name: c, sql: sql4(data[c]) })
|
|
@@ -601,8 +600,8 @@ class CQN2SQLRenderer {
|
|
|
601
600
|
return STREAM.from
|
|
602
601
|
? this.STREAM_from(q)
|
|
603
602
|
: STREAM.into
|
|
604
|
-
|
|
605
|
-
|
|
603
|
+
? this.STREAM_into(q)
|
|
604
|
+
: cds.error`Missing .form or .into in ${q}`
|
|
606
605
|
}
|
|
607
606
|
|
|
608
607
|
/**
|
|
@@ -667,7 +666,7 @@ class CQN2SQLRenderer {
|
|
|
667
666
|
expr(x) {
|
|
668
667
|
const wrap = x.cast ? sql => `cast(${sql} as ${this.type4(x.cast)})` : sql => sql
|
|
669
668
|
if (typeof x === 'string') throw cds.error`Unsupported expr: ${x}`
|
|
670
|
-
if (
|
|
669
|
+
if (x.param) return wrap(this.param(x))
|
|
671
670
|
if ('ref' in x) return wrap(this.ref(x))
|
|
672
671
|
if ('val' in x) return wrap(this.val(x))
|
|
673
672
|
if ('xpr' in x) return wrap(this.xpr(x))
|
|
@@ -703,15 +702,15 @@ class CQN2SQLRenderer {
|
|
|
703
702
|
operator(x, i, xpr) {
|
|
704
703
|
|
|
705
704
|
// Translate = to IS NULL for rhs operand being NULL literal
|
|
706
|
-
if (x === '=')
|
|
705
|
+
if (x === '=') return xpr[i + 1]?.val === null ? 'is' : '='
|
|
707
706
|
|
|
708
707
|
// Translate == to IS NOT NULL for rhs operand being NULL literal, otherwise ...
|
|
709
708
|
// Translate == to IS NOT DISTINCT FROM, unless both operands cannot be NULL
|
|
710
|
-
if (x === '==') return xpr[i+1]?.val === null ? 'is' : _not_null(i-1) && _not_null(i+1) ? '=' : this.is_not_distinct_from_
|
|
709
|
+
if (x === '==') return xpr[i + 1]?.val === null ? 'is' : _not_null(i - 1) && _not_null(i + 1) ? '=' : this.is_not_distinct_from_
|
|
711
710
|
|
|
712
711
|
// Translate != to IS NULL for rhs operand being NULL literal, otherwise...
|
|
713
712
|
// Translate != to IS DISTINCT FROM, unless both operands cannot be NULL
|
|
714
|
-
if (x === '!=') return xpr[i+1]?.val === null ? 'is not' : _not_null(i-1) && _not_null(i+1) ? '<>' : this.is_distinct_from_
|
|
713
|
+
if (x === '!=') return xpr[i + 1]?.val === null ? 'is not' : _not_null(i - 1) && _not_null(i + 1) ? '<>' : this.is_distinct_from_
|
|
715
714
|
|
|
716
715
|
else return x
|
|
717
716
|
|
|
@@ -748,9 +747,9 @@ class CQN2SQLRenderer {
|
|
|
748
747
|
*/
|
|
749
748
|
ref({ ref }) {
|
|
750
749
|
switch (ref[0]) {
|
|
751
|
-
case '$now': return this.func({ func: 'session_context', args: [{ val: '$now' }]})
|
|
750
|
+
case '$now': return this.func({ func: 'session_context', args: [{ val: '$now', param: false }] })
|
|
752
751
|
case '$user':
|
|
753
|
-
case '$user.id': return this.func({ func: 'session_context', args: [{ val: '$user.id' }]})
|
|
752
|
+
case '$user.id': return this.func({ func: 'session_context', args: [{ val: '$user.id', param: false }] })
|
|
754
753
|
default: return ref.map(r => this.quote(r)).join('.')
|
|
755
754
|
}
|
|
756
755
|
}
|
|
@@ -760,7 +759,7 @@ class CQN2SQLRenderer {
|
|
|
760
759
|
* @param {import('./infer/cqn').val} param0
|
|
761
760
|
* @returns {string} SQL
|
|
762
761
|
*/
|
|
763
|
-
val({ val }) {
|
|
762
|
+
val({ val, param }) {
|
|
764
763
|
switch (typeof val) {
|
|
765
764
|
case 'function': throw new Error('Function values not supported.')
|
|
766
765
|
case 'undefined': return 'NULL'
|
|
@@ -769,13 +768,13 @@ class CQN2SQLRenderer {
|
|
|
769
768
|
case 'object':
|
|
770
769
|
if (val === null) return 'NULL'
|
|
771
770
|
if (val instanceof Date) return `'${val.toISOString()}'`
|
|
772
|
-
if (val instanceof Readable)
|
|
771
|
+
if (val instanceof Readable); // go on with default below
|
|
773
772
|
else if (Buffer.isBuffer(val)) val = val.toString('base64')
|
|
774
773
|
else if (is_regexp(val)) val = val.source
|
|
775
774
|
else val = JSON.stringify(val)
|
|
776
775
|
case 'string': // eslint-disable-line no-fallthrough
|
|
777
776
|
}
|
|
778
|
-
if (!this.values) return this.string(val)
|
|
777
|
+
if (!this.values || param === false) return this.string(val)
|
|
779
778
|
else this.values.push(val)
|
|
780
779
|
return '?'
|
|
781
780
|
}
|
|
@@ -859,12 +858,12 @@ class CQN2SQLRenderer {
|
|
|
859
858
|
const requiredColumns = !elements
|
|
860
859
|
? []
|
|
861
860
|
: Object.keys(elements)
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
861
|
+
.filter(
|
|
862
|
+
e =>
|
|
863
|
+
(elements[e]?.[annotation] || (!isUpdate && elements[e]?.default && !elements[e].virtual && !elements[e].isAssociation)) &&
|
|
864
|
+
!columns.find(c => c.name === e),
|
|
865
|
+
)
|
|
866
|
+
.map(name => ({ name, sql: 'NULL' }))
|
|
868
867
|
|
|
869
868
|
return [...columns, ...requiredColumns].map(({ name, sql }) => {
|
|
870
869
|
let element = elements?.[name] || {}
|
|
@@ -874,14 +873,13 @@ class CQN2SQLRenderer {
|
|
|
874
873
|
if (converter && sql[0] !== '$') sql = converter(sql, element)
|
|
875
874
|
|
|
876
875
|
let val = _managed[element[annotation]?.['=']]
|
|
877
|
-
if (val) sql = `coalesce(${sql}, ${this.func({ func: 'session_context', args: [{ val }] })})`
|
|
876
|
+
if (val) sql = `coalesce(${sql}, ${this.func({ func: 'session_context', args: [{ val, param: false }] })})`
|
|
878
877
|
else if (!isUpdate && element.default) {
|
|
879
878
|
const d = element.default
|
|
880
879
|
if (d.val !== undefined || d.ref?.[0] === '$now') {
|
|
881
880
|
// REVISIT: d.ref is not used afterwards
|
|
882
|
-
sql = `(CASE WHEN json_type(value,'$."${name}"') IS NULL THEN ${
|
|
883
|
-
|
|
884
|
-
} ELSE ${sql} END)`
|
|
881
|
+
sql = `(CASE WHEN json_type(value,'$."${name}"') IS NULL THEN ${this.defaultValue(d.val) // REVISIT: this.defaultValue is a strange function
|
|
882
|
+
} ELSE ${sql} END)`
|
|
885
883
|
}
|
|
886
884
|
}
|
|
887
885
|
|
package/lib/cqn4sql.js
CHANGED
|
@@ -48,7 +48,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
48
48
|
if (originalQuery.SELECT?.from.args && !originalQuery.joinTree) return inferred
|
|
49
49
|
|
|
50
50
|
let transformedQuery = cds.ql.clone(inferred)
|
|
51
|
-
const kind = inferred.
|
|
51
|
+
const kind = inferred.kind || Object.keys(inferred)[0]
|
|
52
52
|
|
|
53
53
|
if (inferred.INSERT || inferred.UPSERT) {
|
|
54
54
|
transformedQuery = transformQueryForInsertUpsert(kind)
|
|
@@ -415,7 +415,6 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
415
415
|
return transformedColumns
|
|
416
416
|
|
|
417
417
|
function handleSubquery(col) {
|
|
418
|
-
if (isLocalized(inferred.target)) col.SELECT.localized = true
|
|
419
418
|
if (!col.SELECT.from.as) {
|
|
420
419
|
const uniqueSubqueryAlias = inferred.joinTree.addNextAvailableTableAlias(
|
|
421
420
|
getLastStringSegment(col.SELECT.from.ref[col.SELECT.from.ref.length - 1]),
|
|
@@ -830,7 +829,6 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
830
829
|
one: column.$refLinks[column.$refLinks.length - 1].definition.is2one,
|
|
831
830
|
},
|
|
832
831
|
}
|
|
833
|
-
if (isLocalized(inferred.target)) subquery.SELECT.localized = true
|
|
834
832
|
const expanded = transformSubquery(subquery)
|
|
835
833
|
const correlated = _correlate({ ...expanded, as: columnAlias }, outerAlias)
|
|
836
834
|
Object.defineProperty(correlated, 'elements', { value: subquery.elements, writable: true })
|
|
@@ -946,6 +944,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
946
944
|
outerQueries.push(inferred)
|
|
947
945
|
Object.defineProperty(q, 'outerQueries', { value: outerQueries })
|
|
948
946
|
}
|
|
947
|
+
if (isLocalized(inferred.target)) q.SELECT.localized = true
|
|
949
948
|
return cqn4sql(q, model)
|
|
950
949
|
}
|
|
951
950
|
|
package/package.json
CHANGED