@sap/cds-compiler 5.0.6 → 5.1.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 (40) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/bin/cdsc.js +34 -8
  3. package/bin/cdshi.js +2 -1
  4. package/lib/api/main.js +10 -1
  5. package/lib/api/options.js +2 -3
  6. package/lib/base/message-registry.js +14 -0
  7. package/lib/base/messages.js +2 -1
  8. package/lib/base/meta.js +10 -0
  9. package/lib/base/optionProcessorHelper.js +11 -0
  10. package/lib/checks/dbFeatureFlags.js +5 -0
  11. package/lib/compiler/assert-consistency.js +1 -0
  12. package/lib/compiler/define.js +1 -1
  13. package/lib/compiler/extend.js +43 -7
  14. package/lib/compiler/index.js +4 -4
  15. package/lib/compiler/populate.js +58 -11
  16. package/lib/compiler/propagator.js +9 -4
  17. package/lib/compiler/resolve.js +115 -79
  18. package/lib/compiler/shared.js +2 -1
  19. package/lib/compiler/tweak-assocs.js +29 -5
  20. package/lib/edm/edm.js +8 -0
  21. package/lib/edm/edmPreprocessor.js +7 -3
  22. package/lib/gen/Dictionary.json +37 -0
  23. package/lib/json/to-csn.js +2 -0
  24. package/lib/main.js +2 -5
  25. package/lib/model/cloneCsn.js +1 -0
  26. package/lib/model/csnRefs.js +2 -1
  27. package/lib/model/csnUtils.js +0 -12
  28. package/lib/model/revealInternalProperties.js +0 -1
  29. package/lib/modelCompare/compare.js +12 -10
  30. package/lib/optionProcessor.js +2 -0
  31. package/lib/render/toCdl.js +8 -7
  32. package/lib/render/toHdbcds.js +1 -2
  33. package/lib/render/toSql.js +44 -8
  34. package/lib/transform/db/backlinks.js +20 -5
  35. package/lib/transform/db/killAnnotations.js +3 -0
  36. package/lib/transform/db/processSqlServices.js +63 -0
  37. package/lib/transform/draft/odata.js +6 -1
  38. package/lib/transform/forRelationalDB.js +9 -0
  39. package/lib/utils/file.js +77 -4
  40. package/package.json +1 -1
@@ -67,6 +67,7 @@ const {
67
67
  } = require('./utils');
68
68
 
69
69
  const detectCycles = require('./cycle-detector');
70
+ const { CompilerAssertion } = require('../base/error');
70
71
 
71
72
  const $location = Symbol.for( 'cds.$location' );
72
73
 
