Radek Chalupa   konzultace a školení programování, vývoj software na zakázku

Databáze SQLite - nadstavba v C++

3.2.2020

Pokud píšeme aplikaci v C++ (tedy nemusíme se "omezovat" na čisté neobjektové C), můžeme si některé častěji používané funkce SQLite zabalit do tříd/objektů jazyka C++. Napsal jsem jednoduché zabalení do 2 tříd: database a command. Třída database zabaluje základnífunkce nad ukazatelem sqlite3*. Třída command zabaluje funkce nad "prepared statement", tj. ukazatelem sqlite3_stmt*

Následující zdrojový kód naleznete také na mém GitHubu a můžete jej použít a upravit podle svých potřeb.

#pragma once

#ifndef RCSQLITE3_H
#define RCSQLITE3_H

#include <sqlite3.h>
#include <stdexcept>
#include <string.h>
#include <assert.h>

namespace rc
{

namespace sqlite
{

inline void __attribute__((noreturn)) fatal_exit() noexcept
{
	std::terminate();
}

class database
{
protected:
	sqlite3* _pointer = nullptr;

public:
	~database() noexcept
	{
		release();
	}

	void release() noexcept
	{
		if (_pointer)
		{
			if (sqlite3_close(_pointer) != SQLITE_OK)
				rc::sqlite::fatal_exit();
			_pointer = nullptr;
		}
	}

	__attribute__((always_inline)) operator sqlite3*() const noexcept
	{
		return _pointer;
	}

	__attribute__((always_inline)) sqlite3* handle() const noexcept
	{
		return _pointer;
	}

	__attribute__((always_inline)) bool opened() noexcept
	{
		return (_pointer != nullptr);
	}

	void open(const char* file_path)
	{
		release();
		int sqlres = sqlite3_open(file_path, &_pointer);
		if (sqlres != SQLITE_OK)
		{
			release();
			throw std::runtime_error(sqlite3_errstr(sqlres));
		}
	}

	void create(const char* file_path)
	{
		release();
		int ires = sqlite3_open(file_path, &_pointer);
		if (ires != SQLITE_OK)
		{
			release();
			throw std::runtime_error(sqlite3_errstr(ires));
		}
	}

	void execute_command(const char* sz_sql)
	{
		assert(_pointer != nullptr);
		int sqlres = sqlite3_exec(_pointer, sz_sql, nullptr, nullptr, nullptr);
		if (sqlres != SQLITE_OK)
			throw std::runtime_error(sqlite3_errstr(sqlres));
	}

	void begin_transaction()
	{
		execute_command("BEGIN TRANSACTION;");
	}

	void end_transaction()
	{
		execute_command("END TRANSACTION;");
	}

	void check_integrity()
	{
		execute_command("PRAGMA integrity_check;");
	}
}; // class database


class command
{
private:
	sqlite3_stmt* _stmt = nullptr;
	database* _database = nullptr;

public:
	command(const char* sz_sql, database* db)
	{
		create(sz_sql, db);
	}

	command(const std::string& str_sql, database* db)
	{
		create(str_sql.c_str(), db);
	}

	~command() noexcept
	{
		release();
	}

	void release() noexcept
	{
		if (_stmt != nullptr)
		{
			int vysl = sqlite3_reset(_stmt);
			if (vysl != SQLITE_OK)
				rc::sqlite::fatal_exit();
			vysl = sqlite3_finalize(_stmt);
			if (vysl != SQLITE_OK)
				rc::sqlite::fatal_exit();
			_stmt = nullptr;
		}
	}

	// Pokud není další záznam příkazu, vrátí false
	// Při jiné chybě SQLite vyvolá kritické ukončení
	bool next_line() noexcept
	{
		assert(_stmt != nullptr);
		int sqlres = sqlite3_step(_stmt);
		if (SQLITE_ERROR == sqlres)
			rc::sqlite::fatal_exit();
		return (SQLITE_ROW == sqlres);
	}

	// Musí být ověřeno předchozí úspěšné provedení funkce next_line,
	// a nesmí být před tímto _smtp uvolněn
	std::string get_text(int column_index) noexcept
	{
		assert(_stmt != nullptr);
		const unsigned char* pt = sqlite3_column_text(_stmt, column_index);
		if (nullptr == pt)
			return std::string("");
		else
		{
			size_t length = static_cast(sqlite3_column_bytes(_stmt, column_index));
			return std::string((const char*)pt, length);
		}
	}

	sqlite3_int64 get_int64(int column_index) noexcept
	{
		assert(_stmt != nullptr);
		return sqlite3_column_int64(_stmt, column_index);
	}

	const void* get_blob(int column_index) noexcept
	{
		assert(_stmt != nullptr);
		return sqlite3_column_blob(_stmt, column_index);
	}

	size_t get_blob_size(int column_index) noexcept
	{
		assert(_stmt != nullptr);
		return static_cast(sqlite3_column_bytes(_stmt, column_index));
	}

	void set_param_text(int param_number, const char* text)
	{
		assert(_stmt != nullptr);
		int sqlres = sqlite3_bind_text(_stmt, param_number, text,
			static_cast(strlen(text)), SQLITE_STATIC);
		if (sqlres != SQLITE_OK)
		{
			release();
			throw std::logic_error(sqlite3_errstr(sqlres));
		}
	}

	void set_param_int64(int param_number, sqlite3_int64 i64_value)
	{
		assert(_stmt != nullptr);
		int sqlres = sqlite3_bind_int64(_stmt, param_number, i64_value);
		if (sqlres != SQLITE_OK)
		{
			release();
			throw std::logic_error(sqlite3_errstr(sqlres));
		}
	}

	void set_param_blob(int param_number, const void* p_value, size_t bytes_count)
	{
		assert(_stmt != nullptr);
		int sqlres = sqlite3_bind_blob64(_stmt, param_number, p_value,
			static_cast(bytes_count), nullptr);
		if (sqlres != SQLITE_OK)
		{
			release();
			throw std::logic_error(sqlite3_errstr(sqlres));
		}
	}

	// on error throw exception
	void execute()
	{
		assert(_stmt != nullptr);
		int sqlres = sqlite3_step(_stmt);
		if ((sqlres != SQLITE_OK) && (sqlres != SQLITE_DONE))
			throw std::runtime_error(sqlite3_errstr(sqlres));
	}

private:
	void create(const char* sz_sql, rc::sqlite::database* db)
	{
		assert(db!= nullptr);
		if (nullptr == db)
			rc::sqlite::fatal_exit();
		_database = db;
		int sqlres = sqlite3_prepare_v2(*_database, sz_sql,
			static_cast(strlen(sz_sql)), &_stmt, nullptr);
		if (SQLITE_OK != sqlres)
		{
			release();
			throw std::runtime_error(sqlite3_errstr(sqlres));
		}
	}
}; // class command

} // namespace sqlite

} // namespace rc

#endif // RCSQLITE3_H