@sap/cds 5.6.1 → 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 +136 -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 +1 -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/invocation/SetResponseHeadersCommand.js +1 -0
- 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/result.js +18 -15
- 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 +27 -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 +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/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 +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 +82 -61
- package/libx/_runtime/db/expand/index.js +3 -1
- package/libx/_runtime/db/generic/arrayed.js +14 -27
- 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 +2 -1
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +151 -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 +21 -2
- 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} +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 +13 -10
- 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,9 +1,7 @@
|
|
|
1
1
|
const cds = require('../../../cds')
|
|
2
|
-
|
|
3
2
|
const { SELECT } = cds.ql
|
|
4
3
|
|
|
5
|
-
const {
|
|
6
|
-
const { processDeep, processDeepAsync } = require('../../util/dataProcessUtils')
|
|
4
|
+
const { processDeep } = require('../../util/dataProcessUtils')
|
|
7
5
|
|
|
8
6
|
const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
|
|
9
7
|
|
|
@@ -145,43 +143,6 @@ const flattenDeepToOneAssociations = (req, csn) => {
|
|
|
145
143
|
)
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
const checkIntegrityWrapper = (req, csn, run) => async (data, entity) => {
|
|
149
|
-
const errors = await checkReferenceIntegrity(entity, data, req, csn, run)
|
|
150
|
-
if (errors && errors.length !== 0) {
|
|
151
|
-
for (const err of errors) {
|
|
152
|
-
req.error(err)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const _isUncheckableInsert = query => {
|
|
158
|
-
return query.INSERT && (query.INSERT.rows || query.INSERT.values || query.INSERT.as)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// REVISIT: lower to db layer, where it's used
|
|
162
|
-
const checkIntegrityUtil = async (req, csn, run) => {
|
|
163
|
-
if (!run) return
|
|
164
|
-
|
|
165
|
-
// REVISIT
|
|
166
|
-
if (typeof req.query === 'string' || req.target._unresolved) return
|
|
167
|
-
|
|
168
|
-
// FIXME: doesn't work for uncheckable inserts
|
|
169
|
-
if (_isUncheckableInsert(req.query)) return
|
|
170
|
-
|
|
171
|
-
// REVISIT: integrity check needs context.data
|
|
172
|
-
if (Object.keys(req.data).length === 0) {
|
|
173
|
-
// REVISIT: We may need to double-check re req.data being undefined or empty
|
|
174
|
-
if (req.query.DELETE) {
|
|
175
|
-
req.data = req._beforeDeleteData || {}
|
|
176
|
-
} else if (req.context && req.context.data && Object.keys(req.context.data).length > 0) {
|
|
177
|
-
req.data = req.context.data
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (Object.keys(req.data).length === 0) return
|
|
181
|
-
|
|
182
|
-
await processDeepAsync(checkIntegrityWrapper(req, csn, run), req.data, req.target, false, true)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
146
|
/*
|
|
186
147
|
* merge CQNs
|
|
187
148
|
*/
|
|
@@ -235,6 +196,5 @@ const getDeepSelect = req => {
|
|
|
235
196
|
module.exports = {
|
|
236
197
|
getDeepSelect,
|
|
237
198
|
allKeysAreProvided,
|
|
238
|
-
checkIntegrityUtil,
|
|
239
199
|
flattenDeepToOneAssociations
|
|
240
200
|
}
|
|
@@ -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,26 @@ 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
|
-
_addToData(subData, entity, element, entry)
|
|
209
192
|
const selectEntry = selectDataByKey.get(_serializedKey(entity, entry))
|
|
193
|
+
|
|
210
194
|
if (selectEntry && element.name in selectEntry) {
|
|
195
|
+
if (selectEntry[element.name] === null && entry[element.name] === null) {
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
211
198
|
_addToData(selectSubData, entity, element, selectEntry)
|
|
212
199
|
}
|
|
200
|
+
|
|
201
|
+
_addToData(subData, entity, element, entry)
|
|
213
202
|
}
|
|
214
203
|
}
|
|
204
|
+
|
|
215
205
|
_addSubDeepUpdateCQN({
|
|
216
206
|
definitions,
|
|
217
207
|
compositionTree: element,
|
|
@@ -221,6 +211,7 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
|
|
|
221
211
|
draft
|
|
222
212
|
})
|
|
223
213
|
}
|
|
214
|
+
|
|
224
215
|
return cqns
|
|
225
216
|
}
|
|
226
217
|
|
|
@@ -245,11 +236,11 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
|
|
|
245
236
|
insertCQN,
|
|
246
237
|
definitions
|
|
247
238
|
})
|
|
239
|
+
|
|
248
240
|
_addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN)
|
|
249
241
|
|
|
250
|
-
if (deepUpdateData.length === 0)
|
|
251
|
-
|
|
252
|
-
}
|
|
242
|
+
if (deepUpdateData.length === 0) return Promise.resolve()
|
|
243
|
+
|
|
253
244
|
return _addSubDeepUpdateCQNRecursion({
|
|
254
245
|
definitions,
|
|
255
246
|
compositionTree,
|
|
@@ -267,15 +258,16 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
|
|
|
267
258
|
|
|
268
259
|
const hasDeepUpdate = (definitions, cqn) => {
|
|
269
260
|
if (cqn && cqn.UPDATE && cqn.UPDATE.entity && (cqn.UPDATE.data || cqn.UPDATE.with)) {
|
|
270
|
-
const
|
|
271
|
-
|
|
261
|
+
const updateEntity = cqn.UPDATE.entity
|
|
262
|
+
const entityName = (updateEntity.ref && updateEntity.ref[0]) || updateEntity.name || updateEntity
|
|
272
263
|
const entity = definitions && definitions[ensureNoDraftsSuffix(entityName)]
|
|
264
|
+
|
|
273
265
|
if (entity) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
})
|
|
266
|
+
const keys = Object.keys(Object.assign({}, cqn.UPDATE.data || {}, cqn.UPDATE.with || {}))
|
|
267
|
+
return !!keys.find(k => ctUtils.isCompOrAssoc(entity, k))
|
|
277
268
|
}
|
|
278
269
|
}
|
|
270
|
+
|
|
279
271
|
return false
|
|
280
272
|
}
|
|
281
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) => {
|