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 {QueryResult as QueryResultProto} from '../protos'; 16 17import { 18 createQueryResult, 19 decodeInt64Varint, 20 NUM, 21 NUM_NULL, 22 STR, 23 STR_NULL, 24} from './query_result'; 25 26const T = QueryResultProto.CellsBatch.CellType; 27 28test('QueryResult.SimpleOneRow', () => { 29 const batch = QueryResultProto.CellsBatch.create({ 30 cells: [T.CELL_STRING, T.CELL_VARINT, T.CELL_STRING, T.CELL_FLOAT64], 31 varintCells: [42], 32 stringCells: ['the foo', 'the bar'].join('\0'), 33 float64Cells: [42.42], 34 isLastBatch: true, 35 }); 36 const resProto = QueryResultProto.create({ 37 columnNames: ['a_str', 'b_int', 'c_str', 'd_float'], 38 batch: [batch], 39 }); 40 41 const qr = createQueryResult({query: 'Some query'}); 42 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 43 expect(qr.isComplete()).toBe(true); 44 expect(qr.numRows()).toBe(1); 45 46 // First try iterating without selecting any column. 47 { 48 const iter = qr.iter({}); 49 expect(iter.valid()).toBe(true); 50 iter.next(); 51 expect(iter.valid()).toBe(false); 52 } 53 54 // Then select only two of them. 55 { 56 const iter = qr.iter({c_str: STR, d_float: NUM}); 57 expect(iter.valid()).toBe(true); 58 expect(iter.c_str).toBe('the bar'); 59 expect(iter.d_float).toBeCloseTo(42.42); 60 iter.next(); 61 expect(iter.valid()).toBe(false); 62 } 63 64 // If a column is not present in the result set, iter() should throw. 65 expect(() => qr.iter({nx: NUM})).toThrowError(/\bnx\b.*not found/); 66}); 67 68test('QueryResult.BigNumbers', () => { 69 const numAndExpectedStr = [ 70 [0, '0'], 71 [-1, '-1'], 72 [-1000, '-1000'], 73 [1e12, '1000000000000'], 74 [1e12 * -1, '-1000000000000'], 75 [((1 << 31) - 1) | 0, '2147483647'], 76 [1 << 31, '-2147483648'], 77 [Number.MAX_SAFE_INTEGER, '9007199254740991'], 78 [Number.MIN_SAFE_INTEGER, '-9007199254740991'], 79 ]; 80 const batch = QueryResultProto.CellsBatch.create({ 81 cells: new Array<number>(numAndExpectedStr.length).fill(T.CELL_VARINT), 82 varintCells: numAndExpectedStr.map((x) => x[0]) as number[], 83 isLastBatch: true, 84 }); 85 const resProto = QueryResultProto.create({ 86 columnNames: ['n'], 87 batch: [batch], 88 }); 89 90 const qr = createQueryResult({query: 'Some query'}); 91 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 92 const actual: string[] = []; 93 for (const iter = qr.iter({n: NUM}); iter.valid(); iter.next()) { 94 actual.push(BigInt(iter.n).toString()); 95 } 96 expect(actual).toEqual(numAndExpectedStr.map((x) => x[1]) as string[]); 97}); 98 99test('QueryResult.Floats', () => { 100 const floats = [ 101 0.0, 102 1.0, 103 -1.0, 104 3.14159265358, 105 Number.MIN_SAFE_INTEGER, 106 Number.MAX_SAFE_INTEGER, 107 Number.NEGATIVE_INFINITY, 108 Number.POSITIVE_INFINITY, 109 Number.NaN, 110 ]; 111 const batch = QueryResultProto.CellsBatch.create({ 112 cells: new Array<number>(floats.length).fill(T.CELL_FLOAT64), 113 float64Cells: floats, 114 isLastBatch: true, 115 }); 116 const resProto = QueryResultProto.create({ 117 columnNames: ['n'], 118 batch: [batch], 119 }); 120 121 const qr = createQueryResult({query: 'Some query'}); 122 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 123 const actual: number[] = []; 124 for (const iter = qr.iter({n: NUM}); iter.valid(); iter.next()) { 125 actual.push(iter.n); 126 } 127 expect(actual).toEqual(floats); 128}); 129 130test('QueryResult.Strings', () => { 131 const strings = [ 132 'a', 133 '', 134 '', 135 'hello world', 136 'In einem Bächlein helle da schoß in froher Eil', 137 '色は匂へど散りぬるを我が世誰ぞ常ならん有為の奥山今日越えて浅き夢見じ酔ひもせず', 138 ]; 139 const batch = QueryResultProto.CellsBatch.create({ 140 cells: new Array<number>(strings.length).fill(T.CELL_STRING), 141 stringCells: strings.join('\0'), 142 isLastBatch: true, 143 }); 144 const resProto = QueryResultProto.create({ 145 columnNames: ['s'], 146 batch: [batch], 147 }); 148 149 const qr = createQueryResult({query: 'Some query'}); 150 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 151 const actual: string[] = []; 152 for (const iter = qr.iter({s: STR}); iter.valid(); iter.next()) { 153 actual.push(iter.s); 154 } 155 expect(actual).toEqual(strings); 156}); 157 158test('QueryResult.NullChecks', () => { 159 const cells: number[] = []; 160 cells.push(T.CELL_VARINT, T.CELL_NULL); 161 cells.push(T.CELL_NULL, T.CELL_STRING); 162 cells.push(T.CELL_VARINT, T.CELL_STRING); 163 const batch = QueryResultProto.CellsBatch.create({ 164 cells, 165 varintCells: [1, 2], 166 stringCells: ['a', 'b'].join('\0'), 167 isLastBatch: true, 168 }); 169 const resProto = QueryResultProto.create({ 170 columnNames: ['n', 's'], 171 batch: [batch], 172 }); 173 174 const qr = createQueryResult({query: 'Some query'}); 175 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 176 const actualNums = new Array<number | null>(); 177 const actualStrings = new Array<string | null>(); 178 for ( 179 const iter = qr.iter({n: NUM_NULL, s: STR_NULL}); 180 iter.valid(); 181 iter.next() 182 ) { 183 actualNums.push(iter.n); 184 actualStrings.push(iter.s); 185 } 186 expect(actualNums).toEqual([1, null, 2]); 187 expect(actualStrings).toEqual([null, 'a', 'b']); 188 189 // Check that using NUM / STR throws. 190 expect(() => qr.iter({n: NUM_NULL, s: STR})).toThrowError( 191 /col: 's'.*is NULL.*not expected/, 192 ); 193 expect(() => qr.iter({n: NUM, s: STR_NULL})).toThrowError( 194 /col: 'n'.*is NULL.*not expected/, 195 ); 196 expect(qr.iter({n: NUM_NULL})).toBeTruthy(); 197 expect(qr.iter({s: STR_NULL})).toBeTruthy(); 198}); 199 200test('QueryResult.EarlyError', () => { 201 const resProto = QueryResultProto.create({ 202 columnNames: [], 203 batch: [{isLastBatch: true}], 204 error: 'Oh dear, this SQL query is too complicated, I give up', 205 }); 206 const qr = createQueryResult({query: 'Some query'}); 207 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 208 expect(qr.error()).toContain('Oh dear'); 209 expect(qr.isComplete()).toBe(true); 210 const iter = qr.iter({}); 211 expect(iter.valid()).toBe(false); 212}); 213 214test('QueryResult.LateError', () => { 215 const resProto = QueryResultProto.create({ 216 columnNames: ['n'], 217 batch: [ 218 { 219 cells: [T.CELL_VARINT], 220 varintCells: [1], 221 }, 222 { 223 cells: [T.CELL_VARINT], 224 varintCells: [2], 225 isLastBatch: true, 226 }, 227 ], 228 error: 'I tried, I was getting there, but then I failed', 229 }); 230 const qr = createQueryResult({query: 'Some query'}); 231 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 232 expect(qr.error()).toContain('I failed'); 233 const rows: number[] = []; 234 for (const iter = qr.iter({n: NUM}); iter.valid(); iter.next()) { 235 rows.push(iter.n); 236 } 237 expect(rows).toEqual([1, 2]); 238 expect(qr.isComplete()).toBe(true); 239}); 240 241test('QueryResult.MultipleBatches', async () => { 242 const batch1 = QueryResultProto.create({ 243 columnNames: ['n'], 244 batch: [ 245 { 246 cells: [T.CELL_VARINT], 247 varintCells: [1], 248 isLastBatch: false, 249 }, 250 ], 251 }); 252 const batch2 = QueryResultProto.create({ 253 batch: [ 254 { 255 cells: [T.CELL_VARINT], 256 varintCells: [2], 257 isLastBatch: true, 258 }, 259 ], 260 }); 261 262 const qr = createQueryResult({query: 'Some query'}); 263 expect(qr.isComplete()).toBe(false); 264 265 qr.appendResultBatch(QueryResultProto.encode(batch1).finish()); 266 qr.appendResultBatch(QueryResultProto.encode(batch2).finish()); 267 268 const awaitRes = await qr; 269 270 expect(awaitRes.isComplete()).toBe(true); 271 expect(qr.isComplete()).toBe(true); 272 273 expect(awaitRes.numRows()).toBe(2); 274 expect(qr.numRows()).toBe(2); 275}); 276 277// Regression test for b/194891824 . 278test('QueryResult.DuplicateColumnNames', () => { 279 const batch = QueryResultProto.CellsBatch.create({ 280 cells: [ 281 T.CELL_VARINT, 282 T.CELL_STRING, 283 T.CELL_FLOAT64, 284 T.CELL_STRING, 285 T.CELL_STRING, 286 ], 287 varintCells: [42], 288 stringCells: ['a', 'b', 'c'].join('\0'), 289 float64Cells: [4.2], 290 isLastBatch: true, 291 }); 292 const resProto = QueryResultProto.create({ 293 columnNames: ['x', 'y', 'x', 'x', 'y'], 294 batch: [batch], 295 }); 296 297 const qr = createQueryResult({query: 'Some query'}); 298 qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); 299 expect(qr.isComplete()).toBe(true); 300 expect(qr.numRows()).toBe(1); 301 expect(qr.columns()).toEqual(['x', 'y', 'x_1', 'x_2', 'y_1']); 302 // First try iterating without selecting any column. 303 { 304 const iter = qr.iter({x: NUM, y: STR, x_1: NUM, x_2: STR, y_1: STR}); 305 expect(iter.valid()).toBe(true); 306 expect(iter.x).toBe(42); 307 expect(iter.y).toBe('a'); 308 expect(iter.x_1).toBe(4.2); 309 expect(iter.x_2).toBe('b'); 310 expect(iter.y_1).toBe('c'); 311 iter.next(); 312 expect(iter.valid()).toBe(false); 313 } 314 expect(() => qr.iter({x_3: NUM})).toThrowError(/\bx_3\b.*not found/); 315}); 316 317test('QueryResult.WaitMoreRows', async () => { 318 const batchA = QueryResultProto.CellsBatch.create({ 319 cells: [T.CELL_VARINT], 320 varintCells: [42], 321 isLastBatch: false, 322 }); 323 const resProtoA = QueryResultProto.create({ 324 columnNames: ['a_int'], 325 batch: [batchA], 326 }); 327 328 const qr = createQueryResult({query: 'Some query'}); 329 qr.appendResultBatch(QueryResultProto.encode(resProtoA).finish()); 330 331 const batchB = QueryResultProto.CellsBatch.create({ 332 cells: [T.CELL_VARINT], 333 varintCells: [43], 334 isLastBatch: true, 335 }); 336 const resProtoB = QueryResultProto.create({ 337 columnNames: [], 338 batch: [batchB], 339 }); 340 341 const waitPromise = qr.waitMoreRows(); 342 const appendPromise = new Promise<void>((resolve, _) => { 343 setTimeout(() => { 344 qr.appendResultBatch(QueryResultProto.encode(resProtoB).finish()); 345 resolve(); 346 }, 0); 347 }); 348 349 expect(qr.isComplete()).toBe(false); 350 expect(qr.numRows()).toBe(1); 351 352 await Promise.all([waitPromise, appendPromise]); 353 354 expect(qr.isComplete()).toBe(true); 355 expect(qr.numRows()).toBe(2); 356}); 357 358describe('decodeInt64Varint', () => { 359 test('Parsing empty input should throw an error', () => { 360 expect(() => decodeInt64Varint(new Uint8Array(), 0)).toThrow( 361 'Index out of range', 362 ); 363 }); 364 365 test('Parsing single byte positive integers', () => { 366 const testData: Array<[Uint8Array, BigInt]> = [ 367 [new Uint8Array([0x00]), 0n], 368 [new Uint8Array([0x01]), 1n], 369 [new Uint8Array([0x7f]), 127n], 370 ]; 371 372 testData.forEach(([input, expected]) => { 373 expect(decodeInt64Varint(input, 0)).toEqual(expected); 374 }); 375 }); 376 377 test('Parsing multi-byte positive integers', () => { 378 const testData: Array<[Uint8Array, BigInt]> = [ 379 [new Uint8Array([0x80, 0x01]), 128n], 380 [new Uint8Array([0xff, 0x7f]), 16383n], 381 [new Uint8Array([0x80, 0x80, 0x01]), 16384n], 382 [new Uint8Array([0xff, 0xff, 0x7f]), 2097151n], 383 [ 384 new Uint8Array([ 385 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 386 ]), 387 9223372036854775807n, 388 ], 389 ]; 390 391 testData.forEach(([input, expected]) => { 392 expect(decodeInt64Varint(input, 0)).toEqual(expected); 393 }); 394 }); 395 396 test('Parsing negative integers', () => { 397 const testData: Array<[Uint8Array, BigInt]> = [ 398 [ 399 new Uint8Array([ 400 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 401 ]), 402 -1n, 403 ], 404 [ 405 new Uint8Array([ 406 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 407 ]), 408 -2n, 409 ], 410 [ 411 new Uint8Array([ 412 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01, 413 ]), 414 -9223372036854775808n, 415 ], 416 ]; 417 418 testData.forEach(([input, expected]) => { 419 expect(decodeInt64Varint(input, 0)).toEqual(expected); 420 }); 421 }); 422 423 test('Parsing with incomplete varint should throw an error', () => { 424 const testData: Array<Uint8Array> = [ 425 new Uint8Array([0x80]), 426 new Uint8Array([0x80, 0x80]), 427 ]; 428 429 testData.forEach((input) => { 430 expect(() => decodeInt64Varint(input, 0)).toThrow('Index out of range'); 431 }); 432 }); 433}); 434