/**
 * Ruby/mSQL is a mSQL library
 *
 * @author DATE Ken (as Itacchi) / ge6537@i.bekkoame.ne.jp
 * @code-name Tomoko
 * @see http://www.isc.meiji.ac.jp/~ee77038/ruby/
 * @todo convert method that between Ruby's Time and mSQL's TIME.
 * $Id: msql.c 1.5 2000/12/01 16:17:23 date Exp date $
 */

#include "ruby_msql.h"

#define FIX2BOOL(val) if(FIX2INT(val)==0){val=Qfalse;}else{val=Qtrue;}
#define CHECK_CONNECTION(msql) if(msql->sock==-1){rb_raise(eMsql,"no connection");}

VALUE cMsql;
VALUE cMsqlResult;
VALUE cMsqlField;
VALUE cMsqlSequence;
VALUE eMsql;



/*
 * --------
 *   Msql
 * --------
 */
/*
=begin
= (({Msql}))
mSQL client class.

=== constant

--- Msql::CONFIG_GROUP_GENERAL
    For ((<Msql.get_config>)).

--- Msql::CONFIG_GROUP_SYSTEM
    For ((<Msql.get_config>)).

--- Msql::CONFIG_GROUP_W3MSQL
    For ((<Msql.get_config>)).

--- Msql::INST_DIR
    mSQL installed directory.

--- Msql::SERVER_VERSION
    mSQL server version.

--- Msql::TARGET
    mSQL target platform.

--- Msql::VERSION
    Ruby/mSQL versions.


=== class method

--- Msql.connect([host], [database_name])
    Try to connect mSQL server specified the ((|host|))
    and select database specified the ((|database_name|)).
    If you omit ((|host|)) or specified the ((|nil|)) as ((|host|)),
    then connect local mSQL server.

    ((*ex.*))

    Msql.connect 'sublime', 'artists' #=>
    connect server on host 'sublime' and select database 'artists'.

    Msql.connect nil, 'singles' #=>
    connect local server and select database 'singles'.

    Msql.connect 'frogman' #=> connect server on host 'frogman'.

    Msql.connect #=> connect local server.
    :Returns:
      *Msql: mSQL client.
    :Exceptions:
      *TypeError: ((|host|)) and/or ((|database_name|)) aren't String.
      *MsqlError: connection failed.

--- Msql.get_config(group)
    Get config values from loaded config file.
    ((|group|)) only accepts below
    *((<Msql::CONFIG_GROUP_GENERAL>)) --- "general"
    *((<Msql::CONFIG_GROUP_SYSTEM>))  --- "system"
    *((<Msql::CONFIG_GROUP_W3MSQL>))  --- "w3-msql"
    :Returns:
      *Hash: config values of specified group.
      *nil : unkown group.

--- Msql.load_config_file([conf_file])
    Load config file specified the ((|conf_file|)).
    :Exceptions:
      *TypeError: ((|conf_file|)) isn't String.
      *MsqlError: loading failed.


=== instance method

--- Msql#close
    Close the connection with the connected server.
    :Exceptions:
      *MsqlError: closing failed.

--- Msql#connect([host], [database_name])
    See ((<Msql.connect>)).
    :Exceptions:
      *TypeError: ((|host|)) and/or ((|database_name|)) aren't String.
      *MsqlError: already connected or connection failed.


--- Msql#copy_db(src_db, dest_db)
    Copy a database from the ((|src_db|)) to the ((|dest_db|)).
    ((*This is administrator operation*)).
    :Exceptions:
      *TypeError: ((|src_db|)) and/or ((|dest_db|)) aren't String.
      *MsqlError: copying failed.

--- Msql#create_db(db_name)
    Create new database named ((|db_name|)).
    ((*This is administrator operation*)).
    :Exceptions:
      *TypeError: ((|db_name|)) isn't String.
      *MsqlError: creating failed.

--- Msql#drop_db(db_name)
    Delete database named ((|db_name|)) completely.
    ((*This is administrator operation*)).
    :Exceptions:
      *TypeError: ((|db_name|)) isn't String.
      *MsqlError: droping failed.

--- Msql#database
    Get selected database name (same as specified in ((<Msql.connect>)) or
    ((<Msql#connect>)) or ((<Msql#select_db>))).
    :Returns:
      *String: database name.

--- Msql#get_host
    Get connected host name (same as specified in ((<Msql.connect>)) or ((<Msql#connect>))).
    :Returns:
      *String: host name.

--- Msql#get_host_info
    Get connected host name and connection protocol name.
    :Returns:
      *String: host name and protocol name.

--- Msql#get_sequence_info
    Get information of sequence.
    :Returns:
      *MsqlSequence: sequence info.
      *nil: no sequence exists.

--- Msql#get_server_stats
    Get states of the connected server (for console ouput).
    Output config file name, max connections, current connections, server running as user and connection table.

--- Msql#get_server_info
    Get version of the connected mSQL server.
    :Returns:
      *String: version number.

--- Msql#list_dbs
    Get all database names on the connected server.
    :Returns:
      *MsqlResult: database names.
    :Exceptions:
      *MsqlError: failed.

--- Msql#list_fields(table_name)
    Get all filed names on the table specified the ((|table_name|)).
    :Returns:
       *MsqlResult: field names.
    :Exceptions:
      *TypeError: ((|table_name|)) isn't String.
      *MsqlError: failed.

--- Msql#list_index(table_name, index)
    Get information of the ((|index|)) on the table specified the ((|table_name|)).
    :Returns:
      *MsqlResult: type of the ((|index|)) and filed names included by the ((|index|)).
    :Exceptions:
      *TypeError: ((|table_name|)) and/or ((|index|)) aren't String.
      *MsqlError: failed.

--- Msql#list_tables
    Get all table names on the connected server.
    :Returns:
      *MsqlResult: table names.
    :Exceptions:
      *MsqlError: failed.

--- Msql#move_db(src_db, dest_db)
    Move a database from the ((|src_db|)) to the ((|dest_db|)).
    ((*This is administrator operation*)).
    :Exceptions:
      *TypeError: ((|src_db|)) and ((|dest_db|)) aren't String.
      *MsqlError: moving failed.

--- Msql#query(query)
    Execute ((|query|)).
    ((*Must do ((<Msql#select_db>)) before this operaion*)).
    :Returns:
      *Integer: number of records effected by the ((|query|)).
    :Exceptions:
      *MsqlError: ((|query|)) failed.

--- Msql#reload_acls
    Reload ACL(Access Control List) file.
    ((*This is administrator operation*)).
    :Exceptions:
      *MsqlError: reloading failed.

--- Msql#select_db(db_name)
    Select database on the connected server.
    ((*Must do this before ((<Msql#query>))*)).
    :Exceptions:
      *MsqlError: selection failed.

--- Msql#shutdown
    Shutdown connected server.
    ((*This is administrator operation*)).
    :Exceptions:
      *MsqlError: shutdowning failed.

--- Msql#store_result
--- Msql#get_result
    Get query results (SELECT statement).
    ((*Must do ((<Msql#query>)) before this operaion*)).
    ((*And must do this operaion before execute next ((<Msql#query>))*)).
    :Returns:
      *MsqlResult: query results.
      *nil: no results.
    :Exceptions:
      *MsqlError: failed.
=end
*/


