@sap/cds-compiler 2.13.8 → 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 (78) hide show
  1. package/CHANGELOG.md +109 -4
  2. package/bin/cdsc.js +112 -37
  3. package/lib/api/main.js +20 -22
  4. package/lib/api/options.js +2 -3
  5. package/lib/api/validate.js +6 -6
  6. package/lib/base/message-registry.js +89 -14
  7. package/lib/base/messages.js +85 -64
  8. package/lib/base/optionProcessorHelper.js +19 -0
  9. package/lib/checks/annotationsOData.js +11 -32
  10. package/lib/checks/arrayOfs.js +1 -34
  11. package/lib/checks/validator.js +2 -4
  12. package/lib/compiler/assert-consistency.js +1 -0
  13. package/lib/compiler/base.js +1 -0
  14. package/lib/compiler/builtins.js +11 -0
  15. package/lib/compiler/checks.js +22 -70
  16. package/lib/compiler/define.js +59 -11
  17. package/lib/compiler/extend.js +20 -3
  18. package/lib/compiler/finalize-parse-cdl.js +26 -20
  19. package/lib/compiler/index.js +75 -26
  20. package/lib/compiler/populate.js +6 -5
  21. package/lib/compiler/propagator.js +4 -1
  22. package/lib/compiler/resolve.js +104 -16
  23. package/lib/compiler/shared.js +61 -27
  24. package/lib/compiler/tweak-assocs.js +7 -1
  25. package/lib/edm/annotations/genericTranslation.js +33 -15
  26. package/lib/edm/csn2edm.js +216 -98
  27. package/lib/edm/edm.js +298 -225
  28. package/lib/edm/edmPreprocessor.js +486 -415
  29. package/lib/edm/edmUtils.js +22 -22
  30. package/lib/gen/Dictionary.json +90 -16
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +3 -1
  33. package/lib/gen/languageParser.js +4636 -4368
  34. package/lib/json/csnVersion.js +10 -11
  35. package/lib/json/from-csn.js +3 -2
  36. package/lib/json/to-csn.js +0 -2
  37. package/lib/language/docCommentParser.js +2 -2
  38. package/lib/language/genericAntlrParser.js +47 -2
  39. package/lib/language/language.g4 +59 -27
  40. package/lib/main.d.ts +19 -1
  41. package/lib/main.js +6 -0
  42. package/lib/model/csnRefs.js +33 -6
  43. package/lib/model/csnUtils.js +193 -75
  44. package/lib/model/enrichCsn.js +1 -0
  45. package/lib/model/revealInternalProperties.js +2 -2
  46. package/lib/modelCompare/compare.js +6 -6
  47. package/lib/optionProcessor.js +62 -26
  48. package/lib/render/toCdl.js +844 -679
  49. package/lib/render/toHdbcds.js +189 -243
  50. package/lib/render/toSql.js +180 -198
  51. package/lib/render/utils/common.js +131 -15
  52. package/lib/transform/db/.eslintrc.json +1 -1
  53. package/lib/transform/db/associations.js +2 -2
  54. package/lib/transform/db/constraints.js +3 -1
  55. package/lib/transform/db/expansion.js +15 -10
  56. package/lib/transform/db/flattening.js +94 -64
  57. package/lib/transform/db/transformExists.js +7 -7
  58. package/lib/transform/db/views.js +6 -3
  59. package/lib/transform/forHanaNew.js +43 -26
  60. package/lib/transform/forOdataNew.js +43 -42
  61. package/lib/transform/localized.js +12 -7
  62. package/lib/transform/odata/toFinalBaseType.js +5 -5
  63. package/lib/transform/odata/typesExposure.js +145 -197
  64. package/lib/transform/transformUtilsNew.js +9 -12
  65. package/lib/transform/translateAssocsToJoins.js +1 -1
  66. package/lib/transform/universalCsn/coreComputed.js +5 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
  68. package/lib/utils/moduleResolve.js +13 -6
  69. package/package.json +1 -1
  70. package/share/messages/message-explanations.json +2 -1
  71. package/share/messages/syntax-expected-integer.md +37 -0
  72. package/lib/transform/odata/attachPath.js +0 -96
  73. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  74. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  75. package/lib/transform/odata/referenceFlattener.js +0 -296
  76. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  77. package/lib/transform/odata/structuralPath.js +0 -72
  78. 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,13 @@ 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
