@sap/cds-compiler 4.0.2 → 4.1.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 (84) hide show
  1. package/CHANGELOG.md +100 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +31 -11
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +1 -1
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +5 -4
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/modelCompare/compare.js +112 -39
  54. package/lib/modelCompare/utils/filter.js +54 -24
  55. package/lib/optionProcessor.js +6 -6
  56. package/lib/render/manageConstraints.js +20 -17
  57. package/lib/render/toCdl.js +34 -20
  58. package/lib/render/toHdbcds.js +2 -2
  59. package/lib/render/toRename.js +4 -9
  60. package/lib/render/toSql.js +77 -26
  61. package/lib/render/utils/common.js +3 -3
  62. package/lib/render/utils/unique.js +52 -0
  63. package/lib/transform/db/applyTransformations.js +61 -20
  64. package/lib/transform/db/assertUnique.js +7 -8
  65. package/lib/transform/db/associations.js +2 -2
  66. package/lib/transform/db/cdsPersistence.js +8 -8
  67. package/lib/transform/db/expansion.js +17 -21
  68. package/lib/transform/db/flattening.js +23 -23
  69. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  70. package/lib/transform/db/temporal.js +1 -1
  71. package/lib/transform/db/transformExists.js +8 -7
  72. package/lib/transform/db/views.js +73 -33
  73. package/lib/transform/draft/db.js +11 -9
  74. package/lib/transform/draft/odata.js +1 -1
  75. package/lib/transform/{forOdataNew.js → forOdata.js} +6 -6
  76. package/lib/transform/forRelationalDB.js +69 -75
  77. package/lib/transform/localized.js +6 -5
  78. package/lib/transform/odata/toFinalBaseType.js +3 -3
  79. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  80. package/lib/transform/translateAssocsToJoins.js +14 -28
  81. package/package.json +1 -1
  82. package/share/messages/check-proper-type-of.md +1 -1
  83. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  84. package/share/messages/message-explanations.json +1 -1
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { forEachDefinition, hasAnnotationValue } = require('../../model/csnUtils');
4
- const { getTransformers } = require('../transformUtilsNew');
5
- const { setProp } = require('../../base/model');
4
+ const { getTransformers } = require('../transformUtils');
6
5
  const { pathName } = require('../../compiler/utils');
7
6
 
8
7
 
@@ -97,7 +96,7 @@ function processAssertUnique( csn, options, error, info ) {
97
96
  }
98
97
  // 10) Store remaining paths (if any) in constraint dictionary
99
98
  if (flattenedPathObjects.length)
100
- constraintDict[constraintName.join('.')] = flattenedPathObjects;
99
+ constraintDict[constraintName.join('.')] = { paths: flattenedPathObjects, parentTable: artifactName };
101
100
  }
102
101
  }
103
102
 
@@ -110,9 +109,9 @@ function processAssertUnique( csn, options, error, info ) {
110
109
  // preserve dictionary in '$tableConstraints' on the artifact for path rewriting and rendering
111
110
  if (Object.keys(constraintDict).length) {
112
111
  if (!('$tableConstraints' in artifact))
113
- setProp(artifact, '$tableConstraints', Object.create(null));
112
+ artifact.$tableConstraints = Object.create(null);
114
113
 
115
- setProp(artifact.$tableConstraints, 'unique', constraintDict);
114
+ artifact.$tableConstraints.unique = constraintDict;
116
115
  }
117
116
 
118
117
  /**
@@ -286,7 +285,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
286
285
  }
287
286
  for (const uniqueConstraint in artifact.$tableConstraints.unique) {
288
287
  // iterate over each constraint
289
- const c = uniqueConstraints[uniqueConstraint];
288
+ const c = uniqueConstraints[uniqueConstraint].paths;
290
289
  const rewrittenPaths = [];
291
290
  // and inspect each path of the constraint
292
291
  c.forEach((cpath) => {
@@ -319,7 +318,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
319
318
  }
320
319
  });
321
320
  // preserve the rewritten and filtered paths for toSql
322
- uniqueConstraints[uniqueConstraint] = rewrittenPaths;
321
+ uniqueConstraints[uniqueConstraint] = { paths: rewrittenPaths, parentTable: uniqueConstraints[uniqueConstraint].parentTable };
323
322
 
324
323
  // now add the index for HANA CDS
325
324
  if (options.transformation === 'hdbcds') {
@@ -335,7 +334,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
335
334
  artifact.technicalConfig.hana.indexes[uniqueConstraint] = index;
336
335
  }
337
336
  }
338
- setProp(artifact.$tableConstraints, 'unique', uniqueConstraints);
337
+ artifact.$tableConstraints.unique = uniqueConstraints;
339
338
  }
340
339
  }
341
340
  }
@@ -48,8 +48,8 @@ function attachOnConditions( csn, csnUtils, pathDelimiter ) {
48
48
  // Assemble an ON-condition with the foreign keys created in earlier steps
49
49
  const onCondParts = [];
50
50
  let joinWithAnd = false;
51
- if (elem.keys.length === 0) { // TODO: really kill instead of _ignore?
52
- elem._ignore = true;
51
+ if (elem.keys.length === 0) { // TODO: really kill instead of $ignore?
52
+ elem.$ignore = true;
53
53
  }
54
54
  else {
55
55
  for (const foreignKey of elem.keys) {
@@ -3,13 +3,13 @@
3
3
  const {
4
4
  forEachGeneric, forEachMemberRecursively, hasAnnotationValue, isPersistedOnDatabase,
5
5
  } = require('../../model/csnUtils');
6
- const transformUtils = require('../transformUtilsNew');
6
+ const transformUtils = require('../transformUtils');
7
7
 
8
8
  const exists = '@cds.persistence.exists';
9
9
 
10
10
  /**
11
11
  * Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
12
- * with _ignore.
12
+ * with $ignore.
13
13
  *
14
14
  * @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
15
15
  */
@@ -24,12 +24,12 @@ function getAnnoProcessor() {
24
24
  hasAnnotationValue(artifact, '@cds.persistence.skip') ||
25
25
  hasAnnotationValue(artifact, exists));
26
26
  if (ignoreArtifact)
27
- artifact._ignore = true;
27
+ artifact.$ignore = true;
28
28
  }
29
29
  }
30
30
 
31
31
  /**
32
- * Return a callback function for forEachDefinition that marks associations with _ignore
32
+ * Return a callback function for forEachDefinition that marks associations with $ignore
33
33
  * if their target does not reach the database, i.e. marked with @cds.persistence.skip or is abstract
34
34
  *
35
35
  * @param {CSN.Model} csn
@@ -72,7 +72,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
72
72
  }
73
73
 
74
74
  /**
75
- * Mark the given member with _ignore if it is an association/composition and it's target is unreachable.
75
+ * Mark the given member with $ignore if it is an association/composition and its target is unreachable.
76
76
  *
77
77
  * @param {CSN.Element} member
78
78
  * @param {string} memberName
@@ -81,13 +81,13 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
81
81
  */
82
82
  function ignore( member, memberName, prop, path ) {
83
83
  if (options.sqlDialect === 'hana' &&
84
- !member._ignore && member.target &&
84
+ !member.$ignore && member.target &&
85
85
  isAssocOrComposition(member) &&
86
86
  !isPersistedOnDatabase(csn.definitions[member.target])) {
87
87
  info(null, path,
88
88
  { target: member.target, anno: '@cds.persistence.skip' },
89
89
  'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
90
- member._ignore = true;
90
+ member.$ignore = true;
91
91
  }
92
92
  }
93
93
  }
@@ -124,7 +124,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
124
124
 
125
125
  recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
126
126
  // All elements must have a type for this to work
