/*
Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.

The MySQL Connector/C is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
MySQL Connectors. There are special exceptions to the terms and
conditions of the GPLv2 as it is applied to this software, see the
FLOSS License Exception
<http://www.mysql.com/about/legal/licensing/foss-exception.html>.

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 St, Fifth Floor, Boston, MA 02110-1301  USA
*/
#include "my_test.h"

/* helper functions */
enum { MAX_COLUMN_LENGTH= 255 };

typedef struct st_stmt_fetch
{
  const char *query;
  unsigned stmt_no;
  MYSQL_STMT *handle;
  my_bool is_open;
  MYSQL_BIND *bind_array;
  char **out_data;
  unsigned long *out_data_length;
  unsigned column_count;
  unsigned row_count;
} Stmt_fetch;

MYSQL_STMT *open_cursor(MYSQL *mysql, const char *query)
{
  int rc;
  const ulong type= (ulong)CURSOR_TYPE_READ_ONLY;

  MYSQL_STMT *stmt= mysql_stmt_init(mysql);
  rc= mysql_stmt_prepare(stmt, SL(query));
  if (rc) {
    diag("Error: %s", mysql_stmt_error(stmt));
    return NULL;
  }
  mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type);
  return stmt;
}

/*
  Create statement handle, prepare it with statement, execute and allocate
  fetch buffers.
*/

int stmt_fetch_init(MYSQL *mysql, Stmt_fetch *fetch, unsigned int stmt_no_arg,
                     const char *query_arg)
{
  unsigned long type= CURSOR_TYPE_READ_ONLY;
  int rc;
  unsigned int i;
  MYSQL_RES *metadata;

  /* Save query and statement number for error messages */
  fetch->stmt_no= stmt_no_arg;
  fetch->query= query_arg;

  fetch->handle= mysql_stmt_init(mysql);

  rc= mysql_stmt_prepare(fetch->handle, SL(fetch->query));
  FAIL_IF(rc, mysql_stmt_error(fetch->handle));

  /*
    The attribute is sent to server on execute and asks to open read-only
    for result set
  */
  mysql_stmt_attr_set(fetch->handle, STMT_ATTR_CURSOR_TYPE,
                      (const void*) &type);

  rc= mysql_stmt_execute(fetch->handle);
  FAIL_IF(rc, mysql_stmt_error(fetch->handle));

  /* Find out total number of columns in result set */
  metadata= mysql_stmt_result_metadata(fetch->handle);
  fetch->column_count= mysql_num_fields(metadata);
  mysql_free_result(metadata);

  /*
    Now allocate bind handles and buffers for output data:
    calloc memory to reduce number of MYSQL_BIND members we need to
    set up.
  */

  fetch->bind_array= (MYSQL_BIND *) calloc(1, sizeof(MYSQL_BIND) *
                                              fetch->column_count);
  fetch->out_data= (char**) calloc(1, sizeof(char*) * fetch->column_count);
  fetch->out_data_length= (ulong*) calloc(1, sizeof(ulong) *
                                             fetch->column_count);
  for (i= 0; i < fetch->column_count; ++i)
  {
    fetch->out_data[i]= (char*) calloc(1, MAX_COLUMN_LENGTH);
    fetch->bind_array[i].buffer_type= MYSQL_TYPE_STRING;
    fetch->bind_array[i].buffer= fetch->out_data[i];
    fetch->bind_array[i].buffer_length= MAX_COLUMN_LENGTH;
    fetch->bind_array[i].length= fetch->out_data_length + i;
  }

  mysql_stmt_bind_result(fetch->handle, fetch->bind_array);

  fetch->row_count= 0;
  fetch->is_open= TRUE;

  /* Ready for reading rows */
  return OK;
}


int fill_tables(MYSQL *mysql, const char **query_list, unsigned query_count)
{
  int rc;
  const char **query;
  for (query= query_list; query < query_list + query_count;
       ++query)
  {
    rc= mysql_query(mysql, *query);
    check_mysql_rc(rc, mysql);
  }
  return OK;
}

int stmt_fetch_fetch_row(Stmt_fetch *fetch)
{
  int rc;
  unsigned i;

  if ((rc= mysql_stmt_fetch(fetch->handle)) == 0)
  {
    ++fetch->row_count;
    for (i= 0; i < fetch->column_count; ++i)
    {
      fetch->out_data[i][fetch->out_data_length[i]]= '\0';
    }
  }
  else
    fetch->is_open= FALSE;

  return rc;
}

void stmt_fetch_close(Stmt_fetch *fetch)
{
  unsigned i;

  for (i= 0; i < fetch->column_count; ++i)
    free(fetch->out_data[i]);
  free(fetch->out_data);
  free(fetch->out_data_length);
  free(fetch->bind_array);
  mysql_stmt_close(fetch->handle);
}



enum fetch_type { USE_ROW_BY_ROW_FETCH= 0, USE_STORE_RESULT= 1 };

int fetch_n(MYSQL *mysql, const char **query_list, unsigned query_count,
                enum fetch_type fetch_type)
{
  unsigned open_statements= query_count;
  int rc, error_count= 0;
  Stmt_fetch *fetch_array= (Stmt_fetch*) calloc(1, sizeof(Stmt_fetch) *
                                                  query_count);
  Stmt_fetch *fetch;

  for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch)
  {
    if (stmt_fetch_init(mysql, fetch, (unsigned int)(fetch - fetch_array),
                    query_list[fetch - fetch_array]))
      return FAIL;
  }

  if (fetch_type == USE_STORE_RESULT)
  {
    for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch)
    {
      rc= mysql_stmt_store_result(fetch->handle);
      FAIL_IF(rc, mysql_stmt_error(fetch->handle));
    }
  }

  while (open_statements)
  {
    for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch)
    {
      if (fetch->is_open && (rc= stmt_fetch_fetch_row(fetch)))
      {
        open_statements--;
        /*
          We try to fetch from the rest of the statements in case of
          error
        */
        if (rc != MYSQL_NO_DATA)
          error_count++;
      }
    }
  }
  for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch)
    stmt_fetch_close(fetch);
  free(fetch_array);

  return (error_count) ? FAIL:OK;
}

static int test_basic_cursors(MYSQL *mysql)
{
  const char *basic_tables[]=
  {
    "DROP TABLE IF EXISTS t1, t2",

    "CREATE TABLE t1 "
    "(id INTEGER NOT NULL PRIMARY KEY, "
    " name VARCHAR(20) NOT NULL)",

    "INSERT INTO t1 (id, name) VALUES "
    "  (2, 'Ja'), (3, 'Ede'), "
    "  (4, 'Haag'), (5, 'Kabul'), "
    "  (6, 'Almere'), (7, 'Utrecht'), "
    "  (8, 'Qandahar'), (9, 'Amsterdam'), "
    "  (10, 'Amersfoort'), (11, 'Constantine')",

    "CREATE TABLE t2 "
    "(id INTEGER NOT NULL PRIMARY KEY, "
    " name VARCHAR(20) NOT NULL)",

    "INSERT INTO t2 (id, name) VALUES "
    "  (4, 'Guam'), (5, 'Aruba'), "
    "  (6, 'Angola'), (7, 'Albania'), "
    "  (8, 'Anguilla'), (9, 'Argentina'), "
    "  (10, 'Azerbaijan'), (11, 'Afghanistan'), "
    "  (12, 'Burkina Faso'), (13, 'Faroe Islands')"
  };

  const char *queries[]=
  {
    "SELECT * FROM t1",
    "SELECT * FROM t2"
  };

  
  FAIL_IF(fill_tables(mysql, basic_tables, sizeof(basic_tables)/sizeof(*basic_tables)), "fill_tables failed");

  FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_ROW_BY_ROW_FETCH), "fetch_n failed");
  FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_STORE_RESULT), "fetch_n failed");
  return OK;
}


