@sap/cds 6.1.0 → 6.1.3
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 +67 -0
- package/apis/log.d.ts +112 -36
- package/apis/services.d.ts +13 -2
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -3
- package/bin/build/provider/hana/index.js +4 -2
- package/bin/build/provider/mtx/resourcesTarBuilder.js +4 -8
- package/bin/deploy/to-hana/hana.js +20 -25
- package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
- package/lib/dbs/cds-deploy.js +2 -2
- package/lib/env/schemas/cds-rc.json +10 -1
- package/lib/index.js +1 -1
- package/lib/log/format/kibana.js +19 -1
- package/lib/ql/Query.js +9 -3
- package/lib/ql/SELECT.js +1 -1
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/cds-ql.js +4 -10
- package/lib/req/context.js +15 -11
- package/lib/srv/srv-api.js +8 -0
- package/lib/srv/srv-dispatch.js +11 -7
- package/lib/srv/srv-models.js +4 -3
- package/lib/srv/srv-tx.js +52 -40
- package/lib/utils/cds-utils.js +3 -3
- package/lib/utils/resources/index.js +5 -5
- package/lib/utils/resources/tar.js +1 -1
- package/libx/_runtime/auth/index.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -2
- package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -1
- package/libx/_runtime/cds-services/util/assert.js +3 -0
- package/libx/_runtime/common/generic/input.js +17 -2
- package/libx/_runtime/common/generic/put.js +4 -1
- package/libx/_runtime/common/generic/temporal.js +0 -3
- package/libx/_runtime/common/utils/binary.js +3 -4
- package/libx/_runtime/common/utils/keys.js +14 -6
- package/libx/_runtime/common/utils/propagateForeignKeys.js +122 -0
- package/libx/_runtime/common/utils/resolveView.js +1 -1
- package/libx/_runtime/common/utils/template.js +2 -3
- package/libx/_runtime/db/expand/expandCQNToJoin.js +1 -1
- package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
- package/libx/_runtime/db/generic/input.js +7 -4
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/extensibility/add.js +3 -0
- package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
- package/libx/_runtime/extensibility/push.js +11 -11
- package/libx/_runtime/extensibility/token.js +2 -1
- package/libx/_runtime/extensibility/utils.js +8 -6
- package/libx/_runtime/fiori/generic/new.js +1 -3
- package/libx/_runtime/fiori/generic/patch.js +1 -7
- package/libx/_runtime/fiori/utils/where.js +1 -1
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
- package/libx/_runtime/remote/utils/client.js +29 -10
- package/libx/_runtime/sqlite/Service.js +7 -5
- package/libx/_runtime/sqlite/execute.js +41 -28
- package/libx/odata/cqn2odata.js +6 -2
- package/libx/rest/RestAdapter.js +3 -6
- package/libx/rest/middleware/input.js +2 -3
- package/package.json +1 -1
- package/srv/extensibility-service.cds +4 -3
- package/srv/model-provider.js +1 -1
- package/srv/mtx.js +18 -9
- package/libx/_runtime/db/utils/propagateForeignKeys.js +0 -93
|
@@ -187,11 +187,13 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
187
187
|
return
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
await this.run(async tx => {
|
|
191
|
+
// This starts a new transaction if called from CLI, while joining
|
|
192
|
+
// existing root tx, e.g. when called from DeploymenrService
|
|
193
|
+
await tx.run(dropViews)
|
|
194
|
+
await tx.run(dropTables)
|
|
195
|
+
await tx.run(createEntities)
|
|
196
|
+
})
|
|
195
197
|
|
|
196
198
|
return true
|
|
197
199
|
}
|
|
@@ -19,31 +19,47 @@ const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.san
|
|
|
19
19
|
* -> only if DEBUG (which should not be used in production)
|
|
20
20
|
*/
|
|
21
21
|
const DEBUG = cds.debug('sqlite')
|
|
22
|
-
const
|
|
23
|
-
? () => {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
const _exec = DEBUG
|
|
23
|
+
? (dbc, op, ...args) => {
|
|
24
|
+
const callback = args[args.length - 1]
|
|
25
|
+
const captured = {}
|
|
26
|
+
Error.captureStackTrace(captured, _exec)
|
|
27
|
+
args[args.length - 1] = function (err) {
|
|
28
|
+
if (err) {
|
|
29
|
+
err.message += ' in: \n' + args[0]
|
|
30
|
+
err.query = args[0]
|
|
31
|
+
if (args.length > 2) err.values = SANITIZE_VALUES ? ['***'] : args[1]
|
|
32
|
+
err.stack =
|
|
33
|
+
err.message +
|
|
34
|
+
captured.stack
|
|
35
|
+
.slice(5)
|
|
36
|
+
.replace(/at( _exec)? /, 'at SQLite.' + op + ' ')
|
|
37
|
+
.replace(/\s+at new Promise .*\n.*/, '')
|
|
38
|
+
}
|
|
39
|
+
callback.apply(this, arguments)
|
|
40
|
+
}
|
|
41
|
+
return dbc[op](...args)
|
|
42
|
+
}
|
|
43
|
+
: (dbc, op, ...args) => {
|
|
44
|
+
const callback = args[args.length - 1]
|
|
45
|
+
args[args.length - 1] = function (err) {
|
|
46
|
+
if (err) {
|
|
47
|
+
err.message += ' in: \n' + args[0]
|
|
48
|
+
err.query = args[0]
|
|
49
|
+
if (args.length > 2) err.values = SANITIZE_VALUES ? ['***'] : args[1]
|
|
50
|
+
}
|
|
51
|
+
callback.apply(this, arguments)
|
|
52
|
+
}
|
|
53
|
+
return dbc[op](...args)
|
|
27
54
|
}
|
|
28
|
-
: () => undefined
|
|
29
|
-
|
|
30
|
-
const _augmented = (err, sql, values, o) => {
|
|
31
|
-
err.query = sql
|
|
32
|
-
if (values) err.values = SANITIZE_VALUES ? ['***'] : values
|
|
33
|
-
err.message += ' in: \n' + sql
|
|
34
|
-
if (o) err.stack = err.message + o.stack.slice(5)
|
|
35
|
-
return err
|
|
36
|
-
}
|
|
37
55
|
|
|
38
56
|
function _executeSimpleSQL(dbc, sql, values) {
|
|
39
57
|
LOG._debug &&
|
|
40
58
|
LOG.debug(coloredTxCommands[sql] || sql, Array.isArray(values) ? (SANITIZE_VALUES ? ['***'] : values) : '')
|
|
41
59
|
|
|
42
60
|
return new Promise((resolve, reject) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (err) return reject(_augmented(err, sql, values, o))
|
|
46
|
-
|
|
61
|
+
_exec(dbc, 'run', sql, values, function (err) {
|
|
62
|
+
if (err) return reject(err)
|
|
47
63
|
resolve(this.changes)
|
|
48
64
|
})
|
|
49
65
|
})
|
|
@@ -53,9 +69,8 @@ function executeSelectSQL(dbc, sql, values, isOne, postMapper) {
|
|
|
53
69
|
LOG._debug && LOG.debug(sql, SANITIZE_VALUES ? ['***'] : values)
|
|
54
70
|
|
|
55
71
|
return new Promise((resolve, reject) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (err) return reject(_augmented(err, sql, values, o))
|
|
72
|
+
_exec(dbc, isOne ? 'get' : 'all', sql, values, (err, result) => {
|
|
73
|
+
if (err) return reject(err)
|
|
59
74
|
|
|
60
75
|
// REVISIT
|
|
61
76
|
// .get returns undefined if nothing in db
|
|
@@ -140,9 +155,8 @@ const _executeBulkInsertSQL = (dbc, sql, values) =>
|
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
LOG._debug && LOG.debug(sql, SANITIZE_VALUES ? ['***'] : values)
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
if (err) return reject(_augmented(err, sql, values, o))
|
|
158
|
+
const stmt = _exec(dbc, 'prepare', sql, err => {
|
|
159
|
+
if (err) return reject(err)
|
|
146
160
|
|
|
147
161
|
if (!Array.isArray(values[0])) values = [values]
|
|
148
162
|
|
|
@@ -159,7 +173,7 @@ const _executeBulkInsertSQL = (dbc, sql, values) =>
|
|
|
159
173
|
if (!isFinalized) {
|
|
160
174
|
isFinalized = true
|
|
161
175
|
stmt.finalize()
|
|
162
|
-
return reject(
|
|
176
|
+
return reject(err)
|
|
163
177
|
}
|
|
164
178
|
}
|
|
165
179
|
|
|
@@ -212,9 +226,8 @@ function executeInsertSQL(dbc, sql, values, query) {
|
|
|
212
226
|
LOG._debug && LOG.debug(sql, SANITIZE_VALUES ? ['***'] : values)
|
|
213
227
|
|
|
214
228
|
return new Promise((resolve, reject) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (err) return reject(_augmented(err, sql, values, o))
|
|
229
|
+
_exec(dbc, 'run', sql, values, function (err) {
|
|
230
|
+
if (err) return reject(err)
|
|
218
231
|
|
|
219
232
|
// InsertResult needs an object per row with its values
|
|
220
233
|
if (query && values.length > 0) {
|
package/libx/odata/cqn2odata.js
CHANGED
|
@@ -200,8 +200,12 @@ const _keysOfWhere = (where, kind, target) => {
|
|
|
200
200
|
if (where.length === 3) {
|
|
201
201
|
const [left, op, right] = where
|
|
202
202
|
if (op === '=' && (('val' in left && right.ref) || (left.ref && 'val' in right))) {
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
const formattedValue =
|
|
204
|
+
'val' in left
|
|
205
|
+
? formatVal(left.val, right.ref.join('/'), target, kind)
|
|
206
|
+
: formatVal(right.val, left.ref.join('/'), target, kind)
|
|
207
|
+
|
|
208
|
+
return `(${encodeURIComponent(formattedValue)})`
|
|
205
209
|
}
|
|
206
210
|
}
|
|
207
211
|
|
package/libx/rest/RestAdapter.js
CHANGED
|
@@ -117,8 +117,7 @@ const RestAdapter = function(srv) {
|
|
|
117
117
|
// begin tx
|
|
118
118
|
router.use((req, res, next) => { // REVISIT: -> move to actual handler(s)
|
|
119
119
|
// create tx and set as cds.context
|
|
120
|
-
|
|
121
|
-
req.tx = cds.context = srv.tx({ user: req.user, req, res })
|
|
120
|
+
cds.context = srv.tx(new cds.EventContext({ user: req.user, req, res }))
|
|
122
121
|
next()
|
|
123
122
|
})
|
|
124
123
|
|
|
@@ -148,8 +147,7 @@ const RestAdapter = function(srv) {
|
|
|
148
147
|
|
|
149
148
|
// unfortunately, express doesn't catch async errors -> try catch needed
|
|
150
149
|
try {
|
|
151
|
-
|
|
152
|
-
await req.tx.commit(result)
|
|
150
|
+
await cds.context?.tx?.commit(result)
|
|
153
151
|
} catch (e) {
|
|
154
152
|
return next(e)
|
|
155
153
|
}
|
|
@@ -179,8 +177,7 @@ const RestAdapter = function(srv) {
|
|
|
179
177
|
// request may fail during processing or during commit -> both caught here
|
|
180
178
|
|
|
181
179
|
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
182
|
-
|
|
183
|
-
if (req.tx) req.tx.rollback(err).catch(() => {}) // REVISIT: silently ?!?
|
|
180
|
+
cds.context?.tx?.rollback(err).catch(() => {}) // REVISIT: silently ?!?
|
|
184
181
|
|
|
185
182
|
next(err)
|
|
186
183
|
})
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// const cds = require('../../_runtime/cds')
|
|
2
2
|
const getTemplate = require('../../_runtime/common/utils/template')
|
|
3
3
|
const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
|
|
4
|
-
const { checkStaticElementByKey
|
|
4
|
+
const { checkStaticElementByKey } = require('../../_runtime/cds-services/util/assert')
|
|
5
5
|
const { MULTIPLE_ERRORS } = require('../../_runtime/common/error/constants')
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
//
|
|
9
8
|
// REVISIT: We need to decipher what we are doing here...
|
|
10
9
|
//
|
|
@@ -20,7 +19,7 @@ const _picker = () => {
|
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
const _processorFn = errors => {
|
|
23
|
-
return ({ row, key, plain: categories, target
|
|
22
|
+
return ({ row, key, plain: categories, target }) => {
|
|
24
23
|
// REVISIT move validation to generic asserter => see PR 717
|
|
25
24
|
if (categories['static_validation'] && row[key] != null) {
|
|
26
25
|
const validations = checkStaticElementByKey(target, key, row[key])
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
// using { cds.xt.TAR } from './model-provider'; //> IMPORTANT: don't add this as it will cause services loaded twice
|
|
1
2
|
using { cds.xt.Extensions } from './extensions';
|
|
2
|
-
using { cds.xt.TAR } from './model-provider';
|
|
3
3
|
|
|
4
4
|
@protocol: 'rest'
|
|
5
5
|
@(requires : 'authenticated-user')
|
|
@@ -8,7 +8,8 @@ service cds.xt.ExtensibilityService @(path:'/-/cds/extensibility', impl:'@sap/cd
|
|
|
8
8
|
// TODO: async jobs
|
|
9
9
|
|
|
10
10
|
type ActivationLevel : Extensions:activated;
|
|
11
|
-
type
|
|
11
|
+
type TAR : LargeBinary;
|
|
12
|
+
type CSN : String; // REVISIT: should reuse cds.xt.CSN
|
|
12
13
|
type CSN_OR_CDL: String;
|
|
13
14
|
|
|
14
15
|
// UIFLEX API
|
|
@@ -46,7 +47,7 @@ service cds.xt.ExtensibilityService @(path:'/-/cds/extensibility', impl:'@sap/cd
|
|
|
46
47
|
|
|
47
48
|
@(requires : ['cds.ExtensionDeveloper'])
|
|
48
49
|
action push (
|
|
49
|
-
extension : TAR
|
|
50
|
+
extension : LargeBinary, // REVISIT: Using TAR here leads to a strange type check failure
|
|
50
51
|
tag : Extensions:tag
|
|
51
52
|
// activate : ActivationLevel
|
|
52
53
|
);
|
package/srv/model-provider.js
CHANGED
|
@@ -109,7 +109,7 @@ module.exports = class ModelProviderService extends cds.ApplicationService {
|
|
|
109
109
|
const extensions = !base && await _getExtensions4 (req.data.tenant)
|
|
110
110
|
if (!extensions && checkExt) req.reject(404, 'Missing extensions')
|
|
111
111
|
|
|
112
|
-
const features = !toggles ? [] : toggles === '*' || toggles.includes('*') ? [fts] : toggles.map (f => fts.replace('*',f))
|
|
112
|
+
const features = (!toggles || !main.requires.toggles) ? [] : toggles === '*' || toggles.includes('*') ? [fts] : toggles.map (f => fts.replace('*',f))
|
|
113
113
|
const models = cds.resolve (['*',...features], main); if (!models) return
|
|
114
114
|
|
|
115
115
|
DEBUG && DEBUG ('loading models for', { tenant, toggles } ,'from', models.map (cds.utils.local))
|
package/srv/mtx.js
CHANGED
|
@@ -8,19 +8,28 @@ class MTXServices extends cds.Service { async init(){
|
|
|
8
8
|
}
|
|
9
9
|
// else...
|
|
10
10
|
DEBUG && DEBUG ('bootstrapping MTX services...')
|
|
11
|
+
let defs = cds.model.definitions
|
|
11
12
|
let sources = []
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
|
|
14
|
+
if (cds.requires.multitenancy && !('cds.xt.SaasProvisioningService' in defs)) {
|
|
15
|
+
sources.push('@sap/cds-mtxs/srv/cf/saas-provisioning-service')
|
|
16
|
+
sources.push('@sap/cds/srv/model-provider')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (cds.requires.multitenancy && !('cds.xt.DeploymentService' in defs)) {
|
|
20
|
+
sources.push('@sap/cds-mtxs/srv/deployment-service')
|
|
21
|
+
sources.push('@sap/cds/srv/model-provider')
|
|
17
22
|
}
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
|
|
24
|
+
if (cds.requires.extensibility && !('cds.xt.ExtensibilityService' in defs)) {
|
|
25
|
+
sources.push('@sap/cds/srv/extensibility-service')
|
|
26
|
+
sources.push('@sap/cds/srv/model-provider')
|
|
20
27
|
}
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
|
|
29
|
+
if (cds.requires.toggles && !('cds.xt.ModelProviderService' in defs)) {
|
|
30
|
+
sources.push('@sap/cds/srv/model-provider')
|
|
23
31
|
}
|
|
32
|
+
|
|
24
33
|
let models = cds.resolve(sources); if (!models) return
|
|
25
34
|
let base = cds.model.$sources; models = models.filter(m => !base.includes(m))
|
|
26
35
|
if (models.length) return cds.serve(models).in(cds.app)
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
const cds = require('../../cds')
|
|
2
|
-
|
|
3
|
-
const { prefixForStruct } = require('../../common/utils/csn')
|
|
4
|
-
|
|
5
|
-
const _autoGenerate = e => e && e.isUUID && e.key
|
|
6
|
-
|
|
7
|
-
const _generateParentField = ({ parentElement }, row) => {
|
|
8
|
-
if (_autoGenerate(parentElement) && !row[parentElement.name]) {
|
|
9
|
-
row[parentElement.name] = cds.utils.uuid()
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const _generateChildField = ({ deep, childElement }, childRow) => {
|
|
14
|
-
if (deep) {
|
|
15
|
-
_generateChildField(deep.propagation, childRow[deep.targetName])
|
|
16
|
-
} else if (_autoGenerate(childElement) && childRow && !childRow[childElement.name]) {
|
|
17
|
-
childRow[childElement.name] = cds.utils.uuid()
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const _getNestedVal = (row, prefix) => {
|
|
22
|
-
let val = row
|
|
23
|
-
const splitted = prefix.split('_')
|
|
24
|
-
splitted.pop() // remove last `_`
|
|
25
|
-
let k = ''
|
|
26
|
-
|
|
27
|
-
while (splitted.length > 0) {
|
|
28
|
-
k += splitted.shift()
|
|
29
|
-
if (k in val) {
|
|
30
|
-
val = val[k]
|
|
31
|
-
k = ''
|
|
32
|
-
} else {
|
|
33
|
-
k += '_'
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return val
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const _propagateToChid = ({ parentElement, childElement, parentFieldValue }, row, childRow) => {
|
|
41
|
-
if (!childElement) return
|
|
42
|
-
if (parentElement) {
|
|
43
|
-
const prefix = prefixForStruct(parentElement)
|
|
44
|
-
if (prefix) {
|
|
45
|
-
const nested = _getNestedVal(row, prefix)
|
|
46
|
-
childRow[childElement.name] = nested[parentElement.name]
|
|
47
|
-
} else {
|
|
48
|
-
childRow[childElement.name] = row[parentElement.name]
|
|
49
|
-
}
|
|
50
|
-
} else if (parentFieldValue !== undefined) {
|
|
51
|
-
childRow[childElement.name] = parentFieldValue
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const _propagateToParent = ({ parentElement, childElement, deep }, childRow, row) => {
|
|
56
|
-
if (deep) {
|
|
57
|
-
_propagateToParent(deep.propagation, childRow[deep.targetName], childRow)
|
|
58
|
-
}
|
|
59
|
-
if (parentElement && childElement && childRow && childElement.name in childRow) {
|
|
60
|
-
row[parentElement.name] = childRow[childElement.name]
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
module.exports = (tKey, row, foreignKeyPropagations, isCompositionEffective) => {
|
|
65
|
-
if (row[tKey] === null) {
|
|
66
|
-
for (const foreignKeyPropagation of foreignKeyPropagations) {
|
|
67
|
-
if (!foreignKeyPropagation.fillChild) row[foreignKeyPropagation.parentElement.name] = null
|
|
68
|
-
}
|
|
69
|
-
if (!isCompositionEffective) delete row[tKey]
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const childRows = Array.isArray(row[tKey]) ? row[tKey] : [row[tKey]]
|
|
74
|
-
|
|
75
|
-
for (const childRow of childRows) {
|
|
76
|
-
if (!childRow) return
|
|
77
|
-
|
|
78
|
-
for (const foreignKeyPropagation of foreignKeyPropagations) {
|
|
79
|
-
if (foreignKeyPropagation.fillChild) {
|
|
80
|
-
// propagate or generate in parent
|
|
81
|
-
const pk = foreignKeyPropagation.parentElement && foreignKeyPropagation.parentElement.name
|
|
82
|
-
if (pk && !(pk in row)) _propagateToParent(foreignKeyPropagation, childRow, row)
|
|
83
|
-
if (!(pk in row)) _generateParentField(foreignKeyPropagation, row)
|
|
84
|
-
|
|
85
|
-
if (isCompositionEffective) _propagateToChid(foreignKeyPropagation, row, childRow)
|
|
86
|
-
} else {
|
|
87
|
-
_generateChildField(foreignKeyPropagation, childRow)
|
|
88
|
-
_propagateToParent(foreignKeyPropagation, childRow, row)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (!isCompositionEffective) delete row[tKey]
|
|
93
|
-
}
|