Line data Source code
1 : import * as React from 'react';
2 :
3 : import * as dashql from '@ankoh/dashql-core';
4 : import { useDashQLCoreSetup } from '../../runtime/core_provider.js';
5 : import { ProfileExecutionState } from '../../query/profile_execution_state.js';
6 : import { createProfileLayoutConfig, QueryProfileView } from './query_profile_view.js';
7 : import { rotateProfileLayoutOrientation } from './profile_layout_config.js';
8 :
9 : type ProfileEngine = dashql.buffers.profile.ProfileEngine;
10 :
11 : export interface EngineProfileViewProps {
12 : engine: ProfileEngine;
13 : planText: string;
14 : className?: string;
15 : fallback?: React.ReactNode;
16 : }
17 :
18 : export interface QueryProfileState {
19 : /// The plan structure
20 : plan: dashql.FlatBufferPtr<dashql.buffers.profile.QueryProfile> | null;
21 : /// The execution state the plan carries (the rows of an analyzed plan)
22 : execution: ProfileExecutionState;
23 : /// The plan text is no plan of the engine
24 : rejected: boolean;
25 : /// Lay out the plan again with the other orientation, absent without plan
26 : rotate?: () => void;
27 : }
28 :
29 : /// Load the plan text of an engine into a core query profile
30 1 : function loadPlan(viewModel: dashql.DashQLQueryProfile, engine: ProfileEngine, planText: string) {
31 1 : switch (engine) {
32 1 : case dashql.buffers.profile.ProfileEngine.HYPER:
33 1 : return viewModel.loadHyperPlan(planText);
34 0 : case dashql.buffers.profile.ProfileEngine.TRINO:
35 0 : return viewModel.loadTrinoPlan(planText);
36 0 : default:
37 0 : throw new Error(`Plans of engine ${engine} are not supported`);
38 1 : }
39 1 : }
40 :
41 1 : export function useQueryProfile(engine: ProfileEngine, planText: string | null): QueryProfileState {
42 1 : const coreSetup = useDashQLCoreSetup();
43 1 : const layoutConfig = React.useMemo(() => createProfileLayoutConfig(), []);
44 1 : const viewModelRef = React.useRef<dashql.DashQLQueryProfile | null>(null);
45 1 : const [state, setState] = React.useState<QueryProfileState>({ plan: null, execution: ProfileExecutionState.EMPTY, rejected: false });
46 :
47 1 : React.useEffect(() => {
48 1 : let cancelled = false;
49 1 : setState({ plan: null, execution: ProfileExecutionState.EMPTY, rejected: false });
50 1 : if (planText == null) return;
51 1 : const load = async () => {
52 1 : const core = await coreSetup('engine_profile_view');
53 1 : if (cancelled) return;
54 1 : viewModelRef.current ??= core.createQueryProfile(layoutConfig);
55 1 : try {
56 1 : const plan = loadPlan(viewModelRef.current, engine, planText);
57 1 : if (plan.read().operatorsLength() === 0) {
58 1 : throw new Error('Plan contains no operators');
59 : }
60 1 : const execution = ProfileExecutionState.EMPTY.withEvents(viewModelRef.current.snapshotEvents().read());
61 1 : if (!cancelled) {
62 1 : setState({ plan, execution, rejected: false });
63 : }
64 1 : } catch (error) {
65 1 : console.warn(error);
66 1 : if (!cancelled) {
67 1 : setState({ plan: null, execution: ProfileExecutionState.EMPTY, rejected: true });
68 : }
69 1 : }
70 1 : };
71 1 : void load();
72 1 : return () => { cancelled = true; };
73 1 : }, [coreSetup, layoutConfig, engine, planText]);
74 :
75 1 : React.useEffect(() => () => {
76 1 : viewModelRef.current?.destroy();
77 1 : viewModelRef.current = null;
78 1 : }, []);
79 :
80 : // The view model keeps the orientation, the next plans are laid out with it as well
81 1 : const rotate = React.useCallback(() => {
82 0 : const viewModel = viewModelRef.current;
83 0 : if (viewModel == null) return;
84 0 : const plan = viewModel.relayout(createProfileLayoutConfig(rotateProfileLayoutOrientation(viewModel.layout.orientation)));
85 0 : setState(current => current.plan == null ? current : { ...current, plan });
86 1 : }, []);
87 :
88 1 : return React.useMemo(() => state.plan == null ? state : { ...state, rotate }, [state, rotate]);
89 1 : }
90 :
91 1 : export function EngineProfileView(props: EngineProfileViewProps) {
92 1 : const { plan, execution, rejected, rotate } = useQueryProfile(props.engine, props.planText);
93 :
94 1 : if (rejected) return props.fallback ?? null;
95 1 : return <div className={props.className}>{plan != null && <QueryProfileView plan={plan} execution={execution} onRotate={rotate} />}</div>;
96 1 : }
|