static int test_cursors_with_union(MYSQL *mysql)
{
  const char *queries[]=
  {
    "SELECT t1.name FROM t1 UNION SELECT t2.name FROM t2",
    "SELECT t1.id FROM t1 WHERE t1.id < 5"
  };
  FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_ROW_BY_ROW_FETCH), "fetch_n failed");
  FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_STORE_RESULT), "fetch_n failed");

  return OK;
}


static int test_cursors_with_procedure(MYSQL *mysql)
{
  const char *queries[]=
  {
    "SELECT * FROM t1 procedure analyse()"
  };
  SKIP_MYSQL(mysql);
  FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_ROW_BY_ROW_FETCH), "fetch_n failed");
  FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_STORE_RESULT), "fetch_n failed");

  return OK;
}

/*
  Bug#21206: memory corruption when too many cursors are opened at once

  Memory corruption happens when more than 1024 cursors are open
  simultaneously.
*/
static int test_bug21206(MYSQL *mysql)
{
  int retcode= OK;

  const size_t cursor_count= 1025;

  const char *create_table[]=
  {
    "DROP TABLE IF EXISTS t1",
    "CREATE TABLE t1 (i INT)",
    "INSERT INTO t1 VALUES (1), (2), (3)"
  };
  const char *query= "SELECT * FROM t1";

  Stmt_fetch *fetch_array=
    (Stmt_fetch*) calloc(cursor_count, sizeof(Stmt_fetch));

  Stmt_fetch *fetch;

  FAIL_IF(fill_tables(mysql, create_table, sizeof(create_table) / sizeof(*create_table)), "fill_tables failed");

  for (fetch= fetch_array; fetch < fetch_array + cursor_count; ++fetch)
  {
    if ((retcode= stmt_fetch_init(mysql, fetch, (unsigned int)(fetch - fetch_array), query)))
      break;
  }

  for (fetch= fetch_array; fetch < fetch_array + cursor_count; ++fetch)
    stmt_fetch_close(fetch);

  free(fetch_array);

  return retcode;
}

static int test_bug10729(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[1];
  char a[21];
  int rc;
  const char *stmt_text;
  int i= 0;
  const char *name_array[3]= { "aaa", "bbb", "ccc" };
  ulong type;

  mysql_query(mysql, "drop table if exists t1");
  mysql_query(mysql, "create table t1 (id integer not null primary key,"
                                      "name VARCHAR(20) NOT NULL)");
  rc= mysql_query(mysql, "insert into t1 (id, name) values "
                         "(1, 'aaa'), (2, 'bbb'), (3, 'ccc')");
  check_mysql_rc(rc, mysql);

  stmt= mysql_stmt_init(mysql);
  FAIL_IF(!stmt, mysql_error(mysql));

  type= (ulong) CURSOR_TYPE_READ_ONLY;
  rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type);
  check_stmt_rc(rc, stmt);
  stmt_text= "select name from t1";
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);

  memset(my_bind, '\0', sizeof(my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_STRING;
  my_bind[0].buffer= (void*) a;
  my_bind[0].buffer_length= sizeof(a);
  mysql_stmt_bind_result(stmt, my_bind);

  for (i= 0; i < 3; i++)
  {
    int row_no= 0;
    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);
    while ((rc= mysql_stmt_fetch(stmt)) == 0)
    {
      FAIL_UNLESS(strcmp(a, name_array[row_no]) == 0, "a != name_array[row_no]");
      ++row_no;
    }
    FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA");
  }
  rc= mysql_stmt_close(stmt);

  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);

  return OK;
}

/* Bug#10736: cursors and subqueries, memroot management */

static int test_bug10736(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[1];
  char a[21];
  int rc;
  const char *stmt_text;
  int i= 0;
  ulong type;

  rc= mysql_query(mysql, "drop table if exists t1");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "create table t1 (id integer not null primary key,"
                                      "name VARCHAR(20) NOT NULL)");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "insert into t1 (id, name) values "
                         "(1, 'aaa'), (2, 'bbb'), (3, 'ccc')");
  check_mysql_rc(rc, mysql);

  stmt= mysql_stmt_init(mysql);

  type= (ulong) CURSOR_TYPE_READ_ONLY;
  rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type);
  check_stmt_rc(rc, stmt);
  stmt_text= "select name from t1 where name=(select name from t1 where id=2)";
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);

  memset(my_bind, '\0', sizeof(my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_STRING;
  my_bind[0].buffer= (void*) a;
  my_bind[0].buffer_length= sizeof(a);
  mysql_stmt_bind_result(stmt, my_bind);

  for (i= 0; i < 3; i++)
  {
    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);
    while ((rc= mysql_stmt_fetch(stmt)) == 0);
    FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA");
  }
  rc= mysql_stmt_close(stmt);

  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);

  return OK;
}

/* Bug#10794: cursors, packets out of order */

static int test_bug10794(MYSQL *mysql)
{
  MYSQL_STMT *stmt, *stmt1;
  MYSQL_BIND my_bind[2];
  char a[21];
  int id_val;
  ulong a_len;
  int rc;
  const char *stmt_text;
  int i= 0;
  ulong type;

  rc= mysql_query(mysql, "drop table if exists t1");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "create table t1 (id integer not null primary key,"
                                      "name varchar(20) not null)");
  check_mysql_rc(rc, mysql);

  stmt= mysql_stmt_init(mysql);
  stmt_text= "insert into t1 (id, name) values (?, ?)";
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);

  memset(my_bind, '\0', sizeof(my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_LONG;
  my_bind[0].buffer= (void*) &id_val;
  my_bind[1].buffer_type= MYSQL_TYPE_STRING;
  my_bind[1].buffer= (void*) a;
  my_bind[1].length= &a_len;
  rc= mysql_stmt_bind_param(stmt, my_bind);
  check_stmt_rc(rc, stmt);
  for (i= 0; i < 42; i++)
  {
    id_val= (i+1)*10;
    sprintf(a, "a%d", i);
    a_len= (unsigned long)strlen(a); /* safety against broken sprintf */
    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);
  }
  stmt_text= "select name from t1";
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  type= (ulong) CURSOR_TYPE_READ_ONLY;
  mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type);
  stmt1= mysql_stmt_init(mysql);
  mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE, (const void*) &type);
  memset(my_bind, '\0', sizeof(my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_STRING;
  my_bind[0].buffer= (void*) a;
  my_bind[0].buffer_length= sizeof(a);
  my_bind[0].length= &a_len;
  rc= mysql_stmt_bind_result(stmt, my_bind);
  check_stmt_rc(rc, stmt);
  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);
  rc= mysql_stmt_fetch(stmt);
  check_stmt_rc(rc, stmt);
  /* Don't optimize: an attribute of the original test case */
  mysql_stmt_free_result(stmt);
  mysql_stmt_reset(stmt);
  stmt_text= "select name from t1 where id=10";
  rc= mysql_stmt_prepare(stmt1, SL(stmt_text));
  check_stmt_rc(rc, stmt1);
  rc= mysql_stmt_bind_result(stmt1, my_bind);
  check_stmt_rc(rc, stmt1);
  rc= mysql_stmt_execute(stmt1);
  check_stmt_rc(rc, stmt1);
  while (1)
  {
    rc= mysql_stmt_fetch(stmt1);
    if (rc == MYSQL_NO_DATA)
    {
      break;
    }
    check_stmt_rc(rc, stmt1);
  }
  mysql_stmt_close(stmt);
  mysql_stmt_close(stmt1);

  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);

  return OK;
}

