@sap/cds-compiler 4.5.0 → 4.6.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 (65) hide show
  1. package/CHANGELOG.md +50 -7
  2. package/bin/cdsc.js +13 -11
  3. package/doc/CHANGELOG_BETA.md +6 -0
  4. package/lib/api/main.js +256 -115
  5. package/lib/api/options.js +8 -0
  6. package/lib/base/message-registry.js +17 -4
  7. package/lib/base/messages.js +15 -3
  8. package/lib/base/model.js +1 -0
  9. package/lib/base/optionProcessorHelper.js +45 -176
  10. package/lib/checks/elements.js +32 -34
  11. package/lib/checks/enricher.js +39 -3
  12. package/lib/checks/validator.js +2 -3
  13. package/lib/compiler/assert-consistency.js +2 -1
  14. package/lib/compiler/builtins.js +20 -4
  15. package/lib/compiler/checks.js +30 -6
  16. package/lib/compiler/define.js +31 -9
  17. package/lib/compiler/populate.js +5 -1
  18. package/lib/compiler/resolve.js +26 -21
  19. package/lib/compiler/shared.js +19 -9
  20. package/lib/compiler/tweak-assocs.js +82 -107
  21. package/lib/compiler/utils.js +2 -1
  22. package/lib/edm/annotations/edmJson.js +23 -22
  23. package/lib/edm/annotations/genericTranslation.js +14 -4
  24. package/lib/edm/csn2edm.js +24 -10
  25. package/lib/edm/edmInboundChecks.js +1 -2
  26. package/lib/edm/edmPreprocessor.js +11 -9
  27. package/lib/edm/edmUtils.js +5 -2
  28. package/lib/gen/Dictionary.json +3 -1
  29. package/lib/gen/language.checksum +1 -1
  30. package/lib/gen/language.interp +4 -1
  31. package/lib/gen/language.tokens +1 -0
  32. package/lib/gen/languageParser.js +5253 -5214
  33. package/lib/json/to-csn.js +7 -1
  34. package/lib/language/antlrParser.js +19 -1
  35. package/lib/language/errorStrategy.js +21 -4
  36. package/lib/language/genericAntlrParser.js +9 -11
  37. package/lib/main.d.ts +28 -3
  38. package/lib/main.js +3 -0
  39. package/lib/model/csnRefs.js +4 -1
  40. package/lib/model/csnUtils.js +12 -7
  41. package/lib/optionProcessor.js +21 -19
  42. package/lib/render/manageConstraints.js +13 -29
  43. package/lib/render/toCdl.js +18 -15
  44. package/lib/render/toHdbcds.js +59 -28
  45. package/lib/render/toRename.js +6 -10
  46. package/lib/render/toSql.js +57 -82
  47. package/lib/render/utils/common.js +17 -0
  48. package/lib/transform/.eslintrc.json +9 -1
  49. package/lib/transform/addTenantFields.js +228 -0
  50. package/lib/transform/db/applyTransformations.js +27 -31
  51. package/lib/transform/db/assertUnique.js +4 -4
  52. package/lib/transform/db/cdsPersistence.js +1 -1
  53. package/lib/transform/db/flattening.js +68 -69
  54. package/lib/transform/db/temporal.js +1 -1
  55. package/lib/transform/draft/db.js +2 -16
  56. package/lib/transform/draft/odata.js +3 -3
  57. package/lib/transform/effective/associations.js +3 -5
  58. package/lib/transform/effective/main.js +6 -9
  59. package/lib/transform/forOdata.js +13 -9
  60. package/lib/transform/forRelationalDB.js +36 -17
  61. package/lib/transform/odata/toFinalBaseType.js +3 -3
  62. package/lib/transform/odata/typesExposure.js +14 -5
  63. package/lib/transform/transformUtils.js +47 -34
  64. package/lib/transform/translateAssocsToJoins.js +33 -8
  65. package/package.json +2 -2
@@ -17,14 +17,12 @@ const backlinks = require('../db/backlinks');
17
17
  * @param {CSN.Model} csn Input CSN - will not be transformed
18
18
  * @param {CSN.Options} options
19
19
  * @param {object} csnUtils
