@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
|
@@ -15,17 +15,18 @@ const { csnRefs, pathId, implicitAs } = require('../model/csnRefs');
|
|
|
15
15
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
16
16
|
const validate = require('../checks/validator');
|
|
17
17
|
const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
|
|
18
|
-
const timetrace = require('../utils/timetrace');
|
|
18
|
+
const { timetrace } = require('../utils/timetrace');
|
|
19
19
|
const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
|
|
20
20
|
const { createDict } = require('../utils/objectUtils');
|
|
21
21
|
const handleExists = require('./db/transformExists');
|
|
22
|
-
const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./db/helpers');
|
|
23
22
|
const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
|
|
24
23
|
const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
|
|
25
24
|
const flattening = require('./db/flattening');
|
|
26
25
|
const expansion = require('./db/expansion');
|
|
27
26
|
const assertUnique = require('./db/assertUnique');
|
|
28
27
|
const generateDrafts = require('./db/draft');
|
|
28
|
+
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
29
|
+
const { getViewTransformer } = require('./db/views');
|
|
29
30
|
|
|
30
31
|
// By default: Do not process non-entities/views
|
|
31
32
|
function forEachDefinition(csn, cb) {
|
|
@@ -99,7 +100,6 @@ function forEachDefinition(csn, cb) {
|
|
|
99
100
|
* @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
|
|
100
101
|
*/
|
|
101
102
|
function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
102
|
-
const columnClearer = [];
|
|
103
103
|
// copy the model as we don't want to change the input model
|
|
104
104
|
timetrace.start('HANA transformation');
|
|
105
105
|
/** @type {CSN.Model} */
|
|
@@ -114,13 +114,18 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
114
114
|
let error, warning, info; // message functions
|
|
115
115
|
/** @type {() => void} */
|
|
116
116
|
let throwWithError;
|
|
117
|
-
let artifactRef, inspectRef,
|
|
117
|
+
let artifactRef, inspectRef, effectiveType, // csnRefs
|
|
118
118
|
addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
|
|
119
119
|
get$combined; // csnUtils
|
|
120
120
|
|
|
121
121
|
bindCsnReference();
|
|
122
122
|
|
|
123
123
|
throwWithError(); // reclassify and throw in case of non-configurable errors
|
|
124
|
+
|
|
125
|
+
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
|
|
126
|
+
enrichUniversalCsn(csn, options);
|
|
127
|
+
bindCsnReference();
|
|
128
|
+
}
|
|
124
129
|
|
|
125
130
|
const dialect = options.forHana && options.forHana.dialect || options.toSql && options.toSql.dialect;
|
|
126
131
|
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
|
|
@@ -132,6 +137,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
132
137
|
error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
|
|
133
138
|
});
|
|
134
139
|
|
|
140
|
+
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
|
|
141
|
+
handleExists(csn, options, error);
|
|
142
|
+
|
|
135
143
|
// Check if structured elements and managed associations are compared in an expression
|
|
136
144
|
// and expand these structured elements. This tuple expansion allows all other
|
|
137
145
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
@@ -144,7 +152,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
144
152
|
// FIXME: This does something very similar to cloneWithTransformations -> refactor?
|
|
145
153
|
const transformCsn = transformUtils.transformModel;
|
|
146
154
|
|
|
147
|
-
handleExists(csn, error);
|
|
148
155
|
|
|
149
156
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
150
157
|
// assoc2join eventually rewrites the table aliases
|
|
@@ -325,7 +332,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
325
332
|
// because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
|
|
326
333
|
forEachDefinition(csn, transformSelfInBacklinks);
|
|
327
334
|
|
|
328
|
-
if(
|
|
335
|
+
if(options.forHana){
|
|
329
336
|
/**
|
|
330
337
|
* Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
|
|
331
338
|
* For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
|
|
@@ -337,7 +344,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
337
344
|
if(validOptionsForConstraint())
|
|
338
345
|
createReferentialConstraints(csn, options);
|
|
339
346
|
}
|
|
340
|
-
|
|
347
|
+
// no constraints for drafts
|
|
341
348
|
generateDrafts(csn, options, pathDelimiter, { info, warning, error });
|
|
342
349
|
|
|
343
350
|
// Set the final constraint paths and produce hana tc indexes if required
|
|
@@ -350,6 +357,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
350
357
|
// Apply view-specific transformations
|
|
351
358
|
// (160) Projections now finally become views
|
|
352
359
|
// Replace managed association in group/order by with foreign keys
|
|
360
|
+
const transformEntityOrViewPass2 = getViewTransformer(csn, options, {error, info}, transformCommon);
|
|
353
361
|
forEachDefinition(csn, transformViews);
|
|
354
362
|
|
|
355
363
|
// Recursively apply transformCommon and attach @cds.persistence.name
|
|
@@ -378,8 +386,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
378
386
|
checkConstraintIdentifiers,
|
|
379
387
|
/* (250) Remove all namespaces from definitions */
|
|
380
388
|
removeNamespaces,
|
|
381
|
-
/* (190 b) Replace enum types by their final base type */
|
|
382
|
-
replaceEnumsByBaseTypes,
|
|
383
389
|
/* Check Type Parameters (precision, scale, length ...) */
|
|
384
390
|
checkTypeParameters,
|
|
385
391
|
/* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
|
|
@@ -397,6 +403,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
397
403
|
}
|
|
398
404
|
|
|
399
405
|
const killers = {
|
|
406
|
+
// Used to ignore actions etc from processing and remove associations/elements
|
|
400
407
|
'_ignore': function (parent, a, b, path){
|
|
401
408
|
if(path.length > 2) {
|
|
402
409
|
const tail = path[path.length-1];
|
|
@@ -407,25 +414,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
407
414
|
delete parent._ignore;
|
|
408
415
|
}
|
|
409
416
|
},
|
|
410
|
-
|
|
411
|
-
'_effectiveType': killProp,
|
|
417
|
+
// Still used in flattenStructuredElements - in db/flattening.js
|
|
412
418
|
'_flatElementNameWithDots': killProp,
|
|
413
|
-
|
|
419
|
+
// Set when setting default string/binary length - used in copyTypeProperties and fixBorkedElementsOfLocalized
|
|
420
|
+
// to not copy the .length property if it was only set via default
|
|
414
421
|
'$default': killProp,
|
|
415
|
-
|
|
416
|
-
'$env': killProp,
|
|
417
|
-
'$fksgenerated': killProp,
|
|
418
|
-
'$lateFlattening': killProp,
|
|
419
|
-
'$path': killProp,
|
|
422
|
+
// Set when we turn UUID into String, checked during generateDraftForHana
|
|
420
423
|
'$renamed': killProp,
|
|
421
|
-
|
|
422
|
-
'$
|
|
424
|
+
// Set when we remove .key from temporal things, used in localized.js
|
|
425
|
+
'$key': killProp
|
|
423
426
|
}
|
|
424
427
|
|
|
425
428
|
applyTransformations(csn, killers, [], false);
|
|
426
429
|
|
|
427
430
|
redoProjections.forEach(fn => fn());
|
|
428
|
-
columnClearer.forEach(fn => fn());
|
|
429
431
|
|
|
430
432
|
return csn;
|
|
431
433
|
|
|
@@ -490,14 +492,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
490
492
|
|
|
491
493
|
function bindCsnReference(){
|
|
492
494
|
({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
|
|
493
|
-
({ artifactRef, inspectRef,
|
|
495
|
+
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
|
|
494
496
|
({ getFinalBaseType, get$combined } = getUtils(csn));
|
|
495
497
|
({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
|
|
496
498
|
}
|
|
497
499
|
|
|
498
500
|
function bindCsnReferenceOnly(){
|
|
499
501
|
// invalidate caches for CSN ref API
|
|
500
|
-
({ artifactRef, inspectRef,
|
|
502
|
+
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
|
|
501
503
|
}
|
|
502
504
|
|
|
503
505
|
function handleMixinOnConditions(artifact, artifactName) {
|
|
@@ -613,20 +615,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
613
615
|
}
|
|
614
616
|
}
|
|
615
617
|
|
|
616
|
-
/**
|
|
617
|
-
* @param {CSN.Artifact} artifact
|
|
618
|
-
* @param {string} artifactName
|
|
619
|
-
*/
|
|
620
|
-
function replaceEnumsByBaseTypes(artifact, artifactName) {
|
|
621
|
-
replaceEnumByBaseType(artifact);
|
|
622
|
-
forEachMemberRecursively(artifact, (member) => {
|
|
623
|
-
replaceEnumByBaseType(member);
|
|
624
|
-
if (options.forHana.alwaysResolveDerivedTypes || options.forHana.names === 'plain') {
|
|
625
|
-
toFinalBaseType(member);
|
|
626
|
-
addDefaultTypeFacets(member);
|
|
627
|
-
}
|
|
628
|
-
}, [ 'definitions', artifactName ]);
|
|
629
|
-
}
|
|
630
618
|
|
|
631
619
|
/**
|
|
632
620
|
* @param {CSN.Artifact} artifact
|
|
@@ -704,6 +692,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
704
692
|
function handleAssociations(artifact, artifactName) {
|
|
705
693
|
// Do things specific for entities and views (pass 1)
|
|
706
694
|
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
695
|
+
const alreadyHandled = new WeakMap();
|
|
707
696
|
forAllElements(artifact, artifactName, (parent, elements) => {
|
|
708
697
|
for (const elemName in elements) {
|
|
709
698
|
const elem = elements[elemName];
|
|
@@ -711,7 +700,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
711
700
|
// (unless explicitly asked to keep assocs unchanged)
|
|
712
701
|
if (doA2J) {
|
|
713
702
|
if (isManagedAssociationElement(elem))
|
|
714
|
-
transformManagedAssociation(parent, artifactName, elem, elemName);
|
|
703
|
+
transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
|
|
715
704
|
}
|
|
716
705
|
}
|
|
717
706
|
})
|
|
@@ -837,30 +826,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
837
826
|
if (art.technicalConfig)
|
|
838
827
|
newCsn.definitions[artName].technicalConfig = art.technicalConfig;
|
|
839
828
|
|
|
840
|
-
const newArt = newCsn.definitions[artName];
|
|
841
|
-
|
|
842
|
-
// No need to loop/check artifacts that won't reach the DB anyways
|
|
843
|
-
if (art.query && newArt && newArt.query && isPersistedOnDatabase(newArt)) {
|
|
844
|
-
// Loop through the newCSN and add possible new _ignore mixin to the kill list
|
|
845
|
-
forAllQueries(newArt.query, (q, p) => {
|
|
846
|
-
if (q.SELECT && q.SELECT.mixin) {
|
|
847
|
-
for(let mixinName of Object.keys(q.SELECT.mixin)) {
|
|
848
|
-
const mixinElement = q.SELECT.mixin[mixinName];
|
|
849
|
-
if (mixinElement._ignore && options.toSql) {
|
|
850
|
-
columnClearer.push(() => {
|
|
851
|
-
const query = walkCsnPath(csn, p);
|
|
852
|
-
for(let i = query.columns.length-1; i > -1; i--){
|
|
853
|
-
const col = query.columns[i];
|
|
854
|
-
if(col && col.ref && col.ref[0] === mixinName){
|
|
855
|
-
query.columns.splice(i, 1);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
}, ['definitions', artName, 'query']);
|
|
863
|
-
}
|
|
864
829
|
});
|
|
865
830
|
csn = newCsn;
|
|
866
831
|
}
|
|
@@ -1046,10 +1011,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1046
1011
|
if (isPersistedOnDatabase(artifact)) {
|
|
1047
1012
|
// TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
|
|
1048
1013
|
if (artifact.query) {
|
|
1049
|
-
|
|
1014
|
+
// If we do A2J, we don't need to check the mixin. Either it is used -> a join
|
|
1015
|
+
// or published -> handled via elements/members. Unused mixins are removed anyway.
|
|
1016
|
+
if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
1050
1017
|
forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
|
|
1051
1018
|
|
|
1052
|
-
else if (artifact.query.SET && artifact.query.SET.mixin)
|
|
1019
|
+
else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
|
|
1053
1020
|
forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
|
|
1054
1021
|
}
|
|
1055
1022
|
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
@@ -1090,6 +1057,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1090
1057
|
|
|
1091
1058
|
// (190 a) Replace enum symbols by their value (if found)
|
|
1092
1059
|
replaceEnumSymbolsByValues(obj, path);
|
|
1060
|
+
|
|
1061
|
+
if (obj.enum)
|
|
1062
|
+
delete obj.enum;
|
|
1093
1063
|
}
|
|
1094
1064
|
|
|
1095
1065
|
// Change the names of those builtin types that have different names in HANA.
|
|
@@ -1123,309 +1093,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1123
1093
|
// }
|
|
1124
1094
|
// }
|
|
1125
1095
|
|
|
1126
|
-
/**
|
|
1127
|
-
* Strip of leading $self of the ref
|
|
1128
|
-
* @param {object} col A column
|
|
1129
|
-
*
|
|
1130
|
-
* @returns {object}
|
|
1131
|
-
*/
|
|
1132
|
-
function stripLeadingSelf(col) {
|
|
1133
|
-
if (col.ref && col.ref.length > 1 && col.ref[0] === '$self')
|
|
1134
|
-
col.ref = col.ref.slice(1);
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
return col;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
function isUnion(path){
|
|
1141
|
-
const subquery = path[path.length-1];
|
|
1142
|
-
const queryIndex = path[path.length-2]
|
|
1143
|
-
const args = path[path.length-3];
|
|
1144
|
-
const unionOperator = path[path.length-4];
|
|
1145
|
-
return path.length > 3 && (subquery === 'SET' || subquery === 'SELECT') && typeof queryIndex === 'number' && queryIndex >= 0 && args === 'args' && unionOperator === 'SET';
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
function transformEntityOrViewPass2(query, artifact, artName, path) {
|
|
1149
|
-
const { elements } = queryOrMain(query, artifact);
|
|
1150
|
-
let hasNonAssocElements = false;
|
|
1151
|
-
const isSelect = query && query.SELECT;
|
|
1152
|
-
let isProjection = !!artifact.projection;
|
|
1153
|
-
const columnMap = Object.create(null);
|
|
1154
|
-
let isSelectStar = false;
|
|
1155
|
-
if (isSelect) {
|
|
1156
|
-
if (!query.SELECT.columns) {
|
|
1157
|
-
isProjection = true;
|
|
1158
|
-
}
|
|
1159
|
-
else {
|
|
1160
|
-
query.SELECT.columns.forEach((col) => {
|
|
1161
|
-
if (col === '*') {
|
|
1162
|
-
isSelectStar = true;
|
|
1163
|
-
}
|
|
1164
|
-
else if (col.as) {
|
|
1165
|
-
if (!columnMap[col.as])
|
|
1166
|
-
columnMap[col.as] = col;
|
|
1167
|
-
}
|
|
1168
|
-
else if (col.ref) {
|
|
1169
|
-
if (!columnMap[col.ref[col.ref.length - 1]])
|
|
1170
|
-
columnMap[col.ref[col.ref.length - 1]] = col;
|
|
1171
|
-
}
|
|
1172
|
-
else if (col.func) {
|
|
1173
|
-
columnMap[col.func] = col;
|
|
1174
|
-
}
|
|
1175
|
-
else if (!columnMap[col]) {
|
|
1176
|
-
columnMap[col] = col;
|
|
1177
|
-
}
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
if (query && options.transformation === 'hdbcds') {
|
|
1182
|
-
// check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
|
|
1183
|
-
if (query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1) {
|
|
1184
|
-
for (const elementName in elements) {
|
|
1185
|
-
const element = elements[elementName];
|
|
1186
|
-
if (element.target) {
|
|
1187
|
-
let colLocation;
|
|
1188
|
-
for (let i = 0; i < query.SELECT.columns.length; i++) {
|
|
1189
|
-
const col = query.SELECT.columns[i];
|
|
1190
|
-
if (col.ref && col.ref.length === 1) {
|
|
1191
|
-
if (!colLocation && col.ref[0] === elementName)
|
|
1192
|
-
colLocation = i;
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
if (col.as === elementName)
|
|
1196
|
-
colLocation = i;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
if (colLocation) {
|
|
1200
|
-
const matchingCol = query.SELECT.columns[colLocation];
|
|
1201
|
-
const possibleMixinName = matchingCol.ref[0];
|
|
1202
|
-
const isMixin = query.SELECT.mixin[possibleMixinName] !== undefined;
|
|
1203
|
-
if (element.target && isMixin)
|
|
1204
|
-
error(null, path.concat([ 'columns', colLocation ]),
|
|
1205
|
-
`Element "${ elementName }" is a mixin association${ possibleMixinName !== elementName ? ` ("${ possibleMixinName }")` : '' } and can't be published in a UNION`);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
// Second walk through the entity elements: Deal with associations (might also result in new elements)
|
|
1213
|
-
|
|
1214
|
-
// Will be initialized JIT inside the elements-loop
|
|
1215
|
-
let $combined;
|
|
1216
|
-
|
|
1217
|
-
for (const elemName in elements) {
|
|
1218
|
-
const elem = elements[elemName];
|
|
1219
|
-
if (isSelect) {
|
|
1220
|
-
if (!columnMap[elemName]) {
|
|
1221
|
-
// Prepend an alias if present
|
|
1222
|
-
let alias = (isProjection || isSelectStar) &&
|
|
1223
|
-
(query.SELECT.from.as || (query.SELECT.from.ref && implicitAs(query.SELECT.from.ref)));
|
|
1224
|
-
// In case of * and no explicit alias
|
|
1225
|
-
// find the source of the col by looking at $combined and prepend it
|
|
1226
|
-
if (isSelectStar && !alias && !isProjection) {
|
|
1227
|
-
if (!$combined)
|
|
1228
|
-
$combined = get$combined(query);
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const matchingCombined = $combined[elemName];
|
|
1232
|
-
// Internal errors - this should never happen!
|
|
1233
|
-
if (matchingCombined.length > 1) { // should already be caught by compiler
|
|
1234
|
-
throw new Error(`Ambiguous name - can't be resolved: ${ elemName }. Found in: ${ matchingCombined.map(o => o.parent) }`);
|
|
1235
|
-
}
|
|
1236
|
-
else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
|
|
1237
|
-
throw new Error(`No matching entry found in UNION of all elements for: ${ elemName }`);
|
|
1238
|
-
}
|
|
1239
|
-
alias = matchingCombined[0].parent;
|
|
1240
|
-
}
|
|
1241
|
-
if (alias)
|
|
1242
|
-
columnMap[elemName] = { ref: [ alias, elemName ] };
|
|
1243
|
-
else
|
|
1244
|
-
columnMap[elemName] = { ref: [ elemName ] };
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
// For associations - make sure that the foreign keys have the same "style"
|
|
1248
|
-
// If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
|
|
1249
|
-
if (elem.keys && doA2J) {
|
|
1250
|
-
const assoc_col = columnMap[elemName];
|
|
1251
|
-
if (assoc_col && assoc_col.ref) {
|
|
1252
|
-
elem.keys.forEach((key) => {
|
|
1253
|
-
const ref = cloneCsn(assoc_col.ref, options);
|
|
1254
|
-
ref[ref.length - 1] = [ ref[ref.length - 1] ].concat(key.as || key.ref).join(pathDelimiter);
|
|
1255
|
-
const result = {
|
|
1256
|
-
ref,
|
|
1257
|
-
};
|
|
1258
|
-
if (assoc_col.as)
|
|
1259
|
-
result.as = key.$generatedFieldName;
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
if (assoc_col.key)
|
|
1263
|
-
result.key = true;
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
const colName = result.as || ref[ref.length - 1];
|
|
1267
|
-
columnMap[colName] = result;
|
|
1268
|
-
});
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
// Add flattened structured things preserving aliases and refs with/without table alias
|
|
1272
|
-
// If we add them when we get to them in "elements", we cannot know what table alias was used...
|
|
1273
|
-
if (isStructured(elem) && doA2J) {
|
|
1274
|
-
const col = columnMap[elemName];
|
|
1275
|
-
const originalName = col.ref[col.ref.length - 1];
|
|
1276
|
-
const flatElements = flattenStructuredElement(elem, originalName, [], path);
|
|
1277
|
-
const aliasedFlatElements = originalName !== elemName ? Object.keys(flattenStructuredElement(elem, elemName, [], path)) : [];
|
|
1278
|
-
|
|
1279
|
-
Object.keys(flatElements).forEach((flatElemName, index ) => {
|
|
1280
|
-
const clone = cloneCsn(col, options);
|
|
1281
|
-
// For the ref, use the "original"
|
|
1282
|
-
if (clone.ref)
|
|
1283
|
-
clone.ref[clone.ref.length - 1] = flatElemName;
|
|
1284
|
-
|
|
1285
|
-
// If the column was aliased, use the alias-prefix for the flattened element
|
|
1286
|
-
if (originalName !== elemName)
|
|
1287
|
-
clone.as = aliasedFlatElements[index];
|
|
1288
|
-
|
|
1289
|
-
// Insert into map, giving precedence to the alias
|
|
1290
|
-
columnMap[clone.as || flatElemName] = clone;
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
// Views must have at least one element that is not an unmanaged assoc
|
|
1295
|
-
if (!elem.on && !elem._ignore)
|
|
1296
|
-
hasNonAssocElements = true;
|
|
1297
|
-
|
|
1298
|
-
// (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
|
|
1299
|
-
// CDXCORE-585: Allow mixin associations to be used and published in parallel
|
|
1300
|
-
if (query !== undefined && elem.target) {
|
|
1301
|
-
if(isUnion(path) && options.transformation === 'hdbcds'){
|
|
1302
|
-
if(isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J){
|
|
1303
|
-
if(elem.keys) {
|
|
1304
|
-
info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`)
|
|
1305
|
-
} else {
|
|
1306
|
-
info(null, path, `Association "${elemName}", published in a UNION, will be ignored`)
|
|
1307
|
-
}
|
|
1308
|
-
elem._ignore = true;
|
|
1309
|
-
}
|
|
1310
|
-
else {
|
|
1311
|
-
error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`)
|
|
1312
|
-
}
|
|
1313
|
-
} else if(path.length > 4 && options.transformation === 'hdbcds'){ // path.length > 4 -> is a subquery
|
|
1314
|
-
error(null, path, { name: elemName },
|
|
1315
|
-
'Association $(NAME) can\'t be published in a subquery')
|
|
1316
|
-
} else {
|
|
1317
|
-
/* Old implementation:
|
|
1318
|
-
const isNotMixinByItself = !(elem.value && elem.value.path && elem.value.path.length == 1 && art.query && art.query.mixin && art.query.mixin[elem.value.path[0].id]);
|
|
1319
|
-
*/
|
|
1320
|
-
const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elem, elemName);
|
|
1321
|
-
const {mixinElement, mixinName } = getMixinAssocOfQueryIfPublished(query, elem, elemName);
|
|
1322
|
-
if (isNotMixinByItself || mixinElement !== undefined) {
|
|
1323
|
-
// If the mixin is only published and not used, only display the __ clone. Ignore the "original".
|
|
1324
|
-
if (mixinElement !== undefined && !usesMixinAssociation(query, elem, elemName)){
|
|
1325
|
-
mixinElement._ignore = true;
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
delete elem._typeIsExplicit;
|
|
1329
|
-
// Create an unused alias name for the MIXIN - use 3 _ to avoid collision with usings
|
|
1330
|
-
let mixinElemName = `___${ mixinName || elemName }`;
|
|
1331
|
-
while (elements[mixinElemName])
|
|
1332
|
-
mixinElemName = `_${ mixinElemName }`;
|
|
1333
|
-
|
|
1334
|
-
// Copy the association element to the MIXIN clause under its alias name
|
|
1335
|
-
// (shallow copy is sufficient, just fix name and value)
|
|
1336
|
-
const mixinElem = Object.assign({}, elem);
|
|
1337
|
-
// Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)
|
|
1338
|
-
transformCommon(mixinElem, mixinElemName);
|
|
1339
|
-
// TODO: Can we rely on query.SELECT.mixin to check for mixins?
|
|
1340
|
-
// Yes, we can - only SELECT can have mixin. But:
|
|
1341
|
-
// - UNION
|
|
1342
|
-
// - JOINS
|
|
1343
|
-
// - Subqueries
|
|
1344
|
-
// Are currently (and in the old transformer) not handled!
|
|
1345
|
-
if (query.SELECT && !query.SELECT.mixin)
|
|
1346
|
-
query.SELECT.mixin = Object.create(null);
|
|
1347
|
-
|
|
1348
|
-
// Let the original association element use the newly generated MIXIN name as value and alias
|
|
1349
|
-
delete elem.viaAll;
|
|
1350
|
-
|
|
1351
|
-
// Clone 'on'-condition, pre-pending '$projection' to paths where appropriate,
|
|
1352
|
-
// and fixing the association alias just created
|
|
1353
|
-
|
|
1354
|
-
if (mixinElem.on) {
|
|
1355
|
-
mixinElem.on = cloneWithTransformations(mixinElem.on, {
|
|
1356
|
-
ref: (ref) => {
|
|
1357
|
-
// Clone the path, without any transformations
|
|
1358
|
-
const clonedPath = cloneWithTransformations(ref, {});
|
|
1359
|
-
// Prepend '$projection' to the path, unless the first path step is the (mixin) element itself or starts with '$')
|
|
1360
|
-
if (clonedPath[0] == elemName) {
|
|
1361
|
-
clonedPath[0] = mixinElemName;
|
|
1362
|
-
}
|
|
1363
|
-
else if (!(clonedPath[0] && clonedPath[0].startsWith('$'))) {
|
|
1364
|
-
const projectionId = '$projection';
|
|
1365
|
-
clonedPath.unshift(projectionId);
|
|
1366
|
-
}
|
|
1367
|
-
return clonedPath;
|
|
1368
|
-
},
|
|
1369
|
-
func: (func) => {
|
|
1370
|
-
// Unfortunately, function names are disguised as paths, so we would prepend a '$projection'
|
|
1371
|
-
// above (no way to distinguish that in the callback for 'path' above). We can only pluck it
|
|
1372
|
-
// off again here ... sigh
|
|
1373
|
-
if (func.ref && func.ref[0] && func.ref[0] === '$projection')
|
|
1374
|
-
func.ref = func.ref.slice(1);
|
|
1375
|
-
|
|
1376
|
-
return func;
|
|
1377
|
-
},
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
if (!mixinElem._ignore)
|
|
1382
|
-
columnMap[elemName] = { ref: [ mixinElemName ], as: elemName };
|
|
1383
|
-
|
|
1384
|
-
if (query.SELECT) {
|
|
1385
|
-
query.SELECT.mixin[mixinElemName] = mixinElem;
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
if (query && !hasNonAssocElements) {
|
|
1393
|
-
// Complain if there are no elements other than unmanaged associations
|
|
1394
|
-
// Allow with plain
|
|
1395
|
-
error(null, [ 'definitions', artName ], { $reviewed: true } ,
|
|
1396
|
-
'Expecting view or projection to have at least one element that is not an unmanaged association');
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
if (isSelect) {
|
|
1400
|
-
// Workaround for bugzilla 176495 FIXME FIXME FIXME: is this really still needed?
|
|
1401
|
-
// If a select item of a cdx view contains an expression, the result type cannot be computed
|
|
1402
|
-
// but must be explicitly specified. This is important for the OData channel, which doesn't
|
|
1403
|
-
// work if the type is missing (for HANA channel an explicit type is not required, as HANA CDS
|
|
1404
|
-
// can compute the result type).
|
|
1405
|
-
// Due to bug in HANA CDS, providing explicit type 'LargeString' or 'LargeBinary' causes a
|
|
1406
|
-
// diserver crash. Until a fix in HANA CDS is available, we allow to suppress the explicit
|
|
1407
|
-
// type in the HANA channel via an annotation.
|
|
1408
|
-
Object.keys(columnMap).forEach((value) => {
|
|
1409
|
-
const elem = elements[value];
|
|
1410
|
-
if (elem && elem['@cds.workaround.noExplicitTypeForHANA'])
|
|
1411
|
-
delete columnMap[value].cast;
|
|
1412
|
-
});
|
|
1413
|
-
|
|
1414
|
-
query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
|
|
1415
|
-
// If following an association, explicitly set the implicit alias
|
|
1416
|
-
// due to an issue with HANA
|
|
1417
|
-
for (let i = 0; i < query.SELECT.columns.length; i++) {
|
|
1418
|
-
const col = query.SELECT.columns[i];
|
|
1419
|
-
if (!col.as && col.ref && col.ref.length > 1) {
|
|
1420
|
-
const { links } = inspectRef(path.concat([ 'columns', i ]));
|
|
1421
|
-
if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
|
|
1422
|
-
col.as = col.ref[col.ref.length - 1];
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
delete query.SELECT.excluding; // just to make the output of the new transformer the same as the old
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
1096
|
|
|
1430
1097
|
// If 'elem' has a default that is an enum constant, replace that by its value. Complain
|
|
1431
1098
|
// if not found or not an enum type,
|
|
@@ -1442,7 +1109,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1442
1109
|
// Looks like it is always run?! But message says HANA CDS?!
|
|
1443
1110
|
error(null, path, {
|
|
1444
1111
|
$reviewed: true,
|
|
1445
|
-
name: `#${elem.default['#']}`
|
|
1112
|
+
name: `#${ elem.default['#'] }`
|
|
1446
1113
|
},
|
|
1447
1114
|
'Expecting enum literal $(NAME) to be used with an enum type');
|
|
1448
1115
|
}
|
|
@@ -1452,7 +1119,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1452
1119
|
if (!enumSymbol) {
|
|
1453
1120
|
error(null, path, {
|
|
1454
1121
|
$reviewed: true,
|
|
1455
|
-
name: `#${elem.default['#']}`
|
|
1122
|
+
name: `#${ elem.default['#'] }`
|
|
1456
1123
|
}, 'Enum literal $(NAME) is undefined in enumeration type');
|
|
1457
1124
|
}
|
|
1458
1125
|
else if (enumSymbol.val !== undefined) { // `val` may be `null`
|
|
@@ -1469,30 +1136,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1469
1136
|
}
|
|
1470
1137
|
}
|
|
1471
1138
|
|
|
1472
|
-
// If 'node' has an enum type, change node's type to be the enum's base type
|
|
1473
|
-
// and strip off the 'enum' property.
|
|
1474
|
-
function replaceEnumByBaseType(node) {
|
|
1475
|
-
if (node.items)
|
|
1476
|
-
replaceEnumByBaseType(node.items);
|
|
1477
|
-
|
|
1478
|
-
// (190 b) Replace enum types by their final base type (must happen after 190 a)
|
|
1479
|
-
/* Old implementation:
|
|
1480
|
-
if (node && node._finalType && (node.enum || node._finalType.enum)) {
|
|
1481
|
-
node.type = node._finalType.type
|
|
1482
|
-
// node.type = node._finalType.type._artifact._finalType.type;
|
|
1483
|
-
if (node._finalType.length) {
|
|
1484
|
-
node.length = node._finalType.length;
|
|
1485
|
-
}
|
|
1486
|
-
setProp(node, '_finalType', node.type._artifact);
|
|
1487
|
-
delete node.enum;
|
|
1488
|
-
}
|
|
1489
|
-
*/
|
|
1490
|
-
if (node && node.enum) {
|
|
1491
|
-
// toFinalBaseType(node);
|
|
1492
|
-
// addDefaultTypeFacets(node);
|
|
1493
|
-
delete node.enum;
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
1139
|
|
|
1497
1140
|
// If the association element 'elem' of 'art' is a backlink association, massage its ON-condition
|
|
1498
1141
|
// (in place) so that it
|
|
@@ -1532,14 +1175,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1532
1175
|
if (multipleExprs)
|
|
1533
1176
|
result.push(')');
|
|
1534
1177
|
i += 3;
|
|
1535
|
-
|
|
1536
|
-
if(elem.$selfOnCondition)
|
|
1537
|
-
elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
|
|
1538
|
-
else {
|
|
1539
|
-
setProp(elem, '$selfOnCondition', {
|
|
1540
|
-
backlinkName
|
|
1541
|
-
}) // important for the foreign key constraints
|
|
1542
|
-
}
|
|
1178
|
+
attachBacklinkInformation(backlinkName);
|
|
1543
1179
|
}
|
|
1544
1180
|
else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
|
|
1545
1181
|
const assoc = inspectRef(path.concat([ i ])).art;
|
|
@@ -1550,14 +1186,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1550
1186
|
if (multipleExprs)
|
|
1551
1187
|
result.push(')');
|
|
1552
1188
|
i += 3;
|
|
1553
|
-
|
|
1554
|
-
if(elem.$selfOnCondition)
|
|
1555
|
-
elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
|
|
1556
|
-
else {
|
|
1557
|
-
setProp(elem, '$selfOnCondition', {
|
|
1558
|
-
backlinkName
|
|
1559
|
-
}) // important for the foreign key constraints
|
|
1560
|
-
}
|
|
1189
|
+
attachBacklinkInformation(backlinkName);
|
|
1561
1190
|
}
|
|
1562
1191
|
// Otherwise take one (!) token unchanged
|
|
1563
1192
|
else {
|
|
@@ -1577,6 +1206,24 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1577
1206
|
}
|
|
1578
1207
|
}
|
|
1579
1208
|
return result;
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* The knowledge whether an association was an `<up_>` association in a
|
|
1212
|
+
* `$self = <comp>.<up_>` comparison, is important for the foreign key constraints.
|
|
1213
|
+
* By the time we generate them, such on-conditions are already transformed
|
|
1214
|
+
* --> no more `$self` in the on-conditions, that is why we need to remember it here.
|
|
1215
|
+
*
|
|
1216
|
+
* @param {string} backlinkName name of `<up_>` in a `$self = <comp>.<up_>` comparison
|
|
1217
|
+
*/
|
|
1218
|
+
function attachBacklinkInformation(backlinkName) {
|
|
1219
|
+
if (elem.$selfOnCondition)
|
|
1220
|
+
elem.$selfOnCondition.up_.push(backlinkName);
|
|
1221
|
+
else {
|
|
1222
|
+
setProp(elem, '$selfOnCondition', {
|
|
1223
|
+
up_: [backlinkName]
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1580
1227
|
}
|
|
1581
1228
|
|
|
1582
1229
|
elem.on = processExpressionArgs(elem.on, pathToOn);
|
|
@@ -1690,26 +1337,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1690
1337
|
}
|
|
1691
1338
|
}
|
|
1692
1339
|
|
|
1693
|
-
/**
|
|
1694
|
-
* @todo: XSN - Implementation most likely too naive, can we rely on query.SELECT.mixin?
|
|
1695
|
-
*
|
|
1696
|
-
* @param {CSN.Query} query
|
|
1697
|
-
* @param {object} columnMap
|
|
1698
|
-
* @param {CSN.Artifact} columnMap
|
|
1699
|
-
* @param {string} elementName
|
|
1700
|
-
*/
|
|
1701
|
-
function checkIsNotMixinByItself(query, columnMap, element, elementName) {
|
|
1702
|
-
if (query && query.SELECT && query.SELECT.mixin) {
|
|
1703
|
-
const col = columnMap[elementName];
|
|
1704
|
-
|
|
1705
|
-
const realName = col.ref[col.ref.length - 1];
|
|
1706
|
-
// If the element is not part of the mixin => True
|
|
1707
|
-
return query.SELECT.mixin[realName] == undefined;
|
|
1708
|
-
}
|
|
1709
|
-
// the artifact does not define any mixins, the element cannot be a mixin
|
|
1710
|
-
return true;
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
1340
|
/**
|
|
1714
1341
|
* @param {CSN.Artifact} artifact
|
|
1715
1342
|
* @param {string} artifactName
|
|
@@ -2044,11 +1671,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2044
1671
|
const source = findSource(links, i - 1) || artifact;
|
|
2045
1672
|
// allow specifying managed assoc on the source side
|
|
2046
1673
|
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
|
|
2047
|
-
|
|
2048
1674
|
if(fks && fks.length >= 1){
|
|
2049
1675
|
const fk = fks[0];
|
|
2050
|
-
|
|
2051
|
-
|
|
1676
|
+
const managedAssocStepName = refOwner.ref[i];
|
|
1677
|
+
const fkName = `${ managedAssocStepName }${ pathDelimiter }${ fk.as }`;
|
|
1678
|
+
if(source && source.elements[fkName])
|
|
1679
|
+
refOwner.ref = [ ...ref.slice(0, i), fkName ];
|
|
2052
1680
|
}
|
|
2053
1681
|
}
|
|
2054
1682
|
}
|
|
@@ -2087,12 +1715,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2087
1715
|
* @param {string} artifactName
|
|
2088
1716
|
* @param {Object} elem The association to process
|
|
2089
1717
|
* @param {string} elemName
|
|
1718
|
+
* @param {WeakMap} alreadyHandled To cache which elements were already processed
|
|
2090
1719
|
* @returns {void}
|
|
2091
1720
|
*/
|
|
2092
|
-
function transformManagedAssociation(artifact, artifactName, elem, elemName) {
|
|
1721
|
+
function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
|
|
2093
1722
|
// No need to run over this - we already did, possibly because it was referenced in the ON-Condition
|
|
2094
1723
|
// of another association - see a few lines lower
|
|
2095
|
-
if (elem
|
|
1724
|
+
if (alreadyHandled.has(elem))
|
|
2096
1725
|
return;
|
|
2097
1726
|
// Generate foreign key elements for managed associations, and assemble an ON-condition with them
|
|
2098
1727
|
const onCondParts = [];
|
|
@@ -2109,10 +1738,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2109
1738
|
elemName,
|
|
2110
1739
|
].concat(foreignKey.ref),
|
|
2111
1740
|
};
|
|
2112
|
-
|
|
1741
|
+
const fkName = `${ elemName }${ pathDelimiter }${ foreignKey.as }`;
|
|
2113
1742
|
const fKeyArg = {
|
|
2114
1743
|
ref: [
|
|
2115
|
-
|
|
1744
|
+
fkName,
|
|
2116
1745
|
],
|
|
2117
1746
|
};
|
|
2118
1747
|
|
|
@@ -2151,7 +1780,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2151
1780
|
elem.implicitForeignKeys = true;
|
|
2152
1781
|
*/
|
|
2153
1782
|
// Remember that we already processed this
|
|
2154
|
-
|
|
1783
|
+
alreadyHandled.set(elem, true);
|
|
2155
1784
|
}
|
|
2156
1785
|
}
|
|
2157
1786
|
|