@sap/cds-compiler 6.0.14 → 6.2.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 (61) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/cdsc.js +6 -2
  3. package/bin/cdsse.js +1 -1
  4. package/bin/cdsv2m.js +1 -1
  5. package/lib/api/main.js +29 -7
  6. package/lib/api/options.js +2 -0
  7. package/lib/base/builtins.js +9 -0
  8. package/lib/base/keywords.js +1 -1
  9. package/lib/base/message-registry.js +5 -3
  10. package/lib/base/messages.js +3 -3
  11. package/lib/base/model.js +1 -0
  12. package/lib/base/node-helpers.js +10 -2
  13. package/lib/base/optionProcessorHelper.js +7 -2
  14. package/lib/checks/assocOutsideService.js +3 -1
  15. package/lib/checks/featureFlags.js +4 -1
  16. package/lib/compiler/assert-consistency.js +3 -1
  17. package/lib/compiler/base.js +1 -1
  18. package/lib/compiler/builtins.js +1 -1
  19. package/lib/compiler/checks.js +38 -21
  20. package/lib/compiler/define.js +24 -5
  21. package/lib/compiler/extend.js +1 -1
  22. package/lib/compiler/finalize-parse-cdl.js +9 -1
  23. package/lib/compiler/generate.js +4 -4
  24. package/lib/compiler/index.js +10 -1
  25. package/lib/compiler/lsp-api.js +2 -0
  26. package/lib/compiler/populate.js +8 -8
  27. package/lib/compiler/propagator.js +1 -1
  28. package/lib/compiler/resolve.js +15 -14
  29. package/lib/compiler/shared.js +6 -7
  30. package/lib/compiler/tweak-assocs.js +6 -6
  31. package/lib/compiler/utils.js +9 -16
  32. package/lib/compiler/xpr-rewrite.js +2 -2
  33. package/lib/gen/BaseParser.js +43 -37
  34. package/lib/gen/CdlGrammar.checksum +1 -1
  35. package/lib/gen/CdlParser.js +1424 -1433
  36. package/lib/gen/Dictionary.json +1 -7
  37. package/lib/gen/cdlKeywords.json +26 -0
  38. package/lib/inspect/inspectPropagation.js +1 -1
  39. package/lib/json/from-csn.js +2 -2
  40. package/lib/json/to-csn.js +9 -5
  41. package/lib/language/multiLineStringParser.js +1 -1
  42. package/lib/main.d.ts +10 -2
  43. package/lib/model/cloneCsn.js +1 -0
  44. package/lib/optionProcessor.js +13 -7
  45. package/lib/parsers/AstBuildingParser.js +24 -21
  46. package/lib/parsers/identifiers.js +2 -30
  47. package/lib/render/toCdl.js +63 -9
  48. package/lib/render/toSql.js +127 -108
  49. package/lib/render/utils/sql.js +67 -0
  50. package/lib/transform/addTenantFields.js +4 -4
  51. package/lib/transform/db/killAnnotations.js +1 -0
  52. package/lib/transform/db/processSqlServices.js +20 -2
  53. package/lib/transform/draft/db.js +1 -1
  54. package/lib/transform/draft/odata.js +14 -4
  55. package/lib/transform/forOdata.js +91 -2
  56. package/lib/transform/forRelationalDB.js +1 -1
  57. package/lib/transform/odata/flattening.js +1 -1
  58. package/lib/transform/transformUtils.js +2 -2
  59. package/lib/transform/translateAssocsToJoins.js +2 -26
  60. package/lib/utils/moduleResolve.js +1 -1
  61. package/package.json +2 -2
@@ -18,13 +18,13 @@ const {
18
18
  getDeltaRenderer,
19
19
  } = require('./utils/delta');
20
20
  const {
21
- renderReferentialConstraint, getIdentifierUtils,
21
+ renderReferentialConstraint, getIdentifierUtils, isProjectionView,
22
22
  } = require('./utils/sql');
23
23
  const DuplicateChecker = require('./DuplicateChecker');
24
24
  const { checkCSNVersion } = require('../json/csnVersion');
25
25
  const { timetrace } = require('../utils/timetrace');
26
26
  const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
27
- const { smartFuncId } = require('../sql-identifier');
27
+ const sqlIdentifiers = require('../sql-identifier');
28
28
  const { sortCsn } = require('../model/cloneCsn');
29
29
  const { manageConstraints, manageConstraint } = require('./manageConstraints');
30
30
  const { renderUniqueConstraintString, renderUniqueConstraintDrop, renderUniqueConstraintAdd } = require('./utils/unique');
