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:
Steven Fackler 2013-08-31 16:11:42 -07:00
parent f56c519097
commit 557c61d6db
4 changed files with 178 additions and 62 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
*.so
*.dylib
*.dylib.dSYM
*.dummy
check-*

View File

@ -17,4 +17,4 @@ check-postgres: postgres.dummy src/test.rs
.PHONY: clean
clean:
rm *.dummy *.so check-*
git clean -dfx

View File

@ -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)

View File

@ -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]>) {