@sap/cds-compiler 3.1.2 → 3.4.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 (117) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/bin/cdsc.js +4 -2
  3. package/doc/CHANGELOG_BETA.md +35 -0
  4. package/lib/api/main.js +153 -29
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/error.js +2 -2
  8. package/lib/base/keywords.js +106 -24
  9. package/lib/base/message-registry.js +177 -79
  10. package/lib/base/messages.js +78 -57
  11. package/lib/base/model.js +2 -1
  12. package/lib/checks/actionsFunctions.js +1 -1
  13. package/lib/checks/annotationsOData.js +2 -2
  14. package/lib/checks/arrayOfs.js +15 -7
  15. package/lib/checks/cdsPersistence.js +1 -1
  16. package/lib/checks/checkForTypes.js +53 -0
  17. package/lib/checks/defaultValues.js +4 -2
  18. package/lib/checks/elements.js +81 -6
  19. package/lib/checks/foreignKeys.js +12 -13
  20. package/lib/checks/invalidTarget.js +10 -11
  21. package/lib/checks/managedInType.js +21 -15
  22. package/lib/checks/nullableKeys.js +1 -1
  23. package/lib/checks/onConditions.js +9 -9
  24. package/lib/checks/parameters.js +23 -0
  25. package/lib/checks/queryNoDbArtifacts.js +1 -1
  26. package/lib/checks/selectItems.js +1 -1
  27. package/lib/checks/sql-snippets.js +12 -10
  28. package/lib/checks/types.js +2 -2
  29. package/lib/checks/utils.js +17 -7
  30. package/lib/checks/validator.js +36 -14
  31. package/lib/compiler/assert-consistency.js +21 -13
  32. package/lib/compiler/builtins.js +8 -0
  33. package/lib/compiler/checks.js +57 -40
  34. package/lib/compiler/define.js +139 -69
  35. package/lib/compiler/extend.js +319 -50
  36. package/lib/compiler/finalize-parse-cdl.js +14 -9
  37. package/lib/compiler/kick-start.js +2 -35
  38. package/lib/compiler/populate.js +111 -68
  39. package/lib/compiler/propagator.js +5 -3
  40. package/lib/compiler/resolve.js +71 -108
  41. package/lib/compiler/shared.js +82 -54
  42. package/lib/compiler/tweak-assocs.js +26 -14
  43. package/lib/compiler/utils.js +13 -2
  44. package/lib/edm/annotations/genericTranslation.js +10 -7
  45. package/lib/edm/csn2edm.js +11 -11
  46. package/lib/edm/edm.js +17 -9
  47. package/lib/edm/edmPreprocessor.js +53 -30
  48. package/lib/edm/edmUtils.js +7 -2
  49. package/lib/gen/Dictionary.json +14 -0
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -2
  52. package/lib/gen/languageParser.js +4312 -4186
  53. package/lib/inspect/inspectModelStatistics.js +1 -1
  54. package/lib/inspect/inspectPropagation.js +23 -9
  55. package/lib/json/csnVersion.js +13 -13
  56. package/lib/json/from-csn.js +161 -172
  57. package/lib/json/to-csn.js +70 -10
  58. package/lib/language/.eslintrc.json +4 -0
  59. package/lib/language/antlrParser.js +8 -11
  60. package/lib/language/docCommentParser.js +1 -2
  61. package/lib/language/errorStrategy.js +54 -27
  62. package/lib/language/genericAntlrParser.js +140 -93
  63. package/lib/language/language.g4 +57 -33
  64. package/lib/language/multiLineStringParser.js +75 -63
  65. package/lib/main.d.ts +3 -6
  66. package/lib/main.js +1 -0
  67. package/lib/model/.eslintrc.json +13 -0
  68. package/lib/model/api.js +4 -2
  69. package/lib/model/csnRefs.js +78 -50
  70. package/lib/model/csnUtils.js +272 -222
  71. package/lib/model/enrichCsn.js +41 -31
  72. package/lib/model/revealInternalProperties.js +61 -57
  73. package/lib/model/sortViews.js +35 -31
  74. package/lib/modelCompare/compare.js +52 -18
  75. package/lib/modelCompare/filter.js +83 -0
  76. package/lib/optionProcessor.js +10 -1
  77. package/lib/render/manageConstraints.js +11 -7
  78. package/lib/render/toCdl.js +151 -106
  79. package/lib/render/toHdbcds.js +8 -6
  80. package/lib/render/toRename.js +4 -4
  81. package/lib/render/toSql.js +17 -7
  82. package/lib/render/utils/common.js +27 -9
  83. package/lib/render/utils/sql.js +5 -5
  84. package/lib/sql-identifier.js +7 -0
  85. package/lib/transform/db/applyTransformations.js +32 -3
  86. package/lib/transform/db/assertUnique.js +27 -38
  87. package/lib/transform/db/expansion.js +92 -41
  88. package/lib/transform/db/flattening.js +1 -1
  89. package/lib/transform/db/temporal.js +3 -1
  90. package/lib/transform/db/transformExists.js +8 -2
  91. package/lib/transform/db/views.js +42 -13
  92. package/lib/transform/draft/db.js +2 -2
  93. package/lib/transform/forOdataNew.js +10 -7
  94. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
  95. package/lib/transform/localized.js +29 -20
  96. package/lib/transform/odata/toFinalBaseType.js +8 -11
  97. package/lib/transform/odata/typesExposure.js +2 -1
  98. package/lib/transform/parseExpr.js +245 -0
  99. package/lib/transform/transformUtilsNew.js +122 -51
  100. package/lib/transform/translateAssocsToJoins.js +17 -16
  101. package/lib/utils/moduleResolve.js +5 -5
  102. package/lib/utils/objectUtils.js +3 -3
  103. package/lib/utils/term.js +5 -5
  104. package/package.json +2 -2
  105. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  106. package/share/messages/check-proper-type-of.md +4 -4
  107. package/share/messages/check-proper-type.md +2 -2
  108. package/share/messages/duplicate-autoexposed.md +4 -4
  109. package/share/messages/extend-repeated-intralayer.md +4 -5
  110. package/share/messages/extend-unrelated-layer.md +4 -4
  111. package/share/messages/message-explanations.json +3 -1
  112. package/share/messages/redirected-to-ambiguous.md +7 -6
  113. package/share/messages/redirected-to-complex.md +63 -0
  114. package/share/messages/redirected-to-unrelated.md +6 -5
  115. package/share/messages/rewrite-not-supported.md +4 -4
  116. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
  117. package/share/messages/wildcard-excluding-one.md +37 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,102 @@
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.4.0 - 2022-10-26
11
+
12
+ ### Added
13
+
14
+ - to.sql: Add support for sql dialect `h2`, which renders SQL for H2 2.x
15
+
16
+ ### Fixed
17
+
18
+ - Properly report an error for bare `$self` references,
19
+ except in the `on` condition of unmanaged associations.
20
+ - Do not dump with references to CDS variables like `$now` in `expand`/`inline`.
21
+ - Properly report an error when trying to `cast` a column to an association.
22
+ - to.cdl: Identifiers that are always keywords in special functions are now escaped.
23
+ - to.edm(x):
24
+ + Nested annotation was not applied if outer annotation has value zero.
25
+ + Fix `AppliesTo=ComplexType, TypeDefinition` term definition directive.
26
+ - to.sql/hdi/hdbcds:
27
+ + Properly report an error for `exists` with `$self.managed-association`
28
+ + For sql dialect `hana`, add an implicit alias when using `:param` in the select list
29
+ + Handle `$self` and magic variables during expansion of nested projections
30
+
31
+ ## Version 3.3.2 - 2022-09-30
32
+
33
+ ### Fixed
34
+
35
+ - to.edm(x): Set `Scale` (V4) or `@sap:variable-scale` (V2) attributes correctly when overwriting `cds.Decimal`
36
+ with `@odata.Scale`.
37
+ - to.sql: For dialect `postgres`, add braces around `$now`, `$at.from` and `$at.to`.
38
+
39
+ ## Version 3.3.0 - 2022-09-29
40
+
41
+ ### Added
42
+
43
+ - Nested projections can be used without `beta` option:
44
+ + Support `expand`: columns can look like `assoc_or_struct_or_tabalias { col_expression1, … }`,
45
+ `longer.ref as name { *, … } excluding { … }`, `{ col_expression1 as sub1, … } as name`, etc.
46
+ + Support `inline`: columns can look like `assoc_or_struct_or_tabalias.{ col_expression1, … }`,
47
+ `longer.ref[filter = condition].{ *, … } excluding { … }`, `assoc_or_struct_or_tabalias.*`, etc.
48
+ - to.sql/hdi/hdbcds/edm(x)/for.odata: Allow to structure comparison against `is [not] null`.
49
+ - to.sql: Support dialect `postgres` - generates SQL intended for PostgreSQL. Not supported are `cds.hana` data types and views with parameters.
50
+
51
+ ### Changed
52
+
53
+ - A valid redirection target does not depend on parameters anymore. This
54
+ change could induce a redirection error, which could easily solved by assigning
55
+ `@cds.redirection.target: false` to the entity with “non-matching” parameters.
56
+ - Properly issue an error when projecting associations with parameter
57
+ references in the `on` condition. Before this change, the compiler dumped
58
+ when projecting such an association in a view on top.
59
+ - Update OData vocabularies 'Capabilities', 'Common', 'UI'.
60
+ - to.cdl:
61
+ + Extensions are now always put into property `model` of `to.cdl()`s result.
62
+ + Actions on views and projections are now rendered as part of the definition, instead of an extension.
63
+ - to.edm(x): `@Capabilities` 'pull up' supports all counterpart properties of `@Capabilities.NavigationPropertyRestriction`
64
+ except for properties `NavigationProperty` and `Navigability`.
65
+ - to.hdi: Updated list of `keywords` which must be quoted in naming mode `plain`.
66
+ - to.sql/hdi/hdbcds/edm(x)/for.odata: Reject structure comparison with operators `<,>,<=,>=`. Message id `expr-unexpected-operator`
67
+ is downgradable to a warning.
68
+
69
+ ### Fixed
70
+
71
+ - Do not issue a warning anymore when adding elements via multiple `extend` statements in the same file.
72
+ - An info message for annotating builtins through `extend` statements is now reported, similar to `annotate`.
73
+ - Fix auto-redirection for target of new assoc in query entity
74
+ - for.odata: `@readonly/insertonly/mandatory: false` are not expanded.
75
+
76
+ ## Version 3.2.0 - 2022-08-30
77
+
78
+ ### Added
79
+
80
+ - New Integer types with these mappings:
81
+
82
+ | CDS | OData | SQL | HANA CDS |
83
+ | --------- | --------- | -------- | ------------- |
84
+ | cds.UInt8 | Edm.Byte | TINYINT | hana.TINYINT |
85
+ | cds.Int16 | Edm.Int16 | SMALLINT | hana.SMALLINT |
86
+ | cds.Int32 | Edm.Int32 | INTEGER | cds.Integer |
87
+ | cds.Int64 | Edm.Int64 | BIGINT | cds.Integer64 |
88
+
89
+ - Properties of type definitions and types of direct elements can now be extended,
90
+ e.g. `extend T with (length: 10);`
91
+
92
+ - CDL parser: support SQL function `substr_regexpr` with its special argument syntax.
93
+
94
+ ### Fixed
95
+
96
+ - An internal dump could have occurred in certain situations
97
+ for models with cyclic type definitions.
98
+ - Annotations on inferred enum elements of views were lost during recompilation.
99
+ - to.cdl: Annotations on enum value in query elements were lost.
100
+ - for.odata: Allow dynamic shortcut annotation values (`$edmJson`).
101
+ - to.edm(x):
102
+ + Don't overwrite annotations of input model.
103
+ + Ignore `null` values in `$edmJson` strings.
104
+ - to.hdi.migration: Don't interpret bound action changes as element changes.
105
+
10
106
  ## Version 3.1.2 - 2022-08-19
