rust-postgres/types/mod.rs
Steven Fackler ab2b04d861 More work on Range
Ranges are normalized at creation so Eq works now.
2013-10-29 19:16:45 -07:00

449 lines
12 KiB
Rust

//! Traits dealing with Postgres data types
extern mod extra;
use extra::time::Timespec;
use extra::json;
use extra::json::Json;
use extra::uuid::Uuid;
use std::rt::io::{Reader, Writer, Decorator};
use std::rt::io::mem::{MemWriter, BufReader};
use std::str;
use super::types::range::{RangeBound, Inclusive, Exclusive, Range};
pub mod range;
/// A Postgres OID
pub type Oid = i32;
// Values from pg_type.h
static BOOLOID: Oid = 16;
static BYTEAOID: Oid = 17;
static CHAROID: Oid = 18;
static INT8OID: Oid = 20;
static INT2OID: Oid = 21;
static INT4OID: Oid = 23;
static TEXTOID: Oid = 25;
static JSONOID: Oid = 114;
static FLOAT4OID: Oid = 700;
static FLOAT8OID: Oid = 701;
static BPCHAROID: Oid = 1042;
static VARCHAROID: Oid = 1043;
static TIMESTAMPOID: Oid = 1114;
static TIMESTAMPZOID: Oid = 1184;
static UUIDOID: Oid = 2950;
static INT4RANGEOID: Oid = 3904;
static INT8RANGEOID: Oid = 3926;
static USEC_PER_SEC: i64 = 1_000_000;
static NSEC_PER_USEC: i64 = 1_000;
// Number of seconds from 1970-01-01 to 2000-01-01
static TIME_SEC_CONVERSION: i64 = 946684800;
static RANGE_EMPTY: i8 = 0x18;
static RANGE_UPPER: i8 = 0x08;
static RANGE_LOWER: i8 = 0x12;
static RANGE_FULL: i8 = 0x02;
/// A Postgres type
#[deriving(Eq)]
pub enum PostgresType {
/// BOOL
PgBool,
/// BYTEA
PgByteA,
/// "char"
PgChar,
/// INT8/BIGINT
PgInt8,
/// INT2/SMALLINT
PgInt2,
/// INT4/INT
PgInt4,
/// TEXT
PgText,
/// JSON
PgJson,
/// FLOAT4/REAL
PgFloat4,
/// FLOAT8/DOUBLE PRECISION
PgFloat8,
/// TIMESTAMP
PgTimestamp,
/// TIMESTAMP WITH TIME ZONE
PgTimestampZ,
/// CHAR(n)/CHARACTER(n)
PgCharN,
/// VARCHAR/CHARACTER VARYING
PgVarchar,
/// UUID
PgUuid,
/// INT4RANGE
PgInt4Range,
/// INT8RANGE
PgInt8Range,
/// An unknown type along with its OID
PgUnknownType(Oid)
}
impl PostgresType {
/// Creates a PostgresType from a Postgres OID.
pub fn from_oid(oid: Oid) -> PostgresType {
match oid {
BOOLOID => PgBool,
BYTEAOID => PgByteA,
CHAROID => PgChar,
INT8OID => PgInt8,
INT2OID => PgInt2,
INT4OID => PgInt4,
TEXTOID => PgText,
JSONOID => PgJson,
FLOAT4OID => PgFloat4,
FLOAT8OID => PgFloat8,
TIMESTAMPOID => PgTimestamp,
TIMESTAMPZOID => PgTimestampZ,
BPCHAROID => PgCharN,
VARCHAROID => PgVarchar,
UUIDOID => PgUuid,
INT4RANGEOID => PgInt4Range,
INT8RANGEOID => PgInt8Range,
oid => PgUnknownType(oid)
}
}
/// Returns the wire format needed for the value of `self`.
pub fn result_format(&self) -> Format {
match *self {
PgUnknownType(*) => Text,
_ => Binary
}
}
}
/// The wire format of a Postgres value
pub enum Format {
/// A user-readable string format
Text = 0,
/// A machine-readable binary format
Binary = 1
}
macro_rules! check_types(
($($expected:pat)|+, $actual:ident) => (
match $actual {
$($expected)|+ => (),
actual => fail!("Invalid Postgres type {:?}", actual)
}
)
)
/// A trait for types that can be created from a Postgres value
pub trait FromSql {
/// Creates a new value of this type from a buffer of Postgres data.
///
/// If the value was `NULL`, the buffer will be `None`.
///
/// # Failure
///
/// Fails if this type can not be created from the provided Postgres type.
fn from_sql(ty: PostgresType, raw: &Option<~[u8]>) -> Self;
}
macro_rules! from_map_impl(
($($expected:pat)|+, $t:ty, $blk:expr) => (
impl FromSql for Option<$t> {
fn from_sql(ty: PostgresType, raw: &Option<~[u8]>) -> Option<$t> {
check_types!($($expected)|+, ty)
raw.as_ref().map($blk)
}
}
)
)
macro_rules! from_conversions_impl(
($expected:pat, $t:ty, $f:ident) => (
from_map_impl!($expected, $t, |buf| {
let mut reader = BufReader::new(buf.as_slice());
reader.$f()
})
)
)
macro_rules! from_option_impl(
($t:ty) => (
impl FromSql for $t {
fn from_sql(ty: PostgresType, raw: &Option<~[u8]>) -> $t {
// FIXME when you can specify Self types properly
let ret: Option<$t> = FromSql::from_sql(ty, raw);
ret.unwrap()
}
}
)
)
from_map_impl!(PgBool, bool, |buf| { buf[0] != 0 })
from_option_impl!(bool)
from_conversions_impl!(PgChar, i8, read_i8)
from_option_impl!(i8)
from_conversions_impl!(PgInt2, i16, read_be_i16)
from_option_impl!(i16)
from_conversions_impl!(PgInt4, i32, read_be_i32)
from_option_impl!(i32)
from_conversions_impl!(PgInt8, i64, read_be_i64)
from_option_impl!(i64)
from_conversions_impl!(PgFloat4, f32, read_be_f32)
from_option_impl!(f32)
from_conversions_impl!(PgFloat8, f64, read_be_f64)
from_option_impl!(f64)
from_map_impl!(PgVarchar | PgText | PgCharN, ~str, |buf| {
str::from_utf8(buf.as_slice())
})
from_option_impl!(~str)
impl FromSql for Option<~[u8]> {
fn from_sql(ty: PostgresType, raw: &Option<~[u8]>) -> Option<~[u8]> {
check_types!(PgByteA, ty)
raw.clone()
}
}
from_option_impl!(~[u8])
from_map_impl!(PgJson, Json, |buf| {
json::from_str(str::from_utf8_slice(buf.as_slice())).unwrap()
})
from_option_impl!(Json)
from_map_impl!(PgUuid, Uuid, |buf| {
Uuid::from_bytes(buf.as_slice()).unwrap()
})
from_option_impl!(Uuid)
from_map_impl!(PgTimestamp | PgTimestampZ, Timespec, |buf| {
let mut rdr = BufReader::new(buf.as_slice());
let t = rdr.read_be_i64();
let mut sec = t / USEC_PER_SEC + TIME_SEC_CONVERSION;
let mut usec = t % USEC_PER_SEC;
if usec < 0 {
sec -= 1;
usec = USEC_PER_SEC + usec;
}
Timespec::new(sec, (usec * NSEC_PER_USEC) as i32)
})
from_option_impl!(Timespec)
from_map_impl!(PgInt4Range, Range<i32>, |buf| {
let mut rdr = BufReader::new(buf.as_slice());
let t = rdr.read_i8();
match t {
RANGE_EMPTY => Range::new(None, None),
RANGE_UPPER => {
rdr.read_be_i32();
Range::new(None, Some(RangeBound::new(rdr.read_be_i32(), Exclusive)))
}
RANGE_LOWER => {
rdr.read_be_i32();
Range::new(Some(RangeBound::new(rdr.read_be_i32(), Inclusive)), None)
}
RANGE_FULL => {
rdr.read_be_i32();
let low = rdr.read_be_i32();
rdr.read_be_i32();
let high = rdr.read_be_i32();
Range::new(Some(RangeBound::new(low, Inclusive)),
Some(RangeBound::new(high, Exclusive)))
}
_ => unreachable!()
}
})
from_option_impl!(Range<i32>)
/// A trait for types that can be converted into Postgres values
pub trait ToSql {
/// Converts the value of `self` into a format appropriate for the Postgres
/// backend.
///
/// # Failure
///
/// Fails if this type cannot be converted into the specified Postgres
/// type.
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>);
}
macro_rules! to_option_impl(
($($oid:ident)|+, $t:ty) => (
impl ToSql for Option<$t> {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!($($oid)|+, ty)
match *self {
None => (Text, None),
Some(ref val) => val.to_sql(ty)
}
}
}
);
(self, $($oid:ident)|+, $t:ty) => (
impl<'self> ToSql for Option<$t> {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!($($oid)|+, ty)
match *self {
None => (Text, None),
Some(ref val) => val.to_sql(ty)
}
}
}
)
)
macro_rules! to_conversions_impl(
($($oid:ident)|+, $t:ty, $f:ident) => (
impl ToSql for $t {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!($($oid)|+, ty)
let mut writer = MemWriter::new();
writer.$f(*self);
(Binary, Some(writer.inner()))
}
}
)
)
impl ToSql for bool {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!(PgBool, ty)
(Binary, Some(~[*self as u8]))
}
}
to_option_impl!(PgBool, bool)
to_conversions_impl!(PgChar, i8, write_i8)
to_option_impl!(PgChar, i8)
to_conversions_impl!(PgInt2, i16, write_be_i16)
to_option_impl!(PgInt2, i16)
to_conversions_impl!(PgInt4, i32, write_be_i32)
to_option_impl!(PgInt4, i32)
to_conversions_impl!(PgInt8, i64, write_be_i64)
to_option_impl!(PgInt8, i64)
to_conversions_impl!(PgFloat4, f32, write_be_f32)
to_option_impl!(PgFloat4, f32)
to_conversions_impl!(PgFloat8, f64, write_be_f64)
to_option_impl!(PgFloat8, f64)
impl ToSql for ~str {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!(PgVarchar | PgText | PgCharN, ty)
(Text, Some(self.as_bytes().to_owned()))
}
}
impl<'self> ToSql for &'self str {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!(PgVarchar | PgText | PgCharN, ty)
(Text, Some(self.as_bytes().to_owned()))
}
}
to_option_impl!(PgVarchar | PgText | PgCharN, ~str)
to_option_impl!(self, PgVarchar | PgText | PgCharN, &'self str)
impl ToSql for ~[u8] {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!(PgByteA, ty)
(Binary, Some(self.to_owned()))
}
}
impl<'self> ToSql for &'self [u8] {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!(PgByteA, ty)
(Binary, Some(self.to_owned()))
}
}
to_option_impl!(PgByteA, ~[u8])
to_option_impl!(self, PgByteA, &'self [u8])
impl ToSql for Json {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!(PgJson, ty)
(Text, Some(self.to_str().into_bytes()))
}
}
to_option_impl!(PgJson, Json)
impl ToSql for Uuid {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!(PgUuid, ty)
(Binary, Some(self.to_bytes().to_owned()))
}
}
to_option_impl!(PgUuid, Uuid)
impl ToSql for Timespec {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!(PgTimestamp | PgTimestampZ, ty)
let t = (self.sec - TIME_SEC_CONVERSION) * USEC_PER_SEC
+ self.nsec as i64 / NSEC_PER_USEC;
let mut buf = MemWriter::new();
buf.write_be_i64(t);
(Binary, Some(buf.inner()))
}
}
to_option_impl!(PgTimestamp | PgTimestampZ, Timespec)
impl ToSql for Range<i32> {
fn to_sql(&self, ty: PostgresType) -> (Format, Option<~[u8]>) {
check_types!(PgInt4Range, ty)
let lower = do self.lower().as_ref().map |b| {
match b.type_ {
Inclusive => b.value,
Exclusive => b.value + 1
}
};
let upper = do self.upper().as_ref().map |b| {
match b.type_ {
Inclusive => b.value - 1,
Exclusive => b.value
}
};
let mut buf = MemWriter::new();
match (lower, upper) {
(None, None) => buf.write_i8(RANGE_EMPTY),
(Some(low), None) => {
buf.write_i8(RANGE_LOWER);
buf.write_be_i32(4);
buf.write_be_i32(low);
}
(None, Some(high)) => {
buf.write_i8(RANGE_UPPER);
buf.write_be_i32(4);
buf.write_be_i32(high);
}
(Some(low), Some(high)) => {
buf.write_i8(RANGE_FULL);
buf.write_be_i32(4);
buf.write_be_i32(low);
buf.write_be_i32(4);
buf.write_be_i32(high);
}
}
(Binary, Some(buf.inner()))
}
}
to_option_impl!(PgInt4Range, Range<i32>)