/*
 * --------------
 *   MsqlResult
 * --------------
 */
/*
=begin
= (({MsqlResult}))
mSQL query result class.

=== class method

    No class method.


=== instance method

--- MsqlResult#data_seek(pos)
    Set cursor at ((|pos|)). 
    ((<MsqlResult#fetch_row>)) fetch a row at a position of the cursor.

--- MsqlResult#each_field {|field|...}
    Iterated access to each filed.
    (notice: start at cursor position)

--- MsqlResult#each_field_with_index {|field, index|...}
    Iterated access to each filed with index number.
    (notice: start at cursor position)

--- MsqlResult#each_row {|row|...}
    Iterated access to each row.
    (notice: start at cursor position)

--- MsqlResult#each_row_with_index {|row, index|...}
    Iterated access to each row with index number.
    (notice: start at cursor position)

--- MsqlResult#fetch_all_fields
    Get all fields.
    (notice: range of fields is from the cursor position to the end)
    :Returns:
      *Array: array of fields.

--- MsqlResult#fetch_all_rows
    Get all rows.
    (notice: range of rows is from the cursor position to the end)
    :Returns:
      *Array: array of rows.

--- MsqlResult#fetch_field
    Fetch one field from the result.
    :Returns:
      *MsqlField: information of the field.
         ex. As if a MsqlResult instance has a result such as
         +---------------------+-----------+
         | name                | genre     |
         +---------------------+-----------+
         | Anthony & Cleopatra | Tragedies |
         | Corilanus           | Tragedies |
         | Hamlet              | Tragedies |
         | Julius Caesar       | Tragedies |
         | King Lear           | Tragedies |
         +---------------------+-----------+
         then fetch_field returns field object of 'name'
         and the cursor move to the next 'genre' field
         (next fetch_field return this field).
      *nil: no more fileds.

--- MsqlResult#fetch_hash
    Fetch one row as hash from the result.
    :Returns:
      *Hash: key is field name and value is element.
         ex. As if a MsqlResult instance has a result such as
         +---------------------+-----------+
         | name                | genre     |
         +---------------------+-----------+
         | Anthony & Cleopatra | Tragedies |
         | Corilanus           | Tragedies |
         | Hamlet              | Tragedies |
         | Julius Caesar       | Tragedies |
         | King Lear           | Tragedies |
         +---------------------+-----------+
         then fetch_hash returns
         {"name"=>"Anthony & Cleopatra", "genre"=>"Tragedies"}
         and the cursor move to the next row.
      *nil: no more rows.

--- MsqlResult#fetch_row
    Fetch one row from the result.
    :Returns:
       *Array: array of (String) elements of each fields.
         ex. As if a MsqlResult instance has a result such as
         +---------------------+-----------+
         | name                | genre     |
         +---------------------+-----------+
         | Anthony & Cleopatra | Tragedies |
         | Corilanus           | Tragedies |
         | Hamlet              | Tragedies |
         | Julius Caesar       | Tragedies |
         | King Lear           | Tragedies |
         +---------------------+-----------+
         then fetch_row returns
         ["Anthony & Cleopatra", "Tragedies"]
         and the cursor move to the next row:
         ["Corilanus", "Tragedies"](next fetch_row return this row).
       *nil: no more rows.

--- MsqlResult#field_seek(pos)
    Set cursor at ((|pos|)). 
    ((<MsqlResult#fetch_field>)) fetch a field at a position of the cursor.

--- MsqlResult#num_fields
    Get number of fields of the results.
    :Returns:
      *Integer: number of fields.

--- MsqlResult#num_rows
    Get number of rows of the results.
    :Returns:
      *Integer: number of rows.
=end
*/


/*
 * -------------
 *   MsqlField
 * -------------
 */
