@finos/legend-application-query 13.8.55 → 13.8.56

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/README.md CHANGED
@@ -1,3 +1,10 @@
1
1
  # @finos/legend-application-query
2
2
 
3
3
  Legend Query core
4
+
5
+ ## Documentation
6
+
7
+ - [Data Space Analytics, the Minimal Graph, and Entry Points](./docs/data-space-analytics-and-entry-points.md) —
8
+ why data space analytics exists, how the minimal graph is cached and built,
9
+ every entry point into the query editor, and the saved-query types each one
10
+ produces.
@@ -0,0 +1,644 @@
1
+ # Legend Query — Data Space Analytics, the Minimal Graph, and Entry Points
2
+
3
+ > Technical reference for `@finos/legend-application-query`.
4
+ >
5
+ > Audience: engineers working on Legend Query, the Data Space DSL extension
6
+ > (`@finos/legend-extension-dsl-data-space`), or the query builder
7
+ > (`@finos/legend-query-builder`).
8
+
9
+ This document explains:
10
+
11
+ 1. [Why data space analytics exists](#1-why-data-space-analytics-exists) — the cost model that motivates it.
12
+ 2. [The minimal graph](#2-the-minimal-graph) — what is cached, how it is keyed, and when it is used.
13
+ 3. [Entry points into Legend Query](#3-entry-points-into-legend-query) — every route that can open the query editor, and how each one builds its graph.
14
+ 4. [Saved query types](#4-saved-query-types) — how each entry point persists a query, and how the editor reconstitutes it.
15
+
16
+ ---
17
+
18
+ ## 1. Why data space analytics exists
19
+
20
+ ### 1.1 The cost of a full graph
21
+
22
+ Legend Query's query builder operates on a **PURE graph** — a fully resolved,
23
+ in-memory metamodel. Historically, opening the editor meant:
24
+
25
+ 1. Fetch **every entity** of the project version from the depot server.
26
+ 2. Fetch **every entity of every dependency** (transitively).
27
+ 3. Deserialize the protocol models (`V1_*`) and run the graph builder, which
28
+ resolves every reference (property types, association ends, mapping source
29
+ classes, store/connection pointers, …) into real metamodel objects.
30
+
31
+ For a large enterprise project with deep dependency trees, this dominates
32
+ time-to-first-interaction. And most of it is wasted work: to build a query
33
+ against a data space, the user needs the **classes reachable from one mapping**
34
+ plus the mapping/runtime pointers — not the whole model, not the persistence
35
+ pipelines, not the other 40 mappings in the project.
36
+
37
+ ### 1.2 The insight
38
+
39
+ A data space is a curated, published surface over a project:
40
+
41
+ - a fixed set of **execution contexts**, each pinning a `mapping` + `defaultRuntime`
42
+ - a documented model
43
+ - a set of executables (templates, services, functions)
44
+
45
+ All of that is **known at project build time**. It does not depend on who opens
46
+ it or what they intend to query. So it can be computed once, in the engine, at
47
+ publish time — and stored alongside the project version in the depot server as
48
+ a **file generation artifact**.
49
+
50
+ Legend Query then reads that artifact instead of recomputing it. That is
51
+ "data space analytics".
52
+
53
+ ### 1.3 What analytics buys us
54
+
55
+ | Without analytics | With analytics |
56
+ | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
57
+ | Fetch all project + dependency entities | Fetch one (small) set of artifact files, scoped to the data space |
58
+ | Build the full graph | Build a graph containing only the classes the mapping covers |
59
+ | Run mapping model coverage analysis against the engine (a network round trip per mapping) | Read the pre-computed coverage result out of the artifact |
60
+ | Compute element documentation, diagrams, executables client-side | Read them out of the artifact |
61
+
62
+ The artifacts are produced by the engine's artifact-generation extension keyed
63
+ `dataSpace-analytics`, and retrieved through
64
+ [`DataSpaceAnalysisHelper.ts`](../../legend-extension-dsl-data-space/src/graph-manager/action/analytics/DataSpaceAnalysisHelper.ts):
65
+
66
+ - `retrieveAnalyticsResultCache(...)` → the single legacy `AnalyticsResult.json` blob
67
+ - `retrieveDataspaceArtifactsCache(...)` → the **partitioned** artifact set (see below)
68
+
69
+ ---
70
+
71
+ ## 2. The minimal graph
72
+
73
+ ### 2.1 Artifact layout
74
+
75
+ The engine emits the analytics for a data space as **several files**, not one.
76
+ This partitioning is what makes the minimal graph possible.
77
+
78
+ | Artifact | Type | Contents |
79
+ | ---------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
80
+ | `AnalyticsResult.json` | `V1_DataSpaceAnalysisResult` | Data space metadata: title, description, tagged values, stereotypes, support info, **execution contexts** (name → mapping, defaultRuntime, compatibleRuntimes, datasets, mappingProvider), element docs, diagrams, executables |
81
+ | `MappingAnalysisCoveragePartition` | one per mapping | `V1_MappingModelCoverageAnalysisResult` — the _coverage_ (which classes/properties the mapping maps, and how) |
82
+ | `MappingModelCoveragePartition` | one per mapping | `model`: a **`V1_PureModelContextData`** containing exactly the model elements the mapping covers |
83
+
84
+ Defined in
85
+ [`V1_DataSpaceArtifacts.ts`](../../legend-graph/src/graph-manager/protocol/pure/v1/engine/artifactGeneration/V1_DataSpaceArtifacts.ts).
86
+
87
+ The split matters for two reasons:
88
+
89
+ - **Size.** The data space entity itself is _excluded_ from `AnalyticsResult.json`
90
+ (Legend Query reconstructs a synthetic `V1_DataSpace` from the analysis result
91
+ instead — see `buildDataSpaceAnalytics`). Coverage models are the bulk of the
92
+ payload, and they are only fetched per-mapping.
93
+ - **Granularity.** The user picks one execution context at a time, which pins
94
+ one mapping. Only that mapping's `MappingModelCoveragePartition` is needed to
95
+ build a graph the query builder can work against.
96
+
97
+ > A developer-only diagnostic route,
98
+ > `/dev/dataspace-inspector` ([`DataSpaceArtifactInspector.tsx`](../src/components/DataSpaceArtifactInspector.tsx)),
99
+ > exists to inspect the sizes of these artifacts for a given GAV + data space.
100
+
101
+ ### 2.2 "Cached minimal graph based on mapped classes"
102
+
103
+ The `MappingModelCoveragePartition.model` PMCD **is** the minimal graph seed.
104
+ It contains the model elements the mapping actually covers — the closure of
105
+ classes (and the enumerations, associations, and types they reach) reported by
106
+ mapping model coverage analysis, not the project's whole model.
107
+
108
+ Building the graph from it (in
109
+ [`V1_DSL_DataSpace_PureGraphManagerExtension.buildDataSpaceAnalytics`](../../legend-extension-dsl-data-space/src/graph-manager/protocol/pure/v1/V1_DSL_DataSpace_PureGraphManagerExtension.ts)):
110
+
111
+ ```
112
+ graphEntities =
113
+ pmcd.elements // the mapped model closure
114
+ + stub V1_Mapping per exec context // name/package only — no mapping body
115
+ + stub V1_PackageableRuntime // name/package + empty V1_EngineRuntime
116
+ + stub V1_DataProduct // for mappingProvider-backed contexts
117
+ + synthetic V1_DataSpace // rebuilt from the analysis result
118
+ - anything already in the graph // i.e. system elements
119
+ ```
120
+
121
+ then `graphManager.buildGraph(graph, graphEntities, …, { origin: LegendSDLC(gav) })`.
122
+
123
+ The mappings and runtimes are deliberately **stubs**: the query builder only
124
+ needs them as resolvable `PackageableElementReference` targets for the execution
125
+ context and for `RuntimePointer`. The real mapping body and connection details
126
+ are never needed client-side — execution is delegated to the engine, which
127
+ resolves them from the GAV origin stamped on the graph.
128
+
129
+ Two optional enrichments run afterwards, each independently failure-tolerant:
130
+
131
+ - `processFunctionForMinimalGraph(...)` — fetches only entities classified
132
+ `CORE_PURE_PATH.FUNCTION` for the project + dependencies, so the explorer can
133
+ offer usable functions. Populates `functionInfos` / dependency function infos.
134
+ - `processRuntimeInfo(...)` — fetches entities classified `CORE_PURE_PATH.RUNTIME`
135
+ to enrich the default runtime's info.
136
+
137
+ Both are wrapped in `try { … } catch { /* do nothing */ }`: losing function
138
+ suggestions is preferable to failing the editor load.
139
+
140
+ ### 2.3 The decision flow
141
+
142
+ `QueryEditorStore.buildGraphAndDataspaceAnalyticsResult(...)`
143
+ ([`QueryEditorStore.ts`](../src/stores/QueryEditorStore.ts)) is the single
144
+ entry point for every data-space-backed flow. It returns
145
+ `{ dataSpaceAnalysisResult, isLightGraphEnabled }`.
146
+
147
+ ```mermaid
148
+ flowchart TD
149
+ A[buildGraphAndDataspaceAnalyticsResult] --> B{minimal graph enabled?}
150
+ B -- no --> F[buildFullGraph]
151
+ B -- yes --> C[analyzeDataSpaceCoverage]
152
+ C --> D{artifacts present AND<br/>every exec context's mapping<br/>has a MappingModelCoveragePartition?}
153
+ D -- no --> E[throw GraphBuilderError] --> F
154
+ D -- yes --> G[build minimal graph from PMCD]
155
+ G --> H{resolved mapping has<br/>coverage entities?}
156
+ H -- no --> F
157
+ H -- yes --> I[isLightGraphEnabled = true]
158
+ F --> J[retrieveDataSpaceAnalysisFromCache<br/>legacy AnalyticsResult.json]
159
+ J --> K[isLightGraphEnabled = false]
160
+ ```
161
+
162
+ Two gates control whether the minimal path is even attempted:
163
+
164
+ - `applicationStore.config.options.TEMPORARY__enableMinimalGraph` — deployment-level config flag.
165
+ - `QueryEditorStore.enableMinialGraphForDataSpaceLoadingPerformance` — an
166
+ observable, per-session toggle. The editor UI exposes it, so a user can force
167
+ a full graph build when the light graph is insufficient
168
+ (see [`QueryEditor.tsx`](../src/components/QueryEditor.tsx)).
169
+
170
+ **The all-or-nothing check.** In `analyzeDataSpaceCoverage`, the minimal path is
171
+ taken only if _every_ execution context in the analysis result has a
172
+ corresponding `MappingModelCoveragePartition`:
173
+
174
+ ```ts
175
+ cachedAnalysisResult?.executionContexts.every(
176
+ (e) =>
177
+ mappingModelCoveragePartition.find((p) => p.mapping === e.mapping)
178
+ ?.model !== undefined,
179
+ );
180
+ ```
181
+
182
+ This is conservative on purpose: the user can switch execution contexts inside
183
+ the editor, and a partially-covered data space would strand them on a context
184
+ that cannot be built. If the check fails, a `GraphBuilderError` is thrown and
185
+ caught by the caller, which falls back to the full graph.
186
+
187
+ **Failure is always safe.** Every failure mode — missing artifacts, deserialization
188
+ error, incomplete coverage, missing PMCD for the resolved mapping — sets
189
+ `buildFullGraph = true`, discards the graph (`createNewGraph()`), and runs the
190
+ full build. The minimal graph is a pure optimization; correctness never depends
191
+ on it.
192
+
193
+ ### 2.4 Switching execution context inside the editor
194
+
195
+ Once the editor is open on a light graph, the graph only contains the classes
196
+ of the _initially resolved_ mapping. Switching execution context therefore
197
+ requires rebuilding.
198
+
199
+ That rebuild lives in
200
+ [`Core_LegendQueryApplicationPlugin.tsx`](../src/components/Core_LegendQueryApplicationPlugin.tsx)
201
+ and mirrors the logic in `buildDataSpaceAnalytics`: it creates a fresh graph,
202
+ seeds it with `mappingModelCoverageAnalysisResult.entities` for the newly
203
+ selected mapping plus the same stub mappings/runtimes/data space entity, and
204
+ rebuilds with the SDLC origin. If anything throws, it falls back to
205
+ `editorStore.buildFullGraph()`.
206
+
207
+ This is why `isLightGraphEnabled` is threaded onto the query builder state
208
+ (`LegendQueryDataSpaceQueryBuilderState`): the state has to know whether a
209
+ context switch requires a graph rebuild or is already fully covered.
210
+
211
+ ### 2.5 Minimal graphs for data products
212
+
213
+ Data products (`V1_DataProductArtifact`) follow the same principle with a
214
+ different artifact. `AbstractPureGraphManager.analyzeDataProductAndBuildMinimalGraph`
215
+ (implemented in
216
+ [`V1_PureGraphManager.ts`](../../legend-graph/src/graph-manager/protocol/pure/v1/V1_PureGraphManager.ts))
217
+ fetches the data product artifact from the depot and dispatches on access type:
218
+
219
+ - `MODEL` → `buildModelAccessDataProductAnalysis`
220
+ - `NATIVE` → `buildNativeAccessDataProductAnalysis`
221
+ - `LAKEHOUSE` → `buildLakehouseAccessDataProductAnalysis`
222
+
223
+ Each builds a graph containing only what that access path needs. When the
224
+ artifact has already been fetched (the common case — the store fetches it to
225
+ resolve the mapping path first), `buildDataProductAnalysis` is called directly
226
+ to avoid a second round trip.
227
+
228
+ ---
229
+
230
+ ## 3. Entry points into Legend Query
231
+
232
+ An "entry point" is a route that constructs a concrete `QueryEditorStore`
233
+ subclass. Routes are declared in
234
+ [`LegendQueryNavigation.ts`](../src/__lib__/LegendQueryNavigation.ts) and
235
+ [`DSL_DataSpace_LegendQueryNavigation.ts`](../src/__lib__/DSL_DataSpace_LegendQueryNavigation.ts),
236
+ and wired in [`LegendQueryWebApplication.tsx`](../src/components/LegendQueryWebApplication.tsx).
237
+
238
+ Every store implements the same two hooks:
239
+
240
+ ```ts
241
+ abstract initializeQueryBuilderState(stopWatch?): Promise<QueryBuilderState>;
242
+ abstract getPersistConfiguration(lambda, options?): QueryPersistConfiguration | undefined;
243
+ ```
244
+
245
+ `initializeQueryBuilderState` decides _what kind of query builder_ the user
246
+ gets. `getPersistConfiguration` decides _how a saved query is stamped_.
247
+
248
+ Each creator store also tags its query builder `sourceInfo` with a
249
+ `sourceType` (`LegendQuerySourceType` in
250
+ [`LegendQuerySourceInfo.ts`](../src/__lib__/LegendQuerySourceInfo.ts)):
251
+ `mapping`, `service`, `data-space`, `data-space.template`, `data-product`,
252
+ `data-product.sample`, `ingest`, or `unselected` for the bare editor. These
253
+ values are reported on `query-editor.initialize-query-creator.success` /
254
+ `.failure`, along with whether the most recently visited data space or data
255
+ product was reopened — so the entry points below map one-to-one onto the
256
+ telemetry.
257
+
258
+ ### 3.1 Mapping / Runtime (the "manual" flow)
259
+
260
+ | | |
261
+ | ------------- | --------------------------------------------------------------------------------------- |
262
+ | Route | `/create/manual/:gav/:mappingPath/:runtimePath` |
263
+ | Store | `MappingQueryCreatorStore` ([`QueryEditorStore.ts`](../src/stores/QueryEditorStore.ts)) |
264
+ | Builder state | `MappingQueryBuilderState` |
265
+ | Graph | **Full graph.** No analytics, no minimal graph. |
266
+
267
+ The rawest entry point: the user picks a mapping and a runtime directly. There
268
+ is no curated surface to derive a minimal model from, so the full project graph
269
+ is built. Changing mapping or runtime rewrites the URL (`updateCurrentLocation`)
270
+ so the flow stays deep-linkable.
271
+
272
+ Query search is scoped to the current project **and its dependencies** via
273
+ `decorateSearchSpecification`.
274
+
275
+ ### 3.2 Service
276
+
277
+ | | |
278
+ | ------------- | ------------------------------------------------------------------- |
279
+ | Route | `/create-from-service/:gav/:servicePath` (+ optional execution key) |
280
+ | Store | `ServiceQueryCreatorStore` |
281
+ | Builder state | `ServiceQueryBuilderState` |
282
+ | Graph | **Full graph.** |
283
+
284
+ Opens the editor seeded with an existing service's execution lambda. The store
285
+ asserts the service execution is a `PureExecution`, then calls
286
+ `initializeWithQuery(service.execution.func)` — which routes unsupported query
287
+ shapes into the query builder's "unsupported" handling rather than crashing.
288
+
289
+ For multi-execution services, the execution key selects the execution context;
290
+ changing it rewrites the URL.
291
+
292
+ Related setup flows that funnel into this: `/setup/clone-service-query`,
293
+ `/setup/load-project-service-query`, `/setup/update-existing-service-query`,
294
+ `/setup/productionize-query`.
295
+
296
+ ### 3.3 Data space
297
+
298
+ | | |
299
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
300
+ | Route | `/extensions/dataspace/:gav/:dataSpacePath/:executionContext?` (query params: `runtimePath`, `class`) |
301
+ | Store | `DataProductQueryCreatorStore` with a `QueryableLegacyDataProduct` ([`DataProductQueryCreatorStore.ts`](../src/stores/data-space/DataProductQueryCreatorStore.ts)) |
302
+ | Builder state | `LegendQueryDataSpaceQueryBuilderState` |
303
+ | Graph | **Minimal graph** via `buildGraphAndDataspaceAnalyticsResult` |
304
+
305
+ > **Naming caveat.** The store and queryable-element classes on this path are
306
+ > named `DataProductQueryCreatorStore` / `QueryableLegacyDataProduct`, dating
307
+ > from a period when data spaces were surfaced to users as "data products".
308
+ > That framing was reverted in
309
+ > [#5494](https://github.com/finos/legend-studio/pull/5494): the UI, command
310
+ > palette, and info panels for this path all say **Data Space** again, and
311
+ > "Data Product" now refers exclusively to the distinct element type in §3.4.
312
+ > The class names were left alone, so **read `DataProduct` in an identifier on
313
+ > this path as a historical artifact, not as a claim about which element type
314
+ > is being queried.** The discriminant that actually matters is the queryable
315
+ > element (`QueryableLegacyDataProduct` vs `QueryableDataProduct`) and the
316
+ > resulting builder state.
317
+
318
+ This is the flow section 2 describes. The store:
319
+
320
+ 1. Calls `buildGraphAndDataspaceAnalyticsResult(gav, executionContext, dataSpacePath)`.
321
+ 2. Resolves the data space out of the (minimal) graph via `getDataSpace(...)`.
322
+ 3. Resolves the execution context via `resolveExecutionContext(...)` — by name if
323
+ given, otherwise the data space's default, otherwise the first.
324
+ 4. Validates `mappingProvider`-backed contexts actually resolve to a mapping
325
+ (a context can source its mapping from a Data Product access point group —
326
+ a real cross-element reference, not a naming artifact; if that group is gone,
327
+ this is a hard error rather than a silent empty editor).
328
+ 5. Attaches **fallback Lakehouse runtimes** (`attachDataSpaceFallbackRuntimes`)
329
+ — see below.
330
+ 6. Constructs `LegendQueryDataSpaceQueryBuilderState`, hands it the
331
+ `mappingModelCoverageAnalysisResult` for the resolved mapping, and pins the
332
+ mapping plus — when the context has one — a `RuntimePointer` to its default
333
+ runtime.
334
+
335
+ **Execution contexts without a runtime.** `defaultRuntime` on a data space
336
+ execution context is optional. When a context sources its mapping from a Data
337
+ Product (`mappingProvider` is set) **and** declares no `defaultRuntime` **and**
338
+ the analytics report no compatible runtimes, `attachDataSpaceFallbackRuntimes`
339
+ fills the gap: it creates one shared `LakehouseRuntime` —
340
+ `_internal_::LakehouseRuntime`, via `createOrGetDataSpaceLakehouseFallbackRuntime`
341
+ — and assigns it as that context's `defaultRuntime`. Environment and warehouse
342
+ come from `resolveLakehouseEnvAndWarehouse`: the user's persisted Lakehouse
343
+ settings, else their entitlement environment from the contract server, with the
344
+ warehouse defaulting to `LAKEHOUSE_CONSUMER_DEFAULT_WH`. Snapshot versions are
345
+ resolved separately (`isSnapshot`).
346
+
347
+ The builder state exposes the result as `injectedLakehouseRuntime`. When set:
348
+
349
+ - the runtime selector is **locked** to that one runtime, labelled
350
+ `environment / warehouse`, and **Lakehouse Runtime Configuration** appears in
351
+ the setup panel's settings menu — the same modal data products use;
352
+ - `floatingExecutionElements` returns the runtime, so it rides along with the
353
+ SDLC pointer as a `V1_PureModelContextCombination` (the engine has never seen
354
+ an `_internal_` element);
355
+ - `buildExecutionContextExpression` emits `->with(dataProduct)->from(runtime)`
356
+ instead of `->from(mapping, runtime)`, because the mapping comes from the
357
+ Data Product, not from the project.
358
+
359
+ Contexts that are _not_ Data Product-backed and have no runtime simply get no
360
+ runtime pinned; nothing is injected.
361
+
362
+ The store also records visits in user data
363
+ (`LegendQueryUserDataHelper.getRecentlyVisitedDataSpaces`) so the setup screen
364
+ can offer recents, and so `onInitializeFailure` can evict stale entries.
365
+
366
+ `DataProductQueryCreatorStore` is shared with the data product flow (3.4) —
367
+ `LegendQueryableElement` is the abstraction over "the thing being queried":
368
+
369
+ ```
370
+ LegendQueryableElement (gav + path + execContext)
371
+ ├── QueryableLegacyDataProduct → dataSpacePath + executionContext (+ runtimePath, classPath)
372
+ └── QueryableDataProduct → dataProductPath + dataProductType + id
373
+ ```
374
+
375
+ When the store is constructed with **no** queryable element, it builds a
376
+ `LegendQueryBareQueryBuilderState` — the empty-state editor with a
377
+ data space / data product selector.
378
+
379
+ **Template queries.** Route
380
+ `/extensions/dataspace/:gav/:dataSpacePath/template/:templateQueryId` →
381
+ `DataSpaceTemplateQueryCreatorStore` (extends `BaseTemplateQueryCreatorStore`).
382
+ It also uses `buildGraphAndDataspaceAnalyticsResult`, but overrides `buildGraph`
383
+ to a no-op — the graph is built inside `initializeQueryBuilderState`, since the
384
+ template id is needed to resolve which execution context (and therefore which
385
+ mapping) to cover. `analyzeDataSpaceCoverage` accepts `templateQueryId` for
386
+ exactly this: `retrieveExecutionContextFromTemplateQueryId(...)`.
387
+
388
+ ### 3.4 Data products (native / model access / lakehouse)
389
+
390
+ | | |
391
+ | ------------- | --------------------------------------------------------------- |
392
+ | Route | `/data-product/:accessType/:gav/:dataProductPath/:accessId` |
393
+ | Store | `DataProductQueryCreatorStore` with a `QueryableDataProduct` |
394
+ | Builder state | `LegendQueryDataProductQueryBuilderState` |
395
+ | Graph | **Minimal graph** via `buildGraphAndDataproductAnalyticsResult` |
396
+
397
+ `accessType` is a `DataProductAccessType`:
398
+
399
+ | Access type | `accessId` means | Execution state |
400
+ | ----------- | ---------------------------- | ------------------------------------------- |
401
+ | `NATIVE` | native execution context key | `NativeModelDataProductExecutionState` |
402
+ | `MODEL` | model access point group id | `ModelAccessPointDataProductExecutionState` |
403
+ | `LAKEHOUSE` | lakehouse access point id | `LakehouseDataProductExecutionState` |
404
+
405
+ `QueryEditorStore.buildDataProductQueryBuilderState(...)` does the assembly:
406
+
407
+ 1. Fetch the `V1_DataProductArtifact` (`fetchDataProductArtifact`).
408
+ 2. Build the minimal graph + analysis (`buildGraphAndDataproductAnalyticsResult`).
409
+ 3. Resolve the data product from the graph and the execution state from `accessId`.
410
+ 4. For `MODEL` and `LAKEHOUSE`, synthesize an **adhoc Lakehouse runtime**
411
+ (`createLakehousePackageableRuntime`) from the user's lakehouse environment +
412
+ consumer warehouse, and register it on the graph under `INTERNAL_ELEMENT_PATH`
413
+ (`'_internal_'`).
414
+ These access paths have no modelled `PackageableRuntime` to point at.
415
+ 5. Call `withAdhocRuntime()` on the execution state and select that runtime, so
416
+ the user can execute without picking one.
417
+
418
+ The data product dropdown (`DataProductSelectorState`) lists data products from
419
+ the Lakehouse contract server's lite API, restricted to the **production**
420
+ environment (`getAllLiteDataProducts(PRODUCTION)`), and keeps only those with an
421
+ SDLC deployment origin. It falls back to a depot classifier search only when no
422
+ contract server client is configured. Data spaces in the same dropdown still
423
+ come from depot.
424
+
425
+ **Sample queries.** Route
426
+ `/data-product/native/sample-query/:gav/:dataProductPath/:sampleQueryId` →
427
+ `DataProductSampleQueryCreatorStore` (also a `BaseTemplateQueryCreatorStore`).
428
+
429
+ **Which "About" panel you get.** Since
430
+ [#5494](https://github.com/finos/legend-studio/pull/5494), the editor exposes
431
+ two separate info actions in
432
+ [`Core_LegendQueryApplicationPlugin.tsx`](../src/components/Core_LegendQueryApplicationPlugin.tsx),
433
+ and each keys strictly off the **builder state type** — not the store type:
434
+
435
+ | Action key | Enabled when | Opens |
436
+ | ------------------- | ------------------------------------------------------------ | ------------------------------ |
437
+ | `about-dataspace` | `queryBuilderState instanceof DataSpaceQueryBuilderState` | `setShowDataspaceInfo(true)` |
438
+ | `about-dataproduct` | `queryBuilderState instanceof DataProductQueryBuilderState` | `setShowDataProductInfo(true)` |
439
+ | `about-ingest` | `queryBuilderState instanceof IngestLegendQueryBuilderState` | `setShowIngestInfo(true)` |
440
+
441
+ This replaced a single `About Data Product` action whose predicate also tested
442
+ the store type (`ExistingQueryEditorStore`, `DataSpaceTemplateQueryCreatorStore`,
443
+ `DataProductQueryCreatorStore`). Because `DataProductQueryCreatorStore` backs
444
+ both §3.3 and §3.4, that store-based test could not tell the two apart — which
445
+ is precisely why the predicate now looks only at the builder state.
446
+
447
+ **Export actions unavailable for data products and ingest.** The same builder
448
+ state test gates the export menu. For `DataProductQueryBuilderState` and
449
+ `IngestLegendQueryBuilderState`, four actions are disabled with the tooltip
450
+ _"Not supported for Data Product or Ingest queries"_:
451
+
452
+ | Menu label | Key | Contributed by |
453
+ | ---------------------- | -------------------------------- | ------------------------------------------ |
454
+ | Curated Template Query | `promote-as-template-query` | `Core_LegendQueryApplicationPlugin` |
455
+ | Legend DataCube | `legend-datacube-query` | `Core_LegendQueryApplicationPlugin` |
456
+ | Productionized Query | `export-as-productionized-query` | `DSL_Service_LegendQueryApplicationPlugin` |
457
+ | DEV Service | `export-as-dev-service` | `DSL_Service_LegendQueryApplicationPlugin` |
458
+
459
+ Menu items supply the message through `getDisableMessage(queryBuilderState)`
460
+ (added alongside the static `disableMessage`), so the same item can say
461
+ _"Requires saved query"_ in one context and _"Not supported…"_ in another.
462
+ File-format exports (CSV etc.) are unaffected.
463
+
464
+ ### 3.5 Ingest definitions
465
+
466
+ | | |
467
+ | ------------- | ----------------------------------------------------------------------------------------------------------- |
468
+ | Route | `/ingest/:gav/:ingestDefinitionPath/:dataSet` |
469
+ | Store | `IngestQueryCreatorStore` ([`IngestQueryCreatorStore.ts`](../src/stores/ingest/IngestQueryCreatorStore.ts)) |
470
+ | Builder state | `IngestLegendQueryBuilderState` |
471
+ | Graph | **Targeted graph** — not analytics-driven |
472
+
473
+ Ingest takes a third approach. There is no analytics artifact; instead
474
+ `buildGraph` is overridden to:
475
+
476
+ 1. Fetch **every** ingest definition entity in the project version, by
477
+ classifier (`fetchIngestEntitiesByClassifier`), and cache the payloads in
478
+ `_ingestEntitiesByPath`. This is cheap and lets the source dropdown list
479
+ every ingest.
480
+ 2. Build **only the requested one** into the graph (`swapIngestInGraph`).
481
+
482
+ Swapping ingest definitions in the editor removes the current one from the graph
483
+ and builds the requested entity in its place, on demand — the other entities are
484
+ already in memory as protocol payloads.
485
+
486
+ Like model-access data products, ingest needs an adhoc Lakehouse runtime, added
487
+ as `_internal_` and auto-selected.
488
+
489
+ > `IngestDefinition` is currently self-contained as an entity. Once materialized
490
+ > views can reference other elements (mappings, classes, runtimes), this flow
491
+ > will need to fetch dependent entities or fall back to a full build. This is
492
+ > noted as a TODO on `IngestQueryCreatorStore.buildGraph`.
493
+
494
+ ### 3.6 Existing query
495
+
496
+ | | |
497
+ | ------------- | -------------------------------------------------------------------- |
498
+ | Route | `/edit/:queryId` (and `/edit/:queryId/cube` for the DataCube viewer) |
499
+ | Store | `ExistingQueryEditorStore` |
500
+ | Builder state | Whichever the saved query's execution context implies |
501
+ | Graph | Depends on the saved query — see [§4](#4-saved-query-types) |
502
+
503
+ This is the reverse direction: given a persisted query, reconstruct the entry
504
+ point it was created from. Covered next.
505
+
506
+ ---
507
+
508
+ ## 4. Saved query types
509
+
510
+ ### 4.1 The persisted shape
511
+
512
+ A saved query (`Query`, in
513
+ [`Query.ts`](../../legend-graph/src/graph-manager/action/query/Query.ts)) carries:
514
+
515
+ - **GAV** (`groupId`, `artifactId`, `versionId`, `originalVersionId`)
516
+ - **`content`** — the query lambda as PURE text (compact and stable across releases)
517
+ - **`executionContext: QueryExecutionContext`** — _what the query runs against_
518
+ - `taggedValues` / `stereotypes` — untyped metadata used for search and classification
519
+ - `defaultParameterValues`, `gridConfig`, ownership and timestamps
520
+
521
+ `mapping` and `runtime` are **deprecated** fields, superseded by
522
+ `QueryExecutionContext`. They are still read when reconstituting older queries.
523
+
524
+ ### 4.2 The execution context hierarchy
525
+
526
+ ```
527
+ QueryExecutionContext
528
+ ├── QueryExplicitExecutionContext mapping, runtime
529
+ ├── QueryDataSpaceExecutionContext dataSpacePath, executionKey
530
+ ├── QueryDataProductExecutionContext (abstract) dataProductPath
531
+ │ ├── QueryDataProductNativeExecutionContext executionKey
532
+ │ ├── QueryDataProductModelAccessExecutionContext accessPointGroupId
533
+ │ └── QueryDataProductLakehouseExecutionContext accessPointId, accessGroupId
534
+ └── QueryIngestExecutionContext ingestDefinitionPath, dataSet
535
+ ```
536
+
537
+ `QueryInfo` (the lightweight read model returned by `getQueryInfo`) mirrors this
538
+ with a parallel `QueryExecutionContextInfo` hierarchy — same discriminants,
539
+ paths as strings rather than references.
540
+
541
+ ### 4.3 Where the execution context comes from
542
+
543
+ Each query builder state produces its own via `getQueryExecutionContext()`,
544
+ called from `QueryEditorStore.buildQueryForPersistence`:
545
+
546
+ | Builder state | Produces |
547
+ | ------------------------------- | ----------------------------------------------------------------------------- |
548
+ | `QueryBuilderState` (base) | `QueryExplicitExecutionContext` — mapping + `RuntimePointer` |
549
+ | `DataSpaceQueryBuilderState` | `QueryDataSpaceExecutionContext` — `dataSpace.path` + `executionContext.name` |
550
+ | `DataProductQueryBuilderState` | one of the three data product contexts, by `executionState` type |
551
+ | `IngestLegendQueryBuilderState` | `QueryIngestExecutionContext` — ingest path + current `dataSet` |
552
+
553
+ Note the level of indirection this creates. A data space query does **not**
554
+ persist a mapping and runtime; it persists _"this data space, this execution
555
+ context"_. Reopening it re-resolves the mapping and runtime from the data space
556
+ at the pinned version. If the data space owner repoints an execution context to
557
+ a different mapping in a later version, queries saved against it follow.
558
+
559
+ ### 4.4 Tagged values
560
+
561
+ Distinct from the typed execution context, queries are also stamped with tagged
562
+ values on the `meta::pure::profiles::query` profile:
563
+
564
+ | Tag | Constant | Set by |
565
+ | ------------- | -------------------------------- | -------------------------------------- |
566
+ | `dataSpace` | `QUERY_PROFILE_TAG_DATA_SPACE` | legacy data space flow |
567
+ | `dataProduct` | `QUERY_PROFILE_TAG_DATA_PRODUCT` | data product flow |
568
+ | `class` | `QUERY_PROFILE_TAG_CLASS` | any flow with a resolved `sourceClass` |
569
+
570
+ These are **search and classification** metadata — they are what
571
+ `decorateSearchSpecification` filters on so a data space's query list shows only
572
+ its own queries. The `dataSpace` vs `dataProduct` split is load-bearing and
573
+ tracks the element type, not the historical naming: a query created against a
574
+ Data Product must carry `dataProduct`, or reopening it would mis-classify it as
575
+ a Data Space.
576
+
577
+ ### 4.5 Reconstituting a saved query
578
+
579
+ `ExistingQueryEditorStore` does this in two phases.
580
+
581
+ **Phase 1 — `buildGraph()`** decides _how much graph to build_, from the
582
+ execution context type alone:
583
+
584
+ ```ts
585
+ if (!(dataSpaceTaggedValue || exec is DataSpace | DataProductNative
586
+ | DataProductModelAccess | DataProductLakehouse | Ingest)) {
587
+ buildFullGraph(); // explicit mapping/runtime queries
588
+ } else if (exec is Ingest) {
589
+ buildIngestGraph(...); // fetch all ingests, build the one referenced
590
+ }
591
+ // otherwise: defer — the graph is built inside initQueryBuildStateFromQuery
592
+ ```
593
+
594
+ Data space and data product queries build **nothing** here; their graph is built
595
+ lazily in phase 2, because the analytics call needs the execution key to know
596
+ which mapping to cover.
597
+
598
+ **Phase 2 — `initQueryBuildStateFromQuery(queryInfo)`** dispatches on the
599
+ execution context and rebuilds the matching entry point:
600
+
601
+ | Saved context | Reconstruction |
602
+ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
603
+ | `QueryDataSpaceExecutionContextInfo` | `buildGraphAndDataspaceAnalyticsResult(...)` with the saved `executionKey` → `attachDataSpaceFallbackRuntimes` (§3.3) → `resolveExecutionContext` → `LegendQueryDataSpaceQueryBuilderState`; mapping pinned from the resolved context, runtime pinned only if the context has one (possibly the injected Lakehouse fallback) |
604
+ | `QueryExplicitExecutionContextInfo` | `ClassQueryBuilderState` over the full graph; mapping and runtime resolved directly by path |
605
+ | `QueryDataProduct*ExecutionContextInfo` | `fetchDataProductArtifact(...)` → `buildDataProductQueryBuilderState(...)` with the access type and id derived from the context subtype |
606
+ | `QueryIngestExecutionContextInfo` | Resolve the ingest from the graph built in phase 1, create the adhoc lakehouse runtime, `changeAccessorOwner` + `changeAccessor({ tableName: dataSet })` |
607
+ | anything else | `UnsupportedOperationError` |
608
+
609
+ **Legacy resolution.** `resolveExecutionContext(dataSpace, executionKey, mapping, runtime)`
610
+ handles queries saved before execution keys were reliable. If no key is present
611
+ it falls back to the data space default; and if the query's deprecated
612
+ `mapping`/`runtime` disagree with the default context, it searches the execution
613
+ contexts for one whose mapping matches — disambiguating by runtime when several
614
+ match. This is why the deprecated fields are still read.
615
+
616
+ **Constrained editing.** Reopened queries deliberately disable data space and
617
+ data product switching (`'Switching data products is not supported from the
618
+ existing query editor. Please open a new query instead.'`). Switching would
619
+ invalidate the persisted execution context; the user is pushed toward a new
620
+ query instead. Switching _execution context within the same data space_ is
621
+ allowed, and — when the graph is light — triggers the rebuild described in §2.4,
622
+ gated behind a save prompt if the query has unsaved changes.
623
+
624
+ ---
625
+
626
+ ## 5. Summary
627
+
628
+ | Entry point | Store | Graph strategy | Persisted execution context |
629
+ | --------------------------- | ----------------------------------------------------------------- | ---------------------------------- | --------------------------------------------- |
630
+ | Mapping + runtime | `MappingQueryCreatorStore` | Full | `QueryExplicitExecutionContext` |
631
+ | Service | `ServiceQueryCreatorStore` | Full | `QueryExplicitExecutionContext` |
632
+ | Data space | `DataProductQueryCreatorStore` (via `QueryableLegacyDataProduct`) | Minimal (analytics artifacts) | `QueryDataSpaceExecutionContext` |
633
+ | Data space template | `DataSpaceTemplateQueryCreatorStore` | Minimal (resolved via template id) | `QueryDataSpaceExecutionContext` |
634
+ | Data product — native | `DataProductQueryCreatorStore` (via `QueryableDataProduct`) | Minimal (data product artifact) | `QueryDataProductNativeExecutionContext` |
635
+ | Data product — model access | `DataProductQueryCreatorStore` | Minimal + adhoc lakehouse runtime | `QueryDataProductModelAccessExecutionContext` |
636
+ | Data product — lakehouse | `DataProductQueryCreatorStore` | Minimal + adhoc lakehouse runtime | `QueryDataProductLakehouseExecutionContext` |
637
+ | Ingest definition | `IngestQueryCreatorStore` | Targeted (one ingest entity) | `QueryIngestExecutionContext` |
638
+ | Existing query | `ExistingQueryEditorStore` | Derived from the saved context | unchanged (round-trips) |
639
+
640
+ The through-line: **the more curated the entry point, the less graph Legend
641
+ Query has to build.** A raw mapping gives no signal, so everything is loaded. A
642
+ data space publishes exactly which mapping and which model elements matter, so
643
+ only those are loaded. Data products and ingest definitions narrow it further to
644
+ a single access path.
package/lib/index.css CHANGED
@@ -1,4 +1,4 @@
1
- /** @license @finos/legend-application-query v13.8.55
1
+ /** @license @finos/legend-application-query v13.8.56
2
2
  * Copyright (c) 2020-present, Goldman Sachs
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- /** @license @finos/legend-application-query v13.8.55
1
+ /** @license @finos/legend-application-query v13.8.56
2
2
  * Copyright (c) 2020-present, Goldman Sachs
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finos/legend-application-query",
3
- "version": "13.8.55",
3
+ "version": "13.8.56",
4
4
  "description": "Legend Query application core",
5
5
  "keywords": [
6
6
  "legend",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finos/legend-application-query",
3
- "version": "13.8.55",
3
+ "version": "13.8.56",
4
4
  "description": "Legend Query application core",
5
5
  "keywords": [
6
6
  "legend",
@@ -46,14 +46,14 @@
46
46
  "dependencies": {
47
47
  "@finos/legend-application": "16.0.125",
48
48
  "@finos/legend-art": "7.1.165",
49
- "@finos/legend-code-editor": "2.0.205",
50
- "@finos/legend-data-cube": "0.3.123",
51
- "@finos/legend-extension-dsl-data-space": "10.4.258",
52
- "@finos/legend-graph": "32.7.1",
53
- "@finos/legend-lego": "2.0.225",
54
- "@finos/legend-query-builder": "4.19.14",
49
+ "@finos/legend-code-editor": "2.0.206",
50
+ "@finos/legend-data-cube": "0.3.124",
51
+ "@finos/legend-extension-dsl-data-space": "10.4.259",
52
+ "@finos/legend-graph": "32.7.2",
53
+ "@finos/legend-lego": "2.0.226",
54
+ "@finos/legend-query-builder": "4.19.15",
55
55
  "@finos/legend-server-depot": "6.1.22",
56
- "@finos/legend-server-lakehouse": "0.3.85",
56
+ "@finos/legend-server-lakehouse": "0.3.86",
57
57
  "@finos/legend-shared": "11.0.30",
58
58
  "@finos/legend-storage": "3.0.152",
59
59
  "@testing-library/dom": "10.4.0",