Skip to content

Commit a470682

Browse files
authored
Add Query builder (launchbadge#1780)
* Add Query builder * Make query_builder.rs in sqlx-core * Add QueryBuilder::new() * Add QueryBuilder::push() * Define questions for documentation * Get new, push, push_bind working with types * Handle postgres' numbered bind varaibles * Add a test for QueryBuilder#build * Move arguments into Option * Refactor query builder * Finish testing QueryBuilder#build * Remove design doc * Add a test for pushing strings with push_bind * Integration test green * Adjust some tests * Make query builder generic about placeholder segmenent ('$N' or '?') * Run fmt * Redesign Arguments#format_placeholder in line with code review * Use write! to push sql to QueryBuilder * Add QueryBuilder::reset to allow for QueryBuilder reuse * Run cargo fmt
1 parent 6efc39f commit a470682

File tree

5 files changed

+187
-0
lines changed

5 files changed

+187
-0
lines changed

sqlx-core/src/arguments.rs

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::database::{Database, HasArguments};
44
use crate::encode::Encode;
55
use crate::types::Type;
6+
use std::fmt::{self, Write};
67

78
/// A tuple of arguments to be sent to the database.
89
pub trait Arguments<'q>: Send + Sized + Default {
@@ -16,6 +17,10 @@ pub trait Arguments<'q>: Send + Sized + Default {
1617
fn add<T>(&mut self, value: T)
1718
where
1819
T: 'q + Send + Encode<'q, Self::Database> + Type<Self::Database>;
20+
21+
fn format_placeholder<W: Write>(&self, writer: &mut W) -> fmt::Result {
22+
writer.write_str("?")
23+
}
1924
}
2025

