@sap/cds-compiler 6.9.2 → 7.0.1

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 (69) hide show
  1. package/CHANGELOG.md +86 -2
  2. package/bin/cdsc.js +4 -33
  3. package/doc/IncompatibleChanges_v7.md +639 -0
  4. package/lib/api/main.js +4 -56
  5. package/lib/api/options.js +6 -15
  6. package/lib/api/validate.js +1 -0
  7. package/lib/base/builtins.js +1 -2
  8. package/lib/base/csnRefs.js +2 -6
  9. package/lib/base/message-registry.js +82 -76
  10. package/lib/base/messages.js +23 -4
  11. package/lib/base/optionProcessor.js +2 -72
  12. package/lib/base/specialOptions.js +20 -17
  13. package/lib/checks/defaultValues.js +1 -39
  14. package/lib/checks/hasPersistedElements.js +19 -3
  15. package/lib/checks/parameters.js +0 -34
  16. package/lib/checks/selectItems.js +2 -38
  17. package/lib/checks/typeParameters.js +162 -0
  18. package/lib/checks/validator.js +5 -8
  19. package/lib/compiler/assert-consistency.js +19 -5
  20. package/lib/compiler/checks.js +47 -43
  21. package/lib/compiler/define.js +6 -6
  22. package/lib/compiler/extend.js +102 -111
  23. package/lib/compiler/generate.js +4 -8
  24. package/lib/compiler/populate.js +4 -7
  25. package/lib/compiler/propagator.js +9 -9
  26. package/lib/compiler/resolve.js +205 -7
  27. package/lib/compiler/shared.js +76 -82
  28. package/lib/compiler/tweak-assocs.js +102 -22
  29. package/lib/compiler/utils.js +57 -12
  30. package/lib/compiler/xpr-rewrite.js +2 -15
  31. package/lib/edm/annotations/edmJson.js +14 -10
  32. package/lib/edm/annotations/genericTranslation.js +3 -1
  33. package/lib/edm/annotations/preprocessAnnotations.js +9 -26
  34. package/lib/edm/csn2edm.js +27 -20
  35. package/lib/edm/edmUtils.js +25 -0
  36. package/lib/gen/CdlGrammar.checksum +1 -1
  37. package/lib/gen/CdlParser.js +2237 -2241
  38. package/lib/gen/Dictionary.json +17 -2
  39. package/lib/json/from-csn.js +67 -52
  40. package/lib/json/to-csn.js +28 -25
  41. package/lib/language/textUtils.js +0 -13
  42. package/lib/main.d.ts +34 -59
  43. package/lib/main.js +1 -1
  44. package/lib/model/csnUtils.js +9 -8
  45. package/lib/parsers/AstBuildingParser.js +45 -55
  46. package/lib/parsers/Lexer.js +2 -0
  47. package/lib/parsers/identifiers.js +0 -9
  48. package/lib/render/toCdl.js +41 -40
  49. package/lib/render/toSql.js +8 -1
  50. package/lib/render/utils/common.js +1 -1
  51. package/lib/render/utils/sql.js +2 -3
  52. package/lib/tool-lib/enrichCsn.js +1 -2
  53. package/lib/transform/db/applyTransformations.js +7 -5
  54. package/lib/transform/db/assertUnique.js +8 -51
  55. package/lib/transform/db/associations.js +1 -1
  56. package/lib/transform/db/cdsPersistence.js +1 -15
  57. package/lib/transform/db/expansion.js +9 -12
  58. package/lib/transform/db/flattening.js +1 -1
  59. package/lib/transform/db/groupByOrderBy.js +0 -16
  60. package/lib/transform/db/views.js +57 -161
  61. package/lib/transform/draft/db.js +2 -2
  62. package/lib/transform/forOdata.js +25 -14
  63. package/lib/transform/forRelationalDB.js +93 -301
  64. package/lib/transform/localized.js +33 -102
  65. package/lib/transform/odata/flattening.js +11 -2
  66. package/lib/transform/transformUtils.js +25 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
  68. package/package.json +2 -2
  69. package/lib/render/toHdbcds.js +0 -1810
