@sap/cds-compiler 2.12.0 → 2.13.6

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 (118) hide show
  1. package/CHANGELOG.md +110 -15
  2. package/bin/cdsc.js +13 -13
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +28 -63
  8. package/lib/api/options.js +3 -3
  9. package/lib/api/validate.js +0 -5
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +25 -4
  16. package/lib/base/messages.js +16 -26
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +158 -123
  19. package/lib/checks/annotationsOData.js +1 -1
  20. package/lib/checks/cdsPersistence.js +2 -1
  21. package/lib/checks/enricher.js +17 -1
  22. package/lib/checks/invalidTarget.js +3 -1
  23. package/lib/checks/managedWithoutKeys.js +3 -1
  24. package/lib/checks/selectItems.js +4 -4
  25. package/lib/checks/sql-snippets.js +27 -26
  26. package/lib/checks/types.js +1 -1
  27. package/lib/checks/validator.js +4 -7
  28. package/lib/compiler/assert-consistency.js +5 -3
  29. package/lib/compiler/builtins.js +8 -6
  30. package/lib/compiler/checks.js +14 -3
  31. package/lib/compiler/cycle-detector.js +1 -1
  32. package/lib/compiler/define.js +1103 -0
  33. package/lib/compiler/extend.js +983 -0
  34. package/lib/compiler/finalize-parse-cdl.js +231 -0
  35. package/lib/compiler/index.js +32 -13
  36. package/lib/compiler/kick-start.js +190 -0
  37. package/lib/compiler/moduleLayers.js +4 -4
  38. package/lib/compiler/populate.js +1226 -0
  39. package/lib/compiler/propagator.js +111 -46
  40. package/lib/compiler/resolve.js +1433 -0
  41. package/lib/compiler/shared.js +64 -37
  42. package/lib/compiler/tweak-assocs.js +529 -0
  43. package/lib/compiler/utils.js +197 -33
  44. package/lib/edm/.eslintrc.json +5 -0
  45. package/lib/edm/annotations/genericTranslation.js +5 -9
  46. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  47. package/lib/edm/csn2edm.js +9 -8
  48. package/lib/edm/edm.js +11 -12
  49. package/lib/edm/edmPreprocessor.js +137 -73
  50. package/lib/edm/edmUtils.js +116 -22
  51. package/lib/gen/Dictionary.json +10 -3
  52. package/lib/gen/language.checksum +1 -1
  53. package/lib/gen/language.interp +9 -1
  54. package/lib/gen/language.tokens +86 -83
  55. package/lib/gen/languageLexer.interp +10 -1
  56. package/lib/gen/languageLexer.js +860 -833
  57. package/lib/gen/languageLexer.tokens +78 -75
  58. package/lib/gen/languageParser.js +5282 -4265
  59. package/lib/json/from-csn.js +12 -1
  60. package/lib/json/to-csn.js +126 -66
  61. package/lib/language/docCommentParser.js +2 -2
  62. package/lib/language/genericAntlrParser.js +76 -3
  63. package/lib/language/language.g4 +297 -130
  64. package/lib/language/multiLineStringParser.js +5 -5
  65. package/lib/main.d.ts +468 -59
  66. package/lib/main.js +35 -9
  67. package/lib/model/api.js +3 -1
  68. package/lib/model/csnRefs.js +225 -156
  69. package/lib/model/csnUtils.js +192 -223
  70. package/lib/model/enrichCsn.js +70 -29
  71. package/lib/model/revealInternalProperties.js +27 -6
  72. package/lib/model/sortViews.js +2 -1
  73. package/lib/modelCompare/compare.js +17 -12
  74. package/lib/optionProcessor.js +5 -4
  75. package/lib/render/manageConstraints.js +35 -32
  76. package/lib/render/toCdl.js +73 -288
  77. package/lib/render/toHdbcds.js +25 -23
  78. package/lib/render/toSql.js +98 -41
  79. package/lib/render/utils/common.js +5 -10
  80. package/lib/render/utils/sql.js +4 -3
  81. package/lib/render/utils/stringEscapes.js +111 -0
  82. package/lib/sql-identifier.js +1 -1
  83. package/lib/transform/.eslintrc.json +5 -0
  84. package/lib/transform/db/.eslintrc.json +2 -0
  85. package/lib/transform/db/applyTransformations.js +35 -12
  86. package/lib/transform/db/assertUnique.js +1 -1
  87. package/lib/transform/db/associations.js +103 -305
  88. package/lib/transform/db/cdsPersistence.js +2 -2
  89. package/lib/transform/db/constraints.js +55 -52
  90. package/lib/transform/db/expansion.js +46 -24
  91. package/lib/transform/db/flattening.js +553 -102
  92. package/lib/transform/db/groupByOrderBy.js +3 -1
  93. package/lib/transform/db/transformExists.js +59 -6
  94. package/lib/transform/db/views.js +5 -4
  95. package/lib/transform/draft/.eslintrc.json +38 -0
  96. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  97. package/lib/transform/draft/odata.js +227 -0
  98. package/lib/transform/forHanaNew.js +67 -183
  99. package/lib/transform/forOdataNew.js +17 -171
  100. package/lib/transform/localized.js +34 -19
  101. package/lib/transform/odata/generateForeignKeyElements.js +1 -1
  102. package/lib/transform/odata/referenceFlattener.js +95 -89
  103. package/lib/transform/odata/structureFlattener.js +1 -1
  104. package/lib/transform/odata/toFinalBaseType.js +86 -12
  105. package/lib/transform/odata/typesExposure.js +5 -5
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +36 -22
  108. package/lib/transform/translateAssocsToJoins.js +2 -19
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +170 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/objectUtils.js +30 -0
  114. package/package.json +1 -1
  115. package/share/messages/README.md +26 -0
  116. package/lib/compiler/definer.js +0 -2361
  117. package/lib/compiler/resolver.js +0 -3079
  118. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -20,6 +20,7 @@ const { makeMessageFunction } = require('../base/messages');