2126
pub trait IntoArguments<'q, DB: HasArguments<'q>>: Sized + Send {

sqlx-core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ mod io;
6767
mod logger;
6868
mod net;
6969
pub mod query_as;
70+
pub mod query_builder;
7071
pub mod query_scalar;
7172
pub mod row;
7273
pub mod type_info;

sqlx-core/src/postgres/arguments.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::fmt::{self, Write};
12
use std::ops::{Deref, DerefMut};
23

34
use crate::arguments::Arguments;
@@ -116,6 +117,10 @@ impl<'q> Arguments<'q> for PgArguments {
116117
{
117118
self.add(value)
118119
}
120+
121+
fn format_placeholder<W: Write>(&self, writer: &mut W) -> fmt::Result {
122+
write!(writer, "${}", self.buffer.count)
123+
}
119124
}
120125

121126
impl PgArgumentBuffer {

sqlx-core/src/query_builder.rs

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use std::fmt::Display;
2+
use std::fmt::Write;
3+
4+
use crate::arguments::Arguments;
5+
use crate::database::{Database, HasArguments};
6+
use crate::encode::Encode;
7+
use crate::query::Query;
8+
use crate::types::Type;
9+
use either::Either;
10+
use std::marker::PhantomData;
11+
12+
pub struct QueryBuilder<'a, DB>
13+
where
14+
DB: Database,
15+
{
16+
query: String,
17+
arguments: Option<<DB as HasArguments<'a>>::Arguments>,
18+
}
19+
20+
impl<'a, DB: Database> QueryBuilder<'a, DB>
21+
where
22+
DB: Database,
23+
{
24+
pub fn new(init: impl Into<String>) -> Self
25+
where
26+
<DB as HasArguments<'a>>::Arguments: Default,
27+
{
28+
QueryBuilder {
29+
query: init.into(),
30+
arguments: Some(Default::default()),
31+
}
32+
}
33+
34+
pub fn push(&mut self, sql: impl Display) -> &mut Self {
35+
if self.arguments.is_none() {
36+
panic!("QueryBuilder must be reset before reuse")
37+
}
38+
39+
write!(self.query, "{}", sql).expect("error formatting `sql`");
40+
41+
self
42+
}
43+
44+
pub fn push_bind<A>(&mut self, value: A) -> &mut Self
45+
where
46+
A: 'a + Encode<'a, DB> + Send + Type<DB>,
47+
{
48+
match self.arguments {
49+
Some(ref mut arguments) => {
50+
arguments.add(value);
51+
52+
arguments
53+
.format_placeholder(&mut self.query)
54+
.expect("error in format_placeholder");
55+
}
56+
None => panic!("Arguments taken already"),
57+
}
58+
59+
self
60+
}
61+
62+
pub fn build(&mut self) -> Query<'_, DB, <DB as HasArguments<'a>>::Arguments> {
63+
Query {
64+
statement: Either::Left(&self.query),
65+
arguments: match self.arguments.take() {
66+
Some(arguments) => Some(arguments),
67+
None => None,
68+
},
69+
database: PhantomData,
70+
persistent: true,
71+
}
72+
}
73+
74+
pub fn reset(&mut self) -> &mut Self {
75+
self.query.clear();
76+
self.arguments = Some(Default::default());
77+
78+
self
79+
}
80+
}
81+
82+
#[cfg(test)]
83+
mod test {
84+
use super::*;
85+
use crate::postgres::Postgres;
86+
87+
#[test]
88+
fn test_new() {
89+
let qb: QueryBuilder<'_, Postgres> = QueryBuilder::new("SELECT * FROM users");
90+
assert_eq!(qb.query, "SELECT * FROM users");
91+
}
92+
93+
#[test]
94+
fn test_push() {
95+
let mut qb: QueryBuilder<'_, Postgres> = QueryBuilder::new("SELECT * FROM users");
96+
let second_line = " WHERE last_name LIKE '[A-N]%;";
97+
qb.push(second_line);
98+
99+
assert_eq!(
100+
qb.query,
101+
"SELECT * FROM users WHERE last_name LIKE '[A-N]%;".to_string(),
102+
);
103+
}
104+
105+
#[test]
106+
#[should_panic]
107+
fn test_push_panics_when_no_arguments() {
108+
let mut qb: QueryBuilder<'_, Postgres> = QueryBuilder::new("SELECT * FROM users;");
109+
qb.arguments = None;
110+
111+
qb.push("SELECT * FROM users;");
112+
}
113+
114+
#[test]
115+
fn test_push_bind() {
116+
let mut qb: QueryBuilder<'_, Postgres> =
117+
QueryBuilder::new("SELECT * FROM users WHERE id = ");
118+
119+
qb.push_bind(42i32)
120+
.push(" OR membership_level = ")
121+
.push_bind(3i32);
122+
123+
assert_eq!(
124+
qb.query,
125+
"SELECT * FROM users WHERE id = $1 OR membership_level = $2"
126+
);
127+
}
128+
129+
#[test]
130+
fn test_build() {
131+
let mut qb: QueryBuilder<'_, Postgres> = QueryBuilder::new("SELECT * FROM users");
132+
133+
qb.push(" WHERE id = ").push_bind(42i32);
134+
let query = qb.build();
135+
136+
assert_eq!(
137+
query.statement.unwrap_left(),
138+
"SELECT * FROM users WHERE id = $1"
139+
);
140+
assert_eq!(query.persistent, true);
141+
}
142+
143+
#[test]
144+
fn test_reset() {
145+
let mut qb: QueryBuilder<'_, Postgres> = QueryBuilder::new("");
146+
147+
let _query = qb
148+
.push("SELECT * FROM users WHERE id = ")
149+
.push_bind(42i32)
150+
.build();
151+
152+
qb.reset();
153+
154+
assert_eq!(qb.query, "");
155+
}
156+
157+
#[test]
158+
fn test_query_builder_reuse() {
159+
let mut qb: QueryBuilder<'_, Postgres> = QueryBuilder::new("");
160+
161+
let _query = qb
162+
.push("SELECT * FROM users WHERE id = ")
163+
.push_bind(42i32)
164+
.build();
165+
166+
qb.reset();
167+
168+
let query = qb.push("SELECT * FROM users WHERE id = 99").build();
169+
170+
assert_eq!(
171+
query.statement.unwrap_left(),
172+
"SELECT * FROM users WHERE id = 99"
173+
);
174+
}
175+
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub use sqlx_core::from_row::FromRow;
2323
pub use sqlx_core::pool::{self, Pool};
2424
pub use sqlx_core::query::{query, query_with};
2525
pub use sqlx_core::query_as::{query_as, query_as_with};
26+
pub use sqlx_core::query_builder::QueryBuilder;
2627
pub use sqlx_core::query_scalar::{query_scalar, query_scalar_with};
2728
pub use sqlx_core::row::Row;
2829
pub use sqlx_core::statement::Statement;

0 commit comments

Comments
 (0)