@sap/cds-compiler 4.9.6 → 5.1.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 (95) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +49 -19
  4. package/bin/cdshi.js +3 -1
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +16 -19
  7. package/lib/api/options.js +5 -14
  8. package/lib/api/trace.js +0 -1
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/location.js +4 -1
  11. package/lib/base/message-registry.js +43 -29
  12. package/lib/base/messages.js +23 -26
  13. package/lib/base/meta.js +10 -0
  14. package/lib/base/model.js +0 -2
  15. package/lib/base/node-helpers.js +0 -1
  16. package/lib/base/optionProcessorHelper.js +11 -0
  17. package/lib/checks/dbFeatureFlags.js +5 -0
  18. package/lib/checks/enricher.js +1 -5
  19. package/lib/checks/structuredAnnoExpressions.js +30 -0
  20. package/lib/checks/validator.js +8 -0
  21. package/lib/compiler/assert-consistency.js +5 -1
  22. package/lib/compiler/base.js +1 -1
  23. package/lib/compiler/builtins.js +18 -2
  24. package/lib/compiler/checks.js +2 -5
  25. package/lib/compiler/define.js +8 -8
  26. package/lib/compiler/extend.js +108 -37
  27. package/lib/compiler/generate.js +1 -1
  28. package/lib/compiler/index.js +27 -10
  29. package/lib/compiler/lsp-api.js +501 -2
  30. package/lib/compiler/populate.js +60 -13
  31. package/lib/compiler/propagator.js +10 -8
  32. package/lib/compiler/resolve.js +117 -94
  33. package/lib/compiler/shared.js +114 -32
  34. package/lib/compiler/tweak-assocs.js +31 -21
  35. package/lib/compiler/utils.js +2 -1
  36. package/lib/compiler/xsn-model.js +4 -0
  37. package/lib/edm/annotations/genericTranslation.js +69 -35
  38. package/lib/edm/csn2edm.js +16 -4
  39. package/lib/edm/edm.js +10 -3
  40. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  41. package/lib/edm/edmPreprocessor.js +8 -10
  42. package/lib/gen/Dictionary.json +66 -2
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +2 -1
  45. package/lib/gen/languageParser.js +4995 -4817
  46. package/lib/json/csnVersion.js +1 -1
  47. package/lib/json/from-csn.js +4 -7
  48. package/lib/json/to-csn.js +25 -12
  49. package/lib/language/antlrParser.js +2 -2
  50. package/lib/language/errorStrategy.js +0 -1
  51. package/lib/language/genericAntlrParser.js +35 -12
  52. package/lib/language/multiLineStringParser.js +3 -2
  53. package/lib/language/textUtils.js +1 -0
  54. package/lib/main.d.ts +28 -9
  55. package/lib/main.js +9 -9
  56. package/lib/model/cloneCsn.js +1 -0
  57. package/lib/model/csnRefs.js +22 -5
  58. package/lib/model/csnUtils.js +0 -14
  59. package/lib/model/revealInternalProperties.js +1 -2
  60. package/lib/modelCompare/compare.js +13 -11
  61. package/lib/optionProcessor.js +30 -9
  62. package/lib/render/manageConstraints.js +1 -1
  63. package/lib/render/toCdl.js +44 -14
  64. package/lib/render/toHdbcds.js +1 -2
  65. package/lib/render/toSql.js +45 -8
  66. package/lib/render/utils/common.js +12 -9
  67. package/lib/render/utils/stringEscapes.js +1 -0
  68. package/lib/transform/db/applyTransformations.js +13 -8
  69. package/lib/transform/db/associations.js +62 -54
  70. package/lib/transform/db/backlinks.js +20 -5
  71. package/lib/transform/db/expansion.js +1 -6
  72. package/lib/transform/db/flattening.js +86 -109
  73. package/lib/transform/db/killAnnotations.js +3 -0
  74. package/lib/transform/db/processSqlServices.js +63 -0
  75. package/lib/transform/db/temporal.js +3 -4
  76. package/lib/transform/db/views.js +0 -1
  77. package/lib/transform/draft/odata.js +56 -3
  78. package/lib/transform/effective/annotations.js +3 -2
  79. package/lib/transform/effective/flattening.js +135 -0
  80. package/lib/transform/effective/main.js +6 -4
  81. package/lib/transform/effective/types.js +13 -9
  82. package/lib/transform/forOdata.js +0 -2
  83. package/lib/transform/forRelationalDB.js +9 -19
  84. package/lib/transform/localized.js +7 -8
  85. package/lib/transform/odata/flattening.js +39 -31
  86. package/lib/transform/odata/typesExposure.js +5 -17
  87. package/lib/transform/transformUtils.js +1 -1
  88. package/lib/transform/translateAssocsToJoins.js +0 -1
  89. package/lib/utils/file.js +87 -8
  90. package/lib/utils/moduleResolve.js +59 -8
  91. package/lib/utils/term.js +3 -2
  92. package/package.json +7 -3
  93. package/share/messages/message-explanations.json +2 -0
  94. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  95. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -1,3 +1,11 @@
