@sap/cds-compiler 6.1.0 → 6.3.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 (90) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/bin/cdsc.js +17 -6
  3. package/bin/cdsse.js +1 -1
  4. package/bin/cdsv2m.js +1 -1
  5. package/lib/api/main.js +29 -7
  6. package/lib/api/options.js +1 -1
  7. package/lib/base/builtins.js +9 -0
  8. package/lib/base/keywords.js +1 -1
  9. package/lib/base/message-registry.js +41 -10
  10. package/lib/base/messages.js +13 -6
  11. package/lib/base/model.js +1 -1
  12. package/lib/base/optionProcessorHelper.js +7 -2
  13. package/lib/checks/assocOutsideService.js +17 -30
  14. package/lib/checks/checkForTypes.js +0 -18
  15. package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
  16. package/lib/checks/featureFlags.js +4 -1
  17. package/lib/checks/onConditions.js +2 -2
  18. package/lib/checks/queryNoDbArtifacts.js +16 -15
  19. package/lib/checks/types.js +1 -1
  20. package/lib/checks/utils.js +30 -6
  21. package/lib/checks/validator.js +4 -5
  22. package/lib/compiler/assert-consistency.js +3 -1
  23. package/lib/compiler/base.js +1 -1
  24. package/lib/compiler/builtins.js +1 -1
  25. package/lib/compiler/checks.js +85 -39
  26. package/lib/compiler/define.js +24 -5
  27. package/lib/compiler/extend.js +1 -1
  28. package/lib/compiler/finalize-parse-cdl.js +9 -1
  29. package/lib/compiler/generate.js +4 -4
  30. package/lib/compiler/index.js +88 -6
  31. package/lib/compiler/lsp-api.js +2 -0
  32. package/lib/compiler/populate.js +8 -8
  33. package/lib/compiler/propagator.js +1 -1
  34. package/lib/compiler/resolve.js +22 -21
  35. package/lib/compiler/shared.js +6 -6
  36. package/lib/compiler/tweak-assocs.js +53 -31
  37. package/lib/compiler/utils.js +9 -16
  38. package/lib/compiler/xpr-rewrite.js +2 -2
  39. package/lib/gen/BaseParser.js +35 -29
  40. package/lib/gen/CdlGrammar.checksum +1 -1
  41. package/lib/gen/CdlParser.js +1424 -1430
  42. package/lib/gen/Dictionary.json +1 -2
  43. package/lib/gen/cdlKeywords.json +26 -0
  44. package/lib/inspect/inspectPropagation.js +1 -1
  45. package/lib/json/from-csn.js +2 -2
  46. package/lib/json/to-csn.js +1 -1
  47. package/lib/language/multiLineStringParser.js +1 -1
  48. package/lib/model/cloneCsn.js +1 -0
  49. package/lib/model/csnRefs.js +9 -4
  50. package/lib/model/csnUtils.js +67 -2
  51. package/lib/optionProcessor.js +9 -9
  52. package/lib/parsers/AstBuildingParser.js +28 -26
  53. package/lib/parsers/identifiers.js +2 -30
  54. package/lib/render/toCdl.js +73 -13
  55. package/lib/render/toSql.js +127 -108
  56. package/lib/render/utils/common.js +4 -2
  57. package/lib/render/utils/sql.js +67 -0
  58. package/lib/transform/addTenantFields.js +4 -4
  59. package/lib/transform/db/assertUnique.js +2 -1
  60. package/lib/transform/db/associations.js +37 -1
  61. package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
  62. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  63. package/lib/transform/db/cdsPersistence.js +1 -1
  64. package/lib/transform/db/expansion.js +37 -36
  65. package/lib/transform/db/killAnnotations.js +1 -0
  66. package/lib/transform/db/processSqlServices.js +20 -2
  67. package/lib/transform/draft/db.js +20 -20
  68. package/lib/transform/draft/odata.js +38 -40
  69. package/lib/transform/effective/associations.js +1 -1
  70. package/lib/transform/effective/flattening.js +40 -47
  71. package/lib/transform/effective/main.js +6 -4
  72. package/lib/transform/forOdata.js +201 -92
  73. package/lib/transform/forRelationalDB.js +151 -142
  74. package/lib/transform/localized.js +116 -109
  75. package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
  76. package/lib/transform/odata/createForeignKeys.js +73 -70
  77. package/lib/transform/odata/flattening.js +216 -200
  78. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
  79. package/lib/transform/odata/toFinalBaseType.js +40 -39
  80. package/lib/transform/odata/typesExposure.js +151 -133
  81. package/lib/transform/odata/utils.js +7 -6
  82. package/lib/transform/parseExpr.js +165 -162
  83. package/lib/transform/transformUtils.js +184 -551
  84. package/lib/transform/translateAssocsToJoins.js +511 -596
  85. package/lib/transform/tupleExpansion.js +495 -0
  86. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  87. package/lib/utils/moduleResolve.js +1 -1
  88. package/package.json +2 -2
  89. package/lib/base/cleanSymbols.js +0 -17
  90. package/lib/checks/nonexpandableStructured.js +0 -39
