This commit adds the sort and window parameter to "sticker find"

The three new compare operators "eq", "gt" and "lt" are casting the values to int.

Sort supports:
- uri: sort by uri
- value: sort by value as string
- value_int: casts value to int

Closes #1894
This commit is contained in:
jcorporation 2024-03-07 20:52:11 +01:00
parent 1efb9d41db
commit 45553c5f61
8 changed files with 187 additions and 42 deletions

1
NEWS
View File

@ -13,6 +13,7 @@ ver 0.24 (not yet released)
- new commands "stickernames" and "playlistlength" - new commands "stickernames" and "playlistlength"
- new "search"/"find" filter "added-since" - new "search"/"find" filter "added-since"
- allow range in listplaylist and listplaylistinfo - allow range in listplaylist and listplaylistinfo
- "sticker find" supports sort and window parameter and new sticker compare operators "eq", "lt" and "gt"
* database * database
- attribute "added" shows when each song was added to the database - attribute "added" shows when each song was added to the database
- proxy: require MPD 0.21 or later - proxy: require MPD 0.21 or later

View File

@ -1486,19 +1486,21 @@ the database for songs).
.. _command_sticker_find: .. _command_sticker_find:
:command:`sticker find {TYPE} {URI} {NAME}` :command:`sticker find {TYPE} {URI} {NAME} [sort {SORTTYPE}] [window {START:END}]`
Searches the sticker database for stickers with the Searches the sticker database for stickers with the
specified name, below the specified directory (URI). specified name, below the specified directory (URI).
For each matching song, it prints the URI and that one For each matching song, it prints the URI and that one
sticker's value. sticker's value.
``sort`` sorts the result by "``uri``","``value`` or "``value_int``" (casts the sticker value to an integer). [#since_0_24]_
.. _command_sticker_find_value: .. _command_sticker_find_value:
:command:`sticker find {TYPE} {URI} {NAME} = {VALUE}` :command:`sticker find {TYPE} {URI} {NAME} = {VALUE} [sort {SORTTYPE}] [window {START:END}]`
Searches for stickers with the given value. Searches for stickers with the given value.
Other supported operators are: Other supported operators are:
"``<``", "``>``" "``<``", "``>``" for strings and "``eq``", "``lt``", "``gt``" to cast the value to an integer.
Examples: Examples:

View File

