1134 lines
27 KiB
C++
1134 lines
27 KiB
C++
#ifndef _QTL_SQLITE_H_
|
|
#define _QTL_SQLITE_H_
|
|
|
|
#include "sqlite3.h"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <sstream>
|
|
#include "qtl_common.hpp"
|
|
|
|
namespace qtl
|
|
{
|
|
|
|
namespace sqlite
|
|
{
|
|
|
|
class error : public std::exception
|
|
{
|
|
public:
|
|
explicit error(int error_code) : m_errno(error_code)
|
|
{
|
|
m_errmsg=sqlite3_errstr(error_code);
|
|
}
|
|
explicit error(sqlite3* db)
|
|
{
|
|
m_errno=sqlite3_errcode(db);
|
|
m_errmsg=sqlite3_errmsg(db);
|
|
}
|
|
error(const error& src) = default;
|
|
virtual ~error() throw() { }
|
|
virtual const char* what() const throw() override { return m_errmsg; }
|
|
int code() const throw() { return m_errno; }
|
|
|
|
private:
|
|
int m_errno;
|
|
const char* m_errmsg;
|
|
};
|
|
|
|
class statement final
|
|
{
|
|
public:
|
|
statement() : m_stmt(NULL), m_fetch_result(SQLITE_OK) { }
|
|
statement(const statement&) = delete;
|
|
statement(statement&& src)
|
|
: m_stmt(src.m_stmt), m_fetch_result(src.m_fetch_result),
|
|
m_tail_text(std::forward<std::string>(src.m_tail_text))
|
|
{
|
|
src.m_stmt=NULL;
|
|
src.m_fetch_result=SQLITE_OK;
|
|
}
|
|
statement& operator=(const statement&) = delete;
|
|
statement& operator=(statement&& src)
|
|
{
|
|
if(this!=&src)
|
|
{
|
|
m_stmt=src.m_stmt;
|
|
m_fetch_result=src.m_fetch_result;
|
|
m_tail_text=std::forward<std::string>(src.m_tail_text);
|
|
src.m_stmt=NULL;
|
|
src.m_fetch_result=SQLITE_OK;
|
|
}
|
|
return *this;
|
|
}
|
|
~statement()
|
|
{
|
|
close();
|
|
}
|
|
|
|
void open(sqlite3* db, const char* query_text, size_t text_length=-1)
|
|
{
|
|
const char* tail=NULL;
|
|
close();
|
|
verify_error(sqlite3_prepare_v2(db, query_text, (int)text_length, &m_stmt, &tail));
|
|
if(tail!=NULL)
|
|
{
|
|
if(text_length==-1)
|
|
m_tail_text.assign(tail);
|
|
else
|
|
m_tail_text.assign(tail, query_text+text_length);
|
|
}
|
|
else
|
|
m_tail_text.clear();
|
|
}
|
|
|
|
void close()
|
|
{
|
|
if(m_stmt)
|
|
{
|
|
sqlite3_finalize(m_stmt);
|
|
m_stmt=NULL;
|
|
}
|
|
}
|
|
|
|
void bind_param(int index, int value)
|
|
{
|
|
verify_error(sqlite3_bind_int(m_stmt, index+1, value));
|
|
}
|
|
void bind_param(int index, int64_t value)
|
|
{
|
|
verify_error(sqlite3_bind_int64(m_stmt, index+1, value));
|
|
}
|
|
void bind_param(int index, double value)
|
|
{
|
|
verify_error(sqlite3_bind_double(m_stmt, index+1, value));
|
|
}
|
|
void bind_param(int index, const char* value, size_t n=-1)
|
|
{
|
|
if(value)
|
|
verify_error(sqlite3_bind_text(m_stmt, index+1, value, (int)n, NULL));
|
|
else
|
|
verify_error(sqlite3_bind_null(m_stmt, index+1));
|
|
}
|
|
void bind_param(int index, const wchar_t* value, size_t n=-1)
|
|
{
|
|
if(value)
|
|
verify_error(sqlite3_bind_text16(m_stmt, index+1, value, (int)n, NULL));
|
|
else
|
|
verify_error(sqlite3_bind_null(m_stmt, index+1));
|
|
}
|
|
void bind_param(int index, const std::string& value)
|
|
{
|
|
bind_param(index, value.data(), value.size());
|
|
}
|
|
void bind_param(int index, const std::wstring& value)
|
|
{
|
|
bind_param(index, value.data(), value.size());
|
|
}
|
|
void bind_param(int index, const const_blob_data& value)
|
|
{
|
|
if(value.size)
|
|
{
|
|
if(value.data)
|
|
verify_error(sqlite3_bind_blob(m_stmt, index+1, value.data, (int)value.size, NULL));
|
|
else
|
|
verify_error(sqlite3_bind_zeroblob(m_stmt, index+1, (int)value.size));
|
|
}
|
|
else
|
|
verify_error(sqlite3_bind_null(m_stmt, index+1));
|
|
}
|
|
void bind_zero_blob(int index, int n)
|
|
{
|
|
verify_error(sqlite3_bind_zeroblob(m_stmt, index+1, (int)n));
|
|
}
|
|
//void bind_zero_blob(int index, sqlite3_uint64 n)
|
|
//{
|
|
// verify_error(sqlite3_bind_zeroblob64(m_stmt, index+1, (int)n));
|
|
//}
|
|
void bind_param(int index, qtl::null)
|
|
{
|
|
verify_error(sqlite3_bind_null(m_stmt, index+1));
|
|
}
|
|
void bind_param(int index, std::nullptr_t)
|
|
{
|
|
verify_error(sqlite3_bind_null(m_stmt, index+1));
|
|
}
|
|
int get_parameter_count() const
|
|
{
|
|
return sqlite3_bind_parameter_count(m_stmt);
|
|
}
|
|
const char* get_parameter_name(int i) const
|
|
{
|
|
return sqlite3_bind_parameter_name(m_stmt, i);
|
|
}
|
|
int get_parameter_index(const char* param_name) const
|
|
{
|
|
return sqlite3_bind_parameter_index(m_stmt, param_name);
|
|
}
|
|
|
|
template<class Type>
|
|
void bind_field(size_t index, Type&& value)
|
|
{
|
|
get_value((int)index, std::forward<Type>(value));
|
|
}
|
|
template<class Type>
|
|
void bind_field(size_t index, qtl::indicator<Type>&& value)
|
|
{
|
|
int type=get_column_type(index);
|
|
value.length=0;
|
|
value.is_truncated=false;
|
|
qtl::bind_field(*this, index, value.data);
|
|
if(type==SQLITE_NULL)
|
|
{
|
|
value.is_null=true;
|
|
}
|
|
else
|
|
{
|
|
value.is_null=false;
|
|
if(type==SQLITE_TEXT || type==SQLITE_BLOB)
|
|
value.length=sqlite3_column_bytes(m_stmt, index);
|
|
}
|
|
}
|
|
void bind_field(size_t index, char* value, size_t length)
|
|
{
|
|
size_t col_length=get_column_length((int)index);
|
|
if(col_length>0)
|
|
strncpy(value, (const char*)sqlite3_column_text(m_stmt, (int)index), std::min(length, col_length+1));
|
|
else
|
|
memset(value, 0, length*sizeof(char));
|
|
}
|
|
void bind_field(size_t index, wchar_t* value, size_t length)
|
|
{
|
|
size_t col_length=sqlite3_column_bytes16(m_stmt, (int)index);
|
|
if(col_length>0)
|
|
wcsncpy(value, (const wchar_t*)sqlite3_column_text16(m_stmt, (int)index), std::min(length, col_length+1));
|
|
else
|
|
memset(value, 0, length*sizeof(wchar_t));
|
|
}
|
|
template<size_t N>
|
|
void bind_field(size_t index, char (&&value)[N])
|
|
{
|
|
bind_field(index, value, N);
|
|
}
|
|
template<size_t N>
|
|
void bind_field(size_t index, wchar_t (&&value)[N])
|
|
{
|
|
bind_field(index, value, N);
|
|
}
|
|
template<size_t N>
|
|
void bind_field(size_t index, std::array<char, N>&& value)
|
|
{
|
|
bind_field(index, value.data(), value.size());
|
|
}
|
|
template<size_t N>
|
|
void bind_field(size_t index, std::array<wchar_t, N>&& value)
|
|
{
|
|
bind_field(index, value.data(), value.size());
|
|
}
|
|
|
|
#ifdef _QTL_ENABLE_CPP17
|
|
|
|
template<typename T>
|
|
inline void bind_field(size_t index, std::optional<T>&& value)
|
|
{
|
|
int type = get_column_type(index);
|
|
if (type == SQLITE_NULL)
|
|
{
|
|
value.reset();
|
|
}
|
|
else
|
|
{
|
|
qtl::bind_field(*this, index, *value);
|
|
}
|
|
}
|
|
|
|
inline void bind_field(size_t index, std::any&& value)
|
|
{
|
|
int type = get_column_type(index);
|
|
switch(type)
|
|
{
|
|
case SQLITE_NULL:
|
|
value.reset();
|
|
case SQLITE_INTEGER:
|
|
value = sqlite3_column_int64(m_stmt, index);
|
|
break;
|
|
case SQLITE_FLOAT:
|
|
value = sqlite3_column_double(m_stmt, index);
|
|
break;
|
|
case SQLITE_TEXT:
|
|
value.emplace<std::string_view>((const char*)sqlite3_column_text(m_stmt, index), sqlite3_column_bytes(m_stmt, index));
|
|
break;
|
|
case SQLITE_BLOB:
|
|
value.emplace<const_blob_data>(sqlite3_column_text(m_stmt, index), sqlite3_column_bytes(m_stmt, index));
|
|
break;
|
|
default:
|
|
throw sqlite::error(SQLITE_MISMATCH);
|
|
}
|
|
}
|
|
|
|
#endif // C++17
|
|
|
|
size_t find_field(const char* name) const
|
|
{
|
|
size_t count=get_column_count();
|
|
for(size_t i=0; i!=count; i++)
|
|
{
|
|
if(strcmp(get_column_name(i), name)==0)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool fetch()
|
|
{
|
|
m_fetch_result=sqlite3_step(m_stmt);
|
|
switch(m_fetch_result)
|
|
{
|
|
case SQLITE_ROW:
|
|
return true;
|
|
case SQLITE_DONE:
|
|
return false;
|
|
default:
|
|
throw sqlite::error(m_fetch_result);
|
|
}
|
|
}
|
|
int get_column_count() const
|
|
{
|
|
return sqlite3_column_count(m_stmt);
|
|
}
|
|
const char* get_column_name(int col) const
|
|
{
|
|
return sqlite3_column_name(m_stmt, col);
|
|
}
|
|
void get_value(int col, int&& value) const
|
|
{
|
|
value=sqlite3_column_int(m_stmt, col);
|
|
}
|
|
void get_value(int col, int64_t&& value) const
|
|
{
|
|
value=sqlite3_column_int64(m_stmt, col);
|
|
}
|
|
void get_value(int col, double&& value) const
|
|
{
|
|
value=sqlite3_column_double(m_stmt, col);
|
|
}
|
|
const unsigned char* get_value(int col) const
|
|
{
|
|
return sqlite3_column_text(m_stmt, col);
|
|
}
|
|
|
|
template<typename CharT>
|
|
const CharT* get_text_value(int col) const;
|
|
|
|
template<typename T>
|
|
void get_value(int col, qtl::bind_string_helper<T>&& value) const
|
|
{
|
|
typedef typename qtl::bind_string_helper<T>::char_type char_type;
|
|
int bytes=sqlite3_column_bytes(m_stmt, col);
|
|
if(bytes>0)
|
|
value.assign(get_text_value<char_type>(col), bytes/sizeof(char_type));
|
|
else
|
|
value.clear();
|
|
}
|
|
void get_value(int col, const_blob_data&& value) const
|
|
{
|
|
value.data=sqlite3_column_blob(m_stmt, col);
|
|
value.size=sqlite3_column_bytes(m_stmt, col);
|
|
}
|
|
void get_value(int col, blob_data&& value) const
|
|
{
|
|
const void* data=sqlite3_column_blob(m_stmt, col);
|
|
size_t size=sqlite3_column_bytes(m_stmt, col);
|
|
if(value.size<size)
|
|
throw std::out_of_range("no enough buffer to receive blob data.");
|
|
memcpy(value.data, data, size);
|
|
value.size=size;
|
|
}
|
|
void get_value(int col, std::ostream&& value) const
|
|
{
|
|
const void* data=sqlite3_column_blob(m_stmt, col);
|
|
size_t size=sqlite3_column_bytes(m_stmt, col);
|
|
if(size>0)
|
|
value.write((const char*)data, size);
|
|
}
|
|
|
|
int get_column_length(int col) const
|
|
{
|
|
return sqlite3_column_bytes(m_stmt, col);
|
|
}
|
|
int get_column_type(int col) const
|
|
{
|
|
return sqlite3_column_type(m_stmt, col);
|
|
}
|
|
void clear_bindings()
|
|
{
|
|
sqlite3_clear_bindings(m_stmt);
|
|
}
|
|
void reset()
|
|
{
|
|
sqlite3_reset(m_stmt);
|
|
}
|
|
|
|
template<typename Types>
|
|
void execute(const Types& params)
|
|
{
|
|
unsigned long count=get_parameter_count();
|
|
if(count>0)
|
|
{
|
|
qtl::bind_params(*this, params);
|
|
}
|
|
fetch();
|
|
}
|
|
|
|
template<typename Types>
|
|
bool fetch(Types&& values)
|
|
{
|
|
bool result=false;
|
|
if(m_fetch_result==SQLITE_OK)
|
|
fetch();
|
|
if(m_fetch_result==SQLITE_ROW)
|
|
{
|
|
result=true;
|
|
qtl::bind_record(*this, std::forward<Types>(values));
|
|
m_fetch_result=SQLITE_OK;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool next_result()
|
|
{
|
|
sqlite3* db=sqlite3_db_handle(m_stmt);
|
|
int count=0;
|
|
do
|
|
{
|
|
trim_string(m_tail_text, " \t\r\n");
|
|
if(!m_tail_text.empty())
|
|
{
|
|
open(db, m_tail_text.data(), m_tail_text.size());
|
|
count=sqlite3_column_count(m_stmt);
|
|
m_fetch_result=SQLITE_OK;
|
|
}
|
|
}while(!m_tail_text.empty() && count==0);
|
|
return count>0;;
|
|
}
|
|
|
|
int affetced_rows() const
|
|
{
|
|
sqlite3* db=sqlite3_db_handle(m_stmt);
|
|
return db ? sqlite3_changes(db) : 0;
|
|
}
|
|
|
|
int64_t insert_id() const
|
|
{
|
|
sqlite3* db=sqlite3_db_handle(m_stmt);
|
|
return db ? sqlite3_last_insert_rowid(db) : 0;
|
|
}
|
|
|
|
protected:
|
|
sqlite3_stmt* m_stmt;
|
|
std::string m_tail_text;
|
|
|
|
int m_fetch_result;
|
|
void verify_error(int e)
|
|
{
|
|
if(e!=SQLITE_OK) throw error(e);
|
|
}
|
|
};
|
|
|
|
class database final : public qtl::base_database<database, statement>
|
|
{
|
|
public:
|
|
typedef sqlite::error exception_type;
|
|
|
|
database() : m_db(NULL) { }
|
|
~database() { close(); }
|
|
database(const database&) = delete;
|
|
database(database&& src)
|
|
{
|
|
m_db=src.m_db;
|
|
src.m_db=NULL;
|
|
}
|
|
database& operator=(const database&) = delete;
|
|
database& operator=(database&& src)
|
|
{
|
|
if(this!=&src)
|
|
{
|
|
close();
|
|
m_db=src.m_db;
|
|
src.m_db=NULL;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void open(const char *filename, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
|
|
{
|
|
int result=sqlite3_open_v2(filename, &m_db, flags, NULL);
|
|
if(result!=SQLITE_OK)
|
|
throw sqlite::error(result);
|
|
}
|
|
void open(const wchar_t *filename)
|
|
{
|
|
int result=sqlite3_open16(filename, &m_db);
|
|
if(result!=SQLITE_OK)
|
|
throw sqlite::error(result);
|
|
}
|
|
void close()
|
|
{
|
|
if(m_db)
|
|
{
|
|
sqlite3_close_v2(m_db);
|
|
m_db=NULL;
|
|
}
|
|
}
|
|
|
|
statement open_command(const char* query_text, size_t text_length)
|
|
{
|
|
statement stmt;
|
|
stmt.open(handle(), query_text, text_length);
|
|
return stmt;
|
|
}
|
|
statement open_command(const char* query_text)
|
|
{
|
|
return open_command(query_text, strlen(query_text));
|
|
}
|
|
statement open_command(const std::string& query_text)
|
|
{
|
|
return open_command(query_text.data(), query_text.length());
|
|
}
|
|
|
|
void simple_execute(const char* lpszSql)
|
|
{
|
|
int result=sqlite3_exec(m_db, lpszSql, NULL, NULL, NULL);
|
|
if(result!=SQLITE_OK)
|
|
throw sqlite::error(result);
|
|
}
|
|
|
|
void begin_transaction()
|
|
{
|
|
simple_execute("BEGIN TRANSACTION");
|
|
}
|
|
void commit()
|
|
{
|
|
simple_execute("COMMIT TRANSACTION");
|
|
}
|
|
void rollback()
|
|
{
|
|
simple_execute("ROLLBACK TRANSACTION");
|
|
}
|
|
|
|
bool is_alive()
|
|
{
|
|
#ifdef _WIN32
|
|
return true;
|
|
#else
|
|
int has_moved=0;
|
|
int result=sqlite3_file_control(m_db, NULL, SQLITE_FCNTL_HAS_MOVED, &has_moved);
|
|
if(result!=SQLITE_OK)
|
|
throw sqlite::error(result);
|
|
return has_moved==0;
|
|
#endif //_WIN32
|
|
}
|
|
const char* errmsg() const { return sqlite3_errmsg(m_db); }
|
|
int error() const { return sqlite3_errcode(m_db); }
|
|
uint64_t insert_id() { return sqlite3_last_insert_rowid(m_db); }
|
|
sqlite3* handle() { return m_db; }
|
|
|
|
protected:
|
|
sqlite3* m_db;
|
|
};
|
|
|
|
// stream for blob field
|
|
|
|
class blobbuf : public std::streambuf
|
|
{
|
|
public:
|
|
blobbuf()
|
|
{
|
|
init();
|
|
}
|
|
blobbuf(const blobbuf&) = delete;
|
|
blobbuf(blobbuf&& src) : std::streambuf(std::move(src))
|
|
{
|
|
init();
|
|
swap(src);
|
|
}
|
|
virtual ~blobbuf()
|
|
{
|
|
if(m_blob)
|
|
{
|
|
close();
|
|
}
|
|
}
|
|
|
|
blobbuf& operator=(const blobbuf&) = delete;
|
|
blobbuf& operator=(blobbuf&& src)
|
|
{
|
|
if(this!=&src)
|
|
{
|
|
reset_back();
|
|
close();
|
|
swap(src);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void swap( blobbuf& other )
|
|
{
|
|
std::swap(m_blob, other.m_blob);
|
|
std::swap(m_inbuf, other.m_inbuf);
|
|
std::swap(m_outbuf, other.m_outbuf);
|
|
std::swap(m_size, other.m_size);
|
|
std::swap(m_inpos, other.m_inpos);
|
|
std::swap(m_outpos, other.m_outpos);
|
|
|
|
std::streambuf::swap(other);
|
|
std::swap(m_back_char, other.m_back_char);
|
|
if(eback() == &other.m_back_char)
|
|
set_back();
|
|
else
|
|
reset_back();
|
|
|
|
if(other.eback()==&m_back_char)
|
|
other.set_back();
|
|
else
|
|
other.reset_back();
|
|
}
|
|
|
|
static void init_blob(database& db, const char* table, const char* column, int64_t row, int length)
|
|
{
|
|
statement stmt;
|
|
std::ostringstream oss;
|
|
oss<< "UPDATE " << table << " SET " << column << "=? WHERE rowid=?";
|
|
stmt.open(db.handle(), oss.str().data());
|
|
stmt.bind_zero_blob(0, length);
|
|
stmt.bind_param(1, row);
|
|
stmt.fetch();
|
|
}
|
|
static void init_blob(database& db, const std::string& table, const std::string& column, int64_t row, int length)
|
|
{
|
|
return init_blob(db, table.c_str(), column.c_str(), row, length);
|
|
}
|
|
|
|
bool is_open() const { return m_blob!=nullptr; }
|
|
|
|
blobbuf* open(database& db, const char* table, const char* column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode, const char* dbname="main")
|
|
{
|
|
int flags=0;
|
|
if(mode&std::ios_base::out) flags=1;
|
|
if(sqlite3_blob_open(db.handle(), dbname, table, column, row, flags, &m_blob)==SQLITE_OK)
|
|
{
|
|
m_size=sqlite3_blob_bytes(m_blob)/sizeof(char);
|
|
// prepare buffer
|
|
size_t bufsize=std::min<size_t>(default_buffer_size, m_size);
|
|
if(mode&std::ios_base::in)
|
|
{
|
|
m_inbuf.resize(bufsize);
|
|
m_inpos=0;
|
|
setg(m_inbuf.data(), m_inbuf.data(), m_inbuf.data());
|
|
}
|
|
if(mode&std::ios_base::out)
|
|
{
|
|
m_outbuf.resize(bufsize);
|
|
m_outpos=0;
|
|
setp(m_outbuf.data(), m_outbuf.data()+bufsize);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
blobbuf* open(database& db, const std::string& table, const std::string& column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode, const char* dbname="main")
|
|
{
|
|
return open(db, table.c_str(), column.c_str(), row, mode, dbname);
|
|
}
|
|
|
|
blobbuf* close()
|
|
{
|
|
if(m_blob==nullptr)
|
|
return nullptr;
|
|
|
|
overflow();
|
|
sqlite3_blob_close(m_blob);
|
|
init();
|
|
return this;
|
|
}
|
|
|
|
std::streamoff size() const { return std::streamoff(m_size); }
|
|
|
|
void flush()
|
|
{
|
|
if(m_blob)
|
|
overflow();
|
|
}
|
|
|
|
protected:
|
|
enum { default_buffer_size = 4096 };
|
|
|
|
virtual pos_type seekoff( off_type off, std::ios_base::seekdir dir,
|
|
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out ) override
|
|
{
|
|
if(!is_open())
|
|
return pos_type(off_type(-1));
|
|
|
|
pos_type pos=0;
|
|
if(which&std::ios_base::out)
|
|
{
|
|
pos=seekoff(m_outpos, off, dir);
|
|
}
|
|
else if(which&std::ios_base::in)
|
|
{
|
|
pos=seekoff(m_inpos, off, dir);
|
|
}
|
|
return seekpos(pos, which);
|
|
}
|
|
|
|
virtual pos_type seekpos( pos_type pos,
|
|
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) override
|
|
{
|
|
if(!is_open())
|
|
return pos_type(off_type(-1));
|
|
if(pos>=m_size)
|
|
return pos_type(off_type(-1));
|
|
|
|
if(which&std::ios_base::out)
|
|
{
|
|
if(pos<m_outpos || pos>=m_outpos+off_type(egptr()-pbase()))
|
|
{
|
|
overflow();
|
|
m_outpos=pos;
|
|
setp(m_outbuf.data(), m_outbuf.data()+m_outbuf.size());
|
|
}
|
|
else
|
|
{
|
|
pbump(off_type(pos-pabs()));
|
|
}
|
|
}
|
|
else if(which&std::ios_base::in)
|
|
{
|
|
if(pos<m_inpos || pos>=m_inpos+off_type(epptr()-eback()))
|
|
{
|
|
m_inpos=pos;
|
|
setg(m_inbuf.data(), m_inbuf.data(), m_inbuf.data());
|
|
}
|
|
else
|
|
{
|
|
gbump(off_type(pos-gabs()));
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
virtual std::streamsize showmanyc() override
|
|
{
|
|
return m_size-pabs();
|
|
}
|
|
|
|
//reads characters from the associated input sequence to the get area
|
|
virtual int_type underflow() override
|
|
{
|
|
if(!is_open())
|
|
return traits_type::eof();
|
|
|
|
if(pptr()>pbase())
|
|
overflow();
|
|
|
|
off_type count=egptr()-eback();
|
|
pos_type next_pos=0;
|
|
if(count==0 && eback()==m_inbuf.data())
|
|
{
|
|
setg(m_inbuf.data(), m_inbuf.data(), m_inbuf.data()+m_inbuf.size());
|
|
count=m_inbuf.size();
|
|
}
|
|
else
|
|
{
|
|
next_pos=m_inpos+pos_type(count);
|
|
}
|
|
if(next_pos>=m_size)
|
|
return traits_type::eof();
|
|
|
|
count=std::min(count, m_size-next_pos);
|
|
m_inpos = next_pos;
|
|
if(sqlite3_blob_read(m_blob, eback(), count, m_inpos)!=SQLITE_OK)
|
|
return traits_type::eof();
|
|
setg(eback(), eback(), eback()+count);
|
|
return traits_type::to_int_type(*gptr());
|
|
}
|
|
|
|
/*//reads characters from the associated input sequence to the get area and advances the next pointer
|
|
virtual int_type uflow() override
|
|
{
|
|
|
|
}*/
|
|
|
|
//writes characters to the associated output sequence from the put area
|
|
virtual int_type overflow( int_type ch = traits_type::eof() ) override
|
|
{
|
|
if(!is_open())
|
|
return traits_type::eof();
|
|
|
|
if(pptr()!=pbase())
|
|
{
|
|
size_t count = pptr()-pbase();
|
|
if(sqlite3_blob_write(m_blob, pbase(), count, m_outpos)!=SQLITE_OK)
|
|
return traits_type::eof();
|
|
|
|
auto intersection = interval_intersection(m_inpos, egptr()-eback(), m_outpos, epptr()-pbase());
|
|
if(intersection.first!=intersection.second)
|
|
{
|
|
commit(intersection.first, intersection.second);
|
|
}
|
|
|
|
m_outpos+=count;
|
|
setp(pbase(), epptr());
|
|
}
|
|
if(!traits_type::eq_int_type(ch, traits_type::eof()))
|
|
{
|
|
char_type c = traits_type::to_char_type(ch);
|
|
if(m_outpos>=m_size)
|
|
return traits_type::eof();
|
|
if(sqlite3_blob_write(m_blob, &c, 1, m_outpos)!=SQLITE_OK)
|
|
return traits_type::eof();
|
|
auto intersection = interval_intersection(m_inpos, egptr()-eback(), m_outpos, 1);
|
|
if(intersection.first!=intersection.second)
|
|
{
|
|
eback()[intersection.first-m_inpos]=c;
|
|
}
|
|
m_outpos+=1;
|
|
|
|
}
|
|
return ch;
|
|
}
|
|
|
|
virtual int_type pbackfail( int_type c = traits_type::eof() ) override
|
|
{
|
|
if (gptr() == 0
|
|
|| gptr() <= eback()
|
|
|| (!traits_type::eq_int_type(traits_type::eof(), c)
|
|
&& !traits_type::eq(traits_type::to_char_type(c), gptr()[-1])))
|
|
{
|
|
return (traits_type::eof()); // can't put back, fail
|
|
}
|
|
else
|
|
{ // back up one position and store put-back character
|
|
gbump(-1);
|
|
if (!traits_type::eq_int_type(traits_type::eof(), c))
|
|
*gptr() = traits_type::to_char_type(c);
|
|
return (traits_type::not_eof(c));
|
|
}
|
|
}
|
|
|
|
private:
|
|
sqlite3_blob* m_blob;
|
|
std::vector<char> m_inbuf;
|
|
std::vector<char> m_outbuf;
|
|
pos_type m_size;
|
|
pos_type m_inpos; //position in the input sequence
|
|
pos_type m_outpos; //position in the output sequence
|
|
|
|
void init()
|
|
{
|
|
m_blob=nullptr;
|
|
m_size=0;
|
|
m_inpos=m_outpos=0;
|
|
m_set_eback=m_set_egptr=nullptr;
|
|
m_back_char=0;
|
|
setg(nullptr, nullptr, nullptr);
|
|
setp(nullptr, nullptr);
|
|
}
|
|
|
|
off_type seekoff(off_type position, off_type off, std::ios_base::seekdir dir)
|
|
{
|
|
off_type result=0;
|
|
switch(dir)
|
|
{
|
|
case std::ios_base::beg:
|
|
result=off;
|
|
break;
|
|
case std::ios_base::cur:
|
|
result=position+off;
|
|
break;
|
|
case std::ios_base::end:
|
|
result=m_size-off;
|
|
break;
|
|
}
|
|
if(result>m_size)
|
|
result=m_size;
|
|
return result;
|
|
}
|
|
|
|
void reset_back()
|
|
{ // restore buffer after putback
|
|
if (this->eback() == &m_back_char)
|
|
this->setg(m_set_eback, m_set_eback, m_set_egptr);
|
|
}
|
|
|
|
void set_back()
|
|
{ // set up putback area
|
|
if (this->eback() != &m_back_char)
|
|
{ // save current get buffer
|
|
m_set_eback = this->eback();
|
|
m_set_egptr = this->egptr();
|
|
}
|
|
this->setg(&m_back_char, &m_back_char, &m_back_char + 1);
|
|
}
|
|
|
|
char_type *m_set_eback { nullptr }; // saves eback() during one-element putback
|
|
char_type *m_set_egptr { nullptr }; // saves egptr()
|
|
char_type m_back_char { 0 };
|
|
|
|
void move_data(blobbuf& other)
|
|
{
|
|
m_blob=other.m_blob;
|
|
other.m_blob=nullptr;
|
|
m_size=other.m_size;
|
|
other.m_size=0;
|
|
m_inpos=other.m_inpos;
|
|
other.m_inpos=0;
|
|
m_outpos=other.m_outpos;
|
|
other.m_outpos=0;
|
|
}
|
|
|
|
static std::pair<pos_type, pos_type> interval_intersection(pos_type first1, pos_type last1, pos_type first2, pos_type last2)
|
|
{
|
|
if(first1>first2)
|
|
{
|
|
std::swap(first1, first2);
|
|
std::swap(last1, last2);
|
|
}
|
|
|
|
if(first2<last1)
|
|
return std::make_pair(first2, std::min(last1, last2));
|
|
else
|
|
return std::make_pair(0, 0);
|
|
}
|
|
|
|
static std::pair<pos_type, pos_type> interval_intersection(pos_type first1, off_type count1, pos_type first2, off_type count2)
|
|
{
|
|
return interval_intersection(first1, first1+count1, first2, first2+count2);
|
|
}
|
|
|
|
void commit(off_type first, off_type last)
|
|
{
|
|
char_type* src= pbase()+(first-m_outpos);
|
|
char_type* dest = eback()+(first-m_inpos);
|
|
memmove(dest, src, last-first);
|
|
}
|
|
|
|
pos_type gabs() const // absolute offset of input pointer in blob field
|
|
{
|
|
return m_inpos+off_type(gptr()-eback());
|
|
}
|
|
|
|
pos_type pabs() const // absolute offset of output pointer in blob field
|
|
{
|
|
return m_outpos+off_type(pptr()-pbase());
|
|
}
|
|
};
|
|
|
|
class iblobstream : public std::istream
|
|
{
|
|
public:
|
|
iblobstream() : std::istream(&m_buffer) { }
|
|
iblobstream(database& db, const char* table, const char* column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
|
|
: std::istream(&m_buffer)
|
|
{
|
|
open(db, table, column, row, mode, dbname);
|
|
}
|
|
iblobstream(database& db, const std::string& table, const std::string& column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
|
|
: std::istream(&m_buffer)
|
|
{
|
|
open(db, table, column, row, mode, dbname);
|
|
}
|
|
iblobstream(const iblobstream&) = delete;
|
|
iblobstream(iblobstream&& src) : std::istream(&m_buffer), m_buffer(std::move(src.m_buffer)) { }
|
|
|
|
iblobstream& operator=(const iblobstream&) = delete;
|
|
iblobstream& operator=(iblobstream&& src)
|
|
{
|
|
m_buffer.operator =(std::move(src.m_buffer));
|
|
}
|
|
|
|
bool is_open() const { return m_buffer.is_open(); }
|
|
|
|
void open(database& db, const char* table, const char* column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
|
|
{
|
|
if(m_buffer.open(db, table, column, row, mode|std::ios_base::in, dbname)==nullptr)
|
|
this->setstate(std::ios_base::failbit);
|
|
else
|
|
this->clear();
|
|
}
|
|
void open(database& db, const std::string& table, const std::string& column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
|
|
{
|
|
open(db, table.c_str(), column.c_str(), row, mode, dbname);
|
|
}
|
|
|
|
void close()
|
|
{
|
|
if(m_buffer.close()==nullptr)
|
|
this->setstate(std::ios_base::failbit);
|
|
}
|
|
|
|
blobbuf* rdbuf() const
|
|
{
|
|
return const_cast<blobbuf*>(&m_buffer);
|
|
}
|
|
|
|
std::streamoff blob_size() const { return m_buffer.size(); }
|
|
|
|
private:
|
|
blobbuf m_buffer;
|
|
};
|
|
|
|
class oblobstream : public std::ostream
|
|
{
|
|
public:
|
|
oblobstream() : std::ostream(&m_buffer) { }
|
|
oblobstream(database& db, const char* table, const char* column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
|
|
: std::ostream(&m_buffer)
|
|
{
|
|
open(db, table, column, row, mode, dbname);
|
|
}
|
|
oblobstream(database& db, const std::string& table, const std::string& column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
|
|
: std::ostream(&m_buffer)
|
|
{
|
|
open(db, table, column, row, mode, dbname);
|
|
}
|
|
oblobstream(const oblobstream&) = delete;
|
|
oblobstream(oblobstream&& src) : std::ostream(&m_buffer), m_buffer(std::move(src.m_buffer)) { }
|
|
|
|
oblobstream& operator=(const oblobstream&) = delete;
|
|
oblobstream& operator=(oblobstream&& src)
|
|
{
|
|
m_buffer.operator =(std::move(src.m_buffer));
|
|
}
|
|
|
|
bool is_open() const { return m_buffer.is_open(); }
|
|
|
|
void open(database& db, const char* table, const char* column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::out, const char* dbname="main")
|
|
{
|
|
if(m_buffer.open(db, table, column, row, mode|std::ios_base::out, dbname)==nullptr)
|
|
this->setstate(std::ios_base::failbit);
|
|
else
|
|
this->clear();
|
|
}
|
|
void open(database& db, const std::string& table, const std::string& column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::out, const char* dbname="main")
|
|
{
|
|
open(db, table.c_str(), column.c_str(), row, mode, dbname);
|
|
}
|
|
|
|
void close()
|
|
{
|
|
if(m_buffer.close()==nullptr)
|
|
this->setstate(std::ios_base::failbit);
|
|
}
|
|
|
|
blobbuf* rdbuf() const
|
|
{
|
|
return const_cast<blobbuf*>(&m_buffer);
|
|
}
|
|
|
|
std::streamoff blob_size() const { return m_buffer.size(); }
|
|
|
|
private:
|
|
blobbuf m_buffer;
|
|
};
|
|
|
|
class blobstream : public std::iostream
|
|
{
|
|
public:
|
|
blobstream() : std::iostream(&m_buffer) { }
|
|
blobstream(database& db, const char* table, const char* column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
|
|
: std::iostream(&m_buffer)
|
|
{
|
|
open(db, table, column, row, mode, dbname);
|
|
}
|
|
blobstream(database& db, const std::string& table, const std::string& column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
|
|
: std::iostream(&m_buffer)
|
|
{
|
|
open(db, table, column, row, mode, dbname);
|
|
}
|
|
blobstream(const blobstream&) = delete;
|
|
blobstream(blobstream&& src) : std::iostream(&m_buffer), m_buffer(std::move(src.m_buffer)) { }
|
|
|
|
blobstream& operator=(const blobstream&) = delete;
|
|
blobstream& operator=(blobstream&& src)
|
|
{
|
|
m_buffer.operator =(std::move(src.m_buffer));
|
|
}
|
|
|
|
bool is_open() const { return m_buffer.is_open(); }
|
|
|
|
void open(database& db, const char* table, const char* column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in|std::ios_base::out, const char* dbname="main")
|
|
{
|
|
if(m_buffer.open(db, table, column, row, mode|std::ios_base::in|std::ios_base::out, dbname)==nullptr)
|
|
this->setstate(std::ios_base::failbit);
|
|
else
|
|
this->clear();
|
|
}
|
|
void open(database& db, const std::string& table, const std::string& column, sqlite3_int64 row,
|
|
std::ios_base::openmode mode=std::ios_base::in|std::ios_base::out, const char* dbname="main")
|
|
{
|
|
open(db, table.c_str(), column.c_str(), row, mode, dbname);
|
|
}
|
|
|
|
void close()
|
|
{
|
|
if(m_buffer.close()==nullptr)
|
|
this->setstate(std::ios_base::failbit);
|
|
}
|
|
|
|
blobbuf* rdbuf() const
|
|
{
|
|
return const_cast<blobbuf*>(&m_buffer);
|
|
}
|
|
|
|
std::streamoff blob_size() const { return m_buffer.size(); }
|
|
|
|
private:
|
|
blobbuf m_buffer;
|
|
};
|
|
|
|
|
|
typedef qtl::transaction<database> transaction;
|
|
|
|
template<typename Record>
|
|
using query_iterator = qtl::query_iterator<statement, Record>;
|
|
|
|
template<typename Record>
|
|
using query_result = qtl::query_result<statement, Record>;
|
|
|
|
template<typename Params>
|
|
inline statement& operator<<(statement& stmt, const Params& params)
|
|
{
|
|
stmt.reset();
|
|
stmt.execute(params);
|
|
return stmt;
|
|
}
|
|
|
|
template<>
|
|
inline const char* statement::get_text_value<char>(int col) const
|
|
{
|
|
return (const char*)sqlite3_column_text(m_stmt, col);
|
|
}
|
|
template<>
|
|
inline const wchar_t* statement::get_text_value<wchar_t>(int col) const
|
|
{
|
|
return (const wchar_t*)sqlite3_column_text16(m_stmt, col);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif //_QTL_SQLITE_H_
|