<?php //$Id$
//Copyright (c) 2012-2016 Pierre Pronchery <khorben@defora.org>
//This file is part of DeforaOS Web DaPortal
//
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, version 3 of the License.
//
//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.
//
//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.



//PgSQLDatabase
class PgSQLDatabase extends Database
{
	//public
	//methods
	//essential
	//PgSQLDatabase::~PgSQLDatabase
	public function __destruct()
	{
		if($this->handle !== FALSE)
			pg_close($this->handle);
	}


	//accessors
	//PgSQLDatabase::getLastID
	public function getLastID(Engine $engine = NULL, $table, $field)
	{
		$sequence = $this->getSequence($table, $field);
		$query = $this->query_currval;
		$args = array('sequence' => $sequence);
		if(($res = $this->query($this->engine, $query, $args)) === FALSE
				|| count($res) != 1)
			return FALSE;
		$res = $res->current();
		return $res['currval'];
	}


	//PgSQLDatabase::isFalse
	public function isFalse($value)
	{
		return $value == 'f';
	}


	//PgSQLDatabase::isTrue
	public function isTrue($value)
	{
		return $value == 't';
	}


	//useful
	//PgSQLDatabase::enum
	public function enum(Engine $engine = NULL, $table, $field)
	{
		$query = $this->query_enum;
		$args = array('table' => $table,
			'field' => $table.'_'.$field);

		if(($res = $this->query($this->engine, $query, $args)) === FALSE
				|| count($res) != 1)
			return array();
		$res = $res->current();
		$res = explode("'", $res['constraint']);
		$str = array();
		for($i = 1, $cnt = count($res); $i < $cnt; $i += 2)
			$str[] = $res[$i];
		return $str;
	}


	//PgSQLDatabase::formatDate
	public function formatDate($date, $outformat = FALSE, $informat = FALSE)
	{
		if($informat !== FALSE
				|| ($timestamp = strtotime($date)) === FALSE
				|| $timestamp == -1)
			return parent::formatDate($date, $outformat, $informat);
		return Date::formatTimestamp($timestamp, $outformat);
	}


	//PgSQLDatabase::like
	public function like($case = TRUE, $pattern = FALSE)
	{
		$ret = $case ? 'LIKE' : 'ILIKE';

		if($pattern !== FALSE)
			$ret .= ' '.$this->escape($pattern);
		return $ret;
	}


	//PgSQLDatabase::limit
	public function limit($limit = FALSE, $offset = FALSE)
	{
		if($limit === FALSE && $offset === FALSE)
			return '';
		$ret = ($offset !== FALSE && is_numeric($offset))
			? " OFFSET $offset" : '';
		if($limit !== FALSE && is_numeric($limit))
			$ret .= " LIMIT $limit";
		return $ret;
	}


	//PgSQLDatabase::query
	public function query(Engine $engine = NULL, $query,
			$parameters = FALSE, $async = FALSE)
	{
		global $config;

		if($this->handle === FALSE)
			return FALSE;
		if($config->get('database', 'debug'))
			$this->engine->log(LOG_DEBUG, $query);
		//convert the query to the PostgreSQL way
		//FIXME cache the results of the conversion
		//XXX this may break the query string in illegitimate places
		$q = explode(':', $query);
		$query = $q[0];
		$args = array();
		for($i = 1, $cnt = count($q); $i < $cnt; $i++)
		{
			for($j = 0, $len = strlen($q[$i]); $j < $len
				&& (ctype_alnum($q[$i][$j])
					|| $q[$i][$j] == '_'); $j++);
			$k = substr($q[$i], 0, $j);
			if(!array_key_exists($k, $parameters))
			{
				$error = $k.': Missing parameter in query';
				return $this->engine->log(LOG_ERR, $error);
			}
			$query .= "\$$i ".substr($q[$i], $j);
			if(is_bool($parameters[$k]))
				$args[$i] = $parameters[$k] ? '1' : '0';
			else
				$args[$i] = $parameters[$k];
		}
		if($config->get('database::pgsql', 'debug'))
			$this->engine->log(LOG_DEBUG, get_class().': '.$query);
		//prepare the query
		if(($q = $this->prepare($query, $args)) === FALSE)
			return FALSE;
		//execute the query
		$this->profileStart();
		if($async)
			$res = pg_send_execute($this->handle, $q, $args);
		else
			$res = pg_execute($this->handle, $q, $args);
		$this->profileStop($query);
		if($res === FALSE)
		{
			if(($error = pg_last_error($this->handle)) !== FALSE)
				$this->engine->log(LOG_DEBUG, $error);
			return FALSE;
		}
		if($async)
			return TRUE;
		if(pg_num_rows($res) == -1)
			return FALSE;
		return new PgSQLDatabaseResult($res);
	}


