@sap/cds-compiler 4.0.2 → 4.2.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -7,9 +7,9 @@ const { cloneCsnNonDict,
7
7
  isAspect, walkCsnPath, isPersistedOnDatabase,
8
8
  } = require('../model/csnUtils');
9
9
  const { makeMessageFunction } = require('../base/messages');
10
- const transformUtils = require('./transformUtilsNew');
10
+ const transformUtils = require('./transformUtils');
11
11
  const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
12
- const { csnRefs, pathId, traverseQuery } = require('../model/csnRefs');
12
+ const { csnRefs, pathId, traverseQuery, columnAlias} = require('../model/csnRefs');
13
13
  const { checkCSNVersion } = require('../json/csnVersion');
14
14
  const validate = require('../checks/validator');
15
15
  const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
@@ -26,7 +26,7 @@ const expansion = require('./db/expansion');
26
26
  const assertUnique = require('./db/assertUnique');
27
27
  const generateDrafts = require('./draft/db');
28
28
  const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
29
- const { getViewTransformer } = require('./db/views');
29
+ const { getViewTransformer, ensureColumnNames } = require('./db/views');
30
30
  const cdsPersistence = require('./db/cdsPersistence');
31
31
  const temporal = require('./db/temporal');
32
32
  const associations = require('./db/associations')
@@ -98,17 +98,17 @@ function forEachDefinition(csn, cb) {
98
98
  * - (250) Remove name space definitions again (only in forRelationalDB). Maybe we can omit inserting namespace definitions
99
99
  * completely (TODO)
100
100
  *
101
- * @param {CSN.Model} inputModel
101
+ * @param {CSN.Model} csn
102
102
  * @param {CSN.Options} options
103
103
  * @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
104
104
  */
105
- function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
105
+ function transformForRelationalDBWithCsn(csn, options, moduleName) {
106
106
  // copy the model as we don't want to change the input model
107
107
  timetrace.start('HANA transformation');
108
108
 
109
109
  timetrace.start('Clone CSN');
110
110
  /** @type {CSN.Model} */
111
- let csn = cloneCsnNonDict(inputModel, options);
111
+ csn = cloneCsnNonDict(csn, options);
112
112
  timetrace.stop('Clone CSN');
113
113
 
114
114
  checkCSNVersion(csn, options);
@@ -117,20 +117,13 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
117
117
  // There is also an explicit default length via options.defaultStringLength
118
118
  const implicitDefaultLengths = getDefaultTypeLengths(options.sqlDialect);
119
119
 
120
+ /** @type {object} */
120
121
  let csnUtils;
122
+ /** @type {object} */
123
+ let messageFunctions;
121
124
  let message, error, warning, info; // message functions
122
125
  /** @type {() => void} */
123
126
  let throwWithAnyError;
124
- // csnUtils
125
- let artifactRef,
126
- inspectRef,
127
- effectiveType,
128
- initDefinition,
129
- dropDefinitionCache,
130
- get$combined,
131
- getCsnDef,
132
- isAssocOrComposition,
133
- addStringAnnotationTo;
134
127
  // transformUtils
135
128
  let addDefaultTypeFacets,
136
129
  expandStructsInExpression,
@@ -148,6 +141,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
148
141
  bindCsnReference();
149
142
  }
150
143
 
144
+ ensureColumnNames(csn, options, csnUtils);
145
+
151
146
  const dialect = options.sqlDialect;
152
147
  const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
153
148
  if (!doA2J)
@@ -159,14 +154,16 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
159
154
  timetrace.start('Validate');
160
155
  // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
161
156
  const cleanup = validate.forRelationalDB(csn, {
162
- message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils, csn, options, isAspect
157
+ ...messageFunctions, csnUtils, ...csnUtils, csn, options, isAspect
163
158
  });
164
159
  timetrace.stop('Validate');
165
160
 
166
- rewriteCalculatedElementsInViews(csn, options, pathDelimiter, error);
161
+ rewriteCalculatedElementsInViews(csn, options, csnUtils, pathDelimiter, messageFunctions);
167
162
 
168
163
  // Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
169
- handleExists(csn, options, error, inspectRef, initDefinition, dropDefinitionCache);
164
+ handleExists(csn, options, error, csnUtils.inspectRef, csnUtils.initDefinition, csnUtils.dropDefinitionCache);
165
+
166
+ doA2J && flattening.linkForeignKeyAnnotationExtensionsToAssociation(csn, options);
170
167
 
171
168
  // Check if structured elements and managed associations are compared in an expression
172
169
  // and expand these structured elements. This tuple expansion allows all other
@@ -179,19 +176,17 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
179
176
 
180
177
  throwWithAnyError();
181
178
 
182
- const transformCsn = transformUtils.transformModel;
183
-
184
179
  forEachDefinition(csn, [
185
180
  // (001) Add a temporal where condition to views where applicable before assoc2join
186
181
  // assoc2join eventually rewrites the table aliases
187
- temporal.getViewDecorator(csn, {info}, csnUtils),
182
+ temporal.getViewDecorator(csn, messageFunctions, csnUtils),
188
183
  // check unique constraints - further processing is done in rewriteUniqueConstraints
189
184
  assertUnique.prepare(csn, options, error, info)
190
185
  ]);
191
186
 
192
187
  if(doA2J) {
193
188
  // Expand a structured thing in: keys, columns, order by, group by
194
- expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithAnyError}, csnUtils);
189
+ expansion.expandStructureReferences(csn, options, pathDelimiter, messageFunctions, csnUtils);
195
190
  bindCsnReference();
