Introducing Eloquent a Fluent Query Builder for Rust

Tjardo
3 min readSep 25, 2024

--

As a Rust developer, I often find myself missing the elegance and simplicity of the Laravel query builder when working with databases. Laravel’s query builder is known for its expressive syntax and ease of use, allowing developers to construct complex queries using a fluent, chainable API. With that in mind, I decided to create a Rust crate called eloquent, inspired by the same principles.

Fluent Query Construction in Rust

The primary goal of eloquent is to make building SQL queries as seamless as possible. Instead of manually constructing SQL strings, you can chain methods to define queries, making the code more readable and maintainable.

Here’s an example of constructing a simple SQL SELECT query:

use eloquent::Eloquent;

let query = Eloquent::query()
.table("flights")
.select(vec![
"origin_airport",
"destination_airport",
"flight_duration",
])
.sql()?;

This query will produce the following SQL:

SELECT origin_airport, destination_airport, flight_duration FROM flights

Eloquent supports a wide range of SQL queries, from simple selects to more advanced query patterns like joins, aggregates, and conditional logic.

The following example demonstrates how to construct a query using various methods, including JOIN, GROUP BY, and even closures for dynamic conditions.

use eloquent::Eloquent;

let query = Eloquent::query()
.table("flights")
.select("origin_airport")
.select_avg("startup_time_in_minutes", "startup_time_in_minutes_avg")
.select_as("airports.city", "destination_city")
.join(
"airports",
"flights.destination_airport",
"airports.iata_code",
)
.r#where("origin_airport", "AMS")
.where_not_in("flight_number", vec!["KL123", "KL456"])
.where_not_null("gate_number")
.where_closure(|q| {
q.where_gte("flight_duration", 120)
.or_where_like("airports.city", "%NY%")
})
.group_by(vec!["origin_airport", "airports.city"])
.having_gt("startup_time_in_minutes_avg", 120)
.order_by_asc("startup_time_in_minutes_avg")
.limit(20)
.pretty_sql()?;

In the example above, we used the pretty_sql() method instead of the sql() method. This will format the SQL query in a more readable way, which can be incredibly useful when debugging your queries. It breaks the query into a clean, structured format, making it easier to identify issues or just visually parse complex queries during development.

SELECT
origin_airport,
AVG(startup_time_in_minutes) AS startup_time_in_minutes_avg,
airports.city AS destination_city
FROM
flights
JOIN airports ON flights.destination_airport = airports.iata_code
WHERE
origin_airport = 'AMS'
AND flight_number NOT IN ('KL123', 'KL456')
AND gate_number IS NOT NULL
AND (
flight_duration >= 120
OR airports.city LIKE '%NY%'
)
GROUP BY
origin_airport,
airports.city
HAVING
startup_time_in_minutes_avg > 120
ORDER BY
startup_time_in_minutes_avg ASC
LIMIT
20

Eloquent is designed to closely mimic SQL query naming, making it intuitive for developers familiar with SQL syntax. It also supports advanced features like closures for dynamic conditions and sub queries, allowing for flexible query building.

let subquery = Eloquent::subquery()
.table("tickets")
.select("event_id")
.select_avg("price", "price_avg")
.group_by("event_id")
.order_by_desc("price_avg")
.limit(1);

let query = Eloquent::query()
.table("events")
.select(vec!["event_name", "event_date"])
.r#where("event_id", subquery)
.sql()?;

This query will produce the following SQL:

SELECT event_name, event_date FROM events WHERE event_id = (SELECT event_id, AVG(price) AS price_avg FROM tickets GROUP BY event_id ORDER BY price_avg DESC LIMIT 1)

Additionally, thanks to Rust’s strong type system, your editor will automatically autocomplete and suggest the correct method names.

Many method in Eloquent such as select() and where_not_null() are designed to be flexible, accepting both a single string and a vector of strings. This allows you to quickly build queries using the same methods.

The query builder wraps the query in a Result<> as it performs several common SQL checks, ensuring that your query is valid before execution. If there is any mistake, it will return an error, helping you catch issues early. However, if you prefer to bypass these validations, you can use the skip_validation() method to ignore the checks and proceed with the query as-is. This gives you flexibility when you want to handle validations manually or in specific cases.

If you find anything missing, encounter issues, or have ideas for improvements — feel free to create a PR or raise an issue in the GitHub repository. Even if you’re unsure about your PR, don’t hesitate to submit it — I’m always happy to provide support and work through it with you.

https://crates.io/crates/eloquent

https://github.com/tjardoo/eloquent-rs

--

--

Tjardo
Tjardo

No responses yet