@sap/cds 5.6.4 → 5.7.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 +102 -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 +3 -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 +8 -3
- 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 +3 -1
- package/lib/log/index.js +2 -2
- 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 +24 -0
- 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/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 +3 -6
- 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 -38
- 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 +61 -30
- package/libx/_runtime/common/generic/crud.js +11 -23
- package/libx/_runtime/common/generic/input.js +20 -0
- 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 +289 -114
- 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/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 +123 -57
- package/libx/_runtime/fiori/uiflex/index.js +1 -1
- package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +3 -3
- 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/odata/{odata2cqn/afterburner.js → afterburner.js} +19 -15
- package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
- package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +171 -130
- package/libx/odata/index.js +16 -14
- 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,18 +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 (
|
|
212
|
-
selectEntry[element.name] === null &&
|
|
213
|
-
(entry[element.name] === null || Object.keys(entry[element.name]).length === 0)
|
|
214
|
-
) {
|
|
195
|
+
if (selectEntry[element.name] === null && entry[element.name] === null) {
|
|
215
196
|
continue
|
|
216
197
|
}
|
|
217
198
|
_addToData(selectSubData, entity, element, selectEntry)
|
|
@@ -220,6 +201,7 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
|
|
|
220
201
|
_addToData(subData, entity, element, entry)
|
|
221
202
|
}
|
|
222
203
|
}
|
|
204
|
+
|
|
223
205
|
_addSubDeepUpdateCQN({
|
|
224
206
|
definitions,
|
|
225
207
|
compositionTree: element,
|
|
@@ -229,6 +211,7 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
|
|
|
229
211
|
draft
|
|
230
212
|
})
|
|
231
213
|
}
|
|
214
|
+
|
|
232
215
|
return cqns
|
|
233
216
|
}
|
|
234
217
|
|
|
@@ -253,11 +236,11 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
|
|
|
253
236
|
insertCQN,
|
|
254
237
|
definitions
|
|
255
238
|
})
|
|
239
|
+
|
|
256
240
|
_addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN)
|
|
257
241
|
|
|
258
|
-
if (deepUpdateData.length === 0)
|
|
259
|
-
|
|
260
|
-
}
|
|
242
|
+
if (deepUpdateData.length === 0) return Promise.resolve()
|
|
243
|
+
|
|
261
244
|
return _addSubDeepUpdateCQNRecursion({
|
|
262
245
|
definitions,
|
|
263
246
|
compositionTree,
|
|
@@ -275,15 +258,16 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
|
|
|
275
258
|
|
|
276
259
|
const hasDeepUpdate = (definitions, cqn) => {
|
|
277
260
|
if (cqn && cqn.UPDATE && cqn.UPDATE.entity && (cqn.UPDATE.data || cqn.UPDATE.with)) {
|
|
278
|
-
const
|
|
279
|
-
|
|
261
|
+
const updateEntity = cqn.UPDATE.entity
|
|
262
|
+
const entityName = (updateEntity.ref && updateEntity.ref[0]) || updateEntity.name || updateEntity
|
|
280
263
|
const entity = definitions && definitions[ensureNoDraftsSuffix(entityName)]
|
|
264
|
+
|
|
281
265
|
if (entity) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
})
|
|
266
|
+
const keys = Object.keys(Object.assign({}, cqn.UPDATE.data || {}, cqn.UPDATE.with || {}))
|
|
267
|
+
return !!keys.find(k => ctUtils.isCompOrAssoc(entity, k))
|
|
285
268
|
}
|
|
286
269
|
}
|
|
270
|
+
|
|
287
271
|
return false
|
|
288
272
|
}
|
|
289
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 { ensureDraftsSuffix } = require('../../fiori/utils/handler')
|
|
11
|
+
const { ensureDraftsSuffix, 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
|
|
|
@@ -541,36 +584,9 @@ const _addNormalizedRestrictPerGrant = (grant, where, restrict, restricts, defin
|
|
|
541
584
|
}
|
|
542
585
|
|
|
543
586
|
const _addNormalizedRestrict = (restrict, restricts, definition, definitions) => {
|
|
544
|
-
|
|
587
|
+
const where = restrict.where
|
|
545
588
|
? restrict.where.replace(/\$user/g, '$user.id').replace(/\$user\.id\./g, '$user.')
|
|
546
589
|
: 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
590
|
restrict.grant = Array.isArray(restrict.grant) ? restrict.grant : [restrict.grant || '*']
|
|
575
591
|
restrict.grant.forEach(grant => _addNormalizedRestrictPerGrant(grant, where, restrict, restricts, definition))
|
|
576
592
|
}
|
|
@@ -888,9 +904,24 @@ const _secureDependentEntities = srv => {
|
|
|
888
904
|
}
|
|
889
905
|
}
|
|
890
906
|
|
|
907
|
+
const _restrictExpand = service => {
|
|
908
|
+
service.on('READ', '*', (req, next) => {
|
|
909
|
+
const restricted = _getRestrictedExpand(
|
|
910
|
+
req.query.SELECT && req.query.SELECT.columns,
|
|
911
|
+
req.target,
|
|
912
|
+
service.model.definitions
|
|
913
|
+
)
|
|
914
|
+
if (restricted) {
|
|
915
|
+
return req.reject(400, 'EXPAND_IS_RESTRICTED', [restricted])
|
|
916
|
+
}
|
|
917
|
+
return next()
|
|
918
|
+
})
|
|
919
|
+
}
|
|
920
|
+
|
|
891
921
|
module.exports = cds.service.impl(function () {
|
|
892
922
|
// @restrict, @requires, @readonly, @insertonly, and @Capabilities for entities
|
|
893
923
|
_secureDependentEntities(this)
|
|
924
|
+
_restrictExpand(this)
|
|
894
925
|
for (const k in this.entities) {
|
|
895
926
|
const entity = this.entities[k]
|
|
896
927
|
if (!_authDependsOnParents(entity)) _registerAuthHandlers(entity, this)
|