Refactored predicate and argument stuff into util module that can be

reused in CategoryTrackListQuery.
This commit is contained in:
casey langen 2018-01-03 19:14:39 -08:00
parent 3659395af5
commit 273212bd0d
7 changed files with 383 additions and 171 deletions

View File

@ -31,6 +31,7 @@ set(CORE_SOURCES
./library/query/local/SavePlaylistQuery.cpp
./library/query/local/SearchTrackListQuery.cpp
./library/query/local/TrackMetadataQuery.cpp
./library/query/local/util/CategoryQueryUtil.cpp
./library/metadata/MetadataMap.cpp
./library/metadata/MetadataMapList.cpp
./library/track/IndexerTrack.cpp

View File

@ -117,6 +117,7 @@
<ClCompile Include="library\query\local\SavePlaylistQuery.cpp" />
<ClCompile Include="library\query\local\SearchTrackListQuery.cpp" />
<ClCompile Include="library\query\local\TrackMetadataQuery.cpp" />
<ClCompile Include="library\query\local\util\CategoryQueryUtil.cpp" />
<ClCompile Include="library\track\IndexerTrack.cpp" />
<ClCompile Include="library\track\LibraryTrack.cpp" />
<ClCompile Include="library\track\Track.cpp" />
@ -176,6 +177,7 @@
<ClInclude Include="library\query\local\TrackListQueryBase.h" />
<ClInclude Include="Library\query\local\LocalQueryBase.h" />
<ClInclude Include="library\query\local\TrackMetadataQuery.h" />
<ClInclude Include="library\query\local\util\CategoryQueryUtil.h" />
<ClInclude Include="library\track\IndexerTrack.h" />
<ClInclude Include="library\track\LibraryTrack.h" />
<ClInclude Include="library\track\Track.h" />

View File

@ -59,6 +59,9 @@
<Filter Include="src\sdk\metadata">
<UniqueIdentifier>{a39a447a-65ad-465c-839d-b96cc4a8e2e9}</UniqueIdentifier>
</Filter>
<Filter Include="src\library\query\local\util">
<UniqueIdentifier>{3c3cc444-39fd-482f-aca9-3e67b98d8fc0}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@ -199,6 +202,9 @@
<ClCompile Include="library\query\local\ReplayGainQuery.cpp">
<Filter>src\library\query\local</Filter>
</ClCompile>
<ClCompile Include="library\query\local\util\CategoryQueryUtil.cpp">
<Filter>src\library\query\local\util</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.hpp">
@ -486,5 +492,8 @@
<ClInclude Include="library\query\local\ReplayGainQuery.h">
<Filter>src\library\query\local</Filter>
</ClInclude>
<ClInclude Include="library\query\local\util\CategoryQueryUtil.h">
<Filter>src\library\query\local\util</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -59,57 +59,6 @@ using namespace musik::core::db::local;
#endif
#endif
static std::map<std::string, std::string> PREDICATE_TO_COLUMN_MAP = {
{ Track::ALBUM, "album_id" },
{ Track::ARTIST, "visual_artist_id" },
{ Track::ALBUM_ARTIST, "album_artist_id" },
{ Track::GENRE, "visual_genre_id" }
};
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 ";
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";
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";
static const std::string UNFILTERED_PLAYLISTS_QUERY =
"SELECT DISTINCT id, name "
"FROM playlists "
@ -121,19 +70,6 @@ static const std::string FILTERED_PLAYLISTS_QUERY =
"WHERE LOWER(name) LIKE LOWER(?) "
"ORDER BY name;";
/* quick and dirty structures used to track bind arguments */
struct Id : public CategoryListQuery::Argument {
int64_t id;
Id(int64_t id) : id(id) { }
virtual void Bind(Statement& stmt, int pos) const { stmt.BindInt64(pos, id); }
};
struct String : public CategoryListQuery::Argument {
std::string str;
String(const std::string& str) : str(str) { }
virtual void Bind(Statement& stmt, int pos) const { stmt.BindText(pos, str.c_str()); }
};
/* data structure that we can return to plugins who need metadata info */
class ValueList : public musik::core::sdk::IValueList {
public:
@ -167,19 +103,19 @@ static void replaceAll(std::string& input, const std::string& find, const std::s
CategoryListQuery::CategoryListQuery(
const std::string& trackField, const std::string& filter)
: CategoryListQuery(trackField, PredicateList(), filter) {
: CategoryListQuery(trackField, category::PredicateList(), filter) {
}
CategoryListQuery::CategoryListQuery(
const std::string& trackField,
const Predicate predicate,
const category::Predicate predicate,
const std::string& filter)
: CategoryListQuery(trackField, PredicateList{ predicate }, filter) {
: CategoryListQuery(trackField, category::PredicateList{ predicate }, filter) {
}
CategoryListQuery::CategoryListQuery(
const std::string& trackField,
const PredicateList predicates,
const category::PredicateList predicates,
const std::string& filter)
: trackField(trackField)
, filter(filter) {
@ -192,21 +128,15 @@ CategoryListQuery::CategoryListQuery(
this->filter = "%" + wild + "%";
}
auto end = REGULAR_PROPERTY_MAP.end();
for (auto p : predicates) {
if (REGULAR_PROPERTY_MAP.find(p.first) != end) {
this->regular.push_back(p);
}
else {
this->extended.push_back(p);
}
}
category::SplitPredicates(predicates, this->regular, this->extended);
auto end = category::REGULAR_PROPERTY_MAP.end();
if (trackField == "playlists") {
this->outputType = Playlist;
}
else if (REGULAR_PROPERTY_MAP.find(trackField) != end) {
else if (category::GetPropertyType(trackField) == category::PropertyType::Regular) {
this->outputType = Regular;
}
else {
@ -215,64 +145,6 @@ CategoryListQuery::CategoryListQuery(
}
CategoryListQuery::~CategoryListQuery() {
}
std::string CategoryListQuery::JoinRegular(
const PredicateList& pred, ArgumentList& args, const std::string& prefix)
{
std::string result;
if (pred.size()) {
for (size_t i = 0; i < pred.size(); i++) {
if (i > 0) { result += " AND "; }
auto p = pred[i];
auto str = REGULAR_PREDICATE;
auto map = REGULAR_PROPERTY_MAP[p.first];
replaceAll(str, "{{fk_id}}", map.second);
result += str;
args.push_back(std::make_shared<Id>(p.second));
}
if (prefix.size()) {
result = prefix + result;
}
}
return result;
}
std::string CategoryListQuery::InnerJoinExtended(
const PredicateList& pred, ArgumentList& args)
{
std::string result;
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()));
}
return result;
}
std::string CategoryListQuery::JoinExtended(
const PredicateList& pred, ArgumentList& args)
{
std::string result;
for (size_t i = 0; i < pred.size(); i++) {
if (i > 0) { result += " OR "; }
result += EXTENDED_PREDICATE;
auto p = pred[i];
args.push_back(std::make_shared<String>(p.first));
args.push_back(std::make_shared<Id>(p.second));
}
return result;
}
void CategoryListQuery::Apply(Statement& stmt, const ArgumentList& args) {
for (size_t i = 0; i < args.size(); i++) {
args[i]->Bind(stmt, (int) i);
}
}
CategoryListQuery::ResultList CategoryListQuery::GetResult() {
@ -310,18 +182,18 @@ void CategoryListQuery::QueryPlaylist(musik::core::db::Connection& db) {
}
void CategoryListQuery::QueryRegular(musik::core::db::Connection &db) {
ArgumentList args;
category::ArgumentList args;
auto prop = REGULAR_PROPERTY_MAP[this->trackField];
std::string query = REGULAR_PROPERTY_QUERY;
auto prop = category::REGULAR_PROPERTY_MAP[this->trackField];
std::string query = category::REGULAR_PROPERTY_QUERY;
std::string extended = InnerJoinExtended(this->extended, args);
std::string regular = JoinRegular(this->regular, args, " AND ");
std::string regularFilter;
if (this->filter.size()) {
regularFilter = REGULAR_FILTER;
regularFilter = category::REGULAR_FILTER;
replaceAll(regularFilter, "{{table}}", prop.first);
args.push_back(std::make_shared<String>(this->filter));
args.push_back(category::StringArgument(this->filter));
}
replaceAll(query, "{{table}}", prop.first);
@ -336,23 +208,23 @@ void CategoryListQuery::QueryRegular(musik::core::db::Connection &db) {
}
void CategoryListQuery::QueryExtended(musik::core::db::Connection &db) {
ArgumentList args;
category::ArgumentList args;
std::string query = EXTENDED_PROPERTY_QUERY;
std::string regular = JoinRegular(this->regular, args, " AND ");
std::string extended = InnerJoinExtended(this->extended, args);
std::string query = category::EXTENDED_PROPERTY_QUERY;
std::string regular = category::JoinRegular(this->regular, args, " AND ");
std::string extended = category::InnerJoinExtended(this->extended, args);
std::string extendedFilter;
if (this->filter.size()) {
extendedFilter = EXTENDED_FILTER;
args.push_back(std::make_shared<String>(this->filter));
extendedFilter = category::EXTENDED_FILTER;
args.push_back(category::StringArgument(this->filter));
}
replaceAll(query, "{{regular_predicates}}", regular);
replaceAll(query, "{{extended_predicates}}", extended);
replaceAll(query, "{{extended_filter}}", extendedFilter);
args.push_back(std::make_shared<String>(this->trackField));
args.push_back(category::StringArgument(this->trackField));
DUMP(query);

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/db/Statement.h>
#include <core/db/Connection.h>
#include <core/sdk/IValueList.h>
@ -72,11 +73,6 @@ namespace musik { namespace core { namespace db { namespace local {
int64_t id;
};
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>>;
typedef std::shared_ptr<std::vector<
std::shared_ptr<Result> > > ResultList;
@ -86,12 +82,12 @@ namespace musik { namespace core { namespace db { namespace local {
CategoryListQuery(
const std::string& trackField,
const Predicate predicate,
const category::Predicate predicate,
const std::string& filter = "");
CategoryListQuery(
const std::string& trackField,
const PredicateList predicate,
const category::PredicateList predicate,
const std::string& filter = "");
virtual ~CategoryListQuery();
@ -114,25 +110,10 @@ namespace musik { namespace core { namespace db { namespace local {
void QueryExtended(musik::core::db::Connection &db);
void ProcessResult(musik::core::db::Statement &stmt);
static std::string JoinRegular(
const PredicateList& pred,
ArgumentList& args,
const std::string& prefix = "");
static std::string InnerJoinExtended(
const PredicateList& pred, ArgumentList& args);
static std::string JoinExtended(
const PredicateList& pred, ArgumentList& args);
static void Apply(
musik::core::db::Statement& stmt,
const ArgumentList& args);
std::string trackField;
std::string filter;
OutputType outputType;
PredicateList regular, extended;
category::PredicateList regular, extended;
ResultList result;
};

View File

@ -0,0 +1,163 @@
//////////////////////////////////////////////////////////////////////////////
//
// 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.
//
//////////////////////////////////////////////////////////////////////////////
#include "pch.hpp"
#include "CategoryQueryUtil.h"
#include <mutex>
#include <map>
using musik::core::db::Statement;
using musik::core::db::Row;
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 {
int64_t id;
Id(int64_t id) : id(id) { }
virtual void Bind(Statement& stmt, int pos) const { stmt.BindInt64(pos, id); }
};
struct String : public Argument {
std::string str;
String(const std::string& str) : str(str) { }
virtual void Bind(Statement& stmt, int pos) const { stmt.BindText(pos, str.c_str()); }
};
std::shared_ptr<Argument> IdArgument(int64_t id) {
return std::make_shared<Id>(id);
}
std::shared_ptr<Argument> StringArgument(const std::string str) {
return std::make_shared<String>(str);
}
PropertyType GetPropertyType(const std::string& property) {
auto found = REGULAR_PROPERTY_MAP.find(property);
auto end = REGULAR_PROPERTY_MAP.end();
return (found == end)
? category::PropertyType::Extended
: category::PropertyType::Regular;
}
void SplitPredicates(
const PredicateList& input,
PredicateList& regular,
PredicateList& extended)
{
auto end = REGULAR_PROPERTY_MAP.end();
for (auto p : input) {
if (REGULAR_PROPERTY_MAP.find(p.first) != end) {
regular.push_back(p);
}
else {
extended.push_back(p);
}
}
}
std::string JoinRegular(
const PredicateList& pred,
ArgumentList& args,
const std::string& prefix)
{
std::string result;
if (pred.size()) {
for (size_t i = 0; i < pred.size(); i++) {
if (i > 0) { result += " AND "; }
auto p = pred[i];
auto str = REGULAR_PREDICATE;
auto map = REGULAR_PROPERTY_MAP[p.first];
replaceAll(str, "{{fk_id}}", map.second);
result += str;
args.push_back(std::make_shared<Id>(p.second));
}
if (prefix.size()) {
result = prefix + result;
}
}
return result;
}
std::string category::InnerJoinExtended(
const PredicateList& pred, ArgumentList& args)
{
std::string result;
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()));
}
return result;
}
std::string JoinExtended(
const PredicateList& pred, ArgumentList& args)
{
std::string result;
for (size_t i = 0; i < pred.size(); i++) {
if (i > 0) { result += " OR "; }
result += EXTENDED_PREDICATE;
auto p = pred[i];
args.push_back(std::make_shared<String>(p.first));
args.push_back(std::make_shared<Id>(p.second));
}
return result;
}
void Apply(Statement& stmt, const ArgumentList& args) {
for (size_t i = 0; i < args.size(); i++) {
args[i]->Bind(stmt, (int)i);
}
}
} } } } }

View File

@ -0,0 +1,184 @@
//////////////////////////////////////////////////////////////////////////////
//
// 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")
// 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";
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 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);
}
} } } }