diff --git a/doc/lektor.1 b/doc/lektor.1
index 24b05b1c042f9fabd8bc29752ac5a6bc6345abbf..8ef2076d1624d3953ab077dc780a15700f7b419a 100644
--- a/doc/lektor.1
+++ b/doc/lektor.1
@@ -60,11 +60,7 @@ them.
 the \fBarg\fP should be the id of the kara in the sqlite3 database.
 .TP
 \(bu
-\fBfs://${arg}\fP
-the \fBarg\fP should be a part of the path to the kara.
-.TP
-\(bu
-\fBlang://${arg}\fP / \fBlanguage://${arg}\fP
+\fBlang://${arg}\fP
 the \fBarg\fP should be the language of the kara.
 .TP
 \(bu
@@ -72,12 +68,16 @@ the \fBarg\fP should be the language of the kara.
 the \fBarg\fP should be the type of the kara.
 .TP
 \(bu
-\fBcat://${arg}\fP / \fBcategory://${arg}\fP
+\fBcategory://${arg}\fP
 the \fBarg\fP should be the category of the kara.
 .TP
 \(bu
 \fBauthor://${arg}\fP
-the \fBarg\fP should be the author of the kara
+the \fBarg\fP should be the author of the kara.
+.TP
+\(bu
+\fBplaylist://${plt-name}\fP
+The \fBplt-name\fP should be the name of a playlist.
 .TP
 \(bu
 \fBquery://${arg}\fP
diff --git a/doc/lkt.1 b/doc/lkt.1
index 54cdef401d7d38579de35205b1a3abbbac34b3b9..3976f00b303da4ea953a2cefa5ad59793a4207c1 100644
--- a/doc/lkt.1
+++ b/doc/lkt.1
@@ -108,6 +108,9 @@ Clear the queue and set the state to \fIstopped\fP
 \fBqueue crop\fP
 Crop the queue, delete every kara from it appart from the currently
 playing one
+.TP
+\fBqueue replace\fP <plt-name>
+Replace the queue with the content of a playlist. Keep the playling state
 .PP
 
 \fISEARCH-COMMANDS\fP
@@ -213,19 +216,18 @@ type and the next of the line is the SQL regex that the kara must verify.
 In SQL regexes, the wildcard is the "%" character and the jocker the
 character "_". Queries are case insensitive.
 .PP
-Valid types for a query are the following: \fIid\fP, \fIlanguage\fP, or
-\fIlang\fP, \fItype\fP, \fIcat\fP or \fIcategory\fP, \fIauthor\fP, \fIquery\fP,
-\fIsource\fP and \fItitle\fP.
+Valid types for a query are the following: \fIid\fP, \fIlang\fP, \fItype\fP,
+\fIcategory\fP, \fIauthor\fP, \fIquery\fP and \fIplaylist\fP.
 .PP
 For the type \fItype\fP, the valid values are the following: \fIOP\fP,
 \fIED\fP, \fIIS\fP, \fIAMV\fP, \fIVOCA\fP, \fIMV\fP, \fIPV\fP
 and \fILIVE\fP.
 .PP
-For the type \fIlanguage\fP or \fIlang\fP, the valid values are the following:
-\fIjp\fP, \fIfr\fP, \fIsp\fP, \fIen\fP, \fIlatin\fP, \fIit\fP, \fIru\fP,
-\fImulti\fP and \fIundefined\fP.
+For the type \fIlang\fP, the valid values are the following: \fIjp\fP,
+\fIfr\fP, \fIsp\fP, \fIen\fP, \fIlatin\fP, \fIit\fP, \fIru\fP, \fImulti\fP
+and \fIundefined\fP.
 .PP
-For the \fIcat\fP or \fIcategory\fP type, the valid values are the following:
+For the \fIcategory\fP type, the valid values are the following:
 \fIvo\fP, \fIva\fP, \fIcdg\fP, \fIamv\fP, \fIvocaloid\fP and \fIautres\fP.
 .PP
 Here are some examples of queries:
diff --git a/inc/common/macro.h b/inc/common/macro.h
index c3175cd5a2476939b30d84436a5728675346c6c4..c585059c7ad39800c1fde456f2f4a517eb57217f 100644
--- a/inc/common/macro.h
+++ b/inc/common/macro.h
@@ -74,30 +74,29 @@ enum log_level {
 
 #define URL_MAX_LEN         1024
 
-#define SELF_EXECUTABLE_LINUX           "/proc/self/exe"
-#define SELF_EXECUTABLE_FREEBSD         "/proc/curproc/file"
-#define SELF_EXECUTABLE_SOLARIS         "/proc/self/path/a.out"
-
-#define LKT_ENV_RESTART                 "__LKT_RESTART"
-#define LKT_ENV_CURRENT                 "__LKT_CURRENT"
-
-#define LKT_MAX_SQLITE_STATEMENT        1024
-#define PROTECTED_DATABASE              "disk"
-
-#define LKT_DATABASE_NAME_KID           "id"
-#define LKT_DATABASE_NAME_KNAME         "source_name"
-#define LKT_DATABASE_NAME_KTITLE        "song_title"
-#define LKT_DATABASE_NAME_KCAT          "category"
-#define LKT_DATABASE_NAME_KTYPE         "song_type"
-#define LKT_DATABASE_NAME_KAUTHOR       "author_name"
-#define LKT_DATABASE_NAME_KLANG         "language"
-#define LKT_DATABASE_KARA_COLUMNT_ANY   "any_col"
-#define LKT_DATABASE_KARA_ALL           "string"
-
-#define LEKTOR_TAG_MAX                  256
-#define LKT_MESSAGE_ARGS_MAX            32
-#define LKT_MESSAGE_MAX                 2048
-#define LKT_DEFAULT_LIST_SIZE           10
+#define SELF_EXECUTABLE_LINUX       "/proc/self/exe"
+#define SELF_EXECUTABLE_FREEBSD     "/proc/curproc/file"
+#define SELF_EXECUTABLE_SOLARIS     "/proc/self/path/a.out"
+
+#define LKT_ENV_RESTART             "__LKT_RESTART"
+#define LKT_ENV_CURRENT             "__LKT_CURRENT"
+
+#define LKT_MAX_SQLITE_STATEMENT    1024
+#define PROTECTED_DATABASE          "disk"
+
+#define LKT_DB_ID                   "id"
+#define LKT_DB_NAME                 "source_name"
+#define LKT_DB_TITLE                "song_title"
+#define LKT_DB_CAT                  "category"
+#define LKT_DB_TYPE                 "song_type"
+#define LKT_DB_AUTHOR               "author_name"
+#define LKT_DB_LANG                 "language"
+#define LKT_DB_ALL                  "string"
+
+#define LEKTOR_TAG_MAX              256
+#define LKT_MESSAGE_ARGS_MAX        32
+#define LKT_MESSAGE_MAX             2048
+#define LKT_DEFAULT_LIST_SIZE       10
 
 typedef volatile enum {
     MPD_IDLE_NONE               = 0,
@@ -146,7 +145,8 @@ typedef volatile enum {
 #define SQLITE_BIND_TEXT(db, stmt, pos, text, error)                \
     if (sqlite3_bind_text(stmt, pos, text, -1, 0) != SQLITE_OK) {   \
         LOG_ERROR("DB", "Failed to bind text %s at pos %d: %s",     \
-                      text, pos, sqlite3_errmsg((sqlite3 *) db));   \
+                      (const char *) text, pos,                     \
+                      sqlite3_errmsg((sqlite3 *) db));              \
         goto error;                                                 \
     }
 
diff --git a/inc/common/queue.h b/inc/common/queue.h
index fc3809ae205af4aa4dc43fa1eaf6491f13bbde95..5d5e5115f2658086054e999a6bab423f4226b1ba 100644
--- a/inc/common/queue.h
+++ b/inc/common/queue.h
@@ -7,7 +7,7 @@ enum lkt_event_type {
     lkt_event_null      = 0,            /* NULL         */
     lkt_event_play_pos  = (1 << 1),     /* size_t       */
     lkt_event_play_file = (1 << 2),     /* const char*  */
-} type;
+};
 
 #define lkt_event_play (lkt_event_play_pos | lkt_event_play_file)
 
diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h
index 617046afe664f37f5bbd4af2bf5ae855f76cef8e..dae78100702bc29dd707a051f5a8bab8b611ce01 100644
--- a/inc/lektor/commands.h
+++ b/inc/lektor/commands.h
@@ -38,6 +38,7 @@ bool command_crop   (volatile sqlite3 *db,
 bool command_move   (volatile sqlite3 *db, char *args[LKT_MESSAGE_ARGS_MAX],                      mpd_idle_flag *watch_mask_ptr);
 bool command_shuffle(volatile sqlite3 *db,                                                        mpd_idle_flag *watch_mask_ptr);
 bool command_playid (volatile sqlite3 *db, struct lkt_win *win, char *args[LKT_MESSAGE_ARGS_MAX], mpd_idle_flag *watch_mask_ptr);
+bool command_dump   (volatile sqlite3 *db,                      char *args[LKT_MESSAGE_ARGS_MAX], mpd_idle_flag *watch_mask_ptr);
 
 bool command_queue_list(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX]);
 
diff --git a/inc/lektor/database.h b/inc/lektor/database.h
index 87c63aa1f5fcfea8ec479d0fd679987b09402b90..b6fe3bf2aa4333f7223d80d612ef99b1849be357 100644
--- a/inc/lektor/database.h
+++ b/inc/lektor/database.h
@@ -55,7 +55,7 @@ void database_update_touch        (volatile sqlite3 *db, int id);
 
 /* Control the content of the queue. */
 bool database_queue_add_uri(volatile sqlite3 *db, struct lkt_uri *uri, int priority);
-bool database_queue_add_id(volatile sqlite3 *db, int id, int priority);
+bool database_queue_add_id (volatile sqlite3 *db, int id,              int priority);
 bool database_queue_del_id (volatile sqlite3 *db, int id);
 bool database_queue_del_pos(volatile sqlite3 *db, int pos);
 bool database_queue_clear  (volatile sqlite3 *db);
@@ -63,6 +63,7 @@ bool database_queue_crop   (volatile sqlite3 *db);
 bool database_queue_move   (volatile sqlite3 *db, int from, int to);
 bool database_queue_shuffle(volatile sqlite3 *db);
 bool database_queue_seekid (volatile sqlite3 *db, int id, int *out_pos);
+bool database_queue_dump   (volatile sqlite3 *db, const char *plt_name);
 
 /* Control the playing state of the queue. */
 bool database_queue_toggle_pause(volatile sqlite3 *db);
@@ -91,19 +92,24 @@ struct lkt_search {
         lkt_search_sticker,
     } type;
 
-    void (*call)(void);     /* Called during the iter phase, casted         */
+    void (*call)(void);     /* Called during the iter phase, casted     */
 
     struct lkt_state *srv;
     size_t c;
 
-    long continuation;      /* The continuation state of the client         */
-    int msg_count;          /* How much messages we can send                */
-    const char *name;       /* Stickers and playlists                       */
-    int st_value;           /* The value of a sticker                       */
-    int st_uri;             /* URI of a sticker                             */
-    char st_op;             /* Comparaison operator for stickers            */
-    char *st_type;          /* Type of sticker                              */
-    char *ka_rgx;           /* Regex for the content of the selected column */
+    long continuation;      /* The continuation state of the client     */
+    int msg_count;          /* How much messages we can send            */
+
+    union {
+        const char *st_name;    /* Sticker name                         */
+        const char *plt_name;   /* Playlist name                        */
+    };
+    int st_value;               /* The value of a sticker               */
+    int st_uri;                 /* URI of a sticker                     */
+    char st_op;                 /* Comparaison operator for stickers    */
+    char *st_type;              /* Type of sticker                      */
+
+    struct lkt_uri ka_uri;      /* Search by uri                        */
 };
 
 typedef bool (*lkt_search_init_add_func)(volatile sqlite3 *, struct lkt_uri *, int);
@@ -114,6 +120,7 @@ typedef bool (*lkt_search_sticker_func) (struct lkt_state *srv, size_t c, const
 bool database_search_database_init(volatile sqlite3 *db, struct lkt_search *ret);
 bool database_search_queue_init   (volatile sqlite3 *db, struct lkt_search *ret);
 bool database_search_sticker_init (volatile sqlite3 *db, struct lkt_search *ret);
+bool database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret);
 bool database_search_iter(struct lkt_search *item);
 
 /* Next and prev operation on the queue. */
diff --git a/inc/lektor/uri.h b/inc/lektor/uri.h
index c24f82ef38b92034bdc84f80e7bcb340fef0cba8..513091d46033659ff7a896d8ce2e7bca934efb29 100644
--- a/inc/lektor/uri.h
+++ b/inc/lektor/uri.h
@@ -5,7 +5,6 @@
 #include <stdbool.h>
 
 enum lkt_uri_type {
-    uri_fs,
     uri_id,
     uri_playlist,
     uri_type,
@@ -17,17 +16,17 @@ enum lkt_uri_type {
 
 struct lkt_uri {
     enum lkt_uri_type type;
+    const char *column_name;
     union {
         void *value;
         size_t id;
     };
-
-
     bool _allocated;
 };
 
 /* Create and delete URIs */
 bool lkt_uri_from(struct lkt_uri *ret, char *const str);
+bool lkt_uri_from_list(struct lkt_uri *ret, char **list);
 void lkt_uri_free(struct lkt_uri *ret);
 
 /* Make an URL to download from kurisu.
diff --git a/src/commands.c b/src/commands.c
index 5d53ae23ea0f33641ac397d7bcd6aaa3e8876e30..497786cee00542d7dac11879057c1949d7179be9 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -216,6 +216,15 @@ command_play(volatile sqlite3 *db, struct lkt_win *win, char *args[LKT_MESSAGE_A
     return __play_that_file(db, win, pos);
 }
 
+bool
+command_dump(volatile sqlite3 *db, char *args[LKT_MESSAGE_ARGS_MAX],
+             mpd_idle_flag *watch_mask_ptr)
+{
+    RETURN_UNLESS(args[0], "Invalid argument", false);
+    *watch_mask_ptr |= MPD_IDLE_PLAYLIST;
+    return database_queue_dump(db, args[0]);
+}
+
 bool
 command_playid(volatile sqlite3 *db, struct lkt_win *win, char *args[LKT_MESSAGE_ARGS_MAX], mpd_idle_flag *watch_mask_ptr)
 {
@@ -251,13 +260,12 @@ command_add(volatile sqlite3 *db, struct lkt_win *win,
             char *args[LKT_MESSAGE_ARGS_MAX], mpd_idle_flag *watch_mask_ptr,
             int priority)
 {
-    RETURN_UNLESS(args, "Invalid argument", false);
+    RETURN_UNLESS(args && args[0], "Invalid argument", false);
     *watch_mask_ptr |= MPD_IDLE_PLAYLIST;
     struct lkt_uri uri;
-    char *query = args[0];
     int ret;        /* To be modified according to the command (insert or add) later (TODO) */
     UNUSED(win);    /* No callbacks to the window for the moment */
-    RETURN_UNLESS(lkt_uri_from(&uri, query), "Failed to parse query", false);
+    RETURN_UNLESS(lkt_uri_from(&uri, args[0]), "Failed to parse query", false);
     ret = database_queue_add_uri(db, &uri, priority);
     lkt_uri_free(&uri);
     if (!ret)
@@ -274,9 +282,8 @@ command_addid(volatile sqlite3 *db, struct lkt_win *win,
     errno = 0;
     int i;
     struct lkt_uri uri = { .type = uri_id };
-    for (i = 0; (uri.id = strtol(args[i], NULL, 0)) && ! errno; ++i) {
+    for (i = 0; (uri.id = strtol(args[i], NULL, 0)) && ! errno; ++i)
         errno |= database_queue_add_id(db, uri.id, 1);
-    }
     *watch_mask_ptr |= MPD_IDLE_PLAYLIST;
     return ! errno;
 }
@@ -427,55 +434,29 @@ lkt_callback_send_row_v2(struct lkt_state *srv, size_t c, int id, int id_len, co
 }
 
 bool
-command_find(struct lkt_state *srv, size_t c, char *cmd_args[LKT_MESSAGE_ARGS_MAX], long continuation,
-             bool(*init)(volatile sqlite3 *, struct lkt_search *))
+command_find(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX],
+             long continuation, bool(*init)(volatile sqlite3 *, struct lkt_search *))
 {
-    char rgx[PATH_MAX], *mpd_tag;
     int count;
     struct lkt_search search = {
         .srv = srv,
         .c = c,
         .continuation = continuation,
-        .msg_count = lkt_remaining_msg(srv, c) - 3, /* Reserve slots for OK/ACK and continue: */
+        /* Reserve slots for OK/ACK and continue: */
+        .msg_count = lkt_remaining_msg(srv, c) - 3,
         .call = (void(*)(void)) lkt_callback_send_row_v2,
-        .ka_rgx = rgx,
+        .ka_uri = {0},
+        .plt_name = args[0], /* In case of playlist searchs */
     };
 
     /* Check args */
-    RETURN_UNLESS(cmd_args && cmd_args[0], "Invalid argument", false);
-
-    /* Select the right column */
-    mpd_tag = cmd_args[0];
-    if (STR_MATCH("any", mpd_tag) || STR_MATCH("all", mpd_tag) || STR_MATCH("query", mpd_tag) ||
-        STR_MATCH("source", mpd_tag) || STR_MATCH("title", mpd_tag))
-        search.name = LKT_DATABASE_KARA_COLUMNT_ANY;
-
-    else if (STR_MATCH("author", mpd_tag))
-        search.name = LKT_DATABASE_NAME_KAUTHOR;
-
-    else if (STR_MATCH("category", mpd_tag) || STR_MATCH("cat", mpd_tag))
-        search.name = LKT_DATABASE_NAME_KCAT;
-
-    else if (STR_MATCH("type", mpd_tag))
-        search.name = LKT_DATABASE_NAME_KTYPE;
-
-    else if (STR_MATCH("language", mpd_tag) || STR_MATCH("lang", mpd_tag))
-        search.name = LKT_DATABASE_NAME_KLANG;
-
-    else if (STR_MATCH("id", mpd_tag))
-        search.name = LKT_DATABASE_NAME_KID;
-
-    else
-        return false;
-
-    /* Get the regex */
-    RETURN_UNLESS(cmd_args[1], "No regex", false);
-    memset(rgx, 0, PATH_MAX * sizeof(char));
-
-    for (int i = 1; cmd_args[i]; ++i) {
-        strncat(rgx, cmd_args[i], PATH_MAX - 1);
-        if (cmd_args[i + 1])
-            strncat(rgx, " ", PATH_MAX - 1);
+    RETURN_UNLESS(args[0], "Invalid argument", false);
+    if (!lkt_uri_from_list(&search.ka_uri, args)) {
+        /* Try from idx 1, in case of playlust searches */
+        LOG_DEBUG("COMMAND", "URI may not starts at idx 0, may be because "
+                  "of playlist search. At idx 0, value was '%s'", args[0]);
+        RETURN_UNLESS(lkt_uri_from_list(&search.ka_uri, &args[1]),
+                      "Failed to create the uri", false);
     }
 
     /* Make the search langand do the right action */
@@ -489,6 +470,7 @@ command_find(struct lkt_state *srv, size_t c, char *cmd_args[LKT_MESSAGE_ARGS_MA
     else
         LOG_WARN("COMMAND", "%s", "Nothing found");
 
+    lkt_uri_free(&search.ka_uri);
     return true;
 }
 
@@ -810,8 +792,8 @@ command_sticker_get(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS
     /* list {type} {uri} {name} command */
     else if (argv[0] != NULL && argv[1] != NULL &&
              argv[2] != NULL && argv[3] == NULL) {
-        callback.name = argv[2];
-        callback.st_uri = atoi(argv[1]);
+        callback.st_name = argv[2];
+        callback.st_uri  = atoi(argv[1]);
         if (!database_search_sticker_init(srv->db, &callback))
             return false;
     }
@@ -820,9 +802,9 @@ command_sticker_get(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS
     else if (argv[0] != NULL && argv[1] != NULL &&
              argv[2] != NULL && argv[3] != NULL &&
              argv[4] != NULL && argv[5] == NULL) {
-        callback.name = argv[2];
-        callback.st_uri = atoi(argv[1]);
-        callback.st_op = argv[3][0];
+        callback.st_name  = argv[2];
+        callback.st_uri   = atoi(argv[1]);
+        callback.st_op    = argv[3][0];
         callback.st_value = atoi(argv[4]);
         if (!database_search_sticker_init(srv->db, &callback))
             return false;
diff --git a/src/database/find.c b/src/database/find.c
index ef8f475ae41df4990e29f008833d2bedeb8db70a..c8abc0febb018e14d56e176763f6c0f196599803 100644
--- a/src/database/find.c
+++ b/src/database/find.c
@@ -8,24 +8,59 @@
 #include <stdio.h>
 #include <string.h>
 
+static inline int
+__check_sticker_type(const char *type)
+{
+    return ! ( STR_MATCH(type, "kara") || STR_MATCH(type, "plt") );
+}
+
 bool
 database_search_database_init(volatile sqlite3 *db, struct lkt_search *ret)
 {
     RETURN_UNLESS(ret, "Exit because return pointer is NULL", false);
     static const char *SQL_STMT_TEMPLATE =
         "WITH content AS ("
-        " SELECT kara.id AS id, string AS any_col, LENGTH(CAST(kara.id AS TEXT)) AS len"
+        " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len"
         " FROM kara WHERE %s LIKE ?)"
-        "SELECT id, any_col, (SELECT MAX(len) FROM content)"
+        "SELECT id, string, (SELECT MAX(len) FROM content)"
         "FROM content LIMIT %d OFFSET %d;";
     char SQL_STMT[LKT_MAX_SQLITE_STATEMENT];
     ret->type = lkt_search_database;
 
     /* Search part */
     safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL_STMT_TEMPLATE,
-                  ret->name, ret->msg_count, ret->continuation);
+                  ret->ka_uri.column_name, ret->msg_count, ret->continuation);
+    SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error);
+    SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->ka_uri.value, error);
+    ret->db = db;
+    return true;
+error:
+    sqlite3_finalize(ret->stmt);
+    return false;
+}
+
+bool
+database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret)
+{
+    RETURN_UNLESS(ret, "Exit because return pointer is NULL", false);
+    static const char *SQL_TEMPLATE =
+        "WITH content AS ("
+        " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len"
+        " FROM kara_playlist"
+        " JOIN playlist ON playlist.id = playlist_id"
+        "               AND playlist.name COLLATE nocase = ?"
+        " JOIN kara ON kara.id = kara_id"
+        "           AND %s LIKE ?)"
+        "SELECT id, string, (SELECT MAX(len) FROM content)"
+        "FROM content LIMIT %d OFFSET %d;";
+    char SQL_STMT[LKT_MAX_SQLITE_STATEMENT];
+    ret->type = lkt_search_playlist;
+
+    safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE,
+                  ret->ka_uri.column_name, ret->msg_count, ret->continuation);
     SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error);
-    SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->ka_rgx, error);
+    SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->plt_name, error);
+    SQLITE_BIND_TEXT(db, ret->stmt, 2, ret->ka_uri.value, error);
     ret->db = db;
     return true;
 error:
@@ -36,8 +71,11 @@ error:
 bool
 database_search_sticker_init(volatile sqlite3 *db, struct lkt_search *ret)
 {
-    /* No bound checks in strcats, should be fine. Possible SQL injection,
-       depend on the `type`. */
+    if (__check_sticker_type(ret->st_type)) {
+        LOG_ERROR("DB", "Type '%s' is invalid", ret->st_type);
+        return false;
+    }
+
     static const char *SQL_all_types =
         "SELECT name, sts.id, value FROM 'stickers' JOIN "
         "( SELECT id, sticker, value, 'kara' FROM 'stickers.kara'"
@@ -63,11 +101,11 @@ database_search_sticker_init(volatile sqlite3 *db, struct lkt_search *ret)
         sprintf(SQL + strlen(SQL), " AND sts.value %s %d",
                 ret->st_op == '>' ? ">=" : ret->st_op == '<' ? "<=" : "=",
                 ret->st_value);
-    strcat(SQL, !ret->name ? ";" : " AND name = ?;");
+    strcat(SQL, !ret->st_name ? ";" : " AND name = ?;");
 
     SQLITE_PREPARE(db, ret->stmt, SQL, error);
-    if (ret->name)
-        SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->name, error);
+    if (ret->st_name)
+        SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->st_name, error);
     return true;
 error:
     sqlite3_finalize(ret->stmt);
@@ -82,21 +120,21 @@ database_search_queue_init(volatile sqlite3 *db, struct lkt_search *ret)
         "WITH content AS ("
         " SELECT"
         "  kara_id AS id,"
-        "  string AS any_col,"
+        "  string,"
         "  LENGTH(CAST(kara_id AS TEXT)) AS len"
         " FROM queue"
         " JOIN kara "
         " ON kara_id = kara.id AND %s LIKE ?)"
-        "SELECT id, any_col, (SELECT MAX(len) FROM content)"
+        "SELECT id, string, (SELECT MAX(len) FROM content)"
         "FROM content LIMIT %d OFFSET %d;";
     char SQL_STMT[LKT_MAX_SQLITE_STATEMENT];
     ret->type = lkt_search_queue;
 
     /* Search part */
     safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL_STMT_TEMPLATE,
-                  ret->name, ret->msg_count, ret->continuation);
+                  ret->ka_uri.column_name, ret->msg_count, ret->continuation);
     SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error);
-    SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->ka_rgx, error);
+    SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->ka_uri.value, error);
     ret->db = db;
     return true;
 error:
