Split out TLS implementations

This provides a lot more flexibility around version support, and unlike
the ToSql/FromSql implementations, these don't actually need to be in
postgres itself.
This commit is contained in:
Steven Fackler 2018-05-28 10:36:48 -07:00
parent 374d0ca32e
commit c70a03f9e6
16 changed files with 262 additions and 381 deletions

View File

@ -29,8 +29,6 @@ jobs:
- image: sfackler/rust-postgres-test:3
steps:
- checkout
- run: apt-get update
- run: DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends jq
- *RESTORE_REGISTRY
- run: cargo generate-lockfile
- run: cargo update -p nalgebra --precise 0.14.3 # 0.14.4 requires 1.26 :(
@ -39,6 +37,6 @@ jobs:
- run: rustc --version > ~/rust-version
- *RESTORE_DEPS
- run: cargo test --all
- run: cargo test --manifest-path=postgres/Cargo.toml --features "$(cargo read-manifest --manifest-path=postgres/Cargo.toml | jq -r '.features|keys|map(select(. != "with-security-framework" and . != "with-schannel"))|join(" ")')"
- run: cargo test --manifest-path=tokio-postgres/Cargo.toml --all-features
- run: cargo test -p postgres --all-features
- run: cargo test -p tokio-postgres --all-features
- *SAVE_DEPS

View File

@ -4,5 +4,7 @@ members = [
"postgres",
"postgres-protocol",
"postgres-shared",
"tokio-postgres"
"postgres-openssl",
"postgres-native-tls",
"tokio-postgres",
]

View File

@ -0,0 +1,9 @@
[package]
name = "postgres-native-tls"
version = "0.1.0"
authors = ["Steven Fackler <sfackler@gmail.com>"]
[dependencies]
native-tls = "0.1"
postgres = { version = "0.15", path = "../postgres" }

View File

@ -0,0 +1,72 @@
pub extern crate native_tls;
extern crate postgres;
use native_tls::TlsConnector;
use postgres::tls::{Stream, TlsHandshake, TlsStream};
use std::error::Error;
use std::fmt::{self, Debug};
use std::io::{self, Read, Write};
#[cfg(test)]
mod test;
pub struct NativeTls {
connector: TlsConnector,
}
impl Debug for NativeTls {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("NativeTls").finish()
}
}
impl NativeTls {
pub fn new() -> Result<NativeTls, native_tls::Error> {
let connector = TlsConnector::builder()?.build()?;
Ok(NativeTls::with_connector(connector))
}
pub fn with_connector(connector: TlsConnector) -> NativeTls {
NativeTls { connector }
}
}
impl TlsHandshake for NativeTls {
fn tls_handshake(
&self,
domain: &str,
stream: Stream,
) -> Result<Box<TlsStream>, Box<Error + Sync + Send>> {
let stream = self.connector.connect(domain, stream)?;
Ok(Box::new(NativeTlsStream(stream)))
}
}
#[derive(Debug)]
struct NativeTlsStream(native_tls::TlsStream<Stream>);
impl Read for NativeTlsStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl Write for NativeTlsStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl TlsStream for NativeTlsStream {
fn get_ref(&self) -> &Stream {
self.0.get_ref()
}
fn get_mut(&mut self) -> &mut Stream {
self.0.get_mut()
}
}

View File

@ -0,0 +1,21 @@
use native_tls::{Certificate, TlsConnector};
use postgres::{Connection, TlsMode};
use NativeTls;
#[test]
fn connect() {
let cert = include_bytes!("../../test/server.crt");
let cert = Certificate::from_pem(cert).unwrap();
let mut builder = TlsConnector::builder().unwrap();
builder.add_root_certificate(cert).unwrap();
let connector = builder.build().unwrap();
let handshake = NativeTls::with_connector(connector);
let conn = Connection::connect(
"postgres://ssl_user@localhost:5433/postgres",
TlsMode::Require(&handshake),
).unwrap();
conn.execute("SELECT 1::VARCHAR", &[]).unwrap();
}

View File

@ -0,0 +1,9 @@
[package]
name = "postgres-openssl"
version = "0.1.0"
authors = ["Steven Fackler <sfackler@gmail.com>"]
[dependencies]
openssl = "0.10"
postgres = { version = "0.15", path = "../postgres" }

