@sap/cds-compiler 5.7.2 → 5.8.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 (73) hide show
  1. package/CHANGELOG.md +62 -2
  2. package/bin/cdsse.js +13 -1
  3. package/doc/CHANGELOG_BETA.md +7 -0
  4. package/lib/api/options.js +2 -1
  5. package/lib/api/validate.js +9 -0
  6. package/lib/base/location.js +1 -1
  7. package/lib/base/message-registry.js +55 -20
  8. package/lib/base/messages.js +5 -2
  9. package/lib/base/model.js +8 -6
  10. package/lib/checks/assocOutsideService.js +40 -0
  11. package/lib/checks/featureFlags.js +4 -1
  12. package/lib/checks/types.js +7 -4
  13. package/lib/checks/validator.js +3 -0
  14. package/lib/compiler/assert-consistency.js +11 -5
  15. package/lib/compiler/checks.js +79 -17
  16. package/lib/compiler/define.js +60 -3
  17. package/lib/compiler/extend.js +1 -2
  18. package/lib/compiler/generate.js +1 -1
  19. package/lib/compiler/populate.js +17 -6
  20. package/lib/compiler/propagator.js +1 -1
  21. package/lib/compiler/resolve.js +181 -150
  22. package/lib/compiler/shared.js +276 -22
  23. package/lib/compiler/tweak-assocs.js +15 -4
  24. package/lib/compiler/xpr-rewrite.js +76 -50
  25. package/lib/edm/annotations/edmJson.js +1 -1
  26. package/lib/edm/annotations/genericTranslation.js +2 -2
  27. package/lib/edm/csn2edm.js +2 -2
  28. package/lib/edm/edmPreprocessor.js +15 -9
  29. package/lib/edm/edmUtils.js +12 -5
  30. package/lib/gen/CdlGrammar.checksum +1 -0
  31. package/lib/gen/CdlParser.js +2239 -2229
  32. package/lib/gen/Dictionary.json +55 -8
  33. package/lib/json/from-csn.js +37 -17
  34. package/lib/json/to-csn.js +4 -0
  35. package/lib/language/genericAntlrParser.js +7 -0
  36. package/lib/main.d.ts +5 -1
  37. package/lib/model/cloneCsn.js +1 -0
  38. package/lib/model/csnRefs.js +1 -0
  39. package/lib/model/csnUtils.js +0 -5
  40. package/lib/modelCompare/utils/filter.js +2 -2
  41. package/lib/optionProcessor.js +2 -0
  42. package/lib/parsers/AstBuildingParser.js +72 -34
  43. package/lib/parsers/CdlGrammar.g4 +20 -19
  44. package/lib/parsers/XprTree.js +206 -0
  45. package/lib/parsers/index.js +1 -1
  46. package/lib/render/toCdl.js +61 -89
  47. package/lib/render/toSql.js +59 -29
  48. package/lib/render/utils/standardDatabaseFunctions.js +252 -15
  49. package/lib/transform/addTenantFields.js +9 -3
  50. package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
  51. package/lib/transform/db/assocsToQueries/utils.js +10 -3
  52. package/lib/transform/db/expansion.js +3 -1
  53. package/lib/transform/db/flattening.js +7 -3
  54. package/lib/transform/db/killAnnotations.js +1 -0
  55. package/lib/transform/db/processSqlServices.js +70 -17
  56. package/lib/transform/draft/db.js +8 -3
  57. package/lib/transform/draft/odata.js +27 -4
  58. package/lib/transform/effective/main.js +37 -10
  59. package/lib/transform/effective/misc.js +4 -9
  60. package/lib/transform/effective/service.js +34 -0
  61. package/lib/transform/effective/types.js +28 -17
  62. package/lib/transform/forOdata.js +36 -10
  63. package/lib/transform/forRelationalDB.js +30 -18
  64. package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
  65. package/lib/transform/odata/createForeignKeys.js +120 -116
  66. package/lib/transform/odata/flattening.js +10 -8
  67. package/lib/transform/transformUtils.js +58 -25
  68. package/lib/transform/translateAssocsToJoins.js +10 -6
  69. package/lib/transform/universalCsn/coreComputed.js +5 -1
  70. package/package.json +1 -1
  71. package/share/messages/message-explanations.json +1 -0
  72. package/share/messages/rewrite-not-supported.md +5 -0
  73. package/share/messages/rewrite-undefined-key.md +94 -0