36
  }
37
37
 
38
38
  /**
@@ -56,9 +56,40 @@ function toHdbcdsSource(csn, options) {
56
56
  const hdbcdsNames = options.sqlMapping === 'hdbcds';
57
57
 
58
58
  const {
59
- info, warning, error, throwWithError,
59
+ info, warning, error, throwWithAnyError,
60
60
  } = makeMessageFunction(csn, options, 'to.hdbcds');
61
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
+
62
93
  checkCSNVersion(csn, options);
63
94
 
64
95
  const hdbcdsResult = Object.create(null);
@@ -117,7 +148,7 @@ function toHdbcdsSource(csn, options) {
117
148
 
118
149
  killList.forEach(fn => fn());
119
150
 
120
- throwWithError();
151
+ throwWithAnyError();
121
152
  timetrace.stop();
122
153
  return options.testMode ? sort(hdbcdsResult) : hdbcdsResult;
123
154
 
@@ -217,7 +248,7 @@ function toHdbcdsSource(csn, options) {
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
@@ -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
  }
@@ -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
@@ -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}}`;
@@ -1109,239 +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
- * @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
1146
+ * @param {string|object} s Path step
1147
+ * @param {number} idx Path position
1148
+ * @returns {string} Rendered path step
1122
1149
  */
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
- }
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';
1133
1167
 
1134
- // Not a literal value but part of an operator, function etc - just leave as it is
1135
- 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
1136
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 };
1137
1176
 
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)})`;
1178
-
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))})`;
1185
- }
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))}`;
1189
- }
1177
+ if (idx === 0 &&
1178
+ [ $SELF, $PROJECTION, '$session' ].includes(s))
1179
+ return s;
1190
1180
 
1191
- throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
1181
+ return formatIdentifier(s);
1192
1182
  }
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
- // otherwise fall through to
1216
- default:
1217
- throw new ModelError(`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)})`;
1218
1198
  }
1219
- }
1220
-
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\'))';
1251
-
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
- }
1199
+ if (s.where) {
1200
+ // Filter, possibly with cardinality
1201
+ result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
1258
1202
  }
1259
- return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref)).join('.')}`;
1203
+ return result;
1260
1204
  }
1261
1205
 
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);
1206
+ throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1207
+ }
1270
1208
 
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})`;
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)}`);
1278
1234
  }
1235
+ }
1279
1236
 
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';
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
+ }
1304
1252
 
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
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}'`;
1307
1264
 
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 };
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}'`;
1313
1270
 
1314
- if (idx === 0 &&
1315
- [ $SELF, $PROJECTION, '$session' ].includes(s))
1316
- 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}'`;
1317
1273
 
1318
- return formatIdentifier(s);
1319
- }
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)})`;
1274
+ return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1335
1275
  }
1336
- if (s.where) {
1337
- // Filter, possibly with cardinality
1338
- result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
1276
+ else if (x.ref[1] === 'locale') {
1277
+ return 'SESSION_CONTEXT(\'LOCALE\')';
1339
1278
  }
1340
- return result;
1341
1279
  }
1280
+ else if (x.ref[0] === '$at') {
1281
+ if (x.ref[1] === 'from')
1282
+ return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1342
1283
 
1343
- throw new ModelError(`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
+ }
1344
1290
  }
