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