@@ -15,7 +15,7 @@ const { forEach } = require('../../utils/objectUtils');
15
15
  const { transformExpression } = require('./applyTransformations');
16
16
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
17
17
  const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
18
- const adaptAnnotationsRefs = require('../odata/adaptAnnotationRefs');
18
+ const { adaptAnnotationsRefs } = require('../odata/adaptAnnotationRefs');
19
19
 
20
20
  /**
21
21
  * Strip off leading $self from refs where applicable.
@@ -220,7 +220,8 @@ function getStructStepsFlattener( csn, options, messageFunctions, resolved, path
220
220
  // full path into target, uncomment this line and
221
221
  // comment/remove setProp in expansion.js
222
222
  // setProp(parent, '$structRef', parent.ref);
223
- [ parent.ref, refChanged ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, suspend, suspendPos, parent.$bparam);
223
+ const flattenParameters = false; // structured parameters remain structured
224
+ [ parent.ref, refChanged ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, suspend, suspendPos, parent.$bparam, flattenParameters);
224
225
  resolved.set(parent, { links, art, scope });
225
226
  // Explicitly set implicit alias for things that are now flattened - but only in columns
226
227
  // TODO: Can this be done elegantly during expand phase already?
@@ -649,8 +650,11 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
649
650
  delete element.default;
650
651
  }
651
652
 
652
- if (options.transformation === 'effective')
653
+ if (options.transformation === 'effective') {
653
654
  adaptAnnotationsRefs(fks, csnUtils, messageFunctions, eltPath);
655
+ const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && findAnnotationExpression(element, pn));
656
+ fks.forEach(fk => copyAnnotations(element, fk[1], false, {}, validAnnoNames));
657
+ }
654
658
  orderedElements.push(...fks);
655
659
  });
656
660
 
@@ -25,6 +25,7 @@ const requiredAnnos = {
25
25
  '@cds.redirection.target': true,
26
26
  '@Core.Computed': true,
27
27
  [sqlServiceAnnotation]: true,
28
+ '@cds.external': true, // for external ABAP SQL services and data products for now
28
29
  };
29
30
 
30
31
  /**
@@ -1,23 +1,26 @@
1
1
  'use strict';
2
2
 
3
- const { setProp } = require('../../base/model');
3
+ const { setProp, isBetaEnabled } = require('../../base/model');
4
4
 
5
5
  const sqlServiceAnnotation = '@protocol';
6
- // Problem: How can we clone a Symbol when sorting?
7
- // const sqlServiceEntities = Symbol.for('SQL Service enabled entities');
6
+
8
7
  /**
9
8
  * Find all entities in SQL services and mark them with an annotation and
10
9
  * remember them in a symbol property for easier processing in toSql-rendering.
11
10
  *
12
11
  * @param {CSN.Model} csn
12
+ * @param {CSN.Options} options
13
13
  * @returns {Function}
14
14
  */
