|
| 1 | +use std::fmt::{self, Debug}; |
1 | 2 | use std::marker::PhantomData; |
2 | 3 |
|
3 | 4 | use backend::Backend; |
4 | 5 | use connection::Connection; |
5 | 6 | use deserialize::QueryableByName; |
6 | | -use query_builder::{AstPass, QueryFragment, QueryId}; |
| 7 | +use query_builder::{AstPass, QueryFragment, QueryId, self, debug_query}; |
7 | 8 | use query_dsl::{LoadQuery, RunQueryDsl}; |
8 | 9 | use result::QueryResult; |
9 | 10 | use serialize::ToSql; |
@@ -76,6 +77,15 @@ impl SqlQuery { |
76 | 77 | pub fn bind<ST, Value>(self, value: Value) -> UncheckedBind<Self, Value, ST> { |
77 | 78 | UncheckedBind::new(self, value) |
78 | 79 | } |
| 80 | + |
| 81 | + /// Internally boxes future calls on `bind` and `sql` so that they don't |
| 82 | + /// change the type. |
| 83 | + /// |
| 84 | + /// This allows doing things you otherwise couldn't do, e.g. `bind`ing in a |
| 85 | + /// loop. |
| 86 | + pub fn into_boxed<'f, DB: Backend>(self) -> BoxedSqlQuery<'f, DB, Self> { |
| 87 | + BoxedSqlQuery::new(self) |
| 88 | + } |
79 | 89 | } |
80 | 90 |
|
81 | 91 | impl<DB> QueryFragment<DB> for SqlQuery |
@@ -127,6 +137,10 @@ impl<Query, Value, ST> UncheckedBind<Query, Value, ST> { |
127 | 137 | pub fn bind<ST2, Value2>(self, value: Value2) -> UncheckedBind<Self, Value2, ST2> { |
128 | 138 | UncheckedBind::new(self, value) |
129 | 139 | } |
| 140 | + |
| 141 | + pub fn into_boxed<'f, DB: Backend>(self) -> BoxedSqlQuery<'f, DB, Self> { |
| 142 | + BoxedSqlQuery::new(self) |
| 143 | + } |
130 | 144 | } |
131 | 145 |
|
132 | 146 | impl<Query, Value, ST> QueryId for UncheckedBind<Query, Value, ST> |
@@ -164,3 +178,85 @@ where |
164 | 178 | } |
165 | 179 |
|
166 | 180 | impl<Conn, Query, Value, ST> RunQueryDsl<Conn> for UncheckedBind<Query, Value, ST> {} |
| 181 | + |
| 182 | +#[must_use = "Queries are only executed when calling `load`, `get_result`, or similar."] |
| 183 | +/// See `SqlQuery` |
| 184 | +pub struct BoxedSqlQuery<'f, DB: Backend, Inner> { |
| 185 | + inner: Inner, |
| 186 | + sql: String, |
| 187 | + binds: Vec<Box<dyn Fn(AstPass<DB>) -> QueryResult<()> + 'f>>, |
| 188 | +} |
| 189 | + |
| 190 | +impl<DB: Backend, Inner> Debug for BoxedSqlQuery<'_, DB, Inner> |
| 191 | +where |
| 192 | + for<'a> debug_query::DebugQuery<'a, Self, DB>: Debug, |
| 193 | +{ |
| 194 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 195 | + query_builder::debug_query::<DB, _>(self).fmt(f) |
| 196 | + } |
| 197 | +} |
| 198 | + |
| 199 | +impl<'f, DB: Backend, Inner> BoxedSqlQuery<'f, DB, Inner> { |
| 200 | + pub(crate) fn new(inner: Inner) -> Self { |
| 201 | + BoxedSqlQuery { |
| 202 | + inner, |
| 203 | + sql: "".to_string(), |
| 204 | + binds: vec![], |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | + /// See `SqlQuery`'s `bind` |
| 209 | + pub fn bind<BindSt, Value>(mut self, b: Value) -> Self |
| 210 | + where |
| 211 | + BindSt: QueryId, |
| 212 | + DB: HasSqlType<BindSt>, |
| 213 | + Value: ToSql<BindSt, DB> + 'f, |
| 214 | + { |
| 215 | + self.binds.push(Box::new(move |mut out| { |
| 216 | + out.push_bind_param_value_only(&b) |
| 217 | + })); |
| 218 | + self |
| 219 | + } |
| 220 | + |
| 221 | + /// See `SqlQuery`'s `sql` |
| 222 | + pub fn sql(mut self, sql: &str) -> Self { |
| 223 | + self.sql += sql; |
| 224 | + self |
| 225 | + } |
| 226 | +} |
| 227 | + |
| 228 | +impl<DB, Inner> QueryFragment<DB> for BoxedSqlQuery<'_, DB, Inner> |
| 229 | +where |
| 230 | + DB: Backend, |
| 231 | + Inner: QueryFragment<DB>, |
| 232 | +{ |
| 233 | + fn walk_ast(&self, mut out: AstPass<DB>) -> QueryResult<()> { |
| 234 | + out.unsafe_to_cache_prepared(); |
| 235 | + self.inner.walk_ast(out.reborrow())?; |
| 236 | + out.push_sql(&self.sql); |
| 237 | + |
| 238 | + for b in &self.binds { |
| 239 | + b(out.reborrow())?; |
| 240 | + } |
| 241 | + Ok(()) |
| 242 | + } |
| 243 | +} |
| 244 | + |
| 245 | +impl<DB: Backend, Inner> QueryId for BoxedSqlQuery<'_, DB, Inner> { |
| 246 | + type QueryId = (); |
| 247 | + |
| 248 | + const HAS_STATIC_QUERY_ID: bool = false; |
| 249 | +} |
| 250 | + |
| 251 | +impl<Conn, T, Inner> LoadQuery<Conn, T> for BoxedSqlQuery<'_, Conn::Backend, Inner> |
| 252 | +where |
| 253 | + Conn: Connection, |
| 254 | + T: QueryableByName<Conn::Backend>, |
| 255 | + Self: QueryFragment<Conn::Backend> + QueryId, |
| 256 | +{ |
| 257 | + fn internal_load(self, conn: &Conn) -> QueryResult<Vec<T>> { |
| 258 | + conn.query_by_name(&self) |
| 259 | + } |
| 260 | +} |
| 261 | + |
| 262 | +impl<Conn: Connection, Inner> RunQueryDsl<Conn> for BoxedSqlQuery<'_, Conn::Backend, Inner> {} |
0 commit comments