@sap/cds 5.6.2 → 5.7.2
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 +133 -0
- package/_i18n/i18n_fr.properties +4 -4
- package/apis/cds.d.ts +7 -10
- package/apis/connect.d.ts +3 -3
- package/apis/core.d.ts +2 -4
- package/apis/models.d.ts +2 -3
- package/apis/ql.d.ts +0 -1
- package/apis/services.d.ts +7 -3
- package/bin/build/buildTaskFactory.js +16 -10
- package/bin/build/buildTaskProviderFactory.js +3 -3
- package/bin/build/constants.js +2 -1
- package/bin/build/provider/buildTaskProviderInternal.js +14 -14
- package/bin/build/provider/hana/2migration.js +2 -3
- package/bin/build/provider/hana/index.js +34 -0
- package/bin/build/provider/hana/migrationtable.js +90 -22
- package/bin/build/provider/hana/template/undeploy.json +5 -0
- package/bin/build/provider/node-cf/index.js +9 -2
- package/bin/serve.js +16 -18
- package/lib/compile/cdsc.js +15 -5
- package/lib/compile/etc/_localized.js +4 -4
- package/lib/compile/extend.js +8 -0
- package/lib/compile/index.js +3 -1
- package/lib/compile/minify.js +61 -0
- package/lib/compile/resolve.js +4 -1
- package/lib/compile/to/gql.js +9 -0
- package/lib/compile/to/sql.js +26 -30
- package/lib/connect/index.js +1 -1
- package/lib/core/entities.js +0 -3
- package/lib/core/infer.js +1 -0
- package/lib/core/reflect.js +0 -34
- package/lib/deploy.js +25 -17
- package/lib/env/defaults.js +3 -1
- package/lib/env/index.js +13 -4
- package/lib/env/presets.js +38 -0
- package/lib/env/requires.js +16 -11
- package/lib/index.js +13 -11
- package/lib/log/format/kibana.js +4 -2
- package/lib/log/index.js +2 -2
- package/lib/ql/Whereable.js +1 -0
- package/lib/req/cds-context.js +79 -0
- package/lib/req/context.js +5 -77
- package/lib/req/request.js +1 -1
- package/lib/serve/Service-api.js +8 -4
- package/lib/serve/Service-dispatch.js +0 -7
- package/lib/serve/Service-methods.js +6 -8
- package/lib/serve/Transaction.js +35 -30
- package/lib/serve/adapters.js +1 -4
- package/lib/utils/axios.js +1 -1
- package/libx/_runtime/audit/Service.js +44 -20
- package/libx/_runtime/audit/generic/personal/access.js +16 -11
- package/libx/_runtime/audit/generic/personal/modification.js +5 -5
- package/libx/_runtime/audit/generic/personal/utils.js +46 -37
- package/libx/_runtime/{common/auth → auth}/index.js +21 -7
- package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
- package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +26 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
- package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
- package/libx/_runtime/cds-services/services/Service.js +0 -6
- package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
- package/libx/_runtime/cds-services/util/assert.js +1 -262
- package/libx/_runtime/cds.js +6 -9
- package/libx/_runtime/common/aspects/entity.js +1 -1
- package/libx/_runtime/common/composition/delete.js +4 -2
- package/libx/_runtime/common/composition/update.js +22 -35
- package/libx/_runtime/common/composition/utils.js +3 -7
- package/libx/_runtime/common/error/standardError.js +11 -0
- package/libx/_runtime/common/generic/auth.js +63 -33
- package/libx/_runtime/common/generic/crud.js +11 -23
- package/libx/_runtime/common/generic/input.js +20 -0
- package/libx/_runtime/common/generic/paging.js +2 -2
- package/libx/_runtime/common/generic/put.js +4 -10
- package/libx/_runtime/common/generic/sorting.js +12 -30
- package/libx/_runtime/common/perf/index.js +24 -0
- package/libx/_runtime/common/utils/cqn.js +58 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +297 -121
- package/libx/_runtime/common/utils/csn.js +38 -56
- package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
- package/libx/_runtime/common/utils/resolveView.js +4 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
- package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
- package/libx/_runtime/common/utils/structured.js +35 -25
- package/libx/_runtime/db/Service.js +0 -6
- package/libx/_runtime/db/expand/expand-v2.js +130 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
- package/libx/_runtime/db/expand/index.js +3 -1
- package/libx/_runtime/db/generic/arrayed.js +3 -1
- package/libx/_runtime/db/generic/input.js +52 -10
- package/libx/_runtime/db/generic/integrity.js +367 -26
- package/libx/_runtime/db/generic/virtual.js +51 -13
- package/libx/_runtime/db/query/update.js +9 -3
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
- package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
- package/libx/_runtime/fiori/generic/activate.js +1 -0
- package/libx/_runtime/fiori/generic/before.js +2 -1
- package/libx/_runtime/fiori/generic/edit.js +1 -0
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +155 -57
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
- package/libx/_runtime/fiori/uiflex/index.js +1 -1
- package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
- package/libx/_runtime/fiori/utils/delete.js +7 -1
- package/libx/_runtime/hana/Service.js +1 -8
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
- package/libx/_runtime/hana/execute.js +10 -4
- package/libx/_runtime/hana/pool.js +55 -45
- package/libx/_runtime/hana/search.js +7 -6
- package/libx/_runtime/hana/search2cqn4sql.js +8 -5
- package/libx/_runtime/hana/searchToContains.js +3 -1
- package/libx/_runtime/index.js +5 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
- package/libx/_runtime/messaging/Outbox.js +53 -0
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
- package/libx/_runtime/messaging/common-utils/connections.js +14 -9
- package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
- package/libx/_runtime/messaging/file-based.js +5 -5
- package/libx/_runtime/messaging/message-queuing.js +2 -3
- package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
- package/libx/_runtime/messaging/outbox/utils.js +192 -0
- package/libx/_runtime/messaging/service.js +16 -30
- package/libx/_runtime/remote/Service.js +15 -0
- package/libx/_runtime/remote/utils/client.js +15 -3
- package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
- package/libx/_runtime/sqlite/Service.js +7 -10
- package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
- package/libx/_runtime/sqlite/execute.js +18 -12
- package/libx/_runtime/types/api.js +2 -1
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
- package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
- package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
- package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +182 -118
- package/libx/odata/index.js +18 -15
- package/libx/odata/parser.js +1 -0
- package/libx/odata/utils.js +57 -0
- package/libx/rest/RestAdapter.js +2 -6
- package/libx/rest/utils/data.js +1 -6
- package/package.json +4 -3
- package/server.js +4 -5
- package/srv/audit-log.cds +87 -0
- package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
- package/srv/flex.js +1 -0
- package/srv/outbox.cds +11 -0
- package/srv/outbox.js +0 -0
- package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
- package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
- package/libx/odata/odata2cqn/index.js +0 -3
- package/libx/odata/odata2cqn/parser.js +0 -1
- package/libx/odata/readme.md +0 -1
- package/libx/odata/utils/index.js +0 -64
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
const cds = require('../../cds')
|
|
2
|
-
const { all, resolve } = require('../../common/utils/thenable')
|
|
3
|
-
const { getDependents } = require('../../common/utils/csn')
|
|
4
|
-
|
|
5
1
|
// REVISIT: replace with cds.Request
|
|
6
2
|
const getEntry = require('../../common/error/entry')
|
|
7
|
-
const crypto = require('crypto')
|
|
8
3
|
|
|
9
4
|
const ISO_DATE_PART1 =
|
|
10
5
|
'[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)'
|
|
@@ -27,11 +22,8 @@ const ASSERT_FORMAT = 'ASSERT_FORMAT'
|
|
|
27
22
|
const ASSERT_DATA_TYPE = 'ASSERT_DATA_TYPE'
|
|
28
23
|
const ASSERT_ENUM = 'ASSERT_ENUM'
|
|
29
24
|
const ASSERT_NOT_NULL = 'ASSERT_NOT_NULL'
|
|
30
|
-
const ASSERT_REFERENCE_INTEGRITY = 'ASSERT_REFERENCE_INTEGRITY'
|
|
31
25
|
const ASSERT_DEEP_ASSOCIATION = 'ASSERT_DEEP_ASSOCIATION'
|
|
32
26
|
|
|
33
|
-
const ASSERT_INTEGRITY_ANNOTATION = '@assert.integrity'
|
|
34
|
-
|
|
35
27
|
const _enumValues = element => {
|
|
36
28
|
return Object.keys(element).map(enumKey => {
|
|
37
29
|
const enum_ = element[enumKey]
|
|
@@ -178,6 +170,7 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
|
|
|
178
170
|
|
|
179
171
|
const _checkStaticElementByKey = (definition, key, value, result, ignoreNonModelledData) => {
|
|
180
172
|
const elementsOrParameters = definition.elements || definition.params
|
|
173
|
+
if (!elementsOrParameters) return result
|
|
181
174
|
const elementOrParameter = elementsOrParameters[key]
|
|
182
175
|
|
|
183
176
|
if (!elementOrParameter) {
|
|
@@ -309,259 +302,6 @@ const checkStatic = (definition, data, ignoreNonModelledData = false) => {
|
|
|
309
302
|
}, [])
|
|
310
303
|
}
|
|
311
304
|
|
|
312
|
-
const _checkExistsWhere = (entity, whereList, run) => {
|
|
313
|
-
const checks = whereList.map(where => {
|
|
314
|
-
if (where.length === 0) {
|
|
315
|
-
return true
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const cqn = {
|
|
319
|
-
SELECT: {
|
|
320
|
-
columns: [{ val: 1, as: '_exists' }],
|
|
321
|
-
from: { ref: [entity.name || entity] },
|
|
322
|
-
where: where
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (cds.context) {
|
|
327
|
-
const hash = crypto.createHash('sha1').update(JSON.stringify(cqn)).digest('base64') // fastest hash
|
|
328
|
-
if (!cds.context.__alreadyExecutedIntegrityChecks) cds.context.__alreadyExecutedIntegrityChecks = new Map()
|
|
329
|
-
if (cds.context.__alreadyExecutedIntegrityChecks.has(hash)) {
|
|
330
|
-
return cds.context.__alreadyExecutedIntegrityChecks.get(hash)
|
|
331
|
-
} else {
|
|
332
|
-
const promise = run(cqn).then(exists => {
|
|
333
|
-
return exists.length !== 0
|
|
334
|
-
})
|
|
335
|
-
// we store the promise object in the map, it won't get executed twice when calling await Promise.all([promise, promise])
|
|
336
|
-
cds.context.__alreadyExecutedIntegrityChecks.set(hash, promise)
|
|
337
|
-
return promise
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
return run(cqn).then(exists => {
|
|
341
|
-
return exists.length !== 0
|
|
342
|
-
})
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
return all(checks)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const _checkExists = (entity, data, req, run) => {
|
|
349
|
-
if (!Array.isArray(data)) {
|
|
350
|
-
return _checkExists(entity, [data], req, run).then(result => {
|
|
351
|
-
return result[0]
|
|
352
|
-
})
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const where = data.map(row => {
|
|
356
|
-
return Object.keys(entity.keys).reduce((where, name) => {
|
|
357
|
-
if (row[name] !== undefined && row[name] !== null) {
|
|
358
|
-
if (where.length > 0) {
|
|
359
|
-
where.push('and')
|
|
360
|
-
}
|
|
361
|
-
where.push({ ref: [name] }, '=', { val: row[name] })
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return where
|
|
365
|
-
}, [])
|
|
366
|
-
})
|
|
367
|
-
return _checkExistsWhere(entity, where, run)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const _getFullForeignKeyName = (elementName, foreignKeyName) => `${elementName}_${foreignKeyName}`
|
|
371
|
-
|
|
372
|
-
const _foreignKeyReducer = (key, foreignKeyName, row, element, ref) => {
|
|
373
|
-
const fullForeignKeyName = _getFullForeignKeyName(element.name, foreignKeyName)
|
|
374
|
-
|
|
375
|
-
if (ref.length > 1) {
|
|
376
|
-
// ref includes assoc name, so we need to replace it by foreign key name
|
|
377
|
-
const refWithFlatForeignKey = [...ref.slice(0, ref.length - 1), fullForeignKeyName]
|
|
378
|
-
key[foreignKeyName] = _getDataFromRef(row, refWithFlatForeignKey)
|
|
379
|
-
} else {
|
|
380
|
-
key[foreignKeyName] = Object.prototype.hasOwnProperty.call(row, fullForeignKeyName) ? row[fullForeignKeyName] : null
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return key
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const _buildForeignKey = (element, row, ref) => {
|
|
387
|
-
let foreignKey
|
|
388
|
-
|
|
389
|
-
if (element.keys) {
|
|
390
|
-
foreignKey = element.keys
|
|
391
|
-
.map(obj => obj.ref[obj.ref.length - 1])
|
|
392
|
-
.reduce((key, foreignKeyName) => {
|
|
393
|
-
return _foreignKeyReducer(key, foreignKeyName, row, element, ref)
|
|
394
|
-
}, {})
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return foreignKey
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const _getDataFromRef = (row, ref) => {
|
|
401
|
-
if (row === undefined) return
|
|
402
|
-
|
|
403
|
-
if (ref.length > 1) {
|
|
404
|
-
return _getDataFromRef(row[ref[0]], ref.slice(1))
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return row[ref[0]]
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const _getElement = (entity, ref) => {
|
|
411
|
-
if (ref.length > 1) {
|
|
412
|
-
// structured
|
|
413
|
-
return _getElement(entity.elements[ref[0]], ref.slice(1))
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return entity.elements[ref[0]]
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const _checkCreateUpdate = (result, ref, rootEntity, checks, data, req, run) => {
|
|
420
|
-
const resolvedElement = _getElement(rootEntity, ref)
|
|
421
|
-
|
|
422
|
-
return data.reduce((result, row) => {
|
|
423
|
-
if (resolvedElement.on) return result
|
|
424
|
-
|
|
425
|
-
const foreignKey = _buildForeignKey(resolvedElement, row, ref)
|
|
426
|
-
if (foreignKey === undefined) return result
|
|
427
|
-
|
|
428
|
-
checks.push(
|
|
429
|
-
_checkExists(resolvedElement._target, foreignKey, req, run).then(exists => {
|
|
430
|
-
if (!exists) {
|
|
431
|
-
result.push(assertError(ASSERT_REFERENCE_INTEGRITY, resolvedElement, foreignKey))
|
|
432
|
-
}
|
|
433
|
-
})
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
return result
|
|
437
|
-
}, result)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const _buildWhereDelete = (result, key, element, data) => {
|
|
441
|
-
return data
|
|
442
|
-
.map(d => {
|
|
443
|
-
return Object.keys(d).reduce((result, name) => {
|
|
444
|
-
if (key.ref[0] === name) {
|
|
445
|
-
if (result.length > 0) {
|
|
446
|
-
result.push('and')
|
|
447
|
-
}
|
|
448
|
-
result.push({ ref: [_getFullForeignKeyName(element.name, key.ref[0])] }, '=', { val: d[name] })
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return result
|
|
452
|
-
}, result)
|
|
453
|
-
})
|
|
454
|
-
.reduce((accumulatedWhere, currentWhere, i) => {
|
|
455
|
-
if (i > 0) accumulatedWhere.push('or')
|
|
456
|
-
accumulatedWhere.push(...currentWhere)
|
|
457
|
-
return accumulatedWhere
|
|
458
|
-
}, [])
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const _checkDelete = (result, key, entity, checks, req, csn, run, data) => {
|
|
462
|
-
const elements = csn.definitions[key].elements
|
|
463
|
-
const source = csn.definitions[key].name
|
|
464
|
-
|
|
465
|
-
const dependents = getDependents(req.target, csn) || []
|
|
466
|
-
const sourceDependent = dependents.find(dep => dep.parent.name === source)
|
|
467
|
-
if (!sourceDependent) return result
|
|
468
|
-
|
|
469
|
-
return Object.keys(elements).reduce((result, assoc) => {
|
|
470
|
-
if (!elements[assoc].target || !elements[assoc].keys) return result
|
|
471
|
-
|
|
472
|
-
const targetDependent = dependents.find(dep => dep.target.name === elements[assoc].target)
|
|
473
|
-
if (!targetDependent) return result
|
|
474
|
-
|
|
475
|
-
const where = elements[assoc].keys.reduce((buildWhere, key) => {
|
|
476
|
-
return _buildWhereDelete(buildWhere, key, elements[assoc], data)
|
|
477
|
-
}, [])
|
|
478
|
-
checks.push(
|
|
479
|
-
_checkExistsWhere(source, [where], run).then(exists => {
|
|
480
|
-
if (exists.includes(true)) {
|
|
481
|
-
result.push(assertError(ASSERT_REFERENCE_INTEGRITY, elements[assoc], req.data))
|
|
482
|
-
}
|
|
483
|
-
})
|
|
484
|
-
)
|
|
485
|
-
return result
|
|
486
|
-
}, result)
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function _filterStructured(element, structuredAssocs, prefix) {
|
|
490
|
-
const elements = element.elements
|
|
491
|
-
for (const subElement in elements) {
|
|
492
|
-
if (_filterAssocs(elements[subElement], structuredAssocs, prefix)) {
|
|
493
|
-
structuredAssocs.push([...prefix, elements[subElement].name])
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const _filterAssocs = (element, structuredAssocs, prefix = []) => {
|
|
499
|
-
if (element.elements) {
|
|
500
|
-
_filterStructured(element, structuredAssocs, [...prefix, element.name])
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
return (
|
|
504
|
-
element._isAssociationStrict &&
|
|
505
|
-
!element.virtual &&
|
|
506
|
-
!element.abstract &&
|
|
507
|
-
element[ASSERT_INTEGRITY_ANNOTATION] !== false &&
|
|
508
|
-
!element['@odata.contained'] &&
|
|
509
|
-
!element._target._hasPersistenceSkip
|
|
510
|
-
)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// can be removed ones we switch to db integrity check
|
|
514
|
-
const checkReferenceIntegrity = (entity, data, req, csn, run) => {
|
|
515
|
-
const service = entity._service
|
|
516
|
-
if (entity[ASSERT_INTEGRITY_ANNOTATION] === false || (service && service[ASSERT_INTEGRITY_ANNOTATION] === false)) {
|
|
517
|
-
return
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (!Array.isArray(data)) data = [data]
|
|
521
|
-
|
|
522
|
-
const checks = []
|
|
523
|
-
let result
|
|
524
|
-
if (req.event === 'CREATE' || req.event === 'UPDATE') {
|
|
525
|
-
const structuredAssocRefs = []
|
|
526
|
-
const associationRefs = Object.keys(entity.elements)
|
|
527
|
-
.filter(elementName => _filterAssocs(entity.elements[elementName], structuredAssocRefs))
|
|
528
|
-
.map(name => [name])
|
|
529
|
-
result = [...associationRefs, ...structuredAssocRefs].reduce((createUpdateResult, ref) => {
|
|
530
|
-
return _checkCreateUpdate(createUpdateResult, ref, entity, checks, data, req, run)
|
|
531
|
-
}, [])
|
|
532
|
-
}
|
|
533
|
-
if (req.event === 'DELETE') {
|
|
534
|
-
// we are only interested in table-level references not all derived ones on view levels
|
|
535
|
-
// TODO: why?
|
|
536
|
-
while (entity.query && entity.query._target) {
|
|
537
|
-
entity = csn.definitions[entity.query._target.name]
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
result = Object.keys(csn.definitions)
|
|
541
|
-
.filter(
|
|
542
|
-
key =>
|
|
543
|
-
!csn.definitions[key]['@cds.persistence.skip'] &&
|
|
544
|
-
csn.definitions[key].elements !== undefined &&
|
|
545
|
-
// skip check for events, aspects and localized tables
|
|
546
|
-
csn.definitions[key].kind !== 'event' &&
|
|
547
|
-
csn.definitions[key].kind !== 'aspect' &&
|
|
548
|
-
csn.definitions[key].kind !== 'type' &&
|
|
549
|
-
!csn.definitions[key].name.startsWith('localized.')
|
|
550
|
-
)
|
|
551
|
-
.reduce((deleteResult, key) => {
|
|
552
|
-
return _checkDelete(deleteResult, key, entity, checks, req, csn, run, data)
|
|
553
|
-
}, [])
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (checks.length) {
|
|
557
|
-
return Promise.all(checks).then(() => {
|
|
558
|
-
return result
|
|
559
|
-
})
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
return resolve(result || [])
|
|
563
|
-
}
|
|
564
|
-
|
|
565
305
|
const checkKeys = (entity, data) => {
|
|
566
306
|
if (!Array.isArray(data)) {
|
|
567
307
|
return checkKeys(entity, [data])
|
|
@@ -583,7 +323,6 @@ module.exports = {
|
|
|
583
323
|
checkStatic,
|
|
584
324
|
checkInputConstraints,
|
|
585
325
|
checkKeys,
|
|
586
|
-
checkReferenceIntegrity,
|
|
587
326
|
assertError,
|
|
588
327
|
checkIfAssocDeep
|
|
589
328
|
}
|
package/libx/_runtime/cds.js
CHANGED
|
@@ -22,15 +22,12 @@ Object.defineProperty(cds, '_mtxEnabled', {
|
|
|
22
22
|
* (lazy) feature flags
|
|
23
23
|
*/
|
|
24
24
|
// referential integrity
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
get: () => (assertIntegrity != null ? assertIntegrity : !cds.env.features._foreign_key_constraints),
|
|
32
|
-
set: val => {
|
|
33
|
-
assertIntegrity = val
|
|
25
|
+
// REVISIT: why is _db_foreign_key_constraints necessary?
|
|
26
|
+
Object.defineProperty(cds.env.features, '_db_foreign_key_constraints', {
|
|
27
|
+
get: () => {
|
|
28
|
+
const { assert_integrity: ai, assert_integrity_type: ait } = cds.env.features
|
|
29
|
+
if ((typeof ai === 'string' && ai.match(/individual/i)) || (ait && ait.match(/db/i))) return true
|
|
30
|
+
return false
|
|
34
31
|
},
|
|
35
32
|
configurable: true
|
|
36
33
|
})
|
|
@@ -35,7 +35,7 @@ module.exports = class {
|
|
|
35
35
|
'__isDraftEnabled',
|
|
36
36
|
(this.associations && this.associations.DraftAdministrativeData) ||
|
|
37
37
|
this.name.match(/\.DraftAdministrativeData$/) ||
|
|
38
|
-
this.own('@odata.draft.enabled')
|
|
38
|
+
(this.own('@odata.draft.enabled') && this.own('@Common.DraftRoot.ActivationAction'))
|
|
39
39
|
)
|
|
40
40
|
)
|
|
41
41
|
}
|
|
@@ -140,7 +140,8 @@ const _addToCQNs = (cqns, subCQN, element, definitions, level) => {
|
|
|
140
140
|
// Since `>2.5.2` compiler generates constraints for compositions of one like for annotations
|
|
141
141
|
// Thus only single 2one case (`$self`-managed composition) has DELETE CASCADE
|
|
142
142
|
// Here it's ignored to simplify i.e. handle all "2ones" in a same manner
|
|
143
|
-
|
|
143
|
+
// REVISIT: why is _db_foreign_key_constraints necessary?
|
|
144
|
+
if (!cds.env.features._db_foreign_key_constraints || _is2oneComposition(element, definitions)) {
|
|
144
145
|
cqns[level].push(subCQN)
|
|
145
146
|
}
|
|
146
147
|
}
|
|
@@ -251,7 +252,8 @@ const getDeepDeleteCQNs = (definitions, cqn) => {
|
|
|
251
252
|
_recursivelyAliasRefs(parentWhere, 'ALIAS0', parentAlias)
|
|
252
253
|
}
|
|
253
254
|
const setNullUpdates = []
|
|
254
|
-
|
|
255
|
+
// REVISIT: why is _db_foreign_key_constraints necessary?
|
|
256
|
+
if (cds.env.features._db_foreign_key_constraints && definitions[entityName].own('__oneCompositionParents')) {
|
|
255
257
|
setNullUpdates.push(..._getSetNullParentForeignKeyCQNs(definitions, entityName, parentWhere, draft))
|
|
256
258
|
}
|
|
257
259
|
const subCascadeDeletes = _addSubCascadeDeleteCQN(definitions, compositionTree, parentWhere, 0, [], draft)
|
|
@@ -76,10 +76,13 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, definitions) =>
|
|
|
76
76
|
const oldVal = ctUtils.val(oldData[key])
|
|
77
77
|
|
|
78
78
|
if (newVal !== undefined && newVal !== oldVal) {
|
|
79
|
+
if (!entity.elements[key]) continue
|
|
80
|
+
|
|
79
81
|
if (entity.elements[key]._isStructured && Object.keys(newData[key]).length === 0) {
|
|
80
82
|
// empty structured -> skip
|
|
81
83
|
continue
|
|
82
84
|
}
|
|
85
|
+
|
|
83
86
|
result[key] = newData[key]
|
|
84
87
|
continue
|
|
85
88
|
}
|
|
@@ -135,36 +138,12 @@ function _addSubDeepUpdateCQNForUpdateInsert({
|
|
|
135
138
|
return deepUpdateData
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
function _addSubDeepUpdateCQNCollectDelete(deleteCQNs, cqns, index) {
|
|
139
|
-
deleteCQNs.forEach(deleteCQN => {
|
|
140
|
-
if (
|
|
141
|
-
!cqns.find((subCQNs, subIndex) => {
|
|
142
|
-
if (subIndex > 0) {
|
|
143
|
-
const deleteIndex = subCQNs.findIndex(cqn => {
|
|
144
|
-
return cqn.DELETE && cqn.DELETE.from === deleteCQN.DELETE.from
|
|
145
|
-
})
|
|
146
|
-
if (deleteIndex > -1) {
|
|
147
|
-
if (subIndex < index) {
|
|
148
|
-
subCQNs.splice(deleteIndex, 1)
|
|
149
|
-
} else {
|
|
150
|
-
return true
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return false
|
|
155
|
-
})
|
|
156
|
-
) {
|
|
157
|
-
cqns[index] = cqns[index] || []
|
|
158
|
-
cqns[index].push(deleteCQN)
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
|
|
163
141
|
function _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN) {
|
|
164
142
|
if (updateCQNs.length > 0) {
|
|
165
143
|
cqns[0] = cqns[0] || []
|
|
166
144
|
cqns[0].push(...updateCQNs)
|
|
167
145
|
}
|
|
146
|
+
|
|
168
147
|
if (insertCQN.INSERT.entries.length > 0) {
|
|
169
148
|
cqns[0] = cqns[0] || []
|
|
170
149
|
const deepInsertCQNs = getDeepInsertCQNs(definitions, insertCQN)
|
|
@@ -184,7 +163,10 @@ function _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, d
|
|
|
184
163
|
cqns[0] = cqns[0] || []
|
|
185
164
|
const deepDeleteCQNs = getDeepDeleteCQNs(definitions, deleteCQN)
|
|
186
165
|
deepDeleteCQNs.forEach((deleteCQNs, index) => {
|
|
187
|
-
|
|
166
|
+
deleteCQNs.forEach(el => {
|
|
167
|
+
cqns[index] = cqns[index] || []
|
|
168
|
+
cqns[index].push(el)
|
|
169
|
+
})
|
|
188
170
|
})
|
|
189
171
|
}
|
|
190
172
|
}
|
|
@@ -200,15 +182,17 @@ const _addToData = (subData, entity, element, entry) => {
|
|
|
200
182
|
|
|
201
183
|
function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, data, selectData, cqns, draft }) {
|
|
202
184
|
const selectDataByKey = _dataByKey(entity, selectData)
|
|
185
|
+
|
|
203
186
|
for (const element of compositionTree.compositionElements) {
|
|
204
187
|
const subData = []
|
|
205
188
|
const selectSubData = []
|
|
189
|
+
|
|
206
190
|
for (const entry of data) {
|
|
207
191
|
if (element.name in entry) {
|
|
208
192
|
const selectEntry = selectDataByKey.get(_serializedKey(entity, entry))
|
|
209
193
|
|
|
210
194
|
if (selectEntry && element.name in selectEntry) {
|
|
211
|
-
if (selectEntry[element.name] === null &&
|
|
195
|
+
if (selectEntry[element.name] === null && entry[element.name] === null) {
|
|
212
196
|
continue
|
|
213
197
|
}
|
|
214
198
|
_addToData(selectSubData, entity, element, selectEntry)
|
|
@@ -217,6 +201,7 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
|
|
|
217
201
|
_addToData(subData, entity, element, entry)
|
|
218
202
|
}
|
|
219
203
|
}
|
|
204
|
+
|
|
220
205
|
_addSubDeepUpdateCQN({
|
|
221
206
|
definitions,
|
|
222
207
|
compositionTree: element,
|
|
@@ -226,6 +211,7 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
|
|
|
226
211
|
draft
|
|
227
212
|
})
|
|
228
213
|
}
|
|
214
|
+
|
|
229
215
|
return cqns
|
|
230
216
|
}
|
|
231
217
|
|
|
@@ -250,11 +236,11 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
|
|
|
250
236
|
insertCQN,
|
|
251
237
|
definitions
|
|
252
238
|
})
|
|
239
|
+
|
|
253
240
|
_addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN)
|
|
254
241
|
|
|
255
|
-
if (deepUpdateData.length === 0)
|
|
256
|
-
|
|
257
|
-
}
|
|
242
|
+
if (deepUpdateData.length === 0) return Promise.resolve()
|
|
243
|
+
|
|
258
244
|
return _addSubDeepUpdateCQNRecursion({
|
|
259
245
|
definitions,
|
|
260
246
|
compositionTree,
|
|
@@ -272,15 +258,16 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
|
|
|
272
258
|
|
|
273
259
|
const hasDeepUpdate = (definitions, cqn) => {
|
|
274
260
|
if (cqn && cqn.UPDATE && cqn.UPDATE.entity && (cqn.UPDATE.data || cqn.UPDATE.with)) {
|
|
275
|
-
const
|
|
276
|
-
|
|
261
|
+
const updateEntity = cqn.UPDATE.entity
|
|
262
|
+
const entityName = (updateEntity.ref && updateEntity.ref[0]) || updateEntity.name || updateEntity
|
|
277
263
|
const entity = definitions && definitions[ensureNoDraftsSuffix(entityName)]
|
|
264
|
+
|
|
278
265
|
if (entity) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
})
|
|
266
|
+
const keys = Object.keys(Object.assign({}, cqn.UPDATE.data || {}, cqn.UPDATE.with || {}))
|
|
267
|
+
return !!keys.find(k => ctUtils.isCompOrAssoc(entity, k))
|
|
282
268
|
}
|
|
283
269
|
}
|
|
270
|
+
|
|
284
271
|
return false
|
|
285
272
|
}
|
|
286
273
|
|
|
@@ -32,13 +32,9 @@ const val = element => (element && element.val) || element
|
|
|
32
32
|
|
|
33
33
|
const array = x => (Array.isArray(x) ? x : [x])
|
|
34
34
|
|
|
35
|
-
const isCompOrAssoc = (entity,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
entity.elements[k] &&
|
|
39
|
-
entity.elements[k].isAssociation &&
|
|
40
|
-
((onlyToOne && entity.elements[k].is2one) || !onlyToOne)
|
|
41
|
-
)
|
|
35
|
+
const isCompOrAssoc = (entity, elementName, onlyToOne) => {
|
|
36
|
+
const element = entity.elements && entity.elements[elementName]
|
|
37
|
+
return !!(element && element.isAssociation && ((onlyToOne && element.is2one) || !onlyToOne))
|
|
42
38
|
}
|
|
43
39
|
|
|
44
40
|
const cleanDeepData = (entity, data, onlyToOne = false) => {
|
|
@@ -8,7 +8,8 @@ const { SELECT } = cds.ql
|
|
|
8
8
|
const { getRequiresAsArray } = require('../utils/auth')
|
|
9
9
|
const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
|
|
10
10
|
const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
11
|
-
const {
|
|
11
|
+
const { ensureNoDraftsSuffix } = require('../../fiori/utils/handler')
|
|
12
|
+
const { rewriteExpandAsterisk } = require('../../common/utils/rewriteAsterisks')
|
|
12
13
|
|
|
13
14
|
const WRITE = ['CREATE', 'UPDATE', 'DELETE']
|
|
14
15
|
const MOD = { UPDATE: 1, DELETE: 1, EDIT: 1 }
|
|
@@ -41,6 +42,48 @@ const _reject = req => {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
const _getTarget = (ref, target, definitions) => {
|
|
46
|
+
if (cds.env.effective.odata.proxies) {
|
|
47
|
+
const target_ = target.elements[ref[0]]
|
|
48
|
+
|
|
49
|
+
if (ref.length === 1) {
|
|
50
|
+
return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return _getTarget(ref.slice(1), target_, definitions)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const target_ = target.elements[ref.join('_')]
|
|
57
|
+
return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const _getRestrictedExpand = (columns, target, definitions) => {
|
|
61
|
+
if (!columns || !target || columns === '*') return
|
|
62
|
+
|
|
63
|
+
const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
|
|
64
|
+
const restrictions = annotation && annotation.map(element => element['='])
|
|
65
|
+
|
|
66
|
+
rewriteExpandAsterisk(columns, target)
|
|
67
|
+
|
|
68
|
+
for (const col of columns) {
|
|
69
|
+
if (col.expand) {
|
|
70
|
+
if (restrictions && restrictions.length !== 0) {
|
|
71
|
+
const ref = col.ref.join('_')
|
|
72
|
+
const ref_ = restrictions.find(element => element.replace(/\./g, '_') === ref)
|
|
73
|
+
if (ref_) return ref_
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// expand: '**' or '*3' is only possible within custom handler, no check needed
|
|
77
|
+
if (typeof col.expand === 'string' && /^\*{1}[\d|*]+/.test(col.expand)) {
|
|
78
|
+
continue
|
|
79
|
+
} else {
|
|
80
|
+
const restricted = _getRestrictedExpand(col.expand, _getTarget(col.ref, target, definitions), definitions)
|
|
81
|
+
if (restricted) return restricted
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
44
87
|
const _getCurrentSubClause = (next, restrict) => {
|
|
45
88
|
const escaped = next[0].replace(/\$/g, '\\$').replace(/\./g, '\\.')
|
|
46
89
|
const re1 = new RegExp(`([\\w\\.']*)\\s*=\\s*(${escaped})|(${escaped})\\s*=\\s*([\\w\\.']*)`)
|
|
@@ -367,7 +410,7 @@ const _getRestrictionForTarget = (resolvedApplicables, target) => {
|
|
|
367
410
|
|
|
368
411
|
const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
|
|
369
412
|
if (req.target._isDraftEnabled) {
|
|
370
|
-
req.query._draftRestrictions = resolvedApplicables
|
|
413
|
+
req.query._draftRestrictions = resolvedApplicables
|
|
371
414
|
return
|
|
372
415
|
}
|
|
373
416
|
|
|
@@ -402,12 +445,11 @@ const _getUnrestrictedCount = async req => {
|
|
|
402
445
|
const _getRestrictedCount = async (req, model, resolvedApplicables) => {
|
|
403
446
|
const dbtx = cds.tx(req)
|
|
404
447
|
|
|
405
|
-
|
|
448
|
+
const target =
|
|
406
449
|
(req.query.UPDATE && req.query.UPDATE.entity) ||
|
|
407
450
|
(req.query.DELETE && req.query.DELETE.from) ||
|
|
408
451
|
(req.query.SELECT && req.query.SELECT.from)
|
|
409
|
-
|
|
410
|
-
if (req._ && req._.event === 'draftActivate') target = ensureDraftsSuffix(target)
|
|
452
|
+
|
|
411
453
|
const selectRestricted = SELECT.one(['count(*) as n']).from(target)
|
|
412
454
|
|
|
413
455
|
const whereRestricted = (req.query.UPDATE && req.query.UPDATE.where) || (req.query.DELETE && req.query.DELETE.where)
|
|
@@ -541,36 +583,9 @@ const _addNormalizedRestrictPerGrant = (grant, where, restrict, restricts, defin
|
|
|
541
583
|
}
|
|
542
584
|
|
|
543
585
|
const _addNormalizedRestrict = (restrict, restricts, definition, definitions) => {
|
|
544
|
-
|
|
586
|
+
const where = restrict.where
|
|
545
587
|
? restrict.where.replace(/\$user/g, '$user.id').replace(/\$user\.id\./g, '$user.')
|
|
546
588
|
: undefined
|
|
547
|
-
|
|
548
|
-
// NOTE: "exists toMany.toOne[prop = $user]" -> "exists toMany[exists toOne[prop = $user]]"
|
|
549
|
-
try {
|
|
550
|
-
if (where) {
|
|
551
|
-
// operate on a copy
|
|
552
|
-
let _where = where
|
|
553
|
-
// find all path expressions in order to normalize shorthand (i.e., inject "[exists ...]")
|
|
554
|
-
const paths = (where.match(/ (\w\.*)*/g) || []).filter(m => m.match(/\./) && m !== ' ')
|
|
555
|
-
for (let i = 0; i < paths.length; i++) {
|
|
556
|
-
const parts = paths[i].trim().split('.')
|
|
557
|
-
let current = definition
|
|
558
|
-
while (parts.length) {
|
|
559
|
-
current = current.elements[parts.shift()]
|
|
560
|
-
if (current.isAssociation && _where.includes(current.name + '.')) {
|
|
561
|
-
const matches = _where.match(new RegExp(`(${current.name}).(.*)]`))
|
|
562
|
-
_where = _where.replace(`${matches[1]}.`, `${current.name}[exists `)
|
|
563
|
-
_where = _where.replace(matches[2], `${matches[2]}]`)
|
|
564
|
-
}
|
|
565
|
-
if (current.target) current = definitions[current.target]
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
where = _where
|
|
569
|
-
}
|
|
570
|
-
} catch (e) {
|
|
571
|
-
// ignore
|
|
572
|
-
}
|
|
573
|
-
|
|
574
589
|
restrict.grant = Array.isArray(restrict.grant) ? restrict.grant : [restrict.grant || '*']
|
|
575
590
|
restrict.grant.forEach(grant => _addNormalizedRestrictPerGrant(grant, where, restrict, restricts, definition))
|
|
576
591
|
}
|
|
@@ -888,9 +903,24 @@ const _secureDependentEntities = srv => {
|
|
|
888
903
|
}
|
|
889
904
|
}
|
|
890
905
|
|
|
906
|
+
const _restrictExpand = service => {
|
|
907
|
+
service.on('READ', '*', (req, next) => {
|
|
908
|
+
const restricted = _getRestrictedExpand(
|
|
909
|
+
req.query.SELECT && req.query.SELECT.columns,
|
|
910
|
+
req.target,
|
|
911
|
+
service.model.definitions
|
|
912
|
+
)
|
|
913
|
+
if (restricted) {
|
|
914
|
+
return req.reject(400, 'EXPAND_IS_RESTRICTED', [restricted])
|
|
915
|
+
}
|
|
916
|
+
return next()
|
|
917
|
+
})
|
|
918
|
+
}
|
|
919
|
+
|
|
891
920
|
module.exports = cds.service.impl(function () {
|
|
892
921
|
// @restrict, @requires, @readonly, @insertonly, and @Capabilities for entities
|
|
893
922
|
_secureDependentEntities(this)
|
|
923
|
+
_restrictExpand(this)
|
|
894
924
|
for (const k in this.entities) {
|
|
895
925
|
const entity = this.entities[k]
|
|
896
926
|
if (!_authDependsOnParents(entity)) _registerAuthHandlers(entity, this)
|