15
- function processSqlServices(csn) {
15
+ function processSqlServices(csn, options) {
16
16
  setProp(csn, '$sqlServiceEntities', Object.create(null));
17
+ setProp(csn, '$dummyServiceEntities', Object.create(null));
17
18
  return function findAndMarkSqlServiceArtifacts(artifact, artifactName) {
18
- const sqlServiceName = isEntityInSqlService(artifact, artifactName, csn);
19
+ const { sqlServiceName, dummyServiceName } = isEntityInSqlService(artifact, artifactName, csn, options);
19
20
  if (sqlServiceName?.length > 0)
20
21
  setProp(artifact, '$sqlService', sqlServiceName);
22
+ if (dummyServiceName?.length > 0)
23
+ setProp(artifact, '$dummyService', dummyServiceName);
21
24
  };
22
25
  }
23
26
 
@@ -29,35 +32,85 @@ function processSqlServices(csn) {
29
32
  function isSqlService(artifact) {
30
33
  return artifact.kind === 'service' && artifact[sqlServiceAnnotation] === 'sql';
31
34
  }
35
+
32
36
  /**
37
+ * Checks if the given artifact is an external ABAP SQL service.
33
38
  *
34
- * @param {CSN.Artifact} artifact
35
- * @param {string} artifactName
36
- * @param {CSN.Model} csn
37
- * @returns {string|null}
39
+ * @param {object} artifact - The artifact to check.
40
+ * @param {CSN.Options} options
41
+ * @returns {boolean} - Returns true if the artifact is an external ABAP SQL service, otherwise false.
42
+ */
43
+ function isDummyService(artifact, options) {
44
+ return isBetaEnabled(options, 'sqlServiceDummies') && artifact.kind === 'service' && artifact['@cds.external'] && artifact[sqlServiceAnnotation] === 'dummy';
45
+ }
46
+
47
+ /**
48
+ * Determines if an artifact is part of a SQL service or an external ABAP SQL service.
49
+ *
50
+ * @param {object} artifact - The artifact to check.
51
+ * @param {string} artifactName - The name of the artifact.
52
+ * @param {object} csn - The CSN (Core Schema Notation) object containing definitions.
53
+ * @param {CSN.Options} options
54
+ * @returns {object} An object containing the names of the SQL service and external ABAP SQL service, if found.
38
55
  */
39
- function isEntityInSqlService(artifact, artifactName, csn) {
40
- if (artifact.kind !== 'entity' || !artifactName.includes('.'))
41
- return null;
56
+ function isEntityInSqlService(artifact, artifactName, csn, options) {
57
+ const result = { sqlServiceName: undefined, dummyServiceName: undefined };
58
+ if (artifact.kind !== 'entity' || !artifactName.includes('.') || artifact['@cds.persistence.skip'] === true)
59
+ return result;
42
60
 
43
61
  const nameParts = artifactName.split('.');
44
- for (let i = nameParts.length; i >= 0; i--) {
62
+ for (let i = nameParts.length - 1; i >= 0; i--) {
45
63
  const possibleServiceName = nameParts.slice(0, i).join('.');
46
64
  if (!csn.definitions[possibleServiceName])
47
65
  continue;
48
66
 
49
67
  const definition = csn.definitions[possibleServiceName];
50
68
  if (isSqlService(definition))
51
- return possibleServiceName;
69
+ result.sqlServiceName = possibleServiceName;
70
+
71
+ if (isDummyService(definition, options))
72
+ result.dummyServiceName = possibleServiceName;
52
73
 
53
74
  // We don't allow nested services/contexts - if we find one, we don't need to keep searching
54
75
  if (definition.kind === 'service' || definition.kind === 'context')
55
- return null;
76
+ return result;
56
77
  }
57
78
 
58
- return null;
79
+ return result;
80
+ }
81
+
82
+ /**
83
+ * Creates a dummy ABAP SQL service for the given artifact if it is marked as an external ABAP SQL service.
84
+ * The dummy service is a copy of the original artifact with certain properties removed.
85
+ * The dummy service is then added to the CSN (Core Schema Notation) definitions.
86
+ *
87
+ * @param {object} artifact - The artifact to create a dummy service for.
88
+ * @param {string} artifactName - The name of the artifact.
89
+ * @param {object} csn - The Core Schema Notation (CSN) object where the dummy service will be added.
90
+ * @param {object} messageFunctions
91
+ * @param {Function} messageFunctions.error
92
+ */
93
+ function createServiceDummy(artifact, artifactName, csn, { error }) {
94
+ if (!artifact.$dummyService)
95
+ return;
96
+
97
+ artifact['@cds.persistence.exists'] = true;
98
+ artifact.$ignore = true;
99
+
100
+ const dummy = { ...artifact };
101
+ delete dummy['@cds.persistence.exists'];
102
+ delete dummy.$ignore;
103
+
104
+ if (csn.definitions[`dummy.${artifactName}`])
105
+ error(null, [ 'definitions', artifactName ], { name: `dummy.${artifactName}` }, 'Generated artifact name $(NAME) conflicts with existing entity');
106
+ else
107
+ csn.definitions[`dummy.${artifactName}`] = dummy;
59
108
  }
60
109
 
61
110
  module.exports = {
62
- processSqlServices, isSqlService, sqlServiceAnnotation,
111
+ processSqlServices,
112
+ isSqlService,
113
+ isDummyService,
114
+ sqlServiceAnnotation,
115
+ createServiceDummy,
63
116
  };
@@ -4,7 +4,7 @@ const {
4
4
  hasAnnotationValue, getServiceNames, forEachDefinition,
5
5
  getResultingName, forEachMemberRecursively, applyAnnotationsFromExtensions,
6
6
  } = require('../../model/csnUtils');
7
- const { setProp, isDeprecatedEnabled } = require('../../base/model');
7
+ const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../../base/model');
8
8
  const { getTransformers } = require('../transformUtils');
9
9
  const { ModelError } = require('../../base/error');
10
10
  const { forEach } = require('../../utils/objectUtils');
@@ -25,8 +25,8 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
25
25
  const allServices = getServiceNames(csn);
26
26
  const draftRoots = new WeakMap();
27
27
  const {
28
- createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
29
- addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
28
+ createAndAddDraftAdminDataProjection, createScalarElement,
29
+ createAssociationElement, addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
30
30
  } = getTransformers(csn, options, messageFunctions, pathDelimiter);
31
31
  const { getCsnDef, isComposition } = csnUtils;
32
32
  const { error, warning } = messageFunctions;
@@ -231,6 +231,11 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
231
231
  };
232
232
  draftAdministrativeData.DraftAdministrativeData.notNull = true;
233
233
  addElement(draftAdministrativeData, draftsArtifact, artifactName);
234
+
235
+ if (isBetaEnabled(options, 'draftMessages')) {
236
+ const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
237
+ addElement(draftMessages, draftsArtifact, artifactName);
238
+ }
234
239
  // Note that we may need to do the HANA transformation steps for managed associations
235
240
  // (foreign key field generation, generatedFieldName, creating ON-condition) by hand,
236
241
  // because the corresponding transformation steps have already been done on all artifacts
@@ -7,6 +7,7 @@ const { forEach } = require('../../utils/objectUtils');
7
7
  const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
8
8
  const { getTransformers } = require('../transformUtils');
9
9
  const { makeMessageFunction } = require('../../base/messages');
10
+ const { isBetaEnabled } = require('../../base/model');
10
11
 
11
12
  /**
12
13
  * - Generate artificial draft fields if requested
@@ -35,10 +36,11 @@ function generateDrafts( csn, options, services, messageFunctions ) {
35
36
 
36
37
  const { error, info } = messageFunctions;
37
38
  const {
38
- createAndAddDraftAdminDataProjection, createScalarElement,
39
- createAssociationElement, createAssociationPathComparison,
40
- addElement, createAction, assignAction,
41
- resetAnnotation,
39
+ createAndAddDraftAdminDataProjection, isValidDraftAdminDataMessagesType,
40
+ createScalarElement, createAssociationElement,
41
+ createAssociationPathComparison, addElement,
42
+ createAction, assignAction,
43
+ resetAnnotation, setAnnotation,
42
44
  csnUtils,
43
45
  } = getTransformers(csn, options, messageFunctions);
44
46
  const {
@@ -56,6 +58,16 @@ function generateDrafts( csn, options, services, messageFunctions ) {
56
58
  // @ts-ignore
57
59
  const isExternalServiceMember = (_art, name) => externalServices.includes(getServiceName(name));
58
60
  const filterDict = Object.create(null);
61
+
62
+ // validate the 'DRAFT.DraftAdministrativeData_DraftMessage' type if already present in the model
63
+ if (isBetaEnabled(options, 'draftMessages')) {
64
+ const draftAdminDataMessagesType = csn.definitions['DRAFT.DraftAdministrativeData_DraftMessage'];
65
+ if (draftAdminDataMessagesType && !isValidDraftAdminDataMessagesType(draftAdminDataMessagesType)) {
66
+ error(null, [ 'definitions', 'DRAFT.DraftAdministrativeData_DraftMessage' ], { name: 'DRAFT.DraftAdministrativeData_DraftMessage' },
67
+ 'Generated type $(NAME) conflicts with existing artifact');
68
+ }
69
+ }
70
+
59
71
  forEachDefinition(csn, (def, defName) => {
60
72
  // Generate artificial draft fields for entities/views if requested, ignore if not part of a service
61
73
  if (def.kind === 'entity' && def['@odata.draft.enabled'] && isArtifactInSomeService(defName, services))
@@ -167,6 +179,17 @@ function generateDrafts( csn, options, services, messageFunctions ) {
167
179
  // ... on SiblingEntity.IsActiveEntity != IsActiveEntity ...
168
180
  siblingEntity.SiblingEntity.on = createAssociationPathComparison('SiblingEntity', 'IsActiveEntity', '!=', 'IsActiveEntity');
169
181
 
182
+ if (isBetaEnabled(options, 'draftMessages')) {
183
+ const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
184
+ addElement(draftMessages, artifact, artifactName);
185
+
186
+ if (!artifact['@Common.SideEffects#alwaysFetchMessages'] && artifact['@Common.SideEffects#alwaysFetchMessages'] !== null) {
187
+ setAnnotation(artifact, '@Common.SideEffects#alwaysFetchMessages.SourceEntities', ['']);
188
+ setAnnotation(artifact, '@Common.SideEffects#alwaysFetchMessages.TargetProperties', ['DraftMessages'] );
189
+ }
190
+ setAnnotation(artifact, '@Common.Messages', { '=': 'DraftMessages', ref: ['DraftMessages'] });
191
+ }
192
+
170
193
  // Iterate elements
171
194
  // TODO: Iterative vs recursive? What is more likely: Super deep nesting or cycles via malicious CSN?
172
195
  if (artifact.elements) {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- getUtils, isAspect, mergeTransformers, applyTransformations,
4
+ getUtils, mergeTransformers, applyTransformations,
5
5
  } = require('../../model/csnUtils');
6
6
  const transformUtils = require('../transformUtils');
7
7
  const effectiveFlattening = require('./flattening');
@@ -18,6 +18,7 @@ const annotations = require('./annotations');
18
18
  const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
19
19
  const { cloneFullCsn } = require('../../model/cloneCsn');
20
20
  const { featureFlags } = require('../featureFlags');
21
+ const getServiceFilterFunction = require('./service');
21
22
 
22
23
  /**
23
24
  * This is just a PoC for now!
@@ -36,14 +37,15 @@ function effectiveCsn( model, options, messageFunctions ) {
36
37
  delete csn.vocabularies; // must not be set for effective CSN
37
38
  messageFunctions.setModel(csn);
38
39
 
39
- const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, messageFunctions, '_');
40
+ const transformerUtils = transformUtils.getTransformers(csn, options, messageFunctions, '_');
41
+ const { expandStructsInExpression } = transformerUtils;
40
42
  const redoProjections = queries.projectionToSELECTAndAddColumns(csn);
41
43
 
42
44
  let csnUtils = getUtils(csn, 'init-all');
43
45
 
44
46
  // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
45
47
  const cleanup = validate.forRelationalDB(csn, {
46
- ...messageFunctions, csnUtils, ...csnUtils, csn, options, isAspect,
48
+ ...messageFunctions, csnUtils, ...csnUtils, csn, options,
47
49
  });
48
50
 
49
51
  if (csn.meta?.[featureFlags]?.$calculatedElements)
@@ -63,7 +65,7 @@ function effectiveCsn( model, options, messageFunctions ) {
63
65
  // Expand a structured thing in: keys, columns, order by, group by
64
66
  expansion.expandStructureReferences(csn, options, '_', messageFunctions, csnUtils);
65
67
 
66
- const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils, options);
68
+ const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils, transformerUtils, options);
67
69
 
68
70
  // Remove properties attached by validator - they do not "grow" as the model grows.
69
71
  cleanup();
@@ -72,30 +74,55 @@ function effectiveCsn( model, options, messageFunctions ) {
72
74
  effectiveFlattening.flattenRefs(csn, options, csnUtils, messageFunctions);
73
75
  flattening.flattenElements(csn, options, messageFunctions, '_', { skipDict: { actions: true } });
74
76
 
75
- resolveTypesInActionsAfterFlattening();
76
-
77
- // ensure getElement works on flattened struct_assoc columns
77
+ // ensure getElement works on flattened struct_assoc columns and getFinalTypeInfo refreshes the cache
78
78
  csnUtils = getUtils(csn, 'init-all');
79
79
 
80
+ resolveTypesInActionsAfterFlattening(csnUtils);
81
+
80
82
  processCalculatedElementsInEntities(csn, options);
81
83
  associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
82
84
  associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
83
85
  const transformers = mergeTransformers([
84
- options.addCdsPersistenceName ? misc.attachPersistenceName(csn, options, csnUtils) : {},
85
86
  options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {},
86
87
  misc.removeDefinitionsAndProperties(csn, options),
87
88
  options.deriveAnalyticalAnnotations ? annotations.sealAnnoMagic(csn) : {},
88
89
  ], null);
89
90
 
90
- applyTransformations(csn, transformers, [], { skipIgnore: false, processAnnotations: true });
91
+ if (options.addCdsPersistenceName) {
92
+ applyTransformations(csn, misc.attachPersistenceName(csn, options, csnUtils), [], {
93
+ skipIgnore: false, skipArtifact: artifact => artifact.kind !== 'entity', skipDict: { actions: true }, skipStandard: { items: true },
94
+ });
95
+ }
96
+
97
+ const artifactTransformers = [];
98
+
99
+ const collector = {
100
+ service: null,
101
+ containedArtifacts: Object.create(null),
102
+ };
103
+
104
+ if (options.effectiveServiceName)
105
+ artifactTransformers.push(getServiceFilterFunction(options.effectiveServiceName, collector));
106
+
107
+ applyTransformations(csn, transformers, artifactTransformers, { skipIgnore: false, processAnnotations: true });
91
108
 
92
109
  if (!options.resolveProjections)
93
110
  redoProjections.forEach(fn => fn());
94
111
 
95
-
96
112
  // Remove unapplied extensions/annotations
97
113
  delete csn.extensions;
98
114
 
115
+ if (options.effectiveServiceName) {
116
+ if (collector.service) {
117
+ csn.definitions = collector.containedArtifacts;
118
+ csn.definitions[options.effectiveServiceName] = collector.service;
119
+ }
120
+ else {
121
+ messageFunctions.warning(null, null, { name: options.effectiveServiceName, option: 'effectiveServiceName' }, 'Could not find a service matching requested effective service $(NAME) (option $(OPTION))');
122
+ csn.definitions = Object.create(null);
123
+ }
124
+ }
125
+
99
126
  messageFunctions.throwWithError();
100
127
 
101
128
  return csn;
@@ -19,20 +19,15 @@ function attachPersistenceName( csn, options, csnUtils ) {
19
19
  * @param {object} parent
20
20
  * @param {string} prop
21
21
  * @param {object} dict
22
- * @param {CSN.Path} path
23
22
  */
24
- function addToEachMember( parent, prop, dict, path ) {
25
- const artifact = csn.definitions[path[1]];
26
- if (artifact?.kind === 'entity') {
27
- for (const memberName in dict)
28
- addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), dict[memberName]);
29
- }
23
+ function addToEachMember( parent, prop, dict ) {
24
+ for (const memberName in dict)
25
+ addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), dict[memberName]);
30
26
  }