1
+ // Compiler options
2
+
3
+ // Remarks:
4
+ // - The specification is client-tool centric (bin/cdsc.js):
5
+ // an option named `fooBar` is “produced” by `.option(' --foo-bar')`.
6
+ // - Also list the option in the `help` text, used with `cdsc -h`.
7
+ // - Specify valid values for non-boolean options in lib/api/validate.js.
8
+
1
9
  'use strict';
2
10
 
3
11
  const { createOptionProcessor } = require('./base/optionProcessorHelper');
@@ -15,6 +23,7 @@ optionProcessor
15
23
  .option(' --options <file>')
16
24
  .option('-w, --warning <level>', { valid: ['0', '1', '2', '3'] })
17
25
  .option(' --quiet')
26
+ .option('-i, --stdin')
18
27
  .option(' --show-message-id')
19
28
  .option(' --no-message-id')
20
29
  .option(' --no-message-context')
@@ -34,7 +43,7 @@ optionProcessor
34
43
  .option(' --beta <list>')
35
44
  .option(' --deprecated <list>')
36
45
  .option(' --direct-backend')
37
- .option(' --fallback-parser <type>', { valid: ['cdl', 'csn', 'csn!'] })
46
+ .option(' --fallback-parser <type>', { valid: [ 'auto!', 'cdl', 'csn', 'csn!' ] })
38
47
  .option(' --shuffle <seed>') // 0 | 1..4294967296
39
48
  .option(' --test-mode')
40
49
  .option(' --test-sort-csn')
@@ -81,6 +90,7 @@ optionProcessor
81
90
  --cds-home <dir> When set, modules starting with '@sap/cds/' are searched in <dir>
82
91
  --module-lookup-directories <list> Comma separated list of directories to look
83
92
  for CDS modules. Default is 'node_modules/'.
93
+ -i, --stdin Read input from stdin.
84
94
  -- Indicate the end of options (helpful if source names start with "-")
85
95
 
86
96
  Type options
@@ -117,9 +127,10 @@ optionProcessor
117
127
  eagerPersistenceForGeneratedEntities
118
128
  --fallback-parser <type> If the language cannot be deduced by the file's extensions, use this
119
129
  parser as a fallback. Valid values are:
120
- cdl : Use CDL parser
121
- csn : Use CSN parser
122
- csn! : Use CSN parser even with extension cds, cdl, hdbcds and hdbdd
130
+ cdl : Use CDL parser
131
+ csn : Use CSN parser
132
+ csn! : Use CSN parser even with extension cds, cdl, hdbcds and hdbdd
133
+ auto! : Ignore file extension; use CSN parser if file content starts with '{'
123
134
  --direct-backend Do not compile the given CSN but directly pass it to the backend.
124
135
  Can only be used with certain new CSN based backends. Combination with
125
136
  other flags is limited, e.g. --test-mode will not run a consistency check.
@@ -161,6 +172,8 @@ optionProcessor
161
172
  Environment variables
162
173
  NO_COLOR If set, compiler messages (/output) will not be colored.
163
174
  Can be overwritten by '--color'
175
+ FORCE_COLOR If set, compiler messages (/output) will be colored. Overrides NO_COLOR.
176
+ Can be overwritten by '--color'
164
177
  CDSC_TRACE_TIME If set, additional timing information is printed to stderr.
165
178
  CDSC_TRACE_API If set, additional API calling information is printed to stderr.
166
179
  `);
@@ -318,9 +331,9 @@ optionProcessor.command('Q, toSql')
318
331
  .option(' --pre2134ReferentialConstraintNames')
319
332
  .option(' --disable-hana-comments')
320
333
  .option(' --generated-by-comment')
321
- .option(' --better-sqlite-session-variables')
334
+ .option(' --better-sqlite-session-variables <bool>')
322
335
  .option(' --fewer-localized-views')
323
- .option(' --without-hana-associations')
336
+ .option(' --with-hana-associations <bool>', { valid: [ 'true', 'false' ] })
324
337
  .help(`
325
338
  Usage: cdsc toSql [options] <files...>
326
339
 
@@ -372,11 +385,17 @@ optionProcessor.command('Q, toSql')
372
385
  --pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
373
386
  --disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
374
387
  --generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
