@sap/cds-compiler 3.1.2 → 3.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -3
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_BETA.md +18 -0
- package/lib/api/main.js +8 -13
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +2 -24
- package/lib/base/message-registry.js +43 -14
- package/lib/base/messages.js +20 -10
- package/lib/base/model.js +1 -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 +48 -0
- package/lib/checks/defaultValues.js +2 -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 +21 -0
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +26 -14
- package/lib/compiler/assert-consistency.js +13 -6
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +40 -33
- package/lib/compiler/define.js +50 -44
- package/lib/compiler/extend.js +303 -37
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +83 -62
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +61 -104
- package/lib/compiler/shared.js +16 -6
- package/lib/compiler/tweak-assocs.js +25 -12
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +3 -3
- package/lib/edm/csn2edm.js +10 -10
- 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 +4205 -4100
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +26 -19
- package/lib/json/to-csn.js +47 -5
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +29 -13
- package/lib/language/language.g4 +28 -8
- package/lib/main.d.ts +3 -6
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +74 -47
- package/lib/model/csnUtils.js +236 -218
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +31 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +5 -0
- package/lib/render/manageConstraints.js +2 -2
- package/lib/render/toCdl.js +31 -44
- package/lib/render/toHdbcds.js +7 -5
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +11 -5
- package/lib/render/utils/common.js +20 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/expansion.js +81 -37
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +1 -1
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
- package/lib/transform/localized.js +28 -19
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +1 -1
- package/lib/transform/transformUtilsNew.js +101 -39
- package/lib/transform/translateAssocsToJoins.js +5 -4
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- 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 +3 -3
- package/share/messages/wildcard-excluding-one.md +37 -0
|
@@ -8,7 +8,7 @@ const {
|
|
|
8
8
|
const { forEach } = require('../utils/objectUtils');
|
|
9
9
|
const { makeMessageFunction } = require('../base/messages');
|
|
10
10
|
const { optionProcessor } = require('../optionProcessor');
|
|
11
|
-
const {
|
|
11
|
+
const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
|
|
12
12
|
|
|
13
13
|
const {
|
|
14
14
|
renderReferentialConstraint, getIdentifierUtils,
|
|
@@ -38,7 +38,7 @@ function alterConstraintsWithCsn(csn, options) {
|
|
|
38
38
|
options.assertIntegrityType = 'DB';
|
|
39
39
|
|
|
40
40
|
const transformedOptions = _transformSqlOptions(csn, options);
|
|
41
|
-
const forSqlCsn =
|
|
41
|
+
const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, 'to.sql');
|
|
42
42
|
|
|
43
43
|
if (violations && src && src !== 'sql')
|
|
44
44
|
error(null, null, `Option “--violations“ can't be combined with source style “${src}“`);
|
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
|
/**
|
|
@@ -471,27 +469,6 @@ function csnToCdl(csn, options) {
|
|
|
471
469
|
return `${result};\n`;
|
|
472
470
|
}
|
|
473
471
|
|
|
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
472
|
/**
|
|
496
473
|
* Render annotations that were extended to a query element of a view or projection (they only
|
|
497
474
|
* appear in the view's 'elements', not in their 'columns' for client CSN, because the element
|
|
@@ -507,8 +484,8 @@ function csnToCdl(csn, options) {
|
|
|
507
484
|
* @param {CdlRenderEnvironment} env
|
|
508
485
|
* @return {string}
|
|
509
486
|
*/
|
|
510
|
-
function
|
|
511
|
-
const annotate =
|
|
487
|
+
function renderQueryElementAndEnumAnnotations(artifactName, art, env) {
|
|
488
|
+
const annotate = collectAnnotationsOfElementsAndEnum(art, { artifactName, path: env.path });
|
|
512
489
|
if (annotate)
|
|
513
490
|
return renderExtensions([ annotate ], env);
|
|
514
491
|
return '';
|
|
@@ -518,17 +495,20 @@ function csnToCdl(csn, options) {
|
|
|
518
495
|
* Create an "annotate" statement as a CSN extension for all annotations of (sub-)elements.
|
|
519
496
|
* If no annotation was found, we return `null`.
|
|
520
497
|
*
|
|
521
|
-
* @param {CSN.Artifact}
|
|
498
|
+
* @param {CSN.Artifact} artifact
|
|
522
499
|
* @param {CdlRenderEnvironment} env
|
|
523
500
|
* @return {CSN.Extension|null}
|
|
524
501
|
*/
|
|
525
|
-
function
|
|
526
|
-
// Array
|
|
527
|
-
if (
|
|
502
|
+
function collectAnnotationsOfElementsAndEnum(artifact, env) {
|
|
503
|
+
// Array, which may be annotated as well.
|
|
504
|
+
if (artifact.items) {
|
|
528
505
|
env = envAddPath(env, [ 'items' ]);
|
|
529
|
-
|
|
506
|
+
artifact = artifact.items;
|
|
530
507
|
}
|
|
531
508
|
|
|
509
|
+
if (!artifact.elements && !artifact.enum)
|
|
510
|
+
return null;
|
|
511
|
+
|
|
532
512
|
const annotate = { annotate: env.path[1] };
|
|
533
513
|
|
|
534
514
|
// Based on the current path, create a correctly nested structure
|
|
@@ -551,22 +531,26 @@ function csnToCdl(csn, options) {
|
|
|
551
531
|
// ignore others, e.g. 'items'
|
|
552
532
|
}
|
|
553
533
|
}
|
|
554
|
-
return collectAnnos(obj,
|
|
534
|
+
return collectAnnos(obj, artifact) ? annotate : null;
|
|
555
535
|
|
|
556
536
|
/**
|
|
557
537
|
* Recursive function to collect annotations. `annotateObj` will get an `elements`
|
|
558
|
-
* object with annotations only if there are annotations on `art`'s (sub-)elements
|
|
538
|
+
* object with annotations only if there are annotations on `art`'s (sub-)elements or
|
|
539
|
+
* enums. Returned object will use "elements" even for enums, since that is
|
|
540
|
+
* expected in extensions.
|
|
559
541
|
*
|
|
560
542
|
* @return {boolean} True, if there were annotations, false otherwise.
|
|
561
543
|
*/
|
|
562
544
|
function collectAnnos(annotateObj, art) {
|
|
563
|
-
if (!art.elements)
|
|
545
|
+
if (!art.elements && !art.enum)
|
|
564
546
|
return false;
|
|
565
547
|
|
|
548
|
+
const dictKey = art.elements ? 'elements' : 'enum';
|
|
549
|
+
// Use "elements" for both enums and elements. This is allowed in extensions.
|
|
566
550
|
const collected = { elements: Object.create(null) };
|
|
567
551
|
let hasAnnotation = false;
|
|
568
552
|
|
|
569
|
-
forEach(art
|
|
553
|
+
forEach(art[dictKey], (elemName, element) => {
|
|
570
554
|
if (!collected.elements[elemName])
|
|
571
555
|
collected.elements[elemName] = { };
|
|
572
556
|
|
|
@@ -848,14 +832,15 @@ function csnToCdl(csn, options) {
|
|
|
848
832
|
function renderView(artifactName, art, env) {
|
|
849
833
|
const syntax = (art.projection) ? 'projection' : 'entity';
|
|
850
834
|
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
851
|
-
result += `${env.indent}
|
|
835
|
+
result += `${env.indent}entity ${renderArtifactName(artifactName)}`;
|
|
852
836
|
if (art.params)
|
|
853
837
|
result += renderParameters(art, env);
|
|
854
838
|
result += ' as ';
|
|
855
839
|
result += renderQuery(getNormalizedQuery(art).query, true, syntax, env, [ 'definitions', artifactName, 'query' ], art.elements);
|
|
840
|
+
if (art.actions) // Views/Projections also allow actions. Just the VIEW keyword variant did not.
|
|
841
|
+
result += renderActionsAndFunctions(art, env);
|
|
856
842
|
result += ';\n';
|
|
857
|
-
result +=
|
|
858
|
-
result += renderQueryActionsAndFunctions(artifactName, art, env);
|
|
843
|
+
result += renderQueryElementAndEnumAnnotations(artifactName, art, env);
|
|
859
844
|
return result;
|
|
860
845
|
}
|
|
861
846
|
|
|
@@ -1205,10 +1190,12 @@ function csnToCdl(csn, options) {
|
|
|
1205
1190
|
|
|
1206
1191
|
// If we have a type and elements, we may have sub-structure annotates that would
|
|
1207
1192
|
// get lost if we only render the type name.
|
|
1193
|
+
// We only extract annotations of enums, if "typeRefOnly" is true. Otherwise, since
|
|
1194
|
+
// the full enum is rendered below, we would have unnecessary annotations.
|
|
1208
1195
|
// TODO: Can we annotate elements of targetAspect?
|
|
1209
1196
|
// If so, move this block before the composition rendering.
|
|
1210
|
-
if (!noAnnoCollect && (artifact.
|
|
1211
|
-
const annotate =
|
|
1197
|
+
if (!noAnnoCollect && (!artifact.enum || typeRefOnly)) {
|
|
1198
|
+
const annotate = collectAnnotationsOfElementsAndEnum(artifact, env);
|
|
1212
1199
|
if (annotate)
|
|
1213
1200
|
subelementAnnotates.push(annotate);
|
|
1214
1201
|
}
|
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) {
|
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
|
@@ -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 = {};
|
|
@@ -1507,7 +1507,7 @@ function toSqlDdl(csn, options) {
|
|
|
1507
1507
|
case 'hana':
|
|
1508
1508
|
return 'CURRENT_TIMESTAMP';
|
|
1509
1509
|
case 'postgres':
|
|
1510
|
-
return 'current_timestamp';
|
|
1510
|
+
return '(current_timestamp at time zone \'UTC\')';
|
|
1511
1511
|
default:
|
|
1512
1512
|
return quoteSqlId(x.ref[0]);
|
|
1513
1513
|
}
|
|
@@ -1534,12 +1534,16 @@ function toSqlDdl(csn, options) {
|
|
|
1534
1534
|
if (x.ref[1] === 'id') {
|
|
1535
1535
|
if (options.sqlDialect === 'hana')
|
|
1536
1536
|
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1537
|
+
else if (options.sqlDialect === 'postgres')
|
|
1538
|
+
return 'current_setting(\'CAP.APPLICATIONUSER\')';
|
|
1537
1539
|
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1538
1540
|
return '\'$user.id\'';
|
|
1539
1541
|
}
|
|
1540
1542
|
else if (x.ref[1] === 'locale') {
|
|
1541
1543
|
if (options.sqlDialect === 'hana')
|
|
1542
1544
|
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1545
|
+
else if (options.sqlDialect === 'postgres')
|
|
1546
|
+
return 'current_setting(\'CAP.LOCALE\')';
|
|
1543
1547
|
return '\'en\''; // default language
|
|
1544
1548
|
}
|
|
1545
1549
|
// Basically: Second path step was invalid, do nothing - should not happen.
|
|
@@ -1567,6 +1571,7 @@ function toSqlDdl(csn, options) {
|
|
|
1567
1571
|
case 'hana':
|
|
1568
1572
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1569
1573
|
case 'postgres':
|
|
1574
|
+
return '(to_timestamp(current_setting(\'CAP.VALID_FROM\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
|
|
1570
1575
|
case 'plain':
|
|
1571
1576
|
return 'current_timestamp';
|
|
1572
1577
|
default:
|
|
@@ -1584,6 +1589,7 @@ function toSqlDdl(csn, options) {
|
|
|
1584
1589
|
case 'hana':
|
|
1585
1590
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1586
1591
|
case 'postgres':
|
|
1592
|
+
return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
|
|
1587
1593
|
case 'plain':
|
|
1588
1594
|
return 'current_timestamp';
|
|
1589
1595
|
default:
|
|
@@ -242,6 +242,10 @@ const cdsToSqlTypes = {
|
|
|
242
242
|
'cds.DecimalFloat': 'DECIMAL',
|
|
243
243
|
'cds.Integer64': 'BIGINT',
|
|
244
244
|
'cds.Integer': 'INTEGER',
|
|
245
|
+
'cds.Int64': 'BIGINT',
|
|
246
|
+
'cds.Int32': 'INTEGER',
|
|
247
|
+
'cds.Int16': 'SMALLINT',
|
|
248
|
+
'cds.UInt8': 'TINYINT',
|
|
245
249
|
'cds.hana.SMALLINT': 'SMALLINT',
|
|
246
250
|
'cds.hana.TINYINT': 'TINYINT', // not a Standard SQL type
|
|
247
251
|
'cds.Double': 'DOUBLE',
|
|
@@ -280,19 +284,25 @@ const cdsToSqlTypes = {
|
|
|
280
284
|
'cds.hana.SMALLDECIMAL': 'DECIMAL',
|
|
281
285
|
},
|
|
282
286
|
postgres: {
|
|
283
|
-
//
|
|
284
|
-
// We can't use text types for binary on PostgreSQL due to NUL!
|
|
287
|
+
// See <https://www.postgresql.org/docs/current/datatype.html>
|
|
285
288
|
'cds.String': 'VARCHAR',
|
|
286
|
-
'cds.LargeString': '
|
|
287
|
-
'cds.
|
|
288
|
-
'cds.
|
|
289
|
-
'cds.
|
|
290
|
-
'cds.
|
|
291
|
-
'cds.Double': 'double precision',
|
|
292
|
-
'cds.hana.TINYINT': 'INTEGER',
|
|
289
|
+
'cds.LargeString': 'TEXT',
|
|
290
|
+
'cds.LargeBinary': 'BYTEA',
|
|
291
|
+
'cds.Binary': 'BYTEA',
|
|
292
|
+
'cds.Double': 'FLOAT8',
|
|
293
|
+
'cds.UInt8': 'INTEGER', // Not equivalent
|
|
293
294
|
},
|
|
294
295
|
};
|
|
295
296
|
|
|
297
|
+
// Type mapping from cds type names to HDBCDS type names:
|
|
298
|
+
// Only those types, that need mapping, are listed.
|
|
299
|
+
const cdsToHdbcdsTypes = {
|
|
300
|
+
'cds.UInt8': 'cds.hana.TINYINT',
|
|
301
|
+
'cds.Int16': 'cds.hana.SMALLINT',
|
|
302
|
+
'cds.Int32': 'cds.Integer',
|
|
303
|
+
'cds.Int64': 'cds.Integer64',
|
|
304
|
+
};
|
|
305
|
+
|
|
296
306
|
/**
|
|
297
307
|
* Get the element matching the column
|
|
298
308
|
*
|
|
@@ -521,6 +531,7 @@ module.exports = {
|
|
|
521
531
|
addIntermediateContexts,
|
|
522
532
|
addContextMarkers,
|
|
523
533
|
cdsToSqlTypes,
|
|
534
|
+
cdsToHdbcdsTypes,
|
|
524
535
|
hasHanaComment,
|
|
525
536
|
getHanaComment,
|
|
526
537
|
findElement,
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -43,8 +43,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
43
43
|
options.src === 'hdi' ||
|
|
44
44
|
(options.manageConstraints && options.manageConstraints.src === 'hdi');
|
|
45
45
|
|
|
46
|
-
const { sqlMapping } = options;
|
|
47
|
-
const forSqlite = options.sqlDialect === 'sqlite';
|
|
46
|
+
const { sqlMapping, sqlDialect } = options;
|
|
48
47
|
let result = '';
|
|
49
48
|
result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;
|
|
50
49
|
if (renderAsHdbconstraint)
|
|
@@ -55,7 +54,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
55
54
|
const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
|
|
56
55
|
|
|
57
56
|
// omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
|
|
58
|
-
if (
|
|
57
|
+
if (sqlDialect === 'sqlite') {
|
|
59
58
|
if (constraint.onDelete === 'CASCADE' )
|
|
60
59
|
result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
|
|
61
60
|
}
|
|
@@ -65,13 +64,14 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
// constraint enforcement / validation must be switched off using sqlite pragma statement
|
|
67
|
+
// constraint enforcement / validation not supported by postgres
|
|
68
68
|
// Does not include HDBCDS.
|
|
69
|
-
if (options.toSql &&
|
|
69
|
+
if (options.toSql && sqlDialect !== 'sqlite' && sqlDialect !== 'postgres') {
|
|
70
70
|
result += `${indent}${!constraint.validated ? 'NOT ' : ''}VALIDATED\n`;
|
|
71
71
|
result += `${indent}${!constraint.enforced ? 'NOT ' : ''}ENFORCED\n`;
|
|
72
72
|
}
|
|
73
73
|
// for sqlite, the DEFERRABLE keyword is required
|
|
74
|
-
result += `${indent}${
|
|
74
|
+
result += `${indent}${sqlDialect === 'sqlite' ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
|
|
75
75
|
return result;
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -35,10 +35,16 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const csnPath = [ ...path ];
|
|
38
|
-
if (prop === 'definitions')
|
|
38
|
+
if (prop === 'definitions') {
|
|
39
39
|
definitions( parent, 'definitions', parent.definitions );
|
|
40
|
-
|
|
40
|
+
}
|
|
41
|
+
else if (options.directDict) {
|
|
42
|
+
for (const name of Object.getOwnPropertyNames( parent ))
|
|
43
|
+
standard( parent, name, parent[name] );
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
41
46
|
standard(parent, prop, parent[prop]);
|
|
47
|
+
}
|
|
42
48
|
return parent;
|
|
43
49
|
|
|
44
50
|
/**
|
|
@@ -84,7 +90,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
84
90
|
* @param {object} dict The value of node[_prop]
|
|
85
91
|
*/
|
|
86
92
|
function dictionary( node, _prop, dict ) {
|
|
87
|
-
// Allow skipping dicts like actions in
|
|
93
|
+
// Allow skipping dicts like actions in forRelationalDB
|
|
88
94
|
if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
|
|
89
95
|
return;
|
|
90
96
|
csnPath.push( _prop );
|
|
@@ -194,9 +200,31 @@ function applyTransformationsOnNonDictionary(parent, prop, customTransformers =
|
|
|
194
200
|
return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
|
|
195
201
|
}
|
|
196
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Instead of looping through the whole model, start at a given thing (like .elements),
|
|
205
|
+
* as long as it is a dictionary.
|
|
206
|
+
*
|
|
207
|
+
* Each transformer gets:
|
|
208
|
+
* - the parent having the property
|
|
209
|
+
* - the name of the property
|
|
210
|
+
* - the value of the property
|
|
211
|
+
* - the path to the property
|
|
212
|
+
*
|
|
213
|
+
*
|
|
214
|
+
* @param {object} dictionary Dictionary to enrich in-place
|
|
215
|
+
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
216
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
217
|
+
* @param {CSN.Path} path Path pointing to parent
|
|
218
|
+
* @returns {object} dictionary with transformations applied
|
|
219
|
+
*/
|
|
220
|
+
function applyTransformationsOnDictionary(dictionary, customTransformers = {}, options = {}, path = []) {
|
|
221
|
+
return applyTransformationsInternal(dictionary, null, customTransformers, [], { directDict: true, ...options }, path);
|
|
222
|
+
}
|
|
223
|
+
|
|
197
224
|
module.exports = {
|
|
198
225
|
applyTransformations,
|
|
199
226
|
applyTransformationsOnNonDictionary,
|
|
227
|
+
applyTransformationsOnDictionary,
|
|
200
228
|
};
|
|
201
229
|
|
|
202
230
|
|
|
@@ -209,4 +237,5 @@ module.exports = {
|
|
|
209
237
|
* @property {object} [skipStandard] stop drill-down on certain "standard" props
|
|
210
238
|
* @property {object} [skipDict] stop drill-down on certain "dictionary" props
|
|
211
239
|
* @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
|
|
240
|
+
* @property {boolean} [directDict=false] Implicitly set via applyTransformationsOnDictionary
|
|
212
241
|
*/
|
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
walkCsnPath,
|
|
8
8
|
} = require('../../model/csnUtils');
|
|
9
9
|
const { csnRefs, implicitAs } = require('../../model/csnRefs');
|
|
10
|
-
const { setProp
|
|
10
|
+
const { setProp } = require('../../base/model');
|
|
11
11
|
const { forEach } = require('../../utils/objectUtils');
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -26,12 +26,11 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
26
26
|
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
|
|
27
27
|
const csnUtils = getUtils(csn);
|
|
28
28
|
const {
|
|
29
|
-
isStructured, get$combined, getFinalBaseTypeWithProps,
|
|
29
|
+
isStructured, get$combined, getFinalBaseTypeWithProps,
|
|
30
30
|
} = csnUtils;
|
|
31
31
|
let { effectiveType, inspectRef } = csnUtils;
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
rewriteExpandInline();
|
|
33
|
+
rewriteExpandInline();
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
applyTransformations(csn, {
|
|
@@ -83,8 +82,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
83
82
|
*/
|
|
84
83
|
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
85
84
|
markAsToDummyfy(artifact, path[1]);
|
|
86
|
-
|
|
87
|
-
error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME), which is outside any service');
|
|
85
|
+
error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME)');
|
|
88
86
|
}
|
|
89
87
|
else {
|
|
90
88
|
parent.columns = rewritten.columns;
|
|
@@ -347,14 +345,18 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
347
345
|
}
|
|
348
346
|
else if (current.xpr || current.args) {
|
|
349
347
|
// We need to re-write refs in the .xpr/.args so they stay resolvable - we need to prepend the currentRef
|
|
350
|
-
|
|
348
|
+
rewriteExpressionArrays(current, currentRef);
|
|
351
349
|
expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
|
|
352
350
|
}
|
|
351
|
+
else if (current.on || current.cast?.on) {
|
|
352
|
+
rewriteOn(current, [ currentAlias.slice(0, -1).join(pathDelimiter) ]);
|
|
353
|
+
expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
|
|
354
|
+
}
|
|
353
355
|
else if (current.val !== undefined || current.func !== undefined) {
|
|
354
356
|
expanded.push(Object.assign(current, { as: currentAlias.join(pathDelimiter) }));
|
|
355
357
|
}
|
|
356
|
-
else {
|
|
357
|
-
expanded.push({ ref: currentRef, as: currentAlias.join(pathDelimiter) });
|
|
358
|
+
else { // preserve stuff like .cast for redirection
|
|
359
|
+
expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
|
|
358
360
|
}
|
|
359
361
|
}
|
|
360
362
|
|
|
@@ -367,37 +369,79 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
367
369
|
* @param {object} parent Thing that has an .xpr/.args
|
|
368
370
|
* @param {string[]} ref Ref so far
|
|
369
371
|
*/
|
|
370
|
-
function
|
|
372
|
+
function rewriteExpressionArrays(parent, ref) {
|
|
371
373
|
const stack = [ [ parent, ref ] ];
|
|
372
374
|
while (stack.length > 0) {
|
|
373
375
|
const [ current, currentRef ] = stack.pop();
|
|
374
|
-
if (current.xpr)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
376
|
+
if (current.xpr)
|
|
377
|
+
rewriteSingleExpressionArray(current.xpr, currentRef, stack);
|
|
378
|
+
if (current.args)
|
|
379
|
+
rewriteSingleExpressionArray(current.args, currentRef, stack);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* With a .cast.on or .on in a .expand/.inline, we need to change the references,
|
|
385
|
+
* since we change the overall scope of things (by "heaving" them up into "normal refs").
|
|
386
|
+
*
|
|
387
|
+
* So anything that does not have a $self/$projection infron get's the so-far-traveled alias,
|
|
388
|
+
* since after the transformation it will basically be in "top-level".
|
|
389
|
+
*
|
|
390
|
+
* @param {object} parent
|
|
391
|
+
* @param {Array} ref The so-far effective name (basically the will-be alias), as an array to treat like a ref
|
|
392
|
+
*/
|
|
393
|
+
function rewriteOn(parent, ref) {
|
|
394
|
+
const stack = [ [ parent, ref ] ];
|
|
395
|
+
while (stack.length > 0) {
|
|
396
|
+
const [ current, currentRef ] = stack.pop();
|
|
397
|
+
if (current.on)
|
|
398
|
+
rewriteOnCondition(current.on, currentRef, stack);
|
|
399
|
+
if (current.cast?.on)
|
|
400
|
+
rewriteOnCondition(current.cast.on, currentRef, stack);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Actually rewrite the given oncondition. Once we find something to rewrite,
|
|
406
|
+
* we preprend the currentRef.
|
|
407
|
+
*
|
|
408
|
+
* All stuff is pushed to the stack.
|
|
409
|
+
*
|
|
410
|
+
* @param {Array} on
|
|
411
|
+
* @param {Array} currentRef
|
|
412
|
+
* @param {Array} stack
|
|
413
|
+
*/
|
|
414
|
+
function rewriteOnCondition(on, currentRef, stack) {
|
|
415
|
+
for (let i = 0; i < on.length; i++) {
|
|
416
|
+
const part = on[i];
|
|
417
|
+
if (part.ref && part.ref[0] !== '$self' && part.ref[0] !== '$projection') {
|
|
418
|
+
part.ref = currentRef[0] ? [ currentRef[0], ...part.ref ] : part.ref;
|
|
419
|
+
on[i] = part;
|
|
420
|
+
stack.push([ part, part.ref ]);
|
|
387
421
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
422
|
+
else {
|
|
423
|
+
stack.push([ part, currentRef ]);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Rewrite the given expressionArray, prefixing currentRef to all refs
|
|
430
|
+
*
|
|
431
|
+
* @param {Array} expressionArray
|
|
432
|
+
* @param {Array} currentRef
|
|
433
|
+
* @param {Array} stack
|
|
434
|
+
*/
|
|
435
|
+
function rewriteSingleExpressionArray(expressionArray, currentRef, stack) {
|
|
436
|
+
for (let i = 0; i < expressionArray.length; i++) {
|
|
437
|
+
const part = expressionArray[i];
|
|
438
|
+
if (part.ref) {
|
|
439
|
+
part.ref = currentRef.concat(part.ref);
|
|
440
|
+
expressionArray[i] = part;
|
|
441
|
+
stack.push([ part, part.ref ]);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
stack.push([ part, currentRef ]);
|
|
401
445
|
}
|
|
402
446
|
}
|
|
403
447
|
}
|
|
@@ -409,7 +453,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
409
453
|
*/
|
|
410
454
|
function findAnEntity() {
|
|
411
455
|
for (const name in csn.definitions) {
|
|
412
|
-
if (Object.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
|
|
456
|
+
if (Object.prototype.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
|
|
413
457
|
return name;
|
|
414
458
|
}
|
|
415
459
|
return null;
|