/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.hayabusa.taglib;

// import java.util.Locale;
import java.util.Set;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.ParameterMetaData;

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.resource.GUIInfo;
import org.opengion.fukurou.system.Closer ;
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.ArraySet;
import org.opengion.fukurou.db.Transaction;
import org.opengion.fukurou.db.QueryMaker;
import org.opengion.fukurou.db.ResultSetValue;
import org.opengion.fukurou.db.ConnectionFactory;

import static org.opengion.fukurou.util.StringUtil.nval;
import static org.opengion.fukurou.system.HybsConst.DB_FETCH_SIZE;	// 6.9.4.1 (2018/04/09)

/**
 * データベースのデータコピー/移動/更新/削除を行うタグです。
 *
 * <pre>
 * 検索結果のﾃﾞｰﾀを、action に応じた方法で、処理します。
 * SELECT文は、BODY部に記述することも可能です。
 * BODY にSELECT文を記述しない場合は、names と、table から、SELECT文を作成します。
 * names2 は、INSERTやUPDATE の カラム名で、SELECT文の先頭から順に適用します。
 * WHERE条件は、SELECT結果を利用できますが、必ず、names2 のカラムか、そうでないならば、
 * それ以降に記述してください。
 *
 * このタグは、DBTableModel を経由せず、直接、接続元から接続先へデータ処理を行います。
 * 接続元の１レコード単位に、接続先に対して、処理を実行します。
 * よって、大量データ処理が可能ですが、まとめ処理を行っていない分、時間が掛かります。
 *
 * 用途としては、WORKﾃｰﾌﾞﾙへのﾃﾞｰﾀコピーや、BKUPﾃｰﾌﾞﾙへのコピーが考えられ
 * ますが、それらは、select insert などの直接的な処理のほうが良いです。
 * ここでは、別ユーザーや、別インスタンス、または、別ﾃﾞｰﾀﾍﾞｰｽ(ORACLEから、MySQLへ)など、
 * dbid違いのﾃｰﾌﾞﾙへのﾃﾞｰﾀ処理用途を、想定しています。
 * なので、複雑な処理や、PL/SQL等のﾃﾞｰﾀﾍﾞｰｽ独自処理は行えません。
 * SELECT文は、直接記述できるため、ﾃﾞｰﾀﾍﾞｰｽ固有の関数や、構文を記載可能ですが、
 * INSERT,UPDATE,DELETE 文は、基本的に共通構文であり、WHERE条件等も、一般的は範囲に
 * とどめてください。
 *
 * SELECTカラムとINSERTカラムが異なる場合は、name 指定と、name2 指定のｶﾗﾑが対応します。
 * 追加、更新先のカラム名に変更して置いてください。
 * BODY部にSELECT文を記述した場合は、ｶﾗﾑ順が、name 順となり、name2 と対応されます。
 * constKeys,constVals も、更新先のカラム名で指定します。
 * 処理の途中でエラー(例えば、ﾕﾆｰｸｷｰ制約等)になった場合は、stopError属性の
 * 値に応じて処理を継続するかどうかを決定します。
 * stopError="true" が初期値なので、エラー時点で、処理を中断します。
 *
 * action="INSERT"
 *    SELECT結果を、table2 に、INSERT します。where2,whereNames2 は使用しません。
 *    name2 を使用しない場合は、name と同じｶﾗﾑ配列で、INSERT します。
 *    stopError="false"(エラー時も継続する) とした場合、SELECT結果は、最後まで
 *    INSERTを試みます。
 *
 * action="UPDATE"
 *    SELECT結果を、table2 に、where2,whereNames2 に基づいて UPDATE します。
 *    SELECTには、更新で使用する where条件となるカラムを含める必要があります。
 *    更新するｶﾗﾑは、name2 で指定することになります。
 *    更新対象が存在しなかった場合は、エラーとは判定していません。
 *
 * action="DELETE"
 *    SELECT結果を、table2 に、where2,whereNames2 に基づいて table2 のﾃﾞｰﾀを 削除 します。
 *    SELECTには、削除で使用する where条件となるカラムを含める必要があります。
 *    削除対象が存在しなかった場合は、エラーとは判定していません。
 *
 * action="MERGE"
 *    SELECT結果を、table2 に、where2,whereNames2 に基づいて UPDATE/INSERT します。
 *    SELECTには、更新で使用する where条件となるカラムを含める必要があります。
 *    更新するｶﾗﾑは、name2 で指定することになります。
 *    更新対象が存在しなかった場合は、INSERT になります。
 *    (つまり、更新を一度試みて、更新件数が、０件の場合に、INSERTします。)
 *    INSERTするｶﾗﾑは、SELECTしたすべてのｶﾗﾑが対象になります。
 *
 * useDelete="true" を指定すると、検索元のﾃﾞｰﾀを削除します。
 * INSERT 時に指定すれば、MOVE と同じ効果になります。
 * stopError="false" (エラー時でも処理を継続する)にした場合、検索元のﾃﾞｰﾀ削除は、
 * エラー行については、実行されません。ただし、UPDATE,DELETE 等で、対象データが
 * 存在しない場合は、エラーと判断しないため、検索元のﾃﾞｰﾀを削除します。
 *
 * SystemData の USE_SQL_INJECTION_CHECK が true か、quotCheck 属性が true の場合は、
 * ＳＱＬインジェクション対策用のシングルクォートチェックを行います。リクエスト引数に
 * シングルクォート(')が含まれると、エラーになります。
 *
 * DBLastSql はセットされません。
 * つまり、このタグでSELECTされたデータを、ファイル出力することはできません。
 *
 * 実行後にリクエストパラメータに以下の値がセットされます。
 *   DB.COUNT     : 検索結果の件数
 *   DB.UPCOUNT   : 追加/更新/削除結果の件数
 *   DB.ERR_CODE  : 検索結果のｴﾗｰｺｰﾄﾞ(複数合った場合は、最後のｴﾗｰｺｰﾄﾞ)
 *
 * ※ このタグは、Transaction タグの対象です。
 *
 * @og.formSample
 * ●形式：
 *       ・&lt;og:dbCopy action="INSERT" table="TEST_A" table2="TEST_B" /&gt;
 *         TEST_A のすべてカラム、データを、TEST_B にコピーします。
 *
 *       ・&lt;og:dbCopy action="UPDATE" names2="A2,B2" table2="TEST_B" where2="C2=[c1]" &gt;
 *              select a1,b1,c1 from TEST_A where d1='XXX' order by a1
 *         &lt;/og:dbCopy&gt;
 *         TEST_A のa1→A2 , b1→B2 ｶﾗﾑに、WHERE条件 TEST_B.C2 が、TEST_A.c1 に一致するﾃﾞｰﾀのみ 更新します。
 *
 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
 *
 * ●Tag定義：
 *   &lt;og:dbCopy
 *       action             【TAG】実行方法[INSERT/UPDATE/DELETE/MERGE]を指定します(初期値:INSERT)。
 *       useDelete          【TAG】(jdbcオプション)検索した元のﾃﾞｰﾀを削除するかどうか[true:削除する/false:なにもしない]を指定します(初期値:false)。
 *       maxRowCount        【TAG】(通常は使いません)データの最大読み込み件数を指定します (初期値:0:[無制限])
 *       stopZero           【TAG】検索結果が０件のとき処理を続行するかどうか[true/false]を指定します(初期値:false[続行する])
 *       dbid               【TAG】検索する対象のDB接続IDを指定します(初期値:null)
 *       table              【TAG】検索する対象のテーブル名を指定します
 *       names              【TAG】検索する対象のカラム名をCSV形式で複数指定します(初期値:*)
 *       where              【TAG】検索する対象を特定するキー条件(where句)を指定します
 *       orderBy            【TAG】検索する対象の検索順(order by句)を指定します
 *       dbid2              【TAG】登録する対象のDB接続IDを指定します(初期値:null)
 *       table2             【TAG】登録する対象のテーブル名を指定します
 *       names2             【TAG】登録する対象のカラム名をCSV形式で複数指定します
 *       omitNames2         【TAG】登録する対象外のカラム名をCSV形式で複数指定します
 *       where2             【TAG】登録する対象を特定するキー条件(where句)を指定します
 *       whereNames2        【TAG】登録する対象を特定するキー条件(where句)をCSV形式で複数指定します
 *       constKeys2         【TAG】設定値を固定値と置き換える対象となるカラム名をCSV形式で複数指定します
 *       constVals2         【TAG】設定値を固定値と置き換える対象となる設定値をCSV形式で複数指定します
 *       quotCheck          【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します (初期値:USE_SQL_INJECTION_CHECK[=true])
 *       stopError          【TAG】登録処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)
 *       dispError          【TAG】エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)
 *       fetchSize          【TAG】(通常は使いません)データのフェッチサイズを指定します(初期値:DB_FETCH_SIZE[={@og.value org.opengion.fukurou.system.HybsConst#DB_FETCH_SIZE}])
 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null)
 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null)
 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない)
 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない)
 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   &gt;   ... Body ...
 *   &lt;/og:dbCopy&gt;
 *
 * ●使用例
 *       ・&lt;og:dbCopy action="INSERT" names2="A2,B2,C2" table2="TEST_B" &gt;
 *              select a1,b1,c1 from TEST_A where d1='XXX' order by a1
 *         &lt;/og:dbCopy&gt;
 *         TEST_A のa1→A2 , b1→B2 , c1→C2 ｶﾗﾑに、追加します。
 *
 *       ・&lt;og:dbCopy action="INSERT" names="a1,b1,c1" table="TEST_A" names2="A2,B2,C2" table2="TEST_B" /&gt;
 *         TEST_A のa1→A2 , b1→B2 , c1→C2 ｶﾗﾑに、追加します。 (先の例と同じ処理)
 *
 *       ・&lt;og:dbCopy action="INSERT" table="TEST_A" where="d1='1'" dbid="LOCAL" dbid2="OTHER" &gt;
 *         接続先：LOCAL の TEST_A の 全カラムのd1='1' のレコードを、接続先：OTHER のTEST_A に追加します。
 *         接続先違い（ユーザー、やﾃﾞｰﾀﾍﾞｰｽ違い）へのINSERTです。
 *         table2 を指定しない場合は、table と同じとみなされます。
 *
 *       ・&lt;og:dbCopy action="INSERT" table="TEST_A" where="d1='1'" dbid="LOCAL" dbid2="OTHER" stopError="false" useDelete="true" &gt;
 *         接続先：LOCAL の TEST_A の 全カラムのd1='1' のレコードを、接続先：OTHER のTEST_A に移動します。
 *         接続先違い（ユーザー、やﾃﾞｰﾀﾍﾞｰｽ違い）への移動です。
 *         先のINSERT が成功したレコードは削除され、最後まで処理が行われます。
 *         INSERTが失敗（つまり、接続先：OTHER にすでに、ユニークレコードが存在する場合など）時の、検索元のレコードは
 *         削除されません。
 *
 *       ・&lt;og:dbCopy action="MERGE" table="TEST_A" where="d1='1'" dbid="LOCAL" names2="a1,b1,c1" dbid2="OTHER" where="ukey=[ukey]" stopError="false" useDelete="true" &gt;
 *         接続先：LOCAL の TEST_A の 全カラムのd1='1' のレコードを、接続先：OTHER のTEST_A に移動します。
 *         接続先：OTHER に、移動先.ukey=[移動元ukey] のﾃﾞｰﾀがあれば、name2="a1,b1,c1" カラムだけ、UPDATE を行い、
 *         更新件数が、０件の場合は、検索したすべてのカラムで、INSERT を行います。
 * </pre>
 *
 * @og.group ＤＢ検索
 * @og.group ＤＢ登録
 *
 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
 *
 * @version  6.8.6.0 (2018/01/19)
 * @author	 Kazuhiko Hasegawa
 * @since    JDK8.0,
 */
