Updated CategoryTrackListQuery to support predicate lists, just like

CategoryListQuery.
This commit is contained in:
casey langen 2018-01-03 21:02:42 -08:00
parent 273212bd0d
commit 6c354f513b
7 changed files with 143 additions and 153 deletions

View File

@ -34,13 +34,9 @@
#include "pch.hpp"
#include "CategoryListQuery.h"
#include <core/library/LocalLibraryConstants.h>
#include <core/db/Statement.h>
#include <mutex>
#include <map>
using musik::core::db::Statement;
using musik::core::db::Row;
@ -50,15 +46,6 @@ using namespace musik::core::db::local;
#define RESET_RESULT(x) x.reset(new std::vector<std::shared_ptr<Result>>);
#if 1
#ifdef WIN32
#define DUMP(x) OutputDebugStringA(x.c_str()); OutputDebugStringA("\n");
#else
#include <iostream>
#define DUMP(x) std::cout << x << "\n";
#endif
#endif
static const std::string UNFILTERED_PLAYLISTS_QUERY =
"SELECT DISTINCT id, name "
"FROM playlists "
@ -93,14 +80,6 @@ class ValueList : public musik::core::sdk::IValueList {
CategoryListQuery::ResultList results;
};
static void replaceAll(std::string& input, const std::string& find, const std::string& replace) {
size_t pos = input.find(find);
while (pos != std::string::npos) {
input.replace(pos, find.size(), replace);
pos = input.find(find, pos + replace.size());
}
}
CategoryListQuery::CategoryListQuery(
const std::string& trackField, const std::string& filter)
: CategoryListQuery(trackField, category::PredicateList(), filter) {
@ -128,7 +107,6 @@ CategoryListQuery::CategoryListQuery(
this->filter = "%" + wild + "%";
}
category::SplitPredicates(predicates, this->regular, this->extended);
auto end = category::REGULAR_PROPERTY_MAP.end();
@ -184,6 +162,8 @@ void CategoryListQuery::QueryPlaylist(musik::core::db::Connection& db) {
void CategoryListQuery::QueryRegular(musik::core::db::Connection &db) {
category::ArgumentList args;
/* order of operations with args is important! otherwise bind params
will be out of order! */
auto prop = category::REGULAR_PROPERTY_MAP[this->trackField];
std::string query = category::REGULAR_PROPERTY_QUERY;
std::string extended = InnerJoinExtended(this->extended, args);
@ -192,15 +172,15 @@ void CategoryListQuery::QueryRegular(musik::core::db::Connection &db) {
if (this->filter.size()) {
regularFilter = category::REGULAR_FILTER;
replaceAll(regularFilter, "{{table}}", prop.first);
category::ReplaceAll(regularFilter, "{{table}}", prop.first);
args.push_back(category::StringArgument(this->filter));
}
replaceAll(query, "{{table}}", prop.first);
replaceAll(query, "{{fk_id}}", prop.second);
replaceAll(query, "{{extended_predicates}}", extended);
replaceAll(query, "{{regular_predicates}}", regular);
replaceAll(query, "{{regular_filter}}", regularFilter);
category::ReplaceAll(query, "{{table}}", prop.first);
category::ReplaceAll(query, "{{fk_id}}", prop.second);
category::ReplaceAll(query, "{{extended_predicates}}", extended);
category::ReplaceAll(query, "{{regular_predicates}}", regular);
category::ReplaceAll(query, "{{regular_filter}}", regularFilter);
Statement stmt(query.c_str(), db);
Apply(stmt, args);
@ -210,6 +190,8 @@ void CategoryListQuery::QueryRegular(musik::core::db::Connection &db) {
void CategoryListQuery::QueryExtended(musik::core::db::Connection &db) {
category::ArgumentList args;
/* order of operations with args is important! otherwise bind params
will be out of order! */
std::string query = category::EXTENDED_PROPERTY_QUERY;
std::string regular = category::JoinRegular(this->regular, args, " AND ");
std::string extended = category::InnerJoinExtended(this->extended, args);
@ -220,14 +202,12 @@ void CategoryListQuery::QueryExtended(musik::core::db::Connection &db) {
args.push_back(category::StringArgument(this->filter));
}
replaceAll(query, "{{regular_predicates}}", regular);
replaceAll(query, "{{extended_predicates}}", extended);
replaceAll(query, "{{extended_filter}}", extendedFilter);
category::ReplaceAll(query, "{{regular_predicates}}", regular);
category::ReplaceAll(query, "{{extended_predicates}}", extended);
category::ReplaceAll(query, "{{extended_filter}}", extendedFilter);
args.push_back(category::StringArgument(this->trackField));
DUMP(query);
Statement stmt(query.c_str(), db);
Apply(stmt, args);
ProcessResult(stmt);

View File

@ -43,8 +43,6 @@
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string.hpp>
#include <map>
using musik::core::db::Statement;
using musik::core::db::Row;
using musik::core::TrackPtr;
@ -56,34 +54,44 @@ using namespace musik::core::db::local;
using namespace musik::core::library::constants;
using namespace boost::algorithm;
static std::map<std::string, std::string> FIELD_TO_FOREIGN_KEY = {
{ Track::ALBUM, Track::ALBUM_ID },
{ Track::ARTIST, Track::ARTIST_ID },
{ Track::GENRE, Track::GENRE_ID },
{ Track::ALBUM_ARTIST, Track::ALBUM_ARTIST_ID },
{ Playlists::TABLE_NAME, Playlists::TABLE_NAME }
};
CategoryTrackListQuery::CategoryTrackListQuery(
musik::core::ILibraryPtr library,
const std::string& column,
int64_t id,
const std::string& filter)
: CategoryTrackListQuery(library, { column, id }, filter)
{
}
CategoryTrackListQuery::CategoryTrackListQuery(
ILibraryPtr library, const std::string& column, int64_t id, const std::string& filter)
ILibraryPtr library,
const category::Predicate predicate,
const std::string& filter)
: CategoryTrackListQuery(library, category::PredicateList { predicate }, filter)
{
}
CategoryTrackListQuery::CategoryTrackListQuery(
ILibraryPtr library,
const category::PredicateList predicates,
const std::string& filter)
{
this->library = library;
this->id = id;
this->result.reset(new musik::core::TrackList(library));
this->headers.reset(new std::set<size_t>());
this->hash = 0;
this->hash = category::Hash(predicates);
category::SplitPredicates(predicates, this->regular, this->extended);
if (filter.size()) {
this->filter = "%" + trim_copy(to_lower_copy(filter)) + "%";
}
if (FIELD_TO_FOREIGN_KEY.find(column) != FIELD_TO_FOREIGN_KEY.end()) {
this->type = (column == Playlists::TABLE_NAME) ? Playlist : Regular;
this->column = FIELD_TO_FOREIGN_KEY[column]; /* optimized query */
if (predicates.size() == 1 && predicates[0].first == Playlists::TABLE_NAME) {
this->type = Playlist;
}
else {
this->type = Extended;
this->column = column; /* generalized query */
this->type = Regular;
}
}
@ -100,100 +108,44 @@ CategoryTrackListQuery::Headers CategoryTrackListQuery::GetHeaders() {
}
size_t CategoryTrackListQuery::GetQueryHash() {
if (this->hash == 0) {
std::string parts = boost::str(
boost::format("%s-%s") % this->column % this->id);
this->hash = std::hash<std::string>()(parts);
}
return this->hash;
}
void CategoryTrackListQuery::PlaylistQuery(musik::core::db::Connection &db) {
/* playlists are a special case. we already have a query for this, so
delegate to that. */
GetPlaylistQuery query(this->library, this->id);
GetPlaylistQuery query(this->library, this->extended[0].second);
query.Run(db);
this->result = query.GetResult();
}
void CategoryTrackListQuery::RegularQuery(musik::core::db::Connection &db) {
/* these are the most common queries in the app, and are more optimized
than extended metadata queries. */
std::string query =
"SELECT DISTINCT t.id, al.name "
"FROM tracks t, albums al, artists ar, genres gn "
"WHERE t.visible=1 AND t.%s=? AND t.album_id=al.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id ";
category::ArgumentList args;
/* order of operations with args is important! otherwise bind params
will be out of order! */
std::string query = category::CATEGORY_TRACKLIST_QUERY;
std::string extended = InnerJoinExtended(this->extended, args);
std::string regular = JoinRegular(this->regular, args, " AND ");
std::string trackFilter;
std::string limitAndOffset = this->GetLimitAndOffset();
if (this->filter.size()) {
query += " AND (t.title LIKE ? OR al.name LIKE ? OR ar.name LIKE ? OR gn.name LIKE ?) ";
trackFilter = " AND " + category::CATEGORY_TRACKLIST_FILTER;
args.push_back(category::StringArgument(this->filter));
args.push_back(category::StringArgument(this->filter));
args.push_back(category::StringArgument(this->filter));
args.push_back(category::StringArgument(this->filter));
}
query += "ORDER BY al.name, disc, track, ar.name %s";
query = boost::str(boost::format(query) % this->column % this->GetLimitAndOffset());
category::ReplaceAll(query, "{{extended_predicates}}", extended);
category::ReplaceAll(query, "{{regular_predicates}}", regular);
category::ReplaceAll(query, "{{tracklist_filter}}", trackFilter);
category::ReplaceAll(query, "{{limit_and_offset}}", limitAndOffset);
Statement trackQuery(query.c_str(), db);
if (this->filter.size()) {
trackQuery.BindInt64(0, this->id);
trackQuery.BindText(1, this->filter);
trackQuery.BindText(2, this->filter);
trackQuery.BindText(3, this->filter);
trackQuery.BindText(4, this->filter);
}
else {
trackQuery.BindInt64(0, this->id);
}
this->ProcessResult(trackQuery);
}
void CategoryTrackListQuery::ExtendedQuery(musik::core::db::Connection &db) {
int64_t keyId = 0;
{
Statement keyQuery("SELECT DISTINCT id FROM meta_keys WHERE LOWER(name)=LOWER(?)", db);
keyQuery.BindText(0, this->column);
if (keyQuery.Step() == db::Row) {
keyId = keyQuery.ColumnInt64(0);
}
}
if (keyId > 0) {
/* the core library allows for storage of arbitrary metadata in the form
of key/value pairs linked to tracks. they are slower and require additional
joins and subqueries, but are fully supported here. */
std::string query =
"SELECT DISTINCT t.id, al.name "
"FROM tracks t, albums al, artists ar, genres gn, track_meta tm, meta_keys mk, meta_values mv "
"WHERE t.visible=1 "
" AND t.album_id=al.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id "
" AND t.id=tm.track_id AND tm.meta_value_id=mv.id AND mv.meta_key_id=mk.id "
" AND mk.id=? AND mv.id=? ";
if (this->filter.size()) {
query += " AND (t.title LIKE ? OR al.name LIKE ? OR ar.name LIKE ? OR gn.name LIKE ?) ";
}
query += "ORDER BY al.name, disc, track, ar.name %s";
query = boost::str(boost::format(query) % this->GetLimitAndOffset());
Statement trackQuery(query.c_str(), db);
int bindOffset = 0;
trackQuery.BindInt64(bindOffset++, keyId);
trackQuery.BindInt64(bindOffset++, this->id);
if (this->filter.size()) {
trackQuery.BindText(bindOffset++, this->filter);
trackQuery.BindText(bindOffset++, this->filter);
trackQuery.BindText(bindOffset++, this->filter);
trackQuery.BindText(bindOffset++, this->filter);
}
this->ProcessResult(trackQuery);
}
Statement stmt(query.c_str(), db);
category::Apply(stmt, args);
this->ProcessResult(stmt);
}
void CategoryTrackListQuery::ProcessResult(musik::core::db::Statement& trackQuery) {
@ -223,7 +175,6 @@ bool CategoryTrackListQuery::OnRun(Connection& db) {
switch (this->type) {
case Playlist: this->PlaylistQuery(db); break;
case Regular: this->RegularQuery(db); break;
case Extended: this->ExtendedQuery(db); break;
}
return true;

View File

@ -37,6 +37,7 @@
#include <core/db/Connection.h>
#include <core/library/track/Track.h>
#include <core/library/query/local/LocalQueryBase.h>
#include <core/library/query/local/util/CategoryQueryUtil.h>
#include <core/db/Statement.h>
#include "TrackListQueryBase.h"
@ -51,6 +52,16 @@ namespace musik { namespace core { namespace db { namespace local {
int64_t id,
const std::string& filter = "");
CategoryTrackListQuery(
musik::core::ILibraryPtr library,
const category::Predicate predicate,
const std::string& filter = "");
CategoryTrackListQuery(
musik::core::ILibraryPtr library,
const category::PredicateList predicates,
const std::string& filter = "");
virtual ~CategoryTrackListQuery();
virtual std::string Name() { return "CategoryTrackListQuery"; }
@ -63,19 +74,17 @@ namespace musik { namespace core { namespace db { namespace local {
virtual bool OnRun(musik::core::db::Connection &db);
private:
enum Type { Playlist, Regular, Extended };
enum Type { Playlist, Regular };
void PlaylistQuery(musik::core::db::Connection &db);
void RegularQuery(musik::core::db::Connection &db);
void ExtendedQuery(musik::core::db::Connection &db);
void ProcessResult(musik::core::db::Statement& stmt);
musik::core::ILibraryPtr library;
Result result;
Headers headers;
std::string column;
Type type;
int64_t id;
category::PredicateList regular, extended;
size_t hash;
std::string filter;
};

View File

@ -115,7 +115,7 @@ std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Replace(
std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Rename(
musik::core::ILibraryPtr library,
const int64_t playlistId,
const int64_t playlistId,
const std::string& playlistName)
{
return std::shared_ptr<SavePlaylistQuery>(

View File

@ -45,14 +45,6 @@ using namespace musik::core::db;
using namespace musik::core::library::constants;
using namespace musik::core::db::local;
static void replaceAll(std::string& input, const std::string& find, const std::string& replace) {
size_t pos = input.find(find);
while (pos != std::string::npos) {
input.replace(pos, find.size(), replace);
pos = input.find(find, pos + replace.size());
}
}
namespace musik { namespace core { namespace db { namespace local { namespace category {
struct Id : public Argument {
@ -75,6 +67,18 @@ namespace musik { namespace core { namespace db { namespace local { namespace ca
return std::make_shared<String>(str);
}
void ReplaceAll(
std::string& input,
const std::string& find,
const std::string& replace)
{
size_t pos = input.find(find);
while (pos != std::string::npos) {
input.replace(pos, find.size(), replace);
pos = input.find(find, pos + replace.size());
}
}
PropertyType GetPropertyType(const std::string& property) {
auto found = REGULAR_PROPERTY_MAP.find(property);
auto end = REGULAR_PROPERTY_MAP.end();
@ -84,6 +88,14 @@ namespace musik { namespace core { namespace db { namespace local { namespace ca
: category::PropertyType::Regular;
}
size_t Hash(const PredicateList& input) {
std::string key = "";
for (auto p : input) {
key += p.first + std::to_string(p.second);
}
return std::hash<std::string>()(key);
}
void SplitPredicates(
const PredicateList& input,
PredicateList& regular,
@ -113,7 +125,7 @@ namespace musik { namespace core { namespace db { namespace local { namespace ca
auto p = pred[i];
auto str = REGULAR_PREDICATE;
auto map = REGULAR_PROPERTY_MAP[p.first];
replaceAll(str, "{{fk_id}}", map.second);
ReplaceAll(str, "{{fk_id}}", map.second);
result += str;
args.push_back(std::make_shared<Id>(p.second));
}
@ -133,8 +145,8 @@ namespace musik { namespace core { namespace db { namespace local { namespace ca
std::string joined = JoinExtended(pred, args);
if (joined.size()) {
result = EXTENDED_INNER_JOIN;
replaceAll(result, "{{extended_predicates}}", joined);
replaceAll(result, "{{extended_predicate_count}}", std::to_string(pred.size()));
ReplaceAll(result, "{{extended_predicates}}", joined);
ReplaceAll(result, "{{extended_predicate_count}}", std::to_string(pred.size()));
}
return result;

View File

@ -102,8 +102,8 @@ namespace musik { namespace core { namespace db { namespace local {
// HAVING COUNT(track_id) = 2
// ) AS md ON tracks.id = md.track_id
// WHERE
// albums.id = tracks.album_id AND
// tracks.visible = 1;
// albums.id = tracks.album_id AND
// tracks.visible = 1;
static const std::string REGULAR_PROPERTY_QUERY =
"SELECT DISTINCT {{table}}.id, {{table}}.name "
@ -130,12 +130,13 @@ namespace musik { namespace core { namespace db { namespace local {
// SELECT id AS track_id
// FROM extended_metadata
// WHERE
// (key = "composer" AND value = "J. Cantrell")
// (key = "composer" AND value = "J. Cantrell") OR
// ...
// GROUP BY track_id
// HAVING COUNT(track_id) = 1
// ) AS md ON extended_metadata.id = md.track_id
// WHERE
// extended_metadata.key = "year";
// extended_metadata.key = "year";
static const std::string EXTENDED_PROPERTY_QUERY =
"SELECT DISTINCT meta_value_id, value "
@ -150,6 +151,38 @@ namespace musik { namespace core { namespace db { namespace local {
"{{extended_filter}} "
"ORDER BY extended_metadata.value ASC";
/* used to select all tracks that match a specified set of predicates. both
regular and extended predicates are supported. in essense: */
// SELECT DISTINCT tracks.*
// FROM tracks
// INNER JOIN(
// SELECT id AS track_id
// FROM extended_metadata
// WHERE
// (key = "year" AND value = "1995") OR
// (key = "composer" AND value = "J. Cantrell")
// GROUP BY track_id
// HAVING COUNT(track_id) = 2
// ) AS md ON tracks.id = md.track_id;
static const std::string CATEGORY_TRACKLIST_FILTER =
" AND (tracks.title LIKE ? OR al.name LIKE ? OR ar.name LIKE ? OR gn.name LIKE ?) ";
static const std::string CATEGORY_TRACKLIST_QUERY =
"SELECT DISTINCT tracks.id, al.name "
"FROM tracks, albums al, artists ar, genres gn "
"{{extended_predicates}} "
"WHERE "
" tracks.visible=1 AND "
" tracks.album_id=al.id AND "
" tracks.visual_genre_id=gn.id AND "
" tracks.visual_artist_id=ar.id "
" {{regular_predicates}} "
" {{tracklist_filter}} "
"ORDER BY al.name, disc, track, ar.name "
"{{limit_and_offset}} ";
using Predicate = std::pair<std::string, int64_t>;
using PredicateList = std::vector<Predicate>;
struct Argument { virtual void Bind(Statement& stmt, int pos) const = 0; };
@ -160,6 +193,13 @@ namespace musik { namespace core { namespace db { namespace local {
extern std::shared_ptr<Argument> IdArgument(int64_t);
extern std::shared_ptr<Argument> StringArgument(const std::string);
extern size_t Hash(const PredicateList& input);
extern void ReplaceAll(
std::string& input,
const std::string& find,
const std::string& replace);
extern void SplitPredicates(
const PredicateList& input,
PredicateList& regular,

View File

@ -211,11 +211,9 @@ void BrowseLayout::RequeryTrackList(ListWindow *view) {
if (view == this->categoryList.get()) {
int64_t selectedId = this->categoryList->GetSelectedId();
if (selectedId != -1) {
auto column = this->categoryList->GetFieldName();
this->trackList->Requery(std::shared_ptr<TrackListQueryBase>(
new CategoryTrackListQuery(
this->library,
this->categoryList->GetFieldName(),
selectedId)));
new CategoryTrackListQuery(this->library, column, selectedId)));
}
else {
this->trackList->Clear();