@@ -125,19 +163,19 @@ database_search_iter(struct lkt_search *item)
     switch (item->type) {
     case lkt_search_database:
     case lkt_search_queue:
-        id      = sqlite3_column_int(item->stmt, 0);
-        sql_row = (const char *) sqlite3_column_text(item->stmt, 1);
-        id_len  = sqlite3_column_int(item->stmt, 2);
+    case lkt_search_playlist:
+        id      = sqlite3_column_int  (item->stmt, 0);
+        sql_row = sqlite3_column_chars(item->stmt, 1);
+        id_len  = sqlite3_column_int  (item->stmt, 2);
         return ((lkt_search_database_func) item->call)
                (item->srv, item->c, id, id_len, sql_row);
     case lkt_search_sticker:
-        sql_row = (const char *) sqlite3_column_text(item->stmt, 0); /* Name */
-        id      = sqlite3_column_int(item->stmt, 1);                 /* Id   */
-        code    = sqlite3_column_int(item->stmt, 2);                 /* Val  */
-        type    = (const char *) sqlite3_column_text(item->stmt, 3); /* Type */
+        sql_row = sqlite3_column_chars(item->stmt, 0);  /* Name */
+        id      = sqlite3_column_int  (item->stmt, 1);  /* Id   */
+        code    = sqlite3_column_int  (item->stmt, 2);  /* Val  */
+        type    = sqlite3_column_chars(item->stmt, 3);  /* Type */
         return ((lkt_search_sticker_func) item->call)
                (item->srv, item->c, sql_row, type, id, code);
-    case lkt_search_playlist:
     default:
         LOG_WARN("DB", "Search type %d is not implemented", item->type);
         goto end;
diff --git a/src/database/playlist.c b/src/database/playlist.c
index 61b0abf7acf810024fdbc143643c27b31b0b3ce3..a4b9b26c71516502a54e74277a9fd2ea12841cd2 100644
--- a/src/database/playlist.c
+++ b/src/database/playlist.c
@@ -176,20 +176,19 @@ database_plt_add_uri(volatile sqlite3 *db, const char *name,
 
     switch (uri->type) {
     case uri_type:
-        column = LKT_DATABASE_NAME_KTYPE;
+        column = LKT_DB_TYPE;
         break;
     case uri_author:
-        column = LKT_DATABASE_NAME_KAUTHOR;
+        column = LKT_DB_AUTHOR;
         break;
     case uri_category:
-        column = LKT_DATABASE_NAME_KCAT;
+        column = LKT_DB_CAT;
         break;
     case uri_language:
-        column = LKT_DATABASE_NAME_KLANG;
+        column = LKT_DB_LANG;
         break;
     case uri_query:
-    case uri_fs:
-        column = LKT_DATABASE_KARA_ALL;
+        column = LKT_DB_ALL;
         break;
     default:
         return false;
diff --git a/src/database/queue.c b/src/database/queue.c
index 6c131af43e9d1d4355f8892993afe144dfbef209..92d82e63b59c3e80a1a30828716d045993e21578 100644
--- a/src/database/queue.c
+++ b/src/database/queue.c
@@ -258,29 +258,23 @@ error:
 }
 
 bool
-database_queue_add_uri(volatile sqlite3 *db, struct lkt_uri *uri, int priority)
+database_queue_add_uri(volatile sqlite3 *db, struct lkt_uri *uri, int prio)
 {
     switch (uri->type) {
     case uri_query:
-    case uri_fs:
-        return queue_add_with_col_like_str(db, LKT_DATABASE_KARA_ALL,
-                                           uri->value, priority);
+        return queue_add_with_col_like_str(db, LKT_DB_ALL, uri->value, prio);
     case uri_id:
-        return database_queue_add_id(db, uri->id, priority);
+        return database_queue_add_id(db, uri->id, prio);
     case uri_type:
-        return queue_add_with_col_like_str(db, LKT_DATABASE_NAME_KTYPE,
-                                           uri->value, priority);
+        return queue_add_with_col_like_str(db, LKT_DB_TYPE, uri->value, prio);
     case uri_category:
-        return queue_add_with_col_like_str(db, LKT_DATABASE_NAME_KCAT,
-                                           uri->value, priority);
+        return queue_add_with_col_like_str(db, LKT_DB_CAT, uri->value, prio);
     case uri_language:
-        return queue_add_with_col_like_str(db, LKT_DATABASE_NAME_KLANG,
-                                           uri->value, priority);
+        return queue_add_with_col_like_str(db, LKT_DB_LANG, uri->value, prio);
     case uri_author:
-        return queue_add_with_col_like_str(db, LKT_DATABASE_NAME_KAUTHOR,
-                                           uri->value, priority);
+        return queue_add_with_col_like_str(db, LKT_DB_AUTHOR, uri->value, prio);
     case uri_playlist:
-        return database_queue_add_plt(db, (char *) uri->value, priority);
+        return database_queue_add_plt(db, (char *) uri->value, prio);
     default:
         LOG_WARN("DB", "Add to queue for uri of type %d is not"
                  " implemented", uri->type);
@@ -664,6 +658,27 @@ error:
     return ret;
 }
 
+bool
+database_queue_dump(volatile sqlite3 *db, const char *plt_name)
+{
+    static const char *SQL =
+        "WITH plt_id AS (SELECT playlist.id AS id FROM playlist"
+        " WHERE name COLLATE nocase = ?) "
+        "INSERT OR IGNORE INTO kara_playlist (kara_id, playlist_id)"
+        " SELECT DISTINCT kara_id, plt_id.id"
+        " FROM queue, plt_id"
+        " ORDER BY RANDOM();";
+    sqlite3_stmt *stmt = NULL;
+    SQLITE_PREPARE(db, stmt, SQL, error);
+    SQLITE_BIND_TEXT(db, stmt, 1, plt_name, error);
+    SQLITE_STEP_DONE(db, stmt, error);
+    sqlite3_finalize(stmt);
+    return true;
+error:
+    sqlite3_finalize(stmt);
+    return false;
+}
+
 bool
 database_queue_seekid(volatile sqlite3 *db, int id, int *out_pos)
 {
diff --git a/src/main/lkt.c b/src/main/lkt.c
index cc044f05a918122590e5dcac07e4e10e3c97e282..6057953f84154b01486ddeb97d9d04ad75354885 100644
--- a/src/main/lkt.c
+++ b/src/main/lkt.c
@@ -58,14 +58,13 @@ static const char *LKT_QUEUE_DEFAULT[] = { "10" };
 static int
 lkt_valid_type(const char *type)
 {
-    return (STR_MATCH(type, "all") || STR_MATCH(type, "any") || STR_MATCH(type, "query")    ||
-            STR_MATCH(type, "cat") || STR_MATCH(type, "category")                           ||
-            STR_MATCH(type, "author") || STR_MATCH(type, "auth")                            ||
-            STR_MATCH(type, "lang") || STR_MATCH(type, "language")                          ||
+    return (STR_MATCH(type, "query")    ||
+            STR_MATCH(type, "category") ||
+            STR_MATCH(type, "author")   ||
+            STR_MATCH(type, "lang")     ||
             STR_MATCH(type, "id")       ||
-            STR_MATCH(type, "title")    ||
             STR_MATCH(type, "type")     ||
-            STR_MATCH(type, "source"));
+            STR_MATCH(type, "playlist"));
 }
 
 static int
@@ -183,6 +182,7 @@ just_send_one_arg(stickers_create__, "sticker __create")
 just_send_one_arg(stickers_destroy__, "sticker __destroy")
 just_send_one_arg(plt_destroy__, "rm")
 just_send_one_arg(plt_create__, "playlistadd")
+just_send_one_arg(queue_dump__, "__dump")
 #undef just_send_one_arg
 
 #define just_send(func, msg) /* Just send a simple string functions */  \
@@ -276,6 +276,43 @@ populate__(struct lkt_cmd_args *args)
     rescan_or_update__(args, "__rescan");
 }
 
+noreturn void
+queue_replace__(struct lkt_cmd_args *args)
+{
+    if (args->argc != 1)
+        fail("Invalid argument");
+    bool play = false;
+    char buff[LKT_MESSAGE_MAX];
+    FILE *sock = lkt_connect();
+
+    for (;;) {
+        memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
+        read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
+        size_t len = strcspn(buff, LKT_KEY_VALUE_SEP);
+
+        if (STR_NMATCH(buff, "state", len)) {
+            char *it = lkt_skip_key(buff);
+            play = STR_NMATCH(it, "play", 4);
+            break;
+        }
+
+        if (STR_NMATCH(buff, "OK", 2))
+            break;
+        else if (STR_NMATCH(buff, "ACK", 3))
+            fail("ACK");
+    }
+
+    fclose(sock);
+    sock = lkt_connect();
+    write_socket(sock, "command_list_begin\n");
+    write_socket(sock, "clear\n");
+    write_socket(sock, "add playlist://%s\n", args->argv[0]);
+    if (play)
+        write_socket(sock, "play\n");
+    write_socket(sock, "command_list_end\n");
+    exit_with_status(sock, buff);
+}
+
 noreturn void
 config__(struct lkt_cmd_args *args)
 {
@@ -781,10 +818,46 @@ search_with_cmd__(struct lkt_cmd_args *args, const char *cmd)
 redo:
     sock = lkt_connect();
 
-    write_socket(sock, "%d %s", continuation, cmd);
-    for (i = 0; i < args->argc; ++i)
-        write_socket(sock, " %s", args->argv[i]);
-    write_socket(sock, "\n");
+    write_socket(sock, "%d %s %s://", continuation, cmd, args->argv[0]);
+    for (i = 1; i < args->argc - 1; ++i)
+        write_socket(sock, "%s ", args->argv[i]);
+    write_socket(sock, "%s\n", args->argv[i]);
+
+    for (;;) {
+        memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
+        read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
+
+        if (STR_NMATCH(buff, "continue:", strlen("continue:"))) {
+            continuation = atoi(lkt_skip_key(buff));
+            fclose(sock);
+            goto redo;
+        }
+
+        if (STR_NMATCH(buff, "OK", 2))
+            exit(EXIT_SUCCESS);
+        else if (STR_NMATCH(buff, "ACK", 3))
+            exit(EXIT_FAILURE);
+
+        fprintf(stdout, "%s", buff);
+    }
+}
+
+noreturn void
+search_plt__(struct lkt_cmd_args *args)
+{
+    fail_if(args->argc < 3, "Invalid number of arguments");
+    fail_if(!lkt_valid_type(args->argv[1]), "Invalid type for query");
+    char buff[LKT_MESSAGE_MAX];
+    int continuation = 0, i;
+    FILE *sock = NULL;
+redo:
+    sock = lkt_connect();
+
+    write_socket(sock, "%d listplaylist %s %s://", continuation,
+                 args->argv[0], args->argv[1]);
+    for (i = 2; i < args->argc - 1; ++i)
+        write_socket(sock, "%s ", args->argv[i]);
+    write_socket(sock, "%s\n", args->argv[i]);
 
     for (;;) {
         memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
@@ -808,7 +881,6 @@ redo:
 #define search_with_cmd(func, cmd) /* I don't want to write always the same things */ \
     noreturn void func (struct lkt_cmd_args *args) { search_with_cmd__(args, #cmd); }
 search_with_cmd(search_get__,    search)
-search_with_cmd(search_plt__,    listplaylist)
 search_with_cmd(search_count__,  count)
 search_with_cmd(search_queue__,  playlistfind)
 #undef search_with_cmd
@@ -816,14 +888,16 @@ search_with_cmd(search_queue__,  playlistfind)
 /* Parsing stuff. */
 
 static struct lkt_cmd_opt options_queue[] = {
-    { .name = "insert",     .call = queue_insert__ },
-    { .name = "pos",        .call = queue_pos__    },
-    { .name = "pop",        .call = queue_pop__    },
-    { .name = "add",        .call = queue_add__    },
-    { .name = "seek",       .call = queue_seek__   },
-    { .name = "delete",     .call = queue_delete__ },
-    { .name = "clear",      .call = queue_clear__  },
-    { .name = "crop",       .call = queue_crop__   },
+    { .name = "insert",     .call = queue_insert__  },
+    { .name = "pos",        .call = queue_pos__     },
+    { .name = "pop",        .call = queue_pop__     },
+    { .name = "add",        .call = queue_add__     },
+    { .name = "seek",       .call = queue_seek__    },
+    { .name = "delete",     .call = queue_delete__  },
+    { .name = "clear",      .call = queue_clear__   },
+    { .name = "crop",       .call = queue_crop__    },
+    { .name = "replace",    .call = queue_replace__ },
+    { .name = "dump",       .call = queue_dump__    },
     LKT_OPT_DEFAULT(queue_list__),
 };
 
diff --git a/src/net/listen.c b/src/net/listen.c
index 05110a2d59a968d6747e98ff6fb43eca4f769163..59ffd99c68c90ee7726d4c9a059d7b7be1301da8 100644
--- a/src/net/listen.c
+++ b/src/net/listen.c
@@ -159,7 +159,7 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd)
     case MPD_IDLE_NONE:
         /* Commands that requires authentification. */
         if (STR_MATCH(cmd.name, "__adduser"))
-            err = ! command_user_add(srv, c, (sqlite3 *) srv->db, cmd.args);
+            err = ! command_user_add(srv, c, srv->db, cmd.args);
         else if (STR_MATCH(cmd.name, "__restart"))
             err = ! command_restart(srv, c);
         else if (STR_MATCH(cmd.name, "kill"))
@@ -183,25 +183,25 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd)
             err = 0;
 
         else if (STR_MATCH(cmd.name, "next"))
-            err = !command_next((sqlite3 *) srv->db, &srv->win, &srv->mpd_idle_events);
+            err = !command_next(srv->db, &srv->win, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "pause"))
-            err = !command_pause((sqlite3 *) srv->db, &srv->win, &srv->mpd_idle_events);
+            err = !command_pause(srv->db, &srv->win, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "previous"))
-            err = !command_previous((sqlite3 *) srv->db, &srv->win, &srv->mpd_idle_events);
+            err = !command_previous(srv->db, &srv->win, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "play"))
-            err = ! command_play((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events);
+            err = ! command_play(srv->db, &srv->win, cmd.args, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "playid"))
-            err = ! command_playid((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events);
+            err = ! command_playid(srv->db, &srv->win, cmd.args, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "stop"))
-            err = !command_stop((sqlite3 *) srv->db, &srv->win, &srv->mpd_idle_events);
+            err = !command_stop(srv->db, &srv->win, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "clear"))
-            err = !command_clear((sqlite3 *) srv->db, &srv->mpd_idle_events);
+            err = !command_clear(srv->db, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "crop"))
-            err = !command_crop((sqlite3 *) srv->db, &srv->mpd_idle_events);
+            err = !command_crop(srv->db, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "moveid"))
-            err = !command_move((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events);
+            err = !command_move(srv->db, cmd.args, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "shuffle"))
-            err = !command_shuffle((sqlite3 *) srv->db, &srv->mpd_idle_events);
+            err = !command_shuffle(srv->db, &srv->mpd_idle_events);
 
         else if (STR_MATCH(cmd.name, "playlist") || STR_MATCH(cmd.name, "playlistinfo"))
             err = !command_queue_list(srv, c, cmd.args);
