@sap/cds-compiler 3.4.0 → 3.4.4
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 +21 -1
- package/bin/cdsc.js +3 -4
- package/bin/cdshi.js +19 -6
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/lib/api/main.js +6 -3
- package/lib/base/message-registry.js +61 -38
- package/lib/base/messages.js +7 -3
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/compiler/.eslintrc.json +4 -1
- package/lib/compiler/assert-consistency.js +8 -6
- package/lib/compiler/builtins.js +13 -13
- package/lib/compiler/checks.js +50 -33
- package/lib/compiler/define.js +9 -6
- package/lib/compiler/extend.js +83 -55
- package/lib/compiler/finalize-parse-cdl.js +3 -3
- package/lib/compiler/populate.js +16 -5
- package/lib/compiler/propagator.js +22 -29
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +4 -4
- package/lib/compiler/utils.js +14 -0
- package/lib/edm/annotations/genericTranslation.js +68 -56
- package/lib/edm/csn2edm.js +214 -174
- package/lib/edm/edmAnnoPreprocessor.js +5 -5
- package/lib/edm/edmInboundChecks.js +2 -2
- package/lib/edm/edmPreprocessor.js +1 -1
- package/lib/edm/edmUtils.js +3 -3
- package/lib/gen/Dictionary.json +176 -8
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4776 -4513
- package/lib/json/from-csn.js +21 -16
- package/lib/json/to-csn.js +37 -41
- package/lib/language/.eslintrc.json +4 -1
- package/lib/language/antlrParser.js +5 -2
- package/lib/language/docCommentParser.js +6 -6
- package/lib/language/errorStrategy.js +43 -23
- package/lib/language/genericAntlrParser.js +54 -95
- package/lib/language/language.g4 +92 -66
- package/lib/language/multiLineStringParser.js +2 -2
- package/lib/language/textUtils.js +2 -2
- package/lib/model/csnRefs.js +5 -0
- package/lib/modelCompare/compare.js +2 -2
- package/lib/modelCompare/utils/.eslintrc.json +22 -0
- package/lib/modelCompare/utils/filter.js +99 -0
- package/lib/render/.eslintrc.json +1 -0
- package/lib/render/toCdl.js +96 -127
- package/lib/render/toHdbcds.js +38 -35
- package/lib/render/toSql.js +75 -161
- package/lib/render/utils/common.js +133 -83
- package/lib/render/utils/delta.js +227 -0
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/draft/.eslintrc.json +1 -35
- package/lib/transform/forOdataNew.js +33 -22
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +9 -8
- package/lib/transform/odata/typesExposure.js +26 -4
- package/lib/transform/transformUtilsNew.js +15 -8
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/package.json +2 -3
- package/lib/modelCompare/filter.js +0 -83
|
@@ -72,7 +72,6 @@ function beautifyExprArray(tokens) {
|
|
|
72
72
|
return result;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
76
75
|
/**
|
|
77
76
|
* Get the part that is really the name of this artifact and not just prefix caused by a context/service
|
|
78
77
|
*
|
|
@@ -86,7 +85,6 @@ function getRealName(csn, artifactName) {
|
|
|
86
85
|
if (parts.length === 1)
|
|
87
86
|
return artifactName;
|
|
88
87
|
|
|
89
|
-
|
|
90
88
|
const namespace = getNamespace(csn, artifactName);
|
|
91
89
|
const startIndex = namespace ? namespace.split('.').length : 0;
|
|
92
90
|
let indexOfLastParent = startIndex;
|
|
@@ -129,7 +127,6 @@ function getParentContextName(csn, artifactName) {
|
|
|
129
127
|
if (art && (art.kind === 'context' || art.kind === 'service'))
|
|
130
128
|
return name;
|
|
131
129
|
}
|
|
132
|
-
|
|
133
130
|
return null;
|
|
134
131
|
}
|
|
135
132
|
|
|
@@ -404,8 +401,7 @@ function getSqlSnippets(options, obj) {
|
|
|
404
401
|
*
|
|
405
402
|
* @callback renderPart
|
|
406
403
|
* @param {object|array} expression
|
|
407
|
-
* @
|
|
408
|
-
* @this {{inline: Boolean, nestedExpr: Boolean}}
|
|
404
|
+
* @this {ExpressionRenderer}
|
|
409
405
|
* @returns {string}
|
|
410
406
|
*/
|
|
411
407
|
|
|
@@ -415,7 +411,27 @@ function getSqlSnippets(options, obj) {
|
|
|
415
411
|
*
|
|
416
412
|
* @typedef {object} ExpressionConfiguration
|
|
417
413
|
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
|
|
418
|
-
* @property {renderPart}
|
|
414
|
+
* @property {renderPart} typeCast
|
|
415
|
+
* @property {renderPart} val
|
|
416
|
+
* @property {renderPart} enum
|
|
417
|
+
* @property {renderPart} ref
|
|
418
|
+
* @property {renderPart} aliasOnly
|
|
419
|
+
* @property {renderPart} windowFunction
|
|
420
|
+
* @property {renderPart} func
|
|
421
|
+
* @property {renderPart} xpr
|
|
422
|
+
* @property {renderPart} SELECT
|
|
423
|
+
* @property {renderPart} SET
|
|
424
|
+
* @property {Function} [visitExpr]
|
|
425
|
+
* @property {Function} [renderExpr]
|
|
426
|
+
* @property {Function} [renderSubExpr]
|
|
427
|
+
* @property {boolean} [isNestedXpr]
|
|
428
|
+
* @property {CdlRenderEnvironment} [env]
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @typedef {object} ExpressionRenderer
|
|
433
|
+
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
|
|
434
|
+
* @property {renderPart} typeCast
|
|
419
435
|
* @property {renderPart} val
|
|
420
436
|
* @property {renderPart} enum
|
|
421
437
|
* @property {renderPart} ref
|
|
@@ -425,94 +441,128 @@ function getSqlSnippets(options, obj) {
|
|
|
425
441
|
* @property {renderPart} xpr
|
|
426
442
|
* @property {renderPart} SELECT
|
|
427
443
|
* @property {renderPart} SET
|
|
428
|
-
* @property {
|
|
429
|
-
* @property {
|
|
444
|
+
* @property {Function} visitExpr
|
|
445
|
+
* @property {Function} renderExpr
|
|
446
|
+
* @property {Function} renderSubExpr
|
|
447
|
+
* @property {boolean} isNestedXpr
|
|
448
|
+
* @property {CdlRenderEnvironment} env
|
|
430
449
|
*/
|
|
431
450
|
|
|
451
|
+
/**
|
|
452
|
+
* If `xpr` has a `cast` property, return a copy without it, otherwise return `xpr`.
|
|
453
|
+
* Useful for removing e.g. top-level CDL-style casts that should not be rendered as CAST().
|
|
454
|
+
*
|
|
455
|
+
* @param xpr
|
|
456
|
+
*/
|
|
457
|
+
function withoutCast(xpr) {
|
|
458
|
+
return !xpr.cast ? xpr : { ...xpr, cast: undefined };
|
|
459
|
+
}
|
|
460
|
+
|
|
432
461
|
/**
|
|
433
462
|
* Render an expression (including paths and values) or condition 'x'.
|
|
434
463
|
* (no trailing LF, don't indent if inline)
|
|
435
464
|
*
|
|
436
|
-
* @param {ExpressionConfiguration}
|
|
437
|
-
* @returns {
|
|
465
|
+
* @param {ExpressionConfiguration} rendererBase
|
|
466
|
+
* @returns {ExpressionRenderer} Expression rendering utility
|
|
438
467
|
*/
|
|
439
|
-
function
|
|
468
|
+
function createExpressionRenderer(rendererBase) {
|
|
469
|
+
const renderer = Object.create(rendererBase);
|
|
470
|
+
renderer.visitExpr = visitExpr;
|
|
440
471
|
/**
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
* @todo Reuse this with toCdl
|
|
445
|
-
* @param {Array|object|string} expr Expression to render
|
|
446
|
-
* @param {object} env Render environment
|
|
447
|
-
* @param {boolean} [inline=true] Whether to render the expression inline
|
|
448
|
-
* @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
|
|
449
|
-
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
|
|
450
|
-
* Note: This is a hack for casts() inside groupBy.
|
|
451
|
-
* @returns {string} Rendered expression
|
|
472
|
+
* @param {any} x
|
|
473
|
+
* @param {CdlRenderEnvironment} env
|
|
452
474
|
*/
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
475
|
+
renderer.renderExpr = function renderExpr(x, env) {
|
|
476
|
+
/** @type {ExpressionRenderer} */
|
|
477
|
+
const renderObj = Object.create(renderer);
|
|
478
|
+
renderObj.env = env || this?.env;
|
|
479
|
+
// The outermost expression is not nested. All `.xpr` inside `expr`
|
|
480
|
+
// are nested. This information is used for adding parentheses around
|
|
481
|
+
// expressions (see `this.xpr()`).
|
|
482
|
+
renderObj.isNestedXpr = false;
|
|
483
|
+
return renderObj.visitExpr(x);
|
|
484
|
+
};
|
|
485
|
+
/**
|
|
486
|
+
* @param {any} x
|
|
487
|
+
* @param {CdlRenderEnvironment} env
|
|
488
|
+
*/
|
|
489
|
+
renderer.renderSubExpr = function renderSubExpr(x, env) {
|
|
490
|
+
/** @type {ExpressionRenderer} */
|
|
491
|
+
const renderObj = Object.create(renderer);
|
|
492
|
+
renderObj.env = env || this?.env;
|
|
493
|
+
renderObj.isNestedXpr = true;
|
|
494
|
+
return renderObj.visitExpr(x);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
return renderer;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Render an expression (including paths and values) or condition 'x'.
|
|
503
|
+
* (no trailing LF, don't indent if inline)
|
|
504
|
+
*
|
|
505
|
+
* `this` must refer to an object of type `ExpressionRenderer`, see
|
|
506
|
+
* `createExpressionRenderer()`
|
|
507
|
+
*
|
|
508
|
+
* @param {any} x (Sub-)Expression to render
|
|
509
|
+
*
|
|
510
|
+
* @this ExpressionRenderer
|
|
511
|
+
* @returns {string} Rendered expression
|
|
512
|
+
*/
|
|
513
|
+
function visitExpr(x) {
|
|
514
|
+
if (Array.isArray(x)) {
|
|
515
|
+
// Compound expression, e.g. for on- or where-conditions.
|
|
516
|
+
// If xpr is part of an array, it's always a nested xpr,
|
|
517
|
+
// e.g. CSN for `(1=1 or 2=2) and 3=3`.
|
|
518
|
+
const tokens = x.map(item => this.renderSubExpr(item));
|
|
519
|
+
return beautifyExprArray(tokens);
|
|
520
|
+
}
|
|
521
|
+
else if (typeof x !== 'object' || x === null) {
|
|
464
522
|
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
else if (x.SET) {
|
|
505
|
-
return renderer.SET.call({ inline, nestedExpr }, x, env);
|
|
506
|
-
}
|
|
507
|
-
else if (x.as && x.cast && x.cast.type && x.cast.target) {
|
|
508
|
-
return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
|
|
509
|
-
}
|
|
523
|
+
return this.finalize(x);
|
|
524
|
+
}
|
|
525
|
+
else if (x.cast?.type && !x.cast.target) {
|
|
526
|
+
return this.typeCast(x);
|
|
527
|
+
}
|
|
528
|
+
else if (x.list) {
|
|
529
|
+
// Render as non-nested expr.
|
|
530
|
+
return `(${x.list.map(item => this.renderExpr(item)).join(', ')})`;
|
|
531
|
+
}
|
|
532
|
+
else if (x.val !== undefined) {
|
|
533
|
+
return this.val(x);
|
|
534
|
+
}
|
|
535
|
+
else if (x['#']) {
|
|
536
|
+
// Enum symbol
|
|
537
|
+
return this.enum(x);
|
|
538
|
+
}
|
|
539
|
+
else if (x.ref) {
|
|
540
|
+
// Reference: Array of path steps, possibly preceded by ':'
|
|
541
|
+
return this.ref(x);
|
|
542
|
+
}
|
|
543
|
+
else if (x.func) {
|
|
544
|
+
// Function call, possibly with args (use '=>' for named args)
|
|
545
|
+
if (x.xpr)
|
|
546
|
+
return this.windowFunction(x);
|
|
547
|
+
return this.func(x);
|
|
548
|
+
}
|
|
549
|
+
else if (x.xpr) {
|
|
550
|
+
return this.xpr(x);
|
|
551
|
+
}
|
|
552
|
+
else if (x.SELECT) {
|
|
553
|
+
return this.SELECT(x);
|
|
554
|
+
}
|
|
555
|
+
else if (x.SET) {
|
|
556
|
+
return this.SET(x);
|
|
557
|
+
}
|
|
558
|
+
else if (x.as) {
|
|
559
|
+
return this.aliasOnly(x);
|
|
560
|
+
}
|
|
510
561
|
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
};
|
|
562
|
+
throw new ModelError(`renderExpr(): Unknown expression: ${JSON.stringify(x)}`);
|
|
514
563
|
}
|
|
515
564
|
|
|
565
|
+
|
|
516
566
|
/**
|
|
517
567
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
518
568
|
*
|
|
@@ -531,8 +581,7 @@ function getExpressionRenderer(renderer) {
|
|
|
531
581
|
|
|
532
582
|
module.exports = {
|
|
533
583
|
renderFunc,
|
|
534
|
-
|
|
535
|
-
beautifyExprArray,
|
|
584
|
+
createExpressionRenderer,
|
|
536
585
|
getNamespace,
|
|
537
586
|
getRealName,
|
|
538
587
|
addIntermediateContexts,
|
|
@@ -544,4 +593,5 @@ module.exports = {
|
|
|
544
593
|
findElement,
|
|
545
594
|
funcWithoutParen,
|
|
546
595
|
getSqlSnippets,
|
|
596
|
+
withoutCast,
|
|
547
597
|
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encapsulate all the functions needed to render SQL ALTER/DROP/ADD statements.
|
|
5
|
+
*/
|
|
6
|
+
class DeltaRenderer {
|
|
7
|
+
constructor(options, scopedFunctions) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.scopedFunctions = scopedFunctions;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render column additions as SQL. Checks for duplicate elements.
|
|
14
|
+
*/
|
|
15
|
+
addColumnsFromElementStrings(artifactName, eltStrings) {
|
|
16
|
+
return eltStrings.map(eltString => `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD ${eltString};`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render column additions as SQL. Checks for duplicate elements.
|
|
21
|
+
*/
|
|
22
|
+
addColumnsFromElementsObj(artifactName, elementsObj, env, duplicateChecker) {
|
|
23
|
+
// Only extend with 'ADD' for elements/associations
|
|
24
|
+
// TODO: May also include 'RENAME' at a later stage
|
|
25
|
+
const alterEnv = this.scopedFunctions.activateAlterMode(env);
|
|
26
|
+
const elements = Object.entries(elementsObj)
|
|
27
|
+
.map(([ name, elt ]) => this.scopedFunctions.renderElement(artifactName, name, elt, duplicateChecker, null, alterEnv))
|
|
28
|
+
.filter(s => s !== '');
|
|
29
|
+
|
|
30
|
+
if (elements.length)
|
|
31
|
+
return this.addColumnsFromElementStrings(artifactName, elements);
|
|
32
|
+
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* By default, we don't support rendering association-alters - only for HANA
|
|
38
|
+
*/
|
|
39
|
+
addAssociations() {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Render key addition as SQL.
|
|
45
|
+
*/
|
|
46
|
+
addKey(artifactName, elementsObj) {
|
|
47
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD ${this.primaryKey(elementsObj)};` ];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Render column removals as SQL.
|
|
52
|
+
*/
|
|
53
|
+
dropColumns(artifactName, sqlIds) {
|
|
54
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP ${sqlIds.join(', ')};` ];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* No associations by default - only for HANA.
|
|
59
|
+
*/
|
|
60
|
+
dropAssociation() {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Render primary-key removals as SQL.
|
|
66
|
+
*/
|
|
67
|
+
dropKey(artifactName) {
|
|
68
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP PRIMARY KEY;` ];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Render column modifications as SQL.
|
|
73
|
+
*/
|
|
74
|
+
alterColumns(artifactName, columnName, delta, definitionsStr) {
|
|
75
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER (${definitionsStr});` ];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render primary keys as SQL.
|
|
80
|
+
*/
|
|
81
|
+
primaryKey(elementsObj) {
|
|
82
|
+
const primaryKeys = Object.keys(elementsObj)
|
|
83
|
+
.filter(name => elementsObj[name].key && !elementsObj[name].virtual)
|
|
84
|
+
.map(name => this.scopedFunctions.quoteSqlId(name))
|
|
85
|
+
.join(', ');
|
|
86
|
+
return primaryKeys && `PRIMARY KEY(${primaryKeys})`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Render entity-comment modifications as SQL.
|
|
91
|
+
*/
|
|
92
|
+
alterEntityComment(artifactName, comment) {
|
|
93
|
+
return [ `COMMENT ON TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} IS ${this.comment(comment)};` ];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Render column-comment modifications as SQL.
|
|
98
|
+
*/
|
|
99
|
+
alterColumnComment(artifactName, columnName, comment) {
|
|
100
|
+
return [ `COMMENT ON COLUMN ${this.scopedFunctions.renderArtifactName(artifactName)}.${columnName} IS ${this.comment(comment)};` ];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Render comment string.
|
|
105
|
+
*/
|
|
106
|
+
comment(comment) {
|
|
107
|
+
return comment && this.scopedFunctions.renderStringForSql(this.scopedFunctions.getHanaComment({ doc: comment }), this.options.sqlDialect) || 'NULL';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Alter SQL snippet for entity.
|
|
112
|
+
*/
|
|
113
|
+
alterEntitySqlSnippet(artifactName, snippet) {
|
|
114
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ${snippet};` ];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Concatenate multiple statements which are to be treated as one by the API caller.
|
|
119
|
+
*/
|
|
120
|
+
concat(...statements) {
|
|
121
|
+
return [ statements.join('\n') ];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
class DeltaRendererHana extends DeltaRenderer {
|
|
126
|
+
/**
|
|
127
|
+
* Render column additions as HANA SQL. Checks for duplicate elements.
|
|
128
|
+
*/
|
|
129
|
+
addColumnsFromElementStrings(artifactName, eltStrings) {
|
|
130
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD (${eltStrings.join(', ')});` ];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Render association additions as HANA SQL.
|
|
135
|
+
* TODO duplicity check
|
|
136
|
+
*/
|
|
137
|
+
addAssociations(artifactName, elementsObj, env) {
|
|
138
|
+
return Object.entries(elementsObj)
|
|
139
|
+
.map(([ name, elt ]) => this.scopedFunctions.renderAssociationElement(name, elt, env))
|
|
140
|
+
.filter(s => s !== '')
|
|
141
|
+
.map(eltStr => `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD ASSOCIATION (${eltStr});`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Render column removals as HANA SQL.
|
|
146
|
+
*/
|
|
147
|
+
dropColumns(artifactName, sqlIds) {
|
|
148
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP (${sqlIds.join(', ')});` ];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Render association removals as HANA SQL.
|
|
153
|
+
*/
|
|
154
|
+
dropAssociation(artifactName, sqlId) {
|
|
155
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP ASSOCIATION ${sqlId};` ];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
class DeltaRendererPostgres extends DeltaRenderer {
|
|
160
|
+
/**
|
|
161
|
+
* Render primary-key removals as SQL.
|
|
162
|
+
* @todo tableName is escaped - we cannot simply add _pkey
|
|
163
|
+
*/
|
|
164
|
+
dropKey(artifactName) {
|
|
165
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP CONSTRAINT ${this.scopedFunctions.renderArtifactName(`${artifactName}_pkey`)};` ];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Render column removals as SQL.
|
|
170
|
+
*/
|
|
171
|
+
dropColumns(artifactName, sqlIds) {
|
|
172
|
+
return sqlIds.map(sqlId => `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP ${sqlId};`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Render column modifications as Postgres SQL - no ( ), special NOT NULL.
|
|
177
|
+
*/
|
|
178
|
+
alterColumns(artifactName, columnName, delta, definitionsStr) {
|
|
179
|
+
const sqls = [];
|
|
180
|
+
if (delta.new.notNull === true || delta.new.key === true)
|
|
181
|
+
definitionsStr = definitionsStr.replace(' NOT NULL', ''); // TODO: Is this robust enough?
|
|
182
|
+
else if (delta.new.notNull === false || delta.new.$notNull === false)
|
|
183
|
+
definitionsStr = definitionsStr.replace(' NULL', ''); // TODO: Is this robust enough?
|
|
184
|
+
|
|
185
|
+
sqls.push(`ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${definitionsStr};`);
|
|
186
|
+
if (delta.new.notNull && !delta.old.notNull)
|
|
187
|
+
sqls.push(`ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${columnName} SET NOT NULL;`);
|
|
188
|
+
else if (delta.old.notNull && !delta.new.notNull)
|
|
189
|
+
sqls.push(`ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${columnName} DROP NOT NULL;`);
|
|
190
|
+
|
|
191
|
+
return sqls;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
class DeltaRendererH2 extends DeltaRenderer {
|
|
196
|
+
/**
|
|
197
|
+
* Render column modifications as H2 SQL - no ().
|
|
198
|
+
*/
|
|
199
|
+
alterColumns(artifactName, columnName, delta, definitionsStr) {
|
|
200
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${definitionsStr};` ];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Return an object encapsulating the render-functions for ALTER/DROP for a given db dialect.
|
|
206
|
+
*
|
|
207
|
+
* @param {CSN.Options} options
|
|
208
|
+
* @param {object} scopedFunctions
|
|
209
|
+
* @returns {DeltaRenderer}
|
|
210
|
+
*/
|
|
211
|
+
function getDeltaRenderer(options, scopedFunctions) {
|
|
212
|
+
switch (options.sqlDialect) {
|
|
213
|
+
case 'hana':
|
|
214
|
+
return new DeltaRendererHana(options, scopedFunctions);
|
|
215
|
+
case 'h2':
|
|
216
|
+
return new DeltaRendererH2(options, scopedFunctions);
|
|
217
|
+
case 'postgres':
|
|
218
|
+
return new DeltaRendererPostgres(options, scopedFunctions);
|
|
219
|
+
default:
|
|
220
|
+
return new DeltaRenderer(options, scopedFunctions);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
getDeltaRenderer,
|
|
227
|
+
};
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"template-curly-spacing":["error", "never"],
|
|
11
11
|
"complexity": ["warn", 40],
|
|
12
12
|
"max-len": "off",
|
|
13
|
+
// there seem to be false positives
|
|
14
|
+
"jsdoc/require-returns-check": "off",
|
|
13
15
|
// Don't enforce stupid descriptions
|
|
14
16
|
"jsdoc/require-param-description": "off",
|
|
15
17
|
"jsdoc/require-returns-description": "off",
|
|
@@ -1,38 +1,4 @@
|
|
|
1
1
|
{
|
|
2
2
|
"root": true,
|
|
3
|
-
"
|
|
4
|
-
"extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended", "plugin:jsdoc/recommended"],
|
|
5
|
-
"rules": {
|
|
6
|
-
"prefer-const": "error",
|
|
7
|
-
"quotes": ["error", "single", "avoid-escape"],
|
|
8
|
-
"prefer-template": "error",
|
|
9
|
-
"no-trailing-spaces": "error",
|
|
10
|
-
"template-curly-spacing":["error", "never"],
|
|
11
|
-
"complexity": ["warn", 30],
|
|
12
|
-
"max-len": "off",
|
|
13
|
-
// Don't enforce stupid descriptions
|
|
14
|
-
"jsdoc/require-param-description": "off",
|
|
15
|
-
"jsdoc/require-returns-description": "off",
|
|
16
|
-
// Sometimes if-else's are more specific
|
|
17
|
-
"sonarjs/prefer-single-boolean-return": "off",
|
|
18
|
-
// Very whiny and nitpicky
|
|
19
|
-
"sonarjs/cognitive-complexity": "off",
|
|
20
|
-
// Does not recognize TS types
|
|
21
|
-
"jsdoc/no-undefined-types": "off",
|
|
22
|
-
// Whiny and annoying
|
|
23
|
-
"sonarjs/no-duplicate-string": "off"
|
|
24
|
-
},
|
|
25
|
-
"parserOptions": {
|
|
26
|
-
"ecmaVersion": 2020,
|
|
27
|
-
"sourceType": "script"
|
|
28
|
-
},
|
|
29
|
-
"env": {
|
|
30
|
-
"es2020": true,
|
|
31
|
-
"node": true
|
|
32
|
-
},
|
|
33
|
-
"settings": {
|
|
34
|
-
"jsdoc": {
|
|
35
|
-
"mode": "typescript"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
3
|
+
"extends": ["../db/.eslintrc.json"]
|
|
38
4
|
}
|
|
@@ -109,7 +109,9 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
109
109
|
// @ts-ignore
|
|
110
110
|
const externalServices = services.filter(serviceName => csn.definitions[serviceName]['@cds.external']);
|
|
111
111
|
// @ts-ignore
|
|
112
|
-
const isExternalServiceMember = (
|
|
112
|
+
const isExternalServiceMember = (art, name) => {
|
|
113
|
+
return !!(externalServices.includes(getServiceName(name)) || (art && art['@cds.external']))
|
|
114
|
+
}
|
|
113
115
|
|
|
114
116
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
|
|
115
117
|
enrichUniversalCsn(csn, options);
|
|
@@ -163,10 +165,12 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
163
165
|
// Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
|
|
164
166
|
// OData doesn't resolve type chains after the first 'items'
|
|
165
167
|
flattening.resolveTypeReferences(csn, options, resolved, '_',
|
|
166
|
-
{ skip: [ 'action', 'aspect', 'event', 'function', 'type'],
|
|
168
|
+
{ skip: [ 'action', 'aspect', 'event', 'function', 'type'],
|
|
169
|
+
skipArtifact: isExternalServiceMember, skipStandard: { items: true } });
|
|
167
170
|
// No structured elements exists anymore
|
|
168
171
|
flattening.flattenElements(csn, options, '_', error,
|
|
169
|
-
{ skip: ['action', 'aspect', 'event', 'function', 'type'],
|
|
172
|
+
{ skip: ['action', 'aspect', 'event', 'function', 'type'],
|
|
173
|
+
skipArtifact: isExternalServiceMember,
|
|
170
174
|
skipStandard: { items: true }, // don't drill further into .items
|
|
171
175
|
skipDict: { actions: true } }); // don't drill further into .actions -> bound actions, action-artifacts are handled by skip
|
|
172
176
|
}
|
|
@@ -209,14 +213,14 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
209
213
|
const skipPersNameKinds = {'service':1, 'context':1, 'namespace':1, 'annotation':1, 'action':1, 'function':1};
|
|
210
214
|
forEachDefinition(csn, (def, defName) => {
|
|
211
215
|
// Resolve annotation shorthands for entities, types, annotations, ...
|
|
212
|
-
renameShorthandAnnotations(def);
|
|
216
|
+
renameShorthandAnnotations(def, ['definitions', defName ]);
|
|
213
217
|
|
|
214
218
|
// Annotate artifacts with their DB names if requested.
|
|
215
219
|
// Skip artifacts that have no DB equivalent anyway
|
|
216
220
|
if (options.sqlMapping && !(def.kind in skipPersNameKinds))
|
|
217
221
|
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana'); // hana to allow naming mode "hdbcds"
|
|
218
222
|
|
|
219
|
-
forEachMemberRecursively(def, (member, memberName, propertyName) => {
|
|
223
|
+
forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
|
|
220
224
|
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested
|
|
221
225
|
// Only these are actually required and don't annotate virtual elements in entities or types
|
|
222
226
|
// as they have no DB representation (although in views)
|
|
@@ -229,7 +233,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
229
233
|
annotateCoreComputed(member);
|
|
230
234
|
|
|
231
235
|
// Resolve annotation shorthands for elements, actions, action parameters
|
|
232
|
-
renameShorthandAnnotations(member);
|
|
236
|
+
renameShorthandAnnotations(member, path);
|
|
233
237
|
|
|
234
238
|
// - If the association target is annotated with @cds.odata.valuelist, annotate the
|
|
235
239
|
// association with @Common.ValueList.viaAssociation
|
|
@@ -294,7 +298,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
294
298
|
|
|
295
299
|
// Rename shorthand annotations within artifact or element 'node' according to a builtin
|
|
296
300
|
// list.
|
|
297
|
-
function renameShorthandAnnotations(node) {
|
|
301
|
+
function renameShorthandAnnotations(node, path) {
|
|
298
302
|
// FIXME: Verify this list - are they all still required? Do we need any more?
|
|
299
303
|
const mappings = {
|
|
300
304
|
'@label': '@Common.Label',
|
|
@@ -364,22 +368,29 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
364
368
|
if(!node.enum && node.type && !isBuiltinType(node.type))
|
|
365
369
|
typeDef = csn.definitions[node.type];
|
|
366
370
|
if(typeDef.enum) {
|
|
367
|
-
|
|
371
|
+
const enumValue = [];
|
|
372
|
+
for(const enumSymbol in typeDef.enum) {
|
|
368
373
|
const enumSymbolDef = typeDef.enum[enumSymbol];
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
result
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
374
|
+
if(enumSymbolDef.val === null)
|
|
375
|
+
info('odata-enum-value-type', path,
|
|
376
|
+
{name: enumSymbol, value: null, anno: '@Valiation.AllowedValues' },
|
|
377
|
+
'Value $(VALUE) for enum element $(NAME) not added to $(ANNO)');
|
|
378
|
+
else {
|
|
379
|
+
const result = { '@Core.SymbolicName': enumSymbol };
|
|
380
|
+
if (enumSymbolDef.val !== undefined)
|
|
381
|
+
result.Value = enumSymbolDef.val;
|
|
382
|
+
else if (node.type && node.type === 'cds.String')
|
|
383
|
+
// the symbol is used as value only for type 'cds.String'
|
|
384
|
+
result.Value = enumSymbol;
|
|
385
|
+
// Can't rely that @description has already been renamed to @Core.Description
|
|
386
|
+
// Eval description according to precedence (doc comment must be considered already in Odata transformer
|
|
387
|
+
// as in contrast to the other doc commments as it is used to annotate the @Validation.AllowedValues)
|
|
388
|
+
const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc;
|
|
389
|
+
if (desc)
|
|
390
|
+
result['@Core.Description'] = desc;
|
|
391
|
+
enumValue.push(result);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
383
394
|
setAnnotation(node, '@Validation.AllowedValues', enumValue);
|
|
384
395
|
}
|
|
385
396
|
}
|
|
@@ -705,7 +705,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
705
705
|
setProp(node, '$renamed', 'cds.UUID');
|
|
706
706
|
}
|
|
707
707
|
|
|
708
|
-
if(options.sqlDialect === 'h2' && val === 'cds.Decimal' &&
|
|
708
|
+
if(options.sqlDialect === 'h2' && val === 'cds.Decimal' && node.scale === undefined) {
|
|
709
709
|
node[key] = 'cds.DecimalFloat'; // cds.Decimal and cds.Decimal(p) should map do DECFLOAT for h2
|
|
710
710
|
}
|
|
711
711
|
|