Binary transmission of numeric types
It looks like Postgres doesn't guarantee that its floats are IEEE754, but I don't know if there are any reasonable platforms on which the format is actually different. Work towards #7.
This commit is contained in:
parent
f56c519097
commit
557c61d6db
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
*.so
|
||||
*.dylib
|
||||
*.dylib.dSYM
|
||||
*.dummy
|
||||
check-*
|
||||
|
||||
|
2
Makefile
2
Makefile
@ -17,4 +17,4 @@ check-postgres: postgres.dummy src/test.rs
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm *.dummy *.so check-*
|
||||
git clean -dfx
|
||||
|
152
src/test.rs
152
src/test.rs
@ -1,13 +1,14 @@
|
||||
extern mod postgres;
|
||||
|
||||
use std::f32;
|
||||
use std::f64;
|
||||
|
||||
use postgres::*;
|
||||
use postgres::types::ToSql;
|
||||
use postgres::types::{ToSql, FromSql};
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let conn = PostgresConnection::connect("postgres://postgres@127.0.0.1:5432");
|
||||
|
||||
do conn.in_transaction |trans| {
|
||||
do test_in_transaction |trans| {
|
||||
trans.prepare("CREATE TABLE foo (id BIGINT PRIMARY KEY)").update([]);
|
||||
|
||||
trans.set_rollback();
|
||||
@ -29,11 +30,18 @@ fn test_prepare_err() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query() {
|
||||
fn test_in_transaction(blk: &fn(&PostgresTransaction)) {
|
||||
let conn = PostgresConnection::connect("postgres://postgres@127.0.0.1:5432");
|
||||
|
||||
do conn.in_transaction |trans| {
|
||||
blk(trans);
|
||||
trans.set_rollback();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query() {
|
||||
do test_in_transaction |trans| {
|
||||
trans.prepare("CREATE TABLE foo (id BIGINT PRIMARY KEY)").update([]);
|
||||
trans.prepare("INSERT INTO foo (id) VALUES ($1), ($2)")
|
||||
.update([&1 as &ToSql, &2 as &ToSql]);
|
||||
@ -41,68 +49,118 @@ fn test_query() {
|
||||
let result = stmt.query([]);
|
||||
|
||||
assert_eq!(~[1, 2], result.iter().map(|row| { row[0] }).collect());
|
||||
|
||||
trans.set_rollback();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nulls() {
|
||||
let conn = PostgresConnection::connect("postgres://postgres@127.0.0.1:5432");
|
||||
|
||||
do conn.in_transaction |trans| {
|
||||
do test_in_transaction |trans| {
|
||||
trans.prepare("CREATE TABLE foo (
|
||||
id BIGINT PRIMARY KEY,
|
||||
id SERIAL PRIMARY KEY,
|
||||
val VARCHAR
|
||||
)").update([]);
|
||||
trans.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 = trans.prepare("SELECT id, val FROM foo ORDER BY id");
|
||||
trans.prepare("INSERT INTO foo (val) VALUES ($1), ($2)")
|
||||
.update([& &"foobar" as &ToSql,
|
||||
&None::<~str> as &ToSql]);
|
||||
let stmt = trans.prepare("SELECT val FROM foo ORDER BY id");
|
||||
let result = stmt.query([]);
|
||||
|
||||
assert_eq!(~[Some(~"foobar"), None],
|
||||
result.iter().map(|row| { row[1] }).collect());
|
||||
|
||||
trans.set_rollback();
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_bool_params() {
|
||||
let conn = PostgresConnection::connect("postgres://postgres@127.0.0.1:5432");
|
||||
|
||||
do conn.in_transaction |trans| {
|
||||
trans.prepare("CREATE TABLE foo (
|
||||
id BIGINT PRIMARY KEY,
|
||||
b BOOL
|
||||
)").update([]);
|
||||
trans.prepare("INSERT INTO foo (id, b) VALUES
|
||||
($1, $2), ($3, $4), ($5, $6)")
|
||||
.update([&1 as &ToSql, &true as &ToSql,
|
||||
&2 as &ToSql, &false as &ToSql,
|
||||
&3 as &ToSql, &None::<bool> as &ToSql]);
|
||||
let stmt = trans.prepare("SELECT b FROM foo ORDER BY id");
|
||||
let result = stmt.query([]);
|
||||
|
||||
assert_eq!(~[Some(true), Some(false), None],
|
||||
result.iter().map(|row| { row[0] }).collect());
|
||||
|
||||
trans.set_rollback();
|
||||
}
|
||||
}
|
||||
|
||||
fn test_param_type<T: Eq+ToSql+FromSql>(sql_type: &str, values: &[T]) {
|
||||
do test_in_transaction |trans| {
|
||||
trans.prepare("CREATE TABLE foo (
|
||||
id SERIAL PRIMARY KEY,
|
||||
b " + sql_type +
|
||||
")").update([]);
|
||||
let stmt = trans.prepare("INSERT INTO foo (b) VALUES ($1)");
|
||||
for value in values.iter() {
|
||||
stmt.update([value as &ToSql]);
|
||||
}
|
||||
|
||||
let stmt = trans.prepare("SELECT b FROM foo ORDER BY id");
|
||||
let result = stmt.query([]);
|
||||
|
||||
let actual_values: ~[T] = result.iter().map(|row| { row[0] }).collect();
|
||||
assert_eq!(values, actual_values.as_slice());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_bool_params() {
|
||||
test_param_type("BOOL", [Some(true), Some(false), None]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_i16_params() {
|
||||
test_param_type("SMALLINT", [Some(0x0011), Some(-0x0011), None]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_i32_params() {
|
||||
test_param_type("INT", [Some(0x00112233), Some(-0x00112233), None]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_i64_params() {
|
||||
test_param_type("BIGINT", [Some(0x0011223344556677i64),
|
||||
Some(-0x0011223344556677i64), None]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_f32_params() {
|
||||
test_param_type("REAL", [Some(f32::infinity), Some(f32::neg_infinity),
|
||||
Some(1000.55), None]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_f64_params() {
|
||||
test_param_type("DOUBLE PRECISION", [Some(f64::infinity),
|
||||
Some(f64::neg_infinity),
|
||||
Some(10000.55), None]);
|
||||
}
|
||||
|
||||
fn test_nan_param<T: Float+ToSql+FromSql>(sql_type: &str) {
|
||||
do test_in_transaction |trans| {
|
||||
trans.prepare("CREATE TABLE foo (
|
||||
id SERIAL PRIMARY KEY,
|
||||
b " + sql_type +
|
||||
")").update([]);
|
||||
let nan: T = Float::NaN();
|
||||
trans.prepare("INSERT INTO foo (b) VALUES ($1)")
|
||||
.update([&nan as &ToSql]);
|
||||
|
||||
let stmt = trans.prepare("SELECT b FROM foo");
|
||||
let result = stmt.query([]);
|
||||
let val: T = result.iter().next().unwrap()[0];
|
||||
assert!(val.is_NaN());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_f32_nan_param() {
|
||||
test_nan_param::<f32>("REAL");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_f64_nan_param() {
|
||||
test_nan_param::<f64>("DOUBLE PRECISION");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_num_params() {
|
||||
let conn = PostgresConnection::connect("postgres://postgres@127.0.0.1:5432");
|
||||
|
||||
do conn.in_transaction |trans| {
|
||||
do test_in_transaction |trans| {
|
||||
trans.prepare("CREATE TABLE foo (
|
||||
id BIGINT PRIMARY KEY,
|
||||
id SERIAL PRIMARY KEY,
|
||||
val VARCHAR
|
||||
)").update([]);
|
||||
let res = trans.prepare("INSERT INTO foo (id, val) VALUES ($1, $2), ($3, $4)")
|
||||
.try_update([&1 as &ToSql, & &"foobar" as &ToSql]);
|
||||
let res = trans.prepare("INSERT INTO foo (val) VALUES ($1), ($2)")
|
||||
.try_update([& &"foobar" as &ToSql]);
|
||||
match res {
|
||||
Err(PostgresDbError { code: ~"08P01", _ }) => (),
|
||||
resp => fail!("Unexpected response: %?", resp)
|
||||
|
83
src/types.rs
83
src/types.rs
@ -1,9 +1,19 @@
|
||||
use std::rt::io::Decorator;
|
||||
use std::rt::io::extensions::WriterByteConversions;
|
||||
use std::rt::io::mem::MemWriter;
|
||||
use std::str;
|
||||
use std::f32;
|
||||
use std::f64;
|
||||
|
||||
pub type Oid = i32;
|
||||
|
||||
// Values from pg_type.h
|
||||
static BOOLOID: Oid = 16;
|
||||
static INT8OID: Oid = 20;
|
||||
static INT2OID: Oid = 21;
|
||||
static INT4OID: Oid = 23;
|
||||
static FLOAT4OID: Oid = 700;
|
||||
static FLOAT8OID: Oid = 701;
|
||||
|
||||
pub enum Format {
|
||||
Text = 0,
|
||||
@ -79,11 +89,39 @@ 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)
|
||||
|
||||
impl FromSql for Option<f32> {
|
||||
fn from_sql(raw: &Option<~[u8]>) -> Option<f32> {
|
||||
match *raw {
|
||||
None => None,
|
||||
Some(ref buf) => {
|
||||
Some(match str::from_bytes_slice(buf.as_slice()) {
|
||||
"NaN" => f32::NaN,
|
||||
"Infinity" => f32::infinity,
|
||||
"-Infinity" => f32::neg_infinity,
|
||||
str => FromStr::from_str(str).unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
from_option_impl!(f32)
|
||||
from_str_impl!(f64)
|
||||
|
||||
impl FromSql for Option<f64> {
|
||||
fn from_sql(raw: &Option<~[u8]>) -> Option<f64> {
|
||||
match *raw {
|
||||
None => None,
|
||||
Some(ref buf) => {
|
||||
Some(match str::from_bytes_slice(buf.as_slice()) {
|
||||
"NaN" => f64::NaN,
|
||||
"Infinity" => f64::infinity,
|
||||
"-Infinity" => f64::neg_infinity,
|
||||
str => FromStr::from_str(str).unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
from_option_impl!(f64)
|
||||
|
||||
impl FromSql for Option<~str> {
|
||||
@ -122,6 +160,22 @@ macro_rules! to_option_impl(
|
||||
)
|
||||
)
|
||||
|
||||
macro_rules! to_conversions_impl(
|
||||
($oid:ident, $t:ty, $f:ident) => (
|
||||
impl ToSql for $t {
|
||||
fn to_sql(&self, ty: Oid) -> (Format, Option<~[u8]>) {
|
||||
if ty == $oid {
|
||||
let mut writer = MemWriter::new();
|
||||
writer.$f(*self);
|
||||
(Binary, Some(writer.inner()))
|
||||
} else {
|
||||
(Text, Some(self.to_str().into_bytes()))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
impl ToSql for bool {
|
||||
fn to_sql(&self, ty: Oid) -> (Format, Option<~[u8]>) {
|
||||
if ty == BOOLOID {
|
||||
@ -133,16 +187,21 @@ impl ToSql for bool {
|
||||
}
|
||||
to_option_impl!(bool)
|
||||
|
||||
to_conversions_impl!(INT2OID, i16, write_be_i16_)
|
||||
to_option_impl!(i16)
|
||||
to_conversions_impl!(INT4OID, i32, write_be_i32_)
|
||||
to_option_impl!(i32)
|
||||
to_conversions_impl!(INT8OID, i64, write_be_i64_)
|
||||
to_option_impl!(i64)
|
||||
to_conversions_impl!(FLOAT4OID, f32, write_be_f32_)
|
||||
to_option_impl!(f32)
|
||||
to_conversions_impl!(FLOAT8OID, f64, write_be_f64_)
|
||||
to_option_impl!(f64)
|
||||
|
||||
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)
|
||||
@ -155,10 +214,6 @@ 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, _ty: Oid) -> (Format, Option<~[u8]>) {
|
||||
|
Loading…
Reference in New Issue
Block a user