@sap/cds-compiler 2.13.8 → 3.0.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 (127) hide show
  1. package/CHANGELOG.md +155 -1594
  2. package/bin/cdsc.js +144 -66
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +237 -122
  10. package/lib/api/options.js +17 -88
  11. package/lib/api/validate.js +12 -16
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +152 -37
  14. package/lib/base/messages.js +145 -83
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +19 -0
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +11 -32
  19. package/lib/checks/arrayOfs.js +1 -34
  20. package/lib/checks/cdsPersistence.js +1 -0
  21. package/lib/checks/elements.js +6 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/queryNoDbArtifacts.js +2 -1
  25. package/lib/checks/selectItems.js +5 -1
  26. package/lib/checks/types.js +4 -2
  27. package/lib/checks/utils.js +2 -2
  28. package/lib/checks/validator.js +4 -5
  29. package/lib/compiler/assert-consistency.js +16 -10
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +98 -9
  32. package/lib/compiler/checks.js +22 -70
  33. package/lib/compiler/define.js +61 -13
  34. package/lib/compiler/extend.js +79 -14
  35. package/lib/compiler/finalize-parse-cdl.js +46 -29
  36. package/lib/compiler/index.js +100 -37
  37. package/lib/compiler/moduleLayers.js +7 -0
  38. package/lib/compiler/populate.js +19 -18
  39. package/lib/compiler/propagator.js +7 -4
  40. package/lib/compiler/resolve.js +297 -234
  41. package/lib/compiler/shared.js +107 -102
  42. package/lib/compiler/tweak-assocs.js +16 -11
  43. package/lib/compiler/utils.js +5 -0
  44. package/lib/edm/annotations/genericTranslation.js +93 -21
  45. package/lib/edm/csn2edm.js +230 -115
  46. package/lib/edm/edm.js +305 -226
  47. package/lib/edm/edmPreprocessor.js +509 -438
  48. package/lib/edm/edmUtils.js +31 -45
  49. package/lib/gen/Dictionary.json +98 -22
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +10 -30
  52. package/lib/gen/language.tokens +105 -114
  53. package/lib/gen/languageLexer.interp +1 -34
  54. package/lib/gen/languageLexer.js +889 -1007
  55. package/lib/gen/languageLexer.tokens +95 -106
  56. package/lib/gen/languageParser.js +20786 -22199
  57. package/lib/json/csnVersion.js +10 -11
  58. package/lib/json/from-csn.js +59 -51
  59. package/lib/json/to-csn.js +10 -10
  60. package/lib/language/antlrParser.js +2 -2
  61. package/lib/language/docCommentParser.js +62 -39
  62. package/lib/language/errorStrategy.js +52 -40
  63. package/lib/language/genericAntlrParser.js +348 -229
  64. package/lib/language/language.g4 +629 -653
  65. package/lib/language/multiLineStringParser.js +14 -42
  66. package/lib/language/textUtils.js +44 -0
  67. package/lib/main.d.ts +46 -43
  68. package/lib/main.js +108 -79
  69. package/lib/model/csnRefs.js +34 -7
  70. package/lib/model/csnUtils.js +337 -332
  71. package/lib/model/enrichCsn.js +1 -0
  72. package/lib/model/revealInternalProperties.js +30 -10
  73. package/lib/model/sortViews.js +32 -31
  74. package/lib/modelCompare/compare.js +6 -6
  75. package/lib/optionProcessor.js +73 -46
  76. package/lib/render/.eslintrc.json +1 -1
  77. package/lib/render/DuplicateChecker.js +4 -7
  78. package/lib/render/manageConstraints.js +70 -2
  79. package/lib/render/toCdl.js +1042 -882
  80. package/lib/render/toHdbcds.js +195 -245
  81. package/lib/render/toRename.js +44 -22
  82. package/lib/render/toSql.js +225 -241
  83. package/lib/render/utils/common.js +145 -15
  84. package/lib/render/utils/sql.js +20 -19
  85. package/lib/sql-identifier.js +6 -0
  86. package/lib/transform/db/.eslintrc.json +4 -3
  87. package/lib/transform/db/associations.js +2 -2
  88. package/lib/transform/db/cdsPersistence.js +5 -15
  89. package/lib/transform/db/constraints.js +4 -2
  90. package/lib/transform/db/expansion.js +22 -16
  91. package/lib/transform/db/flattening.js +109 -80
  92. package/lib/transform/db/transformExists.js +7 -7
  93. package/lib/transform/db/views.js +9 -6
  94. package/lib/transform/draft/.eslintrc.json +2 -2
  95. package/lib/transform/draft/db.js +6 -6
  96. package/lib/transform/draft/odata.js +6 -7
  97. package/lib/transform/forHanaNew.js +62 -48
  98. package/lib/transform/forOdataNew.js +49 -50
  99. package/lib/transform/localized.js +31 -20
  100. package/lib/transform/odata/toFinalBaseType.js +16 -14
  101. package/lib/transform/odata/typesExposure.js +146 -198
  102. package/lib/transform/odata/utils.js +1 -38
  103. package/lib/transform/transformUtilsNew.js +67 -84
  104. package/lib/transform/translateAssocsToJoins.js +7 -3
  105. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  106. package/lib/transform/universalCsn/coreComputed.js +16 -9
  107. package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
  108. package/lib/utils/file.js +3 -3
  109. package/lib/utils/moduleResolve.js +13 -6
  110. package/lib/utils/timetrace.js +20 -21
  111. package/package.json +35 -4
  112. package/share/messages/message-explanations.json +2 -1
  113. package/share/messages/syntax-expected-integer.md +37 -0
  114. package/doc/ApiMigration.md +0 -237
  115. package/doc/CommandLineMigration.md +0 -58
  116. package/doc/ErrorMessages.md +0 -175
  117. package/doc/FioriAnnotations.md +0 -94
  118. package/doc/ODataTransformation.md +0 -273
  119. package/lib/backends.js +0 -529
  120. package/lib/fix_antlr4-8_warning.js +0 -56
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -296
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. 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: {},
@@ -277,15 +279,27 @@ const cdsToSqlTypes = {
277
279
  'cds.hana.BINARY': 'BINARY',
278
280
  'cds.hana.SMALLDECIMAL': 'DECIMAL',
279
281
  },
