@sap/cds-compiler 4.0.2 → 4.2.2
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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
|
@@ -14,14 +14,16 @@ const {
|
|
|
14
14
|
setArtifactLink,
|
|
15
15
|
linkToOrigin,
|
|
16
16
|
copyExpr,
|
|
17
|
+
forEachUserArtifact,
|
|
18
|
+
forEachQueryExpr,
|
|
17
19
|
traverseQueryPost,
|
|
18
20
|
traverseQueryExtra,
|
|
19
21
|
setExpandStatus,
|
|
20
|
-
traverseExpr,
|
|
21
22
|
} = require('./utils');
|
|
23
|
+
const { CsnLocation } = require('./classes');
|
|
22
24
|
|
|
23
|
-
const $location = Symbol.for('cds.$location');
|
|
24
|
-
const $inferred = Symbol.for('cds.$inferred');
|
|
25
|
+
const $location = Symbol.for( 'cds.$location' );
|
|
26
|
+
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
25
27
|
|
|
26
28
|
// Export function of this file.
|
|
27
29
|
function tweakAssocs( model ) {
|
|
@@ -30,9 +32,11 @@ function tweakAssocs( model ) {
|
|
|
30
32
|
info, warning, error,
|
|
31
33
|
} = model.$messageFunctions;
|
|
32
34
|
const {
|
|
35
|
+
traverseExpr,
|
|
36
|
+
checkExpr,
|
|
37
|
+
checkOnCondition,
|
|
33
38
|
effectiveType,
|
|
34
39
|
getOrigin,
|
|
35
|
-
navigationEnv,
|
|
36
40
|
} = model.$functions;
|
|
37
41
|
|
|
38
42
|
// Phase 5: rewrite associations
|
|
@@ -40,6 +44,13 @@ function tweakAssocs( model ) {
|
|
|
40
44
|
// Think hard whether an on condition rewrite can lead to a new cyclic
|
|
41
45
|
// dependency. If so, we need other messages anyway. TODO: probably do
|
|
42
46
|
// another cyclic check with testMode.js
|
|
47
|
+
forEachUserArtifact( model, 'definitions', function check( art ) {
|
|
48
|
+
checkOnCondition( art.on, (art.kind !== 'mixin' ? 'on' : 'mixin-on'), art );
|
|
49
|
+
checkExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
|
|
50
|
+
|
|
51
|
+
if (art.kind === 'select')
|
|
52
|
+
forEachQueryExpr( art, checkExpr );
|
|
53
|
+
} );
|
|
43
54
|
return;
|
|
44
55
|
|
|
45
56
|
|
|
@@ -107,7 +118,7 @@ function tweakAssocs( model ) {
|
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
function rewriteAssociationCheck( element ) {
|
|
110
|
-
const elem = element.items || element; // TODO
|
|
121
|
+
const elem = element.items || element; // TODO v5: nested items
|
|
111
122
|
if (elem.elements)
|
|
112
123
|
forEachGeneric( elem, 'elements', rewriteAssociationCheck );
|
|
113
124
|
if (!elem.target)
|
|
@@ -166,13 +177,9 @@ function tweakAssocs( model ) {
|
|
|
166
177
|
}
|
|
167
178
|
if (names.length) {
|
|
168
179
|
const loc = otherAssoc.foreignKeys[$location] || dictLocation( otherAssoc.foreignKeys );
|
|
169
|
-
const location = loc && (!loc.endCol
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
col: loc.endCol - 1,
|
|
173
|
-
endLine: loc.endLine,
|
|
174
|
-
endCol: loc.endCol,
|
|
175
|
-
} );
|
|
180
|
+
const location = loc && (!loc.endCol
|
|
181
|
+
? loc
|
|
182
|
+
: new CsnLocation( loc.file, loc.endLine, loc.endCol - 1, loc.endLine, loc.endCol ));
|
|
176
183
|
const baseAssoc = assocWithExplicitSpec( thisAssoc );
|
|
177
184
|
if (inferredForeignKeys( baseAssoc.foreignKeys )) { // still inferred = via target keys
|
|
178
185
|
error( 'rewrite-key-not-covered-implicit', [ location, otherAssoc ],
|
|
@@ -194,7 +201,7 @@ function tweakAssocs( model ) {
|
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
function assocWithExplicitSpec( assoc ) {
|
|
197
|
-
while (assoc.foreignKeys && inferredForeignKeys( assoc.foreignKeys, 'keys') ||
|
|
204
|
+
while (assoc.foreignKeys && inferredForeignKeys( assoc.foreignKeys, 'keys' ) ||
|
|
198
205
|
assoc.on && assoc.on.$inferred)
|
|
199
206
|
assoc = getOrigin( assoc );
|
|
200
207
|
return assoc;
|
|
@@ -257,7 +264,7 @@ function tweakAssocs( model ) {
|
|
|
257
264
|
fk.$inferred = 'rewrite'; // Override existing value; TODO: other $inferred value?
|
|
258
265
|
// TODO: re-check for case that foreign key is managed association
|
|
259
266
|
if (orig._effectiveType !== undefined)
|
|
260
|
-
setLink( fk, '_effectiveType', orig._effectiveType);
|
|
267
|
+
setLink( fk, '_effectiveType', orig._effectiveType );
|
|
261
268
|
const te = copyExpr( orig.targetElement, elem.location );
|
|
262
269
|
if (elem._redirected) {
|
|
263
270
|
const i = te.path[0]; // TODO: or also follow path like for ON?
|
|
@@ -266,7 +273,7 @@ function tweakAssocs( model ) {
|
|
|
266
273
|
setArtifactLink( te, state );
|
|
267
274
|
}
|
|
268
275
|
fk.targetElement = te;
|
|
269
|
-
});
|
|
276
|
+
} );
|
|
270
277
|
if (elem.foreignKeys) // Possibly no fk was set
|
|
271
278
|
elem.foreignKeys[$inferred] = 'rewrite';
|
|
272
279
|
}
|
|
@@ -281,14 +288,14 @@ function tweakAssocs( model ) {
|
|
|
281
288
|
// same (TODO later: set status whether rewrite changes anything),
|
|
282
289
|
// especially problematic are refs starting with $self:
|
|
283
290
|
setExpandStatus( elem, 'target' );
|
|
284
|
-
if (elem._parent
|
|
291
|
+
if (elem._parent?.kind === 'element') {
|
|
285
292
|
// managed association as sub element not supported yet
|
|
286
293
|
error( null, [ elem.location, elem ], {},
|
|
287
294
|
// eslint-disable-next-line max-len
|
|
288
295
|
'Rewriting the ON-condition of unmanaged association in sub element is not supported' );
|
|
289
296
|
return;
|
|
290
297
|
}
|
|
291
|
-
const nav = (elem._main
|
|
298
|
+
const nav = (elem._main?.query && elem.value)
|
|
292
299
|
? pathNavigation( elem.value ) // redirected source elem or mixin
|
|
293
300
|
: { navigation: assoc }; // redirected user-provided
|
|
294
301
|
const cond = copyExpr( assoc.on,
|
|
@@ -340,15 +347,13 @@ function tweakAssocs( model ) {
|
|
|
340
347
|
if (tableAlias) { // from ON cond of element in source ref/d by table alias
|
|
341
348
|
const source = tableAlias._origin;
|
|
342
349
|
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
343
|
-
// console.log( info(null, [assoc.name.location, assoc],
|
|
344
|
-
// { names: expr.path.map(i=>i.id), art: root }, 'TA').toString())
|
|
350
|
+
// console.log( info(null, [ assoc.name.location, assoc ],
|
|
351
|
+
// { names: expr.path.map(i => i.id), art: root }, 'TA').toString());
|
|
345
352
|
if (!root || root._main !== source)
|
|
346
353
|
return; // not $self or source element
|
|
347
354
|
if (expr.scope === 'param' || root.kind === '$parameters')
|
|
348
355
|
return; // are not allowed anyway - there was an error before
|
|
349
|
-
const item = expr.path
|
|
350
|
-
// console.log('YE', assoc.name, item, root.name, expr.path)
|
|
351
|
-
const elem = navProjection( item && tableAlias.elements[item.id], assoc );
|
|
356
|
+
const { item, elem } = firstProjectionForPath( expr.path, tableAlias, assoc );
|
|
352
357
|
rewritePath( expr, item, assoc, elem, assoc.value.location );
|
|
353
358
|
}
|
|
354
359
|
else if (assoc._main.query) { // from ON cond of mixin element in query
|
|
@@ -379,7 +384,7 @@ function tweakAssocs( model ) {
|
|
|
379
384
|
return; // just $self
|
|
380
385
|
// corresponding elem in including structure
|
|
381
386
|
const elem = (assoc._main.items || assoc._main).elements[item.id];
|
|
382
|
-
if (!(Array.isArray(elem) ||
|
|
387
|
+
if (!(Array.isArray( elem ) || // no msg for redefs
|
|
383
388
|
elem === item._artifact || // redirection for explicit def
|
|
384
389
|
elem._origin === item._artifact)) {
|
|
385
390
|
const art = assoc._origin;
|
|
@@ -392,7 +397,7 @@ function tweakAssocs( model ) {
|
|
|
392
397
|
element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
|
|
393
398
|
} );
|
|
394
399
|
}
|
|
395
|
-
rewritePath( expr, item, assoc, (Array.isArray(elem) ? false : elem), null );
|
|
400
|
+
rewritePath( expr, item, assoc, (Array.isArray( elem ) ? false : elem), null );
|
|
396
401
|
}
|
|
397
402
|
}
|
|
398
403
|
|
|
@@ -401,13 +406,12 @@ function tweakAssocs( model ) {
|
|
|
401
406
|
let root = path[0];
|
|
402
407
|
if (!elem) {
|
|
403
408
|
if (location) {
|
|
404
|
-
error( 'rewrite-not-projected', [ location, assoc ],
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
} );
|
|
409
|
+
error( 'rewrite-not-projected', [ location, assoc ], {
|
|
410
|
+
name: assoc.name.id, art: item._artifact, elemref: { ref: path },
|
|
411
|
+
}, {
|
|
412
|
+
std: 'Projected association $(NAME) uses non-projected element $(ELEMREF)',
|
|
413
|
+
element: 'Projected association $(NAME) uses non-projected element $(MEMBER) of $(ART)',
|
|
414
|
+
} );
|
|
411
415
|
}
|
|
412
416
|
delete root._navigation;
|
|
413
417
|
setArtifactLink( root, elem );
|
|
@@ -415,9 +419,15 @@ function tweakAssocs( model ) {
|
|
|
415
419
|
return;
|
|
416
420
|
}
|
|
417
421
|
if (item !== root) {
|
|
422
|
+
// e.g. mixin ON-condition: Base.foo -> $self.foo or multi-path projection,
|
|
423
|
+
// $projection -> $self
|
|
418
424
|
root.id = '$self';
|
|
419
425
|
setLink( root, '_navigation', assoc._parent.$tableAliases.$self );
|
|
420
426
|
setArtifactLink( root, assoc._parent );
|
|
427
|
+
if (item) {
|
|
428
|
+
const i = path.indexOf(item);
|
|
429
|
+
ref.path = [ root, ...path.slice( i, path.length ) ];
|
|
430
|
+
}
|
|
421
431
|
}
|
|
422
432
|
else if (elem.name.id.charAt(0) === '$') {
|
|
423
433
|
root = { id: '$self', location: item.location };
|
|
@@ -463,14 +473,13 @@ function tweakAssocs( model ) {
|
|
|
463
473
|
// consider intermediate "preferred" elements - not just `assoc`,
|
|
464
474
|
// but its origins, too.
|
|
465
475
|
const proj = navProjection( alias.elements[name], assoc );
|
|
466
|
-
name = proj
|
|
476
|
+
name = proj?.name?.id;
|
|
467
477
|
if (!name) {
|
|
468
478
|
if (!forKeys)
|
|
469
479
|
break;
|
|
470
480
|
setArtifactLink( item, null );
|
|
471
481
|
const culprit = elem.target && !elem.target.$inferred && elem.target ||
|
|
472
|
-
|
|
473
|
-
elem.value.path[elem.value.path.length - 1]) ||
|
|
482
|
+
elem.value?.path?.[elem.value.path.length - 1] ||
|
|
474
483
|
elem;
|
|
475
484
|
// TODO: probably better to collect the non-projected foreign keys
|
|
476
485
|
// and have one message for all
|
|
@@ -482,9 +491,13 @@ function tweakAssocs( model ) {
|
|
|
482
491
|
item.id = name;
|
|
483
492
|
}
|
|
484
493
|
}
|
|
485
|
-
|
|
494
|
+
let env = name && elem._effectiveType; // should have been computed
|
|
495
|
+
// refs in ON cannot navigate along `items`, no need to consider `items` here
|
|
496
|
+
if (env?.target)
|
|
497
|
+
env = env.target._artifact?._effectiveType;
|
|
486
498
|
elem = setArtifactLink( item, env?.elements?.[name] );
|
|
487
|
-
|
|
499
|
+
|
|
500
|
+
if (elem && !Array.isArray( elem ))
|
|
488
501
|
return elem;
|
|
489
502
|
// TODO: better (extra message), TODO: do it
|
|
490
503
|
error( 'query-undefined-element', [ item.location, assoc ],
|
|
@@ -505,20 +518,71 @@ function navProjection( navigation, preferred ) {
|
|
|
505
518
|
: navigation._projections[0] || null;
|
|
506
519
|
}
|
|
507
520
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* For a path `a.b.c.d`, return a projection for the first path item that is projected.
|
|
524
|
+
* For example, if a query has multiple projections such as `a.b, a, a.b.c`, the
|
|
525
|
+
* _first_ possible projection will be used and the caller can rewrite `a.b.c.d` to `b.c.d`.
|
|
526
|
+
* This avoids that `extend`s affect the ON-condition.
|
|
527
|
+
*
|
|
528
|
+
* The returned object `ret` has `ret.item`, which is the path item that is projected.
|
|
529
|
+
* `ret.elem` is the element projection.
|
|
530
|
+
*
|
|
531
|
+
* @param {any[]} path
|
|
532
|
+
* @param {object} tableAlias
|
|
533
|
+
* @param {object} assoc Preferred association that should be used if projected.
|
|
534
|
+
* @return {{elem: object, item: object}|null}
|
|
535
|
+
*/
|
|
536
|
+
function firstProjectionForPath( path, tableAlias, assoc ) {
|
|
537
|
+
const viaSelf = (path[0]._navigation || path[0]._artifact).kind === '$self';
|
|
538
|
+
const root = viaSelf ? 1 : 0;
|
|
539
|
+
if (root >= path.length) // e.g. just `$self` path item
|
|
540
|
+
return { item: undefined, elem: {} };
|
|
541
|
+
|
|
542
|
+
// We want to use the _first_ valid projection that is written by the user (if the preferred
|
|
543
|
+
// `assoc` is not directly projected). To achieve that, look into the table alias' elements.
|
|
544
|
+
const selectedElements = Object.values(tableAlias._parent.elements);
|
|
545
|
+
const proj = [];
|
|
546
|
+
let navItem = tableAlias;
|
|
547
|
+
for (const item of path.slice(root)) {
|
|
548
|
+
navItem = item?.id && navItem.elements?.[item.id];
|
|
549
|
+
if (!navItem) {
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
else if (navItem._projections) {
|
|
553
|
+
const elem = navProjection( navItem, assoc );
|
|
554
|
+
if (elem && elem === assoc) {
|
|
555
|
+
// in case the specified association is found, _always_ use it.
|
|
556
|
+
return { item, elem };
|
|
557
|
+
}
|
|
558
|
+
else if (elem) {
|
|
559
|
+
const index = selectedElements.indexOf(elem);
|
|
560
|
+
proj.push({ item, elem, index });
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return (proj.length === 0)
|
|
566
|
+
? { item: path[root], elem: null }
|
|
567
|
+
: proj.reduce( (acc, curr) => (acc.index > curr.index ? curr : acc), proj[0] ); // first
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Return condensed info about reference in select item
|
|
572
|
+
* - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
|
|
573
|
+
* - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
|
|
574
|
+
* - mixinElem -> { navigation: mixinElement, item: path[0] }
|
|
575
|
+
* - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
|
|
576
|
+
* - $self -> { item: undefined, tableAlias: $self }
|
|
577
|
+
* - $parameters.P, :P -> {}
|
|
578
|
+
* - $now, current_date -> {}
|
|
579
|
+
* - undef, redef -> {}
|
|
580
|
+
* With 'navigation': store that navigation._artifact is projected
|
|
581
|
+
* With 'navigation': rewrite its ON condition
|
|
582
|
+
* With navigation: Do KEY propagation
|
|
583
|
+
*
|
|
584
|
+
* TODO: re-think this function, copied in populate.js and tweak-assocs.js
|
|
585
|
+
*/
|
|
522
586
|
function pathNavigation( ref ) {
|
|
523
587
|
// currently, indirectly projectable elements are not included - we might
|
|
524
588
|
// keep it this way! If we want them to be included - be aware: cycles
|