MCPcopy Index your code
hub / github.com/belchior/sql_query_builder

github.com/belchior/sql_query_builder @2.6.2

Chat with this repo
repository ↗ · DeepWiki ↗ · release 2.6.2 ↗ · + Follow
1,189 symbols 6,373 edges 84 files 183 documented · 15%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Write SQL queries in a simple and composable way.

The main goal is to find the best balance between write idiomatic SQL queries and manage scenarios of complex query composition mixed with conditional clauses.

Quick Start

use sql_query_builder as sql;

let mut select = sql::Select::new()
  .select("id, login")
  .from("users")
  .where_clause("login = $1");

let is_admin = true;

if is_admin {
  select = select.where_clause("is_admin = true");
}

let query = select.as_string();

println!("{query}");

Output

SELECT id, login FROM users WHERE login = $1 AND is_admin = true

Feature Flags

SQL Query Builder comes with the following optional features: - postgresql enable Postgres syntax - sqlite enable SQLite syntax - mysql enable MySQL syntax

You can enable features like

# Cargo.toml

sql_query_builder = { version = "2.x.x", features = ["postgresql"] }

How it's works

In a simplified way, the lib has an API to allows you to write dynamic queries in a style similar to queries written in pure SQL and the result is a code idiomatic to both Rust and SQL. Additionally, the library will not try to understand what you write in the parameters and in some ways this is good as it removes a lot of verbosity to generate a SQL query, in contrast, debugging tends to be more difficult and silly errors can appear, the library has a debug() method which had a good output to minimize the effort of debugging complex queries.

More technically, consecutive calls to the same clause will accumulates values respecting the order of the calls, the two select produce the same SQL query.

use sql_query_builder as sql;

let select = sql::Select::new()
  .select("id, login");

let select = sql::Select::new()
  .select("id")
  .select("login");

Methods like limit and offset will override the previous value, the two select is equivalent

# #[cfg(any(feature = "postgresql", feature = "sqlite"))]
# {
use sql_query_builder as sql;

let select = sql::Select::new()
  .limit("1000")
  .limit("123");

let select = sql::Select::new()
  .limit("123");
# }

The library ignores the order between clauses so the two selects will produce the same query

use sql_query_builder as sql;

let select = sql::Select::new()
  .select("id, login")
  .from("users")
  .where_clause("login = $1");

let select = sql::Select::new()
  .from("users")
  .where_clause("login = $1")
  .select("id, login");

You can conditionally add a clause mutating the select

use sql_query_builder as sql;

let mut select = sql::Select::new()
  .select("id, login")
  .from("users")
  .where_clause("login = $1");

let should_includes_address = true;

if should_includes_address {
  select = select.inner_join("addresses on user.login = addresses.owner_login");
}

Composition

Composition is very welcome to write complex queries, this feature makes the library shine

use sql_query_builder as sql;

fn project(select: sql::Select) -> sql::Select {
  select
    .select("u.id, u.name as user_name, u.login")
    .select("a.name as addresses_name")
    .select("o.name as product_name")
}

fn relations(select: sql::Select) -> sql::Select {
  select
    .from("users u")
    .inner_join("addresses a ON a.user_login = u.login")
    .inner_join("orders o ON o.user_login = u.login")
}

fn conditions(select: sql::Select) -> sql::Select {
  select
    .where_clause("u.login = $1")
    .where_clause("o.id = $2")
}

fn as_string(select: sql::Select) -> String {
  select.as_string()
}

let query = Some(sql::Select::new())
  .map(project)
  .map(relations)
  .map(conditions)
  .map(as_string)
  .unwrap();

println!("{query}");

Output (indented for readability)

SELECT u.id, u.name as user_name, u.login, a.name as addresses_name, o.name as product_name
FROM users u
INNER JOIN addresses a ON a.user_login = u.login
INNER JOIN orders o ON o.user_login = u.login
WHERE u.login = $1 AND o.id = $2

Raw queries

You can use the raw method to reach some edge cases that are hard to rewrite into the Select syntax. The select.raw() method will put any SQL you define on top of the output

use sql_query_builder as sql;

let raw_query = "\
  select u.id as user_id, addr.* \
  from users u \
  inner join addresses addr on u.login = addr.owner_login\
";
let select = sql::Select::new()
  .raw(raw_query)
  .where_clause("login = $1");

To a more precisely use case your can use the select.raw_before() and select.raw_after()

use sql_query_builder as sql;

let raw_query = "\
  from users u \
  inner join addresses addr on u.login = addr.owner_login\
";
let select = sql::Select::new()
  .select("u.id as user_id, addr.*")
  .raw_before(sql::SelectClause::Where, raw_query)
  .where_clause("login = $1");
use sql_query_builder as sql;

let raw_query = "\
  from users u \
  inner join addresses addr on u.login = addr.owner_login\
";
let select = sql::Select::new()
  .select("u.id as user_id, addr.*")
  .raw_after(sql::SelectClause::Select, raw_query)
  .where_clause("login = $1");

Debugging queries

Sometimes it's more ease just print de current state of the query builder, to do so adds the .debug() method anywhere in the builder. In the example below, the where clause will not be printed because the debug was added before the clause

use sql_query_builder as sql;

let mut select = sql::Select::new()
  .select("id, login")
  .from("users")
  .debug()
  .where_clause("login = $1");

Prints to the standard output

-- ------------------------------------------------------------------------------
SELECT id, login
FROM users
-- ------------------------------------------------------------------------------

See the documentation for more builders like Insert, Update and Delete

Extension points exported contracts — how you extend this code

TransactionQuery (Interface)
Represents all commands that can be used in a transaction [10 implementers]
src/behavior.rs
Concat (Interface)
(no doc) [12 implementers]
src/concat/mod.rs
WithQuery (Interface)
(no doc) [5 implementers]
src/behavior.rs
ConcatWhere (Interface)
(no doc) [4 implementers]
src/concat/sql_standard.rs
ConcatWith (Interface)
(no doc) [4 implementers]
src/concat/non_standard.rs
ConcatPartition (Interface)
(no doc) [3 implementers]
src/concat/mysql.rs
ConcatFrom (Interface)
(no doc) [3 implementers]
src/concat/sql_standard.rs

Core symbols most depended-on inside this repo

as_string
called by 1062
src/delete/delete.rs
select
called by 140
src/insert/insert.rs
raw
called by 130
src/delete/delete.rs
from
called by 116
src/delete/delete.rs
where_clause
called by 110
src/delete/delete.rs
raw_after
called by 103
src/delete/delete.rs
raw_before
called by 103
src/delete/delete.rs
values
called by 78
src/insert/insert.rs

Shape

Function 868
Method 276
Enum 17
Class 14
Interface 14

Languages

Rust100%

Modules by API surface

tests/command_create_index_spec.rs118 symbols
tests/command_insert_spec.rs102 symbols
tests/command_alter_table_spec.rs61 symbols
tests/command_create_table_spec.rs57 symbols
tests/command_transaction_spec.rs55 symbols
tests/command_drop_index_spec.rs40 symbols
tests/command_drop_table_spec.rs39 symbols
tests/command_delete_spec.rs39 symbols
tests/command_select_spec.rs38 symbols
src/structure.rs31 symbols
tests/command_update_spec.rs28 symbols
src/select/select.rs28 symbols

For agents

$ claude mcp add sql_query_builder \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact