@sap/cds-compiler 5.1.0 → 5.2.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 +32 -0
- package/bin/cdsc.js +2 -2
- package/bin/cdshi.js +24 -17
- package/bin/cdsse.js +17 -18
- package/lib/api/main.js +19 -2
- package/lib/api/options.js +4 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/message-registry.js +16 -3
- package/lib/base/model.js +0 -10
- package/lib/checks/actionsFunctions.js +0 -12
- package/lib/checks/structuredAnnoExpressions.js +10 -14
- package/lib/compiler/assert-consistency.js +19 -11
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/define.js +6 -4
- package/lib/compiler/extend.js +5 -5
- package/lib/compiler/populate.js +9 -9
- package/lib/compiler/propagator.js +1 -0
- package/lib/compiler/resolve.js +29 -34
- package/lib/compiler/shared.js +7 -8
- package/lib/compiler/tweak-assocs.js +155 -64
- package/lib/compiler/utils.js +1 -1
- package/lib/compiler/xpr-rewrite.js +4 -3
- package/lib/edm/annotations/genericTranslation.js +13 -9
- package/lib/edm/csn2edm.js +26 -2
- package/lib/edm/edm.js +23 -8
- package/lib/edm/edmInboundChecks.js +5 -7
- package/lib/edm/edmPreprocessor.js +43 -30
- package/lib/gen/BaseParser.js +720 -0
- package/lib/gen/CdlParser.js +4421 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +4006 -4001
- package/lib/language/antlrParser.js +62 -0
- package/lib/language/genericAntlrParser.js +28 -0
- 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 +9 -3
- package/lib/parsers/AstBuildingParser.js +1172 -0
- package/lib/parsers/CdlGrammar.g4 +1940 -0
- package/lib/parsers/Lexer.js +239 -0
- package/lib/render/toCdl.js +23 -27
- package/lib/render/toSql.js +5 -5
- 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/odata/flattening.js +42 -31
- package/lib/transform/odata/toFinalBaseType.js +7 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/package.json +2 -2
- package/share/messages/redirected-to-ambiguous.md +5 -4
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// Lexer for CDL grammar
|
|
2
|
+
|
|
3
|
+
// The lexer only cares about potential keywords, not the exact list. That is, it
|
|
4
|
+
// sets the `keyword` property for all non-delimited `Id` tokens.
|
|
5
|
+
|
|
6
|
+
// General remarks about regular expressions in node.js (or in general):
|
|
7
|
+
//
|
|
8
|
+
// - Alternatives in regexps are searched left to right, not longest as in scanner
|
|
9
|
+
// generator!
|
|
10
|
+
// - Beware if a regular expression fails (or matches just one char) after having
|
|
11
|
+
// tested k characters in an input. A regexp having a non-optional match or
|
|
12
|
+
// assertion after a loop (Kleene star) could lead to lexer execution time of
|
|
13
|
+
// O(n*n). Therefore, regexps for strings etc only cover the opening delimiter.
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const { Location } = require('../base/location'); // TODO main: add tokenIndex
|
|
18
|
+
|
|
19
|
+
const rules = [ // must not contain capturing groups!
|
|
20
|
+
{ type: comment, re: '/[*/]' },
|
|
21
|
+
// token type = token text (`type: null`):
|
|
22
|
+
{ type: null, re: '[-+*?()\\[\\]{},;:/@#]|\\.(?:\\.\\.?)?|<[=>]?|>=?|=>?|!=|\\|\\|' },
|
|
23
|
+
{ type: ident, re: '[$_\\p{ID_Start}][$\\p{ID_Continue}\u200C\u200D]*|!\\[|"' },
|
|
24
|
+
{ type: string, re: '[\'"]|`(?:``)?' }, // strings, template literal without …${}
|
|
25
|
+
{ type: 'Number', re: '\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?' },
|
|
26
|
+
{ type: 'IllegalToken', re: '\\S' }, // must be last
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const rulesRegexp = new RegExp( `(${ rules.map( r => r.re ).join( ')|(' ) })`, 'iugm' );
|
|
30
|
+
if (rulesRegexp.exec( '§' )[rules.length] !== '§')
|
|
31
|
+
throw Error( 'Invalid capturing group in rules regexp' );
|
|
32
|
+
const newlineRegexp = /\n/g; // TODO: \r?, PS, LS
|
|
33
|
+
|
|
34
|
+
const commentRegexps = { '//': /$/gm, '/*': /\*\//g };
|
|
35
|
+
const stringRegexps = { "'": /'|$/gm, '`': /[`\\]/g, '```': /```|\\/g };
|
|
36
|
+
const identRegexps = { '![': /\]|$/gm, '"': /"|$/gm };
|
|
37
|
+
|
|
38
|
+
const quotedLiterals = [ 'date', 'time', 'timestamp', 'x' ];
|
|
39
|
+
|
|
40
|
+
class Token {
|
|
41
|
+
type;
|
|
42
|
+
text;
|
|
43
|
+
keyword;
|
|
44
|
+
location;
|
|
45
|
+
parsed;
|
|
46
|
+
get isIdentifier() { // compatibility method
|
|
47
|
+
return this.parsed !== 'keyword' && this.parsed !== 'token' && this.parsed;
|
|
48
|
+
}
|
|
49
|
+
get tokenIndex() {
|
|
50
|
+
return this.location.tokenIndex;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class Lexer {
|
|
55
|
+
constructor( file, input ) {
|
|
56
|
+
this.file = file;
|
|
57
|
+
this.input = input; // string
|
|
58
|
+
this.linePositions = undefined;
|
|
59
|
+
this.location = undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
characterPos( line, col ) {
|
|
63
|
+
return this.linePositions[line - 1] + col - 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
tokenize( parser ) {
|
|
67
|
+
this.linePositions = [ 0 ];
|
|
68
|
+
parser.tokens = [];
|
|
69
|
+
parser.docComments = [];
|
|
70
|
+
newlineRegexp.lastIndex = 0;
|
|
71
|
+
while (newlineRegexp.test( this.input ))
|
|
72
|
+
this.linePositions.push( newlineRegexp.lastIndex );
|
|
73
|
+
|
|
74
|
+
const { file } = this;
|
|
75
|
+
let line = 1;
|
|
76
|
+
rulesRegexp.lastIndex = 0;
|
|
77
|
+
let match;
|
|
78
|
+
// eslint-disable-next-line no-cond-assign
|
|
79
|
+
while (match = rulesRegexp.exec( this.input )) {
|
|
80
|
+
let text = match[0];
|
|
81
|
+
const group = match.indexOf( text, 1 ) - 1;
|
|
82
|
+
let type = rules[group].type || text;
|
|
83
|
+
const pos = match.index;
|
|
84
|
+
while (pos >= this.linePositions[line])
|
|
85
|
+
++line;
|
|
86
|
+
const col = pos - this.linePositions[line - 1] + 1;
|
|
87
|
+
this.location = {
|
|
88
|
+
__proto__: Location.prototype,
|
|
89
|
+
file,
|
|
90
|
+
line,
|
|
91
|
+
col,
|
|
92
|
+
endLine: line,
|
|
93
|
+
endCol: col + text.length,
|
|
94
|
+
// remark: end positions of multi-line tokens must be set by function
|
|
95
|
+
tokenIndex: parser.tokens.length,
|
|
96
|
+
};
|
|
97
|
+
let keyword;
|
|
98
|
+
if (typeof type !== 'function' ||
|
|
99
|
+
([ type, text, keyword ] = type( text, this, parser, pos )) && type) {
|
|
100
|
+
parser.tokens.push( {
|
|
101
|
+
__proto__: Token.prototype,
|
|
102
|
+
type,
|
|
103
|
+
text,
|
|
104
|
+
keyword,
|
|
105
|
+
location: this.location,
|
|
106
|
+
parsed: undefined,
|
|
107
|
+
} );
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
line = this.linePositions.length;
|
|
111
|
+
const endCol = this.input.length - this.linePositions[line - 1] + 1;
|
|
112
|
+
const location = {
|
|
113
|
+
__proto__: Location.prototype,
|
|
114
|
+
file,
|
|
115
|
+
line,
|
|
116
|
+
col: endCol,
|
|
117
|
+
endLine: line,
|
|
118
|
+
endCol,
|
|
119
|
+
tokenIndex: parser.tokens.length,
|
|
120
|
+
};
|
|
121
|
+
parser.tokens.push( {
|
|
122
|
+
__proto__: Token.prototype,
|
|
123
|
+
type: 'EOF',
|
|
124
|
+
text: '',
|
|
125
|
+
keyword: false,
|
|
126
|
+
location,
|
|
127
|
+
parsed: undefined,
|
|
128
|
+
} );
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function comment( text, lexer, parser, beg ) {
|
|
133
|
+
const re = commentRegexps[text];
|
|
134
|
+
re.lastIndex = rulesRegexp.lastIndex;
|
|
135
|
+
if (!re.test( lexer.input )) {
|
|
136
|
+
// eslint-disable-next-line cds-compiler/message-texts
|
|
137
|
+
parser.error( 'syntax-missing-token-end', lexer.location,
|
|
138
|
+
{ '#': 'comment', code: '/*', newCode: '*/' }, {
|
|
139
|
+
comment: 'Comments starting with $(CODE) must end with $(NEWCODE)',
|
|
140
|
+
} );
|
|
141
|
+
}
|
|
142
|
+
else if (text === '/*' && lexer.input.charAt( rulesRegexp.lastIndex ) === '*' &&
|
|
143
|
+
rulesRegexp.lastIndex + 2 < re.lastIndex) { // not just `/**/`
|
|
144
|
+
lexer.location.tokenIndex = parser.docComments.length;
|
|
145
|
+
parser.docComments.push( {
|
|
146
|
+
__proto__: Token.prototype,
|
|
147
|
+
type: 'DocComment',
|
|
148
|
+
text: lexer.input.substring( beg, re.lastIndex ),
|
|
149
|
+
keyword: false,
|
|
150
|
+
location: lexer.location,
|
|
151
|
+
parsed: undefined,
|
|
152
|
+
} );
|
|
153
|
+
adaptEndLocation( lexer, re.lastIndex ); // also works after push ?
|
|
154
|
+
}
|
|
155
|
+
rulesRegexp.lastIndex = re.lastIndex || lexer.input.length;
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function string( text, lexer, parser, beg ) {
|
|
160
|
+
let prefix = null;
|
|
161
|
+
const re = stringRegexps[text];
|
|
162
|
+
re.lastIndex = rulesRegexp.lastIndex;
|
|
163
|
+
let esc = 0;
|
|
164
|
+
if (text !== "'") { // single or triple back-quote
|
|
165
|
+
while (re.test( lexer.input ) && lexer.input[re.lastIndex - 1] === '\\')
|
|
166
|
+
esc = ++re.lastIndex;
|
|
167
|
+
}
|
|
168
|
+
else { // try with previous date/time/timestamp/x
|
|
169
|
+
prefix = parser.tokens[parser.tokens.length - 1];
|
|
170
|
+
if (prefix.location.endLine !== lexer.location.line ||
|
|
171
|
+
prefix.location.endCol !== lexer.location.col ||
|
|
172
|
+
!quotedLiterals.includes( prefix.keyword ))
|
|
173
|
+
prefix = null;
|
|
174
|
+
while (re.test( lexer.input ) && lexer.input[re.lastIndex] === "'")
|
|
175
|
+
esc = ++re.lastIndex;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let keyword;
|
|
179
|
+
const { lastIndex } = re;
|
|
180
|
+
if (!lastIndex || // reached EOF with template literal
|
|
181
|
+
lexer.input[lastIndex - 1] !== lexer.input[beg] || esc === lastIndex) {
|
|
182
|
+
const before = (lastIndex) ? 'string' : 'multi';
|
|
183
|
+
// eslint-disable-next-line cds-compiler/message-texts
|
|
184
|
+
parser.error( 'syntax-missing-token-end', lexer.location,
|
|
185
|
+
{ '#': before, newCode: text }, {
|
|
186
|
+
string: 'The string literal must end with $(NEWCODE) before the end of line',
|
|
187
|
+
multi: 'The multi-line string literal must end with $(NEWCODE)',
|
|
188
|
+
} );
|
|
189
|
+
keyword = 0;
|
|
190
|
+
// TODO: set parsed to 0 → no further error if string is not expected?
|
|
191
|
+
prefix = null; // no combination with date/time/…
|
|
192
|
+
}
|
|
193
|
+
adaptEndLocation( lexer, (rulesRegexp.lastIndex = lastIndex || lexer.input.length) );
|
|
194
|
+
|
|
195
|
+
if (!prefix)
|
|
196
|
+
return [ 'String', lexer.input.substring( beg, rulesRegexp.lastIndex ), keyword ];
|
|
197
|
+
prefix.type = 'QuotedLiteral';
|
|
198
|
+
prefix.text += lexer.input.substring( beg, rulesRegexp.lastIndex );
|
|
199
|
+
prefix.keyword = undefined;
|
|
200
|
+
prefix.location.endLine = lexer.location.endLine;
|
|
201
|
+
prefix.location.endCol = lexer.location.endCol;
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function ident( text, lexer, parser, beg ) {
|
|
206
|
+
if (!Object.hasOwn( identRegexps, text ))
|
|
207
|
+
return [ 'Id', text, text.toLowerCase() ];
|
|
208
|
+
const re = identRegexps[text];
|
|
209
|
+
const close = (text === '"') ? '"' : ']';
|
|
210
|
+
re.lastIndex = rulesRegexp.lastIndex;
|
|
211
|
+
let esc = 0;
|
|
212
|
+
while (re.test( lexer.input ) && lexer.input[re.lastIndex] === close)
|
|
213
|
+
esc = ++re.lastIndex;
|
|
214
|
+
|
|
215
|
+
let keyword;
|
|
216
|
+
const { lastIndex } = re;
|
|
217
|
+
if (lexer.input[lastIndex - 1] !== close || esc === lastIndex) {
|
|
218
|
+
// eslint-disable-next-line cds-compiler/message-texts
|
|
219
|
+
parser.error( 'syntax-missing-token-end', lexer.location,
|
|
220
|
+
{ '#': 'ident', newcode: close }, {
|
|
221
|
+
ident: 'The delimited id must end with $(NEWCODE) before the end of line',
|
|
222
|
+
} );
|
|
223
|
+
keyword = 0;
|
|
224
|
+
// TODO: set parsed to 0 → no further error if string is not expected?
|
|
225
|
+
}
|
|
226
|
+
adaptEndLocation( lexer, (rulesRegexp.lastIndex = lastIndex || lexer.input.length) );
|
|
227
|
+
return [ 'Id', lexer.input.substring( beg, rulesRegexp.lastIndex ), keyword ];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function adaptEndLocation( lexer, pos ) {
|
|
231
|
+
let { line } = lexer.location;
|
|
232
|
+
while (pos >= lexer.linePositions[line])
|
|
233
|
+
++line;
|
|
234
|
+
lexer.location.endLine = line;
|
|
235
|
+
lexer.location.endCol = pos - lexer.linePositions[line - 1] + 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
Lexer.Token = Token;
|
|
239
|
+
module.exports = Lexer;
|
package/lib/render/toCdl.js
CHANGED
|
@@ -320,19 +320,12 @@ function csnToCdl( csn, options, msg ) {
|
|
|
320
320
|
result += renderAnnotateParamsInParentheses(ext, env);
|
|
321
321
|
|
|
322
322
|
// Element extensions and annotations (possibly nested)
|
|
323
|
-
if (ext.elements)
|
|
324
|
-
env
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
else if (ext.enum) {
|
|
329
|
-
env.path.push('enum');
|
|
330
|
-
result += ` ${renderAnnotateStatementElements(ext.enum, env)}`;
|
|
331
|
-
env.path.length -= 1;
|
|
332
|
-
}
|
|
333
|
-
else if (ext.returns) {
|
|
323
|
+
if (ext.elements || ext.enum)
|
|
324
|
+
result += ` ${renderAnnotateStatementElements(ext, env)}`;
|
|
325
|
+
|
|
326
|
+
else if (ext.returns)
|
|
334
327
|
result += renderAnnotateReturns(ext, env);
|
|
335
|
-
|
|
328
|
+
|
|
336
329
|
|
|
337
330
|
if (ext.actions) { // Bound action annotations
|
|
338
331
|
result += ' actions {\n';
|
|
@@ -362,17 +355,18 @@ function csnToCdl( csn, options, msg ) {
|
|
|
362
355
|
|
|
363
356
|
/**
|
|
364
357
|
* Render the elements-specific part of an 'annotate' statement for an element dictionary
|
|
365
|
-
* 'elements' (assuming that the surrounding parent has just been rendered, without trailing newline).
|
|
358
|
+
* 'ext.elements' (assuming that the surrounding parent has just been rendered, without trailing newline).
|
|
366
359
|
* Returns the resulting source string, ending without a trailing newline.
|
|
367
360
|
*
|
|
368
|
-
* @param {object}
|
|
361
|
+
* @param {object} ext
|
|
369
362
|
* @param {CdlRenderEnvironment} env
|
|
370
363
|
* @return {string}
|
|
371
364
|
*/
|
|
372
|
-
function renderAnnotateStatementElements(
|
|
365
|
+
function renderAnnotateStatementElements( ext, env ) {
|
|
366
|
+
const elements = ext.enum ? ext.enum : ext.elements;
|
|
373
367
|
let result = '{\n';
|
|
374
368
|
env.increaseIndent();
|
|
375
|
-
env.path.push('');
|
|
369
|
+
env.path.push(ext.enum ? 'enum' : 'elements', '');
|
|
376
370
|
for (const name in elements) {
|
|
377
371
|
env.path[env.path.length - 1] = name;
|
|
378
372
|
const elem = elements[name];
|
|
@@ -380,18 +374,18 @@ function csnToCdl( csn, options, msg ) {
|
|
|
380
374
|
result += env.indent + quoteNonIdentifierOrKeyword(name, env);
|
|
381
375
|
if (elem.elements) {
|
|
382
376
|
env.path.push('elements');
|
|
383
|
-
result += ` ${renderAnnotateStatementElements(elem
|
|
377
|
+
result += ` ${renderAnnotateStatementElements(elem, env)}`;
|
|
384
378
|
env.path.pop();
|
|
385
379
|
}
|
|
386
380
|
else if (elem.enum) {
|
|
387
381
|
env.path.push('enum');
|
|
388
|
-
result += ` ${renderAnnotateStatementElements(elem
|
|
382
|
+
result += ` ${renderAnnotateStatementElements(elem, env)}`;
|
|
389
383
|
env.path.pop();
|
|
390
384
|
}
|
|
391
385
|
|
|
392
386
|
result += ';\n';
|
|
393
387
|
}
|
|
394
|
-
env.path.length -=
|
|
388
|
+
env.path.length -= 2;
|
|
395
389
|
env.decreaseIndent();
|
|
396
390
|
result += `${env.indent}}`;
|
|
397
391
|
return result;
|
|
@@ -416,7 +410,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
416
410
|
if (ext.returns.elements) {
|
|
417
411
|
// Annotations are on separate lines: Have it aligned nicely
|
|
418
412
|
result += returnAnnos ? `${env.indent}` : ' ';
|
|
419
|
-
result += renderAnnotateStatementElements(ext.returns
|
|
413
|
+
result += renderAnnotateStatementElements(ext.returns, env);
|
|
420
414
|
}
|
|
421
415
|
return result;
|
|
422
416
|
}
|
|
@@ -424,18 +418,20 @@ function csnToCdl( csn, options, msg ) {
|
|
|
424
418
|
/**
|
|
425
419
|
* Render a parameter list for `annotate` statements, in parentheses `()`.
|
|
426
420
|
*
|
|
427
|
-
* @param {CSN.Artifact}
|
|
421
|
+
* @param {CSN.Artifact} ext
|
|
428
422
|
* @param {CdlRenderEnvironment} env
|
|
429
423
|
* @return {string}
|
|
430
424
|
*/
|
|
431
|
-
function renderAnnotateParamsInParentheses(
|
|
425
|
+
function renderAnnotateParamsInParentheses( ext, env ) {
|
|
432
426
|
const childEnv = env.withIncreasedIndent();
|
|
433
427
|
let result = '(\n';
|
|
434
428
|
const paramAnnotations = [];
|
|
435
|
-
forEach(
|
|
429
|
+
forEach(ext.params, (paramName, param) => {
|
|
436
430
|
const annos = renderAnnotationAssignmentsAndDocComment(param, childEnv);
|
|
437
|
-
const name = quoteNonIdentifierOrKeyword(paramName,
|
|
438
|
-
|
|
431
|
+
const name = quoteNonIdentifierOrKeyword(paramName, childEnv);
|
|
432
|
+
// Not supported, yet (#13052)
|
|
433
|
+
// const sub = (param.elements || param.enum) ? ` ${renderAnnotateStatementElements(param, childEnv)}` : '';
|
|
434
|
+
paramAnnotations.push( annos + childEnv.indent + name);
|
|
439
435
|
});
|
|
440
436
|
result += `${paramAnnotations.join(',\n')}\n${env.indent})`;
|
|
441
437
|
return result;
|
|
@@ -686,7 +682,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
686
682
|
let obj = annotate;
|
|
687
683
|
for (let i = 2; i < env.path.length; ++i) {
|
|
688
684
|
const key = env.path[i];
|
|
689
|
-
if (key === 'elements' || key === 'actions') {
|
|
685
|
+
if (key === 'elements' || key === 'actions' || key === 'params') {
|
|
690
686
|
obj[key] = Object.create(null);
|
|
691
687
|
const elem = env.path[i + 1];
|
|
692
688
|
obj[key][elem] = {};
|
|
@@ -1251,7 +1247,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1251
1247
|
*/
|
|
1252
1248
|
function renderParameters( art, env ) {
|
|
1253
1249
|
const childEnv = env.withIncreasedIndent();
|
|
1254
|
-
const parameters = Object.keys(art.params || {}).map(name => renderParameter(name, art.params[name], childEnv
|
|
1250
|
+
const parameters = Object.keys(art.params || {}).map(name => renderParameter(name, art.params[name], childEnv));
|
|
1255
1251
|
if (parameters.length === 0)
|
|
1256
1252
|
return '()';
|
|
1257
1253
|
return `(\n${parameters.join(',\n')}\n${env.indent})`;
|
package/lib/render/toSql.js
CHANGED
|
@@ -415,7 +415,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
415
415
|
const tableName = renderArtifactName(artifactName);
|
|
416
416
|
deletionsDuplicateChecker.addArtifact(tableName, art.$location, artifactName);
|
|
417
417
|
|
|
418
|
-
addDeletion(resultObj, artifactName,
|
|
418
|
+
addDeletion(resultObj, artifactName, `-- [WARNING] this statement is lossy\nDROP TABLE ${tableName}`);
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
// Render an artifact migration into the appropriate dictionary of 'resultObj'.
|
|
@@ -484,7 +484,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
484
484
|
|
|
485
485
|
// Remove columns.
|
|
486
486
|
if (removeCols.length)
|
|
487
|
-
addMigration(resultObj, artifactName, true, render.dropColumns(artifactName, removeCols));
|
|
487
|
+
addMigration(resultObj, artifactName, true, render.dropColumns(artifactName, removeCols).map(s => (options.src !== 'hdi' ? `-- [WARNING] this statement is lossy\n${s}` : s)));
|
|
488
488
|
|
|
489
489
|
// Remove associations.
|
|
490
490
|
removeAssocs.forEach(assoc => addMigration(resultObj, artifactName, true, render.dropAssociation(artifactName, assoc)));
|
|
@@ -550,7 +550,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
550
550
|
}
|
|
551
551
|
}
|
|
552
552
|
|
|
553
|
-
if (options.sqlChangeMode === 'drop' || def.old.target || def.new.target || reducesTypeSize(def)) {
|
|
553
|
+
if (options.sqlChangeMode === 'drop' || def.old.target || def.new.target || (reducesTypeSize(def) && options.src === 'hdi')) {
|
|
554
554
|
// Lossy change because either an association is removed and/or added, or the type size is reduced.
|
|
555
555
|
// Drop old element and re-add it in its new shape.
|
|
556
556
|
const drop = def.old.target
|
|
@@ -559,10 +559,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
559
559
|
const add = def.new.target
|
|
560
560
|
? render.addAssociations(artifactName, { [eltName]: def.new }, env)
|
|
561
561
|
: render.addColumnsFromElementsObj(artifactName, { [eltName]: def.new }, env);
|
|
562
|
-
addMigration(resultObj, artifactName, true, render.concat(...drop, ...add));
|
|
562
|
+
addMigration(resultObj, artifactName, true, render.concat(...drop, ...add).map(s => (def.lossy && options.src !== 'hdi' ? `-- [WARNING] this statement could be lossy\n${s}` : s)));
|
|
563
563
|
}
|
|
564
564
|
else { // Lossless change: no associations directly affected, no size reduction.
|
|
565
|
-
addMigration(resultObj, artifactName, false, render.alterColumns(artifactName, sqlId, def, eltStrNew, eltName, activateAlterMode(env, 'migration')));
|
|
565
|
+
addMigration(resultObj, artifactName, false, render.alterColumns(artifactName, sqlId, def, eltStrNew, eltName, activateAlterMode(env, 'migration')).map(s => (def.lossy ? `-- [WARNING] this statement could be lossy\n${s}` : s)));
|
|
566
566
|
}
|
|
567
567
|
}
|
|
568
568
|
}
|
|
@@ -306,45 +306,82 @@ function applyTransformationsOnDictionary( dictionary, customTransformers = {},
|
|
|
306
306
|
* used primarily to transform annotation expressions.
|
|
307
307
|
* If propName is undefined, all properties of parent are transformed.
|
|
308
308
|
* @param {object} parent Start node
|
|
309
|
-
* @param {string}
|
|
309
|
+
* @param {string|number} parentName Start at specific property of parent
|
|
310
310
|
* @param {object} transformers Map of callback functions
|
|
311
311
|
* @param {CSN.Path} path Path to parent
|
|
312
|
+
* @param {object} ctx bucket to tunnel various info into the transformers
|
|
312
313
|
* @returns {object} transformed node
|
|
313
314
|
*/
|
|
314
|
-
function transformExpression( parent,
|
|
315
|
-
const callT = (t,
|
|
316
|
-
const ct = t[
|
|
315
|
+
function transformExpression( parent, parentName, transformers, path = [], ctx = undefined ) {
|
|
316
|
+
const callT = (t, childName, child) => {
|
|
317
|
+
const ct = t[childName];
|
|
317
318
|
if (ct) {
|
|
318
|
-
const ppn = propName;
|
|
319
319
|
if (Array.isArray(ct))
|
|
320
|
-
ct.forEach(cti => cti(child,
|
|
320
|
+
ct.forEach(cti => cti(child, childName, child[childName], path, parent, parentName, ctx));
|
|
321
321
|
else
|
|
322
|
-
ct(child,
|
|
322
|
+
ct(child, childName, child[childName], path, parent, parentName, ctx);
|
|
323
323
|
}
|
|
324
324
|
};
|
|
325
|
+
if (parentName != null) {
|
|
326
|
+
const child = parent[parentName];
|
|
327
|
+
if (!child || typeof child !== 'object' ||
|
|
328
|
+
!{}.propertyIsEnumerable.call( parent, parentName ))
|
|
329
|
+
return parent;
|
|
330
|
+
|
|
331
|
+
path = [ ...path, parentName ];
|
|
332
|
+
if (Array.isArray(child)) {
|
|
333
|
+
child.forEach( (n, i) => transformExpression( child, i, transformers, path, ctx ) );
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
for (const childName of Object.getOwnPropertyNames( child )) {
|
|
337
|
+
if (Array.isArray(transformers))
|
|
338
|
+
transformers.forEach(t => callT(t, childName, child));
|
|
339
|
+
else
|
|
340
|
+
callT(transformers, childName, child);
|
|
341
|
+
transformExpression(child, childName, transformers, path, ctx);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
for (parentName of Object.getOwnPropertyNames( parent ))
|
|
347
|
+
transformExpression( parent, parentName, transformers, path, ctx );
|
|
348
|
+
}
|
|
349
|
+
return parent;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Drill into an annotation value and inspect each (sub-)object value if it is
|
|
354
|
+
* an annotation expression. If so, call the real transformExpression that will
|
|
355
|
+
* execute the callbacks (most likely reference rewriting), continue otherwise
|
|
356
|
+
*
|
|
357
|
+
* @param {object} parent Start node
|
|
358
|
+
* @param {string|number} propName Start at specific property of parent
|
|
359
|
+
* @param {object} transformers Map of callback functions
|
|
360
|
+
* @param {CSN.Path} path Path to parent
|
|
361
|
+
* @returns {object} transformed node
|
|
362
|
+
*/
|
|
363
|
+
function transformAnnotationExpression( parent, propName, transformers, path = [] ) {
|
|
325
364
|
if (propName != null) {
|
|
326
365
|
const child = parent[propName];
|
|
327
366
|
if (!child || typeof child !== 'object' ||
|
|
328
367
|
!{}.propertyIsEnumerable.call( parent, propName ))
|
|
329
368
|
return parent;
|
|
330
369
|
|
|
370
|
+
if (isAnnotationExpression(child))
|
|
371
|
+
return transformExpression(parent, propName, transformers, path, { annoExpr: child });
|
|
372
|
+
|
|
331
373
|
path = [ ...path, propName ];
|
|
332
374
|
if (Array.isArray(child)) {
|
|
333
|
-
child.forEach( (n, i) =>
|
|
375
|
+
child.forEach( (n, i) => transformAnnotationExpression( child, i, transformers, path ) );
|
|
334
376
|
}
|
|
335
377
|
else {
|
|
336
|
-
for (const cpn of Object.getOwnPropertyNames( child ))
|
|
337
|
-
|
|
338
|
-
transformers.forEach(t => callT(t, cpn, child));
|
|
339
|
-
else
|
|
340
|
-
callT(transformers, cpn, child);
|
|
341
|
-
transformExpression(child, cpn, transformers, path);
|
|
342
|
-
}
|
|
378
|
+
for (const cpn of Object.getOwnPropertyNames( child ))
|
|
379
|
+
transformAnnotationExpression(child, cpn, transformers, path);
|
|
343
380
|
}
|
|
344
381
|
}
|
|
345
382
|
else {
|
|
346
383
|
for (propName of Object.getOwnPropertyNames( parent ))
|
|
347
|
-
|
|
384
|
+
transformAnnotationExpression( parent, propName, transformers, path );
|
|
348
385
|
}
|
|
349
386
|
return parent;
|
|
350
387
|
}
|
|
@@ -384,6 +421,7 @@ function mergeTransformers( transformers, that ) {
|
|
|
384
421
|
module.exports = {
|
|
385
422
|
mergeTransformers,
|
|
386
423
|
transformExpression,
|
|
424
|
+
transformAnnotationExpression,
|
|
387
425
|
applyTransformations,
|
|
388
426
|
applyTransformationsOnNonDictionary,
|
|
389
427
|
applyTransformationsOnDictionary,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { forEachDefinition, forEachMemberRecursively,
|
|
4
4
|
getServiceNames, applyAnnotationsFromExtensions,
|
|
5
|
-
|
|
5
|
+
transformAnnotationExpression } = require('../../model/csnUtils');
|
|
6
6
|
const { forEach } = require('../../utils/objectUtils');
|
|
7
7
|
const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
|
|
8
8
|
const { getTransformers } = require('../transformUtils');
|
|
@@ -231,17 +231,16 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
231
231
|
function $draft2$self(member) {
|
|
232
232
|
Object.keys(member).forEach(pn => {
|
|
233
233
|
if(pn[0] === '@') {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
234
|
+
transformAnnotationExpression(member, pn, {
|
|
235
|
+
ref: (_parent, _prop, xpr, _path, _p, _ppn, ctx) => {
|
|
236
|
+
if(xpr[0] === '$draft') {
|
|
237
|
+
xpr[0] = '$self';
|
|
238
|
+
if(ctx?.annoExpr?.['='])
|
|
239
|
+
ctx.annoExpr['='] = true;
|
|
240
|
+
}
|
|
240
241
|
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (refChanged)
|
|
244
|
-
member[pn]['='] = true;
|
|
242
|
+
},
|
|
243
|
+
);
|
|
245
244
|
}
|
|
246
245
|
});
|
|
247
246
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
forEachDefinition, forEachMemberRecursively,
|
|
4
|
+
forEachDefinition, forEachMemberRecursively, applyTransformationsOnNonDictionary, transformExpression, transformAnnotationExpression,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const { getStructStepsFlattener } = require('../db/flattening');
|
|
7
7
|
const { setProp } = require('../../base/model');
|
|
@@ -15,7 +15,7 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
15
15
|
* @param csnUtils
|
|
16
16
|
* @param messageFunctions
|
|
17
17
|
*/
|
|
18
|
-
function flattenRefs(
|
|
18
|
+
function flattenRefs(csn, options, csnUtils, messageFunctions) {
|
|
19
19
|
const cleanup = [];
|
|
20
20
|
forEachDefinition(csn, (artifact) => {
|
|
21
21
|
if (artifact.elements) {
|
|
@@ -36,15 +36,11 @@ function flattenRefs( csn, options, csnUtils, messageFunctions ) {
|
|
|
36
36
|
|
|
37
37
|
// Absolutify paths in annotation expressions
|
|
38
38
|
Object.keys(element)
|
|
39
|
-
.filter(pn =>
|
|
39
|
+
.filter(pn => pn.startsWith('@') && element[pn])
|
|
40
40
|
.forEach((anno) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
ref: absolutifier,
|
|
45
|
-
}, []);
|
|
46
|
-
},
|
|
47
|
-
}, {}, []);
|
|
41
|
+
transformAnnotationExpression(element, anno, {
|
|
42
|
+
ref: absolutifier,
|
|
43
|
+
}, []);
|
|
48
44
|
if (element[anno].ref)
|
|
49
45
|
absolutifier(element[anno], 'ref', element[anno].ref);
|
|
50
46
|
});
|
|
@@ -83,17 +79,17 @@ function flattenRefs( csn, options, csnUtils, messageFunctions ) {
|
|
|
83
79
|
};
|
|
84
80
|
|
|
85
81
|
Object.keys(a)
|
|
86
|
-
.filter(pn =>
|
|
82
|
+
.filter(pn => pn.startsWith('@') && a[pn])
|
|
87
83
|
.forEach((pn) => {
|
|
88
|
-
|
|
84
|
+
transformAnnotationExpression(a, pn, [ markBindingParam, refFlattener ], [ 'definitions', defName, 'actions', an ]);
|
|
89
85
|
adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
|
|
90
86
|
adaptRefs.length = 0;
|
|
91
87
|
});
|
|
92
88
|
|
|
93
89
|
|
|
94
90
|
forEachMemberRecursively(a, (member, memberName, prop, path) => {
|
|
95
|
-
Object.keys(member).filter(pn =>
|
|
96
|
-
|
|
91
|
+
Object.keys(member).filter(pn => pn.startsWith('@') && member[pn]).forEach((pn) => {
|
|
92
|
+
transformAnnotationExpression(member, pn, [ markBindingParam, refFlattener ], path);
|
|
97
93
|
adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
|
|
98
94
|
adaptRefs.length = 0;
|
|
99
95
|
});
|