/* Bug#10760: cursors, crash in a fetch after rollback. */

static int test_bug10760(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[1];
  int rc;
  const char *stmt_text;
  char id_buf[20];
  ulong id_len;
  int i= 0;
  ulong type;

  rc= mysql_query(mysql, "drop table if exists t1, t2");
  check_mysql_rc(rc, mysql);

  /* create tables */
  rc= mysql_query(mysql, "create table t1 (id integer not null primary key)"
                         " engine=MyISAM");
  check_mysql_rc(rc, mysql);;
  for (; i < 42; ++i)
  {
    char buf[100];
    sprintf(buf, "insert into t1 (id) values (%d)", i+1);
    rc= mysql_query(mysql, buf);
    check_mysql_rc(rc, mysql);;
  }
  mysql_autocommit(mysql, FALSE);
  /* create statement */
  stmt= mysql_stmt_init(mysql);
  type= (ulong) CURSOR_TYPE_READ_ONLY;
  mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type);

  /*
    1: check that a deadlock within the same connection
    is resolved and an error is returned. The deadlock is modelled
    as follows:
    con1: open cursor for select * from t1;
    con1: insert into t1 (id) values (1)
  */
  stmt_text= "select id from t1 order by 1";
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);;
  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);;
  rc= mysql_query(mysql, "update t1 set id=id+100");
  /*
    If cursors are not materialized, the update will return an error;
    we mainly test that it won't deadlock.
  */
  /*  FAIL_IF(!rc, "Error expected"); */
  /*
    2: check that MyISAM tables used in cursors survive
    COMMIT/ROLLBACK.
  */
  rc= mysql_rollback(mysql);                  /* should not close the cursor */
  check_mysql_rc(rc, mysql);;
  rc= mysql_stmt_fetch(stmt);
  check_stmt_rc(rc, stmt);;

  /*
    3: check that cursors to InnoDB tables are closed (for now) by
    COMMIT/ROLLBACK.
  */
  if (check_variable(mysql, "@@have_innodb", "YES"))
  {
    stmt_text= "select id from t1 order by 1";
    rc= mysql_stmt_prepare(stmt, SL(stmt_text));
    check_stmt_rc(rc, stmt);;

    rc= mysql_query(mysql, "alter table t1 engine=InnoDB");
    check_mysql_rc(rc, mysql);;

    memset(my_bind, '\0', sizeof(my_bind));
    my_bind[0].buffer_type= MYSQL_TYPE_STRING;
    my_bind[0].buffer= (void*) id_buf;
    my_bind[0].buffer_length= sizeof(id_buf);
    my_bind[0].length= &id_len;
    check_stmt_rc(rc, stmt);;
    mysql_stmt_bind_result(stmt, my_bind);

    rc= mysql_stmt_execute(stmt);
    rc= mysql_stmt_fetch(stmt);
    FAIL_UNLESS(rc == 0, "rc != 0");
    rc= mysql_rollback(mysql);                  /* should close the cursor */
  }

  mysql_stmt_close(stmt);
  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);
  rc= mysql_autocommit(mysql, TRUE);                /* restore default */
  check_mysql_rc(rc, mysql);

  return OK;
}

/* Bug#11172: cursors, crash on a fetch from a datetime column */

static int test_bug11172(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  MYSQL_BIND bind_in[1], bind_out[2];
  MYSQL_TIME hired;
  int rc;
  const char *stmt_text;
  int i= 0, id;
  ulong type;

  rc= mysql_query(mysql, "drop table if exists t1");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "create table t1 (id integer not null primary key,"
                                      "hired date not null)");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql,
                  "insert into t1 (id, hired) values (1, '1933-08-24'), "
                  "(2, '1965-01-01'), (3, '1949-08-17'), (4, '1945-07-07'), "
                  "(5, '1941-05-15'), (6, '1978-09-15'), (7, '1936-03-28')");
  check_mysql_rc(rc, mysql);
  stmt= mysql_stmt_init(mysql);
  FAIL_IF(!stmt, mysql_error(mysql));
  stmt_text= "SELECT id, hired FROM t1 WHERE hired=?";
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);

  type= (ulong) CURSOR_TYPE_READ_ONLY;
  mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type);

  memset(bind_in, '\0', sizeof(bind_in));
  memset(bind_out, '\0', sizeof(bind_out));
  memset(&hired, '\0', sizeof(hired));
  hired.year= 1965;
  hired.month= 1;
  hired.day= 1;
  bind_in[0].buffer_type= MYSQL_TYPE_DATE;
  bind_in[0].buffer= (void*) &hired;
  bind_in[0].buffer_length= sizeof(hired);
  bind_out[0].buffer_type= MYSQL_TYPE_LONG;
  bind_out[0].buffer= (void*) &id;
  bind_out[1]= bind_in[0];

  for (i= 0; i < 3; i++)
  {
    rc= mysql_stmt_bind_param(stmt, bind_in);
    check_stmt_rc(rc, stmt);
    rc= mysql_stmt_bind_result(stmt, bind_out);
    check_stmt_rc(rc, stmt);
    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);
    while ((rc= mysql_stmt_fetch(stmt)) == 0);
    FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA");
    if (!mysql_stmt_free_result(stmt))
      mysql_stmt_reset(stmt);
  }
  mysql_stmt_close(stmt);
  mysql_rollback(mysql);
  mysql_rollback(mysql);

  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);

  return OK;
}

/* Bug#11656: cursors, crash on a fetch from a query with distinct. */

static int test_bug11656(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[2];
  int rc;
  const char *stmt_text;
  char buf[2][20];
  int i= 0;
  ulong type;

  rc= mysql_query(mysql, "drop table if exists t1");
  check_mysql_rc(rc, mysql);

  rc= mysql_query(mysql, "create table t1 ("
                  "server varchar(40) not null, "
                  "test_kind varchar(1) not null, "
                  "test_id varchar(30) not null , "
                  "primary key (server,test_kind,test_id))");
  check_mysql_rc(rc, mysql);

  stmt_text= "select distinct test_kind, test_id from t1 "
             "where server in (?, ?)";
  stmt= mysql_stmt_init(mysql);
  FAIL_IF(!stmt, mysql_error(mysql));
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);
  type= (ulong) CURSOR_TYPE_READ_ONLY;
  mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type);

  memset(my_bind, '\0', sizeof(my_bind));
  strcpy(buf[0], "pcint502_MY2");
  strcpy(buf[1], "*");
  for (i=0; i < 2; i++)
  {
    my_bind[i].buffer_type= MYSQL_TYPE_STRING;
    my_bind[i].buffer= (uchar* *)&buf[i];
    my_bind[i].buffer_length= (unsigned long)strlen(buf[i]);
  }
  rc= mysql_stmt_bind_param(stmt, my_bind);
  check_stmt_rc(rc, stmt);

  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);

  rc= mysql_stmt_fetch(stmt);
  FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA");

  mysql_stmt_close(stmt);
  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);

  return OK;
}

/* Cursors: opening a cursor to a compilicated query with ORDER BY */