/*
=begin
= (({MsqlField}))
mSQL field has a information of column.

=== constant

--- MsqlField::INT_TYPE
    Field data type: integer.

--- MsqlField::CHAR_TYPE
    Field data type: charactor.

--- MsqlField::REAL_TYPE
    Field data type: real number.

--- MsqlField::IDENT_TYPE
    Field data type: identify.

--- MsqlField::NULL_TYPE
    Field data type: null.

--- MsqlField::TEXT_TYPE
    Field data type: text.

--- MsqlField::DATE_TYPE
    Field data type: date.

--- MsqlField::UINT_TYPE
    Field data type: unsigned integer.

--- MsqlField::MONEY_TYPE
    Field data type: money.

--- MsqlField::LAST_REAL_TYPE
    Field data type: last real number.

--- MsqlField::IDX_TYPE
    Field data type: index.

--- MsqlField::SYSVAR_TYPE
    Field data type: system variable.

--- MsqlField::ANY_TYPE
    Field data type: any.


=== class method

    No class method.


=== instance method

--- MsqlField#name
    Get field name.
    :Returns:
      *String: field name.

--- MsqlField#table
    Get table name.
    :Returns:
      *String: table name.

--- MsqlField#type
    Get filed type.
    :Returns:
      *Integer: filed type.
        *((<MsqlField::INT_TYPE>))
        *((<MsqlField::CHAR_TYPE>))
        *((<MsqlField::REAL_TYPE>))
        *((<MsqlField::IDENT_TYPE>))
        *((<MsqlField::NULL_TYPE>))
        *((<MsqlField::TEXT_TYPE>))
        *((<MsqlField::DATE_TYPE>))
        *((<MsqlField::UINT_TYPE>))
        *((<MsqlField::MONEY_TYPE>))
        *((<MsqlField::LAST_REAL_TYPE>))
        *((<MsqlField::IDX_TYPE>))
        *((<MsqlField::SYSVAR_TYPE>))
        *((<MsqlField::ANY_TYPE>))

--- MsqlField#length
    Get filed length.
    :Returns:
      *Integer: filed length.

--- MsqlField#flags
    Get field flags.
    Usually, access with ((<MsqlField#is_not_null>)) or ((<MsqlField#is_unique>)).
    :Returns:
      *Integer: field flags.

--- MsqlField#is_not_null
--- MsqlField#is_not_null?
    Return true if field defined as NOT NULL.
    :Returns:
      *true/false: field is 'NOT NULL' or not.

--- MsqlField#is_unique
--- MsqlField#is_unique?
    Return true if field is unique.
    Returns:
      *true/false: field is unique or not.

=end
*/

/*
 * ----------------
 *   MsqlSequence
 * ----------------
 */
/*
=begin
= (({MsqlSequence}))
Sequence information.

=== class method

    No class method.


=== instance method

--- MsqlSequence#step
    Get sequence step.
    :Returns:
      *Integer: sequence step.

--- MsqlSequence#value
    Get sequence value.
    :Returns:
      *Integer: sequence value.

=end
*/


/*
 * +------+
 * | Msql |
 * +------+
 */

static void
msql_free(msql)
  msql_object *msql;
{
  if (msql->sock != -1)
    msqlClose(msql->sock);
  free(msql);
}



/*
 * ---------------
 * class method of
 *      Msql
 * ---------------
 */

static VALUE
msql_connect(argc, argv, klass)
  int argc;
  VALUE *argv, klass;
{
  VALUE new_obj;
  msql_object *msql;

  new_obj = Data_Make_Struct(klass, msql_object, 0, msql_free, msql);

  msql->sock = -1;

  msql_connect2(argc, argv, new_obj);

  return new_obj;
}



/* private function for msql_get_(general|system|w3-msql)_conf */
static VALUE
msql_get_char_conf(group, key)
  char *group, *key;
{
  return rb_str_new2((char *)msqlGetCharConf(group, key));
}



/* private function for msql_get_(general|system|w3-msql)_conf */
static VALUE
msql_get_int_conf(group, key)
  char *group, *key;
{
  return INT2FIX(msqlGetIntConf(group, key));
}



/* private function for msql_get_conf */
static VALUE
msql_get_general_conf(void)
{
  VALUE hash;
  VALUE val;

  hash = rb_eval_string("Hash.new");

  val = msql_get_char_conf("general", "inst_dir");
  rb_funcall(hash, rb_intern("[]="),2, rb_str_new2("Inst_Dir"),    val);

/* don't exist in ver.2.0.4
  val = msql_get_char_conf("general", "db_dir");
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("DB_Dir"),     val);
*/

  val = msql_get_char_conf("general", "msql_user");
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("mSQL_User"),  val);

  val = msql_get_char_conf("general", "admin_user");
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Admin_User"), val);

  val = msql_get_char_conf("general", "pid_file");
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Pid_File"),   val);

  val = msql_get_int_conf("general", "tcp_port");
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("TCP_Port"),   val);

  val = msql_get_char_conf("general", "unix_port");
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("UNIX_Port"),  val);

  return hash;
}



/* private function for msql_get_conf */
static VALUE
msql_get_system_conf(void)
{
  VALUE hash;
  VALUE val;

  hash = rb_eval_string("Hash.new");

  val = msql_get_int_conf("system", "msync_timer");
  rb_funcall(hash, rb_intern("[]="),2, rb_str_new2("Msync_Timer"),     val);

  val = msql_get_int_conf("system", "host_lookup");
  FIX2BOOL(val);
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Host_Lookup"),    val);

  val = msql_get_int_conf("system", "read_only");
  FIX2BOOL(val);
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Read_Only"),      val);

  val = msql_get_int_conf("system", "remote_access");
  FIX2BOOL(val);
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Remote_Access"),  val);

  val = msql_get_int_conf("system", "local_access");
  FIX2BOOL(val);
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("local_access"),   val);

/* don't exist in ver.2.0.4
  val = msql_get_int_conf("system", "query_log");
  FIX2BOOL(val);
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Query_Log"),      val);

  val = msql_get_char_conf("system", "query_log_file");
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Query_Log_File"), val);
*/

  return hash;
}



/* private function for msql_get_conf */
static VALUE
msql_get_w3msql_conf(void)
{
  VALUE hash;
  VALUE val;

  hash = rb_eval_string("Hash.new");

/*
  val = msql_get_char_conf("w3-msql", "auth_host");
  rb_funcall(hash, rb_intern("[]="),2, rb_str_new2("Auth_Host"),      val);
*/

  val = msql_get_int_conf("w3-msql", "footer");
  FIX2BOOL(val);
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Footer"),        val);

  val = msql_get_int_conf("w3-msql", "force_private");
  FIX2BOOL(val);
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Force_Private"), val);

/*
  val = msql_get_char_conf("w3-msql", "force_suffix");
  rb_funcall(hash, rb_intern("[]="), 2, rb_str_new2("Force_Suffix"),  val);
*/

  return hash;
}



