@sap/cds-compiler 3.4.2 → 3.5.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 (143) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +15 -16
  5. package/bin/cdshi.js +19 -6
  6. package/doc/CHANGELOG_ARCHIVE.md +2 -2
  7. package/doc/CHANGELOG_BETA.md +9 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  9. package/lib/api/main.js +61 -59
  10. package/lib/api/options.js +4 -2
  11. package/lib/api/validate.js +2 -2
  12. package/lib/base/cleanSymbols.js +2 -3
  13. package/lib/base/dictionaries.js +6 -6
  14. package/lib/base/error.js +2 -2
  15. package/lib/base/keywords.js +6 -6
  16. package/lib/base/location.js +11 -12
  17. package/lib/base/message-registry.js +177 -58
  18. package/lib/base/messages.js +252 -180
  19. package/lib/base/model.js +14 -11
  20. package/lib/base/node-helpers.js +9 -10
  21. package/lib/base/optionProcessorHelper.js +138 -129
  22. package/lib/checks/.eslintrc.json +2 -0
  23. package/lib/checks/actionsFunctions.js +5 -5
  24. package/lib/checks/annotationsOData.js +4 -4
  25. package/lib/checks/arrayOfs.js +1 -1
  26. package/lib/checks/cdsPersistence.js +1 -1
  27. package/lib/checks/checkForTypes.js +3 -3
  28. package/lib/checks/defaultValues.js +3 -3
  29. package/lib/checks/elements.js +7 -7
  30. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  31. package/lib/checks/foreignKeys.js +1 -1
  32. package/lib/checks/invalidTarget.js +4 -4
  33. package/lib/checks/managedInType.js +1 -1
  34. package/lib/checks/managedWithoutKeys.js +1 -1
  35. package/lib/checks/nonexpandableStructured.js +5 -3
  36. package/lib/checks/nullableKeys.js +1 -1
  37. package/lib/checks/onConditions.js +5 -6
  38. package/lib/checks/parameters.js +1 -1
  39. package/lib/checks/queryNoDbArtifacts.js +2 -2
  40. package/lib/checks/selectItems.js +4 -4
  41. package/lib/checks/sql-snippets.js +4 -4
  42. package/lib/checks/types.js +7 -7
  43. package/lib/checks/utils.js +4 -4
  44. package/lib/checks/validator.js +16 -13
  45. package/lib/compiler/.eslintrc.json +4 -1
  46. package/lib/compiler/assert-consistency.js +8 -7
  47. package/lib/compiler/builtins.js +14 -14
  48. package/lib/compiler/checks.js +123 -48
  49. package/lib/compiler/define.js +12 -13
  50. package/lib/compiler/extend.js +266 -60
  51. package/lib/compiler/finalize-parse-cdl.js +10 -5
  52. package/lib/compiler/index.js +17 -14
  53. package/lib/compiler/populate.js +14 -6
  54. package/lib/compiler/propagator.js +2 -0
  55. package/lib/compiler/resolve.js +2 -15
  56. package/lib/compiler/shared.js +27 -16
  57. package/lib/compiler/tweak-assocs.js +5 -6
  58. package/lib/compiler/utils.js +20 -0
  59. package/lib/edm/annotations/genericTranslation.js +604 -358
  60. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  61. package/lib/edm/csn2edm.js +275 -222
  62. package/lib/edm/edm.js +17 -3
  63. package/lib/edm/edmAnnoPreprocessor.js +6 -6
  64. package/lib/edm/edmInboundChecks.js +2 -2
  65. package/lib/edm/edmPreprocessor.js +107 -77
  66. package/lib/edm/edmUtils.js +44 -5
  67. package/lib/gen/Dictionary.json +210 -8
  68. package/lib/gen/language.checksum +1 -1
  69. package/lib/gen/language.interp +67 -63
  70. package/lib/gen/language.tokens +81 -81
  71. package/lib/gen/languageLexer.interp +4 -10
  72. package/lib/gen/languageLexer.js +854 -869
  73. package/lib/gen/languageLexer.tokens +79 -81
  74. package/lib/gen/languageParser.js +14309 -13832
  75. package/lib/inspect/inspectModelStatistics.js +2 -2
  76. package/lib/inspect/inspectPropagation.js +6 -6
  77. package/lib/inspect/inspectUtils.js +2 -2
  78. package/lib/json/from-csn.js +102 -55
  79. package/lib/json/to-csn.js +119 -198
  80. package/lib/language/antlrParser.js +5 -2
  81. package/lib/language/docCommentParser.js +6 -6
  82. package/lib/language/errorStrategy.js +43 -23
  83. package/lib/language/genericAntlrParser.js +113 -133
  84. package/lib/language/language.g4 +1550 -1506
  85. package/lib/language/multiLineStringParser.js +3 -3
  86. package/lib/language/textUtils.js +2 -2
  87. package/lib/main.js +3 -3
  88. package/lib/model/csnRefs.js +5 -0
  89. package/lib/model/csnUtils.js +130 -122
  90. package/lib/model/revealInternalProperties.js +1 -1
  91. package/lib/model/sortViews.js +4 -6
  92. package/lib/modelCompare/compare.js +2 -2
  93. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  94. package/lib/modelCompare/utils/filter.js +100 -0
  95. package/lib/optionProcessor.js +5 -0
  96. package/lib/render/.eslintrc.json +1 -0
  97. package/lib/render/DuplicateChecker.js +1 -1
  98. package/lib/render/manageConstraints.js +12 -12
  99. package/lib/render/toCdl.js +311 -276
  100. package/lib/render/toHdbcds.js +97 -94
  101. package/lib/render/toRename.js +5 -5
  102. package/lib/render/toSql.js +127 -223
  103. package/lib/render/utils/common.js +141 -108
  104. package/lib/render/utils/delta.js +227 -0
  105. package/lib/render/utils/sql.js +22 -6
  106. package/lib/render/utils/stringEscapes.js +3 -3
  107. package/lib/transform/db/.eslintrc.json +2 -0
  108. package/lib/transform/db/applyTransformations.js +3 -3
  109. package/lib/transform/db/assertUnique.js +13 -12
  110. package/lib/transform/db/associations.js +5 -5
  111. package/lib/transform/db/cdsPersistence.js +10 -8
  112. package/lib/transform/db/constraints.js +14 -14
  113. package/lib/transform/db/expansion.js +20 -22
  114. package/lib/transform/db/flattening.js +24 -42
  115. package/lib/transform/db/groupByOrderBy.js +3 -3
  116. package/lib/transform/db/temporal.js +6 -6
  117. package/lib/transform/db/transformExists.js +23 -23
  118. package/lib/transform/db/views.js +16 -16
  119. package/lib/transform/draft/.eslintrc.json +1 -35
  120. package/lib/transform/draft/db.js +10 -10
  121. package/lib/transform/draft/odata.js +2 -2
  122. package/lib/transform/forOdataNew.js +8 -29
  123. package/lib/transform/forRelationalDB.js +16 -6
  124. package/lib/transform/localized.js +11 -10
  125. package/lib/transform/odata/toFinalBaseType.js +41 -27
  126. package/lib/transform/odata/typesExposure.js +113 -47
  127. package/lib/transform/parseExpr.js +209 -106
  128. package/lib/transform/transformUtilsNew.js +17 -10
  129. package/lib/transform/translateAssocsToJoins.js +24 -19
  130. package/lib/transform/universalCsn/coreComputed.js +10 -10
  131. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  132. package/lib/transform/universalCsn/utils.js +3 -3
  133. package/lib/utils/file.js +5 -5
  134. package/lib/utils/moduleResolve.js +13 -13
  135. package/lib/utils/objectUtils.js +6 -6
  136. package/lib/utils/term.js +5 -2
  137. package/lib/utils/timetrace.js +51 -24
  138. package/package.json +5 -8
  139. package/share/messages/check-proper-type-of.md +1 -1
  140. package/share/messages/message-explanations.json +1 -1
  141. package/share/messages/redirected-to-complex.md +4 -4
  142. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
  143. package/lib/modelCompare/filter.js +0 -83