196
191
  }
197
192
 
@@ -217,7 +212,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
217
212
  flattening.flattenElements(csn, options, pathDelimiter, error);
218
213
  } else {
219
214
  // For to.hdbcds with naming mode hdbcds we also need to resolve the types
220
- flattening.resolveTypeReferences(csn, options, undefined, pathDelimiter);
215
+ flattening.resolveTypeReferences(csn, options, new WeakMap(), pathDelimiter);
221
216
  }
222
217
 
223
218
  // (010) If requested, translate associations to joins
@@ -227,7 +222,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
227
222
  bindCsnReference();
228
223
 
229
224
  const redoProjections = [];
230
- // Use the "raw" forEachDefinition here to ensure that the _ignore takes effect
225
+ // Use the "raw" forEachDefinition here to ensure that the $ignore takes effect
231
226
  _forEachDefinition(csn, (artifact) => {
232
227
  if(artifact.kind === 'entity' && artifact.projection) {
233
228
  artifact.query = { SELECT: artifact.projection };
@@ -242,8 +237,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
242
237
  }
243
238
  })
244
239
  } else if(artifact.kind === 'annotation' || artifact.kind === 'action' || artifact.kind === 'function' || artifact.kind === 'event'){
245
- // _ignore actions etc. - this loop seemed handy for this, as we can hook into an existing if
246
- artifact._ignore = true;
240
+ // $ignore actions etc. - this loop seemed handy for this, as we can hook into an existing if
241
+ artifact.$ignore = true;
247
242
  }
248
243
  });
249
244
 
@@ -251,20 +246,30 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
251
246
 
252
247
  timetrace.start('Transform CSN')
253
248
 
254
- // (000) Rename primitive types, make UUID a String
255
- transformCsn(csn, {
256
- type: (val, node, key) => {
257
- renamePrimitiveTypesAndUuid(val, node, key);
258
- addDefaultTypeFacets(node, implicitDefaultLengths);
259
- },
249
+ // Rename primitive types, make UUID a String; replace `items` by cds.LargeString
250
+ //
251
+ // First, gather all nodes that are arrayed: Don't replace inline, or getFinalTypeInfo()
252
+ // may not return `.items` for types that were already processed.
253
+ {
254
+ const removeItems = new Set();
255
+ applyTransformations(csn, {
256
+ type: (node) => {
257
+ if (node.items || node.type && csnUtils.getFinalTypeInfo(node.type)?.items)
258
+ removeItems.add(node);
259
+ renamePrimitiveTypesAndUuid(node.type, node, 'type');
260
+ addDefaultTypeFacets(node, implicitDefaultLengths);
261
+ },
262
+ items: (node) => removeItems.add(node),
263
+ });
260
264
  // no support for array-of - turn into CLOB/Text
261
265
  // must be done after A2J or compiler checks could change
262
266
  // (e.g. annotation def checks for arrayed types)
263
- items: (val, node) => {
267
+ for (const node of removeItems) {
264
268
  node.type = 'cds.LargeString';
265
269
  delete node.items;
266
- },
267
- }, true);
270
+ }
271
+ removeItems.clear();
272
+ }
268
273
 
