/* 
   This was generated code!
 
   Reworked by Hakan Kuecuekyilmaz to get more predictable
   run time behaviour. 2006-03-23.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <mysql.h>
#include <pthread.h>

#define TESTTIME (560)
#define NUMTHREADS (3)
#define DATASIZE (16 * 1024 * 1024)
/* Seed of 303 and 2 threads fails on Linux 32-bit within 1 minute */
/* Seed of 808 and 3 threads fails on Linux 64-bit within 3 minute */
#define SEED 808

char host[]= "127.0.0.1";
char username[]= "root";
char password[]= "";
char database[]= "test";
int port= 3306;

pthread_t pthreads[NUMTHREADS];
int threaddone= 0;
char *databucket= NULL;

int   db_query(MYSQL *dbc, char *sql, int show_results);
char *alocmem(const int num);
int   write_string(char *buf, const int minlen, const int maxlen, const long int random_number, const int flag);
void *worker_thread(void *arg);

int db_query(MYSQL *dbc, char *sql, int show_results)
{
  MYSQL_RES *r= NULL;
  MYSQL_ROW w;
  MYSQL_FIELD *field= NULL;

  int i= 0;
  int more_result= 0;
  int res= 0;

  res= mysql_query(dbc,sql);
  if (res != 0 && show_results > 0)
  {
    /*
       Ignore 1020 "Record has changed since last read".
     */
    if (mysql_errno(dbc) != 1020)
    {
      printf("Query failed: %d (%s)\n", mysql_errno(dbc), mysql_error(dbc));
    }
    return 0;
  }

  do {
    r= mysql_store_result(dbc);
    if (r)
    {
      unsigned int numfields= mysql_num_fields(r);
      while ((field= mysql_fetch_field(r))) {
        //print metadata information about each field
        if (show_results > 1)
        {
          printf("%s    ", field->name);
        }
      }

      if (show_results > 1)
      {
        printf("\n------------------------------------\n");
      }

      while ((w = mysql_fetch_row(r))) {
        for (i = 0; i < numfields; i++) {
          // Print each field here.
          if (show_results > 1)
          {
            printf("%s\t", w[i]);
          }
        }

        if (show_results > 1)
        {
          printf("\n");
        }
      }

      if (show_results > 1)
      {
        printf("\n");
      }
      mysql_free_result(r);
    }
      else // No rows returned. Was it a SELECT?
    {
      if (mysql_field_count(dbc) > 0 && show_results > 0)
      {
        printf("No results for '%s'. (%d) - %s\n", sql, mysql_errno(dbc), mysql_error(dbc));
      }
      else // It could have been some INSERT/UPDATE/DELETE.
      {
        // This is successful query.
      }
    }

    more_result= mysql_next_result(dbc);
    if (more_result > 0 && show_results > 0)
    {
      printf("mysql_next_result returned %d, mysql error %s, (%d)\n", more_result, mysql_error(dbc), mysql_errno(dbc));
      break;
    }
  } while (more_result == 0);
  return 0;
}

char* alocmem(const int num)
{
  char *r= calloc(num, 1);

  if (NULL == r)
  {
    printf("Cannot calloc %d bytes of memory.\n", num);
    exit(1);
  }
  return r;
}

int write_string(char *buf, const int minlen, const int maxlen, const long int random_number, const int flag)
{
  if (!buf || maxlen==0)
    return 0;

  int length= minlen + random_number % (maxlen-minlen) - 1;
  int start_length= random_number % (DATASIZE - length - 1);
  if (length < 0) length= 0;
  if (start_length < 0) start_length= 0;
  memcpy(buf, databucket + start_length, length);
  buf[length]= 0;

  return length;
}