375
- --better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only
376
- active if sqlDialect is \`sqlite\`
388
+ --better-sqlite-session-variables <bool>
389
+ Enable better-sqlite compatible rendering of $user. Only
390
+ active if sqlDialect is \`sqlite\`:
391
+ true : (default) Render better-sqlite session_context(…)
392
+ false : Render session variables as string literals, used e.g. with sqlite3 driver
377
393
  --fewer-localized-views If set, the backends will not create localized convenience views for
378
394
  those views, that only have an association to a localized entity/view.
379
- --without-hana-associations If set, the backend will not render a "WITH ASSOCIATIONS" for sqlDialect 'hana'
395
+ --with-hana-associations <bool>
396
+ Enable and disable rendering of "WITH ASSOCIATIONS" for sqlDialect 'hana'.
397
+ true : (default) Render "WITH ASSOCIATIONS"
398
+ false : Do not render "WITH ASSOCIATIONS"
380
399
  `);
381
400
 
382
401
  optionProcessor.command('toRename')
@@ -482,6 +501,7 @@ optionProcessor.command('toCsn')
482
501
  optionProcessor.command('parseCdl')
483
502
  .option('-h, --help')
484
503
  .positionalArgument('<file>')
504
+ .option(' --with-locations')
485
505
  .help(`
486
506
  Usage: cdsc parseCdl [options] <file>
487
507
 
@@ -489,6 +509,7 @@ optionProcessor.command('parseCdl')
489
509
  resolve imports, apply extensions or expand any queries.
490
510
 
491
511
  Options
512
+ --with-locations Add $location to CSN artifacts.
492
513
  -h, --help Show this help text
493
514
  `);
494
515
 
@@ -58,7 +58,7 @@ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
58
58
  // TODO: Remove / Move to api/options.js once alterConstraintsWithCsn is available outside bin/cdsc
