@sap/cds-compiler 6.6.0 → 6.7.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 +34 -1
- package/bin/cdsc.js +2 -0
- package/bin/cdsse.js +1 -1
- package/lib/base/message-registry.js +6 -7
- package/lib/base/model.js +0 -72
- package/lib/checks/elements.js +1 -1
- package/lib/checks/featureFlags.js +2 -2
- package/lib/compiler/assert-consistency.js +3 -4
- package/lib/compiler/base.js +8 -0
- package/lib/compiler/builtins.js +8 -9
- package/lib/compiler/checks.js +27 -6
- package/lib/compiler/cycle-detector.js +4 -4
- package/lib/compiler/define.js +65 -83
- package/lib/compiler/extend.js +357 -325
- package/lib/compiler/finalize-parse-cdl.js +3 -4
- package/lib/compiler/generate.js +205 -203
- package/lib/compiler/kick-start.js +34 -49
- package/lib/compiler/populate.js +95 -28
- package/lib/compiler/propagator.js +3 -5
- package/lib/compiler/resolve.js +17 -13
- package/lib/compiler/shared.js +47 -19
- package/lib/compiler/tweak-assocs.js +2 -4
- package/lib/compiler/utils.js +84 -31
- package/lib/gen/BaseParser.js +924 -1055
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +5 -2
- package/lib/json/from-csn.js +25 -16
- package/lib/main.d.ts +13 -0
- package/lib/model/revealInternalProperties.js +18 -0
- package/lib/parsers/AstBuildingParser.js +22 -5
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/utils/sql.js +2 -2
- package/lib/render/utils/standardDatabaseFunctions.js +2 -2
- package/lib/transform/db/constraints.js +3 -4
- package/lib/transform/db/killAnnotations.js +1 -1
- package/lib/transform/db/processSqlServices.js +10 -11
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/forOdata.js +7 -124
- package/lib/transform/odata/fioriTreeViews.js +173 -0
- package/lib/transform/odata/flattening.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +7 -4
- package/package.json +1 -1
- package/share/messages/message-explanations.json +0 -2
- package/share/messages/type-unexpected-foreign-keys.md +0 -52
- package/share/messages/type-unexpected-on-condition.md +0 -52
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f0320d5264a6107c245a82427b4f6338
|
package/lib/gen/CdlParser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// Parser generated by redepage v0.3.
|
|
1
|
+
// Parser generated by redepage v0.3.2
|
|
2
2
|
'use strict;'
|
|
3
3
|
const { XsnSource, XsnArtifact, XsnName } = require( '../compiler/xsn-model' )
|
|
4
|
+
const { isValidEnumValue } = require( '../compiler/base' )
|
|
4
5
|
const AstBuildingParser = require('../parsers/AstBuildingParser')
|
|
5
6
|
const keywords={
|
|
6
7
|
abstract:0,
|
|
@@ -2717,7 +2718,9 @@ case'@':this.annoAssignStd({art},336);continue
|
|
|
2717
2718
|
default:this.s=337;continue
|
|
2718
2719
|
}
|
|
2719
2720
|
case 337:switch(this.lk()){
|
|
2720
|
-
case'Id':case'@':case'key':this.elementDef({outer:$.outer,art},0)
|
|
2721
|
+
case'Id':case'@':case'key':if(this.elementDef({outer:$.outer,art},0)){ if (![ 'virtual', 'key', 'masked', '$syntax', 'elements', 'type', 'items' ]
|
|
2722
|
+
.some( p => art[p] ) && isValidEnumValue( art.value ))
|
|
2723
|
+
art.$syntax = 'or-enum'; }continue
|
|
2721
2724
|
case'extend':this.ckP(338,['Id']);continue
|
|
2722
2725
|
default:this.ei();continue
|
|
2723
2726
|
}
|
package/lib/json/from-csn.js
CHANGED
|
@@ -121,6 +121,7 @@ const { isAnnotationExpression } = require('../base/builtins');
|
|
|
121
121
|
const { CompilerAssertion } = require('../base/error');
|
|
122
122
|
const { Location } = require('../base/location');
|
|
123
123
|
const { XsnSource } = require('../compiler/xsn-model');
|
|
124
|
+
const { isValidEnumValue } = require( '../compiler/base' );
|
|
124
125
|
const { xsnAsTree, splitClauses } = require('../parsers/XprTree');
|
|
125
126
|
|
|
126
127
|
const $location = Symbol.for('cds.$location');
|
|
@@ -734,7 +735,7 @@ const schema = compileSchema( {
|
|
|
734
735
|
class: 'expression', // calculated elements
|
|
735
736
|
vZeroFor: 'val', // CSN v0.1.0 property for `val` in enum def
|
|
736
737
|
// type: annoValue,
|
|
737
|
-
inKind: [ 'element'
|
|
738
|
+
inKind: [ 'element' ],
|
|
738
739
|
optional: exprProperties.concat([ 'stored' ]),
|
|
739
740
|
},
|
|
740
741
|
stored: {
|
|
@@ -993,21 +994,15 @@ function definition( def, spec, xsn, csn, name ) {
|
|
|
993
994
|
}
|
|
994
995
|
pushLocation( def );
|
|
995
996
|
const savedInExtensions = inExtensions;
|
|
996
|
-
|
|
997
|
-
const
|
|
997
|
+
const r = { location: location(), kind: undefined };
|
|
998
|
+
const kind = calculateKind( def, spec, r ); // might set inExtensions
|
|
999
|
+
if (kind !== '$column')
|
|
1000
|
+
r.kind ??= kind;
|
|
998
1001
|
const xor = {};
|
|
999
1002
|
const { prop } = spec;
|
|
1000
1003
|
const kind0 = (spec.validKinds.length || spec.prop === 'extensions') && kind;
|
|
1001
1004
|
const csnProps = Object.keys( def );
|
|
1002
1005
|
|
|
1003
|
-
// For compatibility, extension property `elements` could actually be an `enum`:
|
|
1004
|
-
if (savedInExtensions === '' && prop === 'elements' && // in extend property `elements`
|
|
1005
|
-
!Object.keys( def ).some( couldNotBeEnumProperty )) {
|
|
1006
|
-
r.$syntax = 'enum'; // could be an enum
|
|
1007
|
-
if (def.val !== undefined || def['#'] !== undefined)
|
|
1008
|
-
kind = 'enum'; // for function expected(), i.e. allow property `val`/`#`
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
1006
|
if (csnProps.length) {
|
|
1012
1007
|
const valueName = (prop === 'keys' || prop === 'foreignKeys' ? 'targetElement' : 'value');
|
|
1013
1008
|
// the next is basically object() + the inValue handling
|
|
@@ -1057,17 +1052,19 @@ function namespace( ref, spec ) {
|
|
|
1057
1052
|
return ns ? { kind: 'namespace', name: ns } : null;
|
|
1058
1053
|
}
|
|
1059
1054
|
|
|
1060
|
-
function
|
|
1061
|
-
// returns true for `value` (which we allow with warning when extending an enum with `elements`)
|
|
1055
|
+
function couldBeEnumProperty( prop ) {
|
|
1062
1056
|
const inKind = schema[prop]?.inKind; // undefined for annotations, $location, …
|
|
1063
1057
|
// inKind for annotation assignments is function -> can be for enum
|
|
1064
|
-
return
|
|
1058
|
+
return inKind && // `value` handled extra
|
|
1059
|
+
(!Array.isArray( inKind ) || prop === 'value' || inKind.includes( 'enum' ));
|
|
1065
1060
|
}
|
|
1066
1061
|
|
|
1067
1062
|
function actions( def, spec, xsn, csn, name ) {
|
|
1068
1063
|
if (def.kind === 'extend' && (def.elements || def.enum)) {
|
|
1069
1064
|
// TODO: Handle this case in extend.js; already done for `returns`
|
|
1070
1065
|
// See message ext-expecting-returns
|
|
1066
|
+
// TODO: the location would also bad here
|
|
1067
|
+
// (should be at the property, not containing object - but we remove this anyway)
|
|
1071
1068
|
error( 'syntax-unexpected-property', location(true), {
|
|
1072
1069
|
'#': def.kind,
|
|
1073
1070
|
prop: def.enum ? 'enum' : 'elements',
|
|
@@ -1873,7 +1870,7 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
|
|
|
1873
1870
|
return { type: ignore };
|
|
1874
1871
|
}
|
|
1875
1872
|
|
|
1876
|
-
function calculateKind( def, spec ) {
|
|
1873
|
+
function calculateKind( def, spec, xsn ) {
|
|
1877
1874
|
if (inExtensions) {
|
|
1878
1875
|
inExtensions = spec.defaultKind;
|
|
1879
1876
|
return 'annotate';
|
|
@@ -1883,9 +1880,21 @@ function calculateKind( def, spec ) {
|
|
|
1883
1880
|
return (def.extend) ? 'extend' : 'annotate';
|
|
1884
1881
|
}
|
|
1885
1882
|
const kind = (def.kind === 'view') ? 'entity' : def.kind; // 'view' is CSN v0.1.0
|
|
1886
|
-
|
|
1883
|
+
const inExtend = inExtensions === '';
|
|
1884
|
+
if (inExtend && kind === 'extend') // extend in extend -> keep inExtensions
|
|
1887
1885
|
return 'extend';
|
|
1888
1886
|
inExtensions = null;
|
|
1887
|
+
// Compatibilitity: object rendered in `elements` of extend might be enum:
|
|
1888
|
+
if (inExtend && !def.kind && spec.defaultKind === 'element' &&
|
|
1889
|
+
Object.keys( def ).every( couldBeEnumProperty )) {
|
|
1890
|
+
if (isValidEnumValue( def.value ))
|
|
1891
|
+
xsn.$syntax = 'or-enum'; // could be an enum
|
|
1892
|
+
xsn.kind = 'element';
|
|
1893
|
+
// We also allow a `val` which is not embedded in a `value` also on an element
|
|
1894
|
+
// def inside extensions, because that is how compiler v3 has written it:
|
|
1895
|
+
if (def.val && !def.value)
|
|
1896
|
+
return 'enum'; // use schema of enum (for `val`)
|
|
1897
|
+
}
|
|
1889
1898
|
if (!spec.validKinds.includes( kind ))
|
|
1890
1899
|
return spec.defaultKind;
|
|
1891
1900
|
return (def.abstract || def.$syntax === 'aspect')
|
package/lib/main.d.ts
CHANGED
|
@@ -313,6 +313,19 @@ declare namespace compiler {
|
|
|
313
313
|
* @private
|
|
314
314
|
*/
|
|
315
315
|
edm4OpenAPI?: boolean
|
|
316
|
+
/**
|
|
317
|
+
* Enable the draft-generated artifacts to handle state messages.
|
|
318
|
+
*
|
|
319
|
+
* @since v6.1.0
|
|
320
|
+
*/
|
|
321
|
+
draftMessages?: boolean
|
|
322
|
+
/**
|
|
323
|
+
* Add `CreatedByUserDescription`, `LastChangedByUserDescription` and
|
|
324
|
+
* `InProcessByUserDescription` fields to the `DraftAdministrativeData` entity.
|
|
325
|
+
*
|
|
326
|
+
* @since v6.6.0
|
|
327
|
+
*/
|
|
328
|
+
draftUserDescription?: boolean
|
|
316
329
|
}
|
|
317
330
|
|
|
318
331
|
/**
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// This function should return a meaningful result in all circumstances:
|
|
9
9
|
// * with --parse-only, with both CDL and CSN input,
|
|
10
10
|
// * for the core compiler output and all transformations working on the XSN.
|
|
11
|
+
//
|
|
12
|
+
// TODO: make sure that all extend statements are listed)
|
|
11
13
|
|
|
12
14
|
'use strict';
|
|
13
15
|
|
|
@@ -95,6 +97,7 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
95
97
|
_layerExtends: layerExtends,
|
|
96
98
|
_origin: origin,
|
|
97
99
|
$compositionTargets: d => d, // dictionary( boolean )
|
|
100
|
+
_extensions: revealExtensions,
|
|
98
101
|
_extend: reveal,
|
|
99
102
|
_annotate: reveal,
|
|
100
103
|
_annotateS: artifactIdentifier,
|
|
@@ -270,6 +273,21 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
270
273
|
return artifactDictionary( node, parent );
|
|
271
274
|
}
|
|
272
275
|
|
|
276
|
+
function revealExtensions( node ) {
|
|
277
|
+
if (Array.isArray( node ))
|
|
278
|
+
return array( node, revealSingleExtension );
|
|
279
|
+
const r = {};
|
|
280
|
+
for (const prop in node)
|
|
281
|
+
r[prop] = revealExtensions( node[prop] );
|
|
282
|
+
return r;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function revealSingleExtension( node, parent ) {
|
|
286
|
+
return (node.kind && node.__unique_id__ == null && node.$effectiveSeqNo == null && !node.builtin)
|
|
287
|
+
? reveal( node, parent )
|
|
288
|
+
: artifactIdentifier( node, parent );
|
|
289
|
+
}
|
|
290
|
+
|
|
273
291
|
function reveal( node, parent, name ) {
|
|
274
292
|
if (node == null || typeof node !== 'object' )
|
|
275
293
|
return node;
|
|
@@ -39,6 +39,17 @@ const valueTokens = {
|
|
|
39
39
|
true: true,
|
|
40
40
|
};
|
|
41
41
|
const valueTokensLength = Object.values( valueTokens ).length;
|
|
42
|
+
// likewise, if expectedArray contains all the following tokens, replace them by 'Literal'
|
|
43
|
+
const literalTokens = {
|
|
44
|
+
Number: true,
|
|
45
|
+
QuotedLiteral: true,
|
|
46
|
+
String: true,
|
|
47
|
+
'#': true,
|
|
48
|
+
true: true,
|
|
49
|
+
false: true,
|
|
50
|
+
null: true,
|
|
51
|
+
};
|
|
52
|
+
const literalTokensLength = Object.values( literalTokens ).length;
|
|
42
53
|
|
|
43
54
|
const extensionDicts = {
|
|
44
55
|
elements: true, enum: true, params: true, returns: true,
|
|
@@ -107,9 +118,15 @@ class AstBuildingParser extends BaseParser {
|
|
|
107
118
|
let array = this.expectingArray_();
|
|
108
119
|
this.s = savedState;
|
|
109
120
|
// Avoid clutter in messages: replace all value tokens by ‹Value› if all are there:
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
array = [ 'Value', ...
|
|
121
|
+
const withoutValueTokens = raw ? array : array.filter( tok => valueTokens[tok] !== true );
|
|
122
|
+
if (withoutValueTokens.length + valueTokensLength === array.length) {
|
|
123
|
+
array = [ 'Value', ...withoutValueTokens ];
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const withoutLiteralTokens = raw ? array : array.filter( tok => literalTokens[tok] !== true );
|
|
127
|
+
if (withoutLiteralTokens.length + literalTokensLength === array.length)
|
|
128
|
+
array = [ 'Literal', ...withoutLiteralTokens ];
|
|
129
|
+
}
|
|
113
130
|
return array.map( tok => this.antlrName( tok ) )
|
|
114
131
|
.sort( (a, b) => (tokenPrecedence(a) < tokenPrecedence(b) ? -1 : 1) );
|
|
115
132
|
}
|
|
@@ -153,7 +170,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
153
170
|
this.conditionStackLength == null) // after error recovery
|
|
154
171
|
return false;
|
|
155
172
|
if (this.constructor.tracingParser)
|
|
156
|
-
this._traceSubPush( 'queryOnLeft' );
|
|
173
|
+
this._traceSubPush?.( 'queryOnLeft' );
|
|
157
174
|
return this.queryOnLeft( 'table', mode );
|
|
158
175
|
}
|
|
159
176
|
|
|
@@ -175,7 +192,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
175
192
|
if (this.inSameRule_( this.s, this.stack.at( -1 ).followState ))
|
|
176
193
|
return false;
|
|
177
194
|
this.dynamic_.parenthesesCtx = [ null ];
|
|
178
|
-
this._tracePush( 'Parentheses()' );
|
|
195
|
+
this._tracePush?.( 'Parentheses()' );
|
|
179
196
|
}
|
|
180
197
|
const { parenthesesCtx } = this.dynamic_;
|
|
181
198
|
const noQuery = parenthesesCtx?.[0];
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -4,7 +4,7 @@ const {
|
|
|
4
4
|
getLastPartOf, getLastPartOfRef,
|
|
5
5
|
hasValidSkipOrExists, getNormalizedQuery,
|
|
6
6
|
getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement,
|
|
7
|
-
pathName, hasPersistenceSkipAnnotation, implicitAs,
|
|
7
|
+
pathName, hasPersistenceSkipAnnotation, implicitAs, forEachDefinition,
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
9
|
const { isBuiltinType, isMagicVariable } = require('../base/builtins');
|
|
10
10
|
const keywords = require('../base/keywords');
|
|
@@ -18,7 +18,7 @@ const {
|
|
|
18
18
|
renderReferentialConstraint,
|
|
19
19
|
} = require('./utils/sql');
|
|
20
20
|
const DuplicateChecker = require('./DuplicateChecker');
|
|
21
|
-
const {
|
|
21
|
+
const { isDeprecatedEnabled } = require('../base/model');
|
|
22
22
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
23
23
|
const { timetrace } = require('../utils/timetrace');
|
|
24
24
|
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const { getResultingName } = require('../../model/csnUtils');
|
|
6
6
|
const { smartId, delimitedId } = require('../../sql-identifier');
|
|
7
7
|
const { ModelError } = require('../../base/error');
|
|
8
|
-
const {
|
|
8
|
+
const { setProp } = require('../../base/model');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Render a given referential constraint as part of a SQL CREATE TABLE statement, or as .hdbconstraint artefact.
|
|
@@ -161,7 +161,7 @@ const allowedHdbProjectionViewProperties = {
|
|
|
161
161
|
* @returns {boolean} `true` if the artifact is a projection view, otherwise `false`.
|
|
162
162
|
*/
|
|
163
163
|
function isProjectionView( csn, artifact, options ) {
|
|
164
|
-
if (!
|
|
164
|
+
if (!artifact.projection || options.sqlDialect !== 'hana' || artifact.params || !artifact.$dataProductService)
|
|
165
165
|
return false;
|
|
166
166
|
|
|
167
167
|
if (artifact.$isProjectionView !== undefined)
|
|
@@ -666,7 +666,7 @@ const hanaFunctions = {
|
|
|
666
666
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
667
667
|
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
668
668
|
// make sure to cast NUMERIC to BIGINT (corresponds to cds.Int64)
|
|
669
|
-
return `(EXTRACT(EPOCH FROM (${ y }) - (${ x })) * 10000000)::BIGINT`;
|
|
669
|
+
return `(EXTRACT(EPOCH FROM (${ y })::TIMESTAMP - (${ x })::TIMESTAMP) * 10000000)::BIGINT`;
|
|
670
670
|
},
|
|
671
671
|
seconds_between(signature) {
|
|
672
672
|
const { args } = signature;
|
|
@@ -674,7 +674,7 @@ const hanaFunctions = {
|
|
|
674
674
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
675
675
|
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
676
676
|
|
|
677
|
-
return `EXTRACT(EPOCH FROM (${ y }) - (${ x }))::BIGINT`;
|
|
677
|
+
return `EXTRACT(EPOCH FROM (${ y })::TIMESTAMP - (${ x })::TIMESTAMP)::BIGINT`;
|
|
678
678
|
},
|
|
679
679
|
days_between(signature) {
|
|
680
680
|
const { args } = signature;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachDefinition } = require('../../base/model');
|
|
4
3
|
const { applyTransformations, getResultingName, hasPersistenceSkipAnnotation } = require('../../model/csnUtils');
|
|
5
4
|
const { forEach, forEachKey } = require('../../utils/objectUtils');
|
|
6
5
|
const { CompilerAssertion } = require('../../base/error');
|
|
@@ -92,7 +91,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
92
91
|
associations.forEach(association => association.fn());
|
|
93
92
|
|
|
94
93
|
// Step III: Create the final referential constraints from all dependent key <-> parent key pairs stemming from the same $sourceAssociation
|
|
95
|
-
|
|
94
|
+
forEach(csn.definitions, collectAndAttachReferentialConstraints);
|
|
96
95
|
|
|
97
96
|
/**
|
|
98
97
|
* Retrieve the <up_> link of an `cds.Composition` used in an on-condition like `$self = <comp>.<up_>`
|
|
@@ -501,10 +500,10 @@ function createReferentialConstraints( csn, options ) {
|
|
|
501
500
|
* - Find all other elements in artifact with the same sourceAssociation
|
|
502
501
|
* - Create constraints with the information supplied by $parentKey, $parentTable and $onDelete
|
|
503
502
|
*
|
|
504
|
-
* @param {CSN.Artifact} artifact
|
|
505
503
|
* @param {string} artifactName
|
|
504
|
+
* @param {CSN.Artifact} artifact
|
|
506
505
|
*/
|
|
507
|
-
function collectAndAttachReferentialConstraints(
|
|
506
|
+
function collectAndAttachReferentialConstraints( artifactName, artifact ) {
|
|
508
507
|
const referentialConstraints = Object.create(null);
|
|
509
508
|
|
|
510
509
|
// tenant foreign keys may have multiple parent keys
|
|
@@ -26,7 +26,7 @@ const requiredAnnos = {
|
|
|
26
26
|
'@Core.Computed': true,
|
|
27
27
|
[sqlServiceAnnotation]: true,
|
|
28
28
|
'@cds.external': true, // for external ABAP SQL services and data products for now
|
|
29
|
-
'@
|
|
29
|
+
'@data.product': true, // for data product production
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -18,13 +18,13 @@ function processSqlServices(csn, options) {
|
|
|
18
18
|
setProp(csn, '$dummyServiceEntities', Object.create(null));
|
|
19
19
|
setProp(csn, '$dataProductEntities', Object.create(null));
|
|
20
20
|
return function findAndMarkSqlServiceArtifacts(artifact, artifactName) {
|
|
21
|
-
const { sqlServiceName, dummyServiceName,
|
|
21
|
+
const { sqlServiceName, dummyServiceName, dataProductProductionServiceName } = isEntityInSqlService(artifact, artifactName, csn, options);
|
|
22
22
|
if (sqlServiceName?.length > 0)
|
|
23
23
|
setProp(artifact, '$sqlService', sqlServiceName);
|
|
24
24
|
if (dummyServiceName?.length > 0)
|
|
25
25
|
setProp(artifact, '$dummyService', dummyServiceName);
|
|
26
|
-
if (
|
|
27
|
-
setProp(artifact, '$dataProductService',
|
|
26
|
+
if (dataProductProductionServiceName?.length > 0)
|
|
27
|
+
setProp(artifact, '$dataProductService', dataProductProductionServiceName);
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -58,7 +58,7 @@ function isDummyService(artifact, options) {
|
|
|
58
58
|
* @returns {object} An object containing the names of the SQL service and external ABAP SQL service, if found.
|
|
59
59
|
*/
|
|
60
60
|
function isEntityInSqlService(artifact, artifactName, csn, options) {
|
|
61
|
-
const result = { sqlServiceName: undefined, dummyServiceName: undefined,
|
|
61
|
+
const result = { sqlServiceName: undefined, dummyServiceName: undefined, dataProductProductionServiceName: undefined };
|
|
62
62
|
if (artifact.kind !== 'entity' || !artifactName.includes('.') || hasPersistenceSkipAnnotation(artifact))
|
|
63
63
|
return result;
|
|
64
64
|
|
|
@@ -75,8 +75,8 @@ function isEntityInSqlService(artifact, artifactName, csn, options) {
|
|
|
75
75
|
if (isDummyService(definition, options))
|
|
76
76
|
result.dummyServiceName = possibleServiceName;
|
|
77
77
|
|
|
78
|
-
if (
|
|
79
|
-
result.
|
|
78
|
+
if (isDataProductProductionService(definition))
|
|
79
|
+
result.dataProductProductionServiceName = possibleServiceName;
|
|
80
80
|
|
|
81
81
|
// We don't allow nested services/contexts - if we find one, we don't need to keep searching
|
|
82
82
|
if (definition.kind === 'service' || definition.kind === 'context')
|
|
@@ -115,21 +115,20 @@ function createServiceDummy(artifact, artifactName, csn, { error }) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
* Determines if the given artifact is a data product service.
|
|
118
|
+
* Determines if the given artifact is a data product production service.
|
|
119
119
|
*
|
|
120
120
|
* @param {object} artifact - The artifact to evaluate.
|
|
121
|
-
* @param {object} options - The options object containing feature flags.
|
|
122
121
|
* @returns {boolean} - Returns `true` if the artifact is a data product service, otherwise `false`.
|
|
123
122
|
*/
|
|
124
|
-
function
|
|
125
|
-
return
|
|
123
|
+
function isDataProductProductionService(artifact) {
|
|
124
|
+
return artifact.kind === 'service' && artifact['@data.product'] && !artifact['@cds.external'];
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
module.exports = {
|
|
129
128
|
processSqlServices,
|
|
130
129
|
isSqlService,
|
|
131
130
|
isDummyService,
|
|
132
|
-
|
|
131
|
+
isDataProductProductionService,
|
|
133
132
|
sqlServiceAnnotation,
|
|
134
133
|
createServiceDummy,
|
|
135
134
|
};
|
|
@@ -72,7 +72,7 @@ function turnAssociationsIntoUnmanaged( csn, options, transformerUtils, messageF
|
|
|
72
72
|
* @param {object} messageFunctions
|
|
73
73
|
*/
|
|
74
74
|
function transformBacklinks( csn, options, { csnUtils }, messageFunctions ) {
|
|
75
|
-
forEachDefinition(csn, backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, '_', true));
|
|
75
|
+
forEachDefinition(csn, backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, '_', true), { skip: [ 'aspect' ] });
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
module.exports = {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { isBetaEnabled } = require('../base/model');
|
|
4
|
-
const { isBuiltinType } = require('../base/builtins');
|
|
5
4
|
const transformUtils = require('./transformUtils');
|
|
6
5
|
const {
|
|
7
6
|
forEachDefinition,
|
|
@@ -16,12 +15,14 @@ const {
|
|
|
16
15
|
} = require('../model/csnUtils');
|
|
17
16
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
18
17
|
const validate = require('../checks/validator');
|
|
19
|
-
const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./odata/utils');
|
|
20
|
-
const expandToFinalBaseType = require('./odata/toFinalBaseType');
|
|
21
18
|
const { timetrace } = require('../utils/timetrace');
|
|
22
19
|
const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
|
|
20
|
+
|
|
21
|
+
const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./odata/utils');
|
|
22
|
+
const expandToFinalBaseType = require('./odata/toFinalBaseType');
|
|
23
23
|
const flattening = require('./odata/flattening');
|
|
24
24
|
const createForeignKeyElements = require('./odata/createForeignKeys');
|
|
25
|
+
const generateFioriTreeViewAnnotationsAndFields = require('./odata/fioriTreeViews');
|
|
25
26
|
const associations = require('./db/associations');
|
|
26
27
|
const expansion = require('./db/expansion');
|
|
27
28
|
const generateDrafts = require('./draft/odata');
|
|
@@ -92,8 +93,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
92
93
|
|
|
93
94
|
const transformers = transformUtils.getTransformers(csn, options, messageFunctions, '_');
|
|
94
95
|
const {
|
|
95
|
-
addDefaultTypeFacets,
|
|
96
|
-
checkMultipleAssignments, createScalarElement,
|
|
96
|
+
addDefaultTypeFacets, checkMultipleAssignments,
|
|
97
97
|
recurseElements, setAnnotation,
|
|
98
98
|
renameAnnotation, expandStructsInExpression,
|
|
99
99
|
csnUtils,
|
|
@@ -296,7 +296,8 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
296
296
|
renameShorthandAnnotations(def);
|
|
297
297
|
|
|
298
298
|
// Generate annotations and fields needed for the Fiori Tree Views out of the @hierarchy annotation
|
|
299
|
-
|
|
299
|
+
if (def['@hierarchy'])
|
|
300
|
+
generateFioriTreeViewAnnotationsAndFields(def, defName, messageFunctions, csnUtils, transformers);
|
|
300
301
|
|
|
301
302
|
// Annotate artifacts with their DB names if requested.
|
|
302
303
|
// Skip artifacts that have no DB equivalent anyway
|
|
@@ -365,124 +366,6 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
365
366
|
//--------------------------------------------------------------------
|
|
366
367
|
// HELPER SECTION STARTS HERE
|
|
367
368
|
|
|
368
|
-
// =====================================================================
|
|
369
|
-
// Generate annotations and fields needed for the Fiori Tree Views out of the @hierarchy annotation
|
|
370
|
-
function generateFioriTreeViewAnnotationsAndFields(def, defName) {
|
|
371
|
-
const re = new RegExp('^@hierarchy#.+$');
|
|
372
|
-
if (Object.keys(def).some(key => re.test(key)))
|
|
373
|
-
error(null, [ 'definitions', defName ], {}, 'Assigning qualifier with the @hierarchy annotation is not supported');
|
|
374
|
-
|
|
375
|
-
if (!def['@hierarchy'])
|
|
376
|
-
return;
|
|
377
|
-
if (!(def.projection || def.query))
|
|
378
|
-
return;
|
|
379
|
-
|
|
380
|
-
const defKeys = [];
|
|
381
|
-
const assocs = [];
|
|
382
|
-
Object.entries(def.elements).forEach(([ name, elem ]) => {
|
|
383
|
-
if (elem.key)
|
|
384
|
-
defKeys.push([ name, elem ]);
|
|
385
|
-
if (isAssociation(elem) && elem.target === defName)
|
|
386
|
-
assocs.push([ name, elem ]);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// the definition must have exactly one scalar key field
|
|
390
|
-
if (defKeys.length !== 1 && !isBuiltinType(defKeys[0].type))
|
|
391
|
-
return;
|
|
392
|
-
const defKeyName = defKeys[0][0];
|
|
393
|
-
|
|
394
|
-
// if the @hierarchy annotation has a true value, we need to make sure the definition has exacly one
|
|
395
|
-
// association pointing to self and has the single key field as a foreign key
|
|
396
|
-
if (typeof def['@hierarchy'] === 'boolean' && def['@hierarchy'] === true) {
|
|
397
|
-
if (assocs.length !== 1)
|
|
398
|
-
return;
|
|
399
|
-
const assocName = assocs[0][0];
|
|
400
|
-
const assoc = assocs[0][1];
|
|
401
|
-
if (!isValidHierarchyAssociation(assoc, defKeyName))
|
|
402
|
-
return;
|
|
403
|
-
addHierarchyAnnotations(def, defName, defKeyName, assocName);
|
|
404
|
-
addHierarchyFields(def, defName);
|
|
405
|
-
}
|
|
406
|
-
else if (typeof def['@hierarchy'] === 'object' && def['@hierarchy']['=']) {
|
|
407
|
-
const assocName = def['@hierarchy']['='];
|
|
408
|
-
if (!isValidHierarchyAssociation(assocs.find(a => a[0] === assocName)[1], defKeyName))
|
|
409
|
-
return;
|
|
410
|
-
addHierarchyAnnotations(def, defName, defKeyName, assocName);
|
|
411
|
-
addHierarchyFields(def, defName);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// returns true if assoc has one key and that one key is the corresponding 'keyName'
|
|
416
|
-
function isValidHierarchyAssociation(assoc, keyName) {
|
|
417
|
-
return assoc.keys.length === 1 && assoc.keys[0].ref.join() === keyName;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function addHierarchyAnnotations(def, defName, defKeyName, assocName) {
|
|
421
|
-
const qualifier = `${ defName.split('.').pop() }Hierarchy`;
|
|
422
|
-
|
|
423
|
-
[ `@Aggregation.RecursiveHierarchy#${ qualifier }`,
|
|
424
|
-
`@Aggregation.RecursiveHierarchy#${ qualifier }.NodeProperty`,
|
|
425
|
-
`@Aggregation.RecursiveHierarchy#${ qualifier }.ParentNavigationProperty`,
|
|
426
|
-
`@Hierarchy.RecursiveHierarchy#${ qualifier }`,
|
|
427
|
-
`@Hierarchy.RecursiveHierarchy#${ qualifier }.LimitedDescendantCount`,
|
|
428
|
-
`@Hierarchy.RecursiveHierarchy#${ qualifier }.DistanceFromRoot`,
|
|
429
|
-
`@Hierarchy.RecursiveHierarchy#${ qualifier }.DrillState`,
|
|
430
|
-
`@Hierarchy.RecursiveHierarchy#${ qualifier }.LimitedRank` ].forEach(anno => checkAndReportErrorWhenAnnotationExists(def, defName, anno));
|
|
431
|
-
|
|
432
|
-
setAnnotation(def, `@Aggregation.RecursiveHierarchy#${ qualifier }.NodeProperty`, { '=': defKeyName });
|
|
433
|
-
setAnnotation(def, `@Aggregation.RecursiveHierarchy#${ qualifier }.ParentNavigationProperty`, { '=': assocName });
|
|
434
|
-
setAnnotation(def, `@Hierarchy.RecursiveHierarchy#${ qualifier }.LimitedDescendantCount`, { '=': 'LimitedDescendantCount' });
|
|
435
|
-
setAnnotation(def, `@Hierarchy.RecursiveHierarchy#${ qualifier }.DistanceFromRoot`, { '=': 'DistanceFromRoot' });
|
|
436
|
-
setAnnotation(def, `@Hierarchy.RecursiveHierarchy#${ qualifier }.DrillState`, { '=': 'DrillState' });
|
|
437
|
-
setAnnotation(def, `@Hierarchy.RecursiveHierarchy#${ qualifier }.LimitedRank`, { '=': 'LimitedRank' });
|
|
438
|
-
|
|
439
|
-
// if @Capabilities.FilterRestrictions.NonFilterableProperties or @Capabilities.SortRestrictions.NonSortableProperties
|
|
440
|
-
// are already defined, we append to the existing value the created elements
|
|
441
|
-
if (def['@Capabilities.FilterRestrictions.NonFilterableProperties'] && Array.isArray(def['@Capabilities.FilterRestrictions.NonFilterableProperties'])) {
|
|
442
|
-
def['@Capabilities.FilterRestrictions.NonFilterableProperties'].push('LimitedDescendantCount', 'DistanceFromRoot', 'DrillState', 'LimitedRank');
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
setAnnotation(def, '@Capabilities.FilterRestrictions.NonFilterableProperties',
|
|
446
|
-
[ 'LimitedDescendantCount', 'DistanceFromRoot', 'DrillState', 'LimitedRank' ]);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (def['@Capabilities.SortRestrictions.NonSortableProperties'] && Array.isArray(def['@Capabilities.SortRestrictions.NonSortableProperties'])) {
|
|
450
|
-
def['@Capabilities.FilterRestrictions.NonFilterableProperties'].push('LimitedDescendantCount', 'DistanceFromRoot', 'DrillState', 'LimitedRank');
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
setAnnotation(def, '@Capabilities.SortRestrictions.NonSortableProperties',
|
|
454
|
-
[ 'LimitedDescendantCount', 'DistanceFromRoot', 'DrillState', 'LimitedRank' ]);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function addHierarchyFields(def, defName) {
|
|
459
|
-
const limitedDescendantCount = createScalarElement('LimitedDescendantCount', 'cds.Integer');
|
|
460
|
-
limitedDescendantCount.LimitedDescendantCount['@Core.Computed'] = true;
|
|
461
|
-
limitedDescendantCount.LimitedDescendantCount.$calc = { val: null };
|
|
462
|
-
addElement(limitedDescendantCount, def, defName);
|
|
463
|
-
|
|
464
|
-
const distanceFromRoot = createScalarElement('DistanceFromRoot', 'cds.Integer');
|
|
465
|
-
distanceFromRoot.DistanceFromRoot['@Core.Computed'] = true;
|
|
466
|
-
distanceFromRoot.DistanceFromRoot.$calc = { val: null };
|
|
467
|
-
addElement(distanceFromRoot, def, defName);
|
|
468
|
-
|
|
469
|
-
const drillState = createScalarElement('DrillState', 'cds.String');
|
|
470
|
-
drillState.DrillState['@Core.Computed'] = true;
|
|
471
|
-
drillState.DrillState.$calc = { val: null };
|
|
472
|
-
addElement(drillState, def, defName);
|
|
473
|
-
|
|
474
|
-
const limitedRank = createScalarElement('LimitedRank', 'cds.Integer');
|
|
475
|
-
limitedRank.LimitedRank['@Core.Computed'] = true;
|
|
476
|
-
limitedRank.LimitedRank.$calc = { val: null };
|
|
477
|
-
addElement(limitedRank, def, defName);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
function checkAndReportErrorWhenAnnotationExists(def, defName, anno) {
|
|
481
|
-
if (def[anno])
|
|
482
|
-
error(null, [ 'definitions', defName ], { anno, name: defName }, 'Annotation $(ANNO) is already defined on the hierarchy entity $(NAME)');
|
|
483
|
-
}
|
|
484
|
-
// =====================================================================
|
|
485
|
-
|
|
486
369
|
// Transform @readonly/@mandatory/@disabled into @Common.FieldControl annotation
|
|
487
370
|
// with a when/then/else expression consisting of the input from the annotations.
|
|
488
371
|
function processDynamicFieldControlAnnotations(node) {
|