@sap/cds-compiler 2.12.0 → 2.15.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 (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -290
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
  128. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -9,12 +9,14 @@ 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, cloneCsnDictionary, getUtils, isBuiltinType } = require('../model/csnUtils');
12
+ const { cloneCsnNonDict, cloneCsnDictionary, getUtils, isBuiltinType } = require('../model/csnUtils');
13
+ const { ModelError } = require("../base/error");
14
+ const { forEach } = require('../utils/objectUtils');
13
15
 
14
16
  // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
15
17
  // Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
16
18
  // 'model' is compacted new style CSN
17
- // TODO: Error and warnings handling with compacted CSN? - currently just throw new Error for everything
19
+ // TODO: Error and warnings handling with compacted CSN? - currently just throw new ModelError for everything
18
20
  // TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true
19
21
  function getTransformers(model, options, pathDelimiter = '_') {
20
22
  const { error, warning, info } = makeMessageFunction(model, options);
@@ -246,6 +248,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
246
248
  const struct = elemType ? elemType.elements : elem.elements;
247
249
 
248
250
  // Collect all child elements (recursively) into 'result'
251
+ // TODO: Do not report collisions in the generated elements here, but instead
252
+ // leave that work to the receiver of this result
249
253
  let result = Object.create(null);
250
254
  const addGeneratedFlattenedElement = (e, eName) => {
251
255
  if(result[eName]){
@@ -255,7 +259,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
255
259
  result[eName] = e;
256
260
  }
257
261
  }
258
- Object.entries(struct).forEach(([childName, childElem]) => {
262
+ forEach(struct, (childName, childElem) => {
259
263
  if (isStructured(childElem)) {
260
264
  // Descend recursively into structured children
261
265
  let grandChildElems = flattenStructuredElement(childElem, childName, elementPath, pathInCsn.concat('elements',childName));
@@ -273,7 +277,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
273
277
  } else {
274
278
  // Primitive child - clone it and restore its cross references
275
279
  let flatElemName = elemName + pathDelimiter + childName;
276
- let flatElem = cloneCsn(childElem, options);
280
+ let flatElem = cloneCsnNonDict(childElem, options);
277
281
  // Don't take over notNull from leaf elements
278
282
  delete flatElem.notNull;
279
283
  setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));
@@ -282,11 +286,15 @@ function getTransformers(model, options, pathDelimiter = '_') {
282
286
  });
283
287
 
284
288
  // Fix all collected flat elements (names, annotations, properties, origin ..)
285
- Object.values(result).forEach(flatElem => {
289
+ forEach(result, (name, flatElem) => {
286
290
  // Copy annotations from struct (not overwriting, because deep annotations should have precedence)
287
291
  copyAnnotations(elem, flatElem, false);
288
292
  // Copy selected type properties
289
- for (let p of ['key', 'virtual', 'masked', 'viaAll']) {
293
+ const props = ['key', 'virtual', 'masked', 'viaAll'];
294
+ // 'localized' is needed for OData
295
+ if(options.toOdata)
296
+ props.push('localized');
297
+ for (let p of props) {
290
298
  if (elem[p]) {
291
299
  flatElem[p] = elem[p];
292
300
  }
@@ -313,12 +321,14 @@ function getTransformers(model, options, pathDelimiter = '_') {
313
321
  // Refs of length 1 cannot contain steps - no need to check
314
322
  if (ref.length < 2) {
315
323
  return ref;
324
+ } else if(scope === '$self' && ref.length === 2) {
325
+ return ref;
316
326
  }
317
327
 
318
328
  return flatten(ref, path);
319
329
 
320
330
  function flatten(ref, path) {
321
- let result = [];
331
+ let result = scope === '$self' ? [ref[0]] : [];
322
332
  //let stack = []; // IDs of path steps not yet processed or part of a struct traversal
323
333
  if(!links && !scope) { // calculate JIT if not supplied
324
334
  const res = inspectRef(path);
@@ -327,23 +337,25 @@ function getTransformers(model, options, pathDelimiter = '_') {
327
337
  }
328
338
  if (scope === '$magic')
329
339
  return ref;
340
+ // Don't process a leading $self - it will a .art with .elements!
341
+ const startIndex = scope === '$self' ? 1 : 0;
330
342
  let flattenStep = false;
331
- links.forEach((value, idx) => {
343
+ for(let i = startIndex; i < links.length; i++) {
344
+ const value = links[i];
332
345
  if (flattenStep) {
333
- result[result.length - 1] += pathDelimiter + (ref[idx].id ? ref[idx].id : ref[idx]);
346
+ result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
334
347
  // if we had a filter or args, we had an assoc so this step is done
335
348
  // we then keep along the filter/args by updating the id of the current ref
336
- if(ref[idx].id) {
337
- ref[idx].id = result[result.length-1];
338
- result[result.length-1] = ref[idx];
349
+ if(ref[i].id) {
350
+ ref[i].id = result[result.length-1];
351
+ result[result.length-1] = ref[i];
339
352
  }
340
353
  }
341
354
  else {
342
- result.push(ref[idx]);
355
+ result.push(ref[i]);
343
356
  }
344
-
345
357
  flattenStep = value.art && !value.art.kind && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements || (resolvedLinkTypes.get(value)||{}).elements);
346
- });
358
+ }
347
359
 
348
360
  return result;
349
361
  }
@@ -389,7 +401,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
389
401
  *
390
402
  * @param {CSN.Artifact} node
391
403
  * @param {WeakMap} [resolved] WeakMap containing already resolved refs
392
- * @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
404
+ * @param {boolean} [keepLocalized=false] Whether to clone .localized from a type def
393
405
  * @returns {void}
394
406
  */
395
407
  function toFinalBaseType(node, resolved, keepLocalized=false) {
@@ -402,11 +414,11 @@ function getTransformers(model, options, pathDelimiter = '_') {
402
414
  throw Error('Failed to obtain final base type for reference : ' + node.type.ref.join('/'));
403
415
  if(finalBaseType.elements) {
404
416
  // This changes the order - to be discussed!
405
- node.elements = cloneCsn(finalBaseType, options).elements; // copy elements
417
+ node.elements = cloneCsnNonDict(finalBaseType, options).elements; // copy elements
406
418
  delete node.type; // delete the type reference as edm processing does not expect it
407
419
  } else if(finalBaseType.items) {
408
420
  // This changes the order - to be discussed!
409
- node.items = cloneCsn(finalBaseType.items, options); // copy items
421
+ node.items = cloneCsnNonDict(finalBaseType.items, options); // copy items
410
422
  delete node.type;
411
423
  } else {
412
424
  node.type=finalBaseType;
@@ -420,20 +432,19 @@ function getTransformers(model, options, pathDelimiter = '_') {
420
432
  let typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
421
433
  // Nothing to do if type is an array or a struct type
422
434
  if (typeDef.items || typeDef.elements) {
423
- if(!(options.transformation === 'hdbcds' || options.toSql))
424
- return;
425
-
426
435
  // cloneCsn only works correctly if we start "from the top"
427
- const cloneTypeDef = cloneCsn(typeDef, options);
428
- // With hdbcds-hdbcds, don't resolve structured types - but propagrate ".items", to turn into LargeString later on.
436
+ const cloneTypeDef = cloneCsnNonDict(typeDef, options);
437
+ // With hdbcds-hdbcds, don't resolve structured types - but propagate ".items", to turn into LargeString later on.
429
438
  if(typeDef.items) {
430
439
  delete node.type;
431
- Object.assign(node, {items: cloneTypeDef.items});
440
+ if(!node.items)
441
+ Object.assign(node, {items: cloneTypeDef.items});
432
442
  }
433
443
  if(typeDef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
434
444
  if(!typeDef.items)
435
445
  delete node.type;
436
- Object.assign(node, {elements: cloneTypeDef.elements});
446
+ if(!node.elements)
447
+ Object.assign(node, {elements: cloneTypeDef.elements});
437
448
  }
438
449
 
439
450
 
@@ -593,7 +604,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
593
604
  // elemName typeName isKey defaultVal notNull
594
605
  function createScalarElement(elemName, typeName, isKey = false, defaultVal = undefined, notNull=false) {
595
606
  if (!isBuiltinType(typeName) && !model.definitions[typeName]) {
596
- throw new Error('Expecting valid type name: ' + typeName);
607
+ throw new ModelError('Expecting valid type name: ' + typeName);
597
608
  }
598
609
  let result = {
599
610
  [elemName]: {
@@ -684,7 +695,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
684
695
  function addForeignKey(foreignKey, elem) {
685
696
  // Sanity checks
686
697
  if (!elem.target || !elem.keys) {
687
- throw new Error('Expecting managed association element with foreign keys');
698
+ throw new ModelError('Expecting managed association element with foreign keys');
688
699
  }
689
700
 
690
701
  // Add the foreign key
@@ -703,7 +714,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
703
714
  function addElement(elem, artifact, artifactName) {
704
715
  // Sanity check
705
716
  if (!artifact.elements) {
706
- throw new Error('Expecting artifact with elements: ' + JSON.stringify(artifact));
717
+ throw new ModelError('Expecting artifact with elements: ' + JSON.stringify(artifact));
707
718
  }
708
719
  let elemName = Object.keys(elem)[0];
709
720
  // Element must not exist
@@ -734,7 +745,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
734
745
  */
735
746
  function copyAndAddElement(elem, artifact, artifactName, elementName) {
736
747
  if (!artifact.elements) {
737
- throw new Error('Expected structured artifact');
748
+ throw new ModelError('Expected structured artifact');
738
749
  }
739
750
  // Must not already have such an element
740
751
  if (artifact.elements[elementName]) {
@@ -765,14 +776,14 @@ function getTransformers(model, options, pathDelimiter = '_') {
765
776
 
766
777
  if (returnTypeName) {
767
778
  if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
768
- throw new Error('Expecting valid return type name: ' + returnTypeName);
779
+ throw new ModelError('Expecting valid return type name: ' + returnTypeName);
769
780
  action.returns = { type: returnTypeName };
770
781
  }
771
782
 
772
783
  // Add parameter if provided
773
784
  if (paramName && paramTypeName) {
774
785
  if (!isBuiltinType(paramTypeName) && !model.definitions[paramTypeName])
775
- throw new Error('Expecting valid parameter type name: ' + paramTypeName);
786
+ throw new ModelError('Expecting valid parameter type name: ' + paramTypeName);
776
787
 
777
788
  action.params = Object.create(null);
778
789
  action.params[paramName] = {
@@ -885,7 +896,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
885
896
  let elements = artifact.elements;
886
897
  if (elements) {
887
898
  path.push('elements', null);
888
- Object.entries(elements).forEach(([name, obj]) => {
899
+ forEach(elements, (name, obj) => {
889
900
  path[path.length - 1] = name;
890
901
  recurseElements(obj, path, callback);
891
902
  });
@@ -1105,7 +1116,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
1105
1116
  'xpr': (parent, name, xpr, path) => {
1106
1117
  parent.xpr = expand(parent.xpr, path.concat(name));
1107
1118
  }
1108
- }, undefined, undefined, options);
1119
+ }, [], options);
1109
1120
 
1110
1121
  /*
1111
1122
  flatten structured leaf types and return array of paths
@@ -44,7 +44,7 @@ function translateAssocsToJoinsCSN(csn, options){
44
44
  }
45
45
 
46
46
  // If A2J reports error - end! Continuing with a broken CSN makes no sense
47
- makeMessageFunction(model, options).throwWithError();
47
+ makeMessageFunction(model, options).throwWithAnyError();
48
48
  // FIXME: Move this somewhere more appropriate
49
49
  const compact = compactModel(model, compileOptions);
50
50
  return compact;
@@ -120,7 +120,7 @@ function translateAssocsToJoins(model, inputOptions = {})
120
120
  /*
121
121
  Setup QAs for mixins
122
122
  Mark all mixin assoc definitions with a pseudo QA that points to the assoc target.
123
- This QA is required to detect mixin assoc usages to decide wether a minimum or full
123
+ This QA is required to detect mixin assoc usages to decide whether a minimum or full
124
124
  join needs to be done
125
125
  */
126
126
  forEachQuery(createQAForMixinAssoc, env);
@@ -793,25 +793,8 @@ function translateAssocsToJoins(model, inputOptions = {})
793
793
  If the value is an expression in the select block, return the unmodified
794
794
  expression. rewriteGenericPaths will check and rewrite these paths later
795
795
  to the correct ON condition expression.
796
-
797
- Hack alert:
798
- But if this mixin ON condition path starts with $self no foreign key can
799
- be generated, raise the error here and set $check to false
800
- as it is too hard for checkPathDictionary() to find out that this is a
801
- mixin speciality. Use same mesage as in checkPathDictionary().
802
796
  */
803
797
 
804
- path.forEach(ps => { // eslint-disable-line consistent-return
805
- if(ps._artifact.target) {
806
- error(null, pathNode.location,
807
- { name: env.lead.name.absolute, id: ps.id, alias: pathAsStr(pathNode.path) },
808
- '$(NAME): $(ID) in path $(ALIAS) must not be an association'
809
- );
810
- //pathNode.$check = false;
811
- return pathNode;
812
- }
813
- });
814
-
815
798
  if(!value.path)
816
799
  return value;
817
800
  else {
@@ -1090,7 +1073,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1090
1073
 
1091
1074
  /*
1092
1075
  Replace the table alias node in $tableAliases inplace with the newly created JOIN node
1093
- See definer.js initTableExpression for details where _joinParent and $joinArgsIndex is set.
1076
+ See define.js initTableExpression for details where _joinParent and $joinArgsIndex is set.
1094
1077
  */
1095
1078
  function replaceTableAliasInPlace( tableAlias, replacementNode ) {
1096
1079
  if (tableAlias._joinParent)
@@ -0,0 +1,36 @@
1
+ {
2
+ "root": true,
3
+ "plugins": ["sonarjs", "jsdoc"],
4
+ "extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended", "plugin:jsdoc/recommended"],
5
+ "rules": {
6
+ "prefer-const": "error",
7
+ "quotes": ["error", "single", "avoid-escape"],
8
+ "prefer-template": "error",
9
+ "no-trailing-spaces": "error",
10
+ "template-curly-spacing":["error", "never"],
11
+ "complexity": ["warn", 30],
12
+ "max-len": "off",
13
+ // Don't enforce stupid descriptions
14
+ "jsdoc/require-param-description": "off",
15
+ "jsdoc/require-returns-description": "off",
16
+ // Very whiny and nitpicky
17
+ "sonarjs/cognitive-complexity": "off",
18
+ // Does not recognize TS types
19
+ "jsdoc/no-undefined-types": "off",
20
+ // Just annoying as hell
21
+ "sonarjs/no-duplicate-string": "off"
22
+ },
23
+ "parserOptions": {
24
+ "ecmaVersion": 2018,
25
+ "sourceType": "script"
26
+ },
27
+ "env": {
28
+ "es6": true,
29
+ "node": true
30
+ },
31
+ "settings": {
32
+ "jsdoc": {
33
+ "mode": "typescript"
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ getUtils, forEachDefinition, forAllQueries, getNormalizedQuery,
5
+ } = require('../../model/csnUtils');
6
+ const { setAnnotationIfNotDefined } = require('./utils');
7
+
8
+ /**
9
+ * Set @Core.Computed on the elements of views (and projections).
10
+ *
11
+ * @param {CSN.Model} csn
12
+ */
13
+ function setCoreComputedOnViews(csn) {
14
+ const {
15
+ artifactRef, getColumn, getElement,
16
+ } = getUtils(csn, 'init-all');
17
+
18
+ forEachDefinition(csn, (artifact) => {
19
+ if (artifact.query || artifact.projection) {
20
+ forAllQueries(getNormalizedQuery(artifact).query, (query) => {
21
+ if (query.SELECT)
22
+ traverseQueryAndAttachCoreComputed(query, query.SELECT.elements || artifact.elements);
23
+ });
24
+ }
25
+ });
26
+ /**
27
+ * Attach @Core.Computed to elements resulting from calculated fields
28
+ *
29
+ * To do that, for a given element, we search for its matching column/subquery-element (its ancestor).
30
+ * At the ancestor, we can see if it needs a @CoreComputed - check out {@link needsCoreComputed} for details.
31
+ *
32
+ *
33
+ * @param {CSN.Query} query
34
+ * @param {CSN.Elements} elements
35
+ */
36
+ function traverseQueryAndAttachCoreComputed(query, elements) {
37
+ for (const [ name, element ] of Object.entries(elements)) {
38
+ const ancestor = getAncestor(element, name, query.SELECT);
39
+
40
+ if (needsCoreComputed(ancestor)) // calculated field, function or virtual
41
+ setAnnotationIfNotDefined(element, '@Core.Computed', true);
42
+ if (ancestor && (ancestor.expand || ancestor.inline))
43
+ traverseExpandInline(ancestor, attachCoreComputed);
44
+ }
45
+
46
+ /**
47
+ * Get the ancestor of a given element - either a direct column that "caused" it, or an element
48
+ * from some other artifact (table or view/subquery). The later happens via SELECT * and can be found by drilling down into the
49
+ * FROM-clause.
50
+ *
51
+ * @param {CSN.Element} element
52
+ * @param {string} name
53
+ * @param {CSN.QuerySelect} base
54
+ * @returns {CSN.Column|CSN.Element}
55
+ */
56
+ function getAncestor(element, name, base) {
57
+ const column = getColumn(element);
58
+ if (column)
59
+ return column;
60
+ return getElementFromFrom(name, base.from);
61
+ }
62
+
63
+ /**
64
+ * Get the element <name> from the given query-base (from of a select).
65
+ *
66
+ * For a simple ref to a table, resolve the ref and check the elements
67
+ * For a UNION, drill down into the leading query
68
+ * For a JOIN, check each join-argument
69
+ * For a query with subelements, check the subelements
70
+ *
71
+ * @param {string} name
72
+ * @param {object} base
73
+ * @returns {CSN.Element}
74
+ * @todo cleanup throw(s) - but leave in during dev
75
+ */
76
+ function getElementFromFrom(name, base) {
77
+ if (base.SELECT && base.SELECT.elements) {
78
+ return getAncestor(base.SELECT.elements[name], name, base.SELECT);
79
+ }
80
+ else if (base.ref) {
81
+ let artifact = artifactRef(base);
82
+ if (artifact.target)
83
+ artifact = artifactRef(artifact.target);
84
+ return artifact.elements[name];
85
+ }
86
+ else if (base.SET) {
87
+ return getElementFromFrom(name, base.SET.args[0]);
88
+ }
89
+ else if (base.args && base.join) {
90
+ const result = checkJoinSources(base.args, name);
91
+ if (!result)
92
+ throw new Error(`Could not find ${name} in ${JSON.stringify(base.args)}`);
93
+ return result;
94
+ }
95
+
96
+ throw new Error(JSON.stringify(base));
97
+ }
98
+
99
+ /**
100
+ * For the given JOIN-args, check if one of the join sources provides an element <name>
101
+ *
102
+ * @param {Array} args
103
+ * @param {string} name
104
+ * @returns {CSN.Element|null} Null if no element was found
105
+ */
106
+ function checkJoinSources(args, name) {
107
+ for (const arg of args) {
108
+ if (arg.args) { // Join after join - A join B on <..> join C on <..>
109
+ const result = checkJoinSources(arg.args, name);
110
+ if (result)
111
+ return result;
112
+ }
113
+ else { // All other cases - normal ref, a subselect, etc. pp
114
+ const result = getElementFromFrom(name, arg);
115
+ if (result)
116
+ return result;
117
+ }
118
+ }
119
+
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * On a given column, attach @Core.Computed if needed
125
+ *
126
+ * @param {CSN.Column} column
127
+ */
128
+ function attachCoreComputed(column) {
129
+ if (needsCoreComputed(column))
130
+ setAnnotationIfNotDefined(getElement(column), '@Core.Computed', true);
131
+ }
132
+
133
+ /**
134
+ * Return whether the given columns element needs to be marked with @Core.Computed.
135
+ *
136
+ * @param {CSN.Column} column
137
+ * @returns {boolean}
138
+ */
139
+ function needsCoreComputed(column) {
140
+ return column &&
141
+ (
142
+ column.xpr || column.func || column.val !== undefined || column.param || column.SELECT || column.SET ||
143
+ column.ref && [ '$at', '$now', '$user', '$session' ].includes(column.ref[0])
144
+ );
145
+ }
146
+
147
+ /**
148
+ * Call the given callback for all sub-things of a .expand/.inline and drill further down into other .expand/.inline
149
+ *
150
+ * @param {CSN.Column} column
151
+ * @param {Function} callback
152
+ */
153
+ function traverseExpandInline(column, callback) {
154
+ if (column.expand) {
155
+ column.expand.forEach((col) => {
156
+ callback(col);
157
+ traverseExpandInline(col, callback);
158
+ });
159
+ }
160
+ else if (column.inline) {
161
+ column.inline.forEach((col) => {
162
+ callback(col);
163
+ traverseExpandInline(col, callback);
164
+ });
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ module.exports = {
171
+ setCoreComputedOnViews,
172
+ };