/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.bench;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import org.h2.test.TestBase;
import org.h2.tools.Server;
import org.h2.util.JdbcUtils;
import org.h2.util.StringUtils;
/**
* Represents a database in the benchmark test application.
*/
class Database {
private TestPerformance test;
private int id;
private String name, url, user, password;
private ArrayList<String[]> replace = new ArrayList<String[]>();
private String currentAction;
private long startTime;
private Connection conn;
private Statement stat;
private boolean trace = true;
private long lastTrace;
private Random random = new Random(1);
private ArrayList<Object[]> results = new ArrayList<Object[]>();
private int totalTime;
private int executedStatements;
private Server serverH2;
private Object serverDerby;
private boolean serverHSQLDB;
/**
* Get the database name.
*
* @return the database name
*/
String getName() {
return name;
}
/**
* Get the total measured time.
*
* @return the time
*/
int getTotalTime() {
return totalTime;
}
/**
* Get the result array.
*
* @return the result array
*/
ArrayList<Object[]> getResults() {
return results;
}
/**
* Get the random number generator.
*
* @return the generator
*/
Random getRandom() {
return random;
}
/**
* Start the server if the this is a remote connection.
*/
void startServer() throws Exception {
if (url.startsWith("jdbc:h2:tcp:")) {
serverH2 = Server.createTcpServer().start();
Thread.sleep(100);
} else if (url.startsWith("jdbc:derby://")) {
serverDerby = Class.forName("org.apache.derby.drda.NetworkServerControl").newInstance();
Method m = serverDerby.getClass().getMethod("start", PrintWriter.class);
m.invoke(serverDerby, new Object[] { null });
// serverDerby = new NetworkServerControl();
// serverDerby.start(null);
Thread.sleep(100);
} else if (url.startsWith("jdbc:hsqldb:hsql:")) {
if (!serverHSQLDB) {
Class<?> c;
try {
c = Class.forName("org.hsqldb.server.Server");
} catch (Exception e) {
c = Class.forName("org.hsqldb.Server");
}
Method m = c.getMethod("main", String[].class);
m.invoke(null, new Object[] { new String[] { "-database.0",
"data/mydb;hsqldb.default_table_type=cached", "-dbname.0", "xdb" } });
// org.hsqldb.Server.main(new String[]{"-database.0", "mydb",
// "-dbname.0", "xdb"});
serverHSQLDB = true;
Thread.sleep(100);
}
}
}
/**
* Stop the server if this is a remote connection.
*/
void stopServer() throws Exception {
if (serverH2 != null) {
serverH2.stop();
serverH2 = null;
}
if (serverDerby != null) {
Method m = serverDerby.getClass().getMethod("shutdown");
// cast for JDK 1.5
m.invoke(serverDerby, (Object[]) null);
// serverDerby.shutdown();
serverDerby = null;
} else if (serverHSQLDB) {
// can not shut down (shutdown calls System.exit)
// openConnection();
// update("SHUTDOWN");
// closeConnection();
// serverHSQLDB = false;
}
}
/**
* Parse a database configuration and create a database object from it.
*
* @param test the test application
* @param id the database id
* @param dbString the configuration string
* @return a new database object with the given settings
*/
static Database parse(TestPerformance test, int id, String dbString) {
try {
StringTokenizer tokenizer = new StringTokenizer(dbString, ",");
Database db = new Database();
db.id = id;
db.test = test;
db.name = tokenizer.nextToken().trim();
String driver = tokenizer.nextToken().trim();
Class.forName(driver);
db.url = tokenizer.nextToken().trim();
db.user = tokenizer.nextToken().trim();
db.password = "";
if (tokenizer.hasMoreTokens()) {
db.password = tokenizer.nextToken().trim();
}
return db;
} catch (Exception e) {
System.out.println("Cannot load database " + dbString + " :" + e.toString());
return null;
}
}
/**
* Get the database connection. The connection must be opened first.
*
* @return the connection
*/
Connection getConnection() {
return conn;
}
/**
* Open a new database connection. This connection must be closed
* by calling conn.close().
*
* @return the opened connection
*/
Connection openNewConnection() throws SQLException {
Connection newConn = DriverManager.getConnection(url, user, password);
if (url.startsWith("jdbc:derby:")) {
// Derby: use higher cache size
Statement s = null;
try {
s = newConn.createStatement();
// stat.execute("CALL
// SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.storage.pageCacheSize',
// '64')");
// stat.execute("CALL
// SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.storage.pageSize',
// '8192')");
} finally {
JdbcUtils.closeSilently(s);
}
} else if (url.startsWith("jdbc:hsqldb:")) {
// HSQLDB: use a WRITE_DELAY of 1 second
Statement s = null;
try {
s = newConn.createStatement();
s.execute("SET WRITE_DELAY 1");
} finally {
JdbcUtils.closeSilently(s);
}
}
return newConn;
}
/**
* Open the database connection.
*/
void openConnection() throws SQLException {
conn = openNewConnection();
stat = conn.createStatement();
}
/**
* Close the database connection.
*/
void closeConnection() throws SQLException {
// if(!serverHSQLDB && url.startsWith("jdbc:hsqldb:")) {
// stat.execute("SHUTDOWN");
// }
conn.close();
stat = null;
conn = null;
}
/**
* Initialize the SQL statement translation of this database.
*
* @param prop the properties with the translations to use
*/
void setTranslations(Properties prop) {
String databaseType = url.substring("jdbc:".length());
databaseType = databaseType.substring(0, databaseType.indexOf(':'));
for (Object k : prop.keySet()) {
String key = (String) k;
if (key.startsWith(databaseType + ".")) {
String pattern = key.substring(databaseType.length() + 1);
pattern = StringUtils.replaceAll(pattern, "_", " ");
pattern = StringUtils.toUpperEnglish(pattern);
String replacement = prop.getProperty(key);
replace.add(new String[]{pattern, replacement});
}
}
}
/**
* Prepare a SQL statement.
*
* @param sql the SQL statement
* @return the prepared statement
*/
PreparedStatement prepare(String sql) throws SQLException {
sql = getSQL(sql);
return conn.prepareStatement(sql);
}
private String getSQL(String sql) {
for (String[] pair : replace) {
String pattern = pair[0];
String replacement = pair[1];
sql = StringUtils.replaceAll(sql, pattern, replacement);
}
return sql;
}
/**
* Start the benchmark.
*
* @param bench the benchmark
* @param action the action
*/
void start(Bench bench, String action) {
this.currentAction = bench.getName() + ": " + action;
this.startTime = System.currentTimeMillis();
}
/**
* This method is called when the test run ends. This will stop collecting
* data.
*/
void end() {
long time = System.currentTimeMillis() - startTime;
log(currentAction, "ms", (int) time);
if (test.collect) {
totalTime += time;
}
}
/**
* Drop a table. Errors are ignored.
*
* @param table the table name
*/
void dropTable(String table) {
try {
update("DROP TABLE " + table);
} catch (Exception e) {
// ignore - table may not exist
}
}
/**
* Execute an SQL statement.
*
* @param prep the prepared statement
* @param traceMessage the trace message
*/
void update(PreparedStatement prep, String traceMessage) throws SQLException {
test.trace(traceMessage);
prep.executeUpdate();
if (test.collect) {
executedStatements++;
}
}
/**
* Execute an SQL statement.
*
* @param sql the SQL statement
*/
void update(String sql) throws SQLException {
sql = getSQL(sql);
if (sql.trim().length() > 0) {
if (test.collect) {
executedStatements++;
}
stat.execute(sql);
} else {
System.out.println("?");
}
}
/**
* Enable or disable auto-commit.
*
* @param b false to disable
*/
void setAutoCommit(boolean b) throws SQLException {
conn.setAutoCommit(b);
}
/**
* Commit a transaction.
*/
void commit() throws SQLException {
conn.commit();
}
/**
* Roll a transaction back.
*/
void rollback() throws SQLException {
conn.rollback();
}
/**
* Print trace information if trace is enabled.
*
* @param action the action
* @param i the current value
* @param max the maximum value
*/
void trace(String action, int i, int max) {
if (trace) {
long time = System.currentTimeMillis();
if (i == 0 || lastTrace == 0) {
lastTrace = time;
} else if (time > lastTrace + 1000) {
System.out.println(action + ": " + ((100 * i / max) + "%"));
lastTrace = time;
}
}
}
/**
* If data collection is enabled, add the currently used memory size to the
* log.
*
* @param bench the benchmark
* @param action the action
*/
void logMemory(Bench bench, String action) {
log(bench.getName() + ": " + action, "MB", TestBase.getMemoryUsed());
}
/**
* If data collection is enabled, add this information to the log.
*
* @param action the action
* @param scale the scale
* @param value the value
*/
void log(String action, String scale, int value) {
if (test.collect) {
results.add(new Object[] { action, scale, Integer.valueOf(value) });
}
}
/**
* Execute a query.
*
* @param prep the prepared statement
* @return the result set
*/
ResultSet query(PreparedStatement prep) throws SQLException {
// long time = System.currentTimeMillis();
ResultSet rs = prep.executeQuery();
// time = System.currentTimeMillis() - time;
// if(time > 100) {
// System.out.println("time="+time);
// }
if (test.collect) {
executedStatements++;
}
return rs;
}
/**
* Execute a query and read all rows.
*
* @param prep the prepared statement
*/
void queryReadResult(PreparedStatement prep) throws SQLException {
ResultSet rs = prep.executeQuery();
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
while (rs.next()) {
for (int i = 0; i < columnCount; i++) {
rs.getString(i + 1);
}
}
}
/**
* Get the number of executed statements.
*
* @return the number of statements
*/
int getExecutedStatements() {
return executedStatements;
}
/**
* Get the database id.
*
* @return the id
*/
int getId() {
return id;
}
}