@sap/cds-compiler 4.7.6 → 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 +63 -3
- 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 +28 -1
- package/bin/cdshi.js +13 -3
- package/doc/CHANGELOG_BETA.md +24 -1
- package/lib/api/main.js +119 -46
- package/lib/api/options.js +51 -0
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +116 -0
- package/lib/base/keywords.js +5 -1
- package/lib/base/location.js +91 -14
- package/lib/base/message-registry.js +76 -46
- package/lib/base/messages.js +121 -35
- package/lib/base/model.js +4 -7
- package/lib/checks/actionsFunctions.js +3 -3
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/elements.js +2 -1
- package/lib/checks/enricher.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +5 -3
- package/lib/checks/utils.js +1 -1
- package/lib/checks/validator.js +8 -56
- package/lib/compiler/assert-consistency.js +11 -7
- package/lib/compiler/builtins.js +0 -74
- package/lib/compiler/checks.js +105 -29
- package/lib/compiler/define.js +37 -25
- package/lib/compiler/extend.js +35 -12
- package/lib/compiler/index.js +9 -10
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/populate.js +13 -5
- package/lib/compiler/propagator.js +24 -18
- package/lib/compiler/resolve.js +47 -45
- package/lib/compiler/shared.js +61 -21
- package/lib/compiler/tweak-assocs.js +15 -90
- package/lib/compiler/utils.js +3 -3
- package/lib/compiler/xpr-rewrite.js +689 -0
- package/lib/compiler/{classes.js → xsn-model.js} +0 -16
- package/lib/edm/annotations/edmJson.js +7 -5
- package/lib/edm/annotations/genericTranslation.js +149 -71
- package/lib/edm/csn2edm.js +25 -9
- package/lib/edm/edm.js +7 -7
- package/lib/edm/edmInboundChecks.js +57 -5
- package/lib/edm/edmPreprocessor.js +54 -25
- package/lib/edm/edmUtils.js +3 -16
- package/lib/gen/Dictionary.json +138 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2085 -1989
- package/lib/json/csnVersion.js +7 -4
- package/lib/json/from-csn.js +21 -11
- package/lib/json/to-csn.js +8 -4
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +23 -16
- package/lib/language/multiLineStringParser.js +2 -2
- package/lib/language/textUtils.js +1 -1
- package/lib/main.d.ts +90 -14
- package/lib/main.js +9 -1
- package/lib/model/cloneCsn.js +21 -9
- package/lib/model/csnRefs.js +153 -42
- package/lib/model/csnUtils.js +14 -11
- package/lib/model/enrichCsn.js +4 -2
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +135 -122
- package/lib/optionProcessor.js +49 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +6 -3
- package/lib/render/toHdbcds.js +4 -48
- package/lib/render/toSql.js +6 -3
- package/lib/transform/addTenantFields.js +58 -35
- package/lib/transform/db/applyTransformations.js +34 -1
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +11 -3
- package/lib/transform/db/flattening.js +71 -46
- 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/db/views.js +1 -4
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +11 -10
- package/lib/transform/effective/misc.js +45 -14
- package/lib/transform/effective/types.js +4 -3
- package/lib/transform/forOdata.js +29 -12
- package/lib/transform/forRelationalDB.js +104 -113
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +228 -107
- package/lib/transform/odata/toFinalBaseType.js +10 -26
- package/lib/transform/odata/typesExposure.js +41 -25
- package/lib/transform/parseExpr.js +4 -7
- package/lib/transform/transformUtils.js +50 -43
- package/lib/transform/translateAssocsToJoins.js +48 -48
- package/lib/transform/universalCsn/coreComputed.js +2 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
- package/package.json +2 -2
- package/share/messages/README.md +4 -0
- package/share/messages/anno-duplicate-unrelated-layer.md +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/def-duplicate-autoexposed.md +1 -1
- package/share/messages/extend-repeated-intralayer.md +3 -16
- package/share/messages/extend-unrelated-layer.md +1 -1
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/redirected-to-ambiguous.md +1 -1
- package/share/messages/redirected-to-complex.md +1 -1
- package/share/messages/redirected-to-unrelated.md +1 -1
- package/share/messages/rewrite-not-supported.md +1 -1
- package/share/messages/syntax-expecting-unsigned-int.md +2 -2
- package/share/messages/type-missing-enum-value.md +59 -0
- package/share/messages/wildcard-excluding-one.md +1 -1
- 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/render/toHdbcds.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
getLastPartOf, getLastPartOfRef,
|
|
5
|
-
hasValidSkipOrExists,
|
|
5
|
+
hasValidSkipOrExists, generatedByCompilerVersion, getNormalizedQuery,
|
|
6
6
|
getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement, hasAnnotationValue,
|
|
7
|
-
pathName,
|
|
7
|
+
pathName,
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
|
+
const { isBuiltinType, isMagicVariable } = require('../base/builtins');
|
|
9
10
|
const keywords = require('../base/keywords');
|
|
10
11
|
const {
|
|
11
12
|
renderFunc, createExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts,
|
|
@@ -315,49 +316,6 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
|
|
|
315
316
|
return false;
|
|
316
317
|
}
|
|
317
318
|
|
|
318
|
-
/* FIXME: Not yet required
|
|
319
|
-
// Returns the artifact or element that constitutes the final type of
|
|
320
|
-
// construct 'node', i.e. the object in which we would find type properties for
|
|
321
|
-
// 'node'. Note that this may well be 'node' itself.
|
|
322
|
-
function getFinalTypeOf(node) {
|
|
323
|
-
if (node && node.type) {
|
|
324
|
-
if (isBuiltinType(node.type)) {
|
|
325
|
-
return node;
|
|
326
|
-
}
|
|
327
|
-
return getFinalTypeOf(node.type);
|
|
328
|
-
}
|
|
329
|
-
return node;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Resolve path array 'ref' against artifact 'base' (or against 'csn.definitions'
|
|
333
|
-
// if no 'base' given).
|
|
334
|
-
// Return the resulting artifact or element (or 'undefined' if not found).
|
|
335
|
-
function resolveRef(ref, base) {
|
|
336
|
-
let result = base;
|
|
337
|
-
for (let i = 0; i < ref.length; i++) {
|
|
338
|
-
let pathStep = ref[i].id || ref[i];
|
|
339
|
-
// Only first path step may be looked up in 'definitions'
|
|
340
|
-
if (i === 0 && !base) {
|
|
341
|
-
result = csn.definitions[pathStep];
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
// Structured type
|
|
345
|
-
else if (result && result.elements) {
|
|
346
|
-
result = getFinalTypeOf(result.elements[pathStep]);
|
|
347
|
-
}
|
|
348
|
-
// Association
|
|
349
|
-
else if (result && result.target) {
|
|
350
|
-
result = resolveRef([pathStep], csn.definitions[result.target]);
|
|
351
|
-
}
|
|
352
|
-
// Not resolvable
|
|
353
|
-
else {
|
|
354
|
-
return undefined;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return result;
|
|
358
|
-
}
|
|
359
|
-
*/
|
|
360
|
-
|
|
361
319
|
/**
|
|
362
320
|
* Render a context or service. Return the resulting source string.
|
|
363
321
|
*
|
|
@@ -1155,9 +1113,7 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
|
|
|
1155
1113
|
*/
|
|
1156
1114
|
function renderAssociationType( elm, env ) {
|
|
1157
1115
|
// Type, cardinality and target
|
|
1158
|
-
let result =
|
|
1159
|
-
|
|
1160
|
-
result += `${renderCardinality(elm.cardinality)} to `;
|
|
1116
|
+
let result = `association${renderCardinality(elm.cardinality)} to `;
|
|
1161
1117
|
|
|
1162
1118
|
// normal target or named aspect
|
|
1163
1119
|
if (elm.target || elm.targetAspect && typeof elm.targetAspect === 'string') {
|
package/lib/render/toSql.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
const {
|
|
5
5
|
getLastPartOf, getLastPartOfRef,
|
|
6
|
-
hasValidSkipOrExists,
|
|
6
|
+
hasValidSkipOrExists, generatedByCompilerVersion, getNormalizedQuery,
|
|
7
7
|
forEachDefinition, getResultingName,
|
|
8
|
-
getVariableReplacement,
|
|
8
|
+
getVariableReplacement, pathName,
|
|
9
9
|
} = require('../model/csnUtils');
|
|
10
|
+
const { isBuiltinType, isMagicVariable } = require('../base/builtins');
|
|
10
11
|
const { forEach, forEachValue, forEachKey } = require('../utils/objectUtils');
|
|
11
12
|
const {
|
|
12
13
|
renderFunc, cdsToSqlTypes, getHanaComment, hasHanaComment,
|
|
@@ -740,7 +741,9 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
740
741
|
let result = `${env.indent + quotedElementName}${isPostgresAlterColumn ? ' TYPE' : ''} ${renderTypeReference(elm, env)
|
|
741
742
|
}${renderNullability(elm, true, env.alterMode)}`;
|
|
742
743
|
// calculated elements (on write) can't have a default; ignore it
|
|
743
|
-
if (elm
|
|
744
|
+
if (elm.$default && env.alterMode && !elm.value && options.sqlDialect !== 'postgres')
|
|
745
|
+
result += ` DEFAULT ${renderExpr(elm.$default, env.withSubPath([ '$default' ]))}`;
|
|
746
|
+
else if (elm.default && !elm.value)
|
|
744
747
|
result += ` DEFAULT ${renderExpr(elm.default, env.withSubPath([ 'default' ]))}`;
|
|
745
748
|
|
|
746
749
|
// Only SAP HANA has fuzzy indices
|
|
@@ -29,8 +29,9 @@ const tenantDef = {
|
|
|
29
29
|
'@cds.api.ignore': true, // and/or $generated: 'tenant' for the full Universal CSN?
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
function addTenantFields( csn, options ) {
|
|
33
|
-
const { error, throwWithError }
|
|
32
|
+
function addTenantFields( csn, options, messageFunctions ) {
|
|
33
|
+
const { error, throwWithError }
|
|
34
|
+
= messageFunctions ?? createMessageFunctions( options, 'tenant', csn );
|
|
34
35
|
const { tenantDiscriminator } = options;
|
|
35
36
|
const tenantName = tenantDiscriminator === true ? 'tenant' : tenantDiscriminator;
|
|
36
37
|
if (tenantName !== 'tenant') {
|
|
@@ -47,17 +48,21 @@ function addTenantFields( csn, options ) {
|
|
|
47
48
|
const { definitions } = csn;
|
|
48
49
|
if (!definitions)
|
|
49
50
|
return csn;
|
|
50
|
-
const {
|
|
51
|
+
const {
|
|
52
|
+
initDefinition,
|
|
53
|
+
artifactRef,
|
|
54
|
+
effectiveType,
|
|
55
|
+
msgLocations,
|
|
56
|
+
} = csnRefs( csn, true );
|
|
51
57
|
|
|
52
58
|
const typeCache = new WeakMap();
|
|
53
|
-
const csnPath = [
|
|
59
|
+
const csnPath = [ null ];
|
|
54
60
|
let independent;
|
|
55
61
|
let projection;
|
|
56
62
|
|
|
57
63
|
for (const name in definitions) {
|
|
58
|
-
const art =
|
|
59
|
-
|
|
60
|
-
csnPath[1] = name;
|
|
64
|
+
const art = initDefinition( name );
|
|
65
|
+
csnPath[0] = art;
|
|
61
66
|
independent = art[annoTenantIndep];
|
|
62
67
|
projection = art.query || art.projection && art;
|
|
63
68
|
|
|
@@ -68,9 +73,11 @@ function addTenantFields( csn, options ) {
|
|
|
68
73
|
handleElements( art );
|
|
69
74
|
if (projection)
|
|
70
75
|
traverseQuery( projection, null, null, handleQuery );
|
|
76
|
+
// Note: handleQuery sets csnPath[0]; store if needed afterwards
|
|
71
77
|
}
|
|
72
78
|
else if (!independent && independent != null) {
|
|
73
|
-
error( 'tenant-invalid-anno-value', csnPath
|
|
79
|
+
error( 'tenant-invalid-anno-value', msgLocations( csnPath ),
|
|
80
|
+
{ anno: annoTenantIndep, value: independent },
|
|
74
81
|
// eslint-disable-next-line max-len
|
|
75
82
|
'Can\'t add $(ANNO) with value $(VALUE) to a non-entity, which is always tenant-independent' );
|
|
76
83
|
}
|
|
@@ -88,16 +95,15 @@ function addTenantFields( csn, options ) {
|
|
|
88
95
|
// the cache of csnRefs):
|
|
89
96
|
for (const name in definitions) {
|
|
90
97
|
const art = definitions[name];
|
|
91
|
-
|
|
92
|
-
art.elements = { [tenantName]: { ...tenantDef }, ...art.elements };
|
|
93
|
-
// consider non-enumerable `elements` of subqueries if that is supported
|
|
98
|
+
addTenantFieldToArt(art, options);
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
(csn.extensions || []).forEach( ( ext, idx ) => {
|
|
97
102
|
const tenant = ext.elements?.[tenantName];
|
|
98
103
|
const name = ext.annotate || ext.extend; // extend should not happen
|
|
99
104
|
if (tenant && isTenantDepEntity( definitions[name] )) {
|
|
100
|
-
error( 'tenant-unexpected-ext',
|
|
105
|
+
error( 'tenant-unexpected-ext',
|
|
106
|
+
msgLocations( [ 'extensions', idx, 'elements', 'tenant' ] ),
|
|
101
107
|
{ name: tenantName },
|
|
102
108
|
'Can\'t annotate element $(NAME) of a tenant-dependent entity' );
|
|
103
109
|
}
|
|
@@ -112,7 +118,7 @@ function addTenantFields( csn, options ) {
|
|
|
112
118
|
const names = art.includes
|
|
113
119
|
.filter( name => isTenantDepEntity( csn.definitions[name] ) );
|
|
114
120
|
if (names.length) {
|
|
115
|
-
error( 'tenant-invalid-include', csnPath, { names }, {
|
|
121
|
+
error( 'tenant-invalid-include', msgLocations( csnPath ), { names }, {
|
|
116
122
|
// eslint-disable-next-line max-len
|
|
117
123
|
std: 'Can\'t include the tenant-dependent entities $(NAMES) into a tenant-independent definition',
|
|
118
124
|
// eslint-disable-next-line max-len
|
|
@@ -125,12 +131,13 @@ function addTenantFields( csn, options ) {
|
|
|
125
131
|
function handleElements( art ) {
|
|
126
132
|
const { elements } = art;
|
|
127
133
|
if (elements[tenantName]) {
|
|
128
|
-
error( 'tenant-unexpected-element',
|
|
134
|
+
error( 'tenant-unexpected-element',
|
|
135
|
+
msgLocations( [ ...csnPath, 'elements', tenantName ] ),
|
|
129
136
|
{ name: tenantName, option: 'tenantDiscriminator' },
|
|
130
137
|
'Can\'t have entity with element $(NAME) when using option $(OPTION)' );
|
|
131
138
|
}
|
|
132
139
|
else if (!independent && !Object.values( elements ).some( e => e.key )) {
|
|
133
|
-
error( 'tenant-expecting-key', csnPath, {},
|
|
140
|
+
error( 'tenant-expecting-key', msgLocations( csnPath ), {},
|
|
134
141
|
'There must be a key in a tenant-dependent entity' );
|
|
135
142
|
}
|
|
136
143
|
else {
|
|
@@ -147,18 +154,16 @@ function addTenantFields( csn, options ) {
|
|
|
147
154
|
return;
|
|
148
155
|
|
|
149
156
|
if (query !== projection && !independent) {
|
|
150
|
-
error( 'tenant-unsupported-query', csnPath, {},
|
|
157
|
+
error( 'tenant-unsupported-query', msgLocations( csnPath ), {},
|
|
151
158
|
'Can\'t yet have tenant-dependent non-simple query entities' );
|
|
152
159
|
projection = null;
|
|
153
160
|
return;
|
|
154
161
|
}
|
|
155
162
|
|
|
156
|
-
if (query.projection)
|
|
157
|
-
csnPath.push( 'projection' );
|
|
158
|
-
else if (query.SELECT)
|
|
159
|
-
csnPath.push( 'query', 'SELECT' );
|
|
160
|
-
else
|
|
163
|
+
if (!query.projection && !query.SELECT)
|
|
161
164
|
return; // query.SET or query.join
|
|
165
|
+
csnPath[0] = query;
|
|
166
|
+
csnPath.push( query.SELECT ? 'SELECT' : 'projection' );
|
|
162
167
|
|
|
163
168
|
const select = query.SELECT || query.projection;
|
|
164
169
|
if (select.mixin)
|
|
@@ -177,7 +182,7 @@ function addTenantFields( csn, options ) {
|
|
|
177
182
|
else if (query !== projection && select.columns) {
|
|
178
183
|
checkColumnCasts( select.columns );
|
|
179
184
|
}
|
|
180
|
-
csnPath.length =
|
|
185
|
+
csnPath.length = 1;
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
function handleQuerySource( query ) {
|
|
@@ -185,7 +190,7 @@ function addTenantFields( csn, options ) {
|
|
|
185
190
|
const art = query.ref[0]; // yes, the base
|
|
186
191
|
if (csn.definitions[art][annoTenantIndep])
|
|
187
192
|
return true;
|
|
188
|
-
error( 'tenant-invalid-query-source', csnPath, { art, '#': independent }, {
|
|
193
|
+
error( 'tenant-invalid-query-source', msgLocations( csnPath ), { art, '#': independent }, {
|
|
189
194
|
std: 'Can\'t use a tenant-dependent query source $(ART) in a tenant-independent entity',
|
|
190
195
|
event: 'Can\'t use a tenant-dependent query source $(ART) in an event',
|
|
191
196
|
} );
|
|
@@ -194,12 +199,12 @@ function addTenantFields( csn, options ) {
|
|
|
194
199
|
if (query !== (projection.SELECT || projection.projection)?.from) // with `join`
|
|
195
200
|
return false;
|
|
196
201
|
if ((query.as || implicitAs( query.ref )) === tenantName) {
|
|
197
|
-
error( 'tenant-invalid-alias-name', csnPath,
|
|
202
|
+
error( 'tenant-invalid-alias-name', msgLocations( csnPath ),
|
|
198
203
|
{ name: tenantName, '#': (query.as ? 'std' : 'implicit') } );
|
|
199
204
|
}
|
|
200
205
|
const art = artifactRef.from( query );
|
|
201
206
|
if (art[annoTenantIndep]) {
|
|
202
|
-
error( 'tenant-expecting-tenant-source', csnPath, { art: query },
|
|
207
|
+
error( 'tenant-expecting-tenant-source', msgLocations( csnPath ), { art: query },
|
|
203
208
|
// TODO: better the final entity name of assoc navigation in FROM
|
|
204
209
|
// eslint-disable-next-line max-len
|
|
205
210
|
'Expecting the query source $(ART) to be tenant-dependent for a tenant-dependent query entity' );
|
|
@@ -212,7 +217,7 @@ function addTenantFields( csn, options ) {
|
|
|
212
217
|
for (const name in mixin) {
|
|
213
218
|
csnPath[csnPath.length - 1] = name;
|
|
214
219
|
if (name === tenantName && !independent)
|
|
215
|
-
error( 'tenant-invalid-alias-name', csnPath, { name, '#': 'mixin' } );
|
|
220
|
+
error( 'tenant-invalid-alias-name', msgLocations( csnPath ), { name, '#': 'mixin' } );
|
|
216
221
|
handleAssociations( mixin[name], null );
|
|
217
222
|
}
|
|
218
223
|
csnPath.length -= 2;
|
|
@@ -220,7 +225,7 @@ function addTenantFields( csn, options ) {
|
|
|
220
225
|
|
|
221
226
|
function checkExcluding( excludeList ) {
|
|
222
227
|
if (excludeList.includes( tenantName )) {
|
|
223
|
-
error( 'tenant-invalid-excluding', csnPath, { name: tenantName },
|
|
228
|
+
error( 'tenant-invalid-excluding', msgLocations( csnPath ), { name: tenantName },
|
|
224
229
|
'Can\'t exclude $(NAME) from the query source of a tenant-dependent entity' );
|
|
225
230
|
}
|
|
226
231
|
}
|
|
@@ -241,7 +246,7 @@ function addTenantFields( csn, options ) {
|
|
|
241
246
|
for (const col of columns) {
|
|
242
247
|
++csnPath[csnPath.length - 1];
|
|
243
248
|
if (col.expand || col.inline) {
|
|
244
|
-
error( 'tenant-unsupported-expand-inline', csnPath, {},
|
|
249
|
+
error( 'tenant-unsupported-expand-inline', msgLocations( csnPath ), {},
|
|
245
250
|
'Can\'t use expand/inline in a tenant-dependent entity' );
|
|
246
251
|
}
|
|
247
252
|
if (col.key != null) // yes, also with key: false
|
|
@@ -275,12 +280,20 @@ function addTenantFields( csn, options ) {
|
|
|
275
280
|
return null;
|
|
276
281
|
|
|
277
282
|
if (elem.target) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
283
|
+
const { target } = elem;
|
|
284
|
+
if (csn.definitions[target][annoTenantIndep]) {
|
|
285
|
+
if (!independent && isComposition( elem ))
|
|
286
|
+
error( 'tenant-invalid-composition', msgLocations( csnPath ), { target } );
|
|
281
287
|
}
|
|
282
|
-
else if (
|
|
283
|
-
|
|
288
|
+
else if (independent) {
|
|
289
|
+
if (target.endsWith( '.DraftAdministrativeData' ) && csnPath.length === 3 &&
|
|
290
|
+
csnPath[1] === 'elements' && csnPath[2] === 'DraftAdministrativeData') {
|
|
291
|
+
error( 'tenant-invalid-draft', msgLocations( csnPath ), {},
|
|
292
|
+
'A tenant-independent entity can\'t be draft-enabled' );
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
error( 'tenant-invalid-target', msgLocations( csnPath ), { target } );
|
|
296
|
+
}
|
|
284
297
|
}
|
|
285
298
|
}
|
|
286
299
|
else if (elem.type && (independent || !elem.elements && !elem.items)) {
|
|
@@ -290,10 +303,11 @@ function addTenantFields( csn, options ) {
|
|
|
290
303
|
if (independent) {
|
|
291
304
|
if (!dep || dep === 'Composition')
|
|
292
305
|
return true; // check elements (assocs could be redirected)
|
|
293
|
-
error( 'tenant-invalid-target', csnPath, { type: elem.type, '#': 'type' } );
|
|
306
|
+
error( 'tenant-invalid-target', msgLocations( csnPath ), { type: elem.type, '#': 'type' } );
|
|
294
307
|
}
|
|
295
308
|
else if (dep && dep !== 'dependent') {
|
|
296
|
-
error( 'tenant-invalid-composition', csnPath
|
|
309
|
+
error( 'tenant-invalid-composition', msgLocations( csnPath ),
|
|
310
|
+
{ type: elem.type, '#': 'type' } );
|
|
297
311
|
}
|
|
298
312
|
}
|
|
299
313
|
else {
|
|
@@ -398,6 +412,15 @@ function isTenantDepEntity( art ) {
|
|
|
398
412
|
return art?.kind === 'entity' && !art[annoTenantIndep];
|
|
399
413
|
}
|
|
400
414
|
|
|
415
|
+
function addTenantFieldToArt( art, options ) {
|
|
416
|
+
const tenantName = options.tenantDiscriminator === true ? 'tenant' : options.tenantDiscriminator;
|
|
417
|
+
|
|
418
|
+
if (isTenantDepEntity( art ))
|
|
419
|
+
art.elements = { [tenantName]: { ...tenantDef }, ...art.elements };
|
|
420
|
+
// consider non-enumerable `elements` of subqueries if that is supported
|
|
421
|
+
}
|
|
422
|
+
|
|
401
423
|
module.exports = {
|
|
402
424
|
addTenantFields,
|
|
425
|
+
addTenantFieldToArt,
|
|
403
426
|
};
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
const { setProp } = require('../../base/model');
|
|
16
|
-
const { isAnnotationExpression } = require('../../
|
|
16
|
+
const { isAnnotationExpression } = require('../../base/builtins');
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -344,7 +344,40 @@ function transformExpression( parent, propName, transformers, path = [] ) {
|
|
|
344
344
|
return parent;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Merge an array of transformer-objects into a single one, set the this-value of every subfunction to "that"
|
|
349
|
+
*
|
|
350
|
+
* @param {object[]} transformers transformers
|
|
351
|
+
* @param {object} that Value for this
|
|
352
|
+
* @returns {object} Remapped transformers.
|
|
353
|
+
*/
|
|
354
|
+
function mergeTransformers( transformers, that ) {
|
|
355
|
+
const remapped = {};
|
|
356
|
+
for (const transformer of transformers) {
|
|
357
|
+
for (const [ n, fns ] of Object.entries(transformer)) {
|
|
358
|
+
if (!remapped[n])
|
|
359
|
+
remapped[n] = [];
|
|
360
|
+
|
|
361
|
+
if (Array.isArray(fns)) {
|
|
362
|
+
remapped[n].push((parent, name, prop, path, parentParent) => fns.forEach(
|
|
363
|
+
fn => fn.bind(that)(parent, name, prop, path, parentParent)
|
|
364
|
+
));
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
remapped[n].push((parent, name, prop, path, parentParent) => fns.bind(that)(parent, name, prop, path, parentParent));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
for (const [ n, fns ] of Object.entries(remapped))
|
|
373
|
+
remapped[n] = (parent, name, prop, path, parentParent) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent));
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
return remapped;
|
|
377
|
+
}
|
|
378
|
+
|
|
347
379
|
module.exports = {
|
|
380
|
+
mergeTransformers,
|
|
348
381
|
transformExpression,
|
|
349
382
|
applyTransformations,
|
|
350
383
|
applyTransformationsOnNonDictionary,
|
|
@@ -116,7 +116,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
116
116
|
foreignKeyConstraintForAssociation(up_, dependent, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1] );
|
|
117
117
|
}
|
|
118
118
|
else if (!onCondition && composition.keys.length > 0) {
|
|
119
|
-
throw new CompilerAssertion('
|
|
119
|
+
throw new CompilerAssertion('Debug me, an on-condition was expected here, but only found keys');
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -58,6 +58,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
58
58
|
orderBy: (parent, name, orderBy, path) => {
|
|
59
59
|
parent.orderBy = expand(orderBy, path.concat('orderBy'));
|
|
60
60
|
},
|
|
61
|
+
list: (parent, name, list, path) => {
|
|
62
|
+
parent.list = expand(list, path.concat('list'));
|
|
63
|
+
},
|
|
61
64
|
};
|
|
62
65
|
|
|
63
66
|
// To not have a whole model loop for such a "small" thing, we kill all non-sql-backend relevant annotations here
|
|
@@ -95,7 +98,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
95
98
|
*/
|
|
96
99
|
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
97
100
|
markAsToDummify(artifact, path[1]);
|
|
98
|
-
|
|
101
|
+
rewritten.toMany.forEach(({ art }) => {
|
|
102
|
+
error( null, art.$path || [ 'definitions', path[1] ], { name: `${art.$env || path[1]}:${art.ref.map(r => r.id || r)}` }, 'Unexpected .expand with to-many association $(NAME)');
|
|
103
|
+
});
|
|
99
104
|
}
|
|
100
105
|
else {
|
|
101
106
|
parent.columns = rewritten.columns;
|
|
@@ -172,8 +177,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
172
177
|
const target = art.target ? art.target : pathStep;
|
|
173
178
|
if (toDummify.indexOf(target) !== -1) {
|
|
174
179
|
error( null, obj.$path, {
|
|
175
|
-
id: pathStep,
|
|
176
|
-
|
|
180
|
+
id: pathStep,
|
|
181
|
+
elemref: obj,
|
|
182
|
+
name,
|
|
183
|
+
anno: '@cds.persistence.skip',
|
|
184
|
+
}, 'Unexpected $(ANNO) annotation on Association target $(NAME) of $(ID) in path $(ELEMREF) was skipped because of .expand in conjunction with to-many');
|
|
177
185
|
}
|
|
178
186
|
}
|
|
179
187
|
|
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
applyTransformations,
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
applyTransformations,
|
|
5
|
+
applyTransformationsOnNonDictionary,
|
|
6
|
+
cardinality2str,
|
|
7
|
+
copyAnnotations,
|
|
8
|
+
implicitAs,
|
|
9
|
+
isDeepEqual,
|
|
10
|
+
findAnnotationExpression,
|
|
7
11
|
} = require('../../model/csnUtils');
|
|
12
|
+
const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
|
|
8
13
|
const transformUtils = require('../transformUtils');
|
|
9
14
|
const { csnRefs } = require('../../model/csnRefs');
|
|
10
15
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
11
16
|
const { forEach } = require('../../utils/objectUtils');
|
|
12
17
|
const { transformExpression } = require('./applyTransformations');
|
|
13
18
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
|
-
* Strip off leading $self from refs where applicable
|
|
21
|
+
* Strip off leading $self from refs where applicable.
|
|
22
|
+
* Only relevant for HDBCDS, because handling of `$self` is not implemented there.
|
|
16
23
|
*
|
|
17
|
-
* @param {
|
|
24
|
+
* @param {object} parent
|
|
25
|
+
* @param {string} prop
|
|
26
|
+
* @param {CSN.Elements} elements
|
|
18
27
|
*/
|
|
19
|
-
function removeLeadingSelf(
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ref
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}, /* only for kind entity and view */ /* do not go into .actions */
|
|
35
|
-
}, [], { skipIgnore: false, allowArtifact: artifact => (artifact.kind === 'entity'), skipDict: { actions: true } });
|
|
28
|
+
function removeLeadingSelf( parent, prop, elements ) {
|
|
29
|
+
for (const [ elementName, element ] of Object.entries(elements)) {
|
|
30
|
+
if (element.on) {
|
|
31
|
+
applyTransformationsOnNonDictionary(elements, elementName, {
|
|
32
|
+
ref: (root, name, ref) => {
|
|
33
|
+
// HDBCDS renderers seem to expect it to not be there...
|
|
34
|
+
if (ref[0] === '$self' && ref.length > 1 && !isMagicVariable(ref[1]) && ref[1] !== '$projection' && ref[1] !== '$self')
|
|
35
|
+
root.ref.shift();
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
/**
|
|
@@ -79,34 +83,49 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
79
83
|
|
|
80
84
|
const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
|
|
81
85
|
const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
|
|
86
|
+
const stripItems = options.transformation === 'hdbcds' || options.transformation === 'sql';
|
|
87
|
+
const removeItems = new Set();
|
|
82
88
|
applyTransformations(csn, {
|
|
83
89
|
type: (node, prop, type, path, parent, parentProp) => {
|
|
84
90
|
if (options.toOdata && node.kind && node.kind in ignoreOdataKinds)
|
|
85
91
|
return;
|
|
86
92
|
if (parentProp === 'cast') {
|
|
87
93
|
const e = csnUtils.getFinalTypeInfo(type, t => resolved.get(t)?.art || csnUtils.artifactRef(t));
|
|
94
|
+
if (e.items && stripItems)
|
|
95
|
+
removeItems.add(node);
|
|
88
96
|
if (!e || e.items || e.elements)
|
|
89
97
|
return;
|
|
90
98
|
}
|
|
91
99
|
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
|
|
92
100
|
toFinalBaseType(node, resolved, true);
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
|
|
103
|
+
if (node.items && stripItems) {
|
|
104
|
+
removeItems.add(node);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
if (node.items) // items could have unresolved types
|
|
108
|
+
toFinalBaseType(node.items, resolved, true);
|
|
109
|
+
|
|
110
|
+
// structured types might not have the child-types replaced.
|
|
111
|
+
// Drill down to ensure this.
|
|
112
|
+
const nextElements = node.elements || node.items?.elements;
|
|
113
|
+
const stack = nextElements ? [ nextElements ] : [];
|
|
114
|
+
while (stack.length > 0) {
|
|
115
|
+
const elements = stack.pop();
|
|
116
|
+
for (const e of Object.values(elements)) {
|
|
117
|
+
toFinalBaseType(e, resolved, true);
|
|
118
|
+
if (stripItems && e.items) {
|
|
119
|
+
removeItems.add(e);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
if (!options.toOdata && e.items) // items could have unresolved types
|
|
123
|
+
toFinalBaseType(e.items, resolved, true);
|
|
124
|
+
const next = e.elements || e.items?.elements;
|
|
125
|
+
if (next)
|
|
126
|
+
stack.push(next);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
110
129
|
}
|
|
111
130
|
}
|
|
112
131
|
|
|
@@ -115,6 +134,7 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
115
134
|
removeLocalized(node);
|
|
116
135
|
}
|
|
117
136
|
},
|
|
137
|
+
items: node => removeItems.add(node),
|
|
118
138
|
}, [ (definitions, artifactName, artifact) => {
|
|
119
139
|
// Replace events, actions and functions with simple dummies - they don't have effect on forRelationalDB stuff
|
|
120
140
|
// and that way they contain no references and don't hurt.
|
|
@@ -123,6 +143,8 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
123
143
|
// TODO:factor out somewhere else
|
|
124
144
|
if (!options.toOdata && artifact.kind in replaceWithDummyKinds) {
|
|
125
145
|
const dummy = { kind: artifact.kind };
|
|
146
|
+
if (artifact.kind === 'event')
|
|
147
|
+
dummy.elements = {}; // events must be structured for recompilation
|
|
126
148
|
if (artifact.$location)
|
|
127
149
|
setProp(dummy, '$location', artifact.$location);
|
|
128
150
|
|
|
@@ -131,6 +153,13 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
131
153
|
// TODO: skipDict options as default function arguments not via Object.assign
|
|
132
154
|
} ], iterateOptions);
|
|
133
155
|
|
|
156
|
+
// no support for array-of - turn into CLOB/Text
|
|
157
|
+
for (const node of removeItems) {
|
|
158
|
+
node.type = 'cds.LargeString';
|
|
159
|
+
delete node.items;
|
|
160
|
+
}
|
|
161
|
+
removeItems.clear();
|
|
162
|
+
|
|
134
163
|
|
|
135
164
|
/**
|
|
136
165
|
* OData V4 only:
|
|
@@ -439,17 +468,13 @@ function linkForeignKeyAnnotationExtensionsToAssociation( csn, options ) {
|
|
|
439
468
|
*/
|
|
440
469
|
function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFunctions, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
|
|
441
470
|
const { error, warning } = messageFunctions;
|
|
442
|
-
const {
|
|
471
|
+
const { inspectRef, isStructured } = csnUtils;
|
|
443
472
|
const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
444
473
|
if (flattenKeyRefs) {
|
|
445
474
|
applyTransformations(csn, {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
// replace foreign keys that are managed associations by their respective foreign keys
|
|
450
|
-
flattenFKs(element, elementName, [ ...path, 'elements', elementName ]);
|
|
451
|
-
}
|
|
452
|
-
});
|
|
475
|
+
keys: (element, prop, keys, path) => {
|
|
476
|
+
// replace foreign keys that are managed associations by their respective foreign keys
|
|
477
|
+
flattenFKs(element, path.at(-1), path);
|
|
453
478
|
},
|
|
454
479
|
}, [], Object.assign({
|
|
455
480
|
skipIgnore: false,
|
|
@@ -28,8 +28,8 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
|
|
|
28
28
|
// (230 c) If we keep associations as they are (hdbcds naming convention), we can't have associations in GROUP BY
|
|
29
29
|
if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
|
|
30
30
|
error(null, groupByPath,
|
|
31
|
-
{ $reviewed: true },
|
|
32
|
-
'Unexpected managed association in
|
|
31
|
+
{ $reviewed: true, keyword: 'GROUP BY', value: 'hdbcds' },
|
|
32
|
+
'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
const pathPrefix = query.groupBy[i].ref.slice(0, -1);
|
|
@@ -4,7 +4,7 @@ const {
|
|
|
4
4
|
getNormalizedQuery, hasAnnotationValue, forEachMember,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const { implicitAs } = require('../../model/csnRefs');
|
|
7
|
-
const { setProp } = require('../../base/model');
|
|
7
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
8
8
|
const { getTransformers } = require('../transformUtils');
|
|
9
9
|
|
|
10
10
|
const validToString = '@cds.valid.to';
|
|
@@ -21,9 +21,10 @@ const validFromString = '@cds.valid.from';
|
|
|
21
21
|
* @param {object} messageFunctions
|
|
22
22
|
* @param {Function} messageFunctions.info
|
|
23
23
|
* @param {object} csnUtils
|
|
24
|
+
* @param {object} options
|
|
24
25
|
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition applying the where-condition to views.
|
|
25
26
|
*/
|
|
26
|
-
function getViewDecorator( csn, messageFunctions, csnUtils ) {
|
|
27
|
+
function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
|
|
27
28
|
const { info } = messageFunctions;
|
|
28
29
|
const { get$combined } = csnUtils;
|
|
29
30
|
return addTemporalWhereConditionToView;
|
|
@@ -52,7 +53,9 @@ function getViewDecorator( csn, messageFunctions, csnUtils ) {
|
|
|
52
53
|
if (from.length === 1 && to.length === 1) {
|
|
53
54
|
// and both are from the same origin
|
|
54
55
|
if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
|
|
55
|
-
|
|
56
|
+
const omitWhereClause = isBetaEnabled(options, 'temporalRawProjection') &&
|
|
57
|
+
hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0]);
|
|
58
|
+
if (!omitWhereClause) {
|
|
56
59
|
const fromPath = {
|
|
57
60
|
ref: [
|
|
58
61
|
from[0].parent,
|