static int test_bug11901(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[2];
  int rc;
  char workdept[20];
  ulong workdept_len; 
  uint32 empno; 
  const ulong type= (ulong)CURSOR_TYPE_READ_ONLY;
  const char *stmt_text;


  stmt_text= "drop table if exists t1, t2";
  rc= mysql_real_query(mysql, SL(stmt_text));
  check_mysql_rc(rc, mysql);

  stmt_text= "create table t1 ("
    "  empno int(11) not null, firstname varchar(20) not null,"
    "  midinit varchar(20) not null, lastname varchar(20) not null,"
    "  workdept varchar(6) not null, salary double not null,"
    "  bonus float not null, primary key (empno), "
    " unique key (workdept, empno) "
    ") default charset=latin1 collate=latin1_bin";
  rc= mysql_real_query(mysql, SL(stmt_text));
  check_mysql_rc(rc, mysql);

  stmt_text= "insert into t1 values "
     "(10,  'CHRISTINE', 'I', 'HAAS',      'A00', 52750, 1000),"
     "(20,  'MICHAEL',   'L', 'THOMPSON',  'B01', 41250, 800), "
     "(30,  'SALLY',     'A', 'KWAN',      'C01', 38250, 800), "
     "(50,  'JOHN',      'B', 'GEYER',     'E01', 40175, 800), "
     "(60,  'IRVING',    'F', 'STERN',     'D11', 32250, 500), "
     "(70,  'EVA',       'D', 'PULASKI',   'D21', 36170, 700), "
     "(90,  'EILEEN',    'W', 'HENDERSON', 'E11', 29750, 600), "
     "(100, 'THEODORE',  'Q', 'SPENSER',   'E21', 26150, 500), "
     "(110, 'VINCENZO',  'G', 'LUCCHESSI', 'A00', 46500, 900), "
     "(120, 'SEAN',      '',  'O\\'CONNELL', 'A00', 29250, 600), "
     "(130, 'DOLORES',   'M', 'QUINTANA',  'C01', 23800, 500), "
     "(140, 'HEATHER',   'A', 'NICHOLLS',  'C01', 28420, 600), "
     "(150, 'BRUCE',     '',  'ADAMSON',   'D11', 25280, 500), "
     "(160, 'ELIZABETH', 'R', 'PIANKA',    'D11', 22250, 400), "
     "(170, 'MASATOSHI', 'J', 'YOSHIMURA', 'D11', 24680, 500), "
     "(180, 'MARILYN',   'S', 'SCOUTTEN',  'D11', 21340, 500), "
     "(190, 'JAMES',     'H', 'WALKER',    'D11', 20450, 400), "
     "(200, 'DAVID',     '',  'BROWN',     'D11', 27740, 600), "
     "(210, 'WILLIAM',   'T', 'JONES',     'D11', 18270, 400), "
     "(220, 'JENNIFER',  'K', 'LUTZ',      'D11', 29840, 600), "
     "(230, 'JAMES',     'J', 'JEFFERSON', 'D21', 22180, 400), "
     "(240, 'SALVATORE', 'M', 'MARINO',    'D21', 28760, 600), "
     "(250, 'DANIEL',    'S', 'SMITH',     'D21', 19180, 400), "
     "(260, 'SYBIL',     'P', 'JOHNSON',   'D21', 17250, 300), "
     "(270, 'MARIA',     'L', 'PEREZ',     'D21', 27380, 500), "
     "(280, 'ETHEL',     'R', 'SCHNEIDER', 'E11', 26250, 500), "
     "(290, 'JOHN',      'R', 'PARKER',    'E11', 15340, 300), "
     "(300, 'PHILIP',    'X', 'SMITH',     'E11', 17750, 400), "
     "(310, 'MAUDE',     'F', 'SETRIGHT',  'E11', 15900, 300), "
     "(320, 'RAMLAL',    'V', 'MEHTA',     'E21', 19950, 400), "
     "(330, 'WING',      '',  'LEE',       'E21', 25370, 500), "
     "(340, 'JASON',     'R', 'GOUNOT',    'E21', 23840, 500)";

  rc= mysql_real_query(mysql, SL(stmt_text));
  check_mysql_rc(rc, mysql);

  stmt_text= "create table t2 ("
    " deptno varchar(6) not null, deptname varchar(20) not null,"
    " mgrno int(11) not null, location varchar(20) not null,"
    " admrdept varchar(6) not null, refcntd int(11) not null,"
    " refcntu int(11) not null, primary key (deptno)"
    ") default charset=latin1 collate=latin1_bin";
  rc= mysql_real_query(mysql, SL(stmt_text));
  check_mysql_rc(rc, mysql);

  stmt_text= "insert into t2 values "
    "('A00', 'SPIFFY COMPUTER SERV', 10, '', 'A00', 0, 0), "
    "('B01', 'PLANNING',             20, '', 'A00', 0, 0), "
    "('C01', 'INFORMATION CENTER',   30, '', 'A00', 0, 0), "
    "('D01', 'DEVELOPMENT CENTER',   0,  '', 'A00', 0, 0),"
    "('D11', 'MANUFACTURING SYSTEM', 60, '', 'D01', 0, 0), "
    "('D21', 'ADMINISTRATION SYSTE', 70, '', 'D01', 0, 0), "
    "('E01', 'SUPPORT SERVICES',     50, '', 'A00', 0, 0), "
    "('E11', 'OPERATIONS',           90, '', 'E01', 0, 0), "
    "('E21', 'SOFTWARE SUPPORT',     100,'', 'E01', 0, 0)";
  rc= mysql_real_query(mysql, SL(stmt_text));
  check_mysql_rc(rc, mysql);

  stmt_text=        "select t1.empno, t1.workdept "
                    "from (t1 left join t2 on t2.deptno = t1.workdept) "
                    "where t2.deptno in "
                    "   (select t2.deptno "
                    "    from (t1 left join t2 on t2.deptno = t1.workdept) "
                    "    where t1.empno = ?) "
                    "order by 1";
  stmt= mysql_stmt_init(mysql);
  FAIL_IF(!stmt, mysql_error(mysql));
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);
  mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type);
  check_stmt_rc(rc, stmt);


  memset(my_bind, '\0', sizeof(my_bind));

  my_bind[0].buffer_type= MYSQL_TYPE_LONG;
  my_bind[0].buffer= &empno;
  rc= mysql_stmt_bind_param(stmt, my_bind);
  check_stmt_rc(rc, stmt);

  my_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING;
  my_bind[1].buffer= (void*) workdept;
  my_bind[1].buffer_length= sizeof(workdept);
  my_bind[1].length= &workdept_len;

  rc= mysql_stmt_bind_result(stmt, my_bind);
  check_stmt_rc(rc, stmt);

  empno= 10;

  /* ERROR: next statement causes a server crash */
  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);

  mysql_stmt_close(stmt);

  rc= mysql_query(mysql, "drop table t1, t2");
  check_mysql_rc(rc, mysql);

  return OK;
}

/* Bug#11904: mysql_stmt_attr_set CURSOR_TYPE_READ_ONLY grouping wrong result */

