<?php

/**
* DBクラス
*
* PHP version 5
*
* @package    components.db
* @author     stk2k <stk2k@sazysoft.com>
* @copyright  2008 stk2k, sazysoft
*/
class charcoal_DB
{
	private $_source;
	private $_builder;

	/*
	 *	コンストラクタ
	 */
	public function __construct()
	{
		if ( charcoal_Profile::getBoolean('USE_DB') ){
			$data_source_name = charcoal_Profile::getString( 'DB_DATA_SOURCE' );
			$this->_source = charcoal_DataSourceFactory::create( $data_source_name );

			$builder_name = $this->_source->getBackend();
			if ( $builder_name ){
				$this->_builder = charcoal_SQLBuilderFactory::create( $builder_name );
			}
		}
	}

	/*
	 *	データソース
	 */
	public function getDataSource()
	{
		return $this->_source;
	}

	/*
	 *	SQLビルダ
	 */
	public function getSQLBuilder()
	{
		return $this->_builder;
	}

	/*
	 *	保存
	 */
	public  function save( charcoal_ITableModel $model, charcoal_DTO $dto, $converter_str = NULL )
	{
		$conv = $converter_str ? charcoal_EncodingConverter::fromString($converter_str) : NULL;

		try{
			// キーフィールドが指定されていればUPDATE、指定されていなければINSERTと判断
			$pk = $model->getPrimaryKeyField();
			$is_new = !isset($dto[$pk]) || is_null($dto[$pk]) ? TRUE : FALSE;

			// SQLを作成
			if ( $is_new ){
				list( $sql, $params ) = $this->_builder->buildInsertSQL( $model, $dto );
			}
			else{
				list( $sql, $params ) = $this->_builder->buildUpdateSQL( $model, $dto );
			}

			// 文字コード変換
			if ( $conv ){
				foreach( $params as $key => $value ){
					if ( is_string($value) ){
						$value = $conv->convertEncoding( $value );
						$params[$key] = $value;
					}
				}
			}

			// SQL実行
			$this->_source->prepareExecute( $sql, $params );

			// IDを返す
			if ( $is_new ){
				$sql = $this->_builder->buildLastIdSQL();
				
				// 実行
				$result = $this->_source->prepareExecute( $sql );

				// フェッチ
				$row = $this->_source->fetchArray( $result );

				$new_id = $row[0];
			}
			else{
				$new_id = $dto[$pk];
			}

			return $new_id;
		}
		catch ( Exception $e ){
			throw new charcoal_DBException( "DB#save failed", $e );
		}
	}

	/*
	 *	SQL実行(INSERT/DELETE/UPDATE)
	 */
	public  function execute( $sql, $params = NULL )
	{
		try{
			// 実行
			$this->_source->prepareExecute( $sql, $params );
		}
		catch ( Exception $e ) {

			// 上位にthrow
			throw new charcoal_DBException( "exec_SQL failed", $e );
		}
	}

	/*
	 *	SQL実行(SELECT)
	 */
	public  function query( $sql, $params = NULL )
	{
		// コンテナからDataSourceコンポーネントを取得
		$ds = $this->_source;

		$a = array();

		try{

			// 実行
			$result = $ds->prepareExecute( $sql, $params );

			// フェッチ
			while( $row = $ds->fetchAssoc( $result ) ){
				$a[] = $row;
			}
		}
		catch ( Exception $e ) {

			// 上位にthrow
			throw new charcoal_DBException( "exec_SQL failed", $e );
		}

		return $a;
	}

