@sap/cds-compiler 2.15.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -1590
- package/bin/cdsc.js +36 -33
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +220 -103
- package/lib/api/options.js +15 -85
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +60 -20
- package/lib/base/messages.js +65 -24
- package/lib/base/model.js +44 -2
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +2 -1
- package/lib/compiler/assert-consistency.js +15 -10
- package/lib/compiler/builtins.js +87 -9
- package/lib/compiler/define.js +2 -2
- package/lib/compiler/extend.js +59 -11
- package/lib/compiler/finalize-parse-cdl.js +20 -9
- package/lib/compiler/index.js +25 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +13 -13
- package/lib/compiler/propagator.js +3 -3
- package/lib/compiler/resolve.js +193 -218
- package/lib/compiler/shared.js +47 -76
- package/lib/compiler/tweak-assocs.js +9 -10
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/csn2edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +25 -30
- package/lib/edm/edmUtils.js +10 -24
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20632 -22313
- package/lib/json/from-csn.js +56 -49
- package/lib/json/to-csn.js +10 -8
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +303 -229
- package/lib/language/language.g4 +573 -629
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +27 -42
- package/lib/main.js +104 -81
- package/lib/model/csnRefs.js +1 -1
- package/lib/model/csnUtils.js +170 -283
- package/lib/model/revealInternalProperties.js +28 -8
- package/lib/model/sortViews.js +32 -31
- package/lib/optionProcessor.js +12 -21
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +334 -339
- package/lib/render/toHdbcds.js +19 -15
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +53 -51
- package/lib/render/utils/common.js +15 -1
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +18 -19
- package/lib/transform/db/views.js +3 -3
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +19 -22
- package/lib/transform/forOdataNew.js +10 -12
- package/lib/transform/localized.js +22 -16
- package/lib/transform/odata/toFinalBaseType.js +10 -10
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +3 -3
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
package/lib/render/toCdl.js
CHANGED
|
@@ -1,33 +1,32 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const keywords = require('../base/keywords');
|
|
4
|
-
const { getLastPartOf } = require('../model/csnUtils');
|
|
5
4
|
const { isBuiltinType, generatedByCompilerVersion, getNormalizedQuery } = require('../model/csnUtils');
|
|
6
|
-
const {
|
|
5
|
+
const { findElement, getExpressionRenderer } = require('./utils/common');
|
|
7
6
|
const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
|
|
8
7
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
9
8
|
const { timetrace } = require('../utils/timetrace');
|
|
10
|
-
const { csnRefs } = require('../model/csnRefs');
|
|
11
9
|
const { forEachDefinition } = require('../model/csnUtils');
|
|
12
10
|
const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
|
|
13
11
|
const { isBetaEnabled } = require('../base/model');
|
|
14
12
|
const { ModelError } = require('../base/error');
|
|
15
|
-
const { typeParameters } = require('../compiler/builtins');
|
|
13
|
+
const { typeParameters, specialFunctions } = require('../compiler/builtins');
|
|
16
14
|
const { forEach } = require('../utils/objectUtils');
|
|
17
15
|
|
|
16
|
+
const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
|
|
17
|
+
|
|
18
18
|
/**
|
|
19
19
|
* Render the CSN model 'model' to CDS source text.
|
|
20
20
|
* Returned object has the following properties:
|
|
21
21
|
* - `model`: CSN model rendered as CDL (string).
|
|
22
|
-
* -
|
|
22
|
+
* - `namespace`: Namespace statement + `using from './model.cds'.
|
|
23
23
|
* - `unappliedExtensions`: Annotations / Extensions from the `csn.extensions` array.
|
|
24
24
|
*
|
|
25
25
|
* @param {CSN.Model} csn
|
|
26
26
|
* @param {CSN.Options} [options]
|
|
27
27
|
*/
|
|
28
|
-
function
|
|
28
|
+
function csnToCdl(csn, options) {
|
|
29
29
|
timetrace.start('CDL rendering');
|
|
30
|
-
const { artifactRef } = csnRefs(csn);
|
|
31
30
|
let _renderExpr = null;
|
|
32
31
|
|
|
33
32
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
|
|
@@ -48,8 +47,8 @@ function toCdsSourceCsn(csn, options) {
|
|
|
48
47
|
cdlResult.model += renderVocabularies(csn.vocabularies);
|
|
49
48
|
|
|
50
49
|
if (csn.namespace) {
|
|
51
|
-
cdlResult
|
|
52
|
-
cdlResult
|
|
50
|
+
cdlResult.namespace = `namespace ${renderArtifactName(csn.namespace)};\n`;
|
|
51
|
+
cdlResult.namespace += 'using from \'./model.cds\';';
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
// If there are extensions, such as 'extend' and 'annotate' statements, render them separately.
|
|
@@ -93,7 +92,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
93
92
|
if (!anno._ignore) {
|
|
94
93
|
// This environment is passed down the call hierarchy, for dealing with
|
|
95
94
|
// indentation and name resolution issues
|
|
96
|
-
const env = createEnv();
|
|
95
|
+
const env = envNewPath(createEnv(), [ 'vocabularies', name ]);
|
|
97
96
|
const sourceStr = renderTypeOrAnnotation(name, anno, env, 'annotation');
|
|
98
97
|
result += `${sourceStr}\n`;
|
|
99
98
|
}
|
|
@@ -147,7 +146,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
147
146
|
// are possible through CSN, but in CDL, only one include at once is possible.
|
|
148
147
|
const affix = isElementExtend ? 'element ' : '';
|
|
149
148
|
for (const id of ext.includes)
|
|
150
|
-
result += `${env.indent}extend ${affix}${extName} with ${
|
|
149
|
+
result += `${env.indent}extend ${affix}${extName} with ${quotePathIfRequired(id)};\n`;
|
|
151
150
|
return result;
|
|
152
151
|
}
|
|
153
152
|
|
|
@@ -293,7 +292,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
293
292
|
result += `${env.indent}}`;
|
|
294
293
|
}
|
|
295
294
|
|
|
296
|
-
|
|
297
295
|
result += ';\n';
|
|
298
296
|
return result;
|
|
299
297
|
}
|
|
@@ -348,24 +346,22 @@ function toCdsSourceCsn(csn, options) {
|
|
|
348
346
|
* @param {CdlRenderEnvironment} env
|
|
349
347
|
*/
|
|
350
348
|
function renderArtifact(artifactName, art, env) {
|
|
351
|
-
|
|
352
|
-
env.path = [ 'definitions', artifactName ];
|
|
349
|
+
env = envNewPath(env, [ 'definitions', artifactName ]);
|
|
353
350
|
env.artifactName = artifactName;
|
|
354
351
|
|
|
355
352
|
switch (art.kind) {
|
|
356
353
|
case 'entity':
|
|
357
354
|
if (art.query || art.projection)
|
|
358
355
|
return renderView(artifactName, art, env);
|
|
359
|
-
|
|
360
356
|
return renderEntity(artifactName, art, env);
|
|
361
357
|
|
|
362
358
|
case 'context':
|
|
363
359
|
case 'service':
|
|
364
|
-
return
|
|
360
|
+
return renderContextOrService(artifactName, art, env);
|
|
365
361
|
case 'type':
|
|
366
362
|
case 'aspect':
|
|
367
|
-
case 'annotation':
|
|
368
|
-
return renderTypeOrAnnotation(artifactName, art, env
|
|
363
|
+
case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
|
|
364
|
+
return renderTypeOrAnnotation(artifactName, art, env);
|
|
369
365
|
case 'action':
|
|
370
366
|
case 'function':
|
|
371
367
|
return renderActionOrFunction(artifactName, art, env);
|
|
@@ -389,16 +385,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
389
385
|
if (art.includes)
|
|
390
386
|
result += renderIncludes(art.includes);
|
|
391
387
|
if (art.query || art.projection) {
|
|
392
|
-
env._artifact = art;
|
|
393
388
|
result += ' : ';
|
|
394
389
|
result += renderQuery(getNormalizedQuery(art).query, true, 'projection', env,
|
|
395
390
|
[ 'definitions', artifactName, 'query' ]);
|
|
396
391
|
result += ';\n';
|
|
397
|
-
delete env._artifact;
|
|
398
392
|
}
|
|
399
393
|
else if (art.type) {
|
|
400
394
|
// Derived type or annotation with non-anonymous type
|
|
401
|
-
result += ` : ${
|
|
395
|
+
result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
|
|
402
396
|
}
|
|
403
397
|
else if (art.elements) {
|
|
404
398
|
result += ' {\n';
|
|
@@ -411,18 +405,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
411
405
|
}
|
|
412
406
|
|
|
413
407
|
/**
|
|
414
|
-
* Render a context or service. Return the resulting source string.
|
|
415
|
-
*
|
|
416
408
|
* @param {string} artifactName
|
|
417
409
|
* @param {CSN.Artifact} art
|
|
418
410
|
* @param {CdlRenderEnvironment} env
|
|
411
|
+
* @returns {string}
|
|
419
412
|
*/
|
|
420
|
-
function
|
|
413
|
+
function renderContextOrService(artifactName, art, env) {
|
|
421
414
|
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
422
|
-
result += `${env.indent
|
|
423
|
-
if (art.includes)
|
|
424
|
-
result += renderIncludes(art.includes);
|
|
425
|
-
|
|
415
|
+
result += `${env.indent}${art.kind} ${renderArtifactName(artifactName)}`;
|
|
426
416
|
return `${result} {};\n`;
|
|
427
417
|
}
|
|
428
418
|
|
|
@@ -446,18 +436,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
446
436
|
result += ' {\n';
|
|
447
437
|
for (const name in art.elements) {
|
|
448
438
|
const element = art.elements[name];
|
|
449
|
-
// For subelement annotations, this seems to be a pattern to recognize them
|
|
450
|
-
// plus some other stuff unfortunately...
|
|
451
|
-
if (element.type && element.elements) {
|
|
452
|
-
subelementAnnotates.push({
|
|
453
|
-
annotate: artifactName,
|
|
454
|
-
elements: {
|
|
455
|
-
[name]: {
|
|
456
|
-
elements: element.elements,
|
|
457
|
-
},
|
|
458
|
-
},
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
439
|
result += renderElement(name, element, childEnv);
|
|
462
440
|
}
|
|
463
441
|
|
|
@@ -476,19 +454,15 @@ function toCdsSourceCsn(csn, options) {
|
|
|
476
454
|
* @param {Boolean} [isSubElement]
|
|
477
455
|
*/
|
|
478
456
|
function renderElement(elementName, elm, env, isSubElement) {
|
|
479
|
-
env
|
|
457
|
+
env = envAddPath(env, [ 'elements', elementName ]);
|
|
480
458
|
let result = renderAnnotationAssignmentsAndDocComment(elm, env);
|
|
481
|
-
|
|
482
|
-
result +=
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (elm.default)
|
|
489
|
-
result += ` default ${renderExpr(elm.default, env)}`;
|
|
490
|
-
|
|
491
|
-
delete env.elementName;
|
|
459
|
+
result += env.indent;
|
|
460
|
+
result += elm.virtual ? 'virtual ' : '';
|
|
461
|
+
result += elm.key && !isSubElement ? 'key ' : '';
|
|
462
|
+
// TODO(v4): Remove once deprecated flag for `masked` is removed.
|
|
463
|
+
result += elm.masked ? 'masked ' : '';
|
|
464
|
+
result += `${quoteIdIfRequired(elementName)} : ${renderTypeReferenceAndProps(elm, env)}`;
|
|
465
|
+
|
|
492
466
|
return `${result};\n`;
|
|
493
467
|
}
|
|
494
468
|
|
|
@@ -529,7 +503,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
529
503
|
* @return {string}
|
|
530
504
|
*/
|
|
531
505
|
function renderQueryElementAnnotations(artifactName, art, env) {
|
|
532
|
-
const annotate = collectAnnotationsOfElements(artifactName,
|
|
506
|
+
const annotate = collectAnnotationsOfElements(art, { artifactName, path: env.path });
|
|
533
507
|
if (annotate)
|
|
534
508
|
return renderExtensions([ annotate ], env);
|
|
535
509
|
return '';
|
|
@@ -539,13 +513,40 @@ function toCdsSourceCsn(csn, options) {
|
|
|
539
513
|
* Create an "annotate" statement as a CSN extension for all annotations of (sub-)elements.
|
|
540
514
|
* If no annotation was found, we return `null`.
|
|
541
515
|
*
|
|
542
|
-
* @param {string} artifactName
|
|
543
516
|
* @param {CSN.Artifact} artWithElements
|
|
517
|
+
* @param {CdlRenderEnvironment} env
|
|
544
518
|
* @return {CSN.Extension|null}
|
|
545
519
|
*/
|
|
546
|
-
function collectAnnotationsOfElements(
|
|
547
|
-
|
|
548
|
-
|
|
520
|
+
function collectAnnotationsOfElements(artWithElements, env) {
|
|
521
|
+
// Array of structures, which may be annotated as well.
|
|
522
|
+
if (!artWithElements.elements && artWithElements.items) {
|
|
523
|
+
env = envAddPath(env, [ 'items' ]);
|
|
524
|
+
artWithElements = artWithElements.items;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const annotate = { annotate: env.path[1] };
|
|
528
|
+
|
|
529
|
+
// Based on the current path, create a correctly nested structure
|
|
530
|
+
// of elements for which we collect annotations.
|
|
531
|
+
// TODO: More properties?
|
|
532
|
+
let obj = annotate;
|
|
533
|
+
for (let i = 2; i < env.path.length; ++i) {
|
|
534
|
+
const key = env.path[i];
|
|
535
|
+
if (key === 'elements' || key === 'actions') {
|
|
536
|
+
obj[key] = Object.create(null);
|
|
537
|
+
const elem = env.path[i + 1];
|
|
538
|
+
obj[key][elem] = {};
|
|
539
|
+
obj = obj[key][elem];
|
|
540
|
+
}
|
|
541
|
+
else if (key === 'returns') {
|
|
542
|
+
obj.returns = {};
|
|
543
|
+
obj = obj.returns;
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
// ignore others, e.g. 'items'
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return collectAnnos(obj, artWithElements) ? annotate : null;
|
|
549
550
|
|
|
550
551
|
/**
|
|
551
552
|
* Recursive function to collect annotations. `annotateObj` will get an `elements`
|
|
@@ -749,7 +750,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
749
750
|
if (col.cast.target && !col.cast.type)
|
|
750
751
|
result += ` : ${renderRedirectedTo(col.cast, env)}`;
|
|
751
752
|
else
|
|
752
|
-
result += ` : ${
|
|
753
|
+
result += ` : ${renderTypeReferenceAndProps(col.cast, env, { typeRefOnly: true, noAnnoCollect: true })}`;
|
|
753
754
|
}
|
|
754
755
|
return result;
|
|
755
756
|
}
|
|
@@ -758,58 +759,51 @@ function toCdsSourceCsn(csn, options) {
|
|
|
758
759
|
* For the current column, render a (nested) inline/expand. If the current column
|
|
759
760
|
* does not have an .expand/.inline, '' is returned
|
|
760
761
|
*
|
|
761
|
-
* @param {object}
|
|
762
|
-
* @param {CdlRenderEnvironment}
|
|
762
|
+
* @param {object} obj Thing with .expand or .inline
|
|
763
|
+
* @param {CdlRenderEnvironment} env
|
|
763
764
|
* @returns {string}
|
|
764
765
|
*/
|
|
765
|
-
function renderInlineExpand(
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
if (obj.cast && obj.cast.type)
|
|
782
|
-
result += ` : ${renderTypeReference(obj.cast, createEnv())}`;
|
|
783
|
-
else if (obj.cast && obj.cast.target) // test tbd
|
|
784
|
-
result += ` : ${renderRedirectedTo(obj.cast, env)}`;
|
|
785
|
-
return result;
|
|
786
|
-
}
|
|
766
|
+
function renderInlineExpand(obj, env) {
|
|
767
|
+
// No expression to render for { * } as alias
|
|
768
|
+
let result = (obj.as && obj.expand && !obj.ref) ? '' : renderExpr(obj, env);
|
|
769
|
+
|
|
770
|
+
// s as alias { * }
|
|
771
|
+
if (obj.as && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
|
|
772
|
+
result += ` as ${obj.as}`;
|
|
773
|
+
|
|
774
|
+
// We found a leaf - no further drilling
|
|
775
|
+
if (!obj.inline && !obj.expand) {
|
|
776
|
+
if (obj.cast && obj.cast.type)
|
|
777
|
+
result += ` : ${renderTypeReferenceAndProps(obj.cast, createEnv(), { noAnnoCollect: true })}`;
|
|
778
|
+
else if (obj.cast && obj.cast.target) // test tbd
|
|
779
|
+
result += ` : ${renderRedirectedTo(obj.cast, env)}`;
|
|
780
|
+
return result;
|
|
781
|
+
}
|
|
787
782
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
783
|
+
if (obj.inline)
|
|
784
|
+
result += '.{\n';
|
|
785
|
+
else
|
|
786
|
+
result += result !== '' ? ' {\n' : '{\n';
|
|
792
787
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
788
|
+
// Drill down and render children of the expand/inline
|
|
789
|
+
const childEnv = increaseIndent(env);
|
|
790
|
+
const expandInline = obj.expand || obj.inline;
|
|
791
|
+
expandInline.forEach((elm, i) => {
|
|
792
|
+
result += `${childEnv.indent}${renderInlineExpand(elm, childEnv)}`;
|
|
793
|
+
if (i < expandInline.length - 1)
|
|
794
|
+
result += ',\n';
|
|
795
|
+
});
|
|
796
|
+
result += `\n${env.indent}}`;
|
|
802
797
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
798
|
+
// Don't forget about the .excluding
|
|
799
|
+
if (obj.excluding)
|
|
800
|
+
result += ` excluding { ${obj.excluding.join(',')} }`;
|
|
806
801
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
802
|
+
// { * } as expand
|
|
803
|
+
if (!obj.ref && obj.as)
|
|
804
|
+
result += ` as ${obj.as}`;
|
|
810
805
|
|
|
811
|
-
|
|
812
|
-
}
|
|
806
|
+
return result;
|
|
813
807
|
}
|
|
814
808
|
|
|
815
809
|
/**
|
|
@@ -855,7 +849,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
855
849
|
else {
|
|
856
850
|
result += ' as ';
|
|
857
851
|
}
|
|
858
|
-
env._artifact = art;
|
|
859
852
|
result += renderQuery(getNormalizedQuery(art).query, true, syntax, env, [ 'definitions', artifactName, 'query' ], art.elements);
|
|
860
853
|
result += ';\n';
|
|
861
854
|
result += renderQueryElementAnnotations(artifactName, art, env);
|
|
@@ -955,7 +948,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
955
948
|
}
|
|
956
949
|
|
|
957
950
|
/**
|
|
958
|
-
* Render a query's LIMIT clause, which may
|
|
951
|
+
* Render a query's LIMIT clause, which may also have OFFSET.
|
|
959
952
|
*
|
|
960
953
|
* @param {CSN.QueryLimit} limit
|
|
961
954
|
* @param {CdlRenderEnvironment} limitEnv
|
|
@@ -1029,7 +1022,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1029
1022
|
let result = '';
|
|
1030
1023
|
const childEnv = increaseIndent(env);
|
|
1031
1024
|
for (const name in art.actions)
|
|
1032
|
-
result += renderActionOrFunction(name, art.actions[name], childEnv);
|
|
1025
|
+
result += renderActionOrFunction(name, art.actions[name], envAddPath(childEnv, [ 'actions', name ]));
|
|
1033
1026
|
|
|
1034
1027
|
// Even if we have seen actions/functions, they might all have been ignored
|
|
1035
1028
|
if (result !== '')
|
|
@@ -1052,19 +1045,8 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1052
1045
|
const parameters = Object.keys(act.params || []).map(name => renderParameter(name, act.params[name], childEnv)).join(',\n');
|
|
1053
1046
|
result += (parameters === '') ? '()' : `(\n${parameters}\n${env.indent})`;
|
|
1054
1047
|
if (act.returns) {
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
const isBoundAction = (env.artifactName !== actionName);
|
|
1058
|
-
const anno = {
|
|
1059
|
-
annotate: env.artifactName || actionName,
|
|
1060
|
-
};
|
|
1061
|
-
if (isBoundAction)
|
|
1062
|
-
anno.actions = { [actionName]: { returns: { elements: act.returns.elements } } };
|
|
1063
|
-
else
|
|
1064
|
-
anno.returns = { elements: act.returns.elements };
|
|
1065
|
-
subelementAnnotates.push(anno);
|
|
1066
|
-
}
|
|
1067
|
-
result += ` returns ${renderTypeReference(act.returns, env)}${renderNullability(act.returns)}`;
|
|
1048
|
+
const actEnv = envAddPath(env, [ 'returns' ]);
|
|
1049
|
+
result += ` returns ${renderTypeReferenceAndProps(act.returns, actEnv)}`;
|
|
1068
1050
|
}
|
|
1069
1051
|
|
|
1070
1052
|
result += ';\n';
|
|
@@ -1080,11 +1062,9 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1080
1062
|
* @return {string}
|
|
1081
1063
|
*/
|
|
1082
1064
|
function renderParameter(parName, par, env) {
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
result += renderNullability(par);
|
|
1065
|
+
env = envAddPath(env, [ 'params', parName ]);
|
|
1066
|
+
let result = `${renderAnnotationAssignmentsAndDocComment(par, env)}${env.indent}`;
|
|
1067
|
+
result += `${quoteIdIfRequired(parName)} : ${renderTypeReferenceAndProps(par, env)}`;
|
|
1088
1068
|
return result;
|
|
1089
1069
|
}
|
|
1090
1070
|
|
|
@@ -1095,7 +1075,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1095
1075
|
* @param {string} artifactName
|
|
1096
1076
|
* @param {CSN.Artifact} art
|
|
1097
1077
|
* @param {CdlRenderEnvironment} env
|
|
1098
|
-
* @param {String} artType - used for rendering csn.vocabularies, as the annotations there do not have a kind. Only in toCdl mode
|
|
1078
|
+
* @param {String} [artType] - used for rendering csn.vocabularies, as the annotations there do not have a kind. Only in toCdl mode
|
|
1099
1079
|
* @return {string}
|
|
1100
1080
|
*/
|
|
1101
1081
|
function renderTypeOrAnnotation(artifactName, art, env, artType) {
|
|
@@ -1104,96 +1084,71 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1104
1084
|
if (art.includes)
|
|
1105
1085
|
result += renderIncludes(art.includes);
|
|
1106
1086
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
result +=
|
|
1111
|
-
for (const name in art.elements)
|
|
1112
|
-
result += renderElement(name, art.elements[name], childEnv);
|
|
1113
|
-
|
|
1114
|
-
result += `${env.indent}};\n`;
|
|
1115
|
-
}
|
|
1116
|
-
else {
|
|
1117
|
-
// Derived type or annotation with non-anonymous type
|
|
1118
|
-
result += ` : ${renderTypeReference(art, env, false)}`;
|
|
1119
|
-
if (art.default)
|
|
1120
|
-
result += ` default ${renderExpr(art.default, env)}`;
|
|
1121
|
-
result += ';\n';
|
|
1122
|
-
}
|
|
1087
|
+
if (!art.type && art.elements) // For nicer output, no colon if unnamed structure is used.
|
|
1088
|
+
result += ` ${renderTypeReferenceAndProps(art, env)};\n`;
|
|
1089
|
+
else
|
|
1090
|
+
result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
|
|
1123
1091
|
return result;
|
|
1124
1092
|
}
|
|
1125
1093
|
|
|
1126
1094
|
/**
|
|
1127
|
-
* Render a reference to a type used by '
|
|
1128
|
-
*
|
|
1095
|
+
* Render a reference to a type used by 'artifact' (named or inline) and (element) properties
|
|
1096
|
+
* such as `not null` and `default <xpr>`.
|
|
1097
|
+
* Allow suppressing rendering of structs such as enums - used in columns for example.
|
|
1129
1098
|
*
|
|
1130
|
-
* @param {
|
|
1099
|
+
* @param {object} artifact
|
|
1131
1100
|
* @param {CdlRenderEnvironment} env
|
|
1132
|
-
* @param {
|
|
1101
|
+
* @param {object} [config={}] - `typeRefOnly` Whether to only render type defs, no arrayed/structured/enum.
|
|
1102
|
+
* - `noAnnoCollect` Do not collect annotations of sub-elements.
|
|
1133
1103
|
* @return {string}
|
|
1134
1104
|
*/
|
|
1135
|
-
function
|
|
1105
|
+
function renderTypeReferenceAndProps(artifact, env, config = {}) {
|
|
1136
1106
|
let result = '';
|
|
1137
|
-
|
|
1138
|
-
//
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
subelementAnnotates.push(annotate);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
return rc;
|
|
1107
|
+
const { typeRefOnly, noAnnoCollect } = config;
|
|
1108
|
+
let isTypeDef = env.path?.length === 2; // e.g [ 'definitions', typeDef ];
|
|
1109
|
+
|
|
1110
|
+
if (typeRefOnly && !artifact.type)
|
|
1111
|
+
throw new ModelError(`Expected artifact to have a type; in: ${env.artifactName}`);
|
|
1112
|
+
|
|
1113
|
+
if (artifact.localized) // works even for type definitions
|
|
1114
|
+
result += 'localized ';
|
|
1115
|
+
|
|
1116
|
+
if (!artifact.type && artifact.items) {
|
|
1117
|
+
result += 'many '; // alternative: 'array of'; but not used
|
|
1118
|
+
artifact = artifact.items;
|
|
1119
|
+
env = envAddPath(env, 'items');
|
|
1120
|
+
// element keywords allowed in MANY case; was an oversight when arrays were introduced.
|
|
1121
|
+
isTypeDef = false;
|
|
1122
|
+
// "many many" does not work in CDL, so we don't check for it.
|
|
1157
1123
|
}
|
|
1158
1124
|
|
|
1159
|
-
|
|
1160
|
-
result += (elm.localized ? 'localized ' : '');
|
|
1161
|
-
|
|
1162
|
-
// Anonymous structured type
|
|
1163
|
-
if (!elm.type) {
|
|
1164
|
-
if (!elm.elements)
|
|
1165
|
-
throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
|
|
1166
|
-
|
|
1125
|
+
if (!artifact.type && artifact.elements) {
|
|
1167
1126
|
result += '{\n';
|
|
1168
|
-
const childEnv = increaseIndent(env);
|
|
1169
|
-
for (const name in
|
|
1170
|
-
result += renderElement(name,
|
|
1127
|
+
const childEnv = envAddPath(increaseIndent(env), 'items');
|
|
1128
|
+
for (const name in artifact.elements)
|
|
1129
|
+
result += renderElement(name, artifact.elements[name], childEnv, null);
|
|
1171
1130
|
|
|
1172
1131
|
result += `${env.indent}}`;
|
|
1132
|
+
if (!isTypeDef)
|
|
1133
|
+
result += renderNullability(artifact);
|
|
1134
|
+
// structured default not possible at the moment
|
|
1173
1135
|
return result;
|
|
1174
1136
|
}
|
|
1175
1137
|
|
|
1176
|
-
const comp = 'cds.Composition';
|
|
1177
1138
|
// Association type
|
|
1178
|
-
if (
|
|
1139
|
+
if (artifact.type === 'cds.Association' || artifact.type === 'cds.Composition') {
|
|
1140
|
+
const isComp = artifact.type === 'cds.Composition';
|
|
1179
1141
|
// Type, cardinality and target; CAPire uses CamelCase
|
|
1180
|
-
result +=
|
|
1181
|
-
|
|
1182
|
-
if (isSimpleCardinality(elm.cardinality)) {
|
|
1183
|
-
result += renderSimpleCardinality(elm);
|
|
1184
|
-
}
|
|
1185
|
-
else {
|
|
1186
|
-
result += renderCardinality(elm.cardinality) +
|
|
1187
|
-
((elm.type === comp) ? ' of ' : ' to ');
|
|
1188
|
-
}
|
|
1142
|
+
result += isComp ? 'Composition' : 'Association';
|
|
1143
|
+
result += renderCardinality(artifact);
|
|
1189
1144
|
|
|
1190
1145
|
// `targetAspect` may be set by the core compiler and refers to the original named or unnamed aspect.
|
|
1191
1146
|
// In parseCdl, `target` may still be an object containing elements. This would be replaced
|
|
1192
1147
|
// by targetAspect in client CSN, but we can't rely on that.
|
|
1193
1148
|
// If a name exists (either in target or targetAspect), prefer it over rendering elements.
|
|
1194
|
-
const elements =
|
|
1195
|
-
if (typeof
|
|
1196
|
-
result += renderAbsolutePath({ ref: [
|
|
1149
|
+
const elements = artifact.target && artifact.target.elements || artifact.targetAspect && artifact.targetAspect.elements;
|
|
1150
|
+
if (typeof artifact.target === 'string' || typeof artifact.targetAspect === 'string') {
|
|
1151
|
+
result += renderAbsolutePath({ ref: [ artifact.target || artifact.targetAspect ] }, env);
|
|
1197
1152
|
}
|
|
1198
1153
|
else if (elements) {
|
|
1199
1154
|
// anonymous aspect, either parseCdl or client CSN.
|
|
@@ -1209,40 +1164,48 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1209
1164
|
}
|
|
1210
1165
|
|
|
1211
1166
|
// ON-condition (if any)
|
|
1212
|
-
if (
|
|
1213
|
-
result += ` on ${renderExpr(
|
|
1167
|
+
if (artifact.on)
|
|
1168
|
+
result += ` on ${renderExpr(artifact.on, env, true, true)}`;
|
|
1214
1169
|
|
|
1215
1170
|
|
|
1216
1171
|
// Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
|
|
1217
|
-
if (
|
|
1218
|
-
result += ` { ${Object.keys(
|
|
1172
|
+
if (artifact.keys && !artifact.on)
|
|
1173
|
+
result += ` { ${Object.keys(artifact.keys).map(name => renderForeignKey(artifact.keys[name], env)).join(', ')} }`;
|
|
1174
|
+
|
|
1175
|
+
if (!isTypeDef && !artifact.on) // unmanaged associations can't be followed by "not null"
|
|
1176
|
+
result += renderNullability(artifact);
|
|
1177
|
+
// DEFAULT not possible here.
|
|
1219
1178
|
|
|
1220
1179
|
return result;
|
|
1221
1180
|
}
|
|
1222
1181
|
|
|
1223
|
-
//
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
else {
|
|
1236
|
-
result += renderAbsolutePath(elm.type, env);
|
|
1237
|
-
}
|
|
1238
|
-
return result;
|
|
1182
|
+
// At this point, we will render a named type.
|
|
1183
|
+
|
|
1184
|
+
// If we have a type and elements, we may have sub-structure annotates that would
|
|
1185
|
+
// get lost if we only render the type name.
|
|
1186
|
+
// TODO: Can we annotate elements of targetAspect?
|
|
1187
|
+
// If so, move this block before the composition rendering.
|
|
1188
|
+
if (!noAnnoCollect && (artifact.elements || artifact.items?.elements)) {
|
|
1189
|
+
const annotate = collectAnnotationsOfElements(artifact, env);
|
|
1190
|
+
if (annotate)
|
|
1191
|
+
subelementAnnotates.push(annotate);
|
|
1239
1192
|
}
|
|
1240
1193
|
|
|
1241
|
-
//
|
|
1242
|
-
|
|
1194
|
+
// Reference to another artifact
|
|
1195
|
+
if (typeof artifact.type === 'string') {
|
|
1196
|
+
// If we get here, it must be a named type
|
|
1197
|
+
result += renderNamedTypeWithParameters(artifact);
|
|
1198
|
+
}
|
|
1199
|
+
else if (artifact.type?.ref) {
|
|
1200
|
+
result += renderAbsolutePath(artifact.type, env);
|
|
1201
|
+
}
|
|
1243
1202
|
|
|
1244
|
-
if (
|
|
1245
|
-
result += renderEnum(
|
|
1203
|
+
if (artifact.enum && !typeRefOnly)
|
|
1204
|
+
result += renderEnum(artifact.enum, env);
|
|
1205
|
+
if (!isTypeDef) // NOT NULL not possible for not-arrayed type definitions
|
|
1206
|
+
result += renderNullability(artifact);
|
|
1207
|
+
if (artifact.default)
|
|
1208
|
+
result += ` default ${renderExpr(artifact.default, env)}`;
|
|
1246
1209
|
|
|
1247
1210
|
return result;
|
|
1248
1211
|
}
|
|
@@ -1255,7 +1218,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1255
1218
|
* @return {string}
|
|
1256
1219
|
*/
|
|
1257
1220
|
function renderRedirectedTo(art, env) {
|
|
1258
|
-
let result = `redirected to ${
|
|
1221
|
+
let result = `redirected to ${quotePathIfRequired(art.target)}`;
|
|
1259
1222
|
if (art.on)
|
|
1260
1223
|
result += ` on ${renderExpr(art.on, env, true, true)}`;
|
|
1261
1224
|
else if (art.keys)
|
|
@@ -1280,8 +1243,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1280
1243
|
result += artWithType.type.slice(4);
|
|
1281
1244
|
}
|
|
1282
1245
|
else {
|
|
1283
|
-
// Simple absolute name
|
|
1284
|
-
// Type names are never flattened (derived types are unraveled in HANA)
|
|
1285
1246
|
result += renderArtifactName(artWithType.type);
|
|
1286
1247
|
}
|
|
1287
1248
|
|
|
@@ -1346,7 +1307,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1346
1307
|
}
|
|
1347
1308
|
// Shorthand for absolute path (as string)
|
|
1348
1309
|
else if (x['=']) {
|
|
1349
|
-
return
|
|
1310
|
+
return quotePathIfRequired(x['=']);
|
|
1350
1311
|
}
|
|
1351
1312
|
// Shorthand for ellipsis: `... up to <val>`
|
|
1352
1313
|
else if (x['...']) {
|
|
@@ -1355,11 +1316,15 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1355
1316
|
return `... up to ${renderAnnotationValue(x['...'], env)}`;
|
|
1356
1317
|
}
|
|
1357
1318
|
|
|
1358
|
-
// Struct value (can
|
|
1359
|
-
|
|
1360
|
-
//
|
|
1361
|
-
|
|
1362
|
-
|
|
1319
|
+
// Struct value (can currently only occur within an array)
|
|
1320
|
+
// Render as one-liner if there is at most one key. Render as multi-line
|
|
1321
|
+
// struct if there are more and use nicer indentation.
|
|
1322
|
+
const keys = Object.keys(x);
|
|
1323
|
+
const childEnv = keys.length <= 1 ? env : increaseIndent(env);
|
|
1324
|
+
const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(x[key], childEnv)}`);
|
|
1325
|
+
if (values.length <= 1)
|
|
1326
|
+
return `{ ${values.join(', ')} }`;
|
|
1327
|
+
return `{\n${childEnv.indent}${values.join(`,\n${childEnv.indent}`)}\n${env.indent}}`;
|
|
1363
1328
|
}
|
|
1364
1329
|
// Null
|
|
1365
1330
|
else if (x === null) {
|
|
@@ -1430,12 +1395,18 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1430
1395
|
const args = node.args || [];
|
|
1431
1396
|
|
|
1432
1397
|
// Positional arguments
|
|
1433
|
-
if (Array.isArray(args))
|
|
1398
|
+
if (Array.isArray(args)) {
|
|
1399
|
+
const func = node.func?.toUpperCase();
|
|
1400
|
+
if (func)
|
|
1401
|
+
return args.map((arg, i) => renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i))).join(', ');
|
|
1402
|
+
|
|
1434
1403
|
return args.map(arg => renderArgument(arg, env)).join(', ');
|
|
1404
|
+
}
|
|
1435
1405
|
|
|
1436
1406
|
// Named arguments (object/dict)
|
|
1437
|
-
else if (typeof args === 'object')
|
|
1407
|
+
else if (typeof args === 'object') {
|
|
1438
1408
|
return Object.keys(args).map(key => `${quoteIdIfRequired(key)} ${sep} ${renderArgument(args[key], env)}`).join(', ');
|
|
1409
|
+
}
|
|
1439
1410
|
|
|
1440
1411
|
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1441
1412
|
}
|
|
@@ -1446,23 +1417,40 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1446
1417
|
*
|
|
1447
1418
|
* @param {any} arg
|
|
1448
1419
|
* @param {CdlRenderEnvironment} env
|
|
1420
|
+
* @param {string[]} additionalAllowedKeywords
|
|
1449
1421
|
* @return {string}
|
|
1450
1422
|
*/
|
|
1451
|
-
function renderArgument(arg, env) {
|
|
1423
|
+
function renderArgument(arg, env, additionalAllowedKeywords = []) {
|
|
1452
1424
|
// If the argument is a xpr with e.g. `=`, it may require parentheses.
|
|
1453
1425
|
// For nested xpr, `renderExpr()` will already add parentheses.
|
|
1454
|
-
return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr), true);
|
|
1426
|
+
return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalAllowedKeywords), true);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
* Render an artifact's cardinality.
|
|
1431
|
+
*
|
|
1432
|
+
* @param artifact
|
|
1433
|
+
* @returns {string}
|
|
1434
|
+
*/
|
|
1435
|
+
function renderCardinality(artifact) {
|
|
1436
|
+
if (isSimpleCardinality(artifact.cardinality))
|
|
1437
|
+
return renderSimpleCardinality(artifact);
|
|
1438
|
+
return renderBracketCardinality(artifact);
|
|
1455
1439
|
}
|
|
1456
1440
|
|
|
1457
1441
|
/**
|
|
1458
1442
|
* Render a cardinality (only those parts that were actually provided)
|
|
1459
1443
|
*
|
|
1460
|
-
* @param {CSN.
|
|
1444
|
+
* @param {CSN.Artifact} art
|
|
1461
1445
|
* @return {string}
|
|
1462
1446
|
*/
|
|
1463
|
-
function
|
|
1447
|
+
function renderBracketCardinality(art) {
|
|
1448
|
+
const isComp = art.type === 'cds.Composition';
|
|
1449
|
+
const suffix = (isComp ? ' of ' : ' to ');
|
|
1450
|
+
const card = art.cardinality;
|
|
1451
|
+
|
|
1464
1452
|
if (!card)
|
|
1465
|
-
return
|
|
1453
|
+
return suffix;
|
|
1466
1454
|
|
|
1467
1455
|
let result = '[';
|
|
1468
1456
|
if (card.src !== undefined)
|
|
@@ -1474,7 +1462,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1474
1462
|
if (card.max !== undefined)
|
|
1475
1463
|
result += card.max;
|
|
1476
1464
|
|
|
1477
|
-
return `${result}]`;
|
|
1465
|
+
return `${result}]${suffix}`;
|
|
1478
1466
|
}
|
|
1479
1467
|
|
|
1480
1468
|
/**
|
|
@@ -1543,12 +1531,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1543
1531
|
if (params.length === 0)
|
|
1544
1532
|
return '';
|
|
1545
1533
|
|
|
1546
|
-
// TODO(v3): Remove special handling of srid
|
|
1547
|
-
// For backwards compatibility, do not render srid as a named argument for POINT/GEOMETRY builtins.
|
|
1548
|
-
if (artWithType.srid !== undefined &&
|
|
1549
|
-
(artWithType.type === 'cds.hana.ST_POINT' || artWithType.type === 'cds.hana.ST_GEOMETRY'))
|
|
1550
|
-
return `(${artWithType.srid})`;
|
|
1551
|
-
|
|
1552
1534
|
// Special cases for 1 or 2 arguments.
|
|
1553
1535
|
if (params.length === 1 && artWithType.length !== undefined)
|
|
1554
1536
|
return `(${artWithType.length})`;
|
|
@@ -1603,6 +1585,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1603
1585
|
// We expand this pattern to also include dots after the first character.
|
|
1604
1586
|
// If the annotation does not follow this pattern `ident(.@ident)*`, it must be quoted:
|
|
1605
1587
|
// `@identifier@identifier` must be quoted but `@identifier.@identifier` should not.
|
|
1588
|
+
// TODO: Use quoteAnnotationPathIfRequired()
|
|
1606
1589
|
const annoRequiresQuoting = !/^[$_a-zA-Z][$_a-zA-Z0-9.]*(?:\.@[$_a-zA-Z][$_a-zA-Z0-9.]*)*$/.test(nameBeforeVariant);
|
|
1607
1590
|
// Unfortunately, the compiler does not allow `.` after the first variant identifier,
|
|
1608
1591
|
// even though that is the result after flattening.
|
|
@@ -1624,77 +1607,47 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1624
1607
|
}
|
|
1625
1608
|
|
|
1626
1609
|
/**
|
|
1627
|
-
* Render the name of an artifact,
|
|
1628
|
-
* and the real name of the artifact. In case of plain names, this
|
|
1629
|
-
* is equivalent to simply flattening and uppercasing the whole name.
|
|
1630
|
-
*
|
|
1631
|
-
* In cdlMode, the prefix is extended to handle cases like an entity shadowing the prefix
|
|
1632
|
-
* of another entity -> Service.E and Service.E.Sub
|
|
1633
|
-
*
|
|
1634
|
-
* To handle such cases for hdbcds in quoted/hdbcds, we:
|
|
1635
|
-
* - Find the part of the name that is no longer prefix (context/service)
|
|
1636
|
-
* - For Service.E -> E, for Service.E.Sub -> E.Sub
|
|
1637
|
-
* - Replace all dots in this "real name" with underscores
|
|
1638
|
-
* - Join with the env prefix
|
|
1610
|
+
* Render the name of an artifact, quote path steps if necessary.
|
|
1639
1611
|
*
|
|
1640
1612
|
* @param {string} artifactName Artifact name to render
|
|
1641
1613
|
* @return {string} Artifact name ready for rendering
|
|
1642
1614
|
*/
|
|
1643
1615
|
function renderArtifactName(artifactName) {
|
|
1644
|
-
|
|
1645
|
-
const prefix = (realname !== artifactName) ? artifactName.slice(0, artifactName.length - realname.length - 1) : '';
|
|
1646
|
-
return prefix ? `${quoteIdIfRequired(prefix)}.${realname.split('.').map(quoteIdIfRequired).join('.')}` : realname.split('.').map(quoteIdIfRequired).join('.');
|
|
1616
|
+
return quotePathIfRequired(artifactName);
|
|
1647
1617
|
}
|
|
1648
1618
|
|
|
1649
1619
|
/**
|
|
1650
|
-
*
|
|
1620
|
+
* Render a function expression.
|
|
1651
1621
|
*
|
|
1652
|
-
* @param {
|
|
1653
|
-
* @
|
|
1622
|
+
* @param {object} obj Object with .func and optionally .args
|
|
1623
|
+
* @param {CdlRenderEnvironment} env
|
|
1624
|
+
* @returns {string}
|
|
1654
1625
|
*/
|
|
1655
|
-
function
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
let seen = '';
|
|
1663
|
-
for (let i = 0; i < parts.length; i++) {
|
|
1664
|
-
if (seen !== '')
|
|
1665
|
-
seen = `${seen}.${parts[i]}`;
|
|
1666
|
-
else
|
|
1667
|
-
seen = parts[i];
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
const art = csn.definitions[seen];
|
|
1671
|
-
if (!art || (art.kind !== 'service' && art.kind !== 'context')) {
|
|
1672
|
-
// We found a case where the prefix ended
|
|
1673
|
-
// Return everything following
|
|
1674
|
-
return parts.slice(i).join('.');
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
// we seem to have a normal case - just return the last part
|
|
1679
|
-
return getLastPartOf(artifactName);
|
|
1626
|
+
function renderFuncExpr( obj, env ) {
|
|
1627
|
+
if (keywords.cdl_functions.includes(obj.func.toUpperCase()))
|
|
1628
|
+
return obj.func;
|
|
1629
|
+
const name = identifierRegex.test(obj.func) ? obj.func : quote(obj.func);
|
|
1630
|
+
return `${name}(${renderArgs( obj, '=>', env )})`;
|
|
1680
1631
|
}
|
|
1681
1632
|
|
|
1682
1633
|
/**
|
|
1683
1634
|
* Render an expression.
|
|
1684
1635
|
*
|
|
1685
1636
|
* @param {any} expr
|
|
1686
|
-
* @param {CdlRenderEnvironment}
|
|
1687
|
-
* @param {boolean} [
|
|
1688
|
-
* @param {boolean} [
|
|
1637
|
+
* @param {CdlRenderEnvironment} exprEnv
|
|
1638
|
+
* @param {boolean} [isInline]
|
|
1639
|
+
* @param {boolean} [isNestedExpr]
|
|
1689
1640
|
* @param {boolean} [alwaysRenderCast]
|
|
1690
1641
|
* @returns {string}
|
|
1691
1642
|
*/
|
|
1692
|
-
function renderExpr(expr,
|
|
1643
|
+
function renderExpr(expr, exprEnv, isInline, isNestedExpr, alwaysRenderCast) {
|
|
1693
1644
|
if (!_renderExpr) {
|
|
1694
1645
|
_renderExpr = getExpressionRenderer({
|
|
1695
|
-
finalize
|
|
1696
|
-
|
|
1697
|
-
|
|
1646
|
+
finalize(x) {
|
|
1647
|
+
return x;
|
|
1648
|
+
},
|
|
1649
|
+
explicitTypeCast(x, env) {
|
|
1650
|
+
const typeRef = renderTypeReferenceAndProps(x.cast, env, { typeRefOnly: true, noAnnoCollect: true });
|
|
1698
1651
|
const arg = { ...x, cast: null }; // "arg" without cast to avoid recursion.
|
|
1699
1652
|
return `cast(${renderArgument(arg, env)} as ${typeRef})`;
|
|
1700
1653
|
},
|
|
@@ -1723,42 +1676,40 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1723
1676
|
aliasOnly(x, _env) {
|
|
1724
1677
|
return x.as;
|
|
1725
1678
|
},
|
|
1726
|
-
enum
|
|
1679
|
+
enum(x) {
|
|
1680
|
+
return `#${x['#']}`;
|
|
1681
|
+
},
|
|
1727
1682
|
ref(x, env) {
|
|
1728
1683
|
const { inline } = this;
|
|
1729
1684
|
return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, inline, env)).join('.')}`;
|
|
1730
1685
|
},
|
|
1731
|
-
windowFunction
|
|
1732
|
-
const funcDef =
|
|
1686
|
+
windowFunction(x, env) {
|
|
1687
|
+
const funcDef = renderFuncExpr(x, env);
|
|
1733
1688
|
const windowFunctionOperator = x.xpr.shift(); // OVER ...
|
|
1734
1689
|
return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
|
|
1735
1690
|
},
|
|
1736
|
-
func
|
|
1737
|
-
|
|
1738
|
-
// test for non-regular HANA identifier that needs to be quoted
|
|
1739
|
-
// identifier {letter}({letter_or_digit}|[#$])*
|
|
1740
|
-
// letter [A-Za-z_]
|
|
1741
|
-
// letter_or_digit [A-Za-z_0-9]
|
|
1742
|
-
|
|
1743
|
-
const regex = /^[a-zA-Z][\w#$]*$/g;
|
|
1744
|
-
const funcName = regex.test(x.func) ? x.func : quoteIdIfRequired(x.func);
|
|
1745
|
-
return renderFunc(funcName, x, 'cap', a => renderArgs(a, '=>', env));
|
|
1691
|
+
func(x, env) {
|
|
1692
|
+
return renderFuncExpr(x, env);
|
|
1746
1693
|
},
|
|
1747
1694
|
xpr(x, env) {
|
|
1748
1695
|
if (this.nestedExpr && !x.cast || x.xpr.some(s => s === 'exists'))
|
|
1749
1696
|
return `(${renderExpr(x.xpr, env, this.inline, true)})`;
|
|
1750
|
-
|
|
1751
1697
|
return renderExpr(x.xpr, env, this.inline, true);
|
|
1752
1698
|
},
|
|
1753
1699
|
// Sub-queries in expressions need to be in parentheses, otherwise
|
|
1754
1700
|
// left-associativity of UNIONS may result in different results.
|
|
1755
1701
|
// For example: `select from E where id in (select from E union select from E);`:
|
|
1756
1702
|
// Without parentheses, it would be different query.
|
|
1757
|
-
SET
|
|
1758
|
-
|
|
1703
|
+
SET(x, env) {
|
|
1704
|
+
return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
|
|
1705
|
+
},
|
|
1706
|
+
SELECT(x, env) {
|
|
1707
|
+
return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
|
|
1708
|
+
},
|
|
1759
1709
|
});
|
|
1760
1710
|
}
|
|
1761
|
-
|
|
1711
|
+
|
|
1712
|
+
return _renderExpr(expr, exprEnv, isInline, isNestedExpr, alwaysRenderCast);
|
|
1762
1713
|
}
|
|
1763
1714
|
}
|
|
1764
1715
|
|
|
@@ -1772,15 +1723,19 @@ function createEnv() {
|
|
|
1772
1723
|
return {
|
|
1773
1724
|
// Current indentation string
|
|
1774
1725
|
indent: '',
|
|
1775
|
-
|
|
1776
|
-
topLevelAliases: Object.create(null),
|
|
1777
|
-
// Current name prefix (including trailing dot if not empty)
|
|
1778
|
-
namePrefix: '',
|
|
1726
|
+
path: null,
|
|
1779
1727
|
artifactName: '',
|
|
1780
1728
|
elementName: '',
|
|
1781
1729
|
};
|
|
1782
1730
|
}
|
|
1783
1731
|
|
|
1732
|
+
function envAddPath(env, path) {
|
|
1733
|
+
return Object.assign({}, env, { path: [ ...env.path, ...path ] } );
|
|
1734
|
+
}
|
|
1735
|
+
function envNewPath(env, path) {
|
|
1736
|
+
return Object.assign({}, env, { path: [ ...path ] } );
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1784
1739
|
/**
|
|
1785
1740
|
* Returns a copy of 'env' with increased indentation (and reset name prefix)
|
|
1786
1741
|
*
|
|
@@ -1788,22 +1743,25 @@ function createEnv() {
|
|
|
1788
1743
|
* @returns {CdlRenderEnvironment}
|
|
1789
1744
|
*/
|
|
1790
1745
|
function increaseIndent(env) {
|
|
1791
|
-
return Object.assign({}, env, {
|
|
1746
|
+
return Object.assign({}, env, { indent: `${env.indent} ` });
|
|
1792
1747
|
}
|
|
1793
1748
|
|
|
1794
1749
|
/**
|
|
1795
|
-
*
|
|
1750
|
+
* Quote the path steps with `![]` if necessary. For simple ids such as
|
|
1751
|
+
* `elem` use `quoteIdIfRequired` instead.
|
|
1796
1752
|
*
|
|
1797
1753
|
* @param {string} path
|
|
1798
1754
|
* @returns {string}
|
|
1755
|
+
*
|
|
1756
|
+
* @todo For paths such as `E.key`, `key` does not have to be in quotes.
|
|
1799
1757
|
*/
|
|
1800
|
-
function
|
|
1801
|
-
// "foo"."bar"."wiz"."blub"
|
|
1758
|
+
function quotePathIfRequired(path) {
|
|
1802
1759
|
return path.split('.').map(quoteIdIfRequired).join('.');
|
|
1803
1760
|
}
|
|
1804
1761
|
|
|
1805
1762
|
/**
|
|
1806
|
-
*
|
|
1763
|
+
* Quote the id with `![]` if necessary. For paths such as `E.key` use
|
|
1764
|
+
* `quotePathIfRequired` instead.
|
|
1807
1765
|
*
|
|
1808
1766
|
* @param {string} id
|
|
1809
1767
|
* @return {string}
|
|
@@ -1816,6 +1774,23 @@ function quoteIdIfRequired(id) {
|
|
|
1816
1774
|
return id;
|
|
1817
1775
|
}
|
|
1818
1776
|
|
|
1777
|
+
/**
|
|
1778
|
+
* Quote an annotation path, e.g. `@My.@Anno.Description` if necessary.
|
|
1779
|
+
* `anno` can start with `@` but is not required to be.
|
|
1780
|
+
* Example of an annotation path that needs to be quoted:
|
|
1781
|
+
* `@![ spaces in path ].@!["double quotes"]`.
|
|
1782
|
+
*
|
|
1783
|
+
* @param {string} anno
|
|
1784
|
+
* @returns {string}
|
|
1785
|
+
*/
|
|
1786
|
+
function quoteAnnotationPathIfRequired(anno) {
|
|
1787
|
+
return anno.split('.').map((segment) => {
|
|
1788
|
+
if (segment.startsWith('@'))
|
|
1789
|
+
return `@${quoteIdIfRequired(segment.slice(1))}`;
|
|
1790
|
+
return quoteIdIfRequired(segment);
|
|
1791
|
+
}).join('.');
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1819
1794
|
/**
|
|
1820
1795
|
* Quotes the identifier using CDL-style ![]-quotes.
|
|
1821
1796
|
*
|
|
@@ -1828,20 +1803,14 @@ function quote(id) {
|
|
|
1828
1803
|
|
|
1829
1804
|
/**
|
|
1830
1805
|
* Returns true if 'id' requires quotes for CDL, i.e. if 'id'
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
* - uppercase letters
|
|
1834
|
-
* - lowercase letters
|
|
1835
|
-
* - digits
|
|
1836
|
-
* - underscore
|
|
1837
|
-
* 3. is a CDL keyword or a CDL function without parentheses (CURRENT_*, SYSUUID, ...)
|
|
1806
|
+
* does not match the first part of the `Identifier` rule of `language.g4`
|
|
1807
|
+
* or if 'id' is a reserved keyword.
|
|
1838
1808
|
*
|
|
1839
1809
|
* @param {string} id
|
|
1840
1810
|
* @return {boolean}
|
|
1841
1811
|
*/
|
|
1842
1812
|
function requiresQuotingForCdl(id) {
|
|
1843
|
-
return
|
|
1844
|
-
/\W/g.test(id.replace(/\./g, '')) ||
|
|
1813
|
+
return !identifierRegex.test(id) ||
|
|
1845
1814
|
keywords.cdl.includes(id.toUpperCase()) ||
|
|
1846
1815
|
keywords.cdl_functions.includes(id.toUpperCase());
|
|
1847
1816
|
}
|
|
@@ -1865,18 +1834,52 @@ const functionExpressionOperatorsRequireParentheses = [
|
|
|
1865
1834
|
* in a `fct(<xpr>)` expression such as `cast(<xpr> as Type)`. We only need to
|
|
1866
1835
|
* look at the first nesting level. Otherwise, `renderExpr()` will already add parentheses.
|
|
1867
1836
|
*
|
|
1868
|
-
* The list was created by looking at
|
|
1837
|
+
* The list of `functionExpressionOperatorsRequireParentheses` was created by looking at
|
|
1838
|
+
* the `expression` Antlr rule.
|
|
1839
|
+
* Because of token-rewrites, there are functions that allow operators/tokens that would
|
|
1840
|
+
* require parentheses in other functions. For example *regex functions allow `IN` but
|
|
1841
|
+
* if `IN` is used in other functions, it requires parentheses. To allow for that case,
|
|
1842
|
+
* you can set `additionalAllowedKeywords` to list of tokens that are allowed.
|
|
1869
1843
|
*
|
|
1870
|
-
*
|
|
1844
|
+
* Note that this is more of a heuristic for "nicer" CDL output. For example the
|
|
1871
1845
|
* following snippet is parsable without parentheses:
|
|
1872
1846
|
* `cast( case when int > 1 then int else 0 end as Integer ),`
|
|
1873
|
-
* However, because it is a flat xpr-array, we see `>` and assume that it is not
|
|
1847
|
+
* However, because it is a flat xpr-array, we see `>` and assume that it is not
|
|
1848
|
+
* a simple expression.
|
|
1874
1849
|
*
|
|
1875
1850
|
* @param {any[]} xpr
|
|
1851
|
+
* @param {string[]} additionalAllowedKeywords
|
|
1876
1852
|
* @return {boolean}
|
|
1877
1853
|
*/
|
|
1878
|
-
function isSimpleFunctionExpression(xpr) {
|
|
1879
|
-
return !xpr || xpr.every(val => typeof val !== 'string' ||
|
|
1854
|
+
function isSimpleFunctionExpression(xpr, additionalAllowedKeywords = []) {
|
|
1855
|
+
return !xpr || xpr.every(val => typeof val !== 'string' ||
|
|
1856
|
+
(additionalAllowedKeywords.includes(val) ||
|
|
1857
|
+
!functionExpressionOperatorsRequireParentheses.includes(val.toLowerCase())));
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
/**
|
|
1861
|
+
* Special functions may have special parser rules, such as SAP HANA RegEx functions.
|
|
1862
|
+
* They allow certain keywords in their arguments.
|
|
1863
|
+
*
|
|
1864
|
+
* This function is used to determine if arguments need to be put in parentheses or not.
|
|
1865
|
+
* See {@link isSimpleFunctionExpression}.
|
|
1866
|
+
*
|
|
1867
|
+
* @param {string} funcName
|
|
1868
|
+
* @param {number} argumentIndex
|
|
1869
|
+
* @returns {string[]}
|
|
1870
|
+
*/
|
|
1871
|
+
function getKeywordsForSpecialFunctionArgument(funcName, argumentIndex) {
|
|
1872
|
+
const f = specialFunctions[funcName] && specialFunctions[funcName][argumentIndex];
|
|
1873
|
+
if (!f)
|
|
1874
|
+
return [];
|
|
1875
|
+
const additionalKeywords = [];
|
|
1876
|
+
if (f.intro)
|
|
1877
|
+
additionalKeywords.push(...f.intro);
|
|
1878
|
+
if (f.expr)
|
|
1879
|
+
additionalKeywords.push(...f.expr);
|
|
1880
|
+
if (f.separator)
|
|
1881
|
+
additionalKeywords.push(...f.separator);
|
|
1882
|
+
return additionalKeywords;
|
|
1880
1883
|
}
|
|
1881
1884
|
|
|
1882
1885
|
/**
|
|
@@ -1884,7 +1887,7 @@ function isSimpleFunctionExpression(xpr) {
|
|
|
1884
1887
|
* @return {string}
|
|
1885
1888
|
*/
|
|
1886
1889
|
function renderIncludes(includes) {
|
|
1887
|
-
return ` : ${includes.map(name =>
|
|
1890
|
+
return ` : ${includes.map(name => quotePathIfRequired(name)).join(', ')}`;
|
|
1888
1891
|
}
|
|
1889
1892
|
|
|
1890
1893
|
/**
|
|
@@ -1965,17 +1968,9 @@ function isSimpleString(str) {
|
|
|
1965
1968
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
1966
1969
|
*
|
|
1967
1970
|
* @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
|
|
1968
|
-
* @property {
|
|
1971
|
+
* @property {string[]} [path] CSN path to the current artifact
|
|
1969
1972
|
* @property {string} [artifactName] Name of the artifact - set in renderArtifact
|
|
1970
1973
|
* @property {string} [elementName] Name of the element being rendered - set in renderElement
|
|
1971
|
-
* @property {{[name: string]: {
|
|
1972
|
-
quotedName: string,
|
|
1973
|
-
quotedAlias: string
|
|
1974
|
-
}}} topLevelAliases Dictionary of aliases for used artifact names
|
|
1975
|
-
*
|
|
1976
|
-
* @property {string} [namePrefix] Current name prefix (including trailing dot if not empty)
|
|
1977
|
-
* @property {boolean} [skipKeys]
|
|
1978
|
-
* @property {CSN.Artifact} [_artifact]
|
|
1979
1974
|
*/
|
|
1980
1975
|
|
|
1981
|
-
module.exports = {
|
|
1976
|
+
module.exports = { csnToCdl };
|