@sap/cds-compiler 2.10.4 → 2.11.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 (70) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +4 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +9 -23
  7. package/lib/api/options.js +12 -4
  8. package/lib/api/validate.js +23 -2
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/message-registry.js +10 -2
  12. package/lib/base/messages.js +23 -9
  13. package/lib/base/model.js +5 -4
  14. package/lib/base/optionProcessorHelper.js +56 -22
  15. package/lib/checks/selectItems.js +4 -0
  16. package/lib/checks/unknownMagic.js +6 -3
  17. package/lib/compiler/assert-consistency.js +7 -0
  18. package/lib/compiler/base.js +65 -0
  19. package/lib/compiler/builtins.js +28 -1
  20. package/lib/compiler/checks.js +2 -1
  21. package/lib/compiler/definer.js +58 -91
  22. package/lib/compiler/index.js +16 -4
  23. package/lib/compiler/propagator.js +5 -2
  24. package/lib/compiler/resolver.js +93 -34
  25. package/lib/compiler/shared.js +29 -202
  26. package/lib/compiler/utils.js +173 -0
  27. package/lib/edm/annotations/genericTranslation.js +1 -1
  28. package/lib/edm/csn2edm.js +3 -2
  29. package/lib/edm/edmPreprocessor.js +31 -36
  30. package/lib/edm/edmUtils.js +3 -3
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +17 -1
  33. package/lib/gen/language.tokens +79 -73
  34. package/lib/gen/languageLexer.interp +19 -1
  35. package/lib/gen/languageLexer.js +779 -731
  36. package/lib/gen/languageLexer.tokens +71 -65
  37. package/lib/gen/languageParser.js +4668 -4072
  38. package/lib/json/from-csn.js +10 -10
  39. package/lib/json/to-csn.js +169 -34
  40. package/lib/language/antlrParser.js +11 -0
  41. package/lib/language/genericAntlrParser.js +72 -14
  42. package/lib/language/language.g4 +73 -0
  43. package/lib/main.d.ts +136 -17
  44. package/lib/main.js +3 -1
  45. package/lib/model/api.js +2 -2
  46. package/lib/model/csnRefs.js +108 -31
  47. package/lib/model/csnUtils.js +63 -29
  48. package/lib/model/enrichCsn.js +36 -9
  49. package/lib/model/revealInternalProperties.js +20 -4
  50. package/lib/modelCompare/compare.js +2 -1
  51. package/lib/optionProcessor.js +29 -18
  52. package/lib/render/DuplicateChecker.js +1 -1
  53. package/lib/render/toCdl.js +9 -3
  54. package/lib/render/toHdbcds.js +16 -36
  55. package/lib/render/toSql.js +23 -5
  56. package/lib/transform/db/constraints.js +278 -119
  57. package/lib/transform/db/draft.js +3 -2
  58. package/lib/transform/db/expansion.js +6 -4
  59. package/lib/transform/db/flattening.js +17 -1
  60. package/lib/transform/db/transformExists.js +61 -2
  61. package/lib/transform/db/views.js +438 -0
  62. package/lib/transform/forHanaNew.js +56 -435
  63. package/lib/transform/forOdataNew.js +9 -2
  64. package/lib/transform/localized.js +2 -0
  65. package/lib/transform/transformUtilsNew.js +10 -0
  66. package/lib/transform/translateAssocsToJoins.js +5 -13
  67. package/lib/utils/file.js +5 -3
  68. package/lib/utils/term.js +65 -42
  69. package/lib/utils/timetrace.js +48 -26
  70. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,56 @@
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 2.11.0 - 2021-12-02
11
+
12
+ ### Added
13
+
14
+ - Option `defaultBinaryLength` to set a `length` type facet for all definitions with type `cds.Binary`. This option
15
+ overrides the default binary length in the database backends and is also used as `MaxLength` attribute in Odata.
16
+ - If doc-comments are ignored by the compiler, an info message is now emitted. A doc-comment is ignored,
17
+ if it can't be assigned to an artifact. For example for two subsequent doc-comments, the first doc-comment
18
+ is ignored. To suppress these info messages, explicitly set option `docComment` to `false`.
19
+ - `cdsc`:
20
+ - `cdsc explain list` can now be used to get a list of message IDs with explanation texts.
21
+ - `cdsc` now respects the environment variable `NO_COLOR`. If set, no ANSI escape codes will be used.
22
+ Can be overwritten by `cdsc --color always`.
23
+ - to.sql/hdi: Support SQL Window Functions
24
+ - to.sql/hdi/hdbcds:
25
+ + Support configuration of `$session` and `$user` via option `variableReplacements`.
26
+ + Restricted support for SQL foreign key constraints if option `assertIntegrityType` is set to `"DB"`.
27
+ The behavior of this feature might change in the future.
28
+
29
+ ### Changed
30
+
31
+ - Updated OData vocabularies 'Common' and 'UI'.
32
+ - to.sql/hdi/hdbcds: The default length of `cds.Binary` is set to `5000` similar to `cds.String`.
33
+
34
+ ### Removed
35
+
36
+ - to.hdbcds: Doc comments on view columns are not rendered anymore. Doc comments on string literals will make the deployment fail
37
+ as the SAP HANA CDS compiler concatenates the doc comment with the string literal. Besides that, doc comments on view columns
38
+ are not transported to the database by SAP HANA CDS.
39
+ - to.hdbcds/sql/hdi: Forbid associations in filters after `exists` (except for nested `exists`), as the final behavior is not yet specified.
40
+
41
+ ### Fixed
42
+
43
+ - CSN parser: doc-comment extensions are no longer ignored.
44
+ - Properly check for duplicate annotation definitions.
45
+ - Correctly apply annotations on inherited enum symbols.
46
+ - Correctly apply annotations on elements in an inherited structure array.
47
+ - Fix a bug in API `defaultStringLength` value evaluation.
48
+ - Fix crash if named arguments are used in a function that's inside a `CASE` statement.
49
+ - to.sql/hdi/hdbcds:
50
+ + Properly flatten ad-hoc defined elements in `returns` / `params` of `actions` and `functions`.
51
+ + Correctly handle `*` in non-first position.
52
+ + Correctly handle action return types
53
+ + Correctly handle mixin association named `$self`
54
+ - to.cdl: doc-comments are no longer rendered twice.
55
+ - to.edm(x):
56
+ + Fix a bug in V2/V4 partner ship calculation.
57
+ + Remove warning of unknown types for Open Types in `@Core.Dictionary`.
58
+ + An empty CSN no longer results in a JavaScript type error
59
+
10
60
  ## Version 2.10.4 - 2021-11-05
