Composite type support

This commit is contained in:
Steven Fackler 2016-02-15 23:11:01 -08:00
parent 7599c4e146
commit d5f1bdba4a
3 changed files with 105 additions and 6 deletions

View File

@ -79,7 +79,7 @@ use message::{WriteMessage, ReadMessage};
use notification::{Notifications, Notification};
use rows::{Rows, LazyRows};
use stmt::{Statement, Column};
use types::{IsNull, Kind, Type, SessionInfo, Oid, Other, WrongType, ToSql, FromSql};
use types::{IsNull, Kind, Type, SessionInfo, Oid, Other, WrongType, ToSql, FromSql, Field};
use url::Url;
#[macro_use]
@ -97,7 +97,8 @@ pub mod stmt;
pub mod types;
pub mod notification;
const TYPEINFO_QUERY: &'static str = "t";
const TYPEINFO_QUERY: &'static str = "__typeinfo";
const TYPEINFO_ARRAY_QUERY: &'static str = "__typeinfo_array";
/// A type alias of the result returned by many methods.
pub type Result<T> = result::Result<T, Error>;
@ -463,9 +464,22 @@ impl InnerConnection {
#[cfg_attr(rustfmt, rustfmt_skip)]
fn setup_typeinfo_query(&mut self) -> result::Result<(), ConnectError> {
match self.raw_prepare(TYPEINFO_ARRAY_QUERY,
"SELECT attname, atttypid \
FROM pg_catalog.pg_attribute \
WHERE attrelid = $1 \
AND NOT attisdropped \
AND attnum > 0 \
ORDER BY attnum") {
Ok(..) => {}
Err(Error::Io(e)) => return Err(ConnectError::Io(e)),
Err(Error::Db(e)) => return Err(ConnectError::Db(e)),
Err(Error::Conversion(_)) => unreachable!(),
}
match self.raw_prepare(TYPEINFO_QUERY,
"SELECT t.typname, t.typtype, t.typelem, r.rngsubtype, \
t.typbasetype, n.nspname \
t.typbasetype, n.nspname, t.typrelid \
FROM pg_catalog.pg_type t \
LEFT OUTER JOIN pg_catalog.pg_range r ON \
r.rngtypid = t.oid \
@ -823,7 +837,7 @@ impl InnerConnection {
try!(self.read_rows(&mut rows));
let row = rows.pop_front().unwrap();
let (name, type_, elem_oid, rngsubtype, basetype, schema) = {
let (name, type_, elem_oid, rngsubtype, basetype, schema, relid) = {
let ctx = SessionInfo::new(self);
let name = try!(String::from_sql(&Type::Name,
&mut &**row[0].as_ref().unwrap(),
@ -844,7 +858,10 @@ impl InnerConnection {
let schema = try!(String::from_sql(&Type::Name,
&mut &**row[5].as_ref().unwrap(),
&ctx));
(name, type_, elem_oid, rngsubtype, basetype, schema)
let relid = try!(Oid::from_sql(&Type::Oid,
&mut &**row[6].as_ref().unwrap(),
&ctx));
(name, type_, elem_oid, rngsubtype, basetype, schema, relid)
};
let kind = if type_ == b'e' as i8 {
@ -855,6 +872,28 @@ impl InnerConnection {
Kind::Domain(try!(self.get_type(basetype)))
} else if elem_oid != 0 {
Kind::Array(try!(self.get_type(elem_oid)))
} else if relid != 0 {
try!(self.raw_execute(TYPEINFO_ARRAY_QUERY, "", 0, &[Type::Oid], &[&relid]));
let mut rows = VecDeque::new();
try!(self.read_rows(&mut rows));
let mut fields = vec![];
for row in rows {
let (name, type_) = {
let ctx = SessionInfo::new(self);
let name = try!(String::from_sql(&Type::Name,
&mut &**row[0].as_ref().unwrap(),
&ctx));
let type_ = try!(Oid::from_sql(&Type::Oid,
&mut &**row[1].as_ref().unwrap(),
&ctx));
(name, type_)
};
let type_ = try!(self.get_type(type_));
fields.push(Field::new(name, type_));
}
Kind::Composite(fields)
} else {
match rngsubtype {
Some(oid) => Kind::Range(try!(self.get_type(oid))),
@ -1549,3 +1588,7 @@ trait NotificationsNew<'conn> {
trait WrongTypeNew {
fn new(ty: Type) -> WrongType;
}
trait FieldNew {
fn new(name: String, type_: Type) -> Field;
}

View File

@ -8,7 +8,7 @@ use std::sync::Arc;
use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian};
pub use self::slice::Slice;
use {Result, SessionInfoNew, InnerConnection, OtherNew, WrongTypeNew};
use {Result, SessionInfoNew, InnerConnection, OtherNew, WrongTypeNew, FieldNew};
use error::Error;
use util;
@ -115,10 +115,40 @@ pub enum Kind {
Range(Type),
/// A domain type along with its underlying type.
Domain(Type),
/// A composite type along with information about its fields.
Composite(Vec<Field>),
#[doc(hidden)]
__PseudoPrivateForExtensibility,
}
/// Information about a field of a composite type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
name: String,
type_: Type,
}
impl Field {
/// Returns the name of the field.
pub fn name(&self) -> &str {
&self.name
}
/// Returns the type of the field.
pub fn type_(&self) -> &Type {
&self.type_
}
}
impl FieldNew for Field {
fn new(name: String, type_: Type) -> Field {
Field {
name: name,
type_: type_,
}
}
}
macro_rules! as_pat {
($p:pat) => ($p)
}

View File

@ -281,3 +281,29 @@ fn domain() {
let rows = conn.query("SELECT id FROM pg_temp.foo", &[]).unwrap();
assert_eq!(id, rows.get(0).get(0));
}
#[test]
fn composite() {
let conn = Connection::connect("postgres://postgres@localhost", SslMode::None).unwrap();
conn.batch_execute("CREATE TYPE pg_temp.inventory_item AS (
name TEXT,
supplier INTEGER,
price NUMERIC
)")
.unwrap();
let stmt = conn.prepare("SELECT $1::inventory_item").unwrap();
let type_ = &stmt.param_types()[0];
assert_eq!(type_.name(), "inventory_item");
match type_.kind() {
&Kind::Composite(ref fields) => {
assert_eq!(fields[0].name(), "name");
assert_eq!(fields[0].type_(), &Type::Text);
assert_eq!(fields[1].name(), "supplier");
assert_eq!(fields[1].type_(), &Type::Int4);
assert_eq!(fields[2].name(), "price");
assert_eq!(fields[2].type_(), &Type::Numeric);
}
t => panic!("bad type {:?}", t),
}
}