@ -78,7 +78,8 @@ public:
return CommandResult::OK; return CommandResult::OK;
} }
virtual CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value) { virtual CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window) {
auto data = CallbackContext{ auto data = CallbackContext{
.name = name, .name = name,
.sticker_type = sticker_type, .sticker_type = sticker_type,
@ -97,6 +98,7 @@ public:
uri, uri,
name, name,
op, value, op, value,
sort, descending, window,
callback, &data); callback, &data);
return CommandResult::OK; return CommandResult::OK;
@ -172,7 +174,8 @@ public:
database.ReturnSong(song); database.ReturnSong(song);
} }
CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value) override { CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window) override {
struct sticker_song_find_data data = { struct sticker_song_find_data data = {
response, response,
name, name,
@ -180,6 +183,7 @@ public:
sticker_song_find(sticker_database, database, uri, data.name, sticker_song_find(sticker_database, database, uri, data.name,
op, value, op, value,
sort, descending, window,
sticker_song_find_print_cb, &data); sticker_song_find_print_cb, &data);
return CommandResult::OK; return CommandResult::OK;
@ -386,7 +390,37 @@ handle_sticker(Client &client, Request args, Response &r)
return handler->Delete(uri, sticker_name); return handler->Delete(uri, sticker_name);
/* find */ /* find */
if ((args.size() == 4 || args.size() == 6) && StringIsEqual(cmd, "find")) { if (args.size() >= 4 && StringIsEqual(cmd, "find")) {
RangeArg window = RangeArg::All();
if (args.size() >= 6 && StringIsEqual(args[args.size() - 2], "window")) {
window = args.ParseRange(args.size() - 1);
args.pop_back();
args.pop_back();
}
auto sort = "";
bool descending = false;
if (args.size() >= 6 && StringIsEqual(args[args.size() - 2], "sort")) {
const char *s = args.back();
if (*s == '-') {
descending = true;
++s;
}
if (StringIsEqual(s, "uri") ||
StringIsEqual(s, "value") ||
StringIsEqual(s, "value_int")
) {
sort = s;
}
else {
r.FmtError(ACK_ERROR_ARG, "Unknown sort tag \"{}\"", s);
return CommandResult::ERROR;
}
args.pop_back();
args.pop_back();
}
bool has_op = args.size() > 4; bool has_op = args.size() > 4;
auto value = has_op ? args[5] : nullptr; auto value = has_op ? args[5] : nullptr;
StickerOperator op = StickerOperator::EXISTS; StickerOperator op = StickerOperator::EXISTS;
@ -399,12 +433,18 @@ handle_sticker(Client &client, Request args, Response &r)
op = StickerOperator::LESS_THAN; op = StickerOperator::LESS_THAN;
else if (StringIsEqual(op_s, ">")) else if (StringIsEqual(op_s, ">"))
op = StickerOperator::GREATER_THAN; op = StickerOperator::GREATER_THAN;
else if (StringIsEqual(op_s, "eq"))
op = StickerOperator::EQUALS_INT;
else if (StringIsEqual(op_s, "lt"))
op = StickerOperator::LESS_THAN_INT;
else if (StringIsEqual(op_s, "gt"))
op = StickerOperator::GREATER_THAN_INT;
else { else {
r.FmtError(ACK_ERROR_ARG, "bad operator \"{}\"", op_s); r.FmtError(ACK_ERROR_ARG, "bad operator \"{}\"", op_s);
return CommandResult::ERROR; return CommandResult::ERROR;
} }
} }
return handler->Find(uri, sticker_name, op, value); return handler->Find(uri, sticker_name, op, value, sort, descending, window);
} }
r.Error(ACK_ERROR_ARG, "bad request"); r.Error(ACK_ERROR_ARG, "bad request");

View File

@ -10,6 +10,7 @@
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include <fmt/format.h>
#include <cassert> #include <cassert>
#include <iterator> #include <iterator>
#include <array> #include <array>
@ -17,6 +18,19 @@
using namespace Sqlite; using namespace Sqlite;
enum sticker_sql_find {
STICKER_SQL_FIND,
STICKER_SQL_FIND_VALUE,
STICKER_SQL_FIND_LT,
STICKER_SQL_FIND_GT,
STICKER_SQL_FIND_EQ_INT,
STICKER_SQL_FIND_LT_INT,
STICKER_SQL_FIND_GT_INT,
STICKER_SQL_FIND_COUNT
};
enum sticker_sql { enum sticker_sql {
STICKER_SQL_GET, STICKER_SQL_GET,
STICKER_SQL_LIST, STICKER_SQL_LIST,
@ -24,10 +38,6 @@ enum sticker_sql {
STICKER_SQL_INSERT, STICKER_SQL_INSERT,
STICKER_SQL_DELETE, STICKER_SQL_DELETE,
STICKER_SQL_DELETE_VALUE, STICKER_SQL_DELETE_VALUE,
STICKER_SQL_FIND,
STICKER_SQL_FIND_VALUE,
STICKER_SQL_FIND_LT,
STICKER_SQL_FIND_GT,
STICKER_SQL_DISTINCT_TYPE_URI, STICKER_SQL_DISTINCT_TYPE_URI,
STICKER_SQL_TRANSACTION_BEGIN, STICKER_SQL_TRANSACTION_BEGIN,
STICKER_SQL_TRANSACTION_COMMIT, STICKER_SQL_TRANSACTION_COMMIT,
@ -37,6 +47,29 @@ enum sticker_sql {
STICKER_SQL_COUNT STICKER_SQL_COUNT
}; };
static constexpr auto sticker_sql_find = std::array {
//[STICKER_SQL_FIND] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?",
//[STICKER_SQL_FIND_VALUE] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value=?",
//[STICKER_SQL_FIND_LT] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value<?",
//[STICKER_SQL_FIND_GT] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value>?",
//[STICKER_SQL_FIND_EQ_INT] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND CAST(value AS INT)=?",
//[STICKER_SQL_FIND_LT_INT] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND CAST(value AS INT)<?",
//[STICKER_SQL_FIND_GT_INT] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND CAST(value AS INT)>?",
};
static constexpr auto sticker_sql = std::array { static constexpr auto sticker_sql = std::array {
//[STICKER_SQL_GET] = //[STICKER_SQL_GET] =
"SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?",
@ -50,17 +83,6 @@ static constexpr auto sticker_sql = std::array {
"DELETE FROM sticker WHERE type=? AND uri=?", "DELETE FROM sticker WHERE type=? AND uri=?",
//[STICKER_SQL_DELETE_VALUE] = //[STICKER_SQL_DELETE_VALUE] =
"DELETE FROM sticker WHERE type=? AND uri=? AND name=?", "DELETE FROM sticker WHERE type=? AND uri=? AND name=?",
//[STICKER_SQL_FIND] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?",
//[STICKER_SQL_FIND_VALUE] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value=?",
//[STICKER_SQL_FIND_LT] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value<?",
//[STICKER_SQL_FIND_GT] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value>?",
//[STICKER_SQL_DISTINCT_TYPE_URI] = //[STICKER_SQL_DISTINCT_TYPE_URI] =
"SELECT DISTINCT type,uri FROM sticker", "SELECT DISTINCT type,uri FROM sticker",
@ -297,7 +319,8 @@ StickerDatabase::Load(const char *type, const char *uri)
sqlite3_stmt * sqlite3_stmt *
StickerDatabase::BindFind(const char *type, const char *base_uri, StickerDatabase::BindFind(const char *type, const char *base_uri,
const char *name, const char *name,
StickerOperator op, const char *value) StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window)
{ {
assert(type != nullptr); assert(type != nullptr);
assert(name != nullptr); assert(name != nullptr);
@ -305,25 +328,70 @@ StickerDatabase::BindFind(const char *type, const char *base_uri,
if (base_uri == nullptr) if (base_uri == nullptr)
base_uri = ""; base_uri = "";
auto order_by = StringIsEmpty(sort)
? std::string()
: StringIsEqual(sort, "value_int")
? fmt::format("ORDER BY CAST(value AS INT) {}", descending ? "desc" : "asc")
: fmt::format("ORDER BY {} {}", sort, descending ? "desc" : "asc");
auto offset = window.IsAll()
? std::string()
: window.IsOpenEnded()
? fmt::format("LIMIT -1 OFFSET {}", window.start)
: fmt::format("LIMIT {} OFFSET {}", window.Count(), window.start);
std::string sql_str;
sqlite3_stmt *sql;
switch (op) { switch (op) {
case StickerOperator::EXISTS: case StickerOperator::EXISTS:
BindAll(stmt[STICKER_SQL_FIND], type, base_uri, name); sql_str = fmt::format("{} {} {}",
return stmt[STICKER_SQL_FIND]; sticker_sql_find[STICKER_SQL_FIND], order_by, offset);
sql = Prepare(db, sql_str.c_str());
BindAll(sql, type, base_uri, name);
return sql;
case StickerOperator::EQUALS: case StickerOperator::EQUALS:
BindAll(stmt[STICKER_SQL_FIND_VALUE], sql_str = fmt::format("{} {} {}",
type, base_uri, name, value); sticker_sql_find[STICKER_SQL_FIND_VALUE], order_by, offset);
return stmt[STICKER_SQL_FIND_VALUE]; sql = Prepare(db, sql_str.c_str());
BindAll(sql, type, base_uri, name, value);
return sql;
case StickerOperator::LESS_THAN: case StickerOperator::LESS_THAN:
BindAll(stmt[STICKER_SQL_FIND_LT], sql_str = fmt::format("{} {} {}",
type, base_uri, name, value); sticker_sql_find[STICKER_SQL_FIND_LT], order_by, offset);
return stmt[STICKER_SQL_FIND_LT]; sql = Prepare(db, sql_str.c_str());
BindAll(sql, type, base_uri, name, value);
return sql;
case StickerOperator::GREATER_THAN: case StickerOperator::GREATER_THAN:
BindAll(stmt[STICKER_SQL_FIND_GT], sql_str = fmt::format("{} {} {}",
type, base_uri, name, value); sticker_sql_find[STICKER_SQL_FIND_GT], order_by, offset);
return stmt[STICKER_SQL_FIND_GT]; sql = Prepare(db, sql_str.c_str());
BindAll(sql, type, base_uri, name, value);
return sql;
case StickerOperator::EQUALS_INT:
sql_str = fmt::format("{} {} {}",
sticker_sql_find[STICKER_SQL_FIND_EQ_INT], order_by, offset);
sql = Prepare(db, sql_str.c_str());
BindAll(sql, type, base_uri, name, value);
return sql;
case StickerOperator::LESS_THAN_INT:
sql_str = fmt::format("{} {} {}",
sticker_sql_find[STICKER_SQL_FIND_LT_INT], order_by, offset);
sql = Prepare(db, sql_str.c_str());
BindAll(sql, type, base_uri, name, value);
return sql;
case StickerOperator::GREATER_THAN_INT:
sql_str = fmt::format("{} {} {}",
sticker_sql_find[STICKER_SQL_FIND_GT_INT], order_by, offset);
sql = Prepare(db, sql_str.c_str());
BindAll(sql, type, base_uri, name, value);
return sql;
} }
assert(false); assert(false);
@ -333,18 +401,18 @@ StickerDatabase::BindFind(const char *type, const char *base_uri,
void void
StickerDatabase::Find(const char *type, const char *base_uri, const char *name, StickerDatabase::Find(const char *type, const char *base_uri, const char *name,
StickerOperator op, const char *value, StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window,
void (*func)(const char *uri, const char *value, void (*func)(const char *uri, const char *value,
void *user_data), void *user_data),
void *user_data) void *user_data)
{ {
assert(func != nullptr); assert(func != nullptr);
sqlite3_stmt *const s = BindFind(type, base_uri, name, op, value); sqlite3_stmt *const s = BindFind(type, base_uri, name, op, value, sort, descending, window);
assert(s != nullptr); assert(s != nullptr);
AtScopeExit(s) { AtScopeExit(s) {
sqlite3_reset(s); sqlite3_finalize(s);
sqlite3_clear_bindings(s);
}; };
ExecuteForEach(s, [s, func, user_data](){ ExecuteForEach(s, [s, func, user_data](){

View File

@ -28,6 +28,7 @@
#include "Match.hxx" #include "Match.hxx"
#include "lib/sqlite/Database.hxx" #include "lib/sqlite/Database.hxx"
#include "protocol/RangeArg.hxx"
#include <sqlite3.h> #include <sqlite3.h>
@ -46,10 +47,6 @@ class StickerDatabase {
SQL_INSERT, SQL_INSERT,
SQL_DELETE, SQL_DELETE,
SQL_DELETE_VALUE, SQL_DELETE_VALUE,
SQL_FIND,
SQL_FIND_VALUE,
SQL_FIND_LT,
SQL_FIND_GT,
SQL_DISTINCT_TYPE_URI, SQL_DISTINCT_TYPE_URI,
SQL_TRANSACTION_BEGIN, SQL_TRANSACTION_BEGIN,
SQL_TRANSACTION_COMMIT, SQL_TRANSACTION_COMMIT,
@ -59,6 +56,19 @@ class StickerDatabase {
SQL_COUNT SQL_COUNT
}; };
enum SQL_FIND {
SQL_FIND,
SQL_FIND_VALUE,
SQL_FIND_LT,
SQL_FIND_GT,
SQL_FIND_EQ_INT,
SQL_FIND_LT_INT,
SQL_FIND_GT_INT,
SQL_FIND_COUNT
};
std::string path; std::string path;
Sqlite::Database db; Sqlite::Database db;
@ -143,6 +153,7 @@ public:
*/ */
void Find(const char *type, const char *base_uri, const char *name, void Find(const char *type, const char *base_uri, const char *name,
StickerOperator op, const char *value, StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window,
void (*func)(const char *uri, const char *value, void (*func)(const char *uri, const char *value,
void *user_data), void *user_data),
void *user_data); void *user_data);
@ -178,7 +189,8 @@ private:
sqlite3_stmt *BindFind(const char *type, const char *base_uri, sqlite3_stmt *BindFind(const char *type, const char *base_uri,
const char *name, const char *name,
StickerOperator op, const char *value); StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window);
}; };
#endif #endif

View File

@ -28,6 +28,24 @@ enum class StickerOperator {
* value bigger than the specified one. * value bigger than the specified one.
*/ */
GREATER_THAN, GREATER_THAN,
/**
* Matches if a sticker with the specified name exists with a
* integer value equal the specified one.
*/
EQUALS_INT,
/**
* Matches if a sticker with the specified name exists with a
* integer value smaller than the specified one.
*/
LESS_THAN_INT,
/**
* Matches if a sticker with the specified name exists with a
* integer value bigger than the specified one.
*/
GREATER_THAN_INT,
}; };
#endif #endif

View File

@ -92,6 +92,7 @@ void
sticker_song_find(StickerDatabase &sticker_database, const Database &db, sticker_song_find(StickerDatabase &sticker_database, const Database &db,
const char *base_uri, const char *name, const char *base_uri, const char *name,
StickerOperator op, const char *value, StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window,
void (*func)(const LightSong &song, const char *value, void (*func)(const LightSong &song, const char *value,
void *user_data), void *user_data),
void *user_data) void *user_data)
@ -114,5 +115,6 @@ sticker_song_find(StickerDatabase &sticker_database, const Database &db,
data.base_uri_length = strlen(data.base_uri); data.base_uri_length = strlen(data.base_uri);
sticker_database.Find("song", data.base_uri, name, op, value, sticker_database.Find("song", data.base_uri, name, op, value,
sort, descending, window,
sticker_song_find_cb, &data); sticker_song_find_cb, &data);
} }

View File

@ -5,6 +5,7 @@
#define MPD_SONG_STICKER_HXX #define MPD_SONG_STICKER_HXX
#include "Match.hxx" #include "Match.hxx"
#include "protocol/RangeArg.hxx"
#include <string> #include <string>
@ -80,6 +81,7 @@ void
sticker_song_find(StickerDatabase &sticker_database, const Database &db, sticker_song_find(StickerDatabase &sticker_database, const Database &db,
const char *base_uri, const char *name, const char *base_uri, const char *name,
StickerOperator op, const char *value, StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window,
void (*func)(const LightSong &song, const char *value, void (*func)(const LightSong &song, const char *value,
void *user_data), void *user_data),
void *user_data); void *user_data);