/*
 * 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 org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBConstValue;

import org.opengion.fukurou.util.StringUtil ;
import static org.opengion.fukurou.util.StringUtil.nval ;

import java.util.Map;
import java.util.LinkedHashMap;

/**
 * TableUpdateTag にパラメーターを渡す為のタグクラスです。
 *
 * 汎用的なデータベース登録処理を行えるタグ tableUpdate タグを新規作成します。
 * これは、具体的なSLQを作成する tableUpdateParam タグと組み合わせて使用できます。
 * tableUpdate タグは、queryType に JDBCTableUpdate を指定します。基本的にこれだけ
 * です。tableUpdateParam では、sqlType に、INSERT,COPY,UPDATE,MODIFY,DELETE の
 * どれかを指定する事で、SQL文のタイプを指定します。COPY,MODIFY は command と
 * 関連を持たす為に追加しているタイプで、INSERTやUPDATE と同じ処理を行います。
 * tableUpdateParam の table には、作成したい SQL のテーブルを指定します。
 * where 属性は、検索結果の DBTableModel の更新時に使用する条件を指定します。
 *
 * @og.formSample
 * ●形式：&lt;og:tableUpdate command="{@command}" queryType="JDBCTableUpdate" &gt;
 *            &lt;og:tableUpdateParam
 *                sqlType       = "{@sqlType}"       // INSERT,COPY,UPDATE,MODIFY,DELETE
 *                table         = "{@TABLE_NAME}"    // 処理対象のテーブル名
 *                names         = "{@names}"         // 処理対象のカラム名
 *                omitNames     = "{@omitNames}"     // 処理対象外のカラム名
 *                where         = "{@where}"         // 処理対象を特定するキー
 *                constKeys     = "{@constKeys}"     // 処理カラム名の中の固定情報カラム名
 *                constVals     = "{@constVals}"     // 処理カラム名の中の固定情報設定値
 *                logicalDelete = "{@logicalDelete}" // sqlTypeがDELETEの場合にもUPDATE文を発行
 *            /&gt;
 *         &lt;/og:tableUpdate&gt;
 *
 * ●body：なし
 *
 * ●使用例
 *    ・【entry.jsp】
 *         &lt;og:tableUpdate command="{@command}" queryType="JDBCTableUpdate" &gt;
 *            &lt;og:tableUpdateParam
 *               sqlType  = "{@sqlType}"
 *               table    = "{@MEM.TABLE_NAME}"
 *               where    = "ROWID = [ROWID]"
 *            /&gt;
 *         &lt;/og:tableUpdate&gt;
 *
 * @og.rev 3.8.8.0 (2007/12/22) 新規作成
 * @og.rev 4.1.2.0 (2008/03/12) 実装の大幅な修正
 * @og.group ＤＢ登録
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class TableUpdateParamTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "4.0.0 (2005/11/30)" ;

	private static final long serialVersionUID = 4000 ;	// 4.0.0 (2005/11/30)

	/** sqlType属性に設定できる値			{@value} */
	public static final String SQL_TYPE  = "|INSERT|COPY|UPDATE|MODIFY|DELETE|" ;

	// 3.8.0.4 (2005/08/08) 印刷時に使用するシステムID
	private static final String SYSTEM_ID =HybsSystem.sys( "SYSTEM_ID" );
	
	// 4.3.6.0 (2009/05/01) デフォルトで利用するconstObjのシステムリソース名
	private static final String DEFAULT_CONST_OBJ = HybsSystem.sys( "DEFAULT_CONST_CLASS" ); 

	private String		sqlType		= null;			// INSERT,COPY,UPDATE,MODIFY,DELETE
	private String		table		= null;			// 処理対象のテーブル名
	private String[]	names		= null;			// 処理対象のカラム名
	private String		omitNames	= ",ROWID,ROWNUM,WRITABLE,";		// 処理対象外のカラム名
	private String		where		= null;			// 処理対象を特定するキー
	private String[]	constKeys	= null;			// 処理カラム名の中の固定情報カラム名
	private String[]	constVals	= null;			// 処理カラム名の中の固定情報設定値
	private String		constObjKey	= SYSTEM_ID;	// 固定情報カラムの処理オブジェクトを特定するキー
	private boolean		quotCheck	= HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );
	private boolean		logicalDelete = false;		// 4.3.7.0 (2009/06/01) sqlTypeがDELETEの場合にもUPDATE文を発行

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @return  int
	 */
