@sap/cds-compiler 2.13.6 → 2.15.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 +128 -4
- package/bin/cdsc.js +112 -37
- package/lib/api/main.js +20 -22
- package/lib/api/options.js +2 -3
- package/lib/api/validate.js +6 -6
- package/lib/base/message-registry.js +92 -17
- package/lib/base/messages.js +85 -64
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/validator.js +2 -4
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +11 -0
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +59 -11
- package/lib/compiler/extend.js +20 -3
- package/lib/compiler/finalize-parse-cdl.js +26 -20
- package/lib/compiler/index.js +75 -26
- package/lib/compiler/populate.js +6 -5
- package/lib/compiler/propagator.js +4 -1
- package/lib/compiler/resolve.js +104 -16
- package/lib/compiler/shared.js +61 -27
- package/lib/compiler/tweak-assocs.js +7 -1
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +216 -98
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +499 -423
- package/lib/edm/edmUtils.js +22 -22
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageParser.js +4636 -4368
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +0 -2
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +47 -2
- package/lib/language/language.g4 +59 -27
- package/lib/main.d.ts +19 -1
- package/lib/main.js +6 -0
- package/lib/model/csnRefs.js +33 -6
- package/lib/model/csnUtils.js +193 -75
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +2 -2
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +62 -26
- package/lib/render/toCdl.js +844 -679
- package/lib/render/toHdbcds.js +189 -243
- package/lib/render/toSql.js +180 -198
- package/lib/render/utils/common.js +131 -15
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/constraints.js +3 -1
- package/lib/transform/db/expansion.js +15 -10
- package/lib/transform/db/flattening.js +95 -68
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +6 -3
- package/lib/transform/forHanaNew.js +43 -26
- package/lib/transform/forOdataNew.js +43 -42
- package/lib/transform/localized.js +12 -7
- package/lib/transform/odata/toFinalBaseType.js +8 -6
- package/lib/transform/odata/typesExposure.js +145 -197
- package/lib/transform/transformUtilsNew.js +9 -12
- package/lib/transform/translateAssocsToJoins.js +5 -1
- package/lib/transform/universalCsn/coreComputed.js +5 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
- package/lib/utils/moduleResolve.js +13 -6
- package/package.json +1 -1
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
package/lib/render/toCdl.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
getLastPartOf,
|
|
5
|
-
getLastPartOfRef,
|
|
6
|
-
} = require('../model/csnUtils');
|
|
7
|
-
const {
|
|
8
|
-
isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
|
|
9
|
-
} = require('../model/csnUtils');
|
|
10
3
|
const keywords = require('../base/keywords');
|
|
11
|
-
const {
|
|
4
|
+
const { getLastPartOf } = require('../model/csnUtils');
|
|
5
|
+
const { isBuiltinType, generatedByCompilerVersion, getNormalizedQuery } = require('../model/csnUtils');
|
|
6
|
+
const { renderFunc, findElement, getExpressionRenderer } = require('./utils/common');
|
|
12
7
|
const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
|
|
13
8
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
14
9
|
const { timetrace } = require('../utils/timetrace');
|
|
@@ -17,15 +12,15 @@ const { forEachDefinition } = require('../model/csnUtils');
|
|
|
17
12
|
const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
|
|
18
13
|
const { isBetaEnabled } = require('../base/model');
|
|
19
14
|
const { ModelError } = require('../base/error');
|
|
15
|
+
const { typeParameters } = require('../compiler/builtins');
|
|
16
|
+
const { forEach } = require('../utils/objectUtils');
|
|
20
17
|
|
|
21
18
|
/**
|
|
22
|
-
* Render the CSN model 'model' to CDS source text.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* }
|
|
28
|
-
* FIXME: This comment no longer tells the whole truth
|
|
19
|
+
* Render the CSN model 'model' to CDS source text.
|
|
20
|
+
* Returned object has the following properties:
|
|
21
|
+
* - `model`: CSN model rendered as CDL (string).
|
|
22
|
+
* - [csn.namespace]: Namespace statement + `using from './model.cds'.
|
|
23
|
+
* - `unappliedExtensions`: Annotations / Extensions from the `csn.extensions` array.
|
|
29
24
|
*
|
|
30
25
|
* @param {CSN.Model} csn
|
|
31
26
|
* @param {CSN.Options} [options]
|
|
@@ -33,9 +28,7 @@ const { ModelError } = require('../base/error');
|
|
|
33
28
|
function toCdsSourceCsn(csn, options) {
|
|
34
29
|
timetrace.start('CDL rendering');
|
|
35
30
|
const { artifactRef } = csnRefs(csn);
|
|
36
|
-
|
|
37
|
-
// Skip compactModel if already using CSN
|
|
38
|
-
// const csn = cloneCsn(model, options);
|
|
31
|
+
let _renderExpr = null;
|
|
39
32
|
|
|
40
33
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
|
|
41
34
|
enrichUniversalCsn(csn, options);
|
|
@@ -43,177 +36,285 @@ function toCdsSourceCsn(csn, options) {
|
|
|
43
36
|
checkCSNVersion(csn, options);
|
|
44
37
|
|
|
45
38
|
const cdlResult = Object.create(null);
|
|
39
|
+
cdlResult.model = options.testMode ? '' : `// ${generatedByCompilerVersion()} \n`;
|
|
40
|
+
|
|
41
|
+
const subelementAnnotates = [];
|
|
46
42
|
|
|
47
|
-
|
|
43
|
+
cdlResult.model += renderDefinitions();
|
|
44
|
+
// sub-element annotations that can't be written directly.
|
|
45
|
+
cdlResult.model += renderExtensions(subelementAnnotates, createEnv());
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
if (csn.vocabularies)
|
|
48
|
+
cdlResult.model += renderVocabularies(csn.vocabularies);
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
if (csn.namespace) {
|
|
51
|
+
cdlResult[csn.namespace] = `namespace ${renderArtifactName(csn.namespace)};\n`;
|
|
52
|
+
cdlResult[csn.namespace] += 'using from \'./model.cds\';';
|
|
53
|
+
}
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
// If there are extensions, such as 'extend' and 'annotate' statements, render them separately.
|
|
56
|
+
// Used for e.g. parseCdl-style CSN or Universal CSN.
|
|
57
|
+
if (csn.extensions)
|
|
58
|
+
cdlResult.unappliedExtensions = renderExtensions(csn.extensions, createEnv());
|
|
59
|
+
|
|
60
|
+
timetrace.stop();
|
|
61
|
+
return cdlResult;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Render entries from the `csn.definitions` dictionary.
|
|
65
|
+
* Returns an empty string if nothing is rendered.
|
|
66
|
+
*
|
|
67
|
+
* @return {string}
|
|
68
|
+
*/
|
|
69
|
+
function renderDefinitions() {
|
|
70
|
+
let result = '';
|
|
54
71
|
const env = createEnv();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
forEachDefinition(csn, (artifact, artifactName) => {
|
|
73
|
+
const sourceStr = renderArtifact(artifactName, artifact, env);
|
|
74
|
+
if (sourceStr !== '')
|
|
75
|
+
result += `${sourceStr}\n`;
|
|
76
|
+
});
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Render annotation definitions, i.e. entries from csn.vocabularies.
|
|
82
|
+
* Returns an empty string if there isn't anything to render.
|
|
83
|
+
*
|
|
84
|
+
* @param {object} vocabularies
|
|
85
|
+
* @return {string}
|
|
86
|
+
*/
|
|
87
|
+
function renderVocabularies(vocabularies) {
|
|
88
|
+
let result = '';
|
|
89
|
+
forEach(vocabularies, renderVocabulariesEntry);
|
|
90
|
+
return result;
|
|
59
91
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
sourceStr += ` ${elementName} {\n`;
|
|
68
|
-
const env = increaseIndent(increaseIndent(createEnv()));
|
|
69
|
-
const subelements = renderSubelementAnnotates(element, env);
|
|
70
|
-
if (subelements !== '') {
|
|
71
|
-
sourceStr += `${subelements}\n`;
|
|
72
|
-
if (elementName) // action returns do not have element name - we need less {} there
|
|
73
|
-
sourceStr += ' }\n';
|
|
74
|
-
sourceStr += '}\n';
|
|
75
|
-
cdlResult[main] += `${sourceStr}\n`;
|
|
92
|
+
function renderVocabulariesEntry(name, anno) {
|
|
93
|
+
if (!anno._ignore) {
|
|
94
|
+
// This environment is passed down the call hierarchy, for dealing with
|
|
95
|
+
// indentation and name resolution issues
|
|
96
|
+
const env = createEnv();
|
|
97
|
+
const sourceStr = renderTypeOrAnnotation(name, anno, env, 'annotation');
|
|
98
|
+
result += `${sourceStr}\n`;
|
|
76
99
|
}
|
|
77
100
|
}
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
/**
|
|
81
|
-
* Render
|
|
104
|
+
* Render 'extend' and 'annotate' statements from the `extensions` array.
|
|
105
|
+
* Could be annotate-statements for sub-elements annotations or from parseCdl's
|
|
106
|
+
* extensions array or just unapplied extensions.
|
|
82
107
|
*
|
|
83
|
-
* @param {CSN.
|
|
84
|
-
* @param {CdlRenderEnvironment} env
|
|
85
|
-
* @
|
|
108
|
+
* @param {CSN.Extension[]} extensions
|
|
109
|
+
* @param {CdlRenderEnvironment} env
|
|
110
|
+
* @return {string}
|
|
86
111
|
*/
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
for (const [ name, subelement ] of Object.entries(element.elements)) {
|
|
90
|
-
const subresult = [];
|
|
91
|
-
const annos = renderAnnotationAssignments(subelement, env);
|
|
92
|
-
if (annos !== '')
|
|
93
|
-
subresult.push(annos.slice(0, -1));
|
|
94
|
-
|
|
95
|
-
const quotedElementName = quoteOrUppercaseId(name);
|
|
96
|
-
if (subelement.elements) {
|
|
97
|
-
subresult.push(`${env.indent}${quotedElementName} {`);
|
|
98
|
-
subresult.push(renderSubelementAnnotates(subelement, increaseIndent(env)));
|
|
99
|
-
subresult.push(`${env.indent}};`);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
subresult.push(`${env.indent}${quotedElementName};`);
|
|
103
|
-
}
|
|
104
|
-
// Only add result if there really was "something"
|
|
105
|
-
if (annos || subelement.elements)
|
|
106
|
-
result.push(...subresult);
|
|
107
|
-
}
|
|
108
|
-
return result.join('\n');
|
|
112
|
+
function renderExtensions(extensions, env) {
|
|
113
|
+
return extensions.map(ext => renderExtension(ext, env)).join('\n');
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Render an 'extend' and 'annotate' statement.
|
|
118
|
+
*
|
|
119
|
+
* @param {CSN.Extension} ext
|
|
120
|
+
* @param {CdlRenderEnvironment} env
|
|
121
|
+
* @return {string}
|
|
122
|
+
*/
|
|
123
|
+
function renderExtension(ext, env) {
|
|
124
|
+
if (ext.extend)
|
|
125
|
+
return renderExtendStatement(ext.extend, ext, env);
|
|
126
|
+
return renderAnnotateStatement(ext, env);
|
|
127
|
+
}
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Render an 'extend' statement.
|
|
131
|
+
* `extName` is the extension's artifact's name, most likely `ext.extend`.
|
|
132
|
+
* This function is recursive, which is why you need to pass it explicitly.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} extName
|
|
135
|
+
* @param {object} ext
|
|
136
|
+
* @param {CdlRenderEnvironment} env
|
|
137
|
+
* @return {string}
|
|
138
|
+
*/
|
|
139
|
+
function renderExtendStatement(extName, ext, env) {
|
|
140
|
+
extName = renderArtifactName(extName);
|
|
141
|
+
// Element extensions have `kind` set.
|
|
142
|
+
const isElementExtend = (ext.kind === 'extend');
|
|
143
|
+
let result = renderAnnotationAssignmentsAndDocComment(ext, env);
|
|
144
|
+
|
|
145
|
+
if (ext.includes && ext.includes.length > 0) {
|
|
146
|
+
// Includes can't be combined with anything in braces {}. Multiple includes
|
|
147
|
+
// are possible through CSN, but in CDL, only one include at once is possible.
|
|
148
|
+
const affix = isElementExtend ? 'element ' : '';
|
|
149
|
+
for (const id of ext.includes)
|
|
150
|
+
result += `${env.indent}extend ${affix}${extName} with ${quoteIdIfRequired(id)};\n`;
|
|
151
|
+
return result;
|
|
123
152
|
}
|
|
124
|
-
}
|
|
125
153
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
154
|
+
// We have the "old-style" prefix syntax and the "new-style" postfix "with <type>" syntax.
|
|
155
|
+
// The former one can not only extend (sub-)elements but also actions in the same statement whereas
|
|
156
|
+
// the latter cannot.
|
|
157
|
+
// If there are actions, check if there are also elements/columns, and if so, use the prefix notation.
|
|
158
|
+
const usePrefixNotation = isElementExtend || ext.actions && (ext.columns || ext.elements);
|
|
159
|
+
if (usePrefixNotation)
|
|
160
|
+
result += `${env.indent}extend ${getExtendPrefixVariant(ext)} ${extName} with {\n`;
|
|
161
|
+
else
|
|
162
|
+
result += `${env.indent}extend ${extName} with ${getExtendPostfixVariant(ext)} {\n`;
|
|
163
|
+
|
|
164
|
+
if (ext.columns) {
|
|
165
|
+
result += renderViewColumns(ext.columns, increaseIndent(env));
|
|
166
|
+
}
|
|
167
|
+
else if (ext.elements) {
|
|
168
|
+
result += renderExtendStatementElements(ext, env);
|
|
169
|
+
}
|
|
170
|
+
else if (ext.enum) {
|
|
171
|
+
const childEnv = increaseIndent(env);
|
|
172
|
+
forEach(ext.enum, (name, value) => {
|
|
173
|
+
result += renderEnumElement(name, value, childEnv);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Not part of if/else cascade, because it may be in postfix notation.
|
|
178
|
+
if (ext.actions) {
|
|
179
|
+
// TODO: Merge with renderActionsAndFunctions() -> requires removal of static `with actions`
|
|
180
|
+
const childEnv = increaseIndent(env);
|
|
181
|
+
let actions = '';
|
|
182
|
+
forEach(ext.actions, (actionName, action) => {
|
|
183
|
+
actions += renderActionOrFunction(actionName, action, childEnv);
|
|
184
|
+
});
|
|
185
|
+
if (!usePrefixNotation)
|
|
186
|
+
result += actions;
|
|
187
|
+
else if (actions !== '')
|
|
188
|
+
result += `${env.indent}} actions {\n${actions}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
result += `${env.indent}};\n`;
|
|
192
|
+
return result;
|
|
129
193
|
}
|
|
130
194
|
|
|
195
|
+
/**
|
|
196
|
+
* What <extend> prefix type to use. Used to render `extend <type> <ref>` statements.
|
|
197
|
+
*
|
|
198
|
+
* @param {object} ext
|
|
199
|
+
* @return {string}
|
|
200
|
+
*/
|
|
201
|
+
function getExtendPrefixVariant(ext) {
|
|
202
|
+
if (ext.kind === 'extend')
|
|
203
|
+
return 'element'; // element extensions inside an `extend`
|
|
204
|
+
if (ext.columns)
|
|
205
|
+
return 'projection';
|
|
206
|
+
if (ext.elements)
|
|
207
|
+
return 'entity';
|
|
208
|
+
return '';
|
|
209
|
+
}
|
|
131
210
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
211
|
+
/**
|
|
212
|
+
* What <extend> postfix type to use. Used to render `extend <ref> with <type>` statements.
|
|
213
|
+
*
|
|
214
|
+
* @param {CSN.Extension} ext
|
|
215
|
+
* @return {string}
|
|
216
|
+
*/
|
|
217
|
+
function getExtendPostfixVariant(ext) {
|
|
218
|
+
if (ext.columns)
|
|
219
|
+
return 'columns';
|
|
220
|
+
if (ext.actions)
|
|
221
|
+
return 'actions';
|
|
222
|
+
if (ext.elements)
|
|
223
|
+
return 'elements';
|
|
224
|
+
if (ext.enum)
|
|
225
|
+
return 'enum';
|
|
226
|
+
return '';
|
|
138
227
|
}
|
|
139
228
|
|
|
140
|
-
|
|
141
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Render the elements inside an `extend` statement. They may themselves be `extend` statements.
|
|
231
|
+
*
|
|
232
|
+
* @param {CSN.Extension} ext
|
|
233
|
+
* @param {CdlRenderEnvironment} env
|
|
234
|
+
* @return {string}
|
|
235
|
+
*/
|
|
236
|
+
function renderExtendStatementElements(ext, env) {
|
|
237
|
+
let result = '';
|
|
238
|
+
forEach(ext.elements || {}, (elemName, element) => {
|
|
239
|
+
if (element.kind === 'extend')
|
|
240
|
+
result += renderExtendStatement(elemName, element, increaseIndent(env));
|
|
241
|
+
else
|
|
242
|
+
// As soon as we are inside an element, nested `extend` are not possible,
|
|
243
|
+
// since we can't extend an existing element of a new one.
|
|
244
|
+
result += renderElement(elemName, element, increaseIndent(env), true);
|
|
245
|
+
});
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
142
248
|
|
|
143
249
|
/**
|
|
144
|
-
* Render
|
|
250
|
+
* Render an 'annotate' statement.
|
|
145
251
|
*
|
|
146
|
-
* @param {CSN.Extension
|
|
252
|
+
* @param {CSN.Extension} ext
|
|
147
253
|
* @param {CdlRenderEnvironment} env
|
|
148
254
|
* @return {string}
|
|
149
255
|
*/
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
result += `annotate ${ext.annotate}`;
|
|
155
|
-
// Element extensions and annotations (possibly nested)
|
|
156
|
-
if (ext.elements)
|
|
157
|
-
result += renderElementExtensions(ext.elements, env);
|
|
158
|
-
|
|
159
|
-
// Returns annotations
|
|
160
|
-
if (ext.returns) {
|
|
161
|
-
const childEnv = increaseIndent(env);
|
|
162
|
-
result += ` with returns${renderElementExtensions(ext.returns.elements, childEnv)}`;
|
|
163
|
-
}
|
|
256
|
+
function renderAnnotateStatement(ext, env) {
|
|
257
|
+
// Top-level annotations of the artifact
|
|
258
|
+
let result = renderAnnotationAssignmentsAndDocComment(ext, env);
|
|
259
|
+
result += `${env.indent}annotate ${renderArtifactName(ext.annotate)}`;
|
|
164
260
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
result += ' actions {\n';
|
|
168
|
-
const childEnv = increaseIndent(env);
|
|
169
|
-
for (const name in ext.actions) {
|
|
170
|
-
const action = ext.actions[name];
|
|
171
|
-
result += renderAnnotationAssignments(action, childEnv) + childEnv.indent + quoteIdIfRequired(name);
|
|
172
|
-
// Action parameter annotations
|
|
173
|
-
if (action.params) {
|
|
174
|
-
result += '(\n';
|
|
175
|
-
const grandChildEnv = increaseIndent(childEnv);
|
|
176
|
-
const paramAnnotations = [];
|
|
177
|
-
for (const paramName in action.params)
|
|
178
|
-
paramAnnotations.push(renderAnnotationAssignments(action.params[paramName], grandChildEnv) + grandChildEnv.indent + quoteIdIfRequired(paramName));
|
|
179
|
-
|
|
180
|
-
result += `${paramAnnotations.join(',\n')}\n${childEnv.indent})`;
|
|
181
|
-
}
|
|
182
|
-
// Annotations on action returns
|
|
183
|
-
if (action.returns && action.returns.elements) {
|
|
184
|
-
const grandChildEnv = increaseIndent(childEnv);
|
|
185
|
-
result += ` returns${renderElementExtensions(action.returns.elements, grandChildEnv)}`;
|
|
186
|
-
}
|
|
261
|
+
if (ext.params)
|
|
262
|
+
result += renderAnnotateParamsInParentheses(ext.params, env);
|
|
187
263
|
|
|
264
|
+
// Element extensions and annotations (possibly nested)
|
|
265
|
+
if (ext.elements)
|
|
266
|
+
result += renderAnnotateStatementElements(ext.elements, env);
|
|
267
|
+
|
|
268
|
+
// Returns annotations
|
|
269
|
+
if (ext.returns) {
|
|
270
|
+
const childEnv = increaseIndent(env);
|
|
271
|
+
result += ` returns${renderAnnotateStatementElements(ext.returns.elements, childEnv)}`;
|
|
272
|
+
}
|
|
188
273
|
|
|
189
|
-
|
|
274
|
+
// Action annotations
|
|
275
|
+
if (ext.actions) {
|
|
276
|
+
result += ' actions {\n';
|
|
277
|
+
const childEnv = increaseIndent(env);
|
|
278
|
+
for (const name in ext.actions) {
|
|
279
|
+
const action = ext.actions[name];
|
|
280
|
+
result += renderAnnotationAssignmentsAndDocComment(action, childEnv) + childEnv.indent + quoteIdIfRequired(name);
|
|
281
|
+
// Action parameter annotations
|
|
282
|
+
if (action.params)
|
|
283
|
+
result += renderAnnotateParamsInParentheses(action.params, childEnv);
|
|
284
|
+
|
|
285
|
+
// Annotations on action returns
|
|
286
|
+
if (action.returns && action.returns.elements) {
|
|
287
|
+
const grandChildEnv = increaseIndent(childEnv);
|
|
288
|
+
result += ` returns${renderAnnotateStatementElements(action.returns.elements, grandChildEnv)}`;
|
|
190
289
|
}
|
|
191
|
-
|
|
290
|
+
|
|
291
|
+
result += ';\n';
|
|
192
292
|
}
|
|
293
|
+
result += `${env.indent}}`;
|
|
294
|
+
}
|
|
193
295
|
|
|
194
296
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}).join('\n');
|
|
297
|
+
result += ';\n';
|
|
298
|
+
return result;
|
|
198
299
|
}
|
|
199
300
|
|
|
200
301
|
/**
|
|
201
|
-
* Render the elements-specific part of an '
|
|
302
|
+
* Render the elements-specific part of an 'annotate' statement for an element dictionary
|
|
202
303
|
* 'elements' (assuming that the surrounding parent has just been rendered, without trailing newline).
|
|
203
|
-
*
|
|
304
|
+
* Returns the resulting source string, ending without a trailing newline.
|
|
204
305
|
*
|
|
205
306
|
* @param {CSN.Elements} elements
|
|
206
307
|
* @param {CdlRenderEnvironment} env
|
|
207
308
|
* @return {string}
|
|
208
309
|
*/
|
|
209
|
-
function
|
|
310
|
+
function renderAnnotateStatementElements(elements, env) {
|
|
210
311
|
let result = ' {\n';
|
|
211
312
|
const childEnv = increaseIndent(env);
|
|
212
313
|
for (const name in elements) {
|
|
213
314
|
const elem = elements[name];
|
|
214
|
-
result +=
|
|
315
|
+
result += renderAnnotationAssignmentsAndDocComment(elem, childEnv) + childEnv.indent + quoteIdIfRequired(name);
|
|
215
316
|
if (elem.elements)
|
|
216
|
-
result +=
|
|
317
|
+
result += renderAnnotateStatementElements(elem.elements, childEnv);
|
|
217
318
|
|
|
218
319
|
result += ';\n';
|
|
219
320
|
}
|
|
@@ -221,6 +322,24 @@ function toCdsSourceCsn(csn, options) {
|
|
|
221
322
|
return result;
|
|
222
323
|
}
|
|
223
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Render a parameter list for `annotate` statements, in parentheses `()`.
|
|
327
|
+
*
|
|
328
|
+
* @param {object} params
|
|
329
|
+
* @param {CdlRenderEnvironment} env
|
|
330
|
+
* @return {string}
|
|
331
|
+
*/
|
|
332
|
+
function renderAnnotateParamsInParentheses(params, env) {
|
|
333
|
+
const childEnv = increaseIndent(env);
|
|
334
|
+
let result = '(\n';
|
|
335
|
+
const paramAnnotations = [];
|
|
336
|
+
forEach(params, (paramName, param) => {
|
|
337
|
+
paramAnnotations.push( renderAnnotationAssignmentsAndDocComment(param, childEnv) + childEnv.indent + quoteIdIfRequired(paramName) );
|
|
338
|
+
});
|
|
339
|
+
result += `${paramAnnotations.join(',\n')}\n${env.indent})`;
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
|
|
224
343
|
/**
|
|
225
344
|
* Render an artifact. Return the resulting source string.
|
|
226
345
|
*
|
|
@@ -251,7 +370,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
251
370
|
case 'function':
|
|
252
371
|
return renderActionOrFunction(artifactName, art, env);
|
|
253
372
|
case 'event':
|
|
254
|
-
return
|
|
373
|
+
return renderEvent(artifactName, art, env);
|
|
255
374
|
default:
|
|
256
375
|
throw new ModelError(`Unknown artifact kind: ${art.kind}`);
|
|
257
376
|
}
|
|
@@ -262,15 +381,13 @@ function toCdsSourceCsn(csn, options) {
|
|
|
262
381
|
* @param {CSN.Artifact} art
|
|
263
382
|
* @param {CdlRenderEnvironment} env
|
|
264
383
|
*/
|
|
265
|
-
function
|
|
266
|
-
let result =
|
|
384
|
+
function renderEvent(artifactName, art, env) {
|
|
385
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
267
386
|
const childEnv = increaseIndent(env);
|
|
268
387
|
const normalizedArtifactName = renderArtifactName(artifactName);
|
|
269
388
|
result += `${env.indent}event ${normalizedArtifactName}`;
|
|
270
|
-
if (art.includes)
|
|
271
|
-
|
|
272
|
-
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name)).join(', ')}`;
|
|
273
|
-
}
|
|
389
|
+
if (art.includes)
|
|
390
|
+
result += renderIncludes(art.includes);
|
|
274
391
|
if (art.query || art.projection) {
|
|
275
392
|
env._artifact = art;
|
|
276
393
|
result += ' : ';
|
|
@@ -293,49 +410,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
293
410
|
return result;
|
|
294
411
|
}
|
|
295
412
|
|
|
296
|
-
/* FIXME: Not yet required
|
|
297
|
-
// Returns the artifact or element that constitutes the final type of
|
|
298
|
-
// construct 'node', i.e. the object in which we would find type properties for
|
|
299
|
-
// 'node'. Note that this may well be 'node' itself.
|
|
300
|
-
function getFinalTypeOf(node) {
|
|
301
|
-
if (node && node.type) {
|
|
302
|
-
if (isBuiltinType(node.type)) {
|
|
303
|
-
return node;
|
|
304
|
-
}
|
|
305
|
-
return getFinalTypeOf(node.type);
|
|
306
|
-
}
|
|
307
|
-
return node;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Resolve path array 'ref' against artifact 'base' (or against 'csn.definitions'
|
|
311
|
-
// if no 'base' given).
|
|
312
|
-
// Return the resulting artifact or element (or 'undefined' if not found).
|
|
313
|
-
function resolveRef(ref, base) {
|
|
314
|
-
let result = base;
|
|
315
|
-
for (let i = 0; i < ref.length; i++) {
|
|
316
|
-
let pathStep = ref[i].id || ref[i];
|
|
317
|
-
// Only first path step may be looked up in 'definitions'
|
|
318
|
-
if (i === 0 && !base) {
|
|
319
|
-
result = csn.definitions[pathStep];
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
// Structured type
|
|
323
|
-
else if (result && result.elements) {
|
|
324
|
-
result = getFinalTypeOf(result.elements[pathStep]);
|
|
325
|
-
}
|
|
326
|
-
// Association
|
|
327
|
-
else if (result && result.target) {
|
|
328
|
-
result = resolveRef([pathStep], csn.definitions[result.target]);
|
|
329
|
-
}
|
|
330
|
-
// Not resolvable
|
|
331
|
-
else {
|
|
332
|
-
return undefined;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return result;
|
|
336
|
-
}
|
|
337
|
-
*/
|
|
338
|
-
|
|
339
413
|
/**
|
|
340
414
|
* Render a context or service. Return the resulting source string.
|
|
341
415
|
*
|
|
@@ -344,12 +418,11 @@ function toCdsSourceCsn(csn, options) {
|
|
|
344
418
|
* @param {CdlRenderEnvironment} env
|
|
345
419
|
*/
|
|
346
420
|
function renderContext(artifactName, art, env) {
|
|
347
|
-
let result =
|
|
421
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
348
422
|
result += `${env.indent + (art.abstract ? 'abstract ' : '') + art.kind} ${renderArtifactName(artifactName)}`;
|
|
349
|
-
if (art.includes)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
423
|
+
if (art.includes)
|
|
424
|
+
result += renderIncludes(art.includes);
|
|
425
|
+
|
|
353
426
|
return `${result} {};\n`;
|
|
354
427
|
}
|
|
355
428
|
|
|
@@ -362,23 +435,29 @@ function toCdsSourceCsn(csn, options) {
|
|
|
362
435
|
* @return {string}
|
|
363
436
|
*/
|
|
364
437
|
function renderEntity(artifactName, art, env) {
|
|
365
|
-
let result =
|
|
438
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
366
439
|
const childEnv = increaseIndent(env);
|
|
367
440
|
const normalizedArtifactName = renderArtifactName(artifactName);
|
|
368
441
|
result += `${env.indent + (art.abstract ? 'abstract ' : '')}entity ${normalizedArtifactName}`;
|
|
369
442
|
const parameters = Object.keys(art.params || []).map(name => renderParameter(name, art.params[name], childEnv)).join(',\n');
|
|
370
443
|
result += (parameters === '') ? '' : ` (\n${parameters}\n${env.indent})`;
|
|
371
|
-
if (art.includes)
|
|
372
|
-
|
|
373
|
-
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name)).join(', ')}`;
|
|
374
|
-
}
|
|
444
|
+
if (art.includes)
|
|
445
|
+
result += renderIncludes(art.includes);
|
|
375
446
|
result += ' {\n';
|
|
376
447
|
for (const name in art.elements) {
|
|
377
448
|
const element = art.elements[name];
|
|
378
449
|
// For subelement annotations, this seems to be a pattern to recognize them
|
|
379
450
|
// plus some other stuff unfortunately...
|
|
380
|
-
if (element.type && element.elements)
|
|
381
|
-
subelementAnnotates.push(
|
|
451
|
+
if (element.type && element.elements) {
|
|
452
|
+
subelementAnnotates.push({
|
|
453
|
+
annotate: artifactName,
|
|
454
|
+
elements: {
|
|
455
|
+
[name]: {
|
|
456
|
+
elements: element.elements,
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
}
|
|
382
461
|
result += renderElement(name, element, childEnv);
|
|
383
462
|
}
|
|
384
463
|
|
|
@@ -398,8 +477,8 @@ function toCdsSourceCsn(csn, options) {
|
|
|
398
477
|
*/
|
|
399
478
|
function renderElement(elementName, elm, env, isSubElement) {
|
|
400
479
|
env.elementName = elementName;
|
|
401
|
-
let result =
|
|
402
|
-
const quotedElementName =
|
|
480
|
+
let result = renderAnnotationAssignmentsAndDocComment(elm, env);
|
|
481
|
+
const quotedElementName = quoteIdIfRequired(elementName);
|
|
403
482
|
result += `${env.indent + (elm.virtual ? 'virtual ' : '') +
|
|
404
483
|
(elm.key && !isSubElement ? 'key ' : '') +
|
|
405
484
|
((elm.masked && !elm._ignoreMasked) ? 'masked ' : '') +
|
|
@@ -413,22 +492,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
413
492
|
return `${result};\n`;
|
|
414
493
|
}
|
|
415
494
|
|
|
416
|
-
/**
|
|
417
|
-
* Return the SELECT of the leading query of query 'query'
|
|
418
|
-
*
|
|
419
|
-
* @param {CSN.Query} query
|
|
420
|
-
*/
|
|
421
|
-
function leadingQuerySelect(query) {
|
|
422
|
-
if (query.SELECT)
|
|
423
|
-
return query.SELECT;
|
|
424
|
-
|
|
425
|
-
// Sanity checks
|
|
426
|
-
if (!query.SET || !query.SET.args || !query.SET.args[0])
|
|
427
|
-
throw new ModelError(`Expecting set with args in query: ${JSON.stringify(query)}`);
|
|
428
|
-
|
|
429
|
-
return leadingQuerySelect(query.SET.args[0]);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
495
|
/**
|
|
433
496
|
* Render a query's actions and functions (if any) separately as extend-statements, so that actions
|
|
434
497
|
* work not only for projections but also for views, which have no syntax (yet) to directly specify
|
|
@@ -452,9 +515,13 @@ function toCdsSourceCsn(csn, options) {
|
|
|
452
515
|
|
|
453
516
|
/**
|
|
454
517
|
* Render annotations that were extended to a query element of a view or projection (they only
|
|
455
|
-
* appear in the view's 'elements', not in their 'columns', because the element
|
|
456
|
-
* even be in 'columns', e.g. if it was expanded from a '*').
|
|
457
|
-
* statement or an empty string if none required.
|
|
518
|
+
* appear in the view's 'elements', not in their 'columns' for client CSN, because the element
|
|
519
|
+
* itself may not even be in 'columns', e.g. if it was expanded from a '*'). Return the
|
|
520
|
+
* resulting rendered 'annotate' statement or an empty string if none required.
|
|
521
|
+
*
|
|
522
|
+
* Note: In the past, we checked if the annotation also exists in the respective column,
|
|
523
|
+
* however, in client CSN, annotations are not part of the column and in parseCdl CSN,
|
|
524
|
+
* no `elements` exist.
|
|
458
525
|
*
|
|
459
526
|
* @param {string} artifactName
|
|
460
527
|
* @param {CSN.Artifact} art
|
|
@@ -462,43 +529,61 @@ function toCdsSourceCsn(csn, options) {
|
|
|
462
529
|
* @return {string}
|
|
463
530
|
*/
|
|
464
531
|
function renderQueryElementAnnotations(artifactName, art, env) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (col === '*')
|
|
471
|
-
continue;
|
|
472
|
-
|
|
473
|
-
// Column must have an alias or be a path - take last part of that as element name
|
|
474
|
-
columnMap[col.as || col.func || getLastPartOfRef(col.ref)] = col;
|
|
475
|
-
}
|
|
476
|
-
// Now iterate elements - render an annotation if it is different from the column's
|
|
477
|
-
const childEnv = increaseIndent(env);
|
|
478
|
-
let result = '';
|
|
479
|
-
for (const elemName in art.elements) {
|
|
480
|
-
let elemAnnotations = '';
|
|
481
|
-
const elem = art.elements[elemName];
|
|
482
|
-
for (const name in elem) {
|
|
483
|
-
if (!name.startsWith('@'))
|
|
484
|
-
continue;
|
|
485
|
-
|
|
486
|
-
const annotationValue = renderAnnotationAssignment(elem[name], name, childEnv);
|
|
487
|
-
// Skip annotation if column has the same
|
|
488
|
-
if (columnMap[elemName] && columnMap[elemName][name] &&
|
|
489
|
-
renderAnnotationAssignment(columnMap[elemName][name], name, childEnv) === annotationValue)
|
|
490
|
-
continue;
|
|
491
|
-
|
|
492
|
-
// Annotation names are never flattened
|
|
493
|
-
elemAnnotations += annotationValue;
|
|
494
|
-
}
|
|
495
|
-
if (elemAnnotations !== '')
|
|
496
|
-
result += `${elemAnnotations}${childEnv.indent}${quoteOrUppercaseId(elemName)};\n`;
|
|
497
|
-
}
|
|
498
|
-
if (result !== '')
|
|
499
|
-
result = `${env.indent}annotate ${renderArtifactName(artifactName)} with {\n${result}${env.indent}};\n`;
|
|
532
|
+
const annotate = collectAnnotationsOfElements(artifactName, art);
|
|
533
|
+
if (annotate)
|
|
534
|
+
return renderExtensions([ annotate ], env);
|
|
535
|
+
return '';
|
|
536
|
+
}
|
|
500
537
|
|
|
501
|
-
|
|
538
|
+
/**
|
|
539
|
+
* Create an "annotate" statement as a CSN extension for all annotations of (sub-)elements.
|
|
540
|
+
* If no annotation was found, we return `null`.
|
|
541
|
+
*
|
|
542
|
+
* @param {string} artifactName
|
|
543
|
+
* @param {CSN.Artifact} artWithElements
|
|
544
|
+
* @return {CSN.Extension|null}
|
|
545
|
+
*/
|
|
546
|
+
function collectAnnotationsOfElements(artifactName, artWithElements) {
|
|
547
|
+
const annotate = { annotate: artifactName };
|
|
548
|
+
return collectAnnos(annotate, artWithElements) ? annotate : null;
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Recursive function to collect annotations. `annotateObj` will get an `elements`
|
|
552
|
+
* object with annotations only if there are annotations on `art`'s (sub-)elements.
|
|
553
|
+
*
|
|
554
|
+
* @return {boolean} True, if there were annotations, false otherwise.
|
|
555
|
+
*/
|
|
556
|
+
function collectAnnos(annotateObj, art) {
|
|
557
|
+
if (!art.elements)
|
|
558
|
+
return false;
|
|
559
|
+
|
|
560
|
+
const collected = { elements: Object.create(null) };
|
|
561
|
+
let hasAnnotation = false;
|
|
562
|
+
|
|
563
|
+
forEach(art.elements, (elemName, element) => {
|
|
564
|
+
if (!collected.elements[elemName])
|
|
565
|
+
collected.elements[elemName] = { };
|
|
566
|
+
|
|
567
|
+
let hasElementAnnotations = false;
|
|
568
|
+
for (const name in element) {
|
|
569
|
+
if (name.startsWith('@')) {
|
|
570
|
+
collected.elements[elemName][name] = element[name];
|
|
571
|
+
hasElementAnnotations = true;
|
|
572
|
+
hasAnnotation = true;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const hasSubAnnotations = collectAnnos(collected.elements[elemName], element);
|
|
577
|
+
if (!hasElementAnnotations && !hasSubAnnotations)
|
|
578
|
+
delete collected.elements[elemName]; // delete if no annotations exist
|
|
579
|
+
hasAnnotation = hasAnnotation || hasSubAnnotations;
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
if (hasAnnotation)
|
|
583
|
+
annotateObj.elements = collected.elements;
|
|
584
|
+
|
|
585
|
+
return hasAnnotation;
|
|
586
|
+
}
|
|
502
587
|
}
|
|
503
588
|
|
|
504
589
|
/**
|
|
@@ -513,25 +598,24 @@ function toCdsSourceCsn(csn, options) {
|
|
|
513
598
|
function renderViewSource(source, env) {
|
|
514
599
|
// Sub-SELECT
|
|
515
600
|
if (source.SELECT || source.SET) {
|
|
516
|
-
let result = `(${
|
|
601
|
+
let result = `(${renderQuery(source, false, 'view', increaseIndent(env))})`;
|
|
517
602
|
if (source.as)
|
|
518
|
-
result += ` as ${
|
|
603
|
+
result += ` as ${quoteIdIfRequired(source.as)}`;
|
|
519
604
|
|
|
520
605
|
return result;
|
|
521
606
|
}
|
|
522
607
|
// JOIN
|
|
523
608
|
else if (source.join) {
|
|
524
609
|
// One join operation, possibly with ON-condition
|
|
525
|
-
let result =
|
|
610
|
+
let result = `(${renderViewSource(source.args[0], env)}`;
|
|
526
611
|
for (let i = 1; i < source.args.length; i++) {
|
|
527
|
-
result
|
|
612
|
+
result += ` ${source.join} `;
|
|
528
613
|
result += renderJoinCardinality(source.cardinality);
|
|
529
614
|
result += `join ${renderViewSource(source.args[i], env)}`;
|
|
530
615
|
if (source.on)
|
|
531
616
|
result += ` on ${renderExpr(source.on, env, true, true)}`;
|
|
532
|
-
|
|
533
|
-
result += ')';
|
|
534
617
|
}
|
|
618
|
+
result += ')';
|
|
535
619
|
return result;
|
|
536
620
|
}
|
|
537
621
|
// Ordinary path, possibly with an alias
|
|
@@ -572,9 +656,8 @@ function toCdsSourceCsn(csn, options) {
|
|
|
572
656
|
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
573
657
|
const firstArtifactName = path.ref[0].id || path.ref[0];
|
|
574
658
|
|
|
575
|
-
let result = renderDocComment(path, env);
|
|
576
659
|
// Render the first path step (absolute name, with different quoting/naming ..)
|
|
577
|
-
result
|
|
660
|
+
let result = renderArtifactName(firstArtifactName);
|
|
578
661
|
|
|
579
662
|
// Even the first step might have parameters and/or a filter
|
|
580
663
|
if (path.ref[0].args)
|
|
@@ -605,11 +688,26 @@ function toCdsSourceCsn(csn, options) {
|
|
|
605
688
|
let result = renderAbsolutePath(path, env);
|
|
606
689
|
if (path.as) {
|
|
607
690
|
// Source had an alias - render it
|
|
608
|
-
result += ` as ${
|
|
691
|
+
result += ` as ${quoteIdIfRequired(path.as)}`;
|
|
609
692
|
}
|
|
610
693
|
return result;
|
|
611
694
|
}
|
|
612
695
|
|
|
696
|
+
/**
|
|
697
|
+
* Render the given columns.
|
|
698
|
+
*
|
|
699
|
+
* @param {any[]} columns
|
|
700
|
+
* @param {object} elements
|
|
701
|
+
* @param {CdlRenderEnvironment} env
|
|
702
|
+
* @return {string}
|
|
703
|
+
*/
|
|
704
|
+
function renderViewColumns(columns, env, elements = Object.create(null)) {
|
|
705
|
+
const result = columns.map(col => renderViewColumn(col, env, findElement(elements, col)))
|
|
706
|
+
.filter(s => s !== '')
|
|
707
|
+
.join(',\n');
|
|
708
|
+
return `${result}\n`;
|
|
709
|
+
}
|
|
710
|
+
|
|
613
711
|
/**
|
|
614
712
|
* Render a single view or projection column 'col', as it occurs in a select list or
|
|
615
713
|
* projection list within 'art', possibly with annotations.
|
|
@@ -621,7 +719,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
621
719
|
*/
|
|
622
720
|
function renderViewColumn(col, env, element) {
|
|
623
721
|
// Annotations and column
|
|
624
|
-
let result =
|
|
722
|
+
let result = '';
|
|
723
|
+
if (!col.doc) {
|
|
724
|
+
// TODO: In contrast to annotations, we do not render the doc comment as part
|
|
725
|
+
// of an `annotate` statement. That may change in the future.
|
|
726
|
+
result += renderDocComment(element, env);
|
|
727
|
+
}
|
|
728
|
+
// Note: parentheses are a workaround for #9015
|
|
729
|
+
result += renderAnnotationAssignmentsAndDocComment(col, env, { parens: true });
|
|
625
730
|
result += env.indent;
|
|
626
731
|
|
|
627
732
|
// only if column is virtual, keyword virtual was present in the source text
|
|
@@ -632,22 +737,19 @@ function toCdsSourceCsn(csn, options) {
|
|
|
632
737
|
// Use special rendering for .expand/.inline - renderExpr cannot easily handle some cases
|
|
633
738
|
result += key + ((col.expand || col.inline) ? renderInlineExpand(col, env) : renderExpr(col, env, true));
|
|
634
739
|
|
|
635
|
-
// Alias is already handled by renderInlineExpand
|
|
636
|
-
|
|
637
|
-
|
|
740
|
+
// Alias for inline/expand is already handled by renderInlineExpand
|
|
741
|
+
// A new association (cast with `type` and `target`) uses `as` as its primary name, not alias.
|
|
742
|
+
const isNewAssociation = col.cast && col.cast.type && col.cast.target;
|
|
743
|
+
if (col.as && !col.inline && !col.expand && !isNewAssociation)
|
|
744
|
+
result += ` as ${quoteIdIfRequired(col.as)}`;
|
|
638
745
|
|
|
639
746
|
// Explicit type provided for the view element?
|
|
640
747
|
if (col.cast) {
|
|
641
748
|
// Special case: Explicit association type is actually a redirect
|
|
642
|
-
if (col.cast.target)
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if (col.cast.on)
|
|
646
|
-
result += ` on ${renderExpr(col.cast.on, env, true, true)}`;
|
|
647
|
-
}
|
|
648
|
-
else {
|
|
749
|
+
if (col.cast.target && !col.cast.type)
|
|
750
|
+
result += ` : ${renderRedirectedTo(col.cast, env)}`;
|
|
751
|
+
else
|
|
649
752
|
result += ` : ${renderTypeReference(col.cast, env, true)}`;
|
|
650
|
-
}
|
|
651
753
|
}
|
|
652
754
|
return result;
|
|
653
755
|
}
|
|
@@ -676,16 +778,10 @@ function toCdsSourceCsn(csn, options) {
|
|
|
676
778
|
|
|
677
779
|
// We found a leaf - no further drilling
|
|
678
780
|
if (!obj.inline && !obj.expand) {
|
|
679
|
-
if (obj.cast && obj.cast.type)
|
|
781
|
+
if (obj.cast && obj.cast.type)
|
|
680
782
|
result += ` : ${renderTypeReference(obj.cast, createEnv())}`;
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
result += ` : redirected to ${renderAbsoluteNameWithQuotes(obj.cast.target)}`;
|
|
684
|
-
if (obj.cast.on)
|
|
685
|
-
result += ` on ${renderExpr(obj.cast.on, env, true, true)}`;
|
|
686
|
-
else if (obj.cast.keys)
|
|
687
|
-
result += ` { ${Object.keys(obj.cast.keys).map(name => renderForeignKey(obj.cast.keys[name], env)).join(', ')} }`;
|
|
688
|
-
}
|
|
783
|
+
else if (obj.cast && obj.cast.target) // test tbd
|
|
784
|
+
result += ` : ${renderRedirectedTo(obj.cast, env)}`;
|
|
689
785
|
return result;
|
|
690
786
|
}
|
|
691
787
|
|
|
@@ -720,7 +816,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
720
816
|
* Render .doc properties as comments in CDL
|
|
721
817
|
*
|
|
722
818
|
* @param {object} obj Object to render for
|
|
723
|
-
* @param {
|
|
819
|
+
* @param {CdlRenderEnvironment} env
|
|
724
820
|
* @returns {String}
|
|
725
821
|
*/
|
|
726
822
|
function renderDocComment(obj, env) {
|
|
@@ -729,7 +825,13 @@ function toCdsSourceCsn(csn, options) {
|
|
|
729
825
|
else if (obj && obj.doc === null) // empty doc comment needs to be rendered
|
|
730
826
|
return `\n${env.indent}/** */\n`;
|
|
731
827
|
|
|
732
|
-
|
|
828
|
+
// Smaller comment for single-line comments. If the comments starts or ends with whitespace
|
|
829
|
+
// we must use a block comment, or it will be lost when compiling the source again.
|
|
830
|
+
if (!obj.doc.includes('\n') && !/^\s|\s$/.test(obj.doc))
|
|
831
|
+
return `${env.indent}/** ${obj.doc} */\n`;
|
|
832
|
+
|
|
833
|
+
const comment = obj.doc.split('\n').map(line => `${env.indent} * ${line}`).join('\n');
|
|
834
|
+
return `${env.indent}/**\n${comment}\n${env.indent} */\n`;
|
|
733
835
|
}
|
|
734
836
|
|
|
735
837
|
/**
|
|
@@ -743,7 +845,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
743
845
|
*/
|
|
744
846
|
function renderView(artifactName, art, env) {
|
|
745
847
|
const syntax = (art.projection) ? 'projection' : 'entity';
|
|
746
|
-
let result =
|
|
848
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
747
849
|
result += `${env.indent}${art.abstract ? 'abstract ' : ''}${syntax === 'projection' ? 'entity' : syntax} ${renderArtifactName(artifactName)}`;
|
|
748
850
|
if (art.params) {
|
|
749
851
|
const childEnv = increaseIndent(env);
|
|
@@ -775,48 +877,28 @@ function toCdsSourceCsn(csn, options) {
|
|
|
775
877
|
* @param {object} [elements]
|
|
776
878
|
*/
|
|
777
879
|
function renderQuery(query, isLeadingQuery, syntax, env, path = [], elements = query.elements || Object.create(null)) {
|
|
778
|
-
let result = renderDocComment(query, env);
|
|
779
|
-
// Set operator, like UNION, INTERSECT, ...
|
|
780
880
|
if (query.SET) {
|
|
781
|
-
//
|
|
782
|
-
|
|
783
|
-
// FIXME: Clarify if set operators can be n-ary (assuming binary here)
|
|
784
|
-
if (query.SET.op) {
|
|
785
|
-
// Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
|
|
786
|
-
for (let i = 1; i < query.SET.args.length; i++)
|
|
787
|
-
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, 'view', env, path.concat([ 'SET', 'args', i ]), elements)}`;
|
|
788
|
-
}
|
|
789
|
-
result += ')';
|
|
790
|
-
// Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
|
|
791
|
-
// each SELECT)
|
|
792
|
-
if (query.SET.orderBy)
|
|
793
|
-
result += `${continueIndent(result, env)}order by ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
|
|
794
|
-
|
|
795
|
-
if (query.SET.limit)
|
|
796
|
-
result += `${continueIndent(result, env)}${renderLimit(query.SET.limit, env)}`;
|
|
797
|
-
|
|
798
|
-
return result;
|
|
881
|
+
// Set operator, such as UNION, INTERSECT, or EXCEPT...
|
|
882
|
+
return renderQuerySet();
|
|
799
883
|
}
|
|
800
|
-
// Otherwise must have a SELECT
|
|
801
884
|
else if (!query.SELECT) {
|
|
885
|
+
// ...otherwise must have a SELECT
|
|
802
886
|
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
803
887
|
}
|
|
888
|
+
|
|
889
|
+
let result = '';
|
|
804
890
|
const select = query.SELECT;
|
|
805
891
|
const childEnv = increaseIndent(env);
|
|
806
892
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
result += `select from ${renderViewSource(select.from, env)}`;
|
|
811
|
-
else
|
|
812
|
-
throw new ModelError(`Unknown query syntax: ${syntax}`);
|
|
893
|
+
// If not a projection, must be view/entity.
|
|
894
|
+
result += (syntax === 'projection') ? 'projection on ' : 'select from ';
|
|
895
|
+
result += renderViewSource(select.from, env);
|
|
813
896
|
|
|
814
897
|
if (select.mixin) {
|
|
815
898
|
let elems = '';
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
899
|
+
forEach(select.mixin, (name, mixin) => {
|
|
900
|
+
elems += renderElement(name, mixin, childEnv);
|
|
901
|
+
});
|
|
820
902
|
if (elems) {
|
|
821
903
|
result += ' mixin {\n';
|
|
822
904
|
result += elems;
|
|
@@ -826,13 +908,12 @@ function toCdsSourceCsn(csn, options) {
|
|
|
826
908
|
result += select.distinct ? ' distinct' : '';
|
|
827
909
|
if (select.columns) {
|
|
828
910
|
result += ' {\n';
|
|
829
|
-
result +=
|
|
830
|
-
.filter(s => s !== '')
|
|
831
|
-
.join(',\n')}\n`;
|
|
911
|
+
result += renderViewColumns(select.columns, increaseIndent(env), elements);
|
|
832
912
|
result += `${env.indent}}`;
|
|
833
913
|
}
|
|
914
|
+
|
|
834
915
|
if (select.excluding) {
|
|
835
|
-
result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${
|
|
916
|
+
result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${quoteIdIfRequired(id)}`).join(',\n')}\n`;
|
|
836
917
|
result += `${env.indent}}`;
|
|
837
918
|
}
|
|
838
919
|
// FIXME: Currently, only projections can have actions and functions, but we cannot distinguish
|
|
@@ -890,6 +971,29 @@ function toCdsSourceCsn(csn, options) {
|
|
|
890
971
|
|
|
891
972
|
return limitStr;
|
|
892
973
|
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Render UNION, INTERSECT, and EXCEPT, i.e. sets.
|
|
977
|
+
*
|
|
978
|
+
* @return {string}
|
|
979
|
+
*/
|
|
980
|
+
function renderQuerySet() {
|
|
981
|
+
const subQueries = query.SET.args.map((arg, i) => {
|
|
982
|
+
// First arg may be leading query
|
|
983
|
+
const subQuery = renderQuery(arg, isLeadingQuery && (i === 0), 'view', env, path.concat([ 'SET', 'args', i ]), elements);
|
|
984
|
+
return `(${subQuery})`;
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
let setResult = subQueries.join(`\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} `);
|
|
988
|
+
// Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
|
|
989
|
+
// each SELECT)
|
|
990
|
+
if (query.SET.orderBy)
|
|
991
|
+
setResult += `${continueIndent(setResult, env)}order by ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
|
|
992
|
+
|
|
993
|
+
if (query.SET.limit)
|
|
994
|
+
setResult += `${continueIndent(setResult, env)}${renderLimit(query.SET.limit, env)}`;
|
|
995
|
+
return setResult;
|
|
996
|
+
}
|
|
893
997
|
}
|
|
894
998
|
|
|
895
999
|
/**
|
|
@@ -901,7 +1005,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
901
1005
|
* @return {string}
|
|
902
1006
|
*/
|
|
903
1007
|
function renderOrderByEntry(entry, env) {
|
|
904
|
-
let result =
|
|
1008
|
+
let result = renderAnnotationAssignmentsAndDocComment(entry, env) + renderExpr(entry, env, true, false, true);
|
|
905
1009
|
if (entry.sort)
|
|
906
1010
|
result += ` ${entry.sort}`;
|
|
907
1011
|
|
|
@@ -925,7 +1029,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
925
1029
|
let result = '';
|
|
926
1030
|
const childEnv = increaseIndent(env);
|
|
927
1031
|
for (const name in art.actions)
|
|
928
|
-
result +=
|
|
1032
|
+
result += renderActionOrFunction(name, art.actions[name], childEnv);
|
|
929
1033
|
|
|
930
1034
|
// Even if we have seen actions/functions, they might all have been ignored
|
|
931
1035
|
if (result !== '')
|
|
@@ -943,13 +1047,23 @@ function toCdsSourceCsn(csn, options) {
|
|
|
943
1047
|
* @return {string}
|
|
944
1048
|
*/
|
|
945
1049
|
function renderActionOrFunction(actionName, act, env) {
|
|
946
|
-
let result = `${
|
|
1050
|
+
let result = `${renderAnnotationAssignmentsAndDocComment(act, env) + env.indent + act.kind} ${renderArtifactName(actionName)}`;
|
|
947
1051
|
const childEnv = increaseIndent(env);
|
|
948
1052
|
const parameters = Object.keys(act.params || []).map(name => renderParameter(name, act.params[name], childEnv)).join(',\n');
|
|
949
1053
|
result += (parameters === '') ? '()' : `(\n${parameters}\n${env.indent})`;
|
|
950
1054
|
if (act.returns) {
|
|
951
|
-
if (act.returns.type && act.returns.elements)
|
|
952
|
-
|
|
1055
|
+
if (act.returns.type && act.returns.elements) {
|
|
1056
|
+
// Annotation in action returns' structure. Render it as an annotate statement.
|
|
1057
|
+
const isBoundAction = (env.artifactName !== actionName);
|
|
1058
|
+
const anno = {
|
|
1059
|
+
annotate: env.artifactName || actionName,
|
|
1060
|
+
};
|
|
1061
|
+
if (isBoundAction)
|
|
1062
|
+
anno.actions = { [actionName]: { returns: { elements: act.returns.elements } } };
|
|
1063
|
+
else
|
|
1064
|
+
anno.returns = { elements: act.returns.elements };
|
|
1065
|
+
subelementAnnotates.push(anno);
|
|
1066
|
+
}
|
|
953
1067
|
result += ` returns ${renderTypeReference(act.returns, env)}${renderNullability(act.returns)}`;
|
|
954
1068
|
}
|
|
955
1069
|
|
|
@@ -966,7 +1080,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
966
1080
|
* @return {string}
|
|
967
1081
|
*/
|
|
968
1082
|
function renderParameter(parName, par, env) {
|
|
969
|
-
let result = `${
|
|
1083
|
+
let result = `${renderAnnotationAssignmentsAndDocComment(par, env) + env.indent + quoteIdIfRequired(parName)} : ${renderTypeReference(par, env)}`;
|
|
970
1084
|
if (par.default)
|
|
971
1085
|
result += ` default ${renderExpr(par.default, env)}`;
|
|
972
1086
|
|
|
@@ -985,14 +1099,11 @@ function toCdsSourceCsn(csn, options) {
|
|
|
985
1099
|
* @return {string}
|
|
986
1100
|
*/
|
|
987
1101
|
function renderTypeOrAnnotation(artifactName, art, env, artType) {
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
// Includes are never flattened (don't exist in HANA)
|
|
994
|
-
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name)).join(', ')}`;
|
|
995
|
-
}
|
|
1102
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
1103
|
+
result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName)}`;
|
|
1104
|
+
if (art.includes)
|
|
1105
|
+
result += renderIncludes(art.includes);
|
|
1106
|
+
|
|
996
1107
|
const childEnv = increaseIndent(env);
|
|
997
1108
|
if (art.elements && !art.type) {
|
|
998
1109
|
// Structured type or annotation with anonymous struct type
|
|
@@ -1031,8 +1142,16 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1031
1142
|
if (elm.items.notNull != null)
|
|
1032
1143
|
rc += elm.items.notNull ? ' not null' : ' null';
|
|
1033
1144
|
// many sub element annotates
|
|
1034
|
-
|
|
1035
|
-
|
|
1145
|
+
// TODO(andre): This does handle deeply nested elements because we take the full `elements`.
|
|
1146
|
+
// But when is env.elementName set and when not?
|
|
1147
|
+
if (elm.items.type && elm.items.elements && env.artifactName) {
|
|
1148
|
+
const annotate = { annotate: env.artifactName };
|
|
1149
|
+
if (env.elementName)
|
|
1150
|
+
annotate.elements = { [env.elementName]: elm.items };
|
|
1151
|
+
else
|
|
1152
|
+
annotate.elements = elm.items.elements;
|
|
1153
|
+
subelementAnnotates.push(annotate);
|
|
1154
|
+
}
|
|
1036
1155
|
|
|
1037
1156
|
return rc;
|
|
1038
1157
|
}
|
|
@@ -1068,15 +1187,20 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1068
1187
|
((elm.type === comp) ? ' of ' : ' to ');
|
|
1069
1188
|
}
|
|
1070
1189
|
|
|
1071
|
-
//
|
|
1072
|
-
|
|
1190
|
+
// `targetAspect` may be set by the core compiler and refers to the original named or unnamed aspect.
|
|
1191
|
+
// In parseCdl, `target` may still be an object containing elements. This would be replaced
|
|
1192
|
+
// by targetAspect in client CSN, but we can't rely on that.
|
|
1193
|
+
// If a name exists (either in target or targetAspect), prefer it over rendering elements.
|
|
1194
|
+
const elements = elm.target && elm.target.elements || elm.targetAspect && elm.targetAspect.elements;
|
|
1195
|
+
if (typeof elm.target === 'string' || typeof elm.targetAspect === 'string') {
|
|
1073
1196
|
result += renderAbsolutePath({ ref: [ elm.target || elm.targetAspect ] }, env);
|
|
1074
1197
|
}
|
|
1075
|
-
else if (
|
|
1198
|
+
else if (elements) {
|
|
1199
|
+
// anonymous aspect, either parseCdl or client CSN.
|
|
1076
1200
|
const childEnv = increaseIndent(env);
|
|
1077
1201
|
result += '{\n';
|
|
1078
|
-
for (const name in
|
|
1079
|
-
result += renderElement(name,
|
|
1202
|
+
for (const name in elements)
|
|
1203
|
+
result += renderElement(name, elements[name], childEnv);
|
|
1080
1204
|
|
|
1081
1205
|
result += `${env.indent}}`;
|
|
1082
1206
|
}
|
|
@@ -1084,7 +1208,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1084
1208
|
throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
|
|
1085
1209
|
}
|
|
1086
1210
|
|
|
1087
|
-
|
|
1088
1211
|
// ON-condition (if any)
|
|
1089
1212
|
if (elm.on)
|
|
1090
1213
|
result += ` on ${renderExpr(elm.on, env, true, true)}`;
|
|
@@ -1116,14 +1239,8 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1116
1239
|
}
|
|
1117
1240
|
|
|
1118
1241
|
// If we get here, it must be a named type
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
}
|
|
1122
|
-
else {
|
|
1123
|
-
// Simple absolute name
|
|
1124
|
-
// Type names are never flattened (derived types are unraveled in HANA)
|
|
1125
|
-
result += getResultingName(elm.type);
|
|
1126
|
-
}
|
|
1242
|
+
result += renderNamedTypeWithParameters(elm);
|
|
1243
|
+
|
|
1127
1244
|
if (elm.enum && !noEnum)
|
|
1128
1245
|
result += renderEnum(elm.enum, env);
|
|
1129
1246
|
|
|
@@ -1131,18 +1248,48 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1131
1248
|
}
|
|
1132
1249
|
|
|
1133
1250
|
/**
|
|
1134
|
-
*
|
|
1251
|
+
* Render REDIRECTED TO with its keys/on condition for the given artifact.
|
|
1252
|
+
*
|
|
1253
|
+
* @param {object} art
|
|
1254
|
+
* @param {CdlRenderEnvironment} env
|
|
1135
1255
|
* @return {string}
|
|
1136
1256
|
*/
|
|
1137
|
-
function
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1257
|
+
function renderRedirectedTo(art, env) {
|
|
1258
|
+
let result = `redirected to ${quoteIdIfRequired(art.target)}`;
|
|
1259
|
+
if (art.on)
|
|
1260
|
+
result += ` on ${renderExpr(art.on, env, true, true)}`;
|
|
1261
|
+
else if (art.keys)
|
|
1262
|
+
result += ` { ${Object.keys(art.keys).map(name => renderForeignKey(art.keys[name], env)).join(', ')} }`;
|
|
1263
|
+
return result;
|
|
1264
|
+
}
|
|
1142
1265
|
|
|
1143
|
-
|
|
1266
|
+
/**
|
|
1267
|
+
* Render the named type with optional parameters, e.g. `MyString(length: 10)`.
|
|
1268
|
+
* @param {CSN.Artifact} artWithType
|
|
1269
|
+
* @return {string}
|
|
1270
|
+
*/
|
|
1271
|
+
function renderNamedTypeWithParameters(artWithType) {
|
|
1272
|
+
let result = '';
|
|
1273
|
+
|
|
1274
|
+
if (isBuiltinType(artWithType.type)) {
|
|
1275
|
+
// If there is a user-defined type with the same short name (cds.Integer -> Integer),
|
|
1276
|
+
// we render the full name, including the leading "cds."
|
|
1277
|
+
if (csn.definitions[artWithType.type.slice(4)])
|
|
1278
|
+
result += artWithType.type;
|
|
1279
|
+
else
|
|
1280
|
+
result += artWithType.type.slice(4);
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
// Simple absolute name
|
|
1284
|
+
// Type names are never flattened (derived types are unraveled in HANA)
|
|
1285
|
+
result += renderArtifactName(artWithType.type);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
result += renderTypeParameters(artWithType);
|
|
1289
|
+
return result;
|
|
1144
1290
|
}
|
|
1145
1291
|
|
|
1292
|
+
|
|
1146
1293
|
/**
|
|
1147
1294
|
* Render the 'enum { ... } part of a type declaration
|
|
1148
1295
|
*
|
|
@@ -1153,21 +1300,32 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1153
1300
|
function renderEnum(enumPart, env) {
|
|
1154
1301
|
let result = ' enum {\n';
|
|
1155
1302
|
const childEnv = increaseIndent(env);
|
|
1156
|
-
for (const name in enumPart)
|
|
1157
|
-
|
|
1158
|
-
result += renderDocComment(enumConst, childEnv);
|
|
1159
|
-
result += renderAnnotationAssignments(enumConst, childEnv);
|
|
1160
|
-
result += childEnv.indent + quoteIdIfRequired(name);
|
|
1161
|
-
if (enumConst.val !== undefined)
|
|
1162
|
-
result += ` = ${renderExpr(enumConst, childEnv)}`;
|
|
1163
|
-
else if (enumConst['#'] !== undefined)
|
|
1164
|
-
result += ` = #${enumConst['#']}`;
|
|
1165
|
-
result += ';\n';
|
|
1166
|
-
}
|
|
1303
|
+
for (const name in enumPart)
|
|
1304
|
+
result += renderEnumElement(name, enumPart[name], childEnv);
|
|
1167
1305
|
result += `${env.indent}}`;
|
|
1168
1306
|
return result;
|
|
1169
1307
|
}
|
|
1170
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* Render the element of a `<type> enum {}` structure.
|
|
1311
|
+
*
|
|
1312
|
+
* @param {string} name
|
|
1313
|
+
* @param {CSN.EnumValue} enumValue
|
|
1314
|
+
* @param {CdlRenderEnvironment} env
|
|
1315
|
+
* @return {string}
|
|
1316
|
+
*/
|
|
1317
|
+
function renderEnumElement(name, enumValue, env) {
|
|
1318
|
+
let result = '';
|
|
1319
|
+
result += renderAnnotationAssignmentsAndDocComment(enumValue, env);
|
|
1320
|
+
result += env.indent + quoteIdIfRequired(name);
|
|
1321
|
+
if (enumValue.val !== undefined)
|
|
1322
|
+
result += ` = ${renderExpr(enumValue, env)}`;
|
|
1323
|
+
else if (enumValue['#'] !== undefined)
|
|
1324
|
+
result += ` = #${enumValue['#']}`;
|
|
1325
|
+
result += ';\n';
|
|
1326
|
+
return result;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1171
1329
|
/**
|
|
1172
1330
|
* Render an annotation value (somewhat like a simplified expression, with slightly different
|
|
1173
1331
|
* representation)
|
|
@@ -1177,17 +1335,25 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1177
1335
|
*/
|
|
1178
1336
|
function renderAnnotationValue(x, env) {
|
|
1179
1337
|
if (Array.isArray(x)) {
|
|
1180
|
-
// Render array parts as values
|
|
1181
|
-
|
|
1338
|
+
// Render array parts as values. Spaces required if last array value is
|
|
1339
|
+
// a delimited identifier.
|
|
1340
|
+
return `[ ${x.map(item => renderAnnotationValue(item, env)).join(', ')} ]`;
|
|
1182
1341
|
}
|
|
1183
1342
|
else if (typeof x === 'object' && x !== null) {
|
|
1184
1343
|
// Enum symbol
|
|
1185
|
-
if (x['#'])
|
|
1344
|
+
if (x['#']) {
|
|
1186
1345
|
return `#${x['#']}`;
|
|
1187
|
-
|
|
1346
|
+
}
|
|
1188
1347
|
// Shorthand for absolute path (as string)
|
|
1189
|
-
else if (x['='])
|
|
1348
|
+
else if (x['=']) {
|
|
1190
1349
|
return quotePathString(x['=']);
|
|
1350
|
+
}
|
|
1351
|
+
// Shorthand for ellipsis: `... up to <val>`
|
|
1352
|
+
else if (x['...']) {
|
|
1353
|
+
if (x['...'] === true)
|
|
1354
|
+
return '...';
|
|
1355
|
+
return `... up to ${renderAnnotationValue(x['...'], env)}`;
|
|
1356
|
+
}
|
|
1191
1357
|
|
|
1192
1358
|
// Struct value (can actually only occur within an array)
|
|
1193
1359
|
|
|
@@ -1206,172 +1372,54 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1206
1372
|
}
|
|
1207
1373
|
|
|
1208
1374
|
/**
|
|
1209
|
-
* Render
|
|
1210
|
-
* (no trailing LF, don't indent if inline)
|
|
1375
|
+
* Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
|
|
1211
1376
|
*
|
|
1212
|
-
* @param {
|
|
1213
|
-
* @param {
|
|
1214
|
-
* @
|
|
1215
|
-
* @param {boolean} [inExpr=false] Whether the expression is already inside another expression
|
|
1216
|
-
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `inExpr === false`
|
|
1217
|
-
* Note: This is a hack for casts() inside groupBy.
|
|
1377
|
+
* @param {string|object} s
|
|
1378
|
+
* @param {number} idx
|
|
1379
|
+
* @returns {string}
|
|
1218
1380
|
*/
|
|
1219
|
-
function
|
|
1220
|
-
//
|
|
1221
|
-
if (
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
if (typeof x === 'object' && x !== null) {
|
|
1225
|
-
if ((inExpr || alwaysRenderCast) && x.cast && x.cast.type)
|
|
1226
|
-
return renderExplicitTypeCast(renderExprObject());
|
|
1227
|
-
return renderExprObject();
|
|
1228
|
-
}
|
|
1381
|
+
function renderPathStep(s, idx, inline, env) {
|
|
1382
|
+
// Simple id or absolute name
|
|
1383
|
+
if (typeof s === 'string') {
|
|
1384
|
+
// In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
|
|
1385
|
+
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1229
1386
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
/**
|
|
1235
|
-
* Various special cases represented as objects
|
|
1236
|
-
*
|
|
1237
|
-
* @returns {string}
|
|
1238
|
-
*/
|
|
1239
|
-
function renderExprObject() {
|
|
1240
|
-
if (x.list) {
|
|
1241
|
-
// set "inExpr" to false: treat list elements as new expressions
|
|
1242
|
-
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
|
|
1243
|
-
}
|
|
1244
|
-
else if (x.val !== undefined) {
|
|
1245
|
-
// Literal value, possibly with explicit 'literal' property
|
|
1246
|
-
switch (x.literal || typeof x.val) {
|
|
1247
|
-
case 'number':
|
|
1248
|
-
case 'boolean':
|
|
1249
|
-
case 'null':
|
|
1250
|
-
return x.val;
|
|
1251
|
-
case 'x':
|
|
1252
|
-
case 'date':
|
|
1253
|
-
case 'time':
|
|
1254
|
-
case 'timestamp':
|
|
1255
|
-
return `${x.literal}'${x.val}'`;
|
|
1256
|
-
case 'string':
|
|
1257
|
-
return renderString(x.val, env);
|
|
1258
|
-
case 'object':
|
|
1259
|
-
if (x.val === null)
|
|
1260
|
-
return 'null';
|
|
1387
|
+
if (idx === 0 &&
|
|
1388
|
+
s.startsWith('$'))
|
|
1389
|
+
return s;
|
|
1261
1390
|
|
|
1262
|
-
|
|
1263
|
-
default:
|
|
1264
|
-
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
// Enum symbol
|
|
1268
|
-
else if (x['#']) {
|
|
1269
|
-
return `#${x['#']}`;
|
|
1270
|
-
}
|
|
1271
|
-
// Reference: Array of path steps, possibly preceded by ':'
|
|
1272
|
-
else if (x.ref) {
|
|
1273
|
-
// FIXME: no extra magic with x.param or x.global
|
|
1274
|
-
return `${(x.param || x.global) ? ':' : ''}${x.ref.map(renderPathStep).join('.')}`;
|
|
1275
|
-
}
|
|
1276
|
-
else if (x.xpr && x.func) {
|
|
1277
|
-
// window function
|
|
1278
|
-
const funcDef = renderFunc( x.func, x, null, a => renderArgs(a, '=>', env) );
|
|
1279
|
-
const windowFunctionOperator = x.xpr.shift(); // OVER ...
|
|
1280
|
-
return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
|
|
1281
|
-
}
|
|
1282
|
-
// Function call, possibly with args (use '=>' for named args)
|
|
1283
|
-
else if (x.func) {
|
|
1284
|
-
// test for non-regular HANA identifier that needs to be quoted
|
|
1285
|
-
// identifier {letter}({letter_or_digit}|[#$])*
|
|
1286
|
-
// letter [A-Za-z_]
|
|
1287
|
-
// letter_or_digit [A-Za-z_0-9]
|
|
1288
|
-
|
|
1289
|
-
const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
|
|
1290
|
-
const funcName = regex.test(x.func) ? x.func : quoteIdIfRequired(x.func);
|
|
1291
|
-
return renderFunc( funcName, x, 'cap', a => renderArgs(a, '=>', env) );
|
|
1292
|
-
}
|
|
1293
|
-
// Nested expression
|
|
1294
|
-
else if (x.xpr) {
|
|
1295
|
-
// Ensure `exists` is always enclosed by parentheses
|
|
1296
|
-
if ((inExpr && !x.cast ) || x.xpr.some(s => s === 'exists'))
|
|
1297
|
-
return `(${renderExpr(x.xpr, env, inline, true)})`;
|
|
1298
|
-
|
|
1299
|
-
return renderExpr(x.xpr, env, inline, true);
|
|
1300
|
-
}
|
|
1301
|
-
// Sub-select
|
|
1302
|
-
else if (x.SELECT) {
|
|
1303
|
-
// renderQuery for SELECT does not bring its own parentheses (because it is also used in renderView)
|
|
1304
|
-
return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
|
|
1305
|
-
}
|
|
1306
|
-
else if (x.SET) {
|
|
1307
|
-
// renderQuery for SET always brings its own parentheses (because it is also used in renderViewSource)
|
|
1308
|
-
return `${renderQuery(x, false, 'view', increaseIndent(env))}`;
|
|
1309
|
-
}
|
|
1310
|
-
else {
|
|
1311
|
-
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1312
|
-
}
|
|
1391
|
+
return quoteIdIfRequired(s);
|
|
1313
1392
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
* @param {number} idx
|
|
1330
|
-
* @returns {string}
|
|
1331
|
-
*/
|
|
1332
|
-
function renderPathStep(s, idx) {
|
|
1333
|
-
// Simple id or absolute name
|
|
1334
|
-
if (typeof s === 'string') {
|
|
1335
|
-
// In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
|
|
1336
|
-
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1337
|
-
|
|
1338
|
-
if (idx === 0 &&
|
|
1339
|
-
s.startsWith('$'))
|
|
1340
|
-
return s;
|
|
1341
|
-
|
|
1342
|
-
return quoteOrUppercaseId(s);
|
|
1393
|
+
// ID with filters or parameters
|
|
1394
|
+
else if (typeof s === 'object') {
|
|
1395
|
+
// Sanity check
|
|
1396
|
+
if (!s.func && !s.id)
|
|
1397
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1398
|
+
|
|
1399
|
+
// Not really a path step but an object-like function call
|
|
1400
|
+
if (s.func)
|
|
1401
|
+
return `${s.func}(${renderArgs(s, '=>', env)})`;
|
|
1402
|
+
|
|
1403
|
+
// Path step, possibly with view parameters and/or filters
|
|
1404
|
+
let result = `${quoteIdIfRequired(s.id)}`;
|
|
1405
|
+
if (s.args) {
|
|
1406
|
+
// View parameters
|
|
1407
|
+
result += `(${renderArgs(s, ':', env)})`;
|
|
1343
1408
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
if (!s.func && !s.id)
|
|
1348
|
-
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1349
|
-
|
|
1350
|
-
// Not really a path step but an object-like function call
|
|
1351
|
-
if (s.func)
|
|
1352
|
-
return `${s.func}(${renderArgs(s, '=>', env)})`;
|
|
1353
|
-
|
|
1354
|
-
// Path step, possibly with view parameters and/or filters
|
|
1355
|
-
let result = `${quoteOrUppercaseId(s.id)}`;
|
|
1356
|
-
if (s.args) {
|
|
1357
|
-
// View parameters
|
|
1358
|
-
result += `(${renderArgs(s, ':', env)})`;
|
|
1359
|
-
}
|
|
1360
|
-
if (s.where) {
|
|
1361
|
-
// Filter, possibly with cardinality
|
|
1362
|
-
result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
return result;
|
|
1409
|
+
if (s.where) {
|
|
1410
|
+
// Filter, possibly with cardinality
|
|
1411
|
+
result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
|
|
1366
1412
|
}
|
|
1367
1413
|
|
|
1368
|
-
|
|
1414
|
+
return result;
|
|
1369
1415
|
}
|
|
1416
|
+
|
|
1417
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1370
1418
|
}
|
|
1371
1419
|
|
|
1372
1420
|
/**
|
|
1373
1421
|
* Render function arguments or view parameters (positional if array, named if object/dict),
|
|
1374
|
-
* using 'sep' as separator for
|
|
1422
|
+
* using 'sep' as separator for named parameters
|
|
1375
1423
|
*
|
|
1376
1424
|
* @param {object} node with `args` to render
|
|
1377
1425
|
* @param {string} sep
|
|
@@ -1379,19 +1427,33 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1379
1427
|
* @returns {string}
|
|
1380
1428
|
*/
|
|
1381
1429
|
function renderArgs(node, sep, env) {
|
|
1382
|
-
const args = node.args
|
|
1430
|
+
const args = node.args || [];
|
|
1431
|
+
|
|
1383
1432
|
// Positional arguments
|
|
1384
1433
|
if (Array.isArray(args))
|
|
1385
|
-
return args.map(arg =>
|
|
1434
|
+
return args.map(arg => renderArgument(arg, env)).join(', ');
|
|
1386
1435
|
|
|
1387
1436
|
// Named arguments (object/dict)
|
|
1388
1437
|
else if (typeof args === 'object')
|
|
1389
|
-
return Object.keys(args).map(key => `${
|
|
1390
|
-
|
|
1438
|
+
return Object.keys(args).map(key => `${quoteIdIfRequired(key)} ${sep} ${renderArgument(args[key], env)}`).join(', ');
|
|
1391
1439
|
|
|
1392
1440
|
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1393
1441
|
}
|
|
1394
1442
|
|
|
1443
|
+
/**
|
|
1444
|
+
* Render a function argument, e.g. for generic functions or CAST().
|
|
1445
|
+
* Ensures that parentheses are used if necessary, e.g. for `someFct( (1=1), (1=1) )`.
|
|
1446
|
+
*
|
|
1447
|
+
* @param {any} arg
|
|
1448
|
+
* @param {CdlRenderEnvironment} env
|
|
1449
|
+
* @return {string}
|
|
1450
|
+
*/
|
|
1451
|
+
function renderArgument(arg, env) {
|
|
1452
|
+
// If the argument is a xpr with e.g. `=`, it may require parentheses.
|
|
1453
|
+
// For nested xpr, `renderExpr()` will already add parentheses.
|
|
1454
|
+
return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr), true);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1395
1457
|
/**
|
|
1396
1458
|
* Render a cardinality (only those parts that were actually provided)
|
|
1397
1459
|
*
|
|
@@ -1470,28 +1532,35 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1470
1532
|
}
|
|
1471
1533
|
|
|
1472
1534
|
/**
|
|
1473
|
-
* Render (primitive) type parameters of
|
|
1535
|
+
* Render (primitive) type parameters of artifact 'artWithType', i.e.
|
|
1474
1536
|
* length, precision and scale (even if incomplete), plus any other unknown ones.
|
|
1475
1537
|
*
|
|
1476
|
-
* @param {CSN.
|
|
1538
|
+
* @param {CSN.Artifact} artWithType
|
|
1477
1539
|
* @returns {string}
|
|
1478
1540
|
*/
|
|
1479
|
-
function renderTypeParameters(
|
|
1480
|
-
const params = [];
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
params.push(elm.length);
|
|
1541
|
+
function renderTypeParameters(artWithType) {
|
|
1542
|
+
const params = typeParameters.list.filter(param => artWithType[param] !== undefined);
|
|
1543
|
+
if (params.length === 0)
|
|
1544
|
+
return '';
|
|
1484
1545
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1546
|
+
// TODO(v3): Remove special handling of srid
|
|
1547
|
+
// For backwards compatibility, do not render srid as a named argument for POINT/GEOMETRY builtins.
|
|
1548
|
+
if (artWithType.srid !== undefined &&
|
|
1549
|
+
(artWithType.type === 'cds.hana.ST_POINT' || artWithType.type === 'cds.hana.ST_GEOMETRY'))
|
|
1550
|
+
return `(${artWithType.srid})`;
|
|
1487
1551
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1552
|
+
// Special cases for 1 or 2 arguments.
|
|
1553
|
+
if (params.length === 1 && artWithType.length !== undefined)
|
|
1554
|
+
return `(${artWithType.length})`;
|
|
1555
|
+
if (params.length === 2 && artWithType.precision !== undefined && artWithType.scale !== undefined)
|
|
1556
|
+
return `(${artWithType.precision}, ${artWithType.scale})`;
|
|
1490
1557
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1558
|
+
// Render named params
|
|
1559
|
+
const renderedParams = [];
|
|
1560
|
+
for (const param of params)
|
|
1561
|
+
renderedParams.push(`${param}: ${artWithType[param]}`);
|
|
1493
1562
|
|
|
1494
|
-
return
|
|
1563
|
+
return `(${renderedParams.join(', ')})`;
|
|
1495
1564
|
}
|
|
1496
1565
|
|
|
1497
1566
|
/**
|
|
@@ -1499,13 +1568,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1499
1568
|
*
|
|
1500
1569
|
* @param {object} obj Object that has annotations
|
|
1501
1570
|
* @param {CdlRenderEnvironment} env
|
|
1571
|
+
* @param {{parens: boolean}} [config] Config for renderAnnotationAssignment()
|
|
1502
1572
|
* @return {string}
|
|
1503
1573
|
*/
|
|
1504
|
-
function
|
|
1505
|
-
let result =
|
|
1574
|
+
function renderAnnotationAssignmentsAndDocComment(obj, env, config) {
|
|
1575
|
+
let result = renderDocComment(obj, env);
|
|
1506
1576
|
for (const name in obj) {
|
|
1507
1577
|
if (name.startsWith('@'))
|
|
1508
|
-
result += renderAnnotationAssignment(obj[name], name, env);
|
|
1578
|
+
result += renderAnnotationAssignment(obj[name], name, env, config);
|
|
1509
1579
|
}
|
|
1510
1580
|
return result;
|
|
1511
1581
|
}
|
|
@@ -1518,14 +1588,16 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1518
1588
|
* @param {any} anno Annotation value
|
|
1519
1589
|
* @param {string} name Annotation name, e.g. `@A.B.C#foo.C`
|
|
1520
1590
|
* @param {CdlRenderEnvironment} env
|
|
1591
|
+
* @param {object} [config] parens: Whether the annotation assignment must be surrounded by parentheses.
|
|
1521
1592
|
* @return {string} Rendered annotation, possibly quoted: `@![A.B.C#foo.C]: value`
|
|
1522
1593
|
*/
|
|
1523
|
-
function renderAnnotationAssignment(anno, name, env) {
|
|
1594
|
+
function renderAnnotationAssignment(anno, name, env, config = { parens: false }) {
|
|
1524
1595
|
name = name.substring(1);
|
|
1525
1596
|
// Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
|
|
1526
1597
|
const parts = name.split('#');
|
|
1527
1598
|
const nameBeforeVariant = parts[0];
|
|
1528
1599
|
const variant = parts[1];
|
|
1600
|
+
const { parens } = config;
|
|
1529
1601
|
|
|
1530
1602
|
// Identifier according to our grammar: /[$_a-zA-Z][$_a-zA-Z0-9]*/
|
|
1531
1603
|
// We expand this pattern to also include dots after the first character.
|
|
@@ -1536,123 +1608,21 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1536
1608
|
// even though that is the result after flattening.
|
|
1537
1609
|
const variantRequiresQuoting = variant && !/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(variant);
|
|
1538
1610
|
|
|
1539
|
-
let result
|
|
1611
|
+
let result = `${env.indent}@`;
|
|
1612
|
+
if (parens)
|
|
1613
|
+
result += '(';
|
|
1614
|
+
|
|
1540
1615
|
if (annoRequiresQuoting || variantRequiresQuoting)
|
|
1541
|
-
result
|
|
1616
|
+
result += quote(name);
|
|
1542
1617
|
else
|
|
1543
|
-
result
|
|
1618
|
+
result += name;
|
|
1544
1619
|
|
|
1545
1620
|
result += ` : ${renderAnnotationValue(anno, env)}`;
|
|
1621
|
+
if (parens)
|
|
1622
|
+
result += ')';
|
|
1546
1623
|
return `${result}\n`;
|
|
1547
1624
|
}
|
|
1548
1625
|
|
|
1549
|
-
/**
|
|
1550
|
-
* Render an absolute name 'absName', with appropriate quotes. Also record the
|
|
1551
|
-
* fact that 'absName' is used in 'env', so that an appropriate USING can be constructed
|
|
1552
|
-
* if necessary.
|
|
1553
|
-
*
|
|
1554
|
-
* @param {string} absName
|
|
1555
|
-
* @return {string}
|
|
1556
|
-
*/
|
|
1557
|
-
function renderAbsoluteNameWithQuotes(absName) {
|
|
1558
|
-
return absName;
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
/**
|
|
1562
|
-
* Returns a newly created default environment (which keeps track of indentation, required USING
|
|
1563
|
-
* declarations and name prefixes.
|
|
1564
|
-
*
|
|
1565
|
-
* @return {CdlRenderEnvironment}
|
|
1566
|
-
*/
|
|
1567
|
-
function createEnv() {
|
|
1568
|
-
return {
|
|
1569
|
-
// Current indentation string
|
|
1570
|
-
indent: '',
|
|
1571
|
-
// Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
|
|
1572
|
-
topLevelAliases: Object.create(null),
|
|
1573
|
-
// Current name prefix (including trailing dot if not empty)
|
|
1574
|
-
namePrefix: '',
|
|
1575
|
-
artifactName: null,
|
|
1576
|
-
elementName: null,
|
|
1577
|
-
};
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
/**
|
|
1581
|
-
* Returns a copy of 'env' with increased indentation (and resetted name prefix)
|
|
1582
|
-
*
|
|
1583
|
-
* @param {CdlRenderEnvironment} env
|
|
1584
|
-
* @returns {CdlRenderEnvironment}
|
|
1585
|
-
*/
|
|
1586
|
-
function increaseIndent(env) {
|
|
1587
|
-
return Object.assign({}, env, { namePrefix: '', indent: `${env.indent} ` });
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
/**
|
|
1591
|
-
* Return a path string 'path' with appropriate "-quotes.
|
|
1592
|
-
*
|
|
1593
|
-
* @param {string} path
|
|
1594
|
-
* @returns {string}
|
|
1595
|
-
*/
|
|
1596
|
-
function quotePathString(path) {
|
|
1597
|
-
// "foo"."bar"."wiz"."blub"
|
|
1598
|
-
return path.split('.').map(quoteIdIfRequired).join('.');
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
/**
|
|
1602
|
-
* Return an id 'id' with appropriate "-quotes
|
|
1603
|
-
*
|
|
1604
|
-
* @param {string} id
|
|
1605
|
-
* @return {string}
|
|
1606
|
-
*/
|
|
1607
|
-
function quoteIdIfRequired(id) {
|
|
1608
|
-
// Quote if required for CDL
|
|
1609
|
-
if (requiresQuotingForCdl(id))
|
|
1610
|
-
return quote(id);
|
|
1611
|
-
|
|
1612
|
-
return id;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
/**
|
|
1616
|
-
* Quotes the identifier using CDL-style ![]-quotes.
|
|
1617
|
-
*
|
|
1618
|
-
* @param id
|
|
1619
|
-
* @returns {string}
|
|
1620
|
-
*/
|
|
1621
|
-
function quote(id) {
|
|
1622
|
-
return `![${id.replace(/]/g, ']]')}]`;
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
/**
|
|
1626
|
-
* Returns true if 'id' requires quotes for CDL, i.e. if 'id'
|
|
1627
|
-
* 1. starts with a digit
|
|
1628
|
-
* 2. contains chars different than:
|
|
1629
|
-
* - uppercase letters
|
|
1630
|
-
* - lowercase letters
|
|
1631
|
-
* - digits
|
|
1632
|
-
* - underscore
|
|
1633
|
-
* 3. is a CDL keyword or a CDL function without parentheses (CURRENT_*, SYSUUID, ...)
|
|
1634
|
-
*
|
|
1635
|
-
* @param {string} id
|
|
1636
|
-
* @return {boolean}
|
|
1637
|
-
*/
|
|
1638
|
-
function requiresQuotingForCdl(id) {
|
|
1639
|
-
return /^\d/.test(id) ||
|
|
1640
|
-
/\W/g.test(id.replace(/\./g, '')) ||
|
|
1641
|
-
keywords.cdl.includes(id.toUpperCase()) ||
|
|
1642
|
-
keywords.cdl_functions.includes(id.toUpperCase());
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
/**
|
|
1646
|
-
* Quote or uppercase an identifier 'id', depending on naming strategy
|
|
1647
|
-
*
|
|
1648
|
-
* @todo Remove: Now part of toHdbcds.js
|
|
1649
|
-
* @param {string} id
|
|
1650
|
-
* @return {string}
|
|
1651
|
-
*/
|
|
1652
|
-
function quoteOrUppercaseId(id) {
|
|
1653
|
-
return quoteIdIfRequired(id);
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
1626
|
/**
|
|
1657
1627
|
* Render the name of an artifact, using the current name prefix from 'env'
|
|
1658
1628
|
* and the real name of the artifact. In case of plain names, this
|
|
@@ -1667,7 +1637,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1667
1637
|
* - Replace all dots in this "real name" with underscores
|
|
1668
1638
|
* - Join with the env prefix
|
|
1669
1639
|
*
|
|
1670
|
-
*
|
|
1671
1640
|
* @param {string} artifactName Artifact name to render
|
|
1672
1641
|
* @return {string} Artifact name ready for rendering
|
|
1673
1642
|
*/
|
|
@@ -1677,17 +1646,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1677
1646
|
return prefix ? `${quoteIdIfRequired(prefix)}.${realname.split('.').map(quoteIdIfRequired).join('.')}` : realname.split('.').map(quoteIdIfRequired).join('.');
|
|
1678
1647
|
}
|
|
1679
1648
|
|
|
1680
|
-
/**
|
|
1681
|
-
* Get the name that the artifact definition has been rendered as.
|
|
1682
|
-
* Without quoting/escaping stuff.
|
|
1683
|
-
*
|
|
1684
|
-
* @param {String} artifactName Artifact name to use
|
|
1685
|
-
* @returns {String}
|
|
1686
|
-
*/
|
|
1687
|
-
function getResultingName(artifactName) {
|
|
1688
|
-
return renderArtifactName(artifactName);
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
1649
|
/**
|
|
1692
1650
|
* Get the part that is really the name of this artifact and not just prefix caused by a context/service
|
|
1693
1651
|
*
|
|
@@ -1720,6 +1678,213 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1720
1678
|
// we seem to have a normal case - just return the last part
|
|
1721
1679
|
return getLastPartOf(artifactName);
|
|
1722
1680
|
}
|
|
1681
|
+
|
|
1682
|
+
/**
|
|
1683
|
+
* Render an expression.
|
|
1684
|
+
*
|
|
1685
|
+
* @param {any} expr
|
|
1686
|
+
* @param {CdlRenderEnvironment} env
|
|
1687
|
+
* @param {boolean} [inline]
|
|
1688
|
+
* @param {boolean} [nestedExpr]
|
|
1689
|
+
* @param {boolean} [alwaysRenderCast]
|
|
1690
|
+
* @returns {string}
|
|
1691
|
+
*/
|
|
1692
|
+
function renderExpr(expr, env, inline, nestedExpr, alwaysRenderCast) {
|
|
1693
|
+
if (!_renderExpr) {
|
|
1694
|
+
_renderExpr = getExpressionRenderer({
|
|
1695
|
+
finalize: x => x,
|
|
1696
|
+
explicitTypeCast: (x, env) => {
|
|
1697
|
+
const typeRef = renderTypeReference(x.cast, env, true);
|
|
1698
|
+
const arg = { ...x, cast: null }; // "arg" without cast to avoid recursion.
|
|
1699
|
+
return `cast(${renderArgument(arg, env)} as ${typeRef})`;
|
|
1700
|
+
},
|
|
1701
|
+
val(x, env) {
|
|
1702
|
+
// Literal value, possibly with explicit 'literal' property
|
|
1703
|
+
switch (x.literal || typeof x.val) {
|
|
1704
|
+
case 'number':
|
|
1705
|
+
case 'boolean':
|
|
1706
|
+
case 'null':
|
|
1707
|
+
return x.val;
|
|
1708
|
+
case 'x':
|
|
1709
|
+
case 'date':
|
|
1710
|
+
case 'time':
|
|
1711
|
+
case 'timestamp':
|
|
1712
|
+
return `${x.literal}'${x.val}'`;
|
|
1713
|
+
case 'string':
|
|
1714
|
+
return renderString(x.val, env);
|
|
1715
|
+
case 'object':
|
|
1716
|
+
if (x.val === null)
|
|
1717
|
+
return 'null';
|
|
1718
|
+
// otherwise fall through to
|
|
1719
|
+
default:
|
|
1720
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1721
|
+
}
|
|
1722
|
+
},
|
|
1723
|
+
aliasOnly(x, _env) {
|
|
1724
|
+
return x.as;
|
|
1725
|
+
},
|
|
1726
|
+
enum: x => `#${x['#']}`,
|
|
1727
|
+
ref(x, env) {
|
|
1728
|
+
const { inline } = this;
|
|
1729
|
+
return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, inline, env)).join('.')}`;
|
|
1730
|
+
},
|
|
1731
|
+
windowFunction: (x, env) => {
|
|
1732
|
+
const funcDef = renderFunc(x.func, x, null, a => renderArgs(a, '=>', env));
|
|
1733
|
+
const windowFunctionOperator = x.xpr.shift(); // OVER ...
|
|
1734
|
+
return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
|
|
1735
|
+
},
|
|
1736
|
+
func: (x, env) => {
|
|
1737
|
+
// FIXME: Why care about HANA identifier?
|
|
1738
|
+
// test for non-regular HANA identifier that needs to be quoted
|
|
1739
|
+
// identifier {letter}({letter_or_digit}|[#$])*
|
|
1740
|
+
// letter [A-Za-z_]
|
|
1741
|
+
// letter_or_digit [A-Za-z_0-9]
|
|
1742
|
+
|
|
1743
|
+
const regex = /^[a-zA-Z][\w#$]*$/g;
|
|
1744
|
+
const funcName = regex.test(x.func) ? x.func : quoteIdIfRequired(x.func);
|
|
1745
|
+
return renderFunc(funcName, x, 'cap', a => renderArgs(a, '=>', env));
|
|
1746
|
+
},
|
|
1747
|
+
xpr(x, env) {
|
|
1748
|
+
if (this.nestedExpr && !x.cast || x.xpr.some(s => s === 'exists'))
|
|
1749
|
+
return `(${renderExpr(x.xpr, env, this.inline, true)})`;
|
|
1750
|
+
|
|
1751
|
+
return renderExpr(x.xpr, env, this.inline, true);
|
|
1752
|
+
},
|
|
1753
|
+
// Sub-queries in expressions need to be in parentheses, otherwise
|
|
1754
|
+
// left-associativity of UNIONS may result in different results.
|
|
1755
|
+
// For example: `select from E where id in (select from E union select from E);`:
|
|
1756
|
+
// Without parentheses, it would be different query.
|
|
1757
|
+
SET: (x, env) => `(${renderQuery(x, false, 'view', increaseIndent(env))})`,
|
|
1758
|
+
SELECT: (x, env) => `(${renderQuery(x, false, 'view', increaseIndent(env))})`,
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
return _renderExpr(expr, env, inline, nestedExpr, alwaysRenderCast);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
/**
|
|
1766
|
+
* Returns a newly created default environment (which keeps track of indentation, required USING
|
|
1767
|
+
* declarations and name prefixes.
|
|
1768
|
+
*
|
|
1769
|
+
* @return {CdlRenderEnvironment}
|
|
1770
|
+
*/
|
|
1771
|
+
function createEnv() {
|
|
1772
|
+
return {
|
|
1773
|
+
// Current indentation string
|
|
1774
|
+
indent: '',
|
|
1775
|
+
// Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
|
|
1776
|
+
topLevelAliases: Object.create(null),
|
|
1777
|
+
// Current name prefix (including trailing dot if not empty)
|
|
1778
|
+
namePrefix: '',
|
|
1779
|
+
artifactName: '',
|
|
1780
|
+
elementName: '',
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* Returns a copy of 'env' with increased indentation (and reset name prefix)
|
|
1786
|
+
*
|
|
1787
|
+
* @param {CdlRenderEnvironment} env
|
|
1788
|
+
* @returns {CdlRenderEnvironment}
|
|
1789
|
+
*/
|
|
1790
|
+
function increaseIndent(env) {
|
|
1791
|
+
return Object.assign({}, env, { namePrefix: '', indent: `${env.indent} ` });
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Return a path string 'path' with appropriate "-quotes.
|
|
1796
|
+
*
|
|
1797
|
+
* @param {string} path
|
|
1798
|
+
* @returns {string}
|
|
1799
|
+
*/
|
|
1800
|
+
function quotePathString(path) {
|
|
1801
|
+
// "foo"."bar"."wiz"."blub"
|
|
1802
|
+
return path.split('.').map(quoteIdIfRequired).join('.');
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
/**
|
|
1806
|
+
* Return an id 'id' with appropriate "-quotes
|
|
1807
|
+
*
|
|
1808
|
+
* @param {string} id
|
|
1809
|
+
* @return {string}
|
|
1810
|
+
*/
|
|
1811
|
+
function quoteIdIfRequired(id) {
|
|
1812
|
+
// Quote if required for CDL
|
|
1813
|
+
if (requiresQuotingForCdl(id))
|
|
1814
|
+
return quote(id);
|
|
1815
|
+
|
|
1816
|
+
return id;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
/**
|
|
1820
|
+
* Quotes the identifier using CDL-style ![]-quotes.
|
|
1821
|
+
*
|
|
1822
|
+
* @param id
|
|
1823
|
+
* @returns {string}
|
|
1824
|
+
*/
|
|
1825
|
+
function quote(id) {
|
|
1826
|
+
return `![${id.replace(/]/g, ']]')}]`;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* Returns true if 'id' requires quotes for CDL, i.e. if 'id'
|
|
1831
|
+
* 1. starts with a digit
|
|
1832
|
+
* 2. contains chars different than:
|
|
1833
|
+
* - uppercase letters
|
|
1834
|
+
* - lowercase letters
|
|
1835
|
+
* - digits
|
|
1836
|
+
* - underscore
|
|
1837
|
+
* 3. is a CDL keyword or a CDL function without parentheses (CURRENT_*, SYSUUID, ...)
|
|
1838
|
+
*
|
|
1839
|
+
* @param {string} id
|
|
1840
|
+
* @return {boolean}
|
|
1841
|
+
*/
|
|
1842
|
+
function requiresQuotingForCdl(id) {
|
|
1843
|
+
return /^\d/.test(id) ||
|
|
1844
|
+
/\W/g.test(id.replace(/\./g, '')) ||
|
|
1845
|
+
keywords.cdl.includes(id.toUpperCase()) ||
|
|
1846
|
+
keywords.cdl_functions.includes(id.toUpperCase());
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
const functionExpressionOperatorsRequireParentheses = [
|
|
1850
|
+
// Antlr rule 'condition', 'conditionAnd'
|
|
1851
|
+
'and', 'or',
|
|
1852
|
+
|
|
1853
|
+
// Antlr rule 'conditionTerm'
|
|
1854
|
+
'=', '<>', '>', '>=', '<', '<=', '!=',
|
|
1855
|
+
// These are not forbidden, since they must be preceded by one of the comparators above.
|
|
1856
|
+
// 'any', 'some', 'all',
|
|
1857
|
+
|
|
1858
|
+
'is', 'in', 'not', 'null', 'exists',
|
|
1859
|
+
// Antlr rule 'predicate'
|
|
1860
|
+
'between', 'like', 'escape',
|
|
1861
|
+
];
|
|
1862
|
+
|
|
1863
|
+
/**
|
|
1864
|
+
* Returns true if the given xpr-array can be rendered without parentheses
|
|
1865
|
+
* in a `fct(<xpr>)` expression such as `cast(<xpr> as Type)`. We only need to
|
|
1866
|
+
* look at the first nesting level. Otherwise, `renderExpr()` will already add parentheses.
|
|
1867
|
+
*
|
|
1868
|
+
* The list was created by looking at the `expression` Antlr rule.
|
|
1869
|
+
*
|
|
1870
|
+
* This is more of a heuristic for "nicer" CDL output. For example the
|
|
1871
|
+
* following snippet is parsable without parentheses:
|
|
1872
|
+
* `cast( case when int > 1 then int else 0 end as Integer ),`
|
|
1873
|
+
* However, because it is a flat xpr-array, we see `>` and assume that it is not a simple expression.
|
|
1874
|
+
*
|
|
1875
|
+
* @param {any[]} xpr
|
|
1876
|
+
* @return {boolean}
|
|
1877
|
+
*/
|
|
1878
|
+
function isSimpleFunctionExpression(xpr) {
|
|
1879
|
+
return !xpr || xpr.every(val => typeof val !== 'string' || !functionExpressionOperatorsRequireParentheses.includes(val.toLowerCase()));
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
/**
|
|
1883
|
+
* @param {string[]} includes
|
|
1884
|
+
* @return {string}
|
|
1885
|
+
*/
|
|
1886
|
+
function renderIncludes(includes) {
|
|
1887
|
+
return ` : ${includes.map(name => quoteIdIfRequired(name)).join(', ')}`;
|
|
1723
1888
|
}
|
|
1724
1889
|
|
|
1725
1890
|
/**
|
|
@@ -1801,14 +1966,14 @@ function isSimpleString(str) {
|
|
|
1801
1966
|
*
|
|
1802
1967
|
* @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
|
|
1803
1968
|
* @property {CSN.Path} [path] CSN path to the current artifact
|
|
1804
|
-
* @property {string} artifactName Name of the artifact - set in renderArtifact
|
|
1805
|
-
* @property {string} elementName Name of the element being rendered - set in renderElement
|
|
1969
|
+
* @property {string} [artifactName] Name of the artifact - set in renderArtifact
|
|
1970
|
+
* @property {string} [elementName] Name of the element being rendered - set in renderElement
|
|
1806
1971
|
* @property {{[name: string]: {
|
|
1807
1972
|
quotedName: string,
|
|
1808
1973
|
quotedAlias: string
|
|
1809
1974
|
}}} topLevelAliases Dictionary of aliases for used artifact names
|
|
1810
1975
|
*
|
|
1811
|
-
* @property {string} namePrefix Current name prefix (including trailing dot if not empty)
|
|
1976
|
+
* @property {string} [namePrefix] Current name prefix (including trailing dot if not empty)
|
|
1812
1977
|
* @property {boolean} [skipKeys]
|
|
1813
1978
|
* @property {CSN.Artifact} [_artifact]
|
|
1814
1979
|
*/
|