@sap/cds-compiler 2.5.2 → 2.11.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 +235 -9
- package/bin/cdsc.js +44 -27
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +37 -3
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +37 -123
- package/lib/api/options.js +27 -15
- package/lib/api/validate.js +34 -9
- package/lib/backends.js +9 -89
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +73 -11
- package/lib/base/messages.js +86 -30
- package/lib/base/model.js +6 -6
- package/lib/base/optionProcessorHelper.js +56 -22
- 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 +29 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +23 -7
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +8 -5
- package/lib/compiler/definer.js +157 -133
- package/lib/compiler/index.js +89 -31
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +375 -185
- package/lib/compiler/shared.js +49 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +104 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +388 -146
- package/lib/edm/edmUtils.js +104 -34
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5330 -4300
- package/lib/json/from-csn.js +110 -52
- package/lib/json/to-csn.js +434 -120
- package/lib/language/antlrParser.js +15 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +93 -26
- package/lib/language/language.g4 +172 -31
- package/lib/main.d.ts +216 -19
- package/lib/main.js +32 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +413 -149
- package/lib/model/csnUtils.js +286 -75
- package/lib/model/enrichCsn.js +50 -6
- package/lib/model/revealInternalProperties.js +22 -5
- package/lib/modelCompare/compare.js +39 -21
- package/lib/optionProcessor.js +35 -18
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +9 -6
- package/lib/render/toCdl.js +121 -36
- package/lib/render/toHdbcds.js +148 -98
- package/lib/render/toSql.js +114 -43
- 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 +281 -106
- package/lib/transform/db/draft.js +11 -8
- package/lib/transform/db/expansion.js +584 -0
- package/lib/transform/db/flattening.js +341 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +345 -65
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +131 -793
- package/lib/transform/forOdataNew.js +30 -24
- package/lib/transform/localized.js +39 -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 +144 -78
- package/lib/transform/translateAssocsToJoins.js +22 -27
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -14
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- 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
|
@@ -30,7 +30,11 @@ function createOptionProcessor() {
|
|
|
30
30
|
optionClashes: [],
|
|
31
31
|
option,
|
|
32
32
|
command,
|
|
33
|
-
positionalArgument
|
|
33
|
+
positionalArgument: (argumentDefinition) => {
|
|
34
|
+
// Default positional arguments; may be overwritten by commands.
|
|
35
|
+
_positionalArguments(argumentDefinition);
|
|
36
|
+
return optionProcessor;
|
|
37
|
+
},
|
|
34
38
|
help,
|
|
35
39
|
processCmdLine,
|
|
36
40
|
verifyOptions,
|
|
@@ -66,7 +70,12 @@ function createOptionProcessor() {
|
|
|
66
70
|
/** @type {object} */
|
|
67
71
|
const command = {
|
|
68
72
|
options: {},
|
|
73
|
+
positionalArguments: [],
|
|
69
74
|
option,
|
|
75
|
+
positionalArgument: (argumentDefinition) => {
|
|
76
|
+
_positionalArguments(argumentDefinition, command.positionalArguments);
|
|
77
|
+
return command;
|
|
78
|
+
},
|
|
70
79
|
help,
|
|
71
80
|
..._parseCommandString(cmdString)
|
|
72
81
|
};
|
|
@@ -96,28 +105,34 @@ function createOptionProcessor() {
|
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
/**
|
|
99
|
-
*
|
|
100
|
-
* to either require N positional arguments or a dynamic number (but at least one)
|
|
101
|
-
*
|
|
108
|
+
* Set the positional arguments to the command line processor. Instructs the processor
|
|
109
|
+
* to either require N positional arguments or a dynamic number (but at least one).
|
|
110
|
+
* Note that you can only call this function once. Only the last invocation sets
|
|
111
|
+
* the positional arguments.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
|
|
114
|
+
* @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
|
|
102
115
|
*/
|
|
103
|
-
function
|
|
104
|
-
if (
|
|
116
|
+
function _positionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
|
|
117
|
+
if (argList.find((arg) => arg.isDynamic)) {
|
|
105
118
|
throw new Error(`Can't add positional arguments after a dynamic one`);
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
const registeredNames =
|
|
109
|
-
const args =
|
|
121
|
+
const registeredNames = argList.map((arg) => arg.name);
|
|
122
|
+
const args = argumentDefinition.split(' ');
|
|
110
123
|
|
|
111
124
|
for (const arg of args) {
|
|
112
|
-
|
|
125
|
+
// Remove braces, dots and camelify.
|
|
126
|
+
const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
|
|
127
|
+
|
|
113
128
|
if (registeredNames.includes(argName)) {
|
|
114
|
-
throw new Error(`Duplicate positional argument ${arg}`);
|
|
129
|
+
throw new Error(`Duplicate positional argument: ${arg}`);
|
|
115
130
|
}
|
|
116
131
|
if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {
|
|
117
132
|
throw new Error(`Unknown positional argument syntax: ${arg}`)
|
|
118
133
|
}
|
|
119
134
|
|
|
120
|
-
|
|
135
|
+
argList.push({
|
|
121
136
|
name: argName,
|
|
122
137
|
isDynamic: isDynamicPositionalArgument(arg),
|
|
123
138
|
required: true
|
|
@@ -125,7 +140,6 @@ function createOptionProcessor() {
|
|
|
125
140
|
|
|
126
141
|
registeredNames.push(argName);
|
|
127
142
|
}
|
|
128
|
-
return optionProcessor;
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
/**
|
|
@@ -390,21 +404,38 @@ function createOptionProcessor() {
|
|
|
390
404
|
}
|
|
391
405
|
|
|
392
406
|
// Complain about first missing positional arguments
|
|
393
|
-
const missingArg =
|
|
407
|
+
const missingArg = getCurrentPositionArguments().find((arg) => arg.required && !result.args[arg.name]);
|
|
394
408
|
if (missingArg) {
|
|
395
|
-
result.
|
|
409
|
+
const forCommand = result.command ? ` for '${ result.command }'` : '';
|
|
410
|
+
result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
|
|
396
411
|
}
|
|
397
412
|
|
|
398
413
|
return result;
|
|
399
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Specific commands may have custom positional arguments.
|
|
417
|
+
* If the current one does, use it instead of the defaults.
|
|
418
|
+
*
|
|
419
|
+
* @returns {object[]} Array of positional argument configurations.
|
|
420
|
+
*/
|
|
421
|
+
function getCurrentPositionArguments() {
|
|
422
|
+
const cmd = optionProcessor.commands[result.command];
|
|
423
|
+
const args = ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
|
|
424
|
+
return args;
|
|
425
|
+
}
|
|
426
|
+
|
|
400
427
|
function processPositionalArgument(argumentValue) {
|
|
401
|
-
|
|
428
|
+
const argList = getCurrentPositionArguments();
|
|
429
|
+
if ( result.args.length === 0 && argList.length === 0 )
|
|
402
430
|
return;
|
|
403
|
-
const inBounds = result.args.length <
|
|
404
|
-
const lastIndex = inBounds ? result.args.length :
|
|
405
|
-
const nextUnsetArgument =
|
|
431
|
+
const inBounds = result.args.length < argList.length;
|
|
432
|
+
const lastIndex = inBounds ? result.args.length : argList.length - 1;
|
|
433
|
+
const nextUnsetArgument = argList[lastIndex];
|
|
406
434
|
if (!inBounds && !nextUnsetArgument.isDynamic) {
|
|
407
|
-
result.
|
|
435
|
+
if (result.command)
|
|
436
|
+
result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
|
|
437
|
+
else
|
|
438
|
+
result.errors.push(`Too many arguments. Expected ${argList.length}`);
|
|
408
439
|
return;
|
|
409
440
|
}
|
|
410
441
|
result.args.length += 1;
|
|
@@ -486,7 +517,10 @@ function createOptionProcessor() {
|
|
|
486
517
|
}
|
|
487
518
|
|
|
488
519
|
if(options) {
|
|
489
|
-
[
|
|
520
|
+
[
|
|
521
|
+
'defaultBinaryLength', 'defaultStringLength',
|
|
522
|
+
/*'length', 'precision', 'scale'*/
|
|
523
|
+
].forEach(facet => {
|
|
490
524
|
if(options[facet] && isNaN(options[facet])) {
|
|
491
525
|
result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
|
|
492
526
|
} else {
|
|
@@ -577,12 +611,12 @@ function isLongOption(opt) {
|
|
|
577
611
|
|
|
578
612
|
// Check if 'opt' looks like a "<foobar>" parameter
|
|
579
613
|
function isParam(opt) {
|
|
580
|
-
return /^<[a-zA-Z]+>$/.test(opt);
|
|
614
|
+
return /^<[a-zA-Z-]+>$/.test(opt);
|
|
581
615
|
}
|
|
582
616
|
|
|
583
617
|
// Check if 'arg' looks like "<foobar...>"
|
|
584
618
|
function isDynamicPositionalArgument(arg) {
|
|
585
|
-
return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
|
|
619
|
+
return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
|
|
586
620
|
}
|
|
587
621
|
|
|
588
622
|
module.exports = {
|
|
@@ -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'];
|
|
@@ -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 } },
|
|
@@ -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) => {
|
|
@@ -23,6 +23,33 @@ function validateSelectItems(query) {
|
|
|
23
23
|
'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
|
|
27
|
+
this.error(null, selectItem.$path,
|
|
28
|
+
'Window functions are not supported by SAP HANA CDS');
|
|
29
|
+
}
|
|
26
30
|
});
|
|
31
|
+
// .call() with 'this' to ensure we have access to the options
|
|
32
|
+
rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* For the to.hdbcds transformation with naming mode 'hdbcds', structures and managed associations are not flattened/resolved.
|
|
38
|
+
* It is therefore not possible to publish such elements in a view.
|
|
39
|
+
* This function iterates over all published elements of a query artifact and asserts that no such elements are published.
|
|
40
|
+
*
|
|
41
|
+
* @param {CSN.Artifact} queryArtifact the query artifact which should be checked
|
|
42
|
+
* @param {CSN.Path} artifactPath the path to that artifact
|
|
43
|
+
*/
|
|
44
|
+
function rejectManagedAssociationsAndStructuresForHdbcsNames(queryArtifact, artifactPath) {
|
|
45
|
+
if (this.options.transformation === 'hdbcds' && this.options.sqlMapping === 'hdbcds') {
|
|
46
|
+
forEachGeneric(queryArtifact, 'elements', (selectItem, elemName, prop, elementPath) => {
|
|
47
|
+
if (this.csnUtils.isManagedAssociationElement(selectItem))
|
|
48
|
+
this.error('query-unexpected-assoc-hdbcds', elementPath);
|
|
49
|
+
if (this.csnUtils.isStructured(selectItem))
|
|
50
|
+
this.error('query-unexpected-structure-hdbcds', elementPath);
|
|
51
|
+
}, artifactPath);
|
|
52
|
+
}
|
|
27
53
|
}
|
|
28
|
-
|
|
54
|
+
|
|
55
|
+
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,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getVariableReplacement } = require('../model/csnUtils');
|
|
4
|
+
|
|
5
|
+
// We only care about the "wild" ones - $at is validated by the compiler
|
|
6
|
+
const magicVariables = {
|
|
7
|
+
$user: [
|
|
8
|
+
'id', // $user.id
|
|
9
|
+
'locale', // $user.locale
|
|
10
|
+
],
|
|
11
|
+
$session: [
|
|
12
|
+
// no valid ways for this
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check that the given ref does not use magic variables for which we don't have
|
|
18
|
+
* a valid way of rendering.
|
|
19
|
+
*
|
|
20
|
+
* Valid ways:
|
|
21
|
+
* - We know what to do -> $user.id on HANA
|
|
22
|
+
* - The user tells us what to do -> options.variableReplacements
|
|
23
|
+
*
|
|
24
|
+
* @param {object} parent Object with the ref as a property
|
|
25
|
+
* @param {string} name Name of the ref property on parent
|
|
26
|
+
* @param {Array} ref to check
|
|
27
|
+
*/
|
|
28
|
+
function unknownMagicVariable(parent, name, ref) {
|
|
29
|
+
if (parent.$scope && parent.$scope === '$magic') {
|
|
30
|
+
const [ head, ...rest ] = ref;
|
|
31
|
+
const tail = rest.join('.');
|
|
32
|
+
const magicVariable = magicVariables[head];
|
|
33
|
+
if (magicVariable && magicVariable.indexOf(tail) === -1 &&
|
|
34
|
+
getVariableReplacement(ref, this.options) === null)
|
|
35
|
+
this.error(null, parent.$location, { id: tail, elemref: parent }, 'No configuration for magic variable was provided - path $(ELEMREF), step $(ID)');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
ref: unknownMagicVariable,
|
|
41
|
+
};
|
|
@@ -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
|
+
};
|