#!/bin/sh
#
# Copyright 2005 MySQL AB
# 
# 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; either version 2 of the License, or
# (at your option) any later version.
# 
# 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

echo "######################################################################"
echo "#"
echo "# Test Bug#10178 - failure to find a row in heap table by concurrent UPDATEs"
echo "#"

#######################################################################
#
# Settings. CAUTION: Paths are not space safe.
#

# BASEDIR="install directory"
# DATADIR="databases directory e.g. $BASEDIR/var"

MYSQLD="$BASEDIR/libexec/mysqld"
MYSQLC="$BASEDIR/bin/mysql"
MYSQLA="$BASEDIR/bin/mysqladmin"
MYSQLT="$BASEDIR/bin/mysqltest"

PORT_1="${MYSQL_TCP_PORT:+--port=$MYSQL_TCP_PORT}"
SOCK_1="${MYSQL_UNIX_PORT:+--socket=$MYSQL_UNIX_PORT}"
USER_1="-u root -D test"
CLNT_1="-v"
DATA_1="--basedir=$BASEDIR --datadir=$DATADIR"
SERV_1="--log-error --core"
DBUG_1="--debug=t:d:i:O,$DATADIR/mysqld.trace"
DBUG_1= #"--debug=d,thrlock:i:O,$DATADIR/mysqld.trace"

cd $DATADIR || exit $?
rm -f *.trace *.err core* test/#sql* bug10178*

echo "######################################################################"
echo "#"
echo "# Starting database server."
"$MYSQLD" $PORT_1 $SOCK_1 $DATA_1 $SERV_1 $DBUG_1 &
SERV_1_PID=$!
echo "Process_id $SERV_1_PID"
echo
sleep 5


echo "######################################################################"
echo "#"
echo "# Running test."
cat <<EOF > bug10178-1.c
/*
  bug10178-1.c

  Copyright 2005 probably by Andrei Elkin,
  see bug report #10178 on bugs.mysql.com.
  (Slightly) modified by Ingo Struewing, MySQL AB

  Bug#10178 - failure to find a row in heap table by concurrent UPDATEs
*/

#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <string.h>
#include <mysql.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <assert.h>
#include <signal.h>

#define TRUE 1
#define FALSE 0

#define N_DIM 3

#define CMD_LEN 4096
#define MAX_TABLES 256

const double rand_denom=1.0/((double)RAND_MAX + 1.0);
#define drand() ((double)rand()*rand_denom)

#define error_mysql(m) fprintf (stderr, "MySQL server: %s\n", mysql_error (m));

volatile int stop = 0;

volatile int sigint = 0;
void catch_int (int sig)
{
  sigint = sig;
  signal (SIGINT, catch_int);
  signal (SIGTERM, catch_int);
}

int n_tuples = 0;
struct
{
  char db_host[64];
  int  db_port;
  char db_name[64];
  char db_user[64];
  char db_pswd[64];
  int  n_users;
  int  n_tables;
  int  n_rows;
  int  modulus;
  int  query_min;
  int  query_max;
  int  create;
  int  index;
}
config =
{
  .db_host  = "localhost.localdomain",
  .db_port  = 3304,
  .db_name  = "comm",
  .db_user  = "test",
  .db_pswd  = "testpass",
  .n_users  = 16,
  .n_tables = 40,
  .n_rows   = 2,
  .modulus   = 32771,
  .query_min = 8,
  .query_max = 100,
  .create = 1,
  .index = 1
};

struct my_thread
{
  pthread_t thread;
  int id;
  int prim_key;
  int row[N_DIM];
  char cmd[CMD_LEN];
  int  cmd_len;
  MYSQL *mysql;
  MYSQL_RES *mysql_result;
  FILE * log_file;
} *my_threads = NULL;

struct my_table
{
  int prim_key;
  int row[N_DIM];
  int rows;
  char *name;
} *my_tables = NULL;

MYSQL *connect_mysql (MYSQL *mysql, char *db_name)
{
  mysql = mysql_init (mysql);
  if (!mysql) return NULL;
  
  if (!mysql_real_connect (mysql,
                           config.db_host,
                           config.db_user,
                           config.db_pswd,
                           db_name,
                           config.db_port,
                           NULL, 0))
  {
    fprintf (stderr, "MySQL server: %s\n", mysql_error (mysql));
    mysql_close (mysql);
    return NULL;
  }
  return mysql;
}

static inline int get_table (void)
{
  return (config.n_tables * drand());
}

static int run_update (struct my_thread *thread)
{
  int key, table;
	
  key = (n_tuples) * drand() + 1;
  table = get_table ();
  thread->cmd_len = 
    snprintf(thread->cmd, CMD_LEN,
             "UPDATE %s SET "
             "count = count + 1 "
             "WHERE col_key = %d ",
             my_tables[table].name, key);
	
  if (mysql_query (thread->mysql, thread->cmd))
  {
    error (0, errno, "Error in query: %s\n: %s",
           thread->cmd, mysql_error(thread->mysql));
    return -1;
  }
  else
  {
    thread->mysql_result = mysql_store_result (thread->mysql);

    if (mysql_affected_rows(thread->mysql) == 0)
    {
      error (0, errno, "NO ROWS ARE AFFECTED, WHY? query: %s\n: %s",
             thread->cmd, mysql_error(thread->mysql));
      stop = 1;
    };

    mysql_free_result(thread->mysql_result);
  }
  
  return 0;
}

