• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
18export interface QueryResponse {
19  query: string;
20  error?: string;
21  totalRowCount: number;
22  durationMs: number;
23  columns: string[];
24  rows: Row[];
25  statementCount: number;
26  statementWithOutputCount: number;
27  lastStatementSql: string;
28}
29
30/**
31 * Runs a query and pulls out all the columns into a list of 'row' objects,
32 * where each row contains a dictionary of [columnName] -> value.
33 *
34 * This method will not throw if the query fails, instead the error is populated
35 * in the return value.
36 *
37 * This function is designed to be used with table viewers, where the structure
38 * of resulting rows is not known up front. Use engine.query() or
39 * engine.tryQuery() and use the iterators if the resulting data is to be
40 * processed.
41 *
42 * @param sqlQuery - The query to evaluate.
43 * @param engine - The engine to use to run the query.
44 */
45export async function runQueryForQueryTable(
46  sqlQuery: string,
47  engine: Engine,
48): Promise<QueryResponse> {
49  const startMs = performance.now();
50
51  // TODO(primiano): once the controller thread is gone we should pass down
52  // the result objects directly to the frontend, iterate over the result
53  // and deal with pagination there. For now we keep the old behavior and
54  // truncate to 10k rows.
55
56  const maybeResult = await engine.tryQuery(sqlQuery);
57
58  if (maybeResult.ok) {
59    const queryRes = maybeResult.value;
60
61    const durationMs = performance.now() - startMs;
62    const rows: Row[] = [];
63    const columns = queryRes.columns();
64    for (const iter = queryRes.iter({}); iter.valid(); iter.next()) {
65      const row: Row = {};
66      for (const colName of columns) {
67        const value = iter.get(colName);
68        row[colName] = value === null ? 'NULL' : value;
69      }
70      rows.push(row);
71    }
72
73    const result: QueryResponse = {
74      query: sqlQuery,
75      durationMs,
76      error: queryRes.error(),
77      totalRowCount: queryRes.numRows(),
78      columns,
79      rows,
80      statementCount: queryRes.statementCount(),
81      statementWithOutputCount: queryRes.statementWithOutputCount(),
82      lastStatementSql: queryRes.lastStatementSql(),
83    };
84    return result;
85  } else {
86    // In the case of a query error we don't want the exception to bubble up
87    // as a crash. The |queryRes| object will be populated anyways.
88    // queryRes.error() is used to tell if the query errored or not. If it
89    // errored, the frontend will show a graceful message instead.
90    return {
91      query: sqlQuery,
92      durationMs: performance.now() - startMs,
93      error: maybeResult.error,
94      totalRowCount: 0,
95      columns: [],
96      rows: [],
97      statementCount: 0,
98      statementWithOutputCount: 0,
99      lastStatementSql: '',
100    };
101  }
102}
103