@sap/cds-compiler 2.12.0 → 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 (118) hide show
  1. package/CHANGELOG.md +110 -15
  2. package/bin/cdsc.js +13 -13
  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 +28 -63
  8. package/lib/api/options.js +3 -3
  9. package/lib/api/validate.js +0 -5
  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 +25 -4
  16. package/lib/base/messages.js +16 -26
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +158 -123
  19. package/lib/checks/annotationsOData.js +1 -1
  20. package/lib/checks/cdsPersistence.js +2 -1
  21. package/lib/checks/enricher.js +17 -1
  22. package/lib/checks/invalidTarget.js +3 -1
  23. package/lib/checks/managedWithoutKeys.js +3 -1
  24. package/lib/checks/selectItems.js +4 -4
  25. package/lib/checks/sql-snippets.js +27 -26
  26. package/lib/checks/types.js +1 -1
  27. package/lib/checks/validator.js +4 -7
  28. package/lib/compiler/assert-consistency.js +5 -3
  29. package/lib/compiler/builtins.js +8 -6
  30. package/lib/compiler/checks.js +14 -3
  31. package/lib/compiler/cycle-detector.js +1 -1
  32. package/lib/compiler/define.js +1103 -0
  33. package/lib/compiler/extend.js +983 -0
  34. package/lib/compiler/finalize-parse-cdl.js +231 -0
  35. package/lib/compiler/index.js +32 -13
  36. package/lib/compiler/kick-start.js +190 -0
  37. package/lib/compiler/moduleLayers.js +4 -4
  38. package/lib/compiler/populate.js +1226 -0
  39. package/lib/compiler/propagator.js +111 -46
  40. package/lib/compiler/resolve.js +1433 -0
  41. package/lib/compiler/shared.js +64 -37
  42. package/lib/compiler/tweak-assocs.js +529 -0
  43. package/lib/compiler/utils.js +197 -33
  44. package/lib/edm/.eslintrc.json +5 -0
  45. package/lib/edm/annotations/genericTranslation.js +5 -9
  46. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  47. package/lib/edm/csn2edm.js +9 -8
  48. package/lib/edm/edm.js +11 -12
  49. package/lib/edm/edmPreprocessor.js +137 -73
  50. package/lib/edm/edmUtils.js +116 -22
  51. package/lib/gen/Dictionary.json +10 -3
  52. package/lib/gen/language.checksum +1 -1
  53. package/lib/gen/language.interp +9 -1
  54. package/lib/gen/language.tokens +86 -83
  55. package/lib/gen/languageLexer.interp +10 -1
  56. package/lib/gen/languageLexer.js +860 -833
  57. package/lib/gen/languageLexer.tokens +78 -75
  58. package/lib/gen/languageParser.js +5282 -4265
  59. package/lib/json/from-csn.js +12 -1
  60. package/lib/json/to-csn.js +126 -66
  61. package/lib/language/docCommentParser.js +2 -2
  62. package/lib/language/genericAntlrParser.js +76 -3
  63. package/lib/language/language.g4 +297 -130
  64. package/lib/language/multiLineStringParser.js +5 -5
  65. package/lib/main.d.ts +468 -59
  66. package/lib/main.js +35 -9
  67. package/lib/model/api.js +3 -1
  68. package/lib/model/csnRefs.js +225 -156
  69. package/lib/model/csnUtils.js +192 -223
  70. package/lib/model/enrichCsn.js +70 -29
  71. package/lib/model/revealInternalProperties.js +27 -6
  72. package/lib/model/sortViews.js +2 -1
  73. package/lib/modelCompare/compare.js +17 -12
  74. package/lib/optionProcessor.js +5 -4
  75. package/lib/render/manageConstraints.js +35 -32
  76. package/lib/render/toCdl.js +73 -288
  77. package/lib/render/toHdbcds.js +25 -23
  78. package/lib/render/toSql.js +98 -41
  79. package/lib/render/utils/common.js +5 -10
  80. package/lib/render/utils/sql.js +4 -3
  81. package/lib/render/utils/stringEscapes.js +111 -0
  82. package/lib/sql-identifier.js +1 -1
  83. package/lib/transform/.eslintrc.json +5 -0
  84. package/lib/transform/db/.eslintrc.json +2 -0
  85. package/lib/transform/db/applyTransformations.js +35 -12
  86. package/lib/transform/db/assertUnique.js +1 -1
  87. package/lib/transform/db/associations.js +103 -305
  88. package/lib/transform/db/cdsPersistence.js +2 -2
  89. package/lib/transform/db/constraints.js +55 -52
  90. package/lib/transform/db/expansion.js +46 -24
  91. package/lib/transform/db/flattening.js +553 -102
  92. package/lib/transform/db/groupByOrderBy.js +3 -1
  93. package/lib/transform/db/transformExists.js +59 -6
  94. package/lib/transform/db/views.js +5 -4
  95. package/lib/transform/draft/.eslintrc.json +38 -0
  96. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  97. package/lib/transform/draft/odata.js +227 -0
  98. package/lib/transform/forHanaNew.js +67 -183
  99. package/lib/transform/forOdataNew.js +17 -171
  100. package/lib/transform/localized.js +34 -19
  101. package/lib/transform/odata/generateForeignKeyElements.js +1 -1
  102. package/lib/transform/odata/referenceFlattener.js +95 -89
  103. package/lib/transform/odata/structureFlattener.js +1 -1
  104. package/lib/transform/odata/toFinalBaseType.js +86 -12
  105. package/lib/transform/odata/typesExposure.js +5 -5
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +36 -22
  108. package/lib/transform/translateAssocsToJoins.js +2 -19
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +170 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/objectUtils.js +30 -0
  114. package/package.json +1 -1
  115. package/share/messages/README.md +26 -0
  116. package/lib/compiler/definer.js +0 -2361
  117. package/lib/compiler/resolver.js +0 -3079
  118. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -2,7 +2,7 @@
