@sap/cds-compiler 3.9.4 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -4
- package/README.md +0 -1
- package/bin/cdsc.js +11 -23
- package/bin/cdsse.js +3 -3
- package/doc/API.md +5 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +17 -1
- package/doc/CHANGELOG_DEPRECATED.md +28 -0
- package/lib/api/.eslintrc.json +1 -1
- package/lib/api/main.js +55 -9
- package/lib/api/options.js +2 -0
- package/lib/base/error.js +2 -0
- package/lib/base/message-registry.js +143 -64
- package/lib/base/messages.js +213 -107
- package/lib/base/model.js +11 -11
- package/lib/checks/.eslintrc.json +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/elements.js +1 -1
- package/lib/checks/enricher.js +26 -3
- package/lib/checks/onConditions.js +67 -12
- package/lib/checks/queryNoDbArtifacts.js +106 -105
- package/lib/checks/sql-snippets.js +2 -0
- package/lib/checks/types.js +12 -6
- package/lib/checks/validator.js +2 -2
- package/lib/compiler/assert-consistency.js +10 -8
- package/lib/compiler/builtins.js +8 -2
- package/lib/compiler/checks.js +52 -35
- package/lib/compiler/define.js +31 -26
- package/lib/compiler/extend.js +120 -65
- package/lib/compiler/finalize-parse-cdl.js +12 -43
- package/lib/compiler/generate.js +16 -5
- package/lib/compiler/index.js +8 -5
- package/lib/compiler/kick-start.js +4 -3
- package/lib/compiler/populate.js +96 -95
- package/lib/compiler/propagator.js +7 -8
- package/lib/compiler/resolve.js +377 -103
- package/lib/compiler/shared.js +794 -517
- package/lib/compiler/tweak-assocs.js +8 -6
- package/lib/compiler/utils.js +44 -0
- package/lib/edm/annotations/genericTranslation.js +41 -5
- package/lib/edm/csn2edm.js +34 -32
- package/lib/edm/edm.js +34 -31
- package/lib/edm/edmAnnoPreprocessor.js +0 -23
- package/lib/edm/edmInboundChecks.js +7 -2
- package/lib/edm/edmPreprocessor.js +25 -18
- package/lib/edm/edmUtils.js +8 -4
- package/lib/gen/Dictionary.json +18 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -2
- package/lib/gen/languageParser.js +5006 -4582
- package/lib/json/from-csn.js +157 -112
- package/lib/json/to-csn.js +60 -89
- package/lib/language/antlrParser.js +17 -13
- package/lib/language/docCommentParser.js +11 -1
- package/lib/language/genericAntlrParser.js +13 -10
- package/lib/language/language.g4 +168 -97
- package/lib/main.d.ts +128 -36
- package/lib/main.js +1 -1
- package/lib/model/csnRefs.js +24 -5
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +7 -12
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +1 -1
- package/lib/modelCompare/utils/filter.js +40 -2
- package/lib/optionProcessor.js +0 -3
- package/lib/render/toCdl.js +247 -214
- package/lib/render/toHdbcds.js +197 -181
- package/lib/render/toSql.js +325 -289
- package/lib/render/utils/common.js +42 -4
- package/lib/render/utils/delta.js +1 -1
- package/lib/render/utils/sql.js +3 -3
- package/lib/transform/braceExpression.js +2 -2
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/associations.js +24 -12
- package/lib/transform/db/expansion.js +17 -18
- package/lib/transform/db/flattening.js +17 -21
- package/lib/transform/db/rewriteCalculatedElements.js +171 -64
- package/lib/transform/db/views.js +3 -4
- package/lib/transform/draft/db.js +21 -12
- package/lib/transform/draft/odata.js +4 -0
- package/lib/transform/forOdataNew.js +62 -47
- package/lib/transform/forRelationalDB.js +12 -7
- package/lib/transform/localized.js +4 -2
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/parseExpr.js +3 -0
- package/lib/transform/transformUtilsNew.js +43 -23
- package/lib/transform/translateAssocsToJoins.js +7 -6
- package/lib/transform/universalCsn/.eslintrc.json +1 -1
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
- package/package.json +2 -2
- package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
- package/share/messages/message-explanations.json +1 -1
package/lib/render/toHdbcds.js
CHANGED
|
@@ -26,6 +26,42 @@ const { ModelError, CompilerAssertion } = require('../base/error');
|
|
|
26
26
|
const $PROJECTION = '$projection';
|
|
27
27
|
const $SELF = '$self';
|
|
28
28
|
|
|
29
|
+
|
|
30
|
+
// TODO: Unify with other RenderEnvironments
|
|
31
|
+
class HdbcdsRenderEnvironment {
|
|
32
|
+
indent = '';
|
|
33
|
+
path = null;
|
|
34
|
+
/**
|
|
35
|
+
* Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
|
|
36
|
+
* @type {{[name: string]: {
|
|
37
|
+
* quotedName: string,
|
|
38
|
+
* quotedAlias: string
|
|
39
|
+
* }}}
|
|
40
|
+
*/
|
|
41
|
+
topLevelAliases = Object.create(null);
|
|
42
|
+
// Current name prefix (including trailing dot if not empty)
|
|
43
|
+
namePrefix = '';
|
|
44
|
+
// Skip rendering keys in subqueries
|
|
45
|
+
skipKeys = false;
|
|
46
|
+
currentArtifactName = null;
|
|
47
|
+
// The original view artifact, used when rendering queries
|
|
48
|
+
_artifact = null;
|
|
49
|
+
|
|
50
|
+
constructor(values) {
|
|
51
|
+
Object.assign(this, values);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
withIncreasedIndent() {
|
|
55
|
+
return new HdbcdsRenderEnvironment({ ...this, namePrefix: '', indent: ` ${this.indent}` });
|
|
56
|
+
}
|
|
57
|
+
withSubPath(path) {
|
|
58
|
+
return new HdbcdsRenderEnvironment({ ...this, path: [ ...this.path, ...path ] });
|
|
59
|
+
}
|
|
60
|
+
cloneWith(values) {
|
|
61
|
+
return Object.assign(new HdbcdsRenderEnvironment(this), values);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
29
65
|
/**
|
|
30
66
|
* Get the comment and in addition escape \n and `'` so SAP HANA CDS can handle it.
|
|
31
67
|
*
|
|
@@ -33,7 +69,9 @@ const $SELF = '$self';
|
|
|
33
69
|
* @returns {string}
|
|
34
70
|
*/
|
|
35
71
|
function getEscapedHanaComment( obj ) {
|
|
36
|
-
return getHanaComment(obj)
|
|
72
|
+
return getHanaComment(obj)
|
|
73
|
+
.replace(/\n/g, '\\n')
|
|
74
|
+
.replace(/'/g, "''");
|
|
37
75
|
}
|
|
38
76
|
|
|
39
77
|
/**
|
|
@@ -66,7 +104,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
66
104
|
const exprRenderer = createExpressionRenderer({
|
|
67
105
|
finalize: x => x,
|
|
68
106
|
typeCast(x) {
|
|
69
|
-
let typeRef = renderTypeReference(x.cast, this.env);
|
|
107
|
+
let typeRef = renderTypeReference(x.cast, this.env.withSubPath([ 'cast' ]));
|
|
70
108
|
// inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
|
|
71
109
|
const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
|
|
72
110
|
if (hanaSqlType) {
|
|
@@ -82,17 +120,18 @@ function toHdbcdsSource( csn, options ) {
|
|
|
82
120
|
windowFunction: renderExpressionFunc,
|
|
83
121
|
func: renderExpressionFunc,
|
|
84
122
|
xpr(x) {
|
|
123
|
+
const xprEnv = this.env.withSubPath([ 'xpr' ]);
|
|
85
124
|
if (this.isNestedXpr && !x.cast)
|
|
86
|
-
return `(${this.renderSubExpr(x.xpr)})`;
|
|
87
|
-
return this.renderSubExpr(x.xpr);
|
|
125
|
+
return `(${this.renderSubExpr(x.xpr, xprEnv)})`;
|
|
126
|
+
return this.renderSubExpr(x.xpr, xprEnv);
|
|
88
127
|
},
|
|
89
128
|
SELECT(x) {
|
|
90
|
-
return `(${renderQuery(x, false,
|
|
129
|
+
return `(${renderQuery(x, false, this.env.withIncreasedIndent())})`;
|
|
91
130
|
},
|
|
92
131
|
SET(x) {
|
|
93
|
-
return `${renderQuery(x, false,
|
|
132
|
+
return `${renderQuery(x, false, this.env.withIncreasedIndent())}`;
|
|
94
133
|
},
|
|
95
|
-
});
|
|
134
|
+
}, true);
|
|
96
135
|
|
|
97
136
|
function renderExpr( x, env ) {
|
|
98
137
|
return exprRenderer.renderExpr(x, env);
|
|
@@ -118,7 +157,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
118
157
|
// This environment is passed down the call hierarchy, for dealing with
|
|
119
158
|
// indentation and name resolution issues
|
|
120
159
|
const env = createEnv();
|
|
121
|
-
const sourceStr =
|
|
160
|
+
const sourceStr = renderDefinition(artifactName, art, env); // Must come first because it populates 'env.topLevelAliases'
|
|
122
161
|
if (sourceStr !== '') {
|
|
123
162
|
const name = plainNames ? artifactName.replace(/\./g, '_').toUpperCase() : artifactName;
|
|
124
163
|
hdbcds[name] = [
|
|
@@ -138,7 +177,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
138
177
|
Object.entries(art.$tableConstraints.referential)
|
|
139
178
|
.forEach(([ fileName, referentialConstraint ]) => {
|
|
140
179
|
referentialConstraints[fileName] = renderReferentialConstraint(
|
|
141
|
-
referentialConstraint,
|
|
180
|
+
referentialConstraint, createEnv().withIncreasedIndent().indent, false, csn, options
|
|
142
181
|
);
|
|
143
182
|
});
|
|
144
183
|
Object.entries(referentialConstraints)
|
|
@@ -175,14 +214,14 @@ function toHdbcdsSource( csn, options ) {
|
|
|
175
214
|
}
|
|
176
215
|
|
|
177
216
|
/**
|
|
178
|
-
* Render
|
|
217
|
+
* Render a definition. Return the resulting source string.
|
|
179
218
|
*
|
|
180
219
|
* @param {string} artifactName Name of the artifact to render
|
|
181
220
|
* @param {CSN.Artifact} art Content of the artifact to render
|
|
182
|
-
* @param {
|
|
221
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
183
222
|
* @returns {string} The rendered artifact
|
|
184
223
|
*/
|
|
185
|
-
function
|
|
224
|
+
function renderDefinition( artifactName, art, env ) {
|
|
186
225
|
// We're always a top-level artifact.
|
|
187
226
|
env.path = [ 'definitions', artifactName ];
|
|
188
227
|
// Ignore whole artifacts if toHana says so
|
|
@@ -203,7 +242,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
203
242
|
|
|
204
243
|
case 'context':
|
|
205
244
|
case 'service':
|
|
206
|
-
return renderContext(artifactName, art, env);
|
|
245
|
+
return renderContext(artifactName, art, env, false);
|
|
207
246
|
case 'namespace':
|
|
208
247
|
return renderNamespace(artifactName, art, env);
|
|
209
248
|
case 'type':
|
|
@@ -322,7 +361,8 @@ function toHdbcdsSource( csn, options ) {
|
|
|
322
361
|
*
|
|
323
362
|
* @param {string} artifactName Name of the context/service
|
|
324
363
|
* @param {CSN.Artifact} art Content of the context/service
|
|
325
|
-
* @param {
|
|
364
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
365
|
+
* @param {boolean} isShadowed
|
|
326
366
|
* @returns {string} The rendered context/service
|
|
327
367
|
*/
|
|
328
368
|
function renderContext( artifactName, art, env, isShadowed ) {
|
|
@@ -332,24 +372,25 @@ function toHdbcdsSource( csn, options ) {
|
|
|
332
372
|
if (isShadowed) {
|
|
333
373
|
const subArtifacts = getSubArtifacts(artifactName);
|
|
334
374
|
for (const name in subArtifacts)
|
|
335
|
-
result +=
|
|
375
|
+
result += renderDefinition(`${artifactName}.${name}`, subArtifacts[name], env);
|
|
336
376
|
|
|
337
377
|
return `${result}\n`;
|
|
338
378
|
}
|
|
339
379
|
|
|
340
|
-
const childEnv =
|
|
380
|
+
const childEnv = env.withIncreasedIndent();
|
|
341
381
|
result += `${env.indent}context ${renderArtifactName(artifactName, env, true)}`;
|
|
342
382
|
result += ' {\n';
|
|
343
383
|
const subArtifacts = getSubArtifacts(artifactName);
|
|
344
384
|
let renderedSubArtifacts = '';
|
|
345
385
|
for (const name in subArtifacts)
|
|
346
|
-
renderedSubArtifacts +=
|
|
386
|
+
renderedSubArtifacts += renderDefinition(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
|
|
347
387
|
|
|
348
388
|
if (renderedSubArtifacts === '')
|
|
349
389
|
return '';
|
|
350
390
|
|
|
351
391
|
return `${result + renderedSubArtifacts + env.indent}};\n`;
|
|
352
392
|
}
|
|
393
|
+
|
|
353
394
|
/**
|
|
354
395
|
* Check whether the given context is shadowed, i.e. part of his name prefix is shared by a
|
|
355
396
|
* non-context/service/namespace definition
|
|
@@ -375,9 +416,9 @@ function toHdbcdsSource( csn, options ) {
|
|
|
375
416
|
* In case of an artifact with . in the name (that are not a namespace/context part),
|
|
376
417
|
* we need to update the env to correctly render the artifact name.
|
|
377
418
|
*
|
|
378
|
-
* @param {
|
|
419
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
379
420
|
* @param {string} name Possibly dotted artifact name
|
|
380
|
-
* @returns {
|
|
421
|
+
* @returns {HdbcdsRenderEnvironment} Updated env or original instance
|
|
381
422
|
*/
|
|
382
423
|
function updatePrefixForDottedName( env, name ) {
|
|
383
424
|
if (plainNames) {
|
|
@@ -398,7 +439,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
398
439
|
*
|
|
399
440
|
* @param {string} artifactName Name of the namespace
|
|
400
441
|
* @param {CSN.Artifact} art Content of the namespace
|
|
401
|
-
* @param {
|
|
442
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
402
443
|
* @returns {string} The rendered children of the namespace
|
|
403
444
|
*/
|
|
404
445
|
function renderNamespace( artifactName, art, env ) {
|
|
@@ -408,22 +449,22 @@ function toHdbcdsSource( csn, options ) {
|
|
|
408
449
|
const childEnv = addNamePrefix(env, getLastPartOf(artifactName));
|
|
409
450
|
const subArtifacts = getSubArtifacts(artifactName);
|
|
410
451
|
for (const name in subArtifacts)
|
|
411
|
-
result +=
|
|
452
|
+
result += renderDefinition(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
|
|
412
453
|
|
|
413
454
|
return result;
|
|
414
455
|
}
|
|
415
456
|
|
|
416
457
|
/**
|
|
417
|
-
* Render a
|
|
458
|
+
* Render a non-query entity. Return the resulting source string.
|
|
418
459
|
*
|
|
419
460
|
* @param {string} artifactName Name of the entity
|
|
420
461
|
* @param {CSN.Artifact} art Content of the entity
|
|
421
|
-
* @param {
|
|
462
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
422
463
|
* @returns {string} The rendered entity
|
|
423
464
|
*/
|
|
424
465
|
function renderEntity( artifactName, art, env ) {
|
|
425
466
|
let result = '';
|
|
426
|
-
const childEnv =
|
|
467
|
+
const childEnv = env.withIncreasedIndent();
|
|
427
468
|
const normalizedArtifactName = renderArtifactName(artifactName, env);
|
|
428
469
|
|
|
429
470
|
globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], env.path, artifactName);
|
|
@@ -440,17 +481,16 @@ function toHdbcdsSource( csn, options ) {
|
|
|
440
481
|
result += `${env.indent + (art.abstract ? 'abstract ' : '')}entity ${normalizedArtifactName}`;
|
|
441
482
|
if (art.includes) {
|
|
442
483
|
// Includes are never flattened (don't exist in HANA)
|
|
443
|
-
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name, env)).join(', ')}`;
|
|
484
|
+
result += ` : ${art.includes.map((name, i) => renderAbsoluteNameWithQuotes(name, env.withSubPath([ 'includes', i ]))).join(', ')}`;
|
|
444
485
|
}
|
|
445
486
|
result += ' {\n';
|
|
446
487
|
const duplicateChecker = new DuplicateChecker(); // registry for all artifact names and element names
|
|
447
488
|
duplicateChecker.addArtifact(artifactName, env.path, artifactName);
|
|
448
|
-
childEnv.path = env.path.concat('elements');
|
|
449
489
|
// calculate __aliases which must be used in case an association
|
|
450
490
|
// has the same identifier as it's target
|
|
451
491
|
createTopLevelAliasesForArtifact(artifactName, art, env);
|
|
452
492
|
for (const name in art.elements)
|
|
453
|
-
result += renderElement(name, art.elements[name], childEnv, duplicateChecker);
|
|
493
|
+
result += renderElement(name, art.elements[name], childEnv.withSubPath([ 'elements', name ]), duplicateChecker);
|
|
454
494
|
|
|
455
495
|
duplicateChecker.check(error);
|
|
456
496
|
result += `${env.indent}}`;
|
|
@@ -464,11 +504,11 @@ function toHdbcdsSource( csn, options ) {
|
|
|
464
504
|
|
|
465
505
|
/**
|
|
466
506
|
* If an association/composition has the same identifier as it's target
|
|
467
|
-
* we must render
|
|
507
|
+
* we must render a "using target as __target" and use the alias to refer to the target
|
|
468
508
|
*
|
|
469
509
|
* @param {string} artName
|
|
470
510
|
* @param {CSN.Artifact} art
|
|
471
|
-
* @param {
|
|
511
|
+
* @param {HdbcdsRenderEnvironment} env
|
|
472
512
|
*/
|
|
473
513
|
function createTopLevelAliasesForArtifact( artName, art, env ) {
|
|
474
514
|
forEachMember(art, (element) => {
|
|
@@ -492,22 +532,21 @@ function toHdbcdsSource( csn, options ) {
|
|
|
492
532
|
* Render the 'technical configuration { ... }' section 'tc' of an entity.
|
|
493
533
|
*
|
|
494
534
|
* @param {object} tc content of the technical configuration
|
|
495
|
-
* @param {
|
|
535
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
496
536
|
* @returns {string} Return the resulting source string.
|
|
497
537
|
*/
|
|
498
538
|
function renderTechnicalConfiguration( tc, env ) {
|
|
499
|
-
let result = '';
|
|
500
|
-
const childEnv = increaseIndent(env);
|
|
501
|
-
|
|
502
539
|
if (!tc)
|
|
503
|
-
return
|
|
540
|
+
return '';
|
|
504
541
|
|
|
542
|
+
let result = '';
|
|
543
|
+
const childEnv = env.withIncreasedIndent();
|
|
505
544
|
|
|
506
545
|
// FIXME: How to deal with non-HANA technical configurations? We should probably just iterate all entries
|
|
507
546
|
// in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
|
|
508
547
|
tc = tc.hana;
|
|
509
548
|
if (!tc)
|
|
510
|
-
throw new ModelError('Expecting a HANA technical configuration');
|
|
549
|
+
throw new ModelError('Expecting a SAP HANA technical configuration');
|
|
511
550
|
|
|
512
551
|
result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
|
|
513
552
|
|
|
@@ -580,7 +619,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
580
619
|
*
|
|
581
620
|
* @param {string} elementName Name of the element
|
|
582
621
|
* @param {CSN.Element} elm Content of the element
|
|
583
|
-
* @param {
|
|
622
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
584
623
|
* @param {DuplicateChecker} [duplicateChecker] Utility for detecting duplicates
|
|
585
624
|
* @param {boolean} [isSubElement] Whether the given element is a subelement or not - subelements cannot be key!
|
|
586
625
|
* @returns {string} The rendered element
|
|
@@ -603,10 +642,14 @@ function toHdbcdsSource( csn, options ) {
|
|
|
603
642
|
result += env.indent + (elm.key && !isSubElement ? 'key ' : '') +
|
|
604
643
|
(elm.masked ? 'masked ' : '') +
|
|
605
644
|
quotedElementName + (omitColon ? ' ' : ' : ') +
|
|
606
|
-
renderTypeReference(elm, env)
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
645
|
+
renderTypeReference(elm, env);
|
|
646
|
+
// GENERATED AS ALWAYS() can't have a trailing "[not] null" nor "default".
|
|
647
|
+
// Because we already emit an error that calc-on-write is not supported, just ignore nullability/default.
|
|
648
|
+
if (!elm.value?.stored) {
|
|
649
|
+
result += renderNullability(elm);
|
|
650
|
+
if (elm.default)
|
|
651
|
+
result += ` default ${renderExpr(elm.default, env.withSubPath([ 'default' ]))}`;
|
|
652
|
+
}
|
|
610
653
|
|
|
611
654
|
// (table) elements can only have a @sql.append
|
|
612
655
|
const { back } = getSqlSnippets(options, elm);
|
|
@@ -623,13 +666,13 @@ function toHdbcdsSource( csn, options ) {
|
|
|
623
666
|
* Returns the source as a string.
|
|
624
667
|
*
|
|
625
668
|
* @param {object} source Source to render
|
|
626
|
-
* @param {
|
|
669
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
627
670
|
* @returns {string} Rendered view source
|
|
628
671
|
*/
|
|
629
672
|
function renderViewSource( source, env ) {
|
|
630
673
|
// Sub-SELECT
|
|
631
674
|
if (source.SELECT || source.SET) {
|
|
632
|
-
let result = `(${renderQuery(source, false,
|
|
675
|
+
let result = `(${renderQuery(source, false, env.withIncreasedIndent())})`;
|
|
633
676
|
if (source.as)
|
|
634
677
|
result += ` as ${formatIdentifier(source.as)}`;
|
|
635
678
|
return result;
|
|
@@ -637,12 +680,12 @@ function toHdbcdsSource( csn, options ) {
|
|
|
637
680
|
// JOIN
|
|
638
681
|
else if (source.join) {
|
|
639
682
|
// One join operation, possibly with ON-condition
|
|
640
|
-
let result = `${renderViewSource(source.args[0], env)}`;
|
|
683
|
+
let result = `${renderViewSource(source.args[0], env.withSubPath([ 'args', 0 ]))}`;
|
|
641
684
|
for (let i = 1; i < source.args.length; i++) {
|
|
642
685
|
result = `(${result} ${source.join} `;
|
|
643
|
-
result += `join ${renderViewSource(source.args[i], env)}`;
|
|
686
|
+
result += `join ${renderViewSource(source.args[i], env.withSubPath([ 'args', i ]))}`;
|
|
644
687
|
if (source.on)
|
|
645
|
-
result += ` on ${renderExpr(source.on, env)}`;
|
|
688
|
+
result += ` on ${renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
|
|
646
689
|
|
|
647
690
|
result += ')';
|
|
648
691
|
}
|
|
@@ -659,7 +702,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
659
702
|
* Returns the name as a string.
|
|
660
703
|
*
|
|
661
704
|
* @param {object} path Path to render
|
|
662
|
-
* @param {
|
|
705
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
663
706
|
* @returns {string} Rendered path
|
|
664
707
|
*/
|
|
665
708
|
function renderAbsolutePath( path, env ) {
|
|
@@ -680,11 +723,11 @@ function toHdbcdsSource( csn, options ) {
|
|
|
680
723
|
|
|
681
724
|
// Even the first step might have parameters and/or a filter
|
|
682
725
|
if (path.ref[0].args)
|
|
683
|
-
result += `(${renderArgs(path.ref[0], ':', env)})`;
|
|
726
|
+
result += `(${renderArgs(path.ref[0], ':', env.withSubPath([ 'ref', 0 ]))})`;
|
|
684
727
|
|
|
685
728
|
if (path.ref[0].where) {
|
|
686
729
|
const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
|
|
687
|
-
result += `[${cardinality}${renderExpr(path.ref[0].where, env)}]`;
|
|
730
|
+
result += `[${cardinality}${renderExpr(path.ref[0].where, env.withSubPath([ 'ref', 0, 'where' ]))}]`;
|
|
688
731
|
}
|
|
689
732
|
|
|
690
733
|
// Add any path steps (possibly with parameters and filters) that may follow after that
|
|
@@ -702,7 +745,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
702
745
|
* Returns the name and alias as a string.
|
|
703
746
|
*
|
|
704
747
|
* @param {object} path Path to render
|
|
705
|
-
* @param {
|
|
748
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
706
749
|
* @returns {string} Rendered path including alias
|
|
707
750
|
*/
|
|
708
751
|
function renderAbsolutePathWithAlias( path, env ) {
|
|
@@ -727,37 +770,33 @@ function toHdbcdsSource( csn, options ) {
|
|
|
727
770
|
*
|
|
728
771
|
* @param {object} col Column to render
|
|
729
772
|
* @param {CSN.Elements} elements where column exists
|
|
730
|
-
* @param {
|
|
773
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
731
774
|
* @returns {string} Rendered column
|
|
732
775
|
*/
|
|
733
776
|
function renderViewColumn( col, elements, env ) {
|
|
734
|
-
// Annotations and column
|
|
735
|
-
let result = '';
|
|
736
|
-
|
|
737
777
|
const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
|
|
738
778
|
const element = elements[leaf];
|
|
739
779
|
|
|
740
780
|
// Render 'null as <alias>' only for database and if element is virtual
|
|
741
|
-
if (element
|
|
781
|
+
if (element?.virtual) {
|
|
742
782
|
if (isDeprecatedEnabled(options, '_renderVirtualElements'))
|
|
743
|
-
return `${
|
|
744
|
-
|
|
745
|
-
else {
|
|
746
|
-
return renderNonVirtualColumn();
|
|
783
|
+
return `${env.indent}null as ${formatIdentifier(leaf)}`;
|
|
784
|
+
return '';
|
|
747
785
|
}
|
|
748
786
|
|
|
749
|
-
return
|
|
787
|
+
return renderNonVirtualColumn();
|
|
788
|
+
|
|
750
789
|
|
|
751
790
|
function renderNonVirtualColumn() {
|
|
752
|
-
result
|
|
791
|
+
let result = env.indent;
|
|
753
792
|
// only if column is virtual, keyword virtual was present in the source text
|
|
754
793
|
if (col.virtual)
|
|
755
794
|
result += 'virtual ';
|
|
756
795
|
// If key is explicitly set in a non-leading query, issue an error.
|
|
757
796
|
if (col.key && env.skipKeys)
|
|
758
|
-
error(null, env.path, {
|
|
797
|
+
error(null, env.path, { keyword: 'key', $reviewed: true }, 'Unexpected $(KEYWORD) in subquery');
|
|
759
798
|
|
|
760
|
-
const key = (!env.skipKeys && (col.key ||
|
|
799
|
+
const key = (!env.skipKeys && (col.key || element?.key) ? 'key ' : '');
|
|
761
800
|
result += key + renderExpr(withoutCast(col), env);
|
|
762
801
|
let alias = col.as || col.func;
|
|
763
802
|
// HANA requires an alias for 'key' columns just for syntactical reasons
|
|
@@ -771,12 +810,12 @@ function toHdbcdsSource( csn, options ) {
|
|
|
771
810
|
result += ` as ${formatIdentifier(alias)}`;
|
|
772
811
|
|
|
773
812
|
// Explicit type provided for the view element?
|
|
774
|
-
if (col.cast
|
|
813
|
+
if (col.cast?.target) {
|
|
775
814
|
// Special case: Explicit association type is actually a redirect
|
|
776
815
|
// Redirections are never flattened (don't exist in HANA)
|
|
777
|
-
result += ` : redirected to ${renderAbsoluteNameWithQuotes(col.cast.target, env)}`;
|
|
816
|
+
result += ` : redirected to ${renderAbsoluteNameWithQuotes(col.cast.target, env.withSubPath([ 'cast', 'target' ]))}`;
|
|
778
817
|
if (col.cast.on)
|
|
779
|
-
result += ` on ${renderExpr(col.cast.on, env)}`;
|
|
818
|
+
result += ` on ${renderExpr(col.cast.on, env.withSubPath([ 'cast', 'on' ]))}`;
|
|
780
819
|
}
|
|
781
820
|
|
|
782
821
|
return result;
|
|
@@ -790,7 +829,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
790
829
|
*
|
|
791
830
|
* @param {string} artifactName Name of the artifact
|
|
792
831
|
* @param {CSN.Artifact} art Content of the artifact
|
|
793
|
-
* @param {
|
|
832
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
794
833
|
* @returns {string} The rendered view
|
|
795
834
|
*/
|
|
796
835
|
function renderView( artifactName, art, env ) {
|
|
@@ -803,16 +842,18 @@ function toHdbcdsSource( csn, options ) {
|
|
|
803
842
|
|
|
804
843
|
result += `${env.indent}${art.abstract ? 'abstract ' : ''}view ${renderArtifactName(artifactName, env)}`;
|
|
805
844
|
if (art.params) {
|
|
806
|
-
const childEnv =
|
|
807
|
-
const parameters = Object.keys(art.params)
|
|
808
|
-
|
|
845
|
+
const childEnv = env.withIncreasedIndent();
|
|
846
|
+
const parameters = Object.keys(art.params)
|
|
847
|
+
.map(name => renderParameter(name, art.params[name], childEnv.withSubPath([ 'params', name ])))
|
|
848
|
+
.join(',\n');
|
|
849
|
+
// SAP HANA only understands the 'with parameters' syntax'
|
|
809
850
|
result += ` with parameters\n${parameters}\n${env.indent}as `;
|
|
810
851
|
}
|
|
811
852
|
else {
|
|
812
853
|
result += ' as ';
|
|
813
854
|
}
|
|
814
855
|
env._artifact = art;
|
|
815
|
-
result += renderQuery(getNormalizedQuery(art).query, true, env
|
|
856
|
+
result += renderQuery(getNormalizedQuery(art).query, true, env.withSubPath([ art.projection ? 'projection' : 'query' ]), art.elements);
|
|
816
857
|
|
|
817
858
|
// views can only have a @sql.append
|
|
818
859
|
const { back } = getSqlSnippets(options, art);
|
|
@@ -832,47 +873,52 @@ function toHdbcdsSource( csn, options ) {
|
|
|
832
873
|
*
|
|
833
874
|
* @param {CSN.Query} query Query object
|
|
834
875
|
* @param {boolean} isLeadingQuery Whether the query is the leading query or not
|
|
835
|
-
* @param {
|
|
836
|
-
* @param {CSN.Path} [path=[]] CSN path to the query
|
|
876
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
837
877
|
* @param {object} [elements] For leading query, the elements of the artifact
|
|
838
878
|
* @returns {string} The rendered query
|
|
839
879
|
*/
|
|
840
|
-
function renderQuery( query, isLeadingQuery, env,
|
|
880
|
+
function renderQuery( query, isLeadingQuery, env, elements = null ) {
|
|
881
|
+
const isProjection = env.path[env.path.length - 1] === 'projection';
|
|
841
882
|
let result = '';
|
|
842
883
|
env.skipKeys = !isLeadingQuery;
|
|
843
884
|
// Set operator, like UNION, INTERSECT, ...
|
|
844
885
|
if (query.SET) {
|
|
845
886
|
// First arg may be leading query
|
|
846
|
-
result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env
|
|
887
|
+
result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env.withSubPath([ 'SET', 'args', 0 ]), elements || query.SET.elements)}`;
|
|
847
888
|
// FIXME: Clarify if set operators can be n-ary (assuming binary here)
|
|
848
889
|
if (query.SET.op) {
|
|
849
890
|
// Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
|
|
850
891
|
for (let i = 1; i < query.SET.args.length; i++)
|
|
851
|
-
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, env
|
|
892
|
+
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)}`;
|
|
852
893
|
}
|
|
853
894
|
result += ')';
|
|
854
895
|
// Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
|
|
855
896
|
// each SELECT)
|
|
856
897
|
if (query.SET.orderBy)
|
|
857
|
-
result += `${continueIndent(result, env)}order by ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
|
|
898
|
+
result += `${continueIndent(result, env)}order by ${query.SET.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'SET', 'orderBy', i ]))).join(', ')}`;
|
|
858
899
|
|
|
859
900
|
if (query.SET.limit)
|
|
860
|
-
result += `${continueIndent(result, env)}${renderLimit(query.SET.limit, env)}`;
|
|
901
|
+
result += `${continueIndent(result, env)}${renderLimit(query.SET.limit, env.withSubPath([ 'SET', 'limit' ]))}`;
|
|
861
902
|
|
|
862
903
|
return result;
|
|
863
904
|
}
|
|
864
905
|
// Otherwise must have a SELECT
|
|
865
906
|
else if (!query.SELECT) {
|
|
866
|
-
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
907
|
+
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)} at ${JSON.stringify(env.path)}`);
|
|
867
908
|
}
|
|
909
|
+
|
|
910
|
+
if (!isProjection)
|
|
911
|
+
env = env.withSubPath([ 'SELECT' ]);
|
|
912
|
+
|
|
868
913
|
const select = query.SELECT;
|
|
869
|
-
|
|
914
|
+
result += `select from ${renderViewSource(select.from, env.withSubPath([ 'from' ]))}`;
|
|
915
|
+
|
|
916
|
+
const childEnv = env.withIncreasedIndent();
|
|
870
917
|
childEnv.currentArtifactName = $PROJECTION; // $self to be replaced by $projection
|
|
871
|
-
result += `select from ${renderViewSource(select.from, env)}`;
|
|
872
918
|
if (select.mixin) {
|
|
873
919
|
let elems = '';
|
|
874
920
|
for (const name in select.mixin)
|
|
875
|
-
elems += renderElement(name, select.mixin[name], childEnv);
|
|
921
|
+
elems += renderElement(name, select.mixin[name], childEnv.withSubPath([ 'mixin', name ]));
|
|
876
922
|
|
|
877
923
|
if (elems) {
|
|
878
924
|
result += ' mixin {\n';
|
|
@@ -883,10 +929,11 @@ function toHdbcdsSource( csn, options ) {
|
|
|
883
929
|
result += select.distinct ? ' distinct' : '';
|
|
884
930
|
if (select.columns) {
|
|
885
931
|
result += ' {\n';
|
|
886
|
-
result +=
|
|
932
|
+
result += select.columns
|
|
933
|
+
.map((col, i) => renderViewColumn(col, elements || select.elements, childEnv.withSubPath([ 'columns', i ])))
|
|
887
934
|
.filter(s => s !== '')
|
|
888
|
-
.join(',\n')
|
|
889
|
-
result +=
|
|
935
|
+
.join(',\n');
|
|
936
|
+
result += `\n${env.indent}}`;
|
|
890
937
|
}
|
|
891
938
|
if (select.excluding) {
|
|
892
939
|
const excludingList = select.excluding.map(id => `${childEnv.indent}${formatIdentifier(id)}`).join(',\n');
|
|
@@ -902,24 +949,24 @@ function toHdbcdsSource( csn, options ) {
|
|
|
902
949
|
*
|
|
903
950
|
* @param {CSN.QuerySelect} select
|
|
904
951
|
* @param {string} alreadyRendered The query as it has been rendered so far
|
|
905
|
-
* @param {
|
|
952
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
906
953
|
* @returns {string} The query with WHERE etc. added
|
|
907
954
|
*/
|
|
908
955
|
function renderSelectProperties( select, alreadyRendered, env ) {
|
|
909
956
|
if (select.where)
|
|
910
|
-
alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env)}`;
|
|
957
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env.withSubPath([ 'where' ]))}`;
|
|
911
958
|
|
|
912
959
|
if (select.groupBy)
|
|
913
|
-
alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
|
|
960
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map((expr, i) => renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
|
|
914
961
|
|
|
915
962
|
if (select.having)
|
|
916
|
-
alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env)}`;
|
|
963
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env.withSubPath([ 'having' ]))}`;
|
|
917
964
|
|
|
918
965
|
if (select.orderBy)
|
|
919
|
-
alreadyRendered += `${continueIndent(alreadyRendered, env)}order by ${select.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
|
|
966
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}order by ${select.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
|
|
920
967
|
|
|
921
968
|
if (select.limit)
|
|
922
|
-
alreadyRendered += `${continueIndent(alreadyRendered, env)}${renderLimit(select.limit, env)}`;
|
|
969
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}${renderLimit(select.limit, env.withSubPath([ 'limit' ]))}`;
|
|
923
970
|
|
|
924
971
|
return alreadyRendered;
|
|
925
972
|
}
|
|
@@ -928,7 +975,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
928
975
|
* Utility function to make sure that we continue with the same indentation in WHERE, GROUP BY, ... after a closing curly brace and beyond
|
|
929
976
|
*
|
|
930
977
|
* @param {string} result Result of a previous render step
|
|
931
|
-
* @param {
|
|
978
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
932
979
|
* @returns {string} String to join with
|
|
933
980
|
*/
|
|
934
981
|
function continueIndent( result, env ) {
|
|
@@ -937,24 +984,24 @@ function toHdbcdsSource( csn, options ) {
|
|
|
937
984
|
return ' ';
|
|
938
985
|
}
|
|
939
986
|
// Otherwise, start new line and indent normally
|
|
940
|
-
return `\n${
|
|
987
|
+
return `\n${env.withIncreasedIndent().indent}`;
|
|
941
988
|
}
|
|
942
989
|
|
|
943
990
|
/**
|
|
944
|
-
* Render a query's LIMIT clause, which may
|
|
991
|
+
* Render a query's LIMIT clause, which may also have OFFSET.
|
|
945
992
|
*
|
|
946
993
|
* @param {CSN.QueryLimit} limit CSN limit clause
|
|
947
|
-
* @param {
|
|
994
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
948
995
|
* @returns {string} Rendered limit clause
|
|
949
996
|
*/
|
|
950
997
|
function renderLimit( limit, env ) {
|
|
951
998
|
let result = '';
|
|
952
999
|
if (limit.rows !== undefined)
|
|
953
|
-
result += `limit ${renderExpr(limit.rows, env)}`;
|
|
1000
|
+
result += `limit ${renderExpr(limit.rows, env.withSubPath([ 'rows' ]))}`;
|
|
954
1001
|
|
|
955
1002
|
if (limit.offset !== undefined) {
|
|
956
|
-
const indent = result !== '' ? `\n${
|
|
957
|
-
result += `${indent}offset ${renderExpr(limit.offset, env)}`;
|
|
1003
|
+
const indent = result !== '' ? `\n${env.withIncreasedIndent().indent}` : '';
|
|
1004
|
+
result += `${indent}offset ${renderExpr(limit.offset, env.withSubPath([ 'offset' ]))}`;
|
|
958
1005
|
}
|
|
959
1006
|
|
|
960
1007
|
return result;
|
|
@@ -965,7 +1012,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
965
1012
|
* have a 'sort' property for ASC/DESC and a 'nulls' for FIRST/LAST
|
|
966
1013
|
*
|
|
967
1014
|
* @param {object} entry CSN order by
|
|
968
|
-
* @param {
|
|
1015
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
969
1016
|
* @returns {string} Rendered order by
|
|
970
1017
|
*/
|
|
971
1018
|
function renderOrderByEntry( entry, env ) {
|
|
@@ -984,13 +1031,13 @@ function toHdbcdsSource( csn, options ) {
|
|
|
984
1031
|
*
|
|
985
1032
|
* @param {string} parName Name of the parameter
|
|
986
1033
|
* @param {object} par CSN parameter
|
|
987
|
-
* @param {
|
|
1034
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
988
1035
|
* @returns {string} The resulting parameter as source string (no trailing LF).
|
|
989
1036
|
*/
|
|
990
1037
|
function renderParameter( parName, par, env ) {
|
|
991
1038
|
if (par.notNull === true || par.notNull === false)
|
|
992
|
-
info('query-ignoring-param-nullability', env.path
|
|
993
|
-
return `${env.indent + formatParamIdentifier(parName, env.path
|
|
1039
|
+
info('query-ignoring-param-nullability', env.path, { '#': 'std' });
|
|
1040
|
+
return `${env.indent + formatParamIdentifier(parName, env.path)} : ${renderTypeReference(par, env)}`;
|
|
994
1041
|
}
|
|
995
1042
|
|
|
996
1043
|
/**
|
|
@@ -999,7 +1046,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
999
1046
|
*
|
|
1000
1047
|
* @param {string} artifactName Name of the artifact
|
|
1001
1048
|
* @param {CSN.Artifact} art Content of the artifact
|
|
1002
|
-
* @param {
|
|
1049
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1003
1050
|
* @returns {string} Rendered type/annotation
|
|
1004
1051
|
*/
|
|
1005
1052
|
function renderType( artifactName, art, env ) {
|
|
@@ -1011,12 +1058,12 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1011
1058
|
// Includes are never flattened (don't exist in HANA)
|
|
1012
1059
|
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name, env)).join(', ')}`;
|
|
1013
1060
|
}
|
|
1014
|
-
const childEnv = increaseIndent(env);
|
|
1015
1061
|
if (art.elements && !art.type) {
|
|
1062
|
+
const childEnv = env.withIncreasedIndent();
|
|
1016
1063
|
// Structured type or annotation with anonymous struct type
|
|
1017
1064
|
result += ' {\n';
|
|
1018
1065
|
for (const name in art.elements)
|
|
1019
|
-
result += renderElement(name, art.elements[name], childEnv);
|
|
1066
|
+
result += renderElement(name, art.elements[name], childEnv.withSubPath([ 'elements', name ]));
|
|
1020
1067
|
|
|
1021
1068
|
result += `${env.indent}};\n`;
|
|
1022
1069
|
}
|
|
@@ -1032,7 +1079,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1032
1079
|
* Allow suppressing enum-rendering - used in columns for example
|
|
1033
1080
|
*
|
|
1034
1081
|
* @param {object} elm Element using the type reference
|
|
1035
|
-
* @param {
|
|
1082
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1036
1083
|
* @returns {string} Rendered type reference
|
|
1037
1084
|
*/
|
|
1038
1085
|
function renderTypeReference( elm, env ) {
|
|
@@ -1041,7 +1088,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1041
1088
|
// Array type: Render items instead
|
|
1042
1089
|
if (elm.items && !elm.type) {
|
|
1043
1090
|
// HANA CDS does not support keyword many
|
|
1044
|
-
let rc = `array of ${renderTypeReference(elm.items, env)}`;
|
|
1091
|
+
let rc = `array of ${renderTypeReference(elm.items, env.withSubPath([ 'items' ]))}`;
|
|
1045
1092
|
if (elm.items.notNull != null)
|
|
1046
1093
|
rc += elm.items.notNull ? ' not null' : ' null';
|
|
1047
1094
|
return rc;
|
|
@@ -1056,11 +1103,11 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1056
1103
|
throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
|
|
1057
1104
|
|
|
1058
1105
|
result += '{\n';
|
|
1059
|
-
const childEnv =
|
|
1106
|
+
const childEnv = env.withIncreasedIndent();
|
|
1060
1107
|
// omit "key" keyword for nested elements, as this will result in a deployment error in naming mode 'hdbcds'
|
|
1061
1108
|
const dontRenderKeyForNestedElement = hdbcdsNames;
|
|
1062
1109
|
for (const name in elm.elements)
|
|
1063
|
-
result += renderElement(name, elm.elements[name], childEnv, null, dontRenderKeyForNestedElement);
|
|
1110
|
+
result += renderElement(name, elm.elements[name], childEnv.withSubPath([ 'elements', name ]), null, dontRenderKeyForNestedElement);
|
|
1064
1111
|
|
|
1065
1112
|
result += `${env.indent}}`;
|
|
1066
1113
|
return result;
|
|
@@ -1070,11 +1117,10 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1070
1117
|
if ([ 'cds.Association', 'cds.Composition' ].includes(elm.type))
|
|
1071
1118
|
return result + renderAssociationType(elm, env);
|
|
1072
1119
|
|
|
1073
|
-
|
|
1074
1120
|
if (elm.type?.ref) {
|
|
1075
1121
|
// Reference to another element
|
|
1076
1122
|
// For HANA CDS, we need a 'type of'
|
|
1077
|
-
result += `type of ${renderAbsolutePath(elm.type, env)}`;
|
|
1123
|
+
result += `type of ${renderAbsolutePath(elm.type, env.withSubPath([ 'type' ]))}`;
|
|
1078
1124
|
}
|
|
1079
1125
|
else if (isBuiltinType(elm.type)) {
|
|
1080
1126
|
// If we get here, it must be a named type
|
|
@@ -1083,14 +1129,14 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1083
1129
|
else {
|
|
1084
1130
|
// Simple absolute name
|
|
1085
1131
|
// Type names are never flattened (derived types are unraveled in HANA)
|
|
1086
|
-
result += renderAbsoluteNameWithQuotes(elm.type, env);
|
|
1132
|
+
result += renderAbsoluteNameWithQuotes(elm.type, env.withSubPath([ 'type' ]));
|
|
1087
1133
|
}
|
|
1088
1134
|
|
|
1089
1135
|
if (elm.value) {
|
|
1090
1136
|
if (!elm.value.stored)
|
|
1091
1137
|
throw new CompilerAssertion('Found calculated element on-read in rendering; should have been replaced!');
|
|
1092
1138
|
message('def-unsupported-calc-elem', env.path, { '#': 'hdbcds' });
|
|
1093
|
-
result += ` GENERATED ALWAYS AS ${renderExpr(elm.value)}`;
|
|
1139
|
+
result += ` GENERATED ALWAYS AS ${renderExpr(elm.value, env.withSubPath([ 'value' ]))}`;
|
|
1094
1140
|
return result;
|
|
1095
1141
|
}
|
|
1096
1142
|
|
|
@@ -1099,7 +1145,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1099
1145
|
|
|
1100
1146
|
/**
|
|
1101
1147
|
* @param {CSN.Element} elm
|
|
1102
|
-
* @param {
|
|
1148
|
+
* @param {HdbcdsRenderEnvironment} env
|
|
1103
1149
|
* @returns {string}
|
|
1104
1150
|
*/
|
|
1105
1151
|
function renderAssociationType( elm, env ) {
|
|
@@ -1108,7 +1154,6 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1108
1154
|
|
|
1109
1155
|
result += `${renderCardinality(elm.cardinality)} to `;
|
|
1110
1156
|
|
|
1111
|
-
|
|
1112
1157
|
// normal target or named aspect
|
|
1113
1158
|
if (elm.target || elm.targetAspect && typeof elm.targetAspect === 'string') {
|
|
1114
1159
|
// we might have a "using target as __target"
|
|
@@ -1118,28 +1163,28 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1118
1163
|
result += targetAlias.quotedAlias;
|
|
1119
1164
|
}
|
|
1120
1165
|
else {
|
|
1121
|
-
|
|
1122
|
-
|
|
1166
|
+
const target = elm.target || elm.targetAspect;
|
|
1167
|
+
const childEnv = env.withSubPath([ elm.target ? 'target' : 'targetAspect' ]);
|
|
1168
|
+
result += plainNames ? renderAbsoluteNamePlain(target, childEnv) : renderAbsoluteNameWithQuotes(target, childEnv);
|
|
1123
1169
|
}
|
|
1124
1170
|
}
|
|
1125
1171
|
|
|
1126
1172
|
// ON-condition (if any)
|
|
1127
1173
|
if (elm.on) {
|
|
1128
|
-
result += ` on ${renderExpr(elm.on, env)}`;
|
|
1174
|
+
result += ` on ${renderExpr(elm.on, env.withSubPath([ 'on' ]))}`;
|
|
1129
1175
|
}
|
|
1130
|
-
else if (elm.targetAspect
|
|
1131
|
-
const childEnv =
|
|
1176
|
+
else if (elm.targetAspect?.elements) { // anonymous aspect
|
|
1177
|
+
const childEnv = env.withIncreasedIndent();
|
|
1132
1178
|
result += '{\n';
|
|
1133
1179
|
for (const name in elm.targetAspect.elements)
|
|
1134
|
-
result += renderElement(name, elm.targetAspect.elements[name], childEnv);
|
|
1180
|
+
result += renderElement(name, elm.targetAspect.elements[name], childEnv.withSubPath([ 'targetAspect', 'elements', name ]));
|
|
1135
1181
|
|
|
1136
1182
|
result += `${env.indent}}`;
|
|
1137
1183
|
}
|
|
1138
1184
|
|
|
1139
|
-
|
|
1140
1185
|
// Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
|
|
1141
1186
|
if (elm.keys && !elm.on)
|
|
1142
|
-
result += ` { ${Object.keys(elm.keys).map(name => renderForeignKey(elm.keys[name], env)).join(', ')} }`;
|
|
1187
|
+
result += ` { ${Object.keys(elm.keys).map(name => renderForeignKey(elm.keys[name], env.withSubPath([ 'keys', name ]))).join(', ')} }`;
|
|
1143
1188
|
|
|
1144
1189
|
return result;
|
|
1145
1190
|
}
|
|
@@ -1164,6 +1209,8 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1164
1209
|
*
|
|
1165
1210
|
* @param {string|object} s Path step
|
|
1166
1211
|
* @param {number} idx Path position
|
|
1212
|
+
* @param {any[]} ref
|
|
1213
|
+
* @param {HdbcdsRenderEnvironment} env
|
|
1167
1214
|
* @returns {string} Rendered path step
|
|
1168
1215
|
*/
|
|
1169
1216
|
function renderPathStep( s, idx, ref, env ) {
|
|
@@ -1171,7 +1218,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1171
1218
|
if (typeof s === 'string') {
|
|
1172
1219
|
// HANA-specific extra magic (should actually be in forRelationalDB)
|
|
1173
1220
|
// In HANA, we replace leading $self by the absolute name of the current artifact
|
|
1174
|
-
// (see FIXME at
|
|
1221
|
+
// (see FIXME at renderDefinition)
|
|
1175
1222
|
if (idx === 0 && s === $SELF) {
|
|
1176
1223
|
// do not produce USING for $projection
|
|
1177
1224
|
if (env.currentArtifactName === $PROJECTION)
|
|
@@ -1218,12 +1265,12 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1218
1265
|
if (s.where) {
|
|
1219
1266
|
// Filter, possibly with cardinality
|
|
1220
1267
|
const cardinality = s.cardinality ? `${s.cardinality.max}: ` : '';
|
|
1221
|
-
result += `[${cardinality}${renderExpr(s.where, env)}]`;
|
|
1268
|
+
result += `[${cardinality}${renderExpr(s.where, env.withSubPath([ 'where' ]))}]`;
|
|
1222
1269
|
}
|
|
1223
1270
|
return result;
|
|
1224
1271
|
}
|
|
1225
1272
|
|
|
1226
|
-
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1273
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)} at ${JSON.stringify(env.path)}`);
|
|
1227
1274
|
}
|
|
1228
1275
|
|
|
1229
1276
|
/**
|
|
@@ -1265,7 +1312,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1265
1312
|
const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
|
|
1266
1313
|
// we can't quote functions with parens, issue warning if it is a reserved keyword
|
|
1267
1314
|
if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
|
|
1268
|
-
warning(null,
|
|
1315
|
+
warning(null, this.env.path, { id: uppercaseAndUnderscore(funcName) }, 'The identifier $(ID) is a SAP HANA keyword');
|
|
1269
1316
|
return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', this.env));
|
|
1270
1317
|
}
|
|
1271
1318
|
|
|
@@ -1301,7 +1348,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1301
1348
|
return renderStringForHdbcds(magicReplacement);
|
|
1302
1349
|
}
|
|
1303
1350
|
}
|
|
1304
|
-
return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, this.env)).join('.')}`;
|
|
1351
|
+
return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, this.env.withSubPath([ 'ref', index ]))).join('.')}`;
|
|
1305
1352
|
}
|
|
1306
1353
|
|
|
1307
1354
|
/**
|
|
@@ -1310,19 +1357,22 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1310
1357
|
*
|
|
1311
1358
|
* @param {object} node with `args` to render
|
|
1312
1359
|
* @param {string} sep Separator between arguments
|
|
1313
|
-
* @param {
|
|
1360
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1314
1361
|
* @returns {string} Rendered arguments
|
|
1315
1362
|
*/
|
|
1316
1363
|
function renderArgs( node, sep, env ) {
|
|
1317
|
-
const args = node.args
|
|
1364
|
+
const args = node.args || {};
|
|
1318
1365
|
// Positional arguments
|
|
1319
|
-
if (Array.isArray(args))
|
|
1320
|
-
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1321
|
-
|
|
1366
|
+
if (Array.isArray(args)) {
|
|
1367
|
+
return args.map((arg, i) => renderExpr(arg, env.withSubPath([ 'args', i ]))).join(', ');
|
|
1368
|
+
}
|
|
1322
1369
|
// Named arguments (object/dict)
|
|
1323
|
-
else if (typeof args === 'object')
|
|
1370
|
+
else if (typeof args === 'object') {
|
|
1324
1371
|
// if this is a function param which is not a reference to the model, we must not quote it
|
|
1325
|
-
return Object.keys(args)
|
|
1372
|
+
return Object.keys(args)
|
|
1373
|
+
.map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env.withSubPath([ 'args', key ]))}`)
|
|
1374
|
+
.join(', ');
|
|
1375
|
+
}
|
|
1326
1376
|
|
|
1327
1377
|
|
|
1328
1378
|
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
@@ -1371,7 +1421,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1371
1421
|
* @todo Can this still happen after Hana transformation?
|
|
1372
1422
|
*
|
|
1373
1423
|
* @param {object} fKey Foreign key to render
|
|
1374
|
-
* @param {
|
|
1424
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1375
1425
|
* @returns {string} Rendered foreign key
|
|
1376
1426
|
*/
|
|
1377
1427
|
function renderForeignKey( fKey, env ) {
|
|
@@ -1410,7 +1460,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1410
1460
|
* if necessary.
|
|
1411
1461
|
*
|
|
1412
1462
|
* @param {string} absName Absolute name
|
|
1413
|
-
* @param {
|
|
1463
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1414
1464
|
* @returns {string} Uppercased and underscored absName
|
|
1415
1465
|
*/
|
|
1416
1466
|
function renderAbsoluteNamePlain( absName, env ) {
|
|
@@ -1428,7 +1478,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1428
1478
|
* if necessary.
|
|
1429
1479
|
*
|
|
1430
1480
|
* @param {string} absName absolute name
|
|
1431
|
-
* @param {
|
|
1481
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1432
1482
|
* @returns {string} absName, with correct quotes
|
|
1433
1483
|
*/
|
|
1434
1484
|
function renderAbsoluteNameWithQuotes( absName, env ) {
|
|
@@ -1498,7 +1548,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1498
1548
|
* Render appropriate USING directives for all artifacts used by artifact 'artifactName' in 'env'.
|
|
1499
1549
|
*
|
|
1500
1550
|
* @param {string} artifactName Artifact to render usings for
|
|
1501
|
-
* @param {
|
|
1551
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1502
1552
|
* @returns {string} Usings for the given artifact
|
|
1503
1553
|
*/
|
|
1504
1554
|
function renderUsings( artifactName, env ) {
|
|
@@ -1581,7 +1631,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1581
1631
|
* Return the namespace declaration (with trailing LF) or an empty string.
|
|
1582
1632
|
*
|
|
1583
1633
|
* @param {string} topLevelName Name of a top-level artifact
|
|
1584
|
-
* @param {
|
|
1634
|
+
* @param {HdbcdsRenderEnvironment} env Environment
|
|
1585
1635
|
* @returns {string} Rendered namespace declaration
|
|
1586
1636
|
*/
|
|
1587
1637
|
function renderNamespaceDeclaration( topLevelName, env ) {
|
|
@@ -1626,40 +1676,22 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1626
1676
|
* Returns a newly created default environment (which keeps track of indentation, required USING
|
|
1627
1677
|
* declarations and name prefixes.
|
|
1628
1678
|
*
|
|
1629
|
-
* @
|
|
1679
|
+
* @param {object} [values]
|
|
1680
|
+
* @returns {HdbcdsRenderEnvironment} Fresh environment
|
|
1630
1681
|
*/
|
|
1631
|
-
function createEnv() {
|
|
1632
|
-
return
|
|
1633
|
-
// Current indentation string
|
|
1634
|
-
indent: '',
|
|
1635
|
-
// Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
|
|
1636
|
-
topLevelAliases: Object.create(null),
|
|
1637
|
-
// Current name prefix (including trailing dot if not empty)
|
|
1638
|
-
namePrefix: '',
|
|
1639
|
-
// CSN path - should at least point to the correct artifact
|
|
1640
|
-
path: [],
|
|
1641
|
-
};
|
|
1682
|
+
function createEnv( values = {} ) {
|
|
1683
|
+
return new HdbcdsRenderEnvironment(values);
|
|
1642
1684
|
}
|
|
1643
1685
|
|
|
1644
1686
|
/**
|
|
1645
1687
|
* Returns a copy of 'env' with (quoted) name prefix 'id' and a dot appended to the current name prefix
|
|
1646
1688
|
*
|
|
1647
|
-
* @param {
|
|
1689
|
+
* @param {HdbcdsRenderEnvironment} env Current environment
|
|
1648
1690
|
* @param {string} id Name prefix to add
|
|
1649
|
-
* @returns {
|
|
1691
|
+
* @returns {HdbcdsRenderEnvironment} New environment with added prefix
|
|
1650
1692
|
*/
|
|
1651
1693
|
function addNamePrefix( env, id ) {
|
|
1652
|
-
return
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
/**
|
|
1656
|
-
* Returns a copy of 'env' with increased indentation - also resets the name prefix
|
|
1657
|
-
*
|
|
1658
|
-
* @param {CdlRenderEnvironment} env Current environment
|
|
1659
|
-
* @returns {CdlRenderEnvironment} New environment with increased indent
|
|
1660
|
-
*/
|
|
1661
|
-
function increaseIndent( env ) {
|
|
1662
|
-
return Object.assign({}, env, { namePrefix: '', indent: `${env.indent} ` });
|
|
1694
|
+
return env.cloneWith({ namePrefix: `${env.namePrefix + quoteId(id)}.` });
|
|
1663
1695
|
}
|
|
1664
1696
|
|
|
1665
1697
|
/**
|
|
@@ -1772,7 +1804,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1772
1804
|
*
|
|
1773
1805
|
*
|
|
1774
1806
|
* @param {string} artifactName Artifact name to render
|
|
1775
|
-
* @param {
|
|
1807
|
+
* @param {HdbcdsRenderEnvironment} env Render environment
|
|
1776
1808
|
* @param {boolean} [fallthrough=false] For certain artifacts, plain-rendering is supposed to look like quoted/hdbcds
|
|
1777
1809
|
* @returns {string} Artifact name ready for rendering
|
|
1778
1810
|
*/
|
|
@@ -1799,20 +1831,4 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1799
1831
|
}
|
|
1800
1832
|
}
|
|
1801
1833
|
|
|
1802
|
-
/**
|
|
1803
|
-
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
1804
|
-
*
|
|
1805
|
-
* @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
|
|
1806
|
-
* @property {CSN.Path} [path] CSN path to the current artifact
|
|
1807
|
-
* @property {string} [currentArtifactName] Name of the current artifact
|
|
1808
|
-
* @property {{[name: string]: {
|
|
1809
|
-
quotedName: string,
|
|
1810
|
-
quotedAlias: string
|
|
1811
|
-
}}} [topLevelAliases] Dictionary of aliases for used artifact names
|
|
1812
|
-
*
|
|
1813
|
-
* @property {string} [namePrefix] Current name prefix (including trailing dot if not empty)
|
|
1814
|
-
* @property {boolean} [skipKeys] Skip rendering keys in subqueries
|
|
1815
|
-
* @property {CSN.Artifact} [_artifact] The original view artifact, used when rendering queries
|
|
1816
|
-
*/
|
|
1817
|
-
|
|
1818
1834
|
module.exports = { toHdbcdsSource };
|