@sap/cds-compiler 6.0.14 → 6.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/cdsc.js +6 -2
  3. package/bin/cdsse.js +1 -1
  4. package/bin/cdsv2m.js +1 -1
  5. package/lib/api/main.js +29 -7
  6. package/lib/api/options.js +2 -0
  7. package/lib/base/builtins.js +9 -0
  8. package/lib/base/keywords.js +1 -1
  9. package/lib/base/message-registry.js +5 -3
  10. package/lib/base/messages.js +3 -3
  11. package/lib/base/model.js +1 -0
  12. package/lib/base/node-helpers.js +10 -2
  13. package/lib/base/optionProcessorHelper.js +7 -2
  14. package/lib/checks/assocOutsideService.js +3 -1
  15. package/lib/checks/featureFlags.js +4 -1
  16. package/lib/compiler/assert-consistency.js +3 -1
  17. package/lib/compiler/base.js +1 -1
  18. package/lib/compiler/builtins.js +1 -1
  19. package/lib/compiler/checks.js +38 -21
  20. package/lib/compiler/define.js +24 -5
  21. package/lib/compiler/extend.js +1 -1
  22. package/lib/compiler/finalize-parse-cdl.js +9 -1
  23. package/lib/compiler/generate.js +4 -4
  24. package/lib/compiler/index.js +10 -1
  25. package/lib/compiler/lsp-api.js +2 -0
  26. package/lib/compiler/populate.js +8 -8
  27. package/lib/compiler/propagator.js +1 -1
  28. package/lib/compiler/resolve.js +15 -14
  29. package/lib/compiler/shared.js +6 -7
  30. package/lib/compiler/tweak-assocs.js +6 -6
  31. package/lib/compiler/utils.js +9 -16
  32. package/lib/compiler/xpr-rewrite.js +2 -2
  33. package/lib/gen/BaseParser.js +43 -37
  34. package/lib/gen/CdlGrammar.checksum +1 -1
  35. package/lib/gen/CdlParser.js +1424 -1433
  36. package/lib/gen/Dictionary.json +1 -7
  37. package/lib/gen/cdlKeywords.json +26 -0
  38. package/lib/inspect/inspectPropagation.js +1 -1
  39. package/lib/json/from-csn.js +2 -2
  40. package/lib/json/to-csn.js +9 -5
  41. package/lib/language/multiLineStringParser.js +1 -1
  42. package/lib/main.d.ts +10 -2
  43. package/lib/model/cloneCsn.js +1 -0
  44. package/lib/optionProcessor.js +13 -7
  45. package/lib/parsers/AstBuildingParser.js +24 -21
  46. package/lib/parsers/identifiers.js +2 -30
  47. package/lib/render/toCdl.js +63 -9
  48. package/lib/render/toSql.js +127 -108
  49. package/lib/render/utils/sql.js +67 -0
  50. package/lib/transform/addTenantFields.js +4 -4
  51. package/lib/transform/db/killAnnotations.js +1 -0
  52. package/lib/transform/db/processSqlServices.js +20 -2
  53. package/lib/transform/draft/db.js +1 -1
  54. package/lib/transform/draft/odata.js +14 -4
  55. package/lib/transform/forOdata.js +91 -2
  56. package/lib/transform/forRelationalDB.js +1 -1
  57. package/lib/transform/odata/flattening.js +1 -1
  58. package/lib/transform/transformUtils.js +2 -2
  59. package/lib/transform/translateAssocsToJoins.js +2 -26
  60. package/lib/utils/moduleResolve.js +1 -1
  61. package/package.json +2 -2
@@ -59,7 +59,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
59
59
  const filterDict = Object.create(null);
60
60
 
61
61
  // validate the 'DRAFT.DraftAdministrativeData_DraftMessage' type if already present in the model
