@sap/cds-compiler 6.9.2 → 7.0.1
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 +86 -2
- package/bin/cdsc.js +4 -33
- package/doc/IncompatibleChanges_v7.md +639 -0
- package/lib/api/main.js +4 -56
- package/lib/api/options.js +6 -15
- package/lib/api/validate.js +1 -0
- package/lib/base/builtins.js +1 -2
- package/lib/base/csnRefs.js +2 -6
- package/lib/base/message-registry.js +82 -76
- package/lib/base/messages.js +23 -4
- package/lib/base/optionProcessor.js +2 -72
- package/lib/base/specialOptions.js +20 -17
- package/lib/checks/defaultValues.js +1 -39
- package/lib/checks/hasPersistedElements.js +19 -3
- package/lib/checks/parameters.js +0 -34
- package/lib/checks/selectItems.js +2 -38
- package/lib/checks/typeParameters.js +162 -0
- package/lib/checks/validator.js +5 -8
- package/lib/compiler/assert-consistency.js +19 -5
- package/lib/compiler/checks.js +47 -43
- package/lib/compiler/define.js +6 -6
- package/lib/compiler/extend.js +102 -111
- package/lib/compiler/generate.js +4 -8
- package/lib/compiler/populate.js +4 -7
- package/lib/compiler/propagator.js +9 -9
- package/lib/compiler/resolve.js +205 -7
- package/lib/compiler/shared.js +76 -82
- package/lib/compiler/tweak-assocs.js +102 -22
- package/lib/compiler/utils.js +57 -12
- package/lib/compiler/xpr-rewrite.js +2 -15
- package/lib/edm/annotations/edmJson.js +14 -10
- package/lib/edm/annotations/genericTranslation.js +3 -1
- package/lib/edm/annotations/preprocessAnnotations.js +9 -26
- package/lib/edm/csn2edm.js +27 -20
- package/lib/edm/edmUtils.js +25 -0
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +2237 -2241
- package/lib/gen/Dictionary.json +17 -2
- package/lib/json/from-csn.js +67 -52
- package/lib/json/to-csn.js +28 -25
- package/lib/language/textUtils.js +0 -13
- package/lib/main.d.ts +34 -59
- package/lib/main.js +1 -1
- package/lib/model/csnUtils.js +9 -8
- package/lib/parsers/AstBuildingParser.js +45 -55
- package/lib/parsers/Lexer.js +2 -0
- package/lib/parsers/identifiers.js +0 -9
- package/lib/render/toCdl.js +41 -40
- package/lib/render/toSql.js +8 -1
- package/lib/render/utils/common.js +1 -1
- package/lib/render/utils/sql.js +2 -3
- package/lib/tool-lib/enrichCsn.js +1 -2
- package/lib/transform/db/applyTransformations.js +7 -5
- package/lib/transform/db/assertUnique.js +8 -51
- package/lib/transform/db/associations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -15
- package/lib/transform/db/expansion.js +9 -12
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/groupByOrderBy.js +0 -16
- package/lib/transform/db/views.js +57 -161
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdata.js +25 -14
- package/lib/transform/forRelationalDB.js +93 -301
- package/lib/transform/localized.js +33 -102
- package/lib/transform/odata/flattening.js +11 -2
- package/lib/transform/transformUtils.js +25 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
- package/package.json +2 -2
- package/lib/render/toHdbcds.js +0 -1810
package/lib/render/toHdbcds.js
DELETED
|
@@ -1,1810 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
getLastPartOf, getLastPartOfRef,
|
|
5
|
-
hasValidSkipOrExists, getNormalizedQuery,
|
|
6
|
-
getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement,
|
|
7
|
-
pathName, hasPersistenceSkipAnnotation, implicitAs, forEachDefinition,
|
|
8
|
-
} = require('../model/csnUtils');
|
|
9
|
-
const { isBuiltinType, isMagicVariable } = require('../base/builtins');
|
|
10
|
-
const keywords = require('../base/keywords');
|
|
11
|
-
const {
|
|
12
|
-
renderFunc, createExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts,
|
|
13
|
-
hasHanaComment, getHanaComment, funcWithoutParen, getSqlSnippets,
|
|
14
|
-
cdsToSqlTypes, cdsToHdbcdsTypes, withoutCast, variableForDialect,
|
|
15
|
-
isVariableReplacementRequired,
|
|
16
|
-
} = require('./utils/common');
|
|
17
|
-
const {
|
|
18
|
-
renderReferentialConstraint,
|
|
19
|
-
} = require('./utils/sql');
|
|
20
|
-
const DuplicateChecker = require('./DuplicateChecker');
|
|
21
|
-
const { isDeprecatedEnabled } = require('../base/specialOptions');
|
|
22
|
-
const { checkCSNVersion } = require('../json/csnVersion');
|
|
23
|
-
const { timetrace } = require('../utils/timetrace');
|
|
24
|
-
|
|
25
|
-
const { smartId, delimitedId } = require('../sql-identifier');
|
|
26
|
-
const { ModelError, CompilerAssertion } = require('../base/error');
|
|
27
|
-
const { pathId } = require('../base/csnRefs');
|
|
28
|
-
|
|
29
|
-
const $PROJECTION = '$projection';
|
|
30
|
-
const $SELF = '$self';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// TODO: Unify with other RenderEnvironments
|
|
34
|
-
class HdbcdsRenderEnvironment {
|
|
35
|
-
indent = '';
|
|
36
|
-
path = null;
|
|
37
|
-
/**
|
|
38
|
-
* Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
|
|
39
|
-
* @type {{[name: string]: {
|
|
40
|
-
* quotedName: string,
|
|
41
|
-
* quotedAlias: string
|
|
42
|
-
* }}}
|
|
43
|
-
*/
|
|
44
|
-
topLevelAliases = Object.create(null);
|
|
45
|
-
// Current name prefix (including trailing dot if not empty)
|
|
46
|
-
namePrefix = '';
|
|
47
|
-
// Skip rendering keys in subqueries
|
|
48
|
-
skipKeys = false;
|
|
49
|
-
currentArtifactName = null;
|
|
50
|
-
// The original view artifact, used when rendering queries
|
|
51
|
-
_artifact = null;
|
|
52
|
-
|
|
53
|
-
constructor(values) {
|
|
54
|
-
Object.assign(this, values);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
withIncreasedIndent() {
|
|
58
|
-
return new HdbcdsRenderEnvironment({ ...this, namePrefix: '', indent: ` ${ this.indent }` });
|
|
59
|
-
}
|
|
60
|
-
withSubPath(path) {
|
|
61
|
-
return new HdbcdsRenderEnvironment({ ...this, path: [ ...this.path, ...path ] });
|
|
62
|
-
}
|
|
63
|
-
cloneWith(values) {
|
|
64
|
-
return Object.assign(new HdbcdsRenderEnvironment(this), values);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get the comment and in addition escape \n and `'` so SAP HANA CDS can handle it.
|
|
70
|
-
*
|
|
71
|
-
* @param {CSN.Artifact} obj
|
|
72
|
-
* @returns {string}
|
|
73
|
-
*/
|
|
74
|
-
function getEscapedHanaComment( obj ) {
|
|
75
|
-
return getHanaComment(obj)
|
|
76
|
-
.replace(/\n/g, '\\n')
|
|
77
|
-
.replace(/'/g, "''");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Render a string for HDBCDS, i.e. put it in quotes and escape single quotes.
|
|
82
|
-
*
|
|
83
|
-
* @param {string} str
|
|
84
|
-
* @returns {string}
|
|
85
|
-
*/
|
|
86
|
-
function renderStringForHdbcds( str ) {
|
|
87
|
-
return `'${ str.replace(/'/g, '\'\'') }'`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Render the CSN model 'model' to CDS source text.
|
|
92
|
-
*
|
|
93
|
-
* @param {CSN.Model} csn HANA transformed CSN
|
|
94
|
-
* @param {CSN.Options} [options] Transformation options
|
|
95
|
-
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
96
|
-
* @returns {object} Dictionary of filename: content
|
|
97
|
-
*/
|
|
98
|
-
function toHdbcdsSource( csn, options, messageFunctions ) {
|
|
99
|
-
timetrace.start('HDBCDS rendering');
|
|
100
|
-
const plainNames = options.sqlMapping === 'plain';
|
|
101
|
-
const quotedNames = options.sqlMapping === 'quoted';
|
|
102
|
-
const hdbcdsNames = options.sqlMapping === 'hdbcds';
|
|
103
|
-
|
|
104
|
-
const {
|
|
105
|
-
info, warning, error, throwWithAnyError, message,
|
|
106
|
-
} = messageFunctions;
|
|
107
|
-
|
|
108
|
-
const reportedMissingReplacements = Object.create(null);
|
|
109
|
-
|
|
110
|
-
const exprRenderer = createExpressionRenderer({
|
|
111
|
-
finalize: x => x,
|
|
112
|
-
typeCast(x) {
|
|
113
|
-
let typeRef = renderTypeReference(x.cast, this.env.withSubPath([ 'cast' ]));
|
|
114
|
-
// inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
|
|
115
|
-
const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
|
|
116
|
-
if (hanaSqlType) {
|
|
117
|
-
const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
|
|
118
|
-
typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
|
|
119
|
-
}
|
|
120
|
-
return `CAST(${ this.renderExpr(withoutCast(x)) } AS ${ typeRef })`;
|
|
121
|
-
},
|
|
122
|
-
val: renderExpressionLiteral,
|
|
123
|
-
enum: x => `#${ x['#'] }`,
|
|
124
|
-
ref: renderExpressionRef,
|
|
125
|
-
windowFunction: renderExpressionFunc,
|
|
126
|
-
func: renderExpressionFunc,
|
|
127
|
-
xpr(x) {
|
|
128
|
-
const xprEnv = this.env.withSubPath([ 'xpr' ]);
|
|
129
|
-
if (this.isNestedXpr && !x.cast)
|
|
130
|
-
return `(${ this.renderSubExpr(x.xpr, xprEnv) })`;
|
|
131
|
-
return this.renderSubExpr(x.xpr, xprEnv);
|
|
132
|
-
},
|
|
133
|
-
SELECT(x) {
|
|
134
|
-
return `(${ renderQuery(x, false, this.env.withIncreasedIndent()) })`;
|
|
135
|
-
},
|
|
136
|
-
SET(x) {
|
|
137
|
-
return `${ renderQuery(x, false, this.env.withIncreasedIndent()) }`;
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
function renderExpr( x, env ) {
|
|
142
|
-
return exprRenderer.renderExpr(x, env);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
checkCSNVersion(csn, options);
|
|
146
|
-
|
|
147
|
-
const hdbcdsResult = Object.create(null);
|
|
148
|
-
|
|
149
|
-
const globalDuplicateChecker = new DuplicateChecker(options.sqlMapping); // registry for all artifact names and element names
|
|
150
|
-
|
|
151
|
-
const killList = [];
|
|
152
|
-
if (quotedNames)
|
|
153
|
-
addContextMarkers(csn, killList);
|
|
154
|
-
|
|
155
|
-
if (!plainNames)
|
|
156
|
-
addIntermediateContexts(csn, killList);
|
|
157
|
-
|
|
158
|
-
// Render each top-level artifact on its own
|
|
159
|
-
const hdbcds = Object.create(null);
|
|
160
|
-
for (const artifactName in getTopLevelArtifacts()) {
|
|
161
|
-
const art = csn.definitions[artifactName];
|
|
162
|
-
// This environment is passed down the call hierarchy, for dealing with
|
|
163
|
-
// indentation and name resolution issues
|
|
164
|
-
const env = createEnv();
|
|
165
|
-
const sourceStr = renderDefinition(artifactName, art, env); // Must come first because it populates 'env.topLevelAliases'
|
|
166
|
-
if (sourceStr !== '') {
|
|
167
|
-
const name = plainNames ? artifactName.replace(/\./g, '_').toUpperCase() : artifactName;
|
|
168
|
-
hdbcds[name] = [
|
|
169
|
-
renderNamespaceDeclaration(name, env),
|
|
170
|
-
renderUsings(name, env),
|
|
171
|
-
sourceStr,
|
|
172
|
-
].join('');
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// render .hdbconstraint into result
|
|
177
|
-
const hdbconstraint = Object.create(null);
|
|
178
|
-
forEachDefinition(csn, (art) => {
|
|
179
|
-
if (art.$tableConstraints && art.$tableConstraints.referential) {
|
|
180
|
-
const referentialConstraints = {};
|
|
181
|
-
Object.entries(art.$tableConstraints.referential)
|
|
182
|
-
.forEach(([ fileName, referentialConstraint ]) => {
|
|
183
|
-
referentialConstraints[fileName] = renderReferentialConstraint(
|
|
184
|
-
referentialConstraint, createEnv().withIncreasedIndent().indent, false, csn, options
|
|
185
|
-
);
|
|
186
|
-
});
|
|
187
|
-
Object.entries(referentialConstraints)
|
|
188
|
-
.forEach(([ fileName, constraint ]) => {
|
|
189
|
-
hdbconstraint[fileName] = constraint;
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
hdbcdsResult.hdbcds = hdbcds;
|
|
194
|
-
hdbcdsResult.hdbconstraint = hdbconstraint;
|
|
195
|
-
|
|
196
|
-
if (globalDuplicateChecker)
|
|
197
|
-
globalDuplicateChecker.check(error, options); // perform duplicates check
|
|
198
|
-
|
|
199
|
-
killList.forEach(fn => fn());
|
|
200
|
-
|
|
201
|
-
throwWithAnyError();
|
|
202
|
-
timetrace.stop('HDBCDS rendering');
|
|
203
|
-
return options.testMode ? sort(hdbcdsResult) : hdbcdsResult;
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Sort the given object alphabetically
|
|
207
|
-
*
|
|
208
|
-
* @param {Object} obj Object to sort
|
|
209
|
-
* @returns {Object} With keys sorted
|
|
210
|
-
*/
|
|
211
|
-
function sort( obj ) {
|
|
212
|
-
const keys = Object.keys(obj).sort((a, b) => a.localeCompare(b));
|
|
213
|
-
const sortedResult = Object.create(null);
|
|
214
|
-
for (const key of keys)
|
|
215
|
-
sortedResult[key] = obj[key];
|
|
216
|
-
|
|
217
|
-
return sortedResult;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Render a definition. Return the resulting source string.
|
|
222
|
-
*
|
|
223
|
-
* @param {string} artifactName Name of the artifact to render
|
|
224
|
-
* @param {CSN.Artifact} art Content of the artifact to render
|
|
225
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
226
|
-
* @returns {string} The rendered artifact
|
|
227
|
-
*/
|
|
228
|
-
function renderDefinition( artifactName, art, env ) {
|
|
229
|
-
// We're always a top-level artifact.
|
|
230
|
-
env.path = [ 'definitions', artifactName ];
|
|
231
|
-
// Ignore whole artifacts if toHana says so
|
|
232
|
-
if (art.abstract || hasValidSkipOrExists(art))
|
|
233
|
-
return '';
|
|
234
|
-
|
|
235
|
-
switch (art.kind) {
|
|
236
|
-
case 'entity':
|
|
237
|
-
// FIXME: For HANA CDS, we need to replace $self at the beginning of paths in association ON-condition
|
|
238
|
-
// by the full name of the artifact we are rendering (should actually be done by forRelationalDB, but that is
|
|
239
|
-
// somewhat difficult because this kind of absolute path is quite unusual). In order not to have to pass
|
|
240
|
-
// the current artifact name down through the stack to renderExpr, we just put it into the env.
|
|
241
|
-
env.currentArtifactName = artifactName;
|
|
242
|
-
if (art.query || art.projection)
|
|
243
|
-
return renderView(artifactName, art, env);
|
|
244
|
-
|
|
245
|
-
return renderEntity(artifactName, art, env);
|
|
246
|
-
|
|
247
|
-
case 'context':
|
|
248
|
-
case 'service':
|
|
249
|
-
return renderContext(artifactName, art, env, false);
|
|
250
|
-
case 'namespace':
|
|
251
|
-
return renderNamespace(artifactName, art, env);
|
|
252
|
-
case 'type':
|
|
253
|
-
case 'aspect':
|
|
254
|
-
return renderType(artifactName, art, env);
|
|
255
|
-
case 'annotation':
|
|
256
|
-
case 'action':
|
|
257
|
-
case 'function':
|
|
258
|
-
case 'event':
|
|
259
|
-
return '';
|
|
260
|
-
default:
|
|
261
|
-
throw new ModelError(`Unknown artifact kind: ${ art.kind }`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Return a dictionary with the direct sub-artifacts of the artifact with name 'artifactName' in the csn
|
|
267
|
-
*
|
|
268
|
-
* @param {string} artifactName Find all children of this artifact
|
|
269
|
-
* @returns {object} Dictionary with direct sub-artifacts
|
|
270
|
-
*/
|
|
271
|
-
function getSubArtifacts( artifactName ) {
|
|
272
|
-
const prefix = `${ artifactName }.`;
|
|
273
|
-
const result = Object.create(null);
|
|
274
|
-
for (const name in csn.definitions) {
|
|
275
|
-
// We have a direct child if its name starts with prefix and contains no more dots
|
|
276
|
-
if (name.startsWith(prefix) && !name.substring(prefix.length).includes('.')) {
|
|
277
|
-
result[getLastPartOf(name)] = csn.definitions[name];
|
|
278
|
-
}
|
|
279
|
-
else if (name.startsWith(prefix) && !isContainedInOtherContext(name, artifactName)) {
|
|
280
|
-
const prefixPlusNextPart = name.substring(0, name.substring(prefix.length).indexOf('.') + prefix.length);
|
|
281
|
-
if (csn.definitions[prefixPlusNextPart]) {
|
|
282
|
-
const art = csn.definitions[prefixPlusNextPart];
|
|
283
|
-
if (![ 'service', 'context', 'namespace' ].includes(art.kind)) {
|
|
284
|
-
const nameWithoutPrefix = name.substring(prefix.length);
|
|
285
|
-
result[nameWithoutPrefix] = csn.definitions[name];
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
result[name.substring(prefix.length)] = csn.definitions[name];
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return options && options.testMode ? sort(result) : result;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Check whether the given context is the direct parent of the containee.
|
|
298
|
-
*
|
|
299
|
-
* @param {string} containee Name of the contained artifact
|
|
300
|
-
* @param {string} contextName Name of the (grand?)parent context
|
|
301
|
-
* @returns {boolean} True if there is another context in between
|
|
302
|
-
*/
|
|
303
|
-
function isContainedInOtherContext( containee, contextName ) {
|
|
304
|
-
const parts = containee.split('.');
|
|
305
|
-
const prefixLength = contextName.split('.').length;
|
|
306
|
-
|
|
307
|
-
for (let i = parts.length - 1; i > prefixLength; i--) {
|
|
308
|
-
const prefix = parts.slice(0, i).join('.');
|
|
309
|
-
const art = csn.definitions[prefix];
|
|
310
|
-
if (art && (art.kind === 'context' || art.kind === 'service'))
|
|
311
|
-
return true;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Render a context or service. Return the resulting source string.
|
|
319
|
-
*
|
|
320
|
-
* If the context is shadowed by another entity, the context itself is not rendered,
|
|
321
|
-
* but any contained (and transitively contained) entities and views are.
|
|
322
|
-
*
|
|
323
|
-
* @param {string} artifactName Name of the context/service
|
|
324
|
-
* @param {CSN.Artifact} art Content of the context/service
|
|
325
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
326
|
-
* @param {boolean} isShadowed
|
|
327
|
-
* @returns {string} The rendered context/service
|
|
328
|
-
*/
|
|
329
|
-
function renderContext( artifactName, art, env, isShadowed ) {
|
|
330
|
-
let result = '';
|
|
331
|
-
if (!isShadowed)
|
|
332
|
-
isShadowed = contextIsShadowed(artifactName);
|
|
333
|
-
if (isShadowed) {
|
|
334
|
-
const subArtifacts = getSubArtifacts(artifactName);
|
|
335
|
-
for (const name in subArtifacts)
|
|
336
|
-
result += renderDefinition(`${ artifactName }.${ name }`, subArtifacts[name], env);
|
|
337
|
-
|
|
338
|
-
return `${ result }\n`;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const childEnv = env.withIncreasedIndent();
|
|
342
|
-
result += `${ env.indent }context ${ renderArtifactName(artifactName, env, true) }`;
|
|
343
|
-
result += ' {\n';
|
|
344
|
-
const subArtifacts = getSubArtifacts(artifactName);
|
|
345
|
-
let renderedSubArtifacts = '';
|
|
346
|
-
for (const name in subArtifacts)
|
|
347
|
-
renderedSubArtifacts += renderDefinition(`${ artifactName }.${ name }`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
|
|
348
|
-
|
|
349
|
-
if (renderedSubArtifacts === '')
|
|
350
|
-
return '';
|
|
351
|
-
|
|
352
|
-
return `${ result + renderedSubArtifacts + env.indent }};\n`;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Check whether the given context is shadowed, i.e. part of his name prefix is shared by a
|
|
357
|
-
* non-context/service/namespace definition
|
|
358
|
-
*
|
|
359
|
-
* @param {string} artifactName
|
|
360
|
-
* @returns {boolean}
|
|
361
|
-
*/
|
|
362
|
-
function contextIsShadowed( artifactName ) {
|
|
363
|
-
if (artifactName.indexOf('.') === -1)
|
|
364
|
-
return false;
|
|
365
|
-
|
|
366
|
-
const parts = artifactName.split('.');
|
|
367
|
-
|
|
368
|
-
for (let i = 0; i < parts.length; i++) {
|
|
369
|
-
const art = csn.definitions[parts.slice(0, i).join('.')];
|
|
370
|
-
if (art && art.kind !== 'context' && art.kind !== 'service' && art.kind !== 'namespace')
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* In case of an artifact with . in the name (that are not a namespace/context part),
|
|
378
|
-
* we need to update the env to correctly render the artifact name.
|
|
379
|
-
*
|
|
380
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
381
|
-
* @param {string} name Possibly dotted artifact name
|
|
382
|
-
* @returns {HdbcdsRenderEnvironment} Updated env or original instance
|
|
383
|
-
*/
|
|
384
|
-
function updatePrefixForDottedName( env, name ) {
|
|
385
|
-
if (plainNames) {
|
|
386
|
-
let innerEnv = env;
|
|
387
|
-
if (name.indexOf('.') !== -1) {
|
|
388
|
-
const parts = name.split('.');
|
|
389
|
-
for (let i = 0; i < parts.length - 1; i++)
|
|
390
|
-
innerEnv = addNamePrefix(innerEnv, parts[i]);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return innerEnv;
|
|
394
|
-
}
|
|
395
|
-
return env;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Render a namespace. Return the resulting source string.
|
|
400
|
-
*
|
|
401
|
-
* @param {string} artifactName Name of the namespace
|
|
402
|
-
* @param {CSN.Artifact} art Content of the namespace
|
|
403
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
404
|
-
* @returns {string} The rendered children of the namespace
|
|
405
|
-
*/
|
|
406
|
-
function renderNamespace( artifactName, art, env ) {
|
|
407
|
-
// We currently do not render anything for a namespace, we just append its id to
|
|
408
|
-
// the environment's current name prefix and descend into its children
|
|
409
|
-
let result = '';
|
|
410
|
-
const childEnv = addNamePrefix(env, getLastPartOf(artifactName));
|
|
411
|
-
const subArtifacts = getSubArtifacts(artifactName);
|
|
412
|
-
for (const name in subArtifacts)
|
|
413
|
-
result += renderDefinition(`${ artifactName }.${ name }`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
|
|
414
|
-
|
|
415
|
-
return result;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Render a non-query entity. Return the resulting source string.
|
|
420
|
-
*
|
|
421
|
-
* @param {string} artifactName Name of the entity
|
|
422
|
-
* @param {CSN.Artifact} art Content of the entity
|
|
423
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
424
|
-
* @returns {string} The rendered entity
|
|
425
|
-
*/
|
|
426
|
-
function renderEntity( artifactName, art, env ) {
|
|
427
|
-
let result = '';
|
|
428
|
-
const childEnv = env.withIncreasedIndent();
|
|
429
|
-
const normalizedArtifactName = renderArtifactName(artifactName, env);
|
|
430
|
-
|
|
431
|
-
globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], env.path, artifactName);
|
|
432
|
-
|
|
433
|
-
if (hasHanaComment(art, options))
|
|
434
|
-
result += `${ env.indent }@Comment: '${ getEscapedHanaComment(art) }'\n`;
|
|
435
|
-
|
|
436
|
-
// tables can have @sql.prepend and @sql.append
|
|
437
|
-
const { front, back } = getSqlSnippets(options, art);
|
|
438
|
-
|
|
439
|
-
if (front) // attach @sql.prepend after adding @Comment annotation
|
|
440
|
-
result += front;
|
|
441
|
-
|
|
442
|
-
result += `${ env.indent + (art.abstract ? 'abstract ' : '') }entity ${ normalizedArtifactName }`;
|
|
443
|
-
if (art.includes) {
|
|
444
|
-
// Includes are never flattened (don't exist in HANA)
|
|
445
|
-
result += ` : ${ art.includes.map((name, i) => renderAbsoluteNameWithQuotes(name, env.withSubPath([ 'includes', i ]))).join(', ') }`;
|
|
446
|
-
}
|
|
447
|
-
result += ' {\n';
|
|
448
|
-
const duplicateChecker = new DuplicateChecker(); // registry for all artifact names and element names
|
|
449
|
-
duplicateChecker.addArtifact(artifactName, env.path, artifactName);
|
|
450
|
-
// calculate __aliases which must be used in case an association
|
|
451
|
-
// has the same identifier as it's target
|
|
452
|
-
createTopLevelAliasesForArtifact(artifactName, art, env);
|
|
453
|
-
for (const name in art.elements)
|
|
454
|
-
result += renderElement(name, art.elements[name], childEnv.withSubPath([ 'elements', name ]), duplicateChecker);
|
|
455
|
-
|
|
456
|
-
duplicateChecker.check(error);
|
|
457
|
-
result += `${ env.indent }}`;
|
|
458
|
-
result += `${ renderTechnicalConfiguration(art.technicalConfig, env) }`;
|
|
459
|
-
|
|
460
|
-
if (back)
|
|
461
|
-
result += back;
|
|
462
|
-
|
|
463
|
-
return `${ result };\n`;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* If an association/composition has the same identifier as it's target
|
|
468
|
-
* we must render a "using target as __target" and use the alias to refer to the target
|
|
469
|
-
*
|
|
470
|
-
* @param {string} artName
|
|
471
|
-
* @param {CSN.Artifact} art
|
|
472
|
-
* @param {HdbcdsRenderEnvironment} env
|
|
473
|
-
*/
|
|
474
|
-
function createTopLevelAliasesForArtifact( artName, art, env ) {
|
|
475
|
-
forEachMember(art, (element) => {
|
|
476
|
-
if (!element.target)
|
|
477
|
-
return;
|
|
478
|
-
|
|
479
|
-
if (uppercaseAndUnderscore(element.target) === element['@cds.persistence.name']) {
|
|
480
|
-
let alias = createTopLevelAliasName(element['@cds.persistence.name']);
|
|
481
|
-
// calculate new alias if it would conflict with other csn.Artifact
|
|
482
|
-
while (csn.definitions[alias])
|
|
483
|
-
alias = createTopLevelAliasName(alias);
|
|
484
|
-
env.topLevelAliases[element['@cds.persistence.name']] = {
|
|
485
|
-
quotedName: formatIdentifier(element['@cds.persistence.name']),
|
|
486
|
-
quotedAlias: formatIdentifier(alias),
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Render the 'technical configuration { ... }' section 'tc' of an entity.
|
|
494
|
-
*
|
|
495
|
-
* @param {object} tc content of the technical configuration
|
|
496
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
497
|
-
* @returns {string} Return the resulting source string.
|
|
498
|
-
*/
|
|
499
|
-
function renderTechnicalConfiguration( tc, env ) {
|
|
500
|
-
if (!tc)
|
|
501
|
-
return '';
|
|
502
|
-
|
|
503
|
-
let result = '';
|
|
504
|
-
const childEnv = env.withIncreasedIndent();
|
|
505
|
-
|
|
506
|
-
// FIXME: How to deal with non-HANA technical configurations? We should probably just iterate all entries
|
|
507
|
-
// in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
|
|
508
|
-
tc = tc.hana;
|
|
509
|
-
if (!tc)
|
|
510
|
-
throw new ModelError('Expecting a SAP HANA technical configuration');
|
|
511
|
-
|
|
512
|
-
result += `\n${ env.indent }technical ${ tc.calculated ? '' : 'hana ' }configuration {\n`;
|
|
513
|
-
|
|
514
|
-
// Store type (must be separate because SQL wants it between 'CREATE' and 'TABLE')
|
|
515
|
-
if (tc.storeType)
|
|
516
|
-
result += `${ tc.storeType } store;\n`;
|
|
517
|
-
|
|
518
|
-
// Fixed parts belonging to the table (includes migration, unload prio, extended storage,
|
|
519
|
-
// auto merge, partitioning, ...)
|
|
520
|
-
if (tc.tableSuffix) {
|
|
521
|
-
// Unlike SQL, CDL and HANA CDS require a semicolon after each table-suffix part
|
|
522
|
-
// (e.g. `migration enabled; row store; ...`). In order to keep both
|
|
523
|
-
// the simplicity of "the whole bandwurm is just one expression that can be
|
|
524
|
-
// rendered to SQL without further knowledge" and at the same time telling
|
|
525
|
-
// CDS about the boundaries, the compactor has put each part into its own `xpr`
|
|
526
|
-
// object. Semantically equivalent because a "trivial" SQL renderer would just
|
|
527
|
-
// concatenate them.
|
|
528
|
-
for (const xpr of tc.tableSuffix)
|
|
529
|
-
result += `${ childEnv.indent + renderExpr(xpr, childEnv) };\n`;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Indices and full-text indices
|
|
533
|
-
for (const idxName in tc.indexes || {}) {
|
|
534
|
-
if (Array.isArray(tc.indexes[idxName][0])) {
|
|
535
|
-
// FIXME: Should we allow multiple indices with the same name at all?
|
|
536
|
-
for (const index of tc.indexes[idxName])
|
|
537
|
-
result += `${ childEnv.indent + renderExpr(index, childEnv) };\n`;
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
result += `${ childEnv.indent + renderExpr(tc.indexes[idxName], childEnv) };\n`;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
// Fuzzy search indices
|
|
544
|
-
for (const columnName in tc.fzindexes || {}) {
|
|
545
|
-
if (Array.isArray(tc.fzindexes[columnName][0])) {
|
|
546
|
-
// FIXME: Should we allow multiple fuzzy search indices on the same column at all?
|
|
547
|
-
// And if not, why do we wrap this into an array?
|
|
548
|
-
for (const index of tc.fzindexes[columnName])
|
|
549
|
-
result += `${ childEnv.indent + renderExpr(fixFuzzyIndex(index, columnName), childEnv) };\n`;
|
|
550
|
-
}
|
|
551
|
-
else {
|
|
552
|
-
result += `${ childEnv.indent + renderExpr(fixFuzzyIndex(tc.fzindexes[columnName], columnName), childEnv) };\n`;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
result += `${ env.indent }}`;
|
|
556
|
-
return result;
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Fuzzy indices are stored in compact CSN as they would appear in SQL after the column name,
|
|
561
|
-
* i.e. the whole line in SQL looks somewhat like this:
|
|
562
|
-
* s nvarchar(10) FUZZY SEARCH INDEX ON FUZZY SEARCH MODE 'ALPHANUM'
|
|
563
|
-
* But in CDL, we don't write fuzzy search indices together with the table column, so we need
|
|
564
|
-
* to insert the name of the column after 'ON' in CDS syntax, making it look like this:
|
|
565
|
-
* fuzzy search mode on (s) search mode 'ALPHANUM'
|
|
566
|
-
* This function expects an array with the original expression and returns an array with the modified expression
|
|
567
|
-
*
|
|
568
|
-
* @param {Array} fuzzyIndex Expression array representing the fuzzy index
|
|
569
|
-
* @param {string} columnName Name of the SQL column
|
|
570
|
-
* @returns {Array} Modified expression array
|
|
571
|
-
*/
|
|
572
|
-
function fixFuzzyIndex( fuzzyIndex, columnName ) {
|
|
573
|
-
return fuzzyIndex.map(token => (token === 'on' ? { xpr: [ 'on', { xpr: { ref: columnName.split('.') } } ] } : token));
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* Render an element (of an entity, type or annotation, not a projection or view).
|
|
579
|
-
* Return the resulting source string.
|
|
580
|
-
*
|
|
581
|
-
* @param {string} elementName Name of the element
|
|
582
|
-
* @param {CSN.Element} elm Content of the element
|
|
583
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
584
|
-
* @param {DuplicateChecker} [duplicateChecker] Utility for detecting duplicates
|
|
585
|
-
* @param {boolean} [isSubElement] Whether the given element is a subelement or not - subelements cannot be key!
|
|
586
|
-
* @returns {string} The rendered element
|
|
587
|
-
*/
|
|
588
|
-
function renderElement( elementName, elm, env, duplicateChecker, isSubElement ) {
|
|
589
|
-
// Ignore if toHana says so
|
|
590
|
-
if (elm.virtual)
|
|
591
|
-
return '';
|
|
592
|
-
|
|
593
|
-
// Special handling for HANA CDS: Must omit the ':' before anonymous structured types (for historical reasons)
|
|
594
|
-
const omitColon = (!elm.type && elm.elements);
|
|
595
|
-
let result = '';
|
|
596
|
-
const quotedElementName = formatIdentifier(elementName);
|
|
597
|
-
if (duplicateChecker)
|
|
598
|
-
duplicateChecker.addElement(quotedElementName, env.path, elementName);
|
|
599
|
-
|
|
600
|
-
if (hasHanaComment(elm, options))
|
|
601
|
-
result += `${ env.indent }@Comment: '${ getEscapedHanaComment(elm) }'\n`;
|
|
602
|
-
|
|
603
|
-
result += env.indent + (elm.key && !isSubElement ? 'key ' : '') +
|
|
604
|
-
(elm.masked ? 'masked ' : '') +
|
|
605
|
-
quotedElementName + (omitColon ? ' ' : ' : ') +
|
|
606
|
-
renderTypeReference(elm, env);
|
|
607
|
-
// GENERATED AS ALWAYS() can't have a trailing "[not] null" nor "default".
|
|
608
|
-
// Because we already emit an error that calc-on-write is not supported, just ignore nullability/default.
|
|
609
|
-
if (!elm.value?.stored) {
|
|
610
|
-
result += renderNullability(elm);
|
|
611
|
-
if (elm.default && !elm.target)
|
|
612
|
-
result += ` default ${ renderExpr(elm.default, env.withSubPath([ 'default' ])) }`;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// (table) elements can only have a @sql.append
|
|
616
|
-
const { back } = getSqlSnippets(options, elm);
|
|
617
|
-
|
|
618
|
-
if (back)
|
|
619
|
-
result += back;
|
|
620
|
-
|
|
621
|
-
return `${ result };\n`;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* Render the source of a query, which may be a path reference, possibly with an alias,
|
|
626
|
-
* or a subselect, or a join operation, as seen from artifact 'art'.
|
|
627
|
-
* Returns the source as a string.
|
|
628
|
-
*
|
|
629
|
-
* @param {object} source Source to render
|
|
630
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
631
|
-
* @returns {string} Rendered view source
|
|
632
|
-
*/
|
|
633
|
-
function renderViewSource( source, env ) {
|
|
634
|
-
// Sub-SELECT
|
|
635
|
-
if (source.SELECT || source.SET) {
|
|
636
|
-
let result = `(${ renderQuery(source, false, env.withIncreasedIndent()) })`;
|
|
637
|
-
if (source.as)
|
|
638
|
-
result += ` as ${ formatIdentifier(source.as) }`;
|
|
639
|
-
return result;
|
|
640
|
-
}
|
|
641
|
-
// JOIN
|
|
642
|
-
else if (source.join) {
|
|
643
|
-
// One join operation, possibly with ON-condition
|
|
644
|
-
let result = `${ renderViewSource(source.args[0], env.withSubPath([ 'args', 0 ])) }`;
|
|
645
|
-
for (let i = 1; i < source.args.length; i++) {
|
|
646
|
-
result = `(${ result } ${ source.join } `;
|
|
647
|
-
result += `join ${ renderViewSource(source.args[i], env.withSubPath([ 'args', i ])) }`;
|
|
648
|
-
if (source.on)
|
|
649
|
-
result += ` on ${ renderExpr(source.on, env.withSubPath([ 'on' ])) }`;
|
|
650
|
-
|
|
651
|
-
result += ')';
|
|
652
|
-
}
|
|
653
|
-
return result;
|
|
654
|
-
}
|
|
655
|
-
// Ordinary path, possibly with an alias
|
|
656
|
-
|
|
657
|
-
return renderAbsolutePathWithAlias(source, env);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/**
|
|
661
|
-
* Render a path that starts with an absolute name (as used e.g. for the source of a query),
|
|
662
|
-
* with plain or quoted names, depending on options. Expects an object 'path' that has a 'ref'.
|
|
663
|
-
* Returns the name as a string.
|
|
664
|
-
*
|
|
665
|
-
* @param {object} path Path to render
|
|
666
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
667
|
-
* @returns {string} Rendered path
|
|
668
|
-
*/
|
|
669
|
-
function renderAbsolutePath( path, env ) {
|
|
670
|
-
// Sanity checks
|
|
671
|
-
if (!path.ref)
|
|
672
|
-
throw new ModelError(`Expecting ref in path: ${ JSON.stringify(path) }`);
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
676
|
-
const firstArtifactName = path.ref[0].id || path.ref[0];
|
|
677
|
-
|
|
678
|
-
let result = '';
|
|
679
|
-
// Render the first path step (absolute name, with different quoting/naming ..)
|
|
680
|
-
if (plainNames)
|
|
681
|
-
result += renderAbsoluteNamePlain(firstArtifactName, env);
|
|
682
|
-
else
|
|
683
|
-
result += renderAbsoluteNameWithQuotes(firstArtifactName, env);
|
|
684
|
-
|
|
685
|
-
// Even the first step might have parameters and/or a filter
|
|
686
|
-
if (path.ref[0].args)
|
|
687
|
-
result += `(${ renderArgs(path.ref[0], ':', env.withSubPath([ 'ref', 0 ])) })`;
|
|
688
|
-
|
|
689
|
-
if (path.ref[0].where) {
|
|
690
|
-
const cardinality = path.ref[0].cardinality ? (`${ path.ref[0].cardinality.max }: `) : '';
|
|
691
|
-
result += `[${ cardinality }${ renderExpr(path.ref[0].where, env.withSubPath([ 'ref', 0, 'where' ])) }]`;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Add any path steps (possibly with parameters and filters) that may follow after that
|
|
695
|
-
if (path.ref.length > 1)
|
|
696
|
-
result += `.${ renderTypeRef({ ref: path.ref.slice(1) }, env) }`;
|
|
697
|
-
|
|
698
|
-
return result;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
/**
|
|
702
|
-
* Render a path that starts with an absolute name (as used for the source of a query),
|
|
703
|
-
* possibly with an alias, with plain or quoted names, depending on options. Expects an object 'path' that has a
|
|
704
|
-
* 'ref' and (in case of an alias) an 'as'. If necessary, an artificial alias
|
|
705
|
-
* is created to the original implicit name.
|
|
706
|
-
* Returns the name and alias as a string.
|
|
707
|
-
*
|
|
708
|
-
* @param {object} path Path to render
|
|
709
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
710
|
-
* @returns {string} Rendered path including alias
|
|
711
|
-
*/
|
|
712
|
-
function renderAbsolutePathWithAlias( path, env ) {
|
|
713
|
-
let result = renderAbsolutePath(path, env);
|
|
714
|
-
// Take care of aliases - for artifact references, use the resulting name (multi-dot joined with _)
|
|
715
|
-
const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.sqlMapping, path.ref[0])) : getLastPartOfRef(path.ref);
|
|
716
|
-
if (path.as) {
|
|
717
|
-
// Source had an alias - render it
|
|
718
|
-
result += ` as ${ formatIdentifier(path.as) }`;
|
|
719
|
-
}
|
|
720
|
-
else if (getLastPartOf(result) !== formatIdentifier(implicitAlias)) {
|
|
721
|
-
// Render an artificial alias if the result would produce a different one
|
|
722
|
-
result += ` as ${ formatIdentifier(implicitAlias) }`;
|
|
723
|
-
}
|
|
724
|
-
return result;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* Render a single view or projection column 'col', as it occurs in a select list or
|
|
729
|
-
* projection list within 'art', possibly with annotations.
|
|
730
|
-
* Return the resulting source string (no trailing LF).
|
|
731
|
-
*
|
|
732
|
-
* @param {object} col Column to render
|
|
733
|
-
* @param {CSN.Elements} elements where column exists
|
|
734
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
735
|
-
* @returns {string} Rendered column
|
|
736
|
-
*/
|
|
737
|
-
function renderViewColumn( col, elements, env ) {
|
|
738
|
-
const leaf = col.as || col.ref?.[col.ref.length - 1] || col.func;
|
|
739
|
-
const element = elements[leaf];
|
|
740
|
-
|
|
741
|
-
// Render 'null as <alias>' only for database and if element is virtual
|
|
742
|
-
if (element?.virtual) {
|
|
743
|
-
if (isDeprecatedEnabled(options, '_renderVirtualElements'))
|
|
744
|
-
return `${ env.indent }null as ${ formatIdentifier(leaf) }`;
|
|
745
|
-
return '';
|
|
746
|
-
}
|
|
747
|
-
return renderNonVirtualColumn();
|
|
748
|
-
|
|
749
|
-
function renderNonVirtualColumn() {
|
|
750
|
-
let result = env.indent;
|
|
751
|
-
// only if column is virtual, keyword virtual was present in the source text
|
|
752
|
-
if (col.virtual)
|
|
753
|
-
result += 'virtual ';
|
|
754
|
-
// If key is explicitly set in a non-leading query, issue an error.
|
|
755
|
-
if (col.key && env.skipKeys)
|
|
756
|
-
error(null, env.path, { keyword: 'key', $reviewed: true }, 'Unexpected $(KEYWORD) in subquery');
|
|
757
|
-
|
|
758
|
-
// Since magic variables are replaced, we may need to create an alias if there isn't one.
|
|
759
|
-
if (!col.as && col.ref?.[0] && isMagicVariable(pathId(col.ref?.[0])))
|
|
760
|
-
col.as = implicitAs(col.ref);
|
|
761
|
-
|
|
762
|
-
const key = (!env.skipKeys && (col.key || element?.key) ? 'key ' : '');
|
|
763
|
-
result += key + renderExpr(withoutCast(col), env);
|
|
764
|
-
let alias = col.as || (!col.args && col.func); // func: e.g. CURRENT_TIMESTAMP
|
|
765
|
-
// HANA requires an alias for 'key' columns just for syntactical reasons
|
|
766
|
-
// FIXME: This will not complain for non-refs (but that should be checked in forRelationalDB)
|
|
767
|
-
// Explicit or implicit alias?
|
|
768
|
-
// Shouldn't we simply generate an alias all the time?
|
|
769
|
-
if ((key || col.cast) && !alias)
|
|
770
|
-
alias = leaf;
|
|
771
|
-
|
|
772
|
-
if (alias)
|
|
773
|
-
result += ` as ${ formatIdentifier(alias) }`;
|
|
774
|
-
|
|
775
|
-
// Explicit type provided for the view element?
|
|
776
|
-
if (col.cast?.target) {
|
|
777
|
-
// Special case: Explicit association type is actually a redirect
|
|
778
|
-
// Redirections are never flattened (don't exist in HANA)
|
|
779
|
-
result += ` : redirected to ${ renderAbsoluteNameWithQuotes(col.cast.target, env.withSubPath([ 'cast', 'target' ])) }`;
|
|
780
|
-
if (col.cast.on)
|
|
781
|
-
result += ` on ${ renderExpr(col.cast.on, env.withSubPath([ 'cast', 'on' ])) }`;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
return result;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
/**
|
|
789
|
-
* Render a view. If '$syntax' is set (to 'projection', 'view', 'entity'),
|
|
790
|
-
* the view query is rendered in the requested syntax style, otherwise it
|
|
791
|
-
* is rendered as a view.
|
|
792
|
-
*
|
|
793
|
-
* @param {string} artifactName Name of the artifact
|
|
794
|
-
* @param {CSN.Artifact} art Content of the artifact
|
|
795
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
796
|
-
* @returns {string} The rendered view
|
|
797
|
-
*/
|
|
798
|
-
function renderView( artifactName, art, env ) {
|
|
799
|
-
let result = '';
|
|
800
|
-
const artifactPath = [ 'definitions', artifactName ];
|
|
801
|
-
globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], artifactPath, artifactName);
|
|
802
|
-
|
|
803
|
-
if (hasHanaComment(art, options))
|
|
804
|
-
result += `${ env.indent }@Comment: '${ getEscapedHanaComment(art) }'\n`;
|
|
805
|
-
|
|
806
|
-
result += `${ env.indent }${ art.abstract ? 'abstract ' : '' }view ${ renderArtifactName(artifactName, env) }`;
|
|
807
|
-
if (art.params) {
|
|
808
|
-
const childEnv = env.withIncreasedIndent();
|
|
809
|
-
const parameters = Object.keys(art.params)
|
|
810
|
-
.map(name => renderParameter(name, art.params[name], childEnv.withSubPath([ 'params', name ])))
|
|
811
|
-
.join(',\n');
|
|
812
|
-
// SAP HANA only understands the 'with parameters' syntax'
|
|
813
|
-
result += ` with parameters\n${ parameters }\n${ env.indent }as `;
|
|
814
|
-
}
|
|
815
|
-
else {
|
|
816
|
-
result += ' as ';
|
|
817
|
-
}
|
|
818
|
-
env._artifact = art;
|
|
819
|
-
result += renderQuery(getNormalizedQuery(art).query, true, env.withSubPath([ art.projection ? 'projection' : 'query' ]), art.elements);
|
|
820
|
-
|
|
821
|
-
// views can only have a @sql.append
|
|
822
|
-
const { back } = getSqlSnippets(options, art);
|
|
823
|
-
if (back)
|
|
824
|
-
result += back;
|
|
825
|
-
|
|
826
|
-
result += ';\n';
|
|
827
|
-
|
|
828
|
-
return result;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
/**
|
|
832
|
-
* Render a query 'query', i.e. a select statement with where-condition etc.
|
|
833
|
-
* If 'isLeadingQuery' is true, mixins, actions and functions of 'art' are
|
|
834
|
-
* also rendered into the query. Use 'syntax' style ('projection', 'view',
|
|
835
|
-
* or 'entity')
|
|
836
|
-
*
|
|
837
|
-
* @param {CSN.Query} query Query object
|
|
838
|
-
* @param {boolean} isLeadingQuery Whether the query is the leading query or not
|
|
839
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
840
|
-
* @param {object} [elements] For leading query, the elements of the artifact
|
|
841
|
-
* @returns {string} The rendered query
|
|
842
|
-
*/
|
|
843
|
-
function renderQuery( query, isLeadingQuery, env, elements = null ) {
|
|
844
|
-
const isProjection = env.path[env.path.length - 1] === 'projection';
|
|
845
|
-
let result = '';
|
|
846
|
-
env.skipKeys = !isLeadingQuery;
|
|
847
|
-
// Set operator, like UNION, INTERSECT, ...
|
|
848
|
-
if (query.SET) {
|
|
849
|
-
// First arg may be leading query
|
|
850
|
-
result += `(${ renderQuery(query.SET.args[0], isLeadingQuery, env.withSubPath([ 'SET', 'args', 0 ]), elements || query.SET.elements) }`;
|
|
851
|
-
// FIXME: Clarify if set operators can be n-ary (assuming binary here)
|
|
852
|
-
if (query.SET.op) {
|
|
853
|
-
// Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
|
|
854
|
-
for (let i = 1; i < query.SET.args.length; i++)
|
|
855
|
-
result += `\n${ env.indent }${ query.SET.op }${ query.SET.all ? ' all' : '' } ${ renderQuery(query.SET.args[i], false, env.withSubPath([ 'SET', 'args', i ]), elements || query.SET.elements) }`;
|
|
856
|
-
}
|
|
857
|
-
result += ')';
|
|
858
|
-
// Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
|
|
859
|
-
// each SELECT)
|
|
860
|
-
if (query.SET.orderBy)
|
|
861
|
-
result += `${ continueIndent(result, env) }order by ${ query.SET.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'SET', 'orderBy', i ]))).join(', ') }`;
|
|
862
|
-
|
|
863
|
-
if (query.SET.limit)
|
|
864
|
-
result += `${ continueIndent(result, env) }${ renderLimit(query.SET.limit, env.withSubPath([ 'SET', 'limit' ])) }`;
|
|
865
|
-
|
|
866
|
-
return result;
|
|
867
|
-
}
|
|
868
|
-
// Otherwise must have a SELECT
|
|
869
|
-
else if (!query.SELECT) {
|
|
870
|
-
throw new ModelError(`Unexpected query operation ${ JSON.stringify(query) } at ${ JSON.stringify(env.path) }`);
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
if (!isProjection)
|
|
874
|
-
env = env.withSubPath([ 'SELECT' ]);
|
|
875
|
-
|
|
876
|
-
const select = query.SELECT;
|
|
877
|
-
result += `select from ${ renderViewSource(select.from, env.withSubPath([ 'from' ])) }`;
|
|
878
|
-
|
|
879
|
-
const childEnv = env.withIncreasedIndent();
|
|
880
|
-
childEnv.currentArtifactName = $PROJECTION; // $self to be replaced by $projection
|
|
881
|
-
if (select.mixin) {
|
|
882
|
-
let elems = '';
|
|
883
|
-
for (const name in select.mixin)
|
|
884
|
-
elems += renderElement(name, select.mixin[name], childEnv.withSubPath([ 'mixin', name ]));
|
|
885
|
-
|
|
886
|
-
if (elems) {
|
|
887
|
-
result += ' mixin {\n';
|
|
888
|
-
result += elems;
|
|
889
|
-
result += `${ env.indent }} into`;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
result += select.distinct ? ' distinct' : '';
|
|
893
|
-
if (select.columns) {
|
|
894
|
-
result += ' {\n';
|
|
895
|
-
result += select.columns
|
|
896
|
-
.map((col, i) => renderViewColumn(col, elements || select.elements, childEnv.withSubPath([ 'columns', i ])))
|
|
897
|
-
.filter(s => s !== '')
|
|
898
|
-
.join(',\n');
|
|
899
|
-
result += `\n${ env.indent }}`;
|
|
900
|
-
}
|
|
901
|
-
if (select.excluding) {
|
|
902
|
-
const excludingList = select.excluding.map(id => `${ childEnv.indent }${ formatIdentifier(id) }`).join(',\n');
|
|
903
|
-
result += ` excluding {\n${ excludingList }\n`;
|
|
904
|
-
result += `${ env.indent }}`;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
return renderSelectProperties(select, result, env);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* Render WHERE, GROUP BY, HAVING, ORDER BY and LIMIT clause
|
|
912
|
-
*
|
|
913
|
-
* @param {CSN.QuerySelect} select
|
|
914
|
-
* @param {string} alreadyRendered The query as it has been rendered so far
|
|
915
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
916
|
-
* @returns {string} The query with WHERE etc. added
|
|
917
|
-
*/
|
|
918
|
-
function renderSelectProperties( select, alreadyRendered, env ) {
|
|
919
|
-
if (select.where)
|
|
920
|
-
alreadyRendered += `${ continueIndent(alreadyRendered, env) }where ${ renderExpr(select.where, env.withSubPath([ 'where' ])) }`;
|
|
921
|
-
|
|
922
|
-
if (select.groupBy)
|
|
923
|
-
alreadyRendered += `${ continueIndent(alreadyRendered, env) }group by ${ select.groupBy.map((expr, i) => renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ') }`;
|
|
924
|
-
|
|
925
|
-
if (select.having)
|
|
926
|
-
alreadyRendered += `${ continueIndent(alreadyRendered, env) }having ${ renderExpr(select.having, env.withSubPath([ 'having' ])) }`;
|
|
927
|
-
|
|
928
|
-
if (select.orderBy)
|
|
929
|
-
alreadyRendered += `${ continueIndent(alreadyRendered, env) }order by ${ select.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ') }`;
|
|
930
|
-
|
|
931
|
-
if (select.limit)
|
|
932
|
-
alreadyRendered += `${ continueIndent(alreadyRendered, env) }${ renderLimit(select.limit, env.withSubPath([ 'limit' ])) }`;
|
|
933
|
-
|
|
934
|
-
return alreadyRendered;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
/**
|
|
938
|
-
* Utility function to make sure that we continue with the same indentation in WHERE, GROUP BY, ... after a closing curly brace and beyond
|
|
939
|
-
*
|
|
940
|
-
* @param {string} result Result of a previous render step
|
|
941
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
942
|
-
* @returns {string} String to join with
|
|
943
|
-
*/
|
|
944
|
-
function continueIndent( result, env ) {
|
|
945
|
-
if (result.endsWith('}') || result.endsWith('})')) {
|
|
946
|
-
// The preceding clause ended with '}', just append after that
|
|
947
|
-
return ' ';
|
|
948
|
-
}
|
|
949
|
-
// Otherwise, start new line and indent normally
|
|
950
|
-
return `\n${ env.withIncreasedIndent().indent }`;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
/**
|
|
954
|
-
* Render a query's LIMIT clause, which may also have OFFSET.
|
|
955
|
-
*
|
|
956
|
-
* @param {CSN.QueryLimit} limit CSN limit clause
|
|
957
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
958
|
-
* @returns {string} Rendered limit clause
|
|
959
|
-
*/
|
|
960
|
-
function renderLimit( limit, env ) {
|
|
961
|
-
let result = '';
|
|
962
|
-
if (limit.rows !== undefined)
|
|
963
|
-
result += `limit ${ renderExpr(limit.rows, env.withSubPath([ 'rows' ])) }`;
|
|
964
|
-
|
|
965
|
-
if (limit.offset !== undefined) {
|
|
966
|
-
const indent = result !== '' ? `\n${ env.withIncreasedIndent().indent }` : '';
|
|
967
|
-
result += `${ indent }offset ${ renderExpr(limit.offset, env.withSubPath([ 'offset' ])) }`;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
return result;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
/**
|
|
974
|
-
* Render one entry of a query's ORDER BY clause (which always has a 'value' expression, and may
|
|
975
|
-
* have a 'sort' property for ASC/DESC and a 'nulls' for FIRST/LAST
|
|
976
|
-
*
|
|
977
|
-
* @param {object} entry CSN order by
|
|
978
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
979
|
-
* @returns {string} Rendered order by
|
|
980
|
-
*/
|
|
981
|
-
function renderOrderByEntry( entry, env ) {
|
|
982
|
-
let result = renderExpr(entry, env);
|
|
983
|
-
if (entry.sort)
|
|
984
|
-
result += ` ${ entry.sort }`;
|
|
985
|
-
|
|
986
|
-
if (entry.nulls)
|
|
987
|
-
result += ` nulls ${ entry.nulls }`;
|
|
988
|
-
|
|
989
|
-
return result;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* Render a view parameter.
|
|
994
|
-
*
|
|
995
|
-
* @param {string} parName Name of the parameter
|
|
996
|
-
* @param {object} par CSN parameter
|
|
997
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
998
|
-
* @returns {string} The resulting parameter as source string (no trailing LF).
|
|
999
|
-
*/
|
|
1000
|
-
function renderParameter( parName, par, env ) {
|
|
1001
|
-
if (par.notNull === true || par.notNull === false)
|
|
1002
|
-
info('query-ignoring-param-nullability', env.path, { '#': 'std' });
|
|
1003
|
-
return `${ env.indent + formatParamIdentifier(parName, env.path) } : ${ renderTypeReference(par, env) }`;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* Render a type (derived or structured).
|
|
1008
|
-
* Return the resulting source string.
|
|
1009
|
-
*
|
|
1010
|
-
* @param {string} artifactName Name of the artifact
|
|
1011
|
-
* @param {CSN.Artifact} art Content of the artifact
|
|
1012
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1013
|
-
* @returns {string} Rendered type/annotation
|
|
1014
|
-
*/
|
|
1015
|
-
function renderType( artifactName, art, env ) {
|
|
1016
|
-
if (art.kind === 'aspect' || art.kind === 'type' && !hdbcdsNames || art.kind === 'type' && hdbcdsNames && !art.elements)
|
|
1017
|
-
return '';
|
|
1018
|
-
let result = '';
|
|
1019
|
-
result += `${ env.indent + (art.kind) } ${ renderArtifactName(artifactName, env, true) }`;
|
|
1020
|
-
if (art.includes) {
|
|
1021
|
-
// Includes are never flattened (don't exist in HANA)
|
|
1022
|
-
result += ` : ${ art.includes.map(name => renderAbsoluteNameWithQuotes(name, env)).join(', ') }`;
|
|
1023
|
-
}
|
|
1024
|
-
if (art.elements && !art.type) {
|
|
1025
|
-
const childEnv = env.withIncreasedIndent();
|
|
1026
|
-
// Structured type or annotation with anonymous struct type
|
|
1027
|
-
result += ' {\n';
|
|
1028
|
-
for (const name in art.elements)
|
|
1029
|
-
result += renderElement(name, art.elements[name], childEnv.withSubPath([ 'elements', name ]));
|
|
1030
|
-
|
|
1031
|
-
result += `${ env.indent }};\n`;
|
|
1032
|
-
}
|
|
1033
|
-
else {
|
|
1034
|
-
// Derived type or annotation with non-anonymous type
|
|
1035
|
-
result += ` : ${ renderTypeReference(art, env) };\n`;
|
|
1036
|
-
}
|
|
1037
|
-
return result;
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* Render a reference to a type used by 'elm' (named or inline)
|
|
1042
|
-
* Allow suppressing enum-rendering - used in columns for example
|
|
1043
|
-
*
|
|
1044
|
-
* @param {object} elm Element using the type reference
|
|
1045
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1046
|
-
* @returns {string} Rendered type reference
|
|
1047
|
-
*/
|
|
1048
|
-
function renderTypeReference( elm, env ) {
|
|
1049
|
-
let result = '';
|
|
1050
|
-
|
|
1051
|
-
// Array type: Render items instead
|
|
1052
|
-
if (elm.items && !elm.type) {
|
|
1053
|
-
// HANA CDS does not support keyword many
|
|
1054
|
-
let rc = `array of ${ renderTypeReference(elm.items, env.withSubPath([ 'items' ])) }`;
|
|
1055
|
-
if (elm.items.notNull != null)
|
|
1056
|
-
rc += elm.items.notNull ? ' not null' : ' null';
|
|
1057
|
-
return rc;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
// FIXME: Is this a type attribute?
|
|
1061
|
-
result += (elm.localized ? 'localized ' : '');
|
|
1062
|
-
|
|
1063
|
-
// Anonymous structured type
|
|
1064
|
-
if (!elm.type && !elm.value) {
|
|
1065
|
-
if (!elm.elements)
|
|
1066
|
-
throw new ModelError(`Missing type of: ${ JSON.stringify(elm) }`);
|
|
1067
|
-
|
|
1068
|
-
result += '{\n';
|
|
1069
|
-
const childEnv = env.withIncreasedIndent();
|
|
1070
|
-
// omit "key" keyword for nested elements, as this will result in a deployment error in naming mode 'hdbcds'
|
|
1071
|
-
const dontRenderKeyForNestedElement = hdbcdsNames;
|
|
1072
|
-
for (const name in elm.elements)
|
|
1073
|
-
result += renderElement(name, elm.elements[name], childEnv.withSubPath([ 'elements', name ]), null, dontRenderKeyForNestedElement);
|
|
1074
|
-
|
|
1075
|
-
result += `${ env.indent }}`;
|
|
1076
|
-
return result;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// Association type
|
|
1080
|
-
if ([ 'cds.Association', 'cds.Composition' ].includes(elm.type))
|
|
1081
|
-
return result + renderAssociationType(elm, env);
|
|
1082
|
-
|
|
1083
|
-
if (elm.type?.ref) {
|
|
1084
|
-
// Reference to another element
|
|
1085
|
-
// For HANA CDS, we need a 'type of'
|
|
1086
|
-
result += `type of ${ renderAbsolutePath(elm.type, env.withSubPath([ 'type' ])) }`;
|
|
1087
|
-
}
|
|
1088
|
-
else if (isBuiltinType(elm.type)) {
|
|
1089
|
-
// If we get here, it must be a named type
|
|
1090
|
-
result += renderBuiltinType(elm);
|
|
1091
|
-
}
|
|
1092
|
-
else {
|
|
1093
|
-
// Simple absolute name
|
|
1094
|
-
// Type names are never flattened (derived types are unraveled in HANA)
|
|
1095
|
-
result += renderAbsoluteNameWithQuotes(elm.type, env.withSubPath([ 'type' ]));
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
if (elm.value) {
|
|
1099
|
-
if (!elm.value.stored)
|
|
1100
|
-
throw new CompilerAssertion('Found calculated element on-read in rendering; should have been replaced!');
|
|
1101
|
-
message('def-unsupported-calc-elem', env.path, { '#': 'hdbcds' });
|
|
1102
|
-
result += ` GENERATED ALWAYS AS ${ renderExpr(elm.value, env.withSubPath([ 'value' ])) }`;
|
|
1103
|
-
return result;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
return result;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
/**
|
|
1110
|
-
* @param {CSN.Element} elm
|
|
1111
|
-
* @param {HdbcdsRenderEnvironment} env
|
|
1112
|
-
* @returns {string}
|
|
1113
|
-
*/
|
|
1114
|
-
function renderAssociationType( elm, env ) {
|
|
1115
|
-
// Type, cardinality and target
|
|
1116
|
-
let result = `association${ renderCardinality(elm.cardinality) } to `;
|
|
1117
|
-
|
|
1118
|
-
// normal target or named aspect
|
|
1119
|
-
if (elm.target || elm.targetAspect && typeof elm.targetAspect === 'string') {
|
|
1120
|
-
// we might have a "using target as __target"
|
|
1121
|
-
const targetArtifact = csn.definitions[elm.target];
|
|
1122
|
-
const targetAlias = env.topLevelAliases[targetArtifact['@cds.persistence.name']];
|
|
1123
|
-
if (targetAlias) {
|
|
1124
|
-
result += targetAlias.quotedAlias;
|
|
1125
|
-
}
|
|
1126
|
-
else {
|
|
1127
|
-
const target = elm.target || elm.targetAspect;
|
|
1128
|
-
const childEnv = env.withSubPath([ elm.target ? 'target' : 'targetAspect' ]);
|
|
1129
|
-
result += plainNames ? renderAbsoluteNamePlain(target, childEnv) : renderAbsoluteNameWithQuotes(target, childEnv);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
// ON-condition (if any)
|
|
1134
|
-
if (elm.on) {
|
|
1135
|
-
result += ` on ${ renderExpr(elm.on, env.withSubPath([ 'on' ])) }`;
|
|
1136
|
-
}
|
|
1137
|
-
else if (elm.targetAspect?.elements) { // anonymous aspect
|
|
1138
|
-
const childEnv = env.withIncreasedIndent();
|
|
1139
|
-
result += '{\n';
|
|
1140
|
-
for (const name in elm.targetAspect.elements)
|
|
1141
|
-
result += renderElement(name, elm.targetAspect.elements[name], childEnv.withSubPath([ 'targetAspect', 'elements', name ]));
|
|
1142
|
-
|
|
1143
|
-
result += `${ env.indent }}`;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
// Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
|
|
1147
|
-
if (elm.keys && !elm.on)
|
|
1148
|
-
result += ` { ${ Object.keys(elm.keys).map(name => renderForeignKey(elm.keys[name], env.withSubPath([ 'keys', name ]))).join(', ') } }`;
|
|
1149
|
-
|
|
1150
|
-
return result;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
/**
|
|
1154
|
-
* Render a builtin type. cds.Integer => render as Integer (no quotes)
|
|
1155
|
-
* Map Decimal (w/o Prec/Scale) to cds.DecimalFloat for HANA CDS
|
|
1156
|
-
*
|
|
1157
|
-
* @param {CSN.Element} elm Element with the type
|
|
1158
|
-
* @returns {string} The rendered type
|
|
1159
|
-
*/
|
|
1160
|
-
function renderBuiltinType( elm ) {
|
|
1161
|
-
if (elm.type === 'cds.Decimal' && elm.scale === undefined && elm.precision === undefined)
|
|
1162
|
-
return 'DecimalFloat';
|
|
1163
|
-
|
|
1164
|
-
const type = cdsToHdbcdsTypes[elm.type] || elm.type;
|
|
1165
|
-
return type.replace(/^cds\./, '') + renderTypeParameters(elm);
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
/**
|
|
1169
|
-
* Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
|
|
1170
|
-
*
|
|
1171
|
-
* @param {string|object} s Path step
|
|
1172
|
-
* @param {number} idx Path position
|
|
1173
|
-
* @param {HdbcdsRenderEnvironment} env
|
|
1174
|
-
* @returns {string} Rendered path step
|
|
1175
|
-
*/
|
|
1176
|
-
function renderPathStep( s, idx, env ) {
|
|
1177
|
-
// Simple id or absolute name
|
|
1178
|
-
if (typeof s === 'string') {
|
|
1179
|
-
// HANA-specific extra magic (should actually be in forRelationalDB)
|
|
1180
|
-
// In HANA, we replace leading $self by the absolute name of the current artifact
|
|
1181
|
-
// (see FIXME at renderDefinition)
|
|
1182
|
-
if (idx === 0 && s === $SELF) {
|
|
1183
|
-
// do not produce USING for $projection
|
|
1184
|
-
if (env.currentArtifactName === $PROJECTION)
|
|
1185
|
-
return env.currentArtifactName;
|
|
1186
|
-
|
|
1187
|
-
return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
|
|
1188
|
-
: renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
// TODO: quote $parameters if it doesn't reference a parameter, this requires knowledge about the kind
|
|
1192
|
-
// Example: both views are correct in HANA CDS
|
|
1193
|
-
// entity E { key id: Integer; }
|
|
1194
|
-
// view EV with parameters P1: Integer as select from E { id, $parameters.P1 };
|
|
1195
|
-
// view EVp as select from E as "$parameters" { "$parameters".id };
|
|
1196
|
-
|
|
1197
|
-
if (idx === 0 &&
|
|
1198
|
-
[ $SELF, $PROJECTION, '$session' ].includes(s))
|
|
1199
|
-
return s;
|
|
1200
|
-
|
|
1201
|
-
return formatIdentifier(s);
|
|
1202
|
-
}
|
|
1203
|
-
// ID with filters or parameters
|
|
1204
|
-
else if (typeof s === 'object') {
|
|
1205
|
-
// Sanity check
|
|
1206
|
-
if (!s.func && !s.id)
|
|
1207
|
-
throw new ModelError(`Unknown path step object: ${ JSON.stringify(s) }`);
|
|
1208
|
-
|
|
1209
|
-
// Not really a path step but an object-like function call
|
|
1210
|
-
if (s.func)
|
|
1211
|
-
return `${ s.func }(${ renderArgs(s, '=>', env) })`;
|
|
1212
|
-
|
|
1213
|
-
// Path step, possibly with view parameters and/or filters
|
|
1214
|
-
let result = `${ formatIdentifier(s.id) }`;
|
|
1215
|
-
if (s.args) {
|
|
1216
|
-
// View parameters
|
|
1217
|
-
result += `(${ renderArgs(s, ':', env) })`;
|
|
1218
|
-
}
|
|
1219
|
-
if (s.where) {
|
|
1220
|
-
// Filter, possibly with cardinality
|
|
1221
|
-
const cardinality = s.cardinality ? `${ s.cardinality.max }: ` : '';
|
|
1222
|
-
result += `[${ cardinality }${ renderExpr(s.where, env.withSubPath([ 'where' ])) }]`;
|
|
1223
|
-
}
|
|
1224
|
-
return result;
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
throw new ModelError(`Unknown path step: ${ JSON.stringify(s) } at ${ JSON.stringify(env.path) }`);
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* @param {object} x Expression with a val and/or literal property
|
|
1232
|
-
* @returns {string} Rendered expression
|
|
1233
|
-
*/
|
|
1234
|
-
function renderExpressionLiteral( x ) {
|
|
1235
|
-
// Literal value, possibly with explicit 'literal' property
|
|
1236
|
-
switch (x.literal || typeof x.val) {
|
|
1237
|
-
case 'number':
|
|
1238
|
-
case 'boolean':
|
|
1239
|
-
case 'null':
|
|
1240
|
-
return x.val;
|
|
1241
|
-
case 'x':
|
|
1242
|
-
case 'date':
|
|
1243
|
-
case 'time':
|
|
1244
|
-
case 'timestamp':
|
|
1245
|
-
return `${ x.literal }'${ x.val }'`;
|
|
1246
|
-
case 'string':
|
|
1247
|
-
return renderStringForHdbcds(x.val);
|
|
1248
|
-
case 'object':
|
|
1249
|
-
if (x.val === null)
|
|
1250
|
-
return 'null';
|
|
1251
|
-
|
|
1252
|
-
// otherwise fall through to
|
|
1253
|
-
default:
|
|
1254
|
-
throw new ModelError(`Unknown literal or type: ${ JSON.stringify(x) }`);
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* Render the given expression x - which has a .func property
|
|
1260
|
-
*
|
|
1261
|
-
* @param {object} x
|
|
1262
|
-
* @returns {string}
|
|
1263
|
-
*/
|
|
1264
|
-
function renderExpressionFunc( x ) {
|
|
1265
|
-
const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
|
|
1266
|
-
const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
|
|
1267
|
-
// we can't quote functions with parens, issue warning if it is a reserved keyword
|
|
1268
|
-
if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
|
|
1269
|
-
warning(null, this.env.path, { id: uppercaseAndUnderscore(funcName) }, 'The identifier $(ID) is a SAP HANA keyword');
|
|
1270
|
-
return renderFunc(funcName, x, a => renderArgs(a, '=>', this.env), { options });
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
/**
|
|
1275
|
-
* Render a magic variable. Values are determined in following order:
|
|
1276
|
-
* 1. User defined replacement in options.variableReplacements
|
|
1277
|
-
* 2. Predefined fallback values
|
|
1278
|
-
* 3. Rendering of the variable as a string (i.e. its name) + warning
|
|
1279
|
-
*
|
|
1280
|
-
* @param {CSN.Path} ref
|
|
1281
|
-
* @param {object} env
|
|
1282
|
-
* @return {string}
|
|
1283
|
-
*/
|
|
1284
|
-
function renderMagicVariable( ref, env ) {
|
|
1285
|
-
const magicReplacement = getVariableReplacement(ref, options);
|
|
1286
|
-
if (magicReplacement !== null)
|
|
1287
|
-
return renderStringForHdbcds(magicReplacement);
|
|
1288
|
-
|
|
1289
|
-
const name = pathName(ref);
|
|
1290
|
-
const result = variableForDialect(options, name);
|
|
1291
|
-
if (result)
|
|
1292
|
-
return result;
|
|
1293
|
-
|
|
1294
|
-
if (isVariableReplacementRequired(name)) {
|
|
1295
|
-
reportedMissingReplacements[name] = true;
|
|
1296
|
-
error('ref-undefined-var', env.path, { '#': 'value', id: name, option: 'variableReplacements' });
|
|
1297
|
-
}
|
|
1298
|
-
else if (!reportedMissingReplacements[name]) {
|
|
1299
|
-
reportedMissingReplacements[name] = true;
|
|
1300
|
-
warning('ref-unsupported-variable', env.path, { name, option: 'variableReplacements' },
|
|
1301
|
-
'Variable $(NAME) is not supported. Use option $(OPTION) to specify a value for $(NAME)');
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
return renderStringForHdbcds(name);
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
/**
|
|
1308
|
-
* Must not be used for type refs, as something like `$user` will be interpreted as a magic
|
|
1309
|
-
* variable and not definition name.
|
|
1310
|
-
*
|
|
1311
|
-
* @param {object} x Expression with a ref property
|
|
1312
|
-
* @returns {string} Rendered expression
|
|
1313
|
-
* @todo no extra magic with x.param
|
|
1314
|
-
*/
|
|
1315
|
-
function renderExpressionRef( x ) {
|
|
1316
|
-
if (!x.param && isMagicVariable(pathId(x.ref[0])))
|
|
1317
|
-
return renderMagicVariable(x.ref, this.env);
|
|
1318
|
-
|
|
1319
|
-
const prefix = x.param ? ':' : '';
|
|
1320
|
-
const ref = x.ref.map((step, index) => renderPathStep(step, index, this.env.withSubPath([ 'ref', index ]))).join('.');
|
|
1321
|
-
return `${ prefix }${ ref }`;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
function renderTypeRef( x, env ) {
|
|
1325
|
-
const prefix = x.param ? ':' : '';
|
|
1326
|
-
const ref = x.ref.map((step, index) => renderPathStep(step, index, env.withSubPath([ 'ref', index ]))).join('.');
|
|
1327
|
-
return `${ prefix }${ ref }`;
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
/**
|
|
1331
|
-
* Render function arguments or view parameters (positional if array, named if object/dict),
|
|
1332
|
-
* using 'sep' as separator for positional parameters
|
|
1333
|
-
*
|
|
1334
|
-
* @param {object} node with `args` to render
|
|
1335
|
-
* @param {string} sep Separator between arguments
|
|
1336
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1337
|
-
* @returns {string} Rendered arguments
|
|
1338
|
-
*/
|
|
1339
|
-
function renderArgs( node, sep, env ) {
|
|
1340
|
-
const args = node.args || {};
|
|
1341
|
-
// Positional arguments
|
|
1342
|
-
if (Array.isArray(args)) {
|
|
1343
|
-
return args.map((arg, i) => renderExpr(arg, env.withSubPath([ 'args', i ]))).join(', ');
|
|
1344
|
-
}
|
|
1345
|
-
// Named arguments (object/dict)
|
|
1346
|
-
else if (typeof args === 'object') {
|
|
1347
|
-
// if this is a function param which is not a reference to the model, we must not quote it
|
|
1348
|
-
return Object.keys(args)
|
|
1349
|
-
.map(key => `${ node.func ? key : formatIdentifier(key) } ${ sep } ${ renderExpr(args[key], env.withSubPath([ 'args', key ])) }`)
|
|
1350
|
-
.join(', ');
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
throw new ModelError(`Unknown args: ${ JSON.stringify(args) }`);
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
/**
|
|
1358
|
-
* Render a cardinality (only those parts that were actually provided)
|
|
1359
|
-
*
|
|
1360
|
-
* @param {CSN.Cardinality} card Cardinality
|
|
1361
|
-
* @returns {string} Rendered cardinality
|
|
1362
|
-
*/
|
|
1363
|
-
function renderCardinality( card ) {
|
|
1364
|
-
if (!card)
|
|
1365
|
-
return '';
|
|
1366
|
-
|
|
1367
|
-
let result = '[';
|
|
1368
|
-
if (card.src !== undefined)
|
|
1369
|
-
result += `${ card.src }, `;
|
|
1370
|
-
|
|
1371
|
-
if (card.min !== undefined)
|
|
1372
|
-
result += `${ card.min }..`;
|
|
1373
|
-
|
|
1374
|
-
if (card.max !== undefined)
|
|
1375
|
-
result += card.max;
|
|
1376
|
-
|
|
1377
|
-
return `${ result }]`;
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
/**
|
|
1381
|
-
* Render the nullability of an element or parameter (can be unset, true, or false)
|
|
1382
|
-
*
|
|
1383
|
-
* @param {object} obj Thing to render for
|
|
1384
|
-
* @returns {string} null/not null
|
|
1385
|
-
*/
|
|
1386
|
-
function renderNullability( obj /* , env */) {
|
|
1387
|
-
if (obj.notNull === undefined) {
|
|
1388
|
-
// Attribute not set at all
|
|
1389
|
-
return '';
|
|
1390
|
-
}
|
|
1391
|
-
return obj.notNull ? ' not null' : ' null';
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
/**
|
|
1395
|
-
* Render a foreign key (no trailing LF)
|
|
1396
|
-
*
|
|
1397
|
-
* @todo Can this still happen after Hana transformation?
|
|
1398
|
-
*
|
|
1399
|
-
* @param {object} fKey Foreign key to render
|
|
1400
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1401
|
-
* @returns {string} Rendered foreign key
|
|
1402
|
-
*/
|
|
1403
|
-
function renderForeignKey( fKey, env ) {
|
|
1404
|
-
const alias = fKey.as ? (` as ${ formatIdentifier(fKey.as) }`) : '';
|
|
1405
|
-
return `${ renderExpr(fKey, env) }${ alias }`;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
/**
|
|
1409
|
-
* Render (primitive) type parameters of element 'elm', i.e.
|
|
1410
|
-
* length, precision and scale (even if incomplete), plus any other unknown ones.
|
|
1411
|
-
*
|
|
1412
|
-
* @param {CSN.Element} elm Element to render type parameters for
|
|
1413
|
-
* @returns {string} Rendered type parameters
|
|
1414
|
-
*/
|
|
1415
|
-
function renderTypeParameters( elm /* , env */) {
|
|
1416
|
-
const params = [];
|
|
1417
|
-
// Length, precision and scale (even if incomplete)
|
|
1418
|
-
if (elm.length !== undefined)
|
|
1419
|
-
params.push(elm.length);
|
|
1420
|
-
|
|
1421
|
-
if (elm.precision !== undefined)
|
|
1422
|
-
params.push(elm.precision);
|
|
1423
|
-
|
|
1424
|
-
if (elm.scale !== undefined)
|
|
1425
|
-
params.push(elm.scale);
|
|
1426
|
-
|
|
1427
|
-
if (elm.srid !== undefined)
|
|
1428
|
-
params.push(elm.srid);
|
|
1429
|
-
|
|
1430
|
-
return params.length === 0 ? '' : `(${ params.join(', ') })`;
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
/**
|
|
1434
|
-
* Render an absolute name in 'plain' mode, i.e. uppercased and underscored. Also record the
|
|
1435
|
-
* fact that 'absName' is used in 'env', so that an appropriate USING can be constructed
|
|
1436
|
-
* if necessary.
|
|
1437
|
-
*
|
|
1438
|
-
* @param {string} absName Absolute name
|
|
1439
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1440
|
-
* @returns {string} Uppercased and underscored absName
|
|
1441
|
-
*/
|
|
1442
|
-
function renderAbsoluteNamePlain( absName, env ) {
|
|
1443
|
-
// Add using declaration
|
|
1444
|
-
env.topLevelAliases[absName] = {
|
|
1445
|
-
quotedName: formatIdentifier(uppercaseAndUnderscore(absName)),
|
|
1446
|
-
quotedAlias: formatIdentifier(uppercaseAndUnderscore(absName)),
|
|
1447
|
-
};
|
|
1448
|
-
return formatIdentifier(uppercaseAndUnderscore(absName));
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
/**
|
|
1452
|
-
* Render an absolute name 'absName', with appropriate quotes. Also record the
|
|
1453
|
-
* fact that 'absName' is used in 'env', so that an appropriate USING can be constructed
|
|
1454
|
-
* if necessary.
|
|
1455
|
-
*
|
|
1456
|
-
* @param {string} absName absolute name
|
|
1457
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1458
|
-
* @returns {string} absName, with correct quotes
|
|
1459
|
-
*/
|
|
1460
|
-
function renderAbsoluteNameWithQuotes( absName, env ) {
|
|
1461
|
-
// Special case: If the top-level artifact name is not a valid artifact name, it came from an unchecked annotation
|
|
1462
|
-
// and must be left as it is (just quoted)
|
|
1463
|
-
let topLevelName = getRootArtifactName(absName, csn);
|
|
1464
|
-
const realName = getRealName(csn, absName);
|
|
1465
|
-
|
|
1466
|
-
if (realName === absName)
|
|
1467
|
-
topLevelName = absName;
|
|
1468
|
-
|
|
1469
|
-
if (!csn.definitions[topLevelName])
|
|
1470
|
-
return quotePathString(absName);
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
// Another special case: If we are rendering for HANA, and if the first path step is an artifact that is
|
|
1474
|
-
// 'implemented in' something, we need to treat the whole name like a top-level id.
|
|
1475
|
-
if (csn.definitions[absName]?.['@cds.persistence.exists']) {
|
|
1476
|
-
env.topLevelAliases[absName] = {
|
|
1477
|
-
quotedName: quoteAbsoluteNameAsId(absName),
|
|
1478
|
-
quotedAlias: quoteId(createTopLevelAliasName(absName)),
|
|
1479
|
-
};
|
|
1480
|
-
return env.topLevelAliases[absName].quotedAlias;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
// Retrieve or create a suitable alias name for the surrounding top-level artifact
|
|
1484
|
-
let topLevelAlias = env.topLevelAliases[topLevelName];
|
|
1485
|
-
if (!topLevelAlias) {
|
|
1486
|
-
env.topLevelAliases[topLevelName] = {
|
|
1487
|
-
quotedName: quoteAbsolutePathString(topLevelName),
|
|
1488
|
-
quotedAlias: quoteId(createTopLevelAliasName(topLevelName)),
|
|
1489
|
-
};
|
|
1490
|
-
topLevelAlias = env.topLevelAliases[topLevelName];
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
// Replace the top-level name with its alias
|
|
1494
|
-
if (absName === topLevelName) {
|
|
1495
|
-
return topLevelAlias.quotedAlias;
|
|
1496
|
-
}
|
|
1497
|
-
else if (csn.definitions[absName] && realName !== absName) {
|
|
1498
|
-
// special handling for names with dots
|
|
1499
|
-
|
|
1500
|
-
const prefix = absName.slice(0, absName.length - realName.length);
|
|
1501
|
-
const nonTopLevelPrefix = prefix.slice(topLevelName.length + 1, -1); // also trim off .
|
|
1502
|
-
if (nonTopLevelPrefix)
|
|
1503
|
-
return `${ topLevelAlias.quotedAlias }.${ quotePathString(nonTopLevelPrefix) }.${ quotePathString(realName) }`;
|
|
1504
|
-
|
|
1505
|
-
return `${ topLevelAlias.quotedAlias }.${ quotePathString(realName) }`;
|
|
1506
|
-
}
|
|
1507
|
-
return `${ topLevelAlias.quotedAlias }.${ quotePathString(realName) }`;
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
/**
|
|
1511
|
-
* Create a suitable alias name for a top-level artifact name. Ideally, it should not conflict with
|
|
1512
|
-
* any other identifier in the model and be somewhat recognizable and un-ugly...
|
|
1513
|
-
*
|
|
1514
|
-
* @todo check for conflicts instead of praying that it works...
|
|
1515
|
-
* @param {string} topLevelName Name of a top-level artifact
|
|
1516
|
-
* @returns {string} Appropriate __alias
|
|
1517
|
-
*/
|
|
1518
|
-
function createTopLevelAliasName( topLevelName ) {
|
|
1519
|
-
// FIXME: We should rather check for conflicts than just using something obscure like this ...
|
|
1520
|
-
return `__${ topLevelName.replace(/::/g, '__').replace(/\./g, '_') }`;
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
/**
|
|
1524
|
-
* Render appropriate USING directives for all artifacts used by artifact 'artifactName' in 'env'.
|
|
1525
|
-
*
|
|
1526
|
-
* @param {string} artifactName Artifact to render usings for
|
|
1527
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1528
|
-
* @returns {string} Usings for the given artifact
|
|
1529
|
-
*/
|
|
1530
|
-
function renderUsings( artifactName, env ) {
|
|
1531
|
-
const distinct = {};
|
|
1532
|
-
Object.keys(env.topLevelAliases)
|
|
1533
|
-
.filter(name => env.topLevelAliases[name].quotedAlias !== formatIdentifier(uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
|
|
1534
|
-
.forEach((name) => {
|
|
1535
|
-
const nativeObjectExists = csn.definitions[name]?.['@cds.persistence.exists'];
|
|
1536
|
-
if (!plainNames && nativeObjectExists)
|
|
1537
|
-
checkForNameClashesWithNativeObject(name);
|
|
1538
|
-
distinct[`using ${ env.topLevelAliases[name].quotedName } as ${ env.topLevelAliases[name].quotedAlias };\n`] = '';
|
|
1539
|
-
});
|
|
1540
|
-
/**
|
|
1541
|
-
* If we generate a `using <native object> from <bar>` clause,
|
|
1542
|
-
* we warn if we generate a SAP HANA CDS artifact which would hide the
|
|
1543
|
-
* native DB object from being found by the SAP HANA CDS compiler
|
|
1544
|
-
* see cap/cds-compiler#8269 for details
|
|
1545
|
-
* @param {string} name of the native db object
|
|
1546
|
-
*/
|
|
1547
|
-
function checkForNameClashesWithNativeObject( name ) {
|
|
1548
|
-
const possibleShadowName = getNamePrefix(env.topLevelAliases[name].quotedName);
|
|
1549
|
-
const mightBeShadowedBy = csn.definitions[possibleShadowName];
|
|
1550
|
-
if (mightBeShadowedBy) {
|
|
1551
|
-
const artifactWillBeRendered = isArtifactRendered(mightBeShadowedBy, possibleShadowName);
|
|
1552
|
-
// only warn if actually rendered to HANA CDS
|
|
1553
|
-
if (artifactWillBeRendered)
|
|
1554
|
-
warning('anno-hidden-exists', [ 'definitions', name ], { name: possibleShadowName }, 'Native database object is hidden by a definition starting with $(NAME)');
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
function isArtifactRendered( art, artName ) {
|
|
1559
|
-
const isHanaCdsContext = art.kind === 'service' || art.kind === 'context';
|
|
1560
|
-
if (isHanaCdsContext)
|
|
1561
|
-
return isContextRendered(artName);
|
|
1562
|
-
if ([ 'action', 'function', 'event' ].includes(art.kind) || options.sqlMapping !== 'hdbcds' && art.kind === 'type')
|
|
1563
|
-
return false;
|
|
1564
|
-
return !(art['@cds.persistence.exists'] || hasPersistenceSkipAnnotation(art));
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
/**
|
|
1568
|
-
* Check if there is at least one entity which will be rendered as SAP HANA CDS entity
|
|
1569
|
-
* inside the given context (or in its sub-contexts).
|
|
1570
|
-
* Or in other words: If the context will be rendered as a SAP HANA CDS context in the end.
|
|
1571
|
-
*
|
|
1572
|
-
* @param {string} contextName
|
|
1573
|
-
* @returns {boolean} true if a context/service will be rendered as a SAP HANA CDS context.
|
|
1574
|
-
*/
|
|
1575
|
-
function isContextRendered( contextName ) {
|
|
1576
|
-
const subArtifacts = getSubArtifacts(contextName);
|
|
1577
|
-
return Object.entries(subArtifacts).some(([ artName, art ]) => {
|
|
1578
|
-
if (art.kind === 'context')
|
|
1579
|
-
return isContextRendered(`${ contextName }.${ artName }`);
|
|
1580
|
-
return isArtifactRendered(art, artName);
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
/**
|
|
1585
|
-
* @param {string} usingName the string which appears in the `using <string> from ..` including the quotes
|
|
1586
|
-
* @returns the prefix of the `using` name.
|
|
1587
|
-
* @example
|
|
1588
|
-
* "com.sap.foo.native.object" --> com
|
|
1589
|
-
* "com.sap.foo::native.object" --> com.sap.foo.native
|
|
1590
|
-
*/
|
|
1591
|
-
function getNamePrefix( usingName ) {
|
|
1592
|
-
usingName = usingName.replace(/"/g, '');
|
|
1593
|
-
if (usingName.indexOf('::') !== -1) {
|
|
1594
|
-
const parts = usingName.split('::');
|
|
1595
|
-
return `${ parts[0] }.${ parts[1].split('.')[0] }`;
|
|
1596
|
-
}
|
|
1597
|
-
return usingName.split('.')[0];
|
|
1598
|
-
}
|
|
1599
|
-
return Object.keys(distinct).join('');
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
/**
|
|
1603
|
-
* Depending on the naming style, render the namespace declaration for a top-level artifact 'name'
|
|
1604
|
-
* if it has a namespace parent. Assume that this is only called for top-level artifacts.
|
|
1605
|
-
* - For 'quoted' and 'hdbcds' names, render the namespace declaration (resulting in '.' or '::' style names)
|
|
1606
|
-
* - For 'plain' names, do not render anything (namespace already part of flattened names).
|
|
1607
|
-
* Return the namespace declaration (with trailing LF) or an empty string.
|
|
1608
|
-
*
|
|
1609
|
-
* @param {string} topLevelName Name of a top-level artifact
|
|
1610
|
-
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1611
|
-
* @returns {string} Rendered namespace declaration
|
|
1612
|
-
*/
|
|
1613
|
-
function renderNamespaceDeclaration( topLevelName, env ) {
|
|
1614
|
-
if (plainNames) {
|
|
1615
|
-
// No namespaces in plain mode
|
|
1616
|
-
return '';
|
|
1617
|
-
}
|
|
1618
|
-
// The top-level artifact's parent would be the namespace (if any)
|
|
1619
|
-
const namespace = getNamespace(csn, topLevelName);
|
|
1620
|
-
if (namespace)
|
|
1621
|
-
return `${ env.indent }namespace ${ quotePathString(namespace) };\n`;
|
|
1622
|
-
|
|
1623
|
-
return '';
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
/**
|
|
1627
|
-
* Return a dictionary of top-level artifacts contained in the model (by their name)
|
|
1628
|
-
*
|
|
1629
|
-
* @returns {CSN.Definitions} Dictionary of top-level artifacts name:content
|
|
1630
|
-
*/
|
|
1631
|
-
function getTopLevelArtifacts() {
|
|
1632
|
-
const result = Object.create(null);
|
|
1633
|
-
for (const name in csn.definitions) {
|
|
1634
|
-
if (plainNames) {
|
|
1635
|
-
const art = csn.definitions[name];
|
|
1636
|
-
// For 'plain' naming, take all entities and views, nothing else
|
|
1637
|
-
if (art.kind === 'entity')
|
|
1638
|
-
result[name] = art;
|
|
1639
|
-
}
|
|
1640
|
-
else {
|
|
1641
|
-
// For all other naming conventions, take all top-level artifacts except namespaces
|
|
1642
|
-
const topLevelName = getRootArtifactName(name, csn);
|
|
1643
|
-
const topLevelArtifact = csn.definitions[topLevelName];
|
|
1644
|
-
if (topLevelArtifact && topLevelArtifact.kind !== 'namespace')
|
|
1645
|
-
result[topLevelName] = topLevelArtifact;
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
return options && options.testMode ? sort(result) : result;
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
/**
|
|
1652
|
-
* Returns a newly created default environment (which keeps track of indentation, required USING
|
|
1653
|
-
* declarations and name prefixes.
|
|
1654
|
-
*
|
|
1655
|
-
* @param {object} [values]
|
|
1656
|
-
* @returns {HdbcdsRenderEnvironment} Fresh environment
|
|
1657
|
-
*/
|
|
1658
|
-
function createEnv( values = {} ) {
|
|
1659
|
-
return new HdbcdsRenderEnvironment(values);
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
/**
|
|
1663
|
-
* Returns a copy of 'env' with (quoted) name prefix 'id' and a dot appended to the current name prefix
|
|
1664
|
-
*
|
|
1665
|
-
* @param {HdbcdsRenderEnvironment} env Current environment
|
|
1666
|
-
* @param {string} id Name prefix to add
|
|
1667
|
-
* @returns {HdbcdsRenderEnvironment} New environment with added prefix
|
|
1668
|
-
*/
|
|
1669
|
-
function addNamePrefix( env, id ) {
|
|
1670
|
-
return env.cloneWith({ namePrefix: `${ env.namePrefix + quoteId(id) }.` });
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
/**
|
|
1674
|
-
* Return a path string 'path' with appropriate "-quotes.
|
|
1675
|
-
*
|
|
1676
|
-
* @param {string} path Path to quote
|
|
1677
|
-
* @returns {string} Quoted path
|
|
1678
|
-
*/
|
|
1679
|
-
function quotePathString( path ) {
|
|
1680
|
-
// "foo"."bar"."wiz"."blub"
|
|
1681
|
-
return path.split('.').map(quoteId).join('.');
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
/**
|
|
1685
|
-
* Return an absolute path 'absPath', with '::' inserted if required by naming strategy 'hdbcds',
|
|
1686
|
-
* with appropriate "-quotes
|
|
1687
|
-
*
|
|
1688
|
-
* @param {string} absPath Absolute path to quote
|
|
1689
|
-
* @returns {string} Quoted path
|
|
1690
|
-
*/
|
|
1691
|
-
function quoteAbsolutePathString( absPath ) {
|
|
1692
|
-
const namespace = getNamespace(csn, absPath);
|
|
1693
|
-
const resultingName = getResultingName(csn, options.sqlMapping, absPath);
|
|
1694
|
-
|
|
1695
|
-
if (hdbcdsNames && namespace)
|
|
1696
|
-
return `${ quotePathString(namespace) }::${ quotePathString(resultingName.slice(namespace.length + 2)) }`;
|
|
1697
|
-
|
|
1698
|
-
return quotePathString(resultingName);
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
/**
|
|
1702
|
-
* Return an id 'id' with appropriate double-quotes
|
|
1703
|
-
*
|
|
1704
|
-
* @param {string} id Identifier to quote
|
|
1705
|
-
* @returns {string} Properly quoted identifier
|
|
1706
|
-
*/
|
|
1707
|
-
function quoteId( id ) {
|
|
1708
|
-
switch (options.sqlMapping) {
|
|
1709
|
-
case 'plain':
|
|
1710
|
-
return smartId(id, 'hdbcds');
|
|
1711
|
-
case 'quoted':
|
|
1712
|
-
case 'hdbcds':
|
|
1713
|
-
default:
|
|
1714
|
-
return delimitedId(id, 'hdbcds');
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
/*
|
|
1719
|
-
* Return an absolute name 'absName', with '::' inserted if required by naming strategy 'hdbcds', quoted
|
|
1720
|
-
* as if it was a single identifier (required only for native USINGs)
|
|
1721
|
-
*
|
|
1722
|
-
* @param {string} absName Absolute name
|
|
1723
|
-
* @returns {string} Correctly quoted absName
|
|
1724
|
-
*/
|
|
1725
|
-
function quoteAbsoluteNameAsId( absName ) {
|
|
1726
|
-
const resultingName = getResultingName(csn, options.sqlMapping, absName);
|
|
1727
|
-
|
|
1728
|
-
if (hdbcdsNames) {
|
|
1729
|
-
const namespace = getNamespace(csn, absName);
|
|
1730
|
-
if (namespace) {
|
|
1731
|
-
const id = `${ namespace }::${ resultingName.substring(namespace.length + 2) }`;
|
|
1732
|
-
return `"${ id.replace(/"/g, '""') }"`;
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
return `"${ resultingName.replace(/"/g, '""') }"`;
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
/**
|
|
1739
|
-
* Quote and/or uppercase an identifier 'id', depending on naming strategy
|
|
1740
|
-
*
|
|
1741
|
-
* @param {string} id Identifier
|
|
1742
|
-
* @returns {string} Quoted/uppercased id
|
|
1743
|
-
*/
|
|
1744
|
-
function formatIdentifier( id ) {
|
|
1745
|
-
id = plainNames ? id.toUpperCase() : id;
|
|
1746
|
-
return quoteId(id);
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
/**
|
|
1750
|
-
* Quote or uppercase a parameter identifier 'id', depending on naming strategy
|
|
1751
|
-
* Smart quoting cannot be applied to the parameter identifiers, issue warning instead.
|
|
1752
|
-
*
|
|
1753
|
-
*
|
|
1754
|
-
* @param {string} id Identifier
|
|
1755
|
-
* @param {CSN.Path} [location] Optional location for the warning.
|
|
1756
|
-
* @returns {string} Quoted/uppercased id
|
|
1757
|
-
*/
|
|
1758
|
-
function formatParamIdentifier( id, location ) {
|
|
1759
|
-
// Warn if colliding with HANA keyword, but do not quote for plain
|
|
1760
|
-
// --> quoted reserved words as param lead to a weird deployment error
|
|
1761
|
-
if (keywords.hdbcds.includes(uppercaseAndUnderscore(id)))
|
|
1762
|
-
warning(null, location, { id }, 'The identifier $(ID) is a SAP HANA keyword');
|
|
1763
|
-
|
|
1764
|
-
if (plainNames)
|
|
1765
|
-
return uppercaseAndUnderscore(id);
|
|
1766
|
-
|
|
1767
|
-
return quoteId(id);
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
/**
|
|
1771
|
-
* Render the name of an artifact, using the current name prefix from 'env'
|
|
1772
|
-
* and the real name of the artifact. In case of plain names, this
|
|
1773
|
-
* is equivalent to simply flattening and uppercasing the whole name.
|
|
1774
|
-
*
|
|
1775
|
-
* To handle such cases for hdbcds in quoted/hdbcds, we:
|
|
1776
|
-
* - Find the part of the name that is no longer prefix (context/service/namespace)
|
|
1777
|
-
* - For Service.E -> E, for Service.E.Sub -> E.Sub
|
|
1778
|
-
* - Replace all dots in this "real name" with underscores
|
|
1779
|
-
* - Join with the env prefix
|
|
1780
|
-
*
|
|
1781
|
-
*
|
|
1782
|
-
* @param {string} artifactName Artifact name to render
|
|
1783
|
-
* @param {HdbcdsRenderEnvironment} env Render environment
|
|
1784
|
-
* @param {boolean} [fallthrough=false] For certain artifacts, plain-rendering is supposed to look like quoted/hdbcds
|
|
1785
|
-
* @returns {string} Artifact name ready for rendering
|
|
1786
|
-
*/
|
|
1787
|
-
function renderArtifactName( artifactName, env, fallthrough = false ) {
|
|
1788
|
-
if (plainNames && !fallthrough)
|
|
1789
|
-
return formatIdentifier(uppercaseAndUnderscore(artifactName));
|
|
1790
|
-
// hdbcds with quoted or hdbcds naming
|
|
1791
|
-
return env.namePrefix + quoteId(getRealName(csn, artifactName).replace(/\./g, '_'));
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
/**
|
|
1795
|
-
* For 'name', replace '.' by '_', convert to uppercase, and add double-quotes if
|
|
1796
|
-
* required because of non-leading '$' (but do not consider leading '$', other special
|
|
1797
|
-
* characters, or SQL keywords/functions - somewhat weird but this retains maximum
|
|
1798
|
-
* compatibility with a future hdbtable-based solution and with sqlite, where non-leading
|
|
1799
|
-
* '$' is legal again but nothing else)
|
|
1800
|
-
*
|
|
1801
|
-
* @param {string} name Name to transform
|
|
1802
|
-
* @returns {string} Uppercased and underscored name
|
|
1803
|
-
*/
|
|
1804
|
-
function uppercaseAndUnderscore( name ) {
|
|
1805
|
-
// Always replace '.' by '_' and uppercase
|
|
1806
|
-
return name.replace(/\./g, '_').toUpperCase();
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
module.exports = { toHdbcdsSource };
|