static VALUE
msql_get_conf(klass, _group)
  VALUE klass, _group;
{
  char *group;

  Check_Type(_group, T_STRING);

  group = STR2CSTR(_group);

  if (strcmp(group, "general") == 0)
    return msql_get_general_conf();

  if (strcmp(group, "system")  == 0)
    return msql_get_system_conf();

  if (strcmp(group, "w3-msql") == 0)
    return msql_get_w3msql_conf();

  return Qnil;
}



static VALUE
msql_load_config_file(klass, conf_file)
  VALUE klass, conf_file;
{
  int retcode;

  Check_Type(conf_file, T_STRING);

  retcode = msqlLoadConfigFile(STR2CSTR(conf_file));

  if (retcode < 0)
    rb_raise(eMsql, "%s", msqlErrMsg);

  return Qnil;
}



static VALUE
msql_new(klass)
  VALUE klass;
{
  VALUE new_obj;
  msql_object *msql;

  new_obj = Data_Make_Struct(klass, msql_object, 0, msql_free, msql);

  msql->sock = -1;

  rb_iv_set(new_obj, "database", rb_str_new2(""));
  rb_iv_set(new_obj, "host",     rb_str_new2(""));

  return new_obj;
}



/*
 * ------------------
 * instance method of
 *        Msql
 * ------------------
 */

static VALUE
msql_initialize(obj)
  VALUE obj;
{
  return Qnil;
}



static VALUE
msql_close(obj)
  VALUE obj;
{
  msql_object *msql;

  Data_Get_Struct(obj, msql_object, msql);

  if (msql->sock != -1)
    msqlClose(msql->sock);

  return Qnil;
}



static VALUE
msql_connect2(argc, argv, obj)
  int argc;
  VALUE *argv, obj;
{
  VALUE _host, db_name;
  msql_object *msql;
  char *host;
  int retcode;

  Data_Get_Struct(obj, msql_object, msql);

  if(msql->sock != -1)
    rb_raise(eMsql, "already connected");

  rb_scan_args(argc, argv, "02", &_host, &db_name);

  /* get host name */
  switch (TYPE(_host)){
    /* if NULL specified, connect local server */ 
    case T_NIL:
      host = NULL;
      break;

    case T_STRING:
      host = STR2CSTR(_host);
      break;

    default:
      rb_raise(rb_eTypeError, "wrong argument type %s (expected String/nil)",
               rb_class2name(CLASS_OF(_host)));
  }

  msql->sock = msqlConnect(host);
  rb_iv_set(obj, "host", rb_str_new2(host ? host : "localhost"));

  if(msql->sock == -1)
    rb_raise(eMsql, "connect error");

  /* select database */
  switch (TYPE(db_name)){
    case T_NIL:
      break;

    case T_STRING:
      retcode = msqlSelectDB(msql->sock, STR2CSTR(db_name));
      if (retcode < 0)
        rb_raise(eMsql, "%s", msqlErrMsg);
      rb_iv_set(obj, "database", db_name);
      break;

    default:
      rb_raise(rb_eTypeError, "wrong argument type %s (expected String)",
               rb_class2name(CLASS_OF(db_name)));
  }

  return obj;
}



static VALUE
msql_copy_db(obj, src_db, dest_db)
  VALUE obj, src_db, dest_db;
{
  msql_object *msql;
  int retcode;

  Check_Type(src_db,  T_STRING);
  Check_Type(dest_db, T_STRING);

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  retcode = msqlCopyDB(msql->sock, STR2CSTR(src_db), STR2CSTR(dest_db));

  if (retcode < 0)
    rb_raise(eMsql, "%s", msqlErrMsg);

  return obj;
}



static VALUE
msql_create_db(obj, db_name)
  VALUE obj, db_name;
{
  msql_object *msql;
  int retcode;

  Check_Type(db_name, T_STRING);

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  retcode = msqlCreateDB(msql->sock, STR2CSTR(db_name));

  if (retcode < 0)
    rb_raise(eMsql, "%s", msqlErrMsg);

  return obj;
}



static VALUE
msql_drop_db(obj, db_name)
  VALUE obj, db_name;
{
  msql_object *msql;
  int retcode;

  Check_Type(db_name, T_STRING);

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  retcode = msqlDropDB(msql->sock, STR2CSTR(db_name));

  if (retcode < 0)
    rb_raise(eMsql, "%s", msqlErrMsg);

  return obj;
}



static VALUE
msql_get_database(obj)
{
  return rb_iv_get(obj, "database");
}



static VALUE
msql_get_host(obj)
{
  return rb_iv_get(obj, "host");
}



static VALUE
msql_get_host_info(obj)
  VALUE obj;
{
    return rb_str_new2(msqlGetHostInfo());
}



static VALUE
msql_get_protocol_info(obj)
  VALUE obj;
{
    return INT2FIX(msqlGetProtoInfo());
}



static VALUE
msql_get_sequence_info(obj, table_name)
  VALUE obj, table_name;
{
  msql_object *msql;
  m_seq *seq;

  Check_Type(table_name, T_STRING);

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  seq = msqlGetSequenceInfo(msql->sock, STR2CSTR(table_name));

  if (seq == NULL)
    return Qnil;

  return msql_seq_create(seq);
}



static VALUE
msql_get_server_info(obj)
  VALUE obj;
{
  return rb_str_new2(msqlGetServerInfo());
}



static VALUE
msql_get_server_stats(obj)
  VALUE obj;
{
  msql_object *msql;

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  return INT2FIX(msqlGetServerStats(msql->sock));
}