20
- * @param {object} messageFunctions
21
- * @param {Function} messageFunctions.error
22
- * @param {Function} messageFunctions.warning
20
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
23
21
  * @todo Remove .keys afterwards
24
22
  * @todo Add created foreign keys into .columns in case of a query?
25
23
  * @returns {CSN.Model}
26
24
  */
27
- function turnAssociationsIntoUnmanaged( csn, options, csnUtils, { error, warning } ) {
25
+ function turnAssociationsIntoUnmanaged( csn, options, csnUtils, messageFunctions ) {
28
26
  // TODO: Do we really need this?
29
27
  forEachDefinition(csn, (artifact, artifactName) => {
30
28
  setProp(artifact, '$path', [ 'definitions', artifactName ]);
@@ -33,7 +31,7 @@ function turnAssociationsIntoUnmanaged( csn, options, csnUtils, { error, warning
33
31
  }, [ 'definitions', artifactName ]);
34
32
  });
35
33
  // Flatten out the fks and create the corresponding elements
36
- flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', true, csnUtils, { allowArtifact: () => true, skipDict: {} });
34
+ flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_', true, csnUtils, { allowArtifact: () => true, skipDict: {} });
37
35
 
38
36
  // Add the foreign keys also to the columns if the association itself was explicitly selected
39
37
  // TODO: Extend the expansion to also expand managed to their foreign
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { isBetaEnabled } = require('../../base/model');
4
4
  const { CompilerAssertion } = require('../../base/error');
5
- const { makeMessageFunction } = require('../../base/messages');
6
5
  const { cloneCsnNonDict, getUtils, isAspect } = require('../../model/csnUtils');
7
6
  const transformUtils = require('../transformUtils');
8
7
  const flattening = require('../db/flattening');
@@ -26,21 +25,21 @@ const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities }
26
25
  * @private
27
26
  * @param {CSN.Model} model Input CSN - will not be transformed
28
27
  * @param {CSN.Options} options
28
+ * @param {object} messageFunctions
29
29
  * @returns {CSN.Model}
30
30
  */
31
- function effectiveCsn( model, options ) {
31
+ function effectiveCsn( model, options, messageFunctions ) {
32
32
  if (!isBetaEnabled(options, 'effectiveCsn'))
33
33
  throw new CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
34
34
 
35
35
  const csn = cloneCsnNonDict(model, options);
36
-
37
36
  delete csn.vocabularies; // must not be set for effective CSN
37
+ messageFunctions.setModel(csn);
38
38
 
39
- const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, '_');
39
+ const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, messageFunctions, '_');
40
40
  queries.projectionToSELECTAndAddColumns(csn);
41
41
 
42
42
  let csnUtils = getUtils(csn, 'init-all');
43
- const messageFunctions = makeMessageFunction(csn, options, 'for.effective');
44
43
 
45
44
  // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
46
45
  const cleanup = validate.forRelationalDB(csn, {
@@ -65,13 +64,11 @@ function effectiveCsn( model, options ) {
65
64
 
66
65
  const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils);
67
66
 
68
- csnUtils = getUtils(csn, 'init-all');
69
-
70
67
  // Remove properties attached by validator - they do not "grow" as the model grows.
71
68
  cleanup();
72
69
 
73
- flattening.flattenAllStructStepsInRefs(csn, options, new WeakMap(), '_');
74
- flattening.flattenElements(csn, options, '_', messageFunctions.error);
70
+ flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, new WeakMap(), '_');
71
+ flattening.flattenElements(csn, options, messageFunctions, '_');
75
72
 
76
73
  resolveTypesInActionsAfterFlattening();
77
74
 
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { makeMessageFunction } = require('../base/messages');
4
3
  const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
5
4
  const transformUtils = require('./transformUtils');
6
5
  const { cloneCsnNonDict,
@@ -24,6 +23,7 @@ const associations = require('./db/associations')
24
23
  const expansion = require('./db/expansion');
25
24
  const generateDrafts = require('./draft/odata');
26
25
 
26
+ const { addTenantFields } = require('./addTenantFields');
27
27
  const { addLocalizationViews } = require('./localized');
28
28
 
29
29
  // Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
@@ -68,19 +68,20 @@ const { addLocalizationViews } = require('./localized');
68
68
  // (Linter check candidate)
69
69
  module.exports = { transform4odataWithCsn };
70
70
 
71
- function transform4odataWithCsn(inputModel, options) {
71
+ function transform4odataWithCsn(inputModel, options, messageFunctions) {
72
72
  timetrace.start('OData transformation');
73
73
 
74
74
  // copy the model as we don't want to change the input model
75
75
  let csn = cloneCsnNonDict(inputModel, options);
76
+ messageFunctions.setModel(csn);
76
77
 
77
- const { message, error, warning, info, throwWithAnyError } = makeMessageFunction(csn, options, 'for.odata');
78
+ const { message, error, warning, info, throwWithAnyError } = messageFunctions;
78
79
  throwWithAnyError();
79
80
 
80
81
  // the new transformer works only with new CSN
81
82
  checkCSNVersion(csn, options);
82
83
 
83
- const transformers = transformUtils.getTransformers(csn, options, '_');
84
+ const transformers = transformUtils.getTransformers(csn, options, messageFunctions, '_');
84
85
  const {
85
86
  addDefaultTypeFacets, checkMultipleAssignments,
86
87
  recurseElements, setAnnotation, renameAnnotation,
@@ -117,6 +118,9 @@ function transform4odataWithCsn(inputModel, options) {
117
118
  if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
118
119
  enrichUniversalCsn(csn, options);
119
120
 
121
+ if (options.tenantAsColumn)
122
+ addTenantFields(csn, options);
123
+
120
124
  const keepLocalizedViews = isDeprecatedEnabled(options, '_createLocalizedViews');
121
125
 
122
126
  function acceptLocalizedView(_name, parent) {
@@ -178,15 +182,15 @@ function transform4odataWithCsn(inputModel, options) {
178
182
  const resolved = new WeakMap();
179
183
  // No refs with struct-steps exist anymore
180
184
 
181
- flattening.flattenAllStructStepsInRefs(csn, options, resolved, '_', { skipArtifact: isExternalServiceMember });
185
+ flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, resolved, '_', { skipArtifact: isExternalServiceMember });
182
186
  // No type references exist anymore
183
187
  // Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
184
188
  // OData doesn't resolve type chains after the first 'items'
185
- flattening.resolveTypeReferences(csn, options, resolved, '_',
189
+ flattening.resolveTypeReferences(csn, options, messageFunctions, resolved, '_',
186
190
  { skip: [ 'action', 'aspect', 'event', 'function', 'type'],
187
191
  skipArtifact: isExternalServiceMember, skipStandard: { items: true } });
188
192
  // No structured elements exists anymore
189
- flattening.flattenElements(csn, options, '_', error,
193
+ flattening.flattenElements(csn, options, messageFunctions, '_',
190
194
  { skip: ['action', 'aspect', 'event', 'function', 'type'],
191
195
  skipArtifact: isExternalServiceMember,
192
196
  skipStandard: { items: true }, // don't drill further into .items
@@ -195,7 +199,7 @@ function transform4odataWithCsn(inputModel, options) {
195
199
 
196
200
  // TODO: add the generated foreign keys to the columns when we are in a view
197
201
  // see db/views.js::addForeignKeysToColumns
198
- flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
202
+ flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
199
203
 
200
204
  // Allow using managed associations as steps in on-conditions to access their fks
201
205
  // To be done after handleManagedAssociationsAndCreateForeignKeys,
@@ -321,7 +325,7 @@ function transform4odataWithCsn(inputModel, options) {
321
325
  return;
322
326
  // Rename according to map above
323
327
  const renamePrefix = (name in renameMappings)
324
- ? name
328
+ ? name
325
329
  : renameShortCuts.find(p => name.startsWith(p + '.'));
326
330
  if(renamePrefix) {
327
331
  const mapping = renameMappings[renamePrefix];
@@ -6,13 +6,13 @@ const { cloneCsnNonDict,
6
6
  getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
7
7
  isAspect, walkCsnPath, isPersistedOnDatabase
8
8
  } = require('../model/csnUtils');
9
- const { makeMessageFunction } = require('../base/messages');
10
9
  const transformUtils = require('./transformUtils');
11
10
  const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
12
11
  const { csnRefs, pathId, traverseQuery, columnAlias} = require('../model/csnRefs');
13
12
  const { checkCSNVersion } = require('../json/csnVersion');
14
13
  const validate = require('../checks/validator');
15
14
  const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
15
+ const { addTenantFields } = require('../transform/addTenantFields');
16
16
  const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
17
17
  const { timetrace } = require('../utils/timetrace');
18
18
  const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
@@ -100,9 +100,9 @@ function forEachDefinition(csn, cb) {
100
100
  *
101
101
  * @param {CSN.Model} csn
102
102
  * @param {CSN.Options} options
103
- * @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
103
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`,
104
104
  */
105
- function transformForRelationalDBWithCsn(csn, options, moduleName) {
105
+ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
106
106
  // copy the model as we don't want to change the input model
107
107
  timetrace.start('HANA transformation');
108
108
 
@@ -111,6 +111,9 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
111
111
  csn = cloneCsnNonDict(csn, options);
112
112
  timetrace.stop('Clone CSN');
113
113
 
114
+ if (options.tenantAsColumn)
115
+ addTenantFields(csn, options);
116
+
114
117
  checkCSNVersion(csn, options);
115
118
 
116
119
  const pathDelimiter = (options.sqlMapping === 'hdbcds') ? '.' : '_';
@@ -120,12 +123,11 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
120
123
  /** @type {object} */
121
124
  let csnUtils;
122
125
  /** @type {object} */
123
- let messageFunctions;
124
- let message, error, warning, info; // message functions
126
+ let message, error, warning; // message functions
125
127
  /** @type {() => void} */
126
128
  let throwWithAnyError;
127
129
  // transformUtils
128
- let addDefaultTypeFacets,
130
+ let addDefaultTypeFacets,
129
131
  expandStructsInExpression,
130
132
  flattenStructuredElement,
131
133
  flattenStructStepsInRef;
@@ -180,7 +182,7 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
180
182
  // assoc2join eventually rewrites the table aliases
181
183
  temporal.getViewDecorator(csn, messageFunctions, csnUtils),
182
184
  // check unique constraints - further processing is done in rewriteUniqueConstraints
183
- assertUnique.prepare(csn, options, error, info)
185
+ assertUnique.prepare(csn, options, messageFunctions)
184
186
  ]);
185
187
 
186
188
  if(doA2J) {
@@ -204,15 +206,15 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
204
206
  if(doA2J) {
205
207
  const resolved = new WeakMap();
206
208
  // No refs with struct-steps exist anymore
207
- flattening.flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter);
209
+ flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, resolved, pathDelimiter);
208
210
  // No type references exist anymore
209
211
  // Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
210
- flattening.resolveTypeReferences(csn, options, resolved, pathDelimiter);
212
+ flattening.resolveTypeReferences(csn, options, messageFunctions, resolved, pathDelimiter);
211
213
  // No structured elements exists anymore
212
- flattening.flattenElements(csn, options, pathDelimiter, error);
214
+ flattening.flattenElements(csn, options, messageFunctions, pathDelimiter);
213
215
  } else {
214
216
  // For to.hdbcds with naming mode hdbcds we also need to resolve the types
215
- flattening.resolveTypeReferences(csn, options, new WeakMap(), pathDelimiter);
217
+ flattening.resolveTypeReferences(csn, options, messageFunctions, new WeakMap(), pathDelimiter);
216
218
  }
217
219
 
218
220
  // With flattening errors, it makes little sense to continue.
@@ -285,7 +287,7 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
285
287
  ]);
286
288
 
287
289
  // eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
288
- doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
290
+ doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
289
291
 
290
292
  doA2J && forEachDefinition(csn, flattenIndexes);
291
293
  // Managed associations get an on-condition - in views and entities
@@ -358,6 +360,9 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
358
360
  // Associations that point to things marked with @cds.persistence.skip are removed
359
361
  forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, messageFunctions, csnUtils));
360
362
 
363
+ // some errors can't be handled in the subsequent processing steps for e.g. HDBCDS
364
+ messageFunctions.throwWithError();
365
+
361
366
  // Apply view-specific transformations
362
367
  // (160) Projections now finally become views
363
368
  // Replace managed association in group/order by with foreign keys
@@ -449,15 +454,15 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
449
454
  /* ----------------------------------- Functions start here -----------------------------------------------*/
450
455
 
451
456
  function bindCsnReference(){
452
- messageFunctions = makeMessageFunction(csn, options, moduleName);
453
- ({ error, warning, info, message, throwWithAnyError } = messageFunctions);
457
+ messageFunctions.setModel(csn);
458
+ ({ error, warning, message, throwWithAnyError } = messageFunctions);
454
459
 
455
460
  ({ flattenStructuredElement,
456
461
  flattenStructStepsInRef,
457
462
  addDefaultTypeFacets,
458
463
  expandStructsInExpression,
459
464
  csnUtils
460
- } = transformUtils.getTransformers(csn, options, pathDelimiter));
465
+ } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter));
461
466
  }
462
467
 
463
468
  function bindCsnReferenceOnly(){
@@ -614,10 +619,10 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
614
619
  // (100 a) Ignore the property 'masked' itself (but not its effect on projections)
615
620
  if (member.masked)
616
621
  delete member.masked;
617
- // For HANA: Report an error on
622
+ // Report an error on
618
623
  // - view with parameters that has an element of type association/composition
619
624
  // - association that points to entity with parameters
620
- if (options.sqlDialect === 'hana' && member.target && csnUtils.isAssocOrComposition(member) && !isBetaEnabled(options, 'assocsWithParams')) {
625
+ if (member.target && csnUtils.isAssocOrComposition(member) && !isBetaEnabled(options, 'assocsWithParams')) {
621
626
  if (artifact.params) {
622
627
  // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
623
628
  // SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
@@ -862,6 +867,20 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
862
867
  checkTypeParamValue(node, 'srid', { max: Number.MAX_SAFE_INTEGER }, path);
863
868
  break;
864
869
  }
870
+ case 'cds.Vector': {
871
+ if (options.sqlDialect !== 'hana') {
872
+ error('ref-unsupported-type', path, {
873
+ '#': 'hana', type: node.type, value: 'hana',
874
+ othervalue: options.sqlDialect
875
+ });
876
+ }
877
+ else if (options.transformation === 'hdbcds') {
878
+ error('ref-unsupported-type', path, {
879
+ '#': 'hdbcds', type: node.type, value: options.sqlDialect
880
+ });
881
+ }
882
+ break;
883
+ }
865
884
  }
866
885
  }
867
886
 
@@ -7,7 +7,7 @@ const {
7
7
  } = require('../../model/csnUtils');
8
8
  const { isArtifactInSomeService, isArtifactInService } = require('./utils');
9
9
 
10
- function expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember) {
10
+ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
11
11
  const isV4 = options.odataVersion === 'v4';
12
12
  const special$self = !csn?.definitions?.$self && '$self';
13
13
  forEachDefinition(csn, (def, defName) => {
@@ -63,7 +63,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
63
63
  if (def.kind === 'type' && def.items && isArtifactInSomeService(defName, services)) {
64
64
  expandFirstLevelOfArrayed(def);
65
65
  }
66
- }, { skipArtifact: isExternalServiceMember });
66
+ });
67
67
 
68
68
  if(isBetaEnabled(options, 'odataTerms')) {
69
69
  forEachGeneric(csn, 'vocabularies', (def, defName) => {
@@ -75,7 +75,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
75
75
 
76
76
  expandToFinalBaseType(def, defName);
77
77
  expandToFinalBaseType(def.items, defName);
78
- }, [], { skipArtifact: isExternalServiceMember });
78
+ }, []);
79
79
  }
80
80
  // In case we have in the model something like:
81
81
  // type Foo: array of Bar; type Bar: { qux: Integer };
@@ -6,11 +6,11 @@
6
6
  * @module typesExposure
7
7
  */
8
8
 
9
- const { setProp } = require('../../base/model');
9
+ const { setProp, isBetaEnabled } = require('../../base/model');
10
10
  const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
11
- const { cloneCsnNonDict, isBuiltinType, forEachDefinition, forEachMember, forEachGeneric } = require('../../model/csnUtils');
12
- const { copyAnnotations, getNamespace } = require('../../model/csnUtils');
13
- const { isBetaEnabled } = require('../../base/model.js');
11
+ const { getNamespace, copyAnnotations, cloneCsnNonDict,
12
+ isBuiltinType, isAnnotationExpression,
13
+ forEachDefinition, forEachMember, forEachGeneric } = require('../../model/csnUtils');
14
14
  const { CompilerAssertion } = require('../../base/error');
15
15
 
16
16
  /**
@@ -219,7 +219,16 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
219
219
  }
220
220
  }
221
221
  });
222
- copyAnnotations(typeDef, newType);
222
+
223
+ // expression annos and their sub annotations are not propagated to type
224
+ let [ xprANames, nxprANames ] = Object.keys(typeDef).reduce((acc, pn) => {
225
+ if (pn[0] === '@')
226
+ acc[isAnnotationExpression(typeDef[pn]) ? 0 : 1].push(pn);
227
+ return acc;
228
+ }, [ [], [] ]);
229
+ nxprANames = nxprANames.filter(an => !xprANames.some(ean => an.startsWith(`${ean}.`)));
230
+ copyAnnotations(typeDef, newType, false, {}, nxprANames);
231
+
223
232
  // if the origin type had items, add items to exposed type
224
233
  if(typeDef.kind === 'type') {
225
234
  if(typeDef.items) {
@@ -4,7 +4,6 @@
4
4
  // different backends.
5
5
  // The sibling of model/transform/TransformUtil.js which works with compacted new CSN.
6
6
 
7
- const { makeMessageFunction } = require('../base/messages');
8
7
  const { setProp } = require('../base/model');
9
8
 
10
9
  const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand } = require('../model/csnUtils');
@@ -20,8 +19,8 @@ const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOpe
20
19
  // 'model' is compacted new style CSN
21
20
  // TODO: Error and warnings handling with compacted CSN? - currently just throw new ModelError for everything
22
21
  // TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true
23
- function getTransformers(model, options, pathDelimiter = '_') {
24
- const { message, error, warning, info } = makeMessageFunction(model, options);
22
+ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
23
+ const { message, error, warning, info } = msgFunctions;
25
24
  const csnUtils = getUtils(model);
26
25
  const {
27
26
  getCsnDef,
@@ -321,48 +320,62 @@ function getTransformers(model, options, pathDelimiter = '_') {
321
320
  * @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
322
321
  * @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
323
322
  * @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
323
+ * @param {bool} [refParentIsItems] relative ref has an items root (suspend flattening by caller)
324
324
  * @returns {string[]}
325
325
  */
326
- function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap()) {
326
+ function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), refParentIsItems=false) {
327
327
  // Refs of length 1 cannot contain steps - no need to check
328
328
  if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {
329
329
  return ref;
330
330
  }
331
331
 
332
- return flatten(ref, path);
332
+ const result = scope === '$self' ? [ref[0]] : [];
333
+ //let stack = []; // IDs of path steps not yet processed or part of a struct traversal
334
+ if(!links && !scope) { // calculate JIT if not supplied
335
+ const res = inspectRef(path);
336
+ links = res.links;
337
+ scope = res.scope;
338
+ }
339
+ if (scope === '$magic')
340
+ return ref;
333
341
 
334
- function flatten(ref, path) {
335
- let result = scope === '$self' ? [ref[0]] : [];
336
- //let stack = []; // IDs of path steps not yet processed or part of a struct traversal
337
- if(!links && !scope) { // calculate JIT if not supplied
338
- const res = inspectRef(path);
339
- links = res.links;
340
- scope = res.scope;
341
- }
342
- if (scope === '$magic')
343
- return ref;
344
- // Don't process a leading $self - it will a .art with .elements!
345
- const startIndex = scope === '$self' ? 1 : 0;
346
- let flattenStep = false;
347
- for(let i = startIndex; i < links.length; i++) {
348
- const value = links[i];
349
- if (flattenStep) {
350
- result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
351
- // if we had a filter or args, we had an assoc so this step is done
352
- // we then keep along the filter/args by updating the id of the current ref
353
- if(ref[i].id) {
354
- ref[i].id = result[result.length-1];
355
- result[result.length-1] = ref[i];
356
- }
357
- }
358
- else {
359
- result.push(ref[i]);
342
+ // Don't process a leading $self - it will a .art with .elements!
343
+ let i = scope === '$self' ? 1 : 0;
344
+
345
+ // read property from resolved path link
346
+ const art = (propName) =>
347
+ (links[i].art?.[propName] ||
348
+ effectiveType(links[i].art)[propName] ||
349
+ (resolvedLinkTypes.get(links[i])||{})[propName]);
350
+
351
+ let flattenStep = false;
352
+ let nextIsItems = !!art('items') || (refParentIsItems && i === 0);
353
+ for(; i < links.length; i++) {
354
+
355
+ if (flattenStep && !nextIsItems) {
356
+ result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
357
+ // if we had a filter or args, we had an assoc so this step is done
358
+ // we then keep along the filter/args by updating the id of the current ref
359
+ if(ref[i].id) {
360
+ ref[i].id = result[result.length-1];
361
+ result[result.length-1] = ref[i];
360
362
  }
361
- flattenStep = value.art && !value.art.kind && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements || (resolvedLinkTypes.get(value)||{}).elements);
363
+ // suspend flattening if the next path step has some 'items'
364
+ nextIsItems = !!art('items');
362
365
  }
363
-
364
- return result;
366
+ else {
367
+ result.push(ref[i]);
368
+ }
369
+ // revoke items suspension for next assoc step
370
+ if(nextIsItems && art('target'))
371
+ nextIsItems = false;
372
+
373
+ flattenStep = !links[i].art?.kind &&
374
+ !links[i].art?.SELECT &&
375
+ !links[i].art?.from &&
376
+ art('elements');
365
377
  }
378
+ return result;
366
379
  }
367
380
 
368
381
  /**
@@ -47,8 +47,6 @@ function translateAssocsToJoinsCSN(csn, options){
47
47
  deduplicateMessages( options.messages );
48
48
  }
49
49
 
50
- // If A2J reports error - end! Continuing with a broken CSN makes no sense
51
- makeMessageFunction(model, options).throwWithAnyError();
52
50
  // FIXME: Move this somewhere more appropriate
53
51
  const newCsn = compactModel(model, compileOptions);
54
52
  //require('fs').writeFileSync('./csnoutput_a2j.json', JSON.stringify(newCsn, null,2))
@@ -57,7 +55,7 @@ function translateAssocsToJoinsCSN(csn, options){
57
55
 
58
56
  function translateAssocsToJoins(model, inputOptions = {})
59
57
  {
60
- const { error, warning } = makeMessageFunction(model, inputOptions);
58
+ const { error, warning, throwWithError } = makeMessageFunction(model, inputOptions);
61
59
 
62
60
  const options = model.options || inputOptions;
63
61
 
@@ -70,6 +68,9 @@ function translateAssocsToJoins(model, inputOptions = {})
70
68
  forEachDefinition(model, prepareAssociations);
71
69
  forEachDefinition(model, transformQueries);
72
70
 
71
+ // If A2J reports error - end! Continuing with a broken model makes no sense
72
+ throwWithError();
73
+
73
74
  return model;
74
75
 
75
76
  function prepareAssociations(art)
@@ -316,7 +317,7 @@ function translateAssocsToJoins(model, inputOptions = {})
316
317
  // Paths without _navigation in ORDER BY are select item aliases, they must
317
318
  // be rendered verbatim
318
319
  let [head, ...tail] = pathNode.path;
319
- if((env.location === 'OrderBy' && !head._navigation)||
320
+ if((env.location === 'OrderBy' && !head._navigation)||
320
321
  env.location === 'UnionOuterOrderBy' && (!head._navigation || ['$self', '$projection'].includes(head.id)))
321
322
  return;
322
323
 
@@ -494,7 +495,7 @@ function translateAssocsToJoins(model, inputOptions = {})
494
495
  let srcTableAlias = constructTableAliasPathStep(assocSourceQA);
495
496
  let tgtTableAlias = constructTableAliasPathStep(assocQAT.$QA);
496
497
 
497
- node.on = createOnCondition(assoc, srcTableAlias, tgtTableAlias);
498
+ node.on = createOnCondition(assoc, srcTableAlias, tgtTableAlias, options.tenantAsColumn);
498
499
 
499
500
  if(assocQAT._filter)
500
501
  {
@@ -564,7 +565,7 @@ function translateAssocsToJoins(model, inputOptions = {})
564
565
  return xsnCard;
565
566
  }
566
567
  // produce the ON condition for a given association
567
- function createOnCondition(assoc, srcAlias, tgtAlias)
568
+ function createOnCondition(assoc, srcAlias, tgtAlias, compareTenants)
568
569
  {
569
570
  let prefixes = [ assoc.name.id ];
570
571
  /* This is no art and can be removed once ON cond for published
@@ -599,7 +600,7 @@ function translateAssocsToJoins(model, inputOptions = {})
599
600
  Put all src/tgt path siblings into the EQ term and create the proper path objects
600
601
  with the src/tgt table alias path steps in front.
601
602
  */
602
- let args = [];
603
+ let args = []; // TODO: tenant comparison?
603
604
  for(let i = 0; i < assoc.$flatSrcFKs.length; i++)
604
605
  {
605
606
  args.push({op: {val: '=' },
@@ -660,7 +661,25 @@ function translateAssocsToJoins(model, inputOptions = {})
660
661
  env.assocStack.push(assoc);
661
662
  const onCond = cloneOnCondition(assoc.on);
662
663
  env.assocStack.pop();
663
- return onCond;
664
+ return compareTenants ? addTenantComparison(assoc, onCond) : onCond;
665
+ }
666
+
667
+ // Add tenant comparison
668
+ function addTenantComparison(assoc, cond) {
669
+ // It is enough to test whether the target is tenant-dependent. If it is,
670
+ // the current query must also be (check in addTenantFields). If we allow
671
+ // assocs from tenant-independent entities to tenant-dependent ones, we
672
+ // also need to use the current query = `env.lead`.
673
+ if(annotationVal(assoc.target._artifact['@cds.tenant.independent']))
674
+ return cond;
675
+
676
+ const args = [ constructPathNode([ srcAlias ]), constructPathNode([ tgtAlias ]) ];
677
+ args[0].path.push({ id: 'tenant' }); // no need for _artifact
678
+ args[1].path.push({ id: 'tenant' }); // no need for _artifact
679
+ const comparison = { op: {val: '=' }, args };
680
+ if (!cond) // for managed assoc
681
+ return comparison;
682
+ return { op: { val: 'and' }, args: [ comparison, parenthesise(cond) ] };
664
683
  }
665
684
 
666
685
  // make foreign key absolute to its main entity
@@ -1047,6 +1066,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1047
1066
  return node;
1048
1067
  }
1049
1068
 
1069
+ // Remark CW: why boolean and not just truthy/falsy as usual? See annotationVal() below
1050
1070
  function isBooleanAnnotation(prop, val=true) {
1051
1071
  return prop && prop.val !== undefined && prop.val === val && prop.literal === 'boolean';
1052
1072
  }
@@ -1905,4 +1925,9 @@ function walkPath(node, env)
1905
1925
  return path;
1906
1926
  }
1907
1927
 
1928
+ function annotationVal( anno ) {
1929
+ // XSN TODO: also set `val:true` but no location for anno short form
1930
+ return anno && (anno.val === undefined || anno.val);
1931
+ }
1932
+
1908
1933
  module.exports = { translateAssocsToJoinsCSN };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "4.5.0",
3
+ "version": "4.6.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)",
@@ -27,7 +27,7 @@
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
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
+ "coverage:piper": "cross-env nyc mocha --reporter test/TestMochaReporter.js --reporter-options mochaFile=./coverage/TEST-results.xml --reporter-option maxDiffSize=0 --timeout 10000 test/ test3/ && nyc report --reporter=cobertura && nyc report --reporter=lcov",
31
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 .",
32
32
  "tslint": "tsc --pretty -p .",
33
33
  "updateVocs": "node scripts/odataAnnotations/generateDictMain.js && npm run generateAllRefs",