@sap/cds-compiler 2.13.8 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +155 -1594
  2. package/bin/cdsc.js +144 -66
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +237 -122
  10. package/lib/api/options.js +17 -88
  11. package/lib/api/validate.js +12 -16
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +152 -37
  14. package/lib/base/messages.js +145 -83
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +19 -0
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +11 -32
  19. package/lib/checks/arrayOfs.js +1 -34
  20. package/lib/checks/cdsPersistence.js +1 -0
  21. package/lib/checks/elements.js +6 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/queryNoDbArtifacts.js +2 -1
  25. package/lib/checks/selectItems.js +5 -1
  26. package/lib/checks/types.js +4 -2
  27. package/lib/checks/utils.js +2 -2
  28. package/lib/checks/validator.js +4 -5
  29. package/lib/compiler/assert-consistency.js +16 -10
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +98 -9
  32. package/lib/compiler/checks.js +22 -70
  33. package/lib/compiler/define.js +61 -13
  34. package/lib/compiler/extend.js +79 -14
  35. package/lib/compiler/finalize-parse-cdl.js +46 -29
  36. package/lib/compiler/index.js +100 -37
  37. package/lib/compiler/moduleLayers.js +7 -0
  38. package/lib/compiler/populate.js +19 -18
  39. package/lib/compiler/propagator.js +7 -4
  40. package/lib/compiler/resolve.js +297 -234
  41. package/lib/compiler/shared.js +107 -102
  42. package/lib/compiler/tweak-assocs.js +16 -11
  43. package/lib/compiler/utils.js +5 -0
  44. package/lib/edm/annotations/genericTranslation.js +93 -21
  45. package/lib/edm/csn2edm.js +230 -115
  46. package/lib/edm/edm.js +305 -226
  47. package/lib/edm/edmPreprocessor.js +509 -438
  48. package/lib/edm/edmUtils.js +31 -45
  49. package/lib/gen/Dictionary.json +98 -22
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +10 -30
  52. package/lib/gen/language.tokens +105 -114
  53. package/lib/gen/languageLexer.interp +1 -34
  54. package/lib/gen/languageLexer.js +889 -1007
  55. package/lib/gen/languageLexer.tokens +95 -106
  56. package/lib/gen/languageParser.js +20786 -22199
  57. package/lib/json/csnVersion.js +10 -11
  58. package/lib/json/from-csn.js +59 -51
  59. package/lib/json/to-csn.js +10 -10
  60. package/lib/language/antlrParser.js +2 -2
  61. package/lib/language/docCommentParser.js +62 -39
  62. package/lib/language/errorStrategy.js +52 -40
  63. package/lib/language/genericAntlrParser.js +348 -229
  64. package/lib/language/language.g4 +629 -653
  65. package/lib/language/multiLineStringParser.js +14 -42
  66. package/lib/language/textUtils.js +44 -0
  67. package/lib/main.d.ts +46 -43
  68. package/lib/main.js +108 -79
  69. package/lib/model/csnRefs.js +34 -7
  70. package/lib/model/csnUtils.js +337 -332
  71. package/lib/model/enrichCsn.js +1 -0
  72. package/lib/model/revealInternalProperties.js +30 -10
  73. package/lib/model/sortViews.js +32 -31
  74. package/lib/modelCompare/compare.js +6 -6
  75. package/lib/optionProcessor.js +73 -46
  76. package/lib/render/.eslintrc.json +1 -1
  77. package/lib/render/DuplicateChecker.js +4 -7
  78. package/lib/render/manageConstraints.js +70 -2
  79. package/lib/render/toCdl.js +1042 -882
  80. package/lib/render/toHdbcds.js +195 -245
  81. package/lib/render/toRename.js +44 -22
  82. package/lib/render/toSql.js +225 -241
  83. package/lib/render/utils/common.js +145 -15
  84. package/lib/render/utils/sql.js +20 -19
  85. package/lib/sql-identifier.js +6 -0
  86. package/lib/transform/db/.eslintrc.json +4 -3
  87. package/lib/transform/db/associations.js +2 -2
  88. package/lib/transform/db/cdsPersistence.js +5 -15
  89. package/lib/transform/db/constraints.js +4 -2
  90. package/lib/transform/db/expansion.js +22 -16
  91. package/lib/transform/db/flattening.js +109 -80
  92. package/lib/transform/db/transformExists.js +7 -7
  93. package/lib/transform/db/views.js +9 -6
  94. package/lib/transform/draft/.eslintrc.json +2 -2
  95. package/lib/transform/draft/db.js +6 -6
  96. package/lib/transform/draft/odata.js +6 -7
  97. package/lib/transform/forHanaNew.js +62 -48
  98. package/lib/transform/forOdataNew.js +49 -50
  99. package/lib/transform/localized.js +31 -20
  100. package/lib/transform/odata/toFinalBaseType.js +16 -14
  101. package/lib/transform/odata/typesExposure.js +146 -198
  102. package/lib/transform/odata/utils.js +1 -38
  103. package/lib/transform/transformUtilsNew.js +67 -84
  104. package/lib/transform/translateAssocsToJoins.js +7 -3
  105. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  106. package/lib/transform/universalCsn/coreComputed.js +16 -9
  107. package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
  108. package/lib/utils/file.js +3 -3
  109. package/lib/utils/moduleResolve.js +13 -6
  110. package/lib/utils/timetrace.js +20 -21
  111. package/package.json +35 -4
  112. package/share/messages/message-explanations.json +2 -1
  113. package/share/messages/syntax-expected-integer.md +37 -0
  114. package/doc/ApiMigration.md +0 -237
  115. package/doc/CommandLineMigration.md +0 -58
  116. package/doc/ErrorMessages.md +0 -175
  117. package/doc/FioriAnnotations.md +0 -94
  118. package/doc/ODataTransformation.md +0 -273
  119. package/lib/backends.js +0 -529
  120. package/lib/fix_antlr4-8_warning.js +0 -56
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -296
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -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,
@@ -26,13 +26,23 @@ const $PROJECTION = '$projection';
26
26
  const $SELF = '$self';
