Skip to content

Commit 6cfafef

Browse files
committed
Implement a new raw SQL API geared towards complete queries
`sql` has always been a bit clunky to use if you wanted to write a whole query with it. You have to specify the SQL type of the query, which also means you have to list every column out explicitly to ensure they're in the expected order. This adds a replacement API that more geared towards writing complete queries. There are two notable differences. The first is that the SQL type of each field is specified during deserialization, not when the query is constructed. The types are never checked anyway (other than to make sure the deserialization is valid), so it makes sense to defer this to a place where we can derive it. The second difference is that columns are deserialized by name, not by index. This has all of the gotchas that are associated with doing that with other libraries (name conflicts between tables, etc) I had originally planned on having `NamedRow::get` optionally also take a table name. However, PG gives you the OID not the name, so we'd need to do another query to get the table name. SQLite only gives you the table/column name if compiled with a flag to enable it, which the system sqlite3 on mac does not. For those two reasons, I've opted to use the column name alone. This means that the derived `QueryableByName` is unlikely to be usable with joins, but that's a price we'll have to pay for now. I'd like to expand the number of cases that our derive impl can handle (e.g. allow providing a sql type in addition to a column name), but this is a good bare minimum place to start. Fixes diesel-rs#650.
1 parent c46b45b commit 6cfafef

35 files changed

Lines changed: 887 additions & 64 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
4242
* `allow_tables_to_appear_in_same_query!` can now take more than 2 tables, and is the same
4343
as invoking it separately for every combination of those tables.
4444

45+
* Added `sql_query`, a new API for dropping to raw SQL that is more pleasant to
46+
use than `sql` for complete queries. The main difference from `sql` is that
47+
you do not need to state the return type, and data is loaded from the query by
48+
name rather than by index.
49+
4550
### Changed
4651

4752
* The signatures of `QueryId`, `Column`, and `FromSqlRow` have all changed to
@@ -74,6 +79,11 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
7479
* Deprecated `enable_multi_table_joins` in favor of
7580
`allow_tables_to_appear_in_same_query!`
7681

82+
* Deprecated `SqlLiteral#bind`. `sql` is intended for use with small fragments
83+
of SQL, not complete queries. Writing bind parameters in raw SQL when you are
84+
not writing the whole query is error-prone. Use `sql_query` if you need raw
85+
SQL with bind parameters.
86+
7787
### Removed
7888

7989
* `IntoInsertStatement` and `BatchInsertStatement` have been removed. It's

diesel/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ bitflags = { version = "0.9", optional = true }
3333

3434
[dev-dependencies]
3535
cfg-if = "0.1.0"
36-
diesel_codegen = "0.13.0"
36+
diesel_codegen = "0.16.0"
3737
dotenv = ">=0.8, <0.11"
3838
quickcheck = "0.3.1"
3939
tempdir = "^0.3.4"

diesel/src/connection/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod transaction_manager;
33

44
use backend::Backend;
55
use query_builder::{AsQuery, QueryFragment, QueryId};
6-
use query_source::Queryable;
6+
use query_source::{Queryable, QueryableByName};
77
use result::*;
88
use types::HasSqlType;
99

@@ -145,6 +145,12 @@ pub trait Connection: SimpleConnection + Sized + Send {
145145
Self::Backend: HasSqlType<T::SqlType>,
146146
U: Queryable<T::SqlType, Self::Backend>;
147147

148+
#[doc(hidden)]
149+
fn query_by_name<T, U>(&self, source: &T) -> QueryResult<Vec<U>>
150+
where
151+
T: QueryFragment<Self::Backend> + QueryId,
152+
U: QueryableByName<Self::Backend>;
153+
148154
#[doc(hidden)]
149155
fn execute_returning_count<T>(&self, source: &T) -> QueryResult<usize>
150156
where

diesel/src/expression/sql_literal.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use backend::Backend;
44
use expression::*;
55
use query_builder::*;
66
use result::QueryResult;
7+
#[cfg(feature = "with-deprecated")]
78
use super::unchecked_bind::UncheckedBind;
89
use types::HasSqlType;
910

@@ -99,6 +100,8 @@ impl<ST> SqlLiteral<ST> {
99100
/// assert_eq!(Ok(expected), query.load(&connection));
100101
/// # }
101102
/// ```
103+
#[deprecated(since = "0.99.0", note = "use `sql_query` if you need bind parameters")]
104+
#[cfg(feature = "with-deprecated")]
102105
pub fn bind<BindST, T>(self, bind_value: T) -> UncheckedBind<Self, T, BindST> {
103106
UncheckedBind::new(self, bind_value)
104107
}
@@ -137,11 +140,9 @@ impl<ST> NonAggregate for SqlLiteral<ST> {}
137140
/// DSL. You will need to provide the SQL type of the expression, in addition to
138141
/// the SQL.
139142
///
140-
/// # Bound parameters
141-
///
142-
/// If you need to pass arguments to your query, you should use [`.bind()`].
143-
///
144-
/// [`.bind()`]: ../sql_literal/struct.SqlLiteral.html#method.bind
143+
/// This function is intended for use when you need a small bit of raw SQL in
144+
/// your query. If you want to write the entire query using raw SQL, use
145+
/// [`sql_query`](../fn.sql_query.html) instead.
145146
///
146147
/// # Safety
147148
///

diesel/src/expression/unchecked_bind.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![cfg(feature = "with-deprecated")]
2+
13
use std::marker::PhantomData;
24

35
use backend::Backend;

diesel/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ pub use prelude::*;
173173
#[doc(inline)]
174174
pub use query_builder::debug_query;
175175
#[doc(inline)]
176-
pub use query_builder::functions::{delete, insert_into, replace_into, select, update};
176+
pub use query_builder::functions::{delete, insert_into, replace_into, select, sql_query, update};
177177
#[cfg(feature = "with-deprecated")]
178178
#[doc(inline)]
179179
#[allow(deprecated)]

diesel/src/mysql/connection/bind.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ impl Binds {
3434
Binds { data: data }
3535
}
3636

37+
pub fn from_result_metadata(fields: &[ffi::MYSQL_FIELD]) -> Self {
38+
let data = fields
39+
.iter()
40+
.map(|field| field.type_)
41+
.map(BindData::for_output)
42+
.collect();
43+
44+
Binds { data }
45+
}
46+
3747
pub fn with_mysql_binds<F, T>(&mut self, f: F) -> T
3848
where
3949
F: FnOnce(*mut ffi::MYSQL_BIND) -> T,

diesel/src/mysql/connection/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod url;
66
use connection::*;
77
use query_builder::*;
88
use query_builder::bind_collector::RawBytesBindCollector;
9-
use query_source::Queryable;
9+
use query_source::{Queryable, QueryableByName};
1010
use result::*;
1111
use self::raw::RawConnection;
1212
use self::stmt::Statement;
@@ -80,6 +80,19 @@ impl Connection for MysqlConnection {
8080
})
8181
}
8282

