    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.plugin.table;
    017
    018import java.io.File;
    019import java.io.PrintWriter;
    020import java.util.Map;
    021
    022import org.opengion.fukurou.db.DBUtil;
    023import org.opengion.fukurou.db.Transaction;                     // 5.5.2.6 (2012/05/25)
    024import org.opengion.fukurou.util.ErrorMessage;
    025import org.opengion.fukurou.util.FileUtil;
    026import org.opengion.fukurou.util.FixLengthData;
    027import org.opengion.fukurou.util.StringUtil;
    028import org.opengion.hayabusa.common.HybsSystem;
    029import org.opengion.hayabusa.common.HybsSystemException;
    030import org.opengion.hayabusa.db.AbstractTableFilter;
    031import org.opengion.hayabusa.db.DBTableModel;
    032
    033/**
    034 * TableFilter_INDEX は、TableUpda インターフェースを継承した、DBTableModel 処理用の
    035 * 実装クラスです。
    036 *
    037 * ここでは、インデックス一覧の検索結果より、GF07 のインデックスカラム定義テーブルから
    038 * 必要な情報を取得し、インデックス作成スクリプトを作成します。
    039 * 出力ファイルは、テーブル名＋"C.sql" という命名規則で作成します。
    040 * 検索では、(SYSTEM_ID,TBLSYU,TABLE_NAME,TABLE_LABEL,INDEX_NAME,NAME_JA,INDTYPE,TABLESPACE_NAME,INITIAL_EXTENT,NEXT_EXTENT)
    041 * の項目を取得する必要があります。
    042 *
    043 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
    044 * 【パラメータ】
    045 *  {
    046 *       DIR : {&#064;BASE_DIR}/sql/install/02_INDEX ;    出力ファイルの基準フォルダ(必須)
    047 *       XML : false ;                                    XML出力を行うかどうか[true/false]を指定します(初期値:false)。
    048 *       DROP: false ;                                    INDEX構文の前に、DROP構文を出力するかどうか[true/false]を指定します(初期値:false)。
    049 *  }
    050 *
    051 * @og.formSample
    052 * ●形式：
    053 *      select SYSTEM_ID,TBLSYU,TABLE_NAME,TABLE_LABEL,INDEX_NAME,NAME_JA,INDTYPE,TABLESPACE_NAME,INITIAL_EXTENT,NEXT_EXTENT from GF07
    054 *      ① &lt;og:tableFilter classId="INDEX" keys="DIR,XML" vals="{&#064;BASE_DIR}/sql/install/02_INDEX,false" /&gt;
    055 *
    056 *      ② &lt;og:tableFilter classId="INDEX" &gt;
    057 *               {
    058 *                   DIR : {&#064;BASE_DIR}/sql/install/02_INDEX ;
    059 *                   XML : false ;
    060 *                   DROP: false ;
    061 *               }
    062 *         &lt;/og:tableFilter&gt;
    063 *
    064 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
    065 *
    066 * @version  0.9.0  2000/10/17
    067 * @author   Kazuhiko Hasegawa
    068 * @since    JDK1.1,
    069 */
    070public class TableFilter_INDEX extends AbstractTableFilter {
    071        //* このプログラムのVERSION文字列を設定します。   {@value} */
    072        private static final String VERSION = "5.6.9.2 (2013/10/18)" ;
    073
    074        /**
    075         * keys の整合性チェックを行うための初期設定を行います。
    076         *
    077         * @og.rev 5.6.6.1 (2013/07/12) keys の整合性チェック対応
    078         *
    079         * @param       keysMap keys の整合性チェックを行うための Map
    080         */
    081        @Override
    082        protected void init( final Map<String,String> keysMap ) {
    083                keysMap.put( "DIR"      , "出力ファイルの基準フォルダ(必須)"                                                   );
    084                keysMap.put( "XML"      , "XML出力を行うかどうか[true/false]を指定(初期値:false)"              );
    085                keysMap.put( "DROP"     , "INDEX構文の前に、DROP構文を出力するかどうか(初期値:false)"       );
    086        }
    087
    088        private static final String[] DBKEY = {"SYSTEM_ID","TBLSYU","TABLE_NAME","TABLE_LABEL","INDEX_NAME","NAME_JA","INDTYPE",
    089                                                        "TABLESPACE_NAME","INITIAL_EXTENT","NEXT_EXTENT" };
    090
    091        // 5.1.1.0 (2009/12/01) データのアクセス用の配列番号のIDを private ⇒ protected にします。
    092        /** データのアクセス用の配列番号 {@value} */
    093        protected static final int SYSTEM_ID            = 0;
    094        /** データのアクセス用の配列番号 {@value} */
    095        protected static final int TBLSYU                       = 1;
    096        /** データのアクセス用の配列番号 {@value} */
    097        protected static final int TABLE_NAME           = 2;
    098        /** データのアクセス用の配列番号 {@value} */
    099        protected static final int TABLE_LABEL          = 3;    // GF02 の NAME_JA より JOIN
    100        /** データのアクセス用の配列番号 {@value} */
    101        protected static final int INDEX_NAME           = 4;
    102        /** データのアクセス用の配列番号 {@value} */
    103        protected static final int INDTYPE                      = 6;
    104        /** データのアクセス用の配列番号 {@value} */
    105        protected static final int TABLESPACE_NAME      = 7;
    106        /** データのアクセス用の配列番号 {@value} */
    107        protected static final int INITIAL_EXTENT       = 8;
    108        /** データのアクセス用の配列番号 {@value} */
    109        protected static final int NEXT_EXTENT          = 9;
    110
    111        // 5.1.1.2 (2009/12/10)
    112        private static final String GF07_SEL = "select A.CLM, B.USE_LENGTH"
    113                                                                                        + " from GF07 A left outer join GF05 B"
    114                                                                                        + " on    A.SYSTEM_ID  = B.SYSTEM_ID"
    115                                                                                        + " and   A.TBLSYU     = B.TBLSYU"
    116                                                                                        + " and   A.TABLE_NAME = B.TABLE_NAME"
    117                                                                                        + " and   A.CLM        = B.CLM"
    118                                                                                        + " and   B.FGJ        = '1'"
    119                                                                                        + " where A.SYSTEM_ID=? and A.TBLSYU=? and A.TABLE_NAME=? and A.INDEX_NAME=?"
    120                                                                                        + " and   A.FGJ='1'"
    121                                                                                        + " order by A.SEQNO" ;
    122
    123 //     private static final String ENCODE = "Windows-31J" ;
    124        private static final String ENCODE = "UTF-8" ; // 4.3.6.6 (2009/05/15)
    125
    126        private static final String CMNT  = "************************************************************************" ;
    127
    128        private static final int X = FixLengthData.X ;
    129        private static final int S = FixLengthData.S ;
    130        private static final int K = FixLengthData.K ;
    131
    132        /** 各種定数 */
    133        protected static final String XML_START_TAG     = "<?xml version='1.0' encoding='UTF-8'?>" + CR + "<ROWSET tableName='xxx'>";
    134        protected static final String XML_END_TAG       = "</ROWSET>";
    135        protected static final String EXEC_START_TAG= "<EXEC_SQL>";
    136        protected static final String EXEC_END_TAG      = "</EXEC_SQL>";
    137
    138        /** XML形式かどうか  */
    139        protected boolean               isXml                           = false; // 4.3.7.0 (2009/06/01)
    140
    141        /**
    142         * DBTableModel処理を実行します。
    143         *
    144         * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
    145         * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
    146         * @og.rev 4.3.7.0 (2009/06/01) XML出力機能追加
    147         * @og.rev 5.1.1.0 (2009/12/01) XML_START_TAG に、tableName をセットします。
    148         * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応
    149         * @og.rev 5.5.2.6 (2012/05/25) protected変数を、private化したため、getterメソッドで取得するように変更
    150         * @og.rev 5.6.9.2 (2013/10/18) INDEXを作成する前に、削除構文を入れるかどうかを指定。
    151         *
    152         * @return      実行結果のテーブルモデル
    153         */
    154        public DBTableModel execute() {
    155                DBTableModel table = getDBTableModel();         // 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加
    156
    157                isXml  = StringUtil.nval( getValue( "XML" ), false );
    158
    159                // 5.6.9.2 (2013/10/18) DROP構文を出力するかどうか
    160                boolean isDrop = StringUtil.nval( getValue( "DROP" ), false );
    161
    162                int[] clmNo = getTableColumnNo( DBKEY );
    163                int rowCnt = table.getRowCount();
    164
    165                File dir = new File( getValue( "DIR" ) );
    166                if( ! dir.exists() && ! dir.mkdirs() ) {
    167                        String errMsg = "所定のフォルダが作成できませんでした。[" + dir + "]" ;
    168                        // 4.3.4.4 (2009/01/01)
    169                        throw new HybsSystemException( errMsg );
    170                }
    171
    172                String[] data  = null;
    173                String bkTableName = null;
    174                PrintWriter writer = null;
    175                Transaction tran = getTransaction();    // 5.5.2.6 (2012/05/25)
    176                for( int row=0; row<rowCnt; row++ ) {
    177                        String tableName = null;
    178        //              String tableLbl  = null;
    179                        String indexName = null;
    180                        try {
    181                                data  = table.getValues( row );
    182                                String systemId  = data[clmNo[SYSTEM_ID]];
    183                                String tblsyu    = data[clmNo[TBLSYU]];
    184                                tableName = data[clmNo[TABLE_NAME]];
    185        //                      tableLbl  = data[clmNo[TABLE_LABEL]];
    186                                indexName = data[clmNo[INDEX_NAME]];
    187
    188                                // テーブルがキーブレイクすると、セーブファイルを切り替える。
    189                                if( ! tableName.equals( bkTableName ) ) {
    190                                        if( writer != null ) {
    191                                                if( isXml ) { writer.println( XML_END_TAG ); }
    192                                                writer.close();
    193                                        }
    194                                        bkTableName = tableName;
    195                                        writer = FileUtil.getPrintWriter( new File( dir,tableName + ( isXml ? "C.xml" : "C.sql" ) ),ENCODE );
    196                                        if( isXml ) { writer.println( XML_START_TAG.replace( "xxx",tableName ) ); }             // 5.1.1.0 (2009/12/01) tableName をセット
    197                                        writer.print( makeHeadLine( clmNo,data ) );
    198                                }
    199
    200                                String[] vals = new String[] { systemId,tblsyu,tableName,indexName };
    201                                String[][] gf07 = DBUtil.dbExecute( GF07_SEL,vals,tran );       // 5.1.9.0 (2010/08/01) Transaction 対応
    202                                if( gf07.length == 0 ) {
    203                                        System.out.println( "TABLE=[" + tableName + "],INDEX=[" + indexName + "] is Not Found!" );
    204                                        continue;
    205                                }
    206                                // テーブルに対するカラム列
    207                                StringBuilder buf = new StringBuilder() ;
    208                                for( int j=0; j<gf07.length; j++ ) {
    209                                        // 5.1.1.2 (2009/12/10)
    210                                        buf.append( makeIndexClmStr( gf07[j][0], gf07[j][1] ) ).append( "," );
    211                                }
    212                                buf.deleteCharAt( buf.length()-1 );                     // 最後の "," を取り除く処理
    213
    214                                // 5.6.9.2 (2013/10/18) DROP構文を出力する。
    215                                if( isDrop ) {
    216                                        writer.print( makeDropLine( clmNo,data ) );
    217                                }
    218
    219                                String clms = buf.toString();
    220                                writer.print( makeLineList( clmNo,data,clms ) );
    221                                writer.println( makeEndLine( clmNo,data ) );
    222                        }
    223                        catch( RuntimeException ex ) {
    224                                ErrorMessage errMessage = makeErrorMessage( "TableFilter_INDEX Error",ErrorMessage.NG );
    225                                errMessage.addMessage( row+1,ErrorMessage.NG,"INDEX",ex.getMessage() );
    226                                errMessage.addMessage( row+1,ErrorMessage.NG,"INDEX",StringUtil.array2csv( data ) );
    227                                errMessage.addMessage( row+1,ErrorMessage.NG,"INDEX","TABLE=[" + tableName + "],INDEX=[" + indexName + "]" );
    228                                // BAT から呼び出す場合があるため、標準エラー出力にも情報を出しておきます。
    229                                System.out.println( errMessage );
    230                        }
    231                }
    232                if( isXml ) { writer.println( XML_END_TAG ); }
    233                if( writer != null ) { writer.close(); }
    234
    235                return table;
    236        }
    237
    238        /**
    239         * ヘッダー部分の処理を実行します。
    240         *
    241         * @og.rev 5.6.6.0 (2013/07/05) FixLengthData の簡易コンストラクタを使用
    242         *
    243         * @param       clmNo   カラム番号配列
    244         * @param       data    １行分のデータ配列
    245         *
    246         * @return      ヘッダー部分の文字列
    247         */
    248        protected String makeHeadLine( final int[] clmNo,final String[] data ) {
    249                String tableName = data[clmNo[TABLE_NAME]];
    250                String LINE1 = tableName + " ( " + data[clmNo[TABLE_LABEL]] + " )" ;
    251                String LINE2 = "Created : " + HybsSystem.getDate() ;
    252
    253                // 5.6.6.0 (2013/07/05) FixLengthData の簡易コンストラクタを使用
    254                int[] addLen = new int[] { 0,0,0 };     // 各データ間のスペース
    255                int[] type   = new int[] { X,K,X };     // 各データの種別 X:半角 S:空白前埋め K:全角混在
    256                FixLengthData fixData = new FixLengthData( addLen,type );
    257
    258                String[][] outData = new String[][] {
    259                        { "/**",        CMNT ,  "**/" },
    260                        { "/* ",        LINE1,  " */" },
    261                        { "/* ",        LINE2,  " */" },
    262                        { "/**",        CMNT ,  "**/" },
    263                };
    264
    265                fixData.addAllListData( outData );
    266
    267                return fixData.getAllFixData();
    268        }
    269
    270        /**
    271         * インデックス作成の処理を実行します。
    272         *
    273         * @og.rev 5.3.8.0 (2011/08/01) プライマリキー対応
    274         * @og.rev 5.6.9.2 (2013/10/18) INDTYPE で、その他ではなく、2:通常 で判断する。
    275         *
    276         * @param       clmNo   カラム番号配列
    277         * @param       data    １行分のデータ配列
    278         * @param   clms        カラム名(CSV形式)
    279         *
    280         * @return      作成された１行分の文字列
    281         */
    282        protected String makeLineList( final int[] clmNo,final String[] data,final String clms ) {
    283                String tableName = data[clmNo[TABLE_NAME]];
    284                String indexName = data[clmNo[INDEX_NAME]];
    285                String idxtype   = data[clmNo[INDTYPE]];
    286
    287                StringBuilder buf = new StringBuilder();
    288
    289                buf.append( CR );               // 先頭に、改行を入れておきます。
    290                if( isXml ) { buf.append( EXEC_START_TAG ).append( CR ); }
    291
    292                // 5.3.8.0 (2011/08/01) プライマリキー対応
    293                if( "0".equals( idxtype ) ) {   // 0:プライマリキー
    294                        buf.append( "ALTER TABLE " ).append( tableName ).append( " ADD CONSTRAINT " );
    295                        buf.append( indexName ).append( " PRIMARY KEY ( " ).append( clms );
    296                        buf.append( " )" );
    297                }
    298                else if( "1".equals( idxtype ) ) {      // 1:ユニークキー
    299                        buf.append( "ALTER TABLE " ).append( tableName ).append( " ADD CONSTRAINT " );
    300                        buf.append( indexName ).append( " UNIQUE( " ).append( clms );
    301                        buf.append( " )" );
    302                }
    303                // 5.6.9.2 (2013/10/18) INDTYPE で、その他ではなく、2:通常 で判断する。
    304                else if( "2".equals( idxtype ) ) {      // 2:通常
    305                        buf.append( "CREATE INDEX " ).append( indexName ).append( " ON " );
    306                        buf.append( tableName ).append( "( " ).append( clms );
    307                        buf.append( " )" );
    308                }
    309                else {
    310                        String errMsg = "INDTYPE が、0,1,2 以外の値が使われています。INDTYPE=[" + idxtype + "]"
    311                                                        + " TABLE_NAME=[" + tableName + "] INDEX_NAME=[" + indexName + "]" ;
    312                        System.out.println( errMsg );
    313                }
    314
    315                return buf.toString();
    316        }
    317
    318        /**
    319         * 定義の最後の部分の処理を実行します。
    320         *
    321         * 5.8.3.2 (2015/01/23)より
    322         * １．TABLESPACE_NAME を指定しない場合は、TABLESPACE 句を出力しません。
    323         * ２．INITIAL_EXTENT,NEXT_EXTENT を 0 で指定した場合は該当箇所を出力しません。
    324         * ３．PCTINCREASE は、出力しません。
    325         *
    326         * @og.rev 5.3.9.0 (2011/09/01) プライマリキー対応２
    327         * @og.rev 5.8.3.1 (2015/01/23) 不要項目を出さないようにする(V6の6.1.0.0と類似の対応)
    328         *                                              V6ではNEXT_EXTENT完全削除ですが、V5では互換性のため一旦残しておきます。
    329         *
    330         * @param       clmNo   カラム番号配列
    331         * @param       data    １行分のデータ配列
    332         *
    333         * @return      定義の最後の部分
    334         */
    335        protected String makeEndLine( final int[] clmNo,final String[] data ) {
    336                final String tblSpcse = data[clmNo[TABLESPACE_NAME]] ;
    337                final String initExt  = data[clmNo[INITIAL_EXTENT]] ;
    338                final String nextExt  = data[clmNo[NEXT_EXTENT]] ;
    339
    340                StringBuilder buf = new StringBuilder();
    341                buf.append( CR );               // 先頭に、改行を入れておきます。
    342
    343                if( !StringUtil.isNull( tblSpcse ) || !StringUtil.isNull( initExt ) ) {
    344                        String idxtype   = data[clmNo[INDTYPE]];
    345                        if( "0".equals( idxtype ) || "1".equals( idxtype ) ) {  // 0:プライマリキー , 1:ユニークキー
    346                                buf.append( "USING INDEX " );
    347                        }
    348
    349                        if( !StringUtil.isNull( tblSpcse ) ) {
    350                                buf.append( "TABLESPACE " ).append( data[clmNo[TABLESPACE_NAME]] ).append( CR );
    351                        }
    352
    353                        if( !StringUtil.isNull( initExt ) && initExt.charAt(0) != '0' ) {
    354                                buf.append( "STORAGE( INITIAL " ).append(  data[clmNo[INITIAL_EXTENT]] ).append("K");
    355                                if( !StringUtil.isNull( nextExt ) && nextExt.charAt(0) != '0' ) {
    356                                        buf.append( " NEXT " ).append(  data[clmNo[NEXT_EXTENT]] ).append( "K" );
    357                                }
    358//                              buf.append( "K PCTINCREASE 0 )" );
    359                                buf.append( ")" );
    360                        }
    361                }
    362
    363                if( isXml )     { buf.append( CR ).append( EXEC_END_TAG ); }
    364                else            { buf.append( ";" ); }
    365
    366                return buf.toString();
    367        }
    368
    369        /**
    370         * インデックス削除の構文を、作成します。
    371         *
    372         * @og.rev 5.6.9.2 (2013/10/18) 新規作成
    373         *
    374         * @param       clmNo   カラム番号配列
    375         * @param       data    １行分のデータ配列
    376         *
    377         * @return      作成された１行分の文字列
    378         */
    379        protected String makeDropLine( final int[] clmNo,final String[] data ) {
    380                String tableName = data[clmNo[TABLE_NAME]];
    381                String indexName = data[clmNo[INDEX_NAME]];
    382                String idxtype   = data[clmNo[INDTYPE]];
    383
    384                StringBuilder buf = new StringBuilder();
    385
    386                buf.append( CR );               // 先頭に、改行を入れておきます。
    387                if( isXml ) { buf.append( EXEC_START_TAG ).append( CR ); }
    388
    389                // 0:プライマリキー , 1:ユニークキー
    390                if( "0".equals( idxtype ) || "1".equals( idxtype ) ) {
    391                        buf.append( "ALTER TABLE " ).append( tableName ).append( " DROP CONSTRAINT " );
    392                        buf.append( indexName );
    393                }
    394                // 5.6.9.2 (2013/10/18) INDTYPE で、その他ではなく、2:通常 で判断する。
    395                else if( "2".equals( idxtype ) ) {      // 2:通常
    396                        buf.append( "DROP INDEX " ).append( indexName );
    397                }
    398        //  一連の処理で、makeLineList ですでにエラーが出ているハズなので、ここでは出しません。
    399        //      else {
    400        //              String errMsg = "INDTYPE が、0,1,2 以外の値が使われています。INDTYPE=[" + idxtype + "]"
    401        //                                              + " TABLE_NAME=[" + tableName + "] INDEX_NAME=[" + indexName + "]" ;
    402        //              System.out.println( errMsg );
    403        //      }
    404
    405                if( isXml )     { buf.append( CR ).append( EXEC_END_TAG ); }
    406                else            { buf.append( ";" ); }
    407
    408                return buf.toString();
    409        }
    410
    411        /**
    412         * インデックスを作成するための文字列を返します。
    413         * 通常、カラム名をそのまま返します。
    414         * 但し、唯一、MySQLの場合、500バイト以上のカラムについては、TEXTで定義しており、
    415         * この場合、インデックス化するバイト数(最大255)を指定する必要があります。
    416         * このケースに対応するため、カラム名とバイト数を元に判定し、部分インデックスを
    417         * 作成するための文字列を作成します。
    418         *
    419         * @param       clm             カラム名
    420         * @param       useLen  カラムのバイト数
    421         *
    422         * @return      インデックスカラムの文字列
    423         * @see TableFilter_INDEX_MYSQL
    424         */
    425        protected String makeIndexClmStr( final String clm, final String useLen ) {
    426                return clm;
    427        }
    428}