@sap/cds-compiler 2.15.8 → 3.1.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 +102 -1590
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +61 -46
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +26 -5
- package/doc/CHANGELOG_DEPRECATED.md +55 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +282 -156
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +280 -110
- package/lib/base/message-registry.js +85 -25
- package/lib/base/messages.js +119 -89
- package/lib/base/model.js +46 -2
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +15 -12
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +101 -15
- package/lib/checks/types.js +7 -8
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +3 -3
- package/lib/compiler/assert-consistency.js +78 -21
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +177 -10
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +75 -18
- package/lib/compiler/finalize-parse-cdl.js +25 -18
- package/lib/compiler/index.js +27 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +26 -39
- package/lib/compiler/propagator.js +12 -7
- package/lib/compiler/resolve.js +207 -236
- package/lib/compiler/shared.js +100 -93
- package/lib/compiler/tweak-assocs.js +13 -20
- package/lib/compiler/utils.js +20 -6
- package/lib/edm/annotations/preprocessAnnotations.js +12 -13
- package/lib/edm/csn2edm.js +35 -37
- package/lib/edm/edm.js +22 -13
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +338 -689
- package/lib/edm/edmUtils.js +97 -67
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -31
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +892 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20629 -22474
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +74 -69
- package/lib/json/to-csn.js +17 -14
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +424 -292
- package/lib/language/language.g4 +604 -687
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +28 -42
- package/lib/main.js +104 -81
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +57 -30
- package/lib/model/csnUtils.js +189 -287
- package/lib/model/revealInternalProperties.js +32 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +91 -57
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +387 -367
- package/lib/render/toHdbcds.js +20 -16
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +81 -59
- package/lib/render/utils/common.js +16 -3
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +5 -16
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +16 -18
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/db/views.js +3 -3
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +30 -24
- package/lib/transform/forOdataNew.js +14 -16
- package/lib/transform/localized.js +35 -25
- package/lib/transform/odata/toFinalBaseType.js +10 -10
- package/lib/transform/odata/typesExposure.js +17 -8
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +31 -21
- package/lib/utils/moduleResolve.js +0 -1
- package/lib/utils/timetrace.js +20 -21
- package/package.json +34 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/checks/unknownMagic.js +0 -41
- package/lib/fix_antlr4-8_warning.js +0 -56
package/lib/base/model.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { forEach } = require("../utils/objectUtils");
|
|
4
|
+
|
|
3
5
|
const queryOps = {
|
|
4
6
|
query: 'select', // TODO: rename to SELECT
|
|
5
7
|
union: 'union',
|
|
@@ -20,7 +22,6 @@ const queryOps = {
|
|
|
20
22
|
const availableBetaFlags = {
|
|
21
23
|
// enabled by --beta-mode
|
|
22
24
|
toRename: true,
|
|
23
|
-
addTextsLanguageAssoc: true,
|
|
24
25
|
assocsWithParams: true,
|
|
25
26
|
hanaAssocRealCardinality: true,
|
|
26
27
|
mapAssocToJoinCardinality: true,
|
|
@@ -29,6 +30,8 @@ const availableBetaFlags = {
|
|
|
29
30
|
enableUniversalCsn: true,
|
|
30
31
|
// disabled by --beta-mode
|
|
31
32
|
nestedServices: false,
|
|
33
|
+
odataOpenType: true,
|
|
34
|
+
optionalActionFunctionParameters: true,
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
/**
|
|
@@ -58,16 +61,56 @@ function isBetaEnabled( options, feature ) {
|
|
|
58
61
|
* Please do not move this function to the "option processor" code.
|
|
59
62
|
*
|
|
60
63
|
* @param {object} options Options
|
|
61
|
-
* @param {string} [feature] Feature to check for
|
|
64
|
+
* @param {string|null} [feature] Feature to check for
|
|
62
65
|
* @returns {boolean}
|
|
63
66
|
*/
|
|
64
67
|
function isDeprecatedEnabled( options, feature = null ) {
|
|
65
68
|
const { deprecated } = options;
|
|
66
69
|
if (!feature)
|
|
67
70
|
return !!deprecated;
|
|
71
|
+
|
|
68
72
|
return deprecated && typeof deprecated === 'object' && deprecated[feature];
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
const oldDeprecatedFlags_v2 = [
|
|
76
|
+
'createLocalizedViews',
|
|
77
|
+
'downgradableErrors',
|
|
78
|
+
'generatedEntityNameWithUnderscore',
|
|
79
|
+
'longAutoexposed',
|
|
80
|
+
'noElementsExpansion',
|
|
81
|
+
'noInheritedAutoexposeViaComposition',
|
|
82
|
+
'noScopedRedirections',
|
|
83
|
+
'oldVirtualNotNullPropagation',
|
|
84
|
+
'parensAsStrings',
|
|
85
|
+
'projectionAsQuery',
|
|
86
|
+
'redirectInSubQueries',
|
|
87
|
+
'renderVirtualElements',
|
|
88
|
+
'shortAutoexposed',
|
|
89
|
+
'unmanagedUpInComponent',
|
|
90
|
+
'v1KeysForTemporal',
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* In cds-compiler v3, we removed old v2 deprecated flags. That can lead to silent
|
|
95
|
+
* errors such as entity/view names changing. To ensure that the user is forced
|
|
96
|
+
* to change their code, emit an error if one of such removed flags was used.
|
|
97
|
+
*
|
|
98
|
+
* @param {CSN.Options} options
|
|
99
|
+
* @param error Error message function returned by makeMessageFunctions().
|
|
100
|
+
*/
|
|
101
|
+
function checkRemovedDeprecatedFlags( options, { error } ) {
|
|
102
|
+
// Assume that we emitted these errors once if a message with this ID was found.
|
|
103
|
+
if (!options.deprecated || options.messages?.some(m => m.messageId === 'api-invalid-deprecated'))
|
|
104
|
+
return;
|
|
105
|
+
|
|
106
|
+
forEach(options.deprecated, (key, val) => {
|
|
107
|
+
if (val && oldDeprecatedFlags_v2.includes(key)) {
|
|
108
|
+
error('api-invalid-deprecated', null, { name: key },
|
|
109
|
+
'Deprecated flag $(NAME) has been removed in CDS compiler v3');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
71
114
|
// Apply function `callback` to all artifacts in dictionary
|
|
72
115
|
// `model.definitions`. See function `forEachGeneric` for details.
|
|
73
116
|
function forEachDefinition( model, callback ) {
|
|
@@ -129,6 +172,7 @@ module.exports = {
|
|
|
129
172
|
isBetaEnabled,
|
|
130
173
|
availableBetaFlags,
|
|
131
174
|
isDeprecatedEnabled,
|
|
175
|
+
checkRemovedDeprecatedFlags,
|
|
132
176
|
queryOps,
|
|
133
177
|
forEachDefinition,
|
|
134
178
|
forEachMember,
|
|
@@ -153,36 +153,67 @@ function createOptionProcessor() {
|
|
|
153
153
|
* Internal: Define a general or command option.
|
|
154
154
|
* Throws if the option is already registered in the given command context.
|
|
155
155
|
* or in the given command.
|
|
156
|
+
*
|
|
156
157
|
* @private
|
|
157
158
|
* @see option()
|
|
158
159
|
*/
|
|
159
160
|
function _addOption(cmd, optString, validValues, options) {
|
|
160
|
-
const
|
|
161
|
-
Object.assign(
|
|
161
|
+
const cliOpt = _parseOptionString(optString, validValues);
|
|
162
|
+
Object.assign(cliOpt, options);
|
|
163
|
+
_addLongOption(cmd, cliOpt.longName, cliOpt);
|
|
164
|
+
_addShortOption(cmd, cliOpt.shortName, cliOpt);
|
|
165
|
+
|
|
166
|
+
for (const alias of cliOpt.aliases || []) {
|
|
167
|
+
const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
|
|
168
|
+
_addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return cmd;
|
|
172
|
+
}
|
|
162
173
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Internal: Add longName to the list of options.
|
|
176
|
+
* Throws if the option is already registered in the given command context.
|
|
177
|
+
* or in the given command.
|
|
178
|
+
* `longName` may differ from `opt.longName`, e.g. for aliases.
|
|
179
|
+
*
|
|
180
|
+
* @private
|
|
181
|
+
* @see _addOption()
|
|
182
|
+
*/
|
|
183
|
+
function _addLongOption(cmd, longName, opt) {
|
|
184
|
+
if (cmd.options[longName]) {
|
|
185
|
+
throw new Error(`Duplicate assignment for long option ${longName}`);
|
|
186
|
+
} else if (optionProcessor.options[longName]) {
|
|
166
187
|
// This path is only taken if optString is for commands
|
|
167
188
|
optionProcessor.optionClashes.push({
|
|
168
|
-
option:
|
|
169
|
-
description: `Command '${cmd.longName}' has option clash with general options for: ${
|
|
189
|
+
option: longName,
|
|
190
|
+
description: `Command '${cmd.longName}' has option clash with general options for: ${longName}`
|
|
170
191
|
});
|
|
171
192
|
}
|
|
172
|
-
cmd.options[
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
cmd.options[longName] = opt;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Internal: Add shortName to the list of options.
|
|
197
|
+
* Throws if the option is already registered in the given command context.
|
|
198
|
+
* or in the given command.
|
|
199
|
+
* `longName` may differ from `opt.longName`, e.g. for aliases.
|
|
200
|
+
*
|
|
201
|
+
* @private
|
|
202
|
+
* @see _addOption()
|
|
203
|
+
*/
|
|
204
|
+
function _addShortOption(cmd, shortName, opt) {
|
|
205
|
+
if (!shortName)
|
|
206
|
+
return;
|
|
207
|
+
if (cmd.options[shortName]) {
|
|
208
|
+
throw new Error(`Duplicate assignment for short option ${shortName}`);
|
|
209
|
+
} else if (optionProcessor.options[shortName]) {
|
|
210
|
+
// This path is only taken if optString is for commands
|
|
211
|
+
optionProcessor.optionClashes.push({
|
|
212
|
+
option: shortName,
|
|
213
|
+
description: `Command '${cmd.longName}' has option clash with general options for: ${shortName}`
|
|
214
|
+
});
|
|
184
215
|
}
|
|
185
|
-
|
|
216
|
+
cmd.options[shortName] = opt;
|
|
186
217
|
}
|
|
187
218
|
|
|
188
219
|
// Internal: Parse one command string like "F, toFoo". Return an object like this
|
|
@@ -275,7 +306,8 @@ function createOptionProcessor() {
|
|
|
275
306
|
shortName,
|
|
276
307
|
camelName: camelifyLongOption(longName),
|
|
277
308
|
param,
|
|
278
|
-
validValues
|
|
309
|
+
validValues,
|
|
310
|
+
isAlias: false, // default
|
|
279
311
|
}
|
|
280
312
|
}
|
|
281
313
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../model/csnUtils');
|
|
4
|
+
const { isBetaEnabled } = require('../base/model');
|
|
4
5
|
|
|
5
6
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
7
|
|
|
@@ -16,8 +17,8 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
16
17
|
if (!(art.kind === 'action' || art.kind === 'function') && !art.actions)
|
|
17
18
|
return;
|
|
18
19
|
|
|
19
|
-
// const isMultiSchema = this.options.
|
|
20
|
-
// (this.options.
|
|
20
|
+
// const isMultiSchema = this.options.odataFormat === 'structured' &&
|
|
21
|
+
// (this.options.odataProxies || this.options.odataXServiceRefs);
|
|
21
22
|
|
|
22
23
|
const serviceName = this.csnUtils.getServiceName(artName);
|
|
23
24
|
if (!serviceName)
|
|
@@ -52,13 +53,13 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
52
53
|
function checkActionOrFunctionParameter(param, currPath, actKind) {
|
|
53
54
|
const paramType = param.type ? this.csnUtils.getFinalTypeDef(param.type) : param;
|
|
54
55
|
|
|
55
|
-
if (param.default || paramType.default) {
|
|
56
|
-
this.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
if (!isBetaEnabled(this.options, 'optionalActionFunctionParameters') && (param.default || paramType.default)) {
|
|
57
|
+
this.message('param-default', currPath, { '#': actKind },
|
|
58
|
+
{
|
|
59
|
+
std: 'Artifact parameters can\'t have a default value', // Not used
|
|
60
|
+
action: 'Action parameters can\'t have a default value',
|
|
61
|
+
function: 'Function parameters can\'t have a default value',
|
|
62
|
+
});
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
if (paramType.type && this.csnUtils.isAssocOrComposition(param.type)) {
|
|
@@ -85,9 +86,11 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
85
86
|
* @param {string} actKind 'action' or 'function'
|
|
86
87
|
*/
|
|
87
88
|
function checkReturns(returns, currPath, actKind) {
|
|
88
|
-
const finalReturnType = returns.type ? this.csnUtils.
|
|
89
|
+
const finalReturnType = returns.type ? this.csnUtils.getFinalBaseTypeWithProps(returns.type) : returns;
|
|
90
|
+
if (!finalReturnType)
|
|
91
|
+
return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
|
|
89
92
|
|
|
90
|
-
if (this.csnUtils.isAssocOrComposition(finalReturnType)) {
|
|
93
|
+
if (this.csnUtils.isAssocOrComposition(finalReturnType.type)) {
|
|
91
94
|
this.error(null, currPath, { '#': actKind },
|
|
92
95
|
{
|
|
93
96
|
std: 'An association is not allowed as this artifact\'s return type', // Not used
|
|
@@ -98,7 +101,7 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
98
101
|
|
|
99
102
|
if (finalReturnType.items) // check array return type
|
|
100
103
|
checkReturns.bind(this)(finalReturnType.items, currPath.concat('items'), actKind);
|
|
101
|
-
else // check if return type is user
|
|
104
|
+
else // check if return type is user defined from the current service
|
|
102
105
|
checkUserDefinedType.bind(this)(finalReturnType, returns.type, currPath);
|
|
103
106
|
}
|
|
104
107
|
|
|
@@ -23,7 +23,7 @@ function checkCoreMediaTypeAllowence(member) {
|
|
|
23
23
|
'cds.hana.CLOB': 1,
|
|
24
24
|
'cds.hana.BINARY': 1,
|
|
25
25
|
};
|
|
26
|
-
if (member['@Core.MediaType'] && member.type && !(this.csnUtils.
|
|
26
|
+
if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type in allowedCoreMediaTypes)) {
|
|
27
27
|
this.warning(null, member.$path, { names: [ 'Edm.String', 'Edm.Binary' ] },
|
|
28
28
|
'Element annotated with “@Core.MediaType” should be of a type mapped to $(NAMES)');
|
|
29
29
|
}
|
|
@@ -14,6 +14,7 @@ function validateCdsPersistenceAnnotation(artifact, artifactName, prop, path) {
|
|
|
14
14
|
if (artifact.kind === 'entity') {
|
|
15
15
|
// filter for 'table', 'udf', 'calcview' === true
|
|
16
16
|
const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
|
|
17
|
+
// TODO: Why not filter over persistenceAnnos, is shorter!
|
|
17
18
|
const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);
|
|
18
19
|
if (TableUdfCv.length > 1)
|
|
19
20
|
this.error(null, path, `Annotations ${ TableUdfCv.join(', ') } can't be used in combination`);
|
package/lib/checks/elements.js
CHANGED
|
@@ -36,10 +36,10 @@ function checkPrimaryKey(art) {
|
|
|
36
36
|
*/
|
|
37
37
|
function checkIfPrimaryKeyIsOfGeoType(member, elemFqName, parentIsKey, parentPath) {
|
|
38
38
|
if (member.key || parentIsKey) {
|
|
39
|
-
const finalBaseType = this.csnUtils.
|
|
40
|
-
if (
|
|
39
|
+
const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
|
|
40
|
+
if (isGeoTypeName(finalBaseType?.type)) {
|
|
41
41
|
this.error(null, parentPath || member.$path,
|
|
42
|
-
{ type: finalBaseType, name: elemFqName },
|
|
42
|
+
{ type: finalBaseType.type, name: elemFqName },
|
|
43
43
|
'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
|
|
44
44
|
}
|
|
45
45
|
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
|
|
@@ -63,7 +63,7 @@ function checkPrimaryKey(art) {
|
|
|
63
63
|
*/
|
|
64
64
|
function checkIfPrimaryKeyIsArray(member, elemFqName, parentIsKey, parentPath) {
|
|
65
65
|
if (member.key || parentIsKey) {
|
|
66
|
-
const finalBaseType = this.csnUtils.
|
|
66
|
+
const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
|
|
67
67
|
if (member.items || (finalBaseType && finalBaseType.items)) {
|
|
68
68
|
this.error(null, parentPath || member.$path, { name: elemFqName },
|
|
69
69
|
'Array-like type in element $(NAME) can\'t be used as primary key');
|
|
@@ -96,10 +96,10 @@ function checkVirtualElement(member) {
|
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Checks whether managed associations
|
|
99
|
-
* with cardinality 'to many' have an on-condition
|
|
100
|
-
* and if managed associations have foreign keys.
|
|
99
|
+
* with cardinality 'to many' have an on-condition.
|
|
101
100
|
*
|
|
102
101
|
* @param {CSN.Artifact} art The artifact
|
|
102
|
+
* @todo this is a member validator, is it not?
|
|
103
103
|
*/
|
|
104
104
|
function checkManagedAssoc(art) {
|
|
105
105
|
forEachMemberRecursively(art, (member) => {
|
|
@@ -26,7 +26,7 @@ function invalidTarget(member) {
|
|
|
26
26
|
if (!target)
|
|
27
27
|
throw new ModelError(`Expected target ${ mem.target }`);
|
|
28
28
|
if (target.kind !== 'entity') {
|
|
29
|
-
const isAssoc = this.csnUtils.
|
|
29
|
+
const isAssoc = this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type !== 'cds.Composition';
|
|
30
30
|
this.error(
|
|
31
31
|
null,
|
|
32
32
|
member.$path,
|
|
@@ -21,7 +21,7 @@ function nonexpandableStructuredInExpression(parent, name, expression) {
|
|
|
21
21
|
if (_art) {
|
|
22
22
|
_art = resolveArtifactType.call(this, _art);
|
|
23
23
|
// Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
|
|
24
|
-
if (_art
|
|
24
|
+
if (_art?.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
|
|
25
25
|
this.error(null, expression[i].$path, { elemref: { ref } },
|
|
26
26
|
'Unexpected usage of structured type $(ELEMREF)');
|
|
27
27
|
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('../model/csnUtils');
|
|
4
4
|
/**
|
|
5
5
|
* Make sure that all source artifacts and association targets reach the database
|
|
6
|
-
* (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database
|
|
6
|
+
* (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database // <- what does this mean?
|
|
7
|
+
*
|
|
7
8
|
* Check the given query for:
|
|
8
9
|
* - Associations-traversal over skipped/abstract things
|
|
9
10
|
* - Associations (indirectly) using managed associations without foreign keys
|
|
@@ -1,33 +1,119 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachGeneric } = require('../model/csnUtils');
|
|
3
|
+
const { forEachGeneric, applyTransformationsOnNonDictionary } = require('../model/csnUtils');
|
|
4
4
|
|
|
5
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Validate select items of a query.
|
|
8
|
+
* Validate select items of a query. If a column reference starts with $self or $projection, it must not contain association steps.
|
|
9
|
+
* Furthermore, for to.hdbcds, window functions are not allowed.
|
|
10
|
+
*
|
|
11
|
+
* For to.hdbcds-hdbcds, structures and managed associations are not allowed as they are not flattened - @see rejectManagedAssociationsAndStructuresForHdbcdsNames
|
|
9
12
|
*
|
|
10
13
|
* @param {CSN.Query} query query object
|
|
14
|
+
* @todo Why do we care about this with $self?
|
|
11
15
|
*/
|
|
12
16
|
function validateSelectItems(query) {
|
|
13
17
|
const { SELECT } = query;
|
|
14
18
|
if (!SELECT)
|
|
15
19
|
return;
|
|
20
|
+
/**
|
|
21
|
+
* Check for a $self.<assoc> in columns etc. - since the $self.<assoc> references the "outside" view
|
|
22
|
+
* of the association, this is not allowed.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} queryPart Part of the query that is being checked
|
|
25
|
+
* @returns {Function} Function as callback for applyTransformations
|
|
26
|
+
*/
|
|
27
|
+
function checkRefForInvalid$Self(queryPart) {
|
|
28
|
+
const signalError = (error, parent, type) => {
|
|
29
|
+
if (queryPart === 'columns') {
|
|
30
|
+
error(null, parent.$path,
|
|
31
|
+
{ name: parent.ref[0], type },
|
|
32
|
+
'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
33
|
+
}
|
|
34
|
+
else if (queryPart === 'orderBy') {
|
|
35
|
+
error(null, parent.$path,
|
|
36
|
+
{ id: queryPart, type },
|
|
37
|
+
'Items of the $(ID)-clause must not contain path steps of type $(TYPE)');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
error(null, parent.$path,
|
|
41
|
+
{ id: queryPart, name: parent.ref[0], type },
|
|
42
|
+
'Items of the $(ID)-clause starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return function checkForInvalid$SelfInRef(parent) {
|
|
46
|
+
if (parent.ref && (parent.$scope === '$self' || parent.$scope === '$query')) {
|
|
47
|
+
const { _links } = parent;
|
|
48
|
+
for (let j = parent.$scope === '$self' ? 1 : 0; j < _links.length - 1; j++) {
|
|
49
|
+
if (_links[j].art.target) {
|
|
50
|
+
if (_links[j].art.on) {
|
|
51
|
+
// It's an unmanaged association - traversal is always forbidden
|
|
52
|
+
signalError(this.error, parent, _links[j].art.type);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// It's a managed association - access of the foreign keys is allowed
|
|
56
|
+
const nextRef = parent.ref[j + 1].id || parent.ref[j + 1];
|
|
57
|
+
if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
|
|
58
|
+
signalError(this.error, parent, _links[j].art.type);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const last = _links[_links.length - 1];
|
|
64
|
+
|
|
65
|
+
if (last.art.target && last.art.on) {
|
|
66
|
+
// It's an unmanaged association - traversal is always forbidden
|
|
67
|
+
signalError(this.error, parent, last.art.type);
|
|
68
|
+
} // managed is okay, can be handled via tuple expansion
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
16
72
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Check the given assoc filter for usage of $self - in an assoc-filter, you must only
|
|
75
|
+
* address things on the target side of the association, not from global scope.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} parent
|
|
78
|
+
* @param {string} prop
|
|
79
|
+
* @param {Array} where
|
|
80
|
+
*/
|
|
81
|
+
function checkFilterForInvalid$Self(parent, prop, where) {
|
|
82
|
+
where.forEach((whereStep) => {
|
|
83
|
+
if (whereStep.ref && ( whereStep.ref[0] === '$projection' || whereStep.ref[0] === '$self')) {
|
|
84
|
+
this.error('expr-where-unexpected-self', whereStep.$path,
|
|
85
|
+
{ name: whereStep.ref[0] },
|
|
86
|
+
'Path steps inside of filters must not start with $(NAME)');
|
|
24
87
|
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const aTCB = (parent, prop) => {
|
|
92
|
+
applyTransformationsOnNonDictionary(parent, prop, {
|
|
93
|
+
ref: checkRefForInvalid$Self(prop).bind(this),
|
|
94
|
+
where: checkFilterForInvalid$Self.bind(this),
|
|
95
|
+
}, { skipStandard: { on: true }, drillRef: true });
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const transformers = {
|
|
99
|
+
columns: aTCB,
|
|
100
|
+
groupBy: aTCB,
|
|
101
|
+
orderBy: aTCB,
|
|
102
|
+
having: aTCB,
|
|
103
|
+
where: aTCB,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (this.options.transformation === 'hdbcds') {
|
|
107
|
+
transformers.xpr = (parent) => {
|
|
108
|
+
if (parent.func) {
|
|
109
|
+
this.error(null, parent.$path,
|
|
110
|
+
'Window functions are not supported by SAP HANA CDS');
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
applyTransformationsOnNonDictionary(query, 'SELECT', transformers );
|
|
116
|
+
|
|
31
117
|
// .call() with 'this' to ensure we have access to the options
|
|
32
118
|
rejectManagedAssociationsAndStructuresForHdbcdsNames.call(this, SELECT, SELECT.$path);
|
|
33
119
|
}
|
package/lib/checks/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { getUtils,
|
|
3
|
+
const { getUtils, hasAnnotationValue } = require('../model/csnUtils');
|
|
4
4
|
|
|
5
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
6
|
|
|
@@ -55,7 +55,7 @@ function checkElementTypeDefinitionHasType(member, memberName, prop, path) {
|
|
|
55
55
|
|
|
56
56
|
// should only happen with csn input, not in cdl
|
|
57
57
|
if (!hasArtifactTypeInformation(member)) {
|
|
58
|
-
|
|
58
|
+
errorAboutMissingType(this.error, path, memberName, true);
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -96,7 +96,7 @@ function checkTypeDefinitionHasType(artifact, artifactName, prop, path) {
|
|
|
96
96
|
|
|
97
97
|
// should only happen with csn input, not in cdl
|
|
98
98
|
if (!hasArtifactTypeInformation(artifact)) {
|
|
99
|
-
|
|
99
|
+
errorAboutMissingType(this.error, path, artifactName);
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -129,8 +129,8 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
|
|
|
129
129
|
if (!artOrElement.type)
|
|
130
130
|
return;
|
|
131
131
|
|
|
132
|
-
const {
|
|
133
|
-
const typeOfType =
|
|
132
|
+
const { getFinalBaseTypeWithProps } = getUtils(model);
|
|
133
|
+
const typeOfType = getFinalBaseTypeWithProps(artOrElement.type);
|
|
134
134
|
|
|
135
135
|
if (typeOfType === null) {
|
|
136
136
|
if (artOrElement.type.ref) {
|
|
@@ -158,7 +158,7 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
|
|
|
158
158
|
* @param {string} name of the element or the artifact which is dubious
|
|
159
159
|
* @param {boolean} isElement indicates whether we are dealing with an element or an artifact
|
|
160
160
|
*/
|
|
161
|
-
function
|
|
161
|
+
function errorAboutMissingType(error, path, name, isElement = false) {
|
|
162
162
|
error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
|
|
163
163
|
std: 'Dubious type $(ART) without type information',
|
|
164
164
|
elm: 'Dubious element $(ART) without type information',
|
|
@@ -176,8 +176,7 @@ function warnAboutMissingType(error, path, name, isElement = false) {
|
|
|
176
176
|
*/
|
|
177
177
|
function hasArtifactTypeInformation(artifact) {
|
|
178
178
|
// When is what property set?
|
|
179
|
-
return
|
|
180
|
-
artifact.elements || // => `type A {}`
|
|
179
|
+
return artifact.elements || // => `type A {}`
|
|
181
180
|
artifact.items || // => `type A : array of Integer`
|
|
182
181
|
artifact.enum || // => `type A : Integer enum {}`, `type` also set
|
|
183
182
|
artifact.target || // => `type A : Association to B;`
|
package/lib/checks/utils.js
CHANGED
|
@@ -31,7 +31,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
|
|
|
31
31
|
return false;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Artifact is structured or a managed association/
|
|
34
|
+
* Artifact is structured or a managed association/composition
|
|
35
35
|
*
|
|
36
36
|
* @param {CSN.Artifact} art Artifact
|
|
37
37
|
* @returns {boolean} True if expandable
|
|
@@ -49,7 +49,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
|
|
|
49
49
|
*/
|
|
50
50
|
function resolveArtifactType(art) {
|
|
51
51
|
if (art && art.type && !isBuiltinType(art.type))
|
|
52
|
-
return this.
|
|
52
|
+
return this.csnUtils.getFinalBaseTypeWithProps(art.type);
|
|
53
53
|
|
|
54
54
|
return art;
|
|
55
55
|
}
|
package/lib/checks/validator.js
CHANGED
|
@@ -32,7 +32,6 @@ const { validateAssociationsInItems } = require('./arrayOfs');
|
|
|
32
32
|
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
33
33
|
const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
34
34
|
const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
|
|
35
|
-
const unknownMagic = require('./unknownMagic');
|
|
36
35
|
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
37
36
|
const {
|
|
38
37
|
checkSqlAnnotationOnArtifact,
|
|
@@ -62,7 +61,7 @@ const forHanaArtifactValidators
|
|
|
62
61
|
checkSqlAnnotationOnArtifact,
|
|
63
62
|
];
|
|
64
63
|
|
|
65
|
-
const forHanaCsnValidators = [ nonexpandableStructuredInExpression
|
|
64
|
+
const forHanaCsnValidators = [ nonexpandableStructuredInExpression ];
|
|
66
65
|
/**
|
|
67
66
|
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
68
67
|
*/
|
|
@@ -103,8 +102,9 @@ const commonMemberValidators
|
|
|
103
102
|
validateAssociationsInItems, checkForInvalidTarget,
|
|
104
103
|
checkVirtualElement, checkElementTypeDefinitionHasType ];
|
|
105
104
|
|
|
105
|
+
// TODO: checkManagedAssoc is a forEachMemberRecursively!
|
|
106
106
|
const commonArtifactValidators = [ checkTypeDefinitionHasType, checkPrimaryKey, checkManagedAssoc ];
|
|
107
|
-
|
|
107
|
+
// TODO: Does it make sense to run the on-condition check as part of a CSN validator?
|
|
108
108
|
const commonQueryValidators = [ validateMixinOnCondition ];
|
|
109
109
|
|
|
110
110
|
/**
|