diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-15 17:32:48 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-15 17:32:48 +0000 |
commit | e2f541c98dfa4081fa3ab3d28f08ea2309281884 (patch) | |
tree | cb721a55bc88753ddeb9754dc98ef45e2850ce30 /src/svnqt/cache | |
download | tdesvn-e2f541c98dfa4081fa3ab3d28f08ea2309281884.tar.gz tdesvn-e2f541c98dfa4081fa3ab3d28f08ea2309281884.zip |
Added KDE3 version of kdesvn
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kdesvn@1103685 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/svnqt/cache')
-rw-r--r-- | src/svnqt/cache/DatabaseException.cpp | 12 | ||||
-rw-r--r-- | src/svnqt/cache/DatabaseException.hpp | 35 | ||||
-rw-r--r-- | src/svnqt/cache/LogCache.cpp | 468 | ||||
-rw-r--r-- | src/svnqt/cache/LogCache.hpp | 41 | ||||
-rw-r--r-- | src/svnqt/cache/ReposLog.cpp | 535 | ||||
-rw-r--r-- | src/svnqt/cache/ReposLog.hpp | 70 | ||||
-rw-r--r-- | src/svnqt/cache/sqlite3/README | 32 | ||||
-rw-r--r-- | src/svnqt/cache/sqlite3/qsql_sqlite3.cpp | 485 | ||||
-rw-r--r-- | src/svnqt/cache/sqlite3/qsql_sqlite3.h | 90 | ||||
-rw-r--r-- | src/svnqt/cache/sqlite3/qsqlcachedresult.cpp | 260 | ||||
-rw-r--r-- | src/svnqt/cache/sqlite3/qsqlcachedresult.h | 81 | ||||
-rw-r--r-- | src/svnqt/cache/test/CMakeLists.txt | 19 | ||||
-rw-r--r-- | src/svnqt/cache/test/sqlite.cpp | 111 | ||||
-rw-r--r-- | src/svnqt/cache/test/testconfig.h.in | 8 |
14 files changed, 2247 insertions, 0 deletions
diff --git a/src/svnqt/cache/DatabaseException.cpp b/src/svnqt/cache/DatabaseException.cpp new file mode 100644 index 0000000..85812ab --- /dev/null +++ b/src/svnqt/cache/DatabaseException.cpp @@ -0,0 +1,12 @@ +#include "DatabaseException.hpp" + +/*! + \fn svn::cache::DatabaseException::DatabaseException(const QString&msg,int aNumber)throw() + */ +svn::cache::DatabaseException::DatabaseException(const QString&msg,int aNumber)throw() + : Exception(msg),m_number(aNumber) +{ + if (aNumber>-1) { + setMessage(QString("(Code %1) %2").arg(aNumber).arg(msg)); + } +} diff --git a/src/svnqt/cache/DatabaseException.hpp b/src/svnqt/cache/DatabaseException.hpp new file mode 100644 index 0000000..85e5ce4 --- /dev/null +++ b/src/svnqt/cache/DatabaseException.hpp @@ -0,0 +1,35 @@ +#ifndef _DATABASE_EXCEPTION_HPP +#define _DATABASE_EXCEPTION_HPP + +#include "svnqt/exception.hpp" + +namespace svn +{ +namespace cache +{ + +class SVNQT_EXPORT DatabaseException:public svn::Exception +{ + private: + DatabaseException()throw(); + int m_number; + + public: + DatabaseException(const QString&msg)throw() + : Exception(msg),m_number(-1) + {} + + DatabaseException(const DatabaseException&src)throw() + : Exception(src.msg()),m_number(src.number()) + {} + DatabaseException(const QString&msg,int aNumber)throw(); + virtual ~DatabaseException()throw(){} + int number() const + { + return m_number; + } +}; + +} +} +#endif diff --git a/src/svnqt/cache/LogCache.cpp b/src/svnqt/cache/LogCache.cpp new file mode 100644 index 0000000..6356c6f --- /dev/null +++ b/src/svnqt/cache/LogCache.cpp @@ -0,0 +1,468 @@ +#include "LogCache.hpp" + +#include <qdir.h> +#include <qsql.h> +#include <qsqldatabase.h> +#if QT_VERSION < 0x040000 +#include <qthreadstorage.h> +#else +#include <QMutex> +#include <QThreadStorage> +#include <QSqlError> +#include <QSqlQuery> +#include <QVariant> +#endif +#include <qmap.h> + +#include "svnqt/path.hpp" +#include "svnqt/cache/DatabaseException.hpp" + +#ifndef NO_SQLITE3 +#include "sqlite3/qsql_sqlite3.h" +#define SQLTYPE "QSQLITE3" +#else +#define SQLTYPE "QSQLITE" +#endif + +#define SQLMAIN "logmain-logcache" +#define SQLMAINTABLE "logdb" + +namespace svn { +namespace cache { + +LogCache* LogCache::mSelf = 0; + +class ThreadDBStore +{ +public: + ThreadDBStore(){ +#if QT_VERSION < 0x040000 + m_DB=0; +#else + m_DB=QSqlDatabase(); +#endif + } + ~ThreadDBStore(){ +#if QT_VERSION < 0x040000 + m_DB=0; +#else + m_DB=QSqlDatabase(); +#endif + QSqlDatabase::removeDatabase(key); + QMap<QString,QString>::Iterator it; + for (it=reposCacheNames.begin();it!=reposCacheNames.end();++it) { +#if QT_VERSION < 0x040000 + QSqlDatabase::removeDatabase(it.data()); +#else + QSqlDatabase::removeDatabase(it.value()); +#endif + } + } + + QDataBase m_DB; + QString key; + QMap<QString,QString> reposCacheNames; +}; + +class LogCacheData +{ + +protected: + QMutex m_singleDbMutex; + +public: + LogCacheData(){} + ~LogCacheData(){ + if (m_mainDB.hasLocalData()) { + m_mainDB.setLocalData(0L); + } + } + + bool checkReposDb(QDataBase aDb) + { +#if QT_VERSION < 0x040000 + if (!aDb) { + return false; + } + if (!aDb->open()) { + return false; + } +#else + if (!aDb.open()) { + return false; + } +#endif + + QSqlQuery _q(QString::null, aDb); +#if QT_VERSION < 0x040000 + QStringList list = aDb->tables(); +#else + QStringList list = aDb.tables(); +#endif + +#if QT_VERSION < 0x040000 + if (list.find("logentries")==list.end()) { + aDb->transaction(); +#else + if (list.indexOf("logentries")==-1) { + aDb.transaction(); +#endif + _q.exec("CREATE TABLE \"logentries\" (\"revision\" INTEGER UNIQUE,\"date\" INTEGER,\"author\" TEXT, \"message\" TEXT)"); +#if QT_VERSION < 0x040000 + aDb->commit(); +#else + aDb.commit(); +#endif + } +#if QT_VERSION < 0x040000 + if (list.find("changeditems")==list.end()) { + aDb->transaction(); +#else + if (list.indexOf("changeditems")==-1) { + aDb.transaction(); +#endif + _q.exec("CREATE TABLE \"changeditems\" (\"revision\" INTEGER,\"changeditem\" TEXT,\"action\" TEXT,\"copyfrom\" TEXT,\"copyfromrev\" INTEGER, PRIMARY KEY(revision,changeditem,action))"); +#if QT_VERSION < 0x040000 + aDb->commit(); +#else + aDb.commit(); +#endif + } +#if QT_VERSION < 0x040000 + list = aDb->tables(); + if (list.find("logentries")==list.end() || list.find("changeditems")==list.end()) { +#else + list = aDb.tables(); + if (list.indexOf("logentries")==-1 || list.indexOf("changeditems")==-1) { +#endif + return false; + } + return true; + } + + QString createReposDB(const svn::Path&reposroot) { + QMutexLocker locker( &m_singleDbMutex ); + + QDataBase _mdb = getMainDB(); + + QSqlQuery query1(QString::null,_mdb); + QString q("insert into "+QString(SQLMAINTABLE)+" (reposroot) VALUES('"+reposroot+"')"); +#if QT_VERSION < 0x040000 + _mdb->transaction(); +#else + _mdb.transaction(); +#endif + + query1.exec(q); +#if QT_VERSION < 0x040000 + _mdb->commit(); +#else + _mdb.commit(); +#endif + QSqlQuery query(QString::null,_mdb); + query.prepare(s_reposSelect); + query.bindValue(0,reposroot.native()); + query.exec(); + QString db; +#if QT_VERSION < 0x040000 + if (query.lastError().type()==QSqlError::None && query.next()) { +#else + if (query.lastError().type()==QSqlError::NoError && query.next()) { +#endif + db = query.value(0).toString(); + } + else { + qDebug("Error select_01: %s (%s)",query.lastError().text().TOUTF8().data(), + query.lastQuery().TOUTF8().data()); + } + if (!db.isEmpty()) { + QString fulldb = m_BasePath+"/"+db+".db"; + QDataBase _db = QSqlDatabase::addDatabase(SQLTYPE,"tmpdb"); +#if QT_VERSION < 0x040000 + _db->setDatabaseName(fulldb); +#else + _db.setDatabaseName(fulldb); +#endif + if (!checkReposDb(_db)) { + } + QSqlDatabase::removeDatabase("tmpdb"); + } + return db; + } + + QDataBase getReposDB(const svn::Path&reposroot) { +#if QT_VERSION < 0x040000 + if (!getMainDB()) { + return 0; +#else + if (!getMainDB().isValid()) { + return QDataBase(); +#endif + } + bool checkDone = false; + // make sure path is correct eg. without traling slashes. + QString dbFile; + QSqlQuery c(QString::null,getMainDB()); + c.prepare(s_reposSelect); + c.bindValue(0,reposroot.native()); + c.exec(); + +#if QT_VERSION < 0x040000 + //qDebug("Check for path: "+reposroot.native()); +#endif + + // only the first one + if ( c.next() ) { +#if QT_VERSION < 0x040000 +/* qDebug( c.value(0).toString() + ": " + + c.value(0).toString() );*/ +#endif + dbFile = c.value(0).toString(); + } + if (dbFile.isEmpty()) { + dbFile = createReposDB(reposroot); + if (dbFile.isEmpty()) { +#if QT_VERSION < 0x040000 + return 0; +#else + return QSqlDatabase(); +#endif + } + checkDone=true; + } + if (m_mainDB.localData()->reposCacheNames.find(dbFile)!=m_mainDB.localData()->reposCacheNames.end()) { + return QSqlDatabase::database(m_mainDB.localData()->reposCacheNames[dbFile]); + } + int i = 0; + QString _key = dbFile; + while (QSqlDatabase::contains(_key)) { + _key = QString("%1-%2").arg(dbFile).arg(i++); + } +// qDebug("The repository key is now: %s",_key.TOUTF8().data()); + QDataBase _db = QSqlDatabase::addDatabase(SQLTYPE,_key); +#if QT_VERSION < 0x040000 + if (!_db) { + return 0; + } +#endif + QString fulldb = m_BasePath+"/"+dbFile+".db"; +#if QT_VERSION < 0x040000 + _db->setDatabaseName(fulldb); +#else + _db.setDatabaseName(fulldb); +#endif +// qDebug("try database open %s",fulldb.TOUTF8().data()); + if (!checkReposDb(_db)) { + qDebug("no DB opened"); +#if QT_VERSION < 0x040000 + _db = 0; +#else + _db = QSqlDatabase(); +#endif + } else { + qDebug("Insert into map"); + m_mainDB.localData()->reposCacheNames[dbFile]=_key; + } + return _db; + } + + QDataBase getMainDB()const + { + if (!m_mainDB.hasLocalData()) { + unsigned i=0; + QString _key = SQLMAIN; + while (QSqlDatabase::contains(_key)) { + _key.sprintf("%s-%i",SQLMAIN,i++); + } + qDebug("The key is now: %s",_key.TOUTF8().data()); + + QDataBase db = QSqlDatabase::addDatabase(SQLTYPE,_key); +#if QT_VERSION < 0x040000 + db->setDatabaseName(m_BasePath+"/maindb.db"); + if (!db->open()) { +#else + db.setDatabaseName(m_BasePath+"/maindb.db"); + if (!db.open()) { +#endif +#if QT_VERSION < 0x040000 + qWarning("Failed to open main database: " + db->lastError().text()); +#endif + } else { + m_mainDB.setLocalData(new ThreadDBStore); + m_mainDB.localData()->key = _key; + m_mainDB.localData()->m_DB = db; + } + } + if (m_mainDB.hasLocalData()) { + return m_mainDB.localData()->m_DB; + } else { +#if QT_VERSION < 0x040000 + return 0; +#else + return QSqlDatabase(); +#endif + } + } + QString m_BasePath; + + mutable QThreadStorage<ThreadDBStore*> m_mainDB; + + static const QString s_reposSelect; +}; + + +QString LogCache::s_CACHE_FOLDER="logcache"; +const QString LogCacheData::s_reposSelect=QString("SELECT id from ")+QString(SQLMAINTABLE)+QString(" where reposroot=? ORDER by id DESC"); + +/*! + \fn svn::cache::LogCache::LogCache() + */ +LogCache::LogCache() +{ + m_BasePath = QDir::HOMEDIR()+"/.svnqt"; + setupCachePath(); +} + +LogCache::LogCache(const QString&aBasePath) +{ + if (mSelf) { + delete mSelf; + } + mSelf=this; + if (aBasePath.isEmpty()) { + m_BasePath=QDir::HOMEDIR()+"/.svnqt"; + } else { + m_BasePath=aBasePath; + } + setupCachePath(); +} + + +LogCache::~LogCache() +{ +} + +/*! + \fn svn::cache::LogCache::setupCachePath() + */ +void LogCache::setupCachePath() +{ + m_CacheData = new LogCacheData; + m_CacheData->m_BasePath=m_BasePath; + QDir d; + if (!d.exists(m_BasePath)) { + d.mkdir(m_BasePath); + } + m_BasePath=m_BasePath+"/"+s_CACHE_FOLDER; + if (!d.exists(m_BasePath)) { + d.mkdir(m_BasePath); + } + m_CacheData->m_BasePath=m_BasePath; + if (d.exists(m_BasePath)) { + setupMainDb(); + } +} + +void LogCache::setupMainDb() +{ +#ifndef NO_SQLITE3 + if (!QSqlDatabase::isDriverAvailable(SQLTYPE)) { + QSqlDatabase::registerSqlDriver(SQLTYPE,new QSqlDriverCreator<QSQLite3Driver>); + } +#endif + QDataBase mainDB = m_CacheData->getMainDB(); +#if QT_VERSION < 0x040000 + if (!mainDB || !mainDB->open()) { + qWarning("Failed to open main database: " + (mainDB?mainDB->lastError().text():"No database object.")); +#else + if (!mainDB.isValid()) { + qWarning("Failed to open main database."); +#endif + } else { + QSqlQuery q(QString::null, mainDB); +#if QT_VERSION < 0x040000 + mainDB->transaction(); +#else + mainDB.transaction(); +#endif + if (!q.exec("CREATE TABLE IF NOT EXISTS \""+QString(SQLMAINTABLE)+"\" (\"reposroot\" TEXT,\"id\" INTEGER PRIMARY KEY NOT NULL);")) { +#if QT_VERSION < 0x040000 + qWarning("Failed create main database: " + mainDB->lastError().text()); +#endif + } +#if QT_VERSION < 0x040000 + mainDB->commit(); +#else + mainDB.commit(); +#endif + } +} + +} +} + + +/*! + \fn svn::cache::LogCache::self() + */ +svn::cache::LogCache* svn::cache::LogCache::self() +{ + if (!mSelf) { + mSelf=new LogCache(); + } + return mSelf; +} + + +/*! + \fn svn::cache::LogCache::reposDb() + */ +QDataBase svn::cache::LogCache::reposDb(const QString&aRepository) +{ +// qDebug("reposDB"); + return m_CacheData->getReposDB(aRepository); +} + + +/*! + \fn svn::cache::LogCache::cachedRepositories()const + */ +QStringList svn::cache::LogCache::cachedRepositories()const +{ + static QString s_q(QString("select \"reposroot\" from ")+QString(SQLMAINTABLE)+QString("order by reposroot")); + QDataBase mainDB = m_CacheData->getMainDB(); + QStringList _res; +#if QT_VERSION < 0x040000 + if (!mainDB || !mainDB->open()) { +#else + if (!mainDB.isValid()) { +#endif + qWarning("Failed to open main database."); + return _res; + } + QSqlQuery cur(QString::null,mainDB); + cur.prepare(s_q); + if (!cur.exec()) { + qDebug(cur.lastError().text().TOUTF8().data()); + throw svn::cache::DatabaseException(QString("Could not retrieve values: ")+cur.lastError().text()); + return _res; + } + while (cur.next()) { + _res.append(cur.value(0).toString()); + } + + return _res; +} + +bool svn::cache::LogCache::valid()const +{ + QDataBase mainDB = m_CacheData->getMainDB(); +#if QT_VERSION < 0x040000 + if (!mainDB || !mainDB->open()) { +#else + if (!mainDB.isValid()) { +#endif + return false; + } + return true; +} diff --git a/src/svnqt/cache/LogCache.hpp b/src/svnqt/cache/LogCache.hpp new file mode 100644 index 0000000..9e76697 --- /dev/null +++ b/src/svnqt/cache/LogCache.hpp @@ -0,0 +1,41 @@ +#ifndef _LOG_CACHE_HPP +#define _LOG_CACHE_HPP + +#include <qstring.h> +#include <qdir.h> + +#include "svnqt/svnqt_defines.hpp" +#include "svnqt/shared_pointer.hpp" + +namespace svn { + namespace cache { + + class LogCacheData; + + class SVNQT_EXPORT LogCache + { + private: + svn::SharedPointer<LogCacheData> m_CacheData; + + protected: + LogCache(); + static LogCache* mSelf; + QString m_BasePath; + static QString s_CACHE_FOLDER; + void setupCachePath(); + void setupMainDb(); + + public: + ///! should used for testing only! + LogCache(const QString&aBasePath); + virtual ~LogCache(); + static LogCache* self(); + QDataBase reposDb(const QString&aRepository); + QStringList cachedRepositories()const; + + bool valid()const; + }; + } +} + +#endif diff --git a/src/svnqt/cache/ReposLog.cpp b/src/svnqt/cache/ReposLog.cpp new file mode 100644 index 0000000..89be2d0 --- /dev/null +++ b/src/svnqt/cache/ReposLog.cpp @@ -0,0 +1,535 @@ +#include "ReposLog.hpp" + +#include "LogCache.hpp" +#include "svnqt/info_entry.hpp" +#include "svnqt/svnqttypes.hpp" +#include "svnqt/client.hpp" +#include "svnqt/context_listener.hpp" +#include "svnqt/cache/DatabaseException.hpp" + +#include <qsqldatabase.h> + +#if QT_VERSION < 0x040000 +#else +#include <QSqlError> +#include <QSqlQuery> +#include <QVariant> +#define Q_LLONG qlonglong +#endif + +/*! + \fn svn::cache::ReposLog::ReposLog(svn::Client*aClient,const QString&) + */ +svn::cache::ReposLog::ReposLog(svn::Client*aClient,const QString&aRepository) + :m_Client(), +#if QT_VERSION < 0x040000 + m_Database(0), +#else + m_Database(), +#endif + m_ReposRoot(aRepository),m_latestHead(svn::Revision::UNDEFINED) +{ + m_Client=aClient; + ContextP ctx = m_Client->getContext(); + if (!aRepository.isEmpty()) { + m_Database = LogCache::self()->reposDb(aRepository); + } +} + + +/*! + \fn svn::cache::ReposLog::latestHeadRev() + */ +svn::Revision svn::cache::ReposLog::latestHeadRev() +{ + if (!m_Client||m_ReposRoot.isEmpty()) { + return svn::Revision::UNDEFINED; + } +#if QT_VERSION < 0x040000 + if (!m_Database) { +#else + if (!m_Database.isValid()) { +#endif + m_Database = LogCache::self()->reposDb(m_ReposRoot); +#if QT_VERSION < 0x040000 + if (!m_Database) { +#else + if (!m_Database.isValid()) { +#endif + return svn::Revision::UNDEFINED; + } + } + /// no catch - exception has go trough... + qDebug("Getting headrev"); + svn::InfoEntries e = m_Client->info(m_ReposRoot,svn::DepthEmpty,svn::Revision::HEAD,svn::Revision::HEAD); + if (e.count()<1||e[0].reposRoot().isEmpty()) { + return svn::Revision::UNDEFINED; + } + qDebug("Getting headrev done"); + return e[0].revision(); +} + + +/*! + \fn svn::cache::ReposLog::latestCachedRev() + */ +svn::Revision svn::cache::ReposLog::latestCachedRev() +{ + if (m_ReposRoot.isEmpty()) { + return svn::Revision::UNDEFINED; + } +#if QT_VERSION < 0x040000 + if (!m_Database) { +#else + if (!m_Database.isValid()) { +#endif + m_Database = LogCache::self()->reposDb(m_ReposRoot); +#if QT_VERSION < 0x040000 + if (!m_Database) { +#else + if (!m_Database.isValid()) { +#endif + return svn::Revision::UNDEFINED; + } + } + QString q("select revision from 'logentries' order by revision DESC limit 1"); + QSqlQuery _q(QString::null, m_Database); + if (!_q.exec(q)) { + qDebug(_q.lastError().text().TOUTF8().data()); + return svn::Revision::UNDEFINED; + } + int _r; + if (_q.isActive() && _q.next()) { + //qDebug("Sel result: %s",_q.value(0).toString().TOUTF8().data()); + _r = _q.value(0).toInt(); + } else { + qDebug(_q.lastError().text().TOUTF8().data()); + return svn::Revision::UNDEFINED; + } + return _r; +} + +bool svn::cache::ReposLog::checkFill(svn::Revision&start,svn::Revision&end,bool checkHead) +{ +#if QT_VERSION < 0x040000 + if (!m_Database) { +#else + if (!m_Database.isValid()) { +#endif + m_Database = LogCache::self()->reposDb(m_ReposRoot); +#if QT_VERSION < 0x040000 + if (!m_Database) { +#else + if (!m_Database.isValid()) { +#endif + return false; + } + } + ContextP cp = m_Client->getContext(); + long long icount=0; + + svn::Revision _latest=latestCachedRev(); +// qDebug("Latest cached rev: %i",_latest.revnum()); +// qDebug("End revision is: %s",end.toString().TOUTF8().data()); + + if (checkHead && _latest.revnum()>=latestHeadRev().revnum()) { + return true; + } + + start=date2numberRev(start); + end=date2numberRev(end); + + // both should now one of START, HEAD or NUMBER + if (start==svn::Revision::HEAD || (end==svn::Revision::NUMBER && start==svn::Revision::NUMBER && start.revnum()>end.revnum())) { + svn::Revision tmp = start; + start = end; + end = tmp; + } + svn::Revision _rstart=_latest.revnum()+1; + svn::Revision _rend = end; + if (_rend==svn::Revision::UNDEFINED) { +// qDebug("Setting end to Head"); + _rend=svn::Revision::HEAD; + } + // no catch - exception should go outside. + if (_rstart==0){ + _rstart = 1; + } +// qDebug("Getting log %s -> %s",_rstart.toString().TOUTF8().data(),_rend.toString().TOUTF8().data()); + if (_rend==svn::Revision::HEAD) { + _rend=latestHeadRev(); + } + + if (_rend==svn::Revision::HEAD||_rend.revnum()>_latest.revnum()) { + LogEntriesMap _internal; +// qDebug("Retrieving from network."); + if (!m_Client->log(m_ReposRoot,_rstart,_rend,_internal,svn::Revision::UNDEFINED,true,false)) { + return false; + } + LogEntriesMap::ConstIterator it=_internal.begin(); + + for (;it!=_internal.end();++it) { + _insertLogEntry((*it)); + if (cp && cp->getListener()) { + //cp->getListener()->contextProgress(++icount,_internal.size()); + if (cp->getListener()->contextCancel()) { + throw DatabaseException(QString("Could not retrieve values: User cancel.")); + } + } + } + } + return true; +} + +bool svn::cache::ReposLog::fillCache(const svn::Revision&_end) +{ + svn::Revision end = _end; + svn::Revision start = latestCachedRev().revnum()+1; + return checkFill(start,end,false); +} + +/*! + \fn svn::cache::ReposLog::simpleLog(const svn::Revision&start,const svn::Revision&end,LogEntriesMap&target) + */ +bool svn::cache::ReposLog::simpleLog(LogEntriesMap&target,const svn::Revision&_start,const svn::Revision&_end,bool noNetwork) +{ + if (!m_Client||m_ReposRoot.isEmpty()) { + return false; + } + target.clear(); + ContextP cp = m_Client->getContext(); + + svn::Revision end = _end; + svn::Revision start = _start; + if (!noNetwork) { + if (!checkFill(start,end,true)) { + return false; + } + } else { + end=date2numberRev(end,noNetwork); + start=date2numberRev(start,noNetwork); + } + + if (end==svn::Revision::HEAD) { + end = latestCachedRev(); + } + if (start==svn::Revision::HEAD) { + start=latestCachedRev(); + } + static QString sCount("select count(*) from logentries where revision<=? and revision>=?"); + static QString sEntry("select revision,author,date,message from logentries where revision<=? and revision>=?"); + static QString sItems("select changeditem,action,copyfrom,copyfromrev from changeditems where revision=?"); + + QSqlQuery bcount(QString::null,m_Database); + bcount.prepare(sCount); + + QSqlQuery bcur(QString::null,m_Database); + bcur.prepare(sEntry); + + QSqlQuery cur(QString::null,m_Database); + cur.prepare(sItems); + + bcount.bindValue(0,Q_LLONG(end.revnum())); + bcount.bindValue(1,Q_LLONG(start.revnum())); + if (!bcount.exec()) { + qDebug(bcount.lastError().text().TOUTF8().data()); + throw svn::cache::DatabaseException(QString("Could not retrieve count: ")+bcount.lastError().text()); + return false; + } + bcount.next(); + if (bcount.value(0).toLongLong()<1) { + // we didn't found logs with this parameters + return false; + } + + bcur.bindValue(0,Q_LLONG(end.revnum())); + bcur.bindValue(1,Q_LLONG(start.revnum())); + + if (!bcur.exec()) { + qDebug(bcur.lastError().text().TOUTF8().data()); + throw svn::cache::DatabaseException(QString("Could not retrieve values: ")+bcur.lastError().text()); + return false; + } + Q_LLONG revision; + while(bcur.next()) { + revision = bcur.value(0).toLongLong(); + cur.bindValue(0,revision); + if (!cur.exec()) { + qDebug(cur.lastError().text().TOUTF8().data()); + throw svn::cache::DatabaseException(QString("Could not retrieve values: ")+cur.lastError().text() + ,cur.lastError().number()); + return false; + } + target[revision].revision=revision; + target[revision].author=bcur.value(1).toString(); + target[revision].date=bcur.value(2).toLongLong(); + target[revision].message=bcur.value(3).toString(); + while(cur.next()) { + LogChangePathEntry lcp; + QString ac = cur.value(1).toString(); +#if QT_VERSION < 0x040000 + lcp.action=ac[0].latin1(); +#else + lcp.action=ac[0].toLatin1(); +#endif + lcp.copyFromPath=cur.value(2).toString(); + lcp.path= cur.value(0).toString(); + lcp.copyFromRevision=cur.value(3).toLongLong(); + target[revision].changedPaths.push_back(lcp); + } + if (cp && cp->getListener()) { + if (cp->getListener()->contextCancel()) { + throw svn::cache::DatabaseException(QString("Could not retrieve values: User cancel.")); + return false; + } + } + } + return true; +} + + +/*! + \fn svn::cache::ReposLog::date2numberRev(const svn::Revision&) + */ +svn::Revision svn::cache::ReposLog::date2numberRev(const svn::Revision&aRev,bool noNetwork) +{ + if (aRev!=svn::Revision::DATE) { + return aRev; + } +#if QT_VERSION < 0x040000 + if (!m_Database) { +#else + if (!m_Database.isValid()) { +#endif + return svn::Revision::UNDEFINED; + } + static QString _q("select revision from logentries where date<? order by revision desc"); + QSqlQuery query("select revision,date from logentries order by revision desc limit 1",m_Database); + +#if QT_VERSION < 0x040000 + if (query.lastError().type()!=QSqlError::None) { +#else + if (query.lastError().type()!=QSqlError::NoError) { +#endif + qDebug(query.lastError().text().TOUTF8().data()); + } + bool must_remote=!noNetwork; + if (query.next()) { + if (query.value(1).toLongLong()>=aRev.date()) { + must_remote=false; + } + } + if (must_remote) { + svn::InfoEntries e = (m_Client->info(m_ReposRoot,svn::DepthEmpty,aRev,aRev));; + if (e.count()<1||e[0].reposRoot().isEmpty()) { + return aRev; + } + return e[0].revision(); + } + query.prepare(_q); + query.bindValue(0,Q_LLONG(aRev.date())); + query.exec(); +#if QT_VERSION < 0x040000 + if (query.lastError().type()!=QSqlError::None) { +#else + if (query.lastError().type()!=QSqlError::NoError) { +#endif + qDebug(query.lastError().text().TOUTF8().data()); + } + if (query.next()) { + return query.value(0).toInt(); + } + // not found... + if (noNetwork) { + return svn::Revision::UNDEFINED; + } + svn::InfoEntries e = (m_Client->info(m_ReposRoot,svn::DepthEmpty,svn::Revision::HEAD,svn::Revision::HEAD));; + if (e.count()<1||e[0].reposRoot().isEmpty()) { + return svn::Revision::UNDEFINED; + } + return e[0].revision(); +} + + +/*! + \fn svn::cache::ReposLog::insertLogEntry(const svn::LogEntry&) + */ +bool svn::cache::ReposLog::_insertLogEntry(const svn::LogEntry&aEntry) +{ + QSqlRecord *buffer; + +#if QT_VERSION < 0x040000 + m_Database->transaction(); + Q_LLONG j = aEntry.revision; +#else + m_Database.transaction(); + qlonglong j = aEntry.revision; +#endif + static QString qEntry("insert into logentries (revision,date,author,message) values (?,?,?,?)"); + static QString qPathes("insert into changeditems (revision,changeditem,action,copyfrom,copyfromrev) values (?,?,?,?,?)"); + QSqlQuery _q(QString::null,m_Database); + _q.prepare(qEntry); + _q.bindValue(0,j); + _q.bindValue(1,aEntry.date); + _q.bindValue(2,aEntry.author); + _q.bindValue(3,aEntry.message); + if (!_q.exec()) { +#if QT_VERSION < 0x040000 + m_Database->rollback(); +#else + m_Database.rollback(); +#endif + qDebug("Could not insert values: %s",_q.lastError().text().TOUTF8().data()); + qDebug(_q.lastQuery().TOUTF8().data()); + throw svn::cache::DatabaseException(QString("Could not insert values: ")+_q.lastError().text(),_q.lastError().number()); + } + _q.prepare(qPathes); + svn::LogChangePathEntries::ConstIterator cpit = aEntry.changedPaths.begin(); + for (;cpit!=aEntry.changedPaths.end();++cpit){ + _q.bindValue(0,j); + _q.bindValue(1,(*cpit).path); + _q.bindValue(2,QString(QChar((*cpit).action))); + _q.bindValue(3,(*cpit).copyFromPath); + _q.bindValue(4,Q_LLONG((*cpit).copyFromRevision)); + if (!_q.exec()) { +#if QT_VERSION < 0x040000 + m_Database->rollback(); +#else + m_Database.rollback(); +#endif + qDebug("Could not insert values: %s",_q.lastError().text().TOUTF8().data()); + qDebug(_q.lastQuery().TOUTF8().data()); + throw svn::cache::DatabaseException(QString("Could not insert values: ")+_q.lastError().text(),_q.lastError().number()); + } + } +#if QT_VERSION < 0x040000 + m_Database->commit(); +#else + m_Database.commit(); +#endif + return true; +} + +bool svn::cache::ReposLog::insertLogEntry(const svn::LogEntry&aEntry) +{ + return _insertLogEntry(aEntry); +} + + +/*! + \fn svn::cache::ReposLog::log(const svn::Path&,const svn::Revision&start, const svn::Revision&end,const svn::Revision&peg,svn::LogEntriesMap&target, bool strictNodeHistory,int limit)) + */ +bool svn::cache::ReposLog::log(const svn::Path&what,const svn::Revision&_start, const svn::Revision&_end,const svn::Revision&_peg,svn::LogEntriesMap&target, bool strictNodeHistory,int limit) +{ + static QString s_q("select logentries.revision,logentries.author,logentries.date,logentries.message from logentries where logentries.revision in (select changeditems.revision from changeditems where (changeditems.changeditem='%1' or changeditems.changeditem GLOB '%2/*') %3 GROUP BY changeditems.revision) ORDER BY logentries.revision DESC"); + + static QString s_e("select changeditem,action,copyfrom,copyfromrev from changeditems where changeditems.revision='%1'"); + + svn::Revision peg = date2numberRev(_peg,true); + svn::Revision end = date2numberRev(_end,true); + svn::Revision start = date2numberRev(_start,true); + QString query_string = QString(s_q).arg(what.native()).arg(what.native()).arg((peg==svn::Revision::UNDEFINED?"":QString(" AND revision<=%1").arg(peg.revnum()))); + if (peg==svn::Revision::UNDEFINED) { + peg = latestCachedRev(); + } + if (!itemExists(peg,what)) { + throw svn::cache::DatabaseException(QString("Entry '%1' does not exists at revision %2").arg(what.native()).arg(peg.toString())); + } + if (limit>0) { + query_string+=QString(" LIMIT %1").arg(limit); + } + QSqlQuery _q(QString::null,m_Database); + QSqlQuery _q2(QString::null,m_Database); + _q.prepare(query_string); + if (!_q.exec()) { + qDebug("Could not select values: %s",_q.lastError().text().TOUTF8().data()); + qDebug(_q.lastQuery().TOUTF8().data()); + throw svn::cache::DatabaseException(QString("Could not select values: ")+_q.lastError().text(),_q.lastError().number()); + } + while(_q.next()) { + Q_LLONG revision = _q.value(0).toLongLong(); + target[revision].revision=revision; + target[revision].author=_q.value(1).toString(); + target[revision].date=_q.value(2).toLongLong(); + target[revision].message=_q.value(3).toString(); + query_string=s_e.arg(revision); + _q2.prepare(query_string); + if (!_q2.exec()) { + qDebug("Could not select values: %s",_q2.lastError().text().TOUTF8().data()); + } else { + while (_q2.next()) { +#if QT_VERSION < 0x040000 + target[revision].changedPaths.push_back ( + LogChangePathEntry (_q2.value(0).toString(), + _q2.value(1).toString()[0], + _q2.value(2).toString(), + _q2.value(3).toLongLong() + ) + ); +#else + target[revision].changedPaths.push_back ( + LogChangePathEntry (_q2.value(0).toString(), + _q2.value(1).toChar().toLatin1(), + _q2.value(2).toString(), + _q2.value(3).toLongLong() + ) + ); +#endif + } + } + + } + return true; +} + + +/*! + \fn svn::cache::ReposLog::itemExists(const svn::Revision&,const QString&) + */ +bool svn::cache::ReposLog::itemExists(const svn::Revision&peg,const svn::Path&path) +{ + /// @todo this moment I have no idea how to check real with all moves and deletes of parent folders without a hell of sql statements so we make it quite simple: it exists if we found it. + + +#if 0 + static QString _s1("select revision from changeditems where changeditem='%1' and action='A' and revision<=%2 order by revision desc limit 1"); + QSqlQuery _q(QString::null,m_Database); + QString query_string=QString(_s1).arg(path.native()).arg(peg.revnum()); + if (!_q.exec(query_string)) { + qDebug("Could not select values: %s",_q.lastError().text().TOUTF8().data()); + qDebug(_q.lastQuery().TOUTF8().data()); + throw svn::cache::DatabaseException(QString("Could not select values: ")+_q.lastError().text(),_q.lastError().number()); + } + qDebug(_q.lastQuery().TOUTF8().data()); + + + svn::Path _p = path; + static QString _s2("select revision from changeditem where changeditem in (%1) and action='D' and revision>%2 and revision<=%3 order by revision desc limit 1"); + QStringList p_list; + while (_p.length()>0) { + p_list.append(QString("'%1'").arg(_p.native())); + _p.removeLast(); + } + query_string=QString(_s2).arg(p_list.join(",")).arg(); +#endif + return true; +} + +bool svn::cache::ReposLog::isValid()const +{ +#if QT_VERSION < 0x040000 + if (!m_Database) { +#else + if (!m_Database.isValid()) { +#endif + m_Database = LogCache::self()->reposDb(m_ReposRoot); +#if QT_VERSION < 0x040000 + if (!m_Database) { +#else + if (!m_Database.isValid()) { +#endif + return false; + } + } + return true; +} diff --git a/src/svnqt/cache/ReposLog.hpp b/src/svnqt/cache/ReposLog.hpp new file mode 100644 index 0000000..e81a5c9 --- /dev/null +++ b/src/svnqt/cache/ReposLog.hpp @@ -0,0 +1,70 @@ +#ifndef _REPOS_LOG_HPP +#define _REPOS_LOG_HPP + +#include "svnqt/svnqt_defines.hpp" +#include "svnqt/svnqttypes.hpp" +#include "svnqt/revision.hpp" + +#include <qsqldatabase.h> +#include <qstring.h> + +namespace svn +{ + +class Client; + +namespace cache +{ + +class SVNQT_EXPORT ReposLog +{ +protected: + svn::Client*m_Client; + mutable QDataBase m_Database; + QString m_ReposRoot; + svn::Revision m_latestHead; + //! internal insert. + bool _insertLogEntry(const svn::LogEntry&); + bool checkFill(svn::Revision&_start,svn::Revision&_end,bool checkHead); + +public: + ReposLog(svn::Client*aClient,const QString&aRepository=QString::null); + + QString ReposRoot() const + { + return m_ReposRoot; + } + + QDataBase Database() const + { + return m_Database; + } + //! search for latest head revision on network for assigned repository + svn::Revision latestHeadRev(); + //! return lates revision in cache + svn::Revision latestCachedRev(); + //! simple retrieves logentries + /*! + * This method acts on network, too for checking if there are new entries on server. + * + * @param target where to store the result + * @param start revision to start for search + * @param end revision to end for search + * @param noNetwork if yes, no check on network for newer revisions will made + * @return true if entries found and no error, if no entries found false + * @exception svn::DatabaseException in case of errors + */ + bool simpleLog(LogEntriesMap&target,const svn::Revision&start,const svn::Revision&end,bool noNetwork=false); + svn::Revision date2numberRev(const svn::Revision&,bool noNetwork=false); + bool fillCache(const svn::Revision&end); + bool insertLogEntry(const svn::LogEntry&); + bool log(const svn::Path&,const svn::Revision&start, const svn::Revision&end,const svn::Revision&peg,svn::LogEntriesMap&target, bool strictNodeHistory,int limit); + bool itemExists(const svn::Revision&,const svn::Path&); + + bool isValid()const; +}; + +} +} + +#endif diff --git a/src/svnqt/cache/sqlite3/README b/src/svnqt/cache/sqlite3/README new file mode 100644 index 0000000..e2f7914 --- /dev/null +++ b/src/svnqt/cache/sqlite3/README @@ -0,0 +1,32 @@ +With this driver you can access the files created by sqlite3 through +the standard Qt sql module. The driver name is QSQLITE3. + +Although there are many other solutions to access such DB files, I think +that using this driver has some advantages: + +--> You use the standard Qt interface so you can reuse exinting code or + switch to or from other DB types quite easily. + +--> Soft transition to Qt 4: Qt 4 supports sqlite3, you can prepare your + application now. + +--> The source of this driver is smaller than any other, you can incorporate + it on your application with little overhead and without requiring external + libraries. + + +Developer note: + +The driver is a merge between the QSQLITE driver in Qt 3 and in Qt 4 beta 1, with +small tweaks, so I think is quite stable and usable. +Please report success or failure, thanks + +To compile + +qmake +make +cp sqldrivers/libqsqlite3.so $QTDIR/plugins/sqldrivers (probably as root) + +use it as any other Qt sql driver. + +Have fun, Stefano !!!
\ No newline at end of file diff --git a/src/svnqt/cache/sqlite3/qsql_sqlite3.cpp b/src/svnqt/cache/sqlite3/qsql_sqlite3.cpp new file mode 100644 index 0000000..93010c1 --- /dev/null +++ b/src/svnqt/cache/sqlite3/qsql_sqlite3.cpp @@ -0,0 +1,485 @@ +/**************************************************************************** +** +** Implementation of SQLite driver classes. +** +** Copyright (C) 1992-2003 Trolltech AS. All rights reserved. +** +** This file is part of the sql module of the Qt GUI Toolkit. +** EDITIONS: FREE, ENTERPRISE +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#include "qsql_sqlite3.h" + +#include <qdatetime.h> +#include <qvaluevector.h> +#include <qregexp.h> +#include <qfile.h> +#include <sqlite3.h> + +#if (QT_VERSION-0 < 0x030200) +# include <qvector.h> +# if !defined Q_WS_WIN32 +# include <unistd.h> +# endif +#else +# include <qptrvector.h> +# if !defined Q_WS_WIN32 +# include <unistd.h> +# endif +#endif + +typedef struct sqlite3_stmt sqlite3_stmt; + +#define QSQLITE3_DRIVER_NAME "QSQLITE3" + +static QVariant::Type qSqliteType(int tp) +{ + switch (tp) { + case SQLITE_INTEGER: + return QVariant::Int; + case SQLITE_FLOAT: + return QVariant::Double; + case SQLITE_BLOB: + return QVariant::ByteArray; + case SQLITE_TEXT: + default: + return QVariant::String; + } +} + +static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::Type type, + int errorCode = -1) +{ + return QSqlError(descr, + QString::fromUtf8(sqlite3_errmsg(access)), + type, errorCode); +} + +class QSQLite3DriverPrivate +{ +public: + QSQLite3DriverPrivate(); + sqlite3 *access; + bool utf8; +}; + +QSQLite3DriverPrivate::QSQLite3DriverPrivate() : access(0) +{ + utf8 = true; +} + +class QSQLite3ResultPrivate +{ +public: + QSQLite3ResultPrivate(QSQLite3Result *res); + void cleanup(); + bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + bool isSelect(); + // initializes the recordInfo and the cache + void initColumns(); + void finalize(); + + QSQLite3Result* q; + sqlite3 *access; + + sqlite3_stmt *stmt; + + uint skippedStatus: 1; // the status of the fetchNext() that's skipped + uint skipRow: 1; // skip the next fetchNext()? + uint utf8: 1; + QSqlRecord rInf; +}; + +static const uint initial_cache_size = 128; + +QSQLite3ResultPrivate::QSQLite3ResultPrivate(QSQLite3Result* res) : q(res), access(0), + stmt(0), skippedStatus(false), skipRow(false), utf8(false) +{ +} + +void QSQLite3ResultPrivate::cleanup() +{ + finalize(); + rInf.clear(); + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirst); + q->setActive(false); + q->cleanup(); +} + +void QSQLite3ResultPrivate::finalize() +{ + if (!stmt) + return; + + sqlite3_finalize(stmt); + stmt = 0; +} + +// called on first fetch +void QSQLite3ResultPrivate::initColumns() +{ + rInf.clear(); + + int nCols = sqlite3_column_count(stmt); + if (nCols <= 0) + return; + + q->init(nCols); + + for (int i = 0; i < nCols; ++i) { + QString colName = QString::fromUtf8(sqlite3_column_name(stmt, i)); + + int dotIdx = colName.findRev('.'); + rInf.append(QSqlField(colName.mid(dotIdx == -1 ? 0 : dotIdx + 1), + qSqliteType(sqlite3_column_type(stmt, i)))); + } +} + +bool QSQLite3ResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + int res; + unsigned int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + return skippedStatus; + } + skipRow = initialFetch; + + if (!stmt) + return false; + + // keep trying while busy, wish I could implement this better. + while ((res = sqlite3_step(stmt)) == SQLITE_BUSY) { + // sleep instead requesting result again immidiately. +#if defined Q_OS_WIN + Sleep(1000); +#else + sleep(1); +#endif + } + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + initColumns(); + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < rInf.count(); ++i) + // todo - handle other types + values[i + idx] = QString::fromUtf8((char *)(sqlite3_column_text(stmt, i))); + // values[i + idx] = utf8 ? QString::fromUtf8(fvals[i]) : QString::fromAscii(fvals[i]); + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + initColumns(); + q->setAt(QSql::AfterLast); + return false; + case SQLITE_ERROR: + case SQLITE_MISUSE: + default: + // something wrong, don't get col info, but still return false + q->setLastError(qMakeError(access, "Unable to fetch row", QSqlError::Connection, res)); + finalize(); + q->setAt(QSql::AfterLast); + return false; + } + return false; +} + +QSQLite3Result::QSQLite3Result(const QSQLite3Driver* db) + : QSqlCachedResult(db) +{ + d = new QSQLite3ResultPrivate(this); + d->access = db->d->access; +} + +QSQLite3Result::~QSQLite3Result() +{ + d->cleanup(); + delete d; +} + +/* + Execute \a query. +*/ +bool QSQLite3Result::reset (const QString &query) +{ + // this is where we build a query. + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + setSelect(false); + + int res = sqlite3_prepare(d->access, query.utf8().data(), (query.length() + 1) * sizeof(QChar), + &d->stmt, 0); + + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, "Unable to execute statement", QSqlError::Statement, res)); + d->finalize(); + return false; + } + + d->skippedStatus = d->fetchNext(cache(), 0, true); + + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLite3Result::gotoNext(QSqlCachedResult::ValueCache& row, int idx) +{ + return d->fetchNext(row, idx, false); +} + +int QSQLite3Result::size() +{ + return -1; +} + +int QSQLite3Result::numRowsAffected() +{ + return sqlite3_changes(d->access); +} + +///////////////////////////////////////////////////////// + +QSQLite3Driver::QSQLite3Driver(QObject * parent, const char *name) + : QSqlDriver(parent, name) +{ + d = new QSQLite3DriverPrivate(); +} + +QSQLite3Driver::QSQLite3Driver(sqlite3 *connection, QObject *parent, const char *name) + : QSqlDriver(parent, name) +{ + d = new QSQLite3DriverPrivate(); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLite3Driver::~QSQLite3Driver() +{ + delete d; +} + +bool QSQLite3Driver::hasFeature(DriverFeature f) const +{ + switch (f) { + case Transactions: + case Unicode: + case BLOB: + return true; + default: + break; + } + return false; +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLite3Driver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &) +{ + if (isOpen()) + close(); + + if (db.isEmpty()) + return false; + + if (sqlite3_open(QFile::encodeName(db), &d->access) == SQLITE_OK) { + setOpen(true); + setOpenError(false); + return true; + } else { + setLastError(qMakeError(d->access, "Error opening database", + QSqlError::Connection)); + setOpenError(true); + return false; + } +} + +void QSQLite3Driver::close() +{ + if (isOpen()) { + if (sqlite3_close(d->access) != SQLITE_OK) + setLastError(qMakeError(d->access, "Error closing database", + QSqlError::Connection)); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlQuery QSQLite3Driver::createQuery() const +{ + return QSqlQuery(new QSQLite3Result(this)); +} + +bool QSQLite3Driver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createQuery()); + if (!q.exec("BEGIN")) { + setLastError(QSqlError("Unable to begin transaction", + q.lastError().databaseText(), QSqlError::Transaction)); + return false; + } + + return true; +} + +bool QSQLite3Driver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createQuery()); + if (!q.exec("COMMIT")) { + setLastError(QSqlError("Unable to begin transaction", + q.lastError().databaseText(), QSqlError::Transaction)); + return false; + } + + return true; +} + +bool QSQLite3Driver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createQuery()); + if (!q.exec("ROLLBACK")) { + setLastError(QSqlError("Unable to begin transaction", + q.lastError().databaseText(), QSqlError::Transaction)); + return false; + } + + return true; +} + +QStringList QSQLite3Driver::tables(const QString &typeName) const +{ + QStringList res; + if (!isOpen()) + return res; + int type = typeName.toInt(); + + QSqlQuery q = createQuery(); + q.setForwardOnly(TRUE); +#if (QT_VERSION-0 >= 0x030200) + if ((type & (int)QSql::Tables) && (type & (int)QSql::Views)) + q.exec("SELECT name FROM sqlite_master WHERE type='table' OR type='view'"); + else if (typeName.isEmpty() || (type & (int)QSql::Tables)) + q.exec("SELECT name FROM sqlite_master WHERE type='table'"); + else if (type & (int)QSql::Views) + q.exec("SELECT name FROM sqlite_master WHERE type='view'"); +#else + q.exec("SELECT name FROM sqlite_master WHERE type='table' OR type='view'"); +#endif + + + if (q.isActive()) { + while(q.next()) + res.append(q.value(0).toString()); + } + +#if (QT_VERSION-0 >= 0x030200) + if (type & (int)QSql::SystemTables) { + // there are no internal tables beside this one: + res.append("sqlite_master"); + } +#endif + + return res; +} + +QSqlIndex QSQLite3Driver::primaryIndex(const QString &tblname) const +{ + QSqlRecordInfo rec(recordInfo(tblname)); // expensive :( + + if (!isOpen()) + return QSqlIndex(); + + QSqlQuery q = createQuery(); + q.setForwardOnly(TRUE); + // finrst find a UNIQUE INDEX + q.exec("PRAGMA index_list('" + tblname + "');"); + QString indexname; + while(q.next()) { + if (q.value(2).toInt()==1) { + indexname = q.value(1).toString(); + break; + } + } + if (indexname.isEmpty()) + return QSqlIndex(); + + q.exec("PRAGMA index_info('" + indexname + "');"); + + QSqlIndex index(indexname); + while(q.next()) { + QString name = q.value(2).toString(); + QSqlVariant::Type type = QSqlVariant::Invalid; + if (rec.contains(name)) + type = rec.find(name).type(); + index.append(QSqlField(name, type)); + } + return index; +} + +QSqlRecordInfo QSQLite3Driver::recordInfo(const QString &tbl) const +{ + if (!isOpen()) + return QSqlRecordInfo(); + + QSqlQuery q = createQuery(); + q.setForwardOnly(TRUE); + q.exec("SELECT * FROM " + tbl + " LIMIT 1"); + return recordInfo(q); +} + +QSqlRecord QSQLite3Driver::record(const QString &tblname) const +{ + if (!isOpen()) + return QSqlRecord(); + + return recordInfo(tblname).toRecord(); +} + +QSqlRecord QSQLite3Driver::record(const QSqlQuery& query) const +{ + if (query.isActive() && query.driver() == this) { + QSQLite3Result* result = (QSQLite3Result*)query.result(); + return result->d->rInf; + } + return QSqlRecord(); +} + +QSqlRecordInfo QSQLite3Driver::recordInfo(const QSqlQuery& query) const +{ + if (query.isActive() && query.driver() == this) { + QSQLite3Result* result = (QSQLite3Result*)query.result(); + return QSqlRecordInfo(result->d->rInf); + } + return QSqlRecordInfo(); +} diff --git a/src/svnqt/cache/sqlite3/qsql_sqlite3.h b/src/svnqt/cache/sqlite3/qsql_sqlite3.h new file mode 100644 index 0000000..f89c038 --- /dev/null +++ b/src/svnqt/cache/sqlite3/qsql_sqlite3.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Definition of SQLite driver classes. +** +** Copyright (C) 1992-2003 Trolltech AS. All rights reserved. +** +** This file is part of the sql module of the Qt GUI Toolkit. +** EDITIONS: FREE, ENTERPRISE +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#ifndef QSQL_SQLITE3_H +#define QSQL_SQLITE3_H + +#include <qsqldriver.h> +#include <qsqlresult.h> +#include <qsqlrecord.h> +#include <qsqlindex.h> +#include "qsqlcachedresult.h" + +#if (QT_VERSION-0 >= 0x030200) +typedef QVariant QSqlVariant; +#endif + +#if defined (Q_OS_WIN32) +# include <qt_windows.h> +#endif + +class QSQLite3DriverPrivate; +class QSQLite3ResultPrivate; +class QSQLite3Driver; +struct sqlite3; + +class QSQLite3Result : public QSqlCachedResult +{ + friend class QSQLite3Driver; + friend class QSQLite3ResultPrivate; +public: + QSQLite3Result(const QSQLite3Driver* db); + ~QSQLite3Result(); + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int idx); + bool reset (const QString& query); + int size(); + int numRowsAffected(); + +private: + QSQLite3ResultPrivate* d; +}; + +class QSQLite3Driver : public QSqlDriver +{ + friend class QSQLite3Result; +public: + QSQLite3Driver(QObject *parent = 0, const char *name = 0); + QSQLite3Driver(sqlite3 *connection, QObject *parent = 0, const char *name = 0); + ~QSQLite3Driver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts); + bool open( const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port ) { return open (db, user, password, host, port, QString()); } + void close(); + QSqlQuery createQuery() const; + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + QStringList tables(const QString &user) const; + + QSqlRecord record(const QString& tablename) const; + QSqlRecordInfo recordInfo(const QString& tablename) const; + QSqlIndex primaryIndex(const QString &table) const; + QSqlRecord record(const QSqlQuery& query) const; + QSqlRecordInfo recordInfo(const QSqlQuery& query) const; + +private: + QSQLite3DriverPrivate* d; +}; +#endif diff --git a/src/svnqt/cache/sqlite3/qsqlcachedresult.cpp b/src/svnqt/cache/sqlite3/qsqlcachedresult.cpp new file mode 100644 index 0000000..8a23183 --- /dev/null +++ b/src/svnqt/cache/sqlite3/qsqlcachedresult.cpp @@ -0,0 +1,260 @@ +/**************************************************************************** +** +** Copyright (C) 1992-2005 Trolltech AS. All rights reserved. +** +** This file is part of the sql module of the Qt Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#include "qsqlcachedresult.h" + +#include <qvariant.h> +#include <qdatetime.h> +#include <qvaluevector.h> + +static const uint initial_cache_size = 128; + +class QSqlCachedResultPrivate +{ +public: + QSqlCachedResultPrivate(); + bool canSeek(int i) const; + inline int cacheCount() const; + void init(int count, bool fo); + void cleanup(); + int nextIndex(); + void revertLast(); + + QSqlCachedResult::ValueCache cache; + int rowCacheEnd; + int colCount; + bool forwardOnly; +}; + +QSqlCachedResultPrivate::QSqlCachedResultPrivate(): + rowCacheEnd(0), colCount(0), forwardOnly(false) +{ +} + +void QSqlCachedResultPrivate::cleanup() +{ + cache.clear(); + forwardOnly = false; + colCount = 0; + rowCacheEnd = 0; +} + +void QSqlCachedResultPrivate::init(int count, bool fo) +{ + Q_ASSERT(count); + cleanup(); + forwardOnly = fo; + colCount = count; + if (fo) { + cache.resize(count); + rowCacheEnd = count; + } else { + cache.resize(initial_cache_size * count); + } +} + +int QSqlCachedResultPrivate::nextIndex() +{ + if (forwardOnly) + return 0; + int newIdx = rowCacheEnd; + if (rowCacheEnd == (int)cache.size()) + cache.resize(cache.size() * 2); +/* if (newIdx + colCount > cache.size()){ + if(cache.size() * 2 < cache.size() + 10000) + cache.resize(cache.size() * 2); + else + cache.resize(cache.size() + 10000); + }*/ + rowCacheEnd += colCount; + + return newIdx; +} + +bool QSqlCachedResultPrivate::canSeek(int i) const +{ + if (forwardOnly || i < 0) + return false; + return rowCacheEnd >= (i + 1) * colCount; +} + +void QSqlCachedResultPrivate::revertLast() +{ + if (forwardOnly) + return; + rowCacheEnd -= colCount; +} + +inline int QSqlCachedResultPrivate::cacheCount() const +{ + Q_ASSERT(!forwardOnly); + Q_ASSERT(colCount); + return rowCacheEnd / colCount; +} + +////////////// + +QSqlCachedResult::QSqlCachedResult(const QSqlDriver * db): QSqlResult (db) +{ + d = new QSqlCachedResultPrivate(); +} + +QSqlCachedResult::~QSqlCachedResult() +{ + delete d; +} + +void QSqlCachedResult::init(int colCount) +{ + d->init(colCount, isForwardOnly()); +} + +bool QSqlCachedResult::fetch(int i) +{ + if ((!isActive()) || (i < 0)) + return false; + if (at() == i) + return true; + if (d->forwardOnly) { + // speed hack - do not copy values if not needed + if (at() > i || at() == QSql::AfterLast) + return false; + while(at() < i - 1) { + if (!gotoNext(d->cache, -1)) + return false; + setAt(at() + 1); + } + if (!gotoNext(d->cache, 0)) + return false; + setAt(at() + 1); + return true; + } + if (d->canSeek(i)) { + setAt(i); + return true; + } + if (d->rowCacheEnd > 0) + setAt(d->cacheCount()-1); + while (at() < i) { + if (!cacheNext()) + return false; + } + return true; +} + +bool QSqlCachedResult::fetchNext() +{ + if (d->canSeek(at() + 1)) { + setAt(at() + 1); + return true; + } + return cacheNext(); +} + +bool QSqlCachedResult::fetchPrevious() +{ + return fetch(at() - 1); +} + +bool QSqlCachedResult::fetchFirst() +{ + if (d->forwardOnly && at() != QSql::BeforeFirst) { + return false; + } + if (d->canSeek(0)) { + setAt(0); + return true; + } + return cacheNext(); +} + +bool QSqlCachedResult::fetchLast() +{ + if (at() == QSql::AfterLast) { + if (d->forwardOnly) + return false; + else + return fetch(d->cacheCount() - 1); + } + + int i = at(); + while (fetchNext()) + ++i; /* brute force */ + if (d->forwardOnly && at() == QSql::AfterLast) { + setAt(i); + return true; + } else { + return fetch(i); + } +} + +QVariant QSqlCachedResult::data(int i) +{ + int idx = d->forwardOnly ? i : at() * d->colCount + i; + if (i >= d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd) + return QVariant(); + + return d->cache.at(idx); +} + +bool QSqlCachedResult::isNull(int i) +{ + int idx = d->forwardOnly ? i : at() * d->colCount + i; + if (i > d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd) + return true; + + return d->cache.at(idx).isNull(); +} + +void QSqlCachedResult::cleanup() +{ + setAt(QSql::BeforeFirst); + setActive(false); + d->cleanup(); +} + +bool QSqlCachedResult::cacheNext() +{ + if (!gotoNext(d->cache, d->nextIndex())) { + d->revertLast(); + return false; + } + setAt(at() + 1); + return true; +} + +int QSqlCachedResult::colCount() const +{ + return d->colCount; +} + +QSqlCachedResult::ValueCache &QSqlCachedResult::cache() +{ + return d->cache; +} + diff --git a/src/svnqt/cache/sqlite3/qsqlcachedresult.h b/src/svnqt/cache/sqlite3/qsqlcachedresult.h new file mode 100644 index 0000000..fa8924f --- /dev/null +++ b/src/svnqt/cache/sqlite3/qsqlcachedresult.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 1992-2005 Trolltech AS. All rights reserved. +** +** This file is part of the sql module of the Qt Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#ifndef QSQLCACHEDRESULT_P_H +#define QSQLCACHEDRESULT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qsqlresult.h> + +class QVariant; +template <typename T> class QValueVector; + +class QSqlCachedResultPrivate; + +class QM_EXPORT_SQL QSqlCachedResult: public QSqlResult +{ +public: + virtual ~QSqlCachedResult(); + + typedef QValueVector<QVariant> ValueCache; + +protected: + QSqlCachedResult(const QSqlDriver * db); + + void init(int colCount); + void cleanup(); + + virtual bool gotoNext(ValueCache &values, int index) = 0; + + QVariant data(int i); + bool isNull(int i); + bool fetch(int i); + bool fetchNext(); + bool fetchPrevious(); + bool fetchFirst(); + bool fetchLast(); + + int colCount() const; + ValueCache &cache(); + +private: + bool cacheNext(); + QSqlCachedResultPrivate *d; +}; + +#endif // QSQLCACHEDRESULT_P_H diff --git a/src/svnqt/cache/test/CMakeLists.txt b/src/svnqt/cache/test/CMakeLists.txt new file mode 100644 index 0000000..ecc6130 --- /dev/null +++ b/src/svnqt/cache/test/CMakeLists.txt @@ -0,0 +1,19 @@ +SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) + +MACRO(BUILD_TEST tname) + SET(${tname}-src ${tname}.cpp) + IF (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${tname}.h) + SET(${tname}-src ${${tname}-src} ${tname}.h) + ENDIF (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${tname}.h) + ADD_EXECUTABLE(${tname} ${${tname}-src}) + TARGET_LINK_LIBRARIES(${tname} ${svnqt-name} ${QT_LIBRARIES}) + ADD_TEST(${tname} ${CMAKE_CURRENT_BINARY_DIR}/${tname}) +ENDMACRO(BUILD_TEST) + +IF (BUILD_TESTS) + CONFIGURE_FILE( + ${CMAKE_CURRENT_SOURCE_DIR}/testconfig.h.in + ${CMAKE_CURRENT_BINARY_DIR}/testconfig.h + ) + BUILD_TEST(sqlite) +ENDIF(BUILD_TESTS) diff --git a/src/svnqt/cache/test/sqlite.cpp b/src/svnqt/cache/test/sqlite.cpp new file mode 100644 index 0000000..4f14b2d --- /dev/null +++ b/src/svnqt/cache/test/sqlite.cpp @@ -0,0 +1,111 @@ +#include <qsql.h> +#include <qsqldatabase.h> +#include <qstringlist.h> +#include <iostream> +#include <qapplication.h> +#include <qtextstream.h> + +#include "svnqt/client.hpp" +#include "svnqt/svnqttypes.hpp" +#include "svnqt/log_entry.hpp" + +#include "svnqt/cache/LogCache.hpp" +#include "svnqt/cache/ReposLog.hpp" +#include "svnqt/cache/test/testconfig.h" +#include "svnqt/cache/DatabaseException.hpp" + +#if QT_VERSION < 0x040000 +#else +#include <QSqlQuery> +#include <QSqlError> +#endif + +int main(int argc,char**argv) +{ + QApplication app(argc,argv); + + svn::ContextP m_CurrentContext; + svn::Client* m_Svnclient; + m_Svnclient=svn::Client::getobject(0,0); + m_CurrentContext = new svn::Context(); + + m_Svnclient->setContext(m_CurrentContext); + + QStringList list; + QStringList::Iterator it; + // goes into "self" of logcache + new svn::cache::LogCache(TESTDBPATH); + list = QSqlDatabase::drivers(); + it = list.begin(); + while( it != list.end() ) { + std::cout << (*it).TOUTF8().data() << std::endl; + ++it; + } + svn::cache::ReposLog rl(m_Svnclient,"http://www.alwins-world.de/repos/kdesvn"); + QDataBase db = rl.Database(); +#if QT_VERSION < 0x040000 + if (!db) { +#else + if (!db.isValid()) { +#endif + std::cerr << "No database object."<<std::endl; + exit(-1); + } +#if QT_VERSION < 0x040000 + list = db->tables(); +#else + list = db.tables(); +#endif + it = list.begin(); + while( it != list.end() ) { + std::cout << ( *it ).TOUTF8().data() << std::endl; + ++it; + } + svn::LogEntriesMap lm; + try { + rl.simpleLog(lm,100,svn::Revision::HEAD); + } + catch (const svn::cache::DatabaseException&cl) + { + std::cerr << cl.msg().TOUTF8().data() <<std::endl; + } + catch (const svn::Exception&ce) + { + std::cerr << "Exception: " << ce.msg().TOUTF8().data() <<std::endl; + } + svn::LogEntriesMap::ConstIterator lit = lm.begin(); + std::cout<<"Count: "<<lm.count()<<std::endl; + + svn::Revision r("{2006-09-27}"); + std::cout << r.toString().TOUTF8().data() << " -> " << rl.date2numberRev(r).toString().TOUTF8().data()<<std::endl; + r = svn::Revision::HEAD; + std::cout << rl.date2numberRev(r).toString().TOUTF8().data()<<std::endl; + try { + rl.insertLogEntry(lm[100]); + } + catch (const svn::cache::DatabaseException&cl) + { + std::cerr << cl.msg().TOUTF8().data() << std::endl; + } + QSqlQuery q("insert into logentries(revision,date,author,message) values ('100','1122591406','alwin','copy and moving works now in basic form')",db); + q.exec(); + std::cerr << "\n" << q.lastError().text().TOUTF8().data()<<std::endl; + +#if QT_VERSION < 0x040000 +#else + db=QSqlDatabase(); +#endif + try { + rl.log("/trunk/src/svnqt",1,1000,svn::Revision::UNDEFINED,lm,false,-1); + } + catch (const svn::cache::DatabaseException&cl) + { + std::cerr << cl.msg().TOUTF8().data() <<std::endl; + } + catch (const svn::Exception&ce) + { + std::cerr << "Exception: " << ce.msg().TOUTF8().data() <<std::endl; + } + std::cout<<"Count: "<<lm.count()<<std::endl; + return 0; +} diff --git a/src/svnqt/cache/test/testconfig.h.in b/src/svnqt/cache/test/testconfig.h.in new file mode 100644 index 0000000..865ac6e --- /dev/null +++ b/src/svnqt/cache/test/testconfig.h.in @@ -0,0 +1,8 @@ +#ifndef __TEST_CONFIG_H +#define __TEST_CONFIG_H + +#define TESTREPOPATH "@CMAKE_CURRENT_BINARY_DIR@/repo" +#define TESTCOPATH "@CMAKE_CURRENT_BINARY_DIR@/co" +#define TESTDBPATH "@CMAKE_CURRENT_BINARY_DIR@/db" + +#endif |