@sap/cds-compiler 4.6.0 → 4.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/bin/cds_update_identifiers.js +6 -2
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_ARCHIVE.md +9 -9
- package/doc/CHANGELOG_BETA.md +6 -0
- package/lib/api/main.js +56 -9
- package/lib/api/options.js +6 -3
- package/lib/api/validate.js +20 -29
- package/lib/base/message-registry.js +27 -3
- package/lib/base/messages.js +8 -3
- package/lib/base/model.js +2 -0
- package/lib/checks/dbFeatureFlags.js +28 -0
- package/lib/checks/elements.js +81 -13
- package/lib/checks/enricher.js +3 -2
- package/lib/checks/validator.js +38 -4
- package/lib/compiler/assert-consistency.js +4 -4
- package/lib/compiler/checks.js +5 -4
- package/lib/compiler/define.js +2 -2
- package/lib/compiler/generate.js +2 -1
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/propagator.js +3 -11
- package/lib/compiler/shared.js +2 -1
- package/lib/compiler/tweak-assocs.js +43 -24
- package/lib/edm/annotations/edmJson.js +3 -0
- package/lib/edm/annotations/genericTranslation.js +156 -106
- package/lib/edm/annotations/preprocessAnnotations.js +11 -14
- package/lib/edm/csn2edm.js +27 -24
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +135 -37
- package/lib/edm/edmUtils.js +20 -7
- package/lib/gen/Dictionary.json +2 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +9 -11
- package/lib/gen/languageParser.js +5942 -5446
- package/lib/json/to-csn.js +7 -114
- package/lib/language/genericAntlrParser.js +106 -48
- package/lib/model/cloneCsn.js +203 -0
- package/lib/model/csnRefs.js +11 -3
- package/lib/model/csnUtils.js +42 -85
- package/lib/optionProcessor.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +133 -88
- package/lib/render/toHdbcds.js +1 -5
- package/lib/render/toSql.js +7 -9
- package/lib/render/utils/common.js +9 -16
- package/lib/transform/addTenantFields.js +277 -102
- package/lib/transform/db/applyTransformations.js +14 -9
- package/lib/transform/db/backlinks.js +2 -1
- package/lib/transform/db/constraints.js +60 -82
- package/lib/transform/db/expansion.js +6 -6
- package/lib/transform/db/featureFlags.js +5 -0
- package/lib/transform/db/flattening.js +4 -4
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/rewriteCalculatedElements.js +2 -2
- package/lib/transform/db/transformExists.js +12 -0
- package/lib/transform/db/views.js +5 -2
- package/lib/transform/draft/odata.js +7 -6
- package/lib/transform/effective/associations.js +2 -1
- package/lib/transform/effective/main.js +3 -2
- package/lib/transform/effective/types.js +6 -3
- package/lib/transform/forOdata.js +39 -24
- package/lib/transform/forRelationalDB.js +34 -27
- package/lib/transform/localized.js +29 -9
- package/lib/transform/odata/flattening.js +419 -0
- package/lib/transform/odata/toFinalBaseType.js +95 -15
- package/lib/transform/odata/typesExposure.js +9 -7
- package/lib/transform/transformUtils.js +7 -6
- package/lib/transform/translateAssocsToJoins.js +3 -3
- package/lib/utils/objectUtils.js +14 -0
- package/package.json +1 -1
package/lib/checks/elements.js
CHANGED
|
@@ -109,29 +109,97 @@ function checkVirtualElement( member ) {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
* Checks whether managed associations
|
|
113
|
-
*
|
|
112
|
+
* Checks whether managed associations with cardinality 'to many' have no ON-condition.
|
|
113
|
+
* If there isn't, and if _all_ key elements on the target side are covered by the foreign key,
|
|
114
|
+
* then the association is effectively to-one -> warning.
|
|
114
115
|
*
|
|
115
116
|
* @param {CSN.Artifact} member The member (e.g. element artifact)
|
|
116
117
|
*/
|
|
117
118
|
function checkManagedAssoc( member ) {
|
|
118
119
|
if (!member.target || isManagedComposition.bind(this)(member))
|
|
119
120
|
return;
|
|
120
|
-
|
|
121
121
|
// Implementation note: Imported services (i.e. external ones) may contain to-many associations
|
|
122
122
|
// with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
|
|
123
123
|
// foreign key array, we won't emit a warning to avoid spamming the user.
|
|
124
|
-
const max = member.cardinality?.max
|
|
125
|
-
if (max
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
124
|
+
const max = member.cardinality?.max ?? 1;
|
|
125
|
+
if (max === 1 || member.on || !member.keys || member.keys.length === 0)
|
|
126
|
+
return;
|
|
127
|
+
// We use the fact that `key` is only supported top-level (warning otherwise).
|
|
128
|
+
// And if an element of a structured _type_ is "key", we get a warning for the key.
|
|
129
|
+
// However, we may get false negatives for our warning, which is acceptable, e.g. for
|
|
130
|
+
// `type T : { key i: String; }; entity A { id : T; };` with `… to many A { id };`
|
|
131
|
+
const target = typeof member.target === 'object' ? member.target : this.csnUtils.getCsnDef(member.target);
|
|
132
|
+
const targetKeys = Object.entries(target.elements || {}).filter(elem => !!elem[1].key);
|
|
133
|
+
const foreignKeys = structurizeForeignKeys(member.keys);
|
|
134
|
+
if (!coversAllTargetKeys.call(this, foreignKeys, targetKeys))
|
|
135
|
+
return; // foreign key does not cover at least one target key -> can be to-many
|
|
136
|
+
|
|
137
|
+
const isNoDb = this.artifact['@cds.persistence.skip'] || this.artifact['@cds.persistence.exists'];
|
|
138
|
+
this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality?.$path || member.$path, {
|
|
139
|
+
value: cardinality2str(member, false),
|
|
140
|
+
'#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
|
|
141
|
+
}, {
|
|
142
|
+
std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
|
|
143
|
+
comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Returns true if the foreign keys cover _all_ of the target keys.
|
|
149
|
+
* Returns false otherwise.
|
|
150
|
+
*
|
|
151
|
+
* @see checkManagedAssoc()
|
|
152
|
+
*
|
|
153
|
+
* @param {object} foreignKeys Structure returned by `structurizeForeignKeys()`.
|
|
154
|
+
* @param {Array} targetKeys Object.entries() value of target keys.
|
|
155
|
+
* @returns {boolean} Whether all target keys are covered
|
|
156
|
+
*/
|
|
157
|
+
function coversAllTargetKeys( foreignKeys, targetKeys ) {
|
|
158
|
+
if (foreignKeys.length < targetKeys.length || targetKeys.length === 0) {
|
|
159
|
+
// there are fewer foreign keys than keys on the target side
|
|
160
|
+
// or there are no keys on the target side, in which case there is no
|
|
161
|
+
// possibility to cover all keys.
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const [ targetKeyName, targetKey ] of targetKeys) {
|
|
166
|
+
const foreignKey = foreignKeys.entries[targetKeyName];
|
|
167
|
+
if (!foreignKey)
|
|
168
|
+
return false; // foreign key does not cover this target key
|
|
169
|
+
if (foreignKey.length > 0) { // foreign key only selects sub-structures, not whole structured key
|
|
170
|
+
const elements = targetKey.elements || this.csnUtils.getFinalTypeInfo(targetKey.type)?.elements;
|
|
171
|
+
if (!elements)
|
|
172
|
+
return false; // model error (e.g. 'many type')
|
|
173
|
+
if (!coversAllTargetKeys( foreignKey, Object.entries(elements) ))
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Structurizes a foreign key into an object that can be used to compare foreign
|
|
182
|
+
* keys against their corresponding keys on target side.
|
|
183
|
+
*
|
|
184
|
+
* For `Association to T { a.b, a.c, b }` this structure will be returned:
|
|
185
|
+
* `{ length: 2, entries: { a: { length: 2, entries: { b: { length: 0, … }, …} } }}`
|
|
186
|
+
*
|
|
187
|
+
* @param {object[]} keys Foreign key array.
|
|
188
|
+
* @returns {object} Structured foreign key. Custom format, i.e. not via `elements`.
|
|
189
|
+
*/
|
|
190
|
+
function structurizeForeignKeys( keys ) {
|
|
191
|
+
const map = { entries: Object.create(null), length: 0 };
|
|
192
|
+
for (const key of keys) {
|
|
193
|
+
let entry = map;
|
|
194
|
+
for (const step of key.ref) {
|
|
195
|
+
if (!entry.entries[step]) {
|
|
196
|
+
entry.entries[step] = { entries: Object.create(null), length: 0 };
|
|
197
|
+
++entry.length;
|
|
198
|
+
}
|
|
199
|
+
entry = entry.entries[step];
|
|
200
|
+
}
|
|
134
201
|
}
|
|
202
|
+
return map;
|
|
135
203
|
}
|
|
136
204
|
|
|
137
205
|
/**
|
package/lib/checks/enricher.js
CHANGED
|
@@ -85,6 +85,7 @@ function enrichCsn( csn, options ) {
|
|
|
85
85
|
csnPath.pop();
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
|
|
88
89
|
/**
|
|
89
90
|
* Transformer for things that are annotations. When we have a "=" plus an expression of some sorts,
|
|
90
91
|
* we treat it like a "standard" thing.
|
|
@@ -94,7 +95,7 @@ function enrichCsn( csn, options ) {
|
|
|
94
95
|
* @param {object} node The value of node[_prop]
|
|
95
96
|
*/
|
|
96
97
|
function annotation( _parent, _prop, node ) {
|
|
97
|
-
if (options.
|
|
98
|
+
if (options.enrichAnnotations) {
|
|
98
99
|
if (node?.['='] !== undefined && xprInAnnoProperties.some(xProp => node[xProp] !== undefined)) {
|
|
99
100
|
standard(_parent, _prop, node);
|
|
100
101
|
}
|
|
@@ -212,5 +213,5 @@ module.exports = enrichCsn;
|
|
|
212
213
|
|
|
213
214
|
/**
|
|
214
215
|
* @typedef {object} enrichCsnOptions
|
|
215
|
-
* @property {boolean} [
|
|
216
|
+
* @property {boolean} [enrichAnnotations=false] Wether to process annotations and call custom transformers on them
|
|
216
217
|
*/
|
package/lib/checks/validator.js
CHANGED
|
@@ -5,7 +5,7 @@ const {
|
|
|
5
5
|
forEachMember, getNormalizedQuery, hasAnnotationValue,
|
|
6
6
|
applyTransformations, functionList,
|
|
7
7
|
} = require('../model/csnUtils');
|
|
8
|
-
const
|
|
8
|
+
const enrichCsn = require('./enricher');
|
|
9
9
|
|
|
10
10
|
// forRelationalDB
|
|
11
11
|
const { validateSelectItems } = require('./selectItems');
|
|
@@ -48,6 +48,7 @@ const {
|
|
|
48
48
|
checkSqlAnnotationOnArtifact,
|
|
49
49
|
checkSqlAnnotationOnElement,
|
|
50
50
|
} = require('./sql-snippets');
|
|
51
|
+
const dbFeatureFlags = require('./dbFeatureFlags');
|
|
51
52
|
|
|
52
53
|
const forRelationalDBMemberValidators
|
|
53
54
|
= [
|
|
@@ -65,8 +66,7 @@ const forRelationalDBMemberValidators
|
|
|
65
66
|
checkElementTypeDefinitionHasType,
|
|
66
67
|
];
|
|
67
68
|
|
|
68
|
-
const forRelationalDBArtifactValidators
|
|
69
|
-
= [
|
|
69
|
+
const forRelationalDBArtifactValidators = [
|
|
70
70
|
checkPrimaryKey,
|
|
71
71
|
// @cds.persistence has no impact on odata
|
|
72
72
|
validateCdsPersistenceAnnotation,
|
|
@@ -74,6 +74,8 @@ const forRelationalDBArtifactValidators
|
|
|
74
74
|
validateHasPersistedElements,
|
|
75
75
|
// sql.prepend/append
|
|
76
76
|
checkSqlAnnotationOnArtifact,
|
|
77
|
+
// strip down CSN to reduce it's size by removing non-sql relevant parts
|
|
78
|
+
stripNonDbRelevant,
|
|
77
79
|
];
|
|
78
80
|
|
|
79
81
|
const forRelationalDBCsnValidators = [
|
|
@@ -82,6 +84,7 @@ const forRelationalDBCsnValidators = [
|
|
|
82
84
|
nonexpandableStructuredInExpression,
|
|
83
85
|
navigationIntoMany,
|
|
84
86
|
checkPathsInStoredCalcElement,
|
|
87
|
+
dbFeatureFlags,
|
|
85
88
|
];
|
|
86
89
|
/**
|
|
87
90
|
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
@@ -149,7 +152,9 @@ function _validate( csn, that,
|
|
|
149
152
|
artifactValidators = [],
|
|
150
153
|
queryValidators = [],
|
|
151
154
|
iterateOptions = {} ) {
|
|
152
|
-
const { cleanup } =
|
|
155
|
+
const { cleanup } = enrichCsn(csn, that.options);
|
|
156
|
+
// TODO: Don't know if that's feasible? Do we really need to enrich annotations always?
|
|
157
|
+
// const { cleanup } = enrich(csn, { processAnnotations: that.options.tranformation === 'odata' });
|
|
153
158
|
|
|
154
159
|
applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });
|
|
155
160
|
|
|
@@ -277,4 +282,33 @@ function forOdata( csn, that ) {
|
|
|
277
282
|
});
|
|
278
283
|
}
|
|
279
284
|
|
|
285
|
+
const dbRelevantKinds = {
|
|
286
|
+
entity: true,
|
|
287
|
+
type: true,
|
|
288
|
+
aspect: true,
|
|
289
|
+
service: true,
|
|
290
|
+
context: true,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Shrink the CSN by
|
|
295
|
+
* - deleting bound actions
|
|
296
|
+
* - turning all non-entity/type/aspect/service/context entities into dummies
|
|
297
|
+
*
|
|
298
|
+
* @param {CSN.Artifact} artifact
|
|
299
|
+
* @param {string} artifactName
|
|
300
|
+
*/
|
|
301
|
+
function stripNonDbRelevant( artifact, artifactName ) {
|
|
302
|
+
if (this.options.transformation !== 'effective') {
|
|
303
|
+
if ( !dbRelevantKinds[artifact.kind] ) {
|
|
304
|
+
this.csn.definitions[artifactName] = {
|
|
305
|
+
kind: 'action',
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
else if (artifact.actions) {
|
|
309
|
+
delete artifact.actions;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
280
314
|
module.exports = { forRelationalDB, forOdata };
|
|
@@ -364,7 +364,7 @@ function assertConsistency( model, stage ) {
|
|
|
364
364
|
optional: [
|
|
365
365
|
'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
|
|
366
366
|
'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
|
|
367
|
-
'_origin', '_effectiveType', '$effectiveSeqNo', '_extensions',
|
|
367
|
+
'_origin', '_effectiveType', '$effectiveSeqNo', '_extensions', '$contains',
|
|
368
368
|
],
|
|
369
369
|
},
|
|
370
370
|
target: {
|
|
@@ -463,7 +463,7 @@ function assertConsistency( model, stage ) {
|
|
|
463
463
|
...typeProperties, // for CAST
|
|
464
464
|
],
|
|
465
465
|
},
|
|
466
|
-
query: { requires: [ 'query', 'location' ], optional: [ 'stored' ] },
|
|
466
|
+
query: { requires: [ 'query', 'location' ], optional: [ 'stored', '$parens' ] },
|
|
467
467
|
},
|
|
468
468
|
literal: { // TODO: check value against literal
|
|
469
469
|
test: isString,
|
|
@@ -481,7 +481,7 @@ function assertConsistency( model, stage ) {
|
|
|
481
481
|
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
|
|
482
482
|
// expressions as annotation values
|
|
483
483
|
'$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
|
|
484
|
-
'scale', 'srid', 'length', 'precision',
|
|
484
|
+
'scale', 'srid', 'length', 'precision', 'scope',
|
|
485
485
|
],
|
|
486
486
|
// TODO: restrict path to #simplePath
|
|
487
487
|
},
|
|
@@ -490,7 +490,7 @@ function assertConsistency( model, stage ) {
|
|
|
490
490
|
args: {
|
|
491
491
|
inherits: 'value',
|
|
492
492
|
optional: [
|
|
493
|
-
'name', '$duplicate', '$expected', 'args', 'suffix',
|
|
493
|
+
'name', '$duplicate', '$expected', 'args', 'suffix', '$parens',
|
|
494
494
|
'param', 'scope', // for dynamic parameter '?'
|
|
495
495
|
],
|
|
496
496
|
test: args,
|
package/lib/compiler/checks.js
CHANGED
|
@@ -591,10 +591,11 @@ function check( model ) {
|
|
|
591
591
|
*
|
|
592
592
|
* @param {any} xpr The expression to check
|
|
593
593
|
* @param {XSN.Artifact} user User for semantic location
|
|
594
|
-
* @
|
|
594
|
+
* @param {string} [context] where the expression is used, e.g. 'anno'
|
|
595
595
|
*/
|
|
596
|
-
function checkGenericExpression( xpr, user ) {
|
|
597
|
-
|
|
596
|
+
function checkGenericExpression( xpr, user, context ) {
|
|
597
|
+
if (context !== 'anno')
|
|
598
|
+
checkExpressionNotVirtual( xpr, user );
|
|
598
599
|
checkExpressionAssociationUsage( xpr, user, false );
|
|
599
600
|
if (xpr.op?.val === 'cast') {
|
|
600
601
|
requireExplicitTypeInSqlCast( xpr, user );
|
|
@@ -903,7 +904,7 @@ function check( model ) {
|
|
|
903
904
|
*/
|
|
904
905
|
function checkAnnotationExpressions( anno, art ) {
|
|
905
906
|
if (anno.$tokenTexts) {
|
|
906
|
-
checkGenericExpression( anno, art );
|
|
907
|
+
checkGenericExpression( anno, art, 'anno' );
|
|
907
908
|
}
|
|
908
909
|
else if (anno.literal === 'array') {
|
|
909
910
|
anno.val.forEach( val => checkAnnotationExpressions( val, art ) );
|
package/lib/compiler/define.js
CHANGED
|
@@ -946,8 +946,8 @@ function define( model ) {
|
|
|
946
946
|
|
|
947
947
|
if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
|
|
948
948
|
// TODO: the SQL backend should probably delete `excluding` when expanding `*`
|
|
949
|
-
// TODO: use `parent` for semantic location
|
|
950
|
-
warning( 'query-ignoring-
|
|
949
|
+
// TODO: use `parent` for semantic location; requires `_parent`/... links.
|
|
950
|
+
warning( 'query-ignoring-excluding', [ parent.excludingDict[$location], user ],
|
|
951
951
|
{ prop: '*' },
|
|
952
952
|
'Excluding elements without wildcard $(PROP) has no effect' );
|
|
953
953
|
}
|
package/lib/compiler/generate.js
CHANGED
|
@@ -782,7 +782,7 @@ function generate( model ) {
|
|
|
782
782
|
}
|
|
783
783
|
|
|
784
784
|
/**
|
|
785
|
-
* Copy
|
|
785
|
+
* Copy relevant annotations from
|
|
786
786
|
* source to target if present on source but not target.
|
|
787
787
|
*
|
|
788
788
|
* @param {object} target
|
|
@@ -796,6 +796,7 @@ function generate( model ) {
|
|
|
796
796
|
if (copyExists)
|
|
797
797
|
copy( '@cds.persistence.exists' );
|
|
798
798
|
copy( '@cds.persistence.skip' );
|
|
799
|
+
copy( '@cds.tenant.independent' );
|
|
799
800
|
|
|
800
801
|
function copy( anno ) {
|
|
801
802
|
if ( source[anno] && !target[anno] )
|
package/lib/compiler/populate.js
CHANGED
|
@@ -100,6 +100,8 @@ function populate( model ) {
|
|
|
100
100
|
? 'Composition'
|
|
101
101
|
: true;
|
|
102
102
|
const redirectInSubQueries = isDeprecatedEnabled( options, '_redirectInSubQueries' );
|
|
103
|
+
const ignoreSpecifiedElements
|
|
104
|
+
= isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
|
|
103
105
|
|
|
104
106
|
forEachDefinition( model, traverseElementEnvironments );
|
|
105
107
|
while (newAutoExposed.length) {
|
|
@@ -542,7 +544,9 @@ function populate( model ) {
|
|
|
542
544
|
ielem[prop].$priority = 'annotate';
|
|
543
545
|
wasAnnotated = true;
|
|
544
546
|
}
|
|
545
|
-
else if (typePropertiesFromSpecifiedElements[prop]) {
|
|
547
|
+
else if (typePropertiesFromSpecifiedElements[prop] && !ignoreSpecifiedElements) {
|
|
548
|
+
// If ignoreSpecifiedElements is set, we ignore type properties of specified elements,
|
|
549
|
+
// similar to how it was done in cds-compiler v3. Only annotations are copied.
|
|
546
550
|
if (!ielem.typeProps$)
|
|
547
551
|
setLink( ielem, 'typeProps$', Object.create( null ) );
|
|
548
552
|
// Note: At this point in time, effectiveType() was likely not called on the
|
|
@@ -27,11 +27,12 @@ const $inferred = Symbol.for( 'cds.$inferred' );
|
|
|
27
27
|
function propagate( model ) {
|
|
28
28
|
const props = {
|
|
29
29
|
'@com.sap.gtt.core.CoreModel.Indexable': never,
|
|
30
|
-
'@cds.persistence.exists': never,
|
|
30
|
+
'@cds.persistence.exists': never, // also copied in generate.js
|
|
31
31
|
'@cds.persistence.table': never,
|
|
32
32
|
'@cds.persistence.calcview': never,
|
|
33
33
|
'@cds.persistence.udf': never,
|
|
34
|
-
'@cds.persistence.skip': notWithPersistenceTable,
|
|
34
|
+
'@cds.persistence.skip': notWithPersistenceTable, // also copied in generate.js
|
|
35
|
+
// '@cds.tenant.independent' is propagated as normal, but also copied in generate.js
|
|
35
36
|
'@sql.append': never,
|
|
36
37
|
'@sql.prepend': never,
|
|
37
38
|
'@sql.replace': never,
|
|
@@ -106,15 +107,6 @@ function propagate( model ) {
|
|
|
106
107
|
chain.push({ target, source: target.value._artifact });
|
|
107
108
|
if (checkAndSetStatus( target.value._artifact ))
|
|
108
109
|
news.push( target.value._artifact );
|
|
109
|
-
|
|
110
|
-
if (target.value?._artifact.$inferred !== 'include') {
|
|
111
|
-
// If the referred to element is not inferred, it is a new one and not the original.
|
|
112
|
-
// The new one was not originally referred to => error;
|
|
113
|
-
// TODO: no messages in propagator.js
|
|
114
|
-
warning( 'ref-unexpected-override', [ target.name.location, target ],
|
|
115
|
-
{ id: target.name.id, target: target.value?._artifact },
|
|
116
|
-
'Calculated element $(ID) does not originally refer to $(TARGET)' );
|
|
117
|
-
}
|
|
118
110
|
}
|
|
119
111
|
chain.push( { target, source: origin } );
|
|
120
112
|
if (checkAndSetStatus( origin ))
|
package/lib/compiler/shared.js
CHANGED
|
@@ -1245,7 +1245,8 @@ function fns( model ) {
|
|
|
1245
1245
|
else {
|
|
1246
1246
|
if (!art.query && !art.type && !art.params && (art.elements || art.kind === 'aspect'))
|
|
1247
1247
|
return art;
|
|
1248
|
-
|
|
1248
|
+
const variant = art.params && 'param' || 'std';
|
|
1249
|
+
signalNotFound( 'ref-invalid-include', [ ref.location, user ], null, { '#': variant } );
|
|
1249
1250
|
}
|
|
1250
1251
|
return false;
|
|
1251
1252
|
}
|
|
@@ -160,12 +160,23 @@ function tweakAssocs( model ) {
|
|
|
160
160
|
function checkAnnotationForRefs( expr, user ) {
|
|
161
161
|
if (expr.$tokenTexts)
|
|
162
162
|
return traverseExpr( expr, 'annoRewrite', user, checkAnnotationRef );
|
|
163
|
-
if (expr.literal === 'array')
|
|
164
|
-
|
|
163
|
+
if (expr.literal === 'array') {
|
|
164
|
+
for (const val of expr.val) {
|
|
165
|
+
const found = checkAnnotationForRefs( val, user );
|
|
166
|
+
if (found)
|
|
167
|
+
return found;
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
165
171
|
if (expr.literal !== 'struct')
|
|
166
172
|
return null;
|
|
167
173
|
const struct = Object.values( expr.struct );
|
|
168
|
-
|
|
174
|
+
for (const val of struct) {
|
|
175
|
+
const found = checkAnnotationForRefs( val, user );
|
|
176
|
+
if (found)
|
|
177
|
+
return found;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
169
180
|
}
|
|
170
181
|
|
|
171
182
|
function checkAnnotationRef( ref, refCtx, user ) {
|
|
@@ -308,6 +319,8 @@ function tweakAssocs( model ) {
|
|
|
308
319
|
let elem = element.items || element; // TODO v5: nested items
|
|
309
320
|
if (elem.elements)
|
|
310
321
|
forEachGeneric( elem, 'elements', rewriteAssociation );
|
|
322
|
+
if (elem.targetAspect?.elements)
|
|
323
|
+
forEachGeneric( elem.targetAspect, 'elements', rewriteAssociation );
|
|
311
324
|
if (!originTarget( elem ))
|
|
312
325
|
return;
|
|
313
326
|
// console.log(message( null, elem.location, elem,
|
|
@@ -451,11 +464,7 @@ function tweakAssocs( model ) {
|
|
|
451
464
|
'Selecting unmanaged associations from a sub query is not supported' );
|
|
452
465
|
}
|
|
453
466
|
|
|
454
|
-
|
|
455
|
-
// TODO: not correct for e.g. composition-of-inline-aspect (#12223)
|
|
456
|
-
if (elem.$inferred !== 'include')
|
|
457
|
-
addConditionFromAssocPublishing( elem, assoc, nav );
|
|
458
|
-
|
|
467
|
+
addConditionFromAssocPublishing( elem, assoc, nav );
|
|
459
468
|
elem.on.$inferred = 'rewrite';
|
|
460
469
|
}
|
|
461
470
|
|
|
@@ -468,6 +477,10 @@ function tweakAssocs( model ) {
|
|
|
468
477
|
* The added condition (filter) is already rewritten relative to `elem`.
|
|
469
478
|
*/
|
|
470
479
|
function addConditionFromAssocPublishing( elem, assoc, nav ) {
|
|
480
|
+
if (elem.$inferred || elem._main?.$inferred === 'composition-entity') {
|
|
481
|
+
// filter was copied in original element already
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
471
484
|
const publishAssoc = (elem._main?.query || elem.$syntax === 'calc') &&
|
|
472
485
|
elem.value?.path?.length > 0;
|
|
473
486
|
if (!publishAssoc)
|
|
@@ -501,10 +514,10 @@ function tweakAssocs( model ) {
|
|
|
501
514
|
}
|
|
502
515
|
|
|
503
516
|
elem.on = {
|
|
504
|
-
op: { val: '
|
|
517
|
+
op: { val: 'ixpr', location },
|
|
505
518
|
args: [
|
|
506
|
-
// TODO: Get rid of $parens
|
|
507
519
|
{ ...elem.on, $parens: [ assoc.location ] },
|
|
520
|
+
{ val: 'and', literal: 'token', location },
|
|
508
521
|
filterToCondition( lastStep, elem, nav ),
|
|
509
522
|
],
|
|
510
523
|
location,
|
|
@@ -526,7 +539,6 @@ function tweakAssocs( model ) {
|
|
|
526
539
|
*/
|
|
527
540
|
function filterToCondition( assocPathStep, elem, nav ) {
|
|
528
541
|
const cond = copyExpr( assocPathStep.where );
|
|
529
|
-
// TODO: Get rid of $parens
|
|
530
542
|
cond.$parens = [ assocPathStep.location ];
|
|
531
543
|
traverseExpr( cond, 'rewrite-filter', elem, (expr) => {
|
|
532
544
|
if (!expr.path || expr.path.length === 0)
|
|
@@ -584,7 +596,7 @@ function tweakAssocs( model ) {
|
|
|
584
596
|
const lhs = {
|
|
585
597
|
path: [
|
|
586
598
|
{ id: assoc.name.id, location: elem.name.location },
|
|
587
|
-
...copyExpr( fKey.targetElement.path ),
|
|
599
|
+
...copyExpr( fKey.targetElement.path, weakLocation( elem.name.location ) ),
|
|
588
600
|
],
|
|
589
601
|
location: elem.name.location,
|
|
590
602
|
};
|
|
@@ -597,7 +609,7 @@ function tweakAssocs( model ) {
|
|
|
597
609
|
path: [
|
|
598
610
|
// use origin's name; elem could have alias
|
|
599
611
|
{ id: assoc.name.id, location: elem.name.location },
|
|
600
|
-
...copyExpr( fKey.targetElement.path ),
|
|
612
|
+
...copyExpr( fKey.targetElement.path, weakLocation( elem.name.location ) ),
|
|
601
613
|
],
|
|
602
614
|
location: elem.name.location,
|
|
603
615
|
};
|
|
@@ -762,7 +774,7 @@ function tweakAssocs( model ) {
|
|
|
762
774
|
state = setArtifactLink( i, elem );
|
|
763
775
|
}
|
|
764
776
|
else if (i) {
|
|
765
|
-
state = rewriteItem( state, i, i.id, assoc );
|
|
777
|
+
state = rewriteItem( state, i, i.id, assoc, false );
|
|
766
778
|
if (!state || state === true)
|
|
767
779
|
break;
|
|
768
780
|
}
|
|
@@ -801,9 +813,10 @@ function tweakAssocs( model ) {
|
|
|
801
813
|
elem;
|
|
802
814
|
// TODO: probably better to collect the non-projected foreign keys
|
|
803
815
|
// and have one message for all
|
|
804
|
-
error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), assoc ],
|
|
805
|
-
|
|
806
|
-
|
|
816
|
+
error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), assoc ], {
|
|
817
|
+
'#': 'std', id: item.id, target: alias._main, name: assoc.name.id,
|
|
818
|
+
});
|
|
819
|
+
// ''
|
|
807
820
|
return null;
|
|
808
821
|
}
|
|
809
822
|
item.id = name;
|
|
@@ -813,13 +826,19 @@ function tweakAssocs( model ) {
|
|
|
813
826
|
// refs in ON cannot navigate along `items`, no need to consider `items` here
|
|
814
827
|
if (env?.target)
|
|
815
828
|
env = env.target._artifact?._effectiveType;
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
829
|
+
const found = setArtifactLink( item, env?.elements?.[name] );
|
|
830
|
+
if (found)
|
|
831
|
+
return found;
|
|
832
|
+
|
|
833
|
+
const isExplicit = elem.target && !elem.target.$inferred;
|
|
834
|
+
const loc = isExplicit ? elem.target.location : item.location;
|
|
835
|
+
error( 'query-undefined-element', [ loc, assoc ], {
|
|
836
|
+
'#': isExplicit ? 'redirected' : 'std',
|
|
837
|
+
id: name || item.id,
|
|
838
|
+
name: elem.name.id,
|
|
839
|
+
target: elem.target._artifact,
|
|
840
|
+
keyword: 'redirected to',
|
|
841
|
+
} );
|
|
823
842
|
return null;
|
|
824
843
|
}
|
|
825
844
|
}
|
|
@@ -309,6 +309,9 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
309
309
|
anno, elemref: parent, '#': 'wrongref',
|
|
310
310
|
});
|
|
311
311
|
}
|
|
312
|
+
const [ head, ...tail ] = xpr;
|
|
313
|
+
if ((head.id || head) === '$self')
|
|
314
|
+
xpr = tail;
|
|
312
315
|
parentparent[parentprop] = { $Path: xpr.map(ps => ps.id || ps).join('/') };
|
|
313
316
|
};
|
|
314
317
|
//----------------------------------
|