@sap/cds-compiler 6.2.2 → 6.3.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 +49 -0
- package/bin/cdsc.js +11 -4
- package/lib/api/options.js +1 -1
- package/lib/base/message-registry.js +36 -7
- package/lib/base/messages.js +11 -4
- package/lib/base/model.js +0 -1
- package/lib/checks/assocOutsideService.js +17 -30
- package/lib/checks/checkForTypes.js +0 -18
- package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
- package/lib/checks/enricher.js +15 -3
- package/lib/checks/onConditions.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +16 -15
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +30 -6
- package/lib/checks/validator.js +36 -37
- package/lib/compiler/assert-consistency.js +1 -1
- package/lib/compiler/checks.js +47 -18
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/index.js +88 -6
- package/lib/compiler/populate.js +1 -1
- package/lib/compiler/resolve.js +7 -7
- package/lib/compiler/tweak-assocs.js +48 -25
- package/lib/edm/annotations/edmJson.js +19 -19
- package/lib/gen/BaseParser.js +1 -1
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +384 -383
- package/lib/gen/Dictionary.json +0 -2
- package/lib/json/to-csn.js +3 -2
- package/lib/model/csnRefs.js +9 -4
- package/lib/model/csnUtils.js +67 -2
- package/lib/optionProcessor.js +2 -3
- package/lib/parsers/AstBuildingParser.js +12 -11
- package/lib/render/toCdl.js +10 -4
- package/lib/render/utils/common.js +4 -2
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/associations.js +37 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +37 -36
- package/lib/transform/draft/db.js +20 -20
- package/lib/transform/draft/odata.js +38 -40
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/flattening.js +40 -47
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/forOdata.js +135 -115
- package/lib/transform/forRelationalDB.js +151 -142
- package/lib/transform/localized.js +116 -109
- package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
- package/lib/transform/odata/createForeignKeys.js +73 -70
- package/lib/transform/odata/flattening.js +216 -200
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
- package/lib/transform/odata/toFinalBaseType.js +40 -39
- package/lib/transform/odata/typesExposure.js +151 -133
- package/lib/transform/odata/utils.js +7 -6
- package/lib/transform/parseExpr.js +165 -162
- package/lib/transform/transformUtils.js +184 -551
- package/lib/transform/translateAssocsToJoins.js +510 -571
- package/lib/transform/tupleExpansion.js +495 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/package.json +1 -1
- package/lib/base/cleanSymbols.js +0 -17
- package/lib/checks/nonexpandableStructured.js +0 -39
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
const { isBetaEnabled } = require('../base/model');
|
|
4
4
|
const transformUtils = require('./transformUtils');
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
const {
|
|
6
|
+
forEachDefinition,
|
|
7
|
+
forEachMemberRecursively,
|
|
8
|
+
applyTransformationsOnNonDictionary,
|
|
9
|
+
getArtifactDatabaseNameOf,
|
|
10
|
+
getElementDatabaseNameOf,
|
|
11
|
+
getServiceNames,
|
|
12
|
+
forEachGeneric,
|
|
13
|
+
cardinality2str,
|
|
14
|
+
getUtils,
|
|
15
|
+
} = require('../model/csnUtils');
|
|
15
16
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
16
17
|
const validate = require('../checks/validator');
|
|
17
18
|
const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./odata/utils');
|
|
@@ -20,7 +21,7 @@ const { timetrace } = require('../utils/timetrace');
|
|
|
20
21
|
const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
|
|
21
22
|
const flattening = require('./odata/flattening');
|
|
22
23
|
const createForeignKeyElements = require('./odata/createForeignKeys');
|
|
23
|
-
const associations = require('./db/associations')
|
|
24
|
+
const associations = require('./db/associations');
|
|
24
25
|
const expansion = require('./db/expansion');
|
|
25
26
|
const generateDrafts = require('./draft/odata');
|
|
26
27
|
|
|
@@ -80,7 +81,9 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
80
81
|
const csn = cloneFullCsn(inputModel, options);
|
|
81
82
|
messageFunctions.setModel(csn);
|
|
82
83
|
|
|
83
|
-
const {
|
|
84
|
+
const {
|
|
85
|
+
message, error, warning, info, throwWithAnyError,
|
|
86
|
+
} = messageFunctions;
|
|
84
87
|
throwWithAnyError();
|
|
85
88
|
|
|
86
89
|
// the new transformer works only with new CSN
|
|
@@ -116,9 +119,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
116
119
|
// @ts-ignore
|
|
117
120
|
const externalServices = services.filter(serviceName => csn.definitions[serviceName]['@cds.external']);
|
|
118
121
|
// @ts-ignore
|
|
119
|
-
const isExternalServiceMember = (art, name) =>
|
|
120
|
-
return !!(externalServices.includes(getServiceName(name)) || (art && art['@cds.external']))
|
|
121
|
-
}
|
|
122
|
+
const isExternalServiceMember = (art, name) => !!(externalServices.includes(getServiceName(name)) || (art && art['@cds.external']));
|
|
122
123
|
|
|
123
124
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
|
|
124
125
|
enrichUniversalCsn(csn, options);
|
|
@@ -141,13 +142,25 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
141
142
|
// replace all type refs to builtin types with direct type
|
|
142
143
|
transformUtils.rewriteBuiltinTypeRef(csn);
|
|
143
144
|
|
|
144
|
-
|
|
145
|
+
// Rewrite paths in annotations only if beta modes are set
|
|
145
146
|
|
|
146
147
|
options.enrichAnnotations = true;
|
|
147
148
|
const cleanup = validate.forOdata(csn, {
|
|
148
|
-
message,
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
message,
|
|
150
|
+
error,
|
|
151
|
+
warning,
|
|
152
|
+
info,
|
|
153
|
+
inspectRef,
|
|
154
|
+
effectiveType,
|
|
155
|
+
getFinalTypeInfo,
|
|
156
|
+
artifactRef,
|
|
157
|
+
options,
|
|
158
|
+
csnUtils,
|
|
159
|
+
services,
|
|
160
|
+
isExternalServiceMember,
|
|
161
|
+
recurseElements,
|
|
162
|
+
checkMultipleAssignments,
|
|
163
|
+
csn,
|
|
151
164
|
});
|
|
152
165
|
|
|
153
166
|
|
|
@@ -155,19 +168,22 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
155
168
|
throwWithAnyError();
|
|
156
169
|
|
|
157
170
|
// TODO: Refactor out the following logic
|
|
171
|
+
const hasProjection = new Set();
|
|
158
172
|
forEachDefinition(csn, [
|
|
159
173
|
(def) => {
|
|
160
174
|
// Convert a projection into a query for internal processing will be re-converted
|
|
161
175
|
// at the end of the OData processing
|
|
162
176
|
// TODO: handle artifact.projection instead of artifact.query correctly in future V2
|
|
163
177
|
if (def.kind === 'entity' && def.projection) {
|
|
178
|
+
hasProjection.add(def);
|
|
164
179
|
def.query = { SELECT: def.projection };
|
|
180
|
+
delete def.projection;
|
|
165
181
|
dropDefinitionCache(def);
|
|
166
182
|
initDefinition(def);
|
|
167
183
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
{ skipArtifact: isExternalServiceMember });
|
|
171
187
|
|
|
172
188
|
// All type refs must be resolved, including external APIs.
|
|
173
189
|
// OData has no 'type of' so 'real' imported OData APIs marked @cds.external are safe.
|
|
@@ -181,13 +197,13 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
181
197
|
// and expand these structured elements. This tuple expansion allows all other
|
|
182
198
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
183
199
|
// If errors are detected, throwWithAnyError() will return from further processing
|
|
184
|
-
expandStructsInExpression(
|
|
200
|
+
expandStructsInExpression({ skipArtifact: isExternalServiceMember, drillRef: true });
|
|
185
201
|
|
|
186
202
|
// do expansion before Fk creation because of messages reporting
|
|
187
203
|
if (!structuredOData) {
|
|
188
204
|
expansion.expandStructureReferences(csn, options, '_',
|
|
189
|
-
|
|
190
|
-
|
|
205
|
+
{ error, info, throwWithAnyError }, csnUtils,
|
|
206
|
+
{ skipArtifact: isExternalServiceMember, keepKeysOrigin: true });
|
|
191
207
|
}
|
|
192
208
|
|
|
193
209
|
createForeignKeyElements(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });
|
|
@@ -202,34 +218,34 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
202
218
|
const resolved = new WeakMap();
|
|
203
219
|
const { inspectRef, effectiveType } = csnRefs(csn);
|
|
204
220
|
const { getFinalTypeInfo } = getUtils(csn);
|
|
205
|
-
const { adaptRefs, transformer: refFlattener }
|
|
206
|
-
flattening.getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, '_');
|
|
221
|
+
const { adaptRefs, transformer: refFlattener }
|
|
222
|
+
= flattening.getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, '_');
|
|
207
223
|
|
|
208
224
|
const allMgdAssocDefs = flattening.allInOneFlattening(csn, refFlattener, adaptRefs,
|
|
209
|
-
|
|
225
|
+
inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options);
|
|
210
226
|
flattening.flattenAllStructStepsInRefs(csn, refFlattener, adaptRefs,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
227
|
+
inspectRef, effectiveType, csnUtils, error, options,
|
|
228
|
+
{ // skip: ['action', 'aspect', 'event', 'function', 'type'],
|
|
229
|
+
skipArtifact: isExternalServiceMember,
|
|
230
|
+
});
|
|
215
231
|
flattening.replaceManagedAssocsAsKeys(allMgdAssocDefs, csnUtils);
|
|
216
232
|
|
|
217
233
|
// replace structured with flat dictionaries that contain
|
|
218
234
|
// rewritten path expressions
|
|
219
235
|
forEachDefinition(csn, (def) => {
|
|
220
|
-
['elements', 'params'].forEach(dictName => {
|
|
221
|
-
if(def[`$flat${dictName}`])
|
|
222
|
-
def[dictName] = def[`$flat${dictName}`];
|
|
223
|
-
})
|
|
224
|
-
if(def.$flatAnnotations) {
|
|
225
|
-
Object.entries(def.$flatAnnotations).forEach(([an, av]) => {
|
|
236
|
+
[ 'elements', 'params' ].forEach((dictName) => {
|
|
237
|
+
if (def[`$flat${ dictName }`])
|
|
238
|
+
def[dictName] = def[`$flat${ dictName }`];
|
|
239
|
+
});
|
|
240
|
+
if (def.$flatAnnotations) {
|
|
241
|
+
Object.entries(def.$flatAnnotations).forEach(([ an, av ]) => {
|
|
226
242
|
def[an] = av;
|
|
227
|
-
})
|
|
243
|
+
});
|
|
228
244
|
}
|
|
229
|
-
if(def.actions) {
|
|
245
|
+
if (def.actions) {
|
|
230
246
|
Object.values(def.actions).forEach((action) => {
|
|
231
|
-
if(action.$flatAnnotations) {
|
|
232
|
-
Object.entries(action.$flatAnnotations).forEach(([an, av]) => {
|
|
247
|
+
if (action.$flatAnnotations) {
|
|
248
|
+
Object.entries(action.$flatAnnotations).forEach(([ an, av ]) => {
|
|
233
249
|
action[an] = av;
|
|
234
250
|
});
|
|
235
251
|
}
|
|
@@ -243,9 +259,9 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
243
259
|
// Allow using managed associations as steps in on-conditions to access their fks
|
|
244
260
|
// To be done after handleManagedAssociationsAndCreateForeignKeys,
|
|
245
261
|
// since then the foreign keys of the managed assocs are part of the elements
|
|
246
|
-
if(!structuredOData)
|
|
247
|
-
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, '_'));
|
|
248
|
-
|
|
262
|
+
if (!structuredOData)
|
|
263
|
+
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, options, csnUtils, '_'));
|
|
264
|
+
|
|
249
265
|
|
|
250
266
|
// structure flattener reports errors, further processing is not safe -> throw exception in case of errors
|
|
251
267
|
throwWithAnyError();
|
|
@@ -270,7 +286,9 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
270
286
|
// - Perform checks for exposed non-abstract entities and views - check media type and key-ness
|
|
271
287
|
|
|
272
288
|
// Deal with all kind of annotations manipulations here
|
|
273
|
-
const skipPersNameKinds = {
|
|
289
|
+
const skipPersNameKinds = {
|
|
290
|
+
service: 1, context: 1, namespace: 1, annotation: 1, action: 1, function: 1,
|
|
291
|
+
};
|
|
274
292
|
forEachDefinition(csn, (def, defName) => {
|
|
275
293
|
// Resolve annotation shorthands for entities, types, annotations, ...
|
|
276
294
|
renameShorthandAnnotations(def);
|
|
@@ -278,7 +296,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
278
296
|
// Annotate artifacts with their DB names if requested.
|
|
279
297
|
// Skip artifacts that have no DB equivalent anyway
|
|
280
298
|
if (options.sqlMapping && !(def.kind in skipPersNameKinds))
|
|
281
|
-
|
|
299
|
+
// hana to allow naming mode "hdbcds"
|
|
282
300
|
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana');
|
|
283
301
|
|
|
284
302
|
forEachMemberRecursively(def, (member, memberName, propertyName) => {
|
|
@@ -290,8 +308,8 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
290
308
|
!(propertyName === 'enum' || propertyName === 'returns') &&
|
|
291
309
|
(!member.virtual || def.query)) {
|
|
292
310
|
// If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
|
|
293
|
-
member['@cds.persistence.name'] = getElementDatabaseNameOf((!member['@odata.foreignKey4'] && member.$defPath?.slice(1).join('.'))
|
|
294
|
-
|
|
311
|
+
member['@cds.persistence.name'] = getElementDatabaseNameOf((!member['@odata.foreignKey4'] && member.$defPath?.slice(1).join('.')) ||
|
|
312
|
+
memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"
|
|
295
313
|
}
|
|
296
314
|
|
|
297
315
|
processDynamicFieldControlAnnotations(member);
|
|
@@ -319,18 +337,19 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
319
337
|
// to the foreign keys is done very late in edmPreprocessor.initializeAssociation()
|
|
320
338
|
addCommonValueListviaAssociation(member, memberName);
|
|
321
339
|
}
|
|
322
|
-
}, ['definitions', defName]);
|
|
340
|
+
}, [ 'definitions', defName ]);
|
|
323
341
|
|
|
324
342
|
// Convert a query back into a projection for CSN compliance as
|
|
325
343
|
// the very last conversion step of the OData transformation
|
|
326
|
-
if (def.kind === 'entity' &&
|
|
344
|
+
if (def.kind === 'entity' && hasProjection.has(def)) {
|
|
345
|
+
def.projection = def.query.SELECT;
|
|
327
346
|
delete def.query;
|
|
328
347
|
}
|
|
329
|
-
}, { skipArtifact: isExternalServiceMember })
|
|
348
|
+
}, { skipArtifact: isExternalServiceMember });
|
|
330
349
|
|
|
331
|
-
if(isBetaEnabled(options, 'odataTerms'))
|
|
350
|
+
if (isBetaEnabled(options, 'odataTerms'))
|
|
332
351
|
forEachGeneric(csn, 'vocabularies', renameShorthandAnnotations);
|
|
333
|
-
|
|
352
|
+
|
|
334
353
|
|
|
335
354
|
cleanup();
|
|
336
355
|
// Throw exception in case of errors
|
|
@@ -344,16 +363,18 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
344
363
|
// Transform @readonly/@mandatory/@disabled into @Common.FieldControl annotation
|
|
345
364
|
// with a when/then/else expression consisting of the input from the annotations.
|
|
346
365
|
function processDynamicFieldControlAnnotations(node) {
|
|
347
|
-
if (node['@Common.FieldControl'])
|
|
366
|
+
if (node['@Common.FieldControl'])
|
|
367
|
+
return;
|
|
348
368
|
// TODO (SO): factor this out into a constant so we don't create a fresh array all the time?
|
|
349
|
-
if (['@readonly', '@mandatory', '@disabled'].some(key => typeof node[key] === 'boolean'))
|
|
350
|
-
|
|
351
|
-
|
|
369
|
+
if ([ '@readonly', '@mandatory', '@disabled' ].some(key => typeof node[key] === 'boolean'))
|
|
370
|
+
return;
|
|
371
|
+
|
|
352
372
|
|
|
353
|
-
const definedAnnotations = ['@disabled', '@readonly', '@mandatory']
|
|
373
|
+
const definedAnnotations = [ '@disabled', '@readonly', '@mandatory' ]
|
|
354
374
|
.filter(key => node[key] && isAnnotationExpression(node[key]));
|
|
355
|
-
|
|
356
|
-
if (definedAnnotations.length === 0)
|
|
375
|
+
|
|
376
|
+
if (definedAnnotations.length === 0)
|
|
377
|
+
return;
|
|
357
378
|
|
|
358
379
|
const values = {
|
|
359
380
|
'@disabled': { val: 0 },
|
|
@@ -365,7 +386,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
365
386
|
'=': true,
|
|
366
387
|
xpr: createFieldControlExpression(definedAnnotations),
|
|
367
388
|
};
|
|
368
|
-
|
|
389
|
+
|
|
369
390
|
setAnnotation(node, '@Common.FieldControl', fieldControl);
|
|
370
391
|
|
|
371
392
|
function createFieldControlExpression(annotations) {
|
|
@@ -380,12 +401,12 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
380
401
|
const currentExpression = [
|
|
381
402
|
'case',
|
|
382
403
|
'when',
|
|
383
|
-
...(Array.isArray(xprInAnnoValue) ? xprInAnnoValue : [xprInAnnoValue]),
|
|
404
|
+
...(Array.isArray(xprInAnnoValue) ? xprInAnnoValue : [ xprInAnnoValue ]),
|
|
384
405
|
'then',
|
|
385
406
|
annotationVal,
|
|
386
407
|
'else',
|
|
387
|
-
|
|
388
|
-
|
|
408
|
+
// Use the previous nested expression or default value. Note that annotations
|
|
409
|
+
// are looped backwards
|
|
389
410
|
nestedExpression ? { xpr: nestedExpression } : { val: 3 },
|
|
390
411
|
'end',
|
|
391
412
|
];
|
|
@@ -399,28 +420,28 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
399
420
|
function getXprFromAnno(anno) {
|
|
400
421
|
const xprProp = xprInAnnoProperties.find(prop => anno[prop] !== undefined);
|
|
401
422
|
const constructResult = {
|
|
402
|
-
|
|
423
|
+
ref: () => {
|
|
403
424
|
const result = { ref: anno.ref };
|
|
404
425
|
if (anno.cast)
|
|
405
426
|
result.cast = anno.cast;
|
|
406
427
|
return result;
|
|
407
428
|
},
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
429
|
+
xpr: () => anno.xpr,
|
|
430
|
+
list: () => ({ list: anno.list }),
|
|
431
|
+
literal: () => constructResult.val(),
|
|
432
|
+
val: () => {
|
|
412
433
|
const result = { val: anno.val };
|
|
413
434
|
if (anno.literal)
|
|
414
435
|
result.literal = anno.literal;
|
|
415
436
|
return result;
|
|
416
437
|
},
|
|
417
438
|
'#': () => ({ '#': anno['#'] }),
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
439
|
+
func: () => ({ func: anno.func }),
|
|
440
|
+
args: () => ({ args: anno.args }),
|
|
441
|
+
SELECT: () => ({ SELECT: anno.SELECT }),
|
|
442
|
+
SET: () => ({ SET: anno.SET }),
|
|
443
|
+
cast: () => constructResult.ref(),
|
|
444
|
+
};
|
|
424
445
|
return constructResult[xprProp]();
|
|
425
446
|
}
|
|
426
447
|
}
|
|
@@ -428,11 +449,12 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
428
449
|
// Mark elements that are annotated with @odata.on.insert/update with the annotation @Core.Computed.
|
|
429
450
|
function annotateCoreComputed(node) {
|
|
430
451
|
// If @Core.Computed is explicitly set, don't overwrite it!
|
|
431
|
-
if (node['@Core.Computed'] !== undefined)
|
|
452
|
+
if (node['@Core.Computed'] !== undefined)
|
|
453
|
+
return;
|
|
432
454
|
|
|
433
455
|
// For @odata.on.insert/update, also add @Core.Computed
|
|
434
456
|
// @odata.on is deprecated, use @cds.on {update|insert} instead
|
|
435
|
-
if(['@odata.on.insert', '@odata.on.update', '@cds.on.insert', '@cds.on.update'].some(a => node[a]))
|
|
457
|
+
if ([ '@odata.on.insert', '@odata.on.update', '@cds.on.insert', '@cds.on.update' ].some(a => node[a]))
|
|
436
458
|
node['@Core.Computed'] = true;
|
|
437
459
|
}
|
|
438
460
|
|
|
@@ -445,37 +467,36 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
445
467
|
};
|
|
446
468
|
const renameMappings = {
|
|
447
469
|
'@ValueList.entity': { val: '@Common.ValueList', op: 'entity' },
|
|
448
|
-
'@ValueList.type':
|
|
470
|
+
'@ValueList.type': { val: '@Common.ValueList', op: 'type' },
|
|
449
471
|
'@Capabilities.Deletable': { val: '@Capabilities.DeleteRestrictions', op: 'Deletable' },
|
|
450
472
|
'@Capabilities.Insertable': { val: '@Capabilities.InsertRestrictions', op: 'Insertable' },
|
|
451
473
|
'@Capabilities.Updatable': { val: '@Capabilities.UpdateRestrictions', op: 'Updatable' },
|
|
452
|
-
'@Capabilities.Readable': { val: '@Capabilities.ReadRestrictions', op: 'Readable' }
|
|
474
|
+
'@Capabilities.Readable': { val: '@Capabilities.ReadRestrictions', op: 'Readable' },
|
|
453
475
|
};
|
|
454
476
|
|
|
455
477
|
const setShortCuts = Object.keys(setMappings);
|
|
456
478
|
const renameShortCuts = Object.keys(renameMappings);
|
|
457
479
|
|
|
458
480
|
// Capabilities shortcuts have precedence over @readonly/@insertonly
|
|
459
|
-
Object.keys(node).forEach( name => {
|
|
481
|
+
Object.keys(node).forEach( (name) => {
|
|
460
482
|
if (!name.startsWith('@'))
|
|
461
483
|
return;
|
|
462
484
|
// Rename according to map above
|
|
463
485
|
const renamePrefix = (name in renameMappings)
|
|
464
486
|
? name
|
|
465
|
-
: renameShortCuts.find(p => name.startsWith(p
|
|
466
|
-
if(renamePrefix) {
|
|
487
|
+
: renameShortCuts.find(p => name.startsWith(`${ p }.`));
|
|
488
|
+
if (renamePrefix) {
|
|
467
489
|
const mapping = renameMappings[renamePrefix];
|
|
468
|
-
renameAnnotation(node, name, name.replace(renamePrefix, `${mapping.val}.${mapping.op}`));
|
|
490
|
+
renameAnnotation(node, name, name.replace(renamePrefix, `${ mapping.val }.${ mapping.op }`));
|
|
469
491
|
}
|
|
470
492
|
else {
|
|
471
493
|
// The two mappings have no overlap, so no need to check for second map if first matched.
|
|
472
494
|
// Rename according to map above
|
|
473
495
|
const setPrefix = (name in setMappings)
|
|
474
496
|
? name
|
|
475
|
-
: setShortCuts.find(p => name.startsWith(p
|
|
476
|
-
if(setPrefix)
|
|
497
|
+
: setShortCuts.find(p => name.startsWith(`${ p }.`) || name.startsWith(`${ p }#`));
|
|
498
|
+
if (setPrefix)
|
|
477
499
|
setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
|
|
478
|
-
}
|
|
479
500
|
}
|
|
480
501
|
});
|
|
481
502
|
|
|
@@ -483,14 +504,16 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
483
504
|
// but '@Core.Computed' for everything else.
|
|
484
505
|
|
|
485
506
|
// only if not both readonly/insertonly are true do the mapping
|
|
486
|
-
if(!(node['@readonly'] && node['@insertonly'])) {
|
|
487
|
-
if(node['@readonly']) {
|
|
507
|
+
if (!(node['@readonly'] && node['@insertonly'])) {
|
|
508
|
+
if (node['@readonly']) {
|
|
488
509
|
const setRO = (qualifier) => {
|
|
489
510
|
if (node.kind === 'entity' || node.kind === 'aspect') {
|
|
490
|
-
|
|
491
|
-
setAnnotation(node, `@Capabilities.
|
|
492
|
-
setAnnotation(node, `@Capabilities.
|
|
493
|
-
|
|
511
|
+
const qualifierStr = qualifier ? `#${ qualifier }` : '';
|
|
512
|
+
setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifierStr }.Deletable`, false);
|
|
513
|
+
setAnnotation(node, `@Capabilities.InsertRestrictions${ qualifierStr }.Insertable`, false);
|
|
514
|
+
setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifierStr }.Updatable`, false);
|
|
515
|
+
}
|
|
516
|
+
else if (!isAnnotationExpression(node['@readonly'])) {
|
|
494
517
|
// add @Core.Computed only for non-xpr values of @readonly
|
|
495
518
|
setAnnotation(node, '@Core.Computed', true);
|
|
496
519
|
}
|
|
@@ -500,10 +523,11 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
500
523
|
// @insertonly is effective on entities/queries only
|
|
501
524
|
if (node['@insertonly'] && (node.kind === 'entity' || node.kind === 'aspect')) {
|
|
502
525
|
const setIO = (qualifier) => {
|
|
503
|
-
|
|
504
|
-
setAnnotation(node, `@Capabilities.
|
|
505
|
-
setAnnotation(node, `@Capabilities.
|
|
506
|
-
|
|
526
|
+
const qualifierStr = qualifier ? `#${ qualifier }` : '';
|
|
527
|
+
setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifierStr }.Deletable`, false);
|
|
528
|
+
setAnnotation(node, `@Capabilities.ReadRestrictions${ qualifierStr }.Readable`, false);
|
|
529
|
+
setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifierStr }.Updatable`, false);
|
|
530
|
+
};
|
|
507
531
|
setIO(undefined);
|
|
508
532
|
}
|
|
509
533
|
}
|
|
@@ -513,11 +537,11 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
513
537
|
setAnnotation(node, '@Validation.Pattern', node['@assert.format']);
|
|
514
538
|
|
|
515
539
|
// Only on element level
|
|
516
|
-
if(node.kind == null) {
|
|
517
|
-
if (node['@mandatory'] && !isAnnotationExpression(node['@mandatory'])
|
|
518
|
-
|
|
540
|
+
if (node.kind == null) {
|
|
541
|
+
if (node['@mandatory'] && !isAnnotationExpression(node['@mandatory']) &&
|
|
542
|
+
!Object.entries(node).some(([ k, v ]) => k === '@Common.FieldControl' || k.startsWith('@Common.FieldControl.') && v != null))
|
|
519
543
|
setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
|
|
520
|
-
|
|
544
|
+
|
|
521
545
|
if (node['@assert.range'] != null)
|
|
522
546
|
setAssertRangeAnnotation(node);
|
|
523
547
|
}
|
|
@@ -527,7 +551,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
527
551
|
function setAssertRangeAnnotation(node) {
|
|
528
552
|
const range = node['@assert.range'];
|
|
529
553
|
if (!Array.isArray(range) || range.length !== 2)
|
|
530
|
-
|
|
554
|
+
return; // TODO: Warning for wrong format?
|
|
531
555
|
|
|
532
556
|
const min = range[0];
|
|
533
557
|
const max = range[1];
|
|
@@ -539,7 +563,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
539
563
|
// via `@assert.range: [ _, _ ]`.
|
|
540
564
|
// For `_`, minVal is an object and this function returns false, which is ok,
|
|
541
565
|
// since we don't render the annotation for "infinite" values.
|
|
542
|
-
const shouldSet =
|
|
566
|
+
const shouldSet = val => (typeof val !== 'object' && val !== undefined && val !== null);
|
|
543
567
|
|
|
544
568
|
if (shouldSet(minVal)) {
|
|
545
569
|
setAnnotation(node, '@Validation.Minimum', minVal);
|
|
@@ -551,7 +575,6 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
551
575
|
if (max['='] !== undefined)
|
|
552
576
|
setAnnotation(node, '@Validation.Maximum.@Validation.Exclusive', true);
|
|
553
577
|
}
|
|
554
|
-
|
|
555
578
|
}
|
|
556
579
|
|
|
557
580
|
// If an association was modelled as not null, like so:
|
|
@@ -578,30 +601,30 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
578
601
|
// Apply default type facets to each type definition and every member
|
|
579
602
|
// But do not apply default string length (as in DB)
|
|
580
603
|
function setDefaultTypeFacets(def) {
|
|
581
|
-
addDefaultTypeFacets(def.items || def, null)
|
|
582
|
-
forEachMemberRecursively(def,
|
|
583
|
-
if(def.returns)
|
|
604
|
+
addDefaultTypeFacets(def.items || def, null);
|
|
605
|
+
forEachMemberRecursively(def, m => addDefaultTypeFacets(m.items || m, null));
|
|
606
|
+
if (def.returns)
|
|
584
607
|
addDefaultTypeFacets(def.returns.items || def.returns, null);
|
|
585
608
|
}
|
|
586
609
|
|
|
587
610
|
// Handles on-conditions in unmanaged associations
|
|
588
611
|
function processOnCond(def) {
|
|
589
612
|
forEachMemberRecursively(def, (member) => {
|
|
590
|
-
if (member.on && isAssocOrComposition(member))
|
|
613
|
+
if (member.on && isAssocOrComposition(member))
|
|
591
614
|
removeLeadingDollarSelfInOnCondition(member);
|
|
592
|
-
}
|
|
593
615
|
});
|
|
594
616
|
|
|
595
617
|
// removes leading $self in on-conditions's references
|
|
596
618
|
function removeLeadingDollarSelfInOnCondition(assoc) {
|
|
597
|
-
if (!assoc.on)
|
|
619
|
+
if (!assoc.on)
|
|
620
|
+
return; // nothing to do
|
|
598
621
|
// TODO: Shouldn't this only run on the on-condition and not the whole assoc-node?
|
|
599
622
|
applyTransformationsOnNonDictionary({ assoc }, 'assoc', {
|
|
600
623
|
ref: (node, prop, ref) => {
|
|
601
624
|
// remove leading $self when at the beginning of a ref
|
|
602
625
|
if (ref.length > 1 && ref[0] === '$self')
|
|
603
626
|
node.ref.splice(0, 1);
|
|
604
|
-
}
|
|
627
|
+
},
|
|
605
628
|
});
|
|
606
629
|
}
|
|
607
630
|
}
|
|
@@ -616,9 +639,8 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
616
639
|
if (isAssociation(member)) {
|
|
617
640
|
const navigable = member['@odata.navigable'] !== false; // navigable disabled only if explicitly set to false
|
|
618
641
|
const targetDef = getCsnDef(member.target);
|
|
619
|
-
if (navigable && targetDef['@cds.odata.valuelist'] && !member[vlAnno])
|
|
642
|
+
if (navigable && targetDef['@cds.odata.valuelist'] && !member[vlAnno])
|
|
620
643
|
setAnnotation(member, vlAnno, { '=': memberName });
|
|
621
|
-
}
|
|
622
644
|
}
|
|
623
645
|
}
|
|
624
646
|
|
|
@@ -627,6 +649,4 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
627
649
|
const csnRefApi = csnRefs(csn);
|
|
628
650
|
Object.assign(csnUtils, csnRefApi);
|
|
629
651
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
652
|
} // transform4odataWithCsn
|