@sap/cds-compiler 5.7.4 → 5.8.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +54 -2
  2. package/bin/cdsse.js +13 -1
  3. package/doc/CHANGELOG_BETA.md +7 -0
  4. package/lib/api/options.js +2 -1
  5. package/lib/api/validate.js +9 -0
  6. package/lib/base/message-registry.js +55 -20
  7. package/lib/base/messages.js +5 -2
  8. package/lib/base/model.js +4 -1
  9. package/lib/checks/assocOutsideService.js +40 -0
  10. package/lib/checks/featureFlags.js +4 -1
  11. package/lib/checks/types.js +7 -4
  12. package/lib/checks/validator.js +3 -0
  13. package/lib/compiler/assert-consistency.js +11 -5
  14. package/lib/compiler/checks.js +79 -17
  15. package/lib/compiler/define.js +57 -3
  16. package/lib/compiler/extend.js +1 -2
  17. package/lib/compiler/generate.js +1 -1
  18. package/lib/compiler/populate.js +17 -6
  19. package/lib/compiler/propagator.js +1 -1
  20. package/lib/compiler/resolve.js +181 -150
  21. package/lib/compiler/shared.js +276 -22
  22. package/lib/compiler/tweak-assocs.js +15 -4
  23. package/lib/compiler/xpr-rewrite.js +76 -50
  24. package/lib/edm/annotations/edmJson.js +1 -1
  25. package/lib/edm/annotations/genericTranslation.js +2 -2
  26. package/lib/edm/csn2edm.js +2 -2
  27. package/lib/edm/edmPreprocessor.js +15 -9
  28. package/lib/edm/edmUtils.js +12 -5
  29. package/lib/gen/CdlGrammar.checksum +1 -0
  30. package/lib/gen/CdlParser.js +2234 -2233
  31. package/lib/gen/Dictionary.json +55 -8
  32. package/lib/json/from-csn.js +37 -17
  33. package/lib/json/to-csn.js +4 -0
  34. package/lib/language/genericAntlrParser.js +7 -0
  35. package/lib/main.d.ts +5 -0
  36. package/lib/model/cloneCsn.js +1 -0
  37. package/lib/model/csnRefs.js +1 -0
  38. package/lib/model/csnUtils.js +0 -5
  39. package/lib/modelCompare/utils/filter.js +2 -2
  40. package/lib/optionProcessor.js +2 -0
  41. package/lib/parsers/AstBuildingParser.js +47 -17
  42. package/lib/parsers/CdlGrammar.g4 +10 -12
  43. package/lib/parsers/XprTree.js +206 -0
  44. package/lib/render/toCdl.js +61 -89
  45. package/lib/render/toSql.js +59 -29
  46. package/lib/render/utils/standardDatabaseFunctions.js +252 -15
  47. package/lib/transform/addTenantFields.js +9 -3
  48. package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
  49. package/lib/transform/db/assocsToQueries/utils.js +10 -3
  50. package/lib/transform/db/expansion.js +3 -1
  51. package/lib/transform/db/flattening.js +7 -3
  52. package/lib/transform/db/killAnnotations.js +1 -0
  53. package/lib/transform/db/processSqlServices.js +70 -17
  54. package/lib/transform/draft/db.js +8 -3
  55. package/lib/transform/draft/odata.js +27 -4
  56. package/lib/transform/effective/main.js +37 -10
  57. package/lib/transform/effective/misc.js +4 -9
  58. package/lib/transform/effective/service.js +34 -0
  59. package/lib/transform/effective/types.js +28 -17
  60. package/lib/transform/forOdata.js +36 -10
  61. package/lib/transform/forRelationalDB.js +30 -18
  62. package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
  63. package/lib/transform/odata/createForeignKeys.js +120 -116
  64. package/lib/transform/odata/flattening.js +10 -8
  65. package/lib/transform/transformUtils.js +58 -25
  66. package/lib/transform/translateAssocsToJoins.js +10 -6
  67. package/lib/transform/universalCsn/coreComputed.js +5 -1
  68. package/package.json +1 -1
  69. package/share/messages/message-explanations.json +1 -0
  70. package/share/messages/rewrite-not-supported.md +5 -0
  71. 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
+ };
@@ -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 = renderTypeOrAnnotation(name, anno, env, 'annotation');
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 renderEntity(artifactName, art, env);
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 renderTypeOrAnnotation(artifactName, art, env);
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 renderEvent(artifactName, art, env);
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 renderEvent( artifactName, art, env ) {
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}event ${normalizedArtifactName}`;
488
- if (art.includes)
489
- result += renderIncludes(art.includes, env);
490
- if (art.query || art.projection) {
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 if (art.type) {
498
- // Derived type or annotation with non-anonymous type
499
- result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
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
- else if (art.elements) {
502
- result += ` ${renderElements(art, env)};\n`;
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 && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
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 (!obj.ref && obj.as)
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>`.
@@ -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 && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
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 && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
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 && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
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 && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
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 (hanaTc && hanaTc.storeType) {
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, hanaTc), childEnv))
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 && art.$tableConstraints.unique;
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 && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
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 || !hanaTc.fzindexes || !hanaTc.fzindexes[elemName])
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 && elm.cardinality.src === 1)
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 && (elm.cardinality.max === '*' || Number(elm.cardinality.max) > 1))
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 && card.srcmin === 1)
1030
+ if (card.srcmin === 1)
1006
1031
  result += 'EXACT ';
1007
- result += card.src && card.src === 1 ? 'ONE ' : 'MANY ';
1032
+ result += card.src === 1 ? 'ONE ' : 'MANY ';
1008
1033
  result += 'TO ';
1009
- if (card.min && card.min === 1)
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 && ref.params) {
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 && col.ref[col.ref.length - 1] || col.func;
1160
- if (leaf && elements[leaf] && elements[leaf].virtual) {
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 && art.$location, artifactName);
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 && (arg.SELECT.orderBy || arg.SELECT.limit)) ? `(${queryString})` : queryString;
1296
+ return (arg.SET || arg.SELECT?.orderBy || arg.SELECT?.limit) ? `(${queryString})` : queryString;
1272
1297
  })
1273
- .join(`\n${env.indent}${query.SET.op && query.SET.op.toUpperCase()}${query.SET.all ? ' ALL ' : ' '}`);
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
- result += `\n${env.indent}ORDER BY ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env.withSubPath([ 'orderBy' ]))).join(', ')}`;
1283
-
1284
- if (query.SET.limit)
1285
- result += `\n${env.indent}${renderLimit(query.SET.limit, env.withSubPath([ 'limit' ]))}`;
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 ref && ref[0] && (ref[0].id || ref[0]);
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 && types[typeName] || cdsToSqlTypes.standard[typeName];
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';