@sap/cds-compiler 6.4.6 → 6.5.2
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 +42 -1156
- package/README.md +1 -10
- package/bin/cdsc.js +1 -1
- 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 +31 -8
- 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 +330 -558
- package/lib/compiler/extend.js +302 -18
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +33 -5
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/populate.js +25 -70
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +4 -13
- package/lib/compiler/shared.js +18 -5
- package/lib/compiler/tweak-assocs.js +13 -9
- package/lib/compiler/utils.js +98 -2
- 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 +19 -5
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -4
- package/lib/render/toSql.js +0 -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,21 +955,32 @@ 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
|
-
|
|
954
|
-
|
|
955
|
-
returns: '
|
|
956
|
-
|
|
962
|
+
enum: 'Enum symbol $(NAME) has not been found',
|
|
963
|
+
returns: 'The return value has no element $(NAME)',
|
|
964
|
+
'enum-returns': 'The return value has no enum $(NAME)',
|
|
965
|
+
},
|
|
966
|
+
'ext-undefined-element-sec': {
|
|
967
|
+
std: 'Element $(NAME) has not been found',
|
|
968
|
+
enum: 'Enum symbol $(NAME) has not been found',
|
|
969
|
+
returns: 'The return value has no element $(NAME)',
|
|
970
|
+
'enum-returns': 'The return value has no enum $(NAME)',
|
|
957
971
|
},
|
|
958
972
|
'ext-undefined-key': 'Foreign key $(NAME) has not been found',
|
|
959
973
|
'ext-undefined-action': {
|
|
960
|
-
std: 'Action $(
|
|
961
|
-
|
|
974
|
+
std: 'Action $(NAME) has not been found',
|
|
975
|
+
},
|
|
976
|
+
'ext-undefined-action-sec': {
|
|
977
|
+
std: 'Action $(NAME) has not been found',
|
|
962
978
|
},
|
|
963
979
|
'ext-undefined-param': {
|
|
964
|
-
std: 'Parameter $(
|
|
965
|
-
|
|
980
|
+
std: 'Parameter $(NAME) has not been found',
|
|
981
|
+
},
|
|
982
|
+
'ext-undefined-param-sec': {
|
|
983
|
+
std: 'Parameter $(NAME) has not been found',
|
|
966
984
|
},
|
|
967
985
|
|
|
968
986
|
// annotation checks against their definition
|
|
@@ -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
|
}
|