Line data Source code
1 : import * as React from 'react';
2 :
3 : import {
4 : COLUMN_SEARCH_SUCCEEDED,
5 : RESULT_SEARCH_FAILED,
6 : SET_RESULT_SEARCH_PATTERN,
7 : type ComputationAction,
8 : type TableComputationState,
9 : } from '../../compute/computation_state.js';
10 : import {
11 : buildColumnSearchSQL,
12 : collectSearchableResultColumns,
13 : parseColumnSearchMatches,
14 : } from '../../compute/sql/query_result_search_sql.js';
15 : import type { Dispatch } from '../../utils/variant.js';
16 : import { useLogger } from '../../platform/logger/logger_provider.js';
17 : import { stringifyError } from '../../platform/logger/logger.js';
18 : import { useDataFrameRegistry } from '../../compute/computation_registry.js';
19 : import { scheduleTask } from '../../compute/computation_tasks.js';
20 : import { DATA_SEARCH_TASK } from '../../compute/computation_types.js';
21 :
22 1 : const LOG_CTX = 'query_result_search';
23 :
24 : interface SearchExecution {
25 : requestId: number;
26 : sourceDataFrame: object;
27 : abort: AbortController;
28 : }
29 :
30 : interface SearchExecutions {
31 : columns: SearchExecution | null;
32 : data: SearchExecution | null;
33 : }
34 :
35 1 : const executionsByDispatch = new WeakMap<object, Map<number, SearchExecutions>>();
36 :
37 1 : function getExecutions(dispatch: Dispatch<ComputationAction>, tableId: number): SearchExecutions {
38 1 : const owner = dispatch as object;
39 1 : let executions = executionsByDispatch.get(owner);
40 1 : if (executions == null) {
41 1 : executions = new Map();
42 1 : executionsByDispatch.set(owner, executions);
43 1 : }
44 1 : let tableExecutions = executions.get(tableId);
45 1 : if (tableExecutions == null) {
46 1 : tableExecutions = { columns: null, data: null };
47 1 : executions.set(tableId, tableExecutions);
48 1 : }
49 1 : return tableExecutions;
50 1 : }
51 :
52 1 : export function useQueryResultSearch(
53 1 : table: TableComputationState | null,
54 1 : dispatch: Dispatch<ComputationAction>,
55 1 : ) {
56 1 : const logger = useLogger();
57 1 : const dataFrameMemory = useDataFrameRegistry();
58 1 : const columns = React.useMemo(
59 1 : () => collectSearchableResultColumns(table?.columnGroups ?? []),
60 1 : [table?.columnGroups],
61 1 : );
62 :
63 1 : React.useEffect(() => {
64 1 : if (table == null || table.dataFrame == null) return;
65 1 : const search = table.columnSearch;
66 1 : const executions = getExecutions(dispatch, table.tableId);
67 1 : if (search.requestedPattern.length === 0) {
68 1 : executions.columns?.abort.abort();
69 1 : executions.columns = null;
70 1 : return;
71 1 : }
72 1 : if (search.requestId === search.appliedRequestId) return;
73 1 : if (table.dataTableLifetime.signal.aborted) return;
74 :
75 1 : const sql = buildColumnSearchSQL(columns, search.requestedPattern);
76 1 : if (sql == null) {
77 1 : dispatch({
78 1 : type: COLUMN_SEARCH_SUCCEEDED,
79 1 : value: [table.tableId, table.dataFrame, search.requestId, []],
80 1 : });
81 1 : return;
82 1 : }
83 :
84 1 : if (executions.columns?.requestId === search.requestId && executions.columns.sourceDataFrame === table.dataFrame) return;
85 1 : executions.columns?.abort.abort();
86 1 : const execution = { requestId: search.requestId, sourceDataFrame: table.dataFrame, abort: new AbortController() };
87 1 : executions.columns = execution;
88 1 : const abortForTableLifetime = () => execution.abort.abort(table.dataTableLifetime.signal.reason);
89 1 : table.dataTableLifetime.signal.addEventListener('abort', abortForTableLifetime, { once: true });
90 1 : dataFrameMemory.acquire(table.dataFrame);
91 1 : void (async () => {
92 1 : try {
93 1 : const result = await table.dataFrame!.withConnection(async connection => await connection.query(sql, execution.abort.signal));
94 1 : dispatch({
95 1 : type: COLUMN_SEARCH_SUCCEEDED,
96 1 : value: [table.tableId, table.dataFrame!, search.requestId, parseColumnSearchMatches(result)],
97 1 : });
98 1 : } catch (error) {
99 1 : if (execution.abort.signal.aborted) return;
100 1 : logger.warn('Column search failed', { error: stringifyError(error) }, LOG_CTX);
101 1 : dispatch({
102 1 : type: RESULT_SEARCH_FAILED,
103 1 : value: [table.tableId, table.dataFrame!, 'columns', search.requestId, stringifyError(error)],
104 1 : });
105 1 : } finally {
106 1 : table.dataTableLifetime.signal.removeEventListener('abort', abortForTableLifetime);
107 1 : dataFrameMemory.release(table.dataFrame);
108 1 : if (executions.columns === execution) executions.columns = null;
109 1 : }
110 1 : })();
111 1 : }, [columns, dataFrameMemory, dispatch, logger, table?.columnSearch?.requestId, table?.dataFrame, table?.tableId]);
112 :
113 1 : React.useEffect(() => {
114 1 : if (table == null || table.dataFrame == null || table.rowNumberColumnName == null) return;
115 1 : const search = table.dataSearch;
116 1 : const executions = getExecutions(dispatch, table.tableId);
117 1 : if (search.requestedPattern.length === 0) {
118 1 : executions.data?.abort.abort();
119 1 : executions.data = null;
120 1 : return;
121 1 : }
122 1 : if (search.requestId === search.appliedRequestId) return;
123 1 : if (table.dataTableLifetime.signal.aborted) return;
124 :
125 1 : if (executions.data?.requestId === search.requestId && executions.data.sourceDataFrame === table.dataFrame) return;
126 1 : executions.data?.abort.abort();
127 1 : const execution = { requestId: search.requestId, sourceDataFrame: table.dataFrame, abort: new AbortController() };
128 1 : executions.data = execution;
129 1 : const abortForTableLifetime = () => execution.abort.abort(table.dataTableLifetime.signal.reason);
130 1 : table.dataTableLifetime.signal.addEventListener('abort', abortForTableLifetime, { once: true });
131 1 : void (async () => {
132 1 : try {
133 1 : await scheduleTask(DATA_SEARCH_TASK, {
134 1 : tableId: table.tableId,
135 1 : tableVersion: table.version,
136 1 : inputDataFrame: table.dataFrame!,
137 1 : rowNumberColumnName: table.rowNumberColumnName!,
138 1 : columns,
139 1 : pattern: search.requestedPattern,
140 1 : requestId: search.requestId,
141 1 : abortController: execution.abort,
142 1 : }, dispatch);
143 0 : } catch (error) {
144 0 : if (!execution.abort.signal.aborted) {
145 0 : logger.warn('Data search failed', { error: stringifyError(error) }, LOG_CTX);
146 : }
147 0 : } finally {
148 0 : table.dataTableLifetime.signal.removeEventListener('abort', abortForTableLifetime);
149 0 : if (executions.data === execution) executions.data = null;
150 0 : }
151 1 : })();
152 1 : }, [columns, dispatch, logger, table?.dataSearch?.requestId, table?.dataFrame, table?.rowNumberColumnName, table?.tableId, table?.version]);
153 :
154 1 : const setColumnPattern = React.useCallback((pattern: string) => {
155 1 : if (table != null) {
156 1 : dispatch({ type: SET_RESULT_SEARCH_PATTERN, value: [table.tableId, 'columns', pattern] });
157 : }
158 1 : }, [dispatch, table?.tableId]);
159 1 : const setDataPattern = React.useCallback((pattern: string) => {
160 1 : if (table != null) {
161 1 : dispatch({ type: SET_RESULT_SEARCH_PATTERN, value: [table.tableId, 'data', pattern] });
162 : }
163 1 : }, [dispatch, table?.tableId]);
164 :
165 1 : return {
166 1 : columnSearch: table?.columnSearch ?? null,
167 1 : dataSearch: table?.dataSearch ?? null,
168 1 : setColumnPattern,
169 1 : setDataPattern,
170 1 : };
171 1 : }
|