void *worker_thread(void *arg)
{
  /* Every thread should have different seed. */
  int i= (int) arg;
  int local_seed = SEED + i;

  MYSQL *dbc= NULL;
  int cancelstate= 0;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancelstate);

  if (!(dbc= mysql_init(NULL)))
  {
    printf("mysql_init\n");
    dbc= NULL;
    goto threadexit;
  }

  if (!mysql_real_connect(dbc, host, username, password, database, port, NULL, CLIENT_FOUND_ROWS|CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS))
  {
    printf("mysql_real_connect failed: %s (%d)\n", mysql_error(dbc), mysql_errno(dbc));
    dbc= NULL;
  }

  unsigned int counter= 0;
  long int random_number= 0;
  char shortquery[1024]= {0};
  char *longquery= NULL;
  char *c= NULL;

  struct drand48_data buffer;
  srand48_r(local_seed, &buffer);

  while (!threaddone && dbc != NULL) {
    /*
       Printing 9 SQL statements.
     */

    lrand48_r(&buffer, &random_number);
    if (random_number % 5 == 0)
    {
      c= shortquery;
      c+= sprintf(c, "%s", "START TRANSACTION");
      db_query(dbc, shortquery, 1);
    }

    lrand48_r(&buffer, &random_number);
    if (random_number % 5 == 0)
    {
      longquery= alocmem(66111);
      lrand48_r(&buffer, &random_number);
      for (counter= 0; counter < 5; counter++) {
        c= longquery;
        c+= sprintf(c, "%s", "INSERT IGNORE INTO t1 (c1, c2, c3) VALUES (");
        c+= sprintf(c, "%ld", -8388608 + random_number % 16777215);
        c+= sprintf(c, "%s", ",'");
        c+= write_string(c, 0, 255, random_number, 0);
        c+= sprintf(c, "%s", "','");
        c+= write_string(c, 0, 65535, random_number, 0);
        c+= sprintf(c, "%s", "')");
        db_query(dbc, longquery, 1);
      }
      free(longquery);
    }

    lrand48_r(&buffer, &random_number);
    if (random_number % 5 == 0)
    {
      for (counter= 0; counter < 5; counter++) {
        lrand48_r(&buffer, &random_number);
        c= shortquery;
        c+= sprintf(c, "%s", "SELECT c1 FROM t1 WHERE c2 = '");
        c+= write_string(c, 0, 255, random_number, 0);
        c+= sprintf(c, "%s", "'");
        db_query(dbc, shortquery, 1);
      }
    }

    lrand48_r(&buffer, &random_number);
    if (random_number % 5 == 0)
    {
      c= shortquery;
      c+= sprintf(c, "%s", "UPDATE t1 SET c2 = md5(c2)");
      db_query(dbc, shortquery, 1);
    }

    lrand48_r(&buffer, &random_number);
    if (random_number % 5 == 0)
    {
      c= shortquery;
      c+= sprintf(c, "%s", "UPDATE t1 SET c3 = md5(c3)");
      db_query(dbc, shortquery, 1);
    }

    lrand48_r(&buffer, &random_number);
    if (random_number % 5 == 0)
    {
      for (counter= 0; counter < 3; counter++) {
        c= shortquery;
        c+= sprintf(c, "%s", "ROLLBACK");
        db_query(dbc, shortquery, 1);
      }
    }

    lrand48_r(&buffer, &random_number);
    if (random_number % 5 == 0)
    {
      c= shortquery;
      c+= sprintf(c, "%s", "DELETE FROM t1 ORDER BY c1 * rand(");
      c+= sprintf(c, "%d", local_seed);
      c+= sprintf(c, "%s", ") LIMIT 10");
      db_query(dbc, shortquery, 1);
    }

    lrand48_r(&buffer, &random_number);
    if (random_number % 5 == 0)
    {
      c= shortquery;
      c+= sprintf(c, "%s", "COMMIT");
      db_query(dbc, shortquery, 1);
    }
  }
threadexit:
  mysql_close(dbc);
  mysql_thread_end();
  pthread_exit(0);
}

int main(int argc, const char *argv[])
{
  MYSQL *dbc= NULL;
  int i= 0;
  int err= 0;

  time_t timestart= 0,timenow= 0;

  char shortquery[1024]= {0};
  char *c= NULL;

  if (!(dbc = mysql_init(NULL)))
  {
    printf("mysql_init failed.\n");
    dbc= NULL;
    goto threadexit;
  }
  else
  {
    if (!mysql_real_connect(dbc,host,username,password,database,port, NULL, CLIENT_FOUND_ROWS|CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS))
    {
      printf("mysql_real_connect failed: %s (%d)\n", mysql_error(dbc), mysql_errno(dbc));
      dbc= NULL;
      goto threadexit;
    }
  }

  printf("Running initializations ...\n");
  printf("Pre-generating %d bytes of random data.\n", DATASIZE);
  databucket= calloc(DATASIZE, sizeof(char*));
  if (databucket == NULL)
  {
    printf("Error: cannot calloc data buffer.\n");
    exit(1);
  }

  srand48(SEED);
  for (i= 0; i < DATASIZE - 1; i++) {
    databucket[i]= 0x61 + (lrand48() % 26);
  }

  c= shortquery;
  c+= sprintf(c, "%s", "DROP TABLE IF EXISTS t1");
  db_query(dbc, shortquery, 1);

  c= shortquery;
  c+= sprintf(c, "%s", "CREATE TABLE t1(c1 mediumint, c2 mediumblob, c3 mediumtext, key(c1)) Engine Falcon");
  db_query(dbc, shortquery, 1);

  c= shortquery;
  c+= sprintf(c, "%s", "SET GLOBAL max_allowed_packet = 1024*1024*1024");
  db_query(dbc, shortquery, 1);

  mysql_close(dbc);

  printf("About to spawn %d threads\n", NUMTHREADS);
  for (i= 0; i < NUMTHREADS; i++) {
    err= pthread_create(&pthreads[i], NULL, worker_thread, (void *) i);
    if (err != 0)
    {
      printf("Error spawning thread %d, pthread_create returned %d\n", i, err);
    }
    printf("%d ", i);
    sleep(1);
  }
  printf("\n");
  printf("Completed spawning new database worker threads.\n");

  printf("Testcase is now running, so watch for error output.\n");

  timestart= time(NULL);
  timenow= time(NULL);
  for (i = 0; (timenow - timestart) < TESTTIME; timenow = time(NULL)) {
    sleep(1);
  }

  threaddone= 1;

  printf("Waiting for worker threads to finish ...\n");

  for (i= 0; i < NUMTHREADS; i++)
  {
    pthread_join(pthreads[i], NULL);
  }

  exit(0);

threadexit:
  exit(-1);
}
