@sap/cds-compiler 5.9.2 → 6.0.10

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 (111) hide show
  1. package/CHANGELOG.md +109 -319
  2. package/README.md +1 -1
  3. package/bin/cds_update_identifiers.js +3 -5
  4. package/bin/cdsc.js +22 -8
  5. package/bin/cdshi.js +1 -1
  6. package/bin/cdsse.js +4 -4
  7. package/doc/CHANGELOG_BETA.md +11 -0
  8. package/doc/CHANGELOG_DEPRECATED.md +29 -0
  9. package/lib/api/main.js +8 -5
  10. package/lib/api/options.js +12 -10
  11. package/lib/base/builtins.js +1 -0
  12. package/lib/base/message-registry.js +190 -96
  13. package/lib/base/messages.js +29 -20
  14. package/lib/base/model.js +14 -24
  15. package/lib/checks/actionsFunctions.js +10 -20
  16. package/lib/checks/annotationsOData.js +1 -1
  17. package/lib/checks/elements.js +30 -10
  18. package/lib/checks/enums.js +31 -0
  19. package/lib/checks/foreignKeys.js +2 -2
  20. package/lib/checks/hasPersistedElements.js +5 -0
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/managedWithoutKeys.js +5 -4
  23. package/lib/checks/queryNoDbArtifacts.js +10 -8
  24. package/lib/checks/types.js +5 -5
  25. package/lib/checks/validator.js +6 -4
  26. package/lib/compiler/assert-consistency.js +12 -9
  27. package/lib/compiler/checks.js +18 -50
  28. package/lib/compiler/define.js +6 -6
  29. package/lib/compiler/extend.js +2 -1
  30. package/lib/compiler/generate.js +14 -17
  31. package/lib/compiler/populate.js +8 -31
  32. package/lib/compiler/propagator.js +21 -35
  33. package/lib/compiler/resolve.js +35 -22
  34. package/lib/compiler/shared.js +7 -1
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +1 -1
  37. package/lib/edm/annotations/edmJson.js +20 -15
  38. package/lib/edm/annotations/genericTranslation.js +7 -8
  39. package/lib/edm/csn2edm.js +46 -50
  40. package/lib/edm/edm.js +8 -7
  41. package/lib/edm/edmPreprocessor.js +37 -85
  42. package/lib/edm/edmUtils.js +2 -2
  43. package/lib/gen/BaseParser.js +55 -44
  44. package/lib/gen/CdlGrammar.checksum +1 -1
  45. package/lib/gen/CdlParser.js +1133 -1150
  46. package/lib/json/from-csn.js +70 -43
  47. package/lib/json/to-csn.js +6 -8
  48. package/lib/language/multiLineStringParser.js +3 -2
  49. package/lib/main.d.ts +58 -24
  50. package/lib/model/csnUtils.js +28 -39
  51. package/lib/model/xprAsTree.js +23 -9
  52. package/lib/modelCompare/compare.js +5 -4
  53. package/lib/optionProcessor.js +21 -17
  54. package/lib/parsers/AstBuildingParser.js +63 -11
  55. package/lib/parsers/XprTree.js +57 -3
  56. package/lib/parsers/identifiers.js +1 -1
  57. package/lib/parsers/index.js +0 -3
  58. package/lib/render/manageConstraints.js +25 -25
  59. package/lib/render/toCdl.js +173 -170
  60. package/lib/render/toHdbcds.js +126 -128
  61. package/lib/render/toRename.js +7 -7
  62. package/lib/render/toSql.js +128 -125
  63. package/lib/render/utils/common.js +47 -22
  64. package/lib/render/utils/delta.js +25 -25
  65. package/lib/render/utils/operators.js +2 -2
  66. package/lib/render/utils/pretty.js +5 -5
  67. package/lib/render/utils/sql.js +13 -13
  68. package/lib/render/utils/standardDatabaseFunctions.js +115 -103
  69. package/lib/render/utils/unique.js +4 -4
  70. package/lib/transform/db/applyTransformations.js +1 -1
  71. package/lib/transform/db/assertUnique.js +2 -2
  72. package/lib/transform/db/associations.js +6 -7
  73. package/lib/transform/db/assocsToQueries/utils.js +4 -5
  74. package/lib/transform/db/backlinks.js +12 -9
  75. package/lib/transform/db/cdsPersistence.js +8 -7
  76. package/lib/transform/db/constraints.js +13 -10
  77. package/lib/transform/db/expansion.js +7 -3
  78. package/lib/transform/db/flattening.js +4 -14
  79. package/lib/transform/db/processSqlServices.js +2 -1
  80. package/lib/transform/db/temporal.js +5 -7
  81. package/lib/transform/db/views.js +2 -4
  82. package/lib/transform/draft/db.js +8 -8
  83. package/lib/transform/draft/odata.js +10 -7
  84. package/lib/transform/forOdata.js +10 -5
  85. package/lib/transform/forRelationalDB.js +5 -75
  86. package/lib/transform/localized.js +1 -1
  87. package/lib/transform/odata/createForeignKeys.js +11 -10
  88. package/lib/transform/odata/flattening.js +8 -4
  89. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
  90. package/lib/transform/odata/typesExposure.js +3 -3
  91. package/lib/transform/transformUtils.js +4 -8
  92. package/lib/transform/translateAssocsToJoins.js +14 -7
  93. package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
  94. package/lib/utils/objectUtils.js +0 -17
  95. package/package.json +10 -13
  96. package/share/messages/def-upcoming-virtual-change.md +1 -1
  97. package/LICENSE +0 -37
  98. package/bin/cds_remove_invalid_whitespace.js +0 -138
  99. package/doc/CHANGELOG_ARCHIVE.md +0 -3604
  100. package/lib/gen/genericAntlrParser.js +0 -3
  101. package/lib/gen/language.checksum +0 -1
  102. package/lib/gen/language.interp +0 -456
  103. package/lib/gen/language.tokens +0 -180
  104. package/lib/gen/languageLexer.interp +0 -439
  105. package/lib/gen/languageLexer.js +0 -1483
  106. package/lib/gen/languageLexer.tokens +0 -167
  107. package/lib/gen/languageParser.js +0 -24941
  108. package/lib/language/antlrParser.js +0 -205
  109. package/lib/language/errorStrategy.js +0 -646
  110. package/lib/language/genericAntlrParser.js +0 -1572
  111. package/lib/parsers/CdlGrammar.g4 +0 -2070
