@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
@@ -191,10 +191,10 @@ function transform4odataWithCsn(inputModel, options) {
191
191
  generateForeignKeys(csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember);
192
192
 
193
193
  // Apply default type facets as set by options
194
- // Flatten on-conditions in unmanaged associations
194
+ // Flatten on-conditions in unmanaged associations
195
195
  /* FIXME (HJB): Is this comment still correct? processOnCond only strips $self
196
196
  We should not remove $self prefixes in structured OData to not
197
- interfer with path resolution
197
+ interfer with path resolution
198
198
  */
199
199
  // This must be done before all the draft logic as all
200
200
  // composition targets are annotated with @odata.draft.enabled in this step
@@ -303,8 +303,9 @@ function transform4odataWithCsn(inputModel, options) {
303
303
  // If @Core.Computed is explicitly set, don't overwrite it!
304
304
  if (node['@Core.Computed'] !== undefined) return;
305
305
 
306
- // For @odata.on.insert/update, also add @Core.Computed
307
- if (node['@odata.on.insert'] || node['@odata.on.update'])
306
+ // For @odata.on.insert/update, also add @Core.Computed
307
+ // @odata.on is deprecated, use @cds.on {update|insert} instead
308
+ if(['@odata.on.insert', '@odata.on.update', '@cds.on.insert', '@cds.on.update'].some(a => node[a]))
308
309
  node['@Core.Computed'] = true;
309
310
  }
310
311
 
@@ -5,14 +5,12 @@ const { setProp } = require('../base/model');
5
5
  const { hasErrors } = require('../base/messages');
6
6
  const { cloneCsnDictionary } = require('../model/csnUtils');
7
7
  const { cleanSymbols } = require('../base/cleanSymbols.js');
8
- const { rejectManagedAssociationsAndStructuresForHdbcsNames } = require('../checks/selectItems');
9
8
  const {
10
9
  cloneCsn,
11
10
  forEachDefinition,
12
11
  forEachGeneric,
13
12
  forAllQueries,
14
13
  sortCsnDefinitionsForTests,
15
- getUtils,
16
14
  } = require('../model/csnUtils');
17
15
 
18
16
  /**
@@ -79,7 +77,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
79
77
  if (hasExistingLocalizationViews(csn, options))
80
78
  return csn;
81
79
 
82
- const { info, error } = makeMessageFunction(csn, options);
80
+ const { info } = makeMessageFunction(csn, options);
83
81
 
84
82
  const noCoalesce = (options.localizedLanguageFallback === 'none' ||
85
83
  options.localizedWithoutCoalesce);
@@ -87,16 +85,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
87
85
  createDirectConvenienceViews(); // 1
88
86
  createTransitiveConvenienceViews(); // 2 + 3
89
87
 
90
- forEachDefinition(csn, (definition, artName, prop, path) => {
91
- cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor)
92
- if(definition.query) {
93
- // reject managed association and structure publishing for to-hdbcds.hdbcds
94
- const that = { csnUtils: getUtils(csn), options, error };
95
- rejectManagedAssociationsAndStructuresForHdbcsNames.call(that, definition, path)
96
- }
97
- });
98
-
99
-
88
+ forEachDefinition(csn, definition => cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor));
100
89
 
101
90
  sortCsnDefinitionsForTests(csn, options);
102
91
  return csn;
@@ -144,7 +133,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
144
133
 
145
134
  art[_hasLocalizedView] = viewName;
146
135
 
147
- if(acceptLocalizedView && !acceptLocalizedView(viewName, artName))
136
+ if (acceptLocalizedView && !acceptLocalizedView(viewName, artName))
148
137
  return;
149
138
 
150
139
  let view;
@@ -201,13 +201,13 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
201
201
  // Transfer selected type properties from target key element
202
202
  // FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property.
203
203
  for (let prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) {
204
- if (fkArtifact[prop] != undefined) {
204
+ if (fkArtifact[prop] !== undefined) {
205
205
  foreignKeyElement[prop] = fkArtifact[prop];
206
206
  }
207
207
  }
208
208
  // If the association is non-fkArtifact resp. key, so should be the foreign key field
209
209
  for (let prop of ['notNull', 'key']) {
210
- if (assoc[prop] != undefined) {
210
+ if (assoc[prop] !== undefined) {
211
211
  foreignKeyElement[prop] = assoc[prop];
212
212
  }
213
213
  }
@@ -9,7 +9,7 @@ const { setProp } = require('../base/model');
9
9
  const { csnRefs } = require('../model/csnRefs');
10
10
 
11
11
  const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
12
- const { cloneCsn, getUtils, isBuiltinType } = require('../model/csnUtils');
12
+ const { cloneCsn, cloneCsnDictionary, getUtils, isBuiltinType } = require('../model/csnUtils');
13
13
 
14
14
  // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
15
15
  // Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
@@ -108,7 +108,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
108
108
  // Transfer selected type properties from target key element
109
109
  // FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property.
110
110
  for (const prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) {
111
- if (fkArtifact[prop] != undefined) {
111
+ if (fkArtifact[prop] !== undefined) {
112
112
  foreignKeyElement[prop] = fkArtifact[prop];
113
113
  }
114
114
  }
@@ -118,7 +118,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
118
118
 
119
119
  // If the association is non-fkArtifact resp. key, so should be the foreign key field
120
120
  for (const prop of ['notNull', 'key']) {
121
- if (assoc[prop] != undefined) {
121
+ if (assoc[prop] !== undefined) {
122
122
  foreignKeyElement[prop] = assoc[prop];
123
123
  }
124
124
  }
@@ -302,7 +302,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
302
302
  * [ (Entity), (struct1), (struct2), (assoc), (elem) ] should result in
303
303
  * [ (Entity), (struct1_struct2_assoc), (elem) ]
304
304
  *
305
- * @param {string[]} ref
305
+ * @param {string[]} ref
306
306
  * @param {CSN.Path} path CSN path to the ref
307
307
  * @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
308
308
  * @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
@@ -351,7 +351,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
351
351
 
352
352
  /**
353
353
  * Copy properties of the referenced type, but don't resolve to the final base type.
354
- *
354
+ *
355
355
  * Do not copy the length if it was just set via the default-value.
356
356
  *
357
357
  * @param {any} node Node to copy to
@@ -387,7 +387,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
387
387
  * also unravel derived enum types, i.e. take the final base type of the enum's base type.
388
388
  * Similar with associations and compositions (we probably need a _baseType link)
389
389
  *
390
- * @param {CSN.Artifact} node
390
+ * @param {CSN.Artifact} node
391
391
  * @param {WeakMap} [resolved] WeakMap containing already resolved refs
392
392
  * @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
393
393
  * @returns {void}
@@ -406,7 +406,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
406
406
  delete node.type; // delete the type reference as edm processing does not expect it
407
407
  } else if(finalBaseType.items) {
408
408
  // This changes the order - to be discussed!
409
- node.items = cloneCsn(finalBaseType, options).items; // copy items
409
+ node.items = cloneCsn(finalBaseType.items, options); // copy items
410
410
  delete node.type;
411
411
  } else {
412
412
  node.type=finalBaseType;
@@ -424,16 +424,16 @@ function getTransformers(model, options, pathDelimiter = '_') {
424
424
  return;
425
425
 
426
426
  // cloneCsn only works correctly if we start "from the top"
427
- const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options);
427
+ const cloneTypeDef = cloneCsn(typeDef, options);
428
428
  // With hdbcds-hdbcds, don't resolve structured types - but propagrate ".items", to turn into LargeString later on.
429
429
  if(typeDef.items) {
430
430
  delete node.type;
431
- Object.assign(node, {items: clone.definitions.TypeDef.items});
431
+ Object.assign(node, {items: cloneTypeDef.items});
432
432
  }
433
433
  if(typeDef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
434
434
  if(!typeDef.items)
435
435
  delete node.type;
436
- Object.assign(node, {elements: clone.definitions.TypeDef.elements});
436
+ Object.assign(node, {elements: cloneTypeDef.elements});
437
437
  }
438
438
 
439
439
 
@@ -441,7 +441,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
441
441
  }
442
442
  // if the declared element is an enum, these values are with priority
443
443
  if (!node.enum && typeDef.enum) {
444
- const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options).definitions.TypeDef.enum;
444
+ const clone = cloneCsnDictionary(typeDef.enum, options);
445
445
  Object.assign(node, { enum: clone });
446
446
  }
447
447
  if (node.length === undefined && typeDef.length !== undefined)
@@ -865,7 +865,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
865
865
  function checkMultipleAssignments(array, annoName, artifact, artifactName, err = true) {
866
866
  if (array.length > 1) {
867
867
  const loc = ['definitions', artifactName];
868
- if (err == true) {
868
+ if (err === true) {
869
869
  error(null, loc, { anno: annoName }, `Annotation $(ANNO) must be assigned only once`);
870
870
  } else {
871
871
  warning(null, loc, { anno: annoName },`Annotation $(ANNO) must be assigned only once`);
@@ -1084,7 +1084,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
1084
1084
  * Expand structured expression arguments to flat reference paths.
1085
1085
  * Structured elements are real sub element lists and managed associations.
1086
1086
  * All unmanaged association definitions are rewritten if applicable (elements/mixins).
1087
- * Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
1087
+ * Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
1088
1088
  * .xpr in columns.
1089
1089
  *
1090
1090
  * @todo Check if can be skipped for abstract entity and or cds.persistence.skip ?
@@ -315,7 +315,7 @@ function translateAssocsToJoins(model, inputOptions = {})
315
315
  }
316
316
  // if the found QA is the mixin QA and if the path length is one,
317
317
  // this indicates the publishing of a mixin assoc, don't rewrite the path
318
- if(QA.mixin && tail.length == 1)
318
+ if(QA.mixin && tail.length === 1)
319
319
  return;
320
320
  let pos = tail.indexOf(ps);
321
321
  // cut off ps if it's a join relevant association with postfix
@@ -670,7 +670,7 @@ function translateAssocsToJoins(model, inputOptions = {})
670
670
  if(fwdAssoc)
671
671
  {
672
672
  //env.assocStack.includes(fwdAssoc) => recursion
673
- if(env.assocStack.length == 2) {
673
+ if(env.assocStack.length === 2) {
674
674
  // reuse (ugly) error message from forHana
675
675
  error(null, env.assocStack[0].location,
676
676
  { name: '$self', id: '$self' },
@@ -702,7 +702,7 @@ function translateAssocsToJoins(model, inputOptions = {})
702
702
  // ON cond of the forward assoc with swapped src/tgt aliases
703
703
  let fwdAssoc = getForwardAssociationExpr(expr);
704
704
  if(fwdAssoc) {
705
- if(env.assocStack.length == 2) {
705
+ if(env.assocStack.length === 2) {
706
706
  // reuse (ugly) error message from forHana
707
707
  error(null, expr.location, 'An association that uses “$self” in its ON-condition can\'t be compared to “$self”');
708
708
  // don't check these paths again
@@ -736,7 +736,7 @@ function translateAssocsToJoins(model, inputOptions = {})
736
736
  let newSrcAlias = tgtAlias;
737
737
  let newTgtAlias = {};
738
738
  // first try to identify table alias for complex views or redirected associations
739
- if(fwdAssoc._redirected && fwdAssoc._redirected.length &&
739
+ if(fwdAssoc._redirected && fwdAssoc._redirected.length &&
740
740
  // redirected target must have a $QA
741
741
  fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA &&
742
742
  // $QA's artifact must either be same srcAlias artifact
@@ -912,7 +912,7 @@ function translateAssocsToJoins(model, inputOptions = {})
912
912
  combined list of elements made available by the from clause.
913
913
  */
914
914
  let _navigation = undefined; // don't modify original path
915
- if(env.assocStack.length == 2) {
915
+ if(env.assocStack.length === 2) {
916
916
  // a mixin assoc cannot have a structure prefix, it's sufficient to check head
917
917
  if(head.id === env.assocStack.id()) {
918
918
  // source side from view point of view (target side from forward point of view)
@@ -974,7 +974,7 @@ function translateAssocsToJoins(model, inputOptions = {})
974
974
 
975
975
  // Return the original association if expr is a backlink term, undefined otherwise
976
976
  function getForwardAssociationExpr(expr) {
977
- if(expr.op && expr.op.val === '=' && expr.args.length == 2) {
977
+ if(expr.op && expr.op.val === '=' && expr.args.length === 2) {
978
978
  return getForwardAssociation(expr.args[0].path, expr.args[1].path);
979
979
  }
980
980
  return undefined;
@@ -983,10 +983,10 @@ function translateAssocsToJoins(model, inputOptions = {})
983
983
  function getForwardAssociation(lhs, rhs) {
984
984
  // [alpha.]BACKLINK.[beta.]FORWARD
985
985
  if(lhs && rhs) {
986
- if(rhs.length == 1 && rhs[0].id === '$self' &&
986
+ if(rhs.length === 1 && rhs[0].id === '$self' &&
987
987
  lhs.length > 1 && hasPrefix(lhs))
988
988
  return lhs[lhs.length-1]._artifact;
989
- if(lhs.length == 1 && lhs[0].id === '$self' &&
989
+ if(lhs.length === 1 && lhs[0].id === '$self' &&
990
990
  rhs.length > 1 && hasPrefix(rhs))
991
991
  return rhs[rhs.length-1]._artifact;
992
992
  }
@@ -1,67 +1,237 @@
1
1
  'use strict';
2
2
 
3
- const { forEachDefinition } = require('../base/model');
3
+ const { setProp } = require('../base/model');
4
4
  const {
5
- applyTransformations,
6
- cloneCsn,
5
+ forEachDefinition,
6
+ forAllQueries,
7
7
  getUtils,
8
- isBuiltinType,
8
+ forEachMember,
9
+ forEachMemberRecursively,
9
10
  } = require('../model/csnUtils');
10
11
 
11
12
  /**
12
- * Loop through a universal CSN and enrich it with the properties
13
+ * Loop through a universal CSN and enrich it with the properties/annotations
13
14
  * from the source definition - modifies the input model in-place
14
- *
15
+ *
15
16
  * @param {CSN.Model} csn
16
17
  * @param {CSN.Options} options
17
18
  */
18
- module.exports = function(csn, options) {
19
- let { getOrigin, getFinalType, getFinalTypeDef } = getUtils(csn);
20
- // User-defined structured types do not have the elements propagated any longer
21
- // if there is no association among the elements. For that reason,
22
- // as a first step propagate the elements of these
23
- forEachDefinition(csn, (def) => {
24
- if (def.kind === 'type' && def.type && !def.elements) {
25
- const finalType = getFinalType(def.type);
26
- if (isBuiltinType(finalType)) return;
27
- const finalTypeDef = getFinalTypeDef(def.type);
28
- if (finalTypeDef.elements)
29
- def.elements = cloneCsn(finalTypeDef.elements, options);
19
+ module.exports = function (csn, options) {
20
+ let { getOrigin, getQueryPrimarySource, artifactRef } = getUtils(csn);
21
+
22
+ // Properties on definition level that we treat specially.
23
+ // TODO: There might be more annotations that will need special treatment
24
+ // see lib/compiler/propagator.js for reference
25
+ const defProps = {
26
+ '@cds.autoexpose': onlyViaArtifact,
27
+ '@fiori.draft.enabled': onlyViaArtifact,
28
+ '@': (prop, target, source) => { target[prop] = source[prop] },
29
+ // Example: `type E : F;` does not have `elements`, but they are required for e.g. OData.
30
+ 'elements': onlyTypeDef,
31
+ }
32
+
33
+ // In this first loop through the model, missing properties in universal CSN
34
+ // are propagated so the CSN can become client one
35
+ forEachDefinition(csn, propagate);
36
+
37
+ // The $origin properties need to be removed separately
38
+ // as the values are used in csnRef::getOrigin that is used during
39
+ // the propagation above.
40
+ // Currently testMode-only for comparison against client CSN.
41
+ if (options.testMode) removeDollarProperties(csn);
42
+
43
+ /**
44
+ * Identify the sources of the passed object and propagate the relevant
45
+ * properties/annotations.
46
+ *
47
+ * @param {Object} art Target object for propagation
48
+ */
49
+ function propagate(art) {
50
+ // check if art was already processed by the status flag
51
+ // TODO: clean up later on, together with validator clean up probably or
52
+ // when this module is meant to be used standalone -> use internal cache to store already processed definitions?
53
+ if (art._status === 'propagated') return;
54
+
55
+ // collect chain of origins and propagate
56
+ // from the farthest to the nearest one to the target
57
+ let chain = [];
58
+ let target = art;
59
+ let origin = undefined;
60
+ do {
61
+ origin = getOrigin(target);
62
+ if (origin) {
63
+ chain.push({ target, origin });
64
+ target = origin;
65
+ }
66
+ } while (origin);
67
+
68
+ if (chain.length)
69
+ chain.reverse().forEach(propagateSingleStep);
70
+ else
71
+ // even if there weren't any found origin(s) on definition level
72
+ // we need to loop through the members
73
+ // TODO: construct use-/test-case where there might be the need that an origin chain
74
+ // needs to be constructed for members as well, specifically for this 'else'
75
+ // case where we do not run through the definitions
76
+ propagateMembersProps(target);
77
+
78
+ function propagateSingleStep({ target, origin }) {
79
+ // if target was already processed -> continue
80
+ if (target._status === 'propagated') return;
81
+ // propagate relevant definition level properties
82
+ // we check for kind as in the future the function should be
83
+ // generic and work for parts of CSN
84
+ if (target.kind) propagateDefProps(target, origin);
85
+ // propagate properties to members
86
+ propagateMembersProps(target);
87
+
88
+ setProp(target, '_status', 'propagated');
89
+ }
90
+
91
+ /**
92
+ * Propagate from 'source' to 'target' the relevant properties
93
+ * for CSN definitions.
94
+ *
95
+ * @param {CSN.Definition} target
96
+ * @param {CSN.Definition} source
97
+ */
98
+ function propagateDefProps(target, source) {
99
+ const keys = Object.keys(source);
100
+ for (const prop of keys) {
101
+ // do not overwrite properties in target def
102
+ if (!(prop in target)) {
103
+ const func = defProps[prop] || defProps[prop.charAt(0)];
104
+ if (func) func(prop, target, source);
105
+ }
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Propagate properties from Universal to Client CSN relevant for members.
111
+ *
112
+ * @param {CSN.Artifact} target
113
+ */
114
+ function propagateMembersProps(target) {
115
+ // TODO: in the future, consider case when target is a member itself,
116
+ // for when we do not run with the complete CSN
117
+ // TODO: use newly added 'forEachMemberRecursivelyWithQuery'
118
+ forEachMemberRecursively(target, (member) => {
119
+ propagateMemberPropsFromOrigin(member);
120
+ if (member.target && !member.keys && !member.on)
121
+ calculateForeignKeys(member);
122
+ });
123
+ target.query && forAllQueries(target.query, (query) => {
124
+ if (query.SELECT && query.SELECT.elements) {
125
+ forEachMember(query.SELECT, (member) => {
126
+ propagateMemberPropsFromOrigin(member);
127
+ if (member.target && !member.keys && !member.on)
128
+ calculateForeignKeys(member);
129
+ });
130
+ }
131
+ });
132
+ setProp(target, '_status', 'propagated');
30
133
  }
31
- });
32
-
33
- // as a second step, loop through all the $origin properties in the model
34
- // and propagate the properties from the origin definition
35
- applyTransformations(csn, {
36
- '$origin': (node, _$orign, $originValue, _path, parent, propName) => {
37
- if (!node.kind) { // we do not want to replace whole definitions
38
- if (Array.isArray($originValue))
39
- propagatePropsFromOrigin(node, propName, parent);
40
- else if ($originValue.$origin && Array.isArray($originValue.$origin)) {
41
- // cover the case of query entity elements where we have own and ihnerited attributes/annotations
42
- propagatePropsFromOrigin($originValue, propName, parent);
134
+
135
+ function propagateMemberPropsFromOrigin(member) {
136
+ // For empty members (`{}`), the origin was set in a previous call to `getOrigin(definition)`.
137
+ const memberOrigin = getOrigin(member);
138
+ if (!memberOrigin) return;
139
+
140
+ // when having an element with a type property that is
141
+ // user-defined there is no need to propagate 'elements',
142
+ // 'kind', etc. from the origin (which is the type definition)
143
+ if (member.type && Object.keys(member).length === 1) return;
144
+
145
+ const keys = Object.keys(memberOrigin);
146
+ // Copy over properties from the origin element.
147
+ // Don't propagate "kind" as this property is not allowed in elements.
148
+ for (const key of keys) {
149
+ if (!(key in member) && key !== 'kind')
150
+ member[key] = memberOrigin[key];
151
+ }
152
+
153
+ // copy over own annotations/properties
154
+ // TODO: try to create use-/test-case where this needs
155
+ // to be applied on definition level, ATM it is done only for members
156
+ if (member.$origin && !Array.isArray(member.$origin) && member.$origin.$origin) {
157
+ const ownKeys = Object.keys(member.$origin);
158
+ for (const key of ownKeys) {
159
+ if (key !== '$origin')
160
+ member[key] = member.$origin[key];
43
161
  }
162
+ }
44
163
 
164
+ // In case of managed composition an anonymous $origin is used.
165
+ // csnRefs::getOrigin returns {} for such a member, thus have to recreate the client CSN from
166
+ // the values in the $origin. PR #8072
167
+ if (!Object.keys(memberOrigin).length && member.$origin && member.$origin.type === 'cds.Composition') {
168
+ member.type = member.$origin.type;
169
+ member.cardinality = member.$origin.cardinality;
170
+ member.targetAspect = member.$origin.target;
45
171
  }
46
172
  }
47
- }, undefined, undefined, options);
48
-
49
- function propagatePropsFromOrigin(member, memberName, construct) {
50
- // TODO: shall the $origin be kept as part of the element?
51
- const origin = getOrigin(member);
52
- if (origin.kind) return;
53
- if (member.elements && origin.type) {
54
- delete member.$origin;
55
- member.type = origin.type;
56
- return;
173
+
174
+ function calculateForeignKeys(member) {
175
+ // managed assocs in universal CSN have no longer keys
176
+ // if they are not explicitly defined - PR#8064
177
+ const target = artifactRef(member.target);
178
+ const targetKeys = Object.keys(target.elements).filter((key) => target.elements[key].key);
179
+ member.keys = targetKeys.map(
180
+ keyName => { return { ref: [keyName] } }
181
+ );
57
182
  }
58
- let newMember = cloneCsn(origin, options);
59
- // keep targets and keys of assoc, if it was redirected
60
- if (origin.type === 'cds.Association') {
61
- newMember.target = member.target || newMember.target;
62
- newMember.keys = member.keys || newMember.keys;
183
+
184
+ }
185
+
186
+ /**
187
+ * @cds.autoexpose for example, is propagated only if at definition level and only if
188
+ * the primary source (left-most) does not follow an association.
189
+ *
190
+ * @param {String} prop
191
+ * @param {CSN.Definition} target
192
+ * @param {CSN.Definition} source
193
+ */
194
+ function onlyViaArtifact(prop, target, source) {
195
+ if (!target.kind) return;
196
+ const primarySourceRef = getQueryPrimarySource(target.query || target.projection);
197
+ const artRef = primarySourceRef ? artifactRef(primarySourceRef) : source;
198
+ if (!artRef.target) {
199
+ target[prop] = source[prop];
63
200
  }
64
- // TODO: check if this works fine for items/returns/actions
65
- construct[memberName] = newMember;
201
+ }
202
+
203
+ /**
204
+ * Execute only if the target definition is a user-defined type.
205
+ *
206
+ * @param {String} prop
207
+ * @param {CSN.Definition} target
208
+ * @param {CSN.Definition} source
209
+ * @returns
210
+ */
211
+ function onlyTypeDef(prop, target, source) {
212
+ if (target.kind !== 'type') return;
213
+ target[prop] = source[prop];
214
+ }
215
+
216
+ /**
217
+ * Removes every occurrence of '$origin' and '$generated'
218
+ * for compatibility with what we have in client CSN.
219
+ *
220
+ * @param {CSN.Model} csn
221
+ */
222
+ function removeDollarProperties(csn) {
223
+ forEachDefinition(csn, (def) => {
224
+ delete def.$origin;
225
+ delete def.$generated;
226
+ // TODO: use newly added 'forEachMemberRecursivelyWithQuery'
227
+ forEachMemberRecursively(def, (member) => delete member.$origin);
228
+ def.query && forAllQueries(def.query, (query) => {
229
+ if (query.SELECT && query.SELECT.elements) {
230
+ forEachMember(query.SELECT, (member) => {
231
+ delete member.$origin;
232
+ });
233
+ }
234
+ });
235
+ })
66
236
  }
67
237
  }
package/lib/utils/file.js CHANGED
@@ -7,7 +7,7 @@ const util = require('util');
7
7
 
8
8
  /**
9
9
  * Split the given source string into its lines. Respects Unix,
10
- * Windows und Macintosh line breaks.
10
+ * Windows and Macintosh line breaks.
11
11
  *
12
12
  * @param {string} src
13
13
  * @returns {string[]}
@@ -48,6 +48,7 @@ function cdsFs(fileCache, enableTrace) {
48
48
  });
49
49
 
50
50
  return {
51
+ /** @type {function(string, string)} */
51
52
  readFileAsync: util.promisify(readFile),
52
53
  readFile,
53
54
  readFileSync,
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * A single TimeTrace encapsulates the runtime of a selected code frame.
4
+ * A single StopWatch encapsulates the runtime of a selected code frame.
5
5
  *
6
6
  * @class TimeTrace
7
7
  */
@@ -14,6 +14,8 @@ class StopWatch {
14
14
  */
15
15
  constructor(id) {
16
16
  this.id = id;
17
+ // TODO: If we require Node 12, use process.hrtime.bigint()
18
+ // as process.hrtime() is deprecated.
17
19
  // eslint-disable-next-line no-multi-assign
18
20
  this.startTime = this.lapTime = process.hrtime();
19
21
  }
@@ -123,4 +125,8 @@ const ignoreTimeTrace = {
123
125
  };
124
126
 
125
127
  const doTimeTrace = process && process.env && process.env.CDSC_TIMETRACING !== undefined;
126
- module.exports = { timetrace: (doTimeTrace ? new TimeTracer() : ignoreTimeTrace), StopWatch };
128
+ module.exports = {
129
+ timetrace: (doTimeTrace ? new TimeTracer() : ignoreTimeTrace),
130
+ TimeTracer,
131
+ StopWatch,
132
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "2.11.4",
3
+ "version": "2.12.0",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",