<?php
/**
 * Project:     PHPPDO
 * File:        pgsql.php
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 * For questions, help, comments, discussion, etc.,
 * visit <http://devuni.com>
 *
 * @link http://devuni.com/
 * @Copyright 2007, 2008, 2009 Nikolay Ananiev.
 * @author Nikolay Ananiev <admin at devuni dot com>
 */

class phppdo_pgsql extends phppdo_base
{
    public $los = array();
    private $autocommit;

    public function __construct(&$dsn, &$username, &$password, &$driver_options)
    {
        if (!extension_loaded('pgsql')) {
            throw new PDOException('could not find extension');
        }

        $this->driver_options[phppdo_base::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT] = false;
        parent::__construct($dsn, $username, $password, $driver_options);

        $this->driver_param_type = 2;
        $this->driver_quote_type = 1;
    }

    public function beginTransaction()
    {
        parent::beginTransaction();

        if (!pg_query($this->link, 'BEGIN')) {
            $this->set_driver_error(null, PDO::ERRMODE_EXCEPTION, 'beginTransaction');
        }

        return true;
    }

    public function commit()
    {
        parent::commit();

        if (!pg_query($this->link, 'COMMIT')) {
            $this->set_driver_error(null, PDO::ERRMODE_EXCEPTION, 'commit');
        }

        $this->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);

