Line data Source code
1 : import type * as arrow from 'apache-arrow';
2 : import * as React from 'react';
3 :
4 : import { analyzeTable } from '../compute/analyze_table.js';
5 : import { useComputationDispatch, useComputationRegistry, useComputationStore } from '../compute/computation_registry.js';
6 : import { DELETE_COMPUTATION } from '../compute/computation_state.js';
7 : import { scheduleTask } from '../compute/computation_tasks.js';
8 : import { TABLE_ORDERING_TASK } from '../compute/computation_types.js';
9 : import { useComputeDatabase } from '../compute/compute_connection_provider.js';
10 : import type { EmbeddedClientContext } from '../platform/database/embedded_database.js';
11 : import { stringifyError } from '../platform/logger/logger.js';
12 : import { useLogger } from '../platform/logger/logger_provider.js';
13 : import { allocateQueryId } from '../query/tracked_query_execution.js';
14 : import { DataTable } from '../views/query_result/data_table.js';
15 : import { TableColumnHeader } from '../views/query_result/data_table_cell.js';
16 : import { createOrderingTask } from '../views/query_result/data_table_ordering.js';
17 :
18 1 : const LOG_CTX = 'connection_overview';
19 :
20 : /// Run a query in the compute database and register its analyzed result as a computation, for the data table.
21 : /// The queries of the computation run with the client context. The table shows no column plots, the analysis skips
22 : /// the column aggregates.
23 : /// The result of a changed query replaces the computation of the previous one once it is analyzed and ordered like
24 : /// the previous one, so that the table never shows a result that is still being analyzed. A query that changes while
25 : /// another one runs runs next, the queries in between are skipped. A changed refresh count runs the query again.
26 : /// Returns the id of the computation, null until the first result.
27 1 : function useQueryComputation(
28 1 : sql: string,
29 1 : runQuery: (sql: string) => Promise<arrow.Table>,
30 1 : clientContext: EmbeddedClientContext | null,
31 1 : refreshes: number,
32 1 : ): number | null {
33 1 : const dispatch = useComputationDispatch();
34 1 : const computations = useComputationStore();
35 1 : const database = useComputeDatabase();
36 1 : const logger = useLogger();
37 1 : const [tableId, setTableId] = React.useState<number | null>(null);
38 1 : const state = React.useRef({
39 : /// The shown computation
40 1 : shown: null as number | null,
41 : /// The run of the newest query that did not start yet
42 1 : next: null as (() => Promise<void>) | null,
43 : /// Is a run pending or running?
44 1 : pumping: false,
45 1 : unmounted: false,
46 1 : });
47 :
48 : // Delete the shown computation with the table
49 1 : React.useEffect(() => {
50 1 : const current = state.current;
51 1 : current.unmounted = false;
52 1 : return () => {
53 1 : current.unmounted = true;
54 1 : if (current.shown != null) dispatch({ type: DELETE_COMPUTATION, value: [current.shown] });
55 1 : current.shown = null;
56 1 : };
57 1 : }, [dispatch]);
58 :
59 1 : React.useEffect(() => {
60 1 : if (database == null) return;
61 1 : const current = state.current;
62 1 : current.next = async () => {
63 1 : const table = await runQuery(sql);
64 1 : const id = allocateQueryId();
65 1 : try {
66 : // The table has no connection, the client context attributes the queries of its computation
67 1 : await analyzeTable(id, table, dispatch, database, logger, null, { clientContext, columnAggregates: false });
68 1 : const previous = current.shown;
69 1 : const ordering = previous == null ? [] : computations.current.tableComputations[previous]?.dataTableOrdering ?? [];
70 1 : const fields = new Set(table.schema.fields.map(field => field.name));
71 1 : if (ordering.length > 0 && ordering.every(constraint => fields.has(constraint.field))) {
72 0 : const task = createOrderingTask(computations.current.tableComputations[id], ordering);
73 0 : if (task != null) await scheduleTask(TABLE_ORDERING_TASK, task, dispatch);
74 0 : }
75 1 : } catch (e) {
76 0 : dispatch({ type: DELETE_COMPUTATION, value: [id] });
77 0 : throw e;
78 0 : }
79 1 : if (current.unmounted) {
80 0 : dispatch({ type: DELETE_COMPUTATION, value: [id] });
81 0 : return;
82 0 : }
83 1 : const previous = current.shown;
84 1 : current.shown = id;
85 1 : setTableId(id);
86 1 : if (previous != null) dispatch({ type: DELETE_COMPUTATION, value: [previous] });
87 1 : };
88 1 : if (current.pumping) return;
89 : // Run the newest query, one run after another
90 1 : current.pumping = true;
91 1 : void (async () => {
92 1 : while (current.next != null && !current.unmounted) {
93 1 : const run = current.next;
94 1 : current.next = null;
95 1 : await run().catch(e => {
96 0 : logger.warn('Failed to query a table of the connection overview', { error: stringifyError(e) }, LOG_CTX);
97 1 : });
98 1 : }
99 1 : current.pumping = false;
100 1 : })();
101 1 : }, [sql, refreshes, runQuery, clientContext, database, dispatch, computations, logger]);
102 1 : return tableId;
103 1 : }
104 :
105 : export interface QueryTableProps {
106 : /// The query of the table, a changed query runs again
107 : sql: string;
108 : /// The client context of the query, e.g. to attribute it to a connection in the Hyper log
109 : clientContext: EmbeddedClientContext | null;
110 : /// Run the query of the table, e.g. on the reader of the monitoring database of a connection
111 : runQuery: (sql: string) => Promise<arrow.Table>;
112 : /// The refreshes of the table by the user, a changed count runs the query again
113 : refreshes?: number;
114 : }
115 :
116 : /// The result of a query in the data table of query results, e.g. to sort it or to show the SQL of a query
117 1 : export const QueryTable: React.FC<QueryTableProps> = ({ sql, clientContext, runQuery, refreshes = 0 }) => {
118 1 : const [computations, dispatch] = useComputationRegistry();
119 1 : const tableId = useQueryComputation(sql, runQuery, clientContext, refreshes);
120 1 : const computation = tableId == null ? null : computations.tableComputations[tableId] ?? null;
121 1 : if (computation == null) return null;
122 1 : return (
123 1 : <DataTable
124 1 : table={computation}
125 1 : dispatchComputation={dispatch}
126 1 : debugMode={false}
127 1 : columnHeader={TableColumnHeader.OnlyColumnName}
128 1 : compact
129 1 : />
130 : );
131 1 : };
|