282
+ postgres: {
283
+ // TODO: Type mapping for binary types is not correct, yet.
284
+ // We can't use text types for binary on PostgreSQL due to NUL!
285
+ 'cds.String': 'VARCHAR',
286
+ 'cds.LargeString': 'text',
287
+ 'cds.hana.CLOB': 'text',
288
+ 'cds.LargeBinary': 'bytea',
289
+ 'cds.Binary': 'VARCHAR',
290
+ 'cds.hana.BINARY': 'VARCHAR',
291
+ 'cds.Double': 'double precision',
292
+ 'cds.hana.TINYINT': 'INTEGER',
293
+ },
280
294
  };
281
295
 
282
296
  /**
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
- */
297
+ * Get the element matching the column
298
+ *
299
+ * @param {CSN.Elements} elements Elements of a query
300
+ * @param {CSN.Column} column Column from the same query
301
+ * @returns {CSN.Element}
302
+ */
289
303
  function findElement(elements, column) {
290
304
  if (!elements)
291
305
  return undefined;
@@ -341,16 +355,17 @@ function hasHanaComment(obj, options) {
341
355
  return !options.disableHanaComments && typeof obj.doc === 'string';
342
356
  }
343
357
  /**
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
- */
358
+ * Return the comment of the given artifact or element.
359
+ * Uses the first block (everything up to the first empty line (double \n)).
360
+ * Remove leading/trailing whitespace.
361
+ * Does not escape any characters.
362
+ *
363
+ * @param {CSN.Artifact|CSN.Element} obj
364
+ * @returns {string}
365
+ * @todo Warning/info to user?
366
+ */
352
367
  function getHanaComment(obj) {
353
- return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
368
+ return obj.doc.split('\n\n')[0].trim();
354
369
  }
355
370
 
356
371
  /**
@@ -368,6 +383,120 @@ function getSqlSnippets(options, obj) {
368
383
  return { front, back };
369
384
  }
370
385
 
386
+ /**
387
+ * A function used to render a certain part of an expression object
388
+ *
389
+ * @callback renderPart
390
+ * @param {object|array} expression
391
+ * @param {CdlRenderEnvironment} env
392
+ * @this {{inline: Boolean, nestedExpr: Boolean}}
393
+ * @returns {string}
394
+ */
395
+
396
+ /**
397
+ * The object containing the concrete rendering functions for the different parts
398
+ * of an expression
399
+ *
400
+ * @typedef {object} ExpressionConfiguration
401
+ * @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
402
+ * @property {renderPart} explicitTypeCast
403
+ * @property {renderPart} val
404
+ * @property {renderPart} enum
405
+ * @property {renderPart} ref
406
+ * @property {renderPart} aliasOnly
407
+ * @property {renderPart} windowFunction
408
+ * @property {renderPart} func
409
+ * @property {renderPart} xpr
410
+ * @property {renderPart} SELECT
411
+ * @property {renderPart} SET
412
+ * @property {boolean} [inline]
413
+ * @property {boolean} [nestedExpr]
414
+ */
415
+
416
+ /**
417
+ * Render an expression (including paths and values) or condition 'x'.
418
+ * (no trailing LF, don't indent if inline)
419
+ *
420
+ * @param {ExpressionConfiguration} renderer
421
+ * @returns {Function} Rendered expression
422
+ */
423
+ function getExpressionRenderer(renderer) {
424
+ /**
425
+ * Render an expression (including paths and values) or condition 'x'.
426
+ * (no trailing LF, don't indent if inline)
427
+ *
428
+ * @todo Reuse this with toCdl
429
+ * @param {Array|object|string} expr Expression to render
430
+ * @param {object} env Render environment
431
+ * @param {boolean} [inline=true] Whether to render the expression inline
432
+ * @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
433
+ * @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
434
+ * Note: This is a hack for casts() inside groupBy.
435
+ * @returns {string} Rendered expression
436
+ */
437
+ return function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
438
+ // Compound expression
439
+ if (Array.isArray(expr)) {
440
+ const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
441
+ return beautifyExprArray(tokens);
442
+ }
443
+ else if (typeof expr === 'object' && expr !== null) {
444
+ if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type && !expr.cast.target)
445
+ return renderer.explicitTypeCast.call({ inline, nestedExpr }, expr, env);
446
+ return renderExprObject(expr);
447
+ }
448
+ // Not a literal value but part of an operator, function etc - just leave as it is
449
+ // FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
450
+ return renderer.finalize.call({ inline, nestedExpr }, expr, env);
451
+
452
+
453
+ /**
454
+ * Various special cases represented as objects
455
+ *
456
+ * @param {object} x Expression
457
+ * @returns {string} String representation of the expression
458
+ */
459
+ function renderExprObject(x) {
460
+ if (x.list) { // TODO: Does this still exist?
461
+ return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
462
+ }
463
+ else if (x.val !== undefined) {
464
+ return renderer.val.call({ inline, nestedExpr }, x, env);
465
+ }
466
+ // Enum symbol
467
+ else if (x['#']) {
468
+ return renderer.enum.call({ inline, nestedExpr }, x, env);
469
+ }
470
+ // Reference: Array of path steps, possibly preceded by ':'
471
+ else if (x.ref) {
472
+ return renderer.ref.call({ inline, nestedExpr }, x, env);
473
+ }
474
+ // Function call, possibly with args (use '=>' for named args)
475
+ else if (x.func) {
476
+ if (x.xpr)
477
+ return renderer.windowFunction.call({ inline, nestedExpr }, x, env);
478
+ return renderer.func.call({ inline, nestedExpr }, x, env);
479
+ }
480
+ // Nested expression
481
+ else if (x.xpr) {
482
+ return renderer.xpr.call({ inline, nestedExpr }, x, env);
483
+ }
484
+ // Sub-select
485
+ else if (x.SELECT) {
486
+ return renderer.SELECT.call({ inline, nestedExpr }, x, env);
487
+ }
488
+ else if (x.SET) {
489
+ return renderer.SET.call({ inline, nestedExpr }, x, env);
490
+ }
491
+ else if (x.as && x.cast && x.cast.type && x.cast.target) {
492
+ return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
493
+ }
494
+
495
+ throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
496
+ }
497
+ };
498
+ }
499
+
371
500
  /**
372
501
  * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
373
502
  *
@@ -386,6 +515,7 @@ function getSqlSnippets(options, obj) {
386
515
 
387
516
  module.exports = {
388
517
  renderFunc,
518
+ getExpressionRenderer,
389
519
  beautifyExprArray,
390
520
  getNamespace,
391
521
  getRealName,
@@ -40,18 +40,18 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
40
40
  }
41
41
 
42
42
  const renderAsHdbconstraint = options.transformation === 'hdbcds' ||
43
- (options.toSql && options.toSql.src === 'hdi') ||
43
+ options.src === 'hdi' ||
44
44
  (options.manageConstraints && options.manageConstraints.src === 'hdi');
45
45
 
46
- const { names } = options.forHana;
47
- const forSqlite = options.toSql && options.toSql.dialect === 'sqlite';
46
+ const { sqlMapping } = options;
47
+ const forSqlite = options.sqlDialect === 'sqlite';
48
48
  let result = '';
49
49
  result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;
50
50
  if (renderAsHdbconstraint)
51
- result += `${indent}ON ${quoteId(getResultingName(csn, names, constraint.dependentTable))}\n`;
51
+ result += `${indent}ON ${quoteId(getResultingName(csn, sqlMapping, constraint.dependentTable))}\n`;
52
52
  if (!alterConstraint) {
53
53
  result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(quoteId).join(', ')})\n`;
54
- result += `${indent}REFERENCES ${quoteId(getResultingName(csn, names, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
54
+ result += `${indent}REFERENCES ${quoteId(getResultingName(csn, sqlMapping, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
55
55
  const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
56
56
 
57
57
  // omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
@@ -65,12 +65,13 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
65
65
  }
66
66
  }
67
67
  // constraint enforcement / validation must be switched off using sqlite pragma statement
68
- if (options.toSql && options.toSql.dialect !== 'sqlite') {
68
+ // Does not include HDBCDS.
69
+ if (options.toSql && !forSqlite) {
69
70
  result += `${indent}${!constraint.validated ? 'NOT ' : ''}VALIDATED\n`;
70
71
  result += `${indent}${!constraint.enforced ? 'NOT ' : ''}ENFORCED\n`;
71
72
  }
72
73
  // for sqlite, the DEFERRABLE keyword is required
73
- result += `${indent}${options.toSql && options.toSql.dialect === 'sqlite' ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
74
+ result += `${indent}${forSqlite ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
74
75
  return result;
75
76
  }
76
77
 
@@ -85,9 +86,9 @@ function getIdentifierUtils(options) {
85
86
  /**
86
87
  * Return 'name' with appropriate "-quotes.
87
88
  * Additionally perform the following conversions on 'name'
88
- * If 'options.toSql.names' is 'plain'
89
+ * If 'options.sqlMapping' is 'plain'
89
90
  * - replace '.' or '::' by '_'
90
- * else if 'options.toSql.names' is 'quoted'
91
+ * else if 'options.sqlMapping' is 'quoted'
91
92
  * - replace '::' by '.'
92
93
  * Complain about names that collide with known SQL keywords or functions
93
94
  *
@@ -97,13 +98,13 @@ function getIdentifierUtils(options) {
97
98
  function quoteSqlId(name) {
98
99
  name = prepareIdentifier(name);
99
100
 
100
- switch (options.toSql.names) {
101
+ switch (options.sqlMapping) {
101
102
  case 'plain':
102
- return smartId(name, options.toSql.dialect);
103
+ return smartId(name, options.sqlDialect);
103
104
  case 'quoted':
104
- return delimitedId(name, options.toSql.dialect);
105
+ return delimitedId(name, options.sqlDialect);
105
106
  case 'hdbcds':
106
- return delimitedId(name, options.toSql.dialect);
107
+ return delimitedId(name, options.sqlDialect);
107
108
  default:
108
109
  return undefined;
109
110
  }
@@ -111,9 +112,9 @@ function getIdentifierUtils(options) {
111
112
 
112
113
  /**
113
114
  * Prepare an identifier:
114
- * If 'options.toSql.names' is 'plain'
115
+ * If 'options.sqlMapping' is 'plain'
115
116
  * - replace '.' or '::' by '_'
116
- * else if 'options.toSql.names' is 'quoted'
117
+ * else if 'options.sqlMapping' is 'quoted'
117
118
  * - replace '::' by '.'
118
119
  *
119
120
  * @param {string} name Identifier to prepare
@@ -121,11 +122,11 @@ function getIdentifierUtils(options) {
121
122
  */
