Support custom types

This commit is contained in:
Steven Fackler 2018-07-04 21:02:08 -07:00
parent be2ca03fa9
commit a237a471c9
9 changed files with 987 additions and 44 deletions

View File

@ -32,6 +32,7 @@ circle-ci = { repository = "sfackler/rust-postgres" }
"with-uuid-0.6" = ["postgres-shared/with-uuid-0.6"]
[dependencies]
antidote = "1.0"
bytes = "0.4"
fallible-iterator = "0.1.3"
futures = "0.1.7"

View File

@ -1,3 +1,4 @@
extern crate antidote;
extern crate bytes;
extern crate fallible_iterator;
extern crate futures_cpupool;

View File

@ -1,7 +1,10 @@
use antidote::Mutex;
use futures::sync::mpsc;
use postgres_protocol;
use postgres_protocol::message::backend::Message;
use postgres_protocol::message::frontend;
use std::collections::HashMap;
use std::sync::Arc;
use disconnected;
use error::{self, Error};
@ -10,7 +13,7 @@ use proto::execute::ExecuteFuture;
use proto::prepare::PrepareFuture;
use proto::query::QueryStream;
use proto::statement::Statement;
use types::{IsNull, ToSql, Type};
use types::{IsNull, Oid, ToSql, Type};
pub struct PendingRequest {
sender: mpsc::UnboundedSender<Request>,
@ -28,13 +31,30 @@ impl PendingRequest {
}
}
pub struct State {
pub types: HashMap<Oid, Type>,
pub typeinfo_query: Option<Statement>,
pub typeinfo_enum_query: Option<Statement>,
pub typeinfo_composite_query: Option<Statement>,
}
#[derive(Clone)]
pub struct Client {
pub state: Arc<Mutex<State>>,
sender: mpsc::UnboundedSender<Request>,
}
impl Client {
pub fn new(sender: mpsc::UnboundedSender<Request>) -> Client {
Client { sender }
Client {
state: Arc::new(Mutex::new(State {
types: HashMap::new(),
typeinfo_query: None,
typeinfo_enum_query: None,
typeinfo_composite_query: None,
})),
sender,
}
}
pub fn prepare(&mut self, name: String, query: &str, param_types: &[Type]) -> PrepareFuture {
@ -45,7 +65,7 @@ impl Client {
Ok(())
});
PrepareFuture::new(pending, self.sender.clone(), name)
PrepareFuture::new(pending, self.sender.clone(), name, self.clone())
}
pub fn execute(&mut self, statement: &Statement, params: &[&ToSql]) -> ExecuteFuture {

View File

@ -20,6 +20,9 @@ mod query;
mod row;
mod socket;
mod statement;
mod typeinfo;
mod typeinfo_composite;
mod typeinfo_enum;
pub use proto::cancel::CancelFuture;
pub use proto::client::Client;

View File

@ -1,14 +1,17 @@
use fallible_iterator::FallibleIterator;
use futures::sync::mpsc;
use futures::{Poll, Stream};
use postgres_protocol::message::backend::{Message, ParameterDescriptionBody, RowDescriptionBody};
use futures::{Future, Poll, Stream};
use postgres_protocol::message::backend::Message;
use state_machine_future::RentToOwn;
use std::mem;
use std::vec;
use error::{self, Error};
use proto::client::PendingRequest;
use proto::client::{Client, PendingRequest};
use proto::connection::Request;
use proto::statement::Statement;
use types::Type;
use proto::typeinfo::TypeinfoFuture;
use types::{Oid, Type};
use Column;
use {bad_response, disconnected};
@ -19,33 +22,57 @@ pub enum Prepare {
request: PendingRequest,
sender: mpsc::UnboundedSender<Request>,
name: String,
client: Client,
},
#[state_machine_future(transitions(ReadParameterDescription))]
ReadParseComplete {
sender: mpsc::UnboundedSender<Request>,
receiver: mpsc::Receiver<Message>,
name: String,
client: Client,
},
#[state_machine_future(transitions(ReadRowDescription))]
ReadParameterDescription {
sender: mpsc::UnboundedSender<Request>,
receiver: mpsc::Receiver<Message>,
name: String,
client: Client,
},
#[state_machine_future(transitions(ReadReadyForQuery))]
ReadRowDescription {
sender: mpsc::UnboundedSender<Request>,
receiver: mpsc::Receiver<Message>,
name: String,
parameters: ParameterDescriptionBody,
parameters: Vec<Oid>,
client: Client,
},
#[state_machine_future(transitions(Finished))]
#[state_machine_future(transitions(GetParameterTypes, GetColumnTypes, Finished))]
ReadReadyForQuery {
sender: mpsc::UnboundedSender<Request>,
receiver: mpsc::Receiver<Message>,
name: String,
parameters: ParameterDescriptionBody,
columns: Option<RowDescriptionBody>,
parameters: Vec<Oid>,
columns: Vec<(String, Oid)>,
client: Client,
},
#[state_machine_future(transitions(GetColumnTypes, Finished))]
GetParameterTypes {
future: TypeinfoFuture,
remaining_parameters: vec::IntoIter<Oid>,
sender: mpsc::UnboundedSender<Request>,
name: String,
parameters: Vec<Type>,
columns: Vec<(String, Oid)>,
},
#[state_machine_future(transitions(Finished))]
GetColumnTypes {
future: TypeinfoFuture,
cur_column_name: String,
remaining_columns: vec::IntoIter<(String, Oid)>,
sender: mpsc::UnboundedSender<Request>,
name: String,
parameters: Vec<Type>,
columns: Vec<Column>,
},
#[state_machine_future(ready)]
Finished(Statement),
@ -62,6 +89,7 @@ impl PollPrepare for Prepare {
sender: state.sender,
receiver,
name: state.name,
client: state.client,
})
}
@ -76,6 +104,7 @@ impl PollPrepare for Prepare {
sender: state.sender,
receiver: state.receiver,
name: state.name,
client: state.client,
}),
Some(Message::ErrorResponse(body)) => Err(error::__db(body)),
Some(_) => Err(bad_response()),
@ -94,7 +123,8 @@ impl PollPrepare for Prepare {
sender: state.sender,
receiver: state.receiver,
name: state.name,
parameters: body,
parameters: body.parameters().collect()?,
client: state.client,
}),
Some(_) => Err(bad_response()),
None => Err(disconnected()),
@ -107,9 +137,12 @@ impl PollPrepare for Prepare {
let message = try_receive!(state.receiver.poll());
let state = state.take();
let body = match message {
Some(Message::RowDescription(body)) => Some(body),
Some(Message::NoData) => None,
let columns = match message {
Some(Message::RowDescription(body)) => body
.fields()
.map(|f| (f.name().to_string(), f.type_oid()))
.collect()?,
Some(Message::NoData) => vec![],
Some(_) => return Err(bad_response()),
None => return Err(disconnected()),
};
@ -119,7 +152,8 @@ impl PollPrepare for Prepare {
receiver: state.receiver,
name: state.name,
parameters: state.parameters,
columns: body,
columns,
client: state.client,
})
}
@ -130,33 +164,103 @@ impl PollPrepare for Prepare {
let state = state.take();
match message {
Some(Message::ReadyForQuery(_)) => {
// FIXME handle custom types
let parameters = state
.parameters
.parameters()
.map(|oid| Type::from_oid(oid).unwrap())
.collect()?;
let columns = match state.columns {
Some(body) => body
.fields()
.map(|f| {
Column::new(f.name().to_string(), Type::from_oid(f.type_oid()).unwrap())
})
.collect()?,
None => vec![],
};
transition!(Finished(Statement::new(
state.sender,
state.name,
parameters,
columns
)))
}
Some(_) => Err(bad_response()),
None => Err(disconnected()),
Some(Message::ReadyForQuery(_)) => {}
Some(_) => return Err(bad_response()),
None => return Err(disconnected()),
}
let mut parameters = state.parameters.into_iter();
if let Some(oid) = parameters.next() {
transition!(GetParameterTypes {
future: TypeinfoFuture::new(oid, state.client),
remaining_parameters: parameters,
sender: state.sender,
name: state.name,
parameters: vec![],
columns: state.columns,
});
}
let mut columns = state.columns.into_iter();
if let Some((name, oid)) = columns.next() {
transition!(GetColumnTypes {
future: TypeinfoFuture::new(oid, state.client),
cur_column_name: name,
remaining_columns: columns,
sender: state.sender,
name: state.name,
parameters: vec![],
columns: vec![],
});
}
transition!(Finished(Statement::new(
state.sender,
state.name,
vec![],
vec![]
)))
}
fn poll_get_parameter_types<'a>(
state: &'a mut RentToOwn<'a, GetParameterTypes>,
) -> Poll<AfterGetParameterTypes, Error> {
let client = loop {
let (ty, client) = try_ready!(state.future.poll());
state.parameters.push(ty);
match state.remaining_parameters.next() {
Some(oid) => state.future = TypeinfoFuture::new(oid, client),
None => break client,
}
};
let state = state.take();
let mut columns = state.columns.into_iter();
if let Some((name, oid)) = columns.next() {
transition!(GetColumnTypes {
future: TypeinfoFuture::new(oid, client),
cur_column_name: name,
remaining_columns: columns,
sender: state.sender,
name: state.name,
parameters: state.parameters,
columns: vec![],
})
}
transition!(Finished(Statement::new(
state.sender,
state.name,
state.parameters,
vec![],
)))
}
fn poll_get_column_types<'a>(
state: &'a mut RentToOwn<'a, GetColumnTypes>,
) -> Poll<AfterGetColumnTypes, Error> {
loop {
let (ty, client) = try_ready!(state.future.poll());
let name = mem::replace(&mut state.cur_column_name, String::new());
state.columns.push(Column::new(name, ty));
match state.remaining_columns.next() {
Some((name, oid)) => {
state.cur_column_name = name;
state.future = TypeinfoFuture::new(oid, client);
}
None => break,
}
}
let state = state.take();
transition!(Finished(Statement::new(
state.sender,
state.name,
state.parameters,
state.columns,
)))
}
}
@ -165,7 +269,8 @@ impl PrepareFuture {
request: PendingRequest,
sender: mpsc::UnboundedSender<Request>,
name: String,
client: Client,
) -> PrepareFuture {
Prepare::start(request, sender, name)
Prepare::start(request, sender, name, client)
}
}

