+48
-42
@@ -1,6 +1,3 @@
|
||||
#include <vamp_zero/database.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
@@ -16,16 +13,19 @@
|
||||
|
||||
#include <noto.hpp>
|
||||
|
||||
#include <vamp_zero/database.hpp>
|
||||
#include <vamp_zero/errors.hpp>
|
||||
#include <vamp_zero/library.hpp>
|
||||
#include <vamp_zero/score.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto application_id = std::int64_t{0x56414D50};
|
||||
|
||||
auto connection_config(vamp_zero::database_config const& config) -> noto::connection_config {
|
||||
auto result = config.path == ":memory:" ? noto::memory_connection_config("product-vamp") : noto::file_connection_config(config.path, "product-vamp");
|
||||
auto connection_config(vamp_zero::database_config const &config) -> noto::connection_config {
|
||||
auto result = config.path == ":memory:" ? noto::memory_connection_config("product-vamp")
|
||||
: noto::file_connection_config(config.path, "product-vamp");
|
||||
result.busy_timeout = std::chrono::seconds{5};
|
||||
result.foreign_keys = true;
|
||||
result.pragmas = {
|
||||
@@ -35,12 +35,12 @@ auto connection_config(vamp_zero::database_config const& config) -> noto::connec
|
||||
return result;
|
||||
}
|
||||
|
||||
auto text_value(noto::sqlite_value const& value) -> std::string {
|
||||
auto text_value(noto::sqlite_value const &value) -> std::string {
|
||||
auto text = std::get_if<std::string>(&value.value);
|
||||
return text == nullptr ? std::string{} : *text;
|
||||
}
|
||||
|
||||
auto execute_schema_statement(noto::connection& connection, std::string statement) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto execute_schema_statement(noto::connection &connection, std::string statement) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto result = co_await connection.async_execute(std::move(statement));
|
||||
if (!result) {
|
||||
co_return std::unexpected(result.error());
|
||||
@@ -48,12 +48,12 @@ auto execute_schema_statement(noto::connection& connection, std::string statemen
|
||||
co_return std::expected<void, modulus::error>{};
|
||||
}
|
||||
|
||||
auto column_exists(noto::connection& connection, std::string table, std::string column) -> boost::asio::awaitable<std::expected<std::bool_t, modulus::error>> {
|
||||
auto column_exists(noto::connection &connection, std::string table, std::string column) -> boost::asio::awaitable<std::expected<std::bool_t, modulus::error>> {
|
||||
auto selected = co_await connection.async_execute("PRAGMA table_info(" + table + ")");
|
||||
if (!selected) {
|
||||
co_return std::unexpected(selected.error());
|
||||
}
|
||||
for (auto const& row : selected->rows) {
|
||||
for (auto const &row : selected->rows) {
|
||||
if (row.size() >= 2U && text_value(row[1]) == column) {
|
||||
co_return true;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ auto column_exists(noto::connection& connection, std::string table, std::string
|
||||
co_return false;
|
||||
}
|
||||
|
||||
auto ensure_column(noto::connection& connection, std::string table, std::string column, std::string alter_statement) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto ensure_column(noto::connection &connection, std::string table, std::string column, std::string alter_statement) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto exists = co_await column_exists(connection, std::move(table), std::move(column));
|
||||
if (!exists) {
|
||||
co_return std::unexpected(exists.error());
|
||||
@@ -72,12 +72,11 @@ auto ensure_column(noto::connection& connection, std::string table, std::string
|
||||
co_return co_await execute_schema_statement(connection, std::move(alter_statement));
|
||||
}
|
||||
|
||||
auto ensure_current_schema(noto::connection& connection) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto ensure_current_schema(noto::connection &connection) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto begin = co_await connection.async_execute("BEGIN IMMEDIATE");
|
||||
if (!begin) {
|
||||
co_return std::unexpected(begin.error());
|
||||
}
|
||||
|
||||
auto ensured = std::expected<void, modulus::error>{};
|
||||
auto schema_statements = std::vector<std::string>{
|
||||
R"sql(
|
||||
@@ -121,16 +120,15 @@ CREATE TABLE IF NOT EXISTS practice_exercises (
|
||||
)sql",
|
||||
"DROP TABLE IF EXISTS _noto_schema_migrations",
|
||||
};
|
||||
|
||||
for (auto& statement : schema_statements) {
|
||||
for (auto &statement : schema_statements) {
|
||||
if (!ensured) {
|
||||
break;
|
||||
}
|
||||
ensured = co_await execute_schema_statement(connection, std::move(statement));
|
||||
}
|
||||
|
||||
if (ensured) {
|
||||
ensured = co_await ensure_column(connection, "songs", "part", "ALTER TABLE songs ADD COLUMN part TEXT NOT NULL DEFAULT ''");
|
||||
ensured =
|
||||
co_await ensure_column(connection, "songs", "part", "ALTER TABLE songs ADD COLUMN part TEXT NOT NULL DEFAULT ''");
|
||||
}
|
||||
if (ensured) {
|
||||
ensured = co_await ensure_column(connection, "songs", "source_path", "ALTER TABLE songs ADD COLUMN source_path TEXT NOT NULL DEFAULT ''");
|
||||
@@ -142,39 +140,43 @@ CREATE TABLE IF NOT EXISTS practice_exercises (
|
||||
ensured = co_await ensure_column(connection, "songs", "catalog_managed", "ALTER TABLE songs ADD COLUMN catalog_managed INTEGER NOT NULL DEFAULT 1 CHECK(catalog_managed IN (0, 1))");
|
||||
}
|
||||
if (ensured) {
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "practice_type_id", "ALTER TABLE practice_exercises ADD COLUMN practice_type_id TEXT NOT NULL DEFAULT 'general' CHECK(length(trim(practice_type_id)) BETWEEN 1 AND 120)");
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "practice_type_id", "ALTER TABLE practice_exercises ADD COLUMN practice_type_id TEXT NOT NULL DEFAULT 'general' "
|
||||
"CHECK(length(trim(practice_type_id)) BETWEEN 1 AND 120)");
|
||||
}
|
||||
if (ensured) {
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "practice_type_title", "ALTER TABLE practice_exercises ADD COLUMN practice_type_title TEXT NOT NULL DEFAULT 'General Practice' CHECK(length(trim(practice_type_title)) BETWEEN 1 AND 200)");
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "practice_type_title", "ALTER TABLE practice_exercises ADD COLUMN practice_type_title TEXT NOT NULL DEFAULT 'General Practice' "
|
||||
"CHECK(length(trim(practice_type_title)) BETWEEN 1 AND 200)");
|
||||
}
|
||||
if (ensured) {
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "lesson_group_id", "ALTER TABLE practice_exercises ADD COLUMN lesson_group_id TEXT NOT NULL DEFAULT 'general' CHECK(length(trim(lesson_group_id)) BETWEEN 1 AND 120)");
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "lesson_group_id", "ALTER TABLE practice_exercises ADD COLUMN lesson_group_id TEXT NOT NULL DEFAULT 'general' "
|
||||
"CHECK(length(trim(lesson_group_id)) BETWEEN 1 AND 120)");
|
||||
}
|
||||
if (ensured) {
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "lesson_group_title", "ALTER TABLE practice_exercises ADD COLUMN lesson_group_title TEXT NOT NULL DEFAULT 'General' CHECK(length(trim(lesson_group_title)) BETWEEN 1 AND 200)");
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "lesson_group_title", "ALTER TABLE practice_exercises ADD COLUMN lesson_group_title TEXT NOT NULL DEFAULT 'General' "
|
||||
"CHECK(length(trim(lesson_group_title)) BETWEEN 1 AND 200)");
|
||||
}
|
||||
if (ensured) {
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "source_path", "ALTER TABLE practice_exercises ADD COLUMN source_path TEXT NOT NULL DEFAULT ''");
|
||||
}
|
||||
if (ensured) {
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "catalog_managed", "ALTER TABLE practice_exercises ADD COLUMN catalog_managed INTEGER NOT NULL DEFAULT 1 CHECK(catalog_managed IN (0, 1))");
|
||||
ensured = co_await ensure_column(connection, "practice_exercises", "catalog_managed", "ALTER TABLE practice_exercises ADD COLUMN catalog_managed INTEGER NOT NULL DEFAULT 1 CHECK(catalog_managed IN "
|
||||
"(0, 1))");
|
||||
}
|
||||
|
||||
auto index_statements = std::vector<std::string>{
|
||||
"CREATE INDEX IF NOT EXISTS idx_songs_title ON songs(title COLLATE NOCASE)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_songs_title_part ON songs(title COLLATE NOCASE, part COLLATE NOCASE)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_songs_catalog_managed ON songs(catalog_managed)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_practice_exercises_title ON practice_exercises(title COLLATE NOCASE)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_practice_exercises_hierarchy ON practice_exercises(practice_type_id COLLATE NOCASE, lesson_group_id COLLATE NOCASE, title COLLATE NOCASE)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_practice_exercises_hierarchy ON practice_exercises(practice_type_id COLLATE "
|
||||
"NOCASE, lesson_group_id COLLATE NOCASE, title COLLATE NOCASE)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_practice_exercises_catalog_managed ON practice_exercises(catalog_managed)",
|
||||
};
|
||||
for (auto& statement : index_statements) {
|
||||
for (auto &statement : index_statements) {
|
||||
if (!ensured) {
|
||||
break;
|
||||
}
|
||||
ensured = co_await execute_schema_statement(connection, std::move(statement));
|
||||
}
|
||||
|
||||
if (ensured) {
|
||||
auto committed = co_await connection.async_execute("COMMIT");
|
||||
if (!committed) {
|
||||
@@ -183,7 +185,6 @@ CREATE TABLE IF NOT EXISTS practice_exercises (
|
||||
} else {
|
||||
static_cast<void>(co_await connection.async_execute("ROLLBACK"));
|
||||
}
|
||||
|
||||
if (!ensured) {
|
||||
co_return std::unexpected(ensured.error());
|
||||
}
|
||||
@@ -194,15 +195,15 @@ CREATE TABLE IF NOT EXISTS practice_exercises (
|
||||
co_return std::expected<void, modulus::error>{};
|
||||
}
|
||||
|
||||
auto path_parts(std::filesystem::path const& path) -> std::vector<std::string> {
|
||||
auto path_parts(std::filesystem::path const &path) -> std::vector<std::string> {
|
||||
auto parts = std::vector<std::string>{};
|
||||
for (auto const& part : path) {
|
||||
for (auto const &part : path) {
|
||||
parts.push_back(part.string());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
auto logical_practice_exercise_id(std::string const& type_id, std::string const& lesson_id, std::string const& source_path, std::string const& fallback) -> std::string {
|
||||
auto logical_practice_exercise_id(std::string const &type_id, std::string const &lesson_id, std::string const &source_path, std::string const &fallback) -> std::string {
|
||||
auto parts = path_parts(std::filesystem::path{source_path});
|
||||
for (auto index = std::size_t{}; index + 3U < parts.size(); ++index) {
|
||||
if (parts[index] == "practice" && parts[index + 1U] == type_id && parts[index + 2U] == lesson_id) {
|
||||
@@ -217,10 +218,11 @@ auto logical_practice_exercise_id(std::string const& type_id, std::string const&
|
||||
}
|
||||
|
||||
auto now_ms() -> std::int64_t {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
auto execute_delete_stale_catalog_rows(noto::connection& connection, std::string_view table, std::vector<std::string> const& ids) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto execute_delete_stale_catalog_rows(noto::connection &connection, std::string_view table, std::vector<std::string> const &ids) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto sql = std::string{"DELETE FROM "};
|
||||
sql += table;
|
||||
sql += " WHERE catalog_managed = 1";
|
||||
@@ -261,7 +263,6 @@ auto vamp_zero::async_ensure_database(boost::asio::any_io_executor executor, vam
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto connection = noto::connection{executor, connection_config(config)};
|
||||
auto connected = co_await connection.async_connect();
|
||||
if (!connected) {
|
||||
@@ -290,7 +291,6 @@ auto vamp_zero::async_ensure_database(boost::asio::any_io_executor executor, vam
|
||||
co_return std::unexpected(set.error());
|
||||
}
|
||||
}
|
||||
|
||||
auto ensured = co_await ensure_current_schema(connection);
|
||||
if (!ensured) {
|
||||
auto close = co_await connection.async_close();
|
||||
@@ -324,7 +324,7 @@ auto vamp_zero::async_import_catalogs(boost::asio::any_io_executor executor, vam
|
||||
auto timestamp = now_ms();
|
||||
auto song_ids = std::vector<std::string>{};
|
||||
song_ids.reserve(performance->parts.size());
|
||||
for (auto const& part : performance->parts) {
|
||||
for (auto const &part : performance->parts) {
|
||||
if (!imported) {
|
||||
break;
|
||||
}
|
||||
@@ -370,11 +370,11 @@ WHERE songs.catalog_managed = 1
|
||||
}
|
||||
}
|
||||
auto practice_ids = std::vector<std::string>{};
|
||||
for (auto const& exercise : practice->exercises) {
|
||||
for (auto const &exercise : practice->exercises) {
|
||||
if (!imported) {
|
||||
break;
|
||||
}
|
||||
for (auto const& variant : exercise.variants) {
|
||||
for (auto const &variant : exercise.variants) {
|
||||
practice_ids.push_back(variant.id);
|
||||
auto params = std::vector<noto::sqlite_value>{
|
||||
noto::sqlite_value{variant.id},
|
||||
@@ -457,13 +457,16 @@ WHERE practice_exercises.catalog_managed = 1
|
||||
co_return co_await connection.async_close();
|
||||
}
|
||||
|
||||
auto vamp_zero::async_load_performance_library(boost::asio::any_io_executor executor, vamp_zero::database_config config) -> boost::asio::awaitable<std::expected<vamp_zero::library_state, modulus::error>> {
|
||||
auto vamp_zero::async_load_performance_library(boost::asio::any_io_executor executor, vamp_zero::database_config config)
|
||||
-> boost::asio::awaitable<std::expected<vamp_zero::library_state, modulus::error>> {
|
||||
auto connection = noto::connection{executor, connection_config(config)};
|
||||
auto connected = co_await connection.async_connect();
|
||||
if (!connected) {
|
||||
co_return std::unexpected(connected.error());
|
||||
}
|
||||
auto selected = co_await connection.async_execute("SELECT id, title, composer, part, source_path, source_json, key_signature FROM songs ORDER BY title COLLATE NOCASE, part COLLATE NOCASE");
|
||||
auto selected =
|
||||
co_await connection.async_execute("SELECT id, title, composer, part, source_path, source_json, key_signature FROM "
|
||||
"songs ORDER BY title COLLATE NOCASE, part COLLATE NOCASE");
|
||||
if (!selected) {
|
||||
auto close = co_await connection.async_close();
|
||||
if (!close) {
|
||||
@@ -472,7 +475,7 @@ auto vamp_zero::async_load_performance_library(boost::asio::any_io_executor exec
|
||||
co_return std::unexpected(selected.error());
|
||||
}
|
||||
auto parts = std::vector<vamp_zero::library_part>{};
|
||||
for (auto const& row : selected->rows) {
|
||||
for (auto const &row : selected->rows) {
|
||||
if (row.size() < 7U) {
|
||||
continue;
|
||||
}
|
||||
@@ -504,13 +507,16 @@ auto vamp_zero::async_load_performance_library(boost::asio::any_io_executor exec
|
||||
co_return vamp_zero::group_library_parts(std::move(parts));
|
||||
}
|
||||
|
||||
auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executor, vamp_zero::database_config config) -> boost::asio::awaitable<std::expected<vamp_zero::practice_library, modulus::error>> {
|
||||
auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executor, vamp_zero::database_config config)
|
||||
-> boost::asio::awaitable<std::expected<vamp_zero::practice_library, modulus::error>> {
|
||||
auto connection = noto::connection{executor, connection_config(config)};
|
||||
auto connected = co_await connection.async_connect();
|
||||
if (!connected) {
|
||||
co_return std::unexpected(connected.error());
|
||||
}
|
||||
auto selected = co_await connection.async_execute("SELECT id, practice_type_id, practice_type_title, lesson_group_id, lesson_group_title, title, composer, key_signature, source_path, source_json FROM practice_exercises ORDER BY practice_type_id COLLATE NOCASE, lesson_group_id COLLATE NOCASE, source_path COLLATE NOCASE, title COLLATE NOCASE, id COLLATE NOCASE");
|
||||
auto selected = co_await connection.async_execute("SELECT id, practice_type_id, practice_type_title, lesson_group_id, lesson_group_title, title, composer, "
|
||||
"key_signature, source_path, source_json FROM practice_exercises ORDER BY practice_type_id COLLATE NOCASE, "
|
||||
"lesson_group_id COLLATE NOCASE, source_path COLLATE NOCASE, title COLLATE NOCASE, id COLLATE NOCASE");
|
||||
if (!selected) {
|
||||
auto close = co_await connection.async_close();
|
||||
if (!close) {
|
||||
@@ -519,7 +525,7 @@ auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executo
|
||||
co_return std::unexpected(selected.error());
|
||||
}
|
||||
auto exercises = std::vector<vamp_zero::practice_exercise>{};
|
||||
for (auto const& row : selected->rows) {
|
||||
for (auto const &row : selected->rows) {
|
||||
if (row.size() < 10U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user