@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,32 +1,375 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
|
+
const crypto = require('crypto')
|
|
5
|
+
|
|
4
6
|
const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
|
|
5
|
-
const {
|
|
7
|
+
const { all, resolve } = require('../../common/utils/thenable')
|
|
8
|
+
const { assertError } = require('../../cds-services/util/assert')
|
|
9
|
+
const { processDeepAsync } = require('../../cds-services/util/dataProcessUtils')
|
|
10
|
+
|
|
11
|
+
const ASSERT_REFERENCE_INTEGRITY = 'ASSERT_REFERENCE_INTEGRITY'
|
|
12
|
+
const ASSERT_INTEGRITY_ANNOTATION = '@assert.integrity'
|
|
13
|
+
|
|
14
|
+
const CRUD = { CREATE: 1, READ: 1, UPDATE: 1, DELETE: 1 }
|
|
15
|
+
const C_UD = { CREATE: 1, INSERT: 1, UPDATE: 1, DELETE: 1 }
|
|
6
16
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
/*
|
|
18
|
+
* UTILS
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const _requiresRuntimeCheck = def => {
|
|
22
|
+
const val = String(def['@assert.integrity']).toLowerCase()
|
|
23
|
+
if (val === 'rt') return true
|
|
24
|
+
if (val === 'db' || val === 'false') return false
|
|
25
|
+
if (val === 'true') {
|
|
26
|
+
const { assert_integrity_type: ait } = cds.env.features
|
|
27
|
+
if (ait && ait.match(/rt/i)) return true
|
|
28
|
+
}
|
|
12
29
|
}
|
|
13
30
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
20
|
-
|
|
31
|
+
const _runtimeShallCheckIntegrityFor = (assoc, inclComps) => {
|
|
32
|
+
const { parent } = assoc
|
|
33
|
+
|
|
34
|
+
// disqualifications
|
|
35
|
+
if (!assoc._isAssociationStrict && !inclComps) return
|
|
36
|
+
if (!assoc.is2one) return
|
|
37
|
+
if (assoc.on) return
|
|
38
|
+
if (assoc._isCompositionBacklink) return
|
|
39
|
+
if (parent['@cds.persistence.skip']) return
|
|
40
|
+
|
|
41
|
+
// check @assert.integrity bottom-up and break on value (i.e., lowest spec wins)
|
|
42
|
+
if ('@assert.integrity' in assoc) {
|
|
43
|
+
const val = _requiresRuntimeCheck(assoc)
|
|
44
|
+
if (val !== undefined) return val
|
|
45
|
+
}
|
|
46
|
+
if ('@assert.integrity' in parent) {
|
|
47
|
+
const val = _requiresRuntimeCheck(parent)
|
|
48
|
+
if (val !== undefined) return val
|
|
49
|
+
}
|
|
50
|
+
if (parent._service && '@assert.integrity' in parent._service) {
|
|
51
|
+
const val = _requiresRuntimeCheck(parent._service)
|
|
52
|
+
if (val !== undefined) return val
|
|
21
53
|
}
|
|
22
54
|
|
|
23
|
-
|
|
55
|
+
// here, no disqualification and no @assert.integrity specified -> global setting
|
|
56
|
+
const { assert_integrity: ai, assert_integrity_type: ait } = cds.env.features
|
|
57
|
+
if (typeof ai === 'string' && ai.match(/individual/i)) return false
|
|
58
|
+
if (ait && ait.match(/db/i)) return false
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
* this modifies the csn on purpose for caching effect!
|
|
64
|
+
* doing as aspect is difficult due to no global definitons per tenant
|
|
65
|
+
*/
|
|
66
|
+
const _getDependents = (entity, model) => {
|
|
67
|
+
if (entity.own('__dependents')) return entity.__dependents
|
|
68
|
+
|
|
69
|
+
/** @type {Array|boolean} */
|
|
70
|
+
let dependents = []
|
|
71
|
+
for (const def of Object.values(model.definitions)) {
|
|
72
|
+
if (def.kind !== 'entity') continue
|
|
73
|
+
if (!def.associations) continue
|
|
74
|
+
|
|
75
|
+
for (const assoc of Object.values(def.associations)) {
|
|
76
|
+
if (assoc.target !== entity.name) continue
|
|
77
|
+
|
|
78
|
+
if (_runtimeShallCheckIntegrityFor(assoc)) {
|
|
79
|
+
dependents.push({ /* element: assoc, */ parent: assoc.parent, target: model.definitions[assoc.target] })
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (dependents.length === 0) dependents = false
|
|
85
|
+
return entity.set('__dependents', dependents)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const _getElement = (entity, ref) => {
|
|
89
|
+
if (ref.length > 1) return _getElement(entity.elements[ref[0]], ref.slice(1))
|
|
90
|
+
return entity.elements[ref[0]]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const _getFullForeignKeyName = (elementName, foreignKeyName) => `${elementName}_${foreignKeyName}`
|
|
94
|
+
|
|
95
|
+
const _getDataFromRef = (row, ref) => {
|
|
96
|
+
if (row === undefined) return
|
|
97
|
+
if (ref.length > 1) return _getDataFromRef(row[ref[0]], ref.slice(1))
|
|
98
|
+
return row[ref[0]]
|
|
99
|
+
}
|
|
24
100
|
|
|
101
|
+
const _foreignKeyReducer = (key, foreignKeyName, row, element, ref) => {
|
|
102
|
+
const fullForeignKeyName = _getFullForeignKeyName(element.name, foreignKeyName)
|
|
103
|
+
|
|
104
|
+
if (ref.length > 1) {
|
|
105
|
+
// ref includes assoc name, so we need to replace it by foreign key name
|
|
106
|
+
const refWithFlatForeignKey = [...ref.slice(0, ref.length - 1), fullForeignKeyName]
|
|
107
|
+
key[foreignKeyName] = _getDataFromRef(row, refWithFlatForeignKey)
|
|
108
|
+
} else {
|
|
109
|
+
key[foreignKeyName] = Object.prototype.hasOwnProperty.call(row, fullForeignKeyName) ? row[fullForeignKeyName] : null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return key
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const _buildForeignKey = (element, row, ref) => {
|
|
116
|
+
let foreignKey
|
|
117
|
+
|
|
118
|
+
if (element.keys) {
|
|
119
|
+
foreignKey = element.keys
|
|
120
|
+
.map(obj => obj.ref[obj.ref.length - 1])
|
|
121
|
+
.reduce((key, foreignKeyName) => {
|
|
122
|
+
return _foreignKeyReducer(key, foreignKeyName, row, element, ref)
|
|
123
|
+
}, {})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return foreignKey
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const _checkExists = (entity, data, req, run) => {
|
|
130
|
+
if (!Array.isArray(data)) {
|
|
131
|
+
return _checkExists(entity, [data], req, run).then(result => {
|
|
132
|
+
return result[0]
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const where = data.map(row => {
|
|
137
|
+
return Object.keys(entity.keys).reduce((where, name) => {
|
|
138
|
+
if (row[name] !== undefined && row[name] !== null) {
|
|
139
|
+
if (where.length > 0) {
|
|
140
|
+
where.push('and')
|
|
141
|
+
}
|
|
142
|
+
where.push({ ref: [name] }, '=', { val: row[name] })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return where
|
|
146
|
+
}, [])
|
|
147
|
+
})
|
|
148
|
+
return _checkExistsWhere(entity, where, run)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const _checkCreateUpdate = (result, ref, rootEntity, checks, data, req, run) => {
|
|
152
|
+
const resolvedElement = _getElement(rootEntity, ref)
|
|
153
|
+
|
|
154
|
+
// REVISIT: incl comps?!
|
|
155
|
+
if (!_runtimeShallCheckIntegrityFor(resolvedElement /* true */)) return result
|
|
156
|
+
|
|
157
|
+
// REVISIT: reduce should only be used to build object from array, not for loops!
|
|
158
|
+
return data.reduce((result, row) => {
|
|
159
|
+
const foreignKey = _buildForeignKey(resolvedElement, row, ref)
|
|
160
|
+
if (foreignKey === undefined || Object.values(foreignKey).every(v => v === null)) return result
|
|
161
|
+
|
|
162
|
+
// REVISIT: if incl comps, skip check if target is in payload
|
|
163
|
+
// if (resolvedElement.isComposition && resolvedElement.is2one && row[resolvedElement.name]) return result
|
|
164
|
+
|
|
165
|
+
checks.push(
|
|
166
|
+
_checkExists(resolvedElement._target, foreignKey, req, run).then(exists => {
|
|
167
|
+
if (!exists) {
|
|
168
|
+
result.push(assertError(ASSERT_REFERENCE_INTEGRITY, resolvedElement, foreignKey))
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return result
|
|
174
|
+
}, result)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const _buildWhereDelete = (result, key, element, data) => {
|
|
178
|
+
return data
|
|
179
|
+
.map(d => {
|
|
180
|
+
return Object.keys(d).reduce((result, name) => {
|
|
181
|
+
if (key.ref[0] === name) {
|
|
182
|
+
if (result.length > 0) {
|
|
183
|
+
result.push('and')
|
|
184
|
+
}
|
|
185
|
+
result.push({ ref: [_getFullForeignKeyName(element.name, key.ref[0])] }, '=', { val: d[name] })
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result
|
|
189
|
+
}, result)
|
|
190
|
+
})
|
|
191
|
+
.reduce((accumulatedWhere, currentWhere, i) => {
|
|
192
|
+
if (i > 0) accumulatedWhere.push('or')
|
|
193
|
+
accumulatedWhere.push(...currentWhere)
|
|
194
|
+
return accumulatedWhere
|
|
195
|
+
}, [])
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const _checkExistsWhere = (entity, whereList, run) => {
|
|
199
|
+
const checks = whereList.map(where => {
|
|
200
|
+
if (where.length === 0) return true
|
|
201
|
+
|
|
202
|
+
const query = {
|
|
203
|
+
SELECT: {
|
|
204
|
+
columns: [{ val: 1, as: '_exists' }],
|
|
205
|
+
from: { ref: [entity.name || entity] },
|
|
206
|
+
where: where,
|
|
207
|
+
limit: { rows: { val: 1 } }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (cds.context) {
|
|
212
|
+
const hash = crypto.createHash('sha1').update(JSON.stringify(query)).digest('base64') // fastest hash
|
|
213
|
+
if (!cds.context.__alreadyExecutedIntegrityChecks) cds.context.__alreadyExecutedIntegrityChecks = new Map()
|
|
214
|
+
|
|
215
|
+
if (cds.context.__alreadyExecutedIntegrityChecks.has(hash)) {
|
|
216
|
+
return cds.context.__alreadyExecutedIntegrityChecks.get(hash)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const promise = run(query).then(exists => exists.length !== 0)
|
|
220
|
+
|
|
221
|
+
// we store the promise object in the map, it won't get executed twice when calling await Promise.all([promise, promise])
|
|
222
|
+
cds.context.__alreadyExecutedIntegrityChecks.set(hash, promise)
|
|
223
|
+
return promise
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return run(query).then(exists => exists.length !== 0)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
return all(checks)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const _checkDelete = (result, key, entity, checks, req, csn, run, data) => {
|
|
233
|
+
const elements = csn.definitions[key].elements
|
|
234
|
+
const source = csn.definitions[key].name
|
|
235
|
+
|
|
236
|
+
const dependents = _getDependents(req.target, csn) || []
|
|
237
|
+
|
|
238
|
+
const sourceDependent = dependents.find(dep => dep.parent.name === source)
|
|
239
|
+
if (!sourceDependent) return result
|
|
240
|
+
|
|
241
|
+
return Object.keys(elements).reduce((result, assoc) => {
|
|
242
|
+
if (!elements[assoc].target || !elements[assoc].keys) return result
|
|
243
|
+
|
|
244
|
+
const targetDependent = dependents.find(dep => dep.target.name === elements[assoc].target)
|
|
245
|
+
if (!targetDependent) return result
|
|
246
|
+
|
|
247
|
+
const where = elements[assoc].keys.reduce((buildWhere, key) => {
|
|
248
|
+
return _buildWhereDelete(buildWhere, key, elements[assoc], data)
|
|
249
|
+
}, [])
|
|
250
|
+
checks.push(
|
|
251
|
+
_checkExistsWhere(source, [where], run).then(exists => {
|
|
252
|
+
if (exists.includes(true)) {
|
|
253
|
+
result.push(assertError(ASSERT_REFERENCE_INTEGRITY, elements[assoc], req.data))
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
)
|
|
257
|
+
return result
|
|
258
|
+
}, result)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function _filterStructured(element, structuredAssocs, prefix) {
|
|
262
|
+
const elements = element.elements
|
|
263
|
+
for (const subElement in elements) {
|
|
264
|
+
if (_filterAssocs(elements[subElement], structuredAssocs, prefix)) {
|
|
265
|
+
structuredAssocs.push([...prefix, elements[subElement].name])
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const _filterAssocs = (element, structuredAssocs, prefix = []) => {
|
|
271
|
+
if (element.elements) {
|
|
272
|
+
_filterStructured(element, structuredAssocs, [...prefix, element.name])
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
// element._isAssociationStrict && // > comps handled by deep crud logic
|
|
277
|
+
element.isAssociation &&
|
|
278
|
+
!element.virtual &&
|
|
279
|
+
!element.abstract &&
|
|
280
|
+
element[ASSERT_INTEGRITY_ANNOTATION] !== false &&
|
|
281
|
+
!element._target._hasPersistenceSkip
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const _checkReferenceIntegrity = (entity, data, req, csn, run) => {
|
|
286
|
+
const service = entity._service
|
|
287
|
+
if (entity[ASSERT_INTEGRITY_ANNOTATION] === false || (service && service[ASSERT_INTEGRITY_ANNOTATION] === false)) {
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!Array.isArray(data)) data = [data]
|
|
292
|
+
|
|
293
|
+
const checks = []
|
|
294
|
+
let result
|
|
295
|
+
if (req.event === 'CREATE' || req.event === 'UPDATE') {
|
|
296
|
+
const structuredAssocRefs = []
|
|
297
|
+
const associationRefs = Object.keys(entity.elements)
|
|
298
|
+
.filter(elementName => _filterAssocs(entity.elements[elementName], structuredAssocRefs))
|
|
299
|
+
.map(name => [name])
|
|
300
|
+
result = [...associationRefs, ...structuredAssocRefs].reduce((createUpdateResult, ref) => {
|
|
301
|
+
return _checkCreateUpdate(createUpdateResult, ref, entity, checks, data, req, run)
|
|
302
|
+
}, [])
|
|
303
|
+
}
|
|
304
|
+
if (req.event === 'DELETE') {
|
|
305
|
+
// we are only interested in table-level references not all derived ones on view levels
|
|
306
|
+
// TODO: why?
|
|
307
|
+
while (entity.query && entity.query._target) {
|
|
308
|
+
entity = csn.definitions[entity.query._target.name]
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
result = Object.keys(csn.definitions)
|
|
312
|
+
.filter(
|
|
313
|
+
key =>
|
|
314
|
+
!csn.definitions[key]['@cds.persistence.skip'] &&
|
|
315
|
+
csn.definitions[key].elements !== undefined &&
|
|
316
|
+
// skip check for events, aspects and localized tables
|
|
317
|
+
csn.definitions[key].kind !== 'event' &&
|
|
318
|
+
csn.definitions[key].kind !== 'aspect' &&
|
|
319
|
+
csn.definitions[key].kind !== 'type' &&
|
|
320
|
+
!csn.definitions[key].name.startsWith('localized.')
|
|
321
|
+
)
|
|
322
|
+
.reduce((deleteResult, key) => {
|
|
323
|
+
return _checkDelete(deleteResult, key, entity, checks, req, csn, run, data)
|
|
324
|
+
}, [])
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (checks.length) {
|
|
328
|
+
return Promise.all(checks).then(() => {
|
|
329
|
+
return result
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return resolve(result || [])
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const _checkIntegrityWrapper = (req, csn, run) => async (data, entity) => {
|
|
337
|
+
const errors = await _checkReferenceIntegrity(entity, data, req, csn, run)
|
|
338
|
+
if (errors && errors.length !== 0) for (const err of errors) req.error(err)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const _isUncheckableInsert = query => query.INSERT && (query.INSERT.rows || query.INSERT.values || query.INSERT.as)
|
|
342
|
+
|
|
343
|
+
const _checkIntegrityUtil = async (req, csn, run) => {
|
|
344
|
+
if (!run) return
|
|
345
|
+
if (typeof req.query === 'string' || req.target._unresolved) return
|
|
346
|
+
if (_isUncheckableInsert(req.query)) return
|
|
347
|
+
|
|
348
|
+
// REVISIT: integrity check needs context.data
|
|
349
|
+
if (Object.keys(req.data).length === 0) {
|
|
350
|
+
// REVISIT: We may need to double-check re req.data being undefined or empty
|
|
351
|
+
if (req.query.DELETE) {
|
|
352
|
+
req.data = req._beforeDeleteData || {}
|
|
353
|
+
} else if (req.context && req.context.data && Object.keys(req.context.data).length > 0) {
|
|
354
|
+
req.data = req.context.data
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (Object.keys(req.data).length === 0) return
|
|
358
|
+
|
|
359
|
+
await processDeepAsync(_checkIntegrityWrapper(req, csn, run), req.data, req.target, false, true)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/*
|
|
363
|
+
* HANDLERS
|
|
364
|
+
*/
|
|
365
|
+
|
|
366
|
+
const _skipIntegrityCheck = (req, tx) => {
|
|
367
|
+
if (cds.env.features.assert_integrity === false) return true
|
|
368
|
+
if (!tx.model) return true
|
|
25
369
|
if (req.event in CRUD) {
|
|
26
370
|
if (typeof req.query === 'string') return true
|
|
27
371
|
if (!req.target || req.target._unresolved) return true
|
|
28
372
|
}
|
|
29
|
-
|
|
30
373
|
return false
|
|
31
374
|
}
|
|
32
375
|
|
|
@@ -45,18 +388,18 @@ async function beforeDelete(req) {
|
|
|
45
388
|
if (!target) return
|
|
46
389
|
|
|
47
390
|
// only if target has dependents (i.e., is the target of a managed to one association)
|
|
48
|
-
const dependents =
|
|
391
|
+
const dependents = _getDependents(target, this.model)
|
|
49
392
|
if (!dependents) return
|
|
50
393
|
|
|
51
394
|
const keys = Object.keys(target.keys).filter(k => _isPrimitiveKey(target.elements[k]) && k !== 'IsActiveEntity')
|
|
52
|
-
let
|
|
395
|
+
let selectQuery = SELECT(keys).from(req.target.name)
|
|
396
|
+
|
|
53
397
|
if (req.query.DELETE.where) {
|
|
54
|
-
|
|
398
|
+
selectQuery = selectQuery.where(req.query.DELETE.where)
|
|
55
399
|
}
|
|
56
400
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
req._beforeDeleteData = await this._read(this.model, this.dbc, select, req.context || req)
|
|
401
|
+
selectQuery = cqn2cqn4sql(selectQuery, this.model, { service: this })
|
|
402
|
+
req._beforeDeleteData = await this._read(this.model, this.dbc, selectQuery, req.context || req)
|
|
60
403
|
}
|
|
61
404
|
|
|
62
405
|
beforeDelete._initial = true
|
|
@@ -64,9 +407,6 @@ beforeDelete._initial = true
|
|
|
64
407
|
/*
|
|
65
408
|
* perform check
|
|
66
409
|
*/
|
|
67
|
-
const { checkIntegrityUtil } = require('../../cds-services/services/utils/handlerUtils')
|
|
68
|
-
const C_UD = { CREATE: 1, INSERT: 1, UPDATE: 1, DELETE: 1 }
|
|
69
|
-
|
|
70
410
|
const _performCheck = async (req, cur, csn, run) => {
|
|
71
411
|
const prev = (cur.errors && cur.errors.length) || 0
|
|
72
412
|
|
|
@@ -74,11 +414,11 @@ const _performCheck = async (req, cur, csn, run) => {
|
|
|
74
414
|
for (const each of cur.query) {
|
|
75
415
|
const r = { query: each, target: each._target, event: each.cmd === 'INSERT' ? 'CREATE' : each.cmd }
|
|
76
416
|
Object.setPrototypeOf(r, cur)
|
|
77
|
-
await
|
|
417
|
+
await _checkIntegrityUtil(r, csn, run)
|
|
78
418
|
if (r.errors && r.errors.length) r.errors.forEach(e => req.error(e))
|
|
79
419
|
}
|
|
80
420
|
} else {
|
|
81
|
-
await
|
|
421
|
+
await _checkIntegrityUtil(cur, csn, run)
|
|
82
422
|
}
|
|
83
423
|
|
|
84
424
|
// only additional errors
|
|
@@ -99,6 +439,7 @@ function performCheck(req) {
|
|
|
99
439
|
if (Array.isArray(r.query)) return r.query.some(q => q.cmd in C_UD)
|
|
100
440
|
return r.query && r.query.cmd in C_UD
|
|
101
441
|
})
|
|
442
|
+
|
|
102
443
|
if (relevant.length === 0) return
|
|
103
444
|
|
|
104
445
|
return Promise.all(
|
|
@@ -3,11 +3,18 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
|
|
6
|
+
const { getEntityNameFromCQN } = require('../../common/utils/entityFromCqn')
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const _getElement = (ref, target, alias) =>
|
|
9
|
+
alias
|
|
10
|
+
? (ref[0] === alias && target.elements[ref[1]]) || target.elements[ref[0]]
|
|
11
|
+
: target.elements[ref[0]] || target.elements[ref[1]]
|
|
12
|
+
|
|
13
|
+
const _convert = (columns, target, model, alias) => {
|
|
8
14
|
if (!target) return
|
|
9
15
|
for (const col of columns) {
|
|
10
|
-
|
|
16
|
+
if (!col.ref) continue
|
|
17
|
+
const element = _getElement(col.ref, target, alias)
|
|
11
18
|
if (element) {
|
|
12
19
|
if (element.virtual) {
|
|
13
20
|
col.as = col.as || col.ref[col.ref.length - 1]
|
|
@@ -21,22 +28,53 @@ const _convert = (columns, target, model) => {
|
|
|
21
28
|
}
|
|
22
29
|
}
|
|
23
30
|
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const _convert4ref = (query, model) => {
|
|
32
|
+
const { entityName, alias } = getEntityNameFromCQN(query)
|
|
33
|
+
const columns = query.SELECT.columns || []
|
|
34
|
+
const target = entityName && model.definitions[ensureNoDraftsSuffix(entityName)]
|
|
35
|
+
if (target) _convert(columns, target, model, alias)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const _convert4union = (SELECT, model) => {
|
|
39
|
+
for (const arg of SELECT.from.SET.args) {
|
|
40
|
+
const { entityName, alias } = getEntityNameFromCQN(arg)
|
|
41
|
+
const target = entityName && model.definitions[ensureNoDraftsSuffix(entityName)]
|
|
42
|
+
if (target) {
|
|
34
43
|
const columns = (arg.SELECT && arg.SELECT.columns) || []
|
|
35
|
-
_convert(columns, target, model,
|
|
44
|
+
_convert(columns, target, model, alias)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const _convert4join = (SELECT, model) => {
|
|
50
|
+
const columns = SELECT.columns || []
|
|
51
|
+
for (const arg of SELECT.from.args) {
|
|
52
|
+
const { entityName, alias } = getEntityNameFromCQN(arg)
|
|
53
|
+
const target = entityName && model.definitions[ensureNoDraftsSuffix(entityName)]
|
|
54
|
+
if (target) {
|
|
55
|
+
_convert(columns, target, model, alias)
|
|
36
56
|
}
|
|
37
57
|
}
|
|
38
58
|
}
|
|
39
59
|
|
|
60
|
+
// REVISIT: Refactor to have a generic 'columns processor'
|
|
61
|
+
const _convertViruals = (query, model) => {
|
|
62
|
+
if (!query.SELECT.from) return
|
|
63
|
+
if (query.SELECT.from.SET) return _convert4union(query.SELECT, model)
|
|
64
|
+
if (query.SELECT.from.args) return _convert4join(query.SELECT, model)
|
|
65
|
+
if (query.SELECT.from.SELECT) {
|
|
66
|
+
_convert4ref(query, model)
|
|
67
|
+
return _convertViruals(query.SELECT.from, model)
|
|
68
|
+
}
|
|
69
|
+
return _convert4ref(query, model)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const convertVirtuals = function (req, _model) {
|
|
73
|
+
const model = this.model || _model
|
|
74
|
+
if (!model) return
|
|
75
|
+
if (req.query.SELECT) _convertViruals(req.query, model)
|
|
76
|
+
}
|
|
77
|
+
|
|
40
78
|
convertVirtuals._initial = true
|
|
41
79
|
|
|
42
80
|
module.exports = {
|
|
@@ -26,11 +26,13 @@ const _getFilteredCqns = (cqns, model) => {
|
|
|
26
26
|
let moreThanManaged = Object.keys(cqn.UPDATE.data).some(
|
|
27
27
|
k => entity.elements[k]['@cds.on.update'] === undefined || !cqn.UPDATE.data[k].startsWith('$')
|
|
28
28
|
)
|
|
29
|
+
|
|
29
30
|
if (moreThanManaged) continue
|
|
30
31
|
|
|
31
32
|
// REVISIT: remove feature flag update_header_item after grace period of at least two months (> April release)
|
|
32
33
|
if (cds.env.features.update_header_item !== false) {
|
|
33
34
|
const comps = Object.values(entity.associations || {}).filter(assoc => assoc._isCompositionEffective)
|
|
35
|
+
|
|
34
36
|
for (const comp of comps) {
|
|
35
37
|
if (_includesCompositionTarget(cqns, comp.target)) {
|
|
36
38
|
moreThanManaged = true
|
|
@@ -38,6 +40,7 @@ const _getFilteredCqns = (cqns, model) => {
|
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
}
|
|
43
|
+
|
|
41
44
|
if (moreThanManaged) continue
|
|
42
45
|
|
|
43
46
|
// remove current cqn
|
|
@@ -54,11 +57,12 @@ const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query,
|
|
|
54
57
|
if (hasDeepUpdate(model && model.definitions, query)) {
|
|
55
58
|
// REVISIT: _activeData gets set in case of draftActivate for performance, but this is a layer violation
|
|
56
59
|
let selectData = req._ && req._.query && req._.query._activeData
|
|
57
|
-
|
|
60
|
+
|
|
61
|
+
if (selectData) {
|
|
62
|
+
selectData = [selectData]
|
|
63
|
+
} else {
|
|
58
64
|
// REVISIT: avoid additional read
|
|
59
65
|
selectData = await selectDeepUpdateData(model && model.definitions, query, req, false, false, cds.db)
|
|
60
|
-
} else {
|
|
61
|
-
selectData = [selectData]
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
let cqns = getDeepUpdateCQNs(model && model.definitions, query, selectData)
|
|
@@ -71,9 +75,11 @@ const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query,
|
|
|
71
75
|
cqns = _getFilteredCqns(getFlatArray(cqns), model)
|
|
72
76
|
|
|
73
77
|
if (cqns.length === 0) return 0
|
|
78
|
+
|
|
74
79
|
const results = await processCQNs(executeUpdateCQN, cqns, model, dbc, user, locale, isoTs, chunks)
|
|
75
80
|
// return number of affected rows of "root cqn", if an update, 1 otherwise (as not update of root but its children)
|
|
76
81
|
if (cqns[0].UPDATE) return results[0]
|
|
82
|
+
|
|
77
83
|
return 1
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -87,15 +87,9 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
_isStructured(op1, comp, op2) {
|
|
90
|
-
if (op1.ref && comp === '=' && op2.val && typeof op2.val === 'object')
|
|
91
|
-
return true
|
|
92
|
-
}
|
|
90
|
+
if (op1.ref && comp === '=' && op2.val && typeof op2.val === 'object' && !(op2.val instanceof Buffer)) return true
|
|
93
91
|
// also check reverse
|
|
94
|
-
if (op1.val && typeof op1.val === 'object' && comp === '=' && op2.ref)
|
|
95
|
-
return true
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return false
|
|
92
|
+
if (op1.val && typeof op1.val === 'object' && comp === '=' && op2.ref && !(op1.val instanceof Buffer)) return true
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
_expressionObjectsToSQL(objects) {
|
|
@@ -120,6 +114,7 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
120
114
|
i += 3
|
|
121
115
|
continue
|
|
122
116
|
}
|
|
117
|
+
|
|
123
118
|
this._expressionElementToSQL(objects[i])
|
|
124
119
|
i++
|
|
125
120
|
}
|
|
@@ -239,7 +234,7 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
239
234
|
}
|
|
240
235
|
|
|
241
236
|
if (Array.isArray(values.list)) {
|
|
242
|
-
this.
|
|
237
|
+
this._expressionElementToSQL(reference)
|
|
243
238
|
this._outputObj.sql.push(operator)
|
|
244
239
|
this._addListToOutputObj(values.list)
|
|
245
240
|
return true
|
|
@@ -283,6 +278,10 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
283
278
|
)
|
|
284
279
|
}
|
|
285
280
|
|
|
281
|
+
/**
|
|
282
|
+
* This method is overridden in sqlite Expression Builder in order to add the required "VALUES" keyword if needed
|
|
283
|
+
* Make sure to adapt the overridden function as well, if adapting this.
|
|
284
|
+
*/
|
|
286
285
|
_addListToOutputObj(list) {
|
|
287
286
|
this._outputObj.sql.push('(')
|
|
288
287
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
|
|
3
|
+
const _autoGenerate = e => e && e.type === 'cds.UUID' && e.key
|
|
4
|
+
|
|
3
5
|
const _generateParentField = ({ parentElement }, row) => {
|
|
4
6
|
if (_autoGenerate(parentElement) && !row[parentElement.name]) {
|
|
5
7
|
row[parentElement.name] = cds.utils.uuid()
|
|
@@ -14,8 +16,6 @@ const _generateChildField = ({ deep, childElement }, childRow) => {
|
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
const _autoGenerate = e => e && e.type === 'cds.UUID' && e.key
|
|
18
|
-
|
|
19
19
|
const _getNestedVal = (row, prefix) => {
|
|
20
20
|
let val = row
|
|
21
21
|
const splitted = prefix.split('_')
|
|
@@ -52,12 +52,12 @@ const _propagateToParent = ({ parentElement, childElement, deep }, childRow, row
|
|
|
52
52
|
if (deep) {
|
|
53
53
|
_propagateToParent(deep.propagation, childRow[deep.targetName], childRow)
|
|
54
54
|
}
|
|
55
|
-
if (parentElement && childElement && childRow &&
|
|
55
|
+
if (parentElement && childElement && childRow && childElement.name in childRow) {
|
|
56
56
|
row[parentElement.name] = childRow[childElement.name]
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
module.exports = (tKey, row, foreignKeyPropagations, isCompositionEffective) => {
|
|
61
61
|
const childRows = Array.isArray(row[tKey]) ? row[tKey] : [row[tKey]]
|
|
62
62
|
|
|
63
63
|
for (const childRow of childRows) {
|
|
@@ -65,12 +65,13 @@ const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEf
|
|
|
65
65
|
|
|
66
66
|
for (const foreignKeyPropagation of foreignKeyPropagations) {
|
|
67
67
|
if (foreignKeyPropagation.fillChild) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
// propagate or generate in parent
|
|
69
|
+
const pk = foreignKeyPropagation.parentElement && foreignKeyPropagation.parentElement.name
|
|
70
|
+
if (pk && !(pk in row)) _propagateToParent(foreignKeyPropagation, childRow, row)
|
|
71
|
+
if (!(pk in row)) _generateParentField(foreignKeyPropagation, row)
|
|
72
|
+
|
|
73
|
+
if (!isCompositionEffective) delete row[tKey]
|
|
74
|
+
else _propagateToChid(foreignKeyPropagation, row, childRow)
|
|
74
75
|
} else {
|
|
75
76
|
_generateChildField(foreignKeyPropagation, childRow)
|
|
76
77
|
_propagateToParent(foreignKeyPropagation, childRow, row)
|
|
@@ -78,7 +79,3 @@ const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEf
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
-
module.exports = {
|
|
83
|
-
propagateForeignKeys
|
|
84
|
-
}
|