<?php

/**
 *  Mysql Database class
 *
 *  François - aka fser - Serman
 * 
 *  2014/06/24
 */

class DB_Sql {
  
  /* public: connection parameters */
   private $Host;
   private $Database;
   private $User;
   private $Password;

   /* public: configuration parameters */
   private $Auto_Free = False; // Set to True for automatic mysql_free_result()
   private $Debug = False;     // Set to 1 for debugging messages.
   private $Halt_On_Error = "no"; // "yes" (halt with message), "no" (ignore errors quietly), "report" (ignore errror, but spit a warning)
   private $Seq_Table     = "db_sequence";

   /* public: result array and current row number */
   public /* FIXME */ $Record   = array();
   private $Row = 0;
   private $num_rows;

   /* public: current error number and error text */
   private $Errno;
   private $Error;


   /* private: link and query handles */
   private $Query_String;
  

  /* PDO related variables */
  private $pdo_instance = NULL;
  private $pdo_query = NULL;

  /**
   * Constructor 
   */
  function __construct($db, $host, $user, $passwd) {

        $dsn = sprintf('mysql:dbname=%s;host=%s', $db, $host);

        try {
           $this->pdo_instance = new PDO($dsn, $user, $passwd);
        } catch (PDOException $e) {
	   echo "Mysql", "PDO instance", $e->getMessage();
           return FALSE;
        }
  }

  /**
   * function for MySQL database connection management
   *
   * This function manages the connection to the MySQL database.
   *
   * @param $Database name of the database
   * @param $Host DNS of the MySQL hosting server
   * @param $User the user's name
   * @param $Password the user's password
   *
   * @return the class variable $Link_ID
   */
  function connect($Database = "", $Host = "", $User = "", $Password = "") {
     global $err;
     $this->halt('Mysql::connect() : This function should no longer be used');
     /* Handle defaults */
     if ("" == $Database)
        $Database = $this->Database;
     if ("" == $Host)
        $Host     = $this->Host;
     if ("" == $User)
        $User     = $this->User;
     if ("" == $Password)
        $Password = $this->Password;
     
     if (!$this->pdo_instance) {
        $dsn = sprintf('mysql:dbname=%s;host=%s', $Database, $Host);

        try {
           $this->pdo_instance = new PDO($dsn, $User, $Password);
        } catch (PDOException $e) {
           $this->halt("Mysql::PDO_instance" . $e->getMessage());
           return FALSE;
        }
    }
    
    return True;
  }

  /**
   * Discard the query result 
   *
   * This function discards the last query result.
   */
  function free() {
     $this->pdo_query->closeCursor();
  }

  function is_connected() {
     return $this->pdo_instance != FALSE;
  }

  function last_error() {
    return $this->Error;
  }
  /** 
   * Perform a query 
   * 
   * This function performs the MySQL query described in the string parameter
   *
   * @param a string describing the MySQL query   
   * @param arguments is an optionnal array for future use with PDO parametrized requests
   * @return the $Query_ID class variable (null if fails)
   */
  function query($Query_String, $arguments = false) {
     global $debug_alternc;

     if (empty($Query_String) || !$this->is_connected())
        return FALSE;

     $this->Query_String = $Query_String;
     if ($this->Debug)
        printf("Debug: query = %s<br />\n", $Query_String);

     $debug_chrono_start = microtime(true);

     if ($arguments===false) {
       $this->pdo_query = $this->pdo_instance->query($Query_String);
       $exec_state = is_object($this->pdo_query);

     } else {
       
       $this->pdo_query = $this->pdo_instance->prepare($this->Query_String);
       $exec_state = ($arguments) ? $this->pdo_query->execute($arguments) 
	 : $this->pdo_query->execute(); 
       // WARNING: this ternary is when we pass array() as $arguments
     }

     $debug_chrono_start = (microtime(true) - $debug_chrono_start)*1000;
     $this->Row = 0;

     if ($exec_state == FALSE) {
       if (is_object($this->pdo_query)) {
        $this->Errno = $this->pdo_query->errorCode();
        $this->Error = $this->pdo_query->errorInfo();
       } else {
        $this->Errno = $this->pdo_instance->errorCode();
        $this->Error = $this->pdo_instance->errorInfo();
       }

        if( defined("THROW_EXCEPTIONS") && THROW_EXCEPTIONS ){
           throw new \Exception("Mysql query failed : $this->Error");
        }
        $this->halt("SQL Error: ".$Query_String);
        return FALSE;
     }
     
     if (isset($debug_alternc)) {
        $debug_alternc->add("SQL Query : (".substr($debug_chrono_start,0,5)." ms)\t $Query_String");
        $debug_alternc->nb_sql_query++;
        $debug_alternc->tps_sql_query += $debug_chrono_start;
     }

     return TRUE;
  }
   
