Skip to content

Commit

Permalink
SQLite3 support (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
bgs99 authored May 17, 2024
1 parent c8be189 commit 12d7861
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ IncludeCategories:
- Regex: "^<pfr-orm/"
CaseSensitive: true
Priority: 1
- Regex: "^<(fmt/|boost/|libpq-fe\\.h>)"
- Regex: "^<(fmt/|boost/|libpq-fe\\.h|sqlite3\\.h>)"
CaseSensitive: true
Priority: 3
- Regex: "^<"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
uses: actions/checkout@v4

- name: Install dependencies
run: sudo apt-get install libfmt-dev libpq-dev
run: sudo apt-get install libfmt-dev libpq-dev libsqlite3-dev

- name: Configure CMake
run: CXX=${{ env.compiler }} cmake -B ${{ env.build_dir }} -S ${{ github.workspace }} -DPFR_ORM_ASAN:BOOL=ON -DPFR_ORM_TEST:BOOL=ON
Expand Down
8 changes: 5 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ add_subdirectory(vendor)

find_package(fmt REQUIRED)
find_package(PostgreSQL REQUIRED)
find_package(SQLite3 REQUIRED)

add_library(pfr-orm STATIC)
target_compile_features(pfr-orm PUBLIC cxx_std_20)
target_sources(pfr-orm PRIVATE lib/postgres/utils.cpp
lib/postgres/operations.cpp)
target_sources(
pfr-orm PRIVATE lib/sqlite/utils.cpp lib/sqlite/operations.cpp
lib/postgres/utils.cpp lib/postgres/operations.cpp)
target_include_directories(pfr-orm PUBLIC include)
target_link_libraries(
pfr-orm
PUBLIC Boost::pfr
PRIVATE PostgreSQL::PostgreSQL fmt::fmt)
PRIVATE PostgreSQL::PostgreSQL SQLite::SQLite3 fmt::fmt)

option(PFR_ORM_USE_GSL_SPAN
"Use Microsoft.GSL for span implementation instead of std::span" OFF)
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion include/pfr-orm/postgres/operations.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <pfr-orm/definitions.hpp>
#include <pfr-orm/detail/operations.hpp>
#include <pfr-orm/postgres/detail/operations.hpp>
#include <pfr-orm/postgres/utils.hpp>

