@sap/cds-compiler 4.9.2 → 5.0.6
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.
- package/CHANGELOG.md +74 -0
- package/bin/cds_remove_invalid_whitespace.js +2 -1
- package/bin/cdsc.js +15 -11
- package/bin/cdshi.js +1 -0
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +7 -19
- package/lib/api/options.js +5 -11
- package/lib/api/trace.js +0 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/location.js +4 -1
- package/lib/base/message-registry.js +29 -29
- package/lib/base/messages.js +22 -26
- package/lib/base/model.js +0 -2
- package/lib/base/node-helpers.js +0 -1
- package/lib/checks/enricher.js +1 -5
- package/lib/checks/structuredAnnoExpressions.js +30 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +4 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +18 -2
- package/lib/compiler/checks.js +2 -5
- package/lib/compiler/define.js +7 -7
- package/lib/compiler/extend.js +68 -33
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/index.js +23 -6
- package/lib/compiler/lsp-api.js +501 -2
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +1 -4
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +112 -31
- package/lib/compiler/tweak-assocs.js +2 -16
- package/lib/compiler/utils.js +2 -1
- package/lib/compiler/xsn-model.js +4 -0
- package/lib/edm/annotations/genericTranslation.js +95 -42
- package/lib/edm/csn2edm.js +16 -4
- package/lib/edm/edm.js +2 -3
- package/lib/edm/edmAnnoPreprocessor.js +1 -2
- package/lib/edm/edmPreprocessor.js +1 -7
- package/lib/gen/Dictionary.json +29 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4995 -4817
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +4 -7
- package/lib/json/to-csn.js +23 -12
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/errorStrategy.js +0 -1
- package/lib/language/genericAntlrParser.js +35 -12
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/language/textUtils.js +1 -0
- package/lib/main.d.ts +28 -9
- package/lib/main.js +7 -4
- package/lib/model/csnRefs.js +20 -4
- package/lib/model/csnUtils.js +0 -2
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/modelCompare/compare.js +1 -1
- package/lib/optionProcessor.js +28 -9
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +36 -7
- package/lib/render/toSql.js +1 -0
- package/lib/render/utils/common.js +12 -9
- package/lib/render/utils/stringEscapes.js +1 -0
- package/lib/transform/db/applyTransformations.js +13 -8
- package/lib/transform/db/associations.js +62 -54
- package/lib/transform/db/constraints.js +23 -25
- package/lib/transform/db/expansion.js +1 -6
- package/lib/transform/db/flattening.js +89 -111
- package/lib/transform/db/temporal.js +3 -4
- package/lib/transform/db/views.js +0 -1
- package/lib/transform/draft/odata.js +51 -3
- package/lib/transform/effective/annotations.js +3 -2
- package/lib/transform/effective/flattening.js +135 -0
- package/lib/transform/effective/main.js +6 -6
- package/lib/transform/effective/types.js +13 -9
- package/lib/transform/forOdata.js +0 -2
- package/lib/transform/forRelationalDB.js +0 -19
- package/lib/transform/localized.js +7 -8
- package/lib/transform/odata/flattening.js +39 -31
- package/lib/transform/odata/typesExposure.js +5 -17
- package/lib/transform/transformUtils.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +21 -3
- package/lib/utils/file.js +13 -7
- package/lib/utils/moduleResolve.js +59 -8
- package/lib/utils/term.js +3 -2
- package/package.json +7 -3
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/type-unexpected-foreign-keys.md +52 -0
- package/share/messages/type-unexpected-on-condition.md +52 -0
|
@@ -12,10 +12,11 @@ const {
|
|
|
12
12
|
const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
|
|
13
13
|
const transformUtils = require('../transformUtils');
|
|
14
14
|
const { csnRefs } = require('../../model/csnRefs');
|
|
15
|
-
const { setProp
|
|
15
|
+
const { setProp } = require('../../base/model');
|
|
16
16
|
const { forEach } = require('../../utils/objectUtils');
|
|
17
17
|
const { transformExpression } = require('./applyTransformations');
|
|
18
18
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
19
|
+
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Strip off leading $self from refs where applicable.
|
|
@@ -206,9 +207,24 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
206
207
|
* @param {object} iterateOptions
|
|
207
208
|
*/
|
|
208
209
|
function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
|
|
210
|
+
const adaptRefs = [];
|
|
211
|
+
|
|
212
|
+
applyTransformations(csn, getStructStepsFlattener(csn, options, messageFunctions, resolved, pathDelimiter, adaptRefs), [], iterateOptions);
|
|
213
|
+
|
|
214
|
+
adaptRefs.forEach(fn => fn());
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* @param {CSN.Model} csn
|
|
218
|
+
* @param {CSN.Options} options
|
|
219
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
220
|
+
* @param {WeakMap} resolved Cache for resolved refs
|
|
221
|
+
* @param {string} pathDelimiter
|
|
222
|
+
* @param {Function[]} adaptRefs
|
|
223
|
+
* @returns {object} applyTransformations transformer
|
|
224
|
+
*/
|
|
225
|
+
function getStructStepsFlattener( csn, options, messageFunctions, resolved, pathDelimiter, adaptRefs ) {
|
|
209
226
|
const { inspectRef, effectiveType } = csnRefs(csn);
|
|
210
227
|
const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
211
|
-
const adaptRefs = [];
|
|
212
228
|
|
|
213
229
|
/**
|
|
214
230
|
* For each step of the links, check if there is a type reference.
|
|
@@ -229,41 +245,57 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
|
|
|
229
245
|
return resolvedLinkTypes;
|
|
230
246
|
}
|
|
231
247
|
|
|
232
|
-
|
|
248
|
+
const transformer = {
|
|
233
249
|
// @ts-ignore
|
|
234
|
-
ref: (parent, prop, ref, path) => {
|
|
250
|
+
ref: (parent, prop, ref, path, _parent, _prop, context) => {
|
|
235
251
|
const { links, art, scope } = inspectRef(path);
|
|
236
252
|
const resolvedLinkTypes = resolveLinkTypes(links);
|
|
237
253
|
setProp(parent, '$path', [ ...path ]);
|
|
238
254
|
const lastRef = ref[ref.length - 1];
|
|
239
|
-
const fn = (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
parent.
|
|
255
|
+
const fn = (suspend = false, suspendPos = 0,
|
|
256
|
+
refFilter = _parent => true) => {
|
|
257
|
+
let refChanged = false;
|
|
258
|
+
if (refFilter(parent)) {
|
|
259
|
+
const scopedPath = [ ...parent.$path ];
|
|
260
|
+
// TODO: If foreign key annotations should be assigned via
|
|
261
|
+
// full path into target, uncomment this line and
|
|
262
|
+
// comment/remove setProp in expansion.js
|
|
263
|
+
// setProp(parent, '$structRef', parent.ref);
|
|
264
|
+
[ parent.ref, refChanged ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, suspend, suspendPos, parent.$bparam);
|
|
265
|
+
resolved.set(parent, { links, art, scope });
|
|
266
|
+
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
267
|
+
// TODO: Can this be done elegantly during expand phase already?
|
|
268
|
+
if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
|
|
269
|
+
if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
|
|
270
|
+
delete parent.as;
|
|
271
|
+
delete parent.$implicitAlias;
|
|
272
|
+
}
|
|
273
|
+
// To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
|
|
274
|
+
else if (parent.ref[parent.ref.length - 1] !== lastRef &&
|
|
275
|
+
(insideColumns(scopedPath) || insideKeys(scopedPath)) &&
|
|
276
|
+
!parent.as) {
|
|
277
|
+
parent.as = lastRef;
|
|
278
|
+
}
|
|
259
279
|
}
|
|
280
|
+
|
|
281
|
+
return refChanged;
|
|
260
282
|
};
|
|
261
|
-
|
|
262
|
-
|
|
283
|
+
|
|
284
|
+
if (context?.$annotation) {
|
|
285
|
+
const annotation = context.$annotation.value;
|
|
286
|
+
adaptRefs.push((...args) => {
|
|
287
|
+
const refChanged = fn(...args);
|
|
288
|
+
if (refChanged && annotation['='])
|
|
289
|
+
annotation['='] = true;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
// adapt queries later
|
|
294
|
+
adaptRefs.push(fn);
|
|
295
|
+
}
|
|
263
296
|
},
|
|
264
|
-
}
|
|
297
|
+
};
|
|
265
298
|
|
|
266
|
-
adaptRefs.forEach(fn => fn());
|
|
267
299
|
|
|
268
300
|
/**
|
|
269
301
|
* Return true if the path points inside columns
|
|
@@ -283,6 +315,8 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
|
|
|
283
315
|
function insideKeys( path ) {
|
|
284
316
|
return path.length >= 3 && path[path.length - 2] === 'keys' && typeof path[path.length - 1] === 'number';
|
|
285
317
|
}
|
|
318
|
+
|
|
319
|
+
return transformer;
|
|
286
320
|
}
|
|
287
321
|
|
|
288
322
|
/**
|
|
@@ -343,25 +377,28 @@ function flattenElements( csn, options, messageFunctions, pathDelimiter, iterate
|
|
|
343
377
|
if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
|
|
344
378
|
// unmanaged relations can't be primary key
|
|
345
379
|
delete flatElement.key;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0,
|
|
380
|
+
if (options.transformation !== 'effective') {
|
|
381
|
+
const process = endIndex => function processRef(_parent, _prop, xpr) {
|
|
382
|
+
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, endIndex).join(pathDelimiter);
|
|
349
383
|
const possibleFlatName = prefix + pathDelimiter + xpr[0];
|
|
350
384
|
/*
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
385
|
+
when element is defined in the current name resolution scope, like
|
|
386
|
+
entity E {
|
|
387
|
+
key x: Integer;
|
|
388
|
+
s : {
|
|
389
|
+
y : Integer;
|
|
390
|
+
a3 : association to E on a3.x = y;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
|
|
394
|
+
*/
|
|
361
395
|
if (flatElems[possibleFlatName])
|
|
362
396
|
xpr[0] = possibleFlatName;
|
|
363
|
-
}
|
|
364
|
-
|
|
397
|
+
};
|
|
398
|
+
transformExpression(flatElement, 'on', {
|
|
399
|
+
ref: process(-1),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
365
402
|
}
|
|
366
403
|
parent[prop].$orderedElements.push([ flatElemName, flatElement ]);
|
|
367
404
|
// Still add them - otherwise we might not detect collisions between generated elements.
|
|
@@ -427,36 +464,6 @@ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
|
|
|
427
464
|
return branches;
|
|
428
465
|
}
|
|
429
466
|
|
|
430
|
-
/**
|
|
431
|
-
* Link annotate extensions to managed associations as a preparational step
|
|
432
|
-
* for later annotation assignment on the final foreignkeys
|
|
433
|
-
* This function must be applied on an unmodified, structured CSN in order to
|
|
434
|
-
* traverse both the extensions and dictionary trees in corresponding order.
|
|
435
|
-
*
|
|
436
|
-
* @param {CSN.Model} csn
|
|
437
|
-
* @param {object} options
|
|
438
|
-
*/
|
|
439
|
-
function linkForeignKeyAnnotationExtensionsToAssociation( csn, options ) {
|
|
440
|
-
if (isBetaEnabled(options, 'annotateForeignKeys')) {
|
|
441
|
-
csn.extensions?.forEach(( ext ) => {
|
|
442
|
-
const defName = ext.annotate;
|
|
443
|
-
|
|
444
|
-
const traverseExtensions = (env, enode) => {
|
|
445
|
-
if (env?.target && env?.keys) {
|
|
446
|
-
setProp(env, '$fkExtensions', enode);
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
const elements = env?.items?.elements || env?.elements;
|
|
450
|
-
if (enode?.elements && elements)
|
|
451
|
-
Object.keys(enode.elements).forEach(en => traverseExtensions(elements[en], enode.elements[en]));
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
if (ext.annotate)
|
|
455
|
-
traverseExtensions(csn.definitions[defName], ext);
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
467
|
/**
|
|
461
468
|
* @param {CSN.Model} csn
|
|
462
469
|
* @param {CSN.Options} options
|
|
@@ -587,7 +594,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
587
594
|
* @returns {object} The clone of base
|
|
588
595
|
*/
|
|
589
596
|
function cloneAndExtendRef( key, base, ref ) {
|
|
590
|
-
const clone = cloneCsnNonDict(base,
|
|
597
|
+
const clone = cloneCsnNonDict(base, options );
|
|
591
598
|
if (key.ref) {
|
|
592
599
|
// We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
|
|
593
600
|
// Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
|
|
@@ -603,8 +610,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
603
610
|
}
|
|
604
611
|
setProp(clone, '$ref', $ref);
|
|
605
612
|
clone.ref = clone.ref.concat(key.ref);
|
|
606
|
-
if (clone.$structRef && key.$structRef)
|
|
607
|
-
clone.$structRef = clone.$structRef.concat(key.$structRef);
|
|
608
613
|
}
|
|
609
614
|
|
|
610
615
|
if (!clone.as && clone.ref && clone.ref.length > 0) {
|
|
@@ -704,34 +709,9 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
704
709
|
'Expected target cardinality $(VALUE) and $(CODE) to match');
|
|
705
710
|
}
|
|
706
711
|
}
|
|
707
|
-
}
|
|
708
|
-
// assign annotations from fkExtension tree to foreign keys
|
|
709
|
-
if (isBetaEnabled(options, 'annotateForeignKeys')) {
|
|
710
|
-
const extCollector = {};
|
|
711
|
-
fks.forEach(([ _fkn, fk ]) => {
|
|
712
|
-
let ext = element.$fkExtensions;
|
|
713
|
-
let extKey = elementName;
|
|
714
|
-
for (const step of fk.$extensionPath) {
|
|
715
|
-
extKey += `.${step}`;
|
|
716
|
-
ext = ext?.elements?.[step];
|
|
717
|
-
if (!ext)
|
|
718
|
-
break;
|
|
719
|
-
// collect annotations, lowest wins
|
|
720
|
-
// eslint-disable-next-line no-loop-func
|
|
721
|
-
Object.entries(ext).forEach(([ k, v ]) => {
|
|
722
|
-
if (k[0] === '@') {
|
|
723
|
-
fk[k] = v;
|
|
724
|
-
extCollector[extKey] = ext;
|
|
725
|
-
}
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
712
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (k[0] === '@')
|
|
733
|
-
delete ext[k];
|
|
734
|
-
}));
|
|
713
|
+
if (options.transformation === 'effective')
|
|
714
|
+
delete element.default;
|
|
735
715
|
}
|
|
736
716
|
orderedElements.push(...fks);
|
|
737
717
|
});
|
|
@@ -756,11 +736,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
756
736
|
* @param {CSN.Model} csn
|
|
757
737
|
* @param {object} options
|
|
758
738
|
* @param {string} pathDelimiter
|
|
759
|
-
* @param {object} extensionPath
|
|
760
739
|
* @param {number} lvl
|
|
761
740
|
* @returns {Array[]} First element of every sub-array is the foreign key name, second is the foreign key definition
|
|
762
741
|
*/
|
|
763
|
-
function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter,
|
|
742
|
+
function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
|
|
764
743
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
765
744
|
const isInspectRefResult = !Array.isArray(path);
|
|
766
745
|
|
|
@@ -806,7 +785,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
806
785
|
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
807
786
|
const alias = key.as || implicitAs(key.ref);
|
|
808
787
|
const result = csnUtils.inspectRef(continuePath);
|
|
809
|
-
fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter,
|
|
788
|
+
fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
|
|
810
789
|
});
|
|
811
790
|
if (!hasKeys)
|
|
812
791
|
delete finalElement.keys;
|
|
@@ -820,19 +799,18 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
820
799
|
// Skip already produced foreign keys
|
|
821
800
|
if (!elem['@odata.foreignKey4']) {
|
|
822
801
|
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
823
|
-
fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter,
|
|
802
|
+
fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
|
|
824
803
|
}
|
|
825
804
|
});
|
|
826
805
|
}
|
|
827
806
|
// we have reached a leaf element, create a foreign key
|
|
828
807
|
else if (finalElement.type == null || isBuiltinType(finalElement.type)) {
|
|
829
808
|
const newFk = Object.create(null);
|
|
830
|
-
|
|
831
|
-
for (const prop of [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type' ]) {
|
|
809
|
+
[ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
|
|
832
810
|
// copy props from original element to preserve derived types!
|
|
833
811
|
if (element[prop] !== undefined)
|
|
834
812
|
newFk[prop] = element[prop];
|
|
835
|
-
}
|
|
813
|
+
});
|
|
836
814
|
return [ [ prefix, newFk ] ];
|
|
837
815
|
}
|
|
838
816
|
|
|
@@ -886,7 +864,7 @@ module.exports = {
|
|
|
886
864
|
flattenAllStructStepsInRefs,
|
|
887
865
|
flattenElements,
|
|
888
866
|
removeLeadingSelf,
|
|
889
|
-
linkForeignKeyAnnotationExtensionsToAssociation,
|
|
890
867
|
handleManagedAssociationsAndCreateForeignKeys,
|
|
891
868
|
getBranches,
|
|
869
|
+
getStructStepsFlattener,
|
|
892
870
|
};
|
|
@@ -70,11 +70,10 @@ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
|
|
|
70
70
|
],
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const atTo = { ref: [ '$at', 'to' ] };
|
|
73
|
+
const validFrom = { ref: [ '$valid', 'from' ] };
|
|
74
|
+
const validTo = { ref: [ '$valid', 'to' ] };
|
|
76
75
|
|
|
77
|
-
const cond = [ '(', fromPath, '<',
|
|
76
|
+
const cond = [ '(', fromPath, '<', validTo, 'and', toPath, '>', validFrom, ')' ];
|
|
78
77
|
|
|
79
78
|
if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
|
|
80
79
|
normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
|
|
@@ -324,7 +324,6 @@ function getViewTransformer( csn, options, messageFunctions ) {
|
|
|
324
324
|
* @param {string} artName
|
|
325
325
|
* @param {CSN.Path} path
|
|
326
326
|
*/
|
|
327
|
-
// eslint-disable-next-line complexity
|
|
328
327
|
function transformViewOrEntity( query, artifact, artName, path ) {
|
|
329
328
|
const ignoreAssociations = options.sqlDialect === 'hana' && options.withHanaAssociations === false;
|
|
330
329
|
csnUtils.initDefinition(artifact);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachDefinition,
|
|
3
|
+
const { forEachDefinition, forEachMemberRecursively,
|
|
4
|
+
getServiceNames, applyAnnotationsFromExtensions,
|
|
5
|
+
transformExpression } = require('../../model/csnUtils');
|
|
4
6
|
const { forEach } = require('../../utils/objectUtils');
|
|
5
7
|
const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
|
|
6
8
|
const { getTransformers } = require('../transformUtils');
|
|
@@ -58,12 +60,13 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
58
60
|
// Generate artificial draft fields for entities/views if requested, ignore if not part of a service
|
|
59
61
|
if (def.kind === 'entity' && def['@odata.draft.enabled'] && isArtifactInSomeService(defName, services))
|
|
60
62
|
generateDraftForOdata(def, defName, def);
|
|
61
|
-
|
|
62
|
-
visitedArtifacts[defName] = true;
|
|
63
63
|
}, { skipArtifact: isExternalServiceMember });
|
|
64
64
|
|
|
65
65
|
applyAnnotationsFromExtensions(csn, { override: true, filter: name => filterDict[name] });
|
|
66
|
+
rewriteDollarDraft();
|
|
67
|
+
|
|
66
68
|
return csn;
|
|
69
|
+
|
|
67
70
|
/**
|
|
68
71
|
* Generate all that is required in ODATA for draft enablement of 'artifact' into the artifact,
|
|
69
72
|
* into its transitively reachable composition targets, and into the model.
|
|
@@ -84,6 +87,9 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
84
87
|
artifact.actions && artifact.actions.draftPrepare)
|
|
85
88
|
return;
|
|
86
89
|
|
|
90
|
+
if(!visitedArtifacts[artifactName])
|
|
91
|
+
visitedArtifacts[artifactName] = artifact;
|
|
92
|
+
|
|
87
93
|
const draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
|
|
88
94
|
assignAction(draftPrepare, artifact);
|
|
89
95
|
// Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
|
|
@@ -211,6 +217,48 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
211
217
|
}
|
|
212
218
|
}
|
|
213
219
|
}
|
|
220
|
+
|
|
221
|
+
/*
|
|
222
|
+
* After draft decoration, all visited artifacts are supposed to have the draft state elements
|
|
223
|
+
* Is/HasActiveEntity, HasDraftEntity. Now, $draft.<postfix> (with postfix defined as magic variable
|
|
224
|
+
* in the core compiler builtins) needs to be translated into $self.<postfix>.
|
|
225
|
+
*
|
|
226
|
+
* It has to be processed after the late 'applyAnnotationsFromExtensions' which could also merge in
|
|
227
|
+
* some $draft path expressions.
|
|
228
|
+
*/
|
|
229
|
+
function rewriteDollarDraft() {
|
|
230
|
+
|
|
231
|
+
function $draft2$self(member) {
|
|
232
|
+
Object.keys(member).forEach(pn => {
|
|
233
|
+
if(pn[0] === '@')
|
|
234
|
+
transformExpression(member, pn,{
|
|
235
|
+
ref: (_parent, _prop, xpr, _path) => {
|
|
236
|
+
if(xpr[0] === '$draft') {
|
|
237
|
+
xpr[0] = '$self';
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// entity parameters are not substituted as the EDM param entity is not draft enabled
|
|
245
|
+
Object.entries(visitedArtifacts).forEach(([artName, art]) => {
|
|
246
|
+
$draft2$self(art);
|
|
247
|
+
forEachMemberRecursively(art, $draft2$self,
|
|
248
|
+
[ 'definitions', artName ],
|
|
249
|
+
true, { elementsOnly: true }
|
|
250
|
+
);
|
|
251
|
+
if(art.actions) {
|
|
252
|
+
Object.entries(art.actions).forEach(([actionName, action]) => {
|
|
253
|
+
$draft2$self(action);
|
|
254
|
+
forEachMemberRecursively(action, $draft2$self,
|
|
255
|
+
[ 'definitions', artName, 'actions', actionName ]);
|
|
256
|
+
if(action.returns)
|
|
257
|
+
$draft2$self(action.returns);
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
}
|
|
214
262
|
}
|
|
215
263
|
|
|
216
264
|
module.exports = generateDrafts;
|
|
@@ -179,9 +179,10 @@ function remapODataAnnotations( csn ) {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
return {
|
|
182
|
-
elements: (parent, prop, elements, path) => {
|
|
182
|
+
elements: (parent, prop, elements, path, _parentParent, _dummy, context) => {
|
|
183
183
|
const artifact = csn.definitions[path[1]];
|
|
184
|
-
|
|
184
|
+
// Don't process bound actions, as they are still structured
|
|
185
|
+
if (artifact?.kind === 'entity' && !context.$in_actions) {
|
|
185
186
|
for (const elementName in elements)
|
|
186
187
|
remapAnnotationsOnElement(artifact, elements[elementName]);
|
|
187
188
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
forEachDefinition, forEachMemberRecursively, findAnnotationExpression, applyTransformationsOnNonDictionary, transformExpression,
|
|
5
|
+
} = require('../../model/csnUtils');
|
|
6
|
+
const { getStructStepsFlattener } = require('../db/flattening');
|
|
7
|
+
const { setProp } = require('../../base/model');
|
|
8
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param csn
|
|
14
|
+
* @param options
|
|
15
|
+
* @param csnUtils
|
|
16
|
+
* @param messageFunctions
|
|
17
|
+
*/
|
|
18
|
+
function flattenRefs( csn, options, csnUtils, messageFunctions ) {
|
|
19
|
+
const cleanup = [];
|
|
20
|
+
forEachDefinition(csn, (artifact) => {
|
|
21
|
+
if (artifact.elements) {
|
|
22
|
+
const stack = [ { prefix: [], elements: artifact.elements } ];
|
|
23
|
+
while (stack.length > 0) {
|
|
24
|
+
const { prefix, elements } = stack.pop();
|
|
25
|
+
forEach(elements, (elementName, element) => {
|
|
26
|
+
if (element.elements)
|
|
27
|
+
stack.push({ prefix: prefix.concat(elementName), elements: element.elements });
|
|
28
|
+
|
|
29
|
+
const absolutifier = absolutifyPaths(prefix, cleanup);
|
|
30
|
+
// Absolutify paths in on-conditions
|
|
31
|
+
if (element.on) {
|
|
32
|
+
transformExpression(element, 'on', {
|
|
33
|
+
ref: absolutifier,
|
|
34
|
+
}, []);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Absolutify paths in annotation expressions
|
|
38
|
+
Object.keys(element)
|
|
39
|
+
.filter(pn => findAnnotationExpression(element, pn))
|
|
40
|
+
.forEach((anno) => {
|
|
41
|
+
applyTransformationsOnNonDictionary(element, anno, {
|
|
42
|
+
xpr: (parent, prop) => {
|
|
43
|
+
transformExpression(parent, prop, {
|
|
44
|
+
ref: absolutifier,
|
|
45
|
+
}, []);
|
|
46
|
+
},
|
|
47
|
+
}, {}, []);
|
|
48
|
+
if (element[anno].ref)
|
|
49
|
+
absolutifier(element[anno], 'ref', element[anno].ref);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
const adaptRefs = [];
|
|
58
|
+
const resolved = new WeakMap();
|
|
59
|
+
const refFlattener = getStructStepsFlattener(csn, options, messageFunctions, resolved, '_', adaptRefs);
|
|
60
|
+
|
|
61
|
+
forEachDefinition(csn, (def, defName) => {
|
|
62
|
+
if (def.kind === 'entity') {
|
|
63
|
+
applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, { processAnnotations: true, skipDict: { actions: 1 } }, [ 'definitions' ]);
|
|
64
|
+
|
|
65
|
+
adaptRefs.forEach(fn => fn());
|
|
66
|
+
adaptRefs.length = 0;
|
|
67
|
+
|
|
68
|
+
// explicit binding parameter of bound action
|
|
69
|
+
if (def.actions) {
|
|
70
|
+
const special$self = !csn?.definitions?.$self && '$self';
|
|
71
|
+
Object.entries(def.actions).forEach(([ an, a ]) => {
|
|
72
|
+
if (a.params) {
|
|
73
|
+
const params = Object.entries(a.params);
|
|
74
|
+
const firstParam = params[0][1];
|
|
75
|
+
const type = firstParam?.items?.type || firstParam?.type;
|
|
76
|
+
if (type === special$self) {
|
|
77
|
+
const bindingParamName = params[0][0];
|
|
78
|
+
const markBindingParam = {
|
|
79
|
+
ref: (parent, prop, xpr) => {
|
|
80
|
+
if ((xpr[0].id || xpr[0]) === bindingParamName)
|
|
81
|
+
setProp(parent, '$bparam', true);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
Object.keys(a)
|
|
86
|
+
.filter(pn => findAnnotationExpression(a, pn))
|
|
87
|
+
.forEach((pn) => {
|
|
88
|
+
transformExpression(a, pn, [ markBindingParam, refFlattener ], [ 'definitions', defName, 'actions', an ]);
|
|
89
|
+
adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
|
|
90
|
+
adaptRefs.length = 0;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
forEachMemberRecursively(a, (member, memberName, prop, path) => {
|
|
95
|
+
Object.keys(member).filter(pn => findAnnotationExpression(member, pn)).forEach((pn) => {
|
|
96
|
+
transformExpression(member, pn, [ markBindingParam, refFlattener ], path);
|
|
97
|
+
adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
|
|
98
|
+
adaptRefs.length = 0;
|
|
99
|
+
});
|
|
100
|
+
}, [ 'definitions', defName, 'actions', an ]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, { processAnnotations: false }, [ 'definitions' ]);
|
|
108
|
+
adaptRefs.forEach(fn => fn());
|
|
109
|
+
adaptRefs.length = 0;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
cleanup.forEach((obj) => {
|
|
114
|
+
if (obj.ref && obj.ref[0] === '$self')
|
|
115
|
+
obj.ref.shift();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function absolutifyPaths(prefix, cleanup) {
|
|
120
|
+
return function absolutify(_parent, _prop, ref) {
|
|
121
|
+
if (ref[0].id || ref[0] !== '$self' && ref[0] !== '$projection' && !ref[0].startsWith('$') && !_parent.param) {
|
|
122
|
+
_parent.ref = [ '$self', ...prefix, ...ref ];
|
|
123
|
+
cleanup.push(_parent);
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
flattenRefs,
|
|
135
|
+
};
|
|
@@ -4,6 +4,7 @@ const {
|
|
|
4
4
|
getUtils, isAspect, mergeTransformers, applyTransformations,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const transformUtils = require('../transformUtils');
|
|
7
|
+
const effectiveFlattening = require('./flattening');
|
|
7
8
|
const flattening = require('../db/flattening');
|
|
8
9
|
const types = require('./types');
|
|
9
10
|
// const { addLocalizationViews } = require('../../transform/localized');
|
|
@@ -11,7 +12,6 @@ const validate = require('../../checks/validator');
|
|
|
11
12
|
const expansion = require('../db/expansion');
|
|
12
13
|
const queries = require('./queries');
|
|
13
14
|
const associations = require('./associations');
|
|
14
|
-
const generateDrafts = require('../draft/db');
|
|
15
15
|
const handleExists = require('../db/transformExists');
|
|
16
16
|
const misc = require('./misc');
|
|
17
17
|
const annotations = require('./annotations');
|
|
@@ -66,8 +66,9 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
66
66
|
// Remove properties attached by validator - they do not "grow" as the model grows.
|
|
67
67
|
cleanup();
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
|
|
70
|
+
effectiveFlattening.flattenRefs(csn, options, csnUtils, messageFunctions);
|
|
71
|
+
flattening.flattenElements(csn, options, messageFunctions, '_', { skipDict: { actions: true } });
|
|
71
72
|
|
|
72
73
|
resolveTypesInActionsAfterFlattening();
|
|
73
74
|
|
|
@@ -77,9 +78,8 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
77
78
|
processCalculatedElementsInEntities(csn);
|
|
78
79
|
associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
|
|
79
80
|
associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
applyTransformations(csn, transformers, [], { skipIgnore: false });
|
|
81
|
+
const transformers = mergeTransformers([ options.addCdsPersistenceName ? misc.attachPersistenceName(csn, options, csnUtils) : {}, options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
|
|
82
|
+
applyTransformations(csn, transformers, [], { skipIgnore: false, processAnnotations: true });
|
|
83
83
|
|
|
84
84
|
if (!options.resolveProjections)
|
|
85
85
|
redoProjections.forEach(fn => fn());
|