@sap/cds-compiler 4.0.2 → 4.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -13,6 +13,7 @@ const {
13
13
  forAllQueries,
14
14
  sortCsnDefinitionsForTests,
15
15
  } = require('../model/csnUtils');
16
+ const {CompilerAssertion} = require('../base/error');
16
17
 
17
18
  /**
18
19
  * Indicator that a definition is localized and has a convenience view.
@@ -34,10 +35,11 @@ const _isViewForEntity = Symbol('_isViewForEntity'); // $inferred = 'LOCALIZED-H
34
35
  * Used to transitively create convenience views.
35
36
  */
36
37
  const _targetFor = Symbol('_targetFor');
38
+ const annoPersistenceSkip = '@cds.persistence.skip';
37
39
 
38
40
  /**
39
41
  * Callback function returning `true` if the localization view should be created.
40
- * @callback acceptLocalizedView
42
+ * @callback AcceptLocalizedViewCallback
41
43
  * @param {string} viewName localization view name
42
44
  * @param {string} originalName Artifact name of the original view
43
45
  */
@@ -45,20 +47,22 @@ const _targetFor = Symbol('_targetFor');
45
47
  /**
46
48
  * Create transitive localized convenience views.
47
49
  *
48
- * A convenience view is created if the entity/view has a localized element
50
+ * A convenience view is created if the entity/view has a localized element[^1]
49
51
  * or if it exposes an association leading to a localized-tagged target.
50
52
  *
51
53
  * INTERNALS:
52
54
  * We have three kinds of localized convenience views:
53
55
  *
54
56
  * 1. "direct ones" using coalesce() for the table entities with localized
55
- * elements: as projection on the original (created in extend.js)
56
- * 2. for table entities with associations to entities which have a localized
57
- * convenience views or redirections thereon: as projection on the original
58
- * 3. for view entities with associations to entities which have a localized
59
- * convenience views or redirections thereon: as entity using the same
60
- * query as the original, but replacing all sources by their localized
61
- * convenience view variant if present
57
+ * elements[^1]: as projection on the original and '.texts' entity (created in extend.js)
58
+ * 2. for _table_ entities with associations to entities which have a localized
59
+ * convenience: as projection on the original
60
+ * 3. for _view_ entities with either localized elements[^1] or associations
61
+ * to entities which have a localized convenience view:
62
+ * as view using a copy of the original query, but replacing all sources by
63
+ * their localized convenience view variant if present
64
+ *
65
+ * [^1]: That is, the element has `localized: true`.
62
66
  *
63
67
  * First, all "direct ones" are built (1). Then we build all 2 and 3
64
68
  * transitively (i.e. as long as an entity has an association which directly or
@@ -68,21 +72,46 @@ const _targetFor = Symbol('_targetFor');
68
72
  * variant if present.
69
73
  *
70
74
  * @param {CSN.Model} csn
71
- * @param {CSN.Options} options
72
- * @param {boolean} useJoins If true, rewrite the "localized" association to a
73
- * join in direct convenience views.
75
+ * Input CSN model. Should not have existing convenience views.
76
+ *
77
+ * @param {object} options
78
+ * CSN options. Only few options are used, see below for important ones.
79
+ * Options such as `testMode` or `testSortCsn` can also be set.
80
+ *
81
+ * @param {string} [options.localizedLanguageFallback]
82
+ * Valid values (if set): 'none', 'coalesce' (default)
83
+ * Whether to use a `coalesce()` function when selecting from `.texts` entities.
84
+ * If not set, untranslated strings may not return any value. If 'coalesce'
85
+ * is used, it will fall back to the original string.
86
+ *
87
+ * @param {boolean} [options.localizedWithoutCoalesce]
88
+ * Deprecated version of localizedLanguageFallback. Do not use.
89
+ *
90
+ * @param {boolean} [options.fewerLocalizedViews]
91
+ *
74
92
  * @param {object} config
93
+ * Configuration for creating convenience views. Non-user visible options.
94
+ *
95
+ * @param {boolean} [config.useJoins]
96
+ * If true, rewrite the "localized" association to a join in direct convenience views.
97
+ *
98
+ * @param {AcceptLocalizedViewCallback} [config.acceptLocalizedView]
99
+ * A callback that can be used to suppress the creation of localized convenience views
100
+ * if desired. For example, if you want to know which definitions get a convenience view
101
+ * but don't actually want to create them.
102
+ *
103
+ * @param {boolean} [config.ignoreUnknownExtensions]
104
+ * If true, do not emit a warning for annotations on unknown `localized.*` views.
75
105
  */
