Databáze SQLite v C++ - binární data - BLOB
19.11.2019
V předchozích částech tohoto tématu jsme si ukázali základy práce s daty typu celé číslo a text. Nyní se podíváme na obecná binární data - BLOB. Konkrétně jak ukládat a načítat celý obsah zvoleného souboru, v našem případě půjde o obrázky.
V databázi bude tabulka se sloupci (kromě číselného primárního klíče) typu TEXT a BLOB.
void vytvorit_databazi()
{
std::string str_file = get_cesta_db();
if (file_exists(str_file.c_str()))
{
if (remove(str_file.c_str()) !=0)
throw std::runtime_error("Chyba smazání souboru");
}
int ires = sqlite3_open(str_file.c_str(), &_sqlite3);
if (ires != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(ires));
ires = sqlite3_exec(_sqlite3, "CREATE TABLE IF NOT EXISTS polozky (id INTEGER PRIMARY KEY,"
" nazev TEXT, obrazek BLOB)", nullptr, nullptr, nullptr);
if (ires != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(ires));
}
Pro přidání nebo editaci položky typu BLOB je klíčová funkce sqlite3_bind_blob64 použitá v rámci prepared statement. Jako ukázku si napíšeme funkci pro přidání souboru (v našem případě budeme přidávat obrázky, ale může samozřejmě jít i libovolný typ souboru) do nové řádky databáze. Do sloupce nazev si uložíme cestu k zadanému souboru.
void pridat_obrazek(const char* file_path)
{
struct stat stat_file;
if (stat(file_path, &stat_file) != 0)
throw std::runtime_error("chyba vstupního souboru");
if ((stat_file.st_mode & S_IFMT) != S_IFREG)
throw std::runtime_error("vstupního soubor neexistuje");
if (0 == stat_file.st_size)
throw std::runtime_error("soubor je prázdný");
FILE* pfile = nullptr;
void* file_buffer = nullptr;
try
{
pfile = fopen(file_path, "r");
if (nullptr == pfile)
throw std::runtime_error("chyba fopen");
file_buffer = malloc(stat_file.st_size);
if (nullptr == file_buffer)
throw std::runtime_error("chyba alokace - malloc");
if (fread(file_buffer, stat_file.st_size, 1, pfile) != 1)
throw std::runtime_error("chyba čtení souboru");
sqlite3_stmt* stmt = nullptr;
char sz_sql[255];
strcpy(sz_sql, "INSERT INTO polozky (nazev, obrazek) VALUES (?,?)");
int sqlres = sqlite3_prepare_v2(_sqlite3, sz_sql,
static_cast(strlen(sz_sql)), &stmt, nullptr);
if (sqlres != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(sqlres));
sqlres = sqlite3_bind_text(stmt, 1, file_path, (int)(strlen(file_path)), SQLITE_STATIC);
if (sqlres != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(sqlres));
sqlres = sqlite3_bind_blob64(stmt, 2, (const void*)file_buffer,
(sqlite3_int64)stat_file.st_size, 0);
if (sqlres != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(sqlres));
sqlres = sqlite3_step(stmt);
if ((sqlres != SQLITE_OK) && (sqlres != SQLITE_DONE))
throw std::runtime_error(sqlite3_errstr(sqlres));
sqlres = sqlite3_reset(stmt);
if ((sqlres != SQLITE_OK) && (sqlres != SQLITE_DONE))
throw std::runtime_error(sqlite3_errstr(sqlres));
sqlres = sqlite3_finalize(stmt);
if ((sqlres != SQLITE_OK) && (sqlres != SQLITE_DONE))
throw std::runtime_error(sqlite3_errstr(sqlres));
fclose(pfile);
free(file_buffer);
}
catch(const std::exception& e)
{
if (file_buffer)
free(file_buffer);
if (pfile)
fclose(pfile);
throw e;
}
}
Poté co naplníme databázi pár obrázky (v doprovodném kódu zadané natvrdo cestou), napíšeme si "opačnou" funkci, která projde všechny řádky tabulky a obrázky uloží na původní umístění. Pro otestování před tím původní soubory vymažeme. V tomto případě jsou klíčové funkce sqlite3_column_bytes (vrátí počet BYTů v BLOBu) a sqlite3_column_blob (vrátí ukazatel na data typu BLOB - tento ukazatel je alokován interně uvnitř SQLite takže ho po použití nemusíme a ani nesmíme uvolňovat funkcí free).
void ulozit_obrazky()
{
std::string str_file = get_cesta_db();
if (!file_exists(str_file.c_str()))
throw std::runtime_error("soubor databáze neexistuje");
int sqlres = sqlite3_open(str_file.c_str(), &_sqlite3);
if (sqlres != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(sqlres));
sqlite3_stmt* stmt;
const char* strsql = "SELECT nazev, obrazek FROM polozky";
sqlres = sqlite3_prepare_v2(_sqlite3, strsql,
static_cast(strlen(strsql)), &stmt, nullptr);
if (sqlres != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(sqlres));
int istep;
size_t sizedata;
void* pdata;
istep = sqlite3_step(stmt);
FILE* pfile;
while (SQLITE_ROW == istep)
{
sizedata = sqlite3_column_bytes(stmt, 1);
pdata = (void*)sqlite3_column_blob(stmt, 1);
pfile = fopen((const char*)sqlite3_column_text(stmt, 0), "w");
if (nullptr == pfile)
{
istep = sqlite3_step(stmt);
continue;
}
if (1 != fwrite((const void*)pdata, sizedata, 1, pfile))
{
printf("chyba zápisu do: %s\n", sqlite3_column_text(stmt, 1));
}
fclose(pfile);
istep = sqlite3_step(stmt);
}
sqlres = sqlite3_reset(stmt);
if (sqlres != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(sqlres));
sqlres = sqlite3_finalize(stmt);
if (sqlres != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(sqlres));
sqlite3_close(_sqlite3);
}
Celý kód obsahující i zde volané pomocné funkce si můžete prohlédnout na mém githubu. Přeložit lze kompilátorem GCC příkazem:
g++ main.cpp -g -lsqlite3 -osqlite-cpp