mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-03 20:36:16 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			466 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			466 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* Copyright (c) 2023 MariaDB Corporation
 | 
						|
 | 
						|
   This program is free software; you can redistribute it and/or modify
 | 
						|
   it under the terms of the GNU General Public License as published by
 | 
						|
   the Free Software Foundation; version 2 of the License.
 | 
						|
 | 
						|
   This program is distributed in the hope that it will be useful,
 | 
						|
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
   GNU General Public License for more details.
 | 
						|
 | 
						|
   You should have received a copy of the GNU General Public License
 | 
						|
   along with this program; if not, write to the Free Software Foundation,
 | 
						|
   Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA */
 | 
						|
 | 
						|
/*
 | 
						|
  This program simulates the MariaDB query process execution using
 | 
						|
  the SCAN, EQ_REF,  REF and join_cache (CACHE) row lookup methods.
 | 
						|
 | 
						|
  The purpose is to verify that 'prev_record_reads()' function correctly
 | 
						|
  estimates the number of lookups we have to do for EQ_REF access
 | 
						|
  assuming we have 'one-row-cache' before the lookup.
 | 
						|
 | 
						|
  The logic for the prev_record_reads() function in this file should
 | 
						|
  match the logic in sql_select.cc::prev_record_reads() in MariaDB 11.0
 | 
						|
  and above.
 | 
						|
 | 
						|
  The program generates first a randomized plan with the above
 | 
						|
  methods, then executes a full 'query' processing and then lastly
 | 
						|
  checks that the number of EQ_REF engine lookups matches the
 | 
						|
  estimated number of lookups.
 | 
						|
 | 
						|
  If the number of estimated lookups are not exact, the plan and
 | 
						|
  lookup numbers are printed. That a plan is printed is not to be
 | 
						|
  regarded as a failure. It's a failure only of the number of engine
 | 
						|
  calls are far greater than the number of estimated lookups.
 | 
						|
 | 
						|
  Note that the estimated number of lookups are exact only if CACHE
 | 
						|
  refills == 1 and if the EQ_REF table only depends on one earlier
 | 
						|
  table.
 | 
						|
*/
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <assert.h>
 | 
						|
#include <time.h>
 | 
						|
 | 
						|
#define TABLES 21
 | 
						|
#define DEFAULT_TABLES 10
 | 
						|
#define CACHED_ROWS 10000
 | 
						|
#define unlikely(A) A
 | 
						|
 | 
						|
enum JOIN_TYPE { SCAN, EQ_REF,  REF, CACHE };
 | 
						|
const char *type[]= { "SCAN", "EQ_REF", "REF", "CACHE"};
 | 
						|
 | 
						|
typedef unsigned long long DEPEND;
 | 
						|
typedef unsigned int uint;
 | 
						|
typedef unsigned long long ulonglong;
 | 
						|
 | 
						|
struct TABLE
 | 
						|
{
 | 
						|
  ulonglong data;
 | 
						|
  JOIN_TYPE type;
 | 
						|
  DEPEND map;
 | 
						|
  DEPEND ref_depend_map;
 | 
						|
  uint records_in_table;
 | 
						|
  uint matching_records;
 | 
						|
  uint last_key;
 | 
						|
  ulonglong lookups;
 | 
						|
  ulonglong *cache;                             // join cache
 | 
						|
  ulong cached_records;
 | 
						|
  ulong flushed_caches;
 | 
						|
};
 | 
						|
 | 
						|
struct POSITION
 | 
						|
{
 | 
						|
  TABLE *table;
 | 
						|
  JOIN_TYPE type;
 | 
						|
  double records;
 | 
						|
  double record_count;
 | 
						|
  double records_out;
 | 
						|
  double prev_record_read;
 | 
						|
  double same_keys;
 | 
						|
  ulong refills;
 | 
						|
};
 | 
						|
 | 
						|
uint opt_tables= DEFAULT_TABLES;
 | 
						|
bool verbose=0;
 | 
						|
uint rand_init;
 | 
						|
struct TABLE table[TABLES];
 | 
						|
struct POSITION positions[TABLES];
 | 
						|
 | 
						|
