/* * ==================================================================== * 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; } }