@sap/cds-compiler 2.13.6 → 2.15.4

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 (78) hide show
  1. package/CHANGELOG.md +128 -4
  2. package/bin/cdsc.js +112 -37
  3. package/lib/api/main.js +20 -22
  4. package/lib/api/options.js +2 -3
  5. package/lib/api/validate.js +6 -6
  6. package/lib/base/message-registry.js +92 -17
  7. package/lib/base/messages.js +85 -64
  8. package/lib/base/optionProcessorHelper.js +19 -0
  9. package/lib/checks/annotationsOData.js +11 -32
  10. package/lib/checks/arrayOfs.js +1 -34
  11. package/lib/checks/validator.js +2 -4
  12. package/lib/compiler/assert-consistency.js +1 -0
  13. package/lib/compiler/base.js +1 -0
  14. package/lib/compiler/builtins.js +11 -0
  15. package/lib/compiler/checks.js +22 -70
  16. package/lib/compiler/define.js +59 -11
  17. package/lib/compiler/extend.js +20 -3
  18. package/lib/compiler/finalize-parse-cdl.js +26 -20
  19. package/lib/compiler/index.js +75 -26
  20. package/lib/compiler/populate.js +6 -5
  21. package/lib/compiler/propagator.js +4 -1
  22. package/lib/compiler/resolve.js +104 -16
  23. package/lib/compiler/shared.js +61 -27
  24. package/lib/compiler/tweak-assocs.js +7 -1
  25. package/lib/edm/annotations/genericTranslation.js +93 -21
  26. package/lib/edm/csn2edm.js +216 -98
  27. package/lib/edm/edm.js +305 -226
  28. package/lib/edm/edmPreprocessor.js +499 -423
  29. package/lib/edm/edmUtils.js +22 -22
  30. package/lib/gen/Dictionary.json +98 -22
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +3 -1
  33. package/lib/gen/languageParser.js +4636 -4368
  34. package/lib/json/csnVersion.js +10 -11
  35. package/lib/json/from-csn.js +3 -2
  36. package/lib/json/to-csn.js +0 -2
  37. package/lib/language/docCommentParser.js +2 -2
  38. package/lib/language/genericAntlrParser.js +47 -2
  39. package/lib/language/language.g4 +59 -27
  40. package/lib/main.d.ts +19 -1
  41. package/lib/main.js +6 -0
  42. package/lib/model/csnRefs.js +33 -6
  43. package/lib/model/csnUtils.js +193 -75
  44. package/lib/model/enrichCsn.js +1 -0
  45. package/lib/model/revealInternalProperties.js +2 -2
  46. package/lib/modelCompare/compare.js +6 -6
  47. package/lib/optionProcessor.js +62 -26
  48. package/lib/render/toCdl.js +844 -679
  49. package/lib/render/toHdbcds.js +189 -243
  50. package/lib/render/toSql.js +180 -198
  51. package/lib/render/utils/common.js +131 -15
  52. package/lib/transform/db/.eslintrc.json +1 -1
  53. package/lib/transform/db/associations.js +2 -2
  54. package/lib/transform/db/constraints.js +3 -1
  55. package/lib/transform/db/expansion.js +15 -10
  56. package/lib/transform/db/flattening.js +95 -68
  57. package/lib/transform/db/transformExists.js +7 -7
  58. package/lib/transform/db/views.js +6 -3
  59. package/lib/transform/forHanaNew.js +43 -26
  60. package/lib/transform/forOdataNew.js +43 -42
  61. package/lib/transform/localized.js +12 -7
  62. package/lib/transform/odata/toFinalBaseType.js +8 -6
  63. package/lib/transform/odata/typesExposure.js +145 -197
  64. package/lib/transform/transformUtilsNew.js +9 -12
  65. package/lib/transform/translateAssocsToJoins.js +5 -1
  66. package/lib/transform/universalCsn/coreComputed.js +5 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
  68. package/lib/utils/moduleResolve.js +13 -6
  69. package/package.json +1 -1
  70. package/share/messages/message-explanations.json +2 -1
  71. package/share/messages/syntax-expected-integer.md +37 -0
  72. package/lib/transform/odata/attachPath.js +0 -96
  73. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  74. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  75. package/lib/transform/odata/referenceFlattener.js +0 -296
  76. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  77. package/lib/transform/odata/structuralPath.js +0 -72
  78. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -2,6 +2,8 @@