	//PgSQLDatabase::regexp
	public function regexp($case = TRUE, $pattern = FALSE)
	{
		$ret = $case ? '~' : '~*';

		if($pattern !== FALSE)
			$ret .= ' '.$this->escape($pattern);
		return $ret;
	}


	//protected
	//properties
	static protected $transactionClass = 'PgSQLDatabaseTransaction';

	//queries
	//IN:	sequence
	protected $query_currval = 'SELECT currval(:sequence) AS currval';
	//IN:	table
	//	field
	protected $query_enum = 'SELECT
		pg_catalog.pg_get_constraintdef(r.oid) AS constraint
		FROM pg_catalog.pg_class c, pg_catalog.pg_constraint r
		WHERE c.oid=r.conrelid AND c.relname=:table
		AND conname=:field';


	//methods
	//PgSQLDatabase::match
	protected function match(Engine $engine)
	{
		global $config;
		$variables = $this->variables;

		if(!function_exists('pg_connect'))
			return 0;
		foreach($variables as $k => $v)
			if($config->get('database::pgsql', $k) !== FALSE)
				return 100;
		return 1;
	}


	//PgSQLDatabase::attach
	protected function attach(Engine $engine)
	{
		global $config;

		if($this->_attachConfig($engine, $config) === FALSE)
			return $engine->log(LOG_ERR,
					'Could not open database');
		return TRUE;
	}

	protected function _attachConfig(Engine $engine, Config $config,
			$section = FALSE, $new = FALSE)
	{
		$str = '';
		$sep = '';
		$flags = $new ? PGSQL_CONNECT_FORCE_NEW : 0;

		if($section === FALSE)
			$section = 'database::'.$this->name;
		foreach($this->variables as $k => $v)
			if(($p = $config->get($section, $k)) !== FALSE)
			{
				$str .= $sep.$v."='$p'"; //XXX escape?
				$sep = ' ';
			}
		$this->handle = $config->get($section, 'persistent')
			? pg_pconnect($str, $flags) : pg_connect($str, $flags);
		if($this->handle === FALSE)
			return FALSE;
		$this->engine = $engine;
		return TRUE;
	}


	//PgSQLDatabase::escape
	protected function escape($string)
	{
		if(is_bool($string))
			$string = $string ? '1' : '0';
		if(function_exists('pg_escape_literal'))
			return pg_escape_literal($this->handle, $string);
		return "'".pg_escape_string($this->handle, $string)."'";
	}


	//PgSQLDatabase::prepare
	protected function prepare($query, &$parameters = FALSE)
	{
		if(isset($this->statements[$query]))
			return $this->statements[$query];
		$id = uniqid();
		$this->statements[$query] = (pg_prepare($this->handle, $id,
				$query) !== FALSE) ? $id : FALSE;
		return $this->statements[$query];
	}


	//accessors
	//PgSQLDatabase::getSequence
	protected function getSequence($table, $field)
	{
		return $table.'_'.$field.'_seq';
	}


	//private
	//properties
	private $handle = FALSE;
	private $statements = array();

	private $variables = array('username' => 'user',
		'password' => 'password',
		'database' => 'dbname', 'hostname' => 'host',
		'port' => 'port',
		'timeout' => 'connect_timeout',
		'service' => 'service',
		'sslmode' => 'sslmode');
}

?>
