Databáze SQLite v C/C++ na Linuxu - základy
22.10.2019
Instalace potřebných nástrojů
Nejjednodušší cestou jak získat vše potřebné pro psaní aplikací nad SQLite v C/C++ je v Linux nainstalovat příslušný vývojářský balíček. V Debianu a jeho derivátech jde o balíček libsqlite3-dev. Při kompilaci a sestavování projektu pak do zdrojového kódu přidáme (direktivou #include) hlavičkový soubor sqlite3.h a do sestavení knihovnu sqlite3, tj. při použití kompilátoru GCC přidáme do parametrů -lsqlite3. Doporučuji si nainstalovat také balíček sqlitebrowser, což je nástroj DB Browser for SQLite, umožňující prohlížet a upravovat databázové soubory SQLite, samozřejmě jak data tak struktury tabulek.
Další možností je stažení zdrojových souborů z webu SQLite. V archivu sqlite-amalgamation-xxx.zip (kde xxx je číslo aktuální verze) najdeme soubory sqlite3.h a sqlite3.c, které přidáme do sestavení projektu. Jak je zřejmé, celý zdrojový kód databáze je v souboru sqlite3.c.
Ukázka vytvoření databáze a přidání dat
Nyní si již můžeme vytvořit jednoduchý ukázkový program, který vytvoří databázový soubor SQLite, v něm vytvoří tabulku s položkami typu číslo a text, do které přidá data. Následné pak data zobrazí textově v konzoli.
Vše bude v jednom zdrojovém souboru (main.cpp) který přeložíme a sestavíme příkazem:
g++ main.cpp -g -lsqlite3 -osqlite-cpp
Do zdrojového kódu si přidáme potřebné hlavičkové soubory. Kromě běžných céčkovských hlaviček a výše zmíněné hlavičce sqlite3.h si přidáme také specifické Linuxové hlavičky obsahující deklarace funkcí které využijeme ve vlastních pomocných funkcích pro dotazování na souborový systém.
#include <cstdio> #include <stdexcept> #include <unistd.h> #include <pwd.h> #include <sys/stat.h> #include <sys/types.h> #include <assert.h> #include <sqlite3.h>
Nejprve si napíšeme jednoduché pomocné funkce pro zjištění existence zadaného souboru a adresáře:
bool directory_exists(const char* fs_path)
{
struct stat ss;
if (stat(fs_path, &ss) != 0)
return false;
else
return (ss.st_mode & S_IFMT) == S_IFDIR;
}
bool file_exists(const char* file_path)
{
struct stat ss;
if (stat(file_path, &ss) != 0)
return false;
else
return (ss.st_mode & S_IFMT) == S_IFREG;
}
Nyní již můžeme přikročit k vytvoření databázového souboru pomocí funkce sqlite3_open, která vytvoří zadaný soubor nebo pokud již existuje, otevře ho a v případě úspěchu naplní ukazatel zadaný jako 2. parametr. Ten je typu sqlite3* a představuje handle na otevřenou databázi. Pokud máme tento platný handle, můžeme použít některou z mnoha funkcí sqlite, například sqlite3_exec, který provede nad databází zadaný SQL příkaz. V našem případě po otevření a vytvoření tabulky přidáme do databáze pro ukázku 2 položky.
std::string db_file = getenv("HOME");
if (!directory_exists(db_file.c_str()))
db_file = getpwuid(getuid())->pw_dir;
if (!directory_exists(db_file.c_str()))
throw std::runtime_error("Nepodařilo se zjistit domovský adresář.");
db_file.append("/Dokumenty/test.db");
printf("Soubor databáze: %s\n", db_file.c_str());
int ires = sqlite3_open(db_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,"
" cislo INTEGER DEFAULT 0, nazev TEXT)", nullptr, nullptr, nullptr);
if (ires != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(ires));
ires = sqlite3_exec(_sqlite3, "INSERT INTO polozky (cislo, nazev) VALUES (1, 'první položka')",
nullptr, nullptr, nullptr);
if (ires != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(ires));
ires = sqlite3_exec(_sqlite3, "INSERT INTO polozky (cislo, nazev) VALUES (2, 'druhá položka')",
nullptr, nullptr, nullptr);
if (ires != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(ires));
V kódu jak vidno používám výjimky při zjištění chyby, proto celý kód z něhož je uvedená část je uvnitř bloku try-catch.
Nyní si ještě ukážeme jeden ze způsobů jak procházet tabulku a vypsat všechny položky, resp. ty které nadefinujeme v SQL příkazu SELECT. Opět použijeme funkci sqlite3_exec s tím, že jako 3. parametr uvedeme vlastní callback funkci, která bude volaná pro každý výsledný řádek příkazu select. V této funkci máme v parametrech vše potřebné, tj. počet sloupců a pole ukazatelů na data v jednotlivých sloupcích a také názvy sloupců. V našem případě může tato funkce vypadat například takto:
int select_callback(void* p, int count, char** data, char** col_name)
{
for (size_t i = 0; i < count; i++)
{
printf("%s: %s \t", *(col_name+i), *(data+i));
}
printf("\n");
return 0;
}
A volání příkazu SELECT pak vypadá takto:
ires = sqlite3_exec(_sqlite3, "SELECT * FROM polozky", select_callback, nullptr, nullptr); if (ires != SQLITE_OK) throw std::runtime_error(sqlite3_errstr(ires));
Po použití databázi zavřeme funkcí:
sqlite3_close(_sqlite3);
Ukázkový program najdete také na mém githubu a kompletní kód vypadá takto:
#include <cstdio>
#include <stdexcept>
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <sqlite3.h>
sqlite3* _sqlite3 = nullptr;
bool directory_exists(const char* fs_path)
{
struct stat ss;
if (stat(fs_path, &ss) != 0)
return false;
else
return (ss.st_mode & S_IFMT) == S_IFDIR;
}
bool file_exists(const char* file_path)
{
struct stat ss;
if (stat(file_path, &ss) != 0)
return false;
else
return (ss.st_mode & S_IFMT) == S_IFREG;
}
int select_callback(void* p, int count, char** data, char** col_name)
{
for (size_t i = 0; i < count; i++)
{
printf("%s: %s \t", *(col_name+i), *(data+i));
}
printf("\n");
return 0;
}
int main(int argc, char** argv)
{
try
{
std::string db_file = getenv("HOME");
if (!directory_exists(db_file.c_str()))
db_file = getpwuid(getuid())->pw_dir;
if (!directory_exists(db_file.c_str()))
throw std::runtime_error("Nepodařilo se zjistit domovský adresář.");
db_file.append("/Dokumenty/test.db");
printf("Soubor databáze: %s\n", db_file.c_str());
int ires = sqlite3_open(db_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,"
" cislo INTEGER DEFAULT 0, nazev TEXT)", nullptr, nullptr, nullptr);
if (ires != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(ires));
ires = sqlite3_exec(_sqlite3, "INSERT INTO polozky (cislo, nazev) VALUES (1, 'první položka')",
nullptr, nullptr, nullptr);
if (ires != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(ires));
ires = sqlite3_exec(_sqlite3, "INSERT INTO polozky (cislo, nazev) VALUES (2, 'druhá položka')",
nullptr, nullptr, nullptr);
if (ires != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(ires));
ires = sqlite3_exec(_sqlite3, "SELECT * FROM polozky",
select_callback, nullptr, nullptr);
if (ires != SQLITE_OK)
throw std::runtime_error(sqlite3_errstr(ires));
sqlite3_close(_sqlite3);
printf("\nok\n");
return EXIT_SUCCESS;
}
catch(const std::exception& e)
{
printf(e.what());
if (_sqlite3)
sqlite3_close(_sqlite3);
return EXIT_FAILURE;
}
}