@sap/cds-compiler 2.11.4 → 2.12.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 +58 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +9 -10
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +12 -0
- package/lib/api/main.js +2 -0
- package/lib/api/options.js +2 -2
- package/lib/base/message-registry.js +31 -2
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +97 -69
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +5 -3
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/checks.js +32 -9
- package/lib/compiler/definer.js +25 -4
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/propagator.js +3 -2
- package/lib/compiler/resolver.js +97 -6
- package/lib/compiler/shared.js +12 -1
- package/lib/compiler/utils.js +7 -0
- package/lib/edm/annotations/genericTranslation.js +34 -17
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +1 -1
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +30 -23
- package/lib/edm/edmUtils.js +11 -12
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/language.tokens +15 -14
- package/lib/gen/languageLexer.interp +9 -1
- package/lib/gen/languageLexer.js +830 -779
- package/lib/gen/languageLexer.tokens +7 -6
- package/lib/gen/languageParser.js +2401 -2282
- package/lib/json/from-csn.js +47 -16
- package/lib/json/to-csn.js +17 -5
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/genericAntlrParser.js +68 -51
- package/lib/language/language.g4 +128 -74
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +5 -3
- package/lib/main.js +3 -2
- package/lib/model/csnRefs.js +116 -68
- package/lib/model/csnUtils.js +40 -48
- package/lib/model/enrichCsn.js +30 -14
- package/lib/optionProcessor.js +3 -3
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +193 -79
- package/lib/render/toHdbcds.js +179 -95
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +57 -40
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +6 -4
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +4 -5
- package/lib/transform/db/flattening.js +5 -6
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +36 -23
- package/lib/transform/forHanaNew.js +35 -626
- package/lib/transform/forOdataNew.js +5 -4
- package/lib/transform/localized.js +3 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +13 -13
- package/lib/transform/translateAssocsToJoins.js +8 -8
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +2 -1
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
|
-
const { getUtils, cloneCsn,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
9
|
-
isPersistedOnDatabase, getNormalizedQuery, isAspect, walkCsnPath,
|
|
4
|
+
const { getUtils, cloneCsn,
|
|
5
|
+
forEachMemberRecursively, forEachRef, forAllQueries,
|
|
6
|
+
getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
7
|
+
isAspect, walkCsnPath,
|
|
10
8
|
} = require('../model/csnUtils');
|
|
11
9
|
const { makeMessageFunction } = require('../base/messages');
|
|
12
10
|
const transformUtils = require('./transformUtilsNew');
|
|
13
11
|
const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
|
|
14
|
-
const { csnRefs, pathId
|
|
12
|
+
const { csnRefs, pathId } = require('../model/csnRefs');
|
|
15
13
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
16
14
|
const validate = require('../checks/validator');
|
|
15
|
+
const { rejectManagedAssociationsAndStructuresForHdbcsNames } = require('../checks/selectItems');
|
|
17
16
|
const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
|
|
18
17
|
const { timetrace } = require('../utils/timetrace');
|
|
19
18
|
const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
|
|
@@ -27,6 +26,9 @@ const assertUnique = require('./db/assertUnique');
|
|
|
27
26
|
const generateDrafts = require('./db/draft');
|
|
28
27
|
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
29
28
|
const { getViewTransformer } = require('./db/views');
|
|
29
|
+
const cdsPersistence = require('./db/cdsPersistence');
|
|
30
|
+
const temporal = require('./db/temporal');
|
|
31
|
+
const associations = require('./db/associations')
|
|
30
32
|
|
|
31
33
|
// By default: Do not process non-entities/views
|
|
32
34
|
function forEachDefinition(csn, cb) {
|
|
@@ -121,9 +123,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
121
123
|
bindCsnReference();
|
|
122
124
|
|
|
123
125
|
throwWithError(); // reclassify and throw in case of non-configurable errors
|
|
124
|
-
|
|
126
|
+
|
|
125
127
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
|
|
126
|
-
enrichUniversalCsn(csn, options);
|
|
128
|
+
enrichUniversalCsn(csn, options);
|
|
127
129
|
bindCsnReference();
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -145,6 +147,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
145
147
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
146
148
|
// If errors are detected, throwWithError() will return from further processing
|
|
147
149
|
|
|
150
|
+
// If this function is ever undefined, we have a bug in our logic.
|
|
151
|
+
// @ts-ignore
|
|
148
152
|
expandStructsInExpression(csn, { drillRef: true });
|
|
149
153
|
|
|
150
154
|
throwWithError();
|
|
@@ -155,7 +159,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
155
159
|
|
|
156
160
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
157
161
|
// assoc2join eventually rewrites the table aliases
|
|
158
|
-
forEachDefinition(csn,
|
|
162
|
+
forEachDefinition(csn, temporal.getViewDecorator(csn, {info}));
|
|
159
163
|
|
|
160
164
|
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
161
165
|
assertUnique.prepare(csn, options, error, info);
|
|
@@ -215,20 +219,15 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
215
219
|
|
|
216
220
|
const {
|
|
217
221
|
flattenStructuredElement,
|
|
218
|
-
flattenStructStepsInRef,
|
|
222
|
+
flattenStructStepsInRef,
|
|
219
223
|
isAssociationOperand, isDollarSelfOrProjectionOperand,
|
|
220
|
-
extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments,
|
|
221
|
-
recurseElements
|
|
222
224
|
} = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
223
225
|
|
|
224
226
|
const {
|
|
225
227
|
getCsnDef,
|
|
226
228
|
isAssocOrComposition,
|
|
227
|
-
isManagedAssociationElement,
|
|
228
|
-
isStructured,
|
|
229
229
|
addStringAnnotationTo,
|
|
230
230
|
cloneWithTransformations,
|
|
231
|
-
getContextOfArtifact,
|
|
232
231
|
} = getUtils(csn);
|
|
233
232
|
|
|
234
233
|
// (000) Rename primitive types, make UUID a String
|
|
@@ -271,28 +270,28 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
271
270
|
// (040) Ignore entities and views that are abstract or implemented
|
|
272
271
|
// or carry the annotation cds.persistence.skip/exists
|
|
273
272
|
// These entities are not removed from the csn, but flagged as "to be ignored"
|
|
274
|
-
forEachDefinition(csn,
|
|
273
|
+
forEachDefinition(csn, cdsPersistence.getAnnoProcessor(csn, options, {warning}));
|
|
275
274
|
|
|
276
275
|
|
|
277
276
|
// (050) Check @cds.valid.from/to only on entity
|
|
278
277
|
// Views are checked in (001), unbalanced valid.from/to's or mismatching origins
|
|
279
278
|
// Temporal only in beta-mode
|
|
280
|
-
forEachDefinition(csn,
|
|
279
|
+
forEachDefinition(csn, temporal.getAnnotationHandler(csn, options, pathDelimiter, {error}));
|
|
281
280
|
|
|
282
281
|
handleManagedAssociationsAndCreateForeignKeys();
|
|
283
|
-
|
|
282
|
+
|
|
284
283
|
function handleManagedAssociationsAndCreateForeignKeys() {
|
|
285
|
-
forEachDefinition(csn, (
|
|
286
|
-
forEachDefinition(csn, (
|
|
284
|
+
forEachDefinition(csn, associations.getForeignKeyFlattener(csn, options, pathDelimiter));
|
|
285
|
+
forEachDefinition(csn, associations.getForeignKeyElementCreator(csn, options, pathDelimiter, { error }));
|
|
287
286
|
}
|
|
288
287
|
|
|
289
288
|
forEachDefinition(csn, flattenIndexes);
|
|
290
289
|
// Basic handling of associations in views and entities
|
|
291
|
-
forEachDefinition(csn,
|
|
290
|
+
forEachDefinition(csn, associations.getManagedAssociationTransformer(csn, options, pathDelimiter));
|
|
292
291
|
|
|
293
292
|
// (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
|
|
294
293
|
// and make them entities
|
|
295
|
-
forEachDefinition(csn,
|
|
294
|
+
forEachDefinition(csn, cdsPersistence.getPersistenceTableProcessor(csn, options, {error}));
|
|
296
295
|
|
|
297
296
|
// Allow using managed associations as steps in on-conditions to access their fks
|
|
298
297
|
// To be done after handleAssociations, since then the foreign keys of the managed assocs
|
|
@@ -309,6 +308,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
309
308
|
else
|
|
310
309
|
addLocalizationViews(csn, options);
|
|
311
310
|
|
|
311
|
+
forEachDefinition(csn, (definition, artName, prop, path) => {
|
|
312
|
+
if (definition.query) {
|
|
313
|
+
// reject managed association and structure publishing for to-hdbcds.hdbcds
|
|
314
|
+
const that = { csnUtils: getUtils(csn), options, error };
|
|
315
|
+
rejectManagedAssociationsAndStructuresForHdbcsNames.call(that, definition, path)
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
312
319
|
// For generating DB stuff:
|
|
313
320
|
// - table-entity with parameters: not allowed
|
|
314
321
|
// - view with parameters: ok on HANA, not allowed otherwise
|
|
@@ -348,8 +355,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
348
355
|
// See function comment for extensive information.
|
|
349
356
|
assertUnique.rewrite(csn, options, pathDelimiter);
|
|
350
357
|
|
|
351
|
-
// Associations that point to
|
|
352
|
-
forEachDefinition(csn,
|
|
358
|
+
// Associations that point to things marked with @cds.persistence.skip are removed
|
|
359
|
+
forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, {info}));
|
|
353
360
|
|
|
354
361
|
// Apply view-specific transformations
|
|
355
362
|
// (160) Projections now finally become views
|
|
@@ -434,63 +441,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
434
441
|
|
|
435
442
|
/* ----------------------------------- Functions start here -----------------------------------------------*/
|
|
436
443
|
|
|
437
|
-
/**
|
|
438
|
-
* Create the foreign key elements for managed associations.
|
|
439
|
-
* Create them in-place, right after the corresponding association.
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
* @param {CSN.Artifact} art
|
|
443
|
-
* @param {string} artName
|
|
444
|
-
*/
|
|
445
|
-
function createForeignKeyElements(art, artName) {
|
|
446
|
-
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
447
|
-
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
448
|
-
const elementsArray = [];
|
|
449
|
-
forEachGeneric(parent, 'elements', (element, elemName) => {
|
|
450
|
-
elementsArray.push([elemName, element]);
|
|
451
|
-
if (isManagedAssociationElement(element)) {
|
|
452
|
-
if (element.keys) {
|
|
453
|
-
for(let i = 0; i < element.keys.length; i++){
|
|
454
|
-
const foreignKey = element.keys[i];
|
|
455
|
-
const path = [...pathToElements, elemName, 'keys', i];
|
|
456
|
-
foreignKey.ref = flattenStructStepsInRef(foreignKey.ref, path);
|
|
457
|
-
const [fkName, fkElem] = getForeignKeyArtifact(element, elemName, foreignKey, path);
|
|
458
|
-
if(parent.elements[fkName]) {
|
|
459
|
-
error(null, [...pathToElements, elemName], { name: fkName, art: elemName },
|
|
460
|
-
'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
|
|
461
|
-
} else {
|
|
462
|
-
elementsArray.push([fkName, fkElem]);
|
|
463
|
-
}
|
|
464
|
-
applyCachedAlias(foreignKey);
|
|
465
|
-
// join ref array as the struct / assoc steps are not necessary anymore
|
|
466
|
-
foreignKey.ref = [foreignKey.ref.join(pathDelimiter)]
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
// Don't fake consistency of the model by adding empty elements {}
|
|
473
|
-
if(elementsArray.length === 0)
|
|
474
|
-
return;
|
|
475
|
-
|
|
476
|
-
parent.elements = elementsArray.reduce((previous, [name, element]) => {
|
|
477
|
-
previous[name] = element;
|
|
478
|
-
return previous;
|
|
479
|
-
}, Object.create(null));
|
|
480
|
-
|
|
481
|
-
})
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function applyCachedAlias(foreignKey) {
|
|
485
|
-
// If we have a $ref use that - it resolves aliased FKs correctly
|
|
486
|
-
if (foreignKey.$ref) {
|
|
487
|
-
foreignKey.ref = foreignKey.$ref;
|
|
488
|
-
delete foreignKey.$ref;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
|
|
494
444
|
function bindCsnReference(){
|
|
495
445
|
({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
|
|
496
446
|
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
|
|
@@ -624,7 +574,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
624
574
|
function transformSelfInBacklinks(artifact, artifactName, dummy, path) {
|
|
625
575
|
// Fixme: For toHana mixins must be transformed, for toSql -d hana
|
|
626
576
|
// mixin elements must be transformed, why can't toSql also use mixins?
|
|
627
|
-
|
|
577
|
+
if(artifact.kind === 'entity' || artifact.query || (options.toHana && options.toHana.names === 'hdbcds' && artifact.kind == 'type'))
|
|
578
|
+
doit(artifact.elements, path.concat([ 'elements' ]));
|
|
628
579
|
if (artifact.query && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
629
580
|
doit(artifact.query.SELECT.mixin, path.concat([ 'query', 'SELECT', 'mixin' ]));
|
|
630
581
|
|
|
@@ -682,61 +633,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
682
633
|
}, [ 'definitions', artifactName ]);
|
|
683
634
|
}
|
|
684
635
|
|
|
685
|
-
/**
|
|
686
|
-
*
|
|
687
|
-
* Generate foreign keys for managed associations
|
|
688
|
-
* Forbid aliases for foreign keys
|
|
689
|
-
*
|
|
690
|
-
* @param {CSN.Artifact} artifact
|
|
691
|
-
* @param {string} artifactName
|
|
692
|
-
*/
|
|
693
|
-
function handleAssociations(artifact, artifactName) {
|
|
694
|
-
// Do things specific for entities and views (pass 1)
|
|
695
|
-
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
696
|
-
const alreadyHandled = new WeakMap();
|
|
697
|
-
forAllElements(artifact, artifactName, (parent, elements) => {
|
|
698
|
-
for (const elemName in elements) {
|
|
699
|
-
const elem = elements[elemName];
|
|
700
|
-
// (140) Generate foreign key elements and ON-condition for managed associations
|
|
701
|
-
// (unless explicitly asked to keep assocs unchanged)
|
|
702
|
-
if (doA2J) {
|
|
703
|
-
if (isManagedAssociationElement(elem))
|
|
704
|
-
transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
})
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
function fixBorkedElementsOfLocalized(elements, pathToElements){
|
|
712
|
-
const pathToNonLocalized = ['definitions', pathToElements[1].replace('localized.',''), ...pathToElements.slice(2)];
|
|
713
|
-
const nonLocalizedElements = walkCsnPath(csn, pathToNonLocalized);
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
for(const elementName in elements){
|
|
717
|
-
const element = elements[elementName];
|
|
718
|
-
const reference = nonLocalizedElements[elementName];
|
|
719
|
-
|
|
720
|
-
// if the declared element is an enum, these values are with priority
|
|
721
|
-
if (!element.enum && reference.enum)
|
|
722
|
-
Object.assign(element, { enum: reference.enum });
|
|
723
|
-
if (!element.length && reference.length && !reference.$default)
|
|
724
|
-
Object.assign(element, { length: reference.length });
|
|
725
|
-
if (!element.precision && reference.precision)
|
|
726
|
-
Object.assign(element, { precision: reference.precision });
|
|
727
|
-
if (!element.scale && reference.scale)
|
|
728
|
-
Object.assign(element, { scale: reference.scale });
|
|
729
|
-
if (!element.srid && reference.srid)
|
|
730
|
-
Object.assign(element, { srid: reference.srid });
|
|
731
|
-
if (!element.keys && reference.keys)
|
|
732
|
-
Object.assign(element, { keys: cloneCsn(reference.keys, options)})
|
|
733
|
-
if (!element.type && reference.type)
|
|
734
|
-
Object.assign(element, { type: reference.type})
|
|
735
|
-
if (!element.on && reference.on && !reference.keys)
|
|
736
|
-
Object.assign(element, {on: cloneCsn(reference.on, options)})
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
|
|
740
636
|
/**
|
|
741
637
|
* @param {CSN.Artifact} artifact
|
|
742
638
|
* @param {string} artifactName
|
|
@@ -768,65 +664,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
768
664
|
}
|
|
769
665
|
}
|
|
770
666
|
|
|
771
|
-
/**
|
|
772
|
-
* @param {CSN.Artifact} artifact
|
|
773
|
-
* @param {string} artifactName
|
|
774
|
-
*/
|
|
775
|
-
function handleQueryish(artifact, artifactName) {
|
|
776
|
-
const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
|
|
777
|
-
|
|
778
|
-
if (stripQueryish) {
|
|
779
|
-
artifact.kind = 'entity';
|
|
780
|
-
delete artifact.query;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
784
|
-
// All elements must have a type for this to work
|
|
785
|
-
if (stripQueryish && !member._ignore && !member.kind && !member.type)
|
|
786
|
-
error(null, path, 'Expecting element to have a type if view is annotated with “@cds.persistence.table“');
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* @param {CSN.Artifact} artifact
|
|
792
|
-
* @param {string} artifactName
|
|
793
|
-
*/
|
|
794
|
-
function handleCdsPersistence(artifact, artifactName) {
|
|
795
|
-
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
796
|
-
if (artifact.abstract
|
|
797
|
-
|| hasAnnotationValue(artifact, '@cds.persistence.skip')
|
|
798
|
-
|| hasAnnotationValue(artifact, '@cds.persistence.exists'))
|
|
799
|
-
artifact._ignore = true;
|
|
800
|
-
|
|
801
|
-
const namingMode = options.forHana && options.forHana.names;
|
|
802
|
-
// issue #3450 HANA CDS can not handle external artifacts which are part of a HANA CDS context
|
|
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
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
667
|
function handleAssocToJoins() {
|
|
831
668
|
// With flattening errors, it makes little sense to continue.
|
|
832
669
|
throwWithError();
|
|
@@ -848,216 +685,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
848
685
|
csn = newCsn;
|
|
849
686
|
}
|
|
850
687
|
|
|
851
|
-
/**
|
|
852
|
-
* @param {CSN.Artifact} artifact
|
|
853
|
-
* @param {string} artifactName
|
|
854
|
-
*/
|
|
855
|
-
function handleTemporalAnnotations(artifact, artifactName) {
|
|
856
|
-
const validFrom = [];
|
|
857
|
-
const validTo = [];
|
|
858
|
-
const validKey = [];
|
|
859
|
-
|
|
860
|
-
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
861
|
-
const [ f, t, k ] = extractValidFromToKeyElement(member, path);
|
|
862
|
-
validFrom.push(...f);
|
|
863
|
-
validTo.push(...t);
|
|
864
|
-
validKey.push(...k);
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
if (artifact.kind === 'entity' && !artifact.query) {
|
|
868
|
-
validFrom.forEach(obj => checkAssignment('@cds.valid.from', obj.element, obj.path, artifact));
|
|
869
|
-
validTo.forEach(obj => checkAssignment('@cds.valid.to', obj.element, obj.path, artifact));
|
|
870
|
-
validKey.forEach(obj => checkAssignment('@cds.valid.key', obj.element, obj.path, artifact));
|
|
871
|
-
checkMultipleAssignments(validFrom, '@cds.valid.from', artifact, artifactName);
|
|
872
|
-
checkMultipleAssignments(validTo, '@cds.valid.to', artifact, artifactName, true);
|
|
873
|
-
checkMultipleAssignments(validKey, '@cds.valid.key', artifact, artifactName);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// if there is an cds.valid.key, make this the only primary key
|
|
877
|
-
// otherwise add all cds.valid.from to primary key tuple
|
|
878
|
-
if (validKey.length) {
|
|
879
|
-
if (!validFrom.length || !validTo.length)
|
|
880
|
-
error(null, [ 'definitions', artifactName ],
|
|
881
|
-
'Expecting “@cds.valid.from” and “@cds.valid.to” if “@cds.valid.key” is used');
|
|
882
|
-
|
|
883
|
-
forEachMember(artifact, (member) => {
|
|
884
|
-
if (member.key) {
|
|
885
|
-
member.unique = true;
|
|
886
|
-
delete member.key;
|
|
887
|
-
// Remember that this element was a key in the original artifact.
|
|
888
|
-
// This is needed for localized convenience view generation.
|
|
889
|
-
setProp(member, '$key', true);
|
|
890
|
-
}
|
|
891
|
-
});
|
|
892
|
-
validKey.forEach((member) => {
|
|
893
|
-
member.element.key = true;
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
validFrom.forEach((member) => {
|
|
897
|
-
member.element.unique = true;
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
else {
|
|
901
|
-
validFrom.forEach((member) => {
|
|
902
|
-
member.element.key = true;
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
function hasFalsyTemporalAnnotations(SELECT, elements, from, to) {
|
|
908
|
-
let fromElement = elements[from.name];
|
|
909
|
-
let toElement = elements[to.name];
|
|
910
|
-
|
|
911
|
-
if(SELECT.columns) {
|
|
912
|
-
for(const col of SELECT.columns) {
|
|
913
|
-
if(col.ref) {
|
|
914
|
-
const implicitAlias = implicitAs(col.ref);
|
|
915
|
-
if(implicitAlias === from.name)
|
|
916
|
-
fromElement = elements[col.as || implicitAlias];
|
|
917
|
-
else if(implicitAlias === to.name)
|
|
918
|
-
toElement = elements[col.as || implicitAlias];
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
const val = fromElement && toElement && hasAnnotationValue(fromElement, '@cds.valid.from', false) && hasAnnotationValue(toElement, '@cds.valid.to', false);
|
|
923
|
-
return val;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* Add a where condition to views that
|
|
928
|
-
* - are annotated with @cds.valid.from and @cds.valid.to,
|
|
929
|
-
* - have only one @cds.valid.from and @cds.valid.to,
|
|
930
|
-
* - and both annotations come from the same entity
|
|
931
|
-
*
|
|
932
|
-
* If the view has one of the annotations but the other conditions are not met, an error will be raised.
|
|
933
|
-
*
|
|
934
|
-
* @param {CSN.Artifact} artifact
|
|
935
|
-
* @param {string} artifactName
|
|
936
|
-
*/
|
|
937
|
-
function addTemporalWhereConditionToView(artifact, artifactName) {
|
|
938
|
-
const normalizedQuery = getNormalizedQuery(artifact);
|
|
939
|
-
if (normalizedQuery && normalizedQuery.query && normalizedQuery.query.SELECT) {
|
|
940
|
-
// BLOCKER: We need information to handle $combined
|
|
941
|
-
// What we are trying to achieve by this:
|
|
942
|
-
// Forbid joining/selecting from two or more temporal entities
|
|
943
|
-
// Idea: Follow the query-tree and check each from
|
|
944
|
-
// Collect all source-entities and compute our own $combined
|
|
945
|
-
const $combined = get$combined(normalizedQuery.query);
|
|
946
|
-
const [ from, to ] = getFromToElements($combined);
|
|
947
|
-
// exactly one validFrom & validTo
|
|
948
|
-
if (from.length === 1 && to.length === 1) {
|
|
949
|
-
// and both are from the same origin
|
|
950
|
-
if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
|
|
951
|
-
if(!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
|
|
952
|
-
const fromPath = {
|
|
953
|
-
ref: [
|
|
954
|
-
from[0].parent,
|
|
955
|
-
from[0].name,
|
|
956
|
-
],
|
|
957
|
-
};
|
|
958
|
-
|
|
959
|
-
const toPath = {
|
|
960
|
-
ref: [
|
|
961
|
-
to[0].parent,
|
|
962
|
-
to[0].name,
|
|
963
|
-
],
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
const atFrom = { ref: [ '$at', 'from' ] };
|
|
968
|
-
const atTo = { ref: [ '$at', 'to' ] };
|
|
969
|
-
|
|
970
|
-
const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
|
|
971
|
-
|
|
972
|
-
if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
|
|
973
|
-
normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
|
|
974
|
-
}
|
|
975
|
-
else {
|
|
976
|
-
normalizedQuery.query.SELECT.where = cond;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
else {
|
|
981
|
-
info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${ from[0].error_parent }"."${ from[0].name }" and "${ to[0].error_parent }"."${ to[0].name }" are not of same origin`);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
else if (from.length > 0 || to.length > 0) {
|
|
985
|
-
const missingAnnotation = from.length > to.length ? '@cds.valid.to' : '@cds.valid.from';
|
|
986
|
-
info(null, [ 'definitions', artifactName ],
|
|
987
|
-
{ anno: missingAnnotation },
|
|
988
|
-
'No temporal WHERE clause added because $(ANNO) is missing'
|
|
989
|
-
)
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
/**
|
|
995
|
-
* Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
|
|
996
|
-
*
|
|
997
|
-
* @param {any} combined union of all entities of the from-clause
|
|
998
|
-
* @returns {Array[]} Array where first field is array of elements with @cds.valid.from, second field is array of elements with @cds.valid.to.
|
|
999
|
-
*/
|
|
1000
|
-
function getFromToElements(combined) {
|
|
1001
|
-
const from = [];
|
|
1002
|
-
const to = [];
|
|
1003
|
-
for (const name in combined) {
|
|
1004
|
-
let elt = combined[name];
|
|
1005
|
-
if (!Array.isArray(elt))
|
|
1006
|
-
elt = [ elt ];
|
|
1007
|
-
elt.forEach((e) => {
|
|
1008
|
-
if (hasAnnotationValue(e.element, '@cds.valid.from'))
|
|
1009
|
-
from.push(e);
|
|
1010
|
-
|
|
1011
|
-
if (hasAnnotationValue(e.element, '@cds.valid.to'))
|
|
1012
|
-
to.push(e);
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
return [ from, to ];
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
/**
|
|
1020
|
-
* Associations that target a @cds.persistence.skip artifact must be removed
|
|
1021
|
-
* from the persistence model
|
|
1022
|
-
*
|
|
1023
|
-
* @param {CSN.Artifact} artifact
|
|
1024
|
-
* @param {string} artifactName
|
|
1025
|
-
* @param {string} prop
|
|
1026
|
-
* @param {CSN.Path} path
|
|
1027
|
-
*/
|
|
1028
|
-
function ignoreAssociationToSkippedTarget(artifact, artifactName, prop, path) {
|
|
1029
|
-
if (isPersistedOnDatabase(artifact)) {
|
|
1030
|
-
// TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
|
|
1031
|
-
if (artifact.query) {
|
|
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)
|
|
1035
|
-
forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
|
|
1036
|
-
|
|
1037
|
-
else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
|
|
1038
|
-
forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
|
|
1039
|
-
}
|
|
1040
|
-
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
1041
|
-
}
|
|
1042
|
-
function ignore(member, memberName, prop, path) {
|
|
1043
|
-
if (dialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
|
|
1044
|
-
const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], '@cds.persistence.exists') ? '@cds.persistence.exists' : '@cds.persistence.skip';
|
|
1045
|
-
info(null, path,
|
|
1046
|
-
{ target: member.target, anno: targetAnnotation },
|
|
1047
|
-
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)'
|
|
1048
|
-
);
|
|
1049
|
-
member._ignore = true;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
688
|
|
|
1054
|
-
/**
|
|
1055
|
-
* @param {CSN.Artifact} art
|
|
1056
|
-
* @returns {boolean}
|
|
1057
|
-
*/
|
|
1058
|
-
function isUnreachableAssociationTarget(art) {
|
|
1059
|
-
return !isPersistedOnDatabase(art) || hasAnnotationValue(art, '@cds.persistence.exists');
|
|
1060
|
-
}
|
|
1061
689
|
|
|
1062
690
|
/**
|
|
1063
691
|
* Remove `localized` from elements and replace Enum symbols by their values.
|
|
@@ -1312,14 +940,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1312
940
|
conditions.push([ a[0], '=', a[1] ]);
|
|
1313
941
|
});
|
|
1314
942
|
|
|
1315
|
-
|
|
943
|
+
return conditions.reduce((prev, current) => {
|
|
1316
944
|
if (prev.length === 0)
|
|
1317
945
|
return [ ...current ];
|
|
1318
946
|
|
|
1319
947
|
return [ ...prev, 'and', ...current ];
|
|
1320
948
|
}, []);
|
|
1321
|
-
|
|
1322
|
-
return result;
|
|
1323
949
|
}
|
|
1324
950
|
|
|
1325
951
|
// For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
|
|
@@ -1455,148 +1081,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1455
1081
|
return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
|
|
1456
1082
|
}
|
|
1457
1083
|
|
|
1458
|
-
/**
|
|
1459
|
-
* Flatten and create the foreign key elements of managed associaitons
|
|
1460
|
-
*
|
|
1461
|
-
* @param {CSN.Artifact} art
|
|
1462
|
-
* @param {string} artName
|
|
1463
|
-
*/
|
|
1464
|
-
function handleManagedAssociationFKs(art, artName) {
|
|
1465
|
-
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
1466
|
-
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
1467
|
-
if(artName.startsWith('localized.') && pathToElements.length > 3) {
|
|
1468
|
-
// In subqueries, the elements of localized views are missing all the important bits and pieces...
|
|
1469
|
-
fixBorkedElementsOfLocalized(elements, pathToElements);
|
|
1470
|
-
}
|
|
1471
|
-
forEachGeneric(parent, 'elements', (element, elemName) => {
|
|
1472
|
-
if (isManagedAssociationElement(element)) {
|
|
1473
|
-
if (element.keys) {
|
|
1474
|
-
// replace foreign keys that are managed associations by their respective foreign keys
|
|
1475
|
-
flattenFKs(element, elemName, [ ...pathToElements, elemName ]);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
});
|
|
1479
|
-
})
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
/**
|
|
1485
|
-
* Flattens all foreign keys
|
|
1486
|
-
*
|
|
1487
|
-
* Structures will be resolved to individual elements with scalar types
|
|
1488
|
-
*
|
|
1489
|
-
* Associations will be replaced by their respective foreign keys
|
|
1490
|
-
*
|
|
1491
|
-
* If a structure contains an assoc, this will also be resolved and vice versa
|
|
1492
|
-
*
|
|
1493
|
-
* @param {*} assoc
|
|
1494
|
-
* @param {*} assocName
|
|
1495
|
-
* @param {*} path
|
|
1496
|
-
*/
|
|
1497
|
-
function flattenFKs(assoc, assocName, path) {
|
|
1498
|
-
let finished = false;
|
|
1499
|
-
while(!finished) {
|
|
1500
|
-
const newKeys = [];
|
|
1501
|
-
finished = processKeys(assoc, assocName, path, newKeys);
|
|
1502
|
-
assoc.keys = newKeys;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
function processKeys(assoc, assocName, path, collector) {
|
|
1506
|
-
let finished = true;
|
|
1507
|
-
for (let i = 0; i < assoc.keys.length; i++) {
|
|
1508
|
-
const pathToKey = path.concat([ 'keys', i ]);
|
|
1509
|
-
const { art } = inspectRef(pathToKey);
|
|
1510
|
-
const { ref } = assoc.keys[i];
|
|
1511
|
-
if (isStructured(art)) {
|
|
1512
|
-
finished = false;
|
|
1513
|
-
// Mark this element to filter it later - not needed after expansion
|
|
1514
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
1515
|
-
const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
|
|
1516
|
-
Object.keys(flat).forEach((flatElemName) => {
|
|
1517
|
-
const key = assoc.keys[i];
|
|
1518
|
-
const clone = cloneCsn(assoc.keys[i], options);
|
|
1519
|
-
if (clone.as) {
|
|
1520
|
-
const lastRef = clone.ref[clone.ref.length - 1];
|
|
1521
|
-
// Cut off the last ref part from the beginning of the flat name
|
|
1522
|
-
const flatBaseName = flatElemName.slice(lastRef.length);
|
|
1523
|
-
// Join it to the existing table alias
|
|
1524
|
-
clone.as += flatBaseName;
|
|
1525
|
-
// do not loose the $ref for nested keys
|
|
1526
|
-
if(key.$ref){
|
|
1527
|
-
let aliasedLeaf = key.$ref[key.$ref.length - 1 ];
|
|
1528
|
-
aliasedLeaf += flatBaseName;
|
|
1529
|
-
setProp(clone, '$ref', key.$ref.slice(0, key.$ref.length - 1).concat(aliasedLeaf));
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
if (clone.ref) {
|
|
1533
|
-
clone.ref[clone.ref.length - 1] = flatElemName;
|
|
1534
|
-
// Now we need to properly flatten the whole ref
|
|
1535
|
-
clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
|
|
1536
|
-
}
|
|
1537
|
-
if (!clone.as) {
|
|
1538
|
-
clone.as = flatElemName;
|
|
1539
|
-
// TODO: can we use $inferred? Does it have other weird side-effects?
|
|
1540
|
-
setProp(clone, '$inferredAlias', true);
|
|
1541
|
-
}
|
|
1542
|
-
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
|
|
1543
|
-
// Add the newly generated foreign keys to the end - they will be picked up later on
|
|
1544
|
-
// Recursive solutions run into call stack issues
|
|
1545
|
-
collector.push(clone);
|
|
1546
|
-
});
|
|
1547
|
-
}
|
|
1548
|
-
else if (art.target) {
|
|
1549
|
-
finished = false;
|
|
1550
|
-
// Mark this element to filter it later - not needed after expansion
|
|
1551
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
1552
|
-
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
|
|
1553
|
-
// Add the newly generated foreign keys to the end - they will be picked up later on
|
|
1554
|
-
// Recursive solutions run into call stack issues
|
|
1555
|
-
art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
|
|
1556
|
-
}
|
|
1557
|
-
else if (assoc.keys[i].ref && !assoc.keys[i].as) {
|
|
1558
|
-
setProp(assoc.keys[i], '$inferredAlias', true);
|
|
1559
|
-
assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
|
|
1560
|
-
collector.push(assoc.keys[i]);
|
|
1561
|
-
} else {
|
|
1562
|
-
collector.push(assoc.keys[i]);
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
return finished;
|
|
1566
|
-
}
|
|
1567
|
-
assoc.keys = assoc.keys.filter(o => !o.$toDelete);
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
function cloneAndExtendRef(key, base, ref) {
|
|
1571
|
-
const clone = cloneCsn(base, options);
|
|
1572
|
-
if (key.ref) {
|
|
1573
|
-
// We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
|
|
1574
|
-
// Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
|
|
1575
|
-
// Later on, after we know that these foreign key elements are created, we replace ref with this $ref
|
|
1576
|
-
let $ref;
|
|
1577
|
-
if(base.$ref){
|
|
1578
|
-
// if a base $ref is provided, use it to correctly resolve association chains
|
|
1579
|
-
const refChain = [base.$ref[base.$ref.length - 1]].concat(key.as || key.ref);
|
|
1580
|
-
$ref = base.$ref.slice(0, base.$ref.length - 1).concat(refChain)
|
|
1581
|
-
} else {
|
|
1582
|
-
$ref = base.ref.concat( key.as || key.ref); // Keep along the aliases
|
|
1583
|
-
}
|
|
1584
|
-
setProp(clone, '$ref', $ref);
|
|
1585
|
-
clone.ref = clone.ref.concat(key.ref);
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
if (!clone.as && clone.ref && clone.ref.length > 0) {
|
|
1589
|
-
clone.as = ref[ref.length - 1] + pathDelimiter + (key.as || key.ref.join(pathDelimiter));
|
|
1590
|
-
// TODO: can we use $inferred? Does it have other weird side-effects?
|
|
1591
|
-
setProp(clone, '$inferredAlias', true);
|
|
1592
|
-
}
|
|
1593
|
-
else {
|
|
1594
|
-
clone.as += pathDelimiter + (key.as || key.ref.join(pathDelimiter));
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
return clone;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
1084
|
/**
|
|
1601
1085
|
* Flatten technical configuration stuff
|
|
1602
1086
|
*
|
|
@@ -1729,81 +1213,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1729
1213
|
return undefined;
|
|
1730
1214
|
}
|
|
1731
1215
|
}
|
|
1732
|
-
|
|
1733
|
-
/**
|
|
1734
|
-
* Create the foreign key elements for a managed association and build the on-condition
|
|
1735
|
-
*
|
|
1736
|
-
* @param {CSN.Artifact} artifact
|
|
1737
|
-
* @param {string} artifactName
|
|
1738
|
-
* @param {Object} elem The association to process
|
|
1739
|
-
* @param {string} elemName
|
|
1740
|
-
* @param {WeakMap} alreadyHandled To cache which elements were already processed
|
|
1741
|
-
* @returns {void}
|
|
1742
|
-
*/
|
|
1743
|
-
function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
|
|
1744
|
-
// No need to run over this - we already did, possibly because it was referenced in the ON-Condition
|
|
1745
|
-
// of another association - see a few lines lower
|
|
1746
|
-
if (alreadyHandled.has(elem))
|
|
1747
|
-
return;
|
|
1748
|
-
// Generate foreign key elements for managed associations, and assemble an ON-condition with them
|
|
1749
|
-
const onCondParts = [];
|
|
1750
|
-
let join_with_and = false;
|
|
1751
|
-
if(elem.keys.length === 0)
|
|
1752
|
-
elem._ignore = true;
|
|
1753
|
-
else {
|
|
1754
|
-
for (let i = 0; i < elem.keys.length; i++) {
|
|
1755
|
-
const foreignKey = elem.keys[i];
|
|
1756
|
-
|
|
1757
|
-
// Assemble left hand side of 'assoc.key = fkey'
|
|
1758
|
-
const assocKeyArg = {
|
|
1759
|
-
ref: [
|
|
1760
|
-
elemName,
|
|
1761
|
-
].concat(foreignKey.ref),
|
|
1762
|
-
};
|
|
1763
|
-
const fkName = `${ elemName }${ pathDelimiter }${ foreignKey.as }`;
|
|
1764
|
-
const fKeyArg = {
|
|
1765
|
-
ref: [
|
|
1766
|
-
fkName,
|
|
1767
|
-
],
|
|
1768
|
-
};
|
|
1769
|
-
|
|
1770
|
-
if (join_with_and) { // more than one FK
|
|
1771
|
-
onCondParts.push('and');
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
onCondParts.push(
|
|
1775
|
-
assocKeyArg
|
|
1776
|
-
);
|
|
1777
|
-
onCondParts.push('=');
|
|
1778
|
-
onCondParts.push(fKeyArg);
|
|
1779
|
-
|
|
1780
|
-
if (!join_with_and)
|
|
1781
|
-
join_with_and = true;
|
|
1782
|
-
}
|
|
1783
|
-
elem.on = onCondParts;
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
// If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
|
|
1787
|
-
// TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
|
|
1788
|
-
if (elem.key)
|
|
1789
|
-
delete elem.key;
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
// If the managed association has a 'not null' property => remove it
|
|
1793
|
-
if (elem.notNull)
|
|
1794
|
-
delete elem.notNull;
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
// The association is now unmanaged, i.e. actually it should no longer have foreign keys
|
|
1798
|
-
// at all. But the processing of backlink associations below expects to have them, so
|
|
1799
|
-
// we don't delete them (but mark them as implicit so that toCdl does not render them)
|
|
1800
|
-
/* Skip for now - forHana adds this to elements, but it is not part of the resulting CSN
|
|
1801
|
-
forHanaNew -> Somehow ends up in the CSN?!
|
|
1802
|
-
elem.implicitForeignKeys = true;
|
|
1803
|
-
*/
|
|
1804
|
-
// Remember that we already processed this
|
|
1805
|
-
alreadyHandled.set(elem, true);
|
|
1806
|
-
}
|
|
1807
1216
|
}
|
|
1808
1217
|
|
|
1809
1218
|
|