@sap/cds-compiler 2.5.2 → 2.11.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 +235 -9
- package/bin/cdsc.js +44 -27
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +37 -3
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +37 -123
- package/lib/api/options.js +27 -15
- package/lib/api/validate.js +34 -9
- package/lib/backends.js +9 -89
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +73 -11
- package/lib/base/messages.js +86 -30
- package/lib/base/model.js +6 -6
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +25 -7
- package/lib/checks/selectItems.js +29 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +23 -7
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +8 -5
- package/lib/compiler/definer.js +157 -133
- package/lib/compiler/index.js +89 -31
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +375 -185
- package/lib/compiler/shared.js +49 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +104 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +388 -146
- package/lib/edm/edmUtils.js +104 -34
- package/lib/gen/Dictionary.json +22 -0
- 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 +5330 -4300
- package/lib/json/from-csn.js +110 -52
- package/lib/json/to-csn.js +434 -120
- package/lib/language/antlrParser.js +15 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +93 -26
- package/lib/language/language.g4 +172 -31
- package/lib/main.d.ts +216 -19
- package/lib/main.js +32 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +413 -149
- package/lib/model/csnUtils.js +286 -75
- package/lib/model/enrichCsn.js +50 -6
- package/lib/model/revealInternalProperties.js +22 -5
- package/lib/modelCompare/compare.js +39 -21
- package/lib/optionProcessor.js +35 -18
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +9 -6
- package/lib/render/toCdl.js +121 -36
- package/lib/render/toHdbcds.js +148 -98
- package/lib/render/toSql.js +114 -43
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +281 -106
- package/lib/transform/db/draft.js +11 -8
- package/lib/transform/db/expansion.js +584 -0
- package/lib/transform/db/flattening.js +341 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +345 -65
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +131 -793
- package/lib/transform/forOdataNew.js +30 -24
- package/lib/transform/localized.js +39 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +144 -78
- package/lib/transform/translateAssocsToJoins.js +22 -27
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -14
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- package/lib/utils/string.js +0 -17
|
@@ -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,10 +22,11 @@ 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
|
-
const { addLocalizationViews
|
|
29
|
+
const { addLocalizationViews } = require('./localized');
|
|
29
30
|
|
|
30
31
|
// Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
|
|
31
32
|
// The result should be suitable for consumption by EDMX processors (annotations and metadata)
|
|
@@ -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,16 +120,17 @@ 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);
|
|
122
125
|
|
|
123
|
-
addLocalizationViews(csn, options);
|
|
124
126
|
const keepLocalizedViews = isDeprecatedEnabled(options, 'createLocalizedViews');
|
|
125
|
-
forEachDefinition(csn, (artifact, artifactName) => {
|
|
126
|
-
if (hasLocalizedConvenienceView(csn, artifactName))
|
|
127
|
-
artifact.$localized = true;
|
|
128
|
-
else if (!keepLocalizedViews && isInLocalizedNamespace(artifactName))
|
|
129
|
-
delete csn.definitions[artifactName];
|
|
130
|
-
}, { skipArtifact: isExternalServiceMember });
|
|
131
127
|
|
|
128
|
+
function acceptLocalizedView(_name, parent) {
|
|
129
|
+
csn.definitions[parent].$localized = true;
|
|
130
|
+
return keepLocalizedViews && !isExternalServiceMember(undefined, parent);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
addLocalizationViews(csn, options, acceptLocalizedView);
|
|
132
134
|
|
|
133
135
|
validate.forOdata(csn, {
|
|
134
136
|
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
|
|
@@ -159,7 +161,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
159
161
|
// and expand these structured elements. This tuple expansion allows all other
|
|
160
162
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
161
163
|
// If errors are detected, throwWithError() will return from further processing
|
|
162
|
-
|
|
164
|
+
expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
|
|
163
165
|
|
|
164
166
|
// handles reference flattening
|
|
165
167
|
let referenceFlattener = new ReferenceFlattener();
|
|
@@ -189,7 +191,11 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
189
191
|
generateForeignKeys(csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember);
|
|
190
192
|
|
|
191
193
|
// Apply default type facets as set by options
|
|
192
|
-
// 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
|
+
*/
|
|
193
199
|
// This must be done before all the draft logic as all
|
|
194
200
|
// composition targets are annotated with @odata.draft.enabled in this step
|
|
195
201
|
forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });
|
|
@@ -227,16 +233,13 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
227
233
|
if (options.toOdata.names && !['service', 'context', 'namespace', 'annotation', 'action', 'function'].includes(def.kind))
|
|
228
234
|
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.toOdata.names, csn);
|
|
229
235
|
|
|
230
|
-
forEachMemberRecursively(def, (member, memberName, propertyName) => {
|
|
236
|
+
forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
|
|
231
237
|
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested
|
|
232
238
|
// Only these are actually required and don't annotate virtual elements in entities or types
|
|
233
239
|
// as they have no DB representation (although in views)
|
|
234
240
|
if (options.toOdata.names && typeof member === 'object' && !['action', 'function'].includes(member.kind) && propertyName !== 'enum' && (!member.virtual || def.query)) {
|
|
235
241
|
// If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
|
|
236
|
-
|
|
237
|
-
memberName = member._flatElementNameWithDots;
|
|
238
|
-
}
|
|
239
|
-
member['@cds.persistence.name'] = getElementDatabaseNameOf(memberName, options.toOdata.names);
|
|
242
|
+
member['@cds.persistence.name'] = getElementDatabaseNameOf(referenceFlattener.getElementNameWithDots(path) || memberName, options.toOdata.names);
|
|
240
243
|
}
|
|
241
244
|
|
|
242
245
|
// Mark fields with @odata.on.insert/update as @Core.Computed
|
|
@@ -331,7 +334,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
331
334
|
renameAnnotation(node, name, '@UI.Importance');
|
|
332
335
|
let annotation = node['@UI.Importance'];
|
|
333
336
|
if (annotation !== null)
|
|
334
|
-
node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' }
|
|
337
|
+
node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' };
|
|
335
338
|
}
|
|
336
339
|
|
|
337
340
|
// Special case: '@readonly' becomes a triplet of capability restrictions for entities,
|
|
@@ -462,11 +465,11 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
462
465
|
}
|
|
463
466
|
// Generate the annotations describing the draft actions (only draft roots can be activated/edited)
|
|
464
467
|
if (artifact == rootArtifact) {
|
|
465
|
-
artifact
|
|
466
|
-
artifact
|
|
467
|
-
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]);
|
|
468
471
|
} else {
|
|
469
|
-
artifact
|
|
472
|
+
resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
|
|
470
473
|
}
|
|
471
474
|
|
|
472
475
|
artifact.elements && Object.values(artifact.elements).forEach( elem => {
|
|
@@ -534,7 +537,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
534
537
|
|
|
535
538
|
// Ignore if that is our own draft root
|
|
536
539
|
if (draftNode != rootArtifact) {
|
|
537
|
-
//
|
|
540
|
+
// Report error when the draft node has @odata.draft.enabled itself
|
|
538
541
|
if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
|
|
539
542
|
error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
|
|
540
543
|
}
|
|
@@ -570,6 +573,9 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
570
573
|
// CDXCORE-481
|
|
571
574
|
// (4.5) If the member is an association whose target has @cds.odata.valuelist annotate it
|
|
572
575
|
// with @Common.ValueList.viaAssociation.
|
|
576
|
+
/*
|
|
577
|
+
FIXME (HJB): Comment outdated: Anno propagation to FKs is done in EdmPreprocessor
|
|
578
|
+
*/
|
|
573
579
|
// This must be done before foreign keys are calculated and the annotations are propagated
|
|
574
580
|
// to them. This will make sure that association and all its foreign keys are annotated with
|
|
575
581
|
// Common.ValueList in the final EDM.
|
|
@@ -5,12 +5,14 @@ const { setProp } = require('../base/model');
|
|
|
5
5
|
const { hasErrors } = require('../base/messages');
|
|
6
6
|
const { cloneCsnDictionary } = require('../model/csnUtils');
|
|
7
7
|
const { cleanSymbols } = require('../base/cleanSymbols.js');
|
|
8
|
+
const { rejectManagedAssociationsAndStructuresForHdbcsNames } = require('../checks/selectItems');
|
|
8
9
|
const {
|
|
9
10
|
cloneCsn,
|
|
10
11
|
forEachDefinition,
|
|
11
12
|
forEachGeneric,
|
|
12
13
|
forAllQueries,
|
|
13
14
|
sortCsnDefinitionsForTests,
|
|
15
|
+
getUtils,
|
|
14
16
|
} = require('../model/csnUtils');
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -34,6 +36,13 @@ const _isViewForEntity = Symbol('_isViewForEntity'); // $inferred = 'LOCALIZED-H
|
|
|
34
36
|
*/
|
|
35
37
|
const _targetFor = Symbol('_targetFor');
|
|
36
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Callback function returning `true` if the localization view should be created.
|
|
41
|
+
* @callback acceptLocalizedView
|
|
42
|
+
* @param {string} viewName localization view name
|
|
43
|
+
* @param {string} originalName Artifact name of the original view
|
|
44
|
+
*/
|
|
45
|
+
|
|
37
46
|
/**
|
|
38
47
|
* Create transitive localized convenience views
|
|
39
48
|
*
|
|
@@ -60,8 +69,9 @@ const _targetFor = Symbol('_targetFor');
|
|
|
60
69
|
* @param {CSN.Options} options
|
|
61
70
|
* @param {boolean} useJoins If true, rewrite the "localized" association to a
|
|
62
71
|
* join in direct convenience views.
|
|
72
|
+
* @param {acceptLocalizedView} [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
|
|
63
73
|
*/
|
|
64
|
-
function _addLocalizationViews(csn, options, useJoins) {
|
|
74
|
+
function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = null) {
|
|
65
75
|
// Don't try to create convenience views with errors.
|
|
66
76
|
if (hasErrors(options.messages))
|
|
67
77
|
return csn;
|
|
@@ -69,7 +79,7 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
69
79
|
if (hasExistingLocalizationViews(csn, options))
|
|
70
80
|
return csn;
|
|
71
81
|
|
|
72
|
-
const { info } = makeMessageFunction(csn, options);
|
|
82
|
+
const { info, error } = makeMessageFunction(csn, options);
|
|
73
83
|
|
|
74
84
|
const noCoalesce = (options.localizedLanguageFallback === 'none' ||
|
|
75
85
|
options.localizedWithoutCoalesce);
|
|
@@ -77,7 +87,16 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
77
87
|
createDirectConvenienceViews(); // 1
|
|
78
88
|
createTransitiveConvenienceViews(); // 2 + 3
|
|
79
89
|
|
|
80
|
-
forEachDefinition(csn,
|
|
90
|
+
forEachDefinition(csn, (definition, artName, prop, path) => {
|
|
91
|
+
cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor)
|
|
92
|
+
if(definition.query) {
|
|
93
|
+
// reject managed association and structure publishing for to-hdbcds.hdbcds
|
|
94
|
+
const that = { csnUtils: getUtils(csn), options, error };
|
|
95
|
+
rejectManagedAssociationsAndStructuresForHdbcsNames.call(that, definition, path)
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
|
|
81
100
|
|
|
82
101
|
sortCsnDefinitionsForTests(csn, options);
|
|
83
102
|
return csn;
|
|
@@ -125,10 +144,16 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
125
144
|
|
|
126
145
|
art[_hasLocalizedView] = viewName;
|
|
127
146
|
|
|
147
|
+
if(acceptLocalizedView && !acceptLocalizedView(viewName, artName))
|
|
148
|
+
return;
|
|
149
|
+
|
|
150
|
+
let view;
|
|
128
151
|
if (art.query || art.projection)
|
|
129
|
-
|
|
152
|
+
view = createLocalizedViewForView(art);
|
|
130
153
|
else
|
|
131
|
-
|
|
154
|
+
view = createLocalizedViewForEntity(art, artName, textElements);
|
|
155
|
+
|
|
156
|
+
csn.definitions[viewName] = view;
|
|
132
157
|
|
|
133
158
|
copyPersistenceAnnotations(csn.definitions[viewName], art);
|
|
134
159
|
}
|
|
@@ -587,9 +612,10 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
587
612
|
*
|
|
588
613
|
* @param {CSN.Model} csn
|
|
589
614
|
* @param {CSN.Options} options
|
|
615
|
+
* @param [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
|
|
590
616
|
*/
|
|
591
|
-
function addLocalizationViews(csn, options) {
|
|
592
|
-
return _addLocalizationViews(csn, options, false);
|
|
617
|
+
function addLocalizationViews(csn, options, acceptLocalizedView = null) {
|
|
618
|
+
return _addLocalizationViews(csn, options, false, acceptLocalizedView);
|
|
593
619
|
}
|
|
594
620
|
|
|
595
621
|
/**
|
|
@@ -599,9 +625,10 @@ function addLocalizationViews(csn, options) {
|
|
|
599
625
|
*
|
|
600
626
|
* @param {CSN.Model} csn
|
|
601
627
|
* @param {CSN.Options} options
|
|
628
|
+
* @param [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
|
|
602
629
|
*/
|
|
603
|
-
function addLocalizationViewsWithJoins(csn, options) {
|
|
604
|
-
return _addLocalizationViews(csn, options, true);
|
|
630
|
+
function addLocalizationViewsWithJoins(csn, options, acceptLocalizedView = null) {
|
|
631
|
+
return _addLocalizationViews(csn, options, true, acceptLocalizedView);
|
|
605
632
|
}
|
|
606
633
|
|
|
607
634
|
/**
|
|
@@ -659,7 +686,7 @@ function copyPersistenceAnnotations(target, source) {
|
|
|
659
686
|
// Do NOT copy ".exists" at the moment. ".exists" is not propagated
|
|
660
687
|
// and this would lead to some localization views referencing not-existing
|
|
661
688
|
// "localized.XYZ" views.
|
|
662
|
-
if (anno
|
|
689
|
+
if (anno === '@cds.persistence.skip')
|
|
663
690
|
target[anno] = source[anno];
|
|
664
691
|
});
|
|
665
692
|
}
|
|
@@ -671,6 +698,8 @@ function copyPersistenceAnnotations(target, source) {
|
|
|
671
698
|
* @param {CSN.Options} options
|
|
672
699
|
*/
|
|
673
700
|
function hasExistingLocalizationViews(csn, options) {
|
|
701
|
+
if (!csn || !csn.definitions)
|
|
702
|
+
return false;
|
|
674
703
|
const firstLocalizedView = Object.keys(csn.definitions).find(isInLocalizedNamespace);
|
|
675
704
|
if (firstLocalizedView) {
|
|
676
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:
|
|
@@ -38,6 +39,7 @@ class ReferenceFlattener {
|
|
|
38
39
|
this.structuredReference = {};
|
|
39
40
|
this.generatedElementsForPath = {};
|
|
40
41
|
this.elementTransitions = {};
|
|
42
|
+
this.elementNamesWithDots = {};
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -72,8 +74,7 @@ class ReferenceFlattener {
|
|
|
72
74
|
});
|
|
73
75
|
if (paths)
|
|
74
76
|
setProp(node, '$paths', paths);
|
|
75
|
-
|
|
76
|
-
// cache also if structured or not
|
|
77
|
+
// cache if structured or not
|
|
77
78
|
let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
|
|
78
79
|
this.structuredReference[path.join('/')] = structured;
|
|
79
80
|
});
|
|
@@ -157,6 +158,14 @@ class ReferenceFlattener {
|
|
|
157
158
|
return this.generatedElementsForPath[path.join('/')];
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
setElementNameWithDots(path, elementNameWithDots) {
|
|
162
|
+
this.elementNamesWithDots[path.join('/')] = elementNameWithDots;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getElementNameWithDots(path) {
|
|
166
|
+
return this.elementNamesWithDots[path.join('/')];
|
|
167
|
+
}
|
|
168
|
+
|
|
160
169
|
/**
|
|
161
170
|
* Generic reference flattener as {@link ReferenceFlattener described}.
|
|
162
171
|
*
|
|
@@ -167,35 +176,40 @@ class ReferenceFlattener {
|
|
|
167
176
|
if (node.$paths) {
|
|
168
177
|
let newRef = []; // flattened reference
|
|
169
178
|
let flattenWithPrevious = false;
|
|
170
|
-
let
|
|
179
|
+
let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
|
|
171
180
|
node.$paths.forEach((path, i) => {
|
|
172
181
|
if (path === undefined || !ref[i]) return;
|
|
173
|
-
let spath = path.join('/')
|
|
182
|
+
let spath = path.join('/');
|
|
174
183
|
let movedTo = this.elementTransitions[spath]; // detect element transition
|
|
175
184
|
let flattened = this.flattenedElementPaths[spath];
|
|
176
185
|
if (flattenWithPrevious) {
|
|
177
|
-
newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + ref[i];
|
|
178
|
-
|
|
186
|
+
newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
|
|
187
|
+
// if we have a filter or args in an assoc, it needs to be kept, therefore
|
|
188
|
+
// the id of the ref is updated with the flattened version
|
|
189
|
+
if (ref[i].id) {
|
|
190
|
+
ref[i].id = newRef[newRef.length - 1];
|
|
191
|
+
newRef[newRef.length - 1] = ref[i];
|
|
192
|
+
}
|
|
193
|
+
lastFlattenedID = i;
|
|
179
194
|
} else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
|
|
180
195
|
let movedToElementName = movedTo[movedTo.length-1];
|
|
181
196
|
newRef.push(movedToElementName);
|
|
182
|
-
|
|
197
|
+
lastFlattenedID = i;
|
|
183
198
|
} else {
|
|
184
199
|
newRef.push(ref[i]);
|
|
185
200
|
}
|
|
186
201
|
flattenWithPrevious = flattened;
|
|
187
202
|
});
|
|
188
|
-
if (newRef !== undefined &&
|
|
203
|
+
if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
|
|
189
204
|
// check if this is a column and add alias if missing
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
192
208
|
node.as = node.ref[node.ref.length - 1];
|
|
193
|
-
}
|
|
194
209
|
}
|
|
195
210
|
setProp(newRef, '$path', path.concat('ref'));
|
|
196
211
|
if (!node.as) {
|
|
197
|
-
|
|
198
|
-
if (ifKeysScope(path))
|
|
212
|
+
if (isPartOfKeysStructure(structural))
|
|
199
213
|
node.as = implicitAs(node.ref);
|
|
200
214
|
else
|
|
201
215
|
setProp(node, 'as', implicitAs(node.ref))
|
|
@@ -208,13 +222,11 @@ class ReferenceFlattener {
|
|
|
208
222
|
|
|
209
223
|
applyAliasesInOnCond(csn, inspectRef) {
|
|
210
224
|
forEachRef(csn, (ref, node, path) => {
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
if
|
|
214
|
-
// the above condition means: only handle references starting with
|
|
215
|
-
// $self or element references outside queries
|
|
216
|
-
|
|
225
|
+
// Process only on-conditions of associations
|
|
226
|
+
let structural = structuralPath(csn, path);
|
|
227
|
+
if(!isOnCondition(structural)) return;
|
|
217
228
|
let { links } = inspectRef(path);
|
|
229
|
+
if(!links) return; // $user not resolvable
|
|
218
230
|
let keysOfPreviousStepWhenManagedAssoc = undefined;
|
|
219
231
|
|
|
220
232
|
let aliasedRef = [...ref];
|
|
@@ -238,32 +250,41 @@ class ReferenceFlattener {
|
|
|
238
250
|
}
|
|
239
251
|
}
|
|
240
252
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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';
|
|
247
263
|
}
|
|
248
264
|
|
|
249
265
|
/**
|
|
250
|
-
* Checks if the provided path is a column inside a select node
|
|
251
|
-
* 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'.
|
|
252
269
|
*
|
|
253
|
-
* @param
|
|
270
|
+
* @param structural structural path to explore
|
|
254
271
|
* @returns {boolean} True if the provided path matched the requirements to be a select node.
|
|
255
272
|
*/
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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';
|
|
267
288
|
}
|
|
268
289
|
|
|
269
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
|
+
}
|