@sap/cds 6.8.3 → 7.0.0
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 +61 -2
- package/README.md +0 -1
- package/bin/cds-serve.js +50 -3
- package/bin/deploy/to-hana.js +1 -0
- package/bin/serve.js +16 -20
- package/lib/auth/basic-auth.js +6 -4
- package/lib/auth/index.js +4 -3
- package/lib/auth/jwt-auth.js +2 -5
- package/lib/compile/cds-compile.js +34 -89
- package/lib/compile/cdsc.js +11 -0
- package/lib/compile/etc/properties.js +2 -2
- package/lib/compile/for/lean_drafts.js +36 -69
- package/lib/compile/for/nodejs.js +2 -1
- package/lib/compile/load.js +1 -1
- package/lib/compile/minify.js +2 -0
- package/lib/compile/to/csn.js +74 -0
- package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
- package/lib/compile/to/json.js +1 -1
- package/lib/compile/to/sql.js +8 -6
- package/lib/dbs/cds-deploy.js +174 -114
- package/lib/env/cds-env.js +64 -79
- package/lib/env/cds-requires.js +11 -28
- package/lib/env/defaults.js +13 -3
- package/lib/env/plugins.js +1 -12
- package/lib/env/presets.js +25 -21
- package/lib/index.js +121 -147
- package/lib/{core/reflect.js → linked/models.js} +2 -2
- package/lib/{core/infer.js → linked/queries.js} +2 -0
- package/lib/{core/index.js → linked/types.js} +2 -1
- package/lib/log/cds-error.js +13 -7
- package/lib/log/format/cf.js +1 -1
- package/lib/plugins.js +49 -0
- package/lib/ql/Query.js +0 -9
- package/lib/ql/STREAM.js +0 -1
- package/lib/req/context.js +2 -7
- package/lib/req/request.js +6 -2
- package/lib/req/response.js +23 -10
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/middlewares/errors.js +1 -1
- package/lib/srv/protocols/_legacy.js +1 -0
- package/lib/srv/protocols/graphql.js +7 -16
- package/lib/srv/protocols/index.js +59 -45
- package/lib/srv/protocols/odata-v2-proxy.js +2 -70
- package/lib/srv/srv-api.js +9 -3
- package/lib/srv/srv-dispatch.js +12 -9
- package/lib/srv/srv-models.js +4 -21
- package/lib/srv/srv-tx.js +15 -12
- package/lib/utils/cds-test.js +14 -9
- package/lib/utils/cds-utils.js +2 -12
- package/lib/utils/check-version.js +17 -0
- package/{bin/build → lib/utils}/csv-reader.js +23 -24
- package/libx/_runtime/auth/index.js +27 -23
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
- package/libx/_runtime/cds-services/services/Service.js +79 -107
- package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
- package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
- package/libx/_runtime/cds-services/util/assert.js +65 -2
- package/libx/_runtime/common/composition/data.js +1 -0
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/restrict.js +5 -10
- package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
- package/libx/_runtime/common/generic/auth/utils.js +1 -2
- package/libx/_runtime/common/generic/crud.js +32 -16
- package/libx/_runtime/common/generic/etag.js +133 -104
- package/libx/_runtime/common/generic/input.js +6 -21
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/stream.js +52 -0
- package/libx/_runtime/common/generic/temporal.js +25 -8
- package/libx/_runtime/common/i18n/messages.properties +0 -2
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
- package/libx/_runtime/common/utils/csn.js +0 -51
- package/libx/_runtime/common/utils/etag.js +30 -0
- package/libx/_runtime/common/utils/keys.js +1 -1
- package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
- package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
- package/libx/_runtime/common/utils/stream.js +140 -0
- package/libx/_runtime/common/utils/streamProp.js +29 -12
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
- package/libx/_runtime/db/generic/index.js +0 -2
- package/libx/_runtime/db/query/delete.js +2 -2
- package/libx/_runtime/db/query/insert.js +2 -2
- package/libx/_runtime/db/query/read.js +2 -2
- package/libx/_runtime/db/query/run.js +2 -2
- package/libx/_runtime/db/query/update.js +2 -2
- package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
- package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
- package/libx/_runtime/fiori/draft.js +2 -0
- package/libx/_runtime/fiori/generic/activate.js +8 -9
- package/libx/_runtime/fiori/generic/before.js +30 -20
- package/libx/_runtime/fiori/generic/cancel.js +5 -3
- package/libx/_runtime/fiori/generic/delete.js +5 -3
- package/libx/_runtime/fiori/generic/edit.js +7 -7
- package/libx/_runtime/fiori/generic/index.js +10 -16
- package/libx/_runtime/fiori/generic/new.js +5 -3
- package/libx/_runtime/fiori/generic/patch.js +11 -8
- package/libx/_runtime/fiori/generic/prepare.js +13 -6
- package/libx/_runtime/fiori/generic/read.js +12 -6
- package/libx/_runtime/fiori/lean-draft.js +207 -152
- package/libx/_runtime/fiori/utils/delete.js +10 -5
- package/libx/_runtime/fiori/utils/req.js +17 -5
- package/libx/_runtime/fiori/utils/stream.js +36 -0
- package/libx/_runtime/hana/Service.js +12 -9
- package/libx/_runtime/hana/conversion.js +10 -15
- package/libx/_runtime/hana/driver.js +2 -0
- package/libx/_runtime/hana/execute.js +28 -6
- package/libx/_runtime/hana/pool.js +36 -122
- package/libx/_runtime/hana/search2cqn4sql.js +34 -36
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/remote/Service.js +20 -1
- package/libx/_runtime/remote/utils/client.js +3 -5
- package/libx/_runtime/sqlite/Service.js +4 -6
- package/libx/_runtime/sqlite/conversion.js +3 -13
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
- package/libx/_runtime/sqlite/execute.js +5 -16
- package/libx/odata/afterburner.js +22 -6
- package/libx/odata/grammar.pegjs +6 -1
- package/libx/odata/parser.js +1 -1
- package/libx/rest/RestAdapter.js +16 -9
- package/libx/rest/RestRequest.js +1 -1
- package/libx/rest/middleware/input.js +2 -1
- package/libx/rest/middleware/operation.js +1 -0
- package/libx/rest/middleware/parse.js +3 -2
- package/libx/rest/middleware/payload.js +9 -8
- package/libx/rest/middleware/read.js +1 -0
- package/package.json +9 -16
- package/app/fiori/preview.js +0 -270
- package/app/fiori/routes.js +0 -59
- package/bin/build/buildTaskEngine.js +0 -360
- package/bin/build/buildTaskFactory.js +0 -283
- package/bin/build/buildTaskHandler.js +0 -241
- package/bin/build/buildTaskProvider.js +0 -22
- package/bin/build/buildTaskProviderFactory.js +0 -175
- package/bin/build/cds.js +0 -5
- package/bin/build/constants.js +0 -66
- package/bin/build/index.js +0 -58
- package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
- package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
- package/bin/build/provider/buildTaskProviderInternal.js +0 -383
- package/bin/build/provider/fiori/index.js +0 -171
- package/bin/build/provider/hana/2migration.js +0 -179
- package/bin/build/provider/hana/index.js +0 -505
- package/bin/build/provider/hana/migrationtable.js +0 -472
- package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
- package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
- package/bin/build/provider/hana/template/.hdinamespace +0 -4
- package/bin/build/provider/hana/template/package.json +0 -12
- package/bin/build/provider/hana/template/undeploy.json +0 -5
- package/bin/build/provider/java/index.js +0 -111
- package/bin/build/provider/java-cf/index.js +0 -1
- package/bin/build/provider/mtx/index.js +0 -268
- package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
- package/bin/build/provider/mtx-extension/index.js +0 -131
- package/bin/build/provider/mtx-sidecar/index.js +0 -137
- package/bin/build/provider/node-cf/index.js +0 -1
- package/bin/build/provider/nodejs/index.js +0 -192
- package/bin/build/util.js +0 -299
- package/bin/cds.js +0 -125
- package/bin/deploy/to-hana/cfUtil.js +0 -355
- package/bin/deploy/to-hana/gitUtil.js +0 -57
- package/bin/deploy/to-hana/hana.js +0 -306
- package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
- package/bin/deploy/to-hana/index.js +0 -16
- package/bin/deploy/to-hana/mtaUtil.js +0 -170
- package/bin/mtx/in-cds.js +0 -17
- package/bin/plugins.js +0 -32
- package/bin/run.js +0 -24
- package/bin/utils/log.js +0 -24
- package/bin/version.js +0 -178
- package/libx/_runtime/audit/Service.js +0 -222
- package/libx/_runtime/audit/generic/personal/access.js +0 -61
- package/libx/_runtime/audit/generic/personal/index.js +0 -56
- package/libx/_runtime/audit/generic/personal/modification.js +0 -132
- package/libx/_runtime/audit/generic/personal/utils.js +0 -186
- package/libx/_runtime/audit/utils/log.js +0 -23
- package/libx/_runtime/audit/utils/v2.js +0 -176
- package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
- package/libx/_runtime/db/generic/integrity.js +0 -455
- package/srv/audit-log.cds +0 -87
- package/srv/mtx.cds +0 -2
- package/srv/mtx.js +0 -8
- /package/lib/{core → linked}/classes.js +0 -0
- /package/lib/{core → linked}/entities.js +0 -0
|
@@ -2,25 +2,12 @@ const cds = require('../../cds')
|
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
4
|
const { deepCopyArray } = require('../utils/copy')
|
|
5
|
-
|
|
6
5
|
const { getColumns } = require('../../cds-services/services/utils/columns')
|
|
7
|
-
|
|
6
|
+
const { enhanceStreamResult } = require('../utils/stream')
|
|
8
7
|
const getError = require('../error')
|
|
9
8
|
|
|
10
9
|
const _targetEntityDoesNotExist = async req => {
|
|
11
|
-
const
|
|
12
|
-
const cqn = SELECT.from(query.UPDATE.entity, [1])
|
|
13
|
-
|
|
14
|
-
if (query.UPDATE.entity.as) {
|
|
15
|
-
cqn.SELECT.from.as = query.UPDATE.entity.as
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// REVISIT: compat mode for service functions .update
|
|
19
|
-
if (query.UPDATE && query.UPDATE.where) {
|
|
20
|
-
cqn.where(query.UPDATE.where)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const exists = await cds.tx(req).run(cqn)
|
|
10
|
+
const exists = await cds.tx(req).run(SELECT.from(req.subject, [1]))
|
|
24
11
|
return exists.length === 0
|
|
25
12
|
}
|
|
26
13
|
|
|
@@ -80,22 +67,26 @@ exports.impl = cds.service.impl(function () {
|
|
|
80
67
|
result = await cds.tx(req).run(req.query, req.data)
|
|
81
68
|
}
|
|
82
69
|
|
|
70
|
+
// regarding etag validation: we do not want to execute an additional select to distinguish between 412 and 404
|
|
71
|
+
|
|
83
72
|
if (req.event === 'READ') {
|
|
84
73
|
if ((result == null || result.length === 0) && pathExistsQuery) {
|
|
85
74
|
const res = await pathExistsQuery
|
|
86
75
|
if (res.length === 0) req.reject(404)
|
|
87
76
|
}
|
|
77
|
+
if (result == null && req._etagValidationType === 'if-match') req.reject(412)
|
|
88
78
|
return result
|
|
89
79
|
}
|
|
90
80
|
|
|
91
81
|
if (req.event === 'DELETE') {
|
|
92
|
-
if (result === 0) req.reject(404)
|
|
82
|
+
if (result === 0) req.reject(req._etagValidationType ? 412 : 404)
|
|
93
83
|
return result
|
|
94
84
|
}
|
|
95
85
|
|
|
96
86
|
// case: no authorization check and payload more than just keys but no changes
|
|
97
87
|
// -> affected rows === 0 -> no change or not exists?
|
|
98
88
|
if (req.event === 'UPDATE' && result === 0 && !req._authChecked) {
|
|
89
|
+
if (req._etagValidationType) req.reject(412)
|
|
99
90
|
if (await _targetEntityDoesNotExist(req)) req.reject(404)
|
|
100
91
|
}
|
|
101
92
|
|
|
@@ -104,4 +95,29 @@ exports.impl = cds.service.impl(function () {
|
|
|
104
95
|
|
|
105
96
|
return req.data
|
|
106
97
|
})
|
|
98
|
+
|
|
99
|
+
this.after('READ', '*', async function ([result], req) {
|
|
100
|
+
if (req.query?._streaming) {
|
|
101
|
+
await enhanceStreamResult(req, req.query, result, this.model)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
this.on('STREAM', '*', async function (req) {
|
|
106
|
+
if (typeof req.query !== 'string' && req.target && req.target._hasPersistenceSkip) {
|
|
107
|
+
throw getError({
|
|
108
|
+
code: 501,
|
|
109
|
+
message: `Entity "${req.target.name}" is annotated with "@cds.persistence.skip" and cannot be served generically.`
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (req.query.STREAM.from) {
|
|
114
|
+
return { value: await cds.tx(req).run(req.query, req.data) }
|
|
115
|
+
} else {
|
|
116
|
+
return await cds.tx(req).run(req.query, req.data)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
this.after(['STREAM'], '*', async function ([result], req) {
|
|
121
|
+
if (req.query.STREAM.from) await enhanceStreamResult(req, req.query, result, this.model)
|
|
122
|
+
})
|
|
107
123
|
})
|
|
@@ -1,76 +1,66 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
const { ensureDraftsSuffix } = require('../../fiori/utils/handler')
|
|
7
|
-
const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
|
|
8
|
-
const { isAsteriskColumn } = require('../../common/utils/rewriteAsterisks')
|
|
9
|
-
const { resolveView } = require('../utils/resolveView')
|
|
10
|
-
|
|
11
|
-
const C_U_ = {
|
|
12
|
-
CREATE: 1,
|
|
13
|
-
UPDATE: 1
|
|
14
|
-
}
|
|
4
|
+
const { convertPathExpressionToWhere } = require('../utils/cqn2cqn4sql')
|
|
5
|
+
const { addEtagColumns } = require('../utils/etag')
|
|
15
6
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return query.UPDATE.entity
|
|
21
|
-
} else {
|
|
22
|
-
return query.DELETE.from
|
|
7
|
+
const _getMatchHeaders = req => {
|
|
8
|
+
return {
|
|
9
|
+
ifMatch: req.headers['if-match']?.replace(/^"\*"$/, '*'),
|
|
10
|
+
ifNoneMatch: req.headers['if-none-match']?.replace(/^"\*"$/, '*')
|
|
23
11
|
}
|
|
24
12
|
}
|
|
25
13
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
14
|
+
const _parseHeaderEtagValue = value => {
|
|
15
|
+
return value.split(',').map(str => {
|
|
16
|
+
let result = str.trim()
|
|
17
|
+
if (result === '*') return result
|
|
18
|
+
if (result.startsWith('W/')) result = result.substring(2)
|
|
19
|
+
return result.startsWith('"') && result.endsWith('"') ? result.substring(1, result.length - 1) : null
|
|
20
|
+
})
|
|
33
21
|
}
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (targetEntity) {
|
|
55
|
-
_addEtagColumns(expand.expand, targetEntity)
|
|
23
|
+
const _getValidationStmt = (ifMatchEtags, ifNoneMatchEtags, req, model) => {
|
|
24
|
+
const etagElement = req.target.elements[req.target._etag.name]
|
|
25
|
+
|
|
26
|
+
const { target, alias, where } = convertPathExpressionToWhere(req.subject, model, { tableAliasPrefix: 'ETAG_' })
|
|
27
|
+
const select = SELECT.from(target).columns(1).where(where)
|
|
28
|
+
if (alias) select.SELECT.from.as = alias
|
|
29
|
+
// tell resolveView to leave this select alone
|
|
30
|
+
select._doNotResolve = true
|
|
31
|
+
// tell db layer that this is a subselect for etag validation
|
|
32
|
+
// hacky solution for gap in @sap/hana-client
|
|
33
|
+
select._etagValidation = true
|
|
34
|
+
|
|
35
|
+
const cond = []
|
|
36
|
+
if (ifMatchEtags) {
|
|
37
|
+
if (ifMatchEtags.includes('*')) return true
|
|
38
|
+
// HANA does not allow malformed time values in prepared statements
|
|
39
|
+
if (etagElement.type === 'cds.Timestamp' || etagElement.type === 'cds.DateTime') {
|
|
40
|
+
ifMatchEtags = ifMatchEtags.filter(val => new Date(val).toString() !== 'Invalid Date')
|
|
41
|
+
if (!ifMatchEtags.length) return false
|
|
56
42
|
}
|
|
43
|
+
|
|
44
|
+
cond.push({ ref: alias ? [alias, etagElement.name] : [etagElement.name] })
|
|
45
|
+
if (ifMatchEtags.length === 1) cond.push('=', { val: ifMatchEtags[0] })
|
|
46
|
+
else cond.push('in', { list: ifMatchEtags.map(val => ({ val })) })
|
|
47
|
+
} else {
|
|
48
|
+
if (ifNoneMatchEtags.includes('*')) return false
|
|
49
|
+
// if a malformed time value is present, it cannot match -> precondition true
|
|
50
|
+
if (
|
|
51
|
+
(etagElement.type === 'cds.Timestamp' || etagElement.type === 'cds.DateTime') &&
|
|
52
|
+
ifNoneMatchEtags.some(val => new Date(val).toString() === 'Invalid Date')
|
|
53
|
+
) {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
cond.push({ ref: alias ? [alias, etagElement.name] : [etagElement.name] })
|
|
58
|
+
if (ifNoneMatchEtags.length === 1) cond.push('!=', { val: ifNoneMatchEtags[0] })
|
|
59
|
+
else cond.push('not', 'in', { list: ifNoneMatchEtags.map(val => ({ val })) })
|
|
57
60
|
}
|
|
58
|
-
|
|
61
|
+
if (!cond.length) return
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
const isReadAfterDraftAction =
|
|
62
|
-
req.event === 'READ' && req.target._isDraftEnabled && req.context.event in { draftActivate: 1, EDIT: 1 }
|
|
63
|
-
// It's allowed to also delete drafts when actives are deleted
|
|
64
|
-
if (
|
|
65
|
-
cds.env.fiori?.lean_draft &&
|
|
66
|
-
req.event === 'READ' &&
|
|
67
|
-
req.context.event === 'DELETE' &&
|
|
68
|
-
req.target?.name.endsWith('.drafts') &&
|
|
69
|
-
!req.context?.target?.name.endsWith('.drafts')
|
|
70
|
-
)
|
|
71
|
-
return
|
|
72
|
-
const _req = isReadAfterDraftAction ? req.context : req
|
|
73
|
-
return _req._isOData && _req.isConcurrentResource
|
|
63
|
+
return select.where(cond)
|
|
74
64
|
}
|
|
75
65
|
|
|
76
66
|
/**
|
|
@@ -78,71 +68,110 @@ const _isConcurrentODataReq = req => {
|
|
|
78
68
|
*
|
|
79
69
|
* @param req
|
|
80
70
|
*/
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
cqn = getSelectCQN(req.query, req.target, this.model, isActive)
|
|
102
|
-
}
|
|
71
|
+
const commonGenericValidateETag = async function (req) {
|
|
72
|
+
/*
|
|
73
|
+
* currently, etag is only supported for OData requests
|
|
74
|
+
* REST requests should be added later
|
|
75
|
+
* other protocols, such as graphql, need to be checked
|
|
76
|
+
*/
|
|
77
|
+
if (req.protocol !== 'odata-v4') return
|
|
78
|
+
|
|
79
|
+
// automatically add etag columns if not already there
|
|
80
|
+
if (req.query.SELECT) addEtagColumns(req.query.SELECT.columns, req.target)
|
|
81
|
+
|
|
82
|
+
// querying a collection?
|
|
83
|
+
if (req.event === 'READ' && !req.query.SELECT.one) return
|
|
84
|
+
|
|
85
|
+
// etag provided?
|
|
86
|
+
const { ifMatch, ifNoneMatch } = _getMatchHeaders(req)
|
|
87
|
+
if (!ifMatch && !ifNoneMatch) {
|
|
88
|
+
if (req.event === 'READ') return // > ok and nothing more to do
|
|
89
|
+
req.reject(428) // > on writes, an etag must be provided
|
|
90
|
+
}
|
|
103
91
|
|
|
104
|
-
|
|
92
|
+
// normalize
|
|
93
|
+
const ifMatchEtags = ifMatch && _parseHeaderEtagValue(ifMatch)
|
|
94
|
+
const ifNoneMatchEtags = ifNoneMatch && _parseHeaderEtagValue(ifNoneMatch)
|
|
105
95
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
req.validateEtag(etag == null ? 'null' : etag)
|
|
109
|
-
} else {
|
|
110
|
-
req.validateEtag('*')
|
|
111
|
-
}
|
|
112
|
-
}
|
|
96
|
+
// get select for validation
|
|
97
|
+
const validationStmt = _getValidationStmt(ifMatchEtags, ifNoneMatchEtags, req, this.model)
|
|
113
98
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
99
|
+
// shortcuts
|
|
100
|
+
if (validationStmt === true) {
|
|
101
|
+
// wildcard -> nothing to do
|
|
102
|
+
return
|
|
103
|
+
} else if (validationStmt === false) {
|
|
104
|
+
// never true -> reject
|
|
105
|
+
req.reject(412)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// bound action or CRUD?
|
|
109
|
+
if (req.event === 'EDIT' || (req.target.actions && req.event in req.target.actions)) {
|
|
110
|
+
// REVISIT: how to not directly access db?
|
|
111
|
+
if (cds.db) {
|
|
112
|
+
const result = await cds.tx(req).run(validationStmt)
|
|
113
|
+
if (!result.length) req.reject(412)
|
|
117
114
|
}
|
|
115
|
+
} else if (validationStmt) {
|
|
116
|
+
// add where clause for validation
|
|
117
|
+
const validationClause = ['exists', validationStmt]
|
|
118
|
+
req.query.where(validationClause)
|
|
119
|
+
// HACK for current draft impl
|
|
120
|
+
req._etagValidationClause = validationClause
|
|
121
|
+
req._etagValidationType = ifMatchEtags ? 'if-match' : 'if-none-match'
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
/**
|
|
122
|
-
*
|
|
126
|
+
* adds a new uuid for the etag element to the request payload
|
|
123
127
|
*
|
|
128
|
+
* @param req
|
|
129
|
+
*/
|
|
130
|
+
const commonGenericGenerateETag = function (req) {
|
|
131
|
+
const etagElement = req.target.elements[req.target._etag.name]
|
|
132
|
+
req.data[etagElement.name] = cds.utils.uuid()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* handler registration
|
|
124
137
|
*/
|
|
125
138
|
/* istanbul ignore next */
|
|
126
139
|
module.exports = cds.service.impl(function () {
|
|
127
|
-
|
|
140
|
+
commonGenericValidateETag._initial = true
|
|
141
|
+
commonGenericGenerateETag._initial = true
|
|
128
142
|
|
|
129
143
|
for (const k in this.entities) {
|
|
130
144
|
const entity = this.entities[k]
|
|
131
145
|
|
|
132
146
|
if (!entity._etag) continue
|
|
133
147
|
|
|
134
|
-
// handler for CREATE is registered for backwards compatibility w.r.t. ETag generation
|
|
135
|
-
let events = ['CREATE', 'READ', 'UPDATE', 'DELETE']
|
|
136
|
-
|
|
137
|
-
// if odata and fiori is separated, this will not be needed in the odata version
|
|
138
148
|
if (entity._isDraftEnabled) {
|
|
139
|
-
|
|
149
|
+
if (cds.env.fiori?.lean_draft) {
|
|
150
|
+
this.before(['READ', 'DELETE'], entity, commonGenericValidateETag)
|
|
151
|
+
// if draft compat is on, the read handler is automatically registered for <entity> and <entity>.drafts
|
|
152
|
+
const events = cds.env.fiori.draft_compat ? ['UPDATE', 'CANCEL'] : ['READ', 'UPDATE', 'CANCEL']
|
|
153
|
+
this.before(events, entity.drafts, commonGenericValidateETag)
|
|
154
|
+
} else {
|
|
155
|
+
this.before(['READ', 'PATCH', 'CANCEL', 'DELETE'], entity, commonGenericValidateETag)
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
this.before(['READ', 'UPDATE', 'DELETE'], entity, commonGenericValidateETag)
|
|
140
159
|
}
|
|
141
160
|
|
|
142
|
-
this.before(events, entity, commonGenericEtag)
|
|
143
|
-
|
|
144
161
|
for (const action in entity.actions) {
|
|
145
|
-
|
|
162
|
+
// etag not applicable to functions and unbound actions
|
|
163
|
+
if (entity.actions[action].kind !== 'action') continue
|
|
164
|
+
if (entity._isDraftEnabled) {
|
|
165
|
+
let _entity = entity
|
|
166
|
+
if (cds.env.fiori?.lean_draft) _entity = action === 'draftEdit' ? entity : entity.drafts
|
|
167
|
+
this.before(action === 'draftEdit' ? 'EDIT' : action, _entity, commonGenericValidateETag)
|
|
168
|
+
} else {
|
|
169
|
+
this.before(action, entity, commonGenericValidateETag)
|
|
170
|
+
}
|
|
146
171
|
}
|
|
172
|
+
|
|
173
|
+
// for backwards compatibility w.r.t. ETag generation if type UUID
|
|
174
|
+
const etagElement = entity.elements[entity._etag.name]
|
|
175
|
+
if (etagElement.isUUID) this.before(['CREATE', 'UPDATE', 'NEW'], entity, commonGenericGenerateETag)
|
|
147
176
|
}
|
|
148
177
|
})
|
|
@@ -42,18 +42,6 @@ const _isDraftCoreComputed = (req, element, event) =>
|
|
|
42
42
|
req._.event === 'draftActivate' &&
|
|
43
43
|
!((event === 'CREATE' && element['@cds.on.insert']) || element['@cds.on.update'])
|
|
44
44
|
|
|
45
|
-
const _isStreamingProperty = (elements, row, property) =>
|
|
46
|
-
Object.values(elements).some(
|
|
47
|
-
element => element['@Core.MediaType'] && element['@Core.MediaType']['='] === property && row[element.name]
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
const _getMediaTypeValue = () => {
|
|
51
|
-
const ctx = cds.context
|
|
52
|
-
return (
|
|
53
|
-
!ctx?.http?.req?.headers?.['content-type']?.match(/json|multipart/i) && ctx?.http?.req?.headers?.['content-type']
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
45
|
const _preProcessAssertTarget = (assocInfo, assertMap) => {
|
|
58
46
|
const { element: assoc, row } = assocInfo
|
|
59
47
|
const assocTarget = assoc._target
|
|
@@ -147,14 +135,6 @@ const _processCategory = (req, category, value, elementInfo, assertMap) => {
|
|
|
147
135
|
if ((event === 'UPDATE' || event === 'CREATE') && category === '@assert.target') {
|
|
148
136
|
_preProcessAssertTarget(elementInfo, assertMap)
|
|
149
137
|
}
|
|
150
|
-
|
|
151
|
-
// set media type from content-type header if streaming
|
|
152
|
-
if (category === 'stream') {
|
|
153
|
-
if (_isStreamingProperty(element.parent.elements, row, key)) {
|
|
154
|
-
const mtValue = _getMediaTypeValue()
|
|
155
|
-
if (mtValue) row[key] = mtValue
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
138
|
}
|
|
159
139
|
|
|
160
140
|
const _getProcessorFn = (req, errors, assertMap) => {
|
|
@@ -186,7 +166,12 @@ const _pick = element => {
|
|
|
186
166
|
categories.push({ category: 'propagateForeignKeys' })
|
|
187
167
|
}
|
|
188
168
|
|
|
189
|
-
if (
|
|
169
|
+
if (
|
|
170
|
+
element['@assert.range'] ||
|
|
171
|
+
element['@assert.enum'] ||
|
|
172
|
+
element['@assert.format'] ||
|
|
173
|
+
element.type === 'cds.Decimal'
|
|
174
|
+
) {
|
|
190
175
|
categories.push('assert')
|
|
191
176
|
}
|
|
192
177
|
|
|
@@ -24,7 +24,7 @@ const _fillStructure = (row, parts, element, category, args) => {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const _getProcessorFn = req => {
|
|
27
|
-
const REST = req.
|
|
27
|
+
const REST = req.protocol === 'rest'
|
|
28
28
|
|
|
29
29
|
return ({ row, key, element, plain }) => {
|
|
30
30
|
if (!row || row[key] !== undefined) return
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const { isNewStream } = require('../utils/stream')
|
|
2
|
+
|
|
3
|
+
const cds = require('../../cds')
|
|
4
|
+
|
|
5
|
+
const _getStreamingProperties = elements => {
|
|
6
|
+
const result = []
|
|
7
|
+
for (const key in elements) {
|
|
8
|
+
const element = elements[key]
|
|
9
|
+
if (typeof element['@Core.MediaType'] === 'object') {
|
|
10
|
+
result.push({ stream: key, type: element['@Core.MediaType']['='] })
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return result
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const _getMediaTypeValue = () => {
|
|
18
|
+
const ctx = cds.context
|
|
19
|
+
return (
|
|
20
|
+
!ctx?.http?.req?.headers?.['content-type']?.match(/json|multipart/i) && ctx?.http?.req?.headers?.['content-type']
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function _addContentType(req, mtValue) {
|
|
25
|
+
if (!req.data) return
|
|
26
|
+
const streamProp = _getStreamingProperties(req.target.elements).find(prop => req.data[prop.stream])
|
|
27
|
+
if (streamProp) req.data[streamProp.type] = mtValue
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function _addContentTypeStream(req, mtValue) {
|
|
31
|
+
if (req.query.UPDATE || req.query.STREAM?.from) return
|
|
32
|
+
if (req.target._hasPersistenceSkip) return
|
|
33
|
+
const streamProp = _getStreamingProperties(req.target.elements).find(prop => req.query.STREAM.column === prop.stream)
|
|
34
|
+
// REVISIT: move this update to after handler and add etag
|
|
35
|
+
if (streamProp) await UPDATE.entity(req.query.STREAM.into).data({ [streamProp.type]: mtValue })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function addContentType(req) {
|
|
39
|
+
if (!req.query || !req.target) return
|
|
40
|
+
const mtValue = _getMediaTypeValue()
|
|
41
|
+
if (!mtValue) return
|
|
42
|
+
|
|
43
|
+
if (isNewStream()) {
|
|
44
|
+
_addContentTypeStream(req, mtValue)
|
|
45
|
+
} else {
|
|
46
|
+
_addContentType(req, mtValue)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = cds.service.impl(function () {
|
|
51
|
+
this.before(['PATCH', 'UPDATE', 'STREAM'], '*', addContentType)
|
|
52
|
+
})
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
|
+
const normalizeTimestamp = require('../utils/normalizeTimestamp')
|
|
2
3
|
|
|
3
4
|
const _getDateFromQueryOptions = str => {
|
|
4
5
|
if (str) {
|
|
5
6
|
const match = str.match(/^date'(.+)'$/)
|
|
6
7
|
// REVISIT: What happens with invalid date values in query parameter? if match.length > 1
|
|
7
|
-
return
|
|
8
|
+
return normalizeTimestamp(match ? match[1] : str)
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -51,15 +52,31 @@ const commonGenericTemporal = function (req) {
|
|
|
51
52
|
|
|
52
53
|
if (_isAsOfNow(_queryOptions)) {
|
|
53
54
|
const date = new Date()
|
|
54
|
-
_['VALID-FROM'] = date
|
|
55
|
-
_['VALID-TO'] =
|
|
55
|
+
_['VALID-FROM'] = normalizeTimestamp(date)
|
|
56
|
+
_['VALID-TO'] = normalizeTimestamp(date.getTime() + _getTimeDelta(req.target))
|
|
56
57
|
} else if (_queryOptions['sap-valid-at']) {
|
|
57
|
-
const
|
|
58
|
-
_['VALID-FROM'] =
|
|
59
|
-
|
|
58
|
+
const dateAsIsoString = _getDateFromQueryOptions(_queryOptions['sap-valid-at'])
|
|
59
|
+
_['VALID-FROM'] = dateAsIsoString
|
|
60
|
+
|
|
61
|
+
if (cds.env.features.precise_timestamps) {
|
|
62
|
+
const nanos = dateAsIsoString.slice(-5)
|
|
63
|
+
// we would lose the nano precision here, so we just cut it off before and attach it again here
|
|
64
|
+
_['VALID-TO'] =
|
|
65
|
+
normalizeTimestamp(
|
|
66
|
+
new Date(dateAsIsoString).getTime() + _getTimeDelta(req.target, _queryOptions['sap-valid-at'])
|
|
67
|
+
).slice(0, -5) + nanos
|
|
68
|
+
} else {
|
|
69
|
+
_['VALID-TO'] = normalizeTimestamp(
|
|
70
|
+
new Date(dateAsIsoString).getTime() + _getTimeDelta(req.target, _queryOptions['sap-valid-at'])
|
|
71
|
+
)
|
|
72
|
+
}
|
|
60
73
|
} else if (_queryOptions['sap-valid-from'] || _queryOptions['sap-valid-to']) {
|
|
61
|
-
_['VALID-FROM'] =
|
|
62
|
-
|
|
74
|
+
_['VALID-FROM'] = normalizeTimestamp(
|
|
75
|
+
_getDateFromQueryOptions(_queryOptions['sap-valid-from'] ?? normalizeTimestamp('0001-01-01T00:00:00.0000000Z'))
|
|
76
|
+
)
|
|
77
|
+
_['VALID-TO'] = normalizeTimestamp(
|
|
78
|
+
_getDateFromQueryOptions(_queryOptions['sap-valid-to'] ?? normalizeTimestamp('9999-12-31T23:59:59.9999999Z'))
|
|
79
|
+
)
|
|
63
80
|
}
|
|
64
81
|
}
|
|
65
82
|
|
|
@@ -45,9 +45,7 @@ ASSERT_FORMAT=Value "{0}" is not in specified format "{1}"
|
|
|
45
45
|
ASSERT_DATA_TYPE=Value {0} is invalid according to type definition "{1}"
|
|
46
46
|
ASSERT_ENUM=Value {0} is invalid according to enum declaration {{1}}
|
|
47
47
|
ASSERT_NOT_NULL=Value is required
|
|
48
|
-
ASSERT_REFERENCE_INTEGRITY=Reference integrity is violated for association "{0}"
|
|
49
48
|
ASSERT_TARGET="Value doesn't exist"
|
|
50
|
-
ASSERT_DEEP_ASSOCIATION=It is not allowed to modify sub documents in {0} Association "{1}"
|
|
51
49
|
|
|
52
50
|
# db
|
|
53
51
|
NO_DATABASE_CONNECTION=No database connection
|
|
@@ -83,7 +83,7 @@ function isPathToDraft(path, model) {
|
|
|
83
83
|
for (const r of path) {
|
|
84
84
|
if (r.id) {
|
|
85
85
|
const isaIndex = r.where.findIndex(ele => ele.ref && ele.ref[0] === 'IsActiveEntity')
|
|
86
|
-
if (isaIndex) draft = !r.where[isaIndex + 2].val
|
|
86
|
+
if (isaIndex !== -1) draft = !r.where[isaIndex + 2].val
|
|
87
87
|
current = current ? definitions[current.elements[r.id].target] : definitions[r.id]
|
|
88
88
|
} else {
|
|
89
89
|
if (r === 'SiblingEntity') draft = !!draft
|
|
@@ -67,8 +67,8 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
|
|
|
67
67
|
const currentEntityName = draft ? entityName && ensureDraftsSuffix(entityName) : entityName
|
|
68
68
|
if (!currentEntityName) continue
|
|
69
69
|
if (!draft && currentEntityName.endsWith('_drafts')) draft = true
|
|
70
|
-
const tableAlias = `T${i}`
|
|
71
|
-
const currentSelect = SELECT.from(
|
|
70
|
+
const tableAlias = options.tableAliasPrefix ? `${options.tableAliasPrefix}${i}` : `T${i}`
|
|
71
|
+
const currentSelect = SELECT.from({ ref: [currentEntityName], as: tableAlias })
|
|
72
72
|
|
|
73
73
|
if (fromClause.ref[i].where) {
|
|
74
74
|
currentSelect.where(addAliasToExpression(fromClause.ref[i].where, tableAlias))
|
|
@@ -969,13 +969,16 @@ const _convertUpdate = (query, model, options) => {
|
|
|
969
969
|
|
|
970
970
|
if (alias) update.UPDATE.entity = { ref: [target], as: alias }
|
|
971
971
|
if (where) update.where(where)
|
|
972
|
+
|
|
972
973
|
const targetEntity = model.definitions[target]
|
|
974
|
+
|
|
973
975
|
if (query.UPDATE.where) {
|
|
974
976
|
update.where(addAliasToExpression(query.UPDATE.where, alias))
|
|
975
977
|
_convertToOneEqNullInFilter(update.UPDATE, targetEntity)
|
|
976
978
|
}
|
|
977
979
|
|
|
978
980
|
if (!targetEntity) return update
|
|
981
|
+
|
|
979
982
|
return resolveView(update, model, cds.db)
|
|
980
983
|
}
|
|
981
984
|
|
|
@@ -39,56 +39,6 @@ const _getUps = (entity, model) => {
|
|
|
39
39
|
return entity.set('__parents', ups)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const _ifDataSubject = (entity, role) => {
|
|
43
|
-
return entity['@PersonalData.EntitySemantics'] === 'DataSubject' && entity['@PersonalData.DataSubjectRole'] === role
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const _getDataSubjectUp = (role, model, entity, prev, next, result) => {
|
|
47
|
-
for (const element of _getUps(entity, model)) {
|
|
48
|
-
const me = { entity, relative: element.parent, element }
|
|
49
|
-
if (prev) prev.next = me
|
|
50
|
-
if (_ifDataSubject(element.parent, role)) {
|
|
51
|
-
if (!result) result = { dataSubjectEntity: element.parent, subs: [] }
|
|
52
|
-
result.subs.push(next || me)
|
|
53
|
-
return result
|
|
54
|
-
} else {
|
|
55
|
-
// dfs is a must here
|
|
56
|
-
result = _getDataSubjectUp(role, model, element.parent, me, next || me, result)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return result
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const _getDataSubjectDown = (role, entity, prev, next) => {
|
|
63
|
-
const associations = Object.values(entity.associations || {}).filter(e => !e._isBacklink)
|
|
64
|
-
for (const element of associations) {
|
|
65
|
-
const me = { entity, relative: entity, element }
|
|
66
|
-
if (_ifDataSubject(element._target, role)) {
|
|
67
|
-
if (prev) prev.next = me
|
|
68
|
-
return { dataSubjectEntity: element._target, subs: [next || me] }
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// bfs makes more sense here
|
|
72
|
-
for (const element of associations) {
|
|
73
|
-
const me = { entity, relative: entity, element }
|
|
74
|
-
if (prev) prev.next = me
|
|
75
|
-
const dataSubject = _getDataSubjectDown(role, element._target, me, next || me)
|
|
76
|
-
if (dataSubject) return dataSubject
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const getDataSubject = (entity, model, role) => {
|
|
81
|
-
const hash = '__dataSubject4' + role
|
|
82
|
-
if (entity.own(hash)) return entity[hash]
|
|
83
|
-
// entities with EntitySemantics 'DataSubjectDetails' or 'Other' must not necessarily
|
|
84
|
-
// be always below or always above 'DataSubject' entity in CSN tree
|
|
85
|
-
let dataSubject = _getDataSubjectUp(role, model, entity)
|
|
86
|
-
if (!dataSubject) {
|
|
87
|
-
dataSubject = _getDataSubjectDown(role, entity)
|
|
88
|
-
}
|
|
89
|
-
return entity.set(hash, dataSubject)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
42
|
const _resolve = (edmName, model, namespace) => {
|
|
93
43
|
const resolved = model._edmToCSNNameMap[namespace][edmName.replace(/\./g, '_')]
|
|
94
44
|
// the edm name has an additional suffix 'Parameters' in case of views with parameters
|
|
@@ -249,7 +199,6 @@ module.exports = {
|
|
|
249
199
|
getEtagElement,
|
|
250
200
|
findCsnTargetFor,
|
|
251
201
|
getElementDeep,
|
|
252
|
-
getDataSubject,
|
|
253
202
|
alias2ref,
|
|
254
203
|
getComp2oneParents,
|
|
255
204
|
prefixForStruct,
|