62
- if (isBetaEnabled(options, 'draftMessages')) {
62
+ if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
63
63
  const draftAdminDataMessagesType = csn.definitions['DRAFT.DraftAdministrativeData_DraftMessage'];
64
64
  if (draftAdminDataMessagesType && !isValidDraftAdminDataMessagesType(draftAdminDataMessagesType)) {
65
65
  error(null, [ 'definitions', 'DRAFT.DraftAdministrativeData_DraftMessage' ], { name: 'DRAFT.DraftAdministrativeData_DraftMessage' },
@@ -180,7 +180,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
180
180
  // ... on SiblingEntity.IsActiveEntity != IsActiveEntity ...
181
181
  siblingEntity.SiblingEntity.on = createAssociationPathComparison('SiblingEntity', 'IsActiveEntity', '!=', 'IsActiveEntity');
182
182
 
183
- if (isBetaEnabled(options, 'draftMessages')) {
183
+ if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
184
184
  const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
185
185
  addElement(draftMessages, artifact, artifactName);
186
186
 
@@ -189,8 +189,11 @@ function generateDrafts( csn, options, services, messageFunctions ) {
189
189
  setAnnotation(artifact, '@Common.SideEffects#alwaysFetchMessages.TargetProperties', ['DraftMessages'] );
190
190
  }
191
191
  setAnnotation(artifact, '@Common.Messages', { '=': 'DraftMessages', ref: ['DraftMessages'] });
192
- const service = csn.definitions[getServiceOfArtifact(artifactName, services)];
193
- setAnnotation(service, '@Common.AddressViaNavigationPath', true);
192
+ setAnnotationAddressViaNavigationPath(artifactName, services);
193
+ }
194
+
195
+ if (options.addAnnotationAddressViaNavigationPath) {
196
+ setAnnotationAddressViaNavigationPath(artifactName, services);
194
197
  }
195
198
 
196
199
  // Iterate elements
@@ -291,6 +294,13 @@ function generateDrafts( csn, options, services, messageFunctions ) {
291
294
  }
292
295
  })
293
296
  }
297
+
298
+ // Set the @Common.AddressViaNavigationPath annotation to the service of
299
+ // the current artifact, if not set already
300
+ function setAnnotationAddressViaNavigationPath(artifactName, services) {
301
+ const service = csn.definitions[getServiceOfArtifact(artifactName, services)];
302
+ setAnnotation(service, '@Common.AddressViaNavigationPath', true);
303
+ }
294
304
  }
295
305
 
296
306
  module.exports = generateDrafts;
@@ -29,6 +29,7 @@ const { addLocalizationViews } = require('./localized');
29
29
  const { cloneFullCsn } = require('../model/cloneCsn');
30
30
  const { csnRefs } = require('../model/csnRefs');
31
31
  const replaceForeignKeyRefsInExpressionAnnotations = require('./odata/foreignKeyRefsInXprAnnos');
32
+ const { isAnnotationExpression, xprInAnnoProperties } = require('../base/builtins');
32
33
 
33
34
  // Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
34
35
  // The result should be suitable for consumption by EDMX processors (annotations and metadata)
@@ -293,6 +294,8 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
293
294
  || memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"
294
295
  }
295
296
 
297
+ processDynamicFieldControlAnnotations(member);
298
+
296
299
  // Mark fields with @odata.on.insert/update as @Core.Computed
297
300
  annotateCoreComputed(member);
298
301
 
@@ -338,6 +341,90 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
338
341
  //--------------------------------------------------------------------
339
342
  // HELPER SECTION STARTS HERE
340
343
 
