@sap/cds-compiler 5.1.2 → 5.3.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 +58 -0
- package/bin/cdsc.js +7 -2
- package/bin/cdshi.js +24 -17
- package/bin/cdsse.js +17 -18
- package/doc/CHANGELOG_BETA.md +9 -4
- package/lib/api/main.js +19 -2
- package/lib/api/options.js +4 -1
- package/lib/api/validate.js +5 -0
- package/lib/base/builtins.js +1 -0
- package/lib/base/message-registry.js +40 -3
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +0 -11
- package/lib/checks/actionsFunctions.js +0 -12
- package/lib/checks/structuredAnnoExpressions.js +10 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +2 -2
- package/lib/compiler/checks.js +25 -6
- package/lib/compiler/define.js +27 -31
- package/lib/compiler/extend.js +16 -18
- package/lib/compiler/generate.js +3 -3
- package/lib/compiler/populate.js +22 -16
- package/lib/compiler/propagator.js +3 -2
- package/lib/compiler/resolve.js +87 -94
- package/lib/compiler/shared.js +12 -13
- package/lib/compiler/tweak-assocs.js +390 -86
- package/lib/compiler/utils.js +41 -33
- package/lib/compiler/xpr-rewrite.js +45 -58
- package/lib/edm/annotations/genericTranslation.js +17 -13
- package/lib/edm/csn2edm.js +28 -4
- package/lib/edm/edm.js +68 -28
- package/lib/edm/edmInboundChecks.js +5 -8
- package/lib/edm/edmPreprocessor.js +66 -40
- package/lib/edm/edmUtils.js +1 -1
- package/lib/gen/BaseParser.js +778 -0
- package/lib/gen/CdlParser.js +4477 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +4072 -4024
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +5 -3
- package/lib/json/to-csn.js +7 -10
- package/lib/language/antlrParser.js +96 -0
- package/lib/language/errorStrategy.js +1 -1
- package/lib/language/genericAntlrParser.js +32 -4
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +23 -0
- package/lib/model/cloneCsn.js +22 -13
- package/lib/model/csnUtils.js +2 -0
- package/lib/model/revealInternalProperties.js +2 -0
- package/lib/modelCompare/utils/filter.js +70 -42
- package/lib/optionProcessor.js +16 -10
- package/lib/parsers/AstBuildingParser.js +1290 -0
- package/lib/parsers/CdlGrammar.g4 +2013 -0
- package/lib/parsers/Lexer.js +249 -0
- package/lib/render/toCdl.js +46 -45
- package/lib/render/toSql.js +5 -5
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/applyTransformations.js +54 -16
- package/lib/transform/draft/odata.js +10 -11
- package/lib/transform/effective/flattening.js +10 -14
- package/lib/transform/forRelationalDB.js +7 -6
- package/lib/transform/odata/flattening.js +42 -31
- package/lib/transform/odata/toFinalBaseType.js +7 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
- package/share/messages/redirected-to-ambiguous.md +5 -4
- package/share/messages/redirected-to-complex.md +6 -3
|
@@ -36,7 +36,7 @@ function inspectPropagation( xsn, options, artifactName ) {
|
|
|
36
36
|
|
|
37
37
|
if (!artifactXsn) {
|
|
38
38
|
error(null, null, { name: artifactName },
|
|
39
|
-
// eslint-disable-next-line max-len
|
|
39
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
40
40
|
'Artifact $(NAME) not found, only top-level artifacts and their elements are supported for now');
|
|
41
41
|
return null;
|
|
42
42
|
}
|
package/lib/json/from-csn.js
CHANGED
|
@@ -1134,7 +1134,7 @@ function elementsDict( def, spec, xsn ) {
|
|
|
1134
1134
|
return elements;
|
|
1135
1135
|
warning( 'syntax-expecting-returns', elements[$location],
|
|
1136
1136
|
{ prop: 'elements', parentprop: 'returns' },
|
|
1137
|
-
// eslint-disable-next-line max-len
|
|
1137
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1138
1138
|
'Expecting property $(PROP) to be put into an object for property $(PARENTPROP) when annotating action return structures' );
|
|
1139
1139
|
xsn.returns = { kind: 'annotate', elements, location: elements[$location] };
|
|
1140
1140
|
return undefined;
|
|
@@ -2076,8 +2076,10 @@ function toXsn( csn, filename, options, messageFunctions ) {
|
|
|
2076
2076
|
|
|
2077
2077
|
const xsn = new XsnSource( 'json' ); // TODO: 'csn'? LSP does not use $frontend
|
|
2078
2078
|
|
|
2079
|
-
|
|
2080
|
-
({
|
|
2079
|
+
|
|
2080
|
+
({
|
|
2081
|
+
message, error, warning, info,
|
|
2082
|
+
} = messageFunctions);
|
|
2081
2083
|
|
|
2082
2084
|
if (csnVersionZero) {
|
|
2083
2085
|
warning( 'syntax-deprecated-csn-version', location(true), {},
|
package/lib/json/to-csn.js
CHANGED
|
@@ -123,7 +123,7 @@ const transformers = {
|
|
|
123
123
|
value: enumValueOrCalc, // do not list for select items as elements
|
|
124
124
|
query,
|
|
125
125
|
elements,
|
|
126
|
-
actions,
|
|
126
|
+
actions: sortedDict, // TODO: just normal dictionary
|
|
127
127
|
returns, // storing the return type of actions
|
|
128
128
|
// special: top-level, cardinality -----------------------------------------
|
|
129
129
|
sources,
|
|
@@ -609,6 +609,10 @@ function addLocation( loc, csn ) {
|
|
|
609
609
|
// is often not the reason for an error or warning. So we gain little benefit for
|
|
610
610
|
// two more properties. It is also an indication that the location is not exact.
|
|
611
611
|
const val = { file: loc.file, line: loc.line, col: loc.col };
|
|
612
|
+
if (withLocations === 'withEndPosition' && loc.endLine) {
|
|
613
|
+
val.endLine = loc.endLine;
|
|
614
|
+
val.endCol = loc.endCol;
|
|
615
|
+
}
|
|
612
616
|
Object.defineProperty( csn, '$location', {
|
|
613
617
|
value: val, configurable: true, writable: true, enumerable: withLocations,
|
|
614
618
|
} );
|
|
@@ -621,18 +625,11 @@ function insertOrderDict( dict ) {
|
|
|
621
625
|
return dictionary( dict, keys );
|
|
622
626
|
}
|
|
623
627
|
|
|
624
|
-
function sortedDict( dict ) {
|
|
628
|
+
function sortedDict( dict, _csn, _node, prop ) {
|
|
625
629
|
const keys = Object.keys( dict );
|
|
626
630
|
if (strictMode)
|
|
627
631
|
keys.sort();
|
|
628
|
-
return dictionary( dict, keys );
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function actions( dict, _csn, node ) {
|
|
632
|
-
const keys = Object.keys( dict );
|
|
633
|
-
if (strictMode && node.kind === 'annotate')
|
|
634
|
-
keys.sort(); // TODO: always sort with --test-mode ?
|
|
635
|
-
return dictionary( dict, keys, 'actions' );
|
|
632
|
+
return dictionary( dict, keys, prop );
|
|
636
633
|
}
|
|
637
634
|
|
|
638
635
|
function params( dict ) {
|
|
@@ -17,6 +17,11 @@ 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
|
+
|
|
20
25
|
// Error listener used for ANTLR4-generated parser
|
|
21
26
|
class ErrorListener extends antlr4.error.ErrorListener {
|
|
22
27
|
// method which is called by generated parser with --trace-parser[-amg]:
|
|
@@ -129,6 +134,9 @@ const rules = {
|
|
|
129
134
|
function parse( source, filename = '<undefined>.cds',
|
|
130
135
|
options = {}, messageFunctions = null,
|
|
131
136
|
rule = 'cdl' ) {
|
|
137
|
+
if (options.newParser)
|
|
138
|
+
return parseWithNewParser( source, filename, options, messageFunctions, rule );
|
|
139
|
+
|
|
132
140
|
const lexer = new Lexer( new antlr4.InputStream(source) );
|
|
133
141
|
const tokenStream = new RewriteTypeTokenStream(lexer);
|
|
134
142
|
/** @type {object} */
|
|
@@ -211,4 +219,92 @@ function parse( source, filename = '<undefined>.cds',
|
|
|
211
219
|
return ast;
|
|
212
220
|
}
|
|
213
221
|
|
|
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
|
+
CdlParser.prototype.exit_.apply( this, args );
|
|
279
|
+
};
|
|
280
|
+
parser.c = function c( ...args ) { // consume
|
|
281
|
+
CdlParser.prototype.c.apply( this, args );
|
|
282
|
+
parseListener.visitTerminal( { symbol: this.lb() } );
|
|
283
|
+
};
|
|
284
|
+
parser.skipToken_ = function skipToken_( ...args ) { // skip token in error recovery
|
|
285
|
+
CdlParser.prototype.skipToken_.apply( this, args ); // = `++this.tokenIdx`
|
|
286
|
+
parseListener.visitErrorNode( { symbol: this.lb() } );
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const result = {};
|
|
290
|
+
const rulespec = rules[rule];
|
|
291
|
+
if (rulespec) {
|
|
292
|
+
try {
|
|
293
|
+
parser[rulespec.func]( result );
|
|
294
|
+
}
|
|
295
|
+
catch (e) {
|
|
296
|
+
if (!(e instanceof RangeError && /Maximum.*exceeded$/i.test( e.message )))
|
|
297
|
+
throw e;
|
|
298
|
+
messageFunctions.error('syntax-invalid-source', { file: filename },
|
|
299
|
+
{ '#': 'cdl-stackoverflow' } );
|
|
300
|
+
result[rulespec.returns] = undefined;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const ast = result[rulespec?.returns] || (rule === 'cdl' ? new XsnSource( 'cdl' ) : {} );
|
|
304
|
+
ast.options = options;
|
|
305
|
+
if (attachTokens === true || attachTokens === filename)
|
|
306
|
+
ast.tokenStream = parser._input;
|
|
307
|
+
return ast;
|
|
308
|
+
}
|
|
309
|
+
|
|
214
310
|
module.exports = parse;
|
|
@@ -337,7 +337,7 @@ function reportIgnoredWith( recognizer, t ) {
|
|
|
337
337
|
const expecting = this.getExpectedTokensForMessage( recognizer, t );
|
|
338
338
|
const m = recognizer.warning( 'syntax-unexpected-semicolon', t,
|
|
339
339
|
{ offending: "';'", expecting, keyword: 'with' },
|
|
340
|
-
// eslint-disable-next-line max-len
|
|
340
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
341
341
|
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
|
|
342
342
|
m.expectedTokens = expecting;
|
|
343
343
|
}
|
|
@@ -101,6 +101,8 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
101
101
|
warnIfColonFollows,
|
|
102
102
|
fragileAlias,
|
|
103
103
|
identAst,
|
|
104
|
+
reportPathNamedManyOrOne,
|
|
105
|
+
reportMissingSemicolon,
|
|
104
106
|
pushXprToken,
|
|
105
107
|
pushOpToken,
|
|
106
108
|
argsExpression,
|
|
@@ -276,8 +278,9 @@ function markAsSkippedUntilEOF() {
|
|
|
276
278
|
function noAssignmentInSameLine() {
|
|
277
279
|
const t = this.getCurrentToken();
|
|
278
280
|
if (t.text === '@' && t.line <= this._input.LT(-1).line) {
|
|
281
|
+
// TODO: use 'syntax-missing-newline'
|
|
279
282
|
this.warning( 'syntax-missing-semicolon', t, { code: ';' },
|
|
280
|
-
// eslint-disable-next-line max-len
|
|
283
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
281
284
|
'Add a $(CODE) and/or newline before the annotation assignment to indicate that it belongs to the next statement' );
|
|
282
285
|
}
|
|
283
286
|
}
|
|
@@ -682,7 +685,7 @@ function warnIfColonFollows( anno ) {
|
|
|
682
685
|
if (t.text === ':') {
|
|
683
686
|
this.warning( 'syntax-missing-parens', anno.name.location,
|
|
684
687
|
{ code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' },
|
|
685
|
-
// eslint-disable-next-line max-len
|
|
688
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
686
689
|
'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' );
|
|
687
690
|
}
|
|
688
691
|
}
|
|
@@ -793,7 +796,7 @@ function identAst( token, category, noTokenTypeCheck = false ) {
|
|
|
793
796
|
}
|
|
794
797
|
else {
|
|
795
798
|
this.message( 'syntax-deprecated-ident', token, { delimited: id },
|
|
796
|
-
// eslint-disable-next-line max-len
|
|
799
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
797
800
|
'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
|
|
798
801
|
}
|
|
799
802
|
const ast = { id, $delimited: true, location: this.tokenLocation( token ) };
|
|
@@ -801,6 +804,31 @@ function identAst( token, category, noTokenTypeCheck = false ) {
|
|
|
801
804
|
return ast;
|
|
802
805
|
}
|
|
803
806
|
|
|
807
|
+
function reportPathNamedManyOrOne( { path } ) {
|
|
808
|
+
if (path.length === 1 && !path[0].$delimited &&
|
|
809
|
+
[ 'many', 'one' ].includes( path[0].id.toLowerCase() )) {
|
|
810
|
+
this.message( 'syntax-unexpected-many-one', path[0].location,
|
|
811
|
+
{ code: path[0].id, delimited: path[0].id } );
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function reportMissingSemicolon() {
|
|
816
|
+
const next = this._input.LT(1);
|
|
817
|
+
if (next.text !== ';' && next.text !== '' && // ';' by insertSemicolon()
|
|
818
|
+
next.text !== '}' && next.type !== antlr4.Token.EOF &&
|
|
819
|
+
this._input.LT(-1).text !== '}') {
|
|
820
|
+
const offending = this.literalNames[next.type] || this.symbolicNames[next.type];
|
|
821
|
+
const loc = this.tokenLocation( this._input.LT(-1) );
|
|
822
|
+
// better location after the previous token:
|
|
823
|
+
const location = new Location( loc.file, loc.endLine, loc.endCol );
|
|
824
|
+
// it would be nicer to mention the doc comment if present, but not worth the
|
|
825
|
+
// effort; 'syntax-missing-semicolon' already used
|
|
826
|
+
this.warning( 'syntax-missing-proj-semicolon', location,
|
|
827
|
+
{ expecting: [ "';'" ], offending },
|
|
828
|
+
'Missing $(EXPECTING) before $(OFFENDING)');
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
804
832
|
function pushXprToken( args ) {
|
|
805
833
|
const token = this._input.LT(-1);
|
|
806
834
|
args.push( {
|
|
@@ -1021,7 +1049,7 @@ function assignAnnotationValue( anno, value ) {
|
|
|
1021
1049
|
{
|
|
1022
1050
|
std: 'Annotation number $(RAWVALUE) is put as $(VALUE) into the CSN',
|
|
1023
1051
|
rounded: 'Annotation number $(RAWVALUE) is rounded to $(VALUE)',
|
|
1024
|
-
// eslint-disable-next-line max-len
|
|
1052
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1025
1053
|
infinite: 'Annotation value $(RAWVALUE) is infinite as number and put as string into the CSN',
|
|
1026
1054
|
} );
|
|
1027
1055
|
}
|
|
@@ -77,7 +77,7 @@ class MultiLineStringParser {
|
|
|
77
77
|
this.str = token.text; // Copy because .text is a getter
|
|
78
78
|
|
|
79
79
|
if (this.str[0] !== '`' || this.str[this.str.length - 1] !== '`')
|
|
80
|
-
// eslint-disable-next-line max-len
|
|
80
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
81
81
|
throw new CompilerAssertion('Invalid multi-line string sequence: Require string to be surrounded by back-ticks!');
|
|
82
82
|
|
|
83
83
|
this.output = [];
|
package/lib/main.d.ts
CHANGED
|
@@ -154,6 +154,29 @@ declare namespace compiler {
|
|
|
154
154
|
* @since v2.12.1
|
|
155
155
|
*/
|
|
156
156
|
$xsnObjects?: boolean
|
|
157
|
+
/**
|
|
158
|
+
* If `true`, the CSN will have an enumerable property `$locations`.
|
|
159
|
+
* with values for `line` and `col`, i.e. there is only a start position,
|
|
160
|
+
* but no end position.
|
|
161
|
+
*
|
|
162
|
+
* If `false`, the property will be non-enumerable, i.e. it won't be
|
|
163
|
+
* serialized when using `JSON.stringify()`.
|
|
164
|
+
*
|
|
165
|
+
* With value `"withEndPosition"`, the property will be enumerable and
|
|
166
|
+
* will contain values for the end-position. Other string values
|
|
167
|
+
* are not allowed. This value was introduced in v5.3.0.
|
|
168
|
+
*
|
|
169
|
+
* $location is not set on all artifacts, and it only indicates the position
|
|
170
|
+
* of the _name_ of the artifact.
|
|
171
|
+
*/
|
|
172
|
+
withLocations?: boolean|string
|
|
173
|
+
/**
|
|
174
|
+
* Use the new non-ANTLR based parser for compilation.
|
|
175
|
+
* Experimental flag!
|
|
176
|
+
*
|
|
177
|
+
* @since v5.2.0
|
|
178
|
+
*/
|
|
179
|
+
newParser?: boolean
|
|
157
180
|
/**
|
|
158
181
|
* Internal option for LSP only!
|
|
159
182
|
* If set, each AST gets a `tokenStream` property containing all lexed tokens.
|
package/lib/model/cloneCsn.js
CHANGED
|
@@ -5,16 +5,23 @@ const { ModelError } = require('../base/error');
|
|
|
5
5
|
const { setHidden } = require('../utils/objectUtils');
|
|
6
6
|
const { isAnnotationExpression } = require('../base/builtins');
|
|
7
7
|
|
|
8
|
-
const csnDictionaries =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
const csnDictionaries = {
|
|
9
|
+
__proto__: null,
|
|
10
|
+
args: 1,
|
|
11
|
+
params: 1,
|
|
12
|
+
enum: 1,
|
|
13
|
+
mixin: 1,
|
|
14
|
+
elements: 1,
|
|
15
|
+
actions: 1,
|
|
16
|
+
definitions: 1,
|
|
17
|
+
vocabularies: 1,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const sortedCsnDictionaries = {
|
|
21
|
+
__proto__: null,
|
|
22
|
+
definitions: 1,
|
|
23
|
+
actions: 1,
|
|
24
|
+
};
|
|
18
25
|
|
|
19
26
|
function shallowCopy( val, _options, _sort ) {
|
|
20
27
|
return val;
|
|
@@ -74,9 +81,9 @@ function cloneCsn( csn, options, sort ) {
|
|
|
74
81
|
else if (!val || typeof val !== 'object') {
|
|
75
82
|
r[n] = val;
|
|
76
83
|
}
|
|
77
|
-
else if (csnDictionaries
|
|
78
|
-
const sortDict =
|
|
79
|
-
|
|
84
|
+
else if (csnDictionaries[n] && !Array.isArray(val)) {
|
|
85
|
+
const sortDict = (!options || options.testMode || options.testSortCsn) &&
|
|
86
|
+
sortedCsnDictionaries[n];
|
|
80
87
|
// Array check for property `args` which may either be a dictionary or an array.
|
|
81
88
|
r[n] = cloneCsnDict(val, options, sort, sortDict);
|
|
82
89
|
}
|
|
@@ -122,6 +129,8 @@ function hasNonEnumerable( object, property ) {
|
|
|
122
129
|
* @param {object} csn
|
|
123
130
|
* @param {CSN.Options} options Only cloneOptions.dictionaryPrototype is
|
|
124
131
|
* used and cloneOptions are passed to sortCsn().
|
|
132
|
+
* @param {boolean} sortProps Whether to sort CSN properties.
|
|
133
|
+
* @param {boolean} sortDict Whether to sort CSN dictionary entries.
|
|
125
134
|
*/
|
|
126
135
|
function cloneCsnDict( csn, options, sortProps, sortDict ) {
|
|
127
136
|
const proto = options?.dictionaryPrototype;
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { csnRefs, implicitAs, pathId } = require('./csnRefs');
|
|
4
4
|
const {
|
|
5
5
|
transformExpression,
|
|
6
|
+
transformAnnotationExpression,
|
|
6
7
|
applyTransformations,
|
|
7
8
|
applyTransformationsOnNonDictionary,
|
|
8
9
|
applyTransformationsOnDictionary,
|
|
@@ -1419,6 +1420,7 @@ module.exports = {
|
|
|
1419
1420
|
getUnderscoredName,
|
|
1420
1421
|
getElementDatabaseNameOf,
|
|
1421
1422
|
transformExpression,
|
|
1423
|
+
transformAnnotationExpression,
|
|
1422
1424
|
applyTransformations,
|
|
1423
1425
|
applyTransformationsOnNonDictionary,
|
|
1424
1426
|
applyTransformationsOnDictionary,
|
|
@@ -306,6 +306,8 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
function array( node, fn ) {
|
|
309
|
+
if (!Array.isArray( node ))
|
|
310
|
+
return node;
|
|
309
311
|
const r = node.map( n => fn( n, node ) );
|
|
310
312
|
if (node[$location])
|
|
311
313
|
r.push( { '[$location]': locationString( node[$location] ) } );
|
|
@@ -10,46 +10,57 @@ function isKey( element ) {
|
|
|
10
10
|
return element.key;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
function getFilter(options) {
|
|
14
|
+
const filters = {
|
|
15
|
+
sqlite: getFilterObject(
|
|
16
|
+
options,
|
|
17
|
+
'sqlite',
|
|
18
|
+
function forEachExtension(extend, name, elementOrConstraint, { error, warning }) {
|
|
19
|
+
if (isKey(elementOrConstraint)) { // Key must not be extended
|
|
20
|
+
error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite', '#': 'std' } );
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
else if (elementOrConstraint.parentTable) { // constraints have a .parentTable
|
|
24
|
+
warning('def-unsupported-constraint-add', [ 'definitions', elementOrConstraint.parentTable, 'elements', elementOrConstraint.paths ? name : name.slice(elementOrConstraint.parentTable.length + 1) ], { id: elementOrConstraint.identifier || name, name: 'sqlite' },
|
|
25
|
+
'Ignoring add of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return true;
|
|
30
|
+
},
|
|
31
|
+
function forEachMigration(migrate, name, migration, change, error) {
|
|
32
|
+
const newIsKey = isKey(migration.new);
|
|
33
|
+
const oldIsKey = isKey(migration.old);
|
|
34
|
+
if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) // Turned into key or key was removed
|
|
35
|
+
error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite', '#': 'changed' } );
|
|
36
|
+
else
|
|
37
|
+
delete change[name];
|
|
38
|
+
},
|
|
39
|
+
function forEachConstraintRemoval(constraintRemovals, name, constraint, warning) {
|
|
40
|
+
warning('def-unsupported-constraint-drop', [ 'definitions', constraint.parentTable, 'elements', constraint.paths ? name : name.slice(constraint.parentTable.length + 1) ], { id: constraint.identifier || name, name: 'sqlite' },
|
|
41
|
+
'Ignoring drop of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
|
|
42
|
+
delete constraintRemovals[name];
|
|
43
|
+
},
|
|
44
|
+
function primaryKey() {
|
|
24
45
|
return false;
|
|
25
46
|
}
|
|
47
|
+
),
|
|
48
|
+
postgres: getFilterObject(options, 'postgres'),
|
|
49
|
+
h2: getFilterObject(options, 'h2'),
|
|
50
|
+
hana: getFilterObject(options, 'hana'),
|
|
51
|
+
};
|
|
26
52
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const oldIsKey = isKey(migration.old);
|
|
32
|
-
if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) // Turned into key or key was removed
|
|
33
|
-
error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite', '#': 'changed' } );
|
|
34
|
-
else
|
|
35
|
-
delete change[name];
|
|
36
|
-
},
|
|
37
|
-
function forEachConstraintRemoval(constraintRemovals, name, constraint, warning) {
|
|
38
|
-
warning('def-unsupported-constraint-drop', [ 'definitions', constraint.parentTable, 'elements', constraint.paths ? name : name.slice(constraint.parentTable.length + 1) ], { id: constraint.identifier || name, name: 'sqlite' },
|
|
39
|
-
'Ignoring drop of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
|
|
40
|
-
delete constraintRemovals[name];
|
|
41
|
-
},
|
|
42
|
-
function primaryKey() {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
),
|
|
46
|
-
postgres: getFilterObject('postgres'),
|
|
47
|
-
h2: getFilterObject('h2'),
|
|
48
|
-
hana: getFilterObject('hana'),
|
|
53
|
+
return filters[options.sqlDialect];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
49
57
|
csn: filterCsn,
|
|
58
|
+
getFilter,
|
|
50
59
|
};
|
|
51
60
|
|
|
52
|
-
function getFilterObject( dialect, extensionCallback, migrationCallback, removeConstraintsCallback, primaryKeyCallback ) {
|
|
61
|
+
function getFilterObject( options, dialect, extensionCallback, migrationCallback, removeConstraintsCallback, primaryKeyCallback ) {
|
|
62
|
+
const context = { hasLossyChanges: false };
|
|
63
|
+
const raiseErrorOrMarkAsLossy = getSafeguardManager(context, options);
|
|
53
64
|
return {
|
|
54
65
|
// will be called with a simple Array.filter, as we need to filter constraint `ADD` for SQLite
|
|
55
66
|
extension: ({
|
|
@@ -70,24 +81,28 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
|
|
|
70
81
|
},
|
|
71
82
|
// will be called with a Array.forEach
|
|
72
83
|
migration: (migrations, { error, warning, message }) => {
|
|
73
|
-
forEach(migrations.remove, (name) => {
|
|
74
|
-
error('def-unsupported-element-drop', [ 'definitions', migrations.migrate, 'elements', name ], {}, 'Dropping elements is not supported');
|
|
84
|
+
forEach(migrations.remove, (name, migration) => {
|
|
85
|
+
raiseErrorOrMarkAsLossy(migration, () => error('def-unsupported-element-drop', [ 'definitions', migrations.migrate, 'elements', name ], {}, 'Dropping elements is not supported'));
|
|
75
86
|
});
|
|
87
|
+
|
|
76
88
|
forEach(migrations.change, (name, migration) => {
|
|
77
89
|
const loc = [ 'definitions', migrations.migrate, 'elements', name ];
|
|
78
90
|
if (migration.new.type === migration.old.type && migration.new.length < migration.old.length)
|
|
79
|
-
error('type-unsupported-length-change', loc, { id: name }, 'Changed element $(ID) is a length reduction and is not supported');
|
|
91
|
+
raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-length-change', loc, { id: name }, 'Changed element $(ID) is a length reduction and is not supported'));
|
|
80
92
|
else if (migration.new.type === migration.old.type && migration.new.scale !== migration.old.scale)
|
|
81
|
-
error('type-unsupported-scale-change', loc, { id: name }, 'Changed element $(ID) is a scale change and is not supported');
|
|
93
|
+
raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-scale-change', loc, { id: name }, 'Changed element $(ID) is a scale change and is not supported'));
|
|
82
94
|
else if (migration.new.type === migration.old.type && migration.new.precision !== migration.old.scale)
|
|
83
|
-
error('type-unsupported-precision-change', loc, { id: name }, 'Changed element $(ID) is a precision change and is not supported');
|
|
95
|
+
raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-precision-change', loc, { id: name }, 'Changed element $(ID) is a precision change and is not supported'));
|
|
84
96
|
else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type))
|
|
85
|
-
error('type-unsupported-change', loc, { id: name, name: migration.old.type, type: migration.new.type }, 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported');
|
|
97
|
+
raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-change', loc, { id: name, name: migration.old.type, type: migration.new.type }, 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported'));
|
|
86
98
|
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
|
|
87
|
-
message('type-unsupported-key-change', [ 'definitions', migrations.migrate, 'elements', name ], { id: name, '#': 'changed' } );
|
|
99
|
+
raiseErrorOrMarkAsLossy(migration, () => message('type-unsupported-key-change', [ 'definitions', migrations.migrate, 'elements', name ], { id: name, '#': 'changed' } ));
|
|
88
100
|
else if (migrationCallback)
|
|
89
101
|
migrationCallback(migrations.migrate, name, migration, migrations.change, error);
|
|
90
102
|
|
|
103
|
+
if (options.script && migration.lossy && migrationCallback)
|
|
104
|
+
migrationCallback(migrations.migrate, name, migration, migrations.change, error);
|
|
105
|
+
|
|
91
106
|
// TODO: precision/scale growth
|
|
92
107
|
});
|
|
93
108
|
|
|
@@ -102,7 +117,7 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
|
|
|
102
117
|
},
|
|
103
118
|
deletion: ([ artifactName, artifact ], error) => {
|
|
104
119
|
if (isPersistedAsTable(artifact))
|
|
105
|
-
error('def-unsupported-table-drop', [ 'definitions', artifactName ], 'Dropping tables is not supported');
|
|
120
|
+
raiseErrorOrMarkAsLossy(artifact, () => error('def-unsupported-table-drop', [ 'definitions', artifactName ], 'Dropping tables is not supported'));
|
|
106
121
|
},
|
|
107
122
|
changedPrimaryKeys: (changedPrimaryKeyArtifactName) => {
|
|
108
123
|
if (primaryKeyCallback)
|
|
@@ -110,6 +125,7 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
|
|
|
110
125
|
|
|
111
126
|
return true;
|
|
112
127
|
},
|
|
128
|
+
hasLossyChanges: () => context.hasLossyChanges,
|
|
113
129
|
};
|
|
114
130
|
}
|
|
115
131
|
|
|
@@ -173,3 +189,15 @@ function filterCsn( csn ) {
|
|
|
173
189
|
|
|
174
190
|
return csn;
|
|
175
191
|
}
|
|
192
|
+
|
|
193
|
+
function getSafeguardManager( context, options ) {
|
|
194
|
+
return function raiseErrorOrMarkAsLossy(migration, raiseError) {
|
|
195
|
+
if (!options.script) {
|
|
196
|
+
raiseError();
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
migration.lossy = true;
|
|
200
|
+
context.hasLossyChanges = true;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|