@sap/cds-compiler 2.5.0 → 2.10.4
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 +191 -9
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +33 -3
- package/lib/api/main.js +29 -101
- package/lib/api/options.js +15 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +63 -9
- package/lib/base/messages.js +63 -21
- package/lib/base/model.js +2 -3
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +25 -7
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +16 -7
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +99 -42
- package/lib/compiler/index.js +73 -27
- package/lib/compiler/resolver.js +288 -157
- package/lib/compiler/shared.js +31 -11
- package/lib/edm/annotations/genericTranslation.js +182 -186
- package/lib/edm/csn2edm.js +103 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -114
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -1
- package/lib/gen/language.tokens +57 -53
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +770 -744
- package/lib/gen/languageLexer.tokens +49 -46
- package/lib/gen/languageParser.js +4713 -4279
- package/lib/json/from-csn.js +103 -45
- package/lib/json/to-csn.js +296 -117
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +21 -12
- package/lib/language/language.g4 +99 -31
- package/lib/main.d.ts +81 -3
- package/lib/main.js +30 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +329 -142
- package/lib/model/csnUtils.js +235 -58
- package/lib/model/enrichCsn.js +18 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +37 -20
- package/lib/optionProcessor.js +9 -3
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +112 -33
- package/lib/render/toHdbcds.js +134 -64
- package/lib/render/toSql.js +91 -38
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +29 -13
- package/lib/transform/db/draft.js +8 -6
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +284 -63
- package/lib/transform/forHanaNew.js +98 -381
- package/lib/transform/forOdataNew.js +21 -22
- package/lib/transform/localized.js +37 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +134 -78
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- package/lib/utils/string.js +0 -17
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { otherSideIsExpandableStructure, resolveArtifactType } = require('./utils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check the given expression for non-expandable structure usage
|
|
7
|
+
*
|
|
8
|
+
* @param {object} parent Object with the expression as a property
|
|
9
|
+
* @param {string} name Name of the expression property on parent
|
|
10
|
+
* @param {Array} expression Expression to check - .on .xpr .having and .where
|
|
11
|
+
*/
|
|
12
|
+
function nonexpandableStructuredInExpression(parent, name, expression) {
|
|
13
|
+
for (let i = 0; i < expression.length; i++) {
|
|
14
|
+
if (expression[i].ref) {
|
|
15
|
+
const { ref } = expression[i];
|
|
16
|
+
// eslint-disable-next-line prefer-const
|
|
17
|
+
let { _art, $scope } = expression[i];
|
|
18
|
+
if (!_art)
|
|
19
|
+
continue;
|
|
20
|
+
const validStructuredElement = otherSideIsExpandableStructure.call(this, expression, i);
|
|
21
|
+
if (_art) {
|
|
22
|
+
_art = resolveArtifactType.call(this, _art);
|
|
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.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
|
|
25
|
+
this.error(null, expression[i].$path, { elemref: { ref } },
|
|
26
|
+
'Unexpected usage of structured type $(ELEMREF)');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
on: nonexpandableStructuredInExpression,
|
|
35
|
+
having: nonexpandableStructuredInExpression,
|
|
36
|
+
where: nonexpandableStructuredInExpression,
|
|
37
|
+
xpr: nonexpandableStructuredInExpression,
|
|
38
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachGeneric
|
|
3
|
+
const { forEachGeneric } = require('../model/csnUtils');
|
|
4
|
+
const { otherSideIsExpandableStructure, resolveArtifactType } = require('./utils');
|
|
4
5
|
|
|
5
6
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
7
|
|
|
@@ -42,48 +43,6 @@ function otherSideIsValidDollarSelf(on, startIndex) {
|
|
|
42
43
|
return false;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
/**
|
|
46
|
-
* Check that the opposite operand to a relational term is something
|
|
47
|
-
* structured that can be used for tuple expansion. This can either be a
|
|
48
|
-
* real 'elements' thing or a managed association/composition with foreign keys.
|
|
49
|
-
*
|
|
50
|
-
* @param {Array} on the on condition which to check
|
|
51
|
-
* @param {number} startIndex the index of the relational term in the on condition array
|
|
52
|
-
* @returns {boolean} indicates whether the other side of a relational term is expandable
|
|
53
|
-
*/
|
|
54
|
-
function otherSideIsExpandableStructure(on, startIndex) {
|
|
55
|
-
if (on[startIndex - 1] && [ '=', '<', '>', '>=', '<=', '!=', '<>' ].includes(on[startIndex - 1]))
|
|
56
|
-
return isOk(resolveArtifactType.call(this, on[startIndex - 2]._art));
|
|
57
|
-
|
|
58
|
-
else if (on[startIndex + 1] && [ '=', '<', '>', '>=', '<=', '!=', '<>' ].includes(on[startIndex + 1]))
|
|
59
|
-
return isOk(resolveArtifactType.call(this, on[startIndex + 2]._art));
|
|
60
|
-
|
|
61
|
-
return false;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Artifact is structured or a managed association/compoisition
|
|
65
|
-
*
|
|
66
|
-
* @param {CSN.Artifact} art Artifact
|
|
67
|
-
* @returns {boolean} True if expandable
|
|
68
|
-
*/
|
|
69
|
-
function isOk(art) {
|
|
70
|
-
return !!(art && (art.elements || (art.target && art.keys)));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get the real type of an artifact
|
|
76
|
-
*
|
|
77
|
-
* @param {object} art Whatever _art by csnRefs can be - element or artifact
|
|
78
|
-
* @returns {object} final artifact type
|
|
79
|
-
*/
|
|
80
|
-
function resolveArtifactType(art) {
|
|
81
|
-
if (art && art.type && !isBuiltinType(art.type))
|
|
82
|
-
return this.getFinalBaseType(art);
|
|
83
|
-
|
|
84
|
-
return art;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
46
|
/**
|
|
88
47
|
* Validate an on-condition
|
|
89
48
|
*
|
|
@@ -100,6 +59,11 @@ function resolveArtifactType(art) {
|
|
|
100
59
|
*/
|
|
101
60
|
function validateOnCondition(member, memberName, property, path) {
|
|
102
61
|
if (member && member.on) {
|
|
62
|
+
// complain about nullability constraint on managed composition
|
|
63
|
+
if (member.targetAspect && {}.hasOwnProperty.call(member, 'notNull')) {
|
|
64
|
+
this.warning(null, path.concat([ 'on' ]),
|
|
65
|
+
'Unexpected nullability constraint defined on managed composition');
|
|
66
|
+
}
|
|
103
67
|
for (let i = 0; i < member.on.length; i++) {
|
|
104
68
|
if (member.on[i].ref) {
|
|
105
69
|
const { ref } = member.on[i];
|
|
@@ -148,8 +112,8 @@ function validateOnCondition(member, memberName, property, path) {
|
|
|
148
112
|
// 2) Path ends on an association (managed or unmanaged) and the other operand is a '$self'
|
|
149
113
|
|
|
150
114
|
// If this path ends structured or on an association, perform the check:
|
|
151
|
-
if ((_art.
|
|
152
|
-
!( /* 1) */ (_art.
|
|
115
|
+
if ((_art.target) &&
|
|
116
|
+
!( /* 1) */ (_art.target && _art.keys) && validStructuredElement ||
|
|
153
117
|
/* 2) */ (_art.target && validDollarSelf)) &&
|
|
154
118
|
!_art.virtual) {
|
|
155
119
|
this.error(null, onPath, { elemref: { ref } },
|
|
@@ -69,13 +69,31 @@ function checkQueryForNoDBArtifacts(query) {
|
|
|
69
69
|
const pathStep = obj.ref[i].id ? obj.ref[i].id : obj.ref[i];
|
|
70
70
|
const name = art.target ? art.target : pathStep;
|
|
71
71
|
if (!isPersistedOnDatabase(endArtifact)) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
const nextElement = obj.ref[i + 1];
|
|
73
|
+
/**
|
|
74
|
+
* if we only navigate to foreign keys of the managed association in a view, we do not need to join,
|
|
75
|
+
* thus we can produce the view even if the target of the association is not persisted
|
|
76
|
+
*
|
|
77
|
+
* @param {CSN.Element} assoc association in ref
|
|
78
|
+
* @param {string} nextStep the ref step following the association
|
|
79
|
+
* @returns {boolean} true if no join will be generated
|
|
80
|
+
*/
|
|
81
|
+
const isJoinRelevant = (assoc, nextStep) => {
|
|
82
|
+
if (!assoc.keys)
|
|
83
|
+
return true;
|
|
84
|
+
const isExposedColumnAssocOrComposition = this.csnUtils.isAssocOrComposition(obj._art.type);
|
|
85
|
+
return !assoc.keys
|
|
86
|
+
.some(fk => fk.ref[0] === nextStep && !isExposedColumnAssocOrComposition);
|
|
87
|
+
};
|
|
88
|
+
if (isJoinRelevant(art, nextElement)) {
|
|
89
|
+
const cdsPersistenceSkipped = hasAnnotationValue(endArtifact, '@cds.persistence.skip');
|
|
90
|
+
this.error( null, obj.$path, {
|
|
91
|
+
id: pathStep, elemref: obj, name, '#': cdsPersistenceSkipped ? 'std' : 'abstract',
|
|
92
|
+
}, {
|
|
93
|
+
std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
94
|
+
abstract: 'Unexpected “abstract” association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
95
|
+
} );
|
|
96
|
+
}
|
|
79
97
|
}
|
|
80
98
|
// check managed association to have foreign keys array filled
|
|
81
99
|
if (art.keys && leafCount(art) === 0) {
|
|
@@ -11,7 +11,7 @@ const { forEachGeneric } = require('../model/csnUtils');
|
|
|
11
11
|
*/
|
|
12
12
|
function validateSelectItems(query) {
|
|
13
13
|
const { SELECT } = query;
|
|
14
|
-
if (!SELECT
|
|
14
|
+
if (!SELECT)
|
|
15
15
|
return;
|
|
16
16
|
|
|
17
17
|
forEachGeneric(SELECT, 'columns', (selectItem) => {
|
|
@@ -24,5 +24,28 @@ function validateSelectItems(query) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
|
+
// .call() with 'this' to ensure we have access to the options
|
|
28
|
+
rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
|
|
27
29
|
}
|
|
28
|
-
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* For the to.hdbcds transformation with naming mode 'hdbcds', structures and managed associations are not flattened/resolved.
|
|
34
|
+
* It is therefore not possible to publish such elements in a view.
|
|
35
|
+
* This function iterates over all published elements of a query artifact and asserts that no such elements are published.
|
|
36
|
+
*
|
|
37
|
+
* @param {CSN.Artifact} queryArtifact the query artifact which should be checked
|
|
38
|
+
* @param {CSN.Path} artifactPath the path to that artifact
|
|
39
|
+
*/
|
|
40
|
+
function rejectManagedAssociationsAndStructuresForHdbcsNames(queryArtifact, artifactPath) {
|
|
41
|
+
if (this.options.transformation === 'hdbcds' && this.options.sqlMapping === 'hdbcds') {
|
|
42
|
+
forEachGeneric(queryArtifact, 'elements', (selectItem, elemName, prop, elementPath) => {
|
|
43
|
+
if (this.csnUtils.isManagedAssociationElement(selectItem))
|
|
44
|
+
this.error('query-unexpected-assoc-hdbcds', elementPath);
|
|
45
|
+
if (this.csnUtils.isStructured(selectItem))
|
|
46
|
+
this.error('query-unexpected-structure-hdbcds', elementPath);
|
|
47
|
+
}, artifactPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { validateSelectItems, rejectManagedAssociationsAndStructuresForHdbcsNames };
|
package/lib/checks/types.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { getUtils, isBuiltinType } = require('../model/csnUtils');
|
|
3
|
+
const { getUtils, isBuiltinType, 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
|
|
|
7
|
+
/**
|
|
8
|
+
* Scale must not be 'variable' or 'floating'
|
|
9
|
+
*
|
|
10
|
+
* scale property is always propagated
|
|
11
|
+
*
|
|
12
|
+
* @param {CSN.Element} member the element to be checked
|
|
13
|
+
* @param {string} memberName the elements name
|
|
14
|
+
* @param {string} prop which kind of member are we looking at -> only prop "elements"
|
|
15
|
+
* @param {CSN.Path} path the path to the member
|
|
16
|
+
*/
|
|
17
|
+
function checkDecimalScale(member, memberName, prop, path) {
|
|
18
|
+
if (hasAnnotationValue(this.artifact, '@cds.persistence.exists') ||
|
|
19
|
+
// skip is already filtered in validator, here for completeness
|
|
20
|
+
hasAnnotationValue(this.artifact, '@cds.persistence.skip'))
|
|
21
|
+
return;
|
|
22
|
+
if (member.scale && [ 'variable', 'floating' ].includes(member.scale))
|
|
23
|
+
this.error(null, path, { name: member.scale }, 'Unexpected scale $(NAME)');
|
|
24
|
+
}
|
|
25
|
+
|
|
7
26
|
/**
|
|
8
27
|
* View parameter for hana must be of scalar type
|
|
9
28
|
*
|
|
@@ -165,4 +184,9 @@ function hasArtifactTypeInformation(artifact) {
|
|
|
165
184
|
artifact.type; // => `type A : [type of] Integer`
|
|
166
185
|
}
|
|
167
186
|
|
|
168
|
-
module.exports = {
|
|
187
|
+
module.exports = {
|
|
188
|
+
checkTypeDefinitionHasType,
|
|
189
|
+
checkElementTypeDefinitionHasType,
|
|
190
|
+
checkTypeIsScalar,
|
|
191
|
+
checkDecimalScale,
|
|
192
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// We only care about the "wild" ones - $at is validated by the compiler
|
|
4
|
+
const magicVariables = {
|
|
5
|
+
$user: [
|
|
6
|
+
'id', // $user.id
|
|
7
|
+
'locale', // $user.locale
|
|
8
|
+
],
|
|
9
|
+
$session: [
|
|
10
|
+
// no valid ways for this
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check that the given ref does not use magic variables for which we don't have
|
|
16
|
+
* a valid way of rendering.
|
|
17
|
+
*
|
|
18
|
+
* Valid ways:
|
|
19
|
+
* - We know what to do -> $user.id on HANA
|
|
20
|
+
* - The user tells us what to do -> options.magicVars
|
|
21
|
+
*
|
|
22
|
+
* @param {object} parent Object with the ref as a property
|
|
23
|
+
* @param {string} name Name of the ref property on parent
|
|
24
|
+
* @param {Array} ref to check
|
|
25
|
+
*/
|
|
26
|
+
function unknownMagicVariable(parent, name, ref) {
|
|
27
|
+
if (parent.$scope && parent.$scope === '$magic') {
|
|
28
|
+
const [ head, ...rest ] = ref;
|
|
29
|
+
const tail = rest.join('.');
|
|
30
|
+
const magicVariable = magicVariables[head];
|
|
31
|
+
if (magicVariable && magicVariable.indexOf(tail) === -1)
|
|
32
|
+
this.error(null, parent.$location, { id: tail, elemref: parent }, 'Magic variable is not supported - path $(ELEMREF), step $(ID)');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
ref: unknownMagicVariable,
|
|
38
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isBuiltinType } = require('../model/csnUtils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Prepare the ref steps so that they are loggable
|
|
7
|
+
*
|
|
8
|
+
* @param {any} refStep part of a ref
|
|
9
|
+
* @returns {string} Loggable string
|
|
10
|
+
*/
|
|
11
|
+
function logReady(refStep) {
|
|
12
|
+
return refStep.id || refStep;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check that the opposite operand to a relational term is something
|
|
17
|
+
* structured that can be used for tuple expansion. This can either be a
|
|
18
|
+
* real 'elements' thing or a managed association/composition with foreign keys.
|
|
19
|
+
*
|
|
20
|
+
* @param {Array} on the on condition which to check
|
|
21
|
+
* @param {number} startIndex the index of the relational term in the on condition array
|
|
22
|
+
* @returns {boolean} indicates whether the other side of a relational term is expandable
|
|
23
|
+
*/
|
|
24
|
+
function otherSideIsExpandableStructure(on, startIndex) {
|
|
25
|
+
if (on[startIndex - 1] && [ '=', '<', '>', '>=', '<=', '!=', '<>' ].includes(on[startIndex - 1]))
|
|
26
|
+
return isOk(resolveArtifactType.call(this, on[startIndex - 2]._art));
|
|
27
|
+
|
|
28
|
+
else if (on[startIndex + 1] && [ '=', '<', '>', '>=', '<=', '!=', '<>' ].includes(on[startIndex + 1]))
|
|
29
|
+
return isOk(resolveArtifactType.call(this, on[startIndex + 2]._art));
|
|
30
|
+
|
|
31
|
+
return false;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Artifact is structured or a managed association/compoisition
|
|
35
|
+
*
|
|
36
|
+
* @param {CSN.Artifact} art Artifact
|
|
37
|
+
* @returns {boolean} True if expandable
|
|
38
|
+
*/
|
|
39
|
+
function isOk(art) {
|
|
40
|
+
return !!(art && (art.elements || (art.target && art.keys)));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the real type of an artifact
|
|
46
|
+
*
|
|
47
|
+
* @param {object} art Whatever _art by csnRefs can be - element or artifact
|
|
48
|
+
* @returns {object} final artifact type
|
|
49
|
+
*/
|
|
50
|
+
function resolveArtifactType(art) {
|
|
51
|
+
if (art && art.type && !isBuiltinType(art.type))
|
|
52
|
+
return this.getFinalBaseType(art);
|
|
53
|
+
|
|
54
|
+
return art;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
logReady,
|
|
59
|
+
otherSideIsExpandableStructure,
|
|
60
|
+
resolveArtifactType,
|
|
61
|
+
};
|
package/lib/checks/validator.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
forEachDefinition, forEachMemberRecursively, forAllQueries,
|
|
5
|
-
forEachMember, getNormalizedQuery, hasAnnotationValue,
|
|
5
|
+
forEachMember, getNormalizedQuery, hasAnnotationValue, applyTransformations,
|
|
6
6
|
} = require('../model/csnUtils');
|
|
7
7
|
const enrich = require('./enricher');
|
|
8
8
|
|
|
9
9
|
// forHana
|
|
10
|
-
const validateSelectItems = require('./selectItems');
|
|
11
|
-
const { rejectParamDefaultsInHanaCds } = require('./defaultValues');
|
|
10
|
+
const { validateSelectItems } = require('./selectItems');
|
|
11
|
+
const { rejectParamDefaultsInHanaCds, warnAboutDefaultOnAssociationForHanaCds } = require('./defaultValues');
|
|
12
12
|
const validateCdsPersistenceAnnotation = require('./cdsPersistence');
|
|
13
13
|
const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
|
|
14
14
|
const checkForEmptyOrOnlyVirtual = require('./emptyOrOnlyVirtual');
|
|
@@ -23,19 +23,28 @@ const {
|
|
|
23
23
|
// both
|
|
24
24
|
const { validateOnCondition, validateMixinOnCondition } = require('./onConditions');
|
|
25
25
|
const validateForeignKeys = require('./foreignKeys');
|
|
26
|
-
const {
|
|
26
|
+
const {
|
|
27
|
+
checkTypeDefinitionHasType, checkElementTypeDefinitionHasType,
|
|
28
|
+
checkTypeIsScalar, checkDecimalScale,
|
|
29
|
+
} = require('./types');
|
|
27
30
|
const { checkPrimaryKey, checkVirtualElement, checkManagedAssoc } = require('./elements');
|
|
28
31
|
const checkForInvalidTarget = require('./invalidTarget');
|
|
29
32
|
const { validateAssociationsInItems } = require('./arrayOfs');
|
|
30
33
|
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
31
34
|
const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
35
|
+
const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
|
|
36
|
+
const unknownMagic = require('./unknownMagic');
|
|
37
|
+
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
32
38
|
|
|
33
39
|
const forHanaMemberValidators
|
|
34
40
|
= [
|
|
35
41
|
// For HANA CDS specifically, reject any default parameter values, as these are not supported.
|
|
36
42
|
rejectParamDefaultsInHanaCds,
|
|
37
43
|
checkTypeIsScalar,
|
|
44
|
+
checkDecimalScale,
|
|
38
45
|
checkExplicitlyNullableKeys,
|
|
46
|
+
managedWithoutKeys,
|
|
47
|
+
warnAboutDefaultOnAssociationForHanaCds,
|
|
39
48
|
];
|
|
40
49
|
|
|
41
50
|
const forHanaArtifactValidators
|
|
@@ -46,8 +55,11 @@ const forHanaArtifactValidators
|
|
|
46
55
|
checkForEmptyOrOnlyVirtual,
|
|
47
56
|
];
|
|
48
57
|
|
|
49
|
-
const
|
|
50
|
-
|
|
58
|
+
const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];
|
|
59
|
+
/**
|
|
60
|
+
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
61
|
+
*/
|
|
62
|
+
const forHanaQueryValidators = [
|
|
51
63
|
// TODO reason why this is forHana exclusive
|
|
52
64
|
validateSelectItems,
|
|
53
65
|
checkQueryForNoDBArtifacts,
|
|
@@ -57,6 +69,7 @@ const forOdataMemberValidators
|
|
|
57
69
|
= [
|
|
58
70
|
// OData allows only simple values, no expressions or functions
|
|
59
71
|
validateDefaultValues,
|
|
72
|
+
managedWithoutKeys,
|
|
60
73
|
];
|
|
61
74
|
|
|
62
75
|
const forOdataArtifactValidators
|
|
@@ -75,6 +88,8 @@ const forOdataArtifactValidators
|
|
|
75
88
|
checkReadOnlyAndInsertOnly,
|
|
76
89
|
];
|
|
77
90
|
|
|
91
|
+
const forOdataCsnValidators = [ nonexpandableStructuredInExpression ];
|
|
92
|
+
|
|
78
93
|
const forOdataQueryValidators = [];
|
|
79
94
|
|
|
80
95
|
const commonMemberValidators
|
|
@@ -91,6 +106,7 @@ const commonQueryValidators = [ validateMixinOnCondition ];
|
|
|
91
106
|
*
|
|
92
107
|
* @param {CSN.Model} csn CSN to check
|
|
93
108
|
* @param {object} that Will be provided to the validators via "this"
|
|
109
|
+
* @param {object[]} [csnValidators=[]] Validations on whole CSN using applyTransformations
|
|
94
110
|
* @param {Function[]} [memberValidators=[]] Validations on member-level
|
|
95
111
|
* @param {Function[]} [artifactValidators=[]] Validations on artifact-level
|
|
96
112
|
* @param {Function[]} [queryValidators=[]] Validations on query-level
|
|
@@ -98,12 +114,15 @@ const commonQueryValidators = [ validateMixinOnCondition ];
|
|
|
98
114
|
* @returns {Function} Function taking no parameters, that cleans up the attached helpers
|
|
99
115
|
*/
|
|
100
116
|
function _validate(csn, that,
|
|
117
|
+
csnValidators = [],
|
|
101
118
|
memberValidators = [],
|
|
102
119
|
artifactValidators = [],
|
|
103
120
|
queryValidators = [],
|
|
104
121
|
iterateOptions = {}) {
|
|
105
122
|
const { cleanup } = enrich(csn);
|
|
106
123
|
|
|
124
|
+
applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], true, { drillRef: true });
|
|
125
|
+
|
|
107
126
|
forEachDefinition(csn, (artifact, artifactName, prop, path) => {
|
|
108
127
|
artifactValidators.forEach((artifactValidator) => {
|
|
109
128
|
artifactValidator.bind(that)(artifact, artifactName, prop, path);
|
|
@@ -118,12 +137,44 @@ function _validate(csn, that,
|
|
|
118
137
|
}
|
|
119
138
|
|
|
120
139
|
if (queryValidators.length && getNormalizedQuery(artifact).query)
|
|
121
|
-
forAllQueries(getNormalizedQuery(artifact).query, queryValidators.map(v => v.bind(that)), path.concat([ 'query' ]));
|
|
140
|
+
forAllQueries(getNormalizedQuery(artifact).query, queryValidators.map(v => v.bind(that)), path.concat([ artifact.projection ? 'projection' : 'query' ]));
|
|
122
141
|
}, iterateOptions);
|
|
123
142
|
|
|
124
143
|
return cleanup;
|
|
125
144
|
}
|
|
126
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Ensure the CSN validators adhere to the applyTransformation format - also, supply correct this value for each subfunction
|
|
148
|
+
*
|
|
149
|
+
* @param {object[]} csnValidators Validators
|
|
150
|
+
* @param {object} that Value for this
|
|
151
|
+
* @returns {object} Remapped validators.
|
|
152
|
+
*/
|
|
153
|
+
function mergeCsnValidators(csnValidators, that) {
|
|
154
|
+
const remapped = {};
|
|
155
|
+
for (const validator of csnValidators) {
|
|
156
|
+
for (const [ n, fns ] of Object.entries(validator)) {
|
|
157
|
+
if (!remapped[n])
|
|
158
|
+
remapped[n] = [];
|
|
159
|
+
|
|
160
|
+
if (Array.isArray(fns)) {
|
|
161
|
+
remapped[n].push((parent, name, prop, path) => fns.forEach(
|
|
162
|
+
fn => fn.bind(that)(parent, name, prop, path)
|
|
163
|
+
));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
remapped[n].push((parent, name, prop, path) => fns.bind(that)(parent, name, prop, path));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const [ n, fns ] of Object.entries(remapped))
|
|
172
|
+
remapped[n] = (parent, name, prop, path) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path));
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
return remapped;
|
|
176
|
+
}
|
|
177
|
+
|
|
127
178
|
/**
|
|
128
179
|
* @param {CSN.Model} csn CSN to check
|
|
129
180
|
* @param {object} that Will be provided to the validators via "this"
|
|
@@ -131,6 +182,7 @@ function _validate(csn, that,
|
|
|
131
182
|
*/
|
|
132
183
|
function forHana(csn, that) {
|
|
133
184
|
return _validate(csn, that,
|
|
185
|
+
forHanaCsnValidators,
|
|
134
186
|
forHanaMemberValidators.concat(commonMemberValidators),
|
|
135
187
|
forHanaArtifactValidators.concat(commonArtifactValidators).concat(
|
|
136
188
|
// why is this hana exclusive
|
|
@@ -160,6 +212,7 @@ function forHana(csn, that) {
|
|
|
160
212
|
*/
|
|
161
213
|
function forOdata(csn, that) {
|
|
162
214
|
return _validate(csn, that,
|
|
215
|
+
forOdataCsnValidators,
|
|
163
216
|
forOdataMemberValidators.concat(commonMemberValidators),
|
|
164
217
|
forOdataArtifactValidators.concat(commonArtifactValidators).concat(
|
|
165
218
|
(artifact, artifactName) => {
|
|
@@ -97,6 +97,7 @@ function assertConsistency( model, stage ) {
|
|
|
97
97
|
'_entities', '$entity',
|
|
98
98
|
'$blocks',
|
|
99
99
|
'$newfeatures',
|
|
100
|
+
'$messageFunctions',
|
|
100
101
|
],
|
|
101
102
|
},
|
|
102
103
|
':parser': { // top-level from parser
|
|
@@ -245,8 +246,8 @@ function assertConsistency( model, stage ) {
|
|
|
245
246
|
optional: [
|
|
246
247
|
'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
|
|
247
248
|
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
|
|
248
|
-
'_projections', '_block', '_parent', '_main', '_effectiveType',
|
|
249
|
-
'$tableAliases', 'kind', '_$next', '_combined',
|
|
249
|
+
'_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
|
|
250
|
+
'$tableAliases', 'kind', '_$next', '_combined', '$inlines',
|
|
250
251
|
],
|
|
251
252
|
},
|
|
252
253
|
none: { optional: () => true }, // parse error
|
|
@@ -310,6 +311,7 @@ function assertConsistency( model, stage ) {
|
|
|
310
311
|
rows: { inherits: 'value' },
|
|
311
312
|
offset: { inherits: 'value' },
|
|
312
313
|
_combined: { test: TODO },
|
|
314
|
+
$inlines: { test: TODO },
|
|
313
315
|
type: {
|
|
314
316
|
kind: true,
|
|
315
317
|
requires: [ 'location', 'path' ],
|
|
@@ -322,7 +324,7 @@ function assertConsistency( model, stage ) {
|
|
|
322
324
|
requires: [ 'location' ],
|
|
323
325
|
optional: [
|
|
324
326
|
'path', 'elements', '_outer',
|
|
325
|
-
'scope', '_artifact', '$inferred',
|
|
327
|
+
'scope', '_artifact', '$inferred', '$expand',
|
|
326
328
|
'_effectiveType', // by propagation
|
|
327
329
|
],
|
|
328
330
|
},
|
|
@@ -349,6 +351,7 @@ function assertConsistency( model, stage ) {
|
|
|
349
351
|
$delimited: { parser: true, test: isBoolean },
|
|
350
352
|
scope: { test: isScope },
|
|
351
353
|
func: { test: TODO },
|
|
354
|
+
suffix: { test: TODO },
|
|
352
355
|
kind: {
|
|
353
356
|
isRequired: !stageParser && (() => true),
|
|
354
357
|
// required to be set by Core Compiler even with parse errors
|
|
@@ -396,6 +399,7 @@ function assertConsistency( model, stage ) {
|
|
|
396
399
|
optional: [
|
|
397
400
|
'args',
|
|
398
401
|
'func',
|
|
402
|
+
'suffix',
|
|
399
403
|
'quantifier',
|
|
400
404
|
'$inferred',
|
|
401
405
|
'$parens',
|
|
@@ -424,7 +428,7 @@ function assertConsistency( model, stage ) {
|
|
|
424
428
|
struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
|
|
425
429
|
args: {
|
|
426
430
|
inherits: 'value',
|
|
427
|
-
optional: [ 'name', '$duplicate', '$expected' ],
|
|
431
|
+
optional: [ 'name', '$duplicate', '$expected', 'args', 'suffix' ],
|
|
428
432
|
test: args,
|
|
429
433
|
},
|
|
430
434
|
on: { kind: true, inherits: 'value', test: expression },
|
|
@@ -527,11 +531,11 @@ function assertConsistency( model, stage ) {
|
|
|
527
531
|
// query specific
|
|
528
532
|
'op', 'from', 'elements',
|
|
529
533
|
'_combined',
|
|
530
|
-
'$tableAliases',
|
|
534
|
+
'$tableAliases', '$inlines',
|
|
531
535
|
],
|
|
532
536
|
optional: [
|
|
533
537
|
'_effectiveType', '$parens',
|
|
534
|
-
'_deps',
|
|
538
|
+
'_deps', '$expand',
|
|
535
539
|
// query specific
|
|
536
540
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
537
541
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
@@ -572,13 +576,18 @@ function assertConsistency( model, stage ) {
|
|
|
572
576
|
$duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
|
|
573
577
|
$extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
|
|
574
578
|
$inferred: { parser: true, kind: true, test: isString },
|
|
575
|
-
|
|
579
|
+
|
|
580
|
+
// Helper property for the XSN-to-CSN transformation, see function setExpandStatus():
|
|
581
|
+
// client, universal: render expanded elements? gensrc: produce annotate statements?
|
|
582
|
+
$expand: { kind: true, test: isString }, // TODO: rename it to $elementsExpand ?
|
|
583
|
+
|
|
576
584
|
$autoexpose: { kind: [ 'entity' ], test: isBoolean, also: [ null, 'Composition' ] },
|
|
577
585
|
$a2j: { kind: true, enumerable: true, test: TODO },
|
|
578
586
|
$extra: { parser: true, test: TODO }, // for unexpected properties in CSN
|
|
579
587
|
$withLocalized: { test: isBoolean },
|
|
580
588
|
$sources: { parser: true, test: isArray( isString ) },
|
|
581
589
|
$expected: { parser: true, test: isString },
|
|
590
|
+
$messageFunctions: { test: TODO },
|
|
582
591
|
};
|
|
583
592
|
let _noSyntaxErrors = null;
|
|
584
593
|
assertProp( model, null, stageParser ? ':parser' : ':model', null, true );
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -83,6 +83,8 @@ const specialFunctions = {
|
|
|
83
83
|
const magicVariables = {
|
|
84
84
|
$user: {
|
|
85
85
|
elements: { id: {}, locale: {} },
|
|
86
|
+
// Allow $user.<any>
|
|
87
|
+
$uncheckedElements: true,
|
|
86
88
|
// Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
|
|
87
89
|
$autoElement: 'id',
|
|
88
90
|
}, // CDS-specific, not part of SQL
|
package/lib/compiler/checks.js
CHANGED
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
const { makeMessageFunction } = require('../base/messages');
|
|
17
16
|
// const { hasArtifactTypeInformation } = require('../model/csnUtils')
|
|
18
17
|
const builtins = require('../compiler/builtins');
|
|
19
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
forEachGeneric, forEachDefinition, forEachMember,
|
|
20
|
+
} = require('../base/model');
|
|
20
21
|
|
|
21
22
|
function check( model ) { // = XSN
|
|
22
|
-
const { options } = model;
|
|
23
23
|
const {
|
|
24
24
|
error, warning, message,
|
|
25
|
-
} =
|
|
25
|
+
} = model.$messageFunctions;
|
|
26
26
|
forEachDefinition( model, checkArtifact );
|
|
27
27
|
return;
|
|
28
28
|
|
|
@@ -66,6 +66,8 @@ function check( model ) { // = XSN
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function checkName( construct ) {
|
|
69
|
+
if (model.options.$skipNameCheck)
|
|
70
|
+
return;
|
|
69
71
|
// TODO: Move a corrected version of this check to definer (but do not rely
|
|
70
72
|
// on it!): The code below misses to consider CSN input.
|
|
71
73
|
if (construct.name.id && construct.name.id.indexOf('.') !== -1) {
|