344
+ // Transform @readonly/@mandatory/@disabled into @Common.FieldControl annotation
345
+ // with a when/then/else expression consisting of the input from the annotations.
346
+ function processDynamicFieldControlAnnotations(node) {
347
+ if (node['@Common.FieldControl']) return;
348
+ // TODO (SO): factor this out into a constant so we don't create a fresh array all the time?
349
+ if (['@readonly', '@mandatory', '@disabled'].some(key => typeof node[key] === 'boolean')) {
350
+ return;
351
+ }
352
+
353
+ const definedAnnotations = ['@disabled', '@readonly', '@mandatory']
354
+ .filter(key => node[key] && isAnnotationExpression(node[key]));
355
+
356
+ if (definedAnnotations.length === 0) return;
357
+
358
+ const values = {
359
+ '@disabled': { val: 0 },
360
+ '@readonly': { val: 1 },
361
+ '@mandatory': { val: 7 },
362
+ };
363
+
364
+ const fieldControl = {
365
+ '=': true,
366
+ xpr: createFieldControlExpression(definedAnnotations),
367
+ };
368
+
369
+ setAnnotation(node, '@Common.FieldControl', fieldControl);
370
+
371
+ function createFieldControlExpression(annotations) {
372
+ let nestedExpression = null;
373
+
374
+ for (let i = annotations.length - 1; i >= 0; i--) {
375
+ const annotation = annotations[i];
376
+ const xprInAnnoValue = getXprFromAnno(node[annotation]);
377
+ const annotationVal = values[annotation];
378
+
379
+ // Build the current annotation's expression
380
+ const currentExpression = [
381
+ 'case',
382
+ 'when',
383
+ ...(Array.isArray(xprInAnnoValue) ? xprInAnnoValue : [xprInAnnoValue]),
384
+ 'then',
385
+ annotationVal,
386
+ 'else',
387
+ // Use the previous nested expression or default value. Note that annotations
388
+ // are looped backwards
389
+ nestedExpression ? { xpr: nestedExpression } : { val: 3 },
390
+ 'end',
391
+ ];
392
+
393
+ // Update the nested expression
394
+ nestedExpression = currentExpression;
395
+ }
396
+ return nestedExpression;
397
+ }
398
+
399
+ function getXprFromAnno(anno) {
400
+ const xprProp = xprInAnnoProperties.find(prop => anno[prop] !== undefined);
401
+ const constructResult = {
402
+ 'ref': () => {
403
+ const result = { ref: anno.ref };
404
+ if (anno.cast)
405
+ result.cast = anno.cast;
406
+ return result;
407
+ },
408
+ 'xpr': () => anno.xpr,
409
+ 'list': () => ({ list: anno.list }),
410
+ 'literal': () => constructResult['val'](),
411
+ 'val': () => {
412
+ const result = { val: anno.val };
413
+ if (anno.literal)
414
+ result.literal = anno.literal;
415
+ return result;
416
+ },
417
+ '#': () => ({ '#': anno['#'] }),
418
+ 'func': () => ({ func: anno.func }),
419
+ 'args': () => ({ args: anno.args }),
420
+ 'SELECT': () => ({ SELECT: anno.SELECT }),
421
+ 'SET': () => ({ SET: anno.SET }),
422
+ 'cast': () => constructResult['ref']()
423
+ }
424
+ return constructResult[xprProp]();
425
+ }
426
+ }
427
+
341
428
  // Mark elements that are annotated with @odata.on.insert/update with the annotation @Core.Computed.
342
429
  function annotateCoreComputed(node) {
343
430
  // If @Core.Computed is explicitly set, don't overwrite it!
@@ -403,7 +490,8 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
403
490
  setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false);
404
491
  setAnnotation(node, `@Capabilities.InsertRestrictions${ qualifier ? '#' + qualifier : ''}.Insertable`, false);
405
492
  setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false);
406
- } else {
493
+ } else if (!isAnnotationExpression(node['@readonly'])) {
494
+ // add @Core.Computed only for non-xpr values of @readonly
407
495
  setAnnotation(node, '@Core.Computed', true);
408
496
  }
409
497
  };
@@ -426,7 +514,8 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
426
514
 
427
515
  // Only on element level
