@sap/cds-compiler 3.1.2 → 3.4.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 +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +53 -0
- package/lib/checks/defaultValues.js +4 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
- package/share/messages/wildcard-excluding-one.md +37 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
4
4
|
|
|
5
5
|
const { ModelError } = require('../base/error');
|
|
6
|
+
const { setProp } = require('../base/model');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Assert that targets of associations and compositions are entities.
|
|
@@ -38,18 +39,16 @@ function invalidTarget(member) {
|
|
|
38
39
|
);
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// elements have precedence over type
|
|
43
|
+
else if (mem.elements) {
|
|
44
|
+
handleStructured(mem);
|
|
44
45
|
}
|
|
45
|
-
else {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
else
|
|
52
|
-
checkForInvalidTarget(type);
|
|
46
|
+
else if (mem.type) {
|
|
47
|
+
const type = mem.type.ref ? this.artifactRef(mem.type) : this.csn.definitions[mem.type];
|
|
48
|
+
if (type && !type.$visited) {
|
|
49
|
+
setProp(type, '$visited', true);
|
|
50
|
+
checkForInvalidTarget(type);
|
|
51
|
+
delete type.$visited;
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { setProp } = require('../base/model');
|
|
4
|
+
|
|
3
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -35,18 +37,20 @@ function checkUsedTypesForAnonymousAspectComposition(member) {
|
|
|
35
37
|
}
|
|
36
38
|
else if (mem.type && (mem.type === 'cds.Composition') && !mem.on) {
|
|
37
39
|
if (!mem.target && mem.targetAspect && typeof mem.targetAspect !== 'string')
|
|
38
|
-
this.error(null, member.$path, 'Types with anonymous aspect compositions can\'t be used');
|
|
40
|
+
this.error(null, member.$path, {}, 'Types with anonymous aspect compositions can\'t be used');
|
|
39
41
|
}
|
|
40
42
|
else if (mem.elements) {
|
|
41
43
|
handleStructured(mem, assertNoAnonymousAspectComposition);
|
|
42
44
|
}
|
|
43
|
-
else if (mem.type
|
|
44
|
-
const type =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
else if (mem.type) {
|
|
46
|
+
const type = mem.type.ref
|
|
47
|
+
? this.artifactRef(mem.type)
|
|
48
|
+
: this.csn.definitions[mem.type];
|
|
49
|
+
if (type && !type.$visited) {
|
|
50
|
+
setProp(type, '$visited', true);
|
|
51
|
+
assertNoAnonymousAspectComposition(type);
|
|
52
|
+
delete type.$visited;
|
|
53
|
+
}
|
|
50
54
|
}
|
|
51
55
|
};
|
|
52
56
|
|
|
@@ -62,13 +66,15 @@ function checkUsedTypesForAnonymousAspectComposition(member) {
|
|
|
62
66
|
else if (mem.elements) {
|
|
63
67
|
handleStructured(mem, checkTypeUsages);
|
|
64
68
|
}
|
|
65
|
-
else if (mem.type
|
|
66
|
-
const type =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
else if (mem.type) { // type of
|
|
70
|
+
const type = mem.type.ref
|
|
71
|
+
? this.artifactRef(mem.type)
|
|
72
|
+
: this.csn.definitions[mem.type];
|
|
73
|
+
if (type && !type.$visited) {
|
|
74
|
+
setProp(type, '$visited', true);
|
|
75
|
+
assertNoAnonymousAspectComposition(type);
|
|
76
|
+
delete type.$visited;
|
|
77
|
+
}
|
|
72
78
|
}
|
|
73
79
|
};
|
|
74
80
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
function checkExplicitlyNullableKeys(element) {
|
|
9
9
|
if (element.key && element.notNull === false)
|
|
10
|
-
this.error(null, element.$path, 'Expecting primary key element to be not nullable');
|
|
10
|
+
this.error(null, element.$path, {}, 'Expecting primary key element to be not nullable');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
module.exports = checkExplicitlyNullableKeys;
|
|
@@ -61,7 +61,7 @@ function validateOnCondition(member, memberName, property, path) {
|
|
|
61
61
|
if (member && member.on) {
|
|
62
62
|
// complain about nullability constraint on managed composition
|
|
63
63
|
if (member.targetAspect && {}.hasOwnProperty.call(member, 'notNull')) {
|
|
64
|
-
this.warning(null, path.concat([ 'on' ]),
|
|
64
|
+
this.warning(null, path.concat([ 'on' ]), {},
|
|
65
65
|
'Unexpected nullability constraint defined on managed composition');
|
|
66
66
|
}
|
|
67
67
|
for (let i = 0; i < member.on.length; i++) {
|
|
@@ -83,24 +83,24 @@ function validateOnCondition(member, memberName, property, path) {
|
|
|
83
83
|
if (_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || (validDollarSelf && j === _links.length - 1))) {
|
|
84
84
|
if (_links[j].art.on) {
|
|
85
85
|
// It's an unmanaged association - traversal is always forbidden
|
|
86
|
-
this.error(null, csnPath, { id, elemref }, 'ON-
|
|
86
|
+
this.error(null, csnPath, { id, elemref }, 'ON-conditions can\'t follow unmanaged associations, step $(ID) of path $(ELEMREF)');
|
|
87
87
|
}
|
|
88
88
|
else {
|
|
89
89
|
// It's a managed association - access of the foreign keys is allowed
|
|
90
90
|
const nextRef = ref[j + 1].id || ref[j + 1];
|
|
91
91
|
if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
|
|
92
|
-
this.error(null, csnPath, { id, elemref }, 'ON-
|
|
92
|
+
this.error(null, csnPath, { id, elemref }, 'ON-conditions can only follow managed associations to the foreign keys of the managed association, step $(ID) of path $(ELEMREF)');
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
if (_links[j].art.virtual)
|
|
97
|
-
this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-
|
|
97
|
+
this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
|
|
98
98
|
|
|
99
99
|
if (ref[j].where)
|
|
100
|
-
this.error(null, csnPath, { id, elemref }, 'ON-
|
|
100
|
+
this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain filters, step $(ID) of path $(ELEMREF)');
|
|
101
101
|
|
|
102
102
|
if (ref[j].args)
|
|
103
|
-
this.error(null, csnPath, { id, elemref }, 'ON-
|
|
103
|
+
this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain parameters, step $(ID) of path $(ELEMREF)');
|
|
104
104
|
}
|
|
105
105
|
if (_art && $scope !== '$self') {
|
|
106
106
|
_art = resolveArtifactType.call(this, _art);
|
|
@@ -117,15 +117,15 @@ function validateOnCondition(member, memberName, property, path) {
|
|
|
117
117
|
/* 2) */ (_art.target && validDollarSelf)) &&
|
|
118
118
|
!_art.virtual) {
|
|
119
119
|
this.error(null, onPath, { elemref: { ref } },
|
|
120
|
-
'The last path of an
|
|
120
|
+
'The last path of an ON-condition must be a scalar value, path $(ELEMREF)');
|
|
121
121
|
}
|
|
122
122
|
else if (_art.items && !_art.virtual) {
|
|
123
123
|
this.error(null, onPath, { elemref: { ref } },
|
|
124
|
-
'ON-
|
|
124
|
+
'ON-conditions can\'t use array-like elements, path $(ELEMREF)');
|
|
125
125
|
}
|
|
126
126
|
else if (_art.virtual) {
|
|
127
127
|
this.error(null, onPath, { elemref: { ref } },
|
|
128
|
-
'Virtual elements can\'t be used in ON-
|
|
128
|
+
'Virtual elements can\'t be used in ON-conditions, path $(ELEMREF)');
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check that we don't have parameterized views - as we don't know yet how to represent them on postgres
|
|
7
|
+
*
|
|
8
|
+
* @param {object} parent Object with .params
|
|
9
|
+
* @param {string} name Name of the params property on parent
|
|
10
|
+
* @param {object} params params
|
|
11
|
+
* @param {CSN.Path} path
|
|
12
|
+
*/
|
|
13
|
+
function checkForParams(parent, name, params, path) {
|
|
14
|
+
const artifact = this.csn.definitions[path[1]];
|
|
15
|
+
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && !(parent.kind !== 'entity')) {
|
|
16
|
+
this.error('ref-unexpected-params', [ ...path, 'params' ], { value: this.options.sqlDialect },
|
|
17
|
+
'Parameterized views can\'t be used with sqlDialect $(VALUE)');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
params: checkForParams,
|
|
23
|
+
};
|
|
@@ -101,7 +101,7 @@ function checkQueryForNoDBArtifacts(query) {
|
|
|
101
101
|
this.error(null,
|
|
102
102
|
obj.$path,
|
|
103
103
|
{ id: pathStep, elemref: obj },
|
|
104
|
-
|
|
104
|
+
'Path step $(ID) of $(ELEMREF) has no foreign keys');
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
if (art.on) {
|
|
@@ -106,7 +106,7 @@ function validateSelectItems(query) {
|
|
|
106
106
|
if (this.options.transformation === 'hdbcds') {
|
|
107
107
|
transformers.xpr = (parent) => {
|
|
108
108
|
if (parent.func) {
|
|
109
|
-
this.error(null, parent.$path,
|
|
109
|
+
this.error(null, parent.$path, {},
|
|
110
110
|
'Window functions are not supported by SAP HANA CDS');
|
|
111
111
|
}
|
|
112
112
|
};
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
*/
|
|
14
14
|
function checkSqlAnnotationOnElement(member, memberName, prop, path) {
|
|
15
15
|
if (member['@sql.replace'])
|
|
16
|
-
this.error(null, path, { anno: 'sql.replace' },
|
|
16
|
+
this.error(null, path, { anno: 'sql.replace' }, 'Annotation $(ANNO) is reserved and must not be used');
|
|
17
17
|
if (member['@sql.prepend'])
|
|
18
|
-
this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' },
|
|
18
|
+
this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, 'Annotation $(ANNO) can\'t be used on elements' );
|
|
19
19
|
|
|
20
20
|
if (member['@sql.append']) {
|
|
21
21
|
if (this.artifact.query)
|
|
22
|
-
this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' },
|
|
22
|
+
this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on elements in views' );
|
|
23
23
|
else if (this.csnUtils.isStructured(member))
|
|
24
|
-
this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' },
|
|
24
|
+
this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
|
|
25
25
|
else
|
|
26
26
|
checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
|
|
27
27
|
}
|
|
@@ -37,7 +37,7 @@ function checkSqlAnnotationOnElement(member, memberName, prop, path) {
|
|
|
37
37
|
function checkValidAnnoValue(carrier, annotation, path, error, options) {
|
|
38
38
|
if (carrier[annotation] !== undefined && carrier[annotation] !== null) {
|
|
39
39
|
if (typeof carrier[annotation] !== 'string')
|
|
40
|
-
error(null, path, { anno: annotation.slice(1), type: typeof carrier[annotation] },
|
|
40
|
+
error(null, path, { anno: annotation.slice(1), type: typeof carrier[annotation] }, 'Annotation $(ANNO) must be a string, found $(TYPE)' );
|
|
41
41
|
else if (options.transformation === 'sql') // HDI and HDBCDS do their own checks
|
|
42
42
|
guardAgainstInjection(annotation, carrier[annotation], path, error);
|
|
43
43
|
}
|
|
@@ -52,20 +52,22 @@ function checkValidAnnoValue(carrier, annotation, path, error, options) {
|
|
|
52
52
|
function checkSqlAnnotationOnArtifact(artifact, artifactName) {
|
|
53
53
|
if (artifact.kind !== 'entity') {
|
|
54
54
|
if (artifact['@sql.prepend'])
|
|
55
|
-
this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.prepend', kind: artifact.kind },
|
|
55
|
+
this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.prepend', kind: artifact.kind }, 'Annotation $(NAME) can\'t be used on an artifact of kind $(KIND)' );
|
|
56
56
|
if (artifact['@sql.append'])
|
|
57
|
-
this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.append', kind: artifact.kind },
|
|
57
|
+
this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.append', kind: artifact.kind }, 'Annotation $(NAME) can\'t be used on an artifact of kind $(KIND)' );
|
|
58
58
|
}
|
|
59
59
|
else if (artifact['@sql.prepend']) {
|
|
60
60
|
if (artifact.query)
|
|
61
|
-
this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' },
|
|
61
|
+
this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, 'Annotation $(NAME) can\'t be used on views' );
|
|
62
62
|
else
|
|
63
63
|
checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
if (artifact['@sql.replace'])
|
|
68
|
-
this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' },
|
|
67
|
+
if (artifact['@sql.replace']) {
|
|
68
|
+
this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' },
|
|
69
|
+
'Annotation $(ANNO) is reserved and must not be used');
|
|
70
|
+
}
|
|
69
71
|
|
|
70
72
|
checkValidAnnoValue(artifact, '@sql.append', [ 'definitions', artifactName ], this.error, this.options);
|
|
71
73
|
}
|
package/lib/checks/types.js
CHANGED
|
@@ -33,7 +33,7 @@ function checkDecimalScale(member, memberName, prop, path) {
|
|
|
33
33
|
*/
|
|
34
34
|
function checkTypeIsScalar(member, memberName, prop, path) {
|
|
35
35
|
if ( prop === 'params' && this.csnUtils.isStructured(member))
|
|
36
|
-
this.error(null, path, 'View parameter type must be scalar');
|
|
36
|
+
this.error(null, path, {}, 'View parameter type must be scalar');
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -69,7 +69,7 @@ function checkElementTypeDefinitionHasType(member, memberName, prop, path) {
|
|
|
69
69
|
}
|
|
70
70
|
else if (member._type) {
|
|
71
71
|
if ( this.isAspect(member._type) || member._type.kind === 'type' && member._type.$syntax === 'aspect')
|
|
72
|
-
this.error('ref-sloppy-type', path, 'A type or an element is expected here');
|
|
72
|
+
this.error('ref-sloppy-type', path, {}, 'A type or an element is expected here');
|
|
73
73
|
}
|
|
74
74
|
return;
|
|
75
75
|
}
|
package/lib/checks/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../model/csnUtils');
|
|
4
|
-
|
|
4
|
+
const { RelationalOperators } = require('../transform/transformUtilsNew');
|
|
5
5
|
/**
|
|
6
6
|
* Prepare the ref steps so that they are loggable
|
|
7
7
|
*
|
|
@@ -17,17 +17,27 @@ function logReady(refStep) {
|
|
|
17
17
|
* structured that can be used for tuple expansion. This can either be a
|
|
18
18
|
* real 'elements' thing or a managed association/composition with foreign keys.
|
|
19
19
|
*
|
|
20
|
+
* The RHS may be 'null' or any value
|
|
21
|
+
*
|
|
20
22
|
* @param {Array} on the on condition which to check
|
|
21
23
|
* @param {number} startIndex the index of the relational term in the on condition array
|
|
22
24
|
* @returns {boolean} indicates whether the other side of a relational term is expandable
|
|
23
25
|
*/
|
|
24
26
|
function otherSideIsExpandableStructure(on, startIndex) {
|
|
25
|
-
if (on[startIndex - 1] &&
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
if (on[startIndex - 1] && RelationalOperators.includes(on[startIndex - 1])) {
|
|
28
|
+
const lhs = on[startIndex - 2];
|
|
29
|
+
// if ever lhs is allowed to be a value uncomment this
|
|
30
|
+
return /* lhs?.val !== undefined || */ isOk(resolveArtifactType.call(this, lhs?._art));
|
|
31
|
+
}
|
|
32
|
+
else if (on[startIndex + 1] && RelationalOperators.includes(on[startIndex + 1])) {
|
|
33
|
+
const op = on[startIndex + 1];
|
|
34
|
+
const rhs = on[startIndex + 2];
|
|
35
|
+
if (op === 'is')
|
|
36
|
+
// check for unary operator 'is [not] null' as token stream
|
|
37
|
+
return rhs === 'null' || (rhs === 'not' && on[startIndex + 3] === 'null');
|
|
38
|
+
// if ever rhs is allowed to be a value uncomment this
|
|
39
|
+
return /* rhs?.val !== undefined || */ isOk(resolveArtifactType.call(this, rhs?._art));
|
|
40
|
+
}
|
|
31
41
|
return false;
|
|
32
42
|
|
|
33
43
|
/**
|
package/lib/checks/validator.js
CHANGED
|
@@ -6,12 +6,14 @@ const {
|
|
|
6
6
|
} = require('../model/csnUtils');
|
|
7
7
|
const enrich = require('./enricher');
|
|
8
8
|
|
|
9
|
-
//
|
|
9
|
+
// forRelationalDB
|
|
10
10
|
const { validateSelectItems } = require('./selectItems');
|
|
11
11
|
const { rejectParamDefaultsInHanaCds, warnAboutDefaultOnAssociationForHanaCds } = require('./defaultValues');
|
|
12
12
|
const validateCdsPersistenceAnnotation = require('./cdsPersistence');
|
|
13
13
|
const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
|
|
14
14
|
const checkForEmptyOrOnlyVirtual = require('./emptyOrOnlyVirtual');
|
|
15
|
+
const checkForHanaTypes = require('./checkForTypes');
|
|
16
|
+
const checkForParams = require('./parameters');
|
|
15
17
|
// forOdata
|
|
16
18
|
const { validateDefaultValues } = require('./defaultValues');
|
|
17
19
|
const { checkActionOrFunction } = require('./actionsFunctions');
|
|
@@ -26,7 +28,9 @@ const {
|
|
|
26
28
|
checkTypeDefinitionHasType, checkElementTypeDefinitionHasType,
|
|
27
29
|
checkTypeIsScalar, checkDecimalScale,
|
|
28
30
|
} = require('./types');
|
|
29
|
-
const {
|
|
31
|
+
const {
|
|
32
|
+
checkPrimaryKey, checkVirtualElement, checkManagedAssoc, checkRecursiveTypeUsage,
|
|
33
|
+
} = require('./elements');
|
|
30
34
|
const checkForInvalidTarget = require('./invalidTarget');
|
|
31
35
|
const { validateAssociationsInItems } = require('./arrayOfs');
|
|
32
36
|
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
@@ -38,7 +42,7 @@ const {
|
|
|
38
42
|
checkSqlAnnotationOnElement,
|
|
39
43
|
} = require('./sql-snippets');
|
|
40
44
|
|
|
41
|
-
const
|
|
45
|
+
const forRelationalDBMemberValidators
|
|
42
46
|
= [
|
|
43
47
|
// For HANA CDS specifically, reject any default parameter values, as these are not supported.
|
|
44
48
|
rejectParamDefaultsInHanaCds,
|
|
@@ -51,7 +55,7 @@ const forHanaMemberValidators
|
|
|
51
55
|
checkSqlAnnotationOnElement,
|
|
52
56
|
];
|
|
53
57
|
|
|
54
|
-
const
|
|
58
|
+
const forRelationalDBArtifactValidators
|
|
55
59
|
= [
|
|
56
60
|
// @cds.persistence has no impact on odata
|
|
57
61
|
validateCdsPersistenceAnnotation,
|
|
@@ -61,12 +65,12 @@ const forHanaArtifactValidators
|
|
|
61
65
|
checkSqlAnnotationOnArtifact,
|
|
62
66
|
];
|
|
63
67
|
|
|
64
|
-
const
|
|
68
|
+
const forRelationalDBCsnValidators = [ nonexpandableStructuredInExpression ];
|
|
65
69
|
/**
|
|
66
70
|
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
67
71
|
*/
|
|
68
|
-
const
|
|
69
|
-
// TODO reason why this is
|
|
72
|
+
const forRelationalDBQueryValidators = [
|
|
73
|
+
// TODO reason why this is forRelationalDB exclusive
|
|
70
74
|
validateSelectItems,
|
|
71
75
|
checkQueryForNoDBArtifacts,
|
|
72
76
|
];
|
|
@@ -103,7 +107,12 @@ const commonMemberValidators
|
|
|
103
107
|
checkVirtualElement, checkElementTypeDefinitionHasType ];
|
|
104
108
|
|
|
105
109
|
// TODO: checkManagedAssoc is a forEachMemberRecursively!
|
|
106
|
-
const commonArtifactValidators = [
|
|
110
|
+
const commonArtifactValidators = [
|
|
111
|
+
checkTypeDefinitionHasType,
|
|
112
|
+
checkPrimaryKey,
|
|
113
|
+
checkManagedAssoc,
|
|
114
|
+
checkRecursiveTypeUsage,
|
|
115
|
+
];
|
|
107
116
|
// TODO: Does it make sense to run the on-condition check as part of a CSN validator?
|
|
108
117
|
const commonQueryValidators = [ validateMixinOnCondition ];
|
|
109
118
|
|
|
@@ -181,16 +190,29 @@ function mergeCsnValidators(csnValidators, that) {
|
|
|
181
190
|
return remapped;
|
|
182
191
|
}
|
|
183
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Depending on the dialect we need to run different validations.
|
|
195
|
+
*
|
|
196
|
+
* @param {CSN.Options} options
|
|
197
|
+
* @returns {any[]} Array of validator functions (or objects?)
|
|
198
|
+
*/
|
|
199
|
+
function getDBCsnValidators(options) {
|
|
200
|
+
if (options.sqlDialect === 'postgres' || options.sqlDialect === 'h2')
|
|
201
|
+
return [ ...forRelationalDBCsnValidators, checkForHanaTypes, checkForParams ];
|
|
202
|
+
|
|
203
|
+
return forRelationalDBCsnValidators;
|
|
204
|
+
}
|
|
205
|
+
|
|
184
206
|
/**
|
|
185
207
|
* @param {CSN.Model} csn CSN to check
|
|
186
208
|
* @param {object} that Will be provided to the validators via "this"
|
|
187
209
|
* @returns {Function} the validator function with the respective checks for the HANA backend
|
|
188
210
|
*/
|
|
189
|
-
function
|
|
211
|
+
function forRelationalDB(csn, that) {
|
|
190
212
|
return _validate(csn, that,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
213
|
+
getDBCsnValidators(that.options),
|
|
214
|
+
forRelationalDBMemberValidators.concat(commonMemberValidators),
|
|
215
|
+
forRelationalDBArtifactValidators.concat(commonArtifactValidators).concat(
|
|
194
216
|
// why is this hana exclusive
|
|
195
217
|
(artifact) => {
|
|
196
218
|
/* the validation itself performs a recursive check on structured elements.
|
|
@@ -200,7 +222,7 @@ function forHana(csn, that) {
|
|
|
200
222
|
forEachMember(artifact, checkUsedTypesForAnonymousAspectComposition.bind(that));
|
|
201
223
|
}
|
|
202
224
|
),
|
|
203
|
-
|
|
225
|
+
forRelationalDBQueryValidators.concat(commonQueryValidators),
|
|
204
226
|
{
|
|
205
227
|
skipArtifact: artifact => artifact.abstract ||
|
|
206
228
|
hasAnnotationValue(artifact, '@cds.persistence.skip') ||
|
|
@@ -235,4 +257,4 @@ function forOdata(csn, that) {
|
|
|
235
257
|
});
|
|
236
258
|
}
|
|
237
259
|
|
|
238
|
-
module.exports = {
|
|
260
|
+
module.exports = { forRelationalDB, forOdata };
|
|
@@ -229,11 +229,13 @@ function assertConsistency( model, stage ) {
|
|
|
229
229
|
elements: { kind: true, inherits: 'definitions', also: [ 0 ] }, // 0 for cyclic expansions
|
|
230
230
|
// specified elements in query entities (TODO: introduce real "specified elements" instead):
|
|
231
231
|
elements$: { kind: true, enumerable: false, test: TODO },
|
|
232
|
+
enum$: { kind: true, enumerable: false, test: TODO },
|
|
232
233
|
actions: { kind: true, inherits: 'definitions' },
|
|
233
234
|
enum: { kind: true, inherits: 'definitions' },
|
|
234
235
|
foreignKeys: { kind: true, inherits: 'definitions' },
|
|
235
236
|
$keysNavigation: { kind: true, test: TODO },
|
|
236
237
|
params: { kind: true, inherits: 'definitions' },
|
|
238
|
+
_extendType: { kind: true, test: TODO },
|
|
237
239
|
mixin: { inherits: 'definitions' },
|
|
238
240
|
query: {
|
|
239
241
|
kind: true,
|
|
@@ -293,7 +295,7 @@ function assertConsistency( model, stage ) {
|
|
|
293
295
|
none: { optional: () => true }, // parse error
|
|
294
296
|
},
|
|
295
297
|
columns: {
|
|
296
|
-
kind: [ 'extend' ],
|
|
298
|
+
kind: [ 'extend', '$column' ],
|
|
297
299
|
test: isArray( column ),
|
|
298
300
|
optional: thoseWithKind,
|
|
299
301
|
enum: [ '*' ],
|
|
@@ -307,6 +309,7 @@ function assertConsistency( model, stage ) {
|
|
|
307
309
|
kind: 'element',
|
|
308
310
|
test: isDictionary( definition ), // definition since redef
|
|
309
311
|
requires: [ 'location', 'name' ],
|
|
312
|
+
optional: [ '$duplicates' ],
|
|
310
313
|
},
|
|
311
314
|
orderBy: { inherits: 'value', test: isArray( expression ) },
|
|
312
315
|
sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
|
|
@@ -330,7 +333,7 @@ function assertConsistency( model, stage ) {
|
|
|
330
333
|
requires: [ 'location' ],
|
|
331
334
|
optional: [
|
|
332
335
|
'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
|
|
333
|
-
'scope', '_artifact', '$inferred', '$expand', '$tableAliases', '_$next',
|
|
336
|
+
'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
|
|
334
337
|
'_effectiveType', // by propagation
|
|
335
338
|
],
|
|
336
339
|
},
|
|
@@ -360,12 +363,13 @@ function assertConsistency( model, stage ) {
|
|
|
360
363
|
suffix: { test: TODO },
|
|
361
364
|
kind: {
|
|
362
365
|
isRequired: !stageParser && (() => true),
|
|
366
|
+
kind: true,
|
|
363
367
|
// required to be set by Core Compiler even with parse errors
|
|
364
368
|
test: isString,
|
|
365
369
|
enum: [
|
|
366
370
|
'context', 'service', 'entity', 'type', 'aspect', 'const', 'annotation',
|
|
367
371
|
'element', 'enum', 'action', 'function', 'param', 'key', 'event',
|
|
368
|
-
'annotate', 'extend',
|
|
372
|
+
'annotate', 'extend', '$column',
|
|
369
373
|
'select', '$join', 'mixin',
|
|
370
374
|
'source', 'namespace', 'using',
|
|
371
375
|
'$tableAlias', '$navElement',
|
|
@@ -395,7 +399,7 @@ function assertConsistency( model, stage ) {
|
|
|
395
399
|
requires: [ 'location', 'path' ],
|
|
396
400
|
optional: [ 'scope', 'variant', '_artifact', '$inferred', '$parens', 'sort', 'nulls' ],
|
|
397
401
|
},
|
|
398
|
-
none: { optional:
|
|
402
|
+
none: { optional: () => true }, // parse error
|
|
399
403
|
// TODO: why optional / enough in name?
|
|
400
404
|
// TODO: "yes" instead "none": val: true, optional literal/location
|
|
401
405
|
val: {
|
|
@@ -504,8 +508,8 @@ function assertConsistency( model, stage ) {
|
|
|
504
508
|
optional: [
|
|
505
509
|
'enum',
|
|
506
510
|
'elements', 'cardinality', 'target', 'on', 'foreignKeys', 'items',
|
|
507
|
-
'_outer', '_effectiveType', 'notNull',
|
|
508
|
-
'_origin', '_block', '$inferred', '$expand', '_deps',
|
|
511
|
+
'_outer', '_effectiveType', 'notNull', '_parent',
|
|
512
|
+
'_origin', '_block', '$inferred', '$expand', '$inCycle', '_deps',
|
|
509
513
|
'$syntax',
|
|
510
514
|
'_status', '_redirected',
|
|
511
515
|
...typeProperties,
|
|
@@ -602,11 +606,13 @@ function assertConsistency( model, stage ) {
|
|
|
602
606
|
kind: true,
|
|
603
607
|
test: isOneOf([
|
|
604
608
|
// Uppercase values are used in logic, lowercase value are "just for us", i.e.
|
|
605
|
-
// debugging or to add
|
|
609
|
+
// debugging or to add properties such as $generated in Universal CSN.
|
|
606
610
|
// However, that is no longer true. For example, `autoexposed` is used in populate.js
|
|
607
611
|
// as well.
|
|
608
612
|
'IMPLICIT',
|
|
609
613
|
'REDIRECTED',
|
|
614
|
+
'NULL', // from propagator
|
|
615
|
+
'prop', // from propagator
|
|
610
616
|
|
|
611
617
|
'$autoElement', // for magicVars: $user is automatically changed to $user.id
|
|
612
618
|
'$generated', // compiler generated annotations, e.g. @Core.Computed
|
|
@@ -618,8 +624,7 @@ function assertConsistency( model, stage ) {
|
|
|
618
624
|
'composition-entity',
|
|
619
625
|
'copy', // only used in rewriteCondition(): On-condition is copied
|
|
620
626
|
'duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
|
|
621
|
-
'
|
|
622
|
-
'expand-param', // expanded params (difference to expand-element only for debugging)
|
|
627
|
+
'expanded', // expanded elements, items, params
|
|
623
628
|
'include', // through includes, e.g. `entity E : F {}`
|
|
624
629
|
'keys',
|
|
625
630
|
'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
|
|
@@ -628,6 +633,8 @@ function assertConsistency( model, stage ) {
|
|
|
628
633
|
'none', // only used in ensureColumnName(): Used in object representing empty alias
|
|
629
634
|
'query', // inferred query properties, e.g. `key`
|
|
630
635
|
'rewrite', // on-conditions or FKeys are rewritten
|
|
636
|
+
'parent-origin', // annotation/property copied from parent that does not come through
|
|
637
|
+
// $origin and is not a direct annotation
|
|
631
638
|
]),
|
|
632
639
|
},
|
|
633
640
|
|
|
@@ -639,6 +646,7 @@ function assertConsistency( model, stage ) {
|
|
|
639
646
|
// See description of `setExpandStatus()` of in `lib/compiler/utils.js`.
|
|
640
647
|
test: isOneOf([ 'origin', 'annotate', 'target' ]),
|
|
641
648
|
},
|
|
649
|
+
$inCycle: { kind: true, test: isBoolean },
|
|
642
650
|
|
|
643
651
|
$autoexpose: { kind: [ 'entity' ], test: isBoolean, also: [ null, 'Composition' ] },
|
|
644
652
|
$extra: { parser: true, test: TODO }, // for unexpected properties in CSN
|
|
@@ -814,7 +822,7 @@ function assertConsistency( model, stage ) {
|
|
|
814
822
|
const sub = Object.assign( {}, s.inherits && schema[s.inherits], s );
|
|
815
823
|
if (spec.requires && sub.requires)
|
|
816
824
|
sub.requires = [ ...sub.requires, ...spec.requires ];
|
|
817
|
-
if (spec.optional && sub.optional)
|
|
825
|
+
if (Array.isArray( spec.optional ) && Array.isArray( sub.optional ))
|
|
818
826
|
sub.optional = [ ...sub.optional, ...spec.optional ];
|
|
819
827
|
// console.log(expressionSpec(node) );
|
|
820
828
|
(sub.test || standard)( node, parent, prop, sub, idx );
|
|
@@ -827,9 +835,9 @@ function assertConsistency( model, stage ) {
|
|
|
827
835
|
return 'val';
|
|
828
836
|
else if (node.query)
|
|
829
837
|
return 'query';
|
|
830
|
-
else if (
|
|
831
|
-
return '
|
|
832
|
-
return '
|
|
838
|
+
else if (node.op)
|
|
839
|
+
return 'op';
|
|
840
|
+
return 'none'; // parse error
|
|
833
841
|
}
|
|
834
842
|
|
|
835
843
|
function args( node, parent, prop, spec ) {
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -19,6 +19,10 @@ const core = {
|
|
|
19
19
|
DecimalFloat: { category: 'decimal', deprecated: true },
|
|
20
20
|
Integer64: { category: 'integer' },
|
|
21
21
|
Integer: { category: 'integer' },
|
|
22
|
+
UInt8: { category: 'integer' },
|
|
23
|
+
Int16: { category: 'integer' },
|
|
24
|
+
Int32: { category: 'integer' },
|
|
25
|
+
Int64: { category: 'integer' },
|
|
22
26
|
Double: { category: 'decimal' },
|
|
23
27
|
Date: { category: 'dateTime' },
|
|
24
28
|
Time: { category: 'dateTime' },
|
|
@@ -54,6 +58,7 @@ const typeParameters = {
|
|
|
54
58
|
srid: [ 'number' ],
|
|
55
59
|
},
|
|
56
60
|
};
|
|
61
|
+
// a.k.a "typeProperties"
|
|
57
62
|
typeParameters.list = Object.keys( typeParameters.expectedLiteralsFor );
|
|
58
63
|
|
|
59
64
|
// const hana = {
|
|
@@ -137,6 +142,7 @@ const specialFunctions = compileFunctions( {
|
|
|
137
142
|
separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
|
|
138
143
|
},
|
|
139
144
|
],
|
|
145
|
+
SUBSTR_REGEXPR: 'SUBSTRING_REGEXPR',
|
|
140
146
|
} );
|
|
141
147
|
|
|
142
148
|
function compileFunctions( special ) {
|
|
@@ -364,6 +370,8 @@ function isInReservedNamespace(absolute) {
|
|
|
364
370
|
* check against their absolute names. Builtin types are "cds.<something>", i.e. they
|
|
365
371
|
* are directly in 'cds', but not for example in 'cds.foundation'.
|
|
366
372
|
*
|
|
373
|
+
* TODO: Handle `{ ref: [ "cds.Integer" ] }`
|
|
374
|
+
*
|
|
367
375
|
* @param {string} type
|
|
368
376
|
* @returns {boolean}
|
|
369
377
|
*/
|