@sap/cds-compiler 4.4.2 → 4.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 (60) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/bin/cdsc.js +5 -0
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +16 -0
  5. package/lib/api/main.js +68 -47
  6. package/lib/api/options.js +10 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +28 -6
  9. package/lib/base/messages.js +18 -13
  10. package/lib/base/model.js +3 -0
  11. package/lib/checks/annotationsOData.js +49 -0
  12. package/lib/checks/validator.js +6 -4
  13. package/lib/compiler/assert-consistency.js +38 -16
  14. package/lib/compiler/builtins.js +10 -49
  15. package/lib/compiler/checks.js +16 -8
  16. package/lib/compiler/cycle-detector.js +1 -4
  17. package/lib/compiler/define.js +4 -1
  18. package/lib/compiler/extend.js +21 -7
  19. package/lib/compiler/generate.js +3 -0
  20. package/lib/compiler/populate.js +5 -1
  21. package/lib/compiler/propagator.js +46 -9
  22. package/lib/compiler/resolve.js +68 -14
  23. package/lib/compiler/shared.js +44 -27
  24. package/lib/compiler/tweak-assocs.js +158 -37
  25. package/lib/compiler/utils.js +9 -0
  26. package/lib/edm/annotations/edmJson.js +35 -61
  27. package/lib/edm/annotations/genericTranslation.js +13 -5
  28. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  29. package/lib/edm/csn2edm.js +4 -1
  30. package/lib/edm/edmInboundChecks.js +59 -15
  31. package/lib/edm/edmPreprocessor.js +1 -7
  32. package/lib/gen/Dictionary.json +8 -0
  33. package/lib/gen/language.checksum +1 -1
  34. package/lib/gen/language.interp +12 -2
  35. package/lib/gen/languageParser.js +6095 -5195
  36. package/lib/json/from-csn.js +4 -5
  37. package/lib/json/to-csn.js +22 -3
  38. package/lib/language/errorStrategy.js +7 -3
  39. package/lib/language/genericAntlrParser.js +120 -24
  40. package/lib/language/textUtils.js +16 -0
  41. package/lib/model/csnUtils.js +9 -8
  42. package/lib/model/revealInternalProperties.js +5 -2
  43. package/lib/modelCompare/compare.js +10 -4
  44. package/lib/optionProcessor.js +2 -3
  45. package/lib/render/toCdl.js +31 -13
  46. package/lib/render/toHdbcds.js +20 -30
  47. package/lib/render/toSql.js +33 -54
  48. package/lib/render/utils/common.js +24 -6
  49. package/lib/transform/db/applyTransformations.js +59 -2
  50. package/lib/transform/db/backlinks.js +13 -1
  51. package/lib/transform/db/expansion.js +24 -3
  52. package/lib/transform/db/flattening.js +2 -2
  53. package/lib/transform/db/killAnnotations.js +37 -0
  54. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  55. package/lib/transform/forOdata.js +13 -46
  56. package/lib/transform/forRelationalDB.js +2 -1
  57. package/lib/transform/translateAssocsToJoins.js +13 -4
  58. package/lib/transform/universalCsn/coreComputed.js +1 -1
  59. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  60. package/package.json +7 -6
@@ -149,8 +149,11 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
149
149
  art, env, links, scope,
150
150
  } = getRefInfo(parent, p);
151
151
 
152
+ // calc element publishes association, treat as regular
153
+ // unmanaged association
154
+ const calcElementIsAssoc = art?.value && art.target;
152
155
  // TODO: Calculated elements on-write
153
- if (art?.value && !art.value.stored) {
156
+ if (art?.value && !art.value.stored && !calcElementIsAssoc) {
154
157
  const alias = parent.as || implicitAs(parent.ref);
155
158
  // TODO: What about other scopes? expand/inline?
156
159
  const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : keepAssocStepsInRef(ref, links, art).value;
@@ -446,11 +449,15 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
446
449
  unfoldingMap[name] = [ true, [ ...columns ] ];
447
450
  }
448
451
  }
