@sap/cds-compiler 2.15.2 → 2.15.8
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 +29 -0
- package/lib/api/main.js +44 -1
- package/lib/base/message-registry.js +12 -4
- package/lib/compiler/populate.js +30 -12
- package/lib/edm/annotations/genericTranslation.js +60 -6
- package/lib/edm/annotations/preprocessAnnotations.js +6 -2
- package/lib/edm/csn2edm.js +7 -7
- package/lib/edm/edm.js +9 -3
- package/lib/edm/edmPreprocessor.js +14 -9
- package/lib/gen/Dictionary.json +8 -6
- package/lib/json/from-csn.js +1 -0
- package/lib/json/to-csn.js +4 -2
- package/lib/render/toSql.js +3 -3
- package/lib/transform/odata/toFinalBaseType.js +3 -1
- package/lib/transform/translateAssocsToJoins.js +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@
|
|
|
7
7
|
Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
|
|
8
8
|
The compiler behavior concerning `beta` features can change at any time without notice.
|
|
9
9
|
|
|
10
|
+
## Version 2.15.8 - 2022-08-02
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- to.edm(x): Nested `@UI.TextArrangement` has precedence over `@TextArrangement` shortcut annotation for `@Common.Text`.
|
|
15
|
+
- to.hdi.migration:
|
|
16
|
+
+ Respect option `disableHanaComments` when rendering the `ALTER` statements
|
|
17
|
+
+ Doc comments rendered the _full doc comment_ instead of only the first paragraph, as `to.hdi` does.
|
|
18
|
+
- compiler: An association's cardinality was lost for associations published in projections.
|
|
19
|
+
|
|
20
|
+
## Version 2.15.6 - 2022-07-26
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Annotations on sub-elements were lost during re-compilation.
|
|
25
|
+
|
|
26
|
+
## Version 2.15.4 - 2022-06-09
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- for.odata:
|
|
31
|
+
+ Fix derived type to scalar type resolution with intermediate `many`.
|
|
32
|
+
- to.edm(x):
|
|
33
|
+
+ (V4 structured) Fix key paths in combination with `--odata-foreign-keys`.
|
|
34
|
+
+ Add `Edm.PrimitiveType` to `@odata.Type`.
|
|
35
|
+
+ (V4 JSON) Render constant expressions for `Edm.Stream` and `Edm.Untyped`.
|
|
36
|
+
+ Fix a bug in target path calculation for `NavigationPropertyBinding`s to external references.
|
|
37
|
+
+ Render inner annotations even if `$value` is missing.
|
|
38
|
+
- Update OData vocabularies 'Common', 'UI'.
|
|
10
39
|
|
|
11
40
|
## Version 2.15.2 - 2022-05-12
|
|
12
41
|
|
package/lib/api/main.js
CHANGED
|
@@ -36,7 +36,7 @@ const propertyToCheck = {
|
|
|
36
36
|
const { cloneCsnNonDict } = require('../model/csnUtils');
|
|
37
37
|
const { toHdbcdsSource } = require('../render/toHdbcds');
|
|
38
38
|
const { ModelError } = require('../base/error');
|
|
39
|
-
const { forEach } = require('../utils/objectUtils');
|
|
39
|
+
const { forEach, forEachKey } = require('../utils/objectUtils');
|
|
40
40
|
|
|
41
41
|
const relevantGeneralOptions = [ /* for future generic options */ ];
|
|
42
42
|
const relevantOdataOptions = [ 'sqlMapping', 'odataFormat' ];
|
|
@@ -639,6 +639,7 @@ function publishCsnProcessor( processor, _name ) {
|
|
|
639
639
|
*/
|
|
640
640
|
function api( csn, options = {}, ...args ) {
|
|
641
641
|
try {
|
|
642
|
+
checkOutdatedOptions( options );
|
|
642
643
|
return processor( csn, options, ...args );
|
|
643
644
|
}
|
|
644
645
|
catch (err) {
|
|
@@ -662,6 +663,48 @@ function publishCsnProcessor( processor, _name ) {
|
|
|
662
663
|
}
|
|
663
664
|
}
|
|
664
665
|
|
|
666
|
+
// Note: No toCsn, because @sap/cds may still use it (2022-06-15)
|
|
667
|
+
const oldBackendOptionNames = [ 'toSql', 'toOdata', 'toHana', 'forHana' ];
|
|
668
|
+
/**
|
|
669
|
+
* Checks if outdated options are used and if so, throw a compiler error.
|
|
670
|
+
* These include:
|
|
671
|
+
* - magicVars (now variableReplacements)
|
|
672
|
+
* - toOdata/toSql/toHana/forHana -> now flat options
|
|
673
|
+
*
|
|
674
|
+
* @param {CSN.Options} options Backend options
|
|
675
|
+
*/
|
|
676
|
+
function checkOutdatedOptions(options) {
|
|
677
|
+
if (!options)
|
|
678
|
+
return;
|
|
679
|
+
const { warning } = makeMessageFunction(null, options, 'api');
|
|
680
|
+
|
|
681
|
+
// This warning has been emitted once, we don't need to emit it again.
|
|
682
|
+
if (options.messages && options.messages.some(m => m.messageId === 'api-invalid-option'))
|
|
683
|
+
return;
|
|
684
|
+
|
|
685
|
+
for (const name of oldBackendOptionNames) {
|
|
686
|
+
if (typeof options[name] === 'object') // may be a boolean due to internal options
|
|
687
|
+
warning('api-invalid-option', null, { '#': 'std', name });
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (options.magicVars)
|
|
691
|
+
warning('api-invalid-option', null, { '#': 'magicVars' });
|
|
692
|
+
|
|
693
|
+
// Don't check `options.magicVars`. It's likely that the user renamed `magicVars` but
|
|
694
|
+
// forgot about user -> $user and locale -> $user.locale
|
|
695
|
+
if (options.variableReplacements) {
|
|
696
|
+
if (options.variableReplacements.user)
|
|
697
|
+
warning('api-invalid-option', null, { '#': 'user' });
|
|
698
|
+
if (options.variableReplacements.locale)
|
|
699
|
+
warning('api-invalid-option', null, { '#': 'locale' });
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
forEachKey(options.variableReplacements || {}, (name) => {
|
|
703
|
+
if (!name.startsWith('$') && name !== 'user' && name !== 'locale')
|
|
704
|
+
warning('api-invalid-option', null, { '#': 'noDollar', name });
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
665
708
|
|
|
666
709
|
/**
|
|
667
710
|
* Option format used by the old API, where they are grouped thematically.
|
|
@@ -158,12 +158,12 @@ const centralMessages = {
|
|
|
158
158
|
'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
|
|
159
159
|
'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
|
|
160
160
|
'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
|
|
161
|
-
'odata-spec-violation-id': { severity: 'Error' },
|
|
162
|
-
'odata-spec-violation-type': { severity: 'Error', configurableFor:
|
|
161
|
+
'odata-spec-violation-id': { severity: 'Error', configurableFor: true },
|
|
162
|
+
'odata-spec-violation-type': { severity: 'Error', configurableFor: true },
|
|
163
163
|
'odata-spec-violation-type-unknown': { severity: 'Warning' },
|
|
164
164
|
'odata-spec-violation-no-key': { severity: 'Warning' },
|
|
165
|
-
'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
|
|
166
|
-
'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
|
|
165
|
+
'odata-spec-violation-key-array': { severity: 'Error', configurableFor: true }, // more than 30 chars
|
|
166
|
+
'odata-spec-violation-key-null': { severity: 'Error', configurableFor: true }, // more than 30 chars
|
|
167
167
|
'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
|
|
168
168
|
'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
|
|
169
169
|
};
|
|
@@ -193,6 +193,14 @@ for (const oldName in oldMessageIds) {
|
|
|
193
193
|
|
|
194
194
|
// For messageIds, where no text has been provided via code (central def)
|
|
195
195
|
const centralMessageTexts = {
|
|
196
|
+
'api-invalid-option': {
|
|
197
|
+
std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
|
|
198
|
+
magicVars: 'Option “magicVars” is deprecated! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
|
|
199
|
+
user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
|
|
200
|
+
locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
|
|
201
|
+
'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
|
|
202
|
+
},
|
|
203
|
+
|
|
196
204
|
'anno-duplicate': 'Duplicate assignment with $(ANNO)',
|
|
197
205
|
'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
|
|
198
206
|
'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
|
package/lib/compiler/populate.js
CHANGED
|
@@ -109,6 +109,8 @@ function populate( model ) {
|
|
|
109
109
|
function traverseElementEnvironments( art ) {
|
|
110
110
|
populateView( art );
|
|
111
111
|
environment( art );
|
|
112
|
+
if (art.elements$)
|
|
113
|
+
mergeSpecifiedElements(art);
|
|
112
114
|
forEachMember( art, traverseElementEnvironments );
|
|
113
115
|
}
|
|
114
116
|
|
|
@@ -425,8 +427,6 @@ function populate( model ) {
|
|
|
425
427
|
setLink( view, '_status', '_query' );
|
|
426
428
|
// must be run in order “sub query in FROM first”:
|
|
427
429
|
traverseQueryPost( view.query, null, populateQuery );
|
|
428
|
-
if (view.elements$) // specified elements
|
|
429
|
-
mergeSpecifiedElements( view );
|
|
430
430
|
if (!view.$entity) {
|
|
431
431
|
model._entities.push( view );
|
|
432
432
|
view.$entity = ++model.$entity;
|
|
@@ -435,14 +435,25 @@ function populate( model ) {
|
|
|
435
435
|
}
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Merge _specified_ elements with _inferred_ elements in the given view/element,
|
|
440
|
+
* where specified elements can appear through CSN.
|
|
441
|
+
*
|
|
442
|
+
* We only copy annotations, since they are not part of `columns`,
|
|
443
|
+
* but only appear in `elements` in CSN.
|
|
444
|
+
*
|
|
445
|
+
* This is important to ensure re-compilability.
|
|
446
|
+
*
|
|
447
|
+
* @param art
|
|
448
|
+
*/
|
|
449
|
+
function mergeSpecifiedElements( art ) {
|
|
439
450
|
// Later we use specified elements as proxies to inferred of leading query
|
|
440
451
|
// (No, we probably do not.)
|
|
441
|
-
for (const id in
|
|
442
|
-
const ielem =
|
|
443
|
-
const selem =
|
|
452
|
+
for (const id in art.elements) {
|
|
453
|
+
const ielem = art.elements[id]; // inferred element
|
|
454
|
+
const selem = art.elements$[id]; // specified element
|
|
444
455
|
if (!selem) {
|
|
445
|
-
info( 'query-missing-element', [ ielem.name.location,
|
|
456
|
+
info( 'query-missing-element', [ ielem.name.location, art ], { id },
|
|
446
457
|
'Element $(ID) is missing in specified elements' );
|
|
447
458
|
}
|
|
448
459
|
else {
|
|
@@ -452,13 +463,20 @@ function populate( model ) {
|
|
|
452
463
|
ielem[prop] = selem[prop];
|
|
453
464
|
}
|
|
454
465
|
selem.$replacement = true;
|
|
466
|
+
if (selem.elements) {
|
|
467
|
+
setLink(ielem, 'elements$', selem.elements);
|
|
468
|
+
delete selem.elements;
|
|
469
|
+
}
|
|
455
470
|
}
|
|
456
471
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
472
|
+
// Without element expansion, we can't merge nested elements.
|
|
473
|
+
if (art.kind === 'entity' || enableExpandElements) {
|
|
474
|
+
for (const id in art.elements$) {
|
|
475
|
+
const selem = art.elements$[id]; // specified element
|
|
476
|
+
if (!selem.$replacement) {
|
|
477
|
+
error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
|
|
478
|
+
'Element $(ID) does not result from the query' );
|
|
479
|
+
}
|
|
462
480
|
}
|
|
463
481
|
}
|
|
464
482
|
}
|
|
@@ -333,7 +333,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
333
333
|
function handleNestedElements(objname, baseElemName, elementsObj) {
|
|
334
334
|
if(!elementsObj) return;
|
|
335
335
|
Object.entries(elementsObj).forEach(([elemName, element]) => {
|
|
336
|
-
if (Object.keys(element).filter( x => x
|
|
336
|
+
if (Object.keys(element).filter( x => x[0] === '@' ).filter(filterKnownVocabularies).length > 0) {
|
|
337
337
|
message(warning, null, `annotations at nested elements are not yet supported, object ${objname}, element ${baseElemName}.${elemName}`);
|
|
338
338
|
}
|
|
339
339
|
|
|
@@ -437,11 +437,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
437
437
|
|
|
438
438
|
// Filter unknown toplevel annotations
|
|
439
439
|
// Final filtering of all annotations is done in handleTerm
|
|
440
|
-
|
|
440
|
+
|
|
441
|
+
let annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
441
442
|
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
442
|
-
|
|
443
|
+
let knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
443
444
|
if (knownAnnos.length === 0) return;
|
|
444
445
|
|
|
446
|
+
if(rewriteInnerAnnotations()) {
|
|
447
|
+
annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
448
|
+
knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
449
|
+
if (knownAnnos.length === 0) return;
|
|
450
|
+
}
|
|
445
451
|
const prefixTree = createPrefixTree();
|
|
446
452
|
|
|
447
453
|
// usually, for a given carrier there is one target
|
|
@@ -610,6 +616,51 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
610
616
|
*/
|
|
611
617
|
}
|
|
612
618
|
|
|
619
|
+
function rewriteInnerAnnotations() {
|
|
620
|
+
let rc = false;
|
|
621
|
+
for (let a of knownAnnos) {
|
|
622
|
+
const [ prefix, innerAnnotation ] = a.split('.@');
|
|
623
|
+
/*
|
|
624
|
+
New inner annotation (de-)structuring of the core compiler to make
|
|
625
|
+
$value arrays extendable via ellipsis
|
|
626
|
+
@anno: { $value: [ ... ], @innerAnno: ... } is now cracked up by
|
|
627
|
+
the core compiler into:
|
|
628
|
+
@anno: [ ...]
|
|
629
|
+
@anno.@innerAnno: ...
|
|
630
|
+
|
|
631
|
+
Conflict handling if $value is present:
|
|
632
|
+
@anno
|
|
633
|
+
@anno.$value
|
|
634
|
+
@anno.@innerAnno
|
|
635
|
+
|
|
636
|
+
@anno has precedence (as it was before this change) but now
|
|
637
|
+
@anno.$value is overwritten with @anno and the inner annotations
|
|
638
|
+
are applied.
|
|
639
|
+
|
|
640
|
+
Trigger is always the inner annotation, if no inner annotation
|
|
641
|
+
is available, @anno has precedence.
|
|
642
|
+
|
|
643
|
+
Insert $value into $edmJson with inner annotation as well.
|
|
644
|
+
*/
|
|
645
|
+
if(innerAnnotation) {
|
|
646
|
+
if(carrier[prefix]) {
|
|
647
|
+
const valPrefix = prefix + '.$value';
|
|
648
|
+
carrier[valPrefix] = carrier[prefix];
|
|
649
|
+
delete carrier[prefix];
|
|
650
|
+
rc = true;
|
|
651
|
+
}
|
|
652
|
+
const edmJsonPrefix = prefix + '.$edmJson';
|
|
653
|
+
if(carrier[edmJsonPrefix]) {
|
|
654
|
+
const valPrefix = prefix + '.$value.$edmJson';
|
|
655
|
+
carrier[valPrefix] = carrier[edmJsonPrefix];
|
|
656
|
+
delete carrier[edmJsonPrefix];
|
|
657
|
+
rc = true;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return rc;
|
|
662
|
+
}
|
|
663
|
+
|
|
613
664
|
function createPrefixTree() {
|
|
614
665
|
// in csn, all annotations are flattened
|
|
615
666
|
// => values can be - primitive values (string, number)
|
|
@@ -619,6 +670,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
619
670
|
// by building a "prefix tree" for the annotations attached to the carrier
|
|
620
671
|
// see example at definition of function mergePathStepsIntoPrefixTree
|
|
621
672
|
const prefixTree = {};
|
|
673
|
+
|
|
622
674
|
for (let a of knownAnnos) {
|
|
623
675
|
// remove leading @ and split at "."
|
|
624
676
|
// stop splitting at ".@" (used for nested annotations)
|
|
@@ -845,7 +897,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
845
897
|
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
|
|
846
898
|
oTarget.append(handleEdmJson(cAnnoValue['$edmJson'], context));
|
|
847
899
|
}
|
|
848
|
-
else if ( Object.keys(cAnnoValue).filter( x => x
|
|
900
|
+
else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
849
901
|
// object consists only of properties starting with "@"
|
|
850
902
|
message(warning, context, 'nested annotations without corresponding base annotation');
|
|
851
903
|
}
|
|
@@ -952,6 +1004,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
952
1004
|
// Edm.Decimal -> Decimal
|
|
953
1005
|
// integer tpye -> Int
|
|
954
1006
|
function handleSimpleValue(val, dTypeName, context) {
|
|
1007
|
+
// these types must be represented as "String" values in XML:
|
|
1008
|
+
const castToXmlString = [ 'Edm.PrimitiveType', 'Edm.Stream', 'Edm.Untyped' ];
|
|
955
1009
|
// caller already made sure that val is neither object nor array
|
|
956
1010
|
dTypeName = resolveType(dTypeName);
|
|
957
1011
|
|
|
@@ -989,12 +1043,12 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
989
1043
|
message(warning, context, `found String, but expected enum type ${ dTypeName }`);
|
|
990
1044
|
typeName = 'EnumMember';
|
|
991
1045
|
}
|
|
992
|
-
else if (dTypeName && dTypeName.startsWith('Edm.') && dTypeName
|
|
1046
|
+
else if (dTypeName && dTypeName.startsWith('Edm.') && !castToXmlString.includes(dTypeName)) {
|
|
993
1047
|
// this covers also all paths
|
|
994
1048
|
typeName = dTypeName.substring(4);
|
|
995
1049
|
}
|
|
996
1050
|
else {
|
|
997
|
-
if(dTypeName == undefined ||
|
|
1051
|
+
if(dTypeName == undefined || castToXmlString.some(t => t === dTypeName))
|
|
998
1052
|
dTypeName = 'Edm.String';
|
|
999
1053
|
// TODO
|
|
1000
1054
|
//message(warning, context, "type is not yet handled: found String, expected type: " + dTypeName);
|
|
@@ -297,8 +297,12 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
297
297
|
|
|
298
298
|
//change the scalar anno into a "pseudo-structured" one
|
|
299
299
|
// TODO should be flattened, but then alphabetical order is destroyed
|
|
300
|
-
|
|
301
|
-
|
|
300
|
+
|
|
301
|
+
// Do not overwrite existing nested annotation values, instead give existing
|
|
302
|
+
// nested annotation precedence and remove outer annotation (always)
|
|
303
|
+
if(!carrier['@Common.Text.@UI.TextArrangement'] && textAnno) {
|
|
304
|
+
carrier['@Common.Text'] = { '$value': textAnno, '@UI.TextArrangement': value };
|
|
305
|
+
}
|
|
302
306
|
delete carrier[aName];
|
|
303
307
|
}
|
|
304
308
|
}
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -14,7 +14,7 @@ const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../mo
|
|
|
14
14
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
15
15
|
const { makeMessageFunction } = require('../base/messages');
|
|
16
16
|
const { EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm } = require('./edm.js');
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
/*
|
|
19
19
|
OData V2 spec 06/01/2017 PDF version is available from here:
|
|
20
20
|
https://msdn.microsoft.com/en-us/library/dd541474.aspx
|
|
@@ -421,7 +421,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
421
421
|
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
422
422
|
else if (options.isV2() && /^(_|[0-9])/.test(p._edmAttributes.Name)) {
|
|
423
423
|
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
424
|
-
|
|
424
|
+
message('odata-spec-violation-id', pLoc,
|
|
425
425
|
{ prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar' });
|
|
426
426
|
}
|
|
427
427
|
});
|
|
@@ -621,9 +621,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
621
621
|
message('odata-spec-violation-id', pLoc, { id: parameterName });
|
|
622
622
|
|
|
623
623
|
// only scalar or structured type in V2 (not entity)
|
|
624
|
-
if(param._type &&
|
|
625
|
-
!param._type.startsWith('Edm.') &&
|
|
626
|
-
csn.definitions[param._type] &&
|
|
624
|
+
if(param._type &&
|
|
625
|
+
!param._type.startsWith('Edm.') &&
|
|
626
|
+
csn.definitions[param._type] &&
|
|
627
627
|
!edmUtils.isStructuredType(csn.definitions[param._type]))
|
|
628
628
|
warning('odata-spec-violation-param', pLoc, { version: '2.0' });
|
|
629
629
|
|
|
@@ -933,7 +933,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
933
933
|
|
|
934
934
|
// generate the Edm.Annotations tree and append it to the corresponding schema
|
|
935
935
|
function addAnnotations() {
|
|
936
|
-
let { annos, usedVocabularies } = translate.csn2annotationEdm(
|
|
936
|
+
let { annos, usedVocabularies } = translate.csn2annotationEdm(reqDefs, serviceCsn.name, Edm, options, messageFunctions);
|
|
937
937
|
// distribute edm:Annotations into the schemas
|
|
938
938
|
// Distribute each anno into Schema
|
|
939
939
|
annos.forEach(anno => {
|
|
@@ -990,7 +990,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
990
990
|
});
|
|
991
991
|
}
|
|
992
992
|
else {
|
|
993
|
-
message('odata-spec-violation-type-unknown', pLoc,
|
|
993
|
+
message('odata-spec-violation-type-unknown', pLoc,
|
|
994
994
|
{ type:edmType });
|
|
995
995
|
}
|
|
996
996
|
}
|
package/lib/edm/edm.js
CHANGED
|
@@ -54,7 +54,7 @@ const EdmPrimitiveTypeMap = {
|
|
|
54
54
|
'Edm.GeometryMultiLineString': { v4: true, SRID: true, desc: 'Collection of line strings in a flat-earth coordinate system' },
|
|
55
55
|
'Edm.GeometryMultiPolygon': { v4: true, SRID: true, desc: 'Collection of polygons in a flat-earth coordinate system' },
|
|
56
56
|
'Edm.GeometryCollection': { v4: true, SRID: true, desc: 'Collection of arbitrary Geometry values' },
|
|
57
|
-
|
|
57
|
+
'Edm.PrimitiveType': { v4: true, desc: 'Abstract meta type' },
|
|
58
58
|
//'Edm.Untyped': { v4: true, desc: 'Abstract void type' },
|
|
59
59
|
};
|
|
60
60
|
|
|
@@ -1223,9 +1223,15 @@ function getEdm(options, messageFunctions) {
|
|
|
1223
1223
|
// short form: key: value
|
|
1224
1224
|
const inlineConstExpr =
|
|
1225
1225
|
[ 'Edm.Binary', 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Double', 'Edm.Duration', 'Edm.Guid',
|
|
1226
|
-
'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte','Edm.Single',
|
|
1226
|
+
'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte','Edm.Single', 'Edm.Stream', 'Edm.String', 'Edm.TimeOfDay',
|
|
1227
|
+
// Edm.Geo* according to https://issues.oasis-open.org/browse/ODATA-1323
|
|
1228
|
+
/* 'Edm.Geography', 'Edm.GeographyPoint', 'Edm.GeographyLineString', 'Edm.GeographyPolygon', 'Edm.GeographyMultiPoint',
|
|
1229
|
+
'Edm.GeographyMultiLineString', 'Edm.GeographyMultiPolygon', 'Edm.GeographyCollection', 'Edm.Geometry', 'Edm.GeometryPoint',
|
|
1230
|
+
'Edm.GeometryLineString', 'Edm.GeometryPolygon', 'Edm.GeometryMultiPoint', 'Edm.GeometryMultiLineString', 'Edm.GeometryMultiPolygon',
|
|
1231
|
+
'Edm.GeometryCollection',
|
|
1232
|
+
*/
|
|
1227
1233
|
/* UI.xml: defines Annotations with generic type 'Edm.PrimitiveType' */
|
|
1228
|
-
'Edm.PrimitiveType', 'Bool',
|
|
1234
|
+
'Edm.PrimitiveType', 'Edm.Untyped', 'Bool',
|
|
1229
1235
|
// Official JSON V4.01 Spec defines these paths as constant inline expression:
|
|
1230
1236
|
'AnnotationPath', 'ModelElementPath', 'NavigationPropertyPath', 'PropertyPath',
|
|
1231
1237
|
];
|
|
@@ -1455,12 +1455,9 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1455
1455
|
if(isEdmPropertyRendered(k, options) &&
|
|
1456
1456
|
!(options.isV2() && k['@Core.MediaType'])) {
|
|
1457
1457
|
if(options.isV4() && options.isStructFormat) {
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
if(options.renderForeignKeys && !k.target)
|
|
1461
|
-
def.$edmKeyPaths.push([kn]);
|
|
1462
|
-
// else produce paths (isEdmPropertyRendered() has filtered @odata.foreignKey4 already)
|
|
1463
|
-
else if(!options.renderForeignKeys)
|
|
1458
|
+
// This is structured OData ONLY
|
|
1459
|
+
// if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
|
|
1460
|
+
if(!options.renderForeignKeys || (options.renderForeignKeys && !k.target))
|
|
1464
1461
|
def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
|
|
1465
1462
|
}
|
|
1466
1463
|
// In v2/v4 flat, associations are never rendered
|
|
@@ -1543,13 +1540,13 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1543
1540
|
// Nullability
|
|
1544
1541
|
if((!elt.key && (elt.notNull === undefined || elt.notNull === false)) ||
|
|
1545
1542
|
elt.key && (elt.notNull !== undefined && elt.notNull === false)) {
|
|
1546
|
-
|
|
1543
|
+
message('odata-spec-violation-key-null', location,
|
|
1547
1544
|
{name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
|
|
1548
1545
|
}
|
|
1549
1546
|
// many
|
|
1550
1547
|
let type = elt.items || elt.type && !isBuiltinType(elt.type) && csnUtils.getFinalTypeDef(elt.type).items;
|
|
1551
1548
|
if(type) {
|
|
1552
|
-
|
|
1549
|
+
message('odata-spec-violation-key-array', location,
|
|
1553
1550
|
{name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
|
|
1554
1551
|
}
|
|
1555
1552
|
// type
|
|
@@ -1615,7 +1612,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1615
1612
|
if(isAssociationOrComposition(elt) && !elt.$touched) {
|
|
1616
1613
|
if(!elt._target.$edmTgtPaths)
|
|
1617
1614
|
setProp(elt._target, '$edmTgtPaths', []);
|
|
1618
|
-
|
|
1615
|
+
// drill into target only if
|
|
1616
|
+
// 1) target has no entity set and this assoc is not going to the container
|
|
1617
|
+
// 2) current definition and target are the same (cycle)
|
|
1618
|
+
// 3) it's no external reference
|
|
1619
|
+
if(!elt.$externalRef &&
|
|
1620
|
+
!elt._target.$hasEntitySet &&
|
|
1621
|
+
!elt._isToContainer &&
|
|
1622
|
+
curDef !== elt._target) {
|
|
1619
1623
|
// follow elements in the target but avoid cycles
|
|
1620
1624
|
setProp(elt, '$touched', true);
|
|
1621
1625
|
elt._target.$edmTgtPaths.push(newPrefix);
|
|
@@ -1654,6 +1658,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1654
1658
|
// drill into target only if
|
|
1655
1659
|
// 1) target has no entity set and this assoc is not going to the container
|
|
1656
1660
|
// 2) current definition and target are the same (cycle)
|
|
1661
|
+
// 3) it's no external reference
|
|
1657
1662
|
if(!elt.$externalRef &&
|
|
1658
1663
|
!elt._target.$hasEntitySet &&
|
|
1659
1664
|
!elt._isToContainer &&
|
package/lib/gen/Dictionary.json
CHANGED
|
@@ -658,6 +658,7 @@
|
|
|
658
658
|
"Type": "Common.FieldControlType",
|
|
659
659
|
"AppliesTo": [
|
|
660
660
|
"Property",
|
|
661
|
+
"Parameter",
|
|
661
662
|
"Record",
|
|
662
663
|
"EntityType"
|
|
663
664
|
]
|
|
@@ -3246,6 +3247,7 @@
|
|
|
3246
3247
|
"TypeNamePlural": "Edm.String",
|
|
3247
3248
|
"Title": "UI.DataFieldAbstract",
|
|
3248
3249
|
"Description": "UI.DataFieldAbstract",
|
|
3250
|
+
"Image": "Edm.Stream",
|
|
3249
3251
|
"ImageUrl": "Edm.String",
|
|
3250
3252
|
"TypeImageUrl": "Edm.String",
|
|
3251
3253
|
"Initials": "Edm.String"
|
|
@@ -3693,7 +3695,7 @@
|
|
|
3693
3695
|
"$kind": "ComplexType",
|
|
3694
3696
|
"BaseType": "UI.DataFieldAbstract",
|
|
3695
3697
|
"Properties": {
|
|
3696
|
-
"Value": "Edm.
|
|
3698
|
+
"Value": "Edm.Untyped",
|
|
3697
3699
|
"Label": "Edm.String",
|
|
3698
3700
|
"Criticality": "UI.CriticalityType",
|
|
3699
3701
|
"CriticalityRepresentation": "UI.CriticalityRepresentationType",
|
|
@@ -3704,8 +3706,8 @@
|
|
|
3704
3706
|
"$kind": "ComplexType",
|
|
3705
3707
|
"BaseType": "UI.DataField",
|
|
3706
3708
|
"Properties": {
|
|
3707
|
-
"Action": "Common.QualifiedName",
|
|
3708
3709
|
"Value": "Edm.PrimitiveType",
|
|
3710
|
+
"Action": "Common.QualifiedName",
|
|
3709
3711
|
"Label": "Edm.String",
|
|
3710
3712
|
"Criticality": "UI.CriticalityType",
|
|
3711
3713
|
"CriticalityRepresentation": "UI.CriticalityRepresentationType",
|
|
@@ -3716,10 +3718,10 @@
|
|
|
3716
3718
|
"$kind": "ComplexType",
|
|
3717
3719
|
"BaseType": "UI.DataField",
|
|
3718
3720
|
"Properties": {
|
|
3721
|
+
"Value": "Edm.PrimitiveType",
|
|
3719
3722
|
"SemanticObject": "Edm.String",
|
|
3720
3723
|
"Action": "Edm.String",
|
|
3721
3724
|
"Mapping": "Collection(Common.SemanticObjectMappingType)",
|
|
3722
|
-
"Value": "Edm.PrimitiveType",
|
|
3723
3725
|
"Label": "Edm.String",
|
|
3724
3726
|
"Criticality": "UI.CriticalityType",
|
|
3725
3727
|
"CriticalityRepresentation": "UI.CriticalityRepresentationType",
|
|
@@ -3730,8 +3732,8 @@
|
|
|
3730
3732
|
"$kind": "ComplexType",
|
|
3731
3733
|
"BaseType": "UI.DataField",
|
|
3732
3734
|
"Properties": {
|
|
3733
|
-
"Target": "Edm.NavigationPropertyPath",
|
|
3734
3735
|
"Value": "Edm.PrimitiveType",
|
|
3736
|
+
"Target": "Edm.NavigationPropertyPath",
|
|
3735
3737
|
"Label": "Edm.String",
|
|
3736
3738
|
"Criticality": "UI.CriticalityType",
|
|
3737
3739
|
"CriticalityRepresentation": "UI.CriticalityRepresentationType",
|
|
@@ -3742,9 +3744,9 @@
|
|
|
3742
3744
|
"$kind": "ComplexType",
|
|
3743
3745
|
"BaseType": "UI.DataField",
|
|
3744
3746
|
"Properties": {
|
|
3747
|
+
"Value": "Edm.PrimitiveType",
|
|
3745
3748
|
"Url": "Edm.String",
|
|
3746
3749
|
"UrlContentType": "Edm.String",
|
|
3747
|
-
"Value": "Edm.PrimitiveType",
|
|
3748
3750
|
"Label": "Edm.String",
|
|
3749
3751
|
"Criticality": "UI.CriticalityType",
|
|
3750
3752
|
"CriticalityRepresentation": "UI.CriticalityRepresentationType",
|
|
@@ -3755,8 +3757,8 @@
|
|
|
3755
3757
|
"$kind": "ComplexType",
|
|
3756
3758
|
"BaseType": "UI.DataField",
|
|
3757
3759
|
"Properties": {
|
|
3758
|
-
"Actions": "Collection(UI.DataField)",
|
|
3759
3760
|
"Value": "Edm.PrimitiveType",
|
|
3761
|
+
"Actions": "Collection(UI.DataField)",
|
|
3760
3762
|
"Label": "Edm.String",
|
|
3761
3763
|
"Criticality": "UI.CriticalityType",
|
|
3762
3764
|
"CriticalityRepresentation": "UI.CriticalityRepresentationType",
|
package/lib/json/from-csn.js
CHANGED
|
@@ -102,6 +102,7 @@ const ourpropsRegex = /^[_$]?[a-zA-Z]+[0-9]*$/;
|
|
|
102
102
|
const typeProperties = [
|
|
103
103
|
// do not include CSN v0.1.0 properties here:
|
|
104
104
|
'target', 'elements', 'enum', 'items',
|
|
105
|
+
'cardinality', // for association publishing in views
|
|
105
106
|
'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
|
|
106
107
|
'keys', 'on', // only with 'target'
|
|
107
108
|
];
|
package/lib/json/to-csn.js
CHANGED
|
@@ -184,9 +184,11 @@ const propertyOrder = (function orderPositions() {
|
|
|
184
184
|
}());
|
|
185
185
|
|
|
186
186
|
// sync with definition in from-csn.js:
|
|
187
|
+
// Note: Order here is also the property order in CSN.
|
|
187
188
|
const typeProperties = [
|
|
188
|
-
'target', 'elements', 'enum', 'items',
|
|
189
|
-
'
|
|
189
|
+
'target', 'elements', 'enum', 'items',
|
|
190
|
+
'cardinality', // for association publishing in views
|
|
191
|
+
'type', 'length', 'precision', 'scale', 'srid', 'localized', // TODO: notNull?
|
|
190
192
|
'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
|
|
191
193
|
];
|
|
192
194
|
|
package/lib/render/toSql.js
CHANGED
|
@@ -200,7 +200,7 @@ function toSqlDdl(csn, options) {
|
|
|
200
200
|
Render comment string.
|
|
201
201
|
*/
|
|
202
202
|
comment(comment) {
|
|
203
|
-
return comment && renderStringForSql(comment, options.sqlDialect) || 'NULL';
|
|
203
|
+
return comment && renderStringForSql(getHanaComment({ doc: comment }), options.sqlDialect) || 'NULL';
|
|
204
204
|
},
|
|
205
205
|
/*
|
|
206
206
|
Alter SQL snippet for entity.
|
|
@@ -461,7 +461,7 @@ function toSqlDdl(csn, options) {
|
|
|
461
461
|
// Change entity properties
|
|
462
462
|
if (migration.properties) {
|
|
463
463
|
for (const [ prop, def ] of Object.entries(migration.properties)) {
|
|
464
|
-
if (prop === 'doc') {
|
|
464
|
+
if (prop === 'doc' && !options.disableHanaComments) { // def.new may be `null`
|
|
465
465
|
const alterComment = render.alterEntityComment(tableName, def.new);
|
|
466
466
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
467
467
|
}
|
|
@@ -527,7 +527,7 @@ function toSqlDdl(csn, options) {
|
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
-
if (def.old.doc !== def.new.doc) {
|
|
530
|
+
if (!options.disableHanaComments && def.old.doc !== def.new.doc) {
|
|
531
531
|
const eltStrOldNoDoc = getEltStrNoProps(def.old, eltName, 'doc');
|
|
532
532
|
const eltStrNewNoDoc = getEltStrNoProps(def.new, eltName, 'doc');
|
|
533
533
|
if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
|
|
@@ -148,7 +148,9 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
148
148
|
// example in actions: 'action act() return Primitive; type Primitive: array of String;'
|
|
149
149
|
const currService = csnUtils.getServiceName(defName);
|
|
150
150
|
const finalType = csnUtils.getFinalTypeDef(node.type);
|
|
151
|
-
if (finalType.items &&
|
|
151
|
+
if (finalType.items &&
|
|
152
|
+
(isBuiltinType(finalType.items.type) || isBuiltinType(csnUtils.getFinalBaseType(finalType.items.type))))
|
|
153
|
+
{
|
|
152
154
|
if (!isArtifactInService(node.type, currService) || !isV4) {
|
|
153
155
|
node.items = finalType.items;
|
|
154
156
|
delete node.type;
|
|
@@ -1818,6 +1818,10 @@ function walkPath(node, env)
|
|
|
1818
1818
|
// or that are parameters ($parameters or escaped paths (':')
|
|
1819
1819
|
//path.length && path[ path.length-1 ]._artifact
|
|
1820
1820
|
const art = path && path.length && path[path.length-1]._artifact;
|
|
1821
|
+
|
|
1822
|
+
// regardless of the position in the query, ignore paths that have virtual path steps
|
|
1823
|
+
if(art && path.some(ps => ps._artifact && ps._artifact.virtual && ps._artifact.virtual.val))
|
|
1824
|
+
return path;
|
|
1821
1825
|
if(art && !internalArtifactKinds.includes(art.kind))
|
|
1822
1826
|
{
|
|
1823
1827
|
if(env.callback)
|