package org.postgresforest.tool.lib;

import java.sql.*;
import java.util.ArrayList;
import org.postgresforest.tool.ArrayUtil;
import org.postgresforest.tool.Logger;

public class Table {
	private Database database = null;
	private String tableName = null;
	private GSCdata gsc = null;

	public static final int TYPE_REPLICATED  = 0;
	public static final int TYPE_PARTITION_1 = 1;
	public static final int TYPE_PARTITION_2 = 2;
	
	public Table(Database database, String tableName)
	{
		this.database  = database;
		this.tableName = tableName;
		this.gsc       = database.getGSCdata();
	}

	/**
	 * 多重化テーブルをパーティションキーとHash関数を指定して
	 * パーティション化する。多重化テーブルの場合のみ実行可。
	 *
	 * @param partKeys パーティションキー
	 * @param partCount パーティション数
	 * @param hashName ハッシュ関数名
	 *
	 * @return boolean 成功したらtrue、失敗したらfalse
	 */
	public boolean part(String[] partKeys, int partCount, String hashName)
		throws ForestToolException
	{
		String def = null;
		String[] tmp = null;

		if ( getTableType()==TYPE_PARTITION_1 || getTableType()==TYPE_PARTITION_2 )
			throw new ForestToolException("Table `" + tableName + "' is a partitioned table.");

		if ( getPrimaryKeys().length<=0 )
			throw new ForestToolException("Table `" + tableName + "' does not have primary key(s).");

		/*
		 * パーティション化用DDL文を生成。
		 *
		 *   1. CREATE TABLE ... ( LIKE ... ) を使ってパーティションを作成
		 *   2. ALTER TABLE ... RENAME TO ... で元テーブルの名前を変更
		 *   3. ALTER TABLE ... ADD CONSTRAINT ... で各パーティションに制約を追加
		 *   4. CREATE VIEW ... でパーティションテーブル用UNION ALLビューを作成
		 *   5. CREATE RULE ... でパーティションテーブル用UPDATE用ルールを作成
		 *   6. CREATE RULE ... でパーティションテーブル用DELETE用ルールを作成
		 *   7. CREATE INDEX ... で各パーティションにインデックスを作成（主キーインデックスを除く）
		 */
        def = PartitionUtils.buildPartitionDefs(partCount, tableName, partKeys, hashName);
		def = def + "ALTER TABLE " + tableName + " RENAME TO _" + tableName + ";\n";

		tmp = PartitionUtils.buildConstraintDefs(partCount, tableName, getConstraintDefs());
		for (int i=0 ; i<tmp.length ; i++)
			def = def + tmp[i] + "\n";

        def = def + PartitionUtils.buildUnionAllViewDef(partCount, tableName);
        def = def + PartitionUtils.buildUpdateRuleDef(partCount, tableName, getPrimaryKeys(), getColumnNames());
        def = def + PartitionUtils.buildDeleteRuleDef(partCount, tableName, getPrimaryKeys());

		tmp = PartitionUtils.buildIndexDefs(partCount, tableName, getIndexDefs());
		for (int i=0 ; i<tmp.length ; i++)
			def = def + tmp[i] + "\n";

		Logger.debug(def);

		/*
		 * GSC登録用SQLを作成（forest_tablepart）
		 */
		String gscSql = null;

		if ( hashName==null )
			hashName = "null";
		else
			hashName = "'" + hashName + "'";

		gscSql = "UPDATE forest_tablepart SET part_count=" + partCount + ",part_type=1,hash_name=" + hashName +
		         " WHERE dbname='" + database.getDatabaseName() + "' " +
		         "   AND table_name='" + tableName + "';";

		/*
		 * GSC登録用SQLを作成（forest_tablepartdtl）
		 */
		int[] dbno = database.getDatabaseNumbers();

		for (int i=0 ; i<dbno.length ; i++)
		{
			int pri = i;

			gscSql = gscSql + "DELETE FROM forest_tablepartdtl WHERE dbno=" + dbno[i] + " AND " +
			                  "  dbname='" + database.getDatabaseName() + "' AND " +
			                  "  table_name='" + tableName + "' AND " +
			                  "  part_no=0;";

			for (int j=0 ; j<partCount ; j++)
			{
				gscSql = gscSql + "INSERT INTO forest_tablepartdtl(dbno,dbname,table_name,part_no,priority) VALUES (" +
				                  "" + dbno[i] + "," +
				                  "'" + database.getDatabaseName() + "'," +
				                  "'" + tableName + "'," +
				                  "" + j + "," +
				                  "" + pri + ");";

				pri++;
				if ( pri>=dbno.length )
					pri = 0;
			}
		}

		/*
		 * GSC登録用SQLを作成（forest_partatr）
		 */
		for (int i=0 ; i<partKeys.length ; i++)
		{
			gscSql = gscSql + "INSERT INTO forest_partatr(dbname,table_name,column_name,column_no,column_type) VALUES (" +
			                  "'" + database.getDatabaseName() + "'," +
			                  "'" + tableName + "'," +
			                  "'" + partKeys[i] + "'," +
			                  "" + getColumnNumber(partKeys[i]) + "," +
			                  "'" + getColumnType(partKeys[i]) + "');";
		}

		try {
			gsc.executeUpdateGSC(gscSql);
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		/*
		 * 多重化テーブルを保持している各インスタンスに対して
		 * パーティション化用DDL文を実行
		 */
		int[] instanceIds = getInstanceIds();

		for (int i=0 ; i<instanceIds.length ; i++)
		{
			Instance ins = gsc.getInstance(instanceIds[i]);
			Connection con = ins.getDatabaseConnection(database.getDatabaseName(),
													   gsc.getUser(),
													   gsc.getPassword());

			Logger.debug(ins.toString() + " : Attempting to part table " + tableName);

			try {
				con.setAutoCommit(false);
				con.createStatement().executeUpdate(def);
				con.commit();
				con.close();
			}
			catch (Exception e)
			{
				throw new ForestToolException(e);
			}
		}

		/*
		 * 元テーブルを削除
		 */
		instanceIds = getInstanceIds();
		
		for (int i=0 ; i<instanceIds.length ; i++)
		{
			Instance ins = gsc.getInstance(instanceIds[i]);
			Connection con = ins.getDatabaseConnection(database.getDatabaseName(),
													   gsc.getUser(),
													   gsc.getPassword());

			Logger.debug(ins.toString() + " : Attempting to part table " + tableName);

			try {
				con.setAutoCommit(false);
				con.createStatement().executeUpdate("DROP TABLE _" + tableName);
				con.commit();
				con.close();
			}
			catch (Exception e)
			{
				throw new ForestToolException(e);
			}
		}

		return true;
	}

	/**
	 * パーティション化テーブルを多重化テーブルに変更する。
	 * パーティションテーブル１のみ対応。
	 *
	 * @return boolean 成功したらtrue、失敗したらfalse
	 */
	public boolean unpart()
		throws ForestToolException
	{
		if ( getTableType()!=Table.TYPE_PARTITION_1 )
			throw new ForestToolException("This table is not a partition(1) table.");

		int partCount = getPartCount();
		
		/*
		 * テーブル情報（forest_tablepart）をパーティション化テーブルから多重化テーブルに変更
		 */
		String gscSql = null;

		gscSql = "UPDATE forest_tablepart SET part_count=1,part_type=0,hash_name=null " +
		         " WHERE dbname='" + database.getDatabaseName() + "' " +
		         "   AND table_name='" + tableName + "';";

		/*
		 * テーブル詳細情報（forest_tablepartdtl）を多重化テーブル用に更新
		 */
		int[] dbno = database.getDatabaseNumbers();

		for (int i=0 ; i<dbno.length ; i++)
		{
			for (int j=0 ; j<partCount ; j++)
			{
				gscSql = gscSql + "DELETE FROM forest_tablepartdtl WHERE dbno=" + dbno[i] + " AND " +
				                  "  dbname='" + database.getDatabaseName() + "' AND " +
				                  "  table_name='" + tableName + "' AND " +
				                  "  part_no=" + j + ";";
			}

			gscSql = gscSql + "INSERT INTO forest_tablepartdtl(dbno,dbname,table_name,part_no,priority) VALUES (" +
			                  "" + dbno[i] + "," +
			                  "'" + database.getDatabaseName() + "'," +
			                  "'" + tableName + "'," +
			                  "0,0);";
		}

		/*
		 * forest_partatrからパーティション属性情報を削除
		 */
		gscSql = gscSql + "DELETE FROM forest_partatr " +
		                  " WHERE dbname='" + database.getDatabaseName() + "' " +
		                  "   AND table_name='" + tableName + "';";

		/*
		 * ユーザテーブルをパーティションテーブル１から多重化テーブルに変更
		 */
		String def = "DROP VIEW " + tableName + ";\n";

		def = def + "CREATE TABLE " + tableName + " AS ";
		for (int i=0  ; i<partCount ; i++)
		{
			String partName = PartitionUtils.buildPartitionName(tableName, i);

			if ( i>0 )
				def = def + " UNION ALL ";
			def = def + "SELECT * FROM " + partName + " ";
		}
		def = def + ";";

		/*
		 * 各パーティションを削除
		 */
		for (int i=0  ; i<partCount ; i++)
		{
			String partName = PartitionUtils.buildPartitionName(tableName, i);
			def = def + "DROP TABLE " + partName + ";";
		}

		/*
		 * FIXME: 制約、インデックスを追加
		 */
		{
			String partName = PartitionUtils.buildPartitionName(tableName, 0);
			String constDefs = "";
			String[] pkeys = getPrimaryKeys();

            Connection con = null;
			int[] instances = getInstanceIds();

            /*
             * FIXME: ハードコード（・A・）イクナイ！
			 *        多重化テーブル・パーティション化テーブルチェック追加
             */
            con = gsc.getInstance(instances[0]).getDatabaseConnection(database.getDatabaseName(),
                                                                      gsc.getUser(),
                                                                      gsc.getPassword());

			String[] tmp = TableUtils.getIndexDefs(con,
												   TableUtils.getSchemaName(partName),
												   TableUtils.getTableName(partName),
												   tableName);
			for (int i=0 ; i<tmp.length ; i++)
			{
				constDefs = constDefs + tmp[i];
			}

			tmp = TableUtils.getConstraintDefs(con,
											   TableUtils.getSchemaName(partName),
											   TableUtils.getTableName(partName),
											   tableName);
			for (int i=0 ; i<tmp.length ; i++)
			{
				constDefs = constDefs + tmp[i];
			}

			Logger.debug("unpart: constraint, " + constDefs);

			def = def + constDefs;
		}

		/*
		 * 全インスタンスにSQL実行
		 */
		int[] instanceIds = getInstanceIds();

		for (int i=0 ; i<instanceIds.length ; i++)
		{
			Instance ins = gsc.getInstance(instanceIds[i]);
			Connection con = ins.getDatabaseConnection(database.getDatabaseName(),
													   gsc.getUser(),
													   gsc.getPassword());

			Logger.debug(ins.toString() + " : Attempting to unpart table " + tableName);

			try {
				con.setAutoCommit(false);
				con.createStatement().executeUpdate(def);
				con.commit();
				con.close();
			}
			catch (Exception e)
			{
				throw new ForestToolException(e);
			}
		}

		/*
		 * GSCを更新
		 */
		try {
			gsc.executeUpdateGSC(gscSql);
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		Logger.debug(def);

		return true;
	}

	/**
	 * テーブルを保持しているインスタンスのIDを取得する
	 *
	 * @return int[] インスタンスIDの配列
	 */
	public int[] getInstanceIds()
		throws ForestToolException
	{
		String sql = "SELECT s.serverid FROM forest_tablepart t, forest_servdb s " +
			         " WHERE t.dbname=s.dbname AND t.table_name='" + tableName + "'";
		ArrayList a = new ArrayList();

		try {
			ResultSet rs = gsc.executeQueryGSC(sql);

			while ( rs.next() )
			{
				a.add( new Integer(rs.getInt(1)) );
			}

			rs.close();
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		return ArrayUtil.array2intarray(a);
	}

	/**
	 * 指定したパーティションを保持しているインスタンスのIDを取得する
	 *
	 * @param partId パーティションID
	 *
	 * @return int[] インスタンスIDの配列
	 */
	public int[] getInstanceIds(int partId)
	{
		String sql = "SELECT s.serverid FROM forest_server s, forest_servdb d, forest_tablepartdtl t " +
		             " WHERE s.serverid=d.serverid AND d.dbno=t.dbno AND t.table_name='" + tableName + "' " +
		             "   AND part_no=" + partId + " AND d.dbname='" + database.getDatabaseName() + "'";
		ArrayList a = new ArrayList();

		try {
			ResultSet rs = gsc.executeQueryGSC(sql);

			while ( rs.next() )
				a.add( new Integer(rs.getInt(1)) );

			rs.close();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			return null;
		}

		return ArrayUtil.array2intarray(a);
	}

	/**
	 * 指定したパーティションを、指定したインスタンスから
	 * 他のインスタンスへコピーする。
	 *
	 * @param partId コピーするパーティションのパーティションID
	 * @param srcId コピー元のインスタンスのインスタンスID
	 * @param destId コピー先のインスタンスのインスタンスID
	 *
	 * @return boolean 成功したらtrue、失敗したらfalse
	 */
	public boolean copyPart(int partId, int srcId, int destId)
	{
		return false;
	}

	/**
	 * 指定したパーティションを、指定したインスタンスから削除する。
	 * UNION VIEW/RULEも作成し直す。
	 *
	 * @param partId 削除するパーティションのパーティションID
	 * @param srcId 削除するパーティションのあるインスタンスID
	 *
	 * @return boolean 成功したらtrue、失敗したらfalse
	 */
	public boolean dropPart(int partId, int srcId)
	{
		String sql = "SELECT part_no FROM forest_servdb s, forest_tablepartdtl d " +
		             " WHERE s.serverid=" + srcId + " AND d.dbno=s.dbno " +
		             "   AND d.table_name='" + tableName + "' " +
		             "   AND d.dbname='" + database.getDatabaseName() + "' AND d.part_no<>" + partId;

		int[] partNum = null;

		try {
			ArrayList a = new ArrayList();

			ResultSet rs = gsc.executeQueryGSC(sql);
			while (rs.next())
			{
				a.add( new Integer( rs.getInt(1) ) );
			}

			partNum = ArrayUtil.array2intarray(a);
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
		}

		/*
		 * ユーザデータベースからパーティションを削除
		 */
		try {
			Instance ins = gsc.getInstance(srcId);

			Connection con = ins.getDatabaseConnection(database.getDatabaseName(),
													   gsc.getUser(),
													   gsc.getPassword());

			String def = "DROP VIEW " + tableName + ";\n";

			def = def + PartitionUtils.buildUnionAllViewDef(partNum,
															tableName);

			def = def + PartitionUtils.buildUpdateRuleDef(partNum,
														  tableName,
														  getPartitionKeys(),
														  getColumnNames());
			def = def + PartitionUtils.buildDeleteRuleDef(partNum,
														  tableName,
														  getPartitionKeys());

			def = def + "DROP TABLE " + PartitionUtils.buildPartitionName(tableName, partId) + ";\n";

			Logger.debug(ins.toString() + " : Attempting to drop a partition on " + tableName);
			Logger.debug(def);

			con.setAutoCommit(false);
			con.createStatement().executeUpdate(def);
			con.commit();
			con.close();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
			return false;
		}

		/*
		 * GSC(forest_tablepartdtl)から該当するパーティション情報を削除
		 */
		sql = "DELETE FROM forest_tablepartdtl " +
		      " WHERE dbname='" + database.getDatabaseName() + "' " +
		      "   AND table_name='" + tableName + "' " +
		      "   AND part_no=" + partId + " " +
		      "   AND dbno = ( SELECT dbno FROM forest_servdb " +
		                      " WHERE serverid=" + srcId + " " +
		                        " AND dbname='" + database.getDatabaseName() + "' );\n";

		/*
		 * パーティションを削除した場合、自動的にパーティションテーブル２へ変更
		 */
		sql = sql + "UPDATE forest_tablepart SET part_type=2 " +
		      " WHERE dbname='" + database.getDatabaseName() + "' " +
		      "   AND table_name='" + tableName + "';\n";

		try {
			int rc = gsc.executeUpdateGSC(sql);
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
		}

		return true;
	}

    /**
     * テーブルのカラム名一覧を取得する。
     *
     * @return String[] カラム名の配列
     */
	public String[] getColumnNames()
		throws ForestToolException
	{
		String nspname = TableUtils.getSchemaName(tableName);
		String relname = TableUtils.getTableName(tableName);

		String sql = "SELECT attname from pg_class c, pg_attribute a, pg_namespace n " +
			" where n.nspname='" + nspname + "' " +
			"   and n.oid=c.relnamespace " + 
			"   and c.relname='" + relname + "' " +
			"   and a.attrelid=c.oid " +
			"   and attnum>0 order by attnum";

		ArrayList a = new ArrayList();

		int[] instances = database.getInstanceIds();

		try {
			Connection con = null;

			/*
			 * FIXME: ハードコード（・A・）イクナイ！
			 *        多重化テーブル・パーティション化テーブルチェック追加
			 */
			con = gsc.getInstance(instances[0]).getDatabaseConnection(database.getDatabaseName(),
																	  gsc.getUser(),
																	  gsc.getPassword());

			Statement stmt = con.createStatement();
			ResultSet rs = stmt.executeQuery(sql);

			while ( rs.next() )
			{
				a.add( rs.getString("attname") );
			}

			rs.close();
			stmt.close();
			con.close();
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		return ArrayUtil.array2stringarray(a);
	}

	/**
	 * カラムの番号を取得する
	 *
	 * @param colname カラム名
	 *
	 * @return String カラムの型
	 */
	private String getColumnNumber(String colname)
		throws ForestToolException
	{
		return (getColumnInfo(colname))[0];
	}

	/**
	 * カラムの型応情報を取得する
	 *
	 * @param colname カラム名
	 *
	 * @return String カラムの型
	 */
	public String getColumnType(String colname)
		throws ForestToolException
	{
		return (getColumnInfo(colname))[1];
	}

	/**
	 * カラム情報を取得する。
	 * 
	 * @param colname カラム名
	 *
	 * @return String[] 0:カラム番号, 1:型
	 */
	public String[] getColumnInfo(String colname)
		throws ForestToolException
	{
		String[] colinfo = new String[2];

		String nspname = TableUtils.getSchemaName(tableName);
		String relname = TableUtils.getTableName(tableName);

		String sql = "SELECT a.attnum as n, pg_catalog.format_type(a.atttypid, a.atttypmod) as t " +
		             "  FROM pg_namespace n, pg_attribute a, pg_class r " +
		             " WHERE n.nspname='" + nspname + "' " +
		             "   AND n.oid=r.relnamespace " +
		             "   AND r.relname='" + relname + "' " +
		             "   AND a.attrelid=r.oid " +
		             "   AND a.attname='" + colname + "'";

		int[] instances = database.getInstanceIds();

		try {
			Connection con = null;

			/*
			 * FIXME: ハードコード（・A・）イクナイ！
			 *        多重化テーブル・パーティション化テーブルチェック追加
			 */
			con = gsc.getInstance(instances[0]).getDatabaseConnection(database.getDatabaseName(),
																	  gsc.getUser(),
																	  gsc.getPassword());

			Statement stmt = con.createStatement();
			ResultSet rs = stmt.executeQuery(sql);

			if ( rs.next() )
			{
				colinfo[0] = rs.getString("n");
				colinfo[1] = rs.getString("t");
				Logger.debug("getColumnInfo: " + colinfo[0] + "," + colinfo[1]);
			}

			rs.close();
			stmt.close();
			con.close();
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		/*
		 * 別名に対処。
		 * パーティションキーの型名は、特定の表現形にしか対応していないため。
		 */
		if ( colinfo[1].equals("integer") )
			colinfo[1] = "int4";
		else if ( colinfo[1].equals("smallint") )
			colinfo[1] = "int2";
		else if ( colinfo[1].startsWith("character varying") )
			colinfo[1] = "varchar";
		else if ( colinfo[1].startsWith("character") )
			colinfo[1] = "bpchar";
		else if ( colinfo[1].equals("time without time zone") )
			colinfo[1] = "time";
		else if ( colinfo[1].equals("timestamp without time zone") )
			colinfo[1] = "timestamp";
		else if ( colinfo[1].equals("time with time zone") )
			colinfo[1] = "timetz";
		else if ( colinfo[1].equals("timestamp with time zone") )
			colinfo[1] = "timestamptz";

		return colinfo;
	}

    /**
     * テーブルの主キー名一覧を取得する。
     *
     * @return String[] 主キー名の配列
     */
	public String[] getPrimaryKeys()
		throws ForestToolException
	{
		String nspname = null;
		String relname = null;

		/*
		 * パーティションテーブルの場合は、一番目のパーティションから情報を取得
		 */
		Logger.debug("getPrimaryKeys: part_count=" + getPartCount());

		if ( getPartCount()>1 )
		{
			nspname = TableUtils.getSchemaName(PartitionUtils.buildPartitionName(tableName, 0));
			relname = TableUtils.getTableName(PartitionUtils.buildPartitionName(tableName, 0));
		}
		else
		{
			nspname = TableUtils.getSchemaName(tableName);
			relname = TableUtils.getTableName(tableName);
		}

		String sql = "SELECT a.attname as pkey" +
                     "  FROM pg_attribute a, pg_index ii, pg_namespace n, pg_class r " +
                     " WHERE n.nspname='" + nspname + "' " +
                     "   AND r.relname='" + relname + "' " +
                     "   AND n.oid=r.relnamespace " +
                     "   AND a.attrelid=r.oid " +
                     "   AND ii.indisprimary=true " +
                     "   AND ii.indrelid=r.oid " +
                     "   AND a.attnum = ANY(ii.indkey)";

		Logger.debug("getPrimaryKeys: " + sql);

		ArrayList a = new ArrayList();

		int[] instances = database.getInstanceIds();

		try {
			Connection con = null;

			/*
			 * FIXME: ハードコード（・A・）イクナイ！
			 *        多重化テーブル・パーティション化テーブルチェック追加
			 */
			con = gsc.getInstance(instances[0]).getDatabaseConnection(database.getDatabaseName(),
																	  gsc.getUser(),
																	  gsc.getPassword());

			Statement stmt = con.createStatement();
			ResultSet rs = stmt.executeQuery(sql);

			while ( rs.next() )
			{
				a.add( rs.getString("pkey") );
			}

			rs.close();
			stmt.close();
			con.close();
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		return ArrayUtil.array2stringarray(a);
	}

	/**
	 * テーブルにインデックスを作成する。
	 *
	 * パーティションテーブルの場合は、インデックス定義用DDLを書き換え、
	 * 必要なパーティション上にインデックスを作成する
	 * （パーティション１およびパーティション２）。
	 *
	 * @param indexdef インデックス定義用DDL
	 *
	 * @return booelan すべてのパーティションへの作成が成功したらtrue、失敗したらfalse
	 */
	public boolean createIndex(String indexDef)
	{
		boolean rc = true;
		
		if ( getTableType()==TYPE_REPLICATED )
		{
			/*
			 * 多重化テーブルを持つインスタンスIDリストを取得
			 */
			int[] instanceIds = null;

			try {
				instanceIds = getInstanceIds();
			}
			catch (Exception e)
			{
				Logger.error(e.getMessage());
				Logger.trace(e);
				rc = false;
			}

			/*
			 * すべてのインスタンスにインデックスを作成。
			 */
			for (int i=0 ; i<instanceIds.length ; i++)
			{
				Connection con = null;

				try {
					con = gsc.getInstance(instanceIds[i]).getDatabaseConnection(database.getDatabaseName(),
																				gsc.getUser(),
																				gsc.getPassword());
					
					con.createStatement().executeUpdate(indexDef);
					con.close();
				}
				catch (Exception e)
				{
					Logger.error(e.getMessage());
					Logger.trace(e);
					rc = false;
				}
			}
		}
		else
		{
			String[] indexDefs = new String[1];
			indexDefs[0] = indexDef;
			
			String[] partIndexDefs = PartitionUtils.buildIndexDefs(getPartCount(), tableName, indexDefs);

			/*
			 * パーティション分割されたインデックスを、各インスタンスに作成する。
			 */
			for (int p=0 ; p<getPartCount() ; p++)
			{
				/*
				 * パーティション'p'を持つインスタンスIDリストを取得
				 */
				int[] instanceIds = null;

				try {
					instanceIds = getInstanceIds(p);
				}
				catch (Exception e)
				{
					Logger.error(e.getMessage());
					Logger.trace(e);
					rc = false;
				}

				/*
				 * すべてのインスタンスにインデックスを作成。
				 */
				for (int i=0 ; i<instanceIds.length ; i++)
				{
					Connection con = null;

					try {
						con = gsc.getInstance(instanceIds[i]).getDatabaseConnection(database.getDatabaseName(),
																					gsc.getUser(),
																					gsc.getPassword());

						con.createStatement().executeUpdate(partIndexDefs[p]);
						con.close();
					}
					catch (Exception e)
					{
						Logger.error(e.getMessage());
						Logger.trace(e);
						rc = false;
					}
				}
			}
		}

		return rc;
	}

	/**
	 * 指定したインスタンスIDのインスタンスが保持している
	 * パーティション番号のリストを配列で取得する。
	 *
	 * @param instanceId インスタンスID
	 *
	 * @return int[] パーティション番号の配列
	 */
	private int[] getPartitionNums(int instanceId)
	{
		String sql = "SELECT part_no FROM forest_servdb s, forest_tablepartdtl d " +
		             " WHERE s.dbno=d.dbno AND s.dbname=d.dbname " +
		             "   AND s.serverid=" + instanceId + " " +
		             "   AND d.table_name='" + tableName + "' ORDER BY part_no";

		ArrayList a = new ArrayList();

		try {
			ResultSet rs = gsc.executeQueryGSC(sql);

			while ( rs.next() )
			{
				a.add( new Integer(rs.getInt(1)) );
			}

			rs.close();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
			return null;
		}

		return ArrayUtil.array2intarray(a);
	}

	/**
	 * テーブルおよびインデックスに対して、ALTER TABLE/ALTER INDEXを実行する。
	 * このメソッドで動作が保証される（orすべき）のはALTER TABLE/ALTER INDEXのみ。
	 *
	 * パーティションテーブルに対してALTER TABLEを実行する場合、
	 *
	 *   - ALTER TABLE ... RENAME TO ...
	 *   - ALTER TABLE ... CLUSTER ON ...
	 *
	 * の場合は、最初のテーブル名と、後ろのテーブル名orインデックス名を書き変える。
	 * 上記以外の場合は、最初のテーブル名だけを書き換える。
	 *
	 * パーティションテーブルに対してALTER INDEXを行う場合、
	 *
	 *   - ALTER INDEX ... RENAME TO ...
	 *
	 * の場合は、変更前および変更後のインデックス名を書き換える。
	 * 上記以外の場合は、最初のインデックス名のみを書き換える。
	 *
	 * @param alterSql ALTER TABLE/ALTER INDEX文文字列
	 *
	 * @return boolean 実行に成功したらtrue、実行できなかったらfalse
	 */
	public boolean alter(String alterSql)
	{
		String newName = null;

		if ( getPartCount()>1 )
		{
			/*
			 * パーティションテーブルの場合
			 */
			int[] instanceIds = null;

			try {
				instanceIds = getInstanceIds();
			}
			catch (Exception e)
			{
				Logger.error(e.getMessage());
				Logger.trace(e);
				return false;
			}

			for (int i=0 ; i<instanceIds.length ; i++)
			{
				Instance ins = gsc.getInstance(instanceIds[i]);

				try {
					Connection con = ins.getDatabaseConnection(database.getDatabaseName(),
															   gsc.getUser(),
															   gsc.getPassword());
					
					String[] alters = PartitionUtils.rewriteAlter(tableName,
																  getPartCount(),
																  getPartitionNums(instanceIds[i]),
																  alterSql);
					
					if ( alters[0].startsWith("-- rename to ") )
					{
						newName = alters[0].substring(13, alters[0].length()-1);
						Logger.debug("RENAME TO " + newName);
					}
					for (int j=0 ; j<alters.length ; j++)
					{
						int rc = con.createStatement().executeUpdate(alters[j]);
					}
				
					con.close();
				}
				catch (Exception e)
				{
					Logger.error(e.getMessage());
					Logger.trace(e);
					return false;
				}
			}

			if ( newName!=null )
			{
				gsc.executeUpdateGSC("UPDATE forest_tablepart SET table_name='" + newName + "' WHERE table_name='" + tableName + "';" +
									 "UPDATE forest_tablepartdtl SET table_name='" + newName + "' WHERE table_name='" + tableName + "';" +
									 "UPDATE forest_partatr SET table_name='" + newName + "' WHERE table_name='" + tableName + "'");
			}
		}
		else
		{
			/*
			 * 多重化テーブルの場合
			 */
			int[] instanceIds = null;

			try {
				instanceIds = getInstanceIds();
			}
			catch (Exception e)
			{
				Logger.error(e.getMessage());
				Logger.trace(e);
				return false;
			}

			for (int i=0 ; i<instanceIds.length ; i++)
			{
				Instance ins = gsc.getInstance(instanceIds[i]);

				try {
					Connection con = ins.getDatabaseConnection(database.getDatabaseName(),
															   gsc.getUser(),
															   gsc.getPassword());

					int rc = con.createStatement().executeUpdate(alterSql);
				
					con.close();
				}
				catch (Exception e)
				{
					Logger.error(e.getMessage());
					Logger.trace(e);
				}
			}
		}

		return true;
	}

	/**
	 * テーブルに作成されている制約の定義用DDL文を取得する
	 *
	 * @return String[] DDL文の配列（ALTER TABLE ... ADD CONSTRAINT ...）
	 */
	public String[] getConstraintDefs()
		throws ForestToolException
	{
        int[] instances = database.getInstanceIds();

		String[] defs = null;

        try {
            Connection con = null;

            /*
             * FIXME: ハードコード（・A・）イクナイ！
			 *        多重化テーブル・パーティション化テーブルチェック追加
             */
            con = gsc.getInstance(instances[0]).getDatabaseConnection(database.getDatabaseName(),
                                                                      gsc.getUser(),
                                                                      gsc.getPassword());

			defs = TableUtils.getConstraintDefs(con,
												TableUtils.getSchemaName(tableName),
												TableUtils.getTableName(tableName));

            con.close();
        }
        catch (Exception e)
        {
            throw new ForestToolException(e);
        }

        return defs;
	}

	/**
	 * テーブルに作成されているインデックスの定義用DDL文を取得する
	 *
	 * @return String[] DDL文の配列（CREATE INDEX ...）
	 */
	public String[] getIndexDefs()
		throws ForestToolException
	{
        int[] instances = database.getInstanceIds();

		String[] defs = null;

        try {
            Connection con = null;

            /*
             * FIXME: ハードコード（・A・）イクナイ！
			 *        多重化テーブル・パーティション化テーブルチェック追加
             */
            con = gsc.getInstance(instances[0]).getDatabaseConnection(database.getDatabaseName(),
                                                                      gsc.getUser(),
                                                                      gsc.getPassword());

			defs = TableUtils.getIndexDefs(con,
										   TableUtils.getSchemaName(tableName),
										   TableUtils.getTableName(tableName));

            con.close();
        }
        catch (Exception e)
        {
            throw new ForestToolException(e);
        }

        return defs;
	}

	/**
	 * パーティション数を取得する。
	 *
	 * @return int パーティション数。多重化テーブルの場合は '1' を返却。
	 */
	public int getPartCount()
	{
		return Integer.parseInt(getTableParam("part_count"));
	}

	/**
	 * テーブルのタイプ（多重化・パーティション１・パーティション２）を取得する。
	 *
	 * @return int テーブルタイプ（多重化=0 / パーティション1=1 / パーティション2=2）
	 */
	public int getTableType()
	{
		return Integer.parseInt(getTableParam("part_type"));
	}

	/**
	 * forest_tablepartテーブルから、テーブルの属性情報を取得する。
	 *
	 * @param column 取得するforest_tablepartのカラム名
	 *
	 * @return String 取得した値
	 */
	private String getTableParam(String column)
	{
		String val = null;

		try {
			ResultSet rs = null;
			String sql = "SELECT " + column + " FROM forest_tablepart " +
			             " WHERE table_name='" + tableName + "'" +
			             "   AND dbname='" + database.getDatabaseName() + "'";

			rs = gsc.executeQueryGSC(sql);

			if ( rs.next() )
				val = rs.getString(1);
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
		}

		return val;
	}

	/**
	 * パーティションキーを取得する
	 *
	 * @return String[] パーティションキーを保持したString配列
	 */
	public String[] getPartitionKeys()
	{
		String sql = "SELECT column_name FROM forest_partatr " +
		             " WHERE dbname='" + database.getDatabaseName() + "' " +
		             "   AND table_name ='" + tableName + "' ORDER BY column_no";

		ArrayList a = new ArrayList();

		try {

			ResultSet rs = gsc.executeQueryGSC(sql);

			while ( rs.next() )
			{
				a.add( rs.getString(1) );
			}
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			return null;
		}

		return ArrayUtil.array2stringarray(a);
	}

	/**
	 * テーブルからインデックスを削除する。
	 *
	 * パーティションテーブルの場合は、インデックス名を書き換え、
	 * 必要なパーティション上からパーティション化されたインデックスを削除する
	 * （パーティション１およびパーティション２対応）。
	 *
	 * @param index 削除するインデックス名
	 *
	 * @return booelan すべてのパーティションからの削除が成功したらtrue、失敗したらfalse
	 */
	public boolean dropIndex(String index)
	{
		boolean rc = true;
		
		if ( getTableType()==TYPE_REPLICATED )
		{
			/*
			 * 多重化テーブルを持つインスタンスIDリストを取得
			 */
			int[] instanceIds = null;

			try {
				instanceIds = getInstanceIds();
			}
			catch (Exception e)
			{
				Logger.error(e.getMessage());
				Logger.trace(e);
				rc = false;
			}

			/*
			 * すべてのインスタンスからインデックスを削除。
			 */
			for (int i=0 ; i<instanceIds.length ; i++)
			{
				Connection con = null;

				try {
					con = gsc.getInstance(instanceIds[i]).getDatabaseConnection(database.getDatabaseName(),
																				gsc.getUser(),
																				gsc.getPassword());
					
					con.createStatement().executeUpdate("DROP INDEX " + index);
					con.close();
				}
				catch (Exception e)
				{
					Logger.error(e.getMessage());
					Logger.trace(e);
					rc = false;
				}
			}
		}
		else
		{
			/*
			 * パーティション分割されたインデックスを、各インスタンスから削除する。
			 */
			for (int p=0 ; p<getPartCount() ; p++)
			{
				/*
				 * パーティション'p'を持つインスタンスIDリストを取得
				 */
				int[] instanceIds = null;

				try {
					instanceIds = getInstanceIds(p);
				}
				catch (Exception e)
				{
					Logger.error(e.getMessage());
					Logger.trace(e);
					rc = false;
				}

				/*
				 * すべてのインスタンスからインデックスを削除。
				 */
				for (int i=0 ; i<instanceIds.length ; i++)
				{
					Connection con = null;

					try {
						con = gsc.getInstance(instanceIds[i]).getDatabaseConnection(database.getDatabaseName(),
																					gsc.getUser(),
																					gsc.getPassword());

						/*
						 * FIXME: buildPartitionName()は本来パーティション名（テーブル名）作成用だが
						 * ここでは一時的に使用する。
						 */
						String idx = PartitionUtils.buildPartitionName(index, p);

						con.createStatement().executeUpdate("DROP INDEX " + idx);
						con.close();
					}
					catch (Exception e)
					{
						Logger.error(e.getMessage());
						Logger.trace(e);
						rc = false;
					}
				}
			}
		}

		return rc;
	}

	/** 
	 * パーティションテーブルの場合、パーティション関数名を取得する。
	 * デフォルトの場合、あるいは多重化テーブルの場合にはnullを返却する。
	 * 
	 * @return String ハッシュ関数名（デフォルトの場合はnull）
	 */
	public String getHashName()
	{
		return getTableParam("hash_name");
	}

	/**
	 * テーブルのステータスを取得する。
	 *
	 * @return int ステータス（0：運用中、1：閉塞中）
	 */
	public int getStatus()
	{
		return Integer.parseInt(getTableParam("status"));
	}


	/**
	 * テーブル名を取得する
	 *
	 * @return String テーブル名
	 */
	public String getName()
	{
		return tableName;
	}

	/**
	 * テーブルに付加されているインデックス名を取得する。
	 * パーティションテーブルの場合は論理名を取得する。
	 *
	 * @return String[] インデックス名のString配列
	 */
	public String[] getIndexNames()
	{
		int[] instanceids = getInstanceIds(0);
		Instance ins = gsc.getInstance(instanceids[0]);

		ArrayList a = new ArrayList();

		try {
			String t = tableName;
			if ( getPartCount()>1 )
				t = PartitionUtils.buildPartitionName(tableName, 0);

			String n = TableUtils.getSchemaName(tableName);

			Connection con = ins.getDatabaseConnection(database.getDatabaseName(),
													   gsc.getUser(),
													   gsc.getPassword());

			String sql = "SELECT i.relname FROM pg_namespace n, pg_class r, pg_class i, pg_index ii " +
				  " WHERE n.nspname='" + n + "' AND n.oid=r.relnamespace AND r.relname='" + t + "' " +
			      "   AND r.oid=ii.indrelid AND ii.indexrelid=i.oid AND ii.indisprimary=false";

			Statement stmt = con.createStatement();
			ResultSet rs = stmt.executeQuery(sql);

			while (rs.next())
			{
				String ind = rs.getString(1);

				Logger.debug("index: " + ind);
				
				if ( getPartCount()>1 )
					a.add( ind.substring(0, ind.length()-3) );
				else
					a.add( ind );
			}

			rs.close();
			con.close();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
			return null;
		}

		return ArrayUtil.array2stringarray(a);
	}

	/**
	 * 実際に各インスタンスにテーブルが存在しているかどうかを確認する。
	 *
	 * パーティションテーブルの場合には、パーティションキーに指定された
	 * カラムが存在しているかどうかも確認する。
	 *
	 * @return boolean テーブルやキーが存在していればtrue、存在していなければfalse
	 */
	private boolean validateSchema()
	{
		boolean rc = true;

		/*
		 * このテーブルを保持する全インスタンスに 'SELECT * FROM table LIMIT 0' を
		 * 行って、正常に終了することを確認する。
		 *
		 * ただしパーティションテーブルの場合は、
		 * '*' の代わりにパーティションキーを指定する。
		 */
		try {
			int[] instanceIds = getInstanceIds();

			String selectList = "*";

			if ( getPartCount()>=2 )
			{
				String[] pkeys = getPartitionKeys();
				for (int i=0 ; i<pkeys.length ; i++)
				{
					if ( i==0 )
						selectList = "";
					else
						selectList = selectList + ",";

					selectList = selectList + pkeys[i];
				}
			}

			for (int i=0 ; i<instanceIds.length ; i++)
			{
				Instance ins = gsc.getInstance(instanceIds[i]);
				
				Connection con = ins.getDatabaseConnection(database.getDatabaseName(),
														   gsc.getUser(),
														   gsc.getPassword());

				ResultSet rs = con.createStatement().executeQuery("SELECT " + selectList + " FROM " + tableName + " LIMIT 0");
				rc = true;

				con.close();
			}
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
			rc = false;
		}

		return rc;
	}

	/**
	 * 全インスタンスのテーブルのレコード数を比較する
	 *
	 * @return boolean 全インスタンスでレコード数が同じであればtrue、異なるインスタンスがある場合はfalse
	 */
	private boolean validateCount()
	{
		int count = -1;
		boolean result = true;

		try {
			int[] instanceIds = getInstanceIds();

			for (int i=0 ; i<instanceIds.length ; i++)
			{
				Instance ins = gsc.getInstance(instanceIds[i]);
				
				Connection con = ins.getDatabaseConnection(database.getDatabaseName(),
														   gsc.getUser(),
														   gsc.getPassword());

				ResultSet rs = con.createStatement().executeQuery("SELECT count(*) FROM " + tableName);

				if ( rs.next() )
				{
					int c = rs.getInt(1);

					Logger.debug("validateCount(): " + ins.toString());
					Logger.debug("validateCount(): record count, " + tableName + " = " + c);

					if ( i>0 && c!=count )
					{
						Logger.error("Record count is different between instance " + instanceIds[i-1] + "(" + count + ") and " + instanceIds[i] + "(" + c + ").");
						result = false;
					}
					count = c;
				}

				con.close();
			}
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
			return false;
		}

		return result;
	}

	/**
	 * テーブルとGSCの整合性の確認を行う。
	 * 
	 * 確認内容
	 *  1. GSCに登録されているテーブルが実際に各インスタンス上に存在するか確認
	 *  2. パーティションテーブルの場合は、パーティションキーが各実テーブルに存在するか確認
	 *  3. 各テーブルのレコード数を比較
	 *
	 * @return boolean 整合性が取れている場合はtrue、取れていない場合にはfalse
	 */
	public boolean validate(int flags)
	{
		Logger.debug("Validating table " + tableName + "....");
		boolean rc = true;

		if ( (flags & GSCdata.VALIDATE_SCHEMA) != 0 )
			rc = rc && validateSchema();

		if ( (flags & GSCdata.VALIDATE_RECORD_COUNT) != 0 )
			rc = rc && validateCount();

		/*
		 * FIXME: need to implement
		 */
		if ( (flags & GSCdata.VALIDATE_RECORD_COMPARE) !=0 )
			rc = false;

		return rc;
	}

	/**
	 * 指定したサーバ上にあるパーティションの優先度を取得する。
	 *
	 * @param instanceId インスタンスID(0〜n)
	 * @param partitionNo パーティション番号(0〜n)
	 *
	 * @return int 優先度
	 */
	public int getPriority(int instanceId, int partitionNo)
	{
		int priority = -1;

		try {
			String sql = "SELECT priority FROM forest_servdb s, forest_tablepartdtl t" +
			             " WHERE s.serverid=" + instanceId + " " +
			             "   AND s.dbno=t.dbno " +
			             "   AND s.dbname=t.dbname " +
			             "   AND t.dbname='" + database.getDatabaseName() + "' " +
			             "   AND t.table_name='" + tableName + "'" +
			             "   AND t.part_no=" + partitionNo;

			ResultSet rs = gsc.executeQueryGSC(sql);

			if ( rs.next() )
				priority = rs.getInt(1);

			rs.close();
		}
		catch (Exception e) {
			Logger.error(e.getMessage());
			Logger.trace(e);
		}

		return priority;
	}

	/**
	 * 指定したサーバ上にあるパーティションの優先度を設定する。
	 *
	 * @param instanceId インスタンスID(0〜n)
	 * @param partitionNo パーティション番号(0〜n)
	 * @param priority 優先度(0〜n)
	 *
	 * @return boolean 優勢度を設定できればtrue、失敗したらfalse
	 */
	public boolean setPriority(int instanceId, int partitionNo, int priority)
	{
		boolean result = false;

		try {
			String sql = "UPDATE forest_tablepartdtl SET priority=" + priority +
			             " WHERE dbname='" + database.getDatabaseName() + "'" +
			             "   AND table_name='" + tableName + "'" +
			             "   AND part_no=" + partitionNo +
			             "   AND dbno=(SELECT dbno FROM forest_servdb " +
			             "              WHERE dbname='" + database.getDatabaseName() +"' " +
			             "                AND serverid=" + instanceId + ")";
			
			int rc = gsc.executeUpdateGSC(sql);

			if ( rc>0 )
				result = true;
		}
		catch (Exception e) {
			Logger.error(e.getMessage());
			Logger.trace(e);
		}

		return result;
	}
}