static int test_bug11904(MYSQL *mysql)
{
  MYSQL_STMT *stmt1;
  int rc;
  const char *stmt_text;
  const ulong type= (ulong)CURSOR_TYPE_READ_ONLY;
  MYSQL_BIND my_bind[2];
  int country_id=0;
  char row_data[11]= {0};

  /* create tables */
  rc= mysql_query(mysql, "DROP TABLE IF EXISTS bug11904b");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "CREATE TABLE bug11904b (id int, name char(10), primary key(id, name))");
  check_mysql_rc(rc, mysql);

  rc= mysql_query(mysql, "INSERT INTO bug11904b VALUES (1, 'sofia'), (1,'plovdiv'),"
                          " (1,'varna'), (2,'LA'), (2,'new york'), (3,'heidelberg'),"
                          " (3,'berlin'), (3, 'frankfurt')");

  check_mysql_rc(rc, mysql);
  mysql_commit(mysql);
  /* create statement */
  stmt1= mysql_stmt_init(mysql);
  mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE, (const void*) &type);

  stmt_text= "SELECT id, MIN(name) FROM bug11904b GROUP BY id ORDER BY id";

  rc= mysql_stmt_prepare(stmt1, SL(stmt_text));
  check_stmt_rc(rc, stmt1);

  memset(my_bind, 0, sizeof(my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_LONG;
  my_bind[0].buffer=& country_id;
  my_bind[0].buffer_length= 0;
  my_bind[0].length= 0;

  my_bind[1].buffer_type= MYSQL_TYPE_STRING;
  my_bind[1].buffer=& row_data;
  my_bind[1].buffer_length= sizeof(row_data) - 1;
  my_bind[1].length= 0;

  rc= mysql_stmt_bind_result(stmt1, my_bind);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_execute(stmt1);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_fetch(stmt1);
  check_stmt_rc(rc, stmt1);
  FAIL_UNLESS(country_id == 1, "country_id != 1");
  FAIL_UNLESS(memcmp(row_data, "plovdiv", 7) == 0, "row_data != 'plovdiv'");

  rc= mysql_stmt_fetch(stmt1);
  check_stmt_rc(rc, stmt1);
  FAIL_UNLESS(country_id == 2, "country_id != 2");
  FAIL_UNLESS(memcmp(row_data, "LA", 2) == 0, "row_data != 'LA'");

  rc= mysql_stmt_fetch(stmt1);
  check_stmt_rc(rc, stmt1);
  FAIL_UNLESS(country_id == 3, "country_id != 3");
  FAIL_UNLESS(memcmp(row_data, "berlin", 6) == 0, "row_data != 'Berlin'");

  rc= mysql_stmt_close(stmt1);
  check_stmt_rc(rc, stmt1);

  rc= mysql_query(mysql, "drop table bug11904b");
  check_mysql_rc(rc, mysql);

  return OK;
}


/* Bug#12243: multiple cursors, crash in a fetch after commit. */

static int test_bug12243(MYSQL *mysql)
{
  MYSQL_STMT *stmt1, *stmt2;
  int rc;
  const char *stmt_text;
  ulong type;

  if (!check_variable(mysql, "@@have_innodb", "YES"))
  {
    diag("Skip -> Test required InnoDB");
    return SKIP;
  }

  /* create tables */
  rc= mysql_query(mysql, "drop table if exists t1");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "create table t1 (a int) engine=InnoDB");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "insert into t1 (a) values (1), (2)");
  check_mysql_rc(rc, mysql);
  mysql_autocommit(mysql, FALSE);
  /* create statement */
  stmt1= mysql_stmt_init(mysql);
  stmt2= mysql_stmt_init(mysql);
  type= (ulong) CURSOR_TYPE_READ_ONLY;
  rc= mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE, (const void*) &type);
  check_stmt_rc(rc, stmt1);
  rc= mysql_stmt_attr_set(stmt2, STMT_ATTR_CURSOR_TYPE, (const void*) &type);
  check_stmt_rc(rc, stmt1);

  stmt_text= "select a from t1";

  rc= mysql_stmt_prepare(stmt1, SL(stmt_text));
  check_stmt_rc(rc, stmt1);
  rc= mysql_stmt_execute(stmt1);
  check_stmt_rc(rc, stmt1);
  rc= mysql_stmt_fetch(stmt1);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_prepare(stmt2, SL(stmt_text));
  check_stmt_rc(rc, stmt2);
  rc= mysql_stmt_execute(stmt2);
  check_stmt_rc(rc, stmt2);
  rc= mysql_stmt_fetch(stmt2);
  check_stmt_rc(rc, stmt2);

  rc= mysql_stmt_close(stmt1);
  check_stmt_rc(rc, stmt1);
  rc= mysql_commit(mysql);
  check_mysql_rc(rc, mysql);
  rc= mysql_stmt_fetch(stmt2);
  check_stmt_rc(rc, stmt2);

  mysql_stmt_close(stmt2);
  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);
  mysql_autocommit(mysql, TRUE);                /* restore default */

  return OK;
}

/* Bug#11909: wrong metadata if fetching from two cursors */

static int test_bug11909(MYSQL *mysql)
{
  MYSQL_STMT *stmt1, *stmt2;
  MYSQL_BIND my_bind[7];
  int rc;
  char firstname[20], midinit[20], lastname[20], workdept[20];
  ulong firstname_len, midinit_len, lastname_len, workdept_len;
  uint32 empno;
  double salary;
  float bonus;
  const char *stmt_text;
  const ulong type= (ulong)CURSOR_TYPE_READ_ONLY;


  stmt_text= "drop table if exists t1";
  rc= mysql_real_query(mysql, SL(stmt_text));
  check_mysql_rc(rc, mysql);

  stmt_text= "create table t1 ("
    "  empno int(11) not null, firstname varchar(20) not null,"
    "  midinit varchar(20) not null, lastname varchar(20) not null,"
    "  workdept varchar(6) not null, salary double not null,"
    "  bonus float not null, primary key (empno)"
    ") default charset=latin1 collate=latin1_bin";
  rc= mysql_real_query(mysql, SL(stmt_text));
  check_mysql_rc(rc, mysql);

  stmt_text= "insert into t1 values "
    "(10, 'CHRISTINE', 'I', 'HAAS',     'A00', 52750, 1000), "
    "(20, 'MICHAEL',   'L', 'THOMPSON', 'B01', 41250, 800),"
    "(30, 'SALLY',     'A', 'KWAN',     'C01', 38250, 800),"
    "(50, 'JOHN',      'B', 'GEYER',    'E01', 40175, 800), "
    "(60, 'IRVING',    'F', 'STERN',    'D11', 32250, 500)";
  rc= mysql_real_query(mysql, SL(stmt_text));
  check_mysql_rc(rc, mysql);

  /* ****** Begin of trace ****** */

  stmt_text= "SELECT empno, firstname, midinit, lastname,"
             "workdept, salary, bonus FROM t1 ORDER BY empno";
  stmt1= mysql_stmt_init(mysql);
  FAIL_IF(!stmt1, mysql_error(mysql));
  rc= mysql_stmt_prepare(stmt1, SL(stmt_text));
  check_stmt_rc(rc, stmt1);
  mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE,
                      (const void*) &type);

  memset(my_bind, '\0', sizeof(my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_LONG;
  my_bind[0].buffer= (void*) &empno;

  my_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING;
  my_bind[1].buffer= (void*) firstname;
  my_bind[1].buffer_length= sizeof(firstname);
  my_bind[1].length= &firstname_len;

  my_bind[2].buffer_type= MYSQL_TYPE_VAR_STRING;
  my_bind[2].buffer= (void*) midinit;
  my_bind[2].buffer_length= sizeof(midinit);
  my_bind[2].length= &midinit_len;

  my_bind[3].buffer_type= MYSQL_TYPE_VAR_STRING;
  my_bind[3].buffer= (void*) lastname;
  my_bind[3].buffer_length= sizeof(lastname);
  my_bind[3].length= &lastname_len;

  my_bind[4].buffer_type= MYSQL_TYPE_VAR_STRING;
  my_bind[4].buffer= (void*) workdept;
  my_bind[4].buffer_length= sizeof(workdept);
  my_bind[4].length= &workdept_len;

  my_bind[5].buffer_type= MYSQL_TYPE_DOUBLE;
  my_bind[5].buffer= (void*) &salary;

  my_bind[6].buffer_type= MYSQL_TYPE_FLOAT;
  my_bind[6].buffer= (void*) &bonus;
  rc= mysql_stmt_bind_result(stmt1, my_bind);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_execute(stmt1);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_fetch(stmt1);
  FAIL_UNLESS(rc == 0, "rc != 0");
  FAIL_UNLESS(empno == 10, "empno != 10");
  FAIL_UNLESS(strcmp(firstname, "CHRISTINE""") == 0, "firstname != 'Christine'");
  FAIL_UNLESS(strcmp(midinit, "I""") == 0, "");
  FAIL_UNLESS(strcmp(lastname, "HAAS""") == 0, "lastname != 'HAAS'");
  FAIL_UNLESS(strcmp(workdept, "A00""") == 0, "workdept != 'A00'");
  FAIL_UNLESS(salary == (double) 52750.0, "salary != 52750");
  FAIL_UNLESS(bonus == (float) 1000.0, "bonus =! 1000");

  stmt_text = "SELECT empno, firstname FROM t1";
  stmt2= mysql_stmt_init(mysql);
  FAIL_IF(!stmt2, mysql_error(mysql));
  rc= mysql_stmt_prepare(stmt2, SL(stmt_text));
  check_stmt_rc(rc, stmt2);
  mysql_stmt_attr_set(stmt2, STMT_ATTR_CURSOR_TYPE,
                      (const void*) &type);
  rc= mysql_stmt_bind_result(stmt2, my_bind);
  check_stmt_rc(rc, stmt2);

  rc= mysql_stmt_execute(stmt2);
  check_stmt_rc(rc, stmt2);

  rc= mysql_stmt_fetch(stmt2);
  FAIL_UNLESS(rc == 0, "rc != 0");

  FAIL_UNLESS(empno == 10, "empno != 10");
  FAIL_UNLESS(strcmp(firstname, "CHRISTINE""") == 0, "firstname != 'Christine'");

  rc= mysql_stmt_reset(stmt2);
  check_stmt_rc(rc, stmt2);

  /* ERROR: next statement should return 0 */

  rc= mysql_stmt_fetch(stmt1);
  FAIL_UNLESS(rc == 0, "rc != 0");

  mysql_stmt_close(stmt1);
  mysql_stmt_close(stmt2);
  rc= mysql_rollback(mysql);
  check_mysql_rc(rc, mysql);

  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);

  return OK;
}

