NetBeansのバグを追ってみた&修正のアドバイス

何回か指摘しているデータベースのバグ。1分ですぐ分かるバグだと思うのだが…。

6.5の開発版の時点でバグは発覚していて、すぐに直るだろうと高をくくっていたが、ぜんぜん直ってなかった。そして今6.7RC1を試すも直っていない。たぶんRC2でも直っていないのだろう。


ということでもうわりきってNetBeansのソースを追ってみる。データベースのところははじめて触る。


普通ならリポジトリのクローンをするのがいいのだろうが、おそらく馬鹿でかいだろうということとそこまでやるものでも無いだろうということでブラウザ上でソースを斜め読みしながら追ってみた。そしてかなり大変だということにしばらくして気がついた。

おとなしくリポジトリからクローンするか…と思ったところでついに発見!ちかれたー。


http://hg.netbeans.org/release67_m3/file/49efc443b305/db.dataview/src/org/netbeans/modules/db/dataview/util/DBReadWriteHelper.java#l428

ここの428行目がバグの原因だ。以下抜粋

 case Types.CHAR:
 case Types.VARCHAR:
 case Types.LONGVARCHAR:
 case -9:  //NVARCHAR
 case -8:  //ROWID
 case -15: //NCHAR
     if (valueObj.toString().length() > col.getPrecision()) {
         String colName = col.getQualifiedName(false);
         String errMsg = "Too large data \'" + valueObj + "\' for column " + colName;
         throw new DBException(errMsg);
     }
     return valueObj;

サイズの判定が間違っているようだ。JDBCはPrecisionが「0」の場合がありえる。特定の桁数等が分からない場合「0」になるのだ。具体的には桁数指定していないVARCHARやPostgreSQLのtext等で0がかえるので不具合が発生する。ほかのDBでも起こる可能性はある。JDBCドライバによってはこの制限なしを符号あり32bitの最大値に設定しているものも存在しているため、そういったJDBCドライバの実装ならば不具合は発見されない。おそらくMySQLやJavaDBだとこの不具合は出ないのだろう(JavaDBはそうなってるのを確認済み)。でもPostgreSQLでは不具合爆発する。普通に考えて長さ0の文字列のフィールドがあるはずがないよね…。

ここを以下のようにすればたぶん解決するはずだ。

     if ((col.getPrecision() != 0) && (valueObj.toString().length() > col.getPrecision())) {

さらにソースを追ってみたら大変なところに気がついた

さらに追ってみた。そうすると大変なことに気がついた。

http://hg.netbeans.org/release67_m3/file/49efc443b305/db.dataview/src/org/netbeans/modules/db/dataview/util/DBReadWriteHelper.java#l120

データの取得部分だがBIGINT型はBigInteger使ってるかと思ったらLongだった。DECIMALとNUMERICはBigDecimalであらわしている。まぁこれは問題ない。


問題は以下の場所。
http://hg.netbeans.org/release67_m3/file/49efc443b305/db.dataview/src/org/netbeans/modules/db/dataview/util/DBReadWriteHelper.java#l281

 case Types.BIGINT:
 case Types.NUMERIC:
     BigDecimal bigDec = new BigDecimal(valueObj.toString());
     ps.setBigDecimal(index, bigDec);
     break;

ん?BIGINTはLongじゃないのか?まぁここは大目に見よう。というか取得のときはまとめて扱われていたDECIMAL取得のときはまとめて扱われていたがないぞ…。

やばいのはここから。

case Types.DOUBLE:
     numberObj = (valueObj instanceof Number) ? (Number) valueObj : Double.valueOf(valueObj.toString());
     ps.setDouble(index, numberObj.doubleValue());
     break;


 case Types.DECIMAL:
     numberObj = (valueObj instanceof Number) ? (Number) valueObj : new BigDecimal(valueObj.toString());
     ps.setDouble(index, numberObj.doubleValue());
     break;

ふむ。DECIMAL型はdoubleで格納っと…。



…えええええええええええええええええええええええええええええええええええええええええええええ

ありえねぇ。

まずDECIMALとNUMERICはBigDecimalで取り出してるんだから格納もBigDecimalにするべき。BIGINTはLongで取得してるんだからこれもLongで格納すべき。

比較的新しい部分とはいえこれはさすがにないだろう。Number型の変数を使い回ししているが個人的にこれはおいらは嫌い。

以下のようにスタックを1段入れたほうが良いと思われ。

case Types.DOUBLE:{
     double value= (valueObj instanceof Number) ? ((Number) valueObj).doubleValue()  : Double.valueOf(valueObj.toString());
     ps.setDouble(index, value);
     break;
}


どちらも回避方法が無い致命的なバグなので早く直って欲しい。6.5で入力系が追加、6.7でUIが大幅によくなっただけにこれは非常に残念なところ。目玉となる機能なだけに。

英語がかけないので報告できないのが悔やまれる。自分で使う分だけいろいろとパッチあてるかな。オレオレNetBeans