@@ -31,7 +31,7 @@ const { implicitAs } = require('../../model/csnRefs');
31
31
  * @param {(a: string) => string} renderArgs Function to render function arguments
32
32
  * @returns {string} Function string
33
33
  */
34
- function renderFunc( funcName, node, dialect, renderArgs) {
34
+ function renderFunc( funcName, node, dialect, renderArgs ) {
35
35
  if (funcWithoutParen( node, dialect ))
36
36
  return funcName;
37
37
  return `${funcName}(${renderArgs( node )})`;
@@ -60,7 +60,7 @@ function funcWithoutParen( node, dialect ) {
60
60
  *
61
61
  * @returns {string} The rendered xpr
62
62
  */
63
- function beautifyExprArray(tokens) {
63
+ function beautifyExprArray( tokens ) {
64
64
  // Simply concatenate array parts with spaces (with a tiny bit of beautification)
65
65
  let result = '';
66
66
  for (let i = 0; i < tokens.length; i++) {
@@ -72,7 +72,6 @@ function beautifyExprArray(tokens) {
72
72
  return result;
73
73
  }
74
74
 
75
-
76
75
  /**
77
76
  * Get the part that is really the name of this artifact and not just prefix caused by a context/service
78
77
  *
@@ -80,13 +79,12 @@ function beautifyExprArray(tokens) {
80
79
  * @param {string} artifactName Artifact name to use
81
80
  * @returns {string} non-prefix part of the artifact name
82
81
  */
83
- function getRealName(csn, artifactName) {
82
+ function getRealName( csn, artifactName ) {
84
83
  const parts = artifactName.split('.');
85
84
  // Length of 1 -> There can be no prefix
86
85
  if (parts.length === 1)
87
86
  return artifactName;
88
87
 
89
-
90
88
  const namespace = getNamespace(csn, artifactName);
91
89
  const startIndex = namespace ? namespace.split('.').length : 0;
92
90
  let indexOfLastParent = startIndex;
@@ -120,7 +118,7 @@ function getRealName(csn, artifactName) {
120
118
  * @param {string} artifactName Name of the artifact to check for
121
119
  * @returns {string | null} Name of the topmost context or null
122
120
  */
123
- function getParentContextName(csn, artifactName) {
121
+ function getParentContextName( csn, artifactName ) {
124
122
  const parts = artifactName.split('.');
125
123
  for (let i = 0; i < parts.length - 1; i++) {
126
124
  const name = parts.slice(0, i).join('.');
@@ -129,7 +127,6 @@ function getParentContextName(csn, artifactName) {
129
127
  if (art && (art.kind === 'context' || art.kind === 'service'))
130
128
  return name;
131
129
  }
132
-
133
130
  return null;
134
131
  }
135
132
 
@@ -140,7 +137,7 @@ function getParentContextName(csn, artifactName) {
140
137
  *
141
138
  * @param {Function[]} killList Array to add cleanup functions to
142
139
  */
143
- function addContextMarkers(csn, killList) {
140
+ function addContextMarkers( csn, killList ) {
144
141
  const contextsToCreate = Object.create(null);
145
142
  forEachDefinition(csn, (art, artifactName) => {
146
143
  const namespace = getNamespace(csn, artifactName);
@@ -175,7 +172,7 @@ function addContextMarkers(csn, killList) {
175
172
  * @param {string} artifactName Name of the current context
176
173
  * @returns {string[]} All possible context names inbetween
177
174
  */
178
- function getIntermediateContextNames(csn, parentName, artifactName) {
175
+ function getIntermediateContextNames( csn, parentName, artifactName ) {
179
176
  const parentLength = parentName.split('.').length;
180
177
  const parts = artifactName.split('.');
181
178
  const names = [];
@@ -197,7 +194,7 @@ function getIntermediateContextNames(csn, parentName, artifactName) {
197
194
  * @param {string} artifactName
198
195
  * @param {Function[]} killList Array to add cleanup functions to
199
196
  */
200
- function addMissingChildContexts(csn, artifactName, killList) {
197
+ function addMissingChildContexts( csn, artifactName, killList ) {
201
198
  // Get all other definitions sharing the same prefix, sorted by shortest first
202
199
  const possibleNames = Object.keys(csn.definitions).filter(name => name.startsWith(`${artifactName}.`)).sort((a, b) => a.length - b.length);
203
200
  for (const name of possibleNames) {
@@ -206,7 +203,7 @@ function addMissingChildContexts(csn, artifactName, killList) {
206
203
  addPossibleGaps(name.slice(artifactName.length + 1).split('.'), artifactName);
207
204
  }
208
205
 
209
- function addPossibleGaps(possibleGaps, gapArtifactName) {
206
+ function addPossibleGaps( possibleGaps, gapArtifactName ) {
210
207
  for (const gap of possibleGaps) {
211
208
  gapArtifactName += `.${gap}`;
212
209
  if (!csn.definitions[gapArtifactName]) {
@@ -317,7 +314,7 @@ const cdsToHdbcdsTypes = {
317
314
  * @param {CSN.Column} column Column from the same query
318
315
  * @returns {CSN.Element}
319
316
  */
320
- function findElement(elements, column) {
317
+ function findElement( elements, column ) {
321
318
  if (!elements)
322
319
  return undefined;
323
320
  if (column.as)
@@ -336,7 +333,7 @@ function findElement(elements, column) {
336
333
  *
337
334
  * @param {Function[]} killList Array to add cleanup functions to
338
335
  */
339
- function addIntermediateContexts(csn, killList) {
336
+ function addIntermediateContexts( csn, killList ) {
340
337
  for (const artifactName in csn.definitions) {
341
338
  const artifact = csn.definitions[artifactName];
342
339
  if ((artifact.kind === 'context') && !artifact._ignore) {
@@ -368,7 +365,7 @@ function addIntermediateContexts(csn, killList) {
368
365
  * @param {CSN.Options} options To check for `disableHanaComments`
369
366
  * @returns {boolean}
370
367
  */
371
- function hasHanaComment(obj, options) {
368
+ function hasHanaComment( obj, options ) {
372
369
  return !options.disableHanaComments && typeof obj.doc === 'string';
373
370
  }
374
371
  /**
@@ -380,7 +377,7 @@ function hasHanaComment(obj, options) {
380
377
  * @param {CSN.Artifact|CSN.Element} obj
381
378
  * @returns {string}
382
379
  */
383
- function getHanaComment(obj) {
380
+ function getHanaComment( obj ) {
384
381
  return obj.doc.split('\n\n')[0].trim();
385
382
  }
386
383
 
@@ -392,7 +389,7 @@ function getHanaComment(obj) {
392
389
  * @param {object} obj
393
390
  * @returns {object} object with .front and .back
394
391
  */
395
- function getSqlSnippets(options, obj) {
392
+ function getSqlSnippets( options, obj ) {
396
393
  const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
397
394
  const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
398
395
 
@@ -404,8 +401,7 @@ function getSqlSnippets(options, obj) {
404
401
  *
405
402
  * @callback renderPart
406
403
  * @param {object|array} expression
407
- * @param {CdlRenderEnvironment} env
408
- * @this {{inline: Boolean, nestedExpr: Boolean}}
404
+ * @this {ExpressionRenderer}
409
405
  * @returns {string}
410
406
  */
411
407
 
@@ -415,7 +411,27 @@ function getSqlSnippets(options, obj) {
415
411
  *
416
412
  * @typedef {object} ExpressionConfiguration
417
413
  * @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
418
- * @property {renderPart} explicitTypeCast
414
+ * @property {renderPart} typeCast
415
+ * @property {renderPart} val
416
+ * @property {renderPart} enum
417
+ * @property {renderPart} ref
418
+ * @property {renderPart} aliasOnly
419
+ * @property {renderPart} windowFunction
420
+ * @property {renderPart} func
421
+ * @property {renderPart} xpr
422
+ * @property {renderPart} SELECT
423
+ * @property {renderPart} SET
424
+ * @property {Function} [visitExpr]
425
+ * @property {Function} [renderExpr]
426
+ * @property {Function} [renderSubExpr]
427
+ * @property {boolean} [isNestedXpr]
428
+ * @property {object} [env]
429
+ */
430
+
431
+ /**
432
+ * @typedef {object} ExpressionRenderer
433
+ * @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
434
+ * @property {renderPart} typeCast
419
435
  * @property {renderPart} val
420
436
  * @property {renderPart} enum
421
437
  * @property {renderPart} ref
@@ -425,114 +441,130 @@ function getSqlSnippets(options, obj) {
425
441
  * @property {renderPart} xpr
426
442
  * @property {renderPart} SELECT
427
443
  * @property {renderPart} SET
428
- * @property {boolean} [inline]
429
- * @property {boolean} [nestedExpr]
444
+ * @property {Function} visitExpr
445
+ * @property {Function} renderExpr
446
+ * @property {Function} renderSubExpr
447
+ * @property {boolean} isNestedXpr
448
+ * @property {object} env
449
+ */
450
+
451
+ /**
452
+ * If `xpr` has a `cast` property, return a copy without it, otherwise return `xpr`.
453
+ * Useful for removing e.g. top-level CDL-style casts that should not be rendered as CAST().
454
+ *
455
+ * @param xpr
430
456
  */
457
+ function withoutCast( xpr ) {
458
+ return !xpr.cast ? xpr : { ...xpr, cast: undefined };
459
+ }
431
460
 
432
461
  /**
433
462
  * Render an expression (including paths and values) or condition 'x'.
434
463
  * (no trailing LF, don't indent if inline)
435
464
  *
436
- * @param {ExpressionConfiguration} renderer
437
- * @returns {Function} Rendered expression
465
+ * @param {ExpressionConfiguration} rendererBase
466
+ * @returns {ExpressionRenderer} Expression rendering utility
438
467
  */
439
- function getExpressionRenderer(renderer) {
468
+ function createExpressionRenderer( rendererBase ) {
469
+ const renderer = Object.create(rendererBase);
470
+ renderer.visitExpr = visitExpr;
440
471
  /**
441
- * Render an expression (including paths and values) or condition 'x'.
442
- * (no trailing LF, don't indent if inline)
443
- *
444
- * @todo Reuse this with toCdl
445
- * @param {Array|object|string} expr Expression to render
446
- * @param {object} env Render environment
447
- * @param {boolean} [inline=true] Whether to render the expression inline
448
- * @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
449
- * @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
450
- * Note: This is a hack for casts() inside groupBy.
451
- * @returns {string} Rendered expression
472
+ * @param {any} x
473
+ * @param {object} env
452
474
  */
453
- return function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
454
- // Compound expression
455
- if (Array.isArray(expr)) {
456
- const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
457
- return beautifyExprArray(tokens);
458
- }
459
- else if (typeof expr === 'object' && expr !== null) {
460
- if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type && !expr.cast.target)
461
- return renderer.explicitTypeCast.call({ inline, nestedExpr }, expr, env);
462
- return renderExprObject(expr);
463
- }
464
- // Not a literal value but part of an operator, function etc - just leave as it is
465
- // FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
466
- return renderer.finalize.call({ inline, nestedExpr }, expr, env);
467
-
468
-
469
- /**
470
- * Various special cases represented as objects
471
- *
472
- * @param {object} x Expression
473
- * @returns {string} String representation of the expression
474
- */
475
- function renderExprObject(x) {
476
- if (x.list) { // TODO: Does this still exist?
477
- return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
478
- }
479
- else if (x.val !== undefined) {
480
- return renderer.val.call({ inline, nestedExpr }, x, env);
481
- }
482
- // Enum symbol
483
- else if (x['#']) {
484
- return renderer.enum.call({ inline, nestedExpr }, x, env);
485
- }
486
- // Reference: Array of path steps, possibly preceded by ':'
487
- else if (x.ref) {
488
- return renderer.ref.call({ inline, nestedExpr }, x, env);
489
- }
490
- // Function call, possibly with args (use '=>' for named args)
491
- else if (x.func) {
492
- if (x.xpr)
493
- return renderer.windowFunction.call({ inline, nestedExpr }, x, env);
494
- return renderer.func.call({ inline, nestedExpr }, x, env);
495
- }
496
- // Nested expression
497
- else if (x.xpr) {
498
- return renderer.xpr.call({ inline, nestedExpr }, x, env);
499
- }
500
- // Sub-select
501
- else if (x.SELECT) {
502
- return renderer.SELECT.call({ inline, nestedExpr }, x, env);
503
- }
504
- else if (x.SET) {
505
- return renderer.SET.call({ inline, nestedExpr }, x, env);
506
- }
507
- else if (x.as && x.cast && x.cast.type && x.cast.target) {
508
- return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
509
- }
510
-
511
- throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
512
- }
475
+ renderer.renderExpr = function renderExpr(x, env) {
476
+ /** @type {ExpressionRenderer} */
477
+ const renderObj = Object.create(renderer);
478
+ renderObj.env = env || this?.env;
479
+ // The outermost expression is not nested. All `.xpr` inside `expr`
480
+ // are nested. This information is used for adding parentheses around
481
+ // expressions (see `this.xpr()`).
482
+ renderObj.isNestedXpr = false;
483
+ return renderObj.visitExpr(x);
513
484
  };
485
+ /**
486
+ * @param {any} x
487
+ * @param {object} env
488
+ */
489
+ renderer.renderSubExpr = function renderSubExpr(x, env) {
490
+ /** @type {ExpressionRenderer} */
491
+ const renderObj = Object.create(renderer);
492
+ renderObj.env = env || this?.env;
493
+ renderObj.isNestedXpr = true;
494
+ return renderObj.visitExpr(x);
495
+ };
496
+
497
+ return renderer;
514
498
  }
515
499
 
500
+
516
501
  /**
517
- * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
502
+ * Render an expression (including paths and values) or condition 'x'.
503
+ * (no trailing LF, don't indent if inline)
504
+ *
505
+ * `this` must refer to an object of type `ExpressionRenderer`, see
506
+ * `createExpressionRenderer()`
518
507
  *
519
- * @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
520
- * @property {CSN.Path} [path] CSN path to the current artifact
521
- * @property {string} [currentArtifactName] Name of the current artifact
522
- * @property {{[name: string]: {
523
- quotedName: string,
524
- quotedAlias: string
525
- }}} topLevelAliases Dictionary of aliases for used artifact names
508
+ * @param {any} x (Sub-)Expression to render
526
509
  *
527
- * @property {string} namePrefix Current name prefix (including trailing dot if not empty)
528
- * @property {boolean} [skipKeys] Skip rendering keys in subqueries
529
- * @property {CSN.Artifact} [_artifact] The original view artifact, used when rendering queries
510
+ * @this ExpressionRenderer
511
+ * @returns {string} Rendered expression
530
512
  */
513
+ function visitExpr( x ) {
514
+ if (Array.isArray(x)) {
515
+ // Compound expression, e.g. for on- or where-conditions.
516
+ // If xpr is part of an array, it's always a nested xpr,
517
+ // e.g. CSN for `(1=1 or 2=2) and 3=3`.
518
+ const tokens = x.map(item => this.renderSubExpr(item));
519
+ return beautifyExprArray(tokens);
520
+ }
521
+ else if (typeof x !== 'object' || x === null) {
522
+ // Not a literal value but part of an operator, function etc - just leave as it is
523
+ return this.finalize(x);
524
+ }
525
+ else if (x.cast?.type && !x.cast.target) {
526
+ return this.typeCast(x);
527
+ }
528
+ else if (x.list) {
529
+ // Render as non-nested expr.
530
+ return `(${x.list.map(item => this.renderExpr(item)).join(', ')})`;
531
+ }
532
+ else if (x.val !== undefined) {
533
+ return this.val(x);
534
+ }
535
+ else if (x['#']) {
536
+ // Enum symbol
537
+ return this.enum(x);
538
+ }
539
+ else if (x.ref) {
540
+ // Reference: Array of path steps, possibly preceded by ':'
541
+ return this.ref(x);
542
+ }
543
+ else if (x.func) {
544
+ // Function call, possibly with args (use '=>' for named args)
545
+ if (x.xpr)
546
+ return this.windowFunction(x);
547
+ return this.func(x);
548
+ }
549
+ else if (x.xpr) {
550
+ return this.xpr(x);
551
+ }
552
+ else if (x.SELECT) {
553
+ return this.SELECT(x);
554
+ }
555
+ else if (x.SET) {
556
+ return this.SET(x);
557
+ }
558
+ else if (x.as) {
559
+ return this.aliasOnly(x);
560
+ }
561
+
562
+ throw new ModelError(`renderExpr(): Unknown expression: ${JSON.stringify(x)}`);
563
+ }
531
564
 
532
565
  module.exports = {
533
566
  renderFunc,
534
- getExpressionRenderer,
535
- beautifyExprArray,
567
+ createExpressionRenderer,
536
568
  getNamespace,
537
569
  getRealName,
538
570
  addIntermediateContexts,
@@ -544,4 +576,5 @@ module.exports = {
544
576
  findElement,
545
577
  funcWithoutParen,
546
578
  getSqlSnippets,
579
+ withoutCast,
547
580
  };
@@ -0,0 +1,227 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Encapsulate all the functions needed to render SQL ALTER/DROP/ADD statements.
5
+ */
6
+ class DeltaRenderer {
7
+ constructor(options, scopedFunctions) {
8
+ this.options = options;
9
+ this.scopedFunctions = scopedFunctions;
10
+ }
11
+
12
+ /**
13
+ * Render column additions as SQL. Checks for duplicate elements.
14
+ */
15
+ addColumnsFromElementStrings(artifactName, eltStrings) {
16
+ return eltStrings.map(eltString => `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD ${eltString};`);
17
+ }
18
+
19
+ /**
20
+ * Render column additions as SQL. Checks for duplicate elements.
21
+ */
22
+ addColumnsFromElementsObj(artifactName, elementsObj, env, duplicateChecker) {
23
+ // Only extend with 'ADD' for elements/associations
24
+ // TODO: May also include 'RENAME' at a later stage
25
+ const alterEnv = this.scopedFunctions.activateAlterMode(env);
26
+ const elements = Object.entries(elementsObj)
27
+ .map(([ name, elt ]) => this.scopedFunctions.renderElement(artifactName, name, elt, duplicateChecker, null, alterEnv))
28
+ .filter(s => s !== '');
29
+
30
+ if (elements.length)
31
+ return this.addColumnsFromElementStrings(artifactName, elements);
32
+
33
+ return [];
34
+ }
35
+
36
+ /**
37
+ * By default, we don't support rendering association-alters - only for HANA
38
+ */
39
+ addAssociations(_artifactName, _extElements, _env) {
40
+ return [];
41
+ }
42
+
43
+ /**
44
+ * Render key addition as SQL.
45
+ */
46
+ addKey(artifactName, elementsObj) {
47
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD ${this.primaryKey(elementsObj)};` ];
48
+ }
49
+
50
+ /**
51
+ * Render column removals as SQL.
52
+ */
53
+ dropColumns(artifactName, sqlIds) {
54
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP ${sqlIds.join(', ')};` ];
55
+ }
56
+
57
+ /**
58
+ * No associations by default - only for HANA.
59
+ */
60
+ dropAssociation(_artifactName, _sqlId) {
61
+ return [];
62
+ }
63
+
64
+ /**
65
+ * Render primary-key removals as SQL.
66
+ */
67
+ dropKey(artifactName) {
68
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP PRIMARY KEY;` ];
69
+ }
70
+
71
+ /**
72
+ * Render column modifications as SQL.
73
+ */
74
+ alterColumns(artifactName, columnName, delta, definitionsStr) {
75
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER (${definitionsStr});` ];
76
+ }
77
+
78
+ /**
79
+ * Render primary keys as SQL.
80
+ */
81
+ primaryKey(elementsObj) {
82
+ const primaryKeys = Object.keys(elementsObj)
83
+ .filter(name => elementsObj[name].key && !elementsObj[name].virtual)
84
+ .map(name => this.scopedFunctions.quoteSqlId(name))
85
+ .join(', ');
86
+ return primaryKeys && `PRIMARY KEY(${primaryKeys})`;
87
+ }
88
+
89
+ /**
90
+ * Render entity-comment modifications as SQL.
91
+ */
92
+ alterEntityComment(artifactName, comment) {
93
+ return [ `COMMENT ON TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} IS ${this.comment(comment)};` ];
94
+ }
95
+
96
+ /**
97
+ * Render column-comment modifications as SQL.
98
+ */
99
+ alterColumnComment(artifactName, columnName, comment) {
100
+ return [ `COMMENT ON COLUMN ${this.scopedFunctions.renderArtifactName(artifactName)}.${columnName} IS ${this.comment(comment)};` ];
101
+ }
102
+
103
+ /**
104
+ * Render comment string.
105
+ */
106
+ comment(comment) {
107
+ return comment && this.scopedFunctions.renderStringForSql(this.scopedFunctions.getHanaComment({ doc: comment }), this.options.sqlDialect) || 'NULL';
108
+ }
109
+
110
+ /**
111
+ * Alter SQL snippet for entity.
112
+ */
113
+ alterEntitySqlSnippet(artifactName, snippet) {
114
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ${snippet};` ];
115
+ }
116
+
117
+ /**
118
+ * Concatenate multiple statements which are to be treated as one by the API caller.
119
+ */
120
+ concat(...statements) {
121
+ return [ statements.join('\n') ];
122
+ }
123
+ }
124
+
125
+ class DeltaRendererHana extends DeltaRenderer {
126
+ /**
127
+ * Render column additions as HANA SQL. Checks for duplicate elements.
128
+ */
129
+ addColumnsFromElementStrings(artifactName, eltStrings) {
130
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD (${eltStrings.join(', ')});` ];
131
+ }
132
+
133
+ /**
134
+ * Render association additions as HANA SQL.
135
+ * TODO duplicity check
136
+ */
137
+ addAssociations(artifactName, elementsObj, env) {
138
+ return Object.entries(elementsObj)
139
+ .map(([ name, elt ]) => this.scopedFunctions.renderAssociationElement(name, elt, env))
140
+ .filter(s => s !== '')
141
+ .map(eltStr => `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD ASSOCIATION (${eltStr});`);
142
+ }
143
+
144
+ /**
145
+ * Render column removals as HANA SQL.
146
+ */
147
+ dropColumns(artifactName, sqlIds) {
148
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP (${sqlIds.join(', ')});` ];
149
+ }
150
+
151
+ /**
152
+ * Render association removals as HANA SQL.
153
+ */
154
+ dropAssociation(artifactName, sqlId) {
155
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP ASSOCIATION ${sqlId};` ];
156
+ }
157
+ }
158
+
159
+ class DeltaRendererPostgres extends DeltaRenderer {
160
+ /**
161
+ * Render primary-key removals as SQL.
162
+ * @todo tableName is escaped - we cannot simply add _pkey
163
+ */
164
+ dropKey(artifactName) {
165
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP CONSTRAINT ${this.scopedFunctions.renderArtifactName(`${artifactName}_pkey`)};` ];
166
+ }
167
+
168
+ /**
169
+ * Render column removals as SQL.
170
+ */
171
+ dropColumns(artifactName, sqlIds) {
172
+ return sqlIds.map(sqlId => `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP ${sqlId};`);
173
+ }
174
+
175
+ /**
176
+ * Render column modifications as Postgres SQL - no ( ), special NOT NULL.
177
+ */
178
+ alterColumns(artifactName, columnName, delta, definitionsStr) {
179
+ const sqls = [];
180
+ if (delta.new.notNull === true || delta.new.key === true)
181
+ definitionsStr = definitionsStr.replace(' NOT NULL', ''); // TODO: Is this robust enough?
182
+ else if (delta.new.notNull === false || delta.new.$notNull === false)
183
+ definitionsStr = definitionsStr.replace(' NULL', ''); // TODO: Is this robust enough?
184
+
185
+ sqls.push(`ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${definitionsStr};`);
186
+ if (delta.new.notNull && !delta.old.notNull)
187
+ sqls.push(`ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${columnName} SET NOT NULL;`);
188
+ else if (delta.old.notNull && !delta.new.notNull)
189
+ sqls.push(`ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${columnName} DROP NOT NULL;`);
190
+
191
+ return sqls;
192
+ }
193
+ }
194
+
195
+ class DeltaRendererH2 extends DeltaRenderer {
196
+ /**
197
+ * Render column modifications as H2 SQL - no ().
198
+ */
199
+ alterColumns(artifactName, columnName, delta, definitionsStr) {
200
+ return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${definitionsStr};` ];
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Return an object encapsulating the render-functions for ALTER/DROP for a given db dialect.
206
+ *
207
+ * @param {CSN.Options} options
208
+ * @param {object} scopedFunctions
209
+ * @returns {DeltaRenderer}
210
+ */
211
+ function getDeltaRenderer( options, scopedFunctions ) {
212
+ switch (options.sqlDialect) {
213
+ case 'hana':
214
+ return new DeltaRendererHana(options, scopedFunctions);
215
+ case 'h2':
216
+ return new DeltaRendererH2(options, scopedFunctions);
217
+ case 'postgres':
218
+ return new DeltaRendererPostgres(options, scopedFunctions);
219
+ default:
220
+ return new DeltaRenderer(options, scopedFunctions);
221
+ }
222
+ }
223
+
224
+
225
+ module.exports = {
226
+ getDeltaRenderer,
227
+ };
@@ -18,7 +18,7 @@ const { ModelError } = require('../../base/error');
18
18
  *
19
19
  * @returns {string} SQL statement which can be used to create the referential constraint on the db.
20
20
  */
21
- function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint = false) {
21
+ function renderReferentialConstraint( constraint, indent, toUpperCase, csn, options, alterConstraint = false ) {
22
22
  let quoteId;
23
23
  // for to.hana we can't utilize the sql identifier utils
24
24
  if (options.transformation === 'hdbcds') {
@@ -29,7 +29,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
29
29
  };
30
30
  }
31
31
  else {
32
- quoteId = getIdentifierUtils(options).quoteSqlId;
32
+ quoteId = getIdentifierUtils(csn, options).quoteSqlId;
33
33
  }
34
34
  if (toUpperCase) {
35
35
  constraint.identifier = constraint.identifier.toUpperCase();
@@ -81,8 +81,8 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
81
81
  * @param {CSN.Options} options
82
82
  * @returns quoteSqlId and prepareIdentifier function
83
83
  */
84
- function getIdentifierUtils(options) {
85
- return { quoteSqlId, prepareIdentifier };
84
+ function getIdentifierUtils( csn, options ) {
85
+ return { quoteSqlId, prepareIdentifier, renderArtifactName };
86
86
  /**
87
87
  * Return 'name' with appropriate "-quotes.
88
88
  * Additionally perform the following conversions on 'name'
@@ -95,7 +95,7 @@ function getIdentifierUtils(options) {
95
95
  * @param {string} name Identifier to quote
96
96
  * @returns {string} Quoted identifier
97
97
  */
98
- function quoteSqlId(name) {
98
+ function quoteSqlId( name ) {
99
99
  name = prepareIdentifier(name);
100
100
 
101
101
  switch (options.sqlMapping) {
@@ -120,7 +120,7 @@ function getIdentifierUtils(options) {
120
120
  * @param {string} name Identifier to prepare
121
121
  * @returns {string} Identifier prepared for quoting
122
122
  */
123
- function prepareIdentifier(name) {
123
+ function prepareIdentifier( name ) {
124
124
  // Sanity check
125
125
  if (options.sqlDialect === 'sqlite' && options.sqlMapping !== 'plain')
126
126
  throw new ModelError(`Not expecting ${options.sqlMapping} names for 'sqlite' dialect`);
@@ -137,6 +137,22 @@ function getIdentifierUtils(options) {
137
137
  throw new ModelError(`No matching rendering found for naming mode ${options.sqlMapping}`);
138
138
  }
139
139
  }
140
+
141
+ /**
142
+ * Given the following artifact name: namespace.prefix.entity.with.dot, render the following,
143
+ * depending on the naming mode:
144
+ * - plain: NAMESPACE_PREFIX_ENTITY_WITH_DOT
145
+ * - quoted: namespace.prefix.entity_with_dot
146
+ * - hdbcds: namespace::prefix.entity_with_dot
147
+ *
148
+ *
149
+ * @param {string} artifactName Artifact name to render
150
+ *
151
+ * @returns {string} Artifact name
152
+ */
153
+ function renderArtifactName( artifactName ) {
154
+ return quoteSqlId(getResultingName(csn, options.sqlMapping, artifactName));
155
+ }
140
156
  }
141
157
 
142
158