@sap/cds-compiler 4.0.2 → 4.2.2

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 (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -8,6 +8,8 @@ const {
8
8
  isPersistedAsView
9
9
  } = require('../model/csnUtils');
10
10
  const { isBetaEnabled } = require('../base/model');
11
+ const { forEachKey } = require('../utils/objectUtils');
12
+
11
13
  // used to mark a view as changed so we know to drop-create it
12
14
  const isChanged = Symbol('Marks a view as changed');
13
15
 
@@ -25,19 +27,18 @@ function compareModels(beforeModel, afterModel, options) {
25
27
  if(!(options && options.testMode)) // no $version with testMode
26
28
  validateCsnVersions(beforeModel, afterModel, options);
27
29
 
28
- const deletedEntities = Object.create(null);
29
- const elementAdditions = [];
30
- const migrations = []; // element changes/removals or changes of entity properties
30
+ const returnObj = Object.create(null);
31
+ returnObj.definitions = afterModel.definitions;
32
+ returnObj.deletions = Object.create(null);
33
+ returnObj.extensions = [];
34
+ returnObj.migrations = []; // element changes/removals or changes of entity properties
35
+ returnObj.unchangedConstraints = new Set();
31
36
 
32
37
  // There is currently no use in knowing the added entities only. If this changes, hand in `addedEntities` to `getArtifactComparator` below.
33
- forEachDefinition(afterModel, getArtifactComparator(beforeModel, options, null, null, elementAdditions, migrations));
34
- forEachDefinition(beforeModel, getArtifactComparator(afterModel, options, null, deletedEntities, null, null));
38
+ forEachDefinition(afterModel, getExtensionAndMigrations(beforeModel, options, returnObj));
39
+ forEachDefinition(beforeModel, getDeletions(afterModel, options, returnObj));
40
+
35
41
 
36
- const returnObj = Object.create(null);
37
- returnObj.definitions = afterModel.definitions;
38
- returnObj.deletions = deletedEntities;
39
- returnObj.extensions = elementAdditions;
40
- returnObj.migrations = migrations;
41
42
  return returnObj;
42
43
  }
43
44
 
@@ -65,13 +66,21 @@ function validateCsnVersions(beforeModel, afterModel, options) {
65
66
  }
66
67
  }
67
68
 
68
- function getArtifactComparator(otherModel, options, addedEntities, deletedEntities, elementAdditions, migrations) {
69
+ /**
70
+ * Calculate extensions, migrations and unchangedConstraints
71
+ *
72
+ * @param {CSN.Model} beforeModel
73
+ * @param {CSN.Options} options
74
+ * @param {object} returnObj
75
+ * @returns {function}
76
+ */
77
+ function getExtensionAndMigrations(beforeModel, options, { extensions, migrations, unchangedConstraints }) {
69
78
  return function compareArtifacts(artifact, name) {
70
79
  function addElements() {
71
80
  const elements = {};
72
81
  forEachMember(artifact, getElementComparator(otherArtifact, elements), [ 'definitions', name ], true, { elementsOnly: true });
73
82
  if (Object.keys(elements).length > 0) {
74
- elementAdditions.push(addedElements(name, elements));
83
+ extensions.push(addedElements(name, elements));
75
84
  }
76
85
  }
77
86
  function changePropsOrRemoveOrChangeElements() {
@@ -92,6 +101,54 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
92
101
  changedProperties[prop.name] = changedElement(artifact[prop.name], otherArtifact[prop.name] || null);
93
102
  }
94
103
  });
104
+
105
+ const removedConstraints = {};
106
+
107
+ // HDI (src === 'hdi) does handle table constraints via separate files and therefore no delta handling needed
108
+ if(options.src === 'sql' && (artifact.$tableConstraints || otherArtifact.$tableConstraints)) {
109
+ const current = artifact.$tableConstraints || {};
110
+ const old = otherArtifact.$tableConstraints || {};
111
+ let changes = false;
112
+ const constraintTypes = ['unique', 'referential'];
113
+ constraintTypes.forEach(constraintType => {
114
+ // We only render/handle referential constraints for specific cases
115
+ if(hasReferentialConstraints(options) || constraintType !== 'referential')
116
+ if(current[constraintType] || old[constraintType]) {
117
+ removedConstraints[constraintType] = Object.create(null);
118
+
119
+ const cnew = current[constraintType] || Object.create(null);
120
+ const cold = old[constraintType] || Object.create(null);
121
+ forEachKey(cnew, (constraintName) => {
122
+ if(cold[constraintName]) {
123
+ // constraint changed - add it to "removedConstraints" to drop-create it.
124
+ // Quick-and-dirty compare with JSON.stringify - false-positive will just be a drop-create
125
+ if(JSON.stringify(cold[constraintName]) !== JSON.stringify(cnew[constraintName])) {
126
+ changes = true;
127
+ removedConstraints[constraintType][constraintName] = cold[constraintName];
128
+ if(options.constraintsInCreateTable || constraintType !== 'referential') // schedule an "ADD"
129
+ extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
130
+ } else {
131
+ unchangedConstraints.add(constraintName);
132
+ }
133
+ } else if(options.constraintsInCreateTable || constraintType !== 'referential'){
134
+ // Sometimes referential constraints are anyway added via ALTER - no need to add them as explicit extensions then
135
+ extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
136
+ }
137
+ });
138
+
139
+ forEachKey(cold, (constraintName) => {
140
+ if(!cnew[constraintName]) {
141
+ changes = true;
142
+ removedConstraints[constraintType][constraintName] = cold[constraintName];
143
+ }
144
+ })
145
+ }
146
+ });
147
+
148
+ if(changes)
149
+ migration.removeConstraints = removedConstraints;
150
+ }
151
+
95
152
  if (Object.keys(changedProperties).length > 0) {
96
153
  migration.properties = changedProperties;
97
154
  }
@@ -106,27 +163,15 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
106
163
  migration.change = changedElements;
107
164
  }
