Sqlite3*: Complete SQLITE3 support.

This commit is contained in:
ZmnSCPxj jxPCSnmZ 2020-09-12 15:29:03 +08:00
parent c332ce88bf
commit 83b09ed680
15 changed files with 652 additions and 11 deletions

View file

@ -121,8 +121,17 @@ libclboss_la_SOURCES = \
S/Bus.hpp \
S/Detail/Signal.hpp \
S/Detail/SignalBase.hpp \
Sqlite3.hpp \
Sqlite3/Db.cpp \
Sqlite3/Db.hpp \
Sqlite3/Detail/binds.cpp \
Sqlite3/Detail/binds.hpp \
Sqlite3/Detail/columns.cpp \
Sqlite3/Detail/columns.hpp \
Sqlite3/Query.cpp \
Sqlite3/Query.hpp \
Sqlite3/Result.cpp \
Sqlite3/Result.hpp \
Sqlite3/Tx.cpp \
Sqlite3/Tx.hpp \
Stats/RunningMean.cpp \

9
Sqlite3.hpp Normal file
View file

@ -0,0 +1,9 @@
#ifndef SQLITE3_HPP
#define SQLITE3_HPP
#include"Sqlite3/Db.hpp"
#include"Sqlite3/Query.hpp"
#include"Sqlite3/Result.hpp"
#include"Sqlite3/Tx.hpp"
#endif /* !defined(SQLITE3_HPP) */

View file

