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.db;
017
018import java.io.IOException;
019import java.io.Reader;
020import java.sql.Struct;                                                                                         // 6.3.3.0 (2015/07/25)
021import java.sql.Clob;
022import java.sql.ResultSet;
023import java.sql.ResultSetMetaData;
024import java.sql.SQLException;
025import java.sql.Types;
026import java.sql.Date;
027import java.sql.Timestamp;
028import java.util.Locale;
029import java.util.List;                                                                                          // 6.3.3.0 (2015/07/25)
030import java.util.ArrayList;                                                                                     // 6.3.3.0 (2015/07/25)
031import java.util.Map;                                                                                           // 6.8.6.0 (2018/01/19)
032import java.util.HashMap;                                                                                       // 6.8.6.0 (2018/01/19)
033
034import oracle.jdbc.OracleStruct;                                                                        // 6.3.8.0 (2015/09/11)
035import oracle.jdbc.OracleTypeMetaData;                                                          // 6.3.8.0 (2015/09/11)
036
037import org.opengion.fukurou.system.OgRuntimeException ;                         // 6.4.2.0 (2016/01/29)
038import org.opengion.fukurou.system.DateSet;                                                     // 6.4.2.0 (2016/01/29)
039import org.opengion.fukurou.system.Closer;
040import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
041
042/**
043 * ResultSet のデータ処理をまとめたクラスです。
044 * ここでは、ResultSetMetaData から、カラム数、カラム名(NAME列)、
045 * Type属性を取得し、ResultSet で、値を求める時に、Object型の
046 * 処理を行います。
047 * Object型としては、CLOB、ROWID、TIMESTAMP 型のみ取り扱っています。
048 * STRUCTタイプもサポートしますが、1レベルのみとします。(6.3.3.0 (2015/07/25))
049 *
050 * @og.rev 6.0.4.0 (2014/11/28) 新規作成
051 * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
052 * @og.group DB制御
053 *
054 * @version  6.0
055 * @author   Kazuhiko Hasegawa
056 * @since    JDK6.0,
057 */
058// 8.5.5.1 (2024/02/29) spotbugs CT_CONSTRUCTOR_THROW(コンストラクタで、Excweptionを出さない) class を final にすれば、警告は消える。
059// public class ResultSetValue implements AutoCloseable {
060public final class ResultSetValue implements AutoCloseable {
061        // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseUnderscoresInNumericLiterals
062        private static final int BUFFER_MIDDLE = 10_000;                // 6.3.3.0 (2015/07/25)
063
064        private final ResultSet resultSet ;                     // 内部で管理する ResultSet オブジェクト
065        private final List<ColumnInfo> clmInfos ;
066
067        private boolean skipNext  ;                                     // STRUCT 使用時に、next() してデータ構造を取得するため。
068        private boolean firstNext ;                                     // STRUCT 使用時に、next() してデータ構造を取得するため。
069
070        /**
071         * ResultSet を引数にとるコンストラクタ
072         *
073         * ここで、カラムサイズ、カラム名、java.sql.Types の定数定義 を取得します。
074         * STRUCTタイプもサポートしますが、1レベルのみとします。
075         * つまり、Object型のカラムに、Object型を定義した場合、ここでは取り出すことができません。
076         * また、Object型は、継承関係を構築できるため、個々のオブジェクトの要素数は異なります。
077         * 一番最初のレコードのオブジェクト数を元に、算出しますので、ご注意ください。
078         *
079         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
080         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
081         * @og.rev 6.3.8.0 (2015/09/11) Oracle Database 12cリリース1 (12.1)以降、StructDescriptor は非推奨
082         *
083         * @param       res 内部で管理するResultSetオブジェクト
084         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
085         */
086        public ResultSetValue( final ResultSet res ) throws SQLException {
087                resultSet = res;
088
089                final ResultSetMetaData metaData  = resultSet.getMetaData();
090                final int clmSize = metaData.getColumnCount();
091
092                clmInfos = new ArrayList<>();
093
094                for( int i=0; i<clmSize; i++ ) {
095                        final int clmNo = i+1;
096                        final int type = metaData.getColumnType( clmNo );
097                        final String  name = metaData.getColumnLabel( clmNo ).toUpperCase(Locale.JAPAN) ;
098                        if( type == Types.STRUCT ) {
099                                if( !skipNext ) {                                               // オブジェクト型を取得する為、データを取る必要がある。
100                                        skipNext  = true;
101                                        firstNext = resultSet.next();           // 初めての next() の結果を保持(falseなら、データなし)
102                                }
103                                if( firstNext ) {
104                                        // 最初のオブジェクトのタイプを基準にする。
105                                        final Object obj = resultSet.getObject( clmNo );
106                                        if( obj != null ) {
107                                                // 6.3.8.0 (2015/09/11) Oracle Database 12cリリース1 (12.1)以降、StructDescriptor は非推奨
108                                                final OracleTypeMetaData omd  = ((OracleStruct)obj).getOracleMetaData();
109                                                final ResultSetMetaData md    = ((OracleTypeMetaData.Struct)omd).getMetaData();
110
111                                                final int mdsize = md.getColumnCount();
112                                                for( int j=0; j<mdsize; j++ ) {
113                                                        final int objNo = j+1;
114                                                        // カラム名.オブジェクトカラム名
115                                                        final String  name2   = name + '.' + md.getColumnLabel(objNo).toUpperCase(Locale.JAPAN);
116                                                        final int     type2   = md.getColumnType( objNo );
117                                                        final int     size2   = md.getColumnDisplaySize( objNo );
118                                                        final boolean isWrit2 = md.isWritable( objNo );
119                                                        clmInfos.add( new ColumnInfo( name2,type2,size2,isWrit2,clmNo,j ) );    // ※ objNo でなく、「j」
120                                                }
121                                        }
122                                }
123                        }
124                        else {
125                                final int     size   = metaData.getColumnDisplaySize( clmNo );
126                                final boolean isWrit = metaData.isWritable( clmNo );
127                                clmInfos.add( new ColumnInfo( name,type,size,isWrit,clmNo,-1 ) );                       // ※ objNo でなく、「-1」
128                        }
129                }
130        }
131
132        /**
133         * ResultSetMetaData で求めた、カラム数を返します。
134         *
135         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
136         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
137         *
138         * @return  カラム数(データの列数)
139         */
140        public int getColumnCount() {
141                return clmInfos.size();
142        }
143
144        /**
145         * カラム名配列を返します。
146         *
147         * 配列は、0から始まり、カラム数-1 までの文字型配列に設定されます。
148         * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した
149         * 大文字が返されます。
150         *
151         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
152         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
153         *
154         * @return      カラム名配列
155         * @og.rtnNotNull
156         */
157        public String[] getNames() {
158                return clmInfos.stream().map( info -> info.getName() ).toArray( String[]::new );
159        }
160
161        /**
162         * カラム名配列に対応する カラム番号配列を返します。
163         *
164         * 引数のカラム名配列が、null の場合は、長さゼロの配列を返します。
165         * 指定のカラム名が存在しなかった場合は、アドレスに -1 がセットされます。
166         *
167         * @og.rev 6.8.6.0 (2018/01/19) 新規追加
168         *
169         * @param       clmNms  値が参照されるカラム名配列(可変長引数)
170         *
171         * @return  指定されたセルのカラム番号配列。
172         * @og.rtnNotNull
173         */
174        public int[] getColumnNos( final String[] clmNms ) {
175                return getColumnNos( clmNms, false );
176        }
177
178        /**
179         * カラム名配列に対応する カラム番号配列を返します。
180         *
181         * 引数のカラム名配列が、null の場合は、長さゼロの配列を返します。
182         *
183         * 指定のカラム名が存在しなかった場合の動作は、useThrow 引数で決まります。
184         * useThrow が、true の場合は、カラム名が存在しない場合は、 HybsSystemException を
185         * throw します。useThrow が、false の場合は、カラム名が存在しない場合は、
186         * アドレスに -1 がセットされます。
187         *
188         * @og.rev 6.8.6.0 (2018/01/19) 新規追加
189         * @og.rev 6.9.0.2 (2018/02/13) カラム名が、"*" の場合の対応
190         *
191         * @param       clmNms  値が参照されるカラム名配列(可変長引数)
192         * @param   useThrow    カラム名が存在しない場合に、Exception を throw するかどうか
193         *
194         * @return  指定されたセルのカラム番号配列。
195         * @og.rtnNotNull
196         */
197        public int[] getColumnNos( final String[] clmNms,final boolean useThrow ) {
198                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
199                final int[] clmNos;
200
201                // 6.9.0.2 (2018/02/13) カラム名が、"*" の場合の対応
202                if( clmNms != null && clmNms.length == 1 && "*".equals( clmNms[0] ) ) {
203//                      final int[] clmNos = new int[clmInfos.size()];
204                        clmNos = new int[clmInfos.size()];
205                        for( int i=0; i<clmInfos.size(); i++ ) {
206//                              clmNos[i] = 1;
207                                clmNos[i] = i;                          // 8.5.5.1 (2024/02/29) バグ…ですよね?
208                        }
209//                      return clmNos;
210                }
211                else {
212                        // 名前のMapを作成します。
213                        final Map<String, Integer> nameMap = new HashMap<>();
214                        for( int i=0; i<clmInfos.size(); i++ ) {
215                                // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing
216        //                      nameMap.put( clmInfos.get(i).getName() , Integer.valueOf(i) );          // 名前とカラム番号
217                                nameMap.put( clmInfos.get(i).getName() , i );                                           // 名前とカラム番号
218                        }
219
220                        final int size = clmNms == null ? 0 : clmNms.length ;
221
222//                      final int[] clmNos = new int[size];
223                        clmNos = new int[size];
224                        for( int j=0; j<size; j++ ) {
225                                final Integer ad = nameMap.get( clmNms[j] );
226                                if( ad == null ) {                      // カラム名が存在しない。
227                                        if( useThrow ) {
228                                                final String errMsg = "指定のカラム名が存在しません。 カラム名[" + j + "]=[" + clmNms[j]   + "]" + CR
229                                                                                                + " 引数カラム配列=[" + String.join( "," , clmNms )     + "]"  + CR
230                                                                                                + " 内部カラム列=["   + String.join( "," , nameMap.keySet() ) + "]";
231                                                throw new OgRuntimeException( errMsg );
232                                        }
233                                        clmNos[j] = -1;
234                                }
235                                else {
236                                        // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing
237        //                              clmNos[j] = ad.intValue();
238                                        clmNos[j] = ad;
239                                }
240                        }
241                }
242
243                return clmNos;
244        }
245
246        /**
247         * 指定のカラム番号のカラム名を返します。
248         *
249         * カラム名を取得する、カラム番号は、0から始まり、カラム数-1 までの数字で指定します。
250         * データベース上の、1から始まる番号とは、異なります。
251         * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した
252         * 大文字が返されます。
253         *
254         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
255         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
256         *
257         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
258         * @return  指定のカラム番号のカラム名
259         */
260        public String getColumnName( final int clmNo ) {
261                return clmInfos.get( clmNo ).name ;
262        }
263
264        /**
265         * 指定のカラム番号のサイズを返します。
266         *
267         * カラムのサイズは、ResultSetMetaData#getColumnDisplaySize(int) の値です。
268         *
269         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
270         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
271         *
272         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
273         * @return      指定のカラム番号のサイズ
274         */
275        public int getColumnDisplaySize( final int clmNo ) {
276                return clmInfos.get( clmNo ).size ;
277        }
278
279        /**
280         * 指定の書き込み可能かどうかを返します。
281         *
282         * カラムの書き込み可能かどうかは、ResultSetMetaData#isWritable(int) の値です。
283         *
284         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
285         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
286         *
287         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
288         * @return      書き込み可能かどうか
289         */
290        public boolean isWritable( final int clmNo ) {
291                return clmInfos.get( clmNo ).isWrit ;
292        }
293
294        /**
295         * カーソルを現在の位置から順方向に1行移動します。
296         *
297         * ResultSet#next() を呼び出しています。
298         * 結果は、すべて文字列に変換されて格納されます。
299         *
300         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
301         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
302         *
303         * @return  新しい現在の行が有効である場合はtrue、行がそれ以上存在しない場合はfalse
304         * @see         java.sql.ResultSet#next()
305         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合、またはこのメソッドがクローズされた結果セットで呼び出された場合
306         */
307        public boolean next() throws SQLException {
308                if( skipNext ) { skipNext = false; return firstNext; }          // STRUCTタイプ取得時に、一度 next() している。
309                return resultSet.next();
310        }
311
312        /**
313         * 現在のカーソル位置にあるレコードのカラム番号のデータを取得します。
314         *
315         * ResultSet#getObject( clmNo+1 ) を呼び出しています。
316         * 引数のカラム番号は、0から始まりますが、ResultSet のカラム順は、1から始まります。
317         * 指定は、0から始まるカラム番号です。
318         * 結果は、すべて文字列に変換されて返されます。
319         * また、null オブジェクトの場合も、ゼロ文字列に変換されて返されます。
320         *
321         * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getValue( ResultSet , int , int ) から移動
322         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
323         * @og.rev 8.5.5.1 (2024/02/29) switch式の使用
324         *
325         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
326         * @return  現在行のカラム番号のデータ(文字列)
327         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
328         */
329        public String getValue( final int clmNo ) throws SQLException {
330                final ColumnInfo clmInfo = clmInfos.get( clmNo ) ;      // 内部カラム番号に対応したObject
331                final int dbClmNo = clmInfo.clmNo ;                                     // データベース上のカラム番号(+1済み)
332
333                final String val ;
334                final Object obj = resultSet.getObject( dbClmNo );
335
336                if( obj == null ) {
337                        val = "";
338                }
339                else if( clmInfo.isStruct ) {
340                        final Object[] attrs = ((Struct)obj).getAttributes();
341                        final int no = clmInfo.objNo;
342                        val = no < attrs.length ? String.valueOf( attrs[no] ) : "" ;    // 配列オーバーする場合は、""(ゼロ文字列)
343        //              for( Object obj2 : attrs ) { System.out.println( obj2 ); }
344                }
345                else if( clmInfo.isObject ) {
346                        // 8.5.5.1 (2024/02/29) switch式の使用
347//                      switch( clmInfo.type ) {
348//                              case Types.CLOB :               val = getClobData( (Clob)obj ) ;
349//                                                                              break;
350//                              case Types.ROWID:               val = resultSet.getString( dbClmNo );
351//                                                                              break;
352//                              case Types.TIMESTAMP :  val = DateSet.getDate( ((Timestamp)obj).getTime() , "yyyyMMddHHmmss" );
353//                                                                              break;
354//                              default :                               val = String.valueOf( obj );
355//                                                                              break;
356//                      }
357                        val = switch( clmInfo.type ) {
358                                case Types.CLOB                 -> getClobData( (Clob)obj ) ;
359                                case Types.ROWID                -> resultSet.getString( dbClmNo );
360                                case Types.TIMESTAMP    -> DateSet.getDate( ((Timestamp)obj).getTime() , "yyyyMMddHHmmss" );
361                                default                                 -> String.valueOf( obj );
362                        };
363                }
364                else {
365                        val = String.valueOf( obj );
366                }
367
368                return val ;
369        }
370
371        /**
372         * 現在のカーソル位置にあるレコードの全カラムデータを取得します。
373         *
374         * #getValue( clmNo ) を、0から、カラム数-1 まで呼び出して求めた文字列配列を返します。
375         *
376         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
377         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
378         *
379         * @return  現在行の全カラムデータの文字列配列
380         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
381         */
382        public String[] getValues() throws SQLException {
383                final String[] vals = new String[clmInfos.size()];
384
385                for( int i=0; i<vals.length; i++ ) {
386                        vals[i] = getValue( i );
387                }
388
389                return vals ;
390        }
391
392        /**
393         * タイプに応じて変換された、Numberオブジェクトを返します。
394         *
395         * 条件に当てはまらない場合は、null を返します。
396         * org.opengion.hayabusa.io.HybsJDBCCategoryDataset2 から移動してきました。
397         * これは、検索結果をグラフ化する為の 値を取得する為のメソッドですので、
398         * 数値に変換できない場合は、エラーになります。
399         *
400         * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getNumber( int , Object ) から移動
401         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
402         * @og.rev 8.5.5.1 (2024/02/29) switch式の使用
403         *
404         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
405         * @return      Numberオブジェクト(条件に当てはまらない場合は、null)
406         * @see         java.sql.Types
407         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
408         * @throws      RuntimeException 数字変換できなかった場合。
409         */
410        public Number getNumber( final int clmNo ) throws SQLException {
411                final ColumnInfo clmInfo = clmInfos.get( clmNo ) ;      // 内部カラム番号に対応したObject
412                final int dbClmNo = clmInfo.clmNo ;                                     // データベース上のカラム番号(+1済み)
413
414                Number value = null;
415
416                Object obj = resultSet.getObject( dbClmNo );
417                if( obj != null ) {
418                        if( clmInfo.isStruct ) {
419                                final Object[] attrs = ((Struct)obj).getAttributes();
420                                final int no = clmInfo.objNo;
421                                obj = no < attrs.length ? attrs[no] : null ;    // 配列オーバーする場合は、null
422                                if( obj == null ) { return value; }                             // 配列外 or 取出した結果が null の場合、処理を中止。
423                        }
424
425                        // 8.5.5.1 (2024/02/29) switch式の使用
426//      //              switch( type[clmNo] ) {
427//                      switch( clmInfo.type ) {
428//                              case Types.TINYINT:
429//                              case Types.SMALLINT:
430//                              case Types.INTEGER:
431//                              case Types.BIGINT:
432//                              case Types.FLOAT:
433//                              case Types.DOUBLE:
434//                              case Types.DECIMAL:
435//                              case Types.NUMERIC:
436//                              case Types.REAL: {
437//                                      value = (Number)obj;
438//                                      break;
439//                              }
440//                              case Types.DATE:
441//                              case Types.TIME:  {
442//                                      // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing
443////                                    value = Long.valueOf( ((Date)obj).getTime() );
444//                                      value = ((Date)obj).getTime();
445//                                      break;
446//                              }
447//                              // 5.6.2.1 (2013/03/08) Types.DATE と Types.TIMESTAMP で処理を分けます。
448//                              case Types.TIMESTAMP: {
449//                                      // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing
450////                                    value = Long.valueOf( ((Timestamp)obj).getTime() );
451//                                      value = ((Timestamp)obj).getTime();
452//                                      break;
453//                              }
454//                              case Types.CHAR:
455//                              case Types.VARCHAR:
456//                              case Types.LONGVARCHAR: {
457//                                      final String str = (String)obj;
458//                                      try {
459//                                              value = Double.valueOf(str);
460//                                      }
461//                                      catch( final NumberFormatException ex) {
462//                                              final String errMsg = "数字変換できませんでした。in=" + str
463//                                                                              + CR + ex.getMessage() ;
464//                                              throw new OgRuntimeException( errMsg,ex );
465//                                              // suppress (value defaults to null)
466//                                      }
467//                                      break;
468//                              }
469//                              default:
470//                                      // not a value, can't use it (defaults to null)
471//                                      break;
472//                      }
473                        value = switch( clmInfo.type ) {
474                                case Types.TINYINT, Types.SMALLINT, Types.INTEGER, Types.BIGINT, Types.FLOAT,
475                                         Types.DOUBLE, Types.DECIMAL, Types.NUMERIC, Types.REAL -> (Number)obj;
476                                        // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing
477                                case Types.DATE, Types.TIME -> ((Date)obj).getTime();
478                                // 5.6.2.1 (2013/03/08) Types.DATE と Types.TIMESTAMP で処理を分けます。
479                                case Types.TIMESTAMP -> ((Timestamp)obj).getTime();                     // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing
480                                case Types.CHAR, Types.VARCHAR, Types.LONGVARCHAR -> {
481                                        final String str = (String)obj;
482                                        try {
483                                                yield Double.valueOf(str);
484                                        }
485                                        catch( final NumberFormatException ex) {
486                                                final String errMsg = "数字変換できませんでした。in=" + str
487                                                                                + CR + ex.getMessage() ;
488                                                throw new OgRuntimeException( errMsg,ex );
489                                                // suppress (value defaults to null)
490                                        }
491                                }
492                                default -> value;                       // 変更なしなので、自分自身を返す。
493                                        // not a value, can't use it (defaults to null)
494                        };
495                }
496                return value;
497        }
498
499        /**
500         * カラムのタイプを表現する文字列値を返します。
501         *
502         * この文字列を用いて、CCSファイルでタイプごとの表示方法を
503         * 指定することができます。
504         * 現時点では、VARCHAR2,LONG,NUMBER,DATE,CLOB,NONE のどれかにあてはめます。
505         *
506         * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#type2ClassName( int ) から移動
507         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
508         * @og.rev 8.5.5.1 (2024/02/29) switch式の使用
509         *
510         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
511         * @return      カラムのタイプを表現する文字列値
512         * @see         java.sql.Types
513         */
514        public String getClassName( final int clmNo ) {
515                // 8.5.5.1 (2024/02/29) switch式の使用
516//              final String rtn ;
517//
518//              switch( clmInfos.get( clmNo ).type ) {
519//                      case Types.CHAR:
520//                      case Types.VARCHAR:
521//                      case Types.BIT:
522//                              rtn = "VARCHAR2"; break;
523//                      case Types.LONGVARCHAR:
524//                              rtn = "LONG"; break;
525//                      case Types.TINYINT:
526//                      case Types.SMALLINT:
527//                      case Types.INTEGER:
528//                      case Types.NUMERIC:
529//                      case Types.BIGINT:
530//                      case Types.FLOAT:
531//                      case Types.DOUBLE:
532//                      case Types.REAL:
533//                      case Types.DECIMAL:
534//                              rtn = "NUMBER"; break;
535//                      case Types.DATE:
536//                      case Types.TIME:
537//                      case Types.TIMESTAMP:
538//                              rtn = "DATE"; break;
539//                      case Types.CLOB:
540//                              rtn = "CLOB"; break;
541//                      case Types.STRUCT:                                              // 6.3.3.0 (2015/07/25) 内部分解されない2レベル以上の場合のみ
542//                              rtn = "STRUCT"; break;
543//                      default:
544//                              rtn = "NONE"; break;
545//              }
546//
547//              return rtn;
548
549                return switch( clmInfos.get( clmNo ).type ) {
550                        case Types.CHAR, Types.VARCHAR, Types.BIT                                                               -> "VARCHAR2";
551                        case Types.LONGVARCHAR                                                                                                  -> "LONG";
552                        case Types.TINYINT, Types.SMALLINT, Types.INTEGER, Types.NUMERIC,
553                                 Types.BIGINT, Types.FLOAT, Types.DOUBLE, Types.REAL, Types.DECIMAL     -> "NUMBER";
554                        case Types.DATE, Types.TIME, Types.TIMESTAMP                                                    -> "DATE";
555                        case Types.CLOB                                                                                                                 -> "CLOB";
556                        // 6.3.3.0 (2015/07/25) 内部分解されない2レベル以上の場合のみ
557                        case Types.STRUCT                                                                                                               -> "STRUCT";
558                        default                                                                                                                                 -> "NONE";
559                };
560        }
561
562        /**
563         * try-with-resourcesブロックで、自動的に呼ばれる AutoCloseable の実装。
564         *
565         * コンストラクタで渡された ResultSet を close() します。
566         *
567         * @og.rev 6.4.2.1 (2016/02/05) 新規作成。try-with-resourcesブロックで、自動的に呼ばれる AutoCloseable の実装。
568         *
569         * @see         java.lang.AutoCloseable#close()
570         */
571        @Override       // AutoCloseable
572        public void close() {
573                Closer.resultClose( resultSet );
574        }
575
576        /**
577         * Clob オブジェクトから文字列を取り出します。
578         *
579         * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getClobData( Clob ) から移動
580         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
581         *
582         * @param       clobData Clobオブジェクト
583         * @return      Clobオブジェクトから取り出した文字列
584         * @throws      SQLException データベースアクセスエラー
585         * @throws      RuntimeException 入出力エラーが発生した場合
586         * @og.rtnNotNull
587         */
588        private String getClobData( final Clob clobData ) throws SQLException {
589                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
590                // ※ 引数の clobData は、null で来ない事を、呼び出しもとで判定している。
591//              if( clobData == null ) { return ""; }
592
593                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
594
595                // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
596//              Reader reader = null;
597//              try {
598//                      reader = clobData.getCharacterStream();
599                try ( Reader reader = clobData.getCharacterStream() ) {
600                        final char[] ch = new char[BUFFER_MIDDLE];                      // char配列とBuilderの初期値は無関係。
601                        int  len ;
602                        while( (len = reader.read( ch )) >= 0 ) {
603                                buf.append( ch,0,len );
604                        }
605                }
606                catch( final IOException ex ) {
607                        final String errMsg = "CLOBデータの読み込みに失敗しました。"
608                                                                + ex.getMessage() ;
609                        throw new OgRuntimeException( errMsg,ex );
610                }
611//              finally {
612//                      Closer.ioClose( reader );
613//              }
614                return buf.toString();
615        }
616
617        /**
618         * 各種カラム属性の管理を、クラスで行うようにします。
619         *
620         * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
621         *
622         */
623        private static final class ColumnInfo {
624                private final String    name ;                          // カラム名(ResultSetMetaData#getColumnLabel(int) の toUpperCase)
625                private final int               type ;                          // java.sql.Types の定数定義
626                private final int               size ;                          // カラムサイズ(ResultSetMetaData#getColumnDisplaySize(int))
627                private final boolean   isWrit ;                        // 書き込み許可(ResultSetMetaData#isWritable(int))
628                private final int               clmNo ;                         // ResultSet での元のカラムNo( 1から始まる番号 )
629                private final int               objNo ;                         // STRUCT での配列番号( 0から始まる番号 )
630                private final boolean   isStruct ;                      // オリジナルのタイプが、Struct型 かどうか。
631                private final boolean   isObject ;                      // タイプが、CLOB,ROWID,TIMESTAMP かどうか。
632
633                /**
634                 * 引数付コンストラクター
635                 *
636                 * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
637                 *
638                 * @param       name            カラム名(ResultSetMetaData#getColumnLabel(int) の toUpperCase)
639                 * @param       type            java.sql.Types の定数定義
640                 * @param       size            カラムサイズ(ResultSetMetaData#getColumnDisplaySize(int))
641                 * @param       isWrit          書き込み許可(ResultSetMetaData#isWritable(int))
642                 * @param       clmNo           ResultSet での元のカラムNo( 1から始まる番号 )
643                 * @param       objNo           STRUCT での配列番号( 0から始まる番号 )
644                 */
645                /* default */ ColumnInfo( final String name , final int type , final int size , final boolean isWrit
646                                         , final int clmNo , final int objNo ) {
647                        this.name       = name  ;
648                        this.type       = type  ;
649                        this.size       = size  ;
650                        this.isWrit     = isWrit;
651                        this.clmNo      = clmNo;
652                        this.objNo      = objNo;
653                        isStruct        = objNo >= 0;                           // Struct型かどうかは、配列番号で判定する。
654                        isObject        = type == Types.CLOB || type == Types.ROWID || type == Types.TIMESTAMP ;
655                }
656
657                /**
658                 * カラム名を返します。
659                 *
660                 * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
661                 *
662                 * @return      カラム名
663                 */
664                public String getName() { return name; }
665        }
666}