@sap/cds 8.5.1 → 8.6.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 +56 -3
- package/_i18n/i18n.properties +4 -7
- package/eslint.config.mjs +1 -1
- package/lib/compile/etc/properties.js +12 -9
- 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/json.js +2 -2
- package/lib/compile/to/sql.js +15 -8
- package/lib/core/linked-csn.js +10 -4
- package/lib/dbs/cds-deploy.js +1 -1
- 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/files.js +3 -3
- 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 +54 -43
- 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/index.js +5 -3
- 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 +63 -29
- package/libx/odata/parse/grammar.peggy +95 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +5 -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/cds-ql.js
CHANGED
|
@@ -1,37 +1,231 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
SELECT:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
const cds = require ('../index')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This is the default export, accessible to users through cds.ql.
|
|
5
|
+
* It's a function turning given input into an instance of cds.ql.Query.
|
|
6
|
+
*
|
|
7
|
+
* - If input is already a Query, it is returned as is.
|
|
8
|
+
* - If input is a string, it is parsed into a CQN object.
|
|
9
|
+
* - If input is a CQN object, an instance of the corresponding
|
|
10
|
+
* Query subclass is contructed and returned.
|
|
11
|
+
*
|
|
12
|
+
* Use it in a cast-like way like this:
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* let q = cds.ql ({ SELECT: { from: {ref:[ Books.name ]} }})
|
|
16
|
+
* let q = cds.ql (CQL`SELECT from ${Books}`)
|
|
17
|
+
* let q = cds.ql `SELECT from ${Books}`
|
|
18
|
+
*
|
|
19
|
+
* @returns { import('./SELECT').class }, or any other subclass of Query; but IntelliSense doen't allow to specify this
|
|
20
|
+
*/
|
|
21
|
+
const ql = module.exports = exports = (q,...etc) => {
|
|
22
|
+
if (q instanceof Query) return q
|
|
23
|
+
if (q.raw || typeof q === 'string') return ql (cds.parse.cql(q,...etc))
|
|
24
|
+
for (let k in q) if (k in ql) return new ql[k](q[k])
|
|
25
|
+
return q //> no-op
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
// Base class and subclasses for all kinds of queries...
|
|
29
|
+
const Query = exports.Query = require('./cds.ql-Query');
|
|
30
|
+
exports.SELECT = require('./SELECT')
|
|
31
|
+
exports.INSERT = require('./INSERT')
|
|
32
|
+
exports.UPSERT = require('./UPSERT')
|
|
33
|
+
exports.UPDATE = require('./UPDATE')
|
|
34
|
+
exports.DELETE = require('./DELETE')
|
|
35
|
+
exports.CREATE = require('./CREATE')
|
|
36
|
+
exports.DROP = require('./DROP')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
exports.predicate = require('./cds.ql-predicates')
|
|
40
|
+
exports.columns = require('./cds.ql-projections')
|
|
41
|
+
/** @import cqn from './cqn' */
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Constructs a CXN `{ref}` object from given input, which can be one of:
|
|
46
|
+
*
|
|
47
|
+
* - several path segment strings
|
|
48
|
+
* - a single array of the same
|
|
49
|
+
* - a tagged template literal in CXL path syntax
|
|
50
|
+
*
|
|
51
|
+
* @returns {cqn.ref}
|
|
52
|
+
*/
|
|
53
|
+
exports.ref = function (...ref) {
|
|
54
|
+
if (ref[0].raw) ref = String.raw(...ref).split('.')
|
|
55
|
+
return {ref}
|
|
26
56
|
}
|
|
27
57
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Constructs CXN `{val}` object from given input, which can be one of:
|
|
61
|
+
* - a single `string`, `number`, `boolean`, or `null`
|
|
62
|
+
* - a tagged template literal in CXL literal syntax
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* val(`foo`) //> {val:'foo'}`
|
|
66
|
+
* val`foo` //> {val:'foo'}
|
|
67
|
+
* val`11` //> {val:11}
|
|
68
|
+
* val(11) //> {val:11}
|
|
69
|
+
*
|
|
70
|
+
* @returns {cqn.val}
|
|
71
|
+
*/
|
|
72
|
+
exports.val = (...val) => {
|
|
73
|
+
if (val?.[0]?.raw) val = String.raw(...val)
|
|
74
|
+
else [val] = val
|
|
75
|
+
return {val}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Constructs a CXN `xpr` object from given input, which can be one of:
|
|
81
|
+
*
|
|
82
|
+
* - multiple CXN `expr` objects, or strings representing keywords or operators
|
|
83
|
+
* - a single array of the same
|
|
84
|
+
* - a tagged template literal in CXL syntax
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* xpr([ref`foo`,'=',val(11)]) //> {xpr:[{ref:['foo']},'=',{val:11}]}
|
|
88
|
+
* xpr(ref`foo`,'=',val(11)) //> {xpr:[{ref:['foo']},'=',{val:11}]}
|
|
89
|
+
* xpr`foo = 11` //> {xpr:[{ref:['foo']},'=',{val:11}]}
|
|
90
|
+
* xpr`foo` //> {xpr:[{ref:['foo']}]}
|
|
91
|
+
* xpr`'foo'` //> {xpr:[{val:'foo'}]}
|
|
92
|
+
* xpr`11` //> {xpr:[{val:11}]}
|
|
93
|
+
* xpr('=') //> {xpr:['=']}
|
|
94
|
+
* xpr('like') //> {xpr:['like']}
|
|
95
|
+
*
|
|
96
|
+
* @see {@link ql.expr `expr`}
|
|
97
|
+
* @returns {cqn.xpr}
|
|
98
|
+
*/
|
|
99
|
+
exports.xpr = (...xpr) => {
|
|
100
|
+
const x = ql.expr(...xpr)
|
|
101
|
+
return x.xpr ? x : {xpr:[x]} // always returns an `{xpr}` object
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Same as {@link ql.xpr `xpr`}, but if the result contains only single
|
|
107
|
+
* entries these are returned as is.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* expr([ref`foo`,'=',val(11)]) //> {xpr:[{ref:['foo']},'=',{val:11}]}
|
|
111
|
+
* expr(ref`foo`,'=',val(11)) //> {xpr:[{ref:['foo']},'=',{val:11}]}
|
|
112
|
+
* expr`foo = 11` //> {xpr:[{ref:['foo']},'=',{val:11}]}
|
|
113
|
+
* expr`foo` //> {ref:['foo']}
|
|
114
|
+
* expr`11` //> {val:11}
|
|
115
|
+
*
|
|
116
|
+
* @returns { cqn.ref & cqn.val & cqn.xpr & cqn.list & cqn.func } the constructed CXN `expr` object.
|
|
117
|
+
*/
|
|
118
|
+
exports.expr = (...xpr) => {
|
|
119
|
+
const [x] = xpr; if (x?.raw) return cds.parse.expr(...xpr) //> tagged template literal
|
|
120
|
+
else if (is_array(x)) xpr = x //> entries are supposed to be CXN objects
|
|
121
|
+
return xpr.length === 1 ? xpr[0] : {xpr} //> single entries are returned as is
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Constructs a CXN `list` object from given input, with can be one of:
|
|
127
|
+
*
|
|
128
|
+
* - multiple CXN `expr` objects, or values turned into `{val}`s, including strings
|
|
129
|
+
* - a single array of the same
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* list([`foo`,11]) //> {list:[{val:'foo'},{val:11}]}
|
|
133
|
+
* list(`foo`,11) //> {list:[{val:'foo'},{val:11}]}
|
|
134
|
+
* expr`'foo',11` //> {list:[{val:'foo'},{val:11}]}
|
|
135
|
+
* expr`foo,11` //> {list:[{ref:['foo']},{val:11}]}
|
|
136
|
+
*
|
|
137
|
+
* @see Use {@link ql.expr `expr()`} to get the same via a tagged template literal.
|
|
138
|
+
* @returns {cqn.list}
|
|
139
|
+
*/
|
|
140
|
+
exports.list = (...args) => {
|
|
141
|
+
const [x] = args; if (is_array(x)) args = x
|
|
142
|
+
return { list: args.map (_cqn_or_val) }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Constructs a CXN `func` object from given input. The first argument is the
|
|
148
|
+
* function name, the remaining `args` can the same as in {@link ql.list `list()`},
|
|
149
|
+
* and are handled the same way.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* func('substring',[`foo`,1]) //> {func:'substring',args:[{val:'foo'},{val:1}]}
|
|
153
|
+
* func('substring',`foo`,1) //> {func:'substring',args:[{val:'foo'},{val:1}]}
|
|
154
|
+
* expr`substring('foo',1)` //> {func:'substring',args:[{val:'foo'},{val:1}]}
|
|
155
|
+
* expr`substring(foo,1)` //> {func:'substring',args:[{ref:['foo']},{val:1}]}
|
|
156
|
+
* expr`substring(foo,1)` //> {func:'substring',args:[{ref:['foo']},{val:1}]}
|
|
157
|
+
*
|
|
158
|
+
* @see Use {@link ql.expr `expr()`} to get the same via a tagged template literal.
|
|
159
|
+
* @returns {cqn.func}
|
|
160
|
+
*/
|
|
161
|
+
exports.func = (func,...args) => {
|
|
162
|
+
const [x] = args; if (is_array(x)) args = x
|
|
163
|
+
return { func, args: args.map (_cqn_or_val) }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
/** @returns { cqn.ref & cqn.as & cqn.infix &{ columns: cqn.column[] }} */
|
|
168
|
+
exports.nested = (ref, ...args) => {
|
|
169
|
+
if (ref.raw) return ql.nested (ql.ref(ref,...args))
|
|
170
|
+
else if (!ref.ref) ref = ql.ref(ref)
|
|
171
|
+
for (let each of args) {
|
|
172
|
+
if (each.as || each.where || each.orderBy) ref = {...ref, ...each}
|
|
173
|
+
else ref.columns = ql.columns(each)
|
|
174
|
+
}
|
|
175
|
+
ref.columns ??= ['*']
|
|
176
|
+
return ref
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
exports.expand = (...args) => {
|
|
180
|
+
let { columns, ...rest } = ql.nested (...args)
|
|
181
|
+
return { ...rest, expand: columns }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
exports.inline = (...args) => {
|
|
185
|
+
let { columns, ...rest } = ql.nested (...args)
|
|
186
|
+
return { ...rest, inline: columns }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** @returns {{ where: cqn.xo[] }} */
|
|
190
|
+
exports.where = (...args) => ({where: ql.predicate(...args)})
|
|
191
|
+
|
|
192
|
+
/** @returns {{ orderBy: cqn.order[] }} */
|
|
193
|
+
exports.orderBy = (...args) => ({orderBy: ql.orders(...args)})
|
|
194
|
+
exports.orders = (...args) => {
|
|
195
|
+
const [x] = args; if (x.raw) return cds.parse._select('from X order by',args).orderBy
|
|
196
|
+
if (is_array(x)) args = x
|
|
197
|
+
else if (is_object(x) && !x.ref) {
|
|
198
|
+
return Object.entries(x) .map (each => _ordering_term (...each))
|
|
199
|
+
}
|
|
200
|
+
return args.map (each => {
|
|
201
|
+
if (each.ref) return each
|
|
202
|
+
if (typeof each === 'string') return _ordering_term (...each.split(' '))
|
|
203
|
+
})
|
|
204
|
+
function _ordering_term (ref,sort) {
|
|
205
|
+
const ot = cds.parse.ref(ref)
|
|
206
|
+
if (sort) ot.sort = sort == 1 ? 'asc' : sort == -1 ? 'desc' : sort
|
|
207
|
+
return ot
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const _cqn_or_val = x => typeof x === 'object' ? x : {val:x}
|
|
212
|
+
const is_object = x => typeof x === 'object'
|
|
213
|
+
const is_array = Array.isArray
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Returns a new instance of Query for the given input,
|
|
218
|
+
* that has the given one as prototype.
|
|
219
|
+
*/
|
|
220
|
+
exports.clone = function (q,_) {
|
|
221
|
+
return Query.prototype.clone.call(q,_)
|
|
31
222
|
}
|
|
32
223
|
|
|
33
|
-
|
|
34
|
-
|
|
224
|
+
/**
|
|
225
|
+
* For questionable tests only.
|
|
226
|
+
* @protected @deprecated
|
|
227
|
+
*/
|
|
228
|
+
exports._reset = ()=>{
|
|
35
229
|
const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
|
|
36
230
|
Object.defineProperty (Query.prototype,'valueOf',{ configurable:1, value: function(kind=this.kind) {
|
|
37
231
|
return `${kind} ${_name(this._target.name)} `
|
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
const { AsyncResource } = require('async_hooks')
|
|
2
2
|
const cds = require('../index')
|
|
3
3
|
|
|
4
|
+
const cloned = from => from.ref ? { ...from, ref:[...from.ref] } : { ...from }
|
|
5
|
+
const cached = {}
|
|
6
|
+
|
|
7
|
+
|
|
4
8
|
class Query {
|
|
5
9
|
|
|
10
|
+
/** @private */
|
|
11
|
+
static init () {
|
|
12
|
+
const self = this, kind = self.name
|
|
13
|
+
Object.defineProperty (this.prototype, 'kind', {value:kind})
|
|
14
|
+
Object.defineProperty (this, 'name', {value:'cds.ql'})
|
|
15
|
+
return Object.assign (function chimera (x,...etc) {
|
|
16
|
+
if (!new.target) return self.call (x,...etc)
|
|
17
|
+
let q = new self; if (x) q[kind] = x
|
|
18
|
+
return q
|
|
19
|
+
}, this.API)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @private to be implemented in subclassed*/
|
|
23
|
+
static call() {}
|
|
24
|
+
|
|
25
|
+
/** @private */
|
|
26
|
+
get _(){ return this[this.kind] }
|
|
27
|
+
|
|
28
|
+
|
|
6
29
|
/**
|
|
7
30
|
* The kind of query, as in CQN's SELECT, INSERT, UPDATE, DELETE, CREATE, DROP.
|
|
8
|
-
* Overridden in subclasses to return the respective kind.
|
|
9
31
|
*/
|
|
10
32
|
get kind() { return this.constructor.name }
|
|
11
|
-
get _(){ return this[this.kind] }
|
|
12
33
|
|
|
13
|
-
constructor(_={}) { this[this.kind] = _ }
|
|
14
34
|
|
|
15
35
|
/**
|
|
16
36
|
* Note to self: We can't use .as (alias) as that would conflict with
|
|
@@ -37,6 +57,7 @@ class Query {
|
|
|
37
57
|
|
|
38
58
|
/** Binds this query to be executed with the given service */
|
|
39
59
|
bind (srv) {
|
|
60
|
+
// TODO: resolve target from srv.entities -> but better do that in .from
|
|
40
61
|
return Object.defineProperty (this,'_srv',{ value:srv, configurable:true, writable:true })
|
|
41
62
|
}
|
|
42
63
|
|
|
@@ -47,59 +68,48 @@ class Query {
|
|
|
47
68
|
return (r,e) => q.runInAsyncScope (srv.run, srv, this).then (r,e)
|
|
48
69
|
}
|
|
49
70
|
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
/** @private */ get _target_ref() { return this._.from } // overridden in subclasses
|
|
72
|
+
/** @private */ set _target(t) { this._set('_target', t.ref ? {name:t.ref[0]} : t) }
|
|
73
|
+
|
|
74
|
+
/** @private */ _target4 (t,...etc) {
|
|
75
|
+
switch (typeof t) {
|
|
76
|
+
case 'string': {
|
|
77
|
+
// NOTE: even though this._target will be {name:ref[0]} this returns the parsed {ref}
|
|
78
|
+
// NOTE: we need to clone cached refs, as they might get modified afterwards
|
|
79
|
+
return this._target = cloned (cached[t] ??= cds.parse.path(t))
|
|
80
|
+
}
|
|
81
|
+
case 'object': {
|
|
82
|
+
if (t.raw) return this._target = !etc.length ? this._target4(t[0]) : cds.parse.path(t,...etc)
|
|
83
|
+
if (t.name) return {ref:[ (this._target = t).name ]}
|
|
84
|
+
if (t.ref || t.SELECT || t.SET) return this._target = t
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw this._expected `${{target:t}} to be an entity path string, a CSN definition, a {ref}, a {SELECT}, or a {SET}`
|
|
52
88
|
}
|
|
53
89
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// If called with a linked entity -> use it as this.target
|
|
57
|
-
if (target instanceof cds.entity) this.target = target
|
|
58
|
-
|
|
59
|
-
// REVISIT: this._target is not reliable !!!
|
|
60
|
-
this._set ('_target', function _target(t) { return t && (
|
|
61
|
-
typeof t === 'string' ? { name: t } :
|
|
62
|
-
t.ref ? { name: t.ref[0] } :
|
|
63
|
-
t.raw ? _target(arg2) :
|
|
64
|
-
t.SELECT ? _target(t.SELECT.from) :
|
|
65
|
-
t //> default is assumed to be a csn definition or a look-alike or a SELECT
|
|
66
|
-
)}(target))
|
|
67
|
-
|
|
68
|
-
// Determine from.ref or from.SELECT
|
|
69
|
-
const from = target && (
|
|
70
|
-
target.name ? {ref:[target.name]} :
|
|
71
|
-
typeof target === 'string' ? cds.parse.path(target) :
|
|
72
|
-
target.raw ? cds.parse.path(...arguments) :
|
|
73
|
-
target.ref ? target :
|
|
74
|
-
target.SELECT ? target :
|
|
75
|
-
target.SET ? target : 0
|
|
76
|
-
)
|
|
77
|
-
return from || this._expected `${{target}} to be an entity path string, a CSN definition, a {ref}, a {SELECT}, or a {SET}`
|
|
90
|
+
/** @private */ _expected (...args) {
|
|
91
|
+
return cds.error.expected (...args)
|
|
78
92
|
}
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
/** @private */ _assign (...aspects) {
|
|
95
|
+
Object.assign (this._, ...aspects)
|
|
96
|
+
return this
|
|
82
97
|
}
|
|
83
98
|
|
|
84
|
-
_add (property, values) {
|
|
99
|
+
/** @private */ _add (property, values) {
|
|
85
100
|
const {_} = this, pd = Reflect.getOwnPropertyDescriptor (_,property)
|
|
86
|
-
_[property] = !pd
|
|
101
|
+
_[property] = !pd?.value ? values : [ ...pd.value, ...values ]
|
|
87
102
|
return this
|
|
88
103
|
}
|
|
89
104
|
|
|
90
|
-
_set (property, value) {
|
|
91
|
-
|
|
92
|
-
return value
|
|
105
|
+
/** @private */ _set (property, value) {
|
|
106
|
+
return Object.defineProperty (this, property, { value, configurable:true, writable:true })
|
|
93
107
|
}
|
|
94
108
|
|
|
95
109
|
valueOf (prelude = this.kind) {
|
|
96
110
|
return `${prelude} ${_name(this._target.name)} `
|
|
97
111
|
}
|
|
98
112
|
|
|
99
|
-
get _target_ref() {
|
|
100
|
-
throw cds.error `Query subclass ${this.constructor.name} must implement '_target_ref'`
|
|
101
|
-
}
|
|
102
|
-
|
|
103
113
|
/**
|
|
104
114
|
* Returns the inferred query's source, which is the entity referred
|
|
105
115
|
* to in SELECT.from, INSERT.into, UPDATE.entity, or DELETE.from,
|
|
@@ -118,8 +128,9 @@ class Query {
|
|
|
118
128
|
* returns the target of the sub query, recursively.
|
|
119
129
|
*/
|
|
120
130
|
get target() {
|
|
131
|
+
if (this._target instanceof cds.entity) return super.target = this._target
|
|
121
132
|
const m = this._srv?.model || cds.context?.model || cds.model
|
|
122
|
-
return cds.infer (this, m?.definitions)
|
|
133
|
+
return this.target = cds.infer (this, m?.definitions)
|
|
123
134
|
}
|
|
124
135
|
set target(t) { this._set('target',t) }
|
|
125
136
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const cds = require('../index')
|
|
2
|
+
module.exports = predicate
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/** @import cqn from './cqn' */
|
|
6
|
+
/** @returns {cqn.xo[]} */
|
|
7
|
+
function predicate (...args) {
|
|
8
|
+
const [x] = args
|
|
9
|
+
if (x) switch (typeof x) {
|
|
10
|
+
case 'object':
|
|
11
|
+
if (x.raw) return _ttl(args)
|
|
12
|
+
if (args.length > 1) return _legacy(...args) //> legacy support for predicate (ref`a`, '=', val`b`)
|
|
13
|
+
if (is_array(x)) return x
|
|
14
|
+
if (is_cqn(x)) return [x]
|
|
15
|
+
else return _qbe(x)
|
|
16
|
+
case 'string': return _fluid(args)
|
|
17
|
+
default: return args.map(_val)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
function _ttl (args) {
|
|
23
|
+
const cxn = cds.parse.expr(...args)
|
|
24
|
+
return cxn.xpr ?? [cxn] //> the fallback is for single-item exprs like `1` or `ref`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
function _fluid (args) {
|
|
29
|
+
if (args.length === 3) switch (args[1]) {
|
|
30
|
+
case '=': case '<': case '<=': case '>': case '>=': case '!=': case '<>': case 'like': case 'in': case 'IN': case 'LIKE':
|
|
31
|
+
return _legacy (...args)
|
|
32
|
+
}
|
|
33
|
+
if (args.length % 2 === 0) args.push('')
|
|
34
|
+
const expr = args.filter((_, i) => i % 2 === 0).join(' ? ')
|
|
35
|
+
const vals = args.filter((_, i) => i % 2 === 1)
|
|
36
|
+
const { xpr } = _pred_expr(expr); (function _fill_in_vals_into (xpr) {
|
|
37
|
+
xpr.forEach((x, i) => {
|
|
38
|
+
if (x.xpr) _fill_in_vals_into(x.xpr)
|
|
39
|
+
if (x.param) xpr[i] = _val(vals.shift())
|
|
40
|
+
})
|
|
41
|
+
})(xpr)
|
|
42
|
+
return xpr
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
function _qbe (o, xpr=[]) {
|
|
47
|
+
|
|
48
|
+
let count = 0
|
|
49
|
+
for (let k in o) { const x = o[k]
|
|
50
|
+
|
|
51
|
+
if (k.startsWith('not ')) { xpr.push('not'); k = k.slice(4) }
|
|
52
|
+
switch (k) { // handle special cases like {and:{...}} or {or:{...}}
|
|
53
|
+
case 'between':
|
|
54
|
+
xpr.push('between', _val(x), 'and', _val(o.and))
|
|
55
|
+
return xpr
|
|
56
|
+
case 'and': xpr.push(k)
|
|
57
|
+
x.or ? xpr.push({xpr:_qbe(x)}) : _qbe(x,xpr)
|
|
58
|
+
continue
|
|
59
|
+
case 'or': xpr.push(k)
|
|
60
|
+
_qbe(x,xpr)
|
|
61
|
+
continue
|
|
62
|
+
case 'not': xpr.push(k)
|
|
63
|
+
if (x && typeof x === 'object') x.in || x.like || x.exists || x.between ? _qbe(x,xpr) : xpr.push({xpr:_qbe(x)})
|
|
64
|
+
else xpr.push(x === null ? 'null' : {val:x})
|
|
65
|
+
continue
|
|
66
|
+
case 'is': xpr.push('is')
|
|
67
|
+
if (x && typeof x === 'object') _qbe(x,xpr)
|
|
68
|
+
else xpr.push(x === null ? 'null' : {val:x})
|
|
69
|
+
continue
|
|
70
|
+
case 'is not': xpr.push('is','not')
|
|
71
|
+
if (x && typeof x === 'object') _qbe(x,xpr)
|
|
72
|
+
else xpr.push(x === null ? 'null' : {val:x})
|
|
73
|
+
continue
|
|
74
|
+
case 'exists':
|
|
75
|
+
xpr.push(k,_ref(x)||x)
|
|
76
|
+
continue
|
|
77
|
+
case 'in': case 'IN': // REVISIT: 'IN' is for compatibility only
|
|
78
|
+
xpr.push(k, x.SELECT ? x : { list: x.map(_val) })
|
|
79
|
+
continue
|
|
80
|
+
case '=': case '<': case '<=': case '>': case '>=': case '!=': case '<>': case 'like': case 'LIKE': // REVISIT: 'LIKE' is for compatibility only
|
|
81
|
+
xpr.push(k,_val(x))
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const a = cds.parse.ref(k) //> turn key into a ref for the left side of the expression
|
|
86
|
+
if (count++) xpr.push('and') //> add 'and' between conditions
|
|
87
|
+
if (!x || typeof x !== 'object') xpr.push (a,'=',{val:x})
|
|
88
|
+
else if (is_array(x)) xpr.push (a,'in',{list:x.map(_val)})
|
|
89
|
+
else if (x.SELECT || x.list) xpr.push (a,'in',x)
|
|
90
|
+
else if (is_cqn(x)) xpr.push (a,'=',x)
|
|
91
|
+
else if (x instanceof Date) xpr.push (a,'=',{val:x})
|
|
92
|
+
else if (x instanceof Buffer) xpr.push (a,'=',{val:x})
|
|
93
|
+
else if (x instanceof RegExp) xpr.push (a,'like',{val:x})
|
|
94
|
+
else { xpr.push(a); _qbe(x,xpr) } //> recurse into nested qbe
|
|
95
|
+
}
|
|
96
|
+
return xpr
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
/** @deprecated */
|
|
101
|
+
const _legacy = (x,o,y) => [ _ref(x)||x, o, _val(y) ]
|
|
102
|
+
|
|
103
|
+
const _pred_expr = x => {
|
|
104
|
+
if (typeof x !== 'string') return {val:x}
|
|
105
|
+
if (x === '*') return x
|
|
106
|
+
const t = /^\s*([\w.'?]+)(?:\s*([!?\\/:=\-+<~>]+|like)\s*([\w.'?]+))?\s*$/.exec(x)
|
|
107
|
+
if (!t) return cds.parse.expr(x)
|
|
108
|
+
const [,lhs,op,rhs] = t
|
|
109
|
+
return !op ? _ref_or_val(lhs) : {xpr:[ _ref_or_val(lhs), op, _ref_or_val(rhs) ]}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const _ref_or_val = (x) => {
|
|
113
|
+
if (x[0] === '?') return { param: true, ref: x }
|
|
114
|
+
if (x[0] === "'") return { val: x.slice(1,-1).replace(/''/g, "'") }
|
|
115
|
+
if (x === 'null') return { val: null }
|
|
116
|
+
if (x === 'true') return { val: true }
|
|
117
|
+
if (x === 'false') return { val: false }
|
|
118
|
+
if (!isNaN(x)) return { val: Number(x) }
|
|
119
|
+
else return { ref: x.split('.') }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const _ref = x => typeof x === 'string' ? { ref: x.split('.') } : x // if x is a string, turn it into a ref; if not we assume it's a cxn object
|
|
123
|
+
const _val = x => !x ? {val:x} : is_array(x) ? { list: x.map(_val) } : is_cqn(x) ? x : {val:x}
|
|
124
|
+
|
|
125
|
+
const is_cqn = x => typeof x === 'object' && (
|
|
126
|
+
'ref' in x ||
|
|
127
|
+
'val' in x ||
|
|
128
|
+
'xpr' in x ||
|
|
129
|
+
'list' in x ||
|
|
130
|
+
'func' in x ||
|
|
131
|
+
'SELECT' in x
|
|
132
|
+
)
|
|
133
|
+
const is_array = Array.isArray
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const predicate = require('./cds.ql-predicates')
|
|
2
|
+
const cds = require('../index')
|
|
3
|
+
|
|
4
|
+
/** @import cqn from './cqn' */
|
|
5
|
+
/** @returns {cqn.column[]} */
|
|
6
|
+
module.exports = function projection (...args) {
|
|
7
|
+
const [x] = args
|
|
8
|
+
if (x) switch (typeof x) {
|
|
9
|
+
case 'function':
|
|
10
|
+
return _function(x)
|
|
11
|
+
case 'object':
|
|
12
|
+
if (x.raw) return _ttl(args)
|
|
13
|
+
if (is_array(x)) return _array(x)
|
|
14
|
+
break //> processing all args below
|
|
15
|
+
case 'string':
|
|
16
|
+
if (x[0] === '{') return _string(x)
|
|
17
|
+
}
|
|
18
|
+
return _array (args)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
function _ttl (args) {
|
|
23
|
+
if (args[0][0][0] === '{') return cds.parse._select('from X',args).columns
|
|
24
|
+
return cds.parse._select('',args,'from X').columns
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function _string (x) {
|
|
28
|
+
return cds.parse.cql('SELECT from X '+ x).SELECT.columns
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
function _array (args) {
|
|
33
|
+
return args.map (column_expr)
|
|
34
|
+
function column_expr (x) {
|
|
35
|
+
switch (typeof x) {
|
|
36
|
+
case 'string': {
|
|
37
|
+
if (x === '*') return x
|
|
38
|
+
let alias = /\s+as\s+(\w+)$/i.exec(x)
|
|
39
|
+
if (alias) return { ...cds.parse.ref(x.slice(0,alias.index)), as: alias[1] }
|
|
40
|
+
return cds.parse.ref(x)
|
|
41
|
+
}
|
|
42
|
+
case 'object': {
|
|
43
|
+
if (x.name) return { ref: [x.name] } //> reflected element
|
|
44
|
+
if (is_cqn(x)) return x //> already a CQN object
|
|
45
|
+
else for (let e in x) return {...cds.parse.ref(e), as: x[e] } //> { element: alias }
|
|
46
|
+
throw this._expected`Argument for SELECT.columns(${x}) to be a valid column expression`
|
|
47
|
+
}
|
|
48
|
+
default: return { val: x }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
function _function (fn) {
|
|
55
|
+
const columns = []; fn(new Proxy(fn, {
|
|
56
|
+
apply: (_, __, args) => {
|
|
57
|
+
if (!args.length) return columns.push('*')
|
|
58
|
+
let [x] = is_array(args[0]) ? args[0] : args
|
|
59
|
+
columns.push(x === '*' || x === '.*' ? '*' : is_cqn(x) ? x : { ref: [x] })
|
|
60
|
+
return { as: (alias) => (x.as = alias) }
|
|
61
|
+
},
|
|
62
|
+
get: (_, p) => {
|
|
63
|
+
const col = { ref: [p] }; columns.push(col)
|
|
64
|
+
const nested = new Proxy(fn, {
|
|
65
|
+
get: (_, p) => {
|
|
66
|
+
if (p === 'where') return (x) => ((col.where = predicate(x)), nested)
|
|
67
|
+
if (p === 'as') return (alias) => ((col.as = alias), nested)
|
|
68
|
+
else return col.ref.push(p), nested
|
|
69
|
+
},
|
|
70
|
+
apply: (_, __, args) => {
|
|
71
|
+
const [a, b] = args
|
|
72
|
+
if (!a) col.expand = ['*']
|
|
73
|
+
else if (a.raw) switch (a[0]) {
|
|
74
|
+
case '*': col.expand = ['*']; break
|
|
75
|
+
case '.*': col.inline = ['*']; break
|
|
76
|
+
default: {
|
|
77
|
+
// The ttl is the tail of a column expression including infic filter and nested projection.
|
|
78
|
+
// So, we need to add the col name as prefix to be able to parse it...
|
|
79
|
+
const {columns} = cds.parse._select(col.ref.at(-1), args, 'from X')
|
|
80
|
+
Object.assign(col, columns[0])
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (is_array(a)) col.expand = _array(a)
|
|
84
|
+
else if (a === '*') col.expand = ['*']
|
|
85
|
+
else if (a === '.*') col.inline = ['*']
|
|
86
|
+
else if (typeof a === 'string') col.ref.push(a)
|
|
87
|
+
else if (typeof a === 'function') {
|
|
88
|
+
let x = (col[/^\(?_\b/.test(a) ? 'inline' : 'expand'] = _function(a))
|
|
89
|
+
if (b?.levels) while (--b.levels) x.push({ ...col, expand: (x = [...x]) })
|
|
90
|
+
} else if (typeof b === 'function') {
|
|
91
|
+
let x = (col[/^\(?_\b/.test(b) ? 'inline' : 'expand'] = _function(b))
|
|
92
|
+
if (a?.depth) while (--a.depth) x.push({ ...col, expand: (x = [...x]) })
|
|
93
|
+
}
|
|
94
|
+
return nested
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
return nested
|
|
98
|
+
},
|
|
99
|
+
}))
|
|
100
|
+
return columns
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const is_cqn = x => typeof x === 'object' && (
|
|
104
|
+
'ref' in x ||
|
|
105
|
+
'val' in x ||
|
|
106
|
+
'xpr' in x ||
|
|
107
|
+
'list' in x ||
|
|
108
|
+
'func' in x ||
|
|
109
|
+
'SELECT' in x
|
|
110
|
+
)
|
|
111
|
+
const is_array = Array.isArray
|