@sap/cds-compiler 3.8.0 → 3.9.2

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 (79) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +26 -5
  4. package/lib/api/.eslintrc.json +3 -2
  5. package/lib/api/options.js +3 -1
  6. package/lib/api/validate.js +1 -1
  7. package/lib/base/message-registry.js +27 -18
  8. package/lib/base/messages.js +6 -1
  9. package/lib/base/model.js +2 -2
  10. package/lib/checks/.eslintrc.json +1 -0
  11. package/lib/checks/actionsFunctions.js +6 -6
  12. package/lib/checks/annotationsOData.js +1 -1
  13. package/lib/checks/elements.js +28 -17
  14. package/lib/checks/foreignKeys.js +1 -1
  15. package/lib/checks/invalidTarget.js +1 -1
  16. package/lib/checks/onConditions.js +11 -6
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/types.js +1 -1
  19. package/lib/checks/utils.js +1 -1
  20. package/lib/checks/validator.js +3 -2
  21. package/lib/compiler/assert-consistency.js +8 -3
  22. package/lib/compiler/base.js +19 -13
  23. package/lib/compiler/builtins.js +7 -0
  24. package/lib/compiler/checks.js +73 -6
  25. package/lib/compiler/define.js +10 -5
  26. package/lib/compiler/extend.js +924 -1709
  27. package/lib/compiler/finalize-parse-cdl.js +1 -1
  28. package/lib/compiler/generate.js +838 -0
  29. package/lib/compiler/index.js +2 -0
  30. package/lib/compiler/populate.js +2 -2
  31. package/lib/compiler/propagator.js +20 -8
  32. package/lib/compiler/resolve.js +3 -3
  33. package/lib/compiler/shared.js +11 -6
  34. package/lib/edm/annotations/genericTranslation.js +6 -6
  35. package/lib/edm/csn2edm.js +1 -1
  36. package/lib/edm/edm.js +25 -11
  37. package/lib/edm/edmPreprocessor.js +47 -23
  38. package/lib/edm/edmUtils.js +37 -9
  39. package/lib/gen/Dictionary.json +5 -7
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +3 -1
  42. package/lib/gen/language.tokens +24 -23
  43. package/lib/gen/languageLexer.interp +4 -1
  44. package/lib/gen/languageLexer.js +792 -784
  45. package/lib/gen/languageLexer.tokens +12 -11
  46. package/lib/gen/languageParser.js +3944 -3865
  47. package/lib/json/from-csn.js +27 -6
  48. package/lib/json/to-csn.js +10 -6
  49. package/lib/language/antlrParser.js +11 -3
  50. package/lib/language/genericAntlrParser.js +4 -2
  51. package/lib/language/language.g4 +32 -24
  52. package/lib/model/csnRefs.js +15 -7
  53. package/lib/model/csnUtils.js +41 -76
  54. package/lib/modelCompare/utils/.eslintrc.json +1 -1
  55. package/lib/optionProcessor.js +7 -4
  56. package/lib/render/.eslintrc.json +1 -1
  57. package/lib/render/toCdl.js +244 -168
  58. package/lib/render/toHdbcds.js +18 -10
  59. package/lib/render/toSql.js +24 -2
  60. package/lib/transform/db/.eslintrc.json +4 -3
  61. package/lib/transform/db/cdsPersistence.js +1 -1
  62. package/lib/transform/db/expansion.js +11 -6
  63. package/lib/transform/db/flattening.js +22 -15
  64. package/lib/transform/db/rewriteCalculatedElements.js +50 -29
  65. package/lib/transform/db/temporal.js +1 -1
  66. package/lib/transform/db/views.js +1 -1
  67. package/lib/transform/draft/db.js +1 -1
  68. package/lib/transform/draft/odata.js +3 -4
  69. package/lib/transform/forOdataNew.js +5 -6
  70. package/lib/transform/forRelationalDB.js +7 -7
  71. package/lib/transform/odata/toFinalBaseType.js +6 -6
  72. package/lib/transform/odata/typesExposure.js +12 -3
  73. package/lib/transform/odata/utils.js +3 -0
  74. package/lib/transform/transformUtilsNew.js +11 -26
  75. package/lib/transform/translateAssocsToJoins.js +9 -9
  76. package/lib/transform/universalCsn/.eslintrc.json +3 -2
  77. package/lib/transform/universalCsn/coreComputed.js +1 -1
  78. package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
  79. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,67 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 3.9.2 - 2023-04-27
