@sap/cds-compiler 4.8.0 → 4.9.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.
- package/CHANGELOG.md +29 -4
- package/bin/cds_remove_invalid_whitespace.js +135 -0
- package/bin/cds_update_annotations.js +180 -0
- package/bin/cds_update_identifiers.js +3 -4
- package/bin/cdsc.js +14 -1
- package/doc/CHANGELOG_BETA.md +19 -0
- package/lib/api/main.js +59 -24
- package/lib/api/options.js +12 -1
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +27 -0
- package/lib/base/message-registry.js +32 -19
- package/lib/base/messages.js +50 -19
- package/lib/base/model.js +4 -5
- package/lib/checks/actionsFunctions.js +2 -2
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +3 -2
- package/lib/checks/validator.js +2 -34
- package/lib/compiler/assert-consistency.js +8 -2
- package/lib/compiler/checks.js +44 -18
- package/lib/compiler/define.js +34 -22
- package/lib/compiler/extend.js +33 -10
- package/lib/compiler/index.js +0 -1
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/propagator.js +21 -18
- package/lib/compiler/resolve.js +44 -28
- package/lib/compiler/shared.js +60 -20
- package/lib/compiler/tweak-assocs.js +13 -88
- package/lib/compiler/xpr-rewrite.js +689 -0
- package/lib/edm/annotations/genericTranslation.js +80 -60
- package/lib/edm/edm.js +4 -4
- package/lib/edm/edmInboundChecks.js +33 -0
- package/lib/edm/edmPreprocessor.js +9 -6
- package/lib/gen/Dictionary.json +129 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1523 -1518
- package/lib/json/from-csn.js +13 -4
- package/lib/json/to-csn.js +10 -11
- package/lib/language/genericAntlrParser.js +14 -6
- package/lib/main.d.ts +67 -14
- package/lib/main.js +1 -0
- package/lib/model/cloneCsn.js +6 -3
- package/lib/model/csnRefs.js +12 -7
- package/lib/model/csnUtils.js +13 -7
- package/lib/model/enrichCsn.js +3 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +33 -34
- package/lib/optionProcessor.js +27 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +3 -1
- package/lib/transform/db/applyTransformations.js +33 -0
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +8 -3
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/temporal.js +6 -3
- package/lib/transform/db/transformExists.js +2 -2
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +6 -8
- package/lib/transform/effective/misc.js +31 -10
- package/lib/transform/forOdata.js +23 -7
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +189 -106
- package/lib/transform/odata/toFinalBaseType.js +1 -1
- package/lib/transform/odata/typesExposure.js +15 -12
- package/lib/transform/parseExpr.js +4 -4
- package/lib/transform/transformUtils.js +40 -37
- package/lib/transform/translateAssocsToJoins.js +47 -47
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
- package/package.json +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/message-explanations.json +1 -0
- package/bin/.eslintrc.json +0 -17
- package/lib/api/.eslintrc.json +0 -37
- package/lib/checks/.eslintrc.json +0 -31
- package/lib/compiler/.eslintrc.json +0 -8
- package/lib/edm/.eslintrc.json +0 -46
- package/lib/inspect/.eslintrc.json +0 -4
- package/lib/json/.eslintrc.json +0 -4
- package/lib/language/.eslintrc.json +0 -4
- package/lib/model/.eslintrc.json +0 -13
- package/lib/modelCompare/utils/.eslintrc.json +0 -22
- package/lib/render/.eslintrc.json +0 -22
- package/lib/transform/.eslintrc.json +0 -13
- package/lib/transform/db/.eslintrc.json +0 -41
- package/lib/transform/draft/.eslintrc.json +0 -4
- package/lib/transform/effective/.eslintrc.json +0 -4
- package/lib/transform/universalCsn/.eslintrc.json +0 -37
- package/lib/utils/.eslintrc.json +0 -7
|
@@ -162,14 +162,14 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
162
162
|
// Note that this must happen after struct flattening(flattenStructuredElement) - both fot elements and foreign keys.
|
|
163
163
|
// Return the newly generated foreign key element.
|
|
164
164
|
function createForeignKeyElement(assoc, assocName, foreignKey, artifact, artifactName, path) {
|
|
165
|
-
|
|
165
|
+
const result = {};
|
|
166
166
|
|
|
167
167
|
// FIXME: Duplicate code (postfix is added herein, can this be optimized?)
|
|
168
168
|
// Assemble foreign key element name from assoc name, '_' and foreign key name/alias
|
|
169
|
-
|
|
169
|
+
const foreignKeyElementName = `${assocName.replace(/\./g, pathDelimiter)}${pathDelimiter}${foreignKey.as || foreignKey.ref.join(pathDelimiter)}`;
|
|
170
170
|
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
const fkArtifact = inspectRef(path).art;
|
|
173
173
|
newForeignKey(fkArtifact, foreignKeyElementName);
|
|
174
174
|
|
|
175
175
|
function processAssociationOrComposition(fkArtifact,foreignKeyElementName) {
|
|
@@ -188,7 +188,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
const foreignKeyElement = createRealFK(fkArtifact, assoc, assocName, foreignKey, path, foreignKeyElementName);
|
|
192
192
|
|
|
193
193
|
// FIXME: must this code go into createRealFK?
|
|
194
194
|
// Not present in getForeignKeyArtifact
|
|
@@ -229,7 +229,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
229
229
|
// length: 42 },
|
|
230
230
|
// }
|
|
231
231
|
function flattenStructuredElement(elem, elemName, parentElementPath=[], pathInCsn=[]) {
|
|
232
|
-
|
|
232
|
+
const elementPath=parentElementPath.concat(elemName); // elementPath contains only element names without the csn structure node names
|
|
233
233
|
// in case the element is of user defined type => take the definition of the type
|
|
234
234
|
// for type of 'x' -> elem.type is an object, not a string -> use directly
|
|
235
235
|
let elemType;
|
|
@@ -241,7 +241,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
241
241
|
// Collect all child elements (recursively) into 'result'
|
|
242
242
|
// TODO: Do not report collisions in the generated elements here, but instead
|
|
243
243
|
// leave that work to the receiver of this result
|
|
244
|
-
|
|
244
|
+
const result = Object.create(null);
|
|
245
245
|
const addGeneratedFlattenedElement = (e, eName) => {
|
|
246
246
|
if (result[eName])
|
|
247
247
|
error('name-duplicate-element', pathInCsn, { '#': 'flatten-element-gen', name: eName })
|
|
@@ -251,10 +251,10 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
251
251
|
forEach(struct, (childName, childElem) => {
|
|
252
252
|
if (isStructured(childElem)) {
|
|
253
253
|
// Descend recursively into structured children
|
|
254
|
-
|
|
255
|
-
for (
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
const grandChildElems = flattenStructuredElement(childElem, childName, elementPath, pathInCsn.concat('elements',childName));
|
|
255
|
+
for (const grandChildName in grandChildElems) {
|
|
256
|
+
const flatElemName = elemName + pathDelimiter + grandChildName;
|
|
257
|
+
const flatElem = grandChildElems[grandChildName];
|
|
258
258
|
addGeneratedFlattenedElement(flatElem, flatElemName);
|
|
259
259
|
// TODO: check with values. In CSN such elements have only "@Core.Computed": true
|
|
260
260
|
// If the original element had a value, construct one for the flattened element
|
|
@@ -265,8 +265,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
265
265
|
}
|
|
266
266
|
} else {
|
|
267
267
|
// Primitive child - clone it and restore its cross references
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
const flatElemName = elemName + pathDelimiter + childName;
|
|
269
|
+
const flatElem = cloneCsnNonDict(childElem, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
|
|
270
270
|
// Don't take over notNull from leaf elements
|
|
271
271
|
delete flatElem.notNull;
|
|
272
272
|
setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));
|
|
@@ -302,7 +302,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
302
302
|
// 'localized' is needed for OData
|
|
303
303
|
if(options.toOdata)
|
|
304
304
|
props.push('localized');
|
|
305
|
-
for (
|
|
305
|
+
for (const p of props) {
|
|
306
306
|
if (elem[p]) {
|
|
307
307
|
flatElem[p] = elem[p];
|
|
308
308
|
}
|
|
@@ -324,9 +324,11 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
324
324
|
* @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
|
|
325
325
|
* @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
|
|
326
326
|
* @param {bool} [suspend] suspend flattening by caller until association path step
|
|
327
|
+
* @param {int} [suspendPos] suspend if starting pos is lower or equal to suspendPos and suspend is true
|
|
328
|
+
* @param {bool} [revokeAtSuspendPos] revoke suspension after suspendPos (binding parameter path use case)
|
|
327
329
|
* @returns {string[]}
|
|
328
330
|
*/
|
|
329
|
-
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0) {
|
|
331
|
+
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0, revokeAtSuspendPos=false) {
|
|
330
332
|
// Refs of length 1 cannot contain steps - no need to check
|
|
331
333
|
if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {
|
|
332
334
|
return ref;
|
|
@@ -368,9 +370,10 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
368
370
|
}
|
|
369
371
|
else {
|
|
370
372
|
result.push(ref[i]);
|
|
373
|
+
suspend ||= !!art('items');
|
|
371
374
|
}
|
|
372
375
|
// revoke items suspension for next assoc step
|
|
373
|
-
if(suspend && art('target'))
|
|
376
|
+
if(suspend && art('target') || (revokeAtSuspendPos && i === suspendPos))
|
|
374
377
|
suspend = false;
|
|
375
378
|
|
|
376
379
|
flattenStep = !links[i].art?.kind &&
|
|
@@ -471,11 +474,11 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
471
474
|
// Add the created projection to the model and complain if artifact already exists.
|
|
472
475
|
// Used by Draft generation
|
|
473
476
|
function createExposingProjection(art, artName, projectionId, service) {
|
|
474
|
-
|
|
477
|
+
const projectionAbsoluteName = `${service}.${projectionId}`;
|
|
475
478
|
// Create elements matching the artifact's elements
|
|
476
|
-
|
|
479
|
+
const elements = Object.create(null);
|
|
477
480
|
art.elements && Object.entries(art.elements).forEach(([elemName, artElem]) => {
|
|
478
|
-
|
|
481
|
+
const elem = Object.assign({}, artElem);
|
|
479
482
|
// Transfer xrefs, that are redirected to the projection
|
|
480
483
|
// TODO: shall we remove the transferred elements from the original?
|
|
481
484
|
// if (artElem._xref) {
|
|
@@ -485,7 +488,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
485
488
|
elements[elemName] = elem;
|
|
486
489
|
});
|
|
487
490
|
|
|
488
|
-
|
|
491
|
+
const query = {
|
|
489
492
|
'SELECT': {
|
|
490
493
|
'from': {
|
|
491
494
|
'ref': [
|
|
@@ -495,13 +498,13 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
495
498
|
}
|
|
496
499
|
};
|
|
497
500
|
// Assemble the projection itself and add it into the model
|
|
498
|
-
|
|
501
|
+
const projection = {
|
|
499
502
|
'kind': 'entity',
|
|
500
503
|
projection: query.SELECT, // it is important that projection and query refer to the same object!
|
|
501
504
|
elements
|
|
502
505
|
};
|
|
503
506
|
// copy annotations from art to projection
|
|
504
|
-
for (
|
|
507
|
+
for (const a of Object.keys(art).filter(x => x.startsWith('@'))) {
|
|
505
508
|
projection[a] = art[a];
|
|
506
509
|
}
|
|
507
510
|
model.definitions[projectionAbsoluteName] = projection;
|
|
@@ -606,7 +609,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
606
609
|
if (!isBuiltinType(typeName) && !model.definitions[typeName]) {
|
|
607
610
|
throw new ModelError('Expecting valid type name: ' + typeName);
|
|
608
611
|
}
|
|
609
|
-
|
|
612
|
+
const result = {
|
|
610
613
|
[elemName]: {
|
|
611
614
|
type: typeName
|
|
612
615
|
}
|
|
@@ -634,16 +637,16 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
634
637
|
// keys: [{ ref: ['id'] }]
|
|
635
638
|
// } }
|
|
636
639
|
function createAssociationElement(elemName, target, isManaged = false) {
|
|
637
|
-
|
|
638
|
-
|
|
640
|
+
const elem = createScalarElement(elemName, 'cds.Association', false, undefined);
|
|
641
|
+
const assoc = elem[elemName];
|
|
639
642
|
assoc.target = target;
|
|
640
643
|
|
|
641
644
|
if (isManaged) {
|
|
642
645
|
assoc.keys = [];
|
|
643
|
-
|
|
646
|
+
const targetArt = getCsnDef(target);
|
|
644
647
|
targetArt.elements && Object.entries(targetArt.elements).forEach(([keyElemName, keyElem]) => {
|
|
645
648
|
if (keyElem.key) {
|
|
646
|
-
|
|
649
|
+
const foreignKey = createForeignKey(keyElemName, keyElem);
|
|
647
650
|
addForeignKey(foreignKey, assoc);
|
|
648
651
|
}
|
|
649
652
|
});
|
|
@@ -700,7 +703,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
700
703
|
if (!artifact.elements) {
|
|
701
704
|
throw new ModelError('Expecting artifact with elements: ' + JSON.stringify(artifact));
|
|
702
705
|
}
|
|
703
|
-
|
|
706
|
+
const elemName = Object.keys(elem)[0];
|
|
704
707
|
// Element must not exist
|
|
705
708
|
if (artifact.elements[elemName]) {
|
|
706
709
|
let path = null;
|
|
@@ -737,7 +740,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
737
740
|
error(null, path, { name: elementName }, 'Generated element $(NAME) conflicts with existing element');
|
|
738
741
|
}
|
|
739
742
|
|
|
740
|
-
|
|
743
|
+
const result = Object.create(null);
|
|
741
744
|
result[elementName] = {};
|
|
742
745
|
elem && Object.entries(elem).forEach(([prop, value]) => {
|
|
743
746
|
result[elementName][prop] = value;
|
|
@@ -750,13 +753,13 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
750
753
|
// of type name 'paramTypeName'
|
|
751
754
|
function createAction(actionName, returnTypeName = undefined, paramName = undefined, paramTypeName = undefined) {
|
|
752
755
|
// Assemble the action
|
|
753
|
-
|
|
756
|
+
const result = {
|
|
754
757
|
[actionName]: {
|
|
755
758
|
kind: 'action'
|
|
756
759
|
}
|
|
757
760
|
};
|
|
758
761
|
|
|
759
|
-
|
|
762
|
+
const action = result[actionName];
|
|
760
763
|
|
|
761
764
|
if (returnTypeName) {
|
|
762
765
|
if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
|
|
@@ -791,7 +794,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
791
794
|
artifact.actions = Object.create(null);
|
|
792
795
|
}
|
|
793
796
|
|
|
794
|
-
|
|
797
|
+
const actionName = Object.keys(action)[0];
|
|
795
798
|
// Element must not exist
|
|
796
799
|
if (!artifact.actions[actionName]) {
|
|
797
800
|
// Add the action
|
|
@@ -808,7 +811,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
808
811
|
* second field if it has @cds.valid.to. Default value is [] for each field.
|
|
809
812
|
*/
|
|
810
813
|
function extractValidFromToKeyElement(element, path) {
|
|
811
|
-
|
|
814
|
+
const validFroms = [], validTos = [], validKeys = [];
|
|
812
815
|
if (hasAnnotationValue(element, '@cds.valid.from')) {
|
|
813
816
|
validFroms.push({ element, path: [...path] });
|
|
814
817
|
}
|
|
@@ -878,7 +881,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
878
881
|
*/
|
|
879
882
|
function recurseElements(artifact, path, callback) {
|
|
880
883
|
callback(artifact, path);
|
|
881
|
-
|
|
884
|
+
const elements = artifact.elements;
|
|
882
885
|
if (elements) {
|
|
883
886
|
path.push('elements', null);
|
|
884
887
|
forEach(elements, (name, obj) => {
|
|
@@ -892,7 +895,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
892
895
|
|
|
893
896
|
// Rename annotation 'fromName' in 'node' to 'toName' (both names including '@')
|
|
894
897
|
function renameAnnotation(node, fromName, toName) {
|
|
895
|
-
|
|
898
|
+
const annotation = node && node[fromName];
|
|
896
899
|
// Sanity checks
|
|
897
900
|
if (!fromName.startsWith('@')) {
|
|
898
901
|
throw new CompilerAssertion('Annotation name should start with "@": ' + fromName);
|
|
@@ -1039,7 +1042,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
1039
1042
|
if(art) {
|
|
1040
1043
|
if(art && !((art.items && art.items.elements) || art.elements)) {
|
|
1041
1044
|
if(followMgdAssoc && art.target && art.keys) {
|
|
1042
|
-
|
|
1045
|
+
const rc = [];
|
|
1043
1046
|
for(const k of art.keys) {
|
|
1044
1047
|
const nps = { ref: k.ref.map(p => fullRef ? { id: p } : p ) };
|
|
1045
1048
|
setProp(nps, '_art', k._art);
|
|
@@ -1057,7 +1060,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
1057
1060
|
}
|
|
1058
1061
|
const elements = art.items && art.items.elements || art.elements;
|
|
1059
1062
|
if(elements) {
|
|
1060
|
-
|
|
1063
|
+
const rc = []
|
|
1061
1064
|
Object.entries(elements).forEach(([en, elt]) => {
|
|
1062
1065
|
const nps = { ref: [ (fullRef ? { id: en, _art: elt } : en )] };
|
|
1063
1066
|
setProp(nps, '_art', elt);
|
|
@@ -1106,7 +1109,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
1106
1109
|
Flattening stops on all non-structured types.
|
|
1107
1110
|
*/
|
|
1108
1111
|
function expand(expr, location) {
|
|
1109
|
-
|
|
1112
|
+
const rc = [];
|
|
1110
1113
|
for(let i = 0; i < expr.length; i++)
|
|
1111
1114
|
{
|
|
1112
1115
|
if(Array.isArray(expr[i]))
|
|
@@ -29,10 +29,10 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
29
29
|
// Use the effective elements list as columns
|
|
30
30
|
forEachDefinition(model, art => {
|
|
31
31
|
if (art.$queries) {
|
|
32
|
-
for (
|
|
32
|
+
for (const query of art.$queries) {
|
|
33
33
|
query.columns = Object.values(query.elements);
|
|
34
34
|
// TODO: Remove viaAll
|
|
35
|
-
for (
|
|
35
|
+
for (const elemName in query.elements) {
|
|
36
36
|
const elem = query.elements[elemName];
|
|
37
37
|
if (elem.$inferred === '*')
|
|
38
38
|
delete elem.$inferred;
|
|
@@ -172,8 +172,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
172
172
|
// Transform each FROM table path into a join tree and attach the tree to the path object
|
|
173
173
|
function createInnerJoins(fromPathNode, env)
|
|
174
174
|
{
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
const fqat = env.lead.$tableAliases[fromPathNode.name.id].$fqat;
|
|
176
|
+
const joinTree = createJoinTree(env, undefined, fqat, 'inner', '$fqat', undefined);
|
|
177
177
|
|
|
178
178
|
replaceTableAliasInPlace( fromPathNode, joinTree);
|
|
179
179
|
}
|
|
@@ -185,11 +185,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
185
185
|
{
|
|
186
186
|
env.lead = query;
|
|
187
187
|
let joinTree = query.from;
|
|
188
|
-
for(
|
|
188
|
+
for(const tan in query.$tableAliases)
|
|
189
189
|
{
|
|
190
190
|
if(query.$tableAliases[tan].kind !== '$self') // don't drive into $projection/$self tableAlias (yet)
|
|
191
191
|
{
|
|
192
|
-
|
|
192
|
+
const ta = query.$tableAliases[tan];
|
|
193
193
|
joinTree = createJoinTree(env, joinTree, ta.$qat, 'left', '$qat', ta.$QA);
|
|
194
194
|
}
|
|
195
195
|
}
|
|
@@ -207,9 +207,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
207
207
|
*/
|
|
208
208
|
function createQAForFromClauseSubQuery(query, env)
|
|
209
209
|
{
|
|
210
|
-
for (
|
|
210
|
+
for (const taName in query.$tableAliases) {
|
|
211
211
|
if (query.$tableAliases[taName].kind !== '$self') {
|
|
212
|
-
|
|
212
|
+
const ta = query.$tableAliases[taName];
|
|
213
213
|
if(!ta.$QA) {
|
|
214
214
|
let alias = taName;
|
|
215
215
|
if (ta.name.$inferred === '$internal') {
|
|
@@ -306,8 +306,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
306
306
|
if(env.location === 'onCondFrom')
|
|
307
307
|
{
|
|
308
308
|
if(checkPathDictionary(pathNode, env)) {
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
const [ tableAlias, tail ] = constructTableAliasAndTailPath(pathNode.path);
|
|
310
|
+
const pathStr = translateONCondPath(tail).map(ps => ps.id).join(pathDelimiter);
|
|
311
311
|
replaceNodeContent(pathNode,
|
|
312
312
|
constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]));
|
|
313
313
|
}
|
|
@@ -329,11 +329,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
329
329
|
tail = pathNode.path;
|
|
330
330
|
// if tail.length > 1, search bottom up for QA
|
|
331
331
|
// default to rootQA, _parent.$QA has precedence
|
|
332
|
-
|
|
332
|
+
const [QA, ps] = rightMostQA(tail, head._navigation._parent.$QA || head._navigation.$QA);
|
|
333
333
|
if(!QA) {
|
|
334
334
|
error(null, pathNode.$location,
|
|
335
335
|
{ name: pathName(pathNode.path) },
|
|
336
|
-
'
|
|
336
|
+
'Debug me: No QA found for generic path rewriting in $(NAME)')
|
|
337
337
|
return;
|
|
338
338
|
}
|
|
339
339
|
// if the found QA is the mixin QA and if the path length is one,
|
|
@@ -360,7 +360,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
360
360
|
// const revealInternalProperties = require('../model/revealInternalProperties.js');
|
|
361
361
|
// console.log('++++++++ Path tail: ', revealInternalProperties(tail[tail.length-1]._artifact));
|
|
362
362
|
// console.log('******** Flat FKs\n', tail[i]._artifact.$flatSrcFKs.map(f => revealInternalProperties(f._artifact)));
|
|
363
|
-
throw new CompilerAssertion('
|
|
363
|
+
throw new CompilerAssertion('Debug me: No flat FK found for FK rewriting');
|
|
364
364
|
}
|
|
365
365
|
// replace tail path with flattened foreign key including prefix
|
|
366
366
|
tail.splice(i, tail.length, fk);
|
|
@@ -431,7 +431,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
431
431
|
*/
|
|
432
432
|
function createJoinTree(env, joinTree, parentQat, joinType, qatAttribName, lastAssocQA)
|
|
433
433
|
{
|
|
434
|
-
for(
|
|
434
|
+
for(const childQatId in parentQat)
|
|
435
435
|
{
|
|
436
436
|
const childQat = parentQat[childQatId];
|
|
437
437
|
|
|
@@ -454,10 +454,10 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
454
454
|
if(childQat._filter)
|
|
455
455
|
{
|
|
456
456
|
// Filter conditions are unique for each JOIN, they don't need to be copied
|
|
457
|
-
|
|
457
|
+
const filter = childQat._filter;
|
|
458
458
|
rewritePathsInFilterExpression(filter, function(pathNode) {
|
|
459
459
|
return [ /* tableAlias=> */ constructTableAliasPathStep(childQat.$QA),
|
|
460
|
-
/* filterPath=> */ pathNode.path ];
|
|
460
|
+
/* filterPath=> */ pathNode.path ];
|
|
461
461
|
}, env);
|
|
462
462
|
|
|
463
463
|
if(!env.lead.$startFilters)
|
|
@@ -486,28 +486,28 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
486
486
|
|
|
487
487
|
function createJoinQA(joinType, lhs, rhs, assocQAT, assocSourceQA, env)
|
|
488
488
|
{
|
|
489
|
-
|
|
489
|
+
const node = { op: { val: 'join' }, join: { val: joinType }, args: [ lhs, rhs ] };
|
|
490
490
|
const assoc = assocQAT._origin;
|
|
491
491
|
if(isBetaEnabled(options, 'mapAssocToJoinCardinality')) {
|
|
492
492
|
node.cardinality = mapAssocToJoinCardinality(assoc);
|
|
493
493
|
}
|
|
494
494
|
// 'path steps' for the src/tgt table alias
|
|
495
|
-
|
|
496
|
-
|
|
495
|
+
const srcTableAlias = constructTableAliasPathStep(assocSourceQA);
|
|
496
|
+
const tgtTableAlias = constructTableAliasPathStep(assocQAT.$QA);
|
|
497
497
|
|
|
498
498
|
node.on = createOnCondition(assoc, srcTableAlias, tgtTableAlias, options.tenantDiscriminator);
|
|
499
499
|
|
|
500
500
|
if(assocQAT._filter)
|
|
501
501
|
{
|
|
502
502
|
// Filter conditions are unique for each JOIN, they don't need to be copied
|
|
503
|
-
|
|
503
|
+
const filter = assocQAT._filter;
|
|
504
504
|
rewritePathsInFilterExpression(filter, function(pathNode) {
|
|
505
505
|
return [ tgtTableAlias, pathNode.path ];
|
|
506
506
|
}, env);
|
|
507
507
|
|
|
508
508
|
// If toplevel ON cond op is AND add filter condition to the args array,
|
|
509
509
|
// create a new toplevel AND op otherwise
|
|
510
|
-
|
|
510
|
+
const onCond = (Array.isArray(node.on) ? node.on[0] : node.on);
|
|
511
511
|
|
|
512
512
|
if(onCond.op.val === 'and')
|
|
513
513
|
onCond.args.push(parenthesise(filter));
|
|
@@ -567,7 +567,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
567
567
|
// produce the ON condition for a given association
|
|
568
568
|
function createOnCondition(assoc, srcAlias, tgtAlias, compareTenants)
|
|
569
569
|
{
|
|
570
|
-
|
|
570
|
+
const prefixes = [ assoc.name.id ];
|
|
571
571
|
/* This is no art and can be removed once ON cond for published
|
|
572
572
|
and renamed backlink assocs are publicly available. Example:
|
|
573
573
|
|
|
@@ -600,12 +600,12 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
600
600
|
Put all src/tgt path siblings into the EQ term and create the proper path objects
|
|
601
601
|
with the src/tgt table alias path steps in front.
|
|
602
602
|
*/
|
|
603
|
-
|
|
603
|
+
const args = compareTenants && addTenantComparison(assoc) || [];
|
|
604
604
|
for(let i = 0; i < assoc.$flatSrcFKs.length; i++)
|
|
605
605
|
{
|
|
606
606
|
args.push({op: {val: '=' },
|
|
607
607
|
args: [ constructPathNode( [ srcAlias, prefixFK(assoc.$elementPrefix, assoc.$flatSrcFKs[i]) ] ),
|
|
608
|
-
constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ] });
|
|
608
|
+
constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ] });
|
|
609
609
|
}
|
|
610
610
|
return parenthesise((args.length > 1 ? { op: { val: 'and' }, args: [ ...args.map(parenthesise) ] } : args[0] ));
|
|
611
611
|
}
|
|
@@ -761,7 +761,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
761
761
|
// If this is an ordinary expression, clone it and mangle its arguments
|
|
762
762
|
// this will substitute multiple backlink conditions ($self = ... AND $self = ...AND ...)
|
|
763
763
|
if(expr.op) {
|
|
764
|
-
|
|
764
|
+
const x = clone(expr);
|
|
765
765
|
if(expr.args)
|
|
766
766
|
x.args = expr.args.map(cloneOnCondition);
|
|
767
767
|
return x;
|
|
@@ -845,7 +845,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
845
845
|
$projection must be removed from $projection.yid (get's aliased with the mixinAssocQAT.$QA)
|
|
846
846
|
*/
|
|
847
847
|
if(env.assocStack.length < 2) {
|
|
848
|
-
|
|
848
|
+
const value = env.lead.elements[path[0].id].value;
|
|
849
849
|
/*
|
|
850
850
|
If the value is an expression in the select block, return the unmodified
|
|
851
851
|
expression. rewriteGenericPaths will check and rewrite these paths later
|
|
@@ -957,7 +957,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
957
957
|
if(head.id === env.assocStack.id()) {
|
|
958
958
|
// source side from view point of view (target side from forward point of view)
|
|
959
959
|
path = tail; // pop assoc step
|
|
960
|
-
|
|
960
|
+
const elt = env.lead._combined[path[0].id];
|
|
961
961
|
|
|
962
962
|
if(elt) {
|
|
963
963
|
if(Array.isArray(elt)) {
|
|
@@ -1008,7 +1008,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1008
1008
|
path = translateONCondPath(path, !hasDollarSelfPrefix ? assoc.$elementPrefix : undefined);
|
|
1009
1009
|
}
|
|
1010
1010
|
}
|
|
1011
|
-
|
|
1011
|
+
const pathStr = path.map(ps => ps.id).join(pathDelimiter);
|
|
1012
1012
|
return constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]);
|
|
1013
1013
|
}
|
|
1014
1014
|
|
|
@@ -1209,11 +1209,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1209
1209
|
*/
|
|
1210
1210
|
function constructTableAliasAndTailPath(path, navigation=undefined)
|
|
1211
1211
|
{
|
|
1212
|
-
|
|
1212
|
+
const [head, ...tail] = path;
|
|
1213
1213
|
if(navigation === undefined)
|
|
1214
1214
|
navigation = head._navigation;
|
|
1215
1215
|
|
|
1216
|
-
|
|
1216
|
+
const QA = navigation.$QA || navigation._parent.$QA;
|
|
1217
1217
|
|
|
1218
1218
|
// First path step is table alias, use and pop it off
|
|
1219
1219
|
if(navigation.$QA && tail.length > 0)
|
|
@@ -1251,7 +1251,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1251
1251
|
{
|
|
1252
1252
|
if(!pathStr)
|
|
1253
1253
|
pathStr = '';
|
|
1254
|
-
|
|
1254
|
+
const assocStep = path.find(ps => {
|
|
1255
1255
|
if(pathStr.length > 0)
|
|
1256
1256
|
pathStr += pathDelimiter;
|
|
1257
1257
|
pathStr += ps.id;
|
|
@@ -1303,7 +1303,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1303
1303
|
pathStr += fk.name.id;
|
|
1304
1304
|
}
|
|
1305
1305
|
|
|
1306
|
-
|
|
1306
|
+
const tail = path.slice(path.indexOf(fkPs)+1);
|
|
1307
1307
|
// If foreign key is an association itself, apply substituteFKAliasForPath on tail
|
|
1308
1308
|
if(fk && fk.targetElement._artifact.target && tail.length)
|
|
1309
1309
|
return substituteFKAliasForPath(fk.targetElement, tail, pathStr);
|
|
@@ -1377,7 +1377,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1377
1377
|
if(ps._artifact && ps._artifact.target)
|
|
1378
1378
|
{
|
|
1379
1379
|
// if this is not the last path step, complain
|
|
1380
|
-
|
|
1380
|
+
const la1 = pathDict.path[pathDict.path.indexOf(ps)+1];
|
|
1381
1381
|
if(la1) {
|
|
1382
1382
|
if(ps._artifact.on)
|
|
1383
1383
|
{
|
|
@@ -1438,7 +1438,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1438
1438
|
*/
|
|
1439
1439
|
function mergePathIntoQAT(pathDict, env)
|
|
1440
1440
|
{
|
|
1441
|
-
|
|
1441
|
+
const path = pathDict.path;
|
|
1442
1442
|
|
|
1443
1443
|
if(path.length === 0)
|
|
1444
1444
|
return;
|
|
@@ -1505,7 +1505,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1505
1505
|
if(qatParent == undefined)
|
|
1506
1506
|
throw new CompilerAssertion('table alias/qathost not found for path: ' + pathAsStr(path));
|
|
1507
1507
|
|
|
1508
|
-
|
|
1508
|
+
const rootQat = qatParent;
|
|
1509
1509
|
|
|
1510
1510
|
// Create the very first QAT if it doesn't exist
|
|
1511
1511
|
// (filter condition for table alias prefix not allowed)
|
|
@@ -1526,7 +1526,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1526
1526
|
}
|
|
1527
1527
|
if(pathStep.args) {
|
|
1528
1528
|
// sort named arguments
|
|
1529
|
-
|
|
1529
|
+
const sortedNamedArgs = Object.create(null);
|
|
1530
1530
|
Object.keys(pathStep.args).sort().forEach(p => {
|
|
1531
1531
|
sortedNamedArgs[p] = compactExpr(pathStep.args[p]);
|
|
1532
1532
|
})
|
|
@@ -1687,10 +1687,10 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1687
1687
|
// eslint-disable-next-line no-unused-vars
|
|
1688
1688
|
function printPath(pathDict, env)
|
|
1689
1689
|
{
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1690
|
+
const alias = (pathDict.name && pathDict.name.id) || '<undefined>'
|
|
1691
|
+
const path = pathDict.path;
|
|
1692
|
+
const s = pathAsStr(path, '"');
|
|
1693
|
+
const me = env.lead && (env.lead.name.id || env.lead.op);
|
|
1694
1694
|
// eslint-disable-next-line no-console
|
|
1695
1695
|
console.log(me + ': ' + env.location + ': ' + s + ' alias: ' + alias);
|
|
1696
1696
|
}
|
|
@@ -1708,9 +1708,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1708
1708
|
else
|
|
1709
1709
|
newObj = {};
|
|
1710
1710
|
|
|
1711
|
-
|
|
1712
|
-
for (
|
|
1713
|
-
|
|
1711
|
+
const props = Object.getOwnPropertyNames(obj); // clone own properties only, not inherited ones
|
|
1712
|
+
for (const p of props) {
|
|
1713
|
+
const pd = Object.getOwnPropertyDescriptor(obj, p);
|
|
1714
1714
|
if (pd && pd.enumerable === false)
|
|
1715
1715
|
{
|
|
1716
1716
|
pd.value = obj[p]; // don't copy references
|
|
@@ -1740,10 +1740,10 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1740
1740
|
*/
|
|
1741
1741
|
function constructPathNode(pathSteps, alias, rewritten=true)
|
|
1742
1742
|
{
|
|
1743
|
-
|
|
1743
|
+
const node = {
|
|
1744
1744
|
$rewritten: rewritten,
|
|
1745
1745
|
path : pathSteps.map(p => {
|
|
1746
|
-
|
|
1746
|
+
const o = {};
|
|
1747
1747
|
Object.keys(p).forEach(k => {
|
|
1748
1748
|
if(!(rewritten && ['_'].includes(k[0])))
|
|
1749
1749
|
o[k] = p[k];
|
|
@@ -1784,7 +1784,7 @@ function walkQuery(query, env)
|
|
|
1784
1784
|
env.location = 'select';
|
|
1785
1785
|
if(env.walkover[env.location])
|
|
1786
1786
|
{
|
|
1787
|
-
for(
|
|
1787
|
+
for(const alias in query.elements)
|
|
1788
1788
|
walk(query.elements[alias].value, env);
|
|
1789
1789
|
|
|
1790
1790
|
env.location = 'Where';
|
|
@@ -1809,7 +1809,7 @@ function walkQuery(query, env)
|
|
|
1809
1809
|
|
|
1810
1810
|
function walkFrom(fromBlock)
|
|
1811
1811
|
{
|
|
1812
|
-
|
|
1812
|
+
const aliases = [];
|
|
1813
1813
|
env.position = fromBlock;
|
|
1814
1814
|
if(fromBlock)
|
|
1815
1815
|
{
|
|
@@ -9,7 +9,7 @@ const {
|
|
|
9
9
|
applyTransformations,
|
|
10
10
|
implicitAs,
|
|
11
11
|
} = require('../../model/csnUtils');
|
|
12
|
-
const { isBuiltinType } = require('../../base/builtins');
|
|
12
|
+
const { isBuiltinType, propagationRules } = require('../../base/builtins');
|
|
13
13
|
const {
|
|
14
14
|
forEachValue, forEach,
|
|
15
15
|
} = require('../../utils/objectUtils');
|
|
@@ -29,24 +29,10 @@ module.exports = (csn, options) => {
|
|
|
29
29
|
} = csnUtils;
|
|
30
30
|
// Properties on definition level that we treat specially.
|
|
31
31
|
const definitionPropagationRules = {
|
|
32
|
-
|
|
33
|
-
'@cds.external': skip,
|
|
34
|
-
'@fiori.draft.enabled': onlyViaArtifact,
|
|
32
|
+
__proto__: null,
|
|
35
33
|
'@': nullStopsPropagation,
|
|
36
34
|
// Example: `type E : F;` does not have `elements`, but they are required for e.g. OData.
|
|
37
35
|
elements: onlyTypeDef,
|
|
38
|
-
'@cds.persistence.exists': skip,
|
|
39
|
-
'@cds.persistence.table': skip,
|
|
40
|
-
'@cds.persistence.calcview': skip,
|
|
41
|
-
'@cds.persistence.udf': skip,
|
|
42
|
-
'@cds.persistence.skip': notWithPersistenceTable,
|
|
43
|
-
'@sql.append': skip,
|
|
44
|
-
'@sql.prepend': skip,
|
|
45
|
-
'@sql.replace': skip,
|
|
46
|
-
'@Analytics.hidden': skip,
|
|
47
|
-
'@Analytics.visible': skip,
|
|
48
|
-
'@cds.autoexposed': skip,
|
|
49
|
-
'@cds.redirection.target': skip,
|
|
50
36
|
type: always,
|
|
51
37
|
doc: nullStopsPropagation,
|
|
52
38
|
length: always,
|
|
@@ -65,6 +51,16 @@ module.exports = (csn, options) => {
|
|
|
65
51
|
keys: always,
|
|
66
52
|
};
|
|
67
53
|
|
|
54
|
+
const ruleToFunction = {
|
|
55
|
+
__proto__: null,
|
|
56
|
+
never: skip,
|
|
57
|
+
onlyViaArtifact,
|
|
58
|
+
notWithPersistenceTable,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
for (const rule in propagationRules)
|
|
62
|
+
definitionPropagationRules[rule] = ruleToFunction[propagationRules[rule]];
|
|
63
|
+
|
|
68
64
|
// Properties on member level that we treat specially
|
|
69
65
|
const memberPropagationRules = {
|
|
70
66
|
key: skip,
|