@sap/cds-compiler 6.4.6 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -1156
- package/README.md +1 -10
- package/doc/IncompatibleChanges_v5.md +436 -0
- package/doc/IncompatibleChanges_v6.md +659 -0
- package/doc/Versioning.md +3 -7
- package/lib/api/main.js +1 -0
- package/lib/api/options.js +5 -0
- package/lib/api/validate.js +3 -0
- package/lib/base/message-registry.js +23 -0
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +3 -2
- package/lib/checks/actionsFunctions.js +6 -3
- package/lib/checks/existsInForbiddenPlaces.js +32 -0
- package/lib/checks/validator.js +2 -0
- package/lib/compiler/assert-consistency.js +3 -5
- package/lib/compiler/checks.js +4 -8
- package/lib/compiler/define.js +244 -459
- package/lib/compiler/extend.js +297 -11
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +29 -1
- package/lib/compiler/populate.js +21 -63
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +2 -12
- package/lib/compiler/shared.js +18 -5
- package/lib/compiler/tweak-assocs.js +13 -9
- package/lib/compiler/utils.js +97 -0
- package/lib/compiler/xpr-rewrite.js +2 -1
- package/lib/edm/annotations/edmJson.js +9 -6
- package/lib/edm/annotations/genericTranslation.js +8 -4
- package/lib/edm/csn2edm.js +3 -4
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +3 -3
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1 -1
- package/lib/gen/Dictionary.json +16 -1
- package/lib/json/from-csn.js +4 -6
- package/lib/json/to-csn.js +3 -3
- package/lib/model/csnRefs.js +13 -4
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -4
- package/lib/render/utils/sql.js +3 -2
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +3 -3
- package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
- package/lib/transform/db/assocsToQueries/transformExists.js +14 -3
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/backlinks.js +4 -4
- package/lib/transform/db/cdsPersistence.js +4 -4
- package/lib/transform/db/constraints.js +4 -4
- package/lib/transform/db/expansion.js +5 -5
- package/lib/transform/db/flattening.js +4 -5
- package/lib/transform/db/rewriteCalculatedElements.js +3 -3
- package/lib/transform/db/temporal.js +11 -11
- package/lib/transform/draft/db.js +2 -0
- package/lib/transform/draft/odata.js +5 -7
- package/lib/transform/effective/flattening.js +1 -2
- package/lib/transform/forOdata.js +3 -3
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +1 -2
- package/lib/transform/odata/flattening.js +1 -2
- package/lib/transform/odata/toFinalBaseType.js +52 -55
- package/lib/transform/transformUtils.js +3 -4
- package/package.json +1 -1
- package/doc/CHANGELOG_BETA.md +0 -464
- package/doc/CHANGELOG_DEPRECATED.md +0 -235
package/lib/api/options.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// The options are specified in ../optionProcessor.js (and some other files).
|
|
4
|
+
// Some backends feel the need to "translate" option, which require to list
|
|
5
|
+
// all options also here (as “public" or "private" option).
|
|
6
|
+
|
|
3
7
|
const { validate, generateStringValidator } = require('./validate');
|
|
4
8
|
const { makeMessageFunction } = require('../base/messages');
|
|
5
9
|
|
|
@@ -20,6 +24,7 @@ const publicOptionsNewAPI = [
|
|
|
20
24
|
'defaultBinaryLength',
|
|
21
25
|
'defaultStringLength',
|
|
22
26
|
'csnFlavor',
|
|
27
|
+
'noDollarCalc',
|
|
23
28
|
// DB
|
|
24
29
|
'sqlDialect',
|
|
25
30
|
'sqlMapping',
|
package/lib/api/validate.js
CHANGED
|
@@ -92,9 +92,16 @@ const centralMessages = {
|
|
|
92
92
|
'ext-undefined-action': { severity: 'Warning' },
|
|
93
93
|
'ext-undefined-art': { severity: 'Warning' }, // for annotate statement (for CDL path root)
|
|
94
94
|
'ext-undefined-def': { severity: 'Warning' }, // for annotate statement (for CSN or CDL path cont)
|
|
95
|
+
'ext-undefined-art-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
|
|
96
|
+
'ext-undefined-def-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
|
|
95
97
|
'ext-undefined-element': { severity: 'Warning' },
|
|
96
98
|
'ext-undefined-key': { severity: 'Warning' },
|
|
97
99
|
'ext-undefined-param': { severity: 'Warning' },
|
|
100
|
+
'ext-undefined-element-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
|
|
101
|
+
'ext-undefined-action-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
|
|
102
|
+
'ext-undefined-param-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
|
|
103
|
+
'ext-unexpected-returns': { severity: 'Warning' },
|
|
104
|
+
'ext-unexpected-returns-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
|
|
98
105
|
'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
|
|
99
106
|
'anno-unexpected-localized-skip': { severity: 'Error', configurableFor: true },
|
|
100
107
|
|
|
@@ -948,6 +955,8 @@ const centralMessageTexts = {
|
|
|
948
955
|
'anno-builtin': 'Builtin types should not be annotated nor extended. Use custom type instead',
|
|
949
956
|
'ext-undefined-def': 'Artifact $(ART) has not been found',
|
|
950
957
|
'ext-undefined-art': 'No artifact has been found with name $(ART)',
|
|
958
|
+
'ext-undefined-def-sec': 'Artifact $(ART) has not been found',
|
|
959
|
+
'ext-undefined-art-sec': 'No artifact has been found with name $(ART)',
|
|
951
960
|
'ext-undefined-element': {
|
|
952
961
|
std: 'Element $(NAME) has not been found',
|
|
953
962
|
element: 'Artifact $(ART) has no element $(NAME)',
|
|
@@ -955,15 +964,24 @@ const centralMessageTexts = {
|
|
|
955
964
|
returns: 'Return value of $(ART) has no element $(NAME)',
|
|
956
965
|
'enum-returns': 'Return value of $(ART) has no enum $(NAME)',
|
|
957
966
|
},
|
|
967
|
+
'ext-undefined-element-sec': 'Element $(NAME) has not been found',
|
|
958
968
|
'ext-undefined-key': 'Foreign key $(NAME) has not been found',
|
|
959
969
|
'ext-undefined-action': {
|
|
960
970
|
std: 'Action $(ART) has not been found',
|
|
961
971
|
action: 'Artifact $(ART) has no action $(NAME)',
|
|
962
972
|
},
|
|
973
|
+
'ext-undefined-action-sec': {
|
|
974
|
+
std: 'Action $(ART) has not been found',
|
|
975
|
+
action: 'Artifact $(ART) has no action $(NAME)',
|
|
976
|
+
},
|
|
963
977
|
'ext-undefined-param': {
|
|
964
978
|
std: 'Parameter $(ART) has not been found',
|
|
965
979
|
param: 'Artifact $(ART) has no parameter $(NAME)',
|
|
966
980
|
},
|
|
981
|
+
'ext-undefined-param-sec': {
|
|
982
|
+
std: 'Parameter $(ART) has not been found',
|
|
983
|
+
param: 'Artifact $(ART) has no parameter $(NAME)',
|
|
984
|
+
},
|
|
967
985
|
|
|
968
986
|
// annotation checks against their definition
|
|
969
987
|
'anno-expecting-value': {
|
|
@@ -1157,6 +1175,11 @@ const centralMessageTexts = {
|
|
|
1157
1175
|
redirected: 'Target $(TARGET) of $(NAME) is missing element $(ID); add an ON-condition to $(KEYWORD)',
|
|
1158
1176
|
},
|
|
1159
1177
|
'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode', // eslint-disable-line cds-compiler/message-no-quotes
|
|
1178
|
+
'query-unexpected-exists': {
|
|
1179
|
+
std: 'Unexpected $(PROP) predicate',
|
|
1180
|
+
groupBy: 'Unexpected $(PROP) predicate in GROUP BY clause',
|
|
1181
|
+
orderBy: 'Unexpected $(PROP) predicate in ORDER BY clause',
|
|
1182
|
+
},
|
|
1160
1183
|
'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode', // eslint-disable-line cds-compiler/message-no-quotes
|
|
1161
1184
|
'query-ignoring-param-nullability': {
|
|
1162
1185
|
std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
|
package/lib/base/messages.js
CHANGED
|
@@ -1724,7 +1724,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
|
|
|
1724
1724
|
else if (step === 'targetAspect') {
|
|
1725
1725
|
// skip
|
|
1726
1726
|
}
|
|
1727
|
-
else if (step === 'xpr' || step === 'list' || step === 'default' || step === 'ref' || step === 'as' || step === 'value') {
|
|
1727
|
+
else if (step === 'xpr' || step === 'list' || step === 'default' || step === 'ref' || step === 'as' || step === 'value' || step === '$calc') {
|
|
1728
1728
|
break; // don't go into xprs, refs, aliases, values, etc.
|
|
1729
1729
|
}
|
|
1730
1730
|
else if (step === 'returns') {
|
package/lib/base/model.js
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
const { forEach } = require('../utils/objectUtils');
|
|
9
9
|
|
|
10
|
+
// Normal options are in ../optionProcessor.js (and some other files),
|
|
11
|
+
// unfortunately partly non-grep-able (option `fooBar` is defined via `--foo-bar`)
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* Object of all available beta flags that will be enabled/disabled by `--beta-mode`
|
|
12
15
|
* through cdsc. Only intended for INTERNAL USE.
|
|
@@ -29,8 +32,6 @@ const availableBetaFlags = {
|
|
|
29
32
|
rewriteAnnotationExpressionsViaType: true,
|
|
30
33
|
sqlServiceDummies: true,
|
|
31
34
|
projectionViews: true,
|
|
32
|
-
draftAdminDataHiddenFilter: true,
|
|
33
|
-
$calcForDraft: true,
|
|
34
35
|
// disabled by --beta-mode
|
|
35
36
|
nestedServices: false,
|
|
36
37
|
};
|
|
@@ -25,8 +25,9 @@ function checkActionOrFunction( art, artName, prop, path ) {
|
|
|
25
25
|
for (const [ actName, act ] of Object.entries(art.actions)) {
|
|
26
26
|
if (act.params) {
|
|
27
27
|
checkExplicitBindingParameter.call(this, act.params, path.concat([ 'actions', actName, 'params' ]));
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const params = Object.entries(act.params);
|
|
29
|
+
for (const [ paramName, param ] of params)
|
|
30
|
+
checkActionOrFunctionParameter.call(this, param, path.concat([ 'actions', actName, 'params', paramName ]), act.kind, params);
|
|
30
31
|
}
|
|
31
32
|
if (act.returns)
|
|
32
33
|
checkReturns.bind(this)(act.returns, path.concat([ 'actions', actName, 'returns' ]), act.kind);
|
|
@@ -63,7 +64,9 @@ function checkActionOrFunction( art, artName, prop, path ) {
|
|
|
63
64
|
* @param {CSN.Path} currPath path to the parameter
|
|
64
65
|
* @param {string} actKind 'action' or 'function'
|
|
65
66
|
*/
|
|
66
|
-
function checkActionOrFunctionParameter( param, currPath, actKind ) {
|
|
67
|
+
function checkActionOrFunctionParameter( param, currPath, actKind, params ) {
|
|
68
|
+
if ((param.items || param)?.type === '$self' && param === params?.[0][1])
|
|
69
|
+
return; // binding parameter
|
|
67
70
|
const paramType = param.type ? this.csnUtils.getFinalTypeInfo(param.type) : param;
|
|
68
71
|
if (!paramType)
|
|
69
72
|
return; // no type could be resolved
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { applyTransformationsOnNonDictionary } = require('../model/csnUtils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Checks a query for forbidden usage of the `exists` predicate within `groupBy` and `orderBy`.
|
|
7
|
+
*
|
|
8
|
+
* @param {object} query
|
|
9
|
+
* @param {string} prop - either groupBy or orderBy.
|
|
10
|
+
* @param {object[]} _tokenStream the value of query[prop]
|
|
11
|
+
* @param {object|Array} pathToClause - Path to respective groupBy/orderBy clause in the CSN.
|
|
12
|
+
* @this {object} The calling context must provide an `error` function.
|
|
13
|
+
*/
|
|
14
|
+
function existsInForbiddenPlaces( query, prop, _tokenStream, pathToClause ) {
|
|
15
|
+
applyTransformationsOnNonDictionary(query, prop, {
|
|
16
|
+
ref: (_parent, _prop, _ref, pathToError, tokenStream, index) => {
|
|
17
|
+
if (tokenStream[index - 1] === 'exists') {
|
|
18
|
+
// the path should point to the exists
|
|
19
|
+
const pathToExists = pathToError.with(-1, index - 1);
|
|
20
|
+
this.error('query-unexpected-exists', pathToExists, {
|
|
21
|
+
'#': prop,
|
|
22
|
+
prop: 'exists',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
}, null, pathToClause);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
groupBy: existsInForbiddenPlaces,
|
|
31
|
+
orderBy: existsInForbiddenPlaces,
|
|
32
|
+
};
|
package/lib/checks/validator.js
CHANGED
|
@@ -44,6 +44,7 @@ const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
|
44
44
|
const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
45
45
|
const existsMustEndInAssoc = require('./existsMustEndInAssoc');
|
|
46
46
|
const existsMustNotStartWithDollarSelf = require('./existsMustNotStartWithDollarSelf');
|
|
47
|
+
const existsInForbiddenPlaces = require('./existsInForbiddenPlaces');
|
|
47
48
|
const assertFilterOfExists = require('./existsExpressionsOnlyForeignKeys');
|
|
48
49
|
const checkPathsInStoredCalcElement = require('./checkPathsInStoredCalcElement');
|
|
49
50
|
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
@@ -85,6 +86,7 @@ const forRelationalDBArtifactValidators = [
|
|
|
85
86
|
|
|
86
87
|
const forRelationalDBCsnValidators = [
|
|
87
88
|
checkCdsMap,
|
|
89
|
+
existsInForbiddenPlaces,
|
|
88
90
|
existsMustEndInAssoc,
|
|
89
91
|
existsMustNotStartWithDollarSelf,
|
|
90
92
|
assertFilterOfExists,
|
|
@@ -316,7 +316,7 @@ function assertConsistency( model, stage ) {
|
|
|
316
316
|
requires: [ 'location', 'path' ],
|
|
317
317
|
optional: [
|
|
318
318
|
'kind', 'name', '$syntax', '_block', '_parent', '_main',
|
|
319
|
-
'elements', '_origin', '_joinParent', '$joinArgsIndex',
|
|
319
|
+
'elements', '_origin', '_joinParent', '$joinArgsIndex',
|
|
320
320
|
'$parens', '_status', // TODO: only in from
|
|
321
321
|
'scope', '_artifact', '_originalArtifact', '$inferred', 'kind',
|
|
322
322
|
'_effectiveType', '$effectiveSeqNo', // TODO:check this
|
|
@@ -449,7 +449,7 @@ function assertConsistency( model, stage ) {
|
|
|
449
449
|
requires: [ 'location', 'path' ],
|
|
450
450
|
optional: [
|
|
451
451
|
'scope', 'variant', '_artifact', '_originalArtifact',
|
|
452
|
-
'$inferred', '$parens', 'sort', 'nulls',
|
|
452
|
+
'$inferred', '$parens', 'sort', 'nulls', '$syntax',
|
|
453
453
|
],
|
|
454
454
|
},
|
|
455
455
|
none: { optional: () => true }, // parse error
|
|
@@ -507,7 +507,7 @@ function assertConsistency( model, stage ) {
|
|
|
507
507
|
args: {
|
|
508
508
|
inherits: 'value',
|
|
509
509
|
optional: [
|
|
510
|
-
'name', '$duplicate', '
|
|
510
|
+
'name', '$duplicate', 'args', 'suffix', '$parens',
|
|
511
511
|
'param', 'scope', // for dynamic parameter '?'
|
|
512
512
|
],
|
|
513
513
|
test: args,
|
|
@@ -724,7 +724,6 @@ function assertConsistency( model, stage ) {
|
|
|
724
724
|
'localized-entity', // `.texts` entity
|
|
725
725
|
'localized-origin', // `.texts` entity
|
|
726
726
|
'nav', // only used for MASKED, TODO(v6): Remove
|
|
727
|
-
'none', // only used in ensureColumnName(): Used in object representing empty alias
|
|
728
727
|
'query', // inferred query properties, e.g. `key`
|
|
729
728
|
'rewrite', // on-conditions or FKeys are rewritten
|
|
730
729
|
'parent-origin', // annotation/property copied from parent that does not come through
|
|
@@ -747,7 +746,6 @@ function assertConsistency( model, stage ) {
|
|
|
747
746
|
$withLocalized: { test: isBoolean },
|
|
748
747
|
$sources: { parser: true, test: isArray( isString ) },
|
|
749
748
|
tokenStream: { parser: true, test: TODO },
|
|
750
|
-
$expected: { parser: true, test: isOneOf( [ 'approved-exists', 'exists' ] ) },
|
|
751
749
|
$messageFunctions: { test: TODO },
|
|
752
750
|
$functions: { test: TODO },
|
|
753
751
|
$assert: { test: TODO }, // currently just for missing Error[ref-cycle]
|
package/lib/compiler/checks.js
CHANGED
|
@@ -941,14 +941,8 @@ function check( model ) {
|
|
|
941
941
|
*/
|
|
942
942
|
function checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail ) {
|
|
943
943
|
// Arg must not be an association and not $self
|
|
944
|
-
// Only if path is not
|
|
945
|
-
if (arg
|
|
946
|
-
if (arg.$expected === 'exists') {
|
|
947
|
-
const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
|
|
948
|
-
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
else if (rejectAssocTail && isAssociationOperand( arg )) {
|
|
944
|
+
// Only if path is not after an `exists`
|
|
945
|
+
if (arg.$syntax !== 'after-exists' && rejectAssocTail && isAssociationOperand( arg )) {
|
|
952
946
|
if (rejectAssocTail.rejectManaged && rejectAssocTail.rejectUnmanaged ||
|
|
953
947
|
rejectAssocTail.rejectManaged && arg._artifact.keys ||
|
|
954
948
|
rejectAssocTail.rejectUnmanaged && arg._artifact.on) {
|
|
@@ -956,6 +950,8 @@ function check( model ) {
|
|
|
956
950
|
const context = rejectAssocTail.context === 'query' && 'query-' ||
|
|
957
951
|
rejectAssocTail.context === 'anno' && 'anno-' ||
|
|
958
952
|
'';
|
|
953
|
+
// Hm, in other message context about associations or compositions, we do
|
|
954
|
+
// not have separate text variants for association or composition…
|
|
959
955
|
const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
|
|
960
956
|
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': `${ context }${ variant }` } );
|
|
961
957
|
}
|