/* Bug#13488: wrong column metadata when fetching from cursor */

static int test_bug13488(MYSQL *mysql)
{
  MYSQL_BIND my_bind[3];
  MYSQL_STMT *stmt1;
  int rc, f1, f2, f3, i;
  const ulong type= CURSOR_TYPE_READ_ONLY;
  const char *query= "select f1, f2, f3 from t1 left join t2 on f1=f2 where f1=1";


  rc= mysql_query(mysql, "drop table if exists t1, t2");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "create table t1 (f1 int not null primary key)");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "create table t2 (f2 int not null primary key, "
                  "f3 int not null)");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "insert into t1 values (1), (2)");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "insert into t2 values (1,2), (2,4)");
  check_mysql_rc(rc, mysql);

  memset(my_bind, 0, sizeof(my_bind));
  for (i= 0; i < 3; i++)
  {
    my_bind[i].buffer_type= MYSQL_TYPE_LONG;
    my_bind[i].buffer_length= 4;
    my_bind[i].length= 0;
  }
  my_bind[0].buffer=&f1;
  my_bind[1].buffer=&f2;
  my_bind[2].buffer=&f3;

  stmt1= mysql_stmt_init(mysql);
  rc= mysql_stmt_attr_set(stmt1,STMT_ATTR_CURSOR_TYPE, (const void *)&type);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_prepare(stmt1, SL(query));
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_execute(stmt1);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_bind_result(stmt1, my_bind);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_fetch(stmt1);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_free_result(stmt1);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_reset(stmt1);
  check_stmt_rc(rc, stmt1);

  rc= mysql_stmt_close(stmt1);
  check_stmt_rc(rc, stmt1);

  FAIL_UNLESS(f1 == 1, "f1 != 1");
  FAIL_UNLESS(f2 == 1, "f2 != 1");
  FAIL_UNLESS(f3 == 2, "f3 != 2");
  rc= mysql_query(mysql, "drop table t1, t2");
  check_mysql_rc(rc, mysql);

  return OK;
}

/*
  Bug#13524: warnings of a previous command are not reset when fetching
  from a cursor.
*/

static int test_bug13524(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  int rc;
  unsigned int warning_count;
  const ulong type= CURSOR_TYPE_READ_ONLY;
  const char *query= "select * from t1";


  rc= mysql_query(mysql, "drop table if exists t1, t2");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "create table t1 (a int not null primary key)");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "insert into t1 values (1), (2), (3), (4)");
  check_mysql_rc(rc, mysql);

  stmt= mysql_stmt_init(mysql);
  rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type);
  check_stmt_rc(rc, stmt);

  rc= mysql_stmt_prepare(stmt, SL(query));
  check_stmt_rc(rc, stmt);

  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);

  rc= mysql_stmt_fetch(stmt);
  check_stmt_rc(rc, stmt);

  warning_count= mysql_warning_count(mysql);
  FAIL_UNLESS(warning_count == 0, "warning_count != 0");

  /* Check that DROP TABLE produced a warning (no such table) */
  rc= mysql_query(mysql, "drop table if exists t2");
  check_mysql_rc(rc, mysql);
  warning_count= mysql_warning_count(mysql);
  FAIL_UNLESS(warning_count == 1, "warning_count != 1");

  /*
    Check that fetch from a cursor cleared the warning from the previous
    command.
  */
  rc= mysql_stmt_fetch(stmt);
  check_stmt_rc(rc, stmt);
  warning_count= mysql_warning_count(mysql);
  FAIL_UNLESS(warning_count == 0, "warning_count != 0");

  /* Cleanup */
  mysql_stmt_close(stmt);
  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);

  return OK;
}

/*
  Bug#14845 "mysql_stmt_fetch returns MYSQL_NO_DATA when COUNT(*) is 0"
*/

static int test_bug14845(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  int rc;
  const ulong type= CURSOR_TYPE_READ_ONLY;
  const char *query= "select count(*) from t1 where 1 = 0";


  rc= mysql_query(mysql, "drop table if exists t1");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "create table t1 (id int(11) default null, "
                         "name varchar(20) default null)"
                         "engine=MyISAM DEFAULT CHARSET=utf8");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "insert into t1 values (1,'abc'),(2,'def')");
  check_mysql_rc(rc, mysql);

  stmt= mysql_stmt_init(mysql);
  rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type);
  check_stmt_rc(rc, stmt);

  rc= mysql_stmt_prepare(stmt, SL(query));
  check_stmt_rc(rc, stmt);

  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);

  rc= mysql_stmt_fetch(stmt);
  FAIL_UNLESS(rc == 0, "");

  rc= mysql_stmt_fetch(stmt);
  FAIL_UNLESS(rc == MYSQL_NO_DATA, "");

  /* Cleanup */
  mysql_stmt_close(stmt);
  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);
  return OK;
}

/*
  Bug#14210 "Simple query with > operator on large table gives server
  crash"
*/

static int test_bug14210(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  int rc, i;
  const char *stmt_text;
  ulong type;

  rc= mysql_query(mysql, "drop table if exists t1");
  check_mysql_rc(rc, mysql);
  /*
    To trigger the problem the table must be InnoDB, although the problem
    itself is not InnoDB related. In case the table is MyISAM this test
    is harmless.
  */
  rc= mysql_query(mysql, "create table t1 (a varchar(255)) engine=InnoDB");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "insert into t1 (a) values (repeat('a', 256))");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "set @@session.max_heap_table_size=16384");

  /* Create a big enough table (more than max_heap_table_size) */
  for (i= 0; i < 8; i++)
  {
    rc= mysql_query(mysql, "insert into t1 (a) select a from t1");
    check_mysql_rc(rc, mysql);
  }
  /* create statement */
  stmt= mysql_stmt_init(mysql);
  type= (ulong) CURSOR_TYPE_READ_ONLY;
  mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type);

  stmt_text= "select a from t1";

  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);
  rc= mysql_stmt_execute(stmt);
  while ((rc= mysql_stmt_fetch(stmt)) == 0);
  FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA");

  rc= mysql_stmt_close(stmt);

  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "set @@session.max_heap_table_size=default");
  check_mysql_rc(rc, mysql);

  return OK;
}

