@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. package/lib/utils/string.js +0 -17
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ const { setProp } = require('../base/model');
2
3
  const { isBuiltinType, isEdmPropertyRendered } = require('../model/csnUtils');
3
4
 
4
5
  /* eslint max-statements-per-line:off */
@@ -114,6 +115,12 @@ function isToMany(assoc) {
114
115
  return targetMax === '*' || Number(targetMax) > 1;
115
116
  }
116
117
 
118
+ function isSingleton(entityCsn, v) {
119
+ const singleton = entityCsn['@odata.singleton'];
120
+ const hasNullable = entityCsn['@odata.singleton.nullable'] !== undefined && entityCsn['@odata.singleton.nullable'] !== null;
121
+ return v && singleton || ((singleton === undefined || singleton === null) && hasNullable);
122
+ }
123
+
117
124
  function isEntity(artifact)
118
125
  {
119
126
  return ['entity'].includes(artifact.kind);
@@ -142,7 +149,7 @@ function isActionOrFunction(artifact) {
142
149
  return ['action', 'function'].includes(artifact.kind);
143
150
  }
144
151
 
145
- function resolveOnConditionAndPrepareConstraints(assocCsn, messageFunctions) {
152
+ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions) {
146
153
  if(!assocCsn._constraints)
147
154
  throw Error('Please debug me: need _constraints');
148
155
 
@@ -154,7 +161,7 @@ function resolveOnConditionAndPrepareConstraints(assocCsn, messageFunctions) {
154
161
  getExpressionArguments(assocCsn.on);
155
162
 
156
163
  // for all $self conditions, fill constraints of partner (if any)
157
- let isBacklink = assocCsn._constraints.selfs.length == 1 && assocCsn._constraints.termCount == 1;
164
+ let isBacklink = assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1;
158
165
 
159
166
  /* example for _originalTarget:
160
167
  entity E (with parameters) {
@@ -167,14 +174,19 @@ function resolveOnConditionAndPrepareConstraints(assocCsn, messageFunctions) {
167
174
  ON Condition back.toE => parter=toE cannot be resolved in EParameters, _originalTarget 'E' is
168
175
  required for that
169
176
  */
170
- assocCsn._constraints.selfs.filter(p => p).forEach(partner => {
171
- const originAssocCsn = (assocCsn._originalTarget || assocCsn._target).elements[partner];
172
- const parentArtifactName = assocCsn._parent.name;
177
+ assocCsn._constraints.selfs.filter(p => p).forEach(partnerPath => {
178
+ // resolve partner path in target
179
+ const originAssocCsn = resolveOriginAssoc(csn, (assocCsn._originalTarget || assocCsn._target), partnerPath);
180
+ const parentName = assocCsn.$abspath[0];
181
+ const parent = csn.definitions[parentName];
173
182
  if(originAssocCsn) {
174
- if(originAssocCsn._originalTarget !== assocCsn._parent && originAssocCsn._target !== assocCsn._parent) {
183
+ const originParentName = originAssocCsn.$abspath[0];
184
+ if(originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
175
185
  isBacklink = false;
176
- info(null, ['definitions', parentArtifactName, 'elements', assocCsn.name],
177
- `"${originAssocCsn._parent.name}:${partner}" with target "${originAssocCsn._target.name}" is compared with $self which represents "${parentArtifactName}"`);
186
+ // Partnership is ambiguous
187
+ setProp(originAssocCsn, '$noPartner', true);
188
+ info(null, ['definitions', parentName, 'elements', assocCsn.name],
189
+ `"${originParentName}:${partnerPath.join('.')}" with target "${originAssocCsn._target.name}" is compared with $self which represents "${parentName}"`);
178
190
  }
179
191
  if(isAssociationOrComposition(originAssocCsn)) {
180
192
  // Mark this association as backlink if $self appears exactly once
@@ -187,7 +199,10 @@ function resolveOnConditionAndPrepareConstraints(assocCsn, messageFunctions) {
187
199
  else {
188
200
  isBacklink = false;
189
201
  }
190
- // collect all backlinks at forward association
202
+ }
203
+ // store all backlinks at forward, required to calculate rendering of foreign keys
204
+ // if the termCount != 1 or more than one $self compare this is not a backlink
205
+ if(assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1) {
191
206
  originAssocCsn._selfReferences.push(assocCsn);
192
207
  }
193
208
  assocCsn._constraints._origins.push(originAssocCsn);
@@ -203,8 +218,8 @@ function resolveOnConditionAndPrepareConstraints(assocCsn, messageFunctions) {
203
218
  }
204
219
  else
205
220
  {
206
- warning(null, ['definitions', parentArtifactName],
207
- { partner: `${assocCsn._target.name}/${partner}`, name: `${parentArtifactName}/${assocCsn.name}` },
221
+ warning(null, ['definitions', parentName],
222
+ { partner: `${assocCsn._target.name}/${partnerPath}`, name: `${parentName}/${assocCsn.name}` },
208
223
  'Can\'t resolve backlink to $(PARTNER) from $(NAME)');
209
224
  }
210
225
  });
@@ -268,7 +283,7 @@ function resolveOnConditionAndPrepareConstraints(assocCsn, messageFunctions) {
268
283
  // do we have a $self id?
269
284
  // if so, store partner in selfs array
270
285
  if(c[0][0] === '$self' && c[0].length === 1) {
271
- assocCsn._constraints.selfs.push(c[1][0]);
286
+ assocCsn._constraints.selfs.push(c[1]);
272
287
  } else {
273
288
  const key = c.join(',');
274
289
  assocCsn._constraints.constraints[key] = c;
@@ -281,7 +296,7 @@ function resolveOnConditionAndPrepareConstraints(assocCsn, messageFunctions) {
281
296
  }
282
297
  }
283
298
 
284
- function finalizeReferentialConstraints(assocCsn, options, warning)
299
+ function finalizeReferentialConstraints(csn, assocCsn, options, info)
285
300
  {
286
301
  if(!assocCsn._constraints)
287
302
  throw Error('Please debug me: need _constraints');
@@ -317,11 +332,16 @@ function finalizeReferentialConstraints(assocCsn, options, warning)
317
332
  });
318
333
 
319
334
  if(!assocCsn._target.$isParamEntity) {
320
- // Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys
321
- let dependentEntity = assocCsn._parent;
335
+ // Use $path to identify main artifact in case assocs parent was a nested type and deanonymized
336
+ // Some (draft) associations don't have a $path, use _parent as last resort
337
+ let dependentEntity = assocCsn.$path ? csn.definitions[assocCsn.$path[1]] : assocCsn._parent;
338
+ let localDepEntity = assocCsn._parent;
339
+ // _target must always be a main artifact
322
340
  let principalEntity = assocCsn._target;
323
341
  if(assocCsn.type === 'cds.Composition') {
324
- principalEntity = assocCsn._parent;
342
+ // Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys
343
+ principalEntity = dependentEntity;
344
+ localDepEntity = undefined;
325
345
  dependentEntity = assocCsn._target;
326
346
  // Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal]
327
347
  Object.keys(assocCsn._constraints.constraints).forEach(cn => {
@@ -332,14 +352,44 @@ function finalizeReferentialConstraints(assocCsn, options, warning)
332
352
  const remainingPrincipalRefs = [];
333
353
  foreach(assocCsn._constraints.constraints,
334
354
  c => {
355
+ // rc === true will remove the constraint (positive filter expression)
356
+ let rc = true;
335
357
  // concatenate all paths in flat mode to identify the correct element
336
358
  // in structured mode only resolve top level element (path rewriting is done elsewhere)
337
- const fk = dependentEntity.elements[ ( options.isFlatFormat ? c[0].join('_') : c[0][0] )];
338
- const principalEltRef = ( options.isFlatFormat ? c[1].join('_') : c[1][0] );
339
- const pk = principalEntity.$keys && principalEntity.$keys[ principalEltRef ];
340
- if(isConstraintCandidate(fk) && isConstraintCandidate(pk))
341
- remainingPrincipalRefs.push(principalEltRef);
342
- return !(isConstraintCandidate(fk) && isConstraintCandidate(pk));
359
+ const depEltName = ( options.isFlatFormat ? c[0].join('_') : c[0][0] );
360
+ const principalEltName = ( options.isFlatFormat ? c[1].join('_') : c[1][0] );
361
+ const fk = (isEntity(dependentEntity) && dependentEntity.elements[ depEltName ]) ||
362
+ (localDepEntity && localDepEntity.elements && localDepEntity.elements[ depEltName ]);
363
+ const pk = principalEntity.$keys && principalEntity.$keys[ principalEltName ];
364
+ if(isConstraintCandidate(fk) && isConstraintCandidate(pk)) {
365
+ if(options.isStructFormat) {
366
+ // In structured mode it might be the association has a new _parent due to
367
+ // type de-anonymization.
368
+ // There are three cases for dependent ON condition paths:
369
+ // 1) path is relative to assoc in same sub structure
370
+ // 2) path is absolute and ends up in a different environment
371
+ // 3) path is absolute and touches in assoc's environment
372
+
373
+ // => 1) if _parents are equal, fk path is relative to assoc
374
+ if(fk._parent === assocCsn._parent) {
375
+ rc = false;
376
+ }
377
+ // => 2) & 3) if path is not relative to assoc, remove main entity (pos=0) and assoc (pos=n-1)
378
+ // and check path identity: If absolute path touches assoc's _parent, add it
379
+ else if(!assocCsn.$abspath.slice(1, assocCsn.$abspath.length-1).some((p,i) => c[0][i] !== p)) {
380
+ // this was an absolute addressed path, remove environment prefix
381
+ c[0].splice(0, assocCsn.$abspath.length-2);
382
+ rc = false;
383
+ }
384
+ }
385
+ else {
386
+ // for flat mode isConstraintCandidate(fk) && isConstraintCandidate(pk) is sufficient
387
+ rc = false;
388
+ }
389
+ }
390
+ if(!rc)
391
+ remainingPrincipalRefs.push(principalEltName);
392
+ return rc;
343
393
  },
344
394
  (c, cn) => { delete assocCsn._constraints.constraints[cn]; }
345
395
  );
@@ -349,8 +399,8 @@ function finalizeReferentialConstraints(assocCsn, options, warning)
349
399
  const renderedKeys = Object.values(principalEntity.$keys).filter(isConstraintCandidate).map(v=>v.name);
350
400
  if(options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length)
351
401
  if(options.odataV2PartialConstr) {
352
- warning('odata-spec-violation-constraints', ['definitions', assocCsn._parent.name, 'elements', assocCsn.name],
353
- 'Partial referential constraints (not spec compliant) produced based on user setting');
402
+ info('odata-spec-violation-constraints',
403
+ ['definitions', assocCsn._parent.name, 'elements', assocCsn.name], { api: 'OData V2' });
354
404
  }
355
405
  else {
356
406
  assocCsn._constraints.constraints = {};
@@ -387,8 +437,8 @@ function finalizeReferentialConstraints(assocCsn, options, warning)
387
437
  const renderedKeys = Object.values(assocCsn._target.$keys).filter(isConstraintCandidate).map(v=>v.name);
388
438
  if(options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length) {
389
439
  if(options.odataV2PartialConstr) {
390
- warning('odata-spec-violation-constraints', ['definitions', assocCsn._parent.name, 'elements', assocCsn.name],
391
- 'Partial referential constraints (not spec compliant) produced based on user setting');
440
+ info('odata-spec-violation-constraints',
441
+ ['definitions', assocCsn._parent.name, 'elements', assocCsn.name], { api: 'OData V2' } );
392
442
  }
393
443
  else {
394
444
  assocCsn._constraints.constraints = {};
@@ -474,7 +524,7 @@ function determineMultiplicity(csn)
474
524
 
475
525
  function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false)
476
526
  {
477
- const { warning, error } = messageFunctions;
527
+ const { error } = messageFunctions || { error: ()=>true };
478
528
  let cdsType = csn.type;
479
529
  if(cdsType === undefined) {
480
530
  error(null, csn.$location, `no type found`);
@@ -543,8 +593,6 @@ function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false)
543
593
  if(['cds.hana.ST_POINT', 'cds.hana.ST_GEOMETRY'].includes(cdsType)) {
544
594
  error(null, csn.$path, { type: cdsType }, `OData V2 does not support Geometry data types, $(TYPE) can't be mapped`);
545
595
  }
546
- if(cdsType === 'cds.DecimalFloat' || cdsType === 'cds.hana.SMALLDECIMAL')
547
- warning(null, csn.$path, { type: cdsType }, `OData V2 does not support $(TYPE)`);
548
596
  }
549
597
  else // isV4
550
598
  {
@@ -571,13 +619,21 @@ function addTypeFacets(node, csn)
571
619
  // node.Precision = 16;
572
620
  else if (csn.type === 'cds.Timestamp' && node.Type === 'Edm.DateTimeOffset')
573
621
  node.Precision = 7;
574
- else if([ 'cds.Decimal', 'cds.DecimalFloat', 'cds.hana.SMALLDECIMAL' ].includes(csn.type) && !csn.precision && !csn.scale) {
622
+ if([ 'cds.Decimal', 'cds.DecimalFloat', 'cds.hana.SMALLDECIMAL' ].includes(csn.type)) {
575
623
  if(isV2) {
576
- node.setXml( { 'sap:variable-scale': true } );
624
+ // no prec/scale or scale is 'floating'/'variable'
625
+ if(!(csn.precision || csn.scale) || ['floating', 'variable'].includes(csn.scale)) {
626
+ node.setXml( { 'sap:variable-scale': true } );
627
+ delete node.Scale;
628
+ }
577
629
  }
578
630
  else {
579
- // if Decimal has no p, s set scale 'variable'
580
- node.setXml( { Scale: 'variable' } ); // floating is V4.01
631
+ // map both floating and variable to => variable
632
+ if(node.Scale === 'floating')
633
+ node.Scale = 'variable';
634
+ if(!csn.precision && !csn.scale)
635
+ // if Decimal has no p, s set scale 'variable'
636
+ node.setXml( { Scale: 'variable' } ); // floating is V4.01
581
637
  }
582
638
  }
583
639
  // Unicode unused today
@@ -636,6 +692,19 @@ function getBaseName(name) {
636
692
  return (lastDotIdx > 0 ) ? name.substring(lastDotIdx+1, name.length) : name;
637
693
  }
638
694
 
695
+ // This is a poor mans path resolver for $self partner paths only
696
+ function resolveOriginAssoc(csn, env, path) {
697
+ for(let i = 0; i < path.length; i++) {
698
+ let elements = (env.items && env.items.elements || env.elements);
699
+ if(elements)
700
+ env = env.elements[path[i]];
701
+ let type = (env.items && env.items.type || env.type);
702
+ if(type && !isBuiltinType(type) && !(env.items && env.items.elements || env.elements))
703
+ env = csn.definitions[env.type];
704
+ }
705
+ return env;
706
+ }
707
+
639
708
  module.exports = {
640
709
  validateOptions,
641
710
  intersect,
@@ -647,6 +716,7 @@ module.exports = {
647
716
  isComposition,
648
717
  isAssociationOrComposition,
649
718
  isToMany,
719
+ isSingleton,
650
720
  isEntity,
651
721
  isStructuredType,
652
722
  isStructuredArtifact,
@@ -729,6 +729,13 @@
729
729
  ],
730
730
  "$experimental": true
731
731
  },
732
+ "Common.Composition": {
733
+ "Type": "Core.Tag",
734
+ "AppliesTo": [
735
+ "NavigationProperty"
736
+ ],
737
+ "$experimental": true
738
+ },
732
739
  "Common.SAPObjectNodeTypeReference": {
733
740
  "Type": "Edm.String",
734
741
  "AppliesTo": [
@@ -1345,6 +1352,9 @@
1345
1352
  "Core.SymbolicName": {
1346
1353
  "Type": "Core.SimpleIdentifier"
1347
1354
  },
1355
+ "Core.GeometryFeature": {
1356
+ "Type": "Core.GeometryFeatureType"
1357
+ },
1348
1358
  "Graph.links": {
1349
1359
  "Type": "Collection(Graph.link)",
1350
1360
  "AppliesTo": [
@@ -3018,6 +3028,14 @@
3018
3028
  "DefaultValue": "Edm.String"
3019
3029
  }
3020
3030
  },
3031
+ "Core.GeometryFeatureType": {
3032
+ "$kind": "ComplexType",
3033
+ "Properties": {
3034
+ "geometry": "Edm.Geometry",
3035
+ "properties": "Core.Dictionary",
3036
+ "id": "Edm.String"
3037
+ }
3038
+ },
3021
3039
  "Core.RevisionKind": {
3022
3040
  "$kind": "EnumType",
3023
3041
  "Members": [
@@ -3066,6 +3084,10 @@
3066
3084
  "$kind": "TypeDefinition",
3067
3085
  "UnderlyingType": "Edm.String"
3068
3086
  },
3087
+ "Core.QualifiedActionName": {
3088
+ "$kind": "TypeDefinition",
3089
+ "UnderlyingType": "Edm.String"
3090
+ },
3069
3091
  "Core.LocalDateTime": {
3070
3092
  "$kind": "TypeDefinition",
3071
3093
  "UnderlyingType": "Edm.String"
@@ -1 +1 @@
1
- 30c3c4af0d829a94ab568af3036f12a9
1
+ 1bb596306f100150c345f169744f5170