108
165
 
109
- if (migration.properties || migration.remove || migration.change) {
166
+ if (migration.properties || migration.remove || migration.change || migration.removeConstraints) {
110
167
  migrations.push(migration);
111
168
  }
112
169
  }
113
170
 
114
- const otherArtifact = otherModel.definitions[name];
171
+ const otherArtifact = beforeModel.definitions[name];
115
172
  const isPersisted = isPersistedAsTable(artifact);
116
173
  const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
117
174
 
118
- if (deletedEntities) {
119
- // Looking for deleted entities only.
120
- // Arguments are interchanged in this case: `artifact` from beforeModel and `otherArtifact` from afterModel.
121
- if (isPersisted && !isPersistedOther) {
122
- deletedEntities[name] = artifact;
123
- // eslint-disable-next-line sonarjs/no-duplicated-branches
124
- } else if(isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
125
- deletedEntities[name] = artifact;
126
- }
127
- return;
128
- }
129
-
130
175
  // to make it easier to know which views to drop-create
131
176
  if(isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
132
177
  // TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
@@ -136,37 +181,52 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
136
181
  // Looking for added entities and added/deleted/changed elements.
137
182
  // Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
138
183
 
139
- if (!isPersisted) {
140
- // Artifact not persisted in afterModel.
184
+ if (!isPersisted) // Artifact not persisted in afterModel.
141
185
  return;
142
- }
143
186
 
144
187
  if (!isPersistedOther) {
145
- if (addedEntities) {
146
- addedEntities[name] = artifact;
147
- }
188
+ extensions[name] = artifact;
148
189
  return;
149
190
  }
150
191
 
151
192
  // Artifact changed?
152
193
 
153
- if (elementAdditions) {
154
- addElements();
155
- }
156
- if (migrations) {
157
- changePropsOrRemoveOrChangeElements();
194
+ addElements();
195
+ changePropsOrRemoveOrChangeElements();
196
+ };
197
+ }
198
+ /**
199
+ * Calculate all deleted entities.
200
+ *
201
+ * @param {CSN.Model} afterModel
202
+ * @param {CSN.Options} options
203
+ * @param {object} returnObj
204
+ * @returns {function}
205
+ */
206
+ function getDeletions(afterModel, options, { deletions }) {
207
+ return function compareArtifacts(artifact, name) {
208
+ const otherArtifact = afterModel.definitions[name];
209
+ const isPersisted = isPersistedAsTable(artifact);
210
+ const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
211
+
212
+ // Looking for deleted entities only.
213
+ if (isPersisted && !isPersistedOther) {
214
+ deletions[name] = artifact;
215
+ // eslint-disable-next-line sonarjs/no-duplicated-branches
216
+ } else if (isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
217
+ deletions[name] = artifact;
158
218
  }
159
219
  };
160
220
  }
