package org.ibex.mail; import org.ibex.io.*; import org.ibex.mail.protocol.*; import org.ibex.util.*; import org.ibex.net.*; import java.sql.*; import java.net.*; import java.io.*; import java.util.*; import java.sql.Timestamp; import java.sql.Connection; public class SqliteDB { protected Connection conn; private String filename; private HashMap tables = new HashMap(); public static final int REAPER_INTERVAL_SECONDS = 60 * 60; private static final int DAYS = 24 * 60 * 60 * 1000; public static final int REAP_EXPIRATION = 5 * DAYS; public Connection getConnection() { return conn; } public synchronized SqliteTable getTable(String name, String schema) throws SQLException { SqliteTable ret = tables.get(name); if (ret==null) ret = new SqliteTable(name, schema); return ret; } // check upstream: PRAGMA encoding = "UTF-8"; // create indices // periodically run "analyze"? public void setCacheSize(int kilobytes) throws SQLException { conn.prepareStatement("PRAGMA cache_size="+Math.ceil(kilobytes/1.5)+";").executeUpdate(); } public void close(ResultSet rs) { if (rs==null) return; try { rs.close(); } catch (SQLException s) { Log.error(ResultSet.class, s); } } public SqliteDB(String filename) { this.filename = filename; try { Log.error("start", "initializing " + filename); Class.forName("org.sqlite.JDBC"); conn = DriverManager.getConnection("jdbc:sqlite:"+filename); conn.prepareStatement("PRAGMA auto_vacuum = 1").executeUpdate(); //conn.prepareStatement("VACUUM").executeUpdate(); // until we have better assurances about locking on network filesystems... conn.prepareStatement("PRAGMA locking_mode = EXCLUSIVE").executeQuery(); //conn.prepareStatement("PRAGMA temp_store = MEMORY").executeUpdate(); conn.prepareStatement("PRAGMA page_size=4096").executeUpdate(); conn.prepareStatement("PRAGMA cache_size=2000").executeUpdate(); ResultSet rs = conn.prepareStatement("PRAGMA integrity_check").executeQuery(); rs.next(); String result = rs.getString(1); if (!result.equals("ok")) throw new RuntimeException("PRAGMA integrity_check returned \""+result+"\""); Log.error(".", "done initializing " + filename); } catch (SQLException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } public void setFastButDangerous(boolean on) throws SQLException { conn.prepareStatement("PRAGMA synchronous = "+(on?"OFF":"ON")).executeUpdate(); } /* public void dump(OutputStream os) { } */ public class SqliteTable { public final String name; private String reapColumn = null; private SqliteTable(String name, String schema) throws SQLException { this.name = name; PreparedStatement ps = conn.prepareStatement("create table if not exists " + name + " " + schema); ps.executeUpdate(); tables.put(name, this); } public void createIndex(String column) throws SQLException { createIndex(column, column+"_index"); } public void createIndex(String indexName, String column) throws SQLException { PreparedStatement ps = conn.prepareStatement("create index if not exists "+column+" on "+name+" ("+indexName+")"); ps.executeUpdate(); } protected void reap(String reapColumn) { if (this.reapColumn != null) throw new RuntimeException("reapColumn already set"); this.reapColumn = reapColumn; Main.cron.executeLater(1000 * REAPER_INTERVAL_SECONDS, new Reaper(name, reapColumn)); } } // FIXME: desynchronized access to the conn? private class Reaper implements Runnable { public Reaper(String reapTable, String reapColumn) { this.reapTable = reapTable; this.reapColumn = reapColumn; } private String reapTable; private String reapColumn; public void run() { try { Log.warn(Reaper.class, filename + " reaping..."); long when = System.currentTimeMillis(); when -= REAP_EXPIRATION; synchronized(SqliteDB.this) { PreparedStatement ps = conn.prepareStatement("select count(*) from "+reapTable+" where "+reapColumn+"