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