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