static VALUE
msql_inspect(obj)
  VALUE obj;
{
  msql_object *msql;
  VALUE str;
  char buf[256];

  sprintf(buf, "#<Msql:%s>", msqlGetHostInfo());
  str = rb_str_new2(buf);

  return str;
}



static VALUE
msql_list_dbs(obj)
  VALUE obj;
{
  msql_object *msql;
  m_result *res;
  VALUE result;

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  res = msqlListDBs(msql->sock);

  if (res == NULL)
    rb_raise(eMsql, "%s", msqlErrMsg);

  result = msql_res_create(cMsqlResult, res);

  return result;
}



static VALUE
msql_list_fields(obj, table_name)
  VALUE obj, table_name;
{
  msql_object *msql;
  m_result *res;
  VALUE result;

  Check_Type(table_name, T_STRING);

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  res = msqlListFields(msql->sock, STR2CSTR(table_name));

  if (res == NULL)
    rb_raise(eMsql, "%s", msqlErrMsg);

  result = msql_res_create(cMsqlResult, res);

  return result;
}



static VALUE
msql_list_index(obj, table_name, index)
  VALUE obj, table_name, index;
{
  msql_object *msql;
  m_result *res;
  VALUE result;

  Check_Type(table_name, T_STRING);
  Check_Type(index, T_STRING);

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  res = msqlListIndex(msql->sock, STR2CSTR(table_name), STR2CSTR(index));

  if (res == NULL)
      rb_raise(eMsql, "%s", msqlErrMsg);

  result = msql_res_create(cMsqlResult, res);

  return result;
}



static VALUE
msql_list_tables(obj)
  VALUE obj;
{
  msql_object *msql;
  m_result *res;
  VALUE result;

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  res = msqlListTables(msql->sock);

  if (res == NULL)
    rb_raise(eMsql, "%s", msqlErrMsg);

  result = msql_res_create(cMsqlResult, res);

  return result;
}



static VALUE
msql_move_db(obj, src_db, dest_db)
  VALUE obj, src_db, dest_db;
{
  msql_object *msql;
  int retcode;

  Check_Type(src_db,  T_STRING);
  Check_Type(dest_db, T_STRING);

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  retcode = msqlMoveDB(msql->sock, STR2CSTR(src_db), STR2CSTR(dest_db));

  if (retcode < 0)
    rb_raise(eMsql, "%s", msqlErrMsg);

  return obj;
}



static VALUE
msql_query(obj, query)
  VALUE obj, query;
{
  msql_object *msql;
  int retcode;

  Check_Type(query, T_STRING);

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  retcode = msqlQuery(msql->sock, STR2CSTR(query));

  if (retcode < 0)
    rb_raise(eMsql, "%s", msqlErrMsg);

  return INT2FIX(retcode);
}



static VALUE
msql_reload_acls(obj)
  VALUE obj;
{
  msql_object *msql;
  int retcode;

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  retcode = msqlReloadAcls(msql->sock);

  if (retcode < 0)
    rb_raise(eMsql, "%s", msqlErrMsg);

  return obj;
}



static VALUE
msql_select_db(obj, db_name)
  VALUE obj, db_name;
{
  msql_object *msql;
  int retcode;

  Check_Type(db_name, T_STRING);

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  retcode = msqlSelectDB(msql->sock, STR2CSTR(db_name));

  if (retcode < 0)
    rb_raise(eMsql, "%s", msqlErrMsg);

  return obj;
}



static VALUE
msql_shutdown(obj)
  VALUE obj;
{
  msql_object *msql;
  int retcode;

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  retcode = msqlShutdown(msql->sock);

  if (retcode < 0)
    rb_raise(eMsql, "%s", msqlErrMsg);

  return obj;
}



static VALUE
msql_store_result(obj)
  VALUE obj;
{
  msql_object *msql;
  m_result *res;

  Data_Get_Struct(obj, msql_object, msql);

  CHECK_CONNECTION(msql);

  res = msqlStoreResult();

  if (res == NULL)
    return Qnil;

  return msql_res_create(cMsqlResult, res);
}




/*
 * +------------+
 * | MsqlResult |
 * +------------+
 */

static void
msql_res_free(msql_res)
  msql_res_object *msql_res;
{
  msqlFreeResult(msql_res->result);
  free(msql_res);
}



static VALUE
msql_res_create(klass, result)
  VALUE klass;
  m_result *result;
{
  VALUE new_obj;
  msql_res_object *msql_res;

  new_obj = Data_Make_Struct(klass, msql_res_object, NULL, msql_res_free, msql_res);

  msql_res->result = result;

  return new_obj;
}



/*
 * ------------------
 * instance method of
 *     MsqlResult
 * ------------------
 */

static VALUE
msql_res_initialize(obj)
  VALUE obj;
{
  return Qnil;
}



static VALUE
msql_res_data_seek(obj, pos)
  VALUE obj, pos;
{
  msql_res_object *msql_res;

  FIXNUM_P(pos);

  Data_Get_Struct(obj, msql_res_object, msql_res);

  msqlDataSeek(msql_res->result, FIX2INT(pos));

  return Qnil;
}



static VALUE
msql_res_each_field(obj)
     VALUE obj;
{
  msql_res_object *msql_res;
  VALUE field;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  while ((field = msql_res_fetch_field(obj)) != Qnil)
    rb_yield(field);

  return obj;
}



static VALUE
msql_res_each_field_with_index(obj)
     VALUE obj;
{
  msql_res_object *msql_res;
  VALUE ary, field;
  int index;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  index = 0;

  while ((field = msql_res_fetch_field(obj)) != Qnil){
    ary = rb_ary_new();

    rb_ary_push(ary, field);
    rb_ary_push(ary, INT2FIX(index));
    rb_yield(ary);

    index++;
  }

  return obj;
}



static VALUE
msql_res_each_row(obj)
     VALUE obj;
{
  msql_res_object *msql_res;
  VALUE row;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  while ((row = msql_res_fetch_row(obj)) != Qnil)
    rb_yield(row);

  return obj;
}



