@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
@@ -4,9 +4,12 @@ const { makeMessageFunction } = require('../base/messages');
4
4
  const {
5
5
  forEachDefinition,
6
6
  forEachMember,
7
- hasAnnotationValue
7
+ isPersistedAsTable,
8
+ isPersistedAsView
8
9
  } = require('../model/csnUtils');
9
10
  const { isBetaEnabled } = require('../base/model');
11
+ // used to mark a view as changed so we know to drop-create it
12
+ const isChanged = Symbol('Marks a view as changed');
10
13
 
11
14
  /**
12
15
  * Compares two models, in HANA-transformed CSN format, to each other.
@@ -14,7 +17,7 @@ const { isBetaEnabled } = require('../base/model');
14
17
  * @param beforeModel the before-model
15
18
  * @param afterModel the after-model
16
19
  * @param {HdiOptions|false} options
17
- * @returns {object} the sets of deletions, extensions, and migrations of entities necessary to transform the before-model
20
+ * @returns {ModelDiff} the sets of deletions, extensions, and migrations of entities necessary to transform the before-model
18
21
  * to the after-model, together with all the definitions of the after-model
19
22
  */
20
23
  function compareModels(beforeModel, afterModel, options) {
@@ -46,17 +49,18 @@ function validateCsnVersions(beforeModel, afterModel, options) {
46
49
 
47
50
  if (!beforeVersionParts || beforeVersionParts.length < 2) {
48
51
  const { error, throwWithAnyError } = makeMessageFunction(beforeModel, options, 'modelCompare');
49
- error(null, null, `Invalid CSN version: ${beforeVersion}`);
52
+ error(null, null, { version: beforeVersion }, 'Invalid CSN version: $(VERSION)');
50
53
  throwWithAnyError();
51
54
  }
52
55
  if (!afterVersionParts || afterVersionParts.length < 2) {
53
56
  const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
54
- error(null, null, `Invalid CSN version: ${afterVersion}`);
57
+ error(null, null, { version: afterVersion }, 'Invalid CSN version: $(VERSION)');
55
58
  throwWithAnyError();
56
59
  }
57
60
  if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
58
61
  const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
59
- error(null, null, `Incompatible CSN versions: ${afterVersion} is a major downgrade from ${beforeVersion}. Is @sap/cds-compiler version ${require('../../package.json').version} outdated?`);
62
+ error(null, null, { value: afterVersion, othervalue: beforeVersion, version: require('../../package.json').version },
63
+ 'Incompatible CSN versions: $(VALUE) is a major downgrade from $(OTHERVALUE). Is @sap/cds-compiler version $(VERSION) outdated?');
60
64
  throwWithAnyError();
61
65
  }
62
66
  }
@@ -65,7 +69,7 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
65
69
  return function compareArtifacts(artifact, name) {
66
70
  function addElements() {
67
71
  const elements = {};
68
- forEachMember(artifact, getElementComparator(otherArtifact, elements));
72
+ forEachMember(artifact, getElementComparator(otherArtifact, elements), [ 'definitions', name ], true, { elementsOnly: true });
69
73
  if (Object.keys(elements).length > 0) {
70
74
  elementAdditions.push(addedElements(name, elements));
71
75
  }
@@ -92,12 +96,12 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
92
96
  migration.properties = changedProperties;
93
97
  }
94
98
 
95
- forEachMember(otherArtifact, getElementComparator(artifact, removedElements));
99
+ forEachMember(otherArtifact, getElementComparator(artifact, removedElements), [ 'definitions', name ], true, { elementsOnly: true });
96
100
  if (Object.keys(removedElements).length > 0) {
97
101
  migration.remove = removedElements;
98
102
  }
99
103
 
100
- forEachMember(artifact, getElementComparator(otherArtifact, null, changedElements));
104
+ forEachMember(artifact, getElementComparator(otherArtifact, null, changedElements), [ 'definitions', name ], true, { elementsOnly: true });
101
105
  if (Object.keys(changedElements).length > 0) {
102
106
  migration.change = changedElements;
103
107
  }
@@ -116,10 +120,18 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
116
120
  // Arguments are interchanged in this case: `artifact` from beforeModel and `otherArtifact` from afterModel.
117
121
  if (isPersisted && !isPersistedOther) {
118
122
  deletedEntities[name] = artifact;
123
+ } else if(isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
124
+ deletedEntities[name] = artifact;
119
125
  }
120
126
  return;
121
127
  }
122
128
 
129
+ // to make it easier to know which views to drop-create
130
+ if(isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
131
+ // TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
132
+ artifact[isChanged] = JSON.stringify(artifact) !== JSON.stringify(otherArtifact);
133
+ }
134
+
123
135
  // Looking for added entities and added/deleted/changed elements.
124
136
  // Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
125
137
 
@@ -146,15 +158,6 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
146
158
  };
147
159
  }
148
160
 