11
107
 
12
108
  ### Fixed
@@ -25,8 +121,8 @@ The compiler behavior concerning `beta` features can change at any time without
25
121
  `extend SomeEntity with FirstInclude, SecondInclude;`
26
122
  - Aspects can now have actions and functions, similar to entities. Aspects can be extended by actions as well.
27
123
  - `cdsc`:
28
- - `toCsn` now supports `--with-locations` which adds a `$location` property to artifacts
29
- - `toHana`/`toSql` now supports `--disable-hana-comments`, which disables rendering of doc-comments for HANA.
124
+ + `toCsn` now supports `--with-locations` which adds a `$location` property to artifacts
125
+ + `toHana`/`toSql` now supports `--disable-hana-comments`, which disables rendering of doc-comments for HANA.
30
126
  - to.hdi/sql/hdbcds: Support FK-access in `ORDER BY` and `GROUP BY`
31
127
  - to.hdi.migration: Detect an implicit change from `not null` to `null` and render corresponding `ALTER`
32
128
 
@@ -51,8 +147,10 @@ The compiler behavior concerning `beta` features can change at any time without
51
147
  - compiler:
52
148
  + `cast(elem as EnumType)` crashed the compiler.
53
149
  + Annotations on sub-elements in query entities were lost during re-compilation.
