@sap/cds-compiler 6.2.2 → 6.3.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 +35 -0
- package/bin/cdsc.js +11 -4
- package/lib/api/options.js +1 -1
- package/lib/base/message-registry.js +36 -7
- package/lib/base/messages.js +11 -4
- package/lib/base/model.js +0 -1
- package/lib/checks/assocOutsideService.js +17 -30
- package/lib/checks/checkForTypes.js +0 -18
- package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
- package/lib/checks/onConditions.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +16 -15
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +30 -6
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/checks.js +47 -18
- package/lib/compiler/index.js +88 -6
- package/lib/compiler/resolve.js +7 -7
- package/lib/compiler/tweak-assocs.js +47 -25
- package/lib/gen/BaseParser.js +1 -1
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +381 -378
- package/lib/gen/Dictionary.json +0 -2
- package/lib/model/csnRefs.js +9 -4
- package/lib/model/csnUtils.js +67 -2
- package/lib/optionProcessor.js +2 -3
- package/lib/parsers/AstBuildingParser.js +5 -6
- package/lib/render/toCdl.js +10 -4
- package/lib/render/utils/common.js +4 -2
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/associations.js +37 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +37 -36
- package/lib/transform/draft/db.js +20 -20
- package/lib/transform/draft/odata.js +38 -40
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/flattening.js +40 -47
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/forOdata.js +135 -115
- package/lib/transform/forRelationalDB.js +151 -142
- package/lib/transform/localized.js +116 -109
- package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
- package/lib/transform/odata/createForeignKeys.js +73 -70
- package/lib/transform/odata/flattening.js +216 -200
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
- package/lib/transform/odata/toFinalBaseType.js +40 -39
- package/lib/transform/odata/typesExposure.js +151 -133
- package/lib/transform/odata/utils.js +7 -6
- package/lib/transform/parseExpr.js +165 -162
- package/lib/transform/transformUtils.js +184 -551
- package/lib/transform/translateAssocsToJoins.js +510 -571
- package/lib/transform/tupleExpansion.js +495 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/package.json +1 -1
- package/lib/base/cleanSymbols.js +0 -17
- package/lib/checks/nonexpandableStructured.js +0 -39
package/lib/gen/Dictionary.json
CHANGED
|
@@ -808,7 +808,6 @@
|
|
|
808
808
|
"Type": "Core.Tag"
|
|
809
809
|
},
|
|
810
810
|
"Common.IsTimezone": {
|
|
811
|
-
"$experimental": true,
|
|
812
811
|
"AppliesTo": [
|
|
813
812
|
"Property",
|
|
814
813
|
"Parameter"
|
|
@@ -1035,7 +1034,6 @@
|
|
|
1035
1034
|
"Type": "Edm.DateTimeOffset"
|
|
1036
1035
|
},
|
|
1037
1036
|
"Common.Timezone": {
|
|
1038
|
-
"$experimental": true,
|
|
1039
1037
|
"AppliesTo": [
|
|
1040
1038
|
"Property",
|
|
1041
1039
|
"Parameter"
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -250,10 +250,9 @@ function csnRefs( csn, universalReady ) {
|
|
|
250
250
|
// const { definitions } = csn;
|
|
251
251
|
const cache = new WeakMap();
|
|
252
252
|
setCache( BUILTIN_TYPE, '_origin', null );
|
|
253
|
-
if (universalReady === 'init-all')
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
253
|
+
if (universalReady === 'init-all')
|
|
254
|
+
initAllDefinitions();
|
|
255
|
+
|
|
257
256
|
// Functions which set the new `baseEnv`:
|
|
258
257
|
resolveRef.expandInline = function resolveExpandInline( ref, ...args ) {
|
|
259
258
|
return cached( ref, '_env', () => navigationEnv( resolveRef( ref, ...args ).art ) );
|
|
@@ -278,6 +277,7 @@ function csnRefs( csn, universalReady ) {
|
|
|
278
277
|
getColumnName: col => getCache( col, '$as' ),
|
|
279
278
|
$getQueries: def => getCache( def, '$queries' ), // unstable API
|
|
280
279
|
initDefinition,
|
|
280
|
+
initAllDefinitions,
|
|
281
281
|
dropDefinitionCache,
|
|
282
282
|
targetAspect,
|
|
283
283
|
msgLocations,
|
|
@@ -489,6 +489,11 @@ function csnRefs( csn, universalReady ) {
|
|
|
489
489
|
return query && cache.get( query.projection || query );
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
+
function initAllDefinitions() {
|
|
493
|
+
for (const name of Object.keys( csn.definitions || {}))
|
|
494
|
+
initDefinition( name );
|
|
495
|
+
}
|
|
496
|
+
|
|
492
497
|
function initDefinition( main ) {
|
|
493
498
|
const name = typeof main === 'string' && main;
|
|
494
499
|
if (name) {
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -64,6 +64,7 @@ function getUtils( model, universalReady ) {
|
|
|
64
64
|
addStringAnnotationTo,
|
|
65
65
|
getServiceName,
|
|
66
66
|
getFinalTypeInfo,
|
|
67
|
+
resolvePath,
|
|
67
68
|
get$combined,
|
|
68
69
|
getQueryPrimarySource,
|
|
69
70
|
..._csnRefs,
|
|
@@ -448,6 +449,71 @@ function getUtils( model, universalReady ) {
|
|
|
448
449
|
return target;
|
|
449
450
|
}
|
|
450
451
|
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Resolve the type of an artifact
|
|
456
|
+
* If art is undefined, stop
|
|
457
|
+
* If art has elements or items.elements, stop
|
|
458
|
+
* If art has a type and the type is scalar, stop
|
|
459
|
+
* If art has a named type or a type ref, resolve it
|
|
460
|
+
*/
|
|
461
|
+
function resolveType( art ) {
|
|
462
|
+
while (art &&
|
|
463
|
+
!((art.items && art.items.elements) || art.elements) &&
|
|
464
|
+
(art.type &&
|
|
465
|
+
((!art.type.ref && !isBuiltinType(art.type)) || art.type.ref))) {
|
|
466
|
+
if (art.type.ref)
|
|
467
|
+
art = resolvePath(art.type);
|
|
468
|
+
else
|
|
469
|
+
art = model.definitions[art.type];
|
|
470
|
+
}
|
|
471
|
+
return art;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Path resolution, attach artifact to each path step, if found,
|
|
476
|
+
* Dereference types and follow associations.
|
|
477
|
+
*
|
|
478
|
+
* @param {any} path ref object
|
|
479
|
+
* @param {any} art start environment
|
|
480
|
+
* @returns {any} path with resolved artifacts or artifact
|
|
481
|
+
* (if called with simple ref paths)
|
|
482
|
+
*/
|
|
483
|
+
function resolvePath( path, art = undefined ) {
|
|
484
|
+
let notFound = false;
|
|
485
|
+
for (let i = 0; i < path.ref.length && !notFound; i++) {
|
|
486
|
+
const ps = path.ref[i];
|
|
487
|
+
const id = ps.id || ps;
|
|
488
|
+
if (art) {
|
|
489
|
+
if (art.target)
|
|
490
|
+
art = model.definitions[art.target].elements[id];
|
|
491
|
+
else if (art.items && art.items.elements || art.elements)
|
|
492
|
+
art = (art.items && art.items.elements || art.elements)[id];
|
|
493
|
+
|
|
494
|
+
else
|
|
495
|
+
art = undefined;
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
art = model.definitions[id];
|
|
499
|
+
}
|
|
500
|
+
art = resolveType(art);
|
|
501
|
+
|
|
502
|
+
// if path step has id, store art
|
|
503
|
+
if (ps.id && art)
|
|
504
|
+
ps._art = art;
|
|
505
|
+
notFound = !art;
|
|
506
|
+
}
|
|
507
|
+
// if resolve was called on constraint path, path has id.
|
|
508
|
+
// Store art and return path, if called recursively for model ref paths,
|
|
509
|
+
// return artifact only
|
|
510
|
+
if (path.ref[0].id) {
|
|
511
|
+
if (art)
|
|
512
|
+
path._art = art;
|
|
513
|
+
return path;
|
|
514
|
+
}
|
|
515
|
+
return art;
|
|
516
|
+
}
|
|
451
517
|
}
|
|
452
518
|
|
|
453
519
|
|
|
@@ -1248,7 +1314,6 @@ function walkCsnPath( csn, path ) {
|
|
|
1248
1314
|
for (const segment of path)
|
|
1249
1315
|
obj = obj[segment];
|
|
1250
1316
|
|
|
1251
|
-
|
|
1252
1317
|
return obj;
|
|
1253
1318
|
}
|
|
1254
1319
|
|
|
@@ -1356,7 +1421,7 @@ function functionList( functions, thisArg ) {
|
|
|
1356
1421
|
* @returns {boolean}
|
|
1357
1422
|
*/
|
|
1358
1423
|
function isDollarSelfOrProjectionOperand( arg ) {
|
|
1359
|
-
return arg.ref
|
|
1424
|
+
return arg.ref?.length === 1 &&
|
|
1360
1425
|
(arg.ref[0] === '$self' || arg.ref[0] === '$projection');
|
|
1361
1426
|
}
|
|
1362
1427
|
|
package/lib/optionProcessor.js
CHANGED
|
@@ -265,7 +265,7 @@ optionProcessor.command('O, toOdata')
|
|
|
265
265
|
.option(' --odata-vocabularies <list>')
|
|
266
266
|
.option(' --odata-no-creator')
|
|
267
267
|
.option(' --draft-messages')
|
|
268
|
-
.option(' --
|
|
268
|
+
.option(' --draft-user-description')
|
|
269
269
|
.option('-c, --csn')
|
|
270
270
|
.option('-f, --odata-format <format>', { valid: [ 'flat', 'structured' ] })
|
|
271
271
|
.option('-n, --sql-mapping <style>', { valid: [ 'plain', 'quoted', 'hdbcds' ], aliases: [ '--names' ] })
|
|
@@ -301,8 +301,7 @@ optionProcessor.command('O, toOdata')
|
|
|
301
301
|
{ prefix: { alias, ns, uri }, ... }
|
|
302
302
|
--odata-no-creator Omit creator identification in API
|
|
303
303
|
--draft-messages Add draft messages as part of the draft creation
|
|
304
|
-
--
|
|
305
|
-
containing draft enabled entitties
|
|
304
|
+
--draft-user-description Add user description fields to the DraftAdministrativeData entity
|
|
306
305
|
-n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
|
|
307
306
|
the corresponding database name (see "--sql-mapping" for "toSql")
|
|
308
307
|
plain : (default) Names in uppercase and flattened with underscores
|
|
@@ -49,7 +49,6 @@ const PRECEDENCE_OF_EQUAL = 10;
|
|
|
49
49
|
class AstBuildingParser extends BaseParser {
|
|
50
50
|
leanConditions = {
|
|
51
51
|
afterBrace: true,
|
|
52
|
-
atRightParen: true,
|
|
53
52
|
fail: true,
|
|
54
53
|
};
|
|
55
54
|
|
|
@@ -203,6 +202,10 @@ class AstBuildingParser extends BaseParser {
|
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
addTokenToSet_( set, tokenName, val, collectKeywordsOnly ) {
|
|
205
|
+
if (!this.dynamic_.call) {
|
|
206
|
+
super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
206
209
|
const token = parserTokens[tokenName];
|
|
207
210
|
// TODO: use lower-case in specialFunctions
|
|
208
211
|
const realTokens = token && this.dynamic_.generic?.[token];
|
|
@@ -215,7 +218,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
215
218
|
// Works, since `DeleteStarFromSet` comes after `*` (length-sorted):
|
|
216
219
|
delete set['*'];
|
|
217
220
|
}
|
|
218
|
-
else {
|
|
221
|
+
else if (!token) {
|
|
219
222
|
super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
|
|
220
223
|
}
|
|
221
224
|
}
|
|
@@ -489,10 +492,6 @@ class AstBuildingParser extends BaseParser {
|
|
|
489
492
|
// token is not on a new line?
|
|
490
493
|
}
|
|
491
494
|
|
|
492
|
-
atRightParen() {
|
|
493
|
-
return this.l() !== ')';
|
|
494
|
-
}
|
|
495
|
-
|
|
496
495
|
/**
|
|
497
496
|
* For annotations at the beginning of columns outside parentheses
|
|
498
497
|
*/
|
package/lib/render/toCdl.js
CHANGED
|
@@ -796,10 +796,15 @@ class CsnToCdl {
|
|
|
796
796
|
}
|
|
797
797
|
|
|
798
798
|
// Top-level annotations of the artifact
|
|
799
|
-
|
|
799
|
+
const topLevelAnnotations = this.renderAnnotationAssignmentsAndDocComment(ext, env.withIncreasedIndent());
|
|
800
|
+
|
|
800
801
|
// Note: Not renderDefinitionReference, because we don't care if there
|
|
801
802
|
// are annotations to unknown things. That's allowed!
|
|
802
|
-
result
|
|
803
|
+
let result = `${ env.indent }annotate ${ this.renderArtifactName(ext.annotate, env) }`;
|
|
804
|
+
|
|
805
|
+
if (topLevelAnnotations)
|
|
806
|
+
result += ` with\n${ topLevelAnnotations }`;
|
|
807
|
+
|
|
803
808
|
|
|
804
809
|
if (ext.params)
|
|
805
810
|
result += this.renderAnnotateParamsInParentheses(ext, env);
|
|
@@ -1368,7 +1373,7 @@ class CsnToCdl {
|
|
|
1368
1373
|
}
|
|
1369
1374
|
|
|
1370
1375
|
/**
|
|
1371
|
-
* Render the given columns.
|
|
1376
|
+
* Render the given columns / select items.
|
|
1372
1377
|
*
|
|
1373
1378
|
* @param {CSN.Extension | CSN.QuerySelect} art
|
|
1374
1379
|
* @param {object} elements
|
|
@@ -1459,6 +1464,7 @@ class CsnToCdl {
|
|
|
1459
1464
|
|
|
1460
1465
|
const isAnonymousExpand = (obj.expand && !obj.ref);
|
|
1461
1466
|
|
|
1467
|
+
|
|
1462
1468
|
// s as alias { * }
|
|
1463
1469
|
if (obj.as && !isAnonymousExpand)
|
|
1464
1470
|
result += this.renderAlias(obj.as, env);
|
|
@@ -1483,7 +1489,7 @@ class CsnToCdl {
|
|
|
1483
1489
|
const childEnv = env.withIncreasedIndent();
|
|
1484
1490
|
const expandInline = obj.expand || obj.inline;
|
|
1485
1491
|
result += expandInline //
|
|
1486
|
-
.map(elm => this.
|
|
1492
|
+
.map(elm => this.renderViewColumn(elm, childEnv))
|
|
1487
1493
|
.join(',\n');
|
|
1488
1494
|
result += `\n${ env.indent }}`;
|
|
1489
1495
|
|
|
@@ -305,10 +305,12 @@ const cdsToSqlTypes = {
|
|
|
305
305
|
h2: {
|
|
306
306
|
'cds.Binary': 'VARBINARY', // same as for plain
|
|
307
307
|
'cds.LargeBinary': 'BINARY LARGE OBJECT', // BLOB would require a length!
|
|
308
|
-
'cds.
|
|
308
|
+
// 'cds.Decimal': 'DECFLOAT', // Decimal and Decimal(p) is mapped to cds.DecimalFloat
|
|
309
|
+
'cds.DecimalFloat': 'DECFLOAT',
|
|
309
310
|
'cds.DateTime': 'TIMESTAMP(0)',
|
|
310
311
|
'cds.Timestamp': 'TIMESTAMP(7)',
|
|
311
312
|
'cds.Map': 'JSON',
|
|
313
|
+
'cds.UInt8': 'SMALLINT', // See #13870; not equivalent, but smallint can hold >byte
|
|
312
314
|
},
|
|
313
315
|
postgres: {
|
|
314
316
|
// See <https://www.postgresql.org/docs/current/datatype.html>
|
|
@@ -317,7 +319,7 @@ const cdsToSqlTypes = {
|
|
|
317
319
|
'cds.LargeBinary': 'BYTEA',
|
|
318
320
|
'cds.Binary': 'BYTEA',
|
|
319
321
|
'cds.Double': 'FLOAT8',
|
|
320
|
-
'cds.UInt8': '
|
|
322
|
+
'cds.UInt8': 'SMALLINT', // See #13870; not equivalent, but smallint can hold >byte
|
|
321
323
|
'cds.Vector': 'VARCHAR', // Not supported; see #11725
|
|
322
324
|
'cds.Map': 'JSONB',
|
|
323
325
|
},
|
|
@@ -20,7 +20,8 @@ const { pathName } = require('../../compiler/utils');
|
|
|
20
20
|
* @returns {Function} forEachDefinition callback
|
|
21
21
|
*/
|
|
22
22
|
function processAssertUnique( csn, options, messageFunctions ) {
|
|
23
|
-
const {
|
|
23
|
+
const { csnUtils, flattenPath } = getTransformers(csn, options, messageFunctions);
|
|
24
|
+
const { resolvePath } = csnUtils;
|
|
24
25
|
const { error, info } = messageFunctions;
|
|
25
26
|
|
|
26
27
|
return handleAssertUnique;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
+
transformAnnotationExpression,
|
|
4
5
|
applyTransformationsOnNonDictionary,
|
|
5
6
|
applyTransformations,
|
|
6
7
|
implicitAs,
|
|
@@ -106,18 +107,40 @@ function attachOnConditions( csn, csnUtils, pathDelimiter, iterateOptions = {},
|
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
109
|
* @param {CSN.Model} csn
|
|
110
|
+
* @param {CSN.Options} options
|
|
109
111
|
* @param {object} csnUtils
|
|
110
112
|
* @param {string} pathDelimiter
|
|
111
113
|
* @param {boolean} [processOnInQueries=false] Wether to process on-conditions in queries (joins and mixins)
|
|
112
114
|
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
|
|
113
115
|
*/
|
|
114
|
-
function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries = false ) {
|
|
116
|
+
function getFKAccessFinalizer( csn, options, csnUtils, pathDelimiter, processOnInQueries = false ) {
|
|
115
117
|
const {
|
|
116
118
|
inspectRef,
|
|
117
119
|
} = csnUtils;
|
|
118
120
|
|
|
119
121
|
return handleManagedAssocSteps;
|
|
120
122
|
|
|
123
|
+
/**
|
|
124
|
+
*
|
|
125
|
+
* @param {object} obj Object with annotations.
|
|
126
|
+
* @param {object} transformer Annotation expression transformers.
|
|
127
|
+
* @param {CSN.Path} path CSN path for locations.
|
|
128
|
+
*/
|
|
129
|
+
function processRefsInAnnotations(obj, transformer, path) {
|
|
130
|
+
Object.keys(obj)
|
|
131
|
+
.filter(pn => pn.startsWith('@') && obj[pn])
|
|
132
|
+
.forEach((anno) => {
|
|
133
|
+
// TODO: Ensure we only do processing here for annotations that have refs, to save time
|
|
134
|
+
const annoBefore = JSON.stringify(obj[anno]);
|
|
135
|
+
transformAnnotationExpression(obj, anno, transformer, path);
|
|
136
|
+
if (obj[anno].ref)
|
|
137
|
+
transformer.ref(obj[anno], 'ref', obj[anno].ref, path.concat(anno));
|
|
138
|
+
const annoAfter = JSON.stringify(obj[anno]);
|
|
139
|
+
if (annoBefore !== annoAfter)
|
|
140
|
+
obj[anno]['='] = true;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
121
144
|
/**
|
|
122
145
|
* Loop over all elements and for all unmanaged associations translate
|
|
123
146
|
* <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
|
|
@@ -133,6 +156,11 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
133
156
|
function handleManagedAssocSteps( artifact, artifactName ) {
|
|
134
157
|
const transformer = getTransformer();
|
|
135
158
|
const inColumnsTransformer = getTransformer(true);
|
|
159
|
+
|
|
160
|
+
if (options.transformation === 'effective')
|
|
161
|
+
processRefsInAnnotations(artifact, transformer, [ 'definitions', artifactName ]);
|
|
162
|
+
|
|
163
|
+
|
|
136
164
|
for (const elemName in artifact.elements) {
|
|
137
165
|
const elem = artifact.elements[elemName];
|
|
138
166
|
// The association is an unmanaged one
|
|
@@ -140,6 +168,10 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
140
168
|
applyTransformationsOnNonDictionary(elem, 'on', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
|
|
141
169
|
else if (elem.value?.stored)
|
|
142
170
|
applyTransformationsOnNonDictionary(elem, 'value', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
|
|
171
|
+
|
|
172
|
+
// TODO: Is this enough? I suppose that these annotations can be in places that are not an element.
|
|
173
|
+
if (options.transformation === 'effective')
|
|
174
|
+
processRefsInAnnotations(elem, transformer, [ 'definitions', artifactName, 'elements', elemName ]);
|
|
143
175
|
}
|
|
144
176
|
|
|
145
177
|
if (artifact.query || artifact.projection) {
|
|
@@ -165,6 +197,10 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
165
197
|
};
|
|
166
198
|
queryTransformers.on = transform;
|
|
167
199
|
}
|
|
200
|
+
|
|
201
|
+
if (options.transformation === 'effective' || options.transformation === 'odata')
|
|
202
|
+
queryTransformers.xpr = transform;
|
|
203
|
+
|
|
168
204
|
applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', queryTransformers, { drillRef: true }, [ 'definitions', artifactName ]);
|
|
169
205
|
}
|
|
170
206
|
|
|
@@ -26,30 +26,29 @@ const { getHelpers } = require('./utils');
|
|
|
26
26
|
* + Is it part of the source side: <path> is turned into <query source>.<path> - a leading $self is stripped-off
|
|
27
27
|
* + Is it something else: Don't touch it, leave as is
|
|
28
28
|
*
|
|
29
|
-
* Given that `assoc` from above has the on-condition assoc.id = id
|
|
29
|
+
* Given that `assoc` from above has the on-condition `assoc.id = id`, we would generate the following:
|
|
30
30
|
* - F.id = E.id
|
|
31
31
|
*
|
|
32
|
-
* The final subselect looks like (select 1 as dummy from E where F.id = E.id and filter = 100)
|
|
32
|
+
* The final subselect looks like `(select 1 as dummy from E where F.id = E.id and filter = 100)`.
|
|
33
33
|
*
|
|
34
34
|
* For a $self backlink:
|
|
35
35
|
* - For $self = <assoc>.<another-assoc>, we do the following for each foreign key of <another-assoc>
|
|
36
36
|
* + <assoc>.<another-assoc>.<fk> -> <assoc.target>.<another-assoc>.<fk>
|
|
37
|
-
* +
|
|
37
|
+
* + Afterward, we get the corresponding key from the source side: <query-source>.<fk>
|
|
38
38
|
* + And turn this into a comparison: <assoc.target>.<another-assoc>.<fk> = <query-source>.<fk>
|
|
39
39
|
*
|
|
40
40
|
* So for the sample above, given an on-condition like $self = assoc.backToE, we would generate:
|
|
41
41
|
* - F.backToE.id = E.id
|
|
42
42
|
*
|
|
43
|
-
* The final subselect looks like (select 1 as dummy from E where F.backToE.id = E.id and filter = 100)
|
|
43
|
+
* The final subselect looks like `(select 1 as dummy from E where F.backToE.id = E.id and filter = 100)`.
|
|
44
44
|
*
|
|
45
45
|
* @param {CSN.Model} csn
|
|
46
46
|
* @param {CSN.Options} options
|
|
47
|
-
* @param {
|
|
48
|
-
* @param {
|
|
49
|
-
* @param {Function} initDefinition
|
|
50
|
-
* @param {Function} dropDefinitionCache
|
|
47
|
+
* @param {object} messageFunctions
|
|
48
|
+
* @param {object} csnUtils
|
|
51
49
|
*/
|
|
52
|
-
function handleExists( csn, options,
|
|
50
|
+
function handleExists( csn, options, messageFunctions, csnUtils ) {
|
|
51
|
+
const { error } = messageFunctions;
|
|
53
52
|
const {
|
|
54
53
|
getBase,
|
|
55
54
|
firstLinkIsEntityOrQuerySource,
|
|
@@ -57,17 +56,12 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
57
56
|
translateManagedAssocToWhere,
|
|
58
57
|
getQuerySources,
|
|
59
58
|
translateUnmanagedAssocToWhere,
|
|
60
|
-
} = getHelpers(csn, inspectRef, error);
|
|
59
|
+
} = getHelpers(csn, csnUtils.inspectRef, error);
|
|
60
|
+
|
|
61
61
|
const generatedExists = new WeakMap();
|
|
62
62
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
dropDefinitionCache(artifact);
|
|
66
|
-
if (artifact.projection) // do the same hack we do for the other stuff...
|
|
67
|
-
artifact.query = { SELECT: artifact.projection };
|
|
68
|
-
|
|
69
|
-
if (artifact.query) {
|
|
70
|
-
forAllQueries(artifact.query, function handleExistsQuery(query, path) {
|
|
63
|
+
if (artifact.query || artifact.projection) {
|
|
64
|
+
forAllQueries(artifact.query || { SELECT: artifact.projection }, function handleExistsQuery(query, path) {
|
|
71
65
|
if (!generatedExists.has(query)) {
|
|
72
66
|
const toProcess = []; // Collect all expressions we need to process here
|
|
73
67
|
if (query.SELECT?.where?.length > 1)
|
|
@@ -90,8 +84,8 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
90
84
|
while (toProcess.length > 0) {
|
|
91
85
|
const [ queryPath, exprPath ] = toProcess.pop();
|
|
92
86
|
// Re-init caches for this artifact
|
|
93
|
-
dropDefinitionCache(artifact);
|
|
94
|
-
initDefinition(artifact);
|
|
87
|
+
csnUtils.dropDefinitionCache(artifact);
|
|
88
|
+
csnUtils.initDefinition(artifact);
|
|
95
89
|
// leftovers can happen with nested exists - we then need to drill down into the created SELECT
|
|
96
90
|
// to check for further exists
|
|
97
91
|
const { result, leftovers } = processExists(queryPath, exprPath);
|
|
@@ -100,16 +94,10 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
100
94
|
toProcess.push(...leftovers); // any leftovers - schedule for further processing
|
|
101
95
|
}
|
|
102
96
|
// Make sure we leave csnRefs usable
|
|
103
|
-
dropDefinitionCache(artifact);
|
|
104
|
-
initDefinition(artifact);
|
|
97
|
+
csnUtils.dropDefinitionCache(artifact);
|
|
98
|
+
csnUtils.initDefinition(artifact);
|
|
105
99
|
}
|
|
106
|
-
}, [ 'definitions', artifactName, 'query' ]);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (artifact.projection) { // undo our hack
|
|
110
|
-
artifact.projection = artifact.query.SELECT;
|
|
111
|
-
|
|
112
|
-
delete artifact.query;
|
|
100
|
+
}, [ 'definitions', artifactName, artifact.projection ? 'projection' : 'query' ]);
|
|
113
101
|
}
|
|
114
102
|
});
|
|
115
103
|
|
|
@@ -168,7 +156,7 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
168
156
|
}
|
|
169
157
|
}
|
|
170
158
|
const stack = [ [ null, startAssoc, startRest, startIndex ] ];
|
|
171
|
-
const { links } = inspectRef(path);
|
|
159
|
+
const { links } = csnUtils.inspectRef(path);
|
|
172
160
|
while (stack.length > 0) {
|
|
173
161
|
// previous: to nest "up" if the previous assoc did not originally have a filter
|
|
174
162
|
// assoc: the assoc path step
|
|
@@ -258,8 +246,9 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
258
246
|
const newExpr = [];
|
|
259
247
|
const query = walkCsnPath(csn, queryPath);
|
|
260
248
|
const expr = walkCsnPath(csn, exprPath);
|
|
261
|
-
const
|
|
262
|
-
const
|
|
249
|
+
const select = query.projection ?? query.SELECT;
|
|
250
|
+
const queryBase = select?.from?.ref ? (select.from.as || select.from.ref) : null;
|
|
251
|
+
const sources = getQuerySources(select);
|
|
263
252
|
|
|
264
253
|
for (let i = 0; i < expr.length; i++) {
|
|
265
254
|
if (i < expr.length - 1 && expr[i] === 'exists' && expr[i + 1].ref) {
|
|
@@ -430,7 +430,7 @@ function getHelpers( csn, inspectRef, error ) {
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
/**
|
|
433
|
-
* Use
|
|
433
|
+
* Use cached _links and _art or calculate via inspectRef
|
|
434
434
|
* @param {object} obj
|
|
435
435
|
* @param {CSN.Path} objPath
|
|
436
436
|
* @returns {object}
|
|
@@ -125,7 +125,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
|
|
|
125
125
|
|
|
126
126
|
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
127
127
|
// All elements must have a type for this to work
|
|
128
|
-
if (!member.$ignore && !member.kind && !member.type) {
|
|
128
|
+
if (!member.$ignore && !member.kind && !member.type && !member.elements) { // .items? Probably resolved at this point
|
|
129
129
|
error(null, path, { anno: '@cds.persistence.table' },
|
|
130
130
|
'Expecting element to have a type if view is annotated with $(ANNO)');
|
|
131
131
|
}
|
|
@@ -38,18 +38,16 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
38
38
|
columns: (parent, name, columns, path) => {
|
|
39
39
|
const artifact = csn.definitions[path[1]];
|
|
40
40
|
csnUtils.initDefinition(artifact); // potentially not initialized, yet
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
|
|
52
|
-
}
|
|
41
|
+
const root = csnUtils.get$combined({ SELECT: parent });
|
|
42
|
+
// TODO: replace with the correct options.transformation?
|
|
43
|
+
// Do not expand the * in OData for a moment, not to introduce changes
|
|
44
|
+
// while the OData CSN is still official
|
|
45
|
+
const isComplexQuery = parent.from.join !== undefined;
|
|
46
|
+
if (!options.toOdata)
|
|
47
|
+
parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
|
|
48
|
+
// FIXME(v6): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
|
|
49
|
+
// if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
|
|
50
|
+
parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
|
|
53
51
|
},
|
|
54
52
|
groupBy: (parent, name, groupBy, path) => {
|
|
55
53
|
parent.groupBy = expand(groupBy, path.concat('groupBy'));
|
|
@@ -86,24 +84,22 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
86
84
|
// (which is the thing inside SET/SELECT)
|
|
87
85
|
// We can directly use SELECT here, as only projections and SELECT can have .columns
|
|
88
86
|
const root = csnUtils.get$combined({ SELECT: parent });
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
// Make root look like normal .elements - we never cared about conflict afaik anyway
|
|
88
|
+
Object.keys(root).forEach((key) => {
|
|
89
|
+
root[key] = root[key][0].element;
|
|
90
|
+
});
|
|
91
|
+
const rewritten = rewrite(root, parent.columns, parent.excluding);
|
|
92
|
+
/*
|
|
93
|
+
* Do not remove unexpandable many columns in OData
|
|
94
|
+
*/
|
|
95
|
+
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
96
|
+
markAsToDummify(artifact, path[1]);
|
|
97
|
+
rewritten.toMany.forEach(({ art }) => {
|
|
98
|
+
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)');
|
|
93
99
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*/
|
|
98
|
-
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
99
|
-
markAsToDummify(artifact, path[1]);
|
|
100
|
-
rewritten.toMany.forEach(({ art }) => {
|
|
101
|
-
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)');
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
parent.columns = rewritten.columns;
|
|
106
|
-
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
parent.columns = rewritten.columns;
|
|
107
103
|
}
|
|
108
104
|
},
|
|
109
105
|
});
|
|
@@ -280,11 +276,13 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
280
276
|
const allToMany = [];
|
|
281
277
|
const newThing = [];
|
|
282
278
|
const containsExpandInline = columns.some(col => col.expand || col.inline);
|
|
283
|
-
if (containsExpandInline)
|
|
284
|
-
columns = replaceStar(root, columns, excluding);
|
|
285
|
-
else
|
|
279
|
+
if (!containsExpandInline)
|
|
286
280
|
return { columns, toMany: [] };
|
|
287
281
|
|
|
282
|
+
// Replace stars - needs to happen before resolving .expand/.inline since the
|
|
283
|
+
// .expand/.inline first path step affects the root *
|
|
284
|
+
columns = replaceStar(root, columns, excluding);
|
|
285
|
+
|
|
288
286
|
for (const col of columns) {
|
|
289
287
|
if (col.expand || col.inline) {
|
|
290
288
|
const { expanded, toManys } = expandInline(root, col, col.ref || [], col.expand ? [ dbName(col) ] : []);
|
|
@@ -364,7 +362,10 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
364
362
|
}
|
|
365
363
|
else if (current.on || current.cast?.on) {
|
|
366
364
|
rewriteOn(current, [ currentAlias.slice(0, -1).join(pathDelimiter) ]);
|
|
367
|
-
|
|
365
|
+
const expandedCol = Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } );
|
|
366
|
+
if (currentRef.length)
|
|
367
|
+
expandedCol.ref = currentRef;
|
|
368
|
+
expanded.push(expandedCol);
|
|
368
369
|
}
|
|
369
370
|
else if (current.val !== undefined || current.func !== undefined) {
|
|
370
371
|
expanded.push(Object.assign(current, { as: currentAlias.join(pathDelimiter) }));
|
|
@@ -440,7 +441,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
440
441
|
function rewriteOnCondition( on, currentRef, stack ) {
|
|
441
442
|
for (let i = 0; i < on.length; i++) {
|
|
442
443
|
const part = on[i];
|
|
443
|
-
if (part.ref && part
|
|
444
|
+
if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self' && part.$scope !== '$projection') {
|
|
444
445
|
part.ref = currentRef[0] ? [ currentRef[0], ...part.ref ] : part.ref;
|
|
445
446
|
on[i] = part;
|
|
446
447
|
stack.push([ part, part.ref ]);
|
|
@@ -461,7 +462,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
461
462
|
function rewriteSingleExpressionArray( expressionArray, currentRef, stack ) {
|
|
462
463
|
for (let i = 0; i < expressionArray.length; i++) {
|
|
463
464
|
const part = expressionArray[i];
|
|
464
|
-
if (part.ref) {
|
|
465
|
+
if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self') {
|
|
465
466
|
part.ref = currentRef.concat(part.ref);
|
|
466
467
|
expressionArray[i] = part;
|
|
467
468
|
stack.push([ part, part.ref ]);
|
|
@@ -657,7 +658,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
657
658
|
obj.ref = [ root.$env, ...obj.ref ];
|
|
658
659
|
|
|
659
660
|
if (iterateOptions.keepKeysOrigin) {
|
|
660
|
-
setProp(obj, '$originalKeyRef',
|
|
661
|
+
setProp(obj, '$originalKeyRef', root);
|
|
661
662
|
setProp(obj, '$path', root.$path);
|
|
662
663
|
}
|
|
663
664
|
|