@sap/cds-compiler 2.15.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +102 -1590
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +61 -46
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  6. package/doc/CHANGELOG_BETA.md +26 -5
  7. package/doc/CHANGELOG_DEPRECATED.md +55 -1
  8. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  9. package/doc/Versioning.md +20 -1
  10. package/lib/api/.eslintrc.json +2 -2
  11. package/lib/api/main.js +282 -156
  12. package/lib/api/options.js +17 -88
  13. package/lib/api/validate.js +6 -10
  14. package/lib/base/keywords.js +280 -110
  15. package/lib/base/message-registry.js +85 -25
  16. package/lib/base/messages.js +119 -89
  17. package/lib/base/model.js +46 -2
  18. package/lib/base/optionProcessorHelper.js +53 -21
  19. package/lib/checks/actionsFunctions.js +15 -12
  20. package/lib/checks/annotationsOData.js +1 -1
  21. package/lib/checks/cdsPersistence.js +1 -0
  22. package/lib/checks/elements.js +6 -6
  23. package/lib/checks/invalidTarget.js +1 -1
  24. package/lib/checks/nonexpandableStructured.js +1 -1
  25. package/lib/checks/queryNoDbArtifacts.js +2 -1
  26. package/lib/checks/selectItems.js +101 -15
  27. package/lib/checks/types.js +7 -8
  28. package/lib/checks/utils.js +2 -2
  29. package/lib/checks/validator.js +3 -3
  30. package/lib/compiler/assert-consistency.js +78 -21
  31. package/lib/compiler/base.js +6 -4
  32. package/lib/compiler/builtins.js +177 -10
  33. package/lib/compiler/checks.js +1 -1
  34. package/lib/compiler/define.js +28 -23
  35. package/lib/compiler/extend.js +75 -18
  36. package/lib/compiler/finalize-parse-cdl.js +25 -18
  37. package/lib/compiler/index.js +27 -11
  38. package/lib/compiler/moduleLayers.js +7 -0
  39. package/lib/compiler/populate.js +26 -39
  40. package/lib/compiler/propagator.js +12 -7
  41. package/lib/compiler/resolve.js +207 -236
  42. package/lib/compiler/shared.js +100 -93
  43. package/lib/compiler/tweak-assocs.js +13 -20
  44. package/lib/compiler/utils.js +20 -6
  45. package/lib/edm/annotations/preprocessAnnotations.js +12 -13
  46. package/lib/edm/csn2edm.js +35 -37
  47. package/lib/edm/edm.js +22 -13
  48. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  49. package/lib/edm/edmInboundChecks.js +85 -0
  50. package/lib/edm/edmPreprocessor.js +338 -689
  51. package/lib/edm/edmUtils.js +97 -67
  52. package/lib/gen/Dictionary.json +29 -9
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +8 -31
  55. package/lib/gen/language.tokens +105 -114
  56. package/lib/gen/languageLexer.interp +1 -34
  57. package/lib/gen/languageLexer.js +892 -1007
  58. package/lib/gen/languageLexer.tokens +95 -106
  59. package/lib/gen/languageParser.js +20629 -22474
  60. package/lib/inspect/.eslintrc.json +4 -0
  61. package/lib/inspect/index.js +14 -0
  62. package/lib/inspect/inspectModelStatistics.js +81 -0
  63. package/lib/inspect/inspectPropagation.js +189 -0
  64. package/lib/inspect/inspectUtils.js +44 -0
  65. package/lib/json/from-csn.js +74 -69
  66. package/lib/json/to-csn.js +17 -14
  67. package/lib/language/antlrParser.js +2 -2
  68. package/lib/language/docCommentParser.js +61 -38
  69. package/lib/language/errorStrategy.js +52 -40
  70. package/lib/language/genericAntlrParser.js +424 -292
  71. package/lib/language/language.g4 +604 -687
  72. package/lib/language/multiLineStringParser.js +14 -42
  73. package/lib/language/textUtils.js +44 -0
  74. package/lib/main.d.ts +28 -42
  75. package/lib/main.js +104 -81
  76. package/lib/model/api.js +1 -1
  77. package/lib/model/csnRefs.js +57 -30
  78. package/lib/model/csnUtils.js +189 -287
  79. package/lib/model/revealInternalProperties.js +32 -10
  80. package/lib/model/sortViews.js +32 -31
  81. package/lib/modelCompare/compare.js +3 -0
  82. package/lib/optionProcessor.js +91 -57
  83. package/lib/render/.eslintrc.json +1 -1
  84. package/lib/render/DuplicateChecker.js +4 -7
  85. package/lib/render/manageConstraints.js +70 -2
  86. package/lib/render/toCdl.js +387 -367
  87. package/lib/render/toHdbcds.js +20 -16
  88. package/lib/render/toRename.js +44 -22
  89. package/lib/render/toSql.js +81 -59
  90. package/lib/render/utils/common.js +16 -3
  91. package/lib/render/utils/sql.js +20 -19
  92. package/lib/sql-identifier.js +6 -0
  93. package/lib/transform/db/.eslintrc.json +3 -2
  94. package/lib/transform/db/associations.js +43 -35
  95. package/lib/transform/db/cdsPersistence.js +5 -16
  96. package/lib/transform/db/constraints.js +1 -1
  97. package/lib/transform/db/expansion.js +7 -6
  98. package/lib/transform/db/flattening.js +16 -18
  99. package/lib/transform/db/transformExists.js +7 -5
  100. package/lib/transform/db/views.js +3 -3
  101. package/lib/transform/draft/.eslintrc.json +2 -2
  102. package/lib/transform/draft/db.js +6 -6
  103. package/lib/transform/draft/odata.js +6 -7
  104. package/lib/transform/forHanaNew.js +30 -24
  105. package/lib/transform/forOdataNew.js +14 -16
  106. package/lib/transform/localized.js +35 -25
  107. package/lib/transform/odata/toFinalBaseType.js +10 -10
  108. package/lib/transform/odata/typesExposure.js +17 -8
  109. package/lib/transform/odata/utils.js +1 -38
  110. package/lib/transform/transformUtilsNew.js +63 -77
  111. package/lib/transform/translateAssocsToJoins.js +2 -2
  112. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  113. package/lib/transform/universalCsn/coreComputed.js +11 -6
  114. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  115. package/lib/utils/file.js +31 -21
  116. package/lib/utils/moduleResolve.js +0 -1
  117. package/lib/utils/timetrace.js +20 -21
  118. package/package.json +34 -4
  119. package/share/messages/syntax-expected-integer.md +9 -8
  120. package/doc/ApiMigration.md +0 -237
  121. package/doc/CommandLineMigration.md +0 -58
  122. package/doc/ErrorMessages.md +0 -175
  123. package/doc/FioriAnnotations.md +0 -94
  124. package/doc/ODataTransformation.md +0 -273
  125. package/lib/backends.js +0 -529
  126. package/lib/checks/unknownMagic.js +0 -41
  127. package/lib/fix_antlr4-8_warning.js +0 -56
