@sap/cds-compiler 2.10.2 → 2.11.4

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 (82) hide show
  1. package/CHANGELOG.md +90 -5
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +3 -1
  4. package/bin/cdsc.js +49 -25
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_BETA.md +10 -0
  8. package/lib/api/.eslintrc.json +2 -0
  9. package/lib/api/main.js +8 -36
  10. package/lib/api/options.js +15 -6
  11. package/lib/api/validate.js +30 -3
  12. package/lib/backends.js +12 -13
  13. package/lib/base/dictionaries.js +2 -1
  14. package/lib/base/keywords.js +3 -2
  15. package/lib/base/message-registry.js +34 -10
  16. package/lib/base/messages.js +38 -18
  17. package/lib/base/model.js +5 -4
  18. package/lib/base/optionProcessorHelper.js +57 -23
  19. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  20. package/lib/checks/selectItems.js +4 -0
  21. package/lib/checks/unknownMagic.js +6 -3
  22. package/lib/compiler/assert-consistency.js +9 -2
  23. package/lib/compiler/base.js +65 -0
  24. package/lib/compiler/builtins.js +62 -16
  25. package/lib/compiler/checks.js +2 -1
  26. package/lib/compiler/definer.js +66 -108
  27. package/lib/compiler/index.js +29 -29
  28. package/lib/compiler/propagator.js +5 -2
  29. package/lib/compiler/resolver.js +225 -58
  30. package/lib/compiler/shared.js +53 -229
  31. package/lib/compiler/utils.js +184 -0
  32. package/lib/edm/annotations/genericTranslation.js +1 -1
  33. package/lib/edm/csn2edm.js +3 -2
  34. package/lib/edm/edmPreprocessor.js +34 -38
  35. package/lib/edm/edmUtils.js +3 -3
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +17 -1
  38. package/lib/gen/language.tokens +79 -73
  39. package/lib/gen/languageLexer.interp +19 -1
  40. package/lib/gen/languageLexer.js +779 -731
  41. package/lib/gen/languageLexer.tokens +71 -65
  42. package/lib/gen/languageParser.js +4668 -4072
  43. package/lib/json/from-csn.js +10 -10
  44. package/lib/json/to-csn.js +228 -47
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +26 -8
  47. package/lib/language/genericAntlrParser.js +73 -14
  48. package/lib/language/language.g4 +79 -3
  49. package/lib/main.d.ts +215 -18
  50. package/lib/main.js +3 -1
  51. package/lib/model/api.js +2 -2
  52. package/lib/model/csnRefs.js +117 -33
  53. package/lib/model/csnUtils.js +65 -133
  54. package/lib/model/enrichCsn.js +62 -37
  55. package/lib/model/revealInternalProperties.js +25 -8
  56. package/lib/model/sortViews.js +8 -1
  57. package/lib/modelCompare/compare.js +2 -1
  58. package/lib/optionProcessor.js +33 -18
  59. package/lib/render/.eslintrc.json +1 -2
  60. package/lib/render/DuplicateChecker.js +1 -1
  61. package/lib/render/toCdl.js +15 -8
  62. package/lib/render/toHdbcds.js +26 -49
  63. package/lib/render/toSql.js +61 -39
  64. package/lib/render/utils/common.js +1 -1
  65. package/lib/transform/db/applyTransformations.js +189 -0
  66. package/lib/transform/db/constraints.js +273 -119
  67. package/lib/transform/db/draft.js +3 -2
  68. package/lib/transform/db/expansion.js +6 -4
  69. package/lib/transform/db/flattening.js +19 -3
  70. package/lib/transform/db/transformExists.js +102 -9
  71. package/lib/transform/db/views.js +485 -0
  72. package/lib/transform/forHanaNew.js +93 -448
  73. package/lib/transform/forOdataNew.js +9 -2
  74. package/lib/transform/localized.js +2 -0
  75. package/lib/transform/odata/structuralPath.js +1 -5
  76. package/lib/transform/transformUtilsNew.js +22 -8
  77. package/lib/transform/translateAssocsToJoins.js +7 -15
  78. package/lib/utils/file.js +11 -5
  79. package/lib/utils/term.js +65 -42
  80. package/lib/utils/timetrace.js +48 -26
  81. package/package.json +1 -1
  82. package/lib/transform/db/helpers.js +0 -58
