@sap/cds-compiler 5.7.4 → 5.8.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 +54 -2
- package/bin/cdsse.js +13 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/options.js +2 -1
- package/lib/api/validate.js +9 -0
- package/lib/base/message-registry.js +55 -20
- package/lib/base/messages.js +5 -2
- package/lib/base/model.js +4 -1
- package/lib/checks/assocOutsideService.js +40 -0
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/types.js +7 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/assert-consistency.js +11 -5
- package/lib/compiler/checks.js +79 -17
- package/lib/compiler/define.js +57 -3
- package/lib/compiler/extend.js +1 -2
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/populate.js +17 -6
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +181 -150
- package/lib/compiler/shared.js +276 -22
- package/lib/compiler/tweak-assocs.js +15 -4
- package/lib/compiler/xpr-rewrite.js +76 -50
- package/lib/edm/annotations/edmJson.js +1 -1
- package/lib/edm/annotations/genericTranslation.js +2 -2
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edmPreprocessor.js +15 -9
- package/lib/edm/edmUtils.js +12 -5
- package/lib/gen/CdlGrammar.checksum +1 -0
- package/lib/gen/CdlParser.js +2234 -2233
- package/lib/gen/Dictionary.json +55 -8
- package/lib/json/from-csn.js +37 -17
- package/lib/json/to-csn.js +4 -0
- package/lib/language/genericAntlrParser.js +7 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +0 -5
- package/lib/modelCompare/utils/filter.js +2 -2
- package/lib/optionProcessor.js +2 -0
- package/lib/parsers/AstBuildingParser.js +47 -17
- package/lib/parsers/CdlGrammar.g4 +10 -12
- package/lib/parsers/XprTree.js +206 -0
- package/lib/render/toCdl.js +61 -89
- package/lib/render/toSql.js +59 -29
- package/lib/render/utils/standardDatabaseFunctions.js +252 -15
- package/lib/transform/addTenantFields.js +9 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
- package/lib/transform/db/assocsToQueries/utils.js +10 -3
- package/lib/transform/db/expansion.js +3 -1
- package/lib/transform/db/flattening.js +7 -3
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +70 -17
- package/lib/transform/draft/db.js +8 -3
- package/lib/transform/draft/odata.js +27 -4
- package/lib/transform/effective/main.js +37 -10
- package/lib/transform/effective/misc.js +4 -9
- package/lib/transform/effective/service.js +34 -0
- package/lib/transform/effective/types.js +28 -17
- package/lib/transform/forOdata.js +36 -10
- package/lib/transform/forRelationalDB.js +30 -18
- package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
- package/lib/transform/odata/createForeignKeys.js +120 -116
- package/lib/transform/odata/flattening.js +10 -8
- package/lib/transform/transformUtils.js +58 -25
- package/lib/transform/translateAssocsToJoins.js +10 -6
- package/lib/transform/universalCsn/coreComputed.js +5 -1
- package/package.json +1 -1
- package/share/messages/message-explanations.json +1 -0
- package/share/messages/rewrite-not-supported.md +5 -0
- package/share/messages/rewrite-undefined-key.md +94 -0
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../../base/builtins');
|
|
4
4
|
const { setProp } = require('../../base/model');
|
|
5
|
-
const { applyTransformations, implicitAs,
|
|
5
|
+
const { applyTransformations, implicitAs, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
|
|
6
6
|
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
7
|
-
const adaptAnnotationsRefs = require('./adaptAnnotationRefs');
|
|
7
|
+
const { adaptAnnotationsRefs } = require('./adaptAnnotationRefs');
|
|
8
8
|
|
|
9
9
|
function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
|
|
10
10
|
|
|
@@ -28,20 +28,20 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
28
28
|
orderedElements.push([ elementName, element ]);
|
|
29
29
|
if (!csnUtils.isManagedAssociation(element)) return;
|
|
30
30
|
const elementPath = path.concat(prop, elementName);
|
|
31
|
-
const generatedForeignKeys = createForeignKeysForElement(
|
|
31
|
+
const generatedForeignKeys = createForeignKeysForElement(elementPath, element, elementName, csn, options, '_');
|
|
32
32
|
|
|
33
33
|
// Second, finalize the generated FK elements
|
|
34
34
|
const refCount = generatedForeignKeys.reduce((acc, fk) => {
|
|
35
35
|
// count duplicates
|
|
36
|
-
if (acc[fk
|
|
37
|
-
acc[fk
|
|
36
|
+
if (acc[fk.prefix])
|
|
37
|
+
acc[fk.prefix]++;
|
|
38
38
|
else
|
|
39
|
-
acc[fk
|
|
39
|
+
acc[fk.prefix] = 1;
|
|
40
40
|
|
|
41
41
|
// check for name clash with existing elements
|
|
42
|
-
if (parent[prop][fk
|
|
42
|
+
if (parent[prop][fk.prefix] && isDeepEqual(element, parent[prop][fk.prefix], true)) {
|
|
43
43
|
// error location is the colliding element
|
|
44
|
-
error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-exists', name: fk
|
|
44
|
+
error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-exists', name: fk.prefix, art: elementName });
|
|
45
45
|
}
|
|
46
46
|
// attach a proper $path
|
|
47
47
|
setProp(element, '$path', elementPath);
|
|
@@ -50,7 +50,7 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
50
50
|
|
|
51
51
|
// set default for single foreign key from association (if available)
|
|
52
52
|
if (element.default?.val !== undefined && generatedForeignKeys.length === 1)
|
|
53
|
-
generatedForeignKeys[0]
|
|
53
|
+
generatedForeignKeys[0].foreignKey.default = element.default;
|
|
54
54
|
|
|
55
55
|
// check for duplicate foreign keys
|
|
56
56
|
Object.entries(refCount).forEach(([ name, occ ]) => {
|
|
@@ -63,8 +63,8 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
adaptAnnotationsRefs(generatedForeignKeys, csnUtils, messageFunctions);
|
|
66
|
-
setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk
|
|
67
|
-
orderedElements.push(...generatedForeignKeys);
|
|
66
|
+
setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk.prefix));
|
|
67
|
+
orderedElements.push(...generatedForeignKeys.map(gfk => [ gfk.prefix, gfk.foreignKey ]));
|
|
68
68
|
|
|
69
69
|
});
|
|
70
70
|
|
|
@@ -74,123 +74,127 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
74
74
|
}, Object.create(null));
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function createForeignKeysForElement(
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
function createForeignKeysForElement(path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalKey = {} ) {
|
|
78
|
+
const special$self = !csn?.definitions?.$self && '$self';
|
|
79
|
+
const isInspectRefResult = !Array.isArray(path);
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
let fks = [];
|
|
82
|
+
if (!element)
|
|
83
|
+
return fks;
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
let finalElement = element;
|
|
86
|
+
let finalTypeName;
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
// resolve derived type
|
|
89
|
+
if (element.type && !isBuiltinType(element.type) && element.type !== special$self) {
|
|
90
|
+
const tmpElt = csnUtils.effectiveType(element);
|
|
91
|
+
// effective type resolves to structs and enums only but not scalars
|
|
92
|
+
if (Object.keys(tmpElt).length) {
|
|
93
|
+
finalElement = tmpElt;
|
|
94
|
+
finalTypeName = finalElement.$path[1];
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// unwind a derived type chain to a scalar type
|
|
98
|
+
while (finalElement?.type && !isBuiltinType(finalElement?.type)) {
|
|
99
|
+
finalTypeName = finalElement.type;
|
|
100
|
+
finalElement = csn.definitions[finalElement.type];
|
|
101
|
+
}
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
|
-
}
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
105
|
+
if (!finalElement)
|
|
106
|
+
return [];
|
|
107
|
+
|
|
108
|
+
// main if for this function
|
|
109
|
+
// the element is a managed association
|
|
110
|
+
if (csnUtils.isManagedAssociation(finalElement)) {
|
|
111
|
+
finalElement.keys.forEach((key, keyIndex) => {
|
|
112
|
+
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
113
|
+
const alias = key.as || implicitAs(key.ref);
|
|
114
|
+
const result = csnUtils.inspectRef(continuePath);
|
|
115
|
+
let gfks = createForeignKeysForElement(result, result.art, alias, csn, options, pathDelimiter, lvl + 1,
|
|
116
|
+
lvl === 0 ? key : originalKey);
|
|
117
|
+
if (lvl === 0) {
|
|
118
|
+
gfks.forEach(gfk => gfk.keyAnnotations.push( ...copyAnnotations(key, gfk.foreignKey)));
|
|
119
|
+
Object.keys(key).forEach( prop => {
|
|
120
|
+
// once applied -> remove the annotations from the keys array, to keep the OData CSN size as small as possible
|
|
121
|
+
if (prop[0] === '@')
|
|
122
|
+
delete key[prop]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
fks = fks.concat(gfks);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// the element is a structure
|
|
129
|
+
else if (finalElement.elements) {
|
|
130
|
+
Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
|
|
131
|
+
// Skip already produced foreign keys
|
|
132
|
+
if (!elem['@odata.foreignKey4']) {
|
|
133
|
+
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
134
|
+
fks = fks.concat(createForeignKeysForElement(continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalKey));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// we have reached a leaf element, create a foreign key
|
|
139
|
+
else if ((finalElement.type == null || isBuiltinType(finalElement.type)) && !finalElement.on) {
|
|
140
|
+
const newFk = Object.create(null);
|
|
141
|
+
[ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
|
|
142
|
+
// copy props from original element to preserve derived types!
|
|
143
|
+
if (element[prop] !== undefined)
|
|
144
|
+
newFk[prop] = element[prop];
|
|
145
|
+
});
|
|
146
|
+
return [ { prefix, foreignKey: newFk, originalKey, keyAnnotations: [] } ];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fks.forEach((fk) => {
|
|
150
|
+
// prepend current prefix
|
|
151
|
+
fk.prefix = `${prefix}${pathDelimiter}${fk.prefix}`;
|
|
152
|
+
// if this is the entry association, decorate the final foreign keys with the association props/annos
|
|
117
153
|
if (lvl === 0) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
fk.foreignKey['@odata.foreignKey4'] = prefix;
|
|
155
|
+
|
|
156
|
+
const fkPath = path.slice(0, path.length - 1);
|
|
157
|
+
fkPath.push(fk.prefix);
|
|
158
|
+
setProp(fk.foreignKey, '$path', fkPath);
|
|
159
|
+
|
|
160
|
+
const allowedOverwriteAnnotationNames = ['@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`)];
|
|
161
|
+
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !allowedOverwriteAnnotationNames.includes(pn));
|
|
162
|
+
copyAnnotations(element, fk.foreignKey, false, {}, validAnnoNames);
|
|
163
|
+
const overwriteAnnoNames = Object.keys(element).filter(pn => allowedOverwriteAnnotationNames.includes(pn));
|
|
164
|
+
copyAnnotations(element, fk.foreignKey, true, {}, overwriteAnnoNames);
|
|
165
|
+
|
|
166
|
+
// propagate not null to final foreign key
|
|
167
|
+
for (const prop of [ 'notNull', 'key' ]) {
|
|
168
|
+
if (element[prop] !== undefined)
|
|
169
|
+
fk.foreignKey[prop] = element[prop];
|
|
170
|
+
}
|
|
171
|
+
if (element.$location)
|
|
172
|
+
setProp(fk.foreignKey, '$location', element.$location);
|
|
134
173
|
}
|
|
135
174
|
});
|
|
136
|
-
|
|
137
|
-
// we have reached a leaf element, create a foreign key
|
|
138
|
-
else if ((finalElement.type == null || isBuiltinType(finalElement.type)) && !finalElement.on) {
|
|
139
|
-
const newFk = Object.create(null);
|
|
140
|
-
[ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
|
|
141
|
-
// copy props from original element to preserve derived types!
|
|
142
|
-
if (element[prop] !== undefined)
|
|
143
|
-
newFk[prop] = element[prop];
|
|
144
|
-
});
|
|
145
|
-
return [ [ prefix, newFk, originalkey ] ];
|
|
146
|
-
}
|
|
175
|
+
return fks;
|
|
147
176
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (element.$location)
|
|
168
|
-
setProp(fk[1], '$location', element.$location);
|
|
177
|
+
/**
|
|
178
|
+
* Get the path to continue resolving references
|
|
179
|
+
*
|
|
180
|
+
* If we are currently inside of a type, we need to start our path fresh from that given type.
|
|
181
|
+
* Otherwise, we would try to resolve .elements on a thing that does not exist.
|
|
182
|
+
*
|
|
183
|
+
* We also respect if we have a previous inspectRef result as our base.
|
|
184
|
+
*
|
|
185
|
+
* @param {Array} additions
|
|
186
|
+
* @returns {CSN.Path}
|
|
187
|
+
*/
|
|
188
|
+
function getContinuePath( additions ) {
|
|
189
|
+
if (csn.definitions[finalElement.type])
|
|
190
|
+
return [ 'definitions', finalElement.type, ...additions ];
|
|
191
|
+
else if (finalTypeName)
|
|
192
|
+
return [ 'definitions', finalTypeName, ...additions ];
|
|
193
|
+
else if (isInspectRefResult)
|
|
194
|
+
return [ path, ...additions ];
|
|
195
|
+
return [ ...path, ...additions ];
|
|
169
196
|
}
|
|
170
|
-
});
|
|
171
|
-
return fks;
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Get the path to continue resolving references
|
|
175
|
-
*
|
|
176
|
-
* If we are currently inside of a type, we need to start our path fresh from that given type.
|
|
177
|
-
* Otherwise, we would try to resolve .elements on a thing that does not exist.
|
|
178
|
-
*
|
|
179
|
-
* We also respect if we have a previous inspectRef result as our base.
|
|
180
|
-
*
|
|
181
|
-
* @param {Array} additions
|
|
182
|
-
* @returns {CSN.Path}
|
|
183
|
-
*/
|
|
184
|
-
function getContinuePath( additions ) {
|
|
185
|
-
if (csn.definitions[finalElement.type])
|
|
186
|
-
return [ 'definitions', finalElement.type, ...additions ];
|
|
187
|
-
else if (finalTypeName)
|
|
188
|
-
return [ 'definitions', finalTypeName, ...additions ];
|
|
189
|
-
else if (isInspectRefResult)
|
|
190
|
-
return [ path, ...additions ];
|
|
191
|
-
return [ ...path, ...additions ];
|
|
192
197
|
}
|
|
193
198
|
}
|
|
194
|
-
}
|
|
195
199
|
|
|
196
200
|
module.exports = createForeignKeyElements;
|
|
@@ -14,7 +14,7 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
14
14
|
const { assignAnnotation } = require('../../edm/edmUtils');
|
|
15
15
|
|
|
16
16
|
function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options) {
|
|
17
|
-
const allMgdAssocDefs = [];
|
|
17
|
+
const allMgdAssocDefs = [];
|
|
18
18
|
forEachDefinition(csn, (def, defName) => {
|
|
19
19
|
if (def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
|
|
20
20
|
['elements', 'params'].forEach(dictName => {
|
|
@@ -345,7 +345,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
|
|
|
345
345
|
// try to find rightmost 'items', terminate if association comes first.
|
|
346
346
|
let i = links.length-1;
|
|
347
347
|
const getProp = (propName) => links[i].art?.[propName];
|
|
348
|
-
|
|
348
|
+
|
|
349
349
|
let hasItems = false;
|
|
350
350
|
for(; i >= 0 && !getProp('target') && !hasItems; i--) {
|
|
351
351
|
const art = links[i].art;
|
|
@@ -361,7 +361,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
|
|
|
361
361
|
name: refCheck.eltLocationStr,
|
|
362
362
|
'#': 'flatten_builtin'
|
|
363
363
|
});
|
|
364
|
-
}
|
|
364
|
+
}
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
367
|
}
|
|
@@ -467,7 +467,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
|
|
|
467
467
|
let comeFromSameDef = flatElt.$defPath.slice(0, flatElt.$defPath.length - 1).join('.') === se[1].$defPath.slice(0, se[1].$defPath.length - 1).join('.');
|
|
468
468
|
return (comeFromSameDef && (se[1]['@odata.foreignKey4'] && se[1]['@odata.foreignKey4'] === structuredAssocName) && se[0].startsWith(flatEltName))
|
|
469
469
|
});
|
|
470
|
-
|
|
470
|
+
|
|
471
471
|
generatedForeignKeysForAssoc.forEach(gfk => gfk[1]['@odata.foreignKey4'] = flatEltName);
|
|
472
472
|
// reassign the generated foreign keys for current assoc in order to assign
|
|
473
473
|
// correct values for $generatedFieldName later on during flattenManagedAssocsAsKeys();
|
|
@@ -565,7 +565,7 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
565
565
|
if (insideKeys(path))
|
|
566
566
|
setProp(parent, '_art', art);
|
|
567
567
|
const lastRef = ref[ref.length - 1];
|
|
568
|
-
const fn = (suspend = false, suspendPos = 0,
|
|
568
|
+
const fn = (suspend = false, suspendPos = 0,
|
|
569
569
|
refFilter = (_parent) => true) => {
|
|
570
570
|
if (refFilter(parent)) {
|
|
571
571
|
const scopedPath = [ ...parent.$path ];
|
|
@@ -573,9 +573,11 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
573
573
|
// full path into target, uncomment this line and
|
|
574
574
|
// comment/remove setProp in expansion.js
|
|
575
575
|
// setProp(parent, '$structRef', parent.ref);
|
|
576
|
+
const flattenParameters = true; // structured parameters are flattened
|
|
576
577
|
const [ newRef, refChanged ] = flattenStructStepsInRef(ref,
|
|
577
578
|
scopedPath, links, scope, resolvedLinkTypes,
|
|
578
|
-
suspend, suspendPos, parent.$bparam
|
|
579
|
+
suspend, suspendPos, parent.$bparam,
|
|
580
|
+
flattenParameters);
|
|
579
581
|
parent.ref = newRef;
|
|
580
582
|
resolved.set(parent, { links, art, scope });
|
|
581
583
|
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
@@ -628,7 +630,7 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
628
630
|
annoExpr['='] = true;
|
|
629
631
|
});
|
|
630
632
|
}
|
|
631
|
-
else
|
|
633
|
+
else
|
|
632
634
|
adaptRefs.push(fn);
|
|
633
635
|
},
|
|
634
636
|
}
|
|
@@ -679,7 +681,7 @@ function replaceManagedAssocsAsKeys(allFlatManagedAssocDefinitions, csnUtils) {
|
|
|
679
681
|
return done;
|
|
680
682
|
}
|
|
681
683
|
});
|
|
682
|
-
|
|
684
|
+
|
|
683
685
|
function cloneAndExtendRef(keyAssocKey, key) {
|
|
684
686
|
let newKey = { ref: [`${key.ref.join('_')}_${keyAssocKey.as || keyAssocKey.ref.join('_')}`] };
|
|
685
687
|
setProp(newKey, '_art', keyAssocKey._art);
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
// different backends.
|
|
5
5
|
// The sibling of model/transform/TransformUtil.js which works with compacted new CSN.
|
|
6
6
|
|
|
7
|
-
const { setProp } = require('../base/model');
|
|
7
|
+
const { setProp, isBetaEnabled } = require('../base/model');
|
|
8
8
|
|
|
9
|
-
const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand,
|
|
9
|
+
const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand, isDeepEqual } = require('../model/csnUtils');
|
|
10
10
|
const { getUtils } = require('../model/csnUtils');
|
|
11
11
|
const { typeParameters } = require('../compiler/builtins');
|
|
12
|
-
const { isBuiltinType
|
|
12
|
+
const { isBuiltinType } = require('../base/builtins');
|
|
13
13
|
const { ModelError, CompilerAssertion} = require('../base/error');
|
|
14
14
|
const { forEach } = require('../utils/objectUtils');
|
|
15
15
|
const { cloneCsnNonDict, cloneCsnDict } = require('../model/cloneCsn');
|
|
@@ -44,6 +44,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
44
44
|
toFinalBaseType,
|
|
45
45
|
createExposingProjection,
|
|
46
46
|
createAndAddDraftAdminDataProjection,
|
|
47
|
+
isValidDraftAdminDataMessagesType,
|
|
47
48
|
createScalarElement,
|
|
48
49
|
createAssociationElement,
|
|
49
50
|
createAssociationPathComparison,
|
|
@@ -195,24 +196,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
195
196
|
'@odata.Collation': 1,
|
|
196
197
|
'@odata.Unicode': 1,
|
|
197
198
|
} : {};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
for(const annoName of annoNames) {
|
|
201
|
-
if(flatElem.keys && isAnnotationExpression(elem[annoName])) {
|
|
202
|
-
flatElem.keys.forEach(key => {
|
|
203
|
-
if(!key[annoName]) {
|
|
204
|
-
key[annoName] = flatElem[annoName];
|
|
205
|
-
transformAnnotationExpression(key, annoName, {
|
|
206
|
-
ref: (parent, prop, ref) => {
|
|
207
|
-
if(ref[0] !== '$self')
|
|
208
|
-
parent.ref = ['$self', ...parent.ref];
|
|
209
|
-
}
|
|
210
|
-
})
|
|
211
|
-
}
|
|
212
|
-
})
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
199
|
+
|
|
200
|
+
copyAnnotations(elem, flatElem, false, excludes);
|
|
216
201
|
|
|
217
202
|
// Copy selected type properties
|
|
218
203
|
const props = ['key', 'virtual', 'masked', 'viaAll'];
|
|
@@ -243,15 +228,20 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
243
228
|
* @param {bool} [suspend] suspend flattening by caller until association path step
|
|
244
229
|
* @param {int} [suspendPos] suspend if starting pos is lower or equal to suspendPos and suspend is true
|
|
245
230
|
* @param {bool} [revokeAtSuspendPos] revoke suspension after suspendPos (binding parameter path use case)
|
|
231
|
+
* @param {bool} [flattenParameters] Whether to flatten references into structured parameters. OData flattens parameters, SQL/for.effective does not.
|
|
232
|
+
*
|
|
233
|
+
* @todo: Refactor to take config object instead of N boolean arguments.
|
|
246
234
|
* @returns [string[], bool]
|
|
247
235
|
*/
|
|
248
|
-
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0, revokeAtSuspendPos=false) {
|
|
236
|
+
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0, revokeAtSuspendPos=false, flattenParameters = false) {
|
|
237
|
+
// A path is absolute if it starts with $self or a parameter. Then we must not flatten the first path step.
|
|
238
|
+
const pathIsAbsolute = scope === '$self' || (!flattenParameters && scope === 'param');
|
|
249
239
|
// Refs of length 1 cannot contain steps - no need to check
|
|
250
|
-
if (ref.length < 2 || (
|
|
240
|
+
if (ref.length < 2 || (pathIsAbsolute && ref.length === 2)) {
|
|
251
241
|
return [ ref, false ];
|
|
252
242
|
}
|
|
253
243
|
|
|
254
|
-
const result =
|
|
244
|
+
const result = pathIsAbsolute ? [ref[0]] : [];
|
|
255
245
|
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
|
|
256
246
|
if(!links && !scope) { // calculate JIT if not supplied
|
|
257
247
|
const res = inspectRef(path);
|
|
@@ -262,7 +252,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
262
252
|
return [ ref, false ];
|
|
263
253
|
|
|
264
254
|
// Don't process a leading $self - it will a .art with .elements!
|
|
265
|
-
let i =
|
|
255
|
+
let i = pathIsAbsolute ? 1 : 0;
|
|
266
256
|
|
|
267
257
|
// read property from resolved path link
|
|
268
258
|
const art = (propName) =>
|
|
@@ -413,6 +403,11 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
413
403
|
if (!draftAdminDataEntity) {
|
|
414
404
|
draftAdminDataEntity = createAndAddDraftAdminDataEntity();
|
|
415
405
|
model.definitions['DRAFT.DraftAdministrativeData'] = draftAdminDataEntity;
|
|
406
|
+
if (isBetaEnabled(options, 'draftMessages')
|
|
407
|
+
&& options.transformation === 'odata'
|
|
408
|
+
&& !model.definitions['DRAFT.DraftAdministrativeData_DraftMessage']) {
|
|
409
|
+
model.definitions['DRAFT.DraftAdministrativeData_DraftMessage'] = createDraftAdminDataMessagesType();
|
|
410
|
+
}
|
|
416
411
|
if(options.tenantDiscriminator && options.transformation !== 'odata')
|
|
417
412
|
addTenantFieldToArt(model.definitions['DRAFT.DraftAdministrativeData'], options);
|
|
418
413
|
}
|
|
@@ -481,10 +476,48 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
481
476
|
draftIsProcessedByMe.DraftIsProcessedByMe['@Common.Label'] = '{i18n>Draft_DraftIsProcessedByMe}';
|
|
482
477
|
addElement(draftIsProcessedByMe, artifact, artifactName);
|
|
483
478
|
|
|
479
|
+
if (isBetaEnabled(options, 'draftMessages')) {
|
|
480
|
+
const messages = { DraftMessages: { } };
|
|
481
|
+
if (options.transformation === 'odata') {
|
|
482
|
+
messages.DraftMessages = { items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } };
|
|
483
|
+
} else {
|
|
484
|
+
messages.DraftMessages = { type: 'cds.LargeString' };
|
|
485
|
+
}
|
|
486
|
+
messages.DraftMessages['@cds.api.ignore'] = true;
|
|
487
|
+
addElement(messages , artifact, artifactName);
|
|
488
|
+
}
|
|
489
|
+
|
|
484
490
|
return artifact;
|
|
485
491
|
}
|
|
486
492
|
}
|
|
487
493
|
|
|
494
|
+
// Create the artificial 'DRAFT.Draf tAdministrativeData_DraftMessage' type
|
|
495
|
+
// for the beta feature 'draftMessages'
|
|
496
|
+
function createDraftAdminDataMessagesType() {
|
|
497
|
+
const messagesType = {
|
|
498
|
+
kind: 'type',
|
|
499
|
+
elements: Object.create(null)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
addElement(createScalarElement('code', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
|
|
503
|
+
addElement(createScalarElement('message', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
|
|
504
|
+
addElement(createScalarElement('target', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
|
|
505
|
+
addElement({ 'additionalTargets': createScalarElement('items', 'cds.String') }, messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
|
|
506
|
+
addElement(createScalarElement('transition', 'cds.Boolean'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
|
|
507
|
+
addElement(createScalarElement('numericSeverity', 'cds.UInt8'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
|
|
508
|
+
addElement(createScalarElement('longtextUrl', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
|
|
509
|
+
// the tag element not needed for now, but might be added later on
|
|
510
|
+
// addElement(createScalarElement('tag', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
|
|
511
|
+
// setAnnotation(messagesType.tag, '@cds.api.ignore', true);
|
|
512
|
+
return messagesType;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Checks if the given definition is a valid 'DRAFT.DraftAdministrativeData_DraftMessage' type
|
|
516
|
+
function isValidDraftAdminDataMessagesType(def) {
|
|
517
|
+
const expectedType = createDraftAdminDataMessagesType();
|
|
518
|
+
return isDeepEqual(def, expectedType, false);
|
|
519
|
+
}
|
|
520
|
+
|
|
488
521
|
// Create an artificial scalar element 'elemName' with final type 'typeName'.
|
|
489
522
|
// Make the element a key element if 'isKey' is true.
|
|
490
523
|
// Add a default value 'defaultVal' if supplied
|
|
@@ -50,6 +50,7 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
50
50
|
// FIXME: Move this somewhere more appropriate
|
|
51
51
|
const newCsn = compactModel(model, compileOptions);
|
|
52
52
|
//require('fs').writeFileSync('./csnoutput_a2j.json', JSON.stringify(newCsn, null,2))
|
|
53
|
+
//console.log('CSN returned by A2J:',JSON.stringify(newCsn,null,2))
|
|
53
54
|
return newCsn;
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -607,6 +608,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
607
608
|
args: [ constructPathNode( [ srcAlias, prefixFK(assoc.$elementPrefix, assoc.$flatSrcFKs[i]) ] ),
|
|
608
609
|
constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ] });
|
|
609
610
|
}
|
|
611
|
+
// TODO: why inner "parenthesise" - comparison in `and`?
|
|
610
612
|
return parenthesise((args.length > 1 ? { op: { val: 'and' }, args: [ ...args.map(parenthesise) ] } : args[0] ));
|
|
611
613
|
}
|
|
612
614
|
else {
|
|
@@ -689,7 +691,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
689
691
|
// clone ON condition with rewritten paths and substituted backlink conditions
|
|
690
692
|
function cloneOnCondition(expr)
|
|
691
693
|
{
|
|
692
|
-
|
|
694
|
+
const op = expr.op?.val;
|
|
695
|
+
if(op === 'xpr' || op === 'ixpr' || op === 'nary')
|
|
693
696
|
return cloneOnCondExprStream(expr);
|
|
694
697
|
else
|
|
695
698
|
return cloneOnCondExprTree(expr);
|
|
@@ -700,7 +703,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
700
703
|
const result = { op: { val: expr.op.val }, args: [ ] };
|
|
701
704
|
for(let i = 0; i < args.length; i++)
|
|
702
705
|
{
|
|
703
|
-
|
|
706
|
+
const op = args[i].op?.val;
|
|
707
|
+
if(op === 'xpr' || op === 'ixpr' || op === 'nary')
|
|
704
708
|
{
|
|
705
709
|
result.args.push(cloneOnCondition(args[i]));
|
|
706
710
|
}
|
|
@@ -1890,15 +1894,15 @@ function walk(node, env)
|
|
|
1890
1894
|
function walkPath(node, env)
|
|
1891
1895
|
{
|
|
1892
1896
|
const path = node.path;
|
|
1893
|
-
// Ignore paths that
|
|
1897
|
+
// Ignore paths that have no artifact (function calls etc.) or that are builtins ($now, $user)
|
|
1894
1898
|
// or that are parameters ($parameters or escaped paths (':')
|
|
1895
1899
|
//path.length && path[ path.length-1 ]._artifact
|
|
1896
|
-
const art = path
|
|
1900
|
+
const art = path?.length && path[path.length-1]._artifact;
|
|
1897
1901
|
|
|
1898
1902
|
// regardless of the position in the query, ignore paths that have virtual path steps
|
|
1899
|
-
if(art && path.some(ps => ps._artifact
|
|
1903
|
+
if(art && path.some(ps => ps._artifact?.virtual?.val))
|
|
1900
1904
|
return path;
|
|
1901
|
-
if(art && !internalArtifactKinds.includes(art.kind))
|
|
1905
|
+
if(art && !internalArtifactKinds.includes(art.kind) && node.scope !== 'param')
|
|
1902
1906
|
{
|
|
1903
1907
|
if(env.callback)
|
|
1904
1908
|
{
|
|
@@ -21,8 +21,12 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
|
|
|
21
21
|
|
|
22
22
|
forEachDefinition(csn, (artifact, name, prop, path) => {
|
|
23
23
|
if (artifact.query || artifact.projection) {
|
|
24
|
+
// For events and types, the query is only used for inferring the element signature.
|
|
25
|
+
// There, we don't want to set `@Core.Computed`.
|
|
26
|
+
const queryForSignatureOnly = artifact.kind === 'type' || artifact.kind === 'event';
|
|
24
27
|
forAllQueries(getNormalizedQuery(artifact).query, (query) => {
|
|
25
|
-
|
|
28
|
+
const isTopLevelQuery = query.SELECT === (artifact.query?.SELECT || artifact.projection);
|
|
29
|
+
if (query.SELECT && (!isTopLevelQuery || !queryForSignatureOnly))
|
|
26
30
|
traverseQueryAndAttachCoreComputed(query, query.SELECT.elements || artifact.elements);
|
|
27
31
|
}, path);
|
|
28
32
|
}
|
package/package.json
CHANGED
|
@@ -54,3 +54,8 @@ entity View as select from Base {
|
|
|
54
54
|
In the corrected view above, the association `secondary` gets an explicit ON
|
|
55
55
|
condition. For this to work it is necessary to add `secondary_id` to the
|
|
56
56
|
selection list, that means, we have to explicitly use the foreign key.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Related Messages
|
|
60
|
+
|
|
61
|
+
- `rewrite-undefined-key`
|