@sap/cds-compiler 4.8.0 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -4
- package/bin/cds_remove_invalid_whitespace.js +135 -0
- package/bin/cds_update_annotations.js +180 -0
- package/bin/cds_update_identifiers.js +3 -4
- package/bin/cdsc.js +14 -1
- package/doc/CHANGELOG_BETA.md +19 -0
- package/lib/api/main.js +59 -24
- package/lib/api/options.js +12 -1
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +27 -0
- package/lib/base/message-registry.js +32 -19
- package/lib/base/messages.js +50 -19
- package/lib/base/model.js +4 -5
- package/lib/checks/actionsFunctions.js +2 -2
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +3 -2
- package/lib/checks/validator.js +2 -34
- package/lib/compiler/assert-consistency.js +8 -2
- package/lib/compiler/checks.js +44 -18
- package/lib/compiler/define.js +34 -22
- package/lib/compiler/extend.js +33 -10
- package/lib/compiler/index.js +0 -1
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/propagator.js +21 -18
- package/lib/compiler/resolve.js +44 -28
- package/lib/compiler/shared.js +60 -20
- package/lib/compiler/tweak-assocs.js +13 -88
- package/lib/compiler/xpr-rewrite.js +689 -0
- package/lib/edm/annotations/genericTranslation.js +80 -60
- package/lib/edm/edm.js +4 -4
- package/lib/edm/edmInboundChecks.js +33 -0
- package/lib/edm/edmPreprocessor.js +9 -6
- package/lib/gen/Dictionary.json +129 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1523 -1518
- package/lib/json/from-csn.js +13 -4
- package/lib/json/to-csn.js +10 -11
- package/lib/language/genericAntlrParser.js +14 -6
- package/lib/main.d.ts +67 -14
- package/lib/main.js +1 -0
- package/lib/model/cloneCsn.js +6 -3
- package/lib/model/csnRefs.js +12 -7
- package/lib/model/csnUtils.js +13 -7
- package/lib/model/enrichCsn.js +3 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +33 -34
- package/lib/optionProcessor.js +27 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +3 -1
- package/lib/transform/db/applyTransformations.js +33 -0
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +8 -3
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/temporal.js +6 -3
- package/lib/transform/db/transformExists.js +2 -2
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +6 -8
- package/lib/transform/effective/misc.js +31 -10
- package/lib/transform/forOdata.js +23 -7
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +189 -106
- package/lib/transform/odata/toFinalBaseType.js +1 -1
- package/lib/transform/odata/typesExposure.js +15 -12
- package/lib/transform/parseExpr.js +4 -4
- package/lib/transform/transformUtils.js +40 -37
- package/lib/transform/translateAssocsToJoins.js +47 -47
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
- package/package.json +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/message-explanations.json +1 -0
- package/bin/.eslintrc.json +0 -17
- package/lib/api/.eslintrc.json +0 -37
- package/lib/checks/.eslintrc.json +0 -31
- package/lib/compiler/.eslintrc.json +0 -8
- package/lib/edm/.eslintrc.json +0 -46
- package/lib/inspect/.eslintrc.json +0 -4
- package/lib/json/.eslintrc.json +0 -4
- package/lib/language/.eslintrc.json +0 -4
- package/lib/model/.eslintrc.json +0 -13
- package/lib/modelCompare/utils/.eslintrc.json +0 -22
- package/lib/render/.eslintrc.json +0 -22
- package/lib/transform/.eslintrc.json +0 -13
- package/lib/transform/db/.eslintrc.json +0 -41
- package/lib/transform/draft/.eslintrc.json +0 -4
- package/lib/transform/effective/.eslintrc.json +0 -4
- package/lib/transform/universalCsn/.eslintrc.json +0 -37
- package/lib/utils/.eslintrc.json +0 -7
package/lib/json/from-csn.js
CHANGED
|
@@ -649,7 +649,8 @@ const schema = compileSchema( {
|
|
|
649
649
|
noPrefix: false, // just '@' is no CSN property
|
|
650
650
|
prop: '@‹anno›', // which property name do messages use for annotation assignments?
|
|
651
651
|
type: annotation,
|
|
652
|
-
|
|
652
|
+
// allowed in all definitions except mixins (including columns and extensions)
|
|
653
|
+
inKind: _kind => true, // TODO(v5): (kind !== 'mixin')
|
|
653
654
|
schema: {
|
|
654
655
|
'-expr': { // '-expr' and '-' must not exist top-level
|
|
655
656
|
prop: '@‹anno›',
|
|
@@ -745,7 +746,7 @@ const schema = compileSchema( {
|
|
|
745
746
|
ignore: true, type: ignore, // TODO: do we need to do something?
|
|
746
747
|
},
|
|
747
748
|
namespace: {
|
|
748
|
-
type:
|
|
749
|
+
type: namespace,
|
|
749
750
|
},
|
|
750
751
|
meta: { // meta information
|
|
751
752
|
type: ignore, // TODO: should we test s/th here?
|
|
@@ -1043,6 +1044,11 @@ function definition( def, spec, xsn, csn, name ) {
|
|
|
1043
1044
|
}
|
|
1044
1045
|
}
|
|
1045
1046
|
|
|
1047
|
+
function namespace( ref, spec ) {
|
|
1048
|
+
const ns = stringRef(ref, spec);
|
|
1049
|
+
return ns ? { kind: 'namespace', name: ns } : null;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1046
1052
|
function couldNotBeEnumProperty( prop ) {
|
|
1047
1053
|
// returns true for `value` (which we allow with warning when extending an enum with `elements`)
|
|
1048
1054
|
const inKind = schema[prop]?.inKind; // undefined for annotations, $location, …
|
|
@@ -1486,9 +1492,12 @@ function annoValue( val, spec ) {
|
|
|
1486
1492
|
function annotation( val, spec, xsn, csn, name ) {
|
|
1487
1493
|
// not used for the value
|
|
1488
1494
|
const id = (xsn ? name.substring(1) : name);
|
|
1489
|
-
if (!id)
|
|
1495
|
+
if (!id) // `"@": …` is already syntax-unknown-property
|
|
1490
1496
|
message( 'syntax-invalid-name', location(true), { '#': '{}' } );
|
|
1491
|
-
|
|
1497
|
+
|
|
1498
|
+
if (xsn && xsn.kind === 'mixin') // TODO(v5): Remove, adapt spec['@'].inKind
|
|
1499
|
+
message('anno-unexpected-mixin', location(true));
|
|
1500
|
+
|
|
1492
1501
|
const n = { id, location: location() };
|
|
1493
1502
|
const r = annoValue( val, spec );
|
|
1494
1503
|
r.name = n;
|
package/lib/json/to-csn.js
CHANGED
|
@@ -232,15 +232,13 @@ function compactModel( model, options = model.options || {} ) {
|
|
|
232
232
|
if (using.length)
|
|
233
233
|
csn.requires = using;
|
|
234
234
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
235
|
+
// 'namespace' for complete model is 'namespace' of first source
|
|
236
|
+
// (not a really useful property at all, avoids XSN inspection by Umbrella)
|
|
237
|
+
for (const first in srcDict) {
|
|
238
|
+
const { namespace } = srcDict[first];
|
|
239
|
+
if (namespace?.name?.path)
|
|
240
|
+
csn.namespace = pathName( namespace.name.path );
|
|
241
|
+
break;
|
|
244
242
|
}
|
|
245
243
|
set( 'definitions', csn, model );
|
|
246
244
|
if (Object.keys(model.vocabularies || {}).length > 0)
|
|
@@ -1010,8 +1008,9 @@ function artifactRef( node, terse ) {
|
|
|
1010
1008
|
return undefined;
|
|
1011
1009
|
// Works also on XSN directly coming from parser and with XSN from CDL->CSN transformation
|
|
1012
1010
|
// Shortcut for many cases:
|
|
1013
|
-
|
|
1014
|
-
|
|
1011
|
+
const art = node._artifact;
|
|
1012
|
+
if (art && (!art._main || art.kind === '$self') && terse && terse !== '.path')
|
|
1013
|
+
return art.name.id;
|
|
1015
1014
|
let { path } = node;
|
|
1016
1015
|
if (!path)
|
|
1017
1016
|
return undefined; // TODO: complain with strict
|
|
@@ -724,11 +724,11 @@ function fragileAlias( ast, safe = false ) {
|
|
|
724
724
|
return ast;
|
|
725
725
|
if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id )) {
|
|
726
726
|
this.warning( 'syntax-deprecated-auto-as', ast.location, { keyword: 'as' },
|
|
727
|
-
'
|
|
727
|
+
'Add keyword $(KEYWORD) in front of the alias name' );
|
|
728
728
|
}
|
|
729
729
|
else { // configurable error
|
|
730
730
|
this.message( 'syntax-missing-as', ast.location, { keyword: 'as' },
|
|
731
|
-
'
|
|
731
|
+
'Add keyword $(KEYWORD) in front of the alias name' );
|
|
732
732
|
}
|
|
733
733
|
return ast;
|
|
734
734
|
}
|
|
@@ -759,10 +759,16 @@ function identAst( token, category, noTokenTypeCheck = false ) {
|
|
|
759
759
|
|
|
760
760
|
// $delimited is used to complain about ![$self] and other magic vars usage;
|
|
761
761
|
// we might complain about that already here via @arg{category}
|
|
762
|
-
|
|
762
|
+
|
|
763
|
+
const ast = { id, $delimited: true, location: this.tokenLocation( token ) };
|
|
764
|
+
ast.location.tokenIndex = token.tokenIndex;
|
|
765
|
+
return ast;
|
|
766
|
+
}
|
|
767
|
+
if (token.text[0] !== '"') {
|
|
768
|
+
const ast = { id, location: this.tokenLocation(token) };
|
|
769
|
+
ast.location.tokenIndex = token.tokenIndex;
|
|
770
|
+
return ast;
|
|
763
771
|
}
|
|
764
|
-
if (token.text[0] !== '"')
|
|
765
|
-
return { id, location: this.tokenLocation( token ) };
|
|
766
772
|
// delimited:
|
|
767
773
|
id = id.slice( 1, -1 ).replace( /""/g, '"' );
|
|
768
774
|
if (!id) {
|
|
@@ -773,7 +779,9 @@ function identAst( token, category, noTokenTypeCheck = false ) {
|
|
|
773
779
|
// eslint-disable-next-line max-len
|
|
774
780
|
'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
|
|
775
781
|
}
|
|
776
|
-
|
|
782
|
+
const ast = { id, $delimited: true, location: this.tokenLocation( token ) };
|
|
783
|
+
ast.location.tokenIndex = token.tokenIndex;
|
|
784
|
+
return ast;
|
|
777
785
|
}
|
|
778
786
|
|
|
779
787
|
function pushXprToken( args ) {
|
package/lib/main.d.ts
CHANGED
|
@@ -150,6 +150,22 @@ declare namespace compiler {
|
|
|
150
150
|
* @since v2.12.1
|
|
151
151
|
*/
|
|
152
152
|
$xsnObjects?: boolean
|
|
153
|
+
/**
|
|
154
|
+
* Internal option for LSP only!
|
|
155
|
+
* If set, each AST gets a `tokenStream` property containing all lexed tokens.
|
|
156
|
+
*
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
attachTokens?: boolean
|
|
160
|
+
/**
|
|
161
|
+
* Internal option for LSP only!
|
|
162
|
+
* If set, enables some extra checks/work for the CDS LSP.
|
|
163
|
+
* May be implicitly set by `$lsp.<api>` functions.
|
|
164
|
+
*
|
|
165
|
+
* @private
|
|
166
|
+
* @since v4.9
|
|
167
|
+
*/
|
|
168
|
+
lspMode?: boolean
|
|
153
169
|
}
|
|
154
170
|
|
|
155
171
|
/**
|
|
@@ -260,13 +276,34 @@ declare namespace compiler {
|
|
|
260
276
|
/**
|
|
261
277
|
* Options used by the `for.effective()` CSN transformation.
|
|
262
278
|
*
|
|
263
|
-
* WORK IN PROGRESS
|
|
264
|
-
*
|
|
265
279
|
* @internal
|
|
266
280
|
* @see _for.effective()
|
|
267
281
|
*/
|
|
268
282
|
export interface EffectiveCsnOptions extends SqlOptions {
|
|
269
|
-
|
|
283
|
+
/**
|
|
284
|
+
* If true, resolve simple type references to their simple base type.
|
|
285
|
+
*
|
|
286
|
+
* @default true
|
|
287
|
+
*/
|
|
288
|
+
resolveSimpleTypes?: boolean
|
|
289
|
+
/**
|
|
290
|
+
* If true, transform projections into ordinary views with SELECT.
|
|
291
|
+
*
|
|
292
|
+
* @default true
|
|
293
|
+
*/
|
|
294
|
+
resolveProjections?: boolean
|
|
295
|
+
/**
|
|
296
|
+
* If true, remap OData annotations to ABAP annotations.
|
|
297
|
+
*
|
|
298
|
+
* @default false
|
|
299
|
+
*/
|
|
300
|
+
remapOdataAnnotations?: boolean
|
|
301
|
+
/**
|
|
302
|
+
* If true, keep '.localized' property in the CSN.
|
|
303
|
+
*
|
|
304
|
+
* @default false
|
|
305
|
+
*/
|
|
306
|
+
keepLocalized?: boolean
|
|
270
307
|
}
|
|
271
308
|
|
|
272
309
|
/**
|
|
@@ -547,11 +584,6 @@ declare namespace compiler {
|
|
|
547
584
|
* @internal
|
|
548
585
|
*/
|
|
549
586
|
model?: CSN;
|
|
550
|
-
/**
|
|
551
|
-
* Used by `cdsc` to indicate whether the message was already printed to stderr.
|
|
552
|
-
* @private
|
|
553
|
-
*/
|
|
554
|
-
hasBeenReported: boolean;
|
|
555
587
|
}
|
|
556
588
|
|
|
557
589
|
/**
|
|
@@ -666,16 +698,23 @@ declare namespace compiler {
|
|
|
666
698
|
* @param config.noMessageId
|
|
667
699
|
* If true, will _not_ show the message ID (+ explanation hint) in the output.
|
|
668
700
|
*
|
|
701
|
+
* @param config.idInBrackets
|
|
702
|
+
* If true, the message ID (if there is one and noMessageId is falsey) will be put in brackets.
|
|
703
|
+
* This will be the default in cds-compiler v5.
|
|
704
|
+
*
|
|
669
705
|
* @param config.noHome
|
|
670
706
|
* If true, will _not_ show message's semantic location.
|
|
671
707
|
*
|
|
672
|
-
* @param config.
|
|
708
|
+
* @param config.moduleForMarker
|
|
673
709
|
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
|
|
674
|
-
* the message can be downgraded for the given module.
|
|
710
|
+
* the message can be downgraded for the given module. A `‹↑›` is used if the message
|
|
711
|
+
* will be an error in the next major cds-compiler release.
|
|
712
|
+
* Was called `module` in v4.8.0 and earlier.
|
|
675
713
|
*/
|
|
676
714
|
export function messageString(msg: CompileMessage, config?: {
|
|
677
715
|
normalizeFilename?: boolean
|
|
678
716
|
noMessageId?: boolean
|
|
717
|
+
idInBrackets?: boolean
|
|
679
718
|
noHome?: boolean
|
|
680
719
|
module?: string
|
|
681
720
|
}): string;
|
|
@@ -713,9 +752,11 @@ declare namespace compiler {
|
|
|
713
752
|
* @param config.hintExplanation
|
|
714
753
|
* If true, messages with explanations will get a "…" marker, see {@link hasMessageExplanation}.
|
|
715
754
|
*
|
|
716
|
-
* @param config.
|
|
755
|
+
* @param config.moduleForMarker
|
|
717
756
|
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
|
|
718
|
-
* the message can be downgraded for the given module.
|
|
757
|
+
* the message can be downgraded for the given module. A `‹↑›` is used if the message
|
|
758
|
+
* will be an error in the next major cds-compiler release.
|
|
759
|
+
* Was called `module` in v4.8.0 and earlier.
|
|
719
760
|
*
|
|
720
761
|
* @param config.sourceMap
|
|
721
762
|
* A dictionary of filename<->source-code entries. You can pass the fileCache that is used
|
|
@@ -742,7 +783,7 @@ declare namespace compiler {
|
|
|
742
783
|
normalizeFilename?: boolean
|
|
743
784
|
noMessageId?: boolean
|
|
744
785
|
hintExplanation?: boolean
|
|
745
|
-
|
|
786
|
+
moduleForMarker?: string
|
|
746
787
|
sourceMap?: Record<string, string>
|
|
747
788
|
sourceLineMap?: Record<string, number[]>
|
|
748
789
|
cwd?: string
|
|
@@ -857,6 +898,12 @@ declare namespace compiler {
|
|
|
857
898
|
* @internal
|
|
858
899
|
*/
|
|
859
900
|
function effective(csn: CSN, options?: EffectiveCsnOptions): CSN;
|
|
901
|
+
/**
|
|
902
|
+
* Transform the given CSN into one that has the properties required for SEAL
|
|
903
|
+
*
|
|
904
|
+
* @internal
|
|
905
|
+
*/
|
|
906
|
+
function seal(csn: CSN, options?: EffectiveCsnOptions): CSN;
|
|
860
907
|
}
|
|
861
908
|
|
|
862
909
|
export { _for as for };
|
|
@@ -1269,7 +1316,13 @@ declare namespace compiler {
|
|
|
1269
1316
|
* The functions in `userFunctions` are usually transformer functions, which
|
|
1270
1317
|
* change the input CSN destructively.
|
|
1271
1318
|
*/
|
|
1272
|
-
export function traverseCsn(userFunctions: Record<string,
|
|
1319
|
+
export function traverseCsn(userFunctions: Record<string, TraverseCsnCallback>, csn: object|any[]): void;
|
|
1320
|
+
export type TraverseCsnCallback = (
|
|
1321
|
+
userFunctions: Record<string, TraverseCsnCallback>,
|
|
1322
|
+
value: any,
|
|
1323
|
+
csn: any,
|
|
1324
|
+
prop: string
|
|
1325
|
+
) => any;
|
|
1273
1326
|
|
|
1274
1327
|
/**
|
|
1275
1328
|
* CSN Model related functions.
|
package/lib/main.js
CHANGED
|
@@ -128,6 +128,7 @@ module.exports = {
|
|
|
128
128
|
odata: (...args) => snapi.odata(...args),
|
|
129
129
|
java: (...args) => snapi.java(...args),
|
|
130
130
|
effective: (...args) => snapi.for_effective(...args),
|
|
131
|
+
seal: (...args) => snapi.for_seal(...args),
|
|
131
132
|
},
|
|
132
133
|
to: {
|
|
133
134
|
cdl: Object.assign((...args) => snapi.cdl(...args), {
|
package/lib/model/cloneCsn.js
CHANGED
|
@@ -47,9 +47,12 @@ const internalCsnPropertyNames = Object.keys(internalCsnProps);
|
|
|
47
47
|
* @see cloneAnnotationValue()
|
|
48
48
|
* @see cloneCsnDict()
|
|
49
49
|
*
|
|
50
|
-
* @param {object} csn
|
|
51
|
-
*
|
|
52
|
-
* @param {
|
|
50
|
+
* @param {object} csn
|
|
51
|
+
* Top-level CSN. You can pass non-dictionary values.
|
|
52
|
+
* @param {CSN.Options} options
|
|
53
|
+
* CSN Options, only used for `dictionaryPrototype`, `testMode`, and `testSortCsn`.
|
|
54
|
+
* @param {boolean} sort
|
|
55
|
+
* Whether to sort CSN properties.
|
|
53
56
|
*/
|
|
54
57
|
function cloneCsn( csn, options, sort ) {
|
|
55
58
|
if (!csn || typeof csn !== 'object')
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -230,7 +230,8 @@ const referenceSemantics = {
|
|
|
230
230
|
orderBy_ref: { lexical: query => query, dynamic: 'query' },
|
|
231
231
|
orderBy_expr: { lexical: query => query, dynamic: 'source' }, // ref in ORDER BY expression
|
|
232
232
|
orderBy_set_ref: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
|
|
233
|
-
// refs in ORDER BY expr in UNION not really allowed
|
|
233
|
+
// refs in ORDER BY expr in UNION not really allowed
|
|
234
|
+
// only with table alias (of outer queries) or $self
|
|
234
235
|
orderBy_set_expr: { lexical: query => query.$next, dynamic: false },
|
|
235
236
|
// default: { lexical: query => query, dynamic: 'source' }
|
|
236
237
|
};
|
|
@@ -409,8 +410,11 @@ function csnRefs( csn, universalReady ) {
|
|
|
409
410
|
}
|
|
410
411
|
|
|
411
412
|
function getOriginRaw( art ) {
|
|
412
|
-
if (art.type) // TODO: make robust against "linked" = only direct
|
|
413
|
-
return
|
|
413
|
+
if (art.type) { // TODO: make robust against "linked" = only direct
|
|
414
|
+
return (art.type !== '$self' || csn.definitions.$self)
|
|
415
|
+
? artifactRef( art.type, BUILTIN_TYPE )
|
|
416
|
+
: getCache( boundActionOrMain( art ), '_parent' );
|
|
417
|
+
}
|
|
414
418
|
if (typeof art.$origin === 'object') // null, […], {…}
|
|
415
419
|
return getOriginExplicit( art.$origin );
|
|
416
420
|
|
|
@@ -698,7 +702,8 @@ function csnRefs( csn, universalReady ) {
|
|
|
698
702
|
const links = path.map( (_v, idx) => ({ idx }) );
|
|
699
703
|
// TODO: backends should be changed to enable uncommenting:
|
|
700
704
|
// if (!art) // does not work with test3/Associations/KeylessManagedAssociation/
|
|
701
|
-
// throw new ModelError( `Path item 0=${ pathId( path[0] )
|
|
705
|
+
// throw new ModelError( `Path item 0=${ pathId( path[0] )
|
|
706
|
+
// } refers to nothing, scope: ${ scope }`);
|
|
702
707
|
links[0].art = art;
|
|
703
708
|
for (let i = 1; i < links.length; ++i) { // yes, starting at 1, links[0] is set above
|
|
704
709
|
parent = navigationEnv( art, staticAssoc );
|
|
@@ -1010,8 +1015,8 @@ function queryOrMain( query, main ) {
|
|
|
1010
1015
|
* - NOT on a `join` node inside `from`.
|
|
1011
1016
|
*
|
|
1012
1017
|
* @param {CSN.Query} query
|
|
1013
|
-
* @param {CSN.QuerySelect} fromSelect
|
|
1014
|
-
* @param {CSN.Query} parentQuery
|
|
1018
|
+
* @param {CSN.QuerySelect} fromSelect for query in `from`
|
|
1019
|
+
* @param {CSN.Query} parentQuery for a sub query (ex those in `from`)
|
|
1015
1020
|
* @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched, parentQuery: CSN.Query) => void} callback
|
|
1016
1021
|
*/
|
|
1017
1022
|
function traverseQuery( query, fromSelect, parentQuery, callback ) {
|
|
@@ -1135,7 +1140,7 @@ function startCsnPath( csnPath, csn ) {
|
|
|
1135
1140
|
};
|
|
1136
1141
|
}
|
|
1137
1142
|
if (csnPath.length < 2 || head !== 'definitions' && head !== 'vocabularies')
|
|
1138
|
-
throw new CompilerAssertion( 'References outside definitions and vocabularies not supported yet');
|
|
1143
|
+
throw new CompilerAssertion( 'References outside definitions and vocabularies not supported yet' );
|
|
1139
1144
|
const art = csn[head][csnPath[1]];
|
|
1140
1145
|
return {
|
|
1141
1146
|
index: 2, main: art, parent: art, art, query: null,
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
applyTransformations,
|
|
7
7
|
applyTransformationsOnNonDictionary,
|
|
8
8
|
applyTransformationsOnDictionary,
|
|
9
|
+
mergeTransformers,
|
|
9
10
|
} = require('../transform/db/applyTransformations');
|
|
10
11
|
const { isBuiltinType, isAnnotationExpression } = require('../base/builtins');
|
|
11
12
|
const { ModelError, CompilerAssertion } = require('../base/error');
|
|
@@ -134,7 +135,8 @@ function getUtils( model, universalReady ) {
|
|
|
134
135
|
}
|
|
135
136
|
else if (arg.ref) {
|
|
136
137
|
const art = artifactRef.from(arg);
|
|
137
|
-
elements = mergeElementsIntoMap(elements, art.elements, art.$location,
|
|
138
|
+
elements = mergeElementsIntoMap(elements, art.elements, art.$location,
|
|
139
|
+
arg.as || implicitAs(arg.ref), implicitAs(arg.ref) || arg.as);
|
|
138
140
|
}
|
|
139
141
|
else if (arg.SELECT || arg.SET) {
|
|
140
142
|
elements = mergeElementMaps(elements, getSources(arg, true));
|
|
@@ -670,11 +672,13 @@ function isEdmPropertyRendered( elementCsn, options ) {
|
|
|
670
672
|
// V4 struct: on/off
|
|
671
673
|
if (elementCsn == null)
|
|
672
674
|
return false;
|
|
673
|
-
const renderForeignKey = (options.odataVersion === 'v4' && options.odataFormat === 'structured')
|
|
675
|
+
const renderForeignKey = (options.odataVersion === 'v4' && options.odataFormat === 'structured')
|
|
676
|
+
? !!options.odataForeignKeys : true;
|
|
674
677
|
const isNotIgnored = !elementCsn.target ? !elementCsn['@cds.api.ignore'] : true;
|
|
675
678
|
const isNavigable = elementCsn.target
|
|
676
679
|
? (elementCsn['@odata.navigable'] === undefined ||
|
|
677
|
-
elementCsn['@odata.navigable'] !== undefined &&
|
|
680
|
+
elementCsn['@odata.navigable'] !== undefined &&
|
|
681
|
+
(elementCsn['@odata.navigable'] === null || elementCsn['@odata.navigable'] === true)) : true;
|
|
678
682
|
// Foreign Keys can be ignored
|
|
679
683
|
if (elementCsn['@odata.foreignKey4'])
|
|
680
684
|
return isNotIgnored && renderForeignKey;
|
|
@@ -1124,7 +1128,7 @@ function moveAnnotationsAndDoc( sourceNode, targetNode, overwrite = false ) {
|
|
|
1124
1128
|
* override: Whether to ignore existing annotations.
|
|
1125
1129
|
* filter: Positive filter. If it returns true, annotations for the referenced artifact
|
|
1126
1130
|
* will be applied.
|
|
1127
|
-
* applyToElements:
|
|
1131
|
+
* applyToElements: Whether to apply annotations to elements or only to artifacts
|
|
1128
1132
|
*/
|
|
1129
1133
|
function applyAnnotationsFromExtensions( csn, config ) {
|
|
1130
1134
|
if (!csn.extensions)
|
|
@@ -1189,7 +1193,8 @@ function isAspect( node ) {
|
|
|
1189
1193
|
*/
|
|
1190
1194
|
function hasValidSkipOrExists( artifact ) {
|
|
1191
1195
|
return artifact.kind === 'entity' &&
|
|
1192
|
-
(hasAnnotationValue(artifact, '@cds.persistence.exists', true) ||
|
|
1196
|
+
(hasAnnotationValue(artifact, '@cds.persistence.exists', true) ||
|
|
1197
|
+
hasAnnotationValue(artifact, '@cds.persistence.skip', true));
|
|
1193
1198
|
}
|
|
1194
1199
|
|
|
1195
1200
|
/**
|
|
@@ -1368,7 +1373,8 @@ function functionList( functions, thisArg ) {
|
|
|
1368
1373
|
* @returns {boolean}
|
|
1369
1374
|
*/
|
|
1370
1375
|
function isDollarSelfOrProjectionOperand( arg ) {
|
|
1371
|
-
return arg.ref && arg.ref.length === 1 &&
|
|
1376
|
+
return arg.ref && arg.ref.length === 1 &&
|
|
1377
|
+
(arg.ref[0] === '$self' || arg.ref[0] === '$projection');
|
|
1372
1378
|
}
|
|
1373
1379
|
|
|
1374
1380
|
/**
|
|
@@ -1408,7 +1414,6 @@ function findAnnotationExpression( node, prop ) {
|
|
|
1408
1414
|
},
|
|
1409
1415
|
});
|
|
1410
1416
|
}
|
|
1411
|
-
|
|
1412
1417
|
return isExpr;
|
|
1413
1418
|
}
|
|
1414
1419
|
|
|
@@ -1430,6 +1435,7 @@ module.exports = {
|
|
|
1430
1435
|
applyTransformations,
|
|
1431
1436
|
applyTransformationsOnNonDictionary,
|
|
1432
1437
|
applyTransformationsOnDictionary,
|
|
1438
|
+
mergeTransformers,
|
|
1433
1439
|
setDependencies,
|
|
1434
1440
|
isPersistedOnDatabase,
|
|
1435
1441
|
isPersistedAsView,
|
package/lib/model/enrichCsn.js
CHANGED
|
@@ -207,7 +207,9 @@ function enrichCsn( csn, options = {} ) {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
function pathRef( parent, prop, path, inspectionPath = csnPath ) {
|
|
210
|
-
const inspection = handleError( err => ((err)
|
|
210
|
+
const inspection = handleError( err => ((err)
|
|
211
|
+
? { scope: err.toString() }
|
|
212
|
+
: inspectRef( inspectionPath )));
|
|
211
213
|
const {
|
|
212
214
|
links, art, scope, $env,
|
|
213
215
|
} = inspection;
|
|
@@ -38,7 +38,8 @@ const kindsRepresentedAsLinks = {
|
|
|
38
38
|
// represent table alias in from / join-args property as link:
|
|
39
39
|
$tableAlias: tableAliasAsLink,
|
|
40
40
|
// represent "navigation elements" in _combined as links:
|
|
41
|
-
$navElement: (art, parent) => art._parent && parent !== art._parent.elements &&
|
|
41
|
+
$navElement: (art, parent) => art._parent && parent !== art._parent.elements &&
|
|
42
|
+
art._parent.kind !== 'aspect',
|
|
42
43
|
// represent mixin in $tableAliases as link:
|
|
43
44
|
mixin: tableAliasAsLink,
|
|
44
45
|
// represent $projection as link, as it is just another search name for $self:
|
package/lib/model/sortViews.js
CHANGED
|
@@ -5,8 +5,11 @@ const { ModelError } = require('../base/error');
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {Object} Layers
|
|
8
|
-
* @property {Array[]} layers
|
|
9
|
-
*
|
|
8
|
+
* @property {Array[]} layers
|
|
9
|
+
* An array of arrays, each subarray encompassing one Layer - L0 being layers[0].
|
|
10
|
+
* @property {CSN.Artifact[]} leftover
|
|
11
|
+
* Any artifacts not sorted into a layer due to unmet dependencies.
|
|
12
|
+
* Points to there being some error.
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -22,7 +25,8 @@ const { ModelError } = require('../base/error');
|
|
|
22
25
|
*/
|
|
23
26
|
function sortTopologically( csn, _dependents, _dependencies ) {
|
|
24
27
|
const layers = [];
|
|
25
|
-
let { zero, nonZero } = _calculateDepth(Object.entries(csn.definitions),
|
|
28
|
+
let { zero, nonZero } = _calculateDepth(Object.entries(csn.definitions),
|
|
29
|
+
_dependents, _dependencies);
|
|
26
30
|
while (zero.length !== 0) {
|
|
27
31
|
const currentLayer = [];
|
|
28
32
|
zero.forEach(([ artifactName, artifact ]) => {
|
|
@@ -87,8 +91,10 @@ function _findWithXPointers( definitionsArray, x, _dependents, _dependencies ) {
|
|
|
87
91
|
* For ordering, only the FROM clause of views is checked - this requires A2J to
|
|
88
92
|
* be run beforehand to resolve association usages.
|
|
89
93
|
*
|
|
90
|
-
* @param {{sql: string, csn: CSN.Model}} arg
|
|
91
|
-
*
|
|
94
|
+
* @param {{sql: string, csn: CSN.Model}} arg
|
|
95
|
+
* sql: Map of <object name>: "CREATE STATEMENT", csn: Model
|
|
96
|
+
* @returns {{name: string, sql: string}[]}
|
|
97
|
+
* Sorted array of artifact name / "CREATE STATEMENTS" pairs
|
|
92
98
|
*/
|
|
93
99
|
module.exports = function sortViews({ sql, csn }) {
|
|
94
100
|
const { cleanup, _dependents, _dependencies } = setDependencies(csn);
|
|
@@ -98,7 +104,9 @@ module.exports = function sortViews({ sql, csn }) {
|
|
|
98
104
|
|
|
99
105
|
const result = [];
|
|
100
106
|
// keep the "artifact name" - needed for to.hdi sorting
|
|
101
|
-
layers.forEach(layer => layer.forEach(objName => result.push({
|
|
107
|
+
layers.forEach(layer => layer.forEach(objName => result.push({
|
|
108
|
+
name: objName, sql: sql[objName], dependents: csn.definitions[objName][_dependents],
|
|
109
|
+
})));
|
|
102
110
|
// attach sql artifacts which are not considered during the view sorting algorithm
|
|
103
111
|
// --> this is the case for "ALTER TABLE ADD CONSTRAINT" statements,
|
|
104
112
|
// because their identifiers are not part of the csn.definitions
|
|
@@ -13,6 +13,12 @@ const { forEachKey, forEach } = require('../utils/objectUtils');
|
|
|
13
13
|
// used to mark a view as changed so we know to drop-create it
|
|
14
14
|
const isChanged = Symbol('Marks a view as changed');
|
|
15
15
|
|
|
16
|
+
const relevantProperties = {
|
|
17
|
+
doc: true,
|
|
18
|
+
'@sql.prepend': true,
|
|
19
|
+
'@sql.append': true,
|
|
20
|
+
};
|
|
21
|
+
|
|
16
22
|
/**
|
|
17
23
|
* Compares two models, in HANA-transformed CSN format, to each other.
|
|
18
24
|
*
|
|
@@ -82,6 +88,33 @@ function getExtensionAndMigrations(beforeModel, options, {
|
|
|
82
88
|
}) {
|
|
83
89
|
return function compareArtifacts(artifact, name) {
|
|
84
90
|
let hasPrimaryKeyChange = false;
|
|
91
|
+
const otherArtifact = beforeModel.definitions[name];
|
|
92
|
+
const isPersisted = isPersistedAsTable(artifact);
|
|
93
|
+
const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
|
|
94
|
+
|
|
95
|
+
// to make it easier to know which views to drop-create
|
|
96
|
+
if (isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
|
|
97
|
+
// TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
|
|
98
|
+
artifact[isChanged] = JSON.stringify(artifact) !== JSON.stringify(otherArtifact);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Looking for added entities and added/deleted/changed elements.
|
|
102
|
+
// Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
|
|
103
|
+
|
|
104
|
+
if (!isPersisted) // Artifact not persisted in afterModel.
|
|
105
|
+
return;
|
|
106
|
+
|
|
107
|
+
if (!isPersistedOther) {
|
|
108
|
+
extensions[name] = artifact;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Artifact changed?
|
|
113
|
+
|
|
114
|
+
addElements();
|
|
115
|
+
changePropsOrRemoveOrChangeElements();
|
|
116
|
+
if (hasPrimaryKeyChange)
|
|
117
|
+
changedPrimaryKeys.push(name);
|
|
85
118
|
|
|
86
119
|
function addElements() {
|
|
87
120
|
const elements = {};
|
|
@@ -211,34 +244,6 @@ function getExtensionAndMigrations(beforeModel, options, {
|
|
|
211
244
|
if (migration.properties || migration.remove || migration.change || migration.removeConstraints)
|
|
212
245
|
migrations.push(migration);
|
|
213
246
|
}
|
|
214
|
-
|
|
215
|
-
const otherArtifact = beforeModel.definitions[name];
|
|
216
|
-
const isPersisted = isPersistedAsTable(artifact);
|
|
217
|
-
const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
|
|
218
|
-
|
|
219
|
-
// to make it easier to know which views to drop-create
|
|
220
|
-
if (isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
|
|
221
|
-
// TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
|
|
222
|
-
artifact[isChanged] = JSON.stringify(artifact) !== JSON.stringify(otherArtifact);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Looking for added entities and added/deleted/changed elements.
|
|
226
|
-
// Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
|
|
227
|
-
|
|
228
|
-
if (!isPersisted) // Artifact not persisted in afterModel.
|
|
229
|
-
return;
|
|
230
|
-
|
|
231
|
-
if (!isPersistedOther) {
|
|
232
|
-
extensions[name] = artifact;
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Artifact changed?
|
|
237
|
-
|
|
238
|
-
addElements();
|
|
239
|
-
changePropsOrRemoveOrChangeElements();
|
|
240
|
-
if (hasPrimaryKeyChange)
|
|
241
|
-
changedPrimaryKeys.push(name);
|
|
242
247
|
};
|
|
243
248
|
}
|
|
244
249
|
/**
|
|
@@ -374,12 +379,6 @@ function docCommentChanged(element, otherElement) {
|
|
|
374
379
|
return element.doc && !otherElement.doc || otherElement.doc && !element.doc || element.doc && element.doc !== otherElement.doc;
|
|
375
380
|
}
|
|
376
381
|
|
|
377
|
-
const relevantProperties = {
|
|
378
|
-
doc: true,
|
|
379
|
-
'@sql.prepend': true,
|
|
380
|
-
'@sql.append': true,
|
|
381
|
-
};
|
|
382
|
-
|
|
383
382
|
/**
|
|
384
383
|
* Returns whether any type parameters differ between two given elements. Ignores whether types themselves differ (`type` property) and ignores
|
|
385
384
|
* diff in doc comments.
|
package/lib/optionProcessor.js
CHANGED
|
@@ -155,7 +155,8 @@ optionProcessor
|
|
|
155
155
|
manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
|
|
156
156
|
add / modify referential constraints.
|
|
157
157
|
inspect [options] <files...> (internal) Inspect the given CDS files.
|
|
158
|
-
toEffectiveCsn [options] <files...> (internal) Get an effective CSN
|
|
158
|
+
toEffectiveCsn [options] <files...> (internal) Get an effective CSN; requires beta mode
|
|
159
|
+
forSeal [options] <files...> (internal) Get a SEAL CSN
|
|
159
160
|
|
|
160
161
|
Environment variables
|
|
161
162
|
NO_COLOR If set, compiler messages (/output) will not be colored.
|
|
@@ -546,6 +547,8 @@ optionProcessor.command('toEffectiveCsn')
|
|
|
546
547
|
.option('-h, --help')
|
|
547
548
|
.option('--resolve-simple-types <val>', { valid: ['true', 'false'] } )
|
|
548
549
|
.option('--resolve-projections <val>', { valid: ['true', 'false'] } )
|
|
550
|
+
.option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
|
|
551
|
+
.option('--keep-localized <val>', { valid: ['true', 'false'] } )
|
|
549
552
|
.positionalArgument('<files...>')
|
|
550
553
|
.help(`
|
|
551
554
|
Usage: cdsc toEffectiveCsn [options] <files...>
|
|
@@ -560,8 +563,30 @@ optionProcessor.command('toEffectiveCsn')
|
|
|
560
563
|
true: (default) resolve simple type references to their simple base type
|
|
561
564
|
false: do not resolve simple type references
|
|
562
565
|
--resolve-projections <val> Resolve projections:
|
|
563
|
-
true: (default)
|
|
566
|
+
true: (default) transform projections into ordinary views with SELECT
|
|
564
567
|
false: leave them as real projections
|
|
568
|
+
--remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
|
|
569
|
+
true: remap annotations
|
|
570
|
+
false:(default) leave them as is
|
|
571
|
+
--keep-localized <val> Keep '.localized' property in the CSN:
|
|
572
|
+
true: property is kept
|
|
573
|
+
false:(default) property is deleted
|
|
574
|
+
`);
|
|
575
|
+
|
|
576
|
+
optionProcessor.command('forSeal')
|
|
577
|
+
.option('-h, --help')
|
|
578
|
+
.option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
|
|
579
|
+
.positionalArgument('<files...>')
|
|
580
|
+
.help(`
|
|
581
|
+
Usage: cdsc forSeal [options] <files...>
|
|
582
|
+
|
|
583
|
+
(internal): Get the SEAL CSN model compiled from the provided CDS files.
|
|
584
|
+
|
|
585
|
+
Options
|
|
586
|
+
-h, --help Show this help text
|
|
587
|
+
--remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
|
|
588
|
+
true: (default) remap annotations
|
|
589
|
+
false: leave them as is
|
|
565
590
|
`);
|
|
566
591
|
|
|
567
592
|
module.exports = {
|