@@ -223,29 +223,39 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd)
             err = !command_help(srv, c);
 
         else if (STR_MATCH(cmd.name, "__insert"))
-            err = ! command_add((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events, 5);
-        else if (STR_MATCH(cmd.name, "searchadd") || STR_MATCH(cmd.name, "findadd") || STR_MATCH(cmd.name, "add"))
-            err = ! command_add((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events, 1);
+            err = ! command_add(srv->db, &srv->win, cmd.args,
+                                &srv->mpd_idle_events, 5);
+        else if (STR_MATCH(cmd.name, "searchadd")   ||
+                 STR_MATCH(cmd.name, "findadd")     ||
+                 STR_MATCH(cmd.name, "add"))
+            err = ! command_add(srv->db, &srv->win, cmd.args,
+                                &srv->mpd_idle_events, 1);
         else if (STR_MATCH(cmd.name, "addid"))
-            err = ! command_addid((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events);
+            err = ! command_addid(srv->db, &srv->win, cmd.args,
+                                  &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "deleteid"))
             err = ! (cmd.args[0] != NULL &&
-                     command_delid((sqlite3 *) srv->db, &srv->win, cmd.args[0], &srv->mpd_idle_events));
+                     command_delid(srv->db, &srv->win, cmd.args[0],
+                                   &srv->mpd_idle_events));
 
         else if (STR_MATCH(cmd.name, "playlistclear"))
-            err = ! command_plt_clear((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events);
+            err = ! command_plt_clear(srv->db, cmd.args, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "rename"))
-            err = ! command_plt_rename((sqlite3 * ) srv->db, cmd.args, &srv->mpd_idle_events);
+            err = ! command_plt_rename(srv->db, cmd.args, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "playlistdelete"))
-            err = ! command_plt_remove((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events);
+            err = ! command_plt_remove(srv->db, cmd.args, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "playlistadd"))
-            err = ! command_plt_add((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events);
+            err = ! command_plt_add(srv->db, cmd.args, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "rm") && cmd.args[0] != NULL && cmd.args[1] == NULL)
-            err = ! command_plt_remove((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events);
+            err = ! command_plt_remove(srv->db, cmd.args, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "save"))
-            err = ! command_plt_export((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events);
+            err = ! command_plt_export(srv->db, cmd.args, &srv->mpd_idle_events);
         else if (STR_MATCH(cmd.name, "__import"))
-            err = ! command_plt_import((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events);
+            err = ! command_plt_import(srv->db, cmd.args, &srv->mpd_idle_events);
+        else if (STR_MATCH(cmd.name, "__dump"))
+            err = ! command_dump(srv->db, cmd.args, &srv->mpd_idle_events);
+        else if (STR_MATCH(cmd.name, "listplaylist"))
+            err = ! command_find(srv, c, cmd.args, cmd.cont, database_search_playlist_init);
 
         else if (STR_MATCH(cmd.name, "random"))
             err = !command_set_playback_option(srv, c, lkt_playback_option_random, cmd.args);
diff --git a/src/uri.c b/src/uri.c
index 82731d39d278d3db4c284b945f3a89800b118340..82364fc9eed7ba45aaf6318122fd34b9a17fc618 100644
--- a/src/uri.c
+++ b/src/uri.c
@@ -9,76 +9,112 @@
 #include <errno.h>
 #include <limits.h>
 
-bool
-lkt_uri_from(struct lkt_uri *ret, char *const str)
+static inline char *
+__prefix(char *str, struct lkt_uri *ret)
 {
-    char *val, *endptr;
+    char *val;
     size_t len;
-    long id;
-
-    if (ret == NULL || str == NULL)
-        return false;
 
-    len = strlen(str);
+    if (!str || !ret)
+        return NULL;
 
-    // The minimal uri is id://1 which is a 6 characters long
-    if (len <= 5 * sizeof(char))
-        return false;
+    /* The minimal uri is id://1 which is a 6 characters long */
+    if ((len = strlen(str)) <= 5)
+        return NULL;
 
-    if (!strncasecmp(str, "fs", 2 * sizeof(char))) {
-        ret->type = uri_fs;
-        val = str + 2;
-    } else if (!strncasecmp(str, "id", 2 * sizeof(char))) {
+    if (STR_NMATCH(str, "id", 2)) {
+        ret->column_name = LKT_DB_ID;
         ret->type = uri_id;
         val = str + 2;
-    } else if (!strncasecmp(str, "plt", 3 * sizeof(char))) {
+    } else if (STR_NMATCH(str, "playlist", 8)) {
+        ret->column_name = NULL;
         ret->type = uri_playlist;
         val = str + 3;
-    } else if (!strncasecmp(str, "type", 4 * sizeof(char))) {
+    } else if (STR_NMATCH(str, "type", 4)) {
+        ret->column_name = LKT_DB_TYPE;
         ret->type = uri_type;
         val = str + 4;
-    } else if (!strncasecmp(str, "author", 6 * sizeof(char))) {
+    } else if (STR_NMATCH(str, "author", 6)) {
+        ret->column_name = LKT_DB_AUTHOR;
         ret->type = uri_author;
         val = str + 6;
-    } else if (!strncasecmp(str, "category", 8 * sizeof(char))) {
+    } else if (STR_NMATCH(str, "category", 8)) {
+        ret->column_name = LKT_DB_CAT;
         ret->type = uri_category;
         val = str + 8;
-    } else if (!strncasecmp(str, "lang", 4 * sizeof(char))) {
+    } else if (STR_NMATCH(str, "lang", 4)) {
+        ret->column_name = LKT_DB_LANG;
         ret->type = uri_language;
         val = str + 4;
-    } else if (!strncasecmp(str, "query", 5 * sizeof(char))) {
+    } else if (STR_NMATCH(str, "query", 5)) {
+        ret->column_name = LKT_DB_ALL;
         ret->type = uri_query;
         val = str + 5;
     } else
-        return false;
+        return NULL;
 
-    // No place for the '://' separator
+    /* No place for the '://' separator */
     if (len - (val - str) < 3)
-        return false;
+        return NULL;
 
     val += 3;
+    ret->value = val;
+    return val;
+}
 
-    if (ret->type == uri_id) {
-        id = strtol(val, &endptr, 10);
+bool
+lkt_uri_from(struct lkt_uri *ret, char *const str)
+{
+    if (NULL == __prefix(str, ret))
+        return false;
 
-        if ((errno == ERANGE && (id == LONG_MAX || id == LONG_MIN))
-            || (errno != 0 && id == 0) || endptr == str)
+    if (ret->type == uri_id) {
+        errno = 0;
+        ret->id = strtol(ret->value, NULL, 10);  /* Override */
+        if (errno != 0)
             return false;
+    }
 
-        ret->value = calloc(1, sizeof(int));
+    ret->_allocated = false;
+    return true;
+}
 
-        if (ret->value == NULL)
-            return false;
+bool
+lkt_uri_from_list(struct lkt_uri *ret, char **list)
+{
+    int i;
+    if (!list || !list[0])
+        return false;
 
-        *(int *) ret->value = id;
-        ret->_allocated = true;
-    }
+    if (!list[1])
+        return lkt_uri_from(ret, list[0]);
+
+    if (NULL == __prefix(list[0], ret))
+        return false;
+
+    /* Nothing after the separator */
+    if ('\0' == * (char *) ret->value)
+        return false;
+
+    char *buffer = (char *) malloc(sizeof(char) * (strlen(ret->value) + 1));
+    if (NULL == buffer)
+        return false;
+    strcpy(buffer, ret->value);
+
+    for (i = 1; list[i]; ++i) {
+        char *new = realloc(buffer, strlen(buffer) + 1 + strlen(list[i]) + 1);
+        if (NULL == new) {
+            free(buffer);
+            return false;
+        }
 
-    else {
-        ret->_allocated = false;
-        ret->value = val;
+        buffer = new;
+        strcat(buffer, " ");
+        strcat(buffer, list[i]);
     }
 
+    ret->value = buffer;
+    ret->_allocated = true;
     return true;
 }
 
@@ -113,8 +149,8 @@ __lkt_to_str(struct lkt_uri *uri, char *ret, size_t len)
         safe_snprintf(ret, len, "search=%s", (char *) uri->value);
         break;
     default:
-        LOG_ERROR("URI", "type %d may not be supported by kurisu, "
-                  "generate an error", uri->type);
+        LOG_WARN("URI", "type %d may not be supported by kurisu, "
+                 "generate an error", uri->type);
         return 1;
     }