static VALUE
msql_res_each_row_with_index(obj)
     VALUE obj;
{
  msql_res_object *msql_res;
  VALUE ary, row;
  int index;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  index = 0;

  while ((row = msql_res_fetch_row(obj)) != Qnil){
    ary = rb_ary_new();

    rb_ary_push(ary, row);
    rb_ary_push(ary, INT2FIX(index));
    rb_yield(ary);

    index++;
  }

  return obj;
}



static VALUE
msql_res_fetch_all_fields(obj)
  VALUE obj;
{
  msql_res_object *msql_res;
  VALUE ary, field;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  ary = rb_ary_new();

  while ((field = msql_res_fetch_field(obj)) != Qnil)
    rb_ary_push(ary, field);

  return ary;
}



static VALUE
msql_res_fetch_all_rows(obj)
  VALUE obj;
{
  msql_res_object *msql_res;
  VALUE ary, row;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  ary = rb_ary_new();

  while ((row = msql_res_fetch_row(obj)) != Qnil)
    rb_ary_push(ary, row);

  return ary;
}



static VALUE
msql_res_fetch_field(obj)
  VALUE obj;
{
  msql_res_object *msql_res;
  m_field *field;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  field = msqlFetchField(msql_res->result);

  if (field == NULL)
    return Qnil;

  return msql_field_create(field);
}



static VALUE
msql_res_fetch_hash(obj)
  VALUE obj;
{
  msql_res_object *msql_res;
  m_row _row;
  VALUE row, fields, hash;
  int num_fields, i;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  _row = msqlFetchRow(msql_res->result);

  if (_row == NULL)
    return Qnil;

  fields = msql_res_fetch_all_fields(obj);
  hash   = rb_eval_string("Hash.new");

  num_fields = msqlNumFields(msql_res->result);

  for (i = 0; i < num_fields; i++){
    rb_funcall(hash, rb_intern("[]="), 2,
         rb_iv_get(RARRAY(fields)->ptr[i], "name"),
         rb_str_new2(_row[i]));
  }

  return hash;
}



static VALUE
msql_res_fetch_row(obj)
  VALUE obj;
{
  msql_res_object *msql_res;
  m_row _row;
  VALUE row;
  int num_fields, i;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  _row = msqlFetchRow(msql_res->result);

  if (!_row)
    return Qnil;

  row = rb_ary_new();

  num_fields = msqlNumFields(msql_res->result);

  for (i=0; i < num_fields; i++){
    if (!_row[i])
      rb_ary_push(row, Qnil);
    else
      rb_ary_push(row, rb_str_new2(_row[i]));
  }

  return row;
}



static VALUE
msql_res_field_seek(obj, pos)
  VALUE obj, pos;
{
  msql_res_object *msql_res;

  FIXNUM_P(pos);

  Data_Get_Struct(obj, msql_res_object, msql_res);

  msqlFieldSeek(msql_res->result, FIX2INT(pos));

  return obj;
}



static VALUE
msql_res_num_fields(obj)
  VALUE obj;
{
  msql_res_object *msql_res;
  int num_fields;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  num_fields = msqlNumFields(msql_res->result);

  return INT2FIX(num_fields);
}



static VALUE
msql_res_num_rows(obj)
  VALUE obj;
{
  msql_res_object *msql_res;
  int num_rows;

  Data_Get_Struct(obj, msql_res_object, msql_res);

  num_rows = msqlNumRows(msql_res->result);

  return INT2FIX(num_rows);
}




/*
 * +-----------+
 * | MsqlField |
 * +-----------+
 */

static VALUE
msql_field_create(field_src)
  m_field *field_src;
{
  VALUE new_obj;

  if (field_src == NULL)
    return Qnil;

  new_obj = rb_obj_alloc(cMsqlField);

  rb_iv_set(new_obj, "name", field_src->name ?
                     rb_str_freeze(rb_tainted_str_new2(field_src->name)) : Qnil);
  rb_iv_set(new_obj, "table", field_src->table ?
                     rb_str_freeze(rb_tainted_str_new2(field_src->table)) : Qnil);
  rb_iv_set(new_obj, "type",   INT2FIX(field_src->type));
  rb_iv_set(new_obj, "length", INT2FIX(field_src->length));
  rb_iv_set(new_obj, "flags",  INT2FIX(field_src->flags));

  return new_obj;
}



/*
 * ------------------
 * instance method of
 *     MsqlField
 * ------------------
 */
static VALUE
msql_field_get_name(obj)
  VALUE obj;
{
  return rb_iv_get(obj, "name");
}



static VALUE
msql_field_get_table(obj)
  VALUE obj;
{
  return rb_iv_get(obj, "table");
}



static VALUE
msql_field_get_type(obj)
  VALUE obj;
{
  return rb_iv_get(obj, "type");
}



static VALUE
msql_field_get_length(obj)
  VALUE obj;
{
  return rb_iv_get(obj, "length");
}



static VALUE
msql_field_get_flags(obj)
  VALUE obj;
{
  return rb_iv_get(obj, "flags");
}



static VALUE
msql_field_inspect(obj)
  VALUE obj;
{
  VALUE name, str;

  name = rb_iv_get(obj, "name");
  str  = rb_str_new(0, RSTRING(name)->len + 14);
  sprintf(RSTRING(str)->ptr, "#<MsqlField:%s>", RSTRING(name)->ptr);

  return str;
}



static VALUE
msql_field_is_not_null(obj)
  VALUE obj;
{
  int flags;

  flags = FIX2INT(rb_iv_get(obj, "flags"));

  if (IS_NOT_NULL(flags))
    return Qtrue;

  return Qfalse;
}



static VALUE
msql_field_is_unique(obj)
  VALUE obj;
{
  int flags;

  flags = FIX2INT(rb_iv_get(obj, "flags"));

  if (IS_UNIQUE(flags))
    return Qtrue;

  return Qfalse;
}



