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