	/*
	 *	SQL検索
	 */
	public  function findBySQL( charcoal_ITableModel $model, $sql, $params = NULL, $converter_str = NULL ) 
	{
		$conv = $converter_str ? charcoal_EncodingConverter::fromString($converter_str) : NULL;

		$a = array();

		// DataSourceを取得
		$ds = $this->_source;

		try{
			// フィールド一覧
			$field_list = $model->getFieldList();

			// 実行
			$result = $ds->prepareExecute( $sql, $params );

			// フェッチ
			while( $row = $ds->fetchAssoc( $result ) )
			{
				$result_row = new charcoal_DTO();
				foreach ($field_list as $name) 
				{
					// フィールド値
					$value = $row[ $name ];
					// 文字列ならエンコーディング変換
					if ( $conv && is_string($value) ){
						$value = $conv->convertEncoding($value);
					}
					// 返却用配列に設定
					$result_row->$name = $value;
				}
				$a[] = $result_row;
			}
		}
		catch ( Exception $e ) {

			// 上位にthrow
			throw new charcoal_DBException( "findBySQL failed", $e );
		}

		return $a;
	}

	/*
	 *	最初の１件取得
	 */
	public  function findFirst( $dto, charcoal_SQLCriteria $condition = NULL, $converter_str = NULL ) 
	{
		if ( !$condition ){
			$condition = new charcoal_SQLCriteria();
		}

		// LIMIT=1に
		$condition->setLimit( 1 );

		// 検索実行
		$result = $this->findAll( $dto, $condition, $converter_str );

		return array_shift($result);
	}

	/*
	 *	全件取得
	 */
	public  function findAll( charcoal_ITableModel $model, charcoal_SQLCriteria $criteria = NULL, $converter_str = NULL ) 
	{
		try{
			// テーブル名
			$table = $model->getTableName();

			// SQLビルダを取得
			$builder = $this->_builder;

			// SQLの作成
			$sql = $builder->buildSelectSQL( $model, $criteria );

			// パラメータ
			$params = $criteria ? $criteria->getParams() : NULL;

			// 検索実行
			$result = $this->findBySQL( $model, $sql, $params, $converter_str );

			return $result;
		}
		catch ( Exception $e ){
			throw new charcoal_DBException( "DB#findAll failed", $e );
		}
	}

	/*
	 *	１つのフィールドのみで検索
	 */
	public  function findAllBy( charcoal_ITableModel $model, $field, $value, charcoal_SQLCriteria $criteria = NULL, $converter_str = NULL )
	{
		if ( !$criteria ){
			$criteria = new charcoal_SQLCriteria();
		}

		$criteria->setWhereClause( "$field = ?" );
		$criteria->setParams( array( $value ) );

		$a = $this->findAll( $model, $criteria, $converter_str );

		return $a;
	}

	/*
	 *	IDから検索(１つだけ指定した場合)
	 */
	public  function findByID( charcoal_ITableModel $model, $id, $converter_str = NULL ) 
	{
		// １つだけ指定した場合
		$where_clause = 'id = ?';
		$params = array( $id );

		$criteria = new charcoal_SQLCriteria( $where_clause, $params );

		$a = $this->findAll( $model, $criteria, $converter_str );

		return array_shift($a);
	}

	/*
	 *	IDから検索
	 */
	public  function findAllByID( charcoal_ITableModel $model, $id_array ) 
	{
		// 複数個指定した場合
		$where = array();
		$params = array();
		
		foreach( $id_array as $id ){
			$where[] = "?";
			$params[] = $id;
		}

		$where_clause = "id in (" . implode(",",$where) . ")";

		$condition = new charcoal_SQLCriteria( $where_clause, $params );

		$a = $this->findAll( $dto, $condition );

		return $a;
	}

	/*
	 *	削除（１つだけ指定した場合）
	 */
	public  function destroyById( charcoal_ITableModel $model, $id ) 
	{
		try{
			// キーフィールド名
			$pk = $model->getPrimaryKeyField();

			// SQLパラメータ
			$params = array( "$id" );

			// 条件
			$criteria = new charcoal_SQLCriteria();
			$criteria->setWhereClause( "$pk = ?" );
			$criteria->setParams( $params );

			// SQLビルダを使いSQLを生成
			$sql = $this->_builder->buildDeleteSQL( $model, $criteria );

			$this->execute( $sql, $params );
		}
		catch ( Exception $e ){
			throw new charcoal_DBException( "DB#destroyById failed", $e );
		}
	}