/*
  Bug#24179 "select b into $var" fails with --cursor_protocol"
  The failure is correct, check that the returned message is meaningful.
*/

static int test_bug24179(MYSQL *mysql)
{
  int rc;
  MYSQL_STMT *stmt;

  stmt= open_cursor(mysql, "select 1 into @a");
  rc= mysql_stmt_execute(stmt);
  FAIL_UNLESS(rc, "Error expected");
  FAIL_UNLESS(mysql_stmt_errno(stmt) == 1323, "stmt_errno != 1323");
  mysql_stmt_close(stmt);

  return OK;
}

/**
  Bug#32265 Server returns different metadata if prepared statement is used
*/

static int test_bug32265(MYSQL *mysql)
{
  int rc;
  MYSQL_STMT *stmt;
  MYSQL_FIELD *field;
  MYSQL_RES *metadata;

  if (mysql_get_server_version(mysql) < 50100) {
    diag("Test requires MySQL Server version 5.1 or above");
    return SKIP;
  }

  rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "DROP VIEW IF EXISTS v1");
  rc= mysql_query(mysql, "CREATE  TABLE t1 (a INTEGER)");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "INSERT INTO t1 VALUES (1)");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "CREATE VIEW v1 AS SELECT * FROM t1");
  check_mysql_rc(rc, mysql);

  stmt= open_cursor(mysql, "SELECT * FROM t1");
  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);
  metadata= mysql_stmt_result_metadata(stmt);
  field= mysql_fetch_field(metadata);
  FAIL_UNLESS(field, "couldn't fetch field");
  FAIL_UNLESS(strcmp(field->table, "t1") == 0, "table != t1");
  FAIL_UNLESS(strcmp(field->org_table, "t1") == 0, "org_table != t1");
  FAIL_UNLESS(strcmp(field->db, schema) == 0, "db != schema");
  mysql_free_result(metadata);
  mysql_stmt_close(stmt);

  stmt= open_cursor(mysql, "SELECT a '' FROM t1 ``");
  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);
  metadata= mysql_stmt_result_metadata(stmt);
  field= mysql_fetch_field(metadata);
  FAIL_UNLESS(strcmp(field->table, "") == 0, "field != ''");
  FAIL_UNLESS(strcmp(field->org_table, "t1") == 0, "org_table != t1");
  FAIL_UNLESS(strcmp(field->db, schema) == 0, "db != schema");
  mysql_free_result(metadata);
  mysql_stmt_close(stmt);

  stmt= open_cursor(mysql, "SELECT a '' FROM t1 ``");
  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);
  metadata= mysql_stmt_result_metadata(stmt);
  field= mysql_fetch_field(metadata);
  FAIL_UNLESS(strcmp(field->table, "") == 0, "table != ''");
  FAIL_UNLESS(strcmp(field->org_table, "t1") == 0, "org_table != t1");
  FAIL_UNLESS(strcmp(field->db, schema) == 0, "db != schema");
  mysql_free_result(metadata);
  mysql_stmt_close(stmt);

  stmt= open_cursor(mysql, "SELECT * FROM v1");
  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);
  metadata= mysql_stmt_result_metadata(stmt);
  field= mysql_fetch_field(metadata);
  FAIL_UNLESS(strcmp(field->table, "v1") == 0, "table != v1");
  FAIL_UNLESS(strcmp(field->org_table, "v1") == 0, "org_table != v1");
  FAIL_UNLESS(strcmp(field->db, schema) == 0, "db != schema");
  mysql_free_result(metadata);
  mysql_stmt_close(stmt);

  stmt= open_cursor(mysql, "SELECT * FROM v1 /* SIC */ GROUP BY 1");
  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);
  metadata= mysql_stmt_result_metadata(stmt);
  field= mysql_fetch_field(metadata);
  FAIL_UNLESS(strcmp(field->table, "v1") == 0, "table != v1");
  FAIL_UNLESS(strcmp(field->org_table, "v1") == 0, "org_table != v1");
  FAIL_UNLESS(strcmp(field->db, schema) == 0, "schema != db");
  mysql_free_result(metadata);
  mysql_stmt_close(stmt);

  rc= mysql_query(mysql, "DROP VIEW v1");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "DROP TABLE t1");
  check_mysql_rc(rc, mysql);

  return OK;
}

/**
  Bug#38486 Crash when using cursor protocol
*/

static int test_bug38486(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  const char *stmt_text;
  int rc;
  unsigned long type= CURSOR_TYPE_READ_ONLY;

  rc= mysql_query(mysql, "DROP TABLE IF EXISTS t10");
  check_mysql_rc(rc, mysql);

  rc= mysql_query(mysql, "CREATE TABLE t10 (a INT)");
  check_mysql_rc(rc, mysql);

  stmt= mysql_stmt_init(mysql);
  rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*)&type);
  check_stmt_rc(rc, stmt);
  stmt_text= "INSERT INTO t10 VALUES (1)";
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);
  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);
  rc= mysql_query(mysql, "DROP TABLE IF EXISTS t10");
  check_mysql_rc(rc, mysql);
  mysql_stmt_close(stmt);

  return OK;
}

static int test_bug8880(MYSQL *mysql)
{
  MYSQL_STMT *stmt_list[2], **stmt;
  MYSQL_STMT **stmt_list_end= (MYSQL_STMT**) stmt_list + 2;
  int rc;


  rc= mysql_query(mysql, "drop table if exists t1");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "create table t1 (a int not null primary key, b int)");
  check_mysql_rc(rc, mysql);
  rc= mysql_query(mysql, "insert into t1 values (1,1)");
  check_mysql_rc(rc, mysql);
  /*
    when inserting 2 rows everything works well
    mysql_query(mysql, "INSERT INTO t1 VALUES (1,1),(2,2)");
  */
  for (stmt= stmt_list; stmt < stmt_list_end; stmt++)
    *stmt= open_cursor(mysql, "select a from t1");
  for (stmt= stmt_list; stmt < stmt_list_end; stmt++)
  {
    rc= mysql_stmt_execute(*stmt);
    check_stmt_rc(rc, *stmt);
  }
  for (stmt= stmt_list; stmt < stmt_list_end; stmt++)
    mysql_stmt_close(*stmt);
  return OK;
}

static int test_bug9159(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  int rc;
  const char *stmt_text= "select a, b from t1";
  const unsigned long type= CURSOR_TYPE_READ_ONLY;


  mysql_query(mysql, "drop table if exists t1");
  mysql_query(mysql, "create table t1 (a int not null primary key, b int)");
  rc= mysql_query(mysql, "insert into t1 values (1,1)");
  check_mysql_rc(rc, mysql);

  stmt= mysql_stmt_init(mysql);
  mysql_stmt_prepare(stmt, SL(stmt_text));
  mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void *)&type);

  mysql_stmt_execute(stmt);
  mysql_stmt_close(stmt);
  rc= mysql_query(mysql, "drop table if exists t1");
  check_mysql_rc(rc, mysql);
  return OK;
}

/*
  We can't have more than one cursor open for a prepared statement.
  Test re-executions of a PS with cursor; mysql_stmt_reset must close
  the cursor attached to the statement, if there is one.
*/

