1// Copyright (C) 2021 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 * as protoNamespace from '../gen/protos'; 16 17import {createQueryResult, NUM, NUM_NULL, STR, STR_NULL} from './query_result'; 18 19const T = protoNamespace.perfetto.protos.QueryResult.CellsBatch.CellType; 20const QueryResultProto = protoNamespace.perfetto.protos.QueryResult; 21 22test('QueryResult.SimpleOneRow', () => { 23 const batch = QueryResultProto.CellsBatch.create({ 24 cells: [T.CELL_STRING, T.CELL_VARINT, T.CELL_STRING, T.CELL_FLOAT64], 25 varintCells: [42], 26 stringCells: ['the foo', 'the bar'].join('\0'), 27 float64Cells: [42.42], 28 isLastBatch: true, 29 }); 30 const resProto = QueryResultProto.create({ 31 columnNames: ['a_str', 'b_int', 'c_str', 'd_float'], 32 batch: [batch], 33 }); 34 35 const qr = createQueryResult({query: 'Some query'}); 36 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 37 expect(qr.isComplete()).toBe(true); 38 expect(qr.numRows()).toBe(1); 39 40 // First try iterating without selecting any column. 41 { 42 const iter = qr.iter({}); 43 expect(iter.valid()).toBe(true); 44 iter.next(); 45 expect(iter.valid()).toBe(false); 46 } 47 48 // Then select only two of them. 49 { 50 const iter = qr.iter({c_str: STR, d_float: NUM}); 51 expect(iter.valid()).toBe(true); 52 expect(iter.c_str).toBe('the bar'); 53 expect(iter.d_float).toBeCloseTo(42.42); 54 iter.next(); 55 expect(iter.valid()).toBe(false); 56 } 57 58 // If a column is not present in the result set, iter() should throw. 59 expect(() => qr.iter({nx: NUM})).toThrowError(/\bnx\b.*not found/); 60}); 61 62test('QueryResult.BigNumbers', () => { 63 const numAndExpectedStr = [ 64 [0, '0'], 65 [-1, '-1'], 66 [-1000, '-1000'], 67 [1e12, '1000000000000'], 68 [1e12 * -1, '-1000000000000'], 69 [((1 << 31) - 1) | 0, '2147483647'], 70 [1 << 31, '-2147483648'], 71 [Number.MAX_SAFE_INTEGER, '9007199254740991'], 72 [Number.MIN_SAFE_INTEGER, '-9007199254740991'], 73 ]; 74 const batch = QueryResultProto.CellsBatch.create({ 75 cells: new Array<number>(numAndExpectedStr.length).fill(T.CELL_VARINT), 76 varintCells: numAndExpectedStr.map(x => x[0]) as number[], 77 isLastBatch: true, 78 }); 79 const resProto = QueryResultProto.create({ 80 columnNames: ['n'], 81 batch: [batch], 82 }); 83 84 const qr = createQueryResult({query: 'Some query'}); 85 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 86 const actual: string[] = []; 87 for (const iter = qr.iter({n: NUM}); iter.valid(); iter.next()) { 88 actual.push(BigInt(iter.n).toString()); 89 } 90 expect(actual).toEqual(numAndExpectedStr.map(x => x[1]) as string[]); 91}); 92 93test('QueryResult.Floats', () => { 94 const floats = [ 95 0.0, 96 1.0, 97 -1.0, 98 3.14159265358, 99 Number.MIN_SAFE_INTEGER, 100 Number.MAX_SAFE_INTEGER, 101 Number.NEGATIVE_INFINITY, 102 Number.POSITIVE_INFINITY, 103 Number.NaN, 104 ]; 105 const batch = QueryResultProto.CellsBatch.create({ 106 cells: new Array<number>(floats.length).fill(T.CELL_FLOAT64), 107 float64Cells: floats, 108 isLastBatch: true, 109 }); 110 const resProto = QueryResultProto.create({ 111 columnNames: ['n'], 112 batch: [batch], 113 }); 114 115 const qr = createQueryResult({query: 'Some query'}); 116 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 117 const actual: number[] = []; 118 for (const iter = qr.iter({n: NUM}); iter.valid(); iter.next()) { 119 actual.push(iter.n); 120 } 121 expect(actual).toEqual(floats); 122}); 123 124test('QueryResult.Strings', () => { 125 const strings = [ 126 'a', 127 '', 128 '', 129 'hello world', 130 'In einem Bächlein helle da schoß in froher Eil', 131 '色は匂へど散りぬるを我が世誰ぞ常ならん有為の奥山今日越えて浅き夢見じ酔ひもせず' 132 ]; 133 const batch = QueryResultProto.CellsBatch.create({ 134 cells: new Array<number>(strings.length).fill(T.CELL_STRING), 135 stringCells: strings.join('\0'), 136 isLastBatch: true, 137 }); 138 const resProto = QueryResultProto.create({ 139 columnNames: ['s'], 140 batch: [batch], 141 }); 142 143 const qr = createQueryResult({query: 'Some query'}); 144 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 145 const actual: string[] = []; 146 for (const iter = qr.iter({s: STR}); iter.valid(); iter.next()) { 147 actual.push(iter.s); 148 } 149 expect(actual).toEqual(strings); 150}); 151 152test('QueryResult.NullChecks', () => { 153 const cells: number[] = []; 154 cells.push(T.CELL_VARINT, T.CELL_NULL); 155 cells.push(T.CELL_NULL, T.CELL_STRING); 156 cells.push(T.CELL_VARINT, T.CELL_STRING); 157 const batch = QueryResultProto.CellsBatch.create({ 158 cells, 159 varintCells: [1, 2], 160 stringCells: ['a', 'b'].join('\0'), 161 isLastBatch: true, 162 }); 163 const resProto = QueryResultProto.create({ 164 columnNames: ['n', 's'], 165 batch: [batch], 166 }); 167 168 const qr = createQueryResult({query: 'Some query'}); 169 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 170 const actualNums = new Array<number|null>(); 171 const actualStrings = new Array<string|null>(); 172 for (const iter = qr.iter({n: NUM_NULL, s: STR_NULL}); iter.valid(); 173 iter.next()) { 174 actualNums.push(iter.n); 175 actualStrings.push(iter.s); 176 } 177 expect(actualNums).toEqual([1, null, 2]); 178 expect(actualStrings).toEqual([null, 'a', 'b']); 179 180 // Check that using NUM / STR throws. 181 expect(() => qr.iter({n: NUM_NULL, s: STR})) 182 .toThrowError(/col: 's'.*is NULL.*not expected/); 183 expect(() => qr.iter({n: NUM, s: STR_NULL})) 184 .toThrowError(/col: 'n'.*is NULL.*not expected/); 185 expect(qr.iter({n: NUM_NULL})).toBeTruthy(); 186 expect(qr.iter({s: STR_NULL})).toBeTruthy(); 187}); 188 189test('QueryResult.EarlyError', () => { 190 const resProto = QueryResultProto.create({ 191 columnNames: [], 192 batch: [{isLastBatch: true}], 193 error: 'Oh dear, this SQL query is too complicated, I give up', 194 }); 195 const qr = createQueryResult({query: 'Some query'}); 196 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 197 expect(qr.error()).toContain('Oh dear'); 198 expect(qr.isComplete()).toBe(true); 199 const iter = qr.iter({}); 200 expect(iter.valid()).toBe(false); 201}); 202 203test('QueryResult.LateError', () => { 204 const resProto = QueryResultProto.create({ 205 columnNames: ['n'], 206 batch: [ 207 { 208 cells: [T.CELL_VARINT], 209 varintCells: [1], 210 }, 211 { 212 cells: [T.CELL_VARINT], 213 varintCells: [2], 214 isLastBatch: true, 215 }, 216 ], 217 error: 'I tried, I was getting there, but then I failed', 218 }); 219 const qr = createQueryResult({query: 'Some query'}); 220 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 221 expect(qr.error()).toContain('I failed'); 222 const rows: number[] = []; 223 for (const iter = qr.iter({n: NUM}); iter.valid(); iter.next()) { 224 rows.push(iter.n); 225 } 226 expect(rows).toEqual([1, 2]); 227 expect(qr.isComplete()).toBe(true); 228}); 229 230 231test('QueryResult.MultipleBatches', async () => { 232 const batch1 = QueryResultProto.create({ 233 columnNames: ['n'], 234 batch: [{ 235 cells: [T.CELL_VARINT], 236 varintCells: [1], 237 isLastBatch: false, 238 }], 239 }); 240 const batch2 = QueryResultProto.create({ 241 batch: [{ 242 cells: [T.CELL_VARINT], 243 varintCells: [2], 244 isLastBatch: true, 245 }], 246 }); 247 248 const qr = createQueryResult({query: 'Some query'}); 249 expect(qr.isComplete()).toBe(false); 250 251 qr.appendResultBatch(QueryResultProto.encode(batch1).finish()); 252 qr.appendResultBatch(QueryResultProto.encode(batch2).finish()); 253 254 const awaitRes = await qr; 255 256 expect(awaitRes.isComplete()).toBe(true); 257 expect(qr.isComplete()).toBe(true); 258 259 expect(awaitRes.numRows()).toBe(2); 260 expect(qr.numRows()).toBe(2); 261}); 262 263 264// Regression test for b/194891824 . 265test('QueryResult.DuplicateColumnNames', () => { 266 const batch = QueryResultProto.CellsBatch.create({ 267 cells: [ 268 T.CELL_VARINT, 269 T.CELL_STRING, 270 T.CELL_FLOAT64, 271 T.CELL_STRING, 272 T.CELL_STRING 273 ], 274 varintCells: [42], 275 stringCells: ['a', 'b', 'c'].join('\0'), 276 float64Cells: [4.2], 277 isLastBatch: true, 278 }); 279 const resProto = QueryResultProto.create({ 280 columnNames: ['x', 'y', 'x', 'x', 'y'], 281 batch: [batch], 282 }); 283 284 const qr = createQueryResult({query: 'Some query'}); 285 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 286 expect(qr.isComplete()).toBe(true); 287 expect(qr.numRows()).toBe(1); 288 expect(qr.columns()).toEqual(['x', 'y', 'x_1', 'x_2', 'y_1']); 289 // First try iterating without selecting any column. 290 { 291 const iter = qr.iter({x: NUM, y: STR, x_1: NUM, x_2: STR, y_1: STR}); 292 expect(iter.valid()).toBe(true); 293 expect(iter.x).toBe(42); 294 expect(iter.y).toBe('a'); 295 expect(iter.x_1).toBe(4.2); 296 expect(iter.x_2).toBe('b'); 297 expect(iter.y_1).toBe('c'); 298 iter.next(); 299 expect(iter.valid()).toBe(false); 300 } 301 expect(() => qr.iter({x_3: NUM})).toThrowError(/\bx_3\b.*not found/); 302}); 303