#include "pke-at-storage-sql.hpp" #include "pke-at-settings.hpp" #include "sql/schema_000_000_sql.h" #include "sql/section_delete_sql.h" #include "sql/section_get_sql.h" #include "sql/section_upsert_sql.h" #include "sql/setlist_delete_sql.h" #include "sql/setlist_get_sql.h" #include "sql/setlist_upsert_sql.h" #include "sql/setlist_song_delete_sql.h" #include "sql/setlist_song_get_sql.h" #include "sql/setlist_song_upsert_sql.h" #include "sql/song_delete_sql.h" #include "sql/song_get_sql.h" #include "sql/song_upsert_sql.h" #include "pke/asset-manager.hpp" #include "pke/asset-types.h" #include "pke/pk.h" #include #define PASIS_RES(R) { .result_code = pke_at_storage_interface_result_code(R) } #define PASIS_REST(R,V) { .result_code = pke_at_storage_interface_result_code(R), .value = V } constexpr unsigned long query_text_length = 1024; static const char *SQLITE_DB_PATH = "pke-at.sqlite3"; static struct pke_at_storage_sql_master { char query_text[query_text_length]; } sql_mstr; void pke_at_storage_sql::init() const { sqlite3 *db = nullptr; char *sql_err_string = nullptr; int res, null_count; AssetKey asset_key; AssetHandle asset_handle; struct db_version { unsigned char maj = -1; unsigned char min = -1; } version; const Asset *asset = nullptr; db_version check_version; // register assets AM_Register_Static(AssetKey{"sql_scm_000_000"}, PKE_ASSET_TYPE_UNSET, src_sql_schema_000_000_sql, src_sql_schema_000_000_sql_len, nullptr); // check if db can be opened res = sqlite3_open(SQLITE_DB_PATH, &db); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::init] Failed to open database, sqlite err: %i, %s\n", res, sqlite3_errmsg(db)); goto cleanup; } // check db version table exists { const char *query = "SELECT name from sqlite_schema WHERE type='table' AND name='db_version'"; auto fn = [](void *user_data, int column_count, char **argv, char **colname) -> int { (void)argv; (void)colname; db_version *version = reinterpret_cast(user_data); if (column_count != 0) { version->maj = 0; } return SQLITE_OK; }; res = sqlite3_exec(db, query, fn, &version, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::init] Failed to query db schema, sqlite err: %i, %s\n", res, sql_err_string); goto cleanup; } } // check db version if (version.maj == 0) { const char *query = "SELECT version_maj,version_min from [db_version] WHERE id='0'"; auto fn = [](void *user_data, int column_count, char **argv, char **colname) -> int { (void)colname; PK_STN_RES stn_res; db_version *version = reinterpret_cast(user_data); for (int i = 0; i < column_count; ++i) { if (i == 0) { stn_res = pk_stn(&version->maj, argv[i], nullptr); if (stn_res != PK_STN_RES_SUCCESS) { fprintf(stderr, "[pke_at_storage_sql::init] err parsing database version maj.\n"); return SQLITE_ERROR; } } if (i == 1) { stn_res = pk_stn(&version->min, argv[i], nullptr); if (stn_res != PK_STN_RES_SUCCESS) { fprintf(stderr, "[pke_at_storage_sql::init] err parsing database version min.\n"); return SQLITE_ERROR; } } } return SQLITE_OK; }; res = sqlite3_exec(db, query, fn, &version, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::init] Failed to query [db_version], sqlite err: %i, %s\n", res, sql_err_string); goto cleanup; } } // dynamically update db null_count = 0; check_version = version; check_version.min += 1; while (true) { snprintf(asset_key, AssetKeyLength, "sql_scm_%03i_%03i", check_version.maj, check_version.min); asset_handle = AM_GetHandle(asset_key); if (asset_handle == AssetHandle_MAX) { null_count += 1; check_version.maj += 1; check_version.min = 0; if (null_count > 1) break; continue; } asset = AM_Get(asset_handle); null_count = 0; auto fn = [](void *user_data, int column_count, char **argv, char **colname) -> int { (void)user_data; (void)column_count; (void)argv; (void)colname; return SQLITE_OK; }; res = sqlite3_exec(db, (char*)asset->ptr, fn, nullptr, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::init] failed updating database at step %03i_%03i: %i, %s\n", check_version.maj, check_version.min, res, sql_err_string); goto cleanup; } check_version.min += 1; } cleanup: if (db != nullptr) sqlite3_close(db); fflush(stderr); if (res != SQLITE_OK) exit(1); } void pke_at_storage_sql::teardown() const { /* TODO */ } pke_at_storage_interface_response pke_at_storage_sql::pke_at_storage_interface_section_delete(pk_uuid uuid) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { snprintf(sql_mstr.query_text, query_text_length, "%.*s WHERE uuid='" pk_uuid_printf_format "'", src_sql_section_delete_sql_len, src_sql_section_delete_sql, pk_uuid_printf_var(uuid)); res = sqlite3_exec(db, sql_mstr.query_text, nullptr, nullptr, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_section_delete] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_RES(ret); } pke_at_storage_interface_response_t pke_at_storage_sql::pke_at_storage_interface_section_get(pk_uuid uuid) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; pke_at_section_details *details = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { snprintf(sql_mstr.query_text, query_text_length, "%.*s WHERE uuid='" pk_uuid_printf_format "'", src_sql_section_get_sql_len, src_sql_section_get_sql, pk_uuid_printf_var(uuid)); auto fn = [](void *user_data, int column_count, char **argv, char **colname) -> int { int i; (void)colname; PK_STN_RES stn_res; auto **deets = reinterpret_cast(user_data); if (*deets != nullptr) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_section_get::fn] More than one result returned.\n"); return SQLITE_ERROR; } *deets = pk_new(g_at.mem.bkt_transient); auto *d = *deets; new (d) pke_at_section_details(); for (i = 0; i < column_count; ++i) { if (i == 0) { d->song_uuid << argv[i]; } if (i == 1) { stn_res = pk_stn(&d->sequence, argv[i], nullptr); if (stn_res != PK_STN_RES_SUCCESS) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_section_get::fn] sequence STN err: %i.\n", stn_res); return SQLITE_ERROR; } } if (i == 2) { stn_res = pk_stn(&d->section_type_index, argv[i], nullptr); if (stn_res != PK_STN_RES_SUCCESS) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_section_get::fn] section_type_index STN err: %i.\n", stn_res); return SQLITE_ERROR; } } if (i == 3) { stn_res = pk_stn(&d->beats_per_bar, argv[i], nullptr); if (stn_res != PK_STN_RES_SUCCESS) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_section_get::fn] beats_per_bar STN err: %i.\n", stn_res); return SQLITE_ERROR; } } if (i == 4) { stn_res = pk_stn(&d->bar_count, argv[i], nullptr); if (stn_res != PK_STN_RES_SUCCESS) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_section_get::fn] bar_count STN err: %i.\n", stn_res); return SQLITE_ERROR; } } } return SQLITE_OK; }; res = sqlite3_exec(db, sql_mstr.query_text, fn, &details, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_section_get] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_REST(ret, details); } pke_at_storage_interface_response pke_at_storage_sql::pke_at_storage_interface_section_upsert(const pke_at_section_details &val) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { char uuid[59]; snprintf(uuid, 59, pk_uuid_printf_format, pk_uuid_printf_var(val.song_uuid)); snprintf(sql_mstr.query_text, query_text_length, (char*)src_sql_section_upsert_sql, uuid, val.sequence, val.section_type_index, val.beats_per_bar, val.bar_count); res = sqlite3_exec(db, sql_mstr.query_text, nullptr, nullptr, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_section_upsert] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_RES(ret); } pke_at_storage_interface_response pke_at_storage_sql::pke_at_storage_interface_setlist_delete(pk_uuid uuid) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { snprintf(sql_mstr.query_text, query_text_length, "%.*s WHERE uuid='" pk_uuid_printf_format "'", src_sql_setlist_delete_sql_len, src_sql_setlist_delete_sql, pk_uuid_printf_var(uuid)); res = sqlite3_exec(db, sql_mstr.query_text, nullptr, nullptr, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_setlist_delete] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_RES(ret); } pke_at_storage_interface_response_t pke_at_storage_sql::pke_at_storage_interface_setlist_get(pk_uuid uuid) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; pke_at_setlist_details *details = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { snprintf(sql_mstr.query_text, query_text_length, "%.*s WHERE uuid='" pk_uuid_printf_format "'", src_sql_setlist_get_sql_len, src_sql_setlist_get_sql, pk_uuid_printf_var(uuid)); auto fn = [](void *user_data, int column_count, char **argv, char **colname) -> int { int i; (void)colname; auto **deets = reinterpret_cast(user_data); if (*deets != nullptr) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_setlist_get::fn] More than one result returned.\n"); return SQLITE_ERROR; } *deets = pk_new(g_at.mem.bkt_transient); auto *d = *deets; new (d) pke_at_setlist_details(); for (i = 0; i < column_count; ++i) { if (i == 0) { d->uuid << argv[i]; } if (i == 1) { d->title.length = strlen(argv[i]); d->title.reserved = d->title.length + 1; char *str = pk_new_arr(d->title.reserved, g_at.mem.bkt); strncpy(str, argv[i], d->title.length); d->title.val = str; } } return SQLITE_OK; }; res = sqlite3_exec(db, sql_mstr.query_text, fn, &details, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_setlist_get] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_REST(ret, details); } pke_at_storage_interface_response pke_at_storage_sql::pke_at_storage_interface_setlist_upsert(const pke_at_setlist_details &val) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { char uuid[59]; snprintf(uuid, 59, pk_uuid_printf_format, pk_uuid_printf_var(val.uuid)); snprintf(sql_mstr.query_text, query_text_length, (char*)src_sql_setlist_upsert_sql, uuid, val.title); res = sqlite3_exec(db, sql_mstr.query_text, nullptr, nullptr, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_setlist_upsert] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_RES(ret); } pke_at_storage_interface_response pke_at_storage_sql::pke_at_storage_interface_setlist_song_delete(pk_uuid setlist_uuid, pk_uuid song_uuid) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { snprintf(sql_mstr.query_text, query_text_length, "%.*s WHERE setlist_uuid='" pk_uuid_printf_format "' AND song_uuid='" pk_uuid_printf_format "'", src_sql_setlist_song_delete_sql_len, src_sql_setlist_song_delete_sql, pk_uuid_printf_var(setlist_uuid), pk_uuid_printf_var(song_uuid)); res = sqlite3_exec(db, sql_mstr.query_text, nullptr, nullptr, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_setlist_song_delete] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_RES(ret); } pke_at_storage_interface_response_t pke_at_storage_sql::pke_at_storage_interface_setlist_song_get(pk_uuid setlist_uuid, pk_uuid song_uuid) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; pke_at_setlist_song_details *details = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { snprintf(sql_mstr.query_text, query_text_length, "%.*s WHERE setlist_uuid='" pk_uuid_printf_format "' AND song_uuid='" pk_uuid_printf_format "'", src_sql_setlist_song_get_sql_len, src_sql_setlist_song_get_sql, pk_uuid_printf_var(setlist_uuid), pk_uuid_printf_var(song_uuid)); auto fn = [](void *user_data, int column_count, char **argv, char **colname) -> int { int i; (void)colname; auto **deets = reinterpret_cast(user_data); if (*deets != nullptr) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_setlist_song_get::fn] More than one result returned.\n"); return SQLITE_ERROR; } *deets = pk_new(g_at.mem.bkt_transient); auto *d = *deets; new (d) pke_at_setlist_song_details(); for (i = 0; i < column_count; ++i) { if (i == 0) { d->setlist_uuid << argv[i]; } if (i == 1) { d->song_uuid << argv[i]; } } return SQLITE_OK; }; res = sqlite3_exec(db, sql_mstr.query_text, fn, &details, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_setlist_song_get] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_REST(ret, details); } pke_at_storage_interface_response pke_at_storage_sql::pke_at_storage_interface_setlist_song_upsert(const pke_at_setlist_song_details &val) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { char uuid[59], uuid2[59]; snprintf(uuid, 59, pk_uuid_printf_format, pk_uuid_printf_var(val.setlist_uuid)); snprintf(uuid2, 59, pk_uuid_printf_format, pk_uuid_printf_var(val.song_uuid)); snprintf(sql_mstr.query_text, query_text_length, (char*)src_sql_setlist_song_upsert_sql, uuid, uuid2); res = sqlite3_exec(db, sql_mstr.query_text, nullptr, nullptr, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_setlist_song_upsert] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_RES(ret); } pke_at_storage_interface_response pke_at_storage_sql::pke_at_storage_interface_song_delete(pk_uuid uuid) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { snprintf(sql_mstr.query_text, query_text_length, "%.*s WHERE uuid='" pk_uuid_printf_format "'", src_sql_song_delete_sql_len, src_sql_song_delete_sql, pk_uuid_printf_var(uuid)); res = sqlite3_exec(db, sql_mstr.query_text, nullptr, nullptr, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_song_delete] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_RES(ret); } pke_at_storage_interface_response_t pke_at_storage_sql::pke_at_storage_interface_song_get(pk_uuid uuid) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; pke_at_song_details *details = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { snprintf(sql_mstr.query_text, query_text_length, "%.*s WHERE uuid='" pk_uuid_printf_format "'", src_sql_song_get_sql_len, src_sql_song_get_sql, pk_uuid_printf_var(uuid)); auto fn = [](void *user_data, int column_count, char **argv, char **colname) -> int { int i; (void)colname; PK_STN_RES stn_res; auto **deets = reinterpret_cast(user_data); if (*deets != nullptr) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_song_get::fn] More than one result returned.\n"); return SQLITE_ERROR; } *deets = pk_new(g_at.mem.bkt_transient); auto *d = *deets; new (d) pke_at_song_details(); for (i = 0; i < column_count; ++i) { if (i == 0) { d->uuid << argv[i]; } if (i == 1) { stn_res = pk_stn(&d->ccli, argv[i], nullptr); if (stn_res != PK_STN_RES_SUCCESS) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_song_get::fn] ccli STN err: %i.\n", stn_res); return SQLITE_ERROR; } } if (i == 2) { d->title.length = strlen(argv[i]); d->title.reserved = d->title.length + 1; char *str = pk_new_arr(d->title.reserved, g_at.mem.bkt); strncpy(str, argv[i], d->title.length); d->title.val = str; } if (i == 3) { d->arrangement.length = strlen(argv[i]); d->arrangement.reserved = d->title.length + 1; char *str = pk_new_arr(d->title.reserved, g_at.mem.bkt); strncpy(str, argv[i], d->title.length); d->arrangement.val = str; } if (i == 4) { stn_res = pk_stn(&d->bpm, argv[i], nullptr); if (stn_res != PK_STN_RES_SUCCESS) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_song_get::fn] bpm STN err: %i.\n", stn_res); return SQLITE_ERROR; } } } return SQLITE_OK; }; res = sqlite3_exec(db, sql_mstr.query_text, fn, &details, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_song_get] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_REST(ret, details); } pke_at_storage_interface_response pke_at_storage_sql::pke_at_storage_interface_song_upsert(const pke_at_song_details &val) const { int res, ret = 1; sqlite3 *db = nullptr; char *sql_err_string = nullptr; res = sqlite3_open(SQLITE_DB_PATH, &db); assert(res == SQLITE_OK); { char uuid[59]; snprintf(uuid, 59, pk_uuid_printf_format, pk_uuid_printf_var(val.uuid)); snprintf(sql_mstr.query_text, query_text_length, (char*)src_sql_song_upsert_sql, uuid, val.ccli, val.title.val, val.arrangement.val, val.bpm); res = sqlite3_exec(db, sql_mstr.query_text, nullptr, nullptr, &sql_err_string); if (res != SQLITE_OK) { fprintf(stderr, "[pke_at_storage_sql::pke_at_storage_interface_song_upsert] Failed, sqlite err: %i, %s\n", res, sql_err_string); ret = 2; goto cleanup; } } cleanup: if (db != nullptr) sqlite3_close(db); return PASIS_RES(ret); }