449
- else if (!columnMap[name] && hasStar) { // Via * - just append
450
- unfoldingMap[name] = [ true, [ { ref: [ name ] } ] ];
451
- }
452
- else { // just a random column - keep
453
- unfoldingMap[name] = [ false, [ columnMap[name] ] ];
452
+ else {
453
+ if (usesCalcOnRead(branches))
454
+ containsCalcOnRead = true;
455
+ if (!columnMap[name] && hasStar) { // Via * - just append
456
+ unfoldingMap[name] = [ true, [ { ref: [ name ] } ] ];
457
+ }
458
+ else { // just a random column - keep
459
+ unfoldingMap[name] = [ false, [ columnMap[name] ] ];
460
+ }
454
461
  }
455
462
  }
456
463
  }
@@ -491,6 +498,39 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
491
498
  return false;
492
499
  }
493
500
 
501
+ /**
502
+ * Returns true if the branch/column uses a calc-on-read,
503
+ * for example in a filter.
504
+ *
505
+ * TODO: Enable calculated elements next to nested projections
506
+ *
507
+ * @param {object} branches
508
+ * @returns {boolean}
509
+ */
510
+ function usesCalcOnRead( branches ) {
511
+ let returnValue = false;
512
+ for (const branchName in branches) {
513
+ const column = branches[branchName]?.steps[0]?._column;
514
+ if (column) {
515
+ applyTransformationsOnNonDictionary({ column }, 'column', {
516
+ // eslint-disable-next-line no-loop-func
517
+ ref: (parent) => {
518
+ if (hasOnReadValue(parent))
519
+ returnValue = true;
520
+ },
521
+ }, {
522
+ drillRef: true,
523
+ // skip subqueries and nested projections
524
+ // calculated elements and nested projections
525
+ // only conflict on same level
526
+ skipStandard: [ 'SELECT', 'expand', 'inline' ],
527
+ });
528
+ }
529
+ }
530
+
531
+ return returnValue;
532
+ }
533
+
494
534
  /**
495
535
  * A leaf can reference a column which in turn references a real element - that might have a .value.
496
536
  * Find such cases.
@@ -82,9 +82,7 @@ function transform4odataWithCsn(inputModel, options) {
82
82
 
83
83
  const transformers = transformUtils.getTransformers(csn, options, '_');
84
84
  const {
85
- addDefaultTypeFacets,
86
- extractValidFromToKeyElement,
87
- checkAssignment, checkMultipleAssignments,
85
+ addDefaultTypeFacets, checkMultipleAssignments,
88
86
  recurseElements, setAnnotation, renameAnnotation,
89
87
  expandStructsInExpression,
90
88
  csnUtils,
@@ -132,17 +130,17 @@ function transform4odataWithCsn(inputModel, options) {
132
130
  transformUtils.rewriteBuiltinTypeRef(csn);
133
131
 
134
132
  const cleanup = validate.forOdata(csn, {
135
- message, error, warning, info, inspectRef, effectiveType, getFinalTypeInfo, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
133
+ message, error, warning, info, inspectRef, effectiveType, getFinalTypeInfo, artifactRef,
134
+ options, csnUtils, services, isAspect, isExternalServiceMember, recurseElements,
135
+ checkMultipleAssignments, csn,
136
136
  });
137
137
 
138
138
 
139
139
  // Throw exception in case of errors
140
140
  throwWithAnyError();
141
141
 
142
- // Semantic checks before flattening regarding temporal data
143
- // TODO: Move in the validator
142
+ // TODO: Refactor out the following logic
144
143
  forEachDefinition(csn, [
145
- checkTemporalAnnotationsAssignment,
146
144
  (def) => {
147
145
  // Convert a projection into a query for internal processing will be re-converted
148
146
  // at the end of the OData processing
@@ -175,7 +173,6 @@ function transform4odataWithCsn(inputModel, options) {
175
173
  // If errors are detected, throwWithAnyError() will return from further processing
176
174
  expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
177
175
 
178
-
179
176
  if (!structuredOData) {
180
177
  expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, csnUtils, { skipArtifact: isExternalServiceMember });
181
178
  const resolved = new WeakMap();
@@ -272,7 +269,7 @@ function transform4odataWithCsn(inputModel, options) {
272
269
 
273
270
  // Convert a query back into a projection for CSN compliance as
274
271
  // the very last conversion step of the OData transformation
275
- if (def.kind === 'entity' && def.query && def.query && def.projection) {
272
+ if (def.kind === 'entity' && def.query && def.projection) {
276
273
  delete def.query;
277
274
  }
278
275
  }, { skipArtifact: isExternalServiceMember })
@@ -288,29 +285,6 @@ function transform4odataWithCsn(inputModel, options) {
288
285
  timetrace.stop('OData transformation');
289
286
  return csn;
290
287
 
291
- // TODO: Move this to checks?
292
- // @ts-ignore
293
- function checkTemporalAnnotationsAssignment(artifact, artifactName, propertyName, path) {
294
- // Gather all element names with @cds.valid.from/to/key
295
- let validFrom = [], validTo = [], validKey = [];
296
- recurseElements(artifact, ['definitions', artifactName], (member, path) => {
297
- let [f, t, k] = extractValidFromToKeyElement(member, path);
298
- validFrom.push(...f);
299
- validTo.push(...t);
300
- validKey.push(...k);
301
- });
302
- // Check that @cds.valid.from/to/key is only in valid places
303
- validFrom.forEach(obj => checkAssignment('@cds.valid.from', obj.element, obj.path, artifact));
304
- validTo.forEach(obj => checkAssignment('@cds.valid.to', obj.element, obj.path, artifact));
305
- validKey.forEach(obj => checkAssignment('@cds.valid.key', obj.element, obj.path, artifact));
306
- checkMultipleAssignments(validFrom, '@cds.valid.from', artifact, artifactName);
307
- checkMultipleAssignments(validTo, '@cds.valid.to', artifact, artifactName);
308
- checkMultipleAssignments(validKey, '@cds.valid.key', artifact, artifactName);
309
- if (validKey.length && !(validFrom.length && validTo.length)) {
310
- error(null, path, 'Annotation “@cds.valid.key” was used but “@cds.valid.from” and “@cds.valid.to” are missing');
311
- }
312
- }
313
-
314
288
  // Mark elements that are annotated with @odata.on.insert/update with the annotation @Core.Computed.
315
289
  function annotateCoreComputed(node) {
316
290
  // If @Core.Computed is explicitly set, don't overwrite it!
@@ -442,25 +416,18 @@ function transform4odataWithCsn(inputModel, options) {
442
416
  }
443
417
  }
444
418
 
445
- // CDXCORE-481
446
419
  // (4.5) If the member is an association whose target has @cds.odata.valuelist annotate it
447
420
  // with @Common.ValueList.viaAssociation.
448
- /*
449
- FIXME (HJB): Comment outdated: Anno propagation to FKs is done in EdmPreprocessor
450
- */
451
- // This must be done before foreign keys are calculated and the annotations are propagated
452
- // to them. This will make sure that association and all its foreign keys are annotated with
453
- // Common.ValueList in the final EDM.
454
- // Do this only if the association is navigable and the enclosing artifact is
455
- // a service member (don't pollute the CSN with unnecessary annotations).
456
- // TODO: test???
421
+ // Do this only if the association is navigable(@odata.navigable) and the enclosing artifact is
422
+ // a service member (don't pollute the CSN with unnecessary annotations, that is ensured by the caller
423
+ // of this function).
457
424
  function addCommonValueListviaAssociation(member, memberName) {
458
- let vlAnno = '@Common.ValueList.viaAssociation';
425
+ const vlAnno = '@Common.ValueList.viaAssociation';
459
426
  if (isAssociation(member)) {
460
- let navigable = member['@odata.navigable'] !== false; // navigable disabled only if explicitly set to false
461
- let targetDef = getCsnDef(member.target);
427
+ const navigable = member['@odata.navigable'] !== false; // navigable disabled only if explicitly set to false
428
+ const targetDef = getCsnDef(member.target);
462
429
  if (navigable && targetDef['@cds.odata.valuelist'] && !member[vlAnno]) {
463
- member[vlAnno] = { '=': memberName };
430
+ setAnnotation(member, vlAnno, { '=': memberName });
464
431
  }
465
432
  }
466
433
  }
