@sap/cds-compiler 2.12.0 → 2.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +221 -15
- package/bin/cdsc.js +125 -50
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +47 -84
- package/lib/api/options.js +5 -6
- package/lib/api/validate.js +6 -11
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +114 -18
- package/lib/base/messages.js +101 -90
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +177 -123
- package/lib/checks/annotationsOData.js +12 -33
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +6 -11
- package/lib/compiler/assert-consistency.js +6 -3
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +19 -6
- package/lib/compiler/checks.js +23 -60
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1151 -0
- package/lib/compiler/extend.js +1000 -0
- package/lib/compiler/finalize-parse-cdl.js +237 -0
- package/lib/compiler/index.js +107 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1227 -0
- package/lib/compiler/propagator.js +114 -46
- package/lib/compiler/resolve.js +1521 -0
- package/lib/compiler/shared.js +126 -65
- package/lib/compiler/tweak-assocs.js +535 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -24
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +219 -100
- package/lib/edm/edm.js +302 -230
- package/lib/edm/edmPreprocessor.js +554 -419
- package/lib/edm/edmUtils.js +138 -44
- package/lib/gen/Dictionary.json +100 -19
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5765 -4480
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +15 -3
- package/lib/json/to-csn.js +126 -68
- package/lib/language/docCommentParser.js +4 -4
- package/lib/language/genericAntlrParser.js +123 -5
- package/lib/language/language.g4 +355 -156
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +486 -59
- package/lib/main.js +41 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +252 -156
- package/lib/model/csnUtils.js +384 -297
- package/lib/model/enrichCsn.js +71 -29
- package/lib/model/revealInternalProperties.js +29 -8
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +23 -18
- package/lib/optionProcessor.js +63 -26
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +897 -947
- package/lib/render/toHdbcds.js +205 -257
- package/lib/render/toSql.js +264 -225
- package/lib/render/utils/common.js +136 -25
- package/lib/render/utils/sql.js +4 -3
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/db/.eslintrc.json +3 -1
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +104 -306
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +58 -53
- package/lib/transform/db/expansion.js +60 -33
- package/lib/transform/db/flattening.js +582 -104
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +66 -13
- package/lib/transform/db/views.js +11 -7
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +109 -208
- package/lib/transform/forOdataNew.js +59 -212
- package/lib/transform/localized.js +46 -26
- package/lib/transform/odata/toFinalBaseType.js +85 -11
- package/lib/transform/odata/typesExposure.js +147 -199
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +44 -33
- package/lib/transform/translateAssocsToJoins.js +3 -20
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +172 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- 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 -290
- 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/transform/universalCsnEnricher.js +0 -237
package/lib/render/toCdl.js
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
getLastPartOf,
|
|
5
|
-
getLastPartOfRef,
|
|
6
|
-
} = require('../model/csnUtils');
|
|
7
|
-
const {
|
|
8
|
-
isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
|
|
9
|
-
} = require('../model/csnUtils');
|
|
10
3
|
const keywords = require('../base/keywords');
|
|
11
|
-
const {
|
|
4
|
+
const { getLastPartOf } = require('../model/csnUtils');
|
|
5
|
+
const { isBuiltinType, generatedByCompilerVersion, getNormalizedQuery } = require('../model/csnUtils');
|
|
6
|
+
const { renderFunc, findElement, getExpressionRenderer } = require('./utils/common');
|
|
7
|
+
const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
|
|
12
8
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
13
9
|
const { timetrace } = require('../utils/timetrace');
|
|
14
10
|
const { csnRefs } = require('../model/csnRefs');
|
|
15
11
|
const { forEachDefinition } = require('../model/csnUtils');
|
|
16
|
-
const enrichUniversalCsn = require('../transform/universalCsnEnricher');
|
|
12
|
+
const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
|
|
17
13
|
const { isBetaEnabled } = require('../base/model');
|
|
14
|
+
const { ModelError } = require('../base/error');
|
|
15
|
+
const { typeParameters } = require('../compiler/builtins');
|
|
16
|
+
const { forEach } = require('../utils/objectUtils');
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
|
-
* Render the CSN model 'model' to CDS source text.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* }
|
|
26
|
-
* FIXME: This comment no longer tells the whole truth
|
|
19
|
+
* Render the CSN model 'model' to CDS source text.
|
|
20
|
+
* Returned object has the following properties:
|
|
21
|
+
* - `model`: CSN model rendered as CDL (string).
|
|
22
|
+
* - [csn.namespace]: Namespace statement + `using from './model.cds'.
|
|
23
|
+
* - `unappliedExtensions`: Annotations / Extensions from the `csn.extensions` array.
|
|
27
24
|
*
|
|
28
25
|
* @param {CSN.Model} csn
|
|
29
26
|
* @param {CSN.Options} [options]
|
|
@@ -31,9 +28,7 @@ const { isBetaEnabled } = require('../base/model');
|
|
|
31
28
|
function toCdsSourceCsn(csn, options) {
|
|
32
29
|
timetrace.start('CDL rendering');
|
|
33
30
|
const { artifactRef } = csnRefs(csn);
|
|
34
|
-
|
|
35
|
-
// Skip compactModel if already using CSN
|
|
36
|
-
// const csn = cloneCsn(model, options);
|
|
31
|
+
let _renderExpr = null;
|
|
37
32
|
|
|
38
33
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
|
|
39
34
|
enrichUniversalCsn(csn, options);
|
|
@@ -41,177 +36,285 @@ function toCdsSourceCsn(csn, options) {
|
|
|
41
36
|
checkCSNVersion(csn, options);
|
|
42
37
|
|
|
43
38
|
const cdlResult = Object.create(null);
|
|
39
|
+
cdlResult.model = options.testMode ? '' : `// ${generatedByCompilerVersion()} \n`;
|
|
40
|
+
|
|
41
|
+
const subelementAnnotates = [];
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
cdlResult.model += renderDefinitions();
|
|
44
|
+
// sub-element annotations that can't be written directly.
|
|
45
|
+
cdlResult.model += renderExtensions(subelementAnnotates, createEnv());
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
if (csn.vocabularies)
|
|
48
|
+
cdlResult.model += renderVocabularies(csn.vocabularies);
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
if (csn.namespace) {
|
|
51
|
+
cdlResult[csn.namespace] = `namespace ${renderArtifactName(csn.namespace)};\n`;
|
|
52
|
+
cdlResult[csn.namespace] += 'using from \'./model.cds\';';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// If there are extensions, such as 'extend' and 'annotate' statements, render them separately.
|
|
56
|
+
// Used for e.g. parseCdl-style CSN or Universal CSN.
|
|
57
|
+
if (csn.extensions)
|
|
58
|
+
cdlResult.unappliedExtensions = renderExtensions(csn.extensions, createEnv());
|
|
50
59
|
|
|
51
|
-
|
|
60
|
+
timetrace.stop();
|
|
61
|
+
return cdlResult;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Render entries from the `csn.definitions` dictionary.
|
|
65
|
+
* Returns an empty string if nothing is rendered.
|
|
66
|
+
*
|
|
67
|
+
* @return {string}
|
|
68
|
+
*/
|
|
69
|
+
function renderDefinitions() {
|
|
70
|
+
let result = '';
|
|
52
71
|
const env = createEnv();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
forEachDefinition(csn, (artifact, artifactName) => {
|
|
73
|
+
const sourceStr = renderArtifact(artifactName, artifact, env);
|
|
74
|
+
if (sourceStr !== '')
|
|
75
|
+
result += `${sourceStr}\n`;
|
|
76
|
+
});
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Render annotation definitions, i.e. entries from csn.vocabularies.
|
|
82
|
+
* Returns an empty string if there isn't anything to render.
|
|
83
|
+
*
|
|
84
|
+
* @param {object} vocabularies
|
|
85
|
+
* @return {string}
|
|
86
|
+
*/
|
|
87
|
+
function renderVocabularies(vocabularies) {
|
|
88
|
+
let result = '';
|
|
89
|
+
forEach(vocabularies, renderVocabulariesEntry);
|
|
90
|
+
return result;
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
sourceStr += ` ${elementName} {\n`;
|
|
66
|
-
const env = increaseIndent(increaseIndent(createEnv()));
|
|
67
|
-
const subelements = renderSubelementAnnotates(element, env);
|
|
68
|
-
if (subelements !== '') {
|
|
69
|
-
sourceStr += `${subelements}\n`;
|
|
70
|
-
if (elementName) // action returns do not have element name - we need less {} there
|
|
71
|
-
sourceStr += ' }\n';
|
|
72
|
-
sourceStr += '}\n';
|
|
73
|
-
cdlResult[main] += `${sourceStr}\n`;
|
|
92
|
+
function renderVocabulariesEntry(name, anno) {
|
|
93
|
+
if (!anno._ignore) {
|
|
94
|
+
// This environment is passed down the call hierarchy, for dealing with
|
|
95
|
+
// indentation and name resolution issues
|
|
96
|
+
const env = createEnv();
|
|
97
|
+
const sourceStr = renderTypeOrAnnotation(name, anno, env, 'annotation');
|
|
98
|
+
result += `${sourceStr}\n`;
|
|
74
99
|
}
|
|
75
100
|
}
|
|
76
101
|
}
|
|
77
102
|
|
|
78
103
|
/**
|
|
79
|
-
* Render
|
|
104
|
+
* Render 'extend' and 'annotate' statements from the `extensions` array.
|
|
105
|
+
* Could be annotate-statements for sub-elements annotations or from parseCdl's
|
|
106
|
+
* extensions array or just unapplied extensions.
|
|
80
107
|
*
|
|
81
|
-
* @param {CSN.
|
|
82
|
-
* @param {CdlRenderEnvironment} env
|
|
83
|
-
* @
|
|
108
|
+
* @param {CSN.Extension[]} extensions
|
|
109
|
+
* @param {CdlRenderEnvironment} env
|
|
110
|
+
* @return {string}
|
|
84
111
|
*/
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
for (const [ name, subelement ] of Object.entries(element.elements)) {
|
|
88
|
-
const subresult = [];
|
|
89
|
-
const annos = renderAnnotationAssignments(subelement, env);
|
|
90
|
-
if (annos !== '')
|
|
91
|
-
subresult.push(annos.slice(0, -1));
|
|
92
|
-
|
|
93
|
-
const quotedElementName = quoteOrUppercaseId(name);
|
|
94
|
-
if (subelement.elements) {
|
|
95
|
-
subresult.push(`${env.indent}${quotedElementName} {`);
|
|
96
|
-
subresult.push(renderSubelementAnnotates(subelement, increaseIndent(env)));
|
|
97
|
-
subresult.push(`${env.indent}};`);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
subresult.push(`${env.indent}${quotedElementName};`);
|
|
101
|
-
}
|
|
102
|
-
// Only add result if there really was "something"
|
|
103
|
-
if (annos || subelement.elements)
|
|
104
|
-
result.push(...subresult);
|
|
105
|
-
}
|
|
106
|
-
return result.join('\n');
|
|
112
|
+
function renderExtensions(extensions, env) {
|
|
113
|
+
return extensions.map(ext => renderExtension(ext, env)).join('\n');
|
|
107
114
|
}
|
|
108
115
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Render an 'extend' and 'annotate' statement.
|
|
118
|
+
*
|
|
119
|
+
* @param {CSN.Extension} ext
|
|
120
|
+
* @param {CdlRenderEnvironment} env
|
|
121
|
+
* @return {string}
|
|
122
|
+
*/
|
|
123
|
+
function renderExtension(ext, env) {
|
|
124
|
+
if (ext.extend)
|
|
125
|
+
return renderExtendStatement(ext.extend, ext, env);
|
|
126
|
+
return renderAnnotateStatement(ext, env);
|
|
127
|
+
}
|
|
118
128
|
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Render an 'extend' statement.
|
|
131
|
+
* `extName` is the extension's artifact's name, most likely `ext.extend`.
|
|
132
|
+
* This function is recursive, which is why you need to pass it explicitly.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} extName
|
|
135
|
+
* @param {object} ext
|
|
136
|
+
* @param {CdlRenderEnvironment} env
|
|
137
|
+
* @return {string}
|
|
138
|
+
*/
|
|
139
|
+
function renderExtendStatement(extName, ext, env) {
|
|
140
|
+
extName = renderArtifactName(extName);
|
|
141
|
+
// Element extensions have `kind` set.
|
|
142
|
+
const isElementExtend = (ext.kind === 'extend');
|
|
143
|
+
let result = renderAnnotationAssignmentsAndDocComment(ext, env);
|
|
144
|
+
|
|
145
|
+
if (ext.includes && ext.includes.length > 0) {
|
|
146
|
+
// Includes can't be combined with anything in braces {}. Multiple includes
|
|
147
|
+
// are possible through CSN, but in CDL, only one include at once is possible.
|
|
148
|
+
const affix = isElementExtend ? 'element ' : '';
|
|
149
|
+
for (const id of ext.includes)
|
|
150
|
+
result += `${env.indent}extend ${affix}${extName} with ${quoteIdIfRequired(id)};\n`;
|
|
151
|
+
return result;
|
|
121
152
|
}
|
|
122
|
-
}
|
|
123
153
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
154
|
+
// We have the "old-style" prefix syntax and the "new-style" postfix "with <type>" syntax.
|
|
155
|
+
// The former one can not only extend (sub-)elements but also actions in the same statement whereas
|
|
156
|
+
// the latter cannot.
|
|
157
|
+
// If there are actions, check if there are also elements/columns, and if so, use the prefix notation.
|
|
158
|
+
const usePrefixNotation = isElementExtend || ext.actions && (ext.columns || ext.elements);
|
|
159
|
+
if (usePrefixNotation)
|
|
160
|
+
result += `${env.indent}extend ${getExtendPrefixVariant(ext)} ${extName} with {\n`;
|
|
161
|
+
else
|
|
162
|
+
result += `${env.indent}extend ${extName} with ${getExtendPostfixVariant(ext)} {\n`;
|
|
163
|
+
|
|
164
|
+
if (ext.columns) {
|
|
165
|
+
result += renderViewColumns(ext.columns, increaseIndent(env));
|
|
166
|
+
}
|
|
167
|
+
else if (ext.elements) {
|
|
168
|
+
result += renderExtendStatementElements(ext, env);
|
|
169
|
+
}
|
|
170
|
+
else if (ext.enum) {
|
|
171
|
+
const childEnv = increaseIndent(env);
|
|
172
|
+
forEach(ext.enum, (name, value) => {
|
|
173
|
+
result += renderEnumElement(name, value, childEnv);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Not part of if/else cascade, because it may be in postfix notation.
|
|
178
|
+
if (ext.actions) {
|
|
179
|
+
// TODO: Merge with renderActionsAndFunctions() -> requires removal of static `with actions`
|
|
180
|
+
const childEnv = increaseIndent(env);
|
|
181
|
+
let actions = '';
|
|
182
|
+
forEach(ext.actions, (actionName, action) => {
|
|
183
|
+
actions += renderActionOrFunction(actionName, action, childEnv);
|
|
184
|
+
});
|
|
185
|
+
if (!usePrefixNotation)
|
|
186
|
+
result += actions;
|
|
187
|
+
else if (actions !== '')
|
|
188
|
+
result += `${env.indent}} actions {\n${actions}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
result += `${env.indent}};\n`;
|
|
192
|
+
return result;
|
|
127
193
|
}
|
|
128
194
|
|
|
195
|
+
/**
|
|
196
|
+
* What <extend> prefix type to use. Used to render `extend <type> <ref>` statements.
|
|
197
|
+
*
|
|
198
|
+
* @param {object} ext
|
|
199
|
+
* @return {string}
|
|
200
|
+
*/
|
|
201
|
+
function getExtendPrefixVariant(ext) {
|
|
202
|
+
if (ext.kind === 'extend')
|
|
203
|
+
return 'element'; // element extensions inside an `extend`
|
|
204
|
+
if (ext.columns)
|
|
205
|
+
return 'projection';
|
|
206
|
+
if (ext.elements)
|
|
207
|
+
return 'entity';
|
|
208
|
+
return '';
|
|
209
|
+
}
|
|
129
210
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
211
|
+
/**
|
|
212
|
+
* What <extend> postfix type to use. Used to render `extend <ref> with <type>` statements.
|
|
213
|
+
*
|
|
214
|
+
* @param {CSN.Extension} ext
|
|
215
|
+
* @return {string}
|
|
216
|
+
*/
|
|
217
|
+
function getExtendPostfixVariant(ext) {
|
|
218
|
+
if (ext.columns)
|
|
219
|
+
return 'columns';
|
|
220
|
+
if (ext.actions)
|
|
221
|
+
return 'actions';
|
|
222
|
+
if (ext.elements)
|
|
223
|
+
return 'elements';
|
|
224
|
+
if (ext.enum)
|
|
225
|
+
return 'enum';
|
|
226
|
+
return '';
|
|
136
227
|
}
|
|
137
228
|
|
|
138
|
-
|
|
139
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Render the elements inside an `extend` statement. They may themselves be `extend` statements.
|
|
231
|
+
*
|
|
232
|
+
* @param {CSN.Extension} ext
|
|
233
|
+
* @param {CdlRenderEnvironment} env
|
|
234
|
+
* @return {string}
|
|
235
|
+
*/
|
|
236
|
+
function renderExtendStatementElements(ext, env) {
|
|
237
|
+
let result = '';
|
|
238
|
+
forEach(ext.elements || {}, (elemName, element) => {
|
|
239
|
+
if (element.kind === 'extend')
|
|
240
|
+
result += renderExtendStatement(elemName, element, increaseIndent(env));
|
|
241
|
+
else
|
|
242
|
+
// As soon as we are inside an element, nested `extend` are not possible,
|
|
243
|
+
// since we can't extend an existing element of a new one.
|
|
244
|
+
result += renderElement(elemName, element, increaseIndent(env), true);
|
|
245
|
+
});
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
140
248
|
|
|
141
249
|
/**
|
|
142
|
-
* Render
|
|
250
|
+
* Render an 'annotate' statement.
|
|
143
251
|
*
|
|
144
|
-
* @param {CSN.Extension
|
|
252
|
+
* @param {CSN.Extension} ext
|
|
145
253
|
* @param {CdlRenderEnvironment} env
|
|
146
254
|
* @return {string}
|
|
147
255
|
*/
|
|
148
|
-
function
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
result += `annotate ${ext.annotate}`;
|
|
153
|
-
// Element extensions and annotations (possibly nested)
|
|
154
|
-
if (ext.elements)
|
|
155
|
-
result += renderElementExtensions(ext.elements, env);
|
|
156
|
-
|
|
157
|
-
// Returns annotations
|
|
158
|
-
if (ext.returns) {
|
|
159
|
-
const childEnv = increaseIndent(env);
|
|
160
|
-
result += ` with returns${renderElementExtensions(ext.returns.elements, childEnv)}`;
|
|
161
|
-
}
|
|
256
|
+
function renderAnnotateStatement(ext, env) {
|
|
257
|
+
// Top-level annotations of the artifact
|
|
258
|
+
let result = renderAnnotationAssignmentsAndDocComment(ext, env);
|
|
259
|
+
result += `${env.indent}annotate ${renderArtifactName(ext.annotate)}`;
|
|
162
260
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
result += ' actions {\n';
|
|
166
|
-
const childEnv = increaseIndent(env);
|
|
167
|
-
for (const name in ext.actions) {
|
|
168
|
-
const action = ext.actions[name];
|
|
169
|
-
result += renderAnnotationAssignments(action, childEnv) + childEnv.indent + quoteIdIfRequired(name);
|
|
170
|
-
// Action parameter annotations
|
|
171
|
-
if (action.params) {
|
|
172
|
-
result += '(\n';
|
|
173
|
-
const grandChildEnv = increaseIndent(childEnv);
|
|
174
|
-
const paramAnnotations = [];
|
|
175
|
-
for (const paramName in action.params)
|
|
176
|
-
paramAnnotations.push(renderAnnotationAssignments(action.params[paramName], grandChildEnv) + grandChildEnv.indent + quoteIdIfRequired(paramName));
|
|
177
|
-
|
|
178
|
-
result += `${paramAnnotations.join(',\n')}\n${childEnv.indent})`;
|
|
179
|
-
}
|
|
180
|
-
// Annotations on action returns
|
|
181
|
-
if (action.returns && action.returns.elements) {
|
|
182
|
-
const grandChildEnv = increaseIndent(childEnv);
|
|
183
|
-
result += ` returns${renderElementExtensions(action.returns.elements, grandChildEnv)}`;
|
|
184
|
-
}
|
|
261
|
+
if (ext.params)
|
|
262
|
+
result += renderAnnotateParamsInParentheses(ext.params, env);
|
|
185
263
|
|
|
264
|
+
// Element extensions and annotations (possibly nested)
|
|
265
|
+
if (ext.elements)
|
|
266
|
+
result += renderAnnotateStatementElements(ext.elements, env);
|
|
186
267
|
|
|
187
|
-
|
|
268
|
+
// Returns annotations
|
|
269
|
+
if (ext.returns) {
|
|
270
|
+
const childEnv = increaseIndent(env);
|
|
271
|
+
result += ` returns${renderAnnotateStatementElements(ext.returns.elements, childEnv)}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Action annotations
|
|
275
|
+
if (ext.actions) {
|
|
276
|
+
result += ' actions {\n';
|
|
277
|
+
const childEnv = increaseIndent(env);
|
|
278
|
+
for (const name in ext.actions) {
|
|
279
|
+
const action = ext.actions[name];
|
|
280
|
+
result += renderAnnotationAssignmentsAndDocComment(action, childEnv) + childEnv.indent + quoteIdIfRequired(name);
|
|
281
|
+
// Action parameter annotations
|
|
282
|
+
if (action.params)
|
|
283
|
+
result += renderAnnotateParamsInParentheses(action.params, childEnv);
|
|
284
|
+
|
|
285
|
+
// Annotations on action returns
|
|
286
|
+
if (action.returns && action.returns.elements) {
|
|
287
|
+
const grandChildEnv = increaseIndent(childEnv);
|
|
288
|
+
result += ` returns${renderAnnotateStatementElements(action.returns.elements, grandChildEnv)}`;
|
|
188
289
|
}
|
|
189
|
-
|
|
290
|
+
|
|
291
|
+
result += ';\n';
|
|
190
292
|
}
|
|
293
|
+
result += `${env.indent}}`;
|
|
294
|
+
}
|
|
191
295
|
|
|
192
296
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}).join('\n');
|
|
297
|
+
result += ';\n';
|
|
298
|
+
return result;
|
|
196
299
|
}
|
|
197
300
|
|
|
198
301
|
/**
|
|
199
|
-
* Render the elements-specific part of an '
|
|
302
|
+
* Render the elements-specific part of an 'annotate' statement for an element dictionary
|
|
200
303
|
* 'elements' (assuming that the surrounding parent has just been rendered, without trailing newline).
|
|
201
|
-
*
|
|
304
|
+
* Returns the resulting source string, ending without a trailing newline.
|
|
202
305
|
*
|
|
203
306
|
* @param {CSN.Elements} elements
|
|
204
307
|
* @param {CdlRenderEnvironment} env
|
|
205
308
|
* @return {string}
|
|
206
309
|
*/
|
|
207
|
-
function
|
|
310
|
+
function renderAnnotateStatementElements(elements, env) {
|
|
208
311
|
let result = ' {\n';
|
|
209
312
|
const childEnv = increaseIndent(env);
|
|
210
313
|
for (const name in elements) {
|
|
211
314
|
const elem = elements[name];
|
|
212
|
-
result +=
|
|
315
|
+
result += renderAnnotationAssignmentsAndDocComment(elem, childEnv) + childEnv.indent + quoteIdIfRequired(name);
|
|
213
316
|
if (elem.elements)
|
|
214
|
-
result +=
|
|
317
|
+
result += renderAnnotateStatementElements(elem.elements, childEnv);
|
|
215
318
|
|
|
216
319
|
result += ';\n';
|
|
217
320
|
}
|
|
@@ -219,6 +322,24 @@ function toCdsSourceCsn(csn, options) {
|
|
|
219
322
|
return result;
|
|
220
323
|
}
|
|
221
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Render a parameter list for `annotate` statements, in parentheses `()`.
|
|
327
|
+
*
|
|
328
|
+
* @param {object} params
|
|
329
|
+
* @param {CdlRenderEnvironment} env
|
|
330
|
+
* @return {string}
|
|
331
|
+
*/
|
|
332
|
+
function renderAnnotateParamsInParentheses(params, env) {
|
|
333
|
+
const childEnv = increaseIndent(env);
|
|
334
|
+
let result = '(\n';
|
|
335
|
+
const paramAnnotations = [];
|
|
336
|
+
forEach(params, (paramName, param) => {
|
|
337
|
+
paramAnnotations.push( renderAnnotationAssignmentsAndDocComment(param, childEnv) + childEnv.indent + quoteIdIfRequired(paramName) );
|
|
338
|
+
});
|
|
339
|
+
result += `${paramAnnotations.join(',\n')}\n${env.indent})`;
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
|
|
222
343
|
/**
|
|
223
344
|
* Render an artifact. Return the resulting source string.
|
|
224
345
|
*
|
|
@@ -233,7 +354,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
233
354
|
|
|
234
355
|
switch (art.kind) {
|
|
235
356
|
case 'entity':
|
|
236
|
-
case 'view':
|
|
237
357
|
if (art.query || art.projection)
|
|
238
358
|
return renderView(artifactName, art, env);
|
|
239
359
|
|
|
@@ -242,8 +362,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
242
362
|
case 'context':
|
|
243
363
|
case 'service':
|
|
244
364
|
return renderContext(artifactName, art, env);
|
|
245
|
-
case 'namespace':
|
|
246
|
-
return renderNamespace(artifactName, art, env);
|
|
247
365
|
case 'type':
|
|
248
366
|
case 'aspect':
|
|
249
367
|
case 'annotation':
|
|
@@ -252,9 +370,9 @@ function toCdsSourceCsn(csn, options) {
|
|
|
252
370
|
case 'function':
|
|
253
371
|
return renderActionOrFunction(artifactName, art, env);
|
|
254
372
|
case 'event':
|
|
255
|
-
return
|
|
373
|
+
return renderEvent(artifactName, art, env);
|
|
256
374
|
default:
|
|
257
|
-
throw new
|
|
375
|
+
throw new ModelError(`Unknown artifact kind: ${art.kind}`);
|
|
258
376
|
}
|
|
259
377
|
}
|
|
260
378
|
|
|
@@ -263,15 +381,13 @@ function toCdsSourceCsn(csn, options) {
|
|
|
263
381
|
* @param {CSN.Artifact} art
|
|
264
382
|
* @param {CdlRenderEnvironment} env
|
|
265
383
|
*/
|
|
266
|
-
function
|
|
267
|
-
let result =
|
|
384
|
+
function renderEvent(artifactName, art, env) {
|
|
385
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
268
386
|
const childEnv = increaseIndent(env);
|
|
269
387
|
const normalizedArtifactName = renderArtifactName(artifactName);
|
|
270
388
|
result += `${env.indent}event ${normalizedArtifactName}`;
|
|
271
|
-
if (art.includes)
|
|
272
|
-
|
|
273
|
-
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name)).join(', ')}`;
|
|
274
|
-
}
|
|
389
|
+
if (art.includes)
|
|
390
|
+
result += renderIncludes(art.includes);
|
|
275
391
|
if (art.query || art.projection) {
|
|
276
392
|
env._artifact = art;
|
|
277
393
|
result += ' : ';
|
|
@@ -294,80 +410,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
294
410
|
return result;
|
|
295
411
|
}
|
|
296
412
|
|
|
297
|
-
/**
|
|
298
|
-
* Return a dictionary with the direct sub-artifacts of the artifact with name 'artifactName' in the csn
|
|
299
|
-
*
|
|
300
|
-
* @param {string} artifactName
|
|
301
|
-
* @return {object}
|
|
302
|
-
*/
|
|
303
|
-
function getSubArtifacts(artifactName) {
|
|
304
|
-
const prefix = `${artifactName}.`;
|
|
305
|
-
const result = Object.create(null);
|
|
306
|
-
for (const name in csn.definitions) {
|
|
307
|
-
// We have a direct child if its name starts with prefix and contains no more dots
|
|
308
|
-
if (name.startsWith(prefix) && !name.substring(prefix.length).includes('.')) {
|
|
309
|
-
result[getLastPartOf(name)] = csn.definitions[name];
|
|
310
|
-
}
|
|
311
|
-
else if (name.startsWith(prefix)) {
|
|
312
|
-
const prefixPlusNextPart = name.substring(0, name.substring(prefix.length).indexOf('.') + prefix.length);
|
|
313
|
-
if (csn.definitions[prefixPlusNextPart]) {
|
|
314
|
-
const art = csn.definitions[prefixPlusNextPart];
|
|
315
|
-
if (![ 'service', 'context', 'namespace' ].includes(art.kind)) {
|
|
316
|
-
const nameWithoutPrefix = name.substring(prefix.length);
|
|
317
|
-
result[nameWithoutPrefix] = csn.definitions[name];
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
result[name.substring(prefix.length)] = csn.definitions[name];
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return result;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/* FIXME: Not yet required
|
|
329
|
-
// Returns the artifact or element that constitutes the final type of
|
|
330
|
-
// construct 'node', i.e. the object in which we would find type properties for
|
|
331
|
-
// 'node'. Note that this may well be 'node' itself.
|
|
332
|
-
function getFinalTypeOf(node) {
|
|
333
|
-
if (node && node.type) {
|
|
334
|
-
if (isBuiltinType(node.type)) {
|
|
335
|
-
return node;
|
|
336
|
-
}
|
|
337
|
-
return getFinalTypeOf(node.type);
|
|
338
|
-
}
|
|
339
|
-
return node;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Resolve path array 'ref' against artifact 'base' (or against 'csn.definitions'
|
|
343
|
-
// if no 'base' given).
|
|
344
|
-
// Return the resulting artifact or element (or 'undefined' if not found).
|
|
345
|
-
function resolveRef(ref, base) {
|
|
346
|
-
let result = base;
|
|
347
|
-
for (let i = 0; i < ref.length; i++) {
|
|
348
|
-
let pathStep = ref[i].id || ref[i];
|
|
349
|
-
// Only first path step may be looked up in 'definitions'
|
|
350
|
-
if (i === 0 && !base) {
|
|
351
|
-
result = csn.definitions[pathStep];
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
// Structured type
|
|
355
|
-
else if (result && result.elements) {
|
|
356
|
-
result = getFinalTypeOf(result.elements[pathStep]);
|
|
357
|
-
}
|
|
358
|
-
// Association
|
|
359
|
-
else if (result && result.target) {
|
|
360
|
-
result = resolveRef([pathStep], csn.definitions[result.target]);
|
|
361
|
-
}
|
|
362
|
-
// Not resolvable
|
|
363
|
-
else {
|
|
364
|
-
return undefined;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return result;
|
|
368
|
-
}
|
|
369
|
-
*/
|
|
370
|
-
|
|
371
413
|
/**
|
|
372
414
|
* Render a context or service. Return the resulting source string.
|
|
373
415
|
*
|
|
@@ -376,44 +418,12 @@ function toCdsSourceCsn(csn, options) {
|
|
|
376
418
|
* @param {CdlRenderEnvironment} env
|
|
377
419
|
*/
|
|
378
420
|
function renderContext(artifactName, art, env) {
|
|
379
|
-
let result =
|
|
421
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
380
422
|
result += `${env.indent + (art.abstract ? 'abstract ' : '') + art.kind} ${renderArtifactName(artifactName)}`;
|
|
381
|
-
if (art.includes)
|
|
382
|
-
|
|
383
|
-
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name)).join(', ')}`;
|
|
384
|
-
}
|
|
385
|
-
return `${result} {};\n`;
|
|
386
|
-
}
|
|
423
|
+
if (art.includes)
|
|
424
|
+
result += renderIncludes(art.includes);
|
|
387
425
|
|
|
388
|
-
|
|
389
|
-
let innerEnv = env;
|
|
390
|
-
if (name.indexOf('.') !== -1) {
|
|
391
|
-
const parts = name.split('.');
|
|
392
|
-
for (let i = 0; i < parts.length - 1; i++)
|
|
393
|
-
innerEnv = addNamePrefix(innerEnv, parts[i]);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return innerEnv;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Render a namespace. Return the resulting source string.
|
|
401
|
-
*
|
|
402
|
-
* @param {string} artifactName
|
|
403
|
-
* @param {CSN.Artifact} art
|
|
404
|
-
* @param {CdlRenderEnvironment} env
|
|
405
|
-
* @return {string}
|
|
406
|
-
*/
|
|
407
|
-
function renderNamespace(artifactName, art, env) {
|
|
408
|
-
// We currently do not render anything for a namespace, we just append its id to
|
|
409
|
-
// the environment's current name prefix and descend into its children
|
|
410
|
-
let result = renderDocComment(art, env);
|
|
411
|
-
const childEnv = addNamePrefix(env, getLastPartOf(artifactName));
|
|
412
|
-
const subArtifacts = getSubArtifacts(artifactName);
|
|
413
|
-
for (const name in subArtifacts)
|
|
414
|
-
result += renderArtifact(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
|
|
415
|
-
|
|
416
|
-
return result;
|
|
426
|
+
return `${result} {};\n`;
|
|
417
427
|
}
|
|
418
428
|
|
|
419
429
|
/**
|
|
@@ -425,105 +435,37 @@ function toCdsSourceCsn(csn, options) {
|
|
|
425
435
|
* @return {string}
|
|
426
436
|
*/
|
|
427
437
|
function renderEntity(artifactName, art, env) {
|
|
428
|
-
let result =
|
|
438
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
429
439
|
const childEnv = increaseIndent(env);
|
|
430
440
|
const normalizedArtifactName = renderArtifactName(artifactName);
|
|
431
441
|
result += `${env.indent + (art.abstract ? 'abstract ' : '')}entity ${normalizedArtifactName}`;
|
|
432
442
|
const parameters = Object.keys(art.params || []).map(name => renderParameter(name, art.params[name], childEnv)).join(',\n');
|
|
433
443
|
result += (parameters === '') ? '' : ` (\n${parameters}\n${env.indent})`;
|
|
434
|
-
if (art.includes)
|
|
435
|
-
|
|
436
|
-
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name)).join(', ')}`;
|
|
437
|
-
}
|
|
444
|
+
if (art.includes)
|
|
445
|
+
result += renderIncludes(art.includes);
|
|
438
446
|
result += ' {\n';
|
|
439
447
|
for (const name in art.elements) {
|
|
440
448
|
const element = art.elements[name];
|
|
441
449
|
// For subelement annotations, this seems to be a pattern to recognize them
|
|
442
450
|
// plus some other stuff unfortunately...
|
|
443
|
-
if (element.type && element.elements)
|
|
444
|
-
subelementAnnotates.push(
|
|
451
|
+
if (element.type && element.elements) {
|
|
452
|
+
subelementAnnotates.push({
|
|
453
|
+
annotate: artifactName,
|
|
454
|
+
elements: {
|
|
455
|
+
[name]: {
|
|
456
|
+
elements: element.elements,
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
}
|
|
445
461
|
result += renderElement(name, element, childEnv);
|
|
446
462
|
}
|
|
447
463
|
|
|
448
464
|
result += `${env.indent}}`;
|
|
449
|
-
result += `${renderActionsAndFunctions(art, env)
|
|
465
|
+
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
450
466
|
return result;
|
|
451
467
|
}
|
|
452
468
|
|
|
453
|
-
// Render the 'technical configuration { ... }' section 'tc' of an entity.
|
|
454
|
-
// Return the resulting source string.
|
|
455
|
-
function renderTechnicalConfiguration(tc, env) {
|
|
456
|
-
let result = renderDocComment(tc, env);
|
|
457
|
-
const childEnv = increaseIndent(env);
|
|
458
|
-
|
|
459
|
-
if (!tc)
|
|
460
|
-
return result;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
// FIXME: How to deal with non-HANA technical configurations? We should probably just iterate all entries
|
|
464
|
-
// in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
|
|
465
|
-
tc = tc.hana;
|
|
466
|
-
if (!tc)
|
|
467
|
-
throw new Error('Expecting a HANA technical configuration');
|
|
468
|
-
|
|
469
|
-
result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
|
|
470
|
-
|
|
471
|
-
// Store type (must be separate because SQL wants it between 'CREATE' and 'TABLE')
|
|
472
|
-
if (tc.storeType)
|
|
473
|
-
result += `${tc.storeType} store;\n`;
|
|
474
|
-
|
|
475
|
-
// Fixed parts belonging to the table (includes migration, unload prio, extended storage,
|
|
476
|
-
// auto merge, partitioning, ...)
|
|
477
|
-
if (tc.tableSuffix) {
|
|
478
|
-
// Unlike SQL, CDL and HANA CDS require a semicolon after each table-suffix part
|
|
479
|
-
// (e.g. `migration enabled; row store; ...`). In order to keep both
|
|
480
|
-
// the simplicity of "the whole bandwurm is just one expression that can be
|
|
481
|
-
// rendered to SQL without further knowledge" and at the same time telling
|
|
482
|
-
// CDS about the boundaries, the compactor has put each part into its own `xpr`
|
|
483
|
-
// object. Semantically equivalent because a "trivial" SQL renderer would just
|
|
484
|
-
// concatenate them.
|
|
485
|
-
for (const xpr of tc.tableSuffix)
|
|
486
|
-
result += `${childEnv.indent + renderExpr(xpr, childEnv)};\n`;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Indices and full-text indices
|
|
490
|
-
for (const idxName in tc.indexes || {}) {
|
|
491
|
-
if (Array.isArray(tc.indexes[idxName][0])) {
|
|
492
|
-
// FIXME: Should we allow multiple indices with the same name at all?
|
|
493
|
-
for (const index of tc.indexes[idxName])
|
|
494
|
-
result += `${childEnv.indent + renderExpr(index, childEnv)};\n`;
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
result += `${childEnv.indent + renderExpr(tc.indexes[idxName], childEnv)};\n`;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
// Fuzzy search indices
|
|
501
|
-
for (const columnName in tc.fzindexes || {}) {
|
|
502
|
-
if (Array.isArray(tc.fzindexes[columnName][0])) {
|
|
503
|
-
// FIXME: Should we allow multiple fuzzy search indices on the same column at all?
|
|
504
|
-
// And if not, why do we wrap this into an array?
|
|
505
|
-
for (const index of tc.fzindexes[columnName])
|
|
506
|
-
result += `${childEnv.indent + renderExpr(fixFuzzyIndex(index, columnName), childEnv)};\n`;
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
result += `${childEnv.indent + renderExpr(fixFuzzyIndex(tc.fzindexes[columnName], columnName), childEnv)};\n`;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
result += `${env.indent}}`;
|
|
513
|
-
return result;
|
|
514
|
-
|
|
515
|
-
// Fuzzy indices are stored in compact CSN as they would appear in SQL after the column name,
|
|
516
|
-
// i.e. the whole line in SQL looks somewhat like this:
|
|
517
|
-
// s nvarchar(10) FUZZY SEARCH INDEX ON FUZZY SEARCH MODE 'ALPHANUM'
|
|
518
|
-
// But in CDL, we don't write fuzzy search indices together with the table column, so we need
|
|
519
|
-
// to insert the name of the column after 'ON' in CDS syntax, making it look like this:
|
|
520
|
-
// fuzzy search mode on (s) search mode 'ALPHANUM'
|
|
521
|
-
// This function expects an array with the original expression and returns an array with the modified expression
|
|
522
|
-
function fixFuzzyIndex(fuzzyIndex, columnName) {
|
|
523
|
-
return fuzzyIndex.map(token => (token === 'on' ? { xpr: [ 'on', '(', { ref: columnName.split('.') }, ')' ] } : token));
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
469
|
/**
|
|
528
470
|
* Render an element (of an entity, type or annotation, not a projection or view).
|
|
529
471
|
* Return the resulting source string.
|
|
@@ -535,8 +477,8 @@ function toCdsSourceCsn(csn, options) {
|
|
|
535
477
|
*/
|
|
536
478
|
function renderElement(elementName, elm, env, isSubElement) {
|
|
537
479
|
env.elementName = elementName;
|
|
538
|
-
let result =
|
|
539
|
-
const quotedElementName =
|
|
480
|
+
let result = renderAnnotationAssignmentsAndDocComment(elm, env);
|
|
481
|
+
const quotedElementName = quoteIdIfRequired(elementName);
|
|
540
482
|
result += `${env.indent + (elm.virtual ? 'virtual ' : '') +
|
|
541
483
|
(elm.key && !isSubElement ? 'key ' : '') +
|
|
542
484
|
((elm.masked && !elm._ignoreMasked) ? 'masked ' : '') +
|
|
@@ -550,22 +492,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
550
492
|
return `${result};\n`;
|
|
551
493
|
}
|
|
552
494
|
|
|
553
|
-
/**
|
|
554
|
-
* Return the SELECT of the leading query of query 'query'
|
|
555
|
-
*
|
|
556
|
-
* @param {CSN.Query} query
|
|
557
|
-
*/
|
|
558
|
-
function leadingQuerySelect(query) {
|
|
559
|
-
if (query.SELECT)
|
|
560
|
-
return query.SELECT;
|
|
561
|
-
|
|
562
|
-
// Sanity checks
|
|
563
|
-
if (!query.SET || !query.SET.args || !query.SET.args[0])
|
|
564
|
-
throw new Error(`Expecting set with args in query: ${JSON.stringify(query)}`);
|
|
565
|
-
|
|
566
|
-
return leadingQuerySelect(query.SET.args[0]);
|
|
567
|
-
}
|
|
568
|
-
|
|
569
495
|
/**
|
|
570
496
|
* Render a query's actions and functions (if any) separately as extend-statements, so that actions
|
|
571
497
|
* work not only for projections but also for views, which have no syntax (yet) to directly specify
|
|
@@ -589,9 +515,13 @@ function toCdsSourceCsn(csn, options) {
|
|
|
589
515
|
|
|
590
516
|
/**
|
|
591
517
|
* Render annotations that were extended to a query element of a view or projection (they only
|
|
592
|
-
* appear in the view's 'elements', not in their 'columns', because the element
|
|
593
|
-
* even be in 'columns', e.g. if it was expanded from a '*').
|
|
594
|
-
* statement or an empty string if none required.
|
|
518
|
+
* appear in the view's 'elements', not in their 'columns' for client CSN, because the element
|
|
519
|
+
* itself may not even be in 'columns', e.g. if it was expanded from a '*'). Return the
|
|
520
|
+
* resulting rendered 'annotate' statement or an empty string if none required.
|
|
521
|
+
*
|
|
522
|
+
* Note: In the past, we checked if the annotation also exists in the respective column,
|
|
523
|
+
* however, in client CSN, annotations are not part of the column and in parseCdl CSN,
|
|
524
|
+
* no `elements` exist.
|
|
595
525
|
*
|
|
596
526
|
* @param {string} artifactName
|
|
597
527
|
* @param {CSN.Artifact} art
|
|
@@ -599,43 +529,61 @@ function toCdsSourceCsn(csn, options) {
|
|
|
599
529
|
* @return {string}
|
|
600
530
|
*/
|
|
601
531
|
function renderQueryElementAnnotations(artifactName, art, env) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (col === '*')
|
|
608
|
-
continue;
|
|
609
|
-
|
|
610
|
-
// Column must have an alias or be a path - take last part of that as element name
|
|
611
|
-
columnMap[col.as || col.func || getLastPartOfRef(col.ref)] = col;
|
|
612
|
-
}
|
|
613
|
-
// Now iterate elements - render an annotation if it is different from the column's
|
|
614
|
-
const childEnv = increaseIndent(env);
|
|
615
|
-
let result = '';
|
|
616
|
-
for (const elemName in art.elements) {
|
|
617
|
-
let elemAnnotations = '';
|
|
618
|
-
const elem = art.elements[elemName];
|
|
619
|
-
for (const name in elem) {
|
|
620
|
-
if (!name.startsWith('@'))
|
|
621
|
-
continue;
|
|
622
|
-
|
|
623
|
-
const annotationValue = renderAnnotationValue(elem[name], childEnv);
|
|
624
|
-
// Skip annotation if column has the same
|
|
625
|
-
if (columnMap[elemName] && columnMap[elemName][name] &&
|
|
626
|
-
renderAnnotationValue(columnMap[elemName][name], childEnv) === annotationValue)
|
|
627
|
-
continue;
|
|
628
|
-
|
|
629
|
-
// Annotation names are never flattened
|
|
630
|
-
elemAnnotations += `${childEnv.indent}${`@${renderAbsoluteNameWithQuotes(name.substring(1))}`} : ${annotationValue}\n`;
|
|
631
|
-
}
|
|
632
|
-
if (elemAnnotations !== '')
|
|
633
|
-
result += `${elemAnnotations}${childEnv.indent}${quoteOrUppercaseId(elemName)};\n`;
|
|
634
|
-
}
|
|
635
|
-
if (result !== '')
|
|
636
|
-
result = `${env.indent}annotate ${renderArtifactName(artifactName)} with {\n${result}${env.indent}};\n`;
|
|
532
|
+
const annotate = collectAnnotationsOfElements(artifactName, art);
|
|
533
|
+
if (annotate)
|
|
534
|
+
return renderExtensions([ annotate ], env);
|
|
535
|
+
return '';
|
|
536
|
+
}
|
|
637
537
|
|
|
638
|
-
|
|
538
|
+
/**
|
|
539
|
+
* Create an "annotate" statement as a CSN extension for all annotations of (sub-)elements.
|
|
540
|
+
* If no annotation was found, we return `null`.
|
|
541
|
+
*
|
|
542
|
+
* @param {string} artifactName
|
|
543
|
+
* @param {CSN.Artifact} artWithElements
|
|
544
|
+
* @return {CSN.Extension|null}
|
|
545
|
+
*/
|
|
546
|
+
function collectAnnotationsOfElements(artifactName, artWithElements) {
|
|
547
|
+
const annotate = { annotate: artifactName };
|
|
548
|
+
return collectAnnos(annotate, artWithElements) ? annotate : null;
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Recursive function to collect annotations. `annotateObj` will get an `elements`
|
|
552
|
+
* object with annotations only if there are annotations on `art`'s (sub-)elements.
|
|
553
|
+
*
|
|
554
|
+
* @return {boolean} True, if there were annotations, false otherwise.
|
|
555
|
+
*/
|
|
556
|
+
function collectAnnos(annotateObj, art) {
|
|
557
|
+
if (!art.elements)
|
|
558
|
+
return false;
|
|
559
|
+
|
|
560
|
+
const collected = { elements: Object.create(null) };
|
|
561
|
+
let hasAnnotation = false;
|
|
562
|
+
|
|
563
|
+
forEach(art.elements, (elemName, element) => {
|
|
564
|
+
if (!collected.elements[elemName])
|
|
565
|
+
collected.elements[elemName] = { };
|
|
566
|
+
|
|
567
|
+
let hasElementAnnotations = false;
|
|
568
|
+
for (const name in element) {
|
|
569
|
+
if (name.startsWith('@')) {
|
|
570
|
+
collected.elements[elemName][name] = element[name];
|
|
571
|
+
hasElementAnnotations = true;
|
|
572
|
+
hasAnnotation = true;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const hasSubAnnotations = collectAnnos(collected.elements[elemName], element);
|
|
577
|
+
if (!hasElementAnnotations && !hasSubAnnotations)
|
|
578
|
+
delete collected.elements[elemName]; // delete if no annotations exist
|
|
579
|
+
hasAnnotation = hasAnnotation || hasSubAnnotations;
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
if (hasAnnotation)
|
|
583
|
+
annotateObj.elements = collected.elements;
|
|
584
|
+
|
|
585
|
+
return hasAnnotation;
|
|
586
|
+
}
|
|
639
587
|
}
|
|
640
588
|
|
|
641
589
|
/**
|
|
@@ -650,25 +598,24 @@ function toCdsSourceCsn(csn, options) {
|
|
|
650
598
|
function renderViewSource(source, env) {
|
|
651
599
|
// Sub-SELECT
|
|
652
600
|
if (source.SELECT || source.SET) {
|
|
653
|
-
let result = `(${
|
|
601
|
+
let result = `(${renderQuery(source, false, 'view', increaseIndent(env))})`;
|
|
654
602
|
if (source.as)
|
|
655
|
-
result += ` as ${
|
|
603
|
+
result += ` as ${quoteIdIfRequired(source.as)}`;
|
|
656
604
|
|
|
657
605
|
return result;
|
|
658
606
|
}
|
|
659
607
|
// JOIN
|
|
660
608
|
else if (source.join) {
|
|
661
609
|
// One join operation, possibly with ON-condition
|
|
662
|
-
let result =
|
|
610
|
+
let result = `(${renderViewSource(source.args[0], env)}`;
|
|
663
611
|
for (let i = 1; i < source.args.length; i++) {
|
|
664
|
-
result
|
|
612
|
+
result += ` ${source.join} `;
|
|
665
613
|
result += renderJoinCardinality(source.cardinality);
|
|
666
614
|
result += `join ${renderViewSource(source.args[i], env)}`;
|
|
667
615
|
if (source.on)
|
|
668
616
|
result += ` on ${renderExpr(source.on, env, true, true)}`;
|
|
669
|
-
|
|
670
|
-
result += ')';
|
|
671
617
|
}
|
|
618
|
+
result += ')';
|
|
672
619
|
return result;
|
|
673
620
|
}
|
|
674
621
|
// Ordinary path, possibly with an alias
|
|
@@ -703,15 +650,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
703
650
|
function renderAbsolutePath(path, env) {
|
|
704
651
|
// Sanity checks
|
|
705
652
|
if (!path.ref)
|
|
706
|
-
throw new
|
|
653
|
+
throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
|
|
707
654
|
|
|
708
655
|
|
|
709
656
|
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
710
657
|
const firstArtifactName = path.ref[0].id || path.ref[0];
|
|
711
658
|
|
|
712
|
-
let result = renderDocComment(path, env);
|
|
713
659
|
// Render the first path step (absolute name, with different quoting/naming ..)
|
|
714
|
-
result
|
|
660
|
+
let result = renderArtifactName(firstArtifactName);
|
|
715
661
|
|
|
716
662
|
// Even the first step might have parameters and/or a filter
|
|
717
663
|
if (path.ref[0].args)
|
|
@@ -742,11 +688,26 @@ function toCdsSourceCsn(csn, options) {
|
|
|
742
688
|
let result = renderAbsolutePath(path, env);
|
|
743
689
|
if (path.as) {
|
|
744
690
|
// Source had an alias - render it
|
|
745
|
-
result += ` as ${
|
|
691
|
+
result += ` as ${quoteIdIfRequired(path.as)}`;
|
|
746
692
|
}
|
|
747
693
|
return result;
|
|
748
694
|
}
|
|
749
695
|
|
|
696
|
+
/**
|
|
697
|
+
* Render the given columns.
|
|
698
|
+
*
|
|
699
|
+
* @param {any[]} columns
|
|
700
|
+
* @param {object} elements
|
|
701
|
+
* @param {CdlRenderEnvironment} env
|
|
702
|
+
* @return {string}
|
|
703
|
+
*/
|
|
704
|
+
function renderViewColumns(columns, env, elements = Object.create(null)) {
|
|
705
|
+
const result = columns.map(col => renderViewColumn(col, env, findElement(elements, col)))
|
|
706
|
+
.filter(s => s !== '')
|
|
707
|
+
.join(',\n');
|
|
708
|
+
return `${result}\n`;
|
|
709
|
+
}
|
|
710
|
+
|
|
750
711
|
/**
|
|
751
712
|
* Render a single view or projection column 'col', as it occurs in a select list or
|
|
752
713
|
* projection list within 'art', possibly with annotations.
|
|
@@ -758,7 +719,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
758
719
|
*/
|
|
759
720
|
function renderViewColumn(col, env, element) {
|
|
760
721
|
// Annotations and column
|
|
761
|
-
let result =
|
|
722
|
+
let result = '';
|
|
723
|
+
if (!col.doc) {
|
|
724
|
+
// TODO: In contrast to annotations, we do not render the doc comment as part
|
|
725
|
+
// of an `annotate` statement. That may change in the future.
|
|
726
|
+
result += renderDocComment(element, env);
|
|
727
|
+
}
|
|
728
|
+
// Note: parentheses are a workaround for #9015
|
|
729
|
+
result += renderAnnotationAssignmentsAndDocComment(col, env, { parens: true });
|
|
762
730
|
result += env.indent;
|
|
763
731
|
|
|
764
732
|
// only if column is virtual, keyword virtual was present in the source text
|
|
@@ -769,22 +737,19 @@ function toCdsSourceCsn(csn, options) {
|
|
|
769
737
|
// Use special rendering for .expand/.inline - renderExpr cannot easily handle some cases
|
|
770
738
|
result += key + ((col.expand || col.inline) ? renderInlineExpand(col, env) : renderExpr(col, env, true));
|
|
771
739
|
|
|
772
|
-
// Alias is already handled by renderInlineExpand
|
|
773
|
-
|
|
774
|
-
|
|
740
|
+
// Alias for inline/expand is already handled by renderInlineExpand
|
|
741
|
+
// A new association (cast with `type` and `target`) uses `as` as its primary name, not alias.
|
|
742
|
+
const isNewAssociation = col.cast && col.cast.type && col.cast.target;
|
|
743
|
+
if (col.as && !col.inline && !col.expand && !isNewAssociation)
|
|
744
|
+
result += ` as ${quoteIdIfRequired(col.as)}`;
|
|
775
745
|
|
|
776
746
|
// Explicit type provided for the view element?
|
|
777
747
|
if (col.cast) {
|
|
778
748
|
// Special case: Explicit association type is actually a redirect
|
|
779
|
-
if (col.cast.target)
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (col.cast.on)
|
|
783
|
-
result += ` on ${renderExpr(col.cast.on, env, true, true)}`;
|
|
784
|
-
}
|
|
785
|
-
else {
|
|
749
|
+
if (col.cast.target && !col.cast.type)
|
|
750
|
+
result += ` : ${renderRedirectedTo(col.cast, env)}`;
|
|
751
|
+
else
|
|
786
752
|
result += ` : ${renderTypeReference(col.cast, env, true)}`;
|
|
787
|
-
}
|
|
788
753
|
}
|
|
789
754
|
return result;
|
|
790
755
|
}
|
|
@@ -813,16 +778,10 @@ function toCdsSourceCsn(csn, options) {
|
|
|
813
778
|
|
|
814
779
|
// We found a leaf - no further drilling
|
|
815
780
|
if (!obj.inline && !obj.expand) {
|
|
816
|
-
if (obj.cast && obj.cast.type)
|
|
781
|
+
if (obj.cast && obj.cast.type)
|
|
817
782
|
result += ` : ${renderTypeReference(obj.cast, createEnv())}`;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
result += ` : redirected to ${renderAbsoluteNameWithQuotes(obj.cast.target)}`;
|
|
821
|
-
if (obj.cast.on)
|
|
822
|
-
result += ` on ${renderExpr(obj.cast.on, env, true, true)}`;
|
|
823
|
-
else if (obj.cast.keys)
|
|
824
|
-
result += ` { ${Object.keys(obj.cast.keys).map(name => renderForeignKey(obj.cast.keys[name], env)).join(', ')} }`;
|
|
825
|
-
}
|
|
783
|
+
else if (obj.cast && obj.cast.target) // test tbd
|
|
784
|
+
result += ` : ${renderRedirectedTo(obj.cast, env)}`;
|
|
826
785
|
return result;
|
|
827
786
|
}
|
|
828
787
|
|
|
@@ -857,7 +816,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
857
816
|
* Render .doc properties as comments in CDL
|
|
858
817
|
*
|
|
859
818
|
* @param {object} obj Object to render for
|
|
860
|
-
* @param {
|
|
819
|
+
* @param {CdlRenderEnvironment} env
|
|
861
820
|
* @returns {String}
|
|
862
821
|
*/
|
|
863
822
|
function renderDocComment(obj, env) {
|
|
@@ -866,7 +825,13 @@ function toCdsSourceCsn(csn, options) {
|
|
|
866
825
|
else if (obj && obj.doc === null) // empty doc comment needs to be rendered
|
|
867
826
|
return `\n${env.indent}/** */\n`;
|
|
868
827
|
|
|
869
|
-
|
|
828
|
+
// Smaller comment for single-line comments. If the comments starts or ends with whitespace
|
|
829
|
+
// we must use a block comment, or it will be lost when compiling the source again.
|
|
830
|
+
if (!obj.doc.includes('\n') && !/^\s|\s$/.test(obj.doc))
|
|
831
|
+
return `${env.indent}/** ${obj.doc} */\n`;
|
|
832
|
+
|
|
833
|
+
const comment = obj.doc.split('\n').map(line => `${env.indent} * ${line}`).join('\n');
|
|
834
|
+
return `${env.indent}/**\n${comment}\n${env.indent} */\n`;
|
|
870
835
|
}
|
|
871
836
|
|
|
872
837
|
/**
|
|
@@ -880,7 +845,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
880
845
|
*/
|
|
881
846
|
function renderView(artifactName, art, env) {
|
|
882
847
|
const syntax = (art.projection) ? 'projection' : 'entity';
|
|
883
|
-
let result =
|
|
848
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
884
849
|
result += `${env.indent}${art.abstract ? 'abstract ' : ''}${syntax === 'projection' ? 'entity' : syntax} ${renderArtifactName(artifactName)}`;
|
|
885
850
|
if (art.params) {
|
|
886
851
|
const childEnv = increaseIndent(env);
|
|
@@ -909,50 +874,31 @@ function toCdsSourceCsn(csn, options) {
|
|
|
909
874
|
* @param {string} syntax The query syntax, either "projection", "entity" or "view"
|
|
910
875
|
* @param {CdlRenderEnvironment} env
|
|
911
876
|
* @param {CSN.Path} [path=[]]
|
|
877
|
+
* @param {object} [elements]
|
|
912
878
|
*/
|
|
913
879
|
function renderQuery(query, isLeadingQuery, syntax, env, path = [], elements = query.elements || Object.create(null)) {
|
|
914
|
-
let result = renderDocComment(query, env);
|
|
915
|
-
// Set operator, like UNION, INTERSECT, ...
|
|
916
880
|
if (query.SET) {
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
// FIXME: Clarify if set operators can be n-ary (assuming binary here)
|
|
920
|
-
if (query.SET.op) {
|
|
921
|
-
// Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
|
|
922
|
-
for (let i = 1; i < query.SET.args.length; i++)
|
|
923
|
-
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)}`;
|
|
924
|
-
}
|
|
925
|
-
result += ')';
|
|
926
|
-
// Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
|
|
927
|
-
// each SELECT)
|
|
928
|
-
if (query.SET.orderBy)
|
|
929
|
-
result += `${continueIndent(result, env)}order by ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
|
|
930
|
-
|
|
931
|
-
if (query.SET.limit)
|
|
932
|
-
result += `${continueIndent(result, env)}${renderLimit(query.SET.limit, env)}`;
|
|
933
|
-
|
|
934
|
-
return result;
|
|
881
|
+
// Set operator, such as UNION, INTERSECT, or EXCEPT...
|
|
882
|
+
return renderQuerySet();
|
|
935
883
|
}
|
|
936
|
-
// Otherwise must have a SELECT
|
|
937
884
|
else if (!query.SELECT) {
|
|
938
|
-
|
|
885
|
+
// ...otherwise must have a SELECT
|
|
886
|
+
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
939
887
|
}
|
|
888
|
+
|
|
889
|
+
let result = '';
|
|
940
890
|
const select = query.SELECT;
|
|
941
891
|
const childEnv = increaseIndent(env);
|
|
942
892
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
result += `select from ${renderViewSource(select.from, env)}`;
|
|
947
|
-
else
|
|
948
|
-
throw new Error(`Unknown query syntax: ${syntax}`);
|
|
893
|
+
// If not a projection, must be view/entity.
|
|
894
|
+
result += (syntax === 'projection') ? 'projection on ' : 'select from ';
|
|
895
|
+
result += renderViewSource(select.from, env);
|
|
949
896
|
|
|
950
897
|
if (select.mixin) {
|
|
951
898
|
let elems = '';
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
}
|
|
899
|
+
forEach(select.mixin, (name, mixin) => {
|
|
900
|
+
elems += renderElement(name, mixin, childEnv);
|
|
901
|
+
});
|
|
956
902
|
if (elems) {
|
|
957
903
|
result += ' mixin {\n';
|
|
958
904
|
result += elems;
|
|
@@ -962,13 +908,12 @@ function toCdsSourceCsn(csn, options) {
|
|
|
962
908
|
result += select.distinct ? ' distinct' : '';
|
|
963
909
|
if (select.columns) {
|
|
964
910
|
result += ' {\n';
|
|
965
|
-
result +=
|
|
966
|
-
.filter(s => s !== '')
|
|
967
|
-
.join(',\n')}\n`;
|
|
911
|
+
result += renderViewColumns(select.columns, increaseIndent(env), elements);
|
|
968
912
|
result += `${env.indent}}`;
|
|
969
913
|
}
|
|
914
|
+
|
|
970
915
|
if (select.excluding) {
|
|
971
|
-
result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${
|
|
916
|
+
result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${quoteIdIfRequired(id)}`).join(',\n')}\n`;
|
|
972
917
|
result += `${env.indent}}`;
|
|
973
918
|
}
|
|
974
919
|
// FIXME: Currently, only projections can have actions and functions, but we cannot distinguish
|
|
@@ -980,7 +925,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
980
925
|
result += `${continueIndent(result, env)}where ${renderExpr(select.where, env, true, true)}`;
|
|
981
926
|
|
|
982
927
|
if (select.groupBy)
|
|
983
|
-
result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
|
|
928
|
+
result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
|
|
984
929
|
|
|
985
930
|
if (select.having)
|
|
986
931
|
result += `${continueIndent(result, env)}having ${renderExpr(select.having, env, true, true)}`;
|
|
@@ -1026,6 +971,29 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1026
971
|
|
|
1027
972
|
return limitStr;
|
|
1028
973
|
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Render UNION, INTERSECT, and EXCEPT, i.e. sets.
|
|
977
|
+
*
|
|
978
|
+
* @return {string}
|
|
979
|
+
*/
|
|
980
|
+
function renderQuerySet() {
|
|
981
|
+
const subQueries = query.SET.args.map((arg, i) => {
|
|
982
|
+
// First arg may be leading query
|
|
983
|
+
const subQuery = renderQuery(arg, isLeadingQuery && (i === 0), 'view', env, path.concat([ 'SET', 'args', i ]), elements);
|
|
984
|
+
return `(${subQuery})`;
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
let setResult = subQueries.join(`\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} `);
|
|
988
|
+
// Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
|
|
989
|
+
// each SELECT)
|
|
990
|
+
if (query.SET.orderBy)
|
|
991
|
+
setResult += `${continueIndent(setResult, env)}order by ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
|
|
992
|
+
|
|
993
|
+
if (query.SET.limit)
|
|
994
|
+
setResult += `${continueIndent(setResult, env)}${renderLimit(query.SET.limit, env)}`;
|
|
995
|
+
return setResult;
|
|
996
|
+
}
|
|
1029
997
|
}
|
|
1030
998
|
|
|
1031
999
|
/**
|
|
@@ -1037,7 +1005,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1037
1005
|
* @return {string}
|
|
1038
1006
|
*/
|
|
1039
1007
|
function renderOrderByEntry(entry, env) {
|
|
1040
|
-
let result =
|
|
1008
|
+
let result = renderAnnotationAssignmentsAndDocComment(entry, env) + renderExpr(entry, env, true, false, true);
|
|
1041
1009
|
if (entry.sort)
|
|
1042
1010
|
result += ` ${entry.sort}`;
|
|
1043
1011
|
|
|
@@ -1061,7 +1029,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1061
1029
|
let result = '';
|
|
1062
1030
|
const childEnv = increaseIndent(env);
|
|
1063
1031
|
for (const name in art.actions)
|
|
1064
|
-
result +=
|
|
1032
|
+
result += renderActionOrFunction(name, art.actions[name], childEnv);
|
|
1065
1033
|
|
|
1066
1034
|
// Even if we have seen actions/functions, they might all have been ignored
|
|
1067
1035
|
if (result !== '')
|
|
@@ -1079,13 +1047,23 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1079
1047
|
* @return {string}
|
|
1080
1048
|
*/
|
|
1081
1049
|
function renderActionOrFunction(actionName, act, env) {
|
|
1082
|
-
let result = `${
|
|
1050
|
+
let result = `${renderAnnotationAssignmentsAndDocComment(act, env) + env.indent + act.kind} ${renderArtifactName(actionName)}`;
|
|
1083
1051
|
const childEnv = increaseIndent(env);
|
|
1084
1052
|
const parameters = Object.keys(act.params || []).map(name => renderParameter(name, act.params[name], childEnv)).join(',\n');
|
|
1085
1053
|
result += (parameters === '') ? '()' : `(\n${parameters}\n${env.indent})`;
|
|
1086
1054
|
if (act.returns) {
|
|
1087
|
-
if (act.returns.type && act.returns.elements)
|
|
1088
|
-
|
|
1055
|
+
if (act.returns.type && act.returns.elements) {
|
|
1056
|
+
// Annotation in action returns' structure. Render it as an annotate statement.
|
|
1057
|
+
const isBoundAction = (env.artifactName !== actionName);
|
|
1058
|
+
const anno = {
|
|
1059
|
+
annotate: env.artifactName || actionName,
|
|
1060
|
+
};
|
|
1061
|
+
if (isBoundAction)
|
|
1062
|
+
anno.actions = { [actionName]: { returns: { elements: act.returns.elements } } };
|
|
1063
|
+
else
|
|
1064
|
+
anno.returns = { elements: act.returns.elements };
|
|
1065
|
+
subelementAnnotates.push(anno);
|
|
1066
|
+
}
|
|
1089
1067
|
result += ` returns ${renderTypeReference(act.returns, env)}${renderNullability(act.returns)}`;
|
|
1090
1068
|
}
|
|
1091
1069
|
|
|
@@ -1102,7 +1080,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1102
1080
|
* @return {string}
|
|
1103
1081
|
*/
|
|
1104
1082
|
function renderParameter(parName, par, env) {
|
|
1105
|
-
let result = `${
|
|
1083
|
+
let result = `${renderAnnotationAssignmentsAndDocComment(par, env) + env.indent + quoteIdIfRequired(parName)} : ${renderTypeReference(par, env)}`;
|
|
1106
1084
|
if (par.default)
|
|
1107
1085
|
result += ` default ${renderExpr(par.default, env)}`;
|
|
1108
1086
|
|
|
@@ -1121,14 +1099,11 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1121
1099
|
* @return {string}
|
|
1122
1100
|
*/
|
|
1123
1101
|
function renderTypeOrAnnotation(artifactName, art, env, artType) {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
// Includes are never flattened (don't exist in HANA)
|
|
1130
|
-
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name)).join(', ')}`;
|
|
1131
|
-
}
|
|
1102
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
1103
|
+
result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName)}`;
|
|
1104
|
+
if (art.includes)
|
|
1105
|
+
result += renderIncludes(art.includes);
|
|
1106
|
+
|
|
1132
1107
|
const childEnv = increaseIndent(env);
|
|
1133
1108
|
if (art.elements && !art.type) {
|
|
1134
1109
|
// Structured type or annotation with anonymous struct type
|
|
@@ -1140,7 +1115,10 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1140
1115
|
}
|
|
1141
1116
|
else {
|
|
1142
1117
|
// Derived type or annotation with non-anonymous type
|
|
1143
|
-
result += ` : ${renderTypeReference(art, env, false)}
|
|
1118
|
+
result += ` : ${renderTypeReference(art, env, false)}`;
|
|
1119
|
+
if (art.default)
|
|
1120
|
+
result += ` default ${renderExpr(art.default, env)}`;
|
|
1121
|
+
result += ';\n';
|
|
1144
1122
|
}
|
|
1145
1123
|
return result;
|
|
1146
1124
|
}
|
|
@@ -1164,8 +1142,16 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1164
1142
|
if (elm.items.notNull != null)
|
|
1165
1143
|
rc += elm.items.notNull ? ' not null' : ' null';
|
|
1166
1144
|
// many sub element annotates
|
|
1167
|
-
|
|
1168
|
-
|
|
1145
|
+
// TODO(andre): This does handle deeply nested elements because we take the full `elements`.
|
|
1146
|
+
// But when is env.elementName set and when not?
|
|
1147
|
+
if (elm.items.type && elm.items.elements && env.artifactName) {
|
|
1148
|
+
const annotate = { annotate: env.artifactName };
|
|
1149
|
+
if (env.elementName)
|
|
1150
|
+
annotate.elements = { [env.elementName]: elm.items };
|
|
1151
|
+
else
|
|
1152
|
+
annotate.elements = elm.items.elements;
|
|
1153
|
+
subelementAnnotates.push(annotate);
|
|
1154
|
+
}
|
|
1169
1155
|
|
|
1170
1156
|
return rc;
|
|
1171
1157
|
}
|
|
@@ -1176,7 +1162,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1176
1162
|
// Anonymous structured type
|
|
1177
1163
|
if (!elm.type) {
|
|
1178
1164
|
if (!elm.elements)
|
|
1179
|
-
throw new
|
|
1165
|
+
throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
|
|
1180
1166
|
|
|
1181
1167
|
result += '{\n';
|
|
1182
1168
|
const childEnv = increaseIndent(env);
|
|
@@ -1201,23 +1187,27 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1201
1187
|
((elm.type === comp) ? ' of ' : ' to ');
|
|
1202
1188
|
}
|
|
1203
1189
|
|
|
1204
|
-
//
|
|
1205
|
-
|
|
1190
|
+
// `targetAspect` may be set by the core compiler and refers to the original named or unnamed aspect.
|
|
1191
|
+
// In parseCdl, `target` may still be an object containing elements. This would be replaced
|
|
1192
|
+
// by targetAspect in client CSN, but we can't rely on that.
|
|
1193
|
+
// If a name exists (either in target or targetAspect), prefer it over rendering elements.
|
|
1194
|
+
const elements = elm.target && elm.target.elements || elm.targetAspect && elm.targetAspect.elements;
|
|
1195
|
+
if (typeof elm.target === 'string' || typeof elm.targetAspect === 'string') {
|
|
1206
1196
|
result += renderAbsolutePath({ ref: [ elm.target || elm.targetAspect ] }, env);
|
|
1207
1197
|
}
|
|
1208
|
-
else if (
|
|
1198
|
+
else if (elements) {
|
|
1199
|
+
// anonymous aspect, either parseCdl or client CSN.
|
|
1209
1200
|
const childEnv = increaseIndent(env);
|
|
1210
1201
|
result += '{\n';
|
|
1211
|
-
for (const name in
|
|
1212
|
-
result += renderElement(name,
|
|
1202
|
+
for (const name in elements)
|
|
1203
|
+
result += renderElement(name, elements[name], childEnv);
|
|
1213
1204
|
|
|
1214
1205
|
result += `${env.indent}}`;
|
|
1215
1206
|
}
|
|
1216
1207
|
else {
|
|
1217
|
-
throw new
|
|
1208
|
+
throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
|
|
1218
1209
|
}
|
|
1219
1210
|
|
|
1220
|
-
|
|
1221
1211
|
// ON-condition (if any)
|
|
1222
1212
|
if (elm.on)
|
|
1223
1213
|
result += ` on ${renderExpr(elm.on, env, true, true)}`;
|
|
@@ -1249,14 +1239,8 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1249
1239
|
}
|
|
1250
1240
|
|
|
1251
1241
|
// If we get here, it must be a named type
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
}
|
|
1255
|
-
else {
|
|
1256
|
-
// Simple absolute name
|
|
1257
|
-
// Type names are never flattened (derived types are unraveled in HANA)
|
|
1258
|
-
result += getResultingName(elm.type);
|
|
1259
|
-
}
|
|
1242
|
+
result += renderNamedTypeWithParameters(elm);
|
|
1243
|
+
|
|
1260
1244
|
if (elm.enum && !noEnum)
|
|
1261
1245
|
result += renderEnum(elm.enum, env);
|
|
1262
1246
|
|
|
@@ -1264,18 +1248,48 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1264
1248
|
}
|
|
1265
1249
|
|
|
1266
1250
|
/**
|
|
1267
|
-
*
|
|
1251
|
+
* Render REDIRECTED TO with its keys/on condition for the given artifact.
|
|
1252
|
+
*
|
|
1253
|
+
* @param {object} art
|
|
1254
|
+
* @param {CdlRenderEnvironment} env
|
|
1268
1255
|
* @return {string}
|
|
1269
1256
|
*/
|
|
1270
|
-
function
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1257
|
+
function renderRedirectedTo(art, env) {
|
|
1258
|
+
let result = `redirected to ${quoteIdIfRequired(art.target)}`;
|
|
1259
|
+
if (art.on)
|
|
1260
|
+
result += ` on ${renderExpr(art.on, env, true, true)}`;
|
|
1261
|
+
else if (art.keys)
|
|
1262
|
+
result += ` { ${Object.keys(art.keys).map(name => renderForeignKey(art.keys[name], env)).join(', ')} }`;
|
|
1263
|
+
return result;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Render the named type with optional parameters, e.g. `MyString(length: 10)`.
|
|
1268
|
+
* @param {CSN.Artifact} artWithType
|
|
1269
|
+
* @return {string}
|
|
1270
|
+
*/
|
|
1271
|
+
function renderNamedTypeWithParameters(artWithType) {
|
|
1272
|
+
let result = '';
|
|
1275
1273
|
|
|
1276
|
-
|
|
1274
|
+
if (isBuiltinType(artWithType.type)) {
|
|
1275
|
+
// If there is a user-defined type with the same short name (cds.Integer -> Integer),
|
|
1276
|
+
// we render the full name, including the leading "cds."
|
|
1277
|
+
if (csn.definitions[artWithType.type.slice(4)])
|
|
1278
|
+
result += artWithType.type;
|
|
1279
|
+
else
|
|
1280
|
+
result += artWithType.type.slice(4);
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
// Simple absolute name
|
|
1284
|
+
// Type names are never flattened (derived types are unraveled in HANA)
|
|
1285
|
+
result += renderArtifactName(artWithType.type);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
result += renderTypeParameters(artWithType);
|
|
1289
|
+
return result;
|
|
1277
1290
|
}
|
|
1278
1291
|
|
|
1292
|
+
|
|
1279
1293
|
/**
|
|
1280
1294
|
* Render the 'enum { ... } part of a type declaration
|
|
1281
1295
|
*
|
|
@@ -1286,21 +1300,32 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1286
1300
|
function renderEnum(enumPart, env) {
|
|
1287
1301
|
let result = ' enum {\n';
|
|
1288
1302
|
const childEnv = increaseIndent(env);
|
|
1289
|
-
for (const name in enumPart)
|
|
1290
|
-
|
|
1291
|
-
result += renderDocComment(enumConst, childEnv);
|
|
1292
|
-
result += renderAnnotationAssignments(enumConst, childEnv);
|
|
1293
|
-
result += childEnv.indent + quoteIdIfRequired(name);
|
|
1294
|
-
if (enumConst.val !== undefined)
|
|
1295
|
-
result += ` = ${renderExpr(enumConst, childEnv)}`;
|
|
1296
|
-
else if (enumConst['#'] !== undefined)
|
|
1297
|
-
result += ` = #${enumConst['#']}`;
|
|
1298
|
-
result += ';\n';
|
|
1299
|
-
}
|
|
1303
|
+
for (const name in enumPart)
|
|
1304
|
+
result += renderEnumElement(name, enumPart[name], childEnv);
|
|
1300
1305
|
result += `${env.indent}}`;
|
|
1301
1306
|
return result;
|
|
1302
1307
|
}
|
|
1303
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* Render the element of a `<type> enum {}` structure.
|
|
1311
|
+
*
|
|
1312
|
+
* @param {string} name
|
|
1313
|
+
* @param {CSN.EnumValue} enumValue
|
|
1314
|
+
* @param {CdlRenderEnvironment} env
|
|
1315
|
+
* @return {string}
|
|
1316
|
+
*/
|
|
1317
|
+
function renderEnumElement(name, enumValue, env) {
|
|
1318
|
+
let result = '';
|
|
1319
|
+
result += renderAnnotationAssignmentsAndDocComment(enumValue, env);
|
|
1320
|
+
result += env.indent + quoteIdIfRequired(name);
|
|
1321
|
+
if (enumValue.val !== undefined)
|
|
1322
|
+
result += ` = ${renderExpr(enumValue, env)}`;
|
|
1323
|
+
else if (enumValue['#'] !== undefined)
|
|
1324
|
+
result += ` = #${enumValue['#']}`;
|
|
1325
|
+
result += ';\n';
|
|
1326
|
+
return result;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1304
1329
|
/**
|
|
1305
1330
|
* Render an annotation value (somewhat like a simplified expression, with slightly different
|
|
1306
1331
|
* representation)
|
|
@@ -1310,17 +1335,25 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1310
1335
|
*/
|
|
1311
1336
|
function renderAnnotationValue(x, env) {
|
|
1312
1337
|
if (Array.isArray(x)) {
|
|
1313
|
-
// Render array parts as values
|
|
1314
|
-
|
|
1338
|
+
// Render array parts as values. Spaces required if last array value is
|
|
1339
|
+
// a delimited identifier.
|
|
1340
|
+
return `[ ${x.map(item => renderAnnotationValue(item, env)).join(', ')} ]`;
|
|
1315
1341
|
}
|
|
1316
1342
|
else if (typeof x === 'object' && x !== null) {
|
|
1317
1343
|
// Enum symbol
|
|
1318
|
-
if (x['#'])
|
|
1344
|
+
if (x['#']) {
|
|
1319
1345
|
return `#${x['#']}`;
|
|
1320
|
-
|
|
1346
|
+
}
|
|
1321
1347
|
// Shorthand for absolute path (as string)
|
|
1322
|
-
else if (x['='])
|
|
1348
|
+
else if (x['=']) {
|
|
1323
1349
|
return quotePathString(x['=']);
|
|
1350
|
+
}
|
|
1351
|
+
// Shorthand for ellipsis: `... up to <val>`
|
|
1352
|
+
else if (x['...']) {
|
|
1353
|
+
if (x['...'] === true)
|
|
1354
|
+
return '...';
|
|
1355
|
+
return `... up to ${renderAnnotationValue(x['...'], env)}`;
|
|
1356
|
+
}
|
|
1324
1357
|
|
|
1325
1358
|
// Struct value (can actually only occur within an array)
|
|
1326
1359
|
|
|
@@ -1339,170 +1372,54 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1339
1372
|
}
|
|
1340
1373
|
|
|
1341
1374
|
/**
|
|
1342
|
-
* Render
|
|
1343
|
-
* (no trailing LF, don't indent if inline)
|
|
1375
|
+
* Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
|
|
1344
1376
|
*
|
|
1345
|
-
* @param {
|
|
1346
|
-
* @param {
|
|
1347
|
-
* @
|
|
1348
|
-
* @param {boolean} [inExpr=false] Whether the expression is already inside another expression
|
|
1377
|
+
* @param {string|object} s
|
|
1378
|
+
* @param {number} idx
|
|
1379
|
+
* @returns {string}
|
|
1349
1380
|
*/
|
|
1350
|
-
function
|
|
1351
|
-
//
|
|
1352
|
-
if (
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
if (typeof x === 'object' && x !== null) {
|
|
1356
|
-
if (inExpr && x.cast && x.cast.type)
|
|
1357
|
-
return renderExplicitTypeCast(renderExprObject());
|
|
1358
|
-
return renderExprObject();
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
1362
|
-
return x;
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
/**
|
|
1366
|
-
* Various special cases represented as objects
|
|
1367
|
-
*
|
|
1368
|
-
* @returns {string}
|
|
1369
|
-
*/
|
|
1370
|
-
function renderExprObject() {
|
|
1371
|
-
if (x.list) {
|
|
1372
|
-
// set "inExpr" to false: treat list elements as new expressions
|
|
1373
|
-
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
|
|
1374
|
-
}
|
|
1375
|
-
else if (x.val !== undefined) {
|
|
1376
|
-
// Literal value, possibly with explicit 'literal' property
|
|
1377
|
-
switch (x.literal || typeof x.val) {
|
|
1378
|
-
case 'number':
|
|
1379
|
-
case 'boolean':
|
|
1380
|
-
case 'null':
|
|
1381
|
-
return x.val;
|
|
1382
|
-
case 'x':
|
|
1383
|
-
case 'date':
|
|
1384
|
-
case 'time':
|
|
1385
|
-
case 'timestamp':
|
|
1386
|
-
return `${x.literal}'${x.val}'`;
|
|
1387
|
-
case 'string':
|
|
1388
|
-
return renderString(x.val, env);
|
|
1389
|
-
case 'object':
|
|
1390
|
-
if (x.val === null)
|
|
1391
|
-
return 'null';
|
|
1392
|
-
|
|
1393
|
-
// otherwise fall through to
|
|
1394
|
-
default:
|
|
1395
|
-
throw new Error(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
// Enum symbol
|
|
1399
|
-
else if (x['#']) {
|
|
1400
|
-
return `#${x['#']}`;
|
|
1401
|
-
}
|
|
1402
|
-
// Reference: Array of path steps, possibly preceded by ':'
|
|
1403
|
-
else if (x.ref) {
|
|
1404
|
-
// FIXME: no extra magic with x.param or x.global
|
|
1405
|
-
return `${(x.param || x.global) ? ':' : ''}${x.ref.map(renderPathStep).join('.')}`;
|
|
1406
|
-
}
|
|
1407
|
-
else if (x.xpr && x.func) {
|
|
1408
|
-
// window function
|
|
1409
|
-
const funcDef = renderFunc( x.func, x, null, a => renderArgs(a, '=>', env) );
|
|
1410
|
-
const windowFunctionOperator = x.xpr.shift(); // OVER ...
|
|
1411
|
-
return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
|
|
1412
|
-
}
|
|
1413
|
-
// Function call, possibly with args (use '=>' for named args)
|
|
1414
|
-
else if (x.func) {
|
|
1415
|
-
// test for non-regular HANA identifier that needs to be quoted
|
|
1416
|
-
// identifier {letter}({letter_or_digit}|[#$])*
|
|
1417
|
-
// letter [A-Za-z_]
|
|
1418
|
-
// letter_or_digit [A-Za-z_0-9]
|
|
1419
|
-
|
|
1420
|
-
const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
|
|
1421
|
-
const funcName = regex.test(x.func) ? x.func : quoteIdIfRequired(x.func);
|
|
1422
|
-
return renderFunc( funcName, x, 'cap', a => renderArgs(a, '=>', env) );
|
|
1423
|
-
}
|
|
1424
|
-
// Nested expression
|
|
1425
|
-
else if (x.xpr) {
|
|
1426
|
-
// Ensure `exists` is always enclosed by parentheses
|
|
1427
|
-
if ((inExpr && !x.cast ) || x.xpr.some(s => s === 'exists'))
|
|
1428
|
-
return `(${renderExpr(x.xpr, env, inline, true)})`;
|
|
1381
|
+
function renderPathStep(s, idx, inline, env) {
|
|
1382
|
+
// Simple id or absolute name
|
|
1383
|
+
if (typeof s === 'string') {
|
|
1384
|
+
// In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
|
|
1385
|
+
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1429
1386
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
else if (x.SELECT) {
|
|
1434
|
-
// renderQuery for SELECT does not bring its own parentheses (because it is also used in renderView)
|
|
1435
|
-
return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
|
|
1436
|
-
}
|
|
1437
|
-
else if (x.SET) {
|
|
1438
|
-
// renderQuery for SET always brings its own parentheses (because it is also used in renderViewSource)
|
|
1439
|
-
return `${renderQuery(x, false, 'view', increaseIndent(env))}`;
|
|
1440
|
-
}
|
|
1441
|
-
else {
|
|
1442
|
-
throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1387
|
+
if (idx === 0 &&
|
|
1388
|
+
s.startsWith('$'))
|
|
1389
|
+
return s;
|
|
1445
1390
|
|
|
1446
|
-
|
|
1447
|
-
* Renders an explicit `cast()` inside an 'xpr'.
|
|
1448
|
-
* @param {string} value
|
|
1449
|
-
* @returns {string}
|
|
1450
|
-
*/
|
|
1451
|
-
function renderExplicitTypeCast(value) {
|
|
1452
|
-
const typeRef = renderTypeReference(x.cast, env, true);
|
|
1453
|
-
return `cast(${value} as ${typeRef})`;
|
|
1391
|
+
return quoteIdIfRequired(s);
|
|
1454
1392
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
s.startsWith('$'))
|
|
1471
|
-
return s;
|
|
1472
|
-
|
|
1473
|
-
return quoteOrUppercaseId(s);
|
|
1393
|
+
// ID with filters or parameters
|
|
1394
|
+
else if (typeof s === 'object') {
|
|
1395
|
+
// Sanity check
|
|
1396
|
+
if (!s.func && !s.id)
|
|
1397
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1398
|
+
|
|
1399
|
+
// Not really a path step but an object-like function call
|
|
1400
|
+
if (s.func)
|
|
1401
|
+
return `${s.func}(${renderArgs(s, '=>', env)})`;
|
|
1402
|
+
|
|
1403
|
+
// Path step, possibly with view parameters and/or filters
|
|
1404
|
+
let result = `${quoteIdIfRequired(s.id)}`;
|
|
1405
|
+
if (s.args) {
|
|
1406
|
+
// View parameters
|
|
1407
|
+
result += `(${renderArgs(s, ':', env)})`;
|
|
1474
1408
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
if (!s.func && !s.id)
|
|
1479
|
-
throw new Error(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1480
|
-
|
|
1481
|
-
// Not really a path step but an object-like function call
|
|
1482
|
-
if (s.func)
|
|
1483
|
-
return `${s.func}(${renderArgs(s, '=>', env)})`;
|
|
1484
|
-
|
|
1485
|
-
// Path step, possibly with view parameters and/or filters
|
|
1486
|
-
let result = `${quoteOrUppercaseId(s.id)}`;
|
|
1487
|
-
if (s.args) {
|
|
1488
|
-
// View parameters
|
|
1489
|
-
result += `(${renderArgs(s, ':', env)})`;
|
|
1490
|
-
}
|
|
1491
|
-
if (s.where) {
|
|
1492
|
-
// Filter, possibly with cardinality
|
|
1493
|
-
result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
return result;
|
|
1409
|
+
if (s.where) {
|
|
1410
|
+
// Filter, possibly with cardinality
|
|
1411
|
+
result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
|
|
1497
1412
|
}
|
|
1498
1413
|
|
|
1499
|
-
|
|
1414
|
+
return result;
|
|
1500
1415
|
}
|
|
1416
|
+
|
|
1417
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1501
1418
|
}
|
|
1502
1419
|
|
|
1503
1420
|
/**
|
|
1504
1421
|
* Render function arguments or view parameters (positional if array, named if object/dict),
|
|
1505
|
-
* using 'sep' as separator for
|
|
1422
|
+
* using 'sep' as separator for named parameters
|
|
1506
1423
|
*
|
|
1507
1424
|
* @param {object} node with `args` to render
|
|
1508
1425
|
* @param {string} sep
|
|
@@ -1510,17 +1427,31 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1510
1427
|
* @returns {string}
|
|
1511
1428
|
*/
|
|
1512
1429
|
function renderArgs(node, sep, env) {
|
|
1513
|
-
const args = node.args
|
|
1430
|
+
const args = node.args || [];
|
|
1431
|
+
|
|
1514
1432
|
// Positional arguments
|
|
1515
1433
|
if (Array.isArray(args))
|
|
1516
|
-
return args.map(arg =>
|
|
1434
|
+
return args.map(arg => renderArgument(arg, env)).join(', ');
|
|
1517
1435
|
|
|
1518
1436
|
// Named arguments (object/dict)
|
|
1519
1437
|
else if (typeof args === 'object')
|
|
1520
|
-
return Object.keys(args).map(key => `${
|
|
1438
|
+
return Object.keys(args).map(key => `${quoteIdIfRequired(key)} ${sep} ${renderArgument(args[key], env)}`).join(', ');
|
|
1521
1439
|
|
|
1440
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1441
|
+
}
|
|
1522
1442
|
|
|
1523
|
-
|
|
1443
|
+
/**
|
|
1444
|
+
* Render a function argument, e.g. for generic functions or CAST().
|
|
1445
|
+
* Ensures that parentheses are used if necessary, e.g. for `someFct( (1=1), (1=1) )`.
|
|
1446
|
+
*
|
|
1447
|
+
* @param {any} arg
|
|
1448
|
+
* @param {CdlRenderEnvironment} env
|
|
1449
|
+
* @return {string}
|
|
1450
|
+
*/
|
|
1451
|
+
function renderArgument(arg, env) {
|
|
1452
|
+
// If the argument is a xpr with e.g. `=`, it may require parentheses.
|
|
1453
|
+
// For nested xpr, `renderExpr()` will already add parentheses.
|
|
1454
|
+
return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr), true);
|
|
1524
1455
|
}
|
|
1525
1456
|
|
|
1526
1457
|
/**
|
|
@@ -1601,28 +1532,35 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1601
1532
|
}
|
|
1602
1533
|
|
|
1603
1534
|
/**
|
|
1604
|
-
* Render (primitive) type parameters of
|
|
1535
|
+
* Render (primitive) type parameters of artifact 'artWithType', i.e.
|
|
1605
1536
|
* length, precision and scale (even if incomplete), plus any other unknown ones.
|
|
1606
1537
|
*
|
|
1607
|
-
* @param {CSN.
|
|
1538
|
+
* @param {CSN.Artifact} artWithType
|
|
1608
1539
|
* @returns {string}
|
|
1609
1540
|
*/
|
|
1610
|
-
function renderTypeParameters(
|
|
1611
|
-
const params = [];
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
params.push(elm.length);
|
|
1541
|
+
function renderTypeParameters(artWithType) {
|
|
1542
|
+
const params = typeParameters.list.filter(param => artWithType[param] !== undefined);
|
|
1543
|
+
if (params.length === 0)
|
|
1544
|
+
return '';
|
|
1615
1545
|
|
|
1616
|
-
|
|
1617
|
-
|
|
1546
|
+
// TODO(v3): Remove special handling of srid
|
|
1547
|
+
// For backwards compatibility, do not render srid as a named argument for POINT/GEOMETRY builtins.
|
|
1548
|
+
if (artWithType.srid !== undefined &&
|
|
1549
|
+
(artWithType.type === 'cds.hana.ST_POINT' || artWithType.type === 'cds.hana.ST_GEOMETRY'))
|
|
1550
|
+
return `(${artWithType.srid})`;
|
|
1618
1551
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1552
|
+
// Special cases for 1 or 2 arguments.
|
|
1553
|
+
if (params.length === 1 && artWithType.length !== undefined)
|
|
1554
|
+
return `(${artWithType.length})`;
|
|
1555
|
+
if (params.length === 2 && artWithType.precision !== undefined && artWithType.scale !== undefined)
|
|
1556
|
+
return `(${artWithType.precision}, ${artWithType.scale})`;
|
|
1621
1557
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1558
|
+
// Render named params
|
|
1559
|
+
const renderedParams = [];
|
|
1560
|
+
for (const param of params)
|
|
1561
|
+
renderedParams.push(`${param}: ${artWithType[param]}`);
|
|
1624
1562
|
|
|
1625
|
-
return
|
|
1563
|
+
return `(${renderedParams.join(', ')})`;
|
|
1626
1564
|
}
|
|
1627
1565
|
|
|
1628
1566
|
/**
|
|
@@ -1630,13 +1568,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1630
1568
|
*
|
|
1631
1569
|
* @param {object} obj Object that has annotations
|
|
1632
1570
|
* @param {CdlRenderEnvironment} env
|
|
1571
|
+
* @param {{parens: boolean}} [config] Config for renderAnnotationAssignment()
|
|
1633
1572
|
* @return {string}
|
|
1634
1573
|
*/
|
|
1635
|
-
function
|
|
1636
|
-
let result =
|
|
1574
|
+
function renderAnnotationAssignmentsAndDocComment(obj, env, config) {
|
|
1575
|
+
let result = renderDocComment(obj, env);
|
|
1637
1576
|
for (const name in obj) {
|
|
1638
1577
|
if (name.startsWith('@'))
|
|
1639
|
-
result += renderAnnotationAssignment(obj[name], name, env);
|
|
1578
|
+
result += renderAnnotationAssignment(obj[name], name, env, config);
|
|
1640
1579
|
}
|
|
1641
1580
|
return result;
|
|
1642
1581
|
}
|
|
@@ -1649,14 +1588,16 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1649
1588
|
* @param {any} anno Annotation value
|
|
1650
1589
|
* @param {string} name Annotation name, e.g. `@A.B.C#foo.C`
|
|
1651
1590
|
* @param {CdlRenderEnvironment} env
|
|
1591
|
+
* @param {object} [config] parens: Whether the annotation assignment must be surrounded by parentheses.
|
|
1652
1592
|
* @return {string} Rendered annotation, possibly quoted: `@![A.B.C#foo.C]: value`
|
|
1653
1593
|
*/
|
|
1654
|
-
function renderAnnotationAssignment(anno, name, env) {
|
|
1594
|
+
function renderAnnotationAssignment(anno, name, env, config = { parens: false }) {
|
|
1655
1595
|
name = name.substring(1);
|
|
1656
1596
|
// Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
|
|
1657
1597
|
const parts = name.split('#');
|
|
1658
1598
|
const nameBeforeVariant = parts[0];
|
|
1659
1599
|
const variant = parts[1];
|
|
1600
|
+
const { parens } = config;
|
|
1660
1601
|
|
|
1661
1602
|
// Identifier according to our grammar: /[$_a-zA-Z][$_a-zA-Z0-9]*/
|
|
1662
1603
|
// We expand this pattern to also include dots after the first character.
|
|
@@ -1667,151 +1608,21 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1667
1608
|
// even though that is the result after flattening.
|
|
1668
1609
|
const variantRequiresQuoting = variant && !/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(variant);
|
|
1669
1610
|
|
|
1670
|
-
let result
|
|
1611
|
+
let result = `${env.indent}@`;
|
|
1612
|
+
if (parens)
|
|
1613
|
+
result += '(';
|
|
1614
|
+
|
|
1671
1615
|
if (annoRequiresQuoting || variantRequiresQuoting)
|
|
1672
|
-
result
|
|
1616
|
+
result += quote(name);
|
|
1673
1617
|
else
|
|
1674
|
-
result
|
|
1618
|
+
result += name;
|
|
1675
1619
|
|
|
1676
1620
|
result += ` : ${renderAnnotationValue(anno, env)}`;
|
|
1621
|
+
if (parens)
|
|
1622
|
+
result += ')';
|
|
1677
1623
|
return `${result}\n`;
|
|
1678
1624
|
}
|
|
1679
1625
|
|
|
1680
|
-
/**
|
|
1681
|
-
* Render an absolute name 'absName', with appropriate quotes. Also record the
|
|
1682
|
-
* fact that 'absName' is used in 'env', so that an appropriate USING can be constructed
|
|
1683
|
-
* if necessary.
|
|
1684
|
-
*
|
|
1685
|
-
* @param {string} absName
|
|
1686
|
-
* @return {string}
|
|
1687
|
-
*/
|
|
1688
|
-
function renderAbsoluteNameWithQuotes(absName) {
|
|
1689
|
-
return absName;
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
/**
|
|
1693
|
-
* Render appropriate USING directives for all artifacts used by artifact 'artifactName' in 'env'.
|
|
1694
|
-
*
|
|
1695
|
-
* @param {string} artifactName
|
|
1696
|
-
* @param {CdlRenderEnvironment} env
|
|
1697
|
-
* @return {string}
|
|
1698
|
-
*/
|
|
1699
|
-
function renderUsings(artifactName, env) {
|
|
1700
|
-
const distinct = {};
|
|
1701
|
-
Object.keys(env.topLevelAliases)
|
|
1702
|
-
.forEach((name) => {
|
|
1703
|
-
distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
|
|
1704
|
-
});
|
|
1705
|
-
|
|
1706
|
-
return Object.keys(distinct).join('');
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
/**
|
|
1710
|
-
* Returns a newly created default environment (which keeps track of indentation, required USING
|
|
1711
|
-
* declarations and name prefixes.
|
|
1712
|
-
*
|
|
1713
|
-
* @return {CdlRenderEnvironment}
|
|
1714
|
-
*/
|
|
1715
|
-
function createEnv() {
|
|
1716
|
-
return {
|
|
1717
|
-
// Current indentation string
|
|
1718
|
-
indent: '',
|
|
1719
|
-
// Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
|
|
1720
|
-
topLevelAliases: Object.create(null),
|
|
1721
|
-
// Current name prefix (including trailing dot if not empty)
|
|
1722
|
-
namePrefix: '',
|
|
1723
|
-
artifactName: null,
|
|
1724
|
-
elementName: null,
|
|
1725
|
-
};
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
/**
|
|
1729
|
-
* Returns a copy of 'env' with (quoted) name prefix 'id' and a dot appended to the current name prefix
|
|
1730
|
-
*
|
|
1731
|
-
* @param {CdlRenderEnvironment} env
|
|
1732
|
-
* @param {string} id
|
|
1733
|
-
* @returns {CdlRenderEnvironment}
|
|
1734
|
-
*/
|
|
1735
|
-
function addNamePrefix(env, id) {
|
|
1736
|
-
return Object.assign({}, env, { namePrefix: `${env.namePrefix + quoteIdIfRequired(id)}.` });
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
/**
|
|
1740
|
-
* Returns a copy of 'env' with increased indentation (and resetted name prefix)
|
|
1741
|
-
*
|
|
1742
|
-
* @param {CdlRenderEnvironment} env
|
|
1743
|
-
* @returns {CdlRenderEnvironment}
|
|
1744
|
-
*/
|
|
1745
|
-
function increaseIndent(env) {
|
|
1746
|
-
return Object.assign({}, env, { namePrefix: '', indent: `${env.indent} ` });
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
/**
|
|
1750
|
-
* Return a path string 'path' with appropriate "-quotes.
|
|
1751
|
-
*
|
|
1752
|
-
* @param {string} path
|
|
1753
|
-
* @returns {string}
|
|
1754
|
-
*/
|
|
1755
|
-
function quotePathString(path) {
|
|
1756
|
-
// "foo"."bar"."wiz"."blub"
|
|
1757
|
-
return path.split('.').map(quoteIdIfRequired).join('.');
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
/**
|
|
1761
|
-
* Return an id 'id' with appropriate "-quotes
|
|
1762
|
-
*
|
|
1763
|
-
* @param {string} id
|
|
1764
|
-
* @return {string}
|
|
1765
|
-
*/
|
|
1766
|
-
function quoteIdIfRequired(id) {
|
|
1767
|
-
// Quote if required for CDL
|
|
1768
|
-
if (requiresQuotingForCdl(id))
|
|
1769
|
-
return quote(id);
|
|
1770
|
-
|
|
1771
|
-
return id;
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
/**
|
|
1775
|
-
* Quotes the identifier using CDL-style ![]-quotes.
|
|
1776
|
-
*
|
|
1777
|
-
* @param id
|
|
1778
|
-
* @returns {string}
|
|
1779
|
-
*/
|
|
1780
|
-
function quote(id) {
|
|
1781
|
-
return `![${id.replace(/]/g, ']]')}]`;
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
/**
|
|
1785
|
-
* Returns true if 'id' requires quotes for CDL, i.e. if 'id'
|
|
1786
|
-
* 1. starts with a digit
|
|
1787
|
-
* 2. contains chars different than:
|
|
1788
|
-
* - uppercase letters
|
|
1789
|
-
* - lowercase letters
|
|
1790
|
-
* - digits
|
|
1791
|
-
* - underscore
|
|
1792
|
-
* 3. is a CDL keyword or a CDL function without parentheses (CURRENT_*, SYSUUID, ...)
|
|
1793
|
-
*
|
|
1794
|
-
* @param {string} id
|
|
1795
|
-
* @return {boolean}
|
|
1796
|
-
*/
|
|
1797
|
-
function requiresQuotingForCdl(id) {
|
|
1798
|
-
return /^\d/.test(id) ||
|
|
1799
|
-
/\W/g.test(id.replace(/\./g, '')) ||
|
|
1800
|
-
keywords.cdl.includes(id.toUpperCase()) ||
|
|
1801
|
-
keywords.cdl_functions.includes(id.toUpperCase());
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
/**
|
|
1805
|
-
* Quote or uppercase an identifier 'id', depending on naming strategy
|
|
1806
|
-
*
|
|
1807
|
-
* @todo Remove: Now part of toHdbcds.js
|
|
1808
|
-
* @param {string} id
|
|
1809
|
-
* @return {string}
|
|
1810
|
-
*/
|
|
1811
|
-
function quoteOrUppercaseId(id) {
|
|
1812
|
-
return quoteIdIfRequired(id);
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
1626
|
/**
|
|
1816
1627
|
* Render the name of an artifact, using the current name prefix from 'env'
|
|
1817
1628
|
* and the real name of the artifact. In case of plain names, this
|
|
@@ -1821,12 +1632,11 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1821
1632
|
* of another entity -> Service.E and Service.E.Sub
|
|
1822
1633
|
*
|
|
1823
1634
|
* To handle such cases for hdbcds in quoted/hdbcds, we:
|
|
1824
|
-
* - Find the part of the name that is no longer prefix (context/service
|
|
1635
|
+
* - Find the part of the name that is no longer prefix (context/service)
|
|
1825
1636
|
* - For Service.E -> E, for Service.E.Sub -> E.Sub
|
|
1826
1637
|
* - Replace all dots in this "real name" with underscores
|
|
1827
1638
|
* - Join with the env prefix
|
|
1828
1639
|
*
|
|
1829
|
-
*
|
|
1830
1640
|
* @param {string} artifactName Artifact name to render
|
|
1831
1641
|
* @return {string} Artifact name ready for rendering
|
|
1832
1642
|
*/
|
|
@@ -1837,25 +1647,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1837
1647
|
}
|
|
1838
1648
|
|
|
1839
1649
|
/**
|
|
1840
|
-
* Get the
|
|
1841
|
-
* Without quoting/escaping stuff.
|
|
1842
|
-
*
|
|
1843
|
-
* @param {String} artifactName Artifact name to use
|
|
1844
|
-
* @returns {String}
|
|
1845
|
-
*/
|
|
1846
|
-
function getResultingName(artifactName) {
|
|
1847
|
-
return renderArtifactName(artifactName);
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
/**
|
|
1851
|
-
* Get the part that is really the name of this artifact and not just prefix caused by a context/service/namespace
|
|
1650
|
+
* Get the part that is really the name of this artifact and not just prefix caused by a context/service
|
|
1852
1651
|
*
|
|
1853
1652
|
* @param {String} artifactName Artifact name to use
|
|
1854
1653
|
* @returns {String} non-prefix part of the artifact name
|
|
1855
1654
|
*/
|
|
1856
1655
|
function getRealName(artifactName) {
|
|
1857
1656
|
const parts = artifactName.split('.');
|
|
1858
|
-
//
|
|
1657
|
+
// Length of 1 -> There can be no prefix
|
|
1859
1658
|
if (parts.length === 1)
|
|
1860
1659
|
return artifactName;
|
|
1861
1660
|
|
|
@@ -1869,7 +1668,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1869
1668
|
|
|
1870
1669
|
|
|
1871
1670
|
const art = csn.definitions[seen];
|
|
1872
|
-
if (!art ||
|
|
1671
|
+
if (!art || (art.kind !== 'service' && art.kind !== 'context')) {
|
|
1873
1672
|
// We found a case where the prefix ended
|
|
1874
1673
|
// Return everything following
|
|
1875
1674
|
return parts.slice(i).join('.');
|
|
@@ -1879,15 +1678,218 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1879
1678
|
// we seem to have a normal case - just return the last part
|
|
1880
1679
|
return getLastPartOf(artifactName);
|
|
1881
1680
|
}
|
|
1681
|
+
|
|
1682
|
+
/**
|
|
1683
|
+
* Render an expression.
|
|
1684
|
+
*
|
|
1685
|
+
* @param {any} expr
|
|
1686
|
+
* @param {CdlRenderEnvironment} env
|
|
1687
|
+
* @param {boolean} [inline]
|
|
1688
|
+
* @param {boolean} [nestedExpr]
|
|
1689
|
+
* @param {boolean} [alwaysRenderCast]
|
|
1690
|
+
* @returns {string}
|
|
1691
|
+
*/
|
|
1692
|
+
function renderExpr(expr, env, inline, nestedExpr, alwaysRenderCast) {
|
|
1693
|
+
if (!_renderExpr) {
|
|
1694
|
+
_renderExpr = getExpressionRenderer({
|
|
1695
|
+
finalize: x => x,
|
|
1696
|
+
explicitTypeCast: (x, env) => {
|
|
1697
|
+
const typeRef = renderTypeReference(x.cast, env, true);
|
|
1698
|
+
const arg = { ...x, cast: null }; // "arg" without cast to avoid recursion.
|
|
1699
|
+
return `cast(${renderArgument(arg, env)} as ${typeRef})`;
|
|
1700
|
+
},
|
|
1701
|
+
val(x, env) {
|
|
1702
|
+
// Literal value, possibly with explicit 'literal' property
|
|
1703
|
+
switch (x.literal || typeof x.val) {
|
|
1704
|
+
case 'number':
|
|
1705
|
+
case 'boolean':
|
|
1706
|
+
case 'null':
|
|
1707
|
+
return x.val;
|
|
1708
|
+
case 'x':
|
|
1709
|
+
case 'date':
|
|
1710
|
+
case 'time':
|
|
1711
|
+
case 'timestamp':
|
|
1712
|
+
return `${x.literal}'${x.val}'`;
|
|
1713
|
+
case 'string':
|
|
1714
|
+
return renderString(x.val, env);
|
|
1715
|
+
case 'object':
|
|
1716
|
+
if (x.val === null)
|
|
1717
|
+
return 'null';
|
|
1718
|
+
// otherwise fall through to
|
|
1719
|
+
default:
|
|
1720
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1721
|
+
}
|
|
1722
|
+
},
|
|
1723
|
+
aliasOnly(x, _env) {
|
|
1724
|
+
return x.as;
|
|
1725
|
+
},
|
|
1726
|
+
enum: x => `#${x['#']}`,
|
|
1727
|
+
ref(x, env) {
|
|
1728
|
+
const { inline } = this;
|
|
1729
|
+
return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, inline, env)).join('.')}`;
|
|
1730
|
+
},
|
|
1731
|
+
windowFunction: (x, env) => {
|
|
1732
|
+
const funcDef = renderFunc(x.func, x, null, a => renderArgs(a, '=>', env));
|
|
1733
|
+
const windowFunctionOperator = x.xpr.shift(); // OVER ...
|
|
1734
|
+
return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
|
|
1735
|
+
},
|
|
1736
|
+
func: (x, env) => {
|
|
1737
|
+
// FIXME: Why care about HANA identifier?
|
|
1738
|
+
// test for non-regular HANA identifier that needs to be quoted
|
|
1739
|
+
// identifier {letter}({letter_or_digit}|[#$])*
|
|
1740
|
+
// letter [A-Za-z_]
|
|
1741
|
+
// letter_or_digit [A-Za-z_0-9]
|
|
1742
|
+
|
|
1743
|
+
const regex = /^[a-zA-Z][\w#$]*$/g;
|
|
1744
|
+
const funcName = regex.test(x.func) ? x.func : quoteIdIfRequired(x.func);
|
|
1745
|
+
return renderFunc(funcName, x, 'cap', a => renderArgs(a, '=>', env));
|
|
1746
|
+
},
|
|
1747
|
+
xpr(x, env) {
|
|
1748
|
+
if (this.nestedExpr && !x.cast || x.xpr.some(s => s === 'exists'))
|
|
1749
|
+
return `(${renderExpr(x.xpr, env, this.inline, true)})`;
|
|
1750
|
+
|
|
1751
|
+
return renderExpr(x.xpr, env, this.inline, true);
|
|
1752
|
+
},
|
|
1753
|
+
// Sub-queries in expressions need to be in parentheses, otherwise
|
|
1754
|
+
// left-associativity of UNIONS may result in different results.
|
|
1755
|
+
// For example: `select from E where id in (select from E union select from E);`:
|
|
1756
|
+
// Without parentheses, it would be different query.
|
|
1757
|
+
SET: (x, env) => `(${renderQuery(x, false, 'view', increaseIndent(env))})`,
|
|
1758
|
+
SELECT: (x, env) => `(${renderQuery(x, false, 'view', increaseIndent(env))})`,
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
return _renderExpr(expr, env, inline, nestedExpr, alwaysRenderCast);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
/**
|
|
1766
|
+
* Returns a newly created default environment (which keeps track of indentation, required USING
|
|
1767
|
+
* declarations and name prefixes.
|
|
1768
|
+
*
|
|
1769
|
+
* @return {CdlRenderEnvironment}
|
|
1770
|
+
*/
|
|
1771
|
+
function createEnv() {
|
|
1772
|
+
return {
|
|
1773
|
+
// Current indentation string
|
|
1774
|
+
indent: '',
|
|
1775
|
+
// Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
|
|
1776
|
+
topLevelAliases: Object.create(null),
|
|
1777
|
+
// Current name prefix (including trailing dot if not empty)
|
|
1778
|
+
namePrefix: '',
|
|
1779
|
+
artifactName: '',
|
|
1780
|
+
elementName: '',
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* Returns a copy of 'env' with increased indentation (and reset name prefix)
|
|
1786
|
+
*
|
|
1787
|
+
* @param {CdlRenderEnvironment} env
|
|
1788
|
+
* @returns {CdlRenderEnvironment}
|
|
1789
|
+
*/
|
|
1790
|
+
function increaseIndent(env) {
|
|
1791
|
+
return Object.assign({}, env, { namePrefix: '', indent: `${env.indent} ` });
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Return a path string 'path' with appropriate "-quotes.
|
|
1796
|
+
*
|
|
1797
|
+
* @param {string} path
|
|
1798
|
+
* @returns {string}
|
|
1799
|
+
*/
|
|
1800
|
+
function quotePathString(path) {
|
|
1801
|
+
// "foo"."bar"."wiz"."blub"
|
|
1802
|
+
return path.split('.').map(quoteIdIfRequired).join('.');
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
/**
|
|
1806
|
+
* Return an id 'id' with appropriate "-quotes
|
|
1807
|
+
*
|
|
1808
|
+
* @param {string} id
|
|
1809
|
+
* @return {string}
|
|
1810
|
+
*/
|
|
1811
|
+
function quoteIdIfRequired(id) {
|
|
1812
|
+
// Quote if required for CDL
|
|
1813
|
+
if (requiresQuotingForCdl(id))
|
|
1814
|
+
return quote(id);
|
|
1815
|
+
|
|
1816
|
+
return id;
|
|
1882
1817
|
}
|
|
1883
1818
|
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1819
|
+
/**
|
|
1820
|
+
* Quotes the identifier using CDL-style ![]-quotes.
|
|
1821
|
+
*
|
|
1822
|
+
* @param id
|
|
1823
|
+
* @returns {string}
|
|
1824
|
+
*/
|
|
1825
|
+
function quote(id) {
|
|
1826
|
+
return `![${id.replace(/]/g, ']]')}]`;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* Returns true if 'id' requires quotes for CDL, i.e. if 'id'
|
|
1831
|
+
* 1. starts with a digit
|
|
1832
|
+
* 2. contains chars different than:
|
|
1833
|
+
* - uppercase letters
|
|
1834
|
+
* - lowercase letters
|
|
1835
|
+
* - digits
|
|
1836
|
+
* - underscore
|
|
1837
|
+
* 3. is a CDL keyword or a CDL function without parentheses (CURRENT_*, SYSUUID, ...)
|
|
1838
|
+
*
|
|
1839
|
+
* @param {string} id
|
|
1840
|
+
* @return {boolean}
|
|
1841
|
+
*/
|
|
1842
|
+
function requiresQuotingForCdl(id) {
|
|
1843
|
+
return /^\d/.test(id) ||
|
|
1844
|
+
/\W/g.test(id.replace(/\./g, '')) ||
|
|
1845
|
+
keywords.cdl.includes(id.toUpperCase()) ||
|
|
1846
|
+
keywords.cdl_functions.includes(id.toUpperCase());
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
const functionExpressionOperatorsRequireParentheses = [
|
|
1850
|
+
// Antlr rule 'condition', 'conditionAnd'
|
|
1851
|
+
'and', 'or',
|
|
1852
|
+
|
|
1853
|
+
// Antlr rule 'conditionTerm'
|
|
1854
|
+
'=', '<>', '>', '>=', '<', '<=', '!=',
|
|
1855
|
+
// These are not forbidden, since they must be preceded by one of the comparators above.
|
|
1856
|
+
// 'any', 'some', 'all',
|
|
1857
|
+
|
|
1858
|
+
'is', 'in', 'not', 'null', 'exists',
|
|
1859
|
+
// Antlr rule 'predicate'
|
|
1860
|
+
'between', 'like', 'escape',
|
|
1861
|
+
];
|
|
1862
|
+
|
|
1863
|
+
/**
|
|
1864
|
+
* Returns true if the given xpr-array can be rendered without parentheses
|
|
1865
|
+
* in a `fct(<xpr>)` expression such as `cast(<xpr> as Type)`. We only need to
|
|
1866
|
+
* look at the first nesting level. Otherwise, `renderExpr()` will already add parentheses.
|
|
1867
|
+
*
|
|
1868
|
+
* The list was created by looking at the `expression` Antlr rule.
|
|
1869
|
+
*
|
|
1870
|
+
* This is more of a heuristic for "nicer" CDL output. For example the
|
|
1871
|
+
* following snippet is parsable without parentheses:
|
|
1872
|
+
* `cast( case when int > 1 then int else 0 end as Integer ),`
|
|
1873
|
+
* However, because it is a flat xpr-array, we see `>` and assume that it is not a simple expression.
|
|
1874
|
+
*
|
|
1875
|
+
* @param {any[]} xpr
|
|
1876
|
+
* @return {boolean}
|
|
1877
|
+
*/
|
|
1878
|
+
function isSimpleFunctionExpression(xpr) {
|
|
1879
|
+
return !xpr || xpr.every(val => typeof val !== 'string' || !functionExpressionOperatorsRequireParentheses.includes(val.toLowerCase()));
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
/**
|
|
1883
|
+
* @param {string[]} includes
|
|
1884
|
+
* @return {string}
|
|
1885
|
+
*/
|
|
1886
|
+
function renderIncludes(includes) {
|
|
1887
|
+
return ` : ${includes.map(name => quoteIdIfRequired(name)).join(', ')}`;
|
|
1888
|
+
}
|
|
1887
1889
|
|
|
1888
1890
|
/**
|
|
1889
1891
|
* Render the given string. Uses back-tick strings.
|
|
1890
|
-
* env is used for indentation
|
|
1892
|
+
* env is used for indentation of three-back-tick strings.
|
|
1891
1893
|
*
|
|
1892
1894
|
* @param str
|
|
1893
1895
|
* @param env
|
|
@@ -1897,90 +1899,33 @@ function renderString(str, env) {
|
|
|
1897
1899
|
if (isSimpleString(str))
|
|
1898
1900
|
return `'${str.replace(/'/g, '\'\'')}'`;
|
|
1899
1901
|
|
|
1900
|
-
const output = [];
|
|
1901
|
-
|
|
1902
1902
|
// We try to work similar to how JavaScript implements JSON.stringify.
|
|
1903
|
-
// JSON.stringify
|
|
1904
|
-
// <https://tc39.es/ecma262/#sec-quotejsonstring>)
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
output.push('\\t');
|
|
1929
|
-
break;
|
|
1930
|
-
case '\b':
|
|
1931
|
-
output.push('\\b');
|
|
1932
|
-
break;
|
|
1933
|
-
// If CR, LS, or PS appear, they have been encoded explicitly. If we don't escape
|
|
1934
|
-
// them, a recompilation may yield different results because of newline normalization.
|
|
1935
|
-
case '\r':
|
|
1936
|
-
output.push('\\r');
|
|
1937
|
-
break;
|
|
1938
|
-
case '\u{2028}':
|
|
1939
|
-
output.push('\\u{2028}');
|
|
1940
|
-
break;
|
|
1941
|
-
case '\u{2029}':
|
|
1942
|
-
output.push('\\u{2029}');
|
|
1943
|
-
break;
|
|
1944
|
-
default: {
|
|
1945
|
-
// Control Characters: C0
|
|
1946
|
-
// See <https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Basic_ASCII_control_codes>
|
|
1947
|
-
//
|
|
1948
|
-
// JSON.stringify is not required to escape all control characters, but if used, files may
|
|
1949
|
-
// be interpreted as binary. Therefore, we replace them.
|
|
1950
|
-
//
|
|
1951
|
-
// We exclude LF from this list (\u000A). Characters with "nice" escapes have been replaced above.
|
|
1952
|
-
if (controlCharacters.test(char)) {
|
|
1953
|
-
const hex = char.codePointAt(0).toString(16);
|
|
1954
|
-
output.push(`\\u{${hex}}`);
|
|
1955
|
-
break;
|
|
1956
|
-
}
|
|
1903
|
+
// JSON.stringify() also checks for unpaired unicode surrogates (see §25.5.2.2,
|
|
1904
|
+
// <https://tc39.es/ecma262/#sec-quotejsonstring>).
|
|
1905
|
+
str = escapeString(str, {
|
|
1906
|
+
$: '\\$',
|
|
1907
|
+
'`': '\\`',
|
|
1908
|
+
'\\': '\\\\',
|
|
1909
|
+
// Replace commonly known escape sequences for control characters
|
|
1910
|
+
// See lib/language/multiLineStringParser.js
|
|
1911
|
+
'\f': '\\f',
|
|
1912
|
+
'\v': '\\v',
|
|
1913
|
+
'\t': '\\t',
|
|
1914
|
+
'\b': '\\b',
|
|
1915
|
+
// If CR, LS, or PS appear, they have been encoded explicitly. If we don't escape
|
|
1916
|
+
// them, a recompilation may yield different results because of newline normalization.
|
|
1917
|
+
'\r': '\\r',
|
|
1918
|
+
'\u{2028}': '\\u{2028}',
|
|
1919
|
+
'\u{2029}': '\\u{2029}',
|
|
1920
|
+
// Don't encode LF
|
|
1921
|
+
'\n': '\n',
|
|
1922
|
+
// JSON.stringify() is not required to escape all control characters, but if used, files may
|
|
1923
|
+
// be interpreted as binary. Therefore, we replace them.
|
|
1924
|
+
// We exclude LF from this list (\n). Characters with "nice" escapes have been replaced above.
|
|
1925
|
+
control: hexEscape,
|
|
1926
|
+
unpairedSurrogate: hexEscape,
|
|
1927
|
+
});
|
|
1957
1928
|
|
|
1958
|
-
// Unicode Surrogates
|
|
1959
|
-
// These characters appear in _pairs_. A high surrogate must be followed by a low surrogate.
|
|
1960
|
-
// If this is not the case, either needs to be encoded. This is also done by JSON.
|
|
1961
|
-
// See also <https://docs.microsoft.com/en-us/globalization/encoding/surrogate-pairs>
|
|
1962
|
-
if (highSurrogate.test(char)) {
|
|
1963
|
-
if (i + 1 >= str.length || !lowSurrogate.test(str[i + 1])) {
|
|
1964
|
-
const hex = char.codePointAt(0).toString(16);
|
|
1965
|
-
output.push(`\\u{${hex}}`);
|
|
1966
|
-
}
|
|
1967
|
-
else {
|
|
1968
|
-
output.push(char);
|
|
1969
|
-
++i;
|
|
1970
|
-
output.push(str[i]);
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
else if (lowSurrogate.test(char)) {
|
|
1974
|
-
const hex = char.codePointAt(0).toString(16);
|
|
1975
|
-
output.push(`\\u{${hex}}`);
|
|
1976
|
-
}
|
|
1977
|
-
else {
|
|
1978
|
-
output.push(char);
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
str = output.join('');
|
|
1984
1929
|
// Note: String is normalized, only \n is the line separator.
|
|
1985
1930
|
const lines = str.split('\n');
|
|
1986
1931
|
// We don't know whether a text block was used or not. But if there
|
|
@@ -1994,6 +1939,12 @@ function renderString(str, env) {
|
|
|
1994
1939
|
return `\`${str}\``;
|
|
1995
1940
|
}
|
|
1996
1941
|
|
|
1942
|
+
/** @param {number} codePoint */
|
|
1943
|
+
function hexEscape(codePoint) {
|
|
1944
|
+
const hex = codePoint.toString(16);
|
|
1945
|
+
return `\\u{${hex}}`;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1997
1948
|
/**
|
|
1998
1949
|
* Returns true if the given string can be represented by using single quotes.
|
|
1999
1950
|
* @param {string} str
|
|
@@ -2001,14 +1952,13 @@ function renderString(str, env) {
|
|
|
2001
1952
|
function isSimpleString(str) {
|
|
2002
1953
|
// A single-line string allows everything except certain line separators/breaks.
|
|
2003
1954
|
// See ANTLR grammar for specifics.
|
|
2004
|
-
// Furthermore, if control characters
|
|
1955
|
+
// Furthermore, if control characters are used, we escape them,
|
|
2005
1956
|
// as these are explicitly mentioned in the JSON spec (§9):
|
|
2006
1957
|
// <https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf>
|
|
2007
|
-
// On top,
|
|
1958
|
+
// On top, because (invalid) surrogate pairs need to be handled, we check for them as well.
|
|
2008
1959
|
// v3: Not a simple string if ' (\u0027) is in string.
|
|
2009
1960
|
return str === '' || (/^[^\u{0000}-\u{001F}\u2028\u2029]+$/u.test(str) &&
|
|
2010
|
-
!
|
|
2011
|
-
!lowSurrogate.test(str));
|
|
1961
|
+
!hasUnpairedUnicodeSurrogate(str));
|
|
2012
1962
|
}
|
|
2013
1963
|
|
|
2014
1964
|
/**
|
|
@@ -2016,14 +1966,14 @@ function isSimpleString(str) {
|
|
|
2016
1966
|
*
|
|
2017
1967
|
* @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
|
|
2018
1968
|
* @property {CSN.Path} [path] CSN path to the current artifact
|
|
2019
|
-
* @property {string} artifactName Name of the artifact - set in renderArtifact
|
|
2020
|
-
* @property {string} elementName Name of the element being rendered - set in renderElement
|
|
1969
|
+
* @property {string} [artifactName] Name of the artifact - set in renderArtifact
|
|
1970
|
+
* @property {string} [elementName] Name of the element being rendered - set in renderElement
|
|
2021
1971
|
* @property {{[name: string]: {
|
|
2022
1972
|
quotedName: string,
|
|
2023
1973
|
quotedAlias: string
|
|
2024
1974
|
}}} topLevelAliases Dictionary of aliases for used artifact names
|
|
2025
1975
|
*
|
|
2026
|
-
* @property {string} namePrefix Current name prefix (including trailing dot if not empty)
|
|
1976
|
+
* @property {string} [namePrefix] Current name prefix (including trailing dot if not empty)
|
|
2027
1977
|
* @property {boolean} [skipKeys]
|
|
2028
1978
|
* @property {CSN.Artifact} [_artifact]
|
|
2029
1979
|
*/
|