@sap/cds-compiler 5.4.2 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/bin/cds_remove_invalid_whitespace.js +4 -4
  3. package/bin/cds_update_annotations.js +3 -3
  4. package/bin/cds_update_identifiers.js +3 -3
  5. package/lib/api/main.js +18 -30
  6. package/lib/api/validate.js +6 -1
  7. package/lib/base/lazyload.js +28 -0
  8. package/lib/base/location.js +1 -0
  9. package/lib/base/message-registry.js +53 -11
  10. package/lib/base/messages.js +17 -3
  11. package/lib/checks/{dbFeatureFlags.js → featureFlags.js} +1 -1
  12. package/lib/checks/parameters.js +61 -4
  13. package/lib/checks/validator.js +14 -6
  14. package/lib/compiler/index.js +7 -7
  15. package/lib/compiler/shared.js +29 -13
  16. package/lib/gen/BaseParser.js +345 -235
  17. package/lib/gen/CdlParser.js +4434 -4492
  18. package/lib/gen/Dictionary.json +2 -2
  19. package/lib/json/to-csn.js +3 -1
  20. package/lib/language/antlrParser.js +2 -111
  21. package/lib/main.js +16 -37
  22. package/lib/modelCompare/utils/filter.js +47 -21
  23. package/lib/parsers/AstBuildingParser.js +59 -49
  24. package/lib/parsers/CdlGrammar.g4 +91 -130
  25. package/lib/parsers/index.js +123 -0
  26. package/lib/render/toSql.js +8 -2
  27. package/lib/render/utils/delta.js +33 -1
  28. package/lib/transform/db/{transformExists.js → assocsToQueries/transformExists.js} +12 -407
  29. package/lib/transform/db/assocsToQueries/utils.js +440 -0
  30. package/lib/transform/db/expansion.js +2 -2
  31. package/lib/transform/draft/db.js +14 -3
  32. package/lib/transform/effective/annotations.js +3 -3
  33. package/lib/transform/effective/main.js +5 -7
  34. package/lib/transform/featureFlags.js +5 -0
  35. package/lib/transform/forRelationalDB.js +125 -192
  36. package/lib/transform/odata/createForeignKeys.js +1 -1
  37. package/lib/transform/odata/flattening.js +1 -1
  38. package/lib/transform/transformUtils.js +0 -51
  39. package/package.json +2 -2
  40. package/lib/transform/db/featureFlags.js +0 -5
