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