@sap/cds-compiler 4.7.6 → 4.9.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 (129) hide show
  1. package/CHANGELOG.md +63 -3
  2. package/bin/cds_remove_invalid_whitespace.js +135 -0
  3. package/bin/cds_update_annotations.js +180 -0
  4. package/bin/cds_update_identifiers.js +3 -4
  5. package/bin/cdsc.js +28 -1
  6. package/bin/cdshi.js +13 -3
  7. package/doc/CHANGELOG_BETA.md +24 -1
  8. package/lib/api/main.js +119 -46
  9. package/lib/api/options.js +51 -0
  10. package/lib/api/validate.js +1 -5
  11. package/lib/base/builtins.js +116 -0
  12. package/lib/base/keywords.js +5 -1
  13. package/lib/base/location.js +91 -14
  14. package/lib/base/message-registry.js +76 -46
  15. package/lib/base/messages.js +121 -35
  16. package/lib/base/model.js +4 -7
  17. package/lib/checks/actionsFunctions.js +3 -3
  18. package/lib/checks/annotationsOData.js +3 -0
  19. package/lib/checks/defaultValues.js +5 -2
  20. package/lib/checks/elements.js +2 -1
  21. package/lib/checks/enricher.js +2 -2
  22. package/lib/checks/queryNoDbArtifacts.js +5 -3
  23. package/lib/checks/utils.js +1 -1
  24. package/lib/checks/validator.js +8 -56
  25. package/lib/compiler/assert-consistency.js +11 -7
  26. package/lib/compiler/builtins.js +0 -74
  27. package/lib/compiler/checks.js +105 -29
  28. package/lib/compiler/define.js +37 -25
  29. package/lib/compiler/extend.js +35 -12
  30. package/lib/compiler/index.js +9 -10
  31. package/lib/compiler/lsp-api.js +5 -0
  32. package/lib/compiler/populate.js +13 -5
  33. package/lib/compiler/propagator.js +24 -18
  34. package/lib/compiler/resolve.js +47 -45
  35. package/lib/compiler/shared.js +61 -21
  36. package/lib/compiler/tweak-assocs.js +15 -90
  37. package/lib/compiler/utils.js +3 -3
  38. package/lib/compiler/xpr-rewrite.js +689 -0
  39. package/lib/compiler/{classes.js → xsn-model.js} +0 -16
  40. package/lib/edm/annotations/edmJson.js +7 -5
  41. package/lib/edm/annotations/genericTranslation.js +149 -71
  42. package/lib/edm/csn2edm.js +25 -9
  43. package/lib/edm/edm.js +7 -7
  44. package/lib/edm/edmInboundChecks.js +57 -5
  45. package/lib/edm/edmPreprocessor.js +54 -25
  46. package/lib/edm/edmUtils.js +3 -16
  47. package/lib/gen/Dictionary.json +138 -14
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +1 -1
  50. package/lib/gen/languageParser.js +2085 -1989
  51. package/lib/json/csnVersion.js +7 -4
  52. package/lib/json/from-csn.js +21 -11
  53. package/lib/json/to-csn.js +8 -4
  54. package/lib/language/antlrParser.js +1 -1
  55. package/lib/language/genericAntlrParser.js +23 -16
  56. package/lib/language/multiLineStringParser.js +2 -2
  57. package/lib/language/textUtils.js +1 -1
  58. package/lib/main.d.ts +90 -14
  59. package/lib/main.js +9 -1
  60. package/lib/model/cloneCsn.js +21 -9
  61. package/lib/model/csnRefs.js +153 -42
  62. package/lib/model/csnUtils.js +14 -11
  63. package/lib/model/enrichCsn.js +4 -2
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/model/sortViews.js +14 -6
  66. package/lib/modelCompare/compare.js +135 -122
  67. package/lib/optionProcessor.js +49 -2
  68. package/lib/render/DuplicateChecker.js +6 -6
  69. package/lib/render/manageConstraints.js +1 -0
  70. package/lib/render/toCdl.js +6 -3
  71. package/lib/render/toHdbcds.js +4 -48
  72. package/lib/render/toSql.js +6 -3
  73. package/lib/transform/addTenantFields.js +58 -35
  74. package/lib/transform/db/applyTransformations.js +34 -1
  75. package/lib/transform/db/constraints.js +1 -1
  76. package/lib/transform/db/expansion.js +11 -3
  77. package/lib/transform/db/flattening.js +71 -46
  78. package/lib/transform/db/groupByOrderBy.js +2 -2
  79. package/lib/transform/db/temporal.js +6 -3
  80. package/lib/transform/db/transformExists.js +2 -2
  81. package/lib/transform/db/views.js +1 -4
  82. package/lib/transform/effective/annotations.js +194 -0
  83. package/lib/transform/effective/main.js +11 -10
  84. package/lib/transform/effective/misc.js +45 -14
  85. package/lib/transform/effective/types.js +4 -3
  86. package/lib/transform/forOdata.js +29 -12
  87. package/lib/transform/forRelationalDB.js +104 -113
  88. package/lib/transform/localized.js +7 -6
  89. package/lib/transform/odata/flattening.js +228 -107
  90. package/lib/transform/odata/toFinalBaseType.js +10 -26
  91. package/lib/transform/odata/typesExposure.js +41 -25
  92. package/lib/transform/parseExpr.js +4 -7
  93. package/lib/transform/transformUtils.js +50 -43
  94. package/lib/transform/translateAssocsToJoins.js +48 -48
  95. package/lib/transform/universalCsn/coreComputed.js +2 -1
  96. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
  97. package/package.json +2 -2
  98. package/share/messages/README.md +4 -0
  99. package/share/messages/anno-duplicate-unrelated-layer.md +1 -1
  100. package/share/messages/anno-missing-rewrite.md +45 -0
  101. package/share/messages/check-proper-type-of.md +1 -1
  102. package/share/messages/def-duplicate-autoexposed.md +1 -1
  103. package/share/messages/extend-repeated-intralayer.md +3 -16
  104. package/share/messages/extend-unrelated-layer.md +1 -1
  105. package/share/messages/message-explanations.json +2 -0
  106. package/share/messages/redirected-to-ambiguous.md +1 -1
  107. package/share/messages/redirected-to-complex.md +1 -1
  108. package/share/messages/redirected-to-unrelated.md +1 -1
  109. package/share/messages/rewrite-not-supported.md +1 -1
  110. package/share/messages/syntax-expecting-unsigned-int.md +2 -2
  111. package/share/messages/type-missing-enum-value.md +59 -0
  112. package/share/messages/wildcard-excluding-one.md +1 -1
  113. package/bin/.eslintrc.json +0 -17
  114. package/lib/api/.eslintrc.json +0 -37
  115. package/lib/checks/.eslintrc.json +0 -31
  116. package/lib/compiler/.eslintrc.json +0 -8
  117. package/lib/edm/.eslintrc.json +0 -46
  118. package/lib/inspect/.eslintrc.json +0 -4
  119. package/lib/json/.eslintrc.json +0 -4
  120. package/lib/language/.eslintrc.json +0 -4
  121. package/lib/model/.eslintrc.json +0 -13
  122. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  123. package/lib/render/.eslintrc.json +0 -22
  124. package/lib/transform/.eslintrc.json +0 -13
  125. package/lib/transform/db/.eslintrc.json +0 -41
  126. package/lib/transform/draft/.eslintrc.json +0 -4
  127. package/lib/transform/effective/.eslintrc.json +0 -4
  128. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  129. package/lib/utils/.eslintrc.json +0 -7
