@sap/cds-compiler 4.0.0 → 4.1.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 (85) hide show
  1. package/CHANGELOG.md +115 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +60 -12
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +30 -2
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +12 -5
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/model/sortViews.js +4 -2
  54. package/lib/modelCompare/compare.js +112 -39
  55. package/lib/modelCompare/utils/filter.js +54 -24
  56. package/lib/optionProcessor.js +6 -6
  57. package/lib/render/manageConstraints.js +20 -17
  58. package/lib/render/toCdl.js +34 -20
  59. package/lib/render/toHdbcds.js +2 -2
  60. package/lib/render/toRename.js +4 -9
  61. package/lib/render/toSql.js +77 -26
  62. package/lib/render/utils/common.js +3 -3
  63. package/lib/render/utils/unique.js +52 -0
  64. package/lib/transform/db/applyTransformations.js +61 -20
  65. package/lib/transform/db/assertUnique.js +7 -8
  66. package/lib/transform/db/associations.js +2 -2
  67. package/lib/transform/db/cdsPersistence.js +8 -8
  68. package/lib/transform/db/expansion.js +17 -21
  69. package/lib/transform/db/flattening.js +23 -23
  70. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  71. package/lib/transform/db/temporal.js +1 -1
  72. package/lib/transform/db/transformExists.js +8 -7
  73. package/lib/transform/db/views.js +73 -33
  74. package/lib/transform/draft/db.js +11 -9
  75. package/lib/transform/draft/odata.js +1 -1
  76. package/lib/transform/{forOdataNew.js → forOdata.js} +56 -42
  77. package/lib/transform/forRelationalDB.js +69 -75
  78. package/lib/transform/localized.js +6 -5
  79. package/lib/transform/odata/toFinalBaseType.js +3 -3
  80. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  81. package/lib/transform/translateAssocsToJoins.js +14 -28
  82. package/package.json +1 -1
  83. package/share/messages/check-proper-type-of.md +1 -1
  84. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  85. package/share/messages/message-explanations.json +1 -1
@@ -69,7 +69,8 @@ function getUtils( model, universalReady ) {
69
69
  };
70
70
 
71
71
  /**
72
- * Compute and return $combined for the given query.
72
+ * Compute and return $combined sources for the given query,
73
+ * that is, a map of elements that combine e.g. UNION sources.
73
74
  *
74
75
  * @param {CSN.Query} query
75
76
  * @returns {object}
@@ -291,11 +292,8 @@ function getUtils( model, universalReady ) {
291
292
  if (!absoluteName.startsWith('@'))
292
293
  throw new CompilerAssertion(`Annotation name should start with "@": ${ absoluteName }`);
293
294
 
294
- // Only overwrite if undefined or null
295
- if (node[absoluteName] === undefined || node[absoluteName] === null) {
296
- // Assemble the annotation
297
- node[absoluteName] = theValue;
298
- }
295
+ // Assemble the annotation
296
+ node[absoluteName] ??= theValue;
299
297
  }
300
298
 
301
299
  /**
@@ -332,12 +330,18 @@ function getUtils( model, universalReady ) {
332
330
  * - Does _not_ return the underlying type definition! It is an object with all relevant
333
331
  * type properties collected while traversing the type chain!
334
332
  *
335
- * @todo Rename to e.g. getFinalTypeInfo()
333
+ * @param {string|object} type
334
+ * Type as string or type ref, i.e. `{ ref: [...] }`
335
+ *
336
+ * @param {(string)=>CSN.Artifact} getArtifactRef
337
+ * Function used to get an artifact for a reference. Useful in case that the caller
338
+ * has a custom cache or a CSN that is in in-between state.
339
+ * TODO: Can we make getUtils() more modular, so that this argument is already
340
+ * passed to it?
336
341
  *
337
- * @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`
338
342
  * @returns {object|null}
339
343
  */