static VALUE
msql_field_to_s(obj)
  VALUE obj;
{
  VALUE name, type, length, flags, str;
  char buf[64];

  name  = rb_iv_get(obj, "name");
  type  = rb_iv_get(obj, "type");
  flags = rb_iv_get(obj, "flags");

  Check_Type(name, T_STRING);
  FIXNUM_P(type);
  FIXNUM_P(flags);

  str  = rb_str_new2(STR2CSTR(name));
  rb_str_cat(str, " ", 1);

  switch(FIX2INT(type)){
    case INT_TYPE:
      rb_str_cat(str, "INT", 3);
      break;
    case UINT_TYPE:
      rb_str_cat(str, "UINT", 4);
      break;
    case MONEY_TYPE:
      rb_str_cat(str, "MONEY", 5);
      break;
    case TIME_TYPE:
      rb_str_cat(str, "TIME", 4);
      break;
    case DATE_TYPE:
      rb_str_cat(str, "DATE", 4);
      break;
    case CHAR_TYPE:
      length = rb_iv_get(obj, "length");
      FIXNUM_P(length);
      sprintf(buf, "CHAR(%d)", FIX2INT(length));
      rb_str_cat(str, buf, strlen(buf));
      break;
    case REAL_TYPE:
      rb_str_cat(str, "REAL", 4);
      break;
    case TEXT_TYPE:
      length = rb_iv_get(obj, "length");
      FIXNUM_P(length);
      sprintf(buf, "TEXT(%d)", FIX2INT(length));
      rb_str_cat(str, buf, strlen(buf));
      break;
    default:
      rb_raise(eMsql, "Unkonwn field type: %d\n", FIX2INT(type));
  }

  if (IS_NOT_NULL(FIX2INT(flags)))
    rb_str_cat(str, " NOT NULL", 9);

  return str;
}




/*
 * +--------------+
 * | MsqlSequence |
 * +--------------+
 */

static VALUE
msql_seq_create(seq_src)
  m_seq *seq_src;
{
  VALUE new_obj;

  if (seq_src == NULL)
    return Qnil;

  new_obj = rb_obj_alloc(cMsqlSequence);

  rb_iv_set(new_obj, "step",  INT2FIX(seq_src->step));
  rb_iv_set(new_obj, "value", INT2FIX(seq_src->value));

  return new_obj;
}



/*
 * ------------------
 * instance method of
 *     MsqlSequence
 * ------------------
 */

static VALUE
msql_seq_inspect(obj)
  VALUE obj;
{
  VALUE step, str, value;
  char buf[64];

  step  = rb_iv_get(obj, "step");
  value = rb_iv_get(obj, "value");

  sprintf(buf, "#<MsqlSequence:%d/%d>", FIX2INT(step), FIX2INT(value));

  str  = rb_str_new(buf, strlen(buf));

  return str;
}



static VALUE
msql_seq_get_step(obj)
  VALUE obj;
{
  return rb_iv_get(obj, "step");
}



static VALUE
msql_seq_get_value(obj)
  VALUE obj;
{
  return rb_iv_get(obj, "value");
}



static VALUE
msql_seq_to_s(obj)
  VALUE obj;
{
  VALUE step, str, value;
  char buf[64];

  step  = rb_iv_get(obj, "step");
  value = rb_iv_get(obj, "value");

  sprintf(buf, "STEP %d VALUE %d", FIX2INT(step), FIX2INT(value));

  str  = rb_str_new(buf, strlen(buf));

  return str;
}