View File

@ -0,0 +1,337 @@
use futures::stream::{self, Stream};
use futures::{Async, Future, Poll};
use state_machine_future::RentToOwn;
use bad_response;
use error::{Error, SqlState};
use proto::client::Client;
use proto::prepare::PrepareFuture;
use proto::query::QueryStream;
use proto::typeinfo_composite::TypeinfoCompositeFuture;
use proto::typeinfo_enum::TypeinfoEnumFuture;
use types::{Kind, Oid, Type};
const TYPEINFO_NAME: &'static str = "_rust_typeinfo";
const TYPEINFO_QUERY: &'static str = "
SELECT t.typname, t.typtype, t.typelem, r.rngsubtype, 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
INNER JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid
WHERE t.oid = $1
";
// Range types weren't added until Postgres 9.2, so pg_range may not exist
const TYPEINFO_FALLBACK_QUERY: &'static str = "
SELECT t.typname, t.typtype, t.typelem, NULL::OID, t.typbasetype, n.nspname, t.typrelid
FROM pg_catalog.pg_type t
INNER JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid
WHERE t.oid = $1
";
#[derive(StateMachineFuture)]
pub enum Typeinfo {
#[state_machine_future(start, transitions(PreparingTypeinfo, QueryingTypeinfo, Finished))]
Start { oid: Oid, client: Client },
#[state_machine_future(transitions(PreparingTypeinfoFallback, QueryingTypeinfo))]
PreparingTypeinfo {
future: Box<PrepareFuture>,
oid: Oid,
client: Client,
},
#[state_machine_future(transitions(QueryingTypeinfo))]
PreparingTypeinfoFallback {
future: Box<PrepareFuture>,
oid: Oid,
client: Client,
},
#[state_machine_future(
transitions(
CachingType, QueryingEnumVariants, QueryingDomainBasetype, QueryingArrayElem,
QueryingCompositeFields, QueryingRangeSubtype
)
)]
QueryingTypeinfo {
future: stream::Collect<QueryStream>,
oid: Oid,
client: Client,
},
#[state_machine_future(transitions(CachingType))]
QueryingEnumVariants {
future: TypeinfoEnumFuture,
name: String,
oid: Oid,
schema: String,
},
#[state_machine_future(transitions(CachingType))]
QueryingDomainBasetype {
future: Box<TypeinfoFuture>,
name: String,
oid: Oid,
schema: String,
},
#[state_machine_future(transitions(CachingType))]
QueryingArrayElem {
future: Box<TypeinfoFuture>,
name: String,
oid: Oid,
schema: String,
},
#[state_machine_future(transitions(CachingType))]
QueryingCompositeFields {
future: TypeinfoCompositeFuture,
name: String,
oid: Oid,
schema: String,
},
#[state_machine_future(transitions(CachingType))]
QueryingRangeSubtype {
future: Box<TypeinfoFuture>,
name: String,
oid: Oid,
schema: String,
},
#[state_machine_future(transitions(Finished))]
CachingType { ty: Type, oid: Oid, client: Client },
#[state_machine_future(ready)]
Finished((Type, Client)),
#[state_machine_future(error)]
Failed(Error),
}
impl PollTypeinfo for Typeinfo {
fn poll_start<'a>(state: &'a mut RentToOwn<'a, Start>) -> Poll<AfterStart, Error> {
let mut state = state.take();
if let Some(ty) = Type::from_oid(state.oid) {
transition!(Finished((ty, state.client)));
}
let ty = state.client.state.lock().types.get(&state.oid).cloned();
if let Some(ty) = ty {
transition!(Finished((ty, state.client)));
}
let statement = state.client.state.lock().typeinfo_query.clone();
match statement {
Some(statement) => transition!(QueryingTypeinfo {
future: state.client.query(&statement, &[&state.oid]).collect(),
oid: state.oid,
client: state.client,
}),
None => transition!(PreparingTypeinfo {
future: Box::new(state.client.prepare(
TYPEINFO_NAME.to_string(),
TYPEINFO_QUERY,
&[]
)),
oid: state.oid,
client: state.client,
}),
}
}
fn poll_preparing_typeinfo<'a>(
state: &'a mut RentToOwn<'a, PreparingTypeinfo>,
) -> Poll<AfterPreparingTypeinfo, Error> {
let statement = match state.future.poll() {
Ok(Async::Ready(statement)) => statement,
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(ref e) if e.code() == Some(&SqlState::UNDEFINED_TABLE) => {
let mut state = state.take();
transition!(PreparingTypeinfoFallback {
future: Box::new(state.client.prepare(
TYPEINFO_NAME.to_string(),
TYPEINFO_FALLBACK_QUERY,
&[]
)),
oid: state.oid,
client: state.client,
})
}
Err(e) => return Err(e),
};
let mut state = state.take();
let future = state.client.query(&statement, &[&state.oid]).collect();
state.client.state.lock().typeinfo_query = Some(statement);
transition!(QueryingTypeinfo {
future,
oid: state.oid,
client: state.client
})
}
fn poll_preparing_typeinfo_fallback<'a>(
state: &'a mut RentToOwn<'a, PreparingTypeinfoFallback>,
) -> Poll<AfterPreparingTypeinfoFallback, Error> {
let statement = try_ready!(state.future.poll());
let mut state = state.take();
let future = state.client.query(&statement, &[&state.oid]).collect();
state.client.state.lock().typeinfo_query = Some(statement);
transition!(QueryingTypeinfo {
future,
oid: state.oid,
client: state.client
})
}
fn poll_querying_typeinfo<'a>(
state: &'a mut RentToOwn<'a, QueryingTypeinfo>,
) -> Poll<AfterQueryingTypeinfo, Error> {
let rows = try_ready!(state.future.poll());
let state = state.take();
let row = match rows.get(0) {
Some(row) => row,
None => return Err(bad_response()),
};
let name = row.try_get::<_, String>(0)?.ok_or_else(bad_response)?;
let type_ = row.try_get::<_, i8>(1)?.ok_or_else(bad_response)?;
let elem_oid = row.try_get::<_, Oid>(2)?.ok_or_else(bad_response)?;
let rngsubtype = row.try_get::<_, Option<Oid>>(3)?.ok_or_else(bad_response)?;
let basetype = row.try_get::<_, Oid>(4)?.ok_or_else(bad_response)?;
let schema = row.try_get::<_, String>(5)?.ok_or_else(bad_response)?;
let relid = row.try_get::<_, Oid>(6)?.ok_or_else(bad_response)?;
let kind = if type_ == b'e' as i8 {
transition!(QueryingEnumVariants {
future: TypeinfoEnumFuture::new(state.oid, state.client),
name,
oid: state.oid,
schema,
})
} else if type_ == b'p' as i8 {
Kind::Pseudo
} else if basetype != 0 {
transition!(QueryingDomainBasetype {
future: Box::new(TypeinfoFuture::new(basetype, state.client)),
name,
oid: state.oid,
schema,
})
} else if elem_oid != 0 {
transition!(QueryingArrayElem {
future: Box::new(TypeinfoFuture::new(elem_oid, state.client)),
name,
oid: state.oid,
schema,
})
} else if relid != 0 {
transition!(QueryingCompositeFields {
future: TypeinfoCompositeFuture::new(relid, state.client),
name,
oid: state.oid,
schema,
})
} else if let Some(rngsubtype) = rngsubtype {
transition!(QueryingRangeSubtype {
future: Box::new(TypeinfoFuture::new(rngsubtype, state.client)),
name,
oid: state.oid,
schema,
})
} else {
Kind::Simple
};
let ty = Type::_new(name.to_string(), state.oid, kind, schema.to_string());
transition!(CachingType {
ty,
oid: state.oid,
client: state.client,
})
}
fn poll_querying_enum_variants<'a>(
state: &'a mut RentToOwn<'a, QueryingEnumVariants>,
) -> Poll<AfterQueryingEnumVariants, Error> {
let (variants, client) = try_ready!(state.future.poll());
let state = state.take();
let ty = Type::_new(state.name, state.oid, Kind::Enum(variants), state.schema);
transition!(CachingType {
ty,
oid: state.oid,
client,
})
}
fn poll_querying_domain_basetype<'a>(
state: &'a mut RentToOwn<'a, QueryingDomainBasetype>,
) -> Poll<AfterQueryingDomainBasetype, Error> {
let (basetype, client) = try_ready!(state.future.poll());
let state = state.take();
let ty = Type::_new(state.name, state.oid, Kind::Domain(basetype), state.schema);
transition!(CachingType {
ty,
oid: state.oid,
client,
})
}
fn poll_querying_array_elem<'a>(
state: &'a mut RentToOwn<'a, QueryingArrayElem>,
) -> Poll<AfterQueryingArrayElem, Error> {
let (elem, client) = try_ready!(state.future.poll());
let state = state.take();
let ty = Type::_new(state.name, state.oid, Kind::Array(elem), state.schema);
transition!(CachingType {
ty,
oid: state.oid,
client,
})
}
fn poll_querying_composite_fields<'a>(
state: &'a mut RentToOwn<'a, QueryingCompositeFields>,
) -> Poll<AfterQueryingCompositeFields, Error> {
let (fields, client) = try_ready!(state.future.poll());
let state = state.take();
let ty = Type::_new(state.name, state.oid, Kind::Composite(fields), state.schema);
transition!(CachingType {
ty,
oid: state.oid,
client,
})
}
fn poll_querying_range_subtype<'a>(
state: &'a mut RentToOwn<'a, QueryingRangeSubtype>,
) -> Poll<AfterQueryingRangeSubtype, Error> {
let (subtype, client) = try_ready!(state.future.poll());
let state = state.take();
let ty = Type::_new(state.name, state.oid, Kind::Range(subtype), state.schema);
transition!(CachingType {
ty,
oid: state.oid,
client,
})
}
fn poll_caching_type<'a>(
state: &'a mut RentToOwn<'a, CachingType>,
) -> Poll<AfterCachingType, Error> {
let state = state.take();
state
.client
.state
.lock()
.types
.insert(state.oid, state.ty.clone());
transition!(Finished((state.ty, state.client)))
}
}
impl TypeinfoFuture {
pub fn new(oid: Oid, client: Client) -> TypeinfoFuture {
Typeinfo::start(oid, client)
}
}

View File

@ -0,0 +1,145 @@
use futures::stream::{self, Stream};
use futures::{Future, Poll};
use state_machine_future::RentToOwn;
use std::mem;
use std::vec;
use bad_response;
use error::Error;
use proto::client::Client;
use proto::prepare::PrepareFuture;
use proto::query::QueryStream;
use proto::typeinfo::TypeinfoFuture;
use types::{Field, Oid};
const TYPEINFO_COMPOSITE_NAME: &'static str = "_rust_typeinfo_composite";
const TYPEINFO_COMPOSITE_QUERY: &'static str = "
SELECT attname, atttypid
FROM pg_catalog.pg_attribute
WHERE attrelid = $1
AND NOT attisdropped
AND attnum > 0
ORDER BY attnum
";
#[derive(StateMachineFuture)]
pub enum TypeinfoComposite {
#[state_machine_future(
start, transitions(PreparingTypeinfoComposite, QueryingCompositeFields)
)]
Start { oid: Oid, client: Client },
#[state_machine_future(transitions(QueryingCompositeFields))]
PreparingTypeinfoComposite {
future: Box<PrepareFuture>,
oid: Oid,
client: Client,
},
#[state_machine_future(transitions(QueryingCompositeFieldTypes, Finished))]
QueryingCompositeFields {
future: stream::Collect<QueryStream>,
client: Client,
},
#[state_machine_future(transitions(Finished))]
QueryingCompositeFieldTypes {
future: Box<TypeinfoFuture>,
cur_field_name: String,
remaining_fields: vec::IntoIter<(String, Oid)>,
fields: Vec<Field>,
},
#[state_machine_future(ready)]
Finished((Vec<Field>, Client)),
#[state_machine_future(error)]
Failed(Error),
}
impl PollTypeinfoComposite for TypeinfoComposite {
fn poll_start<'a>(state: &'a mut RentToOwn<'a, Start>) -> Poll<AfterStart, Error> {
let mut state = state.take();
let statement = state.client.state.lock().typeinfo_composite_query.clone();
match statement {
Some(statement) => transition!(QueryingCompositeFields {
future: state.client.query(&statement, &[&state.oid]).collect(),
client: state.client,
}),
None => transition!(PreparingTypeinfoComposite {
future: Box::new(state.client.prepare(
TYPEINFO_COMPOSITE_NAME.to_string(),
TYPEINFO_COMPOSITE_QUERY,
&[]
)),
oid: state.oid,
client: state.client,
}),
}
}
fn poll_preparing_typeinfo_composite<'a>(
state: &'a mut RentToOwn<'a, PreparingTypeinfoComposite>,
) -> Poll<AfterPreparingTypeinfoComposite, Error> {
let statement = try_ready!(state.future.poll());
let mut state = state.take();
state.client.state.lock().typeinfo_composite_query = Some(statement.clone());
transition!(QueryingCompositeFields {
future: state.client.query(&statement, &[&state.oid]).collect(),
client: state.client,
})
}
fn poll_querying_composite_fields<'a>(
state: &'a mut RentToOwn<'a, QueryingCompositeFields>,
) -> Poll<AfterQueryingCompositeFields, Error> {
let rows = try_ready!(state.future.poll());
let state = state.take();
let fields = rows
.iter()
.map(|row| {
let name = row.try_get(0)?.ok_or_else(bad_response)?;
let oid = row.try_get(1)?.ok_or_else(bad_response)?;
Ok((name, oid))
})
.collect::<Result<Vec<(String, Oid)>, Error>>()?;
let mut remaining_fields = fields.into_iter();
match remaining_fields.next() {
Some((cur_field_name, oid)) => transition!(QueryingCompositeFieldTypes {
future: Box::new(TypeinfoFuture::new(oid, state.client)),
cur_field_name,
fields: vec![],
remaining_fields,
}),
None => transition!(Finished((vec![], state.client))),
}
}
fn poll_querying_composite_field_types<'a>(
state: &'a mut RentToOwn<'a, QueryingCompositeFieldTypes>,
) -> Poll<AfterQueryingCompositeFieldTypes, Error> {
loop {
let (ty, client) = try_ready!(state.future.poll());
let name = mem::replace(&mut state.cur_field_name, String::new());
state.fields.push(Field::new(name, ty));
match state.remaining_fields.next() {
Some((cur_field_name, oid)) => {
state.cur_field_name = cur_field_name;
state.future = Box::new(TypeinfoFuture::new(oid, client));
}
None => {
let state = state.take();
transition!(Finished((state.fields, client)));
}
}
}
}
}
impl TypeinfoCompositeFuture {
pub fn new(oid: Oid, client: Client) -> TypeinfoCompositeFuture {
TypeinfoComposite::start(oid, client)
}
}