11
+
12
+ ### Fixed
13
+
14
+ - Fix crash in core compiler which occurred when CAP was used in a node environments
15
+ where an enumerable property was added to `Array.prototype`.
16
+ - to.edm(x):
17
+ + Publicly release `@open`.
18
+ + No `DefaultValue` for `Edm.NavigationProperty`.
19
+
20
+ ## Version 3.9.0 - 2023-04-20
21
+
22
+ ### Added
23
+
24
+ - Variables `$valid.from` and `$valid.to` have been added to the compiler.
25
+ They behave the same as `$at.from` and `$at.to`.
26
+ - to.edm(x):
27
+ + Add `--odata-vocabularies` to pass a dictionary `{ <prefix>: { Alias, Namespace, Uri } }`
28
+ into the EDM generation. `<prefix>` must match the value of `Alias`. Entries are ignored
29
+ if they are incomplete, malformed, redefine an official OASIS/SAP vocabulary or match the name
30
+ of the current service. Annotations of the form `@<prefix>.<annotation>` are added to the API
31
+ without evaluation including an `edm:Reference`. It is in the users responsibility to provide
32
+ a URI that a client can resolve against a valid vocabulary document.
33
+ + Support annotation `@open` on entity and structured type level to declare the corresponding
34
+ entity/complex type to be `OpenType=true`. If an open structured type is declared closed
35
+ (with a falsy annotation value), the corresponding EDM type is closed as well and suffixed
36
+ with `_closed` (or `_open` vice versa).
37
+ No further checks are performed on possibly open foreign or primary key types nor on eventually
38
+ bucket elements to store the additional data.
39
+
40
+ ### Changed
41
+
42
+ - compiler: Parameter references in filters such as `assoc[field < :Param]` are now allowed.
43
+ - In the compiled CSN, sort the non-enumerable `$sources` property
44
+ according to the reversed layered extension order.
45
+ - Update OData vocabulary 'Common', 'ODM', 'UI'.
46
+ - to.cdl: If an identifier contains illegal characters (e.g. newline), we no longer produces
47
+ invalid CDL, but emit an error instead.
48
+
49
+ ### Fixed
50
+
51
+ - to.edm(x):
52
+ + Fix spec requirement: "Navigation properties of complex types MUST NOT specify a partner".
53
+ + Set default target cardinality for unspecified `composition of {}` to `[0..1]`.
54
+ + Correct referential constraint calculation for `[0..1]` backlink associations.
55
+ - for.hana/for.odata: Reject final unmanaged assoc path step in ON Condition if preceded with `$self`.
56
+ - to.cdl: Parentheses around expressions containing conditions were sometimes missing.
57
+ - to.sql/hdi/hdbcds:
58
+ + Detect and process calculated elements in functions like `upper`.
59
+ + Better detection of calculated elements in `.expand`/`.inline`.
60
+ + Entities with calculated elements sometimes had incorrect types. This happened, for example,
61
+ if they were marked with `@odata.draft.enabled`
62
+
63
+ ## Version 3.8.2 - 2023-03-30
64
+
65
+ ### Fixed
66
+
67
+ - parser: Identifiers that are keywords were not allowed in annotation values inside arrays
68
+ - compiler: Compatibility against cds-lsp was restored.
69
+ - to.sql/hdbcds/hdi/edm(x): Fix a crash for sub-queries inside nested expressions of on-conditions of JOINs.
70
+
10
71
  ## Version 3.8.0 - 2023-03-27
11
72
 
12
73
  ### Added
package/bin/cdsc.js CHANGED
@@ -353,8 +353,8 @@ function executeCommandLine( command, options, args ) {
353
353
  options.odataFormat = 'structured';
354
354
  options.odataContainment = true;
355
355
  }
356
- if (options.odataVocRefs && typeof options.odataVocRefs === 'string')
357
- options.odataVocRefs = JSON.parse(options.odataVocRefs);
356
+ if (options.odataVocabularies && typeof options.odataVocabularies === 'string')
357
+ options.odataVocabularies = JSON.parse(options.odataVocabularies);
358
358
 
359
359
  const csn = options.directBackend ? model : compactModel(model, options);