54
- + An association's cardinality was lost for associations published in projections.
150
+ + An association's cardinality was lost for new associations published in projections.
55
151
  + Annotations on indirect action parameters were lost in CSN flavor `gensrc`.
152
+ + Re-allow `annotate` statements referring to the same element twice,
153
+ even if there are annotation assignments for sub elements.
56
154
  + If a file's content starts with `{` and if neither file extension is known nor
57
155
  `fallbackParser` is set, assume the source is CSN.
58
156
  - all backends: references in `order by` _expressions_ are correctly resolved.
package/bin/cdsc.js CHANGED
@@ -153,7 +153,9 @@ function cdsc_main() {
153
153
  cmdLine.args.files = [ cmdLine.args.file ];
154
154
  }
155
155
  else if (cmdLine.command === 'parseOnly') {
156
+ // Remap command and command-specific options.
156
157
  cmdLine.command = 'toCsn';
158
+ cmdLine.options.toCsn = cmdLine.options.parseOnly;
157
159
  cmdLine.options.parseOnly = true;
158
160
  cmdLine.args.files = [ cmdLine.args.file ];
159
161
  }
@@ -409,7 +411,7 @@ function executeCommandLine(command, options, args) {
409
411
  }
410
412
  else {
411
413
  const sqlResult = main.to.sql(csn, options);
412
- writeToFileOrDisplay(options.out, 'model.sql', sqlResult.join('\n'), true);
414
+ writeToFileOrDisplay(options.out, 'model.sql', sqlResult.join('\n\n'), true);
413
415
  }
