@sap/cds-compiler 2.12.0 → 2.13.6

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 (118) hide show
  1. package/CHANGELOG.md +110 -15
  2. package/bin/cdsc.js +13 -13
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +28 -63
  8. package/lib/api/options.js +3 -3
  9. package/lib/api/validate.js +0 -5
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +25 -4
  16. package/lib/base/messages.js +16 -26
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +158 -123
  19. package/lib/checks/annotationsOData.js +1 -1
  20. package/lib/checks/cdsPersistence.js +2 -1
  21. package/lib/checks/enricher.js +17 -1
  22. package/lib/checks/invalidTarget.js +3 -1
  23. package/lib/checks/managedWithoutKeys.js +3 -1
  24. package/lib/checks/selectItems.js +4 -4
  25. package/lib/checks/sql-snippets.js +27 -26
  26. package/lib/checks/types.js +1 -1
  27. package/lib/checks/validator.js +4 -7
  28. package/lib/compiler/assert-consistency.js +5 -3
  29. package/lib/compiler/builtins.js +8 -6
  30. package/lib/compiler/checks.js +14 -3
  31. package/lib/compiler/cycle-detector.js +1 -1
  32. package/lib/compiler/define.js +1103 -0
  33. package/lib/compiler/extend.js +983 -0
  34. package/lib/compiler/finalize-parse-cdl.js +231 -0
  35. package/lib/compiler/index.js +32 -13
  36. package/lib/compiler/kick-start.js +190 -0
  37. package/lib/compiler/moduleLayers.js +4 -4
  38. package/lib/compiler/populate.js +1226 -0
  39. package/lib/compiler/propagator.js +111 -46
  40. package/lib/compiler/resolve.js +1433 -0
  41. package/lib/compiler/shared.js +64 -37
  42. package/lib/compiler/tweak-assocs.js +529 -0
  43. package/lib/compiler/utils.js +197 -33
  44. package/lib/edm/.eslintrc.json +5 -0
  45. package/lib/edm/annotations/genericTranslation.js +5 -9
  46. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  47. package/lib/edm/csn2edm.js +9 -8
  48. package/lib/edm/edm.js +11 -12
  49. package/lib/edm/edmPreprocessor.js +137 -73
  50. package/lib/edm/edmUtils.js +116 -22
  51. package/lib/gen/Dictionary.json +10 -3
  52. package/lib/gen/language.checksum +1 -1
  53. package/lib/gen/language.interp +9 -1
  54. package/lib/gen/language.tokens +86 -83
  55. package/lib/gen/languageLexer.interp +10 -1
  56. package/lib/gen/languageLexer.js +860 -833
  57. package/lib/gen/languageLexer.tokens +78 -75
  58. package/lib/gen/languageParser.js +5282 -4265
  59. package/lib/json/from-csn.js +12 -1
  60. package/lib/json/to-csn.js +126 -66
  61. package/lib/language/docCommentParser.js +2 -2
  62. package/lib/language/genericAntlrParser.js +76 -3
  63. package/lib/language/language.g4 +297 -130
  64. package/lib/language/multiLineStringParser.js +5 -5
  65. package/lib/main.d.ts +468 -59
  66. package/lib/main.js +35 -9
  67. package/lib/model/api.js +3 -1
  68. package/lib/model/csnRefs.js +225 -156
  69. package/lib/model/csnUtils.js +192 -223
  70. package/lib/model/enrichCsn.js +70 -29
  71. package/lib/model/revealInternalProperties.js +27 -6
  72. package/lib/model/sortViews.js +2 -1
  73. package/lib/modelCompare/compare.js +17 -12
  74. package/lib/optionProcessor.js +5 -4
  75. package/lib/render/manageConstraints.js +35 -32
  76. package/lib/render/toCdl.js +73 -288
  77. package/lib/render/toHdbcds.js +25 -23
  78. package/lib/render/toSql.js +98 -41
  79. package/lib/render/utils/common.js +5 -10
  80. package/lib/render/utils/sql.js +4 -3
  81. package/lib/render/utils/stringEscapes.js +111 -0
  82. package/lib/sql-identifier.js +1 -1
  83. package/lib/transform/.eslintrc.json +5 -0
  84. package/lib/transform/db/.eslintrc.json +2 -0
  85. package/lib/transform/db/applyTransformations.js +35 -12
  86. package/lib/transform/db/assertUnique.js +1 -1
  87. package/lib/transform/db/associations.js +103 -305
  88. package/lib/transform/db/cdsPersistence.js +2 -2
  89. package/lib/transform/db/constraints.js +55 -52
  90. package/lib/transform/db/expansion.js +46 -24
  91. package/lib/transform/db/flattening.js +553 -102
  92. package/lib/transform/db/groupByOrderBy.js +3 -1
  93. package/lib/transform/db/transformExists.js +59 -6
  94. package/lib/transform/db/views.js +5 -4
  95. package/lib/transform/draft/.eslintrc.json +38 -0
  96. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  97. package/lib/transform/draft/odata.js +227 -0
  98. package/lib/transform/forHanaNew.js +67 -183
  99. package/lib/transform/forOdataNew.js +17 -171
  100. package/lib/transform/localized.js +34 -19
  101. package/lib/transform/odata/generateForeignKeyElements.js +1 -1
  102. package/lib/transform/odata/referenceFlattener.js +95 -89
  103. package/lib/transform/odata/structureFlattener.js +1 -1
  104. package/lib/transform/odata/toFinalBaseType.js +86 -12
  105. package/lib/transform/odata/typesExposure.js +5 -5
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +36 -22
  108. package/lib/transform/translateAssocsToJoins.js +2 -19
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +170 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/objectUtils.js +30 -0
  114. package/package.json +1 -1
  115. package/share/messages/README.md +26 -0
  116. package/lib/compiler/definer.js +0 -2361
  117. package/lib/compiler/resolver.js +0 -3079
  118. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -7,7 +7,7 @@ const { getUtils,
7
7
  cloneCsn,
8
8
  forEachDefinition,
9
9
  forEachMemberRecursively,
10
- forEachRef,
10
+ applyTransformationsOnNonDictionary,
11
11
  getArtifactDatabaseNameOf,
12
12
  getElementDatabaseNameOf,
13
13
  isAspect,
@@ -16,7 +16,7 @@ const { getUtils,
16
16
  } = require('../model/csnUtils');
17
17
  const { checkCSNVersion } = require('../json/csnVersion');
18
18
  const validate = require('../checks/validator');
19
- const { isArtifactInSomeService, getServiceOfArtifact, isLocalizedArtifactInService } = require('./odata/utils');
19
+ const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./odata/utils');
20
20
  const ReferenceFlattener = require('./odata/referenceFlattener');
21
21
  const { flattenCSN } = require('./odata/structureFlattener');
22
22
  const generateForeignKeys = require('./odata/generateForeignKeyElements');
@@ -24,7 +24,8 @@ const expandStructKeysInAssociations = require('./odata/expandStructKeysInAssoci
24
24
  const expandToFinalBaseType = require('./odata/toFinalBaseType');
25
25
  const { timetrace } = require('../utils/timetrace');
26
26
  const { attachPath } = require('./odata/attachPath');
27
- const enrichUniversalCsn = require('./universalCsnEnricher');
27
+ const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
28
+ const generateDrafts = require('./draft/odata');
28
29
 
29
30
  const { addLocalizationViews } = require('./localized');
30
31
 
@@ -75,7 +76,7 @@ function transform4odataWithCsn(inputModel, options) {
75
76
  // copy the model as we don't want to change the input model
76
77
  let csn = cloneCsn(inputModel, options);
77
78
 
78
- const { error, warning, info, throwWithError } = makeMessageFunction(csn, options, 'for.odata');
79
+ const { message, error, warning, info, throwWithError } = makeMessageFunction(csn, options, 'for.odata');
79
80
  throwWithError();
80
81
 
81
82
  // the new transformer works only with new CSN
@@ -84,22 +85,16 @@ function transform4odataWithCsn(inputModel, options) {
84
85
  const transformers = transformUtils.getTransformers(csn, options, '_');
85
86
  const {
86
87
  addDefaultTypeFacets,
87
- createForeignKeyElement,
88
- createAndAddDraftAdminDataProjection, createScalarElement,
89
- createAssociationElement, createAssociationPathComparison,
90
- addElement, createAction, assignAction,
91
88
  extractValidFromToKeyElement,
92
89
  checkAssignment, checkMultipleAssignments,
93
- recurseElements, setAnnotation, resetAnnotation, renameAnnotation,
90
+ recurseElements, setAnnotation, renameAnnotation,
94
91
  expandStructsInExpression
95
92
  } = transformers;
96
93
 
97
94
  const csnUtils = getUtils(csn);
98
95
  const {
99
96
  getCsnDef,
100
- getFinalType,
101
97
  getServiceName,
102
- hasAnnotationValue,
103
98
  isAssocOrComposition,
104
99
  isAssociation,
105
100
  isStructured,
@@ -133,7 +128,7 @@ function transform4odataWithCsn(inputModel, options) {
133
128
  addLocalizationViews(csn, options, acceptLocalizedView);
134
129
 
135
130
  validate.forOdata(csn, {
136
- error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
131
+ message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
137
132
  });
138
133
 
139
134
 
@@ -209,19 +204,7 @@ function transform4odataWithCsn(inputModel, options) {
209
204
  // - structured types must not contain associations for OData V2
210
205
  // - Element must not be an 'array of' for OData V2 TODO: move to the validator
211
206
  // - Perform checks for exposed non-abstract entities and views - check media type and key-ness
212
- let visitedArtifacts = Object.create(null);
213
- forEachDefinition(csn, (def, defName) => {
214
- if (def.kind === 'entity' || def.kind === 'view') {
215
- // Generate artificial draft fields if requested
216
- if (def['@odata.draft.enabled']) {
217
- // Ignore if not part of a service
218
- if (isArtifactInSomeService(defName, services)) {
219
- generateDraftForOdata(def, defName, def, visitedArtifacts);
220
- }
221
- }
222
- }
223
- visitedArtifacts[defName] = true;
224
- }, { skipArtifact: isExternalServiceMember });
207
+ generateDrafts(csn, options, services)
225
208
 
226
209
  // Deal with all kind of annotations manipulations here
227
210
  forEachDefinition(csn, (def, defName) => {
@@ -342,7 +325,7 @@ function transform4odataWithCsn(inputModel, options) {
342
325
  // but '@Core.Immutable' for everything else.
343
326
  if (!(node['@readonly'] && node['@insertonly'])) {
344
327
  if (name === '@readonly' && node[name] !== null) {
345
- if (node.kind === 'entity' || node.kind === 'view') {
328
+ if (node.kind === 'entity') {
346
329
  setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
347
330
  setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
348
331
  setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
@@ -352,7 +335,7 @@ function transform4odataWithCsn(inputModel, options) {
352
335
  }
353
336
  // @insertonly is effective on entities/queries only
354
337
  else if (name === '@insertonly' && node[name] !== null) {
355
- if (node.kind === 'entity' || node.kind === 'view') {
338
+ if (node.kind === 'entity') {
356
339
  setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
357
340
  setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);
358
341
  setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
@@ -423,151 +406,14 @@ function transform4odataWithCsn(inputModel, options) {
423
406
  // removes leading $self in on-conditions's references
424
407
  function removeLeadingDollarSelfInOnCondition(assoc) {
425
408
  if (!assoc.on) return; // nothing to do
426
- forEachRef(assoc, (ref, node) => {
427
- // remove leading $self when at the begining of a ref
428
- if (ref.length > 1 && ref[0] === '$self')
429
- node.ref.splice(0, 1);
430
- });
431
- }
432
- }
433
-
434
- // Generate all that is required in ODATA for draft enablement of 'artifact' into the artifact,
435
- // into its transitively reachable composition targets, and into the model.
436
- // 'rootArtifact' is the root artifact where composition traversal started.
437
-
438
- // Constraints
439
- // Draft Root: Exactly one PK of type UUID
440
- // Draft Node: One PK of type UUID + 0..1 PK of another type
441
- // Draft Node: Must not be reachable from multiple draft roots
442
- function generateDraftForOdata(artifact, artifactName, rootArtifact, visitedArtifacts) {
443
- // Sanity check
444
- // @ts-ignore
445
- if (!isArtifactInSomeService(artifactName, services)) {
446
- throw new Error('Expecting artifact to be part of a service: ' + JSON.stringify(artifact));
447
- }
448
- // Nothing to do if already draft-enabled (composition traversal may have circles)
449
- if ((artifact['@Common.DraftRoot.PreparationAction'] || artifact['@Common.DraftNode.PreparationAction'])
450
- && artifact.actions && artifact.actions.draftPrepare) {
451
- return;
452
- }
453
-
454
- // Generate the DraftAdministrativeData projection into the service, unless there is already one
455
- // @ts-ignore
456
- let draftAdminDataProjectionName = `${getServiceOfArtifact(artifactName, services)}.DraftAdministrativeData`;
457
- let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
458
- if (!draftAdminDataProjection) {
459
- // @ts-ignore
460
- draftAdminDataProjection = createAndAddDraftAdminDataProjection(getServiceOfArtifact(artifactName, services));
461
- }
462
- // Report an error if it is not an entity or not what we expect
463
- if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements['DraftUUID']) {
464
- error(null, ['definitions', draftAdminDataProjectionName], { name: draftAdminDataProjectionName },
465
- `Generated entity $(NAME) conflicts with existing artifact`);
466
- }
467
- // Generate the annotations describing the draft actions (only draft roots can be activated/edited)
468
- if (artifact == rootArtifact) {
469
- resetAnnotation(artifact, '@Common.DraftRoot.ActivationAction', 'draftActivate', info, ['definitions', draftAdminDataProjectionName]);
470
- resetAnnotation(artifact, '@Common.DraftRoot.EditAction', 'draftEdit', info, ['definitions', draftAdminDataProjectionName]);
471
- resetAnnotation(artifact, '@Common.DraftRoot.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
472
- } else {
473
- resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
474
- }
475
-
476
- artifact.elements && Object.values(artifact.elements).forEach( elem => {
477
- // Make all non-key elements nullable
478
- if (elem.notNull && elem.key !== true) {
479
- delete elem.notNull;
480
- }
481
- });
482
- // Generate the additional elements into the draft-enabled artifact
483
-
484
- // key IsActiveEntity : Boolean default true
485
- let isActiveEntity = createScalarElement('IsActiveEntity', 'cds.Boolean', true, true, false);
486
- isActiveEntity.IsActiveEntity['@UI.Hidden'] = true;
487
- addElement(isActiveEntity, artifact, artifactName);
488
-
489
- // HasActiveEntity : Boolean default false
490
- let hasActiveEntity = createScalarElement('HasActiveEntity', 'cds.Boolean', false, false, true);
491
- hasActiveEntity.HasActiveEntity['@UI.Hidden'] = true;
492
- addElement(hasActiveEntity, artifact, artifactName);
493
-
494
- // HasDraftEntity : Boolean default false;
495
- let hasDraftEntity = createScalarElement('HasDraftEntity', 'cds.Boolean', false, false, true);
496
- hasDraftEntity.HasDraftEntity['@UI.Hidden'] = true;
497
- addElement(hasDraftEntity, artifact, artifactName);
498
-
499
- // @odata.contained: true
500
- // DraftAdministrativeData : Association to one DraftAdministrativeData;
501
- let draftAdministrativeData = createAssociationElement('DraftAdministrativeData', draftAdminDataProjectionName, true);
502
- draftAdministrativeData.DraftAdministrativeData.cardinality = { max: 1, };
503
- draftAdministrativeData.DraftAdministrativeData['@odata.contained'] = true;
504
- draftAdministrativeData.DraftAdministrativeData['@UI.Hidden'] = true;
505
- addElement(draftAdministrativeData, artifact, artifactName);
506
-
507
- // Note that we need to do the ODATA transformation steps for managed associations
508
- // (foreign key field generation, generatedFieldName) by hand, because the corresponding
509
- // transformation steps have already been done on all artifacts when we come here)
510
- let uuidDraftKey = draftAdministrativeData.DraftAdministrativeData.keys.filter(key => key.ref && key.ref.length === 1 && key.ref[0] === 'DraftUUID');
511
- if (uuidDraftKey && uuidDraftKey[0]) {
512
- uuidDraftKey = uuidDraftKey[0]; // filter returns an array, but it has only one element
513
- let path = ['definitions', artifactName, 'elements', 'DraftAdministrativeData', 'keys', 0];
514
- createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', uuidDraftKey, artifact, artifactName, path);
515
- }
516
- // SiblingEntity : Association to one <artifact> on (... IsActiveEntity unequal, all other key fields equal ...)
517
- let siblingEntity = createAssociationElement('SiblingEntity', artifactName, false);
518
- siblingEntity.SiblingEntity.cardinality = { max: 1 };
519
- addElement(siblingEntity, artifact, artifactName);
520
- // ... on SiblingEntity.IsActiveEntity != IsActiveEntity ...
521
- siblingEntity.SiblingEntity.on = createAssociationPathComparison('SiblingEntity', 'IsActiveEntity', '!=', 'IsActiveEntity');
522
-
523
- // Iterate elements
524
- artifact.elements && Object.entries(artifact.elements).forEach( ([elemName, elem]) => {
525
- if (elemName !== 'IsActiveEntity' && elem.key) {
526
- // Amend the ON-condition above:
527
- // ... and SiblingEntity.<keyfield> = <keyfield> ... (for all key fields except 'IsActiveEntity')
528
- let cond = createAssociationPathComparison('SiblingEntity', elemName, '=', elemName);
529
- cond.push('and');
530
- cond.push(...siblingEntity.SiblingEntity.on);
531
- siblingEntity.SiblingEntity.on = cond;
532
- }
533
-
534
- // Draft-enable the targets of composition elements (draft nodes), too
535
- // TODO rewrite
536
- if (elem.target && elem.type && getFinalType(elem.type) === 'cds.Composition') {
537
- let draftNode = csn.definitions[elem.target];
538
-
539
- // Ignore if that is our own draft root
540
- if (draftNode != rootArtifact) {
541
- // Report error when the draft node has @odata.draft.enabled itself
542
- if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
543
- error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
544
- }
545
- // Ignore composition if not part of a service or explicitly draft disabled
546
- else if (!getServiceName(elem.target) || hasAnnotationValue(draftNode, '@odata.draft.enabled', false)) {
547
- return;
548
- }
549
- else {
550
- // Generate draft stuff into the target
551
- generateDraftForOdata(draftNode, elem.target, rootArtifact, visitedArtifacts);
552
- }
409
+ // TODO: Shouldn't this only run on the on-condition and not the whole assoc-node?
410
+ applyTransformationsOnNonDictionary({ assoc }, 'assoc', {
411
+ ref: (node, prop, ref) => {
412
+ // remove leading $self when at the begining of a ref
413
+ if (ref.length > 1 && ref[0] === '$self')
414
+ node.ref.splice(0, 1);
553
415
  }
554
- }
555
- });
556
-
557
- // Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
558
-
559
- // action draftPrepare (SideEffectsQualifier: String) return <artifact>;
560
- let draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
561
- assignAction(draftPrepare, artifact);
562
-
563
- if (artifact == rootArtifact) {
564
- // action draftActivate() return <artifact>;
565
- let draftActivate = createAction('draftActivate', artifactName);
566
- assignAction(draftActivate, artifact);
567
-
568
- // action draftEdit (PreserveChanges: Boolean) return <artifact>;
569
- let draftEdit = createAction('draftEdit', artifactName, 'PreserveChanges', 'cds.Boolean');
570
- assignAction(draftEdit, artifact);
416
+ });
571
417
  }
572
418
  }
573
419
 
@@ -48,7 +48,7 @@ const _targetFor = Symbol('_targetFor');
48
48
  * We have three kinds of localized convenience views:
49
49
  *
50
50
  * 1. "direct ones" using coalesce() for the table entities with localized
51
- * elements: as projection on the original (created in definer.js)
51
+ * elements: as projection on the original (created in extend.js)
52
52
  * 2. for table entities with associations to entities which have a localized
53
53
  * convenience views or redirections thereon: as projection on the original
54
54
  * 3. for view entities with associations to entities which have a localized
@@ -74,11 +74,10 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
74
74
  if (hasErrors(options.messages))
75
75
  return csn;
76
76
 
77
- if (hasExistingLocalizationViews(csn, options))
77
+ const messageFunctions = makeMessageFunction(csn, options);
78
+ if (hasExistingLocalizationViews(csn, options, messageFunctions))
78
79
  return csn;
79
80
 
80
- const { info } = makeMessageFunction(csn, options);
81
-
82
81
  const noCoalesce = (options.localizedLanguageFallback === 'none' ||
83
82
  options.localizedWithoutCoalesce);
84
83
 
@@ -102,7 +101,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
102
101
  return;
103
102
 
104
103
  if (isInLocalizedNamespace(artName))
105
- // We already issued a warning for it in warnAboutExistingLocalizationViews()
104
+ // We already issued a warning for it in hasExistingLocalizationViews()
106
105
  return;
107
106
 
108
107
  const localized = getLocalizedTextElements( artName );
@@ -127,7 +126,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
127
126
 
128
127
  if (csn.definitions[viewName]) {
129
128
  // Already exists, skip creation.
130
- info( null, artPath, null, 'Convenience view can\'t be created due to conflicting names' );
129
+ messageFunctions.info( null, artPath, null, 'Convenience view can\'t be created due to conflicting names' );
131
130
  return;
132
131
  }
133
132
 
@@ -339,7 +338,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
339
338
  if (elem.key || elem.$key || elem.localized)
340
339
  textElements.push( elemName );
341
340
 
342
- // TODO: Already warned about in definer.js
341
+ // TODO: Already warned about in extend.js
343
342
  // if (elem.key && isLocalized)
344
343
  // warning( 'localized-key', path, {}, 'Keyword "localized" is ignored for primary keys' );
345
344
  }, artPath);
@@ -349,7 +348,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
349
348
  return null;
350
349
 
351
350
  if (!isEntityPreprocessed( art )) {
352
- info( null, artPath, { name: artName },
351
+ messageFunctions.info( null, artPath, { name: artName },
353
352
  'Skipped creation of convenience view for $(NAME) because the artifact is missing localization elements' );
354
353
  return null;
355
354
  }
@@ -358,13 +357,13 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
358
357
  const textsEntity = csn.definitions[textsName];
359
358
 
360
359
  if (!textsEntity) {
361
- info( null, artPath, { name: artName },
360
+ messageFunctions.info( null, artPath, { name: artName },
362
361
  'Skipped creation of convenience view for $(NAME) because its texts entity could not be found' );
363
362
  return null;
364
363
  }
365
364
 
366
365
  if (!isValidTextsEntity( textsEntity )) {
367
- info( null, [ 'definitions', textsName ], { name: artName },
366
+ messageFunctions.info( null, [ 'definitions', textsName ], { name: artName },
368
367
  'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid' );
369
368
  return null;
370
369
  }
@@ -685,18 +684,32 @@ function copyPersistenceAnnotations(target, source) {
685
684
  *
686
685
  * @param {CSN.Model} csn
687
686
  * @param {CSN.Options} options
687
+ * @param {object} messageFunctions
688
688
  */
689
- function hasExistingLocalizationViews(csn, options) {
689
+ function hasExistingLocalizationViews(csn, options, messageFunctions) {
690
690
  if (!csn || !csn.definitions)
691
691
  return false;
692
- const firstLocalizedView = Object.keys(csn.definitions).find(isInLocalizedNamespace);
693
- if (firstLocalizedView) {
694
- const { info } = makeMessageFunction(csn, options);
695
- info( null, [ 'definitions', firstLocalizedView ], {},
696
- 'Input CSN already contains expansions for localized data' );
697
- return true;
692
+
693
+ let hasExistingViews = false;
694
+ let hasNonViews = false;
695
+
696
+ for (const name in csn.definitions) {
697
+ const art = csn.definitions[name];
698
+ if (isInLocalizedNamespace(name) || name === 'localized') {
699
+ if (!art.query && !art.projection) {
700
+ if (!name.endsWith('.texts')) {
701
+ hasNonViews = true;
702
+ messageFunctions.error('reserved-namespace-localized', ['definitions', name], {},
703
+ 'The namespace "localized" is reserved for localization views');
704
+ }
705
+ } else if (!hasExistingViews) {
706
+ hasExistingViews = true;
707
+ messageFunctions.info( null, [ 'definitions', name ], {},
708
+ 'Input CSN already contains localization views, no further ones will be created' );
709
+ }
710
+ }
698
711
  }
699
- return false;
712
+ return hasExistingViews || hasNonViews;
700
713
  }
701
714
 
702
715
  /**
@@ -728,9 +741,10 @@ function isEntityPreprocessed(entity) {
728
741
 
729
742
  /**
730
743
  * @param {string} name
744
+ * @returns {boolean}
731
745
  */
732
746
  function isInLocalizedNamespace(name) {
733
- return name.startsWith('localized.');
747
+ return name === 'localized' || name.startsWith('localized.');
734
748
  }
735
749
 
736
750
  /**
@@ -738,6 +752,7 @@ function isInLocalizedNamespace(name) {
738
752
  *
739
753
  * @param {CSN.Model} csn
740
754
  * @param {string} artifactName
755
+ * @returns {boolean}
741
756
  */
742
757
  function hasLocalizedConvenienceView(csn, artifactName) {
743
758
  return !isInLocalizedNamespace(artifactName) && !!csn.definitions[`localized.${ artifactName }`];
@@ -37,7 +37,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
37
37
  sortedAssociations.forEach(item => {
38
38
  const { definitionName, structuralNodeName, elementName, element, parent, path } = item;
39
39
 
40
- if (csnUtils.isManagedAssociationElement(element) && element.keys) {
40
+ if (csnUtils.isManagedAssociation(element) && element.keys) {
41
41
  if (flatKeys) // tackling the ref value in assoc.keys
42
42
  takeoverForeignKeysOfTargetAssociations(element, path, generatedForeignKeyNamesForPath);
43
43
  // TODO: move in separate function
@@ -1,4 +1,4 @@
1
- const { forEachRef } = require('../../model/csnUtils');
1
+ const { applyTransformations } = require('../../model/csnUtils');
2
2
  const { setProp } = require('../../base/model');
3
3
  const { implicitAs } = require('../../model/csnRefs');
4
4
  const { structuralPath } = require('./structuralPath');
@@ -52,32 +52,34 @@ class ReferenceFlattener {
52
52
  * @param {*} isStructured Callback function checking of an artifact is a structured element.
53
53
  */
54
54
  resolveAllReferences(csn, inspectRef, isStructured) {
55
- forEachRef(csn, (_ref, node, path) => {
56
- if (!path) return;
57
- let resolved;
58
- try {
59
- resolved = inspectRef(path);
60
- } catch (ex) {
61
- return; // TODO: fix tests: throw Error("Could not inspectRef: " + path.join("/"));
62
- }
63
- if (!resolved)
64
- return; // TODO: fix tests: throw Error("Could not resolve: " + path.join("/"));
65
- if (!resolved.links)
66
- return; // TODO: fix tests: throw Error("Could not resolve links: " + path.join("/"));
67
- let paths = [];
68
- resolved.links.forEach((element) => {
69
- if (!element.art)
70
- paths = undefined; // not resolved -> no paths
71
- if (paths) {
72
- paths.push(element.art.$path);
55
+ applyTransformations(csn, {
56
+ ref: (node, prop, _ref, path) => {
57
+ if (!path) return;
58
+ let resolved;
59
+ try {
60
+ resolved = inspectRef(path);
61
+ } catch (ex) {
62
+ return; // TODO: fix tests: throw Error("Could not inspectRef: " + path.join("/"));
73
63
  }
74
- });
75
- if (paths)
76
- setProp(node, '$paths', paths);
77
- // cache if structured or not
78
- let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
79
- this.structuredReference[path.join('/')] = structured;
80
- });
64
+ if (!resolved)
65
+ return; // TODO: fix tests: throw Error("Could not resolve: " + path.join("/"));
66
+ if (!resolved.links)
67
+ return; // TODO: fix tests: throw Error("Could not resolve links: " + path.join("/"));
68
+ let paths = [];
69
+ resolved.links.forEach((element) => {
70
+ if (!element.art)
71
+ paths = undefined; // not resolved -> no paths
72
+ if (paths) {
73
+ paths.push(element.art.$path);
74
+ }
75
+ });
76
+ if (paths)
77
+ setProp(node, '$paths', paths);
78
+ // cache if structured or not
79
+ let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
80
+ this.structuredReference[path.join('/')] = structured;
81
+ }
82
+ })
81
83
  }
82
84
 
83
85
  /**
@@ -172,80 +174,84 @@ class ReferenceFlattener {
172
174
  * @param {*} csn
173
175
  */
174
176
  flattenAllReferences(csn) {
175
- forEachRef(csn, (ref, node, path) => {
176
- if (node.$paths) {
177
- let newRef = []; // flattened reference
178
- let flattenWithPrevious = false;
179
- let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
180
- node.$paths.forEach((path, i) => {
181
- if (path === undefined || !ref[i]) return;
182
- let spath = path.join('/');
183
- let movedTo = this.elementTransitions[spath]; // detect element transition
184
- let flattened = this.flattenedElementPaths[spath];
185
- if (flattenWithPrevious) {
186
- newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
187
- // if we have a filter or args in an assoc, it needs to be kept, therefore
188
- // the id of the ref is updated with the flattened version
189
- if (ref[i].id) {
190
- ref[i].id = newRef[newRef.length - 1];
191
- newRef[newRef.length - 1] = ref[i];
177
+ applyTransformations(csn, {
178
+ ref: (node, prop, ref, path) => {
179
+ if (node.$paths) {
180
+ let newRef = []; // flattened reference
181
+ let flattenWithPrevious = false;
182
+ let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
183
+ node.$paths.forEach((path, i) => {
184
+ if (path === undefined || !ref[i]) return;
185
+ let spath = path.join('/');
186
+ let movedTo = this.elementTransitions[spath]; // detect element transition
187
+ let flattened = this.flattenedElementPaths[spath];
188
+ if (flattenWithPrevious) {
189
+ newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
190
+ // if we have a filter or args in an assoc, it needs to be kept, therefore
191
+ // the id of the ref is updated with the flattened version
192
+ if (ref[i].id) {
193
+ ref[i].id = newRef[newRef.length - 1];
194
+ newRef[newRef.length - 1] = ref[i];
195
+ }
196
+ lastFlattenedID = i;
197
+ } else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
198
+ let movedToElementName = movedTo[movedTo.length-1];
199
+ newRef.push(movedToElementName);
200
+ lastFlattenedID = i;
201
+ } else {
202
+ newRef.push(ref[i]);
192
203
  }
193
- lastFlattenedID = i;
194
- } else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
195
- let movedToElementName = movedTo[movedTo.length-1];
196
- newRef.push(movedToElementName);
197
- lastFlattenedID = i;
198
- } else {
199
- newRef.push(ref[i]);
200
- }
201
- flattenWithPrevious = flattened;
202
- });
203
- if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
204
- // check if this is a column and add alias if missing
205
- let structural = structuralPath(csn, path);
206
- if (isColumnInSelectOrProjection(structural)) {
207
- if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
208
- node.as = node.ref[node.ref.length - 1];
209
- }
210
- setProp(newRef, '$path', path.concat('ref'));
211
- if (!node.as) {
212
- if (isPartOfKeysStructure(structural))
213
- node.as = implicitAs(node.ref);
214
- else
215
- setProp(node, 'as', implicitAs(node.ref))
204
+ flattenWithPrevious = flattened;
205
+ });
206
+ if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
207
+ // check if this is a column and add alias if missing
208
+ let structural = structuralPath(csn, path);
209
+ if (isColumnInSelectOrProjection(structural)) {
210
+ if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
211
+ node.as = node.ref[node.ref.length - 1];
212
+ }
213
+ setProp(newRef, '$path', path.concat('ref'));
214
+ if (!node.as) {
215
+ if (isPartOfKeysStructure(structural))
216
+ node.as = implicitAs(node.ref);
217
+ else
218
+ setProp(node, 'as', implicitAs(node.ref))
219
+ }
220
+ node.ref = newRef;
216
221
  }
217
- node.ref = newRef;
218
222
  }
219
223
  }
220
224
  })
221
225
  }
222
226
 
223
227
  applyAliasesInOnCond(csn, inspectRef) {
224
- forEachRef(csn, (ref, node, path) => {
225
- // Process only on-conditions of associations
226
- let structural = structuralPath(csn, path);
227
- if(!isOnCondition(structural)) return;
228
- let { links } = inspectRef(path);
229
- if(!links) return; // $user not resolvable
230
- let keysOfPreviousStepWhenManagedAssoc = undefined;
231
-
232
- let aliasedRef = [...ref];
233
-
234
- for (let idx = 0; idx < ref.length; idx++) {
235
- const currArt = links[idx].art;
236
- if (keysOfPreviousStepWhenManagedAssoc) {
237
- const usedKey = keysOfPreviousStepWhenManagedAssoc.find(key => key.ref[0] === ref[idx]);
238
- if (usedKey && usedKey.as) {
239
- aliasedRef.splice(idx, usedKey.ref.length, usedKey.as);
240
- idx += usedKey.ref.length - 1;
241
- keysOfPreviousStepWhenManagedAssoc = undefined;
228
+ applyTransformations(csn, {
229
+ ref: (node, prop, ref, path) => {
230
+ // Process only on-conditions of associations
231
+ let structural = structuralPath(csn, path);
232
+ if(!isOnCondition(structural)) return;
233
+ let { links } = inspectRef(path);
234
+ if(!links) return; // $user not resolvable
235
+ let keysOfPreviousStepWhenManagedAssoc = undefined;
236
+
237
+ let aliasedRef = [...ref];
238
+
239
+ for (let idx = 0; idx < ref.length; idx++) {
240
+ const currArt = links[idx].art;
241
+ if (keysOfPreviousStepWhenManagedAssoc) {
242
+ const usedKey = keysOfPreviousStepWhenManagedAssoc.find(key => key.ref[0] === ref[idx]);
243
+ if (usedKey && usedKey.as) {
244
+ aliasedRef.splice(idx, usedKey.ref.length, usedKey.as);
245
+ idx += usedKey.ref.length - 1;
246
+ keysOfPreviousStepWhenManagedAssoc = undefined;
247
+ }
248
+ } else {
249
+ keysOfPreviousStepWhenManagedAssoc =
250
+ (currArt.type === 'cds.Association' || currArt.type === 'cds.Composition') && currArt.keys;
242
251
  }
243
- } else {
244
- keysOfPreviousStepWhenManagedAssoc =
245
- (currArt.type === 'cds.Association' || currArt.type === 'cds.Composition') && currArt.keys;
246
252
  }
253
+ node.ref = aliasedRef;
247
254
  }
248
- node.ref = aliasedRef;
249
255
  })
250
256
  }
251
257
  }
@@ -71,7 +71,7 @@ function flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExterna
71
71
  * @param {Function} error
72
72
  */
73
73
  function flattenDefinition(definition, definitionPath, csnUtils, options, referenceFlattener, error) {
74
- if (definition.kind !== 'entity' && definition.kind !== 'view')
74
+ if (definition.kind !== 'entity')
75
75
  return;
76
76
 
77
77
  let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);