citext
What it is
citext is a PostgreSQL-supplied, case-insensitive text type. Comparisons behave as though both values were lowered, while the originally entered capitalization remains available for display.
What it provides
- Case-insensitive equality, uniqueness, ordering, and pattern matching
- Index and primary-key behavior that follows case-insensitive comparisons
- A text-like type that removes repeated
lower(...)expressions from queries
Use it for email addresses, usernames, identifiers, tags, or other values where Maya and maya should compare as the same value.
Use it with Rent
extension citext {
name = "citext"
}
model User {
id BigInt @id
email citext.CiText @unique
}Add rent-ext-citext to your dependencies and enable Rent's postgres and extensions features.
Run rent generate and rent migrate dev --name add_case_insensitive_email to generate and migrate the field.
let user = client
.user()
.email_eq("Maya@Example.com")?
.only_or_none()
.await?;Create and update setters accept strings directly: .email("Maya@Example.com"). Reads return CiText, whose
.as_str() preserves the original spelling. Generated SQL comparisons and uniqueness use PostgreSQL's
case-insensitive behavior. Comparing two CiText values in Rust uses their exact spelling. Case rules follow
the database's locale, so choose and test the database collation for your application's languages.
Run the application
Add the extension declaration above to your application's schema. Confirm that your PostgreSQL provider offers the extension and that your migration role can install it, then apply the migration and run your application:
rent migrate dev --name enable_citext
cargo runComplete example
Download this project, or copy the files below into an empty directory.
Replace RENT_REPOSITORY_URL in Cargo.toml with the URL supplied with your preview access. Use the matching Rent CLI release.
Use an empty PostgreSQL database with the extension binaries available on its server. Set DATABASE_URL
to that database and follow any server setup requirements described above.
rent extension check --provider self-hosted
rent generate
rent migrate dev --name initial
cargo runIf your host preinstalls other extensions, declare those in RSL as well. For a database with existing application tables, baseline it first.
Cargo.toml
[package]
name = "rent-extension-citext"
version = "0.1.0"
edition = "2024"
[workspace]
[[bin]]
name = "rent-extension-citext"
path = "main.rs"
[dependencies]
rent = { git = "RENT_REPOSITORY_URL", tag = "v0.8.6", default-features = false, features = ["postgres", "extensions", "migrate"] }
rent-ext-citext = { git = "RENT_REPOSITORY_URL", tag = "v0.8.6" }
anyhow = "1.0.104"
tokio = { version = "1.53.1", features = ["full"] }
# Used by the generated client after `rent generate`.
[package.metadata.cargo-machete]
ignored = ["rent-ext-citext"]rent.toml
schema = "schema.rsl"
generated = "generated"
migrations = "migrations"
[database]
dialect = "postgres"
backends = ["postgres"]schema.rsl
datasource db {
provider = "postgresql"
}
extension citext {
name = "citext"
}
model User {
id BigInt @id
email citext.CiText @unique
}main.rs
use anyhow::Result;
use rent::driver::DatabasePool;
#[allow(dead_code, unused_imports)]
#[rustfmt::skip]
#[path = "generated/mod.rs"]
mod generated;
#[tokio::main]
async fn main() -> Result<()> {
let pool = DatabasePool::connect_url(&std::env::var("DATABASE_URL")?).await?;
let client = generated::client::Client::new(pool.clone());
let email = "Maya@Example.com";
client
.upsert_user(generated::user::unique::id)
.id(1)
.email(email)
.save()
.await?;
let maya = client.user().email_eq("maya@example.com")?.only().await?;
println!("Matched user {}", maya.id);
Ok(())
}