<?php
/*
 * database2/connection/Pgsql.class.php
 * 
 * CopyRight(C) 2010 Shopformer Development Team. All Right Reserved
 * URL: http://sourceforge.jp/projects/shopformer/
 * 
 * 
 * Mail: m_nakashima@users.sourceforge.jp
 * Auther: Masanori Nakashima
 * Last Update: 2010-06-18
 */
require_once ( dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'AbstractConnection.class.php' );
$GLOBALS['DEF_DATABASE2_CONNECTION_PG_ENCODINGS']	= array(
	'EUC_JP'	=> 'EUC-JP',
	'SJIS'		=> 'SJIS-win',
	'UTF8'		=> 'UTF-8',
);
/**
 * データベース接続オブジェクトクラス - PostgreSQL用
 * 
 * 抽象クラスdatabase_Connectionの実装クラスです。
 * MySQLに接続して接続情報を保持し、各種インターフェースを提供します。
 * 
 * @package database データベースパッケージ
 * @subpackage connection データベースコネクションパッケージ
 * @version 2.0.0
 * @author  <m_nakashima@users.sourceforge.jp>
 * @since PHP 4.0
 * @access protected
 * @see database_Connection
 */
class database2_connection_Pgsql extends database2_AbstractConnection {
	
	/**
	 * コンストラクタ
	 * 
	 * database_Connectionクラスのget_instanceメソッドでインスタンスを生成して利用します。
	 * 本メソッドで直接インスタンスを生成して利用しないでください。
	 * @access protected
	 */
	function database2_connection_Pgsql() {
	}
	/**
	 * データベースに接続します
	 * @return boolean
	 * @access public
	 */
	function _connect( $databaseName, $databaseUser, $databasePass, $host=null, $port=null, $dbencoding='auto' ) {
		if( is_null( $host ) || strlen(trim($host)) == 0 ) {
			$host	= 'localhost';
		}
		if( is_null($port) || strlen(trim($port)) == 0 ) {
			$port	= '5432';
		}
		$connect_strings	= 'host='.$host
			. ' port=' .$port
			. ' dbname=' . $databaseName
			. ' user=' . $databaseUser
			. ' password=' . $databasePass
		;
		if( !function_exists('pg_connect') ) {
			$error	= 'データベースに接続できませんでした。pg_connect関数が定義されていません。';
			$this->error($error);
			return false;
		} else {
			if( $this->resource_id = @pg_connect($connect_strings) ) {
				if( $dbencoding == 'auto' ) {
					$this->db_encoding	= $this->_selectDatabaseEncoding($databaseName);
				} else if( strlen($dbencoding) > 0 ) {
					$this->db_encoding	= $dbencoding;
				}
				return true;
			} else {
				$error	= 'データベースに接続できませんでした ';
				$this->error($error);
				return false;
			}
		}
	}
	/**
	 * データベースから切断します
	 * @param boolean $force
	 * @return 
	 * @access public
	 */
	function _disconnect() {
		if( $this->resource_id ) {
			return @pg_close( $this->resource_id );
		}
		return false;
	}
	/**
	 * 文字列をquoteします
	 * @param string $value 文字列
	 * @param string $type カラム型
	 * @param boolean $quote quoteする場合はtrue、しない場合はfalse
	 * @param boolean $escape_wildcards ワイルドカードをquoteする場合はtrue、しない場合はfalse
	 * @return string quoteした文字列
	 * @access public
	 */
	function _quote( $value, $type = null, $quote = true, $escape_wildcards = false ) {
		if( is_null($value) || strlen($value) == 0 ) {
			return 'NULL';
		} else {
			if( $this->db_encoding && $this->db_encoding != $this->encoding ) {
				$value	= mb_convert_encoding( $value, $this->db_encoding, $this->encoding );
			}
			$value	= pg_escape_string($value);
			if( $escape_wildcards ) {
				$value	= str_replace('%','\\%',$value);
				$value	= str_replace('_','\\_',$value);
			}
			if( $quote ) {
				$value	= "'".$value."'";
			}
			return $value;
		}
	}
	/**
	 * Transactionを開始します
	 * @param string $savepoint savepoint名
	 * @return 
	 * @access public
	 */
	function _beginTransaction($savepoint = null){
		if ($this->in_transaction) {
			return true;
		}
		$result =& $this->_query('BEGIN');
		$this->in_transaction = true;
		return true;
	}

