/*
 * Copyright (C) 2005 NTT DATA Corporation
 * 
 */
package org.postgresforest.vm;

import java.net.SocketException;
import java.sql.Connection;
import java.sql.SQLException;

import org.postgresforest.Driver;
import org.postgresforest.core.ParameterList;
import org.postgresforest.core.Query;
import org.postgresforest.core.QueryExecutor;
import org.postgresforest.jdbc2.AbstractJdbc2Connection;
import org.postgresforest.util.GT;
import org.postgresforest.util.PSQLException;
import org.postgresforest.vm.core.QueryExecutorImpl;
import org.postgresforest.vm.core.SimpleParameterList;
import org.postgresforest.vm.core.SimpleQuery;
import org.postgresforest.vm.core.SimpleResultHandler;
import org.postgresforest.vm.err.ForestSQLState;
import org.postgresforest.vm.gsc.GscData;
import org.postgresforest.vm.jdbc.ForestConnection;


/**
 * クエリー実行メインスレッドクラス。
 * Statementクラスのexcute系の関数実行時に生成され、コネクションとクエリーを受け取って
 * StatementExecuteSubを生成・実行し。結果を返す。
 *
 */
public class StatementSub extends AbstractForestThread 
{

	/** 実行結果 OK */
	public static final int STAT_OK = 0;
	/** 実行結果 NG */
	public static final int STAT_NG = -1;

	/** 接続先コネクション */
	protected AbstractJdbc2Connection m_con;
	/** 実行クエリー */
	protected String m_sql;

	/** クエリ情報 */
	protected QueryInfo m_qi;
	/** グローバルシステムカタログ情報 */
	protected GscData m_gsc;
	/** クエリリライタ.リトライのコネクション取得用 */
	protected ReWriter m_rw;
	/** 例外クラス */
	public SQLException m_errObj;
	/** 実行ステータス */
	public int m_errCode = STAT_OK;

	/** エラーリトライカウント @since 1.1 */
	public int m_retry = 0;

	/** スレッド管理クラス（自己解放用）@since 1.1 */
	protected StatementThreadMng m_threadMng;
	
	/** スレッド自己解放フラグ @since 1.1 */
	protected boolean m_selfClose;
	
	/*
	 * 並列処理＆更新非同期モードの際に
	 * 切り離し判定を行うために利用されるフラグ。
	 */
	protected boolean m_serverBrokenChk;

	/** クエリー実行スレッドクラスの生成 @since 1.1 */
	protected StatementExecuteSub m_ses;

	/** 処理待機用同期オブジェクト @since 1.1R2 */
	protected Lock m_lock;

	/** 呼び出し元クエリーExecuter */
	protected QueryExecutorImpl m_queryExecutor;

	/** 実行クエリー */
	protected SimpleQuery m_query;

	/** 実行クエリーバインド変数 */
	protected SimpleParameterList m_parameters;

	/** 実行クエリー MAXROWS */
	protected int m_maxRows;

	/** 実行クエリー fetchSize*/
	protected int m_fetchSize;

	/** 実行クエリー flags */
	protected int m_flags;
	
	/** メタデータアクセスフラグ */
	protected boolean m_metadata;

	
	//@@DEBUG
	static int id = 0;
	private int m_retryCount;
	
	

	/**
	 */
	public StatementSub( StatementThreadMng mng)
	{
		
		//Ver2.1	
		super(mng.getLogUtil());
		//DEBUG
		if(Driver.logInfo){
			setName("StmSub" + id++);
		}

		m_ses = new StatementExecuteSub(getName());

		m_threadMng = mng;

	}

	/**
	 * クエリー実行に必要なパラメータを受け取りインスタンスの生成 
	 * @param srcst - 呼び出し元Statementクラス
	 * @param srcQueryInfo クエリー情報クラス
	 * @param srcGSCdata グローバルシステムカタログ情報
	 * @param srcRewriter クエリリライタ
	 * @param lock 処理待機用同期オブジェクト
	 */
	public void execute(SimpleQuery query,
						 SimpleParameterList parameterList,
						 int maxRows,
						 int fetchSize,
						 int flags,
						 QueryExecutorImpl queryExecutor,
						 QueryInfo srcQueryInfo,
						 ReWriter srcRewriter,
						 Lock lock
				 )
	{
//		waitExecute();//これいる？

		m_query = query;
		m_parameters = parameterList;
		m_maxRows = maxRows;
		m_fetchSize = fetchSize;
		m_flags = flags;
		m_queryExecutor = queryExecutor;
		m_qi = srcQueryInfo;
		m_con = (AbstractJdbc2Connection)m_qi.getConnection();
		m_sql = m_qi.getSql();
		m_gsc = m_queryExecutor.getGsc();
		m_rw = srcRewriter;
		if (m_rw instanceof MetaDataReWriter) {
			m_retry = m_gsc.getServerCount();
			m_metadata = true;
		}else{
			m_retry = m_gsc.getRetry();
			m_metadata = false;
		}
		
		m_lock = lock;
		
		
		m_threadMng.setExecSeq(m_con,this);//コネクション実行順設定

		//実行
		execute();
	}





