@sap/cds-compiler 2.10.2 → 2.11.4
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 +90 -5
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +3 -1
- package/bin/cdsc.js +49 -25
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_BETA.md +10 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +8 -36
- package/lib/api/options.js +15 -6
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +34 -10
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +5 -4
- package/lib/base/optionProcessorHelper.js +57 -23
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/compiler/assert-consistency.js +9 -2
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +2 -1
- package/lib/compiler/definer.js +66 -108
- package/lib/compiler/index.js +29 -29
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +225 -58
- package/lib/compiler/shared.js +53 -229
- package/lib/compiler/utils.js +184 -0
- package/lib/edm/annotations/genericTranslation.js +1 -1
- package/lib/edm/csn2edm.js +3 -2
- package/lib/edm/edmPreprocessor.js +34 -38
- package/lib/edm/edmUtils.js +3 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +17 -1
- package/lib/gen/language.tokens +79 -73
- package/lib/gen/languageLexer.interp +19 -1
- package/lib/gen/languageLexer.js +779 -731
- package/lib/gen/languageLexer.tokens +71 -65
- package/lib/gen/languageParser.js +4668 -4072
- package/lib/json/from-csn.js +10 -10
- package/lib/json/to-csn.js +228 -47
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +73 -14
- package/lib/language/language.g4 +79 -3
- package/lib/main.d.ts +215 -18
- package/lib/main.js +3 -1
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +117 -33
- package/lib/model/csnUtils.js +65 -133
- package/lib/model/enrichCsn.js +62 -37
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +15 -8
- package/lib/render/toHdbcds.js +26 -49
- package/lib/render/toSql.js +61 -39
- package/lib/render/utils/common.js +1 -1
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/constraints.js +273 -119
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +6 -4
- package/lib/transform/db/flattening.js +19 -3
- package/lib/transform/db/transformExists.js +102 -9
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +93 -448
- package/lib/transform/forOdataNew.js +9 -2
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/structuralPath.js +1 -5
- package/lib/transform/transformUtilsNew.js +22 -8
- package/lib/transform/translateAssocsToJoins.js +7 -15
- package/lib/utils/file.js +11 -5
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
4
|
const { getUtils, cloneCsn, forEachGeneric,
|
|
5
5
|
forEachMember,
|
|
6
|
-
forEachMemberRecursively, forEachRef,
|
|
6
|
+
forEachMemberRecursively, forEachRef, getNamespace, getResultingName,
|
|
7
7
|
forAllQueries, forAllElements, hasAnnotationValue, getArtifactDatabaseNameOf,
|
|
8
8
|
getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
9
9
|
isPersistedOnDatabase, getNormalizedQuery, isAspect, walkCsnPath,
|
|
@@ -15,11 +15,10 @@ 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');
|
|
@@ -27,6 +26,7 @@ const expansion = require('./db/expansion');
|
|
|
27
26
|
const assertUnique = require('./db/assertUnique');
|
|
28
27
|
const generateDrafts = require('./db/draft');
|
|
29
28
|
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
29
|
+
const { getViewTransformer } = require('./db/views');
|
|
30
30
|
|
|
31
31
|
// By default: Do not process non-entities/views
|
|
32
32
|
function forEachDefinition(csn, cb) {
|
|
@@ -100,7 +100,6 @@ function forEachDefinition(csn, cb) {
|
|
|
100
100
|
* @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
|
|
101
101
|
*/
|
|
102
102
|
function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
103
|
-
const columnClearer = [];
|
|
104
103
|
// copy the model as we don't want to change the input model
|
|
105
104
|
timetrace.start('HANA transformation');
|
|
106
105
|
/** @type {CSN.Model} */
|
|
@@ -115,7 +114,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
115
114
|
let error, warning, info; // message functions
|
|
116
115
|
/** @type {() => void} */
|
|
117
116
|
let throwWithError;
|
|
118
|
-
let artifactRef, inspectRef,
|
|
117
|
+
let artifactRef, inspectRef, effectiveType, // csnRefs
|
|
119
118
|
addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
|
|
120
119
|
get$combined; // csnUtils
|
|
121
120
|
|
|
@@ -138,6 +137,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
138
137
|
error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
|
|
139
138
|
});
|
|
140
139
|
|
|
140
|
+
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
|
|
141
|
+
handleExists(csn, options, error);
|
|
142
|
+
|
|
141
143
|
// Check if structured elements and managed associations are compared in an expression
|
|
142
144
|
// and expand these structured elements. This tuple expansion allows all other
|
|
143
145
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
@@ -150,7 +152,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
150
152
|
// FIXME: This does something very similar to cloneWithTransformations -> refactor?
|
|
151
153
|
const transformCsn = transformUtils.transformModel;
|
|
152
154
|
|
|
153
|
-
handleExists(csn, options, error);
|
|
154
155
|
|
|
155
156
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
156
157
|
// assoc2join eventually rewrites the table aliases
|
|
@@ -212,10 +213,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
212
213
|
}
|
|
213
214
|
});
|
|
214
215
|
|
|
215
|
-
// Must happen after A2J, as A2J needs $self to correctly resolve stuff
|
|
216
|
-
if(doA2J)
|
|
217
|
-
flattening.removeLeadingSelf(csn);
|
|
218
|
-
|
|
219
216
|
const {
|
|
220
217
|
flattenStructuredElement,
|
|
221
218
|
flattenStructStepsInRef, getForeignKeyArtifact,
|
|
@@ -231,6 +228,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
231
228
|
isStructured,
|
|
232
229
|
addStringAnnotationTo,
|
|
233
230
|
cloneWithTransformations,
|
|
231
|
+
getContextOfArtifact,
|
|
234
232
|
} = getUtils(csn);
|
|
235
233
|
|
|
236
234
|
// (000) Rename primitive types, make UUID a String
|
|
@@ -331,7 +329,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
331
329
|
// because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
|
|
332
330
|
forEachDefinition(csn, transformSelfInBacklinks);
|
|
333
331
|
|
|
334
|
-
if(
|
|
332
|
+
if(options.forHana){
|
|
335
333
|
/**
|
|
336
334
|
* Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
|
|
337
335
|
* For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
|
|
@@ -343,7 +341,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
343
341
|
if(validOptionsForConstraint())
|
|
344
342
|
createReferentialConstraints(csn, options);
|
|
345
343
|
}
|
|
346
|
-
|
|
344
|
+
// no constraints for drafts
|
|
347
345
|
generateDrafts(csn, options, pathDelimiter, { info, warning, error });
|
|
348
346
|
|
|
349
347
|
// Set the final constraint paths and produce hana tc indexes if required
|
|
@@ -356,6 +354,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
356
354
|
// Apply view-specific transformations
|
|
357
355
|
// (160) Projections now finally become views
|
|
358
356
|
// Replace managed association in group/order by with foreign keys
|
|
357
|
+
const transformEntityOrViewPass2 = getViewTransformer(csn, options, {error, info}, transformCommon);
|
|
359
358
|
forEachDefinition(csn, transformViews);
|
|
360
359
|
|
|
361
360
|
// Recursively apply transformCommon and attach @cds.persistence.name
|
|
@@ -384,8 +383,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
384
383
|
checkConstraintIdentifiers,
|
|
385
384
|
/* (250) Remove all namespaces from definitions */
|
|
386
385
|
removeNamespaces,
|
|
387
|
-
/* (190 b) Replace enum types by their final base type */
|
|
388
|
-
replaceEnumsByBaseTypes,
|
|
389
386
|
/* Check Type Parameters (precision, scale, length ...) */
|
|
390
387
|
checkTypeParameters,
|
|
391
388
|
/* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
|
|
@@ -394,6 +391,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
394
391
|
removeKeyPropInType,
|
|
395
392
|
]);
|
|
396
393
|
|
|
394
|
+
// Remove leading $self to keep renderer-diffs smaller
|
|
395
|
+
if(doA2J)
|
|
396
|
+
flattening.removeLeadingSelf(csn);
|
|
397
|
+
|
|
397
398
|
throwWithError();
|
|
398
399
|
|
|
399
400
|
timetrace.stop();
|
|
@@ -403,6 +404,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
403
404
|
}
|
|
404
405
|
|
|
405
406
|
const killers = {
|
|
407
|
+
// Used to ignore actions etc from processing and remove associations/elements
|
|
406
408
|
'_ignore': function (parent, a, b, path){
|
|
407
409
|
if(path.length > 2) {
|
|
408
410
|
const tail = path[path.length-1];
|
|
@@ -413,25 +415,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
413
415
|
delete parent._ignore;
|
|
414
416
|
}
|
|
415
417
|
},
|
|
416
|
-
|
|
417
|
-
'_effectiveType': killProp,
|
|
418
|
+
// Still used in flattenStructuredElements - in db/flattening.js
|
|
418
419
|
'_flatElementNameWithDots': killProp,
|
|
419
|
-
|
|
420
|
+
// Set when setting default string/binary length - used in copyTypeProperties and fixBorkedElementsOfLocalized
|
|
421
|
+
// to not copy the .length property if it was only set via default
|
|
420
422
|
'$default': killProp,
|
|
421
|
-
|
|
422
|
-
'$env': killProp,
|
|
423
|
-
'$fksgenerated': killProp,
|
|
424
|
-
'$lateFlattening': killProp,
|
|
425
|
-
'$path': killProp,
|
|
423
|
+
// Set when we turn UUID into String, checked during generateDraftForHana
|
|
426
424
|
'$renamed': killProp,
|
|
427
|
-
|
|
428
|
-
'$
|
|
425
|
+
// Set when we remove .key from temporal things, used in localized.js
|
|
426
|
+
'$key': killProp
|
|
429
427
|
}
|
|
430
428
|
|
|
431
429
|
applyTransformations(csn, killers, [], false);
|
|
432
430
|
|
|
433
431
|
redoProjections.forEach(fn => fn());
|
|
434
|
-
columnClearer.forEach(fn => fn());
|
|
435
432
|
|
|
436
433
|
return csn;
|
|
437
434
|
|
|
@@ -496,14 +493,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
496
493
|
|
|
497
494
|
function bindCsnReference(){
|
|
498
495
|
({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
|
|
499
|
-
({ artifactRef, inspectRef,
|
|
496
|
+
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
|
|
500
497
|
({ getFinalBaseType, get$combined } = getUtils(csn));
|
|
501
498
|
({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
|
|
502
499
|
}
|
|
503
500
|
|
|
504
501
|
function bindCsnReferenceOnly(){
|
|
505
502
|
// invalidate caches for CSN ref API
|
|
506
|
-
({ artifactRef, inspectRef,
|
|
503
|
+
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
|
|
507
504
|
}
|
|
508
505
|
|
|
509
506
|
function handleMixinOnConditions(artifact, artifactName) {
|
|
@@ -619,20 +616,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
619
616
|
}
|
|
620
617
|
}
|
|
621
618
|
|
|
622
|
-
/**
|
|
623
|
-
* @param {CSN.Artifact} artifact
|
|
624
|
-
* @param {string} artifactName
|
|
625
|
-
*/
|
|
626
|
-
function replaceEnumsByBaseTypes(artifact, artifactName) {
|
|
627
|
-
replaceEnumByBaseType(artifact);
|
|
628
|
-
forEachMemberRecursively(artifact, (member) => {
|
|
629
|
-
replaceEnumByBaseType(member);
|
|
630
|
-
if (options.forHana.alwaysResolveDerivedTypes || options.forHana.names === 'plain') {
|
|
631
|
-
toFinalBaseType(member);
|
|
632
|
-
addDefaultTypeFacets(member);
|
|
633
|
-
}
|
|
634
|
-
}, [ 'definitions', artifactName ]);
|
|
635
|
-
}
|
|
636
619
|
|
|
637
620
|
/**
|
|
638
621
|
* @param {CSN.Artifact} artifact
|
|
@@ -710,6 +693,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
710
693
|
function handleAssociations(artifact, artifactName) {
|
|
711
694
|
// Do things specific for entities and views (pass 1)
|
|
712
695
|
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
696
|
+
const alreadyHandled = new WeakMap();
|
|
713
697
|
forAllElements(artifact, artifactName, (parent, elements) => {
|
|
714
698
|
for (const elemName in elements) {
|
|
715
699
|
const elem = elements[elemName];
|
|
@@ -717,7 +701,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
717
701
|
// (unless explicitly asked to keep assocs unchanged)
|
|
718
702
|
if (doA2J) {
|
|
719
703
|
if (isManagedAssociationElement(elem))
|
|
720
|
-
transformManagedAssociation(parent, artifactName, elem, elemName);
|
|
704
|
+
transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
|
|
721
705
|
}
|
|
722
706
|
}
|
|
723
707
|
})
|
|
@@ -814,14 +798,31 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
814
798
|
|| hasAnnotationValue(artifact, '@cds.persistence.exists'))
|
|
815
799
|
artifact._ignore = true;
|
|
816
800
|
|
|
801
|
+
const namingMode = options.forHana && options.forHana.names;
|
|
817
802
|
// issue #3450 HANA CDS can not handle external artifacts which are part of a HANA CDS context
|
|
818
|
-
if (
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
803
|
+
if (hasAnnotationValue(artifact, '@cds.persistence.exists') &&
|
|
804
|
+
options.transformation === 'hdbcds' &&
|
|
805
|
+
(namingMode === 'quoted' || namingMode === 'hdbcds')) {
|
|
806
|
+
let hanaCDSContextName;
|
|
807
|
+
if(namingMode === 'hdbcds') {
|
|
808
|
+
// for hdbcds names we only create a context if you defined a context/service in your cdl model
|
|
809
|
+
hanaCDSContextName = getContextOfArtifact(artifactName);
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
// for quoted naming mode, we create a context if you either defined a context/service
|
|
813
|
+
// or a namespace in your cdl model
|
|
814
|
+
hanaCDSContextName = getContextOfArtifact(artifactName) || getNamespace(csn, artifactName);
|
|
815
|
+
}
|
|
816
|
+
if (hanaCDSContextName) {
|
|
817
|
+
warning('anno-unstable-hdbcds', [ 'definitions', artifactName ],
|
|
818
|
+
{
|
|
819
|
+
id: getResultingName(csn, options.forHana.names, artifactName),
|
|
820
|
+
name: getResultingName(csn, options.forHana.names, hanaCDSContextName),
|
|
821
|
+
anno: 'cds.persistence.exists',
|
|
822
|
+
},
|
|
823
|
+
'Do not use $(ANNO) on an entity named $(ID) in SAP HANA CDS when we also create a SAP HANA CDS context named $(NAME)'
|
|
824
|
+
);
|
|
825
|
+
}
|
|
825
826
|
}
|
|
826
827
|
}
|
|
827
828
|
}
|
|
@@ -843,30 +844,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
843
844
|
if (art.technicalConfig)
|
|
844
845
|
newCsn.definitions[artName].technicalConfig = art.technicalConfig;
|
|
845
846
|
|
|
846
|
-
const newArt = newCsn.definitions[artName];
|
|
847
|
-
|
|
848
|
-
// No need to loop/check artifacts that won't reach the DB anyways
|
|
849
|
-
if (art.query && newArt && newArt.query && isPersistedOnDatabase(newArt)) {
|
|
850
|
-
// Loop through the newCSN and add possible new _ignore mixin to the kill list
|
|
851
|
-
forAllQueries(newArt.query, (q, p) => {
|
|
852
|
-
if (q.SELECT && q.SELECT.mixin) {
|
|
853
|
-
for(let mixinName of Object.keys(q.SELECT.mixin)) {
|
|
854
|
-
const mixinElement = q.SELECT.mixin[mixinName];
|
|
855
|
-
if (mixinElement._ignore && options.toSql) {
|
|
856
|
-
columnClearer.push(() => {
|
|
857
|
-
const query = walkCsnPath(csn, p);
|
|
858
|
-
for(let i = query.columns.length-1; i > -1; i--){
|
|
859
|
-
const col = query.columns[i];
|
|
860
|
-
if(col && col.ref && col.ref[0] === mixinName){
|
|
861
|
-
query.columns.splice(i, 1);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}, ['definitions', artName, 'query']);
|
|
869
|
-
}
|
|
870
847
|
});
|
|
871
848
|
csn = newCsn;
|
|
872
849
|
}
|
|
@@ -1052,10 +1029,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1052
1029
|
if (isPersistedOnDatabase(artifact)) {
|
|
1053
1030
|
// TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
|
|
1054
1031
|
if (artifact.query) {
|
|
1055
|
-
|
|
1032
|
+
// If we do A2J, we don't need to check the mixin. Either it is used -> a join
|
|
1033
|
+
// or published -> handled via elements/members. Unused mixins are removed anyway.
|
|
1034
|
+
if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
1056
1035
|
forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
|
|
1057
1036
|
|
|
1058
|
-
else if (artifact.query.SET && artifact.query.SET.mixin)
|
|
1037
|
+
else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
|
|
1059
1038
|
forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
|
|
1060
1039
|
}
|
|
1061
1040
|
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
@@ -1096,6 +1075,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1096
1075
|
|
|
1097
1076
|
// (190 a) Replace enum symbols by their value (if found)
|
|
1098
1077
|
replaceEnumSymbolsByValues(obj, path);
|
|
1078
|
+
|
|
1079
|
+
if (obj.enum)
|
|
1080
|
+
delete obj.enum;
|
|
1099
1081
|
}
|
|
1100
1082
|
|
|
1101
1083
|
// Change the names of those builtin types that have different names in HANA.
|
|
@@ -1129,309 +1111,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1129
1111
|
// }
|
|
1130
1112
|
// }
|
|
1131
1113
|
|
|
1132
|
-
/**
|
|
1133
|
-
* Strip of leading $self of the ref
|
|
1134
|
-
* @param {object} col A column
|
|
1135
|
-
*
|
|
1136
|
-
* @returns {object}
|
|
1137
|
-
*/
|
|
1138
|
-
function stripLeadingSelf(col) {
|
|
1139
|
-
if (col.ref && col.ref.length > 1 && col.ref[0] === '$self')
|
|
1140
|
-
col.ref = col.ref.slice(1);
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
return col;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
function isUnion(path){
|
|
1147
|
-
const subquery = path[path.length-1];
|
|
1148
|
-
const queryIndex = path[path.length-2]
|
|
1149
|
-
const args = path[path.length-3];
|
|
1150
|
-
const unionOperator = path[path.length-4];
|
|
1151
|
-
return path.length > 3 && (subquery === 'SET' || subquery === 'SELECT') && typeof queryIndex === 'number' && queryIndex >= 0 && args === 'args' && unionOperator === 'SET';
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
function transformEntityOrViewPass2(query, artifact, artName, path) {
|
|
1155
|
-
const { elements } = queryOrMain(query, artifact);
|
|
1156
|
-
let hasNonAssocElements = false;
|
|
1157
|
-
const isSelect = query && query.SELECT;
|
|
1158
|
-
let isProjection = !!artifact.projection;
|
|
1159
|
-
const columnMap = Object.create(null);
|
|
1160
|
-
let isSelectStar = false;
|
|
1161
|
-
if (isSelect) {
|
|
1162
|
-
if (!query.SELECT.columns) {
|
|
1163
|
-
isProjection = true;
|
|
1164
|
-
}
|
|
1165
|
-
else {
|
|
1166
|
-
query.SELECT.columns.forEach((col) => {
|
|
1167
|
-
if (col === '*') {
|
|
1168
|
-
isSelectStar = true;
|
|
1169
|
-
}
|
|
1170
|
-
else if (col.as) {
|
|
1171
|
-
if (!columnMap[col.as])
|
|
1172
|
-
columnMap[col.as] = col;
|
|
1173
|
-
}
|
|
1174
|
-
else if (col.ref) {
|
|
1175
|
-
if (!columnMap[col.ref[col.ref.length - 1]])
|
|
1176
|
-
columnMap[col.ref[col.ref.length - 1]] = col;
|
|
1177
|
-
}
|
|
1178
|
-
else if (col.func) {
|
|
1179
|
-
columnMap[col.func] = col;
|
|
1180
|
-
}
|
|
1181
|
-
else if (!columnMap[col]) {
|
|
1182
|
-
columnMap[col] = col;
|
|
1183
|
-
}
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
if (query && options.transformation === 'hdbcds') {
|
|
1188
|
-
// check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
|
|
1189
|
-
if (query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1) {
|
|
1190
|
-
for (const elementName in elements) {
|
|
1191
|
-
const element = elements[elementName];
|
|
1192
|
-
if (element.target) {
|
|
1193
|
-
let colLocation;
|
|
1194
|
-
for (let i = 0; i < query.SELECT.columns.length; i++) {
|
|
1195
|
-
const col = query.SELECT.columns[i];
|
|
1196
|
-
if (col.ref && col.ref.length === 1) {
|
|
1197
|
-
if (!colLocation && col.ref[0] === elementName)
|
|
1198
|
-
colLocation = i;
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
if (col.as === elementName)
|
|
1202
|
-
colLocation = i;
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
if (colLocation) {
|
|
1206
|
-
const matchingCol = query.SELECT.columns[colLocation];
|
|
1207
|
-
const possibleMixinName = matchingCol.ref[0];
|
|
1208
|
-
const isMixin = query.SELECT.mixin[possibleMixinName] !== undefined;
|
|
1209
|
-
if (element.target && isMixin)
|
|
1210
|
-
error(null, path.concat([ 'columns', colLocation ]),
|
|
1211
|
-
`Element "${ elementName }" is a mixin association${ possibleMixinName !== elementName ? ` ("${ possibleMixinName }")` : '' } and can't be published in a UNION`);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
// Second walk through the entity elements: Deal with associations (might also result in new elements)
|
|
1219
|
-
|
|
1220
|
-
// Will be initialized JIT inside the elements-loop
|
|
1221
|
-
let $combined;
|
|
1222
|
-
|
|
1223
|
-
for (const elemName in elements) {
|
|
1224
|
-
const elem = elements[elemName];
|
|
1225
|
-
if (isSelect) {
|
|
1226
|
-
if (!columnMap[elemName]) {
|
|
1227
|
-
// Prepend an alias if present
|
|
1228
|
-
let alias = (isProjection || isSelectStar) &&
|
|
1229
|
-
(query.SELECT.from.as || (query.SELECT.from.ref && implicitAs(query.SELECT.from.ref)));
|
|
1230
|
-
// In case of * and no explicit alias
|
|
1231
|
-
// find the source of the col by looking at $combined and prepend it
|
|
1232
|
-
if (isSelectStar && !alias && !isProjection) {
|
|
1233
|
-
if (!$combined)
|
|
1234
|
-
$combined = get$combined(query);
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
const matchingCombined = $combined[elemName];
|
|
1238
|
-
// Internal errors - this should never happen!
|
|
1239
|
-
if (matchingCombined.length > 1) { // should already be caught by compiler
|
|
1240
|
-
throw new Error(`Ambiguous name - can't be resolved: ${ elemName }. Found in: ${ matchingCombined.map(o => o.parent) }`);
|
|
1241
|
-
}
|
|
1242
|
-
else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
|
|
1243
|
-
throw new Error(`No matching entry found in UNION of all elements for: ${ elemName }`);
|
|
1244
|
-
}
|
|
1245
|
-
alias = matchingCombined[0].parent;
|
|
1246
|
-
}
|
|
1247
|
-
if (alias)
|
|
1248
|
-
columnMap[elemName] = { ref: [ alias, elemName ] };
|
|
1249
|
-
else
|
|
1250
|
-
columnMap[elemName] = { ref: [ elemName ] };
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// For associations - make sure that the foreign keys have the same "style"
|
|
1254
|
-
// If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
|
|
1255
|
-
if (elem.keys && doA2J) {
|
|
1256
|
-
const assoc_col = columnMap[elemName];
|
|
1257
|
-
if (assoc_col && assoc_col.ref) {
|
|
1258
|
-
elem.keys.forEach((key) => {
|
|
1259
|
-
const ref = cloneCsn(assoc_col.ref, options);
|
|
1260
|
-
ref[ref.length - 1] = [ ref[ref.length - 1] ].concat(key.as || key.ref).join(pathDelimiter);
|
|
1261
|
-
const result = {
|
|
1262
|
-
ref,
|
|
1263
|
-
};
|
|
1264
|
-
if (assoc_col.as)
|
|
1265
|
-
result.as = key.$generatedFieldName;
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
if (assoc_col.key)
|
|
1269
|
-
result.key = true;
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const colName = result.as || ref[ref.length - 1];
|
|
1273
|
-
columnMap[colName] = result;
|
|
1274
|
-
});
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
// Add flattened structured things preserving aliases and refs with/without table alias
|
|
1278
|
-
// If we add them when we get to them in "elements", we cannot know what table alias was used...
|
|
1279
|
-
if (isStructured(elem) && doA2J) {
|
|
1280
|
-
const col = columnMap[elemName];
|
|
1281
|
-
const originalName = col.ref[col.ref.length - 1];
|
|
1282
|
-
const flatElements = flattenStructuredElement(elem, originalName, [], path);
|
|
1283
|
-
const aliasedFlatElements = originalName !== elemName ? Object.keys(flattenStructuredElement(elem, elemName, [], path)) : [];
|
|
1284
|
-
|
|
1285
|
-
Object.keys(flatElements).forEach((flatElemName, index ) => {
|
|
1286
|
-
const clone = cloneCsn(col, options);
|
|
1287
|
-
// For the ref, use the "original"
|
|
1288
|
-
if (clone.ref)
|
|
1289
|
-
clone.ref[clone.ref.length - 1] = flatElemName;
|
|
1290
|
-
|
|
1291
|
-
// If the column was aliased, use the alias-prefix for the flattened element
|
|
1292
|
-
if (originalName !== elemName)
|
|
1293
|
-
clone.as = aliasedFlatElements[index];
|
|
1294
|
-
|
|
1295
|
-
// Insert into map, giving precedence to the alias
|
|
1296
|
-
columnMap[clone.as || flatElemName] = clone;
|
|
1297
|
-
});
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
// Views must have at least one element that is not an unmanaged assoc
|
|
1301
|
-
if (!elem.on && !elem._ignore)
|
|
1302
|
-
hasNonAssocElements = true;
|
|
1303
|
-
|
|
1304
|
-
// (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
|
|
1305
|
-
// CDXCORE-585: Allow mixin associations to be used and published in parallel
|
|
1306
|
-
if (query !== undefined && elem.target) {
|
|
1307
|
-
if(isUnion(path) && options.transformation === 'hdbcds'){
|
|
1308
|
-
if(isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J){
|
|
1309
|
-
if(elem.keys) {
|
|
1310
|
-
info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`)
|
|
1311
|
-
} else {
|
|
1312
|
-
info(null, path, `Association "${elemName}", published in a UNION, will be ignored`)
|
|
1313
|
-
}
|
|
1314
|
-
elem._ignore = true;
|
|
1315
|
-
}
|
|
1316
|
-
else {
|
|
1317
|
-
error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`)
|
|
1318
|
-
}
|
|
1319
|
-
} else if(path.length > 4 && options.transformation === 'hdbcds'){ // path.length > 4 -> is a subquery
|
|
1320
|
-
error(null, path, { name: elemName },
|
|
1321
|
-
'Association $(NAME) can\'t be published in a subquery')
|
|
1322
|
-
} else {
|
|
1323
|
-
/* Old implementation:
|
|
1324
|
-
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]);
|
|
1325
|
-
*/
|
|
1326
|
-
const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elem, elemName);
|
|
1327
|
-
const {mixinElement, mixinName } = getMixinAssocOfQueryIfPublished(query, elem, elemName);
|
|
1328
|
-
if (isNotMixinByItself || mixinElement !== undefined) {
|
|
1329
|
-
// If the mixin is only published and not used, only display the __ clone. Ignore the "original".
|
|
1330
|
-
if (mixinElement !== undefined && !usesMixinAssociation(query, elem, elemName)){
|
|
1331
|
-
mixinElement._ignore = true;
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
delete elem._typeIsExplicit;
|
|
1335
|
-
// Create an unused alias name for the MIXIN - use 3 _ to avoid collision with usings
|
|
1336
|
-
let mixinElemName = `___${ mixinName || elemName }`;
|
|
1337
|
-
while (elements[mixinElemName])
|
|
1338
|
-
mixinElemName = `_${ mixinElemName }`;
|
|
1339
|
-
|
|
1340
|
-
// Copy the association element to the MIXIN clause under its alias name
|
|
1341
|
-
// (shallow copy is sufficient, just fix name and value)
|
|
1342
|
-
const mixinElem = Object.assign({}, elem);
|
|
1343
|
-
// Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)
|
|
1344
|
-
transformCommon(mixinElem, mixinElemName);
|
|
1345
|
-
// TODO: Can we rely on query.SELECT.mixin to check for mixins?
|
|
1346
|
-
// Yes, we can - only SELECT can have mixin. But:
|
|
1347
|
-
// - UNION
|
|
1348
|
-
// - JOINS
|
|
1349
|
-
// - Subqueries
|
|
1350
|
-
// Are currently (and in the old transformer) not handled!
|
|
1351
|
-
if (query.SELECT && !query.SELECT.mixin)
|
|
1352
|
-
query.SELECT.mixin = Object.create(null);
|
|
1353
|
-
|
|
1354
|
-
// Let the original association element use the newly generated MIXIN name as value and alias
|
|
1355
|
-
delete elem.viaAll;
|
|
1356
|
-
|
|
1357
|
-
// Clone 'on'-condition, pre-pending '$projection' to paths where appropriate,
|
|
1358
|
-
// and fixing the association alias just created
|
|
1359
|
-
|
|
1360
|
-
if (mixinElem.on) {
|
|
1361
|
-
mixinElem.on = cloneWithTransformations(mixinElem.on, {
|
|
1362
|
-
ref: (ref) => {
|
|
1363
|
-
// Clone the path, without any transformations
|
|
1364
|
-
const clonedPath = cloneWithTransformations(ref, {});
|
|
1365
|
-
// Prepend '$projection' to the path, unless the first path step is the (mixin) element itself or starts with '$')
|
|
1366
|
-
if (clonedPath[0] == elemName) {
|
|
1367
|
-
clonedPath[0] = mixinElemName;
|
|
1368
|
-
}
|
|
1369
|
-
else if (!(clonedPath[0] && clonedPath[0].startsWith('$'))) {
|
|
1370
|
-
const projectionId = '$projection';
|
|
1371
|
-
clonedPath.unshift(projectionId);
|
|
1372
|
-
}
|
|
1373
|
-
return clonedPath;
|
|
1374
|
-
},
|
|
1375
|
-
func: (func) => {
|
|
1376
|
-
// Unfortunately, function names are disguised as paths, so we would prepend a '$projection'
|
|
1377
|
-
// above (no way to distinguish that in the callback for 'path' above). We can only pluck it
|
|
1378
|
-
// off again here ... sigh
|
|
1379
|
-
if (func.ref && func.ref[0] && func.ref[0] === '$projection')
|
|
1380
|
-
func.ref = func.ref.slice(1);
|
|
1381
|
-
|
|
1382
|
-
return func;
|
|
1383
|
-
},
|
|
1384
|
-
});
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
if (!mixinElem._ignore)
|
|
1388
|
-
columnMap[elemName] = { ref: [ mixinElemName ], as: elemName };
|
|
1389
|
-
|
|
1390
|
-
if (query.SELECT) {
|
|
1391
|
-
query.SELECT.mixin[mixinElemName] = mixinElem;
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
if (query && !hasNonAssocElements) {
|
|
1399
|
-
// Complain if there are no elements other than unmanaged associations
|
|
1400
|
-
// Allow with plain
|
|
1401
|
-
error(null, [ 'definitions', artName ], { $reviewed: true } ,
|
|
1402
|
-
'Expecting view or projection to have at least one element that is not an unmanaged association');
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
if (isSelect) {
|
|
1406
|
-
// Workaround for bugzilla 176495 FIXME FIXME FIXME: is this really still needed?
|
|
1407
|
-
// If a select item of a cdx view contains an expression, the result type cannot be computed
|
|
1408
|
-
// but must be explicitly specified. This is important for the OData channel, which doesn't
|
|
1409
|
-
// work if the type is missing (for HANA channel an explicit type is not required, as HANA CDS
|
|
1410
|
-
// can compute the result type).
|
|
1411
|
-
// Due to bug in HANA CDS, providing explicit type 'LargeString' or 'LargeBinary' causes a
|
|
1412
|
-
// diserver crash. Until a fix in HANA CDS is available, we allow to suppress the explicit
|
|
1413
|
-
// type in the HANA channel via an annotation.
|
|
1414
|
-
Object.keys(columnMap).forEach((value) => {
|
|
1415
|
-
const elem = elements[value];
|
|
1416
|
-
if (elem && elem['@cds.workaround.noExplicitTypeForHANA'])
|
|
1417
|
-
delete columnMap[value].cast;
|
|
1418
|
-
});
|
|
1419
|
-
|
|
1420
|
-
query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
|
|
1421
|
-
// If following an association, explicitly set the implicit alias
|
|
1422
|
-
// due to an issue with HANA
|
|
1423
|
-
for (let i = 0; i < query.SELECT.columns.length; i++) {
|
|
1424
|
-
const col = query.SELECT.columns[i];
|
|
1425
|
-
if (!col.as && col.ref && col.ref.length > 1) {
|
|
1426
|
-
const { links } = inspectRef(path.concat([ 'columns', i ]));
|
|
1427
|
-
if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
|
|
1428
|
-
col.as = col.ref[col.ref.length - 1];
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
delete query.SELECT.excluding; // just to make the output of the new transformer the same as the old
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
1114
|
|
|
1436
1115
|
// If 'elem' has a default that is an enum constant, replace that by its value. Complain
|
|
1437
1116
|
// if not found or not an enum type,
|
|
@@ -1448,7 +1127,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1448
1127
|
// Looks like it is always run?! But message says HANA CDS?!
|
|
1449
1128
|
error(null, path, {
|
|
1450
1129
|
$reviewed: true,
|
|
1451
|
-
name: `#${elem.default['#']}`
|
|
1130
|
+
name: `#${ elem.default['#'] }`
|
|
1452
1131
|
},
|
|
1453
1132
|
'Expecting enum literal $(NAME) to be used with an enum type');
|
|
1454
1133
|
}
|
|
@@ -1458,7 +1137,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1458
1137
|
if (!enumSymbol) {
|
|
1459
1138
|
error(null, path, {
|
|
1460
1139
|
$reviewed: true,
|
|
1461
|
-
name: `#${elem.default['#']}`
|
|
1140
|
+
name: `#${ elem.default['#'] }`
|
|
1462
1141
|
}, 'Enum literal $(NAME) is undefined in enumeration type');
|
|
1463
1142
|
}
|
|
1464
1143
|
else if (enumSymbol.val !== undefined) { // `val` may be `null`
|
|
@@ -1475,30 +1154,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1475
1154
|
}
|
|
1476
1155
|
}
|
|
1477
1156
|
|
|
1478
|
-
// If 'node' has an enum type, change node's type to be the enum's base type
|
|
1479
|
-
// and strip off the 'enum' property.
|
|
1480
|
-
function replaceEnumByBaseType(node) {
|
|
1481
|
-
if (node.items)
|
|
1482
|
-
replaceEnumByBaseType(node.items);
|
|
1483
|
-
|
|
1484
|
-
// (190 b) Replace enum types by their final base type (must happen after 190 a)
|
|
1485
|
-
/* Old implementation:
|
|
1486
|
-
if (node && node._finalType && (node.enum || node._finalType.enum)) {
|
|
1487
|
-
node.type = node._finalType.type
|
|
1488
|
-
// node.type = node._finalType.type._artifact._finalType.type;
|
|
1489
|
-
if (node._finalType.length) {
|
|
1490
|
-
node.length = node._finalType.length;
|
|
1491
|
-
}
|
|
1492
|
-
setProp(node, '_finalType', node.type._artifact);
|
|
1493
|
-
delete node.enum;
|
|
1494
|
-
}
|
|
1495
|
-
*/
|
|
1496
|
-
if (node && node.enum) {
|
|
1497
|
-
// toFinalBaseType(node);
|
|
1498
|
-
// addDefaultTypeFacets(node);
|
|
1499
|
-
delete node.enum;
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
1157
|
|
|
1503
1158
|
// If the association element 'elem' of 'art' is a backlink association, massage its ON-condition
|
|
1504
1159
|
// (in place) so that it
|
|
@@ -1538,14 +1193,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1538
1193
|
if (multipleExprs)
|
|
1539
1194
|
result.push(')');
|
|
1540
1195
|
i += 3;
|
|
1541
|
-
|
|
1542
|
-
if(elem.$selfOnCondition)
|
|
1543
|
-
elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
|
|
1544
|
-
else {
|
|
1545
|
-
setProp(elem, '$selfOnCondition', {
|
|
1546
|
-
backlinkName
|
|
1547
|
-
}) // important for the foreign key constraints
|
|
1548
|
-
}
|
|
1196
|
+
attachBacklinkInformation(backlinkName);
|
|
1549
1197
|
}
|
|
1550
1198
|
else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
|
|
1551
1199
|
const assoc = inspectRef(path.concat([ i ])).art;
|
|
@@ -1556,14 +1204,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1556
1204
|
if (multipleExprs)
|
|
1557
1205
|
result.push(')');
|
|
1558
1206
|
i += 3;
|
|
1559
|
-
|
|
1560
|
-
if(elem.$selfOnCondition)
|
|
1561
|
-
elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
|
|
1562
|
-
else {
|
|
1563
|
-
setProp(elem, '$selfOnCondition', {
|
|
1564
|
-
backlinkName
|
|
1565
|
-
}) // important for the foreign key constraints
|
|
1566
|
-
}
|
|
1207
|
+
attachBacklinkInformation(backlinkName);
|
|
1567
1208
|
}
|
|
1568
1209
|
// Otherwise take one (!) token unchanged
|
|
1569
1210
|
else {
|
|
@@ -1583,6 +1224,24 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1583
1224
|
}
|
|
1584
1225
|
}
|
|
1585
1226
|
return result;
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* The knowledge whether an association was an `<up_>` association in a
|
|
1230
|
+
* `$self = <comp>.<up_>` comparison, is important for the foreign key constraints.
|
|
1231
|
+
* By the time we generate them, such on-conditions are already transformed
|
|
1232
|
+
* --> no more `$self` in the on-conditions, that is why we need to remember it here.
|
|
1233
|
+
*
|
|
1234
|
+
* @param {string} backlinkName name of `<up_>` in a `$self = <comp>.<up_>` comparison
|
|
1235
|
+
*/
|
|
1236
|
+
function attachBacklinkInformation(backlinkName) {
|
|
1237
|
+
if (elem.$selfOnCondition)
|
|
1238
|
+
elem.$selfOnCondition.up_.push(backlinkName);
|
|
1239
|
+
else {
|
|
1240
|
+
setProp(elem, '$selfOnCondition', {
|
|
1241
|
+
up_: [backlinkName]
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1586
1245
|
}
|
|
1587
1246
|
|
|
1588
1247
|
elem.on = processExpressionArgs(elem.on, pathToOn);
|
|
@@ -1683,6 +1342,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1683
1342
|
if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
|
|
1684
1343
|
{
|
|
1685
1344
|
ref.shift();
|
|
1345
|
+
} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
|
|
1346
|
+
// We could also have a $self infront of the assoc name - so we would need to shift twice
|
|
1347
|
+
ref.shift();
|
|
1348
|
+
ref.shift();
|
|
1686
1349
|
}
|
|
1687
1350
|
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
|
|
1688
1351
|
ref.unshift(elemName);
|
|
@@ -1696,26 +1359,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1696
1359
|
}
|
|
1697
1360
|
}
|
|
1698
1361
|
|
|
1699
|
-
/**
|
|
1700
|
-
* @todo: XSN - Implementation most likely too naive, can we rely on query.SELECT.mixin?
|
|
1701
|
-
*
|
|
1702
|
-
* @param {CSN.Query} query
|
|
1703
|
-
* @param {object} columnMap
|
|
1704
|
-
* @param {CSN.Artifact} columnMap
|
|
1705
|
-
* @param {string} elementName
|
|
1706
|
-
*/
|
|
1707
|
-
function checkIsNotMixinByItself(query, columnMap, element, elementName) {
|
|
1708
|
-
if (query && query.SELECT && query.SELECT.mixin) {
|
|
1709
|
-
const col = columnMap[elementName];
|
|
1710
|
-
|
|
1711
|
-
const realName = col.ref[col.ref.length - 1];
|
|
1712
|
-
// If the element is not part of the mixin => True
|
|
1713
|
-
return query.SELECT.mixin[realName] == undefined;
|
|
1714
|
-
}
|
|
1715
|
-
// the artifact does not define any mixins, the element cannot be a mixin
|
|
1716
|
-
return true;
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
1362
|
/**
|
|
1720
1363
|
* @param {CSN.Artifact} artifact
|
|
1721
1364
|
* @param {string} artifactName
|
|
@@ -2050,11 +1693,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2050
1693
|
const source = findSource(links, i - 1) || artifact;
|
|
2051
1694
|
// allow specifying managed assoc on the source side
|
|
2052
1695
|
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
|
|
2053
|
-
|
|
2054
1696
|
if(fks && fks.length >= 1){
|
|
2055
1697
|
const fk = fks[0];
|
|
2056
|
-
|
|
2057
|
-
|
|
1698
|
+
const managedAssocStepName = refOwner.ref[i];
|
|
1699
|
+
const fkName = `${ managedAssocStepName }${ pathDelimiter }${ fk.as }`;
|
|
1700
|
+
if(source && source.elements[fkName])
|
|
1701
|
+
refOwner.ref = [ ...ref.slice(0, i), fkName ];
|
|
2058
1702
|
}
|
|
2059
1703
|
}
|
|
2060
1704
|
}
|
|
@@ -2093,12 +1737,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2093
1737
|
* @param {string} artifactName
|
|
2094
1738
|
* @param {Object} elem The association to process
|
|
2095
1739
|
* @param {string} elemName
|
|
1740
|
+
* @param {WeakMap} alreadyHandled To cache which elements were already processed
|
|
2096
1741
|
* @returns {void}
|
|
2097
1742
|
*/
|
|
2098
|
-
function transformManagedAssociation(artifact, artifactName, elem, elemName) {
|
|
1743
|
+
function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
|
|
2099
1744
|
// No need to run over this - we already did, possibly because it was referenced in the ON-Condition
|
|
2100
1745
|
// of another association - see a few lines lower
|
|
2101
|
-
if (elem
|
|
1746
|
+
if (alreadyHandled.has(elem))
|
|
2102
1747
|
return;
|
|
2103
1748
|
// Generate foreign key elements for managed associations, and assemble an ON-condition with them
|
|
2104
1749
|
const onCondParts = [];
|
|
@@ -2115,10 +1760,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2115
1760
|
elemName,
|
|
2116
1761
|
].concat(foreignKey.ref),
|
|
2117
1762
|
};
|
|
2118
|
-
|
|
1763
|
+
const fkName = `${ elemName }${ pathDelimiter }${ foreignKey.as }`;
|
|
2119
1764
|
const fKeyArg = {
|
|
2120
1765
|
ref: [
|
|
2121
|
-
|
|
1766
|
+
fkName,
|
|
2122
1767
|
],
|
|
2123
1768
|
};
|
|
2124
1769
|
|
|
@@ -2157,7 +1802,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2157
1802
|
elem.implicitForeignKeys = true;
|
|
2158
1803
|
*/
|
|
2159
1804
|
// Remember that we already processed this
|
|
2160
|
-
|
|
1805
|
+
alreadyHandled.set(elem, true);
|
|
2161
1806
|
}
|
|
2162
1807
|
}
|
|
2163
1808
|
|