Updated AlbumListQuery to support extended property predicates.

This commit is contained in:
casey langen 2018-01-03 23:34:49 -08:00
parent 14ba4ea1bb
commit 03e134aab9
4 changed files with 87 additions and 300 deletions

View File

@ -1,224 +0,0 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2007-2017 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <core/library/LocalLibraryConstants.h>
#include <core/db/Statement.h>
#include <core/db/Connection.h>
#include <memory>
namespace musik { namespace core { namespace db { namespace local {
namespace category {
namespace constants = musik::core::library::constants;
/* there are two types of track resources in our app: Regular, and Extended. "Regular"
properties are well-defined and optimized, and include album, artist, album artist,
and genre. they are fast to retrieve and easy to query. however, we also allow for
plugins to index arbitrary track metadata in a completely denormalized key/value
store. these resource types are called "Extended" properties. */
enum class PropertyType: int { Regular, Extended };
/* property name to foreign key id */
static std::map<std::string, std::string> PREDICATE_TO_COLUMN_MAP = {
{ constants::Track::ALBUM, "album_id" },
{ constants::Track::ARTIST, "visual_artist_id" },
{ constants::Track::ALBUM_ARTIST, "album_artist_id" },
{ constants::Track::GENRE, "visual_genre_id" }
};
/* resource type to a pair { <table_name>, <track_table_fk_name> } */
static std::map<std::string, std::pair<std::string, std::string>> REGULAR_PROPERTY_MAP = {
{ "album",{ "albums", "album_id" } },
{ "artist",{ "artists", "visual_artist_id" } },
{ "album_artist",{ "artists", "album_artist_id" } },
{ "genre",{ "genres", "visual_genre_id" } }
};
static const std::string REGULAR_PREDICATE = " tracks.{{fk_id}}=? ";
static const std::string REGULAR_FILTER = " AND LOWER({{table}}.name) LIKE ? ";
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_INNER_JOIN =
"INNER JOIN ( "
" SELECT id AS track_id "
" FROM extended_metadata "
" WHERE {{extended_predicates}} "
" GROUP BY track_id "
" HAVING COUNT(track_id)={{extended_predicate_count}} "
") AS md ON tracks.id=md.track_id ";
/* REGULAR_PROPERTY_QUERY is used to return a list of property values and their
respective IDs for regular properties, that is, albums, artists, album artists,
and genres. these resource types are the most common, and are optimized for display.
the query may also be predicated to select all resources that are related to other
resources, including extended resources. the full requerly propety looks something
like the following example where we query all albums made in "1995" by "J. Cantrell": */
// SELECT DISTINCT albums.id, albums.name
// FROM albums, 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
// WHERE
// albums.id = tracks.album_id AND
// tracks.visible = 1;
static const std::string REGULAR_PROPERTY_QUERY =
"SELECT DISTINCT {{table}}.id, {{table}}.name "
"FROM {{table}}, tracks "
"{{extended_predicates}} "
"WHERE {{table}}.id=tracks.{{fk_id}} AND tracks.visible=1 "
"{{regular_predicates}} "
"{{regular_filter}} "
"ORDER BY {{table}}.sort_order";
/* REGULAR_PROPERTY_QUERY is similar to REGULAR_PROPERTY_QUERY, but is used to
retrieve non-standard metadata fields. it's slower, has (potentially) more joins,
and is generally more difficult to use. here's an example where we select all
"years" for a particular artist "595" where the composer is "J. Cantrell": */
// SELECT DISTINCT meta_value_id, value
// FROM extended_metadata
// INNER JOIN(
// SELECT id AS track_id
// FROM tracks
// WHERE visual_artist_id = 595
// ) AS reg ON extended_metadata.id = reg.track_id
// INNER JOIN(
// SELECT id AS track_id
// FROM extended_metadata
// WHERE
// (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";
static const std::string EXTENDED_PROPERTY_QUERY =
"SELECT DISTINCT meta_value_id, value "
"FROM extended_metadata "
"INNER JOIN ( "
" SELECT id AS track_id "
" FROM tracks "
" WHERE tracks.visible=1 {{regular_predicates}} "
") AS reg on extended_metadata.id=reg.track_id "
"{{extended_predicates}} "
"WHERE extended_metadata.key=? "
"{{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; };
using ArgumentList = std::vector<std::shared_ptr<Argument>>;
extern PropertyType GetPropertyType(const std::string& key);
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,
PredicateList& extended);
extern std::string JoinRegular(
const PredicateList& pred,
ArgumentList& args,
const std::string& prefix = "");
extern std::string InnerJoinExtended(
const PredicateList& pred, ArgumentList& args);
extern std::string JoinExtended(
const PredicateList& pred, ArgumentList& args);
extern void Apply(
musik::core::db::Statement& stmt,
const ArgumentList& args);
}
} } } }