@@ -3,7 +3,7 @@
3
3
  const {
4
4
  getParentNameOf, getLastPartOf, getLastPartOfRef,
5
5
  hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
6
- getRootArtifactName, getResultingName, getNamespace, forEachMember,
6
+ getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement,
7
7
  } = require('../model/csnUtils');
8
8
  const keywords = require('../base/keywords');
9
9
  const {
@@ -17,7 +17,7 @@ const DuplicateChecker = require('./DuplicateChecker');
17
17
  const { isDeprecatedEnabled, forEachDefinition } = require('../base/model');
18
18
  const { checkCSNVersion } = require('../json/csnVersion');
19
19
  const { makeMessageFunction } = require('../base/messages');
20
- const timetrace = require('../utils/timetrace');
20
+ const { timetrace } = require('../utils/timetrace');
21
21
 
22
22
  const { smartId, delimitedId } = require('../sql-identifier');
23
23
 
@@ -558,7 +558,7 @@ function toHdbcdsSource(csn, options) {
558
558
  result += env.indent + (elm.key && !isSubElement ? 'key ' : '') +
559
559
  (elm.masked ? 'masked ' : '') +
560
560
  quotedElementName + (omitColon ? ' ' : ' : ') +
561
- renderTypeReference(elm, env, undefined) +
561
+ renderTypeReference(elm, env) +
562
562
  renderNullability(elm);
563
563
  if (elm.default)
564
564
  result += ` default ${renderExpr(elm.default, env)}`;
@@ -630,7 +630,7 @@ function toHdbcdsSource(csn, options) {
630
630
 
631
631
  // Even the first step might have parameters and/or a filter
632
632
  if (path.ref[0].args)
633
- result += `(${renderArgs(path.ref[0].args, ':', env)})`;
633
+ result += `(${renderArgs(path.ref[0], ':', env)})`;
634
634
 
635
635
  if (path.ref[0].where)
636
636
  result += `[${path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : ''}${renderExpr(path.ref[0].where, env, true, true)}]`;
@@ -680,7 +680,7 @@ function toHdbcdsSource(csn, options) {
680
680
  */
681
681
  function renderViewColumn(col, env, element) {
682
682
  // Annotations and column
683
- let result = element && hasHanaComment(element, options) ? `${env.indent}@Comment: '${getEscapedHanaComment(element)}'\n` : '';
683
+ let result = '';
684
684
 
685
685
  const leaf = col.as || col.ref && col.ref[col.ref.length - 1];
686
686
  // Render 'null as <alias>' only for database and if element is virtual
@@ -926,7 +926,7 @@ function toHdbcdsSource(csn, options) {
926
926
  */
927
927
  function renderParameter(parName, par, env) {
928
928
  if (par.notNull === true || par.notNull === false)
929
- info(null, env.path.concat([ 'params', parName ]), 'Not Null constraints on HDBCDS view parameters are not allowed and are ignored');
929
+ info('query-ignoring-param-nullability', env.path.concat([ 'params', parName ]), { '#': 'std' });
930
930
  return `${env.indent + formatParamIdentifier(parName, env.path.concat([ 'params', parName ]))} : ${renderTypeReference(par, env)}`;
931
931
  }
932
932
 
@@ -959,7 +959,7 @@ function toHdbcdsSource(csn, options) {
959
959
  }
960
960
  else {
961
961
  // Derived type or annotation with non-anonymous type
962
- result += ` : ${renderTypeReference(art, env, false)};\n`;
962
+ result += ` : ${renderTypeReference(art, env)};\n`;
963
963
  }
964
964
  return result;
965
965
  }
@@ -970,10 +970,9 @@ function toHdbcdsSource(csn, options) {
970
970
  *
971
971
  * @param {CSN.Element} elm Element using the type reference
972
972
  * @param {CdlRenderEnvironment} env Environment
973
- * @param {boolean} [noEnum=false] If true, do not render enums
974
973
  * @returns {string} Rendered type reference
975
974
  */
976
- function renderTypeReference(elm, env, noEnum = false) {
975
+ function renderTypeReference(elm, env) {
977
976
  let result = '';
978
977
 
979
978
  // Array type: Render items instead
@@ -1011,11 +1010,7 @@ function toHdbcdsSource(csn, options) {
1011
1010
  // Reference to another element
1012
1011
  if (elm.type.ref) {
1013
1012
  // For HANA CDS, we need a 'type of'
1014
- let result = `type of ${renderAbsolutePath(elm.type, env)}`;
1015
- if (elm.enum)
1016
- result += renderEnum(elm.enum, env);
1017
-
1018
- return result;
1013
+ return `type of ${renderAbsolutePath(elm.type, env)}`;
1019
1014
  }
1020
1015
 
1021
1016
  // If we get here, it must be a named type
@@ -1027,8 +1022,6 @@ function toHdbcdsSource(csn, options) {
1027
1022
  // Type names are never flattened (derived types are unraveled in HANA)
1028
1023
  result += renderAbsoluteNameWithQuotes(elm.type, env);
1029
1024
  }
1030
- if (elm.enum && !noEnum)
1031
- result += renderEnum(elm.enum, env);
1032
1025
 
1033
1026
  return result;
1034
1027
  }
@@ -1094,27 +1087,6 @@ function toHdbcdsSource(csn, options) {
1094
1087
  return elm.type.replace(/^cds\./, '') + renderTypeParameters(elm);
1095
1088
  }
1096
1089
 
1097
- /**
1098
- * Render the 'enum { ... } part of a type declaration
1099
- *
1100
- * @param {CSN.EnumElements} enumPart Enum part of a type declaration
1101
- * @param {CdlRenderEnvironment} env Environment
1102
- * @returns {string} Rendered enum
1103
- */
1104
- function renderEnum(enumPart, env) {
1105
- let result = ' enum {\n';
1106
- const childEnv = increaseIndent(env);
1107
- for (const name in enumPart) {
1108
- const enumConst = enumPart[name];
1109
- result += childEnv.indent + quoteId(name);
1110
- if (enumConst.val !== undefined)
1111
- result += ` = ${renderExpr(enumConst, childEnv)}`;
1112
- result += ';\n';
1113
- }
1114
- result += `${env.indent}}`;
1115
- return result;
1116
- }
1117
-
1118
1090
  /**
1119
1091
  * Render an expression (including paths and values) or condition 'x'.
1120
1092
  * (no trailing LF, don't indent if inline)
@@ -1229,7 +1201,12 @@ function toHdbcdsSource(csn, options) {
1229
1201
  */
1230
1202
  function renderExpressionRef(x) {
1231
1203
  if (!x.param && !x.global) {
1204
+ const magicReplacement = getVariableReplacement(x.ref, options);
1232
1205
  if (x.ref[0] === '$user') {
1206
+ if (magicReplacement !== null)
1207
+ return `'${magicReplacement}'`;
1208
+
1209
+ // Keep old way of solving this to remain backwards compatible
1233
1210
  // FIXME: this is all not enough: we might need an explicit select item alias
1234
1211
  if (x.ref[1] === 'id') {
1235
1212
  if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
@@ -1251,6 +1228,9 @@ function toHdbcdsSource(csn, options) {
1251
1228
  else if (x.ref[1] === 'to')
1252
1229
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1253
1230
  }
1231
+ else if (x.ref[0] === '$session' && magicReplacement !== null) {
1232
+ return `'${magicReplacement}'`;
1233
+ }
1254
1234
  }
1255
1235
  return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref)).join('.')}`;
1256
1236
  }
@@ -1262,7 +1242,7 @@ function toHdbcdsSource(csn, options) {
1262
1242
  * @returns {string} Rendered cast()
1263
1243
  */
1264
1244
  function renderExplicitTypeCast(value) {
1265
- let typeRef = renderTypeReference(x.cast, env, true);
1245
+ let typeRef = renderTypeReference(x.cast, env);
1266
1246
 
1267
1247
  // inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
1268
1248
  const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
@@ -1288,7 +1268,7 @@ function toHdbcdsSource(csn, options) {
1288
1268
  // (see FIXME at renderArtifact)
1289
1269
  if (idx === 0 && s === $SELF) {
1290
1270
  // do not produce USING for $projection
1291
- if (env.currentArtifactName === $PROJECTION && env._artifact && env._artifact.projection)
1271
+ if (env.currentArtifactName === $PROJECTION)
1292
1272
  return env.currentArtifactName;
1293
1273
 
1294
1274
  return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
@@ -1321,13 +1301,13 @@ function toHdbcdsSource(csn, options) {
1321
1301
 
1322
1302
  // Not really a path step but an object-like function call
1323
1303
  if (s.func)
1324
- return `${s.func}(${renderArgs(s.args, '=>', env)})`;
1304
+ return `${s.func}(${renderArgs(s, '=>', env)})`;
1325
1305
 
1326
1306
  // Path step, possibly with view parameters and/or filters
1327
1307
  let result = `${formatIdentifier(s.id)}`;
1328
1308
  if (s.args) {
1329
1309
  // View parameters
1330
- result += `(${renderArgs(s.args, ':', env)})`;
1310
+ result += `(${renderArgs(s, ':', env)})`;
1331
1311
  }
1332
1312
  if (s.where) {
1333
1313
  // Filter, possibly with cardinality
@@ -1344,19 +1324,21 @@ function toHdbcdsSource(csn, options) {
1344
1324
  * Render function arguments or view parameters (positional if array, named if object/dict),
1345
1325
  * using 'sep' as separator for positional parameters
1346
1326
  *
1347
- * @param {object[]|object} args (Un)Named arguments
1327
+ * @param {object} node with `args` to render
1348
1328
  * @param {string} sep Seperator between arguments
1349
1329
  * @param {CdlRenderEnvironment} env Environment
1350
1330
  * @returns {string} Rendered arguments
1351
1331
  */
1352
- function renderArgs(args, sep, env) {
1332
+ function renderArgs(node, sep, env) {
1333
+ const args = node.args ? node.args : {};
1353
1334
  // Positional arguments
1354
1335
  if (Array.isArray(args))
1355
1336
  return args.map(arg => renderExpr(arg, env)).join(', ');
1356
1337
 
1357
1338
  // Named arguments (object/dict)
1358
1339
  else if (typeof args === 'object')
1359
- return Object.keys(args).map(key => `${formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1340
+ // if this is a function param which is not a reference to the model, we must not quote it
1341
+ return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1360
1342
 
1361
1343
 
1362
1344
  throw new Error(`Unknown args: ${JSON.stringify(args)}`);
@@ -1673,11 +1655,6 @@ function toHdbcdsSource(csn, options) {
1673
1655
  if (id.indexOf('.') !== -1)
1674
1656
  throw new Error(id);
1675
1657
 
1676
- // FIXME: Somewhat arbitrary magic: Do not quote $projection (because HANA CDS doesn't recognize it otherwise). Similar for $self.
1677
- // FIXME: The test should not be on the name, but by checking the _artifact.
1678
- if (id === $PROJECTION || id === $SELF)
1679
- return id;
1680
-
1681
1658
 
1682
1659
  switch (options.forHana.names) {
1683
1660
  case 'plain':
@@ -4,7 +4,7 @@
4
4
  const {
5
5
  getLastPartOf, getLastPartOfRef,
6
6
  hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
7
- forEachDefinition, getResultingName,
7
+ forEachDefinition, getResultingName, getVariableReplacement,
8
8
  } = require('../model/csnUtils');
9
9
  const {
10
10
  renderFunc, beautifyExprArray, cdsToSqlTypes, getHanaComment, hasHanaComment,
@@ -15,10 +15,11 @@ const {
15
15
  const DuplicateChecker = require('./DuplicateChecker');
16
16
  const { checkCSNVersion } = require('../json/csnVersion');
17
17
  const { makeMessageFunction } = require('../base/messages');
18
- const timetrace = require('../utils/timetrace');
18
+ const { timetrace } = require('../utils/timetrace');
19
19
  const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
20
20
  const { smartFuncId } = require('../sql-identifier');
21
21
  const { sortCsn } = require('../json/to-csn');
22
+ const { manageConstraints } = require('./manageConstraints');
22
23
 
23
24
 
24
25
  /**
@@ -221,7 +222,6 @@ function toSqlDdl(csn, options) {
221
222
  for (const artifactName in csn.deletions)
222
223
  renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], resultObj);
223
224
 
224
-
225
225
  // Render each artifact extension
226
226
  // Only HANA SQL is currently supported.
227
227
  // Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
@@ -273,7 +273,6 @@ function toSqlDdl(csn, options) {
273
273
  // Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
274
274
  if (options.toSql.dialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
275
275
  sourceString = sourceString.slice('COLUMN '.length);
276
-
277
276
  sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
278
277
  }
279
278
  else if (!options.testMode) {
@@ -284,6 +283,15 @@ function toSqlDdl(csn, options) {
284
283
  delete resultObj[hdbKind];
285
284
  }
286
285
 
286
+ // add `ALTER TABLE ADD CONSTRAINT` statements if requested
287
+ if (options.sqlDialect !== 'sqlite' && options.constraintsAsAlter) {
288
+ const alterStmts = manageConstraints(csn, options);
289
+
290
+ for ( const constraintName of Object.keys(alterStmts))
291
+ sql[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
292
+ resultObj.sql = sql;
293
+ }
294
+
287
295
  if (options.toSql.src === 'sql')
288
296
  resultObj.sql = sql;
289
297
 
@@ -385,7 +393,7 @@ function toSqlDdl(csn, options) {
385
393
  function reducesTypeSize(def) {
386
394
  // HANA does not allow decreasing the value of any of those type parameters.
387
395
  return def.old.type === def.new.type &&
388
- [ 'length', 'precision', 'scale' ].some(param => def.new[param] < def.old[param]);
396
+ [ 'length', 'precision', 'scale' ].some(param => def.new[param] < def.old[param]);
389
397
  }
390
398
  function getEltStr(defVariant, eltName) {
391
399
  return defVariant.target
@@ -500,13 +508,9 @@ function toSqlDdl(csn, options) {
500
508
  result += `TABLE ${tableName}`;
501
509
  result += ' (\n';
502
510
  const elements = Object.keys(art.elements).map(eltName => renderElement(artifactName, eltName, art.elements[eltName], definitionsDuplicateChecker, getFzIndex(eltName, hanaTc), childEnv)).filter(s => s !== '').join(',\n');
503
- if (elements !== '') {
511
+ if (elements !== '')
504
512
  result += elements;
505
- }
506
- else {
507
- // TODO: Already be handled by 'empty-entity' reclassification; better location
508
- error(null, [ 'definitions', artifactName ], 'Entities must have at least one element that is non-virtual');
509
- }
513
+
510
514
  const uniqueFields = Object.keys(art.elements).filter(name => art.elements[name].unique && !art.elements[name].virtual)
511
515
  .map(name => quoteSqlId(name))
512
516
  .join(', ');
@@ -517,7 +521,8 @@ function toSqlDdl(csn, options) {
517
521
  if (primaryKeys !== '')
518
522
  result += `,\n${childEnv.indent}${primaryKeys}`;
519
523
 
520
- if (art.$tableConstraints && art.$tableConstraints.referential) {
524
+ const constraintsAsAlter = options.constraintsAsAlter && options.sqlDialect !== 'sqlite';
525
+ if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
521
526
  const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
522
527
  const referentialConstraints = {};
523
528
  Object.entries(art.$tableConstraints.referential)
@@ -525,12 +530,12 @@ function toSqlDdl(csn, options) {
525
530
  referentialConstraints[fileName] = renderReferentialConstraint(referentialConstraint, childEnv.indent, false, csn, options);
526
531
  });
527
532
  if (renderReferentialConstraintsAsHdbconstraint) {
528
- Object.entries(referentialConstraints).forEach( ([ fileName, constraint ]) => {
533
+ Object.entries(referentialConstraints).forEach(([ fileName, constraint ]) => {
529
534
  resultObj.hdbconstraint[fileName] = constraint;
530
535
  });
531
536
  }
532
537
  else {
533
- Object.values(referentialConstraints).forEach( (constraint) => {
538
+ Object.values(referentialConstraints).forEach((constraint) => {
534
539
  result += `,\n${constraint}`;
535
540
  });
536
541
  }
@@ -546,8 +551,7 @@ function toSqlDdl(csn, options) {
546
551
  = `UNIQUE INVERTED INDEX ${renderArtifactName(`${artifactName}_${cn}`)} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
547
552
  }
548
553
  else {
549
- result += `,\n${childEnv.indent}CONSTRAINT ${renderArtifactName(`${artifactName}_${cn}`)} UNIQUE (${
550
- c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
554
+ result += `,\n${childEnv.indent}CONSTRAINT ${renderArtifactName(`${artifactName}_${cn}`)} UNIQUE (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
551
555
  }
552
556
  }
553
557
  result += `${env.indent}\n)`;
@@ -568,7 +572,7 @@ function toSqlDdl(csn, options) {
568
572
  if (options.toSql.dialect === 'hana')
569
573
  renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
570
574
 
571
- if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
575
+ if (options.toSql.dialect === 'hana' && hasHanaComment(art, options))
572
576
  result += ` COMMENT '${getHanaComment(art)}'`;
573
577
 
574
578
  resultObj.hdbtable[artifactName] = result;
@@ -659,8 +663,7 @@ function toSqlDdl(csn, options) {
659
663
  if (duplicateChecker)
660
664
  duplicateChecker.addElement(quotedElementName, elm.$location, elementName);
661
665
 
662
- let result = `${env.indent + quotedElementName} ${
663
- renderTypeReference(artifactName, elementName, elm)
666
+ let result = `${env.indent + quotedElementName} ${renderTypeReference(artifactName, elementName, elm)
664
667
  }${renderNullability(elm, true)}`;
665
668
  if (elm.default)
666
669
  result += ` DEFAULT ${renderExpr(elm.default, env)}`;
@@ -956,7 +959,7 @@ function toSqlDdl(csn, options) {
956
959
  // An empty actual parameter list is rendered as `()`.
957
960
  const ref = csn.definitions[path.ref[0].id] || csn.definitions[path.ref[0]];
958
961
  if (ref && ref.params) {
959
- result += `(${renderArgs(path.ref[0].args || {}, '=>', env, syntax)})`;
962
+ result += `(${renderArgs(path.ref[0] || {}, '=>', env, syntax)})`;
960
963
  }
961
964
  else if ([ 'udf' ].includes(syntax)) {
962
965
  // if syntax is user defined function, render empty argument list
@@ -978,21 +981,23 @@ function toSqlDdl(csn, options) {
978
981
  * Render function arguments or view parameters (positional if array, named if object/dict),
979
982
  * using 'sep' as separator for positional parameters
980
983
  *
981
- * @param {Array|object} args Arguments to render
984
+ * @param {object} node with `args` to render
982
985
  * @param {string} sep Separator between args
983
986
  * @param {object} env Render environment
984
987
  * @param {string} syntax Some magic A2J paramter - for calcview parameter rendering
985
988
  * @returns {string} Rendered arguments
986
989
  * @throws Throws if args is not an array or object.
987
990
  */
988
- function renderArgs(args, sep, env, syntax) {
991
+ function renderArgs(node, sep, env, syntax) {
992
+ const args = node.args ? node.args : {};
989
993
  // Positional arguments
990
994
  if (Array.isArray(args))
991
995
  return args.map(arg => renderExpr(arg, env)).join(', ');
992
996
 
993
997
  // Named arguments (object/dict)
994
998
  else if (typeof args === 'object')
995
- return Object.keys(args).map(key => `${decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
999
+ // if this is a function param which is not a reference to the model, we must not quote it
1000
+ return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
996
1001
 
997
1002
 
998
1003
  throw new Error(`Unknown args: ${JSON.stringify(args)}`);
@@ -1054,7 +1059,7 @@ function toSqlDdl(csn, options) {
1054
1059
  definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
1055
1060
  let result = `VIEW ${viewName}`;
1056
1061
 
1057
- if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
1062
+ if (options.toSql.dialect === 'hana' && hasHanaComment(art, options))
1058
1063
  result += ` COMMENT '${getHanaComment(art)}'`;
1059
1064
 
1060
1065
  result += renderParameterDefinitions(artifactName, art.params);
@@ -1087,7 +1092,7 @@ function toSqlDdl(csn, options) {
1087
1092
  for (const pn in params) {
1088
1093
  const p = params[pn];
1089
1094
  if (p.notNull === true || p.notNull === false)
1090
- info(null, [ 'definitions', artifactName, 'params', pn ], 'Not Null constraints on SQL view parameters are not allowed and are ignored');
1095
+ info('query-ignoring-param-nullability', [ 'definitions', artifactName, 'params', pn ], { '#': 'sql' });
1091
1096
  // do not quote parameter identifiers for naming mode "quoted" / "hdbcds"
1092
1097
  // this would be an incompatible change, as non-uppercased, quoted identifiers
1093
1098
  // are rejected by the HANA compiler.
@@ -1151,12 +1156,11 @@ function toSqlDdl(csn, options) {
1151
1156
  const childEnv = increaseIndent(env);
1152
1157
  result += `SELECT${select.distinct ? ' DISTINCT' : ''}`;
1153
1158
  // FIXME: We probably also need to consider `excluding` here ?
1154
- result += `\n${
1155
- (select.columns || [ '*' ])
1156
- .filter(col => !(select.mixin || Object.create(null))[firstPathStepId(col.ref)]) // No mixin columns
1157
- .map(col => renderViewColumn(col, childEnv))
1158
- .filter(s => s !== '')
1159
- .join(',\n')}\n`;
1159
+ result += `\n${(select.columns || [ '*' ])
1160
+ .filter(col => !(select.mixin || Object.create(null))[firstPathStepId(col.ref)]) // No mixin columns
1161
+ .map(col => renderViewColumn(col, childEnv))
1162
+ .filter(s => s !== '')
1163
+ .join(',\n')}\n`;
1160
1164
  result += `${env.indent}FROM ${renderViewSource(artifactName, select.from, env)}`;
1161
1165
  if (select.where)
1162
1166
  result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
@@ -1384,6 +1388,8 @@ function toSqlDdl(csn, options) {
1384
1388
  // Function call, possibly with args (use '=>' for named args)
1385
1389
  else if (x.func) {
1386
1390
  const funcName = smartFuncId(prepareIdentifier(x.func), options.toSql.dialect);
1391
+ if (x.xpr)
1392
+ return renderWindowFunction(funcName, x, env);
1387
1393
  return renderFunc(funcName, x, options.toSql.dialect, a => renderArgs(a, '=>', env, null));
1388
1394
  }
1389
1395
  // Nested expression
@@ -1406,29 +1412,36 @@ function toSqlDdl(csn, options) {
1406
1412
  throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
1407
1413
  }
1408
1414
 
1415
+ function renderWindowFunction(funcName, node, env) {
1416
+ const suffix = node.xpr.shift(); // OVER
1417
+ let r = `${funcName}(${renderArgs(node, '=>', env, null)})`;
1418
+ r += ` ${suffix} (${renderExpr(node.xpr, env)})`;
1419
+ return r;
1420
+ }
1421
+
1409
1422
  function renderExpressionLiteral(x) {
1410
1423
  // Literal value, possibly with explicit 'literal' property
1411
1424
  switch (x.literal || typeof x.val) {
1412
1425
  case 'number':
1413
1426
  case 'boolean':
1414
1427
  case 'null':
1415
- // 17.42, NULL, TRUE
1428
+ // 17.42, NULL, TRUE
1416
1429
  return String(x.val).toUpperCase();
1417
1430
  case 'x':
1418
- // x'f000'
1431
+ // x'f000'
1419
1432
  return `${x.literal}'${x.val}'`;
1420
1433
  case 'date':
1421
1434
  case 'time':
1422
1435
  case 'timestamp':
1423
1436
  if (options.toSql.dialect === 'sqlite') {
1424
- // simple string literal '2017-11-02'
1437
+ // simple string literal '2017-11-02'
1425
1438
  return `'${x.val}'`;
1426
1439
  }
1427
1440
  // date'2017-11-02'
1428
1441
  return `${x.literal}'${x.val}'`;
1429
1442
 
1430
1443
  case 'string':
1431
- // 'foo', with proper escaping
1444
+ // 'foo', with proper escaping
1432
1445
  return `'${x.val.replace(/'/g, '\'\'')}'`;
1433
1446
  case 'object':
1434
1447
  if (x.val === null)
@@ -1442,7 +1455,12 @@ function toSqlDdl(csn, options) {
1442
1455
 
1443
1456
  function renderExpressionRef(x) {
1444
1457
  if (!x.param && !x.global) {
1458
+ const magicReplacement = getVariableReplacement(x.ref, options);
1459
+
1445
1460
  if (x.ref[0] === '$user') {
1461
+ if (magicReplacement !== null)
1462
+ return `'${magicReplacement}'`;
1463
+
1446
1464
  const result = render$user(x);
1447
1465
  // Invalid second path step doesn't cause a return
1448
1466
  if (result)
@@ -1454,6 +1472,9 @@ function toSqlDdl(csn, options) {
1454
1472
  if (result)
1455
1473
  return result;
1456
1474
  }
1475
+ else if (x.ref[0] === '$session' && magicReplacement !== null) {
1476
+ return `'${magicReplacement}'`;
1477
+ }
1457
1478
  }
1458
1479
  // FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
1459
1480
  // assume that it was not if the path has length 2 (
@@ -1476,6 +1497,7 @@ function toSqlDdl(csn, options) {
1476
1497
  function render$user(x) {
1477
1498
  // FIXME: this is all not enough: we might need an explicit select item alias
1478
1499
  if (x.ref[1] === 'id') {
1500
+ // Keep the old-style for compatibilty with magicVars.id - instead of magicVars.user.id...
1479
1501
  if (options.toSql.user && typeof options.toSql.user === 'string' || options.toSql.user instanceof String)
1480
1502
  return `'${options.toSql.user}'`;
1481
1503
 
@@ -1483,7 +1505,7 @@ function toSqlDdl(csn, options) {
1483
1505
  return `'${options.toSql.user.id}'`;
1484
1506
 
1485
1507
  if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
1486
- warning(null, null, 'The "$user" variable is not supported. Use the "toSql.user" option to set a value for "$user.id"');
1508
+ warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1487
1509
  return '\'$user.id\'';
1488
1510
  }
1489
1511
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
@@ -1495,7 +1517,7 @@ function toSqlDdl(csn, options) {
1495
1517
  }
1496
1518
  return 'SESSION_CONTEXT(\'LOCALE\')';
1497
1519
  }
1498
- // Basically: Second path step was invalid, do nothing
1520
+ // Basically: Second path step was invalid, do nothing - should not happen, see 'unknownMagic.js'
1499
1521
  return null;
1500
1522
  }
1501
1523
  /**
@@ -1593,13 +1615,13 @@ function toSqlDdl(csn, options) {
1593
1615
 
1594
1616
  // Not really a path step but an object-like function call
1595
1617
  if (s.func)
1596
- return `${s.func}(${renderArgs(s.args, '=>', env, null)})`;
1618
+ return `${s.func}(${renderArgs(s, '=>', env, null)})`;
1597
1619
 
1598
1620
  // Path step, possibly with view parameters and/or filters
1599
1621
  let result = `${quoteSqlId(s.id)}`;
1600
1622
  if (s.args) {
1601
1623
  // View parameters
1602
- result += `(${renderArgs(s.args, '=>', env, null)})`;
1624
+ result += `(${renderArgs(s, '=>', env, null)})`;
1603
1625
  }
1604
1626
  if (s.where) {
1605
1627
  // Filter, possibly with cardinality
@@ -33,7 +33,7 @@ const { implicitAs } = require('../../model/csnRefs');
33
33
  function renderFunc( funcName, node, dialect, renderArgs) {
34
34
  if (funcWithoutParen( node, dialect ))
35
35
  return funcName;
36
- return `${funcName}(${renderArgs( node.args )})`;
36
+ return `${funcName}(${renderArgs( node )})`;
37
37
  }
38
38
 
39
39
  /**