@sap/cds-compiler 5.4.2 → 5.5.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 (40) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/bin/cds_remove_invalid_whitespace.js +4 -4
  3. package/bin/cds_update_annotations.js +3 -3
  4. package/bin/cds_update_identifiers.js +3 -3
  5. package/lib/api/main.js +18 -30
  6. package/lib/api/validate.js +6 -1
  7. package/lib/base/lazyload.js +28 -0
  8. package/lib/base/location.js +1 -0
  9. package/lib/base/message-registry.js +53 -11
  10. package/lib/base/messages.js +17 -3
  11. package/lib/checks/{dbFeatureFlags.js → featureFlags.js} +1 -1
  12. package/lib/checks/parameters.js +61 -4
  13. package/lib/checks/validator.js +14 -6
  14. package/lib/compiler/index.js +7 -7
  15. package/lib/compiler/shared.js +29 -13
  16. package/lib/gen/BaseParser.js +345 -235
  17. package/lib/gen/CdlParser.js +4434 -4492
  18. package/lib/gen/Dictionary.json +2 -2
  19. package/lib/json/to-csn.js +3 -1
  20. package/lib/language/antlrParser.js +2 -111
  21. package/lib/main.js +16 -37
  22. package/lib/modelCompare/utils/filter.js +47 -21
  23. package/lib/parsers/AstBuildingParser.js +59 -49
  24. package/lib/parsers/CdlGrammar.g4 +91 -130
  25. package/lib/parsers/index.js +123 -0
  26. package/lib/render/toSql.js +8 -2
  27. package/lib/render/utils/delta.js +33 -1
  28. package/lib/transform/db/{transformExists.js → assocsToQueries/transformExists.js} +12 -407
  29. package/lib/transform/db/assocsToQueries/utils.js +440 -0
  30. package/lib/transform/db/expansion.js +2 -2
  31. package/lib/transform/draft/db.js +14 -3
  32. package/lib/transform/effective/annotations.js +3 -3
  33. package/lib/transform/effective/main.js +5 -7
  34. package/lib/transform/featureFlags.js +5 -0
  35. package/lib/transform/forRelationalDB.js +125 -192
  36. package/lib/transform/odata/createForeignKeys.js +1 -1
  37. package/lib/transform/odata/flattening.js +1 -1
  38. package/lib/transform/transformUtils.js +0 -51
  39. package/package.json +2 -2
  40. package/lib/transform/db/featureFlags.js +0 -5
package/CHANGELOG.md CHANGED
@@ -7,18 +7,41 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+
11
+ ## Version 5.5.0 - 2024-11-22
12
+
13
+ ### Added
14
+
15
+ - CDL parser: when the new experimental option `newParser` is used, the compiler
16
+ uses a CDL parser with a significantly smaller footprint (among other things).
17
+ - to.sql|hdi.migration: For SAP HANA, render `ALTER` statements as one big statement to improve performance.
18
+ - to.sql.migration: Give more helpful comments when using option `script`.
19
+
20
+ ### Changed
21
+
22
+ - Update OData vocabularies: 'Common', 'PersonalData', 'UI'.
23
+
24
+ ## Version 5.4.4 - 2024-11-14
25
+
26
+ ### Fixed
27
+
28
+ - Re-allow referring to mixins (and table aliases) in added columns
29
+ - Re-add foreign keys of named aspects to the OData CSN.
30
+
31
+
10
32
  ## Version 5.4.2 - 2024-11-06
11
33
 
12
34
  ### Fixed
13
35
 
14
36
  - to.sql: For SQLite, map `cds.Map` to `JSON_TEXT` to ensure text affinity.
15
37
 
38
+
16
39
  ## Version 5.4.0 - 2024-10-24
17
40
 
18
41
  ### Added
19
42
 
20
43
  - to.edm(x): `cds.Map` as empty open complex type with name `cds_Map` or if the definition
21
- has been assigned `@open: false` as empty open complex type `cds_Map_closed` in OData V4.
44
+ has been assigned `@open: false` as empty closed complex type `cds_Map_closed` in OData V4.
22
45
 
23
46
  ### Changed
24
47
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // Remove whitespace where the compiler does not expect it and will
4
- // emit an error in the next major version.
4
+ // emit an error in the next major version. (Remark: not necessarily)
5
5
  //
6
6
  // This script removes whitespace where necessary.
