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