1291
+ return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, env, this.inline)).join('.')}`;
1345
1292
  }
1346
1293
 
1347
1294
  /**
@@ -1349,7 +1296,7 @@ function toHdbcdsSource(csn, options) {
1349
1296
  * using 'sep' as separator for positional parameters
1350
1297
  *
1351
1298
  * @param {object} node with `args` to render
1352
- * @param {string} sep Seperator between arguments
1299
+ * @param {string} sep Separator between arguments
1353
1300
  * @param {CdlRenderEnvironment} env Environment
1354
1301
  * @returns {string} Rendered arguments
1355
1302
  */
@@ -1692,7 +1639,7 @@ function toHdbcdsSource(csn, options) {
1692
1639
  }
1693
1640
 
1694
1641
  /**
1695
- * 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
1696
1643
  *
1697
1644
  * @param {CdlRenderEnvironment} env Current environment
1698
1645
  * @returns {CdlRenderEnvironment} New environment with increased indent
@@ -1713,15 +1660,15 @@ function toHdbcdsSource(csn, options) {
1713
1660
  }
1714
1661
 
1715
1662
  /**
1716
- * 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',
1717
1664
  * with appropriate "-quotes
1718
1665
  *
1719
- * @param {string} abspath Absolute path to quote
1666
+ * @param {string} absPath Absolute path to quote
1720
1667
  * @returns {string} Quoted path
1721
1668
  */
1722
- function quoteAbsolutePathString(abspath) {
1723
- const namespace = getNamespace(csn, abspath);
1724
- 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);
1725
1672
 
1726
1673
  if (hdbcdsNames && namespace)
1727
1674
  return `${quotePathString(namespace)}::${quotePathString(resultingName.slice(namespace.length + 2))}`;
@@ -1738,14 +1685,13 @@ function toHdbcdsSource(csn, options) {
1738
1685
  function quoteId(id) {
1739
1686
  // Should only ever be called for real IDs (i.e. no dots inside)
1740
1687
  if (id.indexOf('.') !== -1)
1741
- throw new ModelError(id);
1688
+ throw new ModelError(`HDBCDS: Tried to quote id with dot: ${id}`);
1742
1689
 
1743
1690
 
1744
- switch (options.forHana.names) {
1691
+ switch (options.sqlMapping) {
1745
1692
  case 'plain':
1746
1693
  return smartId(id, 'hdbcds');
1747
1694
  case 'quoted':
1748
- return delimitedId(id, 'hdbcds');
1749
1695
  case 'hdbcds':
1750
1696
  return delimitedId(id, 'hdbcds');
1751
1697
  default:
@@ -1754,17 +1700,17 @@ function toHdbcdsSource(csn, options) {
1754
1700
  }
1755
1701
 
1756
1702
  /*
1757
- * 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
1758
1704
  * as if it was a single identifier (required only for native USINGs)
1759
1705
  *
1760
- * @param {string} absname Absolute name
1761
- * @returns {string} Correctly quoted absname
1706
+ * @param {string} absName Absolute name
1707
+ * @returns {string} Correctly quoted absName
1762
1708
  */
1763
- function quoteAbsoluteNameAsId(absname) {
1764
- const resultingName = getResultingName(csn, options.sqlMapping, absname);
1709
+ function quoteAbsoluteNameAsId(absName) {
1710
+ const resultingName = getResultingName(csn, options.sqlMapping, absName);
1765
1711
 
1766
1712
  if (hdbcdsNames) {
1767
- const namespace = getNamespace(csn, absname);
1713
+ const namespace = getNamespace(csn, absName);
1768
1714
  if (namespace)
1769
1715
  return `"${(`${namespace}::${resultingName.substring(namespace.length + 2)}`).replace(/"/g, '""')}"`;
1770
1716
  }
@@ -1778,7 +1724,7 @@ function toHdbcdsSource(csn, options) {
1778
1724
  * @returns {string} Quoted/uppercased id
1779
1725
  */
1780
1726
  function formatIdentifier(id) {
1781
- id = options.forHana.names === 'plain' ? id.toUpperCase() : id;
1727
+ id = plainNames ? id.toUpperCase() : id;
1782
1728
  return quoteId(id);
1783
1729
  }
1784
1730