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.hayabusa.taglib; 017 018import org.opengion.hayabusa.common.HybsSystem; 019import org.opengion.hayabusa.common.HybsSystemException; 020import org.opengion.hayabusa.db.DBConstValue; 021import org.opengion.fukurou.util.StringUtil ; 022import org.opengion.fukurou.util.ToString; // 6.1.1.0 (2015/01/17) 023 024import static org.opengion.fukurou.util.StringUtil.nval ; 025 026import java.util.Map; 027import java.util.LinkedHashMap; 028import java.util.Locale ; // 6.4.1.2 (2016/01/22) 029 030/** 031 * TableUpdateTag にパラメーターを渡す為のタグクラスです。 032 * 033 * 汎用的なデータベース登録処理を行えるタグ tableUpdate タグを新規作成します。 034 * これは、具体的なSLQを作成する tableUpdateParam タグと組み合わせて使用できます。 035 * tableUpdate タグは、queryType に JDBCTableUpdate を指定します。基本的にこれだけ 036 * です。tableUpdateParam では、sqlType に、INSERT,COPY,UPDATE,MODIFY,DELETE の 037 * どれかを指定する事で、SQL文のタイプを指定します。COPY,MODIFY は command と 038 * 関連を持たす為に追加しているタイプで、UPDATE,INSERT と同じ処理を行います。 039 * tableUpdateParam の table には、作成したい SQL のテーブルを指定します。 040 * where 属性は、検索結果の DBTableModel の更新時に使用する条件を指定します。 041 * 042 * @og.formSample 043 * ●形式:<og:tableUpdate command="{@command}" queryType="JDBCTableUpdate" > 044 * <og:tableUpdateParam 045 * sqlType = "{@sqlType}" // INSERT,COPY,UPDATE,MODIFY,DELETE,MERGE 046 * table = "{@TABLE_NAME}" // 処理対象のテーブル名 047 * names = "{@names}" // 処理対象のカラム名 048 * omitNames = "{@omitNames}" // 処理対象外のカラム名 049 * where = "{@where}" // 処理対象を特定するキー 050 * whereNames = "{@whereNames}" // 処理対象を特定するキー条件(where句)をCSV形式 051 * constKeys = "{@constKeys}" // 処理カラム名の中の固定情報カラム名 052 * constVals = "{@constVals}" // 処理カラム名の中の固定情報設定値 053 * asNames = "{@asNames}" // 別名を付けたカラム名(select A as B from TBL の B を指定) 054 * orgNames = "{@orgNames}" // tableの実際のカラム名(select A as B from TBL の A を指定) 055 * funcKeys = "{@funcKeys}" // 関数等を設定するカラム名 056 * funcVals = "{@funcVals}" // 関数等の設定値 057 * logicalDelete = "{@logicalDelete}" // sqlTypeがDELETEの場合にもUPDATE文を発行 058 * /> 059 * </og:tableUpdate> 060 * 061 * ●body:なし 062 * 063 * ●Tag定義: 064 * <og:tableUpdateParam 065 * sqlType ○【TAG】BODY部に書かれている SQLタイプを指定します(INSERT,COPY,UPDATE,MODIFY,DELETE,MERGE)(必須) 066 * table ○【TAG】処理対象のテーブル名を指定します(必須) 067 * names 【TAG】処理対象のカラム名をCSV形式で複数指定します 068 * omitNames 【TAG】処理対象外のカラム名をCSV形式で複数指定します 069 * where 【TAG】処理対象を特定するキー条件(where句)を指定します 070 * whereNames 【TAG】処理対象を特定するキー条件(where句)をCSV形式で複数指定します 071 * insertOnly 【TAG】true に設定すると、sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない(初期値:false) 7.4.1.0 (2021/04/23) 072 * constKeys 【TAG】設定値を固定値と置き換える対象となるカラム名をCSV形式で複数指定します 073 * constVals 【TAG】設定値を固定値と置き換える対象となる設定値をCSV形式で複数指定します 074 * funcKeys 【TAG】関数等を設定するカラム名をCSV形式で複数指定します 075 * funcVals 【TAG】関数等の設定値をCSV形式で複数指定します 076 * asNames 【TAG】別名を付けたカラム名(select A as B from TBL の B を指定)をCSV形式で複数指定します 077 * orgNames 【TAG】tableの実際のカラム名(select A as B from TBL の A を指定)をCSV形式で複数指定します 078 * quotCheck 【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します (初期値:USE_SQL_INJECTION_CHECK[=true]) 079 * constObjKey 【TAG】固定情報カラムの処理オブジェクトを特定するキーを設定します(初期値:SYSTEM_ID) 080 * logicalDelete 【TAG】sqlType="DELETE"の場合に論理削除(UPDATE)を行うかどうかを指定します(初期値:false) 081 * caseKey 【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null) 082 * caseVal 【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null) 083 * caseNN 【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない) 084 * caseNull 【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない) 085 * caseIf 【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない) 086 * debug 【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false) 087 * /> 088 * 089 * ●使用例 090 * ・【entry.jsp】 091 * <og:tableUpdate command="{@command}" queryType="JDBCTableUpdate" > 092 * <og:tableUpdateParam 093 * sqlType = "{@sqlType}" 094 * table = "{@MEM.TABLE_NAME}" 095 * where = "ROWID = [ROWID]" 096 * /> 097 * </og:tableUpdate> 098 * 099 * @og.rev 3.8.8.0 (2007/12/22) 新規作成 100 * @og.rev 4.1.2.0 (2008/03/12) 実装の大幅な修正 101 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 102 * @og.group DB登録 103 * 104 * @version 4.0 105 * @author Kazuhiko Hasegawa 106 * @since JDK5.0, 107 */ 108public class TableUpdateParamTag extends CommonTagSupport { 109 /** このプログラムのVERSION文字列を設定します。 {@value} */ 110 private static final String VERSION = "7.4.1.0 (2021/04/23)" ; 111 private static final long serialVersionUID = 741020210423L ; 112 113 /** sqlType属性に設定できる値 {@value} */ 114// public static final String SQL_TYPE = "|INSERT|COPY|UPDATE|MODIFY|DELETE|" ; 115 public static final String SQL_TYPE = "|INSERT|COPY|UPDATE|MODIFY|DELETE|MERGE|" ; // 7.2.9.1 (2020/10/23) 116 117 // 3.8.0.4 (2005/08/08) 印刷時に使用するシステムID 118 private static final String SYSTEM_ID =HybsSystem.sys( "SYSTEM_ID" ); 119 120 // 4.3.6.0 (2009/05/01) デフォルトで利用するconstObjのシステムリソース名 121 private static final String DEFAULT_CONST_OBJ = HybsSystem.sys( "DEFAULT_CONST_CLASS" ); 122 123 private String sqlType ; // INSERT,COPY,UPDATE,MODIFY,DELETE,MERGE 124 private String table ; // 処理対象のテーブル名 125 private String[] names ; // 処理対象のカラム名 126 private String omitNames = ",ROWID,ROWNUM,WRITABLE,"; // 処理対象外のカラム名 127 private String where ; // 処理対象を特定するキー 128 private String whereNames ; // 5.5.8.5 (2012/11/27) 処理対象を特定するCSV形式のカラム名 129 private boolean insertOnly; // 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 130 private String[] constKeys ; // 処理カラム名の中の固定情報カラム名 131 private String[] constVals ; // 処理カラム名の中の固定情報設定値 132 private String[] funcKeys ; // 5.5.1.9 (2012/04/19) 関数等を設定するカラム名 133 private String[] funcVals ; // 5.5.1.9 (2012/04/19) 関数等の設定値 134 private String[] asNames ; // 5.5.1.9 (2012/04/19) 別名を付けたカラム名(select A as B from TBL の B を指定) 135 private String[] orgNames ; // 5.5.1.9 (2012/04/19) tableの実際のカラム名(select A as B from TBL の A を指定) 136 private String constObjKey = SYSTEM_ID; // 固定情報カラムの処理オブジェクトを特定するキー 137 private boolean quotCheck = HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" ); 138 private boolean logicalDelete; // 4.3.7.0 (2009/06/01) sqlTypeがDELETEの場合にもUPDATE文を発行 139 140 /** 141 * デフォルトコンストラクター 142 * 143 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 144 */ 145 public TableUpdateParamTag() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 146 147 /** 148 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。 149 * 150 * @og.rev 5.5.1.9 (2012/04/19) エラーチェックを先に行います。 151 * @og.rev 6.3.4.0 (2015/08/01) caseKey,caseVal,caseNN,caseNull,caseIf 属性対応 152 * @og.rev 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"/>" 止めを、">" に変更します)。 153 * 154 * @return 後続処理の指示( SKIP_BODY ) 155 */ 156 @Override 157 public int doStartTag() { 158 if( !useTag() ) { return SKIP_BODY ; } // 6.3.4.0 (2015/08/01) 159 160 // constKeys,constVals の個数チェック 161 if( constKeys != null ) { 162 if( constVals == null || constKeys.length != constVals.length ) { 163// final String errMsg = "<b>constKeys と、constVals の個数が異なります。</b><br />" 164 final String errMsg = "<b>constKeys と、constVals の個数が異なります。</b><br>" // 7.0.1.0 (2018/10/15) 165 + " constKeys=[" + StringUtil.array2csv( constKeys ) + "]" 166 + " constVals=[" + StringUtil.array2csv( constVals ) + "]" ; 167 throw new HybsSystemException( errMsg ); 168 } 169 } 170 171 // funcKeys,funcVals の個数チェック 172 if( funcKeys != null ) { 173 if( funcVals == null || funcKeys.length != funcVals.length ) { 174// final String errMsg = "<b>funcKeys と、funcVals の個数が異なります。</b><br />" 175 final String errMsg = "<b>funcKeys と、funcVals の個数が異なります。</b><br>" // 7.0.1.0 (2018/10/15) 176 + " funcKeys=[" + StringUtil.array2csv( funcKeys ) + "]" 177 + " funcVals=[" + StringUtil.array2csv( funcVals ) + "]" ; 178 throw new HybsSystemException( errMsg ); 179 } 180 } 181 182 // asNames,orgNames の個数チェック 183 if( orgNames != null ) { 184 if( asNames == null || orgNames.length != asNames.length ) { 185// final String errMsg = "<b>orgNames と、asNames の個数が異なります。</b><br />" 186 final String errMsg = "<b>orgNames と、asNames の個数が異なります。</b><br>" // 7.0.1.0 (2018/10/15) 187 + " orgNames=[" + StringUtil.array2csv( orgNames ) + "]" 188 + " asNames=[" + StringUtil.array2csv( asNames ) + "]" ; 189 throw new HybsSystemException( errMsg ); 190 } 191 } 192 193 return SKIP_BODY ; // Body を評価しない 194 } 195 196 /** 197 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。 198 * 199 * @og.rev 4.3.7.0 (2009/06/01) 論理削除対応 200 * @og.rev 6.3.4.0 (2015/08/01) caseKey,caseVal,caseNN,caseNull,caseIf 属性対応 201 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 202 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 203 * 204 * @return 後続処理の指示 205 */ 206 @Override 207 public int doEndTag() { 208 debugPrint(); 209// if( !useTag() ) { return EVAL_PAGE ; } // 6.3.4.0 (2015/08/01) 210 if( !useTag() || sqlType == null ) { return EVAL_PAGE ; } // 7.2.9.1 (2020/10/23) sqlType == null の時は、何もしない。 211 212 final TableUpdateTag updateTag = (TableUpdateTag)findAncestorWithClass( this,TableUpdateTag.class ); 213 if( updateTag == null ) { 214 final String errMsg = "<b>" + getTagName() + "タグは、TableUpdateTagの内側(要素)に記述してください。</b>"; 215 throw new HybsSystemException( errMsg ); 216 } 217 218 final String upSqlType = updateTag.getSqlType() ; 219 220 // 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 221 final boolean useInsert = "INSERT,COPY,MERGE".contains( sqlType ) ; 222 final boolean useUpdate = "UPDATE,MODIFY,MERGE".contains( sqlType ) || "DELETE".contains( sqlType ) && logicalDelete ; 223 final boolean useDelete = "DELETE".equals( sqlType ) && !logicalDelete ; 224 225 if( upSqlType == null || upSqlType.equals( sqlType ) ) { 226 // 通常の names カラム配列を設定します。 227 if( names == null ) { names = updateTag.getNames(); } 228 final NamesData namesData = makeNamesData( names ); 229 230 if( useInsert ) { 231 updateTag.setQuery( "INSERT",getInsertSQL( namesData ) ); 232 } 233 if( useUpdate ) { 234 if( insertOnly ) { // 7.4.1.0 (2021/04/23) 235 updateTag.setQuery( "SELECT",getSelectSQL() ); 236 } 237 else { 238 updateTag.setQuery( "UPDATE",getUpdateSQL( namesData ) ); 239 } 240 } 241 if( useDelete ) { 242 updateTag.setQuery( "DELETE",getDeleteSQL() ); 243 } 244 } 245 246// // 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 247// if( upSqlType == null || upSqlType.equals( sqlType ) || ) { 248// String query = null; 249// if( "INSERT".equalsIgnoreCase( sqlType ) || "COPY".equalsIgnoreCase( sqlType ) ) { 250// query = getInsertSQL( namesData ); 251// } 252// else if( "UPDATE".equalsIgnoreCase( sqlType ) || "MODIFY".equalsIgnoreCase( sqlType ) 253// || ( "DELETE".equalsIgnoreCase( sqlType ) && logicalDelete ) ) { // 4.3.7.0 (2009/06/01) 254// query = getUpdateSQL( namesData ); 255// } 256// else if( "DELETE".equalsIgnoreCase( sqlType ) ) { 257// query = getDeleteSQL(); 258// } 259// 260// jspPrint( query ); 261// } 262 263 return EVAL_PAGE ; 264 } 265 266 /** 267 * タグリブオブジェクトをリリースします。 268 * キャッシュされて再利用されるので、フィールドの初期設定を行います。 269 * 270 * @og.rev 4.3.7.0 (2009/06/01) logicalDelete属性追加 271 * @og.rev 5.5.1.9 (2012/04/19) asNames、orgNames、funcKeys、funcVals属性追加 272 * @og.rev 5.5.8.5 (2012/11/27) 処理対象を特定するCSV形式のカラム名 273 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 274 */ 275 @Override 276 protected void release2() { 277 super.release2(); // 3.5.6.0 (2004/06/18) 追加(抜けていた) 278 sqlType = null; // INSERT,COPY,UPDATE,MODIFY,DELETE,MERGE 279 table = null; // 処理対象のテーブル名 280 names = null; // 処理対象のカラム名 281 omitNames = ",ROWID,ROWNUM,WRITABLE,"; // 処理対象外のカラム名 282 where = null; // 処理対象を特定するキー 283 whereNames = null; // 5.5.8.5 (2012/11/27) 処理対象を特定するCSV形式のカラム名 284 insertOnly = false; // 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 285 constKeys = null; // 処理カラム名の中の固定情報カラム名 286 constVals = null; // 処理カラム名の中の固定情報設定値 287 quotCheck = HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" ); 288 constObjKey = SYSTEM_ID; // 固定情報カラムの処理オブジェクトを特定するキー 289 logicalDelete = false; // 4.3.7.0 (2009/06/01) 290 funcKeys = null; // 5.5.1.9 (2012/04/19) 関数等を設定するカラム名 291 funcVals = null; // 5.5.1.9 (2012/04/19) 関数等の設定値 292 asNames = null; // 5.5.1.9 (2012/04/19) 別名を付けたカラム名(select A as B from TBL の B を指定) 293 orgNames = null; // 5.5.1.9 (2012/04/19) tableの実際のカラム名(select A as B from TBL の A を指定) 294 } 295 296 /** 297 * 【TAG】BODY部に書かれている SQLタイプを指定します。 298 * 299 * @og.tag 300 * SQLタイプは、INSERT,COPY,UPDATE,MODIFY,DELETE,MERGE の中から指定する 301 * 必要があります。これらは、内部に書かれるSQLの形式を指定するのに使用します。 302 * 内部処理は、DBTableModelの改廃コード(A,C,D)に対して使用される 303 * SQL を選択する場合の情報に使用されます。 304 * なお、COPY と MODIFY は、command で指定できる簡易機能として用意しています。 305 * 上位の TableUpdateTag の sqlType 属性 と同じsqlType 属性の場合のみ、SQL文を 306 * 合成・出力します。(上位のsqlTypeがnullの場合は、無条件実行します。) 307 * 指定のタイプが、異なる場合は、なにも処理を行いません。 308 * 309 * @param type SQLタイプ [INSERT/COPY/UPDATE/MODIFY/DELETE/MERGE] 310 */ 311 public void setSqlType( final String type ) { 312 sqlType = nval( getRequestParameter( type ),sqlType ); 313 if( sqlType != null && SQL_TYPE.indexOf( "|" + sqlType + "|" ) < 0 ) { 314 sqlType = null; 315 } 316 } 317 318 /** 319 * 【TAG】処理対象のテーブル名を指定します。 320 * 321 * @og.tag 322 * テーブル名を指定することで、sqlTypeに応じた QUERYを生成することが出来ます。 323 * 生成する場合のカラムを特定する場合は、names 属性で指定できます。 324 * また、WHERE条件は、where属性で指定します。 325 * 326 * @param tbl テーブル名 327 * @see #setNames( String ) 328 * @see #setWhere( String ) 329 * @see #setSqlType( String ) 330 */ 331 public void setTable( final String tbl ) { 332 table = nval( getRequestParameter( tbl ),table ); 333 } 334 335 /** 336 * 【TAG】処理対象のカラム名をCSV形式で複数指定します。 337 * 338 * @og.tag 339 * 生成するQUERYのカラム名をCSV形式(CSV)で複数指定します。 340 * 指定がない場合は、DBTableModel の全カラム(※)を使用して、QUERYを構築します。 341 * 一般に、テーブル結合してDBTableModelを構築した場合は、登録すべきカラムを 342 * 指定する必要があります。 343 * (※)正確には、DBTableModel の全カラムのうち、ROWID,ROWNUM,WRITABLE カラムは 344 * 無視します。 345 * 分解方法は、通常のパラメータ取得後に、CSV分解します。 346 * 347 * @og.rev 3.8.8.5 (2007/03/09) 通常のパラメータ取得後に、CSV分解に戻します。 348 * 349 * @param nms カラム名 (CSV形式) 350 * @see #setTable( String ) 351 * @see #setOmitNames( String ) 352 */ 353 public void setNames( final String nms ) { 354 names = StringUtil.csv2Array( getRequestParameter( nms ) ); 355 if( names.length == 0 ) { names = null; } 356 } 357 358 /** 359 * 【TAG】処理対象外のカラム名をCSV形式で複数指定します。 360 * 361 * @og.tag 362 * 生成するQUERYのカラム名に指定しないカラム名をCSV形式(CSV)で複数指定します。 363 * 指定がない場合は、DBTableModel の全カラム(※)を使用して、QUERYを構築します。 364 * テーブル結合などで、処理したくないカラム数の方が少ない場合に、names ですべてを 365 * 指定するより少ない記述ですみます。 366 * (※)正確には、DBTableModel の全カラムのうち、ROWID,ROWNUM,WRITABLE カラムは 367 * 無視します。 368 * 369 * @param nms カラム名 (CSV形式) 370 * @see #setTable( String ) 371 * @see #setNames( String ) 372 */ 373 public void setOmitNames( final String nms ) { 374 omitNames = omitNames + nval( getRequestParameter( nms ),"" ) + ","; 375 } 376 377 /** 378 * 【TAG】処理対象を特定するキー条件(where句)を指定します。 379 * 380 * @og.tag 381 * 生成するQUERYのwhere 句を指定します。通常の WHERE 句の書き方と同じで、 382 * DBTableModelの値を割り当てたい箇所に[カラム名] を記述します。 383 * 文字列の場合、設定値をセットするときに、シングルコーテーションを 384 * 使用しますが、[カラム名]で指定する場合は、その前後に、(')シングル 385 * コーテーションは、不要です。 386 * {@XXXX}変数を使用する場合は、パース時に固定文字に置き換えられる為、 387 * 文字列指定時の(')シングルコーテーションが必要になります。 388 * ※ 5.5.8.5 (2012/11/27) whereNames 属性と併用した場合は、where が、and を付けて、文字列結合されます。 389 * 例:FGJ='1' and CLM=[CLM] and SYSTEM_ID in ([SYSID],'**') and KBSAKU='{@KBSAKU}' 390 * 391 * @param wr 検索条件 (where句) 392 */ 393 public void setWhere( final String wr ) { 394 where = nval( getRequestParameter( wr ),where ); 395 } 396 397 /** 398 * 【TAG】処理対象を特定するキー条件(where句)をCSV形式で複数指定します。 399 * 400 * @og.tag 401 * 生成するQUERYのwhere 句を指定する方法として、複数のカラム名をCSV指定し、内部で 402 * KEY=[KEY] 文字列を作成します。 403 * ここでは、カラム名は、データベースのカラム名と同じで、かつ、DBTableModel にも 404 * 同じカラムのデータが存在していること、という条件付きとします。 405 * また、where 条件との併用を行いますが、こちらの条件が先に使用され、where 条件は、 406 * and を付けて、文字列結合されます。 407 * 例: CLM,SYSTEM_ID,KBSAKU ⇒ CLM=[CLM] and SYSTEM_ID=[SYSTEM_ID] and KBSAKU=[KBSAKU] 408 * 409 * @og.rev 5.5.8.5 (2012/11/27) 新規追加 410 * 411 * @param wrnm 検索条件カラム (where句)作成のためのカラム名(CSV形式) 412 */ 413 public void setWhereNames( final String wrnm ) { 414 whereNames = nval( getRequestParameter( wrnm ),whereNames ); 415 } 416 417 /** 418 *【 TAG】true に設定すると、sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない(初期値:false)。 419 * 420 * @og.tag 421 * true に設定すると、sqlType="MERGE" 時に、where条件で、検索して、存在しない場合は、追加します。 422 * 存在する場合は、何もしません。 423 * 何もしない(更新しない)ところが、通常のMERGEと異なる箇所です。 424 * 動作としては、UPDATEの代わりに、SELECT で判定します。 425 * 初期値は、false で、あれば更新、なければ追加処理を行います。 426 * 427 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 428 * 429 * @param flag "true" に設定すると、sqlType="MERGE" 時に、where条件で検索 430 */ 431 public void setInsertOnly( final String flag ) { 432 insertOnly = nval( getRequestParameter( flag ),insertOnly ); 433 } 434 435 /** 436 * 【TAG】設定値を固定値と置き換える対象となるカラム名をCSV形式で複数指定します。 437 * 438 * @og.tag 439 * names 属性のカラムや table 属性より、QUERYを作成して、DBTableModelの値を 440 * 割り当てる場合、DBTableModelの値ではなく、外部から指定した固定値を 441 * 割り当てたい場合に、そのカラム名をCSV形式(CSV)で複数指定します。 442 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。 443 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。 444 * 445 * @param keys 固定値カラム (CSV形式) 446 * @see #setConstVals( String ) 447 */ 448 public void setConstKeys( final String keys ) { 449 constKeys = getCSVParameter( keys ); 450 } 451 452 /** 453 * 【TAG】設定値を固定値と置き換える対象となる設定値をCSV形式で複数指定します。 454 * 455 * @og.tag 456 * names 属性のカラムや table 属性より、QUERYを作成して、DBTableModelの 457 * 値を割り当てる場合、DBTableModelの値ではなく、外部から指定した固定値を 458 * 割り当てたい場合に、そのカラム名に対応する設定値をCSV形式(CSV)で 459 * 複数指定します。ここで指定する設定値は、constKeys 属性と対応させます。 460 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。 461 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。 462 * 463 * @param vals 設定値(CSV形式) 464 * @see #setConstKeys( String ) 465 */ 466 public void setConstVals( final String vals ) { 467 constVals = getCSVParameter( vals ); 468 } 469 470 /** 471 * 【TAG】関数等を設定するカラム名をCSV形式で複数指定します。 472 * 473 * @og.tag 474 * constVals 属性で設定する値は、必ずシングルクオートが付与されます。 475 * その場合、関数などを設定したい場合でも、文字列として設定しようとします。 476 * ここで指定するカラム名(funcKeys)自身は、constKeys と同じ書式です。 477 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。 478 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。 479 * 480 * @og.rev 5.5.1.9 (2012/04/19) 新規追加 481 * 482 * @param keys 関数カラム (CSV形式) 483 * @see #setFuncVals( String ) 484 */ 485 public void setFuncKeys( final String keys ) { 486 funcKeys = getCSVParameter( keys ); 487 } 488 489 /** 490 * 【TAG】関数等の設定値をCSV形式で複数指定します。 491 * 492 * @og.tag 493 * funcKeys 属性に対応する 関数などの設定値を割り当てます。 494 * constVals 属性との違いは、funcVals の設定値は、そのままの形で、SQL文の 495 * 構築に使われます。 496 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。 497 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。 498 * 499 * @og.rev 5.5.1.9 (2012/04/19) 新規追加 500 * 501 * @param vals 関数設定値 (CSV形式) 502 * @see #setFuncKeys( String ) 503 */ 504 public void setFuncVals( final String vals ) { 505 funcVals = getCSVParameter( vals ); 506 } 507 508 /** 509 * 【TAG】別名を付けたカラム名(select A as B from TBL の B を指定)をCSV形式で複数指定します。 510 * 511 * @og.tag 512 * SELECT 文を記述したとき、別名を付けていたり、SELECTしたテーブルと別のテーブルに 513 * DBTableModelの値を書き込む場合、DBTableModel の持っているカラム名と、実際に 514 * 書き込むカラム名が異なります。そのようなケースに、元の別名カラムを指定します。 515 * orgNames属性の並び順と、asNames属性の並び順を合わせておく必要があります。 516 * このカラム名は、DBTableModel には持っているが、テーブル側には持っていない値 517 * なので、内部的に omitNames 属性に値を設定します。利用者は、omitNames に 518 * 書き込む必要はありません。 519 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。 520 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。 521 * 522 * @og.rev 5.5.1.9 (2012/04/19) 新規追加 523 * 524 * @param keys 別名カラム (CSV形式) 525 * @see #setOrgNames( String ) 526 */ 527 public void setAsNames( final String keys ) { 528 asNames = getCSVParameter( keys ); 529 } 530 531 /** 532 * 【TAG】tableの実際のカラム名(select A as B from TBL の A を指定)をCSV形式で複数指定します。 533 * 534 * @og.tag 535 * SELECT 文を記述したとき、別名を付けていたり、SELECTしたテーブルと別のテーブルに 536 * DBTableModelの値を書き込む場合、DBTableModel の持っているカラム名と、実際に 537 * 書き込むカラム名が異なります。そのようなケースに、テーブルの実カラムを指定します。 538 * orgNames属性の並び順と、asNames属性の並び順を合わせておく必要があります。 539 * このカラム名は、DBTableModel には持っていませんが、テーブル側には持っている値 540 * なので、このカラム名で、SQL文を構築します。 UPDATE TBL SET A=[B] WHERE … となります。 541 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。 542 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。 543 * 544 * @og.rev 5.5.1.9 (2012/04/19) 新規追加 545 * 546 * @param keys 実カラム (CSV形式) 547 * @see #setAsNames( String ) 548 */ 549 public void setOrgNames( final String keys ) { 550 orgNames = getCSVParameter( keys ); 551 } 552 553 /** 554 * 【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します 555 * (初期値:USE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。 556 * 557 * @og.tag 558 * SQLインジェクション対策の一つとして、暫定的ではありますが、SQLのパラメータに 559 * 渡す文字列にシングルクォート(') を許さない設定にすれば、ある程度は防止できます。 560 * 数字タイプの引数には、 or 5=5 などのシングルクォートを使用しないコードを埋めても、 561 * 数字チェックで検出可能です。文字タイプの場合は、必ず (')をはずして、 562 * ' or 'A' like 'A のような形式になる為、(')チェックだけでも有効です。 563 * (') が含まれていたエラーにする(true)/かノーチェックか(false)を指定します。 564 * (初期値:システム定数のUSE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。 565 * 566 * @param flag クォートチェック [true:する/それ以外:しない] 567 * @see org.opengion.hayabusa.common.SystemData#USE_SQL_INJECTION_CHECK 568 */ 569 public void setQuotCheck( final String flag ) { 570 quotCheck = nval( getRequestParameter( flag ),quotCheck ); 571 } 572 573 /** 574 * 【TAG】固定情報カラムの処理オブジェクトを特定するキーを設定します(初期値:SYSTEM_ID)。 575 * 576 * @og.tag 577 * 固定情報カラム をシステム単位にJavaクラスで管理できます。 578 * そのクラスオブジェクトは、org.opengion.hayabusa.db.DBConstValue インターフェースを 579 * 継承した、plugin クラスになります。 580 * そのクラスを特定するキーワードを指定します。 581 * 初期値は、SYSTEM_ID でシステム単位にクラスを作成します。 582 * もし、他のシステムと共通の場合は、継承だけさせることも可能です。 583 * 対応したDBConstValueクラスがプラグインとして存在しない場合は、 584 * システムリソースのDEFAULT_CONST_CLASSで指定されたクラスが利用されます。 585 * 固定情報カラムを使用しない場合は、constObjKey="" をセットしてください。 586 * 587 * 初期値は、SYSTEM_ID です。 588 * 589 * @og.rev 6.9.8.0 (2018/05/28) 固定情報カラムを使用しない場合は、constObjKey="" をセット。 590 * 591 * @param key 固定カラムキー 592 */ 593 public void setConstObjKey( final String key ) { 594// constObjKey = nval( getRequestParameter( key ),constObjKey ); 595 final String objKey = getRequestParameter( key ); 596 if( objKey != null ) { constObjKey = objKey; } 597 } 598 599 /** 600 * 【TAG】sqlType="DELETE"の場合に論理削除(UPDATE)を行うかどうかを指定します(初期値:false)。 601 * 602 * @og.tag 603 * sqlType="DELETE"の場合に論理削除(UPDATE)を行うかどうかを指定します。 604 * trueが指定された場合は、DELETE文ではなく、UPDATE文が発行されます。 605 * falseが指定された場合は、DELETE文が発行されます。 606 * さらに論理削除を行う場合、org.opengion.hayabusa.db.DBConstValue インターフェースに 607 * 定義されている、getLogicalDeleteKeys()及びgetLogicalDeleteValsを実装することで、 608 * 論理削除する際のフラグの更新方法を統一的に管理することが可能になります。 609 * 初期値は、false(物理削除する)です 610 * 611 * @param flag 論理削除可否 [true:UPDATE文/false:DELETE文] 612 */ 613 public void setLogicalDelete( final String flag ) { 614 logicalDelete = nval( getRequestParameter( flag ),logicalDelete ); 615 } 616 617 /** 618 * データをインサートする場合に使用するSQL文を作成します。 619 * 620 * @og.rev 4.1.2.1 (2008/03/17) DBConstValue による固定値セットを採用 621 * @og.rev 4.3.6.4 (2009/05/01) デフォルト設定をシステムリソースで設定可能にする 622 * @og.rev 5.3.4.0 (2011/04/01) DEFAULT_CONST_OBJの初期値変更(null→ゼロ文字列) 623 * @og.rev 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。 624 * 625 * @param namesData NamesDataオブジェクト 626 * 627 * @return インサートSQL 628 * @og.rtnNotNull 629 */ 630 private String getInsertSQL( final NamesData namesData ) { 631 String cls = HybsSystem.sys( "DBConstValue_" + constObjKey ) ; 632 633 // 4.3.6.4 (2009/05/01) 標準の追加 634 if( cls == null){ 635 cls = DEFAULT_CONST_OBJ; 636 } 637 638 if( cls != null && !cls.isEmpty() ) { 639 final DBConstValue constVal = HybsSystem.newInstance( cls ); 640 // 4.2.1.0 (2008/04/16) 初期化追加 641 constVal.init( table,getUser().getUserID(),getGUIInfoAttri( "KEY" ) ); 642 final String[] keys = constVal.getInsertKeys(); 643 final String[] vals = constVal.getInsertVals(); 644 namesData.add( keys,vals ); 645 } 646 647 final String[] nms = namesData.getNames(); 648 final String[] vls = namesData.getVals(); 649 650 // 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。 651 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ) 652 .append( "INSERT INTO " ).append( table ) 653 .append( " ( " ) 654 .append( String.join( "," , nms ) ) // 6.2.3.0 (2015/05/01) 655 .append( " ) VALUES ( " ) 656 .append( String.join( "," , vls ) ) // 6.2.3.0 (2015/05/01) 657 .append( " )" ); 658 659 return sql.toString(); 660 } 661 662 /** 663 * データをアップデートする場合に使用するSQL文を作成します。 664 * 665 * where と whereNames が同時に指定された場合は、whereNames が先に処理され 666 * where 条件は、and 結合されます。 667 * 668 * @og.rev 4.1.2.1 (2008/03/17) DBConstValue による固定値セットを採用 669 * @og.rev 4.3.6.4 (2009/05/01) デフォルト設定をシステムリソースで設定可能にする 670 * @og.rev 4.3.7.0 (2009/06/01) 論理削除対応 671 * @og.rev 5.3.7.0 (2011/07/01) DEFAULT_CONST_OBJの初期値変更(null→ゼロ文字列) 対応忘れ 672 * @og.rev 5.5.8.5 (2012/11/27) whereNames 対応 673 * 674 * @param namesData NamesDataオブジェクト 675 * 676 * @return アップデートSQL 677 * @og.rtnNotNull 678 */ 679 private String getUpdateSQL( final NamesData namesData ) { 680 String cls = HybsSystem.sys( "DBConstValue_" + constObjKey ) ; 681 682 // 4.3.6.4 (2009/05/01) 標準の追加 683 if( cls == null){ 684 cls = DEFAULT_CONST_OBJ; 685 } 686 687 if( cls != null && !cls.isEmpty() ) { // 5.3.7.0 (2011/07/01) 688 final DBConstValue constVal = HybsSystem.newInstance( cls ); 689 // 4.2.1.0 (2008/04/16) 初期化追加 690 constVal.init( table,getUser().getUserID(),getGUIInfoAttri( "KEY" ) ); 691 // 4.3.7.0 (2009/06/01) 論理削除対応 692 String[] keys = null; 693 String[] vals = null; 694 if( "DELETE".equalsIgnoreCase( sqlType ) ) { 695 keys = constVal.getLogicalDeleteKeys(); 696 vals = constVal.getLogicalDeleteVals(); 697 } 698 else { 699 keys = constVal.getUpdateKeys(); 700 vals = constVal.getUpdateVals(); 701 } 702 namesData.add( keys,vals ); 703 } 704 705 final String[] nms = namesData.getNames(); 706 final String[] vls = namesData.getVals(); 707 708 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ) 709 .append( "UPDATE " ).append( table ).append( " SET " ) 710 .append( nms[0] ).append( '=' ).append( vls[0] ); // 6.0.2.5 (2014/10/31) char を append する。 711 712 for( int i=1; i<nms.length; i++ ) { 713 sql.append( ',' ).append( nms[i] ).append( '=' ).append( vls[i] ); // 6.0.2.5 (2014/10/31) char を append する。 714 } 715 716 // 5.5.8.5 (2012/11/27) whereNames 対応 717 String whereAnd = " WHERE " ; 718 if( whereNames != null && whereNames.length() > 0 ) { 719 final String[] wnms = whereNames.split(","); 720 sql.append( whereAnd ).append( wnms[0] ).append( "=[" ).append( wnms[0] ).append( ']' ); // 6.0.2.5 (2014/10/31) char を append する。 721 722 for( int i=1; i<wnms.length; i++ ) { 723 sql.append( " AND " ).append( wnms[i] ).append( "=[" ).append( wnms[i] ).append( ']' ); // 6.0.2.5 (2014/10/31) char を append する。 724 } 725 whereAnd = " AND " ; // whereNames 優先。ここを通らなければ、初期値のまま、" WHERE " が使われる 726 } 727 728 // 5.5.8.5 (2012/11/27) whereNames 対応。whereNames が登録されていれば、AND で繋げる。 729 if( where != null && where.length() > 0 ) { 730 sql.append( whereAnd ).append( where ); 731 } 732 733 return sql.toString(); 734 } 735 736 /** 737 * データをデリートする場合に使用するSQL文を作成します。 738 * 739 * where と whereNames が同時に指定された場合は、whereNames が先に処理され 740 * where 条件は、and 結合されます。 741 * 742 * @og.rev 5.5.8.5 (2012/11/27) whereNames 対応 743 * 744 * @return デリートSQL 745 * @og.rtnNotNull 746 */ 747 private String getDeleteSQL() { 748 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ); 749 sql.append( "DELETE FROM " ).append( table ); 750 751 // 5.5.8.5 (2012/11/27) whereNames 対応 752 String whereAnd = " WHERE " ; 753 if( whereNames != null && whereNames.length() > 0 ) { 754 final String[] wnms = whereNames.split(","); 755 sql.append( whereAnd ).append( wnms[0] ).append( "=[" ).append( wnms[0] ).append( ']' ); // 6.0.2.5 (2014/10/31) char を append する。 756 757 for( int i=1; i<wnms.length; i++ ) { 758 sql.append( " AND " ).append( wnms[i] ).append( "=[" ).append( wnms[i] ).append( ']' ); // 6.0.2.5 (2014/10/31) char を append する。 759 } 760 whereAnd = " AND " ; // whereNames 優先。ここを通らなければ、初期値のまま、" WHERE " が使われる 761 } 762 763 // 5.5.8.5 (2012/11/27) whereNames 対応。whereNames が登録されていれば、AND で繋げる。 764 if( where != null && where.length() > 0 ) { 765 sql.append( whereAnd ).append( where ); 766 } 767 return sql.toString(); 768 } 769 770 /** 771 * データを検索する場合に使用するSQL文を作成します。 772 * 773 * where と whereNames が同時に指定された場合は、whereNames が先に処理され 774 * where 条件は、and 結合されます。 775 * 776 * これは、sqlType="MERGE" 時に、insertOnly="true" が指定された時に、呼ばれます。 777 * 778 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 779 * 780 * @return 検索SQL 781 * @og.rtnNotNull 782 */ 783 private String getSelectSQL() { 784 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ); 785 sql.append( "SELECT count(*) FROM " ).append( table ); 786 787 // 5.5.8.5 (2012/11/27) whereNames 対応 788 String whereAnd = " WHERE " ; 789 if( whereNames != null && whereNames.length() > 0 ) { 790 final String[] wnms = whereNames.split(","); 791 sql.append( whereAnd ).append( wnms[0] ).append( "=[" ).append( wnms[0] ).append( ']' ); // 6.0.2.5 (2014/10/31) char を append する。 792 793 for( int i=1; i<wnms.length; i++ ) { 794 sql.append( " AND " ).append( wnms[i] ).append( "=[" ).append( wnms[i] ).append( ']' ); // 6.0.2.5 (2014/10/31) char を append する。 795 } 796 whereAnd = " AND " ; // whereNames 優先。ここを通らなければ、初期値のまま、" WHERE " が使われる 797 } 798 799 // 5.5.8.5 (2012/11/27) whereNames 対応。whereNames が登録されていれば、AND で繋げる。 800 if( where != null && where.length() > 0 ) { 801 sql.append( whereAnd ).append( where ); 802 } 803 return sql.toString(); 804 } 805 806 /** 807 * names,constKeys,omitNames から、必要なキー情報と、属性情報を持った NamesData を作成します。 808 * 809 * @og.rev 4.1.2.1 (2008/03/17) 固定値の constVals の前後に、"'" を入れる。 810 * @og.rev 5.5.1.9 (2012/04/19) asNames、orgNames、funcKeys、funcVals属性追加 811 * 812 * @param nms カラム名配列(可変長引数) 813 * 814 * @return 属性情報を持ったNamesData 815 */ 816 private NamesData makeNamesData( final String... nms ) { 817 final NamesData namesData = new NamesData(); 818 819 // 5.5.1.9 (2012/04/19) omitNames に、asNames配列の値を設定しておきます。 820 if( asNames != null ) { 821 for( int i=0; i<asNames.length; i++ ) { 822 if( asNames[i] != null && asNames[i].length() > 0 ) { 823 omitNames = omitNames + asNames[i] + ","; 824 } 825 } 826 } 827 828 // names で指定されたカラム名 829 for( int i=0; i<nms.length; i++ ) { 830 final String nm = nms[i]; 831 if( nm != null && nm.length() > 0 && omitNames.indexOf( "," + nm + "," ) < 0 ) { 832 namesData.add( nm,"[" + nm + "]" ) ; 833 } 834 } 835 836 // 固定値の constKeys カラム配列を設定します。 837 if( constKeys != null && constKeys.length > 0 ) { 838 for( int j=0; j<constKeys.length; j++ ) { 839 final String nm = constKeys[j]; 840 if( nm != null && nm.length() > 0 ) { 841 namesData.add( nm,"'" + constVals[j] + "'" ) ; // constVals は、シングルクオートで囲います。 842 } 843 } 844 } 845 846 // 関数値の funcKeys カラム配列を設定します。 847 if( funcKeys != null && funcKeys.length > 0 ) { 848 for( int j=0; j<funcKeys.length; j++ ) { 849 final String nm = funcKeys[j]; 850 if( nm != null && nm.length() > 0 ) { 851 namesData.add( nm, funcVals[j] ) ; // funcVals は、シングルクオートで囲いません。 852 } 853 } 854 } 855 856 // 別名の asNames,orgNames カラム配列を設定します。 857 if( orgNames != null && orgNames.length > 0 ) { 858 for( int j=0; j<orgNames.length; j++ ) { 859 final String onm = orgNames[j]; 860 if( onm != null && onm.length() > 0 ) { 861 namesData.add( onm,"[" + asNames[j] + "]" ) ; 862 } 863 } 864 } 865 866 return namesData ; 867 } 868 869 /** 870 * 内部データを受け渡す為の、簡易クラスです。 871 * 更新するカラム名と値のセット配列を管理しています。 872 * 873 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。 874 * ※ classのfinal化と変数のprivate化、メソッドの修飾子なし(パッケージプライベート)化を行います。 875 * @og.rev 6.4.1.2 (2016/01/22) nameの値を、大文字小文字の区別をなくすために、常に大文字で登録します。 876 */ 877 private static final class NamesData { 878 private final Map<String,String> nameMap = new LinkedHashMap<>() ; 879 880 /** 881 * キーと値のセットを追加します。 882 * 883 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。 884 * @og.rev 6.4.1.2 (2016/01/22) nameの値を、大文字小文字の区別をなくすために、常に大文字で登録します。 885 * 886 * @param nm キー(大文字のみ。内部で変換しておきます。) 887 * @param val 値 888 */ 889 /* default */ void add( final String nm,final String val ) { 890 nameMap.put( nm.toUpperCase(Locale.JAPAN),val ); 891 } 892 893 /** 894 * キー配列と対応する、値配列のセットを追加します。 895 * 896 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。 897 * 898 * @param nms キー配列 899 * @param vals 値配列 900 */ 901 /* default */ void add( final String[] nms,final String[] vals ) { 902 if( nms != null ) { 903 for( int i=0; i<nms.length; i++ ) { 904 nameMap.put( nms[i].toUpperCase(Locale.JAPAN),vals[i] ); 905 } 906 } 907 } 908 909 /** 910 * キー配列を返します。 911 * 912 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。 913 * 914 * @return キー配列 915 * @og.rtnNotNull 916 */ 917 /* default */ String[] getNames() { 918 return nameMap.keySet().toArray( new String[nameMap.size()] ); 919 } 920 921 /** 922 * 値配列を返します。 923 * 924 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。 925 * 926 * @return 値配列 927 * @og.rtnNotNull 928 */ 929 /* default */ String[] getVals() { 930 return nameMap.values().toArray( new String[nameMap.size()] ); 931 } 932 } 933 934 /** 935 * このオブジェクトの文字列表現を返します。 936 * 基本的にデバッグ目的に使用します。 937 * 938 * @return このクラスの文字列表現 939 * @og.rtnNotNull 940 */ 941 @Override 942 public String toString() { 943 return ToString.title( this.getClass().getName() ) 944 .println( "VERSION" ,VERSION ) 945 .println( "sqlType" ,sqlType ) 946 .println( "table" ,table ) 947 .println( "names" ,names ) 948 .println( "omitNames" ,omitNames ) 949 .println( "where" ,where ) 950 .println( "whereNames" ,whereNames ) 951 .println( "constKeys" ,constKeys ) 952 .println( "constVals" ,constVals ) 953 .println( "logicalDelete" ,logicalDelete ) 954 .fixForm().toString() ; 955 } 956}