@sap/cds 6.2.2 → 6.3.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 +56 -0
- package/apis/connect.d.ts +1 -1
- package/apis/cqn.d.ts +1 -1
- package/apis/internal/inference.d.ts +14 -0
- package/apis/ql.d.ts +40 -36
- package/apis/services.d.ts +23 -6
- package/bin/build/buildTaskHandler.js +3 -3
- package/bin/build/provider/buildTaskHandlerEdmx.js +1 -1
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +4 -3
- package/bin/build/provider/buildTaskHandlerInternal.js +2 -2
- package/bin/build/provider/java/index.js +2 -1
- package/bin/build/provider/mtx/index.js +2 -1
- package/bin/build/provider/mtx/resourcesTarBuilder.js +3 -2
- package/bin/build/provider/mtx-extension/index.js +2 -1
- package/bin/build/provider/mtx-sidecar/index.js +3 -1
- package/lib/auth/index.js +2 -1
- package/lib/auth/jwt-auth.js +64 -3
- package/lib/auth/xsuaa-auth.js +2 -3
- package/lib/compile/cdsc.js +1 -0
- package/lib/compile/etc/_localized.js +1 -0
- package/lib/dbs/cds-deploy.js +2 -1
- package/lib/env/cds-env.js +14 -49
- package/lib/env/cds-requires.js +13 -7
- package/lib/env/defaults.js +4 -0
- package/lib/i18n/localize.js +11 -8
- package/lib/index.js +3 -2
- package/lib/log/cds-log.js +2 -2
- package/lib/log/format/cf.js +16 -0
- package/lib/log/format/kibana.js +15 -2
- package/lib/ql/INSERT.js +12 -11
- package/lib/ql/Query.js +14 -7
- package/lib/ql/UPSERT.js +1 -0
- package/lib/ql/Whereable.js +6 -2
- package/lib/ql/cds-ql.js +2 -4
- package/lib/req/request.js +2 -0
- package/lib/srv/middlewares/cds-context.js +1 -1
- package/lib/srv/srv-dispatch.js +1 -0
- package/lib/srv/srv-tx.js +3 -3
- package/lib/utils/cds-utils.js +76 -31
- package/lib/utils/inflect.js +24 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +9 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +23 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +27 -15
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -10
- package/libx/_runtime/cds-services/services/utils/differ.js +6 -4
- package/libx/_runtime/common/composition/data.js +29 -40
- package/libx/_runtime/common/composition/update.js +6 -19
- package/libx/_runtime/common/generic/paging.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -13
- package/libx/_runtime/db/utils/generateAliases.js +1 -0
- package/libx/_runtime/fiori/generic/before.js +2 -1
- package/libx/_runtime/fiori/generic/read.js +11 -4
- package/libx/_runtime/hana/execute.js +2 -2
- package/libx/_runtime/hana/search2cqn4sql.js +1 -0
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +5 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +7 -1
- package/libx/_runtime/messaging/file-based.js +1 -1
- package/libx/_runtime/messaging/message-queuing.js +5 -2
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +13 -8
- package/libx/odata/cqn2odata.js +4 -1
- package/libx/odata/utils.js +8 -7
- package/libx/rest/RestAdapter.js +1 -4
- package/package.json +2 -2
package/lib/utils/cds-utils.js
CHANGED
|
@@ -2,6 +2,7 @@ const cwd = process.env._original_cwd || process.cwd()
|
|
|
2
2
|
const cds = require('../index')
|
|
3
3
|
|
|
4
4
|
const ux = module.exports = exports = new class {
|
|
5
|
+
get inflect() { return super.inflect = require('./inflect') }
|
|
5
6
|
get inspect() { return super.inspect = require('util').inspect }
|
|
6
7
|
get uuid() { return super.uuid = require('@sap/cds-foss').uuid }
|
|
7
8
|
get tar() { return super.tar = require('./tar') }
|
|
@@ -10,20 +11,50 @@ const ux = module.exports = exports = new class {
|
|
|
10
11
|
const path = exports.path = require('path'), { dirname, extname, join, resolve, relative } = path
|
|
11
12
|
const fs = exports.fs = Object.assign (ux,require('fs')) //> for compatibility
|
|
12
13
|
|
|
13
|
-
exports.decodeURIComponent = s => { try { return decodeURIComponent(s) } catch { return s } }
|
|
14
|
-
exports.decodeURI = s => { try { return decodeURI(s) } catch { return s } }
|
|
15
|
-
|
|
16
|
-
exports.local = (file) => relative(cwd,file)
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Variant of `Object.keys()` which includes all keys inherited from the
|
|
17
|
+
* given object's prototypes.
|
|
18
|
+
*/
|
|
19
|
+
exports.Object_keys = o => ({
|
|
20
|
+
[Symbol.iterator]: function*(){ for (let k in o) yield k },
|
|
21
|
+
forEach(f){ let i=0; for (let k in o) f(k,i++,o) },
|
|
22
|
+
filter(f){ let i=0, r=[]; for (let k in o) f(k,i++,o) && r.push(k); return r },
|
|
23
|
+
map(f){ let i=0, r=[]; for (let k in o) r.push(f(k,i++,o)); return r },
|
|
24
|
+
some(f){ for (let k in o) if (f(k)) return true },
|
|
25
|
+
find(f){ for (let k in o) if (f(k)) return k },
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Simple helper to always access results as arrays.
|
|
31
|
+
*/
|
|
32
|
+
exports.results = oa => {
|
|
33
|
+
return Array.isArray(oa) ? oa : oa != null ? [oa] : []
|
|
21
34
|
}
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Should be used in data providers, i.e., db services to return single
|
|
39
|
+
* rows in response to SELECT.one queries.
|
|
40
|
+
*/
|
|
41
|
+
exports.chimera = oa => {
|
|
42
|
+
return Array.isArray(oa) ? oa : Object.defineProperties(oa,chimera)
|
|
26
43
|
}
|
|
44
|
+
const chimera = Object.getOwnPropertyDescriptors (class Chimera {
|
|
45
|
+
*[Symbol.iterator] (){ yield this }
|
|
46
|
+
forEach(f){ f(this,0,this) }
|
|
47
|
+
filter(f){ return f(this,0,this) ? [this] : [] }
|
|
48
|
+
map(f){ return [f(this,0,this)] }
|
|
49
|
+
some(f){ return f(this,0,this) }
|
|
50
|
+
find(f){ if (f(this,0,this)) return this }
|
|
51
|
+
}.prototype)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
exports.decodeURIComponent = s => { try { return decodeURIComponent(s) } catch { return s } }
|
|
55
|
+
exports.decodeURI = s => { try { return decodeURI(s) } catch { return s } }
|
|
56
|
+
|
|
57
|
+
exports.local = (file) => relative(cwd,file)
|
|
27
58
|
|
|
28
59
|
exports.exists = function exists (x) {
|
|
29
60
|
if (x) {
|
|
@@ -50,6 +81,16 @@ exports.isfile = function isfile (x) {
|
|
|
50
81
|
} catch(e){/* ignore */}
|
|
51
82
|
}
|
|
52
83
|
|
|
84
|
+
exports.stat = async function (x) {
|
|
85
|
+
const d = resolve (cds.root,x)
|
|
86
|
+
return fs.promises.stat(d)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exports.readdir = async function (x) {
|
|
90
|
+
const d = resolve (cds.root,x)
|
|
91
|
+
return fs.promises.readdir(d)
|
|
92
|
+
}
|
|
93
|
+
|
|
53
94
|
exports.read = async function read (file, _encoding) {
|
|
54
95
|
const f = resolve (cds.root,file)
|
|
55
96
|
const src = await fs.promises.readFile (f, _encoding !== 'json' && _encoding || 'utf8')
|
|
@@ -64,18 +105,37 @@ exports.write = function write (file, data, o) {
|
|
|
64
105
|
return fs.mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
|
|
65
106
|
}
|
|
66
107
|
|
|
108
|
+
exports.copy = function copy (x,y) {
|
|
109
|
+
if (arguments.length === 1) return {to:(...path) => copy(x,join(...path))}
|
|
110
|
+
const src = resolve (cds.root,x)
|
|
111
|
+
const dst = resolve (cds.root,y)
|
|
112
|
+
if (fs.promises.cp) return fs.promises.cp (src,dst,{recursive:true})
|
|
113
|
+
return fs.mkdirp (dirname(dst)) .then (async ()=>{
|
|
114
|
+
if (fs.isdir(src)) {
|
|
115
|
+
const entries = await fs.promises.readdir(src)
|
|
116
|
+
return Promise.all (entries.map (async each => {
|
|
117
|
+
const e = join (src,each)
|
|
118
|
+
const f = join (dst,each)
|
|
119
|
+
return copy (e,f)
|
|
120
|
+
}))
|
|
121
|
+
} else {
|
|
122
|
+
return fs.promises.copyFile (src,dst)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
67
127
|
exports.mkdirp = async function (...path) {
|
|
68
128
|
const d = resolve (cds.root,...path)
|
|
69
129
|
await fs.promises.mkdir (d,{recursive:true})
|
|
70
130
|
return d
|
|
71
131
|
}
|
|
72
132
|
|
|
73
|
-
exports.rmdir = (...path)
|
|
133
|
+
exports.rmdir = async function (...path) {
|
|
74
134
|
const d = resolve (cds.root,...path)
|
|
75
135
|
return fs.promises.rm (d, {recursive:true})
|
|
76
136
|
}
|
|
77
137
|
|
|
78
|
-
exports.rimraf = (...path)
|
|
138
|
+
exports.rimraf = async function (...path) {
|
|
79
139
|
const d = resolve (cds.root,...path)
|
|
80
140
|
return fs.promises.rm (d, {recursive:true,force:true})
|
|
81
141
|
}
|
|
@@ -85,23 +145,6 @@ exports.rm = async function rm (x) {
|
|
|
85
145
|
return fs.promises.rm(y)
|
|
86
146
|
}
|
|
87
147
|
|
|
88
|
-
exports.copy = async function copy (x,y) {
|
|
89
|
-
const src = resolve (cds.root,x)
|
|
90
|
-
const dst = resolve (cds.root,y)
|
|
91
|
-
if (fs.promises.cp) return fs.promises.cp (src,dst,{recursive:true})
|
|
92
|
-
await fs.mkdirp (dirname(dst))
|
|
93
|
-
if (fs.isdir(src)) {
|
|
94
|
-
const entries = await fs.promises.readdir(src)
|
|
95
|
-
return Promise.all (entries.map (async each => {
|
|
96
|
-
const e = join (src,each)
|
|
97
|
-
const f = join (dst,each)
|
|
98
|
-
return copy (e,f)
|
|
99
|
-
}))
|
|
100
|
-
} else {
|
|
101
|
-
return fs.promises.copyFile (src,dst)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
148
|
exports.find = function find (base, patterns='*', filter=()=>true) {
|
|
106
149
|
const files=[]; base = resolve (cds.root,base)
|
|
107
150
|
if (typeof patterns === 'string') patterns = patterns.split(',')
|
|
@@ -134,9 +177,11 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
|
|
|
134
177
|
}
|
|
135
178
|
|
|
136
179
|
|
|
137
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Internal utility to load a file through ESM or CommonJs. TODO find a better place.
|
|
182
|
+
*/
|
|
138
183
|
exports._import = id => require(id)
|
|
139
|
-
if (
|
|
184
|
+
if (typeof jest === 'undefined') { // jest's ESM support is experimental: https://jestjs.io/docs/ecmascript-modules
|
|
140
185
|
const { pathToFileURL } = require('url')
|
|
141
186
|
exports._import = id => {
|
|
142
187
|
if (extname(id) === '.ts') return require(id) // ts-node w/ ESM not working (cap/issues#11980)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const last = /\w+$/
|
|
2
|
+
|
|
3
|
+
exports.singular4 = (dn,stripped) => {
|
|
4
|
+
let n = dn.name || dn; if (stripped) n = n.match(last)[0]
|
|
5
|
+
return dn['@singular'] || (
|
|
6
|
+
/.*species|news$/i.test(n) ? n :
|
|
7
|
+
/.*ess$/.test(n) ? n : // Address
|
|
8
|
+
/.*ees$/.test(n) ? n.slice(0, -1) : // Employees --> Employee
|
|
9
|
+
/.*[sz]es$/.test(n) ? n.slice(0, -2) :
|
|
10
|
+
/.*[^aeiou]ies$/.test(n) ? n.slice(0, -3) + 'y' : // Deliveries --> Delivery
|
|
11
|
+
/.*s$/.test(n) ? n.slice(0, -1) :
|
|
12
|
+
n
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
exports.plural4 = (dn,stripped) => {
|
|
17
|
+
let n = dn.name || dn; if (stripped) n = n.match(last)[0]
|
|
18
|
+
return dn['@plural'] || (
|
|
19
|
+
/.*analysis|status|species|news$/i.test(n) ? n :
|
|
20
|
+
/.*[^aeiou]y$/.test(n) ? n.slice(0,-1) + 'ies' :
|
|
21
|
+
/.*(s|x|z|ch|sh)$/.test(n) ? n + 'es' :
|
|
22
|
+
n + 's'
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -133,7 +133,15 @@ const getErrorHandler = (crashOnError = true, srv) => {
|
|
|
133
133
|
|
|
134
134
|
// add content id if not generated by okra ("~...")
|
|
135
135
|
const contentId = odataReq.getOdataRequestId()
|
|
136
|
-
if (contentId && !contentId.match(/^~/))
|
|
136
|
+
if (contentId && !contentId.match(/^~/)) {
|
|
137
|
+
err['@Core.ContentID'] = contentId
|
|
138
|
+
// UI5 expects us to add the content ID to each detail message
|
|
139
|
+
if (err.details) {
|
|
140
|
+
err.details.forEach(entry => {
|
|
141
|
+
entry['@Core.ContentID'] = contentId
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
}
|
|
137
145
|
|
|
138
146
|
const { error, statusCode } = normalizeError(err, req)
|
|
139
147
|
// REVISIT: We should also pass stack traces in development
|
|
@@ -19,12 +19,29 @@ const metadata = service => {
|
|
|
19
19
|
const locale = odataRes.getContract().getLocale()
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const { 'cds.xt.ModelProviderService': mps } = cds.services
|
|
23
|
+
// REVISIT: The following block should replaced with the one commented bellow after the next release of cds-mtxs.
|
|
24
|
+
// Currently lkg tests fail w/o the try-catch.
|
|
25
|
+
let edmx
|
|
26
|
+
try {
|
|
27
|
+
if (mps) edmx = await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
|
|
28
|
+
// eslint-disable-next-line no-empty
|
|
29
|
+
} catch (_) {}
|
|
30
|
+
if (!edmx)
|
|
31
|
+
edmx = cds.localize(
|
|
32
|
+
service.model,
|
|
33
|
+
locale,
|
|
34
|
+
cds.compile.to.edmx(service.model, { service: service.definition.name })
|
|
35
|
+
)
|
|
36
|
+
/*
|
|
37
|
+
let edmx = mps
|
|
38
|
+
? await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
|
|
39
|
+
: cds.localize(
|
|
40
|
+
service.model,
|
|
41
|
+
locale,
|
|
42
|
+
// REVISIT: we could cache this in model._cached
|
|
43
|
+
cds.compile.to.edmx(service.model, { service: service.definition.name })
|
|
44
|
+
) */
|
|
28
45
|
return next(null, toODataResult(edmx))
|
|
29
46
|
} catch (e) {
|
|
30
47
|
if (LOG._error) {
|
|
@@ -460,6 +460,7 @@ const read = service => {
|
|
|
460
460
|
|
|
461
461
|
const changeset = odataReq.getAtomicityGroupId()
|
|
462
462
|
const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
|
|
463
|
+
// REVISIT: can be removed when pluggable middlewares are active
|
|
463
464
|
cds.context = tx
|
|
464
465
|
|
|
465
466
|
let result, err
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js
CHANGED
|
@@ -85,6 +85,11 @@ class ValueValidator {
|
|
|
85
85
|
this._mode = mode
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
get property() {
|
|
89
|
+
return this._mode === 'decode' &&
|
|
90
|
+
this._valueConverter._propertyOrReturnType &&
|
|
91
|
+
this._valueConverter._propertyOrReturnType.getName()
|
|
92
|
+
}
|
|
88
93
|
/**
|
|
89
94
|
* Validates value of Edm.Binary type.
|
|
90
95
|
* @param {Buffer} value - Edm.Binary value as base64 or base64url string
|
|
@@ -257,7 +262,10 @@ class ValueValidator {
|
|
|
257
262
|
value +
|
|
258
263
|
' (JavaScript ' +
|
|
259
264
|
typeof value +
|
|
260
|
-
')
|
|
265
|
+
')' +
|
|
266
|
+
(this.property ? ' for property "' + this.property + '"' : '') +
|
|
267
|
+
'. ' +
|
|
268
|
+
'The length of the ' +
|
|
261
269
|
typeName +
|
|
262
270
|
' value must not be greater than the MaxLength facet value (' +
|
|
263
271
|
maxLength +
|
|
@@ -344,7 +352,9 @@ class ValueValidator {
|
|
|
344
352
|
value +
|
|
345
353
|
' (JavaScript ' +
|
|
346
354
|
typeof value +
|
|
347
|
-
')
|
|
355
|
+
')' +
|
|
356
|
+
(this.property ? ' for property "' + this.property + '"' : '') +
|
|
357
|
+
'. ' +
|
|
348
358
|
'The number of milliseconds does not correspond to the Precision facet value (' +
|
|
349
359
|
precision +
|
|
350
360
|
').'
|
|
@@ -368,15 +378,16 @@ class ValueValidator {
|
|
|
368
378
|
if (Number.isNaN(bigValue)) {
|
|
369
379
|
throw this._valueError(value, 'Edm.Decimal', 'number or a string representing a number')
|
|
370
380
|
}
|
|
371
|
-
|
|
372
381
|
// check that the value has no more digits than specified for precision
|
|
373
382
|
if (precision !== null && precision !== undefined && bigValue.c.length > precision) {
|
|
374
383
|
throw new IllegalArgumentError(
|
|
375
|
-
|
|
384
|
+
'Invalid value ' +
|
|
376
385
|
value +
|
|
377
386
|
' (JavaScript ' +
|
|
378
387
|
typeof value +
|
|
379
|
-
')
|
|
388
|
+
')' +
|
|
389
|
+
(this.property ? ' for property "' + this.property + '"' : '') +
|
|
390
|
+
'. ' +
|
|
380
391
|
'The specified Edm.Decimal value does not correspond to the Precision facet value (' +
|
|
381
392
|
precision +
|
|
382
393
|
').'
|
|
@@ -397,7 +408,9 @@ class ValueValidator {
|
|
|
397
408
|
value +
|
|
398
409
|
' (JavaScript ' +
|
|
399
410
|
typeof value +
|
|
400
|
-
')
|
|
411
|
+
')' +
|
|
412
|
+
(this.property ? ' for property "' + this.property + '"' : '') +
|
|
413
|
+
'. ' +
|
|
401
414
|
'If Precision is equal to Scale, a single zero must precede the decimal point ' +
|
|
402
415
|
'in the Edm.Decimal value.'
|
|
403
416
|
)
|
|
@@ -413,7 +426,9 @@ class ValueValidator {
|
|
|
413
426
|
value +
|
|
414
427
|
' (JavaScript ' +
|
|
415
428
|
typeof value +
|
|
416
|
-
')
|
|
429
|
+
')' +
|
|
430
|
+
(this.property ? ' for property "' + this.property + '"' : '') +
|
|
431
|
+
'. ' +
|
|
417
432
|
'The number of digits to the left of the decimal point must not be greater than ' +
|
|
418
433
|
'Precision minus Scale, i.e., ' +
|
|
419
434
|
(precision - scale) +
|
|
@@ -425,11 +440,13 @@ class ValueValidator {
|
|
|
425
440
|
const decimalPart = bigValue.minus(integerPart)
|
|
426
441
|
if (decimalPart.c.length > scale && !decimalPart.eq(0)) {
|
|
427
442
|
throw new IllegalArgumentError(
|
|
428
|
-
|
|
443
|
+
'Invalid value ' +
|
|
429
444
|
value +
|
|
430
445
|
' (JavaScript ' +
|
|
431
446
|
typeof value +
|
|
432
|
-
')
|
|
447
|
+
')' +
|
|
448
|
+
(this.property ? ' for property "' + this.property + '"' : '') +
|
|
449
|
+
'. ' +
|
|
433
450
|
'The specified Edm.Decimal value has more digits to the right of the decimal point ' +
|
|
434
451
|
'than allowed by the Scale facet value (' +
|
|
435
452
|
scale +
|
|
@@ -705,18 +722,13 @@ class ValueValidator {
|
|
|
705
722
|
* @private
|
|
706
723
|
*/
|
|
707
724
|
_valueError (value, typeName, requiredText) {
|
|
708
|
-
const property =
|
|
709
|
-
this._mode === 'decode' &&
|
|
710
|
-
this._valueConverter._propertyOrReturnType &&
|
|
711
|
-
this._valueConverter._propertyOrReturnType.getName()
|
|
712
|
-
|
|
713
725
|
const msg =
|
|
714
726
|
'Invalid value ' +
|
|
715
727
|
(typeName.includes('Geo') ? JSON.stringify(value) : value) +
|
|
716
728
|
' (JavaScript ' +
|
|
717
729
|
typeof value +
|
|
718
730
|
')' +
|
|
719
|
-
(property ? ' for property "' + property + '"' : '') +
|
|
731
|
+
(this.property ? ' for property "' + this.property + '"' : '') +
|
|
720
732
|
'. ' +
|
|
721
733
|
'A ' +
|
|
722
734
|
requiredText +
|
|
@@ -58,7 +58,7 @@ const readAfterWrite = async (req, srv, { operation, isBefore } = { isBefore: fa
|
|
|
58
58
|
// gracefully set location and no body if no read auth or not readable capability
|
|
59
59
|
let result
|
|
60
60
|
try {
|
|
61
|
-
const _req = new Request({ query, event: 'READ', _: req._ })
|
|
61
|
+
const _req = new Request({ query, event: 'READ', _: req._, params: req.params })
|
|
62
62
|
result = await srv.dispatch(_req)
|
|
63
63
|
if (result && req.target._isDraftEnabled) removeDraftUUIDIfNecessary(req)(result)
|
|
64
64
|
if (result === null && !isBefore && (_isWriteWithResponse(req) || _isDraftAction(req))) {
|
|
@@ -77,7 +77,7 @@ const _hasOpDeep = (entry, element) => {
|
|
|
77
77
|
return false
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
const _addCompositionsToResult = (result, entity, prop, newValue, oldValue) => {
|
|
80
|
+
const _addCompositionsToResult = (result, entity, prop, newValue, oldValue, opts) => {
|
|
81
81
|
/*
|
|
82
82
|
* REVISIT: the current impl results in {} instead of keeping null for compo to one.
|
|
83
83
|
* unfortunately, many follow-up errors occur (e.g., prop in null checks) if changed.
|
|
@@ -89,9 +89,9 @@ const _addCompositionsToResult = (result, entity, prop, newValue, oldValue) => {
|
|
|
89
89
|
!Array.isArray(newValue[prop]) &&
|
|
90
90
|
Object.keys(newValue[prop]).length === 0
|
|
91
91
|
) {
|
|
92
|
-
composition = compareJsonDeep(entity.elements[prop]._target, undefined, oldValue && oldValue[prop])
|
|
92
|
+
composition = compareJsonDeep(entity.elements[prop]._target, undefined, oldValue && oldValue[prop], opts)
|
|
93
93
|
} else {
|
|
94
|
-
composition = compareJsonDeep(entity.elements[prop]._target, newValue[prop], oldValue && oldValue[prop])
|
|
94
|
+
composition = compareJsonDeep(entity.elements[prop]._target, newValue[prop], oldValue && oldValue[prop], opts)
|
|
95
95
|
}
|
|
96
96
|
if (composition.some(c => _hasOpDeep(c, entity.elements[prop]))) {
|
|
97
97
|
result[prop] = entity.elements[prop].is2one ? composition[0] : composition
|
|
@@ -154,7 +154,7 @@ const _skipToMany = (entity, prop) => {
|
|
|
154
154
|
return entity.elements[prop] && entity.elements[prop].is2many && _skip(entity, prop)
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
const _iteratePropsInNewEntry = (newEntry, keys, result, oldEntry, entity) => {
|
|
157
|
+
const _iteratePropsInNewEntry = (newEntry, keys, result, oldEntry, entity, opts) => {
|
|
158
158
|
// On app-service layer, generated foreign keys are not enumerable,
|
|
159
159
|
// include them here too.
|
|
160
160
|
for (const prop of Object.getOwnPropertyNames(newEntry)) {
|
|
@@ -164,7 +164,7 @@ const _iteratePropsInNewEntry = (newEntry, keys, result, oldEntry, entity) => {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
// if value did not change --> ignored
|
|
167
|
-
if (newEntry[prop] === (oldEntry && oldEntry[prop]) || prop in DRAFT_COLUMNS_MAP) {
|
|
167
|
+
if (newEntry[prop] === (oldEntry && oldEntry[prop]) || (opts.ignoreDraftColumns && prop in DRAFT_COLUMNS_MAP)) {
|
|
168
168
|
continue
|
|
169
169
|
}
|
|
170
170
|
|
|
@@ -177,7 +177,7 @@ const _iteratePropsInNewEntry = (newEntry, keys, result, oldEntry, entity) => {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
if (entity.elements[prop] && entity.elements[prop].isComposition) {
|
|
180
|
-
_addCompositionsToResult(result, entity, prop, newEntry, oldEntry)
|
|
180
|
+
_addCompositionsToResult(result, entity, prop, newEntry, oldEntry, opts)
|
|
181
181
|
continue
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -185,7 +185,7 @@ const _iteratePropsInNewEntry = (newEntry, keys, result, oldEntry, entity) => {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
const compareJsonDeep = (entity, newValue = [], oldValue = []) => {
|
|
188
|
+
const compareJsonDeep = (entity, newValue = [], oldValue = [], opts) => {
|
|
189
189
|
const resultsArray = []
|
|
190
190
|
const keys = _getKeysOfEntity(entity)
|
|
191
191
|
|
|
@@ -200,7 +200,7 @@ const compareJsonDeep = (entity, newValue = [], oldValue = []) => {
|
|
|
200
200
|
|
|
201
201
|
_addKeysToEntryIfNotExists(keys, newEntry)
|
|
202
202
|
|
|
203
|
-
_iteratePropsInNewEntry(newEntry, keys, result, oldEntry, entity)
|
|
203
|
+
_iteratePropsInNewEntry(newEntry, keys, result, oldEntry, entity, opts)
|
|
204
204
|
|
|
205
205
|
resultsArray.push(result)
|
|
206
206
|
}
|
|
@@ -259,8 +259,9 @@ const compareJsonDeep = (entity, newValue = [], oldValue = []) => {
|
|
|
259
259
|
*
|
|
260
260
|
* @returns {Array}
|
|
261
261
|
*/
|
|
262
|
-
const compareJson = (newValue, oldValue, entity) => {
|
|
263
|
-
const
|
|
262
|
+
const compareJson = (newValue, oldValue, entity, opts = {}) => {
|
|
263
|
+
const options = Object.assign({ ignoreDraftColumns: false }, opts)
|
|
264
|
+
const result = compareJsonDeep(entity, newValue, oldValue, options)
|
|
264
265
|
|
|
265
266
|
// in case of batch insert, result is an array
|
|
266
267
|
// in all other cases it is an array with just one entry
|
|
@@ -44,7 +44,7 @@ module.exports = class Differ {
|
|
|
44
44
|
return cds
|
|
45
45
|
.tx(req)
|
|
46
46
|
.run(query)
|
|
47
|
-
.then(dbState => compareJson(undefined, dbState, req.target))
|
|
47
|
+
.then(dbState => compareJson(undefined, dbState, req.target, { ignoreDraftColumns: true }))
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
async _addPartialPersistentState(req) {
|
|
@@ -59,7 +59,7 @@ module.exports = class Differ {
|
|
|
59
59
|
const combinedData = providedData || Object.assign({}, req.query.UPDATE.data || {}, req.query.UPDATE.with || {})
|
|
60
60
|
const lastTransition = newQuery.UPDATE._transitions[newQuery.UPDATE._transitions.length - 1]
|
|
61
61
|
const revertedPersistent = revertData(req._.partialPersistentState, lastTransition, this._srv)
|
|
62
|
-
return compareJson(combinedData, revertedPersistent, req.target)
|
|
62
|
+
return compareJson(combinedData, revertedPersistent, req.target, { ignoreDraftColumns: true })
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
async _diffPatch(req, providedData) {
|
|
@@ -73,7 +73,9 @@ module.exports = class Differ {
|
|
|
73
73
|
.tx(req)
|
|
74
74
|
.run(SELECT.from(draftRef).where(removeIsActiveEntityRecursively(where)).limit(1))
|
|
75
75
|
|
|
76
|
-
return compareJson(providedData || req.data, req._.partialPersistentState, req.target
|
|
76
|
+
return compareJson(providedData || req.data, req._.partialPersistentState, req.target, {
|
|
77
|
+
ignoreDraftColumns: true
|
|
78
|
+
})
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
@@ -83,7 +85,7 @@ module.exports = class Differ {
|
|
|
83
85
|
? req.query.INSERT.entries[0]
|
|
84
86
|
: req.query.INSERT.entries
|
|
85
87
|
|
|
86
|
-
return compareJson(originalData, undefined, req.target)
|
|
88
|
+
return compareJson(originalData, undefined, req.target, { ignoreDraftColumns: true })
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
async calculate(req, providedData) {
|
|
@@ -8,6 +8,8 @@ const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
|
|
|
8
8
|
const cds = require('../../cds')
|
|
9
9
|
const { SELECT } = cds.ql
|
|
10
10
|
|
|
11
|
+
const CHUNK_SIZE = cds.env.features.chunk_deep || Number.MAX_VALUE
|
|
12
|
+
|
|
11
13
|
/*
|
|
12
14
|
* own utils
|
|
13
15
|
*/
|
|
@@ -62,13 +64,17 @@ const _getLinksOfCompTree = compositionTree => {
|
|
|
62
64
|
return links
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
const _whereKeys =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
const _whereKeys = keySet => {
|
|
68
|
+
if (!keySet.length || !Object.keys(keySet[0]).length) return []
|
|
69
|
+
|
|
70
|
+
const keys0 = Object.keys(keySet[0])
|
|
71
|
+
const keys = { list: keys0.map(pk => ({ ref: [pk] })) }
|
|
72
|
+
const values = {
|
|
73
|
+
list: keySet.map(row => ({
|
|
74
|
+
list: keys0.map(k => ({ val: row[k] }))
|
|
75
|
+
}))
|
|
76
|
+
}
|
|
77
|
+
return [keys, 'in', values]
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
const _parentKey = (element, key) => {
|
|
@@ -136,28 +142,19 @@ const _subWhere = (result, element) => {
|
|
|
136
142
|
const links = [...element.backLinks, ...element.customBackLinks]
|
|
137
143
|
if (result.length && links && links.length > 0) {
|
|
138
144
|
where = {}
|
|
139
|
-
const chunkSize = cds.env.features.chunk_deep
|
|
140
145
|
const keys0 = Object.keys(_getWhereObj(result[0], links))
|
|
141
|
-
if (
|
|
146
|
+
if (keys0.length) {
|
|
142
147
|
const keys = { list: keys0.map(pk => ({ ref: [pk] })) }
|
|
143
|
-
for (let i = 0; i < result.length; i +=
|
|
148
|
+
for (let i = 0; i < result.length; i += CHUNK_SIZE) {
|
|
144
149
|
const values = {
|
|
145
|
-
list: result
|
|
146
|
-
.
|
|
147
|
-
|
|
150
|
+
list: result.slice(i, i + CHUNK_SIZE).map(row => ({
|
|
151
|
+
list: keys0.map(k => {
|
|
152
|
+
return { val: _getWhereObj(row, links)[k] }
|
|
153
|
+
})
|
|
154
|
+
}))
|
|
148
155
|
}
|
|
149
156
|
where[i] = [keys, 'in', values]
|
|
150
157
|
}
|
|
151
|
-
} else {
|
|
152
|
-
where = []
|
|
153
|
-
for (const row of result) {
|
|
154
|
-
const whereObj = _getWhereObj(row, links)
|
|
155
|
-
const whereCQN = ctUtils.whereKey(whereObj)
|
|
156
|
-
if (whereCQN.length) {
|
|
157
|
-
if (where.length > 0) where.push('or')
|
|
158
|
-
where.push({ xpr: [...whereCQN] })
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
158
|
}
|
|
162
159
|
}
|
|
163
160
|
return where
|
|
@@ -232,29 +229,22 @@ const _select = ({
|
|
|
232
229
|
}
|
|
233
230
|
|
|
234
231
|
const _selectDeepUpdateData = async args => {
|
|
235
|
-
const { model, compositionTree, entityName, data, root, selectData, tx, selectAllColumns } = args
|
|
232
|
+
const { model, compositionTree, entityName, data, root, selectData, tx, selectAllColumns, where, parentKeys } = args
|
|
236
233
|
let result = []
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
chunkSize &&
|
|
240
|
-
!args.where &&
|
|
241
|
-
args.parentKeys &&
|
|
242
|
-
args.parentKeys.length > chunkSize &&
|
|
243
|
-
Object.keys(args.parentKeys[0]).length
|
|
244
|
-
) {
|
|
245
|
-
const keys0 = Object.keys(args.parentKeys[0])
|
|
234
|
+
if (!where && parentKeys && parentKeys.length && Object.keys(parentKeys[0]).length) {
|
|
235
|
+
const keys0 = Object.keys(parentKeys[0])
|
|
246
236
|
const keys = { list: keys0.map(pk => ({ ref: [pk] })) }
|
|
247
|
-
for (let i = 0; i <
|
|
237
|
+
for (let i = 0; i < parentKeys.length; i += CHUNK_SIZE) {
|
|
248
238
|
const values = {
|
|
249
|
-
list:
|
|
239
|
+
list: parentKeys.slice(i, i + CHUNK_SIZE).map(row => ({ list: keys0.map(k => ({ val: row[k] })) }))
|
|
250
240
|
}
|
|
251
|
-
const _args = { ...args, where: [keys, 'in', values] }
|
|
241
|
+
const _args = { ...args, where: [keys, 'in', values], parentKeys: undefined }
|
|
252
242
|
const selectCQN = _select(_args)
|
|
253
243
|
result.push(...(await tx.run(selectCQN)))
|
|
254
244
|
}
|
|
255
|
-
} else if (
|
|
256
|
-
for (let
|
|
257
|
-
const _args = { ...args, where }
|
|
245
|
+
} else if (where && !Array.isArray(where)) {
|
|
246
|
+
for (let w of Object.values(where)) {
|
|
247
|
+
const _args = { ...args, where: w }
|
|
258
248
|
const selectCQN = _select(_args)
|
|
259
249
|
result.push(...(await tx.run(selectCQN)))
|
|
260
250
|
}
|
|
@@ -262,7 +252,6 @@ const _selectDeepUpdateData = async args => {
|
|
|
262
252
|
const selectCQN = _select(args)
|
|
263
253
|
result = await tx.run(selectCQN)
|
|
264
254
|
}
|
|
265
|
-
|
|
266
255
|
if (!result.length) return Promise.resolve(result)
|
|
267
256
|
|
|
268
257
|
const keys = _keys(model.definitions[entityName], result)
|
|
@@ -10,6 +10,8 @@ const { deepCopyObject } = require('../utils/copy')
|
|
|
10
10
|
|
|
11
11
|
const getError = require('../../common/error')
|
|
12
12
|
|
|
13
|
+
const CHUNK_SIZE = cds.env.features.chunk_deep || Number.MAX_VALUE
|
|
14
|
+
|
|
13
15
|
/*
|
|
14
16
|
* own utils
|
|
15
17
|
*/
|
|
@@ -35,13 +37,11 @@ const _dataByKey = (entity, data) => {
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
function _addSubDeepUpdateCQNForDelete({ entity, data, selectData, entityName, deleteCQNs }) {
|
|
38
|
-
const chunkSize = cds.env.features.chunk_deep
|
|
39
40
|
const dataByKey = _dataByKey(entity, data)
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
for (let j = 0; j < selectData.length; j += chunkSize) {
|
|
41
|
+
if (selectData.length && selectData[0] && Object.keys(selectData[0]).length) {
|
|
42
|
+
for (let j = 0; j < selectData.length; j += CHUNK_SIZE) {
|
|
43
43
|
const deleteCQN = { DELETE: { from: entityName, where: [] } }
|
|
44
|
-
for (let i = j; i < j +
|
|
44
|
+
for (let i = j; i < j + CHUNK_SIZE && i < selectData.length; i++) {
|
|
45
45
|
const selectEntry = selectData[i]
|
|
46
46
|
if (!selectEntry) continue
|
|
47
47
|
const dataEntry = dataByKey.get(_serializedKey(entity, selectEntry))
|
|
@@ -52,21 +52,8 @@ function _addSubDeepUpdateCQNForDelete({ entity, data, selectData, entityName, d
|
|
|
52
52
|
deleteCQN.DELETE.where.push({ xpr: [...ctUtils.whereKey(ctUtils.key(entity, selectEntry))] })
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
deleteCQNs.push(deleteCQN)
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
const deleteCQN = { DELETE: { from: entityName, where: [] } }
|
|
59
|
-
for (const selectEntry of selectData) {
|
|
60
|
-
if (!selectEntry) continue
|
|
61
|
-
const dataEntry = dataByKey.get(_serializedKey(entity, selectEntry))
|
|
62
|
-
if (!dataEntry) {
|
|
63
|
-
if (deleteCQN.DELETE.where.length > 0) {
|
|
64
|
-
deleteCQN.DELETE.where.push('or')
|
|
65
|
-
}
|
|
66
|
-
deleteCQN.DELETE.where.push({ xpr: [...ctUtils.whereKey(ctUtils.key(entity, selectEntry))] })
|
|
67
|
-
}
|
|
55
|
+
if (deleteCQN.DELETE.where.length) deleteCQNs.push(deleteCQN)
|
|
68
56
|
}
|
|
69
|
-
deleteCQNs.push(deleteCQN)
|
|
70
57
|
}
|
|
71
58
|
}
|
|
72
59
|
|
|
@@ -3,7 +3,7 @@ const { getDefaultPageSize, getMaxPageSize } = require('../utils/page')
|
|
|
3
3
|
|
|
4
4
|
const commonGenericPaging = function (req) {
|
|
5
5
|
// only if http request
|
|
6
|
-
if (!req._.req) return
|
|
6
|
+
if (!(req.http?.req || req._.req)) return
|
|
7
7
|
|
|
8
8
|
// target === null if view with parameters
|
|
9
9
|
if (!req.target || !req.query.SELECT || req.query.SELECT.one) return
|