Added create_index

This commit is contained in:
Dr. Patrick Urbanke
2025-05-18 18:12:55 +02:00
parent b7598c55a6
commit 9afa1867d7
16 changed files with 432 additions and 8 deletions
+1
View File
@@ -17,6 +17,7 @@
#include "sqlgen/begin_transaction.hpp"
#include "sqlgen/col.hpp"
#include "sqlgen/commit.hpp"
#include "sqlgen/create_index.hpp"
#include "sqlgen/create_table.hpp"
#include "sqlgen/delete_from.hpp"
#include "sqlgen/drop.hpp"
+76
View File
@@ -0,0 +1,76 @@
#ifndef SQLGEN_CREATEINDEX_HPP_
#define SQLGEN_CREATEINDEX_HPP_
#include <rfl.hpp>
#include "Ref.hpp"
#include "Result.hpp"
#include "transpilation/columns_t.hpp"
#include "transpilation/to_create_index.hpp"
namespace sqlgen {
template <class ValueType, class ColumnsType, class WhereType>
Result<Ref<Connection>> create_index_impl(const Ref<Connection>& _conn,
const std::string& _name,
const bool _unique,
const bool _if_not_exists,
const WhereType& _where) {
const auto query =
transpilation::to_create_index<ValueType, ColumnsType, WhereType>(
_name, _unique, _if_not_exists, _where);
return _conn->execute(_conn->to_sql(query)).transform([&](const auto&) {
return _conn;
});
}
template <class ValueType, class ColumnsType, class WhereType>
Result<Ref<Connection>> create_index_impl(const Result<Ref<Connection>>& _res,
const std::string& _name,
const bool _unique,
const bool _if_not_exists,
const WhereType& _where) {
return _res.and_then([&](const auto& _conn) {
return create_index_impl<ValueType, ColumnsType, WhereType>(
_conn, _name, _unique, _if_not_exists, _where);
});
}
template <rfl::internal::StringLiteral _name, class ValueType, class WhereType,
class... ColTypes>
struct CreateIndex {
Result<Ref<Connection>> operator()(const auto& _conn) const noexcept {
try {
return create_index_impl<ValueType,
transpilation::columns_t<ValueType, ColTypes...>,
WhereType>(_conn, _name.str(), unique_,
if_not_exists_, where_);
} catch (std::exception& e) {
return error(e.what());
}
}
bool unique_ = false;
bool if_not_exists_ = false;
WhereType where_;
};
template <rfl::internal::StringLiteral _name, class ValueType,
class... ColTypes>
auto create_index(const ColTypes&...) {
return CreateIndex<_name, ValueType, Nothing, ColTypes...>{
.unique_ = false, .if_not_exists_ = false};
}
template <rfl::internal::StringLiteral _name, class ValueType,
class... ColTypes>
auto create_unique_index(const ColTypes&...) {
return CreateIndex<_name, ValueType, Nothing, ColTypes...>{
.unique_ = true, .if_not_exists_ = false};
}
}; // namespace sqlgen
#endif
+2 -2
View File
@@ -37,8 +37,8 @@ struct CreateTable {
bool if_not_exists_;
};
template <class ContainerType>
const auto create_table = CreateTable<ContainerType>{};
template <class ValueType>
const auto create_table = CreateTable<ValueType>{};
}; // namespace sqlgen
+26
View File
@@ -0,0 +1,26 @@
#ifndef SQLGEN_DYNAMIC_CREATEINDEX_HPP_
#define SQLGEN_DYNAMIC_CREATEINDEX_HPP_
#include <string>
#include <vector>
#include "Column.hpp"
#include "Condition.hpp"
#include "Table.hpp"
namespace sqlgen::dynamic {
struct CreateIndex {
std::string name;
Table table;
std::vector<std::string> columns;
bool unique = false;
bool if_not_exists = true;
std::optional<Condition> where = std::nullopt;
};
} // namespace sqlgen::dynamic
#endif
-1
View File
@@ -13,7 +13,6 @@ struct CreateTable {
Table table;
std::vector<Column> columns;
bool if_not_exists = true;
// TODO: Constraints
};
} // namespace sqlgen::dynamic
+3 -2
View File
@@ -3,6 +3,7 @@
#include <rfl.hpp>
#include "CreateIndex.hpp"
#include "CreateTable.hpp"
#include "DeleteFrom.hpp"
#include "Drop.hpp"
@@ -12,8 +13,8 @@
namespace sqlgen::dynamic {
using Statement = rfl::TaggedUnion<"stmt", CreateTable, DeleteFrom, Drop,
Insert, SelectFrom, Update>;
using Statement = rfl::TaggedUnion<"stmt", CreateIndex, CreateTable, DeleteFrom,
Drop, Insert, SelectFrom, Update>;
} // namespace sqlgen::dynamic
@@ -0,0 +1,34 @@
#ifndef SQLGEN_TRANSPILATION_COLUMNST_HPP_
#define SQLGEN_TRANSPILATION_COLUMNST_HPP_
#include <rfl.hpp>
#include <type_traits>
#include <vector>
#include "../col.hpp"
#include "Desc.hpp"
#include "all_columns_exist.hpp"
namespace sqlgen::transpilation {
template <class... _ColTypes>
struct Columns {
rfl::Tuple<_ColTypes...> values;
static std::vector<std::string> to_vec() {
return std::vector<std::string>({_ColTypes().name()...});
}
};
template <class T, class... ColTypes>
auto make_columns() {
static_assert(all_columns_exist<T, ColTypes...>(), "All columns must exist.");
return Columns<ColTypes...>{};
}
template <class T, class... ColTypes>
using columns_t = std::invoke_result_t<decltype(make_columns<T, ColTypes...>)>;
} // namespace sqlgen::transpilation
#endif
@@ -0,0 +1,37 @@
#ifndef SQLGEN_TRANSPILATION_TO_CREATE_INDEX_HPP_
#define SQLGEN_TRANSPILATION_TO_CREATE_INDEX_HPP_
#include <rfl.hpp>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "../dynamic/CreateIndex.hpp"
#include "../dynamic/Table.hpp"
#include "get_schema.hpp"
#include "get_tablename.hpp"
#include "to_condition.hpp"
namespace sqlgen::transpilation {
template <class T, class ColumnsType, class WhereType>
requires std::is_class_v<std::remove_cvref_t<T>> &&
std::is_aggregate_v<std::remove_cvref_t<T>>
dynamic::CreateIndex to_create_index(const std::string& _name,
const bool _unique,
const bool _if_not_exists,
const WhereType& _where) {
return dynamic::CreateIndex{
.name = _name,
.table =
dynamic::Table{.name = get_tablename<T>(), .schema = get_schema<T>()},
.columns = ColumnsType::to_vec(),
.unique = _unique,
.if_not_exists = _if_not_exists,
.where = to_condition<std::remove_cvref_t<T>>(_where)};
}
} // namespace sqlgen::transpilation
#endif
+14
View File
@@ -4,12 +4,15 @@
#include <vector>
#include "../Insert.hpp"
#include "../create_index.hpp"
#include "../create_table.hpp"
#include "../delete_from.hpp"
#include "../drop.hpp"
#include "../dynamic/Statement.hpp"
#include "../read.hpp"
#include "../update.hpp"
#include "columns_t.hpp"
#include "to_create_index.hpp"
#include "to_create_table.hpp"
#include "to_delete_from.hpp"
#include "to_drop.hpp"
@@ -23,6 +26,17 @@ namespace sqlgen::transpilation {
template <class T>
struct ToSQL;
template <rfl::internal::StringLiteral _name, class ValueType, class WhereType,
class... ColTypes>
struct ToSQL<CreateIndex<_name, ValueType, WhereType, ColTypes...>> {
dynamic::Statement operator()(const auto& _create_index) const {
return transpilation::to_create_index<
ValueType, columns_t<ValueType, ColTypes...>, WhereType>(
_name.str(), _create_index.unique_, _create_index.if_not_exists_,
_create_index.where_);
}
};
template <class T>
struct ToSQL<CreateTable<T>> {
dynamic::Statement operator()(const auto&) const {
+45 -1
View File
@@ -22,6 +22,8 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept;
std::string column_to_sql_definition(const dynamic::Column& _col) noexcept;
std::string create_index_to_sql(const dynamic::CreateIndex& _stmt) noexcept;
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept;
std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept;
@@ -130,6 +132,45 @@ std::string column_to_sql_definition(const dynamic::Column& _col) noexcept {
_col.type.visit([](const auto& _t) { return _t.properties; }));
}
std::string create_index_to_sql(const dynamic::CreateIndex& _stmt) noexcept {
using namespace std::ranges::views;
std::stringstream stream;
if (_stmt.unique) {
stream << "CREATE UNIQUE INDEX ";
} else {
stream << "CREATE INDEX ";
}
if (_stmt.if_not_exists) {
stream << "IF NOT EXISTS ";
}
stream << "\"" << _stmt.name << "\" ";
stream << "ON ";
if (_stmt.table.schema) {
stream << "\"" << *_stmt.table.schema << "\".";
}
stream << "\"" << _stmt.table.name << "\"";
stream << "(";
stream << internal::strings::join(
", ",
internal::collect::vector(_stmt.columns | transform(wrap_in_quotes)));
stream << ")";
if (_stmt.where) {
stream << " WHERE " << condition_to_sql(*_stmt.where);
}
stream << ";";
return stream.str();
}
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept {
using namespace std::ranges::views;
@@ -271,7 +312,10 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept {
return _stmt.visit([&](const auto& _s) -> std::string {
using S = std::remove_cvref_t<decltype(_s)>;
if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
if constexpr (std::is_same_v<S, dynamic::CreateIndex>) {
return create_index_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
return create_table_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::DeleteFrom>) {
+46 -1
View File
@@ -18,6 +18,8 @@ std::string condition_to_sql(const dynamic::Condition& _cond) noexcept;
template <class ConditionType>
std::string condition_to_sql_impl(const ConditionType& _condition) noexcept;
std::string create_index_to_sql(const dynamic::CreateIndex& _stmt) noexcept;
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept;
std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept;
@@ -112,6 +114,46 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
return stream.str();
}
std::string create_index_to_sql(const dynamic::CreateIndex& _stmt) noexcept {
using namespace std::ranges::views;
const auto in_quotes = [](const std::string& _str) -> std::string {
return "\"" + _str + "\"";
};
std::stringstream stream;
if (_stmt.unique) {
stream << "CREATE UNIQUE INDEX ";
} else {
stream << "CREATE INDEX ";
}
if (_stmt.if_not_exists) {
stream << "IF NOT EXISTS ";
}
if (_stmt.table.schema) {
stream << "\"" << *_stmt.table.schema << "\".";
}
stream << "\"" << _stmt.name << "\" ";
stream << "ON " << "\"" << _stmt.table.name << "\"";
stream << "(";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.columns | transform(in_quotes)));
stream << ")";
if (_stmt.where) {
stream << " WHERE " << condition_to_sql(*_stmt.where);
}
stream << ";";
return stream.str();
}
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept {
using namespace std::ranges::views;
@@ -259,7 +301,10 @@ std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept {
std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept {
return _stmt.visit([&](const auto& _s) -> std::string {
using S = std::remove_cvref_t<decltype(_s)>;
if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
if constexpr (std::is_same_v<S, dynamic::CreateIndex>) {
return create_index_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
return create_table_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::DeleteFrom>) {
+44
View File
@@ -0,0 +1,44 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_create_index {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(postgres, test_create_index) {
const auto credentials = sqlgen::postgres::Credentials{.user = "postgres",
.password = "password",
.host = "localhost",
.dbname = "postgres"};
using namespace sqlgen;
const auto people = sqlgen::postgres::connect(credentials)
.and_then(drop<Person> | if_exists)
.and_then(create_table<Person> | if_not_exists)
.and_then(create_index<"test_table_ix", Person>(
"first_name"_c, "last_name"_c) |
if_not_exists)
.and_then(sqlgen::read<std::vector<Person>>)
.value();
const std::string expected = R"([])";
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_create_index
#endif
+27
View File
@@ -0,0 +1,27 @@
#include <gtest/gtest.h>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
namespace test_create_index_dry {
struct TestTable {
std::string field1;
int32_t field2;
sqlgen::PrimaryKey<uint32_t> id;
std::optional<std::string> nullable;
};
TEST(postgres, test_create_index_dry) {
using namespace sqlgen;
const auto query =
create_index<"test_table_ix", TestTable>("field1"_c, "field2"_c) |
if_not_exists;
const auto expected =
R"(CREATE INDEX IF NOT EXISTS "test_table_ix" ON "TestTable"("field1", "field2");)";
EXPECT_EQ(sqlgen::postgres::to_sql(query), expected);
}
} // namespace test_create_index_dry
+41
View File
@@ -0,0 +1,41 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_create_table {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(postgres, test_create_table) {
const auto credentials = sqlgen::postgres::Credentials{.user = "postgres",
.password = "password",
.host = "localhost",
.dbname = "postgres"};
using namespace sqlgen;
const auto people = sqlgen::postgres::connect(credentials)
.and_then(drop<Person> | if_exists)
.and_then(create_table<Person> | if_not_exists)
.and_then(sqlgen::read<std::vector<Person>>)
.value();
const std::string expected = R"([])";
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_create_table
#endif
+3 -1
View File
@@ -13,7 +13,9 @@ struct TestTable {
};
TEST(postgres, test_create_table_dry) {
const auto query = sqlgen::CreateTable<TestTable>{};
using namespace sqlgen;
const auto query = create_table<TestTable> | if_not_exists;
const auto expected =
R"(CREATE TABLE IF NOT EXISTS "TestTable" ("field1" TEXT NOT NULL, "field2" INTEGER NOT NULL, "id" INTEGER NOT NULL, "nullable" TEXT, PRIMARY KEY ("id"));)";
+33
View File
@@ -0,0 +1,33 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_create_index {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(sqlite, test_create_index) {
using namespace sqlgen;
const auto people = sqlgen::sqlite::connect()
.and_then(create_table<Person> | if_not_exists)
.and_then(create_index<"person_ix", Person>(
"first_name"_c, "last_name"_c) |
if_not_exists)
.and_then(sqlgen::read<std::vector<Person>>);
const std::string expected = R"([])";
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_create_index