ui: Replace query with queryV2

Change-Id: I4d043e24ff4409eefe6c47e0d33b87b424ac013a
diff --git a/ui/src/controller/aggregation/aggregation_controller.ts b/ui/src/controller/aggregation/aggregation_controller.ts
index 96c91f9..d699b46 100644
--- a/ui/src/controller/aggregation/aggregation_controller.ts
+++ b/ui/src/controller/aggregation/aggregation_controller.ts
@@ -19,7 +19,7 @@
   ThreadStateExtra,
 } from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
-import {NUM, slowlyCountRows} from '../../common/query_iterator';
+import {NUM, NUM_NULL, STR_NULL} from '../../common/query_result';
 import {Area, Sorting} from '../../common/state';
 import {Controller} from '../controller';
 import {globals} from '../globals';
@@ -115,9 +115,9 @@
       sorting = `${pref.sorting.column} ${pref.sorting.direction}`;
     }
     const query = `select ${colIds} from ${this.kind} order by ${sorting}`;
-    const result = await this.args.engine.query(query);
+    const result = await this.args.engine.queryV2(query);
 
-    const numRows = slowlyCountRows(result);
+    const numRows = result.numRows();
     const columns = defs.map(def => this.columnFromColumnDef(def, numRows));
     const columnSums = await Promise.all(defs.map(def => this.getSum(def)));
     const extraData = await this.getExtra(this.args.engine, area);
@@ -135,20 +135,29 @@
       return idx;
     }
 