@@ -7,19 +7,26 @@
7
7
  //
8
8
  // Function/assoc arguments and filter conditions are
9
9
  // - traversed with expressionAsTree() and conditionAsTree(),
10
- // - not traversed with exprAsTree() and condAsTree()
10
+ // - not traversed with exprAsTree() and condAsTree(), you might need to call
11
+ // splitClauses() to cover `order by` in the last function argument
11
12
 
12
13
  'use strict';
13
14
 
14
- const xprParser = require( '../parsers/XprTree' );
15
+ const { csnAsTree, splitClauses } = require( '../parsers/XprTree' );
15
16
 
16
17
  function conditionAsTree( args ) {
17
18
  args.forEach( expressionAsTree );
18
- return xprParser.xprAsTree( args, args, true );
19
+ return asTree( args );
19
20
  }
20
21
  function condAsTree( args ) {
21
22
  args.forEach( exprAsTree );
22
- return xprParser.xprAsTree( args, args, true );
23
+ return asTree( args );
24
+ }
25
+
26
+ function asTree( args ) {
27
+ return (args.length === 2 && args[0] === 'over' && args[1]?.xpr && !args[1].func)
28
+ ? [ 'over', { xpr: splitClauses( args[1].xpr, true ) } ]
29
+ : csnAsTree( args );
23
30
  }
24
31
 
