@sap/cds-compiler 3.7.2 → 3.8.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 (70) hide show
  1. package/CHANGELOG.md +63 -4
  2. package/bin/cdsc.js +3 -0
  3. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  4. package/doc/CHANGELOG_BETA.md +15 -0
  5. package/doc/DeprecatedOptions_v2.md +1 -1
  6. package/doc/NameResolution.md +1 -1
  7. package/lib/api/main.js +61 -22
  8. package/lib/api/options.js +1 -0
  9. package/lib/api/validate.js +5 -0
  10. package/lib/base/dictionaries.js +5 -3
  11. package/lib/base/keywords.js +2 -0
  12. package/lib/base/message-registry.js +64 -22
  13. package/lib/base/messages.js +12 -7
  14. package/lib/base/model.js +3 -2
  15. package/lib/checks/arrayOfs.js +1 -1
  16. package/lib/checks/defaultValues.js +1 -1
  17. package/lib/checks/hasPersistedElements.js +1 -1
  18. package/lib/checks/invalidTarget.js +1 -1
  19. package/lib/checks/onConditions.js +9 -6
  20. package/lib/checks/sql-snippets.js +2 -2
  21. package/lib/checks/types.js +1 -2
  22. package/lib/compiler/assert-consistency.js +24 -5
  23. package/lib/compiler/base.js +49 -2
  24. package/lib/compiler/builtins.js +15 -6
  25. package/lib/compiler/checks.js +4 -4
  26. package/lib/compiler/define.js +59 -80
  27. package/lib/compiler/extend.js +701 -498
  28. package/lib/compiler/finalize-parse-cdl.js +4 -3
  29. package/lib/compiler/index.js +1 -1
  30. package/lib/compiler/kick-start.js +2 -2
  31. package/lib/compiler/populate.js +17 -9
  32. package/lib/compiler/propagator.js +12 -5
  33. package/lib/compiler/resolve.js +26 -173
  34. package/lib/compiler/shared.js +12 -53
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +2 -2
  37. package/lib/edm/annotations/genericTranslation.js +124 -46
  38. package/lib/edm/csn2edm.js +22 -1
  39. package/lib/edm/edmPreprocessor.js +41 -21
  40. package/lib/gen/Dictionary.json +4 -0
  41. package/lib/gen/language.checksum +1 -1
  42. package/lib/gen/language.interp +3 -1
  43. package/lib/gen/languageLexer.js +1 -1
  44. package/lib/gen/languageParser.js +4810 -4482
  45. package/lib/inspect/inspectPropagation.js +20 -36
  46. package/lib/json/from-csn.js +55 -5
  47. package/lib/json/to-csn.js +71 -110
  48. package/lib/language/errorStrategy.js +1 -0
  49. package/lib/language/genericAntlrParser.js +47 -8
  50. package/lib/language/language.g4 +88 -62
  51. package/lib/language/textUtils.js +13 -0
  52. package/lib/main.d.ts +43 -3
  53. package/lib/main.js +4 -2
  54. package/lib/model/csnRefs.js +14 -2
  55. package/lib/model/csnUtils.js +11 -74
  56. package/lib/model/revealInternalProperties.js +3 -0
  57. package/lib/optionProcessor.js +3 -0
  58. package/lib/render/toCdl.js +203 -104
  59. package/lib/render/toHdbcds.js +0 -1
  60. package/lib/render/toRename.js +14 -51
  61. package/lib/transform/braceExpression.js +6 -0
  62. package/lib/transform/db/rewriteCalculatedElements.js +55 -14
  63. package/lib/transform/forOdataNew.js +20 -15
  64. package/lib/transform/forRelationalDB.js +21 -14
  65. package/lib/transform/parseExpr.js +2 -0
  66. package/lib/transform/transformUtilsNew.js +36 -9
  67. package/lib/transform/translateAssocsToJoins.js +11 -4
  68. package/lib/transform/universalCsn/coreComputed.js +15 -7
  69. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  70. package/package.json +2 -1
@@ -3,10 +3,11 @@
3
3
 
4
4
  const { makeMessageFunction } = require('../base/messages');
5
5
  const { checkCSNVersion } = require('../json/csnVersion');
6
- const { getNamespace, forEachDefinition } = require('../model/csnUtils');
6
+ const { forEachDefinition } = require('../model/csnUtils');
7
7
  const { optionProcessor } = require('../optionProcessor');
8
8
  const { isBetaEnabled } = require('../base/model');
