From aeb64ffd37ebe022ecdee2cfd8fa04a0af37c36a Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Fri, 2 Dec 2016 15:54:47 +0100 Subject: Remove SQLite stuff from TrackCollection & Co --- build/depends.py | 1 + src/library/banshee/bansheeplaylistmodel.cpp | 4 +- src/library/basesqltablemodel.cpp | 37 ++- src/library/basetrackcache.cpp | 4 +- src/library/browse/browsetablemodel.cpp | 2 +- src/library/crate/cratestorage.cpp | 21 +- src/library/itunes/itunesfeature.cpp | 4 +- src/library/librarytablemodel.cpp | 2 +- src/library/mixxxlibraryfeature.cpp | 2 +- src/library/playlistfeature.cpp | 4 +- src/library/rhythmbox/rhythmboxfeature.cpp | 2 +- src/library/scanner/libraryscanner.cpp | 2 +- src/library/setlogfeature.cpp | 2 +- src/library/trackcollection.cpp | 398 +------------------------ src/library/trackcollection.h | 30 +- src/library/traktor/traktorfeature.cpp | 2 +- src/test/sqliteliketest.cpp | 34 +-- src/util/db/dbconnection.cpp | 418 +++++++++++++++++++++++++++ src/util/db/dbconnection.h | 40 +++ src/util/string.h | 16 + 20 files changed, 548 insertions(+), 477 deletions(-) create mode 100644 src/util/db/dbconnection.cpp create mode 100644 src/util/db/dbconnection.h create mode 100644 src/util/string.h diff --git a/build/depends.py b/build/depends.py index 79d328c55a..b416902679 100644 --- a/build/depends.py +++ b/build/depends.py @@ -1099,6 +1099,7 @@ class MixxxCore(Feature): "util/tapfilter.cpp", "util/movinginterquartilemean.cpp", "util/console.cpp", + "util/db/dbconnection.cpp", "util/db/dbid.cpp", "util/db/fwdsqlquery.cpp", "util/db/sqllikeescaper.cpp", diff --git a/src/library/banshee/bansheeplaylistmodel.cpp b/src/library/banshee/bansheeplaylistmodel.cpp index 5bfcfb977b..c871095bd3 100644 --- a/src/library/banshee/bansheeplaylistmodel.cpp +++ b/src/library/banshee/bansheeplaylistmodel.cpp @@ -50,7 +50,7 @@ void BansheePlaylistModel::setTableModel(int playlistId) { if (m_playlistId >= 0) { // Clear old playlist m_playlistId = -1; - QSqlQuery query(m_pTrackCollection->getDatabase()); + QSqlQuery query(m_pTrackCollection->database()); QString strQuery("DELETE FROM " BANSHEE_TABLE); if (!query.exec(strQuery)) { LOG_FAILED_QUERY(query); @@ -61,7 +61,7 @@ void BansheePlaylistModel::setTableModel(int playlistId) { // setup new playlist m_playlistId = playlistId; - QSqlQuery query(m_pTrackCollection->getDatabase()); + QSqlQuery query(m_pTrackCollection->database()); QString strQuery("CREATE TEMP TABLE IF NOT EXISTS " BANSHEE_TABLE " (" CLM_VIEW_ORDER " INTEGER, " CLM_ARTIST " TEXT, " diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index 56fe2efa94..b734e1d151 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -29,16 +29,16 @@ static const bool sDebug = false; static const int kIdColumn = 0; static const int kMaxSortColumns = 3; -// Constant for getModelSetting(name) +// Constant for getModelSetting(name) static const char* COLUMNS_SORTING = "ColumnsSorting"; BaseSqlTableModel::BaseSqlTableModel(QObject* pParent, TrackCollection* pTrackCollection, const char* settingsNamespace) : QAbstractTableModel(pParent), - TrackModel(pTrackCollection->getDatabase(), settingsNamespace), + TrackModel(pTrackCollection->database(), settingsNamespace), m_pTrackCollection(pTrackCollection), - m_database(pTrackCollection->getDatabase()), + m_database(pTrackCollection->database()), m_previewDeckGroup(PlayerManager::groupForPreviewDeck(0)), m_bInitialized(false), m_currentSearch("") { @@ -424,24 +424,24 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { qWarning() << "BaseSqlTableModel::setSort invalid column:" << column; return; } - + // There's no item to sort already, load from Settings last sort if (m_sortColumns.isEmpty()) { QString val = getModelSetting(COLUMNS_SORTING); QTextStream in(&val); - + while (!in.atEnd()) { int ordI = -1; QString name; - + in >> name >> ordI; - + int col = fieldIndex(name); if (col < 0) continue; - + Qt::SortOrder ord; ord = ordI > 0 ? Qt::AscendingOrder : Qt::DescendingOrder; - + m_sortColumns << SortColumn(col, ord); } } @@ -465,13 +465,13 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { m_sortColumns.removeLast(); } } - + // Write new sortColumns order to user settings QString val; QTextStream out(&val); for (SortColumn& sc : m_sortColumns) { - QString name; + QString name; if (sc.m_column > 0 && sc.m_column < m_tableColumns.size()) { name = m_tableColumns[sc.m_column]; } else { @@ -489,7 +489,7 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { if (sDebug) { qDebug() << "setSort() sortColumns:" << val; } - + // we have two selects for sorting, since keeping the select history // across the two selects is hard, we do this only for the trackSource @@ -511,9 +511,8 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { QString field = m_tableColumns[column]; QString sort_field = QString("%1.%2").arg(m_tableName, field); m_tableOrderBy.append(sort_field); - #ifdef __SQLITE3__ - m_tableOrderBy.append(" COLLATE localeAwareCompare"); - #endif + m_tableOrderBy.append(" COLLATE "); + m_tableOrderBy.append(DbConnection::kStringCollationFunc); m_tableOrderBy.append((order == Qt::AscendingOrder) ? " ASC" : " DESC"); } m_sortColumns.clear(); @@ -530,7 +529,7 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { sort_field = "RANDOM()"; } else { // we can't sort by other table columns here since primary sort is a track - // column: skip + // column: skip continue; } } else { @@ -544,10 +543,8 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { m_trackSourceOrderBy.append(first ? "ORDER BY ": ", "); m_trackSourceOrderBy.append(sort_field); - - #ifdef __SQLITE3__ - m_trackSourceOrderBy.append(" COLLATE localeAwareCompare"); - #endif + m_trackSourceOrderBy.append(" COLLATE "); + m_trackSourceOrderBy.append(DbConnection::kStringCollationFunc); m_trackSourceOrderBy.append((sc.m_order == Qt::AscendingOrder) ? " ASC" : " DESC"); //qDebug() << m_trackSourceOrderBy; diff --git a/src/library/basetrackcache.cpp b/src/library/basetrackcache.cpp index 882945d7a1..a1f4b96346 100644 --- a/src/library/basetrackcache.cpp +++ b/src/library/basetrackcache.cpp @@ -32,8 +32,8 @@ BaseTrackCache::BaseTrackCache(TrackCollection* pTrackCollection, m_bIndexBuilt(false), m_bIsCaching(isCaching), m_trackDAO(pTrackCollection->getTrackDAO()), - m_database(pTrackCollection->getDatabase()), - m_pQueryParser(new SearchQueryParser(pTrackCollection->getDatabase())) { + m_database(pTrackCollection->database()), + m_pQueryParser(new SearchQueryParser(pTrackCollection->database())) { m_searchColumns << "artist" << "album" << "album_artist" diff --git a/src/library/browse/browsetablemodel.cpp b/src/library/browse/browsetablemodel.cpp index 93f3ae3ff6..a1dc266530 100644 --- a/src/library/browse/browsetablemodel.cpp +++ b/src/library/browse/browsetablemodel.cpp @@ -17,7 +17,7 @@ BrowseTableModel::BrowseTableModel(QObject* parent, TrackCollection* pTrackCollection, RecordingManager* pRecordingManager) - : TrackModel(pTrackCollection->getDatabase(), + : TrackModel(pTrackCollection->database(), "mixxx.db.model.browse"), QStandardItemModel(parent), m_pTrackCollection(pTrackCollection), diff --git a/src/library/crate/cratestorage.cpp b/src/library/crate/cratestorage.cpp index 3176a1b633..83de31e151 100644 --- a/src/library/crate/cratestorage.cpp +++ b/src/library/crate/cratestorage.cpp @@ -1,10 +1,11 @@ #include "library/crate/cratestorage.h" #include "library/crate/crateschema.h" - #include "library/dao/trackschema.h" -#include "util/db/fwdsqlquery.h" + +#include "util/db/dbconnection.h" #include "util/db/sqltransaction.h" +#include "util/db/fwdsqlquery.h" #include @@ -318,9 +319,11 @@ bool CrateStorage::readCrateByName(const QString& name, Crate* pCrate) const { CrateSelectIterator CrateStorage::selectCrates() const { FwdSqlQuery query(m_database, QString( - "SELECT * FROM %1 ORDER BY %2 COLLATE localeAwareCompare").arg( + "SELECT * FROM %1 ORDER BY %2 COLLATE %3").arg( CRATE_TABLE, - CRATETABLE_NAME)); + CRATETABLE_NAME, + DbConnection::kStringCollationFunc)); + if (query.execPrepared()) { return CrateSelectIterator(query); } else { @@ -331,10 +334,11 @@ CrateSelectIterator CrateStorage::selectCrates() const { CrateSelectIterator CrateStorage::selectAutoDjCrates(bool autoDjSource) const { FwdSqlQuery query(m_database, QString( - "SELECT * FROM %1 WHERE %2=:autoDjSource ORDER BY %3 COLLATE localeAwareCompare").arg( + "SELECT * FROM %1 WHERE %2=:autoDjSource ORDER BY %3 COLLATE %4").arg( CRATE_TABLE, CRATETABLE_AUTODJ_SOURCE, - CRATETABLE_NAME)); + CRATETABLE_NAME, + DbConnection::kStringCollationFunc)); query.bindValue(":autoDjSource", autoDjSource); if (query.execPrepared()) { return CrateSelectIterator(query); @@ -346,9 +350,10 @@ CrateSelectIterator CrateStorage::selectAutoDjCrates(bool autoDjSource) const { CrateSummarySelectIterator CrateStorage::selectCrateSummaries() const { FwdSqlQuery query(m_database, QString( - "SELECT * FROM %1 ORDER BY %2 COLLATE localeAwareCompare").arg( + "SELECT * FROM %1 ORDER BY %2 COLLATE %3").arg( CRATE_SUMMARY_VIEW, - CRATETABLE_NAME)); + CRATETABLE_NAME, + DbConnection::kStringCollationFunc)); if (query.execPrepared()) { return CrateSummarySelectIterator(query); } else { diff --git a/src/library/itunes/itunesfeature.cpp b/src/library/itunes/itunesfeature.cpp index 5a3a4d1a80..fa13586498 100644 --- a/src/library/itunes/itunesfeature.cpp +++ b/src/library/itunes/itunesfeature.cpp @@ -68,7 +68,7 @@ ITunesFeature::ITunesFeature(QObject* parent, TrackCollection* pTrackCollection) m_isActivated = false; m_title = tr("iTunes"); - m_database = QSqlDatabase::cloneDatabase(pTrackCollection->getDatabase(), "ITUNES_SCANNER"); + m_database = QSqlDatabase::cloneDatabase(pTrackCollection->database(), "ITUNES_SCANNER"); //Open the database connection in this thread. if (!m_database.open()) { @@ -120,7 +120,7 @@ void ITunesFeature::activate() { void ITunesFeature::activate(bool forceReload) { //qDebug("ITunesFeature::activate()"); if (!m_isActivated || forceReload) { - SettingsDAO settings(m_pTrackCollection->getDatabase()); + SettingsDAO settings(m_pTrackCollection->database()); QString dbSetting(settings.getValue(ITDB_PATH_KEY)); // if a path exists in the database, use it if (!dbSetting.isEmpty() && QFile::exists(dbSetting)) { diff --git a/src/library/librarytablemodel.cpp b/src/library/librarytablemodel.cpp index 21ea1e8676..c6c2f504ea 100644 --- a/src/library/librarytablemodel.cpp +++ b/src/library/librarytablemodel.cpp @@ -32,7 +32,7 @@ void LibraryTableModel::setTableModel(int id) { const QString tableName = "library_view"; - QSqlQuery query(m_pTrackCollection->getDatabase()); + QSqlQuery query(m_pTrackCollection->database()); QString queryString = "CREATE TEMPORARY VIEW IF NOT EXISTS " + tableName + " AS " "SELECT " + columns.join(", ") + " FROM library INNER JOIN track_locations " diff --git a/src/library/mixxxlibraryfeature.cpp b/src/library/mixxxlibraryfeature.cpp index 43393d1d9b..bb090dba9c 100644 --- a/src/library/mixxxlibraryfeature.cpp +++ b/src/library/mixxxlibraryfeature.cpp @@ -66,7 +66,7 @@ MixxxLibraryFeature::MixxxLibraryFeature(Library* pLibrary, << "library." + LIBRARYTABLE_COVERART_LOCATION << "library." + LIBRARYTABLE_COVERART_HASH; - QSqlQuery query(pTrackCollection->getDatabase()); + QSqlQuery query(pTrackCollection->database()); QString tableName = "library_cache_view"; QString queryString = QString( "CREATE TEMPORARY VIEW IF NOT EXISTS %1 AS " diff --git a/src/library/playlistfeature.cpp b/src/library/playlistfeature.cpp index 2ea3ef6348..0f1d513d08 100644 --- a/src/library/playlistfeature.cpp +++ b/src/library/playlistfeature.cpp @@ -140,13 +140,13 @@ void PlaylistFeature::buildPlaylistList() { "LEFT JOIN library ON PlaylistTracks.track_id = library.id " "WHERE Playlists.hidden = 0 " "GROUP BY Playlists.id;"); - QSqlQuery query(m_pTrackCollection->getDatabase()); + QSqlQuery query(m_pTrackCollection->database()); if (!query.exec(queryString)) { LOG_FAILED_QUERY(query); } // Setup the sidebar playlist model - QSqlTableModel playlistTableModel(this, m_pTrackCollection->getDatabase()); + QSqlTableModel playlistTableModel(this, m_pTrackCollection->database()); playlistTableModel.setTable("PlaylistsCountsDurations"); playlistTableModel.setSort(playlistTableModel.fieldIndex("sort_name"), Qt::AscendingOrder); diff --git a/src/library/rhythmbox/rhythmboxfeature.cpp b/src/library/rhythmbox/rhythmboxfeature.cpp index 81dbc2029e..47a4e001e6 100644 --- a/src/library/rhythmbox/rhythmboxfeature.cpp +++ b/src/library/rhythmbox/rhythmboxfeature.cpp @@ -57,7 +57,7 @@ RhythmboxFeature::RhythmboxFeature(QObject* parent, TrackCollection* pTrackColle m_isActivated = false; m_title = tr("Rhythmbox"); - m_database = QSqlDatabase::cloneDatabase(pTrackCollection->getDatabase(), + m_database = QSqlDatabase::cloneDatabase(pTrackCollection->database(), "RHYTHMBOX_SCANNER"); //Open the database connection in this thread. diff --git a/src/library/scanner/libraryscanner.cpp b/src/library/scanner/libraryscanner.cpp index e61fc2479a..843eb31d05 100644 --- a/src/library/scanner/libraryscanner.cpp +++ b/src/library/scanner/libraryscanner.cpp @@ -124,7 +124,7 @@ void LibraryScanner::run() { Trace trace("LibraryScanner"); if (m_pCollection != NULL) { // false only during tests if (!m_database.isValid()) { - m_database = QSqlDatabase::cloneDatabase(m_pCollection->getDatabase(), "LIBRARY_SCANNER"); + m_database = QSqlDatabase::cloneDatabase(m_pCollection->database(), "LIBRARY_SCANNER"); } if (!m_database.isOpen()) { diff --git a/src/library/setlogfeature.cpp b/src/library/setlogfeature.cpp index 18dbbdef4f..530e157c21 100644 --- a/src/library/setlogfeature.cpp +++ b/src/library/setlogfeature.cpp @@ -117,7 +117,7 @@ void SetlogFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index void SetlogFeature::buildPlaylistList() { m_playlistList.clear(); // Setup the sidebar playlist model - QSqlTableModel playlistTableModel(this, m_pTrackCollection->getDatabase()); + QSqlTableModel playlistTableModel(this, m_pTrackCollection->database()); playlistTableModel.setTable("Playlists"); playlistTableModel.setFilter("hidden=2"); // PLHT_SET_LOG playlistTableModel.setSort(playlistTableModel.fieldIndex("id"), diff --git a/src/library/trackcollection.cpp b/src/library/trackcollection.cpp index 70a1bcd46c..6c8559f7b5 100644 --- a/src/library/trackcollection.cpp +++ b/src/library/trackcollection.cpp @@ -3,10 +3,6 @@ #include "library/trackcollection.h" -#ifdef __SQLITE3__ -#include -#endif - #include "library/librarytablemodel.h" #include "library/schemamanager.h" #include "library/crate/cratestorage.h" @@ -20,25 +16,14 @@ const int TrackCollection::kRequiredSchemaVersion = 27; TrackCollection::TrackCollection(UserSettingsPointer pConfig) : m_pConfig(pConfig), - m_db(QSqlDatabase::addDatabase("QSQLITE")), // defaultConnection - m_playlistDao(m_db), - m_cueDao(m_db), - m_directoryDao(m_db), - m_analysisDao(m_db, pConfig), - m_libraryHashDao(m_db), - m_trackDao(m_db, m_cueDao, m_playlistDao, + m_dbConnection(m_pConfig->getSettingsPath()), + m_playlistDao(database()), + m_cueDao(database()), + m_directoryDao(database()), + m_analysisDao(database(), pConfig), + m_libraryHashDao(database()), + m_trackDao(database(), m_cueDao, m_playlistDao, m_analysisDao, m_libraryHashDao, pConfig) { - qDebug() << "Available QtSQL drivers:" << QSqlDatabase::drivers(); - - m_db.setHostName("localhost"); - m_db.setDatabaseName(QDir(pConfig->getSettingsPath()).filePath("mixxxdb.sqlite")); - m_db.setUserName("mixxx"); - m_db.setPassword("mixxx"); - bool ok = m_db.open(); - qDebug() << "DB status:" << m_db.databaseName() << "=" << ok; - if (m_db.lastError().isValid()) { - qDebug() << "Error loading database:" << m_db.lastError(); - } // Check for tables and create them if missing if (!checkForTables()) { // TODO(XXX) something a little more elegant @@ -50,24 +35,10 @@ TrackCollection::~TrackCollection() { qDebug() << "~TrackCollection()"; m_trackDao.finish(); m_crates.detachDatabase(); - - if (m_db.isOpen()) { - // There should never be an outstanding transaction when this code is - // called. If there is, it means we probably aren't committing a - // transaction somewhere that should be. - if (m_db.rollback()) { - qDebug() << "ERROR: There was a transaction in progress on the main database connection while shutting down." - << "There is a logic error somewhere."; - } - m_db.close(); - } else { - qDebug() << "ERROR: The main database connection was closed before TrackCollection closed it." - << "There is a logic error somewhere."; - } } bool TrackCollection::checkForTables() { - if (!m_db.open()) { + if (!m_dbConnection) { QMessageBox::critical(0, tr("Cannot open database"), tr("Unable to establish a database connection.\n" "Mixxx requires QT with SQLite support. Please read " @@ -77,10 +48,6 @@ bool TrackCollection::checkForTables() { return false; } -#ifdef __SQLITE3__ - installSorting(m_db); -#endif - // The schema XML is baked into the binary via Qt resources. QString schemaFilename(":/schema.xml"); QString okToExit = tr("Click OK to exit."); @@ -92,7 +59,7 @@ bool TrackCollection::checkForTables() { "mixxx-devel@lists.sourceforge.net"; SchemaManager::Result result = SchemaManager::upgradeToSchemaVersion( - schemaFilename, m_db, kRequiredSchemaVersion); + schemaFilename, database(), kRequiredSchemaVersion); switch (result) { case SchemaManager::RESULT_BACKWARDS_INCOMPATIBLE: QMessageBox::warning( @@ -130,14 +97,10 @@ bool TrackCollection::checkForTables() { m_cueDao.initialize(); m_directoryDao.initialize(); m_libraryHashDao.initialize(); - m_crates.attachDatabase(m_db); + m_crates.attachDatabase(database()); return true; } -QSqlDatabase& TrackCollection::getDatabase() { - return m_db; -} - TrackDAO& TrackCollection::getTrackDAO() { return m_trackDao; } @@ -391,344 +354,3 @@ bool TrackCollection::removeCrateTracks( return true; } - -#ifdef __SQLITE3__ - -// from public domain code -// http://www.archivum.info/qt-interest@trolltech.com/2008-12/00584/Re-%28Qt-interest%29-Qt-Sqlite-UserDefinedFunction.html - -namespace { - -inline -int compareLocalAwareCaseInsensitive( - const QString& first, const QString& second) { - return QString::localeAwareCompare(first.toLower(), second.toLower()); -} - -void makeLatinLow(QChar* c, int count) { - for (int i = 0; i < count; ++i) { - if (c[i].decompositionTag() != QChar::NoDecomposition) { - c[i] = c[i].decomposition()[0]; - } - if (c[i].isUpper()) { - c[i] = c[i].toLower(); - } - } -} - -const QChar LIKE_MATCH_ONE = '_'; -const QChar LIKE_MATCH_ALL = '%'; -const QChar LIKE_DEFAULT_ESCAPE = '\0'; - -// The collating function callback is invoked with a copy of the pArg -// application data pointer and with two strings in the encoding specified -// by the eTextRep argument. -// The collating function must return an integer that is negative, zero, -// or positive if the first string is less than, equal to, or greater -// than the second, respectively. -int sqliteStringCompare(void* pArg, - int len1, const void* data1, - int len2, const void* data2) { - Q_UNUSED(pArg); - // Construct a QString without copy - QString string1 = QString::fromRawData(reinterpret_cast(data1), - len1 / sizeof(QChar)); - QString string2 = QString::fromRawData(reinterpret_cast(data2), - len2 / sizeof(QChar)); - return compareLocalAwareCaseInsensitive(string1, string2); -} - -// Compare two strings for equality where the first string is -// a "LIKE" expression. Return true (1) if they are the same and -// false (0) if they are different. -// This is the original sqlite3 icuLikeCompare rewritten for QChar -int likeCompareInner( - const QChar* pattern, // LIKE pattern - int patternSize, - const QChar* string, // The string to compare against - int stringSize, - const QChar esc) { // The escape character - - int iPattern = 0; // Current index in pattern - int iString = 0; // Current index in string - - bool prevEscape = false; // True if the previous character was uEsc - - while (iPattern < patternSize) { - // Read (and consume) the next character from the input pattern. - QChar uPattern = pattern[iPattern++]; - // There are now 4 possibilities: - // 1. uPattern is an unescaped match-all character "%", - // 2. uPattern is an unescaped match-one character "_", - // 3. uPattern is an unescaped escape character, or - // 4. uPattern is to be handled as an ordinary character - - if (!prevEscape && uPattern == LIKE_MATCH_ALL) { - // Case 1. - QChar c; - - // Skip any LIKE_MATCH_ALL or LIKE_MATCH_ONE characters that follow a - // LIKE_MATCH_ALL. For each LIKE_MATCH_ONE, skip one character in the - // test string. - - if (iPattern >= patternSize) { - // Tailing % - return 1; - } - - while ((c = pattern[iPattern]) == LIKE_MATCH_ALL || c == LIKE_MATCH_ONE) { - if (c == LIKE_MATCH_ONE) { - if (++iString == stringSize) { - return 0; - } - } - if (++iPattern == patternSize) { - // Two or more tailing % - return 1; - } - } - - while (iString < stringSize) { - if (likeCompareInner(&pattern[iPattern], patternSize - iPattern, - &string[iString], stringSize - iString, esc)) { - return 1; - } - iString++; - } - return 0; - } else if (!prevEscape && uPattern == LIKE_MATCH_ONE) { - // Case 2. - if (++iString == stringSize) { - return 0; - } - } else if (!prevEscape && uPattern == esc) { - // Case 3. - prevEscape = 1; - } else { - // Case 4. - if (iString == stringSize) { - return 0; - } - QChar uString = string[iString++]; - if (uString != uPattern) { - return 0; - } - prevEscape = false; - } - } - return iString == stringSize; -} - -} // anonymous namespace - -void TrackCollection::installSorting(QSqlDatabase &db) { - QVariant v = db.driver()->handle(); - if (v.isValid() && strcmp(v.typeName(), "sqlite3*") == 0) { - // v.data() returns a pointer to the handle - sqlite3* handle = *static_cast(v.data()); - if (handle != 0) { // check that it is not NULL - int result = sqlite3_create_collation( - handle, - "localeAwareCompare", - SQLITE_UTF16, - NULL, - sqliteStringCompare); - if (result != SQLITE_OK) - qWarning() << "Could not add string collation function: " << result; - - result = sqlite3_create_function( - handle, - "like", - 2, - SQLITE_ANY, - NULL, - sqliteLike, - NULL, NULL); - if (result != SQLITE_OK) - qWarning() << "Could not add like 2 function: " << result; - - result = sqlite3_create_function( - handle, - "like", - 3, - SQLITE_UTF8, // No conversion, Data is stored as UTF8 - NULL, - sqliteLike, - NULL, NULL); - if (result != SQLITE_OK) - qWarning() << "Could not add like 3 function: " << result; - } else { - qWarning() << "Could not get sqlite handle"; - } - } else { - qWarning() << "handle variant returned typename " << v.typeName(); - } -} - -// This implements the like() SQL function. This is used by the LIKE operator. -// The SQL statement 'A LIKE B' is implemented as 'like(B, A)', and if there is -// an escape character, say E, it is implemented as 'like(B, A, E)' -//static -void TrackCollection::sqliteLike(sqlite3_context *context, - int aArgc, - sqlite3_value **aArgv) { - VERIFY_OR_DEBUG_ASSERT(aArgc == 2 || aArgc == 3) { - return; - } - - const char* b = reinterpret_cast( - sqlite3_value_text(aArgv[0])); - const char* a = reinterpret_cast( - sqlite3_value_text(aArgv[1])); - - if (!a || !b) { - return; - } - - QString stringB = QString::fromUtf8(b); // Like String - QString stringA = QString::fromUtf8(a); - - QChar esc = LIKE_DEFAULT_ESCAPE; - if (aArgc == 3) { - const char* e = reinterpret_cast( - sqlite3_value_text(aArgv[2])); - if (e) { - QString stringE = QString::fromUtf8(e); - if (!stringE.isEmpty()) { - esc = stringE.data()[0]; - } - } - } - - int ret = likeCompareLatinLow(&stringB, &stringA, esc); - sqlite3_result_int64(context, ret); - return; -} - -//static -int TrackCollection::likeCompareLatinLow( - QString* pattern, - QString* string, - QChar esc) { - makeLatinLow(pattern->data(), pattern->length()); - makeLatinLow(string->data(), string->length()); - //qDebug() << *pattern << *string; - return likeCompareInner(pattern->data(), pattern->length(), string->data(), string->length(), esc); -} - - -/* -static int -likeCompare(nsAString::const_iterator aPatternItr, - nsAString::const_iterator aPatternEnd, - nsAString::const_iterator aStringItr, - nsAString::const_iterator aStringEnd, - PRUnichar aEscape) -{ - const PRUnichar LIKE_MATCH_ALL('%'); - const PRUnichar LIKE_MATCH_ONE('_'); - - PRBool lastWasEscape = PR_FALSE; - while (aPatternItr != aPatternEnd) { - -* What we do in here is take a look at each character from the input -* pattern, and do something with it. There are 4 possibilities: -* 1) character is an un-escaped match-all character -* 2) character is an un-escaped match-one character -* 3) character is an un-escaped escape character -* 4) character is not any of the above - - if (!lastWasEscape && *aPatternItr == LIKE_MATCH_ALL) { - // CASE 1 - -* Now we need to skip any LIKE_MATCH_ALL or LIKE_MATCH_ONE characters that follow a -* LIKE_MATCH_ALL character. For each LIKE_MATCH_ONE character, skip one character -* in the pattern string. - - while (*aPatternItr == LIKE_MATCH_ALL || *aPatternItr == LIKE_MATCH_ONE) { - if (*aPatternItr == LIKE_MATCH_ONE) { - // If we've hit the end of the string we are testing, no match - if (aStringItr == aStringEnd) - return 0; - aStringItr++; - } - aPatternItr++; - } - - // If we've hit the end of the pattern string, match - if (aPatternItr == aPatternEnd) - return 1; - - while (aStringItr != aStringEnd) { - if (likeCompare(aPatternItr, aPatternEnd, aStringItr, aStringEnd, aEscape)) { - // we've hit a match, so indicate this - return 1; - } - aStringItr++; - } - - // No match - return 0; - } else if (!lastWasEscape && *aPatternItr == LIKE_MATCH_ONE) { - // CASE 2 - if (aStringItr == aStringEnd) { - // If we've hit the end of the string we are testing, no match - return 0; - } - aStringItr++; - lastWasEscape = PR_FALSE; - } else if (!lastWasEscape && *aPatternItr == aEscape) { - // CASE 3 - lastWasEscape = PR_TRUE; - } else { - // CASE 4 - if (ToUpperCase(*aStringItr) != ToUpperCase(*aPatternItr)) { - // If we've hit a point where the strings don't match, there is no match - return 0; - } - aStringItr++; - lastWasEscape = PR_FALSE; - } - - aPatternItr++; - } - - return aStringItr == aStringEnd; -} - - -. -void -StorageUnicodeFunctions::likeFunction(sqlite3_context *p, - int aArgc, - sqlite3_value **aArgv) -{ - NS_ASSERTION(2 == aArgc || 3 == aArgc, "Invalid number of arguments!"); - - if (sqlite3_value_bytes(aArgv[0]) > SQLITE_MAX_LIKE_PATTERN_LENGTH) { - sqlite3_result_error(p, "LIKE or GLOB pattern too complex", SQLITE_TOOBIG); - return; - } - - if (!sqlite3_value_text16(aArgv[0]) || !sqlite3_value_text16(aArgv[1])) - return; - - nsDependentString A(static_cast(sqlite3_value_text16(aArgv[1]))); - nsDependentString B(static_cast(sqlite3_value_text16(aArgv[0]))); - NS_ASSERTION(!B.IsEmpty(), "LIKE string must not be null!"); - - PRUnichar E = 0; - if (3 == aArgc) - E = static_cast(sqlite3_value_text16(aArgv[2]))[0]; - - nsAString::const_iterator itrString, endString; - A.BeginReading(itrString); - A.EndReading(endString); - nsAString::const_iterator itrPattern, endPattern; - B.BeginReading(itrPattern); - B.EndReading(endPattern); - sqlite3_result_int(p, likeCompare(itrPattern, endPattern, - itrString, endString, E)); -} -*/ -#endif // __SQLITE3__ diff --git a/src/library/trackcollection.h b/src/library/trackcollection.h index 64f6ace8dc..1e64d1807a 100644 --- a/src/library/trackcollection.h +++ b/src/library/trackcollection.h @@ -15,11 +15,7 @@ #include "library/dao/analysisdao.h" #include "library/dao/directorydao.h" #include "library/dao/libraryhashdao.h" - -#ifdef __SQLITE3__ -typedef struct sqlite3_context sqlite3_context; -typedef struct Mem sqlite3_value; -#endif +#include "util/db/dbconnection.h" // forward declaration(s) class Track; @@ -35,13 +31,17 @@ class TrackCollection : public QObject { public: static const int kRequiredSchemaVersion; - TrackCollection(UserSettingsPointer pConfig); - virtual ~TrackCollection(); + explicit TrackCollection( + UserSettingsPointer pConfig); + ~TrackCollection() override; bool checkForTables(); void resetLibaryCancellation(); - QSqlDatabase& getDatabase(); + + QSqlDatabase& database() { + return m_dbConnection.database(); + } const CrateStorage& crates() const { return m_crates; @@ -87,21 +87,9 @@ class TrackCollection : public QObject { void crateSummaryChanged( const QSet& crates); - protected: -#ifdef __SQLITE3__ - static void installSorting(QSqlDatabase &db); - static void sqliteLike(sqlite3_context *p, - int aArgc, - sqlite3_value **aArgv); - static int likeCompareLatinLow( - QString* pattern, - QString* string, - QChar esc); -#endif // __SQLITE3__ - private: UserSettingsPointer m_pConfig; - QSqlDatabase m_db; + DbConnection m_dbConnection; QSharedPointer m_defaultTrackSource; PlaylistDAO m_playlistDao; CrateStorage m_crates; diff --git a/src/library/traktor/traktorfeature.cpp b/src/library/traktor/traktorfeature.cpp index 09a045e435..8583be90f5 100644 --- a/src/library/traktor/traktorfeature.cpp +++ b/src/library/traktor/traktorfeature.cpp @@ -89,7 +89,7 @@ TraktorFeature::TraktorFeature(QObject* parent, TrackCollection* pTrackCollectio m_title = tr("Traktor"); - m_database = QSqlDatabase::cloneDatabase(pTrackCollection->getDatabase(), + m_database = QSqlDatabase::cloneDatabase(pTrackCollection->database(), "TRAKTOR_SCANNER"); //Open the database connection in this thread. diff --git a/src/test/sqliteliketest.cpp b/src/test/sqliteliketest.cpp index 4b53bb4ff7..6741006055 100644 --- a/src/test/sqliteliketest.cpp +++ b/src/test/sqliteliketest.cpp @@ -1,24 +1,9 @@ #include -#include "library/trackcollection.h" - -class TrackCollectionTest : public TrackCollection { - public: -#ifdef __SQLITE3__ - static int likeCompareLatinLowTest( - QString* pattern, - QString* string, - const QChar esc) { - return TrackCollection::likeCompareLatinLow( - pattern, string, esc); - - } -#endif // __SQLITE3__ -}; +#include "util/db/dbconnection.h" class SqliteLikeTest : public testing::Test {}; -#ifdef __SQLITE3__ TEST_F(SqliteLikeTest, PatternTest) { QString pattern; QString string; @@ -27,41 +12,40 @@ TEST_F(SqliteLikeTest, PatternTest) { pattern = QString::fromUtf8("%väth%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(TrackCollectionTest::likeCompareLatinLowTest(&pattern, &string, esc)); + EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%vath%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(TrackCollectionTest::likeCompareLatinLowTest(&pattern, &string, esc)); + EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%väth%"); string = QString::fromUtf8("Sven Vath"); esc = '\0'; - EXPECT_TRUE(TrackCollectionTest::likeCompareLatinLowTest(&pattern, &string, esc)); + EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%v_th%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(TrackCollectionTest::likeCompareLatinLowTest(&pattern, &string, esc)); + EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%v_th%%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(TrackCollectionTest::likeCompareLatinLowTest(&pattern, &string, esc)); + EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%v%_%th%%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(TrackCollectionTest::likeCompareLatinLowTest(&pattern, &string, esc)); + EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%v!%th%"); string = QString::fromUtf8("Sven V%th"); esc = '!'; - EXPECT_TRUE(TrackCollectionTest::likeCompareLatinLowTest(&pattern, &string, esc)); + EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%ä%"); string = QString::fromUtf8("Tiësto"); esc = '\0'; - EXPECT_FALSE(TrackCollectionTest::likeCompareLatinLowTest(&pattern, &string, esc)); + EXPECT_FALSE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); } -#endif // __SQLITE3__ diff --git a/src/util/db/dbconnection.cpp b/src/util/db/dbconnection.cpp new file mode 100644 index 0000000000..7307bfeb18 --- /dev/null +++ b/src/util/db/dbconnection.cpp @@ -0,0 +1,418 @@ +#include "util/db/dbconnection.h" + +#include +#include +#include + +#ifdef __SQLITE3__ +#include +#endif // __SQLITE3__ + +#include "util/string.h" +#include "util/assert.h" + + +// Originally from public domain code: +// http://www.archivum.info/qt-interest@trolltech.com/2008-12/00584/Re-%28Qt-interest%29-Qt-Sqlite-UserDefinedFunction.html + +namespace { + +const QString kDatabaseType = "QSQLITE"; + +const QString kDatabaseHostName = "localhost"; +const QString kDatabaseFileName = "mixxxdb.sqlite"; +const QString kDatabaseUserName = "mixxx"; +const QString kDatabasePassword = "mixxx"; + +void makeLatinLow(QChar* c, int count) { + for (int i = 0; i < count; ++i) { + if (c[i].decompositionTag() != QChar::NoDecomposition) { + c[i] = c[i].decomposition()[0]; + } + if (c[i].isUpper()) { + c[i] = c[i].toLower(); + } + } +} + +const QChar LIKE_MATCH_ONE = '_'; +const QChar LIKE_MATCH_ALL = '%'; +const QChar LIKE_DEFAULT_ESCAPE = '\0'; + +// The collating function callback is invoked with a copy of the pArg +// application data pointer and with two strings in the encoding specified +// by the eTextRep argument. +// The collating function must return an integer that is negative, zero, +// or positive if the first string is less than, equal to, or greater +// than the second, respectively. +int sqliteStringCompareUTF16(void* pArg, + int len1, const void* data1, + int len2, const void* data2) { + Q_UNUSED(pArg); + // Construct a QString without copy + QString string1 = QString::fromRawData(reinterpret_cast(data1), + len1 / sizeof(QChar)); + QString string2 = QString::fromRawData(reinterpret_cast(data2), + len2 / sizeof(QChar)); + return compareLocalAwareCaseInsensitive(string1, string2); +} + +// Compare two strings for equality where the first string is +// a "LIKE" expression. Return true (1) if they are the same and +// false (0) if they are different. +// This is the original sqlite3 icuLikeCompare rewritten for QChar +int likeCompareInner( + const QChar* pattern, // LIKE pattern + int patternSize, + const QChar* string, // The string to compare against + int stringSize, + const QChar esc) { // The escape character + + int iPattern = 0; // Current index in pattern + int iString = 0; // Current index in string + + bool prevEscape = false; // True if the previous character was uEsc + + while (iPattern < patternSize) { + // Read (and consume) the next character from the input pattern. + QChar uPattern = pattern[iPattern++]; + // There are now 4 possibilities: + // 1. uPattern is an unescaped match-all character "%", + // 2. uPattern is an unescaped match-one character "_", + // 3. uPattern is an unescaped escape character, or + // 4. uPattern is to be handled as an ordinary character + + if (!prevEscape && uPattern == LIKE_MATCH_ALL) { + // Case 1. + QChar c; + + // Skip any LIKE_MATCH_ALL or LIKE_MATCH_ONE characters that follow a + // LIKE_MATCH_ALL. For each LIKE_MATCH_ONE, skip one character in the + // test string. + + if (iPattern >= patternSize) { + // Tailing % + return 1; + } + + while ((c = pattern[iPattern]) == LIKE_MATCH_ALL || c == LIKE_MATCH_ONE) { + if (c == LIKE_MATCH_ONE) { + if (++iString == stringSize) { + return 0; + } + } + if (++iPattern == patternSize) { + // Two or more tailing % + return 1; + } + } + + while (iString < stringSize) { + if (likeCompareInner(&pattern[iPattern], patternSize - iPattern, + &string[iString], stringSize - iString, esc)) { + return 1; + } + iString++; + } + return 0; + } else if (!prevEscape && uPattern == LIKE_MATCH_ONE) { + // Case 2. + if (++iString == stringSize) { + return 0; + } + } else if (!prevEscape && uPattern == esc) { + // Case 3. + prevEscape = 1; + } else { + // Case 4. + if (iString == stringSize) { + return 0; + } + QChar uString = string[iString++]; + if (uString != uPattern) { + return 0; + } + prevEscape = false; + } + } + return iString == stringSize; +} + +#ifdef __SQLITE3__ + +// This implements the like() SQL function. This is used by the LIKE operator. +// The SQL statement 'A LIKE B' is implemented as 'like(B, A)', and if there is +// an escape character, say E, it is implemented as 'like(B, A, E)' +//static +void sqliteLike(sqlite3_context *context, + int aArgc, + sqlite3_value **aArgv) { + DEBUG_ASSERT_AND_HANDLE(aArgc == 2 || aArgc == 3) { + return; + } + + const char* b = reinterpret_cast( + sqlite3_value_text(aArgv[0])); + const char* a = reinterpret_cast( + sqlite3_value_text(aArgv[1])); + + if (!a || !b) { + return; + } + + QString stringB = QString::fromUtf8(b); // Like String + QString stringA = QString::fromUtf8(a); + + QChar esc = LIKE_DEFAULT_ESCAPE; + if (aArgc == 3) { + const char* e = reinterpret_cast( + sqlite3_value_text(aArgv[2])); + if (e) { + QString stringE = QString::fromUtf8(e); + if (!stringE.isEmpty()) { + esc = stringE.data()[0]; + } + } + } + + int ret = DbConnection::likeCompareLatinLow(&stringB, &stringA, esc); + sqlite3_result_int64(context, ret); + return; +} + +#endif // __SQLITE3__ + +} // anonymous namespace + +//static +const char* const DbConnection::kStringCollationFunc = "mixxxStringCollation"; + +DbConnection::DbConnection(const QString& dirPath) + : m_filePath(QDir(dirPath).filePath(kDatabaseFileName)), + m_database(QSqlDatabase::addDatabase(kDatabaseType)) { + qDebug() + << "Available drivers for database connection:" + << QSqlDatabase::drivers(); + + m_database.setHostName(kDatabaseHostName); + m_database.setDatabaseName(m_filePath); + m_database.setUserName(kDatabaseUserName); + m_database.setPassword(kDatabasePassword); + if (!m_database.open()) { + qWarning() << "Failed to open database connection:" + << *this + << m_database.lastError(); + DEBUG_ASSERT(!*this); // failure + return; // early exit + } + + QVariant v = m_database.driver()->handle(); + DEBUG_ASSERT_AND_HANDLE(v.isValid()) { + return; // early exit + } +#ifdef __SQLITE3__ + if (strcmp(v.typeName(), "sqlite3*") == 0) { + // v.data() returns a pointer to the handle + sqlite3* handle = *static_cast(v.data()); + DEBUG_ASSERT_AND_HANDLE(handle != nullptr) { + qWarning() << "Could not get sqlite3 handle"; + m_database.close(); + DEBUG_ASSERT(!*this); // failure + return; // early exit + } + + int result = sqlite3_create_collation( + handle, + kStringCollationFunc, + SQLITE_UTF16, + nullptr, + sqliteStringCompareUTF16); + DEBUG_ASSERT_AND_HANDLE(result == SQLITE_OK) { + qWarning() << "Failed to install string collation function:" << result; + } + + result = sqlite3_create_function( + handle, + "like", + 2, + SQLITE_ANY, + nullptr, + sqliteLike, + nullptr, nullptr); + DEBUG_ASSERT_AND_HANDLE(result == SQLITE_OK) { + qWarning() << "Failed to install like 2 function:" << result; + } + + result = sqlite3_create_function( + handle, + "like", + 3, + SQLITE_UTF8, // No conversion, Data is stored as UTF8 + nullptr, + sqliteLike, + nullptr, nullptr); + DEBUG_ASSERT_AND_HANDLE(result == SQLITE_OK) { + qWarning() << "Failed to install like 3 function:" << result; + } + + DEBUG_ASSERT(*this); // success + return; // early exit + } +#endif // __SQLITE3__ + qWarning() << "Unsupported database driver:" << v.typeName(); +} + +DbConnection::~DbConnection() { + DEBUG_ASSERT_AND_HANDLE(*this) { + qWarning() + << "Database connection has already been closed:" + << *this; + return; // early exit + } + // There should never be an outstanding transaction when this code is + // called. If there is, it means we probably aren't committing a + // transaction somewhere that should be. + DEBUG_ASSERT_AND_HANDLE(!m_database.rollback()) { + qWarning() + << "Rolled back open transaction before closing database connection:" + << *this; + } + DEBUG_ASSERT(*this); + qDebug() + << "Closing database connection:" + << *this; + m_database.close(); + DEBUG_ASSERT(!*this); +} + +QDebug operator<<(QDebug debug, const DbConnection& dbConnection) { + return debug << kDatabaseType << dbConnection.m_filePath; +} + +//static +int DbConnection::likeCompareLatinLow( + QString* pattern, + QString* string, + QChar esc) { + makeLatinLow(pattern->data(), pattern->length()); + makeLatinLow(string->data(), string->length()); + return likeCompareInner( + pattern->data(), pattern->length(), + string->data(), string->length(), + esc); +} + + +/* +static int +likeCompare(nsAString::const_iterator aPatternItr, + nsAString::const_iterator aPatternEnd, + nsAString::const_iterator aStringItr, + nsAString::const_iterator aStringEnd, + PRUnichar aEscape) +{ + const PRUnichar LIKE_MATCH_ALL('%'); + const PRUnichar LIKE_MATCH_ONE('_'); + + PRBool lastWasEscape = PR_FALSE; + while (aPatternItr != aPatternEnd) { + +* What we do in here is take a look at each character from the input +* pattern, and do something with it. There are 4 possibilities: +* 1) character is an un-escaped match-all character +* 2) character is an un-escaped match-one character +* 3) character is an un-escaped escape character +* 4) character is not any of the above + + if (!lastWasEscape && *aPatternItr == LIKE_MATCH_ALL) { + // CASE 1 + +* Now we need to skip any LIKE_MATCH_ALL or LIKE_MATCH_ONE characters that follow a +* LIKE_MATCH_ALL character. For each LIKE_MATCH_ONE character, skip one character +* in the pattern string. + + while (*aPatternItr == LIKE_MATCH_ALL || *aPatternItr == LIKE_MATCH_ONE) { + if (*aPatternItr == LIKE_MATCH_ONE) { + // If we've hit the end of the string we are testing, no match + if (aStringItr == aStringEnd) + return 0; + aStringItr++; + } + aPatternItr++; + } + + // If we've hit the end of the pattern string, match + if (aPatternItr == aPatternEnd) + return 1; + + while (aStringItr != aStringEnd) { + if (likeCompare(aPatternItr, aPatternEnd, aStringItr, aStringEnd, aEscape)) { + // we've hit a match, so indicate this + return 1; + } + aStringItr++; + } + + // No match + return 0; + } else if (!lastWasEscape && *aPatternItr == LIKE_MATCH_ONE) { + // CASE 2 + if (aStringItr == aStringEnd) { + // If we've hit the end of the string we are testing, no match + return 0; + } + aStringItr++; + lastWasEscape = PR_FALSE; + } else if (!lastWasEscape && *aPatternItr == aEscape) { + // CASE 3 + lastWasEscape = PR_TRUE; + } else { + // CASE 4 + if (ToUpperCase(*aStringItr) != ToUpperCase(*aPatternItr)) { + // If we've hit a point where the strings don't match, there is no match + return 0; + } + aStringItr++; + lastWasEscape = PR_FALSE; + } + + aPatternItr++; + } + + return aStringItr == aStringEnd; +} + + +void +likeFunction(sqlite3_context *p, + int aArgc, + sqlite3_value **aArgv) +{ + NS_ASSERTION(2 == aArgc || 3 == aArgc, "Invalid number of arguments!"); + + if (sqlite3_value_bytes(aArgv[0]) > SQLITE_MAX_LIKE_PATTERN_LENGTH) { + sqlite3_result_error(p, "LIKE or GLOB pattern too complex", SQLITE_TOOBIG); + return; + } + + if (!sqlite3_value_text16(aArgv[0]) || !sqlite3_value_text16(aArgv[1])) + return; + + nsDependentString A(static_cast(sqlite3_value_text16(aArgv[1]))); + nsDependentString B(static_cast(sqlite3_value_text16(aArgv[0]))); + NS_ASSERTION(!B.IsEmpty(), "LIKE string must not be null!"); + + PRUnichar E = 0; + if (3 == aArgc) + E = static_cast(sqlite3_value_text16(aArgv[2]))[0]; + + nsAString::const_iterator itrString, endString; + A.BeginReading(itrString); + A.EndReading(endString); + nsAString::const_iterator itrPattern, endPattern; + B.BeginReading(itrPattern); + B.EndReading(endPattern); + sqlite3_result_int(p, likeCompare(itrPattern, endPattern, + itrString, endString, E)); +} +*/ diff --git a/src/util/db/dbconnection.h b/src/util/db/dbconnection.h new file mode 100644 index 0000000000..e3ce9eb27d --- /dev/null +++ b/src/util/db/dbconnection.h @@ -0,0 +1,40 @@ +#ifndef MIXXX_DBCONNECTION_H +#define MIXXX_DBCONNECTION_H + + +#include +#include + + +class DbConnection final { + public: + explicit DbConnection( + const QString& dirPath); + ~DbConnection(); + + operator bool() const { + return m_database.isOpen(); + } + + QSqlDatabase& database() { + return m_database; + } + + static const char* const kStringCollationFunc; + + static int likeCompareLatinLow( + QString* pattern, + QString* string, + QChar esc); + + friend QDebug operator<<( + QDebug debug, + const DbConnection& dbConnection); + + private: + QString m_filePath; + QSqlDatabase m_database; +}; + + +#endif // MIXXX_DBCONNECTION_H diff --git a/src/util/string.h b/src/util/string.h new file mode 100644 index 0000000000..8fa8476e0d --- /dev/null +++ b/src/util/string.h @@ -0,0 +1,16 @@ +#ifndef MIXXX_STRING_H +#define MIXXX_STRING_H + + +#include + + +// The default comparison of strings for sorting. +inline +int compareLocalAwareCaseInsensitive( + const QString& first, const QString& second) { + return QString::localeAwareCompare(first.toLower(), second.toLower()); +} + + +#endif // MIXXX_STRING_H -- cgit v1.2.3