360
360
  if (options.csn) {
@@ -8,6 +8,26 @@ Note: `beta` fixes, changes and features are listed in this ChangeLog just for i
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
  **Don't use `beta` fixes, changes and features in productive mode.**
10
10
 
11
+ ## Version 3.9.2 - 2023-04-27
12
+
13
+ ### Removed `odataOpenType`
14
+
15
+ This feature is now set to production mode.
16
+
17
+ ## Version 3.9.0 - 2023-04-XX
18
+
19
+ ### Added `calculatedElementsOnWrite`
20
+
21
+ Allows to define calculated elements "on-write" in entities and aspects.
22
+ For example:
23
+
24
+ ```cds
25
+ entity E { one: Integer; two = one + 1 stored; };
26
+ ```
27
+
28
+ When a row is added to `E`, column `two` will be set automatically
29
+ in databases supporting generated columns.
30
+
11
31
  ## Version 3.8.0 - 2023-03-27
12
32
 
13
33
  ### Added `v4preview`
@@ -100,11 +120,12 @@ allow lossless, easy to revert actions like adding a column or extending a strin
100
120
 
101
121
  ### Added `odataOpenType`
102
122
 
103
- - to.edm(x): Support annotation `@open` on entity and structured type level to declare the corresponding entity/complex type to
104
- be `OpenType=true`. If an open structured type is declared closed (with a falsy annotation value), the corresponding EDM type
105
- is closed as well and suffixed with `_closed` (or `_open` vice versa).
106
- No further checks are performed on possibly open foreign or primary key types nor on eventually bucket elements to store the
107
- additional data.
123
+ - to.edm(x): Support annotation `@open` on entity and structured type level to declare the
124
+ corresponding entity/complex type to be `OpenType=true`. If an open structured type is declared
125
+ closed (with a falsy annotation value), the corresponding EDM type is closed as well and suffixed
126
+ with `_closed` (or `_open` vice versa).
127
+ No further checks are performed on possibly open foreign or primary key types nor on eventually
128
+ bucket elements to store the additional data.
108
129
 
109
130
  ## Version 3.0.0 - 2022-06-23
110
131
 
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "root": true,
3
3
  "env": {
4
- "es2020": true,
4
+ "es2022": true,
5
5
  "node": true
6
6
  },
7
7
  // we actually do not extend airbnb-base, as it weakens some eslint:recommended rules
8
8
  "extends": ["../../.eslintrc-ydkjsi.json", "plugin:jsdoc/recommended"],
9
9
  "parserOptions": {
10
- "ecmaVersion": 2020,
10
+ "ecmaVersion": 2022,
11
11
  "sourceType": "script"
12
12
  },
13
13
  "plugins": [
@@ -18,6 +18,7 @@
18
18
  "jsdoc/no-undefined-types": "off",
19
19
  // eslint-plugin-jsdoc warning
20
20
  "jsdoc/require-property": 0,
21
+ "jsdoc/tag-lines": "off",
21
22
  // =airbnb, >eslint:
22
23
  "max-len": [ "error", {
23
24
  "code": 100,
@@ -29,7 +29,9 @@ const publicOptionsNewAPI = [
29
29
  'variableReplacements',
30
30
  'pre2134ReferentialConstraintNames',
31
31
  'generatedByComment',
32
+ 'betterSqliteSessionVariables',
32
33
  // ODATA
34
+ 'odataOpenapiHints',
33
35
  'odataVersion',
34
36
  'odataFormat',
35
37
  'odataContainment',
@@ -38,7 +40,7 @@ const publicOptionsNewAPI = [
38
40
  'odataProxies',
39
41
  'odataXServiceRefs',
40
42
  'odataV2PartialConstr',
41
- 'odataVocRefs',
43
+ 'odataVocabularies',
42
44
  'service',
43
45
  'serviceNames',
44
46
  //
@@ -82,7 +82,7 @@ const validators = {
82
82
  sqlMapping: generateStringValidator([ 'plain', 'quoted', 'hdbcds' ]),
83
83
  odataVersion: generateStringValidator([ 'v2', 'v4' ]),
84
84
  odataFormat: generateStringValidator([ 'flat', 'structured' ]),
85
- odataVocRefs: {
85
+ odataVocabularies: {
86
86
  validate: val => (typeof val === 'object' && !Array.isArray(val)),
87
87
  expected: () => 'type JSON object',
88
88
  found: val => `type ${ Array.isArray(val) ? 'JSON array' : typeof val }`,
@@ -164,17 +164,18 @@ const centralMessages = {
164
164
  'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
165
165
  'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
166
166
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
167
- // Published! Used in @sap/cds-odata-v2-adapter-proxy; if renamed, add to oldMessageIds
167
+ 'def-invalid-key-cardinality': { severity: 'Error' },
168
+ // Published! Used in @cap-js-community/odata-v2-adapter; if renamed, add to oldMessageIds
168
169
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
169
- // Published! Used in @sap/cds-odata-v2-adapter-proxy; if renamed, add to oldMessageIds
170
+ // Published! Used in @cap-js-community/odata-v2-adapter; if renamed, add to oldMessageIds
170
171
  'odata-spec-violation-assoc': { severity: 'Warning' }, // more than 30 chars
171
- // Published! Used in @sap/cds-odata-v2-adapter-proxy; if renamed, add to oldMessageIds
172
+ // Published! Used in @cap-js-community/odata-v2-adapter; if renamed, add to oldMessageIds
172
173
  'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
173
174
  'odata-spec-violation-id': { severity: 'Error', configurableFor: true },
174
175
  'odata-spec-violation-namespace': { severity: 'Warning' }, // more than 30 chars
175
- // Published! Used in @sap/cds-odata-v2-adapter-proxy; if renamed, add to oldMessageIds
176
+ // Published! Used in @cap-js-community/odata-v2-adapter; if renamed, add to oldMessageIds
176
177
  'odata-spec-violation-param': { severity: 'Warning' }, // more than 30 chars
177
- // Published! Used in @sap/cds-odata-v2-adapter-proxy; if renamed, add to oldMessageIds
178
+ // Published! Used in @cap-js-community/odata-v2-adapter; if renamed, add to oldMessageIds
178
179
  'odata-spec-violation-returns': { severity: 'Warning' }, // more than 30 chars
179
180
  'odata-spec-violation-type': { severity: 'Error', configurableFor: true },
180
181
  'odata-spec-violation-type-unknown': { severity: 'Warning' },
@@ -458,8 +459,10 @@ const centralMessageTexts = {
458
459
  annotation: 'An annotation can\'t include an entity with calculated elements',
459
460
  },
460
461
  'def-unsupported-calc-elem': {
461
- std: 'Calculated elements are not supported',
462
- nested: 'Calculated elements in structures are not supported, yet'
462
+ std: 'Calculated elements are not supported, yet',
463
+ nested: 'Calculated elements in structures are not supported, yet',
464
+ 'on-write': 'Calculated elements on-write are not supported, yet',
465
+ 'hdbcds': 'Calculated elements on-write are not supported for HDBCDS',
463
466
  },
464
467
  // 'syntax-unknown-property' (Warning? Better configurable Error)
465
468
 
@@ -512,7 +515,7 @@ const centralMessageTexts = {
512
515
  },
513
516
  'ref-expected-element': {
514
517
  std: 'Expected element reference',
515
- magicVar: 'Only elements of magic variable $(ID) can be selected',
518
+ magicVar: 'Only elements of variable $(ID) can be selected',
516
519
  },
517
520
  'ref-expected-direct-structure': {
518
521
  std: '$(ART) can\'t be extended because it originates from an include',
@@ -595,14 +598,14 @@ const centralMessageTexts = {
595
598
  },
596
599
 
597
600
  'def-unexpected-paramview-assoc': {
598
- std: 'SAP HANA doesn\'t support associations in/to parameterized entities',
599
- view: 'SAP HANA doesn\'t support associations in parameterized entities',
600
- target: 'SAP HANA doesn\'t support associations to parameterized entities',
601
+ std: 'unused',
602
+ source: 'Unexpected definition of an association in an entity with parameters',
603
+ target: 'Expected association target to have no parameters',
601
604
  },
602
605
  'def-unexpected-calcview-assoc': {
603
- std: 'SAP HANA doesn\'t allow associations in/to entities annotated with $(ANNO)',
604
- 'entity-persistence': 'SAP HANA doesn\'t allow associations in entities annotated with $(ANNO)',
605
- 'target-persistence': 'SAP HANA doesn\'t allow associations pointing to entities annotated with $(ANNO)',
606
+ std: 'unused',
607
+ 'source': 'Unexpected definition of an association in an entity annotated with $(ANNO)',
608
+ 'target': 'Expected association target not to be annotated with $(ANNO)',
606
609
  },
607
610
  'def-unexpected-key': {
608
611
  std: '$(ART) can\'t have additional keys',
@@ -657,6 +660,12 @@ const centralMessageTexts = {
657
660
  elements: 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
658
661
  actions: 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
659
662
  },
663
+ 'ref-invalid-override': {
664
+ std: 'Overridden element of include must not change element structure', // unused
665
+ 'new-not-structured': 'Expected element $(NAME) to be structured, because it overrides the included element from $(ART)',
666
+ 'old-not-structured': 'Expected element $(NAME) to be scalar, because it overrides the included element from $(ART)',
667
+ missing: 'Expected element $(ID) to have at least all the same sub-elements as included artifacts, but it is missing $(NAME)'
668
+ },
660
669
 
661
670
  // TODO: rename to ref-expected-XYZ
662
671
  'expected-actionparam-type': 'A type, an element, or a service entity is expected here',
@@ -753,12 +762,12 @@ const centralMessageTexts = {
753
762
  },
754
763
  // version independent messages
755
764
  'odata-spec-violation-key-array': {
756
- std: 'Unexpected array type for element $(NAME)',
757
- scalar: 'Unexpected array type',
765
+ std: 'Unexpected array type for primary key $(NAME)',
766
+ assoc: 'Unexpected target cardinality $(VALUE) for primary key $(NAME)',
758
767
  },
759
768
  'odata-spec-violation-key-null': {
760
- std: 'Expected key element $(NAME) to be not nullable', // structured
761
- scalar: 'Expected key element to be not nullable' // flat
769
+ std: 'Expected key element $(NAME) to be not nullable', // flat
770
+ scalar: 'Expected key element $(NAME) to be not nullable', // structured
762
771
  },
763
772
  'odata-spec-violation-key-type': {
764
773
  std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
@@ -670,7 +670,7 @@ const paramsTransform = {
670
670
  alias: quote.double,
671
671
  anno,
672
672
  annos: transformManyWith( anno ),
673
- delimited: n => quote.single( `![${ n }]` ), // represent as delimited id (TODO: double-']')
673
+ delimited: n => quote.single( asDelimitedId(n) ),
674
674
  file: quote.single,
675
675
  option: quote.single,
676
676
  prop: quote.single,
@@ -708,6 +708,11 @@ const paramsTransform = {
708
708
  version: quote.single, // TODO delete: just use for OData $(VERSION), with version: 2.0
709
709
  };
710
710
 
711
+ function asDelimitedId(id) {
712
+ // Same as in toCdl, but we don't want cyclic dependencies to toCdl.
713
+ return `![${id.replace(/]/g, ']]')}]`;
714
+ }
715
+
711
716
  function anno( name ) {
712
717
  return (name.charAt(0) === '@') ? quote.double( name ) : quote.double( `@${ name }` );
713
718
  }
package/lib/base/model.js CHANGED
@@ -29,10 +29,10 @@ const availableBetaFlags = {
29
29
  ignoreAssocPublishingInUnion: true,
30
30
  enableUniversalCsn: true,
31
31
  postgres: true,
32
- aspectWithoutElements: true,
33
- odataOpenType: true,
32
+ aspectWithoutElements: true, // TODO: do not just remove beta flag - remove elements, too!
34
33
  odataTerms: true,
35
34
  optionalActionFunctionParameters: true,
35
+ calculatedElementsOnWrite: true,
36
36
  // disabled by --beta-mode
37
37
  nestedServices: false,
38
38
  v4preview: false,
@@ -16,6 +16,7 @@
16
16
  "jsdoc/require-param-description": 0,
17
17
  // there seem to be false positives
18
18
  "jsdoc/require-returns-check": 0,
19
+ "jsdoc/tag-lines": 0,
19
20
  // =airbnb, >eslint:
20
21
  "max-len": [ "error", {
21
22
  "code": 110,
@@ -67,7 +67,7 @@ function checkActionOrFunction( art, artName, prop, path ) {
67
67
  * @param {string} actKind 'action' or 'function'
68
68
  */
69
69
  function checkActionOrFunctionParameter( param, currPath, actKind ) {
70
- const paramType = param.type ? this.csnUtils.getFinalBaseTypeWithProps(param.type) : param;
70
+ const paramType = param.type ? this.csnUtils.getFinalTypeInfo(param.type) : param;
71
71
  if (!paramType)
72
72
  return; // no type could be resolved
73
73
 
@@ -80,7 +80,7 @@ function checkActionOrFunction( art, artName, prop, path ) {
80
80
  });
81
81
  }
82
82
 
83
- if (param.type && this.csnUtils.isAssocOrComposition(param.type)) {
83
+ if (param.type && this.csnUtils.isAssocOrComposition(param)) {
84
84
  this.error(null, currPath, { '#': actKind }, {
85
85
  std: 'An association is not allowed as this artifact\'s parameter type', // Not used
86
86
  action: 'An association is not allowed as action\'s parameter type',
@@ -103,11 +103,11 @@ function checkActionOrFunction( art, artName, prop, path ) {
103
103
  * @param {string} actKind 'action' or 'function'
104
104
  */
105
105
  function checkReturns( returns, currPath, actKind ) {
106
- const finalReturnType = returns.type ? this.csnUtils.getFinalBaseTypeWithProps(returns.type) : returns;
106
+ const finalReturnType = returns.type ? this.csnUtils.getFinalTypeInfo(returns.type) : returns;
107
107
  if (!finalReturnType)
108
108
  return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
109
109
 
110
- if (this.csnUtils.isAssocOrComposition(finalReturnType.type)) {
110
+ if (this.csnUtils.isAssocOrComposition(returns)) {
111
111
  this.error(null, currPath, { '#': actKind },
112
112
  {
113
113
  std: 'An association is not allowed as this artifact\'s return type', // Not used
@@ -117,9 +117,9 @@ function checkActionOrFunction( art, artName, prop, path ) {
117
117
  }
118
118
 
119
119
  if (finalReturnType.items) // check array return type
120
- checkReturns.bind(this)(finalReturnType.items, currPath.concat('items'), actKind);
120
+ checkReturns.call(this, finalReturnType.items, currPath.concat('items'), actKind);
121
121
  else // check if return type is user defined from the current service
122
- checkUserDefinedType.bind(this)(finalReturnType, returns.type, currPath);
122
+ checkUserDefinedType.call(this, finalReturnType, returns.type, currPath);
123
123
  }
124
124
 
125
125
  /**
@@ -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.getFinalBaseTypeWithProps(member.type)?.type in allowedCoreMediaTypes)) {
26
+ if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalTypeInfo(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
  }
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const { forEachMember, forEachMemberRecursively, isBuiltinType } = require('../model/csnUtils');
3
+ const {
4
+ forEachMember, forEachMemberRecursively, isBuiltinType, cardinality2str,
5
+ } = require('../model/csnUtils');
4
6
  const { isGeoTypeName } = require('../compiler/builtins');
5
7
  const { setProp } = require('../base/model');
6
8
  // Only to be used with validator.js - a correct `this` value needs to be provided!
@@ -36,7 +38,7 @@ function checkPrimaryKey( art ) {
36
38
  */
37
39
  function checkIfPrimaryKeyIsOfGeoType( member, elemFqName, parentIsKey, parentPath ) {
38
40
  if (member.key || parentIsKey) {
39
- const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
41
+ const finalBaseType = this.csnUtils.getFinalTypeInfo(member.type);
40
42
  if (isGeoTypeName(finalBaseType?.type)) {
41
43
  this.error(null, parentPath || member.$path,
42
44
  { type: finalBaseType.type, name: elemFqName },
@@ -65,10 +67,18 @@ function checkPrimaryKey( art ) {
65
67
  */
66
68
  function checkIfPrimaryKeyIsArray( member, elemFqName, parentIsKey, parentPath ) {
67
69
  if (member.key || parentIsKey) {
68
- const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
70
+ const finalBaseType = this.csnUtils.getFinalTypeInfo(member.type);
69
71
  if (member.items || (finalBaseType && finalBaseType.items)) {
70
- this.error(null, parentPath || member.$path, { name: elemFqName },
71
- 'Array-like type in element $(NAME) can\'t be used as primary key');
72
+ let msg = 'std';
73
+ if (member.target)
74
+ msg = this.csnUtils.isComposition(member) ? 'comp' : 'assoc';
75
+
76
+ this.error('def-invalid-key-cardinality', parentPath || member.$path,
77
+ {
78
+ name: elemFqName,
79
+ value: cardinality2str(member, false),
80
+ '#': msg,
81
+ }, 'Array-like type in element $(NAME) can\'t be used as primary key');
72
82
  }
73
83
  else if (finalBaseType && this.csnUtils.isStructured(finalBaseType) && !finalBaseType.$visited) {
74
84
  setProp(finalBaseType, '$visited', true);
@@ -84,17 +94,17 @@ function checkPrimaryKey( art ) {
84
94
  }
85
95
  }
86
96
 
97
+
87
98
  /**
88
- * Checks virtual elelemts and throws an error if some is either structured or
99
+ * Checks virtual elements and throws an error if some is either structured or
89
100
  * an association
90
101
  *
91
102
  * @param {CSN.Element} member Element to be checked
92
103
  */
93
104
  function checkVirtualElement( member ) {
94
105
  if (member.virtual) {
95
- if (this.csnUtils.isAssociation(member.type)) { // or Composition ???
96
- this.error(null, member.$path, {}, 'Element can\'t be virtual and an association');
97
- }
106
+ if (this.csnUtils.isAssocOrComposition(member))
107
+ this.error(null, member.$path, {}, 'Element can\'t be virtual and an association or composition');
98
108
  }
99
109
  }
100
110
 
@@ -107,18 +117,19 @@ function checkVirtualElement( member ) {
107
117
  */
108
118
  function checkManagedAssoc( art ) {
109
119
  forEachMemberRecursively(art, (member) => {
110
- if (this.csnUtils.isAssocOrComposition(member.type) &&
120
+ if (this.csnUtils.isAssocOrComposition(member) &&
111
121
  !isManagedComposition.bind(this)(member)) {
112
- if (member.on)
113
- return;
114
- const max = member.cardinality && member.cardinality.max;
115
- if (max === '*' || Number(max) > 1) {
122
+ const max = member.cardinality?.max ? member.cardinality.max : 1;
123
+ if (max !== 1 && !member.on) {
116
124
  const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
117
125
  this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path,
118
- { '#': this.csnUtils.isComposition(member.type) ? 'cmp' : 'std' },
119
126
  {
120
- std: 'An association can\'t have cardinality "to many" without an ON-condition',
121
- cmp: 'A composition can\'t have cardinality "to many" without an ON-condition',
127
+ value: cardinality2str(member, false),
128
+ '#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
129
+ },
130
+ {
131
+ std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
132
+ comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
122
133
  });
123
134
  }
124
135
  }
@@ -65,7 +65,7 @@ function validateForeignKeys( member, memberName ) {
65
65
  delete type.$visited;
66
66
  }
67
67
  }
68
- else if (mem && !mem.type) {
68
+ else if (mem && !mem.type && this.options.transformation !== 'odata') {
69
69
  const variant = member.type === 'cds.Composition' ? 'managedCompForeignKey' : 'managedAssocForeignKey';
70
70
  this.error('check-proper-type-of', member.$path, {
71
71
  '#': variant, name: memName, art: memberName,
@@ -27,7 +27,7 @@ function invalidTarget( member ) {
27
27
  if (!target)
28
28
  throw new ModelError(`Expected target ${ mem.target }`);
29
29
  if (target.kind !== 'entity') {
30
- const isAssoc = this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type !== 'cds.Composition';
30
+ const isAssoc = this.csnUtils.getFinalTypeInfo(member.type)?.type !== 'cds.Composition';
31
31
  this.error(
32
32
  null,
33
33
  member.$path,
@@ -79,23 +79,27 @@ function validateOnCondition( member, memberName, property, path ) {
79
79
  // For error messages
80
80
  const id = logReady(ref[j]);
81
81
  const elemref = { ref };
82
-
83
- if (_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || (validDollarSelf && j === _links.length - 1))) {
84
- if (_links[j].art.on) {
82
+ const stepArt = _links[j].art;
83
+ if (stepArt.target &&
84
+ !(stepArt === member ||
85
+ ref[j] === '$self' ||
86
+ ref[j] === '$projection' ||
87
+ (validDollarSelf && j === _links.length - 1))) {
88
+ if (stepArt.on) {
85
89
  // It's an unmanaged association - traversal is always forbidden
86
90
  this.error('ref-unexpected-navigation', csnPath, { '#': 'unmanaged', id, elemref });
87
91
  }
88
92
  else {
89
93
  // It's a managed association - access of the foreign keys is allowed
90
94
  const nextRef = ref[j + 1].id || ref[j + 1];
91
- if (!_links[j].art.keys.some(r => r.ref[0] === nextRef)) {
95
+ if (!stepArt.keys.some(r => r.ref[0] === nextRef)) {
92
96
  this.error('ref-unexpected-navigation', csnPath, {
93
97
  '#': 'std', id, elemref, name: nextRef,
94
98
  });
95
99
  }
96
100
  }
97
101
  }
98
- if (_links[j].art.virtual)
102
+ if (stepArt.virtual)
99
103
  this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
100
104
 
101
105
  if (ref[j].where)
@@ -104,7 +108,8 @@ function validateOnCondition( member, memberName, property, path ) {
104
108
  if (ref[j].args)
105
109
  this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain parameters, step $(ID) of path $(ELEMREF)');
106
110
  }
107
- if (_art && $scope !== '$self') {
111
+
112
+ if (_art && !($scope === '$self' && ref.length === 1)) {
108
113
  const type = resolveArtifactType.call(this, _art);
109
114
  if (type) {
110
115
  // For error messages
@@ -84,7 +84,7 @@ function checkQueryForNoDBArtifacts( query ) {
84
84
  const isJoinRelevant = (assoc, nextStep) => {
85
85
  if (!assoc.keys)
86
86
  return true;
87
- const isExposedColumnAssocOrComposition = this.csnUtils.isAssocOrComposition(obj._art.type);
87
+ const isExposedColumnAssocOrComposition = this.csnUtils.isAssocOrComposition(obj._art);
88
88
  return !assoc.keys
89
89
  .some(fk => fk.ref[0] === nextStep && !isExposedColumnAssocOrComposition);
90
90
  };
@@ -130,7 +130,7 @@ function checkTypeOfHasProperType( artOrElement, name, model, error, path, deriv
130
130
  if (!artOrElement.type)
131
131
  return;
132
132
 
133
- const typeOfType = this.csnUtils.getFinalBaseTypeWithProps(artOrElement.type);
133
+ const typeOfType = this.csnUtils.getFinalTypeInfo(artOrElement.type);
134
134
 
135
135
  if (typeOfType === null) {
136
136
  if (artOrElement.type.ref && this?.options?.transformation !== 'odata') {
@@ -59,7 +59,7 @@ function otherSideIsExpandableStructure( on, startIndex ) {
59
59
  */
60
60
  function resolveArtifactType( art ) {
61
61
  if (art && art.type && !isBuiltinType(art.type))
62
- return this.csnUtils.getFinalBaseTypeWithProps(art.type);
62
+ return this.csnUtils.getFinalTypeInfo(art.type);
63
63
 
64
64
  return art;
65
65
  }
@@ -57,10 +57,12 @@ const forRelationalDBMemberValidators
57
57
  checkSqlAnnotationOnElement,
58
58
  // no temporal annotations on calc elements
59
59
  rejectAnnotationsOnCalcElement,
60
+ checkElementTypeDefinitionHasType,
60
61
  ];
61
62
 
62
63
  const forRelationalDBArtifactValidators
63
64
  = [
65
+ checkPrimaryKey,
64
66
  // @cds.persistence has no impact on odata
65
67
  validateCdsPersistenceAnnotation,
66
68
  // virtual items are not persisted on the db
@@ -108,12 +110,11 @@ const forOdataQueryValidators = [];
108
110
  const commonMemberValidators
109
111
  = [ validateOnCondition, validateForeignKeys,
110
112
  validateAssociationsInItems, checkForInvalidTarget,
111
- checkVirtualElement, checkElementTypeDefinitionHasType ];
113
+ checkVirtualElement ];
112
114
 
113
115
  // TODO: checkManagedAssoc is a forEachMemberRecursively!
114
116
  const commonArtifactValidators = [
115
117
  checkTypeDefinitionHasType,
116
- checkPrimaryKey,
117
118
  checkManagedAssoc,
118
119
  checkRecursiveTypeUsage,
119
120
  ];
@@ -100,12 +100,13 @@ function assertConsistency( model, stage ) {
100
100
  '$builtins',
101
101
  '$internal',
102
102
  '$compositionTargets',
103
- '$lateExtensions',
103
+ '$collectedExtensions',
104
104
  '_entities', '$entity',
105
105
  '$blocks',
106
106
  '$messageFunctions',
107
107
  '$functions',
108
108
  '$volatileFunctions',
109
+ '_sortedSources',
109
110
  ],
110
111
  },
111
112
  ':parser': { // top-level from parser
@@ -139,6 +140,7 @@ function assertConsistency( model, stage ) {
139
140
  },
140
141
  },
141
142
  sources: { test: isDictionary( isObject ) },
143
+ _sortedSources: { test: isArray( isObject ) },
142
144
  file: { test: isString },
143
145
  dirname: { test: isString }, // TODO: really necessary?
144
146
  realname: { test: isString }, // TODO: really necessary?
@@ -401,6 +403,8 @@ function assertConsistency( model, stage ) {
401
403
  'elements', 'items', 'enum',
402
404
  // CSN parser may let these properties slip through to XSN, even if input is invalid.
403
405
  'args', 'op', 'func', 'suffix',
406
+ // calculated elements on-write
407
+ 'stored',
404
408
  ],
405
409
 
406
410
  kind: true,
@@ -471,6 +475,7 @@ function assertConsistency( model, stage ) {
471
475
  op: { test: locationVal( isString ) },
472
476
  join: { test: locationVal( isString ) },
473
477
  quantifier: { test: locationVal( isString ) },
478
+ stored: { test: locationVal( isBoolean ) },
474
479
  // preliminary -----------------------------------------------------------
475
480
  doc: {
476
481
  kind: true,
@@ -504,7 +509,7 @@ function assertConsistency( model, stage ) {
504
509
  ],
505
510
  },
506
511
  absolute: { test: isString },
507
- variant: { test: TODO }, // TODO: not set in CDL parser
512
+ variant: { requires: [ 'location', 'path' ] },
508
513
  element: { test: TODO }, // TODO: { test: isString },
509
514
  action: { test: isString },
510
515
  param: { test: TODO },
@@ -608,7 +613,7 @@ function assertConsistency( model, stage ) {
608
613
  $entity: { kind: true, test: TODO },
609
614
  _entities: { test: TODO },
610
615
  $compositionTargets: { test: isDictionary( isBoolean ) },
611
- $lateExtensions: { test: TODO },
616
+ $collectedExtensions: { test: TODO },
612
617
  _upperAspects: { kind: [ 'type', 'entity' ], test: isArray( TODO ) },
613
618
 
614
619
  // for implicit redirection - direct and indirect query sources of simple