428
516
  if(node.kind == null) {
429
- if (node['@mandatory'] && !Object.entries(node).some(([k,v]) => k === '@Common.FieldControl' || k.startsWith('@Common.FieldControl.') && v != null)) {
517
+ if (node['@mandatory'] && !isAnnotationExpression(node['@mandatory'])
518
+ && !Object.entries(node).some(([k,v]) => k === '@Common.FieldControl' || k.startsWith('@Common.FieldControl.') && v != null)) {
430
519
  setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
431
520
  }
432
521
  if (node['@assert.range'] != null)
@@ -360,7 +360,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
360
360
  messageFunctions.throwWithError();
361
361
 
362
362
  // TODO: Might have to do this earlier if we want special rendering for projections?
363
- const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && (csn.meta?.[featureFlags]?.$sqlService || csn.meta?.[featureFlags]?.$dummyService) ? processSqlServices(csn, options): () => {}
363
+ const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && (csn.meta?.[featureFlags]?.$sqlService || csn.meta?.[featureFlags]?.$dummyService || csn.meta?.[featureFlags]?.$dataProductService) ? processSqlServices(csn, options): () => {}
364
364
 
365
365
  // Apply view-specific transformations
366
366
  // (160) Projections now finally become views
@@ -472,7 +472,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
472
472
  generatedForeignKeysForAssoc.forEach(gfk => gfk[1]['@odata.foreignKey4'] = flatEltName);
473
473
  // reassign the generated foreign keys for current assoc in order to assign
474
474
  // correct values for $generatedFieldName later on during flattenManagedAssocsAsKeys();
475
- // eslint-disable-next-line @stylistic/js/max-statements-per-line
475
+ // eslint-disable-next-line @stylistic/max-statements-per-line
476
476
  setProp(flatElt, '$generatedForeignKeys', generatedForeignKeysForAssoc.map(gfk => { return { name: gfk[0] }}));
477
477
  }
478
478
  }