View File

@ -0,0 +1,87 @@
pub extern crate openssl;
extern crate postgres;
use openssl::error::ErrorStack;
use openssl::ssl::{ConnectConfiguration, SslConnector, SslMethod, SslStream};
use postgres::tls::{Stream, TlsHandshake, TlsStream};
use std::error::Error;
use std::fmt;
use std::io::{self, Read, Write};
#[cfg(test)]
mod test;
pub struct OpenSsl {
connector: SslConnector,
config: Box<Fn(&mut ConnectConfiguration) -> Result<(), ErrorStack> + Sync + Send>,
}
impl fmt::Debug for OpenSsl {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("OpenSsl").finish()
}
}
impl OpenSsl {
pub fn new() -> Result<OpenSsl, ErrorStack> {
let connector = SslConnector::builder(SslMethod::tls())?.build();
Ok(OpenSsl::with_connector(connector))
}
pub fn with_connector(connector: SslConnector) -> OpenSsl {
OpenSsl {
connector,
config: Box::new(|_| Ok(())),
}
}
pub fn callback<F>(&mut self, f: F)
where
F: Fn(&mut ConnectConfiguration) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.config = Box::new(f);
}
}
impl TlsHandshake for OpenSsl {
fn tls_handshake(
&self,
domain: &str,
stream: Stream,
) -> Result<Box<TlsStream>, Box<Error + Sync + Send>> {
let mut ssl = self.connector.configure()?;
(self.config)(&mut ssl)?;
let stream = ssl.connect(domain, stream)?;
Ok(Box::new(OpenSslStream(stream)))
}
}
#[derive(Debug)]
struct OpenSslStream(SslStream<Stream>);
impl Read for OpenSslStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl Write for OpenSslStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl TlsStream for OpenSslStream {
fn get_ref(&self) -> &Stream {
self.0.get_ref()
}
fn get_mut(&mut self) -> &mut Stream {
self.0.get_mut()
}
}

View File

@ -0,0 +1,28 @@
use openssl::ssl::{SslConnector, SslMethod};
use postgres::{Connection, TlsMode};
use OpenSsl;
#[test]
fn test_require_ssl_conn() {
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_ca_file("../test/server.crt").unwrap();
let negotiator = OpenSsl::with_connector(builder.build());
let conn = Connection::connect(
"postgres://ssl_user@localhost:5433/postgres",
TlsMode::Require(&negotiator),
).unwrap();
conn.execute("SELECT 1::VARCHAR", &[]).unwrap();
}
#[test]
fn test_prefer_ssl_conn() {
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_ca_file("../test/server.crt").unwrap();
let negotiator = OpenSsl::with_connector(builder.build());
let conn = Connection::connect(
"postgres://ssl_user@localhost:5433/postgres",
TlsMode::Require(&negotiator),
).unwrap();
conn.execute("SELECT 1::VARCHAR", &[]).unwrap();
}

View File

@ -43,11 +43,6 @@ path = "tests/test.rs"
"with-serde_json-1" = ["postgres-shared/with-serde_json-1"]
"with-uuid-0.6" = ["postgres-shared/with-uuid-0.6"]
with-openssl = ["openssl"]
with-native-tls = ["native-tls"]
with-schannel = ["schannel"]
with-security-framework = ["security-framework"]
no-logging = []
[dependencies]
@ -56,11 +51,6 @@ fallible-iterator = "0.1.3"
log = "0.4"
socket2 = { version = "0.3.5", features = ["unix"] }
openssl = { version = "0.9.23", optional = true }
native-tls = { version = "0.1", optional = true }
schannel = { version = "0.1", optional = true }
security-framework = { version = "0.1.2", optional = true }
postgres-protocol = { version = "0.3.0", path = "../postgres-protocol" }
postgres-shared = { version = "0.4.1", path = "../postgres-shared" }

View File