83+
#[doc(hidden)]
84+
fn query_by_name<T, U>(&self, source: &T) -> QueryResult<Vec<U>>
85+
where
86+
T: QueryFragment<Self::Backend> + QueryId,
87+
U: QueryableByName<Self::Backend>,
88+
{
89+
use result::Error::DeserializationError;
90+
91+
let mut stmt = try!(self.prepare_query(source));
92+
let results = unsafe { stmt.named_results()? };
93+
results.map(|row| U::build(&row).map_err(DeserializationError))
94+
}
95+
8396
#[doc(hidden)]
8497
fn execute_returning_count<T>(&self, source: &T) -> QueryResult<usize>
8598
where

diesel/src/mysql/connection/stmt/iterator.rs

Lines changed: 92 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
use super::{ffi, libc, Binds, Statement};
1+
use std::collections::HashMap;
2+
3+
use super::{ffi, libc, Binds, Statement, StatementMetadata};
24
use result::QueryResult;
3-
use row::Row;
5+
use row::*;
46
use mysql::{Mysql, MysqlType};
57

68
pub struct StatementIterator<'a> {
@@ -13,10 +15,7 @@ impl<'a> StatementIterator<'a> {
1315
pub fn new(stmt: &'a mut Statement, types: Vec<MysqlType>) -> QueryResult<Self> {
1416
let mut output_binds = Binds::from_output_types(types);
1517

16-
unsafe {
17-
output_binds.with_mysql_binds(|bind_ptr| stmt.bind_result(bind_ptr))?;
18-
stmt.execute()?;
19-
}
18+
execute_statement(stmt, &mut output_binds)?;
2019

2120
Ok(StatementIterator {
2221
stmt: stmt,
@@ -36,25 +35,14 @@ impl<'a> StatementIterator<'a> {
3635
}
3736

3837
fn next(&mut self) -> Option<QueryResult<MysqlRow>> {
39-
let next_row_result = unsafe { ffi::mysql_stmt_fetch(self.stmt.stmt) };
40-
match next_row_result as libc::c_uint {
41-
ffi::MYSQL_NO_DATA => return None,
42-
ffi::MYSQL_DATA_TRUNCATED => {
43-
let res = self.output_binds.populate_dynamic_buffers(self.stmt);
44-
if let Err(e) = res {
45-
return Some(Err(e));
46-
}
47-
}
48-
0 => self.output_binds.update_buffer_lengths(),
49-
_error => if let Err(e) = self.stmt.did_an_error_occur() {
50-
return Some(Err(e));
51-
},
38+
match populate_row_buffers(self.stmt, &mut self.output_binds) {
39+
Ok(Some(())) => Some(Ok(MysqlRow {
40+
col_idx: 0,
41+
binds: &mut self.output_binds,
42+
})),
43+
Ok(None) => None,
44+
Err(e) => Some(Err(e)),
5245
}
53-
54-
Some(Ok(MysqlRow {
55-
col_idx: 0,
56-
binds: &mut self.output_binds,
57-
}))
5846
}
5947
}
6048

@@ -74,3 +62,83 @@ impl<'a> Row<Mysql> for MysqlRow<'a> {
7462
(0..count).all(|i| self.binds.field_data(self.col_idx + i).is_none())
7563
}
7664
}
65+
66+
pub struct NamedStatementIterator<'a> {
67+
stmt: &'a mut Statement,
68+
output_binds: Binds,
69+
metadata: StatementMetadata,
70+
}
71+
72+
#[cfg_attr(feature = "clippy", allow(should_implement_trait))] // don't need `Iterator` here
73+
impl<'a> NamedStatementIterator<'a> {
74+
pub fn new(stmt: &'a mut Statement) -> QueryResult<Self> {
75+
let metadata = stmt.metadata()?;
76+
let mut output_binds = Binds::from_result_metadata(metadata.fields());
77+
78+
execute_statement(stmt, &mut output_binds)?;
79+
80+
Ok(NamedStatementIterator {
81+
stmt,
82+
output_binds,
83+
metadata,
84+
})
85+
}
86+
87+
pub fn map<F, T>(mut self, mut f: F) -> QueryResult<Vec<T>>
88+
where
89+
F: FnMut(NamedMysqlRow) -> QueryResult<T>,
90+
{
91+
let mut results = Vec::new();
92+
while let Some(row) = self.next() {
93+
results.push(f(row?)?);
94+
}
95+
Ok(results)
96+
}
97+
98+
fn next(&mut self) -> Option<QueryResult<NamedMysqlRow>> {
99+
match populate_row_buffers(self.stmt, &mut self.output_binds) {
100+
Ok(Some(())) => Some(Ok(NamedMysqlRow {
101+
binds: &self.output_binds,
102+
column_indices: self.metadata.column_indices(),
103+
})),
104+
Ok(None) => None,
105+
Err(e) => Some(Err(e)),
106+
}
107+
}
108+
}
109+
110+
pub struct NamedMysqlRow<'a> {
111+
binds: &'a Binds,
112+
column_indices: &'a HashMap<&'a str, usize>,
113+
}
114+
115+
impl<'a> NamedRow<Mysql> for NamedMysqlRow<'a> {
116+
fn index_of(&self, column_name: &str) -> Option<usize> {
117+
self.column_indices.get(column_name).cloned()
118+
}
119+
120+
fn get_raw_value(&self, idx: usize) -> Option<&[u8]> {
121+
self.binds.field_data(idx)
122+
}
123+
}
124+
125+
fn execute_statement(stmt: &mut Statement, binds: &mut Binds) -> QueryResult<()> {
126+
unsafe {
127+
binds.with_mysql_binds(|bind_ptr| stmt.bind_result(bind_ptr))?;
128+
stmt.execute()?;
129+
}
130+
Ok(())
131+
}
132+
133+
fn populate_row_buffers(stmt: &Statement, binds: &mut Binds) -> QueryResult<Option<()>> {
134+
let next_row_result = unsafe { ffi::mysql_stmt_fetch(stmt.stmt) };
135+
match next_row_result as libc::c_uint {
136+
ffi::MYSQL_NO_DATA => Ok(None),
137+
ffi::MYSQL_DATA_TRUNCATED => binds.populate_dynamic_buffers(stmt).map(Some),
138+
0 => {
139+
binds.update_buffer_lengths();
140+
Ok(Some(()))
141+
}
142+
_error => stmt.did_an_error_occur().map(Some),
143+
}
144+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use std::collections::HashMap;
2+
use std::ffi::CStr;
3+
use std::slice;
4+
5+
use super::ffi;
6+
7+
pub struct StatementMetadata {
8+
result: &'static mut ffi::MYSQL_RES,
9+
column_indices: HashMap<&'static str, usize>,
10+
}
11+
12+
impl StatementMetadata {
13+
pub fn new(result: &'static mut ffi::MYSQL_RES) -> Self {
14+
let mut res = StatementMetadata {
15+
column_indices: HashMap::new(),
16+
result,
17+
};
18+
res.populate_column_indices();
19+
res
20+
}
21+
22+
pub fn fields(&self) -> &[ffi::MYSQL_FIELD] {
23+
unsafe {
24+
let ptr = self.result as *const _ as *mut _;
25+
let num_fields = ffi::mysql_num_fields(ptr);
26+
let field_ptr = ffi::mysql_fetch_fields(ptr);
27+
slice::from_raw_parts(field_ptr, num_fields as usize)
28+
}
29+
}
30+
31+
pub fn column_indices(&self) -> &HashMap<&str, usize> {
32+
&self.column_indices
33+
}
34+
35+
fn populate_column_indices(&mut self) {
36+
self.column_indices = self.fields()
37+
.iter()
38+
.enumerate()
39+
.map(|(i, field)| {
40+
let c_name = unsafe { CStr::from_ptr(field.name) };
41+
(c_name.to_str().unwrap_or_default(), i)
42+
})
43+
.collect()
44+
}
45+
}
46+
47+
impl Drop for StatementMetadata {
48+
fn drop(&mut self) {
49+
unsafe { ffi::mysql_free_result(self.result) };
50+
}
51+
}

0 commit comments

Comments
 (0)