void do_select(uint table_index);
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
prev_record_reads(POSITION *position, uint idx, DEPEND found_ref,
 | 
						|
                  double record_count)
 | 
						|
{
 | 
						|
  double found= 1.0;
 | 
						|
  POSITION *pos_end= position - 1;
 | 
						|
  POSITION *cur_pos= position + idx;
 | 
						|
 | 
						|
  /* Safety against const tables */
 | 
						|
  if (!found_ref)
 | 
						|
    goto end;
 | 
						|
 | 
						|
  for (POSITION *pos= cur_pos-1; pos != pos_end; pos--)
 | 
						|
  {
 | 
						|
    if (found_ref & pos->table->map)
 | 
						|
    {
 | 
						|
      found_ref&= ~pos->table->map;
 | 
						|
 | 
						|
      /* Found dependent table */
 | 
						|
      if (pos->type == EQ_REF)
 | 
						|
      {
 | 
						|
        if (!found_ref)
 | 
						|
          found*= pos->same_keys;
 | 
						|
      }
 | 
						|
      else if (pos->type == CACHE)
 | 
						|
      {
 | 
						|
        if (!found_ref)
 | 
						|
          found*= pos->record_count / pos->refills;
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    if (pos->type != CACHE)
 | 
						|
    {
 | 
						|
      /*
 | 
						|
        We are not depending on the current table
 | 
						|
        There are 'records_out' rows with identical rows
 | 
						|
        value for our depending tables.
 | 
						|
        We are ignoring join_cache as in this case the
 | 
						|
        preceding tables row combination can change for
 | 
						|
        each call.
 | 
						|
      */
 | 
						|
      found*= pos->records_out;
 | 
						|
    }
 | 
						|
    else
 | 
						|
      found/= pos->refills;
 | 
						|
  }
 | 
						|
 | 
						|
end:
 | 
						|
  cur_pos->record_count= record_count;
 | 
						|
  cur_pos->same_keys= found;
 | 
						|
  assert(record_count >= found);
 | 
						|
 | 
						|
  if (unlikely(found <= 1.0))
 | 
						|
    cur_pos->prev_record_read= record_count;
 | 
						|
  else if (unlikely(found > record_count))
 | 
						|
    cur_pos->prev_record_read=1;
 | 
						|
  else
 | 
						|
    cur_pos->prev_record_read= record_count / found;
 | 
						|
  return;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void cleanup()
 | 
						|
{
 | 
						|
  for (uint i= 0; i < opt_tables ; i++)
 | 
						|
  {
 | 
						|
    free(table[i].cache);
 | 
						|
    table[i].cache= 0;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void intialize_tables()
 | 
						|
{
 | 
						|
  int eq_ref_tables;
 | 
						|
 | 
						|
restart:
 | 
						|
  eq_ref_tables= 0;
 | 
						|
  for (uint i= 0; i < opt_tables ; i++)
 | 
						|
  {
 | 
						|
    if (i == 0)
 | 
						|
      table[i].type= SCAN;
 | 
						|
    else
 | 
						|
      table[i].type= (JOIN_TYPE) (rand() % 4);
 | 
						|
    table[i].records_in_table= rand() % 5+3;
 | 
						|
    table[i].matching_records= 2 + rand() % 3;
 | 
						|
    table[i].map= (DEPEND) 1 << i;
 | 
						|
    table[i].ref_depend_map= 0;
 | 
						|
 | 
						|
/* The following is for testing */
 | 
						|
#ifdef FORCE_COMB
 | 
						|
    if (i == 5 || i == 6)
 | 
						|
    {
 | 
						|
      table[i].type= REF;
 | 
						|
      table[i].matching_records= 5;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    if (table[i].type != SCAN)
 | 
						|
    {
 | 
						|
      /* This just to make do_select a bit easier */
 | 
						|
      table[i].ref_depend_map=  ((DEPEND) 1) << (rand() % i);
 | 
						|
      if (rand() & 1)
 | 
						|
      {
 | 
						|
        uint second_depend= rand() % i;
 | 
						|
        if (!(table[i].ref_depend_map & second_depend))
 | 
						|
          table[i].ref_depend_map|= ((DEPEND) 1) << second_depend;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (table[i].type == EQ_REF)
 | 
						|
    {
 | 
						|
      table[i].matching_records= 1;
 | 
						|
      eq_ref_tables++;
 | 
						|
    }
 | 
						|
    else if (table[i].type != REF)
 | 
						|
      table[i].matching_records= table[i].records_in_table;
 | 
						|
 | 
						|
    table[i].last_key= 0;
 | 
						|
    table[i].lookups= 0;
 | 
						|
    table[i].cached_records= 0;
 | 
						|
    table[i].flushed_caches= 0;
 | 
						|
    table[i].cache= 0;
 | 
						|
    if (table[i].type == CACHE)
 | 
						|
      table[i].cache= (ulonglong*) malloc(CACHED_ROWS *
 | 
						|
                                          sizeof(table[i].data) * i);
 | 
						|
  }
 | 
						|
 | 
						|
  /* We must have at least one EQ_REF table */
 | 
						|
  if (!eq_ref_tables)
 | 
						|
  {
 | 
						|
    cleanup();
 | 
						|
    goto restart;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void optimize_tables()
 | 
						|
{
 | 
						|
  double record_count= 1.0, records;
 | 
						|
 | 
						|
  for (uint i= 0; i < opt_tables ; i++)
 | 
						|
  {
 | 
						|
    TABLE *tab= table+i;
 | 
						|
    positions[i].refills= 0;
 | 
						|
 | 
						|
    switch (tab->type) {
 | 
						|
    case SCAN:
 | 
						|
      records= tab->records_in_table;
 | 
						|
      break;
 | 
						|
    case EQ_REF:
 | 
						|
      records= 1.0;
 | 
						|
      prev_record_reads(positions, i, tab->ref_depend_map, record_count);
 | 
						|
      break;
 | 
						|
    case REF:
 | 
						|
      records= tab->matching_records;
 | 
						|
      break;
 | 
						|
    case CACHE:
 | 
						|
      records= tab->records_in_table;
 | 
						|
      positions[i].refills= (record_count + CACHED_ROWS-1)/ CACHED_ROWS;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      assert(0);
 | 
						|
    }
 | 
						|
    positions[i].table= table + i;
 | 
						|
    positions[i].type= table[i].type;
 | 
						|
    positions[i].records= records;
 | 
						|
    positions[i].record_count= record_count;
 | 
						|
    positions[i].records_out= records;
 | 
						|
 | 
						|
    record_count*= records;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
void process_join_cache(TABLE *tab, uint table_index)
 | 
						|
{
 | 
						|
  if (!tab->cached_records)
 | 
						|
    return;
 | 
						|
 | 
						|
#ifdef PRINT_CACHE
 | 
						|
  putc('>', stdout);
 | 
						|
  for (uint k= 0 ; k < table_index ; k++)
 | 
						|
  {
 | 
						|
    printf("%8lld ", tab->cache[k]);
 | 
						|
  }
 | 
						|
  putc('\n',stdout);
 | 
						|
  putc('<', stdout);
 | 
						|
  for (uint k= 0 ; k < table_index ; k++)
 | 
						|
  {
 | 
						|
    printf("%8lld ", tab->cache[k+(tab->cached_records-1)*table_index]);
 | 
						|
  }
 | 
						|
  putc('\n',stdout);
 | 
						|
#endif
 | 
						|
 | 
						|
  for (uint k= 0 ; k < tab->records_in_table; k++)
 | 
						|
  {
 | 
						|
    table[table_index].data= k+1;
 | 
						|
    ulonglong *cache= tab->cache;
 | 
						|
    for (uint i= 0 ; i < tab->cached_records ; i++)
 | 
						|
    {
 | 
						|
      for (uint j= 0 ; j < table_index ; j++)
 | 
						|
        table[j].data= *cache++;
 | 
						|
      do_select(table_index+1);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  tab->flushed_caches++;
 | 
						|
  tab->cached_records= 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
  Calculate a key depending on multiple tables
 | 
						|
*/
 | 
						|
 | 
						|
ulonglong calc_ref_key(DEPEND depend_map)
 | 
						|
{
 | 
						|
  ulonglong value= 1;
 | 
						|
  TABLE *t= table;
 | 
						|
 | 
						|
  do
 | 
						|
  {
 | 
						|
    if (t->map & depend_map)
 | 
						|
    {
 | 
						|
      depend_map&= ~t->map;
 | 
						|
      value*= t->data;
 | 
						|
    }
 | 
						|
    t++;
 | 
						|
  } while (depend_map);
 | 
						|
  return value;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void do_select(uint table_index)
 | 
						|
{
 | 
						|
  if (table_index == opt_tables)
 | 
						|
    return;
 | 
						|
 | 
						|
  TABLE *tab= table + table_index;
 | 
						|
  switch (tab->type) {
 | 
						|
  case SCAN:
 | 
						|
    for (uint i= 1 ; i <= tab->records_in_table ; i++)
 | 
						|
    {
 | 
						|
      tab->data= i;
 | 
						|
      do_select(table_index+1);
 | 
						|
    }
 | 
						|
    break;
 | 
						|
  case REF:
 | 
						|
  {
 | 
						|
    ulonglong ref_key= calc_ref_key(tab->ref_depend_map);
 | 
						|
    for (uint i=1 ; i <= tab->matching_records ; i++)
 | 
						|
    {
 | 
						|
      tab->data= ref_key * tab->matching_records + i;
 | 
						|
      do_select(table_index+1);
 | 
						|
    }
 | 
						|
    break;
 | 
						|
  }
 | 
						|
  case EQ_REF:
 | 
						|
  {
 | 
						|
    ulonglong ref_key= calc_ref_key(tab->ref_depend_map);
 | 
						|
    if (ref_key != tab->last_key)
 | 
						|
    {
 | 
						|
      tab->lookups++;
 | 
						|
#ifdef PRINT_EQ_KEY
 | 
						|
      if (table_index == 9)
 | 
						|
        printf("ref_key: %lld\n", ref_key);
 | 
						|
#endif
 | 
						|
      tab->last_key= ref_key;
 | 
						|
      tab->data= ref_key * tab->matching_records;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
      assert(tab->lookups != 0);
 | 
						|
    }
 | 
						|
    do_select(table_index+1);
 | 
						|
    break;
 | 
						|
  }
 | 
						|
  case CACHE:
 | 
						|
  {
 | 
						|
    ulonglong *cache= tab->cache + tab->cached_records * table_index;
 | 
						|
    for (uint i= 0 ; i <= table_index ; i++)
 | 
						|
      *cache++ = table[i].data;
 | 
						|
    if (++tab->cached_records == CACHED_ROWS)
 | 
						|
      process_join_cache(tab, table_index);
 | 
						|
    break;
 | 
						|
  }
 | 
						|
  default:
 | 
						|
    break;
 | 
						|
  }
 | 
						|
  return;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void do_select_end(uint table_index)
 | 
						|
{
 | 
						|
  if (table_index == opt_tables)
 | 
						|
    return;
 | 
						|
 | 
						|
  TABLE *tab= table + table_index;
 | 
						|
  switch (tab->type) {
 | 
						|
  case CACHE:
 | 
						|
    process_join_cache(tab, table_index);
 | 
						|
    break;
 | 
						|
  default:
 | 
						|
    break;
 | 
						|
  }
 | 
						|
  do_select_end(table_index+1);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void execute()
 | 
						|
{
 | 
						|
  do_select(0);
 | 
						|
  do_select_end(0);
 | 
						|
}
 | 
						|
 | 
						|
int check_prev_records()
 | 
						|
{
 | 
						|
  int errors= 0;
 | 
						|
  for (uint i= 0; i < opt_tables ; i++)
 | 
						|
  {
 | 
						|
    TABLE *tab= table + i;
 | 
						|
    if (tab->type == EQ_REF)
 | 
						|
    {
 | 
						|
      if (positions[i].prev_record_read != (double) tab->lookups)
 | 
						|
      {
 | 
						|
        fprintf(stdout, "table: %d  lookups: %lld  prev_record_read: %g\n",
 | 
						|
                i, tab->lookups, positions[i].prev_record_read);
 | 
						|
        errors++;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (errors || verbose)
 | 
						|
  {
 | 
						|
    fprintf(stdout, "tables:     %u\n", opt_tables);
 | 
						|
    fprintf(stdout, "rand_init:  %u\n", rand_init);
 | 
						|
    fprintf(stdout, "cache_size: %u\n", (uint) CACHED_ROWS);
 | 
						|
    for (uint i= 0; i < opt_tables ; i++)
 | 
						|
    {
 | 
						|
      TABLE *tab= table + i;
 | 
						|
      fprintf(stdout, "table: %2d (%3lx)  type: %-6s  comb: %3lg  out: %2lg  lookups: %lld  prev: %lg  depend: %llx\n",
 | 
						|
              i, (uint) 1 << i, type[tab->type], positions[i].record_count,
 | 
						|
              positions[i].records_out, tab->lookups,
 | 
						|
              positions[i].prev_record_read, tab->ref_depend_map);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return errors;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
int main(int argc, char **argv)
 | 
						|
{
 | 
						|
  if (argc > 1)
 | 
						|
  {
 | 
						|
    opt_tables=atoi(argv[1]);
 | 
						|
    if (opt_tables <= 3)
 | 
						|
      opt_tables= 3;
 | 
						|
    if (opt_tables > TABLES)
 | 
						|
      opt_tables= TABLES;
 | 
						|
  }
 | 
						|
  if (argc > 2)
 | 
						|
    rand_init= atoi(argv[2]);
 | 
						|
  else
 | 
						|
    rand_init= (uint) time(0);
 | 
						|
  srand(rand_init);
 | 
						|
 | 
						|
  intialize_tables();
 | 
						|
  optimize_tables();
 | 
						|
  execute();
 | 
						|
  cleanup();
 | 
						|
  exit(check_prev_records() > 0);
 | 
						|
}
 |