31
27
 
32
28
  return {
33
29
  kind: (parent, prop, kind, path) => {
34
- if (kind === 'entity')
35
- addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(path[1], options.sqlMapping, csn, options.sqlDialect), parent);
30
+ addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(path[1], options.sqlMapping, csn, options.sqlDialect), parent);
36
31
  },
37
32
  elements: addToEachMember,
38
33
  params: addToEachMember,
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const { forEach } = require('../../utils/objectUtils');
4
+
5
+ /**
6
+ * Creates a filter function for a specific service that processes and strips artifacts.
7
+ *
8
+ * @param {string} serviceName - The name of the service to filter for.
9
+ * @param {Object} collector - An object to collect the service and its contained artifacts.
10
+ * @param {Object} collector.service - The main service artifact.
11
+ * @param {Object} collector.containedArtifacts - The contained artifacts of the service.
12
+ * @returns {Function} A filter function that processes definitions and artifacts.
13
+ */
14
+ function getServiceFilterFunction(serviceName, collector) {
15
+ return function filterAndStripForService(definitions, artifactName, artifact) {
16
+ if (artifactName === serviceName && artifact.kind === 'service') {
17
+ collector.service = artifact;
18
+ }
19
+ else if (definitions[serviceName]?.kind === 'service' && artifactName.startsWith(`${serviceName }.`)) {
20
+ collector.containedArtifacts[artifactName] = artifact;
21
+ delete artifact.query;
22
+ delete artifact.projection;
23
+ if (artifact.elements) {
24
+ forEach(artifact.elements, (elementName, element) => {
25
+ if (element.target && !element.target.startsWith(`${serviceName }.`))
26
+ delete artifact.elements[elementName];
27
+ });
28
+ }
29
+ }
30
+ };
31
+ }
32
+
33
+
34
+ module.exports = getServiceFilterFunction;
@@ -17,32 +17,43 @@ const { cloneCsnDict, cloneCsnNonDict } = require('../../model/cloneCsn');
17
17
  * @todo What about annotations on the type?