        return true;
    }

    public function exec(&$statement)
    {
        if ($result = @pg_query($this->link, $statement)) {
            if (is_resource($result)) {
                pg_free_result($result);

                return 0;
            }

            return pg_affected_rows($this->link);
        }

        return false;
    }

    public function getAttribute($attribute, &$source = null, $func = 'PDO::getAttribute', &$last_error = null)
    {
        switch ($attribute) {
            case PDO::ATTR_AUTOCOMMIT:
                return $this->autocommit;
            break;

            case PDO::ATTR_CLIENT_VERSION:
                $ver = pg_version($this->link);

                return $ver['client'];
            break;

            case PDO::ATTR_CONNECTION_STATUS:
                if (pg_connection_status($this->link) === PGSQL_CONNECTION_OK) {
                    return 'Connection OK; waiting to send.';
                } else {
                    return 'Connection BAD';
                }
            break;

            case PDO::ATTR_SERVER_INFO:
                return sprintf('PID: %d; Client Encoding: %s; Is Superuser: %s; Session Authorization: %s; Date Style: %s',
                    pg_get_pid($this->link),
                    pg_client_encoding($this->link),
                    pg_parameter_status($this->link, 'is_superuser'),
                    pg_parameter_status($this->link, 'session_authorization'),
                    pg_parameter_status($this->link, 'DateStyle')
                );
            break;

            case PDO::ATTR_SERVER_VERSION:
                return pg_parameter_status($this->link, 'server_version');
            break;

            default:
                return parent::getAttribute($attribute, $source, $func, $last_error);
            break;
        }
    }

    public function lastInsertId($name = '')
    {
        if (!$name) {
            return false;
        }
        if ($result = @pg_query($this->link, 'SELECT currval('.$this->quote($name).')')) {
            $row = pg_fetch_row($result);

            return intval($row[0]);
        }

        return false;
    }

    public function prepare(&$statement, &$options)
    {
        if (!($st = parent::prepare($statement, $options))) {
            return false;
        }

        $result_name = uniqid('phpdo_');
        $result = @pg_prepare($this->link, $result_name, $this->prepared);

        if (!$result) {
            $this->set_driver_error(null, PDO::ERRMODE_SILENT, 'prepare');

            return false;
        }

        $st->_set_result($result, $result_name);

        return $st;
    }

    public function quote(&$param, $parameter_type = -1)
    {
        switch ($parameter_type) {
            case PDO::PARAM_BOOL:
                return $param ? 'TRUE' : 'FALSE';
            break;

            case PDO::PARAM_NULL:
                return 'NULL';
            break;

            case PDO::PARAM_INT:
                return is_null($param) ? 'NULL' : (is_int($param) ? $param : (float) $param);
            break;

            default:
                return '\''.pg_escape_string($this->link, $param).'\'';
            break;
        }
    }

    public function rollBack()
    {
        parent::rollback();

        if (!pg_query($this->link, 'ROLLBACK')) {
            $this->set_driver_error(null, PDO::ERRMODE_EXCEPTION, 'rollBack');
        }

        $this->setAttribute(PDO::ATTR_AUTOCOMMIT, $this->driver_options[PDO::ATTR_AUTOCOMMIT]);

        return true;
    }

    public function setAttribute($attribute, $value, &$source = null, $func = 'PDO::setAttribute', &$last_error = null)
    {
        if ($source == null) {
            $source = & $this->driver_options;
        }

        switch ($attribute) {
            case PDO::ATTR_AUTOCOMMIT:
                $this->autocommit = $value ? 1 : 0;

                return true;
            break;

            default:
                return parent::setAttribute($attribute, $value, $source, $func, $last_error);
            break;
        }

        return false;
    }

    public function set_driver_error($state = null, $mode = PDO::ERRMODE_SILENT, $func = '')
    {
        if ($state === null) {
            $state = 'HY000';
        }
        $this->set_error(7, pg_last_error($this->link), $state, $mode, $func);
    }

    public function pgsqlLOBCreate()
    {
        if (false === ($oid = pg_lo_create($this->link))) {
            $this->set_driver_error(null, PDO::ERRMODE_EXCEPTION, 'pgsqlLOBCreate');
        }

        return $oid;
    }

    public function pgsqlLOBOpen($oid)
    {
        if (!$stream = tmpfile()) {
            $this->set_error(7, 'Could not create tem file', 'HY000', PDO::ERRMODE_EXCEPTION, 'pgsqlLOBOpen');
        }

        if (!$lo_stream = pg_lo_open($this->link, $oid, 'w')) {
            $this->set_error(null, PDO::ERRMODE_EXCEPTION, 'pgsqlLOBOpen');
        }

        $this->los[(int) $stream] = array($oid, $lo_stream);

        return $stream;
    }

    public function pgsqlLOBUnlink($oid)
    {
        return pg_lo_unlink($this->link, $oid);
    }

    protected function connect(&$username, &$password, &$driver_options)
    {
        $dsn = '';
        if (isset($this->dsn['host'])) {
            $dsn .= 'host=\''.$this->dsn['host'].'\'';
        }

        if (isset($this->dsn['port'])) {
            $dsn .= ' port=\''.$this->dsn['port'].'\'';
        }

        if (isset($this->dsn['dbname'])) {
            $dsn .= ' dbname=\''.$this->dsn['dbname'].'\'';
        }

        if ($username) {
            $dsn .= ' user=\''.$username.'\'';
        }

        if ($password) {
            $dsn .= ' password=\''.$password.'\'';
        }

        if (isset($driver_options[PDO::ATTR_TIMEOUT])) {
            $dsn .= ' connect_timeout=\''.$driver_options[PDO::ATTR_TIMEOUT].'\'';
        }

        ob_start();

        if (isset($driver_options[PDO::ATTR_PERSISTENT]) && $driver_options[PDO::ATTR_PERSISTENT]) {
            $this->link = pg_pconnect($dsn);
        } else {
            $this->link = pg_connect($dsn, PGSQL_CONNECT_FORCE_NEW);
        }

        $error = ob_get_contents();
        ob_end_clean();

        if (!$this->link) {
            $this->set_error(7, $this->clear_warning($error), '08006', PDO::ERRMODE_EXCEPTION, '__construct');
        }

        if (isset($this->dsn['charset'])) {
            // returns -1 on error and 0 on success
            if (pg_set_client_encoding($this->link, $this->dsn['charset'])) {
                $this->set_driver_error(null, PDO::ERRMODE_EXCEPTION, '__construct');
            }
        }
    }

    protected function disconnect()
    {
        pg_close($this->link);
    }

    private function clear_warning($msg)
    {
        $pos = strpos($msg, 'server: ');
        $pos2 = strrpos($msg, ' in ');
        if ($pos !== false && $pos2 !== false) {
            $pos += 8;

            return substr($msg, $pos, ($pos2 - $pos));
        }

        return $msg;
    }
}
