<?php
/*
Postgre to MySQL Adapter, Emulates a PostgreSQL Database on a MySQL backend
Copyright (C) 2016  T Duke Perry (http://noprestige.com)

This program 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 3 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 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/>.

--------------------------------------------------

Known Issues:
	pg_connect, pg_delete, pg_insert, pg_update,
		and pg_select won't allow custom options
	pg_query might have problems detecting whether there are one
		or more than one queries in a call.
	pg_field_table cannot fetch OID
	Error handling is missing from most of the functions

Includes replacements for:
	pg_connect
	pg_close
	pg_ping
	pg_last_error

	pg_dbname
	pg_host
	pg_port
	pg_options

	pg_delete
	pg_insert
	pg_update
	pg_select

	pg_query
	pg_query_params
	pg_prepare
	pg_execute

	pg_escape_string
	pg_escape_literal

	pg_num_rows
	pg_affected_rows
	pg_fetch_result
	pg_fetch_row
	pg_fetch_array
	pg_fetch_assoc
	pg_fetch_all_columns
	pg_fetch_all
	pg_fetch_object

	pg_num_fields
	pg_field_is_null
	pg_field_name
	pg_field_num
	pg_field_size
	pg_field_table
	pg_field_type
	pg_field_prtlen

Uses newly designed objects instead of connection and result resources.

For more information:
	http://php.net/manual/en/book.pgsql.php
	http://php.net/manual/en/book.mysqli.php

*/
if (!extension_loaded('pgsql') && extension_loaded('mysqli'))
{
	define("PGSQL_LIBPQ_VERSION", 'v2016-02-22');

	echo "<!--\n";
	echo "Postgre to MySQL Adapter (".PGSQL_LIBPQ_VERSION.") Loaded\n";
	echo "Designed and Developed by T Duke Perry (http://noprestige.com) Copyright (C) 2014-2016\n";
	echo "Licensed under The GNU Lesser General Public License v3\n";
	echo "Full source code available at http://github.com/theblackwidower/Postgre-To-MySQL-Adapter\n";
	echo "-->\n";

	define("PGSQL_LIBPQ_VERSION_STR", 'Postgre to MySQL Adapter '.PGSQL_LIBPQ_VERSION.' on '.php_uname('s').' '.php_uname('r'));

	//constants from pgsql driver
	define("PGSQL_ASSOC",1);define("PGSQL_NUM",2);define("PGSQL_BOTH",3);define("PGSQL_CONNECT_FORCE_NEW",2);define("PGSQL_CONNECT_ASYNC",4);define("PGSQL_CONNECTION_BAD",1);define("PGSQL_CONNECTION_OK",0);define("PGSQL_SEEK_SET",0);define("PGSQL_SEEK_CUR",1);define("PGSQL_SEEK_END",2);define("PGSQL_EMPTY_QUERY",0);define("PGSQL_COMMAND_OK",1);define("PGSQL_TUPLES_OK",2);define("PGSQL_COPY_OUT",3);define("PGSQL_COPY_IN",4);define("PGSQL_BAD_RESPONSE",5);define("PGSQL_NONFATAL_ERROR",6);define("PGSQL_FATAL_ERROR",7);define("PGSQL_TRANSACTION_IDLE",0);define("PGSQL_TRANSACTION_ACTIVE",1);define("PGSQL_TRANSACTION_INTRANS",2);define("PGSQL_TRANSACTION_INERROR",3);define("PGSQL_TRANSACTION_UNKNOWN",4);define("PGSQL_DIAG_SEVERITY",83);define("PGSQL_DIAG_SQLSTATE",67);define("PGSQL_DIAG_MESSAGE_PRIMARY",77);define("PGSQL_DIAG_MESSAGE_DETAIL",68);define("PGSQL_DIAG_MESSAGE_HINT",72);define("PGSQL_DIAG_STATEMENT_POSITION",80);define("PGSQL_DIAG_INTERNAL_POSITION",112);define("PGSQL_DIAG_INTERNAL_QUERY",113);define("PGSQL_DIAG_CONTEXT",87);define("PGSQL_DIAG_SOURCE_FILE",70);define("PGSQL_DIAG_SOURCE_LINE",76);define("PGSQL_DIAG_SOURCE_FUNCTION",82);define("PGSQL_ERRORS_TERSE",0);define("PGSQL_ERRORS_DEFAULT",1);define("PGSQL_ERRORS_VERBOSE",2);define("PGSQL_STATUS_LONG",1);define("PGSQL_STATUS_STRING",2);define("PGSQL_CONV_IGNORE_DEFAULT",2);define("PGSQL_CONV_FORCE_NULL",4);define("PGSQL_CONV_IGNORE_NOT_NULL",8);define("PGSQL_DML_NO_CONV",256);define("PGSQL_DML_EXEC",512);define("PGSQL_DML_ASYNC",1024);define("PGSQL_DML_STRING",2048);define("PGSQL_DML_ESCAPE",4096);define("PGSQL_POLLING_FAILED",0);define("PGSQL_POLLING_READING",1);define("PGSQL_POLLING_WRITING",2);define("PGSQL_POLLING_OK",3);define("PGSQL_POLLING_ACTIVE",4);

	/*****************
	Internal functions
	*****************/
	//taken from http://stackoverflow.com/questions/3681262/php5-3-mysqli-stmtbind-params-with-call-user-func-array-warnings
	function np_p2m_refValues($arr)
	{
		$refs = array();
		foreach ($arr as $key => $value)
			$refs[$key] = &$arr[$key];
		return $refs;
	}

	/***************
	Emulated classes
	***************/
	class np_p2m_link
	{
		//will hold the last created connection
		private static $last_conn = NULL;

		public static function last_connection()
		{
			return self::$last_conn;
		}

		private $mysqli_connection;
		//in Postgre, prepared statements are stored server side.
		//in lieu of that, this array will hold mysqli_stmt objects,
		//as well as query strings for static prepared statements (those that take no parameters).
		private $stored_stmts = array();
		//prepares initial holder variables in advance
		private $hostname = NULL;
		private $database = NULL;
		private $port = NULL;
		private $options = NULL;
		private $is_error = false;

		private static function query_sanitize(&$query)
		{
			//mysql does not support the 'only' option when deleting.
			if (strcasecmp(substr($query, 0, 17), 'DELETE FROM ONLY ') == 0)
				$query = substr_replace($query, 'DELETE FROM ', 0, 17);
		}

		private static function parse_conn_string($string)
		{
			$return = Array();
			$length = strlen($string);

			for($i = 0; $i < $length;
				$return[] = Array("param" => $param, "value" => $value), $i++)
			{
				$param = "";
				$value = "";
				//looping to parse parameter
				for(;$i < $length; $i++)
				{
					if ($string[$i] == "=")
					{
						while ($i < $length && $string[$i + 1] == " ")
							$i++;
						break;
					}
					else if ($string[$i] == " ")
					{
						while ($i < $length && $string[$i + 1] == " ")
							$i++;
						if ($string[$i + 1] == "=")
						{
							$i++;
							while ($i < $length && $string[$i + 1] == " ")
								$i++;
							break;
						}
						else
						{
							//restart first loop
							continue 2;
						}
					}
					else
						$param .= $string[$i];
				}
				//looping to parse value
				for($i++; $i < $length; $i++)
				{
					if ($string[$i] == "'")
					{
						for($i++; $i < $length; $i++)
						{
							if ($string[$i] == "'")
								break;
							else if ($string[$i] == "\\")
							{
								$i++;
								$value .= $string[$i];
							}
							else
								$value .= $string[$i];
						}
					}
					else if ($string[$i] == " ")
					{
						while ($i < $length && $string[$i + 1] == " ")
							$i++;
						break;
					}
					else
						$value .= $string[$i];
				}
			}
			return $return;
		}

		public function __construct($connection_string)
		{
			$username = NULL;
			$password = NULL;

			$elements = self::parse_conn_string($connection_string);
			foreach($elements as $part)
			{
				switch ($part['param'])
				{
					case 'host':
						$this->hostname = $part['value'];
						break;
					case 'dbname':
						$this->database = $part['value'];
						break;
					case 'user':
						$username = $part['value'];
						break;
					case 'password':
						$password = $part['value'];
						break;
					case 'port':
						$this->port = $part['value'];
						break;
					case 'options'://currently does no more than record options
						$this->options = $part['value'];
						break;
					default:
						$this->is_error = true;
				}
			}
			if (!$this->is_error)
			{
				$this->mysqli_connection = new mysqli($this->hostname, $username, $password, $this->database, $this->port);
				if ($this->mysqli_connection->connect_errno !== 0)
					$this->is_error = true;
				else
					self::$last_conn = $this;
			}
		}

		public function close()
		{
			if ($this->mysqli_connection->close())
			{
				$this->stored_stmts = array();
				$this->hostname = NULL;
				$this->database = NULL;
				$this->port = NULL;
				return true;
			}
			else
				return false;
		}

		public function ping()
		{
			return $this->mysqli_connection->ping();
		}

		public function last_error()
		{
			return $this->mysqli_connection->error;
		}

		public function escape_string($string)
		{
			return $this->mysqli_connection->real_escape_string($string);
		}

		public function is_error()
		{
			return $this->is_error;
		}

		public function get_info($call)
		{
			switch ($call)
			{
				case 'dbname':
					$result = $this->database;
					break;
				case 'host':
					$result = $this->hostname;
					break;
				case 'port':
					$result = $this->port;
					break;
				case 'options':
					$result = $this->options;
					break;
				default:
					$result = false;
			}

			return $result;
		}

		public function prepare($name, $query)
		{
			self::query_sanitize($query);

			$this->stored_stmts[$name] = new np_p2m_stmt($query, $this->mysqli_connection);
		}

		public function query_params($query, $params)
		{
			self::query_sanitize($query);

			$stmt = new np_p2m_stmt($query, $this->mysqli_connection);
			return $stmt->execute($params);
		}

		public function execute($name, $params)
		{
			return $this->stored_stmts[$name]->execute($params);
		}

		public function query($query)
		{
			self::query_sanitize($query);

			//clear comments and count queries
			//might have problems if there are only two queries, and the second query does not end in a semicolon
			//might also run into issues if the symbol that indicates the start of a comment block is within quotes.
			$count = preg_match_all('/.*?;/s', preg_replace('/--.*/', '', preg_replace('/\/\*.*?\*\//s', '', $query)));

			//TODO: parse the individual statements and return query results on multiples.
			if ($count > 1)
				$result = $this->mysqli_connection->multi_query($query);
			else
				$result = $this->mysqli_connection->query($query);

			return new np_p2m_result($result, $this->mysqli_connection->affected_rows);
		}

		/************************************************
		For pg_delete, pg_insert, pg_update and pg_select
		************************************************/

		private function direct_execute($mysqli_query, $params, $return_array = false)
		{
			$types = "";
			foreach ($params as $value)
			{
				if (is_int($value))
					$types .= 'i';
				else if (is_float($value))
					$types .= 'd';
				else if (is_string($value))
					$types .= 's';
				else //something might've gone wrong
					$types .= ' ';
			}

			$stmt = $this->link->prepare($mysqli_query);

			call_user_func_array(array($stmt, 'bind_param'), array_merge(array($types), np_p2m_refValues($params)));
			if ($stmt->execute())
			{
				if ($return_array)
					return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
				else
					return true;
			}
			else
				return false;
		}

		public function delete($table, $row_array, $options)
		{
			if (count($row_array) === 0)
				return false;
			else
			{
				$query = 'DELETE FROM '.$table.' WHERE ';
				$params = array();
				foreach ($row_array as $field => $value)
				{
					$query .= $field.' = ? AND ';
					$params[] = $value;
				}

				return $this->direct_execute(substr($query, 0, -5), $params);
			}
		}

		public function insert($table, $row_array, $options)
		{
			if (count($row_array) === 0)
				return false;
			else
			{
				$params = array();
				$fields = '';
				$values = '\'';
				foreach ($row_array as $field => $value)
				{
					$fields .= $field.', ';
					$values .= '?, ';

					$params[] = $value;
				}

				$query = 'INSERT INTO '.$table.' ('.substr($fields, 0, -2).') VALUES ('.substr($values, 0, -2).')';

				return $this->direct_execute($query, $params);
			}
		}

		public function update($table, $new_data, $old_data, $options)
		{
			if (count($row_array) === 0)
				return false;
			else
			{
				$query = 'UPDATE '.$table.' SET ';
				$params = array();

				foreach ($new_data as $field => $value)
				{
					$query .= $field.' = ?, ';
					$params[] = $value;
				}

				$query = substr($query, 0, -2).' WHERE ';

				foreach ($old_data as $field => $value)
				{
					$query .= $field.' = ? AND ';
					$params[] = $value;
				}

				return $this->direct_execute(substr($query, 0, -5), $params);
			}
		}

		public function select($table, $row_array, $options)
		{
			if (count($row_array) === 0)
				return false;
			else
			{
				$query = 'SELECT * FROM '.$table.' WHERE ';
				$params = array();
				foreach ($row_array as $field => $value)
				{
					$query .= $field.' = ? AND ';
					$params[] = $value;
				}

				return $this->direct_execute(substr($query, 0, -5), $params, true);
			}
		}
	}

	class np_p2m_stmt
	{
		private $link; //stores either mysqli object, or mysqli_stmt object
		private $detail; //stores either static query, or array with the proper parameter order

		private $is_static;

		public function __construct($query_statement, $link)
		{
			$mysqli_query = preg_replace('/\$\d*/', '?', $query_statement);

			if ($mysqli_query === $query_statement)//for static prepared statements
			{
				$this->is_static = true;
				$this->link = $link;
				$this->detail = $query_statement;
			}
			else
			{
				$this->is_static = false;
				$this->link = $link->prepare($mysqli_query);
				preg_match_all('/\$(\d*)/', $query_statement, $order);
				$this->detail = $order[1];
			}
		}

		public function execute($params = NULL)
		{
			if ($this->is_static)
				$output = $this->link->query($this->detail);
			else
			{
				$types = "";
				$mysqli_params = array();
				//records the parameter types for the execution of the statement
				foreach ($this->detail as $index)
				{
					$mysqli_params[] = $params[$index - 1];

					if (is_int($params[$index - 1]))
						$types .= 'i';
					else if (is_float($params[$index - 1]))
						$types .= 'd';
					else if (is_string($params[$index - 1]))
						$types .= 's';
					else //something might've gone wrong
						$types .= ' ';
				}
				//sets parameters in mysqli statement object
				call_user_func_array(array($this->link, 'bind_param'), array_merge(array($types), np_p2m_refValues($mysqli_params)));
				//runs prepared statement
				$this->link->execute();
				//fetches result
				$output = $this->link->get_result();
			}
			return new np_p2m_result($output, $this->link->affected_rows);
		}
	}

	class np_p2m_result
	{
		private $is_proper_result;
		private $result; // will hold either mysqli_result or int containing affected_rows
		private $row_pointer = -1;

		public function __construct($mysqli_result_object, $affected_rows)
		{
			if (is_bool($mysqli_result_object))
			{
				$this->result = $affected_rows;
				$this->is_proper_result = false;
			}
			else if (get_class($mysqli_result_object) == 'mysqli_result')
			{
				$this->result = $mysqli_result_object;
				$this->is_proper_result = true;
			}
		}

		public function set_row($index)
		{
			if ($this->result->num_rows > $index)
			{
				$this->row_pointer = $index;
				$return = true;
			}
			else
				$return = false;

			return $return;
		}

		public function inc_row()
		{
			$this->row_pointer++;
			if ($this->result->num_rows == $this->row_pointer)
				$this->row_pointer = 0;
		}

		private function transfer_row_pointer()
		{
			if ($this->row_pointer < 0)
				$this->result->data_seek(0);
			else
				$this->result->data_seek($this->row_pointer);
		}

		public function num_rows()
		{
			if ($this->is_proper_result)
				return $this->result->num_rows;
			else
				return false;
		}

		public function num_fields()
		{
			if ($this->is_proper_result)
				return $this->result->field_count;
			else
				return false;
		}

		public function affected_rows()
		{
			if ($this->is_proper_result)
				return $this->result->num_rows;
			else
				return $this->result;
		}

		public function fetch_array($mode)
		{
			if ($this->is_proper_result)
			{
				$this->transfer_row_pointer();
				return $this->result->fetch_array($mode);
			}
			else
				return false;
		}

		public function fetch_all()
		{
			/*$return = array();
			$this->result->data_seek(0);
			$count = $this->result->num_rows;
			for ($i = 0; $i < $count; $i++)
				$return[$i] = $this->result->fetch_assoc();
			return $return;*/
			if ($this->is_proper_result)
				return $this->result->fetch_all(MYSQLI_ASSOC);
			else
				return false;
		}

		public function fetch_column($field_num)
		{
			if ($this->is_proper_result)
			{
				$return = array();
				$this->result->data_seek(0);
				$count = $this->result->num_rows;
				for ($i = 0; $i < $count; $i++)
					$return[$i] = $this->result->fetch_row()[$field_num];
				return $return;
			}
			else
				return false;
		}

		public function fetch_object($class_name, $params)
		{
			if ($this->is_proper_result)
			{
				$this->transfer_row_pointer();
				return $this->result->fetch_object($class_name, $params);
			}
			else
				return false;
		}

		public function get_col_num($col_name)
		{
			if ($this->is_proper_result)
			{
				$all_cols = $this->result->fetch_fields();
				$count = count($all_cols);
				$return = -1;
				for ($i = 0; $i < $count; $i++)
					if ($all_cols[$i]->name === $col_name)
						$return = $i;
				return $return;
			}
			else
				return false;
		}

		public function col_data($col_index)
		{
			if ($this->is_proper_result)
			{
				/*if (!is_int($col_index))
					$col_index = get_col_num($col_index);*/
				$this->result->field_seek($col_index);
				return $this->result->fetch_field();
			}
			else
				return false;
		}
	}

	/************************
	Adapted Postgre functions
	************************/
	function pg_connect($connection_string) //Still needs work //no connection type
	{					//PGSQL_CONNECT_FORCE_NEW, PGSQL_CONNECT_ASYNC
		$link = new np_p2m_link($connection_string);

		if ($link->is_error())
			$link = false;

		return $link;
	}

	function pg_close()
	{
		if (func_num_args() == 0)
			$link = np_p2m_link::last_connection();
		else if (func_num_args() == 1)
			$link = func_get_arg(0);

		return $link->close();
	}

	function pg_ping()
	{
		if (func_num_args() == 0)
			$link = np_p2m_link::last_connection();
		else if (func_num_args() == 1)
			$link = func_get_arg(0);

		return $link->ping();
	}

	function pg_last_error()
	{
		if (func_num_args() == 0)
			$link = np_p2m_link::last_connection();
		else if (func_num_args() == 1)
			$link = func_get_arg(0);

		return $link->last_error();
	}

	function pg_dbname()
	{
		if (func_num_args() == 0)
			$link = np_p2m_link::last_connection();
		else if (func_num_args() == 1)
			$link = func_get_arg(0);

		return $link->get_info('dbname');
	}

	function pg_host()
	{
		if (func_num_args() == 0)
			$link = np_p2m_link::last_connection();
		else if (func_num_args() == 1)
			$link = func_get_arg(0);

		return $link->get_info('host');
	}

	function pg_port()
	{
		if (func_num_args() == 0)
			$link = np_p2m_link::last_connection();
		else if (func_num_args() == 1)
			$link = func_get_arg(0);

		return $link->get_info('port');
	}

	function pg_options()
	{
		if (func_num_args() == 0)
			$link = np_p2m_link::last_connection();
		else if (func_num_args() == 1)
			$link = func_get_arg(0);

		return $link->get_info('options');
	}

	function pg_delete($link, $table, $row_array, $options = PGSQL_DML_EXEC)//TODO: allow options
	{
		//Valid options: PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC or PGSQL_DML_STRING
		return $link->delete($table, $row_array, $options);
	}

	function pg_insert($link, $table, $row_array, $options = PGSQL_DML_EXEC)//TODO: allow options
	{
		//Valid options: PGSQL_CONV_OPTS, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC or PGSQL_DML_STRING
		return $link->insert($table, $row_array, $options);
	}

	function pg_update($link, $table, $new_data, $old_data, $options = PGSQL_DML_EXEC)//TODO: allow options
	{
		//Valid options: PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC or PGSQL_DML_STRING
		return $link->update($table, $new_data, $old_data, $options);
	}

	function pg_select($link, $table, $row_array, $options = PGSQL_DML_EXEC)//TODO: allow options
	{
		//Valid options: PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC or PGSQL_DML_STRING
		return $link->select($table, $row_array, $options);
	}

	function pg_query()
	{
		if (func_num_args() == 1)
		{
			$link = np_p2m_link::last_connection();
			$query = func_get_arg(0);
		}
		else if (func_num_args() == 2)
		{
			$link = func_get_arg(0);
			$query = func_get_arg(1);
		}

		return $link->query($query);
	}

	function pg_query_params()
	{
		if (func_num_args() == 2)
		{
			$link = np_p2m_link::last_connection();
			$query = func_get_arg(0);
			$params = func_get_arg(1);
		}
		else if (func_num_args() == 3)
		{
			$link = func_get_arg(0);
			$query = func_get_arg(1);
			$params = func_get_arg(2);
		}

		return $link->query_params($query, $params);
	}

	function pg_prepare()
	{
		if (func_num_args() == 2)
		{
			//defaults to last created connection
			$link = np_p2m_link::last_connection();
			$name = func_get_arg(0);
			$query = func_get_arg(1);
		}
		else if (func_num_args() == 3)
		{
			$link = func_get_arg(0);
			$name = func_get_arg(1);
			$query = func_get_arg(2);
		}

		$link->prepare($name, $query);
	}

	function pg_execute()
	{
		if (func_num_args() == 2)
		{
			//defaults to last created connection
			$link = np_p2m_link::last_connection();
			$name = func_get_arg(0);
			$params = func_get_arg(1);
		}
		else if (func_num_args() == 3)
		{
			$link = func_get_arg(0);
			$name = func_get_arg(1);
			$params = func_get_arg(2);
		}

		return $link->execute($name, $params);
	}

	function pg_escape_string()
	{
		if (func_num_args() == 1)
		{
			$link = np_p2m_link::last_connection();
			$string = func_get_arg(0);
		}
		else if (func_num_args() == 2)
		{
			$link = func_get_arg(0);
			$string = func_get_arg(1);
		}

		return $link->escape_string($string);
	}

	function pg_escape_literal()
	{
		if (func_num_args() == 1)
		{
			$link = np_p2m_link::last_connection();
			$string = func_get_arg(0);
		}
		else if (func_num_args() == 2)
		{
			$link = func_get_arg(0);
			$string = func_get_arg(1);
		}

		return "'".$link->escape_string($string)."'";
	}

	function pg_num_rows($data)
	{
		return $data->num_rows();
	}

	function pg_affected_rows($data)
	{
		return $data->affected_rows();
	}

	function pg_fetch_result()
	{
		if (func_num_args() == 2)
		{
			$data = func_get_arg(0);
			$data->inc_row();
			$field = func_get_arg(1);
		}
		else if (func_num_args() == 3)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
			$field = func_get_arg(2);
		}

		return $data->fetch_array(MYSQLI_BOTH)[$field];
	}

	function pg_fetch_row()
	{
		if (func_num_args() == 1)
		{
			$data = func_get_arg(0);
			$data->inc_row();
		}
		else if (func_num_args() == 2)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
		}

		return $data->fetch_array(MYSQLI_NUM);
	}

	function pg_fetch_array()
	{
		if (func_num_args() == 1)
		{
			$data = func_get_arg(0);
			$data->inc_row();
			$option = PGSQL_BOTH;
		}
		else if (func_num_args() == 2)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
			$option = PGSQL_BOTH;
		}
		else if (func_num_args() == 3)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
			$option = func_get_arg(2);
		}

		if ($option == PGSQL_NUM)
			$mysql_option = MYSQLI_NUM;
		else if ($option == PGSQL_ASSOC)
			$mysql_option = MYSQLI_ASSOC;
		else if ($option == PGSQL_BOTH)
			$mysql_option = MYSQLI_BOTH;

		return $data->fetch_array($mysql_option);
	}

	function pg_fetch_assoc()
	{
		if (func_num_args() == 1)
		{
			$data = func_get_arg(0);
			$data->inc_row();
		}
		else if (func_num_args() == 2)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
		}

		return $data->fetch_array(MYSQLI_ASSOC);
	}

	function pg_fetch_all_columns($data, $col_index = 0)
	{
		return $data->fetch_column($col_index);
	}

	function pg_fetch_all($data)
	{
		return $data->fetch_all();
	}

	function pg_fetch_object()//NB: result_type is Ignored and deprecated
	{
		if (func_num_args() == 1)
		{
			$data = func_get_arg(0);
			$data->inc_row();
			$class_name = 'stdClass';
			$params = null;
		}
		else if (func_num_args() == 2)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
			$class_name = 'stdClass';
			$params = null;
		}
		else if (func_num_args() == 3)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
			$class_name = func_get_arg(2);
			$params = null;
		}
		else if (func_num_args() == 4)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
			$class_name = func_get_arg(2);
			$params = func_get_arg(3);
		}

		return $data->fetch_object($class_name, $params);
	}

	function pg_num_fields($data)
	{
		return $data->num_fields();
	}

	function pg_field_is_null()
	{
		if (func_num_args() == 2)
		{
			$data = func_get_arg(0);
			//current row is fetched by default
			$field = func_get_arg(1);
		}
		else if (func_num_args() == 3)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
			$field = func_get_arg(2);
		}

		//TODO: Include error handling
		if ($data->fetch_array(MYSQLI_BOTH)[$field] === null)
			$return = 1;
		else
			$return = 0;

		return $return;
	}

	function pg_field_name($data, $col_index)
	{
		return $data->col_data($col_index)->name;
	}

	function pg_field_num($data, $col_name)
	{
		return $data->get_col_num();
	}

	function pg_field_size($data, $col_index)
	{
		return $data->col_data($col_index)->length;
	}

	function pg_field_table($data, $col_index, $is_oid = false)//TODO: support OID
	{
		return $data->col_data($col_index)->table;
	}

	function pg_field_type($data, $col_index)
	{
		return $data->col_data($col_index)->type;
	}

	function pg_field_prtlen()
	{
		if (func_num_args() == 2)
		{
			$data = func_get_arg(0);
			//current row is fetched by default
			$field = func_get_arg(1);
		}
		else if (func_num_args() == 3)
		{
			$data = func_get_arg(0);
			$data->set_row(func_get_arg(1));
			$field = func_get_arg(2);
		}

		return strlen($data->fetch_array(MYSQLI_BOTH)[$field]);
	}
}