void
Init_msql()
{
  cMsql         = rb_define_class("Msql",         rb_cObject);
  cMsqlResult   = rb_define_class("MsqlResult",   rb_cObject);
  cMsqlField    = rb_define_class("MsqlField",    rb_cObject);
  cMsqlSequence = rb_define_class("MsqlSequence", rb_cObject);
  eMsql         = rb_define_class("MsqlError",    rb_eException);


  /*
   * +--------+
   * |  Msql  |
   * +--------+
   */

  rb_define_const(cMsql, "CONFIG_GROUP_GENERAL", rb_str_new2("general"));
  rb_define_const(cMsql, "CONFIG_GROUP_SYSTEM",  rb_str_new2("system"));
  rb_define_const(cMsql, "CONFIG_GROUP_W3MSQL",  rb_str_new2("w3-msql"));
  rb_define_const(cMsql, "INST_DIR",             rb_str_new2(INST_DIR));
  rb_define_const(cMsql, "SERVER_VERSION",       rb_str_new2(SERVER_VERSION));
  rb_define_const(cMsql, "TARGET",               rb_str_new2(TARGET));
  rb_define_const(cMsql, "VERSION",              rb_str_new2(RUBY_MSQL_VERSION));

  rb_define_singleton_method(cMsql, "connect",          msql_connect,         -1);
  rb_define_singleton_method(cMsql, "get_config",       msql_get_conf,         1);
  rb_define_singleton_method(cMsql, "load_config_file", msql_load_config_file, 1);
  rb_define_singleton_method(cMsql, "new",              msql_new,              0);

  rb_define_method(cMsql, "close",             msql_close,             0);
  rb_define_method(cMsql, "connect",           msql_connect2,          1);
  rb_define_method(cMsql, "copy_db",           msql_copy_db,           2);
  rb_define_method(cMsql, "create_db",         msql_create_db,         1);
  rb_define_method(cMsql, "drop_db",           msql_drop_db,           1);
  rb_define_method(cMsql, "get_database",      msql_get_database,      0);
  rb_define_method(cMsql, "get_host",          msql_get_host,          0);
  rb_define_method(cMsql, "get_host_info",     msql_get_host_info,     0);
  rb_define_method(cMsql, "get_protocol_info", msql_get_protocol_info, 0);
  rb_define_method(cMsql, "get_result",        msql_store_result,      0);
  rb_define_method(cMsql, "get_sequence_info", msql_get_sequence_info, 1);
  rb_define_method(cMsql, "get_server_info",   msql_get_server_info,   0);
  rb_define_method(cMsql, "get_server_stats",  msql_get_server_stats,  0);
  rb_define_method(cMsql, "initialize",        msql_initialize,       -1);
  rb_define_method(cMsql, "inspect",           msql_inspect,           0);
  rb_define_method(cMsql, "list_dbs",          msql_list_dbs,          0);
  rb_define_method(cMsql, "list_tables",       msql_list_tables,       0);
  rb_define_method(cMsql, "list_fields",       msql_list_fields,       1);
  rb_define_method(cMsql, "list_index",        msql_list_index,        2);
  rb_define_method(cMsql, "move_db",           msql_move_db,           2);
  rb_define_method(cMsql, "query",             msql_query,             1);
  rb_define_method(cMsql, "reload_acls",       msql_reload_acls,       0);
  rb_define_method(cMsql, "select_db",         msql_select_db,         1);
  rb_define_method(cMsql, "shutdown",          msql_shutdown,          0);
  rb_define_method(cMsql, "store_result",      msql_store_result,      0);


  /*
   * +--------------+
   * |  MsqlResult  |
   * +--------------+
   */

  rb_undef_method(CLASS_OF(cMsqlResult), "new");


  rb_define_method(cMsqlResult, "data_seek",        msql_res_data_seek,         1);
  rb_define_method(cMsqlResult, "each_field_with_index",
                                  msql_res_each_field_with_index,               0);
  rb_define_method(cMsqlResult, "each_field",       msql_res_each_field,        0);
  rb_define_method(cMsqlResult, "each_row",         msql_res_each_row,          0);
  rb_define_method(cMsqlResult, "each_row_with_index",
                                  msql_res_each_row_with_index,                 0);
  rb_define_method(cMsqlResult, "fetch_all_fields", msql_res_fetch_all_fields,  0);
  rb_define_method(cMsqlResult, "fetch_all_rows",   msql_res_fetch_all_rows,    0);
  rb_define_method(cMsqlResult, "fetch_field",      msql_res_fetch_field,       0);
  rb_define_method(cMsqlResult, "fetch_hash",       msql_res_fetch_hash,        0);
  rb_define_method(cMsqlResult, "fetch_row",        msql_res_fetch_row,         0);
  rb_define_method(cMsqlResult, "field_seek",       msql_res_field_seek,        1);
  rb_define_method(cMsqlResult, "initialize",       msql_res_initialize,       -1);
  rb_define_method(cMsqlResult, "num_fields",       msql_res_num_fields,        0);
  rb_define_method(cMsqlResult, "num_rows",         msql_res_num_rows,          0);


  /*
   * +-------------+
   * |  MsqlField  |
   * +-------------+
   */

  /* constants for field type */
  rb_define_const(cMsqlField, "INT_TYPE",       INT2FIX(INT_TYPE));
  rb_define_const(cMsqlField, "CHAR_TYPE",      INT2FIX(CHAR_TYPE));
  rb_define_const(cMsqlField, "REAL_TYPE",      INT2FIX(REAL_TYPE));
  rb_define_const(cMsqlField, "IDENT_TYPE",     INT2FIX(IDENT_TYPE));
  rb_define_const(cMsqlField, "NULL_TYPE",      INT2FIX(NULL_TYPE));
  rb_define_const(cMsqlField, "TEXT_TYPE",      INT2FIX(TEXT_TYPE));
  rb_define_const(cMsqlField, "DATE_TYPE",      INT2FIX(DATE_TYPE));
  rb_define_const(cMsqlField, "UINT_TYPE",      INT2FIX(UINT_TYPE));
  rb_define_const(cMsqlField, "MONEY_TYPE",     INT2FIX(MONEY_TYPE));
  rb_define_const(cMsqlField, "TIME_TYPE",      INT2FIX(TIME_TYPE));
  rb_define_const(cMsqlField, "LAST_REAL_TYPE", INT2FIX(LAST_REAL_TYPE));
  rb_define_const(cMsqlField, "IDX_TYPE",       INT2FIX(IDX_TYPE));
  rb_define_const(cMsqlField, "SYSVAR_TYPE",    INT2FIX(SYSVAR_TYPE));
  rb_define_const(cMsqlField, "ANY_TYPE",       INT2FIX(ANY_TYPE));

  rb_undef_method(CLASS_OF(cMsqlField), "new");

  rb_define_method(cMsqlField, "inspect",      msql_field_inspect,     0);
  rb_define_method(cMsqlField, "to_s",         msql_field_to_s,        0);
  rb_define_method(cMsqlField, "is_not_null",  msql_field_is_not_null, 0);
  rb_define_method(cMsqlField, "is_not_null?", msql_field_is_not_null, 0);
  rb_define_method(cMsqlField, "is_unique",    msql_field_is_unique,   0);
  rb_define_method(cMsqlField, "is_unique?",   msql_field_is_unique,   0);
  rb_define_method(cMsqlField, "name",         msql_field_get_name,    0);
  rb_define_method(cMsqlField, "table",        msql_field_get_table,   0);
  rb_define_method(cMsqlField, "type",         msql_field_get_type,    0);
  rb_define_method(cMsqlField, "length",       msql_field_get_length,  0);
  rb_define_method(cMsqlField, "flags",        msql_field_get_flags,   0);


  /*
   * +----------------+
   * |  MsqlSequence  |
   * +----------------+
   */

  rb_undef_method(CLASS_OF(cMsqlSequence), "new");

  rb_define_method(cMsqlSequence, "inspect", msql_seq_inspect,   0);
  rb_define_method(cMsqlSequence, "to_s",    msql_seq_to_s,      0);
  rb_define_method(cMsqlSequence, "step",    msql_seq_get_step,  0);
  rb_define_method(cMsqlSequence, "value",   msql_seq_get_value, 0);
}
