Skip to content

Conversation

hos-b
Copy link
Contributor

@hos-b hos-b commented Sep 14, 2025

This PR adds sqlgen::Enum. It uses reflectcpp to perform the string <-> int conversions to/from the DB interface. It uses the built-in enum types for mysql/postgres and string representation for sqlite by default. It allows using the underlying integer representation in all 3 cases.

I consider this a WIP so please feel free to suggest improvements. I for the life of me could not get a mysql/maria server running to test that part. I kept getting SSL certificate errors when trying to start a connection. If you have a docker-compose yml ready, I'd gladly test the mysql implementation as well.

This is the code I used for testing:

#include <iostream>
#include <sqlgen.hpp>
#include <sqlgen/Enum.hpp>
#include <sqlgen/mysql.hpp>
#include <sqlgen/postgres.hpp>
#include <sqlgen/sqlite.hpp>

enum class TestEnum : int {
  TEST_VALUE1 = 1,
  TEST_VALUE2 = 2,
};

struct EnumTest {
  sqlgen::PrimaryKey<int, true> id;
  sqlgen::Enum<TestEnum> type;
  std::string name;
};

struct IntBasedTest {
  sqlgen::PrimaryKey<int, true> id;
  sqlgen::Enum<TestEnum, true> type;
  std::string name;
};

struct User {
  std::string name;
  int age;
};

int main() {
  EnumTest type_based;
  type_based.id = 1;
  type_based.type = TestEnum::TEST_VALUE1;
  type_based.name = "Test";

  const auto postgres_creds =
      sqlgen::postgres::Credentials{.user = "username",
                                    .password = "password",
                                    .host = "172.17.0.1",
                                    .dbname = "mydb",
                                    .port = 5432};

  auto postgres_conn = sqlgen::postgres::connect(postgres_creds);
  const auto mysql_creds =
      sqlgen::mysql::Credentials{.host = "172.17.0.1",
                                 .user = "myuser",
                                 .password = "mypassword",
                                 .dbname = "mydatabase",
                                 .port = 3306,
                                 .unix_socket = "/var/run/mysqld/mysqld.sock"};

  // Connect to a file-based database
  const auto sqlite_conn = sqlgen::sqlite::connect("database.db");

  IntBasedTest int_based;
  int_based.id = 1;
  int_based.type = TestEnum::TEST_VALUE2;
  int_based.name = "Test";
  // Connect to the database
  // const auto mysql_conn = sqlgen::mysql::connect(mysql_creds);

  // Create and insert a user
  sqlgen::write(postgres_conn, type_based);
  sqlgen::write(postgres_conn, int_based);
  sqlgen::write(sqlite_conn, type_based);
  sqlgen::write(sqlite_conn, int_based);

  // Read all users
  const auto postgres_items =
      sqlgen::read<std::vector<EnumTest>>(postgres_conn).value();
  const auto postgres_items2 =
      sqlgen::read<std::vector<IntBasedTest>>(postgres_conn).value();
  const auto sqlite_items =
      sqlgen::read<std::vector<EnumTest>>(sqlite_conn).value();
  const auto sqlite_items2 =
      sqlgen::read<std::vector<IntBasedTest>>(sqlite_conn).value();
  // const auto mysql_items =
  // sqlgen::read<std::vector<EnumTest>>(mysql_conn).value();

  auto print_rows = [](const auto& items, const std::string& db_name) {
    for (const auto& it : items) {
      std::cout << db_name << ": " << it.id.get() << ": name: " << it.name
                << ", uuid: " << "???" << ", type: " << it.type.str()
                << std::endl;
    }
    std::cout << "------------------------" << std::endl;
  };
  print_rows(postgres_items, "postgre:enum");
  print_rows(postgres_items2, "postgres:int-based-enum");
  print_rows(sqlite_items, "sqlite:enum");
  print_rows(sqlite_items2, "sqlite:int-based-enum");

  return 0;
}

One additional change I did was to switch the remote URL for vcpkg from ssh to https. This makes cloning the repo easier, IMO. I could make a separate PR for that, if you'd prefer to keep this one clean.

