@sap/cds-compiler 2.11.2 → 2.13.6
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 +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
package/lib/compiler/utils.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// Please do not add functions “for completeness”, this is not an API file for
|
|
7
7
|
// others but only by the core compiler.
|
|
8
8
|
|
|
9
|
+
// TODO: probably split this file into utils/….js
|
|
10
|
+
|
|
9
11
|
'use strict';
|
|
10
12
|
|
|
11
13
|
const { dictAdd, pushToDict } = require('../base/dictionaries');
|
|
@@ -30,6 +32,15 @@ function annotationIsFalse( anno ) { // falsy, but not null (u
|
|
|
30
32
|
return anno && (anno.val === false || anno.val === 0 || anno.val === '');
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Set compiler-calculated annotation value.
|
|
37
|
+
*
|
|
38
|
+
* @param {XSN.Artifact} art
|
|
39
|
+
* @param {string} anno
|
|
40
|
+
* @param {XSN.Location} [location]
|
|
41
|
+
* @param {*} [val]
|
|
42
|
+
* @param {string} [literal]
|
|
43
|
+
*/
|
|
33
44
|
function annotateWith( art, anno, location = art.location, val = true, literal = 'boolean' ) {
|
|
34
45
|
if (art[anno]) // do not overwrite user-defined including null
|
|
35
46
|
return;
|
|
@@ -37,11 +48,11 @@ function annotateWith( art, anno, location = art.location, val = true, literal =
|
|
|
37
48
|
name: { path: [ { id: anno.slice(1), location } ], location },
|
|
38
49
|
val,
|
|
39
50
|
literal,
|
|
51
|
+
$inferred: '$generated',
|
|
40
52
|
location,
|
|
41
53
|
};
|
|
42
54
|
}
|
|
43
55
|
|
|
44
|
-
// TODO: define setLink() like the current setProp(), we might have setArtifactLink()
|
|
45
56
|
// Do not share this function with CSN processors!
|
|
46
57
|
|
|
47
58
|
// The link (_artifact,_effectiveType,...) usually has the artifact as value.
|
|
@@ -50,28 +61,13 @@ function annotateWith( art, anno, location = art.location, val = true, literal =
|
|
|
50
61
|
// - null: no valid reference, param:true if that is not allowed
|
|
51
62
|
// - false (only complete ref): multiple definitions, rejected
|
|
52
63
|
// - 0 (for _effectiveType only): circular reference
|
|
53
|
-
function setLink( obj,
|
|
64
|
+
function setLink( obj, prop, value ) {
|
|
54
65
|
Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
|
|
55
66
|
return value;
|
|
56
67
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* It's important to set enumerable explicitly to false (although 'false' is the default),
|
|
61
|
-
* as else, if the property already exists, it keeps the old setting for enumerable.
|
|
62
|
-
*
|
|
63
|
-
* @param {object} obj
|
|
64
|
-
* @param {string} prop
|
|
65
|
-
* @param {any} value
|
|
66
|
-
*/
|
|
67
|
-
function setProp(obj, prop, value) {
|
|
68
|
-
const descriptor = {
|
|
69
|
-
value,
|
|
70
|
-
configurable: true,
|
|
71
|
-
writable: true,
|
|
72
|
-
enumerable: false,
|
|
73
|
-
};
|
|
74
|
-
Object.defineProperty( obj, prop, descriptor );
|
|
68
|
+
// And a variant with the most common `prop`:
|
|
69
|
+
function setArtifactLink( obj, value ) {
|
|
70
|
+
Object.defineProperty( obj, '_artifact', { value, configurable: true, writable: true } );
|
|
75
71
|
return value;
|
|
76
72
|
}
|
|
77
73
|
|
|
@@ -85,7 +81,7 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
|
|
|
85
81
|
elem.name.$inferred = origin.name.$inferred;
|
|
86
82
|
if (parent)
|
|
87
83
|
setMemberParent( elem, name, parent, prop ); // TODO: redef in template
|
|
88
|
-
|
|
84
|
+
setLink( elem, '_origin', origin );
|
|
89
85
|
// TODO: should we use silent dependencies also for other things, like
|
|
90
86
|
// included elements? (Currently for $inferred: 'expand-element' only)
|
|
91
87
|
if (silentDep)
|
|
@@ -103,20 +99,21 @@ function setMemberParent( elem, name, parent, prop ) {
|
|
|
103
99
|
p[prop] = Object.create(null);
|
|
104
100
|
dictAdd( p[prop], name, elem );
|
|
105
101
|
}
|
|
106
|
-
if (parent._outer)
|
|
102
|
+
if (parent._outer && parent._outer.items) // TODO: remove for items, too
|
|
107
103
|
parent = parent._outer;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
setLink( elem, '_parent', parent );
|
|
105
|
+
setLink( elem, '_main', parent._main || parent );
|
|
106
|
+
const parentName = parent.name || parent._outer.name;
|
|
107
|
+
elem.name.absolute = parentName.absolute;
|
|
111
108
|
if (name == null)
|
|
112
109
|
return;
|
|
113
110
|
const normalized = kindProperties[elem.kind].normalized || elem.kind;
|
|
114
111
|
[ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
|
|
115
112
|
if (normalized === kind)
|
|
116
|
-
elem.name[kind] = (
|
|
113
|
+
elem.name[kind] = (parentName[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parentName[kind] }.${ name }` : name;
|
|
117
114
|
|
|
118
|
-
else if (
|
|
119
|
-
elem.name[kind] =
|
|
115
|
+
else if (parentName[kind] != null)
|
|
116
|
+
elem.name[kind] = parentName[kind];
|
|
120
117
|
|
|
121
118
|
else
|
|
122
119
|
delete elem.name[kind];
|
|
@@ -133,7 +130,7 @@ function setMemberParent( elem, name, parent, prop ) {
|
|
|
133
130
|
*/
|
|
134
131
|
function dependsOn( user, art, location ) {
|
|
135
132
|
if (!user._deps)
|
|
136
|
-
|
|
133
|
+
setLink( user, '_deps', [] );
|
|
137
134
|
user._deps.push( { art, location } );
|
|
138
135
|
}
|
|
139
136
|
|
|
@@ -146,17 +143,17 @@ function dependsOn( user, art, location ) {
|
|
|
146
143
|
*/
|
|
147
144
|
function dependsOnSilent( user, art ) {
|
|
148
145
|
if (!user._deps)
|
|
149
|
-
|
|
146
|
+
setLink( user, '_deps', [] );
|
|
150
147
|
user._deps.push( { art } );
|
|
151
148
|
}
|
|
152
149
|
|
|
153
150
|
function storeExtension( elem, name, prop, parent, block ) {
|
|
154
151
|
if (prop === 'enum')
|
|
155
152
|
prop = 'elements';
|
|
156
|
-
|
|
153
|
+
setLink( elem, '_block', block );
|
|
157
154
|
const kind = `_${ elem.kind }`; // _extend or _annotate
|
|
158
155
|
if (!parent[kind])
|
|
159
|
-
|
|
156
|
+
setLink( parent, kind, {} );
|
|
160
157
|
// if (name === '' && prop === 'params') {
|
|
161
158
|
// pushToDict( parent[kind], 'returns', elem ); // not really a dict
|
|
162
159
|
// return;
|
|
@@ -181,6 +178,16 @@ function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = fa
|
|
|
181
178
|
return false;
|
|
182
179
|
}
|
|
183
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Return string 'A.B.C' for parsed source `A.B.C` (is vector of ids with
|
|
183
|
+
* locations).
|
|
184
|
+
*
|
|
185
|
+
* @param {XSN.Path} path
|
|
186
|
+
*/
|
|
187
|
+
function pathName(path) {
|
|
188
|
+
return (path.broken) ? '' : path.map( id => id.id ).join('.');
|
|
189
|
+
}
|
|
190
|
+
|
|
184
191
|
/**
|
|
185
192
|
* Generates an XSN path out of the given name. Path segments are delimited by a dot.
|
|
186
193
|
* Each segment will have the given location assigned.
|
|
@@ -201,6 +208,174 @@ function augmentPath( location, ...args ) {
|
|
|
201
208
|
return { path: args.map( id => ({ id, location }) ), location };
|
|
202
209
|
}
|
|
203
210
|
|
|
211
|
+
function copyExpr( expr, location, skipUnderscored, rewritePath ) {
|
|
212
|
+
if (!expr || typeof expr !== 'object')
|
|
213
|
+
return expr;
|
|
214
|
+
else if (Array.isArray(expr))
|
|
215
|
+
return expr.map( e => copyExpr( e, location, skipUnderscored, rewritePath ) );
|
|
216
|
+
|
|
217
|
+
const proto = Object.getPrototypeOf( expr );
|
|
218
|
+
if (proto && proto !== Object.prototype) // do not copy object from special classes
|
|
219
|
+
return expr;
|
|
220
|
+
const r = Object.create( proto );
|
|
221
|
+
for (const prop of Object.getOwnPropertyNames( expr )) {
|
|
222
|
+
const pd = Object.getOwnPropertyDescriptor( expr, prop );
|
|
223
|
+
if (!pd.enumerable) { // should include all properties starting with _
|
|
224
|
+
if (!skipUnderscored ||
|
|
225
|
+
prop === '_artifact' || prop === '_navigation' || prop === '_effectiveType')
|
|
226
|
+
Object.defineProperty( r, prop, pd );
|
|
227
|
+
}
|
|
228
|
+
else if (!proto) {
|
|
229
|
+
r[prop] = copyExpr( pd.value, location, skipUnderscored, rewritePath );
|
|
230
|
+
}
|
|
231
|
+
else if (prop === 'location') {
|
|
232
|
+
r[prop] = location || pd.value;
|
|
233
|
+
}
|
|
234
|
+
else if (prop.charAt(0) !== '$' || prop === '$inferred') {
|
|
235
|
+
r[prop] = copyExpr( pd.value, location, skipUnderscored, rewritePath );
|
|
236
|
+
}
|
|
237
|
+
else if (!skipUnderscored) { // skip $ properties
|
|
238
|
+
Object.defineProperty( r, prop, pd );
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return r;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function testExpr( expr, pathTest, queryTest ) {
|
|
245
|
+
// TODO: also check path arguments/filters
|
|
246
|
+
if (!expr || typeof expr === 'string') { // parse error or keywords in {xpr:...}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
else if (Array.isArray(expr)) {
|
|
250
|
+
return expr.some( e => testExpr( e, pathTest, queryTest ) );
|
|
251
|
+
}
|
|
252
|
+
else if (expr.path) {
|
|
253
|
+
return pathTest( expr );
|
|
254
|
+
}
|
|
255
|
+
else if (expr.query) {
|
|
256
|
+
return queryTest( expr.query );
|
|
257
|
+
}
|
|
258
|
+
else if (expr.op && expr.args) {
|
|
259
|
+
// unnamed args => array
|
|
260
|
+
if (Array.isArray(expr.args))
|
|
261
|
+
return expr.args.some( e => testExpr( e, pathTest, queryTest ) );
|
|
262
|
+
// named args => dictionary
|
|
263
|
+
for (const namedArg of Object.keys(expr.args)) {
|
|
264
|
+
if (testExpr(expr.args[namedArg], pathTest, queryTest))
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Return true if the path `item` with a final type `assoc` has a max target
|
|
272
|
+
// cardinality greater than one - either specified on the path item or assoc type.
|
|
273
|
+
function targetMaxNotOne( assoc, item ) {
|
|
274
|
+
// Semantics of associations without provided cardinality: [*,0..1]
|
|
275
|
+
const cardinality = item.cardinality || assoc.cardinality;
|
|
276
|
+
return cardinality && cardinality.targetMax && cardinality.targetMax.val !== 1;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Query tree post-order traversal - called for everything which contributes to the query
|
|
280
|
+
// i.e. is necessary to calculate the elements of the query
|
|
281
|
+
// except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
|
|
282
|
+
// NOTE: does not run on non-referred sub queries! Consider using ‹main›.$queries instead!
|
|
283
|
+
function traverseQueryPost( query, simpleOnly, callback ) {
|
|
284
|
+
if (!query) // parser error
|
|
285
|
+
return;
|
|
286
|
+
if (!query.op) { // in FROM (not JOIN)
|
|
287
|
+
if (query.query) // subquery
|
|
288
|
+
traverseQueryPost( query.query, simpleOnly, callback );
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (simpleOnly) {
|
|
292
|
+
const { from } = query;
|
|
293
|
+
if (!from || from.join) // parse error or join
|
|
294
|
+
return; // ok are: path or simple sub query (!)
|
|
295
|
+
}
|
|
296
|
+
if (query.from) { // SELECT
|
|
297
|
+
traverseQueryPost( query.from, simpleOnly, callback );
|
|
298
|
+
// console.log('FC:')
|
|
299
|
+
callback( query );
|
|
300
|
+
// console.log('FE:')
|
|
301
|
+
}
|
|
302
|
+
else if (query.args) { // JOIN, UNION, INTERSECT
|
|
303
|
+
if (!query.join && simpleOnly == null) {
|
|
304
|
+
// enough for elements: traverse only first args for UNION/INTERSECT
|
|
305
|
+
// TODO: we might use this also when we do not rewrite associations
|
|
306
|
+
// in non-referred sub queries
|
|
307
|
+
traverseQueryPost( query.args[0], simpleOnly, callback );
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
for (const q of query.args)
|
|
311
|
+
traverseQueryPost( q, simpleOnly, callback );
|
|
312
|
+
// The ON condition has to be traversed extra, because it must be evaluated
|
|
313
|
+
// after the complete FROM has been traversed. It is also not necessary to
|
|
314
|
+
// evaluate it in populateQuery().
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// else: with parse error (`select from <EOF>`, `select distinct from;`)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Call callback on all queries in dependency order, i.e. starting with query Q
|
|
321
|
+
// 1. sub queries in FROM sources of Q
|
|
322
|
+
// 2. Q itself, except if non-referred query, but with right UNION parts
|
|
323
|
+
// 3. sub queries in ON in FROM of Q
|
|
324
|
+
// 4. sub queries in columns, WHERE, HAVING
|
|
325
|
+
function traverseQueryExtra( main, callback ) {
|
|
326
|
+
if (!main.$queries)
|
|
327
|
+
return;
|
|
328
|
+
// with a top-level UNION, $queries[0] is just the left
|
|
329
|
+
traverseQueryPost( main.query, false, (q) => { // also with right of UNION (to be compatible)
|
|
330
|
+
setLink( q, '_status', 'extra' );
|
|
331
|
+
callback( q );
|
|
332
|
+
} );
|
|
333
|
+
for (const query of main.$queries.slice(1)) {
|
|
334
|
+
if (query._status === 'extra' || query._parent.kind === '$tableAlias')
|
|
335
|
+
continue; // if parent is alias, query is FROM source -> run by traverseQueryPost
|
|
336
|
+
// we are now in the top-level (parent is entity) or a non-referred query (parent is query)
|
|
337
|
+
setLink( query, '_status', 'extra' ); // do not call callback() in non-referred query
|
|
338
|
+
// console.log( 'A:', query.name,query._status)
|
|
339
|
+
traverseQueryPost( query, null, (q) => {
|
|
340
|
+
if (q._status !== 'extra') {
|
|
341
|
+
// console.log( 'T:', q.name)
|
|
342
|
+
setLink( q, '_status', 'extra' );
|
|
343
|
+
callback( q );
|
|
344
|
+
}
|
|
345
|
+
// else console.log( 'E:', q.name)
|
|
346
|
+
} );
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// About Helper property $expand for faster the XSN-to-CSN transformation
|
|
351
|
+
// - null/undefined: artifact, member, items does not contain expanded members
|
|
352
|
+
// - 'origin': all expanded (sub) elements have no new target/on and no new annotations
|
|
353
|
+
// that value is only on elements, types, and params -> no other members
|
|
354
|
+
// when set, only on elem/art with expanded elements
|
|
355
|
+
// - 'target': all expanded (sub) elements might only have new target/on, but
|
|
356
|
+
// no indivual annotations on any (sub) member
|
|
357
|
+
// when set, traverse all parents where the value has been 'origin' before
|
|
358
|
+
// - 'annotate': at least one inferred (sub) member has an individual annotation,
|
|
359
|
+
// not counting propagated ones; set up to the definition (main artifact)
|
|
360
|
+
// (only set with anno on $inferred elem), annotate “beats” target
|
|
361
|
+
// Usage according to CSN flavor:
|
|
362
|
+
// - gensrc: do not render inferred elements (including expanded elements),
|
|
363
|
+
// collect annotate statements with value 'annotate'
|
|
364
|
+
// - client: do not render expanded sub elements if artifact/member is no type, has a type,
|
|
365
|
+
// has $expand = 'origin', and all its _origin also have $expand = 'origin'
|
|
366
|
+
// (might sometimes render the elements unnecessarily, which is not wrong)
|
|
367
|
+
// - universal: do not render expanded sub elements if $expand = 'origin'
|
|
368
|
+
function setExpandStatus( elem, status ) {
|
|
369
|
+
// set on element
|
|
370
|
+
while (elem._main) {
|
|
371
|
+
elem = elem._parent;
|
|
372
|
+
if (status === 'annotate' ? elem.$expand === 'annotate' : elem.$expand !== 'origin')
|
|
373
|
+
return;
|
|
374
|
+
elem.$expand = status; // meaning: expanded, containing assocs
|
|
375
|
+
for (let line = elem.items; line; line = line.items)
|
|
376
|
+
line.$expand = status; // to-csn just uses the innermost $expand
|
|
377
|
+
}
|
|
378
|
+
}
|
|
204
379
|
|
|
205
380
|
module.exports = {
|
|
206
381
|
pushLink,
|
|
@@ -208,13 +383,20 @@ module.exports = {
|
|
|
208
383
|
annotationIsFalse,
|
|
209
384
|
annotateWith,
|
|
210
385
|
setLink,
|
|
211
|
-
|
|
386
|
+
setArtifactLink,
|
|
212
387
|
linkToOrigin,
|
|
213
388
|
dependsOn,
|
|
214
389
|
dependsOnSilent,
|
|
215
390
|
setMemberParent,
|
|
216
391
|
storeExtension,
|
|
217
392
|
withAssociation,
|
|
393
|
+
pathName,
|
|
218
394
|
augmentPath,
|
|
219
395
|
splitIntoPath,
|
|
396
|
+
copyExpr,
|
|
397
|
+
testExpr,
|
|
398
|
+
targetMaxNotOne,
|
|
399
|
+
traverseQueryPost,
|
|
400
|
+
traverseQueryExtra,
|
|
401
|
+
setExpandStatus,
|
|
220
402
|
};
|
|
@@ -207,7 +207,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
207
207
|
// Note: we assume that all objects ly flat in the service, i.e. objName always
|
|
208
208
|
// looks like <service name, can contain dots>.<id>
|
|
209
209
|
forEachDefinition(csn, (object, objName) => {
|
|
210
|
-
if(objName
|
|
210
|
+
if (objName === serviceName || objName.startsWith(serviceName + '.')) {
|
|
211
211
|
if (object.kind === 'action' || object.kind === 'function') {
|
|
212
212
|
handleAction(objName, object, null);
|
|
213
213
|
}
|
|
@@ -504,9 +504,18 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
504
504
|
let hasAlternativeCarrier = false; // is the alternative annotation target available in the EDM?
|
|
505
505
|
let testToAlternativeEdmTarget = null; // if true, assign to alternative Edm Target
|
|
506
506
|
|
|
507
|
-
if (carrier.kind === 'entity'
|
|
508
|
-
// If AppliesTo=[EntitySet/Singleton, EntityType], EntitySet/Singleton has precedence
|
|
509
|
-
testToAlternativeEdmTarget = (x =>
|
|
507
|
+
if (carrier.kind === 'entity') {
|
|
508
|
+
// If AppliesTo=[EntitySet/Singleton/Collection, EntityType], EntitySet/Singleton/Collection has precedence
|
|
509
|
+
testToAlternativeEdmTarget = (x => {
|
|
510
|
+
if(options.isV2()) {
|
|
511
|
+
return ['Singleton', 'EntitySet', 'Collection'].some(y => x.includes(y));
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
return edmUtils.isSingleton(carrier)
|
|
515
|
+
? x.includes('Singleton')
|
|
516
|
+
: ['EntitySet', 'Collection'].some(y => x.includes(y));
|
|
517
|
+
}
|
|
518
|
+
});
|
|
510
519
|
testToStandardEdmTarget = (x => x ? x.includes('EntityType') : true);
|
|
511
520
|
// if carrier has an alternate 'entitySetName' use this instead of EdmTargetName
|
|
512
521
|
// (see edmPreprocessor.initializeParameterizedEntityOrView(), where parameterized artifacts
|
|
@@ -534,15 +543,21 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
534
543
|
alternativeEdmTargetName = edmTargetName;
|
|
535
544
|
hasAlternativeCarrier = true; // EntityContainer is always available
|
|
536
545
|
}
|
|
537
|
-
|
|
546
|
+
//element => decide if navprop or normal property
|
|
538
547
|
else if(!carrier.kind) {
|
|
539
|
-
|
|
548
|
+
// if appliesTo is undefined, return true
|
|
540
549
|
if(carrier.target) {
|
|
541
|
-
testToStandardEdmTarget = (x=> x
|
|
550
|
+
testToStandardEdmTarget = (x => x
|
|
551
|
+
? x.includes('NavigationProperty') ||
|
|
552
|
+
carrier.cardinality && carrier.cardinality.max === '*' && x.includes('Collection')
|
|
553
|
+
: true);
|
|
542
554
|
}
|
|
543
555
|
else {
|
|
544
556
|
// this might be more precise if handleAnnotation would know more about the carrier
|
|
545
|
-
testToStandardEdmTarget = (x => x
|
|
557
|
+
testToStandardEdmTarget = (x => x
|
|
558
|
+
? ['Parameter', 'Property'].some(y => x.includes(y) ||
|
|
559
|
+
carrier._isCollection && x.includes('Collection'))
|
|
560
|
+
: true);
|
|
546
561
|
}
|
|
547
562
|
}
|
|
548
563
|
return [
|
|
@@ -851,7 +866,6 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
851
866
|
else if (!expectedType['Members'].includes(enumValue)) {
|
|
852
867
|
message(warning, context, `enumeration type ${ dTypeName } has no value ${ enumValue }`);
|
|
853
868
|
}
|
|
854
|
-
return;
|
|
855
869
|
}
|
|
856
870
|
|
|
857
871
|
// cAnnoValue: array
|
|
@@ -865,8 +879,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
865
879
|
}
|
|
866
880
|
|
|
867
881
|
let index = 0;
|
|
868
|
-
for (
|
|
869
|
-
context.stack.push('[' + index
|
|
882
|
+
for (const e of cAnnoValue) {
|
|
883
|
+
context.stack.push('[' + index + ']');
|
|
884
|
+
index++;
|
|
870
885
|
if (e['#']) {
|
|
871
886
|
checkEnumValue(e['#'], dTypeName, context);
|
|
872
887
|
}
|
|
@@ -901,7 +916,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
901
916
|
}
|
|
902
917
|
else {
|
|
903
918
|
// replace all occurrences of '.' by '/' up to first '@'
|
|
904
|
-
val = expr.split('@').map((o,i) => (i
|
|
919
|
+
val = expr.split('@').map((o,i) => (i === 0 ? o.replace(/\./g, '/') : o)).join('@');
|
|
905
920
|
}
|
|
906
921
|
|
|
907
922
|
return {
|
|
@@ -922,9 +937,10 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
922
937
|
// caller already made sure that val is neither object nor array
|
|
923
938
|
dTypeName = resolveType(dTypeName);
|
|
924
939
|
|
|
925
|
-
if(isEnumType(dTypeName)) {
|
|
940
|
+
if (isEnumType(dTypeName)) {
|
|
926
941
|
const type = getDictType(dTypeName);
|
|
927
|
-
|
|
942
|
+
const expected = type.Members.map(m => `"#${m}"`).join(', ');
|
|
943
|
+
message(warning, context, `found non-enum value "${val}", expected ${expected} for ${dTypeName}`);
|
|
928
944
|
}
|
|
929
945
|
|
|
930
946
|
let typeName = 'String';
|
|
@@ -932,7 +948,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
932
948
|
if (typeof val === 'string') {
|
|
933
949
|
if (dTypeName === 'Edm.Boolean') {
|
|
934
950
|
typeName = 'Bool';
|
|
935
|
-
if (
|
|
951
|
+
if (val !== 'true' && val !== 'false') {
|
|
936
952
|
message(warning, context, `found String, but expected type ${ dTypeName }`);
|
|
937
953
|
}
|
|
938
954
|
}
|
|
@@ -1154,8 +1170,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1154
1170
|
}
|
|
1155
1171
|
|
|
1156
1172
|
let index = 0;
|
|
1157
|
-
for (
|
|
1158
|
-
context.stack.push('[' + index
|
|
1173
|
+
for (const value of annoValue) {
|
|
1174
|
+
context.stack.push('[' + index + ']');
|
|
1175
|
+
index++
|
|
1159
1176
|
|
|
1160
1177
|
// for dealing with the single array entries we unfortunately cannot call handleValue(),
|
|
1161
1178
|
// as the values inside an array are represented differently from the values
|
|
@@ -1214,8 +1231,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1214
1231
|
return edmNode;
|
|
1215
1232
|
}
|
|
1216
1233
|
|
|
1217
|
-
if(dynExprs.length === 0) {
|
|
1218
|
-
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && Object.keys(obj).length
|
|
1234
|
+
if (dynExprs.length === 0) {
|
|
1235
|
+
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && Object.keys(obj).length === 1) {
|
|
1219
1236
|
const k = Object.keys(obj)[0];
|
|
1220
1237
|
const val = obj[k];
|
|
1221
1238
|
edmNode = new Edm.ValueThing(v, k[0] === '$' ? k.slice(1) : k, val );
|
|
@@ -1267,14 +1284,10 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1267
1284
|
});
|
|
1268
1285
|
}
|
|
1269
1286
|
else { // literal
|
|
1270
|
-
let escaped = obj;
|
|
1271
|
-
if (typeof escaped === 'string') {
|
|
1272
|
-
escaped = escaped.replace(/&/g, '&')
|
|
1273
|
-
}
|
|
1274
1287
|
edmNode = new Edm.ValueThing(v,
|
|
1275
|
-
exprDef && exprDef.valueThingName || getXmlTypeName(
|
|
1288
|
+
exprDef && exprDef.valueThingName || getXmlTypeName(obj), obj);
|
|
1276
1289
|
// typename for static expression rendering
|
|
1277
|
-
edmNode.setJSON( { [getJsonTypeName(
|
|
1290
|
+
edmNode.setJSON( { [getJsonTypeName(obj)]: obj } );
|
|
1278
1291
|
}
|
|
1279
1292
|
}
|
|
1280
1293
|
}
|
|
@@ -101,7 +101,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
101
101
|
|
|
102
102
|
// inner functions
|
|
103
103
|
function draftAnnotations(carrier, aName, aNameWithoutQualifier) {
|
|
104
|
-
if ((carrier.kind === 'entity'
|
|
104
|
+
if ((carrier.kind === 'entity') &&
|
|
105
105
|
(aNameWithoutQualifier === '@Common.DraftRoot.PreparationAction' ||
|
|
106
106
|
aNameWithoutQualifier === '@Common.DraftRoot.ActivationAction' ||
|
|
107
107
|
aNameWithoutQualifier === '@Common.DraftRoot.EditAction' ||
|
|
@@ -136,7 +136,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
136
136
|
return false;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
if (carrier.kind === 'entity'
|
|
139
|
+
if (carrier.kind === 'entity') {
|
|
140
140
|
warning(null, null, `annotation preprocessing/${aNameWithoutQualifier}: annotation must not be used for an entity, ${ctx}`);
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
@@ -236,7 +236,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
236
236
|
} else {
|
|
237
237
|
let stringFields = Object.keys(vlEntity.elements).filter(
|
|
238
238
|
x => !vlEntity.elements[x].key && vlEntity.elements[x].type === 'cds.String')
|
|
239
|
-
if (stringFields.length
|
|
239
|
+
if (stringFields.length === 1)
|
|
240
240
|
textField = stringFields[0];
|
|
241
241
|
}
|
|
242
242
|
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -45,6 +45,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
45
45
|
let [ allServices,
|
|
46
46
|
allSchemas,
|
|
47
47
|
whatsMyServiceRootName,
|
|
48
|
+
autoexposeSchemaName,
|
|
48
49
|
options ] = initializeModel(csn, _options, messageFunctions);
|
|
49
50
|
|
|
50
51
|
const Edm = require('./edm.js')(options, error);
|
|
@@ -188,9 +189,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
188
189
|
fqSchemaXRef.sort((a,b) => b.length-a.length);
|
|
189
190
|
|
|
190
191
|
// Bring the schemas in alphabetical order, service first, root last
|
|
191
|
-
const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !==
|
|
192
|
-
if(subSchemaDictionary
|
|
193
|
-
sortedSchemaNames.push(
|
|
192
|
+
const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== autoexposeSchemaName && n !== serviceCsn.name).sort();
|
|
193
|
+
if(subSchemaDictionary[autoexposeSchemaName])
|
|
194
|
+
sortedSchemaNames.push(autoexposeSchemaName);
|
|
194
195
|
|
|
195
196
|
// Finally create the schemas and register them in the service.
|
|
196
197
|
LeadSchema = createSchema(subSchemaDictionary[serviceCsn.name]);
|
|
@@ -234,7 +235,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
234
235
|
Object.entries(csn.definitions).forEach(([fqName, art]) => {
|
|
235
236
|
// Identify service members by their definition name only, this allows
|
|
236
237
|
// to let the internal object.name have the sub-schema name.
|
|
237
|
-
// With nested services we must do a longest path match and check
|
|
238
|
+
// With nested services we must do a longest path match and check whether
|
|
238
239
|
// the current definition belongs to the current toplevel service definition.
|
|
239
240
|
|
|
240
241
|
// Definition is to be considered if
|
|
@@ -246,7 +247,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
246
247
|
// not marked to be ignored as schema member
|
|
247
248
|
if(mySchemaName &&
|
|
248
249
|
serviceCsn.name === whatsMyServiceRootName(fqName, false) &&
|
|
249
|
-
|
|
250
|
+
art.kind !== 'context' && art.kind !== 'service') {
|
|
250
251
|
|
|
251
252
|
// Strip the toplevel serviceName from object.name
|
|
252
253
|
// except if the schema name is the service name itself.
|
|
@@ -420,7 +421,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
420
421
|
/** @type {object} */
|
|
421
422
|
let containerEntry;
|
|
422
423
|
|
|
423
|
-
if(edmUtils.isSingleton(entityCsn
|
|
424
|
+
if(edmUtils.isSingleton(entityCsn) && options.isV4()) {
|
|
424
425
|
containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
|
|
425
426
|
if(entityCsn['@odata.singleton.nullable'])
|
|
426
427
|
containerEntry.Nullable= true;
|
|
@@ -574,7 +575,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
574
575
|
edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
|
|
575
576
|
const paramLoc = [...actLoc, 'params', parameterName];
|
|
576
577
|
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
|
|
577
|
-
if(!param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
|
|
578
|
+
if(param._type && !param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
|
|
578
579
|
warning('odata-spec-violation-param', paramLoc, { api: 'OData V2' });
|
|
579
580
|
}
|
|
580
581
|
if(param._isCollection) {
|
|
@@ -591,8 +592,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
591
592
|
// it is safe to assume that either type or items.type are set
|
|
592
593
|
const returns = action.returns.items || action.returns;
|
|
593
594
|
let type = returns.type;
|
|
594
|
-
if(type){
|
|
595
|
-
if(!isBuiltinType(type) &&
|
|
595
|
+
if (type){
|
|
596
|
+
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
|
|
596
597
|
const returnsLoc = [ ...actLoc, 'returns'];
|
|
597
598
|
warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, api: 'OData V2' });
|
|
598
599
|
}
|