@sap/cds-compiler 6.3.4 → 6.4.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 +54 -0
- package/LICENSE +32 -0
- package/README.md +14 -2
- package/bin/cdsse.js +0 -3
- package/doc/CHANGELOG_BETA.md +1 -1
- package/doc/CHANGELOG_DEPRECATED.md +1 -1
- package/lib/base/message-registry.js +7 -0
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +2 -0
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/checks.js +37 -26
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/extend.js +39 -50
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +29 -6
- package/lib/compiler/resolve.js +13 -3
- package/lib/compiler/shared.js +31 -25
- package/lib/compiler/tweak-assocs.js +86 -28
- package/lib/compiler/xpr-rewrite.js +70 -38
- package/lib/edm/annotations/edmJson.js +206 -37
- package/lib/edm/csn2edm.js +13 -0
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +106 -72
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1500 -1509
- package/lib/json/to-csn.js +8 -5
- package/lib/language/genericAntlrParser.js +0 -0
- package/lib/main.js +19 -16
- package/lib/model/csnRefs.js +589 -521
- package/lib/model/csnUtils.js +26 -7
- package/lib/model/enrichCsn.js +1 -0
- package/lib/parsers/AstBuildingParser.js +72 -27
- package/lib/render/toCdl.js +2 -1
- package/lib/render/toHdbcds.js +6 -3
- package/lib/render/toSql.js +5 -0
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +4 -1
- package/lib/transform/db/cdsPersistence.js +17 -18
- package/lib/transform/db/expansion.js +179 -3
- package/lib/transform/db/flattening.js +16 -5
- package/lib/transform/db/rewriteCalculatedElements.js +79 -283
- package/lib/transform/effective/main.js +8 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/forRelationalDB.js +21 -80
- package/lib/transform/localized.js +65 -110
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
- package/lib/transform/transformUtils.js +23 -21
- package/lib/transform/translateAssocsToJoins.js +7 -5
- package/lib/transform/tupleExpansion.js +16 -3
- package/package.json +1 -1
- package/doc/DeprecatedOptions_v2.md +0 -150
- package/doc/NameResolution.md +0 -837
- package/lib/transform/parseExpr.js +0 -415
package/lib/model/csnRefs.js
CHANGED
|
@@ -170,26 +170,46 @@
|
|
|
170
170
|
// any reference in the entity is inspected: with data for the query
|
|
171
171
|
// hierarchy, query number, table aliases and links from a column to its
|
|
172
172
|
// respective inferred element.
|
|
173
|
-
// - TODO: some `name` property would also be useful (set with `initDefinition`)
|
|
174
173
|
|
|
175
|
-
//
|
|
174
|
+
// Property name convention in cache:
|
|
176
175
|
//
|
|
177
|
-
// -
|
|
178
|
-
// -
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
//
|
|
182
|
-
// in queries always the main artifact (see `$next` for name resolution)
|
|
183
|
-
// - _env on non-string path item: environment provided by the ref so far,
|
|
184
|
-
// next path item is element in it
|
|
185
|
-
// - _ref on non-string `type` or `from` ref, or on alias: the referred def/elem
|
|
176
|
+
// - $PropName: to other cache object (with proto), dictionary (w/o proto), or scalar
|
|
177
|
+
// - _PropName, PropName: to CSN object value or dictionary of CSN node (_PropName),
|
|
178
|
+
// or CSN dictionary (PropName)
|
|
179
|
+
|
|
180
|
+
// Property `_parent` in cache set with initDefinition() is set for members:
|
|
186
181
|
//
|
|
187
|
-
// -
|
|
182
|
+
// - for an action, param or element: the node containing the dictionary of …,
|
|
183
|
+
// → elements of the leading/other query have main/query as _parent
|
|
184
|
+
// - for a `returns`/`items` or anonymous `target`/`targetAspect`: the node containing
|
|
185
|
+
// the object (remark: in XSN, the _parent link of elements/items in items differs)
|
|
186
|
+
// - for any query: use main as _parent
|
|
187
|
+
// - for a mixin or table alias: use query select as _parent
|
|
188
|
+
// - for a column: TODO
|
|
189
|
+
|
|
190
|
+
// Additional properties in cache set with initDefinition():
|
|
191
|
+
//
|
|
192
|
+
// - $name: name for a main artifact (in `definitions`) or a column
|
|
193
|
+
// - _keys: the foreign keys as dictionary
|
|
194
|
+
// - $origin$step: for Univeral CSN (TODO: describe further)
|
|
195
|
+
// - $queries on def: array of caches for all queryies, null in anonymous aspects
|
|
188
196
|
// - $queryNumber: the index position +1 of a query inside the $queries array
|
|
189
|
-
// -
|
|
190
|
-
// - _select: value of the `SELECT` property of a query (or value of `projection`)
|
|
197
|
+
// - _select: value of the `SELECT` or `projection` property of a query
|
|
191
198
|
// - elements: the elements of the query (original CSN elements from query or main)
|
|
199
|
+
// - $next: the “upper” query cache, used when searching for a table alias
|
|
200
|
+
// - $aliases: dictionary of caches for table aliases
|
|
201
|
+
// (currently not stored as cache of table alias, has _ref/_select and elements)
|
|
192
202
|
// - _element on query column: the corresponding element
|
|
203
|
+
// - _column on element of a query entity: the column “creating” it
|
|
204
|
+
|
|
205
|
+
// Other properties set on-demand:
|
|
206
|
+
//
|
|
207
|
+
// - _ref on object with a `ref` property: cached return value of artifactRef{,.from}
|
|
208
|
+
// - _env on non-string path item: environment provided by the ref so far,
|
|
209
|
+
// next path item is element in it
|
|
210
|
+
// - _effectiveType on def/member/items: cached result of effectiveType()
|
|
211
|
+
// - _origin on def/member/items: the "prototype" (via type or relevant ref)
|
|
212
|
+
|
|
193
213
|
|
|
194
214
|
'use strict';
|
|
195
215
|
|
|
@@ -250,7 +270,7 @@ function csnRefs( csn, universalReady ) {
|
|
|
250
270
|
// const { definitions } = csn;
|
|
251
271
|
const cache = new WeakMap();
|
|
252
272
|
setCache( BUILTIN_TYPE, '_origin', null );
|
|
253
|
-
if (universalReady === 'init-all')
|
|
273
|
+
if (universalReady === 'init-all') // TODO: remove
|
|
254
274
|
initAllDefinitions();
|
|
255
275
|
|
|
256
276
|
// Functions which set the new `baseEnv`:
|
|
@@ -263,79 +283,241 @@ function csnRefs( csn, universalReady ) {
|
|
|
263
283
|
return getCache( pathItem, '_env' );
|
|
264
284
|
} );
|
|
265
285
|
};
|
|
266
|
-
artifactRef.from =
|
|
286
|
+
artifactRef.from = artifactRefFrom;
|
|
267
287
|
return {
|
|
288
|
+
// init and drop caches: ----------------------------------------------------
|
|
289
|
+
initAllDefinitions, // TODO: delete
|
|
290
|
+
initDefinition,
|
|
291
|
+
dropDefinitionCache,
|
|
292
|
+
// artifact references, effective type, and origin: -------------------------
|
|
293
|
+
artifactRef, // with prop `from`, see above
|
|
268
294
|
effectiveType,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
queryForElements,
|
|
295
|
+
getOrigin: art => cached( art, '_origin', getOriginRaw ),
|
|
296
|
+
// the main API function: ---------------------------------------------------
|
|
272
297
|
inspectRef,
|
|
273
|
-
|
|
298
|
+
// file and semantic location for CSN path: ---------------------------------
|
|
299
|
+
msgLocations,
|
|
300
|
+
// small cache functions ----------------------------------------------------
|
|
274
301
|
getColumn: elem => getCache( elem, '_column' ),
|
|
275
302
|
getElement: col => getCache( col, '_element' ),
|
|
276
303
|
/** Returns the column's name; either explicit, implicit or internal one. */
|
|
277
|
-
getColumnName: col => getCache( col, '$
|
|
304
|
+
getColumnName: col => getCache( col, '$name' ),
|
|
305
|
+
/** Return the query cache (with `elements`) for the query. */
|
|
306
|
+
queryForElements: query => query && cache.get( query.projection || query ),
|
|
278
307
|
$getQueries: def => getCache( def, '$queries' ), // unstable API
|
|
279
|
-
initDefinition,
|
|
280
|
-
initAllDefinitions,
|
|
281
|
-
dropDefinitionCache,
|
|
282
|
-
targetAspect,
|
|
283
|
-
msgLocations,
|
|
284
308
|
__getCache_forEnrichCsnDebugging: obj => cache.get( obj ),
|
|
309
|
+
// functions which we could return top-level: -------------------------------
|
|
310
|
+
targetAspect,
|
|
311
|
+
queryOrMain,
|
|
285
312
|
};
|
|
286
313
|
|
|
314
|
+
// Init and drop caches: ------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
function initAllDefinitions() { // TODO: delete
|
|
317
|
+
for (const name of Object.keys( csn.definitions || {}))
|
|
318
|
+
initDefinition( name );
|
|
319
|
+
}
|
|
320
|
+
|
|
287
321
|
/**
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* (This function could be simplified if we would use JS prototypes for type refs.)
|
|
322
|
+
* Initialize the cache for the definition named `main` and all its members,
|
|
323
|
+
* including queries and embedded anonymous aspects.
|
|
291
324
|
*
|
|
292
|
-
*
|
|
325
|
+
* Return the initialized artifact.
|
|
293
326
|
*/
|
|
294
|
-
function
|
|
295
|
-
const
|
|
296
|
-
if (
|
|
297
|
-
|
|
327
|
+
function initDefinition( main ) {
|
|
328
|
+
const name = typeof main === 'string' && main;
|
|
329
|
+
if (name) {
|
|
330
|
+
main = csn.definitions[name];
|
|
331
|
+
setCache( main, '$name', name );
|
|
332
|
+
}
|
|
333
|
+
// TODO: some --test-mode check that the argument is in ‹csn›.definitions ?
|
|
334
|
+
if (!main || getCache( main, '$queries' ) !== undefined) // already computed
|
|
335
|
+
return main;
|
|
298
336
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
337
|
+
traverseDef( main, null, null, null, initNode );
|
|
338
|
+
const queries = cached( main, '$queries', allQueries );
|
|
339
|
+
for (const qcache of queries || []) {
|
|
340
|
+
const { _select } = qcache;
|
|
341
|
+
const { elements } = _select;
|
|
342
|
+
if (elements) {
|
|
343
|
+
for (const n of Object.keys( elements ))
|
|
344
|
+
traverseDef( elements[n], _select, n, 'element', initNode );
|
|
345
|
+
}
|
|
346
|
+
if (_select.mixin) {
|
|
347
|
+
for (const n of Object.keys( _select.mixin ))
|
|
348
|
+
setCache( _select.mixin[n], '_parent', _select ); // relevant initNode() part
|
|
349
|
+
}
|
|
307
350
|
}
|
|
308
|
-
|
|
309
|
-
|
|
351
|
+
return main;
|
|
352
|
+
}
|
|
310
353
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
354
|
+
function initNode( art, parent, name, kind ) {
|
|
355
|
+
setCache( art, '_parent', parent );
|
|
356
|
+
if (art.keys) // TODO: what about managed w/o keys in Univeral CSN?
|
|
357
|
+
setCache( art, '_keys', getKeysDict( art ) );
|
|
358
|
+
if (kind === 'target') {
|
|
359
|
+
// Prevent re-initialization of anonymous aspect with initDefinition():
|
|
360
|
+
// (that would be with parent: null which would be wrong)
|
|
361
|
+
setCache( art, '$queries', null );
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (art.type || !kind) // with type, top-level, query or mixin
|
|
365
|
+
return;
|
|
366
|
+
const { $origin } = art;
|
|
367
|
+
if (typeof $origin === 'object') // null, […], {…}
|
|
368
|
+
return;
|
|
369
|
+
const step = $origin || name;
|
|
370
|
+
if (parent.$origin ||
|
|
371
|
+
parent.type && kind !== 'enum' && parent.$origin !== null ||
|
|
372
|
+
getCache( parent, '$origin$step' ))
|
|
373
|
+
setCache( art, '$origin$step', (kind === 'element' ? step : { [kind]: step }) );
|
|
316
374
|
}
|
|
317
375
|
|
|
318
376
|
/**
|
|
319
|
-
*
|
|
377
|
+
* Get the array of all (sub-)queries (value of the `SELECT`/`projection`
|
|
378
|
+
* property) inside the given `main` artifact (of `main.query`).
|
|
379
|
+
*
|
|
380
|
+
* @param {CSN.Definition} main
|
|
381
|
+
* @returns {CSN.Query[]}
|
|
320
382
|
*/
|
|
321
|
-
function
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
383
|
+
function allQueries( main ) {
|
|
384
|
+
const all = [];
|
|
385
|
+
const projection = main.query || main.projection && main;
|
|
386
|
+
if (!projection)
|
|
387
|
+
return null;
|
|
388
|
+
traverseQuery( projection, null, null, function memorize( query, fromSelect, parentQuery ) {
|
|
389
|
+
if (query.ref) { // ref in from
|
|
390
|
+
// console.log('SQ:',query,cache.get(query))
|
|
391
|
+
const as = query.as || implicitAs( query.ref );
|
|
392
|
+
const _ref = artifactRefFrom( query );
|
|
393
|
+
// TODO: really eval from refs here and not on-demand?
|
|
394
|
+
// if not, just have this (w/o elements) as cache elem on the CSN node
|
|
395
|
+
getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements, _parent: query };
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
const qcache = getQueryCache( parentQuery );
|
|
399
|
+
if (query !== main)
|
|
400
|
+
cache.set( query, qcache );
|
|
401
|
+
|
|
402
|
+
if (fromSelect) {
|
|
403
|
+
const $queryNumber = all.length + 1;
|
|
404
|
+
const alias = query.as || `$_select_${ $queryNumber }__`;
|
|
405
|
+
getCache( fromSelect, '$aliases' )[alias] = qcache;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const select = query.SELECT || query.projection;
|
|
409
|
+
if (select) {
|
|
410
|
+
cache.set( select, qcache ); // query and query.SELECT have the same cache qcache
|
|
411
|
+
qcache._select = select;
|
|
412
|
+
qcache._parent = main;
|
|
413
|
+
all.push( qcache );
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} );
|
|
417
|
+
all.forEach( function initElements( qcache, index ) {
|
|
418
|
+
qcache._parent = main;
|
|
419
|
+
qcache.$queryNumber = index + 1;
|
|
420
|
+
const { elements } = (index ? qcache._select : main);
|
|
421
|
+
qcache.elements = elements;
|
|
422
|
+
const { columns } = qcache._select;
|
|
423
|
+
if (elements && columns)
|
|
424
|
+
columns.map( (col, colIndex) => initColumnElement( col, colIndex, qcache ) );
|
|
425
|
+
else if (columns && !elements)
|
|
426
|
+
throw new ModelError( `Query elements not available: ${ Object.keys( (index ? qcache._select : main) ).join('+') }`);
|
|
427
|
+
} );
|
|
428
|
+
return all;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Return the cache object for a new query.
|
|
433
|
+
* Might re-use cache object with the `parentQuery`, or use `parentQuery`
|
|
434
|
+
* for link to next lexical environment.
|
|
435
|
+
*/
|
|
436
|
+
|
|
437
|
+
function getQueryCache( parentQuery ) {
|
|
438
|
+
if (!parentQuery)
|
|
439
|
+
return { $aliases: Object.create(null) };
|
|
440
|
+
const pcache = cache.get( parentQuery.projection || parentQuery );
|
|
441
|
+
if (!parentQuery.SET) // SELECT / projection: real sub query
|
|
442
|
+
return { $aliases: Object.create(null), $next: pcache };
|
|
443
|
+
// the parent query is a SET: that is not a sub query
|
|
444
|
+
// (works, as no sub queries are allowed in ORDER BY)
|
|
445
|
+
return (!pcache._select) // no leading query yet
|
|
446
|
+
? pcache // share cache with parent query
|
|
447
|
+
: { $aliases: Object.create(null), $next: pcache.$next };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* TODO: comment
|
|
452
|
+
*
|
|
453
|
+
* In the OData backend, the sub elements from a column with expand might
|
|
454
|
+
* have been “externalized” into a named type. No backward `_column` link
|
|
455
|
+
* from the (now the type) element is possible this way, of course...
|
|
456
|
+
*/
|
|
457
|
+
function initColumnElement( col, colIndex, parentElementOrQueryCache, externalElements ) {
|
|
458
|
+
if (col === '*')
|
|
459
|
+
return;
|
|
460
|
+
if (col.inline) {
|
|
461
|
+
col.inline.map( c => initColumnElement( c, null, parentElementOrQueryCache, externalElements ) );
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
setCache( col, '_parent', // not set for query (has property _select)
|
|
465
|
+
!parentElementOrQueryCache._select && parentElementOrQueryCache );
|
|
466
|
+
// TODO: why set _parent suddently to a cache and not the SELECT?
|
|
467
|
+
let as = columnAlias( col );
|
|
468
|
+
if (!as && colIndex !== null)
|
|
469
|
+
as = `$_column_${ colIndex + 1 }`;
|
|
470
|
+
setCache( col, '$name', as );
|
|
471
|
+
let type = parentElementOrQueryCache;
|
|
472
|
+
if (col.cast)
|
|
473
|
+
traverseType( col.cast, col, colIndex, 'column', initNode );
|
|
474
|
+
|
|
475
|
+
while (type.items)
|
|
476
|
+
type = type.items;
|
|
477
|
+
if (!type.elements) {
|
|
478
|
+
// in OData backend, the sub elements from a column with expand might have
|
|
479
|
+
// been “externalized” into a named type. No backward _column link is
|
|
480
|
+
// possible this way, of course...
|
|
481
|
+
type = artifactRef( type.type );
|
|
482
|
+
externalElements = true;
|
|
483
|
+
}
|
|
484
|
+
const elem = setCache( col, '_element', type.elements[as] );
|
|
485
|
+
if (elem && !externalElements) // TODO to.sql: something is strange if `elem` is not set
|
|
486
|
+
setCache( elem, '_column', col );
|
|
487
|
+
if (col.expand)
|
|
488
|
+
col.expand.map( c => initColumnElement( c, null, elem, externalElements ) );
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function dropDefinitionCache( main ) {
|
|
492
|
+
const queries = getCache( main, '$queries' );
|
|
493
|
+
if (!queries) // not yet initialized
|
|
494
|
+
return;
|
|
495
|
+
if (!cache.delete( main )) // not yet initialized
|
|
496
|
+
return;
|
|
497
|
+
for (const qcache of queries || []) {
|
|
498
|
+
const { _select } = qcache;
|
|
499
|
+
for (const n of Object.keys( _select.mixin || {} ))
|
|
500
|
+
cache.delete( _select.mixin[n] );
|
|
501
|
+
dropColumnsCache( _select.columns );
|
|
502
|
+
traverseDef( _select, null, null, null, a => cache.delete( a ) ); // elements
|
|
503
|
+
}
|
|
504
|
+
traverseDef( main, null, null, null, a => cache.delete( a ) );
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function dropColumnsCache( select ) {
|
|
508
|
+
if (!select)
|
|
509
|
+
return;
|
|
510
|
+
for (const col of select.columns || select.expand || select.inline || []) {
|
|
511
|
+
dropColumnsCache( col );
|
|
512
|
+
cache.delete( select );
|
|
513
|
+
}
|
|
334
514
|
}
|
|
335
515
|
|
|
516
|
+
// Artifact references, effective type, and origin: ---------------------------
|
|
517
|
+
|
|
336
518
|
/**
|
|
337
519
|
* Return the object pointing to by the artifact reference (in 'type',
|
|
338
|
-
* 'includes', 'target'
|
|
520
|
+
* 'includes', 'target'). For `from`, use artifactRefFrom()!
|
|
339
521
|
*
|
|
340
522
|
* @param {CSN.ArtifactReferencePath|string} ref
|
|
341
523
|
* @param {any} [notFound] Value that is returned in case the artifact reference
|
|
@@ -362,12 +544,11 @@ function csnRefs( csn, universalReady ) {
|
|
|
362
544
|
throw new ModelError( `Unknown artifact reference: ${ typeof ref !== 'string' ? JSON.stringify(ref.ref) : ref }` );
|
|
363
545
|
}
|
|
364
546
|
|
|
365
|
-
function
|
|
366
|
-
|
|
367
|
-
const art = artifactFromRef( ref );
|
|
547
|
+
function artifactRefFrom( ref ) {
|
|
548
|
+
const art = cached( ref, '_ref', r => navigationEnv( artifactPathRef( r ) ) );
|
|
368
549
|
if (art)
|
|
369
550
|
return art;
|
|
370
|
-
throw new ModelError( `Unknown artifact reference: ${
|
|
551
|
+
throw new ModelError( `Unknown artifact reference: ${ JSON.stringify(ref.ref) }` );
|
|
371
552
|
}
|
|
372
553
|
|
|
373
554
|
function artifactPathRef( ref ) {
|
|
@@ -380,35 +561,53 @@ function csnRefs( csn, universalReady ) {
|
|
|
380
561
|
return art;
|
|
381
562
|
}
|
|
382
563
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
564
|
+
/**
|
|
565
|
+
* @param {CSN.Artifact} art
|
|
566
|
+
*/
|
|
567
|
+
function navigationEnv( art, staticAssoc ) {
|
|
568
|
+
let env = effectiveType( art );
|
|
569
|
+
// here, we do not care whether it is semantically ok to navigate into sub
|
|
570
|
+
// elements of array items (that is the task of the core compiler /
|
|
571
|
+
// semantic check)
|
|
572
|
+
while (env.items)
|
|
573
|
+
env = effectiveType( env.items );
|
|
574
|
+
if (env.elements) // shortcut
|
|
575
|
+
return env;
|
|
576
|
+
const target = (staticAssoc ? targetAspect( env ) : env.target || env.targetAspect);
|
|
577
|
+
if (typeof target !== 'string')
|
|
578
|
+
return target || env;
|
|
579
|
+
return initDefinition( target );
|
|
393
580
|
}
|
|
394
581
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
582
|
+
/**
|
|
583
|
+
* Return the type relevant for name resolution, i.e. the object which has a
|
|
584
|
+
* `target`, `elements`, `enum` property, or no `type` property.
|
|
585
|
+
* (This function could be simplified if we would use JS prototypes for type refs.)
|
|
586
|
+
*
|
|
587
|
+
* @param {CSN.ArtifactWithRefs} art
|
|
588
|
+
*/
|
|
589
|
+
function effectiveType( art ) {
|
|
590
|
+
const cachedType = getCache( art, '_effectiveType' );
|
|
591
|
+
if (cachedType !== undefined)
|
|
592
|
+
return cachedType;
|
|
593
|
+
|
|
594
|
+
const chain = [];
|
|
595
|
+
let origin;
|
|
596
|
+
while (getCache( art, '_effectiveType' ) === undefined &&
|
|
597
|
+
(origin = cached( art, '_origin', getOriginRaw )) &&
|
|
598
|
+
!art.elements && !art.target && !art.targetAspect && !art.enum && !art.items) {
|
|
599
|
+
chain.push( art );
|
|
600
|
+
setCache( art, '_effectiveType', 0 ); // initial setting in case of cycles
|
|
601
|
+
art = origin;
|
|
602
|
+
}
|
|
603
|
+
if (!chain.length)
|
|
604
|
+
return setCache( art, '_effectiveType', art );
|
|
409
605
|
|
|
410
|
-
|
|
411
|
-
|
|
606
|
+
if (getCache( art, '_effectiveType' ) === 0)
|
|
607
|
+
throw new ModelError( 'Circular type reference');
|
|
608
|
+
const type = getCache( art, '_effectiveType' ) || art;
|
|
609
|
+
chain.forEach( a => setCache( a, '_effectiveType', type ) );
|
|
610
|
+
return type;
|
|
412
611
|
}
|
|
413
612
|
|
|
414
613
|
function getOriginRaw( art ) {
|
|
@@ -435,6 +634,16 @@ function csnRefs( csn, universalReady ) {
|
|
|
435
634
|
return originNavigation( origin, step );
|
|
436
635
|
}
|
|
437
636
|
|
|
637
|
+
function boundActionOrMain( art ) {
|
|
638
|
+
while (art.kind !== 'action' && art.kind !== 'function') {
|
|
639
|
+
const p = getCache( art, '_parent' );
|
|
640
|
+
if (!p)
|
|
641
|
+
return art;
|
|
642
|
+
art = p;
|
|
643
|
+
}
|
|
644
|
+
return art;
|
|
645
|
+
}
|
|
646
|
+
|
|
438
647
|
function getOriginExplicit( $origin ) { // null, […], {…}
|
|
439
648
|
if (!$origin)
|
|
440
649
|
return null;
|
|
@@ -455,117 +664,28 @@ function csnRefs( csn, universalReady ) {
|
|
|
455
664
|
return navigationEnv( art, true ).elements[step];
|
|
456
665
|
|
|
457
666
|
if (step.action)
|
|
458
|
-
return
|
|
667
|
+
return effectiveArtifactFor( art, 'actions' )[step.action];
|
|
459
668
|
if (step.param)
|
|
460
|
-
return
|
|
669
|
+
return effectiveArtifactFor( art, 'params' )[step.param];
|
|
461
670
|
if (step.returns)
|
|
462
|
-
return
|
|
671
|
+
return effectiveArtifactFor( art, 'returns' );
|
|
463
672
|
if (step.enum)
|
|
464
673
|
return navigationEnv( art, true ).enum[step.enum];
|
|
465
674
|
if (step.items)
|
|
466
675
|
return effectiveType( art ).items;
|
|
467
676
|
if (step.target)
|
|
468
677
|
return targetAspect( effectiveType( art ) );
|
|
469
|
-
|
|
678
|
+
const [ prop, val ] = Object.entries( step )[0];
|
|
679
|
+
throw new CompilerAssertion( `Illegal navigation step {${ prop }: "${ val }"}` );
|
|
470
680
|
}
|
|
471
681
|
|
|
472
|
-
function
|
|
682
|
+
function effectiveArtifactFor( art, property ) {
|
|
473
683
|
while (!art[property])
|
|
474
|
-
art =
|
|
684
|
+
art = cached( art, '_origin', getOriginRaw );
|
|
475
685
|
return art[property];
|
|
476
686
|
}
|
|
477
687
|
|
|
478
|
-
|
|
479
|
-
while (art.kind !== 'action' && art.kind !== 'function') {
|
|
480
|
-
const p = getCache( art, '_parent' );
|
|
481
|
-
if (!p)
|
|
482
|
-
return art;
|
|
483
|
-
art = p;
|
|
484
|
-
}
|
|
485
|
-
return art;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function queryForElements( query ) {
|
|
489
|
-
return query && cache.get( query.projection || query );
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function initAllDefinitions() {
|
|
493
|
-
for (const name of Object.keys( csn.definitions || {}))
|
|
494
|
-
initDefinition( name );
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function initDefinition( main ) {
|
|
498
|
-
const name = typeof main === 'string' && main;
|
|
499
|
-
if (name) {
|
|
500
|
-
main = csn.definitions[name];
|
|
501
|
-
setCache( main, '$name', name );
|
|
502
|
-
}
|
|
503
|
-
// TODO: some --test-mode check that the argument is in ‹csn›.definitions ?
|
|
504
|
-
if (!main || getCache( main, '$queries' ) !== undefined) // already computed
|
|
505
|
-
return main;
|
|
506
|
-
traverseDef( main, null, null, null, initNode );
|
|
507
|
-
const queries = cached( main, '$queries', allQueries );
|
|
508
|
-
for (const qcache of queries || []) {
|
|
509
|
-
const { _select } = qcache;
|
|
510
|
-
const { elements } = _select;
|
|
511
|
-
if (elements) {
|
|
512
|
-
for (const n of Object.keys( elements ))
|
|
513
|
-
traverseDef( elements[n], _select, 'element', n, initNode );
|
|
514
|
-
}
|
|
515
|
-
if (_select.mixin) {
|
|
516
|
-
for (const n of Object.keys( _select.mixin ))
|
|
517
|
-
setCache( _select.mixin[n], '_parent', _select ); // relevant initNode() part
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
return main;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
function initNode( art, parent, kind, name ) {
|
|
524
|
-
setCache( art, '_parent', parent );
|
|
525
|
-
if (art.keys)
|
|
526
|
-
setCache(art, '_keys', getKeysDict( art ));
|
|
527
|
-
if (kind === 'target') {
|
|
528
|
-
// Prevent re-initialization of anonymous aspect with initDefinition():
|
|
529
|
-
// (that would be with parent: null which would be wrong)
|
|
530
|
-
setCache( art, '$queries', null );
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
if (art.type || !kind) // with type, top-level, query or mixin
|
|
534
|
-
return;
|
|
535
|
-
const { $origin } = art;
|
|
536
|
-
if (typeof $origin === 'object') // null, […], {…}
|
|
537
|
-
return;
|
|
538
|
-
const step = $origin || name;
|
|
539
|
-
if (parent.$origin ||
|
|
540
|
-
parent.type && kind !== 'enum' && parent.$origin !== null ||
|
|
541
|
-
getCache( parent, '$origin$step' ))
|
|
542
|
-
setCache( art, '$origin$step', (kind === 'element' ? step : { [kind]: step }) );
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function dropDefinitionCache( main ) {
|
|
546
|
-
const queries = getCache( main, '$queries' );
|
|
547
|
-
if (!queries) // not yet initialized
|
|
548
|
-
return;
|
|
549
|
-
if (!cache.delete( main )) // not yet initialized
|
|
550
|
-
return;
|
|
551
|
-
for (const qcache of queries || []) {
|
|
552
|
-
const { _select } = qcache;
|
|
553
|
-
for (const n of Object.keys( _select.mixin || {} ))
|
|
554
|
-
cache.delete( _select.mixin[n] );
|
|
555
|
-
dropColumnsCache( _select.columns );
|
|
556
|
-
traverseDef( _select, null, null, null, a => cache.delete( a ) ); // elements
|
|
557
|
-
}
|
|
558
|
-
traverseDef( main, null, null, null, a => cache.delete( a ) );
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function dropColumnsCache( select ) {
|
|
562
|
-
if (!select)
|
|
563
|
-
return;
|
|
564
|
-
for (const col of select.columns || select.expand || select.inline || []) {
|
|
565
|
-
dropColumnsCache( col );
|
|
566
|
-
cache.delete( select );
|
|
567
|
-
}
|
|
568
|
-
}
|
|
688
|
+
// ----------------------------------------------------------------------------
|
|
569
689
|
|
|
570
690
|
/**
|
|
571
691
|
* @param {CSN.Path} csnPath
|
|
@@ -663,12 +783,10 @@ function csnRefs( csn, universalReady ) {
|
|
|
663
783
|
return resolvePath( path, elemParent.elements[head], null, 'query' );
|
|
664
784
|
}
|
|
665
785
|
if (!query) { // outside queries - TODO: items?
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
} // Ref to up_ in anonymous aspect
|
|
671
|
-
else if (!art && head === 'up_') {
|
|
786
|
+
// refs in annos on foreign keys use fk name, not fk ref name:
|
|
787
|
+
const dict = parent.elements ?? getCache( parent, '_keys' );
|
|
788
|
+
let art = dict?.[head];
|
|
789
|
+
if (!art && head === 'up_') { // ref to up_ in anonymous aspect
|
|
672
790
|
const up = getCache( parent, '_parent' );
|
|
673
791
|
const target = up && typeof up.target === 'string' && csn.definitions[up.target];
|
|
674
792
|
if (target && target.elements) {
|
|
@@ -757,6 +875,23 @@ function csnRefs( csn, universalReady ) {
|
|
|
757
875
|
};
|
|
758
876
|
}
|
|
759
877
|
|
|
878
|
+
// Return target when resolving references in 'keys'
|
|
879
|
+
function assocTarget( art, refCtx ) {
|
|
880
|
+
// Call contexts:
|
|
881
|
+
// 1. normal definition of association with explicit foreign keys
|
|
882
|
+
// 2. auto-redirected association with renaming of foreign keys
|
|
883
|
+
// (currently: `keys` always available on inherited associations)
|
|
884
|
+
// 3. user-induced redirection (in 'cast') with explicit foreign keys
|
|
885
|
+
// 4. original provided association def inside $origin with explicit foreign keys
|
|
886
|
+
// (outside $origin like 2)
|
|
887
|
+
const targetName = refCtx !== 'keys_origin' && art.target ||
|
|
888
|
+
art.$origin && art.$origin.target ||
|
|
889
|
+
art.cast.target;
|
|
890
|
+
return initDefinition( targetName );
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// File and semantic location for CSN path: -----------------------------------
|
|
894
|
+
|
|
760
895
|
/**
|
|
761
896
|
* Return [ Location, SemanticLocation ] from `csnPath`.
|
|
762
897
|
*/
|
|
@@ -855,336 +990,56 @@ function csnRefs( csn, universalReady ) {
|
|
|
855
990
|
}
|
|
856
991
|
artifact.element.push( elem || name + 1);
|
|
857
992
|
artifact.innerKind = kind || undefined;
|
|
858
|
-
++index;
|
|
859
|
-
}
|
|
860
|
-
function suffix( prop ) {
|
|
861
|
-
artifact.suffix = prop;
|
|
862
|
-
obj = null; // stop
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* Get the array of all (sub-)queries (value of the `SELECT`/`projection`
|
|
867
|
-
* property) inside the given `main` artifact (of `main.query`).
|
|
868
|
-
*
|
|
869
|
-
* @param {CSN.Definition} main
|
|
870
|
-
* @returns {CSN.Query[]}
|
|
871
|
-
*/
|
|
872
|
-
function allQueries( main ) {
|
|
873
|
-
const all = [];
|
|
874
|
-
const projection = main.query || main.projection && main;
|
|
875
|
-
if (!projection)
|
|
876
|
-
return null;
|
|
877
|
-
traverseQuery( projection, null, null, function memorize( query, fromSelect, parentQuery ) {
|
|
878
|
-
if (query.ref) { // ref in from
|
|
879
|
-
// console.log('SQ:',query,cache.get(query))
|
|
880
|
-
const as = query.as || implicitAs( query.ref );
|
|
881
|
-
const _ref = cached( query, '_from', artifactFromRef );
|
|
882
|
-
getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements, _parent: query };
|
|
883
|
-
}
|
|
884
|
-
else {
|
|
885
|
-
const qcache = getQueryCache( parentQuery );
|
|
886
|
-
if (query !== main)
|
|
887
|
-
cache.set( query, qcache );
|
|
888
|
-
|
|
889
|
-
if (fromSelect) {
|
|
890
|
-
const $queryNumber = all.length + 1;
|
|
891
|
-
const alias = query.as || `$_select_${ $queryNumber }__`;
|
|
892
|
-
getCache(fromSelect, '$aliases')[alias] = qcache;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const select = query.SELECT || query.projection;
|
|
896
|
-
if (select) {
|
|
897
|
-
cache.set( select, qcache ); // query and query.SELECT have the same cache qcache
|
|
898
|
-
qcache._select = select;
|
|
899
|
-
qcache._parent = main;
|
|
900
|
-
all.push( qcache );
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
} );
|
|
904
|
-
all.forEach( function initElements( qcache, index ) {
|
|
905
|
-
qcache._parent = main;
|
|
906
|
-
qcache.$queryNumber = index + 1;
|
|
907
|
-
const { elements } = (index ? qcache._select : main);
|
|
908
|
-
qcache.elements = elements;
|
|
909
|
-
const { columns } = qcache._select;
|
|
910
|
-
if (elements && columns)
|
|
911
|
-
columns.map( (col, colIndex) => initColumnElement( col, colIndex, qcache ) );
|
|
912
|
-
else if (columns && !elements)
|
|
913
|
-
throw new ModelError( `Query elements not available: ${ Object.keys( (index ? qcache._select : main) ).join('+') }`);
|
|
914
|
-
} );
|
|
915
|
-
return all;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
* Return the cache object for a new query.
|
|
920
|
-
* Might re-use cache object with the `parentQuery`, or use `parentQuery`
|
|
921
|
-
* for link to next lexical environment.
|
|
922
|
-
*/
|
|
923
|
-
|
|
924
|
-
function getQueryCache( parentQuery ) {
|
|
925
|
-
if (!parentQuery)
|
|
926
|
-
return { $aliases: Object.create(null) };
|
|
927
|
-
const pcache = cache.get( parentQuery.projection || parentQuery );
|
|
928
|
-
if (!parentQuery.SET) // SELECT / projection: real sub query
|
|
929
|
-
return { $aliases: Object.create(null), $next: pcache };
|
|
930
|
-
// the parent query is a SET: that is not a sub query
|
|
931
|
-
// (works, as no sub queries are allowed in ORDER BY)
|
|
932
|
-
return (!pcache._select) // no leading query yet
|
|
933
|
-
? pcache // share cache with parent query
|
|
934
|
-
: { $aliases: Object.create(null), $next: pcache.$next };
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
function initColumnElement( col, colIndex, parentElementOrQueryCache, externalElements ) {
|
|
938
|
-
if (col === '*')
|
|
939
|
-
return;
|
|
940
|
-
if (col.inline) {
|
|
941
|
-
col.inline.map( c => initColumnElement( c, null, parentElementOrQueryCache, externalElements ) );
|
|
942
|
-
return;
|
|
943
|
-
}
|
|
944
|
-
setCache( col, '_parent', // not set for query (has property _select)
|
|
945
|
-
!parentElementOrQueryCache._select && parentElementOrQueryCache );
|
|
946
|
-
let as = columnAlias( col );
|
|
947
|
-
if (!as && colIndex !== null)
|
|
948
|
-
as = `$_column_${ colIndex + 1 }`;
|
|
949
|
-
setCache( col, '$as', as );
|
|
950
|
-
let type = parentElementOrQueryCache;
|
|
951
|
-
if (col.cast)
|
|
952
|
-
traverseType( col.cast, col, 'column', colIndex, initNode );
|
|
953
|
-
|
|
954
|
-
while (type.items)
|
|
955
|
-
type = type.items;
|
|
956
|
-
if (!type.elements) {
|
|
957
|
-
// in OData backend, the sub elements from a column with expand might have
|
|
958
|
-
// been “externalized” into a named type. No backward _column link is
|
|
959
|
-
// possible this way, of course...
|
|
960
|
-
type = artifactRef( type.type );
|
|
961
|
-
externalElements = true;
|
|
962
|
-
}
|
|
963
|
-
const elem = setCache( col, '_element', type.elements[as] );
|
|
964
|
-
if (elem && !externalElements) // TODO to.sql: something is strange if `elem` is not set
|
|
965
|
-
setCache( elem, '_column', col );
|
|
966
|
-
if (col.expand)
|
|
967
|
-
col.expand.map( c => initColumnElement( c, null, elem, externalElements ) );
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
// property name convention in cache:
|
|
971
|
-
// - $name: to other cache object (with proto), dictionary (w/o proto), or scalar
|
|
972
|
-
// - _name, name: to CSN object value (_name) or dictionary (name)
|
|
973
|
-
|
|
974
|
-
function setCache( obj, prop, val ) {
|
|
975
|
-
let hidden = cache.get( obj );
|
|
976
|
-
if (!hidden) {
|
|
977
|
-
hidden = {};
|
|
978
|
-
cache.set( obj, hidden );
|
|
979
|
-
}
|
|
980
|
-
// TODO: we might keep the following with --test-mode
|
|
981
|
-
// if (hidden[prop] !== undefined) {
|
|
982
|
-
// console.log('RS:',prop,hidden[prop],val,obj)
|
|
983
|
-
// throw Error('RESET')
|
|
984
|
-
// }
|
|
985
|
-
hidden[prop] = val;
|
|
986
|
-
return val;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
function getCache( obj, prop ) {
|
|
990
|
-
const hidden = cache.get( obj );
|
|
991
|
-
return hidden && hidden[prop];
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
function cached( obj, prop, calc ) {
|
|
995
|
-
let hidden = cache.get( obj );
|
|
996
|
-
if (!hidden) {
|
|
997
|
-
hidden = {};
|
|
998
|
-
cache.set( obj, hidden );
|
|
999
|
-
}
|
|
1000
|
-
else if (hidden[prop] !== undefined) {
|
|
1001
|
-
return hidden[prop];
|
|
1002
|
-
}
|
|
1003
|
-
const val = calc( obj );
|
|
1004
|
-
hidden[prop] = val;
|
|
1005
|
-
return val;
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
/**
|
|
1010
|
-
* Foreign keys are stored in an array; for easier name resolution, create
|
|
1011
|
-
* a dictionary of them.
|
|
1012
|
-
*/
|
|
1013
|
-
function getKeysDict( art ) {
|
|
1014
|
-
const dict = Object.create(null);
|
|
1015
|
-
for (const key of art.keys)
|
|
1016
|
-
dict[key.as || implicitAs( key.ref )] = key;
|
|
1017
|
-
return dict;
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
/**
|
|
1021
|
-
* Return value of a query SELECT for the query node, or the main artifact,
|
|
1022
|
-
* i.e. a value with an `elements` property.
|
|
1023
|
-
* TODO: only used in forRelationalDB - move somewhere else
|
|
1024
|
-
*
|
|
1025
|
-
* @param {object} query node (object with SET or SELECT property)
|
|
1026
|
-
* @param {object} main definition
|
|
1027
|
-
*/
|
|
1028
|
-
function queryOrMain( query, main ) {
|
|
1029
|
-
while (query.SET)
|
|
1030
|
-
query = query.SET.args[0];
|
|
1031
|
-
if (query.SELECT && query.SELECT.elements)
|
|
1032
|
-
return query.SELECT;
|
|
1033
|
-
let leading = main.query || main;
|
|
1034
|
-
while (leading.SET)
|
|
1035
|
-
leading = leading.SET.args[0];
|
|
1036
|
-
// If an entity has both a projection and query property, the param `query`
|
|
1037
|
-
// can be the entity itself (when inspect is called with a csnPath containing
|
|
1038
|
-
// 'projection'), but `leading` can be its `query` property:
|
|
1039
|
-
if ((leading === query || leading === query.query) && main.elements)
|
|
1040
|
-
return main;
|
|
1041
|
-
throw new ModelError( `Query elements not available: ${ Object.keys( query ).join('+') }`);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
* Traverse query in pre-order
|
|
1046
|
-
*
|
|
1047
|
-
* The callback is called on the following XSN nodes inside the query `query`:
|
|
1048
|
-
* - a query node, which has property `SET` or `SELECT` (or `projection`),
|
|
1049
|
-
* - a query source node inside `from` if it has property `ref`,
|
|
1050
|
-
* - NOT on a `join` node inside `from`.
|
|
1051
|
-
*
|
|
1052
|
-
* @param {CSN.Query} query
|
|
1053
|
-
* @param {CSN.QuerySelect} fromSelect for query in `from`
|
|
1054
|
-
* @param {CSN.Query} parentQuery for a sub query (ex those in `from`)
|
|
1055
|
-
* @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched, parentQuery: CSN.Query) => void} callback
|
|
1056
|
-
*/
|
|
1057
|
-
function traverseQuery( query, fromSelect, parentQuery, callback ) {
|
|
1058
|
-
const select = query.SELECT || query.projection;
|
|
1059
|
-
if (select) {
|
|
1060
|
-
callback( query, fromSelect, parentQuery );
|
|
1061
|
-
traverseFrom( select.from, select, parentQuery, callback );
|
|
1062
|
-
for (const prop of [ 'columns', 'where', 'having' ]) {
|
|
1063
|
-
// all properties which can have sub queries (`join-on` also can)
|
|
1064
|
-
const expr = select[prop];
|
|
1065
|
-
if (expr)
|
|
1066
|
-
expr.forEach( q => traverseExpr( q, query, callback ) );
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
else if (query.SET) {
|
|
1070
|
-
callback( query, fromSelect, parentQuery );
|
|
1071
|
-
const { args } = query.SET;
|
|
1072
|
-
for (const q of args || [])
|
|
1073
|
-
traverseQuery( q, null, query, callback );
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
/**
|
|
1078
|
-
* @param {CSN.QueryFrom} from
|
|
1079
|
-
* @param {CSN.QuerySelect} fromSelect
|
|
1080
|
-
* @param {CSN.Query} parentQuery
|
|
1081
|
-
* @param {(from: CSN.QueryFrom, select: CSN.QuerySelect, parentQuery: CSN.Query) => void} callback
|
|
1082
|
-
*/
|
|
1083
|
-
function traverseFrom( from, fromSelect, parentQuery, callback ) {
|
|
1084
|
-
if (from.ref) {
|
|
1085
|
-
callback( from, fromSelect, parentQuery );
|
|
1086
|
-
}
|
|
1087
|
-
else if (from.args) { // join
|
|
1088
|
-
from.args.forEach( arg => traverseFrom( arg, fromSelect, parentQuery, callback ) );
|
|
1089
|
-
if (from.on) // join-on, potentially having a sub query (in xpr)
|
|
1090
|
-
from.on.forEach(arg => traverseExpr( arg, fromSelect, callback ));
|
|
1091
|
-
}
|
|
1092
|
-
else { // sub query in FROM
|
|
1093
|
-
traverseQuery( from, fromSelect, parentQuery, callback );
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
function traverseExpr( expr, parentQuery, callback ) {
|
|
1098
|
-
if (expr.SELECT || expr.SET)
|
|
1099
|
-
traverseQuery( expr, null, parentQuery, callback );
|
|
1100
|
-
for (const prop of [ 'args', 'xpr' ]) {
|
|
1101
|
-
// all properties which could have sub queries (directly or indirectly),
|
|
1102
|
-
const val = expr[prop];
|
|
1103
|
-
if (val && typeof val === 'object') {
|
|
1104
|
-
const args = Array.isArray( val ) ? val : Object.values( val );
|
|
1105
|
-
args.forEach( e => traverseExpr( e, parentQuery, callback ) );
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
function traverseDef( node, parent, kind, name, callback ) {
|
|
1111
|
-
callback( node, parent, kind, name );
|
|
1112
|
-
if (node.params) {
|
|
1113
|
-
for (const n of Object.keys( node.params ))
|
|
1114
|
-
traverseType( node.params[n], node, 'param', n, callback );
|
|
1115
|
-
}
|
|
1116
|
-
if (node.returns)
|
|
1117
|
-
traverseType( node.returns, node, 'returns', true, callback );
|
|
1118
|
-
traverseType( node, true, kind, name, callback );
|
|
1119
|
-
if (node.actions) {
|
|
1120
|
-
for (const n of Object.keys( node.actions ))
|
|
1121
|
-
traverseDef( node.actions[n], node, 'action', n, callback );
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
function traverseType( node, parent, kind, name, callback ) {
|
|
1126
|
-
if (parent !== true)
|
|
1127
|
-
callback( node, parent, kind, name );
|
|
1128
|
-
const target = targetAspect( node );
|
|
1129
|
-
if (target && typeof target === 'object' && target.elements) {
|
|
1130
|
-
callback( target, node, 'target', true );
|
|
1131
|
-
node = target;
|
|
1132
|
-
}
|
|
1133
|
-
else if (node.items) {
|
|
1134
|
-
let items = 0;
|
|
1135
|
-
while (node.items) {
|
|
1136
|
-
callback( node.items, node, 'items', ++items );
|
|
1137
|
-
node = node.items;
|
|
993
|
+
++index;
|
|
994
|
+
}
|
|
995
|
+
function suffix( prop ) {
|
|
996
|
+
artifact.suffix = prop;
|
|
997
|
+
obj = null; // stop
|
|
1138
998
|
}
|
|
1139
999
|
}
|
|
1140
|
-
if (node.elements) {
|
|
1141
|
-
for (const n of Object.keys( node.elements ))
|
|
1142
|
-
traverseDef( node.elements[n], node, 'element', n, callback );
|
|
1143
|
-
}
|
|
1144
|
-
if (node.enum) {
|
|
1145
|
-
for (const n of Object.keys( node.enum ))
|
|
1146
|
-
traverseDef( node.enum[n], node, 'enum', n, callback );
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
1000
|
|
|
1150
|
-
|
|
1151
|
-
const { $origin } = art;
|
|
1152
|
-
return art.targetAspect ||
|
|
1153
|
-
$origin && typeof $origin === 'object' && !Array.isArray( $origin ) && $origin.target ||
|
|
1154
|
-
art.target;
|
|
1155
|
-
}
|
|
1001
|
+
// Low-level cache functions: -------------------------------------------------
|
|
1156
1002
|
|
|
1157
|
-
function
|
|
1158
|
-
|
|
1159
|
-
|
|
1003
|
+
function setCache( obj, prop, val ) {
|
|
1004
|
+
let hidden = cache.get( obj );
|
|
1005
|
+
if (!hidden) {
|
|
1006
|
+
hidden = {};
|
|
1007
|
+
cache.set( obj, hidden );
|
|
1008
|
+
}
|
|
1009
|
+
// TODO: we might keep the following with --test-mode
|
|
1010
|
+
// if (hidden[prop] !== undefined) {
|
|
1011
|
+
// console.log('RS:',prop,hidden[prop],val,obj)
|
|
1012
|
+
// throw Error('RESET')
|
|
1013
|
+
// }
|
|
1014
|
+
hidden[prop] = val;
|
|
1015
|
+
return val;
|
|
1016
|
+
}
|
|
1160
1017
|
|
|
1161
|
-
function
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
return id.substring( id.lastIndexOf('.') + 1 );
|
|
1166
|
-
}
|
|
1018
|
+
function getCache( obj, prop ) {
|
|
1019
|
+
const hidden = cache.get( obj );
|
|
1020
|
+
return hidden && hidden[prop];
|
|
1021
|
+
}
|
|
1167
1022
|
|
|
1168
|
-
function
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
}
|
|
1023
|
+
function cached( obj, prop, calc ) {
|
|
1024
|
+
let hidden = cache.get( obj );
|
|
1025
|
+
if (!hidden) {
|
|
1026
|
+
hidden = {};
|
|
1027
|
+
cache.set( obj, hidden );
|
|
1028
|
+
}
|
|
1029
|
+
else if (hidden[prop] !== undefined) {
|
|
1030
|
+
return hidden[prop];
|
|
1031
|
+
}
|
|
1032
|
+
const val = calc( obj );
|
|
1033
|
+
hidden[prop] = val;
|
|
1034
|
+
return val;
|
|
1177
1035
|
}
|
|
1178
|
-
if (csnPath.length < 2 || head !== 'definitions' && head !== 'vocabularies')
|
|
1179
|
-
throw new CompilerAssertion( 'References outside definitions and vocabularies not supported yet' );
|
|
1180
|
-
const art = csn[head][csnPath[1]];
|
|
1181
|
-
return {
|
|
1182
|
-
index: 2, main: art, parent: art, art, query: null,
|
|
1183
|
-
};
|
|
1184
1036
|
}
|
|
1185
1037
|
|
|
1038
|
+
// CSN path analysing: ----------------------------------------------------------
|
|
1186
1039
|
|
|
1187
1040
|
/**
|
|
1041
|
+
* Analyse path `csnPath` in the CSN `csn` and call `resolve`.
|
|
1042
|
+
*
|
|
1188
1043
|
* @param {CSN.Path} csnPath
|
|
1189
1044
|
* @param {CSN.Model} csn
|
|
1190
1045
|
* @param {any} resolve
|
|
@@ -1255,6 +1110,8 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1255
1110
|
}
|
|
1256
1111
|
else if (prop === 'items' || prop === 'returns') {
|
|
1257
1112
|
art = obj[prop];
|
|
1113
|
+
// TODO: what about parent here?, see cache property _parent
|
|
1114
|
+
// BTW, still necessary to calculate parent here if we have _parent?
|
|
1258
1115
|
}
|
|
1259
1116
|
else if (prop === 'args') {
|
|
1260
1117
|
isName = true; // for named arguments
|
|
@@ -1276,6 +1133,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1276
1133
|
baseEnv = resolve.expandInline( obj, baseCtx, main, query, parent, baseEnv );
|
|
1277
1134
|
refCtx = prop;
|
|
1278
1135
|
}
|
|
1136
|
+
// TODO: for on condition in expand, also set an environment
|
|
1279
1137
|
isName = prop;
|
|
1280
1138
|
}
|
|
1281
1139
|
else if (prop === 'on') {
|
|
@@ -1309,8 +1167,160 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1309
1167
|
return resolve( obj, refCtx, main, query, parent, baseEnv );
|
|
1310
1168
|
}
|
|
1311
1169
|
|
|
1170
|
+
function startCsnPath( csnPath, csn ) {
|
|
1171
|
+
const head = csnPath[0];
|
|
1172
|
+
if (typeof head !== 'string') {
|
|
1173
|
+
const {
|
|
1174
|
+
main, parent, art, query,
|
|
1175
|
+
} = head;
|
|
1176
|
+
return {
|
|
1177
|
+
index: 1, main, parent, art, query,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
if (csnPath.length < 2 || head !== 'definitions' && head !== 'vocabularies')
|
|
1181
|
+
throw new CompilerAssertion( 'References outside definitions and vocabularies not supported yet' );
|
|
1182
|
+
const art = csn[head][csnPath[1]];
|
|
1183
|
+
return {
|
|
1184
|
+
index: 2, main: art, parent: art, art, query: null,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// CSN traversal functions: -----------------------------------------------------
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Traverse an artifact `node` and its members, but without queries, in pre-order.
|
|
1192
|
+
*
|
|
1193
|
+
* Call `callback` on all nodes with the following arguments:
|
|
1194
|
+
*
|
|
1195
|
+
* - `node`: the current node
|
|
1196
|
+
* - `parent`: the parent node (containing the dictionary),
|
|
1197
|
+
* see the initial file comment for cache property _parent for details
|
|
1198
|
+
* - `name`: the name of the current node in the parent dictionary, or
|
|
1199
|
+
* true for `returns` parameter, and increasing number for `items` object
|
|
1200
|
+
* - `kind`
|
|
1201
|
+
*
|
|
1202
|
+
*
|
|
1203
|
+
* The callback is called on the following CSN nodes inside the query `query`:
|
|
1204
|
+
* - a query node, which has property `SET` or `SELECT` (or `projection`),
|
|
1205
|
+
* - a query source node inside `from` if it has property `ref`,
|
|
1206
|
+
* - NOT on a `join` node inside `from`.
|
|
1207
|
+
*
|
|
1208
|
+
* @param {CSN.Query} query
|
|
1209
|
+
* @param {CSN.QuerySelect} fromSelect for query in `from`
|
|
1210
|
+
* @param {CSN.Query} parentQuery for a sub query (ex those in `from`)
|
|
1211
|
+
* @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched, parentQuery: CSN.Query) => void} callback
|
|
1212
|
+
*/
|
|
1213
|
+
|
|
1214
|
+
function traverseDef( node, parent, name, kind, callback ) {
|
|
1215
|
+
callback( node, parent, name, kind );
|
|
1216
|
+
if (node.params) {
|
|
1217
|
+
for (const n of Object.keys( node.params ))
|
|
1218
|
+
traverseType( node.params[n], node, n, 'param', callback );
|
|
1219
|
+
}
|
|
1220
|
+
if (node.returns)
|
|
1221
|
+
traverseType( node.returns, node, true, 'returns', callback );
|
|
1222
|
+
traverseType( node, true, name, kind, callback );
|
|
1223
|
+
if (node.actions) {
|
|
1224
|
+
for (const n of Object.keys( node.actions ))
|
|
1225
|
+
traverseDef( node.actions[n], node, n, 'action', callback );
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function traverseType( node, parent, name, kind, callback ) {
|
|
1230
|
+
if (parent !== true)
|
|
1231
|
+
callback( node, parent, name, kind );
|
|
1232
|
+
const target = targetAspect( node );
|
|
1233
|
+
if (target && typeof target === 'object' && target.elements) {
|
|
1234
|
+
callback( target, node, true, 'target' );
|
|
1235
|
+
node = target;
|
|
1236
|
+
}
|
|
1237
|
+
else if (node.items) {
|
|
1238
|
+
let items = 0;
|
|
1239
|
+
while (node.items) {
|
|
1240
|
+
callback( node.items, node, ++items, 'items' );
|
|
1241
|
+
node = node.items;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (node.elements) {
|
|
1245
|
+
for (const n of Object.keys( node.elements ))
|
|
1246
|
+
traverseDef( node.elements[n], node, n, 'element', callback );
|
|
1247
|
+
}
|
|
1248
|
+
if (node.enum) {
|
|
1249
|
+
for (const n of Object.keys( node.enum ))
|
|
1250
|
+
traverseDef( node.enum[n], node, n, 'enum', callback );
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* Traverse query in pre-order
|
|
1256
|
+
*
|
|
1257
|
+
* The callback is called on the following CSN nodes inside the query `query`:
|
|
1258
|
+
* - a query node, which has property `SET` or `SELECT` (or `projection`),
|
|
1259
|
+
* - a query source node inside `from` if it has property `ref`,
|
|
1260
|
+
* - NOT on a `join` node inside `from`.
|
|
1261
|
+
*
|
|
1262
|
+
* @param {CSN.Query} query
|
|
1263
|
+
* @param {CSN.QuerySelect} fromSelect for query in `from`
|
|
1264
|
+
* @param {CSN.Query} parentQuery for a sub query (ex those in `from`)
|
|
1265
|
+
* @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched, parentQuery: CSN.Query) => void} callback
|
|
1266
|
+
*/
|
|
1267
|
+
function traverseQuery( query, fromSelect, parentQuery, callback ) {
|
|
1268
|
+
const select = query.SELECT || query.projection;
|
|
1269
|
+
if (select) {
|
|
1270
|
+
callback( query, fromSelect, parentQuery );
|
|
1271
|
+
traverseFrom( select.from, select, parentQuery, callback );
|
|
1272
|
+
for (const prop of [ 'columns', 'where', 'having' ]) {
|
|
1273
|
+
// all properties which can have sub queries (`join-on` also can)
|
|
1274
|
+
const expr = select[prop];
|
|
1275
|
+
if (expr)
|
|
1276
|
+
expr.forEach( q => traverseExpr( q, query, callback ) );
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
else if (query.SET) {
|
|
1280
|
+
callback( query, fromSelect, parentQuery );
|
|
1281
|
+
const { args } = query.SET;
|
|
1282
|
+
for (const q of args || [])
|
|
1283
|
+
traverseQuery( q, null, query, callback );
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* @param {CSN.QueryFrom} from
|
|
1289
|
+
* @param {CSN.QuerySelect} fromSelect
|
|
1290
|
+
* @param {CSN.Query} parentQuery
|
|
1291
|
+
* @param {(from: CSN.QueryFrom, select: CSN.QuerySelect, parentQuery: CSN.Query) => void} callback
|
|
1292
|
+
*/
|
|
1293
|
+
function traverseFrom( from, fromSelect, parentQuery, callback ) {
|
|
1294
|
+
if (from.ref) {
|
|
1295
|
+
callback( from, fromSelect, parentQuery );
|
|
1296
|
+
}
|
|
1297
|
+
else if (from.args) { // join
|
|
1298
|
+
from.args.forEach( arg => traverseFrom( arg, fromSelect, parentQuery, callback ) );
|
|
1299
|
+
if (from.on) // join-on, potentially having a sub query (in xpr)
|
|
1300
|
+
from.on.forEach(arg => traverseExpr( arg, fromSelect, callback ));
|
|
1301
|
+
}
|
|
1302
|
+
else { // sub query in FROM
|
|
1303
|
+
traverseQuery( from, fromSelect, parentQuery, callback );
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function traverseExpr( expr, parentQuery, callback ) {
|
|
1308
|
+
if (expr.SELECT || expr.SET)
|
|
1309
|
+
traverseQuery( expr, null, parentQuery, callback );
|
|
1310
|
+
for (const prop of [ 'args', 'xpr' ]) {
|
|
1311
|
+
// all properties which could have sub queries (directly or indirectly),
|
|
1312
|
+
const val = expr[prop];
|
|
1313
|
+
if (val && typeof val === 'object') {
|
|
1314
|
+
const args = Array.isArray( val ) ? val : Object.values( val );
|
|
1315
|
+
args.forEach( e => traverseExpr( e, parentQuery, callback ) );
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Small CSN utility functions: -------------------------------------------------
|
|
1321
|
+
|
|
1312
1322
|
// A SELECT which is (unnecessarily) put into parentheses, the CSN
|
|
1313
|
-
// representation uses SET without `op` and args of length 1:
|
|
1323
|
+
// representation uses SET without `op` and args of length 1 in compiler <v6:
|
|
1314
1324
|
function isSelectQuery( query ) {
|
|
1315
1325
|
while (query.SET) {
|
|
1316
1326
|
const { args } = query.SET;
|
|
@@ -1332,13 +1342,71 @@ function columnAlias( col ) {
|
|
|
1332
1342
|
return col.as || (!col.args && col.func) || (col.ref && implicitAs( col.ref ));
|
|
1333
1343
|
}
|
|
1334
1344
|
|
|
1345
|
+
function implicitAs( ref ) {
|
|
1346
|
+
if (typeof ref !== 'string')
|
|
1347
|
+
ref = ref[ref.length - 1];
|
|
1348
|
+
const id = (typeof ref === 'string') ? ref : ref.id; // inlined `pathId`
|
|
1349
|
+
return id.substring( id.lastIndexOf('.') + 1 );
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function pathId( item ) {
|
|
1353
|
+
return (typeof item === 'string') ? item : item.id;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* Foreign keys are stored in an array; for easier name resolution, create
|
|
1358
|
+
* a dictionary of them.
|
|
1359
|
+
*/
|
|
1360
|
+
function getKeysDict( art ) {
|
|
1361
|
+
const dict = Object.create(null);
|
|
1362
|
+
for (const key of art.keys)
|
|
1363
|
+
dict[key.as || implicitAs( key.ref )] = key;
|
|
1364
|
+
return dict;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Return the target aspect (or target entity if that does not exist) of `art`.
|
|
1369
|
+
* Works with both CSN flavors `client` and `universal`.
|
|
1370
|
+
*/
|
|
1371
|
+
function targetAspect( art ) {
|
|
1372
|
+
const { $origin } = art;
|
|
1373
|
+
return ($origin
|
|
1374
|
+
? typeof $origin === 'object' && !Array.isArray( $origin ) && $origin.target
|
|
1375
|
+
: art.targetAspect
|
|
1376
|
+
) || art.target;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* Return value of a query SELECT for the query node, or the main artifact,
|
|
1381
|
+
* i.e. a value with an `elements` property.
|
|
1382
|
+
* TODO: only used in transformViewOrEntity() - make it use queryForElements()
|
|
1383
|
+
*
|
|
1384
|
+
* @param {object} query node (object with SET or SELECT property)
|
|
1385
|
+
* @param {object} main definition
|
|
1386
|
+
*/
|
|
1387
|
+
function queryOrMain( query, main ) {
|
|
1388
|
+
while (query.SET)
|
|
1389
|
+
query = query.SET.args[0];
|
|
1390
|
+
if (query.SELECT?.elements)
|
|
1391
|
+
return query.SELECT;
|
|
1392
|
+
let leading = main.query || main;
|
|
1393
|
+
while (leading.SET)
|
|
1394
|
+
leading = leading.SET.args[0];
|
|
1395
|
+
// If an entity has both a projection and query property, the param `query`
|
|
1396
|
+
// can be the entity itself (when inspect is called with a csnPath containing
|
|
1397
|
+
// 'projection'), but `leading` can be its `query` property:
|
|
1398
|
+
if ((leading === query || leading === query.query) && main.elements)
|
|
1399
|
+
return main;
|
|
1400
|
+
throw new ModelError( `Query elements not available: ${ Object.keys( query ).join('+') }`);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1335
1403
|
module.exports = {
|
|
1404
|
+
artifactProperties,
|
|
1336
1405
|
csnRefs,
|
|
1406
|
+
analyseCsnPath,
|
|
1337
1407
|
traverseQuery,
|
|
1338
|
-
|
|
1408
|
+
columnAlias,
|
|
1339
1409
|
implicitAs,
|
|
1340
|
-
getKeysDict,
|
|
1341
|
-
analyseCsnPath,
|
|
1342
1410
|
pathId,
|
|
1343
|
-
|
|
1411
|
+
getKeysDict,
|
|
1344
1412
|
};
|