@sap/cds-compiler 2.5.0 → 2.10.4
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 +191 -9
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +33 -3
- package/lib/api/main.js +29 -101
- package/lib/api/options.js +15 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +63 -9
- package/lib/base/messages.js +63 -21
- package/lib/base/model.js +2 -3
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +25 -7
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +16 -7
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +99 -42
- package/lib/compiler/index.js +73 -27
- package/lib/compiler/resolver.js +288 -157
- package/lib/compiler/shared.js +31 -11
- package/lib/edm/annotations/genericTranslation.js +182 -186
- package/lib/edm/csn2edm.js +103 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -114
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -1
- package/lib/gen/language.tokens +57 -53
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +770 -744
- package/lib/gen/languageLexer.tokens +49 -46
- package/lib/gen/languageParser.js +4713 -4279
- package/lib/json/from-csn.js +103 -45
- package/lib/json/to-csn.js +296 -117
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +21 -12
- package/lib/language/language.g4 +99 -31
- package/lib/main.d.ts +81 -3
- package/lib/main.js +30 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +329 -142
- package/lib/model/csnUtils.js +235 -58
- package/lib/model/enrichCsn.js +18 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +37 -20
- package/lib/optionProcessor.js +9 -3
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +112 -33
- package/lib/render/toHdbcds.js +134 -64
- package/lib/render/toSql.js +91 -38
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +29 -13
- package/lib/transform/db/draft.js +8 -6
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +284 -63
- package/lib/transform/forHanaNew.js +98 -381
- package/lib/transform/forOdataNew.js +21 -22
- package/lib/transform/localized.js +37 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +134 -78
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- package/lib/utils/string.js +0 -17
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { copyAnnotations } = require('../../model/csnUtils');
|
|
4
|
-
const { setProp } = require('../../base/model');
|
|
5
4
|
const { cloneCsn, forEachDefinition } = require('../../model/csnUtils');
|
|
6
5
|
const { attachPathOnPartialCSN } = require('./attachPath');
|
|
7
6
|
|
|
@@ -50,13 +49,12 @@ const { isNotNull, setNotNull, setUpNotNull } = function () {
|
|
|
50
49
|
* During the OData transformations in flat-mode, all structured elements will be flattened.
|
|
51
50
|
* This module performs the complete flattening.
|
|
52
51
|
* It also provides information to the reference flattener: elements produced for specific path in the CSN structure.
|
|
53
|
-
* Each generated element gets hidden attributes:
|
|
54
|
-
* - _flatElementNameWithDots - names in the element path concatenated with dot
|
|
55
52
|
* @param {CSN.Model} csn CSN-object to flatten
|
|
56
53
|
* @param {*} csnUtils instances of utility functions
|
|
57
54
|
* @param {*} options
|
|
58
55
|
* @param {*} referenceFlattener
|
|
59
56
|
* @param {Function} error
|
|
57
|
+
* @param {Function} isExternalServiceMember returns true for an artifact that is part of an external service
|
|
60
58
|
*/
|
|
61
59
|
function flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember) {
|
|
62
60
|
forEachDefinition(csn, (def, defName, propertyName, path) =>
|
|
@@ -76,17 +74,22 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
|
|
|
76
74
|
if (definition.kind !== 'entity' && definition.kind !== 'view')
|
|
77
75
|
return;
|
|
78
76
|
|
|
79
|
-
let { newFlatElements } = flattenStructure(definition, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
77
|
+
let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
80
78
|
|
|
81
79
|
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));
|
|
82
|
-
|
|
83
80
|
definition.elements = newFlatElements;
|
|
81
|
+
|
|
82
|
+
if (definition.params) {
|
|
83
|
+
let { newFlatElements } = flattenStructure(definition.params, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
84
|
+
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('params'));
|
|
85
|
+
definition.params = newFlatElements;
|
|
86
|
+
}
|
|
84
87
|
} // flattenDefinition
|
|
85
88
|
|
|
86
89
|
/**
|
|
87
90
|
* Flattens structured element by calling element flattener for each structured child.
|
|
88
91
|
* Returns a dictionary containing all the new elements for the given structure.
|
|
89
|
-
* @param {*}
|
|
92
|
+
* @param {*} dictionary to flatten
|
|
90
93
|
* @param {CSN.Path} path the path of the structure in the CSN tree
|
|
91
94
|
* @param {*} csnUtils
|
|
92
95
|
* @param {Function} error Error message function
|
|
@@ -95,14 +98,14 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
|
|
|
95
98
|
* @param {*} [newFlatElements]
|
|
96
99
|
* @param {boolean} [isTopLevelElement] states if this is a top level element
|
|
97
100
|
*/
|
|
98
|
-
function flattenStructure(
|
|
101
|
+
function flattenStructure(dictionary, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
|
|
99
102
|
newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
|
|
100
103
|
|
|
101
|
-
if (!isTopLevelElement) addPropsForPropagationFromElement(
|
|
104
|
+
if (!isTopLevelElement) addPropsForPropagationFromElement(dictionary);
|
|
102
105
|
|
|
103
106
|
let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
|
|
104
107
|
|
|
105
|
-
|
|
108
|
+
dictionary && Object.entries(dictionary).forEach(([elementName, element]) => {
|
|
106
109
|
let currPath = path.concat('elements', elementName);
|
|
107
110
|
|
|
108
111
|
if (isTopLevelElement) {
|
|
@@ -120,8 +123,8 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
120
123
|
addPropsForPropagationFromElement(element);
|
|
121
124
|
|
|
122
125
|
// if the child element is structured itself -> needs to be flattened
|
|
123
|
-
const
|
|
124
|
-
let result = flattenStructure(
|
|
126
|
+
const elements = element.elements || csnUtils.getFinalBaseType(element.type).elements;
|
|
127
|
+
let result = flattenStructure(elements, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isNotNull());
|
|
125
128
|
generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements
|
|
126
129
|
|
|
127
130
|
} else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener
|
|
@@ -129,7 +132,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
129
132
|
let elementNameWithDots = elementPathInStructure.concat(elementName).join('.');
|
|
130
133
|
addNewElementToResult(element, newElementName, elementNameWithDots, currPath);
|
|
131
134
|
}
|
|
132
|
-
|
|
133
135
|
});
|
|
134
136
|
|
|
135
137
|
if (referenceFlattener) {
|
|
@@ -137,30 +139,29 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
137
139
|
}
|
|
138
140
|
return { newFlatElements, generatedNewFlatElementsNames };
|
|
139
141
|
|
|
140
|
-
|
|
141
142
|
// adds newly created element into the final dictionary of elements
|
|
142
143
|
function addNewElementToResult(element, elementName, elementNameWithDots, path) {
|
|
143
144
|
if (newFlatElements[elementName]) {
|
|
144
145
|
error(null, path, `Generated element ${elementName} conflicts with other generated element`);
|
|
145
146
|
} else {
|
|
146
|
-
let
|
|
147
|
+
let newPath = path.slice(0, 2).concat('elements', elementName);
|
|
148
|
+
let newElement = createNewElement(element, elementNameWithDots, newPath);
|
|
147
149
|
newFlatElements[elementName] = newElement;
|
|
148
150
|
generatedNewFlatElementsNames.push(elementName);
|
|
149
151
|
|
|
150
152
|
if (referenceFlattener) {
|
|
151
|
-
let newPath = path.slice(0, 2).concat('elements', elementName);
|
|
152
153
|
referenceFlattener.registerElementTransition(path, newPath);
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
} // addNewElementToResult
|
|
156
157
|
|
|
157
158
|
// creates new element by copying the properties of the originating element
|
|
158
|
-
function createNewElement(element, elementNameWithDots) {
|
|
159
|
+
function createNewElement(element, elementNameWithDots, path) {
|
|
159
160
|
let newElement = cloneCsn(element, options);
|
|
160
161
|
if (!isTopLevelElement) propagatePropsToElement(newElement);
|
|
161
162
|
if (isNotNull() === undefined) delete newElement.notNull;
|
|
162
|
-
if (!isTopLevelElement) {
|
|
163
|
-
|
|
163
|
+
if (!isTopLevelElement && referenceFlattener) {
|
|
164
|
+
referenceFlattener.setElementNameWithDots(path, elementNameWithDots);
|
|
164
165
|
}
|
|
165
166
|
return newElement;
|
|
166
167
|
} // createNewElement
|
|
@@ -25,7 +25,6 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
25
25
|
// collect in this variable all the newly exposed types
|
|
26
26
|
const exposedStructTypes = [];
|
|
27
27
|
const schemas = Object.create(null);
|
|
28
|
-
|
|
29
28
|
// walk through the definitions of the given CSN and expose types where needed
|
|
30
29
|
forEachDefinition(csn, (def, defName, propertyName, path) => {
|
|
31
30
|
// we do expose types only for definition from inside services
|
|
@@ -33,11 +32,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
33
32
|
if (serviceName) {
|
|
34
33
|
if (['type', 'entity', 'view'].includes(def.kind)) {
|
|
35
34
|
forEachMember(def, (element, elementName, propertyName, path) => {
|
|
36
|
-
if (
|
|
35
|
+
if (['elements', 'params'].includes(propertyName)) {
|
|
37
36
|
const artificialtName = `${isMultiSchema ?
|
|
38
37
|
defNameWithoutServiceOrContextName(defName, serviceName)
|
|
39
38
|
: defNameWithoutServiceOrContextName(defName, serviceName).replace(/\./g, '_')}_${elementName}`;
|
|
40
|
-
exposeTypeOf(element, elementName, defName, serviceName, artificialtName, path);
|
|
39
|
+
exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, artificialtName, path);
|
|
41
40
|
}
|
|
42
41
|
}, path);
|
|
43
42
|
}
|
|
@@ -64,11 +63,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
64
63
|
* @param {string} artificialName
|
|
65
64
|
* @param {CSN.Path} path
|
|
66
65
|
*/
|
|
67
|
-
function exposeTypeOf(node, memberName, defName, service, artificialName, path) {
|
|
66
|
+
function exposeTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
|
|
68
67
|
if (isArrayed(node))
|
|
69
|
-
exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path);
|
|
68
|
+
exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path);
|
|
70
69
|
else if (csnUtils.isStructured(node))
|
|
71
|
-
exposeStructTypeOf(node, memberName, defName, service, artificialName, path);
|
|
70
|
+
exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
@@ -90,12 +89,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
90
89
|
function exposeTypesOfAction(action, actionName, defName, service, path) {
|
|
91
90
|
if (action.returns) {
|
|
92
91
|
const artificialName = `return_${actionName.replace(/\./g, '_')}`;
|
|
93
|
-
exposeTypeOf(action.returns, actionName, defName, service, artificialName, path.concat(['returns']));
|
|
92
|
+
exposeTypeOf(action.returns, false, actionName, defName, service, artificialName, path.concat(['returns']));
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
|
|
97
96
|
const artificialName = `param_${actionName.replace(/\./g, '_')}_${paramName}`;
|
|
98
|
-
exposeTypeOf(param, actionName, defName, service, artificialName, path.concat(['params', paramName]));
|
|
97
|
+
exposeTypeOf(param, false, actionName, defName, service, artificialName, path.concat(['params', paramName]));
|
|
99
98
|
});
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -108,9 +107,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
108
107
|
* @param {String} service
|
|
109
108
|
* @param {String} artificialName
|
|
110
109
|
*/
|
|
111
|
-
function exposeStructTypeOf(node, memberName, defName, service, artificialName, path, parentName) {
|
|
110
|
+
function exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path, parentName) {
|
|
111
|
+
if (node.items) exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path);
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
// start conservative, assume we're in a named type
|
|
114
|
+
let isAnonymous = false;
|
|
114
115
|
|
|
115
116
|
if (isExposableStructure(node)) {
|
|
116
117
|
let typeDef = node.type ? csnUtils.getCsnDef(node.type) : /* structure|anonymous type */ node;
|
|
@@ -124,6 +125,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
124
125
|
// and the new target. Consequently, we now have both type and elements properties in this case, and the elements should be taken as a priority
|
|
125
126
|
// as the correct target is there and no longer in the type definition
|
|
126
127
|
let newTypeElements = (node.type && node.elements) ? node.elements : typeDef.elements;
|
|
128
|
+
// if node and typeDef are identical, we're anonymous
|
|
129
|
+
isAnonymous = node === typeDef;
|
|
130
|
+
// if we've left the anonymous world, we're no longer in a key def
|
|
131
|
+
if (!isAnonymous)
|
|
132
|
+
isKey = false;
|
|
127
133
|
|
|
128
134
|
let newType = exposeStructType(newTypeFullName, newTypeElements, memberName, path);
|
|
129
135
|
if (!newType) {
|
|
@@ -138,6 +144,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
138
144
|
newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
|
|
139
145
|
if (node.elements && node.elements[elemName].$location) setProp(newElem, '$location', node.elements[elemName].$location);
|
|
140
146
|
exposeStructTypeOf(newElem,
|
|
147
|
+
isKey,
|
|
141
148
|
memberName,
|
|
142
149
|
typeDef.kind === 'type' ? node.type : defName,
|
|
143
150
|
service,
|
|
@@ -251,6 +258,9 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
251
258
|
error(null, path, `"${elemName}": Element name conflicts with existing element`);
|
|
252
259
|
}
|
|
253
260
|
let cloned = cloneCsn(element, options);
|
|
261
|
+
// if this was an anonymous sub element of a key, mark it as not nullable
|
|
262
|
+
if(isAnonymous && isKey && !cloned.key && cloned.notNull === undefined)
|
|
263
|
+
cloned.notNull = true;
|
|
254
264
|
type.elements[elemName] = cloned;
|
|
255
265
|
});
|
|
256
266
|
|
|
@@ -265,12 +275,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
265
275
|
|
|
266
276
|
// If a member is of type "array of <named type|anonymous type>", we expose the arrayed type,
|
|
267
277
|
// like we expose structures in structured mode
|
|
268
|
-
function exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path) {
|
|
278
|
+
function exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
|
|
269
279
|
// if anonymously defined in place -> we always expose the type
|
|
270
280
|
// this would be definition like 'elem: array of { ... }'
|
|
271
281
|
// and we use the artificial name for the new type name
|
|
272
282
|
if (node.items && !node.type) {
|
|
273
|
-
exposeStructTypeOf(node.items, memberName, defName, service, artificialName, path.concat('items'));
|
|
283
|
+
exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path.concat('items'));
|
|
274
284
|
}
|
|
275
285
|
// we can have both of the 'type' and 'items' in the cases:
|
|
276
286
|
// 1. 'elem: Foo' and 'type Foo: array of Baz' and 'type Baz: { ... }'
|
|
@@ -8,9 +8,8 @@ const { hasErrors, makeMessageFunction } = require('../base/messages');
|
|
|
8
8
|
const { setProp } = require('../base/model');
|
|
9
9
|
const { csnRefs } = require('../model/csnRefs');
|
|
10
10
|
|
|
11
|
-
const { copyAnnotations } = require('../model/csnUtils');
|
|
12
|
-
const { cloneCsn,
|
|
13
|
-
getUtils, isBuiltinType } = require('../model/csnUtils');
|
|
11
|
+
const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
|
|
12
|
+
const { cloneCsn, getUtils, isBuiltinType } = require('../model/csnUtils');
|
|
14
13
|
|
|
15
14
|
// Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
|
|
16
15
|
// Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
|
|
@@ -62,6 +61,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
62
61
|
recurseElements,
|
|
63
62
|
renameAnnotation,
|
|
64
63
|
setAnnotation,
|
|
64
|
+
resetAnnotation,
|
|
65
65
|
expandStructsInExpression,
|
|
66
66
|
};
|
|
67
67
|
|
|
@@ -287,50 +287,56 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
287
287
|
return result;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Return a copy of 'ref' where all path steps resulting from struct traversal are
|
|
292
|
+
* fused together into one step, using '_' (so that the path fits again for flattened
|
|
293
|
+
* structs), e.g.
|
|
294
|
+
* [ (Entity), (struct1), (struct2), (assoc), (elem) ] should result in
|
|
295
|
+
* [ (Entity), (struct1_struct2_assoc), (elem) ]
|
|
296
|
+
*
|
|
297
|
+
* @param {string[]} ref
|
|
298
|
+
* @param {CSN.Path} path CSN path to the ref
|
|
299
|
+
* @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
|
|
300
|
+
* @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
|
|
301
|
+
* @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
|
|
302
|
+
* @returns {string[]}
|
|
303
|
+
*/
|
|
304
|
+
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap()) {
|
|
297
305
|
// Refs of length 1 cannot contain steps - no need to check
|
|
298
306
|
if (ref.length < 2) {
|
|
299
307
|
return ref;
|
|
300
308
|
}
|
|
301
309
|
|
|
302
|
-
|
|
303
|
-
return flatten(ref, path);
|
|
304
|
-
} catch (e) {
|
|
305
|
-
if (e.message && e.message === "Scope 'ref-target' but no entity was provided.") {
|
|
306
|
-
// TODO: should not happen anymore
|
|
307
|
-
return flatten(ref, path);
|
|
308
|
-
} else {
|
|
309
|
-
throw e;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
310
|
+
return flatten(ref, path);
|
|
312
311
|
|
|
313
312
|
function flatten(ref, path) {
|
|
314
313
|
let result = [];
|
|
315
314
|
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
|
|
316
|
-
|
|
315
|
+
if(!links && !scope) { // calculate JIT if not supplied
|
|
316
|
+
const res = inspectRef(path);
|
|
317
|
+
links = res.links;
|
|
318
|
+
scope = res.scope;
|
|
319
|
+
}
|
|
317
320
|
if (scope === '$magic')
|
|
318
321
|
return ref;
|
|
319
322
|
let flattenStep = false;
|
|
320
323
|
links.forEach((value, idx) => {
|
|
321
|
-
if (flattenStep)
|
|
322
|
-
result[result.length - 1] += pathDelimiter + ref[idx];
|
|
324
|
+
if (flattenStep) {
|
|
325
|
+
result[result.length - 1] += pathDelimiter + (ref[idx].id ? ref[idx].id : ref[idx]);
|
|
326
|
+
// if we had a filter or args, we had an assoc so this step is done
|
|
327
|
+
// we then keep along the filter/args by updating the id of the current ref
|
|
328
|
+
if(ref[idx].id) {
|
|
329
|
+
ref[idx].id = result[result.length-1];
|
|
330
|
+
result[result.length-1] = ref[idx];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
323
333
|
else {
|
|
324
334
|
result.push(ref[idx]);
|
|
325
335
|
}
|
|
326
|
-
|
|
336
|
+
|
|
337
|
+
flattenStep = value.art && !value.art.kind && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements || (resolvedLinkTypes.get(value)||{}).elements);
|
|
327
338
|
});
|
|
328
|
-
|
|
329
|
-
// If the path starts with '$self', this is now redundant (because of flattening) and can be omitted,
|
|
330
|
-
// making life easier for consumers
|
|
331
|
-
if (result[0] === '$self' && result.length > 1 && !magicVars.includes(result[1])) {
|
|
332
|
-
result = result.slice(1);
|
|
333
|
-
}
|
|
339
|
+
|
|
334
340
|
return result;
|
|
335
341
|
}
|
|
336
342
|
}
|
|
@@ -366,24 +372,31 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
366
372
|
Object.assign(node, { srid: typeDef.srid });
|
|
367
373
|
}
|
|
368
374
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
375
|
+
/**
|
|
376
|
+
* Replace the type of 'node' with its final base type (in contrast to the compiler,
|
|
377
|
+
* also unravel derived enum types, i.e. take the final base type of the enum's base type.
|
|
378
|
+
* Similar with associations and compositions (we probably need a _baseType link)
|
|
379
|
+
*
|
|
380
|
+
* @param {CSN.Artifact} node
|
|
381
|
+
* @param {WeakMap} [resolved] WeakMap containing already resolved refs
|
|
382
|
+
* @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
|
|
383
|
+
* @returns {void}
|
|
384
|
+
*/
|
|
385
|
+
function toFinalBaseType(node, resolved, keepLocalized=false) {
|
|
373
386
|
// Nothing to do if no type (or if array/struct type)
|
|
374
387
|
if (!node || !node.type) return;
|
|
375
388
|
// In case of a ref -> Follow the ref
|
|
376
389
|
if (node.type && node.type.ref) {
|
|
377
|
-
|
|
390
|
+
const finalBaseType = getFinalBaseType(node.type, undefined, resolved);
|
|
378
391
|
if(finalBaseType === null)
|
|
379
392
|
throw Error('Failed to obtain final base type for reference : ' + node.type.ref.join('/'));
|
|
380
|
-
if
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
node.elements = cloneCsn(finalBaseType.elements, options); // copy elements
|
|
393
|
+
if(finalBaseType.elements) {
|
|
394
|
+
// This changes the order - to be discussed!
|
|
395
|
+
node.elements = cloneCsn(finalBaseType, options).elements; // copy elements
|
|
384
396
|
delete node.type; // delete the type reference as edm processing does not expect it
|
|
385
|
-
} else if
|
|
386
|
-
|
|
397
|
+
} else if(finalBaseType.items) {
|
|
398
|
+
// This changes the order - to be discussed!
|
|
399
|
+
node.items = cloneCsn(finalBaseType, options).items; // copy items
|
|
387
400
|
delete node.type;
|
|
388
401
|
} else {
|
|
389
402
|
node.type=finalBaseType;
|
|
@@ -396,10 +409,31 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
396
409
|
// The type might already be a full fledged type def (array of)
|
|
397
410
|
let typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
|
|
398
411
|
// Nothing to do if type is an array or a struct type
|
|
399
|
-
if (typeDef.items || typeDef.elements)
|
|
412
|
+
if (typeDef.items || typeDef.elements) {
|
|
413
|
+
if(!(options.transformation === 'hdbcds' || options.toSql))
|
|
414
|
+
return;
|
|
415
|
+
|
|
416
|
+
// cloneCsn only works correctly if we start "from the top"
|
|
417
|
+
const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options);
|
|
418
|
+
// With hdbcds-hdbcds, don't resolve structured types - but propagrate ".items", to turn into LargeString later on.
|
|
419
|
+
if(typeDef.items) {
|
|
420
|
+
delete node.type;
|
|
421
|
+
Object.assign(node, {items: clone.definitions.TypeDef.items});
|
|
422
|
+
}
|
|
423
|
+
if(typeDef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
|
|
424
|
+
if(!typeDef.items)
|
|
425
|
+
delete node.type;
|
|
426
|
+
Object.assign(node, {elements: clone.definitions.TypeDef.elements});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
400
432
|
// if the declared element is an enum, these values are with priority
|
|
401
|
-
if (!node.enum && typeDef.enum)
|
|
402
|
-
|
|
433
|
+
if (!node.enum && typeDef.enum) {
|
|
434
|
+
const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options).definitions.TypeDef.enum;
|
|
435
|
+
Object.assign(node, { enum: clone });
|
|
436
|
+
}
|
|
403
437
|
if (node.length === undefined && typeDef.length !== undefined)
|
|
404
438
|
Object.assign(node, { length: typeDef.length });
|
|
405
439
|
if (node.precision === undefined && typeDef.precision !== undefined)
|
|
@@ -408,6 +442,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
408
442
|
Object.assign(node, { scale: typeDef.scale });
|
|
409
443
|
if (node.srid === undefined && typeDef.srid !== undefined)
|
|
410
444
|
Object.assign(node, { srid: typeDef.srid });
|
|
445
|
+
if (keepLocalized && node.localized === undefined && typeDef.localized !== undefined)
|
|
446
|
+
Object.assign(node, { localized: typeDef.localized });
|
|
411
447
|
node.type = typeDef.type;
|
|
412
448
|
toFinalBaseType(node);
|
|
413
449
|
}
|
|
@@ -888,6 +924,34 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
888
924
|
node[name] = value;
|
|
889
925
|
}
|
|
890
926
|
|
|
927
|
+
/**
|
|
928
|
+
* Assigns unconditionally annotation to a node, which means it overwrites already existing annotation assignment.
|
|
929
|
+
* Overwritting is when the assignment differs from undefined and null, also when differs from the already set value.
|
|
930
|
+
* Setting new assignment results false as return value and overwriting - true.
|
|
931
|
+
*
|
|
932
|
+
* @param {object} node Assignee
|
|
933
|
+
* @param {string} name Annotation name
|
|
934
|
+
* @param {any} value Annotation value
|
|
935
|
+
* @param {function} info function that reports info messages
|
|
936
|
+
* @param {CSN.Path} path location of the warning
|
|
937
|
+
* @returns {boolean} wasOverwritten true when the annotation was overwritten
|
|
938
|
+
*/
|
|
939
|
+
function resetAnnotation(node, name, value, info, path) {
|
|
940
|
+
if (!name.startsWith('@')) {
|
|
941
|
+
throw Error('Annotation name should start with "@": ' + name);
|
|
942
|
+
}
|
|
943
|
+
if (value === undefined) {
|
|
944
|
+
throw Error('Annotation value must not be undefined');
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const wasOverwritten = node[name] !== undefined && node[name] !== null && node[name] !== value;
|
|
948
|
+
const oldValue = node[name];
|
|
949
|
+
node[name] = value;
|
|
950
|
+
if(wasOverwritten)
|
|
951
|
+
info(null, path, { anno: name, prop: value, otherprop: oldValue },
|
|
952
|
+
`Value $(OTHERPROP) of annotation $(ANNO) is overwritten with new value $(PROP)`);
|
|
953
|
+
return wasOverwritten;
|
|
954
|
+
}
|
|
891
955
|
|
|
892
956
|
/*
|
|
893
957
|
Resolve the type of an artifact
|
|
@@ -969,7 +1033,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
969
1033
|
function flattenPath(path, fullRef=false, followMgdAssoc=false) {
|
|
970
1034
|
let art = path._art;
|
|
971
1035
|
if(art) {
|
|
972
|
-
if(art &&
|
|
1036
|
+
if(art && !((art.items && art.items.elements) || art.elements)) {
|
|
973
1037
|
if(followMgdAssoc && art.target && art.keys) {
|
|
974
1038
|
let rc = [];
|
|
975
1039
|
for(const k of art.keys) {
|
|
@@ -982,9 +1046,9 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
982
1046
|
}
|
|
983
1047
|
return rc;
|
|
984
1048
|
}
|
|
985
|
-
if(art.type.ref)
|
|
1049
|
+
if(art.type && art.type.ref)
|
|
986
1050
|
art = resolvePath(art.type);
|
|
987
|
-
else if(!isBuiltinType(art.type))
|
|
1051
|
+
else if(art.type && !isBuiltinType(art.type))
|
|
988
1052
|
art = model.definitions[art.type];
|
|
989
1053
|
}
|
|
990
1054
|
const elements = art.items && art.items.elements || art.elements;
|
|
@@ -1006,41 +1070,33 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1006
1070
|
return [path];
|
|
1007
1071
|
}
|
|
1008
1072
|
|
|
1009
|
-
|
|
1073
|
+
/**
|
|
1010
1074
|
* Expand structured expression arguments to flat reference paths.
|
|
1011
1075
|
* Structured elements are real sub element lists and managed associations.
|
|
1012
1076
|
* All unmanaged association definitions are rewritten if applicable (elements/mixins).
|
|
1013
|
-
* Also, HAVING and WHERE clauses are rewritten.
|
|
1077
|
+
* Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
|
|
1078
|
+
* .xpr in columns.
|
|
1014
1079
|
*
|
|
1015
|
-
*
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
},
|
|
1036
|
-
path);
|
|
1037
|
-
if(query.SELECT.having)
|
|
1038
|
-
query.SELECT.having = expand(query.SELECT.having, queryPath.concat(['SELECT', 'having']))
|
|
1039
|
-
if(query.SELECT.where)
|
|
1040
|
-
query.SELECT.where = expand(query.SELECT.where, queryPath.concat(['SELECT', 'where']))
|
|
1041
|
-
}
|
|
1042
|
-
}, path.concat([ 'query' ]));
|
|
1043
|
-
}
|
|
1080
|
+
* @todo Check if can be skipped for abstract entity and or cds.persistence.skip ?
|
|
1081
|
+
* @param {CSN.Model} csn
|
|
1082
|
+
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts
|
|
1083
|
+
*/
|
|
1084
|
+
function expandStructsInExpression(csn, options = {}) {
|
|
1085
|
+
applyTransformations(csn, {
|
|
1086
|
+
'on': (parent, name, on, path) => {
|
|
1087
|
+
parent.on = expand(parent.on, path);
|
|
1088
|
+
},
|
|
1089
|
+
'having': (parent, name, having, path) => {
|
|
1090
|
+
parent.having = expand(parent.having, path);
|
|
1091
|
+
},
|
|
1092
|
+
'where': (parent, name, where, path) => {
|
|
1093
|
+
parent.where = expand(parent.where, path);
|
|
1094
|
+
},
|
|
1095
|
+
'xpr': (parent, name, xpr, path) => {
|
|
1096
|
+
parent.xpr = expand(parent.xpr, path);
|
|
1097
|
+
}
|
|
1098
|
+
}, undefined, undefined, options);
|
|
1099
|
+
|
|
1044
1100
|
/*
|
|
1045
1101
|
flatten structured leaf types and return array of paths
|
|
1046
1102
|
Flattening stops on all non-structured types.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { setProp, forEachGeneric, forEachDefinition, isBetaEnabled } = require('../base/model');
|
|
4
|
-
var {
|
|
4
|
+
var { makeMessageFunction } = require('../base/messages');
|
|
5
|
+
const { recompileX } = require('../compiler/index');
|
|
5
6
|
var { linkToOrigin } = require('../compiler/shared');
|
|
6
7
|
const {compactModel, compactExpr} = require('../json/to-csn');
|
|
7
8
|
const { deduplicateMessages } = require('../base/messages');
|
|
@@ -12,13 +13,10 @@ const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param'];
|
|
|
12
13
|
|
|
13
14
|
function translateAssocsToJoinsCSN(csn, options){
|
|
14
15
|
timetrace.start('Recompiling model');
|
|
15
|
-
let { augment } = require('../json/from-csn');
|
|
16
|
-
// Append `.csn` to `.cds` files to indicate recompilation.
|
|
17
|
-
const file = csn.$location && csn.$location.file.replace(/[.]cds$/, '.cds.csn') || '<recompile>.csn';
|
|
18
|
-
let xsn = augment(csn, file, options);
|
|
19
|
-
const { compileSourcesX } = require('../compiler');
|
|
20
16
|
// Do not re-complain about localized
|
|
21
|
-
const
|
|
17
|
+
const compileOptions = { ...options, $skipNameCheck: true };
|
|
18
|
+
delete compileOptions.csnFlavor;
|
|
19
|
+
const model = recompileX(csn, compileOptions);
|
|
22
20
|
timetrace.stop();
|
|
23
21
|
timetrace.start('Translating associations to joins');
|
|
24
22
|
translateAssocsToJoins(model, options);
|
|
@@ -46,9 +44,9 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
// If A2J reports error - end! Continuing with a broken CSN makes no sense
|
|
49
|
-
|
|
47
|
+
makeMessageFunction(model, options).throwWithError();
|
|
50
48
|
// FIXME: Move this somewhere more appropriate
|
|
51
|
-
const compact = compactModel(model,
|
|
49
|
+
const compact = compactModel(model, compileOptions);
|
|
52
50
|
return compact;
|
|
53
51
|
}
|
|
54
52
|
|
|
@@ -618,7 +616,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
618
616
|
const id = this.id();
|
|
619
617
|
if(elt) {
|
|
620
618
|
let found = true;
|
|
621
|
-
const epath = elt
|
|
619
|
+
const epath = [elt];
|
|
622
620
|
const epl = epath.length+offset;
|
|
623
621
|
if(epl < path.length) {
|
|
624
622
|
for(let i = 0; i < epl && found; i++) {
|
|
@@ -630,7 +628,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
630
628
|
}
|
|
631
629
|
if(id) {
|
|
632
630
|
let found = true;
|
|
633
|
-
const epath = id
|
|
631
|
+
const epath = [id];
|
|
634
632
|
const epl = epath.length+offset;
|
|
635
633
|
if(epl < path.length) {
|
|
636
634
|
for(let i = 0; i < epl && found; i++) {
|
|
@@ -745,9 +743,14 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
745
743
|
function swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias) {
|
|
746
744
|
let newSrcAlias = tgtAlias;
|
|
747
745
|
let newTgtAlias = {};
|
|
748
|
-
// first try to identify table alias for complex views or
|
|
749
|
-
|
|
750
|
-
|
|
746
|
+
// first try to identify table alias for complex views or redirected associations
|
|
747
|
+
if(fwdAssoc._redirected && fwdAssoc._redirected.length &&
|
|
748
|
+
// redirected target must have a $QA
|
|
749
|
+
fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA &&
|
|
750
|
+
// $QA's artifact must either be same srcAlias artifact
|
|
751
|
+
(fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA._artifact === srcAlias._artifact ||
|
|
752
|
+
// OR original assoc is a mixin (then just use the $QA)
|
|
753
|
+
assoc.kind === 'mixin')) {
|
|
751
754
|
newTgtAlias.id = fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA.name.id;
|
|
752
755
|
newTgtAlias._artifact = fwdAssoc._redirected[fwdAssoc._redirected.length-1]._effectiveType;
|
|
753
756
|
newTgtAlias._navigation = fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA.path[0]._navigation;
|