/*
* ====================================================================
* Copyright (c) 2004-2010 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.db;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.EnumMap;
import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.SqlJetTransactionMode;
import org.tmatesoft.sqljet.core.internal.SqlJetPagerJournalMode;
import org.tmatesoft.sqljet.core.internal.SqlJetSafetyLevel;
import org.tmatesoft.sqljet.core.table.ISqlJetBusyHandler;
import org.tmatesoft.sqljet.core.table.SqlJetDb;
import org.tmatesoft.sqljet.core.table.SqlJetTimeoutBusyHandler;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc17.db.SvnNodesPristineTrigger;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbStatements;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @author TMate Software Ltd.
*/
public class SVNSqlJetDb {
public static enum Mode {
/** open the database read-only */
ReadOnly,
/** open the database read-write */
ReadWrite,
/** open/create the database read-write */
RWCreate
};
private static final ISqlJetBusyHandler DEFAULT_BUSY_HANDLER = new SqlJetTimeoutBusyHandler(10000);
private static boolean logTransactions = "true".equalsIgnoreCase(System.getProperty("svnkit.log.transactions", "false"));
private static SqlJetPagerJournalMode ourPagerJournalMode = SqlJetPagerJournalMode.DELETE;
private SqlJetDb db;
private EnumMap<SVNWCDbStatements, SVNSqlJetStatement> statements;
private int openCount = 0;
private SVNSqlJetDb temporaryDb;
private boolean temporaryDbInMemory;
private SVNSqlJetDb(SqlJetDb db, boolean temporaryDbInMemory) {
this.db = db;
this.temporaryDbInMemory = temporaryDbInMemory;
statements = new EnumMap<SVNWCDbStatements, SVNSqlJetStatement>(SVNWCDbStatements.class);
}
public SqlJetDb getDb() {
return db;
}
public int getOpenCount() {
return openCount;
}
public void close() throws SVNException {
if (temporaryDb != null) {
try {
temporaryDb.close();
} catch (SVNException e) {
//
}
temporaryDb = null;
}
if (db != null) {
try {
db.close();
} catch (SqlJetException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.SQLITE_ERROR, e);
SVNErrorManager.error(err, SVNLogType.WC);
}
}
}
public static void setJournalMode(SqlJetPagerJournalMode journalMode) {
ourPagerJournalMode = journalMode == null ? SqlJetPagerJournalMode.DELETE : journalMode;
}
public static SqlJetPagerJournalMode getJournalMode() {
return ourPagerJournalMode;
}
public static SVNSqlJetDb open(File sdbAbsPath, Mode mode) throws SVNException {
return open(sdbAbsPath, mode, getJournalMode(), false);
}
public static SVNSqlJetDb open(File sdbAbsPath, Mode mode, SqlJetPagerJournalMode journalMode, boolean temporaryDbInMemory) throws SVNException {
if (mode != Mode.RWCreate) {
if (!sdbAbsPath.exists()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "File not found ''{0}''", sdbAbsPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
}
if (journalMode == null) {
journalMode = getJournalMode();
}
try {
SqlJetDb db = SqlJetDb.open(sdbAbsPath, mode != Mode.ReadOnly);
db.setBusyHandler(DEFAULT_BUSY_HANDLER);
db.setSafetyLevel(SqlJetSafetyLevel.OFF);
db.setJournalMode(journalMode);
return new SVNSqlJetDb(db, temporaryDbInMemory);
} catch (SqlJetException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.SQLITE_ERROR, e);
SVNErrorManager.error(err, SVNLogType.WC);
return null;
}
}
public SVNSqlJetDb getTemporaryDb() throws SVNException {
if (temporaryDb == null) {
try {
temporaryDb = new SVNSqlJetDb(getDb().getTemporaryDatabase(temporaryDbInMemory), temporaryDbInMemory);
} catch (SqlJetException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.SQLITE_ERROR, e);
SVNErrorManager.error(err, SVNLogType.WC);
return null;
}
}
return temporaryDb;
}
public SVNSqlJetStatement getStatement(SVNWCDbStatements statementIndex) throws SVNException {
assert (statementIndex != null);
// SVNDebugLog.getDefaultLog().log(SVNLogType.WC, new StackTraceLog(statementIndex.toString()), Level.INFO);
SVNSqlJetStatement stmt = statements.get(statementIndex);
if (stmt == null) {
stmt = prepareStatement(statementIndex);
statements.put(statementIndex, stmt);
} else {
if (stmt instanceof SVNSqlJetInsertStatement
|| stmt instanceof SVNSqlJetUpdateStatement
|| stmt instanceof SVNSqlJetDeleteStatement) {
String targetTableName = ((SVNSqlJetTableStatement) stmt).getTableName();
if (SVNWCDbSchema.NODES.toString().equals(targetTableName)) {
SvnNodesPristineTrigger trigger = new SvnNodesPristineTrigger();
((SVNSqlJetTableStatement) stmt).addTrigger(trigger);
}
}
}
if (stmt != null && stmt.isNeedsReset()) {
stmt.reset();
}
return stmt;
}
private SVNSqlJetStatement prepareStatement(SVNWCDbStatements statementIndex) throws SVNException {
final Class<? extends SVNSqlJetStatement> statementClass = statementIndex.getStatementClass();
SVNErrorManager.assertionFailure(statementClass != null, String.format("Statement '%s' not defined", statementIndex.toString()), SVNLogType.WC);
if (statementClass == null) {
return null;
}
try {
final Constructor<? extends SVNSqlJetStatement> constructor = statementClass.getConstructor(SVNSqlJetDb.class);
final SVNSqlJetStatement stmt = constructor.newInstance(this);
return stmt;
} catch (Exception e) {
SVNErrorCode errorCode = SVNErrorCode.UNSUPPORTED_FEATURE;
String message = e.getMessage() != null ? e.getMessage() : errorCode.getDescription();
SVNErrorMessage err = SVNErrorMessage.create(errorCode, message, new Object[0], SVNErrorMessage.TYPE_ERROR, e);
SVNErrorManager.error(err, SVNLogType.WC);
return null;
}
}
public void execStatement(SVNWCDbStatements statementIndex) throws SVNException {
final SVNSqlJetStatement statement = getStatement(statementIndex);
if (statement != null) {
statement.exec();
}
}
public static void createSqlJetError(SqlJetException e) throws SVNException {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.SQLITE_ERROR, e);
SVNErrorManager.error(err, SVNLogType.WC);
}
public void beginTransaction(SqlJetTransactionMode mode) throws SVNException {
if (mode != null) {
openCount++;
if (isLogTransactions()) {
logCall("Being transaction request (" + openCount + "): " + mode, 5);
}
if (isNeedStartTransaction(mode)) {
try {
db.beginTransaction(mode);
if (isLogTransactions()) {
SVNDebugLog.getDefaultLog().logFine(SVNLogType.DEFAULT, "transaction started");
}
} catch (SqlJetException e) {
createSqlJetError(e);
}
}
} else {
SVNErrorManager.assertionFailure(mode != null, "transaction mode is null", SVNLogType.WC);
}
}
private boolean isNeedStartTransaction(SqlJetTransactionMode mode) {
if (!db.isInTransaction()) {
return true;
}
SqlJetTransactionMode dbMode = db.getTransactionMode();
return mode != dbMode && (SqlJetTransactionMode.WRITE == mode || SqlJetTransactionMode.EXCLUSIVE == mode) && SqlJetTransactionMode.READ_ONLY == dbMode;
}
public void commit() throws SVNException {
if (openCount > 0) {
openCount--;
if (isLogTransactions()) {
logCall("Commit transaction request (" + openCount + ")", 5);
}
if (openCount == 0) {
try {
db.commit();
if (isLogTransactions()) {
SVNDebugLog.getDefaultLog().logFine(SVNLogType.DEFAULT, "transaction committed");
}
} catch (SqlJetException e) {
createSqlJetError(e);
}
}
} else {
SVNErrorManager.assertionFailure(openCount > 0, "no opened transactions", SVNLogType.WC);
}
}
public void verifyNoWork() {
}
public void runTransaction(final SVNSqlJetTransaction transaction) throws SVNException {
runTransaction(transaction, SqlJetTransactionMode.WRITE);
}
public void runTransaction(final SVNSqlJetTransaction transaction, SqlJetTransactionMode mode) throws SVNException {
try {
beginTransaction(mode);
transaction.transaction(SVNSqlJetDb.this);
} catch (SqlJetException e) {
try {
db.rollback();
} catch (SqlJetException e1) {
e1.initCause(e);
SVNErrorMessage err1 = SVNErrorMessage.create(SVNErrorCode.SQLITE_ERROR, e1);
SVNErrorManager.error(err1, SVNLogType.DEFAULT);
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.SQLITE_ERROR, e);
SVNErrorManager.error(err, SVNLogType.DEFAULT);
} finally {
commit();
}
}
public void rollback() throws SVNException {
try {
db.rollback();
} catch (SqlJetException e1) {
SVNErrorMessage err1 = SVNErrorMessage.create(SVNErrorCode.SQLITE_ERROR, e1);
SVNErrorManager.error(err1, SVNLogType.DEFAULT);
}
}
public boolean hasTable(String tableName) throws SVNException {
try {
return tableName != null && db.getSchema().getTableNames().contains(tableName);
} catch (SqlJetException e) {
SVNErrorMessage err1 = SVNErrorMessage.create(SVNErrorCode.SQLITE_ERROR, e);
SVNErrorManager.error(err1, SVNLogType.DEFAULT);
}
return false;
}
private void logCall(String message, int count) {
if (isLogTransactions()) {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
StringBuffer sb = new StringBuffer();
sb.append(message);
sb.append(":\n");
for (int i = 0; i < trace.length && i < count; i++) {
sb.append(trace[i].getClassName());
sb.append('.');
sb.append(trace[i].getMethodName());
sb.append(':');
sb.append(trace[i].getLineNumber());
sb.append('\n');
}
SVNDebugLog.getDefaultLog().logFine(SVNLogType.DEFAULT, message.toString());
}
}
private static boolean isLogTransactions() {
return logTransactions;
}
}