public class DBCopyTag extends CommonTagSupport {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "7.0.6.1 (2019/10/11)" ;
	private static final long serialVersionUID = 706120191011L ;

	/** action 引数に渡す事の出来る アクションコマンド  追加する {@value} */
	public static final String ACT_INSERT	= "INSERT" ;
	/** action 引数に渡す事の出来る アクションコマンド  更新する {@value} */
	public static final String ACT_UPDATE	= "UPDATE" ;
	/** action 引数に渡す事の出来る アクションコマンド  削除する {@value} */
	public static final String ACT_DELETE	= "DELETE" ;
	/** action 引数に渡す事の出来る アクションコマンド  マージする {@value} */
	public static final String ACT_MERGE	= "MERGE" ;

//	/** 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ  {@value} */
//	private static final int DB_FETCH_SIZE		= HybsSystem.sysInt( "DB_FETCH_SIZE" ) ;

//	/** fetchSize の初期値 {@value} */
//	public static final int FETCH_SIZE	= 1000 ;		// 6.9.3.0 (2018/03/26) 初期値を100→1000 に変更

	private static final Set<String> ACTION_SET = new ArraySet<>( ACT_INSERT , ACT_UPDATE , ACT_DELETE , ACT_MERGE );

	/** エラーメッセージID {@value} */
	private static final String ERR_MSG_ID	 = HybsSystem.ERR_MSG_KEY;