2
2
 
3
3
  const {
4
4
  forEachDefinition, forEachMemberRecursively,
5
- isBuiltinType, cloneCsnDictionary,
5
+ isBuiltinType, cloneCsnDictionary, cloneCsn,
6
6
  } = require('../../model/csnUtils');
7
7
  const { isArtifactInSomeService, isArtifactInService } = require('./utils');
8
8
 
@@ -86,21 +86,95 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
86
86
  // EDMX at the moment and the reference in the OData CSN is fulfilled.
87
87
  if (node.kind === 'event') return;
88
88
 
89
- if (isExpandable(node, defName) || node.kind === 'type') {
90
- transformers.toFinalBaseType(node);
91
- if (node.type && !isBuiltinType(node.type)) {
92
- // handle array of defined via a named type
93
- // example in actions: 'action act() return Primitive; type Primitive: array of String;'
94
- const currService = csnUtils.getServiceName(defName);
95
- const finalType = csnUtils.getFinalTypeDef(node.type);
96
- if (finalType.items && isBuiltinType(finalType.items.type)) {
97
- if (!isArtifactInService(node.type, currService) || !isV4) {
98
- node.items = finalType.items;
89
+ // elements have precedence over type
90
+ if (node.type && (!isBuiltinType(node.type) &&isExpandable(node, defName) || node.kind === 'type')) {
91
+ // 1. Get the final type of the node (resolve derived type chain)
92
+ const finalType = csnUtils.getFinalBaseType(node.type);
93
+ if (finalType) {
94
+ // The type replacement depends on whether 'node' is a definition or a member[element].
95
+ if (node.kind) {
96
+ // It is a definition and we expand to builtin type and to elements
97
+ // type T: S; --> Integer;
98
+ // type S: X; --> Integer;
99
+ // type X: Integer;
100
+ //
101
+ // type A: B; -> {...}
102
+ // type B: C; -> { ... }
103
+ // type C { .... };
104
+ if (isBuiltinType(finalType)) {
105
+ // use transformUrilsNew::toFinalBaseType for the moment,
106
+ // as it is collects along the chain of types
107
+ // attributes that need to be propagated
108
+ // enum, length, scale, etc.
109
+ transformers.toFinalBaseType(node);
110
+ // node.type = finalType;
111
+ }
112
+ else if (csnUtils.isStructured(finalType)) {
113
+ cloneElements(finalType);
114
+ }
115
+ else if (node.type && node.items)
99
116
  delete node.type;
117
+ }
118
+ else {
119
+ // this is a member and we expand to final base only if builtin
120
+ // type T: S; --> Integer;
121
+ // type S: X; --> Integer;
122
+ // type X: Integer;
123
+ //
124
+ // type {
125
+ // struct_elt: many A; ---> stays the same
126
+ // scalar_elt: T; ---> Integer;
127
+ // type_ref_elt: type of struct_elt;
128
+ // };
129
+ // type A: B; -> {...}
130
+ // type B: C; -> { ... }
131
+ // type C { .... };
132
+ if (isBuiltinType(finalType)) {
133
+ // use transformUrilsNew::toFinalBaseType for the moment,
134
+ // as it is collects along the chain of types
135
+ // attributes that need to be propagated
136
+ // enum, length, scale, etc.
137
+ transformers.toFinalBaseType(node);
138
+ // node.type = finalType;
139
+ }
140
+ else if (node.type && node.type.ref) {
141
+ cloneElements(finalType);
100
142
  }
101
143
  }
102
144
  }
103
145
  }
146
+ if (node.type && !isBuiltinType(node.type)) {
147
+ // handle array of defined via a named type
148
+ // example in actions: 'action act() return Primitive; type Primitive: array of String;'
149
+ const currService = csnUtils.getServiceName(defName);
150
+ const finalType = csnUtils.getFinalTypeDef(node.type);
151
+ if (finalType.items && isBuiltinType(finalType.items.type)) {
152
+ if (!isArtifactInService(node.type, currService) || !isV4) {
153
+ node.items = finalType.items;
154
+ delete node.type;
155
+ }
156
+ }
157
+ }
158
+
159
+ function cloneElements(finalType) {
160
+ // cloneCsn only works correctly if we start "from the top"
161
+ let clone;
162
+ // do the clone only if really needed
163
+ if((finalType.items && !node.items) ||
164
+ (finalType.elements && !node.elements))
165
+ clone = cloneCsn({ definitions: { 'TypeDef': finalType } }, options);
166
+ if (finalType.items) {
167
+ delete node.type;
168
+ if(!node.items)
169
+ Object.assign(node, { items: clone.definitions.TypeDef.items });
170
+ }
171
+ if (finalType.elements) {
172
+ if(!finalType.items)
173
+ delete node.type;
174
+ if(!node.elements)
175
+ Object.assign(node, { elements: clone.definitions.TypeDef.elements });
176
+ }
177
+ }
104
178
  }
105
179
 
106
180
  function isExpandable(node, defName) {
@@ -119,4 +193,4 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
119
193
  }
120
194
  }
121
195
 
122
- module.exports = expandToFinalBaseType;
196
+ module.exports = expandToFinalBaseType;
@@ -18,7 +18,7 @@ const { copyAnnotations } = require('../../model/csnUtils');
18
18
  * @param {*} csnUtils
19
19
  * @param {object} message message object with { error } function
20
20
  */
21
- function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
21
+ function typesExposure(csn, whatsMyServiceName, autoexposeSchemaName, options, csnUtils, message) {
22
22
  const { error } = message;
23
23
  // are we working with OData proxies or cross-service refs
24
24
  const isMultiSchema = options.toOdata.version === 'v4' && (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs);
@@ -30,9 +30,9 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
30
30
  // we do expose types only for definition from inside services
31
31
  const serviceName = whatsMyServiceName(defName, false);
32
32
  if (serviceName) {
33
- if (['type', 'entity', 'view'].includes(def.kind)) {
33
+ if (def.kind === 'type' || def.kind === 'entity') {
34
34
  forEachMember(def, (element, elementName, propertyName, path) => {
35
- if (['elements', 'params'].includes(propertyName)) {
35
+ if (propertyName === 'elements' || propertyName === 'params') {
36
36
  const artificialtName = `${isMultiSchema ?
37
37
  defNameWithoutServiceOrContextName(defName, serviceName)
38
38
  : defNameWithoutServiceOrContextName(defName, serviceName).replace(/\./g, '_')}_${elementName}`;
@@ -190,7 +190,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
190
190
  } else {
191
191
  const typeContext = csnUtils.getContextOfArtifact(typeName);
192
192
  const typeNamespace = csnUtils.getNamespaceOfArtifact(typeName);
193
- const newSchemaName = `${service}.${typeContext || typeNamespace || 'root'}`;
193
+ const newSchemaName = `${service}.${typeContext || typeNamespace || autoexposeSchemaName}`;
194
194
  // new type name without any prefixes
195
195
  const typePlainName = typeContext ? defNameWithoutServiceOrContextName(typeName, typeContext)
196
196
  : typeName.replace(`${typeNamespace}.`, '');
@@ -209,7 +209,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
209
209
  */
210
210
  function getAnonymousTypeNameInMultiSchema(typeName, parentName) {
211
211
  let currPrefix = parentName.substring(0, parentName.lastIndexOf('.'));
212
- const newSchemaName = currPrefix || 'root';
212
+ const newSchemaName = currPrefix || autoexposeSchemaName;
213
213
  // new type name without any prefixes
214
214
  const typePlainName = defNameWithoutServiceOrContextName(typeName, newSchemaName);
215
215
 
@@ -18,7 +18,7 @@ function isAssociationOrComposition(artifact) {
18
18
  return isAssociation(artifact) || isComposition(artifact);
19
19
  }
20
20
 
21
- function isManagedAssociationElement(artifact) {
21
+ function isManagedAssociation(artifact) {
22
22
  return artifact.target !== undefined && artifact.on === undefined;
23
23
  }
24
24
 
@@ -94,5 +94,5 @@ module.exports = {
94
94
  isArtifactInSomeService,
95
95
  isAssociationOrComposition,
96
96
  isLocalizedArtifactInService,
97
- isManagedAssociationElement,
97
+ isManagedAssociation,
98
98
  }
@@ -10,11 +10,12 @@ const { csnRefs } = require('../model/csnRefs');
10
10
 
11
11
  const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
12
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);
@@ -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
  }
@@ -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
  }
@@ -389,7 +400,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
389
400
  *
390
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) {
@@ -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
439
  const cloneTypeDef = cloneCsn(typeDef, options);
428
- // With hdbcds-hdbcds, don't resolve structured types - but propagrate ".items", to turn into LargeString later on.
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: cloneTypeDef.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: cloneTypeDef.elements});
449
+ if(!node.elements)
450
+ Object.assign(node, {elements: cloneTypeDef.elements});
437
451
  }
438
452
 
439
453
 
@@ -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] = {
@@ -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
@@ -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,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
+ };