-    for (let row = 0; row < numRows; row++) {
-      const cols = result.columns;
-      for (let col = 0; col < result.columns.length; col++) {
-        if (cols[col].stringValues && cols[col].stringValues!.length > 0) {
-          data.columns[col].data[row] =
-              internString(cols[col].stringValues![row]);
-        } else if (cols[col].longValues && cols[col].longValues!.length > 0) {
-          data.columns[col].data[row] = cols[col].longValues![row];
-        } else if (
-            cols[col].doubleValues && cols[col].doubleValues!.length > 0) {
-          data.columns[col].data[row] = cols[col].doubleValues![row];
+    const spec: {[k: string]: string|number|null} = {};
+    for (const column of columns) {
+      if (column.kind === 'STRING') {
+        spec[column.columnId] = STR_NULL;
+      } else {
+        spec[column.columnId] = NUM_NULL;
+      }
+    }
+
+    const it = result.iter(spec);
+    for (let i = 0; it.valid(); it.next(), ++i) {
+      for (const column of data.columns) {
+        const item = it.get(column.columnId);
+        if (item === null) {
+          column.data[i] = column.kind === 'STRING' ? internString('NULL') : 0;
+        } else if (typeof item === 'string') {
+          column.data[i] = internString(item);
+        } else {
+          column.data[i] = item;
         }
       }
     }
+
     return data;
   }
 
diff --git a/ui/src/controller/aggregation/thread_aggregation_controller.ts b/ui/src/controller/aggregation/thread_aggregation_controller.ts
index 3fe9dac..b9c15fb 100644
--- a/ui/src/controller/aggregation/thread_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/thread_aggregation_controller.ts
@@ -14,7 +14,7 @@
 
 import {ColumnDef, ThreadStateExtra} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
-import {slowlyCountRows} from '../../common/query_iterator';
+import {NUM, NUM_NULL, STR} from '../../common/query_result';
 import {Area, Sorting} from '../../common/state';
 import {translateState} from '../../common/thread_state';
 import {toNs} from '../../common/time';
@@ -73,31 +73,37 @@
     this.setThreadStateUtids(area.tracks);
     if (this.utids === undefined || this.utids.length === 0) return;
 
-    const query = `select state, io_wait, sum(dur) as total_dur from process
+    const query =
+        `select state, io_wait as ioWait, sum(dur) as totalDur from process
       JOIN thread USING(upid)
       JOIN thread_state USING(utid)
       WHERE utid IN (${this.utids}) AND thread_state.ts + thread_state.dur > ${
-        toNs(area.startSec)} AND
+            toNs(area.startSec)} AND
       thread_state.ts < ${toNs(area.endSec)}
       GROUP BY state, io_wait`;
-    const result = await engine.query(query);
-    const numRows = slowlyCountRows(result);
+    const result = await engine.queryV2(query);
+
+    const it = result.iter({
+      state: STR,
+      ioWait: NUM_NULL,
+      totalDur: NUM,
+    });
 
     const summary: ThreadStateExtra = {
       kind: 'THREAD_STATE',
       states: [],
-      values: new Float64Array(numRows),
+      values: new Float64Array(result.numRows()),
       totalMs: 0
     };
-    for (let row = 0; row < numRows; row++) {
-      const state = result.columns[0].stringValues![row];
-      const ioWait = result.columns[1].isNulls![row] ?
-          undefined :
-          !!result.columns[1].longValues![row];
+    summary.totalMs = 0;
+    for (let i = 0; it.valid(); ++i, it.next()) {
+      const state = it.state;
+      const ioWait = it.ioWait === null ? undefined : it.ioWait > 0;
       summary.states.push(translateState(state, ioWait));
-      summary.values[row] = result.columns[2].longValues![row] / 1000000;  // ms
+      const ms = it.totalDur / 1000000;
+      summary.values[i] = ms;
+      summary.totalMs += ms;
     }
-    summary.totalMs = summary.values.reduce((a, b) => a + b, 0);
     return summary;
   }
 
diff --git a/ui/src/controller/cpu_profile_controller.ts b/ui/src/controller/cpu_profile_controller.ts
index 05e03e9..34834d3 100644
--- a/ui/src/controller/cpu_profile_controller.ts
+++ b/ui/src/controller/cpu_profile_controller.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {Engine} from '../common/engine';
-import {slowlyCountRows} from '../common/query_iterator';
+import {NUM, STR} from '../common/query_result';
 import {CallsiteInfo, CpuProfileSampleSelection} from '../common/state';
 import {CpuProfileDetails} from '../frontend/globals';
 
@@ -106,7 +106,7 @@
     // 5. Sort the query by the depth of the callstack frames.
     const sampleQuery = `
       SELECT
-        samples.id,
+        samples.id as id,
         IFNULL(
           (
             SELECT name
@@ -115,8 +115,8 @@
             LIMIT 1
           ),
           spf.name
-        ) AS frame_name,
-        spm.name AS mapping_name
+        ) AS name,
+        spm.name AS mapping
       FROM cpu_profile_stack_sample AS samples
       LEFT JOIN (
         SELECT
@@ -141,26 +141,28 @@
       ORDER BY callsites.depth;
     `;
 
-    const callsites = await this.args.engine.query(sampleQuery);
+    const callsites = await this.args.engine.queryV2(sampleQuery);
 
-    if (slowlyCountRows(callsites) < 1) {
+    if (callsites.numRows() === 0) {
       return undefined;
     }
 
-    const sampleData: CallsiteInfo[] = new Array();
-    for (let i = 0; i < slowlyCountRows(callsites); i++) {
-      const id = +callsites.columns[0].longValues![i];
-      const name = callsites.columns[1].stringValues![i];
-      const mapping = callsites.columns[2].stringValues![i];
+    const it = callsites.iter({
+      id: NUM,
+      name: STR,
+      mapping: STR,
+    });
 
+    const sampleData: CallsiteInfo[] = new Array();
+    for (; it.valid(); it.next()) {
       sampleData.push({
-        id,
+        id: it.id,
         totalSize: 0,
         depth: 0,
         parentId: 0,
-        name,
+        name: it.name,
         selfSize: 0,
-        mapping,
+        mapping: it.mapping,
         merged: false,
         highlighted: false
       });
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index 87dd298..90b8254 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {Engine} from '../common/engine';
-import {slowlyCountRows} from '../common/query_iterator';
+import {NUM, STR_NULL} from '../common/query_result';
 import {Area} from '../common/state';
 import {fromNs, toNs} from '../common/time';
 import {Flow} from '../frontend/globals';
@@ -43,34 +43,53 @@
   }
 
   queryFlowEvents(query: string, callback: (flows: Flow[]) => void) {
-    this.args.engine.query(query).then(res => {
+    this.args.engine.queryV2(query).then(result => {
       const flows: Flow[] = [];
-      for (let i = 0; i < slowlyCountRows(res); i++) {
-        const beginSliceId = res.columns[0].longValues![i];
-        const beginTrackId = res.columns[1].longValues![i];
-        const beginSliceName = res.columns[2].stringValues![i];
-        const beginSliceCategory = res.columns[3].stringValues![i];
-        const beginSliceStartTs = fromNs(res.columns[4].longValues![i]);
-        const beginSliceEndTs = fromNs(res.columns[5].longValues![i]);
-        const beginDepth = res.columns[6].longValues![i];
+      const it = result.iter({
+        beginSliceId: NUM,
+        beginTrackId: NUM,
+        beginSliceName: STR_NULL,
+        beginSliceCategory: STR_NULL,
+        beginSliceStartTs: NUM,
+        beginSliceEndTs: NUM,
+        beginDepth: NUM,
+        endSliceId: NUM,
+        endTrackId: NUM,
+        endSliceName: STR_NULL,
+        endSliceCategory: STR_NULL,
+        endSliceStartTs: NUM,
+        endSliceEndTs: NUM,
+        endDepth: NUM,
+        name: STR_NULL,
+        category: STR_NULL,
+        id: NUM,
+      });
+      for (; it.valid(); it.next()) {
+        const beginSliceId = it.beginSliceId;
+        const beginTrackId = it.beginTrackId;
+        const beginSliceName =
+            it.beginSliceName === null ? 'NULL' : it.beginSliceName;
+        const beginSliceCategory =
+            it.beginSliceCategory === null ? 'NULL' : it.beginSliceCategory;
+        const beginSliceStartTs = fromNs(it.beginSliceStartTs);
+        const beginSliceEndTs = fromNs(it.beginSliceEndTs);
+        const beginDepth = it.beginDepth;
 
-        const endSliceId = res.columns[7].longValues![i];
-        const endTrackId = res.columns[8].longValues![i];
-        const endSliceName = res.columns[9].stringValues![i];
-        const endSliceCategory = res.columns[10].stringValues![i];
-        const endSliceStartTs = fromNs(res.columns[11].longValues![i]);
-        const endSliceEndTs = fromNs(res.columns[12].longValues![i]);
-        const endDepth = res.columns[13].longValues![i];
+        const endSliceId = it.endSliceId;
+        const endTrackId = it.endTrackId;
+        const endSliceName =
+            it.endSliceName === null ? 'NULL' : it.endSliceName;
+        const endSliceCategory =
+            it.endSliceCategory === null ? 'NULL' : it.endSliceCategory;
+        const endSliceStartTs = fromNs(it.endSliceStartTs);
+        const endSliceEndTs = fromNs(it.endSliceEndTs);
+        const endDepth = it.endDepth;
 
         // Category and name present only in version 1 flow events
         // It is most likelly NULL for all other versions
-        const category = res.columns[14].isNulls![i] ?
-            undefined :
-            res.columns[14].stringValues![i];
-        const name = res.columns[15].isNulls![i] ?
-            undefined :
-            res.columns[15].stringValues![i];
-        const id = res.columns[16].longValues![i];
+        const category = it.category === null ? undefined : it.category;
+        const name = it.name === null ? undefined : it.name;
+        const id = it.id;
 
         flows.push({
           id,
@@ -110,13 +129,23 @@
 
     const query = `
     select
-      f.slice_out, t1.track_id, t1.name,
-      t1.category, t1.ts, (t1.ts+t1.dur), t1.depth,
-      f.slice_in, t2.track_id, t2.name,
-      t2.category, t2.ts, (t2.ts+t2.dur), t2.depth,
-      extract_arg(f.arg_set_id, 'cat'),
-      extract_arg(f.arg_set_id, 'name'),
-      f.id
+      f.slice_out as beginSliceId,
+      t1.track_id as beginTrackId,
+      t1.name as beginSliceName,
+      t1.category as beginSliceCategory,
+      t1.ts as beginSliceStartTs,
+      (t1.ts+t1.dur) as beginSliceEndTs,
+      t1.depth as beginDepth,
+      f.slice_in as endSliceId,
+      t2.track_id as endTrackId,
+      t2.name as endSliceName,
+      t2.category as endSliceCategory,
+      t2.ts as endSliceStartTs,
+      (t2.ts+t2.dur) as endSliceEndTs,
+      t2.depth as endDepth,
+      extract_arg(f.arg_set_id, 'cat') as category,
+      extract_arg(f.arg_set_id, 'name') as name,
+      f.id as id
     from directly_connected_flow(${sliceId}) f
     join slice t1 on f.slice_out = t1.slice_id
     join slice t2 on f.slice_in = t2.slice_id
@@ -161,13 +190,23 @@
 
     const query = `
     select
-      f.slice_out, t1.track_id, t1.name,
-      t1.category, t1.ts, (t1.ts+t1.dur), t1.depth,
-      f.slice_in, t2.track_id, t2.name,
-      t2.category, t2.ts, (t2.ts+t2.dur), t2.depth,
-      extract_arg(f.arg_set_id, 'cat'),
-      extract_arg(f.arg_set_id, 'name'),
-      f.id
+      f.slice_out as beginSliceId,
+      t1.track_id as beginTrackId,
+      t1.name as beginSliceName,
+      t1.category as beginSliceCategory,
+      t1.ts as beginSliceStartTs,
+      (t1.ts+t1.dur) as beginSliceEndTs,
+      t1.depth as beginDepth,
+      f.slice_in as endSliceId,
+      t2.track_id as endTrackId,
+      t2.name as endSliceName,
+      t2.category as endSliceCategory,
+      t2.ts as endSliceStartTs,
+      (t2.ts+t2.dur) as endSliceEndTs,
+      t2.depth as endDepth,
+      extract_arg(f.arg_set_id, 'cat') as category,
+      extract_arg(f.arg_set_id, 'name') as name,
+      f.id as id
     from flow f
     join slice t1 on f.slice_out = t1.slice_id
     join slice t2 on f.slice_in = t2.slice_id
diff --git a/ui/src/controller/heap_profile_controller.ts b/ui/src/controller/heap_profile_controller.ts
index 82f4b7e..b2d07f7 100644
--- a/ui/src/controller/heap_profile_controller.ts
+++ b/ui/src/controller/heap_profile_controller.ts
@@ -23,7 +23,7 @@
   OBJECTS_ALLOCATED_NOT_FREED_KEY,
   SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY
 } from '../common/flamegraph_util';
-import {slowlyCountRows} from '../common/query_iterator';
+import {NUM, STR} from '../common/query_result';
 import {CallsiteInfo, HeapProfileFlamegraph} from '../common/state';
 import {fromNs} from '../common/time';
 import {HeapProfileDetails} from '../frontend/globals';
@@ -57,7 +57,7 @@
       // TODO(hjd): This should be LRU.
       if (this.cache.size > this.cacheSizeLimit) {
         for (const name of this.cache.values()) {
-          await this.engine.query(`drop table ${name}`);
+          await this.engine.queryV2(`drop table ${name}`);
         }
         this.cache.clear();
       }
@@ -228,8 +228,9 @@
       tableName: string, viewingOption = DEFAULT_VIEWING_OPTION,
       focusRegex: string) {
     let orderBy = '';
-    let sizeIndex = 4;
-    let selfIndex = 9;
+    let totalColumnName: 'cumulativeSize'|'cumulativeAllocSize'|
+        'cumulativeCount'|'cumulativeAllocCount' = 'cumulativeSize';
+    let selfColumnName: 'size'|'count' = 'size';
     // TODO(fmayer): Improve performance so this is no longer necessary.
     // Alternatively consider collapsing frames of the same label.
     const maxDepth = 100;
@@ -238,73 +239,97 @@
         orderBy = `where cumulative_size > 0 and depth < ${
             maxDepth} order by depth, parent_id,
             cumulative_size desc, name`;
-        sizeIndex = 4;
-        selfIndex = 9;
+        totalColumnName = 'cumulativeSize';
+        selfColumnName = 'size';
         break;
       case ALLOC_SPACE_MEMORY_ALLOCATED_KEY:
         orderBy = `where cumulative_alloc_size > 0 and depth < ${
             maxDepth} order by depth, parent_id,
             cumulative_alloc_size desc, name`;
-        sizeIndex = 5;
-        selfIndex = 9;
+        totalColumnName = 'cumulativeAllocSize';
+        selfColumnName = 'size';
         break;
       case OBJECTS_ALLOCATED_NOT_FREED_KEY:
         orderBy = `where cumulative_count > 0 and depth < ${
             maxDepth} order by depth, parent_id,
             cumulative_count desc, name`;
-        sizeIndex = 6;
-        selfIndex = 10;
+        totalColumnName = 'cumulativeCount';
+        selfColumnName = 'count';
         break;
       case OBJECTS_ALLOCATED_KEY:
         orderBy = `where cumulative_alloc_count > 0 and depth < ${
             maxDepth} order by depth, parent_id,
             cumulative_alloc_count desc, name`;
-        sizeIndex = 7;
-        selfIndex = 10;
+        totalColumnName = 'cumulativeAllocCount';
+        selfColumnName = 'count';
         break;
       default:
         break;
     }
 
-    const callsites = await this.args.engine.query(
-        `SELECT id, IFNULL(DEMANGLE(name), name), IFNULL(parent_id, -1), depth,
-        cumulative_size, cumulative_alloc_size, cumulative_count,
-        cumulative_alloc_count, map_name, size, count,
-        IFNULL(source_file, ''), IFNULL(line_number, -1)
+    const callsites = await this.args.engine.queryV2(`
+        SELECT
+        id as hash,
+        IFNULL(DEMANGLE(name), name) as name,
+        IFNULL(parent_id, -1) as parentHash,
+        depth,
+        cumulative_size as cumulativeSize,
+        cumulative_alloc_size as cumulativeAllocSize,
+        cumulative_count as cumulativeCount,
+        cumulative_alloc_count as cumulativeAllocCount,
+        map_name as mapping,
+        size,
+        count,
+        IFNULL(source_file, '') as sourceFile,
+        IFNULL(line_number, -1) as lineNumber
         from ${tableName} ${orderBy}`);
 
     const flamegraphData: CallsiteInfo[] = new Array();
     const hashToindex: Map<number, number> = new Map();
-    for (let i = 0; i < slowlyCountRows(callsites); i++) {
-      const hash = callsites.columns[0].longValues![i];
-      let name = callsites.columns[1].stringValues![i];
-      const parentHash = callsites.columns[2].longValues![i];
-      const depth = +callsites.columns[3].longValues![i];
-      const totalSize = +callsites.columns[sizeIndex].longValues![i];
-      const mapping = callsites.columns[8].stringValues![i];
-      const selfSize = +callsites.columns[selfIndex].longValues![i];
+    const it = callsites.iter({
+      hash: NUM,
+      name: STR,
+      parentHash: NUM,
+      depth: NUM,
+      cumulativeSize: NUM,
+      cumulativeAllocSize: NUM,
+      cumulativeCount: NUM,
+      cumulativeAllocCount: NUM,
+      mapping: STR,
+      sourceFile: STR,
+      lineNumber: NUM,
+      size: NUM,
+      count: NUM,
+    });
+    for (let i = 0; it.valid(); ++i, it.next()) {
+      const hash = it.hash;
+      let name = it.name;
+      const parentHash = it.parentHash;
+      const depth = it.depth;
+      const totalSize = it[totalColumnName];
+      const selfSize = it[selfColumnName];
+      const mapping = it.mapping;
       const highlighted = focusRegex !== '' &&
           name.toLocaleLowerCase().includes(focusRegex.toLocaleLowerCase());
       const parentId =
           hashToindex.has(+parentHash) ? hashToindex.get(+parentHash)! : -1;
 
       let location: string|undefined;
-      if (callsites.columns[11].stringValues != null &&
-          /[a-zA-Z]/i.test(callsites.columns[11].stringValues[i])) {
-        location = callsites.columns[11].stringValues[i];
-        if (callsites.columns[12].longValues != null &&
-            callsites.columns[12].longValues[i] !== -1) {
-          location += `:${callsites.columns[12].longValues[i].toString()}`;
+      if (/[a-zA-Z]/i.test(it.sourceFile)) {
+        location = it.sourceFile;
+        if (it.lineNumber !== -1) {
+          location += `:${it.lineNumber}`;
         }
       }
 
       if (depth === maxDepth - 1) {
         name += ' [tree truncated]';
       }
-      hashToindex.set(+hash, i);
       // Instead of hash, we will store index of callsite in this original array
       // as an id of callsite. That way, we have quicker access to parent and it
-      // will stay unique.
+      // will stay unique:
+      hashToindex.set(hash, i);
+
       flamegraphData.push({
         id: i,
         totalSize,
@@ -361,9 +386,9 @@
 
     // Collecting data for more information about heap profile, such as:
     // total memory allocated, memory that is allocated and not freed.
-    const pidValue = await this.args.engine.query(
+    const result = await this.args.engine.queryV2(
         `select pid from process where upid = ${upid}`);
-    const pid = pidValue.columns[0].longValues![0];
+    const pid = result.firstRow({pid: NUM}).pid;
     const startTime = fromNs(ts) - globals.state.traceTime.startSec;
     return {ts: startTime, tsNs: ts, pid, upid, type};
   }
diff --git a/ui/src/controller/logs_controller.ts b/ui/src/controller/logs_controller.ts
index fb1ba62..d70e25a 100644
--- a/ui/src/controller/logs_controller.ts
+++ b/ui/src/controller/logs_controller.ts
@@ -20,7 +20,7 @@
   LogEntriesKey,
   LogExistsKey
 } from '../common/logs';
-import {NUM, slowlyCountRows} from '../common/query_iterator';
+import {NUM, STR} from '../common/query_result';
 import {fromNs, TimeSpan, toNsCeil, toNsFloor} from '../common/time';
 
 import {Controller} from './controller';
@@ -76,25 +76,23 @@
   const vizSqlBounds = `ts >= ${vizStartNs} and ts <= ${vizEndNs}`;
 
   const rowsResult =
-      await engine.query(`select ts, prio, tag, msg from android_logs
+      await engine.queryV2(`select ts, prio, tag, msg from android_logs
         where ${vizSqlBounds}
         order by ts
         limit ${pagination.start}, ${pagination.count}`);
 
-  if (!slowlyCountRows(rowsResult)) {
-    return {
-      offset: pagination.start,
-      timestamps: [],
-      priorities: [],
-      tags: [],
-      messages: [],
-    };
-  }
+  const timestamps = [];
+  const priorities = [];
+  const tags = [];
+  const messages = [];
 
-  const timestamps = rowsResult.columns[0].longValues!;
-  const priorities = rowsResult.columns[1].longValues!;
-  const tags = rowsResult.columns[2].stringValues!;
-  const messages = rowsResult.columns[3].stringValues!;
+  const it = rowsResult.iter({ts: NUM, prio: NUM, tag: STR, msg: STR});
+  for (; it.valid(); it.next()) {
+    timestamps.push(it.ts);
+    priorities.push(it.prio);
+    tags.push(it.tag);
+    messages.push(it.msg);
+  }
 
   return {
     offset: pagination.start,
diff --git a/ui/src/controller/metrics_controller.ts b/ui/src/controller/metrics_controller.ts
index 6d0e835..b179d65 100644
--- a/ui/src/controller/metrics_controller.ts
+++ b/ui/src/controller/metrics_controller.ts
@@ -14,7 +14,7 @@
 
 import {Actions} from '../common/actions';
 import {Engine, QueryError} from '../common/engine';
-import {iter, STR} from '../common/query_iterator';
+import {STR} from '../common/query_result';
 
 import {Controller} from './controller';
 import {globals} from './globals';
@@ -33,13 +33,10 @@
 
   private async getMetricNames() {
     const metrics = [];
-    const it = iter(
-        {
-          name: STR,
-        },
-        await this.engine.query('select name from trace_metrics'));
+    const result = await this.engine.queryV2('select name from trace_metrics');
+    const it = result.iter({name: STR});
     for (; it.valid(); it.next()) {
-      metrics.push(it.row.name);
+      metrics.push(it.name);
     }
     return metrics;
   }
diff --git a/ui/src/controller/query_controller.ts b/ui/src/controller/query_controller.ts
index 2247884..7ac7857 100644
--- a/ui/src/controller/query_controller.ts
+++ b/ui/src/controller/query_controller.ts
@@ -15,8 +15,8 @@
 import {assertExists} from '../base/logging';
 import {Actions} from '../common/actions';
 import {Engine} from '../common/engine';
-import {Row} from '../common/protos';
 import {QueryResponse} from '../common/queries';
+import {Row} from '../common/query_result';
 
 import {Controller} from './controller';
 import {globals} from './globals';
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 02be3db..1c5c375 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -17,10 +17,10 @@
 import {Engine} from '../common/engine';
 import {
   NUM,
-  singleRow,
-  slowlyCountRows,
-  STR
-} from '../common/query_iterator';
+  NUM_NULL,
+  STR,
+  STR_NULL,
+} from '../common/query_result';
 import {ChromeSliceSelection} from '../common/state';
 import {translateState} from '../common/thread_state';
 import {fromNs, toNs} from '../common/time';
@@ -101,23 +101,23 @@
       promisedDescription = Promise.resolve(new Map());
       promisedArgs = Promise.resolve(new Map());
     } else {
-      const typeResult = singleRow(
-          {
-            leafTable: STR,
-            argSetId: NUM,
-          },
-          await this.args.engine.query(`
+      const result = await this.args.engine.queryV2(`
         SELECT
           type as leafTable,
           arg_set_id as argSetId
-        FROM slice WHERE id = ${selectedId}`));
+        FROM slice WHERE id = ${selectedId}`);
 
-      if (typeResult === undefined) {
+      if (result.numRows() === 0) {
         return;
       }
 
-      leafTable = typeResult.leafTable;
-      const argSetId = typeResult.argSetId;
+      const row = result.firstRow({
+        leafTable: STR,
+        argSetId: NUM,
+      });
+
+      leafTable = row.leafTable;
+      const argSetId = row.argSetId;
       promisedDescription = this.describeSlice(selectedId);
       promisedArgs = this.getArgs(argSetId);
     }
@@ -186,14 +186,17 @@
     const map = new Map<string, string>();
     if (id === -1) return map;
     const query = `
-      select description, doc_link
+      select
+        ifnull(description, '') as description,
+        ifnull(doc_link, '') as docLink
       from describe_slice
       where slice_id = ${id}
     `;
-    const result = await this.args.engine.query(query);
-    for (let i = 0; i < slowlyCountRows(result); i++) {
-      const description = result.columns[0].stringValues![i];
-      const docLink = result.columns[1].stringValues![i];
+    const result = await this.args.engine.queryV2(query);
+    const it = result.iter({description: STR, docLink: STR});
+    for (; it.valid(); it.next()) {
+      const description = it.description;
+      const docLink = it.docLink;
       map.set('Description', description);
       map.set('Documentation', docLink);
     }
@@ -209,10 +212,14 @@
       FROM args
       WHERE arg_set_id = ${argId}
     `;
-    const result = await this.args.engine.query(query);
-    for (let i = 0; i < slowlyCountRows(result); i++) {
-      const name = result.columns[0].stringValues![i];
-      const value = result.columns[1].stringValues![i];
+    const result = await this.args.engine.queryV2(query);
+    const it = result.iter({
+      name: STR,
+      value: STR,
+    });
+    for (; it.valid(); it.next()) {
+      const name = it.name;
+      const value = it.value;
       if (name === 'destination slice id' && !isNaN(Number(value))) {
         const destTrackId = await this.getDestTrackId(value);
         args.set(
@@ -226,10 +233,10 @@
   }
 
   async getDestTrackId(sliceId: string): Promise<string> {
-    const trackIdQuery = `select track_id from slice
+    const trackIdQuery = `select track_id as trackId from slice
     where slice_id = ${sliceId}`;
-    const destResult = await this.args.engine.query(trackIdQuery);
-    const trackIdTp = destResult.columns[0].longValues![0];
+    const result = await this.args.engine.queryV2(trackIdQuery);
+    const trackIdTp = result.firstRow({trackId: NUM}).trackId;
     // TODO(hjd): If we had a consistent mapping from TP track_id
     // UI track id for slice tracks this would be unnecessary.
     let trackId = '';
@@ -247,95 +254,114 @@
     const query = `
       SELECT
         ts,
-        thread_state.dur,
+        thread_state.dur as dur,
         state,
-        io_wait,
-        thread_state.utid,
-        thread_state.cpu,
-        sched.id,
-        thread_state.blocked_function
+        io_wait as ioWait,
+        thread_state.utid as utid,
+        thread_state.cpu as cpu,
+        sched.id as id,
+        thread_state.blocked_function as blockedFunction
       from thread_state
       left join sched using(ts) where thread_state.id = ${id}
     `;
-    this.args.engine.query(query).then(result => {
-      const selection = globals.state.currentSelection;
-      const cols = result.columns;
-      if (slowlyCountRows(result) === 1 && selection) {
-        const ts = cols[0].longValues![0];
-        const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
-        const dur = fromNs(cols[1].longValues![0]);
-        const stateStr = cols[2].stringValues![0];
-        const ioWait =
-            cols[3].isNulls![0] ? undefined : !!cols[3].longValues![0];
-        const state = translateState(stateStr, ioWait);
-        const utid = cols[4].longValues![0];
-        const cpu = cols[5].isNulls![0] ? undefined : cols[5].longValues![0];
-        const sliceId =
-            cols[6].isNulls![0] ? undefined : cols[6].longValues![0];
-        const blockedFunction =
-            cols[7].isNulls![0] ? undefined : cols[7].stringValues![0];
-        const selected: ThreadStateDetails = {
-          ts: timeFromStart,
-          dur,
-          state,
-          utid,
-          cpu,
-          sliceId,
-          blockedFunction
-        };
-        globals.publish('ThreadStateDetails', selected);
-      }
-    });
+    const result = await this.args.engine.queryV2(query);
+
+    const selection = globals.state.currentSelection;
+    if (result.numRows() > 0 && selection) {
+      const row = result.firstRow({
+        ts: NUM,
+        dur: NUM,
+        state: STR,
+        ioWait: NUM_NULL,
+        utid: NUM,
+        cpu: NUM_NULL,
+        id: NUM_NULL,
+        blockedFunction: STR_NULL,
+      });
+      const ts = row.ts;
+      const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
+      const dur = fromNs(row.dur);
+      const ioWait = row.ioWait === null ? undefined : row.ioWait > 0;
+      const state = translateState(row.state, ioWait);
+      const utid = row.utid;
+      const cpu = row.cpu === null ? undefined : row.cpu;
+      const sliceId = row.id === null ? undefined : row.id;
+      const blockedFunction =
+          row.blockedFunction === null ? undefined : row.blockedFunction;
+      const selected: ThreadStateDetails =
+          {ts: timeFromStart, dur, state, utid, cpu, sliceId, blockedFunction};
+      globals.publish('ThreadStateDetails', selected);
+    }
   }
 
   async sliceDetails(id: number) {
-    const sqlQuery = `SELECT ts, dur, priority, end_state, utid, cpu,
-    thread_state.id FROM sched join thread_state using(ts, utid, dur, cpu)
+    const sqlQuery = `SELECT
+      ts,
+      dur,
+      priority,
+      end_state as endState,
+      utid,
+      cpu,
+      thread_state.id as threadStateId
+    FROM sched join thread_state using(ts, utid, dur, cpu)
     WHERE sched.id = ${id}`;
-    this.args.engine.query(sqlQuery).then(result => {
-      // Check selection is still the same on completion of query.
-      const selection = globals.state.currentSelection;
-      if (slowlyCountRows(result) === 1 && selection) {
-        const ts = result.columns[0].longValues![0];
-        const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
-        const dur = fromNs(result.columns[1].longValues![0]);
-        const priority = result.columns[2].longValues![0];
-        const endState = result.columns[3].stringValues![0];
-        const utid = result.columns[4].longValues![0];
-        const cpu = result.columns[5].longValues![0];
-        const threadStateId = result.columns[6].longValues![0];
-        const selected: SliceDetails = {
-          ts: timeFromStart,
-          dur,
-          priority,
-          endState,
-          cpu,
-          id,
-          utid,
-          threadStateId
-        };
-        this.schedulingDetails(ts, utid)
-            .then(wakeResult => {
-              Object.assign(selected, wakeResult);
-            })
-            .finally(() => {
-              globals.publish('SliceDetails', selected);
-            });
-      }
-    });
+    const result = await this.args.engine.queryV2(sqlQuery);
+    // Check selection is still the same on completion of query.
+    const selection = globals.state.currentSelection;
+    if (result.numRows() > 0 && selection) {
+      const row = result.firstRow({
+        ts: NUM,
+        dur: NUM,
+        priority: NUM,
+        endState: STR,
+        utid: NUM,
+        cpu: NUM,
+        threadStateId: NUM,
+      });
+      const ts = row.ts;
+      const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
+      const dur = fromNs(row.dur);
+      const priority = row.priority;
+      const endState = row.endState;
+      const utid = row.utid;
+      const cpu = row.cpu;
+      const threadStateId = row.threadStateId;
+      const selected: SliceDetails = {
+        ts: timeFromStart,
+        dur,
+        priority,
+        endState,
+        cpu,
+        id,
+        utid,
+        threadStateId
+      };
+      this.schedulingDetails(ts, utid)
+          .then(wakeResult => {
+            Object.assign(selected, wakeResult);
+          })
+          .finally(() => {
+            globals.publish('SliceDetails', selected);
+          });
+    }
   }
 
   async counterDetails(ts: number, rightTs: number, id: number) {
-    const counter = await this.args.engine.query(
-        `SELECT value, track_id FROM counter WHERE id = ${id}`);
-    const value = counter.columns[0].doubleValues![0];
-    const trackId = counter.columns[1].longValues![0];
+    const counter = await this.args.engine.queryV2(
+        `SELECT value, track_id as trackId FROM counter WHERE id = ${id}`);
+    const row = counter.iter({
+      value: NUM,
+      trackId: NUM,
+    });
+    const value = row.value;
+    const trackId = row.trackId;
     // Finding previous value. If there isn't previous one, it will return 0 for
     // ts and value.
-    const previous = await this.args.engine.query(
-        `SELECT MAX(ts), value FROM counter WHERE ts < ${ts} and track_id = ${
-            trackId}`);
-    const previousValue = previous.columns[1].doubleValues![0];
+    const previous = await this.args.engine.queryV2(`SELECT
+          MAX(ts),
+          IFNULL(value, 0)
+        FROM counter WHERE ts < ${ts} and track_id = ${trackId}`);
+    const previousValue = previous.firstRow({value: NUM}).value;
     const endTs =
         rightTs !== -1 ? rightTs : toNs(globals.state.traceTime.endSec);
     const delta = value - previousValue;
@@ -346,12 +372,12 @@
 
   async schedulingDetails(ts: number, utid: number|Long) {
     let event = 'sched_waking';
-    const waking = await this.args.engine.query(
+    const waking = await this.args.engine.queryV2(
         `select * from instants where name = 'sched_waking' limit 1`);
-    const wakeup = await this.args.engine.query(
+    const wakeup = await this.args.engine.queryV2(
         `select * from instants where name = 'sched_wakeup' limit 1`);
-    if (slowlyCountRows(waking) === 0) {
-      if (slowlyCountRows(wakeup) === 0) return undefined;
+    if (waking.numRows() === 0) {
+      if (wakeup.numRows() === 0) return undefined;
       // Only use sched_wakeup if waking is not in the trace.
       event = 'sched_wakeup';
     }
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index b2d38a9..f9b3e44 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -23,7 +23,7 @@
 import {TRACE_MARGIN_TIME_S} from '../common/constants';
 import {Engine, QueryError} from '../common/engine';
 import {HttpRpcEngine} from '../common/http_rpc_engine';
-import {iter, NUM, slowlyCountRows, STR} from '../common/query_iterator';
+import {NUM, NUM_NULL, STR, STR_NULL} from '../common/query_result';
 import {EngineMode} from '../common/state';
 import {TimeSpan, toNs, toNsCeil, toNsFloor} from '../common/time';
 import {WasmEngineProxy} from '../common/wasm_engine_proxy';
@@ -336,8 +336,8 @@
       //   of the limit 1, and instead delegate the filtering to the iterator.
       const query = `select '_' as _ from raw
           where cpu + 1 > 1 or utid + 1 > 1 limit 1`;
-      const result = await assertExists(this.engine).query(query);
-      const hasFtrace = !!slowlyCountRows(result);
+      const result = await assertExists(this.engine).queryV2(query);
+      const hasFtrace = result.numRows() > 0;
       globals.publish('HasFtrace', hasFtrace);
     }
 
@@ -355,11 +355,12 @@
         union
         select distinct(graph_sample_ts) as ts, 'graph' as type, upid from
         heap_graph_object) order by ts limit 1`;
-    const profile = await assertExists(this.engine).query(query);
-    if (profile.numRecords !== 1) return;
-    const ts = profile.columns[0].longValues![0];
-    const type = profile.columns[1].stringValues![0];
-    const upid = profile.columns[2].longValues![0];
+    const profile = await assertExists(this.engine).queryV2(query);
+    if (profile.numRows() !== 1) return;
+    const row = profile.firstRow({ts: NUM, type: STR, upid: NUM});
+    const ts = row.ts;
+    const type = row.type;
+    const upid = row.upid;
     globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
   }
 
@@ -372,25 +373,37 @@
 
   private async listThreads() {
     this.updateStatus('Reading thread list');
-    const sqlQuery = `select utid, tid, pid, thread.name,
+    const query = `select
+        utid,
+        tid,
+        pid,
+        ifnull(thread.name, '') as threadName,
         ifnull(
           case when length(process.name) > 0 then process.name else null end,
-          thread.name),
-        process.cmdline
+          thread.name) as procName,
+        process.cmdline as cmdline
         from (select * from thread order by upid) as thread
         left join (select * from process order by upid) as process
         using(upid)`;
-    const threadRows = await assertExists(this.engine).query(sqlQuery);
+    const result = await assertExists(this.engine).queryV2(query);
     const threads: ThreadDesc[] = [];
-    for (let i = 0; i < slowlyCountRows(threadRows); i++) {
-      const utid = threadRows.columns[0].longValues![i];
-      const tid = threadRows.columns[1].longValues![i];
-      const pid = threadRows.columns[2].longValues![i];
-      const threadName = threadRows.columns[3].stringValues![i];
-      const procName = threadRows.columns[4].stringValues![i];
-      const cmdline = threadRows.columns[5].stringValues![i];
+    const it = result.iter({
+      utid: NUM,
+      tid: NUM,
+      pid: NUM_NULL,
+      threadName: STR,
+      procName: STR_NULL,
+      cmdline: STR_NULL,
+    });
+    for (; it.valid(); it.next()) {
+      const utid = it.utid;
+      const tid = it.tid;
+      const pid = it.pid === null ? undefined : it.pid;
+      const threadName = it.threadName;
+      const procName = it.procName === null ? undefined : it.procName;
+      const cmdline = it.cmdline === null ? undefined : it.cmdline;
       threads.push({utid, tid, threadName, pid, procName, cmdline});
-    }  // for (record ...)
+    }
     globals.publish('Threads', threads);
   }
 
@@ -409,19 +422,20 @@
       const endNs = toNsCeil(endSec);
 
       // Sched overview.
-      const schedRows = await engine.query(
-          `select sum(dur)/${stepSec}/1e9, cpu from sched ` +
+      const schedResult = await engine.queryV2(
+          `select sum(dur)/${stepSec}/1e9 as load, cpu from sched ` +
           `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
           'group by cpu order by cpu');
       const schedData: {[key: string]: QuantizedLoad} = {};
-      for (let i = 0; i < slowlyCountRows(schedRows); i++) {
-        const load = schedRows.columns[0].doubleValues![i];
-        const cpu = schedRows.columns[1].longValues![i];
+      const it = schedResult.iter({load: NUM, cpu: NUM});
+      for (; it.valid(); it.next()) {
+        const load = it.load;
+        const cpu = it.cpu;
         schedData[cpu] = {startSec, endSec, load};
         hasSchedOverview = true;
-      }  // for (record ...)
+      }
       globals.publish('OverviewData', schedData);
-    }  // for (step ...)
+    }
 
     if (hasSchedOverview) {
       return;
@@ -430,10 +444,10 @@
     // Slices overview.
     const traceStartNs = toNs(traceTime.start);
     const stepSecNs = toNs(stepSec);
-    const sliceSummaryQuery = await engine.query(`select
+    const sliceResult = await engine.queryV2(`select
            bucket,
            upid,
-           sum(utid_sum) / cast(${stepSecNs} as float) as upid_sum
+           sum(utid_sum) / cast(${stepSecNs} as float) as load
          from thread
          inner join (
            select
@@ -447,10 +461,11 @@
          group by bucket, upid`);
 
     const slicesData: {[key: string]: QuantizedLoad[]} = {};
-    for (let i = 0; i < slowlyCountRows(sliceSummaryQuery); i++) {
-      const bucket = sliceSummaryQuery.columns[0].longValues![i];
-      const upid = sliceSummaryQuery.columns[1].longValues![i];
-      const load = sliceSummaryQuery.columns[2].doubleValues![i];
+    const it = sliceResult.iter({bucket: NUM, upid: NUM, load: NUM});
+    for (; it.valid(); it.next()) {
+      const bucket = it.bucket;
+      const upid = it.upid;
+      const load = it.load;
 
       const startSec = traceTime.start + stepSec * bucket;
       const endSec = startSec + stepSec;
@@ -467,13 +482,12 @@
 
   private async cacheCurrentTrace() {
     const engine = assertExists(this.engine);
-    const query = await engine.query(`select str_value from metadata
+    const result = await engine.queryV2(`select str_value as uuid from metadata
                   where name = 'trace_uuid'`);
-    const it = iter({'str_value': STR}, query);
-    if (!it.valid()) {
+    if (result.numRows() === 0) {
       throw new Error('metadata.trace_uuid could not be found.');
     }
-    const traceUuid = it.row.str_value;
+    const traceUuid = result.firstRow({uuid: STR}).uuid;
     const engineConfig = assertExists(Object.values(globals.state.engines)[0]);
     cacheTrace(engineConfig.source, traceUuid);
     globals.dispatch(Actions.setTraceUuid({traceUuid}));
@@ -555,13 +569,15 @@
 
       this.updateStatus(`Inserting data for ${metric} metric`);
       try {
-        const result = await engine.query(`pragma table_info(${metric}_event)`);
+        const result =
+            await engine.queryV2(`pragma table_info(${metric}_event)`);
         let hasSliceName = false;
         let hasDur = false;
         let hasUpid = false;
         let hasValue = false;
-        for (let i = 0; i < slowlyCountRows(result); i++) {
-          const name = result.columns[1].stringValues![i];
+        const it = result.iter({name: STR});
+        for (; it.valid(); it.next()) {
+          const name = it.name;
           hasSliceName = hasSliceName || name === 'slice_name';
           hasDur = hasDur || name === 'dur';
           hasUpid = hasUpid || name === 'upid';
diff --git a/ui/src/controller/trace_error_controller.ts b/ui/src/controller/trace_error_controller.ts
index 4f6eaa7..09e3380 100644
--- a/ui/src/controller/trace_error_controller.ts
+++ b/ui/src/controller/trace_error_controller.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {Engine} from '../common/engine';
-import {NUM} from '../common/query_iterator';
+import {NUM} from '../common/query_result';
 
 import {Controller} from './controller';
 import {globals} from './globals';
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
index de05b94..c0dfc8c 100644
--- a/ui/src/controller/track_controller.ts
+++ b/ui/src/controller/track_controller.ts
@@ -113,11 +113,6 @@
     return resolution >= 0.0008;
   }
 
-  protected async query(query: string) {
-    const result = await this.engine.query(query);
-    return result;
-  }
-
   protected async queryV2(query: string) {
     const result = await this.engine.queryV2(query);
     return result;
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index dd69835..84aa565 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -26,7 +26,7 @@
   NUM_NULL,
   STR,
   STR_NULL,
-} from '../common/query_iterator';
+} from '../common/query_result';
 import {SCROLLING_TRACK_GROUP, TrackKindPriority} from '../common/state';
 import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames/common';
 import {ANDROID_LOGS_TRACK_KIND} from '../tracks/android_log/common';