122
123
  function prepareIdentifier(name) {
123
124
  // Sanity check
124
- if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
125
- throw new ModelError(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
125
+ if (options.sqlDialect === 'sqlite' && options.sqlMapping !== 'plain')
126
+ throw new ModelError(`Not expecting ${options.sqlMapping} names for 'sqlite' dialect`);
126
127
 
127
128
 
128
- switch (options.toSql.names) {
129
+ switch (options.sqlMapping) {
129
130
  case 'plain':
130
131
  return name.replace(/(\.|::)/g, '_');
131
132
  case 'quoted':
@@ -133,7 +134,7 @@ function getIdentifierUtils(options) {
133
134
  case 'hdbcds':
134
135
  return name;
135
136
  default:
136
- throw new ModelError(`No matching rendering found for naming mode ${options.toSql.names}`);
137
+ throw new ModelError(`No matching rendering found for naming mode ${options.sqlMapping}`);
137
138
  }
138
139
  }
139
140
  }
@@ -45,6 +45,12 @@ const sqlDialects = {
45
45
  effectiveName: name => name,
46
46
  asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
47
47
  },
48
+ postgres: {
49
+ regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
50
+ reservedWords: keywords.postgres,
51
+ effectiveName: name => name.toLowerCase(),
52
+ asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
53
+ },
48
54
  hana: {
49
55
  regularRegex: /^[A-Za-z_][A-Za-z_$#0-9]*$/,
50
56
  reservedWords: keywords.hana,
@@ -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",
@@ -17,15 +17,16 @@
17
17
  "sonarjs/prefer-single-boolean-return": "off",
18
18
  // Very whiny and nitpicky
19
19
  "sonarjs/cognitive-complexity": "off",
20
+ "sonarjs/no-duplicate-string": "off",
20
21
  // Does not recognize TS types
21
22
  "jsdoc/no-undefined-types": "off"
22
23
  },
23
24
  "parserOptions": {
24
- "ecmaVersion": 2018,
25
+ "ecmaVersion": 2020,
25
26
  "sourceType": "script"
26
27
  },
27
28
  "env": {
28
- "es6": true,
29
+ "es2020": true,
29
30
  "node": true
30
31
  },
31
32
  "settings": {
@@ -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--) {
@@ -81,26 +81,16 @@ function getAssocToSkippedIgnorer(csn, options, messageFunctions) {
81
81
  * @todo Why do we check for @cds.persistence.exists here if the parent-function only calls this for skip/abstract?
82
82
  */
83
83
  function ignore(member, memberName, prop, path) {
84
- if (options.sqlDialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
85
- const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], exists) ? exists : '@cds.persistence.skip';
84
+ if (options.sqlDialect === 'hana' &&
85
+ !member._ignore && member.target &&
86
+ isAssocOrComposition(member.type) &&
87
+ !isPersistedOnDatabase(csn.definitions[member.target])) {
86
88
  info(null, path,
87
- { target: member.target, anno: targetAnnotation },
89
+ { target: member.target, anno: '@cds.persistence.skip' },
88
90
  'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
89
91
  member._ignore = true;
90
92
  }
91
93
  }
92
-
93
- /**
94
- * Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
95
- * - @cds.persistence.skip/exists
96
- * - abstract
97
- *
98
- * @param {CSN.Artifact} art
99
- * @returns {boolean}
100
- */
101
- function isUnreachableAssociationTarget(art) {
102
- return !isPersistedOnDatabase(art) || hasAnnotationValue(art, exists);
103
- }
104
94
  }
105
95
 
106
96
  /**
@@ -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
 
@@ -515,7 +517,7 @@ function createReferentialConstraints(csn, options) {
515
517
  onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
516
518
  referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
517
519
  // constraint identifier start with `c__` to avoid name clashes
518
- identifier: `c__${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
520
+ identifier: `c__${getResultingName(csn, options.sqlMapping, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
519
521
  foreignKey: dependentKey,
520
522
  parentKey,
521
523
  dependentTable: artifactName,
@@ -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,14 +20,15 @@ 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 = {}) {
27
+ const csnUtils = getUtils(csn);
26
28
  const {
27
- isStructured, get$combined, getFinalBaseType, getServiceName,
28
- } = getUtils(csn);
29
- let { effectiveType, inspectRef } = csnRefs(csn);
29
+ isStructured, get$combined, getFinalBaseTypeWithProps, getServiceName,
30
+ } = csnUtils;
31
+ let { effectiveType, inspectRef } = csnUtils;
30
32
 
31
33
  if (isBetaEnabled(options, 'nestedProjections'))
32
34
  rewriteExpandInline();
@@ -182,7 +184,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
182
184
  }
183
185
 
184
186
  // We would be broken if we continue with assoc usage to now skipped
185
- throwWithError();
187
+ throwWithAnyError();
186
188
 
187
189
 
188
190
  for (const {
@@ -211,7 +213,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
211
213
  while (stack.length > 0) {
212
214
  const [ a, n ] = stack.pop();
213
215
  if (a[_dependents]) {
214
- Object.entries(a[_dependents]).forEach(([ dependentName, dependent ]) => {
216
+ forEach(a[_dependents], (dependentName, dependent) => {
215
217
  stack.push([ dependent, dependentName ]);
216
218
  });
217
219
  }
@@ -238,13 +240,13 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
238
240
  */
239
241
  function nextBase(parent, base) {
240
242
  if (parent.ref) {
241
- const finalBaseType = getFinalBaseType(parent._art.type);
243
+ const finalBaseType = getFinalBaseTypeWithProps(parent._art.type);
242
244
  const art = parent._art;
243
245
 
244
- if (finalBaseType === 'cds.Association' || finalBaseType === 'cds.Composition')
246
+ if (finalBaseType && (finalBaseType.type === 'cds.Association' || finalBaseType.type === 'cds.Composition'))
245
247
  return csn.definitions[art.target].elements;
246
248
 
247
- return art.elements || finalBaseType.elements;
249
+ return art.elements || finalBaseType?.elements;
248
250
  }
249
251
 
250
252
  return base;
@@ -253,7 +255,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
253
255
  /**
254
256
  * Rewrite expand and inline to "normal" refs
255
257
  *
256
- * @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
258
+ * @param {CSN.Artifact} root All elements visible from the query source ($combined)
257
259
  * @param {CSN.Column[]} columns
258
260
  * @param {string[]} excluding
259
261
  * @returns {{columns: Array, toMany: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
@@ -261,8 +263,12 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
261
263
  function rewrite(root, columns, excluding) {
262
264
  const allToMany = [];
263
265
  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);
266
+ const containsExpandInline = columns.some(col => col.expand || col.inline);
267
+ if (containsExpandInline) // Replace stars - needs to happen before resolving .expand/.inline since the .expand/.inline first path step affects the root *
268
+ columns = replaceStar(root, columns, excluding);
269
+ else
270
+ return { columns, toMany: [] };
271
+
266
272
  for (const col of columns) {
267
273
  if (col.expand) {
268
274
  // 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 +408,8 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
402
408
  * @returns {string|null} Name of any entity
403
409
  */
404
410
  function findAnEntity() {
405
- for (const [ name, artifact ] of Object.entries(csn.definitions)) {
406
- if (artifact.kind === 'entity' && !artifact.query)
411
+ for (const name in csn.definitions) {
412
+ if (Object.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
407
413
  return name;
408
414
  }
409
415
  return null;
@@ -544,7 +550,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
544
550
  /**
545
551
  * Replace the star and correctly put shadowed things in the right place.
546
552
  *
547
- * @param {Object} base The raw set of things a * can expand to
553
+ * @param {object} base The raw set of things a * can expand to
548
554
  * @param {Array} subs Things - the .expand/.inline or .columns
549
555
  * @param {string[]} [excluding=[]]
550
556
  * @returns {Array} If there was a star, expand it and handle shadowing/excluding, else just return subs