@sap/cds-compiler 2.7.0 → 2.11.2
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 +167 -0
- package/bin/cdsc.js +42 -25
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +10 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +17 -33
- package/lib/api/options.js +25 -13
- package/lib/api/validate.js +33 -9
- package/lib/backends.js +9 -8
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +26 -2
- package/lib/base/messages.js +25 -9
- package/lib/base/model.js +5 -3
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/onConditions.js +5 -0
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/validator.js +7 -2
- package/lib/compiler/assert-consistency.js +18 -5
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +5 -2
- package/lib/compiler/definer.js +145 -120
- package/lib/compiler/index.js +16 -4
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +207 -47
- package/lib/compiler/shared.js +47 -200
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +94 -98
- package/lib/edm/edm.js +16 -20
- package/lib/edm/edmPreprocessor.js +302 -115
- package/lib/edm/edmUtils.js +31 -12
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5308 -4308
- package/lib/json/from-csn.js +59 -30
- package/lib/json/to-csn.js +354 -105
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +81 -14
- package/lib/language/language.g4 +163 -31
- package/lib/main.d.ts +136 -17
- package/lib/main.js +7 -1
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +115 -32
- package/lib/model/csnUtils.js +71 -33
- package/lib/model/enrichCsn.js +36 -9
- package/lib/model/revealInternalProperties.js +20 -4
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -16
- package/lib/render/.eslintrc.json +3 -1
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +60 -17
- package/lib/render/toHdbcds.js +122 -74
- package/lib/render/toSql.js +57 -32
- package/lib/render/utils/common.js +6 -10
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/constraints.js +273 -119
- package/lib/transform/db/draft.js +9 -6
- package/lib/transform/db/expansion.js +19 -7
- package/lib/transform/db/flattening.js +31 -7
- package/lib/transform/db/transformExists.js +344 -66
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +65 -436
- package/lib/transform/forOdataNew.js +21 -10
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +44 -38
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +13 -10
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +55 -9
- package/lib/transform/translateAssocsToJoins.js +11 -17
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -3
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { makeMessageFunction } = require('../base/messages');
|
|
4
|
-
const { isDeprecatedEnabled } = require('../base/model');
|
|
4
|
+
const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
|
|
5
5
|
const transformUtils = require('./transformUtilsNew');
|
|
6
6
|
const { getUtils,
|
|
7
7
|
cloneCsn,
|
|
@@ -22,8 +22,9 @@ const { flattenCSN } = require('./odata/structureFlattener');
|
|
|
22
22
|
const generateForeignKeys = require('./odata/generateForeignKeyElements');
|
|
23
23
|
const expandStructKeysInAssociations = require('./odata/expandStructKeysInAssociations');
|
|
24
24
|
const expandToFinalBaseType = require('./odata/toFinalBaseType');
|
|
25
|
-
const timetrace = require('../utils/timetrace');
|
|
25
|
+
const { timetrace } = require('../utils/timetrace');
|
|
26
26
|
const { attachPath } = require('./odata/attachPath');
|
|
27
|
+
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
27
28
|
|
|
28
29
|
const { addLocalizationViews } = require('./localized');
|
|
29
30
|
|
|
@@ -89,7 +90,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
89
90
|
addElement, createAction, assignAction,
|
|
90
91
|
extractValidFromToKeyElement,
|
|
91
92
|
checkAssignment, checkMultipleAssignments,
|
|
92
|
-
recurseElements, setAnnotation, renameAnnotation,
|
|
93
|
+
recurseElements, setAnnotation, resetAnnotation, renameAnnotation,
|
|
93
94
|
expandStructsInExpression
|
|
94
95
|
} = transformers;
|
|
95
96
|
|
|
@@ -119,6 +120,9 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
119
120
|
// @ts-ignore
|
|
120
121
|
const isExternalServiceMember = (_art, name) => externalServices.includes(getServiceName(name));
|
|
121
122
|
|
|
123
|
+
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
|
|
124
|
+
enrichUniversalCsn(csn, options);
|
|
125
|
+
|
|
122
126
|
const keepLocalizedViews = isDeprecatedEnabled(options, 'createLocalizedViews');
|
|
123
127
|
|
|
124
128
|
function acceptLocalizedView(_name, parent) {
|
|
@@ -187,7 +191,11 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
187
191
|
generateForeignKeys(csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember);
|
|
188
192
|
|
|
189
193
|
// Apply default type facets as set by options
|
|
190
|
-
// Flatten on-conditions in unmanaged associations
|
|
194
|
+
// Flatten on-conditions in unmanaged associations
|
|
195
|
+
/* FIXME (HJB): Is this comment still correct? processOnCond only strips $self
|
|
196
|
+
We should not remove $self prefixes in structured OData to not
|
|
197
|
+
interfer with path resolution
|
|
198
|
+
*/
|
|
191
199
|
// This must be done before all the draft logic as all
|
|
192
200
|
// composition targets are annotated with @odata.draft.enabled in this step
|
|
193
201
|
forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });
|
|
@@ -326,7 +334,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
326
334
|
renameAnnotation(node, name, '@UI.Importance');
|
|
327
335
|
let annotation = node['@UI.Importance'];
|
|
328
336
|
if (annotation !== null)
|
|
329
|
-
node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' }
|
|
337
|
+
node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' };
|
|
330
338
|
}
|
|
331
339
|
|
|
332
340
|
// Special case: '@readonly' becomes a triplet of capability restrictions for entities,
|
|
@@ -457,11 +465,11 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
457
465
|
}
|
|
458
466
|
// Generate the annotations describing the draft actions (only draft roots can be activated/edited)
|
|
459
467
|
if (artifact == rootArtifact) {
|
|
460
|
-
artifact
|
|
461
|
-
artifact
|
|
462
|
-
artifact
|
|
468
|
+
resetAnnotation(artifact, '@Common.DraftRoot.ActivationAction', 'draftActivate', info, ['definitions', draftAdminDataProjectionName]);
|
|
469
|
+
resetAnnotation(artifact, '@Common.DraftRoot.EditAction', 'draftEdit', info, ['definitions', draftAdminDataProjectionName]);
|
|
470
|
+
resetAnnotation(artifact, '@Common.DraftRoot.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
|
|
463
471
|
} else {
|
|
464
|
-
artifact
|
|
472
|
+
resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
|
|
465
473
|
}
|
|
466
474
|
|
|
467
475
|
artifact.elements && Object.values(artifact.elements).forEach( elem => {
|
|
@@ -529,7 +537,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
529
537
|
|
|
530
538
|
// Ignore if that is our own draft root
|
|
531
539
|
if (draftNode != rootArtifact) {
|
|
532
|
-
//
|
|
540
|
+
// Report error when the draft node has @odata.draft.enabled itself
|
|
533
541
|
if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
|
|
534
542
|
error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
|
|
535
543
|
}
|
|
@@ -565,6 +573,9 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
565
573
|
// CDXCORE-481
|
|
566
574
|
// (4.5) If the member is an association whose target has @cds.odata.valuelist annotate it
|
|
567
575
|
// with @Common.ValueList.viaAssociation.
|
|
576
|
+
/*
|
|
577
|
+
FIXME (HJB): Comment outdated: Anno propagation to FKs is done in EdmPreprocessor
|
|
578
|
+
*/
|
|
568
579
|
// This must be done before foreign keys are calculated and the annotations are propagated
|
|
569
580
|
// to them. This will make sure that association and all its foreign keys are annotated with
|
|
570
581
|
// Common.ValueList in the final EDM.
|
|
@@ -698,6 +698,8 @@ function copyPersistenceAnnotations(target, source) {
|
|
|
698
698
|
* @param {CSN.Options} options
|
|
699
699
|
*/
|
|
700
700
|
function hasExistingLocalizationViews(csn, options) {
|
|
701
|
+
if (!csn || !csn.definitions)
|
|
702
|
+
return false;
|
|
701
703
|
const firstLocalizedView = Object.keys(csn.definitions).find(isInLocalizedNamespace);
|
|
702
704
|
if (firstLocalizedView) {
|
|
703
705
|
const { info } = makeMessageFunction(csn, options);
|
|
@@ -24,6 +24,8 @@ const structuralNodeHandlers = {
|
|
|
24
24
|
groupBy: traverseArray,
|
|
25
25
|
having: traverseArray,
|
|
26
26
|
xpr: traverseArray,
|
|
27
|
+
expand: traverseArray,
|
|
28
|
+
inline: traverseArray,
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function attachPath(csn) {
|
|
@@ -38,6 +40,7 @@ function attachPathOnPartialCSN(csnPart, pathPrefix) {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
function traverseRef(obj, path) {
|
|
43
|
+
if(!obj) return;
|
|
41
44
|
setPath(obj, path);
|
|
42
45
|
traverseArray(obj, path);
|
|
43
46
|
}
|
|
@@ -48,7 +51,7 @@ function traverseArray(obj, path) {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
function traverseDict(obj, path) {
|
|
51
|
-
if(typeof obj !== 'object') return;
|
|
54
|
+
if(!obj || typeof obj !== 'object') return;
|
|
52
55
|
forAllEnumerableProperties(obj, name => {
|
|
53
56
|
const ipath = path.concat(name);
|
|
54
57
|
setPath(obj[name], ipath);
|
|
@@ -56,17 +59,29 @@ function traverseDict(obj, path) {
|
|
|
56
59
|
})
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
function traverseDictArray(obj, path) {
|
|
63
|
+
if(!obj || typeof obj !== 'object') return;
|
|
64
|
+
forAllEnumerableProperties(obj, name => {
|
|
65
|
+
const ipath = path.concat(name);
|
|
66
|
+
setPath(obj[name], ipath);
|
|
67
|
+
traverseArray(obj[name], ipath);
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
function traverseTyped(obj, path) {
|
|
60
|
-
if(!obj) return;
|
|
72
|
+
if(!obj || typeof obj !== 'object') return;
|
|
61
73
|
forAllEnumerableProperties(obj, name => {
|
|
62
74
|
if(name[0]==='@') return; // skip annotations
|
|
63
75
|
const func = structuralNodeHandlers[name];
|
|
64
|
-
if(func)
|
|
76
|
+
if(func)
|
|
77
|
+
func(obj[name], path.concat(name));
|
|
78
|
+
else if(path[path.length-2] === 'columns')
|
|
79
|
+
traverseDictArray(obj[name], path.concat(name)); // for columns
|
|
65
80
|
})
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
function setPath(obj, path) {
|
|
69
|
-
if(typeof obj !== 'object') return;
|
|
84
|
+
if(!obj || typeof obj !== 'object') return;
|
|
70
85
|
if(path.length>0)
|
|
71
86
|
Object.defineProperty( obj, '$path', { value: path, configurable: true, writable: true, enumerable: false } );
|
|
72
87
|
}
|
|
@@ -35,7 +35,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
35
35
|
let generatedForeignKeyNamesForPath = Object.create(null); // map<path,[key-name]>
|
|
36
36
|
|
|
37
37
|
sortedAssociations.forEach(item => {
|
|
38
|
-
const { definitionName, elementName, element, parent, path } = item;
|
|
38
|
+
const { definitionName, structuralNodeName, elementName, element, parent, path } = item;
|
|
39
39
|
|
|
40
40
|
if (csnUtils.isManagedAssociationElement(element) && element.keys) {
|
|
41
41
|
if (flatKeys) // tackling the ref value in assoc.keys
|
|
@@ -44,7 +44,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
44
44
|
fixCardinality(element);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, elementName, element, parent, path);
|
|
47
|
+
let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, structuralNodeName, elementName, element, parent, path);
|
|
48
48
|
generatedForeignKeyNamesForPath[item.path.join('/')] = arrayOfGeneratedForeignKeyNames;
|
|
49
49
|
})
|
|
50
50
|
|
|
@@ -111,7 +111,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
111
111
|
/**
|
|
112
112
|
* Generates foreign keys and returns their names as an array
|
|
113
113
|
*/
|
|
114
|
-
function generateForeignKeys(definitionName, assocName, assoc, parent, path) {
|
|
114
|
+
function generateForeignKeys(definitionName, structuralNodeName, assocName, assoc, parent, path) {
|
|
115
115
|
let foreignKeyElements = Object.create(null);
|
|
116
116
|
|
|
117
117
|
// First, loop over the keys array of the association and generate the FKs.
|
|
@@ -134,13 +134,14 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
134
134
|
if (parent.returns)
|
|
135
135
|
parent = parent.returns.items || parent.returns;
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
const dictionary = parent[structuralNodeName];
|
|
138
|
+
let currElementsNames = Object.keys(parent[structuralNodeName]);
|
|
138
139
|
for (const [foreignKeyName, foreignKey] of Object.entries(foreignKeyElements)) {
|
|
139
140
|
copyAnnotations(assoc, foreignKey, true);
|
|
140
141
|
// Insert artificial element into artifact, with all cross-links
|
|
141
|
-
if (
|
|
142
|
-
if (!(
|
|
143
|
-
const path =
|
|
142
|
+
if (dictionary[foreignKeyName]) {
|
|
143
|
+
if (!(dictionary[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(dictionary[foreignKeyName], foreignKey))) {
|
|
144
|
+
const path = dictionary[foreignKeyName].$path;
|
|
144
145
|
error(null, path, { name: foreignKeyName, art: assocName }, 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
|
|
145
146
|
}
|
|
146
147
|
}
|
|
@@ -151,8 +152,8 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
151
152
|
// if (flatKeys)
|
|
152
153
|
currElementsNames.splice(assocIndex + 1, 0, ...Object.keys(foreignKeyElements));
|
|
153
154
|
|
|
154
|
-
parent
|
|
155
|
-
previous[name] =
|
|
155
|
+
parent[structuralNodeName] = currElementsNames.reduce((previous, name) => {
|
|
156
|
+
previous[name] = dictionary[name] || foreignKeyElements[name];
|
|
156
157
|
return previous;
|
|
157
158
|
}, Object.create(null));
|
|
158
159
|
|
|
@@ -180,7 +181,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
180
181
|
|
|
181
182
|
function processStucturedKey(fkArtifact, assocName, foreignKeyRef) {
|
|
182
183
|
const subStruct = fkArtifact.elements ? fkArtifact : csnUtils.getFinalBaseType(fkArtifact.type);
|
|
183
|
-
const flatElements = flattenStructure(subStruct, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
|
|
184
|
+
const flatElements = flattenStructure(subStruct.elements, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
|
|
184
185
|
for (const [flatElemName, flatElem] of Object.entries(flatElements)) {
|
|
185
186
|
const foreignKeyElementName =
|
|
186
187
|
`${assocName.replace(/\./g, '_')}_${foreignKeyRef.as ? flatElemName.replace(implicitAs(foreignKeyRef.ref), foreignKeyRef.as) : flatElemName}`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { forEachRef } = require('../../model/csnUtils');
|
|
2
2
|
const { setProp } = require('../../base/model');
|
|
3
3
|
const { implicitAs } = require('../../model/csnRefs');
|
|
4
|
+
const { structuralPath } = require('./structuralPath');
|
|
4
5
|
|
|
5
6
|
/** This class is used for generic reference flattening.
|
|
6
7
|
* It provides the following functionality:
|
|
@@ -73,8 +74,7 @@ class ReferenceFlattener {
|
|
|
73
74
|
});
|
|
74
75
|
if (paths)
|
|
75
76
|
setProp(node, '$paths', paths);
|
|
76
|
-
|
|
77
|
-
// cache also if structured or not
|
|
77
|
+
// cache if structured or not
|
|
78
78
|
let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
|
|
79
79
|
this.structuredReference[path.join('/')] = structured;
|
|
80
80
|
});
|
|
@@ -176,10 +176,10 @@ class ReferenceFlattener {
|
|
|
176
176
|
if (node.$paths) {
|
|
177
177
|
let newRef = []; // flattened reference
|
|
178
178
|
let flattenWithPrevious = false;
|
|
179
|
-
let
|
|
179
|
+
let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
|
|
180
180
|
node.$paths.forEach((path, i) => {
|
|
181
181
|
if (path === undefined || !ref[i]) return;
|
|
182
|
-
let spath = path.join('/')
|
|
182
|
+
let spath = path.join('/');
|
|
183
183
|
let movedTo = this.elementTransitions[spath]; // detect element transition
|
|
184
184
|
let flattened = this.flattenedElementPaths[spath];
|
|
185
185
|
if (flattenWithPrevious) {
|
|
@@ -190,27 +190,26 @@ class ReferenceFlattener {
|
|
|
190
190
|
ref[i].id = newRef[newRef.length - 1];
|
|
191
191
|
newRef[newRef.length - 1] = ref[i];
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
lastFlattenedID = i;
|
|
194
194
|
} else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
|
|
195
195
|
let movedToElementName = movedTo[movedTo.length-1];
|
|
196
196
|
newRef.push(movedToElementName);
|
|
197
|
-
|
|
197
|
+
lastFlattenedID = i;
|
|
198
198
|
} else {
|
|
199
199
|
newRef.push(ref[i]);
|
|
200
200
|
}
|
|
201
201
|
flattenWithPrevious = flattened;
|
|
202
202
|
});
|
|
203
|
-
if (newRef !== undefined &&
|
|
203
|
+
if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
|
|
204
204
|
// check if this is a column and add alias if missing
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
let structural = structuralPath(csn, path);
|
|
206
|
+
if (isColumnInSelectOrProjection(structural)) {
|
|
207
|
+
if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
|
|
207
208
|
node.as = node.ref[node.ref.length - 1];
|
|
208
|
-
}
|
|
209
209
|
}
|
|
210
210
|
setProp(newRef, '$path', path.concat('ref'));
|
|
211
211
|
if (!node.as) {
|
|
212
|
-
|
|
213
|
-
if (ifKeysScope(path))
|
|
212
|
+
if (isPartOfKeysStructure(structural))
|
|
214
213
|
node.as = implicitAs(node.ref);
|
|
215
214
|
else
|
|
216
215
|
setProp(node, 'as', implicitAs(node.ref))
|
|
@@ -223,13 +222,11 @@ class ReferenceFlattener {
|
|
|
223
222
|
|
|
224
223
|
applyAliasesInOnCond(csn, inspectRef) {
|
|
225
224
|
forEachRef(csn, (ref, node, path) => {
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
if
|
|
229
|
-
// the above condition means: only handle references starting with
|
|
230
|
-
// $self or element references outside queries
|
|
231
|
-
|
|
225
|
+
// Process only on-conditions of associations
|
|
226
|
+
let structural = structuralPath(csn, path);
|
|
227
|
+
if(!isOnCondition(structural)) return;
|
|
232
228
|
let { links } = inspectRef(path);
|
|
229
|
+
if(!links) return; // $user not resolvable
|
|
233
230
|
let keysOfPreviousStepWhenManagedAssoc = undefined;
|
|
234
231
|
|
|
235
232
|
let aliasedRef = [...ref];
|
|
@@ -253,32 +250,41 @@ class ReferenceFlattener {
|
|
|
253
250
|
}
|
|
254
251
|
}
|
|
255
252
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Checks if the provided path is a column inside a key node
|
|
255
|
+
* by exploring the possibility of the structural path to ends with 'elements' and 'keys'.
|
|
256
|
+
*
|
|
257
|
+
* @param structural structural path to explore
|
|
258
|
+
* @returns {boolean} True if the provided path matched the requirements to be part of a key node.
|
|
259
|
+
*/
|
|
260
|
+
function isPartOfKeysStructure(structural) {
|
|
261
|
+
return structural[structural.length-2] === 'elements'
|
|
262
|
+
&& structural[structural.length-1] === 'keys';
|
|
262
263
|
}
|
|
263
264
|
|
|
264
265
|
/**
|
|
265
|
-
* Checks if the provided path is a column inside a select node
|
|
266
|
-
* by exploring the possibility of the path to
|
|
266
|
+
* Checks if the provided path is a column inside a select or a projection node
|
|
267
|
+
* by exploring the possibility of the structural path to contain 'SELECT' or 'projection'
|
|
268
|
+
* and ends with 'columns' or 'columns' and 'expand'.
|
|
267
269
|
*
|
|
268
|
-
* @param
|
|
270
|
+
* @param structural structural path to explore
|
|
269
271
|
* @returns {boolean} True if the provided path matched the requirements to be a select node.
|
|
270
272
|
*/
|
|
271
|
-
function
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
273
|
+
function isColumnInSelectOrProjection(structural) {
|
|
274
|
+
return (structural.includes('SELECT') || structural.includes('projection'))
|
|
275
|
+
&& (structural[structural.length-1] === 'columns' || (structural[structural.length-2] === 'columns' && structural[structural.length-1] === 'expand'));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Checks if the provided path is a column inside an on-condition of an element
|
|
280
|
+
* by exploring the possibility of the structural path to ends with 'elements' and 'on'.
|
|
281
|
+
*
|
|
282
|
+
* @param structural structural path to explore
|
|
283
|
+
* @returns {boolean} True if the provided path matched the requirements to be part of an element's on-condition.
|
|
284
|
+
*/
|
|
285
|
+
function isOnCondition(structural) {
|
|
286
|
+
return structural[structural.length-2] === 'elements'
|
|
287
|
+
&& structural[structural.length-1] === 'on';
|
|
282
288
|
}
|
|
283
289
|
|
|
284
290
|
module.exports = ReferenceFlattener;
|
|
@@ -24,7 +24,7 @@ function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember)
|
|
|
24
24
|
forEachDefinition(csn, (def, definitionName) => {
|
|
25
25
|
/** @type {CSN.Path} */
|
|
26
26
|
let root = ['definitions', definitionName];
|
|
27
|
-
forEachMemberRecursively(def, (element, elementName,
|
|
27
|
+
forEachMemberRecursively(def, (element, elementName, structuralNodeName, subpath, parent) => {
|
|
28
28
|
let path = root.concat(subpath);
|
|
29
29
|
// go only through managed associations and compositions
|
|
30
30
|
if (isAssociationOrComposition(element) && element.keys && !element.on) { // check association FKs
|
|
@@ -44,7 +44,7 @@ function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember)
|
|
|
44
44
|
elementDependencies.push(targetElementPath);
|
|
45
45
|
}
|
|
46
46
|
})
|
|
47
|
-
dependencies[path.join("/")] = { definitionName, elementName, element, path, parent, dependencies: elementDependencies };
|
|
47
|
+
dependencies[path.join("/")] = { structuralNodeName, definitionName, elementName, element, path, parent, dependencies: elementDependencies };
|
|
48
48
|
}
|
|
49
49
|
}) // forEachMemberRecursively
|
|
50
50
|
}, { skipArtifact: isExternalServiceMember }) // forEachDefinition
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// The module traverses a given CSN using a specific path, collects structural node names and returns them.
|
|
2
|
+
|
|
3
|
+
const structuralNodeHandlers = {
|
|
4
|
+
definitions: traverseDict,
|
|
5
|
+
elements: traverseDict,
|
|
6
|
+
actions: traverseDict,
|
|
7
|
+
params: traverseDict,
|
|
8
|
+
items: traverseTyped,
|
|
9
|
+
enum: traverseDict,
|
|
10
|
+
returns: traverseTyped,
|
|
11
|
+
on: traverseArray,
|
|
12
|
+
keys: traverseArray,
|
|
13
|
+
ref: traverseArray,
|
|
14
|
+
query: traverseTyped,
|
|
15
|
+
SELECT: traverseTyped,
|
|
16
|
+
SET: traverseTyped,
|
|
17
|
+
args: traverseArray,
|
|
18
|
+
columns: traverseArray,
|
|
19
|
+
projection: traverseTyped,
|
|
20
|
+
from: traverseTyped,
|
|
21
|
+
mixin: traverseDict,
|
|
22
|
+
where: traverseArray,
|
|
23
|
+
orderBy: traverseArray,
|
|
24
|
+
groupBy: traverseArray,
|
|
25
|
+
having: traverseArray,
|
|
26
|
+
xpr: traverseArray,
|
|
27
|
+
expand: traverseArray,
|
|
28
|
+
inline: traverseArray,
|
|
29
|
+
cast: traverseTyped,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function structuralPath(csn, path) {
|
|
33
|
+
return traverseDict(csn.definitions, path, 1, ['definitions']);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function traverseArray(obj, path, index, typeStack) {
|
|
37
|
+
if(!Array.isArray(obj)) return typeStack;
|
|
38
|
+
const name = path[index];
|
|
39
|
+
const element = obj[name];
|
|
40
|
+
return traverseTyped(element, path, index+1, typeStack);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function traverseDict(obj, path, index, typeStack) {
|
|
44
|
+
if(typeof obj !== 'object') return typeStack;
|
|
45
|
+
const name = path[index];
|
|
46
|
+
if(name === undefined) return typeStack;
|
|
47
|
+
return traverseTyped(obj[name], path, index+1, typeStack);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function traverseDictArray(obj, path, index, typeStack) {
|
|
51
|
+
if(typeof obj !== 'object') return typeStack;
|
|
52
|
+
const name = path[index];
|
|
53
|
+
if(name === undefined) return typeStack;
|
|
54
|
+
return traverseArray(obj[name], path, index+1, typeStack);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function traverseTyped(obj, path, index, typeStack) {
|
|
58
|
+
if(!obj) return typeStack;
|
|
59
|
+
const name = path[index];
|
|
60
|
+
if(name === undefined) return typeStack;
|
|
61
|
+
if(name[0] === '@') return typeStack; // skip annotations
|
|
62
|
+
const func = structuralNodeHandlers[name];
|
|
63
|
+
if(func) return func(obj[name], path, index+1, typeStack.concat(name));
|
|
64
|
+
// not typed -> columns?
|
|
65
|
+
if(typeStack[typeStack.length-1] === 'columns')
|
|
66
|
+
return traverseDictArray(obj, path, index, typeStack);
|
|
67
|
+
return typeStack;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
structuralPath
|
|
72
|
+
}
|
|
@@ -74,17 +74,22 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
|
|
|
74
74
|
if (definition.kind !== 'entity' && definition.kind !== 'view')
|
|
75
75
|
return;
|
|
76
76
|
|
|
77
|
-
let { newFlatElements } = flattenStructure(definition, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
77
|
+
let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
78
78
|
|
|
79
79
|
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));
|
|
80
|
-
|
|
81
80
|
definition.elements = newFlatElements;
|
|
81
|
+
|
|
82
|
+
if (definition.params) {
|
|
83
|
+
let { newFlatElements } = flattenStructure(definition.params, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
84
|
+
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('params'));
|
|
85
|
+
definition.params = newFlatElements;
|
|
86
|
+
}
|
|
82
87
|
} // flattenDefinition
|
|
83
88
|
|
|
84
89
|
/**
|
|
85
90
|
* Flattens structured element by calling element flattener for each structured child.
|
|
86
91
|
* Returns a dictionary containing all the new elements for the given structure.
|
|
87
|
-
* @param {*}
|
|
92
|
+
* @param {*} dictionary to flatten
|
|
88
93
|
* @param {CSN.Path} path the path of the structure in the CSN tree
|
|
89
94
|
* @param {*} csnUtils
|
|
90
95
|
* @param {Function} error Error message function
|
|
@@ -93,14 +98,14 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
|
|
|
93
98
|
* @param {*} [newFlatElements]
|
|
94
99
|
* @param {boolean} [isTopLevelElement] states if this is a top level element
|
|
95
100
|
*/
|
|
96
|
-
function flattenStructure(
|
|
101
|
+
function flattenStructure(dictionary, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
|
|
97
102
|
newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
|
|
98
103
|
|
|
99
|
-
if (!isTopLevelElement) addPropsForPropagationFromElement(
|
|
104
|
+
if (!isTopLevelElement) addPropsForPropagationFromElement(dictionary);
|
|
100
105
|
|
|
101
106
|
let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
|
|
102
107
|
|
|
103
|
-
|
|
108
|
+
dictionary && Object.entries(dictionary).forEach(([elementName, element]) => {
|
|
104
109
|
let currPath = path.concat('elements', elementName);
|
|
105
110
|
|
|
106
111
|
if (isTopLevelElement) {
|
|
@@ -118,8 +123,8 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
118
123
|
addPropsForPropagationFromElement(element);
|
|
119
124
|
|
|
120
125
|
// if the child element is structured itself -> needs to be flattened
|
|
121
|
-
const
|
|
122
|
-
let result = flattenStructure(
|
|
126
|
+
const elements = element.elements || csnUtils.getFinalBaseType(element.type).elements;
|
|
127
|
+
let result = flattenStructure(elements, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isNotNull());
|
|
123
128
|
generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements
|
|
124
129
|
|
|
125
130
|
} else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener
|
|
@@ -127,7 +132,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
127
132
|
let elementNameWithDots = elementPathInStructure.concat(elementName).join('.');
|
|
128
133
|
addNewElementToResult(element, newElementName, elementNameWithDots, currPath);
|
|
129
134
|
}
|
|
130
|
-
|
|
131
135
|
});
|
|
132
136
|
|
|
133
137
|
if (referenceFlattener) {
|
|
@@ -135,7 +139,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
135
139
|
}
|
|
136
140
|
return { newFlatElements, generatedNewFlatElementsNames };
|
|
137
141
|
|
|
138
|
-
|
|
139
142
|
// adds newly created element into the final dictionary of elements
|
|
140
143
|
function addNewElementToResult(element, elementName, elementNameWithDots, path) {
|
|
141
144
|
if (newFlatElements[elementName]) {
|
|
@@ -25,7 +25,6 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
25
25
|
// collect in this variable all the newly exposed types
|
|
26
26
|
const exposedStructTypes = [];
|
|
27
27
|
const schemas = Object.create(null);
|
|
28
|
-
|
|
29
28
|
// walk through the definitions of the given CSN and expose types where needed
|
|
30
29
|
forEachDefinition(csn, (def, defName, propertyName, path) => {
|
|
31
30
|
// we do expose types only for definition from inside services
|
|
@@ -33,11 +32,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
33
32
|
if (serviceName) {
|
|
34
33
|
if (['type', 'entity', 'view'].includes(def.kind)) {
|
|
35
34
|
forEachMember(def, (element, elementName, propertyName, path) => {
|
|
36
|
-
if (
|
|
35
|
+
if (['elements', 'params'].includes(propertyName)) {
|
|
37
36
|
const artificialtName = `${isMultiSchema ?
|
|
38
37
|
defNameWithoutServiceOrContextName(defName, serviceName)
|
|
39
38
|
: defNameWithoutServiceOrContextName(defName, serviceName).replace(/\./g, '_')}_${elementName}`;
|
|
40
|
-
exposeTypeOf(element, elementName, defName, serviceName, artificialtName, path);
|
|
39
|
+
exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, artificialtName, path);
|
|
41
40
|
}
|
|
42
41
|
}, path);
|
|
43
42
|
}
|
|
@@ -64,11 +63,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
64
63
|
* @param {string} artificialName
|
|
65
64
|
* @param {CSN.Path} path
|
|
66
65
|
*/
|
|
67
|
-
function exposeTypeOf(node, memberName, defName, service, artificialName, path) {
|
|
66
|
+
function exposeTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
|
|
68
67
|
if (isArrayed(node))
|
|
69
|
-
exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path);
|
|
68
|
+
exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path);
|
|
70
69
|
else if (csnUtils.isStructured(node))
|
|
71
|
-
exposeStructTypeOf(node, memberName, defName, service, artificialName, path);
|
|
70
|
+
exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
@@ -90,12 +89,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
90
89
|
function exposeTypesOfAction(action, actionName, defName, service, path) {
|
|
91
90
|
if (action.returns) {
|
|
92
91
|
const artificialName = `return_${actionName.replace(/\./g, '_')}`;
|
|
93
|
-
exposeTypeOf(action.returns, actionName, defName, service, artificialName, path.concat(['returns']));
|
|
92
|
+
exposeTypeOf(action.returns, false, actionName, defName, service, artificialName, path.concat(['returns']));
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
|
|
97
96
|
const artificialName = `param_${actionName.replace(/\./g, '_')}_${paramName}`;
|
|
98
|
-
exposeTypeOf(param, actionName, defName, service, artificialName, path.concat(['params', paramName]));
|
|
97
|
+
exposeTypeOf(param, false, actionName, defName, service, artificialName, path.concat(['params', paramName]));
|
|
99
98
|
});
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -108,9 +107,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
108
107
|
* @param {String} service
|
|
109
108
|
* @param {String} artificialName
|
|
110
109
|
*/
|
|
111
|
-
function exposeStructTypeOf(node, memberName, defName, service, artificialName, path, parentName) {
|
|
110
|
+
function exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path, parentName) {
|
|
111
|
+
if (node.items) exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path);
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
// start conservative, assume we're in a named type
|
|
114
|
+
let isAnonymous = false;
|
|
114
115
|
|
|
115
116
|
if (isExposableStructure(node)) {
|
|
116
117
|
let typeDef = node.type ? csnUtils.getCsnDef(node.type) : /* structure|anonymous type */ node;
|
|
@@ -124,6 +125,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
124
125
|
// and the new target. Consequently, we now have both type and elements properties in this case, and the elements should be taken as a priority
|
|
125
126
|
// as the correct target is there and no longer in the type definition
|
|
126
127
|
let newTypeElements = (node.type && node.elements) ? node.elements : typeDef.elements;
|
|
128
|
+
// if node and typeDef are identical, we're anonymous
|
|
129
|
+
isAnonymous = node === typeDef;
|
|
130
|
+
// if we've left the anonymous world, we're no longer in a key def
|
|
131
|
+
if (!isAnonymous)
|
|
132
|
+
isKey = false;
|
|
127
133
|
|
|
128
134
|
let newType = exposeStructType(newTypeFullName, newTypeElements, memberName, path);
|
|
129
135
|
if (!newType) {
|
|
@@ -138,6 +144,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
138
144
|
newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
|
|
139
145
|
if (node.elements && node.elements[elemName].$location) setProp(newElem, '$location', node.elements[elemName].$location);
|
|
140
146
|
exposeStructTypeOf(newElem,
|
|
147
|
+
isKey,
|
|
141
148
|
memberName,
|
|
142
149
|
typeDef.kind === 'type' ? node.type : defName,
|
|
143
150
|
service,
|
|
@@ -251,6 +258,9 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
251
258
|
error(null, path, `"${elemName}": Element name conflicts with existing element`);
|
|
252
259
|
}
|
|
253
260
|
let cloned = cloneCsn(element, options);
|
|
261
|
+
// if this was an anonymous sub element of a key, mark it as not nullable
|
|
262
|
+
if(isAnonymous && isKey && !cloned.key && cloned.notNull === undefined)
|
|
263
|
+
cloned.notNull = true;
|
|
254
264
|
type.elements[elemName] = cloned;
|
|
255
265
|
});
|
|
256
266
|
|
|
@@ -265,12 +275,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
265
275
|
|
|
266
276
|
// If a member is of type "array of <named type|anonymous type>", we expose the arrayed type,
|
|
267
277
|
// like we expose structures in structured mode
|
|
268
|
-
function exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path) {
|
|
278
|
+
function exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
|
|
269
279
|
// if anonymously defined in place -> we always expose the type
|
|
270
280
|
// this would be definition like 'elem: array of { ... }'
|
|
271
281
|
// and we use the artificial name for the new type name
|
|
272
282
|
if (node.items && !node.type) {
|
|
273
|
-
exposeStructTypeOf(node.items, memberName, defName, service, artificialName, path.concat('items'));
|
|
283
|
+
exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path.concat('items'));
|
|
274
284
|
}
|
|
275
285
|
// we can have both of the 'type' and 'items' in the cases:
|
|
276
286
|
// 1. 'elem: Foo' and 'type Foo: array of Baz' and 'type Baz: { ... }'
|