pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file sql.c
 *
 * \brief Code to support PennMUSH connection to SQL databases.
 *
 *
 */

#include "copyrite.h"
#include "config.h"
#ifdef I_UNISTD
#include <unistd.h>
#endif
#include <string.h>
#include "conf.h"
#ifdef WIN32
#include <stdlib.h>
#define sleep Sleep
#endif
#ifdef HAS_MYSQL
#include <mysql/mysql.h>
#include <mysql/errmsg.h>
#endif
#include "externs.h"
#include "log.h"
#include "parse.h"
#include "command.h"
#include "function.h"
#include "mushdb.h"
#include "dbdefs.h"
#include "conf.h"
#include "confmagic.h"

#ifdef HAS_MYSQL
static MYSQL *mysql_struct = NULL;
#define MYSQL_RETRY_TIMES 3

static int safe_sql_query(dbref player, char *q_string, char *rowsep,
			  char *fieldsep, char *buff, char **bp);
static int sql_init(void);

void
sql_shutdown(void)
{
  if (!mysql_struct)
    return;
  mysql_close(mysql_struct);
  mysql_struct = NULL;
}

static int
sql_init(void)
{
  int retries = MYSQL_RETRY_TIMES;

  /* If we are already connected, drop and retry the connection, in
   * case for some reason the server went away.
   */
  if (mysql_struct)
    sql_shutdown();

  if (!strcasecmp(SQL_PLATFORM, "mysql")) {
    while (retries && !mysql_struct) {
      /* Try to connect to the database host. If we have specified
       * localhost, use the Unix domain socket instead.
       */
      mysql_struct = mysql_init(NULL);

      if (!mysql_real_connect
	  (mysql_struct, SQL_HOST, SQL_USER, SQL_PASS, SQL_DB, 3306, 0, 0)) {
	do_rawlog(LT_ERR, "Failed mysql connection: %s\n",
		  mysql_error(mysql_struct));
	sql_shutdown();
	sleep(1);
      }
      retries--;
    }
  }

  if (mysql_struct)
    return 1;
  else
    return 0;
}

static int
safe_sql_query(dbref player, char *q_string, char *rowsep, char *fieldsep,
	       char *buff, char **bp)
{
  MYSQL_RES *qres;
  MYSQL_ROW row_p;
  int num_rows, got_rows, got_fields;
  int i, j;

  /* If we have no connection, and we don't have auto-reconnect on
   * (or we try to auto-reconnect and we fail), this is an error
   * generating a #-1. Notify the player, too, and set the return code.
   */

  if (!mysql_struct) {
    sql_init();
    if (!mysql_struct) {
      notify(player, "No SQL database connection.");
      if (buff)
	safe_str("#-1", buff, bp);
      return -1;
    }
  }
  if (!q_string || !*q_string)
    return 0;

  /* Send the query. */
  got_rows = mysql_real_query(mysql_struct, q_string, strlen(q_string));
  if (got_rows && (mysql_errno(mysql_struct) == CR_SERVER_GONE_ERROR)) {
    /* We got this error because the server died unexpectedly
     * and it shouldn't have. Try repeatedly to reconnect before
     * giving up and failing. This induces a few seconds of lag,
     * depending on number of retries
     */
    sql_init();
    if (mysql_struct)
      got_rows = mysql_real_query(mysql_struct, q_string, strlen(q_string));
  }
  if (got_rows) {
    notify(player, mysql_error(mysql_struct));
    if (buff)
      safe_str("#-1", buff, bp);
    return -1;
  }

  /* Get results. A silent query (INSERT, UPDATE, etc.) will return NULL */
  qres = mysql_use_result(mysql_struct);
  if (!qres) {
    if (!mysql_field_count(mysql_struct)) {
      /* We didn't expect data back, so see if we modified anything */
      num_rows = mysql_affected_rows(mysql_struct);
      notify_format(player, "SQL: %d rows affected.", num_rows);
      return 0;
    } else {
      /* Oops, we should have had data! */
      notify_format(player, "SQL: Error: %s", mysql_error(mysql_struct));
      return -1;
    }
  }

  got_fields = mysql_num_fields(qres);

  /* Construct properly-delimited data. */
  if (buff) {
    i = 0;
    while ((row_p = mysql_fetch_row(qres)) != NULL) {
      if (i++ > 0) {
	safe_str(rowsep, buff, bp);
      }
      for (j = 0; j < got_fields; j++) {
	if (j > 0) {
	  safe_str(fieldsep, buff, bp);
	}
	if (row_p[j] && *row_p[j])
	  if (safe_str(row_p[j], buff, bp))
	    goto finished;	/* We filled the buffer, best stop */
      }
    }
  } else {
    char *cell;
    MYSQL_FIELD *fields = mysql_fetch_fields(qres);
    i = 0;
    while ((row_p = mysql_fetch_row(qres)) != NULL) {
      i++;
      if (got_fields > 0) {
	for (j = 0; j < got_fields; j++) {
	  cell = row_p[j];
	  notify_format(player, "Row %d, Field %s: %s",
			i, fields[j].name, (cell && *cell) ? cell : "NULL");
	}
      } else
	notify_format(player, "Row %d: NULL", i);
    }
  }

finished:
  /* Eat the rest of the results. The MySQL documentation implies this
     has to be done. */
  while ((row_p = mysql_fetch_row(qres)) != NULL)
    (void) 0;
  mysql_free_result(qres);
  return 0;
}


FUNCTION(fun_sql)
{
  char *rowsep = (char *) " ";
  char *fieldsep = (char *) " ";

  if (!Sql_Ok(executor)) {
    safe_str(T(e_perm), buff, bp);
    return;
  }

  if (nargs >= 2) {
    /* we have a row separator in args[1]. */
    rowsep = args[1];
  }

  if (nargs >= 3) {
    /* we have a field separator in args[2]. Got to parse it */
    fieldsep = args[2];
  }

  safe_sql_query(executor, args[0], rowsep, fieldsep, buff, bp);
}


FUNCTION(fun_sql_escape)
{
  char bigbuff[BUFFER_LEN * 2 + 1];

  if (!Sql_Ok(executor)) {
    safe_str(T(e_perm), buff, bp);
    return;
  }

  if (!args[0] || !*args[0])
    return;

  if (!mysql_struct) {
    sql_init();
    if (!mysql_struct) {
      notify(executor, "No SQL database connection.");
      safe_str("#-1", buff, bp);
      return;
    }
  }
  if (mysql_real_escape_string(mysql_struct, bigbuff, args[0], strlen(args[0]))
      < BUFFER_LEN)
    safe_str(bigbuff, buff, bp);
  else
    safe_str("#-1 TOO LONG", buff, bp);
}


COMMAND (cmd_sql) {
  safe_sql_query(player, arg_left, NULL, NULL, NULL, NULL);
}

#else

/* Oops, no SQL */

FUNCTION(fun_sql)
{
  safe_str(T(e_disabled), buff, bp);
}

FUNCTION(fun_sql_escape)
{
  safe_str(T(e_disabled), buff, bp);
}

void
sql_shutdown(void)
{
  return;
}

#endif