@sap/cds-compiler 2.12.0 → 2.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  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 +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  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 +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  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 -290
  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
  128. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -7,8 +7,8 @@ const {
7
7
  } = require('../model/csnUtils');
8
8
  const keywords = require('../base/keywords');
9
9
  const {
10
- renderFunc, beautifyExprArray, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
11
- hasHanaComment, getHanaComment, findElement, funcWithoutParen, getSqlSnippets,
10
+ renderFunc, getExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
11
+ hasHanaComment, getHanaComment, funcWithoutParen, getSqlSnippets,
12
12
  } = require('./utils/common');
13
13
  const {
14
14
  renderReferentialConstraint,
@@ -20,18 +20,19 @@ 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';
26
27
 
27
28
  /**
28
- * Get the comment and in addition escape \n so HANA CDS can handle it.
29
+ * Get the comment and in addition escape \n and `'` so SAP HANA CDS can handle it.
29
30
  *
30
31
  * @param {CSN.Artifact} obj
31
32
  * @returns {string}
32
33
  */
33
34
  function getEscapedHanaComment(obj) {
34
- return getHanaComment(obj).replace(/\n/g, '\\n');
35
+ return getHanaComment(obj).replace(/\n/g, '\\n').replace(/'/g, "''");
35
36
  }
36
37
 
37
38
  /**
@@ -55,9 +56,40 @@ function toHdbcdsSource(csn, options) {
55
56
  const hdbcdsNames = options.sqlMapping === 'hdbcds';
56
57
 
57
58
  const {
58
- info, warning, error, throwWithError,
59
+ info, warning, error, throwWithAnyError,
59
60
  } = makeMessageFunction(csn, options, 'to.hdbcds');
60
61
 
62
+ const renderExpr = getExpressionRenderer({
63
+ finalize: x => x,
64
+ explicitTypeCast(x, env) {
65
+ let typeRef = renderTypeReference(x.cast, env);
66
+
67
+ // inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
68
+ const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
69
+ if (hanaSqlType) {
70
+ const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
71
+ typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
72
+ }
73
+ return `CAST(${renderExpr(x, env)} AS ${typeRef})`;
74
+ },
75
+ val: renderExpressionLiteral,
76
+ enum: x => `#${x['#']}`,
77
+ ref: renderExpressionRef,
78
+ aliasOnly(x, _env) {
79
+ return x.as;
80
+ },
81
+ windowFunction: renderExpressionFunc,
82
+ func: renderExpressionFunc,
83
+ xpr(x, env) {
84
+ if (this.nestedExpr && !x.cast)
85
+ return `(${renderExpr(x.xpr, env, this.inline, true)})`;
86
+
87
+ return renderExpr(x.xpr, env, this.inline, true);
88
+ },
89
+ SELECT: (x, env) => `(${renderQuery(x, false, increaseIndent(env))})`,
90
+ SET: (x, env) => `${renderQuery(x, false, increaseIndent(env))}`,
91
+ });
92
+
61
93
  checkCSNVersion(csn, options);
62
94
 
63
95
  const hdbcdsResult = Object.create(null);
@@ -116,7 +148,7 @@ function toHdbcdsSource(csn, options) {
116
148
 
117
149
  killList.forEach(fn => fn());
118
150
 
119
- throwWithError();
151
+ throwWithAnyError();
120
152
  timetrace.stop();
121
153
  return options.testMode ? sort(hdbcdsResult) : hdbcdsResult;
122
154
 
@@ -152,7 +184,6 @@ function toHdbcdsSource(csn, options) {
152
184
 
153
185
  switch (art.kind) {
154
186
  case 'entity':
155
- case 'view':
156
187
  // FIXME: For HANA CDS, we need to replace $self at the beginning of paths in association ON-condition
157
188
  // by the full name of the artifact we are rendering (should actually be done by forHana, but that is
158
189
  // somewhat difficult because this kind of absolute path is quite unusual). In order not to have to pass
@@ -177,7 +208,7 @@ function toHdbcdsSource(csn, options) {
177
208
  case 'event':
178
209
  return '';
179
210
  default:
180
- throw new Error(`Unknown artifact kind: ${art.kind}`);
211
+ throw new ModelError(`Unknown artifact kind: ${art.kind}`);
181
212
  }
182
213
  }
183
214
 
@@ -213,11 +244,11 @@ function toHdbcdsSource(csn, options) {
213
244
  }
214
245
 
215
246
  /**
216
- * Check wether the given context is the direct parent of the containee.
247
+ * Check whether the given context is the direct parent of the containee.
217
248
  *
218
249
  * @param {string} containee Name of the contained artifact
219
250
  * @param {string} contextName Name of the (grand?)parent context
220
- * @returns {boolean} True if there is another context inbetween
251
+ * @returns {boolean} True if there is another context in between
221
252
  */
222
253
  function isContainedInOtherContext(containee, contextName) {
223
254
  const parts = containee.split('.');
@@ -280,7 +311,7 @@ function toHdbcdsSource(csn, options) {
280
311
  * Render a context or service. Return the resulting source string.
281
312
  *
282
313
  * If the context is shadowed by another entity, the context itself is not rendered,
283
- * but any contained (and transitively contained) entites and views are.
314
+ * but any contained (and transitively contained) entities and views are.
284
315
  *
285
316
  * @param {string} artifactName Name of the context/service
286
317
  * @param {CSN.Artifact} art Content of the context/service
@@ -313,7 +344,7 @@ function toHdbcdsSource(csn, options) {
313
344
  return `${result + renderedSubArtifacts + env.indent}};\n`;
314
345
  }
315
346
  /**
316
- * Check wether the given context is shadowed, i.e. part of his name prefix is shared by a
347
+ * Check whether the given context is shadowed, i.e. part of his name prefix is shared by a
317
348
  * non-context/service/namespace definition
318
349
  *
319
350
  * @param {string} artifactName
@@ -469,7 +500,7 @@ function toHdbcdsSource(csn, options) {
469
500
  // in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
470
501
  tc = tc.hana;
471
502
  if (!tc)
472
- throw new Error('Expecting a HANA technical configuration');
503
+ throw new ModelError('Expecting a HANA technical configuration');
473
504
 
474
505
  result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
475
506
 
@@ -544,7 +575,7 @@ function toHdbcdsSource(csn, options) {
544
575
  * @param {CSN.Element} elm Content of the element
545
576
  * @param {CdlRenderEnvironment} env Environment
546
577
  * @param {DuplicateChecker} [duplicateChecker] Utility for detecting duplicates
547
- * @param {boolean} [isSubElement] Wether the given element is a subelement or not - subelements cannot be key!
578
+ * @param {boolean} [isSubElement] Whether the given element is a subelement or not - subelements cannot be key!
548
579
  * @returns {string} The rendered element
549
580
  */
550
581
  function renderElement(elementName, elm, env, duplicateChecker, isSubElement) {
@@ -628,7 +659,7 @@ function toHdbcdsSource(csn, options) {
628
659
  function renderAbsolutePath(path, env) {
629
660
  // Sanity checks
630
661
  if (!path.ref)
631
- throw new Error(`Expecting ref in path: ${JSON.stringify(path)}`);
662
+ throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
632
663
 
633
664
 
634
665
  // Determine the absolute name of the first artifact on the path (before any associations or element traversals)
@@ -687,18 +718,19 @@ function toHdbcdsSource(csn, options) {
687
718
  * Return the resulting source string (no trailing LF).
688
719
  *
689
720
  * @param {object} col Column to render
721
+ * @param {CSN.Elements} elements where column exists
690
722
  * @param {CdlRenderEnvironment} env Environment
691
- * @param {CSN.Element} [element] Element (non-enum from subquery possibly) corresponding to the column ref
692
723
  * @returns {string} Rendered column
693
724
  */
694
- function renderViewColumn(col, env, element) {
725
+ function renderViewColumn(col, elements, env ) {
695
726
  // Annotations and column
696
727
  let result = '';
697
728
 
698
- const leaf = col.as || col.ref && col.ref[col.ref.length - 1];
699
- // Render 'null as <alias>' only for database and if element is virtual
729
+ const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
730
+ const element = elements[leaf];
700
731
 
701
- if (element && element.virtual || env._artifact.elements[leaf] && env._artifact.elements[leaf].virtual) {
732
+ // Render 'null as <alias>' only for database and if element is virtual
733
+ if (element && element.virtual) {
702
734
  if (isDeprecatedEnabled(options, 'renderVirtualElements'))
703
735
  return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
704
736
  }
@@ -791,7 +823,7 @@ function toHdbcdsSource(csn, options) {
791
823
  * or 'entity')
792
824
  *
793
825
  * @param {CSN.Query} query Query object
794
- * @param {boolean} isLeadingQuery Wether the query is the leading query or not
826
+ * @param {boolean} isLeadingQuery Whether the query is the leading query or not
795
827
  * @param {CdlRenderEnvironment} env Environment
796
828
  * @param {CSN.Path} [path=[]] CSN path to the query
797
829
  * @param {object} [elements] For leading query, the elements of the artifact
@@ -803,12 +835,12 @@ function toHdbcdsSource(csn, options) {
803
835
  // Set operator, like UNION, INTERSECT, ...
804
836
  if (query.SET) {
805
837
  // First arg may be leading query
806
- result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements)}`;
838
+ result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements || query.SET.elements)}`;
807
839
  // FIXME: Clarify if set operators can be n-ary (assuming binary here)
808
840
  if (query.SET.op) {
809
841
  // Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
810
842
  for (let i = 1; i < query.SET.args.length; i++)
811
- result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, env, path.concat([ 'SET', 'args', i ]))}`;
843
+ result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, env, path.concat([ 'SET', 'args', i ]), elements || query.SET.elements)}`;
812
844
  }
813
845
  result += ')';
814
846
  // Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
@@ -823,7 +855,7 @@ function toHdbcdsSource(csn, options) {
823
855
  }
824
856
  // Otherwise must have a SELECT
825
857
  else if (!query.SELECT) {
826
- throw new Error(`Unexpected query operation ${JSON.stringify(query)}`);
858
+ throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
827
859
  }
828
860
  const select = query.SELECT;
829
861
  const childEnv = increaseIndent(env);
@@ -843,7 +875,7 @@ function toHdbcdsSource(csn, options) {
843
875
  result += select.distinct ? ' distinct' : '';
844
876
  if (select.columns) {
845
877
  result += ' {\n';
846
- result += `${select.columns.map(col => renderViewColumn(col, childEnv, findElement(elements, col)))
878
+ result += `${select.columns.map(col => renderViewColumn(col, elements || select.elements, childEnv))
847
879
  .filter(s => s !== '')
848
880
  .join(',\n')}\n`;
849
881
  result += `${env.indent}}`;
@@ -870,7 +902,7 @@ function toHdbcdsSource(csn, options) {
870
902
  alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env, true, true)}`;
871
903
 
872
904
  if (select.groupBy)
873
- alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
905
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
874
906
 
875
907
  if (select.having)
876
908
  alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env, true, true)}`;
@@ -927,7 +959,7 @@ function toHdbcdsSource(csn, options) {
927
959
  * @returns {string} Rendered order by
928
960
  */
929
961
  function renderOrderByEntry(entry, env) {
930
- let result = renderExpr(entry, env);
962
+ let result = renderExpr(entry, env, true, false, true);
931
963
  if (entry.sort)
932
964
  result += ` ${entry.sort}`;
933
965
 
@@ -1011,7 +1043,7 @@ function toHdbcdsSource(csn, options) {
1011
1043
  // Anonymous structured type
1012
1044
  if (!elm.type) {
1013
1045
  if (!elm.elements)
1014
- throw new Error(`Missing type of: ${JSON.stringify(elm)}`);
1046
+ throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
1015
1047
 
1016
1048
  result += '{\n';
1017
1049
  const childEnv = increaseIndent(env);
@@ -1109,237 +1141,154 @@ function toHdbcdsSource(csn, options) {
1109
1141
  }
1110
1142
 
1111
1143
  /**
1112
- * Render an expression (including paths and values) or condition 'x'.
1113
- * (no trailing LF, don't indent if inline)
1144
+ * Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
1114
1145
  *
1115
- * @param {any} expr Expression to render
1116
- * @param {CdlRenderEnvironment} env Environment
1117
- * @param {boolean} [inline=true] Whether to render inline
1118
- * @param {boolean} [inExpr=false] Whether the expression is already inside another expression
1119
- * @returns {string} Rendered expression
1146
+ * @param {string|object} s Path step
1147
+ * @param {number} idx Path position
1148
+ * @returns {string} Rendered path step
1120
1149
  */
1121
- function renderExpr(expr, env, inline = true, inExpr = false) {
1122
- // Compound expression
1123
- if (Array.isArray(expr))
1124
- return beautifyExprArray(expr.map(item => renderExpr(item, env, inline, inExpr)));
1125
-
1126
- if (typeof expr === 'object' && expr !== null) {
1127
- if (inExpr && expr.cast && expr.cast.type)
1128
- return renderExplicitTypeCast(renderExprObject(expr));
1129
- return renderExprObject(expr);
1130
- }
1150
+ function renderPathStep(s, idx, ref, env, inline) {
1151
+ // Simple id or absolute name
1152
+ if (typeof s === 'string') {
1153
+ // HANA-specific extra magic (should actually be in forHana)
1154
+ // In HANA, we replace leading $self by the absolute name of the current artifact
1155
+ // (see FIXME at renderArtifact)
1156
+ if (idx === 0 && s === $SELF) {
1157
+ // do not produce USING for $projection
1158
+ if (env.currentArtifactName === $PROJECTION)
1159
+ return env.currentArtifactName;
1160
+
1161
+ return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
1162
+ : renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
1163
+ }
1164
+ // HANA-specific translation of '$now' and '$user'
1165
+ if (s === '$now' && ref.length === 1)
1166
+ return 'CURRENT_TIMESTAMP';
1131
1167
 
1132
- // Not a literal value but part of an operator, function etc - just leave as it is
1133
- return expr;
1168
+ // In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
1169
+ // FIXME: We should rather explicitly recognize quoting somehow
1134
1170
 
1171
+ // TODO: quote $parameters if it doesn't reference a parameter, this requires knowledge about the kind
1172
+ // Example: both views are correct in HANA CDS
1173
+ // entity E { key id: Integer; }
1174
+ // view EV with parameters P1: Integer as select from E { id, $parameters.P1 };
1175
+ // view EVp as select from E as "$parameters" { "$parameters".id };
1135
1176
 
1136
- /**
1137
- * Various special cases represented as objects
1138
- *
1139
- * @param {object} x Expression
1140
- * @returns {string} Rendered expression object
1141
- */
1142
- function renderExprObject(x) {
1143
- if (x.list) {
1144
- // set "inExpr" to false: treat list elements as new expressions
1145
- return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
1146
- }
1147
- else if (x.val !== undefined) {
1148
- return renderExpressionLiteral(x);
1149
- }
1150
- // Enum symbol
1151
- else if (x['#']) {
1152
- return `#${x['#']}`;
1153
- }
1154
- // Reference: Array of path steps, possibly preceded by ':'
1155
- else if (x.ref) {
1156
- return renderExpressionRef(x);
1157
- }
1158
- // Function call, possibly with args (use '=>' for named args)
1159
- else if (x.func) {
1160
- // test for non-regular HANA identifier that needs to be quoted
1161
- // identifier {letter}({letter_or_digit}|[#$])*
1162
- // letter [A-Za-z_]
1163
- // letter_or_digit [A-Za-z_0-9]
1164
-
1165
- const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
1166
- const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1167
- // we can't quote functions with parens, issue warning if it is a reserved keyword
1168
- if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1169
- warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
1170
- return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
1171
- }
1172
- // Nested expression
1173
- else if (x.xpr) {
1174
- if (inExpr && !x.cast)
1175
- return `(${renderExpr(x.xpr, env, inline, true)})`;
1176
-
1177
- return renderExpr(x.xpr, env, inline, true);
1178
- }
1179
- // Sub-select
1180
- else if (x.SELECT) {
1181
- // renderQuery for SELECT does not bring its own parentheses (because it is also used in renderView)
1182
- return `(${renderQuery(x, false, increaseIndent(env))})`;
1183
- }
1184
- else if (x.SET) {
1185
- // renderQuery for SET always brings its own parentheses (because it is also used in renderViewSource)
1186
- return `${renderQuery(x, false, increaseIndent(env))}`;
1187
- }
1177
+ if (idx === 0 &&
1178
+ [ $SELF, $PROJECTION, '$session' ].includes(s))
1179
+ return s;
1188
1180
 
1189
- throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
1181
+ return formatIdentifier(s);
1190
1182
  }
1191
- /**
1192
- * @param {object} x Expression with a val and/or literal property
1193
- * @returns {string} Rendered expression
1194
- */
1195
- function renderExpressionLiteral(x) {
1196
- // Literal value, possibly with explicit 'literal' property
1197
- switch (x.literal || typeof x.val) {
1198
- case 'number':
1199
- case 'boolean':
1200
- case 'null':
1201
- return x.val;
1202
- case 'x':
1203
- case 'date':
1204
- case 'time':
1205
- case 'timestamp':
1206
- return `${x.literal}'${x.val}'`;
1207
- case 'string':
1208
- return `'${x.val.replace(/'/g, '\'\'')}'`;
1209
- case 'object':
1210
- if (x.val === null)
1211
- return 'null';
1212
-
1213
- // otherwise fall through to
1214
- default:
1215
- throw new Error(`Unknown literal or type: ${JSON.stringify(x)}`);
1183
+ // ID with filters or parameters
1184
+ else if (typeof s === 'object') {
1185
+ // Sanity check
1186
+ if (!s.func && !s.id)
1187
+ throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
1188
+
1189
+ // Not really a path step but an object-like function call
1190
+ if (s.func)
1191
+ return `${s.func}(${renderArgs(s, '=>', env)})`;
1192
+
1193
+ // Path step, possibly with view parameters and/or filters
1194
+ let result = `${formatIdentifier(s.id)}`;
1195
+ if (s.args) {
1196
+ // View parameters
1197
+ result += `(${renderArgs(s, ':', env)})`;
1216
1198
  }
1217
- }
1218
-
1219
- /**
1220
- * @param {object} x Expression with a ref property
1221
- * @returns {string} Rendered expression
1222
- * @todo no extra magic with x.param or x.global
1223
- */
1224
- function renderExpressionRef(x) {
1225
- if (!x.param && !x.global) {
1226
- const magicReplacement = getVariableReplacement(x.ref, options);
1227
- if (x.ref[0] === '$user') {
1228
- if (magicReplacement !== null)
1229
- return `'${magicReplacement}'`;
1230
-
1231
- // Keep old way of solving this to remain backwards compatible
1232
- // FIXME: this is all not enough: we might need an explicit select item alias
1233
- if (x.ref[1] === 'id') {
1234
- if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
1235
- return `'${options.magicVars.user}'`;
1236
-
1237
- else if ((options.magicVars && options.magicVars.user && options.magicVars.user.id) && (typeof options.magicVars.user.id === 'string' || options.magicVars.user.id instanceof String))
1238
- return `'${options.magicVars.user.id}'`;
1239
-
1240
- return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1241
- }
1242
- else if (x.ref[1] === 'locale') {
1243
- return 'SESSION_CONTEXT(\'LOCALE\')';
1244
- }
1245
- }
1246
- else if (x.ref[0] === '$at') {
1247
- if (x.ref[1] === 'from')
1248
- return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1249
-
1250
- else if (x.ref[1] === 'to')
1251
- return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1252
- }
1253
- else if (x.ref[0] === '$session' && magicReplacement !== null) {
1254
- return `'${magicReplacement}'`;
1255
- }
1199
+ if (s.where) {
1200
+ // Filter, possibly with cardinality
1201
+ result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
1256
1202
  }
1257
- return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref)).join('.')}`;
1203
+ return result;
1258
1204
  }
1259
1205
 
1260
- /**
1261
- * Renders an explicit `cast()` inside an 'xpr'.
1262
- *
1263
- * @param {string} value Value to cast
1264
- * @returns {string} Rendered cast()
1265
- */
1266
- function renderExplicitTypeCast(value) {
1267
- let typeRef = renderTypeReference(expr.cast, env);
1206
+ throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1207
+ }
1268
1208
 
1269
- // inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
1270
- const hanaSqlType = cdsToSqlTypes.hana[expr.cast.type] || cdsToSqlTypes.standard[expr.cast.type];
1271
- if (hanaSqlType) {
1272
- const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
1273
- typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
1274
- }
1275
- return `CAST(${value} AS ${typeRef})`;
1209
+ /**
1210
+ * @param {object} x Expression with a val and/or literal property
1211
+ * @returns {string} Rendered expression
1212
+ */
1213
+ function renderExpressionLiteral(x) {
1214
+ // Literal value, possibly with explicit 'literal' property
1215
+ switch (x.literal || typeof x.val) {
1216
+ case 'number':
1217
+ case 'boolean':
1218
+ case 'null':
1219
+ return x.val;
1220
+ case 'x':
1221
+ case 'date':
1222
+ case 'time':
1223
+ case 'timestamp':
1224
+ return `${x.literal}'${x.val}'`;
1225
+ case 'string':
1226
+ return `'${x.val.replace(/'/g, '\'\'')}'`;
1227
+ case 'object':
1228
+ if (x.val === null)
1229
+ return 'null';
1230
+
1231
+ // otherwise fall through to
1232
+ default:
1233
+ throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1276
1234
  }
1235
+ }
1277
1236
 
1278
- /**
1279
- * Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
1280
- *
1281
- * @param {string|object} s Path step
1282
- * @param {number} idx Path position
1283
- * @returns {string} Rendered path step
1284
- */
1285
- function renderPathStep(s, idx, ref) {
1286
- // Simple id or absolute name
1287
- if (typeof s === 'string') {
1288
- // HANA-specific extra magic (should actually be in forHana)
1289
- // In HANA, we replace leading $self by the absolute name of the current artifact
1290
- // (see FIXME at renderArtifact)
1291
- if (idx === 0 && s === $SELF) {
1292
- // do not produce USING for $projection
1293
- if (env.currentArtifactName === $PROJECTION)
1294
- return env.currentArtifactName;
1295
-
1296
- return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
1297
- : renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
1298
- }
1299
- // HANA-specific translation of '$now' and '$user'
1300
- if (s === '$now' && ref.length === 1)
1301
- return 'CURRENT_TIMESTAMP';
1237
+ /**
1238
+ * Render the given expression x - which has a .func property
1239
+ *
1240
+ * @param {object} x
1241
+ * @param {CdlRenderEnvironment} env
1242
+ * @returns {string}
1243
+ */
1244
+ function renderExpressionFunc(x, env) {
1245
+ const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
1246
+ const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1247
+ // we can't quote functions with parens, issue warning if it is a reserved keyword
1248
+ if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1249
+ warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
1250
+ return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
1251
+ }
1302
1252
 
1303
- // In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
1304
- // FIXME: We should rather explicitly recognize quoting somehow
1253
+ /**
1254
+ * @param {object} x Expression with a ref property
1255
+ * @returns {string} Rendered expression
1256
+ * @todo no extra magic with x.param or x.global
1257
+ */
1258
+ function renderExpressionRef(x, env) {
1259
+ if (!x.param && !x.global) {
1260
+ const magicReplacement = getVariableReplacement(x.ref, options);
1261
+ if (x.ref[0] === '$user') {
1262
+ if (magicReplacement !== null)
1263
+ return `'${magicReplacement}'`;
1305
1264
 
1306
- // TODO: quote $parameters if it doesn't reference a parameter, this requires knowledge about the kind
1307
- // Example: both views are correct in HANA CDS
1308
- // entity E { key id: Integer; }
1309
- // view EV with parameters P1: Integer as select from E { id, $parameters.P1 };
1310
- // view EVp as select from E as "$parameters" { "$parameters".id };
1265
+ // Keep old way of solving this to remain backwards compatible
1266
+ // FIXME: this is all not enough: we might need an explicit select item alias
1267
+ if (x.ref[1] === 'id') {
1268
+ if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
1269
+ return `'${options.magicVars.user}'`;
1311
1270
 
1312
- if (idx === 0 &&
1313
- [ $SELF, $PROJECTION, '$session' ].includes(s))
1314
- return s;
1271
+ else if ((options.magicVars && options.magicVars.user && options.magicVars.user.id) && (typeof options.magicVars.user.id === 'string' || options.magicVars.user.id instanceof String))
1272
+ return `'${options.magicVars.user.id}'`;
1315
1273
 
1316
- return formatIdentifier(s);
1317
- }
1318
- // ID with filters or parameters
1319
- else if (typeof s === 'object') {
1320
- // Sanity check
1321
- if (!s.func && !s.id)
1322
- throw new Error(`Unknown path step object: ${JSON.stringify(s)}`);
1323
-
1324
- // Not really a path step but an object-like function call
1325
- if (s.func)
1326
- return `${s.func}(${renderArgs(s, '=>', env)})`;
1327
-
1328
- // Path step, possibly with view parameters and/or filters
1329
- let result = `${formatIdentifier(s.id)}`;
1330
- if (s.args) {
1331
- // View parameters
1332
- result += `(${renderArgs(s, ':', env)})`;
1274
+ return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1333
1275
  }
1334
- if (s.where) {
1335
- // Filter, possibly with cardinality
1336
- result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
1276
+ else if (x.ref[1] === 'locale') {
1277
+ return 'SESSION_CONTEXT(\'LOCALE\')';
1337
1278
  }
1338
- return result;
1339
1279
  }
1280
+ else if (x.ref[0] === '$at') {
1281
+ if (x.ref[1] === 'from')
1282
+ return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1340
1283
 
1341
- throw new Error(`Unknown path step: ${JSON.stringify(s)}`);
1284
+ else if (x.ref[1] === 'to')
1285
+ return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1286
+ }
1287
+ else if (x.ref[0] === '$session' && magicReplacement !== null) {
1288
+ return `'${magicReplacement}'`;
1289
+ }
1342
1290
  }
1291
+ return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, env, this.inline)).join('.')}`;
1343
1292
  }
1344
1293
 
1345
1294
  /**
@@ -1347,7 +1296,7 @@ function toHdbcdsSource(csn, options) {
1347
1296
  * using 'sep' as separator for positional parameters
1348
1297
  *
1349
1298
  * @param {object} node with `args` to render
1350
- * @param {string} sep Seperator between arguments
1299
+ * @param {string} sep Separator between arguments
1351
1300
  * @param {CdlRenderEnvironment} env Environment
1352
1301
  * @returns {string} Rendered arguments
1353
1302
  */
@@ -1355,15 +1304,15 @@ function toHdbcdsSource(csn, options) {
1355
1304
  const args = node.args ? node.args : {};
1356
1305
  // Positional arguments
1357
1306
  if (Array.isArray(args))
1358
- return args.map(arg => renderExpr(arg, env)).join(', ');
1307
+ return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
1359
1308
 
1360
1309
  // Named arguments (object/dict)
1361
1310
  else if (typeof args === 'object')
1362
1311
  // 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(', ');
1312
+ return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
1364
1313
 
1365
1314
 
1366
- throw new Error(`Unknown args: ${JSON.stringify(args)}`);
1315
+ throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1367
1316
  }
1368
1317
 
1369
1318
  /**
@@ -1645,7 +1594,7 @@ function toHdbcdsSource(csn, options) {
1645
1594
  if (plainNames) {
1646
1595
  const art = csn.definitions[name];
1647
1596
  // For 'plain' naming, take all entities and views, nothing else
1648
- if (art.kind === 'entity' || art.kind === 'view')
1597
+ if (art.kind === 'entity')
1649
1598
  result[name] = art;
1650
1599
  }
1651
1600
  else {
@@ -1690,7 +1639,7 @@ function toHdbcdsSource(csn, options) {
1690
1639
  }
1691
1640
 
1692
1641
  /**
1693
- * Returns a copy of 'env' with increased indentation (and resetted name prefix)
1642
+ * Returns a copy of 'env' with increased indentation - also resets the name prefix
1694
1643
  *
1695
1644
  * @param {CdlRenderEnvironment} env Current environment
1696
1645
  * @returns {CdlRenderEnvironment} New environment with increased indent
@@ -1711,15 +1660,15 @@ function toHdbcdsSource(csn, options) {
1711
1660
  }
1712
1661
 
1713
1662
  /**
1714
- * Return an absolute path 'abspath', with '::' inserted if required by naming strategy 'hdbcds',
1663
+ * Return an absolute path 'absPath', with '::' inserted if required by naming strategy 'hdbcds',
1715
1664
  * with appropriate "-quotes
1716
1665
  *
1717
- * @param {string} abspath Absolute path to quote
1666
+ * @param {string} absPath Absolute path to quote
1718
1667
  * @returns {string} Quoted path
1719
1668
  */
1720
- function quoteAbsolutePathString(abspath) {
1721
- const namespace = getNamespace(csn, abspath);
1722
- const resultingName = getResultingName(csn, options.sqlMapping, abspath);
1669
+ function quoteAbsolutePathString(absPath) {
1670
+ const namespace = getNamespace(csn, absPath);
1671
+ const resultingName = getResultingName(csn, options.sqlMapping, absPath);
1723
1672
 
1724
1673
  if (hdbcdsNames && namespace)
1725
1674
  return `${quotePathString(namespace)}::${quotePathString(resultingName.slice(namespace.length + 2))}`;
@@ -1736,14 +1685,13 @@ function toHdbcdsSource(csn, options) {
1736
1685
  function quoteId(id) {
1737
1686
  // Should only ever be called for real IDs (i.e. no dots inside)
1738
1687
  if (id.indexOf('.') !== -1)
1739
- throw new Error(id);
1688
+ throw new ModelError(`HDBCDS: Tried to quote id with dot: ${id}`);
1740
1689
 
1741
1690
 
1742
- switch (options.forHana.names) {
1691
+ switch (options.sqlMapping) {
1743
1692
  case 'plain':
1744
1693
  return smartId(id, 'hdbcds');
1745
1694
  case 'quoted':
1746
- return delimitedId(id, 'hdbcds');
1747
1695
  case 'hdbcds':
1748
1696
  return delimitedId(id, 'hdbcds');
1749
1697
  default:
@@ -1752,17 +1700,17 @@ function toHdbcdsSource(csn, options) {
1752
1700
  }
1753
1701
 
1754
1702
  /*
1755
- * Return an absolute name 'absname', with '::' inserted if required by naming strategy 'hdbcds', quoted
1703
+ * Return an absolute name 'absName', with '::' inserted if required by naming strategy 'hdbcds', quoted
1756
1704
  * as if it was a single identifier (required only for native USINGs)
1757
1705
  *
1758
- * @param {string} absname Absolute name
1759
- * @returns {string} Correctly quoted absname
1706
+ * @param {string} absName Absolute name
1707
+ * @returns {string} Correctly quoted absName
1760
1708
  */
1761
- function quoteAbsoluteNameAsId(absname) {
1762
- const resultingName = getResultingName(csn, options.sqlMapping, absname);
1709
+ function quoteAbsoluteNameAsId(absName) {
1710
+ const resultingName = getResultingName(csn, options.sqlMapping, absName);
1763
1711
 
1764
1712
  if (hdbcdsNames) {
1765
- const namespace = getNamespace(csn, absname);
1713
+ const namespace = getNamespace(csn, absName);
1766
1714
  if (namespace)
1767
1715
  return `"${(`${namespace}::${resultingName.substring(namespace.length + 2)}`).replace(/"/g, '""')}"`;
1768
1716
  }
@@ -1776,7 +1724,7 @@ function toHdbcdsSource(csn, options) {
1776
1724
  * @returns {string} Quoted/uppercased id
1777
1725
  */
1778
1726
  function formatIdentifier(id) {
1779
- id = options.forHana.names === 'plain' ? id.toUpperCase() : id;
1727
+ id = plainNames ? id.toUpperCase() : id;
1780
1728
  return quoteId(id);
1781
1729
  }
1782
1730