@sap/cds-compiler 5.4.2 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -1
- package/bin/cds_remove_invalid_whitespace.js +4 -4
- package/bin/cds_update_annotations.js +3 -3
- package/bin/cds_update_identifiers.js +3 -3
- package/lib/api/main.js +18 -30
- package/lib/api/validate.js +6 -1
- package/lib/base/lazyload.js +28 -0
- package/lib/base/location.js +1 -0
- package/lib/base/message-registry.js +53 -11
- package/lib/base/messages.js +17 -3
- package/lib/checks/{dbFeatureFlags.js → featureFlags.js} +1 -1
- package/lib/checks/parameters.js +61 -4
- package/lib/checks/validator.js +14 -6
- package/lib/compiler/index.js +7 -7
- package/lib/compiler/shared.js +29 -13
- package/lib/gen/BaseParser.js +345 -235
- package/lib/gen/CdlParser.js +4434 -4492
- package/lib/gen/Dictionary.json +2 -2
- package/lib/json/to-csn.js +3 -1
- package/lib/language/antlrParser.js +2 -111
- package/lib/main.js +16 -37
- package/lib/modelCompare/utils/filter.js +47 -21
- package/lib/parsers/AstBuildingParser.js +59 -49
- package/lib/parsers/CdlGrammar.g4 +91 -130
- package/lib/parsers/index.js +123 -0
- package/lib/render/toSql.js +8 -2
- package/lib/render/utils/delta.js +33 -1
- package/lib/transform/db/{transformExists.js → assocsToQueries/transformExists.js} +12 -407
- package/lib/transform/db/assocsToQueries/utils.js +440 -0
- package/lib/transform/db/expansion.js +2 -2
- package/lib/transform/draft/db.js +14 -3
- package/lib/transform/effective/annotations.js +3 -3
- package/lib/transform/effective/main.js +5 -7
- package/lib/transform/featureFlags.js +5 -0
- package/lib/transform/forRelationalDB.js +125 -192
- package/lib/transform/odata/createForeignKeys.js +1 -1
- package/lib/transform/odata/flattening.js +1 -1
- package/lib/transform/transformUtils.js +0 -51
- package/package.json +2 -2
- package/lib/transform/db/featureFlags.js +0 -5
package/lib/gen/Dictionary.json
CHANGED
|
@@ -1089,8 +1089,7 @@
|
|
|
1089
1089
|
"Type": "Core.Tag",
|
|
1090
1090
|
"AppliesTo": [
|
|
1091
1091
|
"EntityContainer"
|
|
1092
|
-
]
|
|
1093
|
-
"$experimental": true
|
|
1092
|
+
]
|
|
1094
1093
|
},
|
|
1095
1094
|
"Common.mediaUploadLink": {
|
|
1096
1095
|
"Type": "Edm.String",
|
|
@@ -4475,6 +4474,7 @@
|
|
|
4475
4474
|
"$kind": "ComplexType",
|
|
4476
4475
|
"BaseType": "UI.DataFieldAbstract",
|
|
4477
4476
|
"Properties": {
|
|
4477
|
+
"ID": "Edm.String",
|
|
4478
4478
|
"Actions": "Collection(UI.DataFieldForActionAbstract)",
|
|
4479
4479
|
"Label": "Edm.String",
|
|
4480
4480
|
"Criticality": "UI.CriticalityType",
|
package/lib/json/to-csn.js
CHANGED
|
@@ -1202,7 +1202,9 @@ function exprInternal( node, xprParens ) {
|
|
|
1202
1202
|
}
|
|
1203
1203
|
if (node.path) {
|
|
1204
1204
|
const ref = node.path.map( pathItem );
|
|
1205
|
-
|
|
1205
|
+
// auto-corrected ORDER BY refs without table alias, or EXTEND … WITH COLUMN
|
|
1206
|
+
// refs to source element shadowed by alias name:
|
|
1207
|
+
if (node.path.$prefix)
|
|
1206
1208
|
ref.unshift( node.path.$prefix );
|
|
1207
1209
|
// we would need to consider node.global here if we introduce that
|
|
1208
1210
|
return extra( { ref }, node );
|
|
@@ -17,11 +17,6 @@ const { XsnSource } = require('../compiler/xsn-model');
|
|
|
17
17
|
const Parser = require('../gen/languageParser').default;
|
|
18
18
|
const Lexer = require('../gen/languageLexer').default;
|
|
19
19
|
|
|
20
|
-
const CdlLexer = require( '../parsers/Lexer' );
|
|
21
|
-
const CdlParser = require( '../gen/CdlParser' );
|
|
22
|
-
const { createMessageFunctions } = require( '../base/messages' );
|
|
23
|
-
const { CompilerAssertion } = require( '../base/error' );
|
|
24
|
-
|
|
25
20
|
// Error listener used for ANTLR4-generated parser
|
|
26
21
|
class ErrorListener extends antlr4.error.ErrorListener {
|
|
27
22
|
// method which is called by generated parser with --trace-parser[-amg]:
|
|
@@ -125,18 +120,7 @@ function tokenTypeOf( recognizer, literalName ) {
|
|
|
125
120
|
// the AST locations and error messages. If provided, `options` are compile
|
|
126
121
|
// options.
|
|
127
122
|
|
|
128
|
-
|
|
129
|
-
cdl: { func: 'start', returns: 'source', $frontend: 'cdl' },
|
|
130
|
-
query: { func: 'queryEOF', returns: 'query' },
|
|
131
|
-
expr: { func: 'conditionEOF', returns: 'cond' }, // yes, condition
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
function parse( source, filename = '<undefined>.cds',
|
|
135
|
-
options = {}, messageFunctions = null,
|
|
136
|
-
rule = 'cdl' ) {
|
|
137
|
-
if (options.newParser)
|
|
138
|
-
return parseWithNewParser( source, filename, options, messageFunctions, rule );
|
|
139
|
-
|
|
123
|
+
function parse( source, filename, options, messageFunctions, rulespec ) {
|
|
140
124
|
const lexer = new Lexer( new antlr4.InputStream(source) );
|
|
141
125
|
const tokenStream = new RewriteTypeTokenStream(lexer);
|
|
142
126
|
/** @type {object} */
|
|
@@ -179,10 +163,9 @@ function parse( source, filename = '<undefined>.cds',
|
|
|
179
163
|
parser.addParseListener(options.parseListener);
|
|
180
164
|
|
|
181
165
|
|
|
182
|
-
const rulespec = rules[rule];
|
|
183
166
|
let tree;
|
|
184
167
|
try {
|
|
185
|
-
tree =
|
|
168
|
+
tree = parser[rulespec.func]();
|
|
186
169
|
}
|
|
187
170
|
catch (e) {
|
|
188
171
|
if (e instanceof RangeError && e.message.match(/Maximum.*exceeded$/i)) {
|
|
@@ -219,96 +202,4 @@ function parse( source, filename = '<undefined>.cds',
|
|
|
219
202
|
return ast;
|
|
220
203
|
}
|
|
221
204
|
|
|
222
|
-
function parseWithNewParser( source, filename, options, messageFunctions, rule ) {
|
|
223
|
-
if (CdlParser.tracingParser) // tracing → direct console output of message
|
|
224
|
-
messageFunctions = createMessageFunctions( {}, 'parse', {} );
|
|
225
|
-
const lexer = new CdlLexer( filename, source );
|
|
226
|
-
const parser = new CdlParser( lexer, options, messageFunctions ).init();
|
|
227
|
-
parser.filename = filename; // LSP compatibility
|
|
228
|
-
|
|
229
|
-
const { parseListener, attachTokens } = options;
|
|
230
|
-
if (parseListener || attachTokens) {
|
|
231
|
-
const combined = [];
|
|
232
|
-
const { tokens, comments, docComments } = parser;
|
|
233
|
-
const length = tokens.length + comments.length + docComments.length;
|
|
234
|
-
let tokenIdx = 0;
|
|
235
|
-
let commentIdx = 0;
|
|
236
|
-
let docCommentIdx = 0;
|
|
237
|
-
for (let index = 0; index < length; ++index) {
|
|
238
|
-
if (tokens[tokenIdx].location.tokenIndex === index) // EOF has largest tokenIndex
|
|
239
|
-
combined.push( tokens[tokenIdx++] );
|
|
240
|
-
else if (comments[commentIdx]?.location.tokenIndex === index)
|
|
241
|
-
combined.push( comments[commentIdx++] );
|
|
242
|
-
else
|
|
243
|
-
combined.push( docComments[docCommentIdx++] );
|
|
244
|
-
}
|
|
245
|
-
if (!combined.at( -1 ))
|
|
246
|
-
throw new CompilerAssertion( 'Invalid values for `tokenIndex`' );
|
|
247
|
-
for (const tok of combined)
|
|
248
|
-
tok.start = lexer.characterPos( tok.location.line, tok.location.col );
|
|
249
|
-
|
|
250
|
-
parser._input = { tokens: combined, lexer }; // lexer for characterPos() in cdshi.js
|
|
251
|
-
parser.getTokenStream = function getTokenStream() {
|
|
252
|
-
return this._input;
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
// LSP feature: provide parse listener with ANTLR-like context:
|
|
256
|
-
if (parseListener) {
|
|
257
|
-
// TODO LSP: we could also call different listener methods: then LSP could
|
|
258
|
-
// have dedicated methods for ANTLR-based and new parser
|
|
259
|
-
parser.rule_ = function rule_( ...args ) {
|
|
260
|
-
CdlParser.prototype.rule_.apply( this, args );
|
|
261
|
-
let state = this.s;
|
|
262
|
-
while (typeof this.table[--state] !== 'string')
|
|
263
|
-
;
|
|
264
|
-
const $ctx = { // TODO LSP: more to add?
|
|
265
|
-
parser: this, // set in generated ANTLR parser for each rule context
|
|
266
|
-
ruleName: this.table[state], // instead of ruleIndex
|
|
267
|
-
start: this.la(), // set in Parser#enterRule
|
|
268
|
-
stop: null,
|
|
269
|
-
};
|
|
270
|
-
parser.stack.at( -1 ).$ctx = $ctx;
|
|
271
|
-
parseListener.enterEveryRule( $ctx );
|
|
272
|
-
};
|
|
273
|
-
parser.exit_ = function exit_( ...args ) {
|
|
274
|
-
const { $ctx } = parser.stack.at( -1 );
|
|
275
|
-
// TODO: what should we do in case of errors?
|
|
276
|
-
$ctx.stop = this.lb();
|
|
277
|
-
parseListener.exitEveryRule( $ctx );
|
|
278
|
-
return CdlParser.prototype.exit_.apply( this, args );
|
|
279
|
-
};
|
|
280
|
-
parser.c = function c( ...args ) { // consume
|
|
281
|
-
const symbol = this.la();
|
|
282
|
-
const result = CdlParser.prototype.c.apply( this, args );
|
|
283
|
-
if (result)
|
|
284
|
-
parseListener.visitTerminal( { symbol } );
|
|
285
|
-
return result;
|
|
286
|
-
};
|
|
287
|
-
parser.skipToken_ = function skipToken_( ...args ) { // skip token in error recovery
|
|
288
|
-
const symbol = this.la();
|
|
289
|
-
CdlParser.prototype.skipToken_.apply( this, args ); // = `++this.tokenIdx`
|
|
290
|
-
parseListener.visitErrorNode( { symbol } );
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
const result = {};
|
|
294
|
-
const rulespec = rules[rule];
|
|
295
|
-
if (rulespec) {
|
|
296
|
-
try {
|
|
297
|
-
parser[rulespec.func]( result );
|
|
298
|
-
}
|
|
299
|
-
catch (e) {
|
|
300
|
-
if (!(e instanceof RangeError && /Maximum.*exceeded$/i.test( e.message )))
|
|
301
|
-
throw e;
|
|
302
|
-
messageFunctions.error('syntax-invalid-source', { file: filename },
|
|
303
|
-
{ '#': 'cdl-stackoverflow' } );
|
|
304
|
-
result[rulespec.returns] = undefined;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const ast = result[rulespec?.returns] || (rule === 'cdl' ? new XsnSource( 'cdl' ) : {} );
|
|
308
|
-
ast.options = options;
|
|
309
|
-
if (attachTokens === true || attachTokens === filename)
|
|
310
|
-
ast.tokenStream = parser._input;
|
|
311
|
-
return ast;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
205
|
module.exports = parse;
|
package/lib/main.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
// Main entry point for the CDS Compiler
|
|
1
|
+
// Main entry point for the CDS Compiler (API)
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// require('cdsv');
|
|
3
|
+
// Other NPM modules must not require any other files than this one.
|
|
5
4
|
|
|
6
5
|
// Proposed intra-module lib dependencies:
|
|
7
6
|
// - lib/base/<file>.js: can be required by all others, requires no other
|
|
8
|
-
// of this project
|
|
7
|
+
// of this project, except a lib/base/<other-file>.js
|
|
8
|
+
// - lib/util/<file>.js: TODO - clarify diff to lib/base/
|
|
9
9
|
// - lib/<dir>/<file>.js: can be required by other files lib/<dir>/,
|
|
10
|
-
// can require other files lib/<dir>/ and lib/base/<file>.js
|
|
10
|
+
// can require other files in lib/<dir>/ and lib/base/<file>.js,
|
|
11
|
+
// and lib/<other-dir>/ (the index.js in <other-dir>).
|
|
11
12
|
// - lib/main.js (this file): can be required by none in lib/ (only in
|
|
12
13
|
// bin/ and test/), can require any other
|
|
13
14
|
|
|
14
15
|
'use strict';
|
|
15
16
|
|
|
17
|
+
const lazyload = require('./base/lazyload')( module );
|
|
18
|
+
|
|
16
19
|
const { traceApi } = require('./api/trace');
|
|
20
|
+
|
|
17
21
|
const snapi = lazyload('./api/main');
|
|
18
22
|
const csnUtils = lazyload('./model/csnUtils');
|
|
19
23
|
const model_api = lazyload('./model/api');
|
|
@@ -22,7 +26,7 @@ const sqlIdentifier = lazyload('./sql-identifier');
|
|
|
22
26
|
const keywords = lazyload( './base/keywords' );
|
|
23
27
|
const toCdl = lazyload('./render/toCdl');
|
|
24
28
|
|
|
25
|
-
const
|
|
29
|
+
const parsers = lazyload('./parsers');
|
|
26
30
|
const compiler = lazyload('./compiler');
|
|
27
31
|
const shared = lazyload('./compiler/shared');
|
|
28
32
|
const define = lazyload('./compiler/define');
|
|
@@ -43,8 +47,8 @@ function parseCdl( cdlSource, filename, options = {} ) {
|
|
|
43
47
|
const messageFunctions = messages.createMessageFunctions( options, 'parse', model );
|
|
44
48
|
model.$messageFunctions = messageFunctions;
|
|
45
49
|
|
|
46
|
-
const xsn =
|
|
47
|
-
|
|
50
|
+
const xsn = parsers.parseCdl( cdlSource, filename, Object.assign( { parseOnly: true }, options ),
|
|
51
|
+
messageFunctions );
|
|
48
52
|
sources[filename] = xsn;
|
|
49
53
|
shared.fns( model );
|
|
50
54
|
define( model );
|
|
@@ -55,16 +59,16 @@ function parseCdl( cdlSource, filename, options = {} ) {
|
|
|
55
59
|
|
|
56
60
|
function parseCql( cdlSource, filename = '<query>.cds', options = {} ) {
|
|
57
61
|
const messageFunctions = messages.createMessageFunctions( options, 'parse' );
|
|
58
|
-
const xsn =
|
|
59
|
-
|
|
62
|
+
const xsn = parsers.parseCdl( cdlSource, filename, Object.assign( { parseOnly: true }, options ),
|
|
63
|
+
messageFunctions, 'query' );
|
|
60
64
|
messageFunctions.throwWithError();
|
|
61
65
|
return toCsn.compactQuery( xsn );
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
function parseExpr( cdlSource, filename = '<expr>.cds', options = {} ) {
|
|
65
69
|
const messageFunctions = messages.createMessageFunctions( options, 'parse' );
|
|
66
|
-
const xsn =
|
|
67
|
-
|
|
70
|
+
const xsn = parsers.parseCdl( cdlSource, filename, Object.assign( { parseOnly: true }, options ),
|
|
71
|
+
messageFunctions, 'expr' );
|
|
68
72
|
messageFunctions.throwWithError();
|
|
69
73
|
return toCsn.compactExpr( xsn );
|
|
70
74
|
}
|
|
@@ -191,28 +195,3 @@ module.exports = {
|
|
|
191
195
|
isInReservedNamespace: (...args) => builtins.isInReservedNamespace(...args),
|
|
192
196
|
},
|
|
193
197
|
};
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Load the module on-demand and not immediately.
|
|
197
|
-
*
|
|
198
|
-
* @param {string} moduleName Name of the module to load - like with require
|
|
199
|
-
* @returns {object} A Proxy that handles the on-demand loading
|
|
200
|
-
*/
|
|
201
|
-
function lazyload(moduleName) {
|
|
202
|
-
let module;
|
|
203
|
-
return new Proxy(((...args) => {
|
|
204
|
-
if (!module)
|
|
205
|
-
module = require(moduleName);
|
|
206
|
-
|
|
207
|
-
if (module.apply && typeof module.apply === 'function')
|
|
208
|
-
return module.apply(this, args);
|
|
209
|
-
return module; // for destructured calls
|
|
210
|
-
}), {
|
|
211
|
-
get(target, name) {
|
|
212
|
-
if (!module)
|
|
213
|
-
module = require(moduleName);
|
|
214
|
-
|
|
215
|
-
return module[name];
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
}
|
|
@@ -61,6 +61,7 @@ module.exports = {
|
|
|
61
61
|
function getFilterObject( options, dialect, extensionCallback, migrationCallback, removeConstraintsCallback, primaryKeyCallback ) {
|
|
62
62
|
const context = { hasLossyChanges: false };
|
|
63
63
|
const raiseErrorOrMarkAsLossy = getSafeguardManager(context, options);
|
|
64
|
+
const messageVariant = options.script ? 'script' : 'std';
|
|
64
65
|
return {
|
|
65
66
|
// will be called with a simple Array.filter, as we need to filter constraint `ADD` for SQLite
|
|
66
67
|
extension: ({
|
|
@@ -69,7 +70,7 @@ function getFilterObject( options, dialect, extensionCallback, migrationCallback
|
|
|
69
70
|
let returnValue = true;
|
|
70
71
|
forEach(elements, (name, element) => {
|
|
71
72
|
if (dialect !== 'sqlite' && isKey(element))
|
|
72
|
-
message('
|
|
73
|
+
message('migration-unsupported-key-change', [ 'definitions', extend, 'elements', name ], { id: name, '#': 'std' } );
|
|
73
74
|
else if (extensionCallback && !extensionCallback(extend, name, element, { error, warning }))
|
|
74
75
|
returnValue = false;
|
|
75
76
|
});
|
|
@@ -82,23 +83,31 @@ function getFilterObject( options, dialect, extensionCallback, migrationCallback
|
|
|
82
83
|
// will be called with a Array.forEach
|
|
83
84
|
migration: (migrations, { error, warning, message }) => {
|
|
84
85
|
forEach(migrations.remove, (name, migration) => {
|
|
85
|
-
raiseErrorOrMarkAsLossy(migration,
|
|
86
|
+
raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-element-drop', id => message(id, [ 'definitions', migrations.migrate, 'elements', name ], { '#': messageVariant }));
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
forEach(migrations.change, (name, migration) => {
|
|
89
90
|
const loc = [ 'definitions', migrations.migrate, 'elements', name ];
|
|
90
|
-
if (migration.new.type === migration.old.type && migration.new.length < migration.old.length)
|
|
91
|
-
raiseErrorOrMarkAsLossy(migration,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
else if (migration.new.type
|
|
97
|
-
raiseErrorOrMarkAsLossy(migration,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
if (migration.new.type === migration.old.type && migration.new.length < migration.old.length) {
|
|
92
|
+
raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-length-change', id => message(id, loc, { '#': messageVariant, id: name }));
|
|
93
|
+
}
|
|
94
|
+
else if (migration.new.type === migration.old.type && migration.new.scale !== migration.old.scale) {
|
|
95
|
+
raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-scale-change', id => message(id, loc, { '#': messageVariant, id: name }));
|
|
96
|
+
}
|
|
97
|
+
else if (migration.new.type === migration.old.type && migration.new.precision !== migration.old.scale) {
|
|
98
|
+
raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-precision-change', id => message(id, loc, { '#': messageVariant, id: name }));
|
|
99
|
+
}
|
|
100
|
+
else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type)) {
|
|
101
|
+
raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-change', id => message(id, loc, {
|
|
102
|
+
'#': messageVariant, id: name, name: migration.old.type, type: migration.new.type,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
else if (dialect !== 'sqlite' && isKey(migration.new) && !isKey(migration.old)) { // key added/changed - pg, hana and sqlite do not support it, h2 probably also - issues when data is in the table already
|
|
106
|
+
raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-key-change', id => message( id, [ 'definitions', migrations.migrate, 'elements', name ], { id: name, '#': 'changed' } ));
|
|
107
|
+
}
|
|
108
|
+
else if (migrationCallback) {
|
|
101
109
|
migrationCallback(migrations.migrate, name, migration, migrations.change, error);
|
|
110
|
+
}
|
|
102
111
|
|
|
103
112
|
if (options.script && migration.lossy && migrationCallback)
|
|
104
113
|
migrationCallback(migrations.migrate, name, migration, migrations.change, error);
|
|
@@ -115,9 +124,9 @@ function getFilterObject( options, dialect, extensionCallback, migrationCallback
|
|
|
115
124
|
});
|
|
116
125
|
}
|
|
117
126
|
},
|
|
118
|
-
deletion: ([ artifactName, artifact ],
|
|
127
|
+
deletion: ([ artifactName, artifact ], { message }) => {
|
|
119
128
|
if (isPersistedAsTable(artifact))
|
|
120
|
-
raiseErrorOrMarkAsLossy(artifact,
|
|
129
|
+
raiseErrorOrMarkAsLossy(artifactName, artifact, 'migration-unsupported-table-drop', id => message(id, [ 'definitions', artifactName ], { '#': messageVariant }));
|
|
121
130
|
},
|
|
122
131
|
changedPrimaryKeys: (changedPrimaryKeyArtifactName) => {
|
|
123
132
|
if (primaryKeyCallback)
|
|
@@ -191,13 +200,30 @@ function filterCsn( csn ) {
|
|
|
191
200
|
}
|
|
192
201
|
|
|
193
202
|
function getSafeguardManager( context, options ) {
|
|
194
|
-
return function raiseErrorOrMarkAsLossy(migration,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
migration.lossy =
|
|
203
|
+
return function raiseErrorOrMarkAsLossy(name, migration, id, raiseMessage) {
|
|
204
|
+
raiseMessage(id);
|
|
205
|
+
|
|
206
|
+
if (options.script) {
|
|
207
|
+
migration.details = getDetails(id, name);
|
|
208
|
+
migration.lossy = id !== 'migration-unsupported-key-change';
|
|
200
209
|
context.hasLossyChanges = true;
|
|
201
210
|
}
|
|
202
211
|
};
|
|
203
212
|
}
|
|
213
|
+
|
|
214
|
+
const details = {
|
|
215
|
+
'migration-unsupported-element-drop': 'drop of element',
|
|
216
|
+
'migration-unsupported-length-change': 'length reduction of element',
|
|
217
|
+
'migration-unsupported-scale-change': 'scale reduction of element',
|
|
218
|
+
'migration-unsupported-precision-change': 'precision reduction of element',
|
|
219
|
+
'migration-unsupported-change': 'incompatible type change of element',
|
|
220
|
+
'migration-unsupported-key-change': 'key property change of element',
|
|
221
|
+
'migration-unsupported-table-drop': 'drop of entity',
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
function getDetails(id, name) {
|
|
225
|
+
if (details[id])
|
|
226
|
+
return `${details[id] } "${ name }" - check warnings for details`;
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
@@ -70,37 +70,31 @@ class AstBuildingParser extends BaseParser {
|
|
|
70
70
|
return this.$messageFunctions.info( id, location?.location || location, args, text );
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
expectingArray(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (expecting.true && expecting.false)
|
|
73
|
+
expectingArray() {
|
|
74
|
+
let array = this.expectingArray_();
|
|
75
|
+
// compatibility: replace true+false by Boolean - TODO: delete
|
|
76
|
+
if (array.includes( 'true' ))
|
|
78
77
|
array = [ 'Boolean', ...array.filter( n => n !== 'true' && n !== 'false' ) ];
|
|
79
78
|
return array.map( antlrName )
|
|
80
79
|
.sort( (a, b) => (tokenPrecedence(a) < tokenPrecedence(b) ? -1 : 1) );
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
reportUnexpectedToken_(
|
|
84
|
-
const
|
|
82
|
+
reportUnexpectedToken_() {
|
|
83
|
+
const token = this.la();
|
|
84
|
+
const expecting = this.expectingArray();
|
|
85
85
|
const err = this.error( 'syntax-unexpected-token', token,
|
|
86
86
|
{ offending: antlrName( token ), expecting } );
|
|
87
87
|
// No 'unwanted' variant, no 'syntax-missing-token'
|
|
88
88
|
err.expectedTokens = expecting;
|
|
89
89
|
}
|
|
90
|
-
reportReservedWord_(
|
|
90
|
+
reportReservedWord_() {
|
|
91
|
+
const token = this.la();
|
|
91
92
|
const err = this.message( 'syntax-unexpected-reserved-word', token,
|
|
92
93
|
{ code: token.text, delimited: token.text } );
|
|
93
94
|
// TODO: at least if one expected keyword is similar, mention expected set
|
|
94
95
|
err.expectedTokens = this.expectingArray();
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
reportInternalError_( token ) {
|
|
98
|
-
this.error( null, token, { offending: antlrName( token ) },
|
|
99
|
-
'Mismatched $(OFFENDING); skipped one token' );
|
|
100
|
-
// TMP: should not happen anymore → remove method in redepage
|
|
101
|
-
throw new Error( 'Repeated error reporting with same token' );
|
|
102
|
-
}
|
|
103
|
-
|
|
104
98
|
tableWithoutAs() {
|
|
105
99
|
// TODO TOOL: if the tool properly creates `default: this.giR()`, this
|
|
106
100
|
// condition method is most likely not necessary
|
|
@@ -180,7 +174,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
180
174
|
return this.lGenericIntroOrExpr( false );
|
|
181
175
|
}
|
|
182
176
|
|
|
183
|
-
lGenericSeparator() {
|
|
177
|
+
lGenericSeparator() { // TODO: { keyword, type } as arg ?
|
|
184
178
|
const { keyword, type } = this.la();
|
|
185
179
|
// TODO: use lower-case in specialFunctions
|
|
186
180
|
const text = typeof keyword === 'string' ? keyword.toUpperCase() : type;
|
|
@@ -191,7 +185,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
191
185
|
translateParserToken_( tokenName ) {
|
|
192
186
|
const realTokens = this.dynamic_.generic?.[parserTokens[tokenName]];
|
|
193
187
|
// TODO: avoid parserTokens dict, use lower-case in specialFunctions
|
|
194
|
-
return realTokens?.map( s => s.toLowerCase() ) ?? [];
|
|
188
|
+
return realTokens?.map( s => s.toLowerCase() ) ?? [ tokenName ];
|
|
195
189
|
}
|
|
196
190
|
|
|
197
191
|
inSelectItem( _test, arg ) { // only as action
|
|
@@ -211,14 +205,14 @@ class AstBuildingParser extends BaseParser {
|
|
|
211
205
|
return true;
|
|
212
206
|
// TODO: it would be best to set this.dynamic_.inSelectItem to null in filters
|
|
213
207
|
// (as <prepare>)
|
|
214
|
-
const next = this.tokens[this.tokenIdx + 1]
|
|
208
|
+
const next = this.tokens[this.tokenIdx + 1]?.type;
|
|
215
209
|
return next !== '*' && next !== '{';
|
|
216
210
|
}
|
|
217
211
|
|
|
218
212
|
// <prec=10, postfix=once> + test that the next token is not `null`; TODO: code
|
|
219
213
|
// completion for `… default 3 not ~;` → currently just `null` but hey
|
|
220
214
|
isNegatedRelation( _test, prec ) {
|
|
221
|
-
return this.tokens[this.tokenIdx + 1]
|
|
215
|
+
return this.tokens[this.tokenIdx + 1]?.keyword !== 'null' &&
|
|
222
216
|
this.precNone_( _test, prec );
|
|
223
217
|
}
|
|
224
218
|
|
|
@@ -261,13 +255,13 @@ class AstBuildingParser extends BaseParser {
|
|
|
261
255
|
*
|
|
262
256
|
* Called as <prepare=…>:
|
|
263
257
|
*
|
|
264
|
-
* - <…, arg=default> in `returnsSpec`: after `returns`
|
|
265
|
-
* disallow `default` in `typeExpression`
|
|
266
258
|
* - <…,arg=elem> in `elementDef` (before calling `typeExpression`):
|
|
267
259
|
* allow `default`/`= calcExpr` with final annotation assignments,
|
|
268
260
|
* delay final doc comment
|
|
261
|
+
* - <…, arg=default> in `returnsSpec`: after `returns`
|
|
262
|
+
* disallow `default` in `typeExpression`
|
|
269
263
|
* - <…, arg=calc> in `typeExpression` (with associations, etc)
|
|
270
|
-
* now disallow `= calcExpr`
|
|
264
|
+
* now disallow `= calcExpr` in `elementDef`,
|
|
271
265
|
* do not delay final doc comments anymore
|
|
272
266
|
* - <…, arg=anno> in `typeExpression` after enums:
|
|
273
267
|
* now disallow annotation assignments after `= calcExpr`,
|
|
@@ -275,25 +269,48 @@ class AstBuildingParser extends BaseParser {
|
|
|
275
269
|
*
|
|
276
270
|
* Called as <cond=…>:
|
|
277
271
|
*
|
|
278
|
-
* - <…, arg=default> in `
|
|
279
|
-
* is `default` allowed? If used, disallow calc
|
|
272
|
+
* - <…, arg=default> in `typeExpression` and `typeProperties`
|
|
273
|
+
* is `default` allowed? If used, disallow calc and further DEFAULT
|
|
274
|
+
* - <…, arg=notNull> in `typeExpression` and `typeProperties`
|
|
275
|
+
* is `null`/`not null` allowed? ensures that it is only used once
|
|
280
276
|
* - <…, arg=calc> in `elementDef`:
|
|
281
|
-
* is `= calcExpr` allowed?
|
|
277
|
+
* is `= calcExpr` allowed? not with struct, assoc or MANY…
|
|
282
278
|
* - <…, arg=anno> in `elementDef`:
|
|
283
|
-
* are annotation assignments after `= calcExpr` allowed?
|
|
279
|
+
* are annotation assignments after `= calcExpr` allowed? not with ENUM…
|
|
280
|
+
*
|
|
281
|
+
* The value of the dynamic var `elementCtx` looks like [REJECTED, DEFAULT,
|
|
282
|
+
* NOTNULL] where
|
|
283
|
+
*
|
|
284
|
+
* - REJECTED is the string containing a to-be-rejected test `arg`
|
|
285
|
+
* - DEFAULT: true if `default` had been provided
|
|
286
|
+
* - NOTNULL: true if `null` or `not null` had been provided
|
|
284
287
|
*/
|
|
285
288
|
elementRestriction( test, arg ) {
|
|
286
|
-
|
|
289
|
+
let { elementCtx } = this.dynamic_;
|
|
287
290
|
if (test) {
|
|
288
|
-
if (
|
|
289
|
-
return
|
|
291
|
+
if (elementCtx?.[0] === arg)
|
|
292
|
+
return false;
|
|
293
|
+
if (!elementCtx) { // with type, param, or annotation defs
|
|
294
|
+
// eslint-disable-next-line no-multi-assign
|
|
295
|
+
elementCtx = this.dynamic_.elementCtx = [ null, false, false ];
|
|
296
|
+
}
|
|
290
297
|
if (arg === 'default') {
|
|
298
|
+
if (elementCtx[1])
|
|
299
|
+
return false;
|
|
300
|
+
elementCtx[1] = true;
|
|
291
301
|
elementCtx[0] = 'calc';
|
|
292
302
|
this.prec_ = PRECEDENCE_OF_EQUAL; // only expressions for DEFAULT expr
|
|
293
303
|
}
|
|
304
|
+
else if (arg === 'notNull') {
|
|
305
|
+
if (elementCtx[2]) {
|
|
306
|
+
if (this.la().keyword !== elementCtx[2] || test === 'M') // TODO v6: always error
|
|
307
|
+
return false; // error if different nullibility specification
|
|
308
|
+
}
|
|
309
|
+
elementCtx[2] = this.la().keyword;
|
|
310
|
+
}
|
|
294
311
|
}
|
|
295
312
|
else if (arg === 'elem' || arg === 'default') {
|
|
296
|
-
this.dynamic_.elementCtx = [ arg ];
|
|
313
|
+
this.dynamic_.elementCtx = [ arg, false, false ];
|
|
297
314
|
}
|
|
298
315
|
else if (elementCtx) {
|
|
299
316
|
elementCtx[0] = arg;
|
|
@@ -737,22 +754,10 @@ class AstBuildingParser extends BaseParser {
|
|
|
737
754
|
* - misplaced doc comments would lead to a parse error (incompatible),
|
|
738
755
|
* - would influence the prediction and error recovery,
|
|
739
756
|
* - is only slightly "more declarative" in the grammar.
|
|
740
|
-
*
|
|
741
|
-
* With argument `delayed`, potentially delay the doc processing.
|
|
742
|
-
* See also `elementRestriction`.
|
|
743
757
|
*/
|
|
744
|
-
docComment( art
|
|
745
|
-
if (delayed !== this.dynamic_?.[0]) {
|
|
746
|
-
if (delayed === 'type')
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
else if (delayed === 'elem') {
|
|
750
|
-
this.dynamic_[0] = 'type';
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
|
|
758
|
+
docComment( art ) {
|
|
754
759
|
const { line: prevLine, col: prevCol } = this.lb()?.location ?? { line: 0, col: 0 };
|
|
755
|
-
const { line: currLine, col: currCol } = this.la().location;
|
|
760
|
+
const { line: currLine, col: currCol } = (this.la() ?? this.lb()).location;
|
|
756
761
|
let token;
|
|
757
762
|
for (;;) {
|
|
758
763
|
token = this.docComments[this.docCommentIndex];
|
|
@@ -801,13 +806,16 @@ class AstBuildingParser extends BaseParser {
|
|
|
801
806
|
setNullability( art, val, location = this.lb().location ) {
|
|
802
807
|
const notNull = { val, location };
|
|
803
808
|
if (art.notNull) {
|
|
804
|
-
|
|
809
|
+
// complain about the second
|
|
810
|
+
this.reportDuplicateClause( 'notNull', notNull, art.notNull,
|
|
805
811
|
(val ? 'not null' : 'null') );
|
|
806
812
|
}
|
|
807
|
-
|
|
813
|
+
else {
|
|
814
|
+
art.notNull = notNull;
|
|
815
|
+
}
|
|
808
816
|
}
|
|
809
817
|
|
|
810
|
-
setAssocAndComposition( art, assoc, card, target ) {
|
|
818
|
+
setAssocAndComposition( art, assoc, card, target = {} ) {
|
|
811
819
|
const { location } = assoc;
|
|
812
820
|
art.type = {
|
|
813
821
|
path: [ { id: keywordTypeNames[assoc.keyword], location } ],
|
|
@@ -816,7 +824,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
816
824
|
};
|
|
817
825
|
art.target = target;
|
|
818
826
|
if (!card)
|
|
819
|
-
return;
|
|
827
|
+
return target;
|
|
820
828
|
|
|
821
829
|
const targetMax = (card.keyword === 'one')
|
|
822
830
|
? { val: 1, literal: 'number', location: card.location }
|
|
@@ -829,6 +837,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
829
837
|
else {
|
|
830
838
|
art.cardinality = { targetMax, location: targetMax.location };
|
|
831
839
|
}
|
|
840
|
+
return target;
|
|
832
841
|
}
|
|
833
842
|
|
|
834
843
|
reportExpandInline( column, isInline ) {
|
|
@@ -868,9 +877,10 @@ class AstBuildingParser extends BaseParser {
|
|
|
868
877
|
col: chosen.location.col,
|
|
869
878
|
};
|
|
870
879
|
if (erroneous.val === chosen.val) {
|
|
880
|
+
// TODO v6: duplicate clause = error, independently whether it is the same
|
|
871
881
|
this.warning( 'syntax-duplicate-equal-clause', erroneous.location, args );
|
|
872
882
|
}
|
|
873
|
-
else {
|
|
883
|
+
else if (prop !== 'notNull') { // already via guard in grammar
|
|
874
884
|
if (literalValIfNotEq)
|
|
875
885
|
args.code = chosen.val;
|
|
876
886
|
this.message( 'syntax-duplicate-clause', erroneous.location, args );
|