LCOV - code coverage report
Current view: top level - dashql-app/src/connections - connection_query_table.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 88.1 % 84 74
Test Date: 2026-11-06 19:30:32

            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 : };
        

Generated by: LCOV version 2.0-1