@sap/cds-compiler 6.4.6 → 6.5.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 (65) hide show
  1. package/CHANGELOG.md +34 -1156
  2. package/README.md +1 -10
  3. package/doc/IncompatibleChanges_v5.md +436 -0
  4. package/doc/IncompatibleChanges_v6.md +659 -0
  5. package/doc/Versioning.md +3 -7
  6. package/lib/api/main.js +1 -0
  7. package/lib/api/options.js +5 -0
  8. package/lib/api/validate.js +3 -0
  9. package/lib/base/message-registry.js +23 -0
  10. package/lib/base/messages.js +1 -1
  11. package/lib/base/model.js +3 -2
  12. package/lib/checks/actionsFunctions.js +6 -3
  13. package/lib/checks/existsInForbiddenPlaces.js +32 -0
  14. package/lib/checks/validator.js +2 -0
  15. package/lib/compiler/assert-consistency.js +3 -5
  16. package/lib/compiler/checks.js +4 -8
  17. package/lib/compiler/define.js +244 -459
  18. package/lib/compiler/extend.js +297 -11
  19. package/lib/compiler/finalize-parse-cdl.js +2 -10
  20. package/lib/compiler/generate.js +29 -1
  21. package/lib/compiler/populate.js +21 -63
  22. package/lib/compiler/propagator.js +1 -2
  23. package/lib/compiler/resolve.js +2 -12
  24. package/lib/compiler/shared.js +18 -5
  25. package/lib/compiler/tweak-assocs.js +13 -9
  26. package/lib/compiler/utils.js +97 -0
  27. package/lib/compiler/xpr-rewrite.js +2 -1
  28. package/lib/edm/annotations/edmJson.js +9 -6
  29. package/lib/edm/annotations/genericTranslation.js +8 -4
  30. package/lib/edm/csn2edm.js +3 -4
  31. package/lib/edm/edmInboundChecks.js +1 -2
  32. package/lib/edm/edmPreprocessor.js +3 -3
  33. package/lib/gen/CdlGrammar.checksum +1 -1
  34. package/lib/gen/CdlParser.js +1 -1
  35. package/lib/gen/Dictionary.json +16 -1
  36. package/lib/json/from-csn.js +4 -6
  37. package/lib/json/to-csn.js +3 -3
  38. package/lib/model/csnRefs.js +13 -4
  39. package/lib/model/enrichCsn.js +4 -2
  40. package/lib/optionProcessor.js +8 -4
  41. package/lib/render/utils/sql.js +3 -2
  42. package/lib/transform/db/applyTransformations.js +1 -1
  43. package/lib/transform/db/assertUnique.js +3 -3
  44. package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
  45. package/lib/transform/db/assocsToQueries/transformExists.js +14 -3
  46. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  47. package/lib/transform/db/backlinks.js +4 -4
  48. package/lib/transform/db/cdsPersistence.js +4 -4
  49. package/lib/transform/db/constraints.js +4 -4
  50. package/lib/transform/db/expansion.js +5 -5
  51. package/lib/transform/db/flattening.js +4 -5
  52. package/lib/transform/db/rewriteCalculatedElements.js +3 -3
  53. package/lib/transform/db/temporal.js +11 -11
  54. package/lib/transform/draft/db.js +2 -0
  55. package/lib/transform/draft/odata.js +5 -7
  56. package/lib/transform/effective/flattening.js +1 -2
  57. package/lib/transform/forOdata.js +3 -3
  58. package/lib/transform/forRelationalDB.js +1 -1
  59. package/lib/transform/odata/createForeignKeys.js +1 -2
  60. package/lib/transform/odata/flattening.js +1 -2
  61. package/lib/transform/odata/toFinalBaseType.js +52 -55
  62. package/lib/transform/transformUtils.js +3 -4
  63. package/package.json +1 -1
  64. package/doc/CHANGELOG_BETA.md +0 -464
  65. package/doc/CHANGELOG_DEPRECATED.md +0 -235
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ // The options are specified in ../optionProcessor.js (and some other files).
4
+ // Some backends feel the need to "translate" option, which require to list
5
+ // all options also here (as “public" or "private" option).
6
+
3
7
  const { validate, generateStringValidator } = require('./validate');
4
8
  const { makeMessageFunction } = require('../base/messages');
5
9
 
