/* * Copyright Aduna (http://www.aduna-software.com/) (c) 2008. * * Licensed under the Aduna BSD-style license. */ package org.openrdf.sail.rdbms.schema; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents and controls the underlying database table. * * @author James Leigh * */ public class RdbmsTable { public static int total_opt; public static int MAX_DELTA_TO_FORCE_OPTIMIZE = 10000; private static final String[] TYPE_TABLE = new String[] { "TABLE" }; private Logger logger = LoggerFactory.getLogger(RdbmsTable.class); private int addedCount; private Connection conn; private String name; private int removedCount; private long rowCount; private PreparedStatement clear; public RdbmsTable(String name) { super(); this.name = name; } public void setConnection(Connection conn) { this.conn = conn; } public void close() throws SQLException { if (clear != null) { clear.close(); } } public long size() { assert rowCount >= 0 : rowCount; return rowCount; } public void clear() throws SQLException { if (clear == null) { clear = conn.prepareStatement(buildClear()); } clear.execute(); rowCount = 0; } public void createTable(CharSequence columns) throws SQLException { execute(buildCreateTable(columns)); rowCount = 0; } public void createTransactionalTable(CharSequence columns) throws SQLException { execute(buildCreateTransactionalTable(columns)); rowCount = 0; } public void createTemporaryTable(CharSequence columns) throws SQLException { try { execute(buildCreateTemporaryTable(columns)); } catch (SQLException e) { // must already exist } } public void execute(String command) throws SQLException { if (command != null) { Statement st = conn.createStatement(); try { st.execute(command); } catch (SQLException e) { logger.warn(e.getMessage() + '\n' + command); throw e; } finally { st.close(); } } } public int executeUpdate(String command, Object... parameters) throws SQLException { PreparedStatement st = conn.prepareStatement(command); try { for (int i = 0; i < parameters.length; i++) { if (parameters[i] == null) { st.setNull(i + 1, Types.VARCHAR); } else { st.setObject(i + 1, parameters[i]); } } return st.executeUpdate(); } catch (SQLException e) { logger.warn(e.getMessage() + '\n' + command); throw e; } finally { st.close(); } } public String getCatalog() { return null; } public String getName() { return name; } public String getSchema() { return null; } public void primaryIndex(String... columns) throws SQLException { execute(buildPrimaryIndex(columns)); } public void index(String... columns) throws SQLException { index(0, columns); } public void index(int sqlType, String... columns) throws SQLException { if (columns.length == 1 && columns[0].equalsIgnoreCase("value") && getName().toUpperCase().contains("LONG_")) { execute(buildLongIndex(sqlType, columns)); } else { execute(buildIndex(sqlType, columns)); } } public void dropIndex(String name) throws SQLException { execute(buildDropIndex(name)); } public boolean isCreated() throws SQLException { DatabaseMetaData metaData = conn.getMetaData(); String c = getCatalog(); String s = getSchema(); String n = getName(); ResultSet tables = metaData.getTables(c, s, n, TYPE_TABLE); try { return tables.next(); } finally { tables.close(); } } public Map<String, List<String>> getIndexes() throws SQLException { DatabaseMetaData metaData = conn.getMetaData(); String c = getCatalog(); String s = getSchema(); String n = getName(); ResultSet indexes = metaData.getIndexInfo(c, s, n, false, false); try { Map<String, List<String>> names = new HashMap<String, List<String>>(); while (indexes.next()) { String index = indexes.getString(6); String column = indexes.getString(9); List<String> columns = names.get(index); if (columns == null) { names.put(index, columns = new ArrayList<String>()); } columns.add(column); } return names; } finally { indexes.close(); } } public long count() throws SQLException { StringBuilder sb = new StringBuilder(); sb.append("SELECT COUNT(*)\n"); sb.append("FROM ").append(name); String query = sb.toString(); Statement st = conn.createStatement(); try { ResultSet rs = st.executeQuery(query); try { if (rs.next()) { rowCount = rs.getLong(1); assert rowCount >= 0 : rowCount; return rowCount; } return 0; } finally { rs.close(); } } finally { st.close(); } } public void modified(int inserted, int deleted) throws SQLException { if (inserted < 1 && deleted < 1) return; addedCount += inserted; removedCount += deleted; rowCount += inserted - deleted; assert rowCount >= 0 : rowCount; } public void optimize() throws SQLException { if (optimize(addedCount + removedCount, rowCount) && null == System.getProperty("org.openrdf.sail.rdbms.schema.RdbmsTable.nooptimize")) { execute(buildOptimize()); addedCount = removedCount = 0; total_opt += 1; } } public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); } public void rollback() throws SQLException { conn.rollback(); } public List<Object[]> select(String... columns) throws SQLException { StringBuilder sb = new StringBuilder(); for (String column : columns) { if (sb.length() == 0) { sb.append("SELECT "); } else { sb.append(", "); } sb.append(column); } sb.append("\nFROM ").append(name); String query = sb.toString(); List<Object[]> result = new ArrayList<Object[]>(); Statement st = conn.createStatement(); try { ResultSet rs = st.executeQuery(query); try { int columnCount = rs.getMetaData().getColumnCount(); while (rs.next()) { Object[] row = new Object[columnCount]; for (int i = 0; i < row.length; i++) { row[i] = rs.getObject(i + 1); } result.add(row); } rowCount = result.size(); assert rowCount >= 0 : rowCount; return result; } finally { rs.close(); } } finally { st.close(); } } public int[] aggregate(String... expressions) throws SQLException { StringBuilder sb = new StringBuilder(); sb.append("SELECT COUNT(*)"); for (String expression : expressions) { sb.append(", ").append(expression); } sb.append("\nFROM ").append(name); String query = sb.toString(); Statement st = conn.createStatement(); try { ResultSet rs = st.executeQuery(query); try { if (!rs.next()) throw new AssertionError(); int columnCount = rs.getMetaData().getColumnCount(); int[] result = new int[columnCount - 1]; for (int i = 0; i < result.length; i++) { result[i] = rs.getInt(i + 2); } rowCount = rs.getLong(1); assert rowCount >= 0 : rowCount; return result; } finally { rs.close(); } } finally { st.close(); } } protected boolean optimize(int delta, long rowCount) { if (delta > MAX_DELTA_TO_FORCE_OPTIMIZE) return true; return delta != 0 && rowCount / delta <= 2; } protected String buildClear() { return "DELETE FROM " + name; } protected String buildCreateTable(CharSequence columns) { StringBuilder sb = new StringBuilder(); sb.append("CREATE TABLE ").append(name); sb.append(" (\n").append(columns).append(")"); return sb.toString(); } protected String buildCreateTransactionalTable(CharSequence columns) { return buildCreateTable(columns); } protected String buildCreateTemporaryTable(CharSequence columns) { StringBuilder sb = new StringBuilder(); sb.append("CREATE TEMPORARY TABLE ").append(name); sb.append(" (\n").append(columns).append(")"); return sb.toString(); } protected String buildPrimaryIndex(String... columns) { StringBuilder sb = new StringBuilder(); sb.append("ALTER TABLE "); sb.append(getName()); sb.append(" ADD PRIMARY KEY ("); for (int i = 0; i < columns.length; i++) { if (i > 0) { sb.append(", "); } sb.append(columns[i]); } sb.append(")"); return sb.toString(); } protected String buildIndex(int sqlType, String... columns) { StringBuilder sb = new StringBuilder(); sb.append("CREATE INDEX ").append(buildIndexName(columns)); sb.append(" ON ").append(name).append(" ("); for (int i = 0; i < columns.length; i++) { if (i > 0) { sb.append(", "); } sb.append(columns[i]); } sb.append(")"); return sb.toString(); } protected String buildLongIndex(int sqlType, String... columns) { return buildIndex(sqlType, columns); } protected String buildDropIndex(String name) { StringBuilder sb = new StringBuilder(); sb.append("DROP INDEX ").append(name); return sb.toString(); } /** * Creates an index name based on the name of the columns and table that it's * supposed to index. */ protected String buildIndexName(String... columns) { StringBuffer sb = new StringBuffer(32); sb.append(getName()).append("_").append(columns[0]); for (int i = 1; i < columns.length; i++) { sb.append("_").append(columns[i]); } sb.append("_idx"); return sb.toString(); } protected String buildOptimize() throws SQLException { // There is no default for this in SQL92. return null; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof RdbmsTable)) return false; final RdbmsTable other = (RdbmsTable)obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } public void drop() throws SQLException { execute("DROP TABLE " + name); } @Override public String toString() { return getName(); } }