	// 6.9.8.0 (2018/05/28) FindBugs:直列化可能クラスの非 transient で非直列化可能なインスタンスフィールド
	private transient	QueryMaker	query	= new QueryMaker();		// 検索元のSELECTのSQL文
	private transient	QueryMaker	query2	= new QueryMaker();		// 登録先のSQL文

	private transient	ErrorMessage	errMessage	;

	private String		action		= ACT_INSERT;		// 実行方法[INSERT/UPDATE/DELETE/MERGE]を指定します(初期値:INSERT)。
	private boolean		useDelete	;					// (jdbcオプション)検索した元のﾃﾞｰﾀを削除するかどうか[true:削除する/false:なにもしない]を指定します(初期値:false)。
	private int			maxRowCount	;					// データの最大読み込み件数を指定します (初期値:0:[無制限])
//	private String		displayMsg	= HybsSystem.sys( "VIEW_DISPLAY_MSG" );		// 検索結果を画面上に表示するメッセージリソースIDを指定します (初期値:VIEW_DISPLAY_MSG[=])
//	private String		overflowMsg	= "MSG0007";		// 検索結果が、制限行数を超えましたので、残りはカットされました。
//	private String		notfoundMsg	= "MSG0077";		// 対象データはありませんでした。
	private boolean		stopZero	;					// 検索結果が０件のとき処理を続行するかどうか[true/false]を指定します(初期値:false[続行する])
	private String		dbid		;					// 検索する対象のDB接続IDを指定します(初期値:null)
	private String		dbid2		;					// 登録する対象のDB接続IDを指定します(初期値:null)
	private boolean		quotCheck	= HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );		// シングルクォート(') 存在チェックを実施するかどうか[true/false]
																							// (初期値:USE_SQL_INJECTION_CHECK[=true])
	private boolean		stopError	= true;				// 登録処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)
	private boolean		dispError	= true;				// エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)
	private int			fetchSize	= DB_FETCH_SIZE ;	// フェッチする行数(初期値を100→HybsConst.DB_FETCH_SIZE に変更)