414
416
  return model;
415
417
  }
@@ -521,7 +523,7 @@ function executeCommandLine(command, options, args) {
521
523
  log(); // newline
522
524
  });
523
525
  if (options.showMessageId && hasAtLeastOneExplanation)
524
- log(`${colorTerm.help('help')}: Messages marked with '…' have an explanation text. Use \`cdsc explain <message-id>\` for a more detailed error description.`);
526
+ log(`${colorTerm.asHelp('help')}: Messages marked with '…' have an explanation text. Use \`cdsc explain <message-id>\` for a more detailed error description.`);
525
527
  }
526
528
  return model;
527
529
  }
@@ -8,6 +8,41 @@ 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.4.0 - 2022-MM-DD
12
+
13
+ ### Added `aspectWithoutElements`
14
+
15
+ - Aspects can now be defined without elements, e.g. `aspect A;`. This allows the definition of annotation-only aspects.
16
+ Views can be extended by such an aspect. For example:
17
+ ```cds
18
+ entity V as projection on SomeEntity;
19
+ @anno aspect A;
20
+ extend V with A;
21
+ ```
22
+
23
+ ### Added `sqlMigration`
24
+
25
+ - to.sql.migration: Offer something similar to to.hdi.migration, but for general SQL. Don't offer a complete out-of-the-box schema evolution, instead only
26
+ allow lossless, easy to revert actions like adding a column or extending a string length.
27
+
28
+ ## Version 3.3.0 - 2022-09-29
29
+
30
+ ### Removed `nestedProjections`
31
+
32
+ - This is now the default - see CHANGELOG entry for 3.3.0.
33
+
34
+ ### Fixed `nestedProjections`
35
+
36
+ - Issue an error for an unexpected `as ‹alias›` for references with `inline`;
37
+ people likely have confused `inline` with `expand`.
38
+ - Resolving references in the user-provided `on` condition of projected or newly-defined
39
+ associations inside `expand` and `inline` now works correctly.
40
+ - Correct `key` propagation for references with `expand` or `inline`.
41
+ - If an element is projected with sibling `expand`, the original data structure is usually
42
+ _not preserved_ (why use an `expand` if it is?). Therefore, the compiler cannot auto-rewrite
43
+ the `on` condition of unmanaged associations if such an element is referred to in the original
44
+ `on` condition. In that case, provide your own via `…: redirected to … on ‹condition›`.
45
+
11
46
  ## Version 3.1.0 - 2022-08-04
12
47
 
13
48
  ### Added `optionalActionFunctionParameters`
package/lib/api/main.js CHANGED
@@ -12,10 +12,11 @@ const forOdataNew = lazyload('../transform/forOdataNew.js');
12
12
  const toSql = lazyload('../render/toSql');
13
13
  const toCdl = require('../render/toCdl');
14
14
  const modelCompare = lazyload('../modelCompare/compare');
15
+ const diffFilter = lazyload('../modelCompare/filter');
15
16
  const sortViews = lazyload('../model/sortViews');
16
17
  const csnUtils = lazyload('../model/csnUtils');
17
18
  const timetrace = lazyload('../utils/timetrace');
18
- const forHanaNew = lazyload('../transform/forHanaNew');
19
+ const forRelationalDB = lazyload('../transform/forRelationalDB');
19
20
 
20
21
  /**
21
22
  * Return the artifact name for use for the hdbresult object
@@ -33,7 +34,7 @@ const { cloneCsnNonDict } = require('../model/csnUtils');
33
34
  const { toHdbcdsSource } = require('../render/toHdbcds');
34
35
  const { ModelError } = require('../base/error');
35
36
  const { forEach, forEachKey } = require('../utils/objectUtils');
36
- const { checkRemovedDeprecatedFlags } = require('../base/model');
37
+ const { checkRemovedDeprecatedFlags, isBetaEnabled } = require('../base/model');
37
38
  const { csn2edm, csn2edmAll } = require('../edm/csn2edm');
38
39
 
39
40
  const relevantGeneralOptions = [ /* for future generic options */ ];
