@cap-js/db-service 1.5.1 → 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 +28 -0
- package/lib/SQLService.js +130 -82
- package/lib/common/DatabaseService.js +6 -14
- package/lib/common/session-context.js +14 -0
- package/lib/cqn2sql.js +165 -96
- 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,34 @@
|
|
|
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
|
+
|
|
7
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)
|
|
8
36
|
|
|
9
37
|
|
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}
|
|
@@ -154,7 +190,7 @@ class SQLService extends DatabaseService {
|
|
|
154
190
|
|
|
155
191
|
get onDELETE() {
|
|
156
192
|
// 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
|
|
193
|
+
return (super.onDELETE = /* cds.env.features.assert_integrity === 'db' ? this.onSIMPLE : */ deep_delete)
|
|
158
194
|
async function deep_delete(/** @type {Request} */ req) {
|
|
159
195
|
let { compositions } = req.target
|
|
160
196
|
if (compositions) {
|
|
@@ -163,24 +199,29 @@ class SQLService extends DatabaseService {
|
|
|
163
199
|
if (typeof from === 'string') from = { ref: [from] }
|
|
164
200
|
if (where) {
|
|
165
201
|
let last = from.ref.at(-1)
|
|
166
|
-
if (last.where) [
|
|
167
|
-
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 }] }
|
|
168
204
|
}
|
|
169
205
|
// Process child compositions depth-first
|
|
170
|
-
let { depth=0, visited=[] } = req
|
|
171
|
-
visited.push
|
|
172
|
-
await Promise.all
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
)
|
|
184
225
|
}
|
|
185
226
|
return this.onSIMPLE(req)
|
|
186
227
|
}
|
|
@@ -233,8 +274,8 @@ class SQLService extends DatabaseService {
|
|
|
233
274
|
// REVISIT: made uppercase count because of HANA reserved word quoting
|
|
234
275
|
const cq = SELECT.one([{ func: 'count', as: 'COUNT' }]).from(
|
|
235
276
|
cds.ql.clone(query, {
|
|
236
|
-
|
|
237
|
-
|
|
277
|
+
localized: false,
|
|
278
|
+
expand: false,
|
|
238
279
|
limit: undefined,
|
|
239
280
|
orderBy: undefined,
|
|
240
281
|
}),
|
|
@@ -259,10 +300,12 @@ class SQLService extends DatabaseService {
|
|
|
259
300
|
// preserves $count for .map calls on array
|
|
260
301
|
static _arrayWithCount = function (a, count) {
|
|
261
302
|
const _map = a.map
|
|
262
|
-
const map = function (..._) {
|
|
303
|
+
const map = function (..._) {
|
|
304
|
+
return SQLService._arrayWithCount(_map.call(a, ..._), count)
|
|
305
|
+
}
|
|
263
306
|
return Object.defineProperties(a, {
|
|
264
307
|
$count: { value: count, enumerable: false, configurable: true, writable: true },
|
|
265
|
-
map: { value: map, enumerable: false, configurable: true, writable: true }
|
|
308
|
+
map: { value: map, enumerable: false, configurable: true, writable: true },
|
|
266
309
|
})
|
|
267
310
|
}
|
|
268
311
|
|
|
@@ -280,10 +323,8 @@ class SQLService extends DatabaseService {
|
|
|
280
323
|
*/
|
|
281
324
|
cqn2sql(query, values) {
|
|
282
325
|
let q = this.cqn4sql(query)
|
|
283
|
-
if (q.SELECT && 'elements' in q) q.SELECT.expand ??= 'root'
|
|
284
|
-
|
|
285
326
|
let kind = q.kind || Object.keys(q)[0]
|
|
286
|
-
if (kind in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 }
|
|
327
|
+
if (kind in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 }) {
|
|
287
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?
|
|
288
329
|
let target = q[kind]._transitions?.[0].target
|
|
289
330
|
if (target) q.target = target // REVISIT: Why isn't that done in resolveView?
|
|
@@ -297,7 +338,14 @@ class SQLService extends DatabaseService {
|
|
|
297
338
|
* @returns {import('./infer/cqn').Query}
|
|
298
339
|
*/
|
|
299
340
|
cqn4sql(q) {
|
|
300
|
-
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
|
+
}
|
|
301
349
|
return cqn4sql(q, this.model)
|
|
302
350
|
}
|
|
303
351
|
|
|
@@ -330,7 +378,6 @@ class SQLService extends DatabaseService {
|
|
|
330
378
|
* @interface
|
|
331
379
|
*/
|
|
332
380
|
class PreparedStatement {
|
|
333
|
-
|
|
334
381
|
/**
|
|
335
382
|
* Executes a prepared DML query, i.e., INSERT, UPDATE, DELETE, CREATE, DROP
|
|
336
383
|
* @abstract
|
|
@@ -380,9 +427,7 @@ const _target_name4 = q => {
|
|
|
380
427
|
q.UPDATE?.entity ||
|
|
381
428
|
q.DELETE?.from ||
|
|
382
429
|
q.CREATE?.entity ||
|
|
383
|
-
q.DROP?.entity
|
|
384
|
-
q.STREAM?.from ||
|
|
385
|
-
q.STREAM?.into
|
|
430
|
+
q.DROP?.entity
|
|
386
431
|
if (target?.SET?.op === 'union') throw new cds.error('UNION-based queries are not supported')
|
|
387
432
|
if (!target?.ref) return target
|
|
388
433
|
const [first] = target.ref
|
|
@@ -401,8 +446,11 @@ const _unquirked = q => {
|
|
|
401
446
|
return q
|
|
402
447
|
}
|
|
403
448
|
|
|
404
|
-
|
|
405
|
-
|
|
449
|
+
const sqls = new (class extends SQLService {
|
|
450
|
+
get factory() {
|
|
451
|
+
return null
|
|
452
|
+
}
|
|
453
|
+
})()
|
|
406
454
|
cds.extend(cds.ql.Query).with(
|
|
407
455
|
class {
|
|
408
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
|