View File

@ -0,0 +1,140 @@
use futures::stream::{self, Stream};
use futures::{Async, Future, Poll};
use state_machine_future::RentToOwn;
use bad_response;
use error::{Error, SqlState};
use proto::client::Client;
use proto::prepare::PrepareFuture;
use proto::query::QueryStream;
use types::Oid;
const TYPEINFO_ENUM_NAME: &'static str = "_rust_typeinfo_enum";
const TYPEINFO_ENUM_QUERY: &'static str = "
SELECT enumlabel
FROM pg_catalog.pg_enum
WHERE enumtypid = $1
ORDER BY enumsortorder
";
// Postgres 9.0 didn't have enumsortorder
const TYPEINFO_ENUM_FALLBACK_QUERY: &'static str = "
SELECT enumlabel
FROM pg_catalog.pg_enum
WHERE enumtypid = $1
ORDER BY oid
";
#[derive(StateMachineFuture)]
pub enum TypeinfoEnum {
#[state_machine_future(start, transitions(PreparingTypeinfoEnum, QueryingEnumVariants))]
Start { oid: Oid, client: Client },
#[state_machine_future(transitions(PreparingTypeinfoEnumFallback, QueryingEnumVariants))]
PreparingTypeinfoEnum {
future: Box<PrepareFuture>,
oid: Oid,
client: Client,
},
#[state_machine_future(transitions(QueryingEnumVariants))]
PreparingTypeinfoEnumFallback {
future: Box<PrepareFuture>,
oid: Oid,
client: Client,
},
#[state_machine_future(transitions(Finished))]
QueryingEnumVariants {
future: stream::Collect<QueryStream>,
client: Client,
},
#[state_machine_future(ready)]
Finished((Vec<String>, Client)),
#[state_machine_future(error)]
Failed(Error),
}
impl PollTypeinfoEnum for TypeinfoEnum {
fn poll_start<'a>(state: &'a mut RentToOwn<'a, Start>) -> Poll<AfterStart, Error> {
let mut state = state.take();
let statement = state.client.state.lock().typeinfo_enum_query.clone();
match statement {
Some(statement) => transition!(QueryingEnumVariants {
future: state.client.query(&statement, &[&state.oid]).collect(),
client: state.client,
}),
None => transition!(PreparingTypeinfoEnum {
future: Box::new(state.client.prepare(
TYPEINFO_ENUM_NAME.to_string(),
TYPEINFO_ENUM_QUERY,
&[]
)),
oid: state.oid,
client: state.client,
}),
}
}
fn poll_preparing_typeinfo_enum<'a>(
state: &'a mut RentToOwn<'a, PreparingTypeinfoEnum>,
) -> Poll<AfterPreparingTypeinfoEnum, Error> {
let statement = match state.future.poll() {
Ok(Async::Ready(statement)) => statement,
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(ref e) if e.code() == Some(&SqlState::UNDEFINED_COLUMN) => {
let mut state = state.take();
transition!(PreparingTypeinfoEnumFallback {
future: Box::new(state.client.prepare(
TYPEINFO_ENUM_NAME.to_string(),
TYPEINFO_ENUM_FALLBACK_QUERY,
&[]
)),
oid: state.oid,
client: state.client,
})
}
Err(e) => return Err(e),
};
let mut state = state.take();
state.client.state.lock().typeinfo_enum_query = Some(statement.clone());
transition!(QueryingEnumVariants {
future: state.client.query(&statement, &[&state.oid]).collect(),
client: state.client,
})
}
fn poll_preparing_typeinfo_enum_fallback<'a>(
state: &'a mut RentToOwn<'a, PreparingTypeinfoEnumFallback>,
) -> Poll<AfterPreparingTypeinfoEnumFallback, Error> {
let statement = try_ready!(state.future.poll());
let mut state = state.take();
state.client.state.lock().typeinfo_enum_query = Some(statement.clone());
transition!(QueryingEnumVariants {
future: state.client.query(&statement, &[&state.oid]).collect(),
client: state.client,
})
}
fn poll_querying_enum_variants<'a>(
state: &'a mut RentToOwn<'a, QueryingEnumVariants>,
) -> Poll<AfterQueryingEnumVariants, Error> {
let rows = try_ready!(state.future.poll());
let state = state.take();
let variants = rows
.iter()
.map(|row| row.try_get(0)?.ok_or_else(bad_response))
.collect::<Result<Vec<_>, _>>()?;
transition!(Finished((variants, state.client)))
}
}
impl TypeinfoEnumFuture {
pub fn new(oid: Oid, client: Client) -> TypeinfoEnumFuture {
TypeinfoEnum::start(oid, client)
}
}

View File

@ -8,7 +8,7 @@ use tokio::prelude::*;
use tokio::runtime::current_thread::Runtime;
use tokio::timer::Delay;
use tokio_postgres::error::SqlState;
use tokio_postgres::types::Type;
use tokio_postgres::types::{Kind, Type};
use tokio_postgres::TlsMode;
fn smoke_test(url: &str) {
@ -256,3 +256,194 @@ fn cancel_query() {
let ((), ()) = runtime.block_on(sleep.join(cancel)).unwrap();
}
#[test]
fn custom_enum() {
let _ = env_logger::try_init();
let mut runtime = Runtime::new().unwrap();
let handshake = tokio_postgres::connect(
"postgres://postgres@localhost:5433".parse().unwrap(),
TlsMode::None,
);
let (mut client, connection) = runtime.block_on(handshake).unwrap();
let connection = connection.map_err(|e| panic!("{}", e));
runtime.handle().spawn(connection).unwrap();
let create_type = client.prepare(
"CREATE TYPE pg_temp.mood AS ENUM (
'sad',
'ok',
'happy'
)",
);
let create_type = runtime.block_on(create_type).unwrap();
let create_type = client.execute(&create_type, &[]);
runtime.block_on(create_type).unwrap();
let select = client.prepare("SELECT $1::mood");
let select = runtime.block_on(select).unwrap();
let ty = &select.params()[0];
assert_eq!("mood", ty.name());
assert_eq!(
&Kind::Enum(vec![
"sad".to_string(),
"ok".to_string(),
"happy".to_string(),
]),
ty.kind()
);
}
#[test]
fn custom_domain() {
let _ = env_logger::try_init();
let mut runtime = Runtime::new().unwrap();
let handshake = tokio_postgres::connect(
"postgres://postgres@localhost:5433".parse().unwrap(),
TlsMode::None,
);
let (mut client, connection) = runtime.block_on(handshake).unwrap();
let connection = connection.map_err(|e| panic!("{}", e));
runtime.handle().spawn(connection).unwrap();
let create_type =
client.prepare("CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16)");
let create_type = runtime.block_on(create_type).unwrap();
let create_type = client.execute(&create_type, &[]);
runtime.block_on(create_type).unwrap();
let select = client.prepare("SELECT $1::session_id");
let select = runtime.block_on(select).unwrap();
let ty = &select.params()[0];
assert_eq!("session_id", ty.name());
assert_eq!(&Kind::Domain(Type::BYTEA), ty.kind());
}
#[test]
fn custom_array() {
let _ = env_logger::try_init();
let mut runtime = Runtime::new().unwrap();
let handshake = tokio_postgres::connect(
"postgres://postgres@localhost:5433".parse().unwrap(),
TlsMode::None,
);
let (mut client, connection) = runtime.block_on(handshake).unwrap();
let connection = connection.map_err(|e| panic!("{}", e));
runtime.handle().spawn(connection).unwrap();
let select = client.prepare("SELECT $1::HSTORE[]");
let select = runtime.block_on(select).unwrap();
let ty = &select.params()[0];
assert_eq!("_hstore", ty.name());
match *ty.kind() {
Kind::Array(ref ty) => {
assert_eq!("hstore", ty.name());
assert_eq!(&Kind::Simple, ty.kind());
}
_ => panic!("unexpected kind"),
}
}
#[test]
fn custom_composite() {
let _ = env_logger::try_init();
let mut runtime = Runtime::new().unwrap();
let handshake = tokio_postgres::connect(
"postgres://postgres@localhost:5433".parse().unwrap(),
TlsMode::None,
);
let (mut client, connection) = runtime.block_on(handshake).unwrap();
let connection = connection.map_err(|e| panic!("{}", e));
runtime.handle().spawn(connection).unwrap();
let create_type = client.prepare(
"CREATE TYPE pg_temp.inventory_item AS (
name TEXT,
supplier INTEGER,
price NUMERIC
)",
);
let create_type = runtime.block_on(create_type).unwrap();
let create_type = client.execute(&create_type, &[]);
runtime.block_on(create_type).unwrap();
let select = client.prepare("SELECT $1::inventory_item");
let select = runtime.block_on(select).unwrap();
let ty = &select.params()[0];
assert_eq!(ty.name(), "inventory_item");
match *ty.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);
}
ref t => panic!("bad type {:?}", t),
}
}
#[test]
fn custom_range() {
let _ = env_logger::try_init();
let mut runtime = Runtime::new().unwrap();
let handshake = tokio_postgres::connect(
"postgres://postgres@localhost:5433".parse().unwrap(),
TlsMode::None,
);
let (mut client, connection) = runtime.block_on(handshake).unwrap();
let connection = connection.map_err(|e| panic!("{}", e));
runtime.handle().spawn(connection).unwrap();
let create_type = client.prepare(
"CREATE TYPE pg_temp.floatrange AS RANGE (
subtype = float8,
subtype_diff = float8mi
)",
);
let create_type = runtime.block_on(create_type).unwrap();
let create_type = client.execute(&create_type, &[]);
runtime.block_on(create_type).unwrap();
let select = client.prepare("SELECT $1::floatrange");
let select = runtime.block_on(select).unwrap();
let ty = &select.params()[0];
assert_eq!("floatrange", ty.name());
assert_eq!(&Kind::Range(Type::FLOAT8), ty.kind());
}
#[test]
fn custom_simple() {
let _ = env_logger::try_init();
let mut runtime = Runtime::new().unwrap();
let handshake = tokio_postgres::connect(
"postgres://postgres@localhost:5433".parse().unwrap(),
TlsMode::None,
);
let (mut client, connection) = runtime.block_on(handshake).unwrap();
let connection = connection.map_err(|e| panic!("{}", e));
runtime.handle().spawn(connection).unwrap();
let select = client.prepare("SELECT $1::HSTORE");
let select = runtime.block_on(select).unwrap();
let ty = &select.params()[0];
assert_eq!("hstore", ty.name());
assert_eq!(&Kind::Simple, ty.kind());
}