//	@Override
//	public int doStartTag() {
//		inputCheck() ;
//
//		return( EVAL_BODY_BUFFERED );		// Body を評価する
//	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 * 
	 * @og.rev 4.3.7.0 (2009/06/01) 論理削除対応
	 *
	 * @return	int
	 */
	@Override
	public int doEndTag() {
		debugPrint();

		TableUpdateTag updateTag = (TableUpdateTag)findAncestorWithClass( this,TableUpdateTag.class );
		if( updateTag == null ) {
			String errMsg = "<b>このタグは、TableUpdateTagの内側(要素)に記述してください。</b>";
			throw new HybsSystemException( errMsg );
		}

		String upSqlType = updateTag.getSqlType() ;
		if( upSqlType == null || upSqlType.equals( sqlType ) ) {
			// 通常の names カラム配列を設定します。
			if( names == null ) { names = updateTag.getNames(); }
			NamesData namesData = makeNamesData( names );

			String query = null;
			if( "INSERT".equalsIgnoreCase( sqlType ) || "COPY".equalsIgnoreCase( sqlType ) ) {
				query = getInsertSQL( namesData );
			}
			else if( "UPDATE".equalsIgnoreCase( sqlType ) || "MODIFY".equalsIgnoreCase( sqlType ) 
					|| ( "DELETE".equalsIgnoreCase( sqlType ) && logicalDelete ) ) { // 4.3.7.0 (2009/06/01)
				query = getUpdateSQL( namesData );
			}
			else if( "DELETE".equalsIgnoreCase( sqlType ) ) {
				query = getDeleteSQL();
			}

			jspPrint( query );
		}

		return(EVAL_PAGE);
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 * 
	 * @og.rev 4.3.7.0 (2009/06/01) logicalDelete属性追加
	 */
	@Override
	protected void release2() {
		super.release2();			// 3.5.6.0 (2004/06/18) 追加(抜けていた)
		sqlType		= null;			// INSERT,COPY,UPDATE,MODIFY,DELETE
		table		= null;			// 処理対象のテーブル名
		names		= null;			// 処理対象のカラム名
		omitNames	= ",ROWID,ROWNUM,WRITABLE,";		// 処理対象外のカラム名
		where		= null;			// 処理対象を特定するキー
		constKeys	= null;			// 処理カラム名の中の固定情報カラム名
		constVals	= null;			// 処理カラム名の中の固定情報設定値
		quotCheck	= HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );
		constObjKey	= SYSTEM_ID;	// 固定情報カラムの処理オブジェクトを特定するキー
		logicalDelete = false;		// 4.3.7.0 (2009/06/01)
	}

	/**
	 * 【TAG】BODY部に書かれている SQLタイプを指定します。
	 *
	 * @og.tag
	 * SQLタイプは、INSERT,COPY,UPDATE,MODIFY,DELETE の中から指定する
	 * 必要があります。これらは、内部に書かれるSQLの形式を指定するのに使用します。
	 * 内部処理は、DBTableModelの改廃コード(A,C,D)に対して使用される
	 * SQL を選択する場合の情報に使用されます。
	 * なお、COPY と MODIFY は、command で指定できる簡易機能として用意しています。
	 * 上位の TableUpdateTag の sqlType 属性 と同じsqlType 属性の場合のみ、SQL文を
	 * 合成・出力します。(上位のsqlTypeがnullの場合は、無条件実行します。)
	 * 指定のタイプが、異なる場合は、なにも処理を行いません。
	 *
	 * @param	type BODY部に書かれている SQL タイプ
	 */
	public void setSqlType( final String type ) {
		sqlType = nval( getRequestParameter( type ),sqlType );
		if( sqlType != null && SQL_TYPE.indexOf( "|" + sqlType + "|" ) < 0 ) {
			sqlType = null;
	//		String errMsg = "sqlType属性には、" + SQL_TYPE + "以外設定できません。"
	//					+ " typeIn=[" + type + "]"
	//					+ " sqlType=[" + sqlType + "]" ;
	//		throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * 【TAG】処理対象のテーブル名を指定します。
	 *
	 * @og.tag
	 * テーブル名を指定することで、sqlTypeに応じた QUERYを生成することが出来ます。
	 * 生成する場合のカラムを特定する場合は、names 属性で指定できます。
	 * また、WHERE条件は、where属性で指定します。
	 *
	 * @param	tbl String
	 * @see		#setNames( String )
	 * @see		#setWhere( String )
	 * @see		#setSqlType( String )
	 */
	public void setTable( final String tbl ) {
		table = nval( getRequestParameter( tbl ),table );
	}

	/**
	 * 【TAG】処理対象のカラム名をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * 生成するQUERYのカラム名をカンマ区切り文字(CSV)で複数指定します。
	 * 指定がない場合は、DBTableModel の全カラム(※)を使用して、QUERYを構築します。
	 * 一般に、テーブル結合してDBTableModelを構築した場合は、登録すべきカラムを
	 * 指定する必要があります。
	 * (※)正確には、DBTableModel の全カラムのうち、ROWID,ROWNUM,WRITABLE カラムは
	 * 無視します。
	 * 分解方法は、通常のパラメータ取得後に、CSV分解します。
	 *
	 * @og.rev 3.8.8.5 (2007/03/09) 通常のパラメータ取得後に、CSV分解に戻します。
	 *
	 * @param	nms String
	 * @see		#setTable( String )
	 * @see		#setOmitNames( String )
	 */
	public void setNames( final String nms ) {
		names = StringUtil.csv2Array( getRequestParameter( nms ) );
		if( names.length == 0 ) { names = null; }
	}

	/**
	 * 【TAG】処理対象外のカラム名をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * 生成するQUERYのカラム名に指定しないカラム名をカンマ区切り文字(CSV)で複数指定します。
	 * 指定がない場合は、DBTableModel の全カラム(※)を使用して、QUERYを構築します。
	 * テーブル結合などで、処理したくないカラム数の方が少ない場合に、names ですべてを
	 * 指定するより少ない記述ですみます。
	 * (※)正確には、DBTableModel の全カラムのうち、ROWID,ROWNUM,WRITABLE カラムは
	 * 無視します。
	 *
	 * @param	nms String
	 * @see		#setTable( String )
	 * @see		#setNames( String )
	 */
	public void setOmitNames( final String nms ) {
		omitNames = omitNames + nval( getRequestParameter( nms ),"" ) + ",";
	}

	/**
	 * 【TAG】処理対象を特定するキー条件（where句）を指定します。
	 *
	 * @og.tag
	 * 生成するQUERYのwhere 句を指定します。通常の WHERE 句の書き方と同じで、
	 * DBTableModelの値を割り当てたい箇所に[カラム名] を記述します。
	 * 文字列の場合、設定値をセットするときに、シングルコーテーションを
	 * 使用しますが、[カラム名]で指定する場合は、その前後に、(')シングル
	 * コーテーションは、不要です。
	 * {&#064;XXXX}変数を使用する場合は、パース時に固定文字に置き換えられる為、
	 * 文字列指定時の(')シングルコーテーションが必要になります。
	 * 例：FGJ='1' and CLM=[CLM] and SYSTEM_ID in ([SYSID],'**') and KBSAKU='{&#064;KBSAKU}'
	 *
	 * @param	wr 検索条件（where句）
	 */
	public void setWhere( final String wr ) {
		where = nval( getRequestParameter( wr ),where );
	}

	/**
	 * 【TAG】設定値を固定値と置き換える対象となるカラム名をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * names 属性のカラムや table 属性より、QUERYを作成して、DBTableModelの値を
	 * 割り当てる場合、DBTableModelの値ではなく、外部から指定した固定値を
	 * 割り当てたい場合に、そのカラム名をカンマ区切り文字(CSV)で複数指定します。
	 * ここで指定するカラム名は、names 属性に含まれるか、DBTableModelのカラムとして
	 * 存在する必要があります。なお、names 属性に含まれる場合は、BTableModelのカラムに
	 * 含まれる必要はありません。
	 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。
	 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。
	 *
	 * @param	keys String
	 * @see		#setConstVals( String )
	 */
	public void setConstKeys( final String keys ) {
		constKeys = getCSVParameter( keys );
	}

	/**
	 * 【TAG】設定値を固定値と置き換える対象となる設定値をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * names 属性のカラムや table 属性より、QUERYを作成して、DBTableModelの
	 * 値を割り当てる場合、DBTableModelの値ではなく、外部から指定した固定値を
	 * 割り当てたい場合に、そのカラム名に対応する設定値をカンマ区切り文字(CSV)で
	 * 複数指定します。ここで指定する設定値は、constKeys 属性と対応させます。
	 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。
	 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。
	 *
	 * @param	vals String
	 * @see		#setConstKeys( String )
	 */
	public void setConstVals( final String vals ) {
		constVals = getCSVParameter( vals );
	}

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

	/**
	 * 【TAG】固定情報カラムの処理オブジェクトを特定するキーを設定します(初期値:SYSTEM_ID)。
	 *
	 * @og.tag
	 * 固定情報カラム をシステム単位にJavaクラスで管理できます。
	 * そのクラスオブジェクトは、org.opengion.hayabusa.db.DBConstValue インターフェースを
	 * 継承した、plugin クラスになります。
	 * そのクラスを特定するキーワードを指定します。
	 * 初期値は、SYSTEM_ID でシステム単位にクラスを作成します。
	 * もし、他のシステムと共通の場合は、継承だけさせることも可能です。
	 * 対応したDBConstValueクラスがプラグインとして存在しない場合は、
	 * システムリソースのDEFAULT_CONST_CLASSで指定されたクラスが利用されます。
	 * 
	 * 初期値は、SYSTEM_ID です。
	 *
	 * @param   key 固定情報カラムの処理オブジェクトを特定するキー
	 */
	public void setConstObjKey( final String key ) {
		constObjKey = nval( getRequestParameter( key ),constObjKey );
	}

	/**
	 * 【TAG】sqlType="DELETE"の場合に論理削除(UPDATE)を行うかどうかを指定します。(初期値:false)。
	 *
	 * @og.tag
	 * sqlType="DELETE"の場合に論理削除(UPDATE)を行うかどうかを指定します。
	 * trueが指定された場合は、DELETE文ではなく、UPDATE文が発行されます。
	 * falseが指定された場合は、DELETE文が発行されます。
	 * さらに論理削除を行う場合、org.opengion.hayabusa.db.DBConstValue インターフェースに
	 * 定義されている、getLogicalDeleteKeys()及びgetLogicalDeleteValsを実装することで、
	 * 論理削除する際のフラグの更新方法を統一的に管理することが可能になります。
	 * 初期値は、false(物理削除する)です
	 *
	 * @param   flg 論理削除(UPDATE)を行うかどうか
	 */
	public void setLogicalDelete( final String flg ) {
		logicalDelete = nval( getRequestParameter( flg ),logicalDelete );
	}

	/**
	 * データをインサートする場合に使用するSQL文を作成します。
	 *
	 * @og.rev 4.1.2.1 (2008/03/17) DBConstValue による固定値セットを採用
	 * @og.rev 4.3.6.4 (2009/05/01) デフォルト設定をシステムリソースで設定可能にする
	 *
	 * @param   namesData NamesData
	 * @return  Insert SQL
	 */
	private String getInsertSQL( final NamesData namesData ) {
		String cls = HybsSystem.sys( "DBConstValue_" + constObjKey ) ;
		
		// 4.3.6.4 (2009/05/01) 標準の追加
		if( cls == null){
			cls = DEFAULT_CONST_OBJ;
		}

		if( cls != null ) {
			DBConstValue constVal = (DBConstValue)HybsSystem.newInstance( cls );
	 		// 4.2.1.0 (2008/04/16) 初期化追加
			constVal.init( table,getUser().getUserID(),getGUIInfoAttri( "KEY" ) );
			String[] keys = constVal.getInsertKeys();
			String[] vals = constVal.getInsertVals();
			namesData.add( keys,vals );
		}

		String[] nms = namesData.getNames();
		String[] vls = namesData.getVals();

		StringBuilder sql = new StringBuilder();
		sql.append( "insert into " ).append( table );
		sql.append( " ( " );
		sql.append( nms[0] );
		for( int i=1; i<nms.length; i++ ) {
			sql.append( "," ).append( nms[i] );
		}
		sql.append( " ) values ( " );
		sql.append( vls[0] );
		for( int i=1; i<vls.length; i++ ) {
			sql.append( "," ).append( vls[i] );
		}
		sql.append( " )" );

		return sql.toString();
	}

	/**
	 * データをアップデートする場合に使用するSQL文を作成します。
	 *
	 * @og.rev 4.1.2.1 (2008/03/17) DBConstValue による固定値セットを採用
	 * @og.rev 4.3.6.4 (2009/05/01) デフォルト設定をシステムリソースで設定可能にする
	 * @og.rev 4.3.7.0 (2009/06/01) 論理削除対応
	 *
	 * @param   namesData NamesData
	 * @return  Update SQL
	 */
	private String getUpdateSQL( final NamesData namesData ) {
		String cls = HybsSystem.sys( "DBConstValue_" + constObjKey ) ;
		
		// 4.3.6.4 (2009/05/01) 標準の追加
		if( cls == null){
			cls = DEFAULT_CONST_OBJ;
		}

		if( cls != null ) {
			DBConstValue constVal = (DBConstValue)HybsSystem.newInstance( cls );
	 		// 4.2.1.0 (2008/04/16) 初期化追加
			constVal.init( table,getUser().getUserID(),getGUIInfoAttri( "KEY" ) );
			// 4.3.7.0 (2009/06/01) 論理削除対応
			String[] keys = null;
			String[] vals = null;
			if( "DELETE".equalsIgnoreCase( sqlType ) ) {
				keys = constVal.getLogicalDeleteKeys();
				vals = constVal.getLogicalDeleteVals();
			}
			else {
				keys = constVal.getUpdateKeys();
				vals = constVal.getUpdateVals();
			}
			namesData.add( keys,vals );
		}

		String[] nms = namesData.getNames();
		String[] vls = namesData.getVals();

		StringBuilder sql = new StringBuilder();
		sql.append( "update " ).append( table ).append( " set " );
		sql.append( nms[0] ).append( "=" ).append( vls[0] );

		for( int i=1; i<nms.length; i++ ) {
			sql.append( "," );
			sql.append( nms[i] ).append( "=" ).append( vls[i] );
		}

		if( where != null && where.length() > 0 ) {
			sql.append( " where " ).append( where );
		}

		return sql.toString();
	}

	/**
	 * データをデリートする場合に使用するSQL文を作成します。
	 *
	 * @return  Delete SQL
	 */
	private String getDeleteSQL() {
		StringBuilder sql = new StringBuilder();
		sql.append( "delete from " ).append( table );
		if( where != null && where.length() > 0 ) {
			sql.append( " where " ).append( where );
		}
		return sql.toString();
	}

	/**
	 * names,constKeys,omitNames から、必要なキー情報と、
	 * 属性情報を持った NamesData を作成します。
	 *
	 * @og.rev 4.1.2.1 (2008/03/17) 固定値の constVals の前後に、"'" を入れる。
	 *
	 * @param   nms String[]
	 * @return  NamesData
	 */
	private NamesData makeNamesData( final String[] nms ) {

		NamesData namesData = new NamesData();

		for( int i=0; i<nms.length; i++ ) {
			String nm = nms[i];
			if( nm != null && nm.length() > 0 && omitNames.indexOf( "," + nm + "," ) < 0 ) {
				namesData.add( nm,"[" + nm + "]" ) ;
			}
		}

		// 固定値の constKeys カラム配列を設定します。
		if( constKeys != null && constKeys.length > 0 ) {
			for( int j=0; j<constKeys.length; j++ ) {
				String nm = constKeys[j];
				if( nm != null && nm.length() > 0 ) {
					namesData.add( nm,"'" + constVals[j] + "'" ) ;
				}
			}
		}

		return namesData ;
	}

	/**
	 * 内部データを受け渡す為の、簡易クラスです。
	 * 更新するカラム名と値のセット配列を管理しています。
	 *
	 */
	private static class NamesData {
		final Map<String,String> data = new LinkedHashMap<String,String>() ;

		/**
		 * キーと値のセットを追加します。
		 *
		 * @param   nm String
		 * @param   val String
		 */
		public void add( final String nm,final String val ) {
			data.put( nm,val );
		}

		/**
		 * キー配列と対応する、値配列のセットを追加します。
		 *
		 * @param   nms String[]
		 * @param   vals String[]
		 */
		public void add( final String[] nms,final String[] vals ) {
			if( nms != null ) {
				for( int i=0; i<nms.length; i++ ) {
					data.put( nms[i],vals[i] );
				}
			}
		}

		/**
		 * キー配列を返します。
		 *
		 * @return   String[]
		 */
		public String[] getNames() {
			return data.keySet().toArray( new String[data.size()] );
		}

		/**
		 * 値配列を返します。
		 *
		 * @return   String[]
		 */
		public String[] getVals()  {
			return data.values().toArray( new String[data.size()] );
		}
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 */
	public String toString() {
		return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
				.println( "VERSION"			,VERSION		)
				.println( "sqlType"			,sqlType		)
				.println( "table"			,table			)
				.println( "names"			,names			)
				.println( "omitNames"		,omitNames		)
				.println( "where"			,where			)
				.println( "constKeys"		,constKeys		)
				.println( "constVals"		,constVals		)
				.println( "logicalDelete"	,logicalDelete	)
				.fixForm().toString() ;
	}
}
