@sap/cds-compiler 2.4.4 → 2.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- 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 +27 -9
- 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 +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- 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/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
package/lib/base/model.js
CHANGED
|
@@ -19,7 +19,6 @@ const queryOps = {
|
|
|
19
19
|
*/
|
|
20
20
|
const availableBetaFlags = {
|
|
21
21
|
// enabled by --beta-mode
|
|
22
|
-
keylessManagedAssoc: true,
|
|
23
22
|
foreignKeyConstraints: true,
|
|
24
23
|
toRename: true,
|
|
25
24
|
addTextsLanguageAssoc: true,
|
|
@@ -28,9 +27,9 @@ const availableBetaFlags = {
|
|
|
28
27
|
mapAssocToJoinCardinality: true,
|
|
29
28
|
ignoreAssocPublishingInUnion: true,
|
|
30
29
|
nestedProjections: true,
|
|
30
|
+
enableUniversalCsn: true,
|
|
31
|
+
windowFunctions: true,
|
|
31
32
|
// disabled by --beta-mode
|
|
32
|
-
pretransformedCSN: false,
|
|
33
|
-
renderSQL: false,
|
|
34
33
|
nestedServices: false,
|
|
35
34
|
};
|
|
36
35
|
|
|
@@ -181,63 +180,6 @@ function setProp (obj, prop, value) {
|
|
|
181
180
|
return value;
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
// FIXME: this function is only used by old tests -- REMOVE!
|
|
185
|
-
//
|
|
186
|
-
// Clone 'node', transforming nodes therein recursively. Object 'transformers' is expected
|
|
187
|
-
// to contain a mapping of property 'key' names to transformer functions. The node's properties
|
|
188
|
-
// are walked recursively, calling each transformer function on its corresponding property
|
|
189
|
-
// 'key' of 'node', replacing 'value' in 'resultNode' with the function's return value
|
|
190
|
-
// (returning 'undefined' will delete the property).
|
|
191
|
-
// If no transformation function is found for 'key', the first letter of 'key' is tried
|
|
192
|
-
// instead (this seems to be intended for handling annotations that start with '@' ?)
|
|
193
|
-
// FIXME: Do we really need this ?
|
|
194
|
-
// If `withArtifactLink` is set, the `_artifact` links of `node` are copied too (not cloned,
|
|
195
|
-
// of course).
|
|
196
|
-
// Regardless of their names, transformers are never applied to dictionary elements.
|
|
197
|
-
//
|
|
198
|
-
// The transformer functions are called with the following signature:
|
|
199
|
-
// transformer(value, node, resultNode, key)
|
|
200
|
-
function cloneWithTransformations(node, transformers, withArtifactLink) {
|
|
201
|
-
|
|
202
|
-
return transformNode(node);
|
|
203
|
-
|
|
204
|
-
// This general transformation function will be applied to each node recursively
|
|
205
|
-
function transformNode(node) {
|
|
206
|
-
// Return primitive values and null unchanged, but let objects and dictionaries through
|
|
207
|
-
// (Note that 'node instanceof Object' would be false for dictionaries).
|
|
208
|
-
if (node === null || typeof node !== 'object') {
|
|
209
|
-
return node
|
|
210
|
-
}
|
|
211
|
-
// Simply return if node is to be ignored
|
|
212
|
-
if (node._ignore)
|
|
213
|
-
return undefined;
|
|
214
|
-
// Transform arrays element-wise
|
|
215
|
-
if (Array.isArray(node)) {
|
|
216
|
-
return node.map(transformNode);
|
|
217
|
-
}
|
|
218
|
-
// Things not having 'proto' are dictionaries
|
|
219
|
-
const proto = Object.getPrototypeOf(node);
|
|
220
|
-
// Iterate own properties of 'node' and transform them into 'resultNode'
|
|
221
|
-
const resultNode = Object.create(proto);
|
|
222
|
-
for (let key of Object.keys(node)) {
|
|
223
|
-
// Dictionary always use transformNode(), other objects their transformer according to key
|
|
224
|
-
const transformer = !proto ? transformNode : transformers[key] || transformers[key.charAt(0)];
|
|
225
|
-
// Apply transformer, or use transformNode() if there is none
|
|
226
|
-
const resultValue = (transformer || transformNode)(node[key], node, resultNode, key);
|
|
227
|
-
if (resultValue !== undefined) {
|
|
228
|
-
resultNode[key] = resultValue;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// For non-dictionaries, take over `_artifact` if requested
|
|
232
|
-
if (withArtifactLink && proto) {
|
|
233
|
-
let pd = Object.getOwnPropertyDescriptor(node, '_artifact')
|
|
234
|
-
if (pd)
|
|
235
|
-
Object.defineProperty(resultNode, '_artifact', pd);
|
|
236
|
-
}
|
|
237
|
-
return resultNode;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
183
|
|
|
242
184
|
module.exports = {
|
|
243
185
|
isBetaEnabled,
|
|
@@ -252,5 +194,4 @@ module.exports = {
|
|
|
252
194
|
forEachGeneric,
|
|
253
195
|
forEachInOrder,
|
|
254
196
|
setProp,
|
|
255
|
-
cloneWithTransformations,
|
|
256
197
|
};
|
package/lib/checks/arrayOfs.js
CHANGED
|
@@ -35,8 +35,33 @@ function validateDefaultValues(member, memberName, prop, path) {
|
|
|
35
35
|
* @param {CSN.Path} path Path to the member
|
|
36
36
|
*/
|
|
37
37
|
function rejectParamDefaultsInHanaCds(member, memberName, prop, path) {
|
|
38
|
-
if (member.default && prop === 'params' && this.options.
|
|
38
|
+
if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
|
|
39
39
|
this.error(null, path, 'Parameter default values are not supported in SAP HANA CDS');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* For HANA CDS, we render a default for a mixin if the projected entity contains
|
|
44
|
+
* a derived association with a default defined on it. This leads to a deployment error
|
|
45
|
+
* and should be warned about.
|
|
46
|
+
*
|
|
47
|
+
* @param {CSN.Element} member Member to validate
|
|
48
|
+
* @param {string} memberName Name of the member
|
|
49
|
+
* @param {string} prop Property being looped over
|
|
50
|
+
* @param {CSN.Path} path Path to the member
|
|
51
|
+
*/
|
|
52
|
+
function warnAboutDefaultOnAssociationForHanaCds(member, memberName, prop, path) {
|
|
53
|
+
const art = this.csn.definitions[path[1]];
|
|
54
|
+
if (!art.query && this.options.transformation === 'hdbcds' && member.target && member.default) {
|
|
55
|
+
this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },
|
|
56
|
+
{
|
|
57
|
+
std: 'Unexpected default defined on association',
|
|
58
|
+
comp: 'Unexpected default defined on composition',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
validateDefaultValues,
|
|
65
|
+
rejectParamDefaultsInHanaCds,
|
|
66
|
+
warnAboutDefaultOnAssociationForHanaCds,
|
|
67
|
+
};
|
package/lib/checks/elements.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { forEachMember, forEachMemberRecursively } = require('../model/csnUtils');
|
|
4
4
|
const { isGeoTypeName } = require('../compiler/builtins');
|
|
5
|
-
const { isBetaEnabled } = require('../base/model');
|
|
6
5
|
|
|
7
6
|
// Only to be used with validator.js - a correct `this` value needs to be provided!
|
|
8
7
|
|
|
@@ -103,15 +102,11 @@ function checkVirtualElement(member) {
|
|
|
103
102
|
* @param {CSN.Artifact} art The artifact
|
|
104
103
|
*/
|
|
105
104
|
function checkManagedAssoc(art) {
|
|
106
|
-
forEachMemberRecursively(art, (member
|
|
105
|
+
forEachMemberRecursively(art, (member) => {
|
|
107
106
|
if (this.csnUtils.isAssocOrComposition(member.type) &&
|
|
108
107
|
!isManagedComposition.bind(this)(member)) {
|
|
109
108
|
if (member.on)
|
|
110
109
|
return;
|
|
111
|
-
if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && (!member.keys || member.keys.length === 0)) {
|
|
112
|
-
this.error(null, member.$path, { name: memberName },
|
|
113
|
-
`The managed association $(NAME) has no foreign keys`);
|
|
114
|
-
}
|
|
115
110
|
const max = member.cardinality && member.cardinality.max;
|
|
116
111
|
if (max === '*' || Number(max) > 1) {
|
|
117
112
|
const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
|
package/lib/checks/enricher.js
CHANGED
|
@@ -31,7 +31,7 @@ function enrichCsn( csn ) {
|
|
|
31
31
|
target: simpleRef,
|
|
32
32
|
includes: simpleRef,
|
|
33
33
|
// Annotations are ignored.
|
|
34
|
-
'@': () => {},
|
|
34
|
+
'@': () => { /* ignore annotations */ },
|
|
35
35
|
};
|
|
36
36
|
let cleanupCallbacks = [];
|
|
37
37
|
|
|
@@ -103,7 +103,9 @@ function enrichCsn( csn ) {
|
|
|
103
103
|
|
|
104
104
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
105
105
|
function pathRef( node, prop, path ) {
|
|
106
|
-
const {
|
|
106
|
+
const {
|
|
107
|
+
links, art, scope, $env,
|
|
108
|
+
} = inspectRef( csnPath );
|
|
107
109
|
if (links) {
|
|
108
110
|
setProp(node, '_links', links);
|
|
109
111
|
cleanupCallbacks.push(() => delete node._links);
|
|
@@ -112,6 +114,10 @@ function enrichCsn( csn ) {
|
|
|
112
114
|
setProp(node, '_art', art );
|
|
113
115
|
cleanupCallbacks.push(() => delete node._art);
|
|
114
116
|
}
|
|
117
|
+
if ($env) {
|
|
118
|
+
setProp(node, '$env', $env );
|
|
119
|
+
cleanupCallbacks.push(() => delete node.$env);
|
|
120
|
+
}
|
|
115
121
|
setProp(node, '$scope', scope);
|
|
116
122
|
cleanupCallbacks.push(() => delete node.$scope);
|
|
117
123
|
setProp(node, '$path', [ ...csnPath ]);
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isBetaEnabled } = require('../base/model');
|
|
4
|
-
|
|
5
3
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
4
|
|
|
7
5
|
/**
|
|
@@ -20,17 +18,13 @@ function validateForeignKeys(member) {
|
|
|
20
18
|
|
|
21
19
|
// Declared as arrow-function to keep scope the same (this value)
|
|
22
20
|
const handleAssociation = (mem) => {
|
|
23
|
-
let elementCount = 0;
|
|
24
21
|
for (let i = 0; i < mem.keys.length; i++) {
|
|
25
22
|
if (mem.keys[i].ref) {
|
|
26
23
|
if (!mem.keys[i]._art)
|
|
27
24
|
continue;
|
|
28
25
|
// eslint-disable-next-line no-use-before-define
|
|
29
26
|
checkForItems(mem.keys[i]._art);
|
|
30
|
-
elementCount++;
|
|
31
27
|
}
|
|
32
|
-
if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && elementCount === 0)
|
|
33
|
-
this.error(null, member.$path, 'Empty structured types/elements must not be used as foreign keys');
|
|
34
28
|
}
|
|
35
29
|
};
|
|
36
30
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Trigger a recompilation in case of an association without .keys and without .on
|
|
6
|
+
*
|
|
7
|
+
* @param {CSN.Element} member the element to be checked
|
|
8
|
+
* @param {string} memberName the elements name
|
|
9
|
+
* @param {string} prop which kind of member are we looking at -> only prop "elements"
|
|
10
|
+
*/
|
|
11
|
+
function managedWithoutKeys(member, memberName, prop) {
|
|
12
|
+
if (prop === 'elements' && member.target && !member.keys && !member.on) { // trigger recompilation
|
|
13
|
+
throw new Error('Expected association to have either an on-condition or foreign keys.');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = managedWithoutKeys;
|
|
@@ -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 } },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
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
6
|
* (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database
|
|
@@ -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) {
|
|
@@ -101,7 +119,7 @@ function checkQueryForNoDBArtifacts(query) {
|
|
|
101
119
|
}
|
|
102
120
|
};
|
|
103
121
|
|
|
104
|
-
if (isPersistedOnDatabase(this.artifact) && !
|
|
122
|
+
if (isPersistedOnDatabase(this.artifact) && !hasAnnotationValue(this.artifact, '@cds.persistence.table')) {
|
|
105
123
|
const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];
|
|
106
124
|
for (const prop of generalQueryProperties) {
|
|
107
125
|
const queryPart = (query.SELECT || query.SET)[prop];
|
|
@@ -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
|
+
};
|