@cap-js/db-service 1.5.0 → 1.6.0
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 +41 -0
- package/lib/SQLService.js +131 -82
- package/lib/common/DatabaseService.js +6 -14
- package/lib/common/session-context.js +14 -0
- package/lib/cqn2sql.js +203 -137
- package/lib/cqn4sql.js +109 -125
- package/lib/fill-in-keys.js +6 -1
- package/lib/infer/cqn.d.ts +0 -3
- package/lib/infer/index.js +36 -23
- package/lib/infer/join-tree.js +5 -6
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,47 @@
|
|
|
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.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.5.1...db-service-v1.6.0) (2024-02-02)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* Add fallback for @cap-js/hana for unknown entities ([#403](https://github.com/cap-js/cds-dbs/issues/403)) ([e7dd6de](https://github.com/cap-js/cds-dbs/commit/e7dd6de4ef65881ef66f7ba9c164ff2b4e9b1111))
|
|
13
|
+
* SELECT returns binaries as Buffers ([#416](https://github.com/cap-js/cds-dbs/issues/416)) ([d4240d5](https://github.com/cap-js/cds-dbs/commit/d4240d5efb7789851593c83a430e601d6ff87118))
|
|
14
|
+
* SELECT returns LargeBinaries as streams unless feature flag "stream_compat" is set ([#251](https://github.com/cap-js/cds-dbs/issues/251)) ([8165a4a](https://github.com/cap-js/cds-dbs/commit/8165a4a3f6bb21c970668c8873f9d9c662b43780))
|
|
15
|
+
* strict mode to validate input for `INSERT`, `UPDATE` and `UPSERT` ([#384](https://github.com/cap-js/cds-dbs/issues/384)) ([4644483](https://github.com/cap-js/cds-dbs/commit/464448384145d934933c473ae2f20d49cc75554d))
|
|
16
|
+
* Support Readable Streams inside INSERT.entries ([#343](https://github.com/cap-js/cds-dbs/issues/343)) ([f6faf89](https://github.com/cap-js/cds-dbs/commit/f6faf8955b7888479c66f1727ade65b382611c2f))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
* **`cqn4sql`:** only transform list if necessary ([#438](https://github.com/cap-js/cds-dbs/issues/438)) ([8a7ec65](https://github.com/cap-js/cds-dbs/commit/8a7ec65fe46c2dae668bb536671943a76d5e8206))
|
|
22
|
+
* always generate unique subquery aliases ([#435](https://github.com/cap-js/cds-dbs/issues/435)) ([c875b7d](https://github.com/cap-js/cds-dbs/commit/c875b7d07a83693febb2543d202fd53b43172f7b))
|
|
23
|
+
* consider `list` in `from.where` ([#429](https://github.com/cap-js/cds-dbs/issues/429)) ([3288e94](https://github.com/cap-js/cds-dbs/commit/3288e943f53a2ba08d97018e016c06932b5c8f88))
|
|
24
|
+
* **cqn2sql:** $user.locale refs ([#431](https://github.com/cap-js/cds-dbs/issues/431)) ([ec55276](https://github.com/cap-js/cds-dbs/commit/ec55276409ccd56d8b831bbff3d3915e078d3f72))
|
|
25
|
+
* **cqn4sql:** expand structured keys in on-conditions ([#421](https://github.com/cap-js/cds-dbs/issues/421)) ([b1e0677](https://github.com/cap-js/cds-dbs/commit/b1e06777ccfce80f50443e61a10ae5d86c6bc232))
|
|
26
|
+
* Do not generate UUIDs for association keys ([#398](https://github.com/cap-js/cds-dbs/issues/398)) ([9970e14](https://github.com/cap-js/cds-dbs/commit/9970e14352679711a9c60807608becff05151fc4))
|
|
27
|
+
* enumeration issue with session context in @cap-js/hana ([#399](https://github.com/cap-js/cds-dbs/issues/399)) ([8106a20](https://github.com/cap-js/cds-dbs/commit/8106a207543be700d37b1f1b510d00d5dd1370e4))
|
|
28
|
+
* make @cap-js/sqlite work with better-sqlite3@9.3.0 ([#422](https://github.com/cap-js/cds-dbs/issues/422)) ([44c0a59](https://github.com/cap-js/cds-dbs/commit/44c0a59277b14be0b81b7f80555e18377ddbfe3c))
|
|
29
|
+
* pass context of navigation for list within infix filter ([#433](https://github.com/cap-js/cds-dbs/issues/433)) ([0ca077f](https://github.com/cap-js/cds-dbs/commit/0ca077f071a1569fa3b46f6ccfb003feaebd1ea0))
|
|
30
|
+
* Restore former deep upsert behavior / error ([#406](https://github.com/cap-js/cds-dbs/issues/406)) ([284b1e3](https://github.com/cap-js/cds-dbs/commit/284b1e3a605957d91dd867794ab1a7dcdf345c40))
|
|
31
|
+
* Skip virtual fields on UPSERTs ([#405](https://github.com/cap-js/cds-dbs/issues/405)) ([1a05dcb](https://github.com/cap-js/cds-dbs/commit/1a05dcb1d032a85e826c76f5a8a710161fa2b679))
|
|
32
|
+
* sqlite date string compatibility parsing only for valid dates ([#410](https://github.com/cap-js/cds-dbs/issues/410)) ([2a8bb2d](https://github.com/cap-js/cds-dbs/commit/2a8bb2d60940760c6280d8cc06100cb9087194b5)), closes [#409](https://github.com/cap-js/cds-dbs/issues/409)
|
|
33
|
+
* UPSERT for @cap-js/hana for entities with multiple keys ([#418](https://github.com/cap-js/cds-dbs/issues/418)) ([9bbac6e](https://github.com/cap-js/cds-dbs/commit/9bbac6ebbbddfa2f620833ce195eedeb0a79f43e))
|
|
34
|
+
|
|
35
|
+
## [1.5.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.5.0...db-service-v1.5.1) (2023-12-20)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
* **cqn2sql:** supporting calculated elements ([#387](https://github.com/cap-js/cds-dbs/issues/387)) ([2153fb9](https://github.com/cap-js/cds-dbs/commit/2153fb9a3910cd4afa3a91918e6cf682646492b7))
|
|
41
|
+
* 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))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Performance Improvements
|
|
45
|
+
|
|
46
|
+
* HANA list placeholder ([#380](https://github.com/cap-js/cds-dbs/issues/380)) ([3eadfea](https://github.com/cap-js/cds-dbs/commit/3eadfea7b94f485030cc8bd0bd298ce088586422))
|
|
47
|
+
|
|
7
48
|
## [1.5.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.4.0...db-service-v1.5.0) (2023-12-06)
|
|
8
49
|
|
|
9
50
|
|
package/lib/SQLService.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
const cds = require('@sap/cds/lib'),
|
|
2
2
|
DEBUG = cds.debug('sql|db')
|
|
3
|
+
const { Readable } = require('stream')
|
|
3
4
|
const { resolveView } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
|
|
4
5
|
const DatabaseService = require('./common/DatabaseService')
|
|
5
6
|
const cqn4sql = require('./cqn4sql')
|
|
6
7
|
|
|
8
|
+
const BINARY_TYPES = {
|
|
9
|
+
'cds.Binary': 1,
|
|
10
|
+
'cds.hana.BINARY': 1
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
/** @typedef {import('@sap/cds/apis/services').Request} Request */
|
|
8
14
|
|
|
9
15
|
/**
|
|
@@ -14,12 +20,29 @@ const cqn4sql = require('./cqn4sql')
|
|
|
14
20
|
*/
|
|
15
21
|
|
|
16
22
|
class SQLService extends DatabaseService {
|
|
17
|
-
|
|
18
23
|
init() {
|
|
19
|
-
this.on(['
|
|
20
|
-
this.on(['UPDATE'], this.transformStreamIntoCQN)
|
|
21
|
-
this.on(['INSERT', 'UPSERT', 'UPDATE'], require('./fill-in-keys')) // REVISIT: should be replaced by correct input processing eventually
|
|
24
|
+
this.on(['INSERT', 'UPSERT', 'UPDATE'], require('./fill-in-keys')) // REVISIT should be replaced by correct input processing eventually
|
|
22
25
|
this.on(['INSERT', 'UPSERT', 'UPDATE'], require('./deep-queries').onDeep)
|
|
26
|
+
if (cds.env.features.db_strict) {
|
|
27
|
+
this.before(['INSERT', 'UPSERT', 'UPDATE'], ({ query }) => {
|
|
28
|
+
const elements = query.target?.elements; if (!elements) return
|
|
29
|
+
const kind = query.kind || Object.keys(query)[0]
|
|
30
|
+
const operation = query[kind]
|
|
31
|
+
if (!operation.columns && !operation.entries && !operation.data) return
|
|
32
|
+
const columns =
|
|
33
|
+
operation.columns ||
|
|
34
|
+
Object.keys(
|
|
35
|
+
operation.data || operation.entries?.reduce((acc, obj) => {
|
|
36
|
+
return Object.assign(acc, obj)
|
|
37
|
+
}, {}),
|
|
38
|
+
)
|
|
39
|
+
const invalidColumns = columns.filter(c => !(c in elements))
|
|
40
|
+
|
|
41
|
+
if (invalidColumns.length > 0) {
|
|
42
|
+
cds.error(`STRICT MODE: Trying to ${kind} non existent columns (${invalidColumns})`)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
23
46
|
this.on(['SELECT'], this.onSELECT)
|
|
24
47
|
this.on(['INSERT'], this.onINSERT)
|
|
25
48
|
this.on(['UPSERT'], this.onUPSERT)
|
|
@@ -27,46 +50,63 @@ class SQLService extends DatabaseService {
|
|
|
27
50
|
this.on(['DELETE'], this.onDELETE)
|
|
28
51
|
this.on(['CREATE ENTITY', 'DROP ENTITY'], this.onSIMPLE)
|
|
29
52
|
this.on(['BEGIN', 'COMMIT', 'ROLLBACK'], this.onEVENT)
|
|
30
|
-
this.on(['STREAM'], this.onSTREAM)
|
|
31
53
|
this.on(['*'], this.onPlainSQL)
|
|
32
54
|
return super.init()
|
|
33
55
|
}
|
|
34
56
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (!
|
|
38
|
-
|
|
39
|
-
if (query.SELECT.where) cqn.STREAM.where = query.SELECT.where
|
|
40
|
-
const stream = await this.run(cqn)
|
|
41
|
-
return stream && { value: stream }
|
|
42
|
-
}
|
|
57
|
+
_changeToStreams(columns, rows, one, compat) {
|
|
58
|
+
if (!rows || !columns) return
|
|
59
|
+
if (!Array.isArray(rows)) rows = [rows]
|
|
60
|
+
if (!rows.length || !Object.keys(rows[0]).length) return
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (!elements) next()
|
|
49
|
-
for (const key in elements) {
|
|
50
|
-
const element = elements[key]
|
|
51
|
-
if (element['@Core.MediaType'] && data[key]?.pipe) col = key
|
|
52
|
-
if (element['@Core.IsMediaType'] && data[key]) type = key
|
|
53
|
-
if (element['@odata.etag'] && data[key]) etag = key
|
|
62
|
+
// REVISIT: remove after removing stream_compat feature flag
|
|
63
|
+
if (compat) {
|
|
64
|
+
rows[0][Object.keys(rows[0])[0]] = this._stream(Object.values(rows[0])[0])
|
|
65
|
+
return
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
for (let col of columns) {
|
|
69
|
+
const name = col.as || col.ref?.[col.ref.length - 1] || (typeof col === 'string' && col)
|
|
70
|
+
if (col.element?.isAssociation) {
|
|
71
|
+
if (one) this._changeToStreams(col.SELECT.columns, rows[0][name], false, compat)
|
|
72
|
+
else
|
|
73
|
+
rows.forEach(row => {
|
|
74
|
+
this._changeToStreams(col.SELECT.columns, row[name], false, compat)
|
|
75
|
+
})
|
|
76
|
+
} else if (col.element?.type === 'cds.LargeBinary') {
|
|
77
|
+
if (one) rows[0][name] = this._stream(rows[0][name])
|
|
78
|
+
else
|
|
79
|
+
rows.forEach(row => {
|
|
80
|
+
row[name] = this._stream(row[name])
|
|
81
|
+
})
|
|
82
|
+
} else if (col.element?.type in BINARY_TYPES) {
|
|
83
|
+
if (one) rows[0][name] = this._buffer(rows[0][name])
|
|
84
|
+
else
|
|
85
|
+
rows.forEach(row => {
|
|
86
|
+
row[name] = this._buffer(row[name])
|
|
87
|
+
})
|
|
88
|
+
}
|
|
67
89
|
}
|
|
90
|
+
}
|
|
68
91
|
|
|
69
|
-
|
|
92
|
+
_stream(val) {
|
|
93
|
+
if (val === null) return null
|
|
94
|
+
if (val instanceof Readable) return val
|
|
95
|
+
// Buffer.from only applies encoding when the input is a string
|
|
96
|
+
let raw = typeof val === 'string' ? Buffer.from(val.toString(), 'base64') : val
|
|
97
|
+
return new Readable({
|
|
98
|
+
read(size) {
|
|
99
|
+
if (raw.length === 0) return this.push(null)
|
|
100
|
+
const chunk = raw.slice(0, size)
|
|
101
|
+
raw = raw.slice(size)
|
|
102
|
+
this.push(chunk)
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_buffer(val) {
|
|
108
|
+
if (val === null) return null
|
|
109
|
+
return Buffer.from(val, 'base64')
|
|
70
110
|
}
|
|
71
111
|
|
|
72
112
|
/**
|
|
@@ -74,15 +114,27 @@ class SQLService extends DatabaseService {
|
|
|
74
114
|
* @type {Handler}
|
|
75
115
|
*/
|
|
76
116
|
async onSELECT({ query, data }) {
|
|
117
|
+
query.SELECT.expand = 'root'
|
|
77
118
|
const { sql, values, cqn } = this.cqn2sql(query, data)
|
|
78
119
|
let ps = await this.prepare(sql)
|
|
79
120
|
let rows = await ps.all(values)
|
|
80
121
|
if (rows.length)
|
|
81
122
|
if (cqn.SELECT.expand) rows = rows.map(r => (typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r))
|
|
123
|
+
|
|
124
|
+
if (cds.env.features.stream_compat) {
|
|
125
|
+
if (query._streaming) {
|
|
126
|
+
this._changeToStreams(cqn.SELECT.columns, rows, true, true)
|
|
127
|
+
return rows.length ? { value: Object.values(rows[0])[0] } : undefined
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
this._changeToStreams(cqn.SELECT.columns, rows, query.SELECT.one, false)
|
|
131
|
+
}
|
|
132
|
+
|
|
82
133
|
if (cqn.SELECT.count) {
|
|
83
134
|
// REVISIT: the runtime always expects that the count is preserved with .map, required for renaming in mocks
|
|
84
135
|
return SQLService._arrayWithCount(rows, await this.count(query, rows))
|
|
85
136
|
}
|
|
137
|
+
|
|
86
138
|
return cqn.SELECT.one || query.SELECT.from?.ref?.[0].cardinality?.max === 1 ? rows[0] : rows
|
|
87
139
|
}
|
|
88
140
|
|
|
@@ -126,22 +178,6 @@ class SQLService extends DatabaseService {
|
|
|
126
178
|
return this.onSIMPLE(req)
|
|
127
179
|
}
|
|
128
180
|
|
|
129
|
-
/**
|
|
130
|
-
* Handler for Stream
|
|
131
|
-
* @type {Handler}
|
|
132
|
-
*/
|
|
133
|
-
async onSTREAM(req) {
|
|
134
|
-
const { one, sql, values } = this.cqn2sql(req.query)
|
|
135
|
-
// writing stream
|
|
136
|
-
if (req.query.STREAM.into) {
|
|
137
|
-
const ps = await this.prepare(sql)
|
|
138
|
-
return (await ps.run(values)).changes
|
|
139
|
-
}
|
|
140
|
-
// reading stream
|
|
141
|
-
const ps = await this.prepare(sql)
|
|
142
|
-
return ps.stream(values, one)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
181
|
/**
|
|
146
182
|
* Handler for CREATE, DROP, UPDATE, DELETE, with simple CQN
|
|
147
183
|
* @type {Handler}
|
|
@@ -153,7 +189,8 @@ class SQLService extends DatabaseService {
|
|
|
153
189
|
}
|
|
154
190
|
|
|
155
191
|
get onDELETE() {
|
|
156
|
-
|
|
192
|
+
// REVISIT: It's not yet 100 % clear under which circumstances we can rely on db constraints
|
|
193
|
+
return (super.onDELETE = /* cds.env.features.assert_integrity === 'db' ? this.onSIMPLE : */ deep_delete)
|
|
157
194
|
async function deep_delete(/** @type {Request} */ req) {
|
|
158
195
|
let { compositions } = req.target
|
|
159
196
|
if (compositions) {
|
|
@@ -162,24 +199,29 @@ class SQLService extends DatabaseService {
|
|
|
162
199
|
if (typeof from === 'string') from = { ref: [from] }
|
|
163
200
|
if (where) {
|
|
164
201
|
let last = from.ref.at(-1)
|
|
165
|
-
if (last.where) [
|
|
166
|
-
from = {ref:[
|
|
202
|
+
if (last.where) [last, where] = [last.id, [{ xpr: last.where }, 'and', { xpr: where }]]
|
|
203
|
+
from = { ref: [...from.ref.slice(0, -1), { id: last, where }] }
|
|
167
204
|
}
|
|
168
205
|
// Process child compositions depth-first
|
|
169
|
-
let { depth=0, visited=[] } = req
|
|
170
|
-
visited.push
|
|
171
|
-
await Promise.all
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
206
|
+
let { depth = 0, visited = [] } = req
|
|
207
|
+
visited.push(req.target.name)
|
|
208
|
+
await Promise.all(
|
|
209
|
+
Object.values(compositions).map(c => {
|
|
210
|
+
if (c._target['@cds.persistence.skip'] === true) return
|
|
211
|
+
if (c._target === req.target) {
|
|
212
|
+
// the Genre.children case
|
|
213
|
+
if (++depth > (c['@depth'] || 3)) return
|
|
214
|
+
} else if (visited.includes(c._target.name))
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Transitive circular composition detected: \n\n` +
|
|
217
|
+
` ${visited.join(' > ')} > ${c._target.name} \n\n` +
|
|
218
|
+
`These are not supported by deep delete.`,
|
|
219
|
+
)
|
|
220
|
+
// Prepare and run deep query, à la CQL`DELETE from Foo[pred]:comp1.comp2...`
|
|
221
|
+
const query = DELETE.from({ ref: [...from.ref, c.name] })
|
|
222
|
+
return this.onDELETE({ query, depth, visited: [...visited], target: c._target })
|
|
223
|
+
}),
|
|
224
|
+
)
|
|
183
225
|
}
|
|
184
226
|
return this.onSIMPLE(req)
|
|
185
227
|
}
|
|
@@ -232,8 +274,8 @@ class SQLService extends DatabaseService {
|
|
|
232
274
|
// REVISIT: made uppercase count because of HANA reserved word quoting
|
|
233
275
|
const cq = SELECT.one([{ func: 'count', as: 'COUNT' }]).from(
|
|
234
276
|
cds.ql.clone(query, {
|
|
235
|
-
|
|
236
|
-
|
|
277
|
+
localized: false,
|
|
278
|
+
expand: false,
|
|
237
279
|
limit: undefined,
|
|
238
280
|
orderBy: undefined,
|
|
239
281
|
}),
|
|
@@ -258,10 +300,12 @@ class SQLService extends DatabaseService {
|
|
|
258
300
|
// preserves $count for .map calls on array
|
|
259
301
|
static _arrayWithCount = function (a, count) {
|
|
260
302
|
const _map = a.map
|
|
261
|
-
const map = function (..._) {
|
|
303
|
+
const map = function (..._) {
|
|
304
|
+
return SQLService._arrayWithCount(_map.call(a, ..._), count)
|
|
305
|
+
}
|
|
262
306
|
return Object.defineProperties(a, {
|
|
263
307
|
$count: { value: count, enumerable: false, configurable: true, writable: true },
|
|
264
|
-
map: { value: map, enumerable: false, configurable: true, writable: true }
|
|
308
|
+
map: { value: map, enumerable: false, configurable: true, writable: true },
|
|
265
309
|
})
|
|
266
310
|
}
|
|
267
311
|
|
|
@@ -279,10 +323,8 @@ class SQLService extends DatabaseService {
|
|
|
279
323
|
*/
|
|
280
324
|
cqn2sql(query, values) {
|
|
281
325
|
let q = this.cqn4sql(query)
|
|
282
|
-
if (q.SELECT && 'elements' in q) q.SELECT.expand ??= 'root'
|
|
283
|
-
|
|
284
326
|
let kind = q.kind || Object.keys(q)[0]
|
|
285
|
-
if (kind in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 }
|
|
327
|
+
if (kind in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 }) {
|
|
286
328
|
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?
|
|
287
329
|
let target = q[kind]._transitions?.[0].target
|
|
288
330
|
if (target) q.target = target // REVISIT: Why isn't that done in resolveView?
|
|
@@ -296,7 +338,14 @@ class SQLService extends DatabaseService {
|
|
|
296
338
|
* @returns {import('./infer/cqn').Query}
|
|
297
339
|
*/
|
|
298
340
|
cqn4sql(q) {
|
|
299
|
-
if (
|
|
341
|
+
if (
|
|
342
|
+
!cds.env.features.db_strict &&
|
|
343
|
+
!q.SELECT?.from?.join &&
|
|
344
|
+
!q.SELECT?.from?.SELECT &&
|
|
345
|
+
!this.model?.definitions[_target_name4(q)]
|
|
346
|
+
) {
|
|
347
|
+
return _unquirked(q)
|
|
348
|
+
}
|
|
300
349
|
return cqn4sql(q, this.model)
|
|
301
350
|
}
|
|
302
351
|
|
|
@@ -329,7 +378,6 @@ class SQLService extends DatabaseService {
|
|
|
329
378
|
* @interface
|
|
330
379
|
*/
|
|
331
380
|
class PreparedStatement {
|
|
332
|
-
|
|
333
381
|
/**
|
|
334
382
|
* Executes a prepared DML query, i.e., INSERT, UPDATE, DELETE, CREATE, DROP
|
|
335
383
|
* @abstract
|
|
@@ -379,9 +427,7 @@ const _target_name4 = q => {
|
|
|
379
427
|
q.UPDATE?.entity ||
|
|
380
428
|
q.DELETE?.from ||
|
|
381
429
|
q.CREATE?.entity ||
|
|
382
|
-
q.DROP?.entity
|
|
383
|
-
q.STREAM?.from ||
|
|
384
|
-
q.STREAM?.into
|
|
430
|
+
q.DROP?.entity
|
|
385
431
|
if (target?.SET?.op === 'union') throw new cds.error('UNION-based queries are not supported')
|
|
386
432
|
if (!target?.ref) return target
|
|
387
433
|
const [first] = target.ref
|
|
@@ -400,8 +446,11 @@ const _unquirked = q => {
|
|
|
400
446
|
return q
|
|
401
447
|
}
|
|
402
448
|
|
|
403
|
-
|
|
404
|
-
|
|
449
|
+
const sqls = new (class extends SQLService {
|
|
450
|
+
get factory() {
|
|
451
|
+
return null
|
|
452
|
+
}
|
|
453
|
+
})()
|
|
405
454
|
cds.extend(cds.ql.Query).with(
|
|
406
455
|
class {
|
|
407
456
|
forSQL() {
|
|
@@ -128,22 +128,14 @@ class DatabaseService extends cds.Service {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// REVISIT: should happen automatically after a configurable time
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const _disconnect = async tenant => {
|
|
136
|
-
const pool = this.pools[tenant]
|
|
137
|
-
if (!pool) {
|
|
138
|
-
return
|
|
139
|
-
}
|
|
131
|
+
async disconnect (tenant) {
|
|
132
|
+
const tenants = tenant ? [tenant] : Object.keys(this.pools)
|
|
133
|
+
await Promise.all (tenants.map (async t => {
|
|
134
|
+
const pool = this.pools[t]; if (!pool) return
|
|
140
135
|
await pool.drain()
|
|
141
136
|
await pool.clear()
|
|
142
|
-
delete this.pools[
|
|
143
|
-
}
|
|
144
|
-
if (tenant == null)
|
|
145
|
-
return Promise.all(Object.keys(this.pools).map(_disconnect))
|
|
146
|
-
return _disconnect(tenant)
|
|
137
|
+
delete this.pools[t]
|
|
138
|
+
}))
|
|
147
139
|
}
|
|
148
140
|
|
|
149
141
|
/**
|
|
@@ -28,5 +28,19 @@ class TemporalSessionContext extends SessionContext {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Set all getters as enumerable
|
|
32
|
+
const iterate = { enumerable: true }
|
|
33
|
+
const getters = (obj) => {
|
|
34
|
+
const prot = obj.prototype
|
|
35
|
+
const patch = {}
|
|
36
|
+
for (const [key, value] of Object.entries(Object.getOwnPropertyDescriptors(prot))) {
|
|
37
|
+
if (!value.get) continue
|
|
38
|
+
patch[key] = iterate
|
|
39
|
+
}
|
|
40
|
+
Object.defineProperties(prot, patch)
|
|
41
|
+
}
|
|
42
|
+
getters(SessionContext)
|
|
43
|
+
getters(TemporalSessionContext)
|
|
44
|
+
|
|
31
45
|
// REVISIT: only set temporal context if required!
|
|
32
46
|
module.exports = TemporalSessionContext
|