@ -100,10 +100,10 @@ use params::{IntoConnectParams, User};
use priv_io::MessageStream;
use rows::Rows;
use stmt::{Column, Statement};
use text_rows::TextRows;
use tls::TlsHandshake;
use transaction::{IsolationLevel, Transaction};
use types::{Field, FromSql, IsNull, Kind, Oid, ToSql, Type};
use text_rows::TextRows;
#[doc(inline)]
pub use error::Error;
@ -119,8 +119,8 @@ pub mod notification;
pub mod params;
mod priv_io;
pub mod rows;
pub mod text_rows;
pub mod stmt;
pub mod text_rows;
pub mod tls;
pub mod transaction;
@ -423,7 +423,8 @@ impl InnerConnection {
}
backend::Message::AuthenticationSasl(body) => {
// count to validate the entire message body.
if body.mechanisms()
if body
.mechanisms()
.filter(|m| *m == sasl::SCRAM_SHA_256)
.count()? == 0
{
@ -726,10 +727,10 @@ impl InnerConnection {
Ok(ty)
}
fn parse_cols(&mut self, raw: Option<backend::RowDescriptionBody>) -> Result<Vec<Column>> {
match raw {
Some(body) => body.fields()
Some(body) => body
.fields()
.and_then(|field| {
Ok(Column::new(
field.name().to_owned(),
@ -865,8 +866,9 @@ impl InnerConnection {
let mut variants = vec![];
for row in rows {
variants
.push(String::from_sql_nullable(&Type::NAME, row.get(0)).map_err(error::conversion)?);
variants.push(
String::from_sql_nullable(&Type::NAME, row.get(0)).map_err(error::conversion)?
);
}
Ok(variants)
@ -929,7 +931,8 @@ impl InnerConnection {
fn simple_query_(&mut self, query: &str) -> Result<Vec<TextRows>> {
check_desync!(self);
debug!("executing query: {}", query);
self.stream.write_message(|buf| frontend::query(query, buf))?;
self.stream
.write_message(|buf| frontend::query(query, buf))?;
self.stream.flush()?;
let mut result = vec![];
@ -946,9 +949,8 @@ impl InnerConnection {
self.stream.write_message(|buf| {
frontend::copy_fail("COPY queries cannot be directly executed", buf)
})?;
self.stream.write_message(
|buf| Ok::<(), io::Error>(frontend::sync(buf)),
)?;
self.stream
.write_message(|buf| Ok::<(), io::Error>(frontend::sync(buf)))?;
self.stream.flush()?;
}
backend::Message::ErrorResponse(body) => {
@ -981,7 +983,8 @@ impl InnerConnection {
match self.read_message()? {
backend::Message::ReadyForQuery(_) => break,
backend::Message::DataRow(body) => {
let row = body.ranges()
let row = body
.ranges()
.map(|r| r.map(|r| String::from_utf8_lossy(&body.buffer()[r]).into_owned()))
.collect()?;
result.push(row);
@ -1304,8 +1307,7 @@ impl Connection {
pub fn set_transaction_config(&self, config: &transaction::Config) -> Result<()> {
let mut command = "SET SESSION CHARACTERISTICS AS TRANSACTION".to_owned();
config.build_command(&mut command);
self.simple_query(&command)
.map(|_| ())
self.simple_query(&command).map(|_| ())
}
/// Execute a sequence of SQL statements.
@ -1342,12 +1344,11 @@ impl Connection {
/// CREATE INDEX ON purchase (time);
/// ").unwrap();
/// ```
#[deprecated(since="0.15.3", note="please use `simple_query` instead")]
#[deprecated(since = "0.15.3", note = "please use `simple_query` instead")]
pub fn batch_execute(&self, query: &str) -> Result<()> {
self.0.borrow_mut().quick_query(query).map(|_| ())
}
/// Send a simple, non-prepared query
///
/// Executes a query without making a prepared statement. All result columns
@ -1451,7 +1452,7 @@ pub trait GenericConnection {
fn transaction<'a>(&'a self) -> Result<Transaction<'a>>;
/// Like `Connection::batch_execute`.
#[deprecated(since="0.15.3", note="please use `simple_query` instead")]
#[deprecated(since = "0.15.3", note = "please use `simple_query` instead")]
fn batch_execute(&self, query: &str) -> Result<()>;
/// Like `Connection::is_active`.
@ -1483,8 +1484,7 @@ impl GenericConnection for Connection {
}
fn batch_execute(&self, query: &str) -> Result<()> {
self.simple_query(query)
.map(|_| ())
self.simple_query(query).map(|_| ())
}
fn is_active(&self) -> bool {
@ -1518,8 +1518,7 @@ impl<'a> GenericConnection for Transaction<'a> {
}
fn batch_execute(&self, query: &str) -> Result<()> {
self.simple_query(query)
.map(|_| ())
self.simple_query(query).map(|_| ())
}
fn simple_query(&self, query: &str) -> Result<Vec<TextRows>> {

View File

@ -2,17 +2,8 @@
pub use priv_io::Stream;
use std::error::Error;
use std::io::prelude::*;
use std::fmt;
#[cfg(feature = "with-native-tls")]
pub mod native_tls;
#[cfg(feature = "with-openssl")]
pub mod openssl;
#[cfg(feature = "with-schannel")]
pub mod schannel;
#[cfg(feature = "with-security-framework")]
pub mod security_framework;
use std::io::prelude::*;
/// A trait implemented by TLS streams.
pub trait TlsStream: fmt::Debug + Read + Write + Send {

View File

@ -1,65 +0,0 @@
//! Native TLS support.
pub extern crate native_tls;
use std::error::Error;
use std::fmt;
use self::native_tls::TlsConnector;
use tls::{TlsStream, Stream, TlsHandshake};
impl TlsStream for native_tls::TlsStream<Stream> {
fn get_ref(&self) -> &Stream {
self.get_ref()
}
fn get_mut(&mut self) -> &mut Stream {
self.get_mut()
}
}
/// A `TlsHandshake` implementation that uses the native-tls crate.
///
/// Requires the `with-native-tls` feature.
pub struct NativeTls(TlsConnector);
impl fmt::Debug for NativeTls {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("NativeTls").finish()
}
}
impl NativeTls {
/// Creates a new `NativeTls` with its default configuration.
pub fn new() -> Result<NativeTls, native_tls::Error> {
let connector = TlsConnector::builder()?;
let connector = connector.build()?;
Ok(NativeTls(connector))
}
/// Returns a reference to the inner `TlsConnector`.
pub fn connector(&self) -> &TlsConnector {
&self.0
}
/// Returns a mutable reference to the inner `TlsConnector`.
pub fn connector_mut(&mut self) -> &mut TlsConnector {
&mut self.0
}
}
impl From<TlsConnector> for NativeTls {
fn from(connector: TlsConnector) -> NativeTls {
NativeTls(connector)
}
}
impl TlsHandshake for NativeTls {
fn tls_handshake(
&self,
domain: &str,
stream: Stream,
) -> Result<Box<TlsStream>, Box<Error + Send + Sync>> {
let stream = self.0.connect(domain, stream)?;
Ok(Box::new(stream))
}
}

View File

@ -1,85 +0,0 @@
//! OpenSSL support.
pub extern crate openssl;
use std::error::Error;
use std::fmt;
use self::openssl::error::ErrorStack;
use self::openssl::ssl::{SslMethod, SslConnector, SslConnectorBuilder, SslStream};
use tls::{TlsStream, Stream, TlsHandshake};
impl TlsStream for SslStream<Stream> {
fn get_ref(&self) -> &Stream {
self.get_ref()
}
fn get_mut(&mut self) -> &mut Stream {
self.get_mut()
}
}
/// A `TlsHandshake` implementation that uses OpenSSL.
///
/// Requires the `with-openssl` feature.
pub struct OpenSsl {
connector: SslConnector,
disable_verification: bool,
}
impl fmt::Debug for OpenSsl {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("OpenSsl").finish()
}
}
impl OpenSsl {
/// Creates a `OpenSsl` with `SslConnector`'s default configuration.
pub fn new() -> Result<OpenSsl, ErrorStack> {
let connector = SslConnectorBuilder::new(SslMethod::tls())?.build();
Ok(OpenSsl::from(connector))
}
/// Returns a reference to the inner `SslConnector`.
pub fn connector(&self) -> &SslConnector {
&self.connector
}
/// Returns a mutable reference to the inner `SslConnector`.
pub fn connector_mut(&mut self) -> &mut SslConnector {
&mut self.connector
}
/// If set, the
/// `SslConnector::danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication`
/// method will be used to connect.
///
/// If certificate verification has been disabled in the `SslConnector`, verification must be
/// additionally disabled here for that setting to take effect.
pub fn danger_disable_hostname_verification(&mut self, disable_verification: bool) {
self.disable_verification = disable_verification;
}
}
impl From<SslConnector> for OpenSsl {
fn from(connector: SslConnector) -> OpenSsl {
OpenSsl {
connector: connector,
disable_verification: false,
}
}
}
impl TlsHandshake for OpenSsl {
fn tls_handshake(
&self,
domain: &str,
stream: Stream,
) -> Result<Box<TlsStream>, Box<Error + Send + Sync>> {
let stream = if self.disable_verification {
self.connector.danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication(stream)?
} else {
self.connector.connect(domain, stream)?
};
Ok(Box::new(stream))
}
}

View File

@ -1,53 +0,0 @@
//! SChannel support.
pub extern crate schannel;
use std::error::Error;
use std::fmt;
use self::schannel::schannel_cred::{SchannelCred, Direction};
use self::schannel::tls_stream;
use tls::{TlsStream, Stream, TlsHandshake};
impl TlsStream for tls_stream::TlsStream<Stream> {
fn get_ref(&self) -> &Stream {
self.get_ref()
}
fn get_mut(&mut self) -> &mut Stream {
self.get_mut()
}
}
/// A `TlsHandshake` implementation that uses the `schannel` crate.
///
/// Requires the `with-schannel` feature.
pub struct Schannel(());
impl fmt::Debug for Schannel {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Schannel").finish()
}
}
impl Schannel {
/// Constructs a new `SChannel` with a default configuration.
pub fn new() -> Schannel {
Schannel(())
}
}
impl TlsHandshake for Schannel {
fn tls_handshake(
&self,
host: &str,
stream: Stream,
) -> Result<Box<TlsStream>, Box<Error + Sync + Send>> {
let creds = SchannelCred::builder().acquire(Direction::Outbound)?;
let stream = tls_stream::Builder::new().domain(host).connect(
creds,
stream,
)?;
Ok(Box::new(stream))
}
}

View File

@ -1,56 +0,0 @@
//! Security Framework support.
pub extern crate security_framework;
use self::security_framework::secure_transport::{SslStream, ClientBuilder};
use tls::{Stream, TlsStream, TlsHandshake};
use std::error::Error;
impl TlsStream for SslStream<Stream> {
fn get_ref(&self) -> &Stream {
self.get_ref()
}
fn get_mut(&mut self) -> &mut Stream {
self.get_mut()
}
}
/// A `TlsHandshake` implementation that uses the Security Framework.
///
/// Requires the `with-security-framework` feature.
#[derive(Debug)]
pub struct SecurityFramework(ClientBuilder);
impl SecurityFramework {
/// Returns a new `SecurityFramework` with default settings.
pub fn new() -> SecurityFramework {
ClientBuilder::new().into()
}
/// Returns a reference to the associated `ClientBuilder`.
pub fn builder(&self) -> &ClientBuilder {
&self.0
}
/// Returns a mutable reference to the associated `ClientBuilder`.
pub fn builder_mut(&mut self) -> &mut ClientBuilder {
&mut self.0
}
}
impl From<ClientBuilder> for SecurityFramework {
fn from(b: ClientBuilder) -> SecurityFramework {
SecurityFramework(b)
}
}
impl TlsHandshake for SecurityFramework {
fn tls_handshake(
&self,
domain: &str,
stream: Stream,
) -> Result<Box<TlsStream>, Box<Error + Send + Sync>> {
let stream = self.0.handshake(domain, stream)?;
Ok(Box::new(stream))
}
}

View File

@ -1,14 +1,9 @@
extern crate fallible_iterator;
#[cfg(feature = "native-tls")]
extern crate native_tls;
#[cfg(feature = "with-openssl")]
extern crate openssl;
extern crate postgres;
extern crate url;
#[macro_use]
extern crate postgres_shared;
#[cfg(feature = "with-security-framework")]
extern crate security_framework;
extern crate url;
use fallible_iterator::FallibleIterator;
use postgres::error::ErrorPosition::Normal;
@ -908,70 +903,6 @@ fn test_cancel_query() {
t.join().unwrap();
}
#[test]
#[cfg(feature = "with-openssl")]
fn test_require_ssl_conn() {
use openssl::ssl::{SslConnectorBuilder, SslMethod};
use postgres::tls::openssl::OpenSsl;
let mut builder = SslConnectorBuilder::new(SslMethod::tls()).unwrap();
builder.set_ca_file("../test/server.crt").unwrap();
let negotiator = OpenSsl::from(builder.build());
let conn = or_panic!(Connection::connect(
"postgres://postgres@localhost:5433",
TlsMode::Require(&negotiator),
));
or_panic!(conn.execute("SELECT 1::VARCHAR", &[]));
}
#[test]
#[cfg(feature = "with-openssl")]
fn test_prefer_ssl_conn() {
use openssl::ssl::{SslConnectorBuilder, SslMethod};
use postgres::tls::openssl::OpenSsl;
let mut builder = SslConnectorBuilder::new(SslMethod::tls()).unwrap();
builder.set_ca_file("../test/server.crt").unwrap();
let negotiator = OpenSsl::from(builder.build());
let conn = or_panic!(Connection::connect(
"postgres://postgres@localhost:5433",
TlsMode::Require(&negotiator),
));
or_panic!(conn.execute("SELECT 1::VARCHAR", &[]));
}
#[test]
#[cfg(feature = "with-security-framework")]
fn security_framework_ssl() {
use postgres::tls::security_framework::SecurityFramework;
use security_framework::certificate::SecCertificate;
let certificate = include_bytes!("../../test/server.der");
let certificate = or_panic!(SecCertificate::from_der(certificate));
let mut negotiator = SecurityFramework::new();
negotiator.builder_mut().anchor_certificates(&[certificate]);
let conn = or_panic!(Connection::connect(
"postgres://postgres@localhost:5433",
TlsMode::Require(&negotiator),
));
or_panic!(conn.execute("SELECT 1::VARCHAR", &[]));
}
#[test]
#[ignore]
// need to ignore until native-tls supports extra root certs :(
#[cfg(feature = "with-native-tls")]
fn native_tls_ssl() {
use postgres::tls::native_tls::NativeTls;
let negotiator = NativeTls::new().unwrap();
let conn = or_panic!(Connection::connect(
"postgres://postgres@localhost:5433",
TlsMode::Require(&negotiator),
));
or_panic!(conn.execute("SELECT 1::VARCHAR", &[]));
}
#[test]
fn test_plaintext_pass() {
or_panic!(Connection::connect(
@ -1399,14 +1330,15 @@ fn test_rows_index() {
#[test]
fn test_type_names() {
let conn = Connection::connect("postgres://postgres@localhost:5433", TlsMode::None).unwrap();
let stmt = conn.prepare(
"SELECT t.oid, t.typname
let stmt =
conn.prepare(
"SELECT t.oid, t.typname
FROM pg_catalog.pg_type t, pg_namespace n
WHERE n.oid = t.typnamespace
AND n.nspname = 'pg_catalog'
AND t.oid < 10000
AND t.typtype != 'c'",
).unwrap();
).unwrap();
for row in &stmt.query(&[]).unwrap() {
let id: Oid = row.get(0);
let name: String = row.get(1);
@ -1423,7 +1355,8 @@ fn test_conn_query() {
INSERT INTO foo (id) VALUES (1), (2), (3);
",
).unwrap();
let ids = conn.query("SELECT id FROM foo ORDER BY id", &[])
let ids = conn
.query("SELECT id FROM foo ORDER BY id", &[])
.unwrap()
.iter()
.map(|r| r.get(0))
@ -1488,7 +1421,8 @@ fn keepalive() {
#[test]
fn explicit_types() {
let conn = Connection::connect("postgres://postgres@localhost:5433", TlsMode::None).unwrap();
let stmt = conn.prepare_typed("SELECT $1::INT4", &[Some(Type::INT8)])
let stmt = conn
.prepare_typed("SELECT $1::INT4", &[Some(Type::INT8)])
.unwrap();
assert_eq!(stmt.param_types()[0], Type::INT8);
}