@sap/cds-compiler 2.11.2 → 2.13.6

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 (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -9,12 +9,13 @@ const { setProp } = require('../base/model');
9
9
  const { csnRefs } = require('../model/csnRefs');
10
10
 
11
11
  const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
12
- const { cloneCsn, getUtils, isBuiltinType } = require('../model/csnUtils');
12
+ const { cloneCsn, cloneCsnDictionary, getUtils, isBuiltinType } = require('../model/csnUtils');
13
+ const { ModelError } = require("../base/error");
13
14
 
14
15
  // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
15
16
  // Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
16
17
  // 'model' is compacted new style CSN
17
- // TODO: Error and warnings handling with compacted CSN? - currently just throw new Error for everything
18
+ // TODO: Error and warnings handling with compacted CSN? - currently just throw new ModelError for everything
18
19
  // TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true
19
20
  function getTransformers(model, options, pathDelimiter = '_') {
20
21
  const { error, warning, info } = makeMessageFunction(model, options);
@@ -108,7 +109,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
108
109
  // Transfer selected type properties from target key element
109
110
  // FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property.
110
111
  for (const prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) {
111
- if (fkArtifact[prop] != undefined) {
112
+ if (fkArtifact[prop] !== undefined) {
112
113
  foreignKeyElement[prop] = fkArtifact[prop];
113
114
  }
114
115
  }
@@ -118,7 +119,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
118
119
 
119
120
  // If the association is non-fkArtifact resp. key, so should be the foreign key field
120
121
  for (const prop of ['notNull', 'key']) {
121
- if (assoc[prop] != undefined) {
122
+ if (assoc[prop] !== undefined) {
122
123
  foreignKeyElement[prop] = assoc[prop];
123
124
  }
124
125
  }
@@ -246,6 +247,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
246
247
  const struct = elemType ? elemType.elements : elem.elements;
247
248
 
248
249
  // Collect all child elements (recursively) into 'result'
250
+ // TODO: Do not report collisions in the generated elements here, but instead
251
+ // leave that work to the receiver of this result
249
252
  let result = Object.create(null);
250
253
  const addGeneratedFlattenedElement = (e, eName) => {
251
254
  if(result[eName]){
@@ -286,7 +289,11 @@ function getTransformers(model, options, pathDelimiter = '_') {
286
289
  // Copy annotations from struct (not overwriting, because deep annotations should have precedence)
287
290
  copyAnnotations(elem, flatElem, false);
288
291
  // Copy selected type properties
289
- for (let p of ['key', 'virtual', 'masked', 'viaAll']) {
292
+ const props = ['key', 'virtual', 'masked', 'viaAll'];
293
+ // 'localized' is needed for OData
294
+ if(options.toOdata)
295
+ props.push('localized');
296
+ for (let p of props) {
290
297
  if (elem[p]) {
291
298
  flatElem[p] = elem[p];
292
299
  }
@@ -302,7 +309,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
302
309
  * [ (Entity), (struct1), (struct2), (assoc), (elem) ] should result in
303
310
  * [ (Entity), (struct1_struct2_assoc), (elem) ]
304
311
  *
305
- * @param {string[]} ref
312
+ * @param {string[]} ref
306
313
  * @param {CSN.Path} path CSN path to the ref
307
314
  * @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
308
315
  * @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
@@ -313,12 +320,14 @@ function getTransformers(model, options, pathDelimiter = '_') {
313
320
  // Refs of length 1 cannot contain steps - no need to check
314
321
  if (ref.length < 2) {
315
322
  return ref;
323
+ } else if(scope === '$self' && ref.length === 2) {
324
+ return ref;
316
325
  }
317
326
 
318
327
  return flatten(ref, path);
319
328
 
320
329
  function flatten(ref, path) {
321
- let result = [];
330
+ let result = scope === '$self' ? [ref[0]] : [];
322
331
  //let stack = []; // IDs of path steps not yet processed or part of a struct traversal
323
332
  if(!links && !scope) { // calculate JIT if not supplied
324
333
  const res = inspectRef(path);
@@ -327,23 +336,25 @@ function getTransformers(model, options, pathDelimiter = '_') {
327
336
  }
328
337
  if (scope === '$magic')
329
338
  return ref;
339
+ // Don't process a leading $self - it will a .art with .elements!
340
+ const startIndex = scope === '$self' ? 1 : 0;
330
341
  let flattenStep = false;
331
- links.forEach((value, idx) => {
342
+ for(let i = startIndex; i < links.length; i++) {
343
+ const value = links[i];
332
344
  if (flattenStep) {
333
- result[result.length - 1] += pathDelimiter + (ref[idx].id ? ref[idx].id : ref[idx]);
345
+ result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
334
346
  // if we had a filter or args, we had an assoc so this step is done
335
347
  // 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];
348
+ if(ref[i].id) {
349
+ ref[i].id = result[result.length-1];
350
+ result[result.length-1] = ref[i];
339
351
  }
340
352
  }
341
353
  else {
342
- result.push(ref[idx]);
354
+ result.push(ref[i]);
343
355
  }
344
-
345
356
  flattenStep = value.art && !value.art.kind && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements || (resolvedLinkTypes.get(value)||{}).elements);
346
- });
357
+ }
347
358
 
348
359
  return result;
349
360
  }
@@ -351,7 +362,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
351
362
 
352
363
  /**
353
364
  * Copy properties of the referenced type, but don't resolve to the final base type.
354
- *
365
+ *
355
366
  * Do not copy the length if it was just set via the default-value.
356
367
  *
357
368
  * @param {any} node Node to copy to
@@ -387,9 +398,9 @@ function getTransformers(model, options, pathDelimiter = '_') {
387
398
  * also unravel derived enum types, i.e. take the final base type of the enum's base type.
388
399
  * Similar with associations and compositions (we probably need a _baseType link)
389
400
  *
390
- * @param {CSN.Artifact} node
401
+ * @param {CSN.Artifact} node
391
402
  * @param {WeakMap} [resolved] WeakMap containing already resolved refs
392
- * @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
403
+ * @param {boolean} [keepLocalized=false] Whether to clone .localized from a type def
393
404
  * @returns {void}
394
405
  */
395
406
  function toFinalBaseType(node, resolved, keepLocalized=false) {
@@ -406,7 +417,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
406
417
  delete node.type; // delete the type reference as edm processing does not expect it
407
418
  } else if(finalBaseType.items) {
408
419
  // This changes the order - to be discussed!
409
- node.items = cloneCsn(finalBaseType, options).items; // copy items
420
+ node.items = cloneCsn(finalBaseType.items, options); // copy items
410
421
  delete node.type;
411
422
  } else {
412
423
  node.type=finalBaseType;
@@ -420,20 +431,23 @@ function getTransformers(model, options, pathDelimiter = '_') {
420
431
  let typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
421
432
  // Nothing to do if type is an array or a struct type
422
433
  if (typeDef.items || typeDef.elements) {
434
+ // Expand structured types on entity elements for flat OData
423
435
  if(!(options.transformation === 'hdbcds' || options.toSql))
424
436
  return;
425
437
 
426
438
  // cloneCsn only works correctly if we start "from the top"
427
- const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options);
428
- // With hdbcds-hdbcds, don't resolve structured types - but propagrate ".items", to turn into LargeString later on.
439
+ const cloneTypeDef = cloneCsn(typeDef, options);
440
+ // With hdbcds-hdbcds, don't resolve structured types - but propagate ".items", to turn into LargeString later on.
429
441
  if(typeDef.items) {
430
442
  delete node.type;
431
- Object.assign(node, {items: clone.definitions.TypeDef.items});
443
+ if(!node.items)
444
+ Object.assign(node, {items: cloneTypeDef.items});
432
445
  }
433
446
  if(typeDef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
434
447
  if(!typeDef.items)
435
448
  delete node.type;
436
- Object.assign(node, {elements: clone.definitions.TypeDef.elements});
449
+ if(!node.elements)
450
+ Object.assign(node, {elements: cloneTypeDef.elements});
437
451
  }
438
452
 
439
453
 
@@ -441,7 +455,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
441
455
  }
442
456
  // if the declared element is an enum, these values are with priority
443
457
  if (!node.enum && typeDef.enum) {
444
- const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options).definitions.TypeDef.enum;
458
+ const clone = cloneCsnDictionary(typeDef.enum, options);
445
459
  Object.assign(node, { enum: clone });
446
460
  }
447
461
  if (node.length === undefined && typeDef.length !== undefined)
@@ -593,7 +607,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
593
607
  // elemName typeName isKey defaultVal notNull
594
608
  function createScalarElement(elemName, typeName, isKey = false, defaultVal = undefined, notNull=false) {
595
609
  if (!isBuiltinType(typeName) && !model.definitions[typeName]) {
596
- throw new Error('Expecting valid type name: ' + typeName);
610
+ throw new ModelError('Expecting valid type name: ' + typeName);
597
611
  }
598
612
  let result = {
599
613
  [elemName]: {
@@ -684,7 +698,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
684
698
  function addForeignKey(foreignKey, elem) {
685
699
  // Sanity checks
686
700
  if (!elem.target || !elem.keys) {
687
- throw new Error('Expecting managed association element with foreign keys');
701
+ throw new ModelError('Expecting managed association element with foreign keys');
688
702
  }
689
703
 
690
704
  // Add the foreign key
@@ -703,7 +717,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
703
717
  function addElement(elem, artifact, artifactName) {
704
718
  // Sanity check
705
719
  if (!artifact.elements) {
706
- throw new Error('Expecting artifact with elements: ' + JSON.stringify(artifact));
720
+ throw new ModelError('Expecting artifact with elements: ' + JSON.stringify(artifact));
707
721
  }
708
722
  let elemName = Object.keys(elem)[0];
709
723
  // Element must not exist
@@ -734,7 +748,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
734
748
  */
735
749
  function copyAndAddElement(elem, artifact, artifactName, elementName) {
736
750
  if (!artifact.elements) {
737
- throw new Error('Expected structured artifact');
751
+ throw new ModelError('Expected structured artifact');
738
752
  }
739
753
  // Must not already have such an element
740
754
  if (artifact.elements[elementName]) {
@@ -765,14 +779,14 @@ function getTransformers(model, options, pathDelimiter = '_') {
765
779
 
766
780
  if (returnTypeName) {
767
781
  if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
768
- throw new Error('Expecting valid return type name: ' + returnTypeName);
782
+ throw new ModelError('Expecting valid return type name: ' + returnTypeName);
769
783
  action.returns = { type: returnTypeName };
770
784
  }
771
785
 
772
786
  // Add parameter if provided
773
787
  if (paramName && paramTypeName) {
774
788
  if (!isBuiltinType(paramTypeName) && !model.definitions[paramTypeName])
775
- throw new Error('Expecting valid parameter type name: ' + paramTypeName);
789
+ throw new ModelError('Expecting valid parameter type name: ' + paramTypeName);
776
790
 
777
791
  action.params = Object.create(null);
778
792
  action.params[paramName] = {
@@ -865,7 +879,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
865
879
  function checkMultipleAssignments(array, annoName, artifact, artifactName, err = true) {
866
880
  if (array.length > 1) {
867
881
  const loc = ['definitions', artifactName];
868
- if (err == true) {
882
+ if (err === true) {
869
883
  error(null, loc, { anno: annoName }, `Annotation $(ANNO) must be assigned only once`);
870
884
  } else {
871
885
  warning(null, loc, { anno: annoName },`Annotation $(ANNO) must be assigned only once`);
@@ -1084,7 +1098,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
1084
1098
  * Expand structured expression arguments to flat reference paths.
1085
1099
  * Structured elements are real sub element lists and managed associations.
1086
1100
  * All unmanaged association definitions are rewritten if applicable (elements/mixins).
1087
- * Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
1101
+ * Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
1088
1102
  * .xpr in columns.
1089
1103
  *
1090
1104
  * @todo Check if can be skipped for abstract entity and or cds.persistence.skip ?
@@ -1105,7 +1119,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
1105
1119
  'xpr': (parent, name, xpr, path) => {
1106
1120
  parent.xpr = expand(parent.xpr, path.concat(name));
1107
1121
  }
1108
- }, undefined, undefined, options);
1122
+ }, [], options);
1109
1123
 
1110
1124
  /*
1111
1125
  flatten structured leaf types and return array of paths
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const { setProp, forEachGeneric, forEachDefinition, isBetaEnabled } = require('../base/model');
4
- var { makeMessageFunction } = require('../base/messages');
4
+ const { makeMessageFunction } = require('../base/messages');
5
5
  const { recompileX } = require('../compiler/index');
6
- var { linkToOrigin } = require('../compiler/utils');
6
+ const { linkToOrigin, pathName } = require('../compiler/utils');
7
7
  const {compactModel, compactExpr} = require('../json/to-csn');
8
8
  const { deduplicateMessages } = require('../base/messages');
9
9
  const { timetrace } = require('../utils/timetrace');
@@ -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);
@@ -309,13 +309,13 @@ function translateAssocsToJoins(model, inputOptions = {})
309
309
  let [QA, ps] = rightMostQA(tail, head._navigation._parent.$QA || head._navigation.$QA);
310
310
  if(!QA) {
311
311
  error(null, pathNode.$location,
312
- { name: pathNode.path.map(ps=ps.id).join('.') },
312
+ { name: pathName(pathNode.path) },
313
313
  'Please debug me: No QA found for generic path rewriting in $(NAME)')
314
314
  return;
315
315
  }
316
316
  // if the found QA is the mixin QA and if the path length is one,
317
317
  // this indicates the publishing of a mixin assoc, don't rewrite the path
318
- if(QA.mixin && tail.length == 1)
318
+ if(QA.mixin && tail.length === 1)
319
319
  return;
320
320
  let pos = tail.indexOf(ps);
321
321
  // cut off ps if it's a join relevant association with postfix
@@ -670,7 +670,7 @@ function translateAssocsToJoins(model, inputOptions = {})
670
670
  if(fwdAssoc)
671
671
  {
672
672
  //env.assocStack.includes(fwdAssoc) => recursion
673
- if(env.assocStack.length == 2) {
673
+ if(env.assocStack.length === 2) {
674
674
  // reuse (ugly) error message from forHana
675
675
  error(null, env.assocStack[0].location,
676
676
  { name: '$self', id: '$self' },
@@ -702,7 +702,7 @@ function translateAssocsToJoins(model, inputOptions = {})
702
702
  // ON cond of the forward assoc with swapped src/tgt aliases
703
703
  let fwdAssoc = getForwardAssociationExpr(expr);
704
704
  if(fwdAssoc) {
705
- if(env.assocStack.length == 2) {
705
+ if(env.assocStack.length === 2) {
706
706
  // reuse (ugly) error message from forHana
707
707
  error(null, expr.location, 'An association that uses “$self” in its ON-condition can\'t be compared to “$self”');
708
708
  // don't check these paths again
@@ -736,7 +736,7 @@ function translateAssocsToJoins(model, inputOptions = {})
736
736
  let newSrcAlias = tgtAlias;
737
737
  let newTgtAlias = {};
738
738
  // first try to identify table alias for complex views or redirected associations
739
- if(fwdAssoc._redirected && fwdAssoc._redirected.length &&
739
+ if(fwdAssoc._redirected && fwdAssoc._redirected.length &&
740
740
  // redirected target must have a $QA
741
741
  fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA &&
742
742
  // $QA's artifact must either be same srcAlias artifact
@@ -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 {
@@ -912,7 +895,7 @@ function translateAssocsToJoins(model, inputOptions = {})
912
895
  combined list of elements made available by the from clause.
913
896
  */
914
897
  let _navigation = undefined; // don't modify original path
915
- if(env.assocStack.length == 2) {
898
+ if(env.assocStack.length === 2) {
916
899
  // a mixin assoc cannot have a structure prefix, it's sufficient to check head
917
900
  if(head.id === env.assocStack.id()) {
918
901
  // source side from view point of view (target side from forward point of view)
@@ -974,7 +957,7 @@ function translateAssocsToJoins(model, inputOptions = {})
974
957
 
975
958
  // Return the original association if expr is a backlink term, undefined otherwise
976
959
  function getForwardAssociationExpr(expr) {
977
- if(expr.op && expr.op.val === '=' && expr.args.length == 2) {
960
+ if(expr.op && expr.op.val === '=' && expr.args.length === 2) {
978
961
  return getForwardAssociation(expr.args[0].path, expr.args[1].path);
979
962
  }
980
963
  return undefined;
@@ -983,10 +966,10 @@ function translateAssocsToJoins(model, inputOptions = {})
983
966
  function getForwardAssociation(lhs, rhs) {
984
967
  // [alpha.]BACKLINK.[beta.]FORWARD
985
968
  if(lhs && rhs) {
986
- if(rhs.length == 1 && rhs[0].id === '$self' &&
969
+ if(rhs.length === 1 && rhs[0].id === '$self' &&
987
970
  lhs.length > 1 && hasPrefix(lhs))
988
971
  return lhs[lhs.length-1]._artifact;
989
- if(lhs.length == 1 && lhs[0].id === '$self' &&
972
+ if(lhs.length === 1 && lhs[0].id === '$self' &&
990
973
  rhs.length > 1 && hasPrefix(rhs))
991
974
  return rhs[rhs.length-1]._artifact;
992
975
  }
@@ -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,170 @@
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 && ( column.xpr || column.func || column.val !== undefined || column.param || column.SELECT || column.SET || column.ref && column.ref[0] in {
141
+ $at: 1, $now: 1, $user: 1, $session: 1,
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Call the given callback for all sub-things of a .expand/.inline and drill further down into other .expand/.inline
147
+ *
148
+ * @param {CSN.Column} column
149
+ * @param {Function} callback
150
+ */
151
+ function traverseExpandInline(column, callback) {
152
+ if (column.expand) {
153
+ column.expand.forEach((col) => {
154
+ callback(col);
155
+ traverseExpandInline(col, callback);
156
+ });
157
+ }
158
+ else if (column.inline) {
159
+ column.inline.forEach((col) => {
160
+ callback(col);
161
+ traverseExpandInline(col, callback);
162
+ });
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ module.exports = {
169
+ setCoreComputedOnViews,
170
+ };