@sap/cds-compiler 2.10.4 → 2.12.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 +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
|
@@ -207,7 +207,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
207
207
|
// Note: we assume that all objects ly flat in the service, i.e. objName always
|
|
208
208
|
// looks like <service name, can contain dots>.<id>
|
|
209
209
|
forEachDefinition(csn, (object, objName) => {
|
|
210
|
-
if(objName
|
|
210
|
+
if (objName === serviceName || objName.startsWith(serviceName + '.')) {
|
|
211
211
|
if (object.kind === 'action' || object.kind === 'function') {
|
|
212
212
|
handleAction(objName, object, null);
|
|
213
213
|
}
|
|
@@ -505,8 +505,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
505
505
|
let testToAlternativeEdmTarget = null; // if true, assign to alternative Edm Target
|
|
506
506
|
|
|
507
507
|
if (carrier.kind === 'entity' || carrier.kind === 'view') {
|
|
508
|
-
// If AppliesTo=[EntitySet/Singleton, EntityType], EntitySet/Singleton has precedence
|
|
509
|
-
testToAlternativeEdmTarget = (x =>
|
|
508
|
+
// If AppliesTo=[EntitySet/Singleton/Collection, EntityType], EntitySet/Singleton/Collection has precedence
|
|
509
|
+
testToAlternativeEdmTarget = (x => {
|
|
510
|
+
if(options.isV2()) {
|
|
511
|
+
return ['Singleton', 'EntitySet', 'Collection'].some(y => x.includes(y));
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
return edmUtils.isSingleton(carrier)
|
|
515
|
+
? x.includes('Singleton')
|
|
516
|
+
: ['EntitySet', 'Collection'].some(y => x.includes(y));
|
|
517
|
+
}
|
|
518
|
+
});
|
|
510
519
|
testToStandardEdmTarget = (x => x ? x.includes('EntityType') : true);
|
|
511
520
|
// if carrier has an alternate 'entitySetName' use this instead of EdmTargetName
|
|
512
521
|
// (see edmPreprocessor.initializeParameterizedEntityOrView(), where parameterized artifacts
|
|
@@ -534,15 +543,21 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
534
543
|
alternativeEdmTargetName = edmTargetName;
|
|
535
544
|
hasAlternativeCarrier = true; // EntityContainer is always available
|
|
536
545
|
}
|
|
537
|
-
|
|
546
|
+
//element => decide if navprop or normal property
|
|
538
547
|
else if(!carrier.kind) {
|
|
539
|
-
|
|
548
|
+
// if appliesTo is undefined, return true
|
|
540
549
|
if(carrier.target) {
|
|
541
|
-
testToStandardEdmTarget = (x=> x
|
|
550
|
+
testToStandardEdmTarget = (x => x
|
|
551
|
+
? x.includes('NavigationProperty') ||
|
|
552
|
+
carrier.cardinality && carrier.cardinality.max === '*' && x.includes('Collection')
|
|
553
|
+
: true);
|
|
542
554
|
}
|
|
543
555
|
else {
|
|
544
556
|
// this might be more precise if handleAnnotation would know more about the carrier
|
|
545
|
-
testToStandardEdmTarget = (x => x
|
|
557
|
+
testToStandardEdmTarget = (x => x
|
|
558
|
+
? ['Parameter', 'Property'].some(y => x.includes(y) ||
|
|
559
|
+
carrier._isCollection && x.includes('Collection'))
|
|
560
|
+
: true);
|
|
546
561
|
}
|
|
547
562
|
}
|
|
548
563
|
return [
|
|
@@ -851,7 +866,6 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
851
866
|
else if (!expectedType['Members'].includes(enumValue)) {
|
|
852
867
|
message(warning, context, `enumeration type ${ dTypeName } has no value ${ enumValue }`);
|
|
853
868
|
}
|
|
854
|
-
return;
|
|
855
869
|
}
|
|
856
870
|
|
|
857
871
|
// cAnnoValue: array
|
|
@@ -865,8 +879,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
865
879
|
}
|
|
866
880
|
|
|
867
881
|
let index = 0;
|
|
868
|
-
for (
|
|
869
|
-
context.stack.push('[' + index
|
|
882
|
+
for (const e of cAnnoValue) {
|
|
883
|
+
context.stack.push('[' + index + ']');
|
|
884
|
+
index++;
|
|
870
885
|
if (e['#']) {
|
|
871
886
|
checkEnumValue(e['#'], dTypeName, context);
|
|
872
887
|
}
|
|
@@ -901,7 +916,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
901
916
|
}
|
|
902
917
|
else {
|
|
903
918
|
// replace all occurrences of '.' by '/' up to first '@'
|
|
904
|
-
val = expr.split('@').map((o,i) => (i
|
|
919
|
+
val = expr.split('@').map((o,i) => (i === 0 ? o.replace(/\./g, '/') : o)).join('@');
|
|
905
920
|
}
|
|
906
921
|
|
|
907
922
|
return {
|
|
@@ -922,9 +937,10 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
922
937
|
// caller already made sure that val is neither object nor array
|
|
923
938
|
dTypeName = resolveType(dTypeName);
|
|
924
939
|
|
|
925
|
-
if(isEnumType(dTypeName)) {
|
|
940
|
+
if (isEnumType(dTypeName)) {
|
|
926
941
|
const type = getDictType(dTypeName);
|
|
927
|
-
|
|
942
|
+
const expected = type.Members.map(m => `"#${m}"`).join(', ');
|
|
943
|
+
message(warning, context, `found non-enum value "${val}", expected ${expected} for ${dTypeName}`);
|
|
928
944
|
}
|
|
929
945
|
|
|
930
946
|
let typeName = 'String';
|
|
@@ -1119,7 +1135,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1119
1135
|
let dictPropertyTypeName = null;
|
|
1120
1136
|
if (dictProperties) {
|
|
1121
1137
|
dictPropertyTypeName = dictProperties[i];
|
|
1122
|
-
if (!dictPropertyTypeName){
|
|
1138
|
+
if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType){
|
|
1123
1139
|
message(warning, context, `record type '${ actualTypeName }' doesn't have a property '${ i }'`);
|
|
1124
1140
|
}
|
|
1125
1141
|
}
|
|
@@ -1154,8 +1170,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1154
1170
|
}
|
|
1155
1171
|
|
|
1156
1172
|
let index = 0;
|
|
1157
|
-
for (
|
|
1158
|
-
context.stack.push('[' + index
|
|
1173
|
+
for (const value of annoValue) {
|
|
1174
|
+
context.stack.push('[' + index + ']');
|
|
1175
|
+
index++
|
|
1159
1176
|
|
|
1160
1177
|
// for dealing with the single array entries we unfortunately cannot call handleValue(),
|
|
1161
1178
|
// as the values inside an array are represented differently from the values
|
|
@@ -1214,8 +1231,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1214
1231
|
return edmNode;
|
|
1215
1232
|
}
|
|
1216
1233
|
|
|
1217
|
-
if(dynExprs.length === 0) {
|
|
1218
|
-
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && Object.keys(obj).length
|
|
1234
|
+
if (dynExprs.length === 0) {
|
|
1235
|
+
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && Object.keys(obj).length === 1) {
|
|
1219
1236
|
const k = Object.keys(obj)[0];
|
|
1220
1237
|
const val = obj[k];
|
|
1221
1238
|
edmNode = new Edm.ValueThing(v, k[0] === '$' ? k.slice(1) : k, val );
|
|
@@ -236,7 +236,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
236
236
|
} else {
|
|
237
237
|
let stringFields = Object.keys(vlEntity.elements).filter(
|
|
238
238
|
x => !vlEntity.elements[x].key && vlEntity.elements[x].type === 'cds.String')
|
|
239
|
-
if (stringFields.length
|
|
239
|
+
if (stringFields.length === 1)
|
|
240
240
|
textField = stringFields[0];
|
|
241
241
|
}
|
|
242
242
|
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -142,7 +142,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
142
142
|
definitions: Object.create(null)
|
|
143
143
|
}
|
|
144
144
|
};
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
if(options.isV4()) {
|
|
147
147
|
// tunnel schema xref and servicename in options to edm.Typebase to rectify
|
|
148
148
|
// type references that are eventually also prefixed with the service schema name.
|
|
@@ -304,6 +304,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
304
304
|
warning('odata-spec-violation-namespace',
|
|
305
305
|
[ 'definitions', schema.name ], { names: reservedNames });
|
|
306
306
|
}
|
|
307
|
+
/** @type {any} */
|
|
307
308
|
const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
|
|
308
309
|
const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
|
|
309
310
|
// now namespace and alias are used to create the fullQualified(name)
|
|
@@ -390,7 +391,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
390
391
|
if(properties.length === 0) {
|
|
391
392
|
warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
|
|
392
393
|
} else if(entityCsn.$edmKeyPaths.length === 0) {
|
|
393
|
-
|
|
394
|
+
message('odata-spec-violation-no-key', loc);
|
|
394
395
|
}
|
|
395
396
|
properties.forEach(p => {
|
|
396
397
|
const pLoc = [...loc, 'elements', p.Name];
|
|
@@ -419,7 +420,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
419
420
|
/** @type {object} */
|
|
420
421
|
let containerEntry;
|
|
421
422
|
|
|
422
|
-
if(edmUtils.isSingleton(entityCsn
|
|
423
|
+
if(edmUtils.isSingleton(entityCsn) && options.isV4()) {
|
|
423
424
|
containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
|
|
424
425
|
if(entityCsn['@odata.singleton.nullable'])
|
|
425
426
|
containerEntry.Nullable= true;
|
package/lib/edm/edm.js
CHANGED
|
@@ -665,7 +665,7 @@ module.exports = function (options, error) {
|
|
|
665
665
|
json['$'+this._typeName] = this._type;
|
|
666
666
|
|
|
667
667
|
edmUtils.forAll(this, (v,p) => {
|
|
668
|
-
if (p !== 'Name' && p
|
|
668
|
+
if (p !== 'Name' && p !== this._typeName
|
|
669
669
|
// remove this line if Nullable=true becomes default
|
|
670
670
|
&& !(p === 'Nullable' && v == false))
|
|
671
671
|
{
|
|
@@ -711,7 +711,7 @@ module.exports = function (options, error) {
|
|
|
711
711
|
if(alias.length > 28) {
|
|
712
712
|
alias = alias.substr(0, 13)+ '__' +alias.substr(alias.length-13, alias.length);
|
|
713
713
|
}
|
|
714
|
-
alias = alias+'_'+c.toString().padStart(3,0);
|
|
714
|
+
alias = alias+'_'+c.toString().padStart(3, '0');
|
|
715
715
|
}
|
|
716
716
|
else if(alias.length > 32) {
|
|
717
717
|
alias = alias.substr(0, 15)+ '__' +alias.substr(alias.length-15, alias.length);
|
|
@@ -835,7 +835,7 @@ module.exports = function (options, error) {
|
|
|
835
835
|
// Nullable=false is default for EDM JSON representation 4.01
|
|
836
836
|
// When a key explicitly (!) has 'notNull = false', it stays nullable
|
|
837
837
|
return (nodeCsn._NotNullCollection !== undefined ? nodeCsn._NotNullCollection :
|
|
838
|
-
(nodeCsn.key &&
|
|
838
|
+
(nodeCsn.key && nodeCsn.notNull !== false) || nodeCsn.notNull === true);
|
|
839
839
|
}
|
|
840
840
|
|
|
841
841
|
toJSONattributes(json)
|
|
@@ -1159,7 +1159,7 @@ module.exports = function (options, error) {
|
|
|
1159
1159
|
/* short notation for Edm.Boolean, Edm.String and Edm.Float, see internal project:
|
|
1160
1160
|
edmx2csn-npm/edm-converters/blob/835d92a1aa6b0be25c56cef85e260c9188187429/lib/edmxV40ToJsonV40/README.md
|
|
1161
1161
|
*/
|
|
1162
|
-
case 'Edm.Boolean':
|
|
1162
|
+
case 'Edm.Boolean':
|
|
1163
1163
|
v = (v=='true'?true:(v=='false'?false:v));
|
|
1164
1164
|
// eslint-no-fallthrough
|
|
1165
1165
|
case 'Edm.String':
|
|
@@ -1275,8 +1275,8 @@ module.exports = function (options, error) {
|
|
|
1275
1275
|
if(this.Type)
|
|
1276
1276
|
json['@type'] = this.Type;
|
|
1277
1277
|
let keys = Object.keys(this).filter(k => k !== 'Type');
|
|
1278
|
-
for(
|
|
1279
|
-
json['$'+
|
|
1278
|
+
for(const key of keys)
|
|
1279
|
+
json['$'+key] = this[key];
|
|
1280
1280
|
}
|
|
1281
1281
|
|
|
1282
1282
|
toJSONchildren(json)
|
|
@@ -1377,7 +1377,7 @@ module.exports = function (options, error) {
|
|
|
1377
1377
|
}
|
|
1378
1378
|
|
|
1379
1379
|
toJSON()
|
|
1380
|
-
{
|
|
1380
|
+
{
|
|
1381
1381
|
// toJSON: depending on number of children unary or n-ary expr
|
|
1382
1382
|
const json = this.mergeJSONAnnotations();
|
|
1383
1383
|
const e = this._children.filter(c=>c.kind !== 'Annotation');
|
|
@@ -1459,7 +1459,7 @@ module.exports = function (options, error) {
|
|
|
1459
1459
|
return json;
|
|
1460
1460
|
}
|
|
1461
1461
|
}
|
|
1462
|
-
// LabeledElementReference is a
|
|
1462
|
+
// LabeledElementReference is a
|
|
1463
1463
|
class LabeledElementReference extends ValueThing {
|
|
1464
1464
|
constructor(v, val) {
|
|
1465
1465
|
super(v, 'LabeledElementReference', val);
|
|
@@ -39,7 +39,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
39
39
|
if (!_options)
|
|
40
40
|
throw Error('Please debug me: initializeModel must be invoked with options');
|
|
41
41
|
|
|
42
|
-
const { info, warning, error, throwWithError } = messageFunctions;
|
|
42
|
+
const { info, warning, error, message, throwWithError } = messageFunctions;
|
|
43
43
|
|
|
44
44
|
const csnUtils = getUtils(csn);
|
|
45
45
|
const {
|
|
@@ -54,7 +54,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
54
54
|
let options = validateOptions(_options);
|
|
55
55
|
|
|
56
56
|
// Fetch service definitions
|
|
57
|
-
const serviceRoots = Object.keys(csn.definitions).reduce((serviceRoots, artName) => {
|
|
57
|
+
const serviceRoots = Object.keys(csn.definitions || {}).reduce((serviceRoots, artName) => {
|
|
58
58
|
const art = csn.definitions[artName];
|
|
59
59
|
if(art.kind === 'service') {
|
|
60
60
|
serviceRoots[artName] = Object.assign(art, { name: artName });
|
|
@@ -67,6 +67,9 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
67
67
|
function whatsMyServiceRootName(n, self=true) {
|
|
68
68
|
return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
|
|
69
69
|
}
|
|
70
|
+
if(serviceRootNames.length === 0) {
|
|
71
|
+
return [serviceRoots, Object.create(null), whatsMyServiceRootName, options];
|
|
72
|
+
}
|
|
70
73
|
|
|
71
74
|
// Structural CSN inbound QA checks
|
|
72
75
|
inboundQualificationChecks();
|
|
@@ -452,8 +455,8 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
452
455
|
// propagate containment information, if containment is recursive, use parameterCsn.name as _containerEntity
|
|
453
456
|
if(entityCsn._containerEntity) {
|
|
454
457
|
setProp(parameterCsn, '_containerEntity', []);
|
|
455
|
-
for(
|
|
456
|
-
parameterCsn._containerEntity.push((c
|
|
458
|
+
for(const c of entityCsn._containerEntity) {
|
|
459
|
+
parameterCsn._containerEntity.push((c === entityCsn.name) ? parameterCsn.name : c);
|
|
457
460
|
}
|
|
458
461
|
}
|
|
459
462
|
entityCsn._containerEntity = [ parameterCsn ];
|
|
@@ -486,12 +489,18 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
486
489
|
member.$path[1] = parameterEntityName;
|
|
487
490
|
});
|
|
488
491
|
|
|
489
|
-
|
|
490
|
-
|
|
492
|
+
if(csn.definitions[parameterCsn.name])
|
|
493
|
+
error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
|
|
494
|
+
else
|
|
495
|
+
csn.definitions[parameterCsn.name] = parameterCsn;
|
|
491
496
|
// modify the original parameter entity with backlink and new name
|
|
492
|
-
csn.definitions[originalEntityName]
|
|
493
|
-
|
|
494
|
-
|
|
497
|
+
if(csn.definitions[originalEntityName])
|
|
498
|
+
error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: originalEntityName });
|
|
499
|
+
else {
|
|
500
|
+
csn.definitions[originalEntityName] = entityCsn;
|
|
501
|
+
delete csn.definitions[entityCsn.name];
|
|
502
|
+
entityCsn.name = originalEntityName;
|
|
503
|
+
}
|
|
495
504
|
setProp(entityCsn, '$entitySetName', originalEntitySetName);
|
|
496
505
|
// add backlink association
|
|
497
506
|
if(hasBacklink) {
|
|
@@ -543,6 +552,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
543
552
|
// convert $path to path starting at main artifact
|
|
544
553
|
function $path2path(p) {
|
|
545
554
|
const path = [];
|
|
555
|
+
/** @type {object} */
|
|
546
556
|
let env = csn;
|
|
547
557
|
for (let i = 0; p && env && i < p.length; i++) {
|
|
548
558
|
const ps = p[i];
|
|
@@ -594,6 +604,16 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
594
604
|
if(element['@cds.valid.from']) {
|
|
595
605
|
validFrom.push(element);
|
|
596
606
|
}
|
|
607
|
+
//forward annotations from managed association element to its foreign keys
|
|
608
|
+
const elements = construct.items && construct.items.elements || construct.elements;
|
|
609
|
+
forAll(elements[element['@odata.foreignKey4']], (attr, attrName) => {
|
|
610
|
+
if(attrName[0] === '@') {
|
|
611
|
+
element[attrName] = attr;
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
// and eventually remove some afterwards:)
|
|
615
|
+
if(options.isV2())
|
|
616
|
+
setSAPSpecificV2AnnotationsToAssociation(element);
|
|
597
617
|
|
|
598
618
|
// initialize an association
|
|
599
619
|
if(isAssociationOrComposition(element)) {
|
|
@@ -602,20 +622,6 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
602
622
|
assignProp(element._target, '$proxies', []);
|
|
603
623
|
// $abspath is used as partner path
|
|
604
624
|
assignProp(element, '$abspath', $path2path(element.$path));
|
|
605
|
-
|
|
606
|
-
//forward annotations from managed association element to its foreign keys
|
|
607
|
-
if(element.keys && options.isFlatFormat) {
|
|
608
|
-
const elements = construct.items && construct.items.elements || construct.elements;
|
|
609
|
-
for(let fk of element.keys) {
|
|
610
|
-
forAll(element, (attr, attrName) => {
|
|
611
|
-
if(attrName[0] === '@' && fk.$generatedFieldName && elements && elements[fk.$generatedFieldName]) {
|
|
612
|
-
elements[fk.$generatedFieldName][attrName] = attr;
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
// and afterwards eventually remove some :)
|
|
618
|
-
setSAPSpecificV2AnnotationsToAssociation(options, element, def);
|
|
619
625
|
}
|
|
620
626
|
|
|
621
627
|
// Collect keys
|
|
@@ -770,7 +776,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
770
776
|
if(element._constraints._partnerCsn.cardinality.src) {
|
|
771
777
|
let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
|
|
772
778
|
let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
|
|
773
|
-
if(options.isV2() && srcMult
|
|
779
|
+
if(options.isV2() && srcMult !== newMult) {
|
|
774
780
|
// Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from
|
|
775
781
|
warning(null, null, `Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
|
|
776
782
|
}
|
|
@@ -1269,9 +1275,8 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1269
1275
|
}
|
|
1270
1276
|
else if(alreadyRegistered && !alreadyRegistered.$proxy &&
|
|
1271
1277
|
!['entity', 'view'].includes(alreadyRegistered.kind)) {
|
|
1272
|
-
warning(
|
|
1273
|
-
{ name: fqProxyName, kind: alreadyRegistered.kind }
|
|
1274
|
-
'No proxy EDM entity type created due to name collision with $(NAME) of kind $(KIND)');
|
|
1278
|
+
warning('odata-definition-exists', ['definitions', element._parent.name, 'elements', element.name],
|
|
1279
|
+
{ '#': 'proxy', name: fqProxyName, kind: alreadyRegistered.kind });
|
|
1275
1280
|
return undefined;
|
|
1276
1281
|
}
|
|
1277
1282
|
}
|
|
@@ -1351,7 +1356,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1351
1356
|
// OData requires all elements along the path to be nullable: false (that is either key or notNull)
|
|
1352
1357
|
|
|
1353
1358
|
const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
|
|
1354
|
-
const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
|
|
1359
|
+
const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
|
|
1355
1360
|
(finalType && (finalType.elements || finalType.items && finalType.items.elements));
|
|
1356
1361
|
if(elements) {
|
|
1357
1362
|
Object.entries(elements).forEach(([eltName, elt]) => {
|
|
@@ -1416,7 +1421,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1416
1421
|
if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
|
|
1417
1422
|
const edmType = edmUtils.mapCdsToEdmType(type);
|
|
1418
1423
|
const legalEdmTypes = [
|
|
1419
|
-
'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
|
|
1424
|
+
'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
|
|
1420
1425
|
'Edm.Guid', 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte', 'Edm.String', 'Edm.TimeOfDay' ];
|
|
1421
1426
|
if(!legalEdmTypes.includes(edmType)) {
|
|
1422
1427
|
warning('odata-spec-violation-key-type', location,
|
|
@@ -1518,8 +1523,8 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1518
1523
|
// end point reached but must not be an external reference nor a proxy nor a composition itself
|
|
1519
1524
|
// last assoc step must not be to-n and target a singleton
|
|
1520
1525
|
let p = undefined;
|
|
1521
|
-
if (!elt._target.$externalRef &&
|
|
1522
|
-
!(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target
|
|
1526
|
+
if (!elt._target.$externalRef &&
|
|
1527
|
+
!(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target) && options.isV4())) {
|
|
1523
1528
|
if(elt._target.$edmTgtPaths && elt._target.$edmTgtPaths.length) {
|
|
1524
1529
|
p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(struct.name)) || elt._target.$edmTgtPaths[0];
|
|
1525
1530
|
}
|
|
@@ -1527,7 +1532,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1527
1532
|
const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
|
|
1528
1533
|
// if own struct and target have a set they either are in the same $mySchemaName or not
|
|
1529
1534
|
// if target is in another schema, target the full qualified entity set
|
|
1530
|
-
p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
|
|
1535
|
+
p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
|
|
1531
1536
|
[ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
|
|
1532
1537
|
}
|
|
1533
1538
|
if(p) {
|
|
@@ -1642,7 +1647,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1642
1647
|
!isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
|
|
1643
1648
|
const parent = whatsMyServiceRootName(sn, false);
|
|
1644
1649
|
if(parent && parent !== sn) {
|
|
1645
|
-
|
|
1650
|
+
message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
|
|
1646
1651
|
'A service can\'t be nested within a service $(ART)' );
|
|
1647
1652
|
}
|
|
1648
1653
|
});
|
|
@@ -1651,7 +1656,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1651
1656
|
if(art.kind === 'context') {
|
|
1652
1657
|
const parent = whatsMyServiceRootName(fqName);
|
|
1653
1658
|
if(parent) {
|
|
1654
|
-
|
|
1659
|
+
message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
|
|
1655
1660
|
'A context can\'t be nested within a service $(ART)' );
|
|
1656
1661
|
}
|
|
1657
1662
|
}
|
|
@@ -1789,8 +1794,8 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1789
1794
|
if(!navPropEntry[prop]) {
|
|
1790
1795
|
// Filter properties with prefix and reduce them into a new dictionary
|
|
1791
1796
|
const o = props.filter(p => p[0].startsWith(prefix+'.')).reduce((a,c) => {
|
|
1792
|
-
a[c[0].replace(prefix+'.', '')] = c[1];
|
|
1793
|
-
return a;
|
|
1797
|
+
a[c[0].replace(prefix+'.', '')] = c[1];
|
|
1798
|
+
return a;
|
|
1794
1799
|
}, { });
|
|
1795
1800
|
// if dictionary has entries, add them to navPropEnty
|
|
1796
1801
|
if(Object.keys(o).length) {
|
|
@@ -1926,8 +1931,9 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
|
|
|
1926
1931
|
// nested functions begin
|
|
1927
1932
|
function PDMSemantics()
|
|
1928
1933
|
{
|
|
1929
|
-
let dict = Object.create(null);
|
|
1930
1934
|
/*
|
|
1935
|
+
let dict = Object.create(null);
|
|
1936
|
+
|
|
1931
1937
|
dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
|
|
1932
1938
|
dict['@PDM.xxx2'] = [ '@sap.pdm-propery' ];
|
|
1933
1939
|
dict['@PDM.xxx3'] = [ '@sap.pdm-display-sq-no' ];
|
|
@@ -1941,7 +1947,7 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
|
|
|
1941
1947
|
// respect flattened annotation $value
|
|
1942
1948
|
Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
|
|
1943
1949
|
*/
|
|
1944
|
-
return
|
|
1950
|
+
return Object.create(null);
|
|
1945
1951
|
}
|
|
1946
1952
|
|
|
1947
1953
|
function AnalyticalAnnotations()
|
|
@@ -2159,19 +2165,17 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
|
|
|
2159
2165
|
}
|
|
2160
2166
|
}
|
|
2161
2167
|
|
|
2162
|
-
function setSAPSpecificV2AnnotationsToAssociation(
|
|
2163
|
-
if(!options.isV2())
|
|
2164
|
-
return;
|
|
2168
|
+
function setSAPSpecificV2AnnotationsToAssociation(carrier) {
|
|
2165
2169
|
// documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
|
|
2166
2170
|
const SetAttributes = {
|
|
2167
2171
|
// Applicable to NavProp and foreign keys, add to AssociationSet
|
|
2168
|
-
'@sap.creatable' : (
|
|
2172
|
+
'@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
|
|
2169
2173
|
// Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
|
|
2170
|
-
'@sap.updatable' :
|
|
2174
|
+
'@sap.updatable' : addToAssociationSet,
|
|
2171
2175
|
// Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
|
|
2172
|
-
'@sap.deletable': (
|
|
2173
|
-
|
|
2174
|
-
removeFromForeignKey(
|
|
2176
|
+
'@sap.deletable': (c, pn, pv) => {
|
|
2177
|
+
addToAssociationSet(c, pn, pv);
|
|
2178
|
+
removeFromForeignKey(c, pn);
|
|
2175
2179
|
},
|
|
2176
2180
|
// applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
|
|
2177
2181
|
'@sap.creatable.path': removeFromForeignKey,
|
|
@@ -2179,24 +2183,22 @@ function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
|
|
|
2179
2183
|
};
|
|
2180
2184
|
|
|
2181
2185
|
Object.entries(carrier).forEach(([p, v]) => {
|
|
2182
|
-
(SetAttributes[p] || function() {/* no-op */})(
|
|
2186
|
+
(SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
|
|
2183
2187
|
});
|
|
2184
2188
|
|
|
2185
|
-
function
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2189
|
+
function addToAssociationSet(carrier, propName, propValue, removeFromType=true) {
|
|
2190
|
+
if(isAssociationOrComposition(carrier)) {
|
|
2191
|
+
assignProp(carrier, '_SetAttributes', Object.create(null));
|
|
2192
|
+
assignAnnotation(carrier._SetAttributes, propName, propValue);
|
|
2193
|
+
if(removeFromType) {
|
|
2194
|
+
delete carrier[propName];
|
|
2195
|
+
}
|
|
2190
2196
|
}
|
|
2191
2197
|
}
|
|
2192
2198
|
|
|
2193
|
-
function removeFromForeignKey(
|
|
2194
|
-
if(carrier.
|
|
2195
|
-
|
|
2196
|
-
if(e['@odata.foreignKey4'] === carrier.name) {
|
|
2197
|
-
delete e[propName];
|
|
2198
|
-
}
|
|
2199
|
-
});
|
|
2199
|
+
function removeFromForeignKey(carrier, propName) {
|
|
2200
|
+
if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
|
|
2201
|
+
delete carrier[propName];
|
|
2200
2202
|
}
|
|
2201
2203
|
}
|
|
2202
2204
|
}
|
package/lib/edm/edmUtils.js
CHANGED
|
@@ -30,18 +30,18 @@ function validateOptions(_options)
|
|
|
30
30
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const v2 = options.version.match(/v2/i)
|
|
34
|
-
const v4 = options.version.match(/v4/i)
|
|
33
|
+
const v2 = options.version.match(/v2/i) !== null;
|
|
34
|
+
const v4 = options.version.match(/v4/i) !== null;
|
|
35
35
|
|
|
36
36
|
options.v = [v2, v4];
|
|
37
37
|
options.isStructFormat = options.odataFormat && options.odataFormat === 'structured';
|
|
38
38
|
options.isFlatFormat = !options.isStructFormat;
|
|
39
39
|
|
|
40
|
-
if(options.v.filter(v=>v).length
|
|
40
|
+
if(options.v.filter(v=>v).length !== 1)
|
|
41
41
|
throw Error(`Please debug me: EDM V2:${v2}, V4:${v4}`);
|
|
42
42
|
|
|
43
|
-
options.isV2 = function() { return this.v[0]
|
|
44
|
-
options.isV4 = function() { return this.v[1]
|
|
43
|
+
options.isV2 = function() { return this.v[0]; }
|
|
44
|
+
options.isV4 = function() { return this.v[1]; }
|
|
45
45
|
|
|
46
46
|
options.pathDelimiter = options.isStructFormat ? '/' : '_';
|
|
47
47
|
|
|
@@ -115,10 +115,10 @@ function isToMany(assoc) {
|
|
|
115
115
|
return targetMax === '*' || Number(targetMax) > 1;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
function isSingleton(entityCsn
|
|
118
|
+
function isSingleton(entityCsn) {
|
|
119
119
|
const singleton = entityCsn['@odata.singleton'];
|
|
120
120
|
const hasNullable = entityCsn['@odata.singleton.nullable'] !== undefined && entityCsn['@odata.singleton.nullable'] !== null;
|
|
121
|
-
return
|
|
121
|
+
return singleton || ((singleton === undefined || singleton === null) && hasNullable);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
function isEntity(artifact)
|
|
@@ -181,7 +181,7 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
|
|
|
181
181
|
const parent = csn.definitions[parentName];
|
|
182
182
|
if(originAssocCsn) {
|
|
183
183
|
const originParentName = originAssocCsn.$abspath[0];
|
|
184
|
-
if(originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
|
|
184
|
+
if(parent.$mySchemaName && originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
|
|
185
185
|
isBacklink = false;
|
|
186
186
|
// Partnership is ambiguous
|
|
187
187
|
setProp(originAssocCsn, '$noPartner', true);
|
|
@@ -192,7 +192,7 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
|
|
|
192
192
|
// Mark this association as backlink if $self appears exactly once
|
|
193
193
|
// to surpress edm:Association generation in V2 mode
|
|
194
194
|
if(isBacklink) {
|
|
195
|
-
//
|
|
195
|
+
// establish partnership with origin assoc but only if this association is the first one
|
|
196
196
|
if(originAssocCsn._selfReferences.length === 0) {
|
|
197
197
|
assocCsn._constraints._partnerCsn = originAssocCsn;
|
|
198
198
|
}
|
|
@@ -202,7 +202,7 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
|
|
|
202
202
|
}
|
|
203
203
|
// store all backlinks at forward, required to calculate rendering of foreign keys
|
|
204
204
|
// if the termCount != 1 or more than one $self compare this is not a backlink
|
|
205
|
-
if(assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1) {
|
|
205
|
+
if(parent.$mySchemaName && assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1) {
|
|
206
206
|
originAssocCsn._selfReferences.push(assocCsn);
|
|
207
207
|
}
|
|
208
208
|
assocCsn._constraints._origins.push(originAssocCsn);
|
|
@@ -358,7 +358,7 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
|
|
|
358
358
|
// in structured mode only resolve top level element (path rewriting is done elsewhere)
|
|
359
359
|
const depEltName = ( options.isFlatFormat ? c[0].join('_') : c[0][0] );
|
|
360
360
|
const principalEltName = ( options.isFlatFormat ? c[1].join('_') : c[1][0] );
|
|
361
|
-
const fk = (isEntity(dependentEntity) && dependentEntity.elements[ depEltName ]) ||
|
|
361
|
+
const fk = (isEntity(dependentEntity) && dependentEntity.elements[ depEltName ]) ||
|
|
362
362
|
(localDepEntity && localDepEntity.elements && localDepEntity.elements[ depEltName ]);
|
|
363
363
|
const pk = principalEntity.$keys && principalEntity.$keys[ principalEltName ];
|
|
364
364
|
if(isConstraintCandidate(fk) && isConstraintCandidate(pk)) {
|
|
@@ -463,12 +463,11 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
|
|
|
463
463
|
* The element must never be an association or composition and be renderable.
|
|
464
464
|
*/
|
|
465
465
|
function isConstraintCandidate(elt) {
|
|
466
|
-
|
|
466
|
+
return (elt &&
|
|
467
467
|
elt.type &&
|
|
468
468
|
(!options.isFlatFormat || options.isFlatFormat && isBuiltinType(elt.type)) &&
|
|
469
469
|
!['cds.Association', 'cds.Composition'].includes(elt.type) &&
|
|
470
470
|
isEdmPropertyRendered(elt, options));
|
|
471
|
-
return rc;
|
|
472
471
|
}
|
|
473
472
|
}
|
|
474
473
|
|
|
@@ -694,10 +693,10 @@ function getBaseName(name) {
|
|
|
694
693
|
|
|
695
694
|
// This is a poor mans path resolver for $self partner paths only
|
|
696
695
|
function resolveOriginAssoc(csn, env, path) {
|
|
697
|
-
for(
|
|
696
|
+
for(const segment of path) {
|
|
698
697
|
let elements = (env.items && env.items.elements || env.elements);
|
|
699
698
|
if(elements)
|
|
700
|
-
env = env.elements[
|
|
699
|
+
env = env.elements[segment];
|
|
701
700
|
let type = (env.items && env.items.type || env.type);
|
|
702
701
|
if(type && !isBuiltinType(type) && !(env.items && env.items.elements || env.elements))
|
|
703
702
|
env = csn.definitions[env.type];
|