package/CHANGELOG.md CHANGED
@@ -8,6 +8,69 @@ Note: `beta` fixes, changes and features are usually not listed in this ChangeLo
8
8
  but in [doc/CHANGELOG_BETA.md](doc/CHANGELOG_BETA.md).
9
9
  The compiler behavior concerning `beta` features can change at any time without notice.
10
10
 
11
+ ## Version 6.3.0 - 2025-08-28
12
+
13
+ ### Added
14
+
15
+ - compiler: Column casts can now use more modifiers such as `default` directly.
16
+ - for.odata/to.edm(x):
17
+ + New option `draftUserDescription` is now available. It adds the fields `CreatedByUserDescription`,
18
+ `LastChangedByUserDescription`, `InProcessByUserDescription` to the `DraftAdministrativeData` entity.
19
+ - to.sql:
20
+ + Structures with only one element can now be compared to scalar values.
21
+ This also applies to associations with only one foreign key.
22
+ + `cds.UInt8` can now be used in SQL dialects "h2" and "postgres".
23
+ + Managed associations can now be used in comparisons, e.g. `assoc = struct`.
24
+ + Structures and managed associations with only one element can be compared with scalars, e.g. `struct = 1`.
25
+ + In the draft use case, the `DRAFT.DraftAdministrativeData` entity now includes the following fields by default:
26
+ `CreatedByUserDescription`, `LastChangedByUserDescription`, `InProcessByUserDescription`, and `DraftMessages`.
27
+
28
+ ### Changed
29
+
30
+ - Update OData vocabularies: Common
31
+ - cdsc: EDMX output uses XML comments as service separators instead of `//`.
32
+ If there is only one service, no header is printed, allowing piping the output to a file.
33
+ - to.sql: path expressions which end in a foreign key are now always optimized to use the element of the source side.
34
+
35
+ ### Fixed
36
+
37
+ - compiler: Redirecting associations to non-query entities was fixed.
38
+ - to.sql/to.edm(x): References to associations can now be compared to other associations and structures.
39
+ - to.sql: Referencing a foreign key of an `@cds.persistence.skip` entity previously caused an
40
+ error in queries. Now the foreign key in the source entity is resolved and rendered.
41
+
42
+ ### Removed
43
+
44
+ - for.odata/to.edm(x): The `addAnnotationAddressViaNavigationPath` option has been removed. Its functionality is included in the `draftMessages` option.
45
+
46
+ ## Version 6.2.2 - 2025-07-28
47
+
48
+ ### Fixed
49
+
50
+ - compiler: `@extension.code` was accidentally restricted to non-expression values.
51
+
52
+ ## Version 6.2.0 - 2025-07-25
53
+
54
+ ### Added
55
+
56
+ - parser: CDL-casts in queries now support all type expressions, e.g. `field : many String not null`.
57
+ - compiler: Association paths in annotation expressions can now end with a filter, e.g. `@anno: (assoc[1=1])`.
58
+
59
+ ### Changed
60
+
61
+ - compiler: Annotation `@extension.code` is no longer propagated.
62
+ - Update OData vocabularies: Common
63
+ - The list of CDL keywords was updated for the latest CDL grammar.
64
+ - to.cdl: Foreign keys of managed associations are only rendered explicitly if
65
+ the compiler can't infer them when recompiled.
66
+ - cdsc: The command `parseCdl` was renamed to `parse`, since it also supports CSN input.
67
+
68
+ ### Fixed
69
+
70
+ - compiler:
71
+ + Calculated elements can now have a localized type.
72
+ + Associations in sub-queries of an `order by` of a `UNION` are now redirected.
73
+
11
74
  ## Version 6.1.0 - 2025-06-27
12
75
 
13
76
  ### Added
@@ -135,6 +198,21 @@ The compiler behavior concerning `beta` features can change at any time without
135
198
 
136
199
  - to.edm(x): Fixed crash for rare case if annotation expressions were used.
137
200
 
201
+ ## Version 5.9.8 - 2025-07-14
202
+
203
+ ### Fixed
204
+
205
+ - compiler: Calculated elements can now have a localized type
206
+
207
+ ## Version 5.9.6 - 2025-06-18
208
+
209
+ ### Fixed
210
+
211
+ - to.sql: Fix error when calculated element refers to a localized element.
212
+ - to.edm(x):
213
+ + Fix errors for service entities containing multiple path steps (e.g. `Service.Prefix.MyEntity`).
214
+ + Support enum references in annotation expressions that were resolved by the compiler.
215
+
138
216
  ## Version 5.9.4 - 2025-05-22
139
217
 
140
218
  ### Fixed
