4.2 KiB
sqlgen::Unique
sqlgen::Unique is used to enforce uniqueness constraints on table columns. It ensures that no two rows in a table can have the same value for the specified column, providing data integrity at the database level.
Usage
Basic Definition
Define a unique field in your struct by wrapping the type with sqlgen::Unique:
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
sqlgen::Unique<double> age;
};
This generates the following SQL schema:
CREATE TABLE IF NOT EXISTS "Person"(
"id" INTEGER NOT NULL,
"first_name" TEXT NOT NULL,
"last_name" TEXT NOT NULL,
"age" NUMERIC NOT NULL UNIQUE,
PRIMARY KEY("id")
);
Template Parameters
The sqlgen::Unique template takes one parameter:
- T: The type of the unique field (any supported SQL data type)
sqlgen::Unique<Type> field_name;
Type Safety and Validation
sqlgen::Unique provides compile-time validation and runtime uniqueness enforcement:
- Type Safety: The wrapped type must be a supported SQL data type
- Database-Level Enforcement: The database enforces uniqueness constraints
- Insert Validation: You cannot insert duplicate values for unique fields
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
sqlgen::Unique<std::string> first_name; // Each first_name must be unique
std::string last_name;
double age;
};
Assignment and Access
Assign values to unique fields:
const auto person = Person{
.id = 1,
.first_name = "Homer", // Must be unique across all Person records
.last_name = "Simpson",
.age = 45
};
Access the underlying value using any of these methods:
person.first_name();
person.first_name.get();
person.first_name.value();
Working with Unique Constraints
Unique constraints are useful for ensuring data integrity in various scenarios:
struct User {
sqlgen::PrimaryKey<uint32_t> id;
sqlgen::Unique<std::string> email; // Each email must be unique
sqlgen::Unique<std::string> username; // Each username must be unique
std::string full_name;
std::string password_hash;
};
// Insert user records
auto users = std::vector<User>({
User{.id = 1, .email = "homer@simpson.com", .username = "homer", .full_name = "Homer Simpson", .password_hash = "hash1"},
User{.id = 2, .email = "marge@simpson.com", .username = "marge", .full_name = "Marge Simpson", .password_hash = "hash2"}
});
// Write to database - duplicate emails or usernames would cause an error
conn.and_then(create_table<User> | if_not_exists)
.and_then(insert(std::ref(users)));
Combining with Other Constraints
You can combine sqlgen::Unique with other constraints and types:
struct Employee {
sqlgen::PrimaryKey<uint32_t> id;
sqlgen::Unique<std::string> employee_id; // Unique employee ID
sqlgen::Unique<std::string> email; // Unique email address
std::string name;
sqlgen::Unique<double> salary; // Unique salary (if applicable)
};
Database-Specific Behavior
The generated SQL will be consistent across all supported databases:
-- PostgreSQL
CREATE TABLE IF NOT EXISTS "Person" ("id" INTEGER NOT NULL, "first_name" TEXT NOT NULL UNIQUE, "last_name" TEXT NOT NULL, "age" NUMERIC NOT NULL, PRIMARY KEY ("id"));
-- MySQL
CREATE TABLE IF NOT EXISTS `Person` (`id` BIGINT NOT NULL, `first_name` TEXT NOT NULL, `last_name` TEXT NOT NULL, `age` DECIMAL NOT NULL UNIQUE, PRIMARY KEY (`id`));
-- SQLite
CREATE TABLE IF NOT EXISTS "Person" ("id" INTEGER PRIMARY KEY NOT NULL, "first_name" TEXT NOT NULL UNIQUE, "last_name" TEXT NOT NULL, "age" REAL NOT NULL);
Notes
- The template parameter specifies the type of the unique field
- The class supports:
- Direct value assignment
- Multiple access methods for the underlying value
- Reflection for SQL operations
- Move and copy semantics
- Compile-time type validation
- Unique constraints can be used with any supported SQL data type
- Database-level enforcement ensures data integrity across concurrent operations
- Unique constraints are useful for business rules like unique emails, usernames, or SKU codes