diff --git a/src/musikcore/library/LocalMetadataProxy.cpp b/src/musikcore/library/LocalMetadataProxy.cpp index 2486ef5bd..e213745d1 100644 --- a/src/musikcore/library/LocalMetadataProxy.cpp +++ b/src/musikcore/library/LocalMetadataProxy.cpp @@ -305,6 +305,7 @@ ITrackList* LocalMetadataProxy::QueryTracks(const char* query, int limit, int of std::shared_ptr search( new SearchTrackListQuery( this->library, + SearchTrackListQuery::MatchType::Substring, std::string(query ? query : ""), TrackSortType::Album)); @@ -456,6 +457,7 @@ IValueList* LocalMetadataProxy::QueryCategoryWithPredicate( std::shared_ptr search( new CategoryListQuery( + CategoryListQuery::MatchType::Substring, type, { field, predicateId }, std::string(filter ? filter : ""))); @@ -480,7 +482,10 @@ IValueList* LocalMetadataProxy::QueryCategoryWithPredicates( auto predicateList = toPredicateList(predicates, predicateCount); auto query = std::make_shared( - type, predicateList, std::string(filter ? filter : "")); + CategoryListQuery::MatchType::Substring, + type, + predicateList, + std::string(filter ? filter : "")); this->library->EnqueueAndWait(query); diff --git a/src/musikcore/library/QueryBase.h b/src/musikcore/library/QueryBase.h index cf72de229..83db6723b 100644 --- a/src/musikcore/library/QueryBase.h +++ b/src/musikcore/library/QueryBase.h @@ -50,6 +50,11 @@ namespace musik { namespace core { namespace library { namespace query { public sigslot::has_slots<> { public: + enum class MatchType : int { + Substring = 1, + Regex = 2 + }; + QueryBase() : status(IQuery::Idle) , options(0) diff --git a/src/musikcore/library/query/CategoryListQuery.cpp b/src/musikcore/library/query/CategoryListQuery.cpp index 29763b939..865349fac 100755 --- a/src/musikcore/library/query/CategoryListQuery.cpp +++ b/src/musikcore/library/query/CategoryListQuery.cpp @@ -56,35 +56,44 @@ static const std::string kUnfilteredPlaylistsQuery = static const std::string kFilteredPlaylistsQuery = "SELECT DISTINCT id, name " "FROM playlists" - "WHERE LOWER(name) LIKE LOWER(?) " + "WHERE LOWER(name) {{match_type}} ? " "ORDER BY name;"; const std::string CategoryListQuery::kQueryName = "CategoryListQuery"; +static std::string getMatchType(CategoryListQuery::MatchType matchType) { + return matchType == CategoryListQuery::MatchType::Regex ? "REGEXP" : "LIKE"; +} + CategoryListQuery::CategoryListQuery() { } CategoryListQuery::CategoryListQuery( - const std::string& trackField, const std::string& filter) -: CategoryListQuery(trackField, category::PredicateList(), filter) { + MatchType matchType, + const std::string& trackField, + const std::string& filter) +: CategoryListQuery(matchType, trackField, category::PredicateList(), filter) { } CategoryListQuery::CategoryListQuery( + MatchType matchType, const std::string& trackField, const category::Predicate predicate, const std::string& filter) -: CategoryListQuery(trackField, category::PredicateList{ predicate }, filter) { +: CategoryListQuery(matchType, trackField, category::PredicateList{ predicate }, filter) { } CategoryListQuery::CategoryListQuery( + MatchType matchType, const std::string& trackField, const category::PredicateList predicates, const std::string& filter) -: trackField(trackField) +: matchType(MatchType::Regex) +, trackField(trackField) , filter(filter) { result.reset(new SdkValueList()); - if (this->filter.size()) { + if (this->filter.size() && this->matchType == MatchType::Substring) { /* transform "FilteR" => "%filter%" */ std::string wild = this->filter; std::transform(wild.begin(), wild.end(), wild.begin(), tolower); @@ -134,6 +143,8 @@ void CategoryListQuery::QueryPlaylist(musik::core::db::Connection& db) { ? kFilteredPlaylistsQuery : kUnfilteredPlaylistsQuery; + category::ReplaceAll(query, "{{match_type}}", getMatchType(matchType)); + Statement stmt(query.c_str() , db); if (filtered) { @@ -157,6 +168,7 @@ void CategoryListQuery::QueryRegular(musik::core::db::Connection &db) { if (this->filter.size()) { regularFilter = category::REGULAR_FILTER; category::ReplaceAll(regularFilter, "{{table}}", prop.first); + category::ReplaceAll(regularFilter, "{{match_type}}", getMatchType(matchType)); args.push_back(category::StringArgument(this->filter)); } @@ -184,6 +196,7 @@ void CategoryListQuery::QueryExtended(musik::core::db::Connection &db) { if (this->filter.size()) { extendedFilter = category::EXTENDED_FILTER; args.push_back(category::StringArgument(this->filter)); + category::ReplaceAll(extendedFilter, "{{match_type}}", getMatchType(matchType)); } category::ReplaceAll(query, "{{regular_predicates}}", regular); @@ -242,6 +255,7 @@ std::string CategoryListQuery::SerializeQuery() { query["options"] = { { "trackField", this->trackField }, { "filter", this->filter }, + { "matchType", this->matchType }, { "outputType", this->outputType }, { "regularPredicateList", PredicateListToJson(this->regular) }, { "extendedPredicateList", PredicateListToJson(this->extended) } @@ -269,6 +283,7 @@ std::shared_ptr CategoryListQuery::DeserializeQuery(const std std::shared_ptr result(new CategoryListQuery()); result->trackField = options.value("trackField", ""); result->filter = options.value("filter", ""); + result->matchType = options.value("matchType", MatchType::Substring); result->outputType = (OutputType)options.value("outputType", OutputType::Regular); PredicateListFromJson(options["regularPredicateList"], result->regular); PredicateListFromJson(options["extendedPredicateList"], result->extended); diff --git a/src/musikcore/library/query/CategoryListQuery.h b/src/musikcore/library/query/CategoryListQuery.h index c59872a29..3cd0bfe02 100755 --- a/src/musikcore/library/query/CategoryListQuery.h +++ b/src/musikcore/library/query/CategoryListQuery.h @@ -52,15 +52,18 @@ namespace musik { namespace core { namespace library { namespace query { using Result = SdkValueList::Shared; CategoryListQuery( + MatchType matchType, const std::string& trackField, const std::string& filter = ""); CategoryListQuery( + MatchType matchType, const std::string& trackField, const category::Predicate predicate, const std::string& filter = ""); CategoryListQuery( + MatchType matchType, const std::string& trackField, const category::PredicateList predicate, const std::string& filter = ""); @@ -100,6 +103,7 @@ namespace musik { namespace core { namespace library { namespace query { std::string trackField; std::string filter; + MatchType matchType; OutputType outputType; category::PredicateList regular, extended; Result result; diff --git a/src/musikcore/library/query/SearchTrackListQuery.cpp b/src/musikcore/library/query/SearchTrackListQuery.cpp index fe5a21f09..592d02c4f 100755 --- a/src/musikcore/library/query/SearchTrackListQuery.cpp +++ b/src/musikcore/library/query/SearchTrackListQuery.cpp @@ -61,9 +61,13 @@ using namespace boost::algorithm; const std::string SearchTrackListQuery::kQueryName = "SearchTrackListQuery"; SearchTrackListQuery::SearchTrackListQuery( - ILibraryPtr library, const std::string& filter, TrackSortType sort) + ILibraryPtr library, + MatchType matchType, + const std::string& filter, + TrackSortType sort) { this->library = library; + this->matchType = matchType; this->sortType = sort; this->filter = filter; @@ -105,11 +109,10 @@ bool SearchTrackListQuery::OnRun(Connection& db) { headers.reset(new std::set()); } + bool useRegex = (matchType == MatchType::Regex); bool hasFilter = (this->filter.size() > 0); - std::string lastAlbum; size_t index = 0; - std::string query; if (hasFilter) { @@ -119,9 +122,11 @@ bool SearchTrackListQuery::OnRun(Connection& db) { "WHERE " " tracks.visible=1 AND " + this->orderByPredicate + - "(tracks.title LIKE ? OR al.name LIKE ? OR ar.name LIKE ? OR gn.name LIKE ?) " + "(tracks.title {{match_type}} ? OR al.name {{match_type}} ? OR ar.name {{match_type}} ? OR gn.name {{match_type}} ?) " " AND tracks.album_id=al.id AND tracks.visual_genre_id=gn.id AND tracks.visual_artist_id=ar.id " "ORDER BY " + this->orderBy + " "; + + ReplaceAll(query, "{{match_type}}", useRegex ? "REGEXP" : "LIKE"); } else { query = @@ -139,11 +144,13 @@ bool SearchTrackListQuery::OnRun(Connection& db) { Statement trackQuery(query.c_str(), db); if (hasFilter) { - std::string filterWithWildcard = "%" + trim_copy(to_lower_copy(filter)) + "%"; - trackQuery.BindText(0, filterWithWildcard); - trackQuery.BindText(1, filterWithWildcard); - trackQuery.BindText(2, filterWithWildcard); - trackQuery.BindText(3, filterWithWildcard); + std::string patternToMatch = useRegex + ? filter : "%" + trim_copy(to_lower_copy(filter)) + "%"; + + trackQuery.BindText(0, patternToMatch); + trackQuery.BindText(1, patternToMatch); + trackQuery.BindText(2, patternToMatch); + trackQuery.BindText(3, patternToMatch); } while (trackQuery.Step() == Row) { @@ -173,7 +180,8 @@ std::string SearchTrackListQuery::SerializeQuery() { { "name", kQueryName }, { "options", { { "filter", filter }, - { "sortType", sortType} + { "matchType", matchType }, + { "sortType", sortType } }} }; return FinalizeSerializedQueryWithLimitAndOffset(output); @@ -194,6 +202,7 @@ std::shared_ptr SearchTrackListQuery::DeserializeQuery(mus auto options = nlohmann::json::parse(data)["options"]; auto result = std::make_shared( library, + options.value("matchType", MatchType::Substring), options["filter"].get(), options["sortType"].get()); result->ExtractLimitAndOffsetFromDeserializedQuery(options); diff --git a/src/musikcore/library/query/SearchTrackListQuery.h b/src/musikcore/library/query/SearchTrackListQuery.h index ebe7c34c0..39c82123c 100755 --- a/src/musikcore/library/query/SearchTrackListQuery.h +++ b/src/musikcore/library/query/SearchTrackListQuery.h @@ -45,6 +45,7 @@ namespace musik { namespace core { namespace library { namespace query { SearchTrackListQuery( musik::core::ILibraryPtr library, + MatchType matchType, const std::string& filter, TrackSortType sort); @@ -70,6 +71,7 @@ namespace musik { namespace core { namespace library { namespace query { private: musik::core::ILibraryPtr library; + MatchType matchType; bool parseHeaders; std::string orderBy; std::string orderByPredicate; diff --git a/src/musikcore/library/query/util/CategoryQueryUtil.h b/src/musikcore/library/query/util/CategoryQueryUtil.h index 5bbaa4e10..e41bd95ba 100644 --- a/src/musikcore/library/query/util/CategoryQueryUtil.h +++ b/src/musikcore/library/query/util/CategoryQueryUtil.h @@ -71,10 +71,10 @@ namespace musik { namespace core { namespace library { namespace query { }; static const std::string REGULAR_PREDICATE = " tracks.{{fk_id}}=? "; - static const std::string REGULAR_FILTER = " AND LOWER({{table}}.name) LIKE ? "; + static const std::string REGULAR_FILTER = " AND LOWER({{table}}.name) {{match_type}} ? "; static const std::string EXTENDED_PREDICATE = " (key=? AND meta_value_id=?) "; - static const std::string EXTENDED_FILTER = " AND LOWER(extended_metadata.value) LIKE ?"; + static const std::string EXTENDED_FILTER = " AND LOWER(extended_metadata.value) {{match_type}} ?"; static const std::string EXTENDED_INNER_JOIN = "INNER JOIN ( " @@ -193,7 +193,7 @@ namespace musik { namespace core { namespace library { namespace query { and other supplementary information. */ static const std::string ALBUM_LIST_FILTER = - " AND (LOWER(album) like ? OR LOWER(album_artist) like ?) "; + " AND (LOWER(album) LIKE ? OR LOWER(album_artist) LIKE ?) "; static const std::string ALBUM_LIST_QUERY = "SELECT DISTINCT " diff --git a/src/musikcube/app/layout/TrackSearchLayout.cpp b/src/musikcube/app/layout/TrackSearchLayout.cpp index d4a882794..72bedaa5e 100755 --- a/src/musikcube/app/layout/TrackSearchLayout.cpp +++ b/src/musikcube/app/layout/TrackSearchLayout.cpp @@ -149,7 +149,11 @@ void TrackSearchLayout::Requery() { const std::string& filter = this->input->GetText(); const TrackSortType sortOrder = getDefaultTrackSort(this->prefs); this->trackList->Requery(std::shared_ptr( - new SearchTrackListQuery(this->library, filter, sortOrder))); + new SearchTrackListQuery( + this->library, + SearchTrackListQuery::MatchType::Substring, + filter, + sortOrder))); } void TrackSearchLayout::PlayFromTop() { diff --git a/src/musikcube/app/overlay/PlayQueueOverlays.cpp b/src/musikcube/app/overlay/PlayQueueOverlays.cpp index 5fecd35d7..675fcb60a 100644 --- a/src/musikcube/app/overlay/PlayQueueOverlays.cpp +++ b/src/musikcube/app/overlay/PlayQueueOverlays.cpp @@ -151,8 +151,8 @@ static void queryPlaylists( ILibraryPtr library, std::function)> callback) { - std::shared_ptr query( - new CategoryListQuery(Playlists::TABLE_NAME, "")); + std::shared_ptr query(new CategoryListQuery( + CategoryListQuery::MatchType::Substring, Playlists::TABLE_NAME, "")); library->Enqueue(query, [callback, query](auto q) { callback(query->GetStatus() == IQuery::Finished diff --git a/src/musikcube/app/window/CategoryListView.cpp b/src/musikcube/app/window/CategoryListView.cpp index 8b0c9817d..ae95ef433 100755 --- a/src/musikcube/app/window/CategoryListView.cpp +++ b/src/musikcube/app/window/CategoryListView.cpp @@ -109,7 +109,8 @@ void CategoryListView::RequeryWithField( this->fieldIdColumn = getFieldIdColumn(fieldName); this->selectAfterQuery = selectAfterQuery; this->filter = filter; - this->activeQuery.reset(new CategoryListQuery(fieldName, filter)); + this->activeQuery.reset(new CategoryListQuery( + CategoryListQuery::MatchType::Substring, fieldName, filter)); this->library->Enqueue(activeQuery); }