@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.
Files changed (80) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +9 -10
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/doc/CHANGELOG_BETA.md +12 -0
  6. package/lib/api/main.js +2 -0
  7. package/lib/api/options.js +2 -2
  8. package/lib/base/message-registry.js +31 -2
  9. package/lib/base/model.js +1 -0
  10. package/lib/base/optionProcessorHelper.js +97 -69
  11. package/lib/checks/.eslintrc.json +2 -0
  12. package/lib/checks/actionsFunctions.js +2 -1
  13. package/lib/checks/foreignKeys.js +4 -4
  14. package/lib/checks/managedInType.js +4 -4
  15. package/lib/checks/queryNoDbArtifacts.js +1 -3
  16. package/lib/checks/sql-snippets.js +93 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +5 -3
  19. package/lib/compiler/base.js +0 -1
  20. package/lib/compiler/checks.js +32 -9
  21. package/lib/compiler/definer.js +25 -4
  22. package/lib/compiler/index.js +1 -1
  23. package/lib/compiler/propagator.js +3 -2
  24. package/lib/compiler/resolver.js +97 -6
  25. package/lib/compiler/shared.js +12 -1
  26. package/lib/compiler/utils.js +7 -0
  27. package/lib/edm/annotations/genericTranslation.js +34 -17
  28. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  29. package/lib/edm/csn2edm.js +1 -1
  30. package/lib/edm/edm.js +8 -8
  31. package/lib/edm/edmPreprocessor.js +30 -23
  32. package/lib/edm/edmUtils.js +11 -12
  33. package/lib/gen/Dictionary.json +82 -40
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +3 -1
  36. package/lib/gen/language.tokens +15 -14
  37. package/lib/gen/languageLexer.interp +9 -1
  38. package/lib/gen/languageLexer.js +830 -779
  39. package/lib/gen/languageLexer.tokens +7 -6
  40. package/lib/gen/languageParser.js +2401 -2282
  41. package/lib/json/from-csn.js +47 -16
  42. package/lib/json/to-csn.js +17 -5
  43. package/lib/language/antlrParser.js +3 -3
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/genericAntlrParser.js +68 -51
  46. package/lib/language/language.g4 +128 -74
  47. package/lib/language/multiLineStringParser.js +536 -0
  48. package/lib/main.d.ts +5 -3
  49. package/lib/main.js +3 -2
  50. package/lib/model/csnRefs.js +116 -68
  51. package/lib/model/csnUtils.js +40 -48
  52. package/lib/model/enrichCsn.js +30 -14
  53. package/lib/optionProcessor.js +3 -3
  54. package/lib/render/DuplicateChecker.js +1 -1
  55. package/lib/render/manageConstraints.js +1 -1
  56. package/lib/render/toCdl.js +193 -79
  57. package/lib/render/toHdbcds.js +179 -95
  58. package/lib/render/toRename.js +7 -10
  59. package/lib/render/toSql.js +57 -40
  60. package/lib/render/utils/common.js +24 -5
  61. package/lib/render/utils/sql.js +6 -4
  62. package/lib/transform/braceExpression.js +4 -2
  63. package/lib/transform/db/associations.js +389 -0
  64. package/lib/transform/db/cdsPersistence.js +150 -0
  65. package/lib/transform/db/constraints.js +6 -4
  66. package/lib/transform/db/draft.js +3 -2
  67. package/lib/transform/db/expansion.js +4 -5
  68. package/lib/transform/db/flattening.js +5 -6
  69. package/lib/transform/db/temporal.js +236 -0
  70. package/lib/transform/db/transformExists.js +36 -23
  71. package/lib/transform/forHanaNew.js +35 -626
  72. package/lib/transform/forOdataNew.js +5 -4
  73. package/lib/transform/localized.js +3 -14
  74. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  75. package/lib/transform/transformUtilsNew.js +13 -13
  76. package/lib/transform/translateAssocsToJoins.js +8 -8
  77. package/lib/transform/universalCsnEnricher.js +217 -47
  78. package/lib/utils/file.js +2 -1
  79. package/lib/utils/timetrace.js +8 -2
  80. 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, forEachGeneric,
5
- forEachMember,
6
- forEachMemberRecursively, forEachRef, getNamespace, getResultingName,
7
- forAllQueries, forAllElements, hasAnnotationValue, getArtifactDatabaseNameOf,
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, implicitAs } = require('../model/csnRefs');
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, addTemporalWhereConditionToView);
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, getForeignKeyArtifact,
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, handleCdsPersistence);
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, handleTemporalAnnotations);
279
+ forEachDefinition(csn, temporal.getAnnotationHandler(csn, options, pathDelimiter, {error}));
281
280
 
282
281
  handleManagedAssociationsAndCreateForeignKeys();
283
-
282
+
284
283
  function handleManagedAssociationsAndCreateForeignKeys() {
285
- forEachDefinition(csn, (art, artName) => handleManagedAssociationFKs(art, artName));
286
- forEachDefinition(csn, (art, artName) => createForeignKeyElements(art, artName));
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, handleAssociations);
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, handleQueryish);
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 thins marked with @cds.persistence.skip are removed
352
- forEachDefinition(csn, ignoreAssociationToSkippedTarget);
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
- doit(artifact.elements, path.concat([ 'elements' ]));
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
- const result = conditions.reduce((prev, current) => {
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