7
7
  // It does not remove comments, even though they count as whitespace.
@@ -23,7 +23,7 @@
23
23
 
24
24
  'use strict';
25
25
 
26
- const parseLanguage = require('../lib/language/antlrParser');
26
+ const parsers = require('../lib/parsers');
27
27
  const { createMessageFunctions } = require('../lib/base/messages');
28
28
 
29
29
  const fs = require('fs');
@@ -58,9 +58,9 @@ function modernizeWhitespace( source, filename ) {
58
58
  const options = { messages: [], attachTokens: true };
59
59
  const messageFunctions = createMessageFunctions( options, 'parse', null );
60
60
 
61
- // parseLanguage does not throw on CompilationError, so
61
+ // parseCdl does not throw on CompilationError, so
62
62
  // we do not need a try...catch block.
63
- const ast = parseLanguage(source, filename, options, messageFunctions);
63
+ const ast = parsers.parseCdl(source, filename, options, messageFunctions);
64
64
 
65
65
  // To avoid spam, only report errors.
66
66
  // Users should use the compiler to get all messages.
@@ -22,7 +22,7 @@
22
22
 
23
23
  'use strict';
24
24
 
25
- const parseLanguage = require('../lib/language/antlrParser');
25
+ const parsers = require('../lib/parsers');
26
26
  const { createMessageFunctions } = require('../lib/base/messages');
27
27
 
28
28
  const fs = require('fs');
@@ -58,9 +58,9 @@ function modernizeAnnotationExpressions( source, filename ) {
58
58
  const options = { messages: [], attachTokens: true };
59
59
  const messageFunctions = createMessageFunctions( options, 'parse', null );
60
60
 
61
- // parseLanguage does not throw on CompilationError, so
61
+ // parseCdl does not throw on CompilationError, so
62
62
  // we do not need a try...catch block.
63
- const ast = parseLanguage(source, filename, options, messageFunctions);
63
+ const ast = parsers.parseCdl(source, filename, options, messageFunctions);
64
64
 
65
65
  // To avoid spam, only report errors.
66
66
  // Users should use the compiler to get all messages.
@@ -22,7 +22,7 @@
22
22
 
23
23
  'use strict';
24
24
 
25
- const parseLanguage = require('../lib/language/antlrParser');
25
+ const parsers = require('../lib/parsers');
26
26
  const { createMessageFunctions } = require('../lib/base/messages');
27
27
 
28
28
  const fs = require('fs');
@@ -56,9 +56,9 @@ function modernizeIdentifierStyle( source, filename ) {
56
56
  const options = { messages: [], attachTokens: true };
57
57
  const messageFunctions = createMessageFunctions( options, 'parse', null );
58
58
 
59
- // parseLanguage does not throw on CompilationError, so
59
+ // parseCdl does not throw on CompilationError, so
60
60
  // we do not need a try...catch block.
61
- const ast = parseLanguage(source, filename, options, messageFunctions);
61
+ const ast = parsers.parseCdl(source, filename, options, messageFunctions);
62
62
 
63
63
  // To avoid spam, only report errors.
64
64
  // Users should use the compiler to get all messages.
package/lib/api/main.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  'use strict';
4
4
 
5
+ const lazyload = require('../base/lazyload')( module );
6
+
5
7
  const prepareOptions = lazyload('./options');
6
8
  const baseModel = lazyload('../base/model');
7
9
  const location = lazyload('../base/location');
@@ -470,18 +472,29 @@ function sqlMigration( csn, options, messageFunctions, beforeImage ) {
470
472
  const internalOptions = prepareOptions.to.sql(options);
471
473
 
472
474
  messageFunctions.setOptions( internalOptions );
475
+ if (internalOptions.script)
476
+ messageFunctions.setModuleName( `${ messageFunctions.moduleName }-script` );
477
+
473
478
  handleTenantDiscriminator(options, internalOptions, messageFunctions);
474
479
  if (!options.dry && internalOptions.script) {
475
480
  messageFunctions.error('api-invalid-combination', null, { '#': 'dry-and-script', value: options.dry || 'undefined' });
476
481
  messageFunctions.throwWithError();
477
482
  }
478
483
 
479
- if (internalOptions.script && !internalOptions.severities?.['type-unsupported-key-change']) {
480
- internalOptions.severities ??= {};
481
- internalOptions.severities['type-unsupported-key-change'] = 'warning';
484
+ if (internalOptions.script && !internalOptions.severities?.['migration-unsupported-key-change']) {
485
+ internalOptions.severities = Object.assign({}, internalOptions.severities ?? {});
486
+ internalOptions.severities['migration-unsupported-key-change'] = 'Warning';
487
+ }
488
+
489
+ if (internalOptions.script) {
490
+ internalOptions.severities = Object.assign({}, internalOptions.severities ?? {});
491
+ const turnToWarning = [ 'migration-unsupported-element-drop', 'migration-unsupported-length-change', 'migration-unsupported-scale-change', 'migration-unsupported-precision-change', 'migration-unsupported-change', 'migration-unsupported-table-drop' ];
492
+ turnToWarning.forEach((id) => {
493
+ internalOptions.severities[id] = 'Warning';
494
+ });
482
495
  }
483
496
 
484
- const { error, throwWithError } = messageFunctions;
497
+ const { throwWithError } = messageFunctions;
485
498
 
486
499
  // Prepare after-image.
487
500
  let afterImage = csnForSql(csn, internalOptions, messageFunctions);
@@ -495,7 +508,7 @@ function sqlMigration( csn, options, messageFunctions, beforeImage ) {
495
508
  if (diffFilterObj) {
496
509
  diff.extensions = diff.extensions.filter(ex => diffFilterObj.extension(ex, messageFunctions));
497
510
  diff.migrations.forEach(migration => diffFilterObj.migration(migration, messageFunctions));
498
- Object.entries(diff.deletions).forEach(entry => diffFilterObj.deletion(entry, error));
511
+ Object.entries(diff.deletions).forEach(entry => diffFilterObj.deletion(entry, messageFunctions));
499
512
  diff.changedPrimaryKeys = diff.changedPrimaryKeys
500
513
  .filter(an => diffFilterObj.changedPrimaryKeys(an));
501
514
 
@@ -1271,31 +1284,6 @@ function ensureClientCsn( csn, options, messageFunctions, module ) {
1271
1284
  return csn;
1272
1285
  }
1273
1286
 
1274
- /**
1275
- * Load the module on-demand and not immediately.
1276
- *
1277
- * @param {string} moduleName Name of the module to load - like with require
1278
- * @returns {object} A Proxy that handles the on-demand loading
1279
- */
1280
- function lazyload( moduleName ) {
1281
- let module;
1282
- return new Proxy(((...args) => {
1283
- if (!module)
1284
- module = require(moduleName);
1285
-
1286
- if (module.apply && typeof module.apply === 'function')
1287
- return module.apply(this, args);
1288
- return module; // for destructured calls
1289
- }), {
1290
- get(target, name) {
1291
- if (!module)
1292
- module = require(moduleName);
1293
-
1294
- return module[name];
1295
- },
1296
- });
1297
- }
1298
-
1299
1287
  /**
1300
1288
  * Error when tenantDiscriminator and withHanaAssociations is set by the user, or
1301
1289
  * if tenantDiscriminator is used with anything but "plain" mode.
@@ -58,7 +58,12 @@ const validators = {
58
58
  },
59
59
  },
60
60
  severities: {
61
- validate: val => val !== null && typeof val === 'object' && !Array.isArray(val),
61
+ validate: (val) => {
62
+ if (val !== null && typeof val === 'object' && !Array.isArray(val))
63
+ return true;
64
+
65
+ return false;
66
+ },
62
67
  expected: () => 'type object',
63
68
  found: (val) => {
64
69
  return val === null ? val : `type ${ typeof val }`;
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ function createLazyload( callingModule ) {
4
+ /**
5
+ * Load the module on-demand and not immediately.
6
+ *
7
+ * @param {string} moduleName Name of the module to load - like with require
8
+ * @returns {object} A Proxy that handles the on-demand loading
9
+ */
10
+ return function lazyload( moduleName ) {
11
+ let module;
12
+ return new Proxy(((...args) => {
13
+ if (!module)
14
+ module = callingModule.require(moduleName);
15
+ if (module.apply && typeof module.apply === 'function')
16
+ return module.apply(this, args);
17
+ return module; // for destructured calls
18
+ }), {
19
+ get(target, name) {
20
+ if (!module)
21
+ module = callingModule.require(moduleName);
22
+ return module[name];
23
+ },
24
+ });
25
+ }
26
+ }
27
+
28
+ module.exports = createLazyload;
@@ -11,6 +11,7 @@ class Location {
11
11
  col;
12
12
  endLine;
13
13
  endCol;
14
+ tokenIndex;
14
15
  constructor( file, line, col, endLine, endCol ) {
15
16
  this.file = file;
16
17
  this.line = line;
@@ -154,7 +154,8 @@ const centralMessages = {
154
154
  // then either issue an error or produce a CSN missing some annotations):
155
155
  'syntax-duplicate-annotate': { severity: 'Error' },
156
156
  'syntax-duplicate-clause': { severity: 'Error', configurableFor: true },
157
- 'syntax-duplicate-equal-clause': { severity: 'Warning' },
157
+ // remark: a hard syntax error in new parser for `null` together with `not null`
158
+ 'syntax-duplicate-equal-clause': { severity: 'Warning', errorFor: [ 'v6' ] },
158
159
  'syntax-invalid-name': { severity: 'Error', configurableFor: 'deprecated' },
159
160
  'syntax-missing-as': { severity: 'Error', configurableFor: true },
160
161
  'syntax-missing-proj-semicolon': { severity: 'Warning', errorFor: [ 'v6' ] },
@@ -168,9 +169,7 @@ const centralMessages = {
168
169
  'syntax-invalid-space': { severity: 'Error', configurableFor: 'test' },
169
170
  'syntax-expecting-space': { severity: 'Error' },
170
171
  'syntax-unexpected-anno': { severity: 'Error' },
171
-
172
- 'type-unsupported-precision-change': { severity: 'Error' },
173
- 'type-unsupported-key-change': { severity: 'Error', configurableFor: true },
172
+ 'migration-unsupported-key-change': { severity: 'Error', configurableFor: [ 'to.sql.migration', 'to.sql.migration-script' ] },
174
173
  'type-missing-enum-value': { severity: 'Error', configurableFor: 'test' },
175
174
 
176
175
  'def-missing-element': { severity: 'Error' },
@@ -204,7 +203,15 @@ const centralMessages = {
204
203
  'odata-anno-value': { severity: 'Warning' },
205
204
  'odata-anno-type': { severity: 'Warning' },
206
205
  'odata-anno-def': { severity: 'Info' },
207
- 'query-ignoring-assoc-in-union': { severity: 'Info' }
206
+ 'query-ignoring-assoc-in-union': { severity: 'Info' },
207
+ // for to.sql.migration - cannot be supplied by the user!
208
+ 'migration-unsupported-precision-change': { severity: 'Error', configurableFor: [ 'to.sql.migration-script'] },
209
+ 'migration-unsupported-element-drop': { severity: 'Error', configurableFor: [ 'to.sql.migration-script'] },
210
+ 'migration-unsupported-length-change': { severity: 'Error', configurableFor: [ 'to.sql.migration-script'] },
211
+ 'migration-unsupported-scale-change': { severity: 'Error', configurableFor: [ 'to.sql.migration-script'] },
212
+ 'migration-unsupported-change': { severity: 'Error', configurableFor: [ 'to.sql.migration-script'] },
213
+ 'migration-unsupported-table-drop': { severity: 'Error', configurableFor: [ 'to.sql.migration-script'] }
214
+ // end of to.sql.migration specific
208
215
  };
209
216
 
210
217
  // Old/Deprecated message IDs that we only still use for backwards-compatibility.
@@ -416,7 +423,7 @@ const centralMessageTexts = {
416
423
  'syntax-duplicate-equal-clause': {
417
424
  std: 'You have already provided the same clause',
418
425
  cardinality: 'You have already provided the target cardinality $(CODE) at line $(LINE), column $(COL)',
419
- notNull: 'You have already provided $(CODE) at line $(LINE), column $(COL) below',
426
+ notNull: 'You have already provided $(CODE) at line $(LINE), column $(COL)',
420
427
  },
421
428
  'syntax-duplicate-extend': {
422
429
  std: 'You can\'t define and refer to $(NAME) repeatedly in the same extend statement',
@@ -1038,6 +1045,12 @@ const centralMessageTexts = {
1038
1045
  none: 'Ambiguous $(ID) requires an explicit table alias, but there are none: add table aliases to all sub-queries to disambiguate $(ID)',
1039
1046
  },
1040
1047
 
1048
+ 'ref-special-in-extend': {
1049
+ std: 'In an added column, $(ID) refers to the element of the projection source $(ART), not the table alias or mixin',
1050
+ alias: 'In an added column, $(ID) refers to the element of the projection source $(ART), not the table alias',
1051
+ mixin: 'In an added column, $(ID) refers to the element of the projection source $(ART), not the mixin',
1052
+ },
1053
+
1041
1054
  'type-managed-composition': {
1042
1055
  std: 'Managed compositions can\'t be used in types', // yet
1043
1056
  sub: 'Managed compositions can\'t be used in sub elements',
@@ -1045,11 +1058,6 @@ const centralMessageTexts = {
1045
1058
  entity: 'Entity $(ART) with managed compositions can\'t be used in types', // yet
1046
1059
  },
1047
1060
 
1048
- 'type-unsupported-key-change': {
1049
- std: 'Added element $(ID) is a primary key change and will not work if the table contains data',
1050
- changed: 'Changed element $(ID) is a primary key change and will not work if the table contains data'
1051
- },
1052
-
1053
1061
  'type-unsupported-key-sqlite': {
1054
1062
  std: 'Added element $(ID) is a primary key change and will not work with dialect $(NAME)',
1055
1063
  changed: 'Changed element $(ID) is a primary key change and will not work with dialect $(NAME)'
@@ -1267,6 +1275,40 @@ const centralMessageTexts = {
1267
1275
  // -----------------------------------------------------------------------------------
1268
1276
  // OData Message section ends here, no messages below this line
1269
1277
  // -----------------------------------------------------------------------------------
1278
+ // -----------------------------------------------------------------------------------
1279
+ // to.sql.migration specific error messages
1280
+ // -----------------------------------------------------------------------------------
1281
+ 'migration-unsupported-key-change': {
1282
+ std: 'Added element $(ID) is a primary key change and will not work if the table contains data',
1283
+ changed: 'Changed element $(ID) is a primary key change and will not work if the table contains data'
1284
+ },
1285
+ 'migration-unsupported-precision-change': {
1286
+ std: 'Changed element $(ID) is a precision change and is not supported',
1287
+ script: 'Changed element $(ID) is a precision change and might lead to data loss'
1288
+ },
1289
+ 'migration-unsupported-element-drop': {
1290
+ std: 'Dropping elements is not supported',
1291
+ script: 'Dropping elements leads to data loss'
1292
+ },
1293
+ 'migration-unsupported-length-change': {
1294
+ std: 'Changed element $(ID) is a length reduction and is not supported',
1295
+ script: 'Changed element $(ID) is a length reduction and might lead to data loss'
1296
+ },
1297
+ 'migration-unsupported-scale-change': {
1298
+ std: 'Changed element $(ID) is a scale change and is not supported',
1299
+ script: 'Changed element $(ID) is a scale change and might lead to data loss'
1300
+ },
1301
+ 'migration-unsupported-change': {
1302
+ std: 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported',
1303
+ script: 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and might lead to data loss'
1304
+ },
1305
+ 'migration-unsupported-table-drop': {
1306
+ std: 'Dropping tables is not supported',
1307
+ script: 'Dropping tables leads to data loss'
1308
+ },
1309
+ // -----------------------------------------------------------------------------------
1310
+ // to.sql.migration specific error messages end here
1311
+ // -----------------------------------------------------------------------------------
1270
1312
  }
1271
1313
 
1272
1314
  /**
@@ -357,9 +357,11 @@ function createMessageFunctions( options, moduleName, model = null ) {
357
357
  * ```
358
358
  * @param {object} model
359
359
  * @param {CSN.Options} [options]
360
- * @param {string|null} [moduleName]
360
+ * @param {string|null} [_moduleName]
361
361
  */
362
- function makeMessageFunction( model, options, moduleName = null ) {
362
+ function makeMessageFunction( model, options, _moduleName = null ) {
363
+ let moduleName = _moduleName;
364
+
363
365
  if (options.testMode) {
364
366
  // ensure message consistency during runtime with --test-mode
365
367
  _check$Init( options );
@@ -395,6 +397,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
395
397
  callTransparently,
396
398
  moduleName,
397
399
  setModel,
400
+ setModuleName,
398
401
  setOptions,
399
402
  };
400
403
 
@@ -631,11 +634,22 @@ function makeMessageFunction( model, options, moduleName = null ) {
631
634
  model = _model;
632
635
  }
633
636
 
637
+ /**
638
+ *
639
+ * Change the moduleName used for reclassifying messages.
640
+ * Needed for to.sql.migration + script
641
+ *
642
+ * @param {string} __moduleName
643
+ */
644
+ function setModuleName( __moduleName ) {
645
+ moduleName = __moduleName;
646
+ }
647
+
634
648
  /**
635
649
  * Change the options used to determine message severities.
636
650
  * This is necessary if you change `options.severities`, as otherwise they may not be picked up.
637
651
  *
638
- * @param {CSN.Model} _model
652
+ * @param {CSN.Options} _options
639
653
  */
640
654
  function setOptions( _options ) {
641
655
  options = _options;
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { setProp } = require('../base/model');
4
- const { featureFlags } = require('../transform/db/featureFlags');
4
+ const { featureFlags } = require('../transform/featureFlags');
5
5
  const { isSqlService } = require('../transform/db/processSqlServices');
6
6
 
7
7
  /**
@@ -12,12 +12,69 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
12
12
  */
13
13
  function checkForParams( parent, name, params, path ) {
14
14
  const artifact = this.csn.definitions[path[1]];
15
- if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && parent.kind === 'entity') {
16
- this.error('ref-unexpected-params', [ ...path, 'params' ], { value: this.options.sqlDialect },
17
- 'Parameterized views can\'t be used with sqlDialect $(VALUE)');
15
+ if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && artifact['@cds.persistence.exists'] !== true && parent.kind === 'entity') {
16
+ if (artifact.query || artifact.projection) {
17
+ if (this.options.sqlDialect === 'hana') {
18
+ for (const pname in artifact.params) {
19
+ if (pname.match(/\W/g) || pname.match(/^\d/) || pname.match(/^_/)) { // parameter name must be regular SQL identifier
20
+ this.warning(null, [ ...path, 'params', pname ], 'Expecting regular SQL-Identifier');
21
+ }
22
+ else if (this.options.sqlMapping !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
23
+ this.warning(null, [ ...path, 'params', pname ], { name: this.options.sqlMapping },
24
+ 'Expecting parameter to be uppercase in naming mode $(NAME)');
25
+ }
26
+ }
27
+ }
28
+ else {
29
+ this.error('ref-unexpected-params', [ ...path, 'params' ], { value: this.options.sqlDialect },
30
+ 'Parameterized views can\'t be used with sqlDialect $(VALUE)');
31
+ }
32
+ }
33
+ else {
34
+ this.error(null, path, { '#': this.options.toSql ? 'sql' : 'std' }, {
35
+ std: 'Table-like entities with parameters are not supported for conversion to SAP HANA CDS',
36
+ sql: 'Table-like entities with parameters are not supported for conversion to SQL',
37
+ });
38
+ }
39
+ }
40
+ }
41
+
42
+ function checkAssocsWithParams( member, memberName, prop, path ) {
43
+ // Report an error on
44
+ // - view with parameters that has an element of type association/composition
45
+ // - association that points to entity with parameters
46
+ if (member.target && this.csnUtils.isAssocOrComposition(member)) {
47
+ if (this.artifact.params) {
48
+ // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
49
+ // SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
50
+ this.message('def-unexpected-paramview-assoc', path, { '#': 'source' });
51
+ }
52
+ else if (this.artifact['@cds.persistence.udf'] || this.artifact['@cds.persistence.calcview']) {
53
+ // UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
54
+ const anno = this.artifact['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
55
+ this.message('def-unexpected-calcview-assoc', path, { '#': 'source', anno });
56
+ }
57
+ if (this.csn.definitions[member.target].params) {
58
+ // HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
59
+ // SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
60
+ this.message('def-unexpected-paramview-assoc', path, { '#': 'target' });
61
+ }
62
+ else if (this.csn.definitions[member.target]['@cds.persistence.udf'] || this.csn.definitions[member.target]['@cds.persistence.calcview']) {
63
+ // HANA won't check the assoc target but when querying an association with target UDF, this is the error:
64
+ // SAP DBTech JDBC: [259]: invalid table name: target object SYSTEM.UDF does not exist: line 3 col 6 (at pos 43)
65
+ // CREATE TABLE F (id INTEGER NOT NULL);
66
+ // CREATE FUNCTION UDF RETURNS TABLE (ID INTEGER) LANGUAGE SQLSCRIPT SQL SECURITY DEFINER AS BEGIN RETURN SELECT ID FROM F; END;
67
+ // CREATE TABLE Y ( id INTEGER NOT NULL, toUDF_id INTEGER) WITH ASSOCIATIONS (MANY TO ONE JOIN UDF AS toUDF ON (toUDF.id = toUDF_id));
68
+ // CREATE VIEW U AS SELECT id, toUDF.a FROM Y;
69
+ const anno = this.csn.definitions[member.target]['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
70
+ this.message('def-unexpected-calcview-assoc', path, { '#': 'target', anno });
71
+ }
18
72
  }
19
73
  }
20
74
 
21
75
  module.exports = {
22
- params: checkForParams,
76
+ csnValidator: {
77
+ params: checkForParams,
78
+ },
79
+ memberValidator: checkAssocsWithParams,
23
80
  };
@@ -49,7 +49,7 @@ const {
49
49
  checkSqlAnnotationOnArtifact,
50
50
  checkSqlAnnotationOnElement,
51
51
  } = require('./sql-snippets');
52
- const dbFeatureFlags = require('./dbFeatureFlags');
52
+ const featureFlags = require('./featureFlags');
53
53
 
54
54
  const forRelationalDBMemberValidators
55
55
  = [
@@ -85,7 +85,7 @@ const forRelationalDBCsnValidators = [
85
85
  nonexpandableStructuredInExpression,
86
86
  navigationIntoMany,
87
87
  checkPathsInStoredCalcElement,
88
- dbFeatureFlags,
88
+ featureFlags,
89
89
  ];
90
90
  /**
91
91
  * @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
@@ -188,10 +188,14 @@ function _validate( csn, that,
188
188
  * @returns {any[]} Array of validator functions (or objects?)
189
189
  */
190
190
  function getDBCsnValidators( options ) {
191
- if (options.sqlDialect === 'postgres' || options.sqlDialect === 'h2')
192
- return [ ...forRelationalDBCsnValidators, checkForHanaTypes, checkForParams ];
191
+ const validations = [ ...forRelationalDBCsnValidators ];
193
192
 
194
- return forRelationalDBCsnValidators;
193
+ if (options.transformation !== 'effective')
194
+ validations.push(checkForParams.csnValidator);
195
+ if (options.sqlDialect === 'h2' || options.sqlDialect === 'postgres')
196
+ validations.push(checkForHanaTypes);
197
+
198
+ return validations;
195
199
  }
196
200
 
197
201
  /**
@@ -200,9 +204,13 @@ function getDBCsnValidators( options ) {
200
204
  * @returns {Function} the validator function with the respective checks for the HANA backend
201
205
  */
202
206
  function forRelationalDB( csn, that ) {
207
+ const memberValidators = [ ...forRelationalDBMemberValidators, ...commonMemberValidators ];
208
+ if (that.options.transformation === 'hdbcds')
209
+ memberValidators.push(checkForParams.memberValidator);
210
+
203
211
  return _validate(csn, that,
204
212
  getDBCsnValidators(that.options),
205
- forRelationalDBMemberValidators.concat(commonMemberValidators),
213
+ memberValidators,
206
214
  forRelationalDBArtifactValidators.concat(commonArtifactValidators).concat(
207
215
  // why is this hana exclusive
208
216
  (artifact) => {
@@ -13,7 +13,7 @@
13
13
  'use strict';
14
14
 
15
15
  const { makeModuleResolver, makeModuleResolverSync } = require('../utils/moduleResolve');
16
- const parseLanguage = require('../language/antlrParser'); // TODO: should we do some lazyload here?
16
+ const parsers = require('../parsers');
17
17
  const parseCsn = require('../json/from-csn');
18
18
 
19
19
  const assertConsistency = require('./assert-consistency');
@@ -43,10 +43,10 @@ const { XsnSource } = require('./xsn-model');
43
43
  const extensionParsers = {
44
44
  csn: parseCsn.parse,
45
45
  json: parseCsn.parse,
46
- cds: parseLanguage,
47
- cdl: parseLanguage,
48
- hdbcds: parseLanguage,
49
- hdbdd: parseLanguage,
46
+ cds: parsers.parseCdl,
47
+ cdl: parsers.parseCdl,
48
+ hdbcds: parsers.parseCdl,
49
+ hdbdd: parsers.parseCdl,
50
50
  };
51
51
 
52
52
  // Class for command invocation errors. Additional members:
@@ -108,7 +108,7 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
108
108
  function parserForFile( source, ext, options ) {
109
109
  // 'auto!' ignores the file's extension
110
110
  if (options.fallbackParser === 'auto!')
111
- return (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage);
111
+ return (source?.startsWith( '{' ) ? parseCsn.parse : parsers.parseCdl);
112
112
 
113
113
  if (options.fallbackParser === 'csn!')
114
114
  return parseCsn.parse;
@@ -458,7 +458,7 @@ function recompileX( csn, options ) {
458
458
  * On the given model (AST like CSN) run the definer, resolver as well as semantic checks.
459
459
  * Creates an augmented CSN (XSN) and returns it.
460
460
  *
461
- * @param {object} model AST like CSN generated e.g. by `parseLanguage()`
461
+ * @param {object} model AST like CSN generated e.g. by `parsers.parseCdl()`
462
462
  * @returns {XSN.Model} Augmented CSN (XSN)
463
463
  */
464
464
  function compileDoX( model ) {
@@ -174,7 +174,7 @@ function fns( model ) {
174
174
  having: 'where',
175
175
  groupBy: 'where',
176
176
  column: {
177
- lexical: tableAliasesIfNotExtendAndSelf,
177
+ lexical: tableAliasesAndSelf,
178
178
  dollar: true,
179
179
  dynamic: combinedSourcesOrParentElements,
180
180
  notFound: undefinedSourceElement,
@@ -721,16 +721,41 @@ function fns( model ) {
721
721
  return setArtifactLink( head, def ); // we do not want to see the using
722
722
  }
723
723
  case 'mixin': {
724
+ // use a source element having that name if in `extend … with columns`:
725
+ const elem = (user._user || user).$extended &&
726
+ art._parent._combined[head.id];
727
+ if (elem) {
728
+ path.$prefix = elem._parent.name.id; // prepend alias name
729
+ info( 'ref-special-in-extend', [ head.location, user ],
730
+ { '#': 'mixin', id: head.id, art: elem._origin._main } );
731
+ setLink( head, '_navigation', elem );
732
+ return setArtifactLink( head, elem._origin );
733
+ }
724
734
  return setLink( head, '_navigation', art );
725
735
  }
726
736
  case '$navElement': {
727
- if (head.id === (user._user || user).$extended)
728
- path.$prefix = head.id;
729
737
  setLink( head, '_navigation', art );
730
738
  return setArtifactLink( head, art._origin );
731
739
  }
732
- case '$self': // TODO: remove $projection from CC
733
740
  case '$tableAlias': {
741
+ // use a source element having that name if in `extend … with columns`:
742
+ const { $extended } = user._user || user;
743
+ const elem = $extended && art.elements[head.id];
744
+ if (elem) {
745
+ path.$prefix = art.name.id; // prepend alias name
746
+ info( 'ref-special-in-extend', [ head.location, user ],
747
+ { '#': 'alias', id: head.id, art: elem._origin._main } );
748
+ setLink( head, '_navigation', elem );
749
+ return setArtifactLink( head, elem._origin );
750
+ }
751
+ else if ($extended) {
752
+ warning( 'ref-deprecated-in-extend', [ head.location, user ], { id: head.id },
753
+ // eslint-disable-next-line @stylistic/js/max-len
754
+ 'In an added column, do not use the table alias $(ID) to refer to source elements' );
755
+ }
756
+ }
757
+ /* FALLTHROUGH */
758
+ case '$self': { // TODO: remove $projection from CC
734
759
  setLink( head, '_navigation', art );
735
760
  setArtifactLink( head, art._origin ); // query source or leading query in FROM
736
761
  if (!art._origin)
@@ -842,15 +867,6 @@ function fns( model ) {
842
867
  function tableAliasesAndSelf( user ) {
843
868
  return userQuery( user ) || user._main || user;
844
869
  }
845
- function tableAliasesIfNotExtendAndSelf( user ) {
846
- if (!user.$extended)
847
- return tableAliasesAndSelf( user );
848
- if (typeof user.$extended !== 'string') {
849
- const aliases = userQuery( user ).$tableAliases;
850
- user.$extended = Object.keys( aliases )[0];
851
- }
852
- return justDollarAliases( user );
853
- }
854
870
 
855
871
  // Functions called via semantics.dynamic: ------------------------------------
856
872