269
274
  forEachDefinition(csn, [
270
275
  // (040) Ignore entities and views that are abstract or implemented
@@ -273,7 +278,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
273
278
  cdsPersistence.getAnnoProcessor(),
274
279
  // (050) Check @cds.valid.from/to only on entity
275
280
  // Views are checked in (001), unbalanced valid.from/to's or mismatching origins
276
- temporal.getAnnotationHandler(csn, options, pathDelimiter, {error})
281
+ temporal.getAnnotationHandler(csn, options, pathDelimiter, messageFunctions)
277
282
  ]);
278
283
 
279
284
  // eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
@@ -285,7 +290,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
285
290
 
286
291
  // (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
287
292
  // and make them entities
288
- forEachDefinition(csn, cdsPersistence.getPersistenceTableProcessor(csn, options, {error}));
293
+ forEachDefinition(csn, cdsPersistence.getPersistenceTableProcessor(csn, options, messageFunctions));
289
294
 
290
295
  // Allow using managed associations as steps in on-conditions to access their fks
291
296
  // To be done after handleAssociations, since then the foreign keys of the managed assocs
@@ -341,19 +346,19 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
341
346
  createReferentialConstraints(csn, options);
342
347
 
343
348
  // no constraints for drafts
344
- generateDrafts(csn, options, pathDelimiter, { info, warning, error });
349
+ generateDrafts(csn, options, pathDelimiter, messageFunctions);
345
350
 
346
351
  // Set the final constraint paths and produce hana tc indexes if required
347
352
  // See function comment for extensive information.
348
353
  assertUnique.rewrite(csn, options, pathDelimiter);
349
354
 
350
355
  // Associations that point to things marked with @cds.persistence.skip are removed
351
- forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, {info}, csnUtils));
356
+ forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, messageFunctions, csnUtils));
352
357
 
353
358
  // Apply view-specific transformations
354
359
  // (160) Projections now finally become views
355
360
  // Replace managed association in group/order by with foreign keys
356
- const transformEntityOrViewPass2 = getViewTransformer(csn, options, {error, info}, transformCommon);
361
+ const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions, transformCommon);
357
362
  forEachDefinition(csn, transformViews);
358
363
 
359
364
  // Recursively apply transformCommon and attach @cds.persistence.name
@@ -384,14 +389,14 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
384
389
 