//	private int			errCode		= ErrorMessage.OK;	// 処理結果のｴﾗｰｺｰﾄﾞ(複数合った場合は、最後のｴﾗｰｺｰﾄﾞ)
	private long		dyStart		;					// 実行時間測定用のDIV要素を出力します。

	private String		selSQL		;					// 検索元のSELECT文を一時保管する変数。
	private int			selCnt		;					// DB.COUNT   : 検索結果の件数
	private int			upCnt		;					// DB.UPCOUNT : 追加/更新/削除結果の件数

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 */
	public DBCopyTag() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doStartTag() {
		useXssCheck( false );					// XSS対策：チェックしません。

		if( useTag() && check( action, ACTION_SET ) ) {
			dyStart = System.currentTimeMillis();
	//		removeSessionAttribute( ERR_MSG_ID );	// 先に、エラーデータを削除しておきます。

			errMessage = new ErrorMessage( "DBCopyTag Database Error!" );
			setSessionAttribute( ERR_MSG_ID,errMessage );

			query.setQueryType( "SELECT" );			// 検索元のQUERYタイプ(SELECT固定)
			query2.setQueryType( action );			// 登録先のQUERYタイプ(actionと同じ)

			// 初期設定
			// table2 を指定しない場合は、table と同じテーブルが使用されます。
			if( StringUtil.isNull( query2.getTable() ) ) { query2.setTable( query.getTable() ); }

			// names2を、指定しない場合は、names または、SELECT文のすべてのカラムが、同一名として処理されます。
			if( StringUtil.isNull( query2.getNames() ) ) { query2.setNames( query.getNames() ); }

			return EVAL_BODY_BUFFERED ;			// Body を評価する。( extends BodyTagSupport 時)
		}
		return SKIP_BODY ;						// Body を評価しない
	}

	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @return	後続処理の指示(SKIP_BODY)
	 */
	@Override
	public int doAfterBody() {
		debugPrint();

		useQuotCheck( quotCheck );		// ＳＱＬインジェクション対策

		selSQL = getBodyString();

		return SKIP_BODY ;
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();

		if( useTag() && check( action, ACTION_SET ) ) {
			if( StringUtil.isNull( selSQL ) ) {
				selSQL = query.getSelectSQL();			// この段階で、SELECT文の整合性チェックが行われます。
			}

			execute();		// 実際の処理

			final int errCode = errMessage.getKekka();

			setRequestAttribute( "DB.COUNT" 	, String.valueOf( selCnt ) );		// DB.COUNT   : 検索結果の件数
			setRequestAttribute( "DB.UPCOUNT"	, String.valueOf( upCnt  ) );		// DB.UPCOUNT : 追加/更新/削除結果の件数
			setRequestAttribute( "DB.ERR_CODE"	, String.valueOf( errCode ) );		// 検索結果のｴﾗｰｺｰﾄﾞ(複数合った場合は、最後のｴﾗｰｺｰﾄﾞ)

			final int rtnCode ;
			if( errCode >= ErrorMessage.NG )  {	// 異常
				setSessionAttribute( ERR_MSG_ID,errMessage );

				final String err = TaglibUtil.makeHTMLErrorTable( errMessage,getResource() );
				// エラーメッセージをリクエスト変数で持つようにしておく
				setRequestAttribute( "DB.ERR_MSG", err );

				if( dispError ) { jspPrint( err ); }		// dispErrorで表示をコントロール

				rtnCode = stopError ? SKIP_PAGE : EVAL_PAGE ;
			}
			else {
				removeSessionAttribute( ERR_MSG_ID );	// 問題なければ、エラーデータを削除しておきます。
				// 件数０件かつ stopZero = true
				rtnCode = selCnt == 0 && stopZero ? SKIP_PAGE : EVAL_PAGE ;
			}
			return rtnCode ;
		}

		return EVAL_PAGE ;
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		errMessage	= null;
		selSQL		= null;				// 検索SELECT文
		action		= ACT_INSERT;		// 実行方法[INSERT/UPDATE/DELETE/MERGE]を指定します(初期値:INSERT)。
		useDelete	= false;			// (jdbcオプション)検索した元のﾃﾞｰﾀを削除するかどうか[true:削除する/false:なにもしない]を指定します(初期値:false)。
		maxRowCount	= 0;				// データの最大読み込み件数を指定します (初期値:0:[無制限])
//		displayMsg	= HybsSystem.sys( "VIEW_DISPLAY_MSG" );		// 検索結果を画面上に表示するメッセージリソースIDを指定します (初期値:VIEW_DISPLAY_MSG[=])
//		overflowMsg	= "MSG0007";		// 検索結果が、制限行数を超えましたので、残りはカットされました。
//		notfoundMsg	= "MSG0077";		// 対象データはありませんでした。
		stopZero	= false;			// 検索結果が０件のとき処理を続行するかどうか[true/false]を指定します(初期値:false[続行する])
		dbid		= null;				// 検索する対象のDB接続IDを指定します(初期値:null)
		query		= new QueryMaker();		// 検索元のSELECTのSQL文
		dbid2		= null;				// 登録する対象のDB接続IDを指定します(初期値:null)
		query2		= new QueryMaker();		// 登録先のSQL文
		quotCheck	= HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );
		stopError	= true;				// 登録処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)
		dispError	= true;				// エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)
		fetchSize	= DB_FETCH_SIZE ;	// フェッチする行数(初期値を100→HybsConst.DB_FETCH_SIZE に変更)