20
20
  const { timetrace } = require('../utils/timetrace');
21
21
 
22
22
  const { smartId, delimitedId } = require('../sql-identifier');
23
+ const { ModelError } = require('../base/error');
23
24
 
24
25
  const $PROJECTION = '$projection';
25
26
  const $SELF = '$self';
@@ -152,7 +153,6 @@ function toHdbcdsSource(csn, options) {
152
153
 
153
154
  switch (art.kind) {
154
155
  case 'entity':
155
- case 'view':
156
156
  // FIXME: For HANA CDS, we need to replace $self at the beginning of paths in association ON-condition
157
157
  // by the full name of the artifact we are rendering (should actually be done by forHana, but that is
158
158
  // somewhat difficult because this kind of absolute path is quite unusual). In order not to have to pass
@@ -177,7 +177,7 @@ function toHdbcdsSource(csn, options) {
177
177
  case 'event':
178
178
  return '';
179
179
  default:
180
- throw new Error(`Unknown artifact kind: ${art.kind}`);
180
+ throw new ModelError(`Unknown artifact kind: ${art.kind}`);
181
181
  }
182
182
  }
183
183
 
@@ -213,7 +213,7 @@ function toHdbcdsSource(csn, options) {
213
213
  }
214
214
 
215
215
  /**
216
- * Check wether the given context is the direct parent of the containee.
216
+ * Check whether the given context is the direct parent of the containee.
217
217
  *
218
218
  * @param {string} containee Name of the contained artifact
219
219
  * @param {string} contextName Name of the (grand?)parent context
@@ -313,7 +313,7 @@ function toHdbcdsSource(csn, options) {
313
313
  return `${result + renderedSubArtifacts + env.indent}};\n`;
314
314
  }
315
315
  /**
316
- * Check wether the given context is shadowed, i.e. part of his name prefix is shared by a
316
+ * Check whether the given context is shadowed, i.e. part of his name prefix is shared by a
317
317
  * non-context/service/namespace definition
318
318
  *
319
319
  * @param {string} artifactName
@@ -469,7 +469,7 @@ function toHdbcdsSource(csn, options) {
469
469
  // in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
470
470
  tc = tc.hana;
471
471
  if (!tc)
472
- throw new Error('Expecting a HANA technical configuration');
472
+ throw new ModelError('Expecting a HANA technical configuration');
473
473
 
474
474
  result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
475
475
 
@@ -544,7 +544,7 @@ function toHdbcdsSource(csn, options) {
544
544
  * @param {CSN.Element} elm Content of the element
545
545
  * @param {CdlRenderEnvironment} env Environment
546
546
  * @param {DuplicateChecker} [duplicateChecker] Utility for detecting duplicates
547
- * @param {boolean} [isSubElement] Wether the given element is a subelement or not - subelements cannot be key!
547
+ * @param {boolean} [isSubElement] Whether the given element is a subelement or not - subelements cannot be key!
548
548
  * @returns {string} The rendered element
549
549
  */
550
550
  function renderElement(elementName, elm, env, duplicateChecker, isSubElement) {
@@ -628,7 +628,7 @@ function toHdbcdsSource(csn, options) {
628
628
  function renderAbsolutePath(path, env) {
629
629
  // Sanity checks
630
630
  if (!path.ref)
631
- throw new Error(`Expecting ref in path: ${JSON.stringify(path)}`);
631
+ throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
632
632
 
633
633
 
634
634
  // Determine the absolute name of the first artifact on the path (before any associations or element traversals)
@@ -791,7 +791,7 @@ function toHdbcdsSource(csn, options) {
791
791
  * or 'entity')
792
792
  *
793
793
  * @param {CSN.Query} query Query object
794
- * @param {boolean} isLeadingQuery Wether the query is the leading query or not
794
+ * @param {boolean} isLeadingQuery Whether the query is the leading query or not
795
795
  * @param {CdlRenderEnvironment} env Environment
796
796
  * @param {CSN.Path} [path=[]] CSN path to the query
797
797
  * @param {object} [elements] For leading query, the elements of the artifact
@@ -823,7 +823,7 @@ function toHdbcdsSource(csn, options) {
823
823
  }
824
824
  // Otherwise must have a SELECT
825
825
  else if (!query.SELECT) {
826
- throw new Error(`Unexpected query operation ${JSON.stringify(query)}`);
826
+ throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
827
827
  }
828
828
  const select = query.SELECT;
829
829
  const childEnv = increaseIndent(env);
@@ -870,7 +870,7 @@ function toHdbcdsSource(csn, options) {
870
870
  alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env, true, true)}`;
871
871
 
872
872
  if (select.groupBy)
873
- alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
873
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
874
874
 
875
875
  if (select.having)
876
876
  alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env, true, true)}`;
@@ -927,7 +927,7 @@ function toHdbcdsSource(csn, options) {
927
927
  * @returns {string} Rendered order by
928
928
  */
929
929
  function renderOrderByEntry(entry, env) {
930
- let result = renderExpr(entry, env);
930
+ let result = renderExpr(entry, env, true, false, true);
931
931
  if (entry.sort)
932
932
  result += ` ${entry.sort}`;
933
933
 
@@ -1011,7 +1011,7 @@ function toHdbcdsSource(csn, options) {
1011
1011
  // Anonymous structured type
1012
1012
  if (!elm.type) {
1013
1013
  if (!elm.elements)
1014
- throw new Error(`Missing type of: ${JSON.stringify(elm)}`);
1014
+ throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
1015
1015
 
1016
1016
  result += '{\n';
1017
1017
  const childEnv = increaseIndent(env);
@@ -1116,15 +1116,17 @@ function toHdbcdsSource(csn, options) {
1116
1116
  * @param {CdlRenderEnvironment} env Environment
1117
1117
  * @param {boolean} [inline=true] Whether to render inline
1118
1118
  * @param {boolean} [inExpr=false] Whether the expression is already inside another expression
1119
+ * @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
1120
+ * Note: This is a hack for casts() inside groupBy.
1119
1121
  * @returns {string} Rendered expression
1120
1122
  */
1121
- function renderExpr(expr, env, inline = true, inExpr = false) {
1123
+ function renderExpr(expr, env, inline = true, inExpr = false, alwaysRenderCast = false) {
1122
1124
  // Compound expression
1123
1125
  if (Array.isArray(expr))
1124
1126
  return beautifyExprArray(expr.map(item => renderExpr(item, env, inline, inExpr)));
1125
1127
 
1126
1128
  if (typeof expr === 'object' && expr !== null) {
1127
- if (inExpr && expr.cast && expr.cast.type)
1129
+ if ((inExpr || alwaysRenderCast) && expr.cast && expr.cast.type)
1128
1130
  return renderExplicitTypeCast(renderExprObject(expr));
1129
1131
  return renderExprObject(expr);
1130
1132
  }
@@ -1186,7 +1188,7 @@ function toHdbcdsSource(csn, options) {
1186
1188
  return `${renderQuery(x, false, increaseIndent(env))}`;
1187
1189
  }
1188
1190
 
1189
- throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
1191
+ throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
1190
1192
  }
1191
1193
  /**
1192
1194
  * @param {object} x Expression with a val and/or literal property
@@ -1212,7 +1214,7 @@ function toHdbcdsSource(csn, options) {
1212
1214
 
1213
1215
  // otherwise fall through to
1214
1216
  default:
1215
- throw new Error(`Unknown literal or type: ${JSON.stringify(x)}`);
1217
+ throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1216
1218
  }
1217
1219
  }
1218
1220
 
@@ -1319,7 +1321,7 @@ function toHdbcdsSource(csn, options) {
1319
1321
  else if (typeof s === 'object') {
1320
1322
  // Sanity check
1321
1323
  if (!s.func && !s.id)
1322
- throw new Error(`Unknown path step object: ${JSON.stringify(s)}`);
1324
+ throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
1323
1325
 
1324
1326
  // Not really a path step but an object-like function call
1325
1327
  if (s.func)
@@ -1338,7 +1340,7 @@ function toHdbcdsSource(csn, options) {
1338
1340
  return result;
1339
1341
  }
1340
1342
 
1341
- throw new Error(`Unknown path step: ${JSON.stringify(s)}`);
1343
+ throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1342
1344
  }
1343
1345
  }
1344
1346
 
@@ -1355,15 +1357,15 @@ function toHdbcdsSource(csn, options) {
1355
1357
  const args = node.args ? node.args : {};
1356
1358
  // Positional arguments
1357
1359
  if (Array.isArray(args))
1358
- return args.map(arg => renderExpr(arg, env)).join(', ');
1360
+ return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
1359
1361
 
1360
1362
  // Named arguments (object/dict)
1361
1363
  else if (typeof args === 'object')
1362
1364
  // if this is a function param which is not a reference to the model, we must not quote it
1363
- return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1365
+ return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
1364
1366
 
1365
1367
 
1366
- throw new Error(`Unknown args: ${JSON.stringify(args)}`);
1368
+ throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1367
1369
  }
1368
1370
 
1369
1371
  /**
@@ -1645,7 +1647,7 @@ function toHdbcdsSource(csn, options) {
1645
1647
  if (plainNames) {
1646
1648
  const art = csn.definitions[name];
1647
1649
  // For 'plain' naming, take all entities and views, nothing else
1648
- if (art.kind === 'entity' || art.kind === 'view')
1650
+ if (art.kind === 'entity')
1649
1651
  result[name] = art;
1650
1652
  }
1651
1653
  else {
@@ -1736,7 +1738,7 @@ function toHdbcdsSource(csn, options) {
1736
1738
  function quoteId(id) {
1737
1739
  // Should only ever be called for real IDs (i.e. no dots inside)
1738
1740
  if (id.indexOf('.') !== -1)
1739
- throw new Error(id);
1741
+ throw new ModelError(id);
1740
1742
 
1741
1743
 
1742
1744
  switch (options.forHana.names) {
@@ -21,6 +21,7 @@ const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
21
21
  const { smartFuncId } = require('../sql-identifier');
22
22
  const { sortCsn } = require('../json/to-csn');
23
23
  const { manageConstraints } = require('./manageConstraints');
24
+ const { ModelError } = require('../base/error');
24
25
 
25
26
 
26
27
  /**
@@ -172,6 +173,12 @@ function toSqlDdl(csn, options) {
172
173
  comment(comment) {
173
174
  return comment && `'${comment.replace(/'/g, '\'\'')}'` || 'NULL';
174
175
  },
176
+ /*
177
+ Alter SQL snippet for entity.
178
+ */
179
+ alterEntitySqlSnippet(tableName, snippet) {
180
+ return [ `ALTER TABLE ${tableName} ${snippet};` ];
181
+ },
175
182
  /*
176
183
  Concatenate multiple statements which are to be treated as one by the API caller.
177
184
  */
@@ -284,8 +291,8 @@ function toSqlDdl(csn, options) {
284
291
  delete mainResultObj[hdbKind];
285
292
  }
286
293
 
287
- // add `ALTER TABLE ADD CONSTRAINT` statements if requested
288
- if (options.sqlDialect !== 'sqlite' && options.constraintsAsAlter) {
294
+ // add `ALTER TABLE ADD CONSTRAINT` statements per default for `to.sql` w/ dialect `hana`
295
+ if (!options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana') {
289
296
  const alterStmts = manageConstraints(csn, options);
290
297
 
291
298
  for ( const constraintName of Object.keys(alterStmts))
@@ -318,7 +325,6 @@ function toSqlDdl(csn, options) {
318
325
 
319
326
  switch (art.kind) {
320
327
  case 'entity':
321
- case 'view':
322
328
  if (getNormalizedQuery(art).query) {
323
329
  const result = renderView(artifactName, art, env);
324
330
  if (result)
@@ -340,7 +346,7 @@ function toSqlDdl(csn, options) {
340
346
  // Ignore: not SQL-relevant
341
347
  return;
342
348
  default:
343
- throw new Error(`Unknown artifact kind: ${art.kind}`);
349
+ throw new ModelError(`Unknown artifact kind: ${art.kind}`);
344
350
  }
345
351
  }
346
352
 
@@ -361,7 +367,7 @@ function toSqlDdl(csn, options) {
361
367
  renderExtendInto(artifactName, artifact.elements, ext.elements, resultObj, env, extensionsDuplicateChecker);
362
368
 
363
369
  if (!artifactName)
364
- throw new Error(`Undefined artifact name: ${artifactName}`);
370
+ throw new ModelError(`Undefined artifact name: ${artifactName}`);
365
371
  }
366
372
 
367
373
  // Render an artifact deletion into the appropriate dictionary of 'resultObj'.
@@ -401,11 +407,25 @@ function toSqlDdl(csn, options) {
401
407
  ? renderAssociationElement(eltName, defVariant, env)
402
408
  : renderElement(artifactName, eltName, defVariant, null, null, env);
403
409
  }
404
- function getEltStrNoProp(defVariant, prop, eltName) {
405
- const defNoProp = Object.assign({}, defVariant);
406
- delete defNoProp[prop];
407
- return getEltStr(defNoProp, eltName);
410
+ function getEltStrNoProps(defVariant, eltName, ...props) {
411
+ const defNoProps = Object.assign({}, defVariant);
412
+ for (const prop of props)
413
+ delete defNoProps[prop];
414
+ return getEltStr(defNoProps, eltName);
408
415
  }
416
+ function oldAnnoChangedIncompatibly(defOld, defNew) {
417
+ return typeof defOld === 'string' && defOld.trim().length && !(typeof defNew === 'string' && defNew.trim().startsWith(`${defOld.trim()} `));
418
+ }
419
+ function getUnknownSqlReason(anno, artifactName, defOld, defNew, eltName) {
420
+ const changeKind = defNew === undefined
421
+ ? `removed (previous value: ${JSON.stringify(defOld)})`
422
+ : `changed from ${JSON.stringify(defOld)} to ${JSON.stringify(defNew)}`;
423
+ return eltName
424
+ ? `annotation ${anno} of element ${artifactName}:${eltName} has been ${changeKind}`
425
+ : `annotation ${anno} of artifact ${artifactName} has been ${changeKind}`;
426
+ }
427
+
428
+ const sqlSnippetAnnos = [ '@sql.prepend', '@sql.append' ];
409
429
 
410
430
  const tableName = renderArtifactName(artifactName);
411
431
 
@@ -416,6 +436,15 @@ function toSqlDdl(csn, options) {
416
436
  const alterComment = render.alterEntityComment(tableName, def.new);
417
437
  addMigration(resultObj, artifactName, false, alterComment);
418
438
  }
439
+ else if (sqlSnippetAnnos.includes(prop)) { // NOTE: @sql.replace may be supported in the future
440
+ if (oldAnnoChangedIncompatibly(def.old, def.new)) {
441
+ // anno was previously set and current change is not simply an appendix → previous anno would have to be reverted → unknown SQL
442
+ addMigration(resultObj, artifactName, false, null, getUnknownSqlReason(prop, artifactName, def.old, def.new));
443
+ }
444
+ else {
445
+ addMigration(resultObj, artifactName, false, render.alterEntitySqlSnippet(tableName, def.new));
446
+ }
447
+ }
419
448
  }
420
449
  }
421
450
 
@@ -450,9 +479,28 @@ function toSqlDdl(csn, options) {
450
479
  if (eltStrNew === eltStrOld)
451
480
  return; // Prevent spurious migrations, where the column DDL does not change.
452
481
 
482
+ const annosIncompat = [];
483
+ sqlSnippetAnnos
484
+ .filter(anno => def.old[anno] !== def.new[anno])
485
+ .forEach((anno) => { // NOTE: @sql.replace may be supported in the future
486
+ if (oldAnnoChangedIncompatibly(def.old[anno], def.new[anno])) {
487
+ annosIncompat.push(anno);
488
+ // anno was previously set and current change is not simply an appendix → previous anno would have to be reverted → unknown SQL
489
+ addMigration(resultObj, artifactName, false, null, getUnknownSqlReason(anno, artifactName, def.old[anno], def.new[anno], eltName));
490
+ }
491
+ });
492
+
493
+ if (annosIncompat.length) {
494
+ const eltStrOldNoAnnos = getEltStrNoProps(def.old, eltName, ...annosIncompat);
495
+ const eltStrNewNoAnnos = getEltStrNoProps(def.new, eltName, ...annosIncompat);
496
+ if (eltStrOldNoAnnos === eltStrNewNoAnnos) { // only incompatibly-changed annos were modified
497
+ continue;
498
+ }
499
+ }
500
+
453
501
  if (def.old.doc !== def.new.doc) {
454
- const eltStrOldNoDoc = getEltStrNoProp(def.old, 'doc', eltName);
455
- const eltStrNewNoDoc = getEltStrNoProp(def.new, 'doc', eltName);
502
+ const eltStrOldNoDoc = getEltStrNoProps(def.old, eltName, 'doc');
503
+ const eltStrNewNoDoc = getEltStrNoProps(def.new, eltName, 'doc');
456
504
  if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
457
505
  const alterComment = render.alterColumnComment(tableName, sqlId, def.new.doc);
458
506
  addMigration(resultObj, artifactName, false, alterComment);
@@ -501,7 +549,7 @@ function toSqlDdl(csn, options) {
501
549
  // Explicitly specified
502
550
  result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
503
551
  }
504
- else {
552
+ else if (!front) {
505
553
  // in 'hdbtable' files, COLUMN or ROW is mandatory, and COLUMN is the default
506
554
  result += 'COLUMN ';
507
555
  }
@@ -524,7 +572,8 @@ function toSqlDdl(csn, options) {
524
572
  if (primaryKeys !== '')
525
573
  result += `,\n${childEnv.indent}${primaryKeys}`;
526
574
 
527
- const constraintsAsAlter = options.constraintsAsAlter && options.sqlDialect !== 'sqlite';
575
+ // for `to.sql` w/ dialect `hana` the constraints will be part of the
576
+ const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
528
577
  if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
529
578
  const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
530
579
  const referentialConstraints = {};
@@ -612,10 +661,15 @@ function toSqlDdl(csn, options) {
612
661
  }
613
662
  }
614
663
 
615
- function addMigration(resultObj, artifactName, drop, sqlArray) {
664
+ function addMigration(resultObj, artifactName, drop, sqlArray, description) {
616
665
  if (!(artifactName in resultObj.migrations))
617
666
  resultObj.migrations[artifactName] = [];
618
667
 
668
+ if (!sqlArray) {
669
+ if (description)
670
+ resultObj.migrations[artifactName].push({ description });
671
+ return;
672
+ }
619
673
  const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
620
674
  resultObj.migrations[artifactName].push(...migrations);
621
675
  }
@@ -678,15 +732,15 @@ function toSqlDdl(csn, options) {
678
732
  if (fzindex && options.toSql.dialect === 'hana')
679
733
  result += ` ${renderExpr(fzindex, env)}`;
680
734
 
681
- if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options))
682
- result += ` COMMENT '${getHanaComment(elm)}'`;
683
-
684
735
  // (table) elements can only have a @sql.append
685
736
  const { back } = getSqlSnippets(options, elm);
686
737
 
687
- if (back !== '')
738
+ if (back !== '') // Needs to be rendered before the COMMENT
688
739
  result += back;
689
740
 
741
+ if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options))
742
+ result += ` COMMENT '${getHanaComment(elm)}'`;
743
+
690
744
  return result;
691
745
  }
692
746
 
@@ -751,7 +805,7 @@ function toSqlDdl(csn, options) {
751
805
  // This also affects renderIndexes
752
806
  tc = tc.hana;
753
807
  if (!tc)
754
- throw new Error('Expecting a HANA technical configuration');
808
+ throw new ModelError('Expecting a HANA technical configuration');
755
809
 
756
810
  if (tc.tableSuffix) {
757
811
  // Although we could just render the whole bandwurm as one stream of tokens, the
@@ -820,7 +874,7 @@ function toSqlDdl(csn, options) {
820
874
  const i = index.indexOf('index');
821
875
  const j = index.indexOf('(');
822
876
  if (i > index.length - 2 || !index[i + 1].ref || j < i || j > index.length - 2)
823
- throw new Error(`Unexpected form of index: "${index}"`);
877
+ throw new ModelError(`Unexpected form of index: "${index}"`);
824
878
 
825
879
  let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
826
880
  if (options.toSql.names === 'plain')
@@ -876,7 +930,7 @@ function toSqlDdl(csn, options) {
876
930
 
877
931
  // Sanity check
878
932
  if (!source.ref)
879
- throw new Error(`Expecting ref in ${JSON.stringify(source)}`);
933
+ throw new ModelError(`Expecting ref in ${JSON.stringify(source)}`);
880
934
 
881
935
  return renderAbsolutePathWithAlias(artifactName, source, env);
882
936
  }
@@ -918,7 +972,7 @@ function toSqlDdl(csn, options) {
918
972
  function renderAbsolutePathWithAlias(artifactName, path, env) {
919
973
  // This actually can't happen anymore because assoc2joins should have taken care of it
920
974
  if (path.ref[0].where)
921
- throw new Error(`"${artifactName}": Filters in FROM are not supported for conversion to SQL`);
975
+ throw new ModelError(`"${artifactName}": Filters in FROM are not supported for conversion to SQL`);
922
976
 
923
977
 
924
978
  // SQL needs a ':' after path.ref[0] to separate associations
@@ -956,7 +1010,7 @@ function toSqlDdl(csn, options) {
956
1010
  function renderAbsolutePath(path, sep, env) {
957
1011
  // Sanity checks
958
1012
  if (!path.ref)
959
- throw new Error(`Expecting ref in path: ${JSON.stringify(path)}`);
1013
+ throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
960
1014
 
961
1015
  // Determine the absolute name of the first artifact on the path (before any associations or element traversals)
962
1016
  const firstArtifactName = path.ref[0].id || path.ref[0];
@@ -973,7 +1027,7 @@ function toSqlDdl(csn, options) {
973
1027
  if (ref && ref.params) {
974
1028
  result += `(${renderArgs(path.ref[0] || {}, '=>', env, syntax)})`;
975
1029
  }
976
- else if ([ 'udf' ].includes(syntax)) {
1030
+ else if (syntax === 'udf') {
977
1031
  // if syntax is user defined function, render empty argument list
978
1032
  // CV without parameters is called as simple view
979
1033
  result += '()';
@@ -1004,15 +1058,15 @@ function toSqlDdl(csn, options) {
1004
1058
  const args = node.args ? node.args : {};
1005
1059
  // Positional arguments
1006
1060
  if (Array.isArray(args))
1007
- return args.map(arg => renderExpr(arg, env)).join(', ');
1061
+ return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
1008
1062
 
1009
1063
  // Named arguments (object/dict)
1010
1064
  else if (typeof args === 'object')
1011
1065
  // if this is a function param which is not a reference to the model, we must not quote it
1012
- return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1066
+ return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
1013
1067
 
1014
1068
 
1015
- throw new Error(`Unknown args: ${JSON.stringify(args)}`);
1069
+ throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1016
1070
 
1017
1071
 
1018
1072
  /**
@@ -1166,7 +1220,7 @@ function toSqlDdl(csn, options) {
1166
1220
  }
1167
1221
  // Otherwise must have a SELECT
1168
1222
  else if (!query.SELECT) {
1169
- throw new Error(`Unexpected query operation ${JSON.stringify(query)}`);
1223
+ throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
1170
1224
  }
1171
1225
  const select = query.SELECT;
1172
1226
  const childEnv = increaseIndent(env);
@@ -1182,7 +1236,7 @@ function toSqlDdl(csn, options) {
1182
1236
  result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
1183
1237
 
1184
1238
  if (select.groupBy)
1185
- result += `\n${env.indent}GROUP BY ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
1239
+ result += `\n${env.indent}GROUP BY ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
1186
1240
 
1187
1241
  if (select.having)
1188
1242
  result += `\n${env.indent}HAVING ${renderExpr(select.having, env, true, true)}`;
@@ -1233,7 +1287,7 @@ function toSqlDdl(csn, options) {
1233
1287
  * @returns {string} Rendered ORDER BY entry
1234
1288
  */
1235
1289
  function renderOrderByEntry(entry, env) {
1236
- let result = renderExpr(entry, env);
1290
+ let result = renderExpr(entry, env, true, false, true);
1237
1291
  if (entry.sort)
1238
1292
  result += ` ${entry.sort.toUpperCase()}`;
1239
1293
 
@@ -1257,7 +1311,7 @@ function toSqlDdl(csn, options) {
1257
1311
  // Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
1258
1312
  if (!elm.type) {
1259
1313
  if (!elm.elements)
1260
- throw new Error(`Missing type of: ${elementName}`);
1314
+ throw new ModelError(`Missing type of: ${elementName}`);
1261
1315
 
1262
1316
  // TODO: Signal is not covered by tests + better location
1263
1317
  error(null, [ 'definitions', artifactName, 'elements', elementName ],
@@ -1280,7 +1334,7 @@ function toSqlDdl(csn, options) {
1280
1334
  result += renderBuiltinType(elm.type);
1281
1335
  }
1282
1336
  else {
1283
- throw new Error(`Unexpected non-primitive type of: ${artifactName}.${elementName}`);
1337
+ throw new ModelError(`Unexpected non-primitive type of: ${artifactName}.${elementName}`);
1284
1338
  }
1285
1339
  result += renderTypeParameters(elm);
1286
1340
  return result;
@@ -1309,7 +1363,7 @@ function toSqlDdl(csn, options) {
1309
1363
  * Render the nullability of an element or parameter (can be unset, true, or false)
1310
1364
  *
1311
1365
  * @param {object} obj Object to render for
1312
- * @param {boolean} treatKeyAsNotNull Wether to render KEY as not null
1366
+ * @param {boolean} treatKeyAsNotNull Whether to render KEY as not null
1313
1367
  * @returns {string} NULL/NOT NULL or ''
1314
1368
  */
1315
1369
  function renderNullability(obj, treatKeyAsNotNull = false) {
@@ -1356,18 +1410,20 @@ function toSqlDdl(csn, options) {
1356
1410
  * @todo Reuse this with toCdl
1357
1411
  * @param {Array|object|string} expr Expression to render
1358
1412
  * @param {object} env Render environment
1359
- * @param {boolean} inline Wether to render the expression inline
1360
- * @param {boolean} nestedExpr Wether to treat the expression as nested
1413
+ * @param {boolean} [inline=true] Whether to render the expression inline
1414
+ * @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
1415
+ * @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
1416
+ * Note: This is a hack for casts() inside groupBy.
1361
1417
  * @returns {string} Rendered expression
1362
1418
  */
1363
- function renderExpr(expr, env, inline = true, nestedExpr = false) {
1419
+ function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
1364
1420
  // Compound expression
1365
1421
  if (Array.isArray(expr)) {
1366
1422
  const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
1367
1423
  return beautifyExprArray(tokens);
1368
1424
  }
1369
1425
  else if (typeof expr === 'object' && expr !== null) {
1370
- if (nestedExpr && expr.cast && expr.cast.type)
1426
+ if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type)
1371
1427
  return renderExplicitTypeCast(expr, renderExprObject(expr));
1372
1428
  return renderExprObject(expr);
1373
1429
  }
@@ -1380,6 +1436,7 @@ function toSqlDdl(csn, options) {
1380
1436
  /**
1381
1437
  * Various special cases represented as objects
1382
1438
  *
1439
+ * @param {object} x Expression
1383
1440
  * @returns {string} String representation of the expression
1384
1441
  */
1385
1442
  function renderExprObject(x) {
@@ -1425,7 +1482,7 @@ function toSqlDdl(csn, options) {
1425
1482
  return `${renderQuery('<union>', x, increaseIndent(env))}`;
1426
1483
  }
1427
1484
 
1428
- throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
1485
+ throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
1429
1486
  }
1430
1487
 
1431
1488
  function renderWindowFunction(funcName, node, fctEnv) {
@@ -1465,7 +1522,7 @@ function toSqlDdl(csn, options) {
1465
1522
 
1466
1523
  // otherwise fall through to
1467
1524
  default:
1468
- throw new Error(`Unknown literal or type: ${JSON.stringify(x)}`);
1525
+ throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1469
1526
  }
1470
1527
  }
1471
1528
 
@@ -1628,7 +1685,7 @@ function toSqlDdl(csn, options) {
1628
1685
  else if (typeof s === 'object') {
1629
1686
  // Sanity check
1630
1687
  if (!s.func && !s.id)
1631
- throw new Error(`Unknown path step object: ${JSON.stringify(s)}`);
1688
+ throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
1632
1689
 
1633
1690
  // Not really a path step but an object-like function call
1634
1691
  if (s.func)
@@ -1648,7 +1705,7 @@ function toSqlDdl(csn, options) {
1648
1705
  return result;
1649
1706
  }
1650
1707
 
1651
- throw new Error(`Unknown path step: ${JSON.stringify(s)}`);
1708
+ throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1652
1709
  }
1653
1710
  }
1654
1711
 
@@ -19,8 +19,6 @@ const {
19
19
  } = require('../../model/csnUtils');
20
20
 
21
21
  const { implicitAs } = require('../../model/csnRefs');
22
- const { isBetaEnabled } = require('../../base/model');
23
-
24
22
 
25
23
  /**
26
24
  * Render the given function
@@ -38,7 +36,7 @@ function renderFunc( funcName, node, dialect, renderArgs) {
38
36
  }
39
37
 
40
38
  /**
41
- * Checks wether the given function is to be rendered without parentheses
39
+ * Checks whether the given function is to be rendered without parentheses
42
40
  *
43
41
  * @param {object} node Content of the function
44
42
  * @param {string} dialect One of 'hana', 'cap' or 'sqlite' - only 'hana' is relevant atm
@@ -332,7 +330,7 @@ function addIntermediateContexts(csn, killList) {
332
330
  }
333
331
 
334
332
  /**
335
- * Check wether the given artifact or element has a comment that needs to be rendered.
333
+ * Check whether the given artifact or element has a comment that needs to be rendered.
336
334
  * Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.
337
335
  *
338
336
  * @param {CSN.Artifact} obj
@@ -364,13 +362,10 @@ function getHanaComment(obj) {
364
362
  * @returns {object} object with .front and .back
365
363
  */
366
364
  function getSqlSnippets(options, obj) {
367
- if (isBetaEnabled(options, 'sqlSnippets')) {
368
- const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
369
- const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
365
+ const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
366
+ const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
370
367
 
371
- return { front, back };
372
- }
373
- return { front: '', back: '' };
368
+ return { front, back };
374
369
  }
375
370
 
376
371
  /**
@@ -4,13 +4,14 @@
4
4
 
5
5
  const { getResultingName } = require('../../model/csnUtils');
6
6
  const { smartId, delimitedId } = require('../../sql-identifier');
7
+ const { ModelError } = require('../../base/error');
7
8
 
8
9
  /**
9
10
  * Render a given referential constraint as part of a SQL CREATE TABLE statement, or as .hdbconstraint artefact.
10
11
  *
11
12
  * @param {CSN.ReferentialConstraint} constraint Content of the constraint
12
13
  * @param {string} indent Indent to render the SQL with
13
- * @param {boolean} toUpperCase Wether to uppercase the identifier
14
+ * @param {boolean} toUpperCase Whether to uppercase the identifier
14
15
  * @param {CSN.Model} csn CSN
15
16
  * @param {CSN.Options} options is needed for the naming mode and the sql dialect
16
17
  * @param {boolean} [alterConstraint=false] whether the constraint should be rendered as part of an ALTER TABLE statement
@@ -121,7 +122,7 @@ function getIdentifierUtils(options) {
121
122
  function prepareIdentifier(name) {
122
123
  // Sanity check
123
124
  if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
124
- throw new Error(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
125
+ throw new ModelError(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
125
126
 
126
127
 
127
128
  switch (options.toSql.names) {
@@ -132,7 +133,7 @@ function getIdentifierUtils(options) {
132
133
  case 'hdbcds':
133
134
  return name;
134
135
  default:
135
- throw new Error(`No matching rendering found for naming mode ${options.toSql.names}`);
136
+ throw new ModelError(`No matching rendering found for naming mode ${options.toSql.names}`);
136
137
  }
137
138
  }
138
139
  }