@@ -90,7 +91,6 @@ function resolve( model ) {
90
91
  resolvePath,
91
92
  resolveDefinitionName,
92
93
  traverseExpr,
93
- createRemainingAnnotateStatements,
94
94
  effectiveType,
95
95
  getOrigin,
96
96
  getInheritedProp,
@@ -131,8 +131,6 @@ function resolve( model ) {
131
131
  resolveDefinitionName( model.sources[name].namespace );
132
132
  }
133
133
 
134
- // create “super” ANNOTATE statements for annotations on unknown artifacts:
135
- createRemainingAnnotateStatements();
136
134
  // report cyclic dependencies:
137
135
  detectCycles( model.definitions, ( user, art, location, semanticLoc ) => {
138
136
  if (location) {
@@ -158,68 +156,102 @@ function resolve( model ) {
158
156
  // Phase 2+3: calculate propagated KEYs
159
157
  //--------------------------------------------------------------------------
160
158
 
159
+ /**
160
+ * Set `_projection` links in navigation elements and creates $navElement hierarchy.
161
+ *
162
+ * @param {XSN.Artifact} view
163
+ */
161
164
  function setNavigationProjections( view ) {
162
165
  if (!view.$queries)
163
166
  return;
164
167
  for (const query of view.$queries) {
165
- forEachGeneric( query, 'elements', ( elem ) => {
166
- if (!elem._origin || elem.expand || !elem.value)
168
+ // traversing sub-elements not necessary, since we're in a view
169
+ // TODO: Handle expand.
170
+ forEachGeneric( query, 'elements', function navProjectionsForElement( elem ) {
171
+ if (!elem._origin || elem.expand || !elem.value?.path)
167
172
  return;
168
173
  // TODO: what about elements where _origin is set without value?
169
174
  // TODO: or should we push elems with `expand` sibling to extra list for
170
175
  // better messages? (Whatever that means exactly.)
171
- const nav = pathNavigation( elem.value );
172
- const { path } = elem.value;
173
-
174
- if (elem._pathHead?.kind === '$inline' && path.length === 1) {
175
- const item = path[0];
176
- const hpath = elem._pathHead.value?.path;
177
- const head = hpath?.length === 1 && hpath[0]._navigation;
178
- // Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
179
- if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
180
- pushLink( head.elements[item.id], '_projections', elem );
181
- }
182
- else if (nav.navigation) { // not set for $self.…
183
- // Path could start with table alias; get start index
184
- let index = path.indexOf(nav.item);
185
- if (index === -1)
186
- return; // should not happen
187
176
 
188
- let navItem = nav.navigation;
189
- if (!nav.item._navigation) // first non-table-alias
190
- setLink( nav.item, '_navigation', navItem );
177
+ if (elem._pathHead) {
178
+ if (elem._pathHead?.kind !== '$inline')
179
+ // we're traversing top-level elements of the query; other _pathHead kinds can't happen
180
+ throw new CompilerAssertion('found unexpected "expand", but expected "inline"');
191
181
 
192
- if (path[index].where || path[index].args)
193
- return;
194
- ++index;
195
- while (navItem && index < path.length) {
196
- const step = path[index];
197
- if (!step?.id || step.where || step.args)
198
- break;
199
- if (!navItem.elements?.[step.id]) {
200
- const elements = navItem._origin?.elements ||
201
- navItem._origin?.target?._artifact?.elements;
202
- if (!elements)
203
- break;
204
- // Only link available path steps (navigation tree).
205
- const origin = elements[step.id];
206
- const member = linkToOrigin( origin, step.id, navItem, 'elements',
207
- navItem.path?.location, true );
208
- member.$inferred = 'expanded';
209
- member.kind = '$navElement';
210
- }
211
- navItem = navItem.elements[step.id];
212
- setLink( step, '_navigation', navItem );
213
- ++index;
182
+ if (!isPathBreakout( elem.value )) {
183
+ const fullPath = columnParentPath( elem );
184
+ if (fullPath)
185
+ setNavigationProjectionsForElementRef({ path: fullPath }, elem);
214
186
  }
215
- // Last path step, if found, is a simple projection
216
- if (index === path.length && navItem)
217
- pushLink( navItem, '_projections', elem );
187
+ }
188
+ else {
189
+ setNavigationProjectionsForElementRef( elem.value, elem );
218
190
  }
219
191
  } );
220
192
  }
221
193
  }
222
194
 
195
+ function setNavigationProjectionsForElementRef( ref, elem ) {
196
+ const { path } = ref;
197
+ const nav = pathNavigation( ref );
198
+ if (nav.navigation) { // not set for $self.…
199
+ // Path could start with table alias; get start index
200
+ let index = path.indexOf(nav.item);
201
+ if (index === -1)
202
+ return; // should not happen
203
+
204
+ let navItem = nav.navigation;
205
+ if (!nav.item._navigation) // first non-table-alias
206
+ setLink( nav.item, '_navigation', navItem );
207
+
208
+ if (path[index].where || path[index].args)
209
+ return;
210
+ ++index;
211
+ while (navItem && index < path.length) {
212
+ const step = path[index];
213
+ if (!step?.id || step.where || step.args)
214
+ break;
215
+ if (!navItem.elements?.[step.id]) {
216
+ const elements = navItem._origin?.elements ||
217
+ navItem._origin?.target?._artifact?.elements;
218
+ if (!elements)
219
+ break;
220
+ // Only link available path steps (navigation tree).
221
+ const origin = elements[step.id];
222
+ const member = linkToOrigin( origin, step.id, navItem, 'elements',
223
+ navItem.path?.location, true );
224
+ member.$inferred = 'expanded';
225
+ member.kind = '$navElement';
226
+ }
227
+ navItem = navItem.elements[step.id];
228
+ setLink( step, '_navigation', navItem );
229
+ ++index;
230
+ }
231
+ // Last path step, if found, is a simple projection
232
+ if (index === path.length && navItem)
233
+ pushLink( navItem, '_projections', elem );
234
+ }
235
+ }
236
+
237
+ function columnParentPath( elem ) {
238
+ if (!elem._pathHead || !elem.value?.path || isPathBreakout( elem.value ))
239
+ return elem.value?.path;
240
+
241
+ const fullPath = [ ...elem.value.path ];
242
+ let pathHead = elem._pathHead;
243
+ while (pathHead) {
244
+ if (pathHead.kind !== '$inline' || !pathHead.value?.path ||
245
+ isPathBreakout( pathHead.value )) {
246
+ // path breakout for e.g. `$self.{ foo }`, `1 as a .{ foo }`
247
+ return null;
248
+ }
249
+ fullPath.unshift(...pathHead.value.path);
250
+ pathHead = pathHead._pathHead;
251
+ }
252
+ return fullPath;
253
+ }
254
+
223
255
  function propagateKeyProps( view ) {
224
256
  // Second argument true ensure that `key` is only propagated along simple
225
257
  // view, i.e. ref or subquery in FROM, not UNION or JOIN.
@@ -256,9 +288,9 @@ function resolve( model ) {
256
288
  function inheritedSourceKeyProp( { value, _pathHead } ) {
257
289
  if (!value || !value.path)
258
290
  return null;
259
- const nav = pathNavigation( value );
291
+ const nav = !_pathHead && pathNavigation( value );
260
292
  const item = value.path[value.path.length - 1];
261
- if (nav.navigation && nav.item === item)
293
+ if (nav?.navigation && nav.item === item)
262
294
  return item._artifact?.key;
263
295
  if (value.path.length !== 1 || _pathHead?.kind !== '$inline')
264
296
  return null;
@@ -282,13 +314,11 @@ function resolve( model ) {
282
314
  const toMany = withAssociation( from, targetMaxNotOne, true );
283
315
  if (toMany) {
284
316
  propagateKeys = false;
285
- info( 'query-from-many', [ toMany.location, query ], { art: toMany },
286
- {
287
- // eslint-disable-next-line max-len
288
- std: 'Key properties are not propagated because a to-many association $(ART) is selected',
289
- // eslint-disable-next-line max-len
290
- element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
291
- } );
317
+ info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
318
+ std: 'Key properties are not propagated because a to-many association $(ART) is selected',
319
+ // eslint-disable-next-line max-len
320
+ element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
321
+ } );
292
322
  }
293
323
  // Check that all keys from the source are projected:
294
324
  const notProjected = []; // we actually push to the array
@@ -298,7 +328,7 @@ function resolve( model ) {
298
328
  if (nav.$duplicates)
299
329
  continue;
300
330
  const { key } = nav._origin;
301
- if (key && key.val && !(nav._projections && nav._projections.length))
331
+ if (key?.val && !nav._projections?.length)
302
332
  notProjected.push( nav.name.id );
303
333
  }
304
334
  if (notProjected.length) {
@@ -312,9 +342,12 @@ function resolve( model ) {
312
342
  // Check that there is no to-many assoc used in select item:
313
343
  for (const name in query.elements) {
314
344
  const elem = query.elements[name];
315
- if (!elem.$inferred && elem.value &&
316
- testExpr( elem.value, selectTest, () => false, elem ))
317
- propagateKeys = false;
345
+
346
+ if (!elem.$inferred && elem.value?.path) {
347
+ const path = elem._pathHead ? columnParentPath( elem ) : elem.value.path;
348
+ if (testExpr({ path }, selectTest, () => false, elem))
349
+ propagateKeys = false;
350
+ }
318
351
  }
319
352
  return propagateKeys;
320
353
 
@@ -322,15 +355,13 @@ function resolve( model ) {
322
355
  const art = withAssociation( expr, targetMaxNotOne );
323
356
  if (art) {
324
357
  // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
325
- info( 'query-navigate-many', [ art.location, user || query ], { art },
326
- {
327
- // eslint-disable-next-line max-len
328
- std: 'Navigating along to-many association $(ART) - key properties are not propagated',
329
- // eslint-disable-next-line max-len
330
- element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
331
- // eslint-disable-next-line max-len
332
- alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
333
- } );
358
+ info( 'query-navigate-many', [ art.location, user || query ], { art }, {
359
+ std: 'Navigating along to-many association $(ART) - key properties are not propagated',
360
+ // eslint-disable-next-line max-len
361
+ element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
362
+ // eslint-disable-next-line max-len
363
+ alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
364
+ } );
334
365
  }
335
366
  return art;
336
367
  }
@@ -511,14 +542,12 @@ function resolve( model ) {
511
542
  else if (obj.targetAspect && obj.targetAspect.elements) { // silent dependencies
512
543
  forEachGeneric( obj.targetAspect, 'elements', elem => dependsOnSilent( art, elem ) );
513
544
  }
545
+
514
546
  if (obj.foreignKeys) { // silent dependencies
515
547
  // Avoid strange ref-cyclic if managed composition is key (check comes later)
516
- // TODO: the following is already done by addImplicitForeignKeys()!
517
- if (obj.$inferred !== 'aspect-composition') {
518
- forEachGeneric( obj, 'foreignKeys', (elem) => {
519
- dependsOnSilent( art, elem );
520
- } );
521
- }
548
+ // Done by addImplicitForeignKeys() for implicit keys.
549
+ if (!art.foreignKeys?.[$inferred] && obj.$inferred !== 'aspect-composition')
550
+ forEachGeneric( obj, 'foreignKeys', elem => dependsOnSilent( art, elem ) );
522
551
  addForeignKeyNavigations( art );
523
552
  }
524
553
 
@@ -1552,8 +1581,6 @@ function resolve( model ) {
1552
1581
  function pathNavigation( ref ) {
1553
1582
  // currently, indirectly projectable elements are not included - we might
1554
1583
  // keep it this way! If we want them to be included - be aware: cycles
1555
- if (!ref._artifact)
1556
- return {};
1557
1584
  let item = ref.path && ref.path[0];
1558
1585
  const root = item && item._navigation;
1559
1586
  if (!root)
@@ -1570,4 +1597,13 @@ function pathNavigation( ref ) {
1570
1597
  return { navigation: root.elements?.[item.id], item, tableAlias: root };
1571
1598
  }
1572
1599
 
1600
+ function isPathBreakout( ref ) {
1601
+ if (!ref.path?.[0])
1602
+ return false;
1603
+ if (ref.scope === 'param')
1604
+ return true;
1605
+ const nav = (ref.path[0]._navigation || ref.path[0]._artifact);
1606
+ return nav && (nav.kind === '$self' || ref.path[0].id.charAt(0) === '$');
1607
+ }
1608
+
1573
1609
  module.exports = resolve;
@@ -1738,7 +1738,8 @@ function fns( model ) {
1738
1738
  * @param {string} msgId
1739
1739
  * @param {any} location
1740
1740
  * @param {object[]} valid
1741
- * @param {object} [textParams]
1741
+ * @param {object} [textParams]
1742
+ * @param {object} [semantics]
1742
1743
  */
1743
1744
  function signalNotFound( msgId, location, valid, textParams, semantics ) {
1744
1745
  if (location.$notFound) // TODO: still necessary?
@@ -38,6 +38,9 @@ function tweakAssocs( model ) {
38
38
  checkOnCondition,
39
39
  effectiveType,
40
40
  getOrigin,
41
+ extendForeignKeys,
42
+ createRemainingAnnotateStatements,
43
+ mergeSpecifiedForeignKeys,
41
44
  } = model.$functions;
42
45
 
43
46
  Object.assign(model.$functions, {
@@ -45,7 +48,7 @@ function tweakAssocs( model ) {
45
48
  });
46
49
 
47
50
  // Phase 5: rewrite associations
48
- model._entities.forEach( rewriteArtifact );
51
+ model._entities.forEach( rewriteArtifact ); // _entities contains all definitions, sorted.
49
52
  // Think hard whether an on condition rewrite can lead to a new cyclic
50
53
  // dependency. If so, we need other messages anyway. TODO: probably dox
51
54
  // another cyclic check with testMode.js
@@ -56,6 +59,11 @@ function tweakAssocs( model ) {
56
59
  if (art.kind === 'select')
57
60
  forEachQueryExpr( art, checkExpr );
58
61
  } );
62
+
63
+
64
+ // create “super” ANNOTATE statements for annotations on unknown artifacts:
65
+ createRemainingAnnotateStatements();
66
+
59
67
  return;
60
68
 
61
69
 
@@ -68,7 +76,6 @@ function tweakAssocs( model ) {
68
76
  // return;
69
77
  if (!art.query) {
70
78
  rewriteAssociation( art );
71
- forEachGeneric( art, 'elements', rewriteAssociation );
72
79
  }
73
80
  else {
74
81
  traverseQueryExtra( art, ( query ) => {
@@ -78,9 +85,11 @@ function tweakAssocs( model ) {
78
85
  if (art._service)
79
86
  forEachGeneric( art, 'elements', complainAboutTargetOutsideService );
80
87
 
81
- traverseQueryPost( art.query, false, ( query ) => {
82
- forEachGeneric( query, 'elements', rewriteAssociationCheck );
83
- } );
88
+ if (art.query) {
89
+ traverseQueryPost(art.query, false, (query) => {
90
+ forEachGeneric( query, 'elements', rewriteAssociationCheck );
91
+ });
92
+ }
84
93
  }
85
94
 
86
95
  // function rewriteView( view ) {
@@ -231,6 +240,16 @@ function tweakAssocs( model ) {
231
240
  }
232
241
 
233
242
  function rewriteAssociation( element ) {
243
+ doRewriteAssociation( element );
244
+ if (element.target) {
245
+ extendForeignKeys( element );
246
+ if (element.foreignKeys$)
247
+ mergeSpecifiedForeignKeys( element );
248
+ }
249
+ }
250
+
251
+ // only to be used by rewriteAssociation()
252
+ function doRewriteAssociation( element ) {
234
253
  let elem = element.items || element; // TODO v6: nested items
235
254
  if (elem.elements)
236
255
  forEachGeneric( elem, 'elements', rewriteAssociation );
@@ -238,6 +257,7 @@ function tweakAssocs( model ) {
238
257
  forEachGeneric( elem.targetAspect, 'elements', rewriteAssociation );
239
258
  if (!originTarget( elem ))
240
259
  return;
260
+
241
261
  // console.log(message( null, elem.location, elem,
242
262
  // {art:assoc,target,ftype:JSON.stringify(ftype)}, 'Info','RA').toString())
243
263
 
@@ -295,6 +315,7 @@ function tweakAssocs( model ) {
295
315
  }
296
316
  }
297
317
 
318
+ /** Returns the element's origin's target artifact. */
298
319
  function originTarget( elem ) {
299
320
  const assoc = !elem.expand && getOrigin( elem );
300
321
  const ftype = assoc && effectiveType( assoc );
@@ -373,6 +394,9 @@ function tweakAssocs( model ) {
373
394
  traverseExpr( elem.on, 'rewrite-on', elem,
374
395
  expr => rewriteExpr( expr, elem, nav.tableAlias ) );
375
396
  }
397
+ else if (elem._pathHead) {
398
+ error( 'rewrite-not-supported', [ elem.target.location, elem ] );
399
+ }
376
400
  else {
377
401
  // TODO: support that, now that the ON condition is rewritten in the right order
378
402
  error( null, [ elem.value.location, elem ], {},
package/lib/edm/edm.js CHANGED
@@ -883,12 +883,20 @@ function getEdm( options, messageFunctions ) {
883
883
  class ReturnType extends PropertyBase {
884
884
  constructor(version, csn) {
885
885
  super(version, {}, csn);
886
+ // CSDL 12.8: If the return type is a collection of entity types,
887
+ // the Nullable attribute has no meaning and MUST NOT be specified.
888
+ if (csn.$NoNullableProperty)
889
+ delete this._edmAttributes.Nullable;
886
890
  }
887
891
 
888
892
  // we need Name but NO $kind, can't use standard to JSON()
889
893
  toJSON() {
890
894
  const json = Object.create(null);
891
895
  this.toJSONattributes(json);
896
+ // CSDL 12.8: If the return type is a collection of entity types,
897
+ // the Nullable attribute has no meaning and MUST NOT be specified.
898
+ if (this._csn.$NoNullableProperty)
899
+ delete json.$Nullable;
892
900
  return json;
893
901
  }
894
902
  }
@@ -1886,7 +1886,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1886
1886
  mapCdsToEdmProp(def);
1887
1887
  annotateAllowedValues(def, defLocation);
1888
1888
  if (def.returns) {
1889
- markCollection(def.returns);
1889
+ markCollection(def.returns, true);
1890
1890
  mapCdsToEdmProp(def.returns);
1891
1891
  annotateAllowedValues(def.returns, [ ...defLocation, 'returns' ]);
1892
1892
  }
@@ -1899,16 +1899,20 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1899
1899
  rewriteAnnotationExpressions(member);
1900
1900
  if (member.returns) {
1901
1901
  edmUtils.assignAnnotation(member.returns, '@Core.Description', member.returns.doc);
1902
- markCollection(member.returns);
1902
+ markCollection(member.returns, true);
1903
1903
  mapCdsToEdmProp(member.returns);
1904
1904
  annotateAllowedValues(member.returns, [ ...location, 'returns' ]);
1905
1905
  rewriteAnnotationExpressions(member.returns);
1906
1906
  }
1907
1907
  }, defLocation);
1908
1908
  // mark members that need to be rendered as collections
1909
- function markCollection( obj ) {
1909
+ function markCollection( obj, isReturns ) {
1910
1910
  const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
1911
1911
  if (items) {
1912
+ edmUtils.assignProp(obj, '$NoNullableProperty',
1913
+ isReturns && items.type &&
1914
+ !isBuiltinType(items.type) &&
1915
+ csn.definitions[items.type]?.kind === 'entity');
1912
1916
  edmUtils.assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : false);
1913
1917
  edmUtils.assignProp(obj, '$isCollection', true);
1914
1918
  }
@@ -1508,6 +1508,13 @@
1508
1508
  "Type": "HTML5.LinkTargetType",
1509
1509
  "$experimental": true
1510
1510
  },
1511
+ "HTML5.RowSpanForDuplicateValues": {
1512
+ "Type": "Core.Tag",
1513
+ "AppliesTo": [
1514
+ "Record"
1515
+ ],
1516
+ "$experimental": true
1517
+ },
1511
1518
  "JSON.Schema": {
1512
1519
  "Type": "JSON.JSON",
1513
1520
  "AppliesTo": [
@@ -1934,6 +1941,16 @@
1934
1941
  ],
1935
1942
  "$experimental": true
1936
1943
  },
1944
+ "UI.IsAIOperation": {
1945
+ "Type": "Core.Tag",
1946
+ "AppliesTo": [
1947
+ "Action",
1948
+ "Function",
1949
+ "ActionImport",
1950
+ "FunctionImport"
1951
+ ],
1952
+ "$experimental": true
1953
+ },
1937
1954
  "UI.CreateHidden": {
1938
1955
  "Type": "Core.Tag",
1939
1956
  "AppliesTo": [
@@ -2019,6 +2036,13 @@
2019
2036
  "Parameter"
2020
2037
  ]
2021
2038
  },
2039
+ "UI.Recommendations": {
2040
+ "Type": "Edm.ComplexType",
2041
+ "AppliesTo": [
2042
+ "EntityType"
2043
+ ],
2044
+ "$experimental": true
2045
+ },
2022
2046
  "UI.ExcludeFromNavigationContext": {
2023
2047
  "Type": "Core.Tag",
2024
2048
  "AppliesTo": [
@@ -3235,6 +3259,7 @@
3235
3259
  "Properties": {
3236
3260
  "Property": "Edm.PropertyPath",
3237
3261
  "DynamicProperty": "Edm.AnnotationPath",
3262
+ "Expression": "Edm.PrimitiveType",
3238
3263
  "Descending": "Edm.Boolean"
3239
3264
  }
3240
3265
  },
@@ -4230,6 +4255,7 @@
4230
4255
  "IncludeGrandTotal": "Edm.Boolean",
4231
4256
  "InitialExpansionLevel": "Edm.Int32",
4232
4257
  "Visualizations": "Collection(Edm.AnnotationPath)",
4258
+ "RecursiveHierarchyQualifier": "Aggregation.HierarchyQualifier",
4233
4259
  "RequestAtLeast": "Collection(Edm.PropertyPath)",
4234
4260
  "SelectionFields": "Collection(Edm.PropertyPath)"
4235
4261
  }
@@ -4479,6 +4505,17 @@
4479
4505
  "ValueListProperty": "Edm.String"
4480
4506
  }
4481
4507
  },
4508
+ "UI.PropertyRecommendationType": {
4509
+ "$kind": "ComplexType",
4510
+ "Abstract": "true",
4511
+ "Properties": {
4512
+ "RecommendedFieldValue": "Edm.PrimitiveType",
4513
+ "RecommendedFieldDescription": "Edm.String",
4514
+ "RecommendedFieldScoreValue": "Edm.Decimal",
4515
+ "RecommendedFieldIsSuggestion": "Edm.Boolean"
4516
+ },
4517
+ "$experimental": true
4518
+ },
4482
4519
  "UI.VisualizationType": {
4483
4520
  "$kind": "EnumType",
4484
4521
  "Members": [
@@ -384,6 +384,8 @@ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = fals
384
384
  attachAnnotations( sub, 'elements', elems, inf, entry.returns );
385
385
  else if (many.enum) // make 'enum' annotations appear in 'elements' annotate
386
386
  attachAnnotations( sub, 'elements', many.enum, inf, entry.returns );
387
+ else if (entry.foreignKeys) // make 'foreignKeys' annotations appear in 'elements' annotate
388
+ attachAnnotations( sub, 'elements', entry.foreignKeys, inf );
387
389
  }
388
390
  if (Object.keys( sub ).length)
389
391
  annoDict[name] = sub;
package/lib/main.js CHANGED
@@ -30,11 +30,8 @@ const builtins = lazyload('./base/builtins');
30
30
  const base = lazyload('./compiler/base');
31
31
  const finalizeParseCdl = lazyload('./compiler/finalize-parse-cdl');
32
32
  const lsp = lazyload('./compiler/lsp-api');
33
+ const meta = lazyload('./base/meta');
33
34
 
34
- // The compiler version (taken from package.json)
35
- function version() {
36
- return require('../package.json').version;
37
- }
38
35
 
39
36
  const toCsn = lazyload('./json/to-csn')
40
37
 
@@ -76,7 +73,7 @@ function parseExpr( cdlSource, filename = '<expr>.cds', options = {} ) {
76
73
  // ATTENTION: Keep in sync with main.d.ts!
77
74
  module.exports = {
78
75
  // Compiler
79
- version,
76
+ version: () => meta.version(),
80
77
  compile: (filenames, dir, options, fileCache) => { // main function
81
78
  traceApi( 'compile', options );
82
79
  return compiler.compileX(filenames, dir, options, fileCache).then(toCsn.compactModel);
@@ -30,6 +30,7 @@ const internalCsnProps = {
30
30
  $tableConstraints: shallowCopy,
31
31
  $default: shallowCopy, // used for HANA CSN migrations
32
32
  $notNull: shallowCopy, // used for HANA CSN migrations
33
+ $sqlService: shallowCopy,
33
34
  };
34
35
  const internalEnumerableCsnProps = {
35
36
  __proto__: null,
@@ -448,7 +448,7 @@ function csnRefs( csn, universalReady ) {
448
448
  if (!step)
449
449
  return null;
450
450
  if (!effectiveType( art ))
451
- throw new TypeError( 'Cyclic type definition' );
451
+ throw new ModelError( 'Cyclic type definition' );
452
452
  if (typeof step === 'string')
453
453
  return navigationEnv( art, true ).elements[step];
454
454
 
@@ -1324,6 +1324,7 @@ module.exports = {
1324
1324
  traverseQuery,
1325
1325
  artifactProperties,
1326
1326
  implicitAs,
1327
+ getKeysDict,
1327
1328
  analyseCsnPath,
1328
1329
  pathId,
1329
1330
  columnAlias,
@@ -12,7 +12,6 @@ const { isBuiltinType, isAnnotationExpression } = require('../base/builtins');
12
12
  const { ModelError, CompilerAssertion } = require('../base/error');
13
13
  const { typeParameters } = require('../compiler/builtins');
14
14
  const { forEach } = require('../utils/objectUtils');
15
- const { version } = require('../../package.json');
16
15
  const { cloneAnnotationValue } = require('./cloneCsn');
17
16
 
18
17
  // Low-level utility functions to work with compact CSN.
@@ -946,16 +945,6 @@ function isPersistedAsTable( artifact ) {
946
945
  !hasAnnotationValue(artifact, '@cds.persistence.exists');
947
946
  }
948
947
 
949
- /**
950
- * Central generated by cds-compiler string generator function without further decoration
951
- * for unified tagging of generated content
952
- *
953
- * @returns {string} String containing compiler version that was used to generate content
954
- */
955
- function generatedByCompilerVersion() {
956
- return `generated by cds-compiler version ${ version }`;
957
- }
958
-
959
948
  /**
960
949
  * Return the projection to look like a query.
961
950
  *
@@ -1438,7 +1427,6 @@ module.exports = {
1438
1427
  isPersistedOnDatabase,
1439
1428
  isPersistedAsView,
1440
1429
  isPersistedAsTable,
1441
- generatedByCompilerVersion,
1442
1430
  getNormalizedQuery,
1443
1431
  getRootArtifactName,
1444
1432
  getLastPartOfRef,
@@ -73,7 +73,6 @@ function revealInternalProperties( model, nameOrPath ) {
73
73
  artifacts: artifactDictionary,
74
74
  definitions: artifactDictionary,
75
75
  vocabularies: dictionary,
76
- $lateExtensions: dictionary,
77
76
  elements,
78
77
  columns,
79
78
  expand: columns,
@@ -257,17 +257,19 @@ function getExtensionAndMigrations(beforeModel, options, {
257
257
  function getDeletions(afterModel, options, { deletions }) {
258
258
  return function compareArtifacts(artifact, name) {
259
259
  const otherArtifact = afterModel.definitions[name];
260
- const isPersisted = isPersistedAsTable(artifact);
261
- const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
262
-
263
- // Looking for deleted entities only.
264
- if (isPersisted && !isPersistedOther) {
260
+ const isPersistedTable = isPersistedAsTable(artifact);
261
+ const isPersistedView = isPersistedAsView(artifact);
262
+ const isPersistedTableOther = otherArtifact && isPersistedAsTable(otherArtifact);
263
+ const isPersistedViewOther = otherArtifact && isPersistedAsView(otherArtifact);
264
+
265
+ // Looking for deleted entities or table -> view / view -> table
266
+ if (
267
+ (isPersistedTable && isPersistedViewOther) || // table -> view
268
+ (isPersistedView && isPersistedTableOther) || // view -> table
269
+ ((isPersistedTable || isPersistedView) && // deleted
270
+ !(isPersistedTableOther || isPersistedViewOther))
271
+ ) // view turned into table - need to render a drop for the view
265
272
  deletions[name] = artifact;
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
269
- deletions[name] = artifact;
270
- }
271
273
  };
272
274
  }
273
275