Support custom types
This commit is contained in:
parent
be2ca03fa9
commit
a237a471c9
@ -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"
|
||||
|
@ -1,3 +1,4 @@
|
||||
extern crate antidote;
|
||||
extern crate bytes;
|
||||
extern crate fallible_iterator;
|
||||
extern crate futures_cpupool;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
337
tokio-postgres/src/proto/typeinfo.rs
Normal file
337
tokio-postgres/src/proto/typeinfo.rs
Normal 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)
|
||||
}
|
||||
}
|
145
tokio-postgres/src/proto/typeinfo_composite.rs
Normal file
145
tokio-postgres/src/proto/typeinfo_composite.rs
Normal 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)
|
||||
}
|
||||
}
|
140
tokio-postgres/src/proto/typeinfo_enum.rs
Normal file
140
tokio-postgres/src/proto/typeinfo_enum.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user