//		errCode		= ErrorMessage.OK;	// 処理結果のｴﾗｰｺｰﾄﾞ(複数合った場合は、最後のｴﾗｰｺｰﾄﾞ)
		dyStart		= 0L;				// 実行時間測定用のDIV要素を出力します。
		selCnt		= 0;				// DB.COUNT   : 検索結果の件数
		upCnt		= 0;				// DB.UPCOUNT : 追加/更新/削除結果の件数
	}

	/**
	 * Query を実行します。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 * @og.rev 6.9.0.2 (2018/02/13) カラム名が、"*" の場合の対応
	 * @og.rev 6.9.9.1 (2018/08/27) SELECT検索できなかった場合
	 * @og.rev 7.0.6.1 (2019/10/11) useParamMetaData ではなく、MetaDataが存在しているかどうかで判定します。
	 */
	private void execute() {
		Statement			stmt   = null;		// 検索に使用するｽﾃｰﾄﾒﾝﾄ
		ResultSetValue		rsv    = null;		// 検索に使用
		PreparedStatement	pstmt2 = null;		// INSERT/UPDATE/DELETE に使用するｽﾃｰﾄﾒﾝﾄ
		PreparedStatement	pstmt3 = null;		// MERGE時に使用する INSERT 用ｽﾃｰﾄﾒﾝﾄ
		ParameterMetaData	pMeta2 = null;
		ParameterMetaData	pMeta3 = null;

		final boolean useParamMetaData = ConnectionFactory.useParameterMetaData( dbid2 );

		String sql2 = null ;
		String sql3 = null ;
		final StringBuilder val2Buf = new StringBuilder( BUFFER_MIDDLE );		// 6.9.0.2 (2018/02/13) デバッグ情報用
		final StringBuilder val3Buf = new StringBuilder( BUFFER_MIDDLE );		// 6.9.0.2 (2018/02/13) デバッグ情報用

		// Transaction でAutoCloseableを使用したtry-with-resources構築に対応。
		try( Transaction tran = getTransaction() ) {
	//		errMessage = new ErrorMessage( "DBCopyTag Database Error!" );

			final Connection conn = tran.getConnection( dbid );
			stmt = conn.createStatement();
			if( fetchSize > 0 ) { stmt.setFetchSize( fetchSize ); }
			final ResultSet resSer = stmt.executeQuery( selSQL );				// useDelete で使うため
			rsv = new ResultSetValue( resSer );									// ResultSet を引数に、インスタンス作成

			if( rsv.getColumnCount() == 0 ) { return; }							// 6.9.9.1 (2018/08/27) SELECT検索できなかった場合

			// names2を、指定しない場合は、names または、SELECT文のすべてのカラムが、同一名として処理されます。
			// 6.9.0.2 (2018/02/13) カラム名が、"*" の場合の対応
//			if( StringUtil.isNull( query2.getNames() ) ) {						// nullチェックは、すでに終了している。
			if( "*".equals( query2.getNames() ) ) {
				final String names2 = String.join( "," , rsv.getNames() );		// SELECT文の名前配列から、CSV文字列を作成
				query2.setNames( names2 );
			}

			switch( action ) {
				case "INSERT" : sql2 = query2.getInsertSQL(); break;
				case "UPDATE" : sql2 = query2.getUpdateSQL(); break;
				case "DELETE" : sql2 = query2.getDeleteSQL(); break;
				case "MERGE"  : sql2 = query2.getUpdateSQL();
								sql3 = query2.getInsertSQL(); break;
				default : break;
			}

			if( StringUtil.isNull( sql2 ) ) {
				final String errMsg = "更新用QUERY が作成できませんでした。 "
										+ " action=[" + action + "]"
										+ " query2=[" + sql2 + "]" ;
				errMessage.addMessage( errMsg );
				throw new HybsSystemException( errMsg );
			}

			final String[]	prmNms2 = query2.getParamNames( false );		// 登録QUERYの、変数設定されているカラム名配列。
			final int[]		clmNos2 = rsv.getColumnNos( prmNms2,true );		// 変数設定カラムのカラム番号。無ければ、Exception

			int[] clmNos3 = null;	// MERGE のときの変数設定カラム

			final boolean useMerge = "MERGE".equals( action );

			final Connection conn2 = tran.getConnection( dbid2 );
			pstmt2 = conn2.prepareStatement( sql2 );
			pMeta2 = useParamMetaData ? pstmt2.getParameterMetaData() : null ;
			if( useMerge ) {
				final Connection conn3 = tran.getConnection( dbid2 );
				pstmt3 = conn3.prepareStatement( sql3 );
				pMeta3 = useParamMetaData ? pstmt3.getParameterMetaData() : null ;

				final String[]	prmNms3 = query2.getParamNames( true );		// MERGE のときの INSERT時なので、true を指定。
				clmNos3 = rsv.getColumnNos( prmNms3,true );				// 変数設定カラムのカラム番号。無ければ、Exception
			}

			while( rsv.next() ) {
				try {
					selCnt++;											// 検索件数の加算
					val2Buf.setLength(0);								// 初期化
					final String[] vals = rsv.getValues();
					for( int no=0; no<clmNos2.length; no++ ) {
						final int cno = clmNos2[no];					// 変数設定カラムに該当するｱﾄﾞﾚｽ。
						final String val = vals[cno];
				//		final String val = nval(vals[cno],"");			// 7.0.6.1 (2019/10/11) where条件の is null 対策(仮)
						val2Buf.append( val ).append( ',' );			// valueのCSV形式
//						if( useParamMetaData ) {
						if( pMeta2 != null ) {							// 7.0.6.1 (2019/10/11) ParameterMetaDataの有無で判定
							final int type = pMeta2.getParameterType( no+1 );
							if( val == null || val.isEmpty() ) {
								pstmt2.setNull( no+1, type );			// where条件がnull の場合、うまく検索できない
							}
							else {
								pstmt2.setObject( no+1,val,type );
							}
						}
						else {
							pstmt2.setObject( no+1,val );
						}
					}

					int cnt = pstmt2.executeUpdate();					// 更新件数
					if( useMerge && cnt == 0 ) {						// マージアクションで、更新が0件の場合は、INSERTを行う。
						val3Buf.setLength(0);							// 初期化
						for( int no=0; no<clmNos3.length; no++ ) {
							final int cno = clmNos3[no];				// 変数設定カラムに該当するｱﾄﾞﾚｽ。
							final String val = vals[cno];
				//			final String val = nval(vals[cno],"");		// 7.0.6.1 (2019/10/11) where条件の is null 対策(仮)
							val3Buf.append( val ).append( ',' );		// valueのCSV形式
//							if( useParamMetaData ) {
							if( pMeta3 != null ) {						// 7.0.6.1 (2019/10/11) ParameterMetaDataの有無で判定
								final int type = pMeta3.getParameterType( no+1 );
								if( val == null || val.isEmpty() ) {
									pstmt3.setNull( no+1, type );		// where条件がnull の場合、うまく検索できない
								}
								else {
									pstmt3.setObject( no+1,val,type );
								}
							}
							else {
								pstmt3.setObject( no+1,val );
							}
						}
						cnt = pstmt3.executeUpdate();					// 追加件数
					}

					upCnt += cnt;										// 更新件数の加算
					if( useDelete ) { resSer.deleteRow(); }				// 途中でエラーになった場合は、ResultSet の削除は行いません。
				}
				catch( final SQLException ex ) {
					errMessage.addMessage( selCnt,ErrorMessage.NG,ex.getSQLState(),ex.getMessage() );
					if( stopError ) {
						tran.rollback();								// stopError=false の場合、最後まで処理され、commit() されています。
						throw ex;										// SELECTループ中のSQLExceptionは、stopErrorの判定を行う。
					}
				}
			}

			tran.commit();
		}
		catch( final SQLException ex ) {
			// 6.9.0.2 (2018/02/13) デバッグ情報
			final String errMsg = new StringBuilder( BUFFER_MIDDLE )
								.append( "更新処理実行中にエラーが発生しました。action=[" )
								.append( action ).append( ']' ).append( CR )
								.append( " query =[" ).append( selSQL  ).append( ']' ).append( CR )
								.append( " query2=[" ).append( sql2    ).append( ']' ).append( CR )
								.append( " query3=[" ).append( sql3    ).append( ']' ).append( CR )
								.append( " value2=[" ).append( val2Buf ).append( ']' ).append( CR )
								.append( " value3=[" ).append( val3Buf ).append( ']' ).append( CR )
								.toString();

//			final String errMsg = "更新処理実行中にエラーが発生しました。action=[" + action + "]" + CR
//									+ " query=[" + selSQL + "]"  + CR
//									+ " query2=[" + sql2 + "]";

			errMessage.addMessage( ex );
	//		errMessage.addMessage( errMsg );
			errMessage.addMessage( -1,ErrorMessage.NG,ex.getSQLState(),errMsg );
			throw new HybsSystemException( errMsg,ex );
		}
		finally {
//			rsv.close();
			Closer.autoClose( rsv  );			// 6.9.8.0 (2018/05/28) FindBugs: null 値を例外経路で利用している可能性がある
			Closer.stmtClose( stmt  );
			Closer.stmtClose( pstmt2 );
			Closer.stmtClose( pstmt3 );
		}

		// 変数の関係で、こちらにもって来ました(データアクセス件数登録)
		final long dyTime = System.currentTimeMillis()-dyStart;
		final GUIInfo guiInfo = (GUIInfo)getSessionAttribute( HybsSystem.GUIINFO_KEY );
		if( guiInfo != null ) {
			guiInfo.addReadCount( selCnt,dyTime,selSQL );
			guiInfo.addWriteCount( upCnt,dyTime,sql2 );
		}
	}

	/**
	 * 【TAG】実行方法を指定します[INSERT/UPDATE/DELETE/MERGE] (初期値:INSERT)。
	 *
	 * @og.tag
	 * 指定できるアクションは、追加(INSERT)、更新(UPDATE)、削除(DELETE)、マージ(MERGE)です。
	 * マージ以外は、お馴染みのSQL処理です。
	 * マージは、条件にしたがって、UPDATEを行い、更新件数が、0件の場合に、INSERTを行う、複合処理です。
	 * 初期値は、INSERT です。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	action アクション [INSERT/UPDATE/DELETE/MERGE]
	 */
	public void setAction( final String action ) {
		this.action = nval( getRequestParameter( action ),this.action );
	}

	/**
	 * 【TAG】(jdbcオプション)検索した元のﾃﾞｰﾀを削除するかどうか[true:削除する/false:なにもしない]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * アクションで指定した処理とともに、検索元のデータを削除するかどうかを指定します。
	 * 例えば、action="INSERT" で、useDelete="true" を指定すると、 ResultSet#deleteRow() を実行して、
	 * 検索元のデータを削除し、更新先にINSERT するため見かけ上、データ移動することになります。
	 * stopError="false" (エラー時でも処理を継続する)にした場合、検索元のﾃﾞｰﾀ削除は、
	 * エラー行については、実行されません。ただし、UPDATE,DELETE 等で、対象データが
	 * 存在しない場合は、エラーと判断しないため、検索元のﾃﾞｰﾀを削除します。
	 * 初期値は、false です。
	 * ※ ResultSet#deleteRow() をサポートしない場合もあるため、仕様の有無は、対象DBをご確認ください。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	useDel 検索した元のﾃﾞｰﾀを削除するかどうか
	 */
	public void setUseDelete( final String useDel ) {
		useDelete = nval( getRequestParameter( useDel ),useDelete );
	}

	/**
	 * 【TAG】(通常は使いません)データの最大読み込み件数を指定します(初期値:0:[無制限])。
	 *
	 * @og.tag
	 * 検索処理の最大件数を指定します。
	 * このタグでは、検索都度、更新するため、メモリ等の負荷は、DBTableModel を使用する
	 * 通常の検索より少なくてすみます。
	 * 初期値は、0(無制限=実際は、Integer.MAX_VALUE)です。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	count 最大件数
	 */
	public void setMaxRowCount( final String count ) {
		maxRowCount = nval( getRequestParameter( count ),maxRowCount );
		if( maxRowCount == 0 ) { maxRowCount = Integer.MAX_VALUE ; }
	}

//	/**
//	 * 【TAG】検索結果を画面上に表示するメッセージリソースIDを指定します
//	 *		(初期値:VIEW_DISPLAY_MSG[={@og.value SystemData#VIEW_DISPLAY_MSG}])。
//	 *
//	 * @og.tag
//	 * ここでは、検索結果の件数や登録された件数をまず出力し、
//	 * その次に、ここで指定したメッセージをリソースから取得して表示します。
//	 * 件数を表示させる場合は、displayMsg = "MSG0033"[　件検索しました] をセットしてください。
//	 * 表示させたくない場合は, displayMsg = "" をセットしてください。
//	 * (初期値:システム定数のVIEW_DISPLAY_MSG[={@og.value SystemData#VIEW_DISPLAY_MSG}])。
//	 *
//	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
//	 * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
//	 *
//	 * @param	id 表示メッセージID
//	 * @see		org.opengion.hayabusa.common.SystemData#VIEW_DISPLAY_MSG
//	 */
//	public void setDisplayMsg( final String id ) {
//		final String ids = getRequestParameter( id );
//		if( ids != null ) { displayMsg = ids; }
//	}

//	/**
//	 * 【TAG】検索データが最大検索数をオーバーした場合に表示するメッセージリソースIDを指定します
//	 *		(初期値:MSG0007[検索結果が、制限行数を超えましたので、残りはカットされました])。
//	 *
//	 * @og.tag
//	 * 検索結果が、maxRowCount で設定された値より多い場合、何らかのデータは検索されず
//	 * 切り捨てられたことになります。
//	 * ここでは、displayMsg を表示した後、必要に応じて、このメッセージを表示します。
//	 * 表示させたくない場合は, overflowMsg = "" をセットしてください。
//	 * 初期値は、MSG0007[検索結果が、制限行数を超えましたので、残りはカットされました]です。
//	 *
//	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
//	 * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
//	 *
//	 * @param	id オーバー時メッセージID
//	 */
//	public void setOverflowMsg( final String id ) {
//		final String ids = getRequestParameter( id );
//		if( ids != null ) { overflowMsg = ids; }
//	}

//	/**
//	 * 【TAG】検索結果がゼロ件の場合に表示するメッセージリソースIDを指定します(初期値:MSG0077[対象データはありませんでした])。
//	 *
//	 * @og.tag
//	 * ここでは、検索結果がゼロ件の場合のみ、特別なメッセージを表示させます。
//	 * 従来は、displayMsg と兼用で、『0　件検索しました』という表示でしたが、
//	 * displayMsg の初期表示は、OFF になりましたので、ゼロ件の場合のみ別に表示させます。
//	 * 表示させたくない場合は, notfoundMsg = "" をセットしてください。
//	 * 初期値は、MSG0077[対象データはありませんでした]です。
//	 *
//	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
//	 * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
//	 *
//	 * @param	id ゼロ件メッセージID
//	 */
//	public void setNotfoundMsg( final String id ) {
//		final String ids = getRequestParameter( id );
//		if( ids != null ) { notfoundMsg = ids; }
//	}

	/**
	 * 【TAG】検索結果が０件のとき処理を停止するかどうか[true/false]を指定します(初期値:false[続行する])。
	 *
	 * @og.tag
	 * 初期値は、false(続行する)です。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param  cmd ０件時停止可否 [true:処理を中止する/false:続行する]
	 */
	public void setStopZero( final String cmd ) {
		stopZero = nval( getRequestParameter( cmd ),stopZero );
	}

	/**
	 * 【TAG】(通常は使いません)検索する対象のDB接続IDを指定します(初期値:null)。
	 *
	 * @og.tag
	 * 検索側のSELECT文を実行するDB接続IDを指定します。
	 * これは、システムリソースで、DEFAULT_DB_URL 等で指定している データベース接続先
	 * 情報に、XX_DB_URL を定義することで、 dbid="XX" とすると、この 接続先を使用して
	 * データベースにアクセスできます。
	 * 初期値は、Default(=null) です。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	id データベース接続ID
	 */
	public void setDbid( final String id ) {
		dbid = nval( getRequestParameter( id ),dbid );
	}

	/**
	 * 【TAG】検索する対象のテーブル名を指定します(初期値:null)。
	 *
	 * @og.tag
	 * 検索は、この table名を検索するか、BODYに記述された SQL 文を実行します。
	 * 単独検索の場合(JOIN等を行わない場合)に、使用します。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	table テーブル名
	 */
	public void setTable( final String table ) {
		query.setTable( getRequestParameter( table ) );
	}

	/**
	 * 【TAG】検索する対象のカラム名をCSV形式で複数指定します(初期値:*)。
	 *
	 * @og.tag
	 * 複数ある場合は、CSV形式で渡します。
	 * BODYにSELECT文を記述した場合は、names 属性は不要です。
	 * 記述した場合は、SELECTしたカラムから、names属性に指定されたカラムだけを
	 * SELECT対象にします。
	 * 検索元の names と、登録先の、names2 が、対応関係になります。
	 * 初期値は、指定のカラムすべて(*)です。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	names 引数の名称 (CSV形式)
	 */
	public void setNames( final String names ) {
		query.setNames( getRequestParameter( names ) );
	}

	/**
	 * 【TAG】検索する対象を特定するキー条件(where句)を指定します。
	 *
	 * @og.tag
	 * 検索するSELECT文のwhere 句を指定します。通常の WHERE 句の書き方と同じで、
	 * {&#064;XXXX} などが使えます。
	 * 複雑な場合は、BODY に記述してください。where タグや、andタグ等を使って、
	 * 通常のquery タグで指定する方法を、そのまま使います。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	where 検索条件 (where句)
	 */
	public void setWhere( final String where ) {
		query.setWhere( getRequestParameter( where.replaceAll("="," ") ) );

//		query.setWhere( getRequestParameter( where ) );
	}

	/**
	 * 【TAG】検索する対象の検索順(order by句)を指定します。
	 *
	 * @og.tag
	 * 検索するSELECT文のorder by 句を指定します。通常の order by 句の書き方と同じで、
	 * {&#064;XXXX} などが使えます。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	orderBy 検索条件 (order By句)
	 */
	public void setOrderBy( final String orderBy ) {
		query.setOrderBy( getRequestParameter( orderBy ) );
	}

	/**
	 * 【TAG】登録する対象のDB接続IDを指定します(初期値:null)。
	 *
	 * @og.tag
	 * 登録側のINSERT/UPDATE/DELETE文を実行するDB接続IDを指定します。
	 * これは、システムリソースで、DEFAULT_DB_URL 等で指定している データベース接続先
	 * 情報に、XX_DB_URL を定義することで、 dbid="XX" とすると、この 接続先を使用して
	 * データベースにアクセスできます。
	 * 初期値は、Default(=null) です。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	id データベース接続ID
	 */
	public void setDbid2( final String id ) {
		dbid2 = nval( getRequestParameter( id ),dbid2 );
	}

	/**
	 * 【TAG】登録する対象のテーブル名を指定します(初期値:null)。
	 *
	 * @og.tag
	 * 登録は、この table名を使用します。
	 * table2 を指定しない場合は、table と同じテーブルが使用されます。
	 * その場合は、必ず、table が指定されます。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	table テーブル名
	 */
	public void setTable2( final String table ) {
		query2.setTable( getRequestParameter( table) );
	}

	/**
	 * 【TAG】登録する対象のカラム名をCSV形式で複数指定します(初期値:null)。
	 *
	 * @og.tag
	 * 登録する対象のカラム名は、検索したカラム名の順番に割り当てられます。
	 * 例えば、names 属性に、a1,b1,c1 と指定した場合、names2 に、A2,B2,C2 と指定すれば、
	 * 順番に、a1→A2 , b1→B2 , c1→C2 に割り当てられます。
	 * BODY にSELECT文を記述した場合も、names2 を指定すれば、指定のカラムの順番に割り当てます。
	 * これは、SELECT 側と、INSERT/UPDATE 側のカラム名が異なる場合に、検索側に、別名(as 別名)を
	 * 指定する必要がありません。
	 * 指定しない場合(初期値)は、names または、SELECT文のすべてのカラムが、同一名として処理されます。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	names 引数の名称 (CSV形式)
	 */
	public void setNames2( final String names ) {
		query2.setNames( getRequestParameter( names) );
	}

	/**
	 * 【TAG】登録対象外のカラム名をCSV形式で複数指定します(初期値:null)。
	 *
	 * @og.tag
	 * names2 の逆で、登録対象から省くカラム名を指定します。
	 * table 指定や、select * from で、カラム名を大量に指定したい場合、names2 で
	 * 指定するより、除外するカラム名を指定するほうが、少なく（判りやすく）なる
	 * 場合があります。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param   omitNames 登録対象外のカラム列 (CSV形式)
	 */
	public void setOmitNames2( final String omitNames ) {
		query2.setOmitNames( getRequestParameter( omitNames ) );
	}

	/**
	 * 【TAG】登録する対象を特定するキー条件(where句)を指定します。
	 *
	 * @og.tag
	 * 登録するUPDATE/DELETE文のwhere 句を指定します。通常の{&#064;XXXX} のほかに、
	 * [検索カラム名] も使用できます。これは、検索側の where 属性と異なります。
	 * ただし、複雑な where 条件は使えませんので、できるだけ、検索側で調整して置いてください。
	 * action="UPDATE/DELETE/MERGE" でのみ有効です。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	where 検索条件 (where句)
	 */
	public void setWhere2( final String where ) {
		query2.setWhere( getRequestParameter( where) );
	}

	/**
	 * 【TAG】登録する対象を特定するキー条件(where句)をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * 生成するUPDATEのwhere 句を指定する方法として、複数のカラム名をCSV指定し、内部で
	 * KEY=[KEY] 文字列を作成します。
	 * ここでは、カラム名は、データベースのカラム名と同じで、かつ、検索側にも
	 * 同じカラムのデータが存在していること、という条件付きとします。
	 * また、where 条件との併用を行いますが、こちらの条件が先に使用され、where 条件は、
	 * and を付けて、文字列結合されます。
	 * 例： CLM,SYSTEM_ID,KBSAKU   ⇒   CLM=[CLM] and SYSTEM_ID=[SYSTEM_ID] and KBSAKU=[KBSAKU]
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	names 登録条件カラム (where句)作成のためのカラム名(CSV形式)
	 */
	public void setWhereNames2( final String names ) {
		query2.setWhereNames( getRequestParameter( names) );
	}

	/**
	 * 【TAG】設定値を固定値と置き換える対象となるカラム名をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * names 属性のカラムや table 属性より、INSERT/UPDATE文を作成する場合
	 * 外部から指定した固定値を指定するための、カラム名をCSV形式(CSV)で複数指定します。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	keys 固定値カラム (CSV形式)
	 * @see		#setConstVals2( String )
	 */
	public void setConstKeys2( final String keys ) {
		query2.setConstKeys( getRequestParameter( keys ) );
	}

	/**
	 * 【TAG】設定値を固定値と置き換える対象となる設定値をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * names 属性のカラムや table 属性より、INSERT/UPDATE文を作成する場合
	 * 外部から指定した固定値を指定するための、カラム名に対応する設定値をCSV形式(CSV)で
	 * 複数指定します。ここで指定する設定値は、constKeys2 属性と対応させます。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param	vals 設定値(CSV形式)
	 * @see		#setConstKeys2( String )
	 */
	public void setConstVals2( final String vals ) {
		query2.setConstVals( getRequestParameter( vals ) );
	}

	/**
	 * 【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します
	 *		(初期値:USE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。
	 *
	 * @og.tag
	 * ＳＱＬインジェクション対策の一つとして、暫定的ではありますが、SQLのパラメータに
	 * 渡す文字列にシングルクォート(') を許さない設定にすれば、ある程度は防止できます。
	 * 数字タイプの引数には、 or 5=5 などのシングルクォートを使用しないコードを埋めても、
	 * 数字チェックで検出可能です。文字タイプの場合は、必ず (')をはずして、
	 * ' or 'A' like 'A のような形式になる為、(')チェックだけでも有効です。
	 * (') が含まれていたエラーにする(true)／かノーチェックか(false)を指定します。
	 * 初期値は、SystemData#USE_SQL_INJECTION_CHECK です。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param   flag クォートチェック [true:する/それ以外:しない]
	 */
	public void setQuotCheck( final String flag ) {
		quotCheck = nval( getRequestParameter( flag ),quotCheck );
	}

	/**
	 * 【TAG】登録処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)。
	 *
	 * @og.tag
	 * false(中止しない)に設定する場合、後続処理では、{&#064;DB.ERR_CODE}の値により、
	 * 異常/正常判断を行いますが、処理は、継続されます。
	 * ちなみに、更新/削除処理で、対象データが存在しない場合(0件更新や、0件削除)は、エラーでは
	 * ありません。
	 * 初期値は、true(中止する)です。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param   flag エラー時処理中止 [true:中止する/false:中止しない]
	 */
	public void setStopError( final String flag ) {
		stopError = nval( getRequestParameter( flag ),stopError );
	}

	/**
	 * 【TAG】エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)。
	 *
	 * @og.tag
	 * false(表示しない)に設定する場合、後続処理では、{&#064;DB.ERR_MSG}の値により、
	 * 本来表示されるはずだったメッセージを取得可能です。
	 * stopErrorと併用して、JSON形式でエラーを返す場合等に利用します。
	 * 初期値は、true(表示する)です。
	 * ※false指定の場合は件数等も表示されなくなります。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @param   flag  [true:表示する/false:表示しない]
	 */
	public void setDispError( final String flag ) {
		dispError = nval( getRequestParameter( flag ),dispError );
	}

	/**
	 * 【TAG】(通常は使いません)データのフェッチサイズを指定します
	 *		(初期値:DB_FETCH_SIZE[={@og.value org.opengion.fukurou.system.HybsConst#DB_FETCH_SIZE}])。
	 *
	 * @og.tag
	 * より多くの行が必要なときに、データベースから取り出す必要がある行数に
	 * ついてのヒントを JDBC ドライバに提供します。
	 * 指定された行数は、この Statement を使って作成された結果セットにだけ影響します。
	 * 指定された値が 0 の場合、ヒントは無視されます。
	 * (初期値:システム定数のDB_FETCH_SIZE[={@og.value org.opengion.fukurou.system.HybsConst#DB_FETCH_SIZE}])。
	 *
	 * @param	size フェッチ行数
	 */
	public void setFetchSize( final String size ) {
		fetchSize = nval( getRequestParameter( size ),fetchSize );
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
	 *
	 * @return このクラスの文字列表現
	 */
	@Override
	public String toString() {
		return selSQL == null ? ""
						   : selSQL.replaceAll( "[\\\t]+"," " ).replaceAll( "[\\s]+\\\n","\\\n" ) ;
		//									  連続するTABをスペースに     連続する空白文字と改行を改行のみに
	}
}