	/**
	 * クエリー実行
	 * クエリーの実行を行い、実行結果の集約を行う。
	 * エラー発生時には、リトライまたはアプリケーションへのthrowを行う
	 */
	protected void _execute() //Ver3.1 この同期は更新非同期モードで同期を取ることになるのでおかしいが、動かなくなるのでこのまま
	{

		boolean flg = true;

		m_errCode = STAT_OK;		
		m_retryCount = 0;
		// Preparedかどうかを判定
        boolean noResults = (m_flags & QueryExecutor.QUERY_NO_RESULTS) != 0;
        boolean noMeta = (m_flags & QueryExecutor.QUERY_NO_METADATA) != 0;
        boolean describeOnly = (m_flags & QueryExecutor.QUERY_DESCRIBE_ONLY) != 0;
        boolean usePortal = (m_flags & QueryExecutor.QUERY_FORWARD_CURSOR) != 0 && !noResults && !noMeta && m_fetchSize > 0 && !describeOnly;
		boolean oneShot = (m_flags & QueryExecutor.QUERY_ONESHOT) != 0 && !usePortal;

		try{

			while(flg)
			{

				m_threadMng.waitExecSeq(m_con,this);//コネクションが使用可能になるまで待機
				
				//クエリー実行スレッドクラスの生成

				if(Driver.forestTestLog)
					System.out.println( "SQL:" + m_sql + "\nURL:" + m_con.getURL() +'\n');

				if (Driver.logInfo){
					m_logUtil.info(getName() +": Execute Query = " + m_sql);
					m_logUtil.info(getName() +": Execute Connection = " + m_con.getURL());
				}



				//クエリー実行

					QueryExecutor queryExecutor =  m_con.getQueryExecutor();
					Query query;
			        if(oneShot){ /* キャッシュ（preparedかどうか）？*/
			        	//通常ステートメント
		        		if( m_parameters == null )
		        			query = queryExecutor.createSimpleQuery(m_sql);
		        		else{
		        			query = queryExecutor.createParameterizedQuery(m_sql);
		        		}

			        }
			        else{
			        	//Preparedステートメント
			        	//すでに実行したことがある場合は、Queryを取得、なければ作成 
			        	query = m_query.getPreparedQuery(m_con,m_sql);
			        	if(query == null){
			        		if( m_parameters == null )
			        			query = queryExecutor.createSimpleQuery(m_sql);
			        		else{
			        			query = queryExecutor.createParameterizedQuery(m_sql);
			        		}
					        m_query.putPreparedQuery(m_con,m_sql,query);
			        	}
			        }	

			        //パラメータをプロトコル別のパラメータにコピー
			        ParameterList preparedParameters = null;
			        if(m_parameters != null ){
			        	preparedParameters = m_parameters.copyTo(query.createParameterList());
			        }
			        
						//タイムアウト値決定。queryのタイムアウトが０以外ならば、queryを使用。
						int timeout = m_gsc.getTimeout(); 
						if( m_query.getTimeout() != 0 ){
							timeout = m_query.getTimeout();
						}
						
						if( timeout !=0 ){
							queryExecutor.getPGStream().getSocket().setSoTimeout(timeout*1000);				
						}else{
							queryExecutor.getPGStream().getSocket().setSoTimeout(0);				
						}
						

		        //SQL実行
				m_ses.execute(queryExecutor, query, preparedParameters, m_maxRows, m_fetchSize, m_flags  );
				
				if(m_metadata){
					flg = excutedMetaData();
				}else{
					flg = excuted();
				}

				
			}
		}
		catch(java.sql.SQLException esql)
		{
			m_errObj = esql;
			m_errCode = STAT_NG;
		}
		catch (SocketException e) {
			m_errObj = new PSQLException(GT.tr("excute internal error.\n{0}",e), ForestSQLState.INTERNAL_ERROR, e);
			m_errCode = STAT_NG;
		}
		//Ver1.1R2
		finally{
//DEBUG			System.out.println( getName() +":ロック解除"  );
			m_threadMng.endExecSeq(m_con, this);// コネクション実行終了
			m_lock.notifyEnd();
		}
		
		if (Driver.logDebug)
			m_logUtil.debug(getName() +": _execute() End");

	}

