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.fukurou.process;
017
018import java.sql.Connection;
019import java.sql.Statement;
020import java.sql.ResultSet;
021import java.sql.SQLException;
022import java.util.Set ;
023import java.util.HashSet ;
024import java.util.Map ;
025import java.util.LinkedHashMap ;
026
027import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
028import org.opengion.fukurou.system.LogWriter;
029import org.opengion.fukurou.util.Argument;
030import org.opengion.fukurou.util.SystemParameter;
031import org.opengion.fukurou.util.HybsEntry ;
032import org.opengion.fukurou.db.ConnectionFactory;
033
034/**
035 * Process_BulkQueryは、データベースから読み取った内容を、一括処理する、
036 * FirstProcess と、ChainProcess のインターフェースを両方持った、実装クラスです。
037 * ParamProcess のサブクラス(Process_DBParam)にセットしたり、加工したりします。
038 *
039 * このクラスは、上流から、下流への処理は、1度しか実行されません。
040 * FirstProcess の検索結果は、Set オブジェクトとして、Process_DBParam に渡します。
041 * ChainProcess は、その結果を取り出し、自分自身の処理結果と合せて加工します。
042 *
043 * FirstProcess では、-action は、query のみです。
044 *   query は、指定のSQL文を実行し、結果のSetをParamProcessに設定します。
045 * ChainProcess では、-action は、query、bulkSet、minus、intersect が指定できます。
046 *   query     は、上記と同じです。
047 *   minus     は、先のSetから、SQL文の実行結果を引き算し、結果Setを再設定します。
048 *   intersect は、先のSetから、SQL文の実行結果と重複する結果Setを再設定します。
049 *   bulkSet   は、先のSetを取り出し、SQL文に加味して処理します。
050 * 流れ的には、query で検索し、minusまたはintersect でSetオブジェクトを加工し、bulkSet で
051 * 利用します。例えば、ORACLEから、ユニークキーのSetを作成し、SQLServerのユニークキーを
052 * minusした結果を、ORACLEからDELETEすれば、不要なデータを削除するなどの処理が実行可能になります。
053 * また、単純に、query だけを、チェインすれば、単発のUPDATE文を実行することが可能です。
054 *
055 * データベース接続先等は、ParamProcess のサブクラス(Process_DBParam)に
056 * 設定された接続(Connection)を使用します。
057 * DBID は、Process_DBParam の -configFile で指定する DBConfig.xml ファイルを使用します。
058 *
059 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
060 * 引数文字列の 『=』 の前後には、スペースは挟めません。必ず、-key=value の様に
061 * 繋げてください。
062 *
063 * SQL文には、{@DATE.YMDH}等のシステム変数が使用できます。
064 *
065 * @og.formSample
066 *  Process_BulkQuery -action=query -dbid=DBGE -sql="select KEY from TABLE_X"
067 *
068 *     -action=処理方法(必須)      : 実行する処理方法を指定します
069 *                                       -action=query     単なるSQL文を実行します。
070 *                                       -action=bulkSet   実行したSQL文の結果を、Set<String> オブジェクトに設定します。
071 *                                       -action=minus     Set<String> オブジェクトと、ここでの実行結果の差分をとります。
072 *                                       -action=plus      Set<String> オブジェクトと、ここでの実行結果の加算をします。
073 *                                       -action=intersect Set<String> オブジェクトと、ここでの実行結果の積分をとります。
074 *   [ -dbid=DB接続ID             ] : -dbid=DBGE (例: Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定)
075 *   [ -sql=検索SQL文             ] : -sql="select * from GEA08"
076 *   [ -sqlFile=検索SQLファイル   ] : -sqlFile=select.sql
077 *                                       -sql= を指定しない場合は、ファイルで必ず指定してください。
078 *   [ -sql_XXXX=固定値           ] : -sql_SYSTEM_ID=GE
079 *                                       SQL文中の{@XXXX}文字列を指定の固定値で置き換えます。
080 *                                       WHERE SYSTEM_ID='{@SYSTEM_ID}' ⇒ WHERE SYSTEM_ID='GE'
081 *   [ -bulkKey=XXXX              ] : -bulkKey=XXXX
082 *                                       SQL文中の{@XXXX}文字列をProcess_BulkQuery等で取得した値で置き換えます。
083 *                                       WHERE SYSTEM_ID IN ( {@XXXX} ) ⇒ WHERE SYSTEM_ID IN ( 'AA','BB','CC' )
084 *   [ -bulkType=NUM|STR          ] : -bulkType=STR
085 *                                     Bulkの値を文字列に変換する場合に、数字型か、文字型を指定します。
086 *                                     数字型では、AA,BB,CC とし、文字型では、'AA','BB','CC' に変換します(初期値:STR)。
087 *   [ -fetchSize=1000            ] :フェッチする行数(初期値:1000)
088 *   [ -display=[false/true]      ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
089 *   [ -debug=[false/true]        ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
090 *
091 * @og.rev 5.3.4.0 (2011/04/01) 新規追加
092 * @version  4.0
093 * @author   Kazuhiko Hasegawa
094 * @since    JDK5.0,
095 */
096public class Process_BulkQuery extends AbstractProcess implements FirstProcess , ChainProcess {
097        private static final int    MAX_BULK_SET        = 500 ;         // ORACLE の制約が 1000 なので。
098
099        private static final String ACT_QUERY           = "query" ;
100        private static final String ACT_BULKSET         = "bulkSet" ;
101        private static final String ACT_MINUS           = "minus" ;
102        private static final String ACT_PLUS            = "plus" ;                      // 6.3.1.1 (2015/07/10) 追加
103        private static final String ACT_INTERSECT       = "intersect" ;
104
105        private static final String[] ACTION_LST = { ACT_QUERY,ACT_BULKSET,ACT_MINUS,ACT_PLUS,ACT_INTERSECT };
106
107//      /** 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ  {@value} */
108//      private static final int DB_FETCH_SIZE = 1001 ;
109
110        private String          actionCmd       ;                                       // SQL結果を加工(query:実行、minus:引き算、intersect:重複分)
111        private String          dbid            ;                                       // メインDB接続ID
112
113//      private String          bulkKey         ;                                       // 8.5.4.2 (2024/01/12) PMD 7.0.0 SingularField
114        private boolean         bulkType        = true;                         // true:STR , false:NUM
115
116        private int                     sqlCount        ;                                       // SQL文の処理件数
117        private int                     setCount        ;                                       // 取り出したSetの件数
118        private int                     outCount        ;                                       // マージ後のSetの件数
119
120        private int                     fetchSize       = 1000;
121        private boolean         display         ;                                       // 表示しない
122        private boolean         debug           ;                                       // デバッグ情報
123        private boolean         firstTime       = true;                         // 最初の一回目
124
125        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
126        private static final Map<String,String> MUST_PROPARTY   ;               // [プロパティ]必須チェック用 Map
127        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
128        private static final Map<String,String> USABLE_PROPARTY ;               // [プロパティ]整合性チェック Map
129
130        static {
131                MUST_PROPARTY = new LinkedHashMap<>();
132                MUST_PROPARTY.put( "action",    "実行する処理方法を指定します。(query|minus|plus|intersect)" );
133
134                USABLE_PROPARTY = new LinkedHashMap<>();
135                USABLE_PROPARTY.put( "dbid",            "Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定" );
136                USABLE_PROPARTY.put( "sql",             "検索SQL文(sql or sqlFile 必須)例: \"select * from GEA08\"" );
137                USABLE_PROPARTY.put( "sqlFile", "検索SQLファイル(sql or sqlFile 必須)例: select.sql" );
138                USABLE_PROPARTY.put( "sql_",            "SQL文中の{&#064;XXXX}文字列を指定の固定値で置き換えます。" +
139                                                                        CR + "WHERE SYSTEM_ID='{&#064;SYSTEM_ID}' ⇒ WHERE SYSTEM_ID='GE'" );
140                USABLE_PROPARTY.put( "dbid2",   "DB接続ID2 例: Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定" );
141                USABLE_PROPARTY.put( "sql2",            "検索SQL文2(sql or sqlFile 必須)例: \"select * from GEA08\"" );
142                USABLE_PROPARTY.put( "sqlFile2",        "検索SQLファイル2(sql or sqlFile 必須)例: select.sql" );
143                USABLE_PROPARTY.put( "sql2_",   "SQL文2中の{&#064;XXXX}文字列を指定の固定値で置き換えます。" +
144                                                                        CR + "WHERE SYSTEM_ID='{&#064;SYSTEM_ID}' ⇒ WHERE SYSTEM_ID='GE'" );
145                USABLE_PROPARTY.put( "bulkKey",         "SQL文中の{&#064;XXXX}文字列をProcess_BulkQuery等で取得した値で置き換えます。" +
146                                                                        CR + "WHERE SYSTEM_ID IN ( {&#064;XXXX} ) ⇒ WHERE SYSTEM_ID IN ( 'AA','BB','CC' )" );
147                USABLE_PROPARTY.put( "bulkType",                "Bulkの値を文字列に変換する場合に、文字型か、数字型を指定します。" +
148                                                                        CR + "数字型では、AA,BB,CC とし、文字型では、'AA','BB','CC' に変換します。(初期値:STR)" );
149                USABLE_PROPARTY.put( "fetchSize","フェッチする行数 (初期値:1000)" );
150                USABLE_PROPARTY.put( "display", "結果を標準出力に表示する(true)かしない(false)か" +
151                                                                                CR + "(初期値:false:表示しない)" );
152                USABLE_PROPARTY.put( "debug",   "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
153                                                                                CR + "(初期値:false:表示しない)" );
154        }
155
156        /**
157         * デフォルトコンストラクター。
158         * このクラスは、動的作成されます。デフォルトコンストラクターで、
159         * super クラスに対して、必要な初期化を行っておきます。
160         *
161         */
162        public Process_BulkQuery() {
163                super( "org.opengion.fukurou.process.Process_BulkQuery",MUST_PROPARTY,USABLE_PROPARTY );
164        }
165
166        /**
167         * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
168         * 初期処理(ファイルオープン、DBオープン等)に使用します。
169         *
170         * @og.rev 5.3.9.0 (2011/09/01) 1000件を超えた場合の処理を追加
171         * @og.rev 6.3.1.1 (2015/07/10) plus アクションの追加
172         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 SingularField 対応
173         *
174         * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
175         */
176        public void init( final ParamProcess paramProcess ) {
177                final Argument arg = getArgument();
178
179                actionCmd       = arg.getProparty( "action"             , null , ACTION_LST );
180
181                fetchSize       = arg.getProparty( "fetchSize"  , fetchSize );
182                display         = arg.getProparty( "display"    , display );
183                debug           = arg.getProparty( "debug"              , debug );
184
185                dbid            = arg.getProparty( "dbid");
186                String sql      = arg.getFileProparty( "sql"    , "sqlFile",true );
187                if( debug ) { println( "入力SQL:" + sql ); }
188
189                final HybsEntry[] entry =arg.getEntrys( "sql_" );               //配列
190                final SystemParameter sysParam = new SystemParameter( sql );
191                sql = sysParam.replace( entry );
192                if( debug ) { println( "変換SQL:" + sql ); }
193
194                if( ACT_BULKSET.equalsIgnoreCase( actionCmd ) ) {
195//                      bulkKey         = arg.getProparty( "bulkKey" );
196                        final String bulkKey = arg.getProparty( "bulkKey" );            // 8.5.4.2 (2024/01/12) PMD 7.0.0 SingularField
197                        final String bkType  = arg.getProparty( "bulkType" );
198                        if( bkType != null ) { bulkType = "STR".equalsIgnoreCase( bkType ); }   // 初期値が true なので、null チャックは外せません。
199
200                        final Set<String> setData = paramProcess.getBulkData();
201                        if( debug ) { println( setData.toString() ); }
202                        setCount = setData.size();
203
204                        if( setCount > 0 ) {
205                                // 5.3.9.0 (2011/09/01) 1000件を超えた場合の処理を追加
206                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
207//                              final String[] sqls = makeBulkQuery( sql,bulkKey,bulkType,setData );
208//                              for( int i=0; i<sqls.length; i++ ) {
209//                                      if( debug ) { println( "BulkSQL:" + sqls[i] ); }
210//                                      createSetData( paramProcess, dbid, sqls[i] );
211//                              }
212                                for( final String blksql : makeBulkQuery( sql,bulkKey,bulkType,setData ) ) {
213                                        if( debug ) { println( "BulkSQL:" + blksql ); }
214                                        createSetData( paramProcess, dbid, blksql );
215                                }
216                        }
217                }
218                else if( ACT_QUERY.equalsIgnoreCase( actionCmd ) ) {
219                        final Set<String> setData2 = createSetData( paramProcess, dbid, sql );
220                        if( debug ) { println( setData2.toString() ); }
221                        setCount = setData2.size();
222                        outCount = setCount;
223                        paramProcess.setBulkData( setData2 );
224                }
225                else {
226                        final Set<String> setData  = paramProcess.getBulkData();
227                        final Set<String> setData2 = createSetData( paramProcess, dbid, sql );
228                        setCount = setData2.size();
229
230                        if( ACT_MINUS.equalsIgnoreCase( actionCmd ) ) {
231                                setData.removeAll( setData2 );
232                        }
233                        else if( ACT_PLUS.equalsIgnoreCase( actionCmd ) ) {                     // 6.3.1.1 (2015/07/10)
234                                setData.addAll( setData2 );
235                        }
236                        else if( ACT_INTERSECT.equalsIgnoreCase( actionCmd ) ) {
237                                setData.retainAll( setData2 );
238                        }
239                        outCount = setData.size();
240                        if( debug ) { println( setData.toString() ); }
241                        paramProcess.setBulkData( setData );
242                }
243        }
244
245        /**
246         * プロセスの終了を行います。最後に一度だけ、呼び出されます。
247         * 終了処理(ファイルクローズ、DBクローズ等)に使用します。
248         *
249         * @param   isOK トータルで、OKだったかどうか [true:成功/false:失敗]
250         */
251        public void end( final boolean isOK ) {
252                // 何もありません。
253        }
254
255        /**
256         * このデータの処理において、次の処理が出来るかどうかを問い合わせます。
257         * この呼び出し1回毎に、次のデータを取得する準備を行います。
258         *
259         * @return      処理できる:true / 処理できない:false
260         */
261        @Override       // FirstProcess
262        public boolean next() {
263                return firstTime;
264        }
265
266        /**
267         * 引数の LineModel を処理するメソッドです。
268         * 変換処理後の LineModel を返します。
269         * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
270         * null データを返します。つまり、null データは、後続処理を行わない
271         * フラグの代わりにも使用しています。
272         * なお、変換処理後の LineModel と、オリジナルの LineModel が、
273         * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
274         * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
275         * 各処理ごとに自分でコピー(クローン)して下さい。
276         *
277         * @param       data    オリジナルのLineModel
278         *
279         * @return      処理変換後のLineModel
280         */
281//      @SuppressWarnings(value={"unchecked"})
282        @Override       // ChainProcess
283        public LineModel action( final LineModel data ) {
284                return data ;
285        }
286
287        /**
288         * 最初に、行データである LineModel を作成します
289         * FirstProcess は、次々と処理をチェインしていく最初の行データを
290         * 作成して、後続の ChainProcess クラスに処理データを渡します。
291         *
292         * @param       rowNo   処理中の行番号
293         *
294         * @return      処理変換後のLineModel
295         */
296        @Override       // FirstProcess
297        public LineModel makeLineModel( final int rowNo ) {
298                firstTime = false;              // 一度しか処理しないため、false を設定する。
299
300                final LineModel model = new LineModel();
301
302                model.setRowNo( rowNo );
303
304                return model;
305        }
306
307        /**
308         * 内部で使用する Set オブジェクトを作成します。
309         * Exception 以外では、必ず Set&lt;String&gt; オブジェクトを返します。
310         *
311         * @og.rev 5.3.9.0 (2011/09/01) 1000件を超えた場合の処理を追加
312         * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。
313         *
314         * @param   paramProcess        データベースの接続先情報などを持っているオブジェクト
315         * @param   dbid                        接続先ID
316         * @param   sql                         実行するSQL文(検索系)
317         *
318         * @return      実行結果から取り出した、最初のカラムのみを集めた Setオブジェクト
319         * @throws RuntimeException データベース処理ができなかった場合。
320         */
321        private Set<String> createSetData( final ParamProcess paramProcess, final String dbid, final String sql ) {
322                final Set<String> data = new HashSet<>();
323
324                Connection connection   = null;
325
326                try {
327                        connection = paramProcess.getConnection( dbid );
328                        // 6.4.2.1 (2016/02/05) try-with-resources 文
329                        try( Statement stmt = connection.createStatement() ) {
330                                if( fetchSize > 0 ) { stmt.setFetchSize( fetchSize ); }
331                                if( stmt.execute( sql ) ) {                     // true:検索系 , false:更新系
332                                        try( ResultSet resultSet = stmt.getResultSet() ) {
333                                                while( resultSet.next() ) {
334                                                        sqlCount++ ;
335                                                        final String str = resultSet.getString(1);
336                                                        if( display ) { println( str ); }
337                                                        data.add( str );
338                                                }
339                                        }
340                                }
341                                else {
342                                        sqlCount += stmt.getUpdateCount();
343                                }
344                        }
345                }
346                catch( final SQLException ex) {
347                        final String errMsg = "SQL を実行できませんでした。" + CR
348                                                + "errMsg=[" + ex.getMessage() + "]" + CR
349                                                + "errorCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" + CR
350                                                + "DBID=" + dbid + CR
351                                                + "SQL =" + sql ;
352
353                        throw new OgRuntimeException( errMsg,ex );
354                }
355                finally {
356
357                        ConnectionFactory.remove( connection,dbid );
358                }
359                return data;
360        }
361
362        /**
363         * 内部で使用する Set オブジェクトを作成します。
364         * Exception 以外では、必ず Set&lt;String[]&gt; オブジェクトを返します。
365         *
366         * @og.rev 5.3.9.0 (2011/09/01) 1000件を超えた場合の処理を追加
367         *
368         * @param       sql                     オリジナルのSQL文
369         * @param       bulkKey         一括処理で置き換えるキー文字列
370         * @param       bulkType        文字型(true)か、数字型(false)を指定
371         * @param   setData             一括処理の元となるSetオブジェクト
372         *
373         * @return      オリジナルのSQL文 に 一括処理の文字列と置換したSQL文の配列
374         */
375        private String[] makeBulkQuery( final String sql, final String bulkKey, final boolean bulkType,final Set<String> setData ) {
376                final String[] sqls = new String[ setData.size()/MAX_BULK_SET + 1 ];            // 8.5.4.2 (2024/01/12) PMD 7.0.0 LocalVariableCouldBeFinal
377                int idx = 0;
378                int cnt = 0;
379
380                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
381                String bulkVal = null;
382                if( bulkType ) {                        // 文字列の場合
383                        for( final String key : setData ) {
384                                cnt++;
385                                buf.append( ",'" ).append( key ).append( '\'' );
386                                if( cnt >= MAX_BULK_SET ) {
387                                        bulkVal = buf.substring( 1 );           // 先頭のコロンをはずす
388                                        sqls[idx++] = sql.replace( "{@" + bulkKey + "}" ,bulkVal );
389                                        cnt = 0;
390                                        buf.setLength(0);                                       // 6.1.0.0 (2014/12/26) refactoring
391                                }
392                        }
393                        if( cnt > 0 ) {                 // きっちりで終わらない場合
394                                bulkVal = buf.substring( 1 );   // 先頭のコロンをはずす
395                                sqls[idx] = sql.replace( "{@" + bulkKey + "}" ,bulkVal );
396                        }
397                }
398                else {                                          // 数字の場合
399                        for( final String key : setData ) {
400                                cnt++;
401                                buf.append( ',' ).append( key );                // 6.0.2.5 (2014/10/31) char を append する。
402                                if( cnt >= MAX_BULK_SET ) {
403                                        bulkVal = buf.substring( 1 );           // 先頭のコロンをはずす
404                                        sqls[idx++] = sql.replace( "{@" + bulkKey + "}" ,bulkVal );
405                                        cnt = 0;
406                                        buf.setLength(0);                                       // 6.1.0.0 (2014/12/26) refactoring
407                                }
408                        }
409                        if( cnt > 0 ) {                 // きっちりで終わらない場合
410                                bulkVal = buf.substring( 1 );                   // 先頭のコロンをはずす
411                                sqls[idx] = sql.replace( "{@" + bulkKey + "}" ,bulkVal );
412                        }
413                }
414
415                return sqls;
416        }
417
418        /**
419         * プロセスの処理結果のレポート表現を返します。
420         * 処理プログラム名、入力件数、出力件数などの情報です。
421         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
422         * 形式で出してください。
423         *
424         * @return   処理結果のレポート
425         */
426        public String report() {
427                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
428                return "[" + getClass().getName() + "]" + CR
429//              final String report = "[" + getClass().getName() + "]" + CR
430                                + TAB + "Action    : " + actionCmd      + CR
431                                + TAB + "DBID      : " + dbid           + CR
432                                + TAB + "sqlCount  : " + sqlCount       + CR
433                                + TAB + "setCount  : " + setCount       + CR
434                                + TAB + "outCount  : " + outCount ;
435
436//              return report ;
437        }
438
439        /**
440         * このクラスの使用方法を返します。
441         *
442         * @return      このクラスの使用方法
443         * @og.rtnNotNull
444         */
445        public String usage() {
446                final StringBuilder buf = new StringBuilder( 1200 )
447                        .append( "Process_BulkQueryは、データベースから読み取った内容を、一括処理するために、"               ).append( CR )
448                        .append( "ParamProcess のサブクラス(Process_DBParam)にセットしたり、加工したりする"                  ).append( CR )
449                        .append( "FirstProcess と、ChainProcess のインターフェースを両方持った、実装クラスです。" ).append( CR )
450                        .append( CR )
451                        .append( "このクラスは、上流から、下流への処理は、1度しか実行されません。"                                     ).append( CR )
452                        .append( "FirstProcess の検索結果は、Set オブジェクトとして、Process_DBParam に渡します。"     ).append( CR )
453                        .append( "ChainProcess は、その結果を取り出し、自分自身の処理結果と合せて加工します。"         ).append( CR )
454                        .append( CR )
455                        .append( "FirstProcess では、-action は、query のみです。"                                                                        ).append( CR )
456                        .append( "  query は、指定のSQL文を実行し、結果のSetをParamProcessに設定します。"                     ).append( CR )
457                        .append( "ChainProcess では、-action は、query、bulkSet、minus、intersect が指定できます。"     ).append( CR )
458                        .append( "  query     は、上記と同じです。"                                                                                                       ).append( CR )
459                        .append( "  minus     は、先のSetから、SQL文の実行結果を引き算し、結果Setを再設定します。"   ).append( CR )
460                        .append( "  intersect は、先のSetから、SQL文の実行結果と重複する結果Setを再設定します。"    ).append( CR )
461                        .append( "  bulkSet   は、先のSetを取り出し、SQL文に加味して処理します。"                                     ).append( CR )
462                        .append( CR )
463                        .append( "流れ的には、query で検索し、minusまたはintersect でSetオブジェクトを加工し、"           ).append( CR )
464                        .append( "bulkSet で利用します。例えば、ORACLEから、ユニークキーのSetを作成し、"                  ).append( CR )
465                        .append( "SQLServerのユニークキーをminusした結果を、ORACLEからDELETEすれば、不要な"                    ).append( CR )
466                        .append( "データを削除するなどの処理が実行可能になります。また、単純に、query だけを、"            ).append( CR )
467                        .append( "チェインすれば、単発のUPDATE文を実行することが可能です。"                                                      ).append( CR )
468                        .append( CR )
469//                      .append( "データベース接続先等は、ParamProcess のサブクラス(Process_DBParam)に"                    ).append( CR )
470//                      .append( "設定された接続(Connection)を使用します。"                                                                           ).append( CR )
471//                      .append( CR )
472//                      .append( "SQL文には、{@DATE.YMDH}等のシステム変数が使用できます。"                                          ).append( CR )
473                        .append( DB_PARAM_USAGE )               // 8.5.6.1 (2024/03/29) 継承元使用
474                        .append( CR )
475//                      .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"    ).append( CR )
476//                      .append( "引数文字列の 『=』 の前後には、空白は挟めません。必ず、-key=value の様に"          ).append( CR )
477//                      .append( "繋げてください。"                                                                                                                             ).append( CR )
478                        .append( PROCESS_PARAM_USAGE )  // 8.5.6.1 (2024/03/29) 継承元使用 ※ 場所の移動
479                        .append( CR ).append( CR )
480                        .append( getArgument().usage() ).append( CR );
481
482                return buf.toString();
483        }
484
485        /**
486         * このクラスは、main メソッドから実行できません。
487         *
488         * @param       args    コマンド引数配列
489         */
490        public static void main( final String[] args ) {
491                LogWriter.log( new Process_BulkQuery().usage() );
492        }
493}