@hos-b hos-b changed the title Add explicit Enum type Add sqlgen::Enum for integer-based enums Sep 14, 2025
@liuzicheng1987
Copy link
Collaborator

@hos-b looks great, but why do we need the explicit sqlgen::Enum wrapper? I think we can easily figure out whether something is an enum using concepts.

@hos-b
Copy link
Contributor Author

hos-b commented Sep 14, 2025

😅 good point. I guess I was biased towards the old PR (specialization of Parser<T>). The only reason I can currently think of is giving the user more control regarding the representation on the DB side. A catchall implementation would need to (*) use string, which is fine (& correct) for mysql/postgres, but might not be the best for sqlite. The extra boolean flag allows using ints, which might even have potential use cases in mysql/postgres.

*: based on my understanding, it's not possible to infer the DB type when sqlgen::parsing::Parser<T> functions are being called. so you'd need an implementation that would work in all 3 cases. That means sqlite would need to use TEXT.

@liuzicheng1987
Copy link
Collaborator

The way I would handle the string vs int situation is to introduce processors, just like we have in reflect-cpp.

But that can be a different PR, it doesn't have to be part of this one.

@liuzicheng1987
Copy link
Collaborator

@hos-b yes, you are right. The library is deliberately designed in such a way that the parser is completely oblivious to the database.

So for SQLite, we should just use TEXT for now and then introduce the processors at a later date.

@hos-b
Copy link
Contributor Author

hos-b commented Sep 14, 2025

I update the code to now handle enums using type traits in Parser_default.hpp. I removed the IntEnum concept, since it's no longer needed.

#include <iostream>
#include <sqlgen.hpp>
#include <sqlgen/mysql.hpp>
#include <sqlgen/postgres.hpp>
#include <sqlgen/sqlite.hpp>

enum class TestEnum : int {
  TEST_VALUE1 = 1,
  TEST_VALUE2 = 2,
};

struct TestStruct {
  sqlgen::PrimaryKey<int, true> id;
  TestEnum type;
  std::string name;
};

int main() {
  TestStruct type_based;
  type_based.id = 1;
  type_based.type = TestEnum::TEST_VALUE2;
  type_based.name = "Test";

  const auto postgres_creds =
      sqlgen::postgres::Credentials{.user = "username",
                                    .password = "password",
                                    .host = "172.17.0.1",
                                    .dbname = "mydb",
                                    .port = 5432};

  auto postgres_conn = sqlgen::postgres::connect(postgres_creds);
  const auto mysql_creds =
      sqlgen::mysql::Credentials{.host = "172.17.0.1",
                                 .user = "myuser",
                                 .password = "mypassword",
                                 .dbname = "mydatabase",
                                 .port = 3306,
                                 .unix_socket = "/var/run/mysqld/mysqld.sock"};

  const auto sqlite_conn = sqlgen::sqlite::connect("database.db");

  sqlgen::write(postgres_conn, type_based);
  sqlgen::write(sqlite_conn, type_based);

  const auto postgres_items =
      sqlgen::read<std::vector<TestStruct>>(postgres_conn).value();
  const auto sqlite_items =
      sqlgen::read<std::vector<TestStruct>>(sqlite_conn).value();

  auto print_rows = [](const auto& items, const std::string& db_name) {
    for (const auto& it : items) {
      std::cout << db_name << ": " << it.id.get() << ": name: " << it.name
                << ", uuid: " << "???"
                << ", type: " << rfl::enum_to_string<TestEnum>(it.type)
                << std::endl;
    }
    std::cout << "------------------------" << std::endl;
  };
  print_rows(postgres_items, "postgre:enum");
  print_rows(sqlite_items, "sqlite:enum");

  return 0;
}

@hos-b hos-b changed the title Add sqlgen::Enum for integer-based enums Add enum support Sep 14, 2025
@liuzicheng1987
Copy link
Collaborator

@hos-b looks great! If you could just add some tests, this would be ready to be merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants