@sap/cds-compiler 5.7.4 → 5.8.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 +54 -2
- package/bin/cdsse.js +13 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/options.js +2 -1
- package/lib/api/validate.js +9 -0
- package/lib/base/message-registry.js +55 -20
- package/lib/base/messages.js +5 -2
- package/lib/base/model.js +4 -1
- package/lib/checks/assocOutsideService.js +40 -0
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/types.js +7 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/assert-consistency.js +11 -5
- package/lib/compiler/checks.js +79 -17
- package/lib/compiler/define.js +57 -3
- package/lib/compiler/extend.js +1 -2
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/populate.js +17 -6
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +181 -150
- package/lib/compiler/shared.js +276 -22
- package/lib/compiler/tweak-assocs.js +15 -4
- package/lib/compiler/xpr-rewrite.js +76 -50
- package/lib/edm/annotations/edmJson.js +1 -1
- package/lib/edm/annotations/genericTranslation.js +2 -2
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edmPreprocessor.js +15 -9
- package/lib/edm/edmUtils.js +12 -5
- package/lib/gen/CdlGrammar.checksum +1 -0
- package/lib/gen/CdlParser.js +2234 -2233
- package/lib/gen/Dictionary.json +55 -8
- package/lib/json/from-csn.js +37 -17
- package/lib/json/to-csn.js +4 -0
- package/lib/language/genericAntlrParser.js +7 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +0 -5
- package/lib/modelCompare/utils/filter.js +2 -2
- package/lib/optionProcessor.js +2 -0
- package/lib/parsers/AstBuildingParser.js +47 -17
- package/lib/parsers/CdlGrammar.g4 +10 -12
- package/lib/parsers/XprTree.js +206 -0
- package/lib/render/toCdl.js +61 -89
- package/lib/render/toSql.js +59 -29
- package/lib/render/utils/standardDatabaseFunctions.js +252 -15
- package/lib/transform/addTenantFields.js +9 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
- package/lib/transform/db/assocsToQueries/utils.js +10 -3
- package/lib/transform/db/expansion.js +3 -1
- package/lib/transform/db/flattening.js +7 -3
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +70 -17
- package/lib/transform/draft/db.js +8 -3
- package/lib/transform/draft/odata.js +27 -4
- package/lib/transform/effective/main.js +37 -10
- package/lib/transform/effective/misc.js +4 -9
- package/lib/transform/effective/service.js +34 -0
- package/lib/transform/effective/types.js +28 -17
- package/lib/transform/forOdata.js +36 -10
- package/lib/transform/forRelationalDB.js +30 -18
- package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
- package/lib/transform/odata/createForeignKeys.js +120 -116
- package/lib/transform/odata/flattening.js +10 -8
- package/lib/transform/transformUtils.js +58 -25
- package/lib/transform/translateAssocsToJoins.js +10 -6
- package/lib/transform/universalCsn/coreComputed.js +5 -1
- package/package.json +1 -1
- package/share/messages/message-explanations.json +1 -0
- package/share/messages/rewrite-not-supported.md +5 -0
- package/share/messages/rewrite-undefined-key.md +94 -0
package/lib/compiler/checks.js
CHANGED
|
@@ -16,9 +16,11 @@ const {
|
|
|
16
16
|
forEachMember,
|
|
17
17
|
forEachMemberRecursively,
|
|
18
18
|
isDeprecatedEnabled,
|
|
19
|
+
isBetaEnabled,
|
|
19
20
|
} = require('../base/model');
|
|
20
21
|
const { typeParameters } = require('./builtins');
|
|
21
22
|
const { propagationRules } = require('../base/builtins');
|
|
23
|
+
const { annotationVal } = require('./utils');
|
|
22
24
|
|
|
23
25
|
const $location = Symbol.for( 'cds.$location' );
|
|
24
26
|
|
|
@@ -32,7 +34,10 @@ function check( model ) {
|
|
|
32
34
|
error, warning, info, message,
|
|
33
35
|
} = model.$messageFunctions;
|
|
34
36
|
|
|
35
|
-
const {
|
|
37
|
+
const {
|
|
38
|
+
getOrigin,
|
|
39
|
+
getInheritedProp,
|
|
40
|
+
} = model.$functions;
|
|
36
41
|
|
|
37
42
|
checkSapCommonLocale( model );
|
|
38
43
|
checkSapCommonTextsAspects( model );
|
|
@@ -55,7 +60,8 @@ function check( model ) {
|
|
|
55
60
|
function checkEvent( def ) {
|
|
56
61
|
// Ensure that events are structured. Up to compiler v4, we allowed non-structured events,
|
|
57
62
|
// because when we introduced them, it was not fully specified what they are.
|
|
58
|
-
if (def.kind === 'event' &&
|
|
63
|
+
if (def.kind === 'event' && def._effectiveType &&
|
|
64
|
+
!def._effectiveType.elements && !def._effectiveType.projection)
|
|
59
65
|
message( 'def-expected-structured', [ (def.type || def.name).location, def ] );
|
|
60
66
|
}
|
|
61
67
|
|
|
@@ -268,11 +274,10 @@ function check( model ) {
|
|
|
268
274
|
function checkLocalizedElement( elem ) {
|
|
269
275
|
if (elem.localized?.val) {
|
|
270
276
|
const type = elem._effectiveType;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
error( '
|
|
274
|
-
{ keyword: 'localized' }
|
|
275
|
-
'Map types can\'t be used with $(KEYWORD)' );
|
|
277
|
+
if (type?.category === 'map' ||
|
|
278
|
+
(type?.elements && isBetaEnabled( model.options, 'v6preview' ))) {
|
|
279
|
+
error( 'def-unexpected-localized', [ elem.localized.location, elem ],
|
|
280
|
+
{ keyword: 'localized', '#': type.category === 'map' ? 'map' : 'struct' } );
|
|
276
281
|
}
|
|
277
282
|
else if (!type || !type.builtin || type.category !== 'string') {
|
|
278
283
|
// See discussion issue #6520: should we allow all scalar types?
|
|
@@ -584,14 +589,36 @@ function check( model ) {
|
|
|
584
589
|
}
|
|
585
590
|
|
|
586
591
|
function checkDefaultValue( art ) {
|
|
587
|
-
if (!art.
|
|
592
|
+
if (!art._effectiveType)
|
|
588
593
|
return;
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
594
|
+
if (art.kind !== 'element' && art.kind !== 'type' && art.kind !== 'param')
|
|
595
|
+
return;
|
|
596
|
+
|
|
597
|
+
const defaultValue = getInheritedProp( art, 'default' );
|
|
598
|
+
if (defaultValue?.val === undefined)
|
|
592
599
|
return;
|
|
593
|
-
|
|
594
|
-
|
|
600
|
+
|
|
601
|
+
// Check that "not null" artifacts don't have `null` default values.
|
|
602
|
+
// At least one property must be written explicitly to avoid reporting on inferred elements.
|
|
603
|
+
if (isBetaEnabled( model.options, 'v6preview' ) &&
|
|
604
|
+
(art.default?.val === null || art.notNull?.val)) {
|
|
605
|
+
const notNullValue = getInheritedProp( art, 'notNull' );
|
|
606
|
+
if (notNullValue?.val && defaultValue?.val === null) {
|
|
607
|
+
const loc = (art.default || art.notNull)?.location || art.location;
|
|
608
|
+
const variant = art.kind + (!art.default && art.notNull ? 'NotNull' : 'DefaultNull');
|
|
609
|
+
message( 'type-unexpected-null', [ loc, art ], {
|
|
610
|
+
'#': variant,
|
|
611
|
+
art,
|
|
612
|
+
keyword: 'not null',
|
|
613
|
+
value: 'null',
|
|
614
|
+
} );
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// TODO(v6): Also reject default for structures (#13154), i.e. add `|| !!art.….elements`
|
|
619
|
+
const isStructured = art._effectiveType?.name.id === 'cds.Map';
|
|
620
|
+
if (isStructured) {
|
|
621
|
+
error( 'type-unexpected-default', [ defaultValue.location, art ], {
|
|
595
622
|
'#': 'map', keyword: 'default', type: 'cds.Map',
|
|
596
623
|
} );
|
|
597
624
|
}
|
|
@@ -956,21 +983,32 @@ function check( model ) {
|
|
|
956
983
|
}
|
|
957
984
|
|
|
958
985
|
function checkAnnotationAssignment1( art, anno ) {
|
|
986
|
+
const name = anno.name?.id;
|
|
959
987
|
if (art.$contains?.$annotation && anno.kind === '$annotation') {
|
|
960
988
|
if (checkAnnotationAcceptsExpressions( anno, art ))
|
|
961
989
|
checkAnnotationExpressions( anno, art );
|
|
962
990
|
}
|
|
963
991
|
|
|
964
|
-
if (!model.vocabularies)
|
|
965
|
-
return;
|
|
966
|
-
|
|
967
992
|
// Has been slightly adapted for model.vocabularies but comments need to be
|
|
968
993
|
// adapted, etc.
|
|
969
994
|
// TODO: rework completely!
|
|
970
995
|
// TODO: if we have such a check, consider #variant, anno.@anno, anno@anno
|
|
971
996
|
// Sanity checks (ignore broken assignments)
|
|
972
|
-
if (!
|
|
997
|
+
if (!name)
|
|
998
|
+
return;
|
|
999
|
+
|
|
1000
|
+
// Compiler specific annotation validation.
|
|
1001
|
+
const annotationChecks = {
|
|
1002
|
+
__proto__: null,
|
|
1003
|
+
'@cds.redirection.target': checkAnnoRedirectionTarget,
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
const annoName = `@${ name }`;
|
|
1007
|
+
annotationChecks[annoName]?.(anno, art);
|
|
1008
|
+
|
|
1009
|
+
if (!model.vocabularies)
|
|
973
1010
|
return;
|
|
1011
|
+
|
|
974
1012
|
// Just a little workaround to adapt to changed `name`s, not nice coding:
|
|
975
1013
|
const hashIndex = anno.name.id.indexOf( '#' );
|
|
976
1014
|
const path = (hashIndex > 0 ? anno.name.id.substring( 0, hashIndex ) : anno.name.id)
|
|
@@ -1000,6 +1038,23 @@ function check( model ) {
|
|
|
1000
1038
|
checkAnnotationAssignment( anno, artifact, endOfPath, art );
|
|
1001
1039
|
}
|
|
1002
1040
|
|
|
1041
|
+
function checkAnnoRedirectionTarget( anno, art ) {
|
|
1042
|
+
if (anno.$inferred)
|
|
1043
|
+
return;
|
|
1044
|
+
if (!annotationVal( anno ))
|
|
1045
|
+
return; // ignore falsey values, including 'null'
|
|
1046
|
+
|
|
1047
|
+
// Non-entities can't have this annotation, nor can non-service entities,
|
|
1048
|
+
// nor can complex queries such as joins, unions, or selecting from an association.
|
|
1049
|
+
|
|
1050
|
+
const isIgnored = isComplexView( art ) || (art.kind !== 'entity') || !art._service;
|
|
1051
|
+
if (isIgnored) {
|
|
1052
|
+
const loc = anno.val ? anno.location : anno.name.location;
|
|
1053
|
+
info( 'anno-ignoring-redirection-target', [ loc, art ], { anno: anno.name.id },
|
|
1054
|
+
'$(ANNO) has no effect here; use it on simple projections inside services' );
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1003
1058
|
// Perform checks for annotation assignment 'anno', using corresponding annotation declaration,
|
|
1004
1059
|
// made of 'annoDecl' (artifact or undefined) and 'elementDecl' (annotation or element
|
|
1005
1060
|
// or undefined). Report errors on 'options.messages.
|
|
@@ -1316,4 +1371,11 @@ function isComposition( model, elem ) {
|
|
|
1316
1371
|
return false;
|
|
1317
1372
|
}
|
|
1318
1373
|
|
|
1374
|
+
function isComplexView( art ) {
|
|
1375
|
+
if (!art?.query) // non-query
|
|
1376
|
+
return false;
|
|
1377
|
+
// Either UNION, JOIN, SUB-SELECT, or target is an association
|
|
1378
|
+
return (!art.query.from?._artifact || art.query.from._artifact.kind === 'element');
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1319
1381
|
module.exports = check;
|
package/lib/compiler/define.js
CHANGED
|
@@ -572,9 +572,57 @@ function define( model ) {
|
|
|
572
572
|
initMembers( art, art, block );
|
|
573
573
|
if (art.params)
|
|
574
574
|
initDollarParameters( art ); // $parameters
|
|
575
|
+
if (art.query) {
|
|
576
|
+
initArtifactQuery( art );
|
|
577
|
+
restrictToSimpleProjection( art );
|
|
578
|
+
}
|
|
579
|
+
}
|
|
575
580
|
|
|
576
|
-
|
|
581
|
+
/**
|
|
582
|
+
* Restrict the query of `art` to only simple projections, i.e. those without 'group by', etc.
|
|
583
|
+
*
|
|
584
|
+
* @param {XSN.Artifact} art
|
|
585
|
+
*/
|
|
586
|
+
function restrictToSimpleProjection( art ) {
|
|
587
|
+
const { query } = art;
|
|
588
|
+
|
|
589
|
+
if (art.kind !== 'type')
|
|
590
|
+
return; // TODO(v6): Also for event
|
|
591
|
+
|
|
592
|
+
if (!query.from?.path)
|
|
593
|
+
return; // union, sub-select, etc. should already be rejected
|
|
594
|
+
|
|
595
|
+
const check = (prop, keyword) => (query[prop] && { prop: query[prop], keyword });
|
|
596
|
+
const unexpectedQueryProp = check( 'where', 'where' ) ||
|
|
597
|
+
check( 'groupBy', 'group by' ) ||
|
|
598
|
+
check( 'limit', 'limit' ) ||
|
|
599
|
+
check( 'having', 'having' ) ||
|
|
600
|
+
check( 'orderBy', 'order by' ) ||
|
|
601
|
+
null;
|
|
602
|
+
|
|
603
|
+
if (unexpectedQueryProp) {
|
|
604
|
+
error( 'query-unexpected-prop', [ unexpectedQueryProp.prop.location, query ], {
|
|
605
|
+
'#': art.kind,
|
|
606
|
+
keyword: unexpectedQueryProp.keyword,
|
|
607
|
+
}, {
|
|
608
|
+
std: 'Unexpected $(KEYWORD) for projection clause used as type expression',
|
|
609
|
+
type: 'Unexpected $(KEYWORD) for type definition',
|
|
610
|
+
event: 'Unexpected $(KEYWORD) for event definition',
|
|
611
|
+
} );
|
|
577
612
|
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const firstCondition = query.from.path.find(step => step.where)?.where;
|
|
616
|
+
if (firstCondition) {
|
|
617
|
+
error( 'query-unexpected-filter', [ firstCondition.location, query ], { '#': art.kind }, {
|
|
618
|
+
std: 'Unexpected filter in query source for projection clause used as type expression',
|
|
619
|
+
type: 'Unexpected filter in projection clause of type definition',
|
|
620
|
+
event: 'Unexpected filter in projection clause of event definition',
|
|
621
|
+
} );
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function initArtifactQuery( art ) {
|
|
578
626
|
art.$queries = [];
|
|
579
627
|
setLink( art, '_from', [] ); // for sequence of resolve steps - TODO: remove
|
|
580
628
|
if (!setLink( art, '_leadingQuery', initQueryExpression( art.query, art ) ) )
|
|
@@ -593,6 +641,12 @@ function define( model ) {
|
|
|
593
641
|
checkRedefinition( art );
|
|
594
642
|
const block = art._block;
|
|
595
643
|
initMembers( art, art, block );
|
|
644
|
+
|
|
645
|
+
if (art.query) {
|
|
646
|
+
initArtifactQuery( art );
|
|
647
|
+
error( 'def-unsupported-projection', [ art.location, art ], null,
|
|
648
|
+
'Projections for annotation definitions are not supported' );
|
|
649
|
+
}
|
|
596
650
|
}
|
|
597
651
|
|
|
598
652
|
function initArtifactParentLink( art, definitions, path, pathIndex ) {
|
|
@@ -665,7 +719,7 @@ function define( model ) {
|
|
|
665
719
|
// Init queries: --------------------------------------------------------------
|
|
666
720
|
|
|
667
721
|
// art is:
|
|
668
|
-
// - entity for top-level queries (including UNION args)
|
|
722
|
+
// - entity/event/type for top-level queries (including UNION args)
|
|
669
723
|
// - $tableAlias for sub query in FROM - TODO: what about UNION there?
|
|
670
724
|
// - $query for real sub query (in columns, WHERE, ...), again: what about UNION there?
|
|
671
725
|
function initQueryExpression( query, art ) {
|
|
@@ -988,7 +1042,7 @@ function define( model ) {
|
|
|
988
1042
|
if (exprOrPathElement.$expected === 'exists')
|
|
989
1043
|
exprOrPathElement.$expected = 'approved-exists';
|
|
990
1044
|
// Drill down
|
|
991
|
-
if (exprOrPathElement.args)
|
|
1045
|
+
if (Array.isArray(exprOrPathElement.args))
|
|
992
1046
|
exprOrPathElement.args.forEach( elem => approveExistsInChildren( elem ) );
|
|
993
1047
|
else if (exprOrPathElement.where?.args)
|
|
994
1048
|
exprOrPathElement.where.args.forEach( elem => approveExistsInChildren( elem ) );
|
package/lib/compiler/extend.js
CHANGED
|
@@ -200,8 +200,7 @@ function extend( model ) {
|
|
|
200
200
|
*/
|
|
201
201
|
function extendForeignKeys( art ) {
|
|
202
202
|
// See extendArtifactAfter() for targetAspect/items handling.
|
|
203
|
-
|
|
204
|
-
if (!art._extensions || sub)
|
|
203
|
+
if (!art._extensions || art.items || art.targetAspect?.elements)
|
|
205
204
|
return;
|
|
206
205
|
|
|
207
206
|
// push down foreign keys
|
package/lib/compiler/generate.js
CHANGED
|
@@ -141,7 +141,7 @@ function generate( model ) {
|
|
|
141
141
|
// Not supported anyway, but important for recompilation (which fails correctly).
|
|
142
142
|
const loc = elem.localized?.location || elem.location;
|
|
143
143
|
error( 'def-unexpected-localized', [ loc, elem ],
|
|
144
|
-
{ '#': !include ? '
|
|
144
|
+
{ '#': !include ? 'elements' : 'include', art: textsAspect } );
|
|
145
145
|
hasError = true;
|
|
146
146
|
}
|
|
147
147
|
else if (elem.targetAspect) {
|
package/lib/compiler/populate.js
CHANGED
|
@@ -740,6 +740,7 @@ function populate( model ) {
|
|
|
740
740
|
query._main.elements = elemsParent.elements;
|
|
741
741
|
}
|
|
742
742
|
|
|
743
|
+
const isExpand = (query.expand === columns);
|
|
743
744
|
if (!columns)
|
|
744
745
|
columns = [ { val: '*' } ];
|
|
745
746
|
|
|
@@ -768,7 +769,7 @@ function populate( model ) {
|
|
|
768
769
|
initFromColumns( query, col.inline, col );
|
|
769
770
|
}
|
|
770
771
|
else if (!col.$replacement) {
|
|
771
|
-
const id = ensureColumnName( col, i, query );
|
|
772
|
+
const id = ensureColumnName( col, i, query, isExpand );
|
|
772
773
|
col.kind = 'element';
|
|
773
774
|
dictAdd( elemsParent.elements, id, col, ( name, location ) => {
|
|
774
775
|
error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
|
|
@@ -780,8 +781,17 @@ function populate( model ) {
|
|
|
780
781
|
return true;
|
|
781
782
|
}
|
|
782
783
|
|
|
783
|
-
|
|
784
|
-
|
|
784
|
+
/**
|
|
785
|
+
* TODO: probably do this already in definer.js
|
|
786
|
+
*
|
|
787
|
+
* @param col
|
|
788
|
+
* @param {number} colIndex
|
|
789
|
+
* @param query
|
|
790
|
+
* @param {boolean} insideExpand
|
|
791
|
+
* Whether the column is inside 'expand'.
|
|
792
|
+
* Anonymous 'expands' don't have a column parent, hence why we need to know this explicitly.
|
|
793
|
+
*/
|
|
794
|
+
function ensureColumnName( col, colIndex, query, insideExpand ) {
|
|
785
795
|
if (col.name)
|
|
786
796
|
return col.name.id;
|
|
787
797
|
if (col.inline || col.val === '*')
|
|
@@ -795,8 +805,9 @@ function populate( model ) {
|
|
|
795
805
|
return col.name.id;
|
|
796
806
|
}
|
|
797
807
|
}
|
|
798
|
-
else if (
|
|
799
|
-
|
|
808
|
+
else if (insideExpand || col.expand ||
|
|
809
|
+
col.value && (col._columnParent || query._parent.kind !== 'select')) {
|
|
810
|
+
// _columnParent => inline/expand with path head; _parent -> only allowed in sub-selects
|
|
800
811
|
error( 'query-req-name', [ col.value?.location || col.location, query ], {},
|
|
801
812
|
'Alias name is required for this select item' );
|
|
802
813
|
}
|
|
@@ -851,7 +862,7 @@ function populate( model ) {
|
|
|
851
862
|
let seenWildcard = null;
|
|
852
863
|
let colIndex = 0;
|
|
853
864
|
for (const col of columns) {
|
|
854
|
-
const id = ensureColumnName( col, colIndex, query );
|
|
865
|
+
const id = ensureColumnName( col, colIndex, query, false );
|
|
855
866
|
if (id) {
|
|
856
867
|
col.$replacement = !seenWildcard;
|
|
857
868
|
siblings[id] = !(id in siblings) && col;
|