2
2
 
3
3
  'use strict';
4
4
 
5
+ const { ModelError } = require('../../base/error');
6
+
5
7
  const functionsWithoutParams = {
6
8
  hana: {
7
9
  CURRENT_CONNECTION: {},
@@ -280,12 +282,12 @@ const cdsToSqlTypes = {
280
282
  };
281
283
 
282
284
  /**
283
- * Get the element matching the column
284
- *
285
- * @param {CSN.Elements} elements Elements of a query
286
- * @param {CSN.Column} column Column from the same query
287
- * @returns {CSN.Element}
288
- */
285
+ * Get the element matching the column
286
+ *
287
+ * @param {CSN.Elements} elements Elements of a query
288
+ * @param {CSN.Column} column Column from the same query
289
+ * @returns {CSN.Element}
290
+ */
289
291
  function findElement(elements, column) {
290
292
  if (!elements)
291
293
  return undefined;
@@ -341,16 +343,17 @@ function hasHanaComment(obj, options) {
341
343
  return !options.disableHanaComments && typeof obj.doc === 'string';
342
344
  }
343
345
  /**
344
- * Return the comment of the given artifact or element.
345
- * Uses the first block (everything up to the first empty line (double \n)).
346
- * Remove leading/trailing whitespace.
347
- *
348
- * @param {CSN.Artifact|CSN.Element} obj
349
- * @returns {string}
350
- * @todo Warning/info to user?
351
- */
346
+ * Return the comment of the given artifact or element.
347
+ * Uses the first block (everything up to the first empty line (double \n)).
348
+ * Remove leading/trailing whitespace.
349
+ * Does not escape any characters.
350
+ *
351
+ * @param {CSN.Artifact|CSN.Element} obj
352
+ * @returns {string}
353
+ * @todo Warning/info to user?
354
+ */
352
355
  function getHanaComment(obj) {
353
- return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
356
+ return obj.doc.split('\n\n')[0].trim();
354
357
  }
355
358
 
356
359
  /**
@@ -368,6 +371,118 @@ function getSqlSnippets(options, obj) {
368
371
  return { front, back };
369
372
  }
370
373
 
374
+ /**
375
+ * A function used to render a certain part of an expression object
376
+ *
377
+ * @callback renderPart
378
+ * @param {object||array} expression
379
+ * @param {CdlRenderEnvironment} env
380
+ * @this {{inline: Boolean, nestedExpr: Boolean}}
381
+ * @returns {string}
382
+ */
383
+
384
+ /**
385
+ * The object containing the concrete rendering functions for the different parts
386
+ * of an expression
387
+ *
388
+ * @typedef {object} ExpressionConfiguration
389
+ * @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
390
+ * @property {renderPart} explicitTypeCast
391
+ * @property {renderPart} val
392
+ * @property {renderPart} enum
393
+ * @property {renderPart} ref
394
+ * @property {renderPart} aliasOnly
395
+ * @property {renderPart} windowFunction
396
+ * @property {renderPart} func
397
+ * @property {renderPart} xpr
398
+ * @property {renderPart} SELECT
399
+ * @property {renderPart} SET
400
+ */
401
+
402
+ /**
403
+ * Render an expression (including paths and values) or condition 'x'.
404
+ * (no trailing LF, don't indent if inline)
405
+ *
406
+ * @param {ExpressionConfiguration} renderer
407
+ * @returns {Function} Rendered expression
408
+ */
409
+ function getExpressionRenderer(renderer) {
410
+ /**
411
+ * Render an expression (including paths and values) or condition 'x'.
412
+ * (no trailing LF, don't indent if inline)
413
+ *
414
+ * @todo Reuse this with toCdl
415
+ * @param {Array|object|string} expr Expression to render
416
+ * @param {object} env Render environment
417
+ * @param {boolean} [inline=true] Whether to render the expression inline
418
+ * @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
419
+ * @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
420
+ * Note: This is a hack for casts() inside groupBy.
421
+ * @returns {string} Rendered expression
422
+ */
423
+ return function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
424
+ // Compound expression
425
+ if (Array.isArray(expr)) {
426
+ const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
427
+ return beautifyExprArray(tokens);
428
+ }
429
+ else if (typeof expr === 'object' && expr !== null) {
430
+ if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type && !expr.cast.target)
431
+ return renderer.explicitTypeCast.call({ inline, nestedExpr }, expr, env);
432
+ return renderExprObject(expr);
433
+ }
434
+ // Not a literal value but part of an operator, function etc - just leave as it is
435
+ // FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
436
+ return renderer.finalize.call({ inline, nestedExpr }, expr, env);
437
+
438
+
439
+ /**
440
+ * Various special cases represented as objects
441
+ *
442
+ * @param {object} x Expression
443
+ * @returns {string} String representation of the expression
444
+ */
445
+ function renderExprObject(x) {
446
+ if (x.list) { // TODO: Does this still exist?
447
+ return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
448
+ }
449
+ else if (x.val !== undefined) {
450
+ return renderer.val.call({ inline, nestedExpr }, x, env);
451
+ }
452
+ // Enum symbol
453
+ else if (x['#']) {
454
+ return renderer.enum.call({ inline, nestedExpr }, x, env);
455
+ }
456
+ // Reference: Array of path steps, possibly preceded by ':'
457
+ else if (x.ref) {
458
+ return renderer.ref.call({ inline, nestedExpr }, x, env);
459
+ }
460
+ // Function call, possibly with args (use '=>' for named args)
461
+ else if (x.func) {
462
+ if (x.xpr)
463
+ return renderer.windowFunction.call({ inline, nestedExpr }, x, env);
464
+ return renderer.func.call({ inline, nestedExpr }, x, env);
465
+ }
466
+ // Nested expression
467
+ else if (x.xpr) {
468
+ return renderer.xpr.call({ inline, nestedExpr }, x, env);
469
+ }
470
+ // Sub-select
471
+ else if (x.SELECT) {
472
+ return renderer.SELECT.call({ inline, nestedExpr }, x, env);
473
+ }
474
+ else if (x.SET) {
475
+ return renderer.SET.call({ inline, nestedExpr }, x, env);
476
+ }
477
+ else if (x.as && x.cast && x.cast.type && x.cast.target) {
478
+ return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
479
+ }
480
+
481
+ throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
482
+ }
483
+ };
484
+ }
485
+
371
486
  /**
372
487
  * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
373
488
  *
@@ -386,6 +501,7 @@ function getSqlSnippets(options, obj) {
386
501
 
387
502
  module.exports = {
388
503
  renderFunc,
504
+ getExpressionRenderer,
389
505
  beautifyExprArray,
390
506
  getNamespace,
391
507
  getRealName,
@@ -8,7 +8,7 @@
8
8
  "prefer-template": "error",
9
9
  "no-trailing-spaces": "error",
10
10
  "template-curly-spacing":["error", "never"],
11
- "complexity": ["warn", 30],
11
+ "complexity": ["warn", 40],
12
12
  "max-len": "off",
13
13
  // Don't enforce stupid descriptions
14
14
  "jsdoc/require-param-description": "off",
@@ -37,7 +37,7 @@ function attachOnConditions(csn, pathDelimiter) {
37
37
  /**
38
38
  * Create the foreign key elements for a managed association and build the on-condition
39
39
  *
40
- * @param {Object} elem The association to process
40
+ * @param {object} elem The association to process
41
41
  * @param {string} elemName
42
42
  * @returns {void}
43
43
  */
@@ -167,7 +167,7 @@ function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
167
167
  *
168
168
  * @param {Array} links
169
169
  * @param {number} startIndex
170
- * @returns {Object| undefined} CSN definition of the source of the managed association
170
+ * @returns {object | undefined} CSN definition of the source of the managed association
171
171
  */
172
172
  function findSource(links, startIndex) {
173
173
  for (let i = startIndex; i >= 0; i--) {
@@ -277,7 +277,9 @@ function createReferentialConstraints(csn, options) {
277
277
  // no constraint if either dependent or parent is not persisted
278
278
  if (
279
279
  hasAnnotationValue(parent, '@cds.persistence.skip') ||
280
- hasAnnotationValue(dependent, '@cds.persistence.skip')
280
+ hasAnnotationValue(dependent, '@cds.persistence.skip') ||
281
+ hasAnnotationValue(parent, '@cds.persistence.exists') ||
282
+ hasAnnotationValue(dependent, '@cds.persistence.exists')
281
283
  )
282
284
  return true;
283
285
 
@@ -8,6 +8,7 @@ const {
8
8
  } = require('../../model/csnUtils');
9
9
  const { csnRefs, implicitAs } = require('../../model/csnRefs');
10
10
  const { setProp, isBetaEnabled } = require('../../base/model');
11
+ const { forEach } = require('../../utils/objectUtils');
11
12
 
12
13
  /**
13
14
  * For keys, columns, groupBy and orderBy, expand structured things.
@@ -19,10 +20,10 @@ const { setProp, isBetaEnabled } = require('../../base/model');
19
20
  * @param {object} messageFunctions
20
21
  * @param {Function} messageFunctions.error
21
22
  * @param {Function} messageFunctions.info
22
- * @param {Function} messageFunctions.throwWithError
23
+ * @param {Function} messageFunctions.throwWithAnyError
23
24
  * @param {object} iterateOptions
24
25
  */
25
- function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithError }, iterateOptions = {}) {
26
+ function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
26
27
  const {
27
28
  isStructured, get$combined, getFinalBaseType, getServiceName,
28
29
  } = getUtils(csn);
@@ -182,7 +183,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
182
183
  }
183
184
 
184
185
  // We would be broken if we continue with assoc usage to now skipped
185
- throwWithError();
186
+ throwWithAnyError();
186
187
 
187
188
 
188
189
  for (const {
@@ -211,7 +212,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
211
212
  while (stack.length > 0) {
212
213
  const [ a, n ] = stack.pop();
213
214
  if (a[_dependents]) {
214
- Object.entries(a[_dependents]).forEach(([ dependentName, dependent ]) => {
215
+ forEach(a[_dependents], (dependentName, dependent) => {
215
216
  stack.push([ dependent, dependentName ]);
216
217
  });
217
218
  }
@@ -253,7 +254,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
253
254
  /**
254
255
  * Rewrite expand and inline to "normal" refs
255
256
  *
256
- * @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
257
+ * @param {CSN.Artifact} root All elements visible from the query source ($combined)
257
258
  * @param {CSN.Column[]} columns
258
259
  * @param {string[]} excluding
259
260
  * @returns {{columns: Array, toMany: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
@@ -261,8 +262,12 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
261
262
  function rewrite(root, columns, excluding) {
262
263
  const allToMany = [];
263
264
  const newThing = [];
264
- // Replace stars - needs to happen here since the .expand/.inline first path step affects the root *
265
- columns = replaceStar(root, columns, excluding);
265
+ const containsExpandInline = columns.some(col => col.expand || col.inline);
266
+ if (containsExpandInline) // Replace stars - needs to happen before resolving .expand/.inline since the .expand/.inline first path step affects the root *
267
+ columns = replaceStar(root, columns, excluding);
268
+ else
269
+ return { columns, toMany: [] };
270
+
266
271
  for (const col of columns) {
267
272
  if (col.expand) {
268
273
  // TODO: Can col.ref be empty without an as? Assumption is it cannot - if it has, it's an error, we throw, compiler checks.
@@ -402,8 +407,8 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
402
407
  * @returns {string|null} Name of any entity
403
408
  */
404
409
  function findAnEntity() {
405
- for (const [ name, artifact ] of Object.entries(csn.definitions)) {
406
- if (artifact.kind === 'entity' && !artifact.query)
410
+ for (const name in csn.definitions) {
411
+ if (Object.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
407
412
  return name;
408
413
  }
409
414
  return null;
@@ -544,7 +549,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
544
549
  /**
545
550
  * Replace the star and correctly put shadowed things in the right place.
546
551
  *
547
- * @param {Object} base The raw set of things a * can expand to
552
+ * @param {object} base The raw set of things a * can expand to
548
553
  * @param {Array} subs Things - the .expand/.inline or .columns
549
554
  * @param {string[]} [excluding=[]]
550
555
  * @returns {Array} If there was a star, expand it and handle shadowing/excluding, else just return subs
@@ -3,12 +3,13 @@
3
3
  const {
4
4
  getUtils, walkCsnPath,
5
5
  applyTransformations, applyTransformationsOnNonDictionary,
6
- isBuiltinType, cloneCsn,
6
+ isBuiltinType, cloneCsnNonDict,
7
7
  copyAnnotations, implicitAs, isDeepEqual,
8
8
  } = require('../../model/csnUtils');
9
9
  const transformUtils = require('../transformUtilsNew');
10
10
  const { csnRefs } = require('../../model/csnRefs');
11
11
  const { setProp } = require('../../base/model');
12
+ const { forEach } = require('../../utils/objectUtils');
12
13
 
13
14
  /**
14
15
  * Strip off leading $self from refs where applicable
@@ -74,17 +75,20 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
74
75
  iterateOptions.skipDict.actions = true;
75
76
  else
76
77
  iterateOptions.skipDict = { actions: true };
78
+
79
+ const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
80
+ const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
77
81
  applyTransformations(csn, {
78
- cast: (parent) => {
82
+ cast: (parent, prop, cast, path) => {
79
83
  // Resolve cast already - we otherwise lose .localized
80
- if (parent.cast.type && !isBuiltinType(parent.cast.type))
84
+ if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
81
85
  toFinalBaseType(parent.cast, resolved, true);
82
86
  },
83
87
  // @ts-ignore
84
- type: (parent, prop, type, csnPath) => {
85
- if (options.toOdata && parent.kind && [ 'aspect', 'event', 'type' ].includes(parent.kind))
88
+ type: (parent, prop, type, path) => {
89
+ if (options.toOdata && parent.kind && parent.kind in ignoreOdataKinds)
86
90
  return;
87
- if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type) && !isODataItems(type))) {
91
+ if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
88
92
  toFinalBaseType(parent, resolved);
89
93
  // structured types might not have the child-types replaced.
90
94
  // Drill down to ensure this.
@@ -110,40 +114,6 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
110
114
  parent.type = 'cds.LargeString';
111
115
  delete parent.items;
112
116
  }
113
-
114
- /**
115
- * OData V4 only:
116
- * Do not replace a type ref if:
117
- * The type definition is terminating on a scalar type (that can also be a derived type chain)
118
- * AND the typeName (that is the start of that (derived) type chain is defined within the same
119
- * service as the artifact from which the type reference has to be resolved.
120
- *
121
- * @param {string} typeName
122
- * @returns {boolean}
123
- */
124
- function isODataV4BuiltinFromService(typeName) {
125
- if (!options.toOdata || (options.toOdata && options.toOdata.version === 'v2'))
126
- return false;
127
-
128
- const typeServiceName = getServiceName(typeName);
129
- const finalBaseType = getFinalBaseType(typeName);
130
- // we need the service of the current definition
131
- const currDefServiceName = getServiceName(csnPath[1]);
132
-
133
- return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
134
- }
135
-
136
- /**
137
- * OData stops replacing types @ 'items', if the type ref is a user defined type
138
- * AND that type has items, don't do toFinalBaseType
139
- *
140
- * @param {string} typeName
141
- * @returns {boolean}
142
- */
143
- function isODataItems(typeName) {
144
- const typeDef = csn.definitions[typeName];
145
- return !!(options.toOdata && typeDef && typeDef.items);
146
- }
147
117
  },
148
118
  // HANA/SQLite do not support array-of - turn into CLOB/Text
149
119
  items: (parent) => {
@@ -159,8 +129,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
159
129
 
160
130
  // Do not do for OData
161
131
  // TODO:factor out somewhere else
162
- if (!options.toOdata &&
163
- ([ 'action', 'function', 'event' ].includes(artifact.kind))) {
132
+ if (!options.toOdata && artifact.kind in replaceWithDummyKinds) {
164
133
  const dummy = { kind: artifact.kind };
165
134
  if (artifact.$location)
166
135
  setProp(dummy, '$location', artifact.$location);
@@ -169,6 +138,42 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
169
138
  }
170
139
  // TODO: skipDict options as default function arguments not via Object.assign
171
140
  } ], iterateOptions);
141
+
142
+
143
+ /**
144
+ * OData V4 only:
145
+ * Do not replace a type ref if:
146
+ * The type definition is terminating on a scalar type (that can also be a derived type chain)
147
+ * AND the typeName (that is the start of that (derived) type chain is defined within the same
148
+ * service as the artifact from which the type reference has to be resolved.
149
+ *
150
+ * @param {string} typeName
151
+ * @param {CSN.Path} path
152
+ * @returns {boolean}
153
+ */
154
+ function isODataV4BuiltinFromService(typeName, path) {
155
+ if (!options.toOdata || (options.toOdata && options.toOdata.version === 'v2') || typeof typeName !== 'string')
156
+ return false;
157
+
158
+ const typeServiceName = getServiceName(typeName);
159
+ const finalBaseType = getFinalBaseType(typeName);
160
+ // we need the service of the current definition
161
+ const currDefServiceName = getServiceName(path[1]);
162
+
163
+ return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
164
+ }
165
+
166
+ /**
167
+ * OData stops replacing types @ 'items', if the type ref is a user defined type
168
+ * AND that type has items, don't do toFinalBaseType
169
+ *
170
+ * @param {string} typeName
171
+ * @returns {boolean}
172
+ */
173
+ function isODataItems(typeName) {
174
+ const typeDef = csn.definitions[typeName];
175
+ return !!(options.toOdata && typeDef && typeDef.items);
176
+ }
172
177
  }
173
178
 
174
179
  /**
@@ -258,7 +263,7 @@ function flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter, iter
258
263
  * @param {CSN.Options} options
259
264
  * @param {string} pathDelimiter
260
265
  * @param {Function} error
261
- * @param {Object} iterateOptions
266
+ * @param {object} iterateOptions
262
267
  */
263
268
  function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}) {
264
269
  const { isAssocOrComposition } = getUtils(csn);
@@ -284,7 +289,7 @@ function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}
284
289
  function flatten(parent, prop, dict, path) {
285
290
  if (!parent[prop].$orderedElements)
286
291
  setProp(parent[prop], '$orderedElements', []);
287
- Object.entries(dict).forEach(([ elementName, element ]) => {
292
+ forEach(dict, (elementName, element) => {
288
293
  if (element.elements) {
289
294
  // Ignore the structured element, replace it by its flattened form
290
295
  // TODO: use $ignore - _ is for links
@@ -295,9 +300,9 @@ function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}
295
300
 
296
301
  for (const flatElemName in flatElems) {
297
302
  if (parent[prop][flatElemName])
298
- // TODO: combine message ID with generated FK duplicate
299
- // do the duplicate check in the consruct callback, requires to mark generated flat elements,
300
- // check: Error location should be the existing element like @odata.foreignKey4
303
+ // TODO: combine message ID with generated FK duplicate
304
+ // do the duplicate check in the consruct callback, requires to mark generated flat elements,
305
+ // check: Error location should be the existing element like @odata.foreignKey4
301
306
  error(null, path.concat([ 'elements', elementName ]), `"${path[1]}.${elementName}": Flattened struct element name conflicts with existing element: "${flatElemName}"`);
302
307
 
303
308
  const flatElement = flatElems[flatElemName];
@@ -315,16 +320,16 @@ function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}
315
320
  const firstRef = onPart.ref[0];
316
321
 
317
322
  /*
318
- when element is defined in the current name resolution scope, like
319
- entity E {
320
- key x: Integer;
321
- s : {
322
- y : Integer;
323
- a3 : association to E on a3.x = y;
324
- }
325
- }
326
- We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
327
- */
323
+ when element is defined in the current name resolution scope, like
324
+ entity E {
325
+ key x: Integer;
326
+ s : {
327
+ y : Integer;
328
+ a3 : association to E on a3.x = y;
329
+ }
330
+ }
331
+ We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
332
+ */
328
333
  const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
329
334
  const possibleFlatName = prefix + pathDelimiter + firstRef;
330
335
 
@@ -418,11 +423,11 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
418
423
  }
419
424
  });
420
425
  },
421
- }, [], {
426
+ }, [], Object.assign({
422
427
  skipIgnore: false,
423
428
  allowArtifact: artifact => (artifact.kind === 'entity' || artifact.kind === 'type'),
424
429
  skipDict: { actions: true },
425
- });
430
+ }, iterateOptions));
426
431
  }
427
432
  createForeignKeyElements();
428
433
 
@@ -469,7 +474,7 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
469
474
  const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
470
475
  Object.keys(flat).forEach((flatElemName) => {
471
476
  const key = assoc.keys[i];
472
- const clone = cloneCsn(assoc.keys[i], options);
477
+ const clone = cloneCsnNonDict(assoc.keys[i], options);
473
478
  if (clone.as) {
474
479
  const lastRef = clone.ref[clone.ref.length - 1];
475
480
  // Cut off the last ref part from the beginning of the flat name
@@ -533,7 +538,7 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
533
538
  * @returns {object} The clone of base
534
539
  */
535
540
  function cloneAndExtendRef(key, base, ref) {
536
- const clone = cloneCsn(base, options);
541
+ const clone = cloneCsnNonDict(base, options);
537
542
  if (key.ref) {
538
543
  // We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
539
544
  // Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
@@ -574,10 +579,7 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
574
579
  if (options.toOdata)
575
580
  transformers.params = createFks;
576
581
 
577
- // Do not respect _ignore flag for toOdata and toRename (we may have facades for existing HANA CDS
578
- // artifacts that shall still be maintained from outside, filter them away in the rename script)
579
- iterateOptions.skipIgnore = !(options.toOdata || options.toRename);
580
- applyTransformations(csn, transformers, [], iterateOptions);
582
+ applyTransformations(csn, transformers, [], Object.assign({ skipIgnore: false }, iterateOptions));
581
583
 
582
584
  /**
583
585
  * Process a given .elements or .params dictionary and create foreign key elements
@@ -685,17 +687,21 @@ function createForeignKeysInternal(path, element, prefix, csn, options, pathDeli
685
687
  return fks;
686
688
 
687
689
  let finalElement = element;
690
+ let finalTypeName; // TODO: Find a way to not rely on $path?
688
691
  // TODO: effectiveType's return value is 'path' for the next inspectRef
689
692
  if (element.type && !isBuiltinType(element.type)) {
690
693
  const tmpElt = effectiveType(element);
691
694
  // effective type resolves to structs and enums only but not scalars
692
695
  if (Object.keys(tmpElt).length) {
693
696
  finalElement = tmpElt;
697
+ finalTypeName = finalElement.$path[1];
694
698
  }
695
699
  else {
696
700
  // unwind a derived type chain to a scalar type
697
- while (finalElement.type && !isBuiltinType(finalElement.type))
701
+ while (finalElement.type && !isBuiltinType(finalElement.type)) {
702
+ finalTypeName = finalElement.type;
698
703
  finalElement = csn.definitions[finalElement.type];
704
+ }
699
705
  }
700
706
  }
701
707
 
@@ -712,7 +718,7 @@ function createForeignKeysInternal(path, element, prefix, csn, options, pathDeli
712
718
  }
713
719
  // TODO: has managed assoc keys?
714
720
  finalElement.keys.forEach((key, keyIndex) => {
715
- const continuePath = isInspectRefResult ? [ path, 'keys', keyIndex ] : [ ...path, 'keys', keyIndex ];
721
+ const continuePath = getContinuePath([ 'keys', keyIndex ]);
716
722
  const alias = key.as || implicitAs(key.ref);
717
723
  const result = inspectRef(continuePath);
718
724
  fks = fks.concat(createForeignKeysInternal(result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
@@ -738,12 +744,33 @@ function createForeignKeysInternal(path, element, prefix, csn, options, pathDeli
738
744
  Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
739
745
  // Skip already produced foreign keys
740
746
  if (!elem['@odata.foreignKey4']) {
741
- const continuePath = isInspectRefResult ? [ path, 'elements', elemName ] : [ ...path, 'elements', elemName ];
747
+ const continuePath = getContinuePath([ 'elements', elemName ]);
742
748
  fks = fks.concat(createForeignKeysInternal(continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
743
749
  }
744
750
  });
745
751
  }
746
752
 
753
+ /**
754
+ * Get the path to continue resolving references
755
+ *
756
+ * If we are currently inside of a type, we need to start our path fresh from that given type.
757
+ * Otherwise, we would try to resolve .elements on a thing that does not exist.
758
+ *
759
+ * We also respect if we have a previous inspectRef result as our base.
760
+ *
761
+ * @param {Array} additions
762
+ * @returns {CSN.Path}
763
+ */
764
+ function getContinuePath(additions) {
765
+ if (csn.definitions[finalElement.type])
766
+ return [ 'definitions', finalElement.type, ...additions ];
767
+ else if (finalTypeName)
768
+ return [ 'definitions', finalTypeName, ...additions ];
769
+ else if (isInspectRefResult)
770
+ return [ path, ...additions ];
771
+ return [ ...path, ...additions ];
772
+ }
773
+
747
774
  fks.forEach((fk) => {
748
775
  // prepend current prefix
749
776
  fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
@@ -110,8 +110,8 @@ function handleExists(csn, options, error) {
110
110
  sources[join.as] = join.as;
111
111
  }
112
112
  else if (join.args) {
113
- const subsources = getJoinSources(join.args);
114
- sources = Object.assign(sources, subsources);
113
+ const subSources = getJoinSources(join.args);
114
+ sources = Object.assign(sources, subSources);
115
115
  }
116
116
  else if (join.ref) {
117
117
  sources[join.ref[join.ref.length - 1]] = join.ref[join.ref.length - 1];
@@ -196,7 +196,7 @@ function handleExists(csn, options, error) {
196
196
  const stack = [ [ null, startAssoc, startRest, startIndex ] ];
197
197
  const { links } = inspectRef(path);
198
198
  while (stack.length > 0) {
199
- // previous: to nest "up" if the previous assoc did not originaly have a filter
199
+ // previous: to nest "up" if the previous assoc did not originally have a filter
200
200
  // assoc: the assoc path step
201
201
  // rest: path steps after assoc
202
202
  // index: index of after-assoc in the overall ref-array - so we know where to start looking for the next assoc
@@ -411,14 +411,14 @@ function handleExists(csn, options, error) {
411
411
  * Translate an `EXISTS <managed assoc>` into a part of a WHERE condition.
412
412
  *
413
413
  * For each of the foreign keys, do:
414
- * + build the target side by prefixing `target` infront of the ref
414
+ * + build the target side by prefixing `target` in front of the ref
415
415
  * + build the source side by prefixing `base` (if not already part of `current`)
416
- * and the assoc name itself (current) infront of the ref
416
+ * and the assoc name itself (current) in front of the ref
417
417
  * + Compare source and target with `=`
418
418
  *
419
419
  * If there is more than one foreign key, join with `and`.
420
420
  *
421
- * The new tokens are immediatly added to the WHERE of the subselect
421
+ * The new tokens are immediately added to the WHERE of the subselect
422
422
  *
423
423
  * @param {CSN.Element} root
424
424
  * @param {string} target
@@ -752,7 +752,7 @@ function handleExists(csn, options, error) {
752
752
  *
753
753
  * @param {string} target
754
754
  * @param {TokenStream} where
755
- * @returns {TokenStream} The input-where with the refs "absolutified"
755
+ * @returns {TokenStream} The input-where with the refs transformed to absolute ones
756
756
  */
757
757
  function remapExistingWhere(target, where) {
758
758
  return where.map((part) => {