@sap/cds-compiler 2.11.2 → 2.13.6
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 +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { setProp } = require('../../base/model');
|
|
4
|
+
const { setAnnotationIfNotDefined, makeClientCompatible } = require('./utils');
|
|
5
|
+
const {
|
|
6
|
+
forEachDefinition,
|
|
7
|
+
getUtils,
|
|
8
|
+
applyTransformations,
|
|
9
|
+
implicitAs,
|
|
10
|
+
isBuiltinType,
|
|
11
|
+
} = require('../../model/csnUtils');
|
|
12
|
+
const {
|
|
13
|
+
forEachValue, forEach,
|
|
14
|
+
} = require('../../utils/objectUtils');
|
|
15
|
+
const { setCoreComputedOnViews } = require('./coreComputed');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Loop through a universal CSN and enrich it with the properties/annotations
|
|
19
|
+
* from the source definition - modifies the input model in-place
|
|
20
|
+
*
|
|
21
|
+
* @param {CSN.Model} csn
|
|
22
|
+
* @param {CSN.Options} options
|
|
23
|
+
*/
|
|
24
|
+
module.exports = (csn, options) => {
|
|
25
|
+
const {
|
|
26
|
+
initDefinition, getOrigin, getQueryPrimarySource, artifactRef, getColumn,
|
|
27
|
+
} = getUtils(csn, 'init-all');
|
|
28
|
+
// Properties on definition level that we treat specially.
|
|
29
|
+
const definitionPropagationRules = {
|
|
30
|
+
'@cds.autoexpose': onlyViaArtifact,
|
|
31
|
+
'@fiori.draft.enabled': onlyViaArtifact,
|
|
32
|
+
'@': (prop, target, source) => {
|
|
33
|
+
if (source[prop] !== null)
|
|
34
|
+
target[prop] = source[prop];
|
|
35
|
+
},
|
|
36
|
+
// Example: `type E : F;` does not have `elements`, but they are required for e.g. OData.
|
|
37
|
+
elements: onlyTypeDef,
|
|
38
|
+
'@cds.persistence.exists': skip,
|
|
39
|
+
'@cds.persistence.table': skip,
|
|
40
|
+
'@cds.persistence.calcview': skip,
|
|
41
|
+
'@cds.persistence.udf': skip,
|
|
42
|
+
'@cds.persistence.skip': notWithPersistenceTable,
|
|
43
|
+
'@sql.append': skip,
|
|
44
|
+
'@sql.prepend': skip,
|
|
45
|
+
'@sql.replace': skip,
|
|
46
|
+
'@Analytics.hidden': skip,
|
|
47
|
+
'@Analytics.visible': skip,
|
|
48
|
+
'@cds.autoexposed': skip,
|
|
49
|
+
'@cds.redirection.target': skip,
|
|
50
|
+
type: always,
|
|
51
|
+
doc: always,
|
|
52
|
+
length: always,
|
|
53
|
+
precision: always,
|
|
54
|
+
scale: always,
|
|
55
|
+
srid: always,
|
|
56
|
+
localized: always,
|
|
57
|
+
target: always,
|
|
58
|
+
targetAspect: always,
|
|
59
|
+
cardinality: always,
|
|
60
|
+
enum: always,
|
|
61
|
+
items: always,
|
|
62
|
+
params: skip, // TODO: (comment is from propagator.js) actually only with parent action
|
|
63
|
+
returns: always,
|
|
64
|
+
notNull: always,
|
|
65
|
+
keys: always,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Properties on member level that we treat specially
|
|
69
|
+
const memberPropagationRules = {
|
|
70
|
+
key: skip,
|
|
71
|
+
enum: notWithTypeOrigin,
|
|
72
|
+
masked: skip,
|
|
73
|
+
virtual: notWithTypeRef,
|
|
74
|
+
items: specialItemsRules,
|
|
75
|
+
elements: (prop, target, source) => {
|
|
76
|
+
if (source.kind === 'type')
|
|
77
|
+
return;
|
|
78
|
+
if (!target.type || target.type && !target.type.ref && hasAnnotationOnSubelement(source.elements)) {
|
|
79
|
+
let needsInitialization = !target[prop];
|
|
80
|
+
if (needsInitialization)
|
|
81
|
+
target[prop] = Object.create(null);
|
|
82
|
+
// Propagate elements thing by thing, applying the appropriate rules
|
|
83
|
+
// to not propagate key in subelements for example
|
|
84
|
+
forEach(source[prop], (name) => {
|
|
85
|
+
if (!target[prop][name]) {
|
|
86
|
+
target[prop][name] = {};
|
|
87
|
+
needsInitialization = true;
|
|
88
|
+
}
|
|
89
|
+
copyProperties(source[prop][name], target[prop][name], getMemberPropagationRuleFor);
|
|
90
|
+
});
|
|
91
|
+
if (needsInitialization) // make it safe to call getOrigin
|
|
92
|
+
initDefinition(target);
|
|
93
|
+
}
|
|
94
|
+
}, // overwrite from defProps
|
|
95
|
+
kind: skip,
|
|
96
|
+
val: always,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
generate();
|
|
100
|
+
|
|
101
|
+
propagateOnMemberLevel();
|
|
102
|
+
|
|
103
|
+
// In this first loop through the model, missing properties in universal CSN
|
|
104
|
+
// are propagated so the CSN can become client one
|
|
105
|
+
forEachDefinition(csn, propagateOnArtifactLevel);
|
|
106
|
+
|
|
107
|
+
// The $origin properties need to be removed separately
|
|
108
|
+
// as the values are used in csnRef::getOrigin that is used during
|
|
109
|
+
// the propagation above.
|
|
110
|
+
// Currently testMode-only for comparison against client CSN.
|
|
111
|
+
if (options.testMode)
|
|
112
|
+
makeClientCompatible(csn);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Before we can start propagating stuff along $origin chains,
|
|
116
|
+
* we have to perform some pre-processing on the csn:
|
|
117
|
+
* - @Core.Computed annotation is not set in the Universal CSN and must be calculated
|
|
118
|
+
* - Annotations on built-in types in the `csn.extensions` must be applied
|
|
119
|
+
* - Annotations for localized and auto-exposed are not set in Universal CSN, the compiler
|
|
120
|
+
* helps us and attaches a `$generated` property which indicates a compiler generated
|
|
121
|
+
* entity for which we must attach the annotations in this pre-processing step
|
|
122
|
+
* - setting properties coming from $origin object (anonymous prototype)
|
|
123
|
+
*/
|
|
124
|
+
function generate() {
|
|
125
|
+
/**
|
|
126
|
+
* `@Core.Computed' must be calculated manually as this annotation
|
|
127
|
+
* is not set in the universal csn flavor.
|
|
128
|
+
*/
|
|
129
|
+
setCoreComputedOnViews( csn );
|
|
130
|
+
/**
|
|
131
|
+
* Construct an extensions object which maps a built-in type to it's annotations
|
|
132
|
+
*/
|
|
133
|
+
const extensions = Object.create( null );
|
|
134
|
+
if (csn.extensions) {
|
|
135
|
+
for ( const extension of csn.extensions ) {
|
|
136
|
+
const annotations = Object.create( null );
|
|
137
|
+
forEach( extension, ( key, val ) => {
|
|
138
|
+
if (!key.startsWith( '@' ))
|
|
139
|
+
return;
|
|
140
|
+
annotations[key] = val;
|
|
141
|
+
} );
|
|
142
|
+
extensions[extension.annotate] = annotations;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
applyTransformations(csn, {
|
|
146
|
+
virtual: ( parent, prop, virtual, path) => {
|
|
147
|
+
// if we are not in columns, we must add `@Core.Computed`,
|
|
148
|
+
// even for `virtual: null`, this is e.g. the case for parameters.
|
|
149
|
+
// This strange behavior of `@Core.Computed` has historic reasons (due to annotation propagation).
|
|
150
|
+
if (path[path.length - 2] !== 'columns')
|
|
151
|
+
setAnnotationIfNotDefined( parent, '@Core.Computed', true );
|
|
152
|
+
},
|
|
153
|
+
target: (parent, prop, target) => {
|
|
154
|
+
if (!(parent.type && parent.type === 'cds.Composition'))
|
|
155
|
+
return;
|
|
156
|
+
if (typeof target === 'string') {
|
|
157
|
+
const artifact = artifactRef(target);
|
|
158
|
+
if (artifact.kind === 'aspect') {
|
|
159
|
+
parent.targetAspect = target;
|
|
160
|
+
if (parent.targetAspect === target)
|
|
161
|
+
delete parent.target;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setTargetAspectIfRequired(parent);
|
|
167
|
+
},
|
|
168
|
+
type: ( parent, prop, type, path, grandParent, parentProp ) => {
|
|
169
|
+
if (parentProp === 'returns') // annos are not propagated to `returns` and `items`
|
|
170
|
+
return;
|
|
171
|
+
const annotationsForBuiltinType = extensions[type];
|
|
172
|
+
Object.assign( parent, annotationsForBuiltinType );
|
|
173
|
+
},
|
|
174
|
+
$generated: ( parent, prop, $generated, path ) => {
|
|
175
|
+
const rootArtifact = csn.definitions[path[1].slice(0, -6)];
|
|
176
|
+
if ( $generated === 'exposed' ) {
|
|
177
|
+
setAnnotationIfNotDefined( parent, '@cds.autoexposed', true );
|
|
178
|
+
const origin = getOrigin( parent );
|
|
179
|
+
if ( origin.$generated === 'localized' && origin.kind === 'entity' ) // generated .texts entity that was then autoexposed
|
|
180
|
+
attachAnnosForTextsTable( parent, rootArtifact );
|
|
181
|
+
}
|
|
182
|
+
else if ( $generated === 'localized' && parent.kind === 'entity' ) { // generated .texts entity
|
|
183
|
+
attachAnnosForTextsTable( parent, rootArtifact );
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
$origin: ( parent, prop, $origin ) => {
|
|
187
|
+
if (typeof $origin === 'string' || Array.isArray($origin))
|
|
188
|
+
return;
|
|
189
|
+
// if $origin is an object, we have to check
|
|
190
|
+
// if there are properties in this object
|
|
191
|
+
// which are not directly set on the `parent`
|
|
192
|
+
// and if not -> assign them
|
|
193
|
+
forEach($origin, (key, val) => {
|
|
194
|
+
if (key !== '$origin' && !parent[key])
|
|
195
|
+
parent[key] = val;
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get the thing we are supposed to use for setting targetAspect
|
|
203
|
+
*
|
|
204
|
+
* @param {object} root
|
|
205
|
+
* @returns {object|string|null}
|
|
206
|
+
*/
|
|
207
|
+
function getTargetAspectBase(root) {
|
|
208
|
+
if (root.target && root.target.elements) {
|
|
209
|
+
return root.target;
|
|
210
|
+
}
|
|
211
|
+
else if (root.$origin) {
|
|
212
|
+
if (Array.isArray(root.$origin) && root.$origin[root.$origin.length - 1].target)
|
|
213
|
+
return getOrigin(root);
|
|
214
|
+
else if (root.$origin.target)
|
|
215
|
+
return root.$origin.target;
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Set targetAspect on root and on any subelements of targetAspect if required.
|
|
222
|
+
*
|
|
223
|
+
* We can detect that via
|
|
224
|
+
* - root.$origin either directly or as part of an array has target
|
|
225
|
+
* - root.target has .elements
|
|
226
|
+
*
|
|
227
|
+
* @see getTargetAspectBase for details on how we find our "start"
|
|
228
|
+
* @param {object} root
|
|
229
|
+
*/
|
|
230
|
+
function setTargetAspectIfRequired(root) {
|
|
231
|
+
if (root.$origin || root.target && root.target.elements) {
|
|
232
|
+
const base = getTargetAspectBase(root);
|
|
233
|
+
if (base && (base.elements || typeof base === 'string' && csn.definitions[base].kind === 'aspect')) {
|
|
234
|
+
root.targetAspect = base;
|
|
235
|
+
if (root.target && root.target.elements)
|
|
236
|
+
delete root.target;
|
|
237
|
+
if (base.elements) {
|
|
238
|
+
// anonymous aspect - we need to set targetAspect on subnodes
|
|
239
|
+
// things are simpler in here, no need to check the origin
|
|
240
|
+
const stack = [ root.targetAspect.elements ];
|
|
241
|
+
while (stack.length > 0) {
|
|
242
|
+
const elements = stack.pop();
|
|
243
|
+
forEach(elements, (name, element) => {
|
|
244
|
+
if (element.target) {
|
|
245
|
+
if (typeof element.target === 'string') {
|
|
246
|
+
const art = artifactRef(element.target);
|
|
247
|
+
if (art.kind === 'aspect') {
|
|
248
|
+
element.targetAspect = element.target;
|
|
249
|
+
delete element.target;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (element.target.elements) {
|
|
253
|
+
element.targetAspect = element.target;
|
|
254
|
+
delete element.target;
|
|
255
|
+
stack.push(element.targetAspect.elements);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Walk over properties on member level and propagate all relevant properties
|
|
268
|
+
* from it's prototype.
|
|
269
|
+
*/
|
|
270
|
+
function propagateOnMemberLevel() {
|
|
271
|
+
applyTransformations(csn, {
|
|
272
|
+
actions: (parent, prop, actions) => {
|
|
273
|
+
forEachValue(actions, (action) => {
|
|
274
|
+
if (!action.kind) // bound actions might not have a kind, only functions
|
|
275
|
+
action.kind = 'action';
|
|
276
|
+
propagateMemberPropsFromOrigin(action);
|
|
277
|
+
});
|
|
278
|
+
},
|
|
279
|
+
params: (parent, prop, params) => {
|
|
280
|
+
forEachValue(params, (param) => {
|
|
281
|
+
propagateMemberPropsFromOrigin(param, {
|
|
282
|
+
items: true, elements: true, enum: true, virtual: true,
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
returns: (parent, prop, returns) => {
|
|
287
|
+
propagateMemberPropsFromOrigin(returns, { items: true, '@': true, elements: true });
|
|
288
|
+
if (returns.target)
|
|
289
|
+
calculateForeignKeys(returns);
|
|
290
|
+
},
|
|
291
|
+
items: (parent, prop, items) => propagateMemberPropsFromOrigin(items, { '@': true, doc: true }),
|
|
292
|
+
elements: (parent, prop, elements) => {
|
|
293
|
+
forEachValue(elements, (e) => {
|
|
294
|
+
// within query elements we have to propagate the `enum` prop
|
|
295
|
+
const skipEnum = !parent.query && !parent.projection ? { enum: true } : null;
|
|
296
|
+
propagateMemberPropsFromOrigin(e, skipEnum);
|
|
297
|
+
});
|
|
298
|
+
},
|
|
299
|
+
enum: (parent, prop, enumProp) => forEachValue( enumProp, e => propagateMemberPropsFromOrigin(e) ),
|
|
300
|
+
target: (parent) => {
|
|
301
|
+
if (parent.type && typeof parent.target === 'string' && !parent.keys && !parent.on)
|
|
302
|
+
calculateForeignKeys(parent);
|
|
303
|
+
},
|
|
304
|
+
SELECT: (parent, prop, SELECT, path, artifact) => {
|
|
305
|
+
if ( SELECT.mixin && artifact.query )
|
|
306
|
+
propagateToPublishedMixin(artifact.query, artifact);
|
|
307
|
+
},
|
|
308
|
+
}, []);
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Propagate properties to the `member` from it's prototype.
|
|
312
|
+
* For that to work we calculate the prototype chain of the member and
|
|
313
|
+
* propagate properties along this prototype chain until we reach the `member`
|
|
314
|
+
* passed to this function.
|
|
315
|
+
*
|
|
316
|
+
* @param {CSN.Element} member
|
|
317
|
+
* @param {object} [except=null] List of properties which should not be propagated along the origin chain
|
|
318
|
+
* of the `member`
|
|
319
|
+
* @param {object} [force=null] Overwrite any member propagation rules or any except and always propagate the corresponding keys
|
|
320
|
+
*/
|
|
321
|
+
function propagateMemberPropsFromOrigin(member, except = null, force = null) {
|
|
322
|
+
const memberChain = getOriginChain(member);
|
|
323
|
+
const virtualOrigin = Object.create(null); // To collect stuff across the origin chain - currently only for .items via type-of
|
|
324
|
+
|
|
325
|
+
if (memberChain.length) {
|
|
326
|
+
for (let i = memberChain.length - 1; i >= 0; i--) { // start from the bottom and propagate member props upwards
|
|
327
|
+
const { origin, target } = memberChain[i];
|
|
328
|
+
if (target._status !== 'propagated' && !skipMemberPropagation(origin)) {
|
|
329
|
+
copyProperties(origin, target, getMemberPropagationRuleFor, except, force);
|
|
330
|
+
|
|
331
|
+
// For a `type of` with .items, we want to take stuff from types (which we skip for "normal" propagation, see specialItemsRules).
|
|
332
|
+
// So for a type of we also propagate stuff from the virtual origin (which we don't give a "kind", therefore skipping that part of the check)
|
|
333
|
+
if (target.type && target.type.ref)
|
|
334
|
+
copyProperties(virtualOrigin, target, getMemberPropagationRuleFor, except);
|
|
335
|
+
|
|
336
|
+
if (!target.kind)
|
|
337
|
+
setProp(target, '_status', 'propagated');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (i > 0) // function needs to be adapted if we need more info
|
|
341
|
+
copyProperties(origin, virtualOrigin, key => (key === 'items' ? always : skip));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// If $origin is an object, it is the anonymous prototype of `member`
|
|
345
|
+
// e.g. if outer query element has an element of a subquery as it's prototype
|
|
346
|
+
// annotations are part of the $origin object and must be copied over to the `member`
|
|
347
|
+
// --> annotations on anonymous prototypes have precedence over those coming from base type
|
|
348
|
+
if (member.$origin && Object.keys(member.$origin).length > 0)
|
|
349
|
+
copyProperties(member.$origin, member, getMemberPropagationRuleFor, except);
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @param {CSN.Element} origin
|
|
353
|
+
* @returns {boolean} whether props from the members origin should be propagated
|
|
354
|
+
* @todo check if still necessary
|
|
355
|
+
*/
|
|
356
|
+
function skipMemberPropagation(origin) {
|
|
357
|
+
// For empty members (`{}`), the origin was set in a previous call to `getOrigin(definition)`.
|
|
358
|
+
return !origin;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Identify the sources of the passed object and propagate the relevant
|
|
365
|
+
* properties/annotations along it's $origin chain.
|
|
366
|
+
*
|
|
367
|
+
* @param {Object} art Target object for propagation
|
|
368
|
+
*/
|
|
369
|
+
function propagateOnArtifactLevel(art) {
|
|
370
|
+
// check if art was already processed by the status flag
|
|
371
|
+
// TODO: clean up later on, together with validator clean up probably or
|
|
372
|
+
// when this module is meant to be used standalone -> use internal cache to store already processed definitions?
|
|
373
|
+
if (art._status === 'propagated')
|
|
374
|
+
return;
|
|
375
|
+
|
|
376
|
+
const chain = getOriginChain(art);
|
|
377
|
+
|
|
378
|
+
if (chain.length)
|
|
379
|
+
chain.reverse().forEach(chainLink => definitionPropagation(chainLink.target, chainLink.origin, chain[0].origin));
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* @param {CSN.Element} targetDefinition
|
|
383
|
+
* @param {CSN.Element} targetsOrigin
|
|
384
|
+
* @param {CSN.Element} rootOrigin
|
|
385
|
+
*/
|
|
386
|
+
function definitionPropagation(targetDefinition, targetsOrigin, rootOrigin) {
|
|
387
|
+
// if target was already processed -> continue
|
|
388
|
+
if (targetDefinition._status === 'propagated')
|
|
389
|
+
return;
|
|
390
|
+
// propagate relevant definition level properties
|
|
391
|
+
// we check for kind as in the future the function should be
|
|
392
|
+
// generic and work for parts of CSN
|
|
393
|
+
if (targetDefinition.kind) {
|
|
394
|
+
propagateDefProps(targetDefinition, targetsOrigin, rootOrigin);
|
|
395
|
+
if (targetDefinition.target && !targetDefinition.keys && !targetDefinition.on) // Association/Composition type
|
|
396
|
+
calculateForeignKeys(targetDefinition);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
setProp(targetDefinition, '_status', 'propagated');
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Propagate from 'source' to 'target' the relevant properties
|
|
403
|
+
* for CSN definitions. For type definitions, also walk up the origin-chain if needed to get .elements
|
|
404
|
+
*
|
|
405
|
+
* @param {CSN.Definition} definition
|
|
406
|
+
* @param {CSN.Definition|CSN.Element} source
|
|
407
|
+
* @param {CSN.Definition|CSN.Element} root
|
|
408
|
+
*/
|
|
409
|
+
function propagateDefProps(definition, source, root) {
|
|
410
|
+
copyProperties(source, definition, getDefinitionPropagationRuleFor);
|
|
411
|
+
// If $origin is an object, it is the anonymous prototype of `definition`
|
|
412
|
+
// e.g. for structure includes annotations are part of the $origin object and must be copied over to the `definition`
|
|
413
|
+
// --> annotations on anonymous prototypes have precedence over those coming from base type
|
|
414
|
+
if (definition.$origin && Object.keys(definition.$origin).length > 0)
|
|
415
|
+
copyProperties(definition.$origin, definition, getDefinitionPropagationRuleFor);
|
|
416
|
+
|
|
417
|
+
// We need to propagate .elements to type artifacts - but our direct origin might not have .elements,
|
|
418
|
+
// because they are not propagated to members. We check if our root had elements (so we know that we should have some aswell)
|
|
419
|
+
// and then walk the origin-chain until we find the first .elements
|
|
420
|
+
if (definition.kind === 'type' && root.elements && !definition.elements) {
|
|
421
|
+
const firstOriginWithElements = getFirstOriginWithElements(source);
|
|
422
|
+
definition.elements = firstOriginWithElements.elements;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Walk the origin-chain until we find the first origin with .elements and return it
|
|
429
|
+
*
|
|
430
|
+
* @param {CSN.Artifact|CSN.Element} start
|
|
431
|
+
* @returns {CSN.Artifact|CSN.Element|null} Null if no origin with .elements was found
|
|
432
|
+
*/
|
|
433
|
+
function getFirstOriginWithElements(start) {
|
|
434
|
+
let target = start;
|
|
435
|
+
let firstOriginWithElements;
|
|
436
|
+
do {
|
|
437
|
+
firstOriginWithElements = getOrigin(target);
|
|
438
|
+
if (firstOriginWithElements && firstOriginWithElements.elements)
|
|
439
|
+
return firstOriginWithElements;
|
|
440
|
+
|
|
441
|
+
target = firstOriginWithElements;
|
|
442
|
+
} while (firstOriginWithElements);
|
|
443
|
+
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Walk the origin-chain until we find the first origin with .elements and return it
|
|
450
|
+
*
|
|
451
|
+
* collect chain of origins and propagate
|
|
452
|
+
* from the farthest to the nearest one to the target
|
|
453
|
+
*
|
|
454
|
+
* @param {CSN.Artifact|CSN.Element} start
|
|
455
|
+
* @returns {object[]} chain of origin - target
|
|
456
|
+
* @todo Optimize: Only return the chain until the first propagated thing?
|
|
457
|
+
*/
|
|
458
|
+
function getOriginChain(start) {
|
|
459
|
+
const chain = [];
|
|
460
|
+
let target = start;
|
|
461
|
+
let origin;
|
|
462
|
+
do {
|
|
463
|
+
origin = getOrigin(target);
|
|
464
|
+
if (origin) {
|
|
465
|
+
chain.push({ target, origin });
|
|
466
|
+
target = origin;
|
|
467
|
+
}
|
|
468
|
+
} while (origin);
|
|
469
|
+
|
|
470
|
+
return chain;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Propagate type properties like cardinality from the mixin definition to the published mixin element.
|
|
475
|
+
* To do that, we scan the elements and mark all associations, we then build a mapping from element name -> column
|
|
476
|
+
* and use that to check if we have a matching mixin element.
|
|
477
|
+
*
|
|
478
|
+
* If we find a match, we propagate the properties.
|
|
479
|
+
*
|
|
480
|
+
* @param {CSN.Query} query
|
|
481
|
+
* @param {CSN.Artifact} artifact
|
|
482
|
+
*/
|
|
483
|
+
function propagateToPublishedMixin(query, artifact) {
|
|
484
|
+
const elements = query.SELECT.elements || artifact.elements;
|
|
485
|
+
forEachValue(elements, (element) => {
|
|
486
|
+
if (element.target) {
|
|
487
|
+
const column = getColumn(element);
|
|
488
|
+
if (column) {
|
|
489
|
+
const mixin = query.SELECT.mixin[implicitAs(column.ref)] || {};
|
|
490
|
+
copyProperties(mixin, element, getMemberPropagationRuleFor);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* @param {CSN.Element} member
|
|
498
|
+
*/
|
|
499
|
+
function calculateForeignKeys(member) {
|
|
500
|
+
// managed assocs in universal CSN have no longer keys
|
|
501
|
+
// if they are not explicitly defined - PR#8064
|
|
502
|
+
const target = artifactRef(member.target);
|
|
503
|
+
const targetKeys = Object.keys(target.elements).filter(key => target.elements[key].key);
|
|
504
|
+
member.keys = targetKeys.map(
|
|
505
|
+
keyName => ({ ref: [ keyName ] })
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* `@cds.autoexposed` for example, is propagated only if at definition level and only if
|
|
511
|
+
* the primary source (left-most) does not follow an association.
|
|
512
|
+
*
|
|
513
|
+
* @param {string} prop
|
|
514
|
+
* @param {CSN.Definition} target
|
|
515
|
+
* @param {CSN.Definition} source
|
|
516
|
+
*/
|
|
517
|
+
function onlyViaArtifact(prop, target, source) {
|
|
518
|
+
if (!target.kind)
|
|
519
|
+
return;
|
|
520
|
+
const primarySourceRef = getQueryPrimarySource(target.query || target.projection);
|
|
521
|
+
const artRef = primarySourceRef ? artifactRef(primarySourceRef) : source;
|
|
522
|
+
if (!artRef.target)
|
|
523
|
+
target[prop] = source[prop];
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Get the custom rule from "memberProps" (or default to "defProps") for the property copying
|
|
528
|
+
*
|
|
529
|
+
* @param {string} key identifier of the csn prop we are looking for
|
|
530
|
+
* @returns {Function} which can be used to apply custom propagation rules for certain props
|
|
531
|
+
*/
|
|
532
|
+
function getMemberPropagationRuleFor(key) {
|
|
533
|
+
return memberPropagationRules[key] || memberPropagationRules[key.charAt(0)] || getDefinitionPropagationRuleFor(key);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Get the custom rule from "defProps" for the property copying
|
|
538
|
+
*
|
|
539
|
+
* @param {string} key identifier of the csn prop we are looking for
|
|
540
|
+
* @returns {Function} which can be used to apply custom propagation rules for certain props
|
|
541
|
+
*/
|
|
542
|
+
function getDefinitionPropagationRuleFor(key) {
|
|
543
|
+
return definitionPropagationRules[key] || definitionPropagationRules[key.charAt(0)];
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Set the annotations for a localized `rootArtifact` on it's `parent`.
|
|
548
|
+
*
|
|
549
|
+
* @param {CSN.Artifact} parent
|
|
550
|
+
* @param {CSN.Artifact} rootArtifact The artifact that had the localized
|
|
551
|
+
*/
|
|
552
|
+
function attachAnnosForTextsTable(parent, rootArtifact) {
|
|
553
|
+
const isFioriDraftEnabled = rootArtifact && (rootArtifact['@fiori.draft.enabled'] === true || getOriginChain(rootArtifact).some(({ origin }) => origin['@fiori.draft.enabled'] === true));
|
|
554
|
+
if (isFioriDraftEnabled) {
|
|
555
|
+
setAnnotationIfNotDefined(parent, '@assert.unique.locale', [ { '=': 'locale' } ]);
|
|
556
|
+
forEach(rootArtifact.elements, (name, element) => {
|
|
557
|
+
if (element.key)
|
|
558
|
+
parent['@assert.unique.locale'].push({ '=': name });
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
setAnnotationIfNotDefined(parent, '@odata.draft.enabled', false);
|
|
563
|
+
|
|
564
|
+
// key elements (except for locale) must get "@odata.containment.ignore": true
|
|
565
|
+
forEach(parent.elements, (name, element) => {
|
|
566
|
+
if (name !== 'locale' && element.key)
|
|
567
|
+
setAnnotationIfNotDefined(element, '@odata.containment.ignore', true);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Simply copy the properties of "from" to "to" - but don't overwrite existing properties.
|
|
575
|
+
*
|
|
576
|
+
* Apply the custom rules from "memberProps" and "defProps" for the copying!
|
|
577
|
+
*
|
|
578
|
+
* @param {object} from
|
|
579
|
+
* @param {object} to
|
|
580
|
+
* @param {Function} getCustomRule getter for the `memberProps` or `defProps`
|
|
581
|
+
* which shall be used for retrieving custom rules
|
|
582
|
+
* @param {object} [except=null] array of properties which should not be propagated
|
|
583
|
+
* @param {object} [force=null] Force propagation of the contained keys via rule "always"
|
|
584
|
+
*/
|
|
585
|
+
function copyProperties(from, to, getCustomRule, except = null, force = null) {
|
|
586
|
+
const keys = Object.keys(from);
|
|
587
|
+
// Copy over properties from the origin element to the target.
|
|
588
|
+
for (const key of keys) {
|
|
589
|
+
if (except && !(force && force[key]) && (key.charAt(0) in except || key in except))
|
|
590
|
+
continue;
|
|
591
|
+
if (!(key in to)) {
|
|
592
|
+
const func = force && force[key] ? always : getCustomRule(key);
|
|
593
|
+
if (func)
|
|
594
|
+
func(key, to, from);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Recursively check if some element in the elements has an annotation.
|
|
601
|
+
*
|
|
602
|
+
* @param {object} elements
|
|
603
|
+
* @returns {boolean} whether some element in the elements has an annotation
|
|
604
|
+
*/
|
|
605
|
+
function hasAnnotationOnSubelement(elements) {
|
|
606
|
+
for (const element of Object.values(elements)) {
|
|
607
|
+
if (Object.keys(element).some(key => key.startsWith('@')))
|
|
608
|
+
return true;
|
|
609
|
+
else if (element.elements)
|
|
610
|
+
return hasAnnotationOnSubelement(element.elements);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Does nothing. Is used as a placeholder
|
|
619
|
+
* in our member- and definition propagation
|
|
620
|
+
* rules.
|
|
621
|
+
*/
|
|
622
|
+
function skip() {
|
|
623
|
+
// Do nothing
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Always copy `prop` from `source` to `target`
|
|
628
|
+
*
|
|
629
|
+
* @param {string} prop
|
|
630
|
+
* @param {object} target
|
|
631
|
+
* @param {object} source
|
|
632
|
+
*/
|
|
633
|
+
function always(prop, target, source) {
|
|
634
|
+
const val = source[prop];
|
|
635
|
+
if (Array.isArray(val))
|
|
636
|
+
target[prop] = [ ...val ];
|
|
637
|
+
else
|
|
638
|
+
target[prop] = val;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Execute only if the target definition is a user-defined type.
|
|
643
|
+
*
|
|
644
|
+
* @param {string} prop
|
|
645
|
+
* @param {CSN.Definition} target
|
|
646
|
+
* @param {CSN.Definition} source
|
|
647
|
+
*/
|
|
648
|
+
function onlyTypeDef(prop, target, source) {
|
|
649
|
+
if (target.kind !== 'type')
|
|
650
|
+
return;
|
|
651
|
+
target[prop] = source[prop];
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Copy `prop` from `source` to `target`
|
|
656
|
+
* If the `target` is annotated with `@cds.persistence.table`,
|
|
657
|
+
* this function does nothing.
|
|
658
|
+
*
|
|
659
|
+
* @param {string} prop
|
|
660
|
+
* @param {object} target
|
|
661
|
+
* @param {object} source
|
|
662
|
+
*/
|
|
663
|
+
function notWithPersistenceTable(prop, target, source) {
|
|
664
|
+
const tableAnno = target['@cds.persistence.table'];
|
|
665
|
+
if (tableAnno === undefined || tableAnno === null)
|
|
666
|
+
target[prop] = source[prop];
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Copy `prop` from `source` to `target`
|
|
671
|
+
* If the `source` has `source.kind === 'type'`
|
|
672
|
+
* this function does nothing.
|
|
673
|
+
*
|
|
674
|
+
* @param {string} prop
|
|
675
|
+
* @param {CSN.Element} target
|
|
676
|
+
* @param {CSN.Element} source
|
|
677
|
+
*/
|
|
678
|
+
function notWithTypeOrigin(prop, target, source) {
|
|
679
|
+
if (source.kind !== 'type')
|
|
680
|
+
target[prop] = source[prop];
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Special propagation rules for .items - depending on the exact type of .items and the
|
|
685
|
+
* way it was referenced (type of, direct type, direct many), we need to propagate (or not).
|
|
686
|
+
*
|
|
687
|
+
* We do not propagate it
|
|
688
|
+
* - from a type
|
|
689
|
+
* - from a type ref
|
|
690
|
+
* - from a custom type
|
|
691
|
+
*
|
|
692
|
+
* In a projection/simple view, our target and source will not have a type - we need to copy the .items there regardless of the type stuff.
|
|
693
|
+
*
|
|
694
|
+
* @param {string} prop
|
|
695
|
+
* @param {CSN.Element} target
|
|
696
|
+
* @param {CSN.Element} source
|
|
697
|
+
*/
|
|
698
|
+
function specialItemsRules(prop, target, source) {
|
|
699
|
+
if (source.kind !== 'type' && ((!source.type && !target.type) || !(source[prop].type && source[prop].type.ref || !isBuiltinType(source[prop].type))))
|
|
700
|
+
target[prop] = source[prop];
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Some properties must not be copied over if the type of this member
|
|
705
|
+
* is a reference to another element.
|
|
706
|
+
*
|
|
707
|
+
* @param {string} prop
|
|
708
|
+
* @param {CSN.Element} target
|
|
709
|
+
* @param {CSN.Element} source
|
|
710
|
+
*/
|
|
711
|
+
function notWithTypeRef(prop, target, source) {
|
|
712
|
+
const typeIsTypeRef = Boolean(typeof target.type === 'object' && target.type.ref);
|
|
713
|
+
if (!typeIsTypeRef)
|
|
714
|
+
target[prop] = source[prop];
|
|
715
|
+
}
|