pgvector
What it is
pgvector adds vector columns, distance operators, and approximate-nearest-neighbor indexes to PostgreSQL. It lets relational records and machine-learning embeddings live and transact together.
What it provides
- Vector, half-vector, sparse-vector, and binary-vector storage
- Exact L2, inner-product, cosine, L1, Hamming, and Jaccard search
- HNSW and IVFFlat indexes for approximate nearest-neighbor queries
Use it for semantic search, recommendation systems, duplicate detection, image similarity, retrieval-augmented generation, or hybrid search that combines embeddings with ordinary filters.
Use it with Rent
extension vector {
name = "vector"
schema = "embeddings"
}
model Post {
id BigInt @id
title String
embedding pgvector.Vector? @db.Vector(1536)
}Run rent generate, then review and apply the migration with rent migrate dev --name add_embeddings --schemas public,embeddings.
Add rent-ext-pgvector to your application's dependencies and enable Rent's postgres and extensions
features. The generated model holds Option<Vector>, not a string or JSON proxy.
use rent_ext_pgvector::Vector;
let query = Vector::new(embedding_service_output)?;
let posts = client
.post()
.order_by_embedding_cosine(&query)?
.limit(10)
.all()
.await?;Generated setters, filters, and distance ordering use native binary values and qualify extension operators
with the installation schema. The field's dimensions are checked before a write or filter reaches PostgreSQL.
Use .embedding(query) to set a value, .clear_embedding() to clear a nullable value, and
.select_embedding() to retrieve typed vectors. Vector distance ordering composes with ordinary filters,
eager loading, limits, and multi-field projections. A cosine query must have nonzero norm to define a useful
distance. Choose L2 when zero vectors are meaningful in your application.
Query filters and distance ordering accept owned or borrowed vectors. Borrowing avoids copying the original component buffer. Rent encodes owned query parameters before returning the query builder, so the builder does not borrow your embedding across an await. Collection filters accept borrowed slices as well:
let candidates = [first_embedding, second_embedding];
let posts = client
.post()
.embedding_in(&candidates)?
.all()
.await?;Model field setters accept an owned or borrowed vector and store an owned copy. The generated native field type represents
pgvector's full-precision vector. Other storage formats require explicit SQL and their own codecs.
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_pgvector
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 --schemas public,embeddings
cargo runThe vector example uses three-dimensional sample values for readability. Use your embedding model's dimensions in an application.
The schema places the extension in embeddings. Include that schema when Rent inspects the database.
If 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-pgvector"
version = "0.1.0"
edition = "2024"
[workspace]
[[bin]]
name = "rent-extension-pgvector"
path = "main.rs"
[dependencies]
rent = { git = "RENT_REPOSITORY_URL", tag = "v0.8.6", default-features = false, features = ["postgres", "extensions", "migrate"] }
rent-ext-pgvector = { git = "RENT_REPOSITORY_URL", tag = "v0.8.6" }
anyhow = "1.0.104"
tokio = { version = "1.53.1", features = ["full"] }rent.toml
schema = "schema.rsl"
generated = "generated"
migrations = "migrations"
[database]
dialect = "postgres"
backends = ["postgres"]schema.rsl
datasource db {
provider = "postgresql"
}
extension vector {
name = "vector"
schema = "embeddings"
}
model Post {
id BigInt @id
title String
embedding pgvector.Vector? @db.Vector(3)
}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());
use rent_ext_pgvector::Vector;
let query = Vector::new(vec![0.1, 0.2, 0.3])?;
client
.upsert_post(generated::post::unique::id)
.id(1)
.title("Launch notes")
.embedding(&query)
.save()
.await?;
let nearby = client.post().order_by_embedding_cosine(&query)?.limit(10).all().await?;
println!("Nearest post: {}", nearby.first().map_or("none", |post| post.title.as_str()));
Ok(())
}