11
61
 
12
62
  ### Fixed
package/bin/cdsc.js CHANGED
@@ -26,8 +26,10 @@ const path = require('path');
26
26
  const { reveal } = require('../lib/model/revealInternalProperties');
27
27
  const enrichCsn = require('../lib/model/enrichCsn');
28
28
  const { optionProcessor } = require('../lib/optionProcessor');
29
- const { explainMessage, hasMessageExplanation, sortMessages } = require('../lib/base/messages');
30
- const term = require('../lib/utils/term');
29
+ const {
30
+ explainMessage, hasMessageExplanation, sortMessages, messageIdsWithExplanation,
31
+ } = require('../lib/base/messages');
32
+ const { term } = require('../lib/utils/term');
31
33
  const { splitLines } = require('../lib/utils/file');
32
34
  const { addLocalizationViews } = require('../lib/transform/localized');
33
35
  const { availableBetaFlags } = require('../lib/base/model');
@@ -52,9 +54,11 @@ function remapCmdOptions(options, cmdOptions) {
52
54
  options.sqlMapping = value;
53
55
  break;
54
56
  case 'user':
55
- if (!options.magicVars)
56
- options.magicVars = {};
57
- options.magicVars.user = value;
57
+ if (!options.variableReplacements)
58
+ options.variableReplacements = {};
59
+ if (!options.variableReplacements.$user)
60
+ options.variableReplacements.$user = {};
61
+ options.variableReplacements.$user.id = value;
58
62
  break;
59
63
  case 'dialect':
60
64
  options.sqlDialect = value;
@@ -63,9 +67,11 @@ function remapCmdOptions(options, cmdOptions) {
63
67
  options.odataVersion = value;
64
68
  break;
65
69
  case 'locale':
66
- if (!options.magicVars)
67
- options.magicVars = {};
68
- options.magicVars.locale = value;
70
+ if (!options.variableReplacements)
71
+ options.variableReplacements = {};
72
+ if (!options.variableReplacements.$user)
73
+ options.variableReplacements.$user = {};
74
+ options.variableReplacements.$user.locale = value;
69
75
  break;
70
76
  default:
71
77
  options[key] = value;
@@ -124,8 +130,6 @@ try {
124
130
  global.cds = {};
125
131
  global.cds.home = cmdLine.options.cdsHome;
126
132
  }
127
- // Default color mode is 'auto'
128
- term.useColor(cmdLine.options.color || 'auto');
129
133
 
130
134
  // Set default command if required
131
135
  cmdLine.command = cmdLine.command || 'toCsn';
@@ -137,10 +141,7 @@ try {
137
141
  if (cmdLine.command === 'parseCdl') {
138
142
  cmdLine.command = 'toCsn';
139
143
  cmdLine.options.parseCdl = true;
140
- if (cmdLine.args.files.length > 1) {
141
- const err = `'parseCdl' expects exactly one file! ${cmdLine.args.files.length} provided.`;
142
- displayUsage(err, optionProcessor.commands.parseCdl.helpText, 2);
143
- }
144
+ cmdLine.args.files = [ cmdLine.args.file ];
144
145
  }
145
146
 
146
147
  if (cmdLine.options.directBackend)
@@ -155,6 +156,14 @@ try {
155
156
  });
156
157
  }
157
158
 
159
+ // remap string values for `assertIntegrity` option to boolean
160
+ if (cmdLine.options.assertIntegrity &&
161
+ cmdLine.options.assertIntegrity === 'true' ||
162
+ cmdLine.options.assertIntegrity === 'false'
163
+ )
164
+ cmdLine.options.assertIntegrity = Boolean(cmdLine.options.assertIntegrity);
165
+
166
+
158
167
  // Enable all beta-flags if betaMode is set to true
159
168
  if (cmdLine.options.betaMode)
160
169
  cmdLine.options.beta = availableBetaFlags;
@@ -231,6 +240,8 @@ function executeCommandLine(command, options, args) {
231
240
  if (options.out && options.out !== '-' && !fs.existsSync(options.out))
232
241
  fs.mkdirSync(options.out);
233
242
 
243
+ // Default color mode is 'auto'
244
+ const colorTerm = term(options.color || 'auto');
234
245
 
235
246
  // Add implementation functions corresponding to commands here
236
247
  const commands = {
@@ -393,14 +404,15 @@ function executeCommandLine(command, options, args) {
393
404
  }
394
405
 
395
406
  function explain() {
396
- if (args.length !== 1)
397
- displayUsage('Command \'explain\' expects exactly one message-id.', optionProcessor.commands.explain.helpText, 2);
407
+ if (args.messageId === 'list') {
408
+ console.log(messageIdsWithExplanation().join('\n'));
409
+ throw new ProcessExitError(0);
410
+ }
398
411
 
399
- const id = args.files[0];
400
- if (!hasMessageExplanation(id))
401
- console.error(`Message '${id}' does not have an explanation!`);
412
+ if (!hasMessageExplanation(args.messageId))
413
+ console.error(`Message '${args.messageId}' does not have an explanation!`);
402
414
  else
403
- console.log(explainMessage(id));
415
+ console.log(explainMessage(args.messageId));
404
416
  }
405
417
 
406
418
  // Display error messages in `err` resulting from a compilation. Also set
@@ -464,16 +476,20 @@ function executeCommandLine(command, options, args) {
464
476
  hasAtLeastOneExplanation = hasAtLeastOneExplanation || main.hasMessageExplanation(msg.messageId);
465
477
  const name = msg.location && msg.location.file;
466
478
  const fullFilePath = name ? path.resolve('', name) : undefined;
467
- const context = fullFilePath && sourceLines(fullFilePath);
479
+ const context = fullFilePath ? sourceLines(fullFilePath) : [];
468
480
  log(main.messageStringMultiline(msg, {
469
- normalizeFilename, noMessageId: !options.showMessageId, withLineSpacer: true, hintExplanation: true,
481
+ normalizeFilename,
482
+ noMessageId: !options.showMessageId,
483
+ withLineSpacer: true,
484
+ hintExplanation: true,
485
+ color: options.color,
470
486
  }));
471
487
  if (context)
472
- log(main.messageContext(context, msg));
488
+ log(main.messageContext(context, msg, { color: options.color }));
473
489
  log(); // newline
474
490
  });
475
491
  if (options.showMessageId && hasAtLeastOneExplanation)
476
- log(`${term.help('help')}: Messages marked with '…' have an explanation text. Use \`cdsc explain <message-id>\` for a more detailed error description.`);
492
+ log(`${colorTerm.help('help')}: Messages marked with '…' have an explanation text. Use \`cdsc explain <message-id>\` for a more detailed error description.`);
477
493
  }
478
494
  return model;
479
495
  }