18
18
  * @param {CSN.Model} csn will be transformed
19
19
  * @param {object} csnUtils
20
+ * @param {object} transformers
20
21
  * @param {CSN.Options} options
21
22
  * @returns {Function} Callback to resolve types in action returns later - as for them, $self would lead to unresolvable constructs at this point
22
23
  * so we can call this callback after flattening is done - then we can safely resolve their types.
23
24
  */
24
- function resolveTypes( csn, csnUtils, options ) {
25
- const { getFinalTypeInfo } = csnUtils;
25
+ function resolveTypes( csn, csnUtils, transformers, options ) {
26
+ const { flattenStructStepsInRef } = transformers;
26
27
  const later = [];
27
28
  applyTransformations(csn, {
28
29
  type: (parent) => {
29
- resolveType(parent);
30
+ resolveType(csnUtils, parent);
30
31
  },
31
32
  }, [ (definitions, artifactName, artifact) => {
32
33
  // In a non-flat model, replacing types with some $self inside causes issues for actions (bound or unbound)
33
34
  // we remember them and replace them after flattening.
34
- if (artifact.kind === 'action' || artifact.kind === 'function')
35
- later.push({ [artifactName]: artifact });
35
+ if (artifact.kind === 'action' || artifact.kind === 'function') // TODO: We still process them, this does not abort that? I am pretty sure at least...
36
+ later.push([ { [artifactName]: artifact }, [ 'definitions' ] ]);
36
37
  else if (artifact.actions)
37
- later.push(artifact.actions);
38
- } ], { skipStandard: { returns: true }, processAnnotations: true });
38
+ later.push([ artifact.actions, [ 'definitions', artifactName, 'actions' ] ]);
39
+ } ], { skipStandard: { returns: true }, processAnnotations: true, skipDict: { actions: true } });
40
+
41
+ // Type refs like E:struct.sub can not be resolved later, as struct will be flattened. So we rewrite it to E:struct_sub here.
42
+ later.forEach(([ action, actionPath ]) => {
43
+ applyTransformationsOnDictionary(action, {
44
+ type: (parent, prop, type, path) => {
45
+ if (type.ref)
46
+ type.ref = flattenStructStepsInRef(type.ref, path.concat('type'))[0];
47
+ },
48
+ }, {}, actionPath);
49
+ });
39
50
 