	protected boolean excuted() throws SQLException {
	
			boolean flg = false;
			//Ver3.0
			int sesStatus = m_ses.getErrCode();
			
			//タイムアウト監視
			if( sesStatus == StatementExecuteSub.ERR_TIMEOUT )
			{
				if (Driver.logInfo)
					m_logUtil.info(getName() +": The timeout occurred in execution of a query.");

				//サーバ異常セット
				m_gsc.setServerBroken((Connection)m_con,m_sql,new PSQLException(GT.tr("The timeout of the query was carried out."),ForestSQLState.INTERNAL_ERROR));
	
				if (Driver.logInfo)
					m_logUtil.info(getName() +": The query execution thread was stopped.");

				if(m_rw.getQueryType() == ReWriter.TYPE_PARALLEL){
					if (Driver.logInfo)
						m_logUtil.info(getName() +": Execution of a query is not re-tried for parallel processing.");
					flg = false;
					m_errObj = new PSQLException(GT.tr("The timeout of the query was carried out."),ForestSQLState.INTERNAL_ERROR);
					m_errCode = STAT_NG;
				}
				//Ver1.1 エラーリトライ 回数-->									
				else if(m_retry <= m_retryCount  ){
					//リトライ回数を超えたらエラー
					if (Driver.logInfo)
						m_logUtil.info(getName() +": Since the number of times of re-trial was exceeded, it is made an error, without re-trying.");
					flg = false;
					m_errObj = new PSQLException(GT.tr("Error retry over."),ForestSQLState.INTERNAL_ERROR);
					m_errCode = STAT_NG;
				}
				//Ver1.1 <--								
				else{
					if (Driver.logInfo)
						m_logUtil.info(getName() +": Execution of a query is re-tried.");
					m_threadMng.endExecSeq(m_con,this);//コネクション実行終了
					m_con = (AbstractJdbc2Connection)m_rw.getRetryConnection(m_qi);
					m_threadMng.setExecSeq(m_con,this);//コネクション実行順設定
					flg = true;
					m_retryCount++;//Ver1.1
				}
	
				
				
			//Ver3.0
			}else if(sesStatus == StatementExecuteSub.ERR_NONE){
	
				//正常終了
				if (Driver.logInfo)
					m_logUtil.info(getName() +": Execution of a query was ended normally.");								
				flg = false;
	
	//Ver3.1rollback対象のエラーはautocommit=trueのとき切り離し対象					
	//				}else if(sesStatus == StatementExecuteSub.ERR_BROKEN){
			}else if(sesStatus == StatementExecuteSub.ERR_BROKEN ||
			          ( sesStatus == StatementExecuteSub.ERR_ROLLBACK && m_queryExecutor.getAutocommit() == true ) ){
	
				//エラー：サーバ切り離し
				if (Driver.logInfo)
					m_logUtil.info(getName() +": The error for server separation occurred.");
				//Ver2.1
				//m_gsc.setServerBroken((Connection)m_con);
				m_gsc.setServerBroken((Connection)m_con,m_sql,m_ses.getErrobj());
				
				if(m_rw.getQueryType() == ReWriter.TYPE_PARALLEL){
					//並列の場合、このスレッドは終了
					if (Driver.logInfo)
						m_logUtil.info(getName() +": Execution of a query is not re-tried for parallel processing.");
					m_errObj = m_ses.getErrobj();
					m_errCode = STAT_NG;
					flg = false;
				}
				
				//Ver1.1 エラーリトライ 回数-->									
				else if(m_retry <= m_retryCount  ){
					//リトライ回数を超えたらエラー
					if (Driver.logInfo)
						m_logUtil.info(getName() +": Since the number of times of re-trial was exceeded, it is made an error, without re-trying.");
					m_errObj = new PSQLException(GT.tr("Error retry over."), ForestSQLState.INTERNAL_ERROR);
					m_errCode = STAT_NG;
					flg = false;
				}
				//Ver1.1 <--								
				
				else{
					//並列処理以外は、リトライ
					if (Driver.logInfo)
						m_logUtil.info(getName() +": Execution of a query is re-tried.");
					m_threadMng.endExecSeq(m_con,this);//コネクション実行終了
					m_con = (AbstractJdbc2Connection)m_rw.getRetryConnection(m_qi);
					m_threadMng.setExecSeq(m_con,this);//コネクション実行順設定
					flg = true;
					m_retryCount++;//Ver1.1
	
				}
			
			}else if(sesStatus == StatementExecuteSub.ERR_NOT_BROKEN){
			    
			
				if (Driver.logInfo)
					m_logUtil.info(getName() +": The error which is not a candidate for server separation occurred.");
				//エラー：アプリケーションへ戻す
				flg = false;
				m_errObj = m_ses.getErrobj();
				m_errCode = STAT_NG;
			
			
			}
			//Ver 3.1 ロールバック
			else if(sesStatus == StatementExecuteSub.ERR_ROLLBACK){
			    
			
				if (Driver.logInfo)
					m_logUtil.info(getName() +": Rollback object error generation.");
				//エラー：アプリケーションへ戻す
				flg = false;
				m_errObj = m_ses.getErrobj();
				m_errCode = STAT_NG;
	
				m_queryExecutor.setRollback(m_errObj);
			
			
			}
			return flg;
		}