@@ -20,6 +24,7 @@ const publicOptionsNewAPI = [
20
24
  'defaultBinaryLength',
21
25
  'defaultStringLength',
22
26
  'csnFlavor',
27
+ 'noDollarCalc',
23
28
  // DB
24
29
  'sqlDialect',
25
30
  'sqlMapping',
@@ -1,5 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ // The options are specified in ../optionProcessor.js (and some other files).
4
+ // Options with non-boolean values must also be listed in this file.
5
+
3
6
  const { forEach } = require('../utils/objectUtils');
4
7
 
5
8
  /* eslint-disable arrow-body-style */
@@ -92,9 +92,16 @@ const centralMessages = {
92
92
  'ext-undefined-action': { severity: 'Warning' },
93
93
  'ext-undefined-art': { severity: 'Warning' }, // for annotate statement (for CDL path root)
94
94
  'ext-undefined-def': { severity: 'Warning' }, // for annotate statement (for CSN or CDL path cont)
95
+ 'ext-undefined-art-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
96
+ 'ext-undefined-def-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
95
97
  'ext-undefined-element': { severity: 'Warning' },
96
98
  'ext-undefined-key': { severity: 'Warning' },
97
99
  'ext-undefined-param': { severity: 'Warning' },
100
+ 'ext-undefined-element-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
101
+ 'ext-undefined-action-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
102
+ 'ext-undefined-param-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
103
+ 'ext-unexpected-returns': { severity: 'Warning' },
104
+ 'ext-unexpected-returns-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
98
105
  'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
99
106
  'anno-unexpected-localized-skip': { severity: 'Error', configurableFor: true },
100
107
 
@@ -948,6 +955,8 @@ const centralMessageTexts = {
948
955
  'anno-builtin': 'Builtin types should not be annotated nor extended. Use custom type instead',
949
956
  'ext-undefined-def': 'Artifact $(ART) has not been found',
950
957
  'ext-undefined-art': 'No artifact has been found with name $(ART)',
958
+ 'ext-undefined-def-sec': 'Artifact $(ART) has not been found',
959
+ 'ext-undefined-art-sec': 'No artifact has been found with name $(ART)',
951
960
  'ext-undefined-element': {
952
961
  std: 'Element $(NAME) has not been found',
953
962
  element: 'Artifact $(ART) has no element $(NAME)',
@@ -955,15 +964,24 @@ const centralMessageTexts = {
955
964
  returns: 'Return value of $(ART) has no element $(NAME)',
956
965
  'enum-returns': 'Return value of $(ART) has no enum $(NAME)',
957
966
  },
967
+ 'ext-undefined-element-sec': 'Element $(NAME) has not been found',
958
968
  'ext-undefined-key': 'Foreign key $(NAME) has not been found',
959
969
  'ext-undefined-action': {
960
970
  std: 'Action $(ART) has not been found',
961
971
  action: 'Artifact $(ART) has no action $(NAME)',
962
972
  },
973
+ 'ext-undefined-action-sec': {
974
+ std: 'Action $(ART) has not been found',
975
+ action: 'Artifact $(ART) has no action $(NAME)',
976
+ },
963
977
  'ext-undefined-param': {
964
978
  std: 'Parameter $(ART) has not been found',
965
979
  param: 'Artifact $(ART) has no parameter $(NAME)',
966
980
  },
981
+ 'ext-undefined-param-sec': {
982
+ std: 'Parameter $(ART) has not been found',
983
+ param: 'Artifact $(ART) has no parameter $(NAME)',
984
+ },
967
985
 
968
986
  // annotation checks against their definition
969
987
  'anno-expecting-value': {
@@ -1157,6 +1175,11 @@ const centralMessageTexts = {
1157
1175
  redirected: 'Target $(TARGET) of $(NAME) is missing element $(ID); add an ON-condition to $(KEYWORD)',
1158
1176
  },
1159
1177
  'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode', // eslint-disable-line cds-compiler/message-no-quotes
1178
+ 'query-unexpected-exists': {
1179
+ std: 'Unexpected $(PROP) predicate',
1180
+ groupBy: 'Unexpected $(PROP) predicate in GROUP BY clause',
1181
+ orderBy: 'Unexpected $(PROP) predicate in ORDER BY clause',
1182
+ },
1160
1183
  'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode', // eslint-disable-line cds-compiler/message-no-quotes
1161
1184
  'query-ignoring-param-nullability': {
1162
1185
  std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
@@ -1724,7 +1724,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1724
1724
  else if (step === 'targetAspect') {
1725
1725
  // skip
1726
1726
  }
1727
- else if (step === 'xpr' || step === 'list' || step === 'default' || step === 'ref' || step === 'as' || step === 'value') {
1727
+ else if (step === 'xpr' || step === 'list' || step === 'default' || step === 'ref' || step === 'as' || step === 'value' || step === '$calc') {
1728
1728
  break; // don't go into xprs, refs, aliases, values, etc.
1729
1729
  }
1730
1730
  else if (step === 'returns') {
package/lib/base/model.js CHANGED
@@ -7,6 +7,9 @@
7
7
 
8
8
  const { forEach } = require('../utils/objectUtils');
9
9
 
10
+ // Normal options are in ../optionProcessor.js (and some other files),
11
+ // unfortunately partly non-grep-able (option `fooBar` is defined via `--foo-bar`)
12
+
10
13
  /**
11
14
  * Object of all available beta flags that will be enabled/disabled by `--beta-mode`
12
15
  * through cdsc. Only intended for INTERNAL USE.
@@ -29,8 +32,6 @@ const availableBetaFlags = {
29
32
  rewriteAnnotationExpressionsViaType: true,
30
33
  sqlServiceDummies: true,
31
34
  projectionViews: true,
32
- draftAdminDataHiddenFilter: true,
33
- $calcForDraft: true,
34
35
  // disabled by --beta-mode
35
36
  nestedServices: false,
36
37
  };
@@ -25,8 +25,9 @@ function checkActionOrFunction( art, artName, prop, path ) {
25
25
  for (const [ actName, act ] of Object.entries(art.actions)) {
26
26
  if (act.params) {
27
27
  checkExplicitBindingParameter.call(this, act.params, path.concat([ 'actions', actName, 'params' ]));
28
- for (const [ paramName, param ] of Object.entries(act.params))
29
- checkActionOrFunctionParameter.call(this, param, path.concat([ 'actions', actName, 'params', paramName ]), act.kind);
28
+ const params = Object.entries(act.params);
29
+ for (const [ paramName, param ] of params)
30
+ checkActionOrFunctionParameter.call(this, param, path.concat([ 'actions', actName, 'params', paramName ]), act.kind, params);
30
31
  }
31
32
  if (act.returns)
32
33
  checkReturns.bind(this)(act.returns, path.concat([ 'actions', actName, 'returns' ]), act.kind);
@@ -63,7 +64,9 @@ function checkActionOrFunction( art, artName, prop, path ) {
63
64
  * @param {CSN.Path} currPath path to the parameter
64
65
  * @param {string} actKind 'action' or 'function'
65
66
  */
66
- function checkActionOrFunctionParameter( param, currPath, actKind ) {
67
+ function checkActionOrFunctionParameter( param, currPath, actKind, params ) {
68
+ if ((param.items || param)?.type === '$self' && param === params?.[0][1])
69
+ return; // binding parameter
67
70
  const paramType = param.type ? this.csnUtils.getFinalTypeInfo(param.type) : param;
68
71
  if (!paramType)
69
72
  return; // no type could be resolved
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const { applyTransformationsOnNonDictionary } = require('../model/csnUtils');
4
+
5
+ /**
6
+ * Checks a query for forbidden usage of the `exists` predicate within `groupBy` and `orderBy`.
7
+ *
8
+ * @param {object} query
9
+ * @param {string} prop - either groupBy or orderBy.
10
+ * @param {object[]} _tokenStream the value of query[prop]
11
+ * @param {object|Array} pathToClause - Path to respective groupBy/orderBy clause in the CSN.
12
+ * @this {object} The calling context must provide an `error` function.
13
+ */
14
+ function existsInForbiddenPlaces( query, prop, _tokenStream, pathToClause ) {
15
+ applyTransformationsOnNonDictionary(query, prop, {
16
+ ref: (_parent, _prop, _ref, pathToError, tokenStream, index) => {
17
+ if (tokenStream[index - 1] === 'exists') {
18
+ // the path should point to the exists
19
+ const pathToExists = pathToError.with(-1, index - 1);
20
+ this.error('query-unexpected-exists', pathToExists, {
21
+ '#': prop,
22
+ prop: 'exists',
23
+ });
24
+ }
25
+ },
26
+ }, null, pathToClause);
27
+ }
28
+
29
+ module.exports = {
30
+ groupBy: existsInForbiddenPlaces,
31
+ orderBy: existsInForbiddenPlaces,
32
+ };
@@ -44,6 +44,7 @@ const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
44
44
  const checkExplicitlyNullableKeys = require('./nullableKeys');
45
45
  const existsMustEndInAssoc = require('./existsMustEndInAssoc');
46
46
  const existsMustNotStartWithDollarSelf = require('./existsMustNotStartWithDollarSelf');
47
+ const existsInForbiddenPlaces = require('./existsInForbiddenPlaces');
47
48
  const assertFilterOfExists = require('./existsExpressionsOnlyForeignKeys');
48
49
  const checkPathsInStoredCalcElement = require('./checkPathsInStoredCalcElement');
49
50
  const managedWithoutKeys = require('./managedWithoutKeys');
@@ -85,6 +86,7 @@ const forRelationalDBArtifactValidators = [
85
86
 
86
87
  const forRelationalDBCsnValidators = [
87
88
  checkCdsMap,
89
+ existsInForbiddenPlaces,
88
90
  existsMustEndInAssoc,
89
91
  existsMustNotStartWithDollarSelf,
90
92
  assertFilterOfExists,
@@ -316,7 +316,7 @@ function assertConsistency( model, stage ) {
316
316
  requires: [ 'location', 'path' ],
317
317
  optional: [
318
318
  'kind', 'name', '$syntax', '_block', '_parent', '_main',
319
- 'elements', '_origin', '_joinParent', '$joinArgsIndex', '$syntax',
319
+ 'elements', '_origin', '_joinParent', '$joinArgsIndex',
320
320
  '$parens', '_status', // TODO: only in from
321
321
  'scope', '_artifact', '_originalArtifact', '$inferred', 'kind',
322
322
  '_effectiveType', '$effectiveSeqNo', // TODO:check this
@@ -449,7 +449,7 @@ function assertConsistency( model, stage ) {
449
449
  requires: [ 'location', 'path' ],
450
450
  optional: [
451
451
  'scope', 'variant', '_artifact', '_originalArtifact',
452
- '$inferred', '$parens', 'sort', 'nulls',
452
+ '$inferred', '$parens', 'sort', 'nulls', '$syntax',
453
453
  ],
454
454
  },
455
455
  none: { optional: () => true }, // parse error
@@ -507,7 +507,7 @@ function assertConsistency( model, stage ) {
507
507
  args: {
508
508
  inherits: 'value',
509
509
  optional: [
510
- 'name', '$duplicate', '$expected', 'args', 'suffix', '$parens',
510
+ 'name', '$duplicate', 'args', 'suffix', '$parens',
511
511
  'param', 'scope', // for dynamic parameter '?'
512
512
  ],
513
513
  test: args,
@@ -724,7 +724,6 @@ function assertConsistency( model, stage ) {
724
724
  'localized-entity', // `.texts` entity
725
725
  'localized-origin', // `.texts` entity
726
726
  'nav', // only used for MASKED, TODO(v6): Remove
727
- 'none', // only used in ensureColumnName(): Used in object representing empty alias
728
727
  'query', // inferred query properties, e.g. `key`
729
728
  'rewrite', // on-conditions or FKeys are rewritten
730
729
  'parent-origin', // annotation/property copied from parent that does not come through
@@ -747,7 +746,6 @@ function assertConsistency( model, stage ) {
747
746
  $withLocalized: { test: isBoolean },
748
747
  $sources: { parser: true, test: isArray( isString ) },
749
748
  tokenStream: { parser: true, test: TODO },
750
- $expected: { parser: true, test: isOneOf( [ 'approved-exists', 'exists' ] ) },
751
749
  $messageFunctions: { test: TODO },
752
750
  $functions: { test: TODO },
753
751
  $assert: { test: TODO }, // currently just for missing Error[ref-cycle]
@@ -941,14 +941,8 @@ function check( model ) {
941
941
  */
942
942
  function checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail ) {
943
943
  // Arg must not be an association and not $self
944
- // Only if path is not approved exists path (that is non-query position)
945
- if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
946
- if (arg.$expected === 'exists') {
947
- const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
948
- error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
949
- }
950
- }
951
- else if (rejectAssocTail && isAssociationOperand( arg )) {
944
+ // Only if path is not after an `exists`
945
+ if (arg.$syntax !== 'after-exists' && rejectAssocTail && isAssociationOperand( arg )) {
952
946
  if (rejectAssocTail.rejectManaged && rejectAssocTail.rejectUnmanaged ||
953
947
  rejectAssocTail.rejectManaged && arg._artifact.keys ||
954
948
  rejectAssocTail.rejectUnmanaged && arg._artifact.on) {
@@ -956,6 +950,8 @@ function check( model ) {
956
950
  const context = rejectAssocTail.context === 'query' && 'query-' ||
957
951
  rejectAssocTail.context === 'anno' && 'anno-' ||
958
952
  '';
953
+ // Hm, in other message context about associations or compositions, we do
954
+ // not have separate text variants for association or composition…
959
955
  const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
960
956
  error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': `${ context }${ variant }` } );
961
957
  }