@sap/cds-compiler 5.7.4 → 5.8.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 +60 -2
- package/bin/cdsse.js +13 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/options.js +2 -1
- package/lib/api/validate.js +9 -0
- package/lib/base/message-registry.js +55 -20
- package/lib/base/messages.js +5 -2
- package/lib/base/model.js +4 -1
- package/lib/checks/assocOutsideService.js +40 -0
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/types.js +7 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/assert-consistency.js +11 -5
- package/lib/compiler/checks.js +79 -17
- package/lib/compiler/define.js +57 -3
- package/lib/compiler/extend.js +1 -2
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/populate.js +17 -6
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +181 -150
- package/lib/compiler/shared.js +276 -22
- package/lib/compiler/tweak-assocs.js +15 -4
- package/lib/compiler/xpr-rewrite.js +76 -50
- package/lib/edm/annotations/edmJson.js +1 -1
- package/lib/edm/annotations/genericTranslation.js +2 -2
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edmPreprocessor.js +15 -9
- package/lib/edm/edmUtils.js +12 -5
- package/lib/gen/CdlGrammar.checksum +1 -0
- package/lib/gen/CdlParser.js +2234 -2233
- package/lib/gen/Dictionary.json +55 -8
- package/lib/json/from-csn.js +37 -17
- package/lib/json/to-csn.js +4 -0
- package/lib/language/genericAntlrParser.js +7 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +0 -5
- package/lib/modelCompare/utils/filter.js +2 -2
- package/lib/optionProcessor.js +2 -0
- package/lib/parsers/AstBuildingParser.js +47 -17
- package/lib/parsers/CdlGrammar.g4 +10 -12
- package/lib/parsers/XprTree.js +206 -0
- package/lib/render/toCdl.js +61 -89
- package/lib/render/toSql.js +59 -29
- package/lib/render/utils/standardDatabaseFunctions.js +252 -15
- package/lib/transform/addTenantFields.js +9 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
- package/lib/transform/db/assocsToQueries/utils.js +10 -3
- package/lib/transform/db/expansion.js +3 -1
- package/lib/transform/db/flattening.js +7 -3
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +70 -17
- package/lib/transform/draft/db.js +8 -3
- package/lib/transform/draft/odata.js +27 -4
- package/lib/transform/effective/main.js +37 -10
- package/lib/transform/effective/misc.js +4 -9
- package/lib/transform/effective/service.js +34 -0
- package/lib/transform/effective/types.js +28 -17
- package/lib/transform/forOdata.js +36 -10
- package/lib/transform/forRelationalDB.js +30 -18
- package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
- package/lib/transform/odata/createForeignKeys.js +121 -117
- package/lib/transform/odata/flattening.js +12 -9
- package/lib/transform/transformUtils.js +58 -25
- package/lib/transform/translateAssocsToJoins.js +10 -6
- package/lib/transform/universalCsn/coreComputed.js +5 -1
- package/package.json +1 -1
- package/share/messages/message-explanations.json +1 -0
- package/share/messages/rewrite-not-supported.md +5 -0
- package/share/messages/rewrite-undefined-key.md +94 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Create a hierarchical expression tree from an `xpr` array
|
|
2
|
+
|
|
3
|
+
// See ./CdlGrammar.js for the operator precedences.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const prefixOperators = { // see <prec=…,prefix> in `expression` of CdlGrammar
|
|
10
|
+
__proto__: null,
|
|
11
|
+
new: 33, // special in CDL (only before ref), clarify with `.`
|
|
12
|
+
exists: 33, // special in CDL
|
|
13
|
+
'+': 30, // note: binary `.` and `over` have higher precedence!
|
|
14
|
+
'-': 30,
|
|
15
|
+
not: 8,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const binaryOperators = {
|
|
19
|
+
__proto__: null,
|
|
20
|
+
'.': 37, // nary
|
|
21
|
+
over: 35, // TODO: only after ref with arg?
|
|
22
|
+
'*': 24, // nary
|
|
23
|
+
'/': 24,
|
|
24
|
+
'+': 22,
|
|
25
|
+
'-': 22,
|
|
26
|
+
'||': 20,
|
|
27
|
+
'=': 10, // with ANY/SOME/ALL
|
|
28
|
+
'<>': 10,
|
|
29
|
+
'>': 10,
|
|
30
|
+
'>=': 10,
|
|
31
|
+
'<': 10,
|
|
32
|
+
'<=': 10,
|
|
33
|
+
'!=': 10,
|
|
34
|
+
'==': 10, // not supported yet in CDL or backends
|
|
35
|
+
and: 4,
|
|
36
|
+
or: 2,
|
|
37
|
+
// with second token or ternary (in the grammar, these ops have prec=10, but
|
|
38
|
+
// also assoc=none, i.e. could not be used without parens together):
|
|
39
|
+
is: 11, // is binary op here, not postfix
|
|
40
|
+
in: 13,
|
|
41
|
+
between: 13,
|
|
42
|
+
like: 13,
|
|
43
|
+
not: 15, // specially handled
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const secondTokens = {
|
|
47
|
+
// the value is the precedence of the first token listed in `binaryOperators`
|
|
48
|
+
__proto__: null,
|
|
49
|
+
any: 10, // for `=` etc
|
|
50
|
+
some: 10,
|
|
51
|
+
all: 10,
|
|
52
|
+
not: 11, // for `is`
|
|
53
|
+
between: 15, // for `not`
|
|
54
|
+
in: 15,
|
|
55
|
+
like: 15,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const naryOperators = {
|
|
59
|
+
__proto__: null,
|
|
60
|
+
'.': true, // CSN-tree really as left-assoc binary?
|
|
61
|
+
'*': true,
|
|
62
|
+
'/': true,
|
|
63
|
+
'+': true,
|
|
64
|
+
'-': true,
|
|
65
|
+
'||': true,
|
|
66
|
+
and: true,
|
|
67
|
+
or: true,
|
|
68
|
+
between: 'and',
|
|
69
|
+
like: 'escape',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class XprTree {
|
|
74
|
+
nodes; // array value of CSN property `xpr`/`where`/…
|
|
75
|
+
nodeIdx = 0;
|
|
76
|
+
args; // corresponding XSN array, with already tree-like sub expressions
|
|
77
|
+
location;
|
|
78
|
+
|
|
79
|
+
constructor( nodes, args, location ) {
|
|
80
|
+
this.nodes = nodes;
|
|
81
|
+
this.args = args;
|
|
82
|
+
this.location = location;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
tree() {
|
|
86
|
+
const args = [];
|
|
87
|
+
const { length } = this.args;
|
|
88
|
+
while (this.nodeIdx < length) {
|
|
89
|
+
const expr = this.expression( -1 );
|
|
90
|
+
if (expr)
|
|
91
|
+
args.push( expr );
|
|
92
|
+
}
|
|
93
|
+
return (args.length === 1) ? args[0] : this.create( args );
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
expression( parentPrec ) {
|
|
97
|
+
let append;
|
|
98
|
+
let naryOp;
|
|
99
|
+
let args;
|
|
100
|
+
|
|
101
|
+
// Term = ref/val or unary operator with expression as operand
|
|
102
|
+
let expr = this.args[this.nodeIdx];
|
|
103
|
+
if (!expr)
|
|
104
|
+
return expr;
|
|
105
|
+
let node = this.nodes[this.nodeIdx++];
|
|
106
|
+
if (typeof node === 'string') {
|
|
107
|
+
const prec = prefixOperators[node]; // <prec=…,prefix> in CdlGrammar
|
|
108
|
+
if (prec) {
|
|
109
|
+
const right = this.expression( prec - 1 );
|
|
110
|
+
if (!right)
|
|
111
|
+
return expr;
|
|
112
|
+
expr = this.create( [ expr, right ] );
|
|
113
|
+
}
|
|
114
|
+
else if (node === 'case') {
|
|
115
|
+
expr = this.caseWhen( [ expr ] );
|
|
116
|
+
}
|
|
117
|
+
else { // unknown token (keyword in CDL):
|
|
118
|
+
return expr; // …from fns with irregular syntax?
|
|
119
|
+
// also handles `null` as right side of `is` in `is null`
|
|
120
|
+
// TODO: `(` from CSN v0.x ?
|
|
121
|
+
// It is important not to handle binary ops after this, because otherwise
|
|
122
|
+
// we would not properly parse functions with irregular syntax
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
node = this.nodes[this.nodeIdx];
|
|
127
|
+
while (typeof node === 'string') {
|
|
128
|
+
const prec = binaryOperators[node]; // <prec=…> in CdlGrammar
|
|
129
|
+
if (!prec || parentPrec >= prec)
|
|
130
|
+
return expr;
|
|
131
|
+
|
|
132
|
+
// handle n-ary operators including ternary
|
|
133
|
+
append = (typeof naryOp === 'string')
|
|
134
|
+
? !append && naryOp // `and` after `between`, `escape` after `like`˛
|
|
135
|
+
: naryOp && node; // nary operators like `+`
|
|
136
|
+
if (node === append) {
|
|
137
|
+
args.push( this.args[this.nodeIdx++] );
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
naryOp = naryOperators[node];
|
|
141
|
+
append = false;
|
|
142
|
+
args = [ expr, this.args[this.nodeIdx++] ];
|
|
143
|
+
expr = this.create( args, naryOp === true );
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// handle second token of operator:
|
|
147
|
+
const second = this.nodes[this.nodeIdx];
|
|
148
|
+
if (typeof second === 'string' && secondTokens[second] === prec && !append) {
|
|
149
|
+
args.push( this.args[this.nodeIdx++] );
|
|
150
|
+
if (node === 'not')
|
|
151
|
+
naryOp = naryOperators[second];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// the right side
|
|
155
|
+
const right = this.expression( prec );
|
|
156
|
+
if (!right) // incomplete
|
|
157
|
+
return expr;
|
|
158
|
+
args.push( right );
|
|
159
|
+
node = this.nodes[this.nodeIdx];
|
|
160
|
+
// TODO: simplyfy between-and and like-escape with pushTokenAndExpression()
|
|
161
|
+
}
|
|
162
|
+
return expr;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
caseWhen( args ) {
|
|
166
|
+
const expr = this.create( args );
|
|
167
|
+
let node = this.nodes[this.nodeIdx];
|
|
168
|
+
if (node !== 'when') {
|
|
169
|
+
const value = this.expression( -1 );
|
|
170
|
+
if (value)
|
|
171
|
+
args.push( value );
|
|
172
|
+
node = this.nodes[this.nodeIdx];
|
|
173
|
+
}
|
|
174
|
+
while (node === 'when') {
|
|
175
|
+
node = this.pushTokenAndExpression( args );
|
|
176
|
+
if (node === 'then')
|
|
177
|
+
node = this.pushTokenAndExpression( args );
|
|
178
|
+
}
|
|
179
|
+
if (node === 'else')
|
|
180
|
+
node = this.pushTokenAndExpression( args );
|
|
181
|
+
if (node === 'end')
|
|
182
|
+
args.push( this.args[this.nodeIdx++] );
|
|
183
|
+
return expr;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
pushTokenAndExpression( args ) {
|
|
187
|
+
args.push( this.args[this.nodeIdx++] );
|
|
188
|
+
const value = this.expression( -1 );
|
|
189
|
+
if (value)
|
|
190
|
+
args.push( value );
|
|
191
|
+
return this.nodes[this.nodeIdx];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
create( args, isNary = false ) {
|
|
195
|
+
// could be adopted for CSN expression tree
|
|
196
|
+
return {
|
|
197
|
+
op: { val: (isNary ? 'nary' : 'ixpr'), location: this.location },
|
|
198
|
+
location: this.location,
|
|
199
|
+
args,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
xprAsTree: ( nodes, args, location ) => (new XprTree( nodes, args, location )).tree(),
|
|
206
|
+
};
|
package/lib/render/toCdl.js
CHANGED
|
@@ -124,7 +124,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
124
124
|
// This environment is passed down the call hierarchy, for dealing with
|
|
125
125
|
// indentation and name resolution issues
|
|
126
126
|
const env = createEnv({ path: [ 'vocabularies', name ] });
|
|
127
|
-
const sourceStr =
|
|
127
|
+
const sourceStr = renderArtifact(name, anno, env, 'annotation');
|
|
128
128
|
result += `${sourceStr}\n`;
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -452,7 +452,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
452
452
|
case 'entity':
|
|
453
453
|
if (art.query || art.projection)
|
|
454
454
|
return renderView(artifactName, art, env);
|
|
455
|
-
return
|
|
455
|
+
return renderArtifact(artifactName, art, env);
|
|
456
456
|
case 'aspect':
|
|
457
457
|
return renderAspect(artifactName, art, env);
|
|
458
458
|
|
|
@@ -460,16 +460,16 @@ function csnToCdl( csn, options, msg ) {
|
|
|
460
460
|
case 'service':
|
|
461
461
|
return renderContextOrService(artifactName, art, env);
|
|
462
462
|
|
|
463
|
-
case 'type':
|
|
464
463
|
case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
|
|
465
|
-
return
|
|
464
|
+
return renderArtifact(artifactName, art, env, 'annotation');
|
|
466
465
|
|
|
467
466
|
case 'action':
|
|
468
467
|
case 'function':
|
|
469
468
|
return renderActionOrFunction(artifactName, art, env);
|
|
470
469
|
|
|
470
|
+
case 'type':
|
|
471
471
|
case 'event':
|
|
472
|
-
return
|
|
472
|
+
return renderArtifact(artifactName, art, env);
|
|
473
473
|
|
|
474
474
|
default:
|
|
475
475
|
throw new ModelError(`to.cdl: Unknown artifact kind: '${art.kind}' at ${JSON.stringify(env.path)}`);
|
|
@@ -477,30 +477,70 @@ function csnToCdl( csn, options, msg ) {
|
|
|
477
477
|
}
|
|
478
478
|
|
|
479
479
|
/**
|
|
480
|
+
* TODO: Also use this function for other kinds such as entities, aspects and views.
|
|
481
|
+
*
|
|
480
482
|
* @param {string} artifactName
|
|
481
483
|
* @param {CSN.Artifact} art
|
|
482
484
|
* @param {CdlRenderEnvironment} env
|
|
485
|
+
* @param {string} [overrideKind] If set, override the artifact kind.
|
|
483
486
|
*/
|
|
484
|
-
function
|
|
487
|
+
function renderArtifact( artifactName, art, env, overrideKind ) {
|
|
485
488
|
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
489
|
+
let kind = overrideKind || art.$syntax === 'aspect' && 'aspect' || art.kind;
|
|
490
|
+
if (art.abstract)
|
|
491
|
+
kind = `abstract ${ kind }`;
|
|
486
492
|
const normalizedArtifactName = renderArtifactName(artifactName, env);
|
|
487
|
-
result += `${env.indent}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
493
|
+
result += `${env.indent}${kind} ${normalizedArtifactName}`;
|
|
494
|
+
|
|
495
|
+
if (art.params)
|
|
496
|
+
result += renderParameters(art, env);
|
|
497
|
+
|
|
498
|
+
let isDirectStruct = false;
|
|
499
|
+
const isQuery = art.query || art.projection;
|
|
500
|
+
if (isQuery) {
|
|
491
501
|
result += ' : ';
|
|
492
|
-
// events (should) only support "projections"
|
|
502
|
+
// types/events (should) only support "projections"
|
|
493
503
|
result += renderQuery(getNormalizedQuery(art).query, true, 'projection',
|
|
494
504
|
env.withSubPath([ art.projection ? 'projection' : 'query' ]));
|
|
495
|
-
result += ';\n';
|
|
496
505
|
}
|
|
497
|
-
else
|
|
498
|
-
|
|
499
|
-
|
|
506
|
+
else {
|
|
507
|
+
const type = renderTypeReferenceAndProps(art, env);
|
|
508
|
+
if (type) {
|
|
509
|
+
isDirectStruct = type.startsWith('{');
|
|
510
|
+
|
|
511
|
+
if (art.includes?.length && isDirectStruct) {
|
|
512
|
+
// We can only render includes, if the type is directly structured. Otherwise, we would
|
|
513
|
+
// render e.g. `type T : Include : T2;`, which is invalid. We use `extend` in such cases.
|
|
514
|
+
result += renderIncludes(art.includes, env);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// For nicer output, no colon if unnamed structure is used.
|
|
518
|
+
result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
msg.warning('syntax-missing-type', env.path, { '#': art.kind, name: artifactName }, {
|
|
522
|
+
std: 'Missing type for definition $(NAME); can\'t be represented in CDL',
|
|
523
|
+
entity: 'Missing elements for entity $(NAME); can\'t be represented in CDL',
|
|
524
|
+
});
|
|
525
|
+
}
|
|
500
526
|
}
|
|
501
|
-
|
|
502
|
-
|
|
527
|
+
|
|
528
|
+
if (art.actions) {
|
|
529
|
+
if (!isQuery && !isDirectStruct) {
|
|
530
|
+
// If there are no elements nor query, but actions, CDL syntax requires braces.
|
|
531
|
+
result += ' { }';
|
|
532
|
+
}
|
|
533
|
+
result += renderActionsAndFunctions(art, env);
|
|
503
534
|
}
|
|
535
|
+
|
|
536
|
+
result += ';\n';
|
|
537
|
+
|
|
538
|
+
if (art.includes?.length && !isDirectStruct) {
|
|
539
|
+
// If we're not a directly structured type, render the `includes` as `extend`
|
|
540
|
+
// statements directly below the type definition.
|
|
541
|
+
result += renderExtendStatement(artifactName, { includes: art.includes }, env);
|
|
542
|
+
}
|
|
543
|
+
|
|
504
544
|
return result;
|
|
505
545
|
}
|
|
506
546
|
|
|
@@ -516,35 +556,6 @@ function csnToCdl( csn, options, msg ) {
|
|
|
516
556
|
return `${result} {};\n`;
|
|
517
557
|
}
|
|
518
558
|
|
|
519
|
-
/**
|
|
520
|
-
* Render a (non-projection, non-view) entity. Return the resulting source string.
|
|
521
|
-
*
|
|
522
|
-
* @param {string} artifactName
|
|
523
|
-
* @param {CSN.Artifact} art
|
|
524
|
-
* @param {CdlRenderEnvironment} env
|
|
525
|
-
* @return {string}
|
|
526
|
-
*/
|
|
527
|
-
function renderEntity( artifactName, art, env ) {
|
|
528
|
-
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
529
|
-
result += env.indent + (art.abstract ? 'abstract ' : '');
|
|
530
|
-
result += `entity ${renderArtifactName(artifactName, env)}`;
|
|
531
|
-
|
|
532
|
-
if (art.params)
|
|
533
|
-
result += renderParameters(art, env);
|
|
534
|
-
if (art.includes)
|
|
535
|
-
result += renderIncludes(art.includes, env);
|
|
536
|
-
|
|
537
|
-
if (art.elements)
|
|
538
|
-
result += ` ${renderElements(art, env)}`;
|
|
539
|
-
else if (art.actions)
|
|
540
|
-
// if there are no elements, but actions, CDL syntax requires braces.
|
|
541
|
-
result += ' { }';
|
|
542
|
-
|
|
543
|
-
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
544
|
-
|
|
545
|
-
return result;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
559
|
/**
|
|
549
560
|
* Render an aspect. Return the resulting source string.
|
|
550
561
|
* Behaves very similar to renderEntity, _except_ that aspects are
|
|
@@ -938,8 +949,10 @@ function csnToCdl( csn, options, msg ) {
|
|
|
938
949
|
// No expression to render for { * } as alias
|
|
939
950
|
let result = (obj.as && obj.expand && !obj.ref) ? '' : exprRenderer.renderExpr(withoutCast(obj), env);
|
|
940
951
|
|
|
952
|
+
const isAnonymousExpand = (obj.expand && !obj.ref);
|
|
953
|
+
|
|
941
954
|
// s as alias { * }
|
|
942
|
-
if (obj.as &&
|
|
955
|
+
if (obj.as && !isAnonymousExpand)
|
|
943
956
|
result += renderAlias(obj.as, env);
|
|
944
957
|
|
|
945
958
|
// We found a leaf - no further drilling
|
|
@@ -971,7 +984,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
971
984
|
result += ` excluding { ${obj.excluding.join(',')} }`;
|
|
972
985
|
|
|
973
986
|
// { * } as expand
|
|
974
|
-
if (
|
|
987
|
+
if (obj.as && isAnonymousExpand)
|
|
975
988
|
result += renderAlias(obj.as, env);
|
|
976
989
|
|
|
977
990
|
return result;
|
|
@@ -1268,47 +1281,6 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1268
1281
|
return result;
|
|
1269
1282
|
}
|
|
1270
1283
|
|
|
1271
|
-
/**
|
|
1272
|
-
* Render a type (derived or structured) or an annotation decl with name 'artifactName'.
|
|
1273
|
-
* Return the resulting source string.
|
|
1274
|
-
*
|
|
1275
|
-
* @param {string} artifactName
|
|
1276
|
-
* @param {CSN.Artifact} art
|
|
1277
|
-
* @param {CdlRenderEnvironment} env
|
|
1278
|
-
* @param {String} [artType] Used for rendering `csn.vocabularies`, as the annotations there do not have a kind.
|
|
1279
|
-
* @return {string}
|
|
1280
|
-
*/
|
|
1281
|
-
function renderTypeOrAnnotation( artifactName, art, env, artType ) {
|
|
1282
|
-
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
1283
|
-
result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName, env)}`;
|
|
1284
|
-
|
|
1285
|
-
const type = renderTypeReferenceAndProps(art, env);
|
|
1286
|
-
const isDirectStruct = type?.startsWith('{');
|
|
1287
|
-
if (art.includes?.length && isDirectStruct)
|
|
1288
|
-
// We can only render includes, if the type is directly structured. Otherwise, we would
|
|
1289
|
-
// render e.g. `type T : Include : T2;`, which is invalid. We use `extend` in such cases.
|
|
1290
|
-
result += renderIncludes(art.includes, env);
|
|
1291
|
-
|
|
1292
|
-
if (type) {
|
|
1293
|
-
// For nicer output, no colon if unnamed structure is used.
|
|
1294
|
-
result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
|
|
1295
|
-
}
|
|
1296
|
-
else {
|
|
1297
|
-
msg.warning('syntax-missing-type', env.path, { name: artifactName },
|
|
1298
|
-
'Missing type for definition $(NAME); can\'t be represented in CDL');
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
result += ';\n';
|
|
1302
|
-
|
|
1303
|
-
if (art.includes?.length && !isDirectStruct) {
|
|
1304
|
-
// If we're not a directly structured type, render the `includes` as `extend`
|
|
1305
|
-
// statements directly below the type definition.
|
|
1306
|
-
result += renderExtendStatement(artifactName, { includes: art.includes }, env);
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
return result;
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
1284
|
/**
|
|
1313
1285
|
* Render a reference to a type used by 'artifact' (named or inline) and (element) properties
|
|
1314
1286
|
* such as `not null` and `default <xpr>`.
|
package/lib/render/toSql.js
CHANGED
|
@@ -189,9 +189,11 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
189
189
|
constraintDeletions: [],
|
|
190
190
|
migrations: Object.create(null),
|
|
191
191
|
hdbrole: Object.create(null),
|
|
192
|
+
hdbsynonym: Object.create(null),
|
|
192
193
|
};
|
|
193
194
|
|
|
194
195
|
const sqlServiceEntities = Object.create(null);
|
|
196
|
+
const dummySqlServiceEntities = Object.create(null);
|
|
195
197
|
|
|
196
198
|
// Registries for artifact and element names per CSN section
|
|
197
199
|
const definitionsDuplicateChecker = new DuplicateChecker(options.sqlMapping);
|
|
@@ -209,8 +211,9 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
209
211
|
for (const artifactName in csn.deletions)
|
|
210
212
|
renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], mainResultObj);
|
|
211
213
|
|
|
214
|
+
const supportsSqlExtensions = (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'));
|
|
212
215
|
|
|
213
|
-
if (csn.changedPrimaryKeys &&
|
|
216
|
+
if (csn.changedPrimaryKeys && supportsSqlExtensions) {
|
|
214
217
|
csn.changedPrimaryKeys = options.testMode ? sortCsn(csn.changedPrimaryKeys) : csn.changedPrimaryKeys;
|
|
215
218
|
csn.changedPrimaryKeys.forEach((artifactName) => {
|
|
216
219
|
const drop = render.dropKey(artifactName);
|
|
@@ -221,7 +224,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
221
224
|
// Render each artifact extension
|
|
222
225
|
// Only SAP HANA SQL is currently supported.
|
|
223
226
|
// Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
|
|
224
|
-
if (csn.extensions &&
|
|
227
|
+
if (csn.extensions && supportsSqlExtensions) {
|
|
225
228
|
csn.extensions = options.testMode ? sortCsn(csn.extensions) : csn.extensions;
|
|
226
229
|
for (let i = 0; i < csn.extensions.length; ++i) {
|
|
227
230
|
const extension = csn.extensions[i];
|
|
@@ -236,7 +239,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
236
239
|
|
|
237
240
|
// Render each artifact change
|
|
238
241
|
// Only SAP HANA SQL is currently supported.
|
|
239
|
-
if (csn.migrations &&
|
|
242
|
+
if (csn.migrations && supportsSqlExtensions) {
|
|
240
243
|
csn.migrations = options.testMode ? sortCsn(csn.migrations) : csn.migrations;
|
|
241
244
|
for (const migration of csn.migrations) {
|
|
242
245
|
if (migration.migrate) {
|
|
@@ -249,7 +252,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
249
252
|
}
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
if (csn.changedPrimaryKeys &&
|
|
255
|
+
if (csn.changedPrimaryKeys && supportsSqlExtensions) {
|
|
253
256
|
csn.changedPrimaryKeys = options.testMode ? sortCsn(csn.changedPrimaryKeys) : csn.changedPrimaryKeys;
|
|
254
257
|
csn.changedPrimaryKeys.forEach((artifactName) => {
|
|
255
258
|
const add = render.addKey(artifactName, csn.definitions[artifactName].elements);
|
|
@@ -276,6 +279,22 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
276
279
|
mainResultObj.hdbrole[`${sqlServiceName }_access`] = JSON.stringify(accessRole, null, 2);
|
|
277
280
|
});
|
|
278
281
|
|
|
282
|
+
// Can only happen for HDI based deployment
|
|
283
|
+
Object.keys(dummySqlServiceEntities).forEach((sqlServiceName) => {
|
|
284
|
+
const synonym = Object.create(null);
|
|
285
|
+
Object.entries(dummySqlServiceEntities[sqlServiceName]).forEach(([ name ]) => {
|
|
286
|
+
const artName = renderArtifactNameWithoutQuotes(name);
|
|
287
|
+
const dummyArtName = renderArtifactNameWithoutQuotes(`dummy.${ name}`);
|
|
288
|
+
synonym[artName] = {
|
|
289
|
+
target: {
|
|
290
|
+
object: dummyArtName,
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
mainResultObj.hdbsynonym[`${sqlServiceName}`] = JSON.stringify(synonym, null, 2);
|
|
296
|
+
});
|
|
297
|
+
|
|
279
298
|
// trigger artifact and element name checks
|
|
280
299
|
definitionsDuplicateChecker.check(error, options);
|
|
281
300
|
extensionsDuplicateChecker.check(error);
|
|
@@ -340,8 +359,14 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
340
359
|
function renderDefinitionInto( artifactName, art, resultObj, env ) {
|
|
341
360
|
env.path = [ 'definitions', artifactName ];
|
|
342
361
|
// Ignore whole artifacts if forRelationalDB says so
|
|
343
|
-
if (art.abstract || hasValidSkipOrExists(art))
|
|
362
|
+
if (art.abstract || hasValidSkipOrExists(art)) {
|
|
363
|
+
if (art.$dummyService) { // collect entities that are in an external ABAP sql service so we can render the .hdbsynonym later
|
|
364
|
+
dummySqlServiceEntities[art.$dummyService] ??= Object.create(null);
|
|
365
|
+
dummySqlServiceEntities[art.$dummyService][artifactName] = art;
|
|
366
|
+
}
|
|
367
|
+
|
|
344
368
|
return;
|
|
369
|
+
}
|
|
345
370
|
|
|
346
371
|
switch (art.kind) {
|
|
347
372
|
case 'entity':
|
|
@@ -349,6 +374,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
349
374
|
sqlServiceEntities[art.$sqlService] ??= Object.create(null);
|
|
350
375
|
sqlServiceEntities[art.$sqlService][artifactName] = art;
|
|
351
376
|
}
|
|
377
|
+
|
|
352
378
|
if (art.query || art.projection) {
|
|
353
379
|
const result = renderView(artifactName, art, env);
|
|
354
380
|
if (result)
|
|
@@ -587,13 +613,12 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
587
613
|
*/
|
|
588
614
|
function renderEntityInto( artifactName, art, resultObj, env ) {
|
|
589
615
|
const childEnv = env.withIncreasedIndent();
|
|
590
|
-
const hanaTc = art.technicalConfig && art.technicalConfig.hana;
|
|
591
616
|
// tables can have @sql.prepend and @sql.append
|
|
592
617
|
const { front, back } = getSqlSnippets(options, art);
|
|
593
618
|
let result = front;
|
|
594
619
|
// Only SAP HANA has row/column tables
|
|
595
620
|
if (options.sqlDialect === 'hana') {
|
|
596
|
-
if (
|
|
621
|
+
if (art.technicalConfig?.hana?.storeType) {
|
|
597
622
|
// Explicitly specified
|
|
598
623
|
result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
|
|
599
624
|
}
|
|
@@ -607,7 +632,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
607
632
|
result += `TABLE ${tableName}`;
|
|
608
633
|
result += ' (\n';
|
|
609
634
|
result += Object.keys(art.elements)
|
|
610
|
-
.map(eltName => renderElement(eltName, art.elements[eltName], definitionsDuplicateChecker, getFzIndex(eltName,
|
|
635
|
+
.map(eltName => renderElement(eltName, art.elements[eltName], definitionsDuplicateChecker, getFzIndex(eltName, art.technicalConfig?.hana), childEnv))
|
|
611
636
|
.filter(s => s !== '')
|
|
612
637
|
.join(',\n');
|
|
613
638
|
|
|
@@ -643,7 +668,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
643
668
|
// Append table constraints if any
|
|
644
669
|
// 'CONSTRAINT <name> UNIQUE (<column_list>)
|
|
645
670
|
// OR create a unique index for HDI
|
|
646
|
-
const uniqueConstraints = art.$tableConstraints
|
|
671
|
+
const uniqueConstraints = art.$tableConstraints?.unique;
|
|
647
672
|
for (const cn in uniqueConstraints) {
|
|
648
673
|
const constraint = renderUniqueConstraintString(uniqueConstraints[cn], renderArtifactName(`${artifactName}_${cn}`), tableName, quoteSqlId, options);
|
|
649
674
|
if (options.src === 'hdi')
|
|
@@ -670,7 +695,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
670
695
|
// Only HANA has indices
|
|
671
696
|
// FIXME: Really? We should provide a DB-agnostic way to specify that
|
|
672
697
|
if (options.sqlDialect === 'hana')
|
|
673
|
-
renderIndexesInto(art.technicalConfig
|
|
698
|
+
renderIndexesInto(art.technicalConfig?.hana?.indexes, artifactName, resultObj, env);
|
|
674
699
|
|
|
675
700
|
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
676
701
|
result += ` COMMENT ${renderStringForSql(getHanaComment(art), options.sqlDialect)}`;
|
|
@@ -746,7 +771,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
746
771
|
* @returns {object} fzindex for the element
|
|
747
772
|
*/
|
|
748
773
|
function getFzIndex( elemName, hanaTc ) {
|
|
749
|
-
if (!hanaTc
|
|
774
|
+
if (!hanaTc?.fzindexes?.[elemName])
|
|
750
775
|
return undefined;
|
|
751
776
|
|
|
752
777
|
if (Array.isArray(hanaTc.fzindexes[elemName][0])) {
|
|
@@ -823,12 +848,12 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
823
848
|
if (elm.target) {
|
|
824
849
|
result += env.indent;
|
|
825
850
|
if (elm.cardinality) {
|
|
826
|
-
if (isBetaEnabled(options, 'hanaAssocRealCardinality') && elm.cardinality.src
|
|
851
|
+
if (isBetaEnabled(options, 'hanaAssocRealCardinality') && elm.cardinality.src === 1)
|
|
827
852
|
result += 'ONE TO ';
|
|
828
853
|
else
|
|
829
854
|
result += 'MANY TO ';
|
|
830
855
|
|
|
831
|
-
if (elm.cardinality.max
|
|
856
|
+
if (elm.cardinality.max === '*' || Number(elm.cardinality.max) > 1)
|
|
832
857
|
result += 'MANY';
|
|
833
858
|
else
|
|
834
859
|
result += 'ONE';
|
|
@@ -1002,11 +1027,11 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1002
1027
|
function renderJoinCardinality( card ) {
|
|
1003
1028
|
let result = '';
|
|
1004
1029
|
if (card) {
|
|
1005
|
-
if (card.srcmin
|
|
1030
|
+
if (card.srcmin === 1)
|
|
1006
1031
|
result += 'EXACT ';
|
|
1007
|
-
result += card.src
|
|
1032
|
+
result += card.src === 1 ? 'ONE ' : 'MANY ';
|
|
1008
1033
|
result += 'TO ';
|
|
1009
|
-
if (card.min
|
|
1034
|
+
if (card.min === 1)
|
|
1010
1035
|
result += 'EXACT ';
|
|
1011
1036
|
if (card.max)
|
|
1012
1037
|
result += (card.max === 1) ? 'ONE ' : 'MANY ';
|
|
@@ -1080,7 +1105,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1080
1105
|
// the ref is not rendered as { id: ...; args: } but as short form of ref[0] ;)
|
|
1081
1106
|
// An empty actual parameter list is rendered as `()`.
|
|
1082
1107
|
const ref = csn.definitions[path.ref[0].id] || csn.definitions[path.ref[0]];
|
|
1083
|
-
if (ref
|
|
1108
|
+
if (ref?.params) {
|
|
1084
1109
|
result += path.ref[0]?.args
|
|
1085
1110
|
? `(${renderArgs(path.ref[0], '=>', env.withSubPath([ 'ref', 0 ]), syntax)})`
|
|
1086
1111
|
: '()';
|
|
@@ -1156,8 +1181,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1156
1181
|
*/
|
|
1157
1182
|
function renderViewColumn( col, elements, env ) {
|
|
1158
1183
|
let result = '';
|
|
1159
|
-
const leaf = col.as || col.ref
|
|
1160
|
-
if (leaf && elements[leaf]
|
|
1184
|
+
const leaf = col.as || col.ref?.[col.ref.length - 1] || col.func;
|
|
1185
|
+
if (leaf && elements[leaf]?.virtual) {
|
|
1161
1186
|
if (isDeprecatedEnabled(options, '_renderVirtualElements'))
|
|
1162
1187
|
// render a virtual column 'null as <alias>'
|
|
1163
1188
|
result += `${env.indent}NULL AS ${quoteSqlId(col.as || leaf)}`;
|
|
@@ -1182,7 +1207,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1182
1207
|
*/
|
|
1183
1208
|
function renderView( artifactName, art, env ) {
|
|
1184
1209
|
const viewName = renderArtifactName(artifactName);
|
|
1185
|
-
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art
|
|
1210
|
+
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art?.$location, artifactName);
|
|
1186
1211
|
let result = `VIEW ${viewName}`;
|
|
1187
1212
|
|
|
1188
1213
|
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
@@ -1268,9 +1293,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1268
1293
|
// - has an ORDER BY/LIMIT (because UNION etc. can't stand directly behind an ORDER BY)
|
|
1269
1294
|
const argEnv = env.withSubPath([ 'args', index ]);
|
|
1270
1295
|
const queryString = renderQuery( arg, argEnv, elements || query.SET.elements, false);
|
|
1271
|
-
return (arg.SET || arg.SELECT
|
|
1296
|
+
return (arg.SET || arg.SELECT?.orderBy || arg.SELECT?.limit) ? `(${queryString})` : queryString;
|
|
1272
1297
|
})
|
|
1273
|
-
.join(`\n${env.indent}${query.SET.op
|
|
1298
|
+
.join(`\n${env.indent}${query.SET.op?.toUpperCase()}${query.SET.all ? ' ALL ' : ' '}`);
|
|
1299
|
+
|
|
1274
1300
|
// Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
|
|
1275
1301
|
// each SELECT)
|
|
1276
1302
|
// If the whole SET has an ORDER BY/LIMIT, wrap the part before that in parentheses
|
|
@@ -1278,12 +1304,16 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1278
1304
|
// to the last SET argument, not to the whole SET)
|
|
1279
1305
|
if (query.SET.orderBy || query.SET.limit) {
|
|
1280
1306
|
result = `(${result})`;
|
|
1281
|
-
if (query.SET.orderBy)
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1307
|
+
if (query.SET.orderBy) {
|
|
1308
|
+
const orderBy = query.SET.orderBy.map(entry => renderOrderByEntry(entry, env.withSubPath([ 'orderBy' ]))).join(', ');
|
|
1309
|
+
result += `\n${env.indent}ORDER BY ${orderBy}`;
|
|
1310
|
+
}
|
|
1311
|
+
if (query.SET.limit) {
|
|
1312
|
+
const limit = renderLimit(query.SET.limit, env.withSubPath([ 'limit' ]));
|
|
1313
|
+
result += `\n${env.indent}${limit}`;
|
|
1314
|
+
}
|
|
1286
1315
|
}
|
|
1316
|
+
|
|
1287
1317
|
return result;
|
|
1288
1318
|
}
|
|
1289
1319
|
// Otherwise must have a SELECT
|
|
@@ -1332,7 +1362,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1332
1362
|
* @returns {string|undefined} Id of first path step
|
|
1333
1363
|
*/
|
|
1334
1364
|
function firstPathStepId( ref ) {
|
|
1335
|
-
return
|
|
1365
|
+
return (ref?.[0]?.id || ref?.[0]);
|
|
1336
1366
|
}
|
|
1337
1367
|
|
|
1338
1368
|
/**
|
|
@@ -1431,7 +1461,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1431
1461
|
*/
|
|
1432
1462
|
function renderBuiltinType( typeName ) {
|
|
1433
1463
|
const types = cdsToSqlTypes[options.sqlDialect];
|
|
1434
|
-
const result = types
|
|
1464
|
+
const result = types?.[typeName] || cdsToSqlTypes.standard[typeName];
|
|
1435
1465
|
if (!result && options.testMode)
|
|
1436
1466
|
throw new CompilerAssertion(`Expected to find a type mapping for ${typeName}`);
|
|
1437
1467
|
return result || 'CHAR';
|