@sap/cds-compiler 4.0.2 → 4.2.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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
package/lib/edm/edmUtils.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
|
|
2
3
|
const { setProp } = require('../base/model');
|
|
3
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
isBuiltinType, isEdmPropertyRendered, applyTransformations, cloneAnnotationValue,
|
|
6
|
+
} = require('../model/csnUtils');
|
|
4
7
|
const { escapeString, hasControlCharacters, hasUnpairedUnicodeSurrogate } = require('../render/utils/stringEscapes');
|
|
5
|
-
const {CompilerAssertion} = require('../base/error');
|
|
8
|
+
const { CompilerAssertion } = require('../base/error');
|
|
6
9
|
|
|
7
10
|
/* eslint max-statements-per-line:off */
|
|
8
|
-
function validateOptions(_options)
|
|
9
|
-
{
|
|
10
|
-
if(!_options.isV2 && !_options.isV4)
|
|
11
|
-
{
|
|
11
|
+
function validateOptions( _options ) {
|
|
12
|
+
if (!_options.isV2 && !_options.isV4) {
|
|
12
13
|
// csn2edm expects "odataVersion" to be a top-level property of options
|
|
13
14
|
// set to 'v4' as default, override with value from incoming options
|
|
14
|
-
const options = Object.assign({ odataVersion: 'v4'}, _options);
|
|
15
|
+
const options = Object.assign({ odataVersion: 'v4' }, _options);
|
|
15
16
|
// global flag that indicates whether or not FKs shall be rendered in general
|
|
16
17
|
// V2/V4 flat: yes
|
|
17
18
|
// V4/struct: depending on odataForeignKeys
|
|
18
|
-
options.renderForeignKeys
|
|
19
|
-
options.odataVersion === 'v4' ? options.odataFormat === 'structured' &&
|
|
19
|
+
options.renderForeignKeys
|
|
20
|
+
= options.odataVersion === 'v4' ? options.odataFormat === 'structured' && !!options.odataForeignKeys : true;
|
|
20
21
|
|
|
21
22
|
const v2 = options.odataVersion.match(/v2/i) !== null;
|
|
22
23
|
const v4 = options.odataVersion.match(/v4/i) !== null;
|
|
23
24
|
|
|
24
|
-
options.v = [v2, v4];
|
|
25
|
+
options.v = [ v2, v4 ];
|
|
25
26
|
options.isStructFormat = options.odataFormat && options.odataFormat === 'structured';
|
|
26
27
|
options.isFlatFormat = !options.isStructFormat;
|
|
27
28
|
|
|
@@ -32,81 +33,80 @@ function validateOptions(_options)
|
|
|
32
33
|
|
|
33
34
|
return options;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
return _options;
|
|
36
|
+
return _options;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// returns intersection of two arrays
|
|
40
|
-
function intersect(a,b)
|
|
41
|
-
|
|
42
|
-
return [...new Set(a)].filter(x => new Set(b).has(x));
|
|
40
|
+
function intersect( a, b ) {
|
|
41
|
+
return [ ...new Set(a) ].filter(x => new Set(b).has(x));
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
// Call func(art, name) for each artifact 'art' with name 'name' in 'dictionary' that returns true for 'filter(art)'
|
|
46
|
-
function foreach(dictionary, filter, func) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if(
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
function foreach( dictionary, filter, func ) {
|
|
46
|
+
if (dictionary) {
|
|
47
|
+
Object.entries(dictionary).forEach(([ name, value ]) => {
|
|
48
|
+
if (filter(value)) {
|
|
49
|
+
if (Array.isArray(func))
|
|
50
|
+
func.forEach(f => f(value, name));
|
|
51
|
+
else
|
|
52
52
|
func(value, name);
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
// true if _containerEntity is unequal to artifact name (non-recursive containment association)
|
|
58
59
|
// or if artifact belongs to an artificial parameter entity
|
|
59
|
-
function isContainee(artifact) {
|
|
60
|
+
function isContainee( artifact ) {
|
|
60
61
|
// if $containerNames is present, it is guaranteed that it has at least one entry
|
|
61
|
-
return (artifact.$containerNames && (artifact.$containerNames.length > 1 || artifact.$containerNames[0]
|
|
62
|
+
return (artifact.$containerNames && (artifact.$containerNames.length > 1 || artifact.$containerNames[0] !== artifact.name));
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
// Return true if the association 'assoc' has cardinality 'to-many'
|
|
65
|
-
function isToMany(assoc) {
|
|
66
|
-
if (!assoc.cardinality)
|
|
66
|
+
function isToMany( assoc ) {
|
|
67
|
+
if (!assoc.cardinality)
|
|
67
68
|
return false;
|
|
68
|
-
|
|
69
|
+
|
|
69
70
|
// Different representations possible: array or targetMax property
|
|
70
|
-
|
|
71
|
-
if (!targetMax)
|
|
71
|
+
const targetMax = assoc.cardinality[1] || assoc.cardinality.max;
|
|
72
|
+
if (!targetMax)
|
|
72
73
|
return false;
|
|
73
|
-
|
|
74
|
+
|
|
74
75
|
return targetMax === '*' || Number(targetMax) > 1;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
function isNavigable(assoc) {
|
|
78
|
+
function isNavigable( assoc ) {
|
|
78
79
|
return (assoc.target && (assoc['@odata.navigable'] == null || assoc['@odata.navigable']));
|
|
79
80
|
}
|
|
80
|
-
function isSingleton(entityCsn) {
|
|
81
|
+
function isSingleton( entityCsn ) {
|
|
81
82
|
const singleton = entityCsn['@odata.singleton'];
|
|
82
83
|
const hasNullable = entityCsn['@odata.singleton.nullable'] != null;
|
|
83
84
|
return singleton || (singleton == null && hasNullable);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
function isParameterizedEntity(artifact) {
|
|
87
|
+
function isParameterizedEntity( artifact ) {
|
|
87
88
|
return artifact.kind === 'entity' && artifact.params;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
// Return true if 'artifact' is structured (i.e. has elements, like a structured type or an entity)
|
|
91
|
-
function isStructuredArtifact(artifact) {
|
|
92
|
+
function isStructuredArtifact( artifact ) {
|
|
92
93
|
// FIXME: No derived types etc yet
|
|
93
94
|
return (artifact.items && artifact.items.elements || artifact.elements);
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
// Return true if 'artifact' is a real structured type (not an entity)
|
|
97
|
-
function isStructuredType(artifact) {
|
|
98
|
+
function isStructuredType( artifact ) {
|
|
98
99
|
return artifact.kind === 'type' && isStructuredArtifact(artifact);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
function isDerivedType(artifact) {
|
|
102
|
+
function isDerivedType( artifact ) {
|
|
102
103
|
return artifact.kind === 'type' && !isStructuredArtifact(artifact);
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions) {
|
|
106
|
+
function resolveOnConditionAndPrepareConstraints( csn, assocCsn, messageFunctions ) {
|
|
106
107
|
const { info, warning } = messageFunctions;
|
|
107
108
|
|
|
108
|
-
if(assocCsn.on)
|
|
109
|
-
{
|
|
109
|
+
if (assocCsn.on) {
|
|
110
110
|
// fill constraint array with [prop, depProp]
|
|
111
111
|
getExpressionArguments(assocCsn.on);
|
|
112
112
|
|
|
@@ -124,37 +124,36 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
|
|
|
124
124
|
ON Condition back.toE => parter=toE cannot be resolved in EParameters, _originalTarget 'E' is
|
|
125
125
|
required for that
|
|
126
126
|
*/
|
|
127
|
-
assocCsn._constraints.selfs.filter(p => p).forEach(partnerPath => {
|
|
127
|
+
assocCsn._constraints.selfs.filter(p => p).forEach((partnerPath) => {
|
|
128
128
|
// resolve partner path in target
|
|
129
129
|
const originAssocCsn = resolveOriginAssoc(csn, (assocCsn._originalTarget || assocCsn._target), partnerPath);
|
|
130
130
|
const parentName = assocCsn.$abspath[0];
|
|
131
131
|
const parent = csn.definitions[parentName];
|
|
132
|
-
if(originAssocCsn && originAssocCsn.$abspath) {
|
|
132
|
+
if (originAssocCsn && originAssocCsn.$abspath) {
|
|
133
133
|
const originParentName = originAssocCsn.$abspath[0];
|
|
134
|
-
if(parent.$mySchemaName && originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
|
|
134
|
+
if (parent.$mySchemaName && originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
|
|
135
135
|
isBacklink = false;
|
|
136
136
|
// Partnership is ambiguous
|
|
137
137
|
setProp(originAssocCsn, '$noPartner', true);
|
|
138
|
-
info(null, ['definitions', parentName, 'elements', assocCsn.name],
|
|
139
|
-
|
|
138
|
+
info(null, [ 'definitions', parentName, 'elements', assocCsn.name ],
|
|
139
|
+
`"${originParentName}:${partnerPath.join('.')}" with target "${originAssocCsn._target.name}" is compared with $self which represents "${parentName}"`);
|
|
140
140
|
}
|
|
141
|
-
if(originAssocCsn.target) {
|
|
141
|
+
if (originAssocCsn.target) {
|
|
142
142
|
// Mark this association as backlink if $self appears exactly once
|
|
143
143
|
// to suppress edm:Association generation in V2 mode
|
|
144
|
-
if(isBacklink) {
|
|
144
|
+
if (isBacklink) {
|
|
145
145
|
// establish partnership with origin assoc but only if this association is the first one
|
|
146
|
-
if(originAssocCsn._selfReferences.length === 0)
|
|
146
|
+
if (originAssocCsn._selfReferences.length === 0)
|
|
147
147
|
assocCsn._constraints._partnerCsn = originAssocCsn;
|
|
148
|
-
|
|
149
|
-
else
|
|
148
|
+
|
|
149
|
+
else
|
|
150
150
|
isBacklink = false;
|
|
151
|
-
}
|
|
152
151
|
}
|
|
153
152
|
// store all backlinks at forward, required to calculate rendering of foreign keys
|
|
154
153
|
// if the termCount != 1 or more than one $self compare this is not a backlink
|
|
155
|
-
if(parent.$mySchemaName && assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1)
|
|
154
|
+
if (parent.$mySchemaName && assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1)
|
|
156
155
|
originAssocCsn._selfReferences.push(assocCsn);
|
|
157
|
-
|
|
156
|
+
|
|
158
157
|
assocCsn._constraints._origins.push(originAssocCsn);
|
|
159
158
|
}
|
|
160
159
|
else {
|
|
@@ -163,78 +162,72 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
|
|
|
163
162
|
key id : Integer;
|
|
164
163
|
toMe: association to E on toMe.id = $self; };
|
|
165
164
|
*/
|
|
166
|
-
throw new CompilerAssertion(
|
|
165
|
+
throw new CompilerAssertion(`Backlink association element is not an association or composition: "${originAssocCsn.name}`);
|
|
167
166
|
}
|
|
168
167
|
}
|
|
169
|
-
else
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
'Can\'t resolve backlink to $(PARTNER) from $(NAME)');
|
|
168
|
+
else {
|
|
169
|
+
warning(null, [ 'definitions', parentName ],
|
|
170
|
+
{ partner: `${assocCsn._target.name}/${partnerPath}`, name: `${parentName}/${assocCsn.name}` },
|
|
171
|
+
'Can\'t resolve backlink to $(PARTNER) from $(NAME)');
|
|
174
172
|
}
|
|
175
173
|
});
|
|
176
174
|
}
|
|
177
175
|
|
|
178
176
|
// nested functions
|
|
179
|
-
function getExpressionArguments(expr)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
expr.forEach(fillConstraints)
|
|
177
|
+
function getExpressionArguments( expr ) {
|
|
178
|
+
const allowedTokens = [ '=', 'and', '(', ')' ];
|
|
179
|
+
if (expr && Array.isArray(expr) && !expr.some(isNotAConstraintTerm))
|
|
180
|
+
// if some returns true, this term is not usable as a constraint term
|
|
181
|
+
expr.forEach(fillConstraints);
|
|
182
|
+
|
|
186
183
|
|
|
187
184
|
// return true if token is not one of '=', 'and', '(', ')' or object
|
|
188
|
-
function isNotAConstraintTerm(tok)
|
|
189
|
-
|
|
190
|
-
if(tok.xpr)
|
|
185
|
+
function isNotAConstraintTerm( tok ) {
|
|
186
|
+
if (tok.xpr)
|
|
191
187
|
return tok.xpr.some(isNotAConstraintTerm);
|
|
192
|
-
if(Array.isArray(tok))
|
|
188
|
+
if (Array.isArray(tok))
|
|
193
189
|
return tok.some(isNotAConstraintTerm);
|
|
194
190
|
return !(typeof tok === 'object' && tok != null || allowedTokens.includes(tok));
|
|
195
191
|
}
|
|
196
192
|
|
|
197
193
|
// fill constraints object with [dependent, principal] pairs and collect all forward assocs for $self terms
|
|
198
|
-
function fillConstraints(arg, pos)
|
|
199
|
-
|
|
200
|
-
if(arg.xpr)
|
|
194
|
+
function fillConstraints( arg, pos ) {
|
|
195
|
+
if (arg.xpr) {
|
|
201
196
|
getExpressionArguments(arg.xpr);
|
|
202
|
-
|
|
203
|
-
{
|
|
204
|
-
let lhs = expr[pos-1];
|
|
205
|
-
let rhs = expr[pos+1];
|
|
206
|
-
if(arg === '=')
|
|
207
|
-
{
|
|
197
|
+
}
|
|
198
|
+
else if (pos > 0 && pos < expr.length) {
|
|
199
|
+
let lhs = expr[pos - 1];
|
|
200
|
+
let rhs = expr[pos + 1];
|
|
201
|
+
if (arg === '=') {
|
|
208
202
|
assocCsn._constraints.termCount++;
|
|
209
|
-
if(lhs.ref && rhs.ref) // ref is a path
|
|
210
|
-
{
|
|
203
|
+
if (lhs.ref && rhs.ref) { // ref is a path
|
|
211
204
|
lhs = lhs.ref;
|
|
212
205
|
rhs = rhs.ref;
|
|
213
206
|
// if exactly one operand starts with the prefix then this is potentially a constraint
|
|
214
207
|
|
|
215
208
|
// strip of prefix '$self's
|
|
216
|
-
if(lhs[0] === '$self' && lhs.length > 1)
|
|
209
|
+
if (lhs[0] === '$self' && lhs.length > 1)
|
|
217
210
|
lhs = lhs.slice(1);
|
|
218
|
-
if(rhs[0] === '$self' && rhs.length > 1)
|
|
211
|
+
if (rhs[0] === '$self' && rhs.length > 1)
|
|
219
212
|
rhs = rhs.slice(1);
|
|
220
213
|
|
|
221
|
-
if((lhs[0] === assocCsn.name && rhs[0] !== assocCsn.name) ||
|
|
222
|
-
(lhs[0] !== assocCsn.name && rhs[0] === assocCsn.name))
|
|
223
|
-
{
|
|
214
|
+
if ((lhs[0] === assocCsn.name && rhs[0] !== assocCsn.name) ||
|
|
215
|
+
(lhs[0] !== assocCsn.name && rhs[0] === assocCsn.name)) {
|
|
224
216
|
// order is always [ property, referencedProperty ]
|
|
225
|
-
//backlink [ self, assocName ]
|
|
217
|
+
// backlink [ self, assocName ]
|
|
226
218
|
|
|
227
219
|
let c;
|
|
228
|
-
if(lhs[0] === assocCsn.name)
|
|
229
|
-
c = [rhs, lhs.slice(1)];
|
|
220
|
+
if (lhs[0] === assocCsn.name)
|
|
221
|
+
c = [ rhs, lhs.slice(1) ];
|
|
230
222
|
else
|
|
231
|
-
c = [lhs, rhs.slice(1)];
|
|
223
|
+
c = [ lhs, rhs.slice(1) ];
|
|
232
224
|
|
|
233
225
|
// do we have a $self id?
|
|
234
226
|
// if so, store partner in selfs array
|
|
235
|
-
if(c[0][0] === '$self' && c[0].length === 1) {
|
|
227
|
+
if (c[0][0] === '$self' && c[0].length === 1) {
|
|
236
228
|
assocCsn._constraints.selfs.push(c[1]);
|
|
237
|
-
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
238
231
|
const key = c.join(',');
|
|
239
232
|
assocCsn._constraints.constraints[key] = c;
|
|
240
233
|
}
|
|
@@ -246,8 +239,7 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
|
|
|
246
239
|
}
|
|
247
240
|
}
|
|
248
241
|
|
|
249
|
-
function finalizeReferentialConstraints(csn, assocCsn, options, info)
|
|
250
|
-
{
|
|
242
|
+
function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
|
|
251
243
|
if (assocCsn.on) {
|
|
252
244
|
/* example for originalTarget:
|
|
253
245
|
entity E (with parameters) {
|
|
@@ -260,15 +252,14 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
|
|
|
260
252
|
ON Condition back.toE => parter=toE cannot be resolved in EParameters, originalTarget 'E' is
|
|
261
253
|
required for that
|
|
262
254
|
*/
|
|
263
|
-
assocCsn._constraints._origins.forEach(originAssocCsn => {
|
|
255
|
+
assocCsn._constraints._origins.forEach((originAssocCsn) => {
|
|
264
256
|
// if the origin assoc is marked as primary key and if it's managed, add all its foreign keys as constraint
|
|
265
257
|
// as they are also primary keys of the origin entity as well
|
|
266
|
-
if(!assocCsn._target.$isParamEntity && originAssocCsn.key && originAssocCsn.keys) {
|
|
267
|
-
for(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if(isConstraintCandidate(pk) && isConstraintCandidate(realFk))
|
|
271
|
-
{
|
|
258
|
+
if (!assocCsn._target.$isParamEntity && originAssocCsn.key && originAssocCsn.keys) {
|
|
259
|
+
for (const fk of originAssocCsn.keys) {
|
|
260
|
+
const realFk = originAssocCsn._parent.elements[fk.$generatedFieldName];
|
|
261
|
+
const pk = assocCsn._parent.elements[fk.ref[0]];
|
|
262
|
+
if (isConstraintCandidate(pk) && isConstraintCandidate(realFk)) {
|
|
272
263
|
const c = [ [ fk.ref[0] ], [ fk.$generatedFieldName ] ];
|
|
273
264
|
const key = c.join(',');
|
|
274
265
|
assocCsn._constraints.constraints[key] = c;
|
|
@@ -277,87 +268,88 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
|
|
|
277
268
|
}
|
|
278
269
|
});
|
|
279
270
|
|
|
280
|
-
if(!assocCsn._target.$isParamEntity) {
|
|
271
|
+
if (!assocCsn._target.$isParamEntity) {
|
|
281
272
|
// Use $path to identify main artifact in case assocs parent was a nested type and deanonymized
|
|
282
273
|
// Some (draft) associations don't have a $path, use _parent as last resort
|
|
283
274
|
let dependentEntity = assocCsn.$path ? csn.definitions[assocCsn.$path[1]] : assocCsn._parent;
|
|
284
|
-
let localDepEntity
|
|
275
|
+
let localDepEntity = assocCsn._parent;
|
|
285
276
|
// _target must always be a main artifact
|
|
286
277
|
let principalEntity = assocCsn._target;
|
|
287
|
-
if(assocCsn.type === 'cds.Composition') {
|
|
278
|
+
if (assocCsn.type === 'cds.Composition') {
|
|
288
279
|
// Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys
|
|
289
280
|
principalEntity = dependentEntity;
|
|
290
281
|
localDepEntity = undefined;
|
|
291
282
|
dependentEntity = assocCsn._target;
|
|
292
283
|
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal]
|
|
293
|
-
Object.keys(assocCsn._constraints.constraints).forEach(cn => {
|
|
294
|
-
assocCsn._constraints.constraints[cn] = [ assocCsn._constraints.constraints[cn][1], assocCsn._constraints.constraints[cn][0] ]
|
|
284
|
+
Object.keys(assocCsn._constraints.constraints).forEach((cn) => {
|
|
285
|
+
assocCsn._constraints.constraints[cn] = [ assocCsn._constraints.constraints[cn][1], assocCsn._constraints.constraints[cn][0] ];
|
|
286
|
+
} );
|
|
295
287
|
}
|
|
296
288
|
// Remove all target elements that are not key in the principal entity
|
|
297
289
|
// and all elements that annotated with '@cds.api.ignore'
|
|
298
290
|
const remainingPrincipalRefs = [];
|
|
299
291
|
foreach(assocCsn._constraints.constraints,
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
(localDepEntity && localDepEntity.elements && localDepEntity.elements[
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
292
|
+
(c) => {
|
|
293
|
+
// rc === true will remove the constraint (positive filter expression)
|
|
294
|
+
let rc = true;
|
|
295
|
+
// concatenate all paths in flat mode to identify the correct element
|
|
296
|
+
// in structured mode only resolve top level element (path rewriting is done elsewhere)
|
|
297
|
+
const depEltName = ( options.isFlatFormat ? c[0].join('_') : c[0][0] );
|
|
298
|
+
const principalEltName = ( options.isFlatFormat ? c[1].join('_') : c[1][0] );
|
|
299
|
+
const fk = (dependentEntity.kind === 'entity' && dependentEntity.elements[depEltName]) ||
|
|
300
|
+
(localDepEntity && localDepEntity.elements && localDepEntity.elements[depEltName]);
|
|
301
|
+
const pk = principalEntity.$keys && principalEntity.$keys[principalEltName];
|
|
302
|
+
if (isConstraintCandidate(fk) && isConstraintCandidate(pk)) {
|
|
303
|
+
if (options.isStructFormat) {
|
|
304
|
+
// In structured mode it might be the association has a new _parent due to
|
|
305
|
+
// type de-anonymization.
|
|
306
|
+
// There are three cases for dependent ON condition paths:
|
|
307
|
+
// 1) path is relative to assoc in same sub structure
|
|
308
|
+
// 2) path is absolute and ends up in a different environment
|
|
309
|
+
// 3) path is absolute and touches in assoc's environment
|
|
310
|
+
|
|
311
|
+
// => 1) if _parents are equal, fk path is relative to assoc
|
|
312
|
+
if (fk._parent === assocCsn._parent) {
|
|
313
|
+
rc = false;
|
|
314
|
+
}
|
|
315
|
+
// => 2) & 3) if path is not relative to assoc, remove main entity (pos=0) and assoc (pos=n-1)
|
|
316
|
+
// and check path identity: If absolute path touches assoc's _parent, add it
|
|
317
|
+
else if (!assocCsn.$abspath.slice(1, assocCsn.$abspath.length - 1).some((p, i) => c[0][i] !== p)) {
|
|
318
|
+
// this was an absolute addressed path, remove environment prefix
|
|
319
|
+
c[0].splice(0, assocCsn.$abspath.length - 2);
|
|
320
|
+
rc = false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
// for flat mode isConstraintCandidate(fk) && isConstraintCandidate(pk) is sufficient
|
|
325
|
+
rc = false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (!rc)
|
|
329
|
+
remainingPrincipalRefs.push(principalEltName);
|
|
330
|
+
return rc;
|
|
331
|
+
},
|
|
332
|
+
(c, cn) => {
|
|
333
|
+
delete assocCsn._constraints.constraints[cn];
|
|
334
|
+
});
|
|
342
335
|
|
|
343
336
|
// V2 check that ALL primary keys are constraints
|
|
344
|
-
if(principalEntity.$keys) {
|
|
345
|
-
const renderedKeys = Object.values(principalEntity.$keys).filter(isConstraintCandidate).map(v=>v.name);
|
|
346
|
-
if(options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length)
|
|
347
|
-
if(options.odataV2PartialConstr) {
|
|
337
|
+
if (principalEntity.$keys) {
|
|
338
|
+
const renderedKeys = Object.values(principalEntity.$keys).filter(isConstraintCandidate).map(v => v.name);
|
|
339
|
+
if (options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length) {
|
|
340
|
+
if (options.odataV2PartialConstr) {
|
|
348
341
|
info('odata-spec-violation-constraints',
|
|
349
|
-
|
|
342
|
+
[ 'definitions', assocCsn._parent.name, 'elements', assocCsn.name ], { version: '2.0' });
|
|
350
343
|
}
|
|
351
344
|
else {
|
|
352
345
|
assocCsn._constraints.constraints = {};
|
|
353
346
|
}
|
|
347
|
+
}
|
|
354
348
|
}
|
|
355
349
|
}
|
|
356
|
-
|
|
357
350
|
}
|
|
358
351
|
// Handle managed association, a managed composition is treated as association
|
|
359
|
-
else
|
|
360
|
-
{
|
|
352
|
+
else if (!assocCsn._target.$isParamEntity && assocCsn.keys) {
|
|
361
353
|
// If FK is key in target => constraint
|
|
362
354
|
// Don't consider primary key associations (fks become keys on the source entity) as
|
|
363
355
|
// this would impose a constraint against the target.
|
|
@@ -365,40 +357,36 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
|
|
|
365
357
|
|
|
366
358
|
// In structured format, foreign keys of managed associations are never rendered, so
|
|
367
359
|
// there are no constraints for them.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const key = c.join(',');
|
|
378
|
-
assocCsn._constraints.constraints[key] = c;
|
|
379
|
-
}
|
|
360
|
+
const remainingPrincipalRefs = [];
|
|
361
|
+
for (const fk of assocCsn.keys) {
|
|
362
|
+
const realFk = assocCsn._parent.items ? assocCsn._parent.items.elements[fk.$generatedFieldName] : assocCsn._parent.elements[fk.$generatedFieldName];
|
|
363
|
+
const pk = assocCsn._target.elements[fk.ref[0]];
|
|
364
|
+
if (pk && pk.key && isConstraintCandidate(pk) && isConstraintCandidate(realFk)) {
|
|
365
|
+
remainingPrincipalRefs.push(fk.ref[0]);
|
|
366
|
+
const c = [ [ fk.$generatedFieldName ], [ fk.ref[0] ] ];
|
|
367
|
+
const key = c.join(',');
|
|
368
|
+
assocCsn._constraints.constraints[key] = c;
|
|
380
369
|
}
|
|
370
|
+
}
|
|
381
371
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
372
|
+
// V2 check that ALL primary keys are constraints
|
|
373
|
+
const renderedKeys = Object.values(assocCsn._target.$keys).filter(isConstraintCandidate).map(v => v.name);
|
|
374
|
+
if (options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length) {
|
|
375
|
+
if (options.odataV2PartialConstr) {
|
|
376
|
+
info('odata-spec-violation-constraints',
|
|
377
|
+
[ 'definitions', assocCsn._parent.name, 'elements', assocCsn.name ], { version: '2.0' } );
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
assocCsn._constraints.constraints = {};
|
|
392
381
|
}
|
|
393
382
|
}
|
|
394
383
|
}
|
|
395
384
|
|
|
396
385
|
// If this association points to a redirected Parameter EntityType, do not calculate any constraints,
|
|
397
386
|
// continue with multiplicity
|
|
398
|
-
if(assocCsn._target.$isParamEntity)
|
|
399
|
-
{
|
|
387
|
+
if (assocCsn._target.$isParamEntity)
|
|
400
388
|
assocCsn._constraints.constraints = Object.create(null);
|
|
401
|
-
|
|
389
|
+
|
|
402
390
|
return assocCsn._constraints;
|
|
403
391
|
|
|
404
392
|
/*
|
|
@@ -408,7 +396,7 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
|
|
|
408
396
|
* type definition (alias to a scalar type).
|
|
409
397
|
* The element must never be an association or composition and be renderable.
|
|
410
398
|
*/
|
|
411
|
-
function isConstraintCandidate(elt) {
|
|
399
|
+
function isConstraintCandidate( elt ) {
|
|
412
400
|
return (elt &&
|
|
413
401
|
elt.type &&
|
|
414
402
|
(!options.isFlatFormat || options.isFlatFormat && isBuiltinType(elt.type)) &&
|
|
@@ -417,8 +405,7 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
|
|
|
417
405
|
}
|
|
418
406
|
}
|
|
419
407
|
|
|
420
|
-
function determineMultiplicity(csn)
|
|
421
|
-
{
|
|
408
|
+
function determineMultiplicity( csn ) {
|
|
422
409
|
/*
|
|
423
410
|
=> SRC Cardinality
|
|
424
411
|
CDS => EDM
|
|
@@ -450,67 +437,66 @@ function determineMultiplicity(csn)
|
|
|
450
437
|
*/
|
|
451
438
|
|
|
452
439
|
const isAssoc = csn.type === 'cds.Association';
|
|
453
|
-
if(!csn.cardinality)
|
|
440
|
+
if (!csn.cardinality)
|
|
454
441
|
csn.cardinality = Object.create(null);
|
|
455
442
|
|
|
456
|
-
if(!csn.cardinality.src)
|
|
443
|
+
if (!csn.cardinality.src)
|
|
457
444
|
csn.cardinality.src = isAssoc ? '*' : '1';
|
|
458
|
-
if(!csn.cardinality.min)
|
|
445
|
+
if (!csn.cardinality.min)
|
|
459
446
|
csn.cardinality.min = 0;
|
|
460
|
-
if(!csn.cardinality.max)
|
|
447
|
+
if (!csn.cardinality.max)
|
|
461
448
|
csn.cardinality.max = 1;
|
|
462
449
|
|
|
463
|
-
const srcCardinality
|
|
464
|
-
(csn.cardinality.src == 1)
|
|
465
|
-
? (!isAssoc || csn.cardinality.srcmin == 1)
|
|
466
|
-
|
|
467
|
-
|
|
450
|
+
const srcCardinality
|
|
451
|
+
= (csn.cardinality.src == 1) // eslint-disable-line eqeqeq
|
|
452
|
+
? (!isAssoc || csn.cardinality.srcmin == 1) // eslint-disable-line eqeqeq
|
|
453
|
+
? '1'
|
|
454
|
+
: '0..1'
|
|
468
455
|
: '*';
|
|
469
|
-
const
|
|
470
|
-
(csn.cardinality.max > 1 || csn.cardinality.max === '*')
|
|
456
|
+
const tgtCardinality
|
|
457
|
+
= (csn.cardinality.max > 1 || csn.cardinality.max === '*')
|
|
471
458
|
? '*'
|
|
472
|
-
: (csn.cardinality.min == 1)
|
|
459
|
+
: (csn.cardinality.min == 1) // eslint-disable-line eqeqeq
|
|
473
460
|
? '1'
|
|
474
461
|
: '0..1';
|
|
475
462
|
|
|
476
|
-
return [srcCardinality, tgtCardinality];
|
|
463
|
+
return [ srcCardinality, tgtCardinality ];
|
|
477
464
|
}
|
|
478
465
|
|
|
479
466
|
// return effective target cardinality
|
|
480
467
|
// If csn is a backlink, return the source cardinality (including srcmin/src) from
|
|
481
468
|
// the forward association
|
|
482
469
|
// This function works only after finalizeConstraints
|
|
483
|
-
function getEffectiveTargetCardinality(csn) {
|
|
470
|
+
function getEffectiveTargetCardinality( csn ) {
|
|
484
471
|
const rc = { min: 0, max: 1 };
|
|
485
|
-
if(!csn._constraints || !csn._constraints.$finalized)
|
|
486
|
-
throw new CompilerAssertion(
|
|
472
|
+
if (!csn._constraints || !csn._constraints.$finalized)
|
|
473
|
+
throw new CompilerAssertion(`_constraints missing or not finalized: "${csn.name}`);
|
|
487
474
|
// partner (forward) cardinality has precedence
|
|
488
|
-
if(csn._constraints._partnerCsn) {
|
|
489
|
-
if(csn._constraints._partnerCsn.cardinality?.srcmin)
|
|
475
|
+
if (csn._constraints._partnerCsn) {
|
|
476
|
+
if (csn._constraints._partnerCsn.cardinality?.srcmin)
|
|
490
477
|
rc.min = csn._constraints._partnerCsn.cardinality.srcmin;
|
|
491
|
-
if(csn._constraints._partnerCsn.cardinality?.src)
|
|
478
|
+
if (csn._constraints._partnerCsn.cardinality?.src)
|
|
492
479
|
rc.max = csn._constraints._partnerCsn.cardinality.src;
|
|
493
480
|
}
|
|
494
|
-
else if(csn.cardinality) {
|
|
495
|
-
if(csn.cardinality.min)
|
|
481
|
+
else if (csn.cardinality) {
|
|
482
|
+
if (csn.cardinality.min)
|
|
496
483
|
rc.min = csn.cardinality.min;
|
|
497
|
-
if(csn.cardinality.max)
|
|
498
|
-
rc.max = csn.cardinality.max
|
|
484
|
+
if (csn.cardinality.max)
|
|
485
|
+
rc.max = csn.cardinality.max;
|
|
499
486
|
}
|
|
500
487
|
return rc;
|
|
501
488
|
}
|
|
502
489
|
|
|
503
|
-
function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false, location=undefined)
|
|
504
|
-
|
|
505
|
-
if(location === undefined)
|
|
490
|
+
function mapCdsToEdmType( csn, messageFunctions, isV2 = false, isMediaType = false, location = undefined ) {
|
|
491
|
+
if (location === undefined)
|
|
506
492
|
location = csn.$path;
|
|
507
|
-
const { error } = messageFunctions || { error: ()=>true };
|
|
508
|
-
|
|
509
|
-
if(cdsType === undefined) {
|
|
493
|
+
const { error } = messageFunctions || { error: () => true };
|
|
494
|
+
const cdsType = csn.type;
|
|
495
|
+
if (cdsType === undefined) {
|
|
510
496
|
error(null, location, 'no type found');
|
|
511
497
|
return '<NOTYPE>';
|
|
512
498
|
}
|
|
513
|
-
if(!isBuiltinType(cdsType))
|
|
499
|
+
if (!isBuiltinType(cdsType))
|
|
514
500
|
return cdsType;
|
|
515
501
|
|
|
516
502
|
let edmType = {
|
|
@@ -565,29 +551,25 @@ function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false, l
|
|
|
565
551
|
Edm.GeometryCollection
|
|
566
552
|
*/
|
|
567
553
|
}[cdsType];
|
|
568
|
-
if (edmType
|
|
554
|
+
if (!edmType)
|
|
569
555
|
error(null, location, { type: cdsType }, 'No EDM type available for $(TYPE)');
|
|
570
|
-
|
|
571
|
-
if(isV2)
|
|
572
|
-
{
|
|
556
|
+
|
|
557
|
+
if (isV2) {
|
|
573
558
|
if (edmType === 'Edm.Date')
|
|
574
559
|
edmType = 'Edm.DateTime';
|
|
575
560
|
if (edmType === 'Edm.TimeOfDay')
|
|
576
561
|
edmType = 'Edm.Time';
|
|
577
562
|
}
|
|
578
|
-
else // isV4
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if(isMediaType)
|
|
582
|
-
edmType = 'Edm.Stream';
|
|
563
|
+
else if (isMediaType) { // isV4
|
|
564
|
+
// CDXCORE-CDXCORE-173
|
|
565
|
+
edmType = 'Edm.Stream';
|
|
583
566
|
}
|
|
584
567
|
return edmType;
|
|
585
568
|
}
|
|
586
569
|
|
|
587
|
-
function addTypeFacets(node, csn)
|
|
588
|
-
{
|
|
570
|
+
function addTypeFacets( node, csn ) {
|
|
589
571
|
const isV2 = node.v2;
|
|
590
|
-
const decimalTypes = {'cds.Decimal':1, 'cds.DecimalFloat':1, 'cds.hana.SMALLDECIMAL':1};
|
|
572
|
+
const decimalTypes = { 'cds.Decimal': 1, 'cds.DecimalFloat': 1, 'cds.hana.SMALLDECIMAL': 1 };
|
|
591
573
|
if (csn.length != null)
|
|
592
574
|
node.setEdmAttribute('MaxLength', csn.length);
|
|
593
575
|
if (csn.scale !== undefined)
|
|
@@ -601,32 +583,32 @@ function addTypeFacets(node, csn)
|
|
|
601
583
|
// node.Precision = 16;
|
|
602
584
|
else if (csn.type === 'cds.Timestamp' && node._edmAttributes.Type === 'Edm.DateTimeOffset')
|
|
603
585
|
node.setEdmAttribute('Precision', 7);
|
|
604
|
-
if(csn.type in decimalTypes) {
|
|
605
|
-
if(isV2) {
|
|
586
|
+
if (csn.type in decimalTypes) {
|
|
587
|
+
if (isV2) {
|
|
606
588
|
// no prec/scale or scale is 'floating'/'variable'
|
|
607
|
-
if(!(csn.precision || csn.scale) || (csn.scale === 'floating' || csn.scale === 'variable')) {
|
|
589
|
+
if (!(csn.precision || csn.scale) || (csn.scale === 'floating' || csn.scale === 'variable')) {
|
|
608
590
|
node.setXml( { 'sap:variable-scale': true } );
|
|
609
591
|
node.removeEdmAttribute('Scale');
|
|
610
592
|
}
|
|
611
593
|
}
|
|
612
594
|
else {
|
|
613
595
|
// map both floating and variable to => variable
|
|
614
|
-
if(node._edmAttributes.Scale === 'floating')
|
|
596
|
+
if (node._edmAttributes.Scale === 'floating')
|
|
615
597
|
node.setEdmAttribute('Scale', 'variable');
|
|
616
|
-
if(
|
|
598
|
+
if (csn.precision == null && csn.scale == null)
|
|
617
599
|
// if Decimal has no p, s set scale 'variable'
|
|
618
600
|
node.setXml( { Scale: 'variable' } ); // floating is V4.01
|
|
619
601
|
}
|
|
620
602
|
}
|
|
621
603
|
// Unicode unused today
|
|
622
|
-
if(csn.unicode)
|
|
604
|
+
if (csn.unicode)
|
|
623
605
|
node.setEdmAttribute('Unicode', csn.unicode);
|
|
624
|
-
if(csn.srid)
|
|
606
|
+
if (csn.srid)
|
|
625
607
|
node.setEdmAttribute('SRID', csn.srid);
|
|
626
608
|
}
|
|
627
609
|
|
|
628
610
|
|
|
629
|
-
|
|
611
|
+
/**
|
|
630
612
|
* A simple identifier is a Unicode character sequence with the following restrictions:
|
|
631
613
|
* - The first character MUST be the underscore character (U+005F) or any character in the Unicode category “Letter (L)” or “Letter number (Nl)”
|
|
632
614
|
* - The remaining characters MUST be the underscore character (U+005F) or any character in the Unicode category:
|
|
@@ -641,9 +623,9 @@ function addTypeFacets(node, csn)
|
|
|
641
623
|
*
|
|
642
624
|
* @param {string} identifier
|
|
643
625
|
*/
|
|
644
|
-
function isODataSimpleIdentifier(identifier){
|
|
626
|
+
function isODataSimpleIdentifier( identifier ) {
|
|
645
627
|
// this regular expression reflects the specification from above
|
|
646
|
-
const regex = /^[\p{Letter}\p{Nl}_][_\p{Letter}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]{0,127}$/gu
|
|
628
|
+
const regex = /^[\p{Letter}\p{Nl}_][_\p{Letter}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]{0,127}$/gu;
|
|
647
629
|
return identifier && identifier.match(regex);
|
|
648
630
|
}
|
|
649
631
|
|
|
@@ -669,7 +651,7 @@ function isODataSimpleIdentifier(identifier){
|
|
|
669
651
|
* @param {string} str
|
|
670
652
|
* @returns {string}
|
|
671
653
|
*/
|
|
672
|
-
function escapeStringForAttributeValue(str) {
|
|
654
|
+
function escapeStringForAttributeValue( str ) {
|
|
673
655
|
if (typeof str !== 'string')
|
|
674
656
|
return str;
|
|
675
657
|
|
|
@@ -718,7 +700,7 @@ function escapeStringForAttributeValue(str) {
|
|
|
718
700
|
* @param {string} str
|
|
719
701
|
* @returns {string}
|
|
720
702
|
*/
|
|
721
|
-
function escapeStringForText(str) {
|
|
703
|
+
function escapeStringForText( str ) {
|
|
722
704
|
if (typeof str !== 'string')
|
|
723
705
|
return str;
|
|
724
706
|
|
|
@@ -749,70 +731,70 @@ function escapeStringForText(str) {
|
|
|
749
731
|
* @param {number} codePoint
|
|
750
732
|
* @returns {string}
|
|
751
733
|
*/
|
|
752
|
-
function encodeNonCharacters(codePoint) {
|
|
734
|
+
function encodeNonCharacters( codePoint ) {
|
|
753
735
|
const hex = codePoint.toString(16).toUpperCase();
|
|
754
736
|
return `&#x${hex};`;
|
|
755
737
|
}
|
|
756
738
|
|
|
757
739
|
// return the path prefix of a given name or if no prefix available 'root'
|
|
758
|
-
function getSchemaPrefix(name) {
|
|
740
|
+
function getSchemaPrefix( name ) {
|
|
759
741
|
const lastDotIdx = name.lastIndexOf('.');
|
|
760
742
|
return (lastDotIdx > 0 ) ? name.substring(0, lastDotIdx) : 'root';
|
|
761
743
|
}
|
|
762
744
|
|
|
763
745
|
// get artifacts base name
|
|
764
|
-
function getBaseName(name) {
|
|
746
|
+
function getBaseName( name ) {
|
|
765
747
|
const lastDotIdx = name.lastIndexOf('.');
|
|
766
|
-
return (lastDotIdx > 0 ) ? name.substring(lastDotIdx+1, name.length) : name;
|
|
748
|
+
return (lastDotIdx > 0 ) ? name.substring(lastDotIdx + 1, name.length) : name;
|
|
767
749
|
}
|
|
768
750
|
|
|
769
751
|
// This is a poor mans path resolver for $self partner paths only
|
|
770
|
-
function resolveOriginAssoc(csn, env, path) {
|
|
771
|
-
for(const segment of path) {
|
|
772
|
-
|
|
773
|
-
if(elements)
|
|
774
|
-
env =
|
|
775
|
-
|
|
776
|
-
if(type && !isBuiltinType(type) && !(env
|
|
777
|
-
env = csn.definitions[
|
|
752
|
+
function resolveOriginAssoc( csn, env, path ) {
|
|
753
|
+
for (const segment of path) {
|
|
754
|
+
const elements = (env?.items?.elements || env?.elements);
|
|
755
|
+
if (elements)
|
|
756
|
+
env = elements[segment];
|
|
757
|
+
const type = (env?.items?.type || env?.type);
|
|
758
|
+
if (type && !isBuiltinType(type) && !(env?.items?.elements || env?.elements))
|
|
759
|
+
env = csn.definitions[type];
|
|
778
760
|
}
|
|
779
761
|
return env;
|
|
780
762
|
}
|
|
781
763
|
|
|
782
|
-
function mergeIntoNavPropEntry(annoPrefix, navPropEntry, prefix, props) {
|
|
764
|
+
function mergeIntoNavPropEntry( annoPrefix, navPropEntry, prefix, props ) {
|
|
783
765
|
let newEntry = false;
|
|
784
766
|
|
|
785
767
|
// Filter properties with prefix and reduce them into a new dictionary
|
|
786
|
-
const o = props.filter(p => p[0].startsWith(annoPrefix
|
|
768
|
+
const o = props.filter(p => p[0].startsWith(`${annoPrefix}.`)).reduce((a, c) => {
|
|
787
769
|
// clone the annotation value to avoid side effects with rewritten paths
|
|
788
|
-
a[c[0].replace(annoPrefix
|
|
770
|
+
a[c[0].replace(`${annoPrefix}.`, '')] = cloneAnnotationValue(c[1]);
|
|
789
771
|
return a;
|
|
790
772
|
}, { });
|
|
791
773
|
|
|
792
774
|
// BEFORE merging found capabilities, prefix the paths
|
|
793
|
-
applyTransformations({ definitions: { o }}, {
|
|
775
|
+
applyTransformations({ definitions: { o } }, {
|
|
794
776
|
'=': (parent, prop, value) => {
|
|
795
777
|
parent[prop] = prefix.concat(value).join('.');
|
|
796
|
-
}
|
|
778
|
+
},
|
|
797
779
|
});
|
|
798
780
|
// don't overwrite existing restrictions
|
|
799
781
|
const prop = annoPrefix.split('.')[1];
|
|
800
|
-
if(!navPropEntry[prop]) {
|
|
782
|
+
if (!navPropEntry[prop]) {
|
|
801
783
|
// if dictionary has entries, add them to navPropEntry
|
|
802
|
-
if(Object.keys(o).length) {
|
|
784
|
+
if (Object.keys(o).length) {
|
|
803
785
|
// ReadRestrictions may have sub type ReadByKeyRestrictions { Description, LongDescription }
|
|
804
786
|
// chop annotations into dictionaries
|
|
805
|
-
if(annoPrefix === '@Capabilities.ReadRestrictions' &&
|
|
787
|
+
if (annoPrefix === '@Capabilities.ReadRestrictions' &&
|
|
806
788
|
Object.keys(o).some(k => k.startsWith('ReadByKeyRestrictions.'))) {
|
|
807
789
|
const no = {};
|
|
808
|
-
Object.entries(o).forEach(([k,v]) => {
|
|
809
|
-
const [head, ...tail] = k.split('.');
|
|
810
|
-
if(head === 'ReadByKeyRestrictions' && tail.length) {
|
|
811
|
-
if(!no
|
|
812
|
-
no
|
|
813
|
-
|
|
814
|
-
if(typeof no
|
|
815
|
-
no
|
|
790
|
+
Object.entries(o).forEach(([ k, v ]) => {
|
|
791
|
+
const [ head, ...tail ] = k.split('.');
|
|
792
|
+
if (head === 'ReadByKeyRestrictions' && tail.length) {
|
|
793
|
+
if (!no.ReadByKeyRestrictions)
|
|
794
|
+
no.ReadByKeyRestrictions = {};
|
|
795
|
+
// Don't try to add entry into non object
|
|
796
|
+
if (typeof no.ReadByKeyRestrictions === 'object')
|
|
797
|
+
no.ReadByKeyRestrictions[tail.join('.')] = v;
|
|
816
798
|
}
|
|
817
799
|
else {
|
|
818
800
|
no[k] = v;
|
|
@@ -828,8 +810,8 @@ function mergeIntoNavPropEntry(annoPrefix, navPropEntry, prefix, props) {
|
|
|
828
810
|
}
|
|
829
811
|
else {
|
|
830
812
|
// merge but don't overwrite into existing navprop
|
|
831
|
-
Object.entries(o).forEach(([k,v]) => {
|
|
832
|
-
if(!navPropEntry[prop][k])
|
|
813
|
+
Object.entries(o).forEach(([ k, v ]) => {
|
|
814
|
+
if (!navPropEntry[prop][k])
|
|
833
815
|
navPropEntry[prop][k] = v;
|
|
834
816
|
});
|
|
835
817
|
}
|
|
@@ -837,57 +819,54 @@ function mergeIntoNavPropEntry(annoPrefix, navPropEntry, prefix, props) {
|
|
|
837
819
|
}
|
|
838
820
|
|
|
839
821
|
// Assign but not overwrite annotation
|
|
840
|
-
function assignAnnotation(node, name, value) {
|
|
841
|
-
if(value !== undefined &&
|
|
842
|
-
name !== undefined && name[0] === '@'
|
|
843
|
-
|
|
844
|
-
node[name] = value;
|
|
845
|
-
}
|
|
822
|
+
function assignAnnotation( node, name, value ) {
|
|
823
|
+
if (value !== undefined &&
|
|
824
|
+
name !== undefined && name[0] === '@')
|
|
825
|
+
node[name] ??= value;
|
|
846
826
|
}
|
|
847
827
|
|
|
848
828
|
// Set non enumerable property if it doesn't exist yet
|
|
849
|
-
function assignProp(obj, prop, value) {
|
|
850
|
-
if(obj[prop] === undefined)
|
|
829
|
+
function assignProp( obj, prop, value ) {
|
|
830
|
+
if (obj[prop] === undefined)
|
|
851
831
|
setProp(obj, prop, value);
|
|
852
|
-
}
|
|
853
832
|
}
|
|
854
833
|
|
|
855
834
|
//
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
function createSchemaRef(serviceRoots, targetSchemaName) {
|
|
859
|
-
|
|
860
|
-
|
|
835
|
+
// create Cross Schema Reference object
|
|
836
|
+
//
|
|
837
|
+
function createSchemaRef( serviceRoots, targetSchemaName ) {
|
|
838
|
+
// prepend as many path ups '..' as there are path steps in the service ref
|
|
839
|
+
const serviceRef = path4(serviceRoots[targetSchemaName]).split('/').filter(c => c.length);
|
|
861
840
|
serviceRef.splice(0, 0, ...Array(serviceRef.length).fill('..'));
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if(serviceRef[serviceRef.length-1] !== '$metadata')
|
|
841
|
+
// uncomment this to make $metadata absolute
|
|
842
|
+
// if(serviceRef.length===0)
|
|
843
|
+
// serviceRef.push('');
|
|
844
|
+
if (serviceRef[serviceRef.length - 1] !== '$metadata')
|
|
866
845
|
serviceRef.push('$metadata');
|
|
867
|
-
|
|
846
|
+
const sc = {
|
|
847
|
+
kind: 'reference',
|
|
868
848
|
name: targetSchemaName,
|
|
869
849
|
ref: { Uri: serviceRef.join('/') },
|
|
870
|
-
inc: { Namespace: targetSchemaName }
|
|
850
|
+
inc: { Namespace: targetSchemaName },
|
|
871
851
|
};
|
|
872
852
|
setProp(sc, '$mySchemaName', targetSchemaName);
|
|
873
853
|
return sc;
|
|
874
854
|
|
|
875
|
-
|
|
855
|
+
/**
|
|
876
856
|
* Resolve a service endpoint path to mount it to as follows...
|
|
877
857
|
* Use _path or def[@path] if given (and remove leading '/')
|
|
878
858
|
* Otherwise, use the service definition name with stripped 'Service'
|
|
879
859
|
*/
|
|
880
|
-
function path4
|
|
860
|
+
function path4( def, _path = def['@path'] ) {
|
|
881
861
|
if (_path)
|
|
882
862
|
return _path.replace(/^\//, '');
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
)
|
|
863
|
+
return ( // generate one from the service's name
|
|
864
|
+
/[^.]+$/.exec(def.name)[0] // > my.very.CatalogService --> CatalogService
|
|
865
|
+
.replace(/Service$/, '') // > CatalogService --> Catalog
|
|
866
|
+
.replace(/([a-z0-9])([A-Z])/g, (_, c, C) => `${c}-${C.toLowerCase()}`) // > ODataFooBarX9 --> odata-foo-bar-x9
|
|
867
|
+
.replace(/_/g, '-') // > foo_bar_baz --> foo-bar-baz
|
|
868
|
+
.toLowerCase() // > FOO --> foo
|
|
869
|
+
);
|
|
891
870
|
}
|
|
892
871
|
}
|
|
893
872
|
|
|
@@ -918,5 +897,5 @@ module.exports = {
|
|
|
918
897
|
escapeStringForText,
|
|
919
898
|
getSchemaPrefix,
|
|
920
899
|
getBaseName,
|
|
921
|
-
mergeIntoNavPropEntry
|
|
922
|
-
}
|
|
900
|
+
mergeIntoNavPropEntry,
|
|
901
|
+
};
|