@sap/cds-compiler 6.9.2 → 7.0.1
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 +86 -2
- package/bin/cdsc.js +4 -33
- package/doc/IncompatibleChanges_v7.md +639 -0
- package/lib/api/main.js +4 -56
- package/lib/api/options.js +6 -15
- package/lib/api/validate.js +1 -0
- package/lib/base/builtins.js +1 -2
- package/lib/base/csnRefs.js +2 -6
- package/lib/base/message-registry.js +82 -76
- package/lib/base/messages.js +23 -4
- package/lib/base/optionProcessor.js +2 -72
- package/lib/base/specialOptions.js +20 -17
- package/lib/checks/defaultValues.js +1 -39
- package/lib/checks/hasPersistedElements.js +19 -3
- package/lib/checks/parameters.js +0 -34
- package/lib/checks/selectItems.js +2 -38
- package/lib/checks/typeParameters.js +162 -0
- package/lib/checks/validator.js +5 -8
- package/lib/compiler/assert-consistency.js +19 -5
- package/lib/compiler/checks.js +47 -43
- package/lib/compiler/define.js +6 -6
- package/lib/compiler/extend.js +102 -111
- package/lib/compiler/generate.js +4 -8
- package/lib/compiler/populate.js +4 -7
- package/lib/compiler/propagator.js +9 -9
- package/lib/compiler/resolve.js +205 -7
- package/lib/compiler/shared.js +76 -82
- package/lib/compiler/tweak-assocs.js +102 -22
- package/lib/compiler/utils.js +57 -12
- package/lib/compiler/xpr-rewrite.js +2 -15
- package/lib/edm/annotations/edmJson.js +14 -10
- package/lib/edm/annotations/genericTranslation.js +3 -1
- package/lib/edm/annotations/preprocessAnnotations.js +9 -26
- package/lib/edm/csn2edm.js +27 -20
- package/lib/edm/edmUtils.js +25 -0
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +2237 -2241
- package/lib/gen/Dictionary.json +17 -2
- package/lib/json/from-csn.js +67 -52
- package/lib/json/to-csn.js +28 -25
- package/lib/language/textUtils.js +0 -13
- package/lib/main.d.ts +34 -59
- package/lib/main.js +1 -1
- package/lib/model/csnUtils.js +9 -8
- package/lib/parsers/AstBuildingParser.js +45 -55
- package/lib/parsers/Lexer.js +2 -0
- package/lib/parsers/identifiers.js +0 -9
- package/lib/render/toCdl.js +41 -40
- package/lib/render/toSql.js +8 -1
- package/lib/render/utils/common.js +1 -1
- package/lib/render/utils/sql.js +2 -3
- package/lib/tool-lib/enrichCsn.js +1 -2
- package/lib/transform/db/applyTransformations.js +7 -5
- package/lib/transform/db/assertUnique.js +8 -51
- package/lib/transform/db/associations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -15
- package/lib/transform/db/expansion.js +9 -12
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/groupByOrderBy.js +0 -16
- package/lib/transform/db/views.js +57 -161
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdata.js +25 -14
- package/lib/transform/forRelationalDB.js +93 -301
- package/lib/transform/localized.js +33 -102
- package/lib/transform/odata/flattening.js +11 -2
- package/lib/transform/transformUtils.js +25 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
- package/package.json +2 -2
- package/lib/render/toHdbcds.js +0 -1810
|
@@ -11,7 +11,7 @@ const { quotedLiteralPatterns, specialFunctions } = require('../compiler/builtin
|
|
|
11
11
|
const { primaryExprProperties } = require('../base/builtins');
|
|
12
12
|
|
|
13
13
|
const { parseMultiLineStringLiteral } = require('../language/multiLineStringParser');
|
|
14
|
-
const {
|
|
14
|
+
const { normalizeNumberString } = require('../language/textUtils');
|
|
15
15
|
const { parseDocComment } = require('../language/docCommentParser');
|
|
16
16
|
|
|
17
17
|
const $location = Symbol.for('cds.$location');
|
|
@@ -537,30 +537,22 @@ class AstBuildingParser extends BaseParser {
|
|
|
537
537
|
* `;` between statements is optional only after a `}` (ex braces of structure
|
|
538
538
|
* values for annotations and foreign key specifications).
|
|
539
539
|
*
|
|
540
|
-
* Unfortunate exception:
|
|
540
|
+
* Unfortunate exception: optional after `entity … as projection on` without sub
|
|
541
|
+
* queries in `where`, …
|
|
541
542
|
*
|
|
542
|
-
*
|
|
543
|
+
* Used via:
|
|
544
|
+
* - <prepare=afterBrace>: make guard succeed after closing `}` (not for anno values)
|
|
545
|
+
* - <prepare=afterBrace, arg=sloppyOn>: switch on sloppy-`;` mode
|
|
546
|
+
* - <guard=afterBrace>: succeeds if after previous `}` (with <prepare>)
|
|
547
|
+
* - <guard=afterBrace, arg=orSloppy>: also succeeds (with warning) if sloppy
|
|
548
|
+
*
|
|
549
|
+
* Beware: mentioned in grammar option `hardGuards`, i.e. executed in predictions!
|
|
550
|
+
* (Needed for the rule exit prediction.)
|
|
543
551
|
*/
|
|
544
552
|
afterBrace( arg, test ) {
|
|
545
553
|
if (!test) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (type !== ';' && type !== '}' && type !== 'EOF' && keyword !== 'actions') {
|
|
549
|
-
const prev = this.lb().location;
|
|
550
|
-
const loc = new Location( prev.file, prev.endLine, prev.endCol );
|
|
551
|
-
this.warning( 'syntax-missing-proj-semicolon', loc,
|
|
552
|
-
{ expecting: [ "';'" ], offending: this.antlrName( this.la() ) },
|
|
553
|
-
'Missing $(EXPECTING) before $(OFFENDING)');
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
// with arg 'init' (used in rule `start`/`artifactsBlock`), and arg 'sloppy'
|
|
557
|
-
// or 'normal' (used in rule `entityDef`), set marker in dynamic context:
|
|
558
|
-
if (!arg)
|
|
559
|
-
this.afterBrace$ = this.tokenIdx;
|
|
560
|
-
else if (arg === 'init')
|
|
561
|
-
this.dynamic_.sloppySemicolon$ = [ false ];
|
|
562
|
-
else if (arg === 'sloppy' || this.la().keyword === 'actions')
|
|
563
|
-
this.dynamic_.sloppySemicolon$[0] = (arg === 'sloppy');
|
|
554
|
+
// -42 is the magic number for sloppy semicolon handling
|
|
555
|
+
this.afterBrace$ = (arg ? -42 : this.tokenIdx);
|
|
564
556
|
return null;
|
|
565
557
|
}
|
|
566
558
|
// TODO TOOL: the following test belongs to the BaseParser.js:
|
|
@@ -568,14 +560,21 @@ class AstBuildingParser extends BaseParser {
|
|
|
568
560
|
this.conditionStackLength == null && // after error recover
|
|
569
561
|
test !== 'M')
|
|
570
562
|
return false;
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
563
|
+
if (this.afterBrace$ === this.tokenIdx)
|
|
564
|
+
return false;
|
|
565
|
+
if (!arg || this.afterBrace$ !== -42 || test === 'M')
|
|
566
|
+
return true;
|
|
567
|
+
if (test === true) { // TODO: single-char mode for run
|
|
568
|
+
const { type, location } = this.lb();
|
|
569
|
+
if (type !== '}') {
|
|
570
|
+
const loc = new Location( location.file, location.endLine, location.endCol );
|
|
571
|
+
this.warning( 'syntax-missing-proj-semicolon', loc,
|
|
572
|
+
{ expecting: [ "';'" ], offending: this.antlrName( this.la() ) },
|
|
573
|
+
'Missing $(EXPECTING) before $(OFFENDING)');
|
|
574
|
+
}
|
|
575
|
+
this.afterBrace$ = -1; // switch off sloopy-`;`
|
|
576
|
+
}
|
|
577
|
+
return false;
|
|
579
578
|
}
|
|
580
579
|
|
|
581
580
|
/**
|
|
@@ -632,7 +631,11 @@ class AstBuildingParser extends BaseParser {
|
|
|
632
631
|
this.conditionStackLength == null && // after error recover
|
|
633
632
|
mode !== 'M')
|
|
634
633
|
return false;
|
|
635
|
-
|
|
634
|
+
// remark: collecting an `Id` as sync token does not help much yet, because
|
|
635
|
+
// using `Id` as sync token is restricted at the moment (too often sub-optimal).
|
|
636
|
+
// TODO (introduce base parser method): allow if followed by ':'/'='/';'/','/'}',
|
|
637
|
+
// or wait for future “try different recovery possibilities”.
|
|
638
|
+
return mode !== 'Y';
|
|
636
639
|
}
|
|
637
640
|
|
|
638
641
|
// Space handling etc, locations ----------------------------------------------
|
|
@@ -743,22 +746,6 @@ class AstBuildingParser extends BaseParser {
|
|
|
743
746
|
return art;
|
|
744
747
|
}
|
|
745
748
|
|
|
746
|
-
ruleTokensText() {
|
|
747
|
-
let tokenIdx = this.stack.at(-1).tokenIdx + 1;
|
|
748
|
-
const stop = this.tokenIdx;
|
|
749
|
-
|
|
750
|
-
let { text: result, location: prev } = this.tokens[tokenIdx];
|
|
751
|
-
while (++tokenIdx < stop) {
|
|
752
|
-
const { text, location } = this.tokens[tokenIdx];
|
|
753
|
-
if (location.line > prev.endLine ||
|
|
754
|
-
location.line === prev.endLine && location.col > prev.endCol)
|
|
755
|
-
result += ' ';
|
|
756
|
-
result += normalizeNewLine( text );
|
|
757
|
-
prev = location;
|
|
758
|
-
}
|
|
759
|
-
return result;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
749
|
// AST building ---------------------------------------------------------------
|
|
763
750
|
|
|
764
751
|
assignAnnotation( art, val, name, prefix = '' ) {
|
|
@@ -1203,11 +1190,14 @@ class AstBuildingParser extends BaseParser {
|
|
|
1203
1190
|
}
|
|
1204
1191
|
|
|
1205
1192
|
checkStructProps( dict ) {
|
|
1206
|
-
const
|
|
1207
|
-
if (
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1193
|
+
const prop = primaryExprProperties.find( p => dict[p] !== undefined );
|
|
1194
|
+
if (prop) {
|
|
1195
|
+
this.error( 'syntax-invalid-anno-struct', dict[prop].name.location,
|
|
1196
|
+
{ '#': 'std', prop } );
|
|
1197
|
+
}
|
|
1198
|
+
else if (dict['='] && (dict['='].literal !== 'string' || !dict['='].val)) {
|
|
1199
|
+
this.error( 'syntax-invalid-anno-struct', dict['='].location,
|
|
1200
|
+
{ '#': 'ref', prop: '=' } );
|
|
1211
1201
|
}
|
|
1212
1202
|
}
|
|
1213
1203
|
|
|
@@ -1350,8 +1340,8 @@ class AstBuildingParser extends BaseParser {
|
|
|
1350
1340
|
funcToken.parsedAs = 'func';
|
|
1351
1341
|
|
|
1352
1342
|
const filter = path[0].cardinality || path[0].where; // XSN TODO: filter$location
|
|
1353
|
-
if (filter) // TODO
|
|
1354
|
-
this.
|
|
1343
|
+
if (filter) // TODO v8: make this error be reported via guard
|
|
1344
|
+
this.error( 'syntax-unexpected-filter', filter.location, {} );
|
|
1355
1345
|
// TODO: XSN representation of functions is a bit strange - rework
|
|
1356
1346
|
return this.attachLocation( {
|
|
1357
1347
|
op: { location, val: 'call' },
|
|
@@ -1382,8 +1372,8 @@ class AstBuildingParser extends BaseParser {
|
|
|
1382
1372
|
}, 'References after function calls can\'t be resolved. Use $(CODE) in function arguments');
|
|
1383
1373
|
}
|
|
1384
1374
|
const filter = item.cardinality || item.where; // XSN TODO: filter$location
|
|
1385
|
-
if (filter) // TODO
|
|
1386
|
-
this.
|
|
1375
|
+
if (filter) // TODO v8: make this error be reported via guard
|
|
1376
|
+
this.error( 'syntax-unexpected-filter', filter.location, {} );
|
|
1387
1377
|
}
|
|
1388
1378
|
|
|
1389
1379
|
const args = [];
|
package/lib/parsers/Lexer.js
CHANGED
|
@@ -181,6 +181,8 @@ function string( text, lexer, parser, beg ) {
|
|
|
181
181
|
prefix.location.endCol !== lexer.location.col ||
|
|
182
182
|
!quotedLiterals.includes( prefix.keyword )))
|
|
183
183
|
prefix = null;
|
|
184
|
+
// TODO: warning (error in v8) if non-quotedLiterals prefix and/or
|
|
185
|
+
// try to do it via grammar = hard `<guard=followedByQuote> 'x' String` etc ?
|
|
184
186
|
while (re.test( lexer.input ) && lexer.input[re.lastIndex] === "'")
|
|
185
187
|
esc = ++re.lastIndex;
|
|
186
188
|
}
|
|
@@ -15,17 +15,8 @@ const functionsWithoutParentheses = [
|
|
|
15
15
|
'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
|
|
16
16
|
];
|
|
17
17
|
|
|
18
|
-
function isSimpleCdlIdentifier( id ) {
|
|
19
|
-
if (undelimitedIdentifierRegex.test(id))
|
|
20
|
-
return true;
|
|
21
|
-
const upperId = id.toUpperCase();
|
|
22
|
-
return !cdlKeywords.includes(upperId) &&
|
|
23
|
-
!functionsWithoutParentheses.includes(upperId);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
18
|
module.exports = {
|
|
27
19
|
undelimitedIdentifierRegex,
|
|
28
20
|
cdlKeywords,
|
|
29
21
|
functionsWithoutParentheses,
|
|
30
|
-
isSimpleCdlIdentifier,
|
|
31
22
|
};
|
package/lib/render/toCdl.js
CHANGED
|
@@ -1123,7 +1123,6 @@ class CsnToCdl {
|
|
|
1123
1123
|
result += env.indent;
|
|
1124
1124
|
result += element.virtual ? 'virtual ' : '';
|
|
1125
1125
|
result += element.key ? 'key ' : '';
|
|
1126
|
-
result += element.masked ? 'masked ' : '';
|
|
1127
1126
|
result += this.quoteNonIdentifierOrKeyword(elementName, env);
|
|
1128
1127
|
if (element['#'] !== undefined) { // enum symbol reference
|
|
1129
1128
|
result += ` = #${ element['#'] }`;
|
|
@@ -2001,50 +2000,52 @@ class CsnToCdl {
|
|
|
2001
2000
|
* @param {any} annoValue
|
|
2002
2001
|
* @param {CdlRenderEnvironment} env
|
|
2003
2002
|
*/
|
|
2004
|
-
renderAnnotationValue( annoValue, env ) {
|
|
2005
|
-
if (
|
|
2006
|
-
// Once inside an expression, we stay there.
|
|
2007
|
-
const xpr = this.exprRenderer.renderExpr(annoValue, env);
|
|
2008
|
-
return `( ${ xpr } )`;
|
|
2009
|
-
}
|
|
2010
|
-
else if (Array.isArray(annoValue)) {
|
|
2003
|
+
renderAnnotationValue( annoValue, env, isTopLevel = false ) {
|
|
2004
|
+
if (Array.isArray(annoValue))
|
|
2011
2005
|
return this.renderAnnotationArrayValue( annoValue, env );
|
|
2012
|
-
}
|
|
2013
|
-
else if (typeof annoValue === 'object' && annoValue !== null) {
|
|
2014
|
-
// Enum symbol
|
|
2015
|
-
if (annoValue['#'] !== undefined) {
|
|
2016
|
-
return `#${ annoValue['#'] }`;
|
|
2017
|
-
}
|
|
2018
|
-
// Shorthand for absolute path (as string)
|
|
2019
|
-
else if (typeof annoValue['='] === 'string') {
|
|
2020
|
-
if (annoValue['='].startsWith('@'))
|
|
2021
|
-
return this.quoteAnnotationPathIfRequired(annoValue['='], env);
|
|
2022
|
-
return this.quotePathIfRequired(annoValue['='], env);
|
|
2023
|
-
}
|
|
2024
|
-
// Shorthand for ellipsis: `... up to <val>`
|
|
2025
|
-
else if (annoValue['...'] !== undefined) {
|
|
2026
|
-
if (annoValue['...'] === true)
|
|
2027
|
-
return '...';
|
|
2028
|
-
return `... up to ${ this.renderAnnotationValue(annoValue['...'], env) }`;
|
|
2029
|
-
}
|
|
2030
2006
|
|
|
2031
|
-
// Struct value (can currently only occur within an array)
|
|
2032
|
-
// Render as one-liner if there is at most one key. Render as multi-line
|
|
2033
|
-
// struct if there are more and use nicer indentation.
|
|
2034
|
-
const keys = Object.keys(annoValue);
|
|
2035
|
-
const childEnv = env.withIncreasedIndent();
|
|
2036
|
-
const values = keys.map(key => `${ this.quoteAnnotationPathIfRequired(key, env) }: ${ this.renderAnnotationValue(annoValue[key], childEnv.withSubPath([ key ])) }`);
|
|
2037
|
-
const result = joinDocuments(values, [ ',', line() ]);
|
|
2038
|
-
return format(nestBy(env.indent.length, bracketBlock(INDENT_SIZE, '{', result, '}') ));
|
|
2039
|
-
}
|
|
2040
2007
|
// Null
|
|
2041
|
-
|
|
2008
|
+
if (annoValue === null)
|
|
2042
2009
|
return 'null';
|
|
2010
|
+
|
|
2011
|
+
if (typeof annoValue !== 'object') {
|
|
2012
|
+
// Primitive: string, number, boolean
|
|
2013
|
+
// Quote strings, leave all others as they are
|
|
2014
|
+
return (typeof annoValue === 'string') ? renderString(annoValue, env) : String(annoValue);
|
|
2043
2015
|
}
|
|
2044
|
-
//
|
|
2016
|
+
// Enum symbol (is also annotation expression, but rendered without `(…)`
|
|
2017
|
+
if (annoValue['#'] !== undefined)
|
|
2018
|
+
return `#${ annoValue['#'] }`;
|
|
2045
2019
|
|
|
2046
|
-
|
|
2047
|
-
|
|
2020
|
+
if (isAnnotationExpression(annoValue)) {
|
|
2021
|
+
// Once inside an expression, we stay there.
|
|
2022
|
+
const xpr = this.exprRenderer.renderExpr(annoValue, env);
|
|
2023
|
+
return `( ${ xpr } )`;
|
|
2024
|
+
}
|
|
2025
|
+
// Unchecked reference in `=` (with additional props: render as struct if not top-level)
|
|
2026
|
+
if (typeof annoValue['='] === 'string' &&
|
|
2027
|
+
(isTopLevel || Object.keys(annoValue).length === 1)) {
|
|
2028
|
+
// TODO: the to.cdl backend could issue a warning if there are non-`=`
|
|
2029
|
+
// properties in a structure outside an array (not representable in CDL)
|
|
2030
|
+
return (annoValue['='].startsWith('@'))
|
|
2031
|
+
? this.quoteAnnotationPathIfRequired(annoValue['='], env)
|
|
2032
|
+
: this.quotePathIfRequired(annoValue['='], env);
|
|
2033
|
+
}
|
|
2034
|
+
// Ellipsis: `... up to <val>`
|
|
2035
|
+
if (annoValue['...'] !== undefined) {
|
|
2036
|
+
return (annoValue['...'] === true)
|
|
2037
|
+
? '...'
|
|
2038
|
+
: `... up to ${ this.renderAnnotationValue(annoValue['...'], env) }`;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// Struct value (TODO: issue warning if outside an array, possible via CSN input)
|
|
2042
|
+
// Render as one-liner if there is at most one key. Render as multi-line
|
|
2043
|
+
// struct if there are more and use nicer indentation.
|
|
2044
|
+
const keys = Object.keys(annoValue);
|
|
2045
|
+
const childEnv = env.withIncreasedIndent();
|
|
2046
|
+
const values = keys.map(key => `${ this.quoteAnnotationPathIfRequired(key, env) }: ${ this.renderAnnotationValue(annoValue[key], childEnv.withSubPath([ key ])) }`);
|
|
2047
|
+
const result = joinDocuments(values, [ ',', line() ]);
|
|
2048
|
+
return format(nestBy(env.indent.length, bracketBlock(INDENT_SIZE, '{', result, '}') ));
|
|
2048
2049
|
}
|
|
2049
2050
|
|
|
2050
2051
|
/**
|
|
@@ -2485,7 +2486,7 @@ class CsnToCdl {
|
|
|
2485
2486
|
// nor multiple `#`, so we're back at simple paths that are possibly quoted.
|
|
2486
2487
|
result += `#${ this.quotePathIfRequired(variant, env) }`;
|
|
2487
2488
|
}
|
|
2488
|
-
result += ` : ${ this.renderAnnotationValue(anno, env) }`;
|
|
2489
|
+
result += ` : ${ this.renderAnnotationValue(anno, env, true) }`;
|
|
2489
2490
|
|
|
2490
2491
|
if (parentheses)
|
|
2491
2492
|
result += ')';
|
package/lib/render/toSql.js
CHANGED
|
@@ -1460,7 +1460,14 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1460
1460
|
* @returns {string} Rendered type
|
|
1461
1461
|
*/
|
|
1462
1462
|
function renderBuiltinType( typeName ) {
|
|
1463
|
-
|
|
1463
|
+
// Determine if we should use REAL affinity for decimal types
|
|
1464
|
+
// Priority: 1) new decimal_affinity option, 2) old sqliteRealAffinityForDecimal option
|
|
1465
|
+
const useRealAffinity = options.sqlDialect === 'sqlite' && (
|
|
1466
|
+
options.decimal_affinity === 'real' ||
|
|
1467
|
+
(options.decimal_affinity === undefined && options.sqliteRealAffinityForDecimal === true)
|
|
1468
|
+
);
|
|
1469
|
+
|
|
1470
|
+
const types = cdsToSqlTypes[useRealAffinity ? 'sqlitereal' : options.sqlDialect];
|
|
1464
1471
|
const result = types?.[typeName] || cdsToSqlTypes.standard[typeName];
|
|
1465
1472
|
if (!result && options.testMode)
|
|
1466
1473
|
throw new CompilerAssertion(`Expected to find a type mapping for ${ typeName }`);
|
|
@@ -37,7 +37,7 @@ function renderFunc( funcName, node, renderArgs, utils ) {
|
|
|
37
37
|
const { sqlDialect } = options;
|
|
38
38
|
if (funcWithoutParen( node, sqlDialect ))
|
|
39
39
|
return funcName;
|
|
40
|
-
const rewriteStandardFunctions =
|
|
40
|
+
const rewriteStandardFunctions = sqlDialect !== 'plain' && options.standardDatabaseFunctions !== false;
|
|
41
41
|
if (rewriteStandardFunctions) {
|
|
42
42
|
// we check function arguments for correctness
|
|
43
43
|
const { error } = messageFunctions;
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -29,8 +29,7 @@ function renderReferentialConstraint( constraint, indent, toUpperCase, csn, opti
|
|
|
29
29
|
constraint.parentTable = constraint.parentTable.toUpperCase();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const renderAsHdbconstraint = options.
|
|
33
|
-
options.src === 'hdi';
|
|
32
|
+
const renderAsHdbconstraint = options.src === 'hdi';
|
|
34
33
|
|
|
35
34
|
const { sqlMapping, sqlDialect } = options;
|
|
36
35
|
let result = '';
|
|
@@ -54,7 +53,7 @@ function renderReferentialConstraint( constraint, indent, toUpperCase, csn, opti
|
|
|
54
53
|
}
|
|
55
54
|
// constraint enforcement / validation must be switched off using sqlite pragma statement
|
|
56
55
|
// constraint enforcement / validation not supported by postgres
|
|
57
|
-
if (options.
|
|
56
|
+
if (options.toSql && sqlDialect !== 'sqlite' && sqlDialect !== 'postgres') {
|
|
58
57
|
result += `${ indent }${ !constraint.validated ? 'NOT ' : '' }VALIDATED\n`;
|
|
59
58
|
result += `${ indent }${ !constraint.enforced ? 'NOT ' : '' }ENFORCED\n`;
|
|
60
59
|
}
|
|
@@ -142,9 +142,8 @@ function enrichCsn( csn, options = {} ) {
|
|
|
142
142
|
obj.forEach( (n, i) => assignment( obj, i, n ) );
|
|
143
143
|
}
|
|
144
144
|
else {
|
|
145
|
-
// TODO: check obj['='] is only String|true ?
|
|
146
145
|
const record = !isAnnotationExpression( obj ) && assignment;
|
|
147
|
-
// is record without
|
|
146
|
+
// is record without primary expression property
|
|
148
147
|
for (const name of Object.getOwnPropertyNames( obj ) ) {
|
|
149
148
|
const trans = record || transformers[name] || transformers[name.charAt(0)] || standard;
|
|
150
149
|
trans( obj, name, obj[name] );
|
|
@@ -358,30 +358,32 @@ function transformExpression( parent, parentName, transformers, path = [], ctx =
|
|
|
358
358
|
* @param {string|number} propName Start at specific property of parent
|
|
359
359
|
* @param {object} transformers Map of callback functions
|
|
360
360
|
* @param {CSN.Path} path Path to parent
|
|
361
|
+
* @param {boolean} annoScope Is it annotation scope
|
|
361
362
|
* @returns {object} transformed node
|
|
362
363
|
*/
|
|
363
|
-
function transformAnnotationExpression( parent, propName, transformers, path = [] ) {
|
|
364
|
+
function transformAnnotationExpression( parent, propName, transformers, path = [], annoScope = undefined ) {
|
|
364
365
|
if (propName != null) {
|
|
365
366
|
const child = parent[propName];
|
|
366
367
|
if (!child || typeof child !== 'object' ||
|
|
367
368
|
!{}.propertyIsEnumerable.call( parent, propName ))
|
|
368
369
|
return parent;
|
|
369
370
|
|
|
370
|
-
|
|
371
|
+
annoScope ||= propName[0] === '@';
|
|
372
|
+
if (annoScope && isAnnotationExpression(child))
|
|
371
373
|
return transformExpression(parent, propName, transformers, path, { annoExpr: child });
|
|
372
374
|
|
|
373
375
|
path = [ ...path, propName ];
|
|
374
376
|
if (Array.isArray(child)) {
|
|
375
|
-
child.forEach( (n, i) => transformAnnotationExpression( child, i, transformers, path ) );
|
|
377
|
+
child.forEach( (n, i) => transformAnnotationExpression( child, i, transformers, path, annoScope ) );
|
|
376
378
|
}
|
|
377
379
|
else {
|
|
378
380
|
for (const cpn of Object.getOwnPropertyNames( child ))
|
|
379
|
-
transformAnnotationExpression(child, cpn, transformers, path);
|
|
381
|
+
transformAnnotationExpression(child, cpn, transformers, path, annoScope);
|
|
380
382
|
}
|
|
381
383
|
}
|
|
382
384
|
else {
|
|
383
385
|
for (propName of Object.getOwnPropertyNames( parent ))
|
|
384
|
-
transformAnnotationExpression( parent, propName, transformers, path );
|
|
386
|
+
transformAnnotationExpression( parent, propName, transformers, path, annoScope );
|
|
385
387
|
}
|
|
386
388
|
return parent;
|
|
387
389
|
}
|
|
@@ -254,11 +254,7 @@ function processAssertUnique( csn, options, messageFunctions ) {
|
|
|
254
254
|
* If the output format is SQL, the toSql renderer is responsible
|
|
255
255
|
* to render the table constraints from the constraint dictionary.
|
|
256
256
|
*
|
|
257
|
-
*
|
|
258
|
-
* paths are replaced with the foreign key paths by simply
|
|
259
|
-
* concatenating the foreign key paths (available in element.keys).
|
|
260
|
-
*
|
|
261
|
-
* If options.toSql, all paths are flattened depending on the naming
|
|
257
|
+
* All paths are flattened depending on the naming
|
|
262
258
|
* mode either with '_' or '.' as delimiter.
|
|
263
259
|
* Each association is replaced by the respective foreign key elements
|
|
264
260
|
* that are annotated with an appropriate '@odata.foreignKey4'.
|
|
@@ -275,47 +271,23 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
|
|
|
275
271
|
function rewrite( artifact ) {
|
|
276
272
|
if (artifact.$tableConstraints && artifact.$tableConstraints.unique) {
|
|
277
273
|
const uniqueConstraints = artifact.$tableConstraints.unique;
|
|
278
|
-
// it's safe to add the tc here
|
|
279
|
-
if (options.transformation === 'hdbcds') {
|
|
280
|
-
if (!artifact.technicalConfig)
|
|
281
|
-
artifact.technicalConfig = Object.create(null);
|
|
282
|
-
|
|
283
|
-
if (!artifact.technicalConfig.hana) {
|
|
284
|
-
artifact.technicalConfig.hana = Object.create(null);
|
|
285
|
-
artifact.technicalConfig.hana.calculated = true;
|
|
286
|
-
}
|
|
287
|
-
if (!artifact.technicalConfig.hana.indexes)
|
|
288
|
-
artifact.technicalConfig.hana.indexes = Object.create(null);
|
|
289
|
-
}
|
|
290
274
|
for (const uniqueConstraint in artifact.$tableConstraints.unique) {
|
|
291
275
|
// iterate over each constraint
|
|
292
276
|
const c = uniqueConstraints[uniqueConstraint].paths;
|
|
293
277
|
const rewrittenPaths = [];
|
|
294
278
|
// and inspect each path of the constraint
|
|
295
279
|
c.forEach((cpath) => {
|
|
296
|
-
// If 'toSql' or 'toHana' and naming !== 'hdbcds'
|
|
297
280
|
// concatenate path refs with appropriate delimiter
|
|
298
|
-
|
|
299
|
-
cpath.ref = [ cpath.ref.map(p => p.id).join( pathDelimiter ) ];
|
|
281
|
+
cpath.ref = [ cpath.ref.map(p => p.id).join( pathDelimiter ) ];
|
|
300
282
|
|
|
301
283
|
// Foreign key substitution
|
|
302
284
|
if (cpath._art.target) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
rewrittenPaths.push(...assoc.keys.map(k => ({ ref: [ k.$generatedFieldName ] })));
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
// This is Classic HANA CDS toHana/hdbcds
|
|
313
|
-
// add foreign key ref path to association path
|
|
314
|
-
// ... for hanacds, the 'real' ref paths are used, and
|
|
315
|
-
// these have not changed before and after A2J transformation,
|
|
316
|
-
// so it's safe to use the original paths.
|
|
317
|
-
rewrittenPaths.push(...cpath._art.keys.map(k => ({ ref: cpath.ref.concat(k.ref) })));
|
|
318
|
-
}
|
|
285
|
+
// read out new association and use $generatedFieldName
|
|
286
|
+
// cpath._art still refers to the assoc definition
|
|
287
|
+
// before the A2J transformation. This assoc
|
|
288
|
+
// doesn't contain the correct $generatedFieldName(s)
|
|
289
|
+
const assoc = artifact.elements[cpath.ref[0]];
|
|
290
|
+
rewrittenPaths.push(...assoc.keys.map(k => ({ ref: [ k.$generatedFieldName ] })));
|
|
319
291
|
}
|
|
320
292
|
else {
|
|
321
293
|
rewrittenPaths.push(cpath);
|
|
@@ -323,21 +295,6 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
|
|
|
323
295
|
});
|
|
324
296
|
// preserve the rewritten and filtered paths for toSql
|
|
325
297
|
uniqueConstraints[uniqueConstraint] = { paths: rewrittenPaths, parentTable: uniqueConstraints[uniqueConstraint].parentTable };
|
|
326
|
-
|
|
327
|
-
// now add the index for HANA CDS
|
|
328
|
-
if (options.transformation === 'hdbcds') {
|
|
329
|
-
const cond = [];
|
|
330
|
-
let i = 0;
|
|
331
|
-
for (const constraint of rewrittenPaths) {
|
|
332
|
-
if (i > 0)
|
|
333
|
-
cond.push(',');
|
|
334
|
-
cond.push(constraint);
|
|
335
|
-
i++;
|
|
336
|
-
}
|
|
337
|
-
artifact.technicalConfig.hana.indexes[uniqueConstraint] = [
|
|
338
|
-
'unique', 'index', { ref: [ uniqueConstraint ] }, 'on', { xpr: cond },
|
|
339
|
-
];
|
|
340
|
-
}
|
|
341
298
|
}
|
|
342
299
|
artifact.$tableConstraints.unique = uniqueConstraints;
|
|
343
300
|
}
|
|
@@ -136,7 +136,7 @@ function getFKAccessFinalizer( csn, options, csnUtils, pathDelimiter, processOnI
|
|
|
136
136
|
if (obj[anno].ref)
|
|
137
137
|
transformer.ref(obj[anno], 'ref', obj[anno].ref, path.concat(anno));
|
|
138
138
|
const annoAfter = JSON.stringify(obj[anno]);
|
|
139
|
-
if (annoBefore !== annoAfter)
|
|
139
|
+
if (annoBefore !== annoAfter && (options.transformation !== 'effective' || obj[anno]['=']))
|
|
140
140
|
obj[anno]['='] = true;
|
|
141
141
|
});
|
|
142
142
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
forEachGeneric,
|
|
5
4
|
forEachMemberRecursively,
|
|
6
5
|
isPersistedOnDatabase,
|
|
7
6
|
hasPersistenceSkipAnnotation,
|
|
@@ -42,7 +41,6 @@ function getAnnoProcessor() {
|
|
|
42
41
|
*/
|
|
43
42
|
function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
44
43
|
const { info } = messageFunctions;
|
|
45
|
-
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
|
|
46
44
|
|
|
47
45
|
const { isAssocOrComposition } = csnUtils;
|
|
48
46
|
|
|
@@ -53,21 +51,9 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
53
51
|
*
|
|
54
52
|
* @param {CSN.Artifact} artifact
|
|
55
53
|
* @param {string} artifactName
|
|
56
|
-
* @param {string} prop
|
|
57
|
-
* @param {CSN.Path} path
|
|
58
54
|
*/
|
|
59
|
-
function ignoreAssociationToSkippedTarget( artifact, artifactName,
|
|
55
|
+
function ignoreAssociationToSkippedTarget( artifact, artifactName, _prop, _path ) {
|
|
60
56
|
if (isPersistedOnDatabase(artifact)) {
|
|
61
|
-
// TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
|
|
62
|
-
if (artifact.query) {
|
|
63
|
-
// If we do A2J, we don't need to check the mixin. Either it is used -> a join
|
|
64
|
-
// or published -> handled via elements/members. Unused mixins are removed anyway.
|
|
65
|
-
if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
66
|
-
forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
|
|
67
|
-
|
|
68
|
-
else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
|
|
69
|
-
forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
|
|
70
|
-
}
|
|
71
57
|
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
72
58
|
}
|
|
73
59
|
}
|
|
@@ -40,13 +40,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
40
40
|
columns: (parent, name, columns, path) => {
|
|
41
41
|
const artifact = csn.definitions[path[1]];
|
|
42
42
|
csnUtils.initDefinition(artifact); // potentially not initialized, yet
|
|
43
|
-
const root = csnUtils.get$combined({ SELECT: parent });
|
|
44
|
-
// TODO: replace with the correct options.transformation?
|
|
45
|
-
// Do not expand the * in OData for a moment, not to introduce changes
|
|
46
|
-
// while the OData CSN is still official
|
|
47
43
|
const isComplexQuery = parent.from.join !== undefined;
|
|
48
|
-
if (!options.toOdata)
|
|
44
|
+
if (!options.toOdata && columns.includes('*')) {
|
|
45
|
+
const root = csnUtils.get$combined({ SELECT: parent });
|
|
49
46
|
parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
|
|
47
|
+
}
|
|
50
48
|
// FIXME(v6): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
|
|
51
49
|
// if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
|
|
52
50
|
parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
|
|
@@ -63,7 +61,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
63
61
|
};
|
|
64
62
|
|
|
65
63
|
// To not have a whole model loop for such a "small" thing, we kill all non-sql-backend relevant annotations here
|
|
66
|
-
if (options.transformation === 'sql'
|
|
64
|
+
if (options.transformation === 'sql')
|
|
67
65
|
transformers['@'] = killNonrequiredAnno;
|
|
68
66
|
|
|
69
67
|
applyTransformations(csn, transformers, [], iterateOptions);
|
|
@@ -362,7 +360,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
362
360
|
if (sub.ref) {
|
|
363
361
|
// Each expand/inline can introduce another layer of $self/$projection. Since $self is
|
|
364
362
|
// a path-breakout, we can simply use the ref without outer expand/inline-references.
|
|
365
|
-
subRef = (sub.$scope === '$self') ? sub.ref : currentRef.concat(sub.ref);
|
|
363
|
+
subRef = (sub.$scope === '$self' || sub.param) ? sub.ref : currentRef.concat(sub.ref);
|
|
366
364
|
}
|
|
367
365
|
else {
|
|
368
366
|
subRef = currentRef;
|
|
@@ -456,7 +454,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
456
454
|
function rewriteOnCondition( on, currentRef, stack ) {
|
|
457
455
|
for (let i = 0; i < on.length; i++) {
|
|
458
456
|
const part = on[i];
|
|
459
|
-
if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self' && part.$scope !== '$projection') {
|
|
457
|
+
if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self' && part.$scope !== '$projection' && !part.param) {
|
|
460
458
|
part.ref = currentRef[0] ? [ currentRef[0], ...part.ref ] : part.ref;
|
|
461
459
|
on[i] = part;
|
|
462
460
|
stack.push([ part, part.ref ]);
|
|
@@ -477,7 +475,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
477
475
|
function rewriteSingleExpressionArray( expressionArray, currentRef, stack ) {
|
|
478
476
|
for (let i = 0; i < expressionArray.length; i++) {
|
|
479
477
|
const part = expressionArray[i];
|
|
480
|
-
if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self') {
|
|
478
|
+
if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self' && !part.param) {
|
|
481
479
|
part.ref = currentRef.concat(part.ref);
|
|
482
480
|
expressionArray[i] = part;
|
|
483
481
|
stack.push([ part, part.ref ]);
|
|
@@ -842,7 +840,7 @@ function expandWildcard( query, csnUtils, options ) {
|
|
|
842
840
|
|
|
843
841
|
/**
|
|
844
842
|
* Get all elements that are expanded by '*'.
|
|
845
|
-
* Respects the query's 'excluding' clause
|
|
843
|
+
* Respects the query's 'excluding' clause.
|
|
846
844
|
* Elements that are replaced by columns have a `replacedByColumn` property.
|
|
847
845
|
*
|
|
848
846
|
* @param {object} query Query with SELECT/projection.
|
|
@@ -855,9 +853,8 @@ function wildcardElements( query, csnUtils ) {
|
|
|
855
853
|
for (const excluded of SELECT.excluding ?? [])
|
|
856
854
|
delete combined[excluded];
|
|
857
855
|
|
|
858
|
-
//
|
|
856
|
+
// TODO?: simplify
|
|
859
857
|
for (const name in combined) {
|
|
860
|
-
combined[name] = combined[name].filter(sourceElement => !sourceElement.element?.masked);
|
|
861
858
|
if (combined[name].length === 0)
|
|
862
859
|
delete combined[name];
|
|
863
860
|
else
|
|
@@ -80,7 +80,7 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
80
80
|
iterateOptions.skipDict = { actions: true };
|
|
81
81
|
|
|
82
82
|
const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
|
|
83
|
-
const stripItems = options.transformation === '
|
|
83
|
+
const stripItems = options.transformation === 'sql';
|
|
84
84
|
const removeItems = new Set();
|
|
85
85
|
applyTransformations(csn, {
|
|
86
86
|
type: (node, prop, type, path, parent, parentProp) => {
|
|
@@ -24,14 +24,6 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
|
|
|
24
24
|
const { art } = inspectRef(groupByPath);
|
|
25
25
|
if (art && art.target) {
|
|
26
26
|
if (art.keys) {
|
|
27
|
-
// This is (or used to be before transformation) a managed assoc
|
|
28
|
-
// (230 c) If we keep associations as they are (hdbcds naming convention), we can't have associations in GROUP BY
|
|
29
|
-
if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
|
|
30
|
-
error(null, groupByPath,
|
|
31
|
-
{ $reviewed: true, keyword: 'GROUP BY', value: 'hdbcds' },
|
|
32
|
-
'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
27
|
const pathPrefix = query.groupBy[i].ref.slice(0, -1);
|
|
36
28
|
getForeignKeyRefs(art)
|
|
37
29
|
.map(fk => ({ ref: pathPrefix.concat(fk.ref) }))
|
|
@@ -57,14 +49,6 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
|
|
|
57
49
|
const { art } = inspectRef(orderByPath);
|
|
58
50
|
if (art && art.target) {
|
|
59
51
|
if (art.keys) {
|
|
60
|
-
// This is (or used to be before transformation) a managed assoc
|
|
61
|
-
// (230 d) If we keep associations as they are (hdbcds naming convention), we can't have associations in ORDER BY
|
|
62
|
-
if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
|
|
63
|
-
error(null, orderByPath,
|
|
64
|
-
{ $reviewed: true, keyword: 'ORDER BY', value: 'hdbcds' },
|
|
65
|
-
'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
52
|
const pathPrefix = query.orderBy[i].ref.slice(0, -1);
|
|
69
53
|
getForeignKeyRefs(art)
|
|
70
54
|
.map(fk => ({ ref: pathPrefix.concat(fk.ref) }))
|