76
- function _addLocalizationViews(csn, options, useJoins, config) {
106
+ function _addLocalizationViews(csn, options, config) {
77
107
  const messageFunctions = makeMessageFunction(csn, options);
78
- if (checkExistingLocalizationViews(csn, options, messageFunctions)) {
79
- messageFunctions.throwWithError();
108
+ if (checkExistingLocalizationViews(csn, options, messageFunctions))
80
109
  return csn;
81
- }
82
110
 
83
- const { acceptLocalizedView, ignoreUnknownExtensions } = config;
111
+ const { useJoins, acceptLocalizedView, ignoreUnknownExtensions } = config;
84
112
  const noCoalesce = (options.localizedLanguageFallback === 'none' ||
85
113
  options.localizedWithoutCoalesce);
114
+ const ignoreAssocToLocalized = !!options.fewerLocalizedViews;
86
115
 
87
116
  createDirectConvenienceViews(); // 1
88
117
  createTransitiveConvenienceViews(); // 2 + 3
@@ -179,10 +208,10 @@ function _addLocalizationViews(csn, options, useJoins, config) {
179
208
  copyLocation(convenienceView.query, entity);
180
209
 
181
210
  if (shouldUseJoin)
182
- // Expand elements:
211
+ // Expand elements; (variant 1)
183
212
  columns.push( ...columnsForEntityWithExcludeList( entity, 'L_0', textElements ) )
184
213
  else
185
- columns.push( '*' );
214
+ columns.push( '*' ); // (variant 2)
186
215
 
187
216
  for (const originalElement of textElements) {
188
217
  const elem = entity.elements[originalElement];
@@ -330,8 +359,8 @@ function _addLocalizationViews(csn, options, useJoins, config) {
330
359
  let keyCount = 0;
331
360
  let textElements = [];
332
361
 
333
- forEachGeneric(art, 'elements', (elem, elemName /*, prop, path*/) => {
334
- if (elem._ignore) // from HANA backend
362
+ forEachGeneric(art, 'elements', (elem, elemName , _prop) => {
363
+ if (elem.$ignore) // from SAP HANA backend
335
364
  return;
336
365
 
337
366
  if (elem.key || elem.$key)
@@ -339,10 +368,6 @@ function _addLocalizationViews(csn, options, useJoins, config) {
339
368
 
340
369
  if (elem.key || elem.$key || elem.localized)
341
370
  textElements.push( elemName );
342
-
343
- // TODO: Already warned about in extend.js
344
- // if (elem.key && isLocalized)
345
- // warning( 'localized-key', path, {}, 'Keyword "localized" is ignored for primary keys' );
346
371
  }, artPath);
347
372
 
348
373
  if (textElements.length <= keyCount || keyCount <= 0)
@@ -363,12 +388,17 @@ function _addLocalizationViews(csn, options, useJoins, config) {
363
388
  'Skipped creation of convenience view for $(NAME) because its texts entity could not be found' );
364
389
  return null;
365
390
  }
366
-
367
391
  if (!isValidTextsEntity( textsEntity )) {
368
392
  messageFunctions.info( null, [ 'definitions', textsName ], { name: artName },
369
393
  'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid' );
370
394
  return null;
371
395
  }
396
+ if (!art[annoPersistenceSkip] && textsEntity[annoPersistenceSkip]) {
397
+ messageFunctions.message( 'anno-unexpected-localized-skip', artPath,
398
+ { name: textsName, art: artName, anno: annoPersistenceSkip },
399
+ 'Compiler generated entity $(NAME) must not be annotated with $(ANNO) if $(ART) is not skipped' );
400
+ return null;
401
+ }
372
402
 
373
403
  // There may be keys in the original artifact that were added by the core compiler,
374
404
  // for example elements that are marked @cds.valid.from.
@@ -390,22 +420,23 @@ function _addLocalizationViews(csn, options, useJoins, config) {
390
420
 
391
421
  /**
392
422
  * Transitively create convenience views for entities/views that have
393
- * associations to localized entities or to views that themselves have such
394
- * a dependency.
423
+ * associations to localized entities, views that themselves have such
424
+ * a dependency or views that contain projections on localized elements.
395
425
  *
396
426
  * The algorithm is as follows:
397
427
  *
398
- * 1. For each view/entity with associations:
399
- * - If target is NOT localized => add view/entity to target's `_targetFor` property
400
- * - If target is localized => add view/entity to array `entities`
428
+ * 1. For each view with elements that have `localized: true` markers:
429
+ * => add view to array `entities`
430
+ * For each view/entity with associations:
431
+ * - If target is NOT localized => add view/entity to target's `_targetFor` property
432
+ * - If target is localized => add view/entity to array `entities`
401
433
  * 2. As long as `entities` has entries:
402
- * a. For each entry in `entities`
403
- * - Create a convenience view
404
- * - If the entry has a `_targetFor` property, add its entries to
405
- * `nextEntities` because they now have a transitive dependency on a
406
- * localized view.
407
- * b. Copy all entries from `nextEntities` to `entities`.
408
- * c. Clear `nextEntities`.
434
+ * a. For each entry in `entities`
435
+ * - Create a convenience view
436
+ * - If the entry has a `_targetFor` property, add its entries to `nextEntities`
437
+ * because they now have a transitive dependency on a localized view.
438
+ * b. Copy all entries from `nextEntities` to `entities`.
439
+ * c. Clear `nextEntities`.
409
440
  * 3. Rewrite all references to the localized variants.
410
441
  */
411
442
  function createTransitiveConvenienceViews() {
@@ -447,10 +478,9 @@ function _addLocalizationViews(csn, options, useJoins, config) {
447
478
  // if the artifact is an entity (already processed in (1))
448
479
  entities.push(artName);
449
480
  }
450
- else if (elem.target) {
481
+ else if (!ignoreAssocToLocalized && elem.target) {
451
482
  // If the target has a localized view then we are localized as well.
452
483
  const def = csn.definitions[elem.target];
453
- // TODO: What if elem.target cannot be found? Could this happen after flattening, ...?
454
484
  if (!def)
455
485
  continue;
456
486
 
@@ -489,7 +519,7 @@ function _addLocalizationViews(csn, options, useJoins, config) {
489
519
 
490
520
  addLocalizedView(artName);
491
521
 
492
- if (art[_targetFor])
522
+ if (!ignoreAssocToLocalized && art[_targetFor])
493
523
  nextEntities.push(...art[_targetFor]);
494
524
  delete art[_targetFor];
495
525
  }
@@ -577,14 +607,22 @@ function _addLocalizationViews(csn, options, useJoins, config) {
577
607
  if (!obj || !obj.ref)
578
608
  return;
579
609
  const ref = Array.isArray(obj.ref) ? obj.ref[0] : obj.ref;
580
- if (typeof ref !== 'string')
581
- return;
582
- const def = csn.definitions[ref];
583
- if (def && def[_hasLocalizedView]) {
584
- if (Array.isArray(obj.ref))
585
- obj.ref[0] = def[_hasLocalizedView];
586
- else
610
+ if (typeof ref === 'string') {
611
+ const def = csn.definitions[ref];
612
+ if (def && def[_hasLocalizedView]) {
613
+ if (Array.isArray(obj.ref))
614
+ obj.ref[0] = def[_hasLocalizedView];
615
+ else
587
616
  obj.ref = def[_hasLocalizedView];
617
+ }
618
+
619
+ } else if (ref.id) {
620
+ const def = csn.definitions[ref.id];
621
+ if (def && def[_hasLocalizedView])
622
+ obj.ref[0].id = def[_hasLocalizedView];
623
+
624
+ } else if (options.testMode) {
625
+ throw new CompilerAssertion('Debug me: Unhandled reference during localized-rewrite!');
588
626
  }
589
627
  }
590
628
 
@@ -592,7 +630,7 @@ function _addLocalizationViews(csn, options, useJoins, config) {
592
630
  * @param {string} artName
593
631
  */
594
632
  function textsEntityName(artName) {
595
- // We can assume, that the element exists. This is checked in isEntityPreprocessed()
633
+ // We can assume that the element exists. This is checked in isEntityPreprocessed().
596
634
  return csn.definitions[artName].elements.texts.target;
597
635
  }
598
636
 
@@ -625,23 +663,23 @@ function _addLocalizationViews(csn, options, useJoins, config) {
625
663
  *
626
664
  * @param {CSN.Model} csn
627
665
  * @param {CSN.Options} options
628
- * @param [config] config.acceptLocalizedView: optional callback function returning true if the localized view name and its parent name provided as parameter should be created
666
+ * @param [config]
629
667
  */
630
668
  function addLocalizationViews(csn, options, config = {}) {
631
- return _addLocalizationViews(csn, options, false, config);
669
+ return _addLocalizationViews(csn, options, { ...config, useJoins: false });
632
670
  }
633
671
 
634
672
  /**
635
673
  * Create transitive localized convenience views to the given CSN but
636
674
  * rewrite the "localized" association to joins in direct entity convenience
637
- * views. This is needed by e.g. SQL for SQLite where A2J is used.
675
+ * views. This is needed e.g. by SQL for SQLite where A2J is used.
638
676
  *
639
677
  * @param {CSN.Model} csn
640
678
  * @param {CSN.Options} options
641
- * @param [config] config.acceptLocalizedView: optional callback function returning true if the localized view name and its parent name provided as parameter should be created
679
+ * @param [config]
642
680
  */
643
681
  function addLocalizationViewsWithJoins(csn, options, config = {}) {
644
- return _addLocalizationViews(csn, options, true, config);
682
+ return _addLocalizationViews(csn, options, { ...config, useJoins: true });
645
683
  }
646
684
 
647
685
  /**
@@ -94,7 +94,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
94
94
  function expandToFinalBaseType(node, defName) {
95
95
  if (!node) return;
96
96
  // TODO: Clarify how should events be handled?
97
- // They are not treated by the transformUtilsNew::toFinalBaseType function
97
+ // They are not treated by the transformUtils::toFinalBaseType function
98
98
  // in the same manner as named types, because the elements of structured events are not
99
99
  // propagated as it is with types.
100
100
  // It is ok to skip the expansion to the final base type for now as events are not rendered in
@@ -130,7 +130,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
130
130
  */
131
131
  if (isBuiltinType(finalBaseType.type)) {
132
132
  /*
133
- use transformUtilsNew::toFinalBaseType for the moment,
133
+ use transformUtils::toFinalBaseType for the moment,
134
134
  as it is collects along the chain of types
135
135
  attributes that need to be propagated
136
136
  enum, length, scale, etc.
@@ -161,7 +161,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
161
161
  */
162
162
  if (isBuiltinType(finalBaseType.type)) {
163
163
  /*
164
- use transformUtilsNew::toFinalBaseType for the moment,
164
+ use transformUtils::toFinalBaseType for the moment,
165
165
  as it is collects along the chain of types
166
166
  attributes that need to be propagated
167
167
  enum, length, scale, etc.
@@ -265,7 +265,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
265
265
  } else {
266
266
  // Primitive child - clone it and restore its cross references
267
267
  let flatElemName = elemName + pathDelimiter + childName;
268
- let flatElem = cloneCsnNonDict(childElem, options);
268
+ let flatElem = cloneCsnNonDict(childElem, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
269
269
  // Don't take over notNull from leaf elements
270
270
  delete flatElem.notNull;
271
271
  setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));
@@ -420,17 +420,10 @@ function getTransformers(model, options, pathDelimiter = '_') {
420
420
  if (typeof type === 'string' && isBuiltinType(type))
421
421
  return;
422
422
 
423
- let typeRef = null;
424
- if (resolved.has(type)) {
425
- typeRef = resolved.get(type)?.art
426
- // The cached entry may not be resolved, yet.
427
- if (typeRef.type && !isBuiltinType(typeRef.type))
428
- typeRef = getFinalTypeInfo(typeRef.type);
429
- } else {
430
- typeRef = getFinalTypeInfo(type);
431
- }
423
+ const typeRef = getFinalTypeInfo(type, (t) => resolved.get(t)?.art || csnUtils.artifactRef(t));
432
424
  if(!typeRef)
433
425
  return;
426
+
434
427
  if (typeRef.elements || typeRef.items) {
435
428
  // Copy elements/items and we're finished. No need to look up actual base type,
436
429
  // since it must also be structured and must contain at least as many elements,
@@ -543,7 +536,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
543
536
  addElement(draftUuid, artifact, artifactName);
544
537
 
545
538
  // CreationDateTime : Timestamp;
546
- const creationDateTime = createScalarElement('CreationDateTime', hanaMode ? 'cds.UTCTimestamp' : 'cds.Timestamp');
539
+ const creationDateTime = createScalarElement('CreationDateTime', 'cds.Timestamp');
547
540
  creationDateTime.CreationDateTime['@Common.Label'] = '{i18n>Draft_CreationDateTime}';
548
541
  addElement(creationDateTime, artifact, artifactName);
549
542
 
@@ -560,7 +553,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
560
553
  addElement(draftIsCreatedByMe, artifact, artifactName);
561
554
 
562
555
  // LastChangeDateTime : Timestamp;
563
- const lastChangeDateTime = createScalarElement('LastChangeDateTime', hanaMode ? 'cds.UTCTimestamp' : 'cds.Timestamp');
556
+ const lastChangeDateTime = createScalarElement('LastChangeDateTime', 'cds.Timestamp');
564
557
  lastChangeDateTime.LastChangeDateTime['@Common.Label'] = '{i18n>Draft_LastChangeDateTime}';
565
558
  addElement(lastChangeDateTime, artifact, artifactName);
566
559
 
@@ -909,7 +902,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
909
902
  if (annotation === undefined) {
910
903
  throw new CompilerAssertion('Annotation ' + fromName + ' not found in ' + JSON.stringify(node));
911
904
  }
912
- if(node[toName] === undefined || node[toName] === null) {
905
+ if(node[toName] == null) {
913
906
  delete node[fromName];
914
907
  node[toName] = annotation;
915
908
  }
@@ -931,9 +924,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
931
924
  if (value === undefined) {
932
925
  throw new CompilerAssertion('Annotation value must not be undefined');
933
926
  }
934
-
935
- if(node[name] === undefined || node[name] === null)
936
- node[name] = value;
927
+ node[name] ??= value;
937
928
  }
938
929
 
939
930
  /**
@@ -1184,13 +1175,12 @@ function getTransformers(model, options, pathDelimiter = '_') {
1184
1175
  // do the paths match?
1185
1176
  if(op !== 'like' && !(x.lhs && x.rhs)) {
1186
1177
  if(xn.length) {
1187
- error(null, location,
1188
- {
1189
- prefix: prefix(lhs, op, rhs),
1190
- name: xn,
1191
- alias: (x.lhs ? rhs : lhs).ref.join('.')
1192
- },
1193
- '$(PREFIX): Sub path $(NAME) not found in $(ALIAS)');
1178
+ error('expr-invalid-expansion', location, {
1179
+ value: prefix(lhs, op, rhs),
1180
+ name: xn,
1181
+ alias: (x.lhs ? rhs : lhs).ref.join('.')
1182
+ },
1183
+ 'Missing sub path $(NAME) in $(ALIAS) for tuple expansion of $(VALUE); both sides must expand to the same sub paths');
1194
1184
  }
1195
1185
  else {
1196
1186
  error(null, location,
@@ -1352,93 +1342,6 @@ function getTransformers(model, options, pathDelimiter = '_') {
1352
1342
 
1353
1343
  }
1354
1344
 
1355
- /**
1356
- * Modify the given CSN/artifact in-place, applying the given customTransformations.
1357
- * Dictionaries are correctly handled - a "type" transformer will not be called on an entity called "type".
1358
- *
1359
- * A custom transformation function has the following signature:
1360
- * (any, object, string, CSN.Path) => undefined
1361
- *
1362
- * Given that we have a custom transformation for "type" and stumble upon a thing like below:
1363
- *
1364
- * {
1365
- * type: "cds.String",
1366
- * anotherProp: 1
1367
- * }
1368
- *
1369
- * The input for the function would be:
1370
- *
1371
- * ("cds.String", { type: <>, anotherProp: <>}, "type", [xy, "type"])
1372
- *
1373
- * @param {CSN.Model} csn
1374
- * @param {object} customTransformations Dictionary of functions to apply - if the property matches a key in this dict, it will be called
1375
- * @param {boolean} [transformNonEnumerableElements=false] Transform non-enumerable elements to work with cds linked...
1376
- * @returns {CSN.Model|CSN.Artifact} Return the CSN/artifact
1377
- */
1378
- function transformModel(csn, customTransformations, transformNonEnumerableElements=false){
1379
- const transformers = {
1380
- elements: dictionary,
1381
- definitions: dictionary,
1382
- actions: dictionary,
1383
- params: dictionary,
1384
- enum: dictionary,
1385
- mixin: dictionary,
1386
- args: dictionary
1387
- };
1388
-
1389
- const csnPath = [];
1390
- if (csn.definitions)
1391
- dictionary( csn, 'definitions', csn.definitions );
1392
- else {
1393
- // fake it till you make it
1394
- const obj = { definitions: Object.create(null)};
1395
- obj.definitions.thing = csn;
1396
- dictionary(obj, 'definitions', obj.definitions);
1397
- }
1398
-
1399
- return csn;
1400
-
1401
- function standard( parent, prop, node ) {
1402
- // checking for .kind and .type is safe because annotations with such properties, are already flattened out
1403
- const isAnnotation = () => (typeof prop === 'string' && prop.startsWith('@') && !node.kind && !node.type);
1404
- if (!node || node._ignore || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ) || isAnnotation())
1405
- return;
1406
-
1407
- csnPath.push( prop );
1408
-
1409
- if (Array.isArray(node)) {
1410
- node.forEach( (n, i) => standard( node, i, n ) );
1411
- }
1412
- else {
1413
- const iterateOver = Object.getOwnPropertyNames( node );
1414
- // cds-linked resolves types and add's them to elements as non-enum - need to be processed
1415
- if(transformNonEnumerableElements && node.elements && !Object.prototype.propertyIsEnumerable.call(node, 'elements')){
1416
- iterateOver.push('elements');
1417
- }
1418
- for (const name of iterateOver) {
1419
- if(customTransformations[name])
1420
- customTransformations[name](node[name], node, name, csnPath.concat(name))
1421
-
1422
- const trans = transformers[name] || standard;
1423
- trans( node, name, node[name] );
1424
- }
1425
- }
1426
- csnPath.pop();
1427
- }
1428
- function dictionary( node, prop, dict ) {
1429
- csnPath.push( prop );
1430
-
1431
- if (Array.isArray(dict)) {
1432
- dict.forEach( (n, i) => standard(dict, i, n))
1433
- } else {
1434
- for (const name of Object.getOwnPropertyNames( dict ))
1435
- standard( dict, name, dict[name] );
1436
- }
1437
-
1438
- csnPath.pop();
1439
- }
1440
- }
1441
-
1442
1345
  /**
1443
1346
  * Mandatory input transformation for all backends:
1444
1347
  * Replace
@@ -1465,7 +1368,6 @@ function rewriteBuiltinTypeRef(csn) {
1465
1368
  module.exports = {
1466
1369
  // This function retrieves the actual exports
1467
1370
  getTransformers,
1468
- transformModel,
1469
1371
  RelationalOperators,
1470
1372
  rewriteBuiltinTypeRef,
1471
1373
  };
@@ -1124,9 +1124,14 @@ function translateAssocsToJoins(model, inputOptions = {})
1124
1124
  // get paths of managed assocs (unmanaged assocs are not allowed in FK paths)
1125
1125
  if(element.foreignKeys)
1126
1126
  {
1127
- for(let fkn in element.foreignKeys)
1127
+ for(const fkn in element.foreignKeys)
1128
1128
  {
1129
- let fk = element.foreignKeys[fkn];
1129
+ const fk = element.foreignKeys[fkn];
1130
+ // ignore an unmanaged association
1131
+ if(fk.targetElement._artifact.target &&
1132
+ fk.targetElement._artifact.on &&
1133
+ !fk.targetElement._artifact.foreignKeys)
1134
+ continue;
1130
1135
  // once a fk is to be followed, treat all sub-paths as srcSide, this will add fk.name.id only
1131
1136
  if(srcSide)
1132
1137
  paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fk.name.id, fk.targetElement.path.map(ps => ps.id).join(pathDelimiter)));
@@ -1146,9 +1151,9 @@ function translateAssocsToJoins(model, inputOptions = {})
1146
1151
  // get paths of plain structured elements
1147
1152
  else if(element.elements)
1148
1153
  {
1149
- for(let n in element.elements)
1154
+ for(const n in element.elements)
1150
1155
  {
1151
- let elt = element.elements[n];
1156
+ const elt = element.elements[n];
1152
1157
  paths = paths.concat(flattenElement(elt, true, elt.name.id, elt.name.id));
1153
1158
  }
1154
1159
  }
@@ -1384,7 +1389,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1384
1389
 
1385
1390
  All prefix trees are put underneath the $tableAlias structure with attribute $qat or $fqat.
1386
1391
  Each path step appears exactly once for a given filter condition in the prefix tree and
1387
- has a link to it's definition (origin). The default filter is an empty string ''.
1392
+ has a link to its definition (origin). The default filter is an empty string ''.
1388
1393
 
1389
1394
  A special note on paths in filter conditions. Filter paths are treated like postfix
1390
1395
  paths to an association path step, meaning, they are inserted into the assoc's $qat or $fqat
@@ -1409,32 +1414,13 @@ function translateAssocsToJoins(model, inputOptions = {})
1409
1414
 
1410
1415
  let [head, ...tail] = path;
1411
1416
 
1412
- if(['$projection', '$self'].includes(head.id) && tail.length && head._navigation.kind === '$self') {
1413
- // make sure not to truncate tail
1414
- if(tail.length > 1)
1415
- [head, ...tail] = tail;
1416
- else
1417
- head = tail[0];
1418
- /*
1419
- if the head is a path (it better be;) then use it as
1420
- anchor for _navigation and just merge the tail into that QAT
1421
- example:
1422
- entity E { key id: Integer; toE: association to E; toF: association to F;}
1423
- entity F { key id: Integer; toE: association to E; }
1424
- view V as select from E { toE, $projection.toE.toF.id };
1425
- */
1426
- let value = env.lead.elements[head.id].value;
1427
- if(value.path) {
1428
- head = value.path[0];
1429
- }
1430
- else // value is another expression, don't consider it
1431
- return;
1432
- }
1433
-
1434
-
1435
1417
  // qatParent is the node where the starting qat is attached to
1436
1418
  let qatParent = undefined;
1437
1419
 
1420
+ // Note: If head is $self, we would need to resolve it if the path follows associations.
1421
+ // However, that is already rejected by SQL backend checks. For example $self paths
1422
+ // can't be "$self.assoc.foo.bar".
1423
+
1438
1424
  // FROM and filter paths do not have a _navigation, but for filter paths
1439
1425
  // the corresponding path step (to where the filter was attached to) is in env.pathStep
1440
1426
  if(!head._navigation)
package/lib/utils/file.js CHANGED
@@ -101,22 +101,22 @@ function cdsFs( fileCache, enableTrace ) {
101
101
  if (body && body.stack && body.message) {
102
102
  // NOTE: checks for instanceof Error are not reliable if error
103
103
  // created in different execution env
104
- traceFS( 'READFILE:cache-error:', filename, body.message );
104
+ traceFS( 'READFILE:cache-err:', filename, body.message );
105
105
  cb( body ); // no need for process.nextTick( cb, body ) with moduleResolve
106
106
  }
107
107
  else {
108
- traceFS( 'READFILE:cache:', filename, body );
108
+ traceFS( 'READFILE:cache: ', filename, body );
109
109
  cb( null, body );
110
110
  }
111
111
  }
112
112
  else {
113
- traceFS( 'READFILE:start:', filename );
113
+ traceFS( 'READFILE:start: ', filename );
114
114
  // TODO: set cache directly to some "delay" - store error differently?
115
115
  // e.g. an error of callback functions!
116
116
  try {
117
117
  reader(filename, enc, (err, data) => {
118
118
  fileCache[filename] = err || data;
119
- traceFS('READFILE:data:', filename, err || data);
119
+ traceFS('READFILE:data: ', filename, err || data);
120
120
  cb(err, data);
121
121
  });
122
122
  }
@@ -137,14 +137,14 @@ function cdsFs( fileCache, enableTrace ) {
137
137
  return ( filename, cb ) => {
138
138
  let body = fileCache[filename];
139
139
  if (body !== undefined) {
140
- traceFS( 'ISFILE:cache:', filename, body );
140
+ traceFS( 'ISFILE:cache: ', filename, body );
141
141
  if (body instanceof Error)
142
142
  cb( body ); // no need for process.nextTick( cb, body ) with moduleResolve
143
143
  else // body could be empty string
144
144
  cb( null, !!body || typeof body === 'string');
145
145
  }
146
146
  else {
147
- traceFS( 'ISFILE:start:', filename, body );
147
+ traceFS( 'ISFILE:start: ', filename, body );
148
148
  // in the future (if we do module resolve ourselves with just readFile),
149
149
  // we avoid parallel readFile by storing having an array of `cb`s in
150
150
  // fileCache[ filename ] before starting fs.readFile().
@@ -156,7 +156,7 @@ function cdsFs( fileCache, enableTrace ) {
156
156
  body = !!(stat.isFile() || stat.isFIFO());
157
157
  if (fileCache[filename] === undefined) // parallel readFile() has been processed
158
158
  fileCache[filename] = body;
159
- traceFS('ISFILE:data:', filename, body);
159
+ traceFS('ISFILE:data: ', filename, body);
160
160
  if (body instanceof Error)
161
161
  cb(err);
162
162
  else