149
- function isPersistedAsTable(artifact) {
150
- return artifact.kind === 'entity'
151
- && !artifact._ignore
152
- && !artifact.abstract
153
- && (!artifact.query && !artifact.projection || hasAnnotationValue(artifact, '@cds.persistence.table'))
154
- && !hasAnnotationValue(artifact, '@cds.persistence.skip')
155
- && !hasAnnotationValue(artifact, '@cds.persistence.exists');
156
- }
157
-
158
161
  function getElementComparator(otherArtifact, addedElementsDict = null, changedElementsDict = null) {
159
162
  return function compareElements(element, name) {
160
163
  if (element._ignore) {
@@ -244,5 +247,36 @@ function changedElement(element, otherElement) {
244
247
 
245
248
  module.exports = {
246
249
  compareModels,
247
- deepEqual
250
+ deepEqual,
251
+ isChanged
248
252
  };
253
+
254
+ /**
255
+ * A ModelDiff encapsulates the changes between two models ("before" and "after"). It contains information
256
+ * about changes to .elements and removed artifacts.
257
+ *
258
+ * @typedef {object} ModelDiff
259
+ * @property {CSN.Definitions} definitions The artifacts present in the "after" model
260
+ * @property {CSN.Definitions} deletions The artifacts present in the "before", but not in the "after"
261
+ * @property {extension[]} extensions The elements added to artifacts
262
+ * @property {migration[]} migrations Altered or removed elements
263
+ */
264
+
265
+ /**
266
+ * @typedef {object} extension
267
+ * @property {CSN.Elements} elements The elements that where added
268
+ * @property {string} extend Name of the artifact that the .elements need to be added to
269
+ */
270
+
271
+ /**
272
+ * @typedef {object} migration
273
+ * @property {Object.<string, ChangeSet>} change An object of changes - the key being the name of the changed element, the value being the change.
274
+ * @property {string} migrate Name of the artifact that the .change and .remove apply to
275
+ * @property {CSN.Elements} remove An object of removed elements
276
+ */
277
+
278
+ /**
279
+ * @typedef {object} ChangeSet Describes the change of one element
280
+ * @property {CSN.Element} old The old element definition
281
+ * @property {CSN.Element} new The new element definition
282
+ */
@@ -0,0 +1,83 @@
1
+ // Each db has some changes that it can and cannot represent, or that cause problems only on that specific db
2
+ // In this file, we define rules for each db-dialect to detect and act on these cases.
3
+
4
+ const { forEach } = require("../utils/objectUtils");
5
+ const { isPersistedAsTable } = require('../model/csnUtils');
6
+
7
+ function isKey(element) {
8
+ return element.key;
9
+ }
10
+
11
+ module.exports = {
12
+ sqlite: getFilterObject(
13
+ 'sqlite',
14
+ (extend, name, element, error) => {
15
+ if(isKey(element)) { // Key must not be extended
16
+ error(null, ['definitions', extend, 'elements', name], {id: name, name: 'sqlite'}, "Added element $(ID) is a primary key change and will not work with $(NAME)")
17
+ }
18
+ },
19
+ (migrate, name, migration, change, error) => {
20
+ const newIsKey = isKey(migration.new);
21
+ const oldIsKey = isKey(migration.old);
22
+ if((newIsKey || oldIsKey) && oldIsKey !== newIsKey) { // Turned into key or key was removed
23
+ error(null, ['definitions', migrate, 'elements', name], {id: name, name: 'sqlite'}, "Changed element $(ID) is a primary key change and will not work with $(NAME)")
24
+ } else { // Ignore simple migrations
25
+ delete change[name];
26
+ }
27
+ })
28
+ }
29
+
30
+ function getFilterObject(dialect, extensionCallback, migrationCallback) {
31
+ return {
32
+ // will be called with a simple Array.forEach
33
+ extension: ({ elements, extend }, error) => {
34
+ forEach(elements, (name, element) => {
35
+ extensionCallback(extend, name, element, error);
36
+ });
37
+ },
38
+ // will be called with a Array.map, as we need to filter "change" for SQLite
39
+ migration: ({ change, migrate, remove }, error) => {
40
+ forEach(remove, (name) => {
41
+ error(null, ['definitions', migrate, 'elements', name], {}, "Dropping elements is not supported")
42
+ });
43
+
44
+ forEach(change, (name, migration) => {
45
+ if(migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type)) {
46
+ error(null, ['definitions', migrate, 'elements', name], { id: name, name: migration.old.type, type: migration.new.type }, "Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported")
47
+ } else if(migration.new.length < migration.old.length) {
48
+ error(null, ['definitions', migrate, 'elements', name], { id: name }, "Changed element $(ID) is a length reduction and is not supported")
49
+ } else {
50
+ migrationCallback(migrate, name, migration, change, error);
51
+ }
52
+
53
+ // TODO: precision/scale growth
54
+ });
55
+ },
56
+ deletion: ([artifactName, artifact ], error) => {
57
+ if(isPersistedAsTable(artifact))
58
+ error(null, ['definitions', artifactName], "Dropping tables is not supported");
59
+ }
60
+ }
61
+ }
62
+
63
+ const baseMatrix = {
64
+ // Integer types
65
+ 'cds.hana.tinyint':['cds.UInt8', 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
66
+ 'cds.UInt8': ['cds.hana.tinyint', 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
67
+ 'cds.Int16': ['cds.hana.smallint', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
68
+ 'cds.hana.smallint':['cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
69
+ 'cds.Int32': ['cds.Integer', 'cds.Int64', 'cds.Integer64'],
70
+ 'cds.Integer': ['cds.Int32', 'cds.Int64', 'cds.Integer64'],
71
+ 'cds.Integer64': ['cds.Int64'],
72
+ 'cds.Int64': ['cds.Integer64']
73
+ }
74
+
75
+ const allowedTypeChanges = {
76
+ 'sqlite': baseMatrix
77
+ };
78
+
79
+ function typeChangeIsNotCompatible(dialect, before, after) {
80
+ if(allowedTypeChanges[dialect])
81
+ return allowedTypeChanges[dialect][before]?.indexOf(after) === -1;
82
+ return true;
83
+ }
@@ -102,6 +102,9 @@ optionProcessor
102
102
  hanaAssocRealCardinality
103
103
  mapAssocToJoinCardinality
104
104
  ignoreAssocPublishingInUnion
105
+ enableUniversalCsn
106
+ postgres
107
+ aspectWithoutElements
105
108
  odataOpenType
106
109
  optionalActionFunctionParameters
107
110
  --deprecated <list> Comma separated list of deprecated options.
@@ -142,6 +145,11 @@ optionProcessor
142
145
  manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
143
146
  add / modify referential constraints.
144
147
  inspect [options] <files...> (internal) Inspect the given CDS files.
148
+
149
+ Environment variables
150
+ NO_COLOR If set, compiler messages (/output) will not be colored.
151
+ Can be overwritten by '--color'
152
+ CDSC_TIMETRACING If set, additional timing information is printed to stderr.
145
153
  `);
146
154
 
147
155
  // ----------- toHana -----------
@@ -257,7 +265,7 @@ optionProcessor.command('C, toCdl')
257
265
  optionProcessor.command('Q, toSql')
258
266
  .option('-h, --help')
259
267
  .option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
260
- .option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres'], { aliases: [ '--dialect' ] })
268
+ .option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres', 'h2'], { aliases: [ '--dialect' ] })
261
269
  .option(' --render-virtual')
262
270
  .option(' --joinfk')
263
271
  .option('-u, --user <user>')
@@ -295,6 +303,7 @@ optionProcessor.command('Q, toSql')
295
303
  hana : SQL with HANA specific language features
296
304
  sqlite : Common SQL for sqlite
297
305
  postgres : Common SQL for postgres - beta-feature
306
+ h2 : Common SQL for h2
298
307
  -u, --user <user> Value for the "$user" variable
299
308
  -l, --locale <locale> Value for the "$user.locale" variable in "sqlite"/"plain" dialect
300
309
  -s, --src <style> Generate SQL source files as <artifact>.<suffix>
@@ -8,7 +8,7 @@ const {
8
8
  const { forEach } = require('../utils/objectUtils');
9
9
  const { makeMessageFunction } = require('../base/messages');
10
10
  const { optionProcessor } = require('../optionProcessor');
11
- const { transformForHanaWithCsn } = require('../transform/forHanaNew');
11
+ const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
12
12
 
13
13
  const {
14
14
  renderReferentialConstraint, getIdentifierUtils,
@@ -38,10 +38,12 @@ function alterConstraintsWithCsn(csn, options) {
38
38
  options.assertIntegrityType = 'DB';
39
39
 
40
40
  const transformedOptions = _transformSqlOptions(csn, options);
41
- const forSqlCsn = transformForHanaWithCsn(csn, transformedOptions, 'to.sql');
41
+ const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, 'to.sql');
42
42
 
43
- if (violations && src && src !== 'sql')
44
- error(null, null, `Option “--violations“ can't be combined with source style “${src}“`);
43
+ if (violations && src && src !== 'sql') {
44
+ error(null, null, { value: '--violations', othervalue: src },
45
+ 'Option $(VALUE) can\'t be combined with source style $(OTHERVALUE)');
46
+ }
45
47
 
46
48
  let intermediateResult;
47
49
  if (violations)
@@ -67,12 +69,14 @@ function _transformSqlOptions(model, options) {
67
69
 
68
70
  if (options.sqlDialect !== 'hana') {
69
71
  // CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
70
- if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds')
71
- error(null, null, `Option "{ sqlDialect: '${options.sqlDialect}' }" can't be combined with "{ sqlMapping: '${options.sqlMapping}' }"`);
72
+ if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds') {
73
+ error(null, null, { value: options.sqlDialect, othervalue: options.sqlMapping },
74
+ 'Option sqlDialect: $(VALUE) can\'t be combined with sqlMapping: $(OTHERVALUE)');
75
+ }
72
76
 
73
77
  // No non-HANA SQL for HDI
74
78
  if (options.src === 'hdi')
75
- error(null, null, `Option "{ sqlDialect: '${options.sqlDialect}' }" can't be used for HDI"`);
79
+ error(null, null, { value: options.sqlDialect }, 'Option sqlDialect: $(VALUE) can\'t be used for SAP HANA HDI');
76
80
  }
77
81
 
78
82
  return options;