@sap/cds 5.5.5 → 5.6.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 +107 -1
- package/apis/services.d.ts +27 -1
- package/app/index.js +22 -11
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +1 -1
- package/bin/build/provider/fiori/index.js +1 -1
- package/bin/build/provider/hana/2migration.js +8 -7
- package/bin/build/provider/java-cf/index.js +1 -1
- package/bin/deploy/to-hana/hana.js +1 -17
- package/common.cds +8 -0
- package/lib/compile/to/sql.js +22 -2
- package/lib/connect/bindings.js +2 -1
- package/lib/core/reflect.js +3 -1
- package/lib/env/index.js +175 -41
- package/lib/env/requires.js +16 -1
- package/lib/i18n/localize.js +31 -4
- package/lib/index.js +3 -3
- package/lib/log/format/kibana.js +6 -2
- package/lib/ql/Query.js +1 -0
- package/lib/ql/SELECT.js +15 -8
- package/lib/ql/Whereable.js +5 -0
- package/lib/req/context.js +13 -5
- package/lib/serve/Service-dispatch.js +8 -1
- package/lib/utils/axios.js +7 -0
- package/lib/utils/data.js +1 -1
- package/lib/utils/tests.js +1 -1
- package/libx/_runtime/audit/Service.js +18 -18
- package/libx/_runtime/audit/generic/personal/access.js +1 -1
- package/libx/_runtime/audit/generic/personal/modification.js +3 -2
- package/libx/_runtime/audit/generic/personal/utils.js +23 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
- package/libx/_runtime/cds-services/util/assert.js +29 -13
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/Association.js +72 -0
- package/libx/_runtime/common/aspects/any.js +8 -45
- package/libx/_runtime/common/aspects/entity.js +0 -1
- package/libx/_runtime/common/aspects/relation.js +40 -0
- package/libx/_runtime/common/aspects/utils.js +73 -1
- package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
- package/libx/_runtime/common/composition/data.js +3 -2
- package/libx/_runtime/common/composition/delete.js +3 -1
- package/libx/_runtime/common/composition/tree.js +23 -18
- package/libx/_runtime/common/composition/utils.js +34 -8
- package/libx/_runtime/common/error/frontend.js +6 -1
- package/libx/_runtime/common/generic/auth.js +5 -9
- package/libx/_runtime/common/generic/crud.js +2 -2
- package/libx/_runtime/common/generic/etag.js +11 -8
- package/libx/_runtime/common/generic/input.js +3 -3
- package/libx/_runtime/common/generic/paging.js +9 -5
- package/libx/_runtime/common/generic/put.js +3 -2
- package/libx/_runtime/common/generic/sorting.js +3 -3
- package/libx/_runtime/common/generic/temporal.js +3 -3
- package/libx/_runtime/common/utils/cqn.js +20 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
- package/libx/_runtime/common/utils/csn.js +50 -52
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
- package/libx/_runtime/common/utils/generateOnCond.js +40 -70
- package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
- package/libx/_runtime/common/utils/postProcessing.js +3 -0
- package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
- package/libx/_runtime/common/utils/resolveStructured.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
- package/libx/_runtime/common/utils/template.js +54 -46
- package/libx/_runtime/db/Service.js +9 -2
- package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
- package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
- package/libx/_runtime/db/generic/create.js +1 -0
- package/libx/_runtime/db/generic/input.js +7 -11
- package/libx/_runtime/db/generic/integrity.js +2 -2
- package/libx/_runtime/db/generic/rewrite.js +2 -5
- package/libx/_runtime/db/generic/update.js +1 -0
- package/libx/_runtime/db/query/read.js +9 -4
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
- package/libx/_runtime/db/sql-builder/annotations.js +1 -0
- package/libx/_runtime/db/utils/columns.js +14 -43
- package/libx/_runtime/fiori/generic/activate.js +3 -2
- package/libx/_runtime/fiori/generic/before.js +2 -2
- package/libx/_runtime/fiori/generic/cancel.js +3 -2
- package/libx/_runtime/fiori/generic/delete.js +3 -2
- package/libx/_runtime/fiori/generic/edit.js +2 -2
- package/libx/_runtime/fiori/generic/new.js +2 -2
- package/libx/_runtime/fiori/generic/patch.js +2 -2
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +17 -63
- package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
- package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
- package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
- package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
- package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
- package/libx/_runtime/fiori/uiflex/index.js +35 -0
- package/libx/_runtime/fiori/uiflex/utils.js +78 -0
- package/libx/_runtime/fiori/utils/handler.js +3 -13
- package/libx/_runtime/fiori/utils/where.js +6 -1
- package/libx/_runtime/hana/pool.js +12 -11
- package/libx/_runtime/hana/search2cqn4sql.js +34 -43
- package/libx/_runtime/hana/searchToContains.js +3 -3
- package/libx/_runtime/index.js +5 -2
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
- package/libx/_runtime/messaging/common-utils/connections.js +11 -14
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
- package/libx/_runtime/messaging/message-queuing.js +18 -0
- package/libx/_runtime/remote/Service.js +14 -2
- package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
- package/libx/_runtime/remote/utils/client.js +117 -23
- package/libx/_runtime/sqlite/Service.js +2 -2
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
- package/libx/gql/GraphQLAdapter.js +33 -0
- package/libx/gql/constants/adapter.js +69 -0
- package/libx/gql/constants/cds.js +18 -0
- package/libx/gql/constants/graphql.js +33 -0
- package/libx/gql/resolvers/crud/create.js +15 -0
- package/libx/gql/resolvers/crud/delete.js +24 -0
- package/libx/gql/resolvers/crud/index.js +6 -0
- package/libx/gql/resolvers/crud/read.js +25 -0
- package/libx/gql/resolvers/crud/update.js +31 -0
- package/libx/gql/resolvers/crud/utils/index.js +36 -0
- package/libx/gql/resolvers/field.js +5 -0
- package/libx/gql/resolvers/index.js +7 -0
- package/libx/gql/resolvers/mutation.js +23 -0
- package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
- package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
- package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
- package/libx/gql/resolvers/parse/ast/index.js +3 -0
- package/libx/gql/resolvers/parse/ast/meta.js +4 -0
- package/libx/gql/resolvers/parse/ast/variable.js +7 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
- package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
- package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
- package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
- package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
- package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
- package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
- package/libx/gql/resolvers/parse/utils/index.js +8 -0
- package/libx/gql/resolvers/query.js +13 -0
- package/libx/gql/resolvers/root.js +34 -0
- package/libx/gql/schema/generate.js +18 -0
- package/libx/gql/schema/index.js +5 -0
- package/libx/gql/schema/mutation.js +76 -0
- package/libx/gql/schema/query.js +108 -0
- package/libx/gql/schema/typeDefMap.js +45 -0
- package/libx/gql/schema/utils/index.js +54 -0
- package/libx/gql/utils/index.js +12 -0
- package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
- package/libx/odata/index.js +80 -0
- package/libx/odata/odata2cqn/afterburner.js +170 -0
- package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
- package/libx/odata/odata2cqn/index.js +3 -0
- package/libx/odata/odata2cqn/parser.js +1 -0
- package/libx/odata/utils/index.js +64 -0
- package/libx/rest/RestAdapter.js +101 -0
- package/libx/rest/RestRequest.js +30 -0
- package/libx/rest/index.js +3 -0
- package/libx/rest/middleware/auth.js +22 -0
- package/libx/rest/middleware/content.js +15 -0
- package/libx/rest/middleware/create.js +40 -0
- package/libx/rest/middleware/delete.js +20 -0
- package/libx/rest/middleware/error.js +56 -0
- package/libx/rest/middleware/operation.js +39 -0
- package/libx/rest/middleware/parse.js +90 -0
- package/libx/rest/middleware/read.js +29 -0
- package/libx/rest/middleware/update.js +42 -0
- package/libx/rest/utils/data.js +65 -0
- package/package.json +4 -1
- package/server.js +20 -2
- package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
- package/libx/_runtime/cds-services/util/auditlog.js +0 -247
- package/libx/_runtime/cds-services/util/xsenv.js +0 -51
- package/libx/_runtime/common/utils/backlinks.js +0 -83
- package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
- package/libx/_runtime/odata/index.js +0 -55
- package/libx/_runtime/odata/odata2cqn.js +0 -1
- package/libx/_runtime/odata/readToCqn.js +0 -129
- package/libx/_runtime/remote/cqn2odata/index.js +0 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const cds = require('../../../cds')
|
|
2
|
+
const { ensureDraftsSuffix } = require('../../../common/utils/draft')
|
|
3
|
+
|
|
4
|
+
const { EXT_BACK_PACK } = require('../utils')
|
|
5
|
+
|
|
6
|
+
const _getDraftTable = (view, cds) => {
|
|
7
|
+
return cds.model.definitions[view]._isDraftEnabled ? ensureDraftsSuffix(view) : undefined
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const _addAnnotation = extension => {
|
|
11
|
+
Object.values(extension.elements).forEach(el => {
|
|
12
|
+
el['@cds.extension'] = true
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const _isProjection = target => target && target.query && target.query._target
|
|
17
|
+
|
|
18
|
+
const _resolveViews = (target, views_ = []) => {
|
|
19
|
+
if (_isProjection(target)) {
|
|
20
|
+
views_.push(target)
|
|
21
|
+
return _resolveViews(target.query._target, views_)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return target
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const _getCsn = req => {
|
|
28
|
+
const csn = {
|
|
29
|
+
extensions: req.data.extensions.map(ext => JSON.parse(ext))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return csn
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const _addViews = csn => {
|
|
36
|
+
csn.extensions.forEach(extension => {
|
|
37
|
+
const target = cds.model.definitions[extension.extend]
|
|
38
|
+
const views_ = []
|
|
39
|
+
const view = _resolveViews(target, views_)
|
|
40
|
+
extension.extend = view && view.name
|
|
41
|
+
_addAnnotation(extension)
|
|
42
|
+
|
|
43
|
+
// All projection views leading to the db entity are extended with back pack in case view columns are explicitly listed.
|
|
44
|
+
// The views using projections with '*' obtain the back pack automatically.
|
|
45
|
+
views_.forEach(view => {
|
|
46
|
+
if (!view.projection || (view.projection.columns && !view.projection.columns.some(col => col === '*'))) {
|
|
47
|
+
csn.extensions.push({
|
|
48
|
+
extend: view.name,
|
|
49
|
+
columns: Object.keys(extension.elements).map(key => {
|
|
50
|
+
return { ref: [key] }
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const _handleDefaults = async (extension, dbEntity, req, cds, draftEntity) => {
|
|
59
|
+
const ext = Object.keys(extension.elements)
|
|
60
|
+
.filter(key => extension.elements[key].default)
|
|
61
|
+
.map(key => {
|
|
62
|
+
const element = extension.elements[key]
|
|
63
|
+
const t = cds.model.definitions[element.type] || cds.builtin.types[element.type]
|
|
64
|
+
const value = t && t instanceof cds.builtin.classes.string ? `"${element.default.val}"` : element.default.val
|
|
65
|
+
return `"${key}":${value}`
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
if (ext.length !== 0) {
|
|
69
|
+
const extStr = ext.join(',')
|
|
70
|
+
const changed = `'{${extStr},' || substr(${EXT_BACK_PACK}, 2, length(${EXT_BACK_PACK})-1)`
|
|
71
|
+
const assign = `${EXT_BACK_PACK} = CASE WHEN ${EXT_BACK_PACK} IS NULL THEN '{${extStr}}' ELSE ${changed} END`
|
|
72
|
+
await UPDATE(dbEntity).with(assign)
|
|
73
|
+
if (draftEntity) await UPDATE(draftEntity).with(assign)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const _validateCsn = (csn, req) => {
|
|
78
|
+
csn.extensions.forEach(extension => {
|
|
79
|
+
if (!extension.extend || !cds.model.definitions[extension.extend]) {
|
|
80
|
+
req.reject(400, 'Invalid extension. Parameter "extend" missing or malformed')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!extension.elements) {
|
|
84
|
+
req.reject(400, 'Invalid extension. Missing parameter "elements"')
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const _validateExtensionFields = async (csn, req) => {
|
|
90
|
+
csn.extensions.forEach(extension => {
|
|
91
|
+
if (extension.elements) {
|
|
92
|
+
Object.keys(extension.elements).forEach(name => {
|
|
93
|
+
if (!/^[A-Za-z]\w*$/.test(name)) {
|
|
94
|
+
req.reject(400, `Invalid extension. Bad element name "${name}"`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (Object.keys(cds.model.definitions[extension.extend].elements).includes(name)) {
|
|
98
|
+
req.reject(400, `Invalid extension. Element "${name}" already exists`)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const _getCompilerError = messages => {
|
|
106
|
+
const defaultMsg = 'Error while compiling extension'
|
|
107
|
+
if (!messages) return defaultMsg
|
|
108
|
+
|
|
109
|
+
for (const msg of messages) {
|
|
110
|
+
if (msg.severity === 'Error') return msg.message
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return defaultMsg
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const _validateExtension = async (csn, req) => {
|
|
117
|
+
try {
|
|
118
|
+
const base = await cds.load('*', cds.options)
|
|
119
|
+
const baseCsn = await cds.compile.to.json(base)
|
|
120
|
+
const extCsn = await cds.compile.to.json(csn)
|
|
121
|
+
await cds.compile.to.csn({ 'base.csn': baseCsn, 'ext.csn': extCsn })
|
|
122
|
+
} catch (err) {
|
|
123
|
+
req.reject(400, _getCompilerError(err.messages))
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = function () {
|
|
128
|
+
this.on('addExtension', async req => {
|
|
129
|
+
const csn = _getCsn(req, cds)
|
|
130
|
+
_validateCsn(csn, req)
|
|
131
|
+
await _validateExtensionFields(csn, req)
|
|
132
|
+
_addViews(csn, cds)
|
|
133
|
+
await _validateExtension(csn, req)
|
|
134
|
+
|
|
135
|
+
const ID = cds.utils.uuid()
|
|
136
|
+
await INSERT.into('cds_r.Extensions').entries([{ ID, csn: JSON.stringify(csn) }])
|
|
137
|
+
|
|
138
|
+
for (const ext of req.data.extensions) {
|
|
139
|
+
const extension = JSON.parse(ext)
|
|
140
|
+
const draft = _getDraftTable(extension.extend, cds)
|
|
141
|
+
const target = cds.model.definitions[extension.extend]
|
|
142
|
+
const dbEntity = _resolveViews(target).name
|
|
143
|
+
await _handleDefaults(extension, dbEntity, req, cds, draft)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
setTimeout(() => process.send('restart'), 1111)
|
|
147
|
+
})
|
|
148
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const { EXT_BACK_PACK, getExtendedFields, hasExtendedEntity, isExtendedEntity, getTargetRead } = require('../utils')
|
|
2
|
+
|
|
3
|
+
const _addBackPack = (columns, extFields, alias) => {
|
|
4
|
+
if (!columns) return
|
|
5
|
+
|
|
6
|
+
const hasBackPack = columns.some(
|
|
7
|
+
col => col.ref && col.ref[col.ref.length - 1] === EXT_BACK_PACK && _hasAlias(col.ref, alias)
|
|
8
|
+
)
|
|
9
|
+
if (hasBackPack) return // get out early, avoiding overhead of second check
|
|
10
|
+
|
|
11
|
+
const hasExtFields = columns.some(
|
|
12
|
+
col => col.ref && extFields.includes(col.ref[col.ref.length - 1]) && _hasAlias(col.ref, alias)
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if (hasExtFields) {
|
|
16
|
+
const col = { ref: [EXT_BACK_PACK] }
|
|
17
|
+
if (alias) col.ref.unshift(alias)
|
|
18
|
+
columns.push(col)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
Removing backpack if not needed doesn't work. Probably ref copy problem.
|
|
23
|
+
if (hasBackPack && !hasExtFields) remove backpack.
|
|
24
|
+
*/
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const _hasAlias = (ref, alias) => {
|
|
28
|
+
return (ref.length === 1 && !alias) || (ref.length > 1 && ref[0] === alias)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const _removeExtendedFields = (columns, extFields, alias) => {
|
|
32
|
+
if (!columns) return
|
|
33
|
+
|
|
34
|
+
let i = columns.length
|
|
35
|
+
while (i--) {
|
|
36
|
+
const col = columns[i]
|
|
37
|
+
if (col.ref && extFields.includes(col.ref[col.ref.length - 1]) && _hasAlias(col.ref, alias)) {
|
|
38
|
+
columns.splice(i, 1)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const _transformUnion = (req, model) => {
|
|
44
|
+
// second element is active entity
|
|
45
|
+
const name = req.target.name.SET ? req.target.name.SET.args[1]._target.name : req.target.name
|
|
46
|
+
const extFields = getExtendedFields(name, model)
|
|
47
|
+
_addBackPack(req.query.SELECT.columns, extFields)
|
|
48
|
+
_removeExtendedFields(req.query.SELECT.columns, extFields)
|
|
49
|
+
|
|
50
|
+
_addBackPack(
|
|
51
|
+
req.query.SELECT.from.SET.args[0].SELECT.columns,
|
|
52
|
+
extFields,
|
|
53
|
+
req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
|
|
54
|
+
)
|
|
55
|
+
_addBackPack(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
|
|
56
|
+
_removeExtendedFields(
|
|
57
|
+
req.query.SELECT.from.SET.args[0].SELECT.columns,
|
|
58
|
+
extFields,
|
|
59
|
+
req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
|
|
60
|
+
)
|
|
61
|
+
_removeExtendedFields(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const _getAliasedEntitiesForJoin = (args, model) => {
|
|
65
|
+
const extEntities = []
|
|
66
|
+
|
|
67
|
+
args.forEach(arg => {
|
|
68
|
+
if (arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministativeData' && isExtendedEntity(arg.ref[0], model)) {
|
|
69
|
+
const extFields = getExtendedFields(arg.ref[0], model)
|
|
70
|
+
extEntities.push({ name: arg.ref[0], as: arg.as, extFields })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (arg.join) {
|
|
74
|
+
extEntities.push(..._getAliasedEntitiesForJoin(arg.args, model))
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
return extEntities
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const _transformJoin = (req, model) => {
|
|
82
|
+
const extEntities = _getAliasedEntitiesForJoin(req.query.SELECT.from.args, model)
|
|
83
|
+
|
|
84
|
+
extEntities.forEach(ext => {
|
|
85
|
+
_addBackPack(req.query.SELECT.columns, ext.extFields, ext.as)
|
|
86
|
+
_removeExtendedFields(req.query.SELECT.columns, ext.extFields, ext.as)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const _transformColumns = (columns, targetName, model) => {
|
|
91
|
+
if (!columns) return
|
|
92
|
+
|
|
93
|
+
const extFields = getExtendedFields(targetName, model)
|
|
94
|
+
if (extFields.length !== 0) {
|
|
95
|
+
_addBackPack(columns, extFields)
|
|
96
|
+
_removeExtendedFields(columns, extFields)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
columns.forEach(col => {
|
|
100
|
+
if (col.ref && col.expand) {
|
|
101
|
+
const expTargetName = model.definitions[targetName].elements[col.ref[0]].target
|
|
102
|
+
_transformColumns(col.expand, expTargetName, model)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function transformExtendedFieldsREAD(req) {
|
|
108
|
+
if (!hasExtendedEntity(req, this.model)) return
|
|
109
|
+
|
|
110
|
+
const target = getTargetRead(req)
|
|
111
|
+
_transformColumns(req.query.SELECT.columns, target.name, this.model)
|
|
112
|
+
|
|
113
|
+
if (req.query.SELECT.from.SET) return _transformUnion(req, this.model) // union
|
|
114
|
+
if (req.query.SELECT.from.join) return _transformJoin(req, this.model) // join
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
transformExtendedFieldsREAD
|
|
119
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { EXT_BACK_PACK, hasExtendedEntity, getTargetRead } = require('../utils')
|
|
2
|
+
|
|
3
|
+
const getTemplate = require('../../../common/utils/template')
|
|
4
|
+
const templateProcessor = require('../../../common/utils/templateProcessor')
|
|
5
|
+
|
|
6
|
+
const _pick = element => {
|
|
7
|
+
return element['@cds.extension']
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const _processorFn = ({ row, key }) => {
|
|
11
|
+
if (row[EXT_BACK_PACK]) {
|
|
12
|
+
const extensions = JSON.parse(row[EXT_BACK_PACK])
|
|
13
|
+
Object.keys(extensions).forEach(field => {
|
|
14
|
+
row[field] = extensions[field]
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
delete row[EXT_BACK_PACK]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (row[key] === undefined) {
|
|
21
|
+
row[key] = null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function transformExtendedFieldsRESULT(result, req) {
|
|
26
|
+
if (!result || !hasExtendedEntity(req, this.model)) return
|
|
27
|
+
|
|
28
|
+
const template = getTemplate('transform-result', this, getTargetRead(req), {
|
|
29
|
+
pick: _pick
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (template.elements.size > 0) {
|
|
33
|
+
const result_ = Array.isArray(result) ? result : [result]
|
|
34
|
+
for (const row of result_) {
|
|
35
|
+
const args = { processFn: _processorFn, row, template }
|
|
36
|
+
templateProcessor(args)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
transformExtendedFieldsRESULT
|
|
43
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { EXT_BACK_PACK, getTargetWrite, isExtendedEntity } = require('../utils')
|
|
2
|
+
|
|
3
|
+
const getTemplate = require('../../../common/utils/template')
|
|
4
|
+
const templateProcessor = require('../../../common/utils/templateProcessor')
|
|
5
|
+
|
|
6
|
+
const _pick = element => {
|
|
7
|
+
return element['@cds.extension']
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const _processorFn = ({ row, key }) => {
|
|
11
|
+
if (row[key] === undefined) return
|
|
12
|
+
|
|
13
|
+
if (!row[EXT_BACK_PACK]) {
|
|
14
|
+
row[EXT_BACK_PACK] = '{}'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const json = JSON.parse(row[EXT_BACK_PACK])
|
|
18
|
+
json[key] = row[key]
|
|
19
|
+
row[EXT_BACK_PACK] = JSON.stringify(json)
|
|
20
|
+
delete row[key]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function transformExtendedFieldsCREATE(req) {
|
|
24
|
+
if (!req.target) return
|
|
25
|
+
|
|
26
|
+
const target = getTargetWrite(req.target, this.model)
|
|
27
|
+
const template = getTemplate('transform-write', this, target, { pick: _pick })
|
|
28
|
+
|
|
29
|
+
if (template && template.elements.size > 0) {
|
|
30
|
+
for (const row of req.query.INSERT.entries) {
|
|
31
|
+
const args = { processFn: _processorFn, row, template }
|
|
32
|
+
templateProcessor(args)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function transformExtendedFieldsUPDATE(req) {
|
|
38
|
+
if (!req.target || !req.query.UPDATE.where) return
|
|
39
|
+
|
|
40
|
+
const target = getTargetWrite(req.target, this.model)
|
|
41
|
+
const template = getTemplate('transform-write', Object.assign(req, { model: this.model }), target, { pick: _pick })
|
|
42
|
+
|
|
43
|
+
if (template && template.elements.size > 0) {
|
|
44
|
+
// In patch case we first should obtain backpack from db.
|
|
45
|
+
// Patch can be only applied to the root.
|
|
46
|
+
if (isExtendedEntity(target.name, this.model)) {
|
|
47
|
+
const current = await SELECT.from(req.query.UPDATE.entity).columns([EXT_BACK_PACK]).where(req.query.UPDATE.where)
|
|
48
|
+
|
|
49
|
+
if (current[0]) {
|
|
50
|
+
req.data[EXT_BACK_PACK] = JSON.stringify(current[0])
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const args = { processFn: _processorFn, row: req.data, template }
|
|
55
|
+
templateProcessor(args)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
transformExtendedFieldsCREATE,
|
|
61
|
+
transformExtendedFieldsUPDATE
|
|
62
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module.exports = async () => {
|
|
2
|
+
const cds = require('../../cds')
|
|
3
|
+
if (!cds.requires.db) return
|
|
4
|
+
|
|
5
|
+
const db = await cds.connect.to({ ...cds.requires.db, model: null, silent: true })
|
|
6
|
+
const rs = await db.read('cds_r.Extensions')
|
|
7
|
+
if (rs.length !== 0) {
|
|
8
|
+
const extensions = []
|
|
9
|
+
rs.forEach(row => extensions.push(...JSON.parse(row.csn).extensions))
|
|
10
|
+
cds.once('loaded', csn => {
|
|
11
|
+
if (cds.model) return // extend cds.model only
|
|
12
|
+
const extended = cds.compile({
|
|
13
|
+
'base.csn': cds.compile.to.json(csn),
|
|
14
|
+
'ext.csn': cds.compile.to.json({ extensions })
|
|
15
|
+
})
|
|
16
|
+
csn.definitions = extended.definitions
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
await db.disconnect()
|
|
20
|
+
|
|
21
|
+
if (cds.db) return // because of tests
|
|
22
|
+
cds.once('served', () => {
|
|
23
|
+
const { transformExtendedFieldsCREATE, transformExtendedFieldsUPDATE } = require('./handler/transformWRITE')
|
|
24
|
+
const { transformExtendedFieldsREAD } = require('./handler/transformREAD')
|
|
25
|
+
const { transformExtendedFieldsRESULT } = require('./handler/transformRESULT')
|
|
26
|
+
cds.db
|
|
27
|
+
.before('CREATE', transformExtendedFieldsCREATE)
|
|
28
|
+
.before('UPDATE', transformExtendedFieldsUPDATE)
|
|
29
|
+
.before('READ', transformExtendedFieldsREAD)
|
|
30
|
+
.after('READ', transformExtendedFieldsRESULT)
|
|
31
|
+
if ('cds_r.ExtensibilityService' in cds.services) return
|
|
32
|
+
const model = require('path').join(__dirname, 'extensibility')
|
|
33
|
+
return cds.serve(model, { silent: true }).to('odata').in(cds.app)
|
|
34
|
+
})
|
|
35
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
|
|
2
|
+
const { ensureUnlocalized } = require('../../fiori/utils/handler')
|
|
3
|
+
|
|
4
|
+
const EXT_BACK_PACK = 'extensions__'
|
|
5
|
+
|
|
6
|
+
const getTargetRead = req => {
|
|
7
|
+
let name = ''
|
|
8
|
+
if (req.query.SELECT.from.join) {
|
|
9
|
+
// join
|
|
10
|
+
name = req.query.SELECT.from.args.find(arg => arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministativeData').ref[0]
|
|
11
|
+
} else if (req.target.name.SET) {
|
|
12
|
+
// union
|
|
13
|
+
name = req.target.name.SET.args[0]._target.name
|
|
14
|
+
} else {
|
|
15
|
+
// simple select
|
|
16
|
+
name = req.target.name
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return { name: ensureUnlocalized(ensureNoDraftsSuffix(name)) }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const getTargetWrite = (target, model) => {
|
|
23
|
+
return model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(target.name))]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isExtendedEntity = (entityName, model) => {
|
|
27
|
+
// REVISIT: Dass alle unsere und auch custom handlers immer die ensureUnlocalized + ensureNoDraftsSuffix schleife drehen müssen, kann nicht sein
|
|
28
|
+
const entity = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))]
|
|
29
|
+
return entity.elements[EXT_BACK_PACK] || Object.values(entity.elements).some(el => el['@cds.extension'])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const _hasExtendedEntityArgs = (args, model) => {
|
|
33
|
+
return args.find(arg => {
|
|
34
|
+
if (arg.ref) {
|
|
35
|
+
return arg.ref[0] !== 'DRAFT.DraftAdministativeData' && isExtendedEntity(arg.ref[0], model)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (arg.join) {
|
|
39
|
+
return _hasExtendedEntityArgs(arg.args, model)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hasExtendedEntity = (req, model) => {
|
|
45
|
+
if (!req.query.SELECT) return false
|
|
46
|
+
|
|
47
|
+
if (req.query.SELECT.from.join) {
|
|
48
|
+
// join
|
|
49
|
+
return _hasExtendedEntityArgs(req.query.SELECT.from.args, model)
|
|
50
|
+
} else if (req.target.name.SET) {
|
|
51
|
+
// union
|
|
52
|
+
return isExtendedEntity(req.target.name.SET.args[0]._target.name, model)
|
|
53
|
+
} else {
|
|
54
|
+
// simple select
|
|
55
|
+
return isExtendedEntity(req.target.name, model)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getExtendedFields = (entityName, model) => {
|
|
60
|
+
const elements = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))].elements
|
|
61
|
+
|
|
62
|
+
return Object.values(elements)
|
|
63
|
+
.filter(element => {
|
|
64
|
+
return element['@cds.extension']
|
|
65
|
+
})
|
|
66
|
+
.map(element => {
|
|
67
|
+
return element.name
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
EXT_BACK_PACK,
|
|
73
|
+
getTargetRead,
|
|
74
|
+
getTargetWrite,
|
|
75
|
+
isExtendedEntity,
|
|
76
|
+
hasExtendedEntity,
|
|
77
|
+
getExtendedFields
|
|
78
|
+
}
|
|
@@ -231,11 +231,12 @@ const addColumnAlias = (columns, alias) => {
|
|
|
231
231
|
const getCompositionTargets = (entity, srv) => {
|
|
232
232
|
if (!entity.own('_deepCompositionTargets')) {
|
|
233
233
|
const _deepCompositionTargets = []
|
|
234
|
-
getTemplate(
|
|
234
|
+
getTemplate(undefined, srv, entity, {
|
|
235
235
|
pick: element => {
|
|
236
236
|
if (element.isAssociation && !element._isAssociationStrict && srv.model.definitions[element.target].drafts)
|
|
237
237
|
_deepCompositionTargets.push(element.target)
|
|
238
|
-
}
|
|
238
|
+
},
|
|
239
|
+
ignore: element => !element.isAssociation || element._isAssociationStrict
|
|
239
240
|
})
|
|
240
241
|
entity.set('_deepCompositionTargets', new Set(_deepCompositionTargets))
|
|
241
242
|
}
|
|
@@ -268,16 +269,6 @@ const getKeyProperty = keys => {
|
|
|
268
269
|
})
|
|
269
270
|
}
|
|
270
271
|
|
|
271
|
-
const hasKeyInWhere = (where, target) => {
|
|
272
|
-
if (!where) {
|
|
273
|
-
return false
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const key = getKeyProperty(target.keys)
|
|
277
|
-
|
|
278
|
-
return where.some(element => (element.ref ? key === element.ref[element.ref.length - 1] : false))
|
|
279
|
-
}
|
|
280
|
-
|
|
281
272
|
const filterKeys = keys => {
|
|
282
273
|
return Object.keys(keys).filter(key => {
|
|
283
274
|
return key !== 'IsActiveEntity' && !keys[key]._isAssociationStrict
|
|
@@ -301,7 +292,6 @@ module.exports = {
|
|
|
301
292
|
adaptStreamCQN,
|
|
302
293
|
replaceRefWithDraft,
|
|
303
294
|
getKeyProperty,
|
|
304
|
-
hasKeyInWhere,
|
|
305
295
|
filterKeys,
|
|
306
296
|
getDeleteDraftAdminCqn,
|
|
307
297
|
getCompositionTargets
|
|
@@ -55,6 +55,9 @@ const _removeIsActiveEntityCondition = where => {
|
|
|
55
55
|
i = i + 4
|
|
56
56
|
} else if (where[i] === 'and' && where[i + 1] === '(' && _isActiveEntity(where[i + 2])) {
|
|
57
57
|
i = i + 6
|
|
58
|
+
} else if (where[i].xpr) {
|
|
59
|
+
newWhere.push({ xpr: _removeIsActiveEntityCondition(where[i].xpr) })
|
|
60
|
+
i++
|
|
58
61
|
} else {
|
|
59
62
|
newWhere.push(where[i])
|
|
60
63
|
i++
|
|
@@ -132,7 +135,7 @@ const readAndDeleteKeywords = (keywords, whereCondition, toDelete = true) => {
|
|
|
132
135
|
const removeIsActiveEntityRecursively = where => {
|
|
133
136
|
for (const entry of where) {
|
|
134
137
|
if (entry.SELECT && entry.SELECT.where && entry.SELECT.from.ref && !entry.SELECT.from.ref[0].endsWith('_drafts')) {
|
|
135
|
-
entry.SELECT.where =
|
|
138
|
+
entry.SELECT.where = removeIsActiveEntityRecursively(entry.SELECT.where)
|
|
136
139
|
|
|
137
140
|
if (entry.SELECT.where.length === 0) {
|
|
138
141
|
delete entry.SELECT.where
|
|
@@ -144,6 +147,8 @@ const removeIsActiveEntityRecursively = where => {
|
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
const isActiveEntityRequested = where => {
|
|
150
|
+
if (!where) return true
|
|
151
|
+
|
|
147
152
|
let i = 0
|
|
148
153
|
|
|
149
154
|
while (where[i]) {
|
|
@@ -9,19 +9,20 @@ const _require = require('../common/utils/require')
|
|
|
9
9
|
let im
|
|
10
10
|
|
|
11
11
|
function multiTenantInstanceManager(db = cds.env.requires.db) {
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
const credentials = db.credentials
|
|
13
|
+
if (
|
|
14
|
+
!credentials ||
|
|
15
|
+
typeof credentials !== 'object' ||
|
|
16
|
+
!(credentials.get_managed_instance_url || credentials.sm_url)
|
|
17
|
+
) {
|
|
18
|
+
throw Object.assign(new Error('No or malformed db credentials'), { credentials: credentials })
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
// new instance manager
|
|
21
22
|
return new Promise((resolve, reject) => {
|
|
22
23
|
// REVISIT: better cache settings? current copied from old cds-hana...
|
|
23
24
|
// note: may need to be low for mtx tests -> configurable?
|
|
24
|
-
const opts = Object.assign(
|
|
25
|
+
const opts = Object.assign(credentials, {
|
|
25
26
|
cache_max_items: 1,
|
|
26
27
|
cache_item_expire_seconds: 1
|
|
27
28
|
})
|
|
@@ -37,16 +38,16 @@ function multiTenantInstanceManager(db = cds.env.requires.db) {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
function singleTenantInstanceManager(db = cds.env.requires.db) {
|
|
40
|
-
const
|
|
41
|
+
const credentials = db.credentials
|
|
41
42
|
|
|
42
|
-
if (!
|
|
43
|
-
throw Object.assign(new Error('No or malformed
|
|
43
|
+
if (!credentials || typeof credentials !== 'object' || !credentials.host) {
|
|
44
|
+
throw Object.assign(new Error('No or malformed db credentials'), { credentials: credentials })
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
// mock instance manager
|
|
47
48
|
return {
|
|
48
49
|
get: (_, cb) => {
|
|
49
|
-
cb(null, { credentials:
|
|
50
|
+
cb(null, { credentials: credentials })
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
}
|