• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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