	/*
	 *	削除（複数を指定した場合）
	 */
	public  function destroyAllById( charcoal_ITableModel $model, $id_array ) 
	{
		try{
			// テーブル名
			$table = self::getTableNameFromDTO( $dto );

			// 複数個指定した場合
			$where = array();
			$params = array();
			
			foreach( $id_array as $id ){
				$where[] = "?";
				$params[] = $id;
			}

			$sql = "delete from $table where id in (" . implode(",",$where) . ")";

			$this->execute( s($sql), a($params) );
		}
		catch ( Exception $e ){
			throw new charcoal_DBException( "DB#destroyById failed", $e );
		}
	}

	/*
	 *	１つのフィールドに合致するレコードを全削除
	 */
	public  function destroyBy( charcoal_ITableModel $model, $field, $value )
	{
		$where_clause = "$field = ?";
		$params = array( $value );

		$this->destroyAll( $dto, $where_clause, $params );
	}


	/*
	 *	条件に合致するレコードを全削除
	 */
	public  function destroyAll( charcoal_ITableModel $model, $where_clause = NULL, $params = NULL ) 
	{
		try{
			// テーブル名
			$table = self::getTableNameFromDTO( $dto );

			$sql = "delete from $table where $where_clause";

			$this->execute( $sql, $params );
		}
		catch ( Exception $e ){
			throw new charcoal_DBException( "DB#destroyAll failed", $e );
		}
	}

	/*
	 *	テーブルのレコード数を取得
	 */
	public  function count( charcoal_ITableModel $model, $where_clause = NULL, $params = NULL ) 
	{
		try{
			if ( $where_clause ){
				$criteria = new charcoal_SQLCriteria();
				$criteria->setWhereClause( $where_clause );
				$criteria->setParams( $params );
			}
			else{
				$criteria = NULL;
			}

			// SQL
			$sql = $this->_builder->buildCountSQL( $model, $criteria );

			// SQL実行
			$result = $this->_source->prepareExecute( $sql, $params );

			// フェッチ
			$rows = $this->_source->fetchArray( $result );

			return $rows[0];
		}
		catch ( Exception $e ){
			throw new charcoal_DBException( "DB#count failed", $e );
		}
	}

	/*
	 *	特定フィールドの最大値を取得
	 */
	public  function max( charcoal_ITableModel $model, $field_name, $where_clause = NULL, $params = NULL ) 
	{
		try{
			if ( $where_clause ){
				$criteria = new charcoal_SQLCriteria();
				$criteria->setWhereClause( $where_clause );
				$criteria->setParams( $params );
			}
			else{
				$criteria = NULL;
			}

			// SQL
			$sql = $this->_builder->buildMaxSQL( $model, $field_name, $criteria );

			// SQL実行
			$result = $this->_source->prepareExecute( $sql, $params );

			// フェッチ
			$rows = $this->_source->fetchArray( $result );

			return $rows[0];
		}
		catch ( Exception $e ){
			throw new charcoal_DBException( "DB#count failed", $e );
		}
	}


	/*
	 *	DBを作成
	 */
	public  function createDatabase( $db_name, $charset ) 
	{
		try{
			// SQLビルダを使いSQLを生成
			$sql = $this->_builder->buildCreateDatabaseSQL( $db_name, $charset );

			// SQL実行
			$this->_source->execute( $sql );

		}
		catch ( Exception $e ){
			throw new charcoal_DBException( "DB#createDB failed", $e );
		}
	}

	/*
	 *	テーブルを作成
	 */
	public  function createTable( charcoal_ITableModel $model ) 
	{
		try{
			// SQLビルダを使いSQLを生成
			$sql = $this->_builder->buildCreateTableSQL( $model );

			// SQL実行
			$this->_source->execute( $sql );

		}
		catch ( Exception $e ){
			throw new charcoal_DBException( "DB#createDB failed", $e );
		}
	}
}