package/lib/base/model.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { forEach } = require("../utils/objectUtils");
4
+
3
5
  const queryOps = {
4
6
  query: 'select', // TODO: rename to SELECT
5
7
  union: 'union',
@@ -20,7 +22,6 @@ const queryOps = {
20
22
  const availableBetaFlags = {
21
23
  // enabled by --beta-mode
22
24
  toRename: true,
23
- addTextsLanguageAssoc: true,
24
25
  assocsWithParams: true,
25
26
  hanaAssocRealCardinality: true,
26
27
  mapAssocToJoinCardinality: true,
@@ -29,6 +30,8 @@ const availableBetaFlags = {
29
30
  enableUniversalCsn: true,
30
31
  // disabled by --beta-mode
31
32
  nestedServices: false,
33
+ odataOpenType: true,
34
+ optionalActionFunctionParameters: true,
32
35
  };
33
36
 
34
37
  /**
@@ -58,16 +61,56 @@ function isBetaEnabled( options, feature ) {
58
61
  * Please do not move this function to the "option processor" code.
59
62
  *
60
63
  * @param {object} options Options
61
- * @param {string} [feature] Feature to check for
64
+ * @param {string|null} [feature] Feature to check for
62
65
  * @returns {boolean}
63
66
  */
64
67
  function isDeprecatedEnabled( options, feature = null ) {
65
68
  const { deprecated } = options;
66
69
  if (!feature)
67
70
  return !!deprecated;
71
+
68
72
  return deprecated && typeof deprecated === 'object' && deprecated[feature];
69
73
  }
70
74
 
75
+ const oldDeprecatedFlags_v2 = [
76
+ 'createLocalizedViews',
77
+ 'downgradableErrors',
78
+ 'generatedEntityNameWithUnderscore',
79
+ 'longAutoexposed',
80
+ 'noElementsExpansion',
81
+ 'noInheritedAutoexposeViaComposition',
82
+ 'noScopedRedirections',
83
+ 'oldVirtualNotNullPropagation',
84
+ 'parensAsStrings',
85
+ 'projectionAsQuery',
86
+ 'redirectInSubQueries',
87
+ 'renderVirtualElements',
88
+ 'shortAutoexposed',
89
+ 'unmanagedUpInComponent',
90
+ 'v1KeysForTemporal',
91
+ ];
92
+
93
+ /**
94
+ * In cds-compiler v3, we removed old v2 deprecated flags. That can lead to silent
95
+ * errors such as entity/view names changing. To ensure that the user is forced
96
+ * to change their code, emit an error if one of such removed flags was used.
97
+ *
98
+ * @param {CSN.Options} options
99
+ * @param error Error message function returned by makeMessageFunctions().
100
+ */
101
+ function checkRemovedDeprecatedFlags( options, { error } ) {
102
+ // Assume that we emitted these errors once if a message with this ID was found.
103
+ if (!options.deprecated || options.messages?.some(m => m.messageId === 'api-invalid-deprecated'))
104
+ return;
105
+
106
+ forEach(options.deprecated, (key, val) => {
107
+ if (val && oldDeprecatedFlags_v2.includes(key)) {
108
+ error('api-invalid-deprecated', null, { name: key },
109
+ 'Deprecated flag $(NAME) has been removed in CDS compiler v3');
110
+ }
111
+ });
112
+ }
113
+
71
114
  // Apply function `callback` to all artifacts in dictionary
72
115
  // `model.definitions`. See function `forEachGeneric` for details.
73
116
  function forEachDefinition( model, callback ) {
@@ -129,6 +172,7 @@ module.exports = {
129
172
  isBetaEnabled,
130
173
  availableBetaFlags,
131
174
  isDeprecatedEnabled,
175
+ checkRemovedDeprecatedFlags,
132
176
  queryOps,
133
177
  forEachDefinition,
134
178
  forEachMember,
@@ -153,36 +153,67 @@ function createOptionProcessor() {
153
153
  * Internal: Define a general or command option.
154
154
  * Throws if the option is already registered in the given command context.
155
155
  * or in the given command.
156
+ *
156
157
  * @private
157
158
  * @see option()
158
159
  */
159
160
  function _addOption(cmd, optString, validValues, options) {
160
- const opt = _parseOptionString(optString, validValues);
161
- Object.assign(opt, options);
161
+ const cliOpt = _parseOptionString(optString, validValues);
162
+ Object.assign(cliOpt, options);
163
+ _addLongOption(cmd, cliOpt.longName, cliOpt);
164
+ _addShortOption(cmd, cliOpt.shortName, cliOpt);
165
+
166
+ for (const alias of cliOpt.aliases || []) {
167
+ const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
168
+ _addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
169
+ }
170
+
171
+ return cmd;
172
+ }
162
173
 
163
- if (cmd.options[opt.longName]) {
164
- throw new Error(`Duplicate assignment for long option ${opt.longName}`);
165
- } else if (optionProcessor.options[opt.longName]) {
174
+ /**
175
+ * Internal: Add longName to the list of options.
176
+ * Throws if the option is already registered in the given command context.
177
+ * or in the given command.
178
+ * `longName` may differ from `opt.longName`, e.g. for aliases.
179
+ *
180
+ * @private
181
+ * @see _addOption()
182
+ */
183
+ function _addLongOption(cmd, longName, opt) {
184
+ if (cmd.options[longName]) {
185
+ throw new Error(`Duplicate assignment for long option ${longName}`);
186
+ } else if (optionProcessor.options[longName]) {
166
187
  // This path is only taken if optString is for commands
167
188
  optionProcessor.optionClashes.push({
168
- option: opt.longName,
169
- description: `Command '${cmd.longName}' has option clash with general options for: ${opt.longName}`
189
+ option: longName,
190
+ description: `Command '${cmd.longName}' has option clash with general options for: ${longName}`
170
191
  });
171
192
  }
172
- cmd.options[opt.longName] = opt;
173
- if (opt.shortName) {
174
- if (cmd.options[opt.shortName]) {
175
- throw new Error(`Duplicate assignment for short option ${opt.shortName}`);
176
- } else if (optionProcessor.options[opt.shortName]) {
177
- // This path is only taken if optString is for commands
178
- optionProcessor.optionClashes.push({
179
- option: opt.shortName,
180
- description: `Command '${cmd.longName}' has option clash with general options for: ${opt.shortName}`
181
- });
182
- }
183
- cmd.options[opt.shortName] = opt;
193
+ cmd.options[longName] = opt;
194
+ }
195
+ /**
196
+ * Internal: Add shortName to the list of options.
197
+ * Throws if the option is already registered in the given command context.
198
+ * or in the given command.
199
+ * `longName` may differ from `opt.longName`, e.g. for aliases.
200
+ *
201
+ * @private
202
+ * @see _addOption()
203
+ */
204
+ function _addShortOption(cmd, shortName, opt) {
205
+ if (!shortName)
206
+ return;
207
+ if (cmd.options[shortName]) {
208
+ throw new Error(`Duplicate assignment for short option ${shortName}`);
209
+ } else if (optionProcessor.options[shortName]) {
210
+ // This path is only taken if optString is for commands
211
+ optionProcessor.optionClashes.push({
212
+ option: shortName,
213
+ description: `Command '${cmd.longName}' has option clash with general options for: ${shortName}`
214
+ });
184
215
  }
185
- return cmd;
216
+ cmd.options[shortName] = opt;
186
217
  }
187
218
 
188
219
  // Internal: Parse one command string like "F, toFoo". Return an object like this
@@ -275,7 +306,8 @@ function createOptionProcessor() {
275
306
  shortName,
276
307
  camelName: camelifyLongOption(longName),
277
308
  param,
278
- validValues
309
+ validValues,
310
+ isAlias: false, // default
279
311
  }
280
312
  }
281
313
 
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { isBuiltinType } = require('../model/csnUtils');
4
+ const { isBetaEnabled } = require('../base/model');
4
5
 
5
6
  // Only to be used with validator.js - a correct this value needs to be provided!
6
7
 
@@ -16,8 +17,8 @@ function checkActionOrFunction(art, artName, prop, path) {
16
17
  if (!(art.kind === 'action' || art.kind === 'function') && !art.actions)
17
18
  return;
18
19
 
19
- // const isMultiSchema = this.options.toOdata.odataFormat === 'structured' &&
20
- // (this.options.toOdata.odataProxies || this.options.toOdata.odataXServiceRefs);
20
+ // const isMultiSchema = this.options.odataFormat === 'structured' &&
21
+ // (this.options.odataProxies || this.options.odataXServiceRefs);
21
22
 
22
23
  const serviceName = this.csnUtils.getServiceName(artName);
23
24
  if (!serviceName)
@@ -52,13 +53,13 @@ function checkActionOrFunction(art, artName, prop, path) {
52
53
  function checkActionOrFunctionParameter(param, currPath, actKind) {
53
54
  const paramType = param.type ? this.csnUtils.getFinalTypeDef(param.type) : param;
54
55
 
55
- if (param.default || paramType.default) {
56
- this.error('param-default', currPath, { '#': actKind },
57
- {
58
- std: 'Artifact parameters can\'t have a default value', // Not used
59
- action: 'Action parameters can\'t have a default value',
60
- function: 'Function parameters can\'t have a default value',
61
- });
56
+ if (!isBetaEnabled(this.options, 'optionalActionFunctionParameters') && (param.default || paramType.default)) {
57
+ this.message('param-default', currPath, { '#': actKind },
58
+ {
59
+ std: 'Artifact parameters can\'t have a default value', // Not used
60
+ action: 'Action parameters can\'t have a default value',
61
+ function: 'Function parameters can\'t have a default value',
62
+ });
62
63
  }
63
64
 
64
65
  if (paramType.type && this.csnUtils.isAssocOrComposition(param.type)) {
@@ -85,9 +86,11 @@ function checkActionOrFunction(art, artName, prop, path) {
85
86
  * @param {string} actKind 'action' or 'function'
86
87
  */
87
88
  function checkReturns(returns, currPath, actKind) {
88
- const finalReturnType = returns.type ? this.csnUtils.getFinalBaseType(returns.type) : returns;
89
+ const finalReturnType = returns.type ? this.csnUtils.getFinalBaseTypeWithProps(returns.type) : returns;
90
+ if (!finalReturnType)
91
+ return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
89
92
 
90
- if (this.csnUtils.isAssocOrComposition(finalReturnType)) {
93
+ if (this.csnUtils.isAssocOrComposition(finalReturnType.type)) {
91
94
  this.error(null, currPath, { '#': actKind },
92
95
  {
93
96
  std: 'An association is not allowed as this artifact\'s return type', // Not used
@@ -98,7 +101,7 @@ function checkActionOrFunction(art, artName, prop, path) {
98
101
 
99
102
  if (finalReturnType.items) // check array return type
100
103
  checkReturns.bind(this)(finalReturnType.items, currPath.concat('items'), actKind);
101
- else // check if return type is user definited from the current service
104
+ else // check if return type is user defined from the current service
102
105
  checkUserDefinedType.bind(this)(finalReturnType, returns.type, currPath);
103
106
  }
104
107
 
@@ -23,7 +23,7 @@ function checkCoreMediaTypeAllowence(member) {
23
23
  'cds.hana.CLOB': 1,
24
24
  'cds.hana.BINARY': 1,
25
25
  };
26
- if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseType(member.type) in allowedCoreMediaTypes)) {
26
+ if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type in allowedCoreMediaTypes)) {
27
27
  this.warning(null, member.$path, { names: [ 'Edm.String', 'Edm.Binary' ] },
28
28
  'Element annotated with “@Core.MediaType” should be of a type mapped to $(NAMES)');
29
29
  }
@@ -14,6 +14,7 @@ function validateCdsPersistenceAnnotation(artifact, artifactName, prop, path) {
14
14
  if (artifact.kind === 'entity') {
15
15
  // filter for 'table', 'udf', 'calcview' === true
16
16
  const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
17
+ // TODO: Why not filter over persistenceAnnos, is shorter!
17
18
  const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);
18
19
  if (TableUdfCv.length > 1)
19
20
  this.error(null, path, `Annotations ${ TableUdfCv.join(', ') } can't be used in combination`);
@@ -36,10 +36,10 @@ function checkPrimaryKey(art) {
36
36
  */
37
37
  function checkIfPrimaryKeyIsOfGeoType(member, elemFqName, parentIsKey, parentPath) {
38
38
  if (member.key || parentIsKey) {
39
- const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
40
- if (typeof finalBaseType === 'string' && isGeoTypeName(finalBaseType)) {
39
+ const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
40
+ if (isGeoTypeName(finalBaseType?.type)) {
41
41
  this.error(null, parentPath || member.$path,
42
- { type: finalBaseType, name: elemFqName },
42
+ { type: finalBaseType.type, name: elemFqName },
43
43
  'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
44
44
  }
45
45
  else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
@@ -63,7 +63,7 @@ function checkPrimaryKey(art) {
63
63
  */
64
64
  function checkIfPrimaryKeyIsArray(member, elemFqName, parentIsKey, parentPath) {
65
65
  if (member.key || parentIsKey) {
66
- const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
66
+ const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
67
67
  if (member.items || (finalBaseType && finalBaseType.items)) {
68
68
  this.error(null, parentPath || member.$path, { name: elemFqName },
69
69
  'Array-like type in element $(NAME) can\'t be used as primary key');
@@ -96,10 +96,10 @@ function checkVirtualElement(member) {
96
96
 
97
97
  /**
98
98
  * Checks whether managed associations
99
- * with cardinality 'to many' have an on-condition
100
- * and if managed associations have foreign keys.
99
+ * with cardinality 'to many' have an on-condition.
101
100
  *
102
101
  * @param {CSN.Artifact} art The artifact
102
+ * @todo this is a member validator, is it not?
103
103
  */
104
104
  function checkManagedAssoc(art) {
105
105
  forEachMemberRecursively(art, (member) => {
@@ -26,7 +26,7 @@ function invalidTarget(member) {
26
26
  if (!target)
27
27
  throw new ModelError(`Expected target ${ mem.target }`);
28
28
  if (target.kind !== 'entity') {
29
- const isAssoc = this.csnUtils.getFinalBaseType(member.type) !== 'cds.Composition';
29
+ const isAssoc = this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type !== 'cds.Composition';
30
30
  this.error(
31
31
  null,
32
32
  member.$path,
@@ -21,7 +21,7 @@ function nonexpandableStructuredInExpression(parent, name, expression) {
21
21
  if (_art) {
22
22
  _art = resolveArtifactType.call(this, _art);
23
23
  // Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
24
- if (_art.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
24
+ if (_art?.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
25
25
  this.error(null, expression[i].$path, { elemref: { ref } },
26
26
  'Unexpected usage of structured type $(ELEMREF)');
27
27
  }
@@ -3,7 +3,8 @@
3
3
  const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('../model/csnUtils');
4
4
  /**
5
5
  * Make sure that all source artifacts and association targets reach the database
6
- * (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database
6
+ * (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database // <- what does this mean?
7
+ *
7
8
  * Check the given query for:
8
9
  * - Associations-traversal over skipped/abstract things
9
10
  * - Associations (indirectly) using managed associations without foreign keys
@@ -1,33 +1,119 @@
1
1
  'use strict';
2
2
 
3
- const { forEachGeneric } = require('../model/csnUtils');
3
+ const { forEachGeneric, applyTransformationsOnNonDictionary } = require('../model/csnUtils');
4
4
 
5
5
  // Only to be used with validator.js - a correct this value needs to be provided!
6
6
 
7
7
  /**
8
- * Validate select items of a query.
8
+ * Validate select items of a query. If a column reference starts with $self or $projection, it must not contain association steps.
9
+ * Furthermore, for to.hdbcds, window functions are not allowed.
10
+ *
11
+ * For to.hdbcds-hdbcds, structures and managed associations are not allowed as they are not flattened - @see rejectManagedAssociationsAndStructuresForHdbcdsNames
9
12
  *
10
13
  * @param {CSN.Query} query query object
14
+ * @todo Why do we care about this with $self?
11
15
  */
12
16
  function validateSelectItems(query) {
13
17
  const { SELECT } = query;
14
18
  if (!SELECT)
15
19
  return;
20
+ /**
21
+ * Check for a $self.<assoc> in columns etc. - since the $self.<assoc> references the "outside" view
22
+ * of the association, this is not allowed.
23
+ *
24
+ * @param {string} queryPart Part of the query that is being checked
25
+ * @returns {Function} Function as callback for applyTransformations
26
+ */
27
+ function checkRefForInvalid$Self(queryPart) {
28
+ const signalError = (error, parent, type) => {
29
+ if (queryPart === 'columns') {
30
+ error(null, parent.$path,
31
+ { name: parent.ref[0], type },
32
+ 'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
33
+ }
34
+ else if (queryPart === 'orderBy') {
35
+ error(null, parent.$path,
36
+ { id: queryPart, type },
37
+ 'Items of the $(ID)-clause must not contain path steps of type $(TYPE)');
38
+ }
39
+ else {
40
+ error(null, parent.$path,
41
+ { id: queryPart, name: parent.ref[0], type },
42
+ 'Items of the $(ID)-clause starting with $(NAME) must not contain path steps of type $(TYPE)');
43
+ }
44
+ };
45
+ return function checkForInvalid$SelfInRef(parent) {
46
+ if (parent.ref && (parent.$scope === '$self' || parent.$scope === '$query')) {
47
+ const { _links } = parent;
48
+ for (let j = parent.$scope === '$self' ? 1 : 0; j < _links.length - 1; j++) {
49
+ if (_links[j].art.target) {
50
+ if (_links[j].art.on) {
51
+ // It's an unmanaged association - traversal is always forbidden
52
+ signalError(this.error, parent, _links[j].art.type);
53
+ }
54
+ else {
55
+ // It's a managed association - access of the foreign keys is allowed
56
+ const nextRef = parent.ref[j + 1].id || parent.ref[j + 1];
57
+ if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
58
+ signalError(this.error, parent, _links[j].art.type);
59
+ }
60
+ }
61
+ }
62
+
63
+ const last = _links[_links.length - 1];
64
+
65
+ if (last.art.target && last.art.on) {
66
+ // It's an unmanaged association - traversal is always forbidden
67
+ signalError(this.error, parent, last.art.type);
68
+ } // managed is okay, can be handled via tuple expansion
69
+ }
70
+ };
71
+ }
16
72
 
17
- forEachGeneric(SELECT, 'columns', (selectItem) => {
18
- if (selectItem.ref && (selectItem.ref[0] === '$self' || selectItem.ref[0] === '$projection')) {
19
- const pathStepWithTarget = selectItem._links.slice(1).find(link => link.art.target);
20
- if (pathStepWithTarget) {
21
- this.error(null, selectItem.$path,
22
- { name: selectItem.ref[0], type: pathStepWithTarget.art.type },
23
- 'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
73
+ /**
74
+ * Check the given assoc filter for usage of $self - in an assoc-filter, you must only
75
+ * address things on the target side of the association, not from global scope.
76
+ *
77
+ * @param {object} parent
78
+ * @param {string} prop
79
+ * @param {Array} where
80
+ */
81
+ function checkFilterForInvalid$Self(parent, prop, where) {
82
+ where.forEach((whereStep) => {
83
+ if (whereStep.ref && ( whereStep.ref[0] === '$projection' || whereStep.ref[0] === '$self')) {
84
+ this.error('expr-where-unexpected-self', whereStep.$path,
85
+ { name: whereStep.ref[0] },
86
+ 'Path steps inside of filters must not start with $(NAME)');
24
87
  }
25
- }
26
- else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
27
- this.error(null, selectItem.$path,
28
- 'Window functions are not supported by SAP HANA CDS');
29
- }
30
- });
88
+ });
89
+ }
90
+
91
+ const aTCB = (parent, prop) => {
92
+ applyTransformationsOnNonDictionary(parent, prop, {
93
+ ref: checkRefForInvalid$Self(prop).bind(this),
94
+ where: checkFilterForInvalid$Self.bind(this),
95
+ }, { skipStandard: { on: true }, drillRef: true });
96
+ };
97
+
98
+ const transformers = {
99
+ columns: aTCB,
100
+ groupBy: aTCB,
101
+ orderBy: aTCB,
102
+ having: aTCB,
103
+ where: aTCB,
104
+ };
105
+
106
+ if (this.options.transformation === 'hdbcds') {
107
+ transformers.xpr = (parent) => {
108
+ if (parent.func) {
109
+ this.error(null, parent.$path,
110
+ 'Window functions are not supported by SAP HANA CDS');
111
+ }
112
+ };
113
+ }
114
+
115
+ applyTransformationsOnNonDictionary(query, 'SELECT', transformers );
116
+
31
117
  // .call() with 'this' to ensure we have access to the options
32
118
  rejectManagedAssociationsAndStructuresForHdbcdsNames.call(this, SELECT, SELECT.$path);
33
119
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getUtils, isBuiltinType, hasAnnotationValue } = require('../model/csnUtils');
3
+ const { getUtils, hasAnnotationValue } = require('../model/csnUtils');
4
4
 
5
5
  // Only to be used with validator.js - a correct this value needs to be provided!
6
6
 
@@ -55,7 +55,7 @@ function checkElementTypeDefinitionHasType(member, memberName, prop, path) {
55
55
 
56
56
  // should only happen with csn input, not in cdl
57
57
  if (!hasArtifactTypeInformation(member)) {
58
- warnAboutMissingType(this.error, path, memberName, true);
58
+ errorAboutMissingType(this.error, path, memberName, true);
59
59
  return;
60
60
  }
61
61
 
@@ -96,7 +96,7 @@ function checkTypeDefinitionHasType(artifact, artifactName, prop, path) {
96
96
 
97
97
  // should only happen with csn input, not in cdl
98
98
  if (!hasArtifactTypeInformation(artifact)) {
99
- warnAboutMissingType(this.error, path, artifactName);
99
+ errorAboutMissingType(this.error, path, artifactName);
100
100
  return;
101
101
  }
102
102
 
@@ -129,8 +129,8 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
129
129
  if (!artOrElement.type)
130
130
  return;
131
131
 
132
- const { getFinalBaseType } = getUtils(model);
133
- const typeOfType = getFinalBaseType(artOrElement.type, path);
132
+ const { getFinalBaseTypeWithProps } = getUtils(model);
133
+ const typeOfType = getFinalBaseTypeWithProps(artOrElement.type);
134
134
 
135
135
  if (typeOfType === null) {
136
136
  if (artOrElement.type.ref) {
@@ -158,7 +158,7 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
158
158
  * @param {string} name of the element or the artifact which is dubious
159
159
  * @param {boolean} isElement indicates whether we are dealing with an element or an artifact
160
160
  */
161
- function warnAboutMissingType(error, path, name, isElement = false) {
161
+ function errorAboutMissingType(error, path, name, isElement = false) {
162
162
  error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
163
163
  std: 'Dubious type $(ART) without type information',
164
164
  elm: 'Dubious element $(ART) without type information',
@@ -176,8 +176,7 @@ function warnAboutMissingType(error, path, name, isElement = false) {
176
176
  */
177
177
  function hasArtifactTypeInformation(artifact) {
178
178
  // When is what property set?
179
- return isBuiltinType(artifact.type) || // => `Integer`
180
- artifact.elements || // => `type A {}`
179
+ return artifact.elements || // => `type A {}`
181
180
  artifact.items || // => `type A : array of Integer`
182
181
  artifact.enum || // => `type A : Integer enum {}`, `type` also set
183
182
  artifact.target || // => `type A : Association to B;`
@@ -31,7 +31,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
31
31
  return false;
32
32
 
33
33
  /**
34
- * Artifact is structured or a managed association/compoisition
34
+ * Artifact is structured or a managed association/composition
35
35
  *
36
36
  * @param {CSN.Artifact} art Artifact
37
37
  * @returns {boolean} True if expandable
@@ -49,7 +49,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
49
49
  */
50
50
  function resolveArtifactType(art) {
51
51
  if (art && art.type && !isBuiltinType(art.type))
52
- return this.getFinalBaseType(art);
52
+ return this.csnUtils.getFinalBaseTypeWithProps(art.type);
53
53
 
54
54
  return art;
55
55
  }
@@ -32,7 +32,6 @@ const { validateAssociationsInItems } = require('./arrayOfs');
32
32
  const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
33
33
  const checkExplicitlyNullableKeys = require('./nullableKeys');
34
34
  const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
35
- const unknownMagic = require('./unknownMagic');
36
35
  const managedWithoutKeys = require('./managedWithoutKeys');
37
36
  const {
38
37
  checkSqlAnnotationOnArtifact,
@@ -62,7 +61,7 @@ const forHanaArtifactValidators
62
61
  checkSqlAnnotationOnArtifact,
63
62
  ];
64
63
 
65
- const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];
64
+ const forHanaCsnValidators = [ nonexpandableStructuredInExpression ];
66
65
  /**
67
66
  * @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
68
67
  */
@@ -103,8 +102,9 @@ const commonMemberValidators
103
102
  validateAssociationsInItems, checkForInvalidTarget,
104
103
  checkVirtualElement, checkElementTypeDefinitionHasType ];
105
104
 
105
+ // TODO: checkManagedAssoc is a forEachMemberRecursively!
106
106
  const commonArtifactValidators = [ checkTypeDefinitionHasType, checkPrimaryKey, checkManagedAssoc ];
107
-
107
+ // TODO: Does it make sense to run the on-condition check as part of a CSN validator?
108
108
  const commonQueryValidators = [ validateMixinOnCondition ];
109
109
 
110
110
  /**