@sap/cds-compiler 4.5.0 → 4.6.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 +50 -7
- package/bin/cdsc.js +13 -11
- package/doc/CHANGELOG_BETA.md +6 -0
- package/lib/api/main.js +256 -115
- package/lib/api/options.js +8 -0
- package/lib/base/message-registry.js +17 -4
- package/lib/base/messages.js +15 -3
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +45 -176
- package/lib/checks/elements.js +32 -34
- package/lib/checks/enricher.js +39 -3
- package/lib/checks/validator.js +2 -3
- package/lib/compiler/assert-consistency.js +2 -1
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +30 -6
- package/lib/compiler/define.js +31 -9
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/resolve.js +26 -21
- package/lib/compiler/shared.js +19 -9
- package/lib/compiler/tweak-assocs.js +82 -107
- package/lib/compiler/utils.js +2 -1
- package/lib/edm/annotations/edmJson.js +23 -22
- package/lib/edm/annotations/genericTranslation.js +14 -4
- package/lib/edm/csn2edm.js +24 -10
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +11 -9
- package/lib/edm/edmUtils.js +5 -2
- package/lib/gen/Dictionary.json +3 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -1
- package/lib/gen/language.tokens +1 -0
- package/lib/gen/languageParser.js +5253 -5214
- package/lib/json/to-csn.js +7 -1
- package/lib/language/antlrParser.js +19 -1
- package/lib/language/errorStrategy.js +21 -4
- package/lib/language/genericAntlrParser.js +9 -11
- package/lib/main.d.ts +28 -3
- package/lib/main.js +3 -0
- package/lib/model/csnRefs.js +4 -1
- package/lib/model/csnUtils.js +12 -7
- package/lib/optionProcessor.js +21 -19
- package/lib/render/manageConstraints.js +13 -29
- package/lib/render/toCdl.js +18 -15
- package/lib/render/toHdbcds.js +59 -28
- package/lib/render/toRename.js +6 -10
- package/lib/render/toSql.js +57 -82
- package/lib/render/utils/common.js +17 -0
- package/lib/transform/.eslintrc.json +9 -1
- package/lib/transform/addTenantFields.js +228 -0
- package/lib/transform/db/applyTransformations.js +27 -31
- package/lib/transform/db/assertUnique.js +4 -4
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/flattening.js +68 -69
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/draft/db.js +2 -16
- package/lib/transform/draft/odata.js +3 -3
- package/lib/transform/effective/associations.js +3 -5
- package/lib/transform/effective/main.js +6 -9
- package/lib/transform/forOdata.js +13 -9
- package/lib/transform/forRelationalDB.js +36 -17
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/transform/transformUtils.js +47 -34
- package/lib/transform/translateAssocsToJoins.js +33 -8
- package/package.json +2 -2
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Add tenant field MANDT to entities
|
|
2
|
+
|
|
3
|
+
// Prerequisites:
|
|
4
|
+
|
|
5
|
+
// - the input CSN is a `client` style CSN from the Core Compiler
|
|
6
|
+
// - using structure types with unmanaged associations is not supported by the
|
|
7
|
+
// Core Compiler (due to missing ON-rewrite)
|
|
8
|
+
|
|
9
|
+
// TODO entities without MANDT:
|
|
10
|
+
|
|
11
|
+
// - cache whether structure type contains (managed) association to entity with MANDT
|
|
12
|
+
// - disallow use of such a type in entity without MANDT
|
|
13
|
+
|
|
14
|
+
// Implementation remark:
|
|
15
|
+
|
|
16
|
+
// - the functions `forEachDefinition` & friends in csnUtils.js have become quite
|
|
17
|
+
// (too) general and are probably slow → not used here
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const { createMessageFunctions } = require( '../base/messages' );
|
|
22
|
+
const { traverseQuery } = require( '../model/csnRefs' );
|
|
23
|
+
|
|
24
|
+
const fieldName = 'tenant';
|
|
25
|
+
const fieldDef = {
|
|
26
|
+
key: true,
|
|
27
|
+
type: 'cds.String',
|
|
28
|
+
length: 36,
|
|
29
|
+
'@cds.api.ignore': true, // and/or $generated: 'tenant' for the full Universal CSN?
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function addTenantFields( csn, options ) {
|
|
33
|
+
const { definitions } = csn;
|
|
34
|
+
if (!definitions)
|
|
35
|
+
return csn;
|
|
36
|
+
const { error, throwWithError } = createMessageFunctions( options, 'tenant', csn );
|
|
37
|
+
|
|
38
|
+
const csnPath = [ 'definitions', '' ];
|
|
39
|
+
let projection;
|
|
40
|
+
|
|
41
|
+
for (const name in definitions) {
|
|
42
|
+
const art = definitions[name];
|
|
43
|
+
if (art?.kind !== 'entity')
|
|
44
|
+
continue;
|
|
45
|
+
csnPath[1] = name;
|
|
46
|
+
|
|
47
|
+
if (art['@cds.tenant.independent'] != null) {
|
|
48
|
+
error( null, csnPath, { anno: '@cds.tenant.independent' },
|
|
49
|
+
'Can\'t yet add annotation $(ANNO) to an entity' );
|
|
50
|
+
}
|
|
51
|
+
if (!handleElements( art ))
|
|
52
|
+
continue;
|
|
53
|
+
projection = art.query || art.projection && art;
|
|
54
|
+
if (projection)
|
|
55
|
+
traverseQuery( projection, null, null, handleQuery );
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
(csn.extensions || []).forEach( (ext, idx) => {
|
|
59
|
+
const tenant = ext.elements?.[fieldName];
|
|
60
|
+
const name = ext.annotate || ext.extend; // extend should not happen
|
|
61
|
+
if (tenant && definitions[name]?.kind === 'entity') { // TODO: ok for tenant-independent
|
|
62
|
+
error( null, [ 'extensions', idx, 'elements', 'tenant' ],
|
|
63
|
+
{ name: fieldName },
|
|
64
|
+
'Can\'t annotate element $(NAME) of a tenant-dependent entity' );
|
|
65
|
+
}
|
|
66
|
+
} );
|
|
67
|
+
|
|
68
|
+
throwWithError();
|
|
69
|
+
return csn; // input CSN changed by side effect
|
|
70
|
+
|
|
71
|
+
function handleElements( art ) {
|
|
72
|
+
const { elements } = art;
|
|
73
|
+
if (elements[fieldName]) {
|
|
74
|
+
error( null, [ ...csnPath, 'elements', fieldName ], { name: fieldName },
|
|
75
|
+
'Can\'t add tenant field to entity having an element $(NAME)' );
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (!Object.values( elements ).some( e => e.key )) {
|
|
79
|
+
error( null, csnPath, {},
|
|
80
|
+
'There must be a key in a tenant-dependent entity' );
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
handleAssociations( art );
|
|
84
|
+
art.elements = { [fieldName]: { ...fieldDef }, ...elements };
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function handleQuery( query ) {
|
|
89
|
+
// TODO: errors are temporary: start with simple projections only = no better
|
|
90
|
+
// message $location necessary yet
|
|
91
|
+
if (!projection) // error already reported
|
|
92
|
+
return;
|
|
93
|
+
if (query.ref) {
|
|
94
|
+
if ((query.as || implicitAs( query.ref )) === fieldName) {
|
|
95
|
+
error( null, csnPath, { name: fieldName },
|
|
96
|
+
'Can\'t have a table alias named $(NAME) in a tenant-dependent entity' );
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const select = query.SELECT || query.projection;
|
|
102
|
+
if (query.SET || query !== projection || !select?.from?.ref) {
|
|
103
|
+
error( null, csnPath, {},
|
|
104
|
+
'Can\'t add tenant columns to non-simple query entities' );
|
|
105
|
+
projection = null;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (query.projection)
|
|
110
|
+
csnPath.push( 'projection' );
|
|
111
|
+
else
|
|
112
|
+
csnPath.push( 'query', 'SELECT' );
|
|
113
|
+
|
|
114
|
+
if (select.mixin)
|
|
115
|
+
handleMixins( select.mixin );
|
|
116
|
+
if (select.excluding)
|
|
117
|
+
checkExcluding( select.excluding );
|
|
118
|
+
if (select.columns)
|
|
119
|
+
handleColumns( select.columns );
|
|
120
|
+
// TODO: for subqueries, we might need to adapt the inferred elements
|
|
121
|
+
// TODO: where exists ref -
|
|
122
|
+
// TODO: select and query clauses, especially with aggregation functions
|
|
123
|
+
handleGroupBy( select );
|
|
124
|
+
csnPath.length = 2;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function handleMixins( mixin ) {
|
|
128
|
+
csnPath.push( 'mixin', '' );
|
|
129
|
+
for (const name in mixin) {
|
|
130
|
+
csnPath[csnPath.length - 1] = name;
|
|
131
|
+
if (name !== fieldName) {
|
|
132
|
+
addToCondition( mixin[name], name );
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
error( null, csnPath, { name },
|
|
136
|
+
'Can\'t define a mixin named $(NAME) in a tenant-dependent entity' );
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
csnPath.length -= 2;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function checkExcluding( excludeList ) {
|
|
143
|
+
if (excludeList.includes( fieldName )) {
|
|
144
|
+
error( null, csnPath, { name: fieldName },
|
|
145
|
+
'Can\'t exclude $(NAME) from query source' );
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function handleGroupBy( select ) {
|
|
150
|
+
// TODO: in the future, we allow model-wise keyless views when using
|
|
151
|
+
// aggregation function, and add a GROUP BY for MANDT in this case. Now, also
|
|
152
|
+
// views with agg functions need to have a key element → it very likely
|
|
153
|
+
// already contains a GROUP BY. And anyway: if we miss to add GROUP BY MANDT,
|
|
154
|
+
// the database will complain → no safetly risk.
|
|
155
|
+
if (select.groupBy)
|
|
156
|
+
select.groupBy.unshift( { ref: [ fieldName ] } );
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function handleColumns( columns ) {
|
|
160
|
+
let specifiedKey = false;
|
|
161
|
+
csnPath.push( 'columns', -1 );
|
|
162
|
+
for (const col of columns) {
|
|
163
|
+
++csnPath[csnPath.length - 1];
|
|
164
|
+
if (col.expand || col.inline) {
|
|
165
|
+
error( null, csnPath, {},
|
|
166
|
+
'Can\'t use expand/inline in a tenant-dependent entity' );
|
|
167
|
+
}
|
|
168
|
+
if (col.key != null) // yes, also with key: false
|
|
169
|
+
specifiedKey = true;
|
|
170
|
+
if (col.cast?.on) // REDIRECTED TO with explicit ON - TODO (low prio): less $self
|
|
171
|
+
addToCondition( col.cast, col.as || implicitAs( col.ref ) );
|
|
172
|
+
}
|
|
173
|
+
csnPath.length -= 2;
|
|
174
|
+
columns.unshift( specifiedKey
|
|
175
|
+
? { key: true, ref: [ fieldName ] }
|
|
176
|
+
: { ref: [ fieldName ] } );
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function handleAssociations( elem ) {
|
|
180
|
+
const { elements } = elem;
|
|
181
|
+
if (elements) {
|
|
182
|
+
csnPath.push( 'elements', '' );
|
|
183
|
+
for (const name in elements) {
|
|
184
|
+
csnPath[csnPath.length - 1] = name;
|
|
185
|
+
handleAssociations( elements[name] );
|
|
186
|
+
}
|
|
187
|
+
csnPath.length -= 2;
|
|
188
|
+
}
|
|
189
|
+
else if (elem.target) {
|
|
190
|
+
if (elem.on) {
|
|
191
|
+
addToCondition( elem, csnPath[csnPath.length - 1] );
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
error( null, csnPath, {},
|
|
195
|
+
'Can\'t yet use managed associations in a tenant-dependent entity' );
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (elem.items) {
|
|
199
|
+
csnPath.push( 'items' );
|
|
200
|
+
handleAssociations( elem.items );
|
|
201
|
+
--csnPath.length;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function addToCondition( elem, assoc ) {
|
|
206
|
+
if (!elem.on)
|
|
207
|
+
return;
|
|
208
|
+
const withSelf = csnPath.length > 4;
|
|
209
|
+
elem.on = [
|
|
210
|
+
{ ref: [ assoc, fieldName ] }, // TODO: consider assoc name starting with '$'
|
|
211
|
+
'=',
|
|
212
|
+
{ ref: (withSelf) ? [ '$self', fieldName ] : [ fieldName ] },
|
|
213
|
+
'and',
|
|
214
|
+
// TODO: avoid (...) for standard AND-ed EQ-comparisons ?
|
|
215
|
+
{ xpr: elem.on },
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function implicitAs( ref ) {
|
|
221
|
+
const item = ref[ref.length - 1];
|
|
222
|
+
const id = (typeof item === 'string') ? item : item.id;
|
|
223
|
+
return id.substring( id.lastIndexOf('.') + 1 );
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
addTenantFields,
|
|
228
|
+
};
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
const { setProp } = require('../../base/model');
|
|
16
|
-
const {
|
|
16
|
+
const { isAnnotationExpression } = require('../../compiler/builtins');
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -148,7 +148,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
148
148
|
*/
|
|
149
149
|
function annotation( _parent, _prop, node ) {
|
|
150
150
|
if (options.processAnnotations) {
|
|
151
|
-
if (
|
|
151
|
+
if (isAnnotationExpression(node)) {
|
|
152
152
|
standard(_parent, _prop, node);
|
|
153
153
|
}
|
|
154
154
|
else if (node && typeof node === 'object') {
|
|
@@ -299,47 +299,43 @@ function applyTransformationsOnDictionary( dictionary, customTransformers = {},
|
|
|
299
299
|
/**
|
|
300
300
|
* transformExpression is a lightweight version of applyTransformations
|
|
301
301
|
* used primarily to transform annotation expressions.
|
|
302
|
+
* If propName is undefined, all properties of parent are transformed.
|
|
302
303
|
* @param {object} parent Start node
|
|
303
|
-
* @param {
|
|
304
|
+
* @param {string} propName Start at specific property of parent
|
|
305
|
+
* @param {object} transformers Map of callback functions
|
|
304
306
|
* @param {CSN.Path} path Path to parent
|
|
305
|
-
* @returns {object} transformed
|
|
307
|
+
* @returns {object} transformed node
|
|
306
308
|
*/
|
|
307
|
-
function transformExpression( parent,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
|
|
318
|
-
(typeof _prop === 'string' && _prop.startsWith('@')))
|
|
319
|
-
return;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
csnPath.push(_prop);
|
|
323
|
-
if (Array.isArray(node)) {
|
|
324
|
-
node.forEach( (n, i) => standard( node, i, n ) );
|
|
309
|
+
function transformExpression( parent, propName, transformers, path = [] ) {
|
|
310
|
+
if (propName != null) {
|
|
311
|
+
const child = parent[propName];
|
|
312
|
+
if (!child || typeof child !== 'object' ||
|
|
313
|
+
!{}.propertyIsEnumerable.call( parent, propName ))
|
|
314
|
+
return parent;
|
|
315
|
+
|
|
316
|
+
path = [ ...path, propName ];
|
|
317
|
+
if (Array.isArray(child)) {
|
|
318
|
+
child.forEach( (n, i) => transformExpression( child, i, transformers, path ) );
|
|
325
319
|
}
|
|
326
320
|
else {
|
|
327
|
-
for (const
|
|
328
|
-
const ct =
|
|
321
|
+
for (const cpn of Object.getOwnPropertyNames( child )) {
|
|
322
|
+
const ct = transformers[cpn];
|
|
329
323
|
if (ct) {
|
|
324
|
+
const ppn = propName;
|
|
330
325
|
if (Array.isArray(ct))
|
|
331
|
-
ct.forEach(cti => cti(
|
|
326
|
+
ct.forEach(cti => cti(child, cpn, child[cpn], path, parent, ppn));
|
|
327
|
+
|
|
332
328
|
else
|
|
333
|
-
ct(
|
|
329
|
+
ct(child, cpn, child[cpn], path, parent, ppn);
|
|
334
330
|
}
|
|
335
|
-
|
|
331
|
+
transformExpression(child, cpn, transformers, path);
|
|
336
332
|
}
|
|
337
333
|
}
|
|
338
|
-
csnPath.pop();
|
|
339
334
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
335
|
+
else {
|
|
336
|
+
for (propName of Object.getOwnPropertyNames( parent ))
|
|
337
|
+
transformExpression( parent, propName, transformers, path );
|
|
338
|
+
}
|
|
343
339
|
return parent;
|
|
344
340
|
}
|
|
345
341
|
|
|
@@ -16,12 +16,12 @@ const { pathName } = require('../../compiler/utils');
|
|
|
16
16
|
*
|
|
17
17
|
* @param {CSN.Model} csn Overall CSN model
|
|
18
18
|
* @param {CSN.Options} options Options
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {Function} info Message function for info
|
|
19
|
+
* @param {object} messageFunctions Message functions (error(), info(), …)
|
|
21
20
|
* @returns {Function} forEachDefinition callback
|
|
22
21
|
*/
|
|
23
|
-
function processAssertUnique( csn, options,
|
|
24
|
-
const { resolvePath, flattenPath } = getTransformers(csn, options);
|
|
22
|
+
function processAssertUnique( csn, options, messageFunctions ) {
|
|
23
|
+
const { resolvePath, flattenPath } = getTransformers(csn, options, messageFunctions);
|
|
24
|
+
const { error, info } = messageFunctions;
|
|
25
25
|
|
|
26
26
|
return handleAssertUnique;
|
|
27
27
|
/**
|
|
@@ -106,7 +106,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
|
|
|
106
106
|
const { error } = messageFunctions;
|
|
107
107
|
const {
|
|
108
108
|
recurseElements,
|
|
109
|
-
} = transformUtils.getTransformers(csn, options, '_');
|
|
109
|
+
} = transformUtils.getTransformers(csn, options, messageFunctions, '_');
|
|
110
110
|
|
|
111
111
|
return handleQueryish;
|
|
112
112
|
|
|
@@ -9,8 +9,8 @@ const transformUtils = require('../transformUtils');
|
|
|
9
9
|
const { csnRefs } = require('../../model/csnRefs');
|
|
10
10
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
11
11
|
const { forEach } = require('../../utils/objectUtils');
|
|
12
|
-
const { cardinality2str } = require('../../model/csnUtils');
|
|
13
|
-
|
|
12
|
+
const { cardinality2str, isAnnotationExpression } = require('../../model/csnUtils');
|
|
13
|
+
const { transformExpression } = require('./applyTransformations');
|
|
14
14
|
/**
|
|
15
15
|
* Strip off leading $self from refs where applicable
|
|
16
16
|
*
|
|
@@ -42,11 +42,12 @@ function removeLeadingSelf( csn ) {
|
|
|
42
42
|
*
|
|
43
43
|
* @param {CSN.Model} csn
|
|
44
44
|
* @param {CSN.Options} options
|
|
45
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
45
46
|
* @param {WeakMap} resolved Cache for resolved refs
|
|
46
47
|
* @param {string} pathDelimiter
|
|
47
48
|
* @param {object} iterateOptions
|
|
48
49
|
*/
|
|
49
|
-
function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOptions = {} ) {
|
|
50
|
+
function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
|
|
50
51
|
/**
|
|
51
52
|
* Remove .localized from the element and any sub-elements
|
|
52
53
|
*
|
|
@@ -67,7 +68,7 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
|
|
|
67
68
|
stack.push(...Object.values(current.elements));
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
|
-
const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
71
|
+
const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
71
72
|
const { getServiceName, getFinalTypeInfo } = csnUtils;
|
|
72
73
|
|
|
73
74
|
// We don't want to iterate over actions
|
|
@@ -95,18 +96,17 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
|
|
|
95
96
|
|
|
96
97
|
// structured types might not have the child-types replaced.
|
|
97
98
|
// Drill down to ensure this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
99
|
+
const nextElements = node.elements || node.items?.elements;
|
|
100
|
+
const stack = nextElements ? [ nextElements ] : [];
|
|
101
|
+
while (stack.length > 0) {
|
|
102
|
+
const elements = stack.pop();
|
|
103
|
+
for (const e of Object.values(elements)) {
|
|
104
|
+
toFinalBaseType(e, resolved, true);
|
|
105
|
+
if (!options.toOdata && e.items) // items could have unresolved types
|
|
106
|
+
toFinalBaseType(e.items, resolved, true);
|
|
107
|
+
const next = e.elements || e.items?.elements;
|
|
108
|
+
if (next)
|
|
109
|
+
stack.push(next);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -164,20 +164,21 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
|
|
|
164
164
|
*/
|
|
165
165
|
function isODataItems( typeName ) {
|
|
166
166
|
const typeDef = csn.definitions[typeName];
|
|
167
|
-
return !!(options.toOdata && typeDef && typeDef
|
|
167
|
+
return !!(options.toOdata && typeDef && typeDef?.items);
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
172
|
* @param {CSN.Model} csn
|
|
173
173
|
* @param {CSN.Options} options
|
|
174
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
174
175
|
* @param {WeakMap} resolved Cache for resolved refs
|
|
175
176
|
* @param {string} pathDelimiter
|
|
176
177
|
* @param {object} iterateOptions
|
|
177
178
|
*/
|
|
178
|
-
function flattenAllStructStepsInRefs( csn, options, resolved, pathDelimiter, iterateOptions = {} ) {
|
|
179
|
+
function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
|
|
179
180
|
const { inspectRef, effectiveType } = csnRefs(csn);
|
|
180
|
-
const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
181
|
+
const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
181
182
|
const adaptRefs = [];
|
|
182
183
|
|
|
183
184
|
/**
|
|
@@ -258,12 +259,13 @@ function flattenAllStructStepsInRefs( csn, options, resolved, pathDelimiter, ite
|
|
|
258
259
|
/**
|
|
259
260
|
* @param {CSN.Model} csn
|
|
260
261
|
* @param {CSN.Options} options
|
|
262
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
261
263
|
* @param {string} pathDelimiter
|
|
262
|
-
* @param {Function} error
|
|
263
264
|
* @param {object} iterateOptions
|
|
264
265
|
*/
|
|
265
|
-
function flattenElements( csn, options,
|
|
266
|
-
const {
|
|
266
|
+
function flattenElements( csn, options, messageFunctions, pathDelimiter, iterateOptions = {} ) {
|
|
267
|
+
const { error } = messageFunctions;
|
|
268
|
+
const { flattenStructuredElement, csnUtils } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
267
269
|
const { isAssocOrComposition, effectiveType } = csnUtils;
|
|
268
270
|
const transformers = {
|
|
269
271
|
elements: flatten,
|
|
@@ -309,33 +311,28 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
|
|
|
309
311
|
if (flatElement.notNull !== false && !branch.some(s => !s.notNull))
|
|
310
312
|
flatElement.notNull = true;
|
|
311
313
|
|
|
312
|
-
|
|
313
314
|
if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
|
|
314
315
|
// unmanaged relations can't be primary key
|
|
315
316
|
delete flatElement.key;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (onPart.ref) {
|
|
319
|
-
const firstRef = onPart.ref[0];
|
|
320
|
-
|
|
321
|
-
/*
|
|
322
|
-
when element is defined in the current name resolution scope, like
|
|
323
|
-
entity E {
|
|
324
|
-
key x: Integer;
|
|
325
|
-
s : {
|
|
326
|
-
y : Integer;
|
|
327
|
-
a3 : association to E on a3.x = y;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
|
|
331
|
-
*/
|
|
317
|
+
transformExpression(flatElement, 'on', {
|
|
318
|
+
ref: (_parent, _prop, xpr) => {
|
|
332
319
|
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
|
|
333
|
-
const possibleFlatName = prefix + pathDelimiter +
|
|
334
|
-
|
|
320
|
+
const possibleFlatName = prefix + pathDelimiter + xpr[0];
|
|
321
|
+
/*
|
|
322
|
+
when element is defined in the current name resolution scope, like
|
|
323
|
+
entity E {
|
|
324
|
+
key x: Integer;
|
|
325
|
+
s : {
|
|
326
|
+
y : Integer;
|
|
327
|
+
a3 : association to E on a3.x = y;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
|
|
331
|
+
*/
|
|
335
332
|
if (flatElems[possibleFlatName])
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
}
|
|
333
|
+
xpr[0] = possibleFlatName;
|
|
334
|
+
},
|
|
335
|
+
});
|
|
339
336
|
}
|
|
340
337
|
parent[prop].$orderedElements.push([ flatElemName, flatElement ]);
|
|
341
338
|
// Still add them - otherwise we might not detect collisions between generated elements.
|
|
@@ -434,16 +431,16 @@ function linkForeignKeyAnnotationExtensionsToAssociation( csn, options ) {
|
|
|
434
431
|
/**
|
|
435
432
|
* @param {CSN.Model} csn
|
|
436
433
|
* @param {CSN.Options} options
|
|
437
|
-
* @param {
|
|
438
|
-
* @param {Function} warning
|
|
434
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
439
435
|
* @param {string} pathDelimiter
|
|
440
436
|
* @param {boolean} flattenKeyRefs
|
|
441
437
|
* @param {object} csnUtils
|
|
442
438
|
* @param {object} iterateOptions
|
|
443
439
|
*/
|
|
444
|
-
function handleManagedAssociationsAndCreateForeignKeys( csn, options,
|
|
440
|
+
function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFunctions, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
|
|
441
|
+
const { error, warning } = messageFunctions;
|
|
445
442
|
const { isManagedAssociation, inspectRef, isStructured } = csnUtils;
|
|
446
|
-
const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
443
|
+
const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
447
444
|
if (flattenKeyRefs) {
|
|
448
445
|
applyTransformations(csn, {
|
|
449
446
|
elements: (parent, prop, elements, path) => {
|
|
@@ -814,6 +811,29 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
814
811
|
return [ [ prefix, newFk ] ];
|
|
815
812
|
}
|
|
816
813
|
|
|
814
|
+
fks.forEach((fk) => {
|
|
815
|
+
// prepend current prefix
|
|
816
|
+
fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
|
|
817
|
+
// if this is the entry association, decorate the final foreign keys with the association props
|
|
818
|
+
if (lvl === 0) {
|
|
819
|
+
if (options.transformation !== 'effective')
|
|
820
|
+
fk[1]['@odata.foreignKey4'] = prefix;
|
|
821
|
+
if (options.transformation === 'odata' || options.transformation === 'effective') {
|
|
822
|
+
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !isAnnotationExpression(element[pn]));
|
|
823
|
+
copyAnnotations(element, fk[1], true, {}, validAnnoNames);
|
|
824
|
+
}
|
|
825
|
+
// propagate not null to final foreign key
|
|
826
|
+
for (const prop of [ 'notNull', 'key' ]) {
|
|
827
|
+
if (element[prop] !== undefined)
|
|
828
|
+
fk[1][prop] = element[prop];
|
|
829
|
+
}
|
|
830
|
+
if (element.$location)
|
|
831
|
+
setProp(fk[1], '$location', element.$location);
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
return fks;
|
|
835
|
+
|
|
836
|
+
|
|
817
837
|
/**
|
|
818
838
|
* Get the path to continue resolving references
|
|
819
839
|
*
|
|
@@ -834,27 +854,6 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
834
854
|
return [ path, ...additions ];
|
|
835
855
|
return [ ...path, ...additions ];
|
|
836
856
|
}
|
|
837
|
-
|
|
838
|
-
fks.forEach((fk) => {
|
|
839
|
-
// prepend current prefix
|
|
840
|
-
fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
|
|
841
|
-
// if this is the entry association, decorate the final foreign keys with the association props
|
|
842
|
-
if (lvl === 0) {
|
|
843
|
-
if (options.transformation !== 'effective')
|
|
844
|
-
fk[1]['@odata.foreignKey4'] = prefix;
|
|
845
|
-
if (options.transformation === 'odata' || options.transformation === 'effective')
|
|
846
|
-
copyAnnotations(element, fk[1], true);
|
|
847
|
-
|
|
848
|
-
// propagate not null to final foreign key
|
|
849
|
-
for (const prop of [ 'notNull', 'key' ]) {
|
|
850
|
-
if (element[prop] !== undefined)
|
|
851
|
-
fk[1][prop] = element[prop];
|
|
852
|
-
}
|
|
853
|
-
if (element.$location)
|
|
854
|
-
setProp(fk[1], '$location', element.$location);
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
return fks;
|
|
858
857
|
}
|
|
859
858
|
|
|
860
859
|
module.exports = {
|
|
@@ -171,7 +171,7 @@ function getAnnotationHandler( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
171
171
|
const { error } = messageFunctions;
|
|
172
172
|
const {
|
|
173
173
|
extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments, recurseElements,
|
|
174
|
-
} = getTransformers(csn, options, pathDelimiter);
|
|
174
|
+
} = getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
175
175
|
|
|
176
176
|
return handleTemporalAnnotations;
|
|
177
177
|
/**
|
|
@@ -26,7 +26,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
26
26
|
const {
|
|
27
27
|
createForeignKeyElement, createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
|
|
28
28
|
addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
|
|
29
|
-
} = getTransformers(csn, options, pathDelimiter);
|
|
29
|
+
} = getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
30
30
|
const { getCsnDef, isComposition } = csnUtils;
|
|
31
31
|
const { error, warning } = messageFunctions;
|
|
32
32
|
const generatedArtifacts = Object.create(null);
|
|
@@ -122,26 +122,12 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
122
122
|
|
|
123
123
|
generatedArtifacts[draftsArtifactName] = true;
|
|
124
124
|
|
|
125
|
-
//
|
|
126
|
-
const keys = [];
|
|
125
|
+
// TODO: Do we really need this? Is this possibly done by a validator earlier?
|
|
127
126
|
forEachMemberRecursively(artifact, (elt, name, prop, path) => {
|
|
128
127
|
if (!elt.elements && !elt.type && !elt.virtual) // only check leafs
|
|
129
128
|
error(null, path, 'Expecting element to have a type when used in a draft-enabled artifact');
|
|
130
|
-
if (elt.key && elt.key === true && !elt.virtual)
|
|
131
|
-
keys.push(elt);
|
|
132
129
|
}, [ 'definitions', artifactName ], false, { elementsOnly: true });
|
|
133
130
|
|
|
134
|
-
// In contrast to EDM, the DB entity may have more than one technical keys but should have ideally exactly one key of type cds.UUID
|
|
135
|
-
if (keys.length !== 1) {
|
|
136
|
-
warning(null, [ 'definitions', artifactName ], { count: keys.length },
|
|
137
|
-
'Entity annotated with “@odata.draft.enabled” should have exactly one key element, but found $(COUNT)');
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
const uuidCount = keys.reduce((acc, k) => ((k.type === 'cds.UUID' || k.type === 'cds.String' && k.$renamed === 'cds.UUID' && k.length === 36) ? acc + 1 : acc), 0);
|
|
141
|
-
if (uuidCount === 0)
|
|
142
|
-
warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
131
|
// Ignore boolean return value. We know that we're inside a service or else we wouldn't have reached this code.
|
|
146
132
|
const matchingService = getMatchingService(artifactName) || '';
|
|
147
133
|
// Generate the DraftAdministrativeData projection into the service, unless there is already one
|
|
@@ -27,21 +27,21 @@ const { makeMessageFunction } = require('../../base/messages');
|
|
|
27
27
|
* @todo check if needed at all: Remove '$projection' from paths in the element's ON-condition
|
|
28
28
|
*/
|
|
29
29
|
function generateDrafts( csn, options, services ) {
|
|
30
|
+
const messageFunctions = makeMessageFunction(csn, options, 'for.odata');
|
|
31
|
+
const { error, info } = messageFunctions;
|
|
30
32
|
const {
|
|
31
33
|
createAndAddDraftAdminDataProjection, createScalarElement,
|
|
32
34
|
createAssociationElement, createAssociationPathComparison,
|
|
33
35
|
addElement, createAction, assignAction,
|
|
34
36
|
resetAnnotation,
|
|
35
37
|
csnUtils,
|
|
36
|
-
} = getTransformers(csn, options);
|
|
38
|
+
} = getTransformers(csn, options, messageFunctions);
|
|
37
39
|
const {
|
|
38
40
|
getServiceName,
|
|
39
41
|
hasAnnotationValue,
|
|
40
42
|
getFinalTypeInfo,
|
|
41
43
|
} = csnUtils;
|
|
42
44
|
|
|
43
|
-
const { error, info } = makeMessageFunction(csn, options, 'for.odata');
|
|
44
|
-
|
|
45
45
|
if (!services)
|
|
46
46
|
services = getServiceNames(csn);
|
|
47
47
|
|