void *user_thread (void *thr)
{
  int n_ops = 0;
  int err = 0;
  struct timeval connect;
  struct my_thread *thread = (struct my_thread *) thr;
  
  if (!thread) error (-1, errno, "Null thread pointer in user_thread");

  while (!stop)
  {
    n_ops = config.query_min + 
      (config.query_max - config.query_min)*drand();

    while (!(thread->mysql = connect_mysql(NULL, config.db_name)) && !stop)
    {
      error (0, errno, "Thread %d: unable to connect to database",
             thread->id);
      sleep (1);
    }
      
    gettimeofday (&connect, NULL);
    mysql_select_db (thread->mysql, config.db_name);

    while (n_ops-- && !stop)
    {
      err = run_update(thread);
    };
      
    mysql_close (thread->mysql);
    mysql_thread_end ();
  }
  return NULL;
}

static int create_table (MYSQL *mysql, struct my_table * my_tables, int i)
{
  char cmd[CMD_LEN];
  char *name = my_tables[i].name;
  static int t_num = 0;

  printf ("%s[%d] ", name, t_num); 
  
  snprintf (cmd, CMD_LEN,
            "create table %s( "
            "col_key int not null primary key, "
            "count  double not null, "
            "index tbl_ind%s (col_key) "
            ") type=heap "
            "MIN_ROWS = 1 MAX_ROWS = %d",
            name, name,
            config.n_rows);
  
  if (mysql_query (mysql, cmd))
    error (-1, errno, "%s failed to create table '%s': %s",
           cmd, name, mysql_error(mysql));
  
  t_num++;
  return t_num;
}

static int fill_table (MYSQL *mysql, int n)
{
  char cmd[CMD_LEN];
  char *name =  my_tables[n].name;
  int i;
  
  printf ("%s ", name); fflush (stdout);
  for (i = 0; i < config.n_rows; i++)
  {
    snprintf (cmd, CMD_LEN, "INSERT INTO %s VALUES (%d, 0)", name, i+1);
    if (mysql_query (mysql, cmd))
    {
      error_mysql(mysql);
      error (-1, errno, "Failed to insert row into %s: %s", my_tables[n].name,
             mysql_error(mysql));
    }
    my_tables[n].rows++;
  }
  return 0;
}

int comm_create ()
{
  MYSQL *mysql = NULL;
  char cmd[CMD_LEN];
  struct timeval begin;
  int i;
  
  puts ("Connecting to server:");

  mysql = connect_mysql (mysql, NULL);
  if (!mysql)
    error (-1, errno, "Unable to connect to server");

  puts ("ok");

  my_tables = (struct my_table *)
    realloc (my_tables, config.n_tables * sizeof (struct my_table));
  if (NULL == my_tables)
    error (-1, errno, "Failed to allocate memory for table structure array");
  memset (my_tables, 0, config.n_tables * sizeof (struct my_table));

  for (i = 0; i < config.n_tables; i++)
  {
    snprintf (cmd, CMD_LEN, "t%d", i);
    my_tables[i].name = strdup (cmd);

    my_tables[i].prim_key = 1;

  }
  
  char *table_types[] = {};			
  unsigned char table_len = 0;
  char *p_type = "heap";		
  if (config.create==1) 
  {
    snprintf (cmd, CMD_LEN, "drop database %s", config.db_name);
    mysql_query (mysql, cmd);

    snprintf (cmd, CMD_LEN, "create database %s", config.db_name);
    if (mysql_query (mysql, cmd))
    {
      error (-1, errno, "Failed to create database %s: %s", config.db_name,
             mysql_error(mysql));
    }

    mysql_select_db (mysql, config.db_name);

    puts ("Creating tables:");
    gettimeofday (&begin, NULL);
    table_types[table_len] = p_type;		
    while (*p_type != '\0') {			
      if (*p_type == ',') {
        *p_type = '\0';			
        table_types[++table_len] = ++p_type;	
	
        }
      else {
        p_type++;
        }
      }
    for (i = 0; i < config.n_tables; i++) create_table(mysql, my_tables, i);
    for (i = 0; i < config.n_tables; i++) fill_table (mysql, i);
    putchar('\n');
    putchar('\n');
  }
   
  n_tuples = config.n_rows;
   
  mysql_select_db (mysql, config.db_name);
  mysql_close (mysql);
  return 0;
}

int main (int argc, char *argv[])
{
  int i;

  config.n_users = atoi(argv[1]);
  strcpy(config.db_host,argv[2]);
  config.db_port = atoi(argv[3]);
  strcpy(config.db_name,argv[4]);
  strcpy(config.db_user,argv[5]);
  strcpy(config.db_pswd,argv[6]);
  config.n_tables = atoi(argv[7]);
  config.n_rows = atoi(argv[8]);
  if (argc > 9) config.create = atoi(argv[9]);

  if (1 != mysql_thread_safe())
    error (1, errno, "This program was not compiled thread safe, exiting.");
      
  my_threads = (struct my_thread *) calloc (config.n_users,
                                            sizeof (struct my_thread));
  
  comm_create();

  for (i = 0; i < config.n_users; i++)
  {
    my_threads[i].id = i + 1;
    pthread_create (&(my_threads[i].thread), NULL, user_thread, my_threads + i);
  }

  fflush (stdout);

  printf ("Running bug test until C-c\n");
  sleep(99999);
  
  stop = 1;
  for (i = 0; i < config.n_users; i++)
    pthread_join (my_threads[i].thread, NULL);
  
  return 0;
}

EOF
cc -o bug10178-1.exe `mysql_config --cflags` bug10178-1.c \
    `mysql_config --libs | sed s/lmysqlclient/lmysqlclient_r/g` -lpthread
trap "echo interrupted" 2
./bug10178-1.exe 100 localhost "$MYSQL_TCP_PORT" test root "" 1 1 # 0
echo
sleep 2


echo "######################################################################"
echo "#"
echo "# Stopping database server."
"$MYSQLA" $PORT_1 $SOCK_1 -u root shutdown
sleep 3


echo "# End of Test."
echo "#"
echo "######################################################################"