40
51
  // TODO: Directly push the .returns into the later so we have a more minimal looping
41
- return function resolveTypesInActions() {
42
- later.forEach((a) => {
43
- applyTransformationsOnDictionary(a, {
52
+ return function resolveTypesInActions(refreshedCsnUtils) {
53
+ later.forEach(([ action ]) => {
54
+ applyTransformationsOnDictionary(action, {
44
55
  type: (parent) => {
45
- resolveType(parent);
56
+ resolveType(refreshedCsnUtils, parent);
46
57
  },
47
58
  });
48
59
  });
@@ -59,9 +70,9 @@ function resolveTypes( csn, csnUtils, options ) {
59
70
  *
60
71
  * @param {object} parent Object with a .type property
61
72
  */
62
- function resolveType( parent ) {
73
+ function resolveType( csnUtils, parent ) {
63
74
  // TODO: I assume there can be cases with a type ref but still having .elements already? Subelement anno?
64
- const final = getFinalTypeInfo(parent.type);
75
+ const final = csnUtils.getFinalTypeInfo(parent.type);
65
76
  if (final?.elements) {
66
77
  // We do full clones so users don't get unexpected linkage later
67
78
  if (!parent.elements)
@@ -88,12 +99,12 @@ function resolveTypes( csn, csnUtils, options ) {
88
99
  const obj = stack.pop();
89
100
  if (obj.elements) {
90
101
  applyTransformationsOnDictionary(obj.elements, {
91
- type: getTypeTransformer(stack),
102
+ type: getTypeTransformer(csnUtils, stack),
92
103
  }, { skipDict: { actions: true } });
93
104
  }
94
105
  else if (obj.items) {
95
106
  applyTransformationsOnNonDictionary(obj, 'items', {
96
- type: getTypeTransformer(stack),
107
+ type: getTypeTransformer(csnUtils, stack),
97
108
  }, { skipDict: { actions: true } });
98
109
  }
99
110
  }
@@ -104,9 +115,9 @@ function resolveTypes( csn, csnUtils, options ) {
104
115
  * @param {object[]} stack
105
116
  * @returns {Function}
106
117
  */
107
- function getTypeTransformer( stack ) {
118
+ function getTypeTransformer( csnUtils, stack ) {
108
119
  return function typeTransformer( _parent, _prop, _type ) {
109
- const finalSub = getFinalTypeInfo(_type);
120
+ const finalSub = csnUtils.getFinalTypeInfo(_type);
110
121
 
111
122
  if (finalSub?.elements) {
112
123
  // We do full clones so users don't get unexpected linkage later
@@ -7,7 +7,6 @@ const { forEachDefinition,
7
7
  applyTransformationsOnNonDictionary,
8
8
  getArtifactDatabaseNameOf,
9
9
  getElementDatabaseNameOf,
10
- isAspect,
11
10
  getServiceNames,
12
11
  forEachGeneric,
13
12
  cardinality2str,
@@ -147,7 +146,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
147
146
  options.enrichAnnotations = true;
148
147
  const cleanup = validate.forOdata(csn, {
149
148
  message, error, warning, info, inspectRef, effectiveType, getFinalTypeInfo, artifactRef,
150
- options, csnUtils, services, isAspect, isExternalServiceMember, recurseElements,
149
+ options, csnUtils, services, isExternalServiceMember, recurseElements,
151
150
  checkMultipleAssignments, csn,
152
151
  });
153
152
 
@@ -208,7 +207,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
208
207
  skipArtifact: isExternalServiceMember,
209
208
  });
210
209
  flattening.replaceManagedAssocsAsKeys(allMgdAssocDefs, csnUtils);
211
-
210
+
212
211
  // replace structured with flat dictionaries that contain
213
212
  // rewritten path expressions
214
213
  forEachDefinition(csn, (def) => {
@@ -218,7 +217,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
218
217
  })
219
218
  if(def.$flatAnnotations) {
220
219
  Object.entries(def.$flatAnnotations).forEach(([an, av]) => {
221
- def[an] = av;
220
+ def[an] = av;
222
221
  })
223
222
  }
224
223
  if(def.actions) {
@@ -425,15 +424,42 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
425
424
  if (node['@mandatory'] && !Object.entries(node).some(([k,v]) => k === '@Common.FieldControl' || k.startsWith('@Common.FieldControl.') && v != null)) {
426
425
  setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
427
426
  }
428
- if (node['@assert.range'] != null &&
429
- (Array.isArray(node['@assert.range']) &&
430
- node['@assert.range'].length === 2)) {
431
- setAnnotation(node, '@Validation.Minimum', node['@assert.range'][0]);
432
- setAnnotation(node, '@Validation.Maximum', node['@assert.range'][1]);
433
- }
427
+ if (node['@assert.range'] != null)
428
+ setAssertRangeAnnotation(node);
434
429
  }
435
430
  }
436
431
 
432
+
433
+ function setAssertRangeAnnotation(node) {
434
+ const range = node['@assert.range'];
435
+ if (!Array.isArray(range) || range.length !== 2)
436
+ return; // TODO: Warning for wrong format?
437
+
438
+ const min = range[0];
439
+ const max = range[1];
440
+ const minVal = min?.val ?? min;
441
+ const maxVal = max?.val ?? max;
442
+
443
+ // CAP Node 8.5 introduced "exclusive" ranges using the annotation expression
444
+ // syntax. Hence, the compiler uses the same. It also introduced "infinity"
445
+ // via `@assert.range: [ _, _ ]`.
446
+ // For `_`, minVal is an object and this function returns false, which is ok,
447
+ // since we don't render the annotation for "infinite" values.
448
+ const shouldSet = (val) => (typeof val !== 'object' && val !== undefined && val !== null);
449
+
450
+ if (shouldSet(minVal)) {
451
+ setAnnotation(node, '@Validation.Minimum', minVal);
452
+ if (min['='] !== undefined)
453
+ setAnnotation(node, '@Validation.Minimum.@Validation.Exclusive', true);
454
+ }
455
+ if (shouldSet(maxVal)) {
456
+ setAnnotation(node, '@Validation.Maximum', maxVal);
457
+ if (max['='] !== undefined)
458
+ setAnnotation(node, '@Validation.Maximum.@Validation.Exclusive', true);
459
+ }
460
+
461
+ }
462
+
437
463
  // If an association was modelled as not null, like so:
438
464
  // <associationName>: Association to <target> not null;
439
465
  // a cardinality property is set to the association member