@@ -0,0 +1,440 @@
1
+ 'use strict';
2
+
3
+ const { getRealName } = require('../../../render/utils/common');
4
+ const { ModelError } = require('../../../base/error');
5
+
6
+ /**
7
+ * Some shared transformation utilities between exists rewriting and general assoc to subselect rewriting
8
+ *
9
+ * @param {CSN.Model} csn
10
+ * @param {Function} inspectRef
11
+ * @param {Function} error
12
+ * @returns {object}
13
+ */
14
+ function getHelpers( csn, inspectRef, error ) {
15
+ return {
16
+ getBase,
17
+ firstLinkIsEntityOrQuerySource,
18
+ getFirstAssoc,
19
+ translateManagedAssocToWhere,
20
+ getQuerySources,
21
+ translateUnmanagedAssocToWhere,
22
+ };
23
+
24
+ /**
25
+ * Get the name of the source-side query source
26
+ *
27
+ * @param {string | Array | null} queryBase
28
+ * @param {boolean} isPrefixedWithTableAlias
29
+ * @param {CSN.Column} current
30
+ * @param {CSN.Path} path
31
+ * @returns {string}
32
+ */
33
+ function getBase( queryBase, isPrefixedWithTableAlias, current, path ) {
34
+ if (typeof queryBase === 'string') // alias
35
+ return queryBase;
36
+ else if (queryBase) // ref
37
+ return queryBase.length > 1 ? queryBase[queryBase.length - 1] : getRealName(csn, queryBase[0]);
38
+ else if (isPrefixedWithTableAlias)
39
+ return current.ref[0];
40
+ return getParent(current, path);
41
+ }
42
+
43
+ /**
44
+ * For a given xpr, check in which entity/query source the ref "is".
45
+ *
46
+ * If the ref already starts with an entity/query source, simply return the first ref step.
47
+ * Otherwise, use $env to figure it out:
48
+ * - $env=<string> -> the string is the source
49
+ * - $env=<number> && $scope='mixin' -> the current query is the source
50
+ * - $env=<number> && $scope!=='mixin' -> such refs start with entity/query source, are already handled
51
+ * - $env=true -> does not apply for "EXISTS" handling, only happens in ORDER BY or explicit on-cond redirection
52
+ *
53
+ * If we have a ref but no $env, throw to trigger recompile - but such cases should have already led to a recompile with
54
+ * the validator/enricher.
55
+ *
56
+ * Since we only call this function when it is not just a simple SELECT FROM X,
57
+ * we can be sure that resolving the ref requires $env information.
58
+ *
59
+ * @param {object} xpr
60
+ * @param {CSN.Path} path
61
+ * @returns {string|undefined} undefined in case of errors
62
+ * @throws {Error} Throws if xpr.ref but no xpr.$env
63
+ * @todo $env is going to be removed from CSN, but csnRefs will provide it
64
+ */
65
+ // eslint-disable-next-line consistent-return
66
+ function getParent( xpr, path ) {
67
+ if (firstLinkIsEntityOrQuerySource(xpr, path)) {
68
+ return xpr.ref[0];
69
+ }
70
+ else if (xpr.$env) {
71
+ if (typeof xpr.$env === 'string') {
72
+ return xpr.$env;
73
+ }
74
+ else if (typeof xpr.$env === 'number') {
75
+ if (xpr.$scope === 'mixin')
76
+ return '';
77
+ return error(null, xpr.$path, '$env with number is not handled yet - report this error!');
78
+ }
79
+
80
+ return error(null, xpr.$path, 'Boolean $env is not handled yet - report this error!');
81
+ }
82
+ else if (xpr.ref) {
83
+ throw new ModelError('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Check (using inspectRef -> links), whether the first path step is an entity or query source
89
+ *
90
+ * @param {object} obj
91
+ * @param {CSN.Path} objPath
92
+ * @returns {boolean}
93
+ */
94
+ function firstLinkIsEntityOrQuerySource( obj, objPath ) {
95
+ const { links } = getLinksAndArt(obj, objPath);
96
+ return links && (links[0].art.kind === 'entity' || links[0].art.query || links[0].art.from);
97
+ }
98
+
99
+ /**
100
+ * From the given expression (having inspectRef -> links), find the first association.
101
+ *
102
+ * @param {object} xprPart
103
+ * @param {CSN.Path} path
104
+ * @returns {{head: Array, root: CSN.Element, ref: string|object, tail: Array}} The first assoc (root), the corresponding ref (ref), anything before the ref (head) and the rest of the ref (tail).
105
+ */
106
+ function getFirstAssoc( xprPart, path ) {
107
+ const { links, art } = getLinksAndArt({}, path);
108
+ for (let i = 0; i < xprPart.ref.length - 1; i++) {
109
+ if (links[i].art && links[i].art.target) {
110
+ return {
111
+ head: (i === 0 ? [] : xprPart.ref.slice(0, i)), root: links[i].art, ref: xprPart.ref[i], tail: xprPart.ref.slice(i + 1),
112
+ };
113
+ }
114
+ }
115
+ return {
116
+ head: (xprPart.ref.length === 1 ? [] : xprPart.ref.slice(0, xprPart.ref.length - 1)), root: art, ref: xprPart.ref[xprPart.ref.length - 1], tail: [],
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Translate an `EXISTS <managed assoc>` into a part of a WHERE condition.
122
+ *
123
+ * For each of the foreign keys, do:
124
+ * + build the target side by prefixing `target` in front of the ref
125
+ * + build the source side by prefixing `base` (if not already part of `current`)
126
+ * and the assoc name itself (current) in front of the ref
127
+ * + Compare source and target with `=`
128
+ *
129
+ * If there is more than one foreign key, join with `and`.
130
+ *
131
+ * The new tokens are immediately added to the WHERE of the subselect
132
+ *
133
+ * @param {CSN.Element} root
134
+ * @param {string} target
135
+ * @param {boolean} isPrefixedWithTableAlias
136
+ * @param {string} base
137
+ * @param {Token} current
138
+ * @returns {object[]} The stuff to add to the where
139
+ */
140
+ function translateManagedAssocToWhere( root, target, isPrefixedWithTableAlias, base, current ) {
141
+ if (current.$scope === '$self') {
142
+ error('ref-unexpected-self', current.$path, { '#': 'exists', id: current.ref[0], name: 'exists' });
143
+ return [];
144
+ }
145
+
146
+ const whereExtension = [];
147
+ for (let j = 0; j < root.keys.length; j++) {
148
+ const lop = { ref: [ target, ...root.keys[j].ref ] }; // target side
149
+ const rop = { ref: (isPrefixedWithTableAlias ? [] : [ base ]).concat([ ...toRawRef(current.ref), ...root.keys[j].ref ]) }; // source side
150
+
151
+ if (j > 0)
152
+ whereExtension.push('and');
153
+
154
+ whereExtension.push(...[ lop, '=', rop ]);
155
+ }
156
+
157
+ return whereExtension;
158
+ }
159
+
160
+ /**
161
+ * Translate an `EXISTS <unmanaged assoc>` into a part of a WHERE condition.
162
+ *
163
+ * A valid $self-backlink is handled in translateDollarSelfToWhere.
164
+ *
165
+ * For an ordinary unmanaged association, we do the following for each part of the on-condition:
166
+ * - target side: We prefix the real target and cut off the assoc-name from the ref
167
+ * - source side w/ leading $self: We remove the $self and add the source side entity/query source
168
+ * - source side w/o leading $self: We simply add the source side entity/query source in front of the ref
169
+ * - all other: Leave intact, usually operators
170
+ *
171
+ * @param {CSN.Element} root
172
+ * @param {string} target
173
+ * @param {boolean} isPrefixedWithTableAlias
174
+ * @param {string} base
175
+ * @param {Token} current
176
+ * @returns {object[]} The stuff to add to the where
177
+ */
178
+ function translateUnmanagedAssocToWhere( root, target, isPrefixedWithTableAlias, base, current ) {
179
+ const whereExtension = [];
180
+
181
+ for (let j = 0; j < root.on.length; j++)
182
+ j = processExpressionPart(root.on, root.$path.concat('on'), j, whereExtension);
183
+
184
+ return whereExtension;
185
+
186
+ /**
187
+ * Process the given expression and apply the steps described above.
188
+ *
189
+ * @param {Array} expression Expression we are processing
190
+ * @param {CSN.Path} path Path to the expression
191
+ * @param {number} expressionIndex Index in the current expression, imporant for paths and stuff
192
+ * @param {Array} collector Array to collect the processed expressionparts into
193
+ * @returns {number} How far along expression we have processed - so the main loop can jump ahead
194
+ */
195
+ function processExpressionPart(expression, path, expressionIndex, collector) {
196
+ const part = expression[expressionIndex];
197
+
198
+ if (part?.xpr) {
199
+ const xpr = { xpr: [] };
200
+ for (let i = 0; i < part.xpr.length; i++)
201
+ i = processExpressionPart(part.xpr, path.concat(expressionIndex, 'xpr'), i, xpr.xpr);
202
+
203
+ collector.push(xpr);
204
+ return expressionIndex;
205
+ }
206
+
207
+ // we can only resolve stuff on refs - skip literals like =
208
+ // but also keep along stuff like null and undefined, so compiler
209
+ // can have a chance to complain/ we can fail later nicely maybe
210
+ if (!(part && part.ref)) {
211
+ collector.push(part);
212
+ return expressionIndex;
213
+ }
214
+
215
+ // root.$path should be safe - we can only reference things in exists that exist when we enrich
216
+ // so all of them should have a $path.
217
+ const { art, links } = getLinksAndArt(part, path.concat(expressionIndex));
218
+ // Dollar Self Backlink
219
+ if (isValidDollarSelf(expression[expressionIndex], path.concat(expressionIndex), expression[expressionIndex + 1], expression[expressionIndex + 2], path.concat(expressionIndex + 2 ))) {
220
+ if (expression[expressionIndex].ref[0] === '$self' && expression[expressionIndex].ref.length === 1)
221
+ collector.push(...translateDollarSelfToWhere(base, target, expression[expressionIndex + 2], path.concat(expressionIndex + 2 )));
222
+ else
223
+ collector.push(...translateDollarSelfToWhere(base, target, expression[expressionIndex], path.concat(expressionIndex)));
224
+
225
+ return expressionIndex + 2;
226
+ }
227
+ else if (links && links[0].art === root) { // target side
228
+ collector.push({ ref: [ target, ...part.ref.slice(1) ] });
229
+ }
230
+ else if (part.$scope === '$self') { // source side - "absolute" scope
231
+ const column = part._art._column;
232
+ if (column && column.as) { // Replace with the "original" expression (the .ref, .xpr etc.)
233
+ collector.push(translateToSourceSide(column));
234
+ }
235
+ else {
236
+ collector.push(assignAndDeleteAsAndKey({}, part, { ref: [ base, ...part.ref.slice(1) ] }));
237
+ }
238
+ }
239
+ else if (art) { // source side - with local scope
240
+ if (isPrefixedWithTableAlias || part.$scope === 'alias')
241
+ collector.push({ ref: [ ...current.ref.slice(0, -1), ...part.ref ] });
242
+ else
243
+ collector.push({ ref: [ base, ...current.ref.slice(0, -1), ...part.ref ] });
244
+ }
245
+ else { // operator - or any other leftover
246
+ collector.push(part);
247
+ }
248
+
249
+ return expressionIndex;
250
+ }
251
+
252
+
253
+ /**
254
+ * Run Object.assign on all of the passed in parameters and delete a .as and .key at the end
255
+ *
256
+ * @param {...any} args
257
+ * @returns {object} The merged args without an .as and .key property
258
+ */
259
+ function assignAndDeleteAsAndKey( ...args ) {
260
+ const obj = Object.assign.apply(null, args);
261
+ delete obj.as;
262
+ delete obj.key;
263
+ return obj;
264
+ }
265
+ /**
266
+ * Translate the given obj (a column-like thing) into an expression that we can use in the WHERE.
267
+ * - Strip off $self/$projection and correctly replace with source expression
268
+ * - Drill further down into .xpr
269
+ * - Correctly set table alias in front of ref
270
+ *
271
+ * @param {object} obj
272
+ * @returns {object}
273
+ */
274
+ function translateToSourceSide( obj ) {
275
+ if (obj.ref) {
276
+ if (obj.$scope === '$self') { // TODO: Check with this way down, do we keep the links?
277
+ const column = obj._art._column;
278
+ if (column && column.as)
279
+ return translateToSourceSide(column);
280
+ return assignAndDeleteAsAndKey({}, obj, { ref: [ base, ...obj.ref.slice(1) ] });
281
+ }
282
+ else if (typeof obj.$env === 'string') {
283
+ return assignAndDeleteAsAndKey({}, obj, { ref: [ obj.$env, ...obj.ref ] });
284
+ }
285
+
286
+ return assignAndDeleteAsAndKey({}, obj, { ref: [ ...obj.ref ] });
287
+ }
288
+ else if (obj.xpr) { // we need to drill further down into .xpr
289
+ return assignAndDeleteAsAndKey({}, obj, { xpr: obj.xpr.map(translateToSourceSide) });
290
+ }
291
+ else if (obj.args) {
292
+ return assignAndDeleteAsAndKey({}, obj, { args: obj.args.map(translateToSourceSide) });
293
+ }
294
+
295
+ return obj;
296
+ }
297
+
298
+ /**
299
+ * Check that an expression triple is a valid $self
300
+ *
301
+ * @param {Token} leftSide
302
+ * @param {CSN.Path} pathLeft
303
+ * @param {Token} middle
304
+ * @param {Token} rightSide
305
+ * @param {CSN.Path} pathRight
306
+ * @returns {boolean}
307
+ */
308
+ function isValidDollarSelf( leftSide, pathLeft, middle, rightSide, pathRight ) {
309
+ if (leftSide && leftSide.ref && rightSide && rightSide.ref && middle === '=') {
310
+ const right = inspectRef(pathRight);
311
+ const left = inspectRef(pathLeft);
312
+
313
+ if (!right || !left)
314
+ return false;
315
+
316
+ const rightSideArt = right.art;
317
+ const leftSideArt = left.art;
318
+
319
+ return leftSide.ref[0] === '$self' && leftSide.ref.length === 1 && rightSideArt && rightSideArt.target ||
320
+ rightSide.ref[0] === '$self' && rightSide.ref.length === 1 && leftSideArt && leftSideArt.target;
321
+ }
322
+
323
+ return false;
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Turn the would-be on-condition of a $self backlink into a WHERE condition.
329
+ *
330
+ * Prefix the target/source side base accordingly and build the source = target comparisons.
331
+ *
332
+ * @param {string} base The source entity/query source name
333
+ * @param {string} target The target entity/query source name
334
+ * @param {object} assoc The association element - the "not-$self" side of the comparison
335
+ * @param {CSN.Path} path
336
+ * @returns {TokenStream} The WHERE representing the $self comparison
337
+ */
338
+ function translateDollarSelfToWhere( base, target, assoc, path ) {
339
+ const where = [];
340
+ const { art } = getLinksAndArt(assoc, path);
341
+ if (art.keys) {
342
+ for (let i = 0; i < art.keys.length; i++) {
343
+ const lop = { ref: [ target, ...assoc.ref.slice(1), ...art.keys[i].ref ] }; // target side
344
+ const rop = { ref: [ base, ...art.keys[i].ref ] }; // source side
345
+ if (i > 0)
346
+ where.push('and');
347
+
348
+ where.push(...[ lop, '=', rop ]);
349
+ }
350
+ }
351
+ else if (art.on) {
352
+ for (let i = 0; i < art.on.length; i++) {
353
+ const part = art.on[i];
354
+ const partInspect = getLinksAndArt(part, art.$path.concat([ 'on', i ]));
355
+ if (partInspect.links && partInspect.links[0].art === art) { // target side
356
+ where.push({ ref: [ base, ...part.ref.slice(1) ] });
357
+ }
358
+ else if (part.$scope === '$self') { // source side - "absolute" scope
359
+ // Same message as in forRelationalDB/transformDollarSelfComparisonWithUnmanagedAssoc
360
+ error(null, part.$path, { name: '$self' },
361
+ 'An association that uses $(NAME) in its ON-condition can\'t be compared to "$self"');
362
+ }
363
+ else if (partInspect.art) { // source side - with local scope
364
+ where.push({ ref: [ target, ...assoc.ref.slice(1, -1), ...part.ref ] });
365
+ }
366
+ else { // operator - or any other leftover
367
+ where.push(part);
368
+ }
369
+ }
370
+ }
371
+ return where;
372
+ }
373
+
374
+ /**
375
+ * Turn a ref-array into an array of strings.
376
+ *
377
+ * @param {Array} ref Array of strings or objects with `id`
378
+ * @returns {string[]}
379
+ */
380
+ function toRawRef( ref ) {
381
+ return ref.map(r => (r.id ? r.id : r));
382
+ }
383
+
384
+ /**
385
+ * Get the source aliases from a query - drill down somewhat into joins (is that correct?)
386
+ *
387
+ * @param {CSN.Query} query
388
+ * @returns {object}
389
+ */
390
+ function getQuerySources( query ) {
391
+ const sources = Object.create(null);
392
+ if (query.from.as)
393
+ sources[query.from.as] = query.from.as;
394
+ else if (query.from.args)
395
+ return Object.assign(sources, getJoinSources(query.from.args));
396
+ else if (query.from.ref)
397
+ sources[query.from.ref[query.from.ref.length - 1]] = query.from.ref[query.from.ref.length - 1];
398
+
399
+ return sources;
400
+ }
401
+
402
+ /**
403
+ * Get the source aliases from a join
404
+ *
405
+ * @param {Array} args Join args
406
+ * @returns {object}
407
+ */
408
+ function getJoinSources( args ) {
409
+ let sources = Object.create(null);
410
+ for (const join of args) {
411
+ if (join.as) {
412
+ sources[join.as] = join.as;
413
+ }
414
+ else if (join.args) {
415
+ const subSources = getJoinSources(join.args);
416
+ sources = Object.assign(sources, subSources);
417
+ }
418
+ else if (join.ref) {
419
+ sources[join.ref[join.ref.length - 1]] = join.ref[join.ref.length - 1];
420
+ }
421
+ }
422
+
423
+ return sources;
424
+ }
425
+
426
+ /**
427
+ * Use cacjed _links and _art or calculate via inspectRef
428
+ * @param {object} obj
429
+ * @param {CSN.Path} objPath
430
+ * @returns {object}
431
+ */
432
+ function getLinksAndArt(obj, objPath) {
433
+ if (obj._links)
434
+ return { links: obj._links, art: obj._art };
435
+ return inspectRef(objPath);
436
+ }
437
+ }
438
+
439
+
440
+ module.exports = { getHelpers };
@@ -11,7 +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
+ const { featureFlags } = require('../featureFlags');
15
15
 
16
16
  /**
17
17
  * For keys, columns, groupBy and orderBy, expand structured things.
@@ -27,7 +27,7 @@ const { featureFlags } = require('./featureFlags');
27
27
  function expandStructureReferences( csn, options, pathDelimiter, messageFunctions, csnUtils, iterateOptions = {} ) {
28
28
  const { error, info, throwWithAnyError } = messageFunctions;
29
29
 
30
- if (options.transformation === 'odata' || options.transformation === 'effective' || csn.meta?.[featureFlags]?.$expandInline)
30
+ if (options.transformation === 'odata' || csn.meta?.[featureFlags]?.$expandInline)
31
31
  rewriteExpandInline();
32
32
 
33
33
  throwWithAnyError();
@@ -7,6 +7,7 @@ const {
7
7
  const { setProp, isDeprecatedEnabled } = require('../../base/model');
8
8
  const { getTransformers } = require('../transformUtils');
9
9
  const { ModelError } = require('../../base/error');
10
+ const { forEach } = require('../../utils/objectUtils');
10
11
  const draftAnnotation = '@odata.draft.enabled';
11
12
  const booleanBuiltin = 'cds.Boolean';
12
13
 
@@ -24,7 +25,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
24
25
  const allServices = getServiceNames(csn);
25
26
  const draftRoots = new WeakMap();
26
27
  const {
27
- createForeignKeyElement, createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
28
+ createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
28
29
  addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
29
30
  } = getTransformers(csn, options, messageFunctions, pathDelimiter);
30
31
  const { getCsnDef, isComposition } = csnUtils;
@@ -268,8 +269,18 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
268
269
 
269
270
  const draftUUIDKey = getDraftUUIDKey(draftAdministrativeData.DraftAdministrativeData);
270
271
  if (!(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') && draftUUIDKey) {
271
- const path = [ 'definitions', draftsArtifactName, 'elements', 'DraftAdministrativeData', 'keys', 0 ];
272
- createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', draftUUIDKey, draftsArtifact, draftsArtifactName, path);
272
+ const source = csn.definitions[draftAdministrativeData.DraftAdministrativeData.target];
273
+ const sourceElement = source.elements[draftUUIDKey.ref[0]];
274
+ const targetElement = {};
275
+ forEach(sourceElement, (key, value) => {
276
+ if(!key.startsWith('@') && key !== 'key')
277
+ targetElement[key] = value;
278
+ })
279
+
280
+ if(sourceElement.key) targetElement.notNull = true;
281
+
282
+ draftsArtifact.elements['DraftAdministrativeData' + (options.sqlMapping === 'hdbcds' ? '.' : '_') + draftUUIDKey.ref[0]] = targetElement;
283
+
273
284
  draftAdministrativeData.DraftAdministrativeData.on = createAssociationPathComparison('DraftAdministrativeData',
274
285
  getNameForRef(draftUUIDKey),
275
286
  '=',
@@ -194,11 +194,10 @@ function remapODataAnnotations( csn ) {
194
194
  /**
195
195
  * Do the .texts anno magic if we can be reasonably sure that we are actually dealing with a .texts entity.
196
196
  *
197
- * @param {CSN.Model} csn
198
197
  * @param {string} artifactName
199
198
  * @param {CSN.Artifact} artifact
200
199
  */
201
- function sealAnnoMagicForTexts(csn, artifactName, artifact) {
200
+ function sealAnnoMagicForTexts(artifactName, artifact) {
202
201
  if (artifactName.endsWith('.texts') && artifact.elements?.locale) {
203
202
  const firstNonKey = getFirstNonKeyElement(artifact);
204
203
  if (firstNonKey && firstNonKey.type === 'cds.String') {
@@ -269,6 +268,8 @@ function sealAnnoMagic(csn) {
269
268
  forEach(parent.elements, (_elementName, element) => {
270
269
  if (element['@ObjectModel.text.element'] && parent.elements[element['@ObjectModel.text.element']['=']] && parent.elements[element['@ObjectModel.text.element']['=']]['@Semantics.text'] === undefined)
271
270
  parent.elements[element['@ObjectModel.text.element']['=']]['@Semantics.text'] = true;
271
+ if (element.target && element.target.endsWith('.texts') && csn.definitions[element.target].elements?.locale)
272
+ sealAnnoMagicForTexts(element.target, csn.definitions[element.target]);
272
273
  });
273
274
  }
274
275
 
@@ -338,5 +339,4 @@ function getOnConditionAsComparisonTuples(on, assocName) {
338
339
  module.exports = {
339
340
  remapODataAnnotations,
340
341
  sealAnnoMagic,
341
- sealAnnoMagicForTexts,
342
342
  };
@@ -12,11 +12,12 @@ const validate = require('../../checks/validator');
12
12
  const expansion = require('../db/expansion');
13
13
  const queries = require('./queries');
14
14
  const associations = require('./associations');
15
- const handleExists = require('../db/transformExists');
15
+ const handleExists = require('../db/assocsToQueries/transformExists');
16
16
  const misc = require('./misc');
17
17
  const annotations = require('./annotations');
18
18
  const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
19
19
  const { cloneFullCsn } = require('../../model/cloneCsn');
20
+ const { featureFlags } = require('../featureFlags');
20
21
 
21
22
  /**
22
23
  * This is just a PoC for now!
@@ -45,7 +46,8 @@ function effectiveCsn( model, options, messageFunctions ) {
45
46
  ...messageFunctions, csnUtils, ...csnUtils, csn, options, isAspect,
46
47
  });
47
48
 
48
- rewriteCalculatedElementsInViews(csn, options, csnUtils, '_', messageFunctions);
49
+ if (csn.meta?.[featureFlags]?.$calculatedElements)
50
+ rewriteCalculatedElementsInViews(csn, options, csnUtils, '_', messageFunctions);
49
51
 
50
52
  // Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
51
53
  handleExists(csn, options, messageFunctions.error, csnUtils.inspectRef, csnUtils.initDefinition, csnUtils.dropDefinitionCache);
@@ -85,11 +87,7 @@ function effectiveCsn( model, options, messageFunctions ) {
85
87
  options.deriveAnalyticalAnnotations ? annotations.sealAnnoMagic(csn) : {},
86
88
  ], null);
87
89
 
88
- const artifactTransformers = [];
89
- if (options.deriveAnalyticalAnnotations)
90
- artifactTransformers.push(annotations.sealAnnoMagicForTexts);
91
-
92
- applyTransformations(csn, transformers, artifactTransformers, { skipIgnore: false, processAnnotations: true });
90
+ applyTransformations(csn, transformers, [], { skipIgnore: false, processAnnotations: true });
93
91
 
94
92
  if (!options.resolveProjections)
95
93
  redoProjections.forEach(fn => fn());
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const featureFlags = Symbol.for('Feature flags');
4
+
5
+ module.exports = { featureFlags };