340
- function getFinalTypeInfo( type ) {
344
+ function getFinalTypeInfo( type, getArtifactRef = artifactRef ) {
341
345
  type = normalizeTypeRef(type);
342
346
  if (!type)
343
347
  return null;
@@ -362,7 +366,7 @@ function getUtils( model, universalReady ) {
362
366
  if (typeof type === 'string' && (isBuiltinType( type ) || type === special$self))
363
367
  return _cacheResolved({ type });
364
368
 
365
- const typeRef = artifactRef(type); // throws if not found
369
+ const typeRef = getArtifactRef(type); // default artifactRef() throws if not found
366
370
  const isNonScalar = _cacheNonScalar({ ...typeRef, type });
367
371
  if (isNonScalar)
368
372
  return finalBaseTypeCache[resolvedKey];
@@ -380,7 +384,7 @@ function getUtils( model, universalReady ) {
380
384
  finalBaseTypeCache[resolvedKey] = true;
381
385
 
382
386
  // Continue the search
383
- const finalBase = getFinalTypeInfo(type);
387
+ const finalBase = getFinalTypeInfo(type, getArtifactRef);
384
388
  if (!finalBase) // Reference has no proper type, e.g. due to `type of View:calculated`.
385
389
  return _cacheResolved(null);
386
390
 
@@ -515,8 +519,8 @@ function forEachDefinition( csn, callback, iterateOptions = {} ) {
515
519
  */
516
520
  function forEachMember( construct, callback, path = [], ignoreIgnore = true, iterateOptions = {},
517
521
  constructCallback = (_construct, _prop, _path) => {} ) {
518
- // Allow processing _ignored elements if requested
519
- if (ignoreIgnore && construct._ignore)
522
+ // Allow processing $ignored elements if requested
523
+ if (ignoreIgnore && construct.$ignore)
520
524
  return;
521
525
 
522
526
  // `items` itself is a structure that can contain "elements", and more.
@@ -775,7 +779,7 @@ function getArtifactDatabaseNameOf( artifactName, sqlMapping, csn, sqlDialect =
775
779
  * @returns {string} The resulting name
776
780
  */
777
781
  function getResultingName( csn, namingMode, artifactName ) {
778
- if (namingMode === 'plain' || artifactName.indexOf('.') === -1)
782
+ if (namingMode === 'plain' || !artifactName.includes('.'))
779
783
  return artifactName;
780
784
 
781
785
  const namespace = getNamespace(csn, artifactName);
@@ -964,7 +968,7 @@ function isPersistedOnDatabase( art ) {
964
968
  */
965
969
  function isPersistedAsView( artifact ) {
966
970
  return artifact && artifact.kind === 'entity' &&
967
- !artifact._ignore &&
971
+ !artifact.$ignore &&
968
972
  !artifact.abstract &&
969
973
  ((artifact.query || artifact.projection) && !hasAnnotationValue(artifact, '@cds.persistence.table')) &&
970
974
  !hasAnnotationValue(artifact, '@cds.persistence.skip') &&
@@ -978,7 +982,7 @@ function isPersistedAsView( artifact ) {
978
982
  */
979
983
  function isPersistedAsTable( artifact ) {
980
984
  return artifact.kind === 'entity' &&
981
- !artifact._ignore &&
985
+ !artifact.$ignore &&
982
986
  !artifact.abstract &&
983
987
  (!artifact.query && !artifact.projection || hasAnnotationValue(artifact, '@cds.persistence.table')) &&
984
988
  !hasAnnotationValue(artifact, '@cds.persistence.skip') &&
@@ -102,8 +102,10 @@ function revealInternalProperties( model, nameOrPath ) {
102
102
  _status: primOrString, // is a string anyway
103
103
  $annotations: as => as.map( $annotation ),
104
104
  $messageFunctions: () => '‹some functions›',
105
+ $functions: () => '‹some functions›',
106
+ $builtins: nameOrPath === '++' ? builtinsDictionary : () => '‹reveal with -R ++›',
105
107
  };
106
- uniqueId = 1;
108
+ uniqueId = -1;
107
109
  return revealXsnPath(nameOrPath, model);
108
110
 
109
111
  // Returns the desired artifact/dictionary in the XSN.
@@ -129,7 +131,7 @@ function revealInternalProperties( model, nameOrPath ) {
129
131
  // `name.space/S/E/elements/a/kind/`
130
132
  // `name.space/S/E/elements/a/type/scope/`
131
133
  function revealXsnPath( path, xsn ) {
132
- if (!path || path === '+')
134
+ if (!path || path === '+' || path === '++')
133
135
  return reveal( xsn );
134
136
 
135
137
  path = path.split('/');
@@ -173,7 +175,9 @@ function revealInternalProperties( model, nameOrPath ) {
173
175
  if (!Array.isArray(deps))
174
176
  return primOrString( deps );
175
177
  return deps
176
- .map( d => `${ d.location ? '' : '-' }${ artifactIdentifier( d.art ) }`);
178
+ .map( d => (d.location
179
+ ? `${ artifactIdentifier( d.art ) } @${ locationString( d.location ) }`
180
+ : artifactIdentifier( d.art )) );
177
181
  }
178
182
 
179
183
  function layerExtends( dict ) {
@@ -213,7 +217,13 @@ function revealInternalProperties( model, nameOrPath ) {
213
217
  }
214
218
 
215
219
  function artifactDictionary( node, parent ) {
216
- if (!node || typeof node !== 'object' || !model.definitions || parent === model )
220
+ if (parent === model )
221
+ return dictionary( node ); // no dictionary or no definitions section
222
+ return builtinsDictionary( node );
223
+ }
224
+
225
+ function builtinsDictionary( node, parent ) {
226
+ if (!node || typeof node !== 'object' || !model.definitions )
217
227
  return dictionary( node ); // no dictionary or no definitions section
218
228
  const dict = Object.create( Object.getPrototypeOf(node)
219
229
  ? NOT_A_DICTIONARY.prototype
@@ -237,7 +247,8 @@ function revealInternalProperties( model, nameOrPath ) {
237
247
  ? NOT_A_DICTIONARY.prototype
238
248
  : Object.prototype );
239
249
  for (const prop of Object.getOwnPropertyNames( node )) { // also non-enumerable
240
- r[prop] = reveal( node[prop], node, prop );
250
+ if (node !== model.definitions || nameOrPath === '++' || !node[prop].builtin)
251
+ r[prop] = reveal( node[prop], node, prop );
241
252
  }
242
253
  if (node[$inferred] && !node['[$inferred]'])
243
254
  r['[$inferred]'] = node[$inferred];
@@ -275,8 +286,8 @@ function revealInternalProperties( model, nameOrPath ) {
275
286
 
276
287
  const r = Object.create( Object.getPrototypeOf( node ) );
277
288
  // property to recognize === objects
278
- if (node.kind && node.__unique_id__ == null)
279
- Object.defineProperty( node, '__unique_id__', { value: ++uniqueId } );
289
+ if (node.kind && node.__unique_id__ == null && node.$effectiveSeqNo == null && !node.builtin)
290
+ Object.defineProperty( node, '__unique_id__', { value: uniqueId-- } );
280
291
 
281
292
  for (const prop of Object.getOwnPropertyNames( node )) { // also non-enumerable
282
293
  const func = transformers[prop] ||
@@ -287,8 +298,8 @@ function revealInternalProperties( model, nameOrPath ) {
287
298
  }
288
299
 
289
300
  function targetAspect( node, parent ) {
290
- if (node.elements && uniqueId && node.__unique_id__ == null)
291
- Object.defineProperty( node, '__unique_id__', { value: ++uniqueId } );
301
+ if (node.elements && node.__unique_id__ == null && node.$effectiveSeqNo == null)
302
+ Object.defineProperty( node, '__unique_id__', { value: uniqueId-- } );
292
303
  return reveal( node, parent );
293
304
  }
294
305
 
@@ -305,19 +316,16 @@ function array( node, fn ) {
305
316
  }
306
317
 
307
318
  function artifactIdentifier( node, parent ) {
319
+ if (!node)
320
+ return `${ node }`;
308
321
  if (Array.isArray(node))
309
322
  return node.map( a => artifactIdentifier( a, node ) );
310
- if (uniqueId && node.__unique_id__ == null)
311
- Object.defineProperty( node, '__unique_id__', { value: ++uniqueId } );
312
- let outer = uniqueId ? `##${ node.__unique_id__ }` : '';
313
- if (node._outer) {
314
- // eslint-disable-next-line no-nested-ternary
315
- outer = (node._outer.items === node) ? `/items${ outer }`
316
- // eslint-disable-next-line no-nested-ternary
317
- : (node._outer.returns === node) ? `/returns${ outer }` // TODO returns now normal
318
- // eslint-disable-next-line no-nested-ternary
319
- : (node._outer.targetAspect === node) ? `/target${ outer }`
320
- : `/returns/items${ outer }`;
323
+ if (uniqueId && node.__unique_id__ == null && node.$effectiveSeqNo == null && !node.builtin)
324
+ Object.defineProperty( node, '__unique_id__', { value: uniqueId-- } );
325
+ const outerNum = node.$effectiveSeqNo || node.__unique_id__;
326
+ let outer = outerNum != null ? `##${ outerNum }` : '';
327
+ if (node._outer) { // anon aspect in targetAspect | items
328
+ outer = (node._outer.targetAspect === node) ? `/target${ outer }` : `/items${ outer }`;
321
329
  node = node._outer;
322
330
  }
323
331
  if (node === parent)
@@ -328,7 +336,7 @@ function artifactIdentifier( node, parent ) {
328
336
  return '$magicVariables';
329
337
  if (!node.name) {
330
338
  try {
331
- return `${ locationString( node.location ) || '' }##${ node.__unique_id__ }`;
339
+ return `${ locationString( node.location ) || '' }##${ outerNum }`;
332
340
  // return JSON.stringify(node);
333
341
  }
334
342
  catch (e) {
@@ -93,13 +93,12 @@ function _findWithXPointers( definitionsArray, x, _dependents, _dependencies ) {
93
93
  module.exports = function sortViews({ sql, csn }) {
94
94
  const { cleanup, _dependents, _dependencies } = setDependencies(csn);
95
95
  const { layers, leftover } = sortTopologically(csn, _dependents, _dependencies);
96
- cleanup.forEach(fn => fn());
97
96
  if (leftover.length > 0)
98
97
  throw new ModelError('Unable to build a correct dependency graph! Are there cycles?');
99
98
 
100
99
  const result = [];
101
100
  // keep the "artifact name" - needed for to.hdi sorting
102
- layers.forEach(layer => layer.forEach(objName => result.push({ name: objName, sql: sql[objName] })));
101
+ layers.forEach(layer => layer.forEach(objName => result.push({ name: objName, sql: sql[objName], dependents: csn.definitions[objName][_dependents] })));
103
102
  // attach sql artifacts which are not considered during the view sorting algorithm
104
103
  // --> this is the case for "ALTER TABLE ADD CONSTRAINT" statements,
105
104
  // because their identifiers are not part of the csn.definitions
@@ -107,5 +106,8 @@ module.exports = function sortViews({ sql, csn }) {
107
106
  if (!result.some( o => o.name === name )) // not in result but in incoming sql
108
107
  result.push({ name, sql: sqlString });
109
108
  });
109
+
110
+ cleanup.forEach(fn => fn());
111
+
110
112
  return result;
111
113
  };
@@ -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;
@@ -239,6 +299,15 @@ function addedElements(entity, elements) {
239
299
  };
240
300
  }
241
301
 
302
+ function addedConstraint(entity, constraint, constraintName, constraintType) {
303
+ return {
304
+ extend: entity,
305
+ constraint,
306
+ constraintName,
307
+ constraintType,
308
+ }
309
+ }
310
+
242
311
  function changedElement(element, otherElement) {
243
312
  return {
244
313
  old: otherElement,
@@ -246,6 +315,10 @@ function changedElement(element, otherElement) {
246
315
  };
247
316
  }
248
317
 
318
+ function hasReferentialConstraints(options) {
319
+ return options.src === 'sql' && (options.sqlDialect === 'postgres' || options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite')
320
+ }
321
+
249
322
  module.exports = {
250
323
  compareModels,
251
324
  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,7 +152,7 @@ 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
  });
@@ -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)
@@ -102,12 +103,11 @@ optionProcessor
102
103
  --beta-mode Enable all unsupported, incomplete (beta) features
103
104
  --beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
104
105
  Valid values are:
105
- hanaAssocRealCardinality
106
- mapAssocToJoinCardinality
107
- enableUniversalCsn
108
- aspectWithoutElements
109
- odataTerms
110
- optionalActionFunctionParameters
106
+ ${
107
+ Object.keys(availableBetaFlags).sort()
108
+ .filter(flag => availableBetaFlags[flag])
109
+ .join('\n' + ' '.repeat(30))
110
+ }
111
111
  --deprecated <list> Comma separated list of deprecated options.
112
112
  Valid values are:
113
113
  eagerPersistenceForGeneratedEntities