	protected boolean excutedMetaData() throws SQLException {

		boolean flg = true;
		//Ver3.0
		int sesStatus = m_ses.getErrCode();
		
		//タイムアウト監視
		
		if(sesStatus == StatementExecuteSub.ERR_NONE){

			//正常終了
			if (Driver.logInfo)
				m_logUtil.info(getName() +": Execution of a query was ended normally.");								

		
			//結果格納
			if( getResultHandler().getTuples().size() != 0 ){
				flg = false;
			}
			

		}
		
		if(flg){

			if (Driver.logInfo){
			if( sesStatus == StatementExecuteSub.ERR_TIMEOUT )
			{
					m_logUtil.info(getName() +": The timeout occurred in execution of a query.");
			}
			}

			
			//Ver1.1 エラーリトライ 回数-->									
			if(m_retry <= m_retryCount  ){
				flg = false;
			}
			else{
				//並列処理以外は、リトライ
				if (Driver.logInfo)
					m_logUtil.info(getName() +": Execution of a query is re-tried.");
				m_threadMng.endExecSeq(m_con,this);//コネクション実行終了
				m_con = (AbstractJdbc2Connection) m_rw.getRetryConnection(m_qi);
				m_threadMng.setExecSeq(m_con,this);//コネクション実行順設定
				m_retryCount++;//Ver1.1

			}
		
		}

		return flg;
	}
	

	/**
	 * スレッド終了
	 * クエリー実行スレッド、タイマースレッドへ終了指示し、自己の終了を呼び出す 
	 * @since 1.1
	 */
	public void terminate(){

		if (Driver.logDebug)
			m_logUtil.debug(getName() +": terminate() In");
		

		super.terminate();

		if (Driver.logDebug)
			m_logUtil.debug(getName() +": terminate() Out");

	}



	/**
	 * 
	 * @param b
	 * @since 1.1
	 */
	public void setSelfClose(boolean b) {
		m_selfClose = b;
	}
	


	/**
	 * 
	 */
	public void close(){

		if (Driver.logDebug)
			m_logUtil.debug(getName() +": The separation check of a server is started.");

		/*
		 * close処理に入ったら以降は切り離しチェック処理を行わない。
		 */
		boolean brokenChk = m_serverBrokenChk;
		setServerBrokenChk(false);

		/*
		 * 並列処理時にエラーが発生した場合には、close時に切り離しを行う。
		 *
		 * FIXME: なぜ？
		 */
		if( brokenChk &&
			 m_rw.getQueryType() == ReWriter.TYPE_PARALLEL &&
			 m_errCode == STAT_NG ){

		    //Ver3.1 ロールバック対象のエラーが発生していたら、切り離しは行わない
            if( m_queryExecutor.isRollback() ){
			    return;
			}

		    if (Driver.logInfo)
				m_logUtil.info(getName() +": There is a server for separation.");		

			//エラー：サーバ切り離し
			try {
				//Ver2.1
				//m_gsc.setServerBroken((Connection)m_con);
				m_gsc.setServerBroken((Connection)m_con,m_sql, m_ses.getErrobj());
			} catch (SQLException e) {
			}

		}
		if (Driver.logDebug)
			m_logUtil.debug(getName() +": The separation check of a server is ended.");				

	}


	/**
	 * @param b
	 */
	public void setServerBrokenChk(boolean b) {
		m_serverBrokenChk = b;
	}

	/* (非 Javadoc)
	 * @see org.postgresforest.vm.AbstractForestThread#_standby()
	 */
	protected void standby() {

		super.standby();


		//自分でスレッドを開放する場合（並列処理でスレッドが終了していなかった）
		if(m_selfClose){
			m_threadMng.closeStatementSub(this);
		}

		//Ver2.1
		m_selfClose=false; 
	}

	public int getErrCode() {
		return m_errCode;
	}

	public SimpleResultHandler getResultHandler() {
		return m_ses.getResultHandler();
	}

	public SQLException getErrObj() {
		return m_errObj;
	}

	public String getURL() throws SQLException {
		return m_con.getURL();
	}

	public String getSql() {
		return m_sql;
	}

}