@@ -1,17 +1,24 @@
1
1
  'use strict';
2
2
 
3
3
  const { makeMessageFunction } = require('../base/messages');
4
+ const { setProp } = require('../base/model');
4
5
  const {
5
6
  forEachDefinition,
6
7
  forEachMember,
7
8
  isPersistedAsTable,
8
- isPersistedAsView
9
+ isPersistedAsView,
9
10
  } = require('../model/csnUtils');
10
11
  const { forEachKey, forEach } = require('../utils/objectUtils');
11
12
 
12
13
  // used to mark a view as changed so we know to drop-create it
13
14
  const isChanged = Symbol('Marks a view as changed');
14
15
 
16
+ const relevantProperties = {
17
+ doc: true,
18
+ '@sql.prepend': true,
19
+ '@sql.append': true,
20
+ };
21
+
15
22
  /**
16
23
  * Compares two models, in HANA-transformed CSN format, to each other.
17
24
  *
@@ -23,7 +30,7 @@ const isChanged = Symbol('Marks a view as changed');
23
30
  */
24
31
  function compareModels(beforeModel, afterModel, options) {
25
32
  // @ts-ignore
26
- if(!(options && options.testMode)) // no $version with testMode
33
+ if (!(options && options.testMode)) // no $version with testMode
27
34
  validateCsnVersions(beforeModel, afterModel, options);
28
35
 
29
36
  const returnObj = Object.create(null);
@@ -45,8 +52,8 @@ function compareModels(beforeModel, afterModel, options) {
45
52
  function validateCsnVersions(beforeModel, afterModel, options) {
46
53
  const beforeVersion = beforeModel.$version;
47
54
  const afterVersion = afterModel.$version;
48
- let beforeVersionParts = beforeVersion && beforeVersion.split('.');
49
- let afterVersionParts = afterVersion && afterVersion.split('.');
55
+ const beforeVersionParts = beforeVersion && beforeVersion.split('.');
56
+ const afterVersionParts = afterVersion && afterVersion.split('.');
50
57
 
51
58
  if (!beforeVersionParts || beforeVersionParts.length < 2) {
52
59
  const { error, throwWithAnyError } = makeMessageFunction(beforeModel, options, 'modelCompare');
@@ -60,53 +67,85 @@ function validateCsnVersions(beforeModel, afterModel, options) {
60
67
  }
61
68
  if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
62
69
  const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
63
- error(null, null, { value: afterVersion, othervalue: beforeVersion, version: require('../../package.json').version },
64
- 'Incompatible CSN versions: $(VALUE) is a major downgrade from $(OTHERVALUE). Is @sap/cds-compiler version $(VERSION) outdated?');
70
+ // eslint-disable-next-line global-require
71
+ const { version } = require('../../package.json');
72
+ error(null, null, { value: afterVersion, othervalue: beforeVersion, version },
73
+ 'Incompatible CSN versions: $(VALUE) is a major downgrade from $(OTHERVALUE). Is @sap/cds-compiler version $(VERSION) outdated?');
65
74
  throwWithAnyError();
66
75
  }
67
76
  }
68
77
 
69
78
  /**
70
79
  * Calculate extensions, migrations and unchangedConstraints
71
- *
72
- * @param {CSN.Model} beforeModel
73
- * @param {CSN.Options} options
74
- * @param {object} returnObj
80
+ *
81
+ * @param {CSN.Model} beforeModel
82
+ * @param {CSN.Options} options
83
+ * @param {object} returnObj
75
84
  * @returns {function}
76
85
  */
77
- function getExtensionAndMigrations(beforeModel, options, { extensions, migrations, unchangedConstraints, changedPrimaryKeys }) {
86
+ function getExtensionAndMigrations(beforeModel, options, {
87
+ extensions, migrations, unchangedConstraints, changedPrimaryKeys,
88
+ }) {
78
89
  return function compareArtifacts(artifact, name) {
79
90
  let hasPrimaryKeyChange = false;
91
+ const otherArtifact = beforeModel.definitions[name];
92
+ const isPersisted = isPersistedAsTable(artifact);
93
+ const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
94
+
95
+ // to make it easier to know which views to drop-create
96
+ if (isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
97
+ // TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
98
+ artifact[isChanged] = JSON.stringify(artifact) !== JSON.stringify(otherArtifact);
99
+ }
100
+
101
+ // Looking for added entities and added/deleted/changed elements.
102
+ // Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
103
+
104
+ if (!isPersisted) // Artifact not persisted in afterModel.
105
+ return;
106
+
107
+ if (!isPersistedOther) {
108
+ extensions[name] = artifact;
109
+ return;
110
+ }
111
+
112
+ // Artifact changed?
113
+
114
+ addElements();
115
+ changePropsOrRemoveOrChangeElements();
116
+ if (hasPrimaryKeyChange)
117
+ changedPrimaryKeys.push(name);
80
118
 
81
119
  function addElements() {
82
120
  const elements = {};
83
121
  const keysNow = [];
84
122
  forEachMember(artifact, (element, eName) => {
85
123
  getElementComparator(otherArtifact, elements)(element, eName);
86
- if(element.key)
124
+ if (element.key)
87
125
  keysNow.push(eName);
88
126
  }, [ 'definitions', name ], true, { elementsOnly: true });
89
127
 
90
128
  // Only do this check for to.hdi.migration - the order only "bites" us when doing .hdbmigrationtable as the end-check against the intended
91
129
  // create-table will fail. TODO: Does a mismatched order of the primary key hurt us for postgres and others?
92
- if(!hasPrimaryKeyChange && options.sqlDialect === 'hana' && options.src === 'hdi') {
130
+ if (!hasPrimaryKeyChange && options.sqlDialect === 'hana' && options.src === 'hdi') {
93
131
  const keysOther = [];
94
132
  forEachMember(otherArtifact, (element, eName) => {
95
- if(element.key)
133
+ if (element.key)
96
134
  keysOther.push(eName);
97
135
  }, [ 'definitions', name ], true, { elementsOnly: true });
98
136
 
99
- if(keysNow.join(',') !== keysOther.join(','))
137
+ if (keysNow.join(',') !== keysOther.join(','))
100
138
  hasPrimaryKeyChange = true;
101
139
  }
102
140
 
103
141
  if (Object.keys(elements).length > 0) {
104
142
  const added = addedElements(name, elements);
105
- if(!hasPrimaryKeyChange)
143
+ if (!hasPrimaryKeyChange) {
106
144
  forEach(added.elements, (_name, element) => {
107
- if(element.key && !element.target)
145
+ if (element.key && !element.target)
108
146
  hasPrimaryKeyChange = true;
109
- });
147
+ });
148
+ }
110
149
  extensions.push(added);
111
150
  }
112
151
  }
@@ -118,128 +157,101 @@ function getExtensionAndMigrations(beforeModel, options, { extensions, migration
118
157
 
119
158
  const migration = { migrate: name };
120
159
 
121
- Object.keys(relevantProperties).forEach(prop => {
122
- if (artifact[prop] !== otherArtifact[prop]) {
160
+ Object.keys(relevantProperties).forEach((prop) => {
161
+ if (artifact[prop] !== otherArtifact[prop])
123
162
  changedProperties[prop] = changedElement(artifact[prop], otherArtifact[prop] || null);
124
- }
125
163
  });
126
164
 
127
165
  const removedConstraints = {};
128
166
 
129
167
  // HDI (src === 'hdi) does handle table constraints via separate files and therefore no delta handling needed
130
- if(options.src === 'sql' && (artifact.$tableConstraints || otherArtifact.$tableConstraints)) {
168
+ if (options.src === 'sql' && (artifact.$tableConstraints || otherArtifact.$tableConstraints)) {
131
169
  const current = artifact.$tableConstraints || {};
132
170
  const old = otherArtifact.$tableConstraints || {};
133
171
  let changes = false;
134
- const constraintTypes = ['unique', 'referential'];
135
- constraintTypes.forEach(constraintType => {
172
+ const constraintTypes = [ 'unique', 'referential' ];
173
+ constraintTypes.forEach((constraintType) => {
136
174
  // We only render/handle referential constraints for specific cases
137
- if(hasReferentialConstraints(options) || constraintType !== 'referential')
138
- if(current[constraintType] || old[constraintType]) {
175
+ if (hasReferentialConstraints(options) || constraintType !== 'referential') {
176
+ if (current[constraintType] || old[constraintType]) {
139
177
  removedConstraints[constraintType] = Object.create(null);
140
178
 
141
179
  const cnew = current[constraintType] || Object.create(null);
142
180
  const cold = old[constraintType] || Object.create(null);
143
181
  forEachKey(cnew, (constraintName) => {
144
- if(cold[constraintName]) {
145
- // constraint changed - add it to "removedConstraints" to drop-create it.
146
- // Quick-and-dirty compare with JSON.stringify - false-positive will just be a drop-create
147
- if(JSON.stringify(cold[constraintName]) !== JSON.stringify(cnew[constraintName])) {
182
+ if (cold[constraintName]) {
183
+ // constraint changed - add it to "removedConstraints" to drop-create it.
184
+ // Quick-and-dirty compare with JSON.stringify - false-positive will just be a drop-create
185
+ if (JSON.stringify(cold[constraintName]) !== JSON.stringify(cnew[constraintName])) {
148
186
  changes = true;
149
187
  removedConstraints[constraintType][constraintName] = cold[constraintName];
150
- if(options.constraintsInCreateTable || constraintType !== 'referential') // schedule an "ADD"
151
- extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
152
- } else {
188
+ if (options.constraintsInCreateTable || constraintType !== 'referential') // schedule an "ADD"
189
+ extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
190
+ }
191
+ else {
153
192
  unchangedConstraints.add(constraintName);
154
193
  }
155
- } else if(options.constraintsInCreateTable || constraintType !== 'referential'){
156
- // Sometimes referential constraints are anyway added via ALTER - no need to add them as explicit extensions then
157
- extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
194
+ }
195
+ else if (options.constraintsInCreateTable || constraintType !== 'referential') {
196
+ // Sometimes referential constraints are anyway added via ALTER - no need to add them as explicit extensions then
197
+ extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
158
198
  }
159
199
  });
160
200
 
161
201
  forEachKey(cold, (constraintName) => {
162
- if(!cnew[constraintName]) {
202
+ if (!cnew[constraintName]) {
163
203
  changes = true;
164
204
  removedConstraints[constraintType][constraintName] = cold[constraintName];
165
205
  }
166
- })
206
+ });
167
207
  }
208
+ }
168
209
  });
169
210
 
170
- if(changes)
211
+ if (changes)
171
212
  migration.removeConstraints = removedConstraints;
172
213
  }
173
214
 
174
- if (Object.keys(changedProperties).length > 0) {
215
+ if (Object.keys(changedProperties).length > 0)
175
216
  migration.properties = changedProperties;
176
- }
217
+
177
218
 
178
219
  forEachMember(otherArtifact, getElementComparator(artifact, removedElements), [ 'definitions', name ], true, { elementsOnly: true });
179
220
  if (Object.keys(removedElements).length > 0) {
180
221
  migration.remove = removedElements;
181
- if(!hasPrimaryKeyChange)
222
+ if (!hasPrimaryKeyChange) {
182
223
  forEach(removedElements, (_name, change) => {
183
- if(change.key && !change.target)
224
+ if (change.key && !change.target)
184
225
  hasPrimaryKeyChange = true;
185
226
  });
227
+ }
186
228
  }
187
229
 
188
230
  forEachMember(artifact, getElementComparator(otherArtifact, null, changedElements), [ 'definitions', name ], true, { elementsOnly: true });
189
231
  if (Object.keys(changedElements).length > 0) {
190
232
  migration.change = changedElements;
191
- if(!hasPrimaryKeyChange)
233
+ if (!hasPrimaryKeyChange) {
192
234
  forEach(changedElements, (_name, change) => {
193
- if(!change.onlyDoc && (change.old.key || change.new.key) && !change.new.target && !change.old.target) {
194
- // For to.hdi.migration: Just drop-create (commented out), for to.sql.migration: Handle case where we add/remove "key" keyword, no drop-create otherwise
195
- if(options.sqlDialect === 'hana' && options.src === 'hdi' || (!change.old.key || !change.new.key)) {
235
+ if (!change.onlyDoc && (change.old.key || change.new.key) && !change.new.target && !change.old.target) {
236
+ // For to.hdi.migration: Just drop-create (commented out), for to.sql.migration: Handle case where we add/remove "key" keyword, no drop-create otherwise
237
+ if (options.sqlDialect === 'hana' && options.src === 'hdi' || (!change.old.key || !change.new.key))
196
238
  hasPrimaryKeyChange = true;
197
- }
198
239
  }
199
240
  });
241
+ }
200
242
  }
201
243
 
202
- if (migration.properties || migration.remove || migration.change || migration.removeConstraints) {
244
+ if (migration.properties || migration.remove || migration.change || migration.removeConstraints)
203
245
  migrations.push(migration);
204
- }
205
- }
206
-
207
- const otherArtifact = beforeModel.definitions[name];
208
- const isPersisted = isPersistedAsTable(artifact);
209
- const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
210
-
211
- // to make it easier to know which views to drop-create
212
- if(isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
213
- // TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
214
- artifact[isChanged] = JSON.stringify(artifact) !== JSON.stringify(otherArtifact);
215
- }
216
-
217
- // Looking for added entities and added/deleted/changed elements.
218
- // Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
219
-
220
- if (!isPersisted) // Artifact not persisted in afterModel.
221
- return;
222
-
223
- if (!isPersistedOther) {
224
- extensions[name] = artifact;
225
- return;
226
- }
227
-
228
- // Artifact changed?
229
-
230
- addElements();
231
- changePropsOrRemoveOrChangeElements();
232
- if(hasPrimaryKeyChange) {
233
- changedPrimaryKeys.push(name);
234
246
  }
235
247
  };
236
248
  }
237
249
  /**
238
250
  * Calculate all deleted entities.
239
- *
240
- * @param {CSN.Model} afterModel
241
- * @param {CSN.Options} options
242
- * @param {object} returnObj
251
+ *
252
+ * @param {CSN.Model} afterModel
253
+ * @param {CSN.Options} options
254
+ * @param {object} returnObj
243
255
  * @returns {function}
244
256
  */
245
257
  function getDeletions(afterModel, options, { deletions }) {
@@ -251,8 +263,9 @@ function getDeletions(afterModel, options, { deletions }) {
251
263
  // Looking for deleted entities only.
252
264
  if (isPersisted && !isPersistedOther) {
253
265
  deletions[name] = artifact;
254
- // eslint-disable-next-line sonarjs/no-duplicated-branches
255
- } else if (isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
266
+ }
267
+ // eslint-disable-next-line sonarjs/no-duplicated-branches
268
+ else if (isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
256
269
  deletions[name] = artifact;
257
270
  }
258
271
  };
@@ -260,37 +273,40 @@ function getDeletions(afterModel, options, { deletions }) {
260
273
 
261
274
  function getElementComparator(otherArtifact, addedElementsDict = null, changedElementsDict = null) {
262
275
  return function compareElements(element, name) {
263
- if (element.$ignore) {
276
+ if (element.$ignore)
264
277
  return;
265
- }
278
+
266
279
 
267
280
  const otherElement = otherArtifact.elements[name];
268
281
  if (otherElement && !otherElement.$ignore) {
269
282
  // Element type changed?
270
- if (!changedElementsDict) {
283
+ if (!changedElementsDict)
271
284
  return;
272
- }
285
+
273
286
  if (relevantTypeChange(element.type, otherElement.type) || typeParametersChanged(element, otherElement)) {
274
287
  // Type or parameters, e.g. association target, changed.
275
- if(otherElement.notNull && element.notNull === undefined) {
276
- element.$notNull = false; // Explicitly set notNull to the implicit default so we render the correct ALTER
277
- }
288
+ if (otherElement.notNull && element.notNull === undefined && !element.key || otherElement.key && !element.key && !element.notNull)
289
+ setProp(element, '$notNull', false); // Explicitly set notNull to the implicit default so we render the correct ALTER
290
+
291
+ if (otherElement.default && element.default === undefined)
292
+ setProp(element, '$default', { val: null }); // Explicitly set default to the implicit default "null" so we render the correct ALTER
293
+
278
294
  changedElementsDict[name] = changedElement(element, otherElement);
279
- } else if(docCommentChanged(element, otherElement)) {
295
+ }
296
+ else if (docCommentChanged(element, otherElement)) {
280
297
  changedElementsDict[name] = { ...changedElement(element, otherElement), onlyDoc: true };
281
298
  }
282
299
 
283
300
  return;
284
301
  }
285
302
 
286
- if (addedElementsDict) {
303
+ if (addedElementsDict)
287
304
  addedElementsDict[name] = element;
288
- }
289
- }
305
+ };
290
306
  }
291
307
 
292
308
  function relevantTypeChange(type, otherType) {
293
- return otherType !== type && !isSuperflousHanaTypeChange(otherType, type) && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
309
+ return otherType !== type && !isSuperflousHanaTypeChange(otherType, type) && ![ type, otherType ].every(t => [ 'cds.Association', 'cds.Composition' ].includes(t));
294
310
  }
295
311
 
296
312
  const superflousTypeChanges = {
@@ -301,7 +317,7 @@ const superflousTypeChanges = {
301
317
  'cds.UTCDateTime': 'cds.DateTime',
302
318
  'cds.UTCTimestamp': 'cds.Timestamp',
303
319
  'cds.LocalDate': 'cds.Date',
304
- 'cds.LocalTime': 'cds.Time' ,
320
+ 'cds.LocalTime': 'cds.Time',
305
321
  };
306
322
 
307
323
  /**
@@ -319,11 +335,11 @@ function isSuperflousHanaTypeChange( before, after ) {
319
335
  /**
320
336
  * If the element has one of the superflous types, do the change so we don't accidentally
321
337
  * pass such an old type into the SQL renderer.
322
- * @param {CSN.Element} element
338
+ * @param {CSN.Element} element
323
339
  * @returns {CSN.Element}
324
340
  */
325
341
  function remapType( element ) {
326
- if(element?.type && superflousTypeChanges[element.type])
342
+ if (element?.type && superflousTypeChanges[element.type])
327
343
  element.type = superflousTypeChanges[element.type];
328
344
  return element;
329
345
  }
@@ -351,23 +367,18 @@ function deepEqual(a, b, include = () => true, depth = 0) {
351
367
  return Object.keys(a).reduce((prev, key) => prev && (!include(key, depth) || deepEqual(a[key], b[key], include, depth + 1)), true);
352
368
  }
353
369
 
354
- return isObject(a)
355
- ? isObject(b)
356
- ? samePropertyCount() && allPropertiesEqual()
357
- : false
358
- : a === b;
370
+ if (isObject(a)) {
371
+ return isObject(b)
372
+ ? samePropertyCount() && allPropertiesEqual()
373
+ : false;
374
+ }
375
+ return a === b;
359
376
  }
360
377
 
361
378
  function docCommentChanged(element, otherElement) {
362
379
  return element.doc && !otherElement.doc || otherElement.doc && !element.doc || element.doc && element.doc !== otherElement.doc;
363
380
  }
364
381
 
365
- const relevantProperties = {
366
- 'doc': true,
367
- '@sql.prepend': true,
368
- '@sql.append': true
369
- };
370
-
371
382
  /**
372
383
  * Returns whether any type parameters differ between two given elements. Ignores whether types themselves differ (`type` property) and ignores
373
384
  * diff in doc comments.
@@ -378,18 +389,20 @@ const relevantProperties = {
378
389
  function typeParametersChanged(element, otherElement) {
379
390
  const checked = new Set();
380
391
  for (const key in element) {
381
- if (Object.prototype.hasOwnProperty.call(element, key))
382
- if((!key.startsWith('@') || relevantProperties[key]) && key !== 'type' && key !== 'doc') {
392
+ if (Object.prototype.hasOwnProperty.call(element, key)) {
393
+ if ((!key.startsWith('@') || relevantProperties[key]) && key !== 'type' && key !== 'doc') {
383
394
  checked.add(key);
384
- if(!deepEqual(element[key], otherElement[key]))
395
+ if (!deepEqual(element[key], otherElement[key]))
385
396
  return true;
386
- }
397
+ }
398
+ }
387
399
  }
388
400
 
389
401
  for (const key in otherElement) {
390
- if (Object.prototype.hasOwnProperty.call(otherElement, key))
391
- if((!key.startsWith('@') || relevantProperties[key]) && key !== 'type' && key !== 'doc' && !checked.has(key))
402
+ if (Object.prototype.hasOwnProperty.call(otherElement, key)) {
403
+ if ((!key.startsWith('@') || relevantProperties[key]) && key !== 'type' && key !== 'doc' && !checked.has(key))
392
404
  return true;
405
+ }
393
406
  }
394
407
 
395
408
  return false;
@@ -398,7 +411,7 @@ function typeParametersChanged(element, otherElement) {
398
411
  function addedElements(entity, elements) {
399
412
  return {
400
413
  extend: entity,
401
- elements
414
+ elements,
402
415
  };
403
416
  }
404
417
 
@@ -408,24 +421,24 @@ function addedConstraint(entity, constraint, constraintName, constraintType) {
408
421
  constraint,
409
422
  constraintName,
410
423
  constraintType,
411
- }
424
+ };
412
425
  }
413
426
 
414
427
  function changedElement(element, otherElement) {
415
428
  return {
416
429
  old: remapType(otherElement),
417
- new: element
430
+ new: element,
418
431
  };
419
432
  }
420
433
 
421
434
  function hasReferentialConstraints(options) {
422
- return options.src === 'sql' && (options.sqlDialect === 'postgres' || options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite')
435
+ return options.src === 'sql' && (options.sqlDialect === 'postgres' || options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite');
423
436
  }
424
437
 
425
438
  module.exports = {
426
439
  compareModels,
427
440
  deepEqual,
428
- isChanged
441
+ isChanged,
429
442
  };
430
443
 
431
444
  /**
@@ -145,6 +145,7 @@ optionProcessor
145
145
  O, toOdata [options] <files...> Generate ODATA metadata and annotations
146
146
  C, toCdl <files...> Generate CDS source files
147
147
  Q, toSql [options] <files...> Generate SQL DDL statements
148
+ J, forJava [options] <files...> Generate CSN for the Java Runtime
148
149
  toCsn [options] <files...> (default) Generate original model as CSN
149
150
  parseCdl [options] <file> Generate a CSN that is close to the CDL source.
150
151
  explain <message-id> Explain a compiler message.
@@ -154,7 +155,8 @@ optionProcessor
154
155
  manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
155
156
  add / modify referential constraints.
156
157
  inspect [options] <files...> (internal) Inspect the given CDS files.
157
- toEffectiveCsn [options] <files...> (internal) Get an effective CSN for SEAL; requires beta mode
158
+ toEffectiveCsn [options] <files...> (internal) Get an effective CSN; requires beta mode
159
+ forSeal [options] <files...> (internal) Get a SEAL CSN
158
160
 
159
161
  Environment variables
160
162
  NO_COLOR If set, compiler messages (/output) will not be colored.
@@ -224,6 +226,7 @@ optionProcessor.command('O, toOdata')
224
226
  .option(' --odata-containment')
225
227
  .option(' --odata-capabilities-pullup')
226
228
  .option(' --odata-openapi-hints')
229
+ .option(' --edm4openapi', { optionName: 'edm4OpenAPI' })
227
230
  .option(' --odata-proxies')
228
231
  .option(' --odata-x-service-refs')
229
232
  .option(' --odata-foreign-keys')
@@ -254,6 +257,7 @@ optionProcessor.command('O, toOdata')
254
257
  --odata-containment Generate Containment Navigation Properties for compositions (V4 only)
255
258
  --odata-capabilities-pullup Rewrite @Capabilities annotations (V4 containment only).
256
259
  --odata-openapi-hints Add various annotations to JSON API as input for OpenAPI generation.
260
+ --edm4openapi Downgrade some errors to warnings for OpenAPI generation.
257
261
  --odata-proxies Generate Proxies for out-of-service navigation targets (V4 only).
258
262
  --odata-x-service-refs Generate schema references (V4 only).
259
263
  --odata-foreign-keys Render foreign keys in structured format (V4 only)
@@ -274,6 +278,17 @@ optionProcessor.command('O, toOdata')
274
278
  those views, that only have an association to a localized entity/view.
275
279
  `);
276
280
 
281
+ optionProcessor.command('J, forJava')
282
+ .option('-h, --help')
283
+ .help(`
284
+ Usage: cdsc forJava [options] <files...>
285
+
286
+ Generate CSN (structured) for Java Runtime.
287
+
288
+ Options
289
+ -h, --help Show this help text
290
+ `);
291
+
277
292
  optionProcessor.command('C, toCdl')
278
293
  .option('-h, --help')
279
294
  .help(`
@@ -530,6 +545,10 @@ optionProcessor.command('inspect')
530
545
 
531
546
  optionProcessor.command('toEffectiveCsn')
532
547
  .option('-h, --help')
548
+ .option('--resolve-simple-types <val>', { valid: ['true', 'false'] } )
549
+ .option('--resolve-projections <val>', { valid: ['true', 'false'] } )
550
+ .option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
551
+ .option('--keep-localized <val>', { valid: ['true', 'false'] } )
533
552
  .positionalArgument('<files...>')
534
553
  .help(`
535
554
  Usage: cdsc toEffectiveCsn [options] <files...>
@@ -539,7 +558,35 @@ optionProcessor.command('toEffectiveCsn')
539
558
  Beta mode is required.
540
559
 
541
560
  Options
542
- -h, --help Show this help text
561
+ -h, --help Show this help text
562
+ --resolve-simple-types <val> Resolve simple types:
563
+ true: (default) resolve simple type references to their simple base type
564
+ false: do not resolve simple type references
565
+ --resolve-projections <val> Resolve projections:
566
+ true: (default) transform projections into ordinary views with SELECT
567
+ false: leave them as real projections
568
+ --remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
569
+ true: remap annotations
570
+ false:(default) leave them as is
571
+ --keep-localized <val> Keep '.localized' property in the CSN:
572
+ true: property is kept
573
+ false:(default) property is deleted
574
+ `);
575
+
576
+ optionProcessor.command('forSeal')
577
+ .option('-h, --help')
578
+ .option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
579
+ .positionalArgument('<files...>')
580
+ .help(`
581
+ Usage: cdsc forSeal [options] <files...>
582
+
583
+ (internal): Get the SEAL CSN model compiled from the provided CDS files.
584
+
585
+ Options
586
+ -h, --help Show this help text
587
+ --remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
588
+ true: (default) remap annotations
589
+ false: leave them as is
543
590
  `);
544
591
 
545
592
  module.exports = {
@@ -97,12 +97,12 @@ class DuplicateChecker {
97
97
  else
98
98
  namingMode = 'plain';
99
99
 
100
- error(null, [ 'definitions', artifact.modelName ],
101
- { name: collidesWith.modelName, prop: namingMode, '#': artifact.modelName.includes('.') ? 'dots' : 'std' },
102
- {
103
- std: 'Artifact name can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
104
- dots: 'Artifact name containing dots can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
105
- });
100
+ error(null, [ 'definitions', artifact.modelName ], {
101
+ name: collidesWith.modelName, prop: namingMode, '#': artifact.modelName.includes('.') ? 'dots' : 'std',
102
+ }, {
103
+ std: 'Artifact name can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
104
+ dots: 'Artifact name containing dots can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
105
+ });
106
106
  });
107
107
  }
108
108
  artifacts.forEach((artifact) => {
@@ -32,6 +32,7 @@ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
32
32
  warning(null, null, { prop: sqlDialect || 'plain' }, 'Referential Constraints are not available for sql dialect $(PROP)');
33
33
 
34
34
  if (drop && alter)
35
+ // eslint-disable-next-line cds-compiler/message-no-quotes
35
36
  error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
36
37
 
37
38
  // Of course, we want the database constraints
@@ -9,13 +9,14 @@ const { forEachDefinition, normalizeTypeRef } = require('../model/csnUtils');
9
9
  const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
10
10
  const { isBetaEnabled } = require('../base/model');
11
11
  const { ModelError } = require('../base/error');
12
- const { typeParameters, specialFunctions, isAnnotationExpression } = require('../compiler/builtins');
12
+ const { typeParameters, specialFunctions } = require('../compiler/builtins');
13
+ const { isAnnotationExpression } = require('../base/builtins');
13
14
  const { forEach } = require('../utils/objectUtils');
14
15
  const {
15
- isBuiltinType,
16
16
  generatedByCompilerVersion,
17
17
  getNormalizedQuery,
18
18
  } = require('../model/csnUtils');
19
+ const { isBuiltinType } = require('../base/builtins');
19
20
  const { cloneFullCsn } = require('../model/cloneCsn');
20
21
 
21
22
  const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
@@ -613,10 +614,12 @@ function csnToCdl( csn, options, msg ) {
613
614
  else if (element['#'] !== undefined) { // enum symbol reference
614
615
  result += ` = #${element['#']}`;
615
616
  }
616
- else if (!isCalcElement || !isDirectAssocOrComp(element.type)) {
617
+ else if (!isCalcElement || !isDirectAssocOrComp(element.type) && !element.$filtered) {
617
618
  // If the element is a calculated element _and_ a direct association or
618
619
  // composition, we'd render `Association to F on (cond) = calcValue;` which
619
620
  // would alter the ON-condition.
621
+ // If it is a calculated element _and_ an indirect association (via type chain),
622
+ // we'd get a cast to an association.
620
623
  const props = renderTypeReferenceAndProps(element, env);
621
624
  if (props !== '')
622
625
  result += ` : ${props}`;