127
- if (!member._ignore && !member.kind && !member.type) {
127
+ if (!member.$ignore && !member.kind && !member.type) {
128
128
  error(null, path, { anno: '@cds.persistence.table' },
129
129
  'Expecting element to have a type if view is annotated with $(ANNO)');
130
130
  }
@@ -274,15 +274,8 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
274
274
  return { columns, toMany: [] };
275
275
 
276
276
  for (const col of columns) {
277
- if (col.expand) {
278
- // TODO: Can col.ref be empty without an as? Assumption is it cannot - if it has, it's an error, we throw, compiler checks.
279
- const { expanded, toManys } = expandInline(root, col, col.ref || [], [ dbName(col) ]);
280
-
281
- allToMany.push(...toManys);
282
- newThing.push(...expanded);
283
- }
284
- else if (col.inline) {
285
- const { expanded, toManys } = expandInline(root, col, col.ref || [], []);
277
+ if (col.expand || col.inline) {
278
+ const { expanded, toManys } = expandInline(root, col, col.ref || [], col.expand ? [ dbName(col) ] : []);
286
279
 
287
280
  allToMany.push(...toManys);
288
281
  newThing.push(...expanded);
@@ -335,18 +328,21 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
335
328
  });
336
329
  toManys.push({ art: current, ref: currentRef, as: currentAlias.join(pathDelimiter) });
337
330
  }
338
- else if (current.expand) {
339
- current.expand = replaceStar(nextBase(current, base), current.expand, current.excluding);
340
- for (let i = current.expand.length - 1; i >= 0; i--) {
341
- const sub = current.expand[i];
342
- stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
343
- }
344
- }
345
- else if (current.inline) {
346
- current.inline = replaceStar(nextBase(current, base), current.inline, current.excluding);
347
- for (let i = current.inline.length - 1; i >= 0; i--) {
348
- const sub = current.inline[i];
349
- stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
331
+ else if (current.expand || current.inline) {
332
+ const withoutStar = replaceStar(nextBase(current, base), current.expand || current.inline, current.excluding);
333
+ current[current.expand ? 'expand' : 'inline'] = withoutStar;
334
+ for (let i = withoutStar.length - 1; i >= 0; i--) {
335
+ const sub = withoutStar[i];
336
+ let subRef;
337
+ if (sub.ref) {
338
+ // Each expand/inline can introduce another layer of $self/$projection. Since $self is
339
+ // a path-breakout, we can simply use the ref without outer expand/inline-references.
340
+ subRef = (sub.$scope === '$self') ? sub.ref : currentRef.concat(sub.ref);
341
+ }
342
+ else {
343
+ subRef = currentRef;
344
+ }
345
+ stack.push([ nextBase(current, base), sub, subRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
350
346
  }
351
347
  }
352
348
  else if (current.xpr || current.args) {
@@ -5,7 +5,7 @@ const {
5
5
  isBuiltinType, cloneCsnNonDict,
6
6
  copyAnnotations, implicitAs, isDeepEqual,
7
7
  } = require('../../model/csnUtils');
8
- const transformUtils = require('../transformUtilsNew');
8
+ const transformUtils = require('../transformUtils');
9
9
  const { csnRefs } = require('../../model/csnRefs');
10
10
  const { setProp } = require('../../base/model');
11
11
  const { forEach } = require('../../utils/objectUtils');
@@ -79,24 +79,23 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
79
79
  const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
80
80
  const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
81
81
  applyTransformations(csn, {
82
- cast: (parent, prop, cast, path) => {
83
- // Resolve cast already - we otherwise lose .localized
84
- if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
85
- toFinalBaseType(parent.cast, resolved, true);
86
- },
87
- // @ts-ignore
88
- type: (parent, prop, type, path) => {
89
- if (options.toOdata && parent.kind && parent.kind in ignoreOdataKinds)
82
+ type: (node, prop, type, path, parent, parentProp) => {
83
+ if (options.toOdata && node.kind && node.kind in ignoreOdataKinds)
90
84
  return;
85
+ if (parentProp === 'cast') {
86
+ const e = csnUtils.getFinalTypeInfo(type, t => resolved.get(t)?.art || csnUtils.artifactRef(t));
87
+ if (!e || e.items || e.elements)
88
+ return;
89
+ }
91
90
  if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
92
- toFinalBaseType(parent, resolved, true);
91
+ toFinalBaseType(node, resolved, true);
93
92
 
94
- if (parent.items) // items could have unresolved types
95
- toFinalBaseType(parent.items, resolved, true);
93
+ if (node.items) // items could have unresolved types
94
+ toFinalBaseType(node.items, resolved, true);
96
95
 
97
96
  // structured types might not have the child-types replaced.
98
97
  // Drill down to ensure this.
99
- let nextElements = parent.elements || parent.items?.elements;
98
+ let nextElements = node.elements || node.items?.elements;
100
99
  if (nextElements) {
101
100
  const stack = [ nextElements ];
102
101
  while (stack.length > 0) {
@@ -111,9 +110,9 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
111
110
  }
112
111
  }
113
112
 
114
- const directLocalized = parent.localized || false;
113
+ const directLocalized = node.localized || false;
115
114
  if (!directLocalized && !options.toOdata)
116
- removeLocalized(parent);
115
+ removeLocalized(node);
117
116
  }
118
117
  },
119
118
  }, [ (definitions, artifactName, artifact) => {
@@ -284,8 +283,7 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
284
283
  forEach(dict, (elementName, element) => {
285
284
  if (element.elements) {
286
285
  // Ignore the structured element, replace it by its flattened form
287
- // TODO: use $ignore - _ is for links
288
- element._ignore = true;
286
+ element.$ignore = true;
289
287
 
290
288
  const branches = getBranches(element, elementName, effectiveType, pathDelimiter);
291
289
  const flatElems = flattenStructuredElement(element, elementName, [], path.concat([ 'elements', elementName ]));
@@ -308,6 +306,8 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
308
306
 
309
307
 
310
308
  if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
309
+ // unmanaged relations can't be primary key
310
+ delete flatElement.key;
311
311
  // Make refs resolvable by fixing the first ref step
312
312
  for (const onPart of flatElement.on) {
313
313
  if (onPart.ref) {
@@ -441,6 +441,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
441
441
  * @param {*} path
442
442
  */
443
443
  function flattenFKs( assoc, assocName, path ) {
444
+ // TODO Depth first search and not iterate mark and sweep approach
444
445
  let finished = false;
445
446
  while (!finished) {
446
447
  const newKeys = [];
@@ -465,8 +466,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
465
466
  const { ref } = assoc.keys[i];
466
467
  if (isStructured(art)) {
467
468
  done = false;
468
- // Mark this element to filter it later - not needed after expansion
469
- setProp(assoc.keys[i], '$toDelete', true);
470
469
  const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
471
470
  Object.keys(flat).forEach((flatElemName) => {
472
471
  const key = assoc.keys[i];
@@ -502,12 +501,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
502
501
  }
503
502
  else if (art.target) {
504
503
  done = false;
505
- // Mark this element to filter it later - not needed after expansion
506
- setProp(assoc.keys[i], '$toDelete', true);
507
504
  // Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
508
505
  // Add the newly generated foreign keys to the end - they will be picked up later on
509
506
  // Recursive solutions run into call stack issues
510
- art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
507
+ art.keys?.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
511
508
  }
512
509
  else if (assoc.keys[i].ref && !assoc.keys[i].as) {
513
510
  setProp(assoc.keys[i], inferredAlias, true);
@@ -523,7 +520,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
523
520
  }
524
521
  return done;
525
522
  }
526
- assoc.keys = assoc.keys.filter(o => !o.$toDelete);
527
523
 
528
524
  /**
529
525
  * Clone base and extend the .ref and .as of the clone with the .ref and .as of ref.
@@ -612,6 +608,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
612
608
  return acc;
613
609
  }, Object.create(null));
614
610
 
611
+ // set default for single foreign key from association (if available)
612
+ if (element.default?.val !== undefined && fks.length === 1)
613
+ fks[0][1].default = element.default;
614
+
615
615
  // check for duplicate foreign keys
616
616
  Object.entries(refCount).forEach(([ name, occ ]) => {
617
617
  if (occ > 1)
@@ -3,12 +3,16 @@
3
3
  const { setProp } = require('../../base/model');
4
4
  const { CompilerAssertion } = require('../../base/error');
5
5
  const {
6
- forEachDefinition, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary, implicitAs, cloneCsnNonDict, getUtils,
6
+ forEachDefinition,
7
+ applyTransformationsOnNonDictionary,
8
+ applyTransformationsOnDictionary,
9
+ implicitAs,
10
+ cloneCsnNonDict,
7
11
  forEachMemberRecursively,
8
12
  } = require('../../model/csnUtils');
9
13
  const { getBranches } = require('./flattening');
10
14
  const { getColumnMap } = require('./views');
11
- const { checkForeignKeyAccess } = require('../../checks/onConditions');
15
+ const { requireForeignKeyAccess } = require('../../checks/onConditions');
12
16
 
13
17
  const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
14
18
 
@@ -19,11 +23,13 @@ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '
19
23
  *
20
24
  * @param {CSN.Model} csn
21
25
  * @param {CSN.Options} options
26
+ * @param {object} csnUtils
22
27
  * @param {string} pathDelimiter
23
- * @param {Function} error
28
+ * @param {object} messageFunctions
24
29
  */
25
- function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error ) {
26
- const { inspectRef, effectiveType } = getUtils(csn, 'init-all');
30
+ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter, messageFunctions ) {
31
+ const { inspectRef, effectiveType } = csnUtils;
32
+ const { error } = messageFunctions;
27
33
 
28
34
  const views = [];
29
35
  const entities = [];
@@ -121,7 +127,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
121
127
  }
122
128
  else {
123
129
  // It's a managed association - access of the foreign keys is allowed
124
- checkForeignKeyAccess(parent, i, csnPath, (errorIndex) => {
130
+ requireForeignKeyAccess(parent, i, (errorIndex) => {
125
131
  error('ref-unexpected-navigation', csnPath, {
126
132
  '#': 'calc-non-fk', id, elemref: parent, name: value[errorIndex].id || value[errorIndex],
127
133
  });
@@ -412,7 +418,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
412
418
  * @param {CSN.QuerySelect} SELECT
413
419
  * @returns {object}
414
420
  */
415
- function getDirectlyAdressableElements( SELECT ) {
421
+ function getDirectlyAddressableElements( SELECT ) {
416
422
  const { from } = SELECT;
417
423
  if (from.ref) {
418
424
  return from._art.elements;
@@ -422,7 +428,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
422
428
  }
423
429
  else if (from.SET) {
424
430
  // args[0] could be SELECT or UNION
425
- return getDirectlyAdressableElements({ from: from.SET.args[0] });
431
+ return getDirectlyAddressableElements({ from: from.SET.args[0] });
426
432
  }
427
433
  else if (from.args) {
428
434
  const mergedElements = Object.create(null);
@@ -432,7 +438,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
432
438
  mergedElements[elementName] = arg._art.elements[elementName];
433
439
  }
434
440
  else if (arg.SET) {
435
- return getDirectlyAdressableElements({ from: arg.SET.args[0] });
441
+ return getDirectlyAddressableElements({ from: arg.SET.args[0] });
436
442
  }
437
443
  else if (arg.SELECT) { // TODO: UNION
438
444
  for (const elementName in arg.SELECT.elements)
@@ -440,7 +446,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
440
446
  }
441
447
  else if (arg.args) { // TODO: Is it safe to do recursion here?
442
448
  for (const subarg of arg.args) {
443
- const elements = getDirectlyAdressableElements({ from: subarg });
449
+ const elements = getDirectlyAddressableElements({ from: subarg });
444
450
  for (const elementName in elements)
445
451
  mergedElements[elementName] = elements[elementName];
446
452
  }
@@ -465,8 +471,8 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
465
471
  */
466
472
  function makeAllCalculatedElementsExplicitColumns( elements, SELECT, containsExpandInline ) {
467
473
  const cleanupCallbacks = [];
468
- const root = getDirectlyAdressableElements(SELECT);
469
- const columnMap = getColumnMap( { SELECT });
474
+ const root = getDirectlyAddressableElements(SELECT);
475
+ const columnMap = getColumnMap( { SELECT }, csnUtils );
470
476
  const hasStar = SELECT.columns.includes('*');
471
477
  const unfoldingMap = {};
472
478
  let starContainsCalculated = false;
@@ -699,9 +705,9 @@ function processCalculatedElementsInEntities( csn ) {
699
705
  */
700
706
  function removeDummyValueInEntity( artifact, path ) {
701
707
  applyTransformationsOnDictionary(artifact.elements, {
702
- value: (parent, prop, value, p, root) => {
708
+ value: (parent, prop, value, p, elements) => {
703
709
  if (!value.stored)
704
- delete root[p[p.length - 1]];
710
+ delete elements[p[p.length - 1]];
705
711
  },
706
712
  }, {}, path.concat( 'elements' ));
707
713
  }
@@ -5,7 +5,7 @@ const {
5
5
  } = require('../../model/csnUtils');
6
6
  const { implicitAs } = require('../../model/csnRefs');
7
7
  const { setProp } = require('../../base/model');
8
- const { getTransformers } = require('../transformUtilsNew');
8
+ const { getTransformers } = require('../transformUtils');
9
9
 
10
10
  const validToString = '@cds.valid.to';
11
11
  const validFromString = '@cds.valid.from';
@@ -53,22 +53,23 @@ const { ModelError } = require('../../base/error');
53
53
  function handleExists( csn, options, error, inspectRef, initDefinition, dropDefinitionCache ) {
54
54
  const generatedExists = new WeakMap();
55
55
  forEachDefinition(csn, (artifact, artifactName) => {
56
+ // drop cache: Otherwise, the projection/query hack below won't work, because csnRefs
57
+ // thinks that the artifact was already initialized (including all queries).
58
+ dropDefinitionCache(artifact);
56
59
  if (artifact.projection) // do the same hack we do for the other stuff...
57
60
  artifact.query = { SELECT: artifact.projection };
58
61
 
59
62
  if (artifact.query) {
60
- forAllQueries(artifact.query, (query, path) => {
63
+ forAllQueries(artifact.query, function handleExistsQuery(query, path) {
61
64
  if (!generatedExists.has(query)) {
62
65
  const toProcess = []; // Collect all expressions we need to process here
63
- if (query.SELECT && query.SELECT.where && query.SELECT.where.length > 1)
66
+ if (query.SELECT?.where?.length > 1)
64
67
  toProcess.push([ path.slice(0, -1), path.concat('where') ]);
65
68
 
66
-
67
- if (query.SELECT && query.SELECT.columns)
69
+ if (query.SELECT?.columns)
68
70
  toProcess.push([ path.slice(0, -1), path.concat('columns') ]);
69
71
 
70
-
71
- if (query.SELECT && query.SELECT.from.on )
72
+ if (query.SELECT?.from.on)
72
73
  toProcess.push([ path.slice(0, -1), path.concat([ 'from', 'on' ]) ]);
73
74
 
74
75
  for (const [ , exprPath ] of toProcess) {
@@ -268,7 +269,7 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
268
269
 
269
270
  const { links } = inspectRef(exprPath.concat(i));
270
271
 
271
- const assocs = links.filter(link => link.art && link.art.target).map(link => current.ref[link.idx]);
272
+ const assocs = links.filter(link => link.art?.target).map(link => current.ref[link.idx]);
272
273
 
273
274
  checkForInvalidAssoc(assocs);
274
275
  }
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- getUtils, cloneCsnNonDict, applyTransformationsOnNonDictionary,
4
+ getUtils, cloneCsnNonDict, applyTransformationsOnNonDictionary, forEachDefinition,
5
5
  } = require('../../model/csnUtils');
6
- const { implicitAs } = require('../../model/csnRefs');
6
+ const { implicitAs, columnAlias } = require('../../model/csnRefs');
7
7
  const { ModelError } = require('../../base/error');
8
+ const { setProp } = require('../../base/model');
8
9
 
9
10
  /**
10
11
  * If a mixin association is published, return the mixin association.
@@ -66,10 +67,11 @@ function usesMixinAssociation( query, association, associationName ) {
66
67
  * @returns {(query: CSN.Query, artifact: CSN.Artifact, artName: string, path: CSN.Path) => void} Transformer function for views
67
68
  */
68
69
  function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
70
+ const csnUtils = getUtils(csn);
69
71
  const {
70
72
  get$combined, isAssocOrComposition,
71
73
  inspectRef, queryOrMain, // csnRefs
72
- } = getUtils(csn);
74
+ } = csnUtils;
73
75
  const pathDelimiter = options.forHana && (options.sqlMapping === 'hdbcds') ? '.' : '_';
74
76
  const { error, info } = messageFunctions;
75
77
  const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
@@ -213,7 +215,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
213
215
  else
214
216
  info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': 'std' });
215
217
 
216
- elem._ignore = true;
218
+ elem.$ignore = true;
217
219
  }
218
220
  else {
219
221
  error(null, queryPath, { name: elemName }, 'Association $(NAME) can\'t be published in a SAP HANA CDS UNION');
@@ -270,7 +272,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
270
272
  }, {}, elementsPath.concat(elemName));
271
273
  }
272
274
 
273
- if (!mixinElem._ignore)
275
+ if (!mixinElem.$ignore)
274
276
  columnMap[elemName] = { ref: [ mixinElemName ], as: elemName };
275
277
 
276
278
  if (query.SELECT) {
@@ -311,7 +313,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
311
313
 
312
314
 
313
315
  /**
314
- * Loop over the columns and call all of the given functions with the column and the path
316
+ * Loop over the columns and call all the given functions with the column and the path
315
317
  *
316
318
  * @param {Function[]} functions
317
319
  * @param {CSN.Column[]} columns
@@ -332,6 +334,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
332
334
  */
333
335
  // eslint-disable-next-line complexity
334
336
  function transformViewOrEntity( query, artifact, artName, path ) {
337
+ csnUtils.initDefinition(artifact);
335
338
  const { elements } = queryOrMain(query, artifact);
336
339
  // We use the elements from the leading query/main artifact - adapt the path
337
340
  const elementsPath = elements === artifact.elements ? path.slice(0, 2).concat('elements') : path.concat('elements');
@@ -340,7 +343,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
340
343
  let hasNonAssocElements = false;
341
344
  const isSelect = query && query.SELECT;
342
345
  const isProjection = !!artifact.projection || query && query.SELECT && !query.SELECT.columns;
343
- const columnMap = getColumnMap(query);
346
+ const columnMap = getColumnMap(query, csnUtils);
344
347
  const isSelectStar = query && query.SELECT && query.SELECT.columns && query.SELECT.columns.indexOf('*') !== -1;
345
348
 
346
349
  // check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
@@ -365,7 +368,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
365
368
  addForeignKeysToColumns(columnMap, elem, elemName);
366
369
  }
367
370
  // Views must have at least one element that is not an unmanaged assoc
368
- if (!elem.on && !elem._ignore)
371
+ if (!elem.on && !elem.$ignore)
369
372
  hasNonAssocElements = true;
370
373
 
371
374
  // (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
@@ -383,7 +386,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
383
386
 
384
387
  if (isSelect) {
385
388
  // Build new columns from the column map - bring elements and columns back in sync basically
386
- query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
389
+ query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem].$ignore).map(key => stripLeadingSelf(columnMap[key]));
387
390
  // If following an association, explicitly set the implicit alias
388
391
  // due to an issue with HANA - this seems to only have an effect on ref files with hdbcds-hdbcds, so only run then
389
392
  const columnProcessors = [];
@@ -476,45 +479,82 @@ function getLastRefStepString( ref ) {
476
479
  }
477
480
 
478
481
  /**
479
- * Build a map of the resulting names (i.e. the element name of the column) and references to the respective columns
482
+ * This function is similar to csnRefs()' `columnName()`, but does not split the
483
+ * last `col.ref` segment on `.`.
480
484
  *
485
+ * TODO: The HDBCDS backend relies on this. Also the HDI backend relies
486
+ * on this for virtual elements somehow. That can probably be fixed
487
+ * by using csnRefs()'s `getElement()`.
488
+ * TODO: Remove this function; update HDBCDS/HDI
489
+ *
490
+ * @param {CSN.Column} col
491
+ * @returns {string}
492
+ */
493
+ function columnNameForMap( col ) {
494
+ return col.as || (!col.args && col.func) || (col.ref && getLastRefStepString( col.ref ));
495
+ }
496
+
497
+ /**
498
+ * Build a map of the resulting names (i.e. the element name of the column) and references
499
+ * to the respective columns.
481
500
  * This can later be used to match from elements to columns.
482
501
  *
483
502
  * @param {CSN.Query} query
503
+ * @param {object} csnUtils
484
504
  * @returns {object}
485
505
  */
486
- function getColumnMap( query ) {
506
+ function getColumnMap( query, csnUtils ) {
487
507
  const map = Object.create(null);
488
- if (query && query.SELECT && query.SELECT.columns) {
508
+ if (query?.SELECT?.columns) {
489
509
  query.SELECT.columns.forEach((col) => {
490
- if (col === '*') {
491
- // do nothing
492
- }
493
- else if (col.as) {
494
- if (!map[col.as])
495
- map[col.as] = col;
496
- }
497
- else if (col.ref) {
498
- // .id on last path step can happen with hdbcds.hdbcds and malicious CSN input - maybe also with params?
499
- // We made things right in the end with the second add of missing stuff, but why not do it
500
- // right from the getgo
501
- const last = getLastRefStepString(col.ref);
502
- if (!map[last])
503
- map[last] = col;
504
- }
505
- else if (col.func) {
506
- map[col.func] = col;
507
- }
508
- else if (!map[col]) {
509
- map[col] = col;
510
+ if (col !== '*') {
511
+ // Fallback to csnUtils for columns without any alias (internal one is created)
512
+ const as = columnNameForMap(col) || csnUtils.getColumnName( col );
513
+ if (as && !map[as])
514
+ map[as] = col;
510
515
  }
511
516
  });
512
517
  }
513
-
514
518
  return map;
515
519
  }
516
520
 
521
+
522
+ /**
523
+ * Ensure that each column in the CSN has a name. A column does not have
524
+ * a name if the column is an expression and there is no explicit alias.
525
+ * In that case an internal alias (from csnRefs()) is used and made explicit
526
+ * via non-enumerable `as`.
527
+ *
528
+ * For HDBCDS, the alias is made explicit as an enumerable property, because
529
+ * HDBCDS does not support expressions as columns without aliases.
530
+ *
531
+ * Notes:
532
+ * - The alias is removed after A2J: we rely on the compiler ignoring non-enumerable CSN properties.
533
+ * - We can't use e.g. `$as`, as csnRefs() does not use that property, and it must not
534
+ * invent another name for the column (could happen after flattening).
535
+ *
536
+ * @param {CSN.Model} csn
537
+ * @param {CSN.Options} options
538
+ * @param {object} csnUtils
539
+ */
540
+ function ensureColumnNames( csn, options, csnUtils ) {
541
+ forEachDefinition(csn, (def) => {
542
+ csnUtils.initDefinition(def);
543
+ for (const query of csnUtils.$getQueries(def) || []) {
544
+ for (const col of query._select.columns || []) {
545
+ if (col !== '*' && !columnAlias(col)) {
546
+ if (options.transformation === 'hdbcds')
547
+ col.as = csnUtils.getColumnName(col);
548
+ else
549
+ setProp(col, 'as', csnUtils.getColumnName(col));
550
+ }
551
+ }
552
+ }
553
+ });
554
+ }
555
+
517
556
  module.exports = {
518
557
  getViewTransformer,
519
558
  getColumnMap,
559
+ ensureColumnNames,
520
560
  };