@sap/cds-compiler 3.1.0 → 3.3.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 +90 -3
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_BETA.md +18 -0
- package/lib/api/main.js +8 -13
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +2 -24
- package/lib/base/message-registry.js +43 -14
- package/lib/base/messages.js +20 -10
- package/lib/base/model.js +1 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +48 -0
- package/lib/checks/defaultValues.js +2 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +21 -0
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +26 -14
- package/lib/compiler/assert-consistency.js +13 -6
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +40 -33
- package/lib/compiler/define.js +50 -44
- package/lib/compiler/extend.js +303 -37
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +83 -62
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +61 -104
- package/lib/compiler/shared.js +16 -6
- package/lib/compiler/tweak-assocs.js +25 -12
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +15 -5
- package/lib/edm/csn2edm.js +10 -10
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +82 -42
- package/lib/edm/edmUtils.js +18 -16
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4205 -4100
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +26 -19
- package/lib/json/to-csn.js +47 -5
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +29 -13
- package/lib/language/language.g4 +28 -8
- package/lib/main.d.ts +3 -6
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +74 -47
- package/lib/model/csnUtils.js +236 -218
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +31 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +5 -0
- package/lib/render/manageConstraints.js +2 -2
- package/lib/render/toCdl.js +31 -44
- package/lib/render/toHdbcds.js +7 -5
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +11 -5
- package/lib/render/utils/common.js +20 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/expansion.js +81 -37
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +1 -1
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
- package/lib/transform/localized.js +28 -19
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +1 -1
- package/lib/transform/transformUtilsNew.js +101 -39
- package/lib/transform/translateAssocsToJoins.js +5 -4
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/syntax-expected-integer.md +3 -3
- package/share/messages/wildcard-excluding-one.md +37 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check that `cds.hana` types are not used - we don't support them for postgres
|
|
7
|
+
*
|
|
8
|
+
* @param {object} parent Object with a type
|
|
9
|
+
* @param {string} name Name of the type property on parent
|
|
10
|
+
* @param {Array} type type to check
|
|
11
|
+
* @param {CSN.Path} path
|
|
12
|
+
*/
|
|
13
|
+
function checkForHanaTypes(parent, name, type, path) {
|
|
14
|
+
const artifact = this.csn.definitions[path[1]];
|
|
15
|
+
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && typeof parent.type === 'string' && parent.type.startsWith('cds.hana.'))
|
|
16
|
+
this.error('ref-unexpected-hana-type', [ ...path, 'type' ], {}, `Types in the ”cds.hana“ namespace can't be used with sqlDialect “postgres”`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check that `cds.UInt8`is not used - we don't have a clear idea how to represent it on postgres
|
|
21
|
+
*
|
|
22
|
+
* @param {object} parent Object with a type
|
|
23
|
+
* @param {string} name Name of the type property on parent
|
|
24
|
+
* @param {Array} type type to check
|
|
25
|
+
* @param {CSN.Path} path
|
|
26
|
+
*/
|
|
27
|
+
function checkForUInt8(parent, name, type, path) {
|
|
28
|
+
const artifact = this.csn.definitions[path[1]];
|
|
29
|
+
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && parent.type === 'cds.UInt8')
|
|
30
|
+
this.error('ref-unexpected-type', [ ...path, 'type' ], {}, `Type ”cds.UInt8“ can't be used with sqlDialect “postgres”`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check types - specifically for postgres
|
|
35
|
+
*
|
|
36
|
+
* @param {object} parent Object with a type
|
|
37
|
+
* @param {string} name Name of the type property on parent
|
|
38
|
+
* @param {Array} type type to check
|
|
39
|
+
* @param {CSN.Path} path
|
|
40
|
+
*/
|
|
41
|
+
function checkTypes(parent, name, type, path) {
|
|
42
|
+
checkForHanaTypes.bind(this)(parent, name, type, path);
|
|
43
|
+
checkForUInt8.bind(this)(parent, name, type, path);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
type: checkTypes,
|
|
48
|
+
};
|
|
@@ -21,7 +21,7 @@ function validateDefaultValues(member, memberName, prop, path) {
|
|
|
21
21
|
while (member.default.xpr[i] === '-' || member.default.xpr[i] === '+')
|
|
22
22
|
i++;
|
|
23
23
|
if (i > 1)
|
|
24
|
-
this.error(null, path, `Illegal number of unary '+/-' operators`);
|
|
24
|
+
this.error(null, path, {}, `Illegal number of unary '+/-' operators`);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -36,7 +36,7 @@ function validateDefaultValues(member, memberName, prop, path) {
|
|
|
36
36
|
*/
|
|
37
37
|
function rejectParamDefaultsInHanaCds(member, memberName, prop, path) {
|
|
38
38
|
if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
|
|
39
|
-
this.error(null, path, 'Parameter default values are not supported in SAP HANA CDS');
|
|
39
|
+
this.error(null, path, {}, 'Parameter default values are not supported in SAP HANA CDS');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
package/lib/checks/elements.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachMember, forEachMemberRecursively } = require('../model/csnUtils');
|
|
3
|
+
const { forEachMember, forEachMemberRecursively, isBuiltinType } = require('../model/csnUtils');
|
|
4
4
|
const { isGeoTypeName } = require('../compiler/builtins');
|
|
5
|
-
|
|
5
|
+
const { setProp } = require('../base/model');
|
|
6
6
|
// Only to be used with validator.js - a correct `this` value needs to be provided!
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -42,13 +42,15 @@ function checkPrimaryKey(art) {
|
|
|
42
42
|
{ type: finalBaseType.type, name: elemFqName },
|
|
43
43
|
'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
|
|
44
44
|
}
|
|
45
|
-
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
|
|
45
|
+
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType) && !finalBaseType.$visited) {
|
|
46
|
+
setProp(finalBaseType, '$visited', true);
|
|
46
47
|
forEachMemberRecursively(finalBaseType,
|
|
47
48
|
(subMember, subMemberName) => checkIfPrimaryKeyIsOfGeoType
|
|
48
49
|
.bind(this)(subMember,
|
|
49
50
|
`${ elemFqName }/${ subMemberName }`,
|
|
50
51
|
member.key || parentIsKey,
|
|
51
52
|
member.$path));
|
|
53
|
+
delete finalBaseType.$visited;
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -68,13 +70,15 @@ function checkPrimaryKey(art) {
|
|
|
68
70
|
this.error(null, parentPath || member.$path, { name: elemFqName },
|
|
69
71
|
'Array-like type in element $(NAME) can\'t be used as primary key');
|
|
70
72
|
}
|
|
71
|
-
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
|
|
73
|
+
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType) && !finalBaseType.$visited) {
|
|
74
|
+
setProp(finalBaseType, '$visited', true);
|
|
72
75
|
forEachMemberRecursively(finalBaseType,
|
|
73
76
|
(subMember, subMemberName) => checkIfPrimaryKeyIsArray
|
|
74
77
|
.bind(this)(subMember,
|
|
75
78
|
`${ elemFqName }/${ subMemberName }`,
|
|
76
79
|
member.key || parentIsKey,
|
|
77
80
|
member.$path));
|
|
81
|
+
delete finalBaseType.$visited;
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
}
|
|
@@ -89,7 +93,7 @@ function checkPrimaryKey(art) {
|
|
|
89
93
|
function checkVirtualElement(member) {
|
|
90
94
|
if (member.virtual) {
|
|
91
95
|
if (this.csnUtils.isAssociation(member.type)) { // or Composition ???
|
|
92
|
-
this.error(null, member.$path, `Element can't be virtual and an association`);
|
|
96
|
+
this.error(null, member.$path, {}, `Element can't be virtual and an association`);
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
}
|
|
@@ -135,4 +139,75 @@ function checkManagedAssoc(art) {
|
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
/**
|
|
143
|
+
* All DB & OData flat mode must reject recursive type usages in entities
|
|
144
|
+
* 'items' break recursion as 'items' will turn into an NCLOB and the path
|
|
145
|
+
* prefix to 'items' can be flattend in the DB.
|
|
146
|
+
* In OData flat mode the first appearance of 'items' breaks out into structured
|
|
147
|
+
* mode producing (legal) recursive complex types.
|
|
148
|
+
*
|
|
149
|
+
* @param {CSN.Artifact} art The artifact
|
|
150
|
+
*/
|
|
151
|
+
function checkRecursiveTypeUsage(art) {
|
|
152
|
+
const visit = (def) => {
|
|
153
|
+
const loc = def.$path;
|
|
154
|
+
// recursive types are allowed inside arrays
|
|
155
|
+
if (def.items)
|
|
156
|
+
return;
|
|
157
|
+
let { type } = def;
|
|
158
|
+
let prevType;
|
|
159
|
+
let isDeref = false;
|
|
160
|
+
if (type && !isBuiltinType(type) && !def.elements) {
|
|
161
|
+
do {
|
|
162
|
+
prevType = type;
|
|
163
|
+
// TODO: `type.ref.length > 1`, but OData backend must be tested first (#5144)
|
|
164
|
+
// e.g. `{ ref: [ "MyType" ] }`
|
|
165
|
+
if (type.ref) {
|
|
166
|
+
def = this.artifactRef(type);
|
|
167
|
+
isDeref = true;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
def = this.csn.definitions[type];
|
|
171
|
+
}
|
|
172
|
+
type = def.type;
|
|
173
|
+
} while (type && !isBuiltinType(type) && !def.items && !def.elements && prevType !== type);
|
|
174
|
+
}
|
|
175
|
+
if (def.$visited || (type && prevType === type)) {
|
|
176
|
+
// Recursion via type is allowed in V4 struct, but not via dereferencing
|
|
177
|
+
if (!isDeref && this.options.odataVersion === 'v4' && this.options.odataFormat === 'structured')
|
|
178
|
+
return;
|
|
179
|
+
if (!def.$recErr) {
|
|
180
|
+
this.error(null, loc, {}, 'Unexpected recursive type definition');
|
|
181
|
+
setProp(def, '$recErr', true);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (def.elements) {
|
|
185
|
+
setProp(def, '$visited', true);
|
|
186
|
+
for (const n in def.elements)
|
|
187
|
+
visit(def.elements[n]);
|
|
188
|
+
delete def.$visited;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
// elements & params are flattening candidates
|
|
192
|
+
// FUTURE:
|
|
193
|
+
// Once we have universal CSN for the runtimes
|
|
194
|
+
// Validate service members only for OData
|
|
195
|
+
if (art.kind === 'entity') {
|
|
196
|
+
for (const n in art.elements)
|
|
197
|
+
visit(art.elements[n]);
|
|
198
|
+
for (const n in art.params)
|
|
199
|
+
visit(art.params[n]);
|
|
200
|
+
}
|
|
201
|
+
if (this.options.odataVersion) {
|
|
202
|
+
// func/action params/returns don't allow recursive type derefs
|
|
203
|
+
if (art.kind === 'action' || art.kind === 'function') {
|
|
204
|
+
for (const n in art.params)
|
|
205
|
+
visit(art.params[n]);
|
|
206
|
+
if (art.returns)
|
|
207
|
+
visit(art.returns);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
module.exports = {
|
|
212
|
+
checkPrimaryKey, checkVirtualElement, checkManagedAssoc, checkRecursiveTypeUsage,
|
|
213
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { setProp } = require('../base/model');
|
|
4
|
+
|
|
3
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -41,10 +43,10 @@ function validateForeignKeys(member) {
|
|
|
41
43
|
// Declared as arrow-function to keep scope the same (this value)
|
|
42
44
|
const checkForItems = (mem) => {
|
|
43
45
|
if (mem.items) {
|
|
44
|
-
this.error(null, member.$path, 'Array-like properties must not be foreign keys');
|
|
46
|
+
this.error(null, member.$path, {}, 'Array-like properties must not be foreign keys');
|
|
45
47
|
}
|
|
46
48
|
else if (isUnmanagedAssoc(mem)) {
|
|
47
|
-
this.error(null, member.$path, 'Unmanaged association must not be a foreign key');
|
|
49
|
+
this.error(null, member.$path, {}, 'Unmanaged association must not be a foreign key');
|
|
48
50
|
}
|
|
49
51
|
else if (mem.keys) {
|
|
50
52
|
handleAssociation(mem);
|
|
@@ -52,17 +54,14 @@ function validateForeignKeys(member) {
|
|
|
52
54
|
else if (mem.elements) {
|
|
53
55
|
handleStructured(mem);
|
|
54
56
|
}
|
|
55
|
-
else if (mem.type
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
else if (type.elements)
|
|
65
|
-
handleStructured(type);
|
|
57
|
+
else if (mem.type) {
|
|
58
|
+
const type = mem.type.ref
|
|
59
|
+
? this.artifactRef(mem.type)
|
|
60
|
+
: this.csn.definitions[mem.type];
|
|
61
|
+
if (type && !type.$visited) {
|
|
62
|
+
setProp(type, '$visited', true);
|
|
63
|
+
checkForItems(type);
|
|
64
|
+
delete type.$visited;
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
4
4
|
|
|
5
5
|
const { ModelError } = require('../base/error');
|
|
6
|
+
const { setProp } = require('../base/model');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Assert that targets of associations and compositions are entities.
|
|
@@ -38,18 +39,16 @@ function invalidTarget(member) {
|
|
|
38
39
|
);
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// elements have precedence over type
|
|
43
|
+
else if (mem.elements) {
|
|
44
|
+
handleStructured(mem);
|
|
44
45
|
}
|
|
45
|
-
else {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
else
|
|
52
|
-
checkForInvalidTarget(type);
|
|
46
|
+
else if (mem.type) {
|
|
47
|
+
const type = mem.type.ref ? this.artifactRef(mem.type) : this.csn.definitions[mem.type];
|
|
48
|
+
if (type && !type.$visited) {
|
|
49
|
+
setProp(type, '$visited', true);
|
|
50
|
+
checkForInvalidTarget(type);
|
|
51
|
+
delete type.$visited;
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { setProp } = require('../base/model');
|
|
4
|
+
|
|
3
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -35,18 +37,20 @@ function checkUsedTypesForAnonymousAspectComposition(member) {
|
|
|
35
37
|
}
|
|
36
38
|
else if (mem.type && (mem.type === 'cds.Composition') && !mem.on) {
|
|
37
39
|
if (!mem.target && mem.targetAspect && typeof mem.targetAspect !== 'string')
|
|
38
|
-
this.error(null, member.$path, 'Types with anonymous aspect compositions can\'t be used');
|
|
40
|
+
this.error(null, member.$path, {}, 'Types with anonymous aspect compositions can\'t be used');
|
|
39
41
|
}
|
|
40
42
|
else if (mem.elements) {
|
|
41
43
|
handleStructured(mem, assertNoAnonymousAspectComposition);
|
|
42
44
|
}
|
|
43
|
-
else if (mem.type
|
|
44
|
-
const type =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
else if (mem.type) {
|
|
46
|
+
const type = mem.type.ref
|
|
47
|
+
? this.artifactRef(mem.type)
|
|
48
|
+
: this.csn.definitions[mem.type];
|
|
49
|
+
if (type && !type.$visited) {
|
|
50
|
+
setProp(type, '$visited', true);
|
|
51
|
+
assertNoAnonymousAspectComposition(type);
|
|
52
|
+
delete type.$visited;
|
|
53
|
+
}
|
|
50
54
|
}
|
|
51
55
|
};
|
|
52
56
|
|
|
@@ -62,13 +66,15 @@ function checkUsedTypesForAnonymousAspectComposition(member) {
|
|
|
62
66
|
else if (mem.elements) {
|
|
63
67
|
handleStructured(mem, checkTypeUsages);
|
|
64
68
|
}
|
|
65
|
-
else if (mem.type
|
|
66
|
-
const type =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
else if (mem.type) { // type of
|
|
70
|
+
const type = mem.type.ref
|
|
71
|
+
? this.artifactRef(mem.type)
|
|
72
|
+
: this.csn.definitions[mem.type];
|
|
73
|
+
if (type && !type.$visited) {
|
|
74
|
+
setProp(type, '$visited', true);
|
|
75
|
+
assertNoAnonymousAspectComposition(type);
|
|
76
|
+
delete type.$visited;
|
|
77
|
+
}
|
|
72
78
|
}
|
|
73
79
|
};
|
|
74
80
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
function checkExplicitlyNullableKeys(element) {
|
|
9
9
|
if (element.key && element.notNull === false)
|
|
10
|
-
this.error(null, element.$path, 'Expecting primary key element to be not nullable');
|
|
10
|
+
this.error(null, element.$path, {}, 'Expecting primary key element to be not nullable');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
module.exports = checkExplicitlyNullableKeys;
|
|
@@ -61,7 +61,7 @@ function validateOnCondition(member, memberName, property, path) {
|
|
|
61
61
|
if (member && member.on) {
|
|
62
62
|
// complain about nullability constraint on managed composition
|
|
63
63
|
if (member.targetAspect && {}.hasOwnProperty.call(member, 'notNull')) {
|
|
64
|
-
this.warning(null, path.concat([ 'on' ]),
|
|
64
|
+
this.warning(null, path.concat([ 'on' ]), {},
|
|
65
65
|
'Unexpected nullability constraint defined on managed composition');
|
|
66
66
|
}
|
|
67
67
|
for (let i = 0; i < member.on.length; i++) {
|
|
@@ -83,24 +83,24 @@ function validateOnCondition(member, memberName, property, path) {
|
|
|
83
83
|
if (_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || (validDollarSelf && j === _links.length - 1))) {
|
|
84
84
|
if (_links[j].art.on) {
|
|
85
85
|
// It's an unmanaged association - traversal is always forbidden
|
|
86
|
-
this.error(null, csnPath, { id, elemref }, 'ON-
|
|
86
|
+
this.error(null, csnPath, { id, elemref }, 'ON-conditions can\'t follow unmanaged associations, step $(ID) of path $(ELEMREF)');
|
|
87
87
|
}
|
|
88
88
|
else {
|
|
89
89
|
// It's a managed association - access of the foreign keys is allowed
|
|
90
90
|
const nextRef = ref[j + 1].id || ref[j + 1];
|
|
91
91
|
if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
|
|
92
|
-
this.error(null, csnPath, { id, elemref }, 'ON-
|
|
92
|
+
this.error(null, csnPath, { id, elemref }, 'ON-conditions can only follow managed associations to the foreign keys of the managed association, step $(ID) of path $(ELEMREF)');
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
if (_links[j].art.virtual)
|
|
97
|
-
this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-
|
|
97
|
+
this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
|
|
98
98
|
|
|
99
99
|
if (ref[j].where)
|
|
100
|
-
this.error(null, csnPath, { id, elemref }, 'ON-
|
|
100
|
+
this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain filters, step $(ID) of path $(ELEMREF)');
|
|
101
101
|
|
|
102
102
|
if (ref[j].args)
|
|
103
|
-
this.error(null, csnPath, { id, elemref }, 'ON-
|
|
103
|
+
this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain parameters, step $(ID) of path $(ELEMREF)');
|
|
104
104
|
}
|
|
105
105
|
if (_art && $scope !== '$self') {
|
|
106
106
|
_art = resolveArtifactType.call(this, _art);
|
|
@@ -117,15 +117,15 @@ function validateOnCondition(member, memberName, property, path) {
|
|
|
117
117
|
/* 2) */ (_art.target && validDollarSelf)) &&
|
|
118
118
|
!_art.virtual) {
|
|
119
119
|
this.error(null, onPath, { elemref: { ref } },
|
|
120
|
-
'The last path of an
|
|
120
|
+
'The last path of an ON-condition must be a scalar value, path $(ELEMREF)');
|
|
121
121
|
}
|
|
122
122
|
else if (_art.items && !_art.virtual) {
|
|
123
123
|
this.error(null, onPath, { elemref: { ref } },
|
|
124
|
-
'ON-
|
|
124
|
+
'ON-conditions can\'t use array-like elements, path $(ELEMREF)');
|
|
125
125
|
}
|
|
126
126
|
else if (_art.virtual) {
|
|
127
127
|
this.error(null, onPath, { elemref: { ref } },
|
|
128
|
-
'Virtual elements can\'t be used in ON-
|
|
128
|
+
'Virtual elements can\'t be used in ON-conditions, path $(ELEMREF)');
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check that we don't have parameterized views - as we don't know yet how to represent them on postgres
|
|
7
|
+
*
|
|
8
|
+
* @param {object} parent Object with .params
|
|
9
|
+
* @param {string} name Name of the params property on parent
|
|
10
|
+
* @param {object} params params
|
|
11
|
+
* @param {CSN.Path} path
|
|
12
|
+
*/
|
|
13
|
+
function checkForParams(parent, name, params, path) {
|
|
14
|
+
const artifact = this.csn.definitions[path[1]];
|
|
15
|
+
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && !(parent.kind !== 'entity'))
|
|
16
|
+
this.error('ref-unexpected-params', [ ...path, 'params' ], `Parameterized views can't be used with sqlDialect “postgres”`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
params: checkForParams,
|
|
21
|
+
};
|
|
@@ -106,7 +106,7 @@ function validateSelectItems(query) {
|
|
|
106
106
|
if (this.options.transformation === 'hdbcds') {
|
|
107
107
|
transformers.xpr = (parent) => {
|
|
108
108
|
if (parent.func) {
|
|
109
|
-
this.error(null, parent.$path,
|
|
109
|
+
this.error(null, parent.$path, {},
|
|
110
110
|
'Window functions are not supported by SAP HANA CDS');
|
|
111
111
|
}
|
|
112
112
|
};
|
package/lib/checks/types.js
CHANGED
|
@@ -33,7 +33,7 @@ function checkDecimalScale(member, memberName, prop, path) {
|
|
|
33
33
|
*/
|
|
34
34
|
function checkTypeIsScalar(member, memberName, prop, path) {
|
|
35
35
|
if ( prop === 'params' && this.csnUtils.isStructured(member))
|
|
36
|
-
this.error(null, path, 'View parameter type must be scalar');
|
|
36
|
+
this.error(null, path, {}, 'View parameter type must be scalar');
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -69,7 +69,7 @@ function checkElementTypeDefinitionHasType(member, memberName, prop, path) {
|
|
|
69
69
|
}
|
|
70
70
|
else if (member._type) {
|
|
71
71
|
if ( this.isAspect(member._type) || member._type.kind === 'type' && member._type.$syntax === 'aspect')
|
|
72
|
-
this.error('ref-sloppy-type', path, 'A type or an element is expected here');
|
|
72
|
+
this.error('ref-sloppy-type', path, {}, 'A type or an element is expected here');
|
|
73
73
|
}
|
|
74
74
|
return;
|
|
75
75
|
}
|
package/lib/checks/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../model/csnUtils');
|
|
4
|
-
|
|
4
|
+
const { RelationalOperators } = require('../transform/transformUtilsNew');
|
|
5
5
|
/**
|
|
6
6
|
* Prepare the ref steps so that they are loggable
|
|
7
7
|
*
|
|
@@ -17,17 +17,27 @@ function logReady(refStep) {
|
|
|
17
17
|
* structured that can be used for tuple expansion. This can either be a
|
|
18
18
|
* real 'elements' thing or a managed association/composition with foreign keys.
|
|
19
19
|
*
|
|
20
|
+
* The RHS may be 'null' or any value
|
|
21
|
+
*
|
|
20
22
|
* @param {Array} on the on condition which to check
|
|
21
23
|
* @param {number} startIndex the index of the relational term in the on condition array
|
|
22
24
|
* @returns {boolean} indicates whether the other side of a relational term is expandable
|
|
23
25
|
*/
|
|
24
26
|
function otherSideIsExpandableStructure(on, startIndex) {
|
|
25
|
-
if (on[startIndex - 1] &&
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
if (on[startIndex - 1] && RelationalOperators.includes(on[startIndex - 1])) {
|
|
28
|
+
const lhs = on[startIndex - 2];
|
|
29
|
+
// if ever lhs is allowed to be a value uncomment this
|
|
30
|
+
return /* lhs?.val !== undefined || */ isOk(resolveArtifactType.call(this, lhs?._art));
|
|
31
|
+
}
|
|
32
|
+
else if (on[startIndex + 1] && RelationalOperators.includes(on[startIndex + 1])) {
|
|
33
|
+
const op = on[startIndex + 1];
|
|
34
|
+
const rhs = on[startIndex + 2];
|
|
35
|
+
if (op === 'is')
|
|
36
|
+
// check for unary operator 'is [not] null' as token stream
|
|
37
|
+
return rhs === 'null' || (rhs === 'not' && on[startIndex + 3] === 'null');
|
|
38
|
+
// if ever rhs is allowed to be a value uncomment this
|
|
39
|
+
return /* rhs?.val !== undefined || */ isOk(resolveArtifactType.call(this, rhs?._art));
|
|
40
|
+
}
|
|
31
41
|
return false;
|
|
32
42
|
|
|
33
43
|
/**
|
package/lib/checks/validator.js
CHANGED
|
@@ -6,12 +6,14 @@ const {
|
|
|
6
6
|
} = require('../model/csnUtils');
|
|
7
7
|
const enrich = require('./enricher');
|
|
8
8
|
|
|
9
|
-
//
|
|
9
|
+
// forRelationalDB
|
|
10
10
|
const { validateSelectItems } = require('./selectItems');
|
|
11
11
|
const { rejectParamDefaultsInHanaCds, warnAboutDefaultOnAssociationForHanaCds } = require('./defaultValues');
|
|
12
12
|
const validateCdsPersistenceAnnotation = require('./cdsPersistence');
|
|
13
13
|
const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
|
|
14
14
|
const checkForEmptyOrOnlyVirtual = require('./emptyOrOnlyVirtual');
|
|
15
|
+
const checkForHanaTypes = require('./checkForTypes');
|
|
16
|
+
const checkForParams = require('./parameters');
|
|
15
17
|
// forOdata
|
|
16
18
|
const { validateDefaultValues } = require('./defaultValues');
|
|
17
19
|
const { checkActionOrFunction } = require('./actionsFunctions');
|
|
@@ -26,7 +28,9 @@ const {
|
|
|
26
28
|
checkTypeDefinitionHasType, checkElementTypeDefinitionHasType,
|
|
27
29
|
checkTypeIsScalar, checkDecimalScale,
|
|
28
30
|
} = require('./types');
|
|
29
|
-
const {
|
|
31
|
+
const {
|
|
32
|
+
checkPrimaryKey, checkVirtualElement, checkManagedAssoc, checkRecursiveTypeUsage,
|
|
33
|
+
} = require('./elements');
|
|
30
34
|
const checkForInvalidTarget = require('./invalidTarget');
|
|
31
35
|
const { validateAssociationsInItems } = require('./arrayOfs');
|
|
32
36
|
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
@@ -38,7 +42,7 @@ const {
|
|
|
38
42
|
checkSqlAnnotationOnElement,
|
|
39
43
|
} = require('./sql-snippets');
|
|
40
44
|
|
|
41
|
-
const
|
|
45
|
+
const forRelationalDBMemberValidators
|
|
42
46
|
= [
|
|
43
47
|
// For HANA CDS specifically, reject any default parameter values, as these are not supported.
|
|
44
48
|
rejectParamDefaultsInHanaCds,
|
|
@@ -51,7 +55,7 @@ const forHanaMemberValidators
|
|
|
51
55
|
checkSqlAnnotationOnElement,
|
|
52
56
|
];
|
|
53
57
|
|
|
54
|
-
const
|
|
58
|
+
const forRelationalDBArtifactValidators
|
|
55
59
|
= [
|
|
56
60
|
// @cds.persistence has no impact on odata
|
|
57
61
|
validateCdsPersistenceAnnotation,
|
|
@@ -61,12 +65,12 @@ const forHanaArtifactValidators
|
|
|
61
65
|
checkSqlAnnotationOnArtifact,
|
|
62
66
|
];
|
|
63
67
|
|
|
64
|
-
const
|
|
68
|
+
const forRelationalDBCsnValidators = [ nonexpandableStructuredInExpression ];
|
|
65
69
|
/**
|
|
66
70
|
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
67
71
|
*/
|
|
68
|
-
const
|
|
69
|
-
// TODO reason why this is
|
|
72
|
+
const forRelationalDBQueryValidators = [
|
|
73
|
+
// TODO reason why this is forRelationalDB exclusive
|
|
70
74
|
validateSelectItems,
|
|
71
75
|
checkQueryForNoDBArtifacts,
|
|
72
76
|
];
|
|
@@ -103,7 +107,12 @@ const commonMemberValidators
|
|
|
103
107
|
checkVirtualElement, checkElementTypeDefinitionHasType ];
|
|
104
108
|
|
|
105
109
|
// TODO: checkManagedAssoc is a forEachMemberRecursively!
|
|
106
|
-
const commonArtifactValidators = [
|
|
110
|
+
const commonArtifactValidators = [
|
|
111
|
+
checkTypeDefinitionHasType,
|
|
112
|
+
checkPrimaryKey,
|
|
113
|
+
checkManagedAssoc,
|
|
114
|
+
checkRecursiveTypeUsage,
|
|
115
|
+
];
|
|
107
116
|
// TODO: Does it make sense to run the on-condition check as part of a CSN validator?
|
|
108
117
|
const commonQueryValidators = [ validateMixinOnCondition ];
|
|
109
118
|
|
|
@@ -186,11 +195,14 @@ function mergeCsnValidators(csnValidators, that) {
|
|
|
186
195
|
* @param {object} that Will be provided to the validators via "this"
|
|
187
196
|
* @returns {Function} the validator function with the respective checks for the HANA backend
|
|
188
197
|
*/
|
|
189
|
-
function
|
|
198
|
+
function forRelationalDB(csn, that) {
|
|
190
199
|
return _validate(csn, that,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
200
|
+
that.options.sqlDialect === 'postgres'
|
|
201
|
+
? [ ...forRelationalDBCsnValidators,
|
|
202
|
+
checkForHanaTypes,
|
|
203
|
+
checkForParams ] : forRelationalDBCsnValidators,
|
|
204
|
+
forRelationalDBMemberValidators.concat(commonMemberValidators),
|
|
205
|
+
forRelationalDBArtifactValidators.concat(commonArtifactValidators).concat(
|
|
194
206
|
// why is this hana exclusive
|
|
195
207
|
(artifact) => {
|
|
196
208
|
/* the validation itself performs a recursive check on structured elements.
|
|
@@ -200,7 +212,7 @@ function forHana(csn, that) {
|
|
|
200
212
|
forEachMember(artifact, checkUsedTypesForAnonymousAspectComposition.bind(that));
|
|
201
213
|
}
|
|
202
214
|
),
|
|
203
|
-
|
|
215
|
+
forRelationalDBQueryValidators.concat(commonQueryValidators),
|
|
204
216
|
{
|
|
205
217
|
skipArtifact: artifact => artifact.abstract ||
|
|
206
218
|
hasAnnotationValue(artifact, '@cds.persistence.skip') ||
|
|
@@ -235,4 +247,4 @@ function forOdata(csn, that) {
|
|
|
235
247
|
});
|
|
236
248
|
}
|
|
237
249
|
|
|
238
|
-
module.exports = {
|
|
250
|
+
module.exports = { forRelationalDB, forOdata };
|