@@ -549,10 +565,11 @@ function executeCommandLine(command, options, args) {
549
565
  }
550
566
 
551
567
  function catchErrors(err) {
568
+ // @ts-ignore
552
569
  if (err instanceof Error && err.hasBeenReported)
553
570
  return;
554
571
  console.error( '' );
555
- console.error( 'INTERNAL ERROR: %s', err );
572
+ console.error( 'INTERNAL ERROR:' );
556
573
  console.error( util.inspect(err, false, null) );
557
574
  console.error( '' );
558
575
  process.exitCode = 70;
package/bin/cdsse.js CHANGED
@@ -11,6 +11,7 @@
11
11
  // Planned are: gotoDefinition, highlight (for syntax highighting).
12
12
 
13
13
  /* eslint no-console:off */
14
+ // @ts-nocheck
14
15
 
15
16
  'use strict';
16
17
 
@@ -8,6 +8,10 @@ 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 2.11.0
12
+
13
+ ### Removed `foreignKeyConstraints`
14
+
11
15
  ## Version 2.10.4
12
16
 
13
17
  ### Fixed `nestedProjections`
@@ -14,6 +14,8 @@
14
14
  "jsdoc"
15
15
  ],
16
16
  "rules": {
17
+ // Does not recognize TS types
18
+ "jsdoc/no-undefined-types": "off",
17
19
  // eslint-plugin-jsdoc warning
18
20
  "jsdoc/require-property": 0,
19
21
  // =airbnb, >eslint:
package/lib/api/main.js CHANGED
@@ -14,7 +14,7 @@ const { toSqlDdl } = require('../render/toSql');
14
14
  const { compareModels } = require('../modelCompare/compare');
15
15
  const sortViews = require('../model/sortViews');
16
16
  const { getResultingName } = require('../model/csnUtils');
17
- const timetrace = require('../utils/timetrace');
17
+ const { timetrace } = require('../utils/timetrace');
18
18
  const { transformForHanaWithCsn } = require('../transform/forHanaNew');
19
19
 
20
20
  /**
@@ -135,7 +135,7 @@ function odataInternal(csn, internalOptions) {
135
135
  * Return a odata-transformed CSN
136
136
  *
137
137
  * @param {CSN.Model} csn Clean input CSN
138
- * @param {oDataOptions} [options={}] Options
138
+ * @param {ODataOptions} [options={}] Options
139
139
  * @returns {oDataCSN} Return an oData-pre-processed CSN
140
140
  */
141
141
  function odata(csn, options = {}) {
@@ -454,7 +454,7 @@ function hdbcds(csn, options = {}) {
454
454
  * Generate a edm document for the given service
455
455
  *
456
456
  * @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
457
- * @param {oDataOptions} [options={}] Options
457
+ * @param {ODataOptions} [options={}] Options
458
458
  * @returns {edm} The JSON representation of the service
459
459
  */
460
460
  function edm(csn, options = {}) {
@@ -484,7 +484,7 @@ edm.all = edmall;
484
484
  * Generate edm documents for all services
485
485
  *
486
486
  * @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
487
- * @param {oDataOptions} [options={}] Options
487
+ * @param {ODataOptions} [options={}] Options
488
488
  * @returns {edms} { <service>:<JSON representation>, ...}
489
489
  */
490
490
  function edmall(csn, options = {}) {
@@ -516,7 +516,7 @@ function edmall(csn, options = {}) {
516
516
  * Generate a edmx document for the given service
517
517
  *
518
518
  * @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
519
- * @param {oDataOptions} [options={}] Options
519
+ * @param {ODataOptions} [options={}] Options
520
520
  * @returns {edmx} The XML representation of the service
521
521
  */
522
522
  function edmx(csn, options = {}) {
@@ -547,7 +547,7 @@ edmx.all = edmxall;
547
547
  * Generate edmx documents for all services
548
548
  *
549
549
  * @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
550
- * @param {oDataOptions} [options={}] Options
550
+ * @param {ODataOptions} [options={}] Options
551
551
  * @returns {edmxs} { <service>:<XML representation>, ...}
552
552
  */
553
553
  function edmxall(csn, options = {}) {
@@ -709,20 +709,6 @@ function publishCsnProcessor( processor, _name ) {
709
709
  * @property {Array} [messages] Allows collecting all messages in the options instead of printing them to stderr.
710
710
  */
711
711
 
712
- /**
713
- * Options available for all oData-based functions
714
- *
715
- * @typedef {object} oDataOptions
716
- * @property {object} [beta] Enable experimental features - not for productive use!
717
- * @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities
718
- * @property {Map<string, number>} [severities={}] Map of message-id and severity that allows setting the severity for the given message
719
- * @property {Array} [messages] Allows collecting all messages in the options instead of printing them to stderr.
720
- * @property {oDataVersion} [odataVersion='v4'] Odata version to use
721
- * @property {oDataFormat} [odataFormat='flat'] Wether to generate oData as flat or as structured. Structured only with v4.
722
- * @property {NamingMode} [sqlMapping='plain'] Naming mode to use
723
- * @property {string} [service] If a single service is to be rendered
724
- */
725
-
726
712
  /**
727
713
  * Options available for to.hdi
728
714
  *
@@ -753,9 +739,9 @@ function publishCsnProcessor( processor, _name ) {
753
739
  * @typedef {object} sqlOptions
754
740
  * @property {NamingMode} [sqlMapping='plain'] Naming mode to use
755
741
  * @property {SQLDialect} [sqlDialect='sqlite'] SQL dialect to use
756
- * @property {object} [magicVars] Object containing values for magic variables like "$user"
757
- * @property {string} [magicVars.locale] Value for the "$user.locale" in "sqlite" dialect
758
- * @property {string} [magicVars.user] Value for the "$user" variable in "sqlite" dialect
742
+ * @property {object} [variableReplacements] Object containing values for magic variables like "$user"
743
+ * @property {string} [variableReplacements.$user.locale] Value for the "$user.locale" variable
744
+ * @property {string} [variableReplacements.$user.id] Value for the "$userid" variable
759
745
  * @property {object} [beta] Enable experimental features - not for productive use!
760
746
  * @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities
761
747
  * @property {Map<string, number>} [severities={}] Map of message-id and severity that allows setting the severity for the given message
@@ -15,6 +15,7 @@ const publicOptionsNewAPI = [
15
15
  'severities',
16
16
  'messages',
17
17
  'withLocations',
18
+ 'defaultBinaryLength',
18
19
  'defaultStringLength',
19
20
  'csnFlavor',
20
21
  // DB
@@ -23,7 +24,8 @@ const publicOptionsNewAPI = [
23
24
  'sqlChangeMode',
24
25
  'allowCsnDowngrade',
25
26
  'joinfk',
26
- 'magicVars',
27
+ 'magicVars', // deprecated
28
+ 'variableReplacements',
27
29
  // ODATA
28
30
  'odataVersion',
29
31
  'odataFormat',
@@ -47,9 +49,10 @@ const privateOptions = [
47
49
  'traceParserAmb',
48
50
  'testMode',
49
51
  'testSortCsn',
50
- 'constraintsNotEnforced',
51
- 'constraintsNotValidated',
52
- 'skipDbConstraints',
52
+ 'integrityNotEnforced',
53
+ 'integrityNotValidated',
54
+ 'assertIntegrity',
55
+ 'assertIntegrityType',
53
56
  'noRecompile',
54
57
  'internalMsg',
55
58
  'disableHanaComments', // in case of issues with hana comment rendering
@@ -109,6 +112,10 @@ function translateOptions(input = {}, defaults = {}, hardRequire = {},
109
112
  mapToOldNames(optionName, optionValue);
110
113
  }
111
114
 
115
+ // Convenience for $user -> $user.id replacement
116
+ if (options.variableReplacements && options.variableReplacements.$user && typeof options.variableReplacements.$user === 'string')
117
+ options.variableReplacements.$user = { id: options.variableReplacements.$user };
118
+
112
119
  /**
113
120
  * Map a new-style option to it's old format
114
121
  *
@@ -130,6 +137,7 @@ function translateOptions(input = {}, defaults = {}, hardRequire = {},
130
137
  case 'sqlMapping':
131
138
  options.names = optionValue;
132
139
  break;
140
+ // No need to remap variableReplacements here - we use the new mechanism with that directly
133
141
  case 'magicVars':
134
142
  if (optionValue.user)
135
143
  options.user = optionValue.user;
@@ -70,6 +70,14 @@ const validators = {
70
70
  return val === null ? val : `type ${ typeof val }`;
71
71
  },
72
72
  },
73
+ // TODO: Maybe do a deep validation of the whole object with leafs?
74
+ variableReplacements: {
75
+ validate: val => val !== null && typeof val === 'object' && !Array.isArray(val),
76
+ expected: () => 'type object',
77
+ found: (val) => {
78
+ return val === null ? val : `type ${ typeof val }`;
79
+ },
80
+ },
73
81
  messages: {
74
82
  validate: val => Array.isArray(val),
75
83
  expected: () => 'type array',
@@ -89,10 +97,17 @@ const validators = {
89
97
  expected: () => 'type array of string',
90
98
  found: val => `type ${ typeof val }`,
91
99
  },
100
+ defaultBinaryLength: {
101
+ validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
102
+
103
+ expected: () => 'Integer literal',
104
+ found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
105
+ },
92
106
  defaultStringLength: {
93
- validate: val => Number.isInteger(val),
107
+ validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
108
+
94
109
  expected: () => 'Integer literal',
95
- found: val => `type ${ typeof val }`,
110
+ found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
96
111
  },
97
112
  csnFlavor: {
98
113
  validate: val => typeof val === 'string',
@@ -102,6 +117,12 @@ const validators = {
102
117
  dictionaryPrototype: {
103
118
  validate: () => true,
104
119
  },
120
+ assertIntegrity: {
121
+ validate: val => typeof val === 'string' && val === 'individual' || typeof val === 'boolean',
122
+ expected: () => 'a boolean or a string with value \'individual\'',
123
+ found: val => (typeof val === 'string' ? val : `type ${ typeof val }`),
124
+ },
125
+ assertIntegrityType: generateStringValidator([ 'DB', 'RT' ]),
105
126
  };
106
127
 
107
128
  const allCombinationValidators = {
package/lib/backends.js CHANGED
@@ -14,7 +14,7 @@ const { csn2edm, csn2edmAll } = require('./edm/csn2edm');
14
14
  const { mergeOptions } = require('./model/csnUtils');
15
15
  const { isBetaEnabled } = require('./base/model');
16
16
  const { optionProcessor } = require('./optionProcessor')
17
- const timetrace = require('./utils/timetrace');
17
+ const { timetrace } = require('./utils/timetrace');
18
18
  const { makeMessageFunction } = require('./base/messages');
19
19
  const { forEachDefinition } = require('./model/csnUtils');
20
20
 
@@ -92,9 +92,9 @@ function toOdataWithCsn(csn, options) {
92
92
  // Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)
93
93
  // using 'options'
94
94
  function preparedCsnToEdmx(csn, service, options) {
95
- let edmx = csn2edm(csn, service, options).toXML('all');
95
+ const e = csn2edm(csn, service, options)
96
96
  return {
97
- edmx,
97
+ edmx: (e ? e.toXML('all') : undefined)
98
98
  };
99
99
  }
100
100
 
@@ -115,9 +115,9 @@ function preparedCsnToEdmxAll(csn, options) {
115
115
  function preparedCsnToEdm(csn, service, options) {
116
116
  // Merge options; override OData version as edm json is always v4
117
117
  options = mergeOptions(options, { toOdata : { version : 'v4' }});
118
- const edmj = csn2edm(csn, service, options).toJSON();
118
+ const e = csn2edm(csn, service, options);
119
119
  return {
120
- edmj,
120
+ edmj: (e ? e.toJSON() : undefined)
121
121
  };
122
122
  }
123
123
 
@@ -405,9 +405,6 @@ function toRenameWithCsn(csn, options) {
405
405
 
406
406
  function alterConstraintsWithCsn(csn, options) {
407
407
  const { error } = makeMessageFunction(csn, options, 'manageConstraints');
408
- // Requires beta mode
409
- if (!isBetaEnabled(options, 'foreignKeyConstraints'))
410
- error(null, null, 'ALTER TABLE statements for adding/modifying referential constraints are only available in beta mode');
411
408
 
412
409
  const {
413
410
  drop, alter, names, src, violations
@@ -420,6 +417,10 @@ function alterConstraintsWithCsn(csn, options) {
420
417
  dialect: 'hana',
421
418
  names: names || 'plain'
422
419
  }
420
+
421
+ // Of course we want the database constraints
422
+ options.assertIntegrityType = 'DB';
423
+
423
424
  const transformedOptions = transformSQLOptions(csn, options);
424
425
  const mergedOptions = mergeOptions(transformedOptions.options, { forHana : transformedOptions.forHanaOptions });
425
426
  const forSqlCsn = transformForHanaWithCsn(csn, mergedOptions, 'to.sql');
@@ -28,6 +28,7 @@ function dictAdd( dict, name, entry, duplicateCallback ) {
28
28
  }
29
29
 
30
30
  function dictForEach( dict, callback ) {
31
+ // TODO: probably define an extra dictForEachArray()
31
32
  for (const name in dict) {
32
33
  const entry = dict[name];
33
34
  if (Array.isArray(entry)) {
@@ -35,7 +36,7 @@ function dictForEach( dict, callback ) {
35
36
  }
36
37
  else {
37
38
  callback( entry );
38
- if (Array.isArray(entry.$duplicates))
39
+ if (Array.isArray( entry.$duplicates ))
39
40
  entry.$duplicates.forEach( callback );
40
41
  }
41
42
  }
@@ -118,6 +118,7 @@ const centralMessages = {
118
118
  'syntax-anno-after-params': { severity: 'Error', configurableFor: true }, // does not hurt
119
119
  'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
120
120
  'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
121
+ 'syntax-csn-expected-length': { severity: 'Error' },
121
122
  'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
122
123
  'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
123
124
  'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
@@ -133,6 +134,7 @@ const centralMessages = {
133
134
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
134
135
  'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
135
136
  'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
137
+ 'odata-spec-violation-no-key': { severity: 'Warning' },
136
138
  'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
137
139
  'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
138
140
  'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
@@ -160,6 +162,11 @@ const centralMessageTexts = {
160
162
  $tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
161
163
  mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
162
164
  },
165
+ 'syntax-csn-expected-length': {
166
+ std: 'Expected array in $(PROP) to have at least $(N) items',
167
+ one: 'Expected array in $(PROP) to have at least one item',
168
+ suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
169
+ },
163
170
  'ref-undefined-def': {
164
171
  std: 'Artifact $(ART) has not been found',
165
172
  // TODO: proposal 'No definition of $(NAME) found',
@@ -203,13 +210,13 @@ const centralMessageTexts = {
203
210
  'duplicate-definition': {
204
211
  std: 'Duplicate definition of $(NAME)',
205
212
  absolute: 'Duplicate definition of artifact $(NAME)',
206
- namespace: 'Other definition blocks $(NAME) for namespace name',
213
+ annotation: 'Duplicate definition of annotation vocabulary $(NAME)',
207
214
  element: 'Duplicate definition of element $(NAME)',
208
215
  enum: 'Duplicate definition of enum $(NAME)',
209
216
  key: 'Duplicate definition of key $(NAME)',
210
217
  action: 'Duplicate definition of action or function $(NAME)',
211
218
  param: 'Duplicate definition of parameter $(NAME)',
212
- $tableAlias: 'Duplicate definition of table alias or mixin $(NAME)',
219
+ alias: 'Duplicate definition of table alias or mixin $(NAME)',
213
220
  },
214
221
 
215
222
  // TODO: rename to ref-expected-XYZ
@@ -262,6 +269,7 @@ const centralMessageTexts = {
262
269
  std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
263
270
  scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
264
271
  },
272
+ 'odata-spec-violation-no-key': 'Expected entity to have a primary key',
265
273
  'odata-spec-violation-type': 'Expected element to have a type',
266
274
  'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
267
275
  'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
@@ -4,7 +4,7 @@
4
4
 
5
5
  'use strict';
6
6
 
7
- const term = require('../utils/term');
7
+ const { term } = require('../utils/term');
8
8
  const { locationString } = require('./location');
9
9
  const { isDeprecatedEnabled } = require('./model');
10
10
  const { centralMessages, centralMessageTexts } = require('./message-registry');
@@ -15,6 +15,8 @@ const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
+ // term instance for messages
19
+ const colorTerm = term();
18
20
 
19
21
  // Functions ensuring message consistency during runtime with --test-mode
20
22
 
@@ -853,19 +855,26 @@ function messageHash(msg) {
853
855
  *
854
856
  * Example:
855
857
  * ```txt
856
- * Error[message-id]: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
858
+ * Error[message-id]: Can't find type `nu` in this scope
857
859
  * |
858
- * <source>.cds:3:11, at entity:“E”
860
+ * <source>.cds:3:11, at entity:“E”/element:“e
859
861
  * ```
862
+ *
860
863
  * @param {CSN.Message} err
861
864
  * @param {object} [config = {}]
862
865
  * @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
863
866
  * @param {boolean} [config.noMessageId]
864
867
  * @param {boolean} [config.hintExplanation] If true, messages with explanations will get a "…" marker.
865
- * @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
868
+ * @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
869
+ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the severity. If false, no
870
+ * coloring will be used. If 'auto', we will decide based on certain factors such
871
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
872
+ * unset.
866
873
  * @returns {string}
867
874
  */
868
875
  function messageStringMultiline( err, config = {} ) {
876
+ colorTerm.changeColorMode(config ? config.color : 'auto');
877
+
869
878
  const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
870
879
  const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
871
880
  const home = !err.home ? '' : ('at ' + err.home);
@@ -878,7 +887,7 @@ function messageStringMultiline( err, config = {} ) {
878
887
  location += ', '
879
888
  }
880
889
  else if (!home)
881
- return term.asSeverity(severity, severity + msgId) + ' ' + err.message;
890
+ return colorTerm.severity(severity, severity + msgId) + ' ' + err.message;
882
891
 
883
892
  let lineSpacer = '';
884
893
  if (config.withLineSpacer) {
@@ -886,8 +895,7 @@ function messageStringMultiline( err, config = {} ) {
886
895
  lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
887
896
  }
888
897
 
889
- // TODO: use ':' before text
890
- return term.asSeverity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
898
+ return colorTerm.severity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
891
899
  }
892
900
 
893
901
  /**
@@ -901,9 +909,15 @@ function messageStringMultiline( err, config = {} ) {
901
909
  * @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
902
910
  * from `lib/utils/file.js`
903
911
  * @param {CSN.Message} err Error object containing all details like line, message, etc.
912
+ * @param {object} [config = {}]
913
+ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
914
+ * coloring will be used. If 'auto', we will decide based on certain factors such
915
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
916
+ * unset.
904
917
  * @returns {string}
905
918
  */
906
- function messageContext(sourceLines, err) {
919
+ function messageContext(sourceLines, err, config) {
920
+ colorTerm.changeColorMode(config ? config.color : 'auto');
907
921
  const MAX_COL_LENGTH = 100;
908
922
 
909
923
  const loc = err.$location;
@@ -955,7 +969,7 @@ function messageContext(sourceLines, err) {
955
969
  // Indicate that the error is further to the right.
956
970
  if (endColumn === MAX_COL_LENGTH)
957
971
  highlighter = highlighter.replace(' ^', '..^');
958
- msg += indent + '| ' + term.asSeverity(severity, highlighter);
972
+ msg += indent + '| ' + colorTerm.severity(severity, highlighter);
959
973
 
960
974
  } else if (maxLine !== endLine) {
961
975
  // error spans more lines which we don't print