static int test_bug9478(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[1];
  char a[6];
  ulong a_len;
  int rc, i;

  mysql_query(mysql, "drop table if exists t1");
  mysql_query(mysql, "create table t1 (id integer not null primary key, "
                     " name varchar(20) not null)");
  rc= mysql_query(mysql, "insert into t1 (id, name) values "
                         " (1, 'aaa'), (2, 'bbb'), (3, 'ccc')");
  check_mysql_rc(rc, mysql);

  stmt= open_cursor(mysql, "select name from t1 where id=2");

  memset(my_bind, '\0', sizeof(my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_STRING;
  my_bind[0].buffer= (char*) a;
  my_bind[0].buffer_length= sizeof(a);
  my_bind[0].length= &a_len;
  mysql_stmt_bind_result(stmt, my_bind);

  for (i= 0; i < 5; i++)
  {
    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);
    rc= mysql_stmt_fetch(stmt);
    check_stmt_rc(rc, stmt);

    /*
      The query above is a one-row result set. Therefore, there is no
      cursor associated with it, as the server won't bother with opening
      a cursor for a one-row result set. The first row was read from the
      server in the fetch above. But there is eof packet pending in the
      network. mysql_stmt_execute will flush the packet and successfully
      execute the statement.
    */

    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);

    rc= mysql_stmt_fetch(stmt);
    check_stmt_rc(rc, stmt);
    rc= mysql_stmt_fetch(stmt);
    FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA");

    {
      char buff[8];
      /* Fill in the fetch packet */
      int4store(buff, stmt->stmt_id);
      buff[4]= 1;                               /* prefetch rows */
/*      rc= ((*mysql->methods->advanced_command)(mysql, COM_STMT_FETCH,
                                               (uchar*) buff,
                                               sizeof(buff), 0,0,1,NULL) ||
           (*mysql->methods->read_query_result)(mysql)); */
      FAIL_UNLESS(rc, "error expected");
    }

    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);

    rc= mysql_stmt_fetch(stmt);
    check_stmt_rc(rc, stmt);

    rc= mysql_stmt_reset(stmt);
    check_stmt_rc(rc, stmt);
    rc= mysql_stmt_fetch(stmt);

    /* mariadb client supports GEOMETRY, so no error will
       be returned 
    FAIL_UNLESS(rc && mysql_stmt_errno(stmt), "Error expected");
    */
  }
  rc= mysql_stmt_close(stmt);
  check_stmt_rc(rc, stmt);

  /* Test the case with a server side cursor */
  stmt= open_cursor(mysql, "select name from t1");

  mysql_stmt_bind_result(stmt, my_bind);

  for (i= 0; i < 5; i++)
  {
    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);
    rc= mysql_stmt_fetch(stmt);
    check_stmt_rc(rc, stmt);
    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);

    while (! (rc= mysql_stmt_fetch(stmt)));
    FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA");

    rc= mysql_stmt_execute(stmt);
    check_stmt_rc(rc, stmt);

    rc= mysql_stmt_fetch(stmt);
    check_stmt_rc(rc, stmt);

    rc= mysql_stmt_reset(stmt);
    check_stmt_rc(rc, stmt);
    rc= mysql_stmt_fetch(stmt);
    FAIL_UNLESS(rc && mysql_stmt_errno(stmt), "Error expected");
  }

  rc= mysql_stmt_close(stmt);
  check_stmt_rc(rc, stmt);

  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);
  return OK;
}

/* Crash when opening a cursor to a query with DISTICNT and no key */

static int test_bug9520(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[1];
  char a[6];
  ulong a_len;
  int rc, row_count= 0;


  mysql_query(mysql, "drop table if exists t1");
  mysql_query(mysql, "create table t1 (a char(5), b char(5), c char(5),"
                     " primary key (a, b, c))");
  rc= mysql_query(mysql, "insert into t1 values ('x', 'y', 'z'), "
                  " ('a', 'b', 'c'), ('k', 'l', 'm')");
  check_mysql_rc(rc, mysql);

  stmt= open_cursor(mysql, "select distinct b from t1");

  /*
    Not crashes with:
    stmt= open_cursor(mysql, "select distinct a from t1");
  */

  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);

  memset(my_bind, '\0', sizeof(my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_STRING;
  my_bind[0].buffer= (char*) a;
  my_bind[0].buffer_length= sizeof(a);
  my_bind[0].length= &a_len;

  mysql_stmt_bind_result(stmt, my_bind);

  while (!(rc= mysql_stmt_fetch(stmt)))
    row_count++;

  FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA");

  FAIL_UNLESS(row_count == 3, "row_count != 3");

  mysql_stmt_close(stmt);

  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);
  return OK;
}

/*
  Error message is returned for unsupported features.
  Test also cursors with non-default PREFETCH_ROWS
*/

static int test_bug9643(MYSQL *mysql)
{
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[1];
  int32 a;
  int rc;
  const char *stmt_text;
  int num_rows= 0;
  ulong type;
  ulong prefetch_rows= 5;


  mysql_query(mysql, "drop table if exists t1");
  mysql_query(mysql, "create table t1 (id integer not null primary key)");
  rc= mysql_query(mysql, "insert into t1 (id) values "
                         " (1), (2), (3), (4), (5), (6), (7), (8), (9)");
  check_mysql_rc(rc, mysql);

  stmt= mysql_stmt_init(mysql);
  /* Not implemented in 5.0 */
  type= (ulong) CURSOR_TYPE_SCROLLABLE;
  rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type);
  FAIL_UNLESS(rc, "Error expected");

  type= (ulong) CURSOR_TYPE_READ_ONLY;
  rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type);
  check_stmt_rc(rc, stmt);
  rc= mysql_stmt_attr_set(stmt, STMT_ATTR_PREFETCH_ROWS,
                          (void*) &prefetch_rows);
  check_stmt_rc(rc, stmt);
  stmt_text= "select * from t1";
  rc= mysql_stmt_prepare(stmt, SL(stmt_text));
  check_stmt_rc(rc, stmt);

  memset(my_bind, '\0', sizeof(my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_LONG;
  my_bind[0].buffer= (void*) &a;
  my_bind[0].buffer_length= sizeof(a);
  mysql_stmt_bind_result(stmt, my_bind);

  rc= mysql_stmt_execute(stmt);
  check_stmt_rc(rc, stmt);

  while ((rc= mysql_stmt_fetch(stmt)) == 0)
    ++num_rows;
  FAIL_UNLESS(num_rows == 9, "num_rows != 9");

  rc= mysql_stmt_close(stmt);
  FAIL_UNLESS(rc == 0, "");

  rc= mysql_query(mysql, "drop table t1");
  check_mysql_rc(rc, mysql);
  return OK;
}


struct my_tests_st my_tests[] = {
  {"test_basic_cursors", test_basic_cursors, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_cursors_with_union", test_cursors_with_union, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_cursors_with_procedure", test_cursors_with_procedure, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug21206", test_bug21206, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug10729", test_bug10729, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, 
  {"test_bug10736", test_bug10736, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug10794", test_bug10794, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug10760", test_bug10760, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug11172", test_bug11172, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug11656", test_bug11656, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug11901", test_bug11901, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug11904", test_bug11904, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug12243", test_bug12243, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug11909", test_bug11909, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug13488", test_bug13488, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug13524", test_bug13524, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug14845", test_bug14845, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug14210", test_bug14210, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug24179", test_bug24179, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug32265", test_bug32265, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug38486", test_bug38486, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug8880", test_bug8880, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug9159", test_bug9159, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug9478", test_bug9478, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug9520", test_bug9520, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {"test_bug9643", test_bug9643, TEST_CONNECTION_DEFAULT, 0, NULL , NULL},
  {NULL, NULL, 0, 0, NULL, NULL}
};

int main(int argc, char **argv)
{
  if (argc > 1)
    get_options(argc, argv);

  get_envvars();

  run_tests(my_tests);

  return(exit_status());
}
