<?php

/*
  ----------------------------------------------------------------------
  LICENSE

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License (GPL)
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  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.

  To read the license please visit http://www.gnu.org/copyleft/gpl.html
  ----------------------------------------------------------------------
*/

/**
 *  Mysql Database class
 *
 * @copyright AlternC-Team 2000-2017 https://alternc.com/
 * 
 */
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: Connect to the database server
     */
    function __construct($db, $host, $user, $passwd) {

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

        $options=array(
            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
        );
        try {
            $this->pdo_instance = new PDO($dsn, $user, $passwd, $options);
        } 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 = "") {
        $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;
    }

    /* pdo equivalent of fetchAll() */
    function fetchAll() {
        if (!$this->pdo_query) {
            $this->halt("next_record called with no query pending.");
            return FALSE;
        }

        $data = $this->pdo_query->fetchAll(PDO::FETCH_BOTH);
        $this->Errno = $this->pdo_query->errorCode();
        $this->Error = $this->pdo_query->errorInfo();

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

        return $data;
    }

    /**
     * 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;

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

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


    /**
     * evaluate the result (size, width)
     */
    function affected_rows() {
        if (!$this->pdo_query) return 0;
        return $this->pdo_query->rowCount();
    }
    function num_rows() {
        if (!$this->pdo_query) return 0;
        return $this->pdo_query->rowCount();
    }

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

    /**
     *  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);
    }


    /**
     * get next 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;
    }


    /** 
     * DEPRECATED return table metadata
     */
    function metadata($table='',$full=false) {
        global $msg;
        $msg->raise("ERROR", '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.");
    }


    /**
     *  private: error handling
     */
    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;
    }

} /* Class DB_Sql */