namespace pfrorm::postgres {
Expand Down
12 changes: 12 additions & 0 deletions include/pfr-orm/sqlite/detail/operations.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include <pfr-orm/definitions.hpp>
#include <pfr-orm/sqlite/utils.hpp>

namespace pfrorm::sqlite::detail {

void createTable(Connection &connection, const EntityDescription &entity);

bool exists(Connection &connection, const EntityDescription &entity);

} // namespace pfrorm::sqlite::detail
17 changes: 17 additions & 0 deletions include/pfr-orm/sqlite/operations.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <pfr-orm/definitions.hpp>
#include <pfr-orm/sqlite/detail/operations.hpp>
#include <pfr-orm/sqlite/utils.hpp>

namespace pfrorm::sqlite {

template <DatabaseEntity T> void createTable(Connection &connection) {
return detail::createTable(connection, DatabaseEntityDescription<T>);
}

template <typename T> bool exists(Connection &connection) {
return detail::exists(connection, DatabaseEntityDescription<T>);
}

} // namespace pfrorm::sqlite
117 changes: 54 additions & 63 deletions include/pfr-orm/sqlite/utils.hpp
Original file line number Diff line number Diff line change
@@ -1,96 +1,87 @@
#pragma once

#include <cstdint>
#include <string>
#include <filesystem>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string_view>

struct pg_conn;
struct pg_result;
struct sqlite3;
struct sqlite3_stmt;

namespace pfrorm::postgres {
namespace pfrorm::sqlite {

class Str {
class Row {
public:
Str(char *str) : str(str) {}
~Str();
class InvalidRowError : public std::out_of_range {
public:
using std::out_of_range::out_of_range;
};

[[nodiscard]] int getColumnCount() const { return this->columnCount; }

/// @throws InvalidRowError if the column is outside of the range
[[nodiscard]] std::string_view text(int column) const;

[[nodiscard]] std::string_view view() const { return str; }
operator std::string_view() const { return str; }
/// @throws InvalidRowError if the column is outside of the range
[[nodiscard]] std::int64_t bigint(int column) const;

Str(const Str &) = delete;
Str(Str &&) = delete;
Str &operator=(const Str &) = delete;
Str &operator=(Str &&) = delete;
/// @throws InvalidRowError if the column is outside of the range
[[nodiscard]] bool boolean(int column) const;

private:
char *str;
sqlite3_stmt *statement;

int columnCount;

friend class Result;

explicit Row(sqlite3_stmt *const statement, const int columnCount)
: statement(statement), columnCount(columnCount) {}
};

class Result {
public:
Result(pg_result *result) : result(result) {}
~Result();
[[nodiscard]] std::optional<Row> getRow() const {
if (!this->statement.has_value()) {
return std::nullopt;
}
return Row{statement->get(), this->columnCount};
}

[[nodiscard]] int status() const;
[[nodiscard]] std::string_view value(int row, int column) const;
bool nextRow();

Result(const Result &) = delete;
Result(Result &&) noexcept;
Result &operator=(const Result &) = delete;
Result &operator=(Result &&) = delete;
[[nodiscard]] int getColumnCount() const { return this->columnCount; }

private:
pg_result *result;
using Statement = std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt *)>;

std::optional<Statement> statement;

int columnCount = 0;

friend class Connection;

explicit Result(Statement statement);
};

class Connection {
public:
Connection(const std::string &connectionStr);
~Connection();
static Connection fromRaw(sqlite3 *connection);

Connection(const Connection &) = delete;
Connection(Connection &&) noexcept;
Connection &operator=(const Connection &) = delete;
Connection &operator=(Connection &&) = delete;
static Connection inMemory(const char *name);

[[nodiscard]] Str escapeIdentifier(std::string_view identifier) const;
static Connection inFile(const std::filesystem::path &path);

Result execute(const std::string &statement);
void execute(std::string_view statement);

Result query(const std::string &statement);
Result query(std::string_view statement);

private:
pg_conn *connection;
};

struct ParameterTraits {
struct Parameter {
std::string data;
bool isBinary; // TODO: pass everything as binary
};
std::unique_ptr<sqlite3, int (*)(sqlite3 *)> connection;

template <typename T> static Parameter toParam(const T &value) = delete;
explicit Connection(sqlite3 *connection);
};

template <>
inline ParameterTraits::Parameter
ParameterTraits::toParam(const uint64_t &value) {
return {
.data = std::to_string(value),
.isBinary = false,
};
}

template <>
inline ParameterTraits::Parameter
ParameterTraits::toParam(const std::string &value) {
return {
.data = value,
.isBinary = true,
};
}

template <typename T>
concept AsParameter = requires { ParameterTraits::toParam<T>; };

} // namespace pfrorm::postgres
} // namespace pfrorm::sqlite
93 changes: 93 additions & 0 deletions lib/sqlite/operations.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "../detail/multilambda.hpp"

#include <pfr-orm/api.hpp>
#include <pfr-orm/definitions.hpp>
#include <pfr-orm/sqlite/detail/operations.hpp>
#include <pfr-orm/sqlite/utils.hpp>

#include <cstddef>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>

#include <fmt/core.h>
#include <fmt/format.h>

namespace pfrorm::sqlite::detail {

namespace {

std::string_view toString(const NativeType type) {
switch (type) {
case NativeType::BigInt:
return "INTEGER";
case NativeType::String:
return "TEXT";
}
throw std::runtime_error{
"Unsupported native type " + std::to_string(static_cast<int>(type)),
};
}

void createTableFields(const FieldDescription &description,
const bool isPrimaryKey, fmt::appender &appender,
std::vector<std::string_view> prefixes, bool &first) {
prefixes.push_back(description.name);

const auto createPrimitiveField =
[isPrimaryKey, &prefixes, &appender,
&first](const PrimitiveFieldDescription &descr) {
fmt::format_to(appender, "{}'{}' '{}'{}", first ? "" : ",",
fmt::to_string(fmt::join(prefixes, "_")),
toString(descr.nativeType),
isPrimaryKey ? " PRIMARY KEY" : "");
first = false;
};

const auto createCompositeField =
[&prefixes, &appender, &first](const CompositeFieldDescription &descr) {
for (const FieldDescription &field : descr.fields) {
createTableFields(field, false, appender, prefixes, first);
}
};

const pfrorm::detail::MultiLambda createField{
createPrimitiveField,
createCompositeField,
};

static_assert(std::is_invocable_r_v<void, decltype(createField),
const PrimitiveFieldDescription &>);
static_assert(std::is_invocable_r_v<void, decltype(createField),
const CompositeFieldDescription &>);

std::visit(createField, description.field);
}

} // namespace

void createTable(Connection &connection, const EntityDescription &entity) {
connection.execute(fmt::format("DROP TABLE IF EXISTS '{}'", entity.name));

fmt::memory_buffer buf;
fmt::appender appender{buf};
fmt::format_to(appender, "CREATE TABLE '{}' (", entity.name);
bool first = true;
for (std::size_t i = 0; i < entity.fields.size(); ++i) {
createTableFields(entity.fields[i], entity.primaryKey == i, appender, {},
first);
}
fmt::format_to(appender, ")");
connection.execute(fmt::to_string(buf));
}

bool exists(Connection &connection, const EntityDescription &entity) {
const Result result = connection.query(
fmt::format("SELECT EXISTS(SELECT 1 FROM '{}')", entity.name));
return result.getRow()->boolean(0);
}

} // namespace pfrorm::sqlite::detail
Loading

0 comments on commit 12d7861

Please sign in to comment.