@sap/cds-compiler 4.9.6 → 5.1.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 +92 -0
- package/bin/cds_remove_invalid_whitespace.js +2 -1
- package/bin/cdsc.js +49 -19
- package/bin/cdshi.js +3 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +16 -19
- package/lib/api/options.js +5 -14
- package/lib/api/trace.js +0 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/location.js +4 -1
- package/lib/base/message-registry.js +43 -29
- package/lib/base/messages.js +23 -26
- package/lib/base/meta.js +10 -0
- package/lib/base/model.js +0 -2
- package/lib/base/node-helpers.js +0 -1
- package/lib/base/optionProcessorHelper.js +11 -0
- package/lib/checks/dbFeatureFlags.js +5 -0
- package/lib/checks/enricher.js +1 -5
- package/lib/checks/structuredAnnoExpressions.js +30 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +5 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +18 -2
- package/lib/compiler/checks.js +2 -5
- package/lib/compiler/define.js +8 -8
- package/lib/compiler/extend.js +108 -37
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/index.js +27 -10
- package/lib/compiler/lsp-api.js +501 -2
- package/lib/compiler/populate.js +60 -13
- package/lib/compiler/propagator.js +10 -8
- package/lib/compiler/resolve.js +117 -94
- package/lib/compiler/shared.js +114 -32
- package/lib/compiler/tweak-assocs.js +31 -21
- package/lib/compiler/utils.js +2 -1
- package/lib/compiler/xsn-model.js +4 -0
- package/lib/edm/annotations/genericTranslation.js +69 -35
- package/lib/edm/csn2edm.js +16 -4
- package/lib/edm/edm.js +10 -3
- package/lib/edm/edmAnnoPreprocessor.js +1 -2
- package/lib/edm/edmPreprocessor.js +8 -10
- package/lib/gen/Dictionary.json +66 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4995 -4817
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +4 -7
- package/lib/json/to-csn.js +25 -12
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/errorStrategy.js +0 -1
- package/lib/language/genericAntlrParser.js +35 -12
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/language/textUtils.js +1 -0
- package/lib/main.d.ts +28 -9
- package/lib/main.js +9 -9
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +22 -5
- package/lib/model/csnUtils.js +0 -14
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +13 -11
- package/lib/optionProcessor.js +30 -9
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +44 -14
- package/lib/render/toHdbcds.js +1 -2
- package/lib/render/toSql.js +45 -8
- package/lib/render/utils/common.js +12 -9
- package/lib/render/utils/stringEscapes.js +1 -0
- package/lib/transform/db/applyTransformations.js +13 -8
- package/lib/transform/db/associations.js +62 -54
- package/lib/transform/db/backlinks.js +20 -5
- package/lib/transform/db/expansion.js +1 -6
- package/lib/transform/db/flattening.js +86 -109
- package/lib/transform/db/killAnnotations.js +3 -0
- package/lib/transform/db/processSqlServices.js +63 -0
- package/lib/transform/db/temporal.js +3 -4
- package/lib/transform/db/views.js +0 -1
- package/lib/transform/draft/odata.js +56 -3
- package/lib/transform/effective/annotations.js +3 -2
- package/lib/transform/effective/flattening.js +135 -0
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/effective/types.js +13 -9
- package/lib/transform/forOdata.js +0 -2
- package/lib/transform/forRelationalDB.js +9 -19
- package/lib/transform/localized.js +7 -8
- package/lib/transform/odata/flattening.js +39 -31
- package/lib/transform/odata/typesExposure.js +5 -17
- package/lib/transform/transformUtils.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +0 -1
- package/lib/utils/file.js +87 -8
- package/lib/utils/moduleResolve.js +59 -8
- package/lib/utils/term.js +3 -2
- package/package.json +7 -3
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/type-unexpected-foreign-keys.md +52 -0
- package/share/messages/type-unexpected-on-condition.md +52 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
forEachDefinition, forEachMemberRecursively, findAnnotationExpression, applyTransformationsOnNonDictionary, transformExpression,
|
|
5
|
+
} = require('../../model/csnUtils');
|
|
6
|
+
const { getStructStepsFlattener } = require('../db/flattening');
|
|
7
|
+
const { setProp } = require('../../base/model');
|
|
8
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param csn
|
|
14
|
+
* @param options
|
|
15
|
+
* @param csnUtils
|
|
16
|
+
* @param messageFunctions
|
|
17
|
+
*/
|
|
18
|
+
function flattenRefs( csn, options, csnUtils, messageFunctions ) {
|
|
19
|
+
const cleanup = [];
|
|
20
|
+
forEachDefinition(csn, (artifact) => {
|
|
21
|
+
if (artifact.elements) {
|
|
22
|
+
const stack = [ { prefix: [], elements: artifact.elements } ];
|
|
23
|
+
while (stack.length > 0) {
|
|
24
|
+
const { prefix, elements } = stack.pop();
|
|
25
|
+
forEach(elements, (elementName, element) => {
|
|
26
|
+
if (element.elements)
|
|
27
|
+
stack.push({ prefix: prefix.concat(elementName), elements: element.elements });
|
|
28
|
+
|
|
29
|
+
const absolutifier = absolutifyPaths(prefix, cleanup);
|
|
30
|
+
// Absolutify paths in on-conditions
|
|
31
|
+
if (element.on) {
|
|
32
|
+
transformExpression(element, 'on', {
|
|
33
|
+
ref: absolutifier,
|
|
34
|
+
}, []);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Absolutify paths in annotation expressions
|
|
38
|
+
Object.keys(element)
|
|
39
|
+
.filter(pn => findAnnotationExpression(element, pn))
|
|
40
|
+
.forEach((anno) => {
|
|
41
|
+
applyTransformationsOnNonDictionary(element, anno, {
|
|
42
|
+
xpr: (parent, prop) => {
|
|
43
|
+
transformExpression(parent, prop, {
|
|
44
|
+
ref: absolutifier,
|
|
45
|
+
}, []);
|
|
46
|
+
},
|
|
47
|
+
}, {}, []);
|
|
48
|
+
if (element[anno].ref)
|
|
49
|
+
absolutifier(element[anno], 'ref', element[anno].ref);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
const adaptRefs = [];
|
|
58
|
+
const resolved = new WeakMap();
|
|
59
|
+
const refFlattener = getStructStepsFlattener(csn, options, messageFunctions, resolved, '_', adaptRefs);
|
|
60
|
+
|
|
61
|
+
forEachDefinition(csn, (def, defName) => {
|
|
62
|
+
if (def.kind === 'entity') {
|
|
63
|
+
applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, { processAnnotations: true, skipDict: { actions: 1 } }, [ 'definitions' ]);
|
|
64
|
+
|
|
65
|
+
adaptRefs.forEach(fn => fn());
|
|
66
|
+
adaptRefs.length = 0;
|
|
67
|
+
|
|
68
|
+
// explicit binding parameter of bound action
|
|
69
|
+
if (def.actions) {
|
|
70
|
+
const special$self = !csn?.definitions?.$self && '$self';
|
|
71
|
+
Object.entries(def.actions).forEach(([ an, a ]) => {
|
|
72
|
+
if (a.params) {
|
|
73
|
+
const params = Object.entries(a.params);
|
|
74
|
+
const firstParam = params[0][1];
|
|
75
|
+
const type = firstParam?.items?.type || firstParam?.type;
|
|
76
|
+
if (type === special$self) {
|
|
77
|
+
const bindingParamName = params[0][0];
|
|
78
|
+
const markBindingParam = {
|
|
79
|
+
ref: (parent, prop, xpr) => {
|
|
80
|
+
if ((xpr[0].id || xpr[0]) === bindingParamName)
|
|
81
|
+
setProp(parent, '$bparam', true);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
Object.keys(a)
|
|
86
|
+
.filter(pn => findAnnotationExpression(a, pn))
|
|
87
|
+
.forEach((pn) => {
|
|
88
|
+
transformExpression(a, pn, [ markBindingParam, refFlattener ], [ 'definitions', defName, 'actions', an ]);
|
|
89
|
+
adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
|
|
90
|
+
adaptRefs.length = 0;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
forEachMemberRecursively(a, (member, memberName, prop, path) => {
|
|
95
|
+
Object.keys(member).filter(pn => findAnnotationExpression(member, pn)).forEach((pn) => {
|
|
96
|
+
transformExpression(member, pn, [ markBindingParam, refFlattener ], path);
|
|
97
|
+
adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
|
|
98
|
+
adaptRefs.length = 0;
|
|
99
|
+
});
|
|
100
|
+
}, [ 'definitions', defName, 'actions', an ]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, { processAnnotations: false }, [ 'definitions' ]);
|
|
108
|
+
adaptRefs.forEach(fn => fn());
|
|
109
|
+
adaptRefs.length = 0;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
cleanup.forEach((obj) => {
|
|
114
|
+
if (obj.ref && obj.ref[0] === '$self')
|
|
115
|
+
obj.ref.shift();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function absolutifyPaths(prefix, cleanup) {
|
|
120
|
+
return function absolutify(_parent, _prop, ref) {
|
|
121
|
+
if (ref[0].id || ref[0] !== '$self' && ref[0] !== '$projection' && !ref[0].startsWith('$') && !_parent.param) {
|
|
122
|
+
_parent.ref = [ '$self', ...prefix, ...ref ];
|
|
123
|
+
cleanup.push(_parent);
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
flattenRefs,
|
|
135
|
+
};
|
|
@@ -4,6 +4,7 @@ const {
|
|
|
4
4
|
getUtils, isAspect, mergeTransformers, applyTransformations,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const transformUtils = require('../transformUtils');
|
|
7
|
+
const effectiveFlattening = require('./flattening');
|
|
7
8
|
const flattening = require('../db/flattening');
|
|
8
9
|
const types = require('./types');
|
|
9
10
|
// const { addLocalizationViews } = require('../../transform/localized');
|
|
@@ -65,8 +66,9 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
65
66
|
// Remove properties attached by validator - they do not "grow" as the model grows.
|
|
66
67
|
cleanup();
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
|
|
70
|
+
effectiveFlattening.flattenRefs(csn, options, csnUtils, messageFunctions);
|
|
71
|
+
flattening.flattenElements(csn, options, messageFunctions, '_', { skipDict: { actions: true } });
|
|
70
72
|
|
|
71
73
|
resolveTypesInActionsAfterFlattening();
|
|
72
74
|
|
|
@@ -76,8 +78,8 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
76
78
|
processCalculatedElementsInEntities(csn);
|
|
77
79
|
associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
|
|
78
80
|
associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
|
|
79
|
-
const transformers = mergeTransformers([ misc.attachPersistenceName(csn, options, csnUtils), options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
|
|
80
|
-
applyTransformations(csn, transformers, [], { skipIgnore: false });
|
|
81
|
+
const transformers = mergeTransformers([ options.addCdsPersistenceName ? misc.attachPersistenceName(csn, options, csnUtils) : {}, options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
|
|
82
|
+
applyTransformations(csn, transformers, [], { skipIgnore: false, processAnnotations: true });
|
|
81
83
|
|
|
82
84
|
if (!options.resolveProjections)
|
|
83
85
|
redoProjections.forEach(fn => fn());
|
|
@@ -18,18 +18,15 @@ const { cloneCsnDict, cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
|
18
18
|
* @param {CSN.Model} csn will be transformed
|
|
19
19
|
* @param {object} csnUtils
|
|
20
20
|
* @param {CSN.Options} options
|
|
21
|
-
* @returns {Function} Callback to resolve
|
|
21
|
+
* @returns {Function} Callback to resolve types in action returns later - as for them, $self would lead to unresolvable constructs at this point
|
|
22
22
|
* so we can call this callback after flattening is done - then we can safely resolve their types.
|
|
23
23
|
*/
|
|
24
24
|
function resolveTypes( csn, csnUtils, options ) {
|
|
25
25
|
const { getFinalTypeInfo } = csnUtils;
|
|
26
26
|
const later = [];
|
|
27
27
|
applyTransformations(csn, {
|
|
28
|
-
type: (parent
|
|
29
|
-
|
|
30
|
-
// TODO: What about events?
|
|
31
|
-
if (!(artifact.kind === 'action' || artifact.kind === 'function'))
|
|
32
|
-
resolveType(parent);
|
|
28
|
+
type: (parent) => {
|
|
29
|
+
resolveType(parent);
|
|
33
30
|
},
|
|
34
31
|
}, [ (definitions, artifactName, artifact) => {
|
|
35
32
|
// In a non-flat model, replacing types with some $self inside causes issues for actions (bound or unbound)
|
|
@@ -38,12 +35,20 @@ function resolveTypes( csn, csnUtils, options ) {
|
|
|
38
35
|
later.push({ [artifactName]: artifact });
|
|
39
36
|
else if (artifact.actions)
|
|
40
37
|
later.push(artifact.actions);
|
|
41
|
-
} ], {
|
|
38
|
+
} ], { skipStandard: { returns: true }, processAnnotations: true });
|
|
42
39
|
|
|
40
|
+
// TODO: Directly push the .returns into the later so we have a more minimal looping
|
|
43
41
|
return function resolveTypesInActions() {
|
|
44
|
-
later.forEach(
|
|
42
|
+
later.forEach((a) => {
|
|
43
|
+
applyTransformationsOnDictionary(a, {
|
|
44
|
+
type: (parent) => {
|
|
45
|
+
resolveType(parent);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
});
|
|
45
49
|
};
|
|
46
50
|
|
|
51
|
+
|
|
47
52
|
/**
|
|
48
53
|
* Resolve a type to its
|
|
49
54
|
* - elements
|
|
@@ -123,7 +128,6 @@ function resolveTypes( csn, csnUtils, options ) {
|
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
130
|
|
|
126
|
-
|
|
127
131
|
module.exports = {
|
|
128
132
|
resolve: resolveTypes,
|
|
129
133
|
};
|
|
@@ -167,8 +167,6 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
167
167
|
{ skipArtifact: isExternalServiceMember }
|
|
168
168
|
);
|
|
169
169
|
|
|
170
|
-
flattening.linkForeignKeyAnnotationExtensionsToAssociation(csn, options);
|
|
171
|
-
|
|
172
170
|
// All type refs must be resolved, including external APIs.
|
|
173
171
|
// OData has no 'type of' so 'real' imported OData APIs marked @cds.external are safe.
|
|
174
172
|
// If in the future 'other' APIs that might support type refs are imported, these refs must be
|
|
@@ -34,6 +34,7 @@ const backlinks = require('./db/backlinks');
|
|
|
34
34
|
const { getDefaultTypeLengths } = require('../render/utils/common');
|
|
35
35
|
const { featureFlags } = require('./db/featureFlags');
|
|
36
36
|
const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
|
|
37
|
+
const { processSqlServices } = require('./db/processSqlServices');
|
|
37
38
|
|
|
38
39
|
// By default: Do not process non-entities/views
|
|
39
40
|
function forEachDefinition(csn, cb) {
|
|
@@ -169,8 +170,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
169
170
|
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
|
|
170
171
|
handleExists(csn, options, error, csnUtils.inspectRef, csnUtils.initDefinition, csnUtils.dropDefinitionCache);
|
|
171
172
|
|
|
172
|
-
doA2J && flattening.linkForeignKeyAnnotationExtensionsToAssociation(csn, options);
|
|
173
|
-
|
|
174
173
|
// Check if structured elements and managed associations are compared in an expression
|
|
175
174
|
// and expand these structured elements. This tuple expansion allows all other
|
|
176
175
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
@@ -370,6 +369,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
370
369
|
removeKeyPropInType,
|
|
371
370
|
]);
|
|
372
371
|
|
|
372
|
+
// TODO: Might have to do this earlier if we want special rendering for projections?
|
|
373
|
+
const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && csn.meta?.[featureFlags]?.$sqlService ? processSqlServices(csn): () => {}
|
|
373
374
|
|
|
374
375
|
// TODO: Could we maybe merge this with the final applyTransformations?
|
|
375
376
|
applyTransformations(csn, {
|
|
@@ -389,6 +390,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
389
390
|
// Attach @cds.persistence.name to artifacts
|
|
390
391
|
if (!artifact.$ignore && artifact.kind !== 'service' && artifact.kind !== 'context')
|
|
391
392
|
csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
393
|
+
|
|
394
|
+
findAndMarkSqlServiceArtifacts(artifact, artifactName);
|
|
392
395
|
}], { allowArtifact: artifact => artifact.kind === 'entity'});
|
|
393
396
|
|
|
394
397
|
throwWithAnyError();
|
|
@@ -702,23 +705,10 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
702
705
|
newCsn.definitions[artName].technicalConfig = art.technicalConfig;
|
|
703
706
|
|
|
704
707
|
});
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if(oldDef?.elements) {
|
|
710
|
-
Object.entries(oldDef.elements).forEach(([eltName, oldElt]) => {
|
|
711
|
-
const newElt = newDef.elements[eltName];
|
|
712
|
-
if(oldElt.$fkExtensions)
|
|
713
|
-
setProp(newElt, '$fkExtensions', oldElt.$fkExtensions);
|
|
714
|
-
oldElt.keys?.forEach((fk, i) => {
|
|
715
|
-
if(fk.$structRef && newElt.keys?.[i])
|
|
716
|
-
setProp(newElt.keys[i], '$structRef', fk.$structRef);
|
|
717
|
-
})
|
|
718
|
-
})
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
}
|
|
708
|
+
|
|
709
|
+
// To ensure we preserve feature flags
|
|
710
|
+
newCsn.meta = csn.meta;
|
|
711
|
+
|
|
722
712
|
csn = newCsn;
|
|
723
713
|
}
|
|
724
714
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { makeMessageFunction } = require('../base/messages');
|
|
4
|
-
const { setProp
|
|
4
|
+
const { setProp } = require('../base/model');
|
|
5
5
|
const { forEachKey } = require('../utils/objectUtils');
|
|
6
6
|
const { cleanSymbols } = require('../base/cleanSymbols.js');
|
|
7
7
|
const {
|
|
@@ -91,6 +91,7 @@ const annoPersistenceSkip = '@cds.persistence.skip';
|
|
|
91
91
|
* Deprecated version of localizedLanguageFallback. Do not use.
|
|
92
92
|
*
|
|
93
93
|
* @param {boolean} [options.fewerLocalizedViews]
|
|
94
|
+
* Default: true
|
|
94
95
|
*
|
|
95
96
|
* @param {object} config
|
|
96
97
|
* Configuration for creating convenience views. Non-user visible options.
|
|
@@ -114,7 +115,8 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
114
115
|
const { useJoins, acceptLocalizedView, ignoreUnknownExtensions } = config;
|
|
115
116
|
const noCoalesce = (options.localizedLanguageFallback === 'none' ||
|
|
116
117
|
options.localizedWithoutCoalesce);
|
|
117
|
-
|
|
118
|
+
// default is true, hence only check for explicitly disabled option
|
|
119
|
+
const ignoreAssocToLocalized = options.fewerLocalizedViews !== false;
|
|
118
120
|
|
|
119
121
|
createDirectConvenienceViews(); // 1
|
|
120
122
|
createTransitiveConvenienceViews(); // 2 + 3
|
|
@@ -176,7 +178,7 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
176
178
|
else
|
|
177
179
|
view = createLocalizedViewForEntity(art, artName, textElements);
|
|
178
180
|
|
|
179
|
-
copyPersistenceAnnotations(view, art
|
|
181
|
+
copyPersistenceAnnotations(view, art);
|
|
180
182
|
csn.definitions[viewName] = view;
|
|
181
183
|
}
|
|
182
184
|
|
|
@@ -752,18 +754,15 @@ function copyLocation(target, source) {
|
|
|
752
754
|
*
|
|
753
755
|
* @param {CSN.Artifact} target
|
|
754
756
|
* @param {CSN.Artifact} source
|
|
755
|
-
* @param {CSN.Options} options
|
|
756
757
|
*/
|
|
757
|
-
function copyPersistenceAnnotations(target, source
|
|
758
|
-
const copyExists = !isBetaEnabled(options, 'v5preview') &&
|
|
759
|
-
!isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
758
|
+
function copyPersistenceAnnotations(target, source) {
|
|
760
759
|
forEachKey(source, anno => {
|
|
761
760
|
// Note:
|
|
762
761
|
// v3/v4: Because `.exists` is copied to the convenience view, it could
|
|
763
762
|
// lead to some localization views referencing non-existing ones.
|
|
764
763
|
// But that is the contract: User says that it already exists!
|
|
765
764
|
// v2/>=v5, `.exists` is never copied.
|
|
766
|
-
if (anno === annoPersistenceSkip
|
|
765
|
+
if (anno === annoPersistenceSkip)
|
|
767
766
|
target[anno] = source[anno];
|
|
768
767
|
});
|
|
769
768
|
}
|
|
@@ -8,8 +8,7 @@ const transformUtils = require('../transformUtils');
|
|
|
8
8
|
const { setProp } = require('../../base/model');
|
|
9
9
|
const { applyTransformationsOnDictionary,
|
|
10
10
|
applyTransformationsOnNonDictionary } = require('../db/applyTransformations.js');
|
|
11
|
-
const {
|
|
12
|
-
handleManagedAssociationsAndCreateForeignKeys } = require('../db/flattening');
|
|
11
|
+
const { handleManagedAssociationsAndCreateForeignKeys } = require('../db/flattening');
|
|
13
12
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
14
13
|
const { forEach } = require('../../utils/objectUtils');
|
|
15
14
|
|
|
@@ -293,17 +292,27 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
293
292
|
|
|
294
293
|
const refCheck = {
|
|
295
294
|
ref: (elemref, prop, xpr, path) => {
|
|
296
|
-
const { art, scope } = inspectRef(path);
|
|
295
|
+
const { links, art, scope } = inspectRef(path);
|
|
297
296
|
if (scope !== '$magic' && art) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
297
|
+
// try to find rightmost 'items', terminate if association comes first.
|
|
298
|
+
let i = links.length-1;
|
|
299
|
+
const getProp = (propName) => links[i].art?.[propName];
|
|
300
|
+
|
|
301
|
+
let hasItems = false;
|
|
302
|
+
for(; i >= 0 && !getProp('target') && !hasItems; i--) {
|
|
303
|
+
hasItems = !!getProp('items')
|
|
304
|
+
}
|
|
305
|
+
if(!hasItems) {
|
|
306
|
+
const ft = csnUtils.getFinalTypeInfo(art.type);
|
|
307
|
+
if (!isBuiltinType(ft?.items?.type || ft?.type) && refCheck.anno !== 'value') {
|
|
308
|
+
error('odata-anno-xpr-ref', path,
|
|
309
|
+
{
|
|
310
|
+
anno: refCheck.anno,
|
|
311
|
+
elemref,
|
|
312
|
+
name: refCheck.eltLocationStr,
|
|
313
|
+
'#': 'flatten_builtin'
|
|
314
|
+
});
|
|
315
|
+
}
|
|
307
316
|
}
|
|
308
317
|
}
|
|
309
318
|
}
|
|
@@ -421,24 +430,24 @@ function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, inspectRef,
|
|
|
421
430
|
|
|
422
431
|
const refCheck = {
|
|
423
432
|
ref: (elemref, prop, xpr, path) => {
|
|
424
|
-
const { links, art } = (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
433
|
+
const { links, art, scope } = inspectRef(path);
|
|
434
|
+
|
|
435
|
+
if (scope !== '$magic' && art) {
|
|
436
|
+
let i = links.length-2;
|
|
437
|
+
const getProp = (propName) =>
|
|
438
|
+
(links[i].art?.[propName] ||
|
|
439
|
+
effectiveType(links[i].art)[propName]);
|
|
440
|
+
|
|
441
|
+
let target = undefined;
|
|
442
|
+
for(; i >= 0 && !getProp('items') && !target; i--) {
|
|
443
|
+
target = getProp('target');
|
|
444
|
+
}
|
|
445
|
+
const ft = csnUtils.getFinalTypeInfo(art.type);
|
|
446
|
+
if (target && csn.definitions[target].$flatelements
|
|
447
|
+
&& !isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
|
|
448
|
+
error('odata-anno-xpr-ref', path,
|
|
449
|
+
{ anno: refCheck.anno, elemref, '#': 'flatten_builtin_type' });
|
|
450
|
+
}
|
|
442
451
|
}
|
|
443
452
|
}
|
|
444
453
|
}
|
|
@@ -548,7 +557,6 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
548
557
|
module.exports = {
|
|
549
558
|
allInOneFlattening,
|
|
550
559
|
flattenAllStructStepsInRefs,
|
|
551
|
-
linkForeignKeyAnnotationExtensionsToAssociation,
|
|
552
560
|
handleManagedAssociationsAndCreateForeignKeys,
|
|
553
561
|
getStructRefFlatteningTransformer
|
|
554
562
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
10
10
|
const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
|
|
11
|
-
const { getNamespace, copyAnnotations,
|
|
11
|
+
const { getNamespace, copyAnnotations,
|
|
12
12
|
forEachDefinition, forEachMember, forEachGeneric, isEdmPropertyRendered } = require('../../model/csnUtils');
|
|
13
13
|
const { isBuiltinType } = require('../../base/builtins');
|
|
14
14
|
const { CompilerAssertion } = require('../../base/error');
|
|
@@ -229,22 +229,10 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
229
229
|
}
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (!isAnonymous) {
|
|
237
|
-
copyAnnotations(typeDef, newType);
|
|
238
|
-
}
|
|
239
|
-
} else {
|
|
240
|
-
// expression annos and their sub annotations are not propagated to type
|
|
241
|
-
let [ xprANames, nxprANames ] = Object.keys(typeDef).reduce((acc, pn) => {
|
|
242
|
-
if (pn[0] === '@')
|
|
243
|
-
acc[findAnnotationExpression(typeDef, pn) ? 0 : 1].push(pn);
|
|
244
|
-
return acc;
|
|
245
|
-
}, [ [], [] ]);
|
|
246
|
-
nxprANames = nxprANames.filter(an => !xprANames.some(ean => an.startsWith(`${ean}.`)));
|
|
247
|
-
copyAnnotations(typeDef, newType, false, {}, nxprANames);
|
|
232
|
+
// Annotations are propagated only from user defined structured
|
|
233
|
+
// types that need to be added to a service
|
|
234
|
+
if (!isAnonymous) {
|
|
235
|
+
copyAnnotations(typeDef, newType);
|
|
248
236
|
}
|
|
249
237
|
|
|
250
238
|
// if the origin type had items, add items to exposed type
|
|
@@ -266,7 +266,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
266
266
|
} else {
|
|
267
267
|
// Primitive child - clone it and restore its cross references
|
|
268
268
|
const flatElemName = elemName + pathDelimiter + childName;
|
|
269
|
-
const flatElem = cloneCsnNonDict(childElem,
|
|
269
|
+
const flatElem = cloneCsnNonDict(childElem, options);
|
|
270
270
|
// Don't take over notNull from leaf elements
|
|
271
271
|
delete flatElem.notNull;
|
|
272
272
|
setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));
|
|
@@ -1710,7 +1710,6 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1710
1710
|
const path = pathDict.path;
|
|
1711
1711
|
const s = pathAsStr(path, '"');
|
|
1712
1712
|
const me = env.lead && (env.lead.name.id || env.lead.op);
|
|
1713
|
-
// eslint-disable-next-line no-console
|
|
1714
1713
|
console.log(me + ': ' + env.location + ': ' + s + ' alias: ' + alias);
|
|
1715
1714
|
}
|
|
1716
1715
|
|
package/lib/utils/file.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const util = require('util');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Split the given source string into its lines. Respects Unix,
|
|
@@ -16,6 +18,74 @@ function splitLines( src ) {
|
|
|
16
18
|
return src.split(/\r\n?|\n/);
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Returns the file's normalized extension, e.g. for `file.CDS` -> `cds`
|
|
23
|
+
* Returns null if the given filename is not a string.
|
|
24
|
+
*
|
|
25
|
+
* @param filename
|
|
26
|
+
* @returns {null|string}
|
|
27
|
+
*/
|
|
28
|
+
function fileExtension( filename ) {
|
|
29
|
+
if (typeof filename === 'string')
|
|
30
|
+
return path.extname( filename ).slice(1).toLowerCase();
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a temporary file path using the system's temporary folder and a filename
|
|
36
|
+
* consisting of the given name/extension and a random string.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} name
|
|
39
|
+
* @param {string} extension
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function tmpFilePath( name, extension ) {
|
|
43
|
+
const crypto = require('crypto');
|
|
44
|
+
const id = crypto.randomBytes(32).toString('hex');
|
|
45
|
+
const filename = `${ name }-${ id }.${ extension }`;
|
|
46
|
+
return path.join(os.tmpdir(), filename);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read input from the given stream.
|
|
51
|
+
* See https://nodejs.org/api/stream.html#readablereadsize
|
|
52
|
+
*
|
|
53
|
+
* @returns {Promise<string>}
|
|
54
|
+
*/
|
|
55
|
+
function readStream( stream ) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const chunks = [];
|
|
58
|
+
|
|
59
|
+
const listeners = {
|
|
60
|
+
__proto__: null,
|
|
61
|
+
data: onData,
|
|
62
|
+
error: onError,
|
|
63
|
+
end: onEnd,
|
|
64
|
+
};
|
|
65
|
+
for (const name in listeners)
|
|
66
|
+
stream.on(name, listeners[name]);
|
|
67
|
+
|
|
68
|
+
function onData( chunk ) {
|
|
69
|
+
chunks.push(chunk);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onEnd() {
|
|
73
|
+
removeListeners();
|
|
74
|
+
resolve(chunks.join(''));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function onError( error ) {
|
|
78
|
+
removeListeners();
|
|
79
|
+
reject(error);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function removeListeners() {
|
|
83
|
+
for (const name in listeners)
|
|
84
|
+
stream.removeListener(name, listeners[name]);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
19
89
|
/**
|
|
20
90
|
* Returns filesystem utils readFile(), isFile(), realpath() for _CDS_ usage.
|
|
21
91
|
* This includes a trace as well as usage of a file cache.
|
|
@@ -49,24 +119,30 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
49
119
|
|
|
50
120
|
return {
|
|
51
121
|
/** @type {function(string, string)} */
|
|
52
|
-
readFileAsync: util.promisify(readFile),
|
|
122
|
+
readFileAsync: util.promisify( readFile ),
|
|
53
123
|
readFile,
|
|
54
124
|
readFileSync,
|
|
55
|
-
isFileAsync: util.promisify(isFile),
|
|
56
125
|
isFile,
|
|
57
126
|
isFileSync,
|
|
58
|
-
|
|
59
|
-
realpath,
|
|
127
|
+
realpath: fs.realpath,
|
|
128
|
+
realpathNative: fs.realpath.native,
|
|
60
129
|
realpathSync,
|
|
130
|
+
realpathSyncNative,
|
|
61
131
|
};
|
|
62
132
|
|
|
63
|
-
|
|
64
|
-
|
|
133
|
+
|
|
134
|
+
function realpathSync( filepath, cb ) {
|
|
135
|
+
try {
|
|
136
|
+
cb(null, fs.realpathSync(filepath));
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
cb(err, null);
|
|
140
|
+
}
|
|
65
141
|
}
|
|
66
142
|
|
|
67
|
-
function
|
|
143
|
+
function realpathSyncNative( filepath, cb ) {
|
|
68
144
|
try {
|
|
69
|
-
cb(null, fs.realpathSync.native(
|
|
145
|
+
cb(null, fs.realpathSync.native(filepath));
|
|
70
146
|
}
|
|
71
147
|
catch (err) {
|
|
72
148
|
cb(err, null);
|
|
@@ -188,5 +264,8 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
188
264
|
|
|
189
265
|
module.exports = {
|
|
190
266
|
splitLines,
|
|
267
|
+
readStream,
|
|
268
|
+
fileExtension,
|
|
269
|
+
tmpFilePath,
|
|
191
270
|
cdsFs,
|
|
192
271
|
};
|