385
390
  const killers = {
386
391
  // Used to ignore actions etc from processing and remove associations/elements
387
- '_ignore': function (parent, a, b, path){
392
+ '$ignore': function (parent, a, b, path){
388
393
  if(path.length > 2) {
389
394
  const tail = path[path.length-1];
390
395
  const parentPath = path.slice(0, -1)
391
396
  const parentParent = walkCsnPath(csn, parentPath);
392
397
  delete parentParent[tail];
393
398
  } else {
394
- delete parent._ignore;
399
+ delete parent.$ignore;
395
400
  }
396
401
  },
397
402
  // Still used in flattenStructuredElements - in db/flattening.js
@@ -441,7 +446,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
441
446
  /* ----------------------------------- Functions start here -----------------------------------------------*/
442
447
 
443
448
  function bindCsnReference(){
444
- ({ error, warning, info, message, throwWithAnyError } = makeMessageFunction(csn, options, moduleName));
449
+ messageFunctions = makeMessageFunction(csn, options, moduleName);
450
+ ({ error, warning, info, message, throwWithAnyError } = messageFunctions);
445
451
 
446
452
  ({ flattenStructuredElement,
447
453
  flattenStructStepsInRef,
@@ -451,94 +457,85 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
451
457
  expandStructsInExpression,
452
458
  csnUtils
453
459
  } = transformUtils.getTransformers(csn, options, pathDelimiter));
454
-
455
- ({ artifactRef,
456
- inspectRef,
457
- effectiveType,
458
- initDefinition,
459
- dropDefinitionCache,
460
- get$combined,
461
- getCsnDef,
462
- isAssocOrComposition,
463
- addStringAnnotationTo,
464
- } = csnUtils);
465
460
  }
466
461
 
467
462
  function bindCsnReferenceOnly(){
468
463
  // invalidate caches for CSN ref API
469
- ({ artifactRef, inspectRef, effectiveType, initDefinition, dropDefinitionCache } = csnRefs(csn));
464
+ const csnRefApi = csnRefs(csn);
465
+ Object.assign(csnUtils, csnRefApi);
470
466
  }
471
467
 
468
+ // For non-A2J only
472
469
  function handleMixinOnConditions(artifact, artifactName) {
473
- if (!artifact.query)
470
+ if (!artifact.query) // projections can't have mixins
474
471
  return;
475
472
  forAllQueries(artifact.query, (query, path) => {
476
- const { mixin } = query.SELECT || {};
477
- if(mixin) {
473
+ const { mixin } = query.SELECT || {};
474
+ if (mixin) {
478
475
  query.SELECT.columns
479
- // filter for associations which are used in the SELECT
480
- .filter((c) => {
481
- return c.ref && c.ref.length > 1;
482
- })
483
- .forEach((usedAssoc) => {
484
- const assocName = pathId(usedAssoc.ref[0]);
485
- const mixinAssociation = mixin[assocName];
486
- if(mixinAssociation){
487
- mixinAssociation.on = getResolvedMixinOnCondition(csn, mixinAssociation, query, assocName, path.concat(['mixin', assocName]));
488
- }
489
- })
476
+ // filter for associations which are used in the SELECT
477
+ .filter((c) => {
478
+ return c.ref && c.ref.length > 1;
479
+ })
480
+ .forEach((usedAssoc) => {
481
+ const assocName = pathId(usedAssoc.ref[0]);
482
+ const mixinAssociation = mixin[assocName];
483
+ if (mixinAssociation)
484
+ mixinAssociation.on = getResolvedMixinOnCondition(mixinAssociation, query, assocName, path.concat(['mixin', assocName]));
485
+ })
490
486
  }
491
- }, [ 'definitions', artifactName, 'query' ]);
492
-
493
- function getResolvedMixinOnCondition(csn, mixinAssociation, query, assocName, path){
494
- const { inspectRef } = csnRefs(csn);
495
- const referencedThroughStar = query.SELECT.columns.some((column) => column === '*');
496
- return mixinAssociation.on
497
- .map((onConditionPart, i) => {
498
- let columnToReplace;
499
- if(onConditionPart.ref && (onConditionPart.ref[0] === '$projection' || onConditionPart.ref[0] === '$self')){
500
- const { links } = inspectRef(path.concat(['on', i]));
501
- if(links){
502
- columnToReplace = onConditionPart.ref[links.length - 1];
503
- }
504
- }
505
- if (!columnToReplace)
506
- return onConditionPart;
507
-
508
- const replaceWith = query.SELECT.columns.find((column) =>
509
- column.as && column.as === columnToReplace ||
510
- column.ref && column.ref[0] === columnToReplace
511
- );
512
- if (!replaceWith && referencedThroughStar) {
513
- // not explicitly in column list, check query sources
514
- // get$combined also includes elements which are part of "excluding {}"
515
- // this shouldn't be an issue here, as such references get rejected
516
- const elementsOfQuerySources = get$combined(query);
517
- forEach(elementsOfQuerySources, (id, element) => {
518
- // if the ref points to an element which is not explicitly exposed in the column list,
519
- // but through the '*' operator -> replace the $projection / $self with the correct source entity
520
- if(id === columnToReplace)
521
- onConditionPart.ref[0] = element[0].parent;
522
- });
523
- }
487
+ }, ['definitions', artifactName, 'query']);
488
+ }
524
489
 
525
- // No implicit CAST in on-condition
526
- if(replaceWith && replaceWith.cast) {
527
- const clone = cloneCsnNonDict(replaceWith, options);
528
- delete clone.cast;
529
- return clone;
530
- }
531
- return replaceWith || onConditionPart;
532
- });
490
+ // For non-A2J only
491
+ function getResolvedMixinOnCondition(mixinAssociation, query, assocName, path) {
492
+ const referencedThroughStar = query.SELECT.columns.some((column) => column === '*');
493
+ return mixinAssociation.on.map(handeMixinOnConditionPart);
494
+
495
+ function handeMixinOnConditionPart(onConditionPart, i) {
496
+ let columnToReplace;
497
+ if (onConditionPart.ref && (onConditionPart.ref[0] === '$projection' || onConditionPart.ref[0] === '$self')){
498
+ const { links } = csnUtils.inspectRef(path.concat(['on', i]));
499
+ if (links) {
500
+ columnToReplace = onConditionPart.ref[links.length - 1];
501
+ }
502
+ }
503
+ if (!columnToReplace)
504
+ return onConditionPart;
505
+
506
+ const replaceWith = query.SELECT.columns.find(col => columnAlias(col) === columnToReplace);
507
+ if (!replaceWith && referencedThroughStar) {
508
+ // not explicitly in column list, check query sources
509
+ // get$combined also includes elements which are part of "excluding {}"
510
+ // this shouldn't be an issue here, as such references get rejected
511
+ const elementsOfQuerySources = csnUtils.get$combined(query);
512
+ forEach(elementsOfQuerySources, (id, element) => {
513
+ // if the ref points to an element which is not explicitly exposed in the column list,
514
+ // but through the '*' operator -> replace the $projection / $self with the correct source entity
515
+ if(id === columnToReplace)
516
+ onConditionPart.ref[0] = element[0].parent;
517
+ });
518
+ return onConditionPart;
519
+ }
520
+ else if (replaceWith) {
521
+ const clone = cloneCsnNonDict(replaceWith, options);
522
+ delete clone.cast; // No implicit CAST in on-condition
523
+ delete clone.as;
524
+ return clone;
525
+ }
526
+ else {
527
+ return onConditionPart
528
+ }
533
529
  }
534
530
  }
535
531
 
532
+
536
533
  /**
537
534
  * @param {CSN.Artifact} artifact
538
535
  * @param {string} artifactName
539
536
  */
540
537
  function transformViews(artifact, artifactName) {
541
- if (!artifact._ignore) {
538
+ if (!artifact.$ignore) {
542
539
  // Do things specific for entities and views (pass 2)
543
540
  if ((artifact.kind === 'entity') && artifact.query) {
544
541
 
@@ -557,7 +554,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
557
554
 
558
555
  const process = (parent, prop, query, path) => {
559
556
  transformEntityOrViewPass2(parent, artifact, artifactName, path.concat(prop))
560
- replaceAssociationsInGroupByOrderBy(parent, options, inspectRef, error, path.concat(prop));
557
+ replaceAssociationsInGroupByOrderBy(parent, options, csnUtils.inspectRef, error, path.concat(prop));
561
558
  return query;
562
559
  }
563
560
  applyTransformationsOnNonDictionary(csn.definitions, artifactName, {
@@ -572,9 +569,9 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
572
569
  * @param {string} artifactName
573
570
  */
574
571
  function recursivelyApplyCommon(artifact, artifactName) {
575
- if (!artifact._ignore) {
572
+ if (!artifact.$ignore) {
576
573
  if (artifact.kind !== 'service' && artifact.kind !== 'context')
577
- addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
574
+ csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
578
575
 
579
576
  forEachMemberRecursively(artifact, (member, memberName, property, path) => {
580
577
  if (property === 'returns')
@@ -584,7 +581,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
584
581
  // Virtual elements in entities and types are not annotated, as they have no DB representation.
585
582
  // In views they are, as we generate a null expression for them (null as <colname>)
586
583
  if ((!member.virtual || artifact.query))
587
- addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member);
584
+ csnUtils.addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member);
588
585
  }, [ 'definitions', artifactName ]);
589
586
  }
590
587
  }
@@ -594,7 +591,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
594
591
  * @param {string} artifactName
595
592
  */
596
593
  function removeKeyPropInType(artifact, artifactName) {
597
- if (!artifact._ignore && artifact.kind === 'type') {
594
+ if (!artifact.$ignore && artifact.kind === 'type') {
598
595
  forEachMemberRecursively(artifact, (member) => {
599
596
  if (member.key)
600
597
  delete member.key;
@@ -618,7 +615,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
618
615
  function doit(dict, subPath) {
619
616
  for (const elemName in dict) {
620
617
  const elem = dict[elemName];
621
- if (elem.on && isAssocOrComposition(elem))
618
+ if (elem.on && csnUtils.isAssocOrComposition(elem))
622
619
  processBacklinkAssoc(elem, elemName, artifact, artifactName, subPath.concat([ elemName, 'on' ]));
623
620
  }
624
621
  }
@@ -641,7 +638,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
641
638
  // For HANA: Report an error on
642
639
  // - view with parameters that has an element of type association/composition
643
640
  // - association that points to entity with parameters
644
- if (options.sqlDialect === 'hana' && member.target && isAssocOrComposition(member) && !isBetaEnabled(options, 'assocsWithParams')) {
641
+ if (options.sqlDialect === 'hana' && member.target && csnUtils.isAssocOrComposition(member) && !isBetaEnabled(options, 'assocsWithParams')) {
645
642
  if (artifact.params) {
646
643
  // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
647
644
  // SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
@@ -676,7 +673,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
676
673
  * @param {string} artifactName
677
674
  */
678
675
  function handleChecksForWithParameters(artifact, artifactName) {
679
- if (!artifact._ignore && artifact.params && (artifact.kind === 'entity')) {
676
+ if (!artifact.$ignore && artifact.params && (artifact.kind === 'entity')) {
680
677
  if (!artifact.query) { // table entity with params
681
678
  // Allow with plain
682
679
  error(null, [ 'definitions', artifactName ], { '#': options.toSql ? 'sql' : 'std' }, {
@@ -707,19 +704,38 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
707
704
  throwWithAnyError();
708
705
  // the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
709
706
  // simply make it invisible and copy it over to the result csn
710
- forEachDefinition(csn, art => art.technicalConfig && setProp(art, 'technicalConfig', art.technicalConfig));
707
+ forEachDefinition(csn,
708
+ art => art.technicalConfig && setProp(art, 'technicalConfig',
709
+ art.technicalConfig));
711
710
 
712
711
  const newCsn = translateAssocsToJoinsCSN(csn, options);
713
712
 
714
713
  // restore all (non-enumerable) properties that wouldn't survive reaugmentation/compactification into the new compact model
715
714
  forEachDefinition(csn, (art, artName) => {
716
715
  if(art['$tableConstraints']) {
717
- setProp(newCsn.definitions[artName], '$tableConstraints', art['$tableConstraints']);
716
+ newCsn.definitions[artName].$tableConstraints = art['$tableConstraints'];
718
717
  }
719
718
  if (art.technicalConfig)
720
719
  newCsn.definitions[artName].technicalConfig = art.technicalConfig;
721
720
 
722
721
  });
722
+ // restore $fkExtensions and $structRef for foreign key annotations
723
+ if (isBetaEnabled(options, 'annotateForeignKeys')) {
724
+ forEachDefinition(csn, (oldDef, artName) => {
725
+ const newDef = newCsn.definitions[artName];
726
+ if(oldDef?.elements) {
727
+ Object.entries(oldDef.elements).forEach(([eltName, oldElt]) => {
728
+ const newElt = newDef.elements[eltName];
729
+ if(oldElt.$fkExtensions)
730
+ setProp(newElt, '$fkExtensions', oldElt.$fkExtensions);
731
+ oldElt.keys?.forEach((fk, i) => {
732
+ if(fk.$structRef && newElt.keys?.[i])
733
+ setProp(newElt.keys[i], '$structRef', fk.$structRef);
734
+ })
735
+ })
736
+ }
737
+ });
738
+ }
723
739
  csn = newCsn;
724
740
  }
725
741
 
@@ -754,11 +770,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
754
770
  function renamePrimitiveTypesAndUuid(val, node, key) {
755
771
  // assert key === 'type'
756
772
  const hanaNamesMap = createDict({
757
- 'cds.DateTime': 'cds.UTCDateTime',
758
- 'cds.Timestamp': 'cds.UTCTimestamp',
759
- 'cds.Date': 'cds.LocalDate',
760
- 'cds.Time': 'cds.LocalTime',
761
- 'cds.UUID': 'cds.String',
773
+ 'cds.UUID': 'cds.String'
762
774
  });
763
775
  node[key] = hanaNamesMap[val] || val;
764
776
  if (val === 'cds.UUID' && !node.length) {
@@ -790,7 +802,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
790
802
  if (elem.default && elem.default['#']) {
791
803
  let Enum = elem.enum;
792
804
  if (!Enum && !isBuiltinType(elem.type)) {
793
- const typeDef = getCsnDef(elem.type);
805
+ const typeDef = csnUtils.getCsnDef(elem.type);
794
806
  Enum = typeDef && typeDef.enum;
795
807
  }
796
808
  if (!Enum) {
@@ -852,7 +864,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
852
864
  // Check if one side is $self and the other an association
853
865
  // (if so, replace all three tokens with the condition generated from the other side, in parentheses)
854
866
  if (isDollarSelfOrProjectionOperand(xprArgs[i]) && isAssociationOperand(xprArgs[i + 2], path.concat([ i + 2 ]))) {
855
- const assoc = inspectRef(path.concat([ i + 2 ])).art;
867
+ const assoc = csnUtils.inspectRef(path.concat([ i + 2 ])).art;
856
868
  if (multipleExprs)
857
869
  result.push('(');
858
870
  const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
@@ -867,7 +879,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
867
879
  attachBacklinkInformation(backlinkName);
868
880
  }
869
881
  else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
870
- const assoc = inspectRef(path.concat([ i ])).art;
882
+ const assoc = csnUtils.inspectRef(path.concat([ i ])).art;
871
883
  if (multipleExprs)
872
884
  result.push('(');
873
885
  const backlinkName = xprArgs[i].ref[xprArgs[i].ref.length - 1];
@@ -943,7 +955,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
943
955
  if(assoc.keys.length)
944
956
  return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName);
945
957
  else {
946
- elem._ignore = true;
958
+ elem.$ignore = true;
947
959
  return [];
948
960
  }
949
961
  }
@@ -1034,10 +1046,10 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
1034
1046
  function checkTypeParameters(artifact, artifactName) {
1035
1047
  forEachMemberRecursively(artifact, (member, memberName, prop, path) => {
1036
1048
  // Check type parameters (length, precision, scale ...)
1037
- if (!member._ignore && member.type)
1049
+ if (!member.$ignore && member.type)
1038
1050
  _check(member, memberName, csn, path);
1039
1051
 
1040
- if (!member._ignore && member.items && member.items.type)
1052
+ if (!member.$ignore && member.items && member.items.type)
1041
1053
  _check(member.items, memberName, csn, path.concat([ 'items' ]));
1042
1054
  }, [ 'definitions', artifactName ]);
1043
1055
 
@@ -1079,7 +1091,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
1079
1091
  // (which can currently only be 'positiveInteger') and (optional) the value is in a given range
1080
1092
  function checkTypeParamValue(node, paramName, range = null, path = null) {
1081
1093
  const paramValue = node[paramName];
1082
- if (paramValue === undefined || paramValue === null) {
1094
+ if (paramValue == null) {
1083
1095
  if(options.toSql || artifact.query || !['cds.Binary','cds.hana.BINARY', 'cds.hana.NCHAR','cds.hana.CHAR'].includes(node.type)) {
1084
1096
  return true;
1085
1097
  } else {
@@ -1134,7 +1146,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
1134
1146
  if (typeof val === 'object' && val.ref) {
1135
1147
  // Replace a reference by references to it's elements, if it is structured
1136
1148
  const path = [ 'definitions', artName, 'technicalConfig', dialect, 'indexes', name, idx ];
1137
- const { art } = inspectRef(path);
1149
+ const { art } = csnUtils.inspectRef(path);
1138
1150
  if (!art) {
1139
1151
  // A reference that has no artifact (e.g. the reference to the index name itself). Just copy it over
1140
1152
  flattenedIndex.push(val);
@@ -1176,8 +1188,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
1176
1188
  }
1177
1189
  }
1178
1190
  }
1179
- }
1180
1191
 
1192
+ }
1181
1193
 
1182
1194
  module.exports = {
1183
1195
  transformForRelationalDBWithCsn,