1// Copyright (C) 2020 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 {RawQueryResult} from './protos'; 16import { 17 findColumnIndex, 18 iter, 19 NUM, 20 NUM_NULL, 21 slowlyCountRows, 22 STR, 23 STR_NULL 24} from './query_iterator'; 25 26const COLUMN_TYPE_STR = RawQueryResult.ColumnDesc.Type.STRING; 27const COLUMN_TYPE_DOUBLE = RawQueryResult.ColumnDesc.Type.DOUBLE; 28const COLUMN_TYPE_LONG = RawQueryResult.ColumnDesc.Type.LONG; 29 30test('Columnar iteration slowlyCountRows', () => { 31 const r = new RawQueryResult({ 32 columnDescriptors: [{ 33 name: 'string_column', 34 type: COLUMN_TYPE_STR, 35 }], 36 numRecords: 1, 37 columns: [{ 38 stringValues: ['foo'], 39 isNulls: [false], 40 }], 41 }); 42 43 expect(slowlyCountRows(r)).toBe(1); 44}); 45 46test('Columnar iteration findColumnIndex', () => { 47 const r = new RawQueryResult({ 48 columnDescriptors: [ 49 { 50 name: 'strings', 51 type: COLUMN_TYPE_STR, 52 }, 53 { 54 name: 'doubles', 55 type: COLUMN_TYPE_DOUBLE, 56 }, 57 { 58 name: 'longs', 59 type: COLUMN_TYPE_LONG, 60 }, 61 { 62 name: 'nullable_strings', 63 type: COLUMN_TYPE_STR, 64 }, 65 { 66 name: 'nullable_doubles', 67 type: COLUMN_TYPE_DOUBLE, 68 }, 69 { 70 name: 'nullable_longs', 71 type: COLUMN_TYPE_LONG, 72 }, 73 { 74 name: 'twin', 75 type: COLUMN_TYPE_LONG, 76 }, 77 { 78 name: 'twin', 79 type: COLUMN_TYPE_STR, 80 } 81 ], 82 numRecords: 1, 83 columns: [ 84 { 85 stringValues: ['foo'], 86 isNulls: [false], 87 }, 88 { 89 doubleValues: [1], 90 isNulls: [false], 91 }, 92 { 93 longValues: [1], 94 isNulls: [false], 95 }, 96 { 97 stringValues: [''], 98 isNulls: [true], 99 }, 100 { 101 doubleValues: [0], 102 isNulls: [true], 103 }, 104 { 105 longValues: [0], 106 isNulls: [true], 107 }, 108 { 109 doubleValues: [0], 110 isNulls: [false], 111 }, 112 { 113 stringValues: [''], 114 isNulls: [false], 115 } 116 ], 117 }); 118 119 expect(findColumnIndex(r, 'strings', STR)).toBe(0); 120 expect(findColumnIndex(r, 'doubles', NUM)).toBe(1); 121 expect(findColumnIndex(r, 'longs', NUM)).toBe(2); 122 123 expect(findColumnIndex(r, 'nullable_strings', STR_NULL)).toBe(3); 124 expect(findColumnIndex(r, 'nullable_doubles', NUM_NULL)).toBe(4); 125 expect(findColumnIndex(r, 'nullable_longs', NUM_NULL)).toBe(5); 126 127 expect(() => findColumnIndex(r, 'no such col', NUM)).toThrow(Error); 128 129 // It's allowable to expect nulls but for the whole column to be non-null... 130 expect(findColumnIndex(r, 'strings', STR_NULL)).toBe(0); 131 expect(findColumnIndex(r, 'doubles', NUM_NULL)).toBe(1); 132 expect(findColumnIndex(r, 'longs', NUM_NULL)).toBe(2); 133 134 // ...but if we expect no-nulls there shouldn't be even one: 135 expect(() => findColumnIndex(r, 'nullable_strings', STR)).toThrow(Error); 136 expect(() => findColumnIndex(r, 'nullable_doubles', NUM)).toThrow(Error); 137 expect(() => findColumnIndex(r, 'nullable_longs', NUM)).toThrow(Error); 138 139 // If multiple columns have the desired name we error even if we could 140 // distinguish based on the type: 141 expect(() => findColumnIndex(r, 'twin', NUM)).toThrow(Error); 142 143 expect(() => findColumnIndex(r, 'strings', NUM)).toThrow(Error); 144 expect(() => findColumnIndex(r, 'longs', STR)).toThrow(Error); 145 expect(() => findColumnIndex(r, 'doubles', STR)).toThrow(Error); 146}); 147 148test('Columnar iteration over two rows', () => { 149 const r = new RawQueryResult({ 150 columnDescriptors: [{ 151 name: 'name', 152 type: COLUMN_TYPE_STR, 153 }], 154 numRecords: 2, 155 columns: [{ 156 stringValues: ['Alice', 'Bob'], 157 isNulls: [false, false], 158 }], 159 }); 160 161 const it = iter({'name': STR}, r); 162 163 expect(it.valid()).toBe(true); 164 const name: string = it.row.name; 165 expect(name).toBe('Alice'); 166 it.next(); 167 168 expect(it.valid()).toBe(true); 169 expect(it.row.name).toBe('Bob'); 170 it.next(); 171 172 expect(it.valid()).toBe(false); 173}); 174 175test('Columnar iteration over empty query set', () => { 176 const r = new RawQueryResult({ 177 columnDescriptors: [{ 178 name: 'emptyColumn', 179 type: COLUMN_TYPE_STR, 180 }], 181 numRecords: 0, 182 columns: [{ 183 stringValues: [], 184 isNulls: [], 185 }], 186 }); 187 188 { 189 const it = iter({'emptyColumn': STR}, r); 190 expect(it.valid()).toBe(false); 191 } 192 193 { 194 const it = iter({'emptyColumn': NUM}, r); 195 expect(it.valid()).toBe(false); 196 } 197 198 { 199 const it = iter({'emptyColumn': NUM_NULL}, r); 200 expect(it.valid()).toBe(false); 201 } 202 203 { 204 const it = iter({'emptyColumn': STR_NULL}, r); 205 expect(it.valid()).toBe(false); 206 } 207}); 208 209test('Columnar iteration first row is null', () => { 210 const r = new RawQueryResult({ 211 columnDescriptors: [{ 212 name: 'numbers', 213 type: COLUMN_TYPE_STR, 214 }], 215 numRecords: 2, 216 columns: [{ 217 stringValues: ['[NULL]'], 218 doubleValues: [0], 219 longValues: [0, 1], 220 isNulls: [true, false], 221 }], 222 }); 223 224 const it = iter({'numbers': NUM_NULL}, r); 225 226 expect(it.valid()).toBe(true); 227 expect(it.row.numbers).toBe(null); 228 it.next(); 229 230 expect(it.valid()).toBe(true); 231 expect(it.row.numbers).toBe(1); 232 it.next(); 233 234 expect(it.valid()).toBe(false); 235}); 236