From 28b0b67eed9ae857dd5b093a05cac9e969651552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 2 Sep 2018 23:31:39 +0200 Subject: [PATCH 01/13] Allow "" to filter for explicite empty fileds --- src/library/crate/cratestorage.cpp | 29 +++++++++++++- src/library/crate/cratestorage.h | 2 + src/library/searchquery.cpp | 62 ++++++++++++++++++++++++++++++ src/library/searchquery.h | 32 +++++++++++++++ src/library/searchqueryparser.cpp | 34 +++++++++++++--- 5 files changed, 151 insertions(+), 8 deletions(-) diff --git a/src/library/crate/cratestorage.cpp b/src/library/crate/cratestorage.cpp index a7509a04e90..f8c87a20d08 100644 --- a/src/library/crate/cratestorage.cpp +++ b/src/library/crate/cratestorage.cpp @@ -462,6 +462,17 @@ QString CrateStorage::formatQueryForTrackIdsByCrateNameLike( escapedCrateNameLike); } +//static +QString CrateStorage::formatQueryForTrackIdsWithCrate() { + return QString("SELECT DISTINCT %1 FROM %2 JOIN %3 ON %4=%5 ORDER BY %1").arg( + CRATETRACKSTABLE_TRACKID, + CRATE_TRACKS_TABLE, + CRATE_TABLE, + CRATETRACKSTABLE_CRATEID, + CRATETABLE_ID); +} + + CrateTrackSelectResult CrateStorage::selectCrateTracksSorted(CrateId crateId) const { FwdSqlQuery query(m_database, QString( @@ -514,8 +525,6 @@ CrateSummarySelectResult CrateStorage::selectCratesWithTrackCount(const QList CrateStorage::collectCrateIdsOfTracks(const QList& trackIds) const { // NOTE(uklotzde): One query per track id. This could be optimized diff --git a/src/library/crate/cratestorage.h b/src/library/crate/cratestorage.h index 1851f04d129..fdf810fd4d8 100644 --- a/src/library/crate/cratestorage.h +++ b/src/library/crate/cratestorage.h @@ -268,6 +268,7 @@ class CrateStorage: public virtual /*implements*/ SqlStorage { QString formatQueryForTrackIdsByCrateNameLike( const QString& crateNameLike) const; // no db access + static QString formatQueryForTrackIdsWithCrate(); // no db access // Select the track ids of a crate or the crate ids of a track respectively. // The results are sorted (ascending) by the target id, i.e. the id that is // not provided for filtering. This enables the caller to perform efficient @@ -280,6 +281,7 @@ class CrateStorage: public virtual /*implements*/ SqlStorage { const QList& trackIds) const; CrateTrackSelectResult selectTracksSortedByCrateNameLike( const QString& crateNameLike) const; + CrateTrackSelectResult selectAllTracksSorted() const; // Returns the set of crate ids for crates that contain any of the // provided track ids. diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index c7aad9931fa..74b1ae12361 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -166,6 +166,26 @@ QString TextFilterNode::toSql() const { return concatSqlClauses(searchClauses, "OR"); } +bool NullTextFilterNode::match(const TrackPointer& pTrack) const { + for (const auto& sqlColumn: m_sqlColumns) { + QVariant value = getTrackValueForColumn(pTrack, sqlColumn); + if (!value.isValid() || !qVariantCanConvert(value)) { + continue; + } + // only use the major coloumn + return value.toString().isEmpty(); + } + return false; +} + +QString NullTextFilterNode::toSql() const { + for (const auto& sqlColumn: m_sqlColumns) { + // only use the major coloumn + return QString("%1 IS NULL").arg(sqlColumn); + } + return QString(); +} + CrateFilterNode::CrateFilterNode(const CrateStorage* pCrateStorage, const QString& crateNameLike) : m_pCrateStorage(pCrateStorage), @@ -193,9 +213,35 @@ QString CrateFilterNode::toSql() const { m_pCrateStorage->formatQueryForTrackIdsByCrateNameLike(m_crateNameLike)); } + +NoCrateFilterNode::NoCrateFilterNode(const CrateStorage* pCrateStorage) + : m_pCrateStorage(pCrateStorage), + m_matchInitialized(false) { +} + +bool NoCrateFilterNode::match(const TrackPointer& pTrack) const { + if (!m_matchInitialized) { + CrateTrackSelectResult crateTracks( + m_pCrateStorage->selectAllTracksSorted()); + + while (crateTracks.next()) { + m_matchingTrackIds.push_back(crateTracks.trackId()); + } + + m_matchInitialized = true; + } + + return !std::binary_search(m_matchingTrackIds.begin(), m_matchingTrackIds.end(), pTrack->getId()); +} + +QString NoCrateFilterNode::toSql() const { + return QString("id NOT IN (%1)").arg(CrateStorage::formatQueryForTrackIdsWithCrate()); +} + NumericFilterNode::NumericFilterNode(const QStringList& sqlColumns) : m_sqlColumns(sqlColumns), m_bOperatorQuery(false), + m_bNullQuery(false), m_operator("="), m_dOperatorArgument(0.0), m_bRangeQuery(false), @@ -210,6 +256,11 @@ NumericFilterNode::NumericFilterNode( } void NumericFilterNode::init(QString argument) { + if (argument == "\"\"") { + m_bNullQuery = true; + return; + } + QRegExp operatorMatcher("^(>|>=|=|<|<=)(.*)$"); if (operatorMatcher.indexIn(argument) != -1) { m_operator = operatorMatcher.cap(1); @@ -244,6 +295,9 @@ bool NumericFilterNode::match(const TrackPointer& pTrack) const { for (const auto& sqlColumn: m_sqlColumns) { QVariant value = getTrackValueForColumn(pTrack, sqlColumn); if (!value.isValid() || !qVariantCanConvert(value)) { + if (m_bNullQuery) { + return true; + } continue; } @@ -265,6 +319,14 @@ bool NumericFilterNode::match(const TrackPointer& pTrack) const { } QString NumericFilterNode::toSql() const { + if (m_bNullQuery) { + for (const auto& sqlColumn: m_sqlColumns) { + // only use the major coloumn + return QString("%1 IS NULL").arg(sqlColumn); + } + return QString(); + } + if (m_bOperatorQuery) { QStringList searchClauses; for (const auto& sqlColumn: m_sqlColumns) { diff --git a/src/library/searchquery.h b/src/library/searchquery.h index bf744ab24db..6a15413f8f4 100644 --- a/src/library/searchquery.h +++ b/src/library/searchquery.h @@ -91,6 +91,23 @@ class TextFilterNode : public QueryNode { QString m_argument; }; +class NullTextFilterNode : public QueryNode { + public: + NullTextFilterNode(const QSqlDatabase& database, + const QStringList& sqlColumns) + : m_database(database), + m_sqlColumns(sqlColumns) { + } + + bool match(const TrackPointer& pTrack) const override; + QString toSql() const override; + + private: + QSqlDatabase m_database; + QStringList m_sqlColumns; +}; + + class CrateFilterNode : public QueryNode { public: CrateFilterNode(const CrateStorage* pCrateStorage, @@ -106,6 +123,20 @@ class CrateFilterNode : public QueryNode { mutable std::vector m_matchingTrackIds; }; +class NoCrateFilterNode : public QueryNode { + public: + NoCrateFilterNode(const CrateStorage* pCrateStorage); + + bool match(const TrackPointer& pTrack) const override; + QString toSql() const override; + + private: + const CrateStorage* m_pCrateStorage; + QString m_crateNameLike; + mutable bool m_matchInitialized; + mutable std::vector m_matchingTrackIds; +}; + class NumericFilterNode : public QueryNode { public: NumericFilterNode(const QStringList& sqlColumns, const QString& argument); @@ -128,6 +159,7 @@ class NumericFilterNode : public QueryNode { QStringList m_sqlColumns; bool m_bOperatorQuery; + bool m_bNullQuery; QString m_operator; double m_dOperatorArgument; bool m_bRangeQuery; diff --git a/src/library/searchqueryparser.cpp b/src/library/searchqueryparser.cpp index 081e8cb1b00..190efc9c9e0 100644 --- a/src/library/searchqueryparser.cpp +++ b/src/library/searchqueryparser.cpp @@ -67,7 +67,7 @@ QString SearchQueryParser::getTextArgument(QString argument, QStringList* tokens) const { // If the argument is empty, assume the user placed a space after an // advanced search command. Consume another token and treat that as the - // argument. TODO(XXX) support quoted search phrases as arguments + // argument. argument = argument.trimmed(); if (argument.length() == 0) { if (tokens->length() > 0) { @@ -99,8 +99,14 @@ QString SearchQueryParser::getTextArgument(QString argument, tokens->push_front(remaining); } - // Slice off the quote and everything after. - argument = argument.left(quote_index); + if (quote_index == 0) { + // We have found an explicit empty string "" + // return it as "" to distingish it from anunfinished empty string + argument = "\"\""; + } else { + // Slice off the quote and everything after. + argument = argument.left(quote_index); + } } return argument; @@ -125,7 +131,18 @@ void SearchQueryParser::parseTokens(QStringList tokens, QString argument = getTextArgument( m_textFilterMatcher.cap(2), &tokens).trimmed(); - if (!argument.isEmpty()) { + if (argument == "\"\"") { + qDebug() << "argument explicit empty"; + if (field == "crate") { + pNode = std::make_unique( + &m_pTrackCollection->crates()); + qDebug() << pNode->toSql(); + } else { + pNode = std::make_unique( + m_pTrackCollection->database(), m_fieldToSqlColumns[field]); + qDebug() << pNode->toSql(); + } + } else if (!argument.isEmpty()) { if (field == "crate") { pNode = std::make_unique( &m_pTrackCollection->crates(), argument); @@ -153,8 +170,13 @@ void SearchQueryParser::parseTokens(QStringList tokens, mixxx::track::io::key::ChromaticKey key = KeyUtils::guessKeyFromText(argument); if (key == mixxx::track::io::key::INVALID) { - pNode = std::make_unique( - m_pTrackCollection->database(), m_fieldToSqlColumns[field], argument); + if (argument == "\"\"") { + pNode = std::make_unique( + m_pTrackCollection->database(), m_fieldToSqlColumns[field]); + } else { + pNode = std::make_unique( + m_pTrackCollection->database(), m_fieldToSqlColumns[field], argument); + } } else { pNode = std::make_unique(key, fuzzy); } From 76a1119a6e69352baeae8c48e8d57ae8f6b1b752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 4 Oct 2018 21:20:28 +0200 Subject: [PATCH 02/13] NullTextFilterNode::match matches also invalid values --- src/library/searchquery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 74b1ae12361..de94a6ba122 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -168,11 +168,11 @@ QString TextFilterNode::toSql() const { bool NullTextFilterNode::match(const TrackPointer& pTrack) const { for (const auto& sqlColumn: m_sqlColumns) { + // only use the major coloumn QVariant value = getTrackValueForColumn(pTrack, sqlColumn); if (!value.isValid() || !qVariantCanConvert(value)) { - continue; + return true; } - // only use the major coloumn return value.toString().isEmpty(); } return false; From b9e2be79b0e9e7572e8388d484deb34c4350d1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 4 Oct 2018 21:26:26 +0200 Subject: [PATCH 03/13] Fix typo --- src/library/searchquery.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index de94a6ba122..28a0b3419ab 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -168,7 +168,7 @@ QString TextFilterNode::toSql() const { bool NullTextFilterNode::match(const TrackPointer& pTrack) const { for (const auto& sqlColumn: m_sqlColumns) { - // only use the major coloumn + // only use the major column QVariant value = getTrackValueForColumn(pTrack, sqlColumn); if (!value.isValid() || !qVariantCanConvert(value)) { return true; @@ -180,7 +180,7 @@ bool NullTextFilterNode::match(const TrackPointer& pTrack) const { QString NullTextFilterNode::toSql() const { for (const auto& sqlColumn: m_sqlColumns) { - // only use the major coloumn + // only use the major column return QString("%1 IS NULL").arg(sqlColumn); } return QString(); @@ -321,7 +321,7 @@ bool NumericFilterNode::match(const TrackPointer& pTrack) const { QString NumericFilterNode::toSql() const { if (m_bNullQuery) { for (const auto& sqlColumn: m_sqlColumns) { - // only use the major coloumn + // only use the major column return QString("%1 IS NULL").arg(sqlColumn); } return QString(); From f54cf981fac88058ca97acd122726dc36c65bb84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 4 Oct 2018 21:33:38 +0200 Subject: [PATCH 04/13] Use the first column esplicit --- src/library/searchquery.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 28a0b3419ab..15d748862be 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -167,9 +167,9 @@ QString TextFilterNode::toSql() const { } bool NullTextFilterNode::match(const TrackPointer& pTrack) const { - for (const auto& sqlColumn: m_sqlColumns) { + if (!m_sqlColumns.isEmpty()) { // only use the major column - QVariant value = getTrackValueForColumn(pTrack, sqlColumn); + QVariant value = getTrackValueForColumn(pTrack, m_sqlColumns.first()); if (!value.isValid() || !qVariantCanConvert(value)) { return true; } @@ -179,9 +179,9 @@ bool NullTextFilterNode::match(const TrackPointer& pTrack) const { } QString NullTextFilterNode::toSql() const { - for (const auto& sqlColumn: m_sqlColumns) { + if (!m_sqlColumns.isEmpty()) { // only use the major column - return QString("%1 IS NULL").arg(sqlColumn); + return QString("%1 IS NULL").arg(m_sqlColumns.first()); } return QString(); } From 11ce5be00571859cfa98b04527ad4387095ac40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 4 Oct 2018 22:02:06 +0200 Subject: [PATCH 05/13] Use CATETABLE_ID --- src/library/searchquery.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 15d748862be..d9b91d21202 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -5,8 +5,10 @@ #include "library/queryutil.h" #include "track/keyutils.h" #include "library/dao/trackschema.h" +#include "library/crate/crateschema.h" #include "util/db/sqllikewildcards.h" + QVariant getTrackValueForColumn(const TrackPointer& pTrack, const QString& column) { if (column == LIBRARYTABLE_ARTIST) { return pTrack->getArtist(); @@ -235,7 +237,9 @@ bool NoCrateFilterNode::match(const TrackPointer& pTrack) const { } QString NoCrateFilterNode::toSql() const { - return QString("id NOT IN (%1)").arg(CrateStorage::formatQueryForTrackIdsWithCrate()); + return QString("%1 NOT IN (%2)").arg( + CRATETABLE_ID, + CrateStorage::formatQueryForTrackIdsWithCrate()); } NumericFilterNode::NumericFilterNode(const QStringList& sqlColumns) From 532c9877721e43caeef541665ce96a163c3ee887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 4 Oct 2018 22:24:33 +0200 Subject: [PATCH 06/13] use a constant for "" --- src/library/searchquery.cpp | 2 +- src/library/searchquery.h | 2 ++ src/library/searchqueryparser.cpp | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index d9b91d21202..813f31c780b 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -260,7 +260,7 @@ NumericFilterNode::NumericFilterNode( } void NumericFilterNode::init(QString argument) { - if (argument == "\"\"") { + if (argument == kExpliciteEmpty) { m_bNullQuery = true; return; } diff --git a/src/library/searchquery.h b/src/library/searchquery.h index 6a15413f8f4..34567498b9d 100644 --- a/src/library/searchquery.h +++ b/src/library/searchquery.h @@ -16,6 +16,8 @@ #include "util/memory.h" #include "library/crate/cratestorage.h" +const QString kExpliciteEmpty = "\"\""; // "" searches for an empty string + QVariant getTrackValueForColumn(const TrackPointer& pTrack, const QString& column); class QueryNode { diff --git a/src/library/searchqueryparser.cpp b/src/library/searchqueryparser.cpp index 190efc9c9e0..818d34c973b 100644 --- a/src/library/searchqueryparser.cpp +++ b/src/library/searchqueryparser.cpp @@ -102,7 +102,7 @@ QString SearchQueryParser::getTextArgument(QString argument, if (quote_index == 0) { // We have found an explicit empty string "" // return it as "" to distingish it from anunfinished empty string - argument = "\"\""; + argument = kExpliciteEmpty; } else { // Slice off the quote and everything after. argument = argument.left(quote_index); @@ -131,7 +131,7 @@ void SearchQueryParser::parseTokens(QStringList tokens, QString argument = getTextArgument( m_textFilterMatcher.cap(2), &tokens).trimmed(); - if (argument == "\"\"") { + if (argument == kExpliciteEmpty) { qDebug() << "argument explicit empty"; if (field == "crate") { pNode = std::make_unique( @@ -170,7 +170,7 @@ void SearchQueryParser::parseTokens(QStringList tokens, mixxx::track::io::key::ChromaticKey key = KeyUtils::guessKeyFromText(argument); if (key == mixxx::track::io::key::INVALID) { - if (argument == "\"\"") { + if (argument == kExpliciteEmpty) { pNode = std::make_unique( m_pTrackCollection->database(), m_fieldToSqlColumns[field]); } else { From db56f9c6f4359410e35b26e01c452495c691d1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 4 Oct 2018 23:58:40 +0200 Subject: [PATCH 07/13] Improved query for tracks without crate --- src/library/crate/cratestorage.cpp | 20 +++++++-------- src/library/crate/cratestorage.h | 39 +++++++++++++++++++++++++++++- src/library/searchquery.cpp | 8 +++--- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/library/crate/cratestorage.cpp b/src/library/crate/cratestorage.cpp index f8c87a20d08..8ca519a70bd 100644 --- a/src/library/crate/cratestorage.cpp +++ b/src/library/crate/cratestorage.cpp @@ -129,6 +129,9 @@ CrateTrackQueryFields::CrateTrackQueryFields(const FwdSqlQuery& query) m_iTrackId(query.fieldIndex(CRATETRACKSTABLE_TRACKID)) { } +TrackQueryFields::TrackQueryFields(const FwdSqlQuery& query) + : m_iTrackId(query.fieldIndex(CRATETRACKSTABLE_TRACKID)) { +} CrateSummaryQueryFields::CrateSummaryQueryFields(const FwdSqlQuery& query) : CrateQueryFields(query), @@ -544,20 +547,15 @@ CrateTrackSelectResult CrateStorage::selectTracksSortedByCrateNameLike(const QSt } } -CrateTrackSelectResult CrateStorage::selectAllTracksSorted() const { +TrackSelectResult CrateStorage::selectAllTracksSorted() const { FwdSqlQuery query(m_database, QString( - "SELECT %1,%2 FROM %3 JOIN %4 ON %5 = %6 ORDER BY %1").arg( - CRATETRACKSTABLE_TRACKID, - CRATETRACKSTABLE_CRATEID, - CRATE_TRACKS_TABLE, - CRATE_TABLE, - CRATETABLE_ID, - CRATETRACKSTABLE_CRATEID, - CRATETABLE_NAME)); + "SELECT %1 FROM %2 GROUP BY %1 ORDER BY %1").arg( + CRATETRACKSTABLE_TRACKID, // %1 + CRATE_TRACKS_TABLE)); // %2 if (query.execPrepared()) { - return CrateTrackSelectResult(std::move(query)); + return TrackSelectResult(std::move(query)); } else { - return CrateTrackSelectResult(); + return TrackSelectResult(); } } diff --git a/src/library/crate/cratestorage.h b/src/library/crate/cratestorage.h index fdf810fd4d8..ee3d1b5171f 100644 --- a/src/library/crate/cratestorage.h +++ b/src/library/crate/cratestorage.h @@ -150,6 +150,20 @@ class CrateTrackQueryFields { DbFieldIndex m_iTrackId; }; +class TrackQueryFields { + public: + TrackQueryFields() = default; + explicit TrackQueryFields(const FwdSqlQuery& query); + virtual ~TrackQueryFields() = default; + + TrackId trackId(const FwdSqlQuery& query) const { + return TrackId(query.fieldValue(m_iTrackId)); + } + + private: + DbFieldIndex m_iTrackId; +}; + class CrateTrackSelectResult: public FwdSqlQuerySelectResult { public: CrateTrackSelectResult(CrateTrackSelectResult&& other) @@ -176,6 +190,29 @@ class CrateTrackSelectResult: public FwdSqlQuerySelectResult { CrateTrackQueryFields m_queryFields; }; +class TrackSelectResult: public FwdSqlQuerySelectResult { +public: + TrackSelectResult(TrackSelectResult&& other) + : FwdSqlQuerySelectResult(std::move(other)), + m_queryFields(std::move(other.m_queryFields)) { + } + ~TrackSelectResult() override = default; + + TrackId trackId() const { + return m_queryFields.trackId(query()); + } + +private: + friend class CrateStorage; + TrackSelectResult() = default; + explicit TrackSelectResult(FwdSqlQuery&& query) + : FwdSqlQuerySelectResult(std::move(query)), + m_queryFields(FwdSqlQuerySelectResult::query()) { + } + + TrackQueryFields m_queryFields; +}; + class CrateStorage: public virtual /*implements*/ SqlStorage { public: CrateStorage() = default; @@ -281,7 +318,7 @@ class CrateStorage: public virtual /*implements*/ SqlStorage { const QList& trackIds) const; CrateTrackSelectResult selectTracksSortedByCrateNameLike( const QString& crateNameLike) const; - CrateTrackSelectResult selectAllTracksSorted() const; + TrackSelectResult selectAllTracksSorted() const; // Returns the set of crate ids for crates that contain any of the // provided track ids. diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 813f31c780b..39ea1983b5f 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -223,11 +223,11 @@ NoCrateFilterNode::NoCrateFilterNode(const CrateStorage* pCrateStorage) bool NoCrateFilterNode::match(const TrackPointer& pTrack) const { if (!m_matchInitialized) { - CrateTrackSelectResult crateTracks( - m_pCrateStorage->selectAllTracksSorted()); + TrackSelectResult tracks( + m_pCrateStorage->selectAllTracksSorted()); - while (crateTracks.next()) { - m_matchingTrackIds.push_back(crateTracks.trackId()); + while (tracks.next()) { + m_matchingTrackIds.push_back(tracks.trackId()); } m_matchInitialized = true; From b817a323bed5a1cb8f4250e665d1e8d516910aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 8 Oct 2018 13:58:19 +0200 Subject: [PATCH 08/13] Search for Null or Empty --- src/library/searchquery.cpp | 6 +++--- src/library/searchquery.h | 4 ++-- src/library/searchqueryparser.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 39ea1983b5f..6b5c370a585 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -168,7 +168,7 @@ QString TextFilterNode::toSql() const { return concatSqlClauses(searchClauses, "OR"); } -bool NullTextFilterNode::match(const TrackPointer& pTrack) const { +bool NullOrEmptyTextFilterNode::match(const TrackPointer& pTrack) const { if (!m_sqlColumns.isEmpty()) { // only use the major column QVariant value = getTrackValueForColumn(pTrack, m_sqlColumns.first()); @@ -180,10 +180,10 @@ bool NullTextFilterNode::match(const TrackPointer& pTrack) const { return false; } -QString NullTextFilterNode::toSql() const { +QString NullOrEmptyTextFilterNode::toSql() const { if (!m_sqlColumns.isEmpty()) { // only use the major column - return QString("%1 IS NULL").arg(m_sqlColumns.first()); + return QString("%1 IS NULL OR %1 IS ''").arg(m_sqlColumns.first()); } return QString(); } diff --git a/src/library/searchquery.h b/src/library/searchquery.h index 34567498b9d..d8ff05534d9 100644 --- a/src/library/searchquery.h +++ b/src/library/searchquery.h @@ -93,9 +93,9 @@ class TextFilterNode : public QueryNode { QString m_argument; }; -class NullTextFilterNode : public QueryNode { +class NullOrEmptyTextFilterNode : public QueryNode { public: - NullTextFilterNode(const QSqlDatabase& database, + NullOrEmptyTextFilterNode(const QSqlDatabase& database, const QStringList& sqlColumns) : m_database(database), m_sqlColumns(sqlColumns) { diff --git a/src/library/searchqueryparser.cpp b/src/library/searchqueryparser.cpp index 818d34c973b..5579f39f8a4 100644 --- a/src/library/searchqueryparser.cpp +++ b/src/library/searchqueryparser.cpp @@ -138,7 +138,7 @@ void SearchQueryParser::parseTokens(QStringList tokens, &m_pTrackCollection->crates()); qDebug() << pNode->toSql(); } else { - pNode = std::make_unique( + pNode = std::make_unique( m_pTrackCollection->database(), m_fieldToSqlColumns[field]); qDebug() << pNode->toSql(); } @@ -171,7 +171,7 @@ void SearchQueryParser::parseTokens(QStringList tokens, KeyUtils::guessKeyFromText(argument); if (key == mixxx::track::io::key::INVALID) { if (argument == kExpliciteEmpty) { - pNode = std::make_unique( + pNode = std::make_unique( m_pTrackCollection->database(), m_fieldToSqlColumns[field]); } else { pNode = std::make_unique( From dd1f85a93cad44da268704721f9c5fd8882e011c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 8 Oct 2018 14:08:28 +0200 Subject: [PATCH 09/13] rename kExpliciteEmpty to kMissingFieldSearchTerm --- src/library/searchquery.cpp | 2 +- src/library/searchquery.h | 2 +- src/library/searchqueryparser.cpp | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 6b5c370a585..36f713d34ec 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -260,7 +260,7 @@ NumericFilterNode::NumericFilterNode( } void NumericFilterNode::init(QString argument) { - if (argument == kExpliciteEmpty) { + if (argument == kMissingFieldSearchTerm) { m_bNullQuery = true; return; } diff --git a/src/library/searchquery.h b/src/library/searchquery.h index d8ff05534d9..b9b579b357b 100644 --- a/src/library/searchquery.h +++ b/src/library/searchquery.h @@ -16,7 +16,7 @@ #include "util/memory.h" #include "library/crate/cratestorage.h" -const QString kExpliciteEmpty = "\"\""; // "" searches for an empty string +const QString kMissingFieldSearchTerm = "\"\""; // "" searches for an empty string QVariant getTrackValueForColumn(const TrackPointer& pTrack, const QString& column); diff --git a/src/library/searchqueryparser.cpp b/src/library/searchqueryparser.cpp index 5579f39f8a4..e081d20aed8 100644 --- a/src/library/searchqueryparser.cpp +++ b/src/library/searchqueryparser.cpp @@ -102,7 +102,7 @@ QString SearchQueryParser::getTextArgument(QString argument, if (quote_index == 0) { // We have found an explicit empty string "" // return it as "" to distingish it from anunfinished empty string - argument = kExpliciteEmpty; + argument = kMissingFieldSearchTerm; } else { // Slice off the quote and everything after. argument = argument.left(quote_index); @@ -131,7 +131,7 @@ void SearchQueryParser::parseTokens(QStringList tokens, QString argument = getTextArgument( m_textFilterMatcher.cap(2), &tokens).trimmed(); - if (argument == kExpliciteEmpty) { + if (argument == kMissingFieldSearchTerm) { qDebug() << "argument explicit empty"; if (field == "crate") { pNode = std::make_unique( @@ -170,7 +170,7 @@ void SearchQueryParser::parseTokens(QStringList tokens, mixxx::track::io::key::ChromaticKey key = KeyUtils::guessKeyFromText(argument); if (key == mixxx::track::io::key::INVALID) { - if (argument == kExpliciteEmpty) { + if (argument == kMissingFieldSearchTerm) { pNode = std::make_unique( m_pTrackCollection->database(), m_fieldToSqlColumns[field]); } else { From 6811cf30221ece68484f0e2755abfc7d8b39bf77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 17 Oct 2018 22:47:06 +0200 Subject: [PATCH 10/13] Fix building after merge --- src/library/searchquery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index a81e9f6230a..5b00049533c 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -172,7 +172,7 @@ bool NullOrEmptyTextFilterNode::match(const TrackPointer& pTrack) const { if (!m_sqlColumns.isEmpty()) { // only use the major column QVariant value = getTrackValueForColumn(pTrack, m_sqlColumns.first()); - if (!value.isValid() || !qVariantCanConvert(value)) { + if (!value.isValid() || !value.canConvert(QMetaType::QString)) { return true; } return value.toString().isEmpty(); From fc22212d003a46749477af737711f3df32b7c3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 21 Oct 2018 22:28:19 +0200 Subject: [PATCH 11/13] use SELECT DISTINCT --- src/library/crate/cratestorage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/crate/cratestorage.cpp b/src/library/crate/cratestorage.cpp index 8affdb23e07..5b8ce8b218c 100644 --- a/src/library/crate/cratestorage.cpp +++ b/src/library/crate/cratestorage.cpp @@ -549,7 +549,7 @@ CrateTrackSelectResult CrateStorage::selectTracksSortedByCrateNameLike(const QSt TrackSelectResult CrateStorage::selectAllTracksSorted() const { FwdSqlQuery query(m_database, QString( - "SELECT %1 FROM %2 GROUP BY %1 ORDER BY %1").arg( + "SELECT DISTINCT %1 FROM %2 ORDER BY %1").arg( CRATETRACKSTABLE_TRACKID, // %1 CRATE_TRACKS_TABLE)); // %2 if (query.execPrepared()) { From 5942d22a0215ec2fea78e86592539dcfb1dd0a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 22 Oct 2018 00:10:25 +0200 Subject: [PATCH 12/13] Added NullNumeric filter node --- src/library/searchquery.cpp | 24 ++++++++++++++++++++++++ src/library/searchquery.h | 10 ++++++++++ src/library/searchqueryparser.cpp | 9 +++++++-- src/library/searchqueryparser.h | 1 + 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 5b00049533c..d100d30e01c 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -356,6 +356,30 @@ QString NumericFilterNode::toSql() const { return QString(); } +NullNumericFilterNode::NullNumericFilterNode(const QStringList& sqlColumns) + : m_sqlColumns(sqlColumns) { +} + +bool NullNumericFilterNode::match(const TrackPointer& pTrack) const { + if (!m_sqlColumns.isEmpty()) { + // only use the major column + QVariant value = getTrackValueForColumn(pTrack, m_sqlColumns.first()); + if (!value.isValid() || !value.canConvert(QMetaType::Double)) { + return true; + } + } + return false; +} + +QString NullNumericFilterNode::toSql() const { + if (!m_sqlColumns.isEmpty()) { + // only use the major column + return QString("%1 IS NULL").arg(m_sqlColumns.first()); + } + return QString(); +} + + DurationFilterNode::DurationFilterNode( const QStringList& sqlColumns, const QString& argument) : NumericFilterNode(sqlColumns) { diff --git a/src/library/searchquery.h b/src/library/searchquery.h index 39b126efa18..422ebf68fad 100644 --- a/src/library/searchquery.h +++ b/src/library/searchquery.h @@ -169,6 +169,16 @@ class NumericFilterNode : public QueryNode { double m_dRangeHigh; }; +class NullNumericFilterNode : public QueryNode { + public: + NullNumericFilterNode(const QStringList& sqlColumns); + + bool match(const TrackPointer& pTrack) const override; + QString toSql() const override; + + QStringList m_sqlColumns; +}; + class DurationFilterNode : public NumericFilterNode { public: DurationFilterNode(const QStringList& sqlColumns, const QString& argument); diff --git a/src/library/searchqueryparser.cpp b/src/library/searchqueryparser.cpp index 0f0b4ccedc5..75cba59e72c 100644 --- a/src/library/searchqueryparser.cpp +++ b/src/library/searchqueryparser.cpp @@ -170,8 +170,13 @@ void SearchQueryParser::parseTokens(QStringList tokens, m_numericFilterMatcher.cap(2), &tokens).trimmed(); if (!argument.isEmpty()) { - pNode = std::make_unique( - m_fieldToSqlColumns[field], argument); + if (argument == kMissingFieldSearchTerm) { + pNode = std::make_unique( + m_fieldToSqlColumns[field]); + } else { + pNode = std::make_unique( + m_fieldToSqlColumns[field], argument); + } } } else if (m_specialFilterMatcher.indexIn(token) != -1) { bool fuzzy = token.startsWith(kFuzzyPrefix); diff --git a/src/library/searchqueryparser.h b/src/library/searchqueryparser.h index 0769bd2f1fc..37a2bf58901 100644 --- a/src/library/searchqueryparser.h +++ b/src/library/searchqueryparser.h @@ -40,6 +40,7 @@ class SearchQueryParser { QRegExp m_fuzzyMatcher; QRegExp m_textFilterMatcher; + QRegExp m_crateFilterMatcher; QRegExp m_numericFilterMatcher; QRegExp m_specialFilterMatcher; From 82cb5924980717f7e64035aa809c4ec7fd61a095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 16 Dec 2018 22:21:01 +0100 Subject: [PATCH 13/13] added explicit keyword --- src/library/searchquery.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/library/searchquery.h b/src/library/searchquery.h index bc2e804b939..a26f046bbb3 100644 --- a/src/library/searchquery.h +++ b/src/library/searchquery.h @@ -123,7 +123,7 @@ class CrateFilterNode : public QueryNode { class NoCrateFilterNode : public QueryNode { public: - NoCrateFilterNode(const CrateStorage* pCrateStorage); + explicit NoCrateFilterNode(const CrateStorage* pCrateStorage); bool match(const TrackPointer& pTrack) const override; QString toSql() const override; @@ -167,7 +167,7 @@ class NumericFilterNode : public QueryNode { class NullNumericFilterNode : public QueryNode { public: - NullNumericFilterNode(const QStringList& sqlColumns); + explicit NullNumericFilterNode(const QStringList& sqlColumns); bool match(const TrackPointer& pTrack) const override; QString toSql() const override;