001: /*
002: * Copyright Aduna (http://www.aduna-software.com/) (c) 2008.
003: *
004: * Licensed under the Aduna BSD-style license.
005: */
006: package org.openrdf.sail.rdbms.schema;
007:
008: import java.sql.Connection;
009: import java.sql.DatabaseMetaData;
010: import java.sql.PreparedStatement;
011: import java.sql.ResultSet;
012: import java.sql.SQLException;
013: import java.sql.Statement;
014: import java.sql.Types;
015: import java.util.ArrayList;
016: import java.util.HashMap;
017: import java.util.List;
018: import java.util.Map;
019:
020: import org.slf4j.Logger;
021: import org.slf4j.LoggerFactory;
022:
023: /**
024: * Represents and controls the underlying database table.
025: *
026: * @author James Leigh
027: *
028: */
029: public class RdbmsTable {
030: public static int total_opt;
031: public static int MAX_DELTA_TO_FORCE_OPTIMIZE = 10000;
032: private static final String[] TYPE_TABLE = new String[] { "TABLE" };
033: private Logger logger = LoggerFactory.getLogger(RdbmsTable.class);
034: private int addedCount;
035: private Connection conn;
036: private String name;
037: private int removedCount;
038: private long rowCount;
039: private PreparedStatement clear;
040:
041: public RdbmsTable(String name) {
042: super ();
043: this .name = name;
044: }
045:
046: public void setConnection(Connection conn) {
047: this .conn = conn;
048: }
049:
050: public void close() throws SQLException {
051: if (clear != null) {
052: clear.close();
053: }
054: }
055:
056: public long size() {
057: assert rowCount >= 0 : rowCount;
058: return rowCount;
059: }
060:
061: public void clear() throws SQLException {
062: if (clear == null) {
063: clear = conn.prepareStatement(buildClear());
064: }
065: clear.execute();
066: rowCount = 0;
067: }
068:
069: public void createTable(CharSequence columns) throws SQLException {
070: execute(buildCreateTable(columns));
071: rowCount = 0;
072: }
073:
074: public void createTransactionalTable(CharSequence columns)
075: throws SQLException {
076: execute(buildCreateTransactionalTable(columns));
077: rowCount = 0;
078: }
079:
080: public void createTemporaryTable(CharSequence columns)
081: throws SQLException {
082: try {
083: execute(buildCreateTemporaryTable(columns));
084: } catch (SQLException e) {
085: // must already exist
086: }
087: }
088:
089: public void execute(String command) throws SQLException {
090: if (command != null) {
091: Statement st = conn.createStatement();
092: try {
093: st.execute(command);
094: } catch (SQLException e) {
095: logger.warn(e.getMessage() + '\n' + command);
096: throw e;
097: } finally {
098: st.close();
099: }
100: }
101: }
102:
103: public int executeUpdate(String command, Object... parameters)
104: throws SQLException {
105: PreparedStatement st = conn.prepareStatement(command);
106: try {
107: for (int i = 0; i < parameters.length; i++) {
108: if (parameters[i] == null) {
109: st.setNull(i + 1, Types.VARCHAR);
110: } else {
111: st.setObject(i + 1, parameters[i]);
112: }
113: }
114: return st.executeUpdate();
115: } catch (SQLException e) {
116: logger.warn(e.getMessage() + '\n' + command);
117: throw e;
118: } finally {
119: st.close();
120: }
121: }
122:
123: public String getCatalog() {
124: return null;
125: }
126:
127: public String getName() {
128: return name;
129: }
130:
131: public String getSchema() {
132: return null;
133: }
134:
135: public void index(String... columns) throws SQLException {
136: if (columns.length == 1 && columns[0].equalsIgnoreCase("value")
137: && getName().toUpperCase().contains("LONG_")) {
138: execute(buildLongIndex(columns));
139: } else {
140: execute(buildIndex(columns));
141: }
142: }
143:
144: public void dropIndex(String name) throws SQLException {
145: execute(buildDropIndex(name));
146: }
147:
148: public boolean isCreated() throws SQLException {
149: DatabaseMetaData metaData = conn.getMetaData();
150: String c = getCatalog();
151: String s = getSchema();
152: String n = getName();
153: ResultSet tables = metaData.getTables(c, s, n, TYPE_TABLE);
154: try {
155: return tables.next();
156: } finally {
157: tables.close();
158: }
159: }
160:
161: public Map<String, List<String>> getIndexes() throws SQLException {
162: DatabaseMetaData metaData = conn.getMetaData();
163: String c = getCatalog();
164: String s = getSchema();
165: String n = getName();
166: ResultSet indexes = metaData
167: .getIndexInfo(c, s, n, false, false);
168: try {
169: Map<String, List<String>> names = new HashMap<String, List<String>>();
170: while (indexes.next()) {
171: String index = indexes.getString(6);
172: String column = indexes.getString(9);
173: List<String> columns = names.get(index);
174: if (columns == null) {
175: names.put(index, columns = new ArrayList<String>());
176: }
177: columns.add(column);
178: }
179: return names;
180: } finally {
181: indexes.close();
182: }
183: }
184:
185: public long count() throws SQLException {
186: StringBuilder sb = new StringBuilder();
187: sb.append("SELECT COUNT(*)\n");
188: sb.append("FROM ").append(name);
189: String query = sb.toString();
190: Statement st = conn.createStatement();
191: try {
192: ResultSet rs = st.executeQuery(query);
193: try {
194: if (rs.next()) {
195: rowCount = rs.getLong(1);
196: assert rowCount >= 0 : rowCount;
197: return rowCount;
198: }
199: return 0;
200: } finally {
201: rs.close();
202: }
203: } finally {
204: st.close();
205: }
206: }
207:
208: public void modified(int inserted, int deleted) throws SQLException {
209: if (inserted < 1 && deleted < 1)
210: return;
211: addedCount += inserted;
212: removedCount += deleted;
213: rowCount += inserted - deleted;
214: assert rowCount >= 0 : rowCount;
215: }
216:
217: public void optimize() throws SQLException {
218: if (optimize(addedCount + removedCount, rowCount)) {
219: execute(buildOptimize());
220: addedCount = removedCount = 0;
221: total_opt += 1;
222: }
223: }
224:
225: public PreparedStatement prepareStatement(String sql)
226: throws SQLException {
227: return conn.prepareStatement(sql);
228: }
229:
230: public void rollback() throws SQLException {
231: conn.rollback();
232: }
233:
234: public List<Object[]> select(String... columns) throws SQLException {
235: StringBuilder sb = new StringBuilder();
236: for (String column : columns) {
237: if (sb.length() == 0) {
238: sb.append("SELECT ");
239: } else {
240: sb.append(", ");
241: }
242: sb.append(column);
243: }
244: sb.append("\nFROM ").append(name);
245: String query = sb.toString();
246: List<Object[]> result = new ArrayList<Object[]>();
247: Statement st = conn.createStatement();
248: try {
249: ResultSet rs = st.executeQuery(query);
250: try {
251: int columnCount = rs.getMetaData().getColumnCount();
252: while (rs.next()) {
253: Object[] row = new Object[columnCount];
254: for (int i = 0; i < row.length; i++) {
255: row[i] = rs.getObject(i + 1);
256: }
257: result.add(row);
258: }
259: rowCount = result.size();
260: assert rowCount >= 0 : rowCount;
261: return result;
262: } finally {
263: rs.close();
264: }
265: } finally {
266: st.close();
267: }
268: }
269:
270: public int[] aggregate(String... expressions) throws SQLException {
271: StringBuilder sb = new StringBuilder();
272: sb.append("SELECT COUNT(*)");
273: for (String expression : expressions) {
274: sb.append(", ").append(expression);
275: }
276: sb.append("\nFROM ").append(name);
277: String query = sb.toString();
278: Statement st = conn.createStatement();
279: try {
280: ResultSet rs = st.executeQuery(query);
281: try {
282: if (!rs.next())
283: throw new AssertionError();
284: int columnCount = rs.getMetaData().getColumnCount();
285: int[] result = new int[columnCount - 1];
286: for (int i = 0; i < result.length; i++) {
287: result[i] = rs.getInt(i + 2);
288: }
289: rowCount = rs.getLong(1);
290: assert rowCount >= 0 : rowCount;
291: return result;
292: } finally {
293: rs.close();
294: }
295: } finally {
296: st.close();
297: }
298: }
299:
300: protected boolean optimize(int delta, long rowCount) {
301: if (delta > MAX_DELTA_TO_FORCE_OPTIMIZE)
302: return true;
303: return delta != 0 && rowCount / delta <= 2;
304: }
305:
306: protected String buildClear() {
307: return "DELETE FROM " + name;
308: }
309:
310: protected String buildCreateTable(CharSequence columns) {
311: StringBuilder sb = new StringBuilder();
312: sb.append("CREATE TABLE ").append(name);
313: sb.append(" (\n").append(columns).append(")");
314: return sb.toString();
315: }
316:
317: protected String buildCreateTransactionalTable(CharSequence columns) {
318: return buildCreateTable(columns);
319: }
320:
321: protected String buildCreateTemporaryTable(CharSequence columns) {
322: StringBuilder sb = new StringBuilder();
323: sb.append("CREATE TEMPORARY TABLE ").append(name);
324: sb.append(" (\n").append(columns).append(")");
325: return sb.toString();
326: }
327:
328: protected String buildIndex(String... columns) {
329: StringBuilder sb = new StringBuilder();
330: sb.append("CREATE INDEX ").append(buildIndexName(columns));
331: sb.append(" ON ").append(name).append(" (");
332: for (int i = 0; i < columns.length; i++) {
333: if (i > 0) {
334: sb.append(", ");
335: }
336: sb.append(columns[i]);
337: }
338: sb.append(")");
339: return sb.toString();
340: }
341:
342: protected String buildLongIndex(String... columns) {
343: return buildIndex(columns);
344: }
345:
346: protected String buildDropIndex(String name) {
347: StringBuilder sb = new StringBuilder();
348: sb.append("DROP INDEX ").append(name);
349: return sb.toString();
350: }
351:
352: /**
353: * Creates an index name based on the name of the columns and table that
354: * it's supposed to index.
355: */
356: protected String buildIndexName(String... columns) {
357: StringBuffer sb = new StringBuffer(32);
358: sb.append(getName()).append("_").append(columns[0]);
359: for (int i = 1; i < columns.length; i++) {
360: sb.append("_").append(columns[i]);
361: }
362: sb.append("_idx");
363: return sb.toString();
364: }
365:
366: protected String buildOptimize() throws SQLException {
367: // There is no default for this in SQL92.
368: return null;
369: }
370:
371: @Override
372: public int hashCode() {
373: final int prime = 31;
374: int result = 1;
375: result = prime * result
376: + ((name == null) ? 0 : name.hashCode());
377: return result;
378: }
379:
380: @Override
381: public boolean equals(Object obj) {
382: if (this == obj)
383: return true;
384: if (obj == null)
385: return false;
386: if (!(obj instanceof RdbmsTable))
387: return false;
388: final RdbmsTable other = (RdbmsTable) obj;
389: if (name == null) {
390: if (other.name != null)
391: return false;
392: } else if (!name.equals(other.name))
393: return false;
394: return true;
395: }
396:
397: public void drop() throws SQLException {
398: execute("DROP TABLE " + name);
399: }
400:
401: @Override
402: public String toString() {
403: return getName();
404: }
405: }
|