xref: /aosp_15_r20/external/perfetto/ui/src/components/query_table/queries.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {Engine} from '../../trace_processor/engine';
16import {Row} from '../../trace_processor/query_result';
17
18const MAX_DISPLAY_ROWS = 10000;
19
20export interface QueryResponse {
21  query: string;
22  error?: string;
23  totalRowCount: number;
24  durationMs: number;
25  columns: string[];
26  rows: Row[];
27  statementCount: number;
28  statementWithOutputCount: number;
29  lastStatementSql: string;
30}
31
32export interface QueryRunParams {
33  // If true, replaces nulls with "NULL" string. Default is true.
34  convertNullsToString?: boolean;
35}
36
37export async function runQuery(
38  sqlQuery: string,
39  engine: Engine,
40  params?: QueryRunParams,
41): Promise<QueryResponse> {
42  const startMs = performance.now();
43
44  // TODO(primiano): once the controller thread is gone we should pass down
45  // the result objects directly to the frontend, iterate over the result
46  // and deal with pagination there. For now we keep the old behavior and
47  // truncate to 10k rows.
48
49  const maybeResult = await engine.tryQuery(sqlQuery);
50
51  if (maybeResult.ok) {
52    const queryRes = maybeResult.value;
53    const convertNullsToString = params?.convertNullsToString ?? true;
54
55    const durationMs = performance.now() - startMs;
56    const rows: Row[] = [];
57    const columns = queryRes.columns();
58    let numRows = 0;
59    for (const iter = queryRes.iter({}); iter.valid(); iter.next()) {
60      const row: Row = {};
61      for (const colName of columns) {
62        const value = iter.get(colName);
63        row[colName] = value === null && convertNullsToString ? 'NULL' : value;
64      }
65      rows.push(row);
66      if (++numRows >= MAX_DISPLAY_ROWS) break;
67    }
68
69    const result: QueryResponse = {
70      query: sqlQuery,
71      durationMs,
72      error: queryRes.error(),
73      totalRowCount: queryRes.numRows(),
74      columns,
75      rows,
76      statementCount: queryRes.statementCount(),
77      statementWithOutputCount: queryRes.statementWithOutputCount(),
78      lastStatementSql: queryRes.lastStatementSql(),
79    };
80    return result;
81  } else {
82    // In the case of a query error we don't want the exception to bubble up
83    // as a crash. The |queryRes| object will be populated anyways.
84    // queryRes.error() is used to tell if the query errored or not. If it
85    // errored, the frontend will show a graceful message instead.
86    return {
87      query: sqlQuery,
88      durationMs: performance.now() - startMs,
89      error: maybeResult.error,
90      totalRowCount: 0,
91      columns: [],
92      rows: [],
93      statementCount: 0,
94      statementWithOutputCount: 0,
95      lastStatementSql: '',
96    };
97  }
98}
99