@@ -109,8 +109,6 @@ class SqlRenderEnvironment {
109
109
  * @returns {object} Dictionary of artifact-type:artifacts, where artifacts is a dictionary of name:content
110
110
  */
111
111
  function toSqlDdl( csn, options, messageFunctions ) {
112
- const withHanaAssociations = options.withHanaAssociations && options.sqlDialect === 'hana';
113
-
114
112
  timetrace.start('SQL rendering');
115
113
  const {
116
114
  error, warning, info, throwWithAnyError,
@@ -137,10 +135,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
137
135
  return renderExpressionRef(x, this.env);
138
136
  },
139
137
  windowFunction( x) {
140
- return renderWindowFunction(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, this.env);
138
+ return renderWindowFunction(smartFuncId(prepareIdentifier(x.func)), x, this.env);
141
139
  },
142
140
  func(x) {
143
- return renderFunc(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, a => renderArgs(a, '=>', this.env, null), { messageFunctions, options, path: this.env.path });
141
+ return renderFunc(smartFuncId(prepareIdentifier(x.func)), x, a => renderArgs(a, '=>', this.env, null), { messageFunctions, options, path: this.env.path });
144
142
  },
145
143
  xpr(x) {
146
144
  const env = this.env.withSubPath([ 'xpr' ]);
@@ -193,6 +191,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
193
191
  hdbindex: Object.create(null),
194
192
  hdbfulltextindex: Object.create(null),
195
193
  hdbview: Object.create(null),
194
+ hdbprojectionview: Object.create(null),
196
195
  hdbconstraint: Object.create(null),
197
196
  deletions: Object.create(null),
198
197
  constraintDeletions: [],
@@ -212,7 +211,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
212
211
  const changeElementsDuplicateChecker = new DuplicateChecker();
213
212
 
214
213
  // Render each artifact on its own
215
- forEachDefinition((options && options.testMode) ? sortCsn(csn, options) : csn, (artifact, artifactName) => {
214
+ forEachDefinition(sortCsnIfTestMode(csn), (artifact, artifactName) => {
216
215
  renderDefinitionInto(artifactName, artifact, mainResultObj, new SqlRenderEnvironment());
217
216
  });
218
217
 
@@ -220,10 +219,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
220
219
  for (const artifactName in csn.deletions)
221
220
  renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], mainResultObj);
222
221
 
223
- const supportsSqlExtensions = (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'));
224
-
225
- if (csn.changedPrimaryKeys && supportsSqlExtensions) {
226
- csn.changedPrimaryKeys = options.testMode ? sortCsn(csn.changedPrimaryKeys) : csn.changedPrimaryKeys;
222
+ if (csn.changedPrimaryKeys && supportsSqlExtensions()) {
223
+ csn.changedPrimaryKeys = sortCsnIfTestMode(csn.changedPrimaryKeys);
227
224
  csn.changedPrimaryKeys.forEach((artifactName) => {
228
225
  const drop = render.dropKey(artifactName);
229
226
  addMigration(mainResultObj, artifactName, true, render.concat(...drop));
@@ -233,8 +230,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
233
230
  // Render each artifact extension
234
231
  // Only SAP HANA SQL is currently supported.
235
232
  // Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
236
- if (csn.extensions && supportsSqlExtensions) {
237
- csn.extensions = options.testMode ? sortCsn(csn.extensions) : csn.extensions;
233
+ if (csn.extensions && supportsSqlExtensions()) {
234
+ csn.extensions = sortCsnIfTestMode(csn.extensions);
238
235
  for (let i = 0; i < csn.extensions.length; ++i) {
239
236
  const extension = csn.extensions[i];
240
237
  if (extension.extend) {
@@ -248,8 +245,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
248
245
 
249
246
  // Render each artifact change
250
247
  // Only SAP HANA SQL is currently supported.
251
- if (csn.migrations && supportsSqlExtensions) {
252
- csn.migrations = options.testMode ? sortCsn(csn.migrations) : csn.migrations;
248
+ if (csn.migrations && supportsSqlExtensions()) {
249
+ csn.migrations = sortCsnIfTestMode(csn.migrations);
253
250
  for (const migration of csn.migrations) {
254
251
  if (migration.migrate) {
255
252
  const artifactName = migration.migrate;
@@ -261,8 +258,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
261
258
  }
262
259
  }
263
260
 
264
- if (csn.changedPrimaryKeys && supportsSqlExtensions) {
265
- csn.changedPrimaryKeys = options.testMode ? sortCsn(csn.changedPrimaryKeys) : csn.changedPrimaryKeys;
261
+ if (csn.changedPrimaryKeys && supportsSqlExtensions()) {
262
+ csn.changedPrimaryKeys = sortCsnIfTestMode(csn.changedPrimaryKeys);
266
263
  csn.changedPrimaryKeys.forEach((artifactName) => {
267
264
  const add = render.addKey(artifactName, csn.definitions[artifactName].elements);
268
265
  addMigration(mainResultObj, artifactName, true, render.concat(...add));
@@ -318,26 +315,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
318
315
  const sql = Object.create(null);
319
316
 
320
317
  // Handle hdbKinds separately from alterTable case
321
- const {
322
- // eslint-disable-next-line no-unused-vars
323
- deletions, constraintDeletions, migrations: _, ...hdbKinds
324
- } = mainResultObj;
325
- for (const hdbKind of Object.keys(hdbKinds)) {
326
- for (const name in mainResultObj[hdbKind]) {
327
- if (options.src === 'sql') {
328
- let sourceString = mainResultObj[hdbKind][name];
329
- // Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
330
- if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
331
- sourceString = sourceString.slice('COLUMN '.length);
332
- sql[name] = `CREATE ${ sourceString };`;
333
- }
334
- }
335
- if (options.src === 'sql')
336
- delete mainResultObj[hdbKind];
337
- }
318
+ if (options.src === 'sql')
319
+ adaptHdbKindsForSql();
338
320
 
339
- // add `ALTER TABLE ADD CONSTRAINT` statements per default for `to.sql` w/ dialect `hana` / `postgres`
340
- if (!options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres' /* || options.sqlDialect === 'sqlite' */)) {
321
+ if (useAlterTableForConstraints()) {
341
322
  const constraints = Object.create(null);
342
323
  const alterStmts = manageConstraints(csn, options);
343
324
 
@@ -351,12 +332,31 @@ function toSqlDdl( csn, options, messageFunctions ) {
351
332
  if (options.src === 'sql')
352
333
  mainResultObj.sql = sql;
353
334
 
354
- for (const name in deletions)
355
- deletions[name] = `${ deletions[name] }`;
356
-
357
335
  timetrace.stop('SQL rendering');
358
336
  return mainResultObj;
359
337
 
338
+ function adaptHdbKindsForSql() {
339
+ const {
340
+ hdbtable,
341
+ // eslint-disable-next-line no-unused-vars
342
+ deletions, constraintDeletions, migrations: _, ...hdbKinds
343
+ } = mainResultObj;
344
+
345
+ for (const name in hdbtable) {
346
+ let sourceString = hdbtable[name];
347
+ // Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
348
+ if (sourceString.startsWith('COLUMN '))
349
+ sourceString = sourceString.slice('COLUMN '.length);
350
+ sql[name] = `CREATE ${ sourceString };`;
351
+ }
352
+
353
+ for (const hdbKind of Object.keys(hdbKinds)) {
354
+ for (const name in mainResultObj[hdbKind])
355
+ sql[name] = `CREATE ${ mainResultObj[hdbKind][name] };`;
356
+ delete mainResultObj[hdbKind];
357
+ }
358
+ }
359
+
360
360
  /**
361
361
  * Render a definition into the appropriate dictionary of 'resultObj'.
362
362
  *
@@ -373,7 +373,6 @@ function toSqlDdl( csn, options, messageFunctions ) {
373
373
  dummySqlServiceEntities[art.$dummyService] ??= Object.create(null);
374
374
  dummySqlServiceEntities[art.$dummyService][artifactName] = art;
375
375
  }
376
-
377
376
  return;
378
377
  }
379
378
 
@@ -384,14 +383,12 @@ function toSqlDdl( csn, options, messageFunctions ) {
384
383
  sqlServiceEntities[art.$sqlService][artifactName] = art;
385
384
  }
386
385
 
387
- if (art.query || art.projection) {
388
- const result = renderView(artifactName, art, env);
389
- if (result)
390
- resultObj.hdbview[artifactName] = result;
391
- }
392
- else {
386
+ if (art.query || art.projection)
387
+ renderViewInto(artifactName, art, resultObj, env);
388
+
389
+ else
393
390
  renderEntityInto(artifactName, art, resultObj, env);
394
- }
391
+
395
392
  break;
396
393
  case 'type':
397
394
  case 'context':
@@ -451,8 +448,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
451
448
  function renderArtifactDeletionInto( artifactName, art, resultObj ) {
452
449
  const tableName = renderArtifactName(artifactName);
453
450
  deletionsDuplicateChecker.addArtifact(tableName, art.$location, artifactName);
454
-
455
- addDeletion(resultObj, artifactName, `-- [WARNING] this statement is lossy\nDROP TABLE ${ tableName }`);
451
+ resultObj.deletions[artifactName] = `-- [WARNING] this statement is lossy\nDROP TABLE ${ tableName }`;
456
452
  }
457
453
 
458
454
  // Render an artifact migration into the appropriate dictionary of 'resultObj'.
@@ -534,13 +530,11 @@ function toSqlDdl( csn, options, messageFunctions ) {
534
530
  if (migration.removeConstraints[constraintType]) {
535
531
  const entries = Object.entries(migration.removeConstraints[constraintType]);
536
532
  const optionsWithDrop = { ...options, drop: true };
537
- let renderer;
538
- if (constraintType === 'referential')
539
- renderer = constraint => manageConstraint(constraint, csn, optionsWithDrop, '', quoteSqlId);
540
- else
541
- renderer = (constraint, constraintName) => renderUniqueConstraintDrop(constraint, renderArtifactName(`${ artifactName }_${ constraintName }`), tableName, quoteSqlId);
533
+ const renderer = (constraintType === 'referential')
534
+ ? constraint => manageConstraint(constraint, csn, optionsWithDrop, '', quoteSqlId)
535
+ : (constraint, constraintName) => renderUniqueConstraintDrop(constraint, renderArtifactName(`${ artifactName }_${ constraintName }`), tableName, quoteSqlId);
542
536
  entries.forEach(( [ constraintName, constraint ]) => {
543
- addConstraintDeletion(resultObj, constraint.parentTable, renderer(constraint, constraintName));
537
+ resultObj.constraintDeletions.push(renderer(constraint, constraintName));
544
538
  });
545
539
  }
546
540
  });
@@ -655,9 +649,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
655
649
  if (primaryKeys !== '')
656
650
  result += `,\n${ childEnv.indent }${ primaryKeys }`;
657
651
 
658
- // for `to.sql` w/ dialect `hana` the constraints will be part of the alter statement
659
- const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres');
660
- if (!constraintsAsAlter && art.$tableConstraints?.referential) {
652
+ if (!useAlterTableForConstraints() && art.$tableConstraints?.referential) {
661
653
  const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
662
654
  const referentialConstraints = {};
663
655
  forEach(art.$tableConstraints.referential, ( fileName, referentialConstraint ) => {
@@ -691,7 +683,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
691
683
  result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
692
684
 
693
685
 
694
- if (withHanaAssociations) {
686
+ if (supportsHanaAssociations()) {
695
687
  const associations = Object.keys(art.elements)
696
688
  .map(name => renderAssociationElement(name, art.elements[name], childEnv))
697
689
  .filter(s => s !== '')
@@ -706,11 +698,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
706
698
  if (options.sqlDialect === 'hana')
707
699
  renderIndexesInto(art.technicalConfig?.hana?.indexes, artifactName, resultObj, env);
708
700
 
709
- if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
710
- result += ` COMMENT ${ renderStringForSql(getHanaComment(art), options.sqlDialect) }`;
711
-
712
- if (back)
713
- result += back;
701
+ result += renderComment(art);
702
+ result += back;
714
703
 
715
704
  resultObj.hdbtable[artifactName] = result;
716
705
  }
@@ -753,24 +742,17 @@ function toSqlDdl( csn, options, messageFunctions ) {
753
742
  }
754
743
 
755
744
  function addMigration( resultObj, artifactName, drop, sqlArray, description ) {
756
- if (!(artifactName in resultObj.migrations))
757
- resultObj.migrations[artifactName] = [];
745
+ resultObj.migrations[artifactName] ??= [];
758
746
 
759
- if (!sqlArray) {
760
- if (description)
761
- resultObj.migrations[artifactName].push({ description });
762
- return;
747
+ if (sqlArray) {
748
+ const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
749
+ resultObj.migrations[artifactName].push(...migrations);
750
+ }
751
+ else if (description) {
752
+ resultObj.migrations[artifactName].push({ description });
763
753
  }
764
- const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
765
- resultObj.migrations[artifactName].push(...migrations);
766
754
  }
767
755
 
768
- function addConstraintDeletion( resultObj, artifactName, deletionSql ) {
769
- resultObj.constraintDeletions.push(deletionSql);
770
- }
771
- function addDeletion( resultObj, artifactName, deletionSql ) {
772
- resultObj.deletions[artifactName] = deletionSql;
773
- }
774
756
 
775
757
  /**
776
758
  * Retrieve the 'fzindex' (fuzzy index) property (if any) for element 'elemName' from hanaTc (if defined)
@@ -834,8 +816,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
834
816
  const { back } = getSqlSnippets(options, elm);
835
817
  result += back; // Needs to be rendered before the COMMENT
836
818
 
837
- if (options.sqlDialect === 'hana' && hasHanaComment(elm, options))
838
- result += ` COMMENT ${ renderStringForSql(getHanaComment(elm), options.sqlDialect) }`;
819
+ result += renderComment(elm);
839
820
 
840
821
  return result;
841
822
  }
@@ -892,11 +873,9 @@ function toSqlDdl( csn, options, messageFunctions ) {
892
873
  */
893
874
  function renderTechnicalConfiguration( tc, env ) {
894
875
  let result = '';
895
-
896
876
  if (!tc)
897
877
  return result;
898
878
 
899
-
900
879
  // FIXME: How to deal with non-HANA technical configurations?
901
880
  // This also affects renderIndexes
902
881
  tc = tc.hana;
@@ -1213,17 +1192,15 @@ function toSqlDdl( csn, options, messageFunctions ) {
1213
1192
  *
1214
1193
  * @param {string} artifactName Name of the view
1215
1194
  * @param {CSN.Artifact} art CSN view
1195
+ * @param {object} resultObj
1216
1196
  * @param {SqlRenderEnvironment} env Render environment
1217
1197
  * @returns {string} Rendered view
1218
1198
  */
1219
- function renderView( artifactName, art, env ) {
1199
+ function renderViewInto( artifactName, art, resultObj, env ) {
1220
1200
  const viewName = renderArtifactName(artifactName);
1221
1201
  definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art?.$location, artifactName);
1222
- let result = `VIEW ${ viewName }`;
1223
-
1224
- if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
1225
- result += ` COMMENT ${ renderStringForSql(getHanaComment(art), options.sqlDialect) }`;
1226
-
1202
+ let result = `${ isProjectionView(csn, art, options) ? 'PROJECTION ' : '' }VIEW ${ viewName }`;
1203
+ result += renderComment(art);
1227
1204
  result += renderParameterDefinitions(art.params, env);
1228
1205
  result += ` AS ${ renderQuery(getNormalizedQuery(art).query,
1229
1206
  env.withSubPath([ art.projection ? 'projection' : 'query' ]),
@@ -1235,17 +1212,19 @@ function toSqlDdl( csn, options, messageFunctions ) {
1235
1212
  .map(name => renderAssociationElement(name, art.elements[name], childEnv))
1236
1213
  .filter(s => s !== '')
1237
1214
  .join(',\n');
1238
- if (associations !== '' && withHanaAssociations) {
1215
+ if (associations !== '' && supportsHanaAssociations() && !isProjectionView(csn, art, options)) {
1239
1216
  result += `${ env.indent }\nWITH ASSOCIATIONS (\n${ associations }\n`;
1240
1217
  result += `${ env.indent })`;
1241
1218
  }
1242
1219
 
1243
1220
  // views can only have a @sql.append
1244
1221
  const { back } = getSqlSnippets(options, art);
1245
- if (back)
1246
- result += back;
1222
+ result += back;
1247
1223
 
1248
- return result;
1224
+ if (result && isProjectionView(csn, art, options))
1225
+ resultObj.hdbprojectionview[artifactName] = result;
1226
+ else if (result)
1227
+ resultObj.hdbview[artifactName] = result;
1249
1228
  }
1250
1229
 
1251
1230
  /**
@@ -1517,16 +1496,19 @@ function toSqlDdl( csn, options, messageFunctions ) {
1517
1496
  if (elm.scale !== undefined)
1518
1497
  params.push(elm.scale);
1519
1498
 
1520
- if (elm.srid !== undefined) {
1521
- // SAP HANA Geometry types translate into CHAR in plain/sqlite (give them the default length of 2000)
1522
- if (options.sqlDialect !== 'hana')
1523
- params.push(2000);
1524
- else
1525
- params.push(elm.srid);
1526
- }
1499
+ if (elm.srid !== undefined)
1500
+ params.push(renderSRID(elm));
1501
+
1527
1502
  return params.length === 0 ? '' : `(${ params.join(', ') })`;
1528
1503
  }
1529
1504
 
1505
+ function renderSRID( element ) {
1506
+ // SAP HANA Geometry types translate into CHAR in plain/sqlite (give them the default length of 2000)
1507
+ if (options.sqlDialect !== 'hana')
1508
+ return 2000;
1509
+ return element.srid;
1510
+ }
1511
+
1530
1512
  function renderExpressionLiteral( x ) {
1531
1513
  // Literal value, possibly with explicit 'literal' property
1532
1514
  switch (x.literal || typeof x.val) {
@@ -1535,22 +1517,19 @@ function toSqlDdl( csn, options, messageFunctions ) {
1535
1517
  case 'null':
1536
1518
  // 17.42, NULL, TRUE
1537
1519
  return String(x.val).toUpperCase();
1520
+
1538
1521
  case 'x':
1539
1522
  // x'f000'
1540
1523
  return `${ x.literal }'${ x.val }'`;
1541
1524
  case 'date':
1542
1525
  case 'time':
1543
1526
  case 'timestamp':
1544
- if (options.sqlDialect === 'sqlite') {
1545
- // simple string literal '2017-11-02'
1546
- return `'${ x.val }'`;
1547
- }
1548
- // date'2017-11-02'
1549
- return `${ x.literal }'${ x.val }'`;
1527
+ return renderTimeLiteral(x);
1550
1528
 
1551
1529
  case 'string':
1552
1530
  // 'foo', with proper escaping
1553
- return renderStringForSql(x.val, options.sqlDialect);
1531
+ return renderString(x.val);
1532
+
1554
1533
  case 'object':
1555
1534
  if (x.val === null)
1556
1535
  return 'NULL';
@@ -1575,7 +1554,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
1575
1554
  function renderMagicVariable( ref, env ) {
1576
1555
  const magicReplacement = getVariableReplacement(ref, options);
1577
1556
  if (magicReplacement !== null)
1578
- return renderStringForSql(magicReplacement, options.sqlDialect);
1557
+ return renderString(magicReplacement);
1579
1558
 
1580
1559
  const name = pathName(ref);
1581
1560
  const result = variableForDialect(options, name, env.isInDefault ? 'default' : null);
@@ -1592,7 +1571,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
1592
1571
  'Variable $(NAME) is not supported. Use option $(OPTION) to specify a value for $(NAME)');
1593
1572
  }
1594
1573
 
1595
- return renderStringForSql(name, options.sqlDialect);
1574
+ return renderString(name);
1596
1575
  }
1597
1576
 
1598
1577
  /**
@@ -1690,6 +1669,46 @@ function toSqlDdl( csn, options, messageFunctions ) {
1690
1669
  function activateAlterMode( env, changeType ) {
1691
1670
  return env.cloneWith({ alterMode: true, changeType });
1692
1671
  }
1672
+
1673
+
1674
+ function renderComment( art ) {
1675
+ if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
1676
+ return ` COMMENT ${ renderString(getHanaComment(art)) }`;
1677
+ return '';
1678
+ }
1679
+
1680
+ function renderString( str ) {
1681
+ return renderStringForSql(str, options.sqlDialect);
1682
+ }
1683
+
1684
+ function renderTimeLiteral( x ) {
1685
+ if (options.sqlDialect === 'sqlite') {
1686
+ // simple string literal '2017-11-02'
1687
+ return `'${ x.val }'`;
1688
+ }
1689
+ return `${ x.literal }'${ x.val }'`; // date'2017-11-02'
1690
+ }
1691
+
1692
+ function supportsHanaAssociations() {
1693
+ return options.withHanaAssociations && options.sqlDialect === 'hana';
1694
+ }
1695
+
1696
+ function smartFuncId( name ) {
1697
+ return sqlIdentifiers.smartFuncId(name, options.sqlDialect);
1698
+ }
1699
+
1700
+ function supportsSqlExtensions() {
1701
+ return options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions');
1702
+ }
1703
+
1704
+ function sortCsnIfTestMode( obj ) {
1705
+ return options?.testMode ? sortCsn(obj, options) : obj;
1706
+ }
1707
+
1708
+ function useAlterTableForConstraints() {
1709
+ // for `to.sql` w/ dialect `hana` the constraints will be part of the alter statement
1710
+ return !options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres');
1711
+ }
1693
1712
  }
1694
1713
 
1695
1714
  /**
@@ -5,6 +5,7 @@
5
5
  const { getResultingName } = require('../../model/csnUtils');
6
6
  const { smartId, delimitedId } = require('../../sql-identifier');
7
7
  const { ModelError } = require('../../base/error');
8
+ const { isBetaEnabled, setProp } = require('../../base/model');
8
9
 
9
10
  /**
10
11
  * Render a given referential constraint as part of a SQL CREATE TABLE statement, or as .hdbconstraint artefact.
@@ -142,8 +143,74 @@ function getIdentifierUtils( csn, options ) {
142
143
  }
143
144
  }
144
145
 
146
+ const allowedHdbProjectionViewProperties = {
147
+ from: true,
148
+ mixin: true,
149
+ columns: true,
150
+ };
151
+
152
+ /**
153
+ * Determines if the given artifact is a projection view. Caches the information via
154
+ * non-enumerable property `$isProjectionView` on the artifact for non-trivial cases.
155
+ *
156
+ * Requires a for.hana processed model including A2J.
157
+ *
158
+ * @param {CSN.Model} csn - The Core Schema Notation (CSN) model.
159
+ * @param {CSN.Artifact} artifact - The artifact to check for being a projection view.
160
+ * @param {Model.Options} options - Compilation options, including feature flags and SQL dialect.
161
+ * @returns {boolean} `true` if the artifact is a projection view, otherwise `false`.
162
+ */
163
+ function isProjectionView( csn, artifact, options ) {
164
+ if (!isBetaEnabled(options, 'projectionViews') || !artifact.projection || options.sqlDialect !== 'hana' || artifact.params || !artifact.$dataProductService)
165
+ return false;
166
+
167
+ if (artifact.$isProjectionView !== undefined)
168
+ return artifact.$isProjectionView;
169
+
170
+ setProp(artifact, '$isProjectionView', _artifactIsProjectionView(csn, artifact));
171
+
172
+ return artifact.$isProjectionView;
173
+ }
174
+
175
+ function _artifactIsProjectionView( csn, artifact ) {
176
+ for (const prop of Object.keys(artifact.projection)) {
177
+ if (!prop.startsWith('@') && !allowedHdbProjectionViewProperties[prop])
178
+ return false;
179
+ }
180
+
181
+ const source = csn.definitions[artifact.projection.from.ref[0]];
182
+ if (!source)
183
+ return false;
184
+
185
+ // Source must be a table or a projection view. Only support table for now
186
+ if (source.kind !== 'entity' || source.query || source.projection)
187
+ return false;
188
+
189
+ const referencedElements = Object.create(null);
190
+
191
+ for (const column of artifact.projection.columns) {
192
+ if (!column.ref)
193
+ return false;
194
+
195
+ if (referencedElements[column.ref.at(-1)])
196
+ return false; // we have an element projected more than once, seems to not be supported
197
+
198
+ referencedElements[column.ref.at(-1)] = true;
199
+ }
200
+
201
+ // Full primary key needs to be projected according to HANA SQL spec
202
+ for (const elementName in source.elements) {
203
+ const element = source.elements[elementName];
204
+ if (element.key && !referencedElements[elementName])
205
+ return false;
206
+ }
207
+
208
+ return true;
209
+ }
210
+
145
211
 
146
212
  module.exports = {
147
213
  renderReferentialConstraint,
148
214
  getIdentifierUtils,
215
+ isProjectionView,
149
216
  };
@@ -85,7 +85,7 @@ function addTenantFields( csn, options, messageFunctions ) {
85
85
  else if (!independent && independent != null) {
86
86
  error( 'tenant-invalid-anno-value', msgLocations( csnPath ),
87
87
  { anno: annoTenantIndep, value: independent },
88
- // eslint-disable-next-line @stylistic/js/max-len
88
+ // eslint-disable-next-line @stylistic/max-len
89
89
  'Can\'t add $(ANNO) with value $(VALUE) to a non-entity, which is always tenant-independent' );
90
90
  }
91
91
  else if (art.includes) {
@@ -129,9 +129,9 @@ function addTenantFields( csn, options, messageFunctions ) {
129
129
  .filter( name => isTenantDepEntity( csn.definitions[name] ) );
130
130
  if (names.length) {
131
131
  error( 'tenant-invalid-include', msgLocations( csnPath ), { names }, {
132
- // eslint-disable-next-line @stylistic/js/max-len
132
+ // eslint-disable-next-line @stylistic/max-len
133
133
  std: 'Can\'t include the tenant-dependent entities $(NAMES) into a tenant-independent definition',
134
- // eslint-disable-next-line @stylistic/js/max-len
134
+ // eslint-disable-next-line @stylistic/max-len
135
135
  one: 'Can\'t include the tenant-dependent entity $(NAMES) into a tenant-independent definition',
136
136
  } );
137
137
  }
@@ -221,7 +221,7 @@ function addTenantFields( csn, options, messageFunctions ) {
221
221
  if (art[annoTenantIndep]) {
222
222
  error( 'tenant-expecting-tenant-source', msgLocations( csnPath ), { art: query },
223
223
  // TODO: better the final entity name of assoc navigation in FROM
224
- // eslint-disable-next-line @stylistic/js/max-len
224
+ // eslint-disable-next-line @stylistic/max-len
225
225
  'Expecting the query source $(ART) to be tenant-dependent for a tenant-dependent query entity' );
226
226
  }
227
227
  return true;
@@ -26,6 +26,7 @@ const requiredAnnos = {
26
26
  '@Core.Computed': true,
27
27
  [sqlServiceAnnotation]: true,
28
28
  '@cds.external': true, // for external ABAP SQL services and data products for now
29
+ '@DataIntegration.dataproduct.type': true, // for data product production
29
30
  };
30
31
 
31
32
  /**
@@ -16,12 +16,15 @@ const sqlServiceAnnotation = '@protocol';
16
16
  function processSqlServices(csn, options) {
17
17
  setProp(csn, '$sqlServiceEntities', Object.create(null));
18
18
  setProp(csn, '$dummyServiceEntities', Object.create(null));
19
+ setProp(csn, '$dataProductEntities', Object.create(null));
19
20
  return function findAndMarkSqlServiceArtifacts(artifact, artifactName) {
20
- const { sqlServiceName, dummyServiceName } = isEntityInSqlService(artifact, artifactName, csn, options);
21
+ const { sqlServiceName, dummyServiceName, dataProductServiceName } = isEntityInSqlService(artifact, artifactName, csn, options);
21
22
  if (sqlServiceName?.length > 0)
22
23
  setProp(artifact, '$sqlService', sqlServiceName);
23
24
  if (dummyServiceName?.length > 0)
24
25
  setProp(artifact, '$dummyService', dummyServiceName);
26
+ if (dataProductServiceName?.length > 0)
27
+ setProp(artifact, '$dataProductService', dataProductServiceName);
25
28
  };
26
29
  }
27
30
 
@@ -55,7 +58,7 @@ function isDummyService(artifact, options) {
55
58
  * @returns {object} An object containing the names of the SQL service and external ABAP SQL service, if found.
56
59
  */
57
60
  function isEntityInSqlService(artifact, artifactName, csn, options) {
58
- const result = { sqlServiceName: undefined, dummyServiceName: undefined };
61
+ const result = { sqlServiceName: undefined, dummyServiceName: undefined, dataProductServiceName: undefined };
59
62
  if (artifact.kind !== 'entity' || !artifactName.includes('.') || hasPersistenceSkipAnnotation(artifact))
60
63
  return result;
61
64
 
@@ -72,6 +75,9 @@ function isEntityInSqlService(artifact, artifactName, csn, options) {
72
75
  if (isDummyService(definition, options))
73
76
  result.dummyServiceName = possibleServiceName;
74
77
 
78
+ if (isDataProductService(definition, options))
79
+ result.dataProductServiceName = possibleServiceName;
80
+
75
81
  // We don't allow nested services/contexts - if we find one, we don't need to keep searching
76
82
  if (definition.kind === 'service' || definition.kind === 'context')
77
83
  return result;
@@ -108,10 +114,22 @@ function createServiceDummy(artifact, artifactName, csn, { error }) {
108
114
  csn.definitions[`dummy.${ artifactName }`] = dummy;
109
115
  }
110
116
 
117
+ /**
118
+ * Determines if the given artifact is a data product service.
119
+ *
120
+ * @param {object} artifact - The artifact to evaluate.
121
+ * @param {object} options - The options object containing feature flags.
122
+ * @returns {boolean} - Returns `true` if the artifact is a data product service, otherwise `false`.
123
+ */
124
+ function isDataProductService(artifact, options) {
125
+ return isBetaEnabled(options, 'projectionViews') && artifact.kind === 'service' && artifact['@DataIntegration.dataproduct.type'] === 'primary';
126
+ }
127
+
111
128
  module.exports = {
112
129
  processSqlServices,
113
130
  isSqlService,
114
131
  isDummyService,
132
+ isDataProductService,
115
133
  sqlServiceAnnotation,
116
134
  createServiceDummy,
117
135
  };
@@ -232,7 +232,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
232
232
  draftAdministrativeData.DraftAdministrativeData.notNull = true;
233
233
  addElement(draftAdministrativeData, draftsArtifact, artifactName);
234
234
 
235
- if (isBetaEnabled(options, 'draftMessages')) {
235
+ if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
236
236
  const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
237
237
  addElement(draftMessages, draftsArtifact, artifactName);
238
238
  }