@sap/cds-compiler 3.1.2 → 3.4.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 +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +53 -0
- package/lib/checks/defaultValues.js +4 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
- package/share/messages/wildcard-excluding-one.md +37 -0
package/lib/render/toCdl.js
CHANGED
|
@@ -20,7 +20,6 @@ const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
|
|
|
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
|
-
* - `unappliedExtensions`: Annotations / Extensions from the `csn.extensions` array.
|
|
24
23
|
*
|
|
25
24
|
* @param {CSN.Model} csn
|
|
26
25
|
* @param {CSN.Options} [options]
|
|
@@ -45,17 +44,14 @@ function csnToCdl(csn, options) {
|
|
|
45
44
|
|
|
46
45
|
if (csn.vocabularies)
|
|
47
46
|
cdlResult.model += renderVocabularies(csn.vocabularies);
|
|
47
|
+
if (csn.extensions)
|
|
48
|
+
cdlResult.model += renderExtensions(csn.extensions, createEnv());
|
|
48
49
|
|
|
49
50
|
if (csn.namespace) {
|
|
50
51
|
cdlResult.namespace = `namespace ${renderArtifactName(csn.namespace)};\n`;
|
|
51
52
|
cdlResult.namespace += 'using from \'./model.cds\';';
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
// If there are extensions, such as 'extend' and 'annotate' statements, render them separately.
|
|
55
|
-
// Used for e.g. parseCdl-style CSN or Universal CSN.
|
|
56
|
-
if (csn.extensions)
|
|
57
|
-
cdlResult.unappliedExtensions = renderExtensions(csn.extensions, createEnv());
|
|
58
|
-
|
|
59
55
|
timetrace.stop();
|
|
60
56
|
return cdlResult;
|
|
61
57
|
|
|
@@ -109,7 +105,9 @@ function csnToCdl(csn, options) {
|
|
|
109
105
|
* @return {string}
|
|
110
106
|
*/
|
|
111
107
|
function renderExtensions(extensions, env) {
|
|
112
|
-
|
|
108
|
+
if (!env.path)
|
|
109
|
+
env = envNewPath(env, [ 'extensions' ]);
|
|
110
|
+
return extensions.map((ext, index) => renderExtension(ext, envAddPath(env, [ index ]))).join('\n');
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
/**
|
|
@@ -353,13 +351,14 @@ function csnToCdl(csn, options) {
|
|
|
353
351
|
if (art.query || art.projection)
|
|
354
352
|
return renderView(artifactName, art, env);
|
|
355
353
|
return renderEntity(artifactName, art, env);
|
|
354
|
+
case 'aspect':
|
|
355
|
+
return renderAspect(artifactName, art, env);
|
|
356
356
|
|
|
357
357
|
case 'context':
|
|
358
358
|
case 'service':
|
|
359
359
|
return renderContextOrService(artifactName, art, env);
|
|
360
360
|
|
|
361
361
|
case 'type':
|
|
362
|
-
case 'aspect':
|
|
363
362
|
case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
|
|
364
363
|
return renderTypeOrAnnotation(artifactName, art, env);
|
|
365
364
|
|
|
@@ -382,7 +381,6 @@ function csnToCdl(csn, options) {
|
|
|
382
381
|
*/
|
|
383
382
|
function renderEvent(artifactName, art, env) {
|
|
384
383
|
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
385
|
-
const childEnv = increaseIndent(env);
|
|
386
384
|
const normalizedArtifactName = renderArtifactName(artifactName);
|
|
387
385
|
result += `${env.indent}event ${normalizedArtifactName}`;
|
|
388
386
|
if (art.includes)
|
|
@@ -398,11 +396,7 @@ function csnToCdl(csn, options) {
|
|
|
398
396
|
result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
|
|
399
397
|
}
|
|
400
398
|
else if (art.elements) {
|
|
401
|
-
result +=
|
|
402
|
-
for (const name in art.elements)
|
|
403
|
-
result += renderElement(name, art.elements[name], childEnv);
|
|
404
|
-
|
|
405
|
-
result += `${env.indent}}`;
|
|
399
|
+
result += ` ${renderElements(art, env)};\n`;
|
|
406
400
|
}
|
|
407
401
|
return result;
|
|
408
402
|
}
|
|
@@ -434,21 +428,57 @@ function csnToCdl(csn, options) {
|
|
|
434
428
|
|
|
435
429
|
if (art.params)
|
|
436
430
|
result += renderParameters(art, env);
|
|
431
|
+
if (art.includes)
|
|
432
|
+
result += renderIncludes(art.includes);
|
|
433
|
+
result += ` ${renderElements(art, env)}`;
|
|
434
|
+
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Render an aspect. Return the resulting source string.
|
|
440
|
+
* Behaves very similar to renderEntity, _except_ that aspects are
|
|
441
|
+
* allowed to _not_ have elements, e.g. `aspect A;`.
|
|
442
|
+
*
|
|
443
|
+
* @param {string} artifactName
|
|
444
|
+
* @param {CSN.Artifact} art
|
|
445
|
+
* @param {CdlRenderEnvironment} env
|
|
446
|
+
* @return {string}
|
|
447
|
+
*/
|
|
448
|
+
function renderAspect(artifactName, art, env) {
|
|
449
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
450
|
+
result += `${env.indent}aspect ${renderArtifactName(artifactName)}`;
|
|
438
451
|
if (art.includes)
|
|
439
452
|
result += renderIncludes(art.includes);
|
|
440
|
-
result += ' {\n';
|
|
441
|
-
const childEnv = increaseIndent(env);
|
|
442
|
-
for (const name in art.elements) {
|
|
443
|
-
const element = art.elements[name];
|
|
444
|
-
result += renderElement(name, element, childEnv);
|
|
445
|
-
}
|
|
446
453
|
|
|
447
|
-
|
|
454
|
+
if (art.elements)
|
|
455
|
+
result += ` ${renderElements(art, env)}`;
|
|
456
|
+
else if (art.actions)
|
|
457
|
+
// if there are no elements, but actions, CDL syntax requires braces.
|
|
458
|
+
result += ' { }';
|
|
459
|
+
|
|
448
460
|
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
449
461
|
return result;
|
|
450
462
|
}
|
|
451
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Render a list of elements enclosed in braces. If the list is empty, returns `{ }`.
|
|
466
|
+
*
|
|
467
|
+
* @param {object} artifact Artifact with `elements` property.
|
|
468
|
+
* @param {CdlRenderEnvironment} env
|
|
469
|
+
* @return {string}
|
|
470
|
+
*/
|
|
471
|
+
function renderElements(artifact, env) {
|
|
472
|
+
let elements = '';
|
|
473
|
+
const childEnv = increaseIndent(env);
|
|
474
|
+
for (const name in artifact.elements)
|
|
475
|
+
elements += renderElement(name, artifact.elements[name], childEnv, null);
|
|
476
|
+
|
|
477
|
+
if (elements === '')
|
|
478
|
+
return '{ }';
|
|
479
|
+
return `{\n${elements}${env.indent}}`;
|
|
480
|
+
}
|
|
481
|
+
|
|
452
482
|
/**
|
|
453
483
|
* Render an element (of an entity, type or annotation, not a projection or view).
|
|
454
484
|
* Return the resulting source string.
|
|
@@ -471,27 +501,6 @@ function csnToCdl(csn, options) {
|
|
|
471
501
|
return `${result};\n`;
|
|
472
502
|
}
|
|
473
503
|
|
|
474
|
-
/**
|
|
475
|
-
* Render a query's actions and functions (if any) separately as extend-statements, so that actions
|
|
476
|
-
* work not only for projections but also for views, which have no syntax (yet) to directly specify
|
|
477
|
-
* actions and functions inline.
|
|
478
|
-
* Return the resulting 'extend' statement or '' if no actions or functions
|
|
479
|
-
* FIXME: Simplify once we have such a syntax
|
|
480
|
-
*
|
|
481
|
-
* @param {string} artifactName
|
|
482
|
-
* @param {CSN.Artifact} art
|
|
483
|
-
* @param {CdlRenderEnvironment} env
|
|
484
|
-
* @return {string}
|
|
485
|
-
*/
|
|
486
|
-
function renderQueryActionsAndFunctions(artifactName, art, env) {
|
|
487
|
-
let result = renderActionsAndFunctions(art, env);
|
|
488
|
-
// Even if we have seen actions/functions, they might all have been ignored
|
|
489
|
-
if (result !== '')
|
|
490
|
-
result = `${env.indent}extend entity ${artifactName} with${result};`;
|
|
491
|
-
|
|
492
|
-
return result;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
504
|
/**
|
|
496
505
|
* Render annotations that were extended to a query element of a view or projection (they only
|
|
497
506
|
* appear in the view's 'elements', not in their 'columns' for client CSN, because the element
|
|
@@ -507,8 +516,8 @@ function csnToCdl(csn, options) {
|
|
|
507
516
|
* @param {CdlRenderEnvironment} env
|
|
508
517
|
* @return {string}
|
|
509
518
|
*/
|
|
510
|
-
function
|
|
511
|
-
const annotate =
|
|
519
|
+
function renderQueryElementAndEnumAnnotations(artifactName, art, env) {
|
|
520
|
+
const annotate = collectAnnotationsOfElementsAndEnum(art, { artifactName, path: env.path });
|
|
512
521
|
if (annotate)
|
|
513
522
|
return renderExtensions([ annotate ], env);
|
|
514
523
|
return '';
|
|
@@ -518,17 +527,20 @@ function csnToCdl(csn, options) {
|
|
|
518
527
|
* Create an "annotate" statement as a CSN extension for all annotations of (sub-)elements.
|
|
519
528
|
* If no annotation was found, we return `null`.
|
|
520
529
|
*
|
|
521
|
-
* @param {CSN.Artifact}
|
|
530
|
+
* @param {CSN.Artifact} artifact
|
|
522
531
|
* @param {CdlRenderEnvironment} env
|
|
523
532
|
* @return {CSN.Extension|null}
|
|
524
533
|
*/
|
|
525
|
-
function
|
|
526
|
-
// Array
|
|
527
|
-
if (
|
|
534
|
+
function collectAnnotationsOfElementsAndEnum(artifact, env) {
|
|
535
|
+
// Array, which may be annotated as well.
|
|
536
|
+
if (artifact.items) {
|
|
528
537
|
env = envAddPath(env, [ 'items' ]);
|
|
529
|
-
|
|
538
|
+
artifact = artifact.items;
|
|
530
539
|
}
|
|
531
540
|
|
|
541
|
+
if (!artifact.elements && !artifact.enum)
|
|
542
|
+
return null;
|
|
543
|
+
|
|
532
544
|
const annotate = { annotate: env.path[1] };
|
|
533
545
|
|
|
534
546
|
// Based on the current path, create a correctly nested structure
|
|
@@ -551,22 +563,26 @@ function csnToCdl(csn, options) {
|
|
|
551
563
|
// ignore others, e.g. 'items'
|
|
552
564
|
}
|
|
553
565
|
}
|
|
554
|
-
return collectAnnos(obj,
|
|
566
|
+
return collectAnnos(obj, artifact) ? annotate : null;
|
|
555
567
|
|
|
556
568
|
/**
|
|
557
569
|
* Recursive function to collect annotations. `annotateObj` will get an `elements`
|
|
558
|
-
* object with annotations only if there are annotations on `art`'s (sub-)elements
|
|
570
|
+
* object with annotations only if there are annotations on `art`'s (sub-)elements or
|
|
571
|
+
* enums. Returned object will use "elements" even for enums, since that is
|
|
572
|
+
* expected in extensions.
|
|
559
573
|
*
|
|
560
574
|
* @return {boolean} True, if there were annotations, false otherwise.
|
|
561
575
|
*/
|
|
562
576
|
function collectAnnos(annotateObj, art) {
|
|
563
|
-
if (!art.elements)
|
|
577
|
+
if (!art.elements && !art.enum)
|
|
564
578
|
return false;
|
|
565
579
|
|
|
580
|
+
const dictKey = art.elements ? 'elements' : 'enum';
|
|
581
|
+
// Use "elements" for both enums and elements. This is allowed in extensions.
|
|
566
582
|
const collected = { elements: Object.create(null) };
|
|
567
583
|
let hasAnnotation = false;
|
|
568
584
|
|
|
569
|
-
forEach(art
|
|
585
|
+
forEach(art[dictKey], (elemName, element) => {
|
|
570
586
|
if (!collected.elements[elemName])
|
|
571
587
|
collected.elements[elemName] = { };
|
|
572
588
|
|
|
@@ -667,7 +683,7 @@ function csnToCdl(csn, options) {
|
|
|
667
683
|
|
|
668
684
|
// Even the first step might have parameters and/or a filter
|
|
669
685
|
if (path.ref[0].args)
|
|
670
|
-
result += `(${
|
|
686
|
+
result += `(${renderArguments(path.ref[0], ':', env)})`;
|
|
671
687
|
|
|
672
688
|
if (path.ref[0].where) {
|
|
673
689
|
const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
|
|
@@ -848,14 +864,17 @@ function csnToCdl(csn, options) {
|
|
|
848
864
|
function renderView(artifactName, art, env) {
|
|
849
865
|
const syntax = (art.projection) ? 'projection' : 'entity';
|
|
850
866
|
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
851
|
-
result += `${env.indent}
|
|
867
|
+
result += `${env.indent}entity ${renderArtifactName(artifactName)}`;
|
|
852
868
|
if (art.params)
|
|
853
869
|
result += renderParameters(art, env);
|
|
854
870
|
result += ' as ';
|
|
855
871
|
result += renderQuery(getNormalizedQuery(art).query, true, syntax, env, [ 'definitions', artifactName, 'query' ], art.elements);
|
|
872
|
+
if (art.actions) // Views/Projections also allow actions. Just the VIEW keyword variant did not.
|
|
873
|
+
result += renderActionsAndFunctions(art, env);
|
|
856
874
|
result += ';\n';
|
|
857
|
-
result +=
|
|
858
|
-
|
|
875
|
+
result += renderQueryElementAndEnumAnnotations(artifactName, art, env);
|
|
876
|
+
if (art.includes)
|
|
877
|
+
result += renderExtension({ extend: artifactName, includes: art.includes }, env);
|
|
859
878
|
return result;
|
|
860
879
|
}
|
|
861
880
|
|
|
@@ -1108,8 +1127,7 @@ function csnToCdl(csn, options) {
|
|
|
1108
1127
|
result += ` ${renderTypeReferenceAndProps(art, env)}`;
|
|
1109
1128
|
else
|
|
1110
1129
|
result += ` : ${renderTypeReferenceAndProps(art, env)}`;
|
|
1111
|
-
|
|
1112
|
-
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
1130
|
+
result += ';\n';
|
|
1113
1131
|
return result;
|
|
1114
1132
|
}
|
|
1115
1133
|
|
|
@@ -1145,12 +1163,7 @@ function csnToCdl(csn, options) {
|
|
|
1145
1163
|
}
|
|
1146
1164
|
|
|
1147
1165
|
if (!artifact.type && artifact.elements) {
|
|
1148
|
-
result +=
|
|
1149
|
-
const childEnv = envAddPath(increaseIndent(env), 'items');
|
|
1150
|
-
for (const name in artifact.elements)
|
|
1151
|
-
result += renderElement(name, artifact.elements[name], childEnv, null);
|
|
1152
|
-
|
|
1153
|
-
result += `${env.indent}}`;
|
|
1166
|
+
result += renderElements(artifact, env);
|
|
1154
1167
|
if (!isTypeDef)
|
|
1155
1168
|
result += renderNullability(artifact);
|
|
1156
1169
|
// structured default not possible at the moment
|
|
@@ -1174,12 +1187,7 @@ function csnToCdl(csn, options) {
|
|
|
1174
1187
|
}
|
|
1175
1188
|
else if (elements) {
|
|
1176
1189
|
// anonymous aspect, either parseCdl or client CSN.
|
|
1177
|
-
|
|
1178
|
-
result += '{\n';
|
|
1179
|
-
for (const name in elements)
|
|
1180
|
-
result += renderElement(name, elements[name], childEnv);
|
|
1181
|
-
|
|
1182
|
-
result += `${env.indent}}`;
|
|
1190
|
+
result += renderElements({ elements }, env);
|
|
1183
1191
|
}
|
|
1184
1192
|
else {
|
|
1185
1193
|
throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
|
|
@@ -1205,10 +1213,12 @@ function csnToCdl(csn, options) {
|
|
|
1205
1213
|
|
|
1206
1214
|
// If we have a type and elements, we may have sub-structure annotates that would
|
|
1207
1215
|
// get lost if we only render the type name.
|
|
1216
|
+
// We only extract annotations of enums, if "typeRefOnly" is true. Otherwise, since
|
|
1217
|
+
// the full enum is rendered below, we would have unnecessary annotations.
|
|
1208
1218
|
// TODO: Can we annotate elements of targetAspect?
|
|
1209
1219
|
// If so, move this block before the composition rendering.
|
|
1210
|
-
if (!noAnnoCollect && (artifact.
|
|
1211
|
-
const annotate =
|
|
1220
|
+
if (!noAnnoCollect && (!artifact.enum || typeRefOnly)) {
|
|
1221
|
+
const annotate = collectAnnotationsOfElementsAndEnum(artifact, env);
|
|
1212
1222
|
if (annotate)
|
|
1213
1223
|
subelementAnnotates.push(annotate);
|
|
1214
1224
|
}
|
|
@@ -1363,6 +1373,8 @@ function csnToCdl(csn, options) {
|
|
|
1363
1373
|
*
|
|
1364
1374
|
* @param {string|object} s
|
|
1365
1375
|
* @param {number} idx
|
|
1376
|
+
* @param {boolean} inline
|
|
1377
|
+
* @param {object} env
|
|
1366
1378
|
* @returns {string}
|
|
1367
1379
|
*/
|
|
1368
1380
|
function renderPathStep(s, idx, inline, env) {
|
|
@@ -1370,12 +1382,9 @@ function csnToCdl(csn, options) {
|
|
|
1370
1382
|
if (typeof s === 'string') {
|
|
1371
1383
|
// In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
|
|
1372
1384
|
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1373
|
-
|
|
1374
|
-
if (idx === 0 &&
|
|
1375
|
-
s.startsWith('$'))
|
|
1385
|
+
if (idx === 0 && s.startsWith('$'))
|
|
1376
1386
|
return s;
|
|
1377
|
-
|
|
1378
|
-
return quoteIdIfRequired(s);
|
|
1387
|
+
return quoteIdIfRequired(s, env.additionalKeywords);
|
|
1379
1388
|
}
|
|
1380
1389
|
// ID with filters or parameters
|
|
1381
1390
|
else if (typeof s === 'object') {
|
|
@@ -1385,13 +1394,13 @@ function csnToCdl(csn, options) {
|
|
|
1385
1394
|
|
|
1386
1395
|
// Not really a path step but an object-like function call
|
|
1387
1396
|
if (s.func)
|
|
1388
|
-
return `${s.func}(${
|
|
1397
|
+
return `${s.func}(${renderArguments(s, '=>', env)})`;
|
|
1389
1398
|
|
|
1390
1399
|
// Path step, possibly with view parameters and/or filters
|
|
1391
|
-
let result = `${quoteIdIfRequired(s.id)}`;
|
|
1400
|
+
let result = `${quoteIdIfRequired(s.id, env.additionalKeywords)}`;
|
|
1392
1401
|
if (s.args) {
|
|
1393
1402
|
// View parameters
|
|
1394
|
-
result += `(${
|
|
1403
|
+
result += `(${renderArguments(s, ':', env)})`;
|
|
1395
1404
|
}
|
|
1396
1405
|
if (s.where) {
|
|
1397
1406
|
// Filter, possibly with cardinality
|
|
@@ -1415,24 +1424,48 @@ function csnToCdl(csn, options) {
|
|
|
1415
1424
|
* @param {CdlRenderEnvironment} env
|
|
1416
1425
|
* @returns {string}
|
|
1417
1426
|
*/
|
|
1418
|
-
function
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1427
|
+
function renderArguments(node, sep, env) {
|
|
1428
|
+
if (!node.args)
|
|
1429
|
+
return '';
|
|
1430
|
+
else if (Array.isArray(node.args))
|
|
1431
|
+
return renderPositionalArguments(node, env);
|
|
1432
|
+
else if (typeof node.args === 'object')
|
|
1433
|
+
return renderNamedArguments(node, sep, env);
|
|
1434
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(node.args)}; expected array/object`);
|
|
1435
|
+
}
|
|
1426
1436
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1437
|
+
/**
|
|
1438
|
+
* Render named function arguments or view parameters,
|
|
1439
|
+
* using 'sep' as separator.
|
|
1440
|
+
*
|
|
1441
|
+
* @param {object} node with `args` to render
|
|
1442
|
+
* @param {string} separator
|
|
1443
|
+
* @param {CdlRenderEnvironment} env
|
|
1444
|
+
* @returns {string}
|
|
1445
|
+
*/
|
|
1446
|
+
function renderNamedArguments(node, separator, env) {
|
|
1447
|
+
return Object.keys(node.args).map(function renderNamedArgument(key) {
|
|
1448
|
+
return `${quoteIdIfRequired(key, env.additionalKeywords)} ${separator} ${renderArgument(node.args[key], env)}`;
|
|
1449
|
+
}).join(', ');
|
|
1450
|
+
}
|
|
1429
1451
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1452
|
+
/**
|
|
1453
|
+
* Render a comma separated list of positional function arguments.
|
|
1454
|
+
*
|
|
1455
|
+
* @param {object} node with `args` to render
|
|
1456
|
+
* @param {CdlRenderEnvironment} env
|
|
1457
|
+
* @returns {string}
|
|
1458
|
+
*/
|
|
1459
|
+
function renderPositionalArguments(node, env) {
|
|
1460
|
+
if (!node.args)
|
|
1461
|
+
return '';
|
|
1462
|
+
const func = node.func?.toUpperCase();
|
|
1463
|
+
if (func) {
|
|
1464
|
+
return node.args.map(function renderFunctionArg(arg, i) {
|
|
1465
|
+
return renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i));
|
|
1466
|
+
}).join(', ');
|
|
1433
1467
|
}
|
|
1434
|
-
|
|
1435
|
-
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1468
|
+
return node.args.map(arg => renderArgument(arg, env)).join(', ');
|
|
1436
1469
|
}
|
|
1437
1470
|
|
|
1438
1471
|
/**
|
|
@@ -1441,13 +1474,14 @@ function csnToCdl(csn, options) {
|
|
|
1441
1474
|
*
|
|
1442
1475
|
* @param {any} arg
|
|
1443
1476
|
* @param {CdlRenderEnvironment} env
|
|
1444
|
-
* @param {string[]}
|
|
1477
|
+
* @param {string[]} additionalKeywords
|
|
1445
1478
|
* @return {string}
|
|
1446
1479
|
*/
|
|
1447
|
-
function renderArgument(arg, env,
|
|
1480
|
+
function renderArgument(arg, env, additionalKeywords = []) {
|
|
1448
1481
|
// If the argument is a xpr with e.g. `=`, it may require parentheses.
|
|
1449
1482
|
// For nested xpr, `renderExpr()` will already add parentheses.
|
|
1450
|
-
|
|
1483
|
+
env = { ...env, additionalKeywords };
|
|
1484
|
+
return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalKeywords), true);
|
|
1451
1485
|
}
|
|
1452
1486
|
|
|
1453
1487
|
/**
|
|
@@ -1652,7 +1686,7 @@ function csnToCdl(csn, options) {
|
|
|
1652
1686
|
if (keywords.cdl_functions.includes(obj.func.toUpperCase()))
|
|
1653
1687
|
return obj.func;
|
|
1654
1688
|
const name = identifierRegex.test(obj.func) ? obj.func : quote(obj.func);
|
|
1655
|
-
return `${name}(${
|
|
1689
|
+
return `${name}(${renderArguments( obj, '=>', env )})`;
|
|
1656
1690
|
}
|
|
1657
1691
|
|
|
1658
1692
|
/**
|
|
@@ -1775,27 +1809,33 @@ function increaseIndent(env) {
|
|
|
1775
1809
|
* Quote the path steps with `![]` if necessary. For simple ids such as
|
|
1776
1810
|
* `elem` use `quoteIdIfRequired` instead.
|
|
1777
1811
|
*
|
|
1812
|
+
* In contrast to quoteIdIfRequired, does not handle additional keywords,
|
|
1813
|
+
* because it was not required, yet.
|
|
1814
|
+
*
|
|
1778
1815
|
* @param {string} path
|
|
1779
1816
|
* @returns {string}
|
|
1780
1817
|
*
|
|
1781
1818
|
* @todo For paths such as `E.key`, `key` does not have to be in quotes.
|
|
1782
1819
|
*/
|
|
1783
1820
|
function quotePathIfRequired(path) {
|
|
1784
|
-
return path.split('.').map(quoteIdIfRequired).join('.');
|
|
1821
|
+
return path.split('.').map(step => quoteIdIfRequired(step)).join('.');
|
|
1785
1822
|
}
|
|
1786
1823
|
|
|
1787
1824
|
/**
|
|
1788
1825
|
* Quote the id with `![]` if necessary. For paths such as `E.key` use
|
|
1789
1826
|
* `quotePathIfRequired` instead.
|
|
1790
1827
|
*
|
|
1828
|
+
* Set additionalKeywords to an array of UPPERCASE keywords
|
|
1829
|
+
* that also need quoting, e.g. in special functions.
|
|
1830
|
+
*
|
|
1791
1831
|
* @param {string} id
|
|
1832
|
+
* @param {string[]} [additionalKeywords]
|
|
1792
1833
|
* @return {string}
|
|
1793
1834
|
*/
|
|
1794
|
-
function quoteIdIfRequired(id) {
|
|
1835
|
+
function quoteIdIfRequired(id, additionalKeywords) {
|
|
1795
1836
|
// Quote if required for CDL
|
|
1796
|
-
if (requiresQuotingForCdl(id))
|
|
1837
|
+
if (requiresQuotingForCdl(id, additionalKeywords || []))
|
|
1797
1838
|
return quote(id);
|
|
1798
|
-
|
|
1799
1839
|
return id;
|
|
1800
1840
|
}
|
|
1801
1841
|
|
|
@@ -1831,13 +1871,18 @@ function quote(id) {
|
|
|
1831
1871
|
* does not match the first part of the `Identifier` rule of `language.g4`
|
|
1832
1872
|
* or if 'id' is a reserved keyword.
|
|
1833
1873
|
*
|
|
1874
|
+
* Set additionalKeywords to an array of UPPERCASE keywords
|
|
1875
|
+
* that also need quoting, e.g. in special functions.
|
|
1876
|
+
*
|
|
1834
1877
|
* @param {string} id
|
|
1878
|
+
* @param {string[]} [additionalKeywords]
|
|
1835
1879
|
* @return {boolean}
|
|
1836
1880
|
*/
|
|
1837
|
-
function requiresQuotingForCdl(id) {
|
|
1881
|
+
function requiresQuotingForCdl(id, additionalKeywords) {
|
|
1838
1882
|
return !identifierRegex.test(id) ||
|
|
1839
1883
|
keywords.cdl.includes(id.toUpperCase()) ||
|
|
1840
|
-
keywords.cdl_functions.includes(id.toUpperCase())
|
|
1884
|
+
keywords.cdl_functions.includes(id.toUpperCase()) ||
|
|
1885
|
+
additionalKeywords.includes(id.toUpperCase());
|
|
1841
1886
|
}
|
|
1842
1887
|
|
|
1843
1888
|
const functionExpressionOperatorsRequireParentheses = [
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -7,8 +7,9 @@ const {
|
|
|
7
7
|
} = require('../model/csnUtils');
|
|
8
8
|
const keywords = require('../base/keywords');
|
|
9
9
|
const {
|
|
10
|
-
renderFunc, getExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts,
|
|
10
|
+
renderFunc, getExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts,
|
|
11
11
|
hasHanaComment, getHanaComment, funcWithoutParen, getSqlSnippets,
|
|
12
|
+
cdsToSqlTypes, cdsToHdbcdsTypes,
|
|
12
13
|
} = require('./utils/common');
|
|
13
14
|
const {
|
|
14
15
|
renderReferentialConstraint,
|
|
@@ -195,7 +196,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
195
196
|
switch (art.kind) {
|
|
196
197
|
case 'entity':
|
|
197
198
|
// FIXME: For HANA CDS, we need to replace $self at the beginning of paths in association ON-condition
|
|
198
|
-
// by the full name of the artifact we are rendering (should actually be done by
|
|
199
|
+
// by the full name of the artifact we are rendering (should actually be done by forRelationalDB, but that is
|
|
199
200
|
// somewhat difficult because this kind of absolute path is quite unusual). In order not to have to pass
|
|
200
201
|
// the current artifact name down through the stack to renderExpr, we just put it into the env.
|
|
201
202
|
env.currentArtifactName = artifactName;
|
|
@@ -763,7 +764,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
763
764
|
result += key + renderExpr(col, env, true);
|
|
764
765
|
let alias = col.as || col.func;
|
|
765
766
|
// HANA requires an alias for 'key' columns just for syntactical reasons
|
|
766
|
-
// FIXME: This will not complain for non-refs (but that should be checked in
|
|
767
|
+
// FIXME: This will not complain for non-refs (but that should be checked in forRelationalDB)
|
|
767
768
|
// Explicit or implicit alias?
|
|
768
769
|
// Shouldn't we simply generate an alias all the time?
|
|
769
770
|
if ((key || col.cast) && !alias)
|
|
@@ -1147,7 +1148,8 @@ function toHdbcdsSource(csn, options) {
|
|
|
1147
1148
|
if (elm.type === 'cds.Decimal' && elm.scale === undefined && elm.precision === undefined)
|
|
1148
1149
|
return 'DecimalFloat';
|
|
1149
1150
|
|
|
1150
|
-
|
|
1151
|
+
const type = cdsToHdbcdsTypes[elm.type] || elm.type;
|
|
1152
|
+
return type.replace(/^cds\./, '') + renderTypeParameters(elm);
|
|
1151
1153
|
}
|
|
1152
1154
|
|
|
1153
1155
|
/**
|
|
@@ -1160,7 +1162,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1160
1162
|
function renderPathStep(s, idx, ref, env, inline) {
|
|
1161
1163
|
// Simple id or absolute name
|
|
1162
1164
|
if (typeof s === 'string') {
|
|
1163
|
-
// HANA-specific extra magic (should actually be in
|
|
1165
|
+
// HANA-specific extra magic (should actually be in forRelationalDB)
|
|
1164
1166
|
// In HANA, we replace leading $self by the absolute name of the current artifact
|
|
1165
1167
|
// (see FIXME at renderArtifact)
|
|
1166
1168
|
if (idx === 0 && s === $SELF) {
|
|
@@ -1256,7 +1258,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1256
1258
|
const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
|
|
1257
1259
|
// we can't quote functions with parens, issue warning if it is a reserved keyword
|
|
1258
1260
|
if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
|
|
1259
|
-
warning(null, x.$location,
|
|
1261
|
+
warning(null, x.$location, { id: uppercaseAndUnderscore(funcName) }, 'The identifier $(ID) is a SAP HANA keyword');
|
|
1260
1262
|
return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
|
|
1261
1263
|
}
|
|
1262
1264
|
|
package/lib/render/toRename.js
CHANGED
|
@@ -6,7 +6,7 @@ const { checkCSNVersion } = require('../json/csnVersion');
|
|
|
6
6
|
const { getUtils, forEachDefinition } = require('../model/csnUtils');
|
|
7
7
|
const { optionProcessor } = require('../optionProcessor');
|
|
8
8
|
const { isBetaEnabled } = require('../base/model');
|
|
9
|
-
const {
|
|
9
|
+
const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -44,9 +44,9 @@ function toRename(inputCsn, options) {
|
|
|
44
44
|
if (!isBetaEnabled(options, 'toRename'))
|
|
45
45
|
error(null, null, 'Generation of SQL rename statements is not supported yet (only in beta mode)');
|
|
46
46
|
|
|
47
|
-
// FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to
|
|
48
|
-
const csn =
|
|
49
|
-
//
|
|
47
|
+
// FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forRelationalDB)
|
|
48
|
+
const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
|
|
49
|
+
// forRelationalDB looses empty contexts and services, add them again so that toRename can calculate the namespaces
|
|
50
50
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
51
51
|
if ((artifact.kind === 'context' || artifact.kind === 'service') && csn.definitions[artifactName] === undefined)
|
|
52
52
|
csn.definitions[artifactName] = artifact;
|
package/lib/render/toSql.js
CHANGED
|
@@ -157,7 +157,7 @@ function toSqlDdl(csn, options) {
|
|
|
157
157
|
Render column removals as HANA SQL.
|
|
158
158
|
*/
|
|
159
159
|
dropColumns(tableName, sqlIds) {
|
|
160
|
-
return [ `ALTER TABLE ${tableName} DROP (${sqlIds.join(', ')});` ];
|
|
160
|
+
return [ `ALTER TABLE ${tableName} DROP ${options.sqlDialect === 'hana' ? '(' : ''}${sqlIds.join(', ')}${options.sqlDialect === 'hana' ? ')' : ''};` ];
|
|
161
161
|
},
|
|
162
162
|
/*
|
|
163
163
|
Render association removals as HANA SQL.
|
|
@@ -221,7 +221,7 @@ function toSqlDdl(csn, options) {
|
|
|
221
221
|
};
|
|
222
222
|
|
|
223
223
|
// FIXME: Currently requires 'options.forHana', because it can only render HANA-ish SQL dialect
|
|
224
|
-
if (!options.forHana)
|
|
224
|
+
if (!options.forHana && !isBetaEnabled(options, 'sqlExtensions'))
|
|
225
225
|
throw new Error('toSql can currently only be used with HANA preprocessing');
|
|
226
226
|
|
|
227
227
|
checkCSNVersion(csn, options);
|
|
@@ -325,7 +325,7 @@ function toSqlDdl(csn, options) {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
// add `ALTER TABLE ADD CONSTRAINT` statements per default for `to.sql` w/ dialect `hana`
|
|
328
|
-
if (!options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana') {
|
|
328
|
+
if (!options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres')) {
|
|
329
329
|
const alterStmts = manageConstraints(csn, options);
|
|
330
330
|
|
|
331
331
|
for ( const constraintName of Object.keys(alterStmts))
|
|
@@ -352,7 +352,7 @@ function toSqlDdl(csn, options) {
|
|
|
352
352
|
* @param {object} env Render environment
|
|
353
353
|
*/
|
|
354
354
|
function renderArtifactInto(artifactName, art, resultObj, env) {
|
|
355
|
-
// Ignore whole artifacts if
|
|
355
|
+
// Ignore whole artifacts if forRelationalDB says so
|
|
356
356
|
if (art.abstract || hasValidSkipOrExists(art))
|
|
357
357
|
return;
|
|
358
358
|
|
|
@@ -605,8 +605,8 @@ function toSqlDdl(csn, options) {
|
|
|
605
605
|
if (primaryKeys !== '')
|
|
606
606
|
result += `,\n${childEnv.indent}${primaryKeys}`;
|
|
607
607
|
|
|
608
|
-
// for `to.sql` w/ dialect `hana` the constraints will be part of the
|
|
609
|
-
const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
|
|
608
|
+
// for `to.sql` w/ dialect `hana` the constraints will be part of the alter statement
|
|
609
|
+
const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres');
|
|
610
610
|
if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
|
|
611
611
|
const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
|
|
612
612
|
const referentialConstraints = {};
|
|
@@ -1506,8 +1506,10 @@ function toSqlDdl(csn, options) {
|
|
|
1506
1506
|
case 'sqlite':
|
|
1507
1507
|
case 'hana':
|
|
1508
1508
|
return 'CURRENT_TIMESTAMP';
|
|
1509
|
-
case '
|
|
1509
|
+
case 'h2':
|
|
1510
1510
|
return 'current_timestamp';
|
|
1511
|
+
case 'postgres':
|
|
1512
|
+
return '(current_timestamp at time zone \'UTC\')';
|
|
1511
1513
|
default:
|
|
1512
1514
|
return quoteSqlId(x.ref[0]);
|
|
1513
1515
|
}
|
|
@@ -1534,12 +1536,16 @@ function toSqlDdl(csn, options) {
|
|
|
1534
1536
|
if (x.ref[1] === 'id') {
|
|
1535
1537
|
if (options.sqlDialect === 'hana')
|
|
1536
1538
|
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1539
|
+
else if (options.sqlDialect === 'postgres')
|
|
1540
|
+
return 'current_setting(\'CAP.APPLICATIONUSER\')';
|
|
1537
1541
|
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1538
1542
|
return '\'$user.id\'';
|
|
1539
1543
|
}
|
|
1540
1544
|
else if (x.ref[1] === 'locale') {
|
|
1541
1545
|
if (options.sqlDialect === 'hana')
|
|
1542
1546
|
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1547
|
+
else if (options.sqlDialect === 'postgres')
|
|
1548
|
+
return 'current_setting(\'CAP.LOCALE\')';
|
|
1543
1549
|
return '\'en\''; // default language
|
|
1544
1550
|
}
|
|
1545
1551
|
// Basically: Second path step was invalid, do nothing - should not happen.
|
|
@@ -1567,6 +1573,8 @@ function toSqlDdl(csn, options) {
|
|
|
1567
1573
|
case 'hana':
|
|
1568
1574
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1569
1575
|
case 'postgres':
|
|
1576
|
+
return '(to_timestamp(current_setting(\'CAP.VALID_FROM\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
|
|
1577
|
+
case 'h2':
|
|
1570
1578
|
case 'plain':
|
|
1571
1579
|
return 'current_timestamp';
|
|
1572
1580
|
default:
|
|
@@ -1584,6 +1592,8 @@ function toSqlDdl(csn, options) {
|
|
|
1584
1592
|
case 'hana':
|
|
1585
1593
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1586
1594
|
case 'postgres':
|
|
1595
|
+
return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
|
|
1596
|
+
case 'h2':
|
|
1587
1597
|
case 'plain':
|
|
1588
1598
|
return 'current_timestamp';
|
|
1589
1599
|
default:
|