S2JDBCでPostgreSQLのhstoreを使う
PostgreSQLにはKVSのような型 hstore ( http://www.postgresql.jp/document/8.4/html/hstore.html ) がある。
横に巨大なExcelをデータベースに突っ込む場合、いちいちカラム作らなくていいんじゃね?ということでhstoreにごっそり入れてみる試み。
そういうExcelに限って分類1、分類2、分類3とか書いてあって謎仕様。全部テキストで入れるからもう好きなだけ作ってくれーって感じで。
取り敢えず、取得と更新まで出来た。
http://d.hatena.ne.jp/kuniku/20090819/1250656540
ここを参考にそれぞれのクラスを作る。
PostgreDialectEx.java
public class PostgreDialectEx extends PostgreDialect { @Override public ValueType getValueType(PropertyMeta propertyMeta) { final Class<?> clazz = propertyMeta.getPropertyClass(); if (clazz == HStore.class) return HStoreType.INSTANCE; return super.getValueType(propertyMeta); } @Override public ValueType getValueType(Class<?> clazz, boolean lob, TemporalType temporalType) { if (clazz == HStore.class) return HStoreType.INSTANCE; return super.getValueType(clazz, lob, temporalType); } }
HStore型のデータはHStoreTypeを使うようにする設定。
HStoreType.java
public class HStoreType extends AbstractValueType { public static final HStoreType INSTANCE = new HStoreType(); public HStoreType() { super(Types.OTHER); } @Override public void bindValue(PreparedStatement ps, int index, Object value) throws SQLException { if (value == null) { setNull(ps, index); } else { ps.setObject(index, value); } } @Override public void bindValue(CallableStatement cs, String parameterName, Object value) throws SQLException { if (value == null) { setNull(cs, parameterName); } else { cs.setObject(parameterName, value); } } @Override public Object getValue(ResultSet rs, int index) throws SQLException { PGobject obj = (PGobject) rs.getObject(index); //if (obj.getType().equals("hstore")) return new HStore(obj.getValue()); } @Override public Object getValue(ResultSet rs, String columnName) throws SQLException { PGobject obj = (PGobject) rs.getObject(columnName); //if (obj.getType().equals("hstore")) return new HStore(obj.getValue()); } @Override public Object getValue(CallableStatement cs, int index) throws SQLException { PGobject obj = (PGobject) cs.getObject(index); return new HStore(obj.getValue()); } @Override public Object getValue(CallableStatement cs, String parameterName) throws SQLException { PGobject obj = (PGobject) cs.getObject(parameterName); return new HStore(obj.getValue()); } @Override public String toText(Object value) { if (value == null) { return BindVariableUtil.nullText(); } return BindVariableUtil.toText(value); } }
ほとんどObjectTypeと同じだけれど。
HStore.java
public class HStore extends PGobject { /** * */ private static final long serialVersionUID = -6859240870323667683L; private final HashMap<String, String> data = new HashMap<String, String>(); public HStore() { super(); setType("hstore"); } public HStore(String text) { this(); if (text == null || text.isEmpty()) { return; } setValue(text); } @Override public void setValue(String value) { for(;;) { String ret = parse(value); if (ret == null) break; value = ret; } } private String parse(String text){ // "key"=>"value", "key"=>"value" の文字列を地道に解析する String buf = text; int keyStart = text.indexOf('"'); if (keyStart == -1) return null; buf = text.substring(keyStart + 1); String key = null; for (int i = 0, length = buf.length(); i<length; i++) { if (buf.charAt(i) == '"') { if (i == 0) { key = ""; buf = buf.substring(i+1); break; } if (buf.charAt(i - 1) != '\\') { key = buf.substring(0, i); buf = buf.substring(i+1); break; } } } String value = null; buf = buf.substring(buf.indexOf("=>") + 2); if (buf.startsWith("NULL, ")) { data.put(key, value); return buf.substring(buf.indexOf("NULL, ")+6); } buf = buf.substring(buf.indexOf('"') + 1); for (int i = 0, length = buf.length(); i<length; i++) { if (buf.charAt(i) == '"') { if (i == 0) { value = ""; buf = buf.substring(i+1); break; } if (buf.charAt(i - 1) != '\\') { value = buf.substring(0, i); buf = buf.substring(i+1); break; } } } data.put(key, value); return buf; } @Override public String getValue() { StringBuilder sb = new StringBuilder(); boolean first = true; for (String key: data.keySet()) { if (!first) sb.append(", "); sb.append('"'); sb.append(key); sb.append("\"=>"); String value = data.get(key); if (value == null) { sb.append("NULL"); }else { sb.append('"'); sb.append(value); sb.append('"'); } first = false; } return sb.toString(); } @Override public int hashCode() { return data.hashCode(); } @Override public boolean equals(Object o) { return data.equals(o); } public HStore put(String key, String value) { data.put(key, value); return this; } public String get(String key) { return data.get(key); } public HashMap<String, String> getInternalMap(){ return data; } }
キーが空白の場合と値がNULLの場合にそれぞれ対応するとややこしくなってきた。PostgreSQLのバージョンによってフォーマットが変わったら使えなくなるかも。(NULLが小文字になるとか区切りに空白が増えるとか)
Mapを引数に取るコンストラクタを書くのを忘れてる…まあいいか。
全体的に深追いしてないのであまり自信なし。
ここまで書けば、あとは s2jdbc.dicon に
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <include path="jdbc.dicon"/> <include path="s2jdbc-internal.dicon"/> <component name="postgreDialectEx" class="com.example.pgsql.PostgreDialectEx" /> <component name="jdbcManager" class="org.seasar.extension.jdbc.manager.JdbcManagerImpl"> <property name="maxRows">0</property> <property name="fetchSize">0</property> <property name="queryTimeout">0</property> <property name="dialect">postgreDialectEx</property> </component> </components>
設定を書くと完成。
S2JDBC-Genも対応しようと思ったけど拡張の仕方が分からなかったので保留。jarのコードを修正せずに拡張できるのだろうか…。
次は演算子と関数を作る。