  /**
   * walk result set 
   *
   * This function tests if a new record is available in the current
   * query result.
   *
   * @return TRUE if a new record is available
   */
  function next_record() {
     if (!$this->pdo_query) {
        $this->halt("next_record called with no query pending.");
        return FALSE;
     }

    $this->Record = $this->pdo_query->fetch(PDO::FETCH_BOTH);
    $this->Row++;
    $this->Errno = $this->pdo_query->errorCode();
    $this->Error = $this->pdo_query->errorInfo();

    if ($this->Record == FALSE) {
      if ($this->Auto_Free) 
	$this->free();
      return FALSE;
    }

    return TRUE;
  }

  /* public: table locking */
  function lock($table, $mode="write") {
     if (!$this->is_connected())
        return FALSE;
    
     $query="lock tables ";
     if (is_array($table)) {
        while (list($key,$value)=each($table)) {
           if ($key=="read" && $key!=0) {
              $query.="$value read, ";
           } else {
              $query.="$value $mode, ";
           }
        }
        $query=substr($query,0,-2);
     } else {
        $query.="$table $mode";
     }
     

     if (!$this->query($query)) {
      $this->halt("lock($table, $mode) failed.");
      return FALSE;
     }

     return TRUE;

  }
  
  function unlock() {
     if (!$this->is_connected())
        return FALSE;

     if (!$this->query('unlock tables')) {
        $this->halt("unlock() failed.");
        return FALSE;
     }
  }


  /* public: evaluate the result (size, width) */
  function affected_rows() {
     return $this->pdo_query->rowCount();
  }

  function num_rows() {
     return $this->pdo_query->rowCount();
  }

  function num_fields() {
     return $this->pdo_query->columnCount();
  }

  /* public: shorthand notation */
  function nf() {
    return $this->num_rows();
  }

  function np() {
    print $this->num_rows();
  }

  /**
   * @param string $Name
   * @return integer
   */
  function f($Name) {
     if (isset($this->Record[$Name]))
        return $this->Record[$Name];
     else
        return false;
  }

  function current_record() {
     return $this->Record;
  }

  function p($Name) {
     print $this->Record[$Name];
  }

  function lastid() {
     return $this->pdo_instance->lastInsertId();
  }

    /**
     *  Escape a string to use it into a SQL PDO query
     *  @param  string  string to escape
     *  @return string  escaped string
     */
  function quote($string) {
    return $this->pdo_instance->quote($string);
  }


    /**
     *  Execute a direct query, not getting any result back
     *  @param  query  string query to execute
     *  @return integer the number of affected rows
     */
  function exec($query) {
    return $this->pdo_instance->exec($query);
  }


  /* public: sequence numbers */
  function nextid($seq_name) {
     if (!$this->is_connected())
        return FALSE;

     if ($this->lock($this->Seq_Table)) {
        /* get sequence number (locked) and increment */
        $q  = sprintf("select nextid from %s where seq_name = '%s'",
                      $this->Seq_Table,
                      $seq_name);
        $this->query($q);
        $this->next_record();
        
        $id = $this->f('nextid');
      
        /* No current value, make one */
        if (!$id) {
           $currentid = 0;
           $q = sprintf("insert into %s values('%s', %s)",
                        $this->Seq_Table,
                        $seq_name,
                        $currentid);
           $this->query($q);
        } else {
           $currentid = $id;
        }
        
        $nextid = $currentid + 1;
        $q = sprintf("update %s set nextid = '%s' where seq_name = '%s'",
                     $this->Seq_Table,
                     $nextid,
                     $seq_name);
        $this->query($q);
        $this->unlock();
     } else {
        $this->halt("cannot lock ".$this->Seq_Table." - has it been created?");
        return FALSE;
     }
     
     return $nextid;
  }

  /* public: return table metadata */
  function metadata($table='',$full=false) {
     global $err;
     $err->raise('Mysql', 'function is no longer implemented (metadata())');
     return FALSE;
  }

  /* private: error handling */
  function halt($msg) {
     if ($this->Halt_On_Error == "no")
        return;

     $this->haltmsg($msg);

     if ($this->Halt_On_Error != "report")
        die("Session halted.");
  }

  function haltmsg($msg) {
     printf("</td></tr></table><b>Database error:</b> %s<br />\n", $msg);
     printf("<b>MySQL Error</b>: %s (%s)<br />\n",
            $this->Errno,
            implode("\n", $this->Error));
  }

  function table_names() {
    $this->query("SHOW TABLES");
    $return = array();
    while ($this->next_record())
       $return[] = array('table_name' => $this->p(0), 'tablespace_name' => $this->Database, 'database' => $this->Database);

    return $return;
  }
}
?>