560 lines
14 KiB
C++
560 lines
14 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// Copyright The Music Player Daemon Project
|
|
|
|
#include "Database.hxx"
|
|
#include "Sticker.hxx"
|
|
#include "lib/sqlite/Util.hxx"
|
|
#include "fs/Path.hxx"
|
|
#include "fs/NarrowPath.hxx"
|
|
#include "Idle.hxx"
|
|
#include "util/StringCompare.hxx"
|
|
#include "util/ScopeExit.hxx"
|
|
|
|
#include <fmt/format.h>
|
|
#include <cassert>
|
|
#include <iterator>
|
|
#include <array>
|
|
#include <stdexcept>
|
|
|
|
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_CONTAINS,
|
|
STICKER_SQL_FIND_STARTS_WITH,
|
|
|
|
STICKER_SQL_FIND_COUNT
|
|
};
|
|
|
|
enum sticker_sql {
|
|
STICKER_SQL_GET,
|
|
STICKER_SQL_LIST,
|
|
STICKER_SQL_SET,
|
|
STICKER_SQL_DELETE,
|
|
STICKER_SQL_DELETE_VALUE,
|
|
STICKER_SQL_DISTINCT_TYPE_URI,
|
|
STICKER_SQL_TRANSACTION_BEGIN,
|
|
STICKER_SQL_TRANSACTION_COMMIT,
|
|
STICKER_SQL_TRANSACTION_ROLLBACK,
|
|
STICKER_SQL_NAMES,
|
|
STICKER_SQL_NAMES_TYPES,
|
|
STICKER_SQL_NAMES_TYPES_BY_TYPE,
|
|
STICKER_SQL_INC,
|
|
STICKER_SQL_DEC,
|
|
|
|
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)>?",
|
|
|
|
//[STICKER_SQL_FIND_CONTAINS] =
|
|
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value LIKE ('%' || ? || '%')",
|
|
|
|
//[STICKER_SQL_FIND_STARTS_WITH] =
|
|
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value LIKE (? || '%')",
|
|
};
|
|
|
|
static constexpr auto sticker_sql = std::array {
|
|
//[STICKER_SQL_GET] =
|
|
"SELECT value FROM sticker WHERE type=? AND uri=? AND name=?",
|
|
//[STICKER_SQL_LIST] =
|
|
"SELECT name,value FROM sticker WHERE type=? AND uri=?",
|
|
//[STICKER_SQL_SET] =
|
|
"INSERT INTO sticker(type, uri, name, value) VALUES(?, ?, ?, ?) "
|
|
"ON CONFLICT(type, uri, name) DO "
|
|
"UPDATE set value = ?",
|
|
//[STICKER_SQL_DELETE] =
|
|
"DELETE FROM sticker WHERE type=? AND uri=?",
|
|
//[STICKER_SQL_DELETE_VALUE] =
|
|
"DELETE FROM sticker WHERE type=? AND uri=? AND name=?",
|
|
|
|
//[STICKER_SQL_DISTINCT_TYPE_URI] =
|
|
"SELECT DISTINCT type,uri FROM sticker",
|
|
|
|
//[STICKER_SQL_TRANSACTION_BEGIN]
|
|
"BEGIN",
|
|
|
|
//[STICKER_SQL_TRANSACTION_COMMIT]
|
|
"COMMIT",
|
|
|
|
//[STICKER_SQL_TRANSACTION_ROLLBACK]
|
|
"ROLLBACK",
|
|
|
|
//[STICKER_SQL_NAMES]
|
|
"SELECT DISTINCT name FROM sticker ORDER BY name",
|
|
|
|
//[STICKER_SQL_NAMES_TYPES]
|
|
"SELECT name,type FROM sticker GROUP BY name,type ORDER BY name",
|
|
|
|
//[STICKER_SQL_NAMES_TYPES_BY_TYPE]
|
|
"SELECT name,type FROM sticker WHERE type = ? GROUP BY name,type ORDER BY name",
|
|
|
|
//[STICKER_SQL_INC] =
|
|
"INSERT INTO sticker (type, uri, name, value) VALUES (?, ?, ?, ?) "
|
|
"ON CONFLICT(type, uri, name) DO "
|
|
"UPDATE set value = value + ?",
|
|
|
|
//[STICKER_SQL_DEC] =
|
|
"INSERT INTO sticker (type, uri, name, value) VALUES (?, ?, ?, ?) "
|
|
"ON CONFLICT(type, uri, name) DO "
|
|
"UPDATE set value = value - ?",
|
|
};
|
|
|
|
static constexpr const char sticker_sql_create[] =
|
|
"CREATE TABLE IF NOT EXISTS sticker("
|
|
" type VARCHAR NOT NULL, "
|
|
" uri VARCHAR NOT NULL, "
|
|
" name VARCHAR NOT NULL, "
|
|
" value VARCHAR NOT NULL"
|
|
");"
|
|
"CREATE UNIQUE INDEX IF NOT EXISTS"
|
|
" sticker_value ON sticker(type, uri, name);"
|
|
"";
|
|
|
|
StickerDatabase::StickerDatabase(const char *_path)
|
|
:path(_path),
|
|
db(path.c_str())
|
|
{
|
|
int ret;
|
|
|
|
/* create the table and index */
|
|
|
|
ret = sqlite3_exec(db, sticker_sql_create,
|
|
nullptr, nullptr, nullptr);
|
|
if (ret != SQLITE_OK)
|
|
throw SqliteError(db, ret,
|
|
"Failed to create sticker table");
|
|
|
|
/* prepare the statements we're going to use */
|
|
|
|
for (size_t i = 0; i < sticker_sql.size(); ++i) {
|
|
assert(sticker_sql[i] != nullptr);
|
|
|
|
stmt[i] = Prepare(db, sticker_sql[i]);
|
|
}
|
|
}
|
|
|
|
StickerDatabase::StickerDatabase(Path _path)
|
|
:StickerDatabase(NarrowPath{_path}) {}
|
|
|
|
StickerDatabase::~StickerDatabase() noexcept
|
|
{
|
|
if (db == nullptr)
|
|
return;
|
|
|
|
for (const auto &sticker : stmt) {
|
|
assert(sticker != nullptr);
|
|
|
|
sqlite3_finalize(sticker);
|
|
}
|
|
}
|
|
|
|
std::string
|
|
StickerDatabase::LoadValue(const char *type, const char *uri, const char *name)
|
|
{
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_GET];
|
|
|
|
assert(type != nullptr);
|
|
assert(uri != nullptr);
|
|
assert(name != nullptr);
|
|
|
|
if (StringIsEmpty(name))
|
|
return {};
|
|
|
|
BindAll(s, type, uri, name);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
sqlite3_clear_bindings(s);
|
|
};
|
|
|
|
std::string value;
|
|
if (ExecuteRow(s))
|
|
value = (const char*)sqlite3_column_text(s, 0);
|
|
|
|
return value;
|
|
}
|
|
|
|
void
|
|
StickerDatabase::ListValues(std::map<std::string, std::string, std::less<>> &table,
|
|
const char *type, const char *uri)
|
|
{
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_LIST];
|
|
|
|
assert(type != nullptr);
|
|
assert(uri != nullptr);
|
|
|
|
BindAll(s, type, uri);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
sqlite3_clear_bindings(s);
|
|
};
|
|
|
|
ExecuteForEach(s, [s, &table](){
|
|
const char *name = (const char *)sqlite3_column_text(s, 0);
|
|
const char *value = (const char *)sqlite3_column_text(s, 1);
|
|
table.emplace(name, value);
|
|
});
|
|
}
|
|
|
|
void
|
|
StickerDatabase::StoreValue(const char *type, const char *uri,
|
|
const char *name, const char *value)
|
|
{
|
|
assert(type != nullptr);
|
|
assert(uri != nullptr);
|
|
assert(name != nullptr);
|
|
assert(value != nullptr);
|
|
|
|
if (StringIsEmpty(name))
|
|
return;
|
|
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_SET];
|
|
|
|
BindAll(s, type, uri, name, value, value);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
sqlite3_clear_bindings(s);
|
|
};
|
|
|
|
ExecuteCommand(s);
|
|
idle_add(IDLE_STICKER);
|
|
}
|
|
|
|
void
|
|
StickerDatabase::IncValue(const char *type, const char *uri,
|
|
const char *name, const char *value)
|
|
{
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_INC];
|
|
|
|
assert(type != nullptr);
|
|
assert(uri != nullptr);
|
|
assert(name != nullptr);
|
|
assert(*name != 0);
|
|
assert(value != nullptr);
|
|
|
|
BindAll(s, type, uri, name, value, value);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
sqlite3_clear_bindings(s);
|
|
};
|
|
|
|
ExecuteCommand(s);
|
|
idle_add(IDLE_STICKER);
|
|
}
|
|
|
|
void
|
|
StickerDatabase::DecValue(const char *type, const char *uri,
|
|
const char *name, const char *value)
|
|
{
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_DEC];
|
|
|
|
assert(type != nullptr);
|
|
assert(uri != nullptr);
|
|
assert(name != nullptr);
|
|
assert(*name != 0);
|
|
assert(value != nullptr);
|
|
|
|
BindAll(s, type, uri, name, value, value);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
sqlite3_clear_bindings(s);
|
|
};
|
|
|
|
ExecuteCommand(s);
|
|
idle_add(IDLE_STICKER);
|
|
}
|
|
|
|
bool
|
|
StickerDatabase::Delete(const char *type, const char *uri)
|
|
{
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_DELETE];
|
|
|
|
assert(type != nullptr);
|
|
assert(uri != nullptr);
|
|
|
|
BindAll(s, type, uri);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
sqlite3_clear_bindings(s);
|
|
};
|
|
|
|
bool modified = ExecuteModified(s);
|
|
if (modified)
|
|
idle_add(IDLE_STICKER);
|
|
return modified;
|
|
}
|
|
|
|
bool
|
|
StickerDatabase::DeleteValue(const char *type, const char *uri,
|
|
const char *name)
|
|
{
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_DELETE_VALUE];
|
|
|
|
assert(type != nullptr);
|
|
assert(uri != nullptr);
|
|
|
|
BindAll(s, type, uri, name);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
sqlite3_clear_bindings(s);
|
|
};
|
|
|
|
bool modified = ExecuteModified(s);
|
|
if (modified)
|
|
idle_add(IDLE_STICKER);
|
|
return modified;
|
|
}
|
|
|
|
Sticker
|
|
StickerDatabase::Load(const char *type, const char *uri)
|
|
{
|
|
Sticker s;
|
|
|
|
ListValues(s.table, type, uri);
|
|
|
|
return s;
|
|
}
|
|
|
|
sqlite3_stmt *
|
|
StickerDatabase::BindFind(const char *type, const char *base_uri,
|
|
const char *name,
|
|
StickerOperator op, const char *value,
|
|
const char *sort, bool descending, RangeArg window)
|
|
{
|
|
assert(type != nullptr);
|
|
assert(name != nullptr);
|
|
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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;
|
|
|
|
case StickerOperator::CONTAINS:
|
|
sql_str = fmt::format("{} {} {}",
|
|
sticker_sql_find[STICKER_SQL_FIND_CONTAINS], order_by, offset);
|
|
sql = Prepare(db, sql_str.c_str());
|
|
BindAll(sql, type, base_uri, name, value);
|
|
return sql;
|
|
|
|
case StickerOperator::STARTS_WITH:
|
|
sql_str = fmt::format("{} {} {}",
|
|
sticker_sql_find[STICKER_SQL_FIND_STARTS_WITH], order_by, offset);
|
|
sql = Prepare(db, sql_str.c_str());
|
|
BindAll(sql, type, base_uri, name, value);
|
|
return sql;
|
|
}
|
|
|
|
assert(false);
|
|
gcc_unreachable();
|
|
}
|
|
|
|
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, sort, descending, window);
|
|
assert(s != nullptr);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_finalize(s);
|
|
};
|
|
|
|
ExecuteForEach(s, [s, func, user_data](){
|
|
func((const char*)sqlite3_column_text(s, 0),
|
|
(const char*)sqlite3_column_text(s, 1),
|
|
user_data);
|
|
});
|
|
}
|
|
|
|
std::list<StickerDatabase::StickerTypeUriPair>
|
|
StickerDatabase::GetUniqueStickers()
|
|
{
|
|
auto result = std::list<StickerTypeUriPair>{};
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_DISTINCT_TYPE_URI];
|
|
assert(s != nullptr);
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
};
|
|
ExecuteForEach(s, [&s, &result]() {
|
|
result.emplace_back((const char*)sqlite3_column_text(s, 0),
|
|
(const char*)sqlite3_column_text(s, 1));
|
|
});
|
|
return result;
|
|
}
|
|
|
|
void
|
|
StickerDatabase::Names(void (*func)(const char *value, void *user_data), void *user_data)
|
|
{
|
|
assert(func != nullptr);
|
|
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_NAMES];
|
|
assert(s != nullptr);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
};
|
|
|
|
ExecuteForEach(s, [s, func, user_data](){
|
|
func((const char*)sqlite3_column_text(s, 0), user_data);
|
|
});
|
|
}
|
|
|
|
void
|
|
StickerDatabase::NamesTypes(const char *type, void (*func)(const char *value, const char *type, void *user_data), void *user_data)
|
|
{
|
|
assert(func != nullptr);
|
|
|
|
sqlite3_stmt *const s = type == nullptr
|
|
? stmt[STICKER_SQL_NAMES_TYPES]
|
|
: stmt[STICKER_SQL_NAMES_TYPES_BY_TYPE];
|
|
assert(s != nullptr);
|
|
|
|
if (type != nullptr)
|
|
BindAll(s, type);
|
|
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
};
|
|
|
|
ExecuteForEach(s, [s, func, user_data](){
|
|
func((const char*)sqlite3_column_text(s, 0),
|
|
(const char*)sqlite3_column_text(s, 1), user_data);
|
|
});
|
|
}
|
|
|
|
void
|
|
StickerDatabase::BatchDeleteNoIdle(const std::list<StickerTypeUriPair> &stickers)
|
|
{
|
|
sqlite3_stmt *const s = stmt[STICKER_SQL_DELETE];
|
|
|
|
sqlite3_stmt *const begin = stmt[STICKER_SQL_TRANSACTION_BEGIN];
|
|
sqlite3_stmt *const rollback = stmt[STICKER_SQL_TRANSACTION_ROLLBACK];
|
|
sqlite3_stmt *const commit = stmt[STICKER_SQL_TRANSACTION_COMMIT];
|
|
|
|
try {
|
|
ExecuteBusy(begin);
|
|
|
|
for (auto &sticker: stickers) {
|
|
AtScopeExit(s) {
|
|
sqlite3_reset(s);
|
|
sqlite3_clear_bindings(s);
|
|
};
|
|
|
|
BindAll(s, sticker.first.c_str(), sticker.second.c_str());
|
|
|
|
ExecuteCommand(s);
|
|
}
|
|
|
|
ExecuteBusy(commit);
|
|
} catch (...) {
|
|
// "If the transaction has already been rolled back automatically by the error response,
|
|
// then the ROLLBACK command will fail with an error, but no harm is caused by this."
|
|
ExecuteBusy(rollback);
|
|
std::throw_with_nested(std::runtime_error{"failed to batch-delete stickers"});
|
|
}
|
|
}
|