	/**
	 * トランザクションをコミットします
	 * @param string $savepoint savepoint名
	 * @return 
	 * @access public
	 */
	function _commit( $savepoint = null ) {
		$result =& $this->_query('COMMIT');
		$this->in_transaction = false;
		return true;
	}
	/**
	 * トランザクションをロールバックします
	 * @param string $savepoint savepoint名
	 * @return 
	 * @access public
	 */
	function _rollback( $savepoint = null ) {
		$result =& $this->_query('ROLLBACK');
		$this->in_transaction = false;
		return true;
	}
	/**
	 * 渡されたSQLクエリ文を実行します
	 * @param string $query SQLクエリ
	 * @return 
	 * @access public
	 */
	function _query( $query ) {
		$result	= @pg_query($query);
		if( false === $result ) {
			$this->error( pg_last_error($this->resource_id).':' . $query );
		}
		return $result;
	}
	/**
	 * 渡されたSQLクエリ文を実行して全ての結果を取得します
	 * @param string $query SQLクエリ
	 * @param mixed $fetchtype 使用する取得モード。0=ハッシュ, 1=配列, AbstractDataの拡張クラス=オブジェクト配列
	 * @return mixed 入れ子状の配列、あるいは失敗した場合にMDB_Errorを返します。
	 * @access public
	 */
	function _queryAll( $query, $fetchtype=null, $setOrg=true, $setDivided=true ) {
		if( preg_match('/^[sS][eE][lL][eE][cC][tT]/',trim($query)) > 0 ) {
			// offsetの設定
			if( preg_match('/^[0-9]+$/',$this->offset ) > 0 ) {
				$query	.= ' OFFSET ' . $this->offset;
			}
			// limitの設定
			if( preg_match('/^[0-9]+$/',$this->limit ) > 0 ) {
				$query	.= ' LIMIT ' . $this->limit;
			}
		} else {
			$this->error('queryAllはSELECT文でしか利用できません。:' . $query);
			return false;
		}
		$result	= $this->_query( $query );
		if( false === $result ) {
			$this->error(pg_last_error($this->resource_id).':'.$query);
			return false;
		} else {
			$this->limit	= null;
			$this->offset	= null;
			if( is_object( $fetchtype ) && ( is_a( $fetchtype, 'database2_AbstractData' ) || is_a( $fetchtype, 'database2_AbstractUser' ) ) ) {
				// Daoオブジェクトが指定されていた場合
				$className		= get_class( $fetchtype );
				$objectArray	= array();
				while ($row = pg_fetch_assoc($result)) {
					if( $this->db_encoding && $this->db_encoding != $this->encoding ) {
						mb_convert_variables($this->encoding,$this->db_encoding,$row);
					}
					$object		= new $className;
					$this->_setRowToFields( $row, $object, $setOrg, $setDivided );
					array_push( $objectArray, $object );
				}
				pg_free_result( $result );
				return $objectArray;
			} else if( 1 == $fetchtype ) {
				// 配列で取得
				$rows	= array();
				while ($row = pg_fetch_row($result)) {
					if( $this->db_encoding && $this->db_encoding != $this->encoding ) {
						mb_convert_variables($this->encoding,$this->db_encoding,$row);
					}
					array_push( $rows, $row );
				}
				pg_free_result( $result );
				return $rows;
			} else {
				// デフォルトはハッシュで取得
				$rows	= array();
				while ($row = pg_fetch_assoc($result)) {
					if( $this->db_encoding && $this->db_encoding != $this->encoding ) {
						mb_convert_variables($this->encoding,$this->db_encoding,$row);
					}
					array_push( $rows, $row );
				}
				pg_free_result( $result );
				return $rows;
			}
		}
	}
	/**
	 * 渡されたSQLクエリ文を実行して1レコードのみ結果を取得します
	 * レコードがなかった場合または、1レコードに絞り込めなかった場合はfalseを返します
	 * @param string $query SQLクエリ
	 * @param mixed $fetchtype 使用する取得モード。0=ハッシュ, 1=配列, AbstractDataの拡張クラス=オブジェクト
	 * @return 
	 * @access public
	 */
	function _queryRow( $query, $fetchtype=null, $setOrg=true, $setDivided=true ) {
		$result	= $this->_query( $query );
		if( false === $result ) {
			$this->error(pg_last_error($this->resource_id).':'.$query);
			return false;
		} else {
			$count = pg_num_rows( $result );
			if( -1 == $count ) {
				$this->error(pg_last_error($this->resource_id).':'.$query);
				return false;
			} else if( 0 == $count ){
				$this->error('該当するデータが存在しませんでした:'.$query);
				return false;
			} else if( 1 == $count ){
				$row				= null;
				if( is_object( $fetchtype ) && ( is_a( $fetchtype, 'database2_AbstractData' ) || is_a( $fetchtype, 'database2_AbstractUser' ) ) ) {
					$row	= pg_fetch_assoc($result);
					if( $this->db_encoding && $this->db_encoding != $this->encoding ) {
						mb_convert_variables($this->encoding,$this->db_encoding,$row);
					}
					// Daoオブジェクトが指定されていた場合
					$className		= get_class( $fetchtype );
					$object			= new $className;
					$this->_setRowToFields( $row, $object, $setOrg, $setDivided );
					return $object;
				} else if( 1 == $fetchtype ) {
					// 配列で取得
					$row	= pg_fetch_row($result);
					if( $this->db_encoding && $this->db_encoding != $this->encoding ) {
						mb_convert_variables($this->encoding,$this->db_encoding,$row);
					}
					pg_free_result( $result );
					return $row;
				} else {
					// デフォルトはハッシュで取得
					$row	= pg_fetch_assoc($result);
					if( $this->db_encoding && $this->db_encoding != $this->encoding ) {
						mb_convert_variables($this->encoding,$this->db_encoding,$row);
					}
					pg_free_result( $result );
					return $row;
				}
			} else {
				$this->error('該当するデータを一意に絞り込めませんでした:'.$query);
				return false;
			}
		}
	}
	/**
	 * 渡されたSQLクエリ文を実行して1カラム分のみ結果を取得します
	 * @param string $query SQLクエリ
	 * @return 
	 * @access public
	 */
	function _queryOne( $query ) {
		$result	= $this->_query( $query );
		if( false === $result ) {
			$this->error(pg_last_error($this->resource_id).':'.$query);
			return false;
		} else {
			if( pg_num_rows($result) > 0 && pg_num_fields($result) > 0 ) {
				// 結果が一つ以上あるなら
				$str		= pg_fetch_result( $result, 0, 0 );
				if( $this->db_encoding && $this->db_encoding != $this->encoding ) {
					$str	= mb_convert_encoding( $str, $this->encoding, $this->db_encoding );
				}
				pg_free_result( $result );
				return $str;
			} else {
//				$this->error('該当するデータが存在しませんでした:'.$query);
//				pg_free_result( $result );
//				return false;
				return '';
			}
		}
	}
	/**
	 * テーブル名称を指定して該当するテーブル情報ハッシュを取得します。
	 * 【テーブル情報ハッシュの構造】
	 * array( [フィールド名] => [フィールド詳細情報ハッシュ] )
	 * 
	 * 【フィールド詳細情報ハッシュの構造】
	 * array(
	 * 		'type'			=> 'int/char/varchar/text...',
	 * 		'size'			=> 数値
	 * 		'not_null'		=> true/false
	 * 		'key'			=> 'primary/unique'
	 * 		'is_serial'		=> true/false
	 * 		'has_default'	=> true/false
	 * )
	 */
	function _getTableInformationHashByName( $tableName ) {
		$sql	= 'SELECT '
			.'pg_attribute.attname AS field_name, '
			.'CASE '
			.'WHEN pg_type.typname LIKE \'int%\' THEN \'int\' '
			.'WHEN pg_type.typname LIKE \'bpchar%\' THEN \'char\' '
			.'ELSE pg_type.typname '
			.'END AS type, '
			.'pg_attribute.attlen AS size, '
			.'pg_attribute.attnotNULL AS not_null, '
			.'CASE '
			.'WHEN pg_constraint.contype=\'p\' THEN \'primary\' '
			.'WHEN pg_constraint.contype=\'u\' THEN \'unique\' '
			.'ELSE null '
			.'END AS key, '
			.'CASE '
			.'WHEN pg_attrdef.adsrc LIKE \'%\' || pg_attribute.attname || \'%\' THEN \'t\' '
			.'ELSE \'f\' '
			.'END AS is_serial, '
			.'pg_attribute.atthasdef AS has_default '
			.'FROM pg_attribute '
			.'INNER JOIN pg_type ON pg_type.oid=pg_attribute.atttypid '
			.'INNER JOIN pg_class ON pg_class.oid=pg_attribute.attrelid '
			.'LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid=pg_class.oid AND pg_attrdef.adsrc LIKE \'nextval%\' '
			.'LEFT OUTER JOIN pg_indexes ON pg_indexes.indexdef LIKE \'CREATE UNIQUE INDEX%\' AND  pg_indexes.indexname LIKE '.$this->_quote('%'.$tableName.'\_').' || pg_attribute.attname || \'%\' AND pg_indexes.tablename='.$this->_quote($tableName).' '
			.'LEFT OUTER JOIN pg_constraint ON pg_constraint.conname=pg_indexes.indexname '
			.'WHERE '
			.'pg_class.relname='.$this->_quote($tableName).' '
			.'AND '
			.'pg_attribute.attnum>0 '
		;
		$result		= $this->_queryAll( $sql, null, 'hash' );
		if( $result === false ) {
			$this->error('データベーステーブル情報の取得に失敗しました。:'.pg_last_error($this->resource_id).':'.$sql);
			return false;
		} else {
			$this->debug( '[EXECUTED SELECT] ' . $sql );
			$fieldHash	= array();
			foreach( $result as $row ) {
				$fieldName	= $row['field_name'];
				if( preg_match('/^[tT](|[rR][uU][eE])$/',trim($row['not_null'])) > 0 ) {
					$row['not_null']	= true;
				} else {
					$row['not_null']	= false;
				}
				if( preg_match('/^[tT](|[rR][uU][eE])$/',trim($row['is_serial'])) > 0 ) {
					$row['is_serial']	= true;
				} else {
					$row['is_serial']	= false;
				}
				if( preg_match('/^[tT](|[rR][uU][eE])$/',trim($row['has_default'])) > 0 ) {
					$row['has_default']	= true;
				} else {
					$row['has_default']	= false;
				}
				$fieldHash[$fieldName]	= $row;
			}
			return $fieldHash;
		}
	}
	/**
	 * テーブル存在確認メソッド
	 */
	function _existsTable( $tableName ) {
		$sql	= 'SELECT COUNT(*) FROM pg_tables WHERE tablename='.$this->_quote($tableName);
		$result	= $this->_queryOne( $sql );
		if( false === $result || $result == 0 ) {
			$this->error('テーブル存在確認に失敗しました。:'.pg_last_error($this->resource_id).':'.$sql);
			return false;
		} else {
			$this->debug( '[EXECUTED SELECT] ' . $sql );
			return true;
		}
	}
	/**
	 * データベース存在確認メソッド
	 */
	function _existsDatabase( $databaseName ) {
		$sql	= 'SELECT COUNT(*) FROM pg_database WHERE datname='.$this->_quote($databaseName);
		$result	= $this->_queryOne( $sql );
		if( false === $result || $result == 0 ) {
			$this->error('データベース存在確認に失敗しました。:'.pg_last_error($this->resource_id).':'.$sql);
			return false;
		} else {
			$this->debug( '[EXECUTED SELECT] ' . $sql );
			return true;
		}
	}
	/**
	 * 指定データベースのエンコード設定を取得します。
	 */
	function _selectDatabaseEncoding($databaseName) {
		$sql	= 'SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname='.$this->_quote($databaseName);
		$result	= $this->_queryOne( $sql );
		if( false === $result || strlen($result) == 0 ) {
			$this->error('データベース文字コードの確認に失敗しました。:'.pg_last_error($this->resource_id).':'.$sql);
			return false;
		} else {
			$this->debug( '[EXECUTED SELECT] ' . $sql );
			if( isset($GLOBALS['DEF_DATABASE2_CONNECTION_PG_ENCODINGS'][$result]) ) {
				return $GLOBALS['DEF_DATABASE2_CONNECTION_PG_ENCODINGS'][$result];
			}
			return $result;
		}
	}
	/**
	 * 対象テーブルのバキューム処理を行います
	 * postgres固有
	 */
	function _pgVacuumTable( $tableName ) {
		$sql	= 'VACUUM ANALYZE '. $tableName;
		$result	= $this->_query( $sql );
		if( $result !== false ) {
			return true;
		} else {
			$this->error('データベース存在確認に失敗しました。:'.pg_last_error($this->resource_id).':'.$sql);
			return false;
		}
	}
}
?>