27
27
 
28
28
  /**
29
- * 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.
30
30
  *
31
31
  * @param {CSN.Artifact} obj
32
32
  * @returns {string}
33
33
  */
34
34
  function getEscapedHanaComment(obj) {
35
- return getHanaComment(obj).replace(/\n/g, '\\n');
35
+ return getHanaComment(obj).replace(/\n/g, '\\n').replace(/'/g, "''");
36
+ }
37
+
38
+ /**
39
+ * Render a string for HDBCDS, i.e. put it in quotes and escape single quotes.
40
+ *
41
+ * @param {string} str
42
+ * @returns {string}
43
+ */
44
+ function renderStringForHdbcds(str) {
45
+ return `'${str.replace(/'/g, '\'\'')}'`;
36
46
  }
37
47
 
38
48
  /**
@@ -56,9 +66,40 @@ function toHdbcdsSource(csn, options) {
56
66
  const hdbcdsNames = options.sqlMapping === 'hdbcds';
57
67
 
58
68
  const {
59
- info, warning, error, throwWithError,
69
+ info, warning, error, throwWithAnyError,
60
70
  } = makeMessageFunction(csn, options, 'to.hdbcds');
61
71
 
72
+ const renderExpr = getExpressionRenderer({
73
+ finalize: x => x,
74
+ explicitTypeCast(x, env) {
75
+ let typeRef = renderTypeReference(x.cast, env);
76
+
77
+ // inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
78
+ const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
79
+ if (hanaSqlType) {
80
+ const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
81
+ typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
82
+ }
83
+ return `CAST(${renderExpr(x, env)} AS ${typeRef})`;
84
+ },
85
+ val: renderExpressionLiteral,
86
+ enum: x => `#${x['#']}`,
87
+ ref: renderExpressionRef,
88
+ aliasOnly(x, _env) {
89
+ return x.as;
90
+ },
91
+ windowFunction: renderExpressionFunc,
92
+ func: renderExpressionFunc,
93
+ xpr(x, env) {
94
+ if (this.nestedExpr && !x.cast)
95
+ return `(${renderExpr(x.xpr, env, this.inline, true)})`;
96
+
97
+ return renderExpr(x.xpr, env, this.inline, true);
98
+ },
99
+ SELECT: (x, env) => `(${renderQuery(x, false, increaseIndent(env))})`,
100
+ SET: (x, env) => `${renderQuery(x, false, increaseIndent(env))}`,
101
+ });
102
+
62
103
  checkCSNVersion(csn, options);
63
104
 
64
105
  const hdbcdsResult = Object.create(null);
@@ -117,7 +158,7 @@ function toHdbcdsSource(csn, options) {
117
158
 
118
159
  killList.forEach(fn => fn());
119
160
 
120
- throwWithError();
161
+ throwWithAnyError();
121
162
  timetrace.stop();
122
163
  return options.testMode ? sort(hdbcdsResult) : hdbcdsResult;
123
164
 
@@ -217,7 +258,7 @@ function toHdbcdsSource(csn, options) {
217
258
  *
218
259
  * @param {string} containee Name of the contained artifact
219
260
  * @param {string} contextName Name of the (grand?)parent context
220
- * @returns {boolean} True if there is another context inbetween
261
+ * @returns {boolean} True if there is another context in between
221
262
  */
222
263
  function isContainedInOtherContext(containee, contextName) {
223
264
  const parts = containee.split('.');
@@ -280,7 +321,7 @@ function toHdbcdsSource(csn, options) {
280
321
  * Render a context or service. Return the resulting source string.
281
322
  *
282
323
  * 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.
324
+ * but any contained (and transitively contained) entities and views are.
284
325
  *
285
326
  * @param {string} artifactName Name of the context/service
286
327
  * @param {CSN.Artifact} art Content of the context/service
@@ -687,19 +728,20 @@ function toHdbcdsSource(csn, options) {
687
728
  * Return the resulting source string (no trailing LF).
688
729
  *
689
730
  * @param {object} col Column to render
731
+ * @param {CSN.Elements} elements where column exists
690
732
  * @param {CdlRenderEnvironment} env Environment
691
- * @param {CSN.Element} [element] Element (non-enum from subquery possibly) corresponding to the column ref
692
733
  * @returns {string} Rendered column
693
734
  */
694
- function renderViewColumn(col, env, element) {
735
+ function renderViewColumn(col, elements, env ) {
695
736
  // Annotations and column
696
737
  let result = '';
697
738
 
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
739
+ const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
740
+ const element = elements[leaf];
700
741
 
701
- if (element && element.virtual || env._artifact.elements[leaf] && env._artifact.elements[leaf].virtual) {
702
- if (isDeprecatedEnabled(options, 'renderVirtualElements'))
742
+ // Render 'null as <alias>' only for database and if element is virtual
743
+ if (element && element.virtual) {
744
+ if (isDeprecatedEnabled(options, '_renderVirtualElements'))
703
745
  return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
704
746
  }
705
747
  else {
@@ -803,12 +845,12 @@ function toHdbcdsSource(csn, options) {
803
845
  // Set operator, like UNION, INTERSECT, ...
804
846
  if (query.SET) {
805
847
  // First arg may be leading query
806
- result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements)}`;
848
+ result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements || query.SET.elements)}`;
807
849
  // FIXME: Clarify if set operators can be n-ary (assuming binary here)
808
850
  if (query.SET.op) {
809
851
  // Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
810
852
  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 ]))}`;
853
+ 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
854
  }
813
855
  result += ')';
814
856
  // Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
@@ -843,7 +885,7 @@ function toHdbcdsSource(csn, options) {
843
885
  result += select.distinct ? ' distinct' : '';
844
886
  if (select.columns) {
845
887
  result += ' {\n';
846
- result += `${select.columns.map(col => renderViewColumn(col, childEnv, findElement(elements, col)))
888
+ result += `${select.columns.map(col => renderViewColumn(col, elements || select.elements, childEnv))
847
889
  .filter(s => s !== '')
848
890
  .join(',\n')}\n`;
849
891
  result += `${env.indent}}`;
@@ -1109,239 +1151,148 @@ function toHdbcdsSource(csn, options) {
1109
1151
  }
1110
1152
 
1111
1153
  /**
1112
- * Render an expression (including paths and values) or condition 'x'.
1113
- * (no trailing LF, don't indent if inline)
1154
+ * Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
1114
1155
  *
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
- * @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.
1121
- * @returns {string} Rendered expression
1156
+ * @param {string|object} s Path step
1157
+ * @param {number} idx Path position
1158
+ * @returns {string} Rendered path step
1122
1159
  */
1123
- function renderExpr(expr, env, inline = true, inExpr = false, alwaysRenderCast = false) {
1124
- // Compound expression
1125
- if (Array.isArray(expr))
1126
- return beautifyExprArray(expr.map(item => renderExpr(item, env, inline, inExpr)));
1127
-
1128
- if (typeof expr === 'object' && expr !== null) {
1129
- if ((inExpr || alwaysRenderCast) && expr.cast && expr.cast.type)
1130
- return renderExplicitTypeCast(renderExprObject(expr));
1131
- return renderExprObject(expr);
1132
- }
1160
+ function renderPathStep(s, idx, ref, env, inline) {
1161
+ // Simple id or absolute name
1162
+ if (typeof s === 'string') {
1163
+ // HANA-specific extra magic (should actually be in forHana)
1164
+ // In HANA, we replace leading $self by the absolute name of the current artifact
1165
+ // (see FIXME at renderArtifact)
1166
+ if (idx === 0 && s === $SELF) {
1167
+ // do not produce USING for $projection
1168
+ if (env.currentArtifactName === $PROJECTION)
1169
+ return env.currentArtifactName;
1170
+
1171
+ return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
1172
+ : renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
1173
+ }
1174
+ // HANA-specific translation of '$now' and '$user'
1175
+ if (s === '$now' && ref.length === 1)
1176
+ return 'CURRENT_TIMESTAMP';
1133
1177
 
1134
- // Not a literal value but part of an operator, function etc - just leave as it is
1135
- return expr;
1178
+ // In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
1179
+ // FIXME: We should rather explicitly recognize quoting somehow
1136
1180
 
1181
+ // TODO: quote $parameters if it doesn't reference a parameter, this requires knowledge about the kind
1182
+ // Example: both views are correct in HANA CDS
1183
+ // entity E { key id: Integer; }
1184
+ // view EV with parameters P1: Integer as select from E { id, $parameters.P1 };
1185
+ // view EVp as select from E as "$parameters" { "$parameters".id };
1137
1186
 
1138
- /**
1139
- * Various special cases represented as objects
1140
- *
1141
- * @param {object} x Expression
1142
- * @returns {string} Rendered expression object
1143
- */
1144
- function renderExprObject(x) {
1145
- if (x.list) {
1146
- // set "inExpr" to false: treat list elements as new expressions
1147
- return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
1148
- }
1149
- else if (x.val !== undefined) {
1150
- return renderExpressionLiteral(x);
1151
- }
1152
- // Enum symbol
1153
- else if (x['#']) {
1154
- return `#${x['#']}`;
1155
- }
1156
- // Reference: Array of path steps, possibly preceded by ':'
1157
- else if (x.ref) {
1158
- return renderExpressionRef(x);
1159
- }
1160
- // Function call, possibly with args (use '=>' for named args)
1161
- else if (x.func) {
1162
- // test for non-regular HANA identifier that needs to be quoted
1163
- // identifier {letter}({letter_or_digit}|[#$])*
1164
- // letter [A-Za-z_]
1165
- // letter_or_digit [A-Za-z_0-9]
1166
-
1167
- const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
1168
- const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1169
- // we can't quote functions with parens, issue warning if it is a reserved keyword
1170
- if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1171
- warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
1172
- return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
1173
- }
1174
- // Nested expression
1175
- else if (x.xpr) {
1176
- if (inExpr && !x.cast)
1177
- return `(${renderExpr(x.xpr, env, inline, true)})`;
1187
+ if (idx === 0 &&
1188
+ [ $SELF, $PROJECTION, '$session' ].includes(s))
1189
+ return s;
1178
1190
 
1179
- return renderExpr(x.xpr, env, inline, true);
1180
- }
1181
- // Sub-select
1182
- else if (x.SELECT) {
1183
- // renderQuery for SELECT does not bring its own parentheses (because it is also used in renderView)
1184
- return `(${renderQuery(x, false, increaseIndent(env))})`;
1191
+ return formatIdentifier(s);
1192
+ }
1193
+ // ID with filters or parameters
1194
+ else if (typeof s === 'object') {
1195
+ // Sanity check
1196
+ if (!s.func && !s.id)
1197
+ throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
1198
+
1199
+ // Not really a path step but an object-like function call
1200
+ if (s.func)
1201
+ return `${s.func}(${renderArgs(s, '=>', env)})`;
1202
+
1203
+ // Path step, possibly with view parameters and/or filters
1204
+ let result = `${formatIdentifier(s.id)}`;
1205
+ if (s.args) {
1206
+ // View parameters
1207
+ result += `(${renderArgs(s, ':', env)})`;
1185
1208
  }
1186
- else if (x.SET) {
1187
- // renderQuery for SET always brings its own parentheses (because it is also used in renderViewSource)
1188
- return `${renderQuery(x, false, increaseIndent(env))}`;
1209
+ if (s.where) {
1210
+ // Filter, possibly with cardinality
1211
+ result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
1189
1212
  }
1190
-
1191
- throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
1213
+ return result;
1192
1214
  }
1193
- /**
1194
- * @param {object} x Expression with a val and/or literal property
1195
- * @returns {string} Rendered expression
1196
- */
1197
- function renderExpressionLiteral(x) {
1198
- // Literal value, possibly with explicit 'literal' property
1199
- switch (x.literal || typeof x.val) {
1200
- case 'number':
1201
- case 'boolean':
1202
- case 'null':
1203
- return x.val;
1204
- case 'x':
1205
- case 'date':
1206
- case 'time':
1207
- case 'timestamp':
1208
- return `${x.literal}'${x.val}'`;
1209
- case 'string':
1210
- return `'${x.val.replace(/'/g, '\'\'')}'`;
1211
- case 'object':
1212
- if (x.val === null)
1213
- return 'null';
1214
1215
 
1215
- // otherwise fall through to
1216
- default:
1217
- throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1218
- }
1219
- }
1216
+ throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1217
+ }
1220
1218
 
1221
- /**
1222
- * @param {object} x Expression with a ref property
1223
- * @returns {string} Rendered expression
1224
- * @todo no extra magic with x.param or x.global
1225
- */
1226
- function renderExpressionRef(x) {
1227
- if (!x.param && !x.global) {
1228
- const magicReplacement = getVariableReplacement(x.ref, options);
1229
- if (x.ref[0] === '$user') {
1230
- if (magicReplacement !== null)
1231
- return `'${magicReplacement}'`;
1232
-
1233
- // Keep old way of solving this to remain backwards compatible
1234
- // FIXME: this is all not enough: we might need an explicit select item alias
1235
- if (x.ref[1] === 'id') {
1236
- if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
1237
- return `'${options.magicVars.user}'`;
1238
-
1239
- else if ((options.magicVars && options.magicVars.user && options.magicVars.user.id) && (typeof options.magicVars.user.id === 'string' || options.magicVars.user.id instanceof String))
1240
- return `'${options.magicVars.user.id}'`;
1241
-
1242
- return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1243
- }
1244
- else if (x.ref[1] === 'locale') {
1245
- return 'SESSION_CONTEXT(\'LOCALE\')';
1246
- }
1247
- }
1248
- else if (x.ref[0] === '$at') {
1249
- if (x.ref[1] === 'from')
1250
- return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1219
+ /**
1220
+ * @param {object} x Expression with a val and/or literal property
1221
+ * @returns {string} Rendered expression
1222
+ */
1223
+ function renderExpressionLiteral(x) {
1224
+ // Literal value, possibly with explicit 'literal' property
1225
+ switch (x.literal || typeof x.val) {
1226
+ case 'number':
1227
+ case 'boolean':
1228
+ case 'null':
1229
+ return x.val;
1230
+ case 'x':
1231
+ case 'date':
1232
+ case 'time':
1233
+ case 'timestamp':
1234
+ return `${x.literal}'${x.val}'`;
1235
+ case 'string':
1236
+ return renderStringForHdbcds(x.val);
1237
+ case 'object':
1238
+ if (x.val === null)
1239
+ return 'null';
1251
1240
 
1252
- else if (x.ref[1] === 'to')
1253
- return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1254
- }
1255
- else if (x.ref[0] === '$session' && magicReplacement !== null) {
1256
- return `'${magicReplacement}'`;
1257
- }
1258
- }
1259
- return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref)).join('.')}`;
1241
+ // otherwise fall through to
1242
+ default:
1243
+ throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1260
1244
  }
1245
+ }
1261
1246
 
1262
- /**
1263
- * Renders an explicit `cast()` inside an 'xpr'.
1264
- *
1265
- * @param {string} value Value to cast
1266
- * @returns {string} Rendered cast()
1267
- */
1268
- function renderExplicitTypeCast(value) {
1269
- let typeRef = renderTypeReference(expr.cast, env);
1270
-
1271
- // inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
1272
- const hanaSqlType = cdsToSqlTypes.hana[expr.cast.type] || cdsToSqlTypes.standard[expr.cast.type];
1273
- if (hanaSqlType) {
1274
- const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
1275
- typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
1276
- }
1277
- return `CAST(${value} AS ${typeRef})`;
1278
- }
1247
+ /**
1248
+ * Render the given expression x - which has a .func property
1249
+ *
1250
+ * @param {object} x
1251
+ * @param {CdlRenderEnvironment} env
1252
+ * @returns {string}
1253
+ */
1254
+ function renderExpressionFunc(x, env) {
1255
+ const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
1256
+ const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1257
+ // we can't quote functions with parens, issue warning if it is a reserved keyword
1258
+ if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1259
+ warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
1260
+ return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
1261
+ }
1279
1262
 
1280
- /**
1281
- * Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
1282
- *
1283
- * @param {string|object} s Path step
1284
- * @param {number} idx Path position
1285
- * @returns {string} Rendered path step
1286
- */
1287
- function renderPathStep(s, idx, ref) {
1288
- // Simple id or absolute name
1289
- if (typeof s === 'string') {
1290
- // HANA-specific extra magic (should actually be in forHana)
1291
- // In HANA, we replace leading $self by the absolute name of the current artifact
1292
- // (see FIXME at renderArtifact)
1293
- if (idx === 0 && s === $SELF) {
1294
- // do not produce USING for $projection
1295
- if (env.currentArtifactName === $PROJECTION)
1296
- return env.currentArtifactName;
1297
-
1298
- return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
1299
- : renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
1300
- }
1301
- // HANA-specific translation of '$now' and '$user'
1302
- if (s === '$now' && ref.length === 1)
1303
- return 'CURRENT_TIMESTAMP';
1263
+ /**
1264
+ * @param {object} x Expression with a ref property
1265
+ * @returns {string} Rendered expression
1266
+ * @todo no extra magic with x.param or x.global
1267
+ */
1268
+ function renderExpressionRef(x, env) {
1269
+ if (!x.param && !x.global) {
1270
+ const magicReplacement = getVariableReplacement(x.ref, options);
1271
+ if (x.ref[0] === '$user') {
1272
+ if (magicReplacement !== null)
1273
+ return renderStringForHdbcds(magicReplacement);
1304
1274
 
1305
- // In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
1306
- // FIXME: We should rather explicitly recognize quoting somehow
1275
+ // Note: The compiler already transforms $user into $user.id.
1307
1276
 
1308
- // TODO: quote $parameters if it doesn't reference a parameter, this requires knowledge about the kind
1309
- // Example: both views are correct in HANA CDS
1310
- // entity E { key id: Integer; }
1311
- // view EV with parameters P1: Integer as select from E { id, $parameters.P1 };
1312
- // view EVp as select from E as "$parameters" { "$parameters".id };
1277
+ // FIXME: this is all not enough: we might need an explicit select item alias (?)
1278
+ if (x.ref[1] === 'id')
1279
+ return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1313
1280
 
1314
- if (idx === 0 &&
1315
- [ $SELF, $PROJECTION, '$session' ].includes(s))
1316
- return s;
1281
+ else if (x.ref[1] === 'locale')
1282
+ return 'SESSION_CONTEXT(\'LOCALE\')';
1283
+ }
1284
+ else if (x.ref[0] === '$at') {
1285
+ if (x.ref[1] === 'from')
1286
+ return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1317
1287
 
1318
- return formatIdentifier(s);
1288
+ else if (x.ref[1] === 'to')
1289
+ return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1319
1290
  }
1320
- // ID with filters or parameters
1321
- else if (typeof s === 'object') {
1322
- // Sanity check
1323
- if (!s.func && !s.id)
1324
- throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
1325
-
1326
- // Not really a path step but an object-like function call
1327
- if (s.func)
1328
- return `${s.func}(${renderArgs(s, '=>', env)})`;
1329
-
1330
- // Path step, possibly with view parameters and/or filters
1331
- let result = `${formatIdentifier(s.id)}`;
1332
- if (s.args) {
1333
- // View parameters
1334
- result += `(${renderArgs(s, ':', env)})`;
1335
- }
1336
- if (s.where) {
1337
- // Filter, possibly with cardinality
1338
- result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
1339
- }
1340
- return result;
1291
+ else if (x.ref[0] === '$session' && magicReplacement !== null) {
1292
+ return renderStringForHdbcds(magicReplacement);
1341
1293
  }
1342
-
1343
- throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1344
1294
  }
1295
+ return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, env, this.inline)).join('.')}`;
1345
1296
  }
1346
1297
 
1347
1298
  /**
@@ -1349,7 +1300,7 @@ function toHdbcdsSource(csn, options) {
1349
1300
  * using 'sep' as separator for positional parameters
1350
1301
  *
1351
1302
  * @param {object} node with `args` to render
1352
- * @param {string} sep Seperator between arguments
1303
+ * @param {string} sep Separator between arguments
1353
1304
  * @param {CdlRenderEnvironment} env Environment
1354
1305
  * @returns {string} Rendered arguments
1355
1306
  */
@@ -1692,7 +1643,7 @@ function toHdbcdsSource(csn, options) {
1692
1643
  }
1693
1644
 
1694
1645
  /**
1695
- * Returns a copy of 'env' with increased indentation (and resetted name prefix)
1646
+ * Returns a copy of 'env' with increased indentation - also resets the name prefix
1696
1647
  *
1697
1648
  * @param {CdlRenderEnvironment} env Current environment
1698
1649
  * @returns {CdlRenderEnvironment} New environment with increased indent
@@ -1713,15 +1664,15 @@ function toHdbcdsSource(csn, options) {
1713
1664
  }
1714
1665
 
1715
1666
  /**
1716
- * Return an absolute path 'abspath', with '::' inserted if required by naming strategy 'hdbcds',
1667
+ * Return an absolute path 'absPath', with '::' inserted if required by naming strategy 'hdbcds',
1717
1668
  * with appropriate "-quotes
1718
1669
  *
1719
- * @param {string} abspath Absolute path to quote
1670
+ * @param {string} absPath Absolute path to quote
1720
1671
  * @returns {string} Quoted path
1721
1672
  */
1722
- function quoteAbsolutePathString(abspath) {
1723
- const namespace = getNamespace(csn, abspath);
1724
- const resultingName = getResultingName(csn, options.sqlMapping, abspath);
1673
+ function quoteAbsolutePathString(absPath) {
1674
+ const namespace = getNamespace(csn, absPath);
1675
+ const resultingName = getResultingName(csn, options.sqlMapping, absPath);
1725
1676
 
1726
1677
  if (hdbcdsNames && namespace)
1727
1678
  return `${quotePathString(namespace)}::${quotePathString(resultingName.slice(namespace.length + 2))}`;
@@ -1738,14 +1689,13 @@ function toHdbcdsSource(csn, options) {
1738
1689
  function quoteId(id) {
1739
1690
  // Should only ever be called for real IDs (i.e. no dots inside)
1740
1691
  if (id.indexOf('.') !== -1)
1741
- throw new ModelError(id);
1692
+ throw new ModelError(`HDBCDS: Tried to quote id with dot: ${id}`);
1742
1693
 
1743
1694
 
1744
- switch (options.forHana.names) {
1695
+ switch (options.sqlMapping) {
1745
1696
  case 'plain':
1746
1697
  return smartId(id, 'hdbcds');
1747
1698
  case 'quoted':
1748
- return delimitedId(id, 'hdbcds');
1749
1699
  case 'hdbcds':
1750
1700
  return delimitedId(id, 'hdbcds');
1751
1701
  default:
@@ -1754,17 +1704,17 @@ function toHdbcdsSource(csn, options) {
1754
1704
  }
1755
1705
 
1756
1706
  /*
1757
- * Return an absolute name 'absname', with '::' inserted if required by naming strategy 'hdbcds', quoted
1707
+ * Return an absolute name 'absName', with '::' inserted if required by naming strategy 'hdbcds', quoted
1758
1708
  * as if it was a single identifier (required only for native USINGs)
1759
1709
  *
1760
- * @param {string} absname Absolute name
1761
- * @returns {string} Correctly quoted absname
1710
+ * @param {string} absName Absolute name
1711
+ * @returns {string} Correctly quoted absName
1762
1712
  */
1763
- function quoteAbsoluteNameAsId(absname) {
1764
- const resultingName = getResultingName(csn, options.sqlMapping, absname);
1713
+ function quoteAbsoluteNameAsId(absName) {
1714
+ const resultingName = getResultingName(csn, options.sqlMapping, absName);
1765
1715
 
1766
1716
  if (hdbcdsNames) {
1767
- const namespace = getNamespace(csn, absname);
1717
+ const namespace = getNamespace(csn, absName);
1768
1718
  if (namespace)
1769
1719
  return `"${(`${namespace}::${resultingName.substring(namespace.length + 2)}`).replace(/"/g, '""')}"`;
1770
1720
  }
@@ -1778,7 +1728,7 @@ function toHdbcdsSource(csn, options) {
1778
1728
  * @returns {string} Quoted/uppercased id
1779
1729
  */
1780
1730
  function formatIdentifier(id) {
1781
- id = options.forHana.names === 'plain' ? id.toUpperCase() : id;
1731
+ id = plainNames ? id.toUpperCase() : id;
1782
1732
  return quoteId(id);
1783
1733
  }
1784
1734