mirror of
				https://github.com/isar/rusqlite.git
				synced 2025-10-31 13:58:55 +08:00 
			
		
		
		
	Merge branch 'named-param' of https://github.com/gwenn/rusqlite into gwenn-named-param
Conflicts: Cargo.toml src/lib.rs
This commit is contained in:
		
							
								
								
									
										40
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								src/lib.rs
									
									
									
									
									
								
							| @@ -82,6 +82,7 @@ pub use load_extension_guard::SqliteLoadExtensionGuard; | ||||
|  | ||||
| pub mod types; | ||||
| mod transaction; | ||||
| mod named_params; | ||||
| #[cfg(feature = "load_extension")]mod load_extension_guard; | ||||
| #[cfg(feature = "trace")]pub mod trace; | ||||
| #[cfg(feature = "backup")]pub mod backup; | ||||
| @@ -790,29 +791,32 @@ impl<'conn> SqliteStatement<'conn> { | ||||
|     pub fn execute(&mut self, params: &[&ToSql]) -> SqliteResult<c_int> { | ||||
|         unsafe { | ||||
|             try!(self.bind_parameters(params)); | ||||
|             self.execute_() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|             let r = ffi::sqlite3_step(self.stmt); | ||||
|             ffi::sqlite3_reset(self.stmt); | ||||
|             match r { | ||||
|                 ffi::SQLITE_DONE => { | ||||
|                     if self.column_count != 0 { | ||||
|                         Err(SqliteError { | ||||
|                             code: ffi::SQLITE_MISUSE, | ||||
|                             message: "Unexpected column count - did you mean to call query?" | ||||
|                             .to_string(), | ||||
|                         }) | ||||
|                     } else { | ||||
|                         Ok(self.conn.changes()) | ||||
|                     } | ||||
|                 } | ||||
|                 ffi::SQLITE_ROW => { | ||||
|     unsafe fn execute_(&mut self) -> SqliteResult<c_int> { | ||||
|         let r = ffi::sqlite3_step(self.stmt); | ||||
|         ffi::sqlite3_reset(self.stmt); | ||||
|         match r { | ||||
|             ffi::SQLITE_DONE => { | ||||
|                 if self.column_count != 0 { | ||||
|                     Err(SqliteError { | ||||
|                         code: r, | ||||
|                         message: "Unexpected row result - did you mean to call query?".to_string(), | ||||
|                         code: ffi::SQLITE_MISUSE, | ||||
|                         message: "Unexpected column count - did you mean to call query?" | ||||
|                         .to_string(), | ||||
|                     }) | ||||
|                 } else { | ||||
|                     Ok(self.conn.changes()) | ||||
|                 } | ||||
|                 _ => Err(self.conn.decode_result(r).unwrap_err()), | ||||
|             } | ||||
|             ffi::SQLITE_ROW => { | ||||
|                 Err(SqliteError { | ||||
|                     code: r, | ||||
|                     message: "Unexpected row result - did you mean to call query?".to_string(), | ||||
|                 }) | ||||
|             } | ||||
|             _ => Err(self.conn.decode_result(r).unwrap_err()), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
							
								
								
									
										186
									
								
								src/named_params.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/named_params.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | ||||
| //use std::collections::HashMap; | ||||
| use std::ffi::CString; | ||||
| use libc::c_int; | ||||
|  | ||||
| use super::ffi; | ||||
|  | ||||
| use {SqliteResult, SqliteError, SqliteConnection, SqliteStatement, SqliteRows, SqliteRow}; | ||||
| use types::ToSql; | ||||
|  | ||||
| impl SqliteConnection { | ||||
|     /// Convenience method to prepare and execute a single SQL statement with named parameter(s). | ||||
|     /// | ||||
|     /// ## Example | ||||
|     /// | ||||
|     /// ```rust,no_run | ||||
|     /// # use rusqlite::{SqliteConnection, SqliteResult}; | ||||
|     /// fn insert(conn: &SqliteConnection) -> SqliteResult<i32> { | ||||
|     ///   conn.execute_named("INSERT INTO test (id, name, flag) VALUES (:id, :name, :flag)", &[(":name", &"one")]) | ||||
|     /// } | ||||
|     /// ``` | ||||
|     /// | ||||
|     /// # Failure | ||||
|     /// | ||||
|     /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the | ||||
|     /// underlying SQLite call fails. | ||||
|     pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> SqliteResult<c_int> { | ||||
|         self.prepare(sql).and_then(|mut stmt| stmt.execute_named(params)) | ||||
|     } | ||||
|  | ||||
|     /// Convenience method to execute a query with named parameter(s) that is expected to return a single row. | ||||
|     /// | ||||
|     /// If the query returns more than one row, all rows except the first are ignored. | ||||
|     /// | ||||
|     /// # Failure | ||||
|     /// | ||||
|     /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the | ||||
|     /// underlying SQLite call fails. | ||||
|     pub fn query_named_row<T, F>(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> SqliteResult<T> | ||||
|                            where F: FnOnce(SqliteRow) -> T { | ||||
|         let mut stmt = try!(self.prepare(sql)); | ||||
|         let mut rows = try!(stmt.query_named(params)); | ||||
|  | ||||
|         match rows.next() { | ||||
|             Some(row) => row.map(f), | ||||
|             None      => Err(SqliteError{ | ||||
|                 code: ffi::SQLITE_NOTICE, | ||||
|                 message: "Query did not return a row".to_string(), | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'conn> SqliteStatement<'conn> { | ||||
|     /*pub fn parameter_names(&self) -> HashMap<String, i32> { | ||||
|         let n = unsafe { ffi::sqlite3_bind_parameter_count(self.stmt) }; | ||||
|         let mut index_by_name = HashMap::with_capacity(n as usize); | ||||
|         for i in 1..n+1 { | ||||
|             let c_name = unsafe { ffi::sqlite3_bind_parameter_name(self.stmt, i) }; | ||||
|             if !c_name.is_null() { | ||||
|                 let c_slice = unsafe { CStr::from_ptr(c_name).to_bytes() }; | ||||
|                 index_by_name.insert(str::from_utf8(c_slice).unwrap().to_string(), n); | ||||
|             } | ||||
|         } | ||||
|         index_by_name | ||||
|     }*/ | ||||
|  | ||||
|     /// Return the index of an SQL parameter given its name. | ||||
|     /// | ||||
|     /// # Failure | ||||
|     /// | ||||
|     /// Return None if `name` is invalid (NulError) or if no matching parameter is found. | ||||
|     pub fn parameter_index(&self, name: &str) -> Option<i32> { | ||||
|         unsafe { | ||||
|             CString::new(name).ok().and_then(|c_name| | ||||
|                 match ffi::sqlite3_bind_parameter_index(self.stmt, c_name.as_ptr()) { | ||||
|                     0 => None, // A zero is returned if no matching parameter is found. | ||||
|                     n => Some(n) | ||||
|                 } | ||||
|             ) | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Execute the prepared statement with named parameter(s). | ||||
|     /// | ||||
|     /// On success, returns the number of rows that were changed or inserted or deleted (via | ||||
|     /// `sqlite3_changes`). | ||||
|     /// | ||||
|     /// ## Example | ||||
|     /// | ||||
|     /// ```rust,no_run | ||||
|     /// # use rusqlite::{SqliteConnection, SqliteResult}; | ||||
|     /// fn insert(conn: &SqliteConnection) -> SqliteResult<i32> { | ||||
|     ///   let mut stmt = try!(conn.prepare("INSERT INTO test (id, name, flag) VALUES (:id, :name, :flag)")); | ||||
|     ///   return stmt.execute_named(&[(":name", &"one")]); | ||||
|     /// } | ||||
|     /// ``` | ||||
|     /// | ||||
|     /// # Failure | ||||
|     /// | ||||
|     /// Will return `Err` if binding parameters fails, the executed statement returns rows (in | ||||
|     /// which case `query` should be used instead), or the underling SQLite call fails. | ||||
|     pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> SqliteResult<c_int> { | ||||
|         unsafe { | ||||
|             try!(self.bind_named_parameters(params)); | ||||
|             self.execute_() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Execute the prepared statement with named parameter(s), returning an iterator over the resulting rows. | ||||
|     /// | ||||
|     /// ## Example | ||||
|     /// | ||||
|     /// ```rust,no_run | ||||
|     /// # use rusqlite::{SqliteConnection, SqliteResult, SqliteRows}; | ||||
|     /// fn query(conn: &SqliteConnection) -> SqliteResult<()> { | ||||
|     ///   let mut stmt = try!(conn.prepare("SELECT * FROM test where name = :name")); | ||||
|     ///   let mut rows = try!(stmt.query_named(&[(":name", &"one")])); | ||||
|     ///   for row in rows { | ||||
|     ///   // ... | ||||
|     ///   } | ||||
|     ///   return Ok(()) | ||||
|     /// } | ||||
|     /// ``` | ||||
|     /// | ||||
|     /// # Failure | ||||
|     /// | ||||
|     /// Will return `Err` if binding parameters fails. | ||||
|     pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> SqliteResult<SqliteRows<'a>> { | ||||
|         self.reset_if_needed(); | ||||
|  | ||||
|         unsafe { | ||||
|             try!(self.bind_named_parameters(params)); | ||||
|         } | ||||
|  | ||||
|         self.needs_reset = true; | ||||
|         Ok(SqliteRows::new(self)) | ||||
|     } | ||||
|  | ||||
|     unsafe fn bind_named_parameters(&mut self, params: &[(&str, &ToSql)]) -> SqliteResult<()> { | ||||
|         for &(name, value) in params { | ||||
|             let i = try!(self.parameter_index(name).ok_or(SqliteError{ | ||||
|                 code: ffi::SQLITE_MISUSE, | ||||
|                 message: format!("Invalid parameter name: {}", name) | ||||
|             })); | ||||
|             try!(self.conn.decode_result(value.bind_parameter(self.stmt, i))); | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use SqliteConnection; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_execute_named() { | ||||
|         let db = SqliteConnection::open_in_memory().unwrap(); | ||||
|         db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap(); | ||||
|  | ||||
|         assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)]).unwrap(), 1); | ||||
|         assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]).unwrap(), 1); | ||||
|  | ||||
|         assert_eq!(3i32, db.query_named_row("SELECT SUM(x) FROM foo WHERE x > :x", &[(":x", &0i32)], |r| r.get(0)).unwrap()); | ||||
|     } | ||||
|  | ||||
|    #[test] | ||||
|     fn test_stmt_execute_named() { | ||||
|         let db = SqliteConnection::open_in_memory().unwrap(); | ||||
|         let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER)"; | ||||
|         db.execute_batch(sql).unwrap(); | ||||
|  | ||||
|         let mut stmt = db.prepare("INSERT INTO test (id, name, flag) VALUES (:id, :name, :flag)").unwrap(); | ||||
|         stmt.execute_named(&[(":name", &"one")]).unwrap(); | ||||
|     } | ||||
|  | ||||
|    #[test] | ||||
|     fn test_query_named() { | ||||
|         let db = SqliteConnection::open_in_memory().unwrap(); | ||||
|         let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER)"; | ||||
|         db.execute_batch(sql).unwrap(); | ||||
|  | ||||
|         let mut stmt = db.prepare("SELECT * FROM test where name = :name").unwrap(); | ||||
|         stmt.query_named(&[(":name", &"one")]).unwrap(); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user