@@ -402,7 +402,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
402
402
  if (!draftAdminDataEntity) {
403
403
  draftAdminDataEntity = createAndAddDraftAdminDataEntity();
404
404
  model.definitions['DRAFT.DraftAdministrativeData'] = draftAdminDataEntity;
405
- if (isBetaEnabled(options, 'draftMessages')
405
+ if ((isBetaEnabled(options, 'draftMessages') || options.draftMessages)
406
406
  && options.transformation === 'odata'
407
407
  && !model.definitions['DRAFT.DraftAdministrativeData_DraftMessage']) {
408
408
  model.definitions['DRAFT.DraftAdministrativeData_DraftMessage'] = createDraftAdminDataMessagesType();
@@ -475,7 +475,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
475
475
  draftIsProcessedByMe.DraftIsProcessedByMe['@Common.Label'] = '{i18n>Draft_DraftIsProcessedByMe}';
476
476
  addElement(draftIsProcessedByMe, artifact, artifactName);
477
477
 
478
- if (isBetaEnabled(options, 'draftMessages')) {
478
+ if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
479
479
  const messages = { DraftMessages: { } };
480
480
  if (options.transformation === 'odata') {
481
481
  messages.DraftMessages = { items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } };
@@ -18,8 +18,6 @@ function translateAssocsToJoinsCSN(csn, options){
18
18
  const compileOptions = { ...options, $skipNameCheck: true };
19
19
  delete compileOptions.csnFlavor;
20
20
 
21
- //require('fs').writeFileSync('./csninput_a2j.json', JSON.stringify(csn, null,2))
22
- //console.log('CSN passed by A2J to compiler:',JSON.stringify(csn,null,2))
23
21
  const model = recompileX(csn, compileOptions);
24
22
  timetrace.stop('A2J: Recompiling model');
25
23
  timetrace.start('A2J: Translating associations to joins');
@@ -45,11 +43,7 @@ function translateAssocsToJoinsCSN(csn, options){
45
43
  deduplicateMessages( options.messages );
46
44
  }
47
45
 
48
- // FIXME: Move this somewhere more appropriate
49
- const newCsn = compactModel(model, compileOptions);
50
- //require('fs').writeFileSync('./csnoutput_a2j.json', JSON.stringify(newCsn, null,2))
51
- //console.log('CSN returned by A2J:',JSON.stringify(newCsn,null,2))
52
- return newCsn;
46
+ return compactModel(model, compileOptions);
53
47
  }
54
48
 
55
49
  function translateAssocsToJoins(model, inputOptions = {})
@@ -355,12 +349,8 @@ function translateAssocsToJoins(model, inputOptions = {})
355
349
  const leafArt = tail[tail.length-1]._artifact;
356
350
  const tailPath = tail.map(p=>p.id).join(pathDelimiter);
357
351
  const fk = tail[i]._artifact.$flatSrcFKs.find(f => f._artifact === leafArt && f.acc.startsWith(tailPath));
358
- if(!fk) {
359
- // const revealInternalProperties = require('../model/revealInternalProperties.js');
360
- // console.log('++++++++ Path tail: ', revealInternalProperties(tail[tail.length-1]._artifact));
361
- // console.log('******** Flat FKs\n', tail[i]._artifact.$flatSrcFKs.map(f => revealInternalProperties(f._artifact)));
352
+ if (!fk)
362
353
  throw new CompilerAssertion('Debug me: No flat FK found for FK rewriting');
363
- }
364
354
  // replace tail path with flattened foreign key including prefix
365
355
  tail.splice(i, tail.length, fk);
366
356
  }
@@ -1707,17 +1697,6 @@ function translateAssocsToJoins(model, inputOptions = {})
1707
1697
  return p.map(p => delim + p.id + delim).join('.');
1708
1698
  }
1709
1699
 
1710
- // for debugging only
1711
- // eslint-disable-next-line no-unused-vars
1712
- function printPath(pathDict, env)
1713
- {
1714
- const alias = (pathDict.name && pathDict.name.id) || '<undefined>'
1715
- const path = pathDict.path;
1716
- const s = pathAsStr(path, '"');
1717
- const me = env.lead && (env.lead.name.id || env.lead.op);
1718
- console.log(me + ': ' + env.location + ': ' + s + ' alias: ' + alias);
1719
- }
1720
-
1721
1700
  function clone(obj) {
1722
1701
  let newObj;
1723
1702
  if (typeof obj !== 'object' || obj === null) // return primitive type, note that typeof null === 'object'
@@ -1941,9 +1920,6 @@ function walkPath(node, env)
1941
1920
  // else if(path) {
1942
1921
  // var util = require('util');
1943
1922
  // var { reveal } = require('../model/revealInternalProperties');
1944
-
1945
- // console.log('Path not resolved, can\'t find leaf _artifact: ' + path.map(p=>p.id).join('.') + '\n' +
1946
- // util.inspect( reveal( node, false ), false, null ));
1947
1923
  // }
1948
1924
  return path;
1949
1925
  }
@@ -580,7 +580,7 @@ function checkFileCase( dep, realpath, nativeRealpath, messageFunctions ) {
580
580
  for (const using of dep.usingFroms) {
581
581
  const { warning } = messageFunctions;
582
582
  warning('file-unexpected-case-mismatch', [ using.location, using ], {},
583
- // eslint-disable-next-line @stylistic/js/max-len
583
+ // eslint-disable-next-line @stylistic/max-len
584
584
  'The imported filename differs on the filesystem; ensure that capitalization matches the actual file\'s name');
585
585
  }
586
586
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "6.0.14",
3
+ "version": "6.2.2",
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)",
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "download": "exit 0",
18
18
  "gen": "npm run rdpg",
19
- "rdpg": "node ./redepage/bin/redepage --compile lib/gen/CdlParser.js --copy-base-parser lib/parsers/CdlGrammar.g4 && node scripts/genGrammarChecksum.js",
19
+ "rdpg": "node ./redepage/bin/redepage --compile lib/gen/CdlParser.js --copy-base-parser lib/parsers/CdlGrammar.g4 && node ./scripts/createCdlKeywordList.js && node scripts/genGrammarChecksum.js",
20
20
  "xmakeAfterInstall": "npm run gen",
21
21
  "xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
22
22
  "test": "node scripts/xmakeTestDispatcher.js",