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 : {@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 *      @ <og:tableFilter classId="INDEX" keys="DIR,XML" vals="{@BASE_DIR}/sql/install/02_INDEX,false" />
055 *
056 *      A <og:tableFilter classId="INDEX" >
057 *               {
058 *                   DIR : {@BASE_DIR}/sql/install/02_INDEX ;
059 *                   XML : false ;
060 *                   DROP: false ;
061 *               }
062 *         </og:tableFilter>
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    1行分のデータ配列
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    1行分のデータ配列
278         * @param   clms        カラム名(CSV形式)
279         *
280         * @return      作成された1行分の文字列
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         * 1.TABLESPACE_NAME を指定しない場合は、TABLESPACE 句を出力しません。
323         * 2.INITIAL_EXTENT,NEXT_EXTENT を 0 で指定した場合は該当箇所を出力しません。
324         * 3.PCTINCREASE は、出力しません。
325         *
326         * @og.rev 5.3.9.0 (2011/09/01) プライマリキー対応2
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    1行分のデータ配列
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    1行分のデータ配列
376         *
377         * @return      作成された1行分の文字列
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}