59
59
  function _transformSqlOptions( csn, options ) {
60
60
  const { src } = options;
61
- // eslint-disable-next-line global-require
61
+
62
62
  const prepareOptions = require('../api/options');
63
63
  options = prepareOptions.to.sql(options);
64
64
  options.src = src;
@@ -13,11 +13,11 @@ const { typeParameters, specialFunctions } = require('../compiler/builtins');
13
13
  const { isAnnotationExpression } = require('../base/builtins');
14
14
  const { forEach } = require('../utils/objectUtils');
15
15
  const {
16
- generatedByCompilerVersion,
17
16
  getNormalizedQuery,
18
17
  } = require('../model/csnUtils');
19
18
  const { isBuiltinType } = require('../base/builtins');
20
19
  const { cloneFullCsn } = require('../model/cloneCsn');
20
+ const { getKeysDict } = require('../model/csnRefs');
21
21
 
22
22
  const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
23
23
  const specialFunctionKeywords = Object.create(null);
@@ -62,7 +62,7 @@ function csnToCdl( csn, options, msg ) {
62
62
  const hanaRequiresAbsolutePath = usings.available.includes('hana');
63
63
 
64
64
  const cdlResult = Object.create(null);
65
- cdlResult.model = options.testMode ? '' : `// ${generatedByCompilerVersion()} \n`;
65
+ cdlResult.model = '';
66
66
 
67
67
  const subelementAnnotates = [];
68
68
 
@@ -676,7 +676,7 @@ function csnToCdl( csn, options, msg ) {
676
676
  artifact = artifact.items;
677
677
  }
678
678
 
679
- if (!artifact.elements && !artifact.enum)
679
+ if (!artifact.elements && !artifact.enum && !artifact.keys)
680
680
  return null;
681
681
 
682
682
  const annotate = { annotate: env.path[1] };
@@ -712,15 +712,16 @@ function csnToCdl( csn, options, msg ) {
712
712
  */
713
713
  function collectAnnos( annotateObj, art ) {
714
714
  if (!Object.hasOwnProperty.call(art, 'elements') &&
715
- !Object.hasOwnProperty.call(art, 'enum'))
715
+ !Object.hasOwnProperty.call(art, 'enum') &&
716
+ !Object.hasOwnProperty.call(art, 'keys'))
716
717
  return false;
717
718
 
718
- const dictKey = art.elements ? 'elements' : 'enum';
719
- // Use "elements" for both enums and elements. This is allowed in extensions.
719
+ const dict = art.enum || art.keys && getKeysDict(art) || art.elements;
720
+ // Use "elements" for all. This is allowed in extensions.
720
721
  const collected = { elements: Object.create(null) };
721
722
  let hasAnnotation = false;
722
723
 
723
- forEach(art[dictKey], (elemName, element) => {
724
+ forEach(dict, (elemName, element) => {
724
725
  if (!collected.elements[elemName])
725
726
  collected.elements[elemName] = { };
726
727
 
@@ -1379,7 +1380,7 @@ function csnToCdl( csn, options, msg ) {
1379
1380
 
1380
1381
  // Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
1381
1382
  if (artifact.keys && !artifact.on)
1382
- result += ` { ${Object.keys(artifact.keys).map(name => renderForeignKey(artifact.keys[name], env.withSubPath([ 'keys', name ]))).join(', ')} }`;
1383
+ result += ` ${ renderForeignKeys(artifact, env) }`;
1383
1384
 
1384
1385
  if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
1385
1386
  result += renderNullability(artifact);
@@ -1435,7 +1436,7 @@ function csnToCdl( csn, options, msg ) {
1435
1436
  if (art.on)
1436
1437
  result += ` on ${exprRenderer.renderExpr(art.on, env.withSubPath([ 'on' ]))}`;
1437
1438
  else if (art.keys)
1438
- result += ` { ${Object.keys(art.keys).map(name => renderForeignKey(art.keys[name], env.withSubPath([ 'keys', name ]))).join(', ')} }`;
1439
+ result += ` ${ renderForeignKeys(art, env) }`;
1439
1440
  return result;
1440
1441
  }
1441
1442
 
@@ -1813,15 +1814,43 @@ function csnToCdl( csn, options, msg ) {
1813
1814
  }
1814
1815
 
1815
1816
  /**
1816
- * Render a foreign key (no trailing LF)
1817
+ * Render foreign keys.
1817
1818
  *
1818
- * @param {object} fKey
1819
+ * @param {object} art
1819
1820
  * @param {CdlRenderEnvironment} env
1820
1821
  * @return {string}
1821
1822
  */
1822
- function renderForeignKey( fKey, env ) {
1823
- const alias = fKey.as ? renderAlias(fKey.as, env) : '';
1824
- return exprRenderer.renderExpr(fKey, env) + alias;
1823
+ function renderForeignKeys( art, env ) {
1824
+ const renderedKeys = [];
1825
+ let hasAnnotations = false;
1826
+ env = env.withSubPath([ 'keys', -1 ]);
1827
+ env.increaseIndent();
1828
+
1829
+ for (let i = 0; i < art.keys.length; ++i) {
1830
+ env.path[env.path.length - 1] = i;
1831
+ const fKey = art.keys[i];
1832
+
1833
+ const annos = renderAnnotationAssignmentsAndDocComment(fKey, env).trim();
1834
+ if (annos) {
1835
+ hasAnnotations = true;
1836
+ renderedKeys.push(annos);
1837
+ }
1838
+
1839
+ const alias = fKey.as ? renderAlias(fKey.as, env) : '';
1840
+ const key = exprRenderer.renderExpr(fKey, env);
1841
+ renderedKeys.push(`${key}${alias},`);
1842
+ }
1843
+
1844
+ if (hasAnnotations) {
1845
+ const sep = `\n${env.indent}`;
1846
+ env.decreaseIndent();
1847
+ return `{${sep}${renderedKeys.join(sep)}\n${env.indent}}`;
1848
+ }
1849
+
1850
+ let result = renderedKeys.join(' ');
1851
+ if (result[result.length - 1] === ',') // remove trailing comma
1852
+ result = result.slice(0, -1);
1853
+ return `{ ${ result } }`;
1825
1854
  }
1826
1855
 
1827
1856
  /**
@@ -2427,6 +2456,7 @@ function isSimpleString( str ) {
2427
2456
  // <https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf>
2428
2457
  // On top, because (invalid) surrogate pairs need to be handled, we check for them as well.
2429
2458
  // v3: Not a simple string if ' (\u0027) is in string.
2459
+ // eslint-disable-next-line no-control-regex
2430
2460
  return str === '' || (/^[^\u{0000}-\u{001F}\u2028\u2029]+$/u.test(str) &&
2431
2461
  !hasUnpairedUnicodeSurrogate(str));
2432
2462
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const {
4
4
  getLastPartOf, getLastPartOfRef,
5
- hasValidSkipOrExists, generatedByCompilerVersion, getNormalizedQuery,
5
+ hasValidSkipOrExists, getNormalizedQuery,
6
6
  getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement, hasAnnotationValue,
7
7
  pathName,
8
8
  } = require('../model/csnUtils');
@@ -167,7 +167,6 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
167
167
  if (sourceStr !== '') {
168
168
  const name = plainNames ? artifactName.replace(/\./g, '_').toUpperCase() : artifactName;
169
169
  hdbcds[name] = [
170
- !options.testMode ? `// ${generatedByCompilerVersion()} \n` : '',
171
170
  renderNamespaceDeclaration(name, env),
172
171
  renderUsings(name, env),
173
172
  sourceStr,
@@ -3,7 +3,7 @@
3
3
 
4
4
  const {
5
5
  getLastPartOf, getLastPartOfRef,
6
- hasValidSkipOrExists, generatedByCompilerVersion, getNormalizedQuery,
6
+ hasValidSkipOrExists, getNormalizedQuery,
7
7
  forEachDefinition, getResultingName,
8
8
  getVariableReplacement, pathName,
9
9
  } = require('../model/csnUtils');
@@ -186,8 +186,11 @@ function toSqlDdl( csn, options, messageFunctions ) {
186
186
  deletions: Object.create(null),
187
187
  constraintDeletions: [],
188
188
  migrations: Object.create(null),
189
+ hdbrole: Object.create(null),
189
190
  };
190
191
 
192
+ const sqlServiceEntities = Object.create(null);
193
+
191
194
  // Registries for artifact and element names per CSN section
192
195
  const definitionsDuplicateChecker = new DuplicateChecker(options.sqlMapping);
193
196
  const deletionsDuplicateChecker = new DuplicateChecker();
@@ -252,6 +255,25 @@ function toSqlDdl( csn, options, messageFunctions ) {
252
255
  });
253
256
  }
254
257
 
258
+ // Can only happen for HDI based deployment
259
+ // .hdbrole documentation: https://help.sap.com/docs/SAP_HANA_PLATFORM/3823b0f33420468ba5f1cf7f59bd6bd9/625d7733c30b4666b4a522d7fa68a550.html
260
+ Object.keys(sqlServiceEntities).forEach((sqlServiceName) => {
261
+ const accessRole = {
262
+ role: {
263
+ name: renderArtifactNameWithoutQuotes(`${sqlServiceName }.access`),
264
+ object_privileges: Object.entries(sqlServiceEntities[sqlServiceName]).map(([ name, entity ]) => ({
265
+ name: renderArtifactNameWithoutQuotes(name),
266
+ type: entity.query || entity.projection ? 'VIEW' : 'TABLE',
267
+ privileges: [ 'SELECT' ],
268
+ privileges_with_grant_option: [],
269
+ })),
270
+ },
271
+ };
272
+
273
+ if (accessRole.role.object_privileges.length > 0)
274
+ mainResultObj.hdbrole[`${sqlServiceName }_access`] = JSON.stringify(accessRole, null, 2);
275
+ });
276
+
255
277
  // trigger artifact and element name checks
256
278
  definitionsDuplicateChecker.check(error, options);
257
279
  extensionsDuplicateChecker.check(error);
@@ -264,7 +286,6 @@ function toSqlDdl( csn, options, messageFunctions ) {
264
286
  // (relying on the order of dictionaries above)
265
287
  // FIXME: Should consider inter-view dependencies, too
266
288
  const sql = Object.create(null);
267
- const sqlVersionLine = `-- ${generatedByCompilerVersion()}\n`;
268
289
 
269
290
  // Handle hdbKinds separately from alterTable case
270
291
  const {
@@ -278,10 +299,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
278
299
  // Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
279
300
  if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
280
301
  sourceString = sourceString.slice('COLUMN '.length);
281
- sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
282
- }
283
- else if (!options.testMode && options.generatedByComment) {
284
- mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
302
+ sql[name] = `CREATE ${sourceString};`;
285
303
  }
286
304
  }
287
305
  if (options.src === 'sql')
@@ -295,7 +313,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
295
313
 
296
314
  forEachKey(alterStmts, (constraintName) => {
297
315
  if (!csn.unchangedConstraints?.has(constraintName))
298
- constraints[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
316
+ constraints[constraintName] = `${alterStmts[constraintName]}`;
299
317
  });
300
318
  mainResultObj.constraints = constraints;
301
319
  }
@@ -304,7 +322,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
304
322
  mainResultObj.sql = sql;
305
323
 
306
324
  for (const name in deletions)
307
- deletions[name] = `${options.testMode || !options.generatedByComment ? '' : sqlVersionLine}${deletions[name]}`;
325
+ deletions[name] = `${deletions[name]}`;
308
326
 
309
327
  timetrace.stop('SQL rendering');
310
328
  return mainResultObj;
@@ -325,6 +343,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
325
343
 
326
344
  switch (art.kind) {
327
345
  case 'entity':
346
+ if (art.$sqlService) { // collect entities that are in a sql service so we can render the .hdbrole later
347
+ sqlServiceEntities[art.$sqlService] ??= Object.create(null);
348
+ sqlServiceEntities[art.$sqlService][artifactName] = art;
349
+ }
328
350
  if (art.query || art.projection) {
329
351
  const result = renderView(artifactName, art, env);
330
352
  if (result)
@@ -350,6 +372,20 @@ function toSqlDdl( csn, options, messageFunctions ) {
350
372
  }
351
373
  }
352
374
 
375
+ /**
376
+ * Render the given artifactName according to the sqlMapping, but
377
+ * - uppercased for plain
378
+ * - without enclosing " for quoted/hdbcds
379
+ *
380
+ * @param {string} artifactName
381
+ * @returns {string}
382
+ */
383
+ function renderArtifactNameWithoutQuotes( artifactName ) {
384
+ if (options.sqlMapping === 'plain')
385
+ return renderArtifactName(artifactName).toUpperCase();
386
+ return renderArtifactName(artifactName).slice(1, -1); // trim leading/trailing "
387
+ }
388
+
353
389
  /**
354
390
  * Render an artifact extension into the appropriate dictionary of 'resultObj'.
355
391
  * Only SAP HANA SQL is currently supported.
@@ -1645,6 +1681,7 @@ function renderStringForSql( str, sqlDialect ) {
1645
1681
  // > Single quotation marks are used to delimit string literals.
1646
1682
  // > A single quotation mark itself can be represented using two single quotation marks.
1647
1683
  str = str.replace(/'/g, '\'\'')
1684
+ // eslint-disable-next-line no-control-regex
1648
1685
  .replace(/\u{0}/ug, '\' || CHAR(0) || \'');
1649
1686
  }
1650
1687
  else {
@@ -372,7 +372,7 @@ const variablesToSql = {
372
372
  '$valid.to': "current_setting('cap.valid_to')::timestamp",
373
373
  $now: 'current_timestamp',
374
374
  },
375
- 'better-sqlite': {
375
+ sqlite: {
376
376
  '$user.id': "session_context( '$user.id' )",
377
377
  '$user.locale': "session_context( '$user.locale' )",
378
378
  $tenant: "session_context( '$tenant' )",
@@ -382,7 +382,7 @@ const variablesToSql = {
382
382
  '$valid.to': "session_context( '$valid.to' )",
383
383
  $now: 'CURRENT_TIMESTAMP',
384
384
  },
385
- sqlite: {
385
+ 'old-sqlite': {
386
386
  // For sqlite, we render the string-format-time (strftime) function.
387
387
  // Because the format of `current_timestamp` is like that: '2021-05-14 09:17:19' whereas
388
388
  // the format for timestamps (at least in Node.js) is like that: '2021-01-01T00:00:00.000Z'
@@ -394,11 +394,14 @@ const variablesToSql = {
394
394
  '$valid.to': "strftime('%Y-%m-%dT%H:%M:%S.001Z', 'now')",
395
395
  $now: 'CURRENT_TIMESTAMP',
396
396
  },
397
- plain: {
398
- '$at.from': 'current_timestamp',
399
- '$at.to': 'current_timestamp',
400
- '$valid.from': 'current_timestamp',
401
- '$valid.to': 'current_timestamp',
397
+ plain: { // better-sqlite defaults
398
+ '$user.id': "session_context( '$user.id' )",
399
+ '$user.locale': "session_context( '$user.locale' )",
400
+ $tenant: "session_context( '$tenant' )",
401
+ '$at.from': "session_context( '$valid.from' )",
402
+ '$at.to': "session_context( '$valid.to' )",
403
+ '$valid.from': "session_context( '$valid.from' )",
404
+ '$valid.to': "session_context( '$valid.to' )",
402
405
  $now: 'CURRENT_TIMESTAMP',
403
406
  },
404
407
  h2: {
@@ -423,8 +426,8 @@ const variablesToSql = {
423
426
  * @return {string|null} `null` if the variable could not be found for the given dialect and in the fallback values.
424
427
  */
425
428
  function variableForDialect( options, variable ) {
426
- const dialect = options.sqlDialect === 'sqlite' && options.betterSqliteSessionVariables
427
- ? 'better-sqlite'
429
+ const dialect = options.sqlDialect === 'sqlite' && options.betterSqliteSessionVariables === false
430
+ ? 'old-sqlite'
428
431
  : options.sqlDialect;
429
432
  return variablesToSql[dialect]?.[variable] || variablesToSql.fallback[variable] || null;
430
433
  }
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ // eslint-disable-next-line no-control-regex
3
4
  const controlCharacters = /[\u{0000}-\u{001F}]/u;
4
5
  const highSurrogate = /[\u{D800}-\u{DBFF}]/u;
5
6
  const lowSurrogate = /[\u{DC00}-\u{DFFF}]/u;
@@ -45,6 +45,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
45
45
  };
46
46
 
47
47
  const csnPath = [ ...path ];
48
+ const context = {};
48
49
  if (prop === 'definitions') {
49
50
  definitions( parent, 'definitions', parent.definitions );
50
51
  }
@@ -84,9 +85,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
84
85
  for (const name of Object.getOwnPropertyNames( node )) {
85
86
  const trans = transformers[name] || transformers[name.charAt(0)] || standard;
86
87
  if (customTransformers[name])
87
- customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
88
+ customTransformers[name](node, name, node[name], csnPath, _parent, _prop, context);
88
89
  else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
89
- customTransformers['@'](node, name, node[name], csnPath, _parent, _prop);
90
+ customTransformers['@'](node, name, node[name], csnPath, _parent, _prop, context);
90
91
  trans( node, name, node[name], csnPath );
91
92
  }
92
93
  }
@@ -110,9 +111,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
110
111
  for (const name of Object.getOwnPropertyNames( node )) {
111
112
  const trans = transformers[name] || transformers[name.charAt(0)] || standard;
112
113
  if (customTransformers[name])
113
- customTransformers[name](node, name, node[name], csnPath, dict);
114
+ customTransformers[name](node, name, node[name], csnPath, dict, null, context);
114
115
  else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
115
- customTransformers['@'](node, name, node[name], csnPath, dict);
116
+ customTransformers['@'](node, name, node[name], csnPath, dict, null, context);
116
117
  trans( node, name, node[name], csnPath );
117
118
  }
118
119
  csnPath.pop();
@@ -130,12 +131,14 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
130
131
  if (options.skipDict?.[_prop] || dict == null) // with universal CSN, dictionaries might be null
131
132
  return;
132
133
  csnPath.push( _prop );
134
+ context[`$in_${_prop}`] = true;
133
135
  for (const name of Object.getOwnPropertyNames( dict ))
134
136
  dictEntry( dict, name, dict[name] );
135
137
 
136
138
  if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
137
139
  setProp(node, `$${_prop}`, dict);
138
140
  csnPath.pop();
141
+ context[`$in_${_prop}`] = undefined;
139
142
  }
140
143
 
141
144
  /**
@@ -148,6 +151,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
148
151
  */
149
152
  function annotation( _parent, _prop, node ) {
150
153
  if (options.processAnnotations) {
154
+ context.$annotation = { name: _prop, value: node };
151
155
  if (isAnnotationExpression(node)) {
152
156
  standard(_parent, _prop, node);
153
157
  }
@@ -164,6 +168,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
164
168
 
165
169
  csnPath.pop();
166
170
  }
171
+ context.$annotation = undefined;
167
172
  }
168
173
  }
169
174
 
@@ -359,18 +364,18 @@ function mergeTransformers( transformers, that ) {
359
364
  remapped[n] = [];
360
365
 
361
366
  if (Array.isArray(fns)) {
362
- remapped[n].push((parent, name, prop, path, parentParent) => fns.forEach(
363
- fn => fn.bind(that)(parent, name, prop, path, parentParent)
367
+ remapped[n].push((parent, name, prop, path, parentParent, opt, context) => fns.forEach(
368
+ fn => fn.bind(that)(parent, name, prop, path, parentParent, opt, context)
364
369
  ));
365
370
  }
366
371
  else {
367
- remapped[n].push((parent, name, prop, path, parentParent) => fns.bind(that)(parent, name, prop, path, parentParent));
372
+ remapped[n].push((parent, name, prop, path, parentParent, opt, context) => fns.bind(that)(parent, name, prop, path, parentParent, opt, context));
368
373
  }
369
374
  }
370
375
  }
371
376
 
372
377
  for (const [ n, fns ] of Object.entries(remapped))
373
- remapped[n] = (parent, name, prop, path, parentParent) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent));
378
+ remapped[n] = (parent, name, prop, path, parentParent, opt, context) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent, opt, context));
374
379
 
375
380
 
376
381
  return remapped;
@@ -132,44 +132,8 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
132
132
  * @param {string} artifactName Name of the artifact
133
133
  */
134
134
  function handleManagedAssocSteps( artifact, artifactName ) {
135
- const transformer = {
136
- ref: (refOwner, prop, ref, path) => {
137
- // [<assoc base>.]<managed assoc>.<field>
138
- if (ref.length > 1) {
139
- const { links } = inspectRef(path);
140
- if (links) {
141
- let fkAlias = '';
142
- // eslint-disable-next-line for-direction
143
- for (let i = links.length - 1; i >= 0; i--) {
144
- const link = links[i];
145
- // We found the latest managed assoc path step
146
- if (link.art && link.art.target && link.art.keys &&
147
- // Doesn't work when ref-target (filter condition) or similar is used
148
- !ref.slice(i).some(refElement => typeof refElement !== 'string')) {
149
- const fkRef = ref[i + 1];
150
- const fkName = (!fkAlias ? fkRef : `${fkRef}${pathDelimiter}${fkAlias}`);
151
- const fks = link.art.keys.filter(key => key.ref[0] === fkName);
152
-
153
- if (fks.length >= 1) { // after flattening, at most one FK will remain.
154
- // `.as` is set for SQL, but not for OData -> fall back to implicit alias
155
- fkAlias = fks[0].as || fks[0].ref[fks[0].ref.length - 1];
156
- const source = findSource(links, i - 1) || artifact;
157
- const managedAssocStepName = ref[i];
158
- const newFkName = `${managedAssocStepName}${pathDelimiter}${fkAlias}`;
159
- if (source?.elements[newFkName])
160
- refOwner.ref = [ ...ref.slice(0, i), newFkName ];
161
- }
162
- }
163
- else {
164
- fkAlias = '';
165
- // Ignore last path step and unmanaged associations.
166
- // Structures should have been already flattened.
167
- }
168
- }
169
- }
170
- }
171
- },
172
- };
135
+ const transformer = getTransformer();
136
+ const inColumnsTransformer = getTransformer(true);
173
137
  for (const elemName in artifact.elements) {
174
138
  const elem = artifact.elements[elemName];
175
139
  // The association is an unmanaged one
@@ -187,28 +151,72 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
187
151
  where: transform,
188
152
  having: transform,
189
153
  };
190
- if (processOnInQueries)
154
+ if (processOnInQueries) {
155
+ queryTransformers.columns = (parent, prop, columns, path) => {
156
+ for (let i = 0; i < columns.length; i++) {
157
+ const column = columns[i];
158
+ if (column.ref) {
159
+ inColumnsTransformer.ref(column, 'ref', column.ref, path.concat([ 'columns', i ]));
160
+ column.ref.forEach((step, index) => {
161
+ if (step.where)
162
+ transform(step, 'where', step.where, path.concat([ 'columns', i, 'ref', index ]));
163
+ });
164
+ }
165
+ }
166
+ };
191
167
  queryTransformers.on = transform;
192
- applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', queryTransformers, {}, [ 'definitions', artifactName ]);
168
+ }
169
+ applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', queryTransformers, { drillRef: true }, [ 'definitions', artifactName ]);
193
170
  }
194
171
 
195
-
196
172
  /**
197
- * Find out where the managed association is
198
173
  *
199
- * @param {Array} links
200
- * @param {number} startIndex
201
- * @returns {object | undefined} CSN definition of the source of the managed association
174
+ * @param {boolean} isColumns Whether the transformation is taking place on a column
175
+ * @returns {object}
202
176
  */
203
- function findSource( links, startIndex ) {
204
- for (let i = startIndex; i >= 0; i--) {
205
- const link = links[i];
206
- // We found the latest assoc step - now check where that points to
207
- if (link.art && link.art.target)
208
- return csn.definitions[link.art.target];
209
- }
210
-
211
- return undefined;
177
+ function getTransformer( isColumns = false ) {
178
+ return {
179
+ ref: (refOwner, prop, ref, path) => {
180
+ // [<assoc base>.]<managed assoc>.<field>
181
+ if (ref.length > 1) {
182
+ const { links } = inspectRef(path);
183
+ if (links) {
184
+ let fkAlias = '';
185
+ for (let i = links.length - 1; i >= 0; i--) {
186
+ const link = links[i];
187
+ // We found the latest managed assoc path step
188
+ if (link.art && link.art.target && link.art.keys &&
189
+ // Doesn't work when ref-target (filter condition) or similar is used
190
+ !ref.slice(i).some(refElement => typeof refElement !== 'string')) {
191
+ const fkRef = ref[i + 1];
192
+ const fkName = (!fkAlias ? fkRef : `${fkRef}${pathDelimiter}${fkAlias}`);
193
+ const fks = link.art.keys.filter(key => key.ref[0] === fkName);
194
+
195
+ if (fks.length >= 1) { // after flattening, at most one FK will remain.
196
+ // `.as` is set for SQL, but not for OData -> fall back to implicit alias
197
+ fkAlias = fks[0].as || fks[0].ref[fks[0].ref.length - 1];
198
+ const managedAssocStepName = ref[i];
199
+ const newFkName = `${managedAssocStepName}${pathDelimiter}${fkAlias}`;
200
+ if (isColumns) {
201
+ refOwner.ref = [ ...ref.slice(0, i), newFkName ];
202
+ if (!refOwner.as)
203
+ refOwner.as = implicitAs(ref);
204
+ }
205
+ else {
206
+ refOwner.ref = [ ...ref.slice(0, i), newFkName ];
207
+ }
208
+ }
209
+ }
210
+ else {
211
+ fkAlias = '';
212
+ // Ignore last path step and unmanaged associations.
213
+ // Structures should have been already flattened.
214
+ }
215
+ }
216
+ }
217
+ }
218
+ },
219
+ };
212
220
  }
213
221
  }
214
222
  }