@sap/cds 8.5.0 → 8.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 +54 -3
- package/_i18n/i18n.properties +4 -7
- package/eslint.config.mjs +1 -1
- package/lib/compile/etc/properties.js +2 -2
- package/lib/compile/for/java.js +15 -3
- package/lib/compile/for/lean_drafts.js +44 -34
- package/lib/compile/for/nodejs.js +19 -10
- package/lib/compile/minify.js +2 -4
- package/lib/compile/parse.js +106 -72
- package/lib/compile/to/edm.js +19 -9
- package/lib/compile/to/hana.js +25 -21
- package/lib/compile/to/sql.js +15 -8
- package/lib/core/linked-csn.js +10 -4
- package/lib/dbs/cds-deploy.js +2 -2
- package/lib/env/cds-env.js +76 -66
- package/lib/env/defaults.js +1 -0
- package/lib/i18n/bundles.js +2 -1
- package/lib/i18n/localize.js +2 -2
- package/lib/index.js +24 -18
- package/lib/ql/CREATE.js +11 -6
- package/lib/ql/DELETE.js +12 -9
- package/lib/ql/DROP.js +15 -8
- package/lib/ql/INSERT.js +19 -14
- package/lib/ql/SELECT.js +95 -168
- package/lib/ql/UPDATE.js +23 -14
- package/lib/ql/UPSERT.js +15 -2
- package/lib/ql/Whereable.js +44 -118
- package/lib/ql/cds-ql.js +222 -28
- package/lib/ql/{Query.js → cds.ql-Query.js} +52 -41
- package/lib/ql/cds.ql-predicates.js +133 -0
- package/lib/ql/cds.ql-projections.js +111 -0
- package/lib/ql/cqn.d.ts +146 -0
- package/lib/srv/cds-connect.js +3 -3
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/cds.Service.js +132 -0
- package/lib/srv/{srv-api.js → cds.ServiceClient.js} +16 -71
- package/lib/srv/cds.ServiceProvider.js +20 -0
- package/lib/srv/factory.js +20 -8
- package/lib/srv/protocols/hcql.js +2 -3
- package/lib/srv/protocols/index.js +3 -3
- package/lib/srv/srv-dispatch.js +7 -6
- package/lib/srv/srv-handlers.js +103 -113
- package/lib/srv/srv-methods.js +14 -14
- package/lib/srv/srv-tx.js +5 -3
- package/lib/utils/cds-utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +3 -3
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/service.js +25 -0
- package/libx/_runtime/common/generic/auth/index.js +5 -0
- package/libx/_runtime/common/generic/auth/restrict.js +36 -14
- package/libx/_runtime/common/generic/auth/service.js +24 -0
- package/libx/_runtime/common/generic/auth/utils.js +14 -6
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/utils/cqn.js +1 -2
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/generateOnCond.js +7 -3
- package/libx/_runtime/common/utils/postProcess.js +4 -1
- package/libx/_runtime/common/utils/restrictions.js +1 -0
- package/libx/_runtime/fiori/lean-draft.js +53 -42
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
- package/libx/_runtime/remote/Service.js +2 -0
- package/libx/_runtime/remote/utils/client.js +12 -0
- package/libx/odata/ODataAdapter.js +2 -1
- package/libx/odata/index.js +5 -3
- package/libx/odata/middleware/batch.js +4 -0
- package/libx/odata/middleware/create.js +2 -2
- package/libx/odata/middleware/delete.js +2 -2
- package/libx/odata/middleware/operation.js +2 -2
- package/libx/odata/middleware/read.js +14 -12
- package/libx/odata/middleware/service-document.js +16 -8
- package/libx/odata/middleware/update.js +2 -2
- package/libx/odata/parse/afterburner.js +64 -30
- package/libx/odata/parse/grammar.peggy +95 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +6 -1
- package/libx/odata/utils/metadata.js +69 -75
- package/libx/odata/utils/postProcess.js +24 -3
- package/package.json +1 -1
- package/server.js +1 -1
- package/lib/ql/parse.js +0 -36
- /package/lib/ql/{infer.js → cds.ql-infer.js} +0 -0
package/lib/ql/SELECT.js
CHANGED
|
@@ -1,89 +1,102 @@
|
|
|
1
|
-
const Whereable = require('./Whereable')
|
|
1
|
+
const Whereable = require('./Whereable')
|
|
2
|
+
const is_number = x => !isNaN(x)
|
|
2
3
|
const cds = require('../index')
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
from:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
4
|
+
const $ = Object.assign
|
|
5
|
+
|
|
6
|
+
class SELECT extends Whereable {
|
|
7
|
+
|
|
8
|
+
/** @type import('./cqn').SELECT['SELECT'] */
|
|
9
|
+
SELECT = {}
|
|
10
|
+
|
|
11
|
+
static call = (..._) => (new this)._select_or_from(..._) // SELECT `x`...
|
|
12
|
+
static API = {
|
|
13
|
+
columns: (..._) => (new this).columns(..._),
|
|
14
|
+
from: $((..._) => (new this).from(..._), {
|
|
15
|
+
localized: (..._) => (new this).localized.from(..._)
|
|
16
|
+
}),
|
|
17
|
+
localized: $((..._) => (new this).localized._select_or_from(..._),{
|
|
18
|
+
columns: (..._) => (new this).localized.columns(..._),
|
|
19
|
+
from: (..._) => (new this).localized.from(..._),
|
|
20
|
+
}),
|
|
21
|
+
distinct: $((..._) => (new this).distinct._select_or_from(..._),{
|
|
22
|
+
localized: (..._) => (new this).distinct.localized.from(..._),
|
|
23
|
+
columns: (..._) => (new this).distinct.columns(..._),
|
|
24
|
+
from: (..._) => (new this).distinct.from(..._),
|
|
25
|
+
}),
|
|
26
|
+
one: $((..._) => (new this).one._select_or_from(..._),{
|
|
27
|
+
localized: (..._) => (new this).one.localized.from(..._),
|
|
28
|
+
columns: (..._) => (new this).one.columns(..._),
|
|
29
|
+
from: (..._) => (new this).one.from(..._),
|
|
30
|
+
}),
|
|
31
|
+
read: (..._) => (new this)._select_or_from(..._),
|
|
32
|
+
class: this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get localized() { this.SELECT.localized = true; return this }
|
|
36
|
+
get distinct() { this.SELECT.distinct = true; return this }
|
|
37
|
+
get one() { this.SELECT.one = true; return this }
|
|
38
|
+
|
|
39
|
+
/** @private */ _select_or_from (x,...etc) {
|
|
40
|
+
if (!x) return this
|
|
41
|
+
else if (x == '*') return this.columns (x,...etc)
|
|
42
|
+
else switch (typeof x) {
|
|
43
|
+
case 'string': {
|
|
44
|
+
if (etc.length) return this._ambiguous (x,...etc)
|
|
45
|
+
else (x = [x]).raw = x // single arg -> resolve in case 'object' block below
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this._expected `Argument ${{cols}} to be a valid argument for SELECT.from or .columns`
|
|
57
|
-
}
|
|
47
|
+
case 'object': { // eslint-disable-line no-fallthrough
|
|
48
|
+
if (x.raw) { // TODO: We might add a cache and reuse parsed [cqn,params] ...
|
|
49
|
+
const SELECT = cds.parse._select('',[x,...etc]), {columns:cols} = SELECT
|
|
50
|
+
if (SELECT.from || cols.length > 1 || !cols[0].ref) // SELECT `from Foo` | `a,b` | `max(a)`
|
|
51
|
+
return this._assign (SELECT)
|
|
52
|
+
const { columns:[{ expand, ...from }], ...more_clauses } = SELECT
|
|
53
|
+
if (expand) more_clauses.columns = expand // SELECT `Foo {a,b}`
|
|
54
|
+
if (0 in Object.keys(more_clauses)) // SELECT `Foo where x=1`
|
|
55
|
+
return this._assign ({from}, more_clauses)
|
|
56
|
+
else return this._ambiguous (x,...etc)
|
|
58
57
|
}
|
|
58
|
+
else if (Array.isArray(x)) return this.columns(x)
|
|
59
|
+
else if (x.kind === 'element') return this.columns (x,...etc)
|
|
60
|
+
else if (x.name) return this.from (x,...etc)
|
|
61
|
+
else if (x.ref) return this._ambiguous (x,...etc)
|
|
62
|
+
else if (x.from) return this._assign(x)
|
|
63
|
+
else if (x.SELECT || x.SET) return this.from(x)
|
|
64
|
+
else break
|
|
59
65
|
}
|
|
66
|
+
default: return this.columns(x)
|
|
60
67
|
}
|
|
68
|
+
throw this._expected `Argument for SELECT(${{x}}) to be a valid argument for SELECT.from or .columns`
|
|
69
|
+
}
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
// columns on a subsequent call of .from, if any.
|
|
71
|
+
/** @private */ _ambiguous (...xy) {
|
|
64
72
|
const {SELECT:_} = this, {one} = _
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return this.from (...args) .columns (cols, ...more)
|
|
69
|
-
}}
|
|
73
|
+
return this.from(...xy)._set('from', (...the_real_target) => {
|
|
74
|
+
if (!one) delete _.one; delete _.columns; delete this.from
|
|
75
|
+
return this.from (...the_real_target) .columns (...xy)
|
|
70
76
|
})
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
columns (...cols) {
|
|
74
|
-
if (cols[0]) this._add ('columns',
|
|
80
|
+
if (cols[0]) this._add ('columns', cds.ql.columns(...cols))
|
|
75
81
|
return this
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
from (
|
|
79
|
-
|
|
80
|
-
if (
|
|
84
|
+
from (...args) {
|
|
85
|
+
const [target] = args
|
|
86
|
+
if (target.raw) {
|
|
87
|
+
let { from, ...more } = cds.parse._select ('from',args)
|
|
88
|
+
this.SELECT.from = this._target4 (from)
|
|
89
|
+
return this._assign(more)
|
|
90
|
+
}
|
|
91
|
+
this.SELECT.from = this._target4 (target)
|
|
92
|
+
const [,second,third] = args
|
|
93
|
+
if (second !== undefined) {
|
|
81
94
|
if (third) {
|
|
82
95
|
this.byKey(second)
|
|
83
96
|
this.columns(third)
|
|
84
97
|
} else {
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
if (Array.isArray(second) || typeof second === 'function') this.columns(second)
|
|
99
|
+
else this.byKey(second)
|
|
87
100
|
}
|
|
88
101
|
}
|
|
89
102
|
return this
|
|
@@ -95,19 +108,22 @@ module.exports = class Query extends Whereable {
|
|
|
95
108
|
innerJoin (other, as) { return this.join (other, as, 'inner') }
|
|
96
109
|
join (other, as, kind='inner') {
|
|
97
110
|
const [, target, alias = as] = /(\S+)(?:\s+(?:as)?\s+(\S+))?/i.exec(other)
|
|
98
|
-
const ref = { ref: [target] }
|
|
99
|
-
if (alias) ref.as = alias
|
|
111
|
+
const ref = { ref: [target] }; if (alias) ref.as = alias
|
|
100
112
|
this.SELECT.from = { join:kind, args: [this.SELECT.from, ref] }
|
|
101
113
|
return Object.defineProperty(this, '_where_or_having', { value: 'on', configurable: true })
|
|
102
114
|
}
|
|
103
115
|
on (...args) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
116
|
+
const {from} = this.SELECT
|
|
117
|
+
if (!from?.join) throw new Error(`Invalid call of "SELECT.on()" without prior call of "SELECT.join()"`)
|
|
118
|
+
// string values in on clause are interpreted as refs, not vals
|
|
119
|
+
const [o] = args; if (typeof o === 'object' && !o.raw && !Array.isArray(o)) {
|
|
120
|
+
for (let a in o) if (typeof o[a] === 'string') o[a] = {ref:o[a].split('.')}
|
|
121
|
+
}
|
|
122
|
+
return this._where (args,'on',from)
|
|
107
123
|
}
|
|
108
124
|
|
|
109
125
|
having(...args) {
|
|
110
|
-
return this._where (args,'
|
|
126
|
+
return this._where (args,'having')
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
search (...args) {
|
|
@@ -118,13 +134,13 @@ module.exports = class Query extends Whereable {
|
|
|
118
134
|
|
|
119
135
|
groupBy (...args) {
|
|
120
136
|
if (!args[0]) return this
|
|
121
|
-
const cqn = args[0].raw ?
|
|
137
|
+
const cqn = args[0].raw ? cds.parse._select('from X group by', args).groupBy : args.map(cds.parse.ref)
|
|
122
138
|
return this._add('groupBy',cqn)
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
orderBy (...args) {
|
|
126
142
|
if (!args[0]) return this
|
|
127
|
-
return this._add('orderBy',
|
|
143
|
+
return this._add('orderBy', cds.ql.orders(...args))
|
|
128
144
|
}
|
|
129
145
|
|
|
130
146
|
limit (rows, offset) {
|
|
@@ -153,101 +169,12 @@ module.exports = class Query extends Whereable {
|
|
|
153
169
|
return super.valueOf('SELECT * FROM')
|
|
154
170
|
}
|
|
155
171
|
|
|
156
|
-
get _target_ref(){ return this.SELECT.from }
|
|
172
|
+
/** @private */ get _target_ref(){ return this.SELECT.from }
|
|
157
173
|
|
|
158
174
|
get elements() { return this.elements = cds.infer.elements4 (this.SELECT.columns, this.source) }
|
|
159
175
|
set elements(e) { this._set('elements',e) }
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (x.raw) {
|
|
166
|
-
if (x[0][0] === '{') return SELECT_('from X ',args).columns
|
|
167
|
-
else return SELECT_('from X {',args,'}').columns
|
|
168
|
-
} else {
|
|
169
|
-
if (typeof x === 'string' && x[0] === '{') return parse.cql('SELECT from X '+ x).SELECT.columns
|
|
170
|
-
else return _columns_or_not(x) || args.map(_column_expr)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const _columns_or_not = (x) => {
|
|
175
|
-
if (typeof x === 'function') return _projection4(x)
|
|
176
|
-
if (Array.isArray(x)) return x.map(_column_expr)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const _column_expr = (x) => {
|
|
180
|
-
if (is_cqn(x)) return x
|
|
181
|
-
if (typeof x === 'string') return parse.column(x)
|
|
182
|
-
if (typeof x === 'object') for (let one in x) return Object.assign(parse.expr(one),{as:x[one]})
|
|
183
|
-
else return {val:x}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const _projection4 = (fn) => {
|
|
187
|
-
const columns=[]; fn (new Proxy (fn,{
|
|
188
|
-
apply: (_, __, args) => { // handle top-level projections such as (foo)=>{ foo('*') }
|
|
189
|
-
if (!args.length) return columns.push('*')
|
|
190
|
-
let [x] = Array.isArray(args[0]) ? args[0] : args
|
|
191
|
-
columns.push (x === '*' || x === '.*' ? '*' : is_cqn(x) ? x : {ref:[x]})
|
|
192
|
-
return { as: (alias) => (x.as = alias) }
|
|
193
|
-
},
|
|
194
|
-
get: (_, p) => { // handle top-level paths like (foo)=>{ foo.bar }
|
|
195
|
-
const col = {ref:[p]}; columns.push(col)
|
|
196
|
-
const nested = new Proxy(fn,{ // handle n-fold paths like (foo)=>{ foo.bar.car }
|
|
197
|
-
get: (_, p) => {
|
|
198
|
-
if (p === 'where') return (x) => ((col.where = predicate4([x])), nested)
|
|
199
|
-
if (p === 'as') return (alias) => ((col.as = alias), nested)
|
|
200
|
-
else return col.ref.push(p), nested
|
|
201
|
-
},
|
|
202
|
-
apply: (_, __, args) => { // handle nested projections e.g. (foo)=>{ foo.bar (b=>{ ... }) }
|
|
203
|
-
const [a, b] = args
|
|
204
|
-
if (!a) col.expand = ['*']
|
|
205
|
-
else if (a.raw) switch (a[0]) {
|
|
206
|
-
case '*': col.expand = ['*']; break
|
|
207
|
-
case '.*': col.inline = ['*']; break
|
|
208
|
-
default: Object.assign (col, SELECT_(col.ref.at(-1)+' ', args, ' from X').columns[0])
|
|
209
|
-
}
|
|
210
|
-
else if (Array.isArray(a)) col.expand = _columns(a)
|
|
211
|
-
else if (a === '*') col.expand = ['*']
|
|
212
|
-
else if (a === '.*') col.inline = ['*']
|
|
213
|
-
else if (typeof a === 'string') col.ref.push(a)
|
|
214
|
-
else if (typeof a === 'function') {
|
|
215
|
-
let x = (col[/^\(?_\b/.test(a) ? 'inline' : 'expand'] = _projection4(a))
|
|
216
|
-
if (b?.levels) while (--b.levels) x.push({ ...col, expand: (x = [...x]) })
|
|
217
|
-
} else if (typeof b === 'function') {
|
|
218
|
-
let x = (col[/^\(?_\b/.test(b) ? 'inline' : 'expand'] = _projection4(b))
|
|
219
|
-
if (a?.depth) while (--a.depth) x.push({ ...col, expand: (x = [...x]) })
|
|
220
|
-
}
|
|
221
|
-
return nested
|
|
222
|
-
},
|
|
223
|
-
})
|
|
224
|
-
return nested
|
|
225
|
-
},
|
|
226
|
-
}))
|
|
227
|
-
return columns
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const _order_by = (args) => {
|
|
231
|
-
if (args[0].raw) return SELECT_('from X order by', args).orderBy
|
|
232
|
-
if (Array.isArray(args[0])) args = args[0]
|
|
233
|
-
const cqn=[], _add = (ref,ad) => {
|
|
234
|
-
const obx = parse.expr(ref); cqn.push(obx)
|
|
235
|
-
if (ad) obx.sort = ad == 1 ? 'asc' : ad == -1 ? 'desc' : ad
|
|
236
|
-
}
|
|
237
|
-
for (let each of args) {
|
|
238
|
-
if (each.ref) cqn.push(each)
|
|
239
|
-
else if (typeof each === 'string') _add (...each.split(' '))
|
|
240
|
-
else for (let ref in each) _add (ref, each[ref])
|
|
241
|
-
}
|
|
242
|
-
return cqn
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const {CQL} = parse, SELECT_ = (prefix, [ strings, ...more ], suffix) => {
|
|
246
|
-
const tts = [...strings]; tts.raw = true
|
|
247
|
-
if (prefix) tts[0] = `SELECT ${prefix} ${tts[0]}`
|
|
248
|
-
if (suffix) tts[tts.length-1] += ` ${suffix}`
|
|
249
|
-
return CQL(tts,...more).SELECT
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const is_cqn = x => x === undefined || x === '*' || x.val !== undefined || x.xpr || x.ref || x.list || x.func || x.SELECT
|
|
253
|
-
const is_number = x => !isNaN(x)
|
|
179
|
+
/** @type SELECT.API & (...columns:string[]) => SELECT */
|
|
180
|
+
module.exports = SELECT.init()
|
package/lib/ql/UPDATE.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
const Whereable = require('./Whereable')
|
|
2
|
-
const
|
|
2
|
+
const cds = require('../index')
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
class UPDATE extends Whereable {
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
/** @type import('./cqn').UPDATE['UPDATE'] */
|
|
7
|
+
UPDATE = {}
|
|
8
|
+
|
|
9
|
+
static call = (..._) => (new this).entity(..._)
|
|
10
|
+
static API = { class:this,
|
|
11
|
+
entity: (..._) => (new this).entity(..._)
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
entity (e,
|
|
14
|
-
this.UPDATE.entity = this._target4 (...
|
|
15
|
-
if (
|
|
14
|
+
entity (e, ...etc) {
|
|
15
|
+
this.UPDATE.entity = this._target4 (e, ...etc) // supporting tts
|
|
16
|
+
if (!e.raw && etc.length) this.byKey(etc[0])
|
|
16
17
|
return this
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -30,8 +31,7 @@ module.exports = class Query extends Whereable {
|
|
|
30
31
|
|
|
31
32
|
// A tagged template string with a single expression, e.g. .with `my.stock -= 1`
|
|
32
33
|
if (args[0].raw) {
|
|
33
|
-
|
|
34
|
-
_add (this, lhs.ref.join('.'), op, ...rhs)
|
|
34
|
+
_add (this, ..._parse_set_expr (this, String.raw(...args)))
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Alternating expr fragment / values args, e.g. .with ('my.stock -=',1, 'lastOrder =', '$now')
|
|
@@ -45,8 +45,7 @@ module.exports = class Query extends Whereable {
|
|
|
45
45
|
// A single string with comma-separated expressions, e.g. .with ('my.stock -= 1, lastOrder = $now')
|
|
46
46
|
else if (typeof args[0] === 'string') {
|
|
47
47
|
for (let each of _comma_separated_exprs(args[0])) {
|
|
48
|
-
|
|
49
|
-
_add (this, lhs.ref.join('.'), op, ...rhs)
|
|
48
|
+
_add (this, ..._parse_set_expr (this, each))
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
@@ -97,4 +96,14 @@ const _comma_separated_exprs = (s) => {
|
|
|
97
96
|
return all
|
|
98
97
|
}
|
|
99
98
|
|
|
99
|
+
const _parse_set_expr = (q,expr) => {
|
|
100
|
+
const [,lhs,rhs] = /^\s*(\w+)\s*=\s*(.*)/.exec(expr)
|
|
101
|
+
|| q._expected `${{expr}} to contain expressions of form 'column = <expr>'`
|
|
102
|
+
return [ lhs, '=', cds.parse.expr(rhs) ]
|
|
103
|
+
}
|
|
104
|
+
|
|
100
105
|
const operators = { '=':1, '-=':2, '+=':2, '*=':2, '/=':2, '%=':2 }
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
/** @type UPDATE.API & (...entries:[]) => UPDATE */
|
|
109
|
+
module.exports = UPDATE.init()
|
package/lib/ql/UPSERT.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const { class:INSERT } = require('./INSERT')
|
|
2
|
+
class UPSERT extends INSERT {
|
|
3
|
+
|
|
4
|
+
/** @type import('./cqn').UPSERT['UPSERT'] */
|
|
5
|
+
UPSERT = {}
|
|
6
|
+
|
|
7
|
+
constructor() { delete super().INSERT }
|
|
8
|
+
static call = (..._) => (new this).entries(..._)
|
|
9
|
+
static API = { class:this,
|
|
10
|
+
into: (..._) => (new this).into(..._)
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
}
|
|
14
|
+
|
|
15
|
+
/** @type UPSERT.API & (...entries:[]) => UPSERT */
|
|
16
|
+
module.exports = UPSERT.init()
|
package/lib/ql/Whereable.js
CHANGED
|
@@ -1,131 +1,57 @@
|
|
|
1
|
-
const { error } = require ('../index')
|
|
2
1
|
const cds = require('../index')
|
|
3
|
-
const
|
|
2
|
+
const Query = require('./cds.ql-Query')
|
|
4
3
|
|
|
5
|
-
class Query extends require('./Query') {
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
let left = _[clause]
|
|
23
|
-
if (!left) { //> .where() called first time
|
|
24
|
-
// SELECT.from `X` .where `x or y` .and `z` -> SELECT from X where (x or y) and z
|
|
25
|
-
if (pred.includes('or')) this._left_has_or = true
|
|
26
|
-
_[clause] = pred
|
|
27
|
-
} else { //> .where(), .and(), .or() called successively
|
|
28
|
-
if (_where) {
|
|
29
|
-
// SELECT.from `X` .where `x` .or `y` .where `z` -> SELECT from X where (x or y) and z
|
|
30
|
-
if (left.includes('or')) left = [{xpr:left}]
|
|
31
|
-
} else if (and_or === 'and') {
|
|
32
|
-
// SELECT.from `X` .where `x` .or `y` .and `z` -> SELECT from X where x or y and z
|
|
33
|
-
if (this._left_has_or) { left = [{xpr:left}]; delete this._left_has_or }
|
|
34
|
-
}
|
|
35
|
-
// SELECT.from `X` .where `x` .and `y or z` -> SELECT from X where x and (y or z)
|
|
36
|
-
if (pred.includes('or')) pred = [{xpr:pred}]
|
|
37
|
-
_[clause] = [ ...left, and_or, ...pred ]
|
|
38
|
-
}
|
|
5
|
+
class Whereable extends Query {
|
|
6
|
+
|
|
7
|
+
where (...args) { return this._where (args) }
|
|
8
|
+
and (...args) { return this._and_or ('and',args) }
|
|
9
|
+
or (...args) { return this._and_or ('or',args) }
|
|
10
|
+
|
|
11
|
+
/** @protected */
|
|
12
|
+
_where (args, clause='where', cqn=this._) {
|
|
13
|
+
let tail = cds.ql.predicate(...args); if (!tail?.length) return this
|
|
14
|
+
let head = cqn[clause]
|
|
15
|
+
if (!head) cqn[clause] = tail
|
|
16
|
+
else { //> always wrap ORs on subsequent .where() calls, as these happen somewhere else
|
|
17
|
+
if (head.includes('or')) head = [{xpr:head}]
|
|
18
|
+
if (tail.includes('or')) tail = [{xpr:tail}]
|
|
19
|
+
cqn[clause] = [ ...head, 'and', ...tail ]
|
|
39
20
|
}
|
|
21
|
+
this._clause = this._direct = clause
|
|
40
22
|
return this
|
|
41
23
|
}
|
|
42
24
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
if (args.length === 1 && typeof x === 'object') {
|
|
61
|
-
if (is_array(x)) return x
|
|
62
|
-
if (is_cqn(x)) return args
|
|
63
|
-
else return _object_predicate(args,_clause)
|
|
25
|
+
/** @private */
|
|
26
|
+
_and_or (ao, args) {
|
|
27
|
+
let clause = this._clause ??= ( //> some projects use .and()/.or() with manually filled in .where
|
|
28
|
+
this._.having ? 'having' :
|
|
29
|
+
this._.where ? 'where' :
|
|
30
|
+
this._.from?.on ? 'on' :
|
|
31
|
+
cds.error `Unexpected use of .${ao}() without prior .where(), .having(), or .on()`
|
|
32
|
+
)
|
|
33
|
+
let tail = cds.ql.predicate(...args); if (!tail?.length) return this
|
|
34
|
+
let cqn = clause === 'on' ? this._.from : this._
|
|
35
|
+
let head = cqn[clause], direct = this._direct; delete this._direct
|
|
36
|
+
if (direct && ao === 'and') //> wrap ORs for .and() directly following .where()
|
|
37
|
+
if (head.includes('or')) head = [{xpr:head}]
|
|
38
|
+
if (tail.includes('or')) tail = [{xpr:tail}]
|
|
39
|
+
cqn[clause] = [ ...head, ao, ...tail ]
|
|
40
|
+
return this
|
|
64
41
|
}
|
|
65
|
-
else return _fluid_predicate(args)
|
|
66
|
-
}
|
|
67
42
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
if (k === 'or') {
|
|
78
|
-
pred.push('or', ...predicate4([x],_clause))
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
if (k === 'not') {
|
|
82
|
-
pred.push('not', {xpr:predicate4([x],_clause)})
|
|
83
|
-
continue
|
|
84
|
-
}
|
|
85
|
-
if (k === 'exists') {
|
|
86
|
-
pred.push('and', 'exists', typeof x === 'object' ? x : { ref: x.split('.') })
|
|
87
|
-
continue
|
|
88
|
-
}
|
|
89
|
-
if (k === 'not exists') {
|
|
90
|
-
pred.push('and', 'not', 'exists', typeof x === 'object' ? x : { ref: x.split('.') })
|
|
91
|
-
continue
|
|
92
|
-
}
|
|
93
|
-
if (k === 'in') {
|
|
94
|
-
pred.push('in', val(x))
|
|
95
|
-
continue
|
|
96
|
-
}
|
|
97
|
-
else pred.push('and', parse.expr(k))
|
|
98
|
-
if (!x || x==='*') pred.push('=', {val:x})
|
|
99
|
-
else if (x.SELECT || x.list) pred.push('in', x)
|
|
100
|
-
else if (is_array(x)) pred.push('in', {list:x.map(val)})
|
|
101
|
-
else if (is_cqn(x)) pred.push('=', x)
|
|
102
|
-
else if (x instanceof Buffer) pred.push('=', {val:x})
|
|
103
|
-
else if (x instanceof RegExp) pred.push('like', {val:x})
|
|
104
|
-
else if (x instanceof Date) pred.push('=', {val:x})
|
|
105
|
-
else if (typeof x === 'object') for (let op in x) x[op]?.in ? pred.push(op, ...predicate4([x[op]],_clause)) : pred.push(op, val(x[op])) // REVIST: Should always be proper recursion
|
|
106
|
-
else if (_clause === 'on' && typeof x === 'string') pred.push('=', { ref: x.split('.') })
|
|
107
|
-
else pred.push('=', {val:x})
|
|
43
|
+
byKey (key) {
|
|
44
|
+
if (typeof key !== 'object' || key === null)
|
|
45
|
+
key = cds.env.features.keys_as_val ? { val: key } : { [Object.keys(this._target.keys||{ID:1})[0]]: key }
|
|
46
|
+
if (this.SELECT) this.SELECT.one = true
|
|
47
|
+
if (cds.env.features.keys_into_where) return this.where(key)
|
|
48
|
+
const {ref} = this._target_ref
|
|
49
|
+
ref[ref.length-1] = { id: ref.at(-1), where: cds.ql.predicate(key) }
|
|
50
|
+
return this
|
|
108
51
|
}
|
|
109
|
-
return pred[0] === 'and' ? pred.slice(1) : pred
|
|
110
|
-
}
|
|
111
52
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (args.length % 2 === 0) args.push('')
|
|
115
|
-
const expr = args.filter((_, i) => i % 2 === 0).join(' ? ')
|
|
116
|
-
const vals = args.filter((_, i) => i % 2 === 1)
|
|
117
|
-
const {xpr} = parse.expr(expr)
|
|
118
|
-
;(function _fill_in_vals_into (xpr) { xpr.forEach ((x,i) => {
|
|
119
|
-
if (x.xpr) _fill_in_vals_into (x.xpr)
|
|
120
|
-
if (x.param) xpr[i] = val(vals.shift())
|
|
121
|
-
})})(xpr)
|
|
122
|
-
return xpr
|
|
53
|
+
/** @private */ set _clause (clause) { this._set('_clause',clause) }
|
|
54
|
+
/** @private */ set _direct (clause) { this._set('_direct',clause) }
|
|
123
55
|
}
|
|
124
56
|
|
|
125
|
-
|
|
126
|
-
const val = x => !x ? {val:x} : is_array(x) ? {list:x.map(val)} : is_cqn(x) ? x : {val:x}
|
|
127
|
-
const is_cqn = x => x.val !== undefined || x.xpr || x.ref || x.list || x.func || x.SELECT
|
|
128
|
-
const is_array = Array.isArray
|
|
129
|
-
const operators = { '=':1, '<':2, '<=':2, '>':2, '>=':2, '!=':3, '<>':3, in:4, like:4, IN:4, LIKE:4 }
|
|
130
|
-
|
|
131
|
-
module.exports = Object.assign (Query, { predicate4, parse })
|
|
57
|
+
module.exports = Whereable
|