@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. package/lib/utils/string.js +0 -17
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { makeMessageFunction, handleMessages } = require('../base/messages');
3
+ const { makeMessageFunction } = require('../base/messages');
4
4
 
5
5
  /* eslint-disable arrow-body-style */
6
6
  const booleanValidator = {
@@ -94,6 +94,11 @@ const validators = {
94
94
  expected: () => 'Integer literal',
95
95
  found: val => `type ${ typeof val }`,
96
96
  },
97
+ csnFlavor: {
98
+ validate: val => typeof val === 'string',
99
+ expected: () => 'type string',
100
+ found: val => `type ${ typeof val }`,
101
+ },
97
102
  dictionaryPrototype: {
98
103
  validate: () => true,
99
104
  },
@@ -122,15 +127,17 @@ const allCombinationValidators = {
122
127
  * Use a custom validator or "default" custom validator, fallback to Boolean validator.
123
128
  *
124
129
  * @param {object} options Flat options object to validate
130
+ * @param {string} moduleName The called module, e.g. 'for.odata', 'to.hdi'. Needed to initialize the message functions
125
131
  * @param {object} [customValidators] Map of custom validators to use
126
132
  * @param {string[]} [combinationValidators] Validate option combinations
127
133
  * @returns {void}
128
134
  * @throws {CompilationError} Throws in case of invalid option usage
129
135
  */
130
- function validate(options, customValidators = {}, combinationValidators = []) {
136
+ function validate(options, moduleName, customValidators = {}, combinationValidators = []) {
137
+ // TODO: issuing messages in this function looks very strange...
131
138
  {
132
139
  const messageCollector = { messages: [] };
133
- const { error, throwWithError } = makeMessageFunction(null, messageCollector);
140
+ const { error, throwWithError } = makeMessageFunction(null, messageCollector, moduleName);
134
141
 
135
142
  for (const optionName of Object.keys(options)) {
136
143
  const optionValue = options[optionName];
@@ -142,7 +149,7 @@ function validate(options, customValidators = {}, combinationValidators = []) {
142
149
  throwWithError();
143
150
  }
144
151
 
145
- const message = makeMessageFunction(null, options);
152
+ const message = makeMessageFunction(null, options, moduleName);
146
153
 
147
154
  for (const combinationValidatorName of combinationValidators.concat([ 'beta-no-test' ])) {
148
155
  const combinationValidator = allCombinationValidators[combinationValidatorName];
@@ -150,10 +157,7 @@ function validate(options, customValidators = {}, combinationValidators = []) {
150
157
  message[combinationValidator.severity]('invalid-option-combination', null, {}, combinationValidator.getMessage(options));
151
158
  }
152
159
 
153
- // TODO: Replace with message.throwWithError():
154
- // But be aware that it only throws with non-configurable errors and that this
155
- // will lead to issues in test3. See #6037
156
- handleMessages(undefined, options);
160
+ message.throwWithError();
157
161
  }
158
162
  /* eslint-enable jsdoc/no-undefined-types */
159
163
 
package/lib/backends.js CHANGED
@@ -6,7 +6,6 @@
6
6
  const { transformForHanaWithCsn } = require('./transform/forHanaNew');
7
7
  const { compactModel, sortCsn } = require('./json/to-csn')
8
8
  const { toCdsSourceCsn } = require('./render/toCdl');
9
- const { toHdbcdsSource } = require('./render/toHdbcds');
10
9
  const { toSqlDdl } = require('./render/toSql');
11
10
  const { toRenameDdl } = require('./render/toRename');
12
11
  const { manageConstraints, listReferentialIntegrityViolations } = require('./render/manageConstraints');
@@ -19,85 +18,6 @@ const timetrace = require('./utils/timetrace');
19
18
  const { makeMessageFunction } = require('./base/messages');
20
19
  const { forEachDefinition } = require('./model/csnUtils');
21
20
 
22
- /**
23
- * Transform a CSN into HANA-compatible CDS source.
24
- * The following options control what is actually generated (see help above):
25
- * options : {
26
- * toHana.names
27
- * toHana.src
28
- * toHana.csn
29
- * }
30
- * Options provided here are merged with (and take precedence over) options from 'model'.
31
- * If 'toHana.names' is not provided, 'quoted' is used.
32
- * If neither 'toHana.src' nor 'toHana.csn' are provided, the default is to generate only HANA CDS
33
- * source files.
34
- * If all provided options are part of 'toHana', the 'toHana' wrapper can be omitted.
35
- * The result object contains the generation results as follows (as enabled in 'options'):
36
- * result : {
37
- * csn : the (compact) transformed CSN model
38
- * hdbcds : a dictionary of top-level artifact names, containing for each name 'X':
39
- * <X> : the HANA CDS source string of the artifact 'X'. Please note that the
40
- * name of 'X' may contain characters that are not legal for filenames on
41
- * all operating systems (e.g. ':', '\' or '/').
42
- * X reflects the naming policy set by toHana.names
43
- * }
44
- * Throws a CompilationError on errors.
45
- *
46
- * @param {CSN.Model} csn
47
- * @param {CSN.Options} [options]
48
- */
49
- function toHanaWithCsn(csn, options) {
50
- timetrace.start('toHanaWithCsn');
51
- // In case of API usage the options are in the 'options' argument
52
- // put the OData specific options under the 'options.toHana' wrapper
53
- // and leave the rest under 'options'
54
- if (options && !options.toHana) {
55
- _wrapRelevantOptionsForCmd(options, 'toHana');
56
- }
57
- // Provide defaults and merge options with those from model
58
- options = mergeOptions({ toHana : getDefaultBackendOptions().toHana }, options);
59
-
60
- // Provide something to generate if nothing else was given (conditional default)
61
- if (!options.toHana.src && !options.toHana.csn) {
62
- options.toHana.src = true;
63
- }
64
-
65
- const { warning } = makeMessageFunction(csn, options, 'to.hdbcds');
66
-
67
- // Verify options
68
- optionProcessor.verifyOptions(options, 'toHana', true).forEach(complaint => warning(null, null, `${complaint}`));
69
-
70
- // Special case: For naming variant 'hdbcds' in combination with 'toHana' (and only there!), 'forHana'
71
- // must leave namespaces, structs and associations alone.
72
- if (options.toHana.names === 'hdbcds') {
73
- options = mergeOptions(options, { forHana : { keepNamespaces: true, keepStructsAssocs: true } });
74
- }
75
-
76
- options = mergeOptions(options, { forHana: { dialect: 'hana' } }, { forHana : options.toHana });
77
-
78
- // Prepare model for HANA (transferring the options to forHana, and setting 'dialect' to 'hana', because 'toHana' is only used for that)
79
- let forHanaCsn = transformForHanaWithCsn(csn, options, 'to.hdbcds');
80
-
81
- // Assemble result
82
- let result = {};
83
-
84
- if (options.toHana.src) {
85
- if(options.testMode){
86
- const sorted = sortCsn(forHanaCsn, options);
87
- result = toHdbcdsSource(sorted, options);
88
- } else {
89
- result = toHdbcdsSource(forHanaCsn, options);
90
- }
91
- }
92
-
93
- if (options.toHana.csn) {
94
- result.csn = options.testMode ? sortCsn(forHanaCsn, options) : forHanaCsn;
95
- }
96
-
97
- timetrace.stop();
98
- return result;
99
- }
100
-
101
21
  /**
102
22
  * Generate ODATA for `csn` using `options`.
103
23
  * The twin of the toOdata function but using CSN
@@ -603,7 +523,6 @@ function _wrapRelevantOptionsForCmd(options, command) {
603
523
  }
604
524
 
605
525
  module.exports = {
606
- toHanaWithCsn,
607
526
  toOdataWithCsn,
608
527
  preparedCsnToEdmx,
609
528
  preparedCsnToEdmxAll,
@@ -194,7 +194,7 @@ module.exports = {
194
194
  'OTHERS',
195
195
  'TIES',
196
196
  ],
197
- // HANA keywords, used to warn in 'toSql' renderer with dialect 'hana' or in 'toHana' renderer (both with 'plain' names only)
197
+ // HANA keywords, used for smart quoting in to-hdi.plain
198
198
  // Taken from https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/28bcd6af3eb6437892719f7c27a8a285.html
199
199
  // Better use keywords in ptime/query/parser/syntax/qp_keyword.cc minus those
200
200
  // in rule unreserved_keyword_column (=…_common - "CONSTRAINT") in
@@ -673,5 +673,35 @@ module.exports = {
673
673
  'WITHIN',
674
674
  'XMLTABLE',
675
675
  'YEAR'
676
- ]
676
+ ],
677
+ // HANA CDS keywords, used for smart quoting in to-hdbcds.plain
678
+ hdbcds: [
679
+ 'ALL', 'ALTER', 'AS',
680
+ 'BEFORE', 'BEGIN', 'BOTH',
681
+ 'CASE', 'CHAR', 'CONDITION',
682
+ 'CONNECT', 'CROSS', 'CUBE',
683
+ 'CURRENT_CONNECTION', 'CURRENT_DATE', 'CURRENT_SCHEMA',
684
+ 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER',
685
+ 'CURRENT_UTCDATE', 'CURRENT_UTCTIME', 'CURRENT_UTCTIMESTAMP',
686
+ 'CURRVAL', 'CURSOR', 'DECLARE',
687
+ 'DISTINCT', 'ELSE', 'ELSEIF',
688
+ 'ELSIF', 'END', 'EXCEPT',
689
+ 'EXCEPTION', 'EXEC', 'FOR',
690
+ 'FROM', 'FULL', 'GROUP',
691
+ 'HAVING', 'IF', 'IN',
692
+ 'INNER', 'INOUT', 'INTERSECT',
693
+ 'INTO', 'IS', 'JOIN',
694
+ 'LEADING', 'LEFT', 'LIMIT',
695
+ 'LOOP', 'MINUS', 'NATURAL',
696
+ 'NEXTVAL', 'NULL', 'ON',
697
+ 'ORDER', 'OUT', 'OUTER',
698
+ 'PRIOR', 'RETURN', 'RETURNS',
699
+ 'REVERSE', 'RIGHT', 'ROLLUP',
700
+ 'ROWID', 'SELECT', 'SET',
701
+ 'SQL', 'START', 'SYSDATE',
702
+ 'SYSTIME', 'SYSTIMESTAMP', 'SYSUUID',
703
+ 'TOP', 'TRAILING', 'UNION',
704
+ 'USING', 'VALUES', 'WHEN',
705
+ 'WHERE', 'WHILE', 'WITH'
706
+ ]
677
707
  }
@@ -1,5 +1,33 @@
1
1
  // Central registry for messages.
2
2
 
3
+ // `centralMessages` contains all details of a message-id except its standard texts
4
+ // (`standardTexts` exists for that). Only `severity` is required, all other
5
+ // properties are optional.
6
+
7
+ // The user can specify "severity wishes" via the option `severities`. Errors
8
+ // that don't have a `configurableFor` property cannot be reclassified by
9
+ // users. If a module is used that is _not_ listed in `configurableFor` (if it
10
+ // is an array) property of the message then the message cannot be
11
+ // reclassified.
12
+
13
+ // We also allow `configurableFor` to have value `true` for errors which are
14
+ // always configurable; useful for issues like deprecated syntax variants which
15
+ // do not affect the compiler or CSN processors. Temporarily, we also allow
16
+ // value `deprecated` for errors which are only configurable if the option
17
+ // `deprecated.downgradableErrors` is set.
18
+
19
+ // Messages other than errors can always be reclassified by the user except if
20
+ // the module is listed in the message's `errorFor` property.
21
+
22
+ // __NEW__: If the future `poc` (proof of concept) or `sloppy` option is set,
23
+ // the module name `compile` is added to all configurable messages, i.e. to all
24
+ // `configurableFor` arrays. (module `compile` includes all parsers and the
25
+ // core compiler). This allows creators of _non-productive models_ to
26
+ // reclassify errors which usually cannot be reclassified, and continue the
27
+ // compilation but has the side effect that the result may be unstable, hence
28
+ // "sloppy": with an upcoming _minor_ version of the compiler, the compilation
29
+ // might lead to an error anyway or the compiled CSN might look different.
30
+
3
31
  'use strict';
4
32
 
5
33
  /**
@@ -59,13 +87,15 @@ const centralMessages = {
59
87
  'param-default': { severity: 'Error', configurableFor: 'deprecated' }, // not supported yet
60
88
 
61
89
  'query-undefined-element': { severity: 'Error' },
90
+ 'query-unexpected-assoc-hdbcds': { severity: 'Error' },
91
+ 'query-unexpected-structure-hdbcds': { severity: 'Error' },
62
92
 
63
93
  'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js
64
94
  'redirected-implicitly-ambiguous': { severity: 'Error', configurableFor: true }, // does not hurt us - TODO: ref-ambiguous-target
65
95
  'type-ambiguous-target': { severity: 'Warning' },
66
96
 
67
97
  'ref-autoexposed': { severity: 'Error', configurableFor: 'deprecated' },
68
- 'ref-undefined-art': { severity: 'Error' }, // TODO: Remove if shared.js uses makeMessageFunction()
98
+ 'ref-undefined-art': { severity: 'Error' },
69
99
  'ref-undefined-def': { severity: 'Error' },
70
100
  'ref-undefined-var': { severity: 'Error' },
71
101
  'ref-undefined-element': { severity: 'Error' },
@@ -100,8 +130,12 @@ const centralMessages = {
100
130
  'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
101
131
  'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
102
132
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
103
- 'odata-spec-violation-array-of-v2': { severity: 'Warning' }, // more than 30 chars
104
- 'odata-spec-violation-constraints': { severity: 'Warning' }, // more than 30 chars
133
+ 'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
134
+ 'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
135
+ 'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
136
+ 'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
137
+ 'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
138
+ 'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
105
139
  'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
106
140
  'odata-spec-violation-namespace-name': { severity: 'Warning' }, // more than 30 chars
107
141
  };
@@ -191,6 +225,10 @@ const centralMessageTexts = {
191
225
  'expected-target': 'An entity or an aspect is expected here',
192
226
  'extend-columns': 'Artifact $(ART) can\'t be extended with columns, only projections can',
193
227
  'extend-repeated-intralayer': 'Unstable element order due to repeated extensions in same layer',
228
+
229
+ 'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
230
+ 'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
231
+
194
232
  'ref-sloppy-type': 'A type or an element is expected here',
195
233
  'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
196
234
  'ref-sloppy-target': 'An entity or an aspect (not type) is expected here',
@@ -204,13 +242,29 @@ const centralMessageTexts = {
204
242
  },
205
243
 
206
244
  'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
207
- 'odata-spec-violation-array-of-v2': 'EDM $(LITERAL) must not have an attribute $(PROP) with the value $(NAME)',
208
- 'odata-spec-violation-param-v2' : 'Type of EDM FunctionImport Parameter must be an EDM SimpleType or ComplexType',
209
- 'odata-spec-violation-returns-v2': 'EDM FunctionImport must return EDM SimpleType, ComplexType, EntityType or a collection of one of these',
210
- 'odata-spec-violation-assoc-v2': 'EDM ComplexType $(NAME) must not contain an EDM NavigationProperty',
211
- 'odata-spec-violation-attribute': 'EDM Property $(NAME) has no attribute $(PROP)',
212
- 'odata-spec-violation-property-name': 'EDM Property $(NAME) must not have the same name as the declaring $(TYPE)',
213
245
 
246
+ // OData version dependent messages
247
+ 'odata-spec-violation-array': 'Unexpected array type for $(API)',
248
+ 'odata-spec-violation-param' : 'Expected parameter to be typed with either scalar or structured type for $(API)',
249
+ 'odata-spec-violation-returns': 'Expected $(KIND) to return one or many values of scalar, complex, entity or view type for $(API)',
250
+ 'odata-spec-violation-assoc': 'Unexpected association in structured type for $(API)',
251
+ 'odata-spec-violation-constraints': 'Partial referential constraints produced for $(API)',
252
+ // version independent messages
253
+ 'odata-spec-violation-key-array': {
254
+ std: 'Unexpected array type for element $(NAME)',
255
+ scalar: 'Unexpected array type'
256
+ },
257
+ 'odata-spec-violation-key-null': {
258
+ std: 'Expected key element $(NAME) to be not nullable', // structured
259
+ scalar: 'Expected key element to be not nullable' // flat
260
+ },
261
+ 'odata-spec-violation-key-type': {
262
+ std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
263
+ scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
264
+ },
265
+ 'odata-spec-violation-type': 'Expected element to have a type',
266
+ 'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
267
+ 'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
214
268
  }
215
269
 
216
270
  /**
@@ -1,5 +1,7 @@
1
1
  // Functions and classes for syntax messages
2
2
 
3
+ // See internalDoc/ReportingMessages.md and lib/base/message-registry.js for details.
4
+
3
5
  'use strict';
4
6
 
5
7
  const term = require('../utils/term');
@@ -92,14 +94,17 @@ class CompilationError extends Error {
92
94
 
93
95
  /** @type {boolean} model */
94
96
  this.hasBeenReported = false; // TODO: remove this bin/cdsc.js specifics
95
- // TODO: remove property `model`
97
+ // property `model` is only set with options.attachValidNames:
96
98
  Object.defineProperty( this, 'model', { value: model || undefined, configurable: true } );
97
99
  }
98
100
  toString() { // does not really help -> set message
99
101
  return this.message.includes('\n')
100
102
  ? this.message
101
- : this.message + '\n' + this.errors.map( m => m.toString() ).join('\n');
103
+ : this.message + '\n' + this.messages.map( m => m.toString() ).join('\n');
102
104
  }
105
+ /**
106
+ * @deprecated Use `.messages` instead.
107
+ */
103
108
  get errors() {
104
109
  return this.messages;
105
110
  }
@@ -210,6 +215,7 @@ const severitySpecs = {
210
215
  * @returns {CSN.MessageSeverity}
211
216
  *
212
217
  * TODO: we should pass options as usual
218
+ * TODO: should be part of the returned function
213
219
  */
214
220
  function reclassifiedSeverity( id, severity, severities, moduleName, deprecatedDowngradable ) {
215
221
  const spec = centralMessages[id] || { severity };
@@ -294,6 +300,30 @@ function searchForLocation( model, path ) {
294
300
  return lastLocation;
295
301
  }
296
302
 
303
+ /**
304
+ * Create the `message` functions to emit messages.
305
+ * See internalDoc/ReportingMessages.md for detail
306
+ *
307
+ * @example
308
+ * ```
309
+ * const { createMessageFunctions } = require(‘../base/messages’);
310
+ * function module( …, options ) {
311
+ * const { message, info, throwWithError } = createMessageFunctions( options, moduleName );
312
+ * // [...]
313
+ * message( 'message-id', <location>, <text-arguments>, <severity>, <text> );
314
+ * info( 'message-id', <location>, [<text-arguments>,] <text> );
315
+ * // [...]
316
+ * throwWithError();
317
+ * }
318
+ * ```
319
+ * @param {CSN.Options} [options]
320
+ * @param {string} [moduleName]
321
+ * @param {object} [model=null] the CSN or XSN model, used for convenience
322
+ */
323
+ function createMessageFunctions( options, moduleName, model = null ) {
324
+ return makeMessageFunction( model, options, moduleName, true );
325
+ }
326
+
297
327
  /**
298
328
  * Create the `message` function to emit messages.
299
329
  *
@@ -312,8 +342,9 @@ function searchForLocation( model, path ) {
312
342
  * @param {object} model
313
343
  * @param {CSN.Options} [options]
314
344
  * @param {string} [moduleName]
345
+ * @param {boolean} [throwOnlyWithNew=false] behave like createMessageFunctions
315
346
  */
316
- function makeMessageFunction( model, options = model.options || {}, moduleName = null ) {
347
+ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNew = false ) {
317
348
  // ensure message consistency during runtime with --test-mode
318
349
  if (options.testMode)
319
350
  _check$Init( options );
@@ -327,11 +358,12 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
327
358
  *
328
359
  * @type {CSN.Message[]}
329
360
  */
330
- let messages = options && options.messages || [];
331
-
361
+ let messages = options.messages || [];
362
+ let hasNewError = false;
332
363
  return {
333
- message, error, warning, info, debug,
334
- messages, throwWithError, callTransparently
364
+ message, error, warning, info, debug, messages,
365
+ throwWithError: (throwOnlyWithNew ? throwWithError : throwWithAnyError),
366
+ callTransparently, moduleName,
335
367
  };
336
368
 
337
369
  function _message(id, location, textOrArguments, severity, texts = null) {
@@ -342,7 +374,6 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
342
374
  texts = { std: textOrArguments };
343
375
  textOrArguments = {};
344
376
  }
345
-
346
377
  if (id) {
347
378
  if (options.testMode && !options.$recompile)
348
379
  _check$Consistency( id, moduleName, severity, texts, options )
@@ -360,6 +391,8 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
360
391
  msg.$location.address = { definition };
361
392
 
362
393
  messages.push( msg );
394
+ hasNewError = hasNewError || msg.severity === 'Error' &&
395
+ !(options.testMode && msg.messageId && isDowngradable( msg.messageId, moduleName ));
363
396
  if (!hasMessageArray)
364
397
  console.error( messageString( msg ) );
365
398
  return msg;
@@ -488,6 +521,11 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
488
521
  return _message(id, location, textOrArguments, 'Debug', texts);
489
522
  }
490
523
 
524
+ function throwWithError() {
525
+ if (hasNewError)
526
+ throw new CompilationError( messages, options.attachValidNames && model );
527
+ }
528
+
491
529
  /**
492
530
  * Throws a CompilationError exception if there is at least one error message
493
531
  * in the model's messages after reclassifying existing messages according to
@@ -495,22 +533,20 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
495
533
  * If `--test-mode` is enabled, this function will only throw if the
496
534
  * error *cannot* be downgraded to a warning. This is done to ensure that
497
535
  * developers do not rely on certain errors leading to an exception.
498
- *
499
- * @param {CSN.Message[]} [msgs] Which messages to check for. Default: The ones of
500
- * this makeMessageFunction() scope.
501
536
  */
502
- function throwWithError(msgs = messages) {
503
- if (!msgs || !msgs.length)
537
+ function throwWithAnyError() {
538
+ if (!messages || !messages.length)
504
539
  return;
505
- reclassifyMessagesForModule(msgs, severities, moduleName); // TODO: no, at the beginning of the module
540
+ reclassifyMessagesForModule( messages, severities, moduleName ); // TODO: no, at the beginning of the module
506
541
  const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
507
- if (hasError( msgs, moduleName ))
508
- throw new CompilationError( msgs, options.attachValidNames && model );
542
+ if (hasError( messages, moduleName ))
543
+ throw new CompilationError( messages, options.attachValidNames && model );
509
544
  }
510
545
 
511
546
  /**
512
547
  * Collects all messages during the call of the callback function instead of
513
548
  * storing them in the model. Returns the collected messages.
549
+ * Not yet in use.
514
550
  *
515
551
  * @param {Function} callback
516
552
  * @param {...any} args
@@ -613,6 +649,8 @@ const paramsTransform = {
613
649
  // more complex convenience:
614
650
  names: transformManyWith( quoted ),
615
651
  number: n => n,
652
+ line: l => l,
653
+ col: c => c,
616
654
  literal: l => l,
617
655
  art: transformArg,
618
656
  service: transformArg,
@@ -993,12 +1031,12 @@ function homeSortName( { home, messageId } ) {
993
1031
 
994
1032
  /**
995
1033
  * Removes duplicate messages from the given messages array without destroying
996
- * references to the array.
1034
+ * references to the array, i.e. removes them in-place.
997
1035
  *
998
- * Does NOT keep the original order!
1036
+ * _Note_: Does NOT keep the original order!
999
1037
  *
1000
1038
  * Two messages are the same if they have the same message hash. See messageHash().
1001
- * If one of the two is more precise then it replaces the other.
1039
+ * If one of the two is more precise, then it replaces the other.
1002
1040
  * A message is more precise if it is contained in the other or if
1003
1041
  * the first does not have an endLine/endCol.
1004
1042
  *
@@ -1197,7 +1235,10 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1197
1235
  if (!csnPath[index + 1]) {
1198
1236
  result += select();
1199
1237
  }
1200
- else if (queryProps.includes(csnPath[index + 1]) && !csnPath[index + 2]) {
1238
+ // only print last query prop for paths like
1239
+ // [... 'query', 'SELECT', 'from', 'SELECT', 'elements', 'struct'] -> select:2/element:"struct"
1240
+ // no from in the semantic location in this case
1241
+ else if (queryProps.includes(csnPath[index + 1]) && (!csnPath[index + 2] || query.isOnlySelect)) {
1201
1242
  const clause = csnPath[index + 1];
1202
1243
  result += select();
1203
1244
  result += `/${ clause }`;
@@ -1266,7 +1307,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1266
1307
  if (currentThing.as)
1267
1308
  result += `:${ _quoted(currentThing.as) }`;
1268
1309
  else
1269
- result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.join('.')) }` : '';
1310
+ result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.map(r => r.id ? r.id : r).join('.')) }` : '';
1270
1311
 
1271
1312
  break;
1272
1313
  }
@@ -1380,6 +1421,7 @@ module.exports = {
1380
1421
  messageStringMultiline,
1381
1422
  messageContext,
1382
1423
  searchName,
1424
+ createMessageFunctions,
1383
1425
  makeMessageFunction,
1384
1426
  artName,
1385
1427
  handleMessages,
package/lib/base/model.js CHANGED
@@ -19,7 +19,6 @@ const queryOps = {
19
19
  */
20
20
  const availableBetaFlags = {
21
21
  // enabled by --beta-mode
22
- keylessManagedAssoc: true,
23
22
  foreignKeyConstraints: true,
24
23
  toRename: true,
25
24
  addTextsLanguageAssoc: true,
@@ -28,9 +27,9 @@ const availableBetaFlags = {
28
27
  mapAssocToJoinCardinality: true,
29
28
  ignoreAssocPublishingInUnion: true,
30
29
  nestedProjections: true,
30
+ enableUniversalCsn: true,
31
+ windowFunctions: true,
31
32
  // disabled by --beta-mode
32
- pretransformedCSN: false,
33
- renderSQL: false,
34
33
  nestedServices: false,
35
34
  };
36
35
 
@@ -35,8 +35,33 @@ function validateDefaultValues(member, memberName, prop, path) {
35
35
  * @param {CSN.Path} path Path to the member
36
36
  */
37
37
  function rejectParamDefaultsInHanaCds(member, memberName, prop, path) {
38
- if (member.default && prop === 'params' && this.options.toHana)
38
+ if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
39
39
  this.error(null, path, 'Parameter default values are not supported in SAP HANA CDS');
40
40
  }
41
41
 
42
- module.exports = { validateDefaultValues, rejectParamDefaultsInHanaCds };
42
+ /**
43
+ * For HANA CDS, we render a default for a mixin if the projected entity contains
44
+ * a derived association with a default defined on it. This leads to a deployment error
45
+ * and should be warned about.
46
+ *
47
+ * @param {CSN.Element} member Member to validate
48
+ * @param {string} memberName Name of the member
49
+ * @param {string} prop Property being looped over
50
+ * @param {CSN.Path} path Path to the member
51
+ */
52
+ function warnAboutDefaultOnAssociationForHanaCds(member, memberName, prop, path) {
53
+ const art = this.csn.definitions[path[1]];
54
+ if (!art.query && this.options.transformation === 'hdbcds' && member.target && member.default) {
55
+ this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },
56
+ {
57
+ std: 'Unexpected default defined on association',
58
+ comp: 'Unexpected default defined on composition',
59
+ });
60
+ }
61
+ }
62
+
63
+ module.exports = {
64
+ validateDefaultValues,
65
+ rejectParamDefaultsInHanaCds,
66
+ warnAboutDefaultOnAssociationForHanaCds,
67
+ };
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { forEachMember, forEachMemberRecursively } = require('../model/csnUtils');
4
4
  const { isGeoTypeName } = require('../compiler/builtins');
5
- const { isBetaEnabled } = require('../base/model');
6
5
 
7
6
  // Only to be used with validator.js - a correct `this` value needs to be provided!
8
7
 
@@ -103,15 +102,11 @@ function checkVirtualElement(member) {
103
102
  * @param {CSN.Artifact} art The artifact
104
103
  */
105
104
  function checkManagedAssoc(art) {
106
- forEachMemberRecursively(art, (member, memberName) => {
105
+ forEachMemberRecursively(art, (member) => {
107
106
  if (this.csnUtils.isAssocOrComposition(member.type) &&
108
107
  !isManagedComposition.bind(this)(member)) {
109
108
  if (member.on)
110
109
  return;
111
- if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && (!member.keys || member.keys.length === 0)) {
112
- this.error(null, member.$path, { name: memberName },
113
- `The managed association $(NAME) has no foreign keys`);
114
- }
115
110
  const max = member.cardinality && member.cardinality.max;
116
111
  if (max === '*' || Number(max) > 1) {
117
112
  const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { isBetaEnabled } = require('../base/model');
4
-
5
3
  // Only to be used with validator.js - a correct this value needs to be provided!
6
4
 
7
5
  /**
@@ -20,17 +18,13 @@ function validateForeignKeys(member) {
20
18
 
21
19
  // Declared as arrow-function to keep scope the same (this value)
22
20
  const handleAssociation = (mem) => {
23
- let elementCount = 0;
24
21
  for (let i = 0; i < mem.keys.length; i++) {
25
22
  if (mem.keys[i].ref) {
26
23
  if (!mem.keys[i]._art)
27
24
  continue;
28
25
  // eslint-disable-next-line no-use-before-define
29
26
  checkForItems(mem.keys[i]._art);
30
- elementCount++;
31
27
  }
32
- if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && elementCount === 0)
33
- this.error(null, member.$path, 'Empty structured types/elements must not be used as foreign keys');
34
28
  }
35
29
  };
36
30
 
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+
4
+ /**
5
+ * Trigger a recompilation in case of an association without .keys and without .on
6
+ *
7
+ * @param {CSN.Element} member the element to be checked
8
+ * @param {string} memberName the elements name
9
+ * @param {string} prop which kind of member are we looking at -> only prop "elements"
10
+ */
11
+ function managedWithoutKeys(member, memberName, prop) {
12
+ if (prop === 'elements' && member.target && !member.keys && !member.on) { // trigger recompilation
13
+ throw new Error('Expected association to have either an on-condition or foreign keys.');
14
+ }
15
+ }
16
+
17
+ module.exports = managedWithoutKeys;