@sap/cds-compiler 4.6.2 → 4.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/cds_update_identifiers.js +6 -2
  3. package/bin/cdsc.js +1 -1
  4. package/doc/CHANGELOG_ARCHIVE.md +9 -9
  5. package/doc/CHANGELOG_BETA.md +6 -0
  6. package/lib/api/main.js +56 -9
  7. package/lib/api/options.js +6 -3
  8. package/lib/api/validate.js +20 -29
  9. package/lib/base/message-registry.js +27 -3
  10. package/lib/base/messages.js +8 -3
  11. package/lib/base/model.js +2 -0
  12. package/lib/checks/dbFeatureFlags.js +28 -0
  13. package/lib/checks/elements.js +81 -13
  14. package/lib/checks/enricher.js +3 -2
  15. package/lib/checks/validator.js +38 -4
  16. package/lib/compiler/assert-consistency.js +4 -4
  17. package/lib/compiler/checks.js +5 -4
  18. package/lib/compiler/define.js +2 -2
  19. package/lib/compiler/generate.js +2 -1
  20. package/lib/compiler/propagator.js +3 -11
  21. package/lib/compiler/shared.js +2 -1
  22. package/lib/compiler/tweak-assocs.js +43 -24
  23. package/lib/edm/annotations/edmJson.js +3 -0
  24. package/lib/edm/annotations/genericTranslation.js +156 -106
  25. package/lib/edm/annotations/preprocessAnnotations.js +11 -14
  26. package/lib/edm/csn2edm.js +27 -24
  27. package/lib/edm/edm.js +8 -8
  28. package/lib/edm/edmPreprocessor.js +135 -37
  29. package/lib/edm/edmUtils.js +20 -7
  30. package/lib/gen/Dictionary.json +1 -0
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +9 -11
  33. package/lib/gen/languageParser.js +5942 -5446
  34. package/lib/json/to-csn.js +7 -114
  35. package/lib/language/genericAntlrParser.js +106 -48
  36. package/lib/model/cloneCsn.js +203 -0
  37. package/lib/model/csnRefs.js +11 -3
  38. package/lib/model/csnUtils.js +42 -85
  39. package/lib/optionProcessor.js +2 -2
  40. package/lib/render/manageConstraints.js +1 -1
  41. package/lib/render/toCdl.js +133 -88
  42. package/lib/render/toHdbcds.js +1 -5
  43. package/lib/render/toSql.js +7 -9
  44. package/lib/render/utils/common.js +9 -16
  45. package/lib/transform/addTenantFields.js +277 -102
  46. package/lib/transform/db/applyTransformations.js +14 -9
  47. package/lib/transform/db/backlinks.js +2 -1
  48. package/lib/transform/db/constraints.js +60 -82
  49. package/lib/transform/db/expansion.js +6 -6
  50. package/lib/transform/db/featureFlags.js +5 -0
  51. package/lib/transform/db/flattening.js +4 -4
  52. package/lib/transform/db/killAnnotations.js +1 -0
  53. package/lib/transform/db/rewriteCalculatedElements.js +2 -2
  54. package/lib/transform/db/transformExists.js +12 -0
  55. package/lib/transform/db/views.js +5 -2
  56. package/lib/transform/draft/odata.js +7 -6
  57. package/lib/transform/effective/associations.js +2 -1
  58. package/lib/transform/effective/main.js +3 -2
  59. package/lib/transform/effective/types.js +6 -3
  60. package/lib/transform/forOdata.js +39 -24
  61. package/lib/transform/forRelationalDB.js +34 -27
  62. package/lib/transform/localized.js +29 -9
  63. package/lib/transform/odata/flattening.js +419 -0
  64. package/lib/transform/odata/toFinalBaseType.js +95 -15
  65. package/lib/transform/odata/typesExposure.js +9 -7
  66. package/lib/transform/transformUtils.js +7 -6
  67. package/lib/transform/translateAssocsToJoins.js +3 -3
  68. package/lib/utils/objectUtils.js +14 -0
  69. package/package.json +1 -1
