@sap/cds-compiler 4.2.2 → 4.3.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 +32 -0
- package/bin/cdsc.js +8 -0
- package/bin/cdshi.js +3 -3
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +19 -0
- package/lib/base/location.js +16 -0
- package/lib/base/message-registry.js +47 -16
- package/lib/base/messages.js +49 -38
- package/lib/base/model.js +2 -1
- package/lib/checks/checkPathsInStoredCalcElement.js +83 -0
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +71 -0
- package/lib/checks/existsMustEndInAssoc.js +27 -0
- package/lib/checks/onConditions.js +47 -1
- package/lib/checks/validator.js +10 -1
- package/lib/compiler/assert-consistency.js +23 -15
- package/lib/compiler/base.js +31 -14
- package/lib/compiler/builtins.js +21 -20
- package/lib/compiler/checks.js +36 -49
- package/lib/compiler/define.js +71 -91
- package/lib/compiler/extend.js +27 -25
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +67 -87
- package/lib/compiler/kick-start.js +7 -5
- package/lib/compiler/populate.js +32 -30
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/resolve.js +29 -25
- package/lib/compiler/shared.js +57 -31
- package/lib/compiler/tweak-assocs.js +203 -22
- package/lib/compiler/utils.js +0 -18
- package/lib/gen/Dictionary.json +10 -4
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/languageParser.js +3 -3
- package/lib/inspect/inspectPropagation.js +2 -1
- package/lib/json/from-csn.js +63 -28
- package/lib/json/to-csn.js +23 -13
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/errorStrategy.js +5 -1
- package/lib/language/genericAntlrParser.js +67 -61
- package/lib/main.d.ts +26 -1
- package/lib/main.js +2 -1
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +28 -0
- package/lib/model/revealInternalProperties.js +3 -9
- package/lib/optionProcessor.js +17 -1
- package/lib/render/toCdl.js +1 -1
- package/lib/transform/db/associations.js +3 -4
- package/lib/transform/db/backlinks.js +293 -0
- package/lib/transform/db/expansion.js +18 -7
- package/lib/transform/db/flattening.js +3 -2
- package/lib/transform/db/rewriteCalculatedElements.js +1 -67
- package/lib/transform/db/transformExists.js +3 -58
- package/lib/transform/db/views.js +8 -14
- package/lib/transform/effective/.eslintrc.json +4 -0
- package/lib/transform/effective/associations.js +101 -0
- package/lib/transform/effective/main.js +88 -0
- package/lib/transform/effective/misc.js +61 -0
- package/lib/transform/effective/queries.js +42 -0
- package/lib/transform/effective/types.js +121 -0
- package/lib/transform/forRelationalDB.js +12 -235
- package/lib/transform/localized.js +22 -3
- package/lib/transform/parseExpr.js +7 -3
- package/lib/transform/transformUtils.js +5 -22
- package/lib/transform/translateAssocsToJoins.js +42 -38
- package/lib/transform/universalCsn/universalCsnEnricher.js +17 -1
- package/package.json +1 -2
- package/lib/language/language.g4 +0 -3260
|
@@ -4,7 +4,7 @@ const { setProp, isBetaEnabled } = require('../base/model');
|
|
|
4
4
|
const { cloneCsnNonDict,
|
|
5
5
|
forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
|
|
6
6
|
getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
7
|
-
isAspect, walkCsnPath, isPersistedOnDatabase
|
|
7
|
+
isAspect, walkCsnPath, isPersistedOnDatabase
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
9
|
const { makeMessageFunction } = require('../base/messages');
|
|
10
10
|
const transformUtils = require('./transformUtils');
|
|
@@ -29,8 +29,8 @@ const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
|
|
|
29
29
|
const { getViewTransformer, ensureColumnNames } = require('./db/views');
|
|
30
30
|
const cdsPersistence = require('./db/cdsPersistence');
|
|
31
31
|
const temporal = require('./db/temporal');
|
|
32
|
-
const associations = require('./db/associations')
|
|
33
|
-
const
|
|
32
|
+
const associations = require('./db/associations');
|
|
33
|
+
const backlinks = require('./db/backlinks');
|
|
34
34
|
const { getDefaultTypeLengths } = require('../render/utils/common');
|
|
35
35
|
|
|
36
36
|
// By default: Do not process non-entities/views
|
|
@@ -125,12 +125,10 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
125
125
|
/** @type {() => void} */
|
|
126
126
|
let throwWithAnyError;
|
|
127
127
|
// transformUtils
|
|
128
|
-
let addDefaultTypeFacets,
|
|
128
|
+
let addDefaultTypeFacets,
|
|
129
129
|
expandStructsInExpression,
|
|
130
130
|
flattenStructuredElement,
|
|
131
|
-
flattenStructStepsInRef
|
|
132
|
-
isAssociationOperand,
|
|
133
|
-
isDollarSelfOrProjectionOperand;
|
|
131
|
+
flattenStructStepsInRef;
|
|
134
132
|
|
|
135
133
|
bindCsnReference();
|
|
136
134
|
|
|
@@ -158,6 +156,9 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
158
156
|
});
|
|
159
157
|
timetrace.stop('Validate');
|
|
160
158
|
|
|
159
|
+
// exit if validators found errors
|
|
160
|
+
throwWithAnyError();
|
|
161
|
+
|
|
161
162
|
rewriteCalculatedElementsInViews(csn, options, csnUtils, pathDelimiter, messageFunctions);
|
|
162
163
|
|
|
163
164
|
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
|
|
@@ -174,8 +175,6 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
174
175
|
// @ts-ignore
|
|
175
176
|
expandStructsInExpression(csn, { drillRef: true });
|
|
176
177
|
|
|
177
|
-
throwWithAnyError();
|
|
178
|
-
|
|
179
178
|
forEachDefinition(csn, [
|
|
180
179
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
181
180
|
// assoc2join eventually rewrites the table aliases
|
|
@@ -215,6 +214,9 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
215
214
|
flattening.resolveTypeReferences(csn, options, new WeakMap(), pathDelimiter);
|
|
216
215
|
}
|
|
217
216
|
|
|
217
|
+
// With flattening errors, it makes little sense to continue.
|
|
218
|
+
throwWithAnyError();
|
|
219
|
+
|
|
218
220
|
// (010) If requested, translate associations to joins
|
|
219
221
|
if (doA2J)
|
|
220
222
|
handleAssocToJoins();
|
|
@@ -335,7 +337,7 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
335
337
|
// draft shadow entities have crooked '_artifact' links, confusing the backlink processing.
|
|
336
338
|
// But it must also happen after flattenForeignKeys has been called for all artifacts,
|
|
337
339
|
// because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
|
|
338
|
-
forEachDefinition(csn,
|
|
340
|
+
forEachDefinition(csn, backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, pathDelimiter, doA2J));
|
|
339
341
|
|
|
340
342
|
/**
|
|
341
343
|
* Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
|
|
@@ -451,8 +453,6 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
451
453
|
|
|
452
454
|
({ flattenStructuredElement,
|
|
453
455
|
flattenStructStepsInRef,
|
|
454
|
-
isAssociationOperand,
|
|
455
|
-
isDollarSelfOrProjectionOperand,
|
|
456
456
|
addDefaultTypeFacets,
|
|
457
457
|
expandStructsInExpression,
|
|
458
458
|
csnUtils
|
|
@@ -599,28 +599,6 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
|
|
602
|
-
|
|
603
|
-
/**
|
|
604
|
-
* @param {CSN.Artifact} artifact
|
|
605
|
-
* @param {string} artifactName
|
|
606
|
-
*/
|
|
607
|
-
function transformSelfInBacklinks(artifact, artifactName, dummy, path) {
|
|
608
|
-
// Fixme: For toHana mixins must be transformed, for toSql -d hana
|
|
609
|
-
// mixin elements must be transformed, why can't toSql also use mixins?
|
|
610
|
-
if(artifact.kind === 'entity' || artifact.query || (options.forHana && options.sqlMapping === 'hdbcds' && artifact.kind === 'type'))
|
|
611
|
-
doit(artifact.elements, path.concat([ 'elements' ]));
|
|
612
|
-
if (artifact.query && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
613
|
-
doit(artifact.query.SELECT.mixin, path.concat([ 'query', 'SELECT', 'mixin' ]));
|
|
614
|
-
|
|
615
|
-
function doit(dict, subPath) {
|
|
616
|
-
for (const elemName in dict) {
|
|
617
|
-
const elem = dict[elemName];
|
|
618
|
-
if (elem.on && csnUtils.isAssocOrComposition(elem))
|
|
619
|
-
processBacklinkAssoc(elem, elemName, artifact, artifactName, subPath.concat([ elemName, 'on' ]));
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
602
|
/**
|
|
625
603
|
* @param {CSN.Artifact} artifact
|
|
626
604
|
* @param {string} artifactName
|
|
@@ -700,8 +678,6 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
700
678
|
}
|
|
701
679
|
|
|
702
680
|
function handleAssocToJoins() {
|
|
703
|
-
// With flattening errors, it makes little sense to continue.
|
|
704
|
-
throwWithAnyError();
|
|
705
681
|
// the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
|
|
706
682
|
// simply make it invisible and copy it over to the result csn
|
|
707
683
|
forEachDefinition(csn,
|
|
@@ -838,206 +814,7 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
838
814
|
}
|
|
839
815
|
|
|
840
816
|
|
|
841
|
-
// If the association element 'elem' of 'art' is a backlink association, massage its ON-condition
|
|
842
|
-
// (in place) so that it
|
|
843
|
-
// - compares the generated foreign key fields of the corresponding forward
|
|
844
|
-
// association with their respective keys in 'art' (for managed forward associations)
|
|
845
|
-
// - contains the corresponding forward association's ON-condition in "reversed" form,
|
|
846
|
-
// i.e. as seen from 'elem' (for unmanaged associations)
|
|
847
|
-
// Otherwise, do nothing.
|
|
848
|
-
function processBacklinkAssoc(elem, elemName, art, artName, pathToOn) {
|
|
849
|
-
// Don't add braces if it is a single expression (ignoring superfluous braces)
|
|
850
|
-
const multipleExprs = elem.on.filter(x => x !== '(' && x !== ')' ).length > 3;
|
|
851
|
-
/**
|
|
852
|
-
* Process the args
|
|
853
|
-
*
|
|
854
|
-
* @param {Array} xprArgs
|
|
855
|
-
* @param {CSN.Path} path
|
|
856
|
-
* @returns {Array} Array of parsed expression
|
|
857
|
-
*/
|
|
858
|
-
function processExpressionArgs(xprArgs, path) {
|
|
859
|
-
const result = [];
|
|
860
|
-
let i = 0;
|
|
861
|
-
while (i < xprArgs.length) {
|
|
862
|
-
// Only token tripel `<path>, '=', <path>` are of interest here
|
|
863
|
-
if (i < xprArgs.length - 2 && xprArgs[i + 1] === '=') {
|
|
864
|
-
// Check if one side is $self and the other an association
|
|
865
|
-
// (if so, replace all three tokens with the condition generated from the other side, in parentheses)
|
|
866
|
-
if (isDollarSelfOrProjectionOperand(xprArgs[i]) && isAssociationOperand(xprArgs[i + 2], path.concat([ i + 2 ]))) {
|
|
867
|
-
const assoc = csnUtils.inspectRef(path.concat([ i + 2 ])).art;
|
|
868
|
-
if (multipleExprs)
|
|
869
|
-
result.push('(');
|
|
870
|
-
const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
|
|
871
|
-
result.push(...transformDollarSelfComparison(xprArgs[i + 2],
|
|
872
|
-
assoc,
|
|
873
|
-
backlinkName,
|
|
874
|
-
elem, elemName, art, artName, path.concat([ i ])
|
|
875
|
-
));
|
|
876
|
-
if (multipleExprs)
|
|
877
|
-
result.push(')');
|
|
878
|
-
i += 3;
|
|
879
|
-
attachBacklinkInformation(backlinkName);
|
|
880
|
-
}
|
|
881
|
-
else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
|
|
882
|
-
const assoc = csnUtils.inspectRef(path.concat([ i ])).art;
|
|
883
|
-
if (multipleExprs)
|
|
884
|
-
result.push('(');
|
|
885
|
-
const backlinkName = xprArgs[i].ref[xprArgs[i].ref.length - 1];
|
|
886
|
-
result.push(...transformDollarSelfComparison(xprArgs[i], assoc, backlinkName, elem, elemName, art, artName, path.concat([ i + 2 ])));
|
|
887
|
-
if (multipleExprs)
|
|
888
|
-
result.push(')');
|
|
889
|
-
i += 3;
|
|
890
|
-
attachBacklinkInformation(backlinkName);
|
|
891
|
-
}
|
|
892
|
-
// Otherwise take one (!) token unchanged
|
|
893
|
-
else {
|
|
894
|
-
result.push(xprArgs[i]);
|
|
895
|
-
i++;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
// Process subexpressions - but keep them as subexpressions
|
|
899
|
-
else if(xprArgs[i].xpr){
|
|
900
|
-
result.push({xpr: processExpressionArgs(xprArgs[i].xpr, path.concat([i, 'xpr']))});
|
|
901
|
-
i++;
|
|
902
|
-
}
|
|
903
|
-
// Take all other tokens unchanged
|
|
904
|
-
else {
|
|
905
|
-
result.push(xprArgs[i]);
|
|
906
|
-
i++;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
return result;
|
|
910
|
-
|
|
911
|
-
/**
|
|
912
|
-
* The knowledge whether an association was an `<up_>` association in a
|
|
913
|
-
* `$self = <comp>.<up_>` comparison, is important for the foreign key constraints.
|
|
914
|
-
* By the time we generate them, such on-conditions are already transformed
|
|
915
|
-
* --> no more `$self` in the on-conditions, that is why we need to remember it here.
|
|
916
|
-
*
|
|
917
|
-
* @param {string} backlinkName name of `<up_>` in a `$self = <comp>.<up_>` comparison
|
|
918
|
-
*/
|
|
919
|
-
function attachBacklinkInformation(backlinkName) {
|
|
920
|
-
if (elem.$selfOnCondition)
|
|
921
|
-
elem.$selfOnCondition.up_.push(backlinkName);
|
|
922
|
-
else {
|
|
923
|
-
setProp(elem, '$selfOnCondition', {
|
|
924
|
-
up_: [backlinkName]
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
elem.on = processExpressionArgs(elem.on, pathToOn);
|
|
931
|
-
|
|
932
|
-
// Return the condition to replace the comparison `<assocOp> = $self` in the ON-condition
|
|
933
|
-
// of element <elem> of artifact 'art'. If there is anything to complain, use location <loc>
|
|
934
|
-
function transformDollarSelfComparison(assocOp, assoc, assocName, elem, elemName, art, artifactName, path) {
|
|
935
|
-
// Check: The forward link <assocOp> must point back to this artifact
|
|
936
|
-
// FIXME: Unfortunately, we can currently only check this for non-views (because when a view selects
|
|
937
|
-
// a backlink association element from an entity, the forward link will point to the entity,
|
|
938
|
-
// not to the view).
|
|
939
|
-
// FIXME: This also means that corresponding key fields should be in the select list etc ...
|
|
940
|
-
if (!art.query && !art.projection && assoc.target && assoc.target !== artifactName)
|
|
941
|
-
error( null, path, { id: '$self', name: artifactName, target: assoc.target },
|
|
942
|
-
'Expected association using $(ID) to point back to $(NAME) but found $(TARGET)' );
|
|
943
|
-
|
|
944
|
-
// Check: The forward link <assocOp> must not contain '$self' in its own ON-condition
|
|
945
|
-
if (assoc.on) {
|
|
946
|
-
const containsDollarSelf = assoc.on.some(isDollarSelfOrProjectionOperand);
|
|
947
|
-
|
|
948
|
-
if (containsDollarSelf)
|
|
949
|
-
error(null, path, { name: '$self' },
|
|
950
|
-
'An association that uses $(NAME) in its ON-condition can\'t be compared to $(NAME)');
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
// Transform comparison of $self to managed association into AND-combined foreign key comparisons
|
|
954
|
-
if (assoc.keys) {
|
|
955
|
-
if(assoc.keys.length)
|
|
956
|
-
return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName);
|
|
957
|
-
else {
|
|
958
|
-
elem.$ignore = true;
|
|
959
|
-
return [];
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// Transform comparison of $self to unmanaged association into "reversed" ON-condition
|
|
964
|
-
else if (assoc.on)
|
|
965
|
-
return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName);
|
|
966
|
-
|
|
967
|
-
throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${ JSON.stringify(elem.on) }`);
|
|
968
|
-
}
|
|
969
817
|
|
|
970
|
-
// For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
|
|
971
|
-
// where <assoc> is a managed association, return a condition comparing the generated
|
|
972
|
-
// foreign key elements <elemName>.<assoc>_<fkey1..n> of <assoc> to the corresponding
|
|
973
|
-
// keys in this artifact.
|
|
974
|
-
// For example, `ON elem.ass = $self` becomes `ON elem.ass_key1 = key1 AND elem.ass_key2 = key2`
|
|
975
|
-
// (assuming that `ass` has the foreign keys `key1` and `key2`)
|
|
976
|
-
function transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, originalAssocName, elemName) {
|
|
977
|
-
const conditions = [];
|
|
978
|
-
// if the element was structured then it was flattened => change of the delimiter from '.' to '_'
|
|
979
|
-
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
|
|
980
|
-
const assocName = originalAssocName.replace(/\./g, pathDelimiter);
|
|
981
|
-
elemName = elemName.replace(/\./g, pathDelimiter);
|
|
982
|
-
|
|
983
|
-
assoc.keys.forEach((k) => {
|
|
984
|
-
// Depending on naming conventions, the foreign key may two path steps (hdbcds) or be a single path step with a flattened name (plain, quoted)
|
|
985
|
-
// With to.hdbcds in conjunction with hdbcds naming, we need to NOT use the alias - else we get deployment errors
|
|
986
|
-
const keyName = k.as && doA2J ? [k.as] : k.ref;
|
|
987
|
-
const fKeyPath = !doA2J ? [ assocName, ...keyName ] : [ `${ assocName }${ pathDelimiter }${ keyName[0] }` ];
|
|
988
|
-
// FIXME: _artifact to the args ???
|
|
989
|
-
const a = [
|
|
990
|
-
{
|
|
991
|
-
ref: [ elemName, ...fKeyPath ],
|
|
992
|
-
},
|
|
993
|
-
{ ref: k.ref },
|
|
994
|
-
];
|
|
995
|
-
|
|
996
|
-
conditions.push([ a[0], '=', a[1] ]);
|
|
997
|
-
});
|
|
998
|
-
|
|
999
|
-
return conditions.reduce((prev, current) => {
|
|
1000
|
-
if (prev.length === 0)
|
|
1001
|
-
return [ ...current ];
|
|
1002
|
-
|
|
1003
|
-
return [ ...prev, 'and', ...current ];
|
|
1004
|
-
}, []);
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
|
|
1008
|
-
// where <assoc> is an unmanaged association, return the ON-condition of <assoc> as it would
|
|
1009
|
-
// be written from the perspective of the artifact containing association <elemName>.
|
|
1010
|
-
// For example, `ON elem.ass = $self` becomes `ON a = elem.x AND b = elem.y`
|
|
1011
|
-
// (assuming that `ass` has the ON-condition `ON ass.a = x AND ass.b = y`)
|
|
1012
|
-
function transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, originalAssocName, elemName) {
|
|
1013
|
-
// if the element was structured then it may have been flattened => change of the delimiter from '.' to '_'
|
|
1014
|
-
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
|
|
1015
|
-
elemName = elemName.replace(/\./g, pathDelimiter);
|
|
1016
|
-
const assocName = originalAssocName.replace(/\./g, pathDelimiter);
|
|
1017
|
-
// clone the onCond for later use in the path transformation
|
|
1018
|
-
const newOnCond = cloneCsnNonDict(assoc.on, options);
|
|
1019
|
-
applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {
|
|
1020
|
-
ref: (parent, prop, ref) => {
|
|
1021
|
-
if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
|
|
1022
|
-
{
|
|
1023
|
-
ref.shift();
|
|
1024
|
-
} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
|
|
1025
|
-
// We could also have a $self in front of the assoc name - so we would need to shift twice
|
|
1026
|
-
ref.shift();
|
|
1027
|
-
ref.shift();
|
|
1028
|
-
}
|
|
1029
|
-
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
|
|
1030
|
-
ref.unshift(elemName);
|
|
1031
|
-
// if there was a $self identifier in the forwarding association onCond
|
|
1032
|
-
// we do not need it anymore, as we prepended in the previous step the back association's id
|
|
1033
|
-
if (ref[1] === '$self')
|
|
1034
|
-
ref.splice(1, 1);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
});
|
|
1038
|
-
return newOnCond;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
818
|
|
|
1042
819
|
/**
|
|
1043
820
|
* @param {CSN.Artifact} artifact
|
|
@@ -395,8 +395,7 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
395
395
|
}
|
|
396
396
|
if (!art[annoPersistenceSkip] && textsEntity[annoPersistenceSkip]) {
|
|
397
397
|
messageFunctions.message( 'anno-unexpected-localized-skip', artPath,
|
|
398
|
-
{ name: textsName, art: artName, anno: annoPersistenceSkip }
|
|
399
|
-
'Compiler generated entity $(NAME) must not be annotated with $(ANNO) if $(ART) is not skipped' );
|
|
398
|
+
{ name: textsName, art: artName, anno: annoPersistenceSkip } );
|
|
400
399
|
return null;
|
|
401
400
|
}
|
|
402
401
|
|
|
@@ -654,8 +653,28 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
654
653
|
}
|
|
655
654
|
},
|
|
656
655
|
});
|
|
656
|
+
forEachDefinition(csn, checkAnnotationsOnLocalized);
|
|
657
657
|
}
|
|
658
658
|
|
|
659
|
+
/**
|
|
660
|
+
* @param {CSN.Definition} def
|
|
661
|
+
* @param {string} defName
|
|
662
|
+
*/
|
|
663
|
+
function checkAnnotationsOnLocalized(def, defName) {
|
|
664
|
+
const localizedPrefix = 'localized.';
|
|
665
|
+
if (defName.startsWith(localizedPrefix)) {
|
|
666
|
+
const artName = defName.substring(localizedPrefix.length);
|
|
667
|
+
const art = csn.definitions[artName];
|
|
668
|
+
if (def[annoPersistenceSkip] && !art?.[annoPersistenceSkip]) {
|
|
669
|
+
messageFunctions.message( 'anno-unexpected-localized-skip', ['definitions', defName], {
|
|
670
|
+
'#': 'view',
|
|
671
|
+
name: defName,
|
|
672
|
+
art: artName,
|
|
673
|
+
anno: annoPersistenceSkip
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
659
678
|
}
|
|
660
679
|
|
|
661
680
|
/**
|
|
@@ -740,7 +759,7 @@ function copyPersistenceAnnotations(target, source, options) {
|
|
|
740
759
|
// lead to some localization views referencing non-existing ones.
|
|
741
760
|
// But that is the contract: User says that it already exists!
|
|
742
761
|
// In v2, `.exists` was never copied.
|
|
743
|
-
if (anno ===
|
|
762
|
+
if (anno === annoPersistenceSkip || (!doNotCopyExists && anno === '@cds.persistence.exists'))
|
|
744
763
|
target[anno] = source[anno];
|
|
745
764
|
});
|
|
746
765
|
}
|
|
@@ -167,7 +167,7 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
167
167
|
function conditionTerm(xpr, s, e, state) {
|
|
168
168
|
if(Array.isArray(xpr)) {
|
|
169
169
|
if(xpr.length >= 3 && xpr[s+1] === 'is') {
|
|
170
|
-
const isnull = conditionOR(xpr[s], state);
|
|
170
|
+
const isnull = conditionOR(xpr[s], 0, 0, state);
|
|
171
171
|
if(xpr[s+2] === 'null')
|
|
172
172
|
return state.array ? [ isnull, 'is', 'null' ] : { 'isNull': isnull };
|
|
173
173
|
else if(xpr[s+2] === 'not' && xpr[s+3] === 'null')
|
|
@@ -253,12 +253,16 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
function exprMulDiv(xpr, s, e, state) {
|
|
256
|
-
return binaryExpr(xpr, ['*', '/'], unary, s, e, state);
|
|
256
|
+
return binaryExpr(xpr, ['*', '/'], (state.array ? unary : dot), s, e, state);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function dot(xpr, s, e, state) {
|
|
260
|
+
return binaryExpr(xpr, ['.'], unary, s, e, state);
|
|
257
261
|
}
|
|
258
262
|
|
|
259
263
|
function unary(xpr, s, e, state) {
|
|
260
264
|
if(Array.isArray(xpr)) {
|
|
261
|
-
if(xpr[s] === '+' || xpr[s] === '-') {
|
|
265
|
+
if(xpr[s] === '+' || xpr[s] === '-' || (!state.array && xpr[s] === 'new')) {
|
|
262
266
|
return [ xpr[s], unary(xpr, s+1, e, state) ];
|
|
263
267
|
}
|
|
264
268
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
const { makeMessageFunction } = require('../base/messages');
|
|
8
8
|
const { setProp } = require('../base/model');
|
|
9
9
|
|
|
10
|
-
const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
|
|
10
|
+
const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand } = require('../model/csnUtils');
|
|
11
11
|
const { cloneCsnNonDict, cloneCsnDictionary, getUtils } = require('../model/csnUtils');
|
|
12
12
|
const { typeParameters, isBuiltinType } = require('../compiler/builtins');
|
|
13
13
|
const { ModelError, CompilerAssertion} = require('../base/error');
|
|
@@ -43,8 +43,6 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
43
43
|
flattenStructStepsInRef,
|
|
44
44
|
toFinalBaseType,
|
|
45
45
|
copyTypeProperties,
|
|
46
|
-
isAssociationOperand,
|
|
47
|
-
isDollarSelfOrProjectionOperand,
|
|
48
46
|
createExposingProjection,
|
|
49
47
|
createAndAddDraftAdminDataProjection,
|
|
50
48
|
createScalarElement,
|
|
@@ -73,7 +71,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
73
71
|
*
|
|
74
72
|
* @param {CSN.Element} element
|
|
75
73
|
* @param {null|object} [internalDefaultLengths] Either null (no implicit default) or an object `{ 'cds.String': N, 'cds.Binary': N }`.
|
|
76
|
-
|
|
74
|
+
**/
|
|
77
75
|
function addDefaultTypeFacets(element, internalDefaultLengths = null) {
|
|
78
76
|
if (!element || !element.type)
|
|
79
77
|
return;
|
|
@@ -121,7 +119,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
121
119
|
}
|
|
122
120
|
}
|
|
123
121
|
|
|
124
|
-
if (
|
|
122
|
+
if (options.transformation === 'odata' || options.transformation === 'effective')
|
|
125
123
|
copyAnnotations(assoc, foreignKeyElement, true);
|
|
126
124
|
|
|
127
125
|
// If the association is non-fkArtifact resp. key, so should be the foreign key field
|
|
@@ -134,7 +132,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
134
132
|
// Establish the relationship between generated field and association:
|
|
135
133
|
// - generated field has annotation '@odata.foreignKey4'.
|
|
136
134
|
// - foreign key info has 'generatedFieldName'
|
|
137
|
-
|
|
135
|
+
if(options.transformation !== 'effective')
|
|
136
|
+
foreignKeyElement['@odata.foreignKey4'] = assocName;
|
|
138
137
|
foreignKey.$generatedFieldName = foreignKeyElementName;
|
|
139
138
|
// attach $path to the newly created element - used for inspectRef in processAssociationOrComposition
|
|
140
139
|
setProp(foreignKeyElement, '$path', path);
|
|
@@ -608,22 +607,6 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
608
607
|
return result;
|
|
609
608
|
}
|
|
610
609
|
|
|
611
|
-
// Return true if 'arg' is an expression argument denoting "$self" || "$projection"
|
|
612
|
-
function isDollarSelfOrProjectionOperand(arg) {
|
|
613
|
-
return arg.ref && arg.ref.length === 1 && (arg.ref[0] === '$self' || arg.ref[0] === '$projection');
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Return true if 'arg' is an expression argument of type association or composition
|
|
617
|
-
function isAssociationOperand(arg, path) {
|
|
618
|
-
if (!arg.ref) {
|
|
619
|
-
// Not a path, hence not an association (literal, expression, function, whatever ...)
|
|
620
|
-
return false;
|
|
621
|
-
}
|
|
622
|
-
const { art } = inspectRef(path);
|
|
623
|
-
// If it has a target, it is an association or composition
|
|
624
|
-
return art && art.target !== undefined;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
610
|
// Create an artificial element 'elemName' of type 'cds.Association',
|
|
628
611
|
// having association target 'target'. If 'isManaged' is true, take all keys
|
|
629
612
|
// of 'target' as foreign keys.
|