View File

@ -38,64 +38,46 @@
#include <core/library/LocalLibraryConstants.h>
#include <core/db/Statement.h>
using musik::core::db::Statement;
using musik::core::db::Row;
using namespace musik::core;
using namespace musik::core::db;
using namespace musik::core::db::local;
using namespace musik::core::library::constants;
using namespace musik::core::sdk;
static std::map<std::string, std::string> FIELD_TO_FOREIGN_KEY = {
std::make_pair(Track::ALBUM, Track::ALBUM_ID),
std::make_pair(Track::ARTIST, Track::ARTIST_ID),
std::make_pair(Track::GENRE, Track::GENRE_ID),
std::make_pair(Track::ALBUM_ARTIST, Track::ALBUM_ARTIST_ID)
};
#define RESET_RESULT(x) x.reset(new MetadataMapList())
static const std::string COLUMNS =
"albums.id, "
"albums.name as album, "
"tracks.album_artist_id, "
"artists.name as album_artist, "
"albums.thumbnail_id ";
static const std::string TABLES =
"albums, tracks, artists ";
static const std::string VISIBLE_PREDICATE =
" tracks.visible == 1 AND ";
static const std::string FILTER_PREDICATE =
" (LOWER(album) like ? OR LOWER(album_artist) like ?) AND ";
static const std::string CATEGORY_PREDICATE =
" tracks.%s=? AND ";
static const std::string GENERAL_PREDICATE =
"albums.id = tracks.album_id AND "
"artists.id = tracks.album_artist_id ";
static const std::string ORDER =
"albums.name asc ";
AlbumListQuery::AlbumListQuery(const std::string& filter)
: filter(filter)
, fieldIdValue(-1) {
RESET_RESULT(result);
: AlbumListQuery(category::PredicateList(), filter)
{
}
AlbumListQuery::AlbumListQuery(
const std::string& fieldIdName,
int64_t fieldIdValue,
const std::string& filter)
: filter(filter)
, fieldIdValue(fieldIdValue) {
this->fieldIdName = FIELD_TO_FOREIGN_KEY[fieldIdName];
: AlbumListQuery(category::Predicate{ fieldIdName, fieldIdValue }, filter)
{
}
AlbumListQuery::AlbumListQuery(
const category::Predicate predicate,
const std::string& filter)
: AlbumListQuery(category::PredicateList { predicate }, filter)
{
}
AlbumListQuery::AlbumListQuery(
const category::PredicateList predicates,
const std::string& filter)
{
RESET_RESULT(result);
if (filter.size()) {
std::string wild = filter;
std::transform(wild.begin(), wild.end(), wild.begin(), tolower);
this->filter = "%" + wild + "%";
}
category::SplitPredicates(predicates, this->regular, this->extended);
}
AlbumListQuery::~AlbumListQuery() {
@ -113,35 +95,27 @@ musik::core::sdk::IMapList* AlbumListQuery::GetSdkResult() {
bool AlbumListQuery::OnRun(Connection& db) {
RESET_RESULT(result);
bool filtered = this->filter.size() > 0;
bool category = fieldIdName.size() && fieldIdValue != -1;
category::ArgumentList args;
std::string query = "SELECT DISTINCT " + COLUMNS + " FROM " + TABLES + " WHERE ";
query += VISIBLE_PREDICATE;
query += filtered ? FILTER_PREDICATE : "";
/* order of operations with args is important! otherwise bind params
will be out of order! */
std::string query = category::ALBUM_LIST_QUERY;
std::string extended = InnerJoinExtended(this->extended, args);
std::string regular = JoinRegular(this->regular, args, " AND ");
std::string albumFilter;
if (category) {
query += boost::str(boost::format(CATEGORY_PREDICATE) % fieldIdName);
if (this->filter.size()) {
albumFilter = category::ALBUM_LIST_FILTER;
args.push_back(category::StringArgument(this->filter));
args.push_back(category::StringArgument(this->filter));
}
query += GENERAL_PREDICATE + " ORDER BY " + ORDER + ";";
category::ReplaceAll(query, "{{extended_predicates}}", extended);
category::ReplaceAll(query, "{{regular_predicates}}", regular);
category::ReplaceAll(query, "{{album_list_filter}}", albumFilter);
Statement stmt(query.c_str(), db);
int bindIndex = 0;
if (filtered) {
/* transform "FilteR" => "%filter%" */
std::string wild = this->filter;
std::transform(wild.begin(), wild.end(), wild.begin(), tolower);
wild = "%" + wild + "%";
stmt.BindText(bindIndex++, wild);
stmt.BindText(bindIndex++, wild);
}
if (category) {
stmt.BindInt64(bindIndex, this->fieldIdValue);
}
Apply(stmt, args);
while (stmt.Step() == Row) {
std::shared_ptr<MetadataMap> row(new MetadataMap(

View File

@ -35,6 +35,7 @@
#pragma once
#include <core/library/query/local/LocalQueryBase.h>
#include <core/library/query/local/util/CategoryQueryUtil.h>
#include <core/library/metadata/MetadataMapList.h>
#include <core/db/Connection.h>
@ -42,12 +43,20 @@ namespace musik { namespace core { namespace db { namespace local {
class AlbumListQuery : public musik::core::db::LocalQueryBase {
public:
AlbumListQuery(
const std::string& filter = "");
AlbumListQuery(
const std::string& fieldIdName,
int64_t fieldIdValue,
const std::string& filter = "");
AlbumListQuery(
const category::Predicate predicate,
const std::string& filter = "");
AlbumListQuery(
const category::PredicateList predicates,
const std::string& filter = "");
virtual ~AlbumListQuery();
@ -61,8 +70,7 @@ namespace musik { namespace core { namespace db { namespace local {
virtual bool OnRun(musik::core::db::Connection &db);
std::string filter;
std::string fieldIdName;
int64_t fieldIdValue;
category::PredicateList regular, extended;
musik::core::MetadataMapListPtr result;
};

View File

@ -147,8 +147,9 @@ namespace musik { namespace core { namespace db { namespace local {
" WHERE tracks.visible=1 {{regular_predicates}} "
") AS reg on extended_metadata.id=reg.track_id "
"{{extended_predicates}} "
"WHERE extended_metadata.key=? "
"{{extended_filter}} "
"WHERE "
" extended_metadata.key=? "
" {{extended_filter}} "
"ORDER BY extended_metadata.value ASC";
/* used to select all tracks that match a specified set of predicates. both
@ -174,20 +175,48 @@ namespace musik { namespace core { namespace db { namespace local {
"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}} "
" 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}} ";
/* ALBUM_LIST_QUERY is like a specialized REGULAR_PROPERTY_QUERY used by
LocalSimpleDataProvider to return album resources with thumbnail, artist,
and other supplementary information. */
static const std::string ALBUM_LIST_FILTER =
" AND (LOWER(album) like ? OR LOWER(album_artist) like ?) ";
static const std::string ALBUM_LIST_QUERY =
"SELECT DISTINCT "
" albums.id, "
" albums.name as album, "
" tracks.album_artist_id, "
" artists.name as album_artist, "
" albums.thumbnail_id "
"FROM albums, tracks, artists "
"{{extended_predicates}} "
"WHERE "
" albums.id = tracks.album_id AND "
" artists.id = tracks.album_artist_id AND "
" tracks.visible=1 "
" {{regular_predicates}} "
" {{album_list_filter}} "
"ORDER BY albums.name ASC ";
/* data types */
using Predicate = std::pair<std::string, int64_t>;
using PredicateList = std::vector<Predicate>;
struct Argument { virtual void Bind(Statement& stmt, int pos) const = 0; };
using ArgumentList = std::vector<std::shared_ptr<Argument>>;
/* functions */
extern PropertyType GetPropertyType(const std::string& key);
extern std::shared_ptr<Argument> IdArgument(int64_t);