@sap/cds-compiler 3.9.4 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -4
- package/README.md +0 -1
- package/bin/cdsc.js +11 -23
- package/bin/cdsse.js +3 -3
- package/doc/API.md +5 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +17 -1
- package/doc/CHANGELOG_DEPRECATED.md +28 -0
- package/lib/api/.eslintrc.json +1 -1
- package/lib/api/main.js +55 -9
- package/lib/api/options.js +2 -0
- package/lib/base/error.js +2 -0
- package/lib/base/message-registry.js +143 -64
- package/lib/base/messages.js +213 -107
- package/lib/base/model.js +11 -11
- package/lib/checks/.eslintrc.json +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/elements.js +1 -1
- package/lib/checks/enricher.js +26 -3
- package/lib/checks/onConditions.js +67 -12
- package/lib/checks/queryNoDbArtifacts.js +106 -105
- package/lib/checks/sql-snippets.js +2 -0
- package/lib/checks/types.js +12 -6
- package/lib/checks/validator.js +2 -2
- package/lib/compiler/assert-consistency.js +10 -8
- package/lib/compiler/builtins.js +8 -2
- package/lib/compiler/checks.js +52 -35
- package/lib/compiler/define.js +31 -26
- package/lib/compiler/extend.js +120 -65
- package/lib/compiler/finalize-parse-cdl.js +12 -43
- package/lib/compiler/generate.js +16 -5
- package/lib/compiler/index.js +8 -5
- package/lib/compiler/kick-start.js +4 -3
- package/lib/compiler/populate.js +96 -95
- package/lib/compiler/propagator.js +7 -8
- package/lib/compiler/resolve.js +377 -103
- package/lib/compiler/shared.js +794 -517
- package/lib/compiler/tweak-assocs.js +8 -6
- package/lib/compiler/utils.js +44 -0
- package/lib/edm/annotations/genericTranslation.js +41 -5
- package/lib/edm/csn2edm.js +34 -32
- package/lib/edm/edm.js +34 -31
- package/lib/edm/edmAnnoPreprocessor.js +0 -23
- package/lib/edm/edmInboundChecks.js +7 -2
- package/lib/edm/edmPreprocessor.js +25 -18
- package/lib/edm/edmUtils.js +8 -4
- package/lib/gen/Dictionary.json +18 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -2
- package/lib/gen/languageParser.js +5006 -4582
- package/lib/json/from-csn.js +157 -112
- package/lib/json/to-csn.js +60 -89
- package/lib/language/antlrParser.js +17 -13
- package/lib/language/docCommentParser.js +11 -1
- package/lib/language/genericAntlrParser.js +13 -10
- package/lib/language/language.g4 +168 -97
- package/lib/main.d.ts +128 -36
- package/lib/main.js +1 -1
- package/lib/model/csnRefs.js +24 -5
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +7 -12
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +1 -1
- package/lib/modelCompare/utils/filter.js +40 -2
- package/lib/optionProcessor.js +0 -3
- package/lib/render/toCdl.js +247 -214
- package/lib/render/toHdbcds.js +197 -181
- package/lib/render/toSql.js +325 -289
- package/lib/render/utils/common.js +42 -4
- package/lib/render/utils/delta.js +1 -1
- package/lib/render/utils/sql.js +3 -3
- package/lib/transform/braceExpression.js +2 -2
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/associations.js +24 -12
- package/lib/transform/db/expansion.js +17 -18
- package/lib/transform/db/flattening.js +17 -21
- package/lib/transform/db/rewriteCalculatedElements.js +171 -64
- package/lib/transform/db/views.js +3 -4
- package/lib/transform/draft/db.js +21 -12
- package/lib/transform/draft/odata.js +4 -0
- package/lib/transform/forOdataNew.js +62 -47
- package/lib/transform/forRelationalDB.js +12 -7
- package/lib/transform/localized.js +4 -2
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/parseExpr.js +3 -0
- package/lib/transform/transformUtilsNew.js +43 -23
- package/lib/transform/translateAssocsToJoins.js +7 -6
- package/lib/transform/universalCsn/.eslintrc.json +1 -1
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
- package/package.json +2 -2
- package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
- package/share/messages/message-explanations.json +1 -1
|
@@ -51,7 +51,7 @@ const { addLocalizationViews } = require('./localized');
|
|
|
51
51
|
// -- exposed associations do not point to non-exposed targets
|
|
52
52
|
// -- structured types must not contain associations for OData V2
|
|
53
53
|
// - Element must not be an 'array of' for OData V2 TODO: move to the validator
|
|
54
|
-
// (Linter
|
|
54
|
+
// (Linter Candidate, move as hard error into EdmPreproc on V2 generation)
|
|
55
55
|
// - Perform checks for exposed non-abstract entities and views - check media type and
|
|
56
56
|
// key-ness (requires that containers have been identified) (Linter candidate, scenario check)
|
|
57
57
|
// Annotations related:
|
|
@@ -201,7 +201,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
201
201
|
// Flatten on-conditions in unmanaged associations
|
|
202
202
|
/* FIXME (HJB): Is this comment still correct? processOnCond only strips $self
|
|
203
203
|
We should not remove $self prefixes in structured OData to not
|
|
204
|
-
|
|
204
|
+
interfere with path resolution
|
|
205
205
|
*/
|
|
206
206
|
// This must be done before all the draft logic as all
|
|
207
207
|
// composition targets are annotated with @odata.draft.enabled in this step
|
|
@@ -230,12 +230,13 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
230
230
|
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana'); // hana to allow naming mode "hdbcds"
|
|
231
231
|
|
|
232
232
|
forEachMemberRecursively(def, (member, memberName, propertyName) => {
|
|
233
|
-
if (memberName === '' && propertyName === 'params')
|
|
234
|
-
return; // ignore "returns" type
|
|
235
233
|
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested
|
|
236
234
|
// Only these are actually required and don't annotate virtual elements in entities or types
|
|
237
235
|
// as they have no DB representation (although in views)
|
|
238
|
-
if (options.sqlMapping && typeof member === 'object' &&
|
|
236
|
+
if (options.sqlMapping && typeof member === 'object' &&
|
|
237
|
+
!(member.kind === 'action' || member.kind === 'function') &&
|
|
238
|
+
!(propertyName === 'enum' || propertyName === 'returns') &&
|
|
239
|
+
(!member.virtual || def.query)) {
|
|
239
240
|
// If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
|
|
240
241
|
member['@cds.persistence.name'] = getElementDatabaseNameOf(member._flatElementNameWithDots || memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"
|
|
241
242
|
}
|
|
@@ -311,88 +312,102 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
311
312
|
node['@Core.Computed'] = true;
|
|
312
313
|
}
|
|
313
314
|
|
|
314
|
-
// Rename shorthand annotations within artifact or element 'node' according to a builtin
|
|
315
|
-
// list.
|
|
315
|
+
// Rename shorthand annotations within artifact or element 'node' according to a builtin list
|
|
316
316
|
function renameShorthandAnnotations(node) {
|
|
317
|
-
// FIXME: Verify this list - are they all still required? Do we need any more?
|
|
318
317
|
const setMappings = {
|
|
319
318
|
'@label': '@Common.Label',
|
|
320
319
|
'@title': '@Common.Label',
|
|
321
320
|
'@description': '@Core.Description',
|
|
322
321
|
};
|
|
323
322
|
const renameMappings = {
|
|
324
|
-
'@ValueList.entity': '@Common.ValueList
|
|
325
|
-
'@ValueList.type': '@Common.ValueList
|
|
326
|
-
'@Capabilities.Deletable': '@Capabilities.DeleteRestrictions
|
|
327
|
-
'@Capabilities.Insertable': '@Capabilities.InsertRestrictions
|
|
328
|
-
'@Capabilities.Updatable': '@Capabilities.UpdateRestrictions
|
|
329
|
-
'@Capabilities.Readable': '@Capabilities.ReadRestrictions
|
|
323
|
+
'@ValueList.entity': { val: '@Common.ValueList', op: 'entity' },
|
|
324
|
+
'@ValueList.type': { val: '@Common.ValueList', op: 'type' },
|
|
325
|
+
'@Capabilities.Deletable': { val: '@Capabilities.DeleteRestrictions', op: 'Deletable' },
|
|
326
|
+
'@Capabilities.Insertable': { val: '@Capabilities.InsertRestrictions', op: 'Insertable' },
|
|
327
|
+
'@Capabilities.Updatable': { val: '@Capabilities.UpdateRestrictions', op: 'Updatable' },
|
|
328
|
+
'@Capabilities.Readable': { val: '@Capabilities.ReadRestrictions', op: 'Readable' }
|
|
330
329
|
};
|
|
331
330
|
|
|
332
331
|
const setShortCuts = Object.keys(setMappings);
|
|
333
332
|
const renameShortCuts = Object.keys(renameMappings);
|
|
333
|
+
|
|
334
|
+
// Capabilities shortcuts have precedence over @readonly/@insertonly
|
|
334
335
|
Object.keys(node).forEach( name => {
|
|
335
336
|
if (!name.startsWith('@'))
|
|
336
337
|
return;
|
|
337
338
|
// Rename according to map above
|
|
338
|
-
const renamePrefix = (name in renameMappings)
|
|
339
|
+
const renamePrefix = (name in renameMappings)
|
|
340
|
+
? name
|
|
341
|
+
: renameShortCuts.find(p => name.startsWith(p + '.'));
|
|
339
342
|
if(renamePrefix) {
|
|
340
|
-
|
|
341
|
-
|
|
343
|
+
const mapping = renameMappings[renamePrefix];
|
|
344
|
+
renameAnnotation(node, name, name.replace(renamePrefix, `${mapping.val}.${mapping.op}`));
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
342
347
|
// The two mappings have no overlap, so no need to check for second map if first matched.
|
|
343
348
|
// Rename according to map above
|
|
344
|
-
const setPrefix = (name in setMappings)
|
|
349
|
+
const setPrefix = (name in setMappings)
|
|
350
|
+
? name
|
|
351
|
+
: setShortCuts.find(p => name.startsWith(p + '.') || name.startsWith(p + '#'));
|
|
345
352
|
if(setPrefix) {
|
|
346
353
|
setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
|
|
347
354
|
}
|
|
348
355
|
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Special case: '@readonly' becomes a triplet of capability restrictions for entities,
|
|
359
|
+
// but '@Core.Computed' for everything else.
|
|
349
360
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if
|
|
353
|
-
|
|
361
|
+
// only if not both readonly/insertonly are true do the mapping
|
|
362
|
+
if(!(node['@readonly'] && node['@insertonly'])) {
|
|
363
|
+
if(node['@readonly']) {
|
|
364
|
+
const setRO = (qualifier) => {
|
|
354
365
|
if (node.kind === 'entity' || node.kind === 'aspect') {
|
|
355
|
-
setAnnotation(node,
|
|
356
|
-
setAnnotation(node,
|
|
357
|
-
setAnnotation(node,
|
|
366
|
+
setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false);
|
|
367
|
+
setAnnotation(node, `@Capabilities.InsertRestrictions${ qualifier ? '#' + qualifier : ''}.Insertable`, false);
|
|
368
|
+
setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false);
|
|
358
369
|
} else {
|
|
359
370
|
setAnnotation(node, '@Core.Computed', true);
|
|
360
371
|
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
else if (name === '@insertonly' && node[name]) {
|
|
364
|
-
if (node.kind === 'entity' || node.kind === 'aspect') {
|
|
365
|
-
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
|
|
366
|
-
setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);
|
|
367
|
-
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
372
|
+
};
|
|
373
|
+
setRO(undefined);
|
|
370
374
|
}
|
|
371
|
-
//
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
|
|
375
|
+
// @insertonly is effective on entities/queries only
|
|
376
|
+
if (node['@insertonly'] && (node.kind === 'entity' || node.kind === 'aspect')) {
|
|
377
|
+
const setIO = (qualifier) => {
|
|
378
|
+
setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false);
|
|
379
|
+
setAnnotation(node, `@Capabilities.ReadRestrictions${ qualifier ? '#' + qualifier : ''}.Readable`, false);
|
|
380
|
+
setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false);
|
|
381
|
+
}
|
|
382
|
+
setIO(undefined);
|
|
375
383
|
}
|
|
384
|
+
}
|
|
376
385
|
|
|
377
|
-
|
|
378
|
-
|
|
386
|
+
// @Validation.Pattern is applicable to "Term" => node.kind === annotation
|
|
387
|
+
if (node['@assert.format'] != null)
|
|
388
|
+
setAnnotation(node, '@Validation.Pattern', node['@assert.format']);
|
|
379
389
|
|
|
380
|
-
|
|
390
|
+
// Only on element level
|
|
391
|
+
if(node.kind == null) {
|
|
392
|
+
if (node['@mandatory']&& node['@Common.FieldControl'] === undefined) {
|
|
393
|
+
setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
|
|
394
|
+
}
|
|
395
|
+
if (node['@assert.range'] != null) {
|
|
381
396
|
if (Array.isArray(node['@assert.range']) && node['@assert.range'].length === 2) {
|
|
382
397
|
setAnnotation(node, '@Validation.Minimum', node['@assert.range'][0]);
|
|
383
398
|
setAnnotation(node, '@Validation.Maximum', node['@assert.range'][1]);
|
|
384
399
|
}
|
|
385
400
|
}
|
|
386
|
-
}
|
|
401
|
+
}
|
|
387
402
|
}
|
|
388
403
|
|
|
389
404
|
// Apply default type facets to each type definition and every member
|
|
390
|
-
// But do not apply default string length
|
|
405
|
+
// But do not apply default string length (as in DB)
|
|
391
406
|
function setDefaultTypeFacets(def) {
|
|
392
|
-
addDefaultTypeFacets(def.items || def,
|
|
393
|
-
forEachMemberRecursively(def, m=>addDefaultTypeFacets(m.items || m,
|
|
407
|
+
addDefaultTypeFacets(def.items || def, null)
|
|
408
|
+
forEachMemberRecursively(def, m=>addDefaultTypeFacets(m.items || m, null));
|
|
394
409
|
if(def.returns)
|
|
395
|
-
addDefaultTypeFacets(def.returns.items || def.returns,
|
|
410
|
+
addDefaultTypeFacets(def.returns.items || def.returns, null);
|
|
396
411
|
}
|
|
397
412
|
|
|
398
413
|
// Handles on-conditions in unmanaged associations
|
|
@@ -409,7 +424,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
409
424
|
// TODO: Shouldn't this only run on the on-condition and not the whole assoc-node?
|
|
410
425
|
applyTransformationsOnNonDictionary({ assoc }, 'assoc', {
|
|
411
426
|
ref: (node, prop, ref) => {
|
|
412
|
-
// remove leading $self when at the
|
|
427
|
+
// remove leading $self when at the beginning of a ref
|
|
413
428
|
if (ref.length > 1 && ref[0] === '$self')
|
|
414
429
|
node.ref.splice(0, 1);
|
|
415
430
|
}
|
|
@@ -31,6 +31,7 @@ const cdsPersistence = require('./db/cdsPersistence');
|
|
|
31
31
|
const temporal = require('./db/temporal');
|
|
32
32
|
const associations = require('./db/associations')
|
|
33
33
|
const { ModelError } = require('../base/error');
|
|
34
|
+
const { getDefaultTypeLengths } = require('../render/utils/common');
|
|
34
35
|
|
|
35
36
|
// By default: Do not process non-entities/views
|
|
36
37
|
function forEachDefinition(csn, cb) {
|
|
@@ -61,7 +62,7 @@ function forEachDefinition(csn, cb) {
|
|
|
61
62
|
* - (045) The query is stripped from entities that are annotated with '@cds.persistence.table',
|
|
62
63
|
* essentially converting views to entities.
|
|
63
64
|
* - (060) Users of primitive type 'UUID' (which is renamed to 'String' in 000) get length 36'.
|
|
64
|
-
* - (070) Default length
|
|
65
|
+
* - (070) Default length N is supplied for strings if not specified.
|
|
65
66
|
* - (080) Annotation definitions are ignored (note that annotation assignments are filtered out by toCdl).
|
|
66
67
|
* - (090) Compositions become associations.
|
|
67
68
|
* - (100) 'masked' is ignored (a), and attribute 'localized' is removed (b)
|
|
@@ -113,6 +114,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
113
114
|
checkCSNVersion(csn, options);
|
|
114
115
|
|
|
115
116
|
const pathDelimiter = (options.sqlMapping === 'hdbcds') ? '.' : '_';
|
|
117
|
+
// There is also an explicit default length via options.defaultStringLength
|
|
118
|
+
const implicitDefaultLengths = getDefaultTypeLengths(options.sqlDialect);
|
|
116
119
|
|
|
117
120
|
let csnUtils;
|
|
118
121
|
let message, error, warning, info; // message functions
|
|
@@ -160,6 +163,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
160
163
|
});
|
|
161
164
|
timetrace.stop('Validate');
|
|
162
165
|
|
|
166
|
+
rewriteCalculatedElementsInViews(csn, options, pathDelimiter, error);
|
|
167
|
+
|
|
163
168
|
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
|
|
164
169
|
handleExists(csn, options, error, inspectRef, initDefinition, dropDefinitionCache);
|
|
165
170
|
|
|
@@ -184,8 +189,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
184
189
|
assertUnique.prepare(csn, options, error, info)
|
|
185
190
|
]);
|
|
186
191
|
|
|
187
|
-
rewriteCalculatedElementsInViews(csn, options, pathDelimiter, error);
|
|
188
|
-
|
|
189
192
|
if(doA2J) {
|
|
190
193
|
// Expand a structured thing in: keys, columns, order by, group by
|
|
191
194
|
expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithAnyError}, csnUtils);
|
|
@@ -252,9 +255,11 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
252
255
|
transformCsn(csn, {
|
|
253
256
|
type: (val, node, key) => {
|
|
254
257
|
renamePrimitiveTypesAndUuid(val, node, key);
|
|
255
|
-
addDefaultTypeFacets(node);
|
|
258
|
+
addDefaultTypeFacets(node, implicitDefaultLengths);
|
|
256
259
|
},
|
|
257
|
-
//
|
|
260
|
+
// no support for array-of - turn into CLOB/Text
|
|
261
|
+
// must be done after A2J or compiler checks could change
|
|
262
|
+
// (e.g. annotation def checks for arrayed types)
|
|
258
263
|
items: (val, node) => {
|
|
259
264
|
node.type = 'cds.LargeString';
|
|
260
265
|
delete node.items;
|
|
@@ -572,7 +577,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
572
577
|
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
573
578
|
|
|
574
579
|
forEachMemberRecursively(artifact, (member, memberName, property, path) => {
|
|
575
|
-
if (
|
|
580
|
+
if (property === 'returns')
|
|
576
581
|
return; // ignore "returns" type
|
|
577
582
|
transformCommon(member, memberName, path);
|
|
578
583
|
// (240 b) Annotate elements, foreign keys, parameters etc with their DB names
|
|
@@ -1146,7 +1151,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
1146
1151
|
// if it's not the first entry, add a ',' ...
|
|
1147
1152
|
if (i)
|
|
1148
1153
|
flattenedIndex.push(',');
|
|
1149
|
-
// ... then add the
|
|
1154
|
+
// ... then add the flattened element name as a single ref
|
|
1150
1155
|
flattenedIndex.push({ ref: [ elem ] });
|
|
1151
1156
|
// ... then check if we have to propagate a 'asc'/'desc', omitting the last, which will be copied automatically
|
|
1152
1157
|
if ((idx + 1) < index.length && (index[idx + 1] === 'asc' || index[idx + 1] === 'desc') && i < elems.length - 1)
|
|
@@ -610,8 +610,10 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
610
610
|
override: true,
|
|
611
611
|
filter: (name) => name.startsWith('localized.'),
|
|
612
612
|
notFound(name, index) {
|
|
613
|
-
if (!ignoreUnknownExtensions)
|
|
614
|
-
messageFunctions.message('anno-undefined-art', [ 'extensions', index ],
|
|
613
|
+
if (!ignoreUnknownExtensions) {
|
|
614
|
+
messageFunctions.message('anno-undefined-art', [ 'extensions', index ],
|
|
615
|
+
{ art: name });
|
|
616
|
+
}
|
|
615
617
|
},
|
|
616
618
|
});
|
|
617
619
|
}
|
|
@@ -103,14 +103,14 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
103
103
|
|
|
104
104
|
if(node.type && !isBuiltinType(node.type)) {
|
|
105
105
|
const finalBaseType = csnUtils.getFinalTypeInfo(node.type);
|
|
106
|
-
if(
|
|
106
|
+
if(finalBaseType == null) {
|
|
107
107
|
/*
|
|
108
|
-
type could not be resolved,
|
|
109
|
-
Today, all type refs must be resolvable,
|
|
110
|
-
|
|
108
|
+
type could not be resolved, delete type property to be equal to a typeless element
|
|
109
|
+
definition. Today, all type refs must be resolvable, input validations
|
|
110
|
+
checkTypeDefinitionHasType, checkElementTypeDefinitionHasType
|
|
111
111
|
guarantee this. In the future this may change.
|
|
112
112
|
*/
|
|
113
|
-
node.type
|
|
113
|
+
delete node.type;
|
|
114
114
|
}
|
|
115
115
|
else {
|
|
116
116
|
if (isExpandable(finalBaseType) || node.kind === 'type') {
|
|
@@ -26,7 +26,7 @@ const { CompilerAssertion } = require('../../base/error');
|
|
|
26
26
|
* and edges e(v_r, v_d) such that all v_r, v_d are elements of S (v_rs, v_ds) and with that
|
|
27
27
|
* all edges are { e(v_rs, v_ds) }.
|
|
28
28
|
*
|
|
29
|
-
* The input CSN may contain edges e(v_rs, v_dns) with v_r element of S_i (v_rs) and v_d not element
|
|
29
|
+
* The input CSN may contain edges e(v_rs, v_dns) with v_r element of S_i (v_rs) and v_d not element
|
|
30
30
|
* of S_i (v_dns).
|
|
31
31
|
*
|
|
32
32
|
* The aim of this algorithm is to produce Tc's for all requested S_i by 'filling' up the missing
|
|
@@ -50,7 +50,7 @@ const { CompilerAssertion } = require('../../base/error');
|
|
|
50
50
|
*
|
|
51
51
|
* If name(v_dns) has no prefix segments, the fallback schema name is prepended instead:
|
|
52
52
|
* name(v_ds) = name(v_s) + '.' + fallbackschema + name(v_dns);
|
|
53
|
-
*
|
|
53
|
+
*
|
|
54
54
|
* @param {CSN.Model} csn
|
|
55
55
|
* @param {function} whatsMyServiceName
|
|
56
56
|
* @param {string[]} requestedServiceNames
|
|
@@ -160,7 +160,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
160
160
|
// we're no longer in a key def => don't set notNull:true on named types
|
|
161
161
|
if(isKey)
|
|
162
162
|
isKey = false;
|
|
163
|
-
// in case this was a named type and if the
|
|
163
|
+
// in case this was a named type and if the openness does not match the type definition
|
|
164
164
|
// expose the type as a new one not changing the original definition.
|
|
165
165
|
if(elements && !!node['@open'] !== !!typeDef['@open'])
|
|
166
166
|
fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
|
|
@@ -282,6 +282,9 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
282
282
|
// if(xpr?.func && funkyfuncs.includes(xpr?.func))
|
|
283
283
|
// return xpr;
|
|
284
284
|
for(let n in xpr) {
|
|
285
|
+
// xpr could be an array with polluted prototype
|
|
286
|
+
if (!Object.hasOwnProperty.call(xpr, n))
|
|
287
|
+
continue;
|
|
285
288
|
const x = xpr[n];
|
|
286
289
|
const isAnno = n[0] === '@' && isSimpleAnnoValue(x);
|
|
287
290
|
if(isAnno)
|
|
@@ -66,28 +66,35 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
66
66
|
expandStructsInExpression,
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Try to apply length, precision, scale from options if no type facet is set on the primitive types 'cds.String' or 'cds.Decimal'.
|
|
71
|
+
* If 'obj' has primitive type 'cds.String' and no length try to apply length from options if available or set to default internalDefaultLengths[type].
|
|
72
|
+
* if 'obj' has primitive type 'cds.Decimal' try to apply precision, scale from options if available.
|
|
73
|
+
*
|
|
74
|
+
* @param {CSN.Element} element
|
|
75
|
+
* @param {null|object} [internalDefaultLengths] Either null (no implicit default) or an object `{ 'cds.String': N, 'cds.Binary': N }`.
|
|
76
|
+
* */
|
|
77
|
+
function addDefaultTypeFacets(element, internalDefaultLengths = null) {
|
|
73
78
|
if (!element || !element.type)
|
|
74
79
|
return;
|
|
75
80
|
|
|
76
81
|
if (element.type === 'cds.String' && element.length === undefined) {
|
|
77
|
-
if(options.defaultStringLength) {
|
|
82
|
+
if (options.defaultStringLength) {
|
|
78
83
|
element.length = options.defaultStringLength;
|
|
79
84
|
setProp(element, '$default', true);
|
|
80
85
|
}
|
|
81
|
-
else if(
|
|
82
|
-
element.length =
|
|
86
|
+
else if (internalDefaultLengths !== null) {
|
|
87
|
+
element.length = internalDefaultLengths[element.type];
|
|
88
|
+
}
|
|
83
89
|
}
|
|
84
90
|
if (element.type === 'cds.Binary' && element.length === undefined) {
|
|
85
|
-
if(options.defaultBinaryLength) {
|
|
91
|
+
if (options.defaultBinaryLength) {
|
|
86
92
|
element.length = options.defaultBinaryLength;
|
|
87
93
|
setProp(element, '$default', true);
|
|
88
94
|
}
|
|
89
|
-
else if(
|
|
90
|
-
element.length =
|
|
95
|
+
else if(internalDefaultLengths !== null) {
|
|
96
|
+
element.length = internalDefaultLengths[element.type];
|
|
97
|
+
}
|
|
91
98
|
}
|
|
92
99
|
/*
|
|
93
100
|
if (element.type === 'cds.Decimal' && element.precision === undefined && options.precision) {
|
|
@@ -273,7 +280,22 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
273
280
|
// This has historic reasons. We don't copy doc-comments because copying annotations
|
|
274
281
|
// is questionable to begin with. Only selected annotations should have been copied,
|
|
275
282
|
// if at all.
|
|
276
|
-
|
|
283
|
+
// When flattening structured elements for OData don't propagate the odata.Type annotations
|
|
284
|
+
// as these would falsify the flattened elements. Type facets must be aligned with
|
|
285
|
+
// EdmTypeFacetMap defined in edm.js
|
|
286
|
+
const excludes = options.toOdata ?
|
|
287
|
+
{
|
|
288
|
+
'@odata.Type': 1,
|
|
289
|
+
'@odata.Scale': 1,
|
|
290
|
+
'@odata.Precision': 1,
|
|
291
|
+
'@odata.MaxLength': 1,
|
|
292
|
+
'@odata.SRID': 1,
|
|
293
|
+
'@odata.FixedLength': 1,
|
|
294
|
+
'@odata.Collation': 1,
|
|
295
|
+
'@odata.Unicode': 1,
|
|
296
|
+
} : {};
|
|
297
|
+
copyAnnotations(elem, flatElem, false, excludes);
|
|
298
|
+
|
|
277
299
|
// Copy selected type properties
|
|
278
300
|
const props = ['key', 'virtual', 'masked', 'viaAll'];
|
|
279
301
|
// 'localized' is needed for OData
|
|
@@ -413,14 +435,11 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
413
435
|
// Copy elements/items and we're finished. No need to look up actual base type,
|
|
414
436
|
// since it must also be structured and must contain at least as many elements,
|
|
415
437
|
// if not more (in client style CSN).
|
|
416
|
-
if (typeRef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds'))
|
|
438
|
+
if (typeRef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds'))
|
|
417
439
|
nodeWithType.elements = cloneCsnDictionary(typeRef.elements, options);
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
if (typeRef.items) {
|
|
440
|
+
else if (typeRef.items)
|
|
421
441
|
nodeWithType.items = cloneCsnNonDict(typeRef.items, options);
|
|
422
|
-
|
|
423
|
-
}
|
|
442
|
+
|
|
424
443
|
return;
|
|
425
444
|
}
|
|
426
445
|
|
|
@@ -450,7 +469,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
450
469
|
art.elements && Object.entries(art.elements).forEach(([elemName, artElem]) => {
|
|
451
470
|
let elem = Object.assign({}, artElem);
|
|
452
471
|
// Transfer xrefs, that are redirected to the projection
|
|
453
|
-
// TODO: shall we remove the
|
|
472
|
+
// TODO: shall we remove the transferred elements from the original?
|
|
454
473
|
// if (artElem._xref) {
|
|
455
474
|
// setProp(elem, '_xref', artElem._xref.filter(xref => xref.user && xref.user._main && xref.user._main._service == service));
|
|
456
475
|
// }
|
|
@@ -470,7 +489,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
470
489
|
// Assemble the projection itself and add it into the model
|
|
471
490
|
let projection = {
|
|
472
491
|
'kind': 'entity',
|
|
473
|
-
projection: query.SELECT, // it is important that
|
|
492
|
+
projection: query.SELECT, // it is important that projection and query refer to the same object!
|
|
474
493
|
elements
|
|
475
494
|
};
|
|
476
495
|
// copy annotations from art to projection
|
|
@@ -749,6 +768,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
749
768
|
if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
|
|
750
769
|
throw new ModelError('Expecting valid return type name: ' + returnTypeName);
|
|
751
770
|
action.returns = { type: returnTypeName };
|
|
771
|
+
// TODO: What about annotation propagation from return type to `returns`?
|
|
752
772
|
}
|
|
753
773
|
|
|
754
774
|
// Add parameter if provided
|
|
@@ -918,7 +938,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
918
938
|
|
|
919
939
|
/**
|
|
920
940
|
* Assigns unconditionally annotation to a node, which means it overwrites already existing annotation assignment.
|
|
921
|
-
*
|
|
941
|
+
* Overwriting is when the assignment differs from undefined and null, also when differs from the already set value.
|
|
922
942
|
* Setting new assignment results false as return value and overwriting - true.
|
|
923
943
|
*
|
|
924
944
|
* @param {object} node Assignee
|
|
@@ -1114,7 +1134,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1114
1134
|
const lhsArt = lhs._art || lhs.ref && !lhs.$scope && inspectRef(location.concat(i)).art;
|
|
1115
1135
|
const rhsArt = rhs._art || rhs.ref && !rhs.$scope && inspectRef(location.concat(i+2)).art;
|
|
1116
1136
|
const lhsIsVal = (lhs.val !== undefined);
|
|
1117
|
-
// if ever rhs should be
|
|
1137
|
+
// if ever rhs should be allowed to be a value uncomment this
|
|
1118
1138
|
const rhsIsVal = (rhs === 'null' /*|| rhs.val !== undefined*/);
|
|
1119
1139
|
|
|
1120
1140
|
// lhs & rhs must be expandable types (structures or managed associations)
|
|
@@ -1300,8 +1320,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1300
1320
|
}
|
|
1301
1321
|
|
|
1302
1322
|
function getType(art) {
|
|
1303
|
-
const
|
|
1304
|
-
return Object.keys(
|
|
1323
|
+
const effArt = effectiveType(art);
|
|
1324
|
+
return Object.keys(effArt).length ? effArt : art.type;
|
|
1305
1325
|
}
|
|
1306
1326
|
|
|
1307
1327
|
function isExpandable(art) {
|
|
@@ -230,7 +230,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
230
230
|
|
|
231
231
|
/*
|
|
232
232
|
Add an artificial QA for each mixin definition. This QA completes the QAT
|
|
233
|
-
|
|
233
|
+
data-structure that requires a QA at the rootQat before starting the join generation.
|
|
234
234
|
This QA is marked as 'mixin' which indicates that the paths of the ON condition must
|
|
235
235
|
not receive the usual source and target table alias (which is used for generic associations)
|
|
236
236
|
but instead just use the rootQA of the individual ON condition paths. These paths are
|
|
@@ -684,7 +684,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
684
684
|
//env.assocStack.includes(fwdAssoc) => recursion
|
|
685
685
|
if(env.assocStack.length === 2) {
|
|
686
686
|
// reuse (ugly) error message from forHana
|
|
687
|
-
error(null, env.assocStack[0].location,
|
|
687
|
+
error(null, [env.assocStack[0].location,env.assocStack[0]],
|
|
688
688
|
{ name: '$self', id: '$self' },
|
|
689
689
|
'An association that uses $(NAME) in its ON-condition can\'t be compared to $(ID)');
|
|
690
690
|
// don't check these paths again
|
|
@@ -706,6 +706,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
706
706
|
}
|
|
707
707
|
|
|
708
708
|
function cloneOnCondExprTree(expr) {
|
|
709
|
+
// TODO: This function is not covered by an tests, only cloneOnCondExprStream is.
|
|
709
710
|
// keep parentheses intact
|
|
710
711
|
if(Array.isArray(expr))
|
|
711
712
|
return expr.map(cloneOnCondition);
|
|
@@ -1126,7 +1127,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1126
1127
|
for(let fkn in element.foreignKeys)
|
|
1127
1128
|
{
|
|
1128
1129
|
let fk = element.foreignKeys[fkn];
|
|
1129
|
-
// once a fk is to be followed, treat all sub
|
|
1130
|
+
// once a fk is to be followed, treat all sub-paths as srcSide, this will add fk.name.id only
|
|
1130
1131
|
if(srcSide)
|
|
1131
1132
|
paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fk.name.id, fk.targetElement.path.map(ps => ps.id).join(pathDelimiter)));
|
|
1132
1133
|
else
|
|
@@ -1199,7 +1200,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1199
1200
|
/*
|
|
1200
1201
|
Munch path steps and append them to a path string until an
|
|
1201
1202
|
assoc step is found. The assoc path step is also appended
|
|
1202
|
-
to the path string. If no assoc path step has
|
|
1203
|
+
to the path string. If no assoc path step has occurred, all
|
|
1203
1204
|
path steps are added to the path string and tail is empty.
|
|
1204
1205
|
|
|
1205
1206
|
Return assocPathStep, the remaining tail path and the path string
|
|
@@ -1219,7 +1220,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1219
1220
|
|
|
1220
1221
|
/*
|
|
1221
1222
|
Substitute the n first path steps of a given path against a FK alias name.
|
|
1222
|
-
Resolve a foreign key of a
|
|
1223
|
+
Resolve a foreign key of a managed association by following the n first
|
|
1223
1224
|
path steps. Longest path matches:
|
|
1224
1225
|
Example: fk tuple { a.b, a.b.c, a.b.e },
|
|
1225
1226
|
path: a.b.c.d.e.f: FK a.b.c is found, even if FK a.b is one level higher in the prefix tree.
|
|
@@ -1866,7 +1867,7 @@ function walkPath(node, env)
|
|
|
1866
1867
|
/*
|
|
1867
1868
|
NOTE: As long as association path steps are not allowed in filters,
|
|
1868
1869
|
it is not required to walk over filter expressions.
|
|
1869
|
-
Simple filter paths are rewritten
|
|
1870
|
+
Simple filter paths are rewritten in createJoinTree (first filter)
|
|
1870
1871
|
and createJoinQA (subsequent one that belong to the ON condition).
|
|
1871
1872
|
|
|
1872
1873
|
If the filter becomes JOIN relevant, default FILTERS (part of the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"root": true,
|
|
3
3
|
"plugins": ["sonarjs", "jsdoc"],
|
|
4
|
-
"extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended"
|
|
4
|
+
"extends": ["plugin:jsdoc/recommended", "../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended"],
|
|
5
5
|
"rules": {
|
|
6
6
|
"prefer-const": "error",
|
|
7
7
|
"quotes": ["error", "single", "avoid-escape"],
|
|
@@ -27,7 +27,9 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
|
|
|
27
27
|
}
|
|
28
28
|
else if (artifact.kind === 'entity' || artifact.kind === 'aspect') {
|
|
29
29
|
forEachMemberRecursively(artifact, (element) => {
|
|
30
|
-
|
|
30
|
+
// Calculated elements, but simple references are ignored for on-read.
|
|
31
|
+
// casts() are also computed. In CSN, they appear next to a `.ref`.
|
|
32
|
+
if (element.value && (!element.value.ref || element.value.cast || element.value.stored))
|
|
31
33
|
setAnnotationIfNotDefined(element, '@Core.Computed', true);
|
|
32
34
|
}, path);
|
|
33
35
|
}
|
|
@@ -95,7 +97,7 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
|
|
|
95
97
|
return getAncestor(base.SELECT.elements[name], name, base.SELECT);
|
|
96
98
|
}
|
|
97
99
|
else if (base.ref) {
|
|
98
|
-
let artifact = artifactRef(base);
|
|
100
|
+
let artifact = artifactRef.from(base);
|
|
99
101
|
if (artifact.target)
|
|
100
102
|
artifact = artifactRef(artifact.target);
|
|
101
103
|
return artifact.elements[name];
|
|
@@ -145,7 +147,7 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
|
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
/**
|
|
148
|
-
*
|
|
150
|
+
* Returns true, if the given columns element needs to be annotated with @Core.Computed.
|
|
149
151
|
*
|
|
150
152
|
* @param {CSN.Column} column
|
|
151
153
|
* @returns {boolean}
|
|
@@ -153,9 +155,9 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
|
|
|
153
155
|
function needsCoreComputed( column ) {
|
|
154
156
|
return column &&
|
|
155
157
|
(
|
|
156
|
-
column.xpr || column.list || column.func || column.val !== undefined || column.param ||
|
|
158
|
+
column.xpr || column.list || column.func || column.val !== undefined || column['#'] !== undefined || column.param ||
|
|
157
159
|
column.SELECT || column.SET ||
|
|
158
|
-
column.ref && [ '$at', '$valid', '$now', '$user', '$session' ].includes(column.ref[0])
|
|
160
|
+
column.ref && [ '$at', '$valid', '$now', '$user', '$session', '$parameters' ].includes(column.ref[0])
|
|
159
161
|
);
|
|
160
162
|
}
|
|
161
163
|
|