@sap/cds-compiler 3.6.2 → 3.8.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 +109 -1
- package/README.md +3 -0
- package/bin/cdsc.js +12 -5
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +35 -2
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +63 -23
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +15 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +120 -34
- package/lib/base/messages.js +51 -27
- package/lib/base/model.js +4 -2
- package/lib/base/shuffle.js +2 -1
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/elements.js +29 -1
- package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/onConditions.js +15 -9
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +5 -1
- package/lib/checks/validator.js +7 -3
- package/lib/compiler/assert-consistency.js +42 -26
- package/lib/compiler/base.js +50 -4
- package/lib/compiler/builtins.js +17 -8
- package/lib/compiler/checks.js +241 -246
- package/lib/compiler/define.js +113 -146
- package/lib/compiler/extend.js +889 -383
- package/lib/compiler/finalize-parse-cdl.js +5 -58
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +7 -8
- package/lib/compiler/populate.js +297 -293
- package/lib/compiler/propagator.js +27 -18
- package/lib/compiler/resolve.js +146 -463
- package/lib/compiler/shared.js +36 -79
- package/lib/compiler/tweak-assocs.js +30 -28
- package/lib/compiler/utils.js +31 -5
- package/lib/edm/annotations/genericTranslation.js +131 -59
- package/lib/edm/annotations/preprocessAnnotations.js +3 -0
- package/lib/edm/csn2edm.js +22 -5
- package/lib/edm/edm.js +6 -4
- package/lib/edm/edmAnnoPreprocessor.js +1 -0
- package/lib/edm/edmPreprocessor.js +42 -26
- package/lib/gen/Dictionary.json +38 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageLexer.js +1 -1
- package/lib/gen/languageParser.js +4828 -4472
- package/lib/inspect/inspectPropagation.js +20 -34
- package/lib/json/from-csn.js +140 -44
- package/lib/json/to-csn.js +114 -122
- package/lib/language/errorStrategy.js +2 -0
- package/lib/language/genericAntlrParser.js +156 -36
- package/lib/language/language.g4 +100 -58
- package/lib/language/textUtils.js +13 -0
- package/lib/main.d.ts +43 -3
- package/lib/main.js +4 -2
- package/lib/model/csnRefs.js +15 -3
- package/lib/model/csnUtils.js +12 -74
- package/lib/model/revealInternalProperties.js +4 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +3 -0
- package/lib/render/manageConstraints.js +5 -2
- package/lib/render/toCdl.js +216 -104
- package/lib/render/toHdbcds.js +2 -9
- package/lib/render/toRename.js +14 -51
- package/lib/render/toSql.js +4 -3
- package/lib/render/utils/common.js +9 -5
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/expansion.js +2 -0
- package/lib/transform/db/flattening.js +37 -36
- package/lib/transform/db/rewriteCalculatedElements.js +600 -0
- package/lib/transform/db/transformExists.js +4 -0
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +58 -41
- package/lib/transform/odata/typesExposure.js +50 -15
- package/lib/transform/parseExpr.js +16 -8
- package/lib/transform/transformUtilsNew.js +42 -14
- package/lib/transform/translateAssocsToJoins.js +60 -37
- package/lib/transform/universalCsn/coreComputed.js +15 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +2 -1
package/lib/compiler/resolve.js
CHANGED
|
@@ -46,30 +46,24 @@ const {
|
|
|
46
46
|
} = require('../base/model');
|
|
47
47
|
const { dictAdd } = require('../base/dictionaries');
|
|
48
48
|
const { dictLocation } = require('../base/location');
|
|
49
|
-
const {
|
|
49
|
+
const { weakLocation } = require('../base/messages');
|
|
50
50
|
const { combinedLocation } = require('../base/location');
|
|
51
51
|
const { typeParameters } = require('./builtins');
|
|
52
52
|
|
|
53
|
-
const { kindProperties } = require('./base');
|
|
54
53
|
const {
|
|
54
|
+
pushLink,
|
|
55
55
|
setLink,
|
|
56
56
|
setArtifactLink,
|
|
57
|
-
annotationHasEllipsis,
|
|
58
|
-
pathName,
|
|
59
|
-
linkToOrigin,
|
|
60
57
|
setMemberParent,
|
|
61
58
|
withAssociation,
|
|
62
|
-
storeExtension,
|
|
63
59
|
dependsOn,
|
|
64
60
|
dependsOnSilent,
|
|
65
|
-
setExpandStatusAnnotate,
|
|
66
61
|
testExpr,
|
|
67
62
|
targetMaxNotOne,
|
|
68
63
|
traverseQueryPost,
|
|
69
64
|
} = require('./utils');
|
|
70
65
|
|
|
71
66
|
const detectCycles = require('./cycle-detector');
|
|
72
|
-
const { CompilerAssertion } = require('../base/error');
|
|
73
67
|
|
|
74
68
|
const $location = Symbol.for('cds.$location');
|
|
75
69
|
|
|
@@ -87,19 +81,11 @@ function resolve( model ) {
|
|
|
87
81
|
} = model.$messageFunctions;
|
|
88
82
|
const {
|
|
89
83
|
resolvePath,
|
|
90
|
-
checkAnnotate,
|
|
91
|
-
initAnnotations,
|
|
92
|
-
copyAnnotationsForExtensions,
|
|
93
|
-
attachAndEmitValidNames,
|
|
94
84
|
lateExtensions,
|
|
95
|
-
applyTypeExtensions,
|
|
96
85
|
effectiveType,
|
|
97
|
-
|
|
86
|
+
getOrigin,
|
|
98
87
|
resolveType,
|
|
99
88
|
resolveTypeArgumentsUnchecked,
|
|
100
|
-
populateQuery,
|
|
101
|
-
layeredAssignments,
|
|
102
|
-
assignmentsOfHighestLayers,
|
|
103
89
|
} = model.$functions;
|
|
104
90
|
const { environment } = model.$volatileFunctions;
|
|
105
91
|
Object.assign( model.$functions, {
|
|
@@ -117,7 +103,11 @@ function resolve( model ) {
|
|
|
117
103
|
// addImplicitForeignKeys() in populate.js, as we might need to know the
|
|
118
104
|
// foreign keys in populate.js (foreign key access w/o JOINs).
|
|
119
105
|
|
|
120
|
-
// Phase 3: calculate keys along simple queries in collected views:
|
|
106
|
+
// Phase 2+3: calculate keys along simple queries in collected views:
|
|
107
|
+
model._entities = Object.values( model.definitions )
|
|
108
|
+
.filter( art => art.$effectiveSeqNo )
|
|
109
|
+
.sort( (x, y) => x.$effectiveSeqNo - y.$effectiveSeqNo );
|
|
110
|
+
model._entities.forEach( setNavigationProjections );
|
|
121
111
|
model._entities.forEach( propagateKeyProps );
|
|
122
112
|
// While most dependencies leading have been added at this point, new
|
|
123
113
|
// cycles could be added later (e.g. via assocs in where conditions),
|
|
@@ -126,14 +116,9 @@ function resolve( model ) {
|
|
|
126
116
|
// Phase 4: resolve all artifacts:
|
|
127
117
|
forEachDefinition( model, resolveRefs );
|
|
128
118
|
forEachGeneric( model, 'vocabularies', resolveRefs );
|
|
129
|
-
// for
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// Phase 6: apply ANNOTATE on auto-exposed entities and unknown artifacts:
|
|
133
|
-
lateExtensions( annotateMembers );
|
|
134
|
-
if (model.extensions)
|
|
135
|
-
model.extensions.map( annotateUnknown );
|
|
136
|
-
// Phase 7: report cyclic dependencies:
|
|
119
|
+
// create “super” ANNOTATE statements for annotations on unknown artifacts:
|
|
120
|
+
lateExtensions();
|
|
121
|
+
// report cyclic dependencies:
|
|
137
122
|
detectCycles( model.definitions, ( user, art, location ) => {
|
|
138
123
|
if (location) {
|
|
139
124
|
error( 'ref-cyclic', [ location, user ], { art }, {
|
|
@@ -146,9 +131,38 @@ function resolve( model ) {
|
|
|
146
131
|
}
|
|
147
132
|
|
|
148
133
|
//--------------------------------------------------------------------------
|
|
149
|
-
// Phase 3: calculate propagated KEYs
|
|
134
|
+
// Phase 2+3: calculate propagated KEYs
|
|
150
135
|
//--------------------------------------------------------------------------
|
|
151
136
|
|
|
137
|
+
function setNavigationProjections( view ) {
|
|
138
|
+
if (!view.$queries)
|
|
139
|
+
return;
|
|
140
|
+
for (const query of view.$queries) {
|
|
141
|
+
forEachGeneric( query, 'elements', ( elem ) => {
|
|
142
|
+
if (!elem._origin || elem.expand || !elem.value)
|
|
143
|
+
return;
|
|
144
|
+
// TODO: what about elements where _origin is set without value?
|
|
145
|
+
// TODO: or should we push elems with `expand` sibling to extra list for
|
|
146
|
+
// better messages? (Whatever that means exaclty.)
|
|
147
|
+
const nav = pathNavigation( elem.value );
|
|
148
|
+
const { path } = elem.value;
|
|
149
|
+
const item = path[path.length - 1];
|
|
150
|
+
if (nav.navigation && nav.item === item) {
|
|
151
|
+
// sourceElem, alias.sourceElem, mixin:
|
|
152
|
+
// redirectImplicitly( elem, origin );
|
|
153
|
+
pushLink( nav.navigation, '_projections', elem );
|
|
154
|
+
}
|
|
155
|
+
else if (elem._pathHead?.kind === '$inline' && path.length === 1) {
|
|
156
|
+
const hpath = elem._pathHead.value?.path;
|
|
157
|
+
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
158
|
+
// Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
|
|
159
|
+
if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
|
|
160
|
+
pushLink( head.elements[item.id], '_projections', elem );
|
|
161
|
+
}
|
|
162
|
+
} );
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
152
166
|
function propagateKeyProps( view ) {
|
|
153
167
|
// Second argument true ensure that `key` is only propagated along simple
|
|
154
168
|
// view, i.e. ref or subquery in FROM, not UNION or JOIN.
|
|
@@ -293,16 +307,17 @@ function resolve( model ) {
|
|
|
293
307
|
function resolveRefs( art ) {
|
|
294
308
|
if (art.builtin)
|
|
295
309
|
return;
|
|
296
|
-
// console.log(
|
|
297
|
-
// console.log(
|
|
310
|
+
// console.log(info( null, [ art.location, art ], {}, 'REFS').toString());
|
|
311
|
+
// console.log(info( null, [ art.location, art ], { art: art.target || 'none' },
|
|
312
|
+
// 'RR: $(ART)').toString());
|
|
298
313
|
const parent = art._parent;
|
|
299
314
|
const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
|
|
300
315
|
const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
|
|
301
|
-
if (art.key
|
|
316
|
+
if (art.key?.val && !art.key.$inferred && !(allowedInMain && isTopLevelElement)) {
|
|
302
317
|
warning( 'unexpected-key', [ art.key.location, art ],
|
|
303
|
-
{ '#': allowedInMain ? 'sub' : 'std' }, {
|
|
304
|
-
std: '
|
|
305
|
-
sub: '
|
|
318
|
+
{ '#': allowedInMain ? 'sub' : 'std', keyword: 'key' }, {
|
|
319
|
+
std: '$(KEYWORD) is only supported for elements in an entity or an aspect',
|
|
320
|
+
sub: '$(KEYWORD) is only supported for top-level elements',
|
|
306
321
|
});
|
|
307
322
|
}
|
|
308
323
|
if (art.targetAspect && !(allowedInMain && isTopLevelElement)) {
|
|
@@ -348,7 +363,7 @@ function resolve( model ) {
|
|
|
348
363
|
if (elemtype && effectiveType( elemtype )) {
|
|
349
364
|
const assocType = getAssocSpec( elemtype ) || {};
|
|
350
365
|
if (assocType.on && !obj.on)
|
|
351
|
-
obj.on = { $inferred: 'rewrite' };
|
|
366
|
+
obj.on = { $inferred: 'rewrite' }; // TODO: no extra rewrite here
|
|
352
367
|
if (assocType.targetAspect) {
|
|
353
368
|
error( 'composition-as-type-of', [ obj.type.location, art ], {},
|
|
354
369
|
'A managed aspect composition element can\'t be used as type' );
|
|
@@ -406,9 +421,7 @@ function resolve( model ) {
|
|
|
406
421
|
if (art.$queries)
|
|
407
422
|
art.$queries.forEach( resolveQuery );
|
|
408
423
|
|
|
409
|
-
|
|
410
|
-
effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
|
|
411
|
-
|
|
424
|
+
// TODO: or should we set silent dependencies in init()?
|
|
412
425
|
if (obj.elements) { // silent dependencies
|
|
413
426
|
forEachGeneric( obj, 'elements', elem => dependsOnSilent( art, elem ) );
|
|
414
427
|
}
|
|
@@ -424,14 +437,14 @@ function resolve( model ) {
|
|
|
424
437
|
|
|
425
438
|
resolveExpr( art.default, 'default', art );
|
|
426
439
|
resolveExpr( art.value, 'expr', art, undefined, art.expand || art.inline );
|
|
427
|
-
if (art.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
440
|
+
if (art.type?.$inferred === 'cast')
|
|
441
|
+
inferTypePropertiesFromCast( art );
|
|
442
|
+
if (art.value) {
|
|
443
|
+
if (art.$syntax === 'calc')
|
|
444
|
+
checkCalculatedElement(art);
|
|
445
|
+
else if (art.type && !art.$inferred )
|
|
446
|
+
checkStructureCast(art);
|
|
447
|
+
}
|
|
435
448
|
|
|
436
449
|
forEachMember( art, resolveRefs, art.targetAspect );
|
|
437
450
|
|
|
@@ -452,435 +465,98 @@ function resolve( model ) {
|
|
|
452
465
|
}
|
|
453
466
|
}
|
|
454
467
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// op.val is also correctly set with CSN input
|
|
475
|
-
elem.type = { ...type, $inferred: 'cast' };
|
|
476
|
-
setArtifactLink( elem.type, type._artifact );
|
|
477
|
-
for (const prop of typeParameters.list) {
|
|
478
|
-
if (elem.value[prop])
|
|
479
|
-
elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
|
|
468
|
+
function checkCalculatedElement( art ) {
|
|
469
|
+
const loc = [ art.value.location, art ];
|
|
470
|
+
if (art._parent.kind === 'element') {
|
|
471
|
+
// TODO: Support calculated elements in structures.
|
|
472
|
+
// The checks below are already aware of those.
|
|
473
|
+
message( 'def-unsupported-calc-elem', loc, { '#': 'nested' } );
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const allowedInKind = [ 'entity', 'aspect', 'element' ];
|
|
477
|
+
let parent = art._parent;
|
|
478
|
+
while (parent.kind === 'element')
|
|
479
|
+
parent = parent._parent;
|
|
480
|
+
|
|
481
|
+
if (!allowedInKind.includes(art._main.kind)) {
|
|
482
|
+
if (art.$inferred === 'include') {
|
|
483
|
+
// even for include-chains, we find the correct ref due to element-expansion.
|
|
484
|
+
const include = art._main.includes.find(i => i._artifact === art._origin._main);
|
|
485
|
+
error('ref-invalid-calc-elem', [ include.location || art.value.location, art ],
|
|
486
|
+
{ '#': art._main.kind });
|
|
480
487
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// Phase 4 - annotations ---------------------------------------------------
|
|
485
|
-
|
|
486
|
-
function annotateUnknown( ext ) {
|
|
487
|
-
// extensions may have annotations for elements/actions/... which may
|
|
488
|
-
// themselves may be unknown
|
|
489
|
-
forEachMember(ext, annotateUnknown);
|
|
490
|
-
|
|
491
|
-
if (ext.$extension) // extension for known artifact -> already applied
|
|
492
|
-
return;
|
|
493
|
-
annotateMembers( ext );
|
|
494
|
-
chooseAnnotationsInArtifact( ext );
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* @param {XSN.Artifact} art
|
|
499
|
-
* @param {XSN.Extension[]} [extensions]
|
|
500
|
-
* @param {string} [prop]
|
|
501
|
-
* @param {string} [name]
|
|
502
|
-
* @param {object} [parent]
|
|
503
|
-
* @param {string} [kind]
|
|
504
|
-
*/
|
|
505
|
-
function annotateMembers( art, extensions, prop, name, parent, kind ) {
|
|
506
|
-
const showMsg = !art && parent && parent.kind !== 'annotate';
|
|
507
|
-
if (!art && extensions && extensions.length) {
|
|
508
|
-
if (Array.isArray( parent ))
|
|
509
|
-
return;
|
|
510
|
-
const parentExt = extensionFor(parent);
|
|
511
|
-
art = parentExt[prop] && parentExt[prop][name];
|
|
512
|
-
if (!art) {
|
|
513
|
-
art = {
|
|
514
|
-
kind, // for setMemberParent()
|
|
515
|
-
name: { id: name, location: extensions[0].name.location },
|
|
516
|
-
location: extensions[0].location,
|
|
517
|
-
};
|
|
518
|
-
setMemberParent( art, name, parentExt, prop );
|
|
519
|
-
art.kind = 'annotate'; // after setMemberParent()!
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
for (const ext of extensions || []) {
|
|
524
|
-
if ('_artifact' in ext.name) // already applied
|
|
525
|
-
continue;
|
|
526
|
-
setArtifactLink( ext.name, art );
|
|
527
|
-
|
|
528
|
-
if (art) {
|
|
529
|
-
checkAnnotate( ext, art );
|
|
530
|
-
initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
|
|
531
|
-
copyAnnotationsForExtensions( ext, art );
|
|
532
|
-
// eslint-disable-next-line no-shadow
|
|
533
|
-
forEachMember( ext, ( elem, name, prop ) => {
|
|
534
|
-
storeExtension( elem, name, prop, art, ext._block );
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
if (showMsg) {
|
|
538
|
-
// somehow similar to checkDefinitions():
|
|
539
|
-
const feature = kindProperties[parent.kind][prop];
|
|
540
|
-
if (prop === 'elements' || prop === 'enum') {
|
|
541
|
-
if (!feature) {
|
|
542
|
-
warning( 'anno-unexpected-elements', [ ext.name.location, art ], {},
|
|
543
|
-
'Elements only exist in entities, types or typed constructs' );
|
|
544
|
-
}
|
|
545
|
-
else {
|
|
546
|
-
const isEntity = (parent.returns?.type || parent.type)?._artifact?.kind === 'entity';
|
|
547
|
-
let variant = parent.enum ? 'enum' : 'element';
|
|
548
|
-
if (isEntity)
|
|
549
|
-
variant = 'entity-element';
|
|
550
|
-
else if (parent.returns)
|
|
551
|
-
variant = 'returns';
|
|
552
|
-
notFound( 'anno-undefined-element', ext.name.location, art,
|
|
553
|
-
{ '#': variant, art: parent, name },
|
|
554
|
-
parent.elements || parent.enum );
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
else if (prop === 'actions') {
|
|
558
|
-
if (!feature) {
|
|
559
|
-
warning( 'anno-unexpected-actions', [ ext.name.location, art._parent || art ], {},
|
|
560
|
-
'Actions and functions only exist top-level and for entities' );
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
notFound( 'anno-undefined-action', ext.name.location, art,
|
|
564
|
-
{ art: searchName( parent, name, 'action' ) },
|
|
565
|
-
parent.actions );
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
else if (!feature) {
|
|
569
|
-
warning( 'anno-unexpected-params', [ ext.name.location, art ], {},
|
|
570
|
-
'Parameters only exist for actions or functions' );
|
|
571
|
-
} // TODO: entities betaMod
|
|
572
|
-
else {
|
|
573
|
-
notFound( 'anno-undefined-param', ext.name.location, art,
|
|
574
|
-
{ art: searchName( parent, name, 'param' ) },
|
|
575
|
-
parent.params );
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
if (art?._annotate) {
|
|
580
|
-
if (art.kind === 'action' || art.kind === 'function') {
|
|
581
|
-
expandParameters( art );
|
|
582
|
-
if (art.returns)
|
|
583
|
-
effectiveType( art.returns );
|
|
488
|
+
else {
|
|
489
|
+
error( 'def-invalid-calc-elem', loc, { '#': art._main.kind } );
|
|
584
490
|
}
|
|
585
|
-
const aor = art.returns || art;
|
|
586
|
-
const obj = aor.items || aor.targetAspect || aor;
|
|
587
|
-
// Currently(?), effectiveType() does not calculate the effective type of
|
|
588
|
-
// its line item:
|
|
589
|
-
effectiveType( obj );
|
|
590
|
-
if (art._annotate.elements) // explicit $expand on aor needed
|
|
591
|
-
setExpandStatusAnnotate( aor, 'annotate' );
|
|
592
|
-
annotate( obj, 'element', 'elements', 'enum', art );
|
|
593
|
-
annotate( art, 'action', 'actions' );
|
|
594
|
-
annotate( art, 'param', 'params' );
|
|
595
|
-
// const { returns } = art._annotate;
|
|
596
|
-
// if (returns) {
|
|
597
|
-
// const dict = returns.elements;
|
|
598
|
-
// const env = obj.returns && obj.returns.elements || null;
|
|
599
|
-
// for (const n in dict)
|
|
600
|
-
// annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
|
|
601
|
-
// }
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
if (art?._extendType) {
|
|
605
|
-
// Only works because annotateMembers is called in resolveRefs() _after_ `.type` was resolved.
|
|
606
|
-
// TODO: If we allow extending included elements, we may need custom $expand,
|
|
607
|
-
// similar to annotate above.
|
|
608
|
-
art._extendType.forEach(resolveRefs);
|
|
609
|
-
applyTypeExtensions(art);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
return;
|
|
613
|
-
|
|
614
|
-
function notFound( msgId, location, address, args, validDict ) {
|
|
615
|
-
// TODO: probably move this to shared.js and use for EXTEND, too
|
|
616
|
-
const msg = message( msgId, [ location, address ], args );
|
|
617
|
-
attachAndEmitValidNames(msg, validDict);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// eslint-disable-next-line no-shadow
|
|
621
|
-
function annotate( obj, kind, prop, altProp, parent = obj ) {
|
|
622
|
-
const dict = art._annotate[prop];
|
|
623
|
-
if (dict && art._annotate[prop])
|
|
624
|
-
setExpandStatusAnnotate( art, 'annotate' );
|
|
625
|
-
const env = obj[prop] || altProp && obj[altProp] || null;
|
|
626
|
-
for (const n in dict)
|
|
627
|
-
annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function expandParameters( action ) {
|
|
632
|
-
// see also expandElements()
|
|
633
|
-
if (!effectiveType( action ))
|
|
634
|
-
return;
|
|
635
|
-
const chain = [];
|
|
636
|
-
// Should we be able to consider params and returns separately?
|
|
637
|
-
// Probably not, let to-csn omit unchanged params/returns.
|
|
638
|
-
while (action._origin && !action.params) {
|
|
639
|
-
chain.push( action );
|
|
640
|
-
action = action._origin;
|
|
641
491
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const origin = art._origin;
|
|
645
|
-
if (!art.params && origin.params) {
|
|
646
|
-
for (const name in origin.params) {
|
|
647
|
-
// TODO: we could check _annotate here to decide whether we really
|
|
648
|
-
// not to create proxies
|
|
649
|
-
const orig = origin.params[name];
|
|
650
|
-
linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
|
|
651
|
-
.$inferred = 'expanded';
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
if (!art.returns && origin.returns) {
|
|
655
|
-
// TODO: make linkToOrigin() work for returns, kind/name?
|
|
656
|
-
const location = weakLocation( origin.returns.location );
|
|
657
|
-
art.returns = {
|
|
658
|
-
name: Object.assign( {}, art.name, { id: '', param: '', location } ),
|
|
659
|
-
kind: 'param',
|
|
660
|
-
location,
|
|
661
|
-
$inferred: 'expanded',
|
|
662
|
-
};
|
|
663
|
-
setLink( art.returns, '_parent', art );
|
|
664
|
-
setLink( art.returns, '_main', art._main || art );
|
|
665
|
-
setLink( art.returns, '_origin', origin.returns );
|
|
666
|
-
}
|
|
492
|
+
else if (!allowedInKind.includes(parent.kind)) {
|
|
493
|
+
error( 'def-invalid-calc-elem', loc, { '#': parent.kind } );
|
|
667
494
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (art.kind === 'annotate')
|
|
672
|
-
return art;
|
|
673
|
-
if (art._extension)
|
|
674
|
-
return art._extension;
|
|
675
|
-
|
|
676
|
-
// $extension means: already applied
|
|
677
|
-
const ext = {
|
|
678
|
-
kind: art.kind, // set kind for setMemberParent()
|
|
679
|
-
$extension: 'exists',
|
|
680
|
-
location: art.location, // location( extension to existing art ) = location(art)
|
|
681
|
-
};
|
|
682
|
-
const { location } = art.name;
|
|
683
|
-
if (!art._main) {
|
|
684
|
-
ext.name = {
|
|
685
|
-
path: [ { id: art.name.absolute, location } ],
|
|
686
|
-
location,
|
|
687
|
-
absolute: art.name.absolute,
|
|
688
|
-
};
|
|
689
|
-
if (model.extensions)
|
|
690
|
-
model.extensions.push(ext);
|
|
495
|
+
else if (effectiveType(art)?.elements) {
|
|
496
|
+
if (art.type)
|
|
497
|
+
error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
|
|
691
498
|
else
|
|
692
|
-
|
|
499
|
+
error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
|
|
693
500
|
}
|
|
694
501
|
else {
|
|
695
|
-
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
|
|
703
|
-
}
|
|
704
|
-
ext.kind = 'annotate'; // after setMemberParent()!
|
|
705
|
-
setLink( art, '_extension', ext );
|
|
706
|
-
setArtifactLink( ext.name, art );
|
|
707
|
-
if (art.returns)
|
|
708
|
-
ext.$syntax = 'returns';
|
|
709
|
-
return ext;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* Goes through all (applied) annotations in the given artifact and chooses one
|
|
714
|
-
* if multiple exist according to the module layer.
|
|
715
|
-
*
|
|
716
|
-
* @param {XSN.Artifact} art
|
|
717
|
-
*/
|
|
718
|
-
function chooseAnnotationsInArtifact( art ) {
|
|
719
|
-
for (const prop in art) {
|
|
720
|
-
if (prop.charAt(0) === '@')
|
|
721
|
-
chooseAssignment( prop, art );
|
|
722
|
-
}
|
|
723
|
-
if (art.doc)
|
|
724
|
-
chooseAssignment( 'doc', art );
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function chooseAssignment( annoName, art ) {
|
|
728
|
-
let anno = art[annoName];
|
|
729
|
-
if (!Array.isArray( anno )) { // just one assignment -> use it
|
|
730
|
-
if (!annotationHasEllipsis( anno ))
|
|
731
|
-
return;
|
|
732
|
-
anno = [ anno ];
|
|
733
|
-
}
|
|
734
|
-
// console.log('ASSIGN:',art.name.absolute,annoName)
|
|
735
|
-
const scheduledAssignments = [];
|
|
736
|
-
// sort assignment according to layer (define is bottom layer):
|
|
737
|
-
const layeredAnnos = layeredAssignments( anno );
|
|
738
|
-
let cont = true;
|
|
739
|
-
while (cont) {
|
|
740
|
-
const { assignments, issue } = assignmentsOfHighestLayers( layeredAnnos );
|
|
741
|
-
let index = assignments.length;
|
|
742
|
-
cont = !!index; // safety
|
|
743
|
-
while (--index >= 0) {
|
|
744
|
-
const a = assignments[index];
|
|
745
|
-
scheduledAssignments.push( a );
|
|
746
|
-
if (!annotationHasEllipsis( a )) {
|
|
747
|
-
cont = false;
|
|
748
|
-
break;
|
|
502
|
+
const noTruthyAllowed = [ 'localized', 'key', 'virtual' ];
|
|
503
|
+
for (const prop of noTruthyAllowed) {
|
|
504
|
+
if (art[prop]?.val) {
|
|
505
|
+
// probably better than a parse error (which is good for DEFAULT vs calc),
|
|
506
|
+
// also appears with parse-cdl:
|
|
507
|
+
error('def-invalid-calc-elem', loc, { '#': prop });
|
|
508
|
+
return; // one error is enough
|
|
749
509
|
}
|
|
750
510
|
}
|
|
751
|
-
if (issue) {
|
|
752
|
-
// eslint-disable-next-line no-nested-ternary
|
|
753
|
-
const msg = (issue === true)
|
|
754
|
-
? 'anno-duplicate'
|
|
755
|
-
: (index >= 0) ? 'anno-duplicate-unrelated-layer' : 'anno-unstable-array';
|
|
756
|
-
const variant = annoName === 'doc' ? 'doc' : 'std';
|
|
757
|
-
for (const a of assignments) {
|
|
758
|
-
if (!a.$errorReported) {
|
|
759
|
-
message( msg, [ a.name?.location || a.location, art ],
|
|
760
|
-
{ '#': variant, anno: annoName } );
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
// else if (index > 0) -- if we allow multiple assignments in one file - the last wins
|
|
765
511
|
}
|
|
766
|
-
// Now apply the assignments - all but the first have a '...'
|
|
767
|
-
let result = null;
|
|
768
|
-
scheduledAssignments.reverse();
|
|
769
|
-
for (const a of scheduledAssignments)
|
|
770
|
-
result = applyAssignment( result, a, art, annoName );
|
|
771
|
-
art[annoName] = result.name ? result
|
|
772
|
-
: Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
|
|
773
512
|
}
|
|
774
513
|
|
|
775
|
-
function
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
message( 'anno-unexpected-ellipsis-layers', [ loc, art ], { code: '...' } );
|
|
514
|
+
function checkStructureCast( art ) {
|
|
515
|
+
const elem = (art.value.op?.val === 'cast')
|
|
516
|
+
? art.value.args[0]?._artifact
|
|
517
|
+
: art.value._artifact;
|
|
518
|
+
if (elem && art.type) { // has explicit type
|
|
519
|
+
if (art.type._artifact?.elements) {
|
|
520
|
+
error('type-cast-to-structured', [ art.type.location, art ], {},
|
|
521
|
+
'Can\'t cast to structured element');
|
|
784
522
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
// TODO: If we introduce sub-messages, point to the non-array base value.
|
|
789
|
-
error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
|
|
790
|
-
previousAnno = { val: [] };
|
|
791
|
-
}
|
|
792
|
-
const previousValue = previousAnno.val;
|
|
793
|
-
let prevPos = 0;
|
|
794
|
-
const result = [];
|
|
795
|
-
for (const item of anno.val) {
|
|
796
|
-
const ell = item && item.literal === 'token' && item.val === '...';
|
|
797
|
-
if (!ell) {
|
|
798
|
-
result.push( item );
|
|
799
|
-
}
|
|
800
|
-
else {
|
|
801
|
-
let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
|
|
802
|
-
while (prevPos < previousValue.length) {
|
|
803
|
-
const prevItem = previousValue[prevPos++];
|
|
804
|
-
result.push( prevItem );
|
|
805
|
-
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
|
|
806
|
-
upToSpec = false;
|
|
807
|
-
break;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
if (upToSpec && hasBase) {
|
|
811
|
-
// non-matched UP TO; if there is no base to apply to, there is already an error.
|
|
812
|
-
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
813
|
-
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
814
|
-
}
|
|
523
|
+
else if (elem.elements) { // TODO: calc elements
|
|
524
|
+
error('type-cast-structured', [ art.type.location, art ], {},
|
|
525
|
+
'Structured elements can\'t be cast to a different type');
|
|
815
526
|
}
|
|
816
527
|
}
|
|
817
|
-
// console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
|
|
818
|
-
return { val: result, literal: 'array' };
|
|
819
528
|
}
|
|
820
|
-
// function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
|
|
821
529
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
530
|
+
// Return type containing the assoc spec (keys, on); note that no
|
|
531
|
+
// propagation/rewrite has been done yet, cyclic dependency must have been
|
|
532
|
+
// checked before!
|
|
533
|
+
function getAssocSpec( type ) {
|
|
534
|
+
// only to be called without cycles
|
|
535
|
+
let unmanaged = null;
|
|
536
|
+
while (type) {
|
|
537
|
+
if (type.on) // if unmanaged, continue trying to find targetAspect
|
|
538
|
+
unmanaged = type;
|
|
539
|
+
else if (type.foreignKeys || type.targetAspect)
|
|
540
|
+
return type;
|
|
541
|
+
type = getOrigin( type );
|
|
833
542
|
}
|
|
834
|
-
|
|
835
|
-
{ anno: annoName, code: '... up to', '#': literal },
|
|
836
|
-
{
|
|
837
|
-
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
838
|
-
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
839
|
-
// eslint-disable-next-line max-len
|
|
840
|
-
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
841
|
-
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
842
|
-
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
843
|
-
} );
|
|
844
|
-
return false;
|
|
543
|
+
return unmanaged;
|
|
845
544
|
}
|
|
846
545
|
|
|
847
|
-
function
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
852
|
-
return true;
|
|
853
|
-
const typeUpTo = typeof upToSpec.val;
|
|
854
|
-
const typePrev = typeof previousItem.val;
|
|
855
|
-
if (typeUpTo === 'number')
|
|
856
|
-
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
857
|
-
if (typePrev === 'number')
|
|
858
|
-
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
859
|
-
}
|
|
860
|
-
else if (upToSpec.path) {
|
|
861
|
-
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
862
|
-
}
|
|
863
|
-
else if (upToSpec.sym) {
|
|
864
|
-
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
546
|
+
function inferTypePropertiesFromCast( elem ) {
|
|
547
|
+
for (const prop of typeParameters.list) {
|
|
548
|
+
if (elem.value[prop])
|
|
549
|
+
elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
|
|
865
550
|
}
|
|
866
|
-
else if (upToSpec.struct && previousItem.struct) {
|
|
867
|
-
return Object.entries( upToSpec.struct )
|
|
868
|
-
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
869
|
-
}
|
|
870
|
-
return false;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
function normalizeRef( node ) { // see to-csn.js
|
|
874
|
-
const ref = pathName( node.path );
|
|
875
|
-
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
876
551
|
}
|
|
877
552
|
|
|
878
553
|
// Phase 4 - queries and associations --------------------------------------
|
|
879
554
|
|
|
880
555
|
function resolveQuery( query ) {
|
|
881
|
-
if (!query._main)
|
|
556
|
+
if (!query._main || !query._effectiveType) // parse error
|
|
882
557
|
return;
|
|
883
|
-
|
|
558
|
+
// TODO: or set silent dependencies in init?
|
|
559
|
+
forEachGeneric( query, 'elements', elem => dependsOnSilent( query, elem ) );
|
|
884
560
|
forEachGeneric( query, '$tableAliases', ( alias ) => {
|
|
885
561
|
// console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
|
|
886
562
|
if (alias.kind === 'mixin')
|
|
@@ -889,6 +565,7 @@ function resolve( model ) {
|
|
|
889
565
|
// pure path has been resolved, resolve args and filter now:
|
|
890
566
|
resolveExpr( alias, 'from', query._parent );
|
|
891
567
|
} );
|
|
568
|
+
// if (!query.$inlines) console.log('RQ:',query)
|
|
892
569
|
for (const col of query.$inlines)
|
|
893
570
|
resolveExpr( col.value, 'expr', col, undefined, true );
|
|
894
571
|
// for (const col of query.$inlines)
|
|
@@ -1002,13 +679,9 @@ function resolve( model ) {
|
|
|
1002
679
|
return; // avoid subsequent errors
|
|
1003
680
|
}
|
|
1004
681
|
else if (target && !obj.foreignKeys && target.kind === 'entity') {
|
|
1005
|
-
|
|
682
|
+
// redirected or explicit type cds.Association, ...
|
|
683
|
+
if (obj.$inferred === 'REDIRECTED' || obj.type?._artifact?.internal)
|
|
1006
684
|
addImplicitForeignKeys( art, obj, target );
|
|
1007
|
-
}
|
|
1008
|
-
else if (obj.type && obj.type._artifact && obj.type._artifact.internal) {
|
|
1009
|
-
// cds.Association, ...
|
|
1010
|
-
addImplicitForeignKeys( art, obj, target );
|
|
1011
|
-
}
|
|
1012
685
|
}
|
|
1013
686
|
|
|
1014
687
|
if (target && !target.$inferred) {
|
|
@@ -1072,8 +745,10 @@ function resolve( model ) {
|
|
|
1072
745
|
// TODO: add this somehow to tweak-assocs.js ?
|
|
1073
746
|
function resolveRedirected( elem, target ) {
|
|
1074
747
|
setLink( elem, '_redirected', null ); // null = do not touch path steps after assoc
|
|
1075
|
-
const assoc =
|
|
748
|
+
const assoc = getOrigin( elem );
|
|
1076
749
|
const origType = assoc && effectiveType( assoc );
|
|
750
|
+
if (origType === 0)
|
|
751
|
+
return;
|
|
1077
752
|
if (!origType || !origType.target) {
|
|
1078
753
|
const path = (elem.value && elem.value.path);
|
|
1079
754
|
const loc = (path && path[path.length - 1] || elem.value || elem).location;
|
|
@@ -1240,11 +915,13 @@ function resolve( model ) {
|
|
|
1240
915
|
// Also: For enums, it points to the enum type, which is why this trick is needed.
|
|
1241
916
|
// TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
|
|
1242
917
|
// trick may be removed if effectiveType() does not stop at enums.
|
|
918
|
+
// TODO: this is wrong - we must check typeArt.enum, not its effectiveType
|
|
919
|
+
// TODO: this function is not complete(!): parallel `elements` and `length`, … - rework function
|
|
1243
920
|
const cyclic = new Set();
|
|
1244
921
|
let effectiveTypeArt = effectiveType( typeArt );
|
|
1245
922
|
while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
|
|
1246
923
|
cyclic.add(effectiveTypeArt);
|
|
1247
|
-
const underlyingEnumType =
|
|
924
|
+
const underlyingEnumType = getOrigin(effectiveTypeArt);
|
|
1248
925
|
if (underlyingEnumType)
|
|
1249
926
|
effectiveTypeArt = effectiveType(underlyingEnumType);
|
|
1250
927
|
else
|
|
@@ -1261,7 +938,7 @@ function resolve( model ) {
|
|
|
1261
938
|
if (artWithType[param] !== undefined) {
|
|
1262
939
|
if (!params.includes(param)) {
|
|
1263
940
|
// Whether the type ref itself is a builtin or a custom type with a builtin as base.
|
|
1264
|
-
const type =
|
|
941
|
+
const type = getOrigin(artWithType);
|
|
1265
942
|
|
|
1266
943
|
let variant;
|
|
1267
944
|
if (type.builtin)
|
|
@@ -1274,6 +951,7 @@ function resolve( model ) {
|
|
|
1274
951
|
// effectiveType is not a builtin -> array or structured
|
|
1275
952
|
variant = 'non-scalar';
|
|
1276
953
|
|
|
954
|
+
// console.log(typeArt.name,artWithType.name,effectiveTypeArt.name)
|
|
1277
955
|
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
1278
956
|
'#': variant, prop: param, art: artWithType.type, type: effectiveTypeArt,
|
|
1279
957
|
});
|
|
@@ -1347,7 +1025,7 @@ function resolve( model ) {
|
|
|
1347
1025
|
}
|
|
1348
1026
|
|
|
1349
1027
|
function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
|
|
1350
|
-
const alias = step._navigation
|
|
1028
|
+
const alias = (step._navigation?.kind === '$tableAlias') ? step._navigation : null;
|
|
1351
1029
|
const type = alias || effectiveType( step._artifact );
|
|
1352
1030
|
const art = (type && type.target) ? type.target._artifact : type;
|
|
1353
1031
|
if (!art)
|
|
@@ -1360,14 +1038,19 @@ function resolve( model ) {
|
|
|
1360
1038
|
if (step.where)
|
|
1361
1039
|
resolveExpr( step.where, 'filter', user, environment( type ) );
|
|
1362
1040
|
}
|
|
1363
|
-
else if (step.where
|
|
1041
|
+
else if (step.where?.location || step.cardinality ) {
|
|
1364
1042
|
const location = combinedLocation( step.where, step.cardinality );
|
|
1043
|
+
let variant = alias ? 'tableAlias' : 'std';
|
|
1044
|
+
if (expected === 'from')
|
|
1045
|
+
variant = 'from';
|
|
1365
1046
|
// XSN TODO: filter$location including […]
|
|
1366
|
-
message( 'expr-no-filter', [ location, user ], { '#':
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1047
|
+
message( 'expr-no-filter', [ location, user ], { '#': variant }, {
|
|
1048
|
+
std: 'A filter can only be provided when navigating along associations',
|
|
1049
|
+
// to help users for `… from E:toF { toF[…].x }`
|
|
1050
|
+
// eslint-disable-next-line max-len
|
|
1051
|
+
tableAlias: 'A filter can only be provided when navigating along associations, but found table alias',
|
|
1052
|
+
from: 'A filter can only be provided for the source entity or associations',
|
|
1053
|
+
} );
|
|
1371
1054
|
}
|
|
1372
1055
|
}
|
|
1373
1056
|
|