25
32
  function expressionAsTree( expr ) {
@@ -31,11 +38,17 @@ function expressionAsTree( expr ) {
31
38
  }
32
39
  if (expr.list) // expression, ref
33
40
  expr.list.forEach( expressionAsTree );
34
- if (expr.args) { // expression, ref
35
- if (Array.isArray( expr.args ) )
36
- expr.args.forEach(expressionAsTree);
37
- else
38
- Object.values(expr.args).forEach(expressionAsTree);
41
+ const { args } = expr;
42
+ if (args) { // expression, ref
43
+ if (!Array.isArray( args )) {
44
+ Object.values( args ).forEach( expressionAsTree );
45
+ }
46
+ else if (args.length) {
47
+ args.forEach( expressionAsTree );
48
+ const last = args.at( -1 );
49
+ if (last?.xpr && last.xpr.length > 4) // order by in last argument
50
+ last.xpr = splitClauses( last.xpr, true );
51
+ }
39
52
  }
40
53
  if (expr.ref) // expression
41
54
  expr.ref.forEach( expressionAsTree );
@@ -68,4 +81,5 @@ module.exports = {
68
81
  condAsTree,
69
82
  expressionAsTree,
70
83
  exprAsTree,
84
+ splitClauses: nodes => splitClauses( nodes, true ),
71
85
  };
@@ -57,20 +57,21 @@ function validateCsnVersions(beforeModel, afterModel, options) {
57
57
 
58
58
  if (!beforeVersionParts || beforeVersionParts.length < 2) {
59
59
  const { error, throwWithAnyError } = makeMessageFunction(beforeModel, options, 'modelCompare');
60
- error(null, null, { version: beforeVersion || 'undefined' }, 'Invalid CSN version: $(VERSION)');
60
+ error('api-invalid-version', null, { version: beforeVersion || 'unknown' });
61
61
  throwWithAnyError();
62
62
  }
63
63
  if (!afterVersionParts || afterVersionParts.length < 2) {
64
64
  const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
65
- error(null, null, { version: afterVersion || 'undefined' }, 'Invalid CSN version: $(VERSION)');
65
+ error('api-invalid-version', null, { version: afterVersion || 'unknown' });
66
66
  throwWithAnyError();
67
67
  }
68
68
  if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
69
69
  const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
70
70
 
71
71
  const { version } = require('../../package.json');
72
- error(null, null, { value: afterVersion, othervalue: beforeVersion, version },
73
- 'Incompatible CSN versions: $(VALUE) is a major downgrade from $(OTHERVALUE). Is @sap/cds-compiler version $(VERSION) outdated?');
72
+ error('api-invalid-version', null, {
73
+ '#': 'migrationComparison', value: afterVersion, othervalue: beforeVersion, version,
74
+ });
74
75
  throwWithAnyError();
75
76
  }
76
77
  }
@@ -18,7 +18,6 @@ const { availableBetaFlags } = require('./base/model');
18
18
  const optionProcessor = createOptionProcessor();
19
19
 
20
20
  // General options
21
- // FIXME: Since they mainly affect the compiler, they could also live near main.compile
22
21
  optionProcessor
23
22
  .option('-h, --help')
24
23
  .option('-v, --version')
@@ -40,7 +39,6 @@ optionProcessor
40
39
  .option(' --debug <id-list>')
41
40
  .option('-E, --enrich-csn')
42
41
  .option('-R, --raw-output <name>')
43
- .option(' --new-parser')
44
42
  .option(' --internal-msg')
45
43
  .option(' --beta-mode')
46
44
  .option(' --beta <list>')
@@ -51,6 +49,7 @@ optionProcessor
51
49
  .option(' --test-mode')
52
50
  .option(' --test-sort-csn')
53
51
  .option(' --doc-comment')
52
+ .option(' --propagate-doc-comments')
54
53
  .option(' --add-texts-language-assoc')
55
54
  .option(' --localized-without-coalesce')
56
55
  .option(' --tenant-discriminator')
@@ -119,7 +118,6 @@ optionProcessor
119
118
  with name = "+", write complete XSN, long!
120
119
  --tenant-discriminator Add tenant fields to entities
121
120
  --internal-msg Write raw messages with call stack to <stdout>/<stderr>
122
- --new-parser Use the new CDL parser
123
121
  --beta-mode Enable all unsupported, incomplete (beta) features
124
122
  --beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
125
123
  Valid values are:
@@ -130,8 +128,6 @@ optionProcessor
130
128
  .join(`\n${ ' '.repeat(30) }`)
131
129
  }
132
130
  --deprecated <list> Comma separated list of deprecated options.
133
- Valid values are:
134
- eagerPersistenceForGeneratedEntities
135
131
  --fallback-parser <type> If the language cannot be deduced by the file's extensions, use this
136
132
  parser as a fallback. Valid values are:
137
133
  cdl : Use CDL parser
@@ -153,6 +149,7 @@ optionProcessor
153
149
  OData CSN, CDL order and more. When --test-mode is enabled, this
154
150
  option is implicitly enabled as well.
155
151
  --doc-comment Preserve /** */ comments at annotation positions as doc property in CSN
152
+ --propagate-doc-comments Propagate doc comments ('--doc-comment')
156
153
  --add-texts-language-assoc In generated texts entities, add association "language"
157
154
  to "sap.common.Languages" if it exists
158
155
  --localized-without-coalesce Omit coalesce in localized convenience views
@@ -163,7 +160,7 @@ optionProcessor
163
160
  --skip-name-check Skip certain name checks, e.g. that there must be no '.' in element names.
164
161
 
165
162
  Commands
166
- H, toHana [options] <files...> Generate HANA CDS source files
163
+ H, toHana [options] <files...> (deprecated) Generate HANA CDS source files
167
164
  O, toOdata [options] <files...> Generate ODATA metadata and annotations
168
165
  C, toCdl <files...> Generate CDS source files
169
166
  Q, toSql [options] <files...> Generate SQL DDL statements
@@ -204,10 +201,14 @@ optionProcessor.command('H, toHana')
204
201
  .option(' --assert-integrity-type <type>', { valid: [ 'RT', 'DB' ], ignoreCase: true })
205
202
  .option(' --pre2134ReferentialConstraintNames')
206
203
  .option(' --disable-hana-comments')
207
- .option(' --standard-database-functions')
204
+ .option(' --no-standard-database-functions')
208
205
  .help(`
209
206
  Usage: cdsc toHana [options] <files...>
210
207
 
208
+ ====================================================
209
+ DEPRECATED! Since v5, this backend is deprecated!
210
+ ====================================================
211
+
211
212
  Generate HANA CDS source files, or CSN.
212
213
 
213
214
  Options
@@ -241,7 +242,7 @@ optionProcessor.command('H, toHana')
241
242
  DB : Create database constraints for associations
242
243
  --pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
243
244
  --disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
244
- --standard-database-functions Enable rendering of standard database function mappings.
245
+ --no-standard-database-functions Disable rendering of standard database function mappings.
245
246
  `);