@@ -185,7 +185,8 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
185
185
 
186
186
  if(doA2J) {
187
187
  // Expand a structured thing in: keys, columns, order by, group by
188
- expansion.expandStructureReferences(csn, options, pathDelimiter, messageFunctions, csnUtils);
188
+ // In addition, kill all non-sql-backend relevant annotations
189
+ expansion.expandStructureReferences(csn, options, pathDelimiter, messageFunctions, csnUtils, { processAnnotations: true });
189
190
  bindCsnReference();
190
191
  }
191
192
 
@@ -263,8 +263,12 @@ function translateAssocsToJoins(model, inputOptions = {})
263
263
  /*
264
264
  Substitute $self/$projection expression with its value
265
265
  */
266
- function substituteDollarSelf(pathNode)
266
+ function substituteDollarSelf(pathNode, env)
267
267
  {
268
+ // do not substitute $self values for outer order by clauses
269
+ if (env?.location === 'UnionOuterOrderBy')
270
+ return;
271
+
268
272
  let pathValue = pathNode;
269
273
  let [head, ...tail] = pathValue.path;
270
274
  while(tail.length && head._navigation?.kind === '$self') {
@@ -311,12 +315,14 @@ function translateAssocsToJoins(model, inputOptions = {})
311
315
  {
312
316
  // Paths without _navigation in ORDER BY are select item aliases, they must
313
317
  // be rendered verbatim
314
- if((env.location === 'OrderBy' && !pathNode.path[0]._navigation))
318
+ let [head, ...tail] = pathNode.path;
319
+ if((env.location === 'OrderBy' && !head._navigation)||
320
+ env.location === 'UnionOuterOrderBy' && (!head._navigation || ['$self', '$projection'].includes(head.id)))
315
321
  return;
316
322
 
317
323
  // path outside ON cond:
318
324
  // spin the crystal ball to identify the correct table alias
319
- let [head, ...tail] = pathNode.path;
325
+
320
326
  // pop ta ps
321
327
  if(head._navigation.kind !== '$tableAlias')
322
328
  tail = pathNode.path;
@@ -1438,7 +1444,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1438
1444
  {
1439
1445
  // speciality for OrderBy: If path has no _navigation don't merge it.
1440
1446
  // Path is alias to select item expression
1441
- if(env.location === 'OrderBy')
1447
+ if(['OrderBy', 'UnionOuterOrderBy'].includes(env.location))
1442
1448
  return;
1443
1449
 
1444
1450
  // env.pathStep is set in walkPath for walk on filter conditions
@@ -1769,6 +1775,9 @@ function walkQuery(query, env)
1769
1775
  walk(query.having, env);
1770
1776
  env.location = 'OrderBy';
1771
1777
  walk(query.orderBy, env);
1778
+ env.location = 'UnionOuterOrderBy';
1779
+ // outer orderBy's of anonymous union
1780
+ walk(query.$orderBy, env);
1772
1781
  if(query.limit) {
1773
1782
  env.location = 'Limit';
1774
1783
  walk(query.limit.rows, env);
@@ -157,7 +157,7 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
157
157
  (
158
158
  column.xpr || column.list || column.func || column.val !== undefined || column['#'] !== undefined || column.param ||
159
159
  column.SELECT || column.SET ||
160
- column.ref && [ '$at', '$valid', '$now', '$user', '$session', '$parameters' ].includes(column.ref[0])
160
+ column.ref && [ '$at', '$valid', '$now', '$user', '$tenant', '$session', '$parameters' ].includes(column.ref[0])
161
161
  );
162
162
  }
163
163
 
@@ -328,9 +328,9 @@ module.exports = (csn, options) => {
328
328
  * passed to this function.
329
329
  *
330
330
  * @param {CSN.Element} member
331
- * @param {object} [except=null] List of properties which should not be propagated along the origin chain
331
+ * @param {object} [except] List of properties which should not be propagated along the origin chain
332
332
  * of the `member`
333
- * @param {object} [force=null] Overwrite any member propagation rules or any except and always propagate the corresponding keys
333
+ * @param {object} [force] Overwrite any member propagation rules or any except and always propagate the corresponding keys
334
334
  */
335
335
  function propagateMemberPropsFromOrigin( member, except = null, force = null ) {
336
336
  const memberChain = getOriginChain(member);
@@ -613,8 +613,8 @@ module.exports = (csn, options) => {
613
613
  * @param {object} to
614
614
  * @param {Function} getCustomRule getter for the `memberProps` or `defProps`
615
615
  * which shall be used for retrieving custom rules
616
- * @param {object} [except=null] array of properties which should not be propagated
617
- * @param {object} [force=null] Force propagation of the contained keys via a custom rule.
616
+ * @param {object} [except] array of properties which should not be propagated
617
+ * @param {object} [force] Force propagation of the contained keys via a custom rule.
618
618
  */
619
619
  function copyProperties( from, to, getCustomRule, except = null, force = null ) {
620
620
  const keys = Object.keys(from);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "4.4.2",
3
+ "version": "4.5.0",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",
@@ -18,15 +18,16 @@
18
18
  "gen": "node ./scripts/build.js && node scripts/genGrammarChecksum.js",
19
19
  "xmakeAfterInstall": "npm run gen",
20
20
  "xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
21
- "test": "node scripts/verifyGrammarChecksum.js && mocha --reporter min --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/",
22
- "testci": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter-option maxDiffSize=0 test/ test3/",
23
- "testverbose": "node scripts/verifyGrammarChecksum.js && mocha --parallel test/ test3/",
21
+ "test": "npm run test:piper",
22
+ "test:ci": "node scripts/verifyGrammarChecksum.js && mocha --timeout 10000 --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter-option maxDiffSize=0 test/ test3/",
23
+ "test:piper": "node scripts/verifyGrammarChecksum.js && npm run coverage:piper",
24
24
  "test3": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 test3/",
25
25
  "deployHanaSql": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.hana-sql.js",
26
26
  "deployHdiHdbcds": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.hdi.hdbcds.js",
27
27
  "deployGitDiffs": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.git-diffs.js",
28
28
  "gentest3": "cross-env MAKEREFS=${MAKEREFS:-'true'} mocha --reporter-option maxDiffSize=0 test3/testRefFiles.js",
29
- "coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/ && nyc report --reporter=lcov",
29
+ "coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/testRefFiles.js && nyc report --reporter=lcov",
30
+ "coverage:piper": "cross-env nyc mocha --reporter mocha-junit-reporter --reporter-options mochaFile=./coverage/TEST-results.xml --reporter-option maxDiffSize=0 --timeout 10000 test/ test3/ && nyc report --reporter=cobertura && nyc report --reporter=lcov",
30
31
  "lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessages.js && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint .",
31
32
  "tslint": "tsc --pretty -p .",
32
33
  "updateVocs": "node scripts/odataAnnotations/generateDictMain.js && npm run generateAllRefs",
@@ -40,7 +41,7 @@
40
41
  "generateToSqlRefs": "cross-env MAKEREFS='true' mocha test/testToSql.js",
41
42
  "generateToRenameRefs": "cross-env MAKEREFS='true' mocha test/testToRename.js",
42
43
  "generateDraftRefs": "cross-env MAKEREFS='true' mocha test/testDraft.js",
43
- "generateAllRefs": "node scripts/verifyGrammarChecksum.js && cross-env MAKEREFS='true' mocha --reporter-option maxDiffSize=0 test/ test3/"
44
+ "generateAllRefs": "node scripts/verifyGrammarChecksum.js && cross-env MAKEREFS=force mocha --reporter-option maxDiffSize=0 test/ test3/"
44
45
  },
45
46
  "keywords": [
46
47
  "CDS"