@sap/cds-compiler 4.0.0 → 4.1.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 +115 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_BETA.md +11 -0
- package/lib/api/main.js +60 -12
- package/lib/api/validate.js +1 -1
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +84 -38
- package/lib/base/messages.js +11 -10
- package/lib/base/model.js +6 -2
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/onConditions.js +17 -12
- package/lib/checks/queryNoDbArtifacts.js +132 -72
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +1 -1
- package/lib/compiler/assert-consistency.js +44 -16
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +7 -8
- package/lib/compiler/checks.js +274 -197
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +3 -3
- package/lib/compiler/define.js +63 -50
- package/lib/compiler/extend.js +38 -20
- package/lib/compiler/finalize-parse-cdl.js +2 -1
- package/lib/compiler/generate.js +0 -8
- package/lib/compiler/index.js +9 -7
- package/lib/compiler/kick-start.js +2 -0
- package/lib/compiler/populate.js +139 -110
- package/lib/compiler/propagator.js +4 -3
- package/lib/compiler/resolve.js +157 -126
- package/lib/compiler/shared.js +706 -404
- package/lib/compiler/tweak-assocs.js +21 -10
- package/lib/compiler/utils.js +228 -36
- package/lib/edm/annotations/genericTranslation.js +30 -2
- package/lib/edm/edm.js +4 -1
- package/lib/edm/edmPreprocessor.js +12 -5
- package/lib/edm/edmUtils.js +2 -4
- 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 +3987 -3963
- package/lib/json/from-csn.js +43 -47
- package/lib/json/to-csn.js +11 -11
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +59 -59
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +20 -16
- package/lib/model/revealInternalProperties.js +29 -21
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +112 -39
- package/lib/modelCompare/utils/filter.js +54 -24
- package/lib/optionProcessor.js +6 -6
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +34 -20
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +77 -26
- package/lib/render/utils/common.js +3 -3
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +61 -20
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +8 -8
- package/lib/transform/db/expansion.js +17 -21
- package/lib/transform/db/flattening.js +23 -23
- 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} +56 -42
- package/lib/transform/forRelationalDB.js +69 -75
- package/lib/transform/localized.js +6 -5
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/package.json +1 -1
- 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
package/lib/compiler/populate.js
CHANGED
|
@@ -41,9 +41,10 @@ const {
|
|
|
41
41
|
linkToOrigin,
|
|
42
42
|
setMemberParent,
|
|
43
43
|
proxyCopyMembers,
|
|
44
|
-
dependsOn,
|
|
45
44
|
setExpandStatus,
|
|
46
45
|
setExpandStatusAnnotate,
|
|
46
|
+
dependsOnSilent,
|
|
47
|
+
columnRefStartsWithSelf,
|
|
47
48
|
} = require('./utils');
|
|
48
49
|
const { typeParameters } = require('./builtins');
|
|
49
50
|
|
|
@@ -73,6 +74,7 @@ function populate( model ) {
|
|
|
73
74
|
} = model.$messageFunctions;
|
|
74
75
|
const {
|
|
75
76
|
resolvePath,
|
|
77
|
+
nestedElements,
|
|
76
78
|
attachAndEmitValidNames,
|
|
77
79
|
initArtifact,
|
|
78
80
|
extendArtifactBefore,
|
|
@@ -81,8 +83,7 @@ function populate( model ) {
|
|
|
81
83
|
Object.assign( model.$functions, {
|
|
82
84
|
effectiveType,
|
|
83
85
|
getOrigin,
|
|
84
|
-
|
|
85
|
-
navigationEnv,
|
|
86
|
+
getInheritedProp,
|
|
86
87
|
} );
|
|
87
88
|
// let depth = 100;
|
|
88
89
|
|
|
@@ -111,12 +112,17 @@ function populate( model ) {
|
|
|
111
112
|
newAutoExposed = true; // internal error if auto-expose after here
|
|
112
113
|
return;
|
|
113
114
|
|
|
115
|
+
/** Make sure that effectiveType() is called on all members and items */
|
|
114
116
|
function traverseElementEnvironments( art ) {
|
|
115
|
-
|
|
117
|
+
let type = effectiveType( art );
|
|
118
|
+
while (type?.items)
|
|
119
|
+
type = effectiveType( type.items );
|
|
116
120
|
if (art.$queries)
|
|
117
121
|
art.$queries.forEach( traverseElementEnvironments );
|
|
118
122
|
if (art.mixin)
|
|
119
|
-
dictForEach( art.mixin,
|
|
123
|
+
dictForEach( art.mixin, effectiveType );
|
|
124
|
+
if (art.targetAspect?.elements)
|
|
125
|
+
effectiveType( art.targetAspect );
|
|
120
126
|
if (art !== art._main?._leadingQuery) // already done
|
|
121
127
|
forEachMember( art, traverseElementEnvironments );
|
|
122
128
|
}
|
|
@@ -125,37 +131,6 @@ function populate( model ) {
|
|
|
125
131
|
//--------------------------------------------------------------------------
|
|
126
132
|
// The central functions for path resolution - must work on-demand
|
|
127
133
|
//--------------------------------------------------------------------------
|
|
128
|
-
// Phase 2: call effectiveType() on-demand, which also calculates view elems
|
|
129
|
-
|
|
130
|
-
// TODO: move setting dependencies and complaining about assocs in keys
|
|
131
|
-
// to resolvePath - then remove params: location, user
|
|
132
|
-
function navigationEnv( art, location, user, assocSpec ) {
|
|
133
|
-
// = effectiveType() on from-path, TODO: should actually already part of
|
|
134
|
-
// resolvePath() on FROM
|
|
135
|
-
if (!art)
|
|
136
|
-
return undefined;
|
|
137
|
-
let type = effectiveType( art );
|
|
138
|
-
while (type?.items) // TODO: disallow navigation to many sometimes
|
|
139
|
-
type = effectiveType( type.items );
|
|
140
|
-
if (!type?.target || assocSpec === 'targetAspectOnly')
|
|
141
|
-
return type;
|
|
142
|
-
|
|
143
|
-
if (assocSpec === false) {
|
|
144
|
-
// TODO: combine this with setTargetReferenceKey&Co in getPathItem?
|
|
145
|
-
error( null, [ location, user ], {},
|
|
146
|
-
'Following an association is not allowed in an association key definition' );
|
|
147
|
-
} // TODO: else warning for assoc usage with falsy assocSpec
|
|
148
|
-
const target = resolvePath( type.target, 'target', type );
|
|
149
|
-
if (!target)
|
|
150
|
-
return target;
|
|
151
|
-
if (target && assocSpec && user)
|
|
152
|
-
dependsOn( user, target, location || user.location );
|
|
153
|
-
const effectiveTarget = effectiveType( target );
|
|
154
|
-
if (effectiveTarget === 0 && location)
|
|
155
|
-
dependsOn( user, user, (user.target || user.type || user.value || user).location );
|
|
156
|
-
// console.log('NT:',assocSpec,!!user,target)
|
|
157
|
-
return effectiveTarget;
|
|
158
|
-
}
|
|
159
134
|
|
|
160
135
|
/**
|
|
161
136
|
* Return the artifact having properties which are relevant for further name
|
|
@@ -189,10 +164,11 @@ function populate( model ) {
|
|
|
189
164
|
* artifact is an association or composition; it also does not give you the
|
|
190
165
|
* information about the technical base type of an enum.
|
|
191
166
|
*
|
|
192
|
-
* Calculating an effective association/composition
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
167
|
+
* Calculating an effective association/composition implies calculating its
|
|
168
|
+
* target entity (including redirections), but not induce calculating the
|
|
169
|
+
* target's elements. Calculating an effective structure (entities, …) does
|
|
170
|
+
* not imply calculating the effective types of its elements. Calculating an
|
|
171
|
+
* effective array does not imply calculating its effective line type.
|
|
196
172
|
*/
|
|
197
173
|
function effectiveType( art ) {
|
|
198
174
|
if (!art)
|
|
@@ -212,16 +188,28 @@ function populate( model ) {
|
|
|
212
188
|
}
|
|
213
189
|
if (art)
|
|
214
190
|
art = art._effectiveType;
|
|
215
|
-
if (art === 0)
|
|
191
|
+
if (art === 0) {
|
|
192
|
+
model.$assert = 'cycle';
|
|
193
|
+
// throw Error(`CYCLE: ${ chain.length }`);
|
|
216
194
|
return art;
|
|
217
|
-
|
|
195
|
+
}
|
|
218
196
|
chain.reverse();
|
|
219
197
|
for (const a of chain) {
|
|
198
|
+
// Ensure that the _effectiveType of the parent has been calculated. This
|
|
199
|
+
// is usually the case, but might not be for elements of anonymous target
|
|
200
|
+
// aspects. Without it, extensions/annotations might get lost.
|
|
201
|
+
// For a query and its parent (usually the query entity!), it is the other way
|
|
202
|
+
// around: to calculate the _effectiveType of the query entity, we might need
|
|
203
|
+
// to calculate the _effectiveType of a query in FROM first.
|
|
204
|
+
if (a.kind !== 'select')
|
|
205
|
+
effectiveType( a._outer || a._main && a._parent );
|
|
206
|
+
// TODO: forbid $self+$self.elem inline, see expandWildcard()
|
|
220
207
|
// Without type, value.path or _origin at beginning, link to itself:
|
|
221
208
|
extendArtifactBefore( a );
|
|
222
209
|
art = populateArtifact( a, art ) || a;
|
|
223
210
|
setLink( a, '_effectiveType', art );
|
|
224
211
|
a.$effectiveSeqNo = ++effectiveSeqNo;
|
|
212
|
+
// console.log('PE:',require('../model/revealInternalProperties').ref(a))
|
|
225
213
|
if (a.elements$ || a.enum$)
|
|
226
214
|
mergeSpecifiedElementsOrEnum( a );
|
|
227
215
|
// console.log( 'ET-DO:', effectiveSeqNo, a?.kind, a?.name, a._extensions?.elements?.length )
|
|
@@ -237,13 +225,16 @@ function populate( model ) {
|
|
|
237
225
|
// Name-resolution relevant properties directly at artifact:
|
|
238
226
|
// ‹view›.elements of input must have been moved (to elements$) before!
|
|
239
227
|
// console.log('Q:',art.elements,art.enum,art.items,!!art.query)
|
|
228
|
+
// console.log('PA:',require('../model/revealInternalProperties').ref(art))
|
|
240
229
|
if (art.includes) // first version of includes via effectiveTpe()
|
|
241
230
|
art.includes.forEach( i => effectiveType( i._artifact ) );
|
|
242
231
|
if (art.elements != null || art.enum != null || art.items != null)
|
|
243
232
|
return art;
|
|
244
233
|
if (art.target) {
|
|
234
|
+
// make sure that target._artifact is set:
|
|
235
|
+
const target = resolvePath( art.target, 'target', art );
|
|
245
236
|
// try to implicitly redirect explicitly provided target:
|
|
246
|
-
if (!origEffective?.target && art.kind !== 'mixin')
|
|
237
|
+
if (target && !origEffective?.target && art.kind !== 'mixin')
|
|
247
238
|
redirectImplicitly( art, art );
|
|
248
239
|
if (!art.expand)
|
|
249
240
|
return art;
|
|
@@ -257,8 +248,14 @@ function populate( model ) {
|
|
|
257
248
|
const leading = art.$queries[0];
|
|
258
249
|
if (!leading) // parse error
|
|
259
250
|
return null;
|
|
260
|
-
|
|
261
|
-
|
|
251
|
+
if (leading._effectiveType !== undefined) {
|
|
252
|
+
// You cannot refer to a query of another artifact:
|
|
253
|
+
throw new CompilerAssertion(
|
|
254
|
+
`Unexpected _effectiveType on leading query of ${ art.name.absolute }`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
// TODO: try just return (effectiveType( leading )) === 0 ? 0 : art;
|
|
258
|
+
setLink( leading, '_effectiveType', 0 ); // relevant to detect invalid $self.*
|
|
262
259
|
populateQuery( leading );
|
|
263
260
|
setLink( leading, '_effectiveType', leading );
|
|
264
261
|
leading.$effectiveSeqNo = ++effectiveSeqNo;
|
|
@@ -313,7 +310,7 @@ function populate( model ) {
|
|
|
313
310
|
if (art._origin !== undefined)
|
|
314
311
|
return art._origin;
|
|
315
312
|
if (art.type) // not stored in _origin
|
|
316
|
-
return
|
|
313
|
+
return resolvePath( art.type, 'type', art );
|
|
317
314
|
return setLink( art, '_origin', getOriginRaw( art ) );
|
|
318
315
|
}
|
|
319
316
|
|
|
@@ -321,17 +318,23 @@ function populate( model ) {
|
|
|
321
318
|
if (!art._main) {
|
|
322
319
|
if (art.query)
|
|
323
320
|
return getOrigin( art.$queries?.[0] );
|
|
321
|
+
// TODO: if we add the `includes` mechanism, use resolveUncheckedPath() for
|
|
322
|
+
// includes here, because the accept function for includes requires the
|
|
323
|
+
// elements to have been calculated!
|
|
324
324
|
}
|
|
325
325
|
else {
|
|
326
326
|
// TODO: write checks for path in enum?
|
|
327
327
|
if (art.value?.path)
|
|
328
|
-
return resolvePath( art.value, (art.$syntax === 'calc' ? 'calc' : '
|
|
329
|
-
if (art.kind === 'select')
|
|
330
|
-
|
|
328
|
+
return resolvePath( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
|
|
329
|
+
if (art.kind === 'select') {
|
|
330
|
+
const alias = dictFirst( art.$tableAliases );
|
|
331
|
+
// With parse errors, the first “alias” might be $self. Using its origin
|
|
332
|
+
// would lead to a cyclic processing dependency.
|
|
333
|
+
return (alias.kind === '$tableAlias') ? getOrigin( alias ) : null;
|
|
334
|
+
}
|
|
331
335
|
// init sets _origin for alias to sub query, only need to handle ref here:
|
|
332
336
|
if (art.kind === '$tableAlias') {
|
|
333
|
-
// do not
|
|
334
|
-
// source → we would have a deeper callstack
|
|
337
|
+
// do not call effectiveType() on the source to avoid a deeper callstack
|
|
335
338
|
const source = resolvePath( art, 'from', art._parent );
|
|
336
339
|
if (!source?._main)
|
|
337
340
|
return source; // direct entity (or undefined)
|
|
@@ -339,33 +342,19 @@ function populate( model ) {
|
|
|
339
342
|
// to call effectiveType() on the last assoc of a from ref:
|
|
340
343
|
// TODO: check this with test3/Queries/DollarSelf/CorruptedSource.err.cds
|
|
341
344
|
const assoc = effectiveType( source );
|
|
342
|
-
return
|
|
345
|
+
return assoc?.target._artifact;
|
|
343
346
|
}
|
|
344
347
|
}
|
|
345
348
|
return '';
|
|
346
349
|
}
|
|
347
350
|
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (user.kind === 'event')
|
|
354
|
-
return resolvePath( ref, 'eventType', user );
|
|
355
|
-
if (user.kind === 'param' && user._parent &&
|
|
356
|
-
(user._parent.kind === 'action' || user._parent.kind === 'function'))
|
|
357
|
-
return resolvePath( ref, 'actionParamType', user );
|
|
358
|
-
return resolvePath( ref, 'type', user );
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function getCardinality( assoc ) {
|
|
362
|
-
while (assoc?._effectiveType) {
|
|
363
|
-
// if (--depth) throw Error(`GCARD: ${ Object.keys(art) }`)
|
|
364
|
-
if (assoc.cardinality)
|
|
365
|
-
return assoc.cardinality;
|
|
366
|
-
assoc = getOrigin( assoc );
|
|
351
|
+
function getInheritedProp( art, prop ) {
|
|
352
|
+
while (art?._effectiveType) {
|
|
353
|
+
if (art[prop] !== undefined)
|
|
354
|
+
return art[prop];
|
|
355
|
+
art = getOrigin( art );
|
|
367
356
|
}
|
|
368
|
-
return
|
|
357
|
+
return undefined;
|
|
369
358
|
}
|
|
370
359
|
|
|
371
360
|
function userQuery( user ) {
|
|
@@ -511,7 +500,7 @@ function populate( model ) {
|
|
|
511
500
|
//--------------------------------------------------------------------------
|
|
512
501
|
|
|
513
502
|
// TODO: delete XSN._entities
|
|
514
|
-
// TODO: delete ENTITY._from
|
|
503
|
+
// TODO: delete ENTITY._from - use _origin? instead _from[0]
|
|
515
504
|
// TODO (after on-demand ext): delete XSN.$entity
|
|
516
505
|
|
|
517
506
|
/**
|
|
@@ -533,8 +522,12 @@ function populate( model ) {
|
|
|
533
522
|
const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
|
|
534
523
|
const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
|
|
535
524
|
if (!selem) {
|
|
536
|
-
info( 'query-missing-element', [ ielem.name.location, art ], {
|
|
537
|
-
|
|
525
|
+
info( 'query-missing-element', [ ielem.name.location, art ], {
|
|
526
|
+
'#': ielem.kind === 'enum' ? 'enum' : 'std', id,
|
|
527
|
+
}, {
|
|
528
|
+
std: 'Element $(ID) is missing in specified elements',
|
|
529
|
+
enum: 'Enum $(ID) is missing in specified enum values',
|
|
530
|
+
} );
|
|
538
531
|
}
|
|
539
532
|
else {
|
|
540
533
|
for (const prop in selem) {
|
|
@@ -543,8 +536,6 @@ function populate( model ) {
|
|
|
543
536
|
ielem[prop] = selem[prop];
|
|
544
537
|
// required for gensrc mode of to-csn.js, otherwise the annotation
|
|
545
538
|
// may be lost during recompilation.
|
|
546
|
-
// TODO: Clarify: Should gensrc add this annotation to the column or as an
|
|
547
|
-
// annotate statement? Currently it's at the column.
|
|
548
539
|
ielem[prop].$priority = 'annotate';
|
|
549
540
|
wasAnnotated = true;
|
|
550
541
|
}
|
|
@@ -552,7 +543,7 @@ function populate( model ) {
|
|
|
552
543
|
if (!ielem.typeProps$)
|
|
553
544
|
setLink(ielem, 'typeProps$', Object.create(null));
|
|
554
545
|
// Note: At this point in time, effectiveType() was likely not called on the
|
|
555
|
-
// element, yet. Setting it here, we can't compare it to
|
|
546
|
+
// element, yet. Setting it here, we can't compare it to its value from _origin.
|
|
556
547
|
ielem.typeProps$[prop] = selem[prop];
|
|
557
548
|
}
|
|
558
549
|
}
|
|
@@ -602,17 +593,19 @@ function populate( model ) {
|
|
|
602
593
|
|
|
603
594
|
function populateQuery( query ) {
|
|
604
595
|
if (query._combined || !query.from || !query.$tableAliases)
|
|
605
|
-
// already done or $join query or parse error
|
|
596
|
+
// already done (TODO: re-check!) or $join query or parse error
|
|
606
597
|
return query;
|
|
607
598
|
setLink( query, '_combined', Object.create(null) );
|
|
608
599
|
query.$inlines = [];
|
|
609
600
|
forEachGeneric( query, '$tableAliases', resolveTabRef );
|
|
610
|
-
|
|
611
601
|
initFromColumns( query, query.columns );
|
|
612
602
|
if (query.excludingDict) {
|
|
613
603
|
for (const name in query.excludingDict)
|
|
614
604
|
resolveExcluding( name, query._combined, query.excludingDict, query );
|
|
615
605
|
}
|
|
606
|
+
// TODO: should we to set some falsy values? E.g. with $self.*, cyclic from?
|
|
607
|
+
// Yes, when element names cannot fully be determined (wrong source ref,
|
|
608
|
+
// cyclic, ...) BTW, similar with `includes`
|
|
616
609
|
return query;
|
|
617
610
|
|
|
618
611
|
function resolveTabRef( alias ) {
|
|
@@ -652,10 +645,11 @@ function populate( model ) {
|
|
|
652
645
|
return null;
|
|
653
646
|
// If we allow CDL-style casts of `expand`s to associations in the future, we
|
|
654
647
|
// need to ignore an explicit type, i.e. not getOrigin():
|
|
655
|
-
const assoc = resolvePath( elem.value, '
|
|
648
|
+
const assoc = resolvePath( elem.value, 'column', elem );
|
|
656
649
|
if (!effectiveType( assoc )?.target)
|
|
657
650
|
return initFromColumns( elem, elem.expand );
|
|
658
|
-
const { targetMax } = path[path.length - 1].cardinality ||
|
|
651
|
+
const { targetMax } = path[path.length - 1].cardinality ||
|
|
652
|
+
getInheritedProp( assoc, 'cardinality' ) || {};
|
|
659
653
|
if (targetMax && (targetMax.val === '*' || targetMax.val > 1)) {
|
|
660
654
|
elem.items = { location: elem.expand[$location] };
|
|
661
655
|
setLink( elem.items, '_outer', elem );
|
|
@@ -666,9 +660,9 @@ function populate( model ) {
|
|
|
666
660
|
// TODO: make this function shorter - make part of this (e.g. setting
|
|
667
661
|
// parent/name) also be part of definer.js
|
|
668
662
|
// TODO: query is actually the elemParent, where the new elements are added to
|
|
669
|
-
// top-level:
|
|
670
|
-
// inline:
|
|
671
|
-
// expand:
|
|
663
|
+
// top-level: ( query, query.columns )
|
|
664
|
+
// inline: ( queryOrColParent, col.inline, col )
|
|
665
|
+
// expand: ( col, col.expand )
|
|
672
666
|
function initFromColumns( query, columns, inlineHead = undefined ) {
|
|
673
667
|
const elemsParent = query.items || query;
|
|
674
668
|
if (!inlineHead) {
|
|
@@ -677,7 +671,11 @@ function populate( model ) {
|
|
|
677
671
|
query._main.elements = elemsParent.elements;
|
|
678
672
|
}
|
|
679
673
|
|
|
680
|
-
|
|
674
|
+
if (!columns)
|
|
675
|
+
columns = [ { val: '*' } ];
|
|
676
|
+
|
|
677
|
+
for (let i = 0; i < columns.length; ++i) {
|
|
678
|
+
const col = columns[i];
|
|
681
679
|
if (col.val === '*') {
|
|
682
680
|
const siblings = wildcardSiblings( columns, query );
|
|
683
681
|
expandWildcard( col, siblings, inlineHead, query );
|
|
@@ -689,15 +687,16 @@ function populate( model ) {
|
|
|
689
687
|
col.kind = '$inline';
|
|
690
688
|
col.name = {};
|
|
691
689
|
// a name for this internal symtab entry (e.g. '.2' to avoid clashes
|
|
692
|
-
// with real elements) is only relevant for
|
|
690
|
+
// with real elements) is only relevant for `cdsc -R`/debugging
|
|
693
691
|
const q = userQuery( query );
|
|
694
692
|
q.$inlines.push( col );
|
|
693
|
+
dependsOnSilent( q, col );
|
|
695
694
|
// or use userQuery( query ) in the following, too?
|
|
696
695
|
setMemberParent( col, `.${ q.$inlines.length }`, query );
|
|
697
696
|
initFromColumns( query, col.inline, col );
|
|
698
697
|
}
|
|
699
698
|
else if (!col.$replacement) {
|
|
700
|
-
const id = ensureColumnName( col, query );
|
|
699
|
+
const id = ensureColumnName( col, i, query );
|
|
701
700
|
col.kind = 'element';
|
|
702
701
|
dictAdd( elemsParent.elements, id, col, ( name, location ) => {
|
|
703
702
|
error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
|
|
@@ -710,25 +709,36 @@ function populate( model ) {
|
|
|
710
709
|
}
|
|
711
710
|
|
|
712
711
|
// TODO: probably do this already in definer.js
|
|
713
|
-
function ensureColumnName( col, query ) {
|
|
712
|
+
function ensureColumnName( col, colIndex, query ) {
|
|
714
713
|
if (col.name)
|
|
715
714
|
return col.name.id;
|
|
716
715
|
if (col.inline || col.val === '*')
|
|
717
716
|
return '';
|
|
718
717
|
const path = col.value &&
|
|
719
|
-
(col.value.path || !col.value.args && col.value.func
|
|
720
|
-
if (path
|
|
721
|
-
const last =
|
|
718
|
+
(col.value.path || !col.value.args && col.value.func?.path);
|
|
719
|
+
if (path) {
|
|
720
|
+
const last = path.length && !path.broken && path[path.length - 1];
|
|
722
721
|
if (last) {
|
|
723
722
|
col.name = { id: last.id || '', location: last.location, $inferred: 'as' };
|
|
724
723
|
return col.name.id;
|
|
725
724
|
}
|
|
726
725
|
}
|
|
727
|
-
else if (col.
|
|
728
|
-
|
|
726
|
+
else if (col.expand || col.value && (col._pathHead || query._parent.kind !== 'select')) {
|
|
727
|
+
// _pathHead => inline/expand; _parent -> only allowed in sub-selects
|
|
728
|
+
error( 'query-req-name', [ col.value?.location || col.location, query ], {},
|
|
729
729
|
'Alias name is required for this select item' );
|
|
730
730
|
}
|
|
731
|
-
|
|
731
|
+
else if (col.value) {
|
|
732
|
+
col.name = {
|
|
733
|
+
// NOTE: If the alias is changed, corresponding name-clash tests must be updated as well!
|
|
734
|
+
id: `$_column_${ colIndex + 1 }`,
|
|
735
|
+
location: col.value.location || col.location,
|
|
736
|
+
$inferred: '$internal',
|
|
737
|
+
};
|
|
738
|
+
col.$inferred = '$internal';
|
|
739
|
+
return col.name.id;
|
|
740
|
+
}
|
|
741
|
+
// invent a name for code completion in expression, see also #10596
|
|
732
742
|
col.name = {
|
|
733
743
|
id: '',
|
|
734
744
|
location: col.value && col.value.location || col.location,
|
|
@@ -768,8 +778,9 @@ function populate( model ) {
|
|
|
768
778
|
return siblings;
|
|
769
779
|
|
|
770
780
|
let seenWildcard = null;
|
|
781
|
+
let colIndex = 0;
|
|
771
782
|
for (const col of columns) {
|
|
772
|
-
const id = ensureColumnName( col, query );
|
|
783
|
+
const id = ensureColumnName( col, colIndex, query );
|
|
773
784
|
if (id) {
|
|
774
785
|
col.$replacement = !seenWildcard;
|
|
775
786
|
siblings[id] = !(id in siblings) && col;
|
|
@@ -777,6 +788,7 @@ function populate( model ) {
|
|
|
777
788
|
else if (col.val === '*') {
|
|
778
789
|
seenWildcard = true;
|
|
779
790
|
}
|
|
791
|
+
++colIndex;
|
|
780
792
|
}
|
|
781
793
|
return siblings;
|
|
782
794
|
}
|
|
@@ -788,10 +800,13 @@ function populate( model ) {
|
|
|
788
800
|
const inferred = query._main.$inferred;
|
|
789
801
|
const excludingDict = (colParent || query).excludingDict || Object.create(null);
|
|
790
802
|
|
|
791
|
-
const envParent = wildcard._pathHead; // TODO: rename _pathHead to
|
|
803
|
+
const envParent = wildcard._pathHead; // TODO: rename _pathHead to _columnParent
|
|
792
804
|
// console.log('S1:',location.line,location.col,
|
|
793
805
|
// envParent&&!!envParent._origin&&envParent._origin.name)
|
|
794
|
-
const env =
|
|
806
|
+
const env = wildcardColumnEnv( wildcard, query );
|
|
807
|
+
if (!env)
|
|
808
|
+
return;
|
|
809
|
+
|
|
795
810
|
// if (envParent) console.log('S2:',location.line,location.col,
|
|
796
811
|
// envParent?.name,envParent?._origin?.name,
|
|
797
812
|
// Object.keys(env),Object.keys(elements))
|
|
@@ -851,14 +866,25 @@ function populate( model ) {
|
|
|
851
866
|
}
|
|
852
867
|
}
|
|
853
868
|
|
|
854
|
-
function
|
|
869
|
+
function wildcardColumnEnv( wildcard, query ) { // etc. wildcard._pathHead;
|
|
855
870
|
// if (envParent) console.log( 'CE:', envParent._origin, query );
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
871
|
+
const colParent = wildcard._pathHead;
|
|
872
|
+
if (!colParent)
|
|
873
|
+
return userQuery( query )._combined; // see combinedSourcesOrParentElements
|
|
874
|
+
|
|
875
|
+
const head = resolvePath( colParent.value, 'column', colParent );
|
|
876
|
+
// eslint-disable-next-line no-nested-ternary
|
|
877
|
+
if (!head
|
|
878
|
+
? !columnRefStartsWithSelf( colParent )
|
|
879
|
+
: head._main
|
|
880
|
+
? userQuery( head ) !== userQuery( query )
|
|
881
|
+
: head._main !== query._main)
|
|
882
|
+
return nestedElements( wildcard );
|
|
883
|
+
|
|
884
|
+
error( 'def-unexpected-wildcard', [ wildcard.location, colParent ], { code: '*' },
|
|
885
|
+
'Unexpected $(CODE) (wildcard) after $self/association to self reference' );
|
|
886
|
+
model.$assert = null; // explains cyclic dependencies
|
|
887
|
+
return null;
|
|
862
888
|
}
|
|
863
889
|
|
|
864
890
|
function reportReplacement( sibling, navElem, query ) {
|
|
@@ -902,7 +928,7 @@ function populate( model ) {
|
|
|
902
928
|
function setElementOrigin( queryElem, navElem, name, location ) {
|
|
903
929
|
const sourceElem = navElem._origin;
|
|
904
930
|
const alias = navElem._parent;
|
|
905
|
-
// always expand * to path with table alias (reason: columns
|
|
931
|
+
// always expand * to path with table alias (reason: columns $user etc)
|
|
906
932
|
const path = [ { id: alias.name.id, location }, { id: name, location } ];
|
|
907
933
|
queryElem.value = { path, location };
|
|
908
934
|
setLink( path[0], '_navigation', alias );
|
|
@@ -931,7 +957,7 @@ function populate( model ) {
|
|
|
931
957
|
// TODO: Custom kind?
|
|
932
958
|
if (elem.$isSpecifiedElement)
|
|
933
959
|
return false;
|
|
934
|
-
const assocTarget =
|
|
960
|
+
const assocTarget = assoc.target._artifact;
|
|
935
961
|
let target = assocTarget;
|
|
936
962
|
// console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
|
|
937
963
|
// 'RED').toString())
|
|
@@ -1139,9 +1165,10 @@ function populate( model ) {
|
|
|
1139
1165
|
function isDirectProjection( proj, base ) {
|
|
1140
1166
|
return proj.kind === 'entity' && // not event
|
|
1141
1167
|
// direct proj (TODO: or should we add them to another list?)
|
|
1168
|
+
// TODO: delete ENTITY._from
|
|
1142
1169
|
proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
|
|
1143
1170
|
proj._from && proj._from.length === 1 &&
|
|
1144
|
-
base === resolvePath( proj._from[0], 'from', proj );
|
|
1171
|
+
base === resolvePath( proj._from[0], 'from', proj.query );
|
|
1145
1172
|
}
|
|
1146
1173
|
|
|
1147
1174
|
// Auto-exposure -----------------------------------------------------------
|
|
@@ -1152,7 +1179,8 @@ function populate( model ) {
|
|
|
1152
1179
|
return target.$autoexpose;
|
|
1153
1180
|
const origTarget = target;
|
|
1154
1181
|
const chain = [];
|
|
1155
|
-
|
|
1182
|
+
const alias1 = target._from?.[0]; // TODO: delete ENTITY._from ?
|
|
1183
|
+
let source = alias1 && resolvePath( alias1, 'from', alias1._parent );
|
|
1156
1184
|
// query source ref might not have been resolved yet, cycle avoided as
|
|
1157
1185
|
// setAutoExposed() sets $autoexpose and a second call on same art would
|
|
1158
1186
|
// return false
|
|
@@ -1160,7 +1188,8 @@ function populate( model ) {
|
|
|
1160
1188
|
// stop at first ancestor with annotation or at non-query entity
|
|
1161
1189
|
chain.push( target );
|
|
1162
1190
|
target = source;
|
|
1163
|
-
|
|
1191
|
+
const alias = target._from?.[0]; // TODO: delete ENTITY._from ?
|
|
1192
|
+
source = alias && resolvePath( alias, 'from', alias._parent );
|
|
1164
1193
|
}
|
|
1165
1194
|
const autoexpose = target.$autoexpose;
|
|
1166
1195
|
if (typeof autoexpose === 'boolean') {
|
|
@@ -159,7 +159,8 @@ function propagate( model ) {
|
|
|
159
159
|
(!target.type.$inferred || target.type.$inferred === 'cast');
|
|
160
160
|
const keys = Object.keys( source );
|
|
161
161
|
for (const prop of keys) {
|
|
162
|
-
|
|
162
|
+
// TODO: warning with competing props from multi-includes
|
|
163
|
+
if (target[prop] !== undefined || source[prop] === undefined)
|
|
163
164
|
continue;
|
|
164
165
|
const transformer = props[prop] || props[prop.charAt(0)];
|
|
165
166
|
if (transformer)
|
|
@@ -174,9 +175,9 @@ function propagate( model ) {
|
|
|
174
175
|
while (elem._parent.kind === 'element')
|
|
175
176
|
elem = elem._parent;
|
|
176
177
|
if (elem !== source) {
|
|
177
|
-
if (
|
|
178
|
+
if (target.notNull === undefined && elem.notNull !== undefined)
|
|
178
179
|
props.notNull( 'notNull', target, elem );
|
|
179
|
-
if (
|
|
180
|
+
if (target.virtual === undefined && elem.virtual !== undefined)
|
|
180
181
|
props.virtual( 'virtual', target, elem );
|
|
181
182
|
}
|
|
182
183
|
}
|