246
247
 
247
248
  optionProcessor.command('O, toOdata')
@@ -294,7 +295,7 @@ optionProcessor.command('O, toOdata')
294
295
  { prefix: { alias, ns, uri }, ... }
295
296
  --odata-no-creator Omit creator identification in API
296
297
  -n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
297
- the corresponding database name (see "--sql-mapping" for "toHana or "toSql")
298
+ the corresponding database name (see "--sql-mapping" for "toSql")
298
299
  plain : (default) Names in uppercase and flattened with underscores
299
300
  quoted : Names in original case as in CDL. Entity names with dots,
300
301
  but element names flattened with underscores
@@ -319,8 +320,8 @@ optionProcessor.command('J, forJava')
319
320
 
320
321
  optionProcessor.command('C, toCdl')
321
322
  .option('-h, --help')
322
- .option(' --render-cdl-definition-nesting')
323
- .option(' --render-cdl-common-namespace')
323
+ .option(' --no-render-cdl-definition-nesting')
324
+ .option(' --no-render-cdl-common-namespace')
324
325
  .help(`
325
326
  Usage: cdsc toCdl [options] <files...>
326
327
 
@@ -328,10 +329,10 @@ optionProcessor.command('C, toCdl')
328
329
 
329
330
  Options
330
331
  -h, --help Show this help text
331
- --render-cdl-definition-nesting If set, definitions will be nested
332
+ --no-render-cdl-definition-nesting If set, definitions will be nested
332
333
  inside services/contexts instead of having only top-level
333
334
  definitions.
334
- --render-cdl-common-namespace If true and render-cdl-definition-nesting
335
+ --no-render-cdl-common-namespace If true and render-cdl-definition-nesting
335
336
  is set, a common namespace will be extracted and rendered.
336
337
  `);
337
338
 
@@ -355,9 +356,10 @@ optionProcessor.command('Q, toSql')
355
356
  .option(' --generated-by-comment')
356
357
  .option(' --better-sqlite-session-variables <bool>')
357
358
  .option(' --transitive-localized-views')
358
- .option(' --boolean-equality')
359
+ .option(' --no-boolean-equality')
359
360
  .option(' --with-hana-associations <bool>', { valid: [ 'true', 'false' ] })
360
- .option(' --standard-database-functions')
361
+ .option(' --no-standard-database-functions')
362
+ .option(' --dollar-now-as-timestamp')
361
363
  .help(`
362
364
  Usage: cdsc toSql [options] <files...>
363
365
 
@@ -420,8 +422,10 @@ optionProcessor.command('Q, toSql')
420
422
  Enable or disable rendering of "WITH ASSOCIATIONS" for sqlDialect 'hana'.
421
423
  true : (default) Render "WITH ASSOCIATIONS"
422
424
  false : Do not render "WITH ASSOCIATIONS"
423
- --standard-database-functions Enable rendering of standard database function mappings.
424
- --boolean-equality Enable support for boolean logic '!=' operator.
425
+ --no-standard-database-functions Disable rendering of standard database function mappings.
426
+ --no-boolean-equality Enable support for boolean logic '!=' operator.
427
+ --dollar-now-as-timestamp Render '$now' variable as 'CURRENT_TIMESTAMP' instead of 'SESSION_CONTEXT(…)'
428
+ for SQL dialect 'hana'.
425
429
  `);
426
430
 
427
431
  optionProcessor.command('toRename')
@@ -44,7 +44,6 @@ const extensionsCode = {
44
44
  service: 'extend service',
45
45
  };
46
46
 
47
- const PRECEDENCE_OF_IN_PREDICATE = 10;
48
47
  const PRECEDENCE_OF_EQUAL = 10;
49
48
 
50
49
  class AstBuildingParser extends BaseParser {
@@ -177,8 +176,6 @@ class AstBuildingParser extends BaseParser {
177
176
  const text = typeof keyword === 'string' ? keyword.toUpperCase() : type;
178
177
  const generic = this.dynamic_.generic?.[text];
179
178
  if (tryGenericIntro) {
180
- if (this.dynamic_.generic?.IN === 'separator')
181
- this.prec_ = PRECEDENCE_OF_IN_PREDICATE; // only expressions if `in` is separator
182
179
  if (generic !== 'expr')
183
180
  return (generic === 'intro') ? 'GenericIntro' : type;
184
181
  // if both intro and expr: specialFunctions[fn][argPos][token] = 'expr'
@@ -268,6 +265,15 @@ class AstBuildingParser extends BaseParser {
268
265
  this.precNone_( _test, prec );
269
266
  }
270
267
 
268
+ // TODO: as leanCondition ? `order` should probably appear in the message for
269
+ // test3/Compiler/GrammarRobustness/InvalidSelectInWhere.err.cds
270
+ orderByLimitRestriction( mode ) {
271
+ if (mode && (!this.$allowOrderByLimit || this.precPost_( mode, 0 )))
272
+ return true;
273
+ this.$allowOrderByLimit = !mode;
274
+ return false;
275
+ }
276
+
271
277
  isNamedArg() {
272
278
  const type = this.tokens[this.tokenIdx + 1]?.type;
273
279
  return type !== ':' && type !== '=>';
@@ -569,13 +575,27 @@ class AstBuildingParser extends BaseParser {
569
575
 
570
576
  noAssignmentInSameLine() {
571
577
  const next = this.la();
572
- if (next.text === '@' && next.line <= this.lb().endLine) {
578
+ if (next.text === '@' && next.location.line <= this.lb().location.endLine &&
579
+ // TODO Tool Runtime: it is probably better to skip tokens directly
580
+ // do not report error if the '@' is not correct:
581
+ this.s !== null && this.tokenIdx > this.recoverTokenIdx) {
573
582
  this.warning( 'syntax-missing-semicolon', next, { code: ';' },
574
583
  // eslint-disable-next-line @stylistic/js/max-len
575
584
  'Add a $(CODE) and/or newline before the annotation assignment to indicate that it belongs to the next statement' );
576
585
  }
577
586
  }
578
587
 
588
+ reportDubiousAnnoSpacing() {
589
+ const at = this.lb();
590
+ const before = this.tokens[this.tokenIdx - 2];
591
+ if (before?.type === 'Id' && before.location.endLine === at.location.line &&
592
+ before.location.endCol === at.location.col) {
593
+ this.warning( 'syntax-expecting-anno-space', at.location, { code: '@' },
594
+ 'Expecting a space before the $(CODE) starting an annotation assignment' );
595
+ }
596
+ this.reportUnexpectedSpace( at );
597
+ }
598
+
579
599
  // For :param, #variant, #symbol, @(…) and @Begin and `@` inside annotation paths,
580
600
  // inside `.*` and `.{`
581
601
  reportUnexpectedSpace( prefix = this.lb(),
@@ -749,8 +769,23 @@ class AstBuildingParser extends BaseParser {
749
769
  return ast;
750
770
  }
751
771
 
772
+ virtualOrImplicit( art ) {
773
+ const token = this.lb();
774
+ const ref = art.value.func || art.value;
775
+ if (!art.virtual ||
776
+ ref.path[0].location.tokenIndex < token.location.tokenIndex ||
777
+ this.la().text === '{') {
778
+ this.classifyImplicitName( 'ItemImplicit', ref );
779
+ }
780
+ else {
781
+ token.parsedAs = 'ItemAlias';
782
+ art.name = ref.path[0];
783
+ art.value = undefined;
784
+ }
785
+ }
786
+
752
787
  classifyImplicitName( category, ref ) {
753
- if (!ref || ref.path) {
788
+ if (!ref || ref.path) { // TODO: func
754
789
  const tokenIndex = ref?.path.at(-1)?.location.tokenIndex;
755
790
  const token = this.prevTokenWithIndex( tokenIndex ) ?? this.tokens[this.tokenIdx - 1];
756
791
  const { parsedAs } = token;
@@ -1043,10 +1078,8 @@ class AstBuildingParser extends BaseParser {
1043
1078
  line: chosen.location.line,
1044
1079
  col: chosen.location.col,
1045
1080
  };
1046
- if (erroneous.val === chosen.val) {
1047
- // TODO v6: duplicate clause = error, independently whether it is the same
1048
- this.warning( 'syntax-duplicate-equal-clause', erroneous.location, args );
1049
- }
1081
+ if (erroneous.val === chosen.val)
1082
+ this.message( 'syntax-duplicate-equal-clause', erroneous.location, args );
1050
1083
  // TODO extra msg text 'syntax-duplicate-clause' for noRepeatedCardinality()
1051
1084
  }
1052
1085
 
@@ -1169,6 +1202,21 @@ class AstBuildingParser extends BaseParser {
1169
1202
  };
1170
1203
  }
1171
1204
 
1205
+ valuePathAstWithNew( expr, path ) {
1206
+ path = this.valuePathAst( path );
1207
+ if (path.op?.val !== 'ixpr') {
1208
+ expr.args.push( path );
1209
+ }
1210
+ else {
1211
+ const ref = path.args[0];
1212
+ const op = { val: 'ixpr', location: expr.args[0].location };
1213
+ const location = this.combineLocation( expr.args[0], ref );
1214
+ path.args[0] = { op, args: [ expr.args[0], ref ], location };
1215
+ expr.args = path.args;
1216
+ }
1217
+ this.attachLocation( expr );
1218
+ }
1219
+
1172
1220
  valuePathAst( ref ) {
1173
1221
  // TODO: XSN representation of functions is a bit strange - rework
1174
1222
  // TODO: rework this function
@@ -1189,8 +1237,12 @@ class AstBuildingParser extends BaseParser {
1189
1237
  if (filter) // TODO v7: make this be reported via guard, as error
1190
1238
  this.message( 'syntax-unexpected-filter', filter.location, {} );
1191
1239
  // TODO: XSN representation of functions is a bit strange - rework
1192
- const op = { location, val: 'call' };
1193
- return this.attachLocation( { op, func: ref, args } );
1240
+ return this.attachLocation( {
1241
+ op: { location, val: 'call' },
1242
+ func: ref,
1243
+ args,
1244
+ location: ref.location,
1245
+ } );
1194
1246
  }
1195
1247
 
1196
1248
  // $syntax === ':' => path(P: 1)
@@ -9,7 +9,7 @@
9
9
 
10
10
  const prefixOperators = { // see <prec=…,prefix> in `expression` of CdlGrammar
11
11
  __proto__: null,
12
- new: 33, // special in CDL (only before ref), clarify with `.`
12
+ new: 39, // special in CDL (only before ref)
13
13
  exists: 33, // special in CDL
14
14
  '+': 30, // note: binary `.` and `over` have higher precedence!
15
15
  '-': 30,
@@ -85,10 +85,54 @@ class XprTree {
85
85
  this.location = location;
86
86
  }
87
87
 
88
+ splitClauses() {
89
+ const { length } = this.args;
90
+ if (length < 3)
91
+ return this.args;
92
+ const args = [];
93
+ let idx = 0;
94
+ while (idx < length) {
95
+ if (this.isToken( idx + 1, 'by' ) &&
96
+ this.isToken( idx, 'partition' ) || this.isToken( idx, 'order' )) {
97
+ this.pushSection( args, idx );
98
+ args.push( this.args[idx++] );
99
+ args.push( this.args[idx++] );
100
+ this.nodeIdx = idx;
101
+ }
102
+ else if (this.isToken( idx, 'rows' )) {
103
+ this.pushSection( args, idx );
104
+ args.push( this.args[idx++] );
105
+ this.nodeIdx = idx;
106
+ }
107
+ else {
108
+ ++idx;
109
+ }
110
+ }
111
+ if (!args.length)
112
+ return this.args;
113
+ this.pushSection( args, idx );
114
+ return args;
115
+ }
116
+
117
+ isToken( idx, keyword ) {
118
+ const tok = this.args[idx];
119
+ return tok && (this.location === true)
120
+ ? keyword === tok
121
+ : keyword === tok?.val && tok.literal === 'token';
122
+ }
123
+
124
+ pushSection( args, idx ) {
125
+ if (idx > this.nodeIdx) {
126
+ args.push( (idx > this.nodeIdx + 1)
127
+ ? this.create( this.args.slice( this.nodeIdx, idx ) )
128
+ : this.args[this.nodeIdx] );
129
+ this.nodeIdx = idx;
130
+ }
131
+ }
132
+
88
133
  tree() {
89
134
  const args = [];
90
135
  const { length } = this.args;
91
- // TODO: orderByClauseAsXpr, overClause
92
136
  while (this.nodeIdx < length) {
93
137
  const expr = this.expression( -1 );
94
138
  if (expr)
@@ -210,6 +254,16 @@ class XprTree {
210
254
  }
211
255
  }
212
256
 
257
+ function xprAsTree( nodes, args, location ) {
258
+ return (new XprTree( nodes, args, location )).tree();
259
+ }
260
+
261
+ function splitClauses( tree, isCsn ) {
262
+ return (new XprTree( null, tree, !!isCsn || tree.location )).splitClauses();
263
+ }
264
+
213
265
  module.exports = {
214
- xprAsTree: ( nodes, args, location ) => (new XprTree( nodes, args, location )).tree(),
266
+ xsnAsTree: xprAsTree,
267
+ csnAsTree: nodes => xprAsTree( nodes, nodes, true ),
268
+ splitClauses,
215
269
  };
@@ -14,7 +14,7 @@ const functionsWithoutParentheses = [
14
14
  ];
15
15
 
16
16
  // CDL reserved keywords, used for automatic quoting in 'toCdl' renderer
17
- // Keep in sync with reserved keywords in language.g4
17
+ // TODO: Use `parser.keywords` from our generated CdlParser.js (#13856)
18
18
  const cdlKeywords = [
19
19
  'ALL',
20
20
  'ANY',
@@ -7,7 +7,6 @@ const { CompilerAssertion } = require( '../base/error' );
7
7
  const { createMessageFunctions } = require( '../base/messages' );
8
8
  const { XsnSource } = require('../compiler/xsn-model');
9
9
 
10
- const parseWithAntlr = lazyload('../language/antlrParser');
11
10
  const CdlLexer = require( './Lexer' );
12
11
  const gen = lazyload( '../gen/CdlParser' );
13
12
 
@@ -21,8 +20,6 @@ function parseCdl( source, filename = '<undefined>.cds',
21
20
  options = {}, messageFunctions = null,
22
21
  rule = 'cdl' ) {
23
22
  const rulespec = rules[rule];
24
- if (!options.newParser && !options.newparser)
25
- return parseWithAntlr( source, filename, options, messageFunctions, rulespec );
26
23
  const { CdlParser } = gen;
27
24
  if (CdlParser.tracingParser) // tracing → direct console output of message
28
25
  messageFunctions = createMessageFunctions( {}, 'parse', {} );
@@ -100,13 +100,13 @@ function manageConstraint( constraint, csn, options, indent, quoteSqlId ) {
100
100
  if (options.src === 'hdi' && !options.drop)
101
101
  return renderedConstraint;
102
102
  let alterTableStatement = '';
103
- alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.sqlMapping, constraint.dependentTable))}`;
103
+ alterTableStatement += `${ indent }ALTER TABLE ${ quoteSqlId(getResultingName(csn, options.sqlMapping, constraint.dependentTable)) }`;
104
104
  if (renderAlterConstraintStatement)
105
- alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;
105
+ alterTableStatement += `\n${ indent }ALTER ${ renderedConstraint };`;
106
106
  else if (options.drop)
107
- alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
107
+ alterTableStatement += `${ indent } DROP CONSTRAINT ${ quoteSqlId(constraint.identifier) };`;
108
108
  else
109
- alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
109
+ alterTableStatement += `\n${ indent }ADD ${ renderedConstraint };`;
110
110
 
111
111
  return alterTableStatement;
112
112
  }
@@ -123,29 +123,29 @@ function listReferentialIntegrityViolations( csn, options ) {
123
123
  const referentialConstraints = getListOfAllConstraints(csn);
124
124
  const resultArtifacts = {};
125
125
  const indent = ' ';
126
- const increaseIndent = str => ` ${str}`;
126
+ const increaseIndent = str => ` ${ str }`;
127
127
  // helper function to reduce parent key / foreign key array to a comma separated string which can be used in a select clause
128
- const keyStringReducer = prefix => (prev, curr, index) => (index > 0 ? `${prev},\n${curr} AS "${prefix}:${curr}"` : prev);
128
+ const keyStringReducer = prefix => (prev, curr, index) => (index > 0 ? `${ prev },\n${ curr } AS "${ prefix }:${ curr }"` : prev);
129
129
  // helper function to reduce the parent key / foreign key arrays of a referential constraint to a join list which can be used in a where clause
130
130
  const joinPkWithFkReducer = (constraint, subQueryAlias, mainQueryAlias) => (prev, curr, index) => (index > 0
131
- ? `${prev} AND
132
- ${increaseIndent(indent)}"${mainQueryAlias}".${quoteSqlId(constraint.foreignKey[index])} = ${subQueryAlias}.${quoteSqlId(constraint.parentKey[index])}`
131
+ ? `${ prev } AND
132
+ ${ increaseIndent(indent) }"${ mainQueryAlias }".${ quoteSqlId(constraint.foreignKey[index]) } = ${ subQueryAlias }.${ quoteSqlId(constraint.parentKey[index]) }`
133
133
  : increaseIndent(increaseIndent(indent)) + prev);
134
134
 
135
135
  Object.entries(referentialConstraints).forEach(([ identifier, constraint ], index) => {
136
136
  let selectViolations = 'SELECT\n';
137
137
  // this column indicates which SELECT revealed the integrity violation
138
138
  // and helps to identify the corrupted table
139
- selectViolations += `${index} as "SELECT-ID",\n`;
139
+ selectViolations += `${ index } as "SELECT-ID",\n`;
140
140
  // SELECT <primary_key>,
141
141
  const primaryKeyList = selectPrimaryKeyColumns(constraint);
142
142
  if (primaryKeyList)
143
- selectViolations += `${primaryKeyList},\n`;
143
+ selectViolations += `${ primaryKeyList },\n`;
144
144
  // ... <foreign_key>
145
145
  selectViolations += selectForeignKeyColumns(constraint);
146
- const mainQueryAlias = `MAIN_${index}`;
146
+ const mainQueryAlias = `MAIN_${ index }`;
147
147
  // ... FROM <dependent table> AS "${index}"
148
- selectViolations += `\nFROM ${quoteAndGetResultingName(constraint.dependentTable)} AS "${mainQueryAlias}"\n`;
148
+ selectViolations += `\nFROM ${ quoteAndGetResultingName(constraint.dependentTable) } AS "${ mainQueryAlias }"\n`;
149
149
  // ... WHERE NOT (<(part of) foreign key is null>)
150
150
  selectViolations += whereNotForeignKeyIsNull(constraint);
151
151
  /*
@@ -173,7 +173,7 @@ function listReferentialIntegrityViolations( csn, options ) {
173
173
  // if no primary key is set in the table
174
174
  if (primaryKeyOfDependentTable.length === 0)
175
175
  return '';
176
- return primaryKeyOfDependentTable.reduce(pkReducer, `${quoteSqlId(primaryKeyOfDependentTable[0])} AS "K:${primaryKeyOfDependentTable[0]}"`);
176
+ return primaryKeyOfDependentTable.reduce(pkReducer, `${ quoteSqlId(primaryKeyOfDependentTable[0]) } AS "K:${ primaryKeyOfDependentTable[0] }"`);
177
177
  }
178
178
 
179
179
  /**
@@ -184,7 +184,7 @@ function listReferentialIntegrityViolations( csn, options ) {
184
184
  */
185
185
  function selectForeignKeyColumns( constraint ) {
186
186
  const fkReducer = keyStringReducer('FK');
187
- return constraint.foreignKey.reduce(fkReducer, `${quoteSqlId(constraint.foreignKey[0])} AS "FK:${constraint.foreignKey[0]}"`);
187
+ return constraint.foreignKey.reduce(fkReducer, `${ quoteSqlId(constraint.foreignKey[0]) } AS "FK:${ constraint.foreignKey[0] }"`);
188
188
  }
189
189
 
190
190
  /**
@@ -194,14 +194,14 @@ function listReferentialIntegrityViolations( csn, options ) {
194
194
  * @returns WHERE NOT ( <foreign_key IS NULL ... ) statement
195
195
  */
196
196
  function whereNotForeignKeyIsNull( constraint ) {
197
- let whereNot = `${indent}WHERE NOT (\n`;
197
+ let whereNot = `${ indent }WHERE NOT (\n`;
198
198
  whereNot += constraint.foreignKey
199
199
  .reduce((prev, curr, index) => {
200
200
  if (index > 0)
201
- return `${prev} OR \n${increaseIndent(indent)}${quoteSqlId(curr)} IS NULL`;
201
+ return `${ prev } OR \n${ increaseIndent(indent) }${ quoteSqlId(curr) } IS NULL`;
202
202
  return increaseIndent(indent) + prev;
203
- }, `${quoteSqlId(constraint.foreignKey[0])} IS NULL`);
204
- whereNot += `\n${indent})`;
203
+ }, `${ quoteSqlId(constraint.foreignKey[0]) } IS NULL`);
204
+ whereNot += `\n${ indent })`;
205
205
  return whereNot;
206
206
  }
207
207
 
@@ -213,19 +213,19 @@ function listReferentialIntegrityViolations( csn, options ) {
213
213
  * @returns AND NOT EXISTS ( SELECT * FROM <parent_table> WHERE <dependent_table>.<foreign_key> = <parent_table>.<parent_key> ) statement
214
214
  */
215
215
  function andNoMatchingPrimaryKeyExists( constraint, mainQueryAlias ) {
216
- let andNotExists = `\n${indent}AND NOT EXISTS (\n`;
217
- andNotExists += `${increaseIndent(indent)}SELECT * FROM ${quoteAndGetResultingName(constraint.parentTable)}`;
216
+ let andNotExists = `\n${ indent }AND NOT EXISTS (\n`;
217
+ andNotExists += `${ increaseIndent(indent) }SELECT * FROM ${ quoteAndGetResultingName(constraint.parentTable) }`;
218
218
  // add an alias to both queries so that they can be distinguished at all times
219
219
  const subQueryAlias = '"SUB"';
220
- andNotExists += ` AS ${subQueryAlias}`;
220
+ andNotExists += ` AS ${ subQueryAlias }`;
221
221
  andNotExists += '\n';
222
222
  const joinListReducer = joinPkWithFkReducer(constraint, subQueryAlias, mainQueryAlias);
223
- andNotExists += `${increaseIndent(indent)}WHERE (\n`;
223
+ andNotExists += `${ increaseIndent(indent) }WHERE (\n`;
224
224
  andNotExists += constraint.foreignKey
225
225
  .reduce(joinListReducer,
226
- `"${mainQueryAlias}".${quoteSqlId(constraint.foreignKey[0])} = ${subQueryAlias}.${quoteSqlId(constraint.parentKey[0])}`);
227
- andNotExists += `\n${increaseIndent(indent)})`;
228
- andNotExists += `\n${indent});`;
226
+ `"${ mainQueryAlias }".${ quoteSqlId(constraint.foreignKey[0]) } = ${ subQueryAlias }.${ quoteSqlId(constraint.parentKey[0]) }`);
227
+ andNotExists += `\n${ increaseIndent(indent) })`;
228
+ andNotExists += `\n${ indent });`;
229
229
  return andNotExists;
230
230
  }
231
231