@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +86 -2
  2. package/bin/cdsc.js +4 -33
  3. package/doc/IncompatibleChanges_v7.md +639 -0
  4. package/lib/api/main.js +4 -56
  5. package/lib/api/options.js +6 -15
  6. package/lib/api/validate.js +1 -0
  7. package/lib/base/builtins.js +1 -2
  8. package/lib/base/csnRefs.js +2 -6
  9. package/lib/base/message-registry.js +82 -76
  10. package/lib/base/messages.js +23 -4
  11. package/lib/base/optionProcessor.js +2 -72
  12. package/lib/base/specialOptions.js +20 -17
  13. package/lib/checks/defaultValues.js +1 -39
  14. package/lib/checks/hasPersistedElements.js +19 -3
  15. package/lib/checks/parameters.js +0 -34
  16. package/lib/checks/selectItems.js +2 -38
  17. package/lib/checks/typeParameters.js +162 -0
  18. package/lib/checks/validator.js +5 -8
  19. package/lib/compiler/assert-consistency.js +19 -5
  20. package/lib/compiler/checks.js +47 -43
  21. package/lib/compiler/define.js +6 -6
  22. package/lib/compiler/extend.js +102 -111
  23. package/lib/compiler/generate.js +4 -8
  24. package/lib/compiler/populate.js +4 -7
  25. package/lib/compiler/propagator.js +9 -9
  26. package/lib/compiler/resolve.js +205 -7
  27. package/lib/compiler/shared.js +76 -82
  28. package/lib/compiler/tweak-assocs.js +102 -22
  29. package/lib/compiler/utils.js +57 -12
  30. package/lib/compiler/xpr-rewrite.js +2 -15
  31. package/lib/edm/annotations/edmJson.js +14 -10
  32. package/lib/edm/annotations/genericTranslation.js +3 -1
  33. package/lib/edm/annotations/preprocessAnnotations.js +9 -26
  34. package/lib/edm/csn2edm.js +27 -20
  35. package/lib/edm/edmUtils.js +25 -0
  36. package/lib/gen/CdlGrammar.checksum +1 -1
  37. package/lib/gen/CdlParser.js +2237 -2241
  38. package/lib/gen/Dictionary.json +17 -2
  39. package/lib/json/from-csn.js +67 -52
  40. package/lib/json/to-csn.js +28 -25
  41. package/lib/language/textUtils.js +0 -13
  42. package/lib/main.d.ts +34 -59
  43. package/lib/main.js +1 -1
  44. package/lib/model/csnUtils.js +9 -8
  45. package/lib/parsers/AstBuildingParser.js +45 -55
  46. package/lib/parsers/Lexer.js +2 -0
  47. package/lib/parsers/identifiers.js +0 -9
  48. package/lib/render/toCdl.js +41 -40
  49. package/lib/render/toSql.js +8 -1
  50. package/lib/render/utils/common.js +1 -1
  51. package/lib/render/utils/sql.js +2 -3
  52. package/lib/tool-lib/enrichCsn.js +1 -2
  53. package/lib/transform/db/applyTransformations.js +7 -5
  54. package/lib/transform/db/assertUnique.js +8 -51
  55. package/lib/transform/db/associations.js +1 -1
  56. package/lib/transform/db/cdsPersistence.js +1 -15
  57. package/lib/transform/db/expansion.js +9 -12
  58. package/lib/transform/db/flattening.js +1 -1
  59. package/lib/transform/db/groupByOrderBy.js +0 -16
  60. package/lib/transform/db/views.js +57 -161
  61. package/lib/transform/draft/db.js +2 -2
  62. package/lib/transform/forOdata.js +25 -14
  63. package/lib/transform/forRelationalDB.js +93 -301
  64. package/lib/transform/localized.js +33 -102
  65. package/lib/transform/odata/flattening.js +11 -2
  66. package/lib/transform/transformUtils.js +25 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
  68. package/package.json +2 -2
  69. package/lib/render/toHdbcds.js +0 -1810
@@ -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 };