9
9
  const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
10
+ const { getIdentifierUtils } = require('./utils/sql');
10
11
 
11
12
 
12
13
  /**
@@ -46,6 +47,9 @@ function toRename( inputCsn, options ) {
46
47
 
47
48
  // FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forRelationalDB)
48
49
  const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
50
+ const hdbcdsOrQuotedIdentifiers = getIdentifierUtils(csn, options);
51
+ const plainIdentifiers = getIdentifierUtils(csn, { sqlDialect: 'hana', sqlMapping: 'plain' });
52
+
49
53
  // forRelationalDB looses empty contexts and services, add them again so that toRename can calculate the namespaces
50
54
  forEachDefinition(csn, (artifact, artifactName) => {
51
55
  if ((artifact.kind === 'context' || artifact.kind === 'service') && csn.definitions[artifactName] === undefined)
@@ -82,10 +86,12 @@ function toRename( inputCsn, options ) {
82
86
  function renameTableAndColumns( artifactName, art ) {
83
87
  let resultStr = '';
84
88
  if (art.kind === 'entity' && !art.query) {
85
- const beforeTableName = quoteSqlId(absoluteCdsName(artifactName));
86
- const afterTableName = plainSqlId(artifactName);
89
+ const beforeTableName = hdbcdsOrQuotedIdentifiers.renderArtifactName(artifactName);
90
+ const afterTableName = plainIdentifiers.renderArtifactName(artifactName);
87
91
 
88
- if (beforeTableName !== afterTableName)
92
+ if (beforeTableName.toUpperCase() === `"${afterTableName}"`)
93
+ resultStr += ` --EXEC 'RENAME TABLE ${beforeTableName} TO ${afterTableName}';\n`;
94
+ else if (beforeTableName !== afterTableName)
89
95
  resultStr += ` EXEC 'RENAME TABLE ${beforeTableName} TO ${afterTableName}';\n`;
90
96
 
91
97
 
@@ -93,13 +99,14 @@ function toRename( inputCsn, options ) {
93
99
  const e = art.elements[name];
94
100
  let str = '';
95
101
 
96
- const beforeColumnName = quoteSqlId(name);
97
- const afterColumnName = plainSqlId(name);
102
+ const beforeColumnName = hdbcdsOrQuotedIdentifiers.quoteSqlId(name);
103
+ const afterColumnName = plainIdentifiers.quoteSqlId(name);
98
104
 
99
105
  if (!e._ignore) {
100
106
  if (e.target)
101
107
  str = ` EXEC 'ALTER TABLE ${afterTableName} DROP ASSOCIATION ${beforeColumnName}';\n`;
102
-
108
+ else if (beforeColumnName.toUpperCase() === `"${afterColumnName}"` ) // Basically a no-op - render commented out
109
+ str = ` --EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
103
110
  else if (beforeColumnName !== afterColumnName)
104
111
  str = ` EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
105
112
  }
@@ -108,50 +115,6 @@ function toRename( inputCsn, options ) {
108
115
  }
109
116
  return resultStr;
110
117
  }
111
-
112
- /**
113
- * Return 'name' in the form of an absolute CDS name - for the 'hdbcds' naming convention,
114
- * this means converting '.' to '::' on the border between namespace and top-level artifact.
115
- * For all other naming conventions, this is a no-op.
116
- *
117
- * @param {string} name Name to absolutify
118
- * @returns {string} Absolute name
119
- */
120
- function absoluteCdsName( name ) {
121
- if (options.sqlMapping !== 'hdbcds')
122
- return name;
123
-
124
- const namespaceName = getNamespace(inputCsn, name);
125
- if (namespaceName)
126
- return `${namespaceName}::${name.substring(namespaceName.length + 1)}`;
127
-
128
- return name;
129
- }
130
-
131
- /**
132
- * Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.sqlMapping'
133
- * is 'quoted'
134
- *
135
- * @param {string} name Name to quote
136
- * @returns {string} Quoted string
137
- */
138
- function quoteSqlId( name ) {
139
- if (options.sqlMapping === 'quoted')
140
- name = name.replace(/::/g, '.');
141
-
142
- return `"${name.replace(/"/g, '""')}"`;
143
- }
144
-
145
- /**
146
- * Return 'name' with uppercasing and appropriate "-quotes, also replacing '::' and '.' by '_'
147
- * (to be used by 'plain' naming convention).
148
- *
149
- * @param {string} name Name to turn into a plain identifier
150
- * @returns {string} A plain SQL identifier
151
- */
152
- function plainSqlId( name ) {
153
- return `"${name.toUpperCase().replace(/(::|\.)/g, '_').replace(/"/g, '""')}"`;
154
- }
155
118
  }