@@ -11,7 +11,7 @@ const { quotedLiteralPatterns, specialFunctions } = require('../compiler/builtin
11
11
  const { primaryExprProperties } = require('../base/builtins');
12
12
 
13
13
  const { parseMultiLineStringLiteral } = require('../language/multiLineStringParser');
14
- const { normalizeNewLine, normalizeNumberString } = require('../language/textUtils');
14
+ const { normalizeNumberString } = require('../language/textUtils');
15
15
  const { parseDocComment } = require('../language/docCommentParser');
16
16
 
17
17
  const $location = Symbol.for('cds.$location');
@@ -537,30 +537,22 @@ class AstBuildingParser extends BaseParser {
537
537
  * `;` between statements is optional only after a `}` (ex braces of structure
538
538
  * values for annotations and foreign key specifications).
539
539
  *
540
- * Unfortunate exception: always optional after `entity … as projection on`.
540
+ * Unfortunate exception: optional after `entity … as projection on` without sub
541
+ * queries in `where`, …
541
542
  *
542
- * Beware: mentioned in leanConditions, i.e. executed in predictions!
543
+ * Used via:
544
+ * - <prepare=afterBrace>: make guard succeed after closing `}` (not for anno values)
545
+ * - <prepare=afterBrace, arg=sloppyOn>: switch on sloppy-`;` mode
546
+ * - <guard=afterBrace>: succeeds if after previous `}` (with <prepare>)
547
+ * - <guard=afterBrace, arg=orSloppy>: also succeeds (with warning) if sloppy
548
+ *
549
+ * Beware: mentioned in grammar option `hardGuards`, i.e. executed in predictions!
550
+ * (Needed for the rule exit prediction.)
543
551
  */
544
552
  afterBrace( arg, test ) {
545
553
  if (!test) {
546
- if (arg === 'normal' && this.lb().type !== '}') {
547
- const { type, keyword } = this.la();
548
- if (type !== ';' && type !== '}' && type !== 'EOF' && keyword !== 'actions') {
549
- const prev = this.lb().location;
550
- const loc = new Location( prev.file, prev.endLine, prev.endCol );
551
- this.warning( 'syntax-missing-proj-semicolon', loc,
552
- { expecting: [ "';'" ], offending: this.antlrName( this.la() ) },
553
- 'Missing $(EXPECTING) before $(OFFENDING)');
554
- }
555
- }
556
- // with arg 'init' (used in rule `start`/`artifactsBlock`), and arg 'sloppy'
557
- // or 'normal' (used in rule `entityDef`), set marker in dynamic context:
558
- if (!arg)
559
- this.afterBrace$ = this.tokenIdx;
560
- else if (arg === 'init')
561
- this.dynamic_.sloppySemicolon$ = [ false ];
562
- else if (arg === 'sloppy' || this.la().keyword === 'actions')
563
- this.dynamic_.sloppySemicolon$[0] = (arg === 'sloppy');
554
+ // -42 is the magic number for sloppy semicolon handling
555
+ this.afterBrace$ = (arg ? -42 : this.tokenIdx);
564
556
  return null;
565
557
  }
566
558
  // TODO TOOL: the following test belongs to the BaseParser.js:
@@ -568,14 +560,21 @@ class AstBuildingParser extends BaseParser {
568
560
  this.conditionStackLength == null && // after error recover
569
561
  test !== 'M')
570
562
  return false;
571
- const { sloppySemicolon$ } = this.dynamic_;
572
- if (!sloppySemicolon$?.[0])
573
- return this.afterBrace$ !== this.tokenIdx;
574
- if (test === true && sloppySemicolon$) // TODO: single-let mode for running parser
575
- sloppySemicolon$[0] = false;
576
- return this.afterBrace$ !== this.tokenIdx && test === 'M';
577
- // TODO: should we always fail for expected set (test === 'M'), at least if
578
- // token is not on a new line?
563
+ if (this.afterBrace$ === this.tokenIdx)
564
+ return false;
565
+ if (!arg || this.afterBrace$ !== -42 || test === 'M')
566
+ return true;
567
+ if (test === true) { // TODO: single-char mode for run
568
+ const { type, location } = this.lb();
569
+ if (type !== '}') {
570
+ const loc = new Location( location.file, location.endLine, location.endCol );
571
+ this.warning( 'syntax-missing-proj-semicolon', loc,
572
+ { expecting: [ "';'" ], offending: this.antlrName( this.la() ) },
573
+ 'Missing $(EXPECTING) before $(OFFENDING)');
574
+ }
575
+ this.afterBrace$ = -1; // switch off sloopy-`;`
576
+ }
577
+ return false;
579
578
  }
580
579
 
581
580
  /**
@@ -632,7 +631,11 @@ class AstBuildingParser extends BaseParser {
632
631
  this.conditionStackLength == null && // after error recover
633
632
  mode !== 'M')
634
633
  return false;
635
- return true; // mode !== 'Y';
634
+ // remark: collecting an `Id` as sync token does not help much yet, because
635
+ // using `Id` as sync token is restricted at the moment (too often sub-optimal).
636
+ // TODO (introduce base parser method): allow if followed by ':'/'='/';'/','/'}',
637
+ // or wait for future “try different recovery possibilities”.
638
+ return mode !== 'Y';
636
639
  }
637
640
 
638
641
  // Space handling etc, locations ----------------------------------------------
@@ -743,22 +746,6 @@ class AstBuildingParser extends BaseParser {
743
746
  return art;
744
747
  }
745
748
 
746
- ruleTokensText() {
747
- let tokenIdx = this.stack.at(-1).tokenIdx + 1;
748
- const stop = this.tokenIdx;
749
-
750
- let { text: result, location: prev } = this.tokens[tokenIdx];
751
- while (++tokenIdx < stop) {
752
- const { text, location } = this.tokens[tokenIdx];
753
- if (location.line > prev.endLine ||
754
- location.line === prev.endLine && location.col > prev.endCol)
755
- result += ' ';
756
- result += normalizeNewLine( text );
757
- prev = location;
758
- }
759
- return result;
760
- }
761
-
762
749
  // AST building ---------------------------------------------------------------
763
750
 
764
751
  assignAnnotation( art, val, name, prefix = '' ) {
@@ -1203,11 +1190,14 @@ class AstBuildingParser extends BaseParser {
1203
1190
  }
1204
1191
 
1205
1192
  checkStructProps( dict ) {
1206
- const special = primaryExprProperties.find( prop => dict[prop] !== undefined );
1207
- if (special || dict['=']) {
1208
- const prop = special || '=';
1209
- this.message( 'syntax-invalid-anno-struct', dict[prop].name.location,
1210
- { '#': (special ? 'std' : 'ref'), prop } );
1193
+ const prop = primaryExprProperties.find( p => dict[p] !== undefined );
1194
+ if (prop) {
1195
+ this.error( 'syntax-invalid-anno-struct', dict[prop].name.location,
1196
+ { '#': 'std', prop } );
1197
+ }
1198
+ else if (dict['='] && (dict['='].literal !== 'string' || !dict['='].val)) {
1199
+ this.error( 'syntax-invalid-anno-struct', dict['='].location,
1200
+ { '#': 'ref', prop: '=' } );
1211
1201
  }
1212
1202
  }
1213
1203
 
@@ -1350,8 +1340,8 @@ class AstBuildingParser extends BaseParser {
1350
1340
  funcToken.parsedAs = 'func';
1351
1341
 
1352
1342
  const filter = path[0].cardinality || path[0].where; // XSN TODO: filter$location
1353
- if (filter) // TODO v7: make this be reported via guard, as error
1354
- this.message( 'syntax-unexpected-filter', filter.location, {} );
1343
+ if (filter) // TODO v8: make this error be reported via guard
1344
+ this.error( 'syntax-unexpected-filter', filter.location, {} );
1355
1345
  // TODO: XSN representation of functions is a bit strange - rework
1356
1346
  return this.attachLocation( {
1357
1347
  op: { location, val: 'call' },
@@ -1382,8 +1372,8 @@ class AstBuildingParser extends BaseParser {
1382
1372
  }, 'References after function calls can\'t be resolved. Use $(CODE) in function arguments');
1383
1373
  }
1384
1374
  const filter = item.cardinality || item.where; // XSN TODO: filter$location
1385
- if (filter) // TODO v7: make this be reported via guard, as error
1386
- this.message( 'syntax-unexpected-filter', filter.location, {} );
1375
+ if (filter) // TODO v8: make this error be reported via guard
1376
+ this.error( 'syntax-unexpected-filter', filter.location, {} );
1387
1377
  }
1388
1378
 
1389
1379
  const args = [];
@@ -181,6 +181,8 @@ function string( text, lexer, parser, beg ) {
181
181
  prefix.location.endCol !== lexer.location.col ||
182
182
  !quotedLiterals.includes( prefix.keyword )))
183
183
  prefix = null;
184
+ // TODO: warning (error in v8) if non-quotedLiterals prefix and/or
185
+ // try to do it via grammar = hard `<guard=followedByQuote> 'x' String` etc ?
184
186
  while (re.test( lexer.input ) && lexer.input[re.lastIndex] === "'")
185
187
  esc = ++re.lastIndex;
186
188
  }
@@ -15,17 +15,8 @@ const functionsWithoutParentheses = [
15
15
  'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
16
16
  ];
17
17
 
18
- function isSimpleCdlIdentifier( id ) {
19
- if (undelimitedIdentifierRegex.test(id))
20
- return true;
21
- const upperId = id.toUpperCase();
22
- return !cdlKeywords.includes(upperId) &&
23
- !functionsWithoutParentheses.includes(upperId);
24
- }
25
-
26
18
  module.exports = {
27
19
  undelimitedIdentifierRegex,
28
20
  cdlKeywords,
29
21
  functionsWithoutParentheses,
30
- isSimpleCdlIdentifier,
31
22
  };
@@ -1123,7 +1123,6 @@ class CsnToCdl {
1123
1123
  result += env.indent;
1124
1124
  result += element.virtual ? 'virtual ' : '';
1125
1125
  result += element.key ? 'key ' : '';
1126
- result += element.masked ? 'masked ' : '';
1127
1126
  result += this.quoteNonIdentifierOrKeyword(elementName, env);
1128
1127
  if (element['#'] !== undefined) { // enum symbol reference
1129
1128
  result += ` = #${ element['#'] }`;
@@ -2001,50 +2000,52 @@ class CsnToCdl {
2001
2000
  * @param {any} annoValue
2002
2001
  * @param {CdlRenderEnvironment} env
2003
2002
  */
2004
- renderAnnotationValue( annoValue, env ) {
2005
- if (isAnnotationExpression(annoValue)) {
2006
- // Once inside an expression, we stay there.
2007
- const xpr = this.exprRenderer.renderExpr(annoValue, env);
2008
- return `( ${ xpr } )`;
2009
- }
2010
- else if (Array.isArray(annoValue)) {
2003
+ renderAnnotationValue( annoValue, env, isTopLevel = false ) {
2004
+ if (Array.isArray(annoValue))
2011
2005
  return this.renderAnnotationArrayValue( annoValue, env );
2012
- }
2013
- else if (typeof annoValue === 'object' && annoValue !== null) {
2014
- // Enum symbol
2015
- if (annoValue['#'] !== undefined) {
2016
- return `#${ annoValue['#'] }`;
2017
- }
2018
- // Shorthand for absolute path (as string)
2019
- else if (typeof annoValue['='] === 'string') {
2020
- if (annoValue['='].startsWith('@'))
2021
- return this.quoteAnnotationPathIfRequired(annoValue['='], env);
2022
- return this.quotePathIfRequired(annoValue['='], env);
2023
- }
2024
- // Shorthand for ellipsis: `... up to <val>`
2025
- else if (annoValue['...'] !== undefined) {
2026
- if (annoValue['...'] === true)
2027
- return '...';
2028
- return `... up to ${ this.renderAnnotationValue(annoValue['...'], env) }`;
2029
- }
2030
2006
 
2031
- // Struct value (can currently only occur within an array)
2032
- // Render as one-liner if there is at most one key. Render as multi-line
2033
- // struct if there are more and use nicer indentation.
2034
- const keys = Object.keys(annoValue);
2035
- const childEnv = env.withIncreasedIndent();
2036
- const values = keys.map(key => `${ this.quoteAnnotationPathIfRequired(key, env) }: ${ this.renderAnnotationValue(annoValue[key], childEnv.withSubPath([ key ])) }`);
2037
- const result = joinDocuments(values, [ ',', line() ]);
2038
- return format(nestBy(env.indent.length, bracketBlock(INDENT_SIZE, '{', result, '}') ));
2039
- }
2040
2007
  // Null
2041
- else if (annoValue === null) {
2008
+ if (annoValue === null)
2042
2009
  return 'null';
2010
+
2011
+ if (typeof annoValue !== 'object') {
2012
+ // Primitive: string, number, boolean
2013
+ // Quote strings, leave all others as they are
2014
+ return (typeof annoValue === 'string') ? renderString(annoValue, env) : String(annoValue);
2043
2015
  }
2044
- // Primitive: string, number, boolean
2016
+ // Enum symbol (is also annotation expression, but rendered without `(…)`
2017
+ if (annoValue['#'] !== undefined)
2018
+ return `#${ annoValue['#'] }`;
2045
2019
 
2046
- // Quote strings, leave all others as they are
2047
- return (typeof annoValue === 'string') ? renderString(annoValue, env) : String(annoValue);
2020
+ if (isAnnotationExpression(annoValue)) {
2021
+ // Once inside an expression, we stay there.
2022
+ const xpr = this.exprRenderer.renderExpr(annoValue, env);
2023
+ return `( ${ xpr } )`;
2024
+ }
2025
+ // Unchecked reference in `=` (with additional props: render as struct if not top-level)
2026
+ if (typeof annoValue['='] === 'string' &&
2027
+ (isTopLevel || Object.keys(annoValue).length === 1)) {
2028
+ // TODO: the to.cdl backend could issue a warning if there are non-`=`
2029
+ // properties in a structure outside an array (not representable in CDL)
2030
+ return (annoValue['='].startsWith('@'))
2031
+ ? this.quoteAnnotationPathIfRequired(annoValue['='], env)
2032
+ : this.quotePathIfRequired(annoValue['='], env);
2033
+ }
2034
+ // Ellipsis: `... up to <val>`
2035
+ if (annoValue['...'] !== undefined) {
2036
+ return (annoValue['...'] === true)
2037
+ ? '...'
2038
+ : `... up to ${ this.renderAnnotationValue(annoValue['...'], env) }`;
2039
+ }
2040
+
2041
+ // Struct value (TODO: issue warning if outside an array, possible via CSN input)
2042
+ // Render as one-liner if there is at most one key. Render as multi-line
2043
+ // struct if there are more and use nicer indentation.
2044
+ const keys = Object.keys(annoValue);
2045
+ const childEnv = env.withIncreasedIndent();
2046
+ const values = keys.map(key => `${ this.quoteAnnotationPathIfRequired(key, env) }: ${ this.renderAnnotationValue(annoValue[key], childEnv.withSubPath([ key ])) }`);
2047
+ const result = joinDocuments(values, [ ',', line() ]);
2048
+ return format(nestBy(env.indent.length, bracketBlock(INDENT_SIZE, '{', result, '}') ));
2048
2049
  }
2049
2050
 
2050
2051
  /**
@@ -2485,7 +2486,7 @@ class CsnToCdl {
2485
2486
  // nor multiple `#`, so we're back at simple paths that are possibly quoted.
2486
2487
  result += `#${ this.quotePathIfRequired(variant, env) }`;
2487
2488
  }
2488
- result += ` : ${ this.renderAnnotationValue(anno, env) }`;
2489
+ result += ` : ${ this.renderAnnotationValue(anno, env, true) }`;
2489
2490
 
2490
2491
  if (parentheses)
2491
2492
  result += ')';
@@ -1460,7 +1460,14 @@ function toSqlDdl( csn, options, messageFunctions ) {
1460
1460
  * @returns {string} Rendered type
1461
1461
  */
1462
1462
  function renderBuiltinType( typeName ) {
1463
- const types = cdsToSqlTypes[options.sqliteRealAffinityForDecimal && options.sqlDialect === 'sqlite' ? 'sqlitereal' : options.sqlDialect];
1463
+ // Determine if we should use REAL affinity for decimal types
1464
+ // Priority: 1) new decimal_affinity option, 2) old sqliteRealAffinityForDecimal option
1465
+ const useRealAffinity = options.sqlDialect === 'sqlite' && (
1466
+ options.decimal_affinity === 'real' ||
1467
+ (options.decimal_affinity === undefined && options.sqliteRealAffinityForDecimal === true)
1468
+ );
1469
+
1470
+ const types = cdsToSqlTypes[useRealAffinity ? 'sqlitereal' : options.sqlDialect];
1464
1471
  const result = types?.[typeName] || cdsToSqlTypes.standard[typeName];
1465
1472
  if (!result && options.testMode)
1466
1473
  throw new CompilerAssertion(`Expected to find a type mapping for ${ typeName }`);
@@ -37,7 +37,7 @@ function renderFunc( funcName, node, renderArgs, utils ) {
37
37
  const { sqlDialect } = options;
38
38
  if (funcWithoutParen( node, sqlDialect ))
39
39
  return funcName;
40
- const rewriteStandardFunctions = options.transformation !== 'hdbcds' && sqlDialect !== 'plain' && options.standardDatabaseFunctions !== false;
40
+ const rewriteStandardFunctions = sqlDialect !== 'plain' && options.standardDatabaseFunctions !== false;
41
41
  if (rewriteStandardFunctions) {
42
42
  // we check function arguments for correctness
43
43
  const { error } = messageFunctions;
@@ -29,8 +29,7 @@ function renderReferentialConstraint( constraint, indent, toUpperCase, csn, opti
29
29
  constraint.parentTable = constraint.parentTable.toUpperCase();
30
30
  }
31
31
 
32
- const renderAsHdbconstraint = options.transformation === 'hdbcds' ||
33
- options.src === 'hdi';
32
+ const renderAsHdbconstraint = options.src === 'hdi';
34
33
 
35
34
  const { sqlMapping, sqlDialect } = options;
36
35
  let result = '';
@@ -54,7 +53,7 @@ function renderReferentialConstraint( constraint, indent, toUpperCase, csn, opti
54
53
  }
55
54
  // constraint enforcement / validation must be switched off using sqlite pragma statement
56
55
  // constraint enforcement / validation not supported by postgres
57
- if (options.transformation === 'hdbcds' || (options.toSql && sqlDialect !== 'sqlite' && sqlDialect !== 'postgres')) {
56
+ if (options.toSql && sqlDialect !== 'sqlite' && sqlDialect !== 'postgres') {
58
57
  result += `${ indent }${ !constraint.validated ? 'NOT ' : '' }VALIDATED\n`;
59
58
  result += `${ indent }${ !constraint.enforced ? 'NOT ' : '' }ENFORCED\n`;
60
59
  }
@@ -142,9 +142,8 @@ function enrichCsn( csn, options = {} ) {
142
142
  obj.forEach( (n, i) => assignment( obj, i, n ) );
143
143
  }
144
144
  else {
145
- // TODO: check obj['='] is only String|true ?
146
145
  const record = !isAnnotationExpression( obj ) && assignment;
147
- // is record without `=` and other expression property
146
+ // is record without primary expression property
148
147
  for (const name of Object.getOwnPropertyNames( obj ) ) {
149
148
  const trans = record || transformers[name] || transformers[name.charAt(0)] || standard;
150
149
  trans( obj, name, obj[name] );
@@ -358,30 +358,32 @@ function transformExpression( parent, parentName, transformers, path = [], ctx =
358
358
  * @param {string|number} propName Start at specific property of parent
359
359
  * @param {object} transformers Map of callback functions
360
360
  * @param {CSN.Path} path Path to parent
361
+ * @param {boolean} annoScope Is it annotation scope
361
362
  * @returns {object} transformed node
362
363
  */
363
- function transformAnnotationExpression( parent, propName, transformers, path = [] ) {
364
+ function transformAnnotationExpression( parent, propName, transformers, path = [], annoScope = undefined ) {
364
365
  if (propName != null) {
365
366
  const child = parent[propName];
366
367
  if (!child || typeof child !== 'object' ||
367
368
  !{}.propertyIsEnumerable.call( parent, propName ))
368
369
  return parent;
369
370
 
370
- if (isAnnotationExpression(child))
371
+ annoScope ||= propName[0] === '@';
372
+ if (annoScope && isAnnotationExpression(child))
371
373
  return transformExpression(parent, propName, transformers, path, { annoExpr: child });
372
374
 
373
375
  path = [ ...path, propName ];
374
376
  if (Array.isArray(child)) {
375
- child.forEach( (n, i) => transformAnnotationExpression( child, i, transformers, path ) );
377
+ child.forEach( (n, i) => transformAnnotationExpression( child, i, transformers, path, annoScope ) );
376
378
  }
377
379
  else {
378
380
  for (const cpn of Object.getOwnPropertyNames( child ))
379
- transformAnnotationExpression(child, cpn, transformers, path);
381
+ transformAnnotationExpression(child, cpn, transformers, path, annoScope);
380
382
  }
381
383
  }
382
384
  else {
383
385
  for (propName of Object.getOwnPropertyNames( parent ))
384
- transformAnnotationExpression( parent, propName, transformers, path );
386
+ transformAnnotationExpression( parent, propName, transformers, path, annoScope );
385
387
  }
386
388
  return parent;
387
389
  }
@@ -254,11 +254,7 @@ function processAssertUnique( csn, options, messageFunctions ) {
254
254
  * If the output format is SQL, the toSql renderer is responsible
255
255
  * to render the table constraints from the constraint dictionary.
256
256
  *
257
- * If options.transformation === 'hdbcds', no path flattening is done and association
258
- * paths are replaced with the foreign key paths by simply
259
- * concatenating the foreign key paths (available in element.keys).
260
- *
261
- * If options.toSql, all paths are flattened depending on the naming
257
+ * All paths are flattened depending on the naming
262
258
  * mode either with '_' or '.' as delimiter.
263
259
  * Each association is replaced by the respective foreign key elements
264
260
  * that are annotated with an appropriate '@odata.foreignKey4'.
@@ -275,47 +271,23 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
275
271
  function rewrite( artifact ) {
276
272
  if (artifact.$tableConstraints && artifact.$tableConstraints.unique) {
277
273
  const uniqueConstraints = artifact.$tableConstraints.unique;
278
- // it's safe to add the tc here
279
- if (options.transformation === 'hdbcds') {
280
- if (!artifact.technicalConfig)
281
- artifact.technicalConfig = Object.create(null);
282
-
283
- if (!artifact.technicalConfig.hana) {
284
- artifact.technicalConfig.hana = Object.create(null);
285
- artifact.technicalConfig.hana.calculated = true;
286
- }
287
- if (!artifact.technicalConfig.hana.indexes)
288
- artifact.technicalConfig.hana.indexes = Object.create(null);
289
- }
290
274
  for (const uniqueConstraint in artifact.$tableConstraints.unique) {
291
275
  // iterate over each constraint
292
276
  const c = uniqueConstraints[uniqueConstraint].paths;
293
277
  const rewrittenPaths = [];
294
278
  // and inspect each path of the constraint
295
279
  c.forEach((cpath) => {
296
- // If 'toSql' or 'toHana' and naming !== 'hdbcds'
297
280
  // concatenate path refs with appropriate delimiter
298
- if (options.transformation !== 'hdbcds' || (options.transformation === 'hdbcds' && options.sqlMapping !== 'hdbcds'))
299
- cpath.ref = [ cpath.ref.map(p => p.id).join( pathDelimiter ) ];
281
+ cpath.ref = [ cpath.ref.map(p => p.id).join( pathDelimiter ) ];
300
282
 
301
283
  // Foreign key substitution
302
284
  if (cpath._art.target) {
303
- if (options.transformation !== 'hdbcds' || (options.transformation === 'hdbcds' && options.sqlMapping !== 'hdbcds')) {
304
- // read out new association and use $generatedFieldName
305
- // cpath._art still refers to the assoc definition
306
- // before the A2J transformation. This assoc
307
- // doesn't contain the correct $generatedFieldName(s)
308
- const assoc = artifact.elements[cpath.ref[0]];
309
- rewrittenPaths.push(...assoc.keys.map(k => ({ ref: [ k.$generatedFieldName ] })));
310
- }
311
- else {
312
- // This is Classic HANA CDS toHana/hdbcds
313
- // add foreign key ref path to association path
314
- // ... for hanacds, the 'real' ref paths are used, and
315
- // these have not changed before and after A2J transformation,
316
- // so it's safe to use the original paths.
317
- rewrittenPaths.push(...cpath._art.keys.map(k => ({ ref: cpath.ref.concat(k.ref) })));
318
- }
285
+ // read out new association and use $generatedFieldName
286
+ // cpath._art still refers to the assoc definition
287
+ // before the A2J transformation. This assoc
288
+ // doesn't contain the correct $generatedFieldName(s)
289
+ const assoc = artifact.elements[cpath.ref[0]];
290
+ rewrittenPaths.push(...assoc.keys.map(k => ({ ref: [ k.$generatedFieldName ] })));
319
291
  }
320
292
  else {
321
293
  rewrittenPaths.push(cpath);
@@ -323,21 +295,6 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
323
295
  });
324
296
  // preserve the rewritten and filtered paths for toSql
325
297
  uniqueConstraints[uniqueConstraint] = { paths: rewrittenPaths, parentTable: uniqueConstraints[uniqueConstraint].parentTable };
326
-
327
- // now add the index for HANA CDS
328
- if (options.transformation === 'hdbcds') {
329
- const cond = [];
330
- let i = 0;
331
- for (const constraint of rewrittenPaths) {
332
- if (i > 0)
333
- cond.push(',');
334
- cond.push(constraint);
335
- i++;
336
- }
337
- artifact.technicalConfig.hana.indexes[uniqueConstraint] = [
338
- 'unique', 'index', { ref: [ uniqueConstraint ] }, 'on', { xpr: cond },
339
- ];
340
- }
341
298
  }
342
299
  artifact.$tableConstraints.unique = uniqueConstraints;
343
300
  }
@@ -136,7 +136,7 @@ function getFKAccessFinalizer( csn, options, csnUtils, pathDelimiter, processOnI
136
136
  if (obj[anno].ref)
137
137
  transformer.ref(obj[anno], 'ref', obj[anno].ref, path.concat(anno));
138
138
  const annoAfter = JSON.stringify(obj[anno]);
139
- if (annoBefore !== annoAfter)
139
+ if (annoBefore !== annoAfter && (options.transformation !== 'effective' || obj[anno]['=']))
140
140
  obj[anno]['='] = true;
141
141
  });
142
142
  }
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- forEachGeneric,
5
4
  forEachMemberRecursively,
6
5
  isPersistedOnDatabase,
7
6
  hasPersistenceSkipAnnotation,
@@ -42,7 +41,6 @@ function getAnnoProcessor() {
42
41
  */
43
42
  function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
44
43
  const { info } = messageFunctions;
45
- const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
46
44
 
47
45
  const { isAssocOrComposition } = csnUtils;
48
46
 
@@ -53,21 +51,9 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
53
51
  *
54
52
  * @param {CSN.Artifact} artifact
55
53
  * @param {string} artifactName
56
- * @param {string} prop
57
- * @param {CSN.Path} path
58
54
  */
59
- function ignoreAssociationToSkippedTarget( artifact, artifactName, prop, path ) {
55
+ function ignoreAssociationToSkippedTarget( artifact, artifactName, _prop, _path ) {
60
56
  if (isPersistedOnDatabase(artifact)) {
61
- // TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
62
- if (artifact.query) {
63
- // If we do A2J, we don't need to check the mixin. Either it is used -> a join
64
- // or published -> handled via elements/members. Unused mixins are removed anyway.
65
- if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
66
- forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
67
-
68
- else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
69
- forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
70
- }
71
57
  forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
72
58
  }
73
59
  }
@@ -40,13 +40,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
40
40
  columns: (parent, name, columns, path) => {
41
41
  const artifact = csn.definitions[path[1]];
42
42
  csnUtils.initDefinition(artifact); // potentially not initialized, yet
43
- const root = csnUtils.get$combined({ SELECT: parent });
44
- // TODO: replace with the correct options.transformation?
45
- // Do not expand the * in OData for a moment, not to introduce changes
46
- // while the OData CSN is still official
47
43
  const isComplexQuery = parent.from.join !== undefined;
48
- if (!options.toOdata)
44
+ if (!options.toOdata && columns.includes('*')) {
45
+ const root = csnUtils.get$combined({ SELECT: parent });
49
46
  parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
47
+ }
50
48
  // FIXME(v6): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
51
49
  // if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
52
50
  parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
@@ -63,7 +61,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
63
61
  };
64
62
 
65
63
  // To not have a whole model loop for such a "small" thing, we kill all non-sql-backend relevant annotations here
66
- if (options.transformation === 'sql' || options.transformation === 'hdbcds')
64
+ if (options.transformation === 'sql')
67
65
  transformers['@'] = killNonrequiredAnno;
68
66
 
69
67
  applyTransformations(csn, transformers, [], iterateOptions);
@@ -362,7 +360,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
362
360
  if (sub.ref) {
363
361
  // Each expand/inline can introduce another layer of $self/$projection. Since $self is
364
362
  // a path-breakout, we can simply use the ref without outer expand/inline-references.
365
- subRef = (sub.$scope === '$self') ? sub.ref : currentRef.concat(sub.ref);
363
+ subRef = (sub.$scope === '$self' || sub.param) ? sub.ref : currentRef.concat(sub.ref);
366
364
  }
367
365
  else {
368
366
  subRef = currentRef;
@@ -456,7 +454,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
456
454
  function rewriteOnCondition( on, currentRef, stack ) {
457
455
  for (let i = 0; i < on.length; i++) {
458
456
  const part = on[i];
459
- if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self' && part.$scope !== '$projection') {
457
+ if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self' && part.$scope !== '$projection' && !part.param) {
460
458
  part.ref = currentRef[0] ? [ currentRef[0], ...part.ref ] : part.ref;
461
459
  on[i] = part;
462
460
  stack.push([ part, part.ref ]);
@@ -477,7 +475,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
477
475
  function rewriteSingleExpressionArray( expressionArray, currentRef, stack ) {
478
476
  for (let i = 0; i < expressionArray.length; i++) {
479
477
  const part = expressionArray[i];
480
- if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self') {
478
+ if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self' && !part.param) {
481
479
  part.ref = currentRef.concat(part.ref);
482
480
  expressionArray[i] = part;
483
481
  stack.push([ part, part.ref ]);
@@ -842,7 +840,7 @@ function expandWildcard( query, csnUtils, options ) {
842
840
 
843
841
  /**
844
842
  * Get all elements that are expanded by '*'.
845
- * Respects the query's 'excluding' clause and 'masked' elements.
843
+ * Respects the query's 'excluding' clause.
846
844
  * Elements that are replaced by columns have a `replacedByColumn` property.
847
845
  *
848
846
  * @param {object} query Query with SELECT/projection.
@@ -855,9 +853,8 @@ function wildcardElements( query, csnUtils ) {
855
853
  for (const excluded of SELECT.excluding ?? [])
856
854
  delete combined[excluded];
857
855
 
858
- // Handle deprecated 'masked' keyword.
856
+ // TODO?: simplify
859
857
  for (const name in combined) {
860
- combined[name] = combined[name].filter(sourceElement => !sourceElement.element?.masked);
861
858
  if (combined[name].length === 0)
862
859
  delete combined[name];
863
860
  else
@@ -80,7 +80,7 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
80
80
  iterateOptions.skipDict = { actions: true };
81
81
 
82
82
  const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
83
- const stripItems = options.transformation === 'hdbcds' || options.transformation === 'sql';
83
+ const stripItems = options.transformation === 'sql';
84
84
  const removeItems = new Set();
85
85
  applyTransformations(csn, {
86
86
  type: (node, prop, type, path, parent, parentProp) => {
@@ -24,14 +24,6 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
24
24
  const { art } = inspectRef(groupByPath);
25
25
  if (art && art.target) {
26
26
  if (art.keys) {
27
- // This is (or used to be before transformation) a managed assoc
28
- // (230 c) If we keep associations as they are (hdbcds naming convention), we can't have associations in GROUP BY
29
- if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
30
- error(null, groupByPath,
31
- { $reviewed: true, keyword: 'GROUP BY', value: 'hdbcds' },
32
- 'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
33
- continue;
34
- }
35
27
  const pathPrefix = query.groupBy[i].ref.slice(0, -1);
36
28
  getForeignKeyRefs(art)
37
29
  .map(fk => ({ ref: pathPrefix.concat(fk.ref) }))
@@ -57,14 +49,6 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
57
49
  const { art } = inspectRef(orderByPath);
58
50
  if (art && art.target) {
59
51
  if (art.keys) {
60
- // This is (or used to be before transformation) a managed assoc
61
- // (230 d) If we keep associations as they are (hdbcds naming convention), we can't have associations in ORDER BY
62
- if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
63
- error(null, orderByPath,
64
- { $reviewed: true, keyword: 'ORDER BY', value: 'hdbcds' },
65
- 'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
66
- continue;
67
- }
68
52
  const pathPrefix = query.orderBy[i].ref.slice(0, -1);
69
53
  getForeignKeyRefs(art)
70
54
  .map(fk => ({ ref: pathPrefix.concat(fk.ref) }))