@@ -92,13 +93,17 @@ function checkPreTransformedCsn(csn, options, relevantOptionNames, warnAboutMism
92
93
  const { error, warning, throwWithAnyError } = messages.makeMessageFunction(csn, options, module);
93
94
 
94
95
  for (const name of relevantOptionNames ) {
95
- if (options[name] !== csn.meta.options[name])
96
- error('wrong-pretransformed-csn', null, `Expected pre-processed CSN to have option "${ name }" set to "${ options[name] }". Found: "${ csn.meta.options[name] }"`);
96
+ if (options[name] !== csn.meta.options[name]) {
97
+ error('wrong-pretransformed-csn', null, { prop: name, value: options[name], othervalue: csn.meta.options[name] },
98
+ 'Expected pre-processed CSN to have option $(PROP) set to $(VALUE). Found: $(OTHERVALUE)');
99
+ }
97
100
  }
98
101
 
99
102
  for (const name of warnAboutMismatch ) {
100
- if (options[name] !== csn.meta.options[name])
101
- warning('options-mismatch-pretransformed-csn', null, `Expected pre-processed CSN to have option "${ name }" set to "${ options[name] }". Found: "${ csn.meta.options[name] }"`);
103
+ if (options[name] !== csn.meta.options[name]) {
104
+ warning('options-mismatch-pretransformed-csn', null, { prop: name, value: options[name], othervalue: csn.meta.options[name] },
105
+ 'Expected pre-processed CSN to have option $(PROP) set to $(VALUE). Found: $(OTHERVALUE)');
106
+ }
102
107
  }
103
108
 
104
109
  throwWithAnyError();
@@ -147,7 +152,7 @@ function odata(csn, options = {}) {
147
152
  *
148
153
  * @param {object} csn CSN to process
149
154
  * @param {object} [externalOptions={}] Options
150
- * @returns {object} { model: string, namespace: string, unappliedExtensions: string }
155
+ * @returns {object} { model: string, namespace: string }
151
156
  */
152
157
  function cdl(csn, externalOptions = {}) {
153
158
  const internalOptions = prepareOptions.to.cdl(externalOptions);
@@ -165,7 +170,7 @@ function cdl(csn, externalOptions = {}) {
165
170
  function forSql(csn, options = {}) {
166
171
  const internalOptions = prepareOptions.to.sql(options);
167
172
  internalOptions.transformation = 'sql';
168
- const transformedCsn = forHanaNew.transformForHanaWithCsn(csn, internalOptions, 'to.sql');
173
+ const transformedCsn = forRelationalDB.transformForRelationalDBWithCsn(csn, internalOptions, 'to.sql');
169
174
 
170
175
  return internalOptions.testMode ? toCsn.sortCsn(transformedCsn, internalOptions) : transformedCsn;
171
176
  }
@@ -180,7 +185,7 @@ function forSql(csn, options = {}) {
180
185
  function forHdi(csn, options = {}) {
181
186
  const internalOptions = prepareOptions.to.hdi(options);
182
187
  internalOptions.transformation = 'sql';
183
- const transformedCsn = forHanaNew.transformForHanaWithCsn(csn, internalOptions, 'to.hdi');
188
+ const transformedCsn = forRelationalDB.transformForRelationalDBWithCsn(csn, internalOptions, 'to.hdi');
184
189
 
185
190
  return internalOptions.testMode ? toCsn.sortCsn(transformedCsn, internalOptions) : transformedCsn;
186
191
  }
@@ -196,7 +201,7 @@ function forHdbcds(csn, options = {}) {
196
201
  const internalOptions = prepareOptions.to.hdbcds(options);
197
202
  internalOptions.transformation = 'hdbcds';
198
203
 
199
- const hanaCsn = forHanaNew.transformForHanaWithCsn(csn, internalOptions, 'to.hdbcds');
204
+ const hanaCsn = forRelationalDB.transformForRelationalDBWithCsn(csn, internalOptions, 'to.hdbcds');
200
205
 
201
206
  return internalOptions.testMode ? toCsn.sortCsn(hanaCsn, internalOptions) : hanaCsn;
202
207
  }
@@ -212,11 +217,6 @@ function sql(csn, options = {}) {
212
217
  const internalOptions = prepareOptions.to.sql(options);
213
218
  internalOptions.transformation = 'sql';
214
219
 
215
- const { error } = messages.makeMessageFunction(csn, internalOptions, 'for.odata');
216
-
217
- if (internalOptions.sqlDialect === 'postgres' && !baseModel.isBetaEnabled(internalOptions, 'postgres'))
218
- error(null, null, 'sqlDialect: \'postgres\' is only supported with beta-flag \'postgres\'');
219
-
220
220
  // we need the CSN for view sorting
221
221
  const transformedCsn = forSql(csn, options);
222
222
  const sqls = toSql.toSqlDdl(transformedCsn, internalOptions);
@@ -366,6 +366,119 @@ function remapName(key, csn, filter = () => true) {
366
366
  return key;
367
367
  }
368
368
 
369
+ /**
370
+ * Return all changes in artifacts between two given models.
371
+ * Note: Only supports changes in artifacts compiled/rendered as db-CSN/SQL.
372
+ *
373
+ * @param {CSN.Model} csn A clean input CSN representing the desired "after-image"
374
+ * @param {HdiOptions} options Options
375
+ * @param {CSN.Model} beforeImage A db-transformed CSN representing the "before-image", or null in case no such image
376
+ * is known, i.e. for the very first migration step
377
+ * @returns {object} An object with three properties:
378
+ * - afterImage: A db-transformed CSN representing the "after-image"
379
+ * - drops: An array of SQL statements to drop views/tables
380
+ * - createsAndAlters: An array of SQL statements to ALTER/CREATE tables/views
381
+ */
382
+ function sqlMigration(csn, options, beforeImage) {
383
+ const internalOptions = prepareOptions.to.sql(options);
384
+ const { error, throwWithError } = messages.makeMessageFunction(csn, options, 'to.sql.migration');
385
+
386
+ if (!isBetaEnabled(internalOptions, 'sqlMigration'))
387
+ throw new Error('Function `to.sql.migration` requires beta-flag `sqlMigration`');
388
+
389
+ // Prepare after-image.
390
+ const afterImage = forSql(csn, options);
391
+ // Compare both images.
392
+ const diff = modelCompare.compareModels(beforeImage || afterImage, afterImage, internalOptions);
393
+ const diffFilterObj = diffFilter[options.sqlDialect];
394
+
395
+ if (diffFilterObj) {
396
+ diff.extensions.forEach(ex => diffFilterObj.extension(ex, error));
397
+ diff.migrations.forEach(migration => diffFilterObj.migration(migration, error));
398
+ Object.entries(diff.deletions).forEach(entry => diffFilterObj.deletion(entry, error));
399
+ }
400
+
401
+ const drops = {
402
+ creates: {},
403
+ final: Object.entries(diff.deletions).reduce((previous, [ name, artifact ]) => {
404
+ previous[name] = `DROP ${ (artifact.query || artifact.projection) ? 'VIEW' : 'TABLE' } ${ artifact['@cds.persistence.name'] };`;
405
+ return previous;
406
+ }, {}),
407
+ };
408
+
409
+ const cleanup = [];
410
+ // Delete artifacts that are already present in csn
411
+ if (beforeImage?.definitions) {
412
+ Object.keys(beforeImage.definitions).forEach((artifactName) => {
413
+ const beforeArtifact = beforeImage.definitions[artifactName];
414
+ const diffArtifact = diff.definitions[artifactName];
415
+ // TODO: exists, abstract? isPersistedOnDb?
416
+ if (diffArtifact && diffArtifact['@cds.persistence.name'] && !diffArtifact['@cds.persistence.skip'] &&
417
+ (diffArtifact.query || diffArtifact.projection) &&
418
+ (diffArtifact[modelCompare.isChanged] === true || // we know it changed because we compared two views
419
+ diffArtifact[modelCompare.isChanged] === undefined)) { // if it was removed in the after, then we don't have the flag
420
+ drops.creates[artifactName] = `DROP VIEW ${ diffArtifact['@cds.persistence.name'] };`;
421
+ } // TODO: What happens with a changed kind -> entity becomes a view?
422
+ else if (diffArtifact &&
423
+ diffArtifact['@cds.persistence.skip'] !== true &&
424
+ diffArtifact.kind === beforeArtifact.kind && // detect action -> entity
425
+ csnUtils.isPersistedAsView(diffArtifact) === csnUtils.isPersistedAsView(beforeArtifact) // detect view -> entity
426
+ ) { // don't render again, but need info for primary key extension
427
+ diffArtifact['@cds.persistence.skip'] = true;
428
+ cleanup.push(() => delete diffArtifact['@cds.persistence.skip']);
429
+ }
430
+ });
431
+ }
432
+
433
+ // Convert the diff to SQL.
434
+ if (!internalOptions.beta)
435
+ internalOptions.beta = {};
436
+
437
+ internalOptions.beta.sqlExtensions = true;
438
+
439
+ // eslint-disable-next-line no-unused-vars
440
+ const { deletions, migrations, ...hdbkinds } = toSql.toSqlDdl(diff, internalOptions);
441
+
442
+ cleanup.forEach(fn => fn());
443
+ // TODO: Handle `ADD CONSTRAINT` etc!
444
+ const sortOrder = sortViews({ sql: {}, csn: afterImage });
445
+
446
+ const dropSqls = [];
447
+ const createAndAlterSqls = [];
448
+ // Turn the structured result into just a flat dictionary of "artifact name": "sql"
449
+ const flatSqlDict = Object.values(hdbkinds).reduce((prev, curr) => {
450
+ forEach(curr, (name, value) => {
451
+ prev[name] = value;
452
+ });
453
+ return prev;
454
+ }, Object.create(null));
455
+
456
+ // Sort all the SQL statements according to the overall order
457
+ for (const { name } of sortOrder) {
458
+ if (drops.final[name])
459
+ dropSqls.push(drops.final[name]);
460
+ else if (drops.creates[name])
461
+ dropSqls.push(drops.creates[name]);
462
+
463
+ // No else-if, since we have drop-creates for views!
464
+ if (flatSqlDict[name])
465
+ createAndAlterSqls.push(flatSqlDict[name]);
466
+ else if (migrations[name])
467
+ createAndAlterSqls.push(...migrations[name].map(m => m.sql));
468
+ }
469
+
470
+ // We need to drop the things without dependants first - so inversely sorted
471
+ dropSqls.reverse();
472
+
473
+ throwWithError();
474
+
475
+ return {
476
+ afterImage,
477
+ drops: dropSqls,
478
+ createsAndAlters: createAndAlterSqls,
479
+ };
480
+ }
481
+
369
482
  /**
370
483
  * Return all changes in artifacts between two given models.
371
484
  * Note: Only supports changes in entities (not views etc.) compiled/rendered as HANA-CSN/SQL.
@@ -374,17 +487,7 @@ function remapName(key, csn, filter = () => true) {
374
487
  * @param {HdiOptions} options Options
375
488
  * @param {CSN.Model} beforeImage A HANA-transformed CSN representing the "before-image", or null in case no such image
376
489
  * is known, i.e. for the very first migration step
377
- * @returns {object} - afterImage: The desired after-image in HANA-CSN format
378
- * - definitions: An array of objects with all artifacts in the after-image. Each object specifies
379
- * the artifact filename, the suffix, and the corresponding SQL statement to create
380
- * the artifact.
381
- * - deletions: An array of objects with the deleted artifacts. Each object specifies the artifact
382
- * filename and the suffix.
383
- * - migrations: An array of objects with the changed (migrated) artifacts. Each object specifies the
384
- * artifact filename, the suffix, and the changeset (an array of changes, each specifying
385
- * whether it incurs potential data loss, and its respective SQL statement(s), with
386
- * multiple statements concatenated as a multi-line string in case the change e.g.
387
- * consists of a column drop and add).
490
+ * @returns {migration} The migration result
388
491
  */
389
492
  function hdiMigration(csn, options, beforeImage) {
390
493
  const internalOptions = prepareOptions.to.hdi(options);
@@ -392,11 +495,14 @@ function hdiMigration(csn, options, beforeImage) {
392
495
  // Prepare after-image.
393
496
  const afterImage = forHdi(csn, options);
394
497
 
395
- // Compare both images.
396
498
  const diff = modelCompare.compareModels(beforeImage || afterImage, afterImage, internalOptions);
397
499
 
398
500
  // Convert the diff to SQL.
399
- internalOptions.forHana = true; // Make it pass the SQL rendering
501
+ if (!internalOptions.beta)
502
+ internalOptions.beta = {};
503
+
504
+ internalOptions.beta.sqlExtensions = true;
505
+
400
506
  const { deletions, migrations, ...hdbkinds } = toSql.toSqlDdl(diff, internalOptions);
401
507
 
402
508
  return {
@@ -454,6 +560,8 @@ function createSqlMigrations(migrations, afterImage) {
454
560
 
455
561
  hdi.migration = hdiMigration;
456
562
 
563
+ sql.migration = sqlMigration;
564
+
457
565
  /**
458
566
  * Process the given CSN into HDBCDS artifacts.
459
567
  *
@@ -514,7 +622,7 @@ function edmall(csn, options = {}) {
514
622
  const { error } = messages.makeMessageFunction(csn, internalOptions, 'for.odata');
515
623
 
516
624
  if (internalOptions.odataVersion === 'v2')
517
- error(null, null, 'OData JSON output is not available for OData V2');
625
+ error(null, null, {}, 'OData JSON output is not available for OData V2');
518
626
 
519
627
  const result = {};
520
628
  let oDataCsn = csn;
@@ -927,3 +1035,19 @@ function lazyload(moduleName) {
927
1035
  *
928
1036
  * @typedef {object} edms
929
1037
  */
1038
+
1039
+ /**
1040
+ * - afterImage: The desired after-image in db-CSN format
1041
+ * - definitions: An array of objects with all artifacts in the after-image. Each object specifies
1042
+ * the artifact filename, the suffix, and the corresponding SQL statement to create
1043
+ * the artifact.
1044
+ * - deletions: An array of objects with the deleted artifacts. Each object specifies the artifact
1045
+ * filename and the suffix.
1046
+ * - migrations: An array of objects with the changed (migrated) artifacts. Each object specifies the
1047
+ * artifact filename, the suffix, and the changeset (an array of changes, each specifying
1048
+ * whether it incurs potential data loss, and its respective SQL statement(s), with
1049
+ * multiple statements concatenated as a multi-line string in case the change e.g.
1050
+ * consists of a column drop and add).
1051
+ *
1052
+ * @typedef {object} migration
1053
+ */
@@ -78,7 +78,7 @@ const validators = {
78
78
  expected: () => 'type array',
79
79
  found: val => `type ${ typeof val }`,
80
80
  },
81
- sqlDialect: generateStringValidator([ 'sqlite', 'hana', 'plain', 'postgres' ]),
81
+ sqlDialect: generateStringValidator([ 'sqlite', 'hana', 'plain', 'postgres', 'h2' ]),
82
82
  sqlMapping: generateStringValidator([ 'plain', 'quoted', 'hdbcds' ]),
83
83
  odataVersion: generateStringValidator([ 'v2', 'v4' ]),
84
84
  odataFormat: generateStringValidator([ 'flat', 'structured' ]),
@@ -161,8 +161,13 @@ function validate(options, moduleName, customValidators = {}, combinationValidat
161
161
  forEach(options, (optionName, optionValue) => {
162
162
  const validator = customValidators[optionName] || validators[optionName] || booleanValidator;
163
163
 
164
- if (!validator.validate(optionValue))
165
- error('invalid-option', null, {}, `Expected option "${ optionName }" to have "${ validator.expected(optionValue) }". Found: "${ validator.found(optionValue) }"`);
164
+ if (!validator.validate(optionValue)) {
165
+ error('invalid-option', null, {
166
+ prop: optionName,
167
+ value: validator.expected(optionValue),
168
+ othervalue: validator.found(optionValue),
169
+ }, 'Expected option $(PROP) to have $(VALUE). Found: $(OTHERVALUE)');
170
+ }
166
171
  });
167
172
  throwWithAnyError();
168
173
  }
@@ -19,7 +19,7 @@ function dictAdd( dict, name, entry, duplicateCallback ) {
19
19
  duplicateCallback( name, found.name.location, found );
20
20
  }
21
21
  found.$duplicates.push( entry );
22
- if (Array.isArray(entry.$duplicates))
22
+ if (Array.isArray( entry.$duplicates ))
23
23
  found.$duplicates.push( ...entry.$duplicates )
24
24
  else if (duplicateCallback && name) // do not complain with empty name ''
25
25
  duplicateCallback( name, entry.name.location, entry );
@@ -31,7 +31,7 @@ function dictForEach( dict, callback ) {
31
31
  // TODO: probably define an extra dictForEachArray()
32
32
  for (const name in dict) {
33
33
  const entry = dict[name];
34
- if (Array.isArray(entry)) {
34
+ if (Array.isArray( entry )) {
35
35
  entry.forEach( callback );
36
36
  }
37
37
  else {
@@ -53,8 +53,8 @@ function dictAddArray( dict, name, entry, messageCallback ) {
53
53
  dict[name] = entry; // also ok if array (redefined)
54
54
  return entry;
55
55
  }
56
- if (Array.isArray(entry)) {
57
- if (Array.isArray(found)) {
56
+ if (Array.isArray( entry )) {
57
+ if (Array.isArray( found )) {
58
58
  dict[name] = [ ...found, ...entry ];
59
59
  }
60
60
  else {
@@ -65,7 +65,7 @@ function dictAddArray( dict, name, entry, messageCallback ) {
65
65
  }
66
66
  }
67
67
  else {
68
- if (Array.isArray(found)) {
68
+ if (Array.isArray( found )) {
69
69
  dict[name] = [ ...found, entry ];
70
70
  }
71
71
  else {
@@ -84,7 +84,7 @@ function dictAddArray( dict, name, entry, messageCallback ) {
84
84
  // Push `entry` to the array value with key `name` in the dictionary `dict`.
85
85
  function pushToDict( dict, name, entry ) {
86
86
  if (dict[name])
87
- dict[name].push(entry);
87
+ dict[name].push( entry );
88
88
  else
89
89
  dict[name] = [entry];
90
90
  }
package/lib/base/error.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  class CompilerAssertion extends Error {
8
8
  constructor(message) {
9
- super(message);
9
+ super(`cds-compiler assertion failed: ${message}`);
10
10
  }
11
11
  }
12
12
 
@@ -16,7 +16,7 @@ class CompilerAssertion extends Error {
16
16
  */
17
17
  class ModelError extends Error {
18
18
  constructor(message) {
19
- super(message);
19
+ super(`cds-compiler model error: ${message}`);
20
20
  }
21
21
  }
22
22