@sap/cds-compiler 3.3.2 → 3.4.2
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 +33 -0
- package/bin/cdsc.js +3 -1
- package/doc/CHANGELOG_BETA.md +17 -0
- package/lib/api/main.js +147 -18
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/keywords.js +104 -0
- package/lib/base/message-registry.js +137 -68
- package/lib/base/messages.js +59 -48
- package/lib/base/model.js +1 -0
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +13 -8
- package/lib/checks/defaultValues.js +3 -1
- package/lib/checks/elements.js +1 -1
- package/lib/checks/parameters.js +4 -2
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/validator.js +14 -4
- package/lib/compiler/assert-consistency.js +8 -7
- package/lib/compiler/checks.js +30 -20
- package/lib/compiler/define.js +89 -25
- package/lib/compiler/extend.js +33 -28
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/populate.js +30 -8
- package/lib/compiler/propagator.js +23 -28
- package/lib/compiler/resolve.js +11 -5
- package/lib/compiler/shared.js +66 -48
- package/lib/compiler/tweak-assocs.js +2 -3
- package/lib/compiler/utils.js +11 -0
- package/lib/edm/annotations/genericTranslation.js +7 -4
- package/lib/edm/csn2edm.js +1 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3565 -3544
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +140 -158
- package/lib/json/to-csn.js +23 -5
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +7 -10
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +115 -84
- package/lib/language/language.g4 +29 -25
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.js +1 -0
- package/lib/model/csnRefs.js +4 -3
- package/lib/model/csnUtils.js +39 -7
- package/lib/model/sortViews.js +7 -3
- package/lib/modelCompare/compare.js +49 -15
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +5 -1
- package/lib/render/manageConstraints.js +9 -5
- package/lib/render/toCdl.js +120 -62
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +6 -2
- package/lib/render/utils/common.js +7 -0
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +11 -4
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +7 -1
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +7 -3
- package/lib/transform/forRelationalDB.js +12 -6
- package/lib/transform/localized.js +1 -1
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +23 -14
- package/lib/transform/translateAssocsToJoins.js +12 -12
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +1 -1
|
@@ -25,9 +25,9 @@ const {
|
|
|
25
25
|
* of whitespace characters removed.
|
|
26
26
|
*/
|
|
27
27
|
function stripIndentation(str) {
|
|
28
|
-
if (str === '')
|
|
29
|
-
return ['', 0];
|
|
30
|
-
|
|
28
|
+
if (str === '')
|
|
29
|
+
return [ '', 0 ];
|
|
30
|
+
|
|
31
31
|
|
|
32
32
|
// Note: We have to check all newline characters, as the string is not normalized, yet.
|
|
33
33
|
const lines = str.split(cdlNewLineRegEx);
|
|
@@ -39,20 +39,19 @@ function stripIndentation(str) {
|
|
|
39
39
|
// If there is a trailing line break, it means that ``` is on newline and
|
|
40
40
|
// therefore the indentation to remove is 0.
|
|
41
41
|
// Remove the last newline, which may be CRLF.
|
|
42
|
-
return [lines.slice(0, -1).join('\n'), 0];
|
|
42
|
+
return [ lines.slice(0, -1).join('\n'), 0 ];
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const minIndent = lines.reduce((min, line, index) => {
|
|
46
46
|
// Note: Last line is the line containing ```. There, we always count the indentation,
|
|
47
47
|
// even if blank. For all other lines, blank lines are ignored.
|
|
48
|
-
if (isWhitespaceOrNewLineOnly(line) && index !== (n-1))
|
|
48
|
+
if (isWhitespaceOrNewLineOnly(line) && index !== (n - 1))
|
|
49
49
|
return min;
|
|
50
50
|
|
|
51
51
|
let count = 0;
|
|
52
52
|
const length = Math.min(min, line.length);
|
|
53
|
-
while (count < length && isWhitespaceCharacterNoNewline(line[count]))
|
|
53
|
+
while (count < length && isWhitespaceCharacterNoNewline(line[count]))
|
|
54
54
|
count++;
|
|
55
|
-
}
|
|
56
55
|
return Math.min(min, count);
|
|
57
56
|
}, Number.MAX_SAFE_INTEGER);
|
|
58
57
|
|
|
@@ -63,10 +62,10 @@ function stripIndentation(str) {
|
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
// Remove trailing last line, if there was nothing else in that line.
|
|
66
|
-
if (lines[n-1] === '')
|
|
65
|
+
if (lines[n - 1] === '')
|
|
67
66
|
lines.pop();
|
|
68
67
|
|
|
69
|
-
return [lines.join('\n'), minIndent];
|
|
68
|
+
return [ lines.join('\n'), minIndent ];
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
class MultiLineStringParser {
|
|
@@ -75,9 +74,9 @@ class MultiLineStringParser {
|
|
|
75
74
|
this.token = token;
|
|
76
75
|
this.str = token.text; // Copy because .text is a getter
|
|
77
76
|
|
|
78
|
-
if (this.str[0] !== '`' || this.str[this.str.length-1] !== '`')
|
|
77
|
+
if (this.str[0] !== '`' || this.str[this.str.length - 1] !== '`')
|
|
78
|
+
// eslint-disable-next-line max-len
|
|
79
79
|
throw new Error('Invalid multi-line string sequence: Require string to be surrounded by back-ticks!');
|
|
80
|
-
}
|
|
81
80
|
|
|
82
81
|
this.output = [];
|
|
83
82
|
this.isTextBlock = this.str.startsWith('```');
|
|
@@ -90,7 +89,8 @@ class MultiLineStringParser {
|
|
|
90
89
|
if (this.isTextBlock) {
|
|
91
90
|
this.i = 3;
|
|
92
91
|
this.end = this.str.length - 3;
|
|
93
|
-
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
94
|
this.i = 1;
|
|
95
95
|
this.end = this.str.length - 1;
|
|
96
96
|
}
|
|
@@ -102,9 +102,8 @@ class MultiLineStringParser {
|
|
|
102
102
|
* @return {string}
|
|
103
103
|
*/
|
|
104
104
|
parse() {
|
|
105
|
-
if (this.str.length === 2)
|
|
105
|
+
if (this.str.length === 2)
|
|
106
106
|
return ''; // Nothing to do: ``
|
|
107
|
-
}
|
|
108
107
|
|
|
109
108
|
if (this.isTextBlock) {
|
|
110
109
|
// If there are no line breaks, emit an error as normal single-back-tick
|
|
@@ -112,7 +111,7 @@ class MultiLineStringParser {
|
|
|
112
111
|
// there is no text without at least one line break.
|
|
113
112
|
if (!cdlNewLineRegEx.test(this.str)) {
|
|
114
113
|
const loc = this._locationForCharacters(this.end, 1);
|
|
115
|
-
this.parser.
|
|
114
|
+
this.parser.error('syntax-invalid-text-block', loc);
|
|
116
115
|
return '';
|
|
117
116
|
}
|
|
118
117
|
this._skipOptionalLanguageIdentifierLine();
|
|
@@ -135,7 +134,7 @@ class MultiLineStringParser {
|
|
|
135
134
|
// Note: Index is at first character of string
|
|
136
135
|
|
|
137
136
|
do {
|
|
138
|
-
switch(this._current()) {
|
|
137
|
+
switch (this._current()) {
|
|
139
138
|
case this._matchLineBreakAtCurrentChar():
|
|
140
139
|
this.output.push('\n');
|
|
141
140
|
break;
|
|
@@ -146,8 +145,8 @@ class MultiLineStringParser {
|
|
|
146
145
|
case '$':
|
|
147
146
|
if (this._lookahead() === '{') {
|
|
148
147
|
const loc = this._locationForCharacters(this.i, 2);
|
|
149
|
-
this.parser.
|
|
150
|
-
|
|
148
|
+
this.parser.error('syntax-missing-escape', loc,
|
|
149
|
+
{ '#': 'placeholder', code: '${', newcode: '\\${' });
|
|
151
150
|
}
|
|
152
151
|
this.output.push(this.str[this.i]);
|
|
153
152
|
break;
|
|
@@ -155,7 +154,7 @@ class MultiLineStringParser {
|
|
|
155
154
|
this.output.push(this.str[this.i]);
|
|
156
155
|
break;
|
|
157
156
|
}
|
|
158
|
-
} while(this._move());
|
|
157
|
+
} while (this._move());
|
|
159
158
|
|
|
160
159
|
return this.output.join('');
|
|
161
160
|
}
|
|
@@ -166,27 +165,27 @@ class MultiLineStringParser {
|
|
|
166
165
|
* @private
|
|
167
166
|
*/
|
|
168
167
|
_innerEscape() {
|
|
169
|
-
switch(this._current()) {
|
|
168
|
+
switch (this._current()) {
|
|
170
169
|
case this._matchLineBreakAtCurrentChar():
|
|
171
170
|
// Don't add to output -> line break is escaped
|
|
172
171
|
break;
|
|
173
172
|
case 'b': // backspace
|
|
174
|
-
this.output.push(
|
|
173
|
+
this.output.push('\b');
|
|
175
174
|
break;
|
|
176
175
|
case 'f': // form feed
|
|
177
|
-
this.output.push(
|
|
176
|
+
this.output.push('\f');
|
|
178
177
|
break;
|
|
179
178
|
case 'v': // vertical tabulator
|
|
180
|
-
this.output.push(
|
|
179
|
+
this.output.push('\v');
|
|
181
180
|
break;
|
|
182
181
|
case 'r': // carriage return
|
|
183
|
-
this.output.push(
|
|
182
|
+
this.output.push('\r');
|
|
184
183
|
break;
|
|
185
184
|
case 'n': // line feed
|
|
186
|
-
this.output.push(
|
|
185
|
+
this.output.push('\n');
|
|
187
186
|
break;
|
|
188
187
|
case 't': // tab
|
|
189
|
-
this.output.push(
|
|
188
|
+
this.output.push('\t');
|
|
190
189
|
break;
|
|
191
190
|
case '\\':
|
|
192
191
|
case '"':
|
|
@@ -206,21 +205,23 @@ class MultiLineStringParser {
|
|
|
206
205
|
break;
|
|
207
206
|
case '0': // null terminator
|
|
208
207
|
if (!/^\d$/.test(this._lookahead())) {
|
|
209
|
-
this.output.push(
|
|
208
|
+
this.output.push('\0');
|
|
210
209
|
break;
|
|
211
210
|
}
|
|
212
211
|
// Let the default case handle octal representation.
|
|
213
212
|
// fallthrough
|
|
214
213
|
default: {
|
|
215
214
|
this.output.push(this._current());
|
|
216
|
-
const loc = this._locationForCharacters(this.i-1, 2);
|
|
215
|
+
const loc = this._locationForCharacters(this.i - 1, 2);
|
|
217
216
|
if (/\s/.test(this._current())) {
|
|
218
|
-
this.parser.
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
217
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'whitespace' });
|
|
218
|
+
}
|
|
219
|
+
else if (/\d/.test(this._current())) {
|
|
220
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'octal' });
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
const code = this._makeCode(`\\${ this._current() }`);
|
|
224
|
+
this.parser.message('syntax-unknown-escape', loc, { '#': 'std', code });
|
|
224
225
|
}
|
|
225
226
|
break;
|
|
226
227
|
}
|
|
@@ -239,9 +240,9 @@ class MultiLineStringParser {
|
|
|
239
240
|
// syntax variant as the first invalid code point is \u{110000}
|
|
240
241
|
// and an empty `codePoint` is only possible with the braced variant.
|
|
241
242
|
const reportInvalidCodePoint = () => {
|
|
242
|
-
const code = this._makeCode(`\\u{${codePoint}}`);
|
|
243
|
-
const loc = this._locationForCharacters(this.i-codePoint.length, codePoint.length);
|
|
244
|
-
this.parser.
|
|
243
|
+
const code = this._makeCode(`\\u{${ codePoint }}`);
|
|
244
|
+
const loc = this._locationForCharacters(this.i - codePoint.length, codePoint.length);
|
|
245
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'codepoint', code });
|
|
245
246
|
};
|
|
246
247
|
|
|
247
248
|
const n = Number.parseInt(codePoint, 16);
|
|
@@ -252,7 +253,8 @@ class MultiLineStringParser {
|
|
|
252
253
|
|
|
253
254
|
try {
|
|
254
255
|
this.output.push(String.fromCodePoint(n));
|
|
255
|
-
}
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
256
258
|
// RangeError is thrown if number isn't a valid code point
|
|
257
259
|
reportInvalidCodePoint();
|
|
258
260
|
}
|
|
@@ -275,18 +277,20 @@ class MultiLineStringParser {
|
|
|
275
277
|
if (!this._eos() && /^[\dA-Fa-f]$/.test(this._lookahead())) {
|
|
276
278
|
this._move();
|
|
277
279
|
codePoint += this._current();
|
|
278
|
-
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
279
282
|
break;
|
|
280
283
|
}
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
if (codePoint.length === count) {
|
|
284
287
|
this._parseHexCodePoint(codePoint);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
this.
|
|
289
|
-
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
const loc = this._locationForCharacters(this.i + 1, 1);
|
|
291
|
+
const code = this._eos(this.i + 1) ? `\\${ mode }${ codePoint }` : `\\${ mode }${ codePoint }${ this._lookahead() }`;
|
|
292
|
+
this.parser.error('syntax-invalid-escape', loc,
|
|
293
|
+
{ '#': 'hex-count', number: count, code: this._makeCode(code) });
|
|
290
294
|
}
|
|
291
295
|
}
|
|
292
296
|
|
|
@@ -305,14 +309,17 @@ class MultiLineStringParser {
|
|
|
305
309
|
if (/^[\dA-Fa-f]$/.test(this._lookahead())) {
|
|
306
310
|
this._move();
|
|
307
311
|
codePoint += this._current();
|
|
308
|
-
}
|
|
312
|
+
}
|
|
313
|
+
else if (this._lookahead() === '}') {
|
|
309
314
|
break;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
this.
|
|
315
|
+
}
|
|
316
|
+
else if (!this._eos(this.i + 1)) {
|
|
317
|
+
const loc = this._locationForCharacters(this.i + 1, 1); // Point to the exact character
|
|
318
|
+
const code = this._makeCode(`\\u{${ codePoint }${ this._lookahead() }…}`);
|
|
319
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'unicode-hex', code });
|
|
314
320
|
return;
|
|
315
|
-
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
316
323
|
break;
|
|
317
324
|
}
|
|
318
325
|
}
|
|
@@ -320,9 +327,10 @@ class MultiLineStringParser {
|
|
|
320
327
|
if (this._lookahead() === '}') {
|
|
321
328
|
this._move();
|
|
322
329
|
this._parseHexCodePoint(codePoint);
|
|
323
|
-
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
324
332
|
const loc = this._locationForCharacters(this.i, 1);
|
|
325
|
-
this.parser.
|
|
333
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'unicode-brace' });
|
|
326
334
|
}
|
|
327
335
|
}
|
|
328
336
|
|
|
@@ -333,7 +341,7 @@ class MultiLineStringParser {
|
|
|
333
341
|
* @private
|
|
334
342
|
*/
|
|
335
343
|
_skipOptionalLanguageIdentifierLine() {
|
|
336
|
-
while(!this._eos()) {
|
|
344
|
+
while (!this._eos()) {
|
|
337
345
|
switch (this._current()) {
|
|
338
346
|
case this._matchLineBreakAtCurrentChar():
|
|
339
347
|
this._move();
|
|
@@ -342,7 +350,7 @@ class MultiLineStringParser {
|
|
|
342
350
|
// Do not allow an escape in the language identifier. If at the line's end, users
|
|
343
351
|
// may expect the identifier to span more than the first line, which is _not_ the case.
|
|
344
352
|
const loc = this._locationForCharacters(this.i, 1);
|
|
345
|
-
this.parser.
|
|
353
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'language-identifier' });
|
|
346
354
|
this._move();
|
|
347
355
|
break;
|
|
348
356
|
}
|
|
@@ -367,7 +375,7 @@ class MultiLineStringParser {
|
|
|
367
375
|
*/
|
|
368
376
|
_matchLineBreakAtCurrentChar() {
|
|
369
377
|
// Only increase line number for \n, because ANTLR does the same
|
|
370
|
-
switch(this._current()) {
|
|
378
|
+
switch (this._current()) {
|
|
371
379
|
case '\r':
|
|
372
380
|
if (this._lookahead() === '\n') {
|
|
373
381
|
this._move(); // \r\n is normalized
|
|
@@ -382,6 +390,7 @@ class MultiLineStringParser {
|
|
|
382
390
|
case '\u2028': // LS
|
|
383
391
|
case '\u2029': // PS
|
|
384
392
|
return this._current();
|
|
393
|
+
default: break;
|
|
385
394
|
}
|
|
386
395
|
return null;
|
|
387
396
|
}
|
|
@@ -419,7 +428,7 @@ class MultiLineStringParser {
|
|
|
419
428
|
* @returns {string}
|
|
420
429
|
*/
|
|
421
430
|
_lookahead() {
|
|
422
|
-
return this.str[this.i+1];
|
|
431
|
+
return this.str[this.i + 1];
|
|
423
432
|
}
|
|
424
433
|
|
|
425
434
|
/**
|
|
@@ -439,7 +448,7 @@ class MultiLineStringParser {
|
|
|
439
448
|
* @returns {string}
|
|
440
449
|
*/
|
|
441
450
|
_previous() {
|
|
442
|
-
return this.str[this.i-1];
|
|
451
|
+
return this.str[this.i - 1];
|
|
443
452
|
}
|
|
444
453
|
|
|
445
454
|
/**
|
|
@@ -455,8 +464,12 @@ class MultiLineStringParser {
|
|
|
455
464
|
file: this.parser.filename,
|
|
456
465
|
line: this.token.line + this._lineInString,
|
|
457
466
|
endLine: this.token.line + this._lineInString,
|
|
458
|
-
col: this._lineInString > 0
|
|
459
|
-
|
|
467
|
+
col: this._lineInString > 0
|
|
468
|
+
? i - this._currentLineBreakIndex + this._indentation
|
|
469
|
+
: this.token.column + i + 1,
|
|
470
|
+
endCol: this._lineInString > 0
|
|
471
|
+
? i - this._currentLineBreakIndex + width + this._indentation
|
|
472
|
+
: this.token.column + i + width + 1,
|
|
460
473
|
};
|
|
461
474
|
}
|
|
462
475
|
|
|
@@ -468,7 +481,7 @@ class MultiLineStringParser {
|
|
|
468
481
|
* @param {string} code
|
|
469
482
|
* @private
|
|
470
483
|
*/
|
|
471
|
-
_makeCode(code) {
|
|
484
|
+
_makeCode(code) { // eslint-disable-line class-methods-use-this
|
|
472
485
|
// For characters that may be rendered as newline,
|
|
473
486
|
// see <https://www.unicode.org/reports/tr14/tr14-32.html>.
|
|
474
487
|
//
|
|
@@ -484,9 +497,8 @@ class MultiLineStringParser {
|
|
|
484
497
|
//
|
|
485
498
|
// For Visualization, see <https://en.wikipedia.org/wiki/Newline#Unicode>
|
|
486
499
|
// U+23CE: ⏎
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
return code.replace(allPossibleNewLineCharacters, '\u{23CE}');
|
|
500
|
+
const allNewLineCharacters = /[\u{000A}\u{000B}\u{000C}\u{000D}\u{0085}\u{2028}\u{2029}]/ug;
|
|
501
|
+
return code.replace(allNewLineCharacters, '\u{23CE}');
|
|
490
502
|
}
|
|
491
503
|
}
|
|
492
504
|
|
package/lib/main.js
CHANGED
|
@@ -115,6 +115,7 @@ module.exports = {
|
|
|
115
115
|
functions: Object.freeze([ ...keywords.cdl_functions ] ),
|
|
116
116
|
}),
|
|
117
117
|
sql: Object.assign((...args) => snapi.sql(...args), {
|
|
118
|
+
migration: (...args) => snapi.sql.migration(...args),
|
|
118
119
|
sqlite: {
|
|
119
120
|
keywords: Object.freeze([ ...keywords.sqlite ] )
|
|
120
121
|
},
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -924,14 +924,15 @@ function startCsnPath( csnPath, csn ) {
|
|
|
924
924
|
index: 1, main, parent, art,
|
|
925
925
|
};
|
|
926
926
|
}
|
|
927
|
-
if (csnPath.length < 2 || csnPath[0] !== 'definitions')
|
|
928
|
-
throw new CompilerAssertion( 'References outside definitions not supported yet');
|
|
929
|
-
const art = csn
|
|
927
|
+
if (csnPath.length < 2 || csnPath[0] !== 'definitions' && csnPath[0] !== 'vocabularies')
|
|
928
|
+
throw new CompilerAssertion( 'References outside definitions and vocabularies not supported yet');
|
|
929
|
+
const art = csn[csnPath[0]][csnPath[1]];
|
|
930
930
|
return {
|
|
931
931
|
index: 2, main: art, parent: null, art,
|
|
932
932
|
};
|
|
933
933
|
}
|
|
934
934
|
|
|
935
|
+
|
|
935
936
|
/**
|
|
936
937
|
* @param {CSN.Path} csnPath
|
|
937
938
|
* @param {CSN.Model} csn
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -1106,22 +1106,24 @@ const _dependents = Symbol('_dependents');
|
|
|
1106
1106
|
* _dependents: All artifacts that depend on this artifact (because they have a ref that points to it)
|
|
1107
1107
|
* _dependencies: All artifacts this artifact depends on (because it has a ref to it)
|
|
1108
1108
|
*
|
|
1109
|
-
* @param {
|
|
1109
|
+
* @param {CSN.Model} csn A CSN to enrich in-place
|
|
1110
|
+
* @param {object} refs csnRefs, only used for artifactRef
|
|
1110
1111
|
* @returns {object} CSN with _dependents/_dependencies set, "cleanup" function, _dependents/_dependencies Symbol used
|
|
1111
1112
|
*/
|
|
1112
|
-
function setDependencies( csn ) {
|
|
1113
|
+
function setDependencies( csn, refs = csnRefs(csn) ) {
|
|
1113
1114
|
const cleanup = [];
|
|
1114
|
-
const { artifactRef } =
|
|
1115
|
+
const { artifactRef } = refs;
|
|
1115
1116
|
|
|
1116
1117
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
1117
|
-
|
|
1118
|
+
const queries = getNormalizedQuery(artifact).query;
|
|
1119
|
+
if (queries) {
|
|
1118
1120
|
initDependencies(artifact);
|
|
1119
|
-
forAllQueries(
|
|
1120
|
-
if (query.SELECT
|
|
1121
|
+
forAllQueries(queries, (query) => {
|
|
1122
|
+
if (query.SELECT?.from) {
|
|
1121
1123
|
if (query.SELECT.from.args)
|
|
1122
1124
|
handleArgs(artifact, artifactName, query.SELECT.from.args);
|
|
1123
1125
|
|
|
1124
|
-
else if (typeof query.SELECT.from === 'string' || query.SELECT.from.ref
|
|
1126
|
+
else if (typeof query.SELECT.from === 'string' || query.SELECT.from.ref)
|
|
1125
1127
|
handleDependency(artifactRef(query.SELECT.from), artifact, artifactName);
|
|
1126
1128
|
}
|
|
1127
1129
|
}, [ 'definitions', artifactName, (artifact.projection ? 'projection' : 'query') ]);
|
|
@@ -1172,6 +1174,34 @@ function setDependencies( csn ) {
|
|
|
1172
1174
|
function isPersistedOnDatabase(art) {
|
|
1173
1175
|
return !(art.kind === 'entity' && (art.abstract || hasAnnotationValue(art, '@cds.persistence.skip')));
|
|
1174
1176
|
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Check if the given artifact will be persisted on the database via `CREATE VIEW`
|
|
1179
|
+
*
|
|
1180
|
+
* @param {CSN.Artifact} artifact
|
|
1181
|
+
* @returns {boolean}
|
|
1182
|
+
*/
|
|
1183
|
+
function isPersistedAsView(artifact) {
|
|
1184
|
+
return artifact && artifact.kind === 'entity' &&
|
|
1185
|
+
!artifact._ignore &&
|
|
1186
|
+
!artifact.abstract &&
|
|
1187
|
+
((artifact.query || artifact.projection) && !hasAnnotationValue(artifact, '@cds.persistence.table')) &&
|
|
1188
|
+
!hasAnnotationValue(artifact, '@cds.persistence.skip') &&
|
|
1189
|
+
!hasAnnotationValue(artifact, '@cds.persistence.exists');
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Check if the given artifact will be persisted on the database via `CREATE TABLE`
|
|
1193
|
+
*
|
|
1194
|
+
* @param {CSN.Artifact} artifact
|
|
1195
|
+
* @returns {boolean}
|
|
1196
|
+
*/
|
|
1197
|
+
function isPersistedAsTable(artifact) {
|
|
1198
|
+
return artifact.kind === 'entity' &&
|
|
1199
|
+
!artifact._ignore &&
|
|
1200
|
+
!artifact.abstract &&
|
|
1201
|
+
(!artifact.query && !artifact.projection || hasAnnotationValue(artifact, '@cds.persistence.table')) &&
|
|
1202
|
+
!hasAnnotationValue(artifact, '@cds.persistence.skip') &&
|
|
1203
|
+
!hasAnnotationValue(artifact, '@cds.persistence.exists');
|
|
1204
|
+
}
|
|
1175
1205
|
|
|
1176
1206
|
/**
|
|
1177
1207
|
* Central generated by cds-compiler string generator function without further decoration
|
|
@@ -1533,6 +1563,8 @@ module.exports = {
|
|
|
1533
1563
|
applyTransformationsOnDictionary,
|
|
1534
1564
|
setDependencies,
|
|
1535
1565
|
isPersistedOnDatabase,
|
|
1566
|
+
isPersistedAsView,
|
|
1567
|
+
isPersistedAsTable,
|
|
1536
1568
|
generatedByCompilerVersion,
|
|
1537
1569
|
getNormalizedQuery,
|
|
1538
1570
|
getRootArtifactName,
|
package/lib/model/sortViews.js
CHANGED
|
@@ -51,7 +51,7 @@ function _calculateDepth(definitionsArray, _dependents, _dependencies) {
|
|
|
51
51
|
nonZero.push([ artifactName, artifact ]);
|
|
52
52
|
}
|
|
53
53
|
else {
|
|
54
|
-
artifact.$pointers
|
|
54
|
+
delete artifact.$pointers;
|
|
55
55
|
zero.push([ artifactName, artifact ]);
|
|
56
56
|
}
|
|
57
57
|
});
|
|
@@ -66,10 +66,14 @@ function _findWithXPointers(definitionsArray, x, _dependents, _dependencies) {
|
|
|
66
66
|
const nonZero = [];
|
|
67
67
|
|
|
68
68
|
definitionsArray.forEach(([ artifactName, artifact ]) => {
|
|
69
|
-
if (artifact.$pointers !== undefined && artifact.$pointers === x)
|
|
69
|
+
if (artifact.$pointers !== undefined && artifact.$pointers === x) {
|
|
70
70
|
zero.push([ artifactName, artifact ]);
|
|
71
|
-
|
|
71
|
+
if (artifact.$pointers === 0)
|
|
72
|
+
delete artifact.$pointers;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
72
75
|
nonZero.push([ artifactName, artifact ]);
|
|
76
|
+
}
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
return {
|
|
@@ -4,9 +4,12 @@ const { makeMessageFunction } = require('../base/messages');
|
|
|
4
4
|
const {
|
|
5
5
|
forEachDefinition,
|
|
6
6
|
forEachMember,
|
|
7
|
-
|
|
7
|
+
isPersistedAsTable,
|
|
8
|
+
isPersistedAsView
|
|
8
9
|
} = require('../model/csnUtils');
|
|
9
10
|
const { isBetaEnabled } = require('../base/model');
|
|
11
|
+
// used to mark a view as changed so we know to drop-create it
|
|
12
|
+
const isChanged = Symbol('Marks a view as changed');
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Compares two models, in HANA-transformed CSN format, to each other.
|
|
@@ -14,7 +17,7 @@ const { isBetaEnabled } = require('../base/model');
|
|
|
14
17
|
* @param beforeModel the before-model
|
|
15
18
|
* @param afterModel the after-model
|
|
16
19
|
* @param {HdiOptions|false} options
|
|
17
|
-
* @returns {
|
|
20
|
+
* @returns {ModelDiff} the sets of deletions, extensions, and migrations of entities necessary to transform the before-model
|
|
18
21
|
* to the after-model, together with all the definitions of the after-model
|
|
19
22
|
*/
|
|
20
23
|
function compareModels(beforeModel, afterModel, options) {
|
|
@@ -46,17 +49,18 @@ function validateCsnVersions(beforeModel, afterModel, options) {
|
|
|
46
49
|
|
|
47
50
|
if (!beforeVersionParts || beforeVersionParts.length < 2) {
|
|
48
51
|
const { error, throwWithAnyError } = makeMessageFunction(beforeModel, options, 'modelCompare');
|
|
49
|
-
error(null, null, {},
|
|
52
|
+
error(null, null, { version: beforeVersion }, 'Invalid CSN version: $(VERSION)');
|
|
50
53
|
throwWithAnyError();
|
|
51
54
|
}
|
|
52
55
|
if (!afterVersionParts || afterVersionParts.length < 2) {
|
|
53
56
|
const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
|
|
54
|
-
error(null, null, {},
|
|
57
|
+
error(null, null, { version: afterVersion }, 'Invalid CSN version: $(VERSION)');
|
|
55
58
|
throwWithAnyError();
|
|
56
59
|
}
|
|
57
60
|
if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
|
|
58
61
|
const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
|
|
59
|
-
error(null, null, {
|
|
62
|
+
error(null, null, { value: afterVersion, othervalue: beforeVersion, version: require('../../package.json').version },
|
|
63
|
+
'Incompatible CSN versions: $(VALUE) is a major downgrade from $(OTHERVALUE). Is @sap/cds-compiler version $(VERSION) outdated?');
|
|
60
64
|
throwWithAnyError();
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -116,10 +120,18 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
|
|
|
116
120
|
// Arguments are interchanged in this case: `artifact` from beforeModel and `otherArtifact` from afterModel.
|
|
117
121
|
if (isPersisted && !isPersistedOther) {
|
|
118
122
|
deletedEntities[name] = artifact;
|
|
123
|
+
} else if(isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
|
|
124
|
+
deletedEntities[name] = artifact;
|
|
119
125
|
}
|
|
120
126
|
return;
|
|
121
127
|
}
|
|
122
128
|
|
|
129
|
+
// to make it easier to know which views to drop-create
|
|
130
|
+
if(isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
|
|
131
|
+
// TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
|
|
132
|
+
artifact[isChanged] = JSON.stringify(artifact) !== JSON.stringify(otherArtifact);
|
|
133
|
+
}
|
|
134
|
+
|
|
123
135
|
// Looking for added entities and added/deleted/changed elements.
|
|
124
136
|
// Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
|
|
125
137
|
|
|
@@ -146,15 +158,6 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
|
|
|
146
158
|
};
|
|
147
159
|
}
|
|
148
160
|
|
|
149
|
-
function isPersistedAsTable(artifact) {
|
|
150
|
-
return artifact.kind === 'entity'
|
|
151
|
-
&& !artifact._ignore
|
|
152
|
-
&& !artifact.abstract
|
|
153
|
-
&& (!artifact.query && !artifact.projection || hasAnnotationValue(artifact, '@cds.persistence.table'))
|
|
154
|
-
&& !hasAnnotationValue(artifact, '@cds.persistence.skip')
|
|
155
|
-
&& !hasAnnotationValue(artifact, '@cds.persistence.exists');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
161
|
function getElementComparator(otherArtifact, addedElementsDict = null, changedElementsDict = null) {
|
|
159
162
|
return function compareElements(element, name) {
|
|
160
163
|
if (element._ignore) {
|
|
@@ -244,5 +247,36 @@ function changedElement(element, otherElement) {
|
|
|
244
247
|
|
|
245
248
|
module.exports = {
|
|
246
249
|
compareModels,
|
|
247
|
-
deepEqual
|
|
250
|
+
deepEqual,
|
|
251
|
+
isChanged
|
|
248
252
|
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* A ModelDiff encapsulates the changes between two models ("before" and "after"). It contains information
|
|
256
|
+
* about changes to .elements and removed artifacts.
|
|
257
|
+
*
|
|
258
|
+
* @typedef {object} ModelDiff
|
|
259
|
+
* @property {CSN.Definitions} definitions The artifacts present in the "after" model
|
|
260
|
+
* @property {CSN.Definitions} deletions The artifacts present in the "before", but not in the "after"
|
|
261
|
+
* @property {extension[]} extensions The elements added to artifacts
|
|
262
|
+
* @property {migration[]} migrations Altered or removed elements
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @typedef {object} extension
|
|
267
|
+
* @property {CSN.Elements} elements The elements that where added
|
|
268
|
+
* @property {string} extend Name of the artifact that the .elements need to be added to
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @typedef {object} migration
|
|
273
|
+
* @property {Object.<string, ChangeSet>} change An object of changes - the key being the name of the changed element, the value being the change.
|
|
274
|
+
* @property {string} migrate Name of the artifact that the .change and .remove apply to
|
|
275
|
+
* @property {CSN.Elements} remove An object of removed elements
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @typedef {object} ChangeSet Describes the change of one element
|
|
280
|
+
* @property {CSN.Element} old The old element definition
|
|
281
|
+
* @property {CSN.Element} new The new element definition
|
|
282
|
+
*/
|