@sap/cds-compiler 5.9.4 → 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.
- package/CHANGELOG.md +104 -319
- package/README.md +1 -1
- package/bin/cds_update_identifiers.js +3 -5
- package/bin/cdsc.js +22 -8
- package/bin/cdshi.js +1 -1
- package/bin/cdsse.js +4 -4
- package/doc/CHANGELOG_BETA.md +11 -0
- package/doc/CHANGELOG_DEPRECATED.md +29 -0
- package/lib/api/main.js +8 -5
- package/lib/api/options.js +12 -10
- package/lib/base/builtins.js +1 -0
- package/lib/base/message-registry.js +190 -96
- package/lib/base/messages.js +29 -20
- package/lib/base/model.js +14 -24
- package/lib/checks/actionsFunctions.js +10 -20
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/elements.js +30 -10
- package/lib/checks/enums.js +31 -0
- package/lib/checks/foreignKeys.js +2 -2
- package/lib/checks/hasPersistedElements.js +5 -0
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/managedWithoutKeys.js +5 -4
- package/lib/checks/queryNoDbArtifacts.js +10 -8
- package/lib/checks/types.js +5 -5
- package/lib/checks/validator.js +6 -4
- package/lib/compiler/assert-consistency.js +12 -9
- package/lib/compiler/checks.js +18 -50
- package/lib/compiler/define.js +6 -6
- package/lib/compiler/extend.js +2 -1
- package/lib/compiler/generate.js +14 -17
- package/lib/compiler/populate.js +8 -31
- package/lib/compiler/propagator.js +21 -35
- package/lib/compiler/resolve.js +35 -22
- package/lib/compiler/shared.js +7 -1
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +1 -1
- package/lib/edm/annotations/edmJson.js +20 -15
- package/lib/edm/annotations/genericTranslation.js +7 -8
- package/lib/edm/csn2edm.js +46 -50
- package/lib/edm/edm.js +8 -7
- package/lib/edm/edmPreprocessor.js +33 -83
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +55 -44
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1133 -1150
- package/lib/json/from-csn.js +70 -43
- package/lib/json/to-csn.js +6 -8
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/main.d.ts +58 -24
- package/lib/model/csnUtils.js +28 -39
- package/lib/model/xprAsTree.js +23 -9
- package/lib/modelCompare/compare.js +5 -4
- package/lib/optionProcessor.js +21 -17
- package/lib/parsers/AstBuildingParser.js +63 -11
- package/lib/parsers/XprTree.js +57 -3
- package/lib/parsers/identifiers.js +1 -1
- package/lib/parsers/index.js +0 -3
- package/lib/render/manageConstraints.js +25 -25
- package/lib/render/toCdl.js +173 -170
- package/lib/render/toHdbcds.js +126 -128
- package/lib/render/toRename.js +7 -7
- package/lib/render/toSql.js +128 -125
- package/lib/render/utils/common.js +47 -22
- package/lib/render/utils/delta.js +25 -25
- package/lib/render/utils/operators.js +2 -2
- package/lib/render/utils/pretty.js +5 -5
- package/lib/render/utils/sql.js +13 -13
- package/lib/render/utils/standardDatabaseFunctions.js +115 -103
- package/lib/render/utils/unique.js +4 -4
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +2 -2
- package/lib/transform/db/associations.js +6 -7
- package/lib/transform/db/assocsToQueries/utils.js +4 -5
- package/lib/transform/db/backlinks.js +12 -9
- package/lib/transform/db/cdsPersistence.js +8 -7
- package/lib/transform/db/constraints.js +13 -10
- package/lib/transform/db/expansion.js +7 -3
- package/lib/transform/db/flattening.js +4 -14
- package/lib/transform/db/processSqlServices.js +2 -1
- package/lib/transform/db/temporal.js +5 -7
- package/lib/transform/db/views.js +2 -4
- package/lib/transform/draft/db.js +8 -8
- package/lib/transform/draft/odata.js +10 -7
- package/lib/transform/forOdata.js +10 -5
- package/lib/transform/forRelationalDB.js +5 -75
- package/lib/transform/localized.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +11 -10
- package/lib/transform/odata/flattening.js +8 -4
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/transformUtils.js +4 -8
- package/lib/transform/translateAssocsToJoins.js +14 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
- package/lib/utils/objectUtils.js +0 -17
- package/package.json +10 -13
- package/share/messages/def-upcoming-virtual-change.md +1 -1
- package/LICENSE +0 -37
- package/bin/cds_remove_invalid_whitespace.js +0 -138
- package/doc/CHANGELOG_ARCHIVE.md +0 -3604
- package/lib/gen/genericAntlrParser.js +0 -3
- package/lib/gen/language.checksum +0 -1
- package/lib/gen/language.interp +0 -456
- package/lib/gen/language.tokens +0 -180
- package/lib/gen/languageLexer.interp +0 -439
- package/lib/gen/languageLexer.js +0 -1483
- package/lib/gen/languageLexer.tokens +0 -167
- package/lib/gen/languageParser.js +0 -24941
- package/lib/language/antlrParser.js +0 -205
- package/lib/language/errorStrategy.js +0 -646
- package/lib/language/genericAntlrParser.js +0 -1572
- package/lib/parsers/CdlGrammar.g4 +0 -2070
package/lib/model/xprAsTree.js
CHANGED
|
@@ -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
|
|
15
|
+
const { csnAsTree, splitClauses } = require( '../parsers/XprTree' );
|
|
15
16
|
|
|
16
17
|
function conditionAsTree( args ) {
|
|
17
18
|
args.forEach( expressionAsTree );
|
|
18
|
-
return
|
|
19
|
+
return asTree( args );
|
|
19
20
|
}
|
|
20
21
|
function condAsTree( args ) {
|
|
21
22
|
args.forEach( exprAsTree );
|
|
22
|
-
return
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
73
|
-
|
|
72
|
+
error('api-invalid-version', null, {
|
|
73
|
+
'#': 'migrationComparison', value: afterVersion, othervalue: beforeVersion, version,
|
|
74
|
+
});
|
|
74
75
|
throwWithAnyError();
|
|
75
76
|
}
|
|
76
77
|
}
|
package/lib/optionProcessor.js
CHANGED
|
@@ -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
|
|
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 "
|
|
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
|
|
424
|
-
--boolean-equality
|
|
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
|
-
|
|
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
|
-
|
|
1193
|
-
|
|
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)
|
package/lib/parsers/XprTree.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const prefixOperators = { // see <prec=…,prefix> in `expression` of CdlGrammar
|
|
11
11
|
__proto__: null,
|
|
12
|
-
new:
|
|
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
|
-
|
|
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
|
-
//
|
|
17
|
+
// TODO: Use `parser.keywords` from our generated CdlParser.js (#13856)
|
|
18
18
|
const cdlKeywords = [
|
|
19
19
|
'ALL',
|
|
20
20
|
'ANY',
|
package/lib/parsers/index.js
CHANGED
|
@@ -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
|
|