@sap/cds-compiler 6.4.6 → 6.5.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 (65) hide show
  1. package/CHANGELOG.md +34 -1156
  2. package/README.md +1 -10
  3. package/doc/IncompatibleChanges_v5.md +436 -0
  4. package/doc/IncompatibleChanges_v6.md +659 -0
  5. package/doc/Versioning.md +3 -7
  6. package/lib/api/main.js +1 -0
  7. package/lib/api/options.js +5 -0
  8. package/lib/api/validate.js +3 -0
  9. package/lib/base/message-registry.js +23 -0
  10. package/lib/base/messages.js +1 -1
  11. package/lib/base/model.js +3 -2
  12. package/lib/checks/actionsFunctions.js +6 -3
  13. package/lib/checks/existsInForbiddenPlaces.js +32 -0
  14. package/lib/checks/validator.js +2 -0
  15. package/lib/compiler/assert-consistency.js +3 -5
  16. package/lib/compiler/checks.js +4 -8
  17. package/lib/compiler/define.js +244 -459
  18. package/lib/compiler/extend.js +297 -11
  19. package/lib/compiler/finalize-parse-cdl.js +2 -10
  20. package/lib/compiler/generate.js +29 -1
  21. package/lib/compiler/populate.js +21 -63
  22. package/lib/compiler/propagator.js +1 -2
  23. package/lib/compiler/resolve.js +2 -12
  24. package/lib/compiler/shared.js +18 -5
  25. package/lib/compiler/tweak-assocs.js +13 -9
  26. package/lib/compiler/utils.js +97 -0
  27. package/lib/compiler/xpr-rewrite.js +2 -1
  28. package/lib/edm/annotations/edmJson.js +9 -6
  29. package/lib/edm/annotations/genericTranslation.js +8 -4
  30. package/lib/edm/csn2edm.js +3 -4
  31. package/lib/edm/edmInboundChecks.js +1 -2
  32. package/lib/edm/edmPreprocessor.js +3 -3
  33. package/lib/gen/CdlGrammar.checksum +1 -1
  34. package/lib/gen/CdlParser.js +1 -1
  35. package/lib/gen/Dictionary.json +16 -1
  36. package/lib/json/from-csn.js +4 -6
  37. package/lib/json/to-csn.js +3 -3
  38. package/lib/model/csnRefs.js +13 -4
  39. package/lib/model/enrichCsn.js +4 -2
  40. package/lib/optionProcessor.js +8 -4
  41. package/lib/render/utils/sql.js +3 -2
  42. package/lib/transform/db/applyTransformations.js +1 -1
  43. package/lib/transform/db/assertUnique.js +3 -3
  44. package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
  45. package/lib/transform/db/assocsToQueries/transformExists.js +14 -3
  46. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  47. package/lib/transform/db/backlinks.js +4 -4
  48. package/lib/transform/db/cdsPersistence.js +4 -4
  49. package/lib/transform/db/constraints.js +4 -4
  50. package/lib/transform/db/expansion.js +5 -5
  51. package/lib/transform/db/flattening.js +4 -5
  52. package/lib/transform/db/rewriteCalculatedElements.js +3 -3
  53. package/lib/transform/db/temporal.js +11 -11
  54. package/lib/transform/draft/db.js +2 -0
  55. package/lib/transform/draft/odata.js +5 -7
  56. package/lib/transform/effective/flattening.js +1 -2
  57. package/lib/transform/forOdata.js +3 -3
  58. package/lib/transform/forRelationalDB.js +1 -1
  59. package/lib/transform/odata/createForeignKeys.js +1 -2
  60. package/lib/transform/odata/flattening.js +1 -2
  61. package/lib/transform/odata/toFinalBaseType.js +52 -55
  62. package/lib/transform/transformUtils.js +3 -4
  63. package/package.json +1 -1
  64. package/doc/CHANGELOG_BETA.md +0 -464
  65. package/doc/CHANGELOG_DEPRECATED.md +0 -235
@@ -75,7 +75,6 @@ const $location = Symbol.for( 'cds.$location' );
75
75
  const $inferred = Symbol.for( 'cds.$inferred' );
76
76
 
77
77
  // TODO: make this part of specExpected in shared.js
78
- // (standard: not possible on last if !ref.$expected → ref.scope: '$exists')
79
78
  const expWithFilter = [ 'from', 'expand', 'inline' ];
80
79
 
81
80
  // Export function of this file. Resolve type references in augmented CSN
@@ -1557,16 +1556,6 @@ function resolve( model ) {
1557
1556
  }