156
119
 
157
120
  module.exports = {
@@ -1,3 +1,9 @@
1
+ // Currently unused, but may become useful again if HDBCDS -> HDBTABLE
2
+ // handover becomes more prominent. Historically used with the no longer
3
+ // existent option `--compatibility`.
4
+ // If necessary, more complex expressions could be parsed with parseExpr.js
5
+ // and then stringified with parentheses again.
6
+
1
7
  'use strict';
2
8
 
3
9
  function isAlreadyBraced(expression, start, end){
@@ -1,12 +1,13 @@
1
1
  'use strict';
2
2
 
3
- const { isBetaEnabled, setProp } = require('../../base/model');
3
+ const { setProp } = require('../../base/model');
4
4
  const { CompilerAssertion } = require('../../base/error');
5
5
  const {
6
6
  forEachDefinition, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary, implicitAs, cloneCsnNonDict, getUtils,
7
7
  } = require('../../model/csnUtils');
8
8
  const { getBranches } = require('./flattening');
9
9
  const { getColumnMap } = require('./views');
10
+
10
11
  const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
11
12
  /**
12
13
  * Rewrite usage of calculated Elements into the expression itself.
@@ -18,9 +19,6 @@ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '
18
19
  * @param {Function} error
19
20
  */
20
21
  function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error ) {
21
- if (!isBetaEnabled(options, 'calculatedElements'))
22
- return;
23
-
24
22
  const { inspectRef, effectiveType } = getUtils(csn, 'init-all');
25
23
 
26
24
  const views = [];
@@ -107,7 +105,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
107
105
  else
108
106
  makeAllCalculatedElementsExplicitColumns(elements, SELECT, containsExpandInline);
109
107
 
110
- const name = SELECT.from.args ? undefined : SELECT.from.as || implicitAs(SELECT.from.ref);
108
+ const name = SELECT.from.args ? undefined : SELECT.from.as || (SELECT.from.ref && implicitAs(SELECT.from.ref));
111
109
 
112
110
  if (!containsExpandInline) {
113
111
  applyTransformationsOnNonDictionary({ SELECT }, 'SELECT', {
@@ -119,7 +117,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
119
117
  if (art?.value) {
120
118
  const alias = parent.as || implicitAs(parent.ref);
121
119
  // TODO: What about other scopes? expand/inline?
122
- const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : art.value;
120
+ const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : keepAssocStepsInRef(ref, links, art).value;
123
121
 
124
122
  // Is a shallow copy enough?
125
123
  if (art.value.cast)
@@ -146,8 +144,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
146
144
  if (!SELECT.columns)
147
145
  return false;
148
146
 
149
- for (let i = 0; i < SELECT.columns.length; i++) {
150
- const column = SELECT.columns[i];
147
+ for (const column of SELECT.columns) {
151
148
  if (column.expand || column.inline)
152
149
  return true;
153
150
  }
@@ -307,7 +304,9 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
307
304
  return from.SELECT.elements;
308
305
  }
309
306
  else if (from.SET) {
310
- return from.SET.elements || getDirectlyAdressableElements(from.SET.args[0].SELECT);
307
+ // FIXME: Check if this is correct
308
+ // args[0] could be SELECT or UNION
309
+ return getDirectlyAdressableElements({ from: from.SET.args[0] });
311
310
  }
312
311
  else if (from.args) {
313
312
  const mergedElements = Object.create(null);
@@ -316,10 +315,21 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
316
315
  for (const elementName in arg._art.elements)
317
316
  mergedElements[elementName] = arg._art.elements[elementName];
318
317
  }
318
+ else if (arg.SET) {
319
+ // FIXME: Check if this is correct
320
+ return getDirectlyAdressableElements({ from: arg.SET.args[0] });
321
+ }
319
322
  else if (arg.SELECT) { // TODO: UNION
320
323
  for (const elementName in arg.SELECT.elements)
321
324
  mergedElements[elementName] = arg.SELECT.elements[elementName];
322
325
  }
326
+ else if (arg.args) { // TODO: Is it safe to do recursion here?
327
+ for (const subarg of arg.args) {
328
+ const elements = getDirectlyAdressableElements({ from: subarg });
329
+ for (const elementName in elements)
330
+ mergedElements[elementName] = elements[elementName];
331
+ }
332
+ }
323
333
  else {
324
334
  throw new CompilerAssertion(`Unhandled arg type: ${JSON.stringify(arg, null, 2)}`);
325
335
  }
@@ -441,6 +451,40 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
441
451
  return false;
442
452
  }
443
453
 
454
+ /**
455
+ * We need to keep association steps in front of the paths - else they would lead into nothing
456
+ *
457
+ * @param {Array} artRef
458
+ * @param {Array} links
459
+ * @param {object} art
460
+ * @returns {object}
461
+ */
462
+ function keepAssocStepsInRef( artRef, links, art ) {
463
+ let lastAssocIndex = -1;
464
+ for (let i = links.length - 1; i > -1; i--) {
465
+ if (links[i].art.target) {
466
+ lastAssocIndex = i;
467
+ break;
468
+ }
469
+ }
470
+
471
+ if (lastAssocIndex > -1) {
472
+ const clone = { value: cloneCsnNonDict(art.value, cloneCsnOptions) };
473
+ applyTransformationsOnNonDictionary(clone, 'value', {
474
+ ref: (parent, prop, ref) => {
475
+ parent.ref = [ ...artRef.slice(0, lastAssocIndex + 1), ...ref ];
476
+ if (parent._links)
477
+ parent._links = [ ...links.slice(0, lastAssocIndex + 1), ...parent._links ];
478
+ },
479
+ }, {
480
+ skipStandard: { where: true }, // Do not rewrite refs inside of an association-where
481
+ });
482
+
483
+ return clone;
484
+ }
485
+
486
+ return art;
487
+ }
444
488
 
445
489
  /**
446
490
  * In order to just replace them in views, our calculated elements need to reference absolute things, i.e. have a table alias in front!
@@ -508,13 +552,10 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
508
552
  }
509
553
 
510
554
  /**
511
- *
512
555
  * @param {CSN.Model} csn
513
- * @param {CSN.Options} options
556
+ * @param {CSN.Options} _options
514
557
  */
515
- function processCalculatedElementsInEntities( csn, options ) {
516
- if (!isBetaEnabled(options, 'calculatedElements'))
517
- return;
558
+ function processCalculatedElementsInEntities( csn, _options ) {
518
559
  forEachDefinition(csn, (artifact, artifactName) => {
519
560
  if (artifact.kind === 'entity' && !(artifact.query || artifact.projection))
520
561
  killInEntity(artifact, [ 'definitions', artifactName ]);
@@ -60,7 +60,7 @@ const { addLocalizationViews } = require('./localized');
60
60
  // - Mark fields with @odata.on.insert/update as @Core.Computed
61
61
  // (EdmPreproc candidate, check with RT if @Core.Computed required by them)
62
62
  // - Rename shorthand annotations according to a builtin list (EdmPreproc Candidate)
63
- // e.g. @label -> @Common.Label or @important: [true|false] -> @UI.Importance: [#High|#Low]
63
+ // e.g. @label -> @Common.Label
64
64
  // - If the association target is annotated with @cds.odata.valuelist, annotate the
65
65
  // association with @Common.ValueList.viaAssociation (EdmPreproc Candidate)
66
66
  // - Check for @Analytics.Measure and @Aggregation.default (Linter check candidate, remove)
@@ -315,31 +315,36 @@ function transform4odataWithCsn(inputModel, options) {
315
315
  // list.
316
316
  function renameShorthandAnnotations(node) {
317
317
  // FIXME: Verify this list - are they all still required? Do we need any more?
318
- const mappings = {
318
+ const setMappings = {
319
319
  '@label': '@Common.Label',
320
320
  '@title': '@Common.Label',
321
321
  '@description': '@Core.Description',
322
+ };
323
+ const renameMappings = {
322
324
  '@ValueList.entity': '@Common.ValueList.entity',
323
325
  '@ValueList.type': '@Common.ValueList.type',
324
326
  '@Capabilities.Deletable': '@Capabilities.DeleteRestrictions.Deletable',
325
327
  '@Capabilities.Insertable': '@Capabilities.InsertRestrictions.Insertable',
326
328
  '@Capabilities.Updatable': '@Capabilities.UpdateRestrictions.Updatable',
327
329
  '@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
328
- }
330
+ };
329
331
 
330
- const shortCuts = Object.keys(mappings);
332
+ const setShortCuts = Object.keys(setMappings);
333
+ const renameShortCuts = Object.keys(renameMappings);
331
334
  Object.keys(node).forEach( name => {
335
+ if (!name.startsWith('@'))
336
+ return;
332
337
  // Rename according to map above
333
- const prefix = shortCuts.find(p => name.startsWith(p + '.') || name === p);
334
- if(prefix) {
335
- renameAnnotation(node, name, name.replace(prefix, mappings[prefix]));
336
- }
337
- // Special case: '@important: [true|false]' becomes '@UI.Importance: [#High|#Low]'
338
- if (name === '@important') {
339
- renameAnnotation(node, name, '@UI.Importance');
340
- let annotation = node['@UI.Importance'];
341
- if (annotation !== null)
342
- node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' };
338
+ const renamePrefix = (name in renameMappings) ? name : renameShortCuts.find(p => name.startsWith(p + '.'));
339
+ if(renamePrefix) {
340
+ renameAnnotation(node, name, name.replace(renamePrefix, renameMappings[renamePrefix]));
341
+ } else {
342
+ // The two mappings have no overlap, so no need to check for second map if first matched.
343
+ // Rename according to map above
344
+ const setPrefix = (name in setMappings) ? name : setShortCuts.find(p => name.startsWith(p + '.'));
345
+ if(setPrefix) {
346
+ setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
347
+ }
343
348
  }
344
349
 
345
350
  // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
@@ -351,7 +356,7 @@ function transform4odataWithCsn(inputModel, options) {
351
356
  setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
352
357
  setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
353
358
  } else {
354
- renameAnnotation(node, name, '@Core.Computed');
359
+ setAnnotation(node, '@Core.Computed', true);
355
360
  }
356
361
  }
357
362
  // @insertonly is effective on entities/queries only
@@ -9,7 +9,7 @@ const { cloneCsnNonDict,
9
9
  const { makeMessageFunction } = require('../base/messages');
10
10
  const transformUtils = require('./transformUtilsNew');
11
11
  const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
12
- const { csnRefs, pathId } = require('../model/csnRefs');
12
+ const { csnRefs, pathId, traverseQuery } = require('../model/csnRefs');
13
13
  const { checkCSNVersion } = require('../json/csnVersion');
14
14
  const validate = require('../checks/validator');
15
15
  const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
@@ -127,8 +127,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
127
127
  get$combined,
128
128
  getCsnDef,
129
129
  isAssocOrComposition,
130
- addStringAnnotationTo,
131
- cloneWithTransformations;
130
+ addStringAnnotationTo;
132
131
  // transformUtils
133
132
  let addDefaultTypeFacets,
134
133
  expandStructsInExpression,
@@ -175,11 +174,9 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
175
174
 
176
175
  throwWithAnyError();
177
176
 
178
- // FIXME: This does something very similar to cloneWithTransformations -> refactor?
179
177
  const transformCsn = transformUtils.transformModel;
180
178
 
181
-
182
- forEachDefinition(csn, [
179
+ forEachDefinition(csn, [
183
180
  // (001) Add a temporal where condition to views where applicable before assoc2join
184
181
  // assoc2join eventually rewrites the table aliases
185
182
  temporal.getViewDecorator(csn, {info}, csnUtils),
@@ -459,7 +456,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
459
456
  getCsnDef,
460
457
  isAssocOrComposition,
461
458
  addStringAnnotationTo,
462
- cloneWithTransformations
463
459
  } = csnUtils);
464
460
  }
465
461
 
@@ -540,6 +536,20 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
540
536
  if (!artifact._ignore) {
541
537
  // Do things specific for entities and views (pass 2)
542
538
  if ((artifact.kind === 'entity') && artifact.query) {
539
+
540
+ // First pass: Set alias name for SELECTs without table alias. Required for setting proper table aliases
541
+ // for HDBCDS in naming mode HDBCDS. We use the same schema as the core-compiler, so duplicates should
542
+ // have already been reported.
543
+ let selectDepth = 0;
544
+ traverseQuery(artifact.query, null, null, (query, fromSelect) => {
545
+ if (!query.ref && !query.as && fromSelect) {
546
+ // Use +1; for UNION, it's the next select, for SELECT, it's increased later.
547
+ query.as = `$_select_${selectDepth + 1}__`;
548
+ }
549
+ if (query.SELECT)
550
+ ++selectDepth;
551
+ });
552
+
543
553
  const process = (parent, prop, query, path) => {
544
554
  transformEntityOrViewPass2(parent, artifact, artifactName, path.concat(prop))
545
555
  replaceAssociationsInGroupByOrderBy(parent, options, inspectRef, error, path.concat(prop));
@@ -987,25 +997,22 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
987
997
  // this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
988
998
  elemName = elemName.replace(/\./g, pathDelimiter);
989
999
  const assocName = originalAssocName.replace(/\./g, pathDelimiter);
990
- // clone the onCond for later use in the path transformation,
991
- // also assign the _artifact elements of the path elements to the copy
992
- const newOnCond = cloneWithTransformations(assoc.on, {
993
- ref: (value) => cloneWithTransformations(value, {}),
994
- });
1000
+ // clone the onCond for later use in the path transformation
1001
+ const newOnCond = cloneCsnNonDict(assoc.on, options);
995
1002
  applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {
996
1003
  ref: (parent, prop, ref) => {
997
1004
  if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
998
1005
  {
999
1006
  ref.shift();
1000
1007
  } else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
1001
- // We could also have a $self infront of the assoc name - so we would need to shift twice
1008
+ // We could also have a $self in front of the assoc name - so we would need to shift twice
1002
1009
  ref.shift();
1003
1010
  ref.shift();
1004
1011
  }
1005
1012
  else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
1006
1013
  ref.unshift(elemName);
1007
1014
  // if there was a $self identifier in the forwarding association onCond
1008
- // we do not need it any more, as we prepended in the previous step the back association's id
1015
+ // we do not need it anymore, as we prepended in the previous step the back association's id
1009
1016
  if (ref[1] === '$self')
1010
1017
  ref.splice(1, 1);
1011
1018
  }
@@ -346,6 +346,8 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
346
346
 
347
347
  function isSimpleAnnoValue(val) {
348
348
  // Expressions as annotation values always have a `=` and another property.
349
+ // TODO: There must be at least one known expression property, otherwise
350
+ // it could be `type: 'unchecked'`.
349
351
  return !val?.['='] || Object.keys(val) < 2;
350
352
  }
351
353
 
@@ -12,7 +12,6 @@ const { cloneCsnNonDict, cloneCsnDictionary, getUtils } = require('../model/csnU
12
12
  const { typeParameters, isBuiltinType } = require('../compiler/builtins');
13
13
  const { ModelError, CompilerAssertion} = require('../base/error');
14
14
  const { forEach } = require('../utils/objectUtils');
15
- const { pathName } = require('../compiler/utils');
16
15
 
17
16
  const RestrictedOperators = ['<', '>', '>=', '<='];
18
17
  const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOperators];
@@ -1160,26 +1159,41 @@ function getTransformers(model, options, pathDelimiter = '_') {
1160
1159
  const xrefvalues = Object.values(xref);
1161
1160
  let cont = true;
1162
1161
 
1162
+ const prefix = (lhs, op, rhs) => {
1163
+ return `${lhsIsVal ? lhs.val : lhs.ref.join('.')} ${op} ${rhsIsVal ? rhs : rhs.ref.join('.')}`
1164
+ }
1163
1165
  if(op === 'like' && xrefvalues.reduce((a, v) => {
1164
1166
  return (v.lhs && v.rhs) ? a + 1: a;
1165
1167
  }, 0) === 0) {
1166
1168
  // error if intersection of paths is zero
1167
- error(null, location, { lhs: pathName(lhs.ref), op, rhs: pathName(rhs.ref) },
1168
- 'Expected compatible types for $(LHS) $(OP) $(RHS)');
1169
+ error(null, location,
1170
+ {
1171
+ prefix: prefix(lhs, op, rhs)
1172
+ },
1173
+ 'Expected compatible types for $(PREFIX)');
1169
1174
  cont = false;
1170
1175
  }
1171
1176
 
1172
1177
  cont && xrefkeys.forEach(xn => {
1173
1178
  const x = xref[xn];
1174
- const prefix = `${pathName(lhs.ref)} ${op} ${pathName(rhs.ref)}`;
1175
1179
  // do the paths match?
1176
1180
  if(op !== 'like' && !(x.lhs && x.rhs)) {
1177
1181
  if(xn.length) {
1178
- error(null, location, { prefix, name: xn, alias: pathName((x.lhs ? rhs : lhs).ref) },
1182
+ error(null, location,
1183
+ {
1184
+ prefix: prefix(lhs, op, rhs),
1185
+ name: xn,
1186
+ alias: (x.lhs ? rhs : lhs).ref.join('.')
1187
+ },
1179
1188
  '$(PREFIX): Sub path $(NAME) not found in $(ALIAS)');
1180
1189
  }
1181
1190
  else {
1182
- error(null, location, { prefix, name: pathName((x.lhs ? lhs : rhs).ref), alias: pathName((x.lhs ? rhs : lhs).ref) },
1191
+ error(null, location,
1192
+ {
1193
+ prefix: prefix(lhs, op, rhs),
1194
+ name: (x.lhs ? lhs : rhs).ref.join('.'),
1195
+ alias: (x.lhs ? rhs : lhs).ref.join('.')
1196
+ },
1183
1197
  '$(PREFIX): Path $(NAME) does not match $(ALIAS)');
1184
1198
  }
1185
1199
  cont = false;
@@ -1189,13 +1203,21 @@ function getTransformers(model, options, pathDelimiter = '_') {
1189
1203
  // is lhs scalar?
1190
1204
  // eslint-disable-next-line sonarjs/no-gratuitous-expressions
1191
1205
  if(!lhsIsVal && x.lhs && !isScalarOrNoType(x.lhs._art)) {
1192
- error(null, location, { prefix, name: `${pathName(x.lhs.ref)}${(xn.length ? '.' + xn : '')}` },
1206
+ error(null, location,
1207
+ {
1208
+ prefix: prefix(lhs, op, rhs),
1209
+ name: `${x.lhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
1210
+ },
1193
1211
  '$(PREFIX): Path $(NAME) must end on a scalar type')
1194
1212
  cont = false;
1195
1213
  }
1196
1214
  // is rhs scalar?
1197
1215
  if(!rhsIsVal && x.rhs && !isScalarOrNoType(x.rhs._art)) {
1198
- error(null, location, { prefix, name: `${pathName(x.rhs.ref)}${(xn.length ? '.' + xn : '')}` },
1216
+ error(null, location,
1217
+ {
1218
+ prefix: prefix(lhs, op, rhs),
1219
+ name: `${x.rhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
1220
+ },
1199
1221
  '$(PREFIX): Path $(NAME) must end on a scalar type');
1200
1222
  cont = false;
1201
1223
  }
@@ -1205,7 +1227,12 @@ function getTransformers(model, options, pathDelimiter = '_') {
1205
1227
  const lhst = getType(x.lhs._art);
1206
1228
  const rhst = getType(x.rhs._art);
1207
1229
  if(lhst !== rhst) {
1208
- info(null, location, { prefix, name: xn },'$(PREFIX): Types for sub path $(NAME) don\'t match');
1230
+ info(null, location,
1231
+ {
1232
+ prefix: prefix(lhs, op, rhs),
1233
+ name: xn
1234
+ },
1235
+ '$(PREFIX): Types for sub path $(NAME) don\'t match');
1209
1236
  }
1210
1237
  }
1211
1238
  }
@@ -196,7 +196,7 @@ function translateAssocsToJoins(model, inputOptions = {})
196
196
  'leaf' QAT and to the respective $tableAlias which is used to link paths to the correct
197
197
  table alias. Subqueries are not considered in the mergePathIntoQat(), so a subquery QA
198
198
  must be created and added separately to the lead query $tableAlias'es.
199
- Also the name of the subquery (the alias) needs to be set to the final QA alias name.
199
+ Also, the name of the subquery (the alias) needs to be set to the final QA alias name.
200
200
  */
201
201
  function createQAForFromClauseSubQuery(query, env)
202
202
  {
@@ -204,7 +204,14 @@ function translateAssocsToJoins(model, inputOptions = {})
204
204
  if (query.$tableAliases[taName].kind !== '$self') {
205
205
  let ta = query.$tableAliases[taName];
206
206
  if(!ta.$QA) {
207
- ta.$QA = createQA(env, ta._origin, taName, undefined);
207
+ let alias = taName;
208
+ if (ta.name.$inferred === '$internal') {
209
+ // query has no explicit table alias, i.e. is internal: make it visible and remove `$`
210
+ alias = ta.name.alias.replace(/^[$]/, '_');
211
+ ta.$inferred = undefined;
212
+ ta.name.$inferred = undefined;
213
+ }
214
+ ta.$QA = createQA(env, ta._origin, alias, undefined);
208
215
  incAliasCount(env, ta.$QA);
209
216
  if(ta.name && ta.name.id) {
210
217
  ta.name.id = ta.$QA.name.id;
@@ -307,7 +314,7 @@ function translateAssocsToJoins(model, inputOptions = {})
307
314
  // pop ta ps
308
315
  if(head._navigation.kind !== '$tableAlias')
309
316
  tail = pathNode.path;
310
- // if tail.lenth > 1, search bottom up for QA
317
+ // if tail.length > 1, search bottom up for QA
311
318
  // default to rootQA, _parent.$QA has precedence
312
319
  let [QA, ps] = rightMostQA(tail, head._navigation._parent.$QA || head._navigation.$QA);
313
320
  if(!QA) {
@@ -1005,7 +1012,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1005
1012
  A QA (QueryArtifact) is a representative for a table/view that must appear
1006
1013
  in the FROM clause either named directly or indirectly through an association.
1007
1014
  */
1008
- function createQA(env, artifact, alias=undefined, namedArgs=undefined)
1015
+ function createQA(env, artifact, alias, namedArgs=undefined)
1009
1016
  {
1010
1017
  if(alias === undefined) {
1011
1018
  throw new CompilerAssertion('no alias provided');
@@ -1,18 +1,19 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- forEachDefinition, forAllQueries, getNormalizedQuery,
4
+ forEachDefinition, forAllQueries, getNormalizedQuery, forEachMemberRecursively,
5
5
  } = require('../../model/csnUtils');
6
6
  const { setAnnotationIfNotDefined } = require('./utils');
7
7
  const { CompilerAssertion } = require('../../base/error');
8
8
 
9
9
  /**
10
- * Set @Core.Computed on the elements of views (and projections).
10
+ * Set @Core.Computed on the elements of views (and projections) as well
11
+ * as on calculated elements of entities and aspects.
11
12
  *
12
13
  * @param {CSN.Model} csn
13
14
  * @param {object} csnUtils
14
15
  */
15
- function setCoreComputedOnViews( csn, csnUtils ) {
16
+ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
16
17
  const {
17
18
  artifactRef, getColumn, getElement, getOrigin,
18
19
  } = csnUtils;
@@ -24,6 +25,12 @@ function setCoreComputedOnViews( csn, csnUtils ) {
24
25
  traverseQueryAndAttachCoreComputed(query, query.SELECT.elements || artifact.elements);
25
26
  }, path);
26
27
  }
28
+ else if (artifact.kind === 'entity' || artifact.kind === 'aspect') {
29
+ forEachMemberRecursively(artifact, (element) => {
30
+ if (element.value && !element.value?.ref) // calculated elements, but simple references are ignored
31
+ setAnnotationIfNotDefined(element, '@Core.Computed', true);
32
+ }, path);
33
+ }
27
34
  });
28
35
  /**
29
36
  * Attach @Core.Computed to elements resulting from calculated fields
@@ -84,7 +91,7 @@ function setCoreComputedOnViews( csn, csnUtils ) {
84
91
  * @todo cleanup throw(s) - but leave in during dev
85
92
  */
86
93
  function getElementFromFrom( name, base ) {
87
- if (base.SELECT && base.SELECT.elements) {
94
+ if (base.SELECT?.elements?.[name]) {
88
95
  return getAncestor(base.SELECT.elements[name], name, base.SELECT);
89
96
  }
90
97
  else if (base.ref) {
@@ -100,7 +107,7 @@ function setCoreComputedOnViews( csn, csnUtils ) {
100
107
  return checkJoinSources(base.args, name);
101
108
  }
102
109
 
103
- throw new CompilerAssertion(JSON.stringify(base));
110
+ throw new CompilerAssertion(`Element “${name}” not found in: ${JSON.stringify(base)}`);
104
111
  }
105
112
 
106
113
  /**
@@ -146,7 +153,8 @@ function setCoreComputedOnViews( csn, csnUtils ) {
146
153
  function needsCoreComputed( column ) {
147
154
  return column &&
148
155
  (
149
- column.xpr || column.func || column.val !== undefined || column.param || column.SELECT || column.SET ||
156
+ column.xpr || column.list || column.func || column.val !== undefined || column.param ||
157
+ column.SELECT || column.SET ||
150
158
  column.ref && [ '$at', '$now', '$user', '$session' ].includes(column.ref[0])
151
159
  );
152
160
  }
@@ -175,5 +183,5 @@ function setCoreComputedOnViews( csn, csnUtils ) {
175
183
  }
176
184
 
177
185
  module.exports = {
178
- setCoreComputedOnViews,
186
+ setCoreComputedOnViewsAndCalculatedElements,
179
187
  };