@@ -1,12 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- cloneCsnNonDict, applyTransformationsOnNonDictionary, isAssociationOperand, isDollarSelfOrProjectionOperand,
4
+ applyTransformationsOnNonDictionary, isAssociationOperand, isDollarSelfOrProjectionOperand,
5
5
  } = require('../../model/csnUtils');
6
6
 
7
7
  const { setProp } = require('../../base/model');
8
8
  const { ModelError } = require('../../base/error');
9
9
  const { forEach } = require('../../utils/objectUtils');
10
+ const { cloneCsnNonDict } = require('../../model/cloneCsn');
10
11
 
11
12
  /**
12
13
  * Get a function that transforms $self backlinks
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { forEachDefinition } = require('../../base/model');
4
4
  const { applyTransformations, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
5
- const { csnRefs } = require('../../model/csnRefs');
6
5
  const { forEach, forEachKey } = require('../../utils/objectUtils');
7
6
  const { CompilerAssertion } = require('../../base/error');
8
7
 
@@ -16,6 +15,7 @@ const ASSOCIATION = 'cds.Association';
16
15
  * @param {CSN.Options} options are used to modify the validate / enforced flag on the constraints
17
16
  */
18
17
  function createReferentialConstraints( csn, options ) {
18
+ const isTenant = options.tenantDiscriminator === true;
19
19
  let validated = true;
20
20
  let enforced = true;
21
21
  if (options.integrityNotValidated)
@@ -24,14 +24,13 @@ function createReferentialConstraints( csn, options ) {
24
24
  if (options.integrityNotEnforced)
25
25
  enforced = false;
26
26
 
27
- const { inspectRef } = csnRefs(csn);
28
27
  // prepare the functions with the compositions and associations across all entities first
29
28
  // and execute it afterwards.
30
29
  // compositions must be processed first, as the <up_> links for them must result in `ON DELETE CASCADE`
31
30
  const compositions = [];
32
31
  const associations = [];
33
32
  applyTransformations(csn, {
34
- elements: (parent, prop, elements, path) => {
33
+ elements: (art, prop, elements, path) => {
35
34
  // Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
36
35
  for (const elementName in elements) {
37
36
  const element = elements[elementName];
@@ -39,7 +38,7 @@ function createReferentialConstraints( csn, options ) {
39
38
  if (element.type === COMPOSITION && element.$selfOnCondition) {
40
39
  compositions.push({
41
40
  fn: () => {
42
- foreignKeyConstraintForUpLinkOfComposition(element, parent, ePath);
41
+ foreignKeyConstraintForUpLinkOfComposition(element, art, ePath);
43
42
  },
44
43
  });
45
44
  }
@@ -52,32 +51,39 @@ function createReferentialConstraints( csn, options ) {
52
51
  if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
53
52
  associations.push({
54
53
  fn: () => {
55
- foreignKeyConstraintForAssociation(element, ePath );
54
+ // parent entity of assoc becomes dependent table
55
+ foreignKeyConstraintForAssociation( element, art, ePath );
56
56
  },
57
57
  });
58
58
  }
59
59
  // for `texts` compositions, we may generate foreign key constraints even w/o `up_`
60
60
  else if (elementName === 'texts' && element.target === `${path[path.length - 1]}.texts`) {
61
61
  const { on } = element;
62
- const target = csn.definitions[element.target];
62
+ const textsEntity = csn.definitions[element.target];
63
63
  // `texts` entities have a key named "locale"
64
- const targetSideHasLocaleKey = target.elements.locale?.key;
65
- if (targetSideHasLocaleKey && !skipConstraintGeneration(parent, target, { /* there is no assoc */ })) {
66
- const sourceElements = Array.from(elementsOfSourceSide(on, elements));
67
- const targetElements = Array.from(elementsOfTargetSide(on, target.elements));
64
+ const targetSideHasLocaleKey = textsEntity.elements.locale?.key;
65
+ if (targetSideHasLocaleKey && !skipConstraintGeneration(art, textsEntity, { /* there is no assoc */ })) {
66
+ // Note: a `texts` composition in `Foo` will co-modify `Foo.texts` and create the constraints over there
67
+ const { dependentKeys, parentKeys } = extractKeys(on, textsEntity.elements, elements);
68
+
69
+ const keysInParent = Object.values(elements).filter(e => e.key);
70
+ if (keysInParent.length !== dependentKeys.length)
71
+ continue;
72
+
68
73
  // `texts` entities have all the keys the original entity has
69
- const allElementsAreKeysAndHaveTheSameName = targetElements.length &&
70
- targetElements.every(
71
- ([ targetKey, e ]) => e.key &&
72
- sourceElements.some(([ sourceKey, sourceElement ]) => sourceElement.key && targetKey === sourceKey )
73
- );
74
+ const allElementsAreKeysAndHaveTheSameName = parentKeys.length &&
75
+ parentKeys
76
+ .every(
77
+ ([ targetKey, e ]) => e.key &&
78
+ dependentKeys.some(([ sourceKey, sourceElement ]) => sourceElement.key && targetKey === sourceKey )
79
+ );
74
80
  if (allElementsAreKeysAndHaveTheSameName)
75
- attachConstraintsToDependentKeys(targetElements, sourceElements, path[path.length - 1], 'texts', { texts: true });
81
+ attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 1], 'texts', { texts: true });
76
82
  }
77
83
  }
78
84
  }
79
85
  },
80
- }, [], { skipIgnore: false, skipArtifact: a => a.query || a.kind !== 'entity' });
86
+ }, [], { skipIgnore: false, skipArtifact: a => !!(a.query || a.kind !== 'entity' ) });
81
87
 
82
88
  // create constraints on foreign keys
83
89
  // always process unmanaged first, up_ links must be flagged
@@ -98,7 +104,7 @@ function createReferentialConstraints( csn, options ) {
98
104
  * @param {CSN.Path} path
99
105
  */
100
106
  function foreignKeyConstraintForUpLinkOfComposition( composition, parent, path ) {
101
- const dependent = csn.definitions[path[1]];
107
+ const dependent = csn.definitions[composition.target];
102
108
  if (skipConstraintGeneration(parent, dependent, composition))
103
109
  return;
104
110
 
@@ -107,7 +113,7 @@ function createReferentialConstraints( csn, options ) {
107
113
  const upLinkName = composition.$selfOnCondition.up_[0];
108
114
  const up_ = csn.definitions[composition.target].elements[upLinkName];
109
115
  if (up_.keys && isToOne(up_)) // no constraint for unmanaged / to-many up_ links
110
- foreignKeyConstraintForAssociation(up_, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1]);
116
+ foreignKeyConstraintForAssociation(up_, dependent, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1] );
111
117
  }
112
118
  else if (!onCondition && composition.keys.length > 0) {
113
119
  throw new CompilerAssertion('Please debug me, an on-condition was expected here, but only found keys');
@@ -120,28 +126,53 @@ function createReferentialConstraints( csn, options ) {
120
126
  * If the association is used as an <up_> link in a compositions on-condition, the ON DELETE rule will be `CASCADE`
121
127
  *
122
128
  * @param {CSN.Association} association for that a constraint should be generated
129
+ * @param {CSN.Entity} dependent the entity for which a constraint will be generated
123
130
  * @param {CSN.Path} path
124
131
  * @param {CSN.PathSegment} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
125
132
  */
126
- function foreignKeyConstraintForAssociation( association, path, upLinkFor = null ) {
133
+ function foreignKeyConstraintForAssociation( association, dependent, path, upLinkFor = null ) {
127
134
  const parent = csn.definitions[association.target];
128
- const dependent = csn.definitions[path[1]];
129
135
  if (skipConstraintGeneration(parent, dependent, association))
130
136
  return;
131
- const { elements } = csn.definitions[path[1]];
132
- const onCondition = association.on;
133
- if (onCondition && hasConstraintCompliantOnCondition(association, elements, path)) {
137
+ const { elements } = dependent;
138
+ if (association.keys) {
134
139
  // 1. cds.Association has constraint compliant on-condition
135
140
  // mark each dependent key - in the entity containing the association - referenced in the on-condition
136
- const dependentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
137
- const parentKeys = Array.from(elementsOfTargetSide(onCondition, parent.elements));
141
+ const parentKeys = [];
142
+ const dependentKeys = [];
143
+ association.keys.forEach( (k) => {
144
+ dependentKeys.push( [ k.$generatedFieldName, elements[k.$generatedFieldName] ] );
145
+ const parentKey = parent.elements[k.ref[0]];
146
+ if (parentKey.key) // only keys are valid references in foreign key constraints
147
+ parentKeys.push( [ k.ref[0], parent.elements[k.ref[0]] ] );
148
+ });
149
+ if (isTenant && elements.tenant && parent.elements.tenant) { // `tenant` is not part of on
150
+ dependentKeys.push([ 'tenant', elements.tenant ]);
151
+ parentKeys.push([ 'tenant', parent.elements.tenant ]);
152
+ }
153
+ const allKeysCovered = parentKeys.length === Object.values(parent.elements).filter(e => e.key).length;
138
154
  // sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
139
- if (dependentKeys.length === parentKeys.length)
155
+ if (allKeysCovered && dependentKeys.length === parentKeys.length)
140
156
  attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path[path.length - 1], upLinkFor);
141
157
  }
142
- else if (!onCondition && association.keys.length > 0) {
143
- throw new CompilerAssertion('Please debug me, an on-condition was expected here, but only found keys');
158
+ }
159
+
160
+ /**
161
+ * Extracts dependent keys and their parent keys based on an 'on' condition.
162
+ *
163
+ * @param {CSN.OnCondition} on - on condition from which dependent keys and their parent keys are extracted.
164
+ * @param {CSN.Elements} elements - The elements of the dependent entity, containing the foreign keys.
165
+ * @param {CSN.Elements} parentElements - The elements of the parent entity, containing the referenced parent keys.
166
+ * @returns {object} An object containing dependent keys and the parent keys which they reference.
167
+ */
168
+ function extractKeys( on, elements, parentElements ) {
169
+ const dependentKeys = Array.from(elementsOfSourceSide(on, elements));
170
+ const parentKeys = Array.from(elementsOfTargetSide(on, parentElements));
171
+ if (isTenant && elements.tenant && parentElements.tenant) { // `tenant` is not part of on
172
+ dependentKeys.push([ 'tenant', elements.tenant ]);
173
+ parentKeys.push([ 'tenant', parentElements.tenant ]);
144
174
  }
175
+ return { dependentKeys, parentKeys };
145
176
  }
146
177
 
147
178
  /**
@@ -181,59 +212,6 @@ function createReferentialConstraints( csn, options ) {
181
212
  }
182
213
  }
183
214
 
184
- /**
185
- * Constraints can only be generated if the full primary key of the target is referenced by the foreign key in an on-condition.
186
- * 1. on-condition only contains AND as logical operator
187
- * 2. each part of the on-condition must either:
188
- * - reference a valid field in the dependent entity:
189
- * a) for cds.Composition this is in the target entity
190
- * b) for cds.Association this is the entity, where the association is defined
191
- * - reference a key element in the parent entity:
192
- * a) for cds.Composition this is the entity, where the composition itself is defined
193
- * b) for cds.Association this is the target entity
194
- * 3. parent keys must be the full primary key tuple
195
- *
196
- * @param {CSN.Association} element
197
- * @param {CSN.Elements} siblingElements
198
- * @param {CSN.Path} path the path to the element
199
- * @returns {boolean} indicating whether the association / composition is a constraint candidate
200
- */
201
- function hasConstraintCompliantOnCondition( element, siblingElements, path ) {
202
- const onCondition = element.on;
203
- const allowedTokens = [ '=', 'and', '(', ')' ];
204
- // on condition must only contain logical operator 'AND'
205
- if (onCondition.some(step => typeof step === 'string' && !allowedTokens.includes(step)))
206
- return false;
207
-
208
- // on-condition like ... TemplateAuthGroupAssignments.isTemplate = true; is not allowed
209
- if (onCondition.some(step => typeof step === 'object' && Object.prototype.hasOwnProperty.call(step, 'val')))
210
- return false;
211
-
212
- // no magic vars in on-condition
213
- // e.g. for localized: ... and localized.locale = $user.locale; -> not a valid on-condition
214
- if (onCondition.some((step, index) => typeof step === 'object' && inspectRef(path.concat([ 'on', index ])).scope === '$magic'))
215
- return false;
216
-
217
- // for cds.Associations the parent keys are in the associations target entity
218
- // for cds.Composition the parent keys are in the entity, where the composition is defined
219
- const parentElements = csn.definitions[element.target].elements;
220
- const parentKeys = elementsOfTargetSide(onCondition, parentElements);
221
-
222
- const referencesNonPrimaryKeyField = Array.from(parentKeys.values()).some(parentKey => !parentKey.key);
223
- if (referencesNonPrimaryKeyField)
224
- return false;
225
-
226
- // returns true if the parentKeys found in the on-condition are covering the full primary key tuple in the parent entity
227
- return Array.from(parentKeys.entries())
228
- // check if primary key found in on-condition is present in association target / composition source
229
- .filter(([ keyName, pk ]) => pk.key && parentElements[keyName].key).length ===
230
- Object.keys(parentElements)
231
- // compare that with the length of the primary key tuple found in association target / composition source
232
- .filter(key => parentElements[key].key &&
233
- parentElements[key].type !== ASSOCIATION &&
234
- parentElements[key].type !== COMPOSITION)
235
- .length;
236
- }
237
215
  /**
238
216
  * Skip referential constraint if the parent table (association target, or artifact where composition is defined)
239
217
  * of the relation is:
@@ -11,6 +11,7 @@ const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
11
11
  const { setProp } = require('../../base/model');
12
12
  const { forEach } = require('../../utils/objectUtils');
13
13
  const { killNonrequiredAnno } = require('./killAnnotations');
14
+ const { featureFlags } = require('./featureFlags');
14
15
 
15
16
  /**
16
17
  * For keys, columns, groupBy and orderBy, expand structured things.
@@ -26,7 +27,10 @@ const { killNonrequiredAnno } = require('./killAnnotations');
26
27
  function expandStructureReferences( csn, options, pathDelimiter, messageFunctions, csnUtils, iterateOptions = {} ) {
27
28
  const { error, info, throwWithAnyError } = messageFunctions;
28
29
 
29
- rewriteExpandInline();
30
+ if (options.transformation === 'odata' || options.transformation === 'effective' || csn.meta?.[featureFlags]?.$expandInline)
31
+ rewriteExpandInline();
32
+
33
+ throwWithAnyError();
30
34
 
31
35
  const transformers = {
32
36
  keys: (parent, name, keys, path) => {
@@ -189,10 +193,6 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
189
193
  parent[name].splice(kill[i]);
190
194
  }
191
195
 
192
- // We would be broken if we continue with assoc usage to now skipped
193
- throwWithAnyError();
194
-
195
-
196
196
  for (const {
197
197
  parent, target, path,
198
198
  } of publishing) {
@@ -615,7 +615,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
615
615
  const obj = { ...root, ref: currentRef };
616
616
  if (withAlias) {
617
617
  // TODO: Remove this line in case foreign key annotations should
618
- // be adressed via full path into target instead of using alias
618
+ // be addressed via full path into target instead of using alias
619
619
  // names. See flattening.js::flattenAllStructStepsInRefs()
620
620
  // apply transformations on `ref` counterpart comment.
621
621
  setProp(obj, '$structRef', currentAlias);
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const featureFlags = Symbol.for('Feature flags forRelationalDb');
4
+
5
+ module.exports = { featureFlags };
@@ -2,15 +2,15 @@
2
2
 
3
3
  const {
4
4
  applyTransformations, applyTransformationsOnNonDictionary,
5
- isBuiltinType, cloneCsnNonDict,
6
- copyAnnotations, implicitAs, isDeepEqual,
5
+ isBuiltinType, cardinality2str,
6
+ copyAnnotations, implicitAs, isDeepEqual, findAnnotationExpression,
7
7
  } = require('../../model/csnUtils');
8
8
  const transformUtils = require('../transformUtils');
9
9
  const { csnRefs } = require('../../model/csnRefs');
10
10
  const { setProp, isBetaEnabled } = require('../../base/model');
11
11
  const { forEach } = require('../../utils/objectUtils');
12
- const { cardinality2str, isAnnotationExpression } = require('../../model/csnUtils');
13
12
  const { transformExpression } = require('./applyTransformations');
13
+ const { cloneCsnNonDict } = require('../../model/cloneCsn');
14
14
  /**
15
15
  * Strip off leading $self from refs where applicable
16
16
  *
@@ -819,7 +819,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
819
819
  if (options.transformation !== 'effective')
820
820
  fk[1]['@odata.foreignKey4'] = prefix;
821
821
  if (options.transformation === 'odata' || options.transformation === 'effective') {
822
- const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !isAnnotationExpression(element[pn]));
822
+ const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !findAnnotationExpression(element, pn));
823
823
  copyAnnotations(element, fk[1], true, {}, validAnnoNames);
824
824
  }
825
825
  // propagate not null to final foreign key
@@ -5,6 +5,7 @@ const requiredAnnos = {
5
5
  '@cds.persistence.exists': true,
6
6
  '@cds.persistence.table': true,
7
7
  '@cds.persistence.journal': true, // Build checks on it
8
+ '@cds.tenant.independent': true,
8
9
  '@sql.append': true,
9
10
  '@sql.prepend': true,
10
11
  '@sql.replace': true, // We do a check on this, no real function
@@ -7,10 +7,10 @@ const {
7
7
  applyTransformationsOnNonDictionary,
8
8
  applyTransformationsOnDictionary,
9
9
  implicitAs,
10
- cloneCsnNonDict,
11
10
  } = require('../../model/csnUtils');
12
11
  const { getBranches } = require('./flattening');
13
12
  const { getColumnMap } = require('./views');
13
+ const { cloneCsnNonDict } = require('../../model/cloneCsn');
14
14
 
15
15
  const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
16
16
 
@@ -305,7 +305,7 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
305
305
  * @returns {object|Array}
306
306
  */
307
307
  function replaceInRef( oldValue, newValue, isInXpr, refBase, linksBase ) {
308
- const clone = { value: cloneCsnNonDict({ value: newValue }, cloneCsnOptions).value };
308
+ const clone = { value: cloneCsnNonDict(newValue, cloneCsnOptions) };
309
309
  if (oldValue.stored)
310
310
  clone.value.stored = oldValue.stored;
311
311
 
@@ -308,9 +308,21 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
308
308
 
309
309
  const target = subselect.SELECT.from.as; // use subquery alias as target - prevent shadowing
310
310
  const extension = root.keys ? translateManagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) : translateUnmanagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current);
311
+
312
+ if (options.tenantDiscriminator) {
313
+ const targetEntity = csn.definitions[root.target];
314
+ if (!targetEntity['@cds.tenant.independent']) {
315
+ subselect.SELECT.where.push(
316
+ { ref: [ target, 'tenant' ] }, '=', { ref: [ base, 'tenant' ] }, 'AND'
317
+ );
318
+ }
319
+ }
320
+
321
+ // TODO: parentheses around sub expressions are to be represented by inner `xpr`
311
322
  if (extension.length > 3)
312
323
  subselect.SELECT.where.push('('); // add braces around the on-condition part to ensure precedence is kept
313
324
 
325
+ // TODO: add tenant comparison here ?
314
326
  subselect.SELECT.where.push(...extension);
315
327
 
316
328
  if (extension.length > 3)
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- getUtils, cloneCsnNonDict, applyTransformationsOnNonDictionary, forEachDefinition,
4
+ getUtils, applyTransformationsOnNonDictionary, forEachDefinition,
5
5
  } = require('../../model/csnUtils');
6
6
  const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
7
7
  const { ModelError } = require('../../base/error');
8
8
  const { setProp } = require('../../base/model');
9
+ const { cloneCsnNonDict } = require('../../model/cloneCsn');
9
10
 
10
11
  /**
11
12
  * If a mixin association is published, return the mixin association.
@@ -328,6 +329,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
328
329
  */
329
330
  // eslint-disable-next-line complexity
330
331
  function transformViewOrEntity( query, artifact, artName, path ) {
332
+ const ignoreAssociations = options.sqlDialect === 'hana' && options.withHanaAssociations === false;
331
333
  csnUtils.initDefinition(artifact);
332
334
  const { elements } = queryOrMain(query, artifact);
333
335
  // We use the elements from the leading query/main artifact - adapt the path
@@ -352,6 +354,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
352
354
 
353
355
  for (const elemName in elements) {
354
356
  const elem = elements[elemName];
357
+
355
358
  if (isSelect) {
356
359
  if (!columnMap[elemName])
357
360
  addProjectionOrStarElement(query, isProjection, isSelectStar, $combined, columnMap, elemName);
@@ -380,7 +383,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
380
383
 
381
384
  if (isSelect) {
382
385
  // Build new columns from the column map - bring elements and columns back in sync basically
383
- query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem].$ignore).map(key => stripLeadingSelf(columnMap[key]));
386
+ query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem].$ignore && !(elements[elem].target && ignoreAssociations)).map(key => stripLeadingSelf(columnMap[key]));
384
387
  // If following an association, explicitly set the implicit alias
385
388
  // due to an issue with HANA - this seems to only have an effect on ref files with hdbcds-hdbcds, so only run then
386
389
  const columnProcessors = [];
@@ -21,13 +21,16 @@ const { makeMessageFunction } = require('../../base/messages');
21
21
  *
22
22
  * @param {CSN.Model} csn
23
23
  * @param {CSN.Options} options
24
- * @param {Array} [services] Will be calculated JIT if not provided
24
+ * @param {string[]|undefined} services Will be calculated JIT if not provided
25
+ * @param {object} [messageFunctions]
25
26
  * @returns {CSN.Model} Returns the transformed input model
26
27
  * @todo should be done by the compiler - Check associations for valid foreign keys
27
28
  * @todo check if needed at all: Remove '$projection' from paths in the element's ON-condition
28
29
  */
29
- function generateDrafts( csn, options, services ) {
30
- const messageFunctions = makeMessageFunction(csn, options, 'for.odata');
30
+ function generateDrafts( csn, options, services, messageFunctions ) {
31
+ // TEMP(2024-02-26): Temporary! Umbrella uses this file directly in cds/lib/compile/for/drafts.js#L1
32
+ messageFunctions ??= makeMessageFunction(csn, options, 'odata-drafts');
33
+
31
34
  const { error, info } = messageFunctions;
32
35
  const {
33
36
  createAndAddDraftAdminDataProjection, createScalarElement,
@@ -77,8 +80,7 @@ function generateDrafts( csn, options, services ) {
77
80
  */
78
81
  function generateDraftForOdata( artifact, artifactName, rootArtifact ) {
79
82
  // Nothing to do if already draft-enabled (composition traversal may have circles)
80
- if ((artifact['@Common.DraftRoot.PreparationAction'] || artifact['@Common.DraftNode.PreparationAction']) &&
81
- artifact.actions && artifact.actions.draftPrepare)
83
+ if (filterDict[artifactName])
82
84
  return;
83
85
 
84
86
 
@@ -194,7 +196,6 @@ function generateDrafts( csn, options, services ) {
194
196
  }
195
197
  }
196
198
 
197
-
198
199
  // Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
199
200
 
200
201
  // action draftPrepare (SideEffectsQualifier: String) return <artifact>;
@@ -3,10 +3,11 @@
3
3
  const { setProp } = require('../../base/model');
4
4
  const flattening = require('../db/flattening');
5
5
  const {
6
- applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs, cloneCsnNonDict, forEachMember, applyTransformationsOnNonDictionary,
6
+ applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs, forEachMember, applyTransformationsOnNonDictionary,
7
7
  } = require('../../model/csnUtils');
8
8
  const associations = require('../db/associations');
9
9
  const backlinks = require('../db/backlinks');
10
+ const { cloneCsnNonDict } = require('../../model/cloneCsn');
10
11
 
11
12
 
12
13
  /**
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { isBetaEnabled } = require('../../base/model');
4
4
  const { CompilerAssertion } = require('../../base/error');
5
- const { cloneCsnNonDict, getUtils, isAspect } = require('../../model/csnUtils');
5
+ const { getUtils, isAspect } = require('../../model/csnUtils');
6
6
  const transformUtils = require('../transformUtils');
7
7
  const flattening = require('../db/flattening');
8
8
  const types = require('./types');
@@ -15,6 +15,7 @@ const generateDrafts = require('../draft/db');
15
15
  const handleExists = require('../db/transformExists');
16
16
  const misc = require('./misc');
17
17
  const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
18
+ const { cloneFullCsn } = require('../../model/cloneCsn');
18
19
 
19
20
  /**
20
21
  * This is just a PoC for now!
@@ -32,7 +33,7 @@ function effectiveCsn( model, options, messageFunctions ) {
32
33
  if (!isBetaEnabled(options, 'effectiveCsn'))
33
34
  throw new CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
34
35
 
35
- const csn = cloneCsnNonDict(model, options);
36
+ const csn = cloneFullCsn(model, options);
36
37
  delete csn.vocabularies; // must not be set for effective CSN
37
38
  messageFunctions.setModel(csn);
38
39
 
@@ -1,9 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- cloneCsnNonDict, applyTransformations, applyTransformationsOnNonDictionary, cloneCsnDictionary, applyTransformationsOnDictionary,
4
+ applyTransformations,
5
+ applyTransformationsOnNonDictionary,
6
+ applyTransformationsOnDictionary,
5
7
  } = require('../../model/csnUtils');
6
8
  const { forEachKey } = require('../../utils/objectUtils');
9
+ const { cloneCsnDict, cloneCsnNonDict } = require('../../model/cloneCsn');
7
10
 
8
11
  /**
9
12
  * Resolve all references to structured types in entities to the underlying elements.
@@ -56,7 +59,7 @@ function resolveTypes( csn, csnUtils ) {
56
59
  if (final?.elements) {
57
60
  // We do full clones so users don't get unexpected linkage later
58
61
  if (!parent.elements)
59
- parent.elements = cloneCsnDictionary(final.elements);
62
+ parent.elements = cloneCsnDict(final.elements);
60
63
  delete parent.type;
61
64
  }
62
65
  else if (final && final.items) {
@@ -102,7 +105,7 @@ function resolveTypes( csn, csnUtils ) {
102
105
  if (finalSub?.elements) {
103
106
  // We do full clones so users don't get unexpected linkage later
104
107
  if (!_parent.elements)
105
- _parent.elements = cloneCsnDictionary(finalSub.elements);
108
+ _parent.elements = cloneCsnDict(finalSub.elements);
106
109
  delete _parent.type;
107
110
  stack.push( _parent );
108
111
  }