1558
1557
 
1559
1558
  function resolveExprPath( expr, expected, user ) {
1560
- // TODO: re-think this $expected: 'exists' thing
1561
- if (expr.$expected === 'exists') {
1562
- if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
1563
- error( 'expr-unexpected-exists', [ expr.location, user ], {},
1564
- 'An EXISTS predicate is not expected here' );
1565
- }
1566
- // We complain about the EXISTS before, as EXISTS subquery is also not supported
1567
- // TODO: location of EXISTS, TODO: really do this in define.js
1568
- expr.$expected = 'approved-exists'; // only complain once
1569
- }
1570
1559
  const ref = resolvePath( expr, expected, user );
1571
1560
 
1572
1561
  if (expected === 'annotation') {
@@ -1588,9 +1577,10 @@ function resolve( model ) {
1588
1577
  last._navigation?.kind === '$tableAlias') // error already reported
1589
1578
  return ref;
1590
1579
 
1591
- if (expr.$expected === 'approved-exists') {
1580
+ if (expr.$syntax === 'after-exists') {
1592
1581
  if (last.where?.args?.length === 0) {
1593
1582
  // at the moment, empty filter is not allowed on last path step
1583
+ // TODO: allow it at some places
1594
1584
  reportUnexpectedArgsAndFilter( last, expected, user, null, 'last-empty-filter' );
1595
1585
  }
1596
1586
  return ref;
@@ -64,6 +64,17 @@ function fns( model ) {
64
64
  notFound: undefinedForAnnotate,
65
65
  accept: extendableArtifact,
66
66
  },
67
+ 'annotate-sec': {
68
+ isMainRef: 'all',
69
+ lexical: userBlock,
70
+ dynamic: modelDefinitions,
71
+ notFound: undefinedDefinition,
72
+ messageMap: {
73
+ 'ref-undefined-art': 'ext-undefined-art-sec',
74
+ 'ref-undefined-def': 'ext-undefined-def-sec',
75
+ },
76
+ accept: extendableArtifact,
77
+ },
67
78
  extend: {
68
79
  isMainRef: 'no-generated',
69
80
  lexical: userBlock,
@@ -233,6 +244,7 @@ function fns( model ) {
233
244
  check: checkAssocOn,
234
245
  param: () => '$scopePar', // TODO: check that assocs containing param in ON is not published
235
246
  },
247
+ 'rewrite-on': {}, // only for traversal when rewriting on condition
236
248
  'orderBy-ref': {
237
249
  lexical: tableAliasesAndSelf,
238
250
  dollar: true,
@@ -382,7 +394,7 @@ function fns( model ) {
382
394
  }
383
395
  if (expr.args) {
384
396
  const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
385
- for (const arg of args ) {
397
+ for (const arg of args) {
386
398
  if (traverseExpr( arg, exprCtx, user, callback ) === traverseExpr.STOP)
387
399
  return traverseExpr.STOP;
388
400
  }
@@ -1283,11 +1295,11 @@ function fns( model ) {
1283
1295
 
1284
1296
  // Functions called via semantics.notFound: -----------------------------------
1285
1297
 
1286
- function undefinedDefinition( user, item, valid, _dict, prev ) {
1298
+ function undefinedDefinition( user, item, valid, _dict, prev, _path, semantics ) {
1287
1299
  // in a CSN source or for `using`, only one env was tested (valid.length 1) :
1288
1300
  const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
1289
1301
  signalNotFound( (valid.length > 1 ? 'ref-undefined-art' : 'ref-undefined-def'),
1290
- [ item.location, user ], valid, { art } );
1302
+ [ item.location, user ], valid, { art }, semantics );
1291
1303
  // TODO: improve text, use text variant for: "or builtin" or "definitions" or none
1292
1304
  }
1293
1305
 
@@ -1876,8 +1888,9 @@ function fns( model ) {
1876
1888
  }
1877
1889
  const index = userTargetElementPathIndex( user, path );
1878
1890
  checkOnlyForeignKeyNavigation( user, path, index );
1891
+ // TODO: did we check for no filter/args, no `exists` etc?
1879
1892
  const last = path[path.length - 1];
1880
- if (!last.where && ref._artifact?.on) { // filter already complained about
1893
+ if (!last.where && ref._artifact?.on) { // filter already complained about - TODO: where?
1881
1894
  const target = index > 0 && index < path.length && ref._artifact?.target;
1882
1895
  const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
1883
1896
  error( 'ref-unexpected-assoc', [ last.location, user ],
@@ -2016,7 +2029,7 @@ function fns( model ) {
2016
2029
  }
2017
2030
 
2018
2031
  function checkNoUnmanaged( ref, user, self, messageVariant = 'unmanaged' ) {
2019
- if (ref._artifact?.on && !ref.$expected) {
2032
+ if (ref._artifact?.on && ref.$syntax !== 'after-exists') {
2020
2033
  const { path } = ref;
2021
2034
  const last = path[path.length - 1];
2022
2035
  if (self && last.where) // already complained about filter
@@ -5,7 +5,6 @@
5
5
  const {
6
6
  forEachGeneric,
7
7
  forEachInOrder,
8
- isBetaEnabled,
9
8
  } = require('../base/model');
10
9
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
11
10
 
@@ -138,10 +137,10 @@ function tweakAssocs( model ) {
138
137
 
139
138
  function handleQueryElements( column ) {
140
139
  rewriteAssociationCheck( column );
141
- if (!isBetaEnabled( model.options, '$calcForDraft' ))
142
- return;
140
+ if (model.options.noDollarCalc || column._columnParent)
141
+ return; // no $calc supported with inline
143
142
  const { value } = column; // `value` = column expression
144
- if (!value || !value.args && !value.suffix)
143
+ if (!value || value.path) // not with references
145
144
  return;
146
145
  // TODO: what about non-simple refs (assocs, even with filter/args)?
147
146
 
@@ -946,6 +945,8 @@ function tweakAssocs( model ) {
946
945
  * for item.
947
946
  */
948
947
  function rewriteColumnPath( ref, column ) {
948
+ if (ref.query)
949
+ return traverseExpr.STOP; // sub queries rewrite not supported
949
950
  if (!ref._artifact)
950
951
  return null;
951
952
  const root = ref.path?.[0];
@@ -1010,9 +1011,12 @@ function tweakAssocs( model ) {
1010
1011
  state = setArtifactLink( i, elem );
1011
1012
  }
1012
1013
  else {
1013
- state = rewriteItem( state, i, assoc );
1014
- if (!state || state === true)
1015
- break;
1014
+ state = rewriteItem( state, i, assoc, !location );
1015
+ if (state && state !== true)
1016
+ continue;
1017
+ if (!state && !location)
1018
+ return true;
1019
+ break;
1016
1020
  }
1017
1021
  }
1018
1022
  if (state !== true)
@@ -1032,7 +1036,7 @@ function tweakAssocs( model ) {
1032
1036
  * @param item Path segment to rewrite.
1033
1037
  * @param assoc Published association of query.
1034
1038
  */
1035
- function rewriteItem( elem, item, assoc ) {
1039
+ function rewriteItem( elem, item, assoc, noError ) {
1036
1040
  if (!elem._redirected)
1037
1041
  return true;
1038
1042
  let name = item.id;
@@ -1055,7 +1059,7 @@ function tweakAssocs( model ) {
1055
1059
  if (env?.target)
1056
1060
  env = env.target._artifact?._effectiveType;
1057
1061
  const found = setArtifactLink( item, env?.elements?.[name] );
1058
- if (found)
1062
+ if (found || noError)
1059
1063
  return found;
1060
1064
 
1061
1065
  const isExplicit = elem.target && !elem.target.$inferred;
@@ -134,6 +134,28 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
134
134
  }
135
135
  }
136
136
 
137
+ // Initialization (define.js), shared with extend.js: ---------------------------
138
+
139
+ /**
140
+ * Initialize artifact links inside `obj.items` (for nested ones as well).
141
+ * Does nothing, it `obj.items` does not exist.
142
+ *
143
+ * @param {XSN.Artifact} obj
144
+ * @param {object} block
145
+ * @return {XSN.Artifact}
146
+ */
147
+ function initItemsLinks( obj, block ) {
148
+ let { items } = obj;
149
+ while (items) {
150
+ setLink( items, '_outer', obj );
151
+ setLink( items, '_parent', obj._parent );
152
+ setLink( items, '_block', block );
153
+ obj = items;
154
+ items = obj.items;
155
+ }
156
+ return obj;
157
+ }
158
+
137
159
  /**
138
160
  * Set the member `elem` to have a _parent link to `parent` and a corresponding
139
161
  * _main link. Also set the member's name accordingly, where argument `name`
@@ -162,6 +184,76 @@ function createAndLinkCalcDepElement( elem ) {
162
184
  setLink( r, '_outer', elem );
163
185
  }
164
186
 
187
+ function initExprAnnoBlock( art, block ) {
188
+ // remark: `art` could also be the extension
189
+ for (const prop in art) {
190
+ if (prop.charAt(0) !== '@')
191
+ continue;
192
+ const anno = art[prop];
193
+ // _block links needed for `cast( … as Type )`, `[ ..., cast( … as Type ) ]`
194
+ if (anno.literal === 'array') {
195
+ anno.kind = '$annotation';
196
+ for (const item of anno.val)
197
+ setLink( item, '_block', block );
198
+ }
199
+ else if (anno.$tokenTexts) {
200
+ // remark: it wouldn't hurt to set it always...
201
+ anno.kind = '$annotation';
202
+ setLink( anno, '_block', block );
203
+ }
204
+ }
205
+ }
206
+
207
+ function initDollarSelf( art ) {
208
+ // TODO: use setMemberParent() ?
209
+ const self = {
210
+ name: { id: '$self', location: art.location },
211
+ kind: '$self',
212
+ location: art.location,
213
+ };
214
+ setLink( self, '_parent', art );
215
+ setLink( self, '_main', art ); // used on main artifact
216
+ setLink( self, '_origin', art );
217
+ art.$tableAliases = Object.create( null );
218
+ art.$tableAliases.$self = self;
219
+ }
220
+
221
+ function initDollarParameters( art ) {
222
+ // TODO: use setMemberParent() ?
223
+ const parameters = {
224
+ name: { id: '$parameters' },
225
+ kind: '$parameters',
226
+ location: art.location,
227
+ deprecated: true, // hide in code completion
228
+ };
229
+ setLink( parameters, '_parent', art );
230
+ setLink( parameters, '_main', art );
231
+ // Search for :const after :param. If there will be a possibility in the
232
+ // future that we can extend <query>.columns, we must be sure to use
233
+ // _block of that new column after :param (or just allow $parameters there).
234
+ setLink( parameters, '_block', art._block );
235
+ if (art.params) {
236
+ parameters.elements = art.params;
237
+ parameters.$tableAliases = art.params; // TODO: find better name - $lexical?
238
+ }
239
+ art.$tableAliases.$parameters = parameters;
240
+ }
241
+
242
+ function initBoundSelfParam( params, main ) {
243
+ if (!params)
244
+ return;
245
+ const first = params[Object.keys( params )[0] || ''];
246
+ const type = first?.type || first?.items?.type; // this sequence = no derived type
247
+ const path = type?.path;
248
+ if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
249
+ const $self = main.$tableAliases?.$self ||
250
+ main.kind === 'extend' && { name: { id: '$self' } };
251
+ // remark: an 'extend' has no "table alias" `$self` (relevant for parse-cdl)
252
+ setLink( type, '_artifact', $self );
253
+ setLink( path[0], '_artifact', $self );
254
+ }
255
+ }
256
+
165
257
  /**
166
258
  * Adds a dependency user -> art with the given location.
167
259
  *
@@ -702,8 +794,13 @@ module.exports = {
702
794
  proxyCopyMembers,
703
795
  dependsOn,
704
796
  dependsOnSilent,
797
+ initItemsLinks,
705
798
  setMemberParent,
706
799
  createAndLinkCalcDepElement,
800
+ initExprAnnoBlock,
801
+ initDollarSelf,
802
+ initDollarParameters,
803
+ initBoundSelfParam,
707
804
  storeExtension,
708
805
  withAssociation,
709
806
  pathName,
@@ -395,7 +395,8 @@ function xprRewriteFns( model ) {
395
395
  }
396
396
  }
397
397
 
398
- if (isSimpleSelectItem && model.options.testMode)
398
+ if (isSimpleSelectItem && model.options.testMode &&
399
+ destination.value?.path[0]._navigation?.kind !== 'mixin')
399
400
  throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(destination.value.path) }`);
400
401
 
401
402
  if (isAnnoPathAbsolute( expr ))
@@ -93,8 +93,6 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
93
93
  //----------------------------------
94
94
  // operators not supported as dynamic expression
95
95
  '.': notADynExpr,
96
- isNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is null'),
97
- isNotNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is not null'),
98
96
  exists: notADynExpr,
99
97
  SELECT: notADynExpr,
100
98
  SET: (p, o) => notADynExpr(p, o, null, null, null, null, 'UNION'),
@@ -302,6 +300,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
302
300
  transform.$Mul = noOp;
303
301
  transform['/'] = op('$DivBy');
304
302
  transform.$DivBy = noOp;
303
+ transform.isNull = op('$Eq');
304
+ transform.isNotNull = op('$Ne');
305
305
  // $Div, $Mod are functions
306
306
  //----------------------------------
307
307
  // LITERALS
@@ -885,10 +885,13 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
885
885
  if (xpr.length === 2 && (xpr[0] === '+' || xpr[0] === '-' || xpr[0] === 'not' || xpr[0] === 'new'))
886
886
  return { [xpr[0]]: xpr[1] };
887
887
 
888
- if (xpr.length === 3 && xpr[1] === 'is' && xpr[2] === 'null') {
889
- rejectInvalidExpression(xpr);
890
- return xpr;
891
- }
888
+ // is null
889
+ if (xpr.length === 3 && xpr[1] === 'is' && xpr[2] === 'null')
890
+ return { isNull: [ xpr[0], { val: null } ] };
891
+
892
+ // is not null
893
+ if (xpr.length === 4 && xpr[1] === 'is' && xpr[2] === 'not' && xpr[3] === 'null')
894
+ return { isNotNull: [ xpr[0], { val: null } ] };
892
895
 
893
896
  // binary operators: '=', '<>', 'like', ...
894
897
  if (xpr.length === 3 && typeof xpr[1] === 'string')
@@ -916,8 +916,8 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
916
916
  }
917
917
  // expression
918
918
  const res = handleExpression(cAnnoValue['='], dTypeName);
919
- oTarget.setXml( { [res.name]: res.value });
920
- oTarget.setJSON( { [res.name]: res.value });
919
+ oTarget.setXml({ [res.name]: res.value });
920
+ oTarget.setJSON({ [res.name]: res.value });
921
921
  }
922
922
  else if (cAnnoValue['#'] !== undefined) {
923
923
  const enumSymbol = cAnnoValue['#'];
@@ -968,9 +968,13 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
968
968
  }
969
969
  else if (cAnnoValue.$edmJson) {
970
970
  // "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
971
- oTarget.append(handleEdmJson(cAnnoValue.$edmJson, msg));
971
+ const edmNode = handleEdmJson(cAnnoValue.$edmJson, msg);
972
+ if (edmNode && edmNode._kind === 'Path' && typeof edmNode._value === 'string' && !edmNode._children.length)
973
+ oTarget.setXml({ [edmNode._kind]: edmNode._value });
974
+ else
975
+ oTarget.append(edmNode);
972
976
  }
973
- else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
977
+ else if (Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
974
978
  // object consists only of properties starting with "@", no $value
975
979
  setProp(oTarget, '$isInvalid', true);
976
980
  message('odata-anno-value', msg.location,
@@ -49,7 +49,6 @@ function csn2edm( _csn, serviceName, _options, messageFunctions ) {
49
49
  function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
50
50
  // get us a fresh model copy that we can work with
51
51
  const csn = cloneFullCsn(_csn, _options);
52
- const special$self = !csn?.definitions?.$self && '$self';
53
52
  messageFunctions.setModel(csn);
54
53
 
55
54
  const {
@@ -689,7 +688,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
689
688
  }
690
689
  }
691
690
 
692
- if (options.isV2() && isBetaEnabled(options, 'draftAdminDataHiddenFilter')) {
691
+ if (options.isV2()) {
693
692
  if (elementCsn._edmParentCsn.name === `${ elementCsn._edmParentCsn.$mySchemaName }.DraftAdministrativeData`)
694
693
  elementCsn['@UI.HiddenFilter'] ??= true;
695
694
  }
@@ -762,7 +761,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
762
761
  const entries = Object.entries(actionCsn.params);
763
762
  const firstParam = entries[0][1];
764
763
  const type = firstParam?.items?.type || firstParam?.type;
765
- if (type === special$self) {
764
+ if (type === '$self') {
766
765
  bpName = entries[0][0];
767
766
  setProp(actionCsn, '$bindingParam', firstParam);
768
767
  // preserve the original type (as it is the key to reqDefs.defintions)
@@ -938,7 +937,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
938
937
  if (parameterCsn['@cds.api.ignore'])
939
938
  return;
940
939
  const type = parameterCsn?.items?.type || parameterCsn?.type;
941
- if (i === 0 && type === special$self) {
940
+ if (i === 0 && type === '$self') {
942
941
  // skip and remove the first parameter if it is a $self binding parameter to
943
942
  // omit annotation rendering later on
944
943
  setProp(actionCsn, '$bindingParam', parameterCsn);
@@ -130,12 +130,11 @@ function inboundQualificationChecks( csn, options, messageFunctions,
130
130
  // we need to know if the first path step is the bindind param
131
131
  // for the rejection of V2 paths where the BP is ignored
132
132
  function markBindingParamPaths( action, loc ) {
133
- const special$self = !csn?.definitions?.$self && '$self';
134
133
  if (action.params) {
135
134
  const params = Object.entries(action.params);
136
135
  const firstParam = params[0][1];
137
136
  const type = firstParam?.items?.type || firstParam?.type;
138
- if (type === special$self) {
137
+ if (type === '$self') {
139
138
  const bindingParamName = params[0][0];
140
139
  const markBindingParam = {
141
140
  ref: (parent, prop, xpr) => {
@@ -40,7 +40,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
40
40
  const {
41
41
  info, warning, error, message,
42
42
  } = messageFunctions;
43
- const special$self = !csn?.definitions?.$self && '$self';
44
43
  const csnUtils = getUtils(csn);
45
44
 
46
45
  // proxies are merged into the final model after all proxy elements are collected
@@ -2077,7 +2076,8 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
2077
2076
 
2078
2077
  function iterateParams( action, location ) {
2079
2078
  let optPns = [];
2080
- const isBP = p => (p.items?.type || p.type) === special$self;
2079
+ const isBP = p => (p.items?.type || p.type) === '$self' &&
2080
+ p === Object.values( action.params )[0];
2081
2081
 
2082
2082
  if (action.params) {
2083
2083
  Object.entries(action.params).forEach(([ pn, p ]) => {
@@ -2126,7 +2126,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
2126
2126
  }
2127
2127
  else if (action.kind === 'function') {
2128
2128
  // this is a mandatory parameter, warn about all previously collected optional parameters
2129
- if (optPns.filter(op => (op.items?.type || op.type) !== special$self).length)
2129
+ if (optPns.length)
2130
2130
  error('odata-parameter-order', location.concat(pn));
2131
2131
  optPns = [];
2132
2132
  }
@@ -1 +1 @@
1
- 8e38091fda3711e1d4d2b8b87fbdd328
1
+ 2af41f8c84ca1fa40a50b8c58903dadc
@@ -3827,7 +3827,7 @@ case 645:this.s=651;{ this.attachLocation( $.expr ); }continue
3827
3827
  case 646:if(this.valuePath(_={},651)){e=_.expr; this.valuePathAstWithNew( $.expr, e ); }continue
3828
3828
  case 647:switch(this.l()){
3829
3829
  case'(':if(this.c(648))open=this.lb();continue
3830
- case'Id':if(this.valuePath(_={},651)){e=_.expr; e = this.valuePathAst( e ); e.$expected = 'exists';
3830
+ case'Id':if(this.valuePath(_={},651)){e=_.expr; e = this.valuePathAst( e ); e.$syntax = 'after-exists';
3831
3831
  $.expr.args.push( e ); this.attachLocation( $.expr ); }continue
3832
3832
  case'?':if(this.c(651)){ this.csnParseOnly( 'syntax-unsupported-param', -1, { '#': 'dynamic', code: '?' } );
3833
3833
  $.expr.args.push( { param: this.valueWithLocation(), scope: 'param' } ); this.attachLocation( $.expr ); }continue
@@ -596,7 +596,7 @@
596
596
  "Property",
597
597
  "Parameter"
598
598
  ],
599
- "Type": "Edm.String"
599
+ "Type": "Edm.PrimitiveType"
600
600
  },
601
601
  "Common.FieldControl": {
602
602
  "AppliesTo": [
@@ -910,6 +910,13 @@
910
910
  "$deprecationText": "Use terms [Aggregation.RecursiveHierarchy](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Aggregation.V1.md#RecursiveHierarchy) and [Hierarchy.RecursiveHierarchy](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Hierarchy.md#RecursiveHierarchy) instead",
911
911
  "Type": "Common.RecursiveHierarchyType"
912
912
  },
913
+ "Common.ReferentialConstraint": {
914
+ "$experimental": true,
915
+ "AppliesTo": [
916
+ "NavigationProperty"
917
+ ],
918
+ "Type": "Collection(Common.ReferentialConstraintType)"
919
+ },
913
920
  "Common.RelatedRecursiveHierarchy": {
914
921
  "AppliesTo": [
915
922
  "Property"
@@ -3345,6 +3352,14 @@
3345
3352
  "NodeDrillStateProperty": "Edm.PropertyPath"
3346
3353
  }
3347
3354
  },
3355
+ "Common.ReferentialConstraintType": {
3356
+ "$experimental": true,
3357
+ "$kind": "ComplexType",
3358
+ "Properties": {
3359
+ "Property": "Edm.PropertyPath",
3360
+ "ReferencedProperty": "Edm.PropertyPath"
3361
+ }
3362
+ },
3348
3363
  "Common.SAPObjectNodeTypeType": {
3349
3364
  "$experimental": true,
3350
3365
  "$kind": "ComplexType",
@@ -1682,15 +1682,13 @@ function exprOrString( val, spec ) {
1682
1682
  : expr( val, spec );
1683
1683
  }
1684
1684
 
1685
- // mark path argument of 'exists' predicate with $expected:'exists'
1685
+ // mark path argument of 'exists' predicate with $syntax:'after-exists'
1686
1686
  function exprArgs( cond, spec ) {
1687
1687
  const rxsn = arrayOf( exprOrString )( cond, spec );
1688
- // TODO: do that in definer.js, neither here nor in CDL parser
1689
1688
  if (Array.isArray( rxsn )) {
1690
- for (let i = 0; i < rxsn.length - 1; i++) {
1691
- // TODO: disallow param ref - write test
1692
- if (cond[i] === 'exists' && rxsn[i + 1].path)
1693
- rxsn[++i].$expected = 'exists';
1689
+ for (let i = 0; i < rxsn.length - 1;) {
1690
+ if (cond[i++] === 'exists' && rxsn[i].path)
1691
+ rxsn[i].$syntax = 'after-exists';
1694
1692
  }
1695
1693
  }
1696
1694
  return rxsn;
@@ -1172,15 +1172,15 @@ function enumValueOrCalc( v, csn, node, prop ) {
1172
1172
  if (node.kind === 'enum') {
1173
1173
  Object.assign( csn, expression( v ) );
1174
1174
  }
1175
- else if (v === true && prop === '$calc') {
1176
- return true; // calculation has used non-projected elements
1175
+ else if (prop === '$calc') {
1176
+ return v === true || expression( v );
1177
1177
  }
1178
1178
  // In XSN, there are combined elem/column objects: do not represent column
1179
1179
  // expression when presented as element in CSN
1180
1180
 
1181
1181
  // node.$syntax set in define.el(!), but not inside an `extend`, a _parent might
1182
1182
  // not be set always for parse-only, especially with CSN input
1183
- else if (node.$syntax === 'calc' || prop === '$calc' ||
1183
+ else if (node.$syntax === 'calc' ||
1184
1184
  !node._parent || node._parent.kind === 'extend') {
1185
1185
  const stored = v.stored ? { stored: value(v.stored) } : {};
1186
1186
  return Object.assign( stored, expression( v ) );
@@ -519,6 +519,8 @@ function csnRefs( csn, universalReady ) {
519
519
  * Return the object pointing to by the artifact reference (in 'type',
520
520
  * 'includes', 'target'). For `from`, use artifactRefFrom()!
521
521
  *
522
+ * Warning: do not use it for the binding parameter if the ref is `$self`.
523
+ *
522
524
  * @param {CSN.ArtifactReferencePath|string} ref
523
525
  * @param {any} [notFound] Value that is returned in case the artifact reference
524
526
  * could not be found.
@@ -612,9 +614,16 @@ function csnRefs( csn, universalReady ) {
612
614
 
613
615
  function getOriginRaw( art ) {
614
616
  if (art.type) { // TODO: make robust against "linked" = only direct
615
- return (art.type !== '$self' || csn.definitions.$self)
616
- ? artifactRef( art.type, BUILTIN_TYPE )
617
- : getCache( boundActionOrMain( art ), '_parent' );
617
+ if (art.type !== '$self')
618
+ return artifactRef( art.type, BUILTIN_TYPE );
619
+ const action = boundActionOrMain( art );
620
+ const binding = action?.params && Object.values( action.params )[0];
621
+ // binding parameter must be typed with `$self` or `many $self`:
622
+ const entity = binding && (art === binding || art === binding.items) &&
623
+ getCache( action, '_parent' );
624
+ // if name-deprecated-$self is a non-config error, this could be simplified to:
625
+ // const entity = action?.params && getCache( action, '_parent' );
626
+ return entity || artifactRef( art.type, BUILTIN_TYPE );
618
627
  }
619
628
  if (typeof art.$origin === 'object') // null, […], {…}
620
629
  return getOriginExplicit( art.$origin );
@@ -798,7 +807,7 @@ function csnRefs( csn, universalReady ) {
798
807
  }
799
808
 
800
809
  if (!qcache)
801
- throw new CompilerAssertion( `Query not in cache at: ${ locationString(query.$location) }` );
810
+ throw new CompilerAssertion( `For semantics '${ refCtx }', query not in cache at: ${ locationString(query.$location) }` );
802
811
 
803
812
  if (semantics.dynamic === 'query') {
804
813
  // TODO: for ON condition in expand, would need to use cached _element
@@ -69,7 +69,6 @@ function enrichCsn( csn, options = {} ) {
69
69
  // options.enrichCsn = 'DEBUG';
70
70
  let $$cacheObjectNumber = 0; // for debugging
71
71
  const debugLocationInfo = options.enrichCsn === 'DEBUG' && Object.create(null);
72
- const special$self = !csn.definitions.$self && '$self';
73
72
 
74
73
  setLocations( csn, false, null );
75
74
  const {
@@ -180,7 +179,10 @@ function enrichCsn( csn, options = {} ) {
180
179
  parent[`_${ prop }`] = ref.map( r => refLocation( artifactRef( r, notFound ) ) );
181
180
  }
182
181
  else if (typeof ref === 'string') {
183
- if (!ref.startsWith( 'cds.') && ref !== special$self)
182
+ // Don't use `artifactRef( 'self' )` for binding parameter. Because it is not
183
+ // easily recognizable here, always omit `_type` property for `$self` even if
184
+ // there is a type named `$self` (which would induce a configurable error):
185
+ if (!ref.startsWith( 'cds.') && ref !== '$self')
184
186
  parent[`_${ prop }`] = refLocation( artifactRef( ref, notFound ) );
185
187
  }
186
188
  else if (!ref.elements) {
@@ -2,11 +2,13 @@
2
2
 
3
3
  /* eslint @stylistic/max-len: 0 */
4
4
 
5
- // Remarks:
6
- // - The specification is client-tool centric (bin/cdsc.js):
5
+ // Remarks (`git blame` on these remarks does not reveal the options designer)
6
+ // - The specification is client-tool centric (../bin/cdsc.js):
7
7
  // an option named `fooBar` is “produced” by `.option(' --foo-bar')`.
8
8
  // - Also list the option in the `help` text, used with `cdsc -h`.
9
- // - Specify valid values for non-boolean options in lib/api/validate.js.
9
+ // - All options must also be added to ./api/options.js.
10
+ // - Specify valid values for non-boolean options in ./api/validate.js.
11
+ // - Beta and deprecated options are specified in ./base/model.js.
10
12
 
11
13
  'use strict';
12
14
 
@@ -54,7 +56,8 @@ optionProcessor
54
56
  .option(' --add-texts-language-assoc')
55
57
  .option(' --localized-without-coalesce')
56
58
  .option(' --tenant-discriminator')
57
- .option(' --no-composition-includes')
59
+ .option(' --no-composition-includes') // TODO: missed to be removed in #13762
60
+ .option(' --no-dollar-calc') // for grep: option `noDollarCalc`
58
61
  .option(' --default-binary-length <length>')
59
62
  .option(' --default-string-length <length>')
60
63
  .option(' --struct-xpr')
@@ -157,6 +160,7 @@ optionProcessor
157
160
  to "sap.common.Languages" if it exists
158
161
  --localized-without-coalesce Omit coalesce in localized convenience views
159
162
  --no-composition-includes Do NOT add named aspects to 'includes' property of generated composition entity.
163
+ --no-dollar-calc Don't set $calc property in CSN
160
164
  --no-recompile Don't recompile in case of internal errors
161
165
  --struct-xpr Write structured expressions to the compiler CSN output (possibly then
162
166
  used as input for backends)
@@ -198,10 +198,11 @@ function _artifactIsProjectionView( csn, artifact ) {
198
198
  referencedElements[column.ref.at(-1)] = true;
199
199
  }
200
200
 
201
- // Full primary key needs to be projected according to HANA SQL spec
201
+ // Full primary key/not null chain needs to be projected according to HANA SQL spec
202
+ // https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/create-projection-view-statement-data-definition
202
203
  for (const elementName in source.elements) {
203
204
  const element = source.elements[elementName];
204
- if (element.key && !referencedElements[elementName])
205
+ if ((element.notNull || element.key) && !referencedElements[elementName])
205
206
  return false;
206
207
  }
207
208
 
@@ -211,7 +211,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
211
211
  *
212
212
  * @param {object | Array} node the thing that has _prop
213
213
  * @param {string|number} _prop the name of the current property
214
- * @param {any} _path The value of node[_prop]
214
+ * @param {Array} _path The value of node[_prop]
215
215
  */
216
216
  function pathRef( node, _prop, _path ) {
217
217
  csnPath.push( _prop );