package org.maachang.mimdb.core;

import java.io.OutputStream;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Types;
import java.util.Arrays;
import java.util.List;

import org.maachang.mimdb.MimdbException;
import org.maachang.mimdb.core.impl.MBoolIndex;
import org.maachang.mimdb.core.impl.MDateIndex;
import org.maachang.mimdb.core.impl.MFloatIndex;
import org.maachang.mimdb.core.impl.MIntIndex;
import org.maachang.mimdb.core.impl.MLongIndex;
import org.maachang.mimdb.core.impl.MStringIndex;
import org.maachang.mimdb.core.impl.MTimeIndex;
import org.maachang.mimdb.core.impl.MTimestampIndex;
import org.maachang.mimdb.core.impl.MimdbUtils;
import org.maachang.mimdb.core.util.NNObjectKeyValue;
import org.maachang.mimdb.core.util.ObjectBinary;
import org.maachang.mimdb.core.util.ObjectKeySet;
import org.maachang.mimdb.core.util.ObjectList;

/**
 * テーブル情報.
 * 
 * @version 2013/10/13
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
@SuppressWarnings("unchecked")
public class MimdbTable implements BaseTable {
    
    /** DB更新ID. **/
    protected long dbId = -1 ;
    
    /** テーブル名. **/
    protected String name = null ;
    
    /** テーブル情報. **/
    protected MimdbRow[] mainTable = null ;
    
    /** ワークテーブル **/
    private ObjectList<Object[]> workTable = null ;
    
    /** カラム名一覧. **/
    protected String[] columns = null ;
    
    /** 主キーカラム項番. **/
    protected int primaryIndexKeyNo = -1 ;
    
    /** sqlカラム型定義. **/
    private int[] sqlColumnTypes = null ;
    
    /** カラム型定義. **/
    protected int[] columnTypes = null ;
    
    /** カラム名Map. **/
    protected NNObjectKeyValue<String,Integer> columnsMap = null ;
    
    /** キーMap. **/
    protected NNObjectKeyValue<String,MimdbIndex> keyMap = null ;
    
    /** データバッファ長. **/
    private int bufferLength = -1 ;
    
    /** カラム長. **/
    protected int columnLength = -1 ;
    
    /** 行圧縮. **/
    protected boolean compressFlag = false ;
    
    /** 1度の行圧縮長. */
    protected int compressLength = 1 ;
    
    /** 全データ長. **/
    protected int allBinaryLength = 0 ;
    
    /** 圧縮データ長. **/
    protected int allCompressLength = 0 ;
    
    /**
     * コンストラクタ.
     */
    private MimdbTable() {}
    
    /**
     * コンストラクタ.
     * 直接データセット用.
     * @param dbId DB更新IDを設定します.
     * @param name テーブル名を設定します.
     * @param length 総データ長を設定します.または、バッファ長を設定します.
     * @param columns カラム名一覧を設定します.
     * @param columnTypes カラム型定義を設定します.
     * @param primaryIndexKey 主キーカラム名を設定します.
     * @param keyNames キーカラム名を設定します.
     * @param ngramKeys NGramでキー検索を行うキー名群を設定します.
     * @param hashKeys HashIndexを行うキー名群を設定します.
     * @param compressFlag 行圧縮処理を行う場合は[true]をセット.
     * @param compressLength この情報は１度の圧縮に対する行数を設定します.
     *                        この値設定は[compressFlag=true]でないと反映されません.
     *                        この値が少ないと、圧縮率は低下しますが、速度は向上します.
     *                        逆にこの値が大きすぎる場合は、圧縮率は上がりますが、速度が
     *                        低下します.
     * @exception Exception 例外.
     * データを自前で設定して、テーブル作成を行う場合は、第四引数のカラムタイプ[columnTypes]を
     * 以下の形で定義してください.
     * java.lang.Boolean  : MimdbIndex.COLUMN_BOOL
     * java.lang.Integer  : MimdbIndex.COLUMN_INT
     * java.lang.Long     : MimdbIndex.COLUMN_LONG
     * java.lang.Double   : MimdbIndex.COLUMN_FLOAT
     * java.lang.String   : MimdbIndex.COLUMN_STRING
     * java.sql.Date      : MimdbIndex.COLUMN_DATE
     * java.sql.Time      : MimdbIndex.COLUMN_TIME
     * java.sql.Timestamp : MimdbIndex.COLUMN_TIMESTAMP
     * java.util.Date     : MimdbIndex.COLUMN_TIMESTAMP
     */
    public MimdbTable( long dbId,String name,int length,String[] columns,int[] columnTypes,
        String primaryIndexKey,String[] keyNames,String[] ngramKeys,String[] hashKeys,
        boolean compressFlag,int compressLength )
        throws Exception {
        this.init( dbId,name,length,columns,columnTypes,primaryIndexKey,
            keyNames,ngramKeys,hashKeys,compressFlag,compressLength ) ;
    }
    
    /**
     * コンストラクタ.
     * JDBC経由でのデータセット用.
     * この処理で、ResultSet内の全データが追加されます.
     * @param dbId DB更新IDを設定します.
     * @param name テーブル名を設定します.
     * @param length 総データ長を設定します.または、バッファ長を設定します.
     * @param primaryIndexKey 主キーカラム名を設定します.
     * @param keyNames キー名を設定します.
     * @param ngramKeys NGramでキー検索を行うキー名群を設定します.
     * @param hashKeys HashIndexを行うキー名群を設定します.
     * @param compressFlag 行圧縮処理を行う場合は[true]をセット.
     * @param compressLength この情報は１度の圧縮に対する行数を設定します.
     *                        この値設定は[compressFlag=true]でないと反映されません.
     *                        この値が少ないと、圧縮率は低下しますが、速度は向上します.
     *                        逆にこの値が大きすぎる場合は、圧縮率は上がりますが、速度が
     *                        低下します.
     * @param rs JDBC経由で取得したResultSetを設定します.
     * @exception Exception 例外.
     */
    public MimdbTable( long dbId,String name,int length,String primaryIndexKey,String[] keyNames,
        String[] ngramKeys,String[] hashKeys,boolean compressFlag,int compressLength,ResultSet rs )
        throws Exception {
        if( rs == null ) {
            throw new MimdbException( "対象のResultSetがNULL" ) ;
        }
        // 初期処理.
        this.init( dbId,name,length,primaryIndexKey,keyNames,ngramKeys,hashKeys,
            compressFlag,compressLength,rs.getMetaData() ) ;
        // データ追加.
        this.addAllByJDBC( rs ) ;
    }
    
    /**
     * コンストラクタ.
     * JDBC経由でのデータセット用.
     * @param dbId DB更新IDを設定します.
     * @param name テーブル名を設定します.
     * @param length 総データ長を設定します.または、バッファ長を設定します.
     * @param primaryIndexKey 主キーカラム名を設定します.
     * @param keyNames キー名を設定します.
     * @param ngramKeys NGramでキー検索を行うキー名群を設定します.
     * @param hashKeys HashIndexを行うキー名群を設定します.
     * @param hashKeys HashIndexを行うキー名群を設定します.
     * @param compressFlag 行圧縮処理を行う場合は[true]をセット.
     * @param compressLength この情報は１度の圧縮に対する行数を設定します.
     *                        この値設定は[compressFlag=true]でないと反映されません.
     *                        この値が少ないと、圧縮率は低下しますが、速度は向上します.
     *                        逆にこの値が大きすぎる場合は、圧縮率は上がりますが、速度が
     *                        低下します.
     * @param meta JDBC経由で取得したResultSetのメタデータを設定します.
     * @exception Exception 例外.
     */
    public MimdbTable( long dbId,String name,int length,String primaryIndexKey,String[] keyNames,
        String[] ngramKeys,String[] hashKeys,boolean compressFlag,int compressLength,ResultSetMetaData meta )
        throws Exception {
        this.init( dbId,name,length,primaryIndexKey,keyNames,ngramKeys,hashKeys,
            compressFlag,compressLength,meta ) ;
    }
    
    /** キーマップ作成. **/
    private final NNObjectKeyValue<String,MimdbIndex> createKeyMap( String[] keyNames,String[] ngramKeys,
        String[] hashKeys,NNObjectKeyValue<String,Integer> columnsMap,int[] columnTypes,int length )
        throws Exception {
        // NGram作成条件を生成.
        ObjectKeySet<String> ngramKeySet ;
        ObjectKeySet<String> hashIndexKeySet ;
        
        // ngram.
        if( ngramKeys == null || ngramKeys.length <= 0 ) {
            ngramKeySet = null ;
        }
        else {
            ngramKeySet = new ObjectKeySet<String>() ;
            int len = ngramKeys.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( ( ngramKeys[ i ] = ngramKeys[ i ].trim() ).length() > 0 ) {
                    ngramKeySet.add( ngramKeys[ i ].toLowerCase() ) ;
                }
            }
            if( ngramKeySet.size() <= 0 ) {
                ngramKeySet = null ;
            }
        }
        
        // hashIndex.
        if( hashKeys == null || hashKeys.length <= 0 ) {
            hashIndexKeySet = null ;
        }
        else {
            hashIndexKeySet = new ObjectKeySet<String>() ;
            int len = hashKeys.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( ( hashKeys[ i ] = hashKeys[ i ].trim() ).length() > 0 ) {
                    hashIndexKeySet.add( hashKeys[ i ].toLowerCase() ) ;
                }
            }
            if( hashIndexKeySet.size() <= 0 ) {
                hashIndexKeySet = null ;
            }
        }
        
        // キーチェック.
        boolean hashIndexFlag ;
        int len = keyNames.length ;
        NNObjectKeyValue<String,MimdbIndex> ret = new NNObjectKeyValue<String,MimdbIndex>( len * 2 ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( keyNames[ i ] == null || ( keyNames[ i ] = keyNames[ i ].trim() ).length() <= 0 ) {
                throw new MimdbException( "キー名は存在しません[" + (i+1) + "]" ) ;
            }
            keyNames[ i ] = keyNames[ i ].toLowerCase() ;
            if( !columnsMap.containsKey( keyNames[ i ] ) ) {
                throw new MimdbException( "キー名[" + keyNames[ i ] + "]はカラム登録されていません" ) ;
            }
            
            // ハッシュインデックスフラグを取得.
            hashIndexFlag = ( hashIndexKeySet != null && hashIndexKeySet.contains( keyNames[ i ] ) ) ;
            
            // キーカラム生成.
            switch( columnTypes[ columnsMap.get( keyNames[ i ] ) ] ) {
            case MimdbIndex.COLUMN_BOOL :
                ret.put( keyNames[ i ],new MBoolIndex( dbId,keyNames[ i ],length ) ) ;
                break ;
            case MimdbIndex.COLUMN_INT :
                ret.put( keyNames[ i ],new MIntIndex( dbId,keyNames[ i ],length,hashIndexFlag ) ) ;
                break ;
            case MimdbIndex.COLUMN_LONG :
                ret.put( keyNames[ i ],new MLongIndex( dbId,keyNames[ i ],length,hashIndexFlag ) ) ;
                break ;
            case MimdbIndex.COLUMN_FLOAT :
                ret.put( keyNames[ i ],new MFloatIndex( dbId,keyNames[ i ],length,hashIndexFlag ) ) ;
                break ;
            case MimdbIndex.COLUMN_STRING :
                // NGramでキー生成を行う場合.
                if( ngramKeySet != null && ngramKeySet.contains( keyNames[ i ] ) ) {
                    ret.put( keyNames[ i ],new MStringIndex( dbId,keyNames[ i ],length,true,hashIndexFlag ) ) ;
                }
                else {
                    ret.put( keyNames[ i ],new MStringIndex( dbId,keyNames[ i ],length,false,hashIndexFlag ) ) ;
                }
                break ;
            case MimdbIndex.COLUMN_DATE :
                ret.put( keyNames[ i ],new MDateIndex( dbId,keyNames[ i ],length,hashIndexFlag ) ) ;
                break ;
            case MimdbIndex.COLUMN_TIME :
                ret.put( keyNames[ i ],new MTimeIndex( dbId,keyNames[ i ],length,hashIndexFlag ) ) ;
                break ;
            case MimdbIndex.COLUMN_TIMESTAMP :
                ret.put( keyNames[ i ],new MTimestampIndex( dbId,keyNames[ i ],length,hashIndexFlag ) ) ;
                break ;
            }
        }
        return ret ;
    }
    
    /** 直接データセット用初期処理. **/
    private final void init( long dbId,String name,int length,String[] columns,int[] columnTypes,
        String primaryIndexKey,String[] keyNames,String[] ngramKeys,String[] hashKeys,boolean compressFlag,int compressLength )
        throws Exception {
        if( dbId < 0 ) {
            throw new MimdbException( "更新IDが設定されていません" ) ;
        }
        if( name == null || ( name = name.trim().toLowerCase() ).length() <= 0 ) {
            throw new MimdbException( "テーブル名が設定されていません" ) ;
        }
        if( columns == null || columnTypes == null || keyNames == null ) {
            throw new MimdbException( "カラム条件が設定されていません" ) ;
        }
        if( columns.length != columnTypes.length ) {
            throw new MimdbException( "カラム条件数が一致しません" ) ;
        }
        // カラム名チェック.
        int len = columns.length ;
        int[] sqlColumnTypes = new int[ len ] ;
        NNObjectKeyValue<String,Integer> m = new NNObjectKeyValue<String,Integer>( len * 2 ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( columns[ i ] == null || ( columns[ i ] = columns[ i ].trim() ).length() <= 0 ) {
                throw new MimdbException( "カラム名は無効か、存在しません[" + (i+1) + "]" ) ;
            }
            columns[ i ] = columns[ i ].toLowerCase() ;
            if( m.containsKey( columns[ i ] ) ) {
                throw new MimdbException( "カラム名[" + columns[ i ] + "]は複数存在します" ) ;
            }
            // カラムタイプチェック.
            switch( columnTypes[ i ] ) {
            case MimdbIndex.COLUMN_BOOL :
                sqlColumnTypes[ i ] = Types.BOOLEAN ;
                break ;
            case MimdbIndex.COLUMN_INT :
                sqlColumnTypes[ i ] = Types.INTEGER ;
                break ;
            case MimdbIndex.COLUMN_LONG :
                sqlColumnTypes[ i ] = Types.BIGINT ;
                break ;
            case MimdbIndex.COLUMN_FLOAT :
                sqlColumnTypes[ i ] = Types.FLOAT ;
                break ;
            case MimdbIndex.COLUMN_STRING :
                sqlColumnTypes[ i ] = Types.VARCHAR ;
                break ;
            case MimdbIndex.COLUMN_DATE :
                sqlColumnTypes[ i ] = Types.DATE ;
                break ;
            case MimdbIndex.COLUMN_TIME :
                sqlColumnTypes[ i ] = Types.TIME ;
                break ;
            case MimdbIndex.COLUMN_TIMESTAMP :
                sqlColumnTypes[ i ] = Types.TIMESTAMP ;
                break ;
            default :
                throw new MimdbException( "対象カラム[" + columns[ i ] + "]の形式はサポートされていません[" + columnTypes[ i ] + "]" ) ;
            }
            // カラム名キー項番を設定.
            m.put( columns[ i ],i ) ;
        }
        
        // キーマップ作成.
        NNObjectKeyValue<String,MimdbIndex> k = createKeyMap( keyNames,ngramKeys,hashKeys,m,columnTypes,length ) ;
        
        // 主キーチェック.
        int primaryIndexKeyNo = -1 ;
        if( primaryIndexKey != null && ( primaryIndexKey = primaryIndexKey.trim() ).length() > 0 ) {
            primaryIndexKey = primaryIndexKey.toLowerCase() ;
            primaryIndexKeyNo = m.containsKey( primaryIndexKey ) ? m.get( primaryIndexKey ) : -1 ;
        }
        
        // データセット.
        this.mainTable = null ;
        this.workTable = new ObjectList<Object[]>( length ) ;
        this.columns = columns ;
        this.primaryIndexKeyNo = primaryIndexKeyNo ;
        this.columnTypes = columnTypes ;
        this.sqlColumnTypes = sqlColumnTypes ;
        this.columnsMap = m ;
        this.keyMap = k ;
        this.dbId = dbId ;
        this.name = name ;
        this.bufferLength = length ;
        this.columnLength = columns.length ;
        this.compressFlag = compressFlag ;
        if( compressFlag ) {
            this.compressLength = compressLength( compressLength ) ;
        }
        else {
            this.compressLength = 1 ;
        }
    }
    
    /** JDBC経由初期処理. **/
    private final void init( long dbId,String name,int length,String primaryIndexKey,String[] keyNames,
        String[] ngramKeys,String[] hashKeys,boolean compressFlag,int compressLength,ResultSetMetaData meta )
        throws Exception {
        if( dbId < 0 ) {
            throw new MimdbException( "更新IDが設定されていません" ) ;
        }
        if( name == null || ( name = name.trim().toLowerCase() ).length() <= 0 ) {
            throw new MimdbException( "テーブル名が設定されていません" ) ;
        }
        if( meta == null || keyNames == null ) {
            throw new MimdbException( "カラム条件が設定されていません" ) ;
        }
        
        // メタデータからカラム情報を取得.
        int len = meta.getColumnCount() ;
        String[] columns = new String[ len ] ;
        int[] columnTypes = new int[ len ] ;
        int[] sqlColumnTypes = new int[ len ] ;
        NNObjectKeyValue<String,Integer> m = new NNObjectKeyValue<String,Integer>( len * 2 ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            // カラム条件をセット.
            columns[ i ] = meta.getColumnName( i + 1 ).trim().toLowerCase() ;
            sqlColumnTypes[ i ] = meta.getColumnType( i + 1 ) ;
            
            // サポートカラムチェック.
            switch( sqlColumnTypes[ i ] ){
            case Types.BIT :
            case Types.BOOLEAN :
            case Types.TINYINT :
                columnTypes[ i ] = MimdbIndex.COLUMN_BOOL ;
                break ;
            case Types.SMALLINT :
                columnTypes[ i ] = MimdbIndex.COLUMN_INT ;
                break ;
            case Types.INTEGER :
            case Types.BIGINT :
                columnTypes[ i ] = MimdbIndex.COLUMN_LONG ;
                break ;
            case Types.FLOAT :
            case Types.REAL :
            case Types.DOUBLE :
            case Types.NUMERIC :
            case Types.DECIMAL :
                columnTypes[ i ] = MimdbIndex.COLUMN_FLOAT ;
                break ;
            case Types.CHAR :
            case Types.VARCHAR :
            case Types.LONGVARCHAR :
            case Types.DATALINK :
                columnTypes[ i ] = MimdbIndex.COLUMN_STRING ;
                break ;
            case Types.DATE :
                columnTypes[ i ] = MimdbIndex.COLUMN_DATE ;
                break ;
            case Types.TIME :
                columnTypes[ i ] = MimdbIndex.COLUMN_TIME ;
                break ;
            case Types.TIMESTAMP :
                columnTypes[ i ] = MimdbIndex.COLUMN_TIMESTAMP ;
                break ;
            default :
                throw new MimdbException( "対象カラム[" + columns[ i ] +
                        "]の形式はサポートされていません[" + columnTypes[ i ] + "]" ) ;
            }
            // カラム名キー項番を設定.
            m.put( columns[ i ],i ) ;
        }
        
        // キーマップ作成.
        NNObjectKeyValue<String,MimdbIndex> k = createKeyMap( keyNames,ngramKeys,hashKeys,m,columnTypes,length ) ;
        
        // 主キーチェック.
        int primaryIndexKeyNo = -1 ;
        if( primaryIndexKey != null && ( primaryIndexKey = primaryIndexKey.trim() ).length() > 0 ) {
            primaryIndexKey = primaryIndexKey.toLowerCase() ;
            primaryIndexKeyNo = m.containsKey( primaryIndexKey ) ? m.get( primaryIndexKey ) : -1 ;
        }
        
        // データセット.
        this.mainTable = null ;
        this.workTable = new ObjectList<Object[]>( length ) ;
        this.columns = columns ;
        this.primaryIndexKeyNo = primaryIndexKeyNo ;
        this.columnTypes = columnTypes ;
        this.sqlColumnTypes = sqlColumnTypes ;
        this.columnsMap = m ;
        this.keyMap = k ;
        this.dbId = dbId ;
        this.name = name ;
        this.bufferLength = length ;
        this.columnLength = columns.length ;
        this.compressFlag = compressFlag ;
        if( compressFlag ) {
            this.compressLength = compressLength( compressLength ) ;
        }
        else {
            this.compressLength = 1 ;
        }
    }
    
    /**
     * 追加データのクリア処理.
     */
    public void clear() {
        MimdbIndex index ;
        NNObjectKeyValue it = keyMap.reset() ;
        while( it.hasNext() ) {
            index = ( MimdbIndex )keyMap.get( ( String )it.next() ) ;
            index.clear() ;
        }
        this.mainTable = null ;
        this.workTable = new ObjectList<Object[]>( bufferLength ) ;
        this.allBinaryLength = 0 ;
        this.allCompressLength = 0 ;
    }
    
    /**
     * DB更新IDを取得.
     * この情報が、結果データと一致しない場合は、その結果データは古くなっています.
     * @return int DB更新IDが返却されます.
     */
    public long getDbId() {
        return dbId ;
    }
    
    /**
     * テーブル名を取得.
     * @return String テーブル名が返却されます.
     */
    public String getName() {
        return name ;
    }
    
    /** インデックス情報にデータを追加. **/
    private final void indexToValue( Object o,int line,int no ) throws Exception {
        MimdbIndex index ;
        // インデックス追加.
        switch( columnTypes[ no ] ) {
        case MimdbIndex.COLUMN_BOOL :
            if( ( index = keyMap.get( columns[ no ] ) ) != null ) {
                ((MBoolIndex)index).add( ( Boolean )o,line ) ;
            }
            break ;
        case MimdbIndex.COLUMN_INT :
            if( ( index = keyMap.get( columns[ no ] ) ) != null ) {
                ((MIntIndex)index).add( ( Integer )o,line ) ;
            }
            break ;
        case MimdbIndex.COLUMN_LONG :
            if( ( index = keyMap.get( columns[ no ] ) ) != null ) {
                ((MLongIndex)index).add( ( Long )o,line ) ;
            }
            break ;
        case MimdbIndex.COLUMN_FLOAT :
            if( ( index = keyMap.get( columns[ no ] ) ) != null ) {
                ((MFloatIndex)index).add( ( Double )o,line ) ;
            }
            break ;
        case MimdbIndex.COLUMN_STRING :
            if( ( index = keyMap.get( columns[ no ] ) ) != null ) {
                ((MStringIndex)index).add( ( String )o,line ) ;
            }
            break ;
        case MimdbIndex.COLUMN_DATE :
            if( ( index = keyMap.get( columns[ no ] ) ) != null ) {
                ((MDateIndex)index).add( ( java.sql.Date )o,line ) ;
            }
            break ;
        case MimdbIndex.COLUMN_TIME :
            if( ( index = keyMap.get( columns[ no ] ) ) != null ) {
                ((MTimeIndex)index).add( ( java.sql.Time )o,line ) ;
            }
            break ;
        case MimdbIndex.COLUMN_TIMESTAMP :
            if( ( index = keyMap.get( columns[ no ] ) ) != null ) {
                ((MTimestampIndex)index).add( ( java.sql.Timestamp )o,line ) ;
            }
            break ;
        }
    }
    
    /** JDBC経由のResultSet取得. **/
    private static final Object getJDBCColumnData( int[] sqlColumnTypes,int no,ResultSet result )
        throws Exception {
        int columnNo = no + 1 ;
        // 対象位置の情報がNULLの場合.
        if( result.getObject( columnNo ) == null ) {
            return null ;
        }
        Object ret = null ;
        switch( sqlColumnTypes[ no ] ){
            case Types.BOOLEAN :
                ret = result.getBoolean( columnNo ) ;
                break ;
            case Types.BIT :
            case Types.TINYINT :
                ret = result.getByte( columnNo ) ;
                byte b = ( ( Byte )ret ).byteValue() ;
                if( b == 0 ) {
                    ret = Boolean.FALSE ;
                }
                else {
                    ret = Boolean.TRUE ;
                }
                break ;
            case Types.SMALLINT :
                ret = result.getInt( columnNo ) ;
                break ;
            case Types.INTEGER :
            case Types.BIGINT :
                ret = result.getLong( columnNo ) ;
                break ;
            case Types.FLOAT :
            case Types.REAL :
                ret = MimdbUtils.convertDouble( result.getFloat( columnNo ) ) ;
                break ;
            case Types.DOUBLE :
                ret = MimdbUtils.convertDouble( result.getDouble( columnNo ) ) ;
                break ;
            case Types.NUMERIC :
            case Types.DECIMAL :
                ret = MimdbUtils.convertDouble( result.getBigDecimal( columnNo ) ) ;
                break ;
            case Types.CHAR :
            case Types.VARCHAR :
            case Types.LONGVARCHAR :
            case Types.DATALINK :
                ret = result.getString( columnNo ) ;
                break ;
            case Types.DATE :
                ret = MimdbUtils.convertSqlDate( result.getDate( columnNo ) ) ;
                break ;
            case Types.TIME :
                ret = MimdbUtils.convertSqlTime( result.getTime( columnNo ) ) ;
                break ;
            case Types.TIMESTAMP :
                ret = MimdbUtils.convertSqlTimestamp( result.getTimestamp( columnNo ) ) ;
                break ;
        }
        return ret ;
    }
    
    /**
     * 1件のデータを追加.
     * 直接データセット用.
     * @param oneLine 追加対象のオブジェクトを設定します.
     * @exception Exception 例外.
     */
    public void add( Object[] oneLine ) throws Exception {
        if( oneLine == null ) {
            throw new MimdbException( "追加対象のデータが存在しません" ) ;
        }
        if( workTable == null ) {
            throw new MimdbException( "このテーブルはデータ追加がFixされています" ) ;
        }
        
        int len = oneLine.length ;
        //int line = workTable.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            switch( columnTypes[ i ] ) {
            case MimdbIndex.COLUMN_BOOL :
                if( oneLine[ i ] == null || ((String)oneLine[ i ]).length() == 0 ) {
                    oneLine[ i ] = null ;
                }
                else {
                    oneLine[ i ] = MimdbUtils.convertBool( oneLine[ i ] ) ;
                }
                break ;
            case MimdbIndex.COLUMN_INT :
                if( oneLine[ i ] == null || ((String)oneLine[ i ]).length() == 0 ) {
                    oneLine[ i ] = null ;
                }
                else {
                    oneLine[ i ] = MimdbUtils.convertInt( oneLine[ i ] ) ;
                }
                break ;
            case MimdbIndex.COLUMN_LONG :
                if( oneLine[ i ] == null || ((String)oneLine[ i ]).length() == 0 ) {
                    oneLine[ i ] = null ;
                }
                else {
                    oneLine[ i ] = MimdbUtils.convertLong( oneLine[ i ] ) ;
                }
                break ;
            case MimdbIndex.COLUMN_FLOAT :
                if( oneLine[ i ] == null || ((String)oneLine[ i ]).length() == 0 ) {
                    oneLine[ i ] = null ;
                }
                else {
                    oneLine[ i ] = MimdbUtils.convertDouble( oneLine[ i ] ) ;
                }
                break ;
            case MimdbIndex.COLUMN_STRING :
                oneLine[ i ] = MimdbUtils.convertString( oneLine[ i ] ) ;
                break ;
            case MimdbIndex.COLUMN_DATE :
                if( oneLine[ i ] == null || ((String)oneLine[ i ]).length() == 0 ) {
                    oneLine[ i ] = null ;
                }
                else {
                    oneLine[ i ] = MimdbUtils.convertSqlDate( oneLine[ i ] ) ;
                }
                break ;
            case MimdbIndex.COLUMN_TIME :
                if( oneLine[ i ] == null || ((String)oneLine[ i ]).length() == 0 ) {
                    oneLine[ i ] = null ;
                }
                else {
                    oneLine[ i ] = MimdbUtils.convertSqlTime( oneLine[ i ] ) ;
                }
                break ;
            case MimdbIndex.COLUMN_TIMESTAMP :
                if( oneLine[ i ] == null || ((String)oneLine[ i ]).length() == 0 ) {
                    oneLine[ i ] = null ;
                }
                else {
                    oneLine[ i ] = MimdbUtils.convertSqlTimestamp( oneLine[ i ] ) ;
                }
                break ;
            }
        }
        // データセット.
        workTable.add( oneLine ) ;
    }
    
    /**
     * データを複数追加.
     * 直接データセット用.
     * @param allLine 追加対象のオブジェクトを設定します.
     * @return int 追加された件数が返却されます.
     * @exception Exception 例外.
     */
    public int addAll( List<Object[]> allLine ) throws Exception {
        if( allLine == null ) {
            throw new MimdbException( "追加対象の複数データが設定されていません" ) ;
        }
        if( workTable == null ) {
            throw new MimdbException( "このテーブルはデータ追加がFixされています" ) ;
        }
        int len = allLine.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            add( allLine.get( i ) ) ;
        }
        return len ;
    }
    
    /**
     * JDBCのResultSet経由でデータ追加.
     * @param result JDBC経由で取得したResultSetを設定します.
     * @return int 追加された件数が返却されます.
     * @exception Exception 例外.
     */
    public int addAllByJDBC( ResultSet result ) throws Exception {
        if( result == null ) {
            throw new MimdbException( "追加対象のResultSetが設定されていません" ) ;
        }
        if( workTable == null ) {
            throw new MimdbException( "このテーブルはデータ追加がFixされています" ) ;
        }
        Object o ;
        Object[] oneLine ;
        int i ;
        int ret = 0 ;
        int lineNo = workTable.size() ;
        int len = columnLength ;
        while( result.next() ) {
            oneLine = new Object[len] ;
            for( i = 0 ; i < len ; i ++ ) {
                o = getJDBCColumnData( sqlColumnTypes,i,result ) ;
                oneLine[ i ] = o ;
            }
            workTable.add( oneLine ) ;
            ret ++ ;
            lineNo ++ ;
        }
        
        return ret ;
    }
    
    /**
     * テーブル情報のFIX.
     * この操作を行った場合、テーブルに新たな情報は追加できません.
     * @exception Exception 例外.
     */
    public void fix() throws Exception {
        // 既にFixしている場合は処理しない.
        if( mainTable != null ) {
            return ;
        }
        else if( workTable == null || workTable.size() == 0 ) {
            //throw new MimdbException( "Fix対象のデータは存在しません" ) ;
            
            // テーブルが存在しない場合、ゼロ件のテーブル情報として作成.
            this.workTable = null ;
            this.mainTable = new MimdbRow[ 0 ] ;
            this.allBinaryLength = 0 ;
            this.allCompressLength = 0 ;
            return ;
        }
        // ワークテーブルから、メインテーブルに情報を移動.
        int len = workTable.size() ;
        int allLen = 0,compressLen = 0 ;
        MimdbRow[] list = new MimdbRow[ len ] ;
        // 圧縮条件の場合.
        if( compressFlag ) {
            int rawLen ;
            // 圧縮単位が１行単位の場合.
            if( compressLength == 1 ) {
                for( int i = 0 ; i < len ; i ++ ) {
                    rawLen = MimdbSnappyRow.byteLength( columnTypes,workTable.get( i ) ) ;
                    list[ i ] = new MimdbSnappyRow( this,i,workTable.get( i ),( rawLen > 64 ),rawLen ) ;
                    
                    // 圧縮、非圧縮データ長をセット.
                    allLen += rawLen ;
                    compressLen += list[ i ].compressLength() ;
                }
            }
            // 圧縮単位が１行以上の場合.
            else {
                MimdbSnappyRow parent ;
                int parentBinLen,j,k,b ;
                int parentLen = ( len / compressLength ) ;
                int etcLen = len - ( compressLength * parentLen ) ;
                Object[] cList = new Object[ compressLength ] ;
                // 親オブジェクト有りの条件で作成.
                for( int i = 0 ; i < parentLen ; i ++ ) {
                    b = i * compressLength ;
                    // 親オブジェクトの作成.
                    parentBinLen = 0 ;
                    for( j = 0 ; j < compressLength ; j ++ ) {
                        cList[ j ] = workTable.get( b+j ) ;
                        parentBinLen += MimdbSnappyRow.byteLength( columnTypes,(Object[])cList[ j ] ) ;
                    }
                    // 親オブジェクト生成.
                    parent = new MimdbSnappyRow( this,b,cList,parentBinLen ) ;
                    
                    // 圧縮、非圧縮データ長をセット.
                    allLen += parentBinLen ;
                    compressLen += parent.compressLength() ;
                    
                    // リストにオブジェクトをセット.
                    list[ b ] = parent ;
                    
                    // 子オブジェクトを作成.
                    for( j = 1,k = b + 1 ; j < compressLength ; j ++,k ++ ) {
                        list[ k ] = new MimdbSnappyRow( parent,this,k,workTable.get( k ) ) ;
                    }
                }
                cList = null ;
                // 余りの情報は、１行圧縮情報で作成.
                if( etcLen > 0 ) {
                    for( int i = len-etcLen ; i < len ; i ++ ) {
                        rawLen = MimdbSnappyRow.byteLength( columnTypes,workTable.get( i ) ) ;
                        list[ i ] = new MimdbSnappyRow( this,i,workTable.get( i ),( rawLen > 64 ),rawLen ) ;
                        // 圧縮、非圧縮データ長をセット.
                        allLen += rawLen ;
                        compressLen += list[ i ].compressLength() ;
                    }
                }
            }
        }
        // 非圧縮条件の場合.
        else {
            for( int i = 0 ; i < len ; i ++ ) {
                list[ i ] = new MimdbRawRow( this,i,workTable.get( i ) ) ;
            }
        }
        // 主キーが存在する場合.
        if( primaryIndexKeyNo != -1 ) {
            // 主キーでソートして、行番号を入れ替える.
            Arrays.sort( list ) ;
            Object b = null ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( i != 0 ) {
                    // 重複チェック.
                    if( b.equals( list[ i ].getValue( primaryIndexKeyNo ) ) ) {
                        throw new MimdbException( "テーブル[" + name + "]で定義された主キー[" +
                            columns[ primaryIndexKeyNo ] + "]は重複条件が存在しています" ) ;
                    }
                }
                list[ i ].lineNo = i ;
                b = list[ i ].getValue( primaryIndexKeyNo ) ;
            }
        }
        workTable = null ;
        mainTable = list ;
        
        // インデックス情報に現在のテーブル内容の条件を追加.
        int j ;
        Object[] values ;
        len = list.length ;
        int lenJ = columns.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            values = list[ i ].getValues() ;
            for( j = 0 ; j < lenJ ; j ++ ) {
                indexToValue( values[ j ],i,j ) ;
            }
        }
        
        // インデックス情報をインデックス化.
        MimdbIndex index ;
        NNObjectKeyValue<String,MimdbIndex> it = keyMap.reset() ;
        while( it.hasNext() ) {
            index = keyMap.get( it.next() ) ;
            index.createIndex() ;
        }
        
        this.allBinaryLength = allLen ;
        this.allCompressLength = compressLen ;
    }
    
    /**
     * 指定行の情報を取得.
     * @param no 対象の項番を設定します.
     * @return MimdbRow 行情報が返却されます.
     * @exception Exception 例外.
     */
    public MimdbRow get( int no ) throws Exception {
        if( !isFix() ) {
            throw new MimdbException( "対象テーブル[" + name + "]はFixされていません" ) ;
        }
        if( mainTable.length <= no || no < 0 ) {
            return null ;
        }
        return mainTable[ no ] ;
    }
    
    /**
     * データがFIXされているか取得.
     * @return boolean [true]の場合、追加データがFixされています.
     */
    public boolean isFix() {
        return ( mainTable != null ) ;
    }
    
    /**
     * データ件数の取得.
     * @return int データ件数が返却されます.
     */
    public int size() {
        return ( mainTable != null ) ? mainTable.length : -1 ;
    }
    
    /**
     * テーブル内配列情報を取得.
     * @return MimdbRow[] メインテーブル情報が返却されます.
     */
    public MimdbRow[] getMainTable() {
        return mainTable ;
    }
    
    /**
     * カラム数の取得.
     * @return int カラム数が返却されます.
     */
    public int getColumnSize() {
        return columnLength ;
    }
    
    /**
     * カラム名を取得.
     * @param no カラム項番を設定します.
     * @return String カラム名が返却されます.
     */
    public String getColumnName( int no ) {
        if( no < 0 || no >= columnLength ) {
            return null ;
        }
        return columns[ no ] ;
    }
    
    /**
     * 指定カラム名に対する項番を取得.
     * @param name カラム名を設定します.
     * @return int カラム項番が返却されます.
     *             [-1]の場合は対象カラム名は存在しません.
     */
    public int getColumnNameByNo( String name ) {
        if( name == null || ( name = name.trim().toLowerCase() ).length() <= 0 ) {
            return -1 ;
        }
        Integer ret = columnsMap.get( name ) ;
        if( ret == null ) {
            return -1 ;
        }
        return ret ;
    }
    
    /**
     * カラムタイプを取得.
     * @param no カラム項番を設定します.
     * @return int カラムタイプが返却されます.
     *             -1が返却された場合は、対象カラムは存在しません.
     * java.lang.Boolean  : MimdbIndex.COLUMN_BOOL
     * java.lang.Integer  : MimdbIndex.COLUMN_INT
     * java.lang.Long     : MimdbIndex.COLUMN_LONG
     * java.lang.Double   : MimdbIndex.COLUMN_FLOAT
     * java.lang.String   : MimdbIndex.COLUMN_STRING
     * java.sql.Date      : MimdbIndex.COLUMN_DATE
     * java.sql.Time      : MimdbIndex.COLUMN_TIME
     * java.sql.Timestamp : MimdbIndex.COLUMN_TIMESTAMP
     * java.util.Date     : MimdbIndex.COLUMN_DATE
     */
    public int getColumnType( int no ) {
        if( no < 0 || no >= columnLength ) {
            return -1 ;
        }
        return columnTypes[ no ] ;
    }
    
    /**
     * カラムタイプを取得.
     * @param name 対象のカラム名を設定します.
     * @return int カラムタイプが返却されます.
     *             -1が返却された場合は、対象カラムは存在しません.
     * java.lang.Boolean  : MimdbIndex.COLUMN_BOOL
     * java.lang.Integer  : MimdbIndex.COLUMN_INT
     * java.lang.Long     : MimdbIndex.COLUMN_LONG
     * java.lang.Double   : MimdbIndex.COLUMN_FLOAT
     * java.lang.String   : MimdbIndex.COLUMN_STRING
     * java.sql.Date      : MimdbIndex.COLUMN_DATE
     * java.sql.Time      : MimdbIndex.COLUMN_TIME
     * java.sql.Timestamp : MimdbIndex.COLUMN_TIMESTAMP
     * java.util.Date     : MimdbIndex.COLUMN_DATE
     */
    public int getColumnType( String name ) {
        if( name == null || ( name = name.trim().toLowerCase() ).length() <= 0 ) {
            return -1 ;
        }
        Integer no = columnsMap.get( name ) ;
        if( no == null ) {
            return -1 ;
        }
        return columnTypes[ no ] ;
    }
    
    /**
     * カラム名一覧を取得.
     * @return String[] カラム名一覧が返却されます.
     */
    public String[] getColumns() {
        return columns ;
    }
    
    /**
     * 指定項番のカラムがインデックスかチェック.
     * @param no 対象のカラム項番を設定します.
     * @return boolean [true]の場合は、インデックスです.
     */
    public boolean isIndex( int no ) {
        if( no < 0 || no >= columnLength ) {
            return false ;
        }
        return keyMap.containsKey( columns[ no ] ) ;
    }
    
    /**
     * 指定項番のカラムがインデックスかチェック.
     * @param name 対象のカラム名を設定します.
     * @return boolean [true]の場合は、インデックスです.
     */
    public boolean isIndex( String name ) {
        if( name == null || ( name = name.trim().toLowerCase() ).length() <= 0 ) {
            return false ;
        }
        return keyMap.containsKey( name ) ;
    }
    
    /**
     * 指定項番のインデックス情報を取得.
     * @param no 対象のカラム項番を設定します.
     * @return MimdbIndex インデックスオブジェクトが返却されます.
     *                    [null]の場合、インデックス情報ではありません.
     */
    public MimdbIndex getIndex( int no ) {
        if( no < 0 || no >= columnLength ) {
            return null ;
        }
        return keyMap.get( columns[ no ] ) ;
    }
    
    /**
     * 指定項番のカラムがインデックスかチェック.
     * @param name 対象のカラム名を設定します.
     * @return MimdbIndex インデックスオブジェクトが返却されます.
     *                    [null]の場合、インデックス情報ではありません.
     */
    public MimdbIndex getIndex( String name ) {
        if( name == null || ( name = name.trim().toLowerCase() ).length() <= 0 ) {
            return null ;
        }
        return keyMap.get( name ) ;
    }
    
    /**
     * 主キーカラム名を取得.
     * @return String 主キーカラム名が返却されます.
     *                [null]が返却された場合、主キーは存在しません.
     */
    public String getPrimaryIndexKey() {
        if( primaryIndexKeyNo == -1 ) {
            return null ;
        }
        return columns[ primaryIndexKeyNo ] ;
    }
    
    /**
     * 主キーカラム項番を取得.
     * @return int 主キーカラム項番が返却されます.
     *             [-1]の場合は、主キーは定義されていません.
     */
    public int getPrimaryIndexKeyNo() {
        return primaryIndexKeyNo ;
    }
    
    /**
     * 主キーが有効かチェック.
     * @return boolean [true]の場合、主キーは有効です.
     */
    public boolean isPrimaryIndexKey() {
        return ( primaryIndexKeyNo != -1 ) ;
    }
    
    /**
     * 主キー条件を検索.
     * @param value 主キー検索条件を設定します.
     * @return MimdbResultRow 主キーに対する行情報が返却されます.
     * @exception Exception 例外.
     */
    public MimdbResultRow searchPrimaryKey( Object value ) throws Exception {
        if( !isFix() ) {
            throw new MimdbException( "対象テーブル[" + name + "]はFixされていません" ) ;
        }
        if( primaryIndexKeyNo == -1 || value == null ) {
            return null ;
        }
        // 引数の型変換.
        switch( columnTypes[ primaryIndexKeyNo ] ) {
        case MimdbIndex.COLUMN_BOOL :
            value = MimdbUtils.convertBool( value ) ;
            break ;
        case MimdbIndex.COLUMN_INT :
            value = MimdbUtils.convertInt( value ) ;
            break ;
        case MimdbIndex.COLUMN_LONG :
            value = MimdbUtils.convertLong( value ) ;
            break ;
        case MimdbIndex.COLUMN_FLOAT :
            value = MimdbUtils.convertDouble( value ) ;
            break ;
        case MimdbIndex.COLUMN_STRING :
            value = MimdbUtils.convertString( value ) ;
            break ;
        case MimdbIndex.COLUMN_DATE :
            value = MimdbUtils.convertSqlDate( value ) ;
            break ;
        case MimdbIndex.COLUMN_TIME :
            value = MimdbUtils.convertSqlTime( value ) ;
            break ;
        case MimdbIndex.COLUMN_TIMESTAMP :
            value = MimdbUtils.convertSqlTimestamp( value ) ;
            break ;
        }
        // 主キー検索.
        int res = searchPrimaryKey( mainTable,value ) ;
        if( res == -1 ) {
            return null ;
        }
        return new PrimaryResultRowImpl( mainTable[ res ] ) ;
    }
    
    /**
     * 行圧縮条件が設定されている場合.
     * @return boolean [true]の場合は、行圧縮されています.
     */
    public boolean isComress() {
        return compressFlag ;
    }
    
    /**
     * 行圧縮サイズを取得.
     * @return int 1度に行圧縮を行う数が返却されます.
     */
    public int getCompressByLine() {
        return compressLength ;
    }
    
    /**
     * バイナリデータ長を取得.
     * @return int バイナリデータ長が返却されます.
     */
    public int getBinaryLength() {
        return allBinaryLength ;
    }
    
    /**
     * 圧縮データ長を取得.
     * @return int 圧縮データ長が返却されます.
     */
    public int getCompressLength() {
        return allCompressLength ;
    }
    
    /**
     * テーブル定義情報をバイナリ変換.
     * @param out 対象のOutputStreamを設定します.
     * @exception Exception 例外.
     */
    public void getBinary( OutputStream out )
        throws Exception {
        
        // テーブル名をセット.
        ObjectBinary.encode( out,name ) ;
        
        // テーブル更新IDをセット.
        ObjectBinary.encode( out,dbId ) ;
        
        // カラム名をセット.
        ObjectBinary.encode( out,columns ) ;
        
        // カラムタイプをセット.
        ObjectBinary.encode( out,columnTypes ) ;
        
        // インデックスIDをセット.
        int cnt = 0 ;
        int len = keyMap.size() ;
        int[] indexs = new int[ len ] ;
        NNObjectKeyValue<String,MimdbIndex> it = keyMap.reset() ;
        while( it.hasNext() ) {
            indexs[ cnt ++ ] = columnsMap.get( it.next() ) ;
        }
        ObjectBinary.encode( out,indexs ) ;
        
    }
    
    /**
     * 主キー検索.
     */
    private static final int searchPrimaryKey(MimdbRow[] a,Object key) {
        // 主キー検索はバイナリサーチを利用.
        int low = 0 ;
        int high = a.length -1 ;
        int mid,cmp ;
        while (low <= high) {
            mid = (low + high) >>> 1;
            if (( cmp = ( (Comparable)( a[mid].getPrimaryKey() ) ).compareTo(key) ) < 0) {
                low = mid + 1;
            }
            else if (cmp > 0) {
                high = mid - 1;
            }
            else {
                return mid; // key found
            }
        }
        return -1 ;
    }
    
    /** 圧縮行長を取得. **/
    protected static final int compressLength( int x ) {
        if( x < 1 ) {
            return 1 ;
        }
        x |= ( x >>  1 );
        x |= ( x >>  2 );
        x |= ( x >>  4 );
        x |= ( x >>  8 );
        x |= ( x >> 16 );
        x = (x & 0x55555555) + (x >> 1 & 0x55555555);
        x = (x & 0x33333333) + (x >> 2 & 0x33333333);
        x = (x & 0x0f0f0f0f) + (x >> 4 & 0x0f0f0f0f);
        x = (x & 0x00ff00ff) + (x >> 8 & 0x00ff00ff);
        x = (x & 0x0000ffff) + (x >>16 & 0x0000ffff);
        return 1 << ( ( (x & 0x0000ffff) + (x >>16 & 0x0000ffff) ) - 1 ) ;
    }

}
