diff --git a/NEWS b/NEWS index 699945f0a..c5afc7c42 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,7 @@ ver 0.24 (not yet released) - new commands "stickernames" and "playlistlength" - new "search"/"find" filter "added-since" - allow range in listplaylist and listplaylistinfo + - "sticker find" supports sort and window parameter and new sticker compare operators "eq", "lt" and "gt" * database - attribute "added" shows when each song was added to the database - proxy: require MPD 0.21 or later diff --git a/doc/protocol.rst b/doc/protocol.rst index 338b072c1..3a357d342 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -1486,19 +1486,21 @@ the database for songs). .. _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 specified name, below the specified directory (URI). For each matching song, it prints the URI and that one 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 {TYPE} {URI} {NAME} = {VALUE}` +:command:`sticker find {TYPE} {URI} {NAME} = {VALUE} [sort {SORTTYPE}] [window {START:END}]` Searches for stickers with the given value. Other supported operators are: - "``<``", "``>``" + "``<``", "``>``" for strings and "``eq``", "``lt``", "``gt``" to cast the value to an integer. Examples: diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx index 4f74049a0..99d38f789 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.cxx @@ -78,7 +78,8 @@ public: 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{ .name = name, .sticker_type = sticker_type, @@ -97,6 +98,7 @@ public: uri, name, op, value, + sort, descending, window, callback, &data); return CommandResult::OK; @@ -172,7 +174,8 @@ public: 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 = { response, name, @@ -180,6 +183,7 @@ public: sticker_song_find(sticker_database, database, uri, data.name, op, value, + sort, descending, window, sticker_song_find_print_cb, &data); return CommandResult::OK; @@ -386,7 +390,37 @@ handle_sticker(Client &client, Request args, Response &r) return handler->Delete(uri, sticker_name); /* 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; auto value = has_op ? args[5] : nullptr; StickerOperator op = StickerOperator::EXISTS; @@ -399,12 +433,18 @@ handle_sticker(Client &client, Request args, Response &r) op = StickerOperator::LESS_THAN; else if (StringIsEqual(op_s, ">")) 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 { r.FmtError(ACK_ERROR_ARG, "bad operator \"{}\"", op_s); 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"); diff --git a/src/sticker/Database.cxx b/src/sticker/Database.cxx index c5b3429e2..22d8dbb8d 100644 --- a/src/sticker/Database.cxx +++ b/src/sticker/Database.cxx @@ -10,6 +10,7 @@ #include "util/StringCompare.hxx" #include "util/ScopeExit.hxx" +#include #include #include #include @@ -17,6 +18,19 @@ 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 { STICKER_SQL_GET, STICKER_SQL_LIST, @@ -24,10 +38,6 @@ enum sticker_sql { STICKER_SQL_INSERT, STICKER_SQL_DELETE, 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_TRANSACTION_BEGIN, STICKER_SQL_TRANSACTION_COMMIT, @@ -37,6 +47,29 @@ enum sticker_sql { 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_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)?", +}; + static constexpr auto sticker_sql = std::array { //[STICKER_SQL_GET] = "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=?", //[STICKER_SQL_DELETE_VALUE] = "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_DISTINCT_TYPE_URI] = "SELECT DISTINCT type,uri FROM sticker", @@ -297,7 +319,8 @@ StickerDatabase::Load(const char *type, const char *uri) sqlite3_stmt * StickerDatabase::BindFind(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) { assert(type != nullptr); assert(name != nullptr); @@ -305,25 +328,70 @@ StickerDatabase::BindFind(const char *type, const char *base_uri, if (base_uri == nullptr) 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) { case StickerOperator::EXISTS: - BindAll(stmt[STICKER_SQL_FIND], type, base_uri, name); - return stmt[STICKER_SQL_FIND]; + sql_str = fmt::format("{} {} {}", + 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: - BindAll(stmt[STICKER_SQL_FIND_VALUE], - type, base_uri, name, value); - return stmt[STICKER_SQL_FIND_VALUE]; + sql_str = fmt::format("{} {} {}", + sticker_sql_find[STICKER_SQL_FIND_VALUE], order_by, offset); + sql = Prepare(db, sql_str.c_str()); + BindAll(sql, type, base_uri, name, value); + return sql; case StickerOperator::LESS_THAN: - BindAll(stmt[STICKER_SQL_FIND_LT], - type, base_uri, name, value); - return stmt[STICKER_SQL_FIND_LT]; + sql_str = fmt::format("{} {} {}", + sticker_sql_find[STICKER_SQL_FIND_LT], order_by, offset); + sql = Prepare(db, sql_str.c_str()); + BindAll(sql, type, base_uri, name, value); + return sql; case StickerOperator::GREATER_THAN: - BindAll(stmt[STICKER_SQL_FIND_GT], - type, base_uri, name, value); - return stmt[STICKER_SQL_FIND_GT]; + sql_str = fmt::format("{} {} {}", + sticker_sql_find[STICKER_SQL_FIND_GT], order_by, offset); + 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); @@ -333,18 +401,18 @@ StickerDatabase::BindFind(const char *type, const char *base_uri, void StickerDatabase::Find(const char *type, const char *base_uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window, void (*func)(const char *uri, const char *value, void *user_data), void *user_data) { 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); AtScopeExit(s) { - sqlite3_reset(s); - sqlite3_clear_bindings(s); + sqlite3_finalize(s); }; ExecuteForEach(s, [s, func, user_data](){ diff --git a/src/sticker/Database.hxx b/src/sticker/Database.hxx index 8d7f8d28f..726fb9310 100644 --- a/src/sticker/Database.hxx +++ b/src/sticker/Database.hxx @@ -28,6 +28,7 @@ #include "Match.hxx" #include "lib/sqlite/Database.hxx" +#include "protocol/RangeArg.hxx" #include @@ -46,10 +47,6 @@ class StickerDatabase { SQL_INSERT, SQL_DELETE, SQL_DELETE_VALUE, - SQL_FIND, - SQL_FIND_VALUE, - SQL_FIND_LT, - SQL_FIND_GT, SQL_DISTINCT_TYPE_URI, SQL_TRANSACTION_BEGIN, SQL_TRANSACTION_COMMIT, @@ -59,6 +56,19 @@ class StickerDatabase { 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; Sqlite::Database db; @@ -143,6 +153,7 @@ public: */ void Find(const char *type, const char *base_uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window, void (*func)(const char *uri, const char *value, void *user_data), void *user_data); @@ -178,7 +189,8 @@ private: sqlite3_stmt *BindFind(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); }; #endif diff --git a/src/sticker/Match.hxx b/src/sticker/Match.hxx index d3a0cc9e3..18c8b1d14 100644 --- a/src/sticker/Match.hxx +++ b/src/sticker/Match.hxx @@ -28,6 +28,24 @@ enum class StickerOperator { * value bigger than the specified one. */ 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 diff --git a/src/sticker/SongSticker.cxx b/src/sticker/SongSticker.cxx index c5a168f54..401ad81cc 100644 --- a/src/sticker/SongSticker.cxx +++ b/src/sticker/SongSticker.cxx @@ -92,6 +92,7 @@ void sticker_song_find(StickerDatabase &sticker_database, const Database &db, const char *base_uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window, void (*func)(const LightSong &song, const char *value, 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); sticker_database.Find("song", data.base_uri, name, op, value, + sort, descending, window, sticker_song_find_cb, &data); } diff --git a/src/sticker/SongSticker.hxx b/src/sticker/SongSticker.hxx index a29e21b3c..2d6587bb3 100644 --- a/src/sticker/SongSticker.hxx +++ b/src/sticker/SongSticker.hxx @@ -5,6 +5,7 @@ #define MPD_SONG_STICKER_HXX #include "Match.hxx" +#include "protocol/RangeArg.hxx" #include @@ -80,6 +81,7 @@ void sticker_song_find(StickerDatabase &sticker_database, const Database &db, const char *base_uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window, void (*func)(const LightSong &song, const char *value, void *user_data), void *user_data);