Parameter binding support!

This commit is contained in:
Steven Fackler
2013-08-24 23:47:36 -04:00
parent 9ff747724a
commit ceaac705d6
3 changed files with 180 additions and 20 deletions

View File

@@ -46,7 +46,7 @@ impl PostgresConnection {
loop {
match conn.read_message() {
ParameterStatus(param, value) =>
printfln!("Param %s = %s", param, value),
info!("Param %s = %s", param, value),
BackendKeyData(*) => (),
ReadyForQuery(*) => break,
resp => fail!("Bad response: %?", resp.to_str())
@@ -173,9 +173,15 @@ impl<'self> PostgresStatement<'self> {
self.num_params
}
fn execute(&self, portal_name: &str) {
fn execute(&self, portal_name: &str, params: &[&ToSql]) {
if self.num_params != params.len() {
fail!("Expected %u params but got %u", self.num_params,
params.len());
}
let formats = [];
let values = [];
let values: ~[Option<~[u8]>] = params.iter().map(|val| val.to_sql())
.collect();
let result_formats = [];
self.conn.write_message(&Bind(portal_name, self.name.as_slice(),
@@ -190,8 +196,9 @@ impl<'self> PostgresStatement<'self> {
}
}
pub fn update(&self) -> uint {
self.execute("");
pub fn update(&self, params: &[&ToSql]) -> uint {
// The unnamed portal is automatically cleaned up at sync time
self.execute("", params);
let mut num = 0;
loop {
@@ -216,12 +223,12 @@ impl<'self> PostgresStatement<'self> {
num
}
pub fn query<'a>(&'a self) -> PostgresResult<'a> {
pub fn query<'a>(&'a self, params: &[&ToSql]) -> PostgresResult<'a> {
let id = self.next_portal_id.take();
let portal_name = ifmt!("{:s}_portal_{}", self.name.as_slice(), id);
self.next_portal_id.put_back(id + 1);
self.execute(portal_name);
self.execute(portal_name, params);
let mut data = ~[];
loop {
@@ -313,9 +320,139 @@ pub trait FromSql {
fn from_sql(raw: &Option<~[u8]>) -> Self;
}
impl FromSql for int {
fn from_sql(raw: &Option<~[u8]>) -> int {
FromStr::from_str(str::from_bytes_slice(raw.get_ref().as_slice()))
.unwrap()
macro_rules! from_str_impl(
($t:ty) => (
impl FromSql for Option<$t> {
fn from_sql(raw: &Option<~[u8]>) -> Option<$t> {
match *raw {
None => None,
Some(ref buf) => {
let s = str::from_bytes_slice(buf.as_slice());
Some(FromStr::from_str(s).unwrap())
}
}
}
}
)
)
macro_rules! from_option_impl(
($t:ty) => (
impl FromSql for $t {
fn from_sql(raw: &Option<~[u8]>) -> $t {
FromSql::from_sql::<Option<$t>>(raw).unwrap()
}
}
)
)
from_str_impl!(int)
from_option_impl!(int)
from_str_impl!(i8)
from_option_impl!(i8)
from_str_impl!(i16)
from_option_impl!(i16)
from_str_impl!(i32)
from_option_impl!(i32)
from_str_impl!(i64)
from_option_impl!(i64)
from_str_impl!(uint)
from_option_impl!(uint)
from_str_impl!(u8)
from_option_impl!(u8)
from_str_impl!(u16)
from_option_impl!(u16)
from_str_impl!(u32)
from_option_impl!(u32)
from_str_impl!(u64)
from_option_impl!(u64)
from_str_impl!(float)
from_option_impl!(float)
from_str_impl!(f32)
from_option_impl!(f32)
from_str_impl!(f64)
from_option_impl!(f64)
impl FromSql for Option<~str> {
fn from_sql(raw: &Option<~[u8]>) -> Option<~str> {
do raw.chain_ref |buf| {
Some(str::from_bytes(buf.as_slice()))
}
}
}
from_option_impl!(~str)
pub trait ToSql {
fn to_sql(&self) -> Option<~[u8]>;
}
macro_rules! to_str_impl(
($t:ty) => (
impl ToSql for $t {
fn to_sql(&self) -> Option<~[u8]> {
Some(self.to_str().into_bytes())
}
}
)
)
macro_rules! to_option_impl(
($t:ty) => (
impl ToSql for Option<$t> {
fn to_sql(&self) -> Option<~[u8]> {
do self.chain |val| {
val.to_sql()
}
}
}
)
)
to_str_impl!(int)
to_option_impl!(int)
to_str_impl!(i8)
to_option_impl!(i8)
to_str_impl!(i16)
to_option_impl!(i16)
to_str_impl!(i32)
to_option_impl!(i32)
to_str_impl!(i64)
to_option_impl!(i64)
to_str_impl!(uint)
to_option_impl!(uint)
to_str_impl!(u8)
to_option_impl!(u8)
to_str_impl!(u16)
to_option_impl!(u16)
to_str_impl!(u32)
to_option_impl!(u32)
to_str_impl!(u64)
to_option_impl!(u64)
to_str_impl!(float)
to_option_impl!(float)
to_str_impl!(f32)
to_option_impl!(f32)
to_str_impl!(f64)
to_option_impl!(f64)
impl<'self> ToSql for &'self str {
fn to_sql(&self) -> Option<~[u8]> {
Some(self.as_bytes().to_owned())
}
}
impl ToSql for Option<~str> {
fn to_sql(&self) -> Option<~[u8]> {
do self.chain_ref |val| {
val.to_sql()
}
}
}
impl<'self> ToSql for Option<&'self str> {
fn to_sql(&self) -> Option<~[u8]> {
do self.chain |val| {
val.to_sql()
}
}
}

View File

@@ -41,7 +41,7 @@ pub struct RowDescriptionEntry {
pub enum FrontendMessage<'self> {
/// portal, stmt, formats, values, result formats
Bind(&'self str, &'self str, &'self [i16], &'self [Option<&'self [u8]>],
Bind(&'self str, &'self str, &'self [i16], &'self [Option<~[u8]>],
&'self [i16]),
Close(u8, &'self str),
Describe(u8, &'self str),
@@ -90,11 +90,11 @@ impl<W: Writer> WriteMessage for W {
for value in values.iter() {
match *value {
None => {
buf.write_be_i16_(-1);
buf.write_be_i32_(-1);
}
Some(value) => {
Some(ref value) => {
buf.write_be_i32_(value.len() as i32);
buf.write(value);
buf.write(*value);
}
}
}

View File

@@ -1,13 +1,13 @@
extern mod postgres;
use postgres::PostgresConnection;
use postgres::{PostgresConnection, ToSql};
#[test]
fn test_basic() {
let conn = PostgresConnection::connect("postgres://postgres@127.0.0.1:5432");
do conn.in_transaction |conn| {
conn.prepare("CREATE TABLE foo (id BIGINT PRIMARY KEY)").update();
conn.prepare("CREATE TABLE foo (id BIGINT PRIMARY KEY)").update([]);
Err::<(), ()>(())
};
@@ -18,13 +18,36 @@ fn test_query() {
let conn = PostgresConnection::connect("postgres://postgres@127.0.0.1:5432");
do conn.in_transaction |conn| {
conn.prepare("CREATE TABLE foo (id BIGINT PRIMARY KEY)").update();
conn.prepare("INSERT INTO foo (id) VALUES (1), (2)").update();
conn.prepare("CREATE TABLE foo (id BIGINT PRIMARY KEY)").update([]);
conn.prepare("INSERT INTO foo (id) VALUES ($1), ($2)")
.update([&1 as &ToSql, &2 as &ToSql]);
let stmt = conn.prepare("SELECT * from foo ORDER BY id");
let result = stmt.query();
let result = stmt.query([]);
assert_eq!(~[1, 2], result.iter().map(|row| { row[0] }).collect());
Err::<(), ()>(())
};
}
#[test]
fn test_nulls() {
let conn = PostgresConnection::connect("postgres://postgres@127.0.0.1:5432");
do conn.in_transaction |conn| {
conn.prepare("CREATE TABLE foo (
id BIGINT PRIMARY KEY,
val VARCHAR
)").update([]);
conn.prepare("INSERT INTO foo (id, val) VALUES ($1, $2), ($3, $4)")
.update([&1 as &ToSql, & &"foobar" as &ToSql,
&2 as &ToSql, &None::<~str> as &ToSql]);
let stmt = conn.prepare("SELECT id, val FROM foo ORDER BY id");
let result = stmt.query([]);
assert_eq!(~[Some(~"foobar"), None],
result.iter().map(|row| { row[1] }).collect());
Err::<(), ()>(())
};
}