161
221
 
162
222
  function getElementComparator(otherArtifact, addedElementsDict = null, changedElementsDict = null) {
163
223
  return function compareElements(element, name) {
164
- if (element._ignore) {
224
+ if (element.$ignore) {
165
225
  return;
166
226
  }
167
227
 
168
228
  const otherElement = otherArtifact.elements[name];
169
- if (otherElement && !otherElement._ignore) {
229
+ if (otherElement && !otherElement.$ignore) {
170
230
  // Element type changed?
171
231
  if (!changedElementsDict) {
172
232
  return;
@@ -189,7 +249,42 @@ function getElementComparator(otherArtifact, addedElementsDict = null, changedEl
189
249
  }
190
250
 
191
251
  function relevantTypeChange(type, otherType) {
192
- return otherType !== type && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
252
+ return otherType !== type && !isSuperflousHanaTypeChange(otherType, type) && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
253
+ }
254
+
255
+ const superflousTypeChanges = {
256
+ // turn it into a real dict
257
+ __proto__: null,
258
+ // We used to put these types into the CSN, although they are just internal
259
+ // so we need to be robust against them now.
260
+ 'cds.UTCDateTime': 'cds.DateTime',
261
+ 'cds.UTCTimestamp': 'cds.Timestamp',
262
+ 'cds.LocalDate': 'cds.Date',
263
+ 'cds.LocalTime': 'cds.Time' ,
264
+ };
265
+
266
+ /**
267
+ * We removed some old SAP HANA types from the CSN and we now need to not
268
+ * detect them as changes.
269
+ *
270
+ * @param {string} before Type before
271
+ * @param {string} after Type after
272
+ * @returns {boolean}
273
+ */
274
+ function isSuperflousHanaTypeChange( before, after ) {
275
+ return superflousTypeChanges[before] ? superflousTypeChanges[before] === after : false;
276
+ }
277
+
278
+ /**
279
+ * If the element has one of the superflous types, do the change so we don't accidentally
280
+ * pass such an old type into the SQL renderer.
281
+ * @param {CSN.Element} element
282
+ * @returns {CSN.Element}
283
+ */
284
+ function remapType( element ) {
285
+ if(element?.type && superflousTypeChanges[element.type])
286
+ element.type = superflousTypeChanges[element.type];
287
+ return element;
193
288
  }
194
289
 
195
290
  /**
@@ -239,13 +334,26 @@ function addedElements(entity, elements) {
239
334
  };
240
335
  }
241
336
 
337
+ function addedConstraint(entity, constraint, constraintName, constraintType) {
338
+ return {
339
+ extend: entity,
340
+ constraint,
341
+ constraintName,
342
+ constraintType,
343
+ }
344
+ }
345
+
242
346
  function changedElement(element, otherElement) {
243
347
  return {
244
- old: otherElement,
348
+ old: remapType(otherElement),
245
349
  new: element
246
350
  };
247
351
  }
248
352
 
353
+ function hasReferentialConstraints(options) {
354
+ return options.src === 'sql' && (options.sqlDialect === 'postgres' || options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite')
355
+ }
356
+
249
357
  module.exports = {
250
358
  compareModels,
251
359
  deepEqual,
@@ -13,20 +13,31 @@ function isKey( element ) {
13
13
  module.exports = {
14
14
  sqlite: getFilterObject(
15
15
  'sqlite',
16
- function forEachExtension(extend, name, element, error) {
17
- if (isKey(element)) { // Key must not be extended
18
- error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite' }, 'Added element $(ID) is a primary key change and will not work with dialect $(NAME)');
16
+ function forEachExtension(extend, name, elementOrConstraint, { error, warning }) {
17
+ if (isKey(elementOrConstraint)) { // Key must not be extended
18
+ error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite', '#': 'std' } );
19
+ return false;
19
20
  }
21
+ else if (elementOrConstraint.parentTable) { // constraints have a .parentTable
22
+ warning('def-unsupported-constraint-add', [ 'definitions', elementOrConstraint.parentTable, 'elements', elementOrConstraint.paths ? name : name.slice(elementOrConstraint.parentTable.length + 1) ], { id: elementOrConstraint.identifier || name, name: 'sqlite' },
23
+ 'Ignoring add of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
24
+ return false;
25
+ }
26
+
27
+ return true;
20
28
  },
21
29
  function forEachMigration(migrate, name, migration, change, error) {
22
30
  const newIsKey = isKey(migration.new);
23
31
  const oldIsKey = isKey(migration.old);
24
- if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) { // Turned into key or key was removed
25
- error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite' }, 'Changed element $(ID) is a primary key change and will not work with dialect $(NAME)');
26
- }
27
- else { // Ignore simple migrations
32
+ if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) // Turned into key or key was removed
33
+ error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite', '#': 'changed' } );
34
+ else
28
35
  delete change[name];
29
- }
36
+ },
37
+ function forEachConstraintRemoval(constraintRemovals, name, constraint, warning) {
38
+ warning('def-unsupported-constraint-drop', [ 'definitions', constraint.parentTable, 'elements', constraint.paths ? name : name.slice(constraint.parentTable.length + 1) ], { id: constraint.identifier || name, name: 'sqlite' },
39
+ 'Ignoring drop of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
40
+ delete constraintRemovals[name];
30
41
  }
31
42
  ),
32
43
  postgres: getFilterObject('postgres'),
@@ -35,22 +46,32 @@ module.exports = {
35
46
  csn: filterCsn,
36
47
  };
37
48
 
38
- function getFilterObject( dialect, extensionCallback, migrationCallback ) {
49
+ function getFilterObject( dialect, extensionCallback, migrationCallback, removeConstraintsCallback ) {
39
50
  return {
40
- // will be called with a simple Array.forEach
41
- extension: ({ elements, extend }, error) => {
42
- if (extensionCallback) {
43
- forEach(elements, (name, element) => {
44
- extensionCallback(extend, name, element, error);
45
- });
46
- }
51
+ // will be called with a simple Array.filter, as we need to filter constraint `ADD` for SQLite
52
+ extension: ({
53
+ elements, constraint, constraintName, extend,
54
+ }, { error, message, warning }) => {
55
+ let returnValue = true;
56
+ forEach(elements, (name, element) => {
57
+ if (dialect !== 'sqlite' && isKey(element))
58
+ message('type-unsupported-key-change', [ 'definitions', extend, 'elements', name ], { id: name, '#': 'std' } );
59
+ else if (extensionCallback && !extensionCallback(extend, name, element, { error, warning }))
60
+ returnValue = false;
61
+ });
62
+
63
+ if (constraint && extensionCallback && !extensionCallback(extend, constraintName, constraint, { error, warning }))
64
+ returnValue = false;
65
+
66
+ return returnValue;
47
67
  },
48
- // will be called with a Array.map, as we need to filter "change" for SQLite
49
- migration: ({ change, migrate, remove }, error) => {
68
+ // will be called with a Array.forEach
69
+ migration: ({
70
+ change, migrate, remove, removeConstraints,
71
+ }, { error, warning, message }) => {
50
72
  forEach(remove, (name) => {
51
73
  error('def-unsupported-element-drop', [ 'definitions', migrate, 'elements', name ], {}, 'Dropping elements is not supported');
52
74
  });
53
-
54
75
  forEach(change, (name, migration) => {
55
76
  const loc = [ 'definitions', migrate, 'elements', name ];
56
77
  if (migration.new.type === migration.old.type && migration.new.length < migration.old.length)
@@ -61,11 +82,22 @@ function getFilterObject( dialect, extensionCallback, migrationCallback ) {
61
82
  error('type-unsupported-precision-change', loc, { id: name }, 'Changed element $(ID) is a precision change and is not supported');
62
83
  else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type))
63
84
  error('type-unsupported-change', loc, { 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');
85
+ else if (dialect !== 'sqlite' && isKey(migration.new) && !isKey(migration.old)) // key added/changed - pg, hana and sqlite do not support it, h2 probably also - issues when data is in the table already
86
+ message('type-unsupported-key-change', [ 'definitions', migrate, 'elements', name ], { id: name, '#': 'changed' } );
64
87
  else if (migrationCallback)
65
88
  migrationCallback(migrate, name, migration, change, error);
66
89
 
67
90
  // TODO: precision/scale growth
68
91
  });
92
+
93
+ if (removeConstraintsCallback) {
94
+ const constraintTypes = [ 'unique', 'referential' ];
95
+ constraintTypes.forEach((constraintType) => {
96
+ forEach(removeConstraints?.[constraintType], (name, constraint) => {
97
+ removeConstraintsCallback(removeConstraints[constraintType], name, constraint, warning);
98
+ });
99
+ });
100
+ }
69
101
  },
70
102
  deletion: ([ artifactName, artifact ], error) => {
71
103
  if (isPersistedAsTable(artifact))
@@ -110,10 +142,8 @@ function typeChangeIsNotCompatible( dialect, before, after ) {
110
142
  */
111
143
  function filterCsn( csn ) {
112
144
  const annosToKeep = {
113
- '@cds.persistence.skip': true,
114
- '@cds.persistence.exists': true,
115
- '@cds.persistence.table': true,
116
- '@cds.persistence.name': true,
145
+ // + @cds.persistence.*
146
+ '@assert.integrity': true,
117
147
  '@sql.append': true,
118
148
  '@sql.prepend': true,
119
149
  };
@@ -122,14 +152,14 @@ function filterCsn( csn ) {
122
152
  elements: (parent, prop, elements) => {
123
153
  forEachValue(elements, (element) => {
124
154
  forEachKey(element, (key) => {
125
- if ((key.startsWith('@') && !annosToKeep[key]) || key === 'keys')
155
+ if ((key.startsWith('@') && !key.startsWith('@cds.persistence.') && !annosToKeep[key]) || key === 'keys')
126
156
  delete element[key];
127
157
  });
128
158
  });
129
159
  },
130
160
  }, [ (artifact) => {
131
161
  forEachKey(artifact, (key) => {
132
- if (key.startsWith('@') && !annosToKeep[key])
162
+ if (key.startsWith('@') && !key.startsWith('@cds.persistence.') && !annosToKeep[key])
133
163
  delete artifact[key];
134
164
  });
135
165
  } ]);
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { createOptionProcessor } = require('./base/optionProcessorHelper');
4
+ const { availableBetaFlags } = require('./base/model');
4
5
 
5
6
  // This option processor is used both by the command line parser (to translate cmd line options
6
7
  // into an options object) and by the API functions (to verify options)
@@ -20,7 +21,7 @@ optionProcessor
20
21
  .option(' --color <mode>', ['auto', 'always', 'never'])
21
22
  .option('-o, --out <dir>')
22
23
  .option(' --cds-home <dir>')
23
- .option(' --fuzzy-csn-error')
24
+ .option(' --module-lookup-directories <list>')
24
25
  .option(' --trace-fs')
25
26
  .option(' --error <id-list>')
26
27
  .option(' --warn <id-list>')
@@ -77,7 +78,8 @@ optionProcessor
77
78
  never:
78
79
  -o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout>
79
80
  --cds-home <dir> When set, modules starting with '@sap/cds/' are searched in <dir>
80
- --fuzzy-csn-error Report free-style CSN properties as errors
81
+ --module-lookup-directories <list> Comma separated list of directories to look
82
+ for CDS modules. Default is 'node_modules/'.
81
83
  -- Indicate the end of options (helpful if source names start with "-")
82
84
 
83
85
  Type options
@@ -102,12 +104,11 @@ optionProcessor
102
104
  --beta-mode Enable all unsupported, incomplete (beta) features
103
105
  --beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
104
106
  Valid values are:
105
- hanaAssocRealCardinality
106
- mapAssocToJoinCardinality
107
- enableUniversalCsn
108
- aspectWithoutElements
109
- odataTerms
110
- optionalActionFunctionParameters
107
+ ${
108
+ Object.keys(availableBetaFlags).sort()
109
+ .filter(flag => availableBetaFlags[flag])
110
+ .join('\n' + ' '.repeat(30))
111
+ }
111
112
  --deprecated <list> Comma separated list of deprecated options.
112
113
  Valid values are:
113
114
  eagerPersistenceForGeneratedEntities
@@ -228,6 +229,7 @@ optionProcessor.command('O, toOdata')
228
229
  .option('-f, --odata-format <format>', ['flat', 'structured'])
229
230
  .option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
230
231
  .option('-s, --service-names <list>')
232
+ .option(' --fewer-localized-views')
231
233
  .help(`
232
234
  Usage: cdsc toOdata [options] <files...>
233
235
 
@@ -264,6 +266,8 @@ optionProcessor.command('O, toOdata')
264
266
  source (like "quoted", but using element names with dots)
265
267
  -s, --service-names <list> List of comma-separated service names to be rendered
266
268
  (default) empty, all services are rendered
269
+ --fewer-localized-views If set, the backends will not create localized convenience views for
270
+ those views, that only have an association to a localized entity/view.
267
271
  `);
268
272
 
269
273
  optionProcessor.command('C, toCdl')
@@ -296,6 +300,7 @@ optionProcessor.command('Q, toSql')
296
300
  .option(' --disable-hana-comments')
297
301
  .option(' --generated-by-comment')
298
302
  .option(' --better-sqlite-session-variables')
303
+ .option(' --fewer-localized-views')
299
304
  .help(`
300
305
  Usage: cdsc toSql [options] <files...>
301
306
 
@@ -347,7 +352,10 @@ optionProcessor.command('Q, toSql')
347
352
  --pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
348
353
  --disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
349
354
  --generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
350
- --better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only active if sqlDialect is \`sqlite\`
355
+ --better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only
356
+ active if sqlDialect is \`sqlite\`
357
+ --fewer-localized-views If set, the backends will not create localized convenience views for
358
+ those views, that only have an association to a localized entity/view.
351
359
 
352
360
  `);
353
361
 
@@ -425,6 +433,7 @@ optionProcessor.command('toCsn')
425
433
  .option(' --with-localized')
426
434
  .option(' --with-locations')
427
435
  .option(' --struct-xpr')
436
+ .option(' --fewer-localized-views')
428
437
  .help(`
429
438
  Usage: cdsc toCsn [options] <files...>
430
439
 
@@ -441,6 +450,9 @@ optionProcessor.command('toCsn')
441
450
  universal: in development (BETA)
442
451
  --with-locations Add $location to CSN artifacts. In contrast to \`--enrich-csn\`,
443
452
  $location is an object with 'file', 'line' and 'col' properties.
453
+ --fewer-localized-views If --with-locations and this option are set, the backends
454
+ will not create localized convenience views for those views,
455
+ that only have an association to a localized entity/view.
444
456
 
445
457
  Internal options (for testing only, may be changed/removed at any time)
446
458
  --with-localized Add localized convenience views to the CSN output.
@@ -101,26 +101,28 @@ function manageConstraints( csn, options ) {
101
101
  forEachDefinition(csn, (artifact) => {
102
102
  if (artifact.$tableConstraints?.referential) {
103
103
  forEach(artifact.$tableConstraints.referential, (fileName, constraint) => {
104
- const renderAlterConstraintStatement = options.alter && options.src !== 'hdi';
105
- const renderedConstraint = renderReferentialConstraint(constraint, indent, false, csn, options, renderAlterConstraintStatement);
106
- if (options.src === 'hdi' && !options.drop) {
107
- resultArtifacts[fileName] = renderedConstraint;
108
- return;
109
- }
110
- let alterTableStatement = '';
111
- alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.sqlMapping, constraint.dependentTable))}`;
112
- if (renderAlterConstraintStatement)
113
- alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;
114
- else if (options.drop)
115
- alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
116
- else
117
- alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
118
-
119
- resultArtifacts[fileName] = alterTableStatement;
104
+ resultArtifacts[fileName] = manageConstraint(constraint, csn, options, indent, quoteSqlId);
120
105
  });
121
106
  }
122
107
  });
123
- return options.src === 'hdi' ? resultArtifacts : Object.values(resultArtifacts);
108
+ return resultArtifacts;
109
+ }
110
+
111
+ function manageConstraint( constraint, csn, options, indent, quoteSqlId ) {
112
+ const renderAlterConstraintStatement = options.alter && options.src !== 'hdi';
113
+ const renderedConstraint = renderReferentialConstraint(constraint, indent, false, csn, options, renderAlterConstraintStatement);
114
+ if (options.src === 'hdi' && !options.drop)
115
+ return renderedConstraint;
116
+ let alterTableStatement = '';
117
+ alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.sqlMapping, constraint.dependentTable))}`;
118
+ if (renderAlterConstraintStatement)
119
+ alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;
120
+ else if (options.drop)
121
+ alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
122
+ else
123
+ alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
124
+
125
+ return alterTableStatement;
124
126
  }
125
127
 
126
128
  /**
@@ -264,5 +266,6 @@ function getListOfAllConstraints( csn ) {
264
266
  module.exports = {
265
267
  alterConstraintsWithCsn,
266
268
  manageConstraints,
269
+ manageConstraint,
267
270
  listReferentialIntegrityViolations,
268
271
  };