Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
hash-table-test.cc
Go to the documentation of this file.
1 // Copyright 2012 Cloudera Inc.
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 
15 #include <stdlib.h>
16 #include <stdio.h>
17 #include <iostream>
18 #include <vector>
19 
20 #include <gtest/gtest.h>
21 
22 #include "common/compiler-util.h"
23 #include "exec/hash-table.inline.h"
24 #include "exprs/expr.h"
25 #include "exprs/expr-context.h"
26 #include "exprs/slot-ref.h"
27 #include "runtime/mem-pool.h"
28 #include "runtime/mem-tracker.h"
29 #include "runtime/string-value.h"
30 #include "runtime/mem-tracker.h"
31 #include "util/cpu-info.h"
32 #include "util/runtime-profile.h"
33 
34 #include "common/names.h"
35 
36 namespace impala {
37 
38 class HashTableTest : public testing::Test {
39  public:
41 
42  protected:
46  vector<ExprContext*> build_expr_ctxs_;
47  vector<ExprContext*> probe_expr_ctxs_;
48 
49  virtual void SetUp() {
50  RowDescriptor desc;
51  Status status;
52 
53  // Not very easy to test complex tuple layouts so this test will use the
54  // simplest. The purpose of these tests is to exercise the hash map
55  // internals so a simple build/probe expr is fine.
56  Expr* expr = pool_.Add(new SlotRef(TYPE_INT, 0));
57  build_expr_ctxs_.push_back(pool_.Add(new ExprContext(expr)));
58  status = Expr::Prepare(build_expr_ctxs_, NULL, desc, &tracker_);
59  EXPECT_TRUE(status.ok());
60  status = Expr::Open(build_expr_ctxs_, NULL);
61  EXPECT_TRUE(status.ok());
62 
63  expr = pool_.Add(new SlotRef(TYPE_INT, 0));
64  probe_expr_ctxs_.push_back(pool_.Add(new ExprContext(expr)));
65  status = Expr::Prepare(probe_expr_ctxs_, NULL, desc, &tracker_);
66  EXPECT_TRUE(status.ok());
67  status = Expr::Open(probe_expr_ctxs_, NULL);
68  EXPECT_TRUE(status.ok());
69  }
70 
71  virtual void TearDown() {
74  }
75 
76  TupleRow* CreateTupleRow(int32_t val) {
77  uint8_t* tuple_row_mem = mem_pool_.Allocate(sizeof(int32_t*));
78  Tuple* tuple_mem = Tuple::Create(sizeof(int32_t), &mem_pool_);
79  *reinterpret_cast<int32_t*>(tuple_mem) = val;
80  TupleRow* row = reinterpret_cast<TupleRow*>(tuple_row_mem);
81  row->SetTuple(0, tuple_mem);
82  return row;
83  }
84 
85  // Wrapper to call private methods on HashTable
86  // TODO: understand google testing, there must be a more natural way to do this
87  void ResizeTable(HashTable* table, int64_t new_size, HashTableCtx* ht_ctx) {
88  table->ResizeBuckets(new_size, ht_ctx);
89  }
90 
91  // Do a full table scan on table. All values should be between [min,max). If
92  // all_unique, then each key(int value) should only appear once. Results are
93  // stored in results, indexed by the key. Results must have been preallocated to
94  // be at least max size.
95  void FullScan(HashTable* table, HashTableCtx* ht_ctx, int min, int max,
96  bool all_unique, TupleRow** results, TupleRow** expected) {
97  HashTable::Iterator iter = table->Begin(ht_ctx);
98  while (!iter.AtEnd()) {
99  TupleRow* row = iter.GetRow();
100  int32_t val = *reinterpret_cast<int32_t*>(build_expr_ctxs_[0]->GetValue(row));
101  EXPECT_GE(val, min);
102  EXPECT_LT(val, max);
103  if (all_unique) EXPECT_TRUE(results[val] == NULL);
104  EXPECT_EQ(row->GetTuple(0), expected[val]->GetTuple(0));
105  results[val] = row;
106  iter.Next();
107  }
108  }
109 
110  // Validate that probe_row evaluates overs probe_exprs is equal to build_row
111  // evaluated over build_exprs
112  void ValidateMatch(TupleRow* probe_row, TupleRow* build_row) {
113  EXPECT_TRUE(probe_row != build_row);
114  int32_t build_val =
115  *reinterpret_cast<int32_t*>(build_expr_ctxs_[0]->GetValue(probe_row));
116  int32_t probe_val =
117  *reinterpret_cast<int32_t*>(probe_expr_ctxs_[0]->GetValue(build_row));
118  EXPECT_EQ(build_val, probe_val);
119  }
120 
121  struct ProbeTestData {
123  vector<TupleRow*> expected_build_rows;
124  };
125 
126  void ProbeTest(HashTable* table, HashTableCtx* ht_ctx,
127  ProbeTestData* data, int num_data, bool scan) {
128  uint32_t hash = 0;
129  for (int i = 0; i < num_data; ++i) {
130  TupleRow* row = data[i].probe_row;
131 
132  HashTable::Iterator iter;
133  if (ht_ctx->EvalAndHashProbe(row, &hash)) continue;
134  iter = table->Find(ht_ctx, hash);
135 
136  if (data[i].expected_build_rows.size() == 0) {
137  EXPECT_TRUE(iter.AtEnd());
138  } else {
139  if (scan) {
140  map<TupleRow*, bool> matched;
141  while (!iter.AtEnd()) {
142  EXPECT_EQ(matched.find(iter.GetRow()), matched.end());
143  matched[iter.GetRow()] = true;
144  iter.Next();
145  }
146  EXPECT_EQ(matched.size(), data[i].expected_build_rows.size());
147  for (int j = 0; i < data[j].expected_build_rows.size(); ++j) {
148  EXPECT_TRUE(matched[data[i].expected_build_rows[j]]);
149  }
150  } else {
151  EXPECT_EQ(data[i].expected_build_rows.size(), 1);
152  EXPECT_EQ(data[i].expected_build_rows[0]->GetTuple(0),
153  iter.GetRow()->GetTuple(0));
154  ValidateMatch(row, iter.GetRow());
155  }
156  }
157  }
158  }
159 
160  // Constructs and closes a hash table.
161  void SetupTest(bool quadratic, int table_size) {
162  TupleRow* build_row1 = CreateTupleRow(1);
163  TupleRow* build_row2 = CreateTupleRow(2);
164  TupleRow* probe_row3 = CreateTupleRow(3);
165  TupleRow* probe_row4 = CreateTupleRow(4);
166 
167  int32_t* val_row1 =
168  reinterpret_cast<int32_t*>(build_expr_ctxs_[0]->GetValue(build_row1));
169  EXPECT_EQ(*val_row1, 1);
170  int32_t* val_row2 =
171  reinterpret_cast<int32_t*>(build_expr_ctxs_[0]->GetValue(build_row2));
172  EXPECT_EQ(*val_row2, 2);
173  int32_t* val_row3 =
174  reinterpret_cast<int32_t*>(probe_expr_ctxs_[0]->GetValue(probe_row3));
175  EXPECT_EQ(*val_row3, 3);
176  int32_t* val_row4 =
177  reinterpret_cast<int32_t*>(probe_expr_ctxs_[0]->GetValue(probe_row4));
178  EXPECT_EQ(*val_row4, 4);
179 
180  // Create and close the hash table.
181  HashTable hash_table(&mem_pool_, quadratic, table_size);
182  hash_table.Close();
183  mem_pool_.FreeAll();
184  }
185 
186  // This test inserts the build rows [0->5) to hash table. It validates that they
187  // are all there using a full table scan. It also validates that Find() is correct
188  // testing for probe rows that are both there and not.
189  // The hash table is resized a few times and the scans/finds are tested again.
190  void BasicTest(bool quadratic, int table_size) {
191  TupleRow* build_rows[5];
192  TupleRow* scan_rows[5] = {0};
193  for (int i = 0; i < 5; ++i) build_rows[i] = CreateTupleRow(i);
194 
195  ProbeTestData probe_rows[10];
196  for (int i = 0; i < 10; ++i) {
197  probe_rows[i].probe_row = CreateTupleRow(i);
198  if (i < 5) probe_rows[i].expected_build_rows.push_back(build_rows[i]);
199  }
200 
201  // Create the hash table and insert the build rows
202  HashTable hash_table(&mem_pool_, quadratic, table_size);
203  HashTableCtx ht_ctx(build_expr_ctxs_, probe_expr_ctxs_, false, false, 1, 0, 1);
204 
205  uint32_t hash = 0;
206  bool success = hash_table.CheckAndResize(5, &ht_ctx);
207  EXPECT_TRUE(success);
208  for (int i = 0; i < 5; ++i) {
209  if (!ht_ctx.EvalAndHashBuild(build_rows[i], &hash)) continue;
210  bool inserted = hash_table.Insert(&ht_ctx, build_rows[i]->GetTuple(0), hash);
211  EXPECT_TRUE(inserted);
212  }
213  EXPECT_EQ(hash_table.size(), 5);
214 
215  // Do a full table scan and validate returned pointers
216  FullScan(&hash_table, &ht_ctx, 0, 5, true, scan_rows, build_rows);
217  ProbeTest(&hash_table, &ht_ctx, probe_rows, 10, false);
218 
219  // Double the size of the hash table and scan again.
220  ResizeTable(&hash_table, 2048, &ht_ctx);
221  EXPECT_EQ(hash_table.num_buckets(), 2048);
222  EXPECT_EQ(hash_table.size(), 5);
223  memset(scan_rows, 0, sizeof(scan_rows));
224  FullScan(&hash_table, &ht_ctx, 0, 5, true, scan_rows, build_rows);
225  ProbeTest(&hash_table, &ht_ctx, probe_rows, 10, false);
226 
227  // Try to shrink and scan again.
228  ResizeTable(&hash_table, 64, &ht_ctx);
229  EXPECT_EQ(hash_table.num_buckets(), 64);
230  EXPECT_EQ(hash_table.size(), 5);
231  memset(scan_rows, 0, sizeof(scan_rows));
232  FullScan(&hash_table, &ht_ctx, 0, 5, true, scan_rows, build_rows);
233  ProbeTest(&hash_table, &ht_ctx, probe_rows, 10, false);
234 
235  // Resize to 8, which is the smallest value to fit the number of filled buckets.
236  ResizeTable(&hash_table, 8, &ht_ctx);
237  EXPECT_EQ(hash_table.num_buckets(), 8);
238  EXPECT_EQ(hash_table.size(), 5);
239  memset(scan_rows, 0, sizeof(scan_rows));
240  FullScan(&hash_table, &ht_ctx, 0, 5, true, scan_rows, build_rows);
241  ProbeTest(&hash_table, &ht_ctx, probe_rows, 10, false);
242 
243  hash_table.Close();
244  mem_pool_.FreeAll();
245  }
246 
247  void ScanTest(bool quadratic, int initial_size, int rows_to_insert,
248  int additional_rows) {
249  int total_rows = rows_to_insert + additional_rows;
250  int target_size = BitUtil::NextPowerOfTwo(initial_size);
251  HashTable hash_table(&mem_pool_, quadratic, target_size);
252  HashTableCtx ht_ctx(build_expr_ctxs_, probe_expr_ctxs_, false, false, 1, 0, 1);
253 
254  // Add 1 row with val 1, 2 with val 2, etc
255  vector<TupleRow*> build_rows;
256  ProbeTestData* probe_rows = new ProbeTestData[total_rows];
257  probe_rows[0].probe_row = CreateTupleRow(0);
258  uint32_t hash = 0;
259  for (int val = 1; val <= rows_to_insert; ++val) {
260  bool success = hash_table.CheckAndResize(val, &ht_ctx);
261  EXPECT_TRUE(success) << " failed to resize: " << val;
262  probe_rows[val].probe_row = CreateTupleRow(val);
263  for (int i = 0; i < val; ++i) {
264  TupleRow* row = CreateTupleRow(val);
265  if (!ht_ctx.EvalAndHashBuild(row, &hash)) continue;
266  hash_table.Insert(&ht_ctx, row->GetTuple(0), hash);
267  build_rows.push_back(row);
268  probe_rows[val].expected_build_rows.push_back(row);
269  }
270  }
271 
272  // Add some more probe rows that aren't there.
273  for (int val = rows_to_insert; val < rows_to_insert + additional_rows; ++val) {
274  probe_rows[val].probe_row = CreateTupleRow(val);
275  }
276 
277  // Test that all the builds were found.
278  ProbeTest(&hash_table, &ht_ctx, probe_rows, total_rows, true);
279 
280  // Resize and try again.
281  target_size = BitUtil::NextPowerOfTwo(2 * total_rows);
282  ResizeTable(&hash_table, target_size, &ht_ctx);
283  EXPECT_EQ(hash_table.num_buckets(), target_size);
284  ProbeTest(&hash_table, &ht_ctx, probe_rows, total_rows, true);
285 
286  target_size = BitUtil::NextPowerOfTwo(total_rows + 1);
287  ResizeTable(&hash_table, target_size, &ht_ctx);
288  EXPECT_EQ(hash_table.num_buckets(), target_size);
289  ProbeTest(&hash_table, &ht_ctx, probe_rows, total_rows, true);
290 
291  delete [] probe_rows;
292  hash_table.Close();
293  mem_pool_.FreeAll();
294  }
295 
296  // This test continues adding tuples to the hash table and exercises the resize code
297  // paths.
298  void GrowTableTest(bool quadratic) {
299  uint64_t num_to_add = 4;
300  int expected_size = 0;
301 
302  // Allocate a new pool to test OOM.
303  MemTracker tracker(100 * 1024 * 1024);
304  MemPool pool(&tracker);
305  HashTable hash_table(&pool, quadratic, num_to_add);
306  HashTableCtx ht_ctx(build_expr_ctxs_, probe_expr_ctxs_, false, false, 1, 0, 1);
307 
308  // Inserts num_to_add + (num_to_add^2) + (num_to_add^4) + ... + (num_to_add^20)
309  // entries. When num_to_add == 4, then the total number of inserts is 4194300.
310  int build_row_val = 0;
311  uint32_t hash = 0;
312  for (int i = 0; i < 20; ++i) {
313  // Currently the mem used for the bucket is not being tracked by the mem tracker.
314  // Thus the resize is expected to be successful.
315  // TODO: Keep track of the mem used for the buckets and test cases where we
316  // actually hit OOM.
317  bool success = hash_table.CheckAndResize(num_to_add, &ht_ctx);
318  EXPECT_TRUE(success) << " failed to resize: " << num_to_add;
319  for (int j = 0; j < num_to_add; ++build_row_val, ++j) {
320  TupleRow* row = CreateTupleRow(build_row_val);
321  if (!ht_ctx.EvalAndHashBuild(row, &hash)) continue;
322  bool inserted = hash_table.Insert(&ht_ctx, row->GetTuple(0), hash);
323  if (!inserted) goto done_inserting;
324  }
325  expected_size += num_to_add;
326  num_to_add *= 2;
327  }
328  done_inserting:
329  EXPECT_FALSE(tracker.LimitExceeded());
330  EXPECT_EQ(hash_table.size(), 4194300);
331  // Validate that we can find the entries before we went over the limit
332  for (int i = 0; i < expected_size * 5; i += 100000) {
333  TupleRow* probe_row = CreateTupleRow(i);
334  if (!ht_ctx.EvalAndHashProbe(probe_row, &hash)) continue;
335  HashTable::Iterator iter = hash_table.Find(&ht_ctx, hash);
336  if (i < hash_table.size()) {
337  EXPECT_TRUE(!iter.AtEnd()) << " i: " << i;
338  ValidateMatch(probe_row, iter.GetRow());
339  } else {
340  EXPECT_TRUE(iter.AtEnd()) << " i: " << i;
341  }
342  }
343  hash_table.Close();
344  pool.FreeAll();
345  mem_pool_.FreeAll();
346  }
347 
348  // This test inserts and probes as many elements as the size of the hash table without
349  // calling resize. All the inserts and probes are expected to succeed, because there is
350  // enough space in the hash table (it is also expected to be slow). It also expects that
351  // a probe for a N+1 element will return BUCKET_NOT_FOUND.
352  void InsertFullTest(bool quadratic, int table_size) {
353  // Allocate a new pool to test OOM.
354  MemTracker tracker(100 * 1024 * 1024);
355  MemPool pool(&tracker);
356  HashTable hash_table(&pool, quadratic, table_size);
357  HashTableCtx ht_ctx(build_expr_ctxs_, probe_expr_ctxs_, false, false, 1, 0, 1);
358  EXPECT_EQ(hash_table.EmptyBuckets(), table_size);
359 
360  // Insert and probe table_size different tuples. All of them are expected to be
361  // successfully inserted and probed.
362  uint32_t hash = 0;
363  HashTable::Iterator iter;
364  for (int build_row_val = 0; build_row_val < table_size; ++build_row_val) {
365  TupleRow* row = CreateTupleRow(build_row_val);
366  bool passes = ht_ctx.EvalAndHashBuild(row, &hash);
367  EXPECT_TRUE(passes);
368  bool inserted = hash_table.Insert(&ht_ctx, row->GetTuple(0), hash);
369  EXPECT_TRUE(inserted);
370  EXPECT_EQ(hash_table.EmptyBuckets(), table_size - build_row_val - 1);
371 
372  passes = ht_ctx.EvalAndHashProbe(row, &hash);
373  EXPECT_TRUE(passes);
374  iter = hash_table.Find(&ht_ctx, hash);
375  EXPECT_FALSE(iter.AtEnd());
376  }
377 
378  // Probe for a tuple that does not exist. This should exercise the probe of a full
379  // hash table code path.
380  EXPECT_EQ(hash_table.EmptyBuckets(), 0);
381  TupleRow* probe_row = CreateTupleRow(table_size);
382  bool passes = ht_ctx.EvalAndHashProbe(probe_row, &hash);
383  EXPECT_TRUE(passes);
384  iter = hash_table.Find(&ht_ctx, hash);
385  EXPECT_TRUE(iter.AtEnd());
386  hash_table.Close();
387  pool.FreeAll();
388  mem_pool_.FreeAll();
389  }
390 };
391 
392 TEST_F(HashTableTest, LinearSetupTest) {
393  SetupTest(false, 1);
394  SetupTest(false, 1024);
395  SetupTest(false, 65536);
396 }
397 
398 TEST_F(HashTableTest, QuadraticSetupTest) {
399  SetupTest(true, 1);
400  SetupTest(true, 1024);
401  SetupTest(true, 65536);
402 }
403 
404 TEST_F(HashTableTest, LinearBasicTest) {
405  BasicTest(false, 1);
406  BasicTest(false, 1024);
407  BasicTest(false, 65536);
408 }
409 
410 TEST_F(HashTableTest, QuadraticBasicTest) {
411  BasicTest(true, 1);
412  BasicTest(true, 1024);
413  BasicTest(true, 65536);
414 }
415 
416 // This test makes sure we can scan ranges of buckets.
417 TEST_F(HashTableTest, LinearScanTest) {
418  ScanTest(false, 1, 10, 5);
419  ScanTest(false, 1024, 1000, 5);
420  ScanTest(false, 1024, 1000, 500);
421 }
422 
423 TEST_F(HashTableTest, QuadraticScanTest) {
424  ScanTest(true, 1, 10, 5);
425  ScanTest(true, 1024, 1000, 5);
426  ScanTest(true, 1024, 1000, 500);
427 }
428 
429 TEST_F(HashTableTest, LinearGrowTableTest) {
430  GrowTableTest(false);
431 }
432 
433 TEST_F(HashTableTest, QuadraticGrowTableTest) {
434  GrowTableTest(true);
435 }
436 
437 TEST_F(HashTableTest, LinearInsertFullTest) {
438  InsertFullTest(false, 1);
439  InsertFullTest(false, 4);
440  InsertFullTest(false, 64);
441  InsertFullTest(false, 1024);
442  InsertFullTest(false, 65536);
443 }
444 
445 TEST_F(HashTableTest, QuadraticInsertFullTest) {
446  InsertFullTest(true, 1);
447  InsertFullTest(true, 4);
448  InsertFullTest(true, 64);
449  InsertFullTest(true, 1024);
450  InsertFullTest(true, 65536);
451 }
452 
453 }
454 
455 int main(int argc, char** argv) {
456  ::testing::InitGoogleTest(&argc, argv);
458  return RUN_ALL_TESTS();
459 }
vector< ExprContext * > build_expr_ctxs_
stl-like iterator interface.
Definition: hash-table.h:450
void SetupTest(bool quadratic, int table_size)
bool AtEnd() const
Returns true if this iterator is at the end, i.e. GetRow() cannot be called.
Definition: hash-table.h:492
void ValidateMatch(TupleRow *probe_row, TupleRow *build_row)
TEST_F(InstructionCounterTest, Count)
Tuple * GetTuple(int tuple_idx)
Definition: tuple-row.h:30
void BasicTest(bool quadratic, int table_size)
MemTracker tracker
void InsertFullTest(bool quadratic, int table_size)
void FullScan(HashTable *table, HashTableCtx *ht_ctx, int min, int max, bool all_unique, TupleRow **results, TupleRow **expected)
static Status Open(const std::vector< ExprContext * > &ctxs, RuntimeState *state)
Convenience function for opening multiple expr trees.
void ScanTest(bool quadratic, int initial_size, int rows_to_insert, int additional_rows)
A tuple with 0 materialised slots is represented as NULL.
Definition: tuple.h:48
const StringSearch UrlParser::hash_search & hash
Definition: url-parser.cc:41
Iterator Begin(HashTableCtx *ht_ctx)
bool CheckAndResize(uint64_t buckets_to_fill, HashTableCtx *ht_ctx)
Definition: hash-table.cc:282
int64_t EmptyBuckets() const
Returns the number of empty buckets.
Definition: hash-table.h:385
static Tuple * Create(int size, MemPool *pool)
initialize individual tuple with data residing in mem pool
Definition: tuple.h:51
void ProbeTest(HashTable *table, HashTableCtx *ht_ctx, ProbeTestData *data, int num_data, bool scan)
static void Close(const std::vector< ExprContext * > &ctxs, RuntimeState *state)
Convenience function for closing multiple expr trees.
virtual void TearDown()
void FreeAll()
Definition: mem-pool.cc:73
ObjectPool pool
void GrowTableTest(bool quadratic)
vector< ExprContext * > probe_expr_ctxs_
void IR_ALWAYS_INLINE Next()
Iterates to the next element. It should be called only if !AtEnd().
This is the superclass of all expr evaluation nodes.
Definition: expr.h:116
bool ResizeBuckets(int64_t num_buckets, HashTableCtx *ht_ctx)
Resize the hash table to 'num_buckets'. Returns false on OOM.
Definition: hash-table.cc:293
This class is thread-safe.
Definition: mem-tracker.h:61
uint64_t Test(T *ht, const ProbeTuple *input, uint64_t num_tuples)
void SetTuple(int tuple_idx, Tuple *tuple)
Definition: tuple-row.h:34
static int64_t NextPowerOfTwo(int64_t v)
Definition: bit-util.h:50
int64_t num_buckets() const
Returns the number of buckets.
Definition: hash-table.h:388
int main(int argc, char **argv)
TupleRow * CreateTupleRow(int32_t val)
Reference to a single slot of a tuple.
Definition: slot-ref.h:23
Iterator IR_ALWAYS_INLINE Find(HashTableCtx *ht_ctx, uint32_t hash)
static void Init()
Initialize CpuInfo.
Definition: cpu-info.cc:75
static Status Prepare(const std::vector< ExprContext * > &ctxs, RuntimeState *state, const RowDescriptor &row_desc, MemTracker *tracker)
void ResizeTable(HashTable *table, int64_t new_size, HashTableCtx *ht_ctx)
void Close()
Call to cleanup any resources. Must be called once.
Definition: hash-table.cc:257
bool IR_ALWAYS_INLINE EvalAndHashBuild(TupleRow *row, uint32_t *hash)
bool ok() const
Definition: status.h:172
bool IR_ALWAYS_INLINE Insert(HashTableCtx *ht_ctx, const BufferedTupleStream::RowIdx &idx, TupleRow *row, uint32_t hash)
int64_t size() const
Returns number of elements inserted in the hash table.
Definition: hash-table.h:380
uint8_t * Allocate(int size)
Definition: mem-pool.h:92
bool IR_ALWAYS_INLINE EvalAndHashProbe(TupleRow *row, uint32_t *hash)