@cap-js/db-service 1.14.0 → 1.14.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 +9 -0
- package/lib/SQLService.js +31 -4
- package/lib/cqn2sql.js +137 -81
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
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.14.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.14.0...db-service-v1.14.1) (2024-10-28)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* deep delete for views ([#496](https://github.com/cap-js/cds-dbs/issues/496)) ([82154ef](https://github.com/cap-js/cds-dbs/commit/82154ef8b837f17e81e2516056e03ff215f1dff8))
|
|
13
|
+
* properly support `default`, `cds.on.insert` and `cds.on.update` for `UPSERT` queries ([#425](https://github.com/cap-js/cds-dbs/issues/425)) ([338e9f5](https://github.com/cap-js/cds-dbs/commit/338e9f5de9109d36013208547fc648c17ce8c7b0))
|
|
14
|
+
* SELECT cds.hana.BINARY ([#870](https://github.com/cap-js/cds-dbs/issues/870)) ([33c3ebe](https://github.com/cap-js/cds-dbs/commit/33c3ebe84be4c0181b1c230d5f2d332332201ce0))
|
|
15
|
+
|
|
7
16
|
## [1.14.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.13.0...db-service-v1.14.0) (2024-10-15)
|
|
8
17
|
|
|
9
18
|
|
package/lib/SQLService.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const cds = require('@sap/cds'),
|
|
2
2
|
DEBUG = cds.debug('sql|db')
|
|
3
3
|
const { Readable } = require('stream')
|
|
4
|
-
const { resolveView } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
|
|
4
|
+
const { resolveView, getDBTable, getTransition } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
|
|
5
5
|
const DatabaseService = require('./common/DatabaseService')
|
|
6
6
|
const cqn4sql = require('./cqn4sql')
|
|
7
7
|
|
|
@@ -25,14 +25,16 @@ class SQLService extends DatabaseService {
|
|
|
25
25
|
this.on(['INSERT', 'UPSERT', 'UPDATE'], require('./deep-queries').onDeep)
|
|
26
26
|
if (cds.env.features.db_strict) {
|
|
27
27
|
this.before(['INSERT', 'UPSERT', 'UPDATE'], ({ query }) => {
|
|
28
|
-
const elements = query.target?.elements
|
|
28
|
+
const elements = query.target?.elements
|
|
29
|
+
if (!elements) return
|
|
29
30
|
const kind = query.kind || Object.keys(query)[0]
|
|
30
31
|
const operation = query[kind]
|
|
31
32
|
if (!operation.columns && !operation.entries && !operation.data) return
|
|
32
33
|
const columns =
|
|
33
34
|
operation.columns ||
|
|
34
35
|
Object.keys(
|
|
35
|
-
operation.data ||
|
|
36
|
+
operation.data ||
|
|
37
|
+
operation.entries?.reduce((acc, obj) => {
|
|
36
38
|
return Object.assign(acc, obj)
|
|
37
39
|
}, {}),
|
|
38
40
|
)
|
|
@@ -214,7 +216,31 @@ class SQLService extends DatabaseService {
|
|
|
214
216
|
// REVISIT: It's not yet 100 % clear under which circumstances we can rely on db constraints
|
|
215
217
|
return (super.onDELETE = /* cds.env.features.assert_integrity === 'db' ? this.onSIMPLE : */ deep_delete)
|
|
216
218
|
async function deep_delete(/** @type {Request} */ req) {
|
|
217
|
-
|
|
219
|
+
const transitions = getTransition(req.target, this, false, req.query.cmd || 'DELETE')
|
|
220
|
+
if (transitions.target !== transitions.queryTarget) {
|
|
221
|
+
const keys = []
|
|
222
|
+
const transitionsTarget = transitions.queryTarget.keys || transitions.queryTarget.elements
|
|
223
|
+
for (const key in transitionsTarget) {
|
|
224
|
+
const exists = e => e && !e.virtual && !e.value && !e.isAssociation
|
|
225
|
+
if (exists(transitionsTarget[key])) keys.push(key)
|
|
226
|
+
}
|
|
227
|
+
const matchedKeys = keys.filter(key => transitions.mapping.has(key)).map(k => ({ ref: [k] }))
|
|
228
|
+
const query = DELETE.from({
|
|
229
|
+
ref: [
|
|
230
|
+
{
|
|
231
|
+
id: transitions.target.name,
|
|
232
|
+
where: [
|
|
233
|
+
{ list: matchedKeys.map(k => transitions.mapping.get(k.ref[0])) },
|
|
234
|
+
'in',
|
|
235
|
+
SELECT.from(req.query.DELETE.from).columns(matchedKeys).where(req.query.DELETE.where),
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
})
|
|
240
|
+
return this.onDELETE({ query, target: transitions.target })
|
|
241
|
+
}
|
|
242
|
+
const table = getDBTable(req.target)
|
|
243
|
+
const { compositions } = table
|
|
218
244
|
if (compositions) {
|
|
219
245
|
// Transform CQL`DELETE from Foo[p1] WHERE p2` into CQL`DELETE from Foo[p1 and p2]`
|
|
220
246
|
let { from, where } = req.query.DELETE
|
|
@@ -241,6 +267,7 @@ class SQLService extends DatabaseService {
|
|
|
241
267
|
)
|
|
242
268
|
// Prepare and run deep query, à la CQL`DELETE from Foo[pred]:comp1.comp2...`
|
|
243
269
|
const query = DELETE.from({ ref: [...from.ref, c.name] })
|
|
270
|
+
query.target = c._target
|
|
244
271
|
return this.onDELETE({ query, depth, visited: [...visited], target: c._target })
|
|
245
272
|
}),
|
|
246
273
|
)
|
package/lib/cqn2sql.js
CHANGED
|
@@ -4,12 +4,6 @@ const cqn4sql = require('./cqn4sql')
|
|
|
4
4
|
const _simple_queries = cds.env.features.sql_simple_queries
|
|
5
5
|
const _strict_booleans = _simple_queries < 2
|
|
6
6
|
|
|
7
|
-
const BINARY_TYPES = {
|
|
8
|
-
'cds.Binary': 1,
|
|
9
|
-
'cds.LargeBinary': 1,
|
|
10
|
-
'cds.hana.BINARY': 1,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
const { Readable } = require('stream')
|
|
14
8
|
|
|
15
9
|
const DEBUG = (() => {
|
|
@@ -42,6 +36,12 @@ class CQN2SQLRenderer {
|
|
|
42
36
|
}
|
|
43
37
|
}
|
|
44
38
|
|
|
39
|
+
BINARY_TYPES = {
|
|
40
|
+
'cds.Binary': 1,
|
|
41
|
+
'cds.LargeBinary': 1,
|
|
42
|
+
'cds.hana.BINARY': 1,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
45
|
static _add_mixins(aspect, mixins) {
|
|
46
46
|
const fqn = this.name + aspect
|
|
47
47
|
const types = cds.builtin.types
|
|
@@ -142,13 +142,15 @@ class CQN2SQLRenderer {
|
|
|
142
142
|
*/
|
|
143
143
|
CREATE_elements(elements) {
|
|
144
144
|
let sql = ''
|
|
145
|
+
let keys = ''
|
|
145
146
|
for (let e in elements) {
|
|
146
147
|
const definition = elements[e]
|
|
147
148
|
if (definition.isAssociation) continue
|
|
149
|
+
if (definition.key) keys = `${keys}, ${this.quote(definition.name)}`
|
|
148
150
|
const s = this.CREATE_element(definition)
|
|
149
|
-
if (s) sql +=
|
|
151
|
+
if (s) sql += `, ${s}`
|
|
150
152
|
}
|
|
151
|
-
return sql.slice(
|
|
153
|
+
return `${sql.slice(2)}${keys && `, PRIMARY KEY(${keys.slice(2)})`}`
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
/**
|
|
@@ -491,8 +493,6 @@ class CQN2SQLRenderer {
|
|
|
491
493
|
*/
|
|
492
494
|
INSERT_entries(q) {
|
|
493
495
|
const { INSERT } = q
|
|
494
|
-
const entity = this.name(q.target?.name || INSERT.into.ref[0])
|
|
495
|
-
const alias = INSERT.into.as
|
|
496
496
|
const elements = q.elements || q.target?.elements
|
|
497
497
|
if (!elements && !INSERT.entries?.length) {
|
|
498
498
|
return // REVISIT: mtx sends an insert statement without entries and no reference entity
|
|
@@ -504,19 +504,14 @@ class CQN2SQLRenderer {
|
|
|
504
504
|
/** @type {string[]} */
|
|
505
505
|
this.columns = columns
|
|
506
506
|
|
|
507
|
+
const alias = INSERT.into.as
|
|
508
|
+
const entity = this.name(q.target?.name || INSERT.into.ref[0])
|
|
507
509
|
if (!elements) {
|
|
508
510
|
this.entries = INSERT.entries.map(e => columns.map(c => e[c]))
|
|
509
511
|
const param = this.param.bind(this, { ref: ['?'] })
|
|
510
512
|
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(c))}) VALUES (${columns.map(param)})`)
|
|
511
513
|
}
|
|
512
514
|
|
|
513
|
-
const extractions = this.managed(
|
|
514
|
-
columns.map(c => ({ name: c })),
|
|
515
|
-
elements,
|
|
516
|
-
!!q.UPSERT,
|
|
517
|
-
)
|
|
518
|
-
const extraction = extractions.map(c => c.sql)
|
|
519
|
-
|
|
520
515
|
// Include this.values for placeholders
|
|
521
516
|
/** @type {unknown[][]} */
|
|
522
517
|
this.entries = []
|
|
@@ -530,8 +525,9 @@ class CQN2SQLRenderer {
|
|
|
530
525
|
this.entries = [[...this.values, stream]]
|
|
531
526
|
}
|
|
532
527
|
|
|
528
|
+
const extractions = this._managed = this.managed(columns.map(c => ({ name: c })), elements)
|
|
533
529
|
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(c))
|
|
534
|
-
}) SELECT ${
|
|
530
|
+
}) SELECT ${extractions.map(c => c.insert)} FROM json_each(?)`)
|
|
535
531
|
}
|
|
536
532
|
|
|
537
533
|
async *INSERT_entries_stream(entries, binaryEncoding = 'base64') {
|
|
@@ -569,7 +565,7 @@ class CQN2SQLRenderer {
|
|
|
569
565
|
|
|
570
566
|
buffer += '"'
|
|
571
567
|
} else {
|
|
572
|
-
if (elements[key]?.type in BINARY_TYPES) {
|
|
568
|
+
if (elements[key]?.type in this.BINARY_TYPES) {
|
|
573
569
|
val = transformBase64(val)
|
|
574
570
|
}
|
|
575
571
|
buffer += `${keyJSON}${JSON.stringify(val)}`
|
|
@@ -617,7 +613,7 @@ class CQN2SQLRenderer {
|
|
|
617
613
|
|
|
618
614
|
buffer += '"'
|
|
619
615
|
} else {
|
|
620
|
-
if (elements[this.columns[key]]?.type in BINARY_TYPES) {
|
|
616
|
+
if (elements[this.columns[key]]?.type in this.BINARY_TYPES) {
|
|
621
617
|
val = transformBase64(val)
|
|
622
618
|
}
|
|
623
619
|
buffer += `${sepsub}${val === undefined ? 'null' : JSON.stringify(val)}`
|
|
@@ -646,18 +642,7 @@ class CQN2SQLRenderer {
|
|
|
646
642
|
const entity = this.name(q.target?.name || INSERT.into.ref[0])
|
|
647
643
|
const alias = INSERT.into.as
|
|
648
644
|
const elements = q.elements || q.target?.elements
|
|
649
|
-
const columns = INSERT.columns
|
|
650
|
-
|| cds.error`Cannot insert rows without columns or elements`
|
|
651
|
-
|
|
652
|
-
const inputConverter = this.class._convertInput
|
|
653
|
-
const extraction = columns.map((c, i) => {
|
|
654
|
-
const extract = `value->>'$[${i}]'`
|
|
655
|
-
const element = elements?.[c]
|
|
656
|
-
const converter = element?.[inputConverter]
|
|
657
|
-
return converter?.(extract, element) || extract
|
|
658
|
-
})
|
|
659
|
-
|
|
660
|
-
this.columns = columns
|
|
645
|
+
const columns = this.columns = INSERT.columns || cds.error`Cannot insert rows without columns or elements`
|
|
661
646
|
|
|
662
647
|
if (!elements) {
|
|
663
648
|
this.entries = INSERT.rows
|
|
@@ -675,6 +660,10 @@ class CQN2SQLRenderer {
|
|
|
675
660
|
this.entries = [[...this.values, stream]]
|
|
676
661
|
}
|
|
677
662
|
|
|
663
|
+
const extraction = (this._managed = this.managed(columns.map(c => ({ name: c })), elements))
|
|
664
|
+
.slice(0, columns.length)
|
|
665
|
+
.map(c => c.converter(c.extract))
|
|
666
|
+
|
|
678
667
|
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(c))
|
|
679
668
|
}) SELECT ${extraction} FROM json_each(?)`)
|
|
680
669
|
}
|
|
@@ -686,7 +675,7 @@ class CQN2SQLRenderer {
|
|
|
686
675
|
*/
|
|
687
676
|
INSERT_values(q) {
|
|
688
677
|
let { columns, values } = q.INSERT
|
|
689
|
-
return this.
|
|
678
|
+
return this.render({ __proto__: q, INSERT: { __proto__: q.INSERT, columns, rows: [values] } })
|
|
690
679
|
}
|
|
691
680
|
|
|
692
681
|
/**
|
|
@@ -737,14 +726,37 @@ class CQN2SQLRenderer {
|
|
|
737
726
|
*/
|
|
738
727
|
UPSERT(q) {
|
|
739
728
|
const { UPSERT } = q
|
|
740
|
-
|
|
729
|
+
|
|
741
730
|
let sql = this.INSERT({ __proto__: q, INSERT: UPSERT })
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
731
|
+
if (!q.target?.keys) return sql
|
|
732
|
+
const keys = []
|
|
733
|
+
for (const k of ObjectKeys(q.target?.keys)) {
|
|
734
|
+
const element = q.target.keys[k]
|
|
735
|
+
if (element.isAssociation || element.virtual) continue
|
|
736
|
+
keys.push(k)
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const elements = q.target?.elements || {}
|
|
740
|
+
// temporal data
|
|
741
|
+
for (const k of ObjectKeys(elements)) {
|
|
742
|
+
if (elements[k]['@cds.valid.from']) keys.push(k)
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const keyCompare = keys
|
|
746
|
+
.map(k => `NEW.${this.quote(k)}=OLD.${this.quote(k)}`)
|
|
747
|
+
.join(' AND ')
|
|
748
|
+
|
|
749
|
+
const columns = this.columns // this.columns is computed as part of this.INSERT
|
|
750
|
+
const managed = this._managed.slice(0, columns.length)
|
|
751
|
+
|
|
752
|
+
const extractkeys = managed
|
|
753
|
+
.filter(c => keys.includes(c.name))
|
|
754
|
+
.map(c => `${c.onInsert || c.sql} as ${this.quote(c.name)}`)
|
|
745
755
|
|
|
746
|
-
|
|
747
|
-
|
|
756
|
+
const entity = this.name(q.target?.name || UPSERT.into.ref[0])
|
|
757
|
+
sql = `SELECT ${managed.map(c => c.upsert)} FROM (SELECT value, ${extractkeys} from json_each(?)) as NEW LEFT JOIN ${this.quote(entity)} AS OLD ON ${keyCompare}`
|
|
758
|
+
|
|
759
|
+
const updateColumns = columns.filter(c => {
|
|
748
760
|
if (keys.includes(c)) return false //> keys go into ON CONFLICT clause
|
|
749
761
|
let e = elements[c]
|
|
750
762
|
if (!e) return true //> pass through to native SQL columns not in CDS model
|
|
@@ -754,14 +766,8 @@ class CQN2SQLRenderer {
|
|
|
754
766
|
else return true
|
|
755
767
|
}).map(c => `${this.quote(c)} = excluded.${this.quote(c)}`)
|
|
756
768
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
keys = keys.map(k => this.quote(k))
|
|
761
|
-
const conflict = updateColumns.length
|
|
762
|
-
? `ON CONFLICT(${keys}) DO UPDATE SET ` + updateColumns
|
|
763
|
-
: `ON CONFLICT(${keys}) DO NOTHING`
|
|
764
|
-
return (this.sql = `${sql} WHERE true ${conflict}`)
|
|
769
|
+
return (this.sql = `INSERT INTO ${this.quote(entity)} (${columns.map(c => this.quote(c))}) ${sql
|
|
770
|
+
} WHERE TRUE ON CONFLICT(${keys.map(c => this.quote(c))}) DO ${updateColumns.length ? `UPDATE SET ${updateColumns}` : 'NOTHING'}`)
|
|
765
771
|
}
|
|
766
772
|
|
|
767
773
|
// UPDATE Statements ------------------------------------------------
|
|
@@ -790,7 +796,9 @@ class CQN2SQLRenderer {
|
|
|
790
796
|
}
|
|
791
797
|
}
|
|
792
798
|
|
|
793
|
-
const extraction = this.managed(columns, elements
|
|
799
|
+
const extraction = this.managed(columns, elements)
|
|
800
|
+
.filter((c, i) => columns[i] || c.onUpdate)
|
|
801
|
+
.map((c, i) => `${this.quote(c.name)}=${!columns[i] ? c.onUpdate : c.sql}`)
|
|
794
802
|
|
|
795
803
|
sql += ` SET ${extraction}`
|
|
796
804
|
if (where) sql += ` WHERE ${this.where(where)}`
|
|
@@ -1042,56 +1050,104 @@ class CQN2SQLRenderer {
|
|
|
1042
1050
|
}
|
|
1043
1051
|
|
|
1044
1052
|
/**
|
|
1045
|
-
*
|
|
1053
|
+
* Converts the columns array into an array of SQL expressions that extract the correct value from inserted JSON data
|
|
1046
1054
|
* @param {object[]} columns
|
|
1047
1055
|
* @param {import('./infer/cqn').elements} elements
|
|
1048
1056
|
* @param {Boolean} isUpdate
|
|
1049
1057
|
* @returns {string[]} Array of SQL expressions for processing input JSON data
|
|
1050
1058
|
*/
|
|
1051
|
-
managed(columns, elements
|
|
1052
|
-
const
|
|
1059
|
+
managed(columns, elements) {
|
|
1060
|
+
const cdsOnInsert = '@cds.on.insert'
|
|
1061
|
+
const cdsOnUpdate = '@cds.on.update'
|
|
1062
|
+
|
|
1053
1063
|
const { _convertInput } = this.class
|
|
1054
1064
|
// Ensure that missing managed columns are added
|
|
1055
1065
|
const requiredColumns = !elements
|
|
1056
1066
|
? []
|
|
1057
|
-
:
|
|
1058
|
-
.filter(
|
|
1059
|
-
e
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1067
|
+
: ObjectKeys(elements)
|
|
1068
|
+
.filter(e => {
|
|
1069
|
+
const element = elements[e]
|
|
1070
|
+
// Actual mandatory check
|
|
1071
|
+
if (!(element.default || element[cdsOnInsert] || element[cdsOnUpdate])) return false
|
|
1072
|
+
// Physical column check
|
|
1073
|
+
if (!element || element.virtual || element.isAssociation) return false
|
|
1074
|
+
// Existence check
|
|
1075
|
+
if (columns.find(c => c.name === e)) return false
|
|
1076
|
+
return true
|
|
1077
|
+
})
|
|
1063
1078
|
.map(name => ({ name, sql: 'NULL' }))
|
|
1064
1079
|
|
|
1080
|
+
const keys = ObjectKeys(elements).filter(e => elements[e].key && !elements[e].isAssociation)
|
|
1081
|
+
const keyZero = keys[0] && this.quote(keys[0])
|
|
1082
|
+
|
|
1065
1083
|
return [...columns, ...requiredColumns].map(({ name, sql }) => {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
let
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
else if (!isUpdate && element.default) {
|
|
1075
|
-
const d = element.default
|
|
1076
|
-
if (d.val !== undefined || d.ref?.[0] === '$now') {
|
|
1077
|
-
// REVISIT: d.ref is not used afterwards
|
|
1078
|
-
sql = `(CASE WHEN json_type(value,'$."${name}"') IS NULL THEN ${this.defaultValue(d.val) // REVISIT: this.defaultValue is a strange function
|
|
1079
|
-
} ELSE ${sql} END)`
|
|
1080
|
-
}
|
|
1084
|
+
const element = elements?.[name] || {}
|
|
1085
|
+
|
|
1086
|
+
const converter = a => element[_convertInput]?.(a, element) || a
|
|
1087
|
+
let extract
|
|
1088
|
+
if (!sql) {
|
|
1089
|
+
({ sql, extract } = this.managed_extract(name, element, converter))
|
|
1090
|
+
} else {
|
|
1091
|
+
extract = sql = converter(sql)
|
|
1081
1092
|
}
|
|
1093
|
+
// if (sql[0] !== '$') sql = converter(sql, element)
|
|
1094
|
+
|
|
1095
|
+
let onInsert = this.managed_session_context(element[cdsOnInsert]?.['='])
|
|
1096
|
+
|| this.managed_session_context(element.default?.ref?.[0])
|
|
1097
|
+
|| (element.default?.val !== undefined && { val: element.default.val, param: false })
|
|
1098
|
+
let onUpdate = this.managed_session_context(element[cdsOnUpdate]?.['='])
|
|
1099
|
+
|
|
1100
|
+
if (onInsert) onInsert = this.expr(onInsert)
|
|
1101
|
+
if (onUpdate) onUpdate = this.expr(onUpdate)
|
|
1102
|
+
|
|
1103
|
+
const qname = this.quote(name)
|
|
1104
|
+
|
|
1105
|
+
const insert = onInsert ? this.managed_default(name, converter(onInsert), sql) : sql
|
|
1106
|
+
const update = onUpdate ? this.managed_default(name, converter(onUpdate), sql) : sql
|
|
1107
|
+
const upsert = keyZero && (
|
|
1108
|
+
// upsert requires the keys to be provided for the existance join (default values optional)
|
|
1109
|
+
element.key
|
|
1110
|
+
// If both insert and update have the same managed definition exclude the old value check
|
|
1111
|
+
|| (onInsert && onUpdate && insert === update)
|
|
1112
|
+
? `${insert} as ${qname}`
|
|
1113
|
+
: `CASE WHEN OLD.${keyZero} IS NULL THEN ${
|
|
1114
|
+
// If key of old is null execute insert
|
|
1115
|
+
insert
|
|
1116
|
+
} ELSE ${
|
|
1117
|
+
// Else execute managed update or keep old if no new data if provided
|
|
1118
|
+
onUpdate ? update : this.managed_default(name, `OLD.${qname}`, update)
|
|
1119
|
+
} END as ${qname}`
|
|
1120
|
+
)
|
|
1082
1121
|
|
|
1083
|
-
return {
|
|
1122
|
+
return {
|
|
1123
|
+
name, // Element name
|
|
1124
|
+
sql, // Reference SQL
|
|
1125
|
+
extract, // Source SQL
|
|
1126
|
+
converter, // Converter logic
|
|
1127
|
+
// action specific full logic
|
|
1128
|
+
insert, update, upsert,
|
|
1129
|
+
// action specific isolated logic
|
|
1130
|
+
onInsert, onUpdate
|
|
1131
|
+
}
|
|
1084
1132
|
})
|
|
1085
1133
|
}
|
|
1086
1134
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1135
|
+
managed_extract(name, element, converter) {
|
|
1136
|
+
const { UPSERT, INSERT } = this.cqn
|
|
1137
|
+
const extract = !(INSERT?.entries || UPSERT?.entries) && (INSERT?.rows || UPSERT?.rows)
|
|
1138
|
+
? `value->>'$[${this.columns.indexOf(name)}]'`
|
|
1139
|
+
: `value->>'$."${name.replace(/"/g, '""')}"'`
|
|
1140
|
+
const sql = converter?.(extract) || extract
|
|
1141
|
+
return { extract, sql }
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
managed_session_context(src) {
|
|
1145
|
+
const val = _managed[src]
|
|
1146
|
+
return val && { func: 'session_context', args: [{ val, param: false }] }
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
managed_default(name, managed, src) {
|
|
1150
|
+
return `(CASE WHEN json_type(value,${this.managed_extract(name).extract.slice(8)}) IS NULL THEN ${managed} ELSE ${src} END)`
|
|
1095
1151
|
}
|
|
1096
1152
|
}
|
|
1097
1153
|
|
package/package.json
CHANGED