#pragma once /* * Copyright (C) 2024 Brett Terpstra * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef SQL_H #define SQL_H #include #include #include #include #include #include namespace detail { template std::string_view sql_name() { using Decay = std::decay_t if constexpr (std::is_same_v) return "NULL"; else if constexpr (std::is_integral_v) return "INTEGER"; else if constexpr (std::is_floating_point_v) return "REAL"; else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) return "TEXT"; else if constexpr (std::is_same_v) return "BOOLEAN"; else return "BLOB"; } } class column_t { public: explicit column_t(sqlite3_stmt* statement): statement{statement} {} template auto get(const int col) const -> decltype(auto) { using Decay = std::decay_t; if constexpr (std::is_integral_v) { if constexpr (sizeof(Decay) == 8) { return sqlite3_column_int64(statement, col); } else { return sqlite3_column_int(statement, col); } } else if constexpr (std::is_same_v) { return sqlite3_column_double(statement, col); } else if constexpr (std::is_same_v) { return static_cast(sqlite3_column_double(statement, col)); } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { return T(sqlite3_column_text(statement, col)); } else { return static_cast(sqlite3_column_blob(statement, col)); } } template [[nodiscard]] std::tuple get() const { return get_internal(std::index_sequence_for()); } [[nodiscard]] size_t size(const int col) const { return static_cast(sqlite3_column_bytes(statement, col)); } private: template [[nodiscard]] std::tuple get_internal(std::index_sequence) const { return std::tuple{get(Indices)...}; } sqlite3_stmt* statement; }; class column_binder_t { public: explicit column_binder_t(sqlite3_stmt* statement): statement{statement} {} template auto bind(T&& type, int col) { static_assert(!std::is_rvalue_reference_v, "Lifetime of object must outlive its usage!"); using Decay = std::decay; if constexpr (std::is_same_v || std::is_integral_v) { if constexpr (sizeof(Decay) == 8) return sqlite3_bind_int64(statement, col, type); else return sqlite3_bind_int(statement, col, type); } else if constexpr (std::is_floating_point_v) { return sqlite3_bind_double(statement, col, type); } else if constexpr (std::is_same_v) { return sqlite3_bind_null(statement, col); } else if constexpr (std::is_same_v || std::is_same_v) { return sqlite3_bind_text(statement, col, type.data(), type.size(), nullptr); } else { return sqlite3_bind_blob64(statement, col, type.data(), type.size()); } } private: sqlite3_stmt* statement; }; class binder_t { public: explicit binder_t(sqlite3_stmt* statement): statement(statement) {} /** * Indexes start at 1 for the left most template parameter */ template auto bind(T&& type, int col) { return column_binder_t{statement}.bind(std::forward(type), col); } template auto bind(T&& type, const std::string& name) { auto index = sqlite3_bind_parameter_index(statement, name.c_str()); return column_binder_t{statement}.bind(std::forward(type), index); } template auto bind(Types&& types) { return bind_internal(std::index_sequence_for(), std::forward(types)...); } private: template auto bind_internal(std::index_sequence, Types&& types) { return ((bind(Indices, std::forward(types)) != SQLITE_OK) || ...); } sqlite3_stmt* statement; }; class statement_t { public: explicit statement_t(sqlite3* db, const std::string& stmt); statement_t(const statement_t& copy) = delete; statement_t(statement_t& move) noexcept: statement{std::exchange(move.statement, nullptr)}, db{move.db} {} statement_t& operator=(const statement_t&) = delete; statement_t& operator=(statement_t&& move) noexcept { statement = std::exchange(move.statement, statement); db = std::exchange(move.db, db); return *this; } binder_t bind() const { sqlite3_reset(statement); return binder_t{statement}; } [[nodiscard]] bool execute() const; [[nodiscard]] column_t fetch() const { return column_t{statement}; } ~statement_t(); private: sqlite3_stmt* statement = nullptr; sqlite3* db; }; class table_builder_t; class table_column_builder_t { public: table_column_builder_t(table_builder_t& parent, std::vector& columns, std::string type, std::string name): parent{parent}, columns{columns}, type{std::move(type)}, name{std::move(name)} {} table_column_builder_t& primary_key() { attributes.emplace_back("PRIMARY KEY"); return *this; } table_column_builder_t& unique() { attributes.emplace_back("UNIQUE"); return *this; } table_column_builder_t& with_default(const std::string& value) { if (type == "TEXT") attributes.push_back("DEFAULT \"" + value + '"'); else attributes.push_back("DEFAULT " + value); return *this; } table_column_builder_t& not_null() { attributes.emplace_back("NOT NULL"); return *this; } table_builder_t& finish() { if (!created) { columns.push_back(name + ' ' + type + ' ' + join()); created = true; } return parent; } ~table_column_builder_t() { finish(); } private: [[nodiscard]] std::string join() const { std::string out; for (const auto& str : attributes) { out += str; out += ' '; } return out; } bool created = false; table_builder_t& parent std::vector& columns; std::vector attributes; std::string type; std::string name; }; class table_builder_t { public: explicit table_builder_t(sqlite3* db, std::string name): db{db}, name{std::move(name)} {} template table_column_builder_t& with_type(const std::string& name) { return table_column_builder_t{*this, columns, detail::sql_name(), name}; } private: std::vector columns{}; sqlite3* db; std::string name{}; }; class statement_builder_t { public: explicit statement_builder_t(sqlite3* db): db{db} {} table_builder_t create_table(const std::string& name) { return table_builder_t{db, name}; } private: sqlite3* db; }; class database_t { public: explicit database_t(const std::string& file); database_t(const database_t& copy) = delete; database_t(database_t& move) noexcept: db{std::exchange(move.db, nullptr)} {} database_t& operator=(const database_t&) = delete; database_t& operator=(database_t&& move) noexcept { db = std::exchange(move.db, db); return *this; } [[nodiscard]] statement_t prepare(const std::string& stmt) const { return statement_t{db, stmt}; } [[nodiscard]] statement_builder_t builder() const { return statement_builder_t{db}; } ~database_t(); private: sqlite3* db = nullptr; }; #endif //SQL_H