@sap/cds-compiler 4.0.2 → 4.2.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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { forEachGeneric } = require('../model/csnUtils');
|
|
4
4
|
const { otherSideIsExpandableStructure, resolveArtifactType } = require('./utils');
|
|
5
|
+
const { pathId } = require('../model/csnRefs');
|
|
5
6
|
|
|
6
7
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
7
8
|
|
|
@@ -93,7 +94,7 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
93
94
|
}
|
|
94
95
|
else {
|
|
95
96
|
// It's a managed association - access of the foreign keys is allowed
|
|
96
|
-
|
|
97
|
+
requireForeignKeyAccess(member.on[i], j, (errorIndex) => {
|
|
97
98
|
this.error('ref-unexpected-navigation', csnPath, {
|
|
98
99
|
'#': 'std', id, elemref, name: ref[errorIndex].id || ref[errorIndex],
|
|
99
100
|
});
|
|
@@ -158,29 +159,36 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
158
159
|
|
|
159
160
|
/**
|
|
160
161
|
* Ensure that only foreign keys of the association `parent.ref[refIndex]` are accessed in `parent.ref`.
|
|
161
|
-
* If a non-fk field is accessed, `
|
|
162
|
+
* If a non-fk field is accessed, `noForeignKeyCallback` is invoked.
|
|
162
163
|
*
|
|
163
|
-
* @param {object} parent
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* @param {
|
|
167
|
-
*
|
|
164
|
+
* @param {object} parent
|
|
165
|
+
* Object containing `ref` and `_links` from csnRefs.
|
|
166
|
+
*
|
|
167
|
+
* @param {number} refIndex
|
|
168
|
+
* Index of the to-be-checked association in `parent.ref`
|
|
169
|
+
*
|
|
170
|
+
* @param {(errorIndex: number) => void} noForeignKeyCallback
|
|
171
|
+
* Called if there are non-fk path steps. Argument is index in `parent.ref` that is faulty.
|
|
172
|
+
* If a fk-step is missing, `errorIndex` will be `> parent.ref.length`.
|
|
168
173
|
*/
|
|
169
|
-
function
|
|
174
|
+
function requireForeignKeyAccess( parent, refIndex, noForeignKeyCallback ) {
|
|
170
175
|
const { ref, _links } = parent;
|
|
171
176
|
const assoc = _links[refIndex].art;
|
|
172
177
|
|
|
173
|
-
const next = ref[refIndex + 1]
|
|
178
|
+
const next = pathId(ref[refIndex + 1]);
|
|
174
179
|
let possibleKeys = next && assoc.keys.filter(r => r.ref[0] === next);
|
|
175
180
|
if (!possibleKeys || possibleKeys.length === 0) {
|
|
176
|
-
|
|
181
|
+
noForeignKeyCallback(refIndex + 1);
|
|
177
182
|
}
|
|
178
183
|
else {
|
|
179
184
|
// For cases where `Association to T { struct.one, struct.two };` is used instead of `{ struct }`.
|
|
180
|
-
//
|
|
185
|
+
// We know that `{ struct, struct.one }` is not possible, so no prefix check required.
|
|
186
|
+
// If `ref.length` does not cover any full foreign key, then we call noForeignKeyCallback()
|
|
187
|
+
// as well. This could happen for `struct.one` as foreign key, and `assoc.struct = …`
|
|
188
|
+
// in ON-condition.
|
|
181
189
|
let fkIndex = 0;
|
|
182
190
|
let success = false;
|
|
183
|
-
while (!success && possibleKeys.length > 0) {
|
|
191
|
+
while (!success && possibleKeys.length > 0 && refIndex + fkIndex + 1 < ref.length) {
|
|
184
192
|
const pathStep = ref[refIndex + fkIndex + 1].id || ref[refIndex + fkIndex + 1];
|
|
185
193
|
|
|
186
194
|
// Function is immediately executed, before next iteration of loop. Access is fine.
|
|
@@ -195,7 +203,7 @@ function checkForeignKeyAccess( parent, refIndex, csnPath, callback ) {
|
|
|
195
203
|
++fkIndex;
|
|
196
204
|
}
|
|
197
205
|
if (!success)
|
|
198
|
-
|
|
206
|
+
noForeignKeyCallback(refIndex + fkIndex);
|
|
199
207
|
}
|
|
200
208
|
}
|
|
201
209
|
|
|
@@ -211,4 +219,4 @@ function validateMixinOnCondition( query, path ) {
|
|
|
211
219
|
forEachGeneric( query.SELECT, 'mixin', validateOnCondition.bind(this), path );
|
|
212
220
|
}
|
|
213
221
|
|
|
214
|
-
module.exports = { validateOnCondition, validateMixinOnCondition,
|
|
222
|
+
module.exports = { validateOnCondition, validateMixinOnCondition, requireForeignKeyAccess };
|
|
@@ -1,136 +1,195 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('../model/csnUtils');
|
|
4
|
+
const { requireForeignKeyAccess } = require('./onConditions');
|
|
5
|
+
const { pathId } = require('../model/csnRefs');
|
|
6
|
+
|
|
7
|
+
const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
10
|
+
* Ensure that all source artifacts and association targets are persisted on the database.
|
|
11
|
+
* Otherwise, we would end up with a JOIN against a non-existent table.
|
|
7
12
|
*
|
|
8
13
|
* Check the given query for:
|
|
9
|
-
* -
|
|
14
|
+
* - Association-traversal over skipped/abstract things
|
|
10
15
|
* - Associations (indirectly) using managed associations without foreign keys
|
|
11
16
|
*
|
|
12
17
|
* Currently checked:
|
|
13
|
-
* - "columns" for something like toF.
|
|
18
|
+
* - "columns" for something like toF.field, where F is skipped. But publishing toF is fine, will be ignored later on
|
|
14
19
|
* - "from" for something like "select from E.toF" where E, F or E AND F are no-db.
|
|
15
20
|
*
|
|
16
|
-
*
|
|
17
21
|
* @param {CSN.Query} query Query to check
|
|
18
22
|
*/
|
|
19
23
|
function checkQueryForNoDBArtifacts( query ) {
|
|
20
24
|
if (isPersistedOnDatabase(this.artifact) && !hasAnnotationValue(this.artifact, '@cds.persistence.table')) {
|
|
21
|
-
const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];
|
|
22
25
|
for (const prop of generalQueryProperties) {
|
|
23
26
|
const queryPart = (query.SELECT || query.SET)[prop];
|
|
24
27
|
if (Array.isArray(queryPart)) {
|
|
25
28
|
for (const part of queryPart)
|
|
26
|
-
|
|
29
|
+
checkQueryRef.call(this, part, prop === 'columns');
|
|
27
30
|
}
|
|
28
31
|
else if (typeof queryPart === 'object') {
|
|
29
|
-
|
|
32
|
+
checkQueryRef.call(this, queryPart, prop === 'columns');
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
|
-
*
|
|
39
|
+
* @param {CSN.Element} assoc Definition to check
|
|
40
|
+
* @returns {boolean} True, if there are any foreign keys.
|
|
41
|
+
*/
|
|
42
|
+
function hasForeignKeys( assoc ) {
|
|
43
|
+
if (!assoc || !assoc.keys)
|
|
44
|
+
return false;
|
|
45
|
+
return _hasForeignKeyOrElements.call(this, assoc);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns true if the given definition has at least one foreign key or element leaf node.
|
|
37
50
|
*
|
|
38
|
-
* @param {CSN.
|
|
39
|
-
* @returns {
|
|
51
|
+
* @param {CSN.Artifact} def
|
|
52
|
+
* @returns {boolean} True if there are FKs/element leaves.
|
|
40
53
|
*/
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return c;
|
|
45
|
-
if (def.elements) {
|
|
46
|
-
c += Object.values(def.elements).reduce((acc, e) => {
|
|
47
|
-
acc += leafCount.call(this, e);
|
|
48
|
-
return acc;
|
|
49
|
-
}, 0);
|
|
54
|
+
function _hasForeignKeyOrElements( def ) {
|
|
55
|
+
if (!def) {
|
|
56
|
+
return false;
|
|
50
57
|
}
|
|
51
58
|
else if (def.keys) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
return def.keys.some(e => _hasForeignKeyOrElements.call(this, e._art));
|
|
60
|
+
}
|
|
61
|
+
else if (def.elements) {
|
|
62
|
+
return Object.values(def.elements).some( e => _hasForeignKeyOrElements.call(this, e));
|
|
56
63
|
}
|
|
57
64
|
else if (def.type) {
|
|
58
65
|
if (isBuiltinType(def.type) && !(def.target))
|
|
59
|
-
return
|
|
60
|
-
|
|
66
|
+
return true;
|
|
67
|
+
return _hasForeignKeyOrElements.call(this, this.artifactRef(def.type, null));
|
|
61
68
|
}
|
|
62
|
-
return
|
|
69
|
+
return false;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
/**
|
|
66
|
-
* Check the given ref for usage of skipped/abstract assoc targets
|
|
73
|
+
* Check the given `obj.ref` for usage of skipped/abstract assoc targets
|
|
67
74
|
*
|
|
68
|
-
* @param {
|
|
75
|
+
* @param {CSN.Column} obj CSN "thing" to check
|
|
69
76
|
* @param {boolean} inColumns True if the ref is part of a from
|
|
70
77
|
*/
|
|
71
|
-
function
|
|
72
|
-
if (!
|
|
78
|
+
function checkQueryRef( obj, inColumns ) {
|
|
79
|
+
if (!obj)
|
|
73
80
|
return;
|
|
74
81
|
|
|
75
|
-
|
|
82
|
+
if (obj.expand || obj.inline)
|
|
83
|
+
_checkExpandInline.call(this, obj);
|
|
84
|
+
|
|
85
|
+
else if (obj.ref && obj._links)
|
|
86
|
+
_checkRef.call(this, obj.ref, obj._links, obj.$path, inColumns);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Run _checkRef on all expand/inline structure leaf nodes.
|
|
91
|
+
* We do so by creating artificial paths that follow expand/inline nodes to their leaves.
|
|
92
|
+
*
|
|
93
|
+
* @param {CSN.Column} obj
|
|
94
|
+
* @param {CSN.Path} previousRefs
|
|
95
|
+
* @param {object[]} previousLinks
|
|
96
|
+
*/
|
|
97
|
+
function _checkExpandInline( obj, previousRefs = [], previousLinks = [] ) {
|
|
98
|
+
if (obj.ref && obj._links) { // There could be anonymous nested "expand".
|
|
99
|
+
previousRefs = previousRefs.concat(obj.ref);
|
|
100
|
+
previousLinks = previousLinks.concat(obj._links);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!obj.expand && !obj.inline) {
|
|
104
|
+
if (obj.ref && obj._links) {
|
|
105
|
+
// `inColumns: true` for expand/inline
|
|
106
|
+
_checkRef.call(this, previousRefs, previousLinks, obj.$path, true);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const col of obj.expand || obj.inline)
|
|
112
|
+
_checkExpandInline.call(this, col, previousRefs, previousLinks);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Implementation of checkQueryRef() that works on ref/links arrays instead of a column.
|
|
117
|
+
*
|
|
118
|
+
* @param {CSN.Path} ref
|
|
119
|
+
* @param {object[]} _links
|
|
120
|
+
* @param {CSN.Path} $path
|
|
121
|
+
* @param {boolean} inColumns
|
|
122
|
+
*/
|
|
123
|
+
function _checkRef( ref, _links, $path, inColumns ) {
|
|
124
|
+
if (!ref || !_links )
|
|
125
|
+
return;
|
|
126
|
+
|
|
127
|
+
let nonPersistedTarget = null;
|
|
128
|
+
const isPublishedAssoc = this.csnUtils.isAssocOrComposition(_links[_links.length - 1].art);
|
|
76
129
|
|
|
77
130
|
// Don't check the last element - to allow association publishing in columns
|
|
78
|
-
for (let i = 0; i < (inColumns ?
|
|
79
|
-
const link =
|
|
131
|
+
for (let i = 0; i < (inColumns ? _links.length - 1 : _links.length); i++) {
|
|
132
|
+
const link = _links[i];
|
|
80
133
|
if (!link)
|
|
81
134
|
continue;
|
|
82
|
-
|
|
83
135
|
const { art } = link;
|
|
84
136
|
if (!art)
|
|
85
137
|
continue;
|
|
86
138
|
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
const isLast = i >= _links.length - 1;
|
|
140
|
+
const isUnmanagedOrNoKeys = !art.keys;
|
|
141
|
+
const targetArt = art.target ? this.artifactRef(art.target) : art;
|
|
142
|
+
const pathStep = pathId(ref[i]);
|
|
143
|
+
const name = art.target || pathStep;
|
|
144
|
+
|
|
145
|
+
// If any path-step is not persisted, then all following path steps must only access foreign keys.
|
|
146
|
+
// For example, it could be toF.toG.field, where toG is FK of toF; the FK-only-check would succeed,
|
|
147
|
+
// but we only check "field" in the next iteration, where it is seen as access on a non-skipped
|
|
148
|
+
// entity, hence the need to store if any target is skipped.
|
|
149
|
+
if (!isPersistedOnDatabase(targetArt))
|
|
150
|
+
nonPersistedTarget = { name, pathStep };
|
|
151
|
+
|
|
152
|
+
if (nonPersistedTarget) {
|
|
153
|
+
let isJoinRelevant = isPublishedAssoc || // publishing associations is always join relevant
|
|
154
|
+
isLast || // e.g. FROM targets are always join relevant.
|
|
155
|
+
isUnmanagedOrNoKeys; // unmanaged associations are always join relevant -> no FKs
|
|
156
|
+
|
|
157
|
+
if (!isJoinRelevant) {
|
|
158
|
+
// for managed, published associations with more than one $path-step, only FK
|
|
159
|
+
// access is allowed.
|
|
160
|
+
requireForeignKeyAccess({ ref, _links }, i, () => {
|
|
161
|
+
isJoinRelevant = true;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (isJoinRelevant) {
|
|
166
|
+
const cdsPersistenceSkipped = hasAnnotationValue(targetArt, '@cds.persistence.skip');
|
|
167
|
+
this.error( null, $path, {
|
|
168
|
+
'#': cdsPersistenceSkipped ? 'std' : 'abstract',
|
|
169
|
+
id: nonPersistedTarget.pathStep,
|
|
170
|
+
elemref: { ref },
|
|
171
|
+
name: nonPersistedTarget.name,
|
|
111
172
|
}, {
|
|
112
173
|
std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
113
174
|
abstract: 'Unexpected “abstract” association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
114
175
|
} );
|
|
176
|
+
break; // only one error per path
|
|
115
177
|
}
|
|
116
178
|
}
|
|
179
|
+
|
|
117
180
|
// check managed association to have foreign keys array filled
|
|
118
|
-
if (art.keys &&
|
|
119
|
-
this.error(
|
|
120
|
-
|
|
121
|
-
{ id: pathStep, elemref: obj },
|
|
122
|
-
'Path step $(ID) of $(ELEMREF) has no foreign keys');
|
|
181
|
+
if (art.keys && !hasForeignKeys.call(this, art)) {
|
|
182
|
+
this.error('expr-missing-foreign-key', $path, { id: pathStep, elemref: { ref } } );
|
|
183
|
+
break; // only one error per path
|
|
123
184
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (j < art.on.length - 2 && art.on[j].ref && art.on[j + 1] === '=' && art.on[j + 2].ref) {
|
|
185
|
+
else if (art.on) {
|
|
186
|
+
for (let j = 0; j < art.on.length - 2; j++) {
|
|
187
|
+
if (art.on[j].ref && art.on[j + 1] === '=' && art.on[j + 2].ref) {
|
|
128
188
|
const [ fwdAssoc, fwdPath ] = getForwardAssociation(pathStep, art.on[j], art.on[j + 2]);
|
|
129
|
-
if (fwdAssoc
|
|
130
|
-
this.error(null,
|
|
131
|
-
{ name: pathStep, elemref: obj, id: fwdPath },
|
|
189
|
+
if (fwdAssoc?.keys && !hasForeignKeys.call(this, fwdAssoc)) {
|
|
190
|
+
this.error(null, $path, { name: pathStep, elemref: { ref }, id: fwdPath },
|
|
132
191
|
'Path step $(NAME) of $(ELEMREF) is a $self comparison with $(ID) that has no foreign keys');
|
|
133
|
-
|
|
192
|
+
break; // only one error per path
|
|
134
193
|
}
|
|
135
194
|
}
|
|
136
195
|
}
|
|
@@ -139,7 +198,7 @@ function checkRef( obj, inColumns ) {
|
|
|
139
198
|
}
|
|
140
199
|
|
|
141
200
|
/**
|
|
142
|
-
* Get the forward association from a
|
|
201
|
+
* Get the forward association from a backlink $self association.
|
|
143
202
|
*
|
|
144
203
|
* @param {string} prefix Name of the association
|
|
145
204
|
* @param {object} lhs Left hand side of the on-condition part
|
|
@@ -5,10 +5,12 @@ const { forEachGeneric, applyTransformationsOnNonDictionary } = require('../mode
|
|
|
5
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Validate select items of a query. If a column reference starts with $self or
|
|
8
|
+
* Validate select items of a query. If a column reference starts with $self or
|
|
9
|
+
* $projection, it must not contain association steps.
|
|
9
10
|
* Furthermore, for to.hdbcds, window functions are not allowed.
|
|
10
11
|
*
|
|
11
|
-
* For to.hdbcds-hdbcds, structures and managed associations are not allowed
|
|
12
|
+
* For to.hdbcds-hdbcds, structures and managed associations are not allowed
|
|
13
|
+
* as they are not flattened - @see rejectManagedAssociationsAndStructuresForHdbcdsNames
|
|
12
14
|
*
|
|
13
15
|
* @param {CSN.Query} query query object
|
|
14
16
|
* @todo Why do we care about this with $self?
|
|
@@ -17,58 +19,6 @@ function validateSelectItems( query ) {
|
|
|
17
19
|
const { SELECT } = query;
|
|
18
20
|
if (!SELECT)
|
|
19
21
|
return;
|
|
20
|
-
/**
|
|
21
|
-
* Check for a $self.<assoc> in columns etc. - since the $self.<assoc> references the "outside" view
|
|
22
|
-
* of the association, this is not allowed.
|
|
23
|
-
*
|
|
24
|
-
* @param {string} queryPart Part of the query that is being checked
|
|
25
|
-
* @returns {Function} Function as callback for applyTransformations
|
|
26
|
-
*/
|
|
27
|
-
function checkRefForInvalid$Self( queryPart ) {
|
|
28
|
-
const signalError = (error, parent, type) => {
|
|
29
|
-
if (queryPart === 'columns') {
|
|
30
|
-
error(null, parent.$path,
|
|
31
|
-
{ name: parent.ref[0], type },
|
|
32
|
-
'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
33
|
-
}
|
|
34
|
-
else if (queryPart === 'orderBy') {
|
|
35
|
-
error(null, parent.$path,
|
|
36
|
-
{ id: queryPart, type },
|
|
37
|
-
'Items of the $(ID)-clause must not contain path steps of type $(TYPE)');
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
error(null, parent.$path,
|
|
41
|
-
{ id: queryPart, name: parent.ref[0], type },
|
|
42
|
-
'Items of the $(ID)-clause starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
return function checkForInvalid$SelfInRef(parent) {
|
|
46
|
-
if (parent.ref && (parent.$scope === '$self' || parent.$scope === '$query')) {
|
|
47
|
-
const { _links } = parent;
|
|
48
|
-
for (let j = parent.$scope === '$self' ? 1 : 0; j < _links.length - 1; j++) {
|
|
49
|
-
if (_links[j].art.target) {
|
|
50
|
-
if (_links[j].art.on) {
|
|
51
|
-
// It's an unmanaged association - traversal is always forbidden
|
|
52
|
-
signalError(this.error, parent, _links[j].art.type);
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
// It's a managed association - access of the foreign keys is allowed
|
|
56
|
-
const nextRef = parent.ref[j + 1].id || parent.ref[j + 1];
|
|
57
|
-
if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
|
|
58
|
-
signalError(this.error, parent, _links[j].art.type);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const last = _links[_links.length - 1];
|
|
64
|
-
|
|
65
|
-
if (last.art.target && last.art.on) {
|
|
66
|
-
// It's an unmanaged association - traversal is always forbidden
|
|
67
|
-
signalError(this.error, parent, last.art.type);
|
|
68
|
-
} // managed is okay, can be handled via tuple expansion
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
22
|
|
|
73
23
|
/**
|
|
74
24
|
* Check the given assoc filter for usage of $self - in an assoc-filter, you must only
|
|
@@ -90,7 +40,6 @@ function validateSelectItems( query ) {
|
|
|
90
40
|
|
|
91
41
|
const aTCB = (parent, prop) => {
|
|
92
42
|
applyTransformationsOnNonDictionary(parent, prop, {
|
|
93
|
-
ref: checkRefForInvalid$Self(prop).bind(this),
|
|
94
43
|
where: checkFilterForInvalid$Self.bind(this),
|
|
95
44
|
}, { skipStandard: { on: true }, drillRef: true });
|
|
96
45
|
};
|
|
@@ -18,14 +18,25 @@ function checkSqlAnnotationOnElement( member, memberName, prop, path ) {
|
|
|
18
18
|
this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, 'Annotation $(ANNO) can\'t be used on elements' );
|
|
19
19
|
|
|
20
20
|
if (member['@sql.append']) {
|
|
21
|
-
if (this.artifact.query || this.artifact.projection)
|
|
21
|
+
if (this.artifact.query || this.artifact.projection) {
|
|
22
22
|
this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on elements in views' );
|
|
23
|
-
|
|
23
|
+
}
|
|
24
|
+
else if (this.csnUtils.isStructured(member)) {
|
|
24
25
|
this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
|
|
25
|
-
|
|
26
|
+
}
|
|
27
|
+
else if (this.csnUtils.isManagedAssociation(member)) {
|
|
28
|
+
this.message('anno-invalid-sql-assoc', path, { anno: 'sql.append', '#': member.type }, {
|
|
29
|
+
std: 'Annotation $(ANNO) can\'t be used here',
|
|
30
|
+
'cds.Association': 'Annotation $(ANNO) can\'t be used on association elements',
|
|
31
|
+
'cds.Composition': 'Annotation $(ANNO) can\'t be used on composition elements',
|
|
32
|
+
} );
|
|
33
|
+
}
|
|
34
|
+
else if (member.value && !member.value.stored) {
|
|
26
35
|
this.message('anno-invalid-sql-calc', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on calculated elements on read' );
|
|
27
|
-
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
28
38
|
checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
|
|
39
|
+
}
|
|
29
40
|
}
|
|
30
41
|
}
|
|
31
42
|
|
package/lib/checks/types.js
CHANGED
|
@@ -162,9 +162,9 @@ function errorAboutMissingType( error, path, artifact, name,
|
|
|
162
162
|
let variant = isElement ? 'elm' : 'std';
|
|
163
163
|
if (artifact.value?.stored)
|
|
164
164
|
variant = 'calc';
|
|
165
|
-
error('
|
|
166
|
-
std: '
|
|
167
|
-
elm: '
|
|
165
|
+
error('def-missing-type', path, { art: name, '#': variant }, {
|
|
166
|
+
std: 'Missing type for $(ART)',
|
|
167
|
+
elm: 'Missing type for element $(ART)',
|
|
168
168
|
calc: 'A stored calculated element must have a type',
|
|
169
169
|
});
|
|
170
170
|
}
|
package/lib/checks/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../model/csnUtils');
|
|
4
|
-
const { RelationalOperators } = require('../transform/
|
|
4
|
+
const { RelationalOperators } = require('../transform/transformUtils');
|
|
5
5
|
/**
|
|
6
6
|
* Prepare the ref steps so that they are loggable
|
|
7
7
|
*
|
|
@@ -58,8 +58,9 @@ function otherSideIsExpandableStructure( on, startIndex ) {
|
|
|
58
58
|
* @returns {object} final artifact type
|
|
59
59
|
*/
|
|
60
60
|
function resolveArtifactType( art ) {
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const type = art?._type?.type || art?.type;
|
|
62
|
+
if (type && !isBuiltinType(type))
|
|
63
|
+
return this.csnUtils.getFinalTypeInfo(type);
|
|
63
64
|
|
|
64
65
|
return art;
|
|
65
66
|
}
|
package/lib/checks/validator.js
CHANGED
|
@@ -11,6 +11,7 @@ const enrich = require('./enricher');
|
|
|
11
11
|
const { validateSelectItems } = require('./selectItems');
|
|
12
12
|
const { rejectParamDefaultsInHanaCds, warnAboutDefaultOnAssociationForHanaCds } = require('./defaultValues');
|
|
13
13
|
const validateCdsPersistenceAnnotation = require('./cdsPersistence');
|
|
14
|
+
const navigationIntoMany = require('./manyNavigations');
|
|
14
15
|
const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
|
|
15
16
|
const validateHasPersistedElements = require('./hasPersistedElements');
|
|
16
17
|
const checkForHanaTypes = require('./checkForTypes');
|
|
@@ -71,7 +72,7 @@ const forRelationalDBArtifactValidators
|
|
|
71
72
|
checkSqlAnnotationOnArtifact,
|
|
72
73
|
];
|
|
73
74
|
|
|
74
|
-
const forRelationalDBCsnValidators = [ nonexpandableStructuredInExpression ];
|
|
75
|
+
const forRelationalDBCsnValidators = [ nonexpandableStructuredInExpression, navigationIntoMany ];
|
|
75
76
|
/**
|
|
76
77
|
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
77
78
|
*/
|
|
@@ -233,6 +234,7 @@ function forRelationalDB( csn, that ) {
|
|
|
233
234
|
{
|
|
234
235
|
skipArtifact: artifact => artifact.abstract ||
|
|
235
236
|
hasAnnotationValue(artifact, '@cds.persistence.skip') ||
|
|
237
|
+
hasAnnotationValue(artifact, '@cds.persistence.exists') ||
|
|
236
238
|
[ 'action', 'function', 'event' ].includes(artifact.kind),
|
|
237
239
|
});
|
|
238
240
|
}
|