package/bin/cdsc.js CHANGED
@@ -151,7 +151,7 @@ function cdscMain() {
151
151
  cmdLine.options.attachValidNames = true;
152
152
 
153
153
  // Internally, parseCdl/parseOnly are options, so we map the command to it.
154
- if (cmdLine.command === 'parseCdl') {
154
+ if (cmdLine.command === 'parse') {
155
155
  cmdLine.command = 'toCsn';
156
156
  cmdLine.options.toCsn = cmdLine.options.parseCdl;
157
157
  cmdLine.options.parseCdl = true;
@@ -462,13 +462,15 @@ async function executeCommandLine( command, options, args ) {
462
462
  }
463
463
  else if (options.json) {
464
464
  const result = main.to.edm.all(csn, options);
465
+ const omitHeadline = Object.keys(result).length === 1;
465
466
  for (const serviceName in result)
466
- writeToFileOrDisplay(options.out, `${ serviceName }.json`, result[serviceName]);
467
+ writeToFileOrDisplay(options.out, `${ serviceName }.json`, result[serviceName], omitHeadline);
467
468
  }
468
469
  else {
469
470
  const result = main.to.edmx.all(csn, options);
471
+ const omitHeadline = Object.keys(result).length === 1;
470
472
  for (const serviceName in result)
471
- writeToFileOrDisplay(options.out, `${ serviceName }.xml`, result[serviceName]);
473
+ writeToFileOrDisplay(options.out, `${ serviceName }.xml`, result[serviceName], omitHeadline);
472
474
  }
473
475
  return model;
474
476
  }
@@ -704,10 +706,19 @@ async function executeCommandLine( command, options, args ) {
704
706
  return;
705
707
  if (!omitHeadline) {
706
708
  const sqlTypes = {
707
- sql: true, hdbconstraint: true, hdbtable: true, hdbview: true,
709
+ sql: true,
710
+ hdbconstraint: true,
711
+ hdbtable: true,
712
+ hdbview: true,
713
+ hdbprojectionview: true,
708
714
  };
709
- const commentStarter = fileName.split('.').pop() in sqlTypes ? '--$' : '//';
710
- process.stdout.write(`${ commentStarter } ------------------- ${ fileName } -------------------\n`);
715
+ if (fileName.endsWith('.xml')) {
716
+ process.stdout.write(`<!-- ------------------- ${ fileName.replaceAll('-->', '-- >') } ------------------- -->\n`);
717
+ }
718
+ else {
719
+ const commentStarter = fileName.split('.').pop() in sqlTypes ? '--$' : '//';
720
+ process.stdout.write(`${ commentStarter } ------------------- ${ fileName } -------------------\n`);
721
+ }
711
722
  }
712
723
 
713
724
  process.stdout.write(`${ content }\n`);
package/bin/cdsse.js CHANGED
@@ -10,7 +10,7 @@
10
10
  // capabilities supported at the moments is: complete, find, lint.
11
11
  // Syntax highlighting is supported by ./cdshi.js.
12
12
 
13
- /* eslint @stylistic/js/max-len: 0, no-console: 0 */
13
+ /* eslint @stylistic/max-len: 0, no-console: 0 */
14
14
 
15
15
  // @ts-nocheck
16
16
 
package/bin/cdsv2m.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /* eslint @stylistic/js/max-len: 0, no-console: 0 */
3
+ /* eslint @stylistic/max-len: 0, no-console: 0 */
4
4
 
5
5
  'use strict';
6
6
 
package/lib/api/main.js CHANGED
@@ -15,6 +15,7 @@ const generateDrafts = lazyload('../transform/draft/odata');
15
15
  const tenant = lazyload('../transform/addTenantFields');
16
16
  const toSql = lazyload('../render/toSql');
17
17
  const toCdl = require('../render/toCdl');
18
+ const sqlRenderUtils = lazyload('../render/utils/sql');
18
19
  const modelCompare = lazyload('../modelCompare/compare');
19
20
  const diffFilter = lazyload('../modelCompare/utils/filter');
20
21
  const sortViews = lazyload('../model/sortViews');
@@ -386,11 +387,13 @@ function hdi( csn, options, messageFunctions ) {
386
387
 
387
388
  objectUtils.forEach(flat, (key) => {
388
389
  const artifactNameLikeInCsn = key.replace(/\.[^/.]+$/, '');
389
- nameMapping[artifactNameLikeInCsn] = key;
390
- if (key.endsWith('.hdbtable') || key.endsWith('.hdbview'))
390
+ if (key.endsWith('.hdbtable') || key.endsWith('.hdbview') || key.endsWith('.hdbprojectionview')) {
391
+ nameMapping[artifactNameLikeInCsn] = key;
391
392
  sqlArtifactsWithCSNNamesToSort[artifactNameLikeInCsn] = flat[key];
392
- else
393
+ }
394
+ else {
393
395
  sqlArtifactsNotToSort[key] = flat[key];
396
+ }
394
397
  });
395
398
 
396
399
  const sorted = sortViews({ sql: sqlArtifactsWithCSNNamesToSort, csn: sqlCSN })
@@ -687,7 +690,7 @@ function hdiMigration( csn, options, messageFunctions, beforeImage ) {
687
690
  return {
688
691
  afterImage,
689
692
  definitions: createSqlDefinitions(hdbkinds, afterImage),
690
- deletions: createSqlDeletions(deletions, beforeImage),
693
+ deletions: createSqlDeletions(deletions, beforeImage, options),
691
694
  migrations: createSqlMigrations(migrations, afterImage),
692
695
  };
693
696
  }
@@ -719,11 +722,30 @@ function createSqlDefinitions( hdbkinds, afterImage ) {
719
722
  * @param {CSN.Model} beforeImage CSN used to create correct file names in result structure.
720
723
  * @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now
721
724
  */
722
- function createSqlDeletions( deletions, beforeImage ) {
725
+ function createSqlDeletions( deletions, beforeImage, options ) {
723
726
  const result = [];
724
- objectUtils.forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: beforeImage.definitions[name].query ? '.hdbview' : '.hdbtable' }));
727
+ objectUtils.forEach(deletions, name => result.push({
728
+ name: getFileName(name, beforeImage), suffix: getSuffix(beforeImage, beforeImage.definitions[name], options),
729
+ }));
725
730
  return result;
726
731
  }
732
+
733
+ /**
734
+ * Determines the appropriate file suffix based on the type of the provided artifact.
735
+ *
736
+ * @param {CSN.Artifact} artifact - The artifact object to evaluate.
737
+ * @returns {string} The file suffix corresponding to the artifact type:
738
+ * - '.hdbview' for query artifacts
739
+ * - '.hdbprojectionview' for projection artifacts
740
+ * - '.hdbtable' for other artifacts
741
+ */
742
+ function getSuffix( csn, artifact, options ) {
743
+ if (artifact.query || artifact.projection)
744
+ return sqlRenderUtils.isProjectionView(csn, artifact, options) ? '.hdbprojectionview' : '.hdbview';
745
+
746
+ return '.hdbtable';
747
+ }
748
+
727
749
  /**
728
750
  * From the given migrations, create the correct result structure.
729
751
  *
@@ -1382,7 +1404,7 @@ function handleTenantDiscriminator( options, internalOptions, messageFunctions )
1382
1404
  */
1383
1405
 
1384
1406
  /**
1385
- * A map of { <file.hdbtable|hdbview|hdbconstraint...>:<content> }.
1407
+ * A map of { <file.hdbtable|hdbview|hdbprojectionview|hdbconstraint...>:<content> }.
1386
1408
  *
1387
1409
  * @typedef {object} HDIArtifacts
1388
1410
  */
@@ -36,7 +36,6 @@ const publicOptionsNewAPI = [
36
36
  'booleanEquality',
37
37
  'dollarNowAsTimestamp',
38
38
  // ODATA
39
- 'addAnnotationAddressViaNavigationPath',
40
39
  'odataOpenapiHints',
41
40
  'edm4OpenAPI',
42
41
  'odataVersion',
@@ -50,6 +49,7 @@ const publicOptionsNewAPI = [
50
49
  'odataVocabularies',
51
50
  'odataNoCreator',
52
51
  'draftMessages',
52
+ 'draftUserDescription',
53
53
  'service',
54
54
  'serviceNames',
55
55
  // to.cdl
@@ -29,6 +29,14 @@ const propagationRules = {
29
29
  '@sql.append': 'never',
30
30
  '@sql.prepend': 'never',
31
31
  '@sql.replace': 'never',
32
+ '@extension.code': 'never', // for cds-oyster, not security relevant, but convenience
33
+ };
34
+ /**
35
+ * Of the annotations above, only these accept expressions as annotation values.
36
+ */
37
+ const acceptsExprValues = {
38
+ __proto__: null,
39
+ '@extension.code': true,
32
40
  };
33
41
 
34
42
  /**
@@ -101,6 +109,7 @@ function isAnnotationExpression( val ) {
101
109
 
102
110
  module.exports = {
103
111
  propagationRules,
112
+ acceptsExprValues,
104
113
  xprInAnnoProperties,
105
114
  isInReservedNamespace,
106
115
  isBuiltinType,
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- /* eslint @stylistic/js/no-multi-spaces: 0 */
3
+ /* eslint @stylistic/no-multi-spaces: 0 */
4
4
 
5
5
  const { functionsWithoutParentheses, cdlKeywords } = require('../parsers/identifiers');
6
6
 
@@ -29,9 +29,9 @@
29
29
  // "sloppy": with an upcoming _minor_ version of the compiler, the compilation
30
30
  // might lead to an error anyway or the compiled CSN might look different.
31
31
 
32
- /* eslint @stylistic/js/no-multi-spaces: 0 */
33
- /* eslint @stylistic/js/max-len: 0 */
34
- /* eslint @stylistic/js/key-spacing: 0 */
32
+ /* eslint @stylistic/no-multi-spaces: 0 */
33
+ /* eslint @stylistic/max-len: 0 */
34
+ /* eslint @stylistic/key-spacing: 0 */
35
35
 
36
36
  'use strict';
37
37
 
@@ -178,7 +178,7 @@ const centralMessages = {
178
178
  'service-nested-context': { severity: 'Error', configurableFor: true }, // does not hurt compile, TODO
179
179
  'service-nested-service': { severity: 'Error' }, // not supported yet; TODO: configurableFor:'test'?
180
180
 
181
- 'expr-unexpected-operator': { severity: 'Error', configurableFor: true },
181
+ 'expr-unexpected-operator': { severity: 'Error' },
182
182
 
183
183
  // Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues
184
184
  // Also used by other projects that rely on double-quotes for delimited identifiers.
@@ -196,7 +196,7 @@ const centralMessages = {
196
196
  'syntax-missing-as': { severity: 'Error', configurableFor: true },
197
197
  'syntax-missing-proj-semicolon': { severity: 'Warning' },
198
198
  'syntax-unexpected-after': { severity: 'Error' },
199
- 'syntax-unexpected-filter': { severity: 'Error', configurableFor: true },
199
+ 'syntax-unexpected-filter': { severity: 'Error', configurableFor: 'v7' },
200
200
  'syntax-unexpected-many-one': { severity: 'Error' },
201
201
  'syntax-deprecated-ref-virtual': { severity: 'Error' },
202
202
  'syntax-unexpected-reserved-word': { severity: 'Error', configurableFor: true },
@@ -786,8 +786,11 @@ const centralMessageTexts = {
786
786
  calc: 'Calculated elements can\'t use parameter references',
787
787
  },
788
788
  'ref-unexpected-structured': {
789
- std: 'Unexpected usage of structured type $(ELEMREF)',
790
- expr: 'Structured elements can\'t be used in expressions',
789
+ std: 'Unexpected usage of structured element $(ELEMREF)',
790
+ assoc: 'Unexpected usage of managed association $(ELEMREF)',
791
+ 'struct-expr': 'Structured element $(ELEMREF) can\'t be used in expressions with scalars; only possible for structures with one leaf-element',
792
+ 'assoc-expr': 'Associations $(ELEMREF) can\'t be used in expressions with scalars; only possible for association with one foreign key',
793
+ complexExpr: 'Unexpected reference to a structured element $(ELEMREF) in expression $(VALUE)',
791
794
  },
792
795
  'ref-unexpected-virtual': {
793
796
  std: 'Unexpected reference to virtual element $(NAME)', // "std" currently unused
@@ -801,8 +804,14 @@ const centralMessageTexts = {
801
804
  'with-filter': 'Unexpected reference to an association with filter',
802
805
  'self-with-filter': 'Unexpected column reference starting with $(ALIAS) to an association with filter',
803
806
  self: 'A reference to an unmanaged association is only valid when compared via $(CODE)',
807
+
804
808
  expr: 'Associations can\'t be used as values in expressions',
805
809
  'expr-comp': 'Compositions can\'t be used as values in expressions',
810
+ 'anno-expr': 'Associations can\'t be used in expressions for annotation values',
811
+ 'anno-expr-comp': 'Compositions can\'t be used in expressions for annotation values',
812
+ 'query-expr': 'Unmanaged associations can\'t be used in expressions in queries',
813
+ 'query-expr-comp': 'Unmanaged compositions can\'t be used in expressions in queries',
814
+
806
815
  'assoc-stored': 'Associations and compositions can\'t be used as values in stored calculated elements',
807
816
 
808
817
  'managed-filter': 'Unexpected managed association $(NAME) in filter expression of $(ID)',
@@ -831,6 +840,7 @@ const centralMessageTexts = {
831
840
  unmanagedleaf: 'Unexpected unmanaged association as final path step of $(ELEMREF) in an ON-condition',
832
841
  'calc-non-fk': 'Can\'t follow association $(ID) in path $(ELEMREF) in a stored calculated element; only foreign keys can be referred to, but not $(NAME)',
833
842
  'calc-unmanaged': 'Can\'t follow association $(ID) in path $(ELEMREF) in a stored calculated element',
843
+ 'calc-missing': 'Missing foreign key access for association $(ID) in path $(ELEMREF) in a stored calculated element',
834
844
  },
835
845
  'ref-unexpected-filter': {
836
846
  std: 'Unexpected filter in path $(ELEMREF)', // unused
@@ -983,10 +993,16 @@ const centralMessageTexts = {
983
993
  },
984
994
  'def-unexpected-key': {
985
995
  std: '$(ART) can\'t have additional keys',
986
- virtual: 'Unexpected $(PROP) for virtual element',
996
+ virtual: 'Unexpected $(KEYWORD) for virtual element',
987
997
  // TODO: Better message?
988
998
  include: '$(ART) can\'t have additional keys (through include)',
989
- invalidType: 'Unexpected $(PROP) for element of type $(TYPE)',
999
+ invalidType: 'Unexpected $(KEYWORD) for element of type $(TYPE)',
1000
+ },
1001
+ 'def-unsupported-key': {
1002
+ std: '$(KEYWORD) is not supported here', // unused variant
1003
+ kind: '$(KEYWORD) is only supported for elements in an entity or an aspect',
1004
+ sub: '$(KEYWORD) is only supported for top-level elements',
1005
+ type: '$(KEYWORD) is not supported for elements of type $(TYPE)',
990
1006
  },
991
1007
  'def-unexpected-localized': {
992
1008
  std: 'Unexpected $(KEYWORD)',
@@ -1046,6 +1062,9 @@ const centralMessageTexts = {
1046
1062
  'include-elements': 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
1047
1063
  'include-actions': 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
1048
1064
  },
1065
+ 'ref-invalid-assoc-navigation': {
1066
+ std: 'Invalid navigation along association $(ID) in path $(ELEMREF) to target $(NAME) having annotation $(ANNO)',
1067
+ },
1049
1068
  'ref-invalid-element': {
1050
1069
  std: 'Invalid element reference',
1051
1070
  $tableAlias: 'Can\'t refer to source elements of table alias $(ID)',
@@ -1219,11 +1238,13 @@ const centralMessageTexts = {
1219
1238
  'type-invalid-cast': {
1220
1239
  std: 'Can\'t cast to $(TYPE)',
1221
1240
  'to-structure': 'Can\'t cast to a structured type',
1241
+ 'to-inline-structure': 'Can\'t cast to a structure',
1222
1242
  'from-structure': 'Structured elements can\'t be cast to a different type',
1223
1243
  'expr-to-structure': 'Can\'t cast an expression to a structured type',
1224
1244
  'val-to-structure': 'Can\'t cast $(VALUE) to a structured type',
1225
1245
  'from-assoc': 'Invalid type cast on an association',
1226
1246
  assoc: 'Can\'t cast to an association',
1247
+ expand: 'Expands can\'t have a type cast', // TODO: Improve
1227
1248
  },
1228
1249
 
1229
1250
  // -----------------------------------------------------------------------------------
@@ -1252,7 +1273,17 @@ const centralMessageTexts = {
1252
1273
  publishingFilter: 'Can\'t publish managed association $(ID) with filter, as it must have at least one foreign key',
1253
1274
  },
1254
1275
 
1255
- // tenenat isolation via discriminator column:
1276
+ 'expr-invalid-expansion': {
1277
+ std: 'Path $(NAME) in expression $(VALUE) can\'t be expanded',
1278
+ 'path-mismatch': 'Missing sub path $(NAME) in $(ALIAS) for tuple expansion of $(VALUE); both sides must expand to the same sub paths',
1279
+ 'non-scalar': 'Path $(NAME) in expression $(VALUE) can\'t be expanded as it does not contain any scalar element',
1280
+ },
1281
+ 'expr-unsupported-expansion': {
1282
+ std: 'Unsupported $(ELEMREF) in structural expression $(VALUE)',
1283
+ scalarRef: 'Unsupported scalar reference $(ELEMREF) in structural expression $(VALUE)',
1284
+ },
1285
+
1286
+ // tenant isolation via discriminator column:
1256
1287
  'tenant-invalid-alias-name': {
1257
1288
  std: 'Can\'t have a table alias named $(NAME) in a tenant-dependent entity',
1258
1289
  implicit: 'Provide an explicit table alias name; do not use $(NAME)',
@@ -911,14 +911,20 @@ function transformElementRef( arg ) {
911
911
  if (!ref)
912
912
  return quoted( arg );
913
913
  // Can be used by CSN backends or compiler to create a simple path such as E:elem
914
- return quoted(
915
- ((arg.scope === 'param' || arg.param) ? ':' : '') +
914
+ return quoted( pathToMessageString( arg ) );
915
+ }
916
+
917
+ function pathToMessageString( arg ) {
918
+ const ref = arg?.ref || arg?.path || arg; // support CSN and XSN
919
+ if (!ref)
920
+ return null;
921
+
922
+ return ((arg.scope === 'param' || arg.param) ? ':' : '') +
916
923
  ref.map(
917
924
  item => (typeof item !== 'string'
918
925
  ? `${ item.id }${ item.args ? '(…)' : '' }${ item.where ? '[…]' : '' }`
919
926
  : item)
920
- ).join('.')
921
- );
927
+ ).join('.');
922
928
  }
923
929
 
924
930
  function transformArg( arg, r, args, texts ) {
@@ -1329,8 +1335,8 @@ function compareMessage( a, b ) {
1329
1335
  const aFile = a.$location && a.$location.file;
1330
1336
  const bFile = b.$location && b.$location.file;
1331
1337
  if (aFile && bFile) {
1332
- const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/js/max-len
1333
- const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/js/max-len
1338
+ const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/max-len
1339
+ const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/max-len
1334
1340
  return ( c( aFile, bFile ) ||
1335
1341
  c( a.$location.line, b.$location.line ) ||
1336
1342
  c( a.$location.col, b.$location.col ) ||
@@ -1958,4 +1964,5 @@ module.exports = {
1958
1964
  // for tests only
1959
1965
  constructSemanticLocationFromCsnPath,
1960
1966
  homeName,
1967
+ pathToMessageString,
1961
1968
  };
package/lib/base/model.js CHANGED
@@ -26,9 +26,9 @@ const availableBetaFlags = {
26
26
  calcAssoc: true,
27
27
  temporalRawProjection: true,
28
28
  v7preview: true,
29
- draftMessages: true,
30
29
  rewriteAnnotationExpressionsViaType: true,
31
30
  sqlServiceDummies: true,
31
+ projectionViews: true,
32
32
  // disabled by --beta-mode
33
33
  nestedServices: false,
34
34
  };
@@ -71,8 +71,9 @@ function createOptionProcessor() {
71
71
  /**
72
72
  * API: Define a command
73
73
  * @param {string} cmdString Command name, short and long form, e.g. 'S, toSql'
74
+ * @param {object} [cmdOptions] Optional options, e.g. `aliases`.
74
75
  */
75
- function command( cmdString ) {
76
+ function command( cmdString, cmdOptions = null ) {
76
77
  /** @type {object} */
77
78
  const cmd = {
78
79
  options: {},
@@ -83,6 +84,7 @@ function createOptionProcessor() {
83
84
  return cmd;
84
85
  },
85
86
  help: commandHelp,
87
+ aliases: cmdOptions?.aliases,
86
88
  ..._parseCommandString(cmdString),
87
89
  };
88
90
  if (optionProcessor.commands[cmd.longName])
@@ -96,6 +98,10 @@ function createOptionProcessor() {
96
98
 
97
99
  optionProcessor.commands[cmd.shortName] = cmd;
98
100
  }
101
+
102
+ for (const alias of cmdOptions?.aliases ?? [])
103
+ optionProcessor.commands[alias] = cmd;
104
+
99
105
  return cmd;
100
106
 
101
107
  // Command API: Define a command option
@@ -400,7 +406,6 @@ function createOptionProcessor() {
400
406
  else
401
407
  result.errors.push(errorMsg);
402
408
  }
403
-
404
409
  return result;
405
410
 
406
411
  /**
@@ -1,42 +1,29 @@
1
1
  'use strict';
2
2
 
3
+ // Only to be used with validator.js - a correct this value needs to be provided!
4
+ const { forEachMemberRecursively } = require('../model/csnUtils');
5
+
3
6
  /**
4
7
  * Asserts that there is no association usage outside of the specified service.
5
8
  * We do not check in type-ofs - we resolve them, so they are not a problem.
6
9
  *
7
- * @param {object} parent - The parent object in the CSN (Core Schema Notation).
8
- * @param {string} prop - The property name of the parent object.
9
- * @param {object} ref - The reference object.
10
- * @param {Array} path - The path array indicating the location in the CSN.
11
- * @param {object} grandparent - The grandparent object in the CSN.
12
- * @param {string} parentProp - The property name of the grandparent object.
10
+ * @param {CSN.Artifact} artifact Artifact to validate
11
+ * @param {string} artifactName Name of the artifact
13
12
  */
14
- function assertNoAssocUsageOutsideOfService( parent, prop, ref, path, grandparent, parentProp ) {
15
- const artifactName = path[1];
16
- if (parentProp === 'type')
17
- return;
18
-
19
- if (this.csn.definitions[this.options.effectiveServiceName]?.kind !== 'service' ||
20
- !artifactName.startsWith(`${ this.options.effectiveServiceName }.`))
21
- return;
22
-
23
- const { _links } = parent;
24
- // session variables can't have assoc steps, _links of 1 can't have assoc steps
25
- // TODO: (typeof parentProp === 'number' && path[path.length - 2] === 'on') - ignore on-conditions, as they are cut off anyway
26
- if (parent.$scope === '$magic' || _links?.length <= 1 )
13
+ function assertNoAssocUsageOutsideOfService( artifact, artifactName ) {
14
+ if (artifact.kind !== 'entity' || this.csn.definitions[this.options.effectiveServiceName]?.kind !== 'service' ||
15
+ !artifactName.startsWith(`${ this.options.effectiveServiceName }.`))
27
16
  return;
28
17
 
29
- for (let i = 0; i < _links.length - 1; i++) {
30
- const { art } = _links[i];
31
- if (art.target && !art.target.startsWith(`${ this.options.effectiveServiceName }.`)) {
32
- this.error('assoc-invalid-outside-service', path.concat('ref', i),
33
- { name: this.options.effectiveServiceName, id: ref[i].id || ref[i] },
34
- 'Association $(ID) pointing outside of service $(NAME) must not be used');
35
- return;
36
- }
18
+ if (artifact.kind === 'entity' && (artifact.query || artifact.projection)) {
19
+ forEachMemberRecursively(artifact, (element, elementName, prop, path) => {
20
+ if (element && element.target && !element.target.startsWith(`${ this.options.effectiveServiceName }.`)) {
21
+ this.error('assoc-invalid-outside-service', path,
22
+ { name: this.options.effectiveServiceName, id: elementName },
23
+ 'Association $(ID) pointing outside of service $(NAME) must not be published');
24
+ }
25
+ }, [ 'definitions', artifactName ], true, { elementsOnly: true });
37
26
  }
38
27
  }
39
28
 
40
- module.exports = {
41
- ref: assertNoAssocUsageOutsideOfService,
42
- };
29
+ module.exports = assertNoAssocUsageOutsideOfService;
@@ -18,22 +18,6 @@ function checkForHanaTypes( parent, name, type, path ) {
18
18
  }
19
19
  }
20
20
 
21
- /**
22
- * Check that `cds.UInt8` is not used - we don't have a clear idea how to represent it on postgres and h2
23
- *
24
- * @param {object} parent Object with a type
25
- * @param {string} name Name of the type property on parent
26
- * @param {Array} type type to check
27
- * @param {CSN.Path} path
28
- */
29
- function CheckForUInt8( parent, name, type, path ) {
30
- const artifact = this.csn.definitions[path[1]];
31
- if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && parent.type === 'cds.UInt8') {
32
- this.error('ref-unexpected-type', [ ...path, 'type' ], { type: 'cds.UInt8', value: this.options.sqlDialect },
33
- 'Type $(TYPE) can\'t be used with sqlDialect $(VALUE)');
34
- }
35
- }
36
-
37
21
  /**
38
22
  * Check types - specifically for postgres and h2
39
23
  *
@@ -44,8 +28,6 @@ function CheckForUInt8( parent, name, type, path ) {
44
28
  */
45
29
  function checkTypes( parent, name, type, path ) {
46
30
  checkForHanaTypes.bind(this)(parent, name, type, path);
47
- if (this.options.sqlDialect === 'postgres' || this.options.sqlDialect === 'h2')
48
- CheckForUInt8.bind(this)(parent, name, type, path);
49
31
  }
50
32
 
51
33
  module.exports = {
@@ -56,8 +56,9 @@ function _checkPathsInStoredCalcElement( parent, value, csnPath ) {
56
56
  else {
57
57
  // It's a managed association - access of the foreign keys is allowed
58
58
  requireForeignKeyAccess(parent, i, (errorIndex) => {
59
+ const variant = errorIndex >= value.length ? 'calc-missing' : 'calc-non-fk';
59
60
  this.error('ref-unexpected-navigation', csnPath, {
60
- '#': 'calc-non-fk', id, elemref: parent, name: value[errorIndex].id || value[errorIndex],
61
+ '#': variant, id, elemref: parent, name: value[errorIndex]?.id || value[errorIndex],
61
62
  });
62
63
  hasPathError = true;
63
64
  });
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { setProp } = require('../base/model');
4
4
  const { featureFlags } = require('../transform/featureFlags');
5
- const { isSqlService, isDummyService } = require('../transform/db/processSqlServices');
5
+ const { isSqlService, isDummyService, isDataProductService } = require('../transform/db/processSqlServices');
6
6
 
7
7
  /**
8
8
  *
@@ -32,5 +32,8 @@ module.exports = {
32
32
 
33
33
  if (isDummyService(artifact, this.options))
34
34
  setFeatureFlag( '$dummyService' ).call(this);
35
+
36
+ if (isDataProductService(artifact, this.options))
37
+ setFeatureFlag( '$dataProductService' ).call(this);
35
38
  },
36
39
  };
@@ -135,7 +135,7 @@ function validateOnCondition( member, memberName, property, path ) {
135
135
  ((type.target && type.keys || type.elements) && validStructuredElement ||
136
136
  (type.target && validDollarSelf)) && !type.virtual
137
137
  ) {
138
- // Do nothing - handled by lib/checks/nonexpandableStructured.js
138
+ // Do nothing - handled by tuple expansion
139
139
  }
140
140
  else if (type.items && !type.virtual) {
141
141
  this.error(null, onPath, { elemref: { ref } },
@@ -183,7 +183,7 @@ function requireForeignKeyAccess( parent, refIndex, noForeignKeyCallback ) {
183
183
  ref.splice(refIndex + 1, 1, ...resolved);
184
184
  }
185
185
 
186
- const next = pathId(ref[refIndex + 1]);
186
+ const next = ref[refIndex + 1] && pathId(ref[refIndex + 1]);
187
187
  let possibleKeys = next && assoc.keys.filter(r => r.ref[0] === next);
188
188
  if (!possibleKeys || possibleKeys.length === 0) {
189
189
  noForeignKeyCallback(refIndex + 1);