diff --git a/.travis.yml b/.travis.yml index 6d56b2a6..d3de6547 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,3 +11,4 @@ before_script: script: - cargo test - cargo test --features "uuid rustc-serialize time unix_socket serde_json chrono openssl bit-vec eui48" +- (test $TRAVIS_RUST_VERSION != "nightly" || cargo test --features nightly) diff --git a/Cargo.toml b/Cargo.toml index 4689b36c..6ded623e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "postgres" -version = "0.11.4" +version = "0.11.5" authors = ["Steven Fackler "] license = "MIT" description = "A native PostgreSQL driver" repository = "https://github.com/sfackler/rust-postgres" -documentation = "https://sfackler.github.io/rust-postgres/doc/v0.11.4/postgres" +documentation = "https://sfackler.github.io/rust-postgres/doc/v0.11.5/postgres" readme = "README.md" keywords = ["database", "postgres", "postgresql", "sql"] include = ["src/*", "Cargo.toml", "LICENSE", "README.md", "THIRD_PARTY"] @@ -20,11 +20,14 @@ bench = false name = "test" path = "tests/test.rs" +[features] +nightly = [] + [dependencies] bufstream = "0.1" -byteorder = ">= 0.3, < 0.5" +byteorder = "0.5" log = "0.3" -phf = "0.7" +phf = "=0.7.14" hex = "0.1" net2 = "0.2.16" rustc-serialize = { version = "0.3", optional = true } diff --git a/README.md b/README.md index 2a359324..aab9c898 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Rust-Postgres A native PostgreSQL driver for Rust. -[Documentation](https://sfackler.github.io/rust-postgres/doc/v0.11.4/postgres) +[Documentation](https://sfackler.github.io/rust-postgres/doc/v0.11.5/postgres) [![Build Status](https://travis-ci.org/sfackler/rust-postgres.png?branch=master)](https://travis-ci.org/sfackler/rust-postgres) [![Latest Version](https://img.shields.io/crates/v/postgres.svg)](https://crates.io/crates/postgres) @@ -74,11 +74,11 @@ let conn = try!(Connection::connect("postgres://user:pass@host:port/database?arg defaults to the value of `user` if not specified. The driver supports `trust`, `password`, and `md5` authentication. -Unix domain sockets can be used as well by activating the `unix_socket` feature. -The `host` portion of the URI should be set to the absolute path to the -directory containing the socket file. Since `/` is a reserved character in -URLs, the path should be URL encoded. If Postgres stored its socket files in -`/run/postgres`, the connection would then look like: +Unix domain sockets can be used as well by activating the `unix_socket` or +`nightly` features. The `host` portion of the URI should be set to the absolute +path to the directory containing the socket file. Since `/` is a reserved +character in URLs, the path should be URL encoded. If Postgres stored its socket +files in `/run/postgres`, the connection would then look like: ```rust let conn = try!(Connection::connect("postgres://postgres@%2Frun%2Fpostgres", SslMode::None)); ``` @@ -269,7 +269,7 @@ The [postgres-derive](https://github.com/sfackler/rust-postgres-derive) crate will synthesize `ToSql` and `FromSql` implementations for enum, domain, and composite Postgres types. -Support for array types is located in the +Full support for array types is located in the [postgres-array](https://github.com/sfackler/rust-postgres-array) crate. Support for range types is located in the @@ -284,8 +284,8 @@ crate. ### Unix socket connections Support for connections through Unix domain sockets is provided optionally by -the `unix_socket` feature. It is only available on "unixy" platforms such as -OSX, BSD and Linux. +either the `unix_socket` or `nightly` features. It is only available on "unixy" +platforms such as OSX, BSD and Linux. ### UUID type diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 1ed48c4e..5787d24a 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" authors = ["Steven Fackler "] [dependencies] -phf_codegen = "0.7" +phf_codegen = "=0.7.14" regex = "0.1" marksman_escape = "0.1" diff --git a/src/error/mod.rs b/src/error/mod.rs index 3074cfdd..f072f439 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,6 +1,5 @@ //! Error types. -use byteorder; use std::error; use std::convert::From; use std::fmt; @@ -227,12 +226,6 @@ impl From for ConnectError { } } -impl From for ConnectError { - fn from(err: byteorder::Error) -> ConnectError { - ConnectError::Io(From::from(err)) - } -} - /// Represents the position of an error in a query. #[derive(Clone, PartialEq, Eq, Debug)] pub enum ErrorPosition { @@ -299,12 +292,6 @@ impl From for Error { } } -impl From for Error { - fn from(err: byteorder::Error) -> Error { - Error::Io(From::from(err)) - } -} - impl From for io::Error { fn from(err: Error) -> io::Error { io::Error::new(io::ErrorKind::Other, err) diff --git a/src/error/sqlstate.rs b/src/error/sqlstate.rs index 0063f090..0653d002 100644 --- a/src/error/sqlstate.rs +++ b/src/error/sqlstate.rs @@ -482,7 +482,7 @@ pub enum SqlState { #[cfg_attr(rustfmt, rustfmt_skip)] static SQLSTATE_MAP: phf::Map<&'static str, SqlState> = ::phf::Map { key: 1897749892740154578, - disps: &[ + disps: ::phf::Slice::Static(&[ (0, 10), (1, 206), (0, 38), @@ -531,8 +531,8 @@ static SQLSTATE_MAP: phf::Map<&'static str, SqlState> = ::phf::Map { (0, 233), (2, 149), (0, 105), - ], - entries: &[ + ]), + entries: ::phf::Slice::Static(&[ ("42P03", SqlState::DuplicateCursor), ("22019", SqlState::InvalidEscapeCharacter), ("22022", SqlState::IndicatorOverflow), @@ -769,7 +769,7 @@ static SQLSTATE_MAP: phf::Map<&'static str, SqlState> = ::phf::Map { ("28000", SqlState::InvalidAuthorizationSpecification), ("0Z002", SqlState::StackedDiagnosticsAccessedWithoutActiveHandler), ("02000", SqlState::NoData), - ] + ]), }; impl SqlState { diff --git a/src/lib.rs b/src/lib.rs index f6f19a9b..6597474e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,9 +38,10 @@ //! } //! } //! ``` -#![doc(html_root_url="https://sfackler.github.io/rust-postgres/doc/v0.11.4")] +#![doc(html_root_url="https://sfackler.github.io/rust-postgres/doc/v0.11.5")] #![warn(missing_docs)] #![allow(unknown_lints, needless_lifetimes)] // for clippy +#![cfg_attr(all(unix, feature = "nightly"), feature(unix_socket))] extern crate bufstream; extern crate byteorder; @@ -68,7 +69,7 @@ use std::mem; use std::result; use std::sync::Arc; use std::time::Duration; -#[cfg(feature = "unix_socket")] +#[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] use std::path::PathBuf; // FIXME remove in 0.12 @@ -115,8 +116,8 @@ pub enum ConnectTarget { Tcp(String), /// Connect via a Unix domain socket in the specified directory. /// - /// Requires the `unix_socket` feature. - #[cfg(feature = "unix_socket")] + /// Requires the `unix_socket` or `nightly` feature. + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] Unix(PathBuf), } @@ -173,14 +174,14 @@ impl<'a> IntoConnectParams for &'a str { impl IntoConnectParams for Url { fn into_connect_params(self) -> result::Result> { - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] fn make_unix(maybe_path: String) -> result::Result> { Ok(ConnectTarget::Unix(PathBuf::from(maybe_path))) } - #[cfg(not(feature = "unix_socket"))] + #[cfg(not(any(feature = "unix_socket", all(unix, feature = "nightly"))))] fn make_unix(_: String) -> result::Result> { - Err("unix socket support requires the `unix_socket` feature".into()) + Err("unix socket support requires the `unix_socket` or `nightly` features".into()) } let Url { host, port, user, path: url::Path { mut path, query: options, .. }, .. } = self; @@ -1019,13 +1020,13 @@ impl Connection { /// (5432) is used if none is specified. The database name defaults to the /// username if not specified. /// - /// Connection via Unix sockets is supported with the `unix_socket` - /// feature. To connect to the server via Unix sockets, `host` should be - /// set to the absolute path of the directory containing the socket file. - /// Since `/` is a reserved character in URLs, the path should be URL - /// encoded. If the path contains non-UTF 8 characters, a `ConnectParams` - /// struct should be created manually and passed in. Note that Postgres - /// does not support SSL over Unix sockets. + /// Connection via Unix sockets is supported with either the `unix_socket` + /// or `nightly` features. To connect to the server via Unix sockets, `host` + /// should be set to the absolute path of the directory containing the + /// socket file. Since `/` is a reserved character in URLs, the path should + /// be URL encoded. If the path contains non-UTF 8 characters, a + /// `ConnectParams` struct should be created manually and passed in. Note + /// that Postgres does not support SSL over Unix sockets. /// /// # Examples /// diff --git a/src/priv_io.rs b/src/priv_io.rs index e4af4d60..154e3baf 100644 --- a/src/priv_io.rs +++ b/src/priv_io.rs @@ -9,6 +9,8 @@ use std::time::Duration; use bufstream::BufStream; #[cfg(feature = "unix_socket")] use unix_socket::UnixStream; +#[cfg(all(not(feature = "unix_socket"), all(unix, feature = "nightly")))] +use std::os::unix::net::UnixStream; #[cfg(unix)] use std::os::unix::io::{AsRawFd, RawFd}; #[cfg(windows)] @@ -32,7 +34,7 @@ impl StreamOptions for BufStream> { fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { match self.get_ref().get_ref().0 { InternalStream::Tcp(ref s) => s.set_read_timeout(timeout), - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] InternalStream::Unix(ref s) => s.set_read_timeout(timeout), } } @@ -40,7 +42,7 @@ impl StreamOptions for BufStream> { fn set_nonblocking(&self, nonblock: bool) -> io::Result<()> { match self.get_ref().get_ref().0 { InternalStream::Tcp(ref s) => s.set_nonblocking(nonblock), - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] InternalStream::Unix(ref s) => s.set_nonblocking(nonblock), } } @@ -56,7 +58,7 @@ impl fmt::Debug for Stream { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match self.0 { InternalStream::Tcp(ref s) => fmt::Debug::fmt(s, fmt), - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] InternalStream::Unix(ref s) => fmt::Debug::fmt(s, fmt), } } @@ -93,7 +95,7 @@ impl AsRawFd for Stream { fn as_raw_fd(&self) -> RawFd { match self.0 { InternalStream::Tcp(ref s) => s.as_raw_fd(), - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] InternalStream::Unix(ref s) => s.as_raw_fd(), } } @@ -111,7 +113,7 @@ impl AsRawSocket for Stream { enum InternalStream { Tcp(TcpStream), - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] Unix(UnixStream), } @@ -119,7 +121,7 @@ impl Read for InternalStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { match *self { InternalStream::Tcp(ref mut s) => s.read(buf), - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] InternalStream::Unix(ref mut s) => s.read(buf), } } @@ -129,7 +131,7 @@ impl Write for InternalStream { fn write(&mut self, buf: &[u8]) -> io::Result { match *self { InternalStream::Tcp(ref mut s) => s.write(buf), - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] InternalStream::Unix(ref mut s) => s.write(buf), } } @@ -137,7 +139,7 @@ impl Write for InternalStream { fn flush(&mut self) -> io::Result<()> { match *self { InternalStream::Tcp(ref mut s) => s.flush(), - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] InternalStream::Unix(ref mut s) => s.flush(), } } @@ -149,7 +151,7 @@ fn open_socket(params: &ConnectParams) -> Result { ConnectTarget::Tcp(ref host) => { Ok(try!(TcpStream::connect(&(&**host, port)).map(InternalStream::Tcp))) } - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] ConnectTarget::Unix(ref path) => { let path = path.join(&format!(".s.PGSQL.{}", port)); Ok(try!(UnixStream::connect(&path).map(InternalStream::Unix))) @@ -183,7 +185,7 @@ pub fn initialize_stream(params: &ConnectParams, // Postgres doesn't support SSL over unix sockets let host = match params.target { ConnectTarget::Tcp(ref host) => host, - #[cfg(feature = "unix_socket")] + #[cfg(any(feature = "unix_socket", all(unix, feature = "nightly")))] ConnectTarget::Unix(_) => return Err(ConnectError::Io(::bad_response())), }; diff --git a/src/types/mod.rs b/src/types/mod.rs index 6bc40a5f..ef023cf9 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -296,6 +296,11 @@ impl WrongTypeNew for WrongType { /// In addition to the types listed above, `FromSql` is implemented for /// `Option` where `T` implements `FromSql`. An `Option` represents a /// nullable Postgres value. +/// +/// # Arrays +/// +/// `FromSql` is implemented for `Vec` where `T` implements `FromSql`, and +/// corresponds to one-dimensional Postgres arrays. pub trait FromSql: Sized { /// Creates a new value of this type from a `Read`er of the binary format /// of the specified Postgres `Type`. @@ -343,6 +348,46 @@ impl FromSql for bool { accepts!(Type::Bool); } +impl FromSql for Vec { + fn from_sql(ty: &Type, raw: &mut R, info: &SessionInfo) -> Result> { + let member_type = match *ty.kind() { + Kind::Array(ref member) => member, + _ => panic!("expected array type"), + }; + + if try!(raw.read_i32::()) != 1 { + return Err(Error::Conversion("array contains too many dimensions".into())); + } + + let _has_nulls = try!(raw.read_i32::()); + let _member_oid = try!(raw.read_u32::()); + + let count = try!(raw.read_i32::()); + let _index_offset = try!(raw.read_i32::()); + + let mut out = Vec::with_capacity(count as usize); + for _ in 0..count { + let len = try!(raw.read_i32::()); + let value = if len < 0 { + try!(T::from_sql_null(&member_type, info)) + } else { + let mut raw = raw.take(len as u64); + try!(T::from_sql(&member_type, &mut raw, info)) + }; + out.push(value) + } + + Ok(out) + } + + fn accepts(ty: &Type) -> bool { + match *ty.kind() { + Kind::Array(ref inner) => T::accepts(inner), + _ => false, + } + } +} + impl FromSql for Vec { fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result> { let mut buf = vec![]; @@ -496,6 +541,12 @@ pub enum IsNull { /// In addition to the types listed above, `ToSql` is implemented for /// `Option` where `T` implements `ToSql`. An `Option` represents a /// nullable Postgres value. +/// +/// # Arrays +/// +/// `ToSql` is implemented for `Vec` and `&[T]` where `T` implements `ToSql`, +/// and corresponds to one-dimentional Postgres arrays with an index offset of +/// 0. pub trait ToSql: fmt::Debug { /// Converts the value of `self` into the binary format of the specified /// Postgres `Type`, writing it to `out`. @@ -573,6 +624,48 @@ impl ToSql for bool { accepts!(Type::Bool); } +impl<'a, T: ToSql> ToSql for &'a [T] { + to_sql_checked!(); + + fn to_sql(&self, ty: &Type, + mut w: &mut W, + ctx: &SessionInfo) + -> Result { + let member_type = match *ty.kind() { + Kind::Array(ref member) => member, + _ => panic!("expected array type"), + }; + + try!(w.write_i32::(1)); // number of dimensions + try!(w.write_i32::(1)); // has nulls + try!(w.write_u32::(member_type.oid())); + + try!(w.write_i32::(try!(downcast(self.len())))); + try!(w.write_i32::(0)); // index offset + + let mut inner_buf = vec![]; + for e in *self { + match try!(e.to_sql(&member_type, &mut inner_buf, ctx)) { + IsNull::No => { + try!(w.write_i32::(try!(downcast(inner_buf.len())))); + try!(w.write_all(&inner_buf)); + } + IsNull::Yes => try!(w.write_i32::(-1)), + } + inner_buf.clear(); + } + + Ok(IsNull::No) + } + + fn accepts(ty: &Type) -> bool { + match *ty.kind() { + Kind::Array(ref member) => T::accepts(member), + _ => false, + } + } +} + impl<'a> ToSql for &'a [u8] { to_sql_checked!(); @@ -584,6 +677,18 @@ impl<'a> ToSql for &'a [u8] { accepts!(Type::Bytea); } +impl ToSql for Vec { + to_sql_checked!(); + + fn to_sql(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result { + <&[T] as ToSql>::to_sql(&&**self, ty, w, ctx) + } + + fn accepts(ty: &Type) -> bool { + <&[T] as ToSql>::accepts(ty) + } +} + impl ToSql for Vec { to_sql_checked!(); diff --git a/src/types/slice.rs b/src/types/slice.rs index cbd67973..a5072921 100644 --- a/src/types/slice.rs +++ b/src/types/slice.rs @@ -1,70 +1,21 @@ use std::io::prelude::*; -use byteorder::{WriteBytesExt, BigEndian}; use Result; -use types::{Type, ToSql, Kind, IsNull, SessionInfo, downcast}; +use types::{Type, ToSql, IsNull, SessionInfo}; -/// An adapter type mapping slices to Postgres arrays. +/// # Deprecated /// -/// `Slice`'s `ToSql` implementation maps the slice to a one-dimensional -/// Postgres array of the relevant type. This is particularly useful with the -/// `ANY` function to match a column against multiple values without having -/// to dynamically construct the query string. -/// -/// # Examples -/// -/// ```rust,no_run -/// # use postgres::{Connection, SslMode}; -/// use postgres::types::Slice; -/// -/// # let conn = Connection::connect("", SslMode::None).unwrap(); -/// let values = &[1i32, 2, 3, 4, 5, 6]; -/// let stmt = conn.prepare("SELECT * FROM foo WHERE id = ANY($1)").unwrap(); -/// for row in &stmt.query(&[&Slice(values)]).unwrap() { -/// // ... -/// } -/// ``` +/// `ToSql` is now implemented directly for slices. #[derive(Debug)] pub struct Slice<'a, T: 'a + ToSql>(pub &'a [T]); impl<'a, T: 'a + ToSql> ToSql for Slice<'a, T> { - fn to_sql(&self, - ty: &Type, - mut w: &mut W, - ctx: &SessionInfo) - -> Result { - let member_type = match *ty.kind() { - Kind::Array(ref member) => member, - _ => panic!("expected array type"), - }; - - try!(w.write_i32::(1)); // number of dimensions - try!(w.write_i32::(1)); // has nulls - try!(w.write_u32::(member_type.oid())); - - try!(w.write_i32::(try!(downcast(self.0.len())))); - try!(w.write_i32::(0)); // index offset - - let mut inner_buf = vec![]; - for e in self.0 { - match try!(e.to_sql(&member_type, &mut inner_buf, ctx)) { - IsNull::No => { - try!(w.write_i32::(try!(downcast(inner_buf.len())))); - try!(w.write_all(&inner_buf)); - } - IsNull::Yes => try!(w.write_i32::(-1)), - } - inner_buf.clear(); - } - - Ok(IsNull::No) + fn to_sql(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result { + self.0.to_sql(ty, w, ctx) } fn accepts(ty: &Type) -> bool { - match *ty.kind() { - Kind::Array(ref member) => T::accepts(member), - _ => false, - } + <&[T] as ToSql>::accepts(ty) } to_sql_checked!(); diff --git a/tests/test.rs b/tests/test.rs index 6386b857..ede7438d 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -75,7 +75,7 @@ fn test_connection_finish() { } #[test] -#[cfg_attr(not(feature = "unix_socket"), ignore)] +#[cfg_attr(not(any(feature = "unix_socket", all(unix, feature = "nightly"))), ignore)] fn test_unix_connection() { let conn = or_panic!(Connection::connect("postgres://postgres@localhost", SslMode::None)); let stmt = or_panic!(conn.prepare("SHOW unix_socket_directories")); diff --git a/tests/types/mod.rs b/tests/types/mod.rs index fe71b053..c571b81f 100644 --- a/tests/types/mod.rs +++ b/tests/types/mod.rs @@ -6,7 +6,7 @@ use std::io::{Read, Write}; use postgres::{Connection, SslMode, Result}; use postgres::error::Error; -use postgres::types::{ToSql, FromSql, Slice, WrongType, Type, IsNull, Kind, SessionInfo}; +use postgres::types::{ToSql, FromSql, WrongType, Type, IsNull, Kind, SessionInfo}; #[cfg(feature = "bit-vec")] mod bit_vec; @@ -207,7 +207,7 @@ fn test_slice() { INSERT INTO foo (f) VALUES ('a'), ('b'), ('c'), ('d');").unwrap(); let stmt = conn.prepare("SELECT f FROM foo WHERE id = ANY($1)").unwrap(); - let result = stmt.query(&[&Slice(&[1i32, 3, 4])]).unwrap(); + let result = stmt.query(&[&&[1i32, 3, 4][..]]).unwrap(); assert_eq!(vec!["a".to_owned(), "c".to_owned(), "d".to_owned()], result.iter().map(|r| r.get::<_, String>(0)).collect::>()); } @@ -218,7 +218,7 @@ fn test_slice_wrong_type() { conn.batch_execute("CREATE TEMPORARY TABLE foo (id SERIAL PRIMARY KEY)").unwrap(); let stmt = conn.prepare("SELECT * FROM foo WHERE id = ANY($1)").unwrap(); - match stmt.query(&[&Slice(&["hi"])]) { + match stmt.query(&[&&["hi"][..]]) { Ok(_) => panic!("Unexpected success"), Err(Error::Conversion(ref e)) if e.is::() => {} Err(e) => panic!("Unexpected error {:?}", e), @@ -230,7 +230,7 @@ fn test_slice_range() { let conn = Connection::connect("postgres://postgres@localhost", SslMode::None).unwrap(); let stmt = conn.prepare("SELECT $1::INT8RANGE").unwrap(); - match stmt.query(&[&Slice(&[1i64])]) { + match stmt.query(&[&&[1i64][..]]) { Ok(_) => panic!("Unexpected success"), Err(Error::Conversion(ref e)) if e.is::() => {} Err(e) => panic!("Unexpected error {:?}", e),