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
Školení
Kontakt
739 219 991
live:radekchalupa_1
Nové články