@cap-js/db-service 1.0.1 → 1.2.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 +29 -2
- package/index.js +16 -2
- package/lib/InsertResults.js +22 -4
- package/lib/SQLService.js +169 -73
- package/lib/common/DatabaseService.js +93 -88
- package/lib/common/factory.d.ts +5 -0
- package/lib/common/generic-pool.js +34 -0
- package/lib/common/session-context.js +32 -0
- package/lib/converters.d.ts +24 -0
- package/lib/cql-functions.js +205 -5
- package/lib/cqn2sql.js +463 -154
- package/lib/cqn4sql.js +176 -71
- package/lib/deep-queries.js +31 -3
- package/lib/fill-in-keys.js +15 -4
- package/lib/infer/cqn.d.ts +45 -0
- package/lib/infer/index.js +128 -31
- package/lib/infer/join-tree.js +64 -19
- package/package.json +17 -8
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
|
+
## Version 1.2.0 - 2023-09-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- support for calculated elements on read. #113 #123
|
|
12
|
+
- support for managed associations with default values. #193
|
|
13
|
+
- introduced new operator `==` which translates to `IS NOT DISTINCT FROM`. #164
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- resolved a type error which occured in some cases for deeply nested `expand`s. #173
|
|
18
|
+
- path expression traversing non-foreign-key fields within infix filters are now properly rejected for `exists` predicates. #181
|
|
19
|
+
- CQL functions: In the `args` of the `concat` function an `xpr` is now wrapped in parentheses. #196
|
|
20
|
+
- Make `UPDATE` and `ofarray` typed column compatible. #184
|
|
21
|
+
- Ensure that `INSERT` with `rows` always inserts into the correct column. #193
|
|
22
|
+
- Allow `DateTime` columns to compare against their returned value. #206
|
|
23
|
+
- Deep Insert using backlink associations as key #199.
|
|
24
|
+
|
|
25
|
+
## Version 1.1.0 - 2023-08-01
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- `UPDATE` with path expressions do not end up in a dump anymore. Instead, a proper error message is emitted.
|
|
30
|
+
- `UPDATE` is only noop if it does not include an element annotated with `@cds.on.update`.
|
|
31
|
+
- `SELECT` with `'*'` that is not expanded creates now a clearer error when the column name is required.
|
|
32
|
+
- `SELECT` with plain SQL statements will return correct result regardless of casing.
|
|
33
|
+
- View resolving for streams.
|
|
34
|
+
|
|
7
35
|
## Version 1.0.1 - 2023-07-03
|
|
8
36
|
|
|
9
37
|
### Fixed
|
|
@@ -12,7 +40,6 @@
|
|
|
12
40
|
are now correctly substituted.
|
|
13
41
|
- Mapping for OData `average` function to ANSI SQL compliant `avg` function.
|
|
14
42
|
|
|
15
|
-
|
|
16
43
|
## Version 1.0.0 - 2023-06-23
|
|
17
44
|
|
|
18
|
-
- Initial Release
|
|
45
|
+
- Initial Release
|
package/index.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
const DatabaseService = require('./lib/common/DatabaseService')
|
|
2
|
+
const SQLService = require('./lib/SQLService')
|
|
3
|
+
const CQN2SQL = require('./lib/cqn2sql').classDefinition
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @template T
|
|
7
|
+
* @typedef {import('./lib/common/factory').Factory<T>} Factory
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('./lib/SQLService').prototype.PreparedStatement} PreparedStatement
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
module.exports = {
|
|
2
|
-
DatabaseService
|
|
3
|
-
SQLService
|
|
15
|
+
DatabaseService,
|
|
16
|
+
SQLService,
|
|
17
|
+
CQN2SQL,
|
|
4
18
|
}
|
package/lib/InsertResults.js
CHANGED
|
@@ -12,12 +12,17 @@ const USAGE_SAMPLE = async () => {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
module.exports = class InsertResult {
|
|
15
|
+
/**
|
|
16
|
+
* @param {import('@sap/cds/apis/cqn').INSERT} query
|
|
17
|
+
* @param {unknown[]} results
|
|
18
|
+
*/
|
|
15
19
|
constructor(query, results) {
|
|
16
|
-
|
|
20
|
+
// Storing query as non-enumerable property to avoid polluting trace output
|
|
21
|
+
Object.defineProperty(this, 'query', { value: query })
|
|
17
22
|
this.results = results
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
/**
|
|
21
26
|
* Lazy access to auto-generated keys.
|
|
22
27
|
*/
|
|
23
28
|
get [iterator]() {
|
|
@@ -70,8 +75,9 @@ module.exports = class InsertResult {
|
|
|
70
75
|
})
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
|
|
78
|
+
/**
|
|
74
79
|
* the number of inserted (root) entries or the number of affectedRows in case of INSERT into SELECT
|
|
80
|
+
* @return {number}
|
|
75
81
|
*/
|
|
76
82
|
get affectedRows() {
|
|
77
83
|
const { INSERT: _ } = this.query
|
|
@@ -79,16 +85,28 @@ module.exports = class InsertResult {
|
|
|
79
85
|
else return (super.affectedRows = _.entries?.length || _.rows?.length || this.results.length || 1)
|
|
80
86
|
}
|
|
81
87
|
|
|
82
|
-
|
|
88
|
+
/**
|
|
83
89
|
* for checks such as res > 2
|
|
90
|
+
* @return {number}
|
|
84
91
|
*/
|
|
85
92
|
valueOf() {
|
|
86
93
|
return this.affectedRows
|
|
87
94
|
}
|
|
88
95
|
|
|
96
|
+
/**
|
|
97
|
+
* The last id of the auto incremented key column
|
|
98
|
+
* @param {unknown[]} result
|
|
99
|
+
* @returns {number}
|
|
100
|
+
*/
|
|
89
101
|
insertedRowId4(result) {
|
|
90
102
|
return result.lastID
|
|
91
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Number of affected rows
|
|
107
|
+
* @param {unknown[]} result
|
|
108
|
+
* @returns {number}
|
|
109
|
+
*/
|
|
92
110
|
affectedRows4(result) {
|
|
93
111
|
return result.changes
|
|
94
112
|
}
|
package/lib/SQLService.js
CHANGED
|
@@ -4,7 +4,21 @@ const { resolveView } = require('@sap/cds/libx/_runtime/common/utils/resolveView
|
|
|
4
4
|
const DatabaseService = require('./common/DatabaseService')
|
|
5
5
|
const cqn4sql = require('./cqn4sql')
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @callback next
|
|
9
|
+
* @param {Error} param0
|
|
10
|
+
* @returns {Promise<unknown>}
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @callback Handler
|
|
15
|
+
* @param {import('@sap/cds/apis/services').Request} param0
|
|
16
|
+
* @param {next} param1
|
|
17
|
+
* @returns {Promise<unknown>}
|
|
18
|
+
*/
|
|
19
|
+
|
|
7
20
|
class SQLService extends DatabaseService {
|
|
21
|
+
|
|
8
22
|
init() {
|
|
9
23
|
this.on(['SELECT'], this.transformStreamFromCQN)
|
|
10
24
|
this.on(['UPDATE'], this.transformStreamIntoCQN)
|
|
@@ -21,6 +35,7 @@ class SQLService extends DatabaseService {
|
|
|
21
35
|
return super.init()
|
|
22
36
|
}
|
|
23
37
|
|
|
38
|
+
/** @type {Handler} */
|
|
24
39
|
async transformStreamFromCQN({ query }, next) {
|
|
25
40
|
if (!query._streaming) return next()
|
|
26
41
|
const cqn = STREAM.from(query.SELECT.from).column(query.SELECT.columns[0].ref[0])
|
|
@@ -29,6 +44,7 @@ class SQLService extends DatabaseService {
|
|
|
29
44
|
return stream && { value: stream }
|
|
30
45
|
}
|
|
31
46
|
|
|
47
|
+
/** @type {Handler} */
|
|
32
48
|
async transformStreamIntoCQN({ query, data, target }, next) {
|
|
33
49
|
let col, type, etag
|
|
34
50
|
const elements = query._target?.elements || target?.elements
|
|
@@ -56,78 +72,107 @@ class SQLService extends DatabaseService {
|
|
|
56
72
|
return result
|
|
57
73
|
}
|
|
58
74
|
|
|
59
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Handler for SELECT
|
|
77
|
+
* @type {Handler}
|
|
78
|
+
*/
|
|
60
79
|
async onSELECT({ query, data }) {
|
|
61
|
-
// REVISIT: disable this for queries like (SELECT 1)
|
|
62
|
-
// Will return multiple rows with objects inside
|
|
63
|
-
query.SELECT.expand = 'root'
|
|
64
80
|
const { sql, values, cqn } = this.cqn2sql(query, data)
|
|
65
81
|
let ps = await this.prepare(sql)
|
|
66
82
|
let rows = await ps.all(values)
|
|
67
83
|
if (rows.length)
|
|
68
84
|
if (cqn.SELECT.expand) rows = rows.map(r => (typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r))
|
|
69
85
|
if (cqn.SELECT.count) rows.$count = await this.count(query, rows)
|
|
70
|
-
return cqn.SELECT.one || query.SELECT.from
|
|
86
|
+
return cqn.SELECT.one || query.SELECT.from?.ref?.[0].cardinality?.max === 1 ? rows[0] : rows
|
|
71
87
|
}
|
|
72
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Handler for INSERT
|
|
91
|
+
* @type {Handler}
|
|
92
|
+
*/
|
|
73
93
|
async onINSERT({ query, data }) {
|
|
74
94
|
const { sql, entries, cqn } = this.cqn2sql(query, data)
|
|
75
|
-
if (!sql) return // Do nothing when there is nothing to be done
|
|
95
|
+
if (!sql) return // Do nothing when there is nothing to be done // REVISIT: fix within mtxs
|
|
76
96
|
const ps = await this.prepare(sql)
|
|
77
97
|
const results = entries ? await Promise.all(entries.map(e => ps.run(e))) : await ps.run()
|
|
78
98
|
return new this.class.InsertResults(cqn, results)
|
|
79
99
|
}
|
|
80
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Handler for UPSERT
|
|
103
|
+
* @type {Handler}
|
|
104
|
+
*/
|
|
81
105
|
async onUPSERT({ query, data }) {
|
|
82
106
|
const { sql, entries } = this.cqn2sql(query, data)
|
|
83
|
-
if (!sql) return // Do nothing when there is nothing to be done
|
|
107
|
+
if (!sql) return // Do nothing when there is nothing to be done // REVISIT: When does this happen?
|
|
84
108
|
const ps = await this.prepare(sql)
|
|
85
109
|
const results = entries ? await Promise.all(entries.map(e => ps.run(e))) : await ps.run()
|
|
86
|
-
|
|
110
|
+
// REVISIT: results isn't an array, when no entries -> how could that work? when do we have no entries?
|
|
111
|
+
return results.reduce((total, affectedRows) => (total += affectedRows.changes), 0)
|
|
87
112
|
}
|
|
88
113
|
|
|
89
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Handler for UPDATE
|
|
116
|
+
* @type {Handler}
|
|
117
|
+
*/
|
|
90
118
|
async onUPDATE(req) {
|
|
91
|
-
if
|
|
119
|
+
// noop if not a touch for @cds.on.update
|
|
120
|
+
if (
|
|
121
|
+
!req.query.UPDATE.data &&
|
|
122
|
+
!req.query.UPDATE.with &&
|
|
123
|
+
!Object.values(req.target?.elements || {}).some(e => e['@cds.on.update'])
|
|
124
|
+
)
|
|
125
|
+
return 0
|
|
92
126
|
return this.onSIMPLE(req)
|
|
93
127
|
}
|
|
94
128
|
|
|
95
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* Handler for Stream
|
|
131
|
+
* @type {Handler}
|
|
132
|
+
*/
|
|
96
133
|
async onSTREAM(req) {
|
|
97
|
-
const { sql, values
|
|
134
|
+
const { one, sql, values } = this.cqn2sql(req.query)
|
|
98
135
|
// writing stream
|
|
99
136
|
if (req.query.STREAM.into) {
|
|
100
|
-
const stream = entries[0]
|
|
101
|
-
stream.on('error', () => stream.removeAllListeners('error'))
|
|
102
|
-
values.unshift(stream)
|
|
103
137
|
const ps = await this.prepare(sql)
|
|
104
138
|
return (await ps.run(values)).changes
|
|
105
139
|
}
|
|
106
140
|
// reading stream
|
|
107
141
|
const ps = await this.prepare(sql)
|
|
108
|
-
|
|
109
|
-
if (result.length === 0) return
|
|
110
|
-
|
|
111
|
-
return Object.values(result[0])[0]
|
|
142
|
+
return ps.stream(values, one)
|
|
112
143
|
}
|
|
113
144
|
|
|
114
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Handler for CREATE, DROP, UPDATE, DELETE, with simple CQN
|
|
147
|
+
* @type {Handler}
|
|
148
|
+
*/
|
|
115
149
|
async onSIMPLE({ query, data }) {
|
|
116
150
|
const { sql, values } = this.cqn2sql(query, data)
|
|
117
151
|
let ps = await this.prepare(sql)
|
|
118
152
|
return (await ps.run(values)).changes
|
|
119
153
|
}
|
|
120
154
|
|
|
121
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Handler for BEGIN, COMMIT, ROLLBACK, which don't have any CQN
|
|
157
|
+
* @type {Handler}
|
|
158
|
+
*/
|
|
122
159
|
async onEVENT({ event }) {
|
|
123
160
|
DEBUG?.(event) // in the other cases above DEBUG happens in cqn2sql
|
|
124
161
|
return await this.exec(event)
|
|
125
162
|
}
|
|
126
163
|
|
|
127
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Handler for SQL statements which don't have any CQN
|
|
166
|
+
* @type {Handler}
|
|
167
|
+
*/
|
|
128
168
|
async onPlainSQL({ query, data }, next) {
|
|
129
169
|
if (typeof query === 'string') {
|
|
130
|
-
|
|
170
|
+
// REVISIT: this is a hack the target of $now might not be a timestamp or date time
|
|
171
|
+
// Add input converter to CURRENT_TIMESTAMP inside views using $now
|
|
172
|
+
if(/^CREATE VIEW.* CURRENT_TIMESTAMP[( ]/is.test(query)) {
|
|
173
|
+
query = query.replace(/CURRENT_TIMESTAMP/gi, 'ISO(CURRENT_TIMESTAMP)')
|
|
174
|
+
}
|
|
175
|
+
DEBUG?.(query, data)
|
|
131
176
|
const ps = await this.prepare(query)
|
|
132
177
|
const exec = this.hasResults(query) ? d => ps.all(d) : d => ps.run(d)
|
|
133
178
|
if (Array.isArray(data) && typeof data[0] === 'object') return await Promise.all(data.map(exec))
|
|
@@ -135,12 +180,20 @@ class SQLService extends DatabaseService {
|
|
|
135
180
|
} else return next()
|
|
136
181
|
}
|
|
137
182
|
|
|
138
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Override in subclasses to detect more statements to be called with ps.all()
|
|
185
|
+
* @param {string} sql
|
|
186
|
+
*/
|
|
139
187
|
hasResults(sql) {
|
|
140
|
-
return /^(SELECT|WITH|CALL|PRAGMA table_info)
|
|
188
|
+
return /^(SELECT|WITH|CALL|PRAGMA table_info)/i.test(sql)
|
|
141
189
|
}
|
|
142
190
|
|
|
143
|
-
/**
|
|
191
|
+
/**
|
|
192
|
+
* Derives and executes a query to fill in `$count` for given query
|
|
193
|
+
* @param {import('@sap/cds/apis/cqn').SELECT} query - SELECT CQN
|
|
194
|
+
* @param {unknown[]} ret - Results of the original query
|
|
195
|
+
* @returns {Promise<number>}
|
|
196
|
+
*/
|
|
144
197
|
async count(query, ret) {
|
|
145
198
|
if (ret) {
|
|
146
199
|
const { one, limit: _ } = query.SELECT,
|
|
@@ -148,8 +201,9 @@ class SQLService extends DatabaseService {
|
|
|
148
201
|
const [max, offset = 0] = one ? [1] : _ ? [_.rows?.val, _.offset?.val] : []
|
|
149
202
|
if (max === undefined || (n < max && (n || !offset))) return n + offset
|
|
150
203
|
}
|
|
204
|
+
// REVISIT: made uppercase count because of HANA reserved word quoting
|
|
151
205
|
const cq = cds.ql.clone(query, {
|
|
152
|
-
columns: [{ func: 'count' }],
|
|
206
|
+
columns: [{ func: 'count', as: 'COUNT' }],
|
|
153
207
|
localized: false,
|
|
154
208
|
expand: false,
|
|
155
209
|
limit: 0,
|
|
@@ -157,10 +211,14 @@ class SQLService extends DatabaseService {
|
|
|
157
211
|
})
|
|
158
212
|
const { sql, values } = this.cqn2sql(cq)
|
|
159
213
|
const ps = await this.prepare(sql)
|
|
160
|
-
const { count } = await ps.get(values)
|
|
161
|
-
return count
|
|
214
|
+
const { count, COUNT } = await ps.get(values)
|
|
215
|
+
return count ?? COUNT
|
|
162
216
|
}
|
|
163
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Helper class for results of INSERTs.
|
|
220
|
+
* Subclasses may override this.
|
|
221
|
+
*/
|
|
164
222
|
static InsertResults = require('./InsertResults')
|
|
165
223
|
|
|
166
224
|
/**
|
|
@@ -168,71 +226,110 @@ class SQLService extends DatabaseService {
|
|
|
168
226
|
* Subclasses commonly override this.
|
|
169
227
|
*/
|
|
170
228
|
static CQN2SQL = require('./cqn2sql').class
|
|
171
|
-
|
|
172
|
-
|
|
229
|
+
|
|
230
|
+
/** @param {unknown[]} args */
|
|
231
|
+
constructor(...args) {
|
|
232
|
+
super(...args)
|
|
233
|
+
/** @type {unknown} */
|
|
173
234
|
this.class = new.target // for IntelliSense
|
|
174
235
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @param {import('@sap/cds/apis/cqn').Query} query
|
|
239
|
+
* @param {unknown} values
|
|
240
|
+
* @returns {typeof SQLService.CQN2SQL}
|
|
241
|
+
*/
|
|
242
|
+
cqn2sql(query, values) {
|
|
243
|
+
let q = this.cqn4sql(query)
|
|
244
|
+
if (q.SELECT && q.elements) q.SELECT.expand = q.SELECT.expand ?? 'root'
|
|
245
|
+
|
|
246
|
+
let cmd = q.cmd || Object.keys(q)[0]
|
|
247
|
+
if (cmd in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 } || q.STREAM?.into) {
|
|
248
|
+
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?
|
|
249
|
+
let target = q[cmd]._transitions?.[0].target
|
|
250
|
+
if (target) q.target = target // REVISIT: Why isn't that done in resolveView?
|
|
186
251
|
}
|
|
187
|
-
|
|
252
|
+
let cqn2sql = new this.class.CQN2SQL(this)
|
|
253
|
+
return cqn2sql.render(q, values)
|
|
188
254
|
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @param {import('@sap/cds/apis/cqn').Query} q
|
|
258
|
+
* @returns {import('./infer/cqn').Query}
|
|
259
|
+
*/
|
|
189
260
|
cqn4sql(q) {
|
|
190
|
-
|
|
191
|
-
if (!q.SELECT?.from?.join && !this.model?.definitions[_target_name4(q)]) return _unquirked(q)
|
|
261
|
+
if (!q.SELECT?.from?.join && !q.SELECT?.from?.SELECT && !this.model?.definitions[_target_name4(q)]) return _unquirked(q)
|
|
192
262
|
return cqn4sql(q, this.model)
|
|
193
263
|
}
|
|
194
264
|
|
|
195
265
|
/**
|
|
196
266
|
* Returns a Promise which resolves to a prepared statement object with
|
|
197
267
|
* `{run,get,all}` signature as specified in {@link PreparedStatement}.
|
|
268
|
+
* @abstract
|
|
269
|
+
* @param {string} sql The SQL String to be prepared
|
|
198
270
|
* @returns {PreparedStatement}
|
|
199
271
|
*/
|
|
200
|
-
|
|
201
|
-
|
|
272
|
+
async prepare(sql) {
|
|
273
|
+
sql
|
|
202
274
|
throw '2b overridden by subclass'
|
|
203
275
|
}
|
|
204
276
|
|
|
205
277
|
/**
|
|
206
278
|
* Used to execute simple SQL statement like BEGIN, COMMIT, ROLLBACK
|
|
279
|
+
* @param {string} sql
|
|
280
|
+
* @returns {Promise<unknown>} The result of the query
|
|
207
281
|
*/
|
|
208
|
-
// eslint-disable-next-line no-unused-vars
|
|
209
282
|
async exec(sql) {
|
|
283
|
+
sql
|
|
210
284
|
throw '2b overridden by subclass'
|
|
211
285
|
}
|
|
212
286
|
}
|
|
213
287
|
|
|
214
|
-
/**
|
|
288
|
+
/**
|
|
289
|
+
* Interface of prepared statement objects as returned by {@link SQLService#prepare}
|
|
290
|
+
* @class
|
|
291
|
+
* @interface
|
|
292
|
+
*/
|
|
215
293
|
class PreparedStatement {
|
|
216
|
-
|
|
294
|
+
|
|
217
295
|
/**
|
|
218
296
|
* Executes a prepared DML query, i.e., INSERT, UPDATE, DELETE, CREATE, DROP
|
|
219
|
-
* @
|
|
297
|
+
* @abstract
|
|
298
|
+
* @param {unknown|unknown[]} binding_params
|
|
220
299
|
*/
|
|
221
|
-
async run(
|
|
300
|
+
async run(binding_params) {
|
|
301
|
+
binding_params
|
|
302
|
+
return 0
|
|
303
|
+
}
|
|
222
304
|
/**
|
|
223
305
|
* Executes a prepared SELECT query and returns a single/first row only
|
|
224
|
-
* @
|
|
306
|
+
* @abstract
|
|
307
|
+
* @param {unknown|unknown[]} binding_params
|
|
308
|
+
* @returns {Promise<unknown>}
|
|
225
309
|
*/
|
|
226
|
-
async get(
|
|
310
|
+
async get(binding_params) {
|
|
311
|
+
binding_params
|
|
227
312
|
return {}
|
|
228
|
-
}
|
|
313
|
+
}
|
|
229
314
|
/**
|
|
230
315
|
* Executes a prepared SELECT query and returns an array of all rows
|
|
231
|
-
* @
|
|
316
|
+
* @abstract
|
|
317
|
+
* @param {unknown|unknown[]} binding_params
|
|
318
|
+
* @returns {Promise<unknown[]>}
|
|
232
319
|
*/
|
|
233
|
-
async all(
|
|
320
|
+
async all(binding_params) {
|
|
321
|
+
binding_params
|
|
234
322
|
return [{}]
|
|
235
|
-
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Executes a prepared SELECT query and returns a stream of the result
|
|
326
|
+
* @abstract
|
|
327
|
+
* @param {unknown|unknown[]} binding_params
|
|
328
|
+
* @returns {ReadableStream<string|Buffer>} A stream of the result
|
|
329
|
+
*/
|
|
330
|
+
async stream(binding_params) {
|
|
331
|
+
binding_params
|
|
332
|
+
}
|
|
236
333
|
}
|
|
237
334
|
SQLService.prototype.PreparedStatement = PreparedStatement
|
|
238
335
|
|
|
@@ -246,29 +343,26 @@ const _target_name4 = q => {
|
|
|
246
343
|
q.CREATE?.entity ||
|
|
247
344
|
q.DROP?.entity ||
|
|
248
345
|
q.STREAM?.from ||
|
|
249
|
-
q.STREAM?.into
|
|
250
|
-
|
|
251
|
-
if (target?.SET?.op === 'union') throw new cds.error('”UNION” based queries are not supported')
|
|
346
|
+
q.STREAM?.into
|
|
347
|
+
if (target?.SET?.op === 'union') throw new cds.error('UNION-based queries are not supported')
|
|
252
348
|
if (!target?.ref) return target
|
|
253
349
|
const [first] = target.ref
|
|
254
350
|
return first.id || first
|
|
255
351
|
}
|
|
256
352
|
|
|
257
353
|
const _unquirked = q => {
|
|
258
|
-
if (
|
|
259
|
-
if (typeof q.
|
|
260
|
-
if (typeof q.
|
|
261
|
-
if (typeof q.
|
|
262
|
-
if (typeof q.
|
|
263
|
-
if (typeof q.
|
|
354
|
+
if (!q) return q
|
|
355
|
+
else if (typeof q.SELECT?.from === 'string') q.SELECT.from = { ref: [q.SELECT.from] }
|
|
356
|
+
else if (typeof q.INSERT?.into === 'string') q.INSERT.into = { ref: [q.INSERT.into] }
|
|
357
|
+
else if (typeof q.UPSERT?.into === 'string') q.UPSERT.into = { ref: [q.UPSERT.into] }
|
|
358
|
+
else if (typeof q.UPDATE?.entity === 'string') q.UPDATE.entity = { ref: [q.UPDATE.entity] }
|
|
359
|
+
else if (typeof q.DELETE?.from === 'string') q.DELETE.from = { ref: [q.DELETE.from] }
|
|
360
|
+
else if (typeof q.CREATE?.entity === 'string') q.CREATE.entity = { ref: [q.CREATE.entity] }
|
|
361
|
+
else if (typeof q.DROP?.entity === 'string') q.DROP.entity = { ref: [q.DROP.entity] }
|
|
264
362
|
return q
|
|
265
363
|
}
|
|
266
364
|
|
|
267
|
-
const sqls = new
|
|
268
|
-
get factory() {
|
|
269
|
-
return null
|
|
270
|
-
}
|
|
271
|
-
})()
|
|
365
|
+
const sqls = new class extends SQLService { get factory() { return null } }
|
|
272
366
|
cds.extend(cds.ql.Query).with(
|
|
273
367
|
class {
|
|
274
368
|
forSQL() {
|
|
@@ -276,6 +370,7 @@ cds.extend(cds.ql.Query).with(
|
|
|
276
370
|
return this.flat(cqn)
|
|
277
371
|
}
|
|
278
372
|
toSQL() {
|
|
373
|
+
if (this.SELECT) this.SELECT.expand = 'root' // Enforces using json functions always for top-level SELECTS
|
|
279
374
|
let { sql, values } = (cds.db || sqls).cqn2sql(this)
|
|
280
375
|
return { sql, values } // skipping .cqn property
|
|
281
376
|
}
|
|
@@ -285,4 +380,5 @@ cds.extend(cds.ql.Query).with(
|
|
|
285
380
|
},
|
|
286
381
|
)
|
|
287
382
|
|
|
288
|
-
|
|
383
|
+
Object.assign(SQLService, { _target_name4 })
|
|
384
|
+
module.exports = SQLService
|