@ -5,6 +5,7 @@
#include<string>
namespace Ev { template<typename a> class Io; }
namespace Sqlite3 { class Result; }
namespace Sqlite3 { class Tx; }
namespace Sqlite3 {
@ -22,6 +23,7 @@ private:
class Impl;
std::shared_ptr<Impl> pimpl;
friend class Sqlite3::Result;
friend class Sqlite3::Tx;
void* get_connection() const;
@ -40,6 +42,8 @@ public:
Db() =default;
Db(Db const&) =default;
Db(Db&&) =default;
Db& operator=(Db const&) =default;
Db& operator=(Db&&) =default;
~Db() =default;
/* If the Db is false (invalid), you cannot use transact(). */

35
Sqlite3/Detail/binds.cpp Normal file
View file

@ -0,0 +1,35 @@
#include"Sqlite3/Detail/binds.hpp"
#include<sqlite3.h>
#include<stdexcept>
namespace Sqlite3 { namespace Detail {
void bind_d(void* stmt, int l, double v) {
auto res = sqlite3_bind_double( (sqlite3_stmt*) stmt
, l, v
);
if (res != SQLITE_OK)
throw std::runtime_error("Sqlite3: bind error.");
}
void bind_i(void* stmt, int l, std::int64_t v) {
auto res = sqlite3_bind_int64( (sqlite3_stmt*) stmt
, l, v
);
if (res != SQLITE_OK)
throw std::runtime_error("Sqlite3: bind error.");
}
void bind_s(void* stmt, int l, std::string v) {
auto res = sqlite3_bind_text( (sqlite3_stmt*) stmt
, l, v.c_str(), v.size()
, SQLITE_TRANSIENT
);
if (res != SQLITE_OK)
throw std::runtime_error("Sqlite3: bind error.");
}
void bind_null(void* stmt, int l) {
auto res = sqlite3_bind_null( (sqlite3_stmt*) stmt, l);
if (res != SQLITE_OK)
throw std::runtime_error("Sqlite3: bind error.");
}
}}

102
Sqlite3/Detail/binds.hpp Normal file
View file

@ -0,0 +1,102 @@
#ifndef SQLITE3_DETAILS_BINDS_HPP
#define SQLITE3_DETAILS_BINDS_HPP
#include<cstddef>
#include<cstdint>
#include<string>
namespace Sqlite3 { namespace Detail {
void bind_d(void* stmt, int l, double);
void bind_i(void* stmt, int l, std::int64_t);
void bind_s(void* stmt, int l, std::string);
void bind_null(void *stmt, int l);
template<typename a>
struct Bind;
template<>
struct Bind<float> {
static void bind(void *stmt, int l, float v) {
bind_d(stmt, l, v);
}
};
template<>
struct Bind<double> {
static void bind(void *stmt, int l, double v) {
bind_d(stmt, l, v);
}
};
template<>
struct Bind<std::int8_t> {
static void bind(void *stmt, int l, std::int8_t v) {
bind_i(stmt, l, v);
}
};
template<>
struct Bind<std::uint8_t> {
static void bind(void *stmt, int l, std::uint8_t v) {
bind_i(stmt, l, v);
}
};
template<>
struct Bind<std::int16_t> {
static void bind(void *stmt, int l, std::int16_t v) {
bind_i(stmt, l, v);
}
};
template<>
struct Bind<std::uint16_t> {
static void bind(void *stmt, int l, std::uint16_t v) {
bind_i(stmt, l, v);
}
};
template<>
struct Bind<std::int32_t> {
static void bind(void *stmt, int l, std::int32_t v) {
bind_i(stmt, l, v);
}
};
template<>
struct Bind<std::uint32_t> {
static void bind(void *stmt, int l, std::uint32_t v) {
bind_i(stmt, l, v);
}
};
template<>
struct Bind<std::int64_t> {
static void bind(void *stmt, int l, std::int64_t v) {
bind_i(stmt, l, v);
}
};
template<>
struct Bind<std::uint64_t> {
static void bind(void *stmt, int l, std::uint64_t v) {
bind_i(stmt, l, v);
}
};
template<>
struct Bind<char const*> {
static void bind(void *stmt, int l, char const* v) {
bind_s(stmt, l, v);
}
};
template<>
struct Bind<std::string> {
static void bind(void *stmt, int l, std::string v) {
bind_s(stmt, l, std::move(v));
}
};
template<>
struct Bind<std::nullptr_t> {
static void bind(void *stmt, int l, std::nullptr_t) {
bind_null(stmt, l);
}
};
}}
#endif /* !defined(SQLITE3_DETAILS_BINDS_HPP) */

View file

@ -0,0 +1,25 @@
#include"Sqlite3/Detail/columns.hpp"
#include<algorithm>
#include<sqlite3.h>
namespace Sqlite3 { namespace Detail {
double column_d(void* stmt, int c) {
return sqlite3_column_double((sqlite3_stmt*) stmt, c);
}
std::int64_t column_i(void* stmt, int c) {
return sqlite3_column_int64((sqlite3_stmt*) stmt, c);
}
std::string column_s(void* vstmt, int c) {
auto stmt = (sqlite3_stmt*) vstmt;
auto len = sqlite3_column_bytes(stmt, c);
auto dat = sqlite3_column_text(stmt, c);
auto s = std::string();
s.resize(len);
std::copy(dat, dat + len, s.begin());
return s;
}
}}

View file

@ -0,0 +1,98 @@
#ifndef SQLITE3_DETAIL_COLUMNS_HPP
#define SQLITE3_DETAIL_COLUMNS_HPP
#include<cstdint>
#include<string>
namespace Sqlite3 { namespace Detail {
double column_d(void* stmt, int c);
std::int64_t column_i(void* stmt, int c);
std::string column_s(void* stmt, int c);
template<typename a>
struct Column;
template<>
struct Column<float> {
static
float column(void* stmt, int c) {
return column_d(stmt, c);
}
};
template<>
struct Column<double> {
static
double column(void* stmt, int c) {
return column_d(stmt, c);
}
};
template<>
struct Column<std::int8_t> {
static
std::int8_t column(void* stmt, int c) {
return column_i(stmt, c);
}
};
template<>
struct Column<std::uint8_t> {
static
std::uint8_t column(void* stmt, int c) {
return column_i(stmt, c);
}
};
template<>
struct Column<std::int16_t> {
static
std::int16_t column(void* stmt, int c) {
return column_i(stmt, c);
}
};
template<>
struct Column<std::uint16_t> {
static
std::uint16_t column(void* stmt, int c) {
return column_i(stmt, c);
}
};
template<>
struct Column<std::int32_t> {
static
std::int32_t column(void* stmt, int c) {
return column_i(stmt, c);
}
};
template<>
struct Column<std::uint32_t> {
static
std::uint32_t column(void* stmt, int c) {
return column_i(stmt, c);
}
};
template<>
struct Column<std::int64_t> {
static
std::int64_t column(void* stmt, int c) {
return column_i(stmt, c);
}
};
template<>
struct Column<std::uint64_t> {
static
std::uint64_t column(void* stmt, int c) {
return column_i(stmt, c);
}
};
template<>
struct Column<std::string> {
static
std::string column(void* stmt, int c) {
return column_s(stmt, c);
}
};
}}
#endif /* !defined(SQLITE3_DETAIL_COLUMNS_HPP) */

63
Sqlite3/Query.cpp Normal file
View file

@ -0,0 +1,63 @@
#include"Sqlite3/Db.hpp"
#include"Sqlite3/Query.hpp"
#include"Sqlite3/Result.hpp"
#include"Util/make_unique.hpp"
#include<sqlite3.h>
#include<stdexcept>
namespace Sqlite3 {
class Query::Impl {
private:
Db db;
sqlite3_stmt* stmt;
public:
Impl( Db const& db_
, void* stmt_
) : db(db_)
, stmt((sqlite3_stmt*) stmt_)
{ }
~Impl() {
if (stmt)
(void) sqlite3_finalize(stmt);
}
void* get_stmt() const { return stmt; }
int get_location(char const* field) const {
auto res = sqlite3_bind_parameter_index(stmt, field);
if (res == 0)
throw std::runtime_error(
std::string("Sqlite3::Query::bind: "
"no field: "
) + field
);
return res;
}
Result execute() {
/* Move responsibility off ourself. */
auto my_stmt = stmt;
stmt = nullptr;
/* Result constructor executes first sqlite3_step. */
return Result(db, my_stmt);
}
};
Query::Query(Sqlite3::Db const& db, void* stmt)
: pimpl(Util::make_unique<Impl>(db, stmt)) { }
Query::Query(Query&& o) : pimpl(std::move(o.pimpl)) { }
Query::~Query() { }
void* Query::get_stmt() const { return pimpl->get_stmt(); }
int Query::get_location(const char* field) const {
return pimpl->get_location(field);
}
Result Query::execute() {
auto r = pimpl->execute();
pimpl = nullptr;
return r;
}
}

66
Sqlite3/Query.hpp Normal file
View file

@ -0,0 +1,66 @@
#ifndef SQLITE3_QUERY_HPP
#define SQLITE3_QUERY_HPP
#include"Sqlite3/Detail/binds.hpp"
#include<memory>
#include<string>
namespace Sqlite3 { class Db; }
namespace Sqlite3 { class Result; }
namespace Sqlite3 { class Tx; }
namespace Sqlite3 {
/** class Sqlite3::Query
*
* @brief Represents a query that will have parameters bound,
* then later executed to produce a result.
*/
class Query {
private:
class Impl;
std::unique_ptr<Impl> pimpl;
friend class Sqlite3::Tx;
Query(Sqlite3::Db const&, void*);
void* get_stmt() const;
int get_location(char const*) const;
public:
Query() =delete;
Query(Query&&);
~Query();
/** Sqlite3::Query::bind
*
* @brief binds a named parameter of the query.
* Named parameters are of the form :VVV, @VVV,
* or $VVV, where VVV is alphanumeric.
*/
template<typename a>
Query& bind(char const* field, a value) {
Detail::Bind<a>::bind( get_stmt(), get_location(field)
, std::move(value)
);
return *this;
}
template<typename a>
Query& bind(std::string const& field, a value) {
return bind<a>(field.c_str(), value);
}
/** Sqlite3::Query::execute
*
* @brief executes the query.
* Pre-condition: you should have bound all the
* parameters (or else they would be NULL).
* Post-condition: the query cannot be reused
* and is in an "invalid" state.
*/
Result execute();
};
}
#endif /* !defined(SQLITE3_QUERY_HPP) */

View file

@ -6,14 +6,18 @@ Here is our desired interface:
/* This could block if a transaction is currently in-flight. */
return db.transact();
}).then([](Sqlite3::Tx tx) {
auto it = tx.query("SELECT (a, b) FROM tablename WHERE id=:ID")
.bind(":ID", 42)
.execute()
;
/* `it` is a single-pass input iterator. */
for (; it != tx.end(); ++it) {
auto a = it->column<int>(0);
auto b = it->column<std::string>(1);
auto res = tx.query("SELECT (a, b) FROM tablename WHERE id=:ID")
.bind(":ID", 42)
.execute()
;
/* `res` is a single-pass sequence!
* It has an iterator type that is an input iterator
* (can only dereference and advance, but copying
* does not create a distinct state).
*/
for (auto r : res) {
auto a = r.column<int>(0);
auto b = r.column<std::string>(1);
process(a, b);
}
/* Sqlite3::Tx cannot be copied, but can be moved.

39
Sqlite3/Result.cpp Normal file
View file

@ -0,0 +1,39 @@
#include"Sqlite3/Db.hpp"
#include"Sqlite3/Result.hpp"
#include<sqlite3.h>
namespace Sqlite3 {
Result::Result(Sqlite3::Db const& db_, void* stmt_
) : db(db_), stmt(stmt_) {
advance();
}
Result::Result(Result&& o) {
db = std::move(o.db);
stmt = o.stmt;
o.stmt = nullptr;
}
Result::~Result() {
if (stmt)
sqlite3_finalize((sqlite3_stmt*) stmt);
}
bool Result::advance() {
auto ss = (sqlite3_stmt*) stmt;
auto res = sqlite3_step(ss);
if (res == SQLITE_DONE) {
sqlite3_finalize((sqlite3_stmt*) stmt);
stmt = nullptr;
return false;
} else if (res == SQLITE_ROW)
return true;
else {
auto connection = (sqlite3*) db.get_connection();
auto err = std::string(sqlite3_errmsg(connection));
throw std::runtime_error(
std::string("Sqlite3::Result: ") + err
);
}
}
}

130
Sqlite3/Result.hpp Normal file
View file

@ -0,0 +1,130 @@
#ifndef SQLITE3_RESULT_HPP
#define SQLITE3_RESULT_HPP
#include"Sqlite3/Db.hpp"
#include"Sqlite3/Detail/columns.hpp"
#include<iterator>
namespace Sqlite3 { class Query; }
namespace Sqlite3 { class Result; }
namespace Sqlite3 { class Row; }
namespace Sqlite3 {
/** class Sqlite3::Row
*
* @brief repersents a row in the results.
*/
class Row {
private:
Result* r;
friend class Sqlite3::Result;
explicit
Row(Result& r_) : r(&r_) { }
Row() : r(nullptr) { }
public:
Row(Row const&) =delete;
Row(Row&) =delete;
~Row() =default;
template<typename a>
a get(int c);
};
/** Sqlite3::Result
*
* @brief sequence of rows emitted by a query.
*
* @desc
* IMPORTANT a `Sqlite3::Result` can only be traversed
* exactly once!!!
*/
class Result {
private:
Sqlite3::Db db;
void* stmt;
friend class Sqlite3::Query;
friend class Sqlite3::Row;
Result(Sqlite3::Db const& db_, void* stmt_);
/* Return false at end of result. */
bool advance();
public:
Result() =delete;
Result(Result const&) =delete;
Result(Result&&);
~Result();
Result& operator=(Result&& o) {
auto tmp = std::move(o);
std::swap(db, tmp.db);
std::swap(stmt, tmp.stmt);
return *this;
}
/** Sqlite3::Result::iterator
*
* @brief An input iterator that lets you traverse
* the results once.
*/
class iterator : Row {
private:
friend class Sqlite3::Result;
explicit
iterator(Result& r_) : Row(r_) {
if (!r->stmt)
r = nullptr;
}
public:
iterator() : Row() { }
iterator(iterator const& o) : Row() {
r = o.r;
}
bool operator==(iterator const& o) const {
return o.r == r;
}
bool operator!=(iterator const& o) const {
return o.r != r;
}
iterator& operator++() {
if (r)
if (!r->advance())
r = nullptr;
return *this;
}
typedef std::input_iterator_tag iterator_category;
typedef Row* pointer;
typedef Row reference;
typedef Row value_type;
Row& operator*() {
return *this;
}
};
iterator begin() {
return iterator(*this);
}
iterator end() {
return iterator();
}
};
template<typename a>
a Row::get(int c) {
return Detail::Column<a>::column(r->stmt, c);
}
}
#endif /* !defined(SQLITE3_RESULT_HPP) */

View file

@ -1,4 +1,5 @@
#include"Sqlite3/Db.hpp"
#include"Sqlite3/Query.hpp"
#include"Sqlite3/Tx.hpp"
#include"Util/make_unique.hpp"
#include<sqlite3.h>
@ -47,6 +48,18 @@ public:
if (res != SQLITE_OK)
throw_sqlite3(q);
}
Query query(char const* sql) {
auto connection = (sqlite3*) db.get_connection();
auto stmt = (sqlite3_stmt*) nullptr;
auto res = sqlite3_prepare_v2( connection, sql, -1
, &stmt, nullptr
);
if (res != SQLITE_OK)
throw_sqlite3(sql);
return Query(db, stmt);
}
};
Tx::Tx(Sqlite3::Db const& db)
@ -69,6 +82,10 @@ void Tx::rollback() {
pimpl = nullptr;
}
Query Tx::query(char const* sql) {
return pimpl->query(sql);
}
void Tx::query_execute(char const* q) {
return pimpl->query_execute(q);
}

View file

@ -52,7 +52,13 @@ public:
operator bool() const { return !!pimpl; }
bool operator!() const { return !pimpl; }
Sqlite3::Query query(std::string const&);
/** Sqlite3::Tx::query
*
* @brief Create a query; provide the query template
* and bind any parameters to it, then execute to
* retrieve results.
*/
Sqlite3::Query query(char const*);
/** Sqlite3::Tx::query_execute
*

View file

@ -1,6 +1,5 @@
#undef NDEBUG
#include"Sqlite3/Db.hpp"
#include"Sqlite3/Tx.hpp"
#include"Sqlite3.hpp"
#include"Ev/Io.hpp"
#include"Ev/concurrent.hpp"
#include"Ev/start.hpp"
@ -47,6 +46,41 @@ int main() {
return db.transact();
}).then([&](Sqlite3::Tx tx) {
tx.query_execute("CREATE TABLE \"foo\" (c1 INTEGER, c2 TEXT);");
tx.commit();
/* Test full query interface. */
return db.transact();
}).then([&](Sqlite3::Tx tx) {
auto res = tx.query("INSERT INTO \"foo\" VALUES(:c1, :c2)")
.bind(":c1", 42)
.bind(":c2", "some text")
.execute()
;
for (auto& r : res) {
(void) r;
/* Should have empty result! */
assert(false);
}
tx.commit();
/* Test full query interface again. */
return db.transact();
}).then([&](Sqlite3::Tx tx) {
auto res = tx.query("SELECT c1, c2 FROM \"foo\"")
.execute()
;
auto flag = false;
for (auto& r : res) {
/* Should have single result! */
assert(!flag);
flag = true;
/* Should be what we inserted. */
assert(r.get<int>(0) == 42);
assert(r.get<std::string>(1) == "some text");
}
/* Should have result! */
assert(flag);
tx.commit();
return Ev::lift(0);
});