├── postgres-openssl ├── LICENSE-MIT ├── LICENSE-APACHE ├── CHANGELOG.md ├── Cargo.toml └── src │ └── test.rs ├── postgres-native-tls ├── LICENSE-MIT ├── LICENSE-APACHE ├── CHANGELOG.md ├── Cargo.toml └── src │ ├── test.rs │ └── lib.rs ├── .gitignore ├── test ├── server.der └── server.crt ├── docker ├── Dockerfile └── sql_setup.sh ├── docker-compose.yml ├── tokio-postgres ├── src │ ├── types.rs │ ├── cancel_query_raw.rs │ ├── bind.rs │ ├── cancel_query.rs │ ├── portal.rs │ ├── to_statement.rs │ ├── connect_tls.rs │ ├── connect_socket.rs │ ├── copy_out.rs │ ├── statement.rs │ ├── maybe_tls_stream.rs │ ├── socket.rs │ ├── codec.rs │ ├── connect.rs │ ├── simple_query.rs │ ├── tls.rs │ ├── row.rs │ └── transaction.rs ├── tests │ └── test │ │ ├── types │ │ ├── eui48_04.rs │ │ ├── uuid_08.rs │ │ ├── bit_vec_06.rs │ │ ├── serde_json_1.rs │ │ ├── geo_010.rs │ │ └── chrono_04.rs │ │ ├── runtime.rs │ │ ├── parse.rs │ │ └── binary_copy.rs ├── LICENSE-MIT ├── Cargo.toml ├── benches │ └── bench.rs └── CHANGELOG.md ├── postgres-derive ├── CHANGELOG.md ├── Cargo.toml ├── src │ ├── composites.rs │ ├── enums.rs │ ├── lib.rs │ ├── overrides.rs │ ├── accepts.rs │ ├── tosql.rs │ └── fromsql.rs └── LICENSE-MIT ├── codegen ├── Cargo.toml └── src │ ├── main.rs │ ├── pg_range.dat │ └── sqlstate.rs ├── postgres-protocol ├── src │ ├── message │ │ └── mod.rs │ ├── authentication │ │ └── mod.rs │ ├── lib.rs │ └── types │ │ └── test.rs ├── Cargo.toml ├── CHANGELOG.md └── LICENSE-MIT ├── postgres-derive-test ├── src │ ├── compile-fail │ │ ├── unknown-override.rs │ │ ├── unknown-override.stderr │ │ ├── invalid-types.rs │ │ └── invalid-types.stderr │ ├── lib.rs │ ├── enums.rs │ ├── domains.rs │ └── composites.rs └── Cargo.toml ├── Cargo.toml ├── postgres-types ├── CHANGELOG.md ├── src │ ├── uuid_08.rs │ ├── eui48_04.rs │ ├── bit_vec_06.rs │ ├── private.rs │ ├── serde_json_1.rs │ ├── geo_types_04.rs │ ├── special.rs │ └── chrono_04.rs ├── LICENSE-MIT └── Cargo.toml ├── postgres ├── benches │ └── bench.rs ├── src │ ├── lazy_pin.rs │ ├── row_iter.rs │ ├── copy_out_reader.rs │ ├── copy_in_writer.rs │ ├── lib.rs │ ├── binary_copy.rs │ └── transaction.rs ├── LICENSE-MIT ├── Cargo.toml └── CHANGELOG.md ├── LICENSE ├── .circleci └── config.yml ├── README.md └── THIRD_PARTY /postgres-openssl/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../tokio-postgres/LICENSE-MIT -------------------------------------------------------------------------------- /postgres-native-tls/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../tokio-postgres/LICENSE-MIT -------------------------------------------------------------------------------- /postgres-openssl/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../tokio-postgres/LICENSE-APACHE -------------------------------------------------------------------------------- /postgres-native-tls/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../tokio-postgres/LICENSE-APACHE -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .cargo 4 | .idea 5 | *.iml 6 | .vscode 7 | -------------------------------------------------------------------------------- /test/server.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fafhrd91/postgres/HEAD/test/server.der -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:12 2 | 3 | COPY sql_setup.sh /docker-entrypoint-initdb.d/ 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | postgres: 4 | image: "sfackler/rust-postgres-test:6" 5 | ports: 6 | - 5433:5433 7 | -------------------------------------------------------------------------------- /tokio-postgres/src/types.rs: -------------------------------------------------------------------------------- 1 | //! Types. 2 | //! 3 | //! This module is a reexport of the `postgres_types` crate. 4 | 5 | #[doc(inline)] 6 | pub use postgres_types::*; 7 | -------------------------------------------------------------------------------- /postgres-derive/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.4.0 - 2019-12-23 4 | 5 | No changes 6 | 7 | ## v0.4.0-alpha.1 - 2019-10-14 8 | 9 | * Initial release 10 | -------------------------------------------------------------------------------- /codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codegen" 3 | version = "0.1.0" 4 | authors = ["Steven Fackler "] 5 | 6 | [dependencies] 7 | phf_codegen = "0.8" 8 | regex = "1.0" 9 | marksman_escape = "0.1" 10 | linked-hash-map = "0.5" 11 | -------------------------------------------------------------------------------- /postgres-protocol/src/message/mod.rs: -------------------------------------------------------------------------------- 1 | //! Postgres message protocol support. 2 | //! 3 | //! See [Postgres's documentation][docs] for more information on message flow. 4 | //! 5 | //! [docs]: https://www.postgresql.org/docs/9.5/static/protocol-flow.html 6 | 7 | pub mod backend; 8 | pub mod frontend; 9 | -------------------------------------------------------------------------------- /postgres-derive-test/src/compile-fail/unknown-override.rs: -------------------------------------------------------------------------------- 1 | use postgres_types::{FromSql, ToSql}; 2 | 3 | #[derive(FromSql)] 4 | #[postgres(foo = "bar")] 5 | struct Foo { 6 | a: i32, 7 | } 8 | 9 | #[derive(ToSql)] 10 | #[postgres(foo = "bar")] 11 | struct Bar { 12 | a: i32, 13 | } 14 | 15 | fn main() {} 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "codegen", 4 | "postgres", 5 | "postgres-derive", 6 | "postgres-derive-test", 7 | "postgres-native-tls", 8 | "postgres-openssl", 9 | "postgres-protocol", 10 | "postgres-types", 11 | "tokio-postgres", 12 | ] 13 | 14 | [profile.release] 15 | debug = 2 16 | -------------------------------------------------------------------------------- /postgres-derive-test/src/compile-fail/unknown-override.stderr: -------------------------------------------------------------------------------- 1 | error: unknown override 2 | --> $DIR/unknown-override.rs:4:12 3 | | 4 | 4 | #[postgres(foo = "bar")] 5 | | ^^^ 6 | 7 | error: unknown override 8 | --> $DIR/unknown-override.rs:10:12 9 | | 10 | 10 | #[postgres(foo = "bar")] 11 | | ^^^ 12 | -------------------------------------------------------------------------------- /postgres-derive-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postgres-derive-test" 3 | version = "0.1.0" 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | 7 | [dev-dependencies] 8 | trybuild = "1.0" 9 | 10 | postgres-types = { path = "../postgres-types", features = ["derive"] } 11 | postgres = { path = "../postgres" } 12 | -------------------------------------------------------------------------------- /tokio-postgres/src/cancel_query_raw.rs: -------------------------------------------------------------------------------- 1 | use ntex::io::Io; 2 | use postgres_protocol::message::frontend; 3 | use tokio::io::{AsyncRead, AsyncWrite}; 4 | 5 | use crate::config::SslMode; 6 | use crate::tls::TlsConnect; 7 | use crate::Error; 8 | 9 | pub async fn cancel_query_raw( 10 | stream: Io, 11 | mode: SslMode, 12 | process_id: i32, 13 | secret_key: i32, 14 | ) -> Result<(), Error> { 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /postgres-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postgres-derive" 3 | version = "0.4.0" 4 | authors = ["Steven Fackler "] 5 | license = "MIT/Apache-2.0" 6 | edition = "2018" 7 | description = "An internal crate used by postgres-types" 8 | repository = "https://github.com/sfackler/rust-postgres" 9 | 10 | [lib] 11 | proc-macro = true 12 | test = false 13 | 14 | [dependencies] 15 | syn = "1.0" 16 | proc-macro2 = "1.0" 17 | quote = "1.0" 18 | -------------------------------------------------------------------------------- /tokio-postgres/tests/test/types/eui48_04.rs: -------------------------------------------------------------------------------- 1 | use eui48_04::MacAddress; 2 | 3 | use crate::types::test_type; 4 | 5 | #[tokio::test] 6 | async fn test_eui48_params() { 7 | test_type( 8 | "MACADDR", 9 | &[ 10 | ( 11 | Some(MacAddress::parse_str("12-34-56-AB-CD-EF").unwrap()), 12 | "'12-34-56-ab-cd-ef'", 13 | ), 14 | (None, "NULL"), 15 | ], 16 | ) 17 | .await 18 | } 19 | -------------------------------------------------------------------------------- /postgres-types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.1.0 - 2019-12-23 4 | 5 | ### Changed 6 | 7 | * `Kind` is now a true non-exhaustive enum. 8 | 9 | ### Removed 10 | 11 | * Removed `uuid` 0.7 support. 12 | 13 | ### Added 14 | 15 | * Added a `Hash` implementation for `Type`. 16 | 17 | ## v0.1.0-alpha.2 - 2019-11-27 18 | 19 | ### Changed 20 | 21 | * Upgraded `bytes` to 0.5. 22 | * Upgraded `uuid` to 0.8. 23 | 24 | ## v0.1.0-alpha.1 - 2019-10-14 25 | 26 | Initial release 27 | -------------------------------------------------------------------------------- /tokio-postgres/tests/test/types/uuid_08.rs: -------------------------------------------------------------------------------- 1 | use uuid_08::Uuid; 2 | 3 | use crate::types::test_type; 4 | 5 | #[tokio::test] 6 | async fn test_uuid_params() { 7 | test_type( 8 | "UUID", 9 | &[ 10 | ( 11 | Some(Uuid::parse_str("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11").unwrap()), 12 | "'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'", 13 | ), 14 | (None, "NULL"), 15 | ], 16 | ) 17 | .await 18 | } 19 | -------------------------------------------------------------------------------- /postgres-openssl/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.3.0 - 2019-12-23 4 | 5 | ### Changed 6 | 7 | * Upgraded to `tokio-postgres` 0.5. 8 | 9 | ## v0.3.0-alpha.2 - 2019-11-27 10 | 11 | ### Changed 12 | 13 | * Upgraded `tokio-postgres` v0.5.0-alpha.2. 14 | 15 | ## v0.3.0-alpha.1 - 2019-10-14 16 | 17 | ### Changed 18 | 19 | * Updated to `tokio-postgres` v0.5.0-alpha.1. 20 | 21 | ## v0.2.0-rc.1 - 2019-03-06 22 | 23 | ### Changed 24 | 25 | * Updated to `tokio-postgres` v0.4.0-rc. 26 | -------------------------------------------------------------------------------- /postgres-derive-test/src/compile-fail/invalid-types.rs: -------------------------------------------------------------------------------- 1 | use postgres_types::{FromSql, ToSql}; 2 | 3 | #[derive(ToSql)] 4 | struct ToSqlUnit; 5 | 6 | #[derive(FromSql)] 7 | struct FromSqlUnit; 8 | 9 | #[derive(ToSql)] 10 | struct ToSqlTuple(i32, i32); 11 | 12 | #[derive(FromSql)] 13 | struct FromSqlTuple(i32, i32); 14 | 15 | #[derive(ToSql)] 16 | enum ToSqlEnum { 17 | Foo(i32), 18 | } 19 | 20 | #[derive(FromSql)] 21 | enum FromSqlEnum { 22 | Foo(i32), 23 | } 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /postgres-native-tls/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.3.0 - 2019-12-23 4 | 5 | ### Changed 6 | 7 | * Upgraded to `tokio-postgres` 0.5. 8 | 9 | ## v0.3.0-alpha.2 - 2019-11-27 10 | 11 | ### Changed 12 | 13 | * Upgraded to `tokio-postgres` v0.5.0-alpha.2. 14 | 15 | ## v0.3.0-alpha.1 - 2019-10-14 16 | 17 | ### Changed 18 | 19 | * Updated to `tokio-postgres` v0.5.0-alpha.1. 20 | 21 | ## v0.2.0-rc.1 - 2019-06-29 22 | 23 | ### Changed 24 | 25 | * Updated to `tokio-postgres` v0.4.0-rc. 26 | -------------------------------------------------------------------------------- /postgres-protocol/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postgres-protocol" 3 | version = "0.5.0" 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | description = "Low level Postgres protocol APIs" 7 | license = "MIT/Apache-2.0" 8 | repository = "https://github.com/sfackler/rust-postgres-protocol" 9 | readme = "../README.md" 10 | 11 | [dependencies] 12 | base64 = "0.11" 13 | byteorder = "1.0" 14 | bytes = { version = "0.1.1", package = "ntex-bytes" } 15 | fallible-iterator = "0.2" 16 | hmac = "0.7" 17 | md5 = "0.7" 18 | memchr = "2.0" 19 | rand = "0.7" 20 | sha2 = "0.8" 21 | stringprep = "0.1" 22 | -------------------------------------------------------------------------------- /postgres/benches/bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use postgres::{Client, NoTls}; 3 | 4 | // spawned: 249us 252us 255us 5 | // local: 214us 216us 219us 6 | fn query_prepared(c: &mut Criterion) { 7 | let mut client = Client::connect("host=localhost port=5433 user=postgres", NoTls).unwrap(); 8 | 9 | let stmt = client.prepare("SELECT $1::INT8").unwrap(); 10 | 11 | c.bench_function("query_prepared", move |b| { 12 | b.iter(|| client.query(&stmt, &[&1i64]).unwrap()) 13 | }); 14 | } 15 | 16 | criterion_group!(group, query_prepared); 17 | criterion_main!(group); 18 | -------------------------------------------------------------------------------- /postgres-derive/src/composites.rs: -------------------------------------------------------------------------------- 1 | use syn::{Error, Ident, Type}; 2 | 3 | use crate::overrides::Overrides; 4 | 5 | pub struct Field { 6 | pub name: String, 7 | pub ident: Ident, 8 | pub type_: Type, 9 | } 10 | 11 | impl Field { 12 | pub fn parse(raw: &syn::Field) -> Result { 13 | let overrides = Overrides::extract(&raw.attrs)?; 14 | 15 | let ident = raw.ident.as_ref().unwrap().clone(); 16 | Ok(Field { 17 | name: overrides.name.unwrap_or_else(|| ident.to_string()), 18 | ident, 19 | type_: raw.ty.clone(), 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /postgres/src/lazy_pin.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | 3 | pub(crate) struct LazyPin { 4 | value: Box, 5 | pinned: bool, 6 | } 7 | 8 | impl LazyPin { 9 | pub fn new(value: T) -> LazyPin { 10 | LazyPin { 11 | value: Box::new(value), 12 | pinned: false, 13 | } 14 | } 15 | 16 | pub fn pinned(&mut self) -> Pin<&mut T> { 17 | self.pinned = true; 18 | unsafe { Pin::new_unchecked(&mut *self.value) } 19 | } 20 | 21 | pub fn into_unpinned(self) -> Option { 22 | if self.pinned { 23 | None 24 | } else { 25 | Some(*self.value) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /postgres-types/src/uuid_08.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use postgres_protocol::types; 3 | use std::error::Error; 4 | use uuid_08::Uuid; 5 | 6 | use crate::{FromSql, IsNull, ToSql, Type}; 7 | 8 | impl<'a> FromSql<'a> for Uuid { 9 | fn from_sql(_: &Type, raw: &[u8]) -> Result> { 10 | let bytes = types::uuid_from_sql(raw)?; 11 | Ok(Uuid::from_bytes(bytes)) 12 | } 13 | 14 | accepts!(UUID); 15 | } 16 | 17 | impl ToSql for Uuid { 18 | fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result> { 19 | types::uuid_to_sql(*self.as_bytes(), w); 20 | Ok(IsNull::No) 21 | } 22 | 23 | accepts!(UUID); 24 | to_sql_checked!(); 25 | } 26 | -------------------------------------------------------------------------------- /postgres-derive/src/enums.rs: -------------------------------------------------------------------------------- 1 | use syn::{Error, Fields, Ident}; 2 | 3 | use crate::overrides::Overrides; 4 | 5 | pub struct Variant { 6 | pub ident: Ident, 7 | pub name: String, 8 | } 9 | 10 | impl Variant { 11 | pub fn parse(raw: &syn::Variant) -> Result { 12 | match raw.fields { 13 | Fields::Unit => {} 14 | _ => { 15 | return Err(Error::new_spanned( 16 | raw, 17 | "non-C-like enums are not supported", 18 | )) 19 | } 20 | } 21 | 22 | let overrides = Overrides::extract(&raw.attrs)?; 23 | Ok(Variant { 24 | ident: raw.ident.clone(), 25 | name: overrides.name.unwrap_or_else(|| raw.ident.to_string()), 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tokio-postgres/tests/test/types/bit_vec_06.rs: -------------------------------------------------------------------------------- 1 | use bit_vec_06::BitVec; 2 | 3 | use crate::types::test_type; 4 | 5 | #[tokio::test] 6 | async fn test_bit_params() { 7 | let mut bv = BitVec::from_bytes(&[0b0110_1001, 0b0000_0111]); 8 | bv.pop(); 9 | bv.pop(); 10 | test_type( 11 | "BIT(14)", 12 | &[(Some(bv), "B'01101001000001'"), (None, "NULL")], 13 | ) 14 | .await 15 | } 16 | 17 | #[tokio::test] 18 | async fn test_varbit_params() { 19 | let mut bv = BitVec::from_bytes(&[0b0110_1001, 0b0000_0111]); 20 | bv.pop(); 21 | bv.pop(); 22 | test_type( 23 | "VARBIT", 24 | &[ 25 | (Some(bv), "B'01101001000001'"), 26 | (Some(BitVec::from_bytes(&[])), "B''"), 27 | (None, "NULL"), 28 | ], 29 | ) 30 | .await 31 | } 32 | -------------------------------------------------------------------------------- /codegen/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all)] 2 | #![allow(clippy::write_with_newline)] 3 | 4 | extern crate linked_hash_map; 5 | extern crate marksman_escape; 6 | extern crate phf_codegen; 7 | extern crate regex; 8 | 9 | mod sqlstate; 10 | mod type_gen; 11 | 12 | fn main() { 13 | sqlstate::build(); 14 | type_gen::build(); 15 | } 16 | 17 | fn snake_to_camel(s: &str) -> String { 18 | let mut out = String::new(); 19 | 20 | let mut upper = true; 21 | for ch in s.chars() { 22 | if ch == '_' { 23 | upper = true; 24 | } else { 25 | let ch = if upper { 26 | upper = false; 27 | ch.to_ascii_uppercase() 28 | } else { 29 | ch 30 | }; 31 | out.push(ch); 32 | } 33 | } 34 | 35 | out 36 | } 37 | -------------------------------------------------------------------------------- /postgres-types/src/eui48_04.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use eui48_04::MacAddress; 3 | use postgres_protocol::types; 4 | use std::error::Error; 5 | 6 | use crate::{FromSql, IsNull, ToSql, Type}; 7 | 8 | impl<'a> FromSql<'a> for MacAddress { 9 | fn from_sql(_: &Type, raw: &[u8]) -> Result> { 10 | let bytes = types::macaddr_from_sql(raw)?; 11 | Ok(MacAddress::new(bytes)) 12 | } 13 | 14 | accepts!(MACADDR); 15 | } 16 | 17 | impl ToSql for MacAddress { 18 | fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result> { 19 | let mut bytes = [0; 6]; 20 | bytes.copy_from_slice(self.as_bytes()); 21 | types::macaddr_to_sql(bytes, w); 22 | Ok(IsNull::No) 23 | } 24 | 25 | accepts!(MACADDR); 26 | to_sql_checked!(); 27 | } 28 | -------------------------------------------------------------------------------- /postgres-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An internal crate for `postgres-types`. 2 | 3 | #![recursion_limit = "256"] 4 | extern crate proc_macro; 5 | 6 | use proc_macro::TokenStream; 7 | 8 | mod accepts; 9 | mod composites; 10 | mod enums; 11 | mod fromsql; 12 | mod overrides; 13 | mod tosql; 14 | 15 | #[proc_macro_derive(ToSql, attributes(postgres))] 16 | pub fn derive_tosql(input: TokenStream) -> TokenStream { 17 | let input = syn::parse(input).unwrap(); 18 | tosql::expand_derive_tosql(input) 19 | .unwrap_or_else(|e| e.to_compile_error()) 20 | .into() 21 | } 22 | 23 | #[proc_macro_derive(FromSql, attributes(postgres))] 24 | pub fn derive_fromsql(input: TokenStream) -> TokenStream { 25 | let input = syn::parse(input).unwrap(); 26 | fromsql::expand_derive_fromsql(input) 27 | .unwrap_or_else(|e| e.to_compile_error()) 28 | .into() 29 | } 30 | -------------------------------------------------------------------------------- /postgres-openssl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postgres-openssl" 3 | version = "0.3.0" 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "TLS support for tokio-postgres via openssl" 8 | repository = "https://github.com/sfackler/rust-postgres" 9 | readme = "../README.md" 10 | 11 | [badges] 12 | circle-ci = { repository = "sfackler/rust-postgres" } 13 | 14 | [features] 15 | default = ["runtime"] 16 | runtime = ["tokio-postgres/runtime"] 17 | 18 | [dependencies] 19 | bytes = "0.5" 20 | futures = "0.3" 21 | openssl = "0.10" 22 | tokio = "0.2" 23 | tokio-openssl = "0.4" 24 | tokio-postgres = { version = "0.5.0", path = "../tokio-postgres", default-features = false } 25 | 26 | [dev-dependencies] 27 | tokio = { version = "0.2", features = ["full"] } 28 | postgres = { version = "0.17.0", path = "../postgres" } 29 | -------------------------------------------------------------------------------- /postgres-native-tls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postgres-native-tls" 3 | version = "0.3.0" 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "TLS support for tokio-postgres via native-tls" 8 | repository = "https://github.com/sfackler/rust-postgres" 9 | readme = "../README.md" 10 | 11 | [badges] 12 | circle-ci = { repository = "sfackler/rust-postgres" } 13 | 14 | [features] 15 | default = ["runtime"] 16 | runtime = ["tokio-postgres/runtime"] 17 | 18 | [dependencies] 19 | bytes = "0.5" 20 | futures = "0.3" 21 | native-tls = "0.2" 22 | tokio = "0.2" 23 | tokio-tls = "0.3" 24 | tokio-postgres = { version = "0.5.0", path = "../tokio-postgres", default-features = false } 25 | 26 | [dev-dependencies] 27 | tokio = { version = "0.2", features = ["full"] } 28 | postgres = { version = "0.17.0", path = "../postgres" } 29 | -------------------------------------------------------------------------------- /postgres/src/row_iter.rs: -------------------------------------------------------------------------------- 1 | use crate::Rt; 2 | use fallible_iterator::FallibleIterator; 3 | use futures::StreamExt; 4 | use std::pin::Pin; 5 | use tokio_postgres::{Error, Row, RowStream}; 6 | 7 | /// The iterator returned by `query_raw`. 8 | pub struct RowIter<'a> { 9 | runtime: Rt<'a>, 10 | it: Pin>, 11 | } 12 | 13 | // no-op impl to extend the borrow until drop 14 | impl Drop for RowIter<'_> { 15 | fn drop(&mut self) {} 16 | } 17 | 18 | impl<'a> RowIter<'a> { 19 | pub(crate) fn new(runtime: Rt<'a>, stream: RowStream) -> RowIter<'a> { 20 | RowIter { 21 | runtime, 22 | it: Box::pin(stream), 23 | } 24 | } 25 | } 26 | 27 | impl FallibleIterator for RowIter<'_> { 28 | type Item = Row; 29 | type Error = Error; 30 | 31 | fn next(&mut self) -> Result, Error> { 32 | self.runtime.block_on(self.it.next()).transpose() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /postgres-types/src/bit_vec_06.rs: -------------------------------------------------------------------------------- 1 | use bit_vec_06::BitVec; 2 | use bytes::BytesMut; 3 | use postgres_protocol::types; 4 | use std::error::Error; 5 | 6 | use crate::{FromSql, IsNull, ToSql, Type}; 7 | 8 | impl<'a> FromSql<'a> for BitVec { 9 | fn from_sql(_: &Type, raw: &[u8]) -> Result> { 10 | let varbit = types::varbit_from_sql(raw)?; 11 | let mut bitvec = BitVec::from_bytes(varbit.bytes()); 12 | while bitvec.len() > varbit.len() { 13 | bitvec.pop(); 14 | } 15 | 16 | Ok(bitvec) 17 | } 18 | 19 | accepts!(BIT, VARBIT); 20 | } 21 | 22 | impl ToSql for BitVec { 23 | fn to_sql(&self, _: &Type, out: &mut BytesMut) -> Result> { 24 | types::varbit_to_sql(self.len(), self.to_bytes().into_iter(), out)?; 25 | Ok(IsNull::No) 26 | } 27 | 28 | accepts!(BIT, VARBIT); 29 | to_sql_checked!(); 30 | } 31 | -------------------------------------------------------------------------------- /postgres-derive-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use postgres::Client; 4 | use postgres_types::{FromSqlOwned, ToSql}; 5 | use std::fmt; 6 | 7 | mod composites; 8 | mod domains; 9 | mod enums; 10 | 11 | pub fn test_type(conn: &mut Client, sql_type: &str, checks: &[(T, S)]) 12 | where 13 | T: PartialEq + FromSqlOwned + ToSql + Sync, 14 | S: fmt::Display, 15 | { 16 | for &(ref val, ref repr) in checks.iter() { 17 | let stmt = conn 18 | .prepare(&*format!("SELECT {}::{}", *repr, sql_type)) 19 | .unwrap(); 20 | let result = conn.query_one(&stmt, &[]).unwrap().get(0); 21 | assert_eq!(val, &result); 22 | 23 | let stmt = conn.prepare(&*format!("SELECT $1::{}", sql_type)).unwrap(); 24 | let result = conn.query_one(&stmt, &[val]).unwrap().get(0); 25 | assert_eq!(val, &result); 26 | } 27 | } 28 | 29 | #[test] 30 | fn compile_fail() { 31 | trybuild::TestCases::new().compile_fail("src/compile-fail/*.rs"); 32 | } 33 | -------------------------------------------------------------------------------- /postgres-types/src/private.rs: -------------------------------------------------------------------------------- 1 | use crate::{FromSql, Type}; 2 | pub use bytes::{BytesMut, BytesVec}; 3 | use std::error::Error; 4 | 5 | pub fn read_be_i32(buf: &mut &[u8]) -> Result> { 6 | if buf.len() < 4 { 7 | return Err("invalid buffer size".into()); 8 | } 9 | let mut bytes = [0; 4]; 10 | bytes.copy_from_slice(&buf[..4]); 11 | *buf = &buf[4..]; 12 | Ok(i32::from_be_bytes(bytes)) 13 | } 14 | 15 | pub fn read_value<'a, T>( 16 | type_: &Type, 17 | buf: &mut &'a [u8], 18 | ) -> Result> 19 | where 20 | T: FromSql<'a>, 21 | { 22 | let len = read_be_i32(buf)?; 23 | let value = if len < 0 { 24 | None 25 | } else { 26 | if len as usize > buf.len() { 27 | return Err("invalid buffer size".into()); 28 | } 29 | let (head, tail) = buf.split_at(len as usize); 30 | *buf = tail; 31 | Some(head) 32 | }; 33 | T::from_sql_nullable(type_, value) 34 | } 35 | -------------------------------------------------------------------------------- /postgres-protocol/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.5.0 - 2019-12-23 4 | 5 | ### Changed 6 | 7 | * `frontend::Message` is now a true non-exhaustive enum. 8 | 9 | ## v0.5.0-alpha.2 - 2019-11-27 10 | 11 | ### Changed 12 | 13 | * Upgraded `bytes` to 0.5. 14 | 15 | ## v0.5.0-alpha.1 - 2019-10-14 16 | 17 | ### Changed 18 | 19 | * Frontend messages and types now serialize to `BytesMut` rather than `Vec`. 20 | 21 | ## v0.4.1 - 2019-06-29 22 | 23 | ### Added 24 | 25 | * Added `backend::Framed` to minimally parse the structure of backend messages. 26 | 27 | ## v0.4.0 - 2019-03-05 28 | 29 | ### Added 30 | 31 | * Added channel binding support to SCRAM authentication API. 32 | 33 | ### Changed 34 | 35 | * Passwords are no longer required to be UTF8 strings. 36 | * `types::array_to_sql` now automatically computes the required flags and no longer takes a has_nulls parameter. 37 | 38 | ## Older 39 | 40 | Look at the [release tags] for information about older releases. 41 | 42 | [release tags]: https://github.com/sfackler/rust-postgres/releases 43 | -------------------------------------------------------------------------------- /tokio-postgres/src/bind.rs: -------------------------------------------------------------------------------- 1 | use crate::client::InnerClient; 2 | use crate::codec::FrontendMessage; 3 | use crate::types::ToSql; 4 | use crate::{query, Error, Portal, Statement}; 5 | use postgres_protocol::message::backend::Message; 6 | use postgres_protocol::message::frontend; 7 | use std::rc::Rc; 8 | use std::sync::atomic::{AtomicUsize, Ordering}; 9 | 10 | static NEXT_ID: AtomicUsize = AtomicUsize::new(0); 11 | 12 | pub async fn bind( 13 | client: &Rc, 14 | statement: Statement, 15 | params: &[&(dyn ToSql)], 16 | ) -> Result { 17 | let name = format!("p{}", NEXT_ID.fetch_add(1, Ordering::SeqCst)); 18 | let buf = client.with_buf(|buf| { 19 | query::encode_bind(&statement, params, &name, buf)?; 20 | frontend::sync(buf); 21 | Ok::<_, Error>(buf.split().freeze()) 22 | })?; 23 | 24 | let responses = client.send(FrontendMessage::Raw(buf))?; 25 | let msg = responses.receiver.await?; 26 | if let Message::BindComplete = msg[0] { 27 | Ok(Portal::new(client, name, statement)) 28 | } else { 29 | Err(Error::unexpected_message()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tokio-postgres/tests/test/types/serde_json_1.rs: -------------------------------------------------------------------------------- 1 | use serde_json_1::Value; 2 | 3 | use crate::types::test_type; 4 | 5 | #[tokio::test] 6 | async fn test_json_params() { 7 | test_type( 8 | "JSON", 9 | &[ 10 | ( 11 | Some(serde_json_1::from_str::("[10, 11, 12]").unwrap()), 12 | "'[10, 11, 12]'", 13 | ), 14 | ( 15 | Some(serde_json_1::from_str::("{\"f\": \"asd\"}").unwrap()), 16 | "'{\"f\": \"asd\"}'", 17 | ), 18 | (None, "NULL"), 19 | ], 20 | ) 21 | .await 22 | } 23 | 24 | #[tokio::test] 25 | async fn test_jsonb_params() { 26 | test_type( 27 | "JSONB", 28 | &[ 29 | ( 30 | Some(serde_json_1::from_str::("[10, 11, 12]").unwrap()), 31 | "'[10, 11, 12]'", 32 | ), 33 | ( 34 | Some(serde_json_1::from_str::("{\"f\": \"asd\"}").unwrap()), 35 | "'{\"f\": \"asd\"}'", 36 | ), 37 | (None, "NULL"), 38 | ], 39 | ) 40 | .await 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2017 Steven Fackler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /postgres/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Steven Fackler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /postgres-derive/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Steven Fackler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /postgres-protocol/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Steven Fackler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /postgres-types/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Steven Fackler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tokio-postgres/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Steven Fackler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /postgres-protocol/src/authentication/mod.rs: -------------------------------------------------------------------------------- 1 | //! Authentication protocol support. 2 | use md5::Context; 3 | 4 | pub mod sasl; 5 | 6 | /// Hashes authentication information in a way suitable for use in response 7 | /// to an `AuthenticationMd5Password` message. 8 | /// 9 | /// The resulting string should be sent back to the database in a 10 | /// `PasswordMessage` message. 11 | #[inline] 12 | pub fn md5_hash(username: &[u8], password: &[u8], salt: [u8; 4]) -> String { 13 | let mut context = Context::new(); 14 | context.consume(password); 15 | context.consume(username); 16 | let output = context.compute(); 17 | context = Context::new(); 18 | context.consume(format!("{:x}", output)); 19 | context.consume(&salt); 20 | format!("md5{:x}", context.compute()) 21 | } 22 | 23 | #[cfg(test)] 24 | mod test { 25 | use super::*; 26 | 27 | #[test] 28 | fn md5() { 29 | let username = b"md5_user"; 30 | let password = b"password"; 31 | let salt = [0x2a, 0x3d, 0x8f, 0xe0]; 32 | 33 | assert_eq!( 34 | md5_hash(username, password, salt), 35 | "md562af4dd09bbb41884907a838a3233294" 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /postgres-derive-test/src/compile-fail/invalid-types.stderr: -------------------------------------------------------------------------------- 1 | error: #[derive(ToSql)] may only be applied to structs, single field tuple structs, and enums 2 | --> $DIR/invalid-types.rs:4:1 3 | | 4 | 4 | struct ToSqlUnit; 5 | | ^^^^^^^^^^^^^^^^^ 6 | 7 | error: #[derive(FromSql)] may only be applied to structs, single field tuple structs, and enums 8 | --> $DIR/invalid-types.rs:7:1 9 | | 10 | 7 | struct FromSqlUnit; 11 | | ^^^^^^^^^^^^^^^^^^^ 12 | 13 | error: #[derive(ToSql)] may only be applied to structs, single field tuple structs, and enums 14 | --> $DIR/invalid-types.rs:10:1 15 | | 16 | 10 | struct ToSqlTuple(i32, i32); 17 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | error: #[derive(FromSql)] may only be applied to structs, single field tuple structs, and enums 20 | --> $DIR/invalid-types.rs:13:1 21 | | 22 | 13 | struct FromSqlTuple(i32, i32); 23 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 24 | 25 | error: non-C-like enums are not supported 26 | --> $DIR/invalid-types.rs:17:5 27 | | 28 | 17 | Foo(i32), 29 | | ^^^^^^^^ 30 | 31 | error: non-C-like enums are not supported 32 | --> $DIR/invalid-types.rs:22:5 33 | | 34 | 22 | Foo(i32), 35 | | ^^^^^^^^ 36 | -------------------------------------------------------------------------------- /postgres/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postgres" 3 | version = "0.17.0" 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "A native, synchronous PostgreSQL client" 8 | repository = "https://github.com/sfackler/rust-postgres" 9 | readme = "../README.md" 10 | keywords = ["database", "postgres", "postgresql", "sql"] 11 | categories = ["database"] 12 | 13 | [[bench]] 14 | name = "bench" 15 | harness = false 16 | 17 | [package.metadata.docs.rs] 18 | all-features = true 19 | 20 | [badges] 21 | circle-ci = { repository = "sfackler/rust-postgres" } 22 | 23 | [features] 24 | with-bit-vec-0_6 = ["tokio-postgres/with-bit-vec-0_6"] 25 | with-chrono-0_4 = ["tokio-postgres/with-chrono-0_4"] 26 | with-eui48-0_4 = ["tokio-postgres/with-eui48-0_4"] 27 | with-geo-types-0_4 = ["tokio-postgres/with-geo-types-0_4"] 28 | with-serde_json-1 = ["tokio-postgres/with-serde_json-1"] 29 | with-uuid-0_8 = ["tokio-postgres/with-uuid-0_8"] 30 | 31 | [dependencies] 32 | bytes = "1.0" 33 | fallible-iterator = "0.2" 34 | futures = "0.3" 35 | tokio-postgres = { version = "0.5.0", path = "../tokio-postgres" } 36 | 37 | tokio = { version = "1" } 38 | log = "0.4" 39 | 40 | [dev-dependencies] 41 | criterion = "0.3" 42 | -------------------------------------------------------------------------------- /tokio-postgres/src/cancel_query.rs: -------------------------------------------------------------------------------- 1 | use crate::client::SocketConfig; 2 | use crate::config::{Host, SslMode}; 3 | use crate::tls::MakeTlsConnect; 4 | use crate::{cancel_query_raw, connect_socket, Error, Socket}; 5 | use std::io; 6 | 7 | pub(crate) async fn cancel_query( 8 | config: Option, 9 | ssl_mode: SslMode, 10 | process_id: i32, 11 | secret_key: i32, 12 | ) -> Result<(), Error> { 13 | let config = match config { 14 | Some(config) => config, 15 | None => { 16 | return Err(Error::connect(io::Error::new( 17 | io::ErrorKind::InvalidInput, 18 | "unknown host", 19 | ))) 20 | } 21 | }; 22 | 23 | let hostname = match &config.host { 24 | Host::Tcp(host) => &**host, 25 | // postgres doesn't support TLS over unix sockets, so the choice here doesn't matter 26 | #[cfg(unix)] 27 | Host::Unix(_) => "", 28 | }; 29 | 30 | let socket = connect_socket::connect_socket( 31 | &config.host, 32 | config.port, 33 | config.connect_timeout, 34 | config.keepalives, 35 | config.keepalives_idle, 36 | ) 37 | .await?; 38 | 39 | cancel_query_raw::cancel_query_raw(socket, ssl_mode, process_id, secret_key).await 40 | } 41 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | restore_registry: &RESTORE_REGISTRY 2 | restore_cache: 3 | key: registry 4 | save_registry: &SAVE_REGISTRY 5 | save_cache: 6 | key: registry-{{ .BuildNum }} 7 | paths: 8 | - /usr/local/cargo/registry/index 9 | deps_key: &DEPS_KEY 10 | key: deps-{{ checksum "~/rust-version" }}-{{ checksum "Cargo.lock" }} 11 | restore_deps: &RESTORE_DEPS 12 | restore_cache: 13 | <<: *DEPS_KEY 14 | save_deps: &SAVE_DEPS 15 | save_cache: 16 | <<: *DEPS_KEY 17 | paths: 18 | - target 19 | - /usr/local/cargo/registry/cache 20 | 21 | version: 2 22 | jobs: 23 | build: 24 | docker: 25 | - image: rust:1.40.0 26 | environment: 27 | RUSTFLAGS: -D warnings 28 | - image: sfackler/rust-postgres-test:6 29 | steps: 30 | - checkout 31 | - run: rustup component add rustfmt clippy 32 | - *RESTORE_REGISTRY 33 | - run: cargo generate-lockfile 34 | - *SAVE_REGISTRY 35 | - run: rustc --version > ~/rust-version 36 | - *RESTORE_DEPS 37 | - run: cargo fmt --all -- --check 38 | - run: cargo clippy --all --all-targets --all-features 39 | - run: cargo test --all 40 | - run: cargo test --manifest-path tokio-postgres/Cargo.toml --no-default-features 41 | - run: cargo test --manifest-path tokio-postgres/Cargo.toml --all-features 42 | - *SAVE_DEPS 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust-Postgres 2 | [![CircleCI](https://circleci.com/gh/sfackler/rust-postgres.svg?style=shield)](https://circleci.com/gh/sfackler/rust-postgres) 3 | 4 | PostgreSQL support for Rust. 5 | 6 | ## postgres [![Latest Version](https://img.shields.io/crates/v/postgres.svg)](https://crates.io/crates/postgres) 7 | 8 | [Documentation](https://docs.rs/postgres) 9 | 10 | A native, synchronous PostgreSQL client. 11 | 12 | ## tokio-postgres [![Latest Version](https://img.shields.io/crates/v/tokio-postgres.svg)](https://crates.io/crates/tokio-postgres) 13 | 14 | [Documentation](https://docs.rs/tokio-postgres) 15 | 16 | A native, asynchronous PostgreSQL client. 17 | 18 | ## postgres-types [![Latest Version](https://img.shields.io/crates/v/postgres-types.svg)](https://crates.io/crates/postgres-types) 19 | 20 | [Documentation](https://docs.rs/postgres-types) 21 | 22 | Conversions between Rust and Postgres types. 23 | 24 | ## postgres-native-tls [![Latest Version](https://img.shields.io/crates/v/postgres-native-tls.svg)](https://crates.io/crates/postgres-native-tls) 25 | 26 | [Documentation](https://docs.rs/postgres-native-tls) 27 | 28 | TLS support for postgres and tokio-postgres via native-tls. 29 | 30 | ## postgres-openssl [![Latest Version](https://img.shields.io/crates/v/postgres-openssl.svg)](https://crates.io/crates/postgres-openssl) 31 | 32 | [Documentation](https://docs.rs/postgres-openssl) 33 | 34 | TLS support for postgres and tokio-postgres via openssl. 35 | -------------------------------------------------------------------------------- /codegen/src/pg_range.dat: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------- 2 | # 3 | # pg_range.dat 4 | # Initial contents of the pg_range system catalog. 5 | # 6 | # Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group 7 | # Portions Copyright (c) 1994, Regents of the University of California 8 | # 9 | # src/include/catalog/pg_range.dat 10 | # 11 | #---------------------------------------------------------------------- 12 | 13 | [ 14 | 15 | { rngtypid => 'int4range', rngsubtype => 'int4', rngsubopc => 'btree/int4_ops', 16 | rngcanonical => 'int4range_canonical', rngsubdiff => 'int4range_subdiff' }, 17 | { rngtypid => 'numrange', rngsubtype => 'numeric', 18 | rngsubopc => 'btree/numeric_ops', rngcanonical => '-', 19 | rngsubdiff => 'numrange_subdiff' }, 20 | { rngtypid => 'tsrange', rngsubtype => 'timestamp', 21 | rngsubopc => 'btree/timestamp_ops', rngcanonical => '-', 22 | rngsubdiff => 'tsrange_subdiff' }, 23 | { rngtypid => 'tstzrange', rngsubtype => 'timestamptz', 24 | rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-', 25 | rngsubdiff => 'tstzrange_subdiff' }, 26 | { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops', 27 | rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' }, 28 | { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops', 29 | rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' }, 30 | 31 | ] 32 | -------------------------------------------------------------------------------- /tokio-postgres/src/portal.rs: -------------------------------------------------------------------------------- 1 | use crate::client::InnerClient; 2 | use crate::codec::FrontendMessage; 3 | use crate::Statement; 4 | use postgres_protocol::message::frontend; 5 | use std::rc::{Rc, Weak}; 6 | 7 | struct Inner { 8 | client: Weak, 9 | name: String, 10 | statement: Statement, 11 | } 12 | 13 | impl Drop for Inner { 14 | fn drop(&mut self) { 15 | if let Some(client) = self.client.upgrade() { 16 | let buf = client.with_buf(|buf| { 17 | frontend::close(b'P', &self.name, buf).unwrap(); 18 | frontend::sync(buf); 19 | buf.split().freeze() 20 | }); 21 | let _ = client.send(FrontendMessage::Raw(buf)); 22 | } 23 | } 24 | } 25 | 26 | /// A portal. 27 | /// 28 | /// Portals can only be used with the connection that created them, and only exist for the duration of the transaction 29 | /// in which they were created. 30 | #[derive(Clone)] 31 | pub struct Portal(Rc); 32 | 33 | impl Portal { 34 | pub(crate) fn new(client: &Rc, name: String, statement: Statement) -> Portal { 35 | Portal(Rc::new(Inner { 36 | client: Rc::downgrade(client), 37 | name, 38 | statement, 39 | })) 40 | } 41 | 42 | pub(crate) fn name(&self) -> &str { 43 | &self.0.name 44 | } 45 | 46 | pub(crate) fn statement(&self) -> &Statement { 47 | &self.0.statement 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /postgres-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postgres-types" 3 | version = "0.1.0" 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "Conversions between Rust and Postgres values" 8 | repository = "https://github.com/sfackler/rust-postgres" 9 | readme = "../README.md" 10 | keywords = ["database", "postgres", "postgresql", "sql"] 11 | categories = ["database"] 12 | 13 | [features] 14 | derive = ["postgres-derive"] 15 | with-bit-vec-0_6 = ["bit-vec-06"] 16 | with-chrono-0_4 = ["chrono-04"] 17 | with-eui48-0_4 = ["eui48-04"] 18 | with-geo-types-0_4 = ["geo-types-04"] 19 | with-serde_json-1 = ["serde-1", "serde_json-1"] 20 | with-uuid-0_8 = ["uuid-08"] 21 | 22 | [dependencies] 23 | bytes = { version = "0.1.1", package = "ntex-bytes" } 24 | fallible-iterator = "0.2" 25 | postgres-protocol = { version = "0.5.0", path = "../postgres-protocol" } 26 | postgres-derive = { version = "0.4.0", optional = true, path = "../postgres-derive" } 27 | 28 | bit-vec-06 = { version = "0.6", package = "bit-vec", optional = true } 29 | chrono-04 = { version = "0.4", package = "chrono", optional = true } 30 | eui48-04 = { version = "0.4", package = "eui48", optional = true } 31 | geo-types-04 = { version = "0.4", package = "geo-types", optional = true } 32 | serde-1 = { version = "1.0", package = "serde", optional = true } 33 | serde_json-1 = { version = "1.0", package = "serde_json", optional = true } 34 | uuid-08 = { version = "0.8", package = "uuid", optional = true } 35 | -------------------------------------------------------------------------------- /test/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID9DCCAtygAwIBAgIJAIYfg4EQ2pVAMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNjA2MjgyMjQw 5 | NDFaFw0yNjA2MjYyMjQwNDFaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21l 6 | LVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV 7 | BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJZS 8 | LV8K4+tjJMSlZc9hYpiZONllnNyvNkZ9rwJes9/M0MUgo7fblsF1k8eWDnRolHAP 9 | iVAK7mcaje73X6YSaGLU63K6U+KwvxbAjCJgcZI9XMXWE6veEhZ/W0AUWZO0VMeC 10 | qJbfv4dHdz6TSG+A28kPANDFbzVcS6UUVcHOskD/jZETMoB0fptz/H8RxLZBwlcu 11 | xzkWwErzfH0ZURDBwy9oZGnV9ATTO9gw6Pg1oTwPBhell7SJdYFOhj+qUmxHjBw9 12 | 3/ro+3/Yko75Kx6zrdpy1EPUJ3r9p4ZlNP3TiMHkNe/xa5S/Y2A1FBTTkco0Z5V1 13 | 1KD+QTvy3RAAKk9gNKcCAwEAAaOBvjCBuzAdBgNVHQ4EFgQUEcuoFxzUZ4VV9VPv 14 | 5frDyIuFA5cwgYsGA1UdIwSBgzCBgIAUEcuoFxzUZ4VV9VPv5frDyIuFA5ehXaRb 15 | MFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJ 16 | bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdIIJAIYf 17 | g4EQ2pVAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHwMzmXdtz3R 18 | 83HIdRQic40bJQf9ucSwY5ArkttPhC8ewQGyiGexm1Tvx9YA/qT2rscKPHXCPYcP 19 | IUE+nJTc8lQb8wPnFwGdHUsJfCvurxE4Yv4Oi74+q1enhHBGsvhFdFY5jTYD9unM 20 | zBEn+ZHX3PlKhe3wMub4khBTbPLK+n/laQWuZNsa+kj7BynkAg8W/6RK0Z0cJzzw 21 | aiVP0bSvatAAcSwkEfKEv5xExjWqoewjSlQLEZYIjJhXdtx/8AMnrcyxrFvKALUQ 22 | 9M15FXvlPOB7ez14xIXQBKvvLwXvteHF6kYbzg/Bl1Q2GE9usclPa4UvTpnLv6gq 23 | NmFaAhoxnXA= 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /tokio-postgres/src/to_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::to_statement::private::{Sealed, ToStatementType}; 2 | use crate::Statement; 3 | 4 | mod private { 5 | use crate::{Client, Error, Statement}; 6 | 7 | pub trait Sealed {} 8 | 9 | pub enum ToStatementType<'a> { 10 | Statement(&'a Statement), 11 | Query(&'a str), 12 | } 13 | 14 | impl<'a> ToStatementType<'a> { 15 | pub async fn into_statement(self, client: &Client) -> Result { 16 | match self { 17 | ToStatementType::Statement(s) => Ok(s.clone()), 18 | ToStatementType::Query(s) => client.prepare(s).await, 19 | } 20 | } 21 | } 22 | } 23 | 24 | /// A trait abstracting over prepared and unprepared statements. 25 | /// 26 | /// Many methods are generic over this bound, so that they support both a raw query string as well as a statement which 27 | /// was prepared previously. 28 | /// 29 | /// This trait is "sealed" and cannot be implemented by anything outside this crate. 30 | pub trait ToStatement: Sealed { 31 | #[doc(hidden)] 32 | fn __convert(&self) -> ToStatementType<'_>; 33 | } 34 | 35 | impl ToStatement for Statement { 36 | fn __convert(&self) -> ToStatementType<'_> { 37 | ToStatementType::Statement(self) 38 | } 39 | } 40 | 41 | impl Sealed for Statement {} 42 | 43 | impl ToStatement for str { 44 | fn __convert(&self) -> ToStatementType<'_> { 45 | ToStatementType::Query(self) 46 | } 47 | } 48 | 49 | impl Sealed for str {} 50 | -------------------------------------------------------------------------------- /tokio-postgres/src/connect_tls.rs: -------------------------------------------------------------------------------- 1 | use crate::config::SslMode; 2 | use crate::maybe_tls_stream::MaybeTlsStream; 3 | use crate::tls::private::ForcePrivateApi; 4 | use crate::tls::TlsConnect; 5 | use crate::Error; 6 | use bytes::BytesMut; 7 | use postgres_protocol::message::frontend; 8 | use tokio::io::{AsyncRead, AsyncWrite}; 9 | 10 | pub async fn connect_tls( 11 | mut stream: S, 12 | mode: SslMode, 13 | tls: T, 14 | ) -> Result, Error> 15 | where 16 | S: AsyncRead + AsyncWrite + Unpin, 17 | T: TlsConnect, 18 | { 19 | match mode { 20 | SslMode::Disable => return Ok(MaybeTlsStream::Raw(stream)), 21 | SslMode::Prefer if !tls.can_connect(ForcePrivateApi) => { 22 | return Ok(MaybeTlsStream::Raw(stream)) 23 | } 24 | SslMode::Prefer | SslMode::Require => {} 25 | } 26 | 27 | let mut buf = BytesMut::new(); 28 | frontend::ssl_request(&mut buf); 29 | stream.write_all(&buf).await.map_err(Error::io)?; 30 | 31 | let mut buf = [0]; 32 | stream.read_exact(&mut buf).await.map_err(Error::io)?; 33 | 34 | if buf[0] != b'S' { 35 | if SslMode::Require == mode { 36 | return Err(Error::tls("server does not support TLS".into())); 37 | } else { 38 | return Ok(MaybeTlsStream::Raw(stream)); 39 | } 40 | } 41 | 42 | let stream = tls 43 | .connect(stream) 44 | .await 45 | .map_err(|e| Error::tls(e.into()))?; 46 | 47 | Ok(MaybeTlsStream::Tls(stream)) 48 | } 49 | -------------------------------------------------------------------------------- /postgres/src/copy_out_reader.rs: -------------------------------------------------------------------------------- 1 | use crate::lazy_pin::LazyPin; 2 | use crate::Rt; 3 | use bytes::{Buf, Bytes}; 4 | use futures::StreamExt; 5 | use std::io::{self, BufRead, Read}; 6 | use tokio_postgres::CopyOutStream; 7 | 8 | /// The reader returned by the `copy_out` method. 9 | pub struct CopyOutReader<'a> { 10 | pub(crate) runtime: Rt<'a>, 11 | pub(crate) stream: LazyPin, 12 | cur: Bytes, 13 | } 14 | 15 | impl<'a> CopyOutReader<'a> { 16 | pub(crate) fn new(runtime: Rt<'a>, stream: CopyOutStream) -> CopyOutReader<'a> { 17 | CopyOutReader { 18 | runtime, 19 | stream: LazyPin::new(stream), 20 | cur: Bytes::new(), 21 | } 22 | } 23 | } 24 | 25 | impl Read for CopyOutReader<'_> { 26 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 27 | let b = self.fill_buf()?; 28 | let len = usize::min(buf.len(), b.len()); 29 | buf[..len].copy_from_slice(&b[..len]); 30 | self.consume(len); 31 | Ok(len) 32 | } 33 | } 34 | 35 | impl BufRead for CopyOutReader<'_> { 36 | fn fill_buf(&mut self) -> io::Result<&[u8]> { 37 | if !self.cur.has_remaining() { 38 | match self.runtime.block_on(self.stream.pinned().next()) { 39 | Some(Ok(cur)) => self.cur = cur, 40 | Some(Err(e)) => return Err(io::Error::new(io::ErrorKind::Other, e)), 41 | None => {} 42 | }; 43 | } 44 | 45 | Ok(self.cur.bytes()) 46 | } 47 | 48 | fn consume(&mut self, amt: usize) { 49 | self.cur.advance(amt); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tokio-postgres/tests/test/types/geo_010.rs: -------------------------------------------------------------------------------- 1 | use geo_010::{Coordinate, LineString, Point, Rect}; 2 | 3 | use crate::types::test_type; 4 | 5 | #[tokio::test] 6 | async fn test_point_params() { 7 | test_type( 8 | "POINT", 9 | &[ 10 | (Some(Point::new(0.0, 0.0)), "POINT(0, 0)"), 11 | (Some(Point::new(-3.14, 1.618)), "POINT(-3.14, 1.618)"), 12 | (None, "NULL"), 13 | ], 14 | ) 15 | .await; 16 | } 17 | 18 | #[tokio::test] 19 | async fn test_box_params() { 20 | test_type( 21 | "BOX", 22 | &[ 23 | ( 24 | Some(Rect { 25 | min: Coordinate { x: -3.14, y: 1.618 }, 26 | max: Coordinate { 27 | x: 160.0, 28 | y: 69701.5615, 29 | }, 30 | }), 31 | "BOX(POINT(160.0, 69701.5615), POINT(-3.14, 1.618))", 32 | ), 33 | (None, "NULL"), 34 | ], 35 | ) 36 | .await; 37 | } 38 | 39 | #[tokio::test] 40 | async fn test_path_params() { 41 | let points = vec![ 42 | Coordinate { x: 0., y: 0. }, 43 | Coordinate { x: -3.14, y: 1.618 }, 44 | Coordinate { 45 | x: 160.0, 46 | y: 69701.5615, 47 | }, 48 | ]; 49 | test_type( 50 | "PATH", 51 | &[ 52 | ( 53 | Some(LineString(points)), 54 | "path '((0, 0), (-3.14, 1.618), (160.0, 69701.5615))'", 55 | ), 56 | (None, "NULL"), 57 | ], 58 | ) 59 | .await; 60 | } 61 | -------------------------------------------------------------------------------- /postgres-derive/src/overrides.rs: -------------------------------------------------------------------------------- 1 | use syn::{Attribute, Error, Lit, Meta, NestedMeta}; 2 | 3 | pub struct Overrides { 4 | pub name: Option, 5 | } 6 | 7 | impl Overrides { 8 | pub fn extract(attrs: &[Attribute]) -> Result { 9 | let mut overrides = Overrides { name: None }; 10 | 11 | for attr in attrs { 12 | let attr = match attr.parse_meta() { 13 | Ok(meta) => meta, 14 | Err(_) => continue, 15 | }; 16 | 17 | if !attr.path().is_ident("postgres") { 18 | continue; 19 | } 20 | 21 | let list = match attr { 22 | Meta::List(ref list) => list, 23 | bad => return Err(Error::new_spanned(bad, "expected a #[postgres(...)]")), 24 | }; 25 | 26 | for item in &list.nested { 27 | match item { 28 | NestedMeta::Meta(Meta::NameValue(meta)) => { 29 | if !meta.path.is_ident("name") { 30 | return Err(Error::new_spanned(&meta.path, "unknown override")); 31 | } 32 | 33 | let value = match &meta.lit { 34 | Lit::Str(s) => s.value(), 35 | bad => { 36 | return Err(Error::new_spanned(bad, "expected a string literal")) 37 | } 38 | }; 39 | 40 | overrides.name = Some(value); 41 | } 42 | bad => return Err(Error::new_spanned(bad, "expected a name-value meta item")), 43 | } 44 | } 45 | } 46 | 47 | Ok(overrides) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /postgres/src/copy_in_writer.rs: -------------------------------------------------------------------------------- 1 | use crate::lazy_pin::LazyPin; 2 | use crate::Rt; 3 | use bytes::{Bytes, BytesMut}; 4 | use futures::SinkExt; 5 | use std::io; 6 | use std::io::Write; 7 | use tokio_postgres::{CopyInSink, Error}; 8 | 9 | /// The writer returned by the `copy_in` method. 10 | /// 11 | /// The copy *must* be explicitly completed via the `finish` method. If it is not, the copy will be aborted. 12 | pub struct CopyInWriter<'a> { 13 | pub(crate) runtime: Rt<'a>, 14 | pub(crate) sink: LazyPin>, 15 | buf: BytesMut, 16 | } 17 | 18 | impl<'a> CopyInWriter<'a> { 19 | pub(crate) fn new(runtime: Rt<'a>, sink: CopyInSink) -> CopyInWriter<'a> { 20 | CopyInWriter { 21 | runtime, 22 | sink: LazyPin::new(sink), 23 | buf: BytesMut::new(), 24 | } 25 | } 26 | 27 | /// Completes the copy, returning the number of rows written. 28 | /// 29 | /// If this is not called, the copy will be aborted. 30 | pub fn finish(mut self) -> Result { 31 | self.flush_inner()?; 32 | self.runtime.block_on(self.sink.pinned().finish()) 33 | } 34 | 35 | fn flush_inner(&mut self) -> Result<(), Error> { 36 | if self.buf.is_empty() { 37 | return Ok(()); 38 | } 39 | 40 | self.runtime 41 | .block_on(self.sink.pinned().send(self.buf.split().freeze())) 42 | } 43 | } 44 | 45 | impl Write for CopyInWriter<'_> { 46 | fn write(&mut self, buf: &[u8]) -> io::Result { 47 | if self.buf.len() > 4096 { 48 | self.flush()?; 49 | } 50 | 51 | self.buf.extend_from_slice(buf); 52 | Ok(buf.len()) 53 | } 54 | 55 | fn flush(&mut self) -> io::Result<()> { 56 | self.flush_inner() 57 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tokio-postgres/src/connect_socket.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, io, time::Duration}; 2 | 3 | use ntex::{connect, io::Io, rt, time, util::PoolId}; 4 | 5 | use crate::config::Host; 6 | use crate::{Error, Socket}; 7 | 8 | pub(crate) async fn connect_socket( 9 | host: &Host, 10 | port: u16, 11 | connect_timeout: Option, 12 | keepalives: bool, 13 | keepalives_idle: Duration, 14 | ) -> Result { 15 | PoolId::P10.set_read_params(65535, 8192); 16 | PoolId::P10.set_write_params(65535, 8192); 17 | 18 | match host { 19 | Host::Tcp(host) => { 20 | let fut = connect::connect(connect::Connect::new(host.clone()).set_port(port)); 21 | let socket = connect_with_timeout( 22 | async move { 23 | fut.await 24 | .map_err(|e| io::Error::new(io::ErrorKind::Other, "connect error")) 25 | }, 26 | connect_timeout, 27 | ) 28 | .await?; 29 | socket.set_memory_pool(PoolId::P10.pool_ref()); 30 | Ok(socket) 31 | } 32 | #[cfg(unix)] 33 | Host::Unix(path) => { 34 | panic!("not supported") 35 | } 36 | } 37 | } 38 | 39 | async fn connect_with_timeout(connect: F, timeout: Option) -> Result 40 | where 41 | F: Future>, 42 | { 43 | match timeout { 44 | Some(timeout) => match time::timeout(timeout, connect).await { 45 | Ok(Ok(socket)) => Ok(socket), 46 | Ok(Err(e)) => Err(Error::connect(e)), 47 | Err(_) => Err(Error::connect(io::Error::new( 48 | io::ErrorKind::TimedOut, 49 | "connection timed out", 50 | ))), 51 | }, 52 | None => match connect.await { 53 | Ok(socket) => Ok(socket), 54 | Err(e) => Err(Error::connect(e)), 55 | }, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tokio-postgres/src/copy_out.rs: -------------------------------------------------------------------------------- 1 | use crate::client::{InnerClient, Responses}; 2 | use crate::codec::FrontendMessage; 3 | use crate::connection::RequestMessages; 4 | use crate::{query, Error, Statement}; 5 | use bytes::Bytes; 6 | use futures::{ready, Stream}; 7 | use log::debug; 8 | use pin_project_lite::pin_project; 9 | use postgres_protocol::message::backend::Message; 10 | use std::marker::PhantomPinned; 11 | use std::pin::Pin; 12 | use std::task::{Context, Poll}; 13 | 14 | pub async fn copy_out(client: &InnerClient, statement: Statement) -> Result { 15 | debug!("executing copy out statement {}", statement.name()); 16 | 17 | let buf = query::encode(client, &statement, &[])?; 18 | let responses = start(client, buf).await?; 19 | Ok(CopyOutStream { 20 | responses, 21 | _p: PhantomPinned, 22 | }) 23 | } 24 | 25 | async fn start(client: &InnerClient, buf: Bytes) -> Result { 26 | let mut responses = client.send(RequestMessages::Single(FrontendMessage::Raw(buf)))?; 27 | 28 | match responses.next().await? { 29 | Message::BindComplete => {} 30 | _ => return Err(Error::unexpected_message()), 31 | } 32 | 33 | match responses.next().await? { 34 | Message::CopyOutResponse(_) => {} 35 | _ => return Err(Error::unexpected_message()), 36 | } 37 | 38 | Ok(responses) 39 | } 40 | 41 | pin_project! { 42 | /// A stream of `COPY ... TO STDOUT` query data. 43 | pub struct CopyOutStream { 44 | responses: Responses, 45 | #[pin] 46 | _p: PhantomPinned, 47 | } 48 | } 49 | 50 | impl Stream for CopyOutStream { 51 | type Item = Result; 52 | 53 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 54 | let this = self.project(); 55 | 56 | match ready!(this.responses.poll_next(cx)?) { 57 | Message::CopyData(body) => Poll::Ready(Some(Ok(body.into_bytes()))), 58 | Message::CopyDone => Poll::Ready(None), 59 | _ => Poll::Ready(Some(Err(Error::unexpected_message()))), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /postgres-types/src/serde_json_1.rs: -------------------------------------------------------------------------------- 1 | use crate::{FromSql, IsNull, ToSql, Type}; 2 | use bytes::buf::BufMutExt; 3 | use bytes::{BufMut, BytesMut}; 4 | use serde_1::{Deserialize, Serialize}; 5 | use serde_json_1::Value; 6 | use std::error::Error; 7 | use std::fmt::Debug; 8 | use std::io::Read; 9 | 10 | /// A wrapper type to allow arbitrary `Serialize`/`Deserialize` types to convert to Postgres JSON values. 11 | #[derive(Debug)] 12 | pub struct Json(pub T); 13 | 14 | impl<'a, T> FromSql<'a> for Json 15 | where 16 | T: Deserialize<'a>, 17 | { 18 | fn from_sql(ty: &Type, mut raw: &'a [u8]) -> Result, Box> { 19 | if *ty == Type::JSONB { 20 | let mut b = [0; 1]; 21 | raw.read_exact(&mut b)?; 22 | // We only support version 1 of the jsonb binary format 23 | if b[0] != 1 { 24 | return Err("unsupported JSONB encoding version".into()); 25 | } 26 | } 27 | serde_json_1::de::from_slice(raw) 28 | .map(Json) 29 | .map_err(Into::into) 30 | } 31 | 32 | accepts!(JSON, JSONB); 33 | } 34 | 35 | impl ToSql for Json 36 | where 37 | T: Serialize + Debug, 38 | { 39 | fn to_sql( 40 | &self, 41 | ty: &Type, 42 | out: &mut BytesMut, 43 | ) -> Result> { 44 | if *ty == Type::JSONB { 45 | out.put_u8(1); 46 | } 47 | serde_json_1::ser::to_writer(out.writer(), &self.0)?; 48 | Ok(IsNull::No) 49 | } 50 | 51 | accepts!(JSON, JSONB); 52 | to_sql_checked!(); 53 | } 54 | 55 | impl<'a> FromSql<'a> for Value { 56 | fn from_sql(ty: &Type, raw: &[u8]) -> Result> { 57 | Json::::from_sql(ty, raw).map(|json| json.0) 58 | } 59 | 60 | accepts!(JSON, JSONB); 61 | } 62 | 63 | impl ToSql for Value { 64 | fn to_sql( 65 | &self, 66 | ty: &Type, 67 | out: &mut BytesMut, 68 | ) -> Result> { 69 | Json(self).to_sql(ty, out) 70 | } 71 | 72 | accepts!(JSON, JSONB); 73 | to_sql_checked!(); 74 | } 75 | -------------------------------------------------------------------------------- /tokio-postgres/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-postgres" 3 | version = "0.5.1" 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "A native, asynchronous PostgreSQL client" 8 | repository = "https://github.com/sfackler/rust-postgres" 9 | readme = "../README.md" 10 | keywords = ["database", "postgres", "postgresql", "sql", "async"] 11 | categories = ["database"] 12 | 13 | [lib] 14 | test = false 15 | 16 | [[bench]] 17 | name = "bench" 18 | harness = false 19 | 20 | [package.metadata.docs.rs] 21 | all-features = true 22 | 23 | [badges] 24 | circle-ci = { repository = "sfackler/rust-postgres" } 25 | 26 | [features] 27 | default = ["runtime"] 28 | runtime = ["tokio/net", "tokio/time"] 29 | 30 | with-bit-vec-0_6 = ["postgres-types/with-bit-vec-0_6"] 31 | with-chrono-0_4 = ["postgres-types/with-chrono-0_4"] 32 | with-eui48-0_4 = ["postgres-types/with-eui48-0_4"] 33 | with-geo-types-0_4 = ["postgres-types/with-geo-types-0_4"] 34 | with-serde_json-1 = ["postgres-types/with-serde_json-1"] 35 | with-uuid-0_8 = ["postgres-types/with-uuid-0_8"] 36 | 37 | [dependencies] 38 | ntex = "0.7.2" 39 | # bytes = { version = "0.1.8", package = "ntex-bytes" } 40 | byteorder = "1.4" 41 | fallible-iterator = "0.2" 42 | futures = "0.3.13" 43 | log = "0.4" 44 | percent-encoding = "2.0" 45 | pin-project-lite = "0.2" 46 | phf = "0.8" 47 | postgres-protocol = { version = "0.5.0", path = "../postgres-protocol" } 48 | postgres-types = { version = "0.1.0", path = "../postgres-types" } 49 | tokio = { version = "1", default-features = false } 50 | 51 | [dev-dependencies] 52 | tokio = { version = "1" } 53 | ntex = { version = "0.7.2", features = ["tokio"] } 54 | env_logger = "0.10" 55 | criterion = "0.3" 56 | 57 | bit-vec-06 = { version = "0.6", package = "bit-vec" } 58 | chrono-04 = { version = "0.4", package = "chrono" } 59 | eui48-04 = { version = "0.4", package = "eui48" } 60 | geo-types-04 = { version = "0.4", package = "geo-types" } 61 | serde-1 = { version = "1.0", package = "serde" } 62 | serde_json-1 = { version = "1.0", package = "serde_json" } 63 | uuid-07 = { version = "0.7", package = "uuid" } 64 | uuid-08 = { version = "0.8", package = "uuid" } 65 | -------------------------------------------------------------------------------- /tokio-postgres/benches/bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use futures::channel::oneshot; 3 | use futures::executor; 4 | use std::sync::Arc; 5 | use std::time::Instant; 6 | use tokio::runtime::Runtime; 7 | use tokio_postgres::{Client, NoTls}; 8 | 9 | fn setup() -> (Client, Runtime) { 10 | let mut runtime = Runtime::new().unwrap(); 11 | let (client, conn) = runtime 12 | .block_on(tokio_postgres::connect( 13 | "host=localhost port=5433 user=postgres", 14 | NoTls, 15 | )) 16 | .unwrap(); 17 | runtime.spawn(async { conn.await.unwrap() }); 18 | (client, runtime) 19 | } 20 | 21 | fn query_prepared(c: &mut Criterion) { 22 | let (client, mut runtime) = setup(); 23 | let statement = runtime.block_on(client.prepare("SELECT $1::INT8")).unwrap(); 24 | c.bench_function("runtime_block_on", move |b| { 25 | b.iter(|| { 26 | runtime 27 | .block_on(client.query(&statement, &[&1i64])) 28 | .unwrap() 29 | }) 30 | }); 31 | 32 | let (client, mut runtime) = setup(); 33 | let statement = runtime.block_on(client.prepare("SELECT $1::INT8")).unwrap(); 34 | c.bench_function("executor_block_on", move |b| { 35 | b.iter(|| executor::block_on(client.query(&statement, &[&1i64])).unwrap()) 36 | }); 37 | 38 | let (client, mut runtime) = setup(); 39 | let client = Arc::new(client); 40 | let statement = runtime.block_on(client.prepare("SELECT $1::INT8")).unwrap(); 41 | c.bench_function("spawned", move |b| { 42 | b.iter_custom(|iters| { 43 | let (tx, rx) = oneshot::channel(); 44 | let client = client.clone(); 45 | let statement = statement.clone(); 46 | runtime.spawn(async move { 47 | let start = Instant::now(); 48 | for _ in 0..iters { 49 | client.query(&statement, &[&1i64]).await.unwrap(); 50 | } 51 | tx.send(start.elapsed()).unwrap(); 52 | }); 53 | executor::block_on(rx).unwrap() 54 | }) 55 | }); 56 | } 57 | 58 | criterion_group!(benches, query_prepared); 59 | criterion_main!(benches); 60 | -------------------------------------------------------------------------------- /tokio-postgres/src/statement.rs: -------------------------------------------------------------------------------- 1 | use crate::client::InnerClient; 2 | use crate::codec::FrontendMessage; 3 | use crate::types::Type; 4 | use postgres_protocol::message::frontend; 5 | use std::rc::{Rc, Weak}; 6 | 7 | struct StatementInner { 8 | client: Weak, 9 | name: String, 10 | params: Vec, 11 | columns: Vec, 12 | } 13 | 14 | impl Drop for StatementInner { 15 | fn drop(&mut self) { 16 | if let Some(client) = self.client.upgrade() { 17 | let buf = client.with_buf(|buf| { 18 | frontend::close(b'S', &self.name, buf).unwrap(); 19 | frontend::sync(buf); 20 | buf.split().freeze() 21 | }); 22 | let _ = client.send(FrontendMessage::Raw(buf)); 23 | } 24 | } 25 | } 26 | 27 | /// A prepared statement. 28 | /// 29 | /// Prepared statements can only be used with the connection that created them. 30 | #[derive(Clone)] 31 | pub struct Statement(Rc); 32 | 33 | impl Statement { 34 | pub(crate) fn new( 35 | inner: &Rc, 36 | name: String, 37 | params: Vec, 38 | columns: Vec, 39 | ) -> Statement { 40 | Statement(Rc::new(StatementInner { 41 | client: Rc::downgrade(inner), 42 | name, 43 | params, 44 | columns, 45 | })) 46 | } 47 | 48 | pub(crate) fn name(&self) -> &str { 49 | &self.0.name 50 | } 51 | 52 | /// Returns the expected types of the statement's parameters. 53 | pub fn params(&self) -> &[Type] { 54 | &self.0.params 55 | } 56 | 57 | /// Returns information about the columns returned when the statement is queried. 58 | pub fn columns(&self) -> &[Column] { 59 | &self.0.columns 60 | } 61 | } 62 | 63 | /// Information about a column of a query. 64 | #[derive(Debug)] 65 | pub struct Column { 66 | name: String, 67 | type_: Type, 68 | } 69 | 70 | impl Column { 71 | pub(crate) fn new(name: String, type_: Type) -> Column { 72 | Column { name, type_ } 73 | } 74 | 75 | /// Returns the name of the column. 76 | pub fn name(&self) -> &str { 77 | &self.name 78 | } 79 | 80 | /// Returns the type of the column. 81 | pub fn type_(&self) -> &Type { 82 | &self.type_ 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tokio-postgres/src/maybe_tls_stream.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::mem::MaybeUninit; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 6 | 7 | use ntex::util::{Buf, BufMut}; 8 | 9 | use crate::tls::{ChannelBinding, TlsStream}; 10 | 11 | pub enum MaybeTlsStream { 12 | Raw(S), 13 | Tls(T), 14 | } 15 | 16 | impl AsyncRead for MaybeTlsStream 17 | where 18 | S: AsyncRead + Unpin, 19 | T: AsyncRead + Unpin, 20 | { 21 | fn poll_read( 22 | mut self: Pin<&mut Self>, 23 | cx: &mut Context<'_>, 24 | buf: &mut ReadBuf<'_>, 25 | ) -> Poll> { 26 | match &mut *self { 27 | MaybeTlsStream::Raw(s) => Pin::new(s).poll_read(cx, buf), 28 | MaybeTlsStream::Tls(s) => Pin::new(s).poll_read(cx, buf), 29 | } 30 | } 31 | } 32 | 33 | impl AsyncWrite for MaybeTlsStream 34 | where 35 | S: AsyncWrite + Unpin, 36 | T: AsyncWrite + Unpin, 37 | { 38 | fn poll_write( 39 | mut self: Pin<&mut Self>, 40 | cx: &mut Context<'_>, 41 | buf: &[u8], 42 | ) -> Poll> { 43 | match &mut *self { 44 | MaybeTlsStream::Raw(s) => Pin::new(s).poll_write(cx, buf), 45 | MaybeTlsStream::Tls(s) => Pin::new(s).poll_write(cx, buf), 46 | } 47 | } 48 | 49 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 50 | match &mut *self { 51 | MaybeTlsStream::Raw(s) => Pin::new(s).poll_flush(cx), 52 | MaybeTlsStream::Tls(s) => Pin::new(s).poll_flush(cx), 53 | } 54 | } 55 | 56 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 57 | match &mut *self { 58 | MaybeTlsStream::Raw(s) => Pin::new(s).poll_shutdown(cx), 59 | MaybeTlsStream::Tls(s) => Pin::new(s).poll_shutdown(cx), 60 | } 61 | } 62 | } 63 | 64 | impl TlsStream for MaybeTlsStream 65 | where 66 | S: AsyncRead + AsyncWrite + Unpin, 67 | T: TlsStream + Unpin, 68 | { 69 | fn channel_binding(&self) -> ChannelBinding { 70 | match self { 71 | MaybeTlsStream::Raw(_) => ChannelBinding::none(), 72 | MaybeTlsStream::Tls(s) => s.channel_binding(), 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tokio-postgres/src/socket.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::mem::MaybeUninit; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 6 | use tokio::net::TcpStream; 7 | #[cfg(unix)] 8 | use tokio::net::UnixStream; 9 | 10 | use ntex::util::{Buf, BufMut}; 11 | 12 | #[derive(Debug)] 13 | enum Inner { 14 | Tcp(TcpStream), 15 | #[cfg(unix)] 16 | Unix(UnixStream), 17 | } 18 | 19 | /// The standard stream type used by the crate. 20 | /// 21 | /// Requires the `runtime` Cargo feature (enabled by default). 22 | #[derive(Debug)] 23 | pub struct Socket(Inner); 24 | 25 | impl Socket { 26 | pub(crate) fn new_tcp(stream: TcpStream) -> Socket { 27 | Socket(Inner::Tcp(stream)) 28 | } 29 | 30 | #[cfg(unix)] 31 | pub(crate) fn new_unix(stream: UnixStream) -> Socket { 32 | Socket(Inner::Unix(stream)) 33 | } 34 | } 35 | 36 | impl AsyncRead for Socket { 37 | fn poll_read( 38 | mut self: Pin<&mut Self>, 39 | cx: &mut Context<'_>, 40 | buf: &mut ReadBuf<'_>, 41 | ) -> Poll> { 42 | match &mut self.0 { 43 | Inner::Tcp(s) => Pin::new(s).poll_read(cx, buf), 44 | #[cfg(unix)] 45 | Inner::Unix(s) => Pin::new(s).poll_read(cx, buf), 46 | } 47 | } 48 | } 49 | 50 | impl AsyncWrite for Socket { 51 | fn poll_write( 52 | mut self: Pin<&mut Self>, 53 | cx: &mut Context<'_>, 54 | buf: &[u8], 55 | ) -> Poll> { 56 | match &mut self.0 { 57 | Inner::Tcp(s) => Pin::new(s).poll_write(cx, buf), 58 | #[cfg(unix)] 59 | Inner::Unix(s) => Pin::new(s).poll_write(cx, buf), 60 | } 61 | } 62 | 63 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 64 | match &mut self.0 { 65 | Inner::Tcp(s) => Pin::new(s).poll_flush(cx), 66 | #[cfg(unix)] 67 | Inner::Unix(s) => Pin::new(s).poll_flush(cx), 68 | } 69 | } 70 | 71 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 72 | match &mut self.0 { 73 | Inner::Tcp(s) => Pin::new(s).poll_shutdown(cx), 74 | #[cfg(unix)] 75 | Inner::Unix(s) => Pin::new(s).poll_shutdown(cx), 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /postgres-types/src/geo_types_04.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use fallible_iterator::FallibleIterator; 3 | use geo_types_04::{Coordinate, LineString, Point, Rect}; 4 | use postgres_protocol::types; 5 | use std::error::Error; 6 | 7 | use crate::{FromSql, IsNull, ToSql, Type}; 8 | 9 | impl<'a> FromSql<'a> for Point { 10 | fn from_sql(_: &Type, raw: &[u8]) -> Result> { 11 | let point = types::point_from_sql(raw)?; 12 | Ok(Point::new(point.x(), point.y())) 13 | } 14 | 15 | accepts!(POINT); 16 | } 17 | 18 | impl ToSql for Point { 19 | fn to_sql(&self, _: &Type, out: &mut BytesMut) -> Result> { 20 | types::point_to_sql(self.x(), self.y(), out); 21 | Ok(IsNull::No) 22 | } 23 | 24 | accepts!(POINT); 25 | to_sql_checked!(); 26 | } 27 | 28 | impl<'a> FromSql<'a> for Rect { 29 | fn from_sql(_: &Type, raw: &[u8]) -> Result> { 30 | let rect = types::box_from_sql(raw)?; 31 | Ok(Rect { 32 | min: Coordinate { 33 | x: rect.lower_left().x(), 34 | y: rect.lower_left().y(), 35 | }, 36 | max: Coordinate { 37 | x: rect.upper_right().x(), 38 | y: rect.upper_right().y(), 39 | }, 40 | }) 41 | } 42 | 43 | accepts!(BOX); 44 | } 45 | 46 | impl ToSql for Rect { 47 | fn to_sql(&self, _: &Type, out: &mut BytesMut) -> Result> { 48 | types::box_to_sql(self.min.x, self.min.y, self.max.x, self.max.y, out); 49 | Ok(IsNull::No) 50 | } 51 | 52 | accepts!(BOX); 53 | to_sql_checked!(); 54 | } 55 | 56 | impl<'a> FromSql<'a> for LineString { 57 | fn from_sql(_: &Type, raw: &[u8]) -> Result> { 58 | let path = types::path_from_sql(raw)?; 59 | let points = path 60 | .points() 61 | .map(|p| Ok(Coordinate { x: p.x(), y: p.y() })) 62 | .collect()?; 63 | Ok(LineString(points)) 64 | } 65 | 66 | accepts!(PATH); 67 | } 68 | 69 | impl ToSql for LineString { 70 | fn to_sql(&self, _: &Type, out: &mut BytesMut) -> Result> { 71 | let closed = false; // always encode an open path from LineString 72 | types::path_to_sql(closed, self.0.iter().map(|p| (p.x, p.y)), out)?; 73 | Ok(IsNull::No) 74 | } 75 | 76 | accepts!(PATH); 77 | to_sql_checked!(); 78 | } 79 | -------------------------------------------------------------------------------- /tokio-postgres/tests/test/runtime.rs: -------------------------------------------------------------------------------- 1 | use futures::{join, FutureExt}; 2 | use std::time::Duration; 3 | use tokio::time; 4 | use tokio_postgres::error::SqlState; 5 | use tokio_postgres::{Client, NoTls}; 6 | 7 | async fn connect(s: &str) -> Client { 8 | let (client, connection) = tokio_postgres::connect(s, NoTls).await.unwrap(); 9 | let connection = connection.map(|e| e.unwrap()); 10 | tokio::spawn(connection); 11 | 12 | client 13 | } 14 | 15 | async fn smoke_test(s: &str) { 16 | let client = connect(s).await; 17 | 18 | let stmt = client.prepare("SELECT $1::INT").await.unwrap(); 19 | let rows = client.query(&stmt, &[&1i32]).await.unwrap(); 20 | assert_eq!(rows[0].get::<_, i32>(0), 1i32); 21 | } 22 | 23 | #[tokio::test] 24 | #[ignore] // FIXME doesn't work with our docker-based tests :( 25 | async fn unix_socket() { 26 | smoke_test("host=/var/run/postgresql port=5433 user=postgres").await; 27 | } 28 | 29 | #[tokio::test] 30 | async fn tcp() { 31 | smoke_test("host=localhost port=5433 user=postgres").await; 32 | } 33 | 34 | #[tokio::test] 35 | async fn multiple_hosts_one_port() { 36 | smoke_test("host=foobar.invalid,localhost port=5433 user=postgres").await; 37 | } 38 | 39 | #[tokio::test] 40 | async fn multiple_hosts_multiple_ports() { 41 | smoke_test("host=foobar.invalid,localhost port=5432,5433 user=postgres").await; 42 | } 43 | 44 | #[tokio::test] 45 | async fn wrong_port_count() { 46 | tokio_postgres::connect("host=localhost port=5433,5433 user=postgres", NoTls) 47 | .await 48 | .err() 49 | .unwrap(); 50 | } 51 | 52 | #[tokio::test] 53 | async fn target_session_attrs_ok() { 54 | smoke_test("host=localhost port=5433 user=postgres target_session_attrs=read-write").await; 55 | } 56 | 57 | #[tokio::test] 58 | async fn target_session_attrs_err() { 59 | tokio_postgres::connect( 60 | "host=localhost port=5433 user=postgres target_session_attrs=read-write 61 | options='-c default_transaction_read_only=on'", 62 | NoTls, 63 | ) 64 | .await 65 | .err() 66 | .unwrap(); 67 | } 68 | 69 | #[tokio::test] 70 | async fn cancel_query() { 71 | let client = connect("host=localhost port=5433 user=postgres").await; 72 | 73 | let cancel = client.cancel_query(NoTls); 74 | let cancel = time::delay_for(Duration::from_millis(100)).then(|()| cancel); 75 | 76 | let sleep = client.batch_execute("SELECT pg_sleep(100)"); 77 | 78 | match join!(sleep, cancel) { 79 | (Err(ref e), Ok(())) if e.code() == Some(&SqlState::QUERY_CANCELED) => {} 80 | t => panic!("unexpected return: {:?}", t), 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /postgres/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A synchronous client for the PostgreSQL database. 2 | //! 3 | //! # Example 4 | //! 5 | //! ```no_run 6 | //! use postgres::{Client, NoTls}; 7 | //! 8 | //! # fn main() -> Result<(), postgres::Error> { 9 | //! let mut client = Client::connect("host=localhost user=postgres", NoTls)?; 10 | //! 11 | //! client.batch_execute(" 12 | //! CREATE TABLE person ( 13 | //! id SERIAL PRIMARY KEY, 14 | //! name TEXT NOT NULL, 15 | //! data BYTEA 16 | //! ) 17 | //! ")?; 18 | //! 19 | //! let name = "Ferris"; 20 | //! let data = None::<&[u8]>; 21 | //! client.execute( 22 | //! "INSERT INTO person (name, data) VALUES ($1, $2)", 23 | //! &[&name, &data], 24 | //! )?; 25 | //! 26 | //! for row in client.query("SELECT id, name, data FROM person", &[])? { 27 | //! let id: i32 = row.get(0); 28 | //! let name: &str = row.get(1); 29 | //! let data: Option<&[u8]> = row.get(2); 30 | //! 31 | //! println!("found person: {} {} {:?}", id, name, data); 32 | //! } 33 | //! # Ok(()) 34 | //! # } 35 | //! ``` 36 | //! 37 | //! # Implementation 38 | //! 39 | //! This crate is a lightweight wrapper over tokio-postgres. The `postgres::Client` is simply a wrapper around a 40 | //! `tokio_postgres::Client` along side a tokio `Runtime`. The client simply blocks on the futures provided by the async 41 | //! client. 42 | //! 43 | //! # SSL/TLS support 44 | //! 45 | //! TLS support is implemented via external libraries. `Client::connect` and `Config::connect` take a TLS implementation 46 | //! as an argument. The `NoTls` type in this crate can be used when TLS is not required. Otherwise, the 47 | //! `postgres-openssl` and `postgres-native-tls` crates provide implementations backed by the `openssl` and `native-tls` 48 | //! crates, respectively. 49 | #![doc(html_root_url = "https://docs.rs/postgres/0.17")] 50 | #![warn(clippy::all, rust_2018_idioms, missing_docs)] 51 | 52 | pub use fallible_iterator; 53 | pub use tokio_postgres::{ 54 | error, row, tls, types, Column, Portal, SimpleQueryMessage, Socket, Statement, ToStatement, 55 | }; 56 | 57 | pub use crate::client::*; 58 | pub use crate::config::Config; 59 | pub use crate::copy_in_writer::CopyInWriter; 60 | pub use crate::copy_out_reader::CopyOutReader; 61 | #[doc(no_inline)] 62 | pub use crate::error::Error; 63 | #[doc(no_inline)] 64 | pub use crate::row::{Row, SimpleQueryRow}; 65 | pub use crate::row_iter::RowIter; 66 | #[doc(no_inline)] 67 | pub use crate::tls::NoTls; 68 | pub use crate::transaction::*; 69 | 70 | pub mod binary_copy; 71 | mod client; 72 | pub mod config; 73 | mod copy_in_writer; 74 | mod copy_out_reader; 75 | mod lazy_pin; 76 | mod row_iter; 77 | mod transaction; 78 | 79 | #[cfg(test)] 80 | mod test; 81 | -------------------------------------------------------------------------------- /postgres-derive/src/accepts.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use quote::quote; 3 | use std::iter; 4 | use syn::Ident; 5 | 6 | use crate::composites::Field; 7 | use crate::enums::Variant; 8 | 9 | pub fn domain_body(name: &str, field: &syn::Field) -> TokenStream { 10 | let ty = &field.ty; 11 | 12 | quote! { 13 | if type_.name() != #name { 14 | return false; 15 | } 16 | 17 | match *type_.kind() { 18 | ::postgres_types::Kind::Domain(ref type_) => { 19 | <#ty as ::postgres_types::ToSql>::accepts(type_) 20 | } 21 | _ => false, 22 | } 23 | } 24 | } 25 | 26 | pub fn enum_body(name: &str, variants: &[Variant]) -> TokenStream { 27 | let num_variants = variants.len(); 28 | let variant_names = variants.iter().map(|v| &v.name); 29 | 30 | quote! { 31 | if type_.name() != #name { 32 | return false; 33 | } 34 | 35 | match *type_.kind() { 36 | ::postgres_types::Kind::Enum(ref variants) => { 37 | if variants.len() != #num_variants { 38 | return false; 39 | } 40 | 41 | variants.iter().all(|v| { 42 | match &**v { 43 | #( 44 | #variant_names => true, 45 | )* 46 | _ => false, 47 | } 48 | }) 49 | } 50 | _ => false, 51 | } 52 | } 53 | } 54 | 55 | pub fn composite_body(name: &str, trait_: &str, fields: &[Field]) -> TokenStream { 56 | let num_fields = fields.len(); 57 | let trait_ = Ident::new(trait_, Span::call_site()); 58 | let traits = iter::repeat(&trait_); 59 | let field_names = fields.iter().map(|f| &f.name); 60 | let field_types = fields.iter().map(|f| &f.type_); 61 | 62 | quote! { 63 | if type_.name() != #name { 64 | return false; 65 | } 66 | 67 | match *type_.kind() { 68 | ::postgres_types::Kind::Composite(ref fields) => { 69 | if fields.len() != #num_fields { 70 | return false; 71 | } 72 | 73 | fields.iter().all(|f| { 74 | match f.name() { 75 | #( 76 | #field_names => { 77 | <#field_types as ::postgres_types::#traits>::accepts(f.type_()) 78 | } 79 | )* 80 | _ => false, 81 | } 82 | }) 83 | } 84 | _ => false, 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /THIRD_PARTY: -------------------------------------------------------------------------------- 1 | Rust-Postgres contains some third-party content. 2 | 3 | ------------------------------------------------------------------------------- 4 | 5 | * Some documentation has been copied from PostgreSQL 6 | 7 | PostgreSQL Database Management System 8 | (formerly known as Postgres, then as Postgres95) 9 | 10 | Portions Copyright (c) 1996-2013, The PostgreSQL Global Development Group 11 | 12 | Portions Copyright (c) 1994, The Regents of the University of California 13 | 14 | Permission to use, copy, modify, and distribute this software and its 15 | documentation for any purpose, without fee, and without a written agreement is 16 | hereby granted, provided that the above copyright notice and this paragraph and 17 | the following two paragraphs appear in all copies. 18 | 19 | IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR 20 | DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST 21 | PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 22 | THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 23 | DAMAGE. 24 | 25 | THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, 26 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 | A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, 28 | AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, 29 | SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 30 | 31 | ------------------------------------------------------------------------------- 32 | 33 | * src/url.rs has been copied from Rust 34 | 35 | Copyright (c) 2014 The Rust Project Developers 36 | 37 | Permission is hereby granted, free of charge, to any 38 | person obtaining a copy of this software and associated 39 | documentation files (the "Software"), to deal in the 40 | Software without restriction, including without 41 | limitation the rights to use, copy, modify, merge, 42 | publish, distribute, sublicense, and/or sell copies of 43 | the Software, and to permit persons to whom the Software 44 | is furnished to do so, subject to the following 45 | conditions: 46 | 47 | The above copyright notice and this permission notice 48 | shall be included in all copies or substantial portions 49 | of the Software. 50 | 51 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 52 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 53 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 54 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 55 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 56 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 57 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 58 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 59 | DEALINGS IN THE SOFTWARE. 60 | -------------------------------------------------------------------------------- /postgres-protocol/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Low level Postgres protocol APIs. 2 | //! 3 | //! This crate implements the low level components of Postgres's communication 4 | //! protocol, including message and value serialization and deserialization. 5 | //! It is designed to be used as a building block by higher level APIs such as 6 | //! `rust-postgres`, and should not typically be used directly. 7 | //! 8 | //! # Note 9 | //! 10 | //! This library assumes that the `client_encoding` backend parameter has been 11 | //! set to `UTF8`. It will most likely not behave properly if that is not the case. 12 | #![doc(html_root_url = "https://docs.rs/postgres-protocol/0.5")] 13 | // #![warn(missing_docs, rust_2018_idioms, clippy::all)] 14 | 15 | use byteorder::{BigEndian, ByteOrder}; 16 | use bytes::{BufMut, BytesMut, BytesVec}; 17 | use std::io; 18 | 19 | pub mod authentication; 20 | pub mod message; 21 | pub mod types; 22 | 23 | /// A Postgres OID. 24 | pub type Oid = u32; 25 | 26 | /// An enum indicating if a value is `NULL` or not. 27 | pub enum IsNull { 28 | /// The value is `NULL`. 29 | Yes, 30 | /// The value is not `NULL`. 31 | No, 32 | } 33 | 34 | pub fn write_nullable(serializer: F, buf: &mut BytesMut) -> Result<(), E> 35 | where 36 | F: FnOnce(&mut BytesMut) -> Result, 37 | E: From, 38 | { 39 | let base = buf.len(); 40 | buf.put_i32(0); 41 | let size = match serializer(buf)? { 42 | IsNull::No => i32::from_usize(buf.len() - base - 4)?, 43 | IsNull::Yes => -1, 44 | }; 45 | BigEndian::write_i32(&mut buf[base..], size); 46 | 47 | Ok(()) 48 | } 49 | 50 | pub fn write_nullable_vec(serializer: F, buf: &mut BytesVec) -> Result<(), E> 51 | where 52 | F: FnOnce(&mut BytesVec) -> Result, 53 | E: From, 54 | { 55 | let base = buf.len(); 56 | buf.put_i32(0); 57 | let size = match serializer(buf)? { 58 | IsNull::No => i32::from_usize(buf.len() - base - 4)?, 59 | IsNull::Yes => -1, 60 | }; 61 | BigEndian::write_i32(&mut buf[base..], size); 62 | 63 | Ok(()) 64 | } 65 | 66 | trait FromUsize: Sized { 67 | fn from_usize(x: usize) -> Result; 68 | } 69 | 70 | macro_rules! from_usize { 71 | ($t:ty) => { 72 | impl FromUsize for $t { 73 | #[inline] 74 | fn from_usize(x: usize) -> io::Result<$t> { 75 | if x > <$t>::max_value() as usize { 76 | Err(io::Error::new( 77 | io::ErrorKind::InvalidInput, 78 | "value too large to transmit", 79 | )) 80 | } else { 81 | Ok(x as $t) 82 | } 83 | } 84 | } 85 | }; 86 | } 87 | 88 | from_usize!(i16); 89 | from_usize!(i32); 90 | -------------------------------------------------------------------------------- /codegen/src/sqlstate.rs: -------------------------------------------------------------------------------- 1 | use linked_hash_map::LinkedHashMap; 2 | use phf_codegen; 3 | use std::fs::File; 4 | use std::io::{BufWriter, Write}; 5 | 6 | const ERRCODES_TXT: &str = include_str!("errcodes.txt"); 7 | 8 | pub fn build() { 9 | let mut file = BufWriter::new(File::create("../tokio-postgres/src/error/sqlstate.rs").unwrap()); 10 | 11 | let codes = parse_codes(); 12 | 13 | make_type(&mut file); 14 | make_consts(&codes, &mut file); 15 | make_map(&codes, &mut file); 16 | } 17 | 18 | fn parse_codes() -> LinkedHashMap> { 19 | let mut codes = LinkedHashMap::new(); 20 | 21 | for line in ERRCODES_TXT.lines() { 22 | if line.starts_with('#') || line.starts_with("Section") || line.trim().is_empty() { 23 | continue; 24 | } 25 | 26 | let mut it = line.split_whitespace(); 27 | let code = it.next().unwrap().to_owned(); 28 | it.next(); 29 | let name = it.next().unwrap().replace("ERRCODE_", ""); 30 | 31 | codes.entry(code).or_insert_with(Vec::new).push(name); 32 | } 33 | 34 | codes 35 | } 36 | 37 | fn make_type(file: &mut BufWriter) { 38 | write!( 39 | file, 40 | "// Autogenerated file - DO NOT EDIT 41 | use std::borrow::Cow; 42 | 43 | /// A SQLSTATE error code 44 | #[derive(PartialEq, Eq, Clone, Debug)] 45 | pub struct SqlState(Cow<'static, str>); 46 | 47 | impl SqlState {{ 48 | /// Creates a `SqlState` from its error code. 49 | pub fn from_code(s: &str) -> SqlState {{ 50 | match SQLSTATE_MAP.get(s) {{ 51 | Some(state) => state.clone(), 52 | None => SqlState(Cow::Owned(s.to_string())), 53 | }} 54 | }} 55 | 56 | /// Returns the error code corresponding to the `SqlState`. 57 | pub fn code(&self) -> &str {{ 58 | &self.0 59 | }} 60 | " 61 | ) 62 | .unwrap(); 63 | } 64 | 65 | fn make_consts(codes: &LinkedHashMap>, file: &mut BufWriter) { 66 | for (code, names) in codes { 67 | for name in names { 68 | write!( 69 | file, 70 | r#" 71 | /// {code} 72 | pub const {name}: SqlState = SqlState(Cow::Borrowed("{code}")); 73 | "#, 74 | name = name, 75 | code = code, 76 | ) 77 | .unwrap(); 78 | } 79 | } 80 | 81 | write!(file, "}}").unwrap(); 82 | } 83 | 84 | fn make_map(codes: &LinkedHashMap>, file: &mut BufWriter) { 85 | let mut builder = phf_codegen::Map::new(); 86 | for (code, names) in codes { 87 | builder.entry(&**code, &format!("SqlState::{}", &names[0])); 88 | } 89 | write!( 90 | file, 91 | " 92 | #[rustfmt::skip] 93 | static SQLSTATE_MAP: phf::Map<&'static str, SqlState> = \n{};\n", 94 | builder.build() 95 | ) 96 | .unwrap(); 97 | } 98 | -------------------------------------------------------------------------------- /tokio-postgres/src/codec.rs: -------------------------------------------------------------------------------- 1 | use fallible_iterator::FallibleIterator; 2 | use ntex::codec::{Decoder, Encoder}; 3 | use ntex::util::{Buf, Bytes, BytesMut}; 4 | use postgres_protocol::message::backend; 5 | use postgres_protocol::message::frontend::CopyData; 6 | use std::io; 7 | 8 | pub enum FrontendMessage { 9 | Raw(Bytes), 10 | CopyData(CopyData>), 11 | } 12 | 13 | pub enum BackendMessage { 14 | Normal { 15 | messages: BackendMessages, 16 | request_complete: bool, 17 | }, 18 | Async(backend::Message), 19 | } 20 | 21 | #[derive(Debug)] 22 | pub struct BackendMessages(BytesMut); 23 | 24 | impl BackendMessages { 25 | pub fn empty() -> BackendMessages { 26 | BackendMessages(BytesMut::new()) 27 | } 28 | } 29 | 30 | impl FallibleIterator for BackendMessages { 31 | type Item = backend::Message; 32 | type Error = io::Error; 33 | 34 | fn next(&mut self) -> io::Result> { 35 | backend::Message::parse(&mut self.0) 36 | } 37 | } 38 | 39 | pub struct PostgresCodec; 40 | 41 | impl Encoder for PostgresCodec { 42 | type Item = FrontendMessage; 43 | type Error = io::Error; 44 | 45 | fn encode(&self, item: FrontendMessage, dst: &mut BytesMut) -> io::Result<()> { 46 | match item { 47 | FrontendMessage::Raw(buf) => dst.extend_from_slice(&buf), 48 | FrontendMessage::CopyData(data) => data.write(dst), 49 | } 50 | 51 | Ok(()) 52 | } 53 | } 54 | 55 | impl Decoder for PostgresCodec { 56 | type Item = BackendMessage; 57 | type Error = io::Error; 58 | 59 | fn decode(&self, src: &mut BytesMut) -> Result, io::Error> { 60 | let mut idx = 0; 61 | let mut request_complete = false; 62 | 63 | while let Some(header) = backend::Header::parse(&src[idx..])? { 64 | let len = header.len() as usize + 1; 65 | if src[idx..].len() < len { 66 | break; 67 | } 68 | 69 | match header.tag() { 70 | backend::NOTICE_RESPONSE_TAG 71 | | backend::NOTIFICATION_RESPONSE_TAG 72 | | backend::PARAMETER_STATUS_TAG => { 73 | if idx == 0 { 74 | let message = backend::Message::parse(src)?.unwrap(); 75 | return Ok(Some(BackendMessage::Async(message))); 76 | } else { 77 | break; 78 | } 79 | } 80 | _ => {} 81 | } 82 | 83 | idx += len; 84 | 85 | if header.tag() == backend::READY_FOR_QUERY_TAG { 86 | request_complete = true; 87 | break; 88 | } 89 | } 90 | 91 | if idx == 0 { 92 | Ok(None) 93 | } else { 94 | Ok(Some(BackendMessage::Normal { 95 | messages: BackendMessages(src.split_to(idx)), 96 | request_complete, 97 | })) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /postgres-derive-test/src/enums.rs: -------------------------------------------------------------------------------- 1 | use crate::test_type; 2 | use postgres::{Client, NoTls}; 3 | use postgres_types::{FromSql, ToSql, WrongType}; 4 | use std::error::Error; 5 | 6 | #[test] 7 | fn defaults() { 8 | #[derive(Debug, ToSql, FromSql, PartialEq)] 9 | enum Foo { 10 | Bar, 11 | Baz, 12 | } 13 | 14 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 15 | conn.execute("CREATE TYPE pg_temp.\"Foo\" AS ENUM ('Bar', 'Baz')", &[]) 16 | .unwrap(); 17 | 18 | test_type( 19 | &mut conn, 20 | "\"Foo\"", 21 | &[(Foo::Bar, "'Bar'"), (Foo::Baz, "'Baz'")], 22 | ); 23 | } 24 | 25 | #[test] 26 | fn name_overrides() { 27 | #[derive(Debug, ToSql, FromSql, PartialEq)] 28 | #[postgres(name = "mood")] 29 | enum Mood { 30 | #[postgres(name = "sad")] 31 | Sad, 32 | #[postgres(name = "ok")] 33 | Ok, 34 | #[postgres(name = "happy")] 35 | Happy, 36 | } 37 | 38 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 39 | conn.execute( 40 | "CREATE TYPE pg_temp.mood AS ENUM ('sad', 'ok', 'happy')", 41 | &[], 42 | ) 43 | .unwrap(); 44 | 45 | test_type( 46 | &mut conn, 47 | "mood", 48 | &[ 49 | (Mood::Sad, "'sad'"), 50 | (Mood::Ok, "'ok'"), 51 | (Mood::Happy, "'happy'"), 52 | ], 53 | ); 54 | } 55 | 56 | #[test] 57 | fn wrong_name() { 58 | #[derive(Debug, ToSql, FromSql, PartialEq)] 59 | enum Foo { 60 | Bar, 61 | Baz, 62 | } 63 | 64 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 65 | conn.execute("CREATE TYPE pg_temp.foo AS ENUM ('Bar', 'Baz')", &[]) 66 | .unwrap(); 67 | 68 | let err = conn.execute("SELECT $1::foo", &[&Foo::Bar]).unwrap_err(); 69 | assert!(err.source().unwrap().is::()); 70 | } 71 | 72 | #[test] 73 | fn extra_variant() { 74 | #[derive(Debug, ToSql, FromSql, PartialEq)] 75 | #[postgres(name = "foo")] 76 | enum Foo { 77 | Bar, 78 | Baz, 79 | Buz, 80 | } 81 | 82 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 83 | conn.execute("CREATE TYPE pg_temp.foo AS ENUM ('Bar', 'Baz')", &[]) 84 | .unwrap(); 85 | 86 | let err = conn.execute("SELECT $1::foo", &[&Foo::Bar]).unwrap_err(); 87 | assert!(err.source().unwrap().is::()); 88 | } 89 | 90 | #[test] 91 | fn missing_variant() { 92 | #[derive(Debug, ToSql, FromSql, PartialEq)] 93 | #[postgres(name = "foo")] 94 | enum Foo { 95 | Bar, 96 | } 97 | 98 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 99 | conn.execute("CREATE TYPE pg_temp.foo AS ENUM ('Bar', 'Baz')", &[]) 100 | .unwrap(); 101 | 102 | let err = conn.execute("SELECT $1::foo", &[&Foo::Bar]).unwrap_err(); 103 | assert!(err.source().unwrap().is::()); 104 | } 105 | -------------------------------------------------------------------------------- /postgres-native-tls/src/test.rs: -------------------------------------------------------------------------------- 1 | use futures::FutureExt; 2 | use native_tls::{self, Certificate}; 3 | use tokio::net::TcpStream; 4 | use tokio_postgres::tls::TlsConnect; 5 | 6 | #[cfg(feature = "runtime")] 7 | use crate::MakeTlsConnector; 8 | use crate::TlsConnector; 9 | 10 | async fn smoke_test(s: &str, tls: T) 11 | where 12 | T: TlsConnect, 13 | T::Stream: 'static + Send, 14 | { 15 | let stream = TcpStream::connect("127.0.0.1:5433").await.unwrap(); 16 | 17 | let builder = s.parse::().unwrap(); 18 | let (client, connection) = builder.connect_raw(stream, tls).await.unwrap(); 19 | 20 | let connection = connection.map(|r| r.unwrap()); 21 | tokio::spawn(connection); 22 | 23 | let stmt = client.prepare("SELECT $1::INT4").await.unwrap(); 24 | let rows = client.query(&stmt, &[&1i32]).await.unwrap(); 25 | 26 | assert_eq!(rows.len(), 1); 27 | assert_eq!(rows[0].get::<_, i32>(0), 1); 28 | } 29 | 30 | #[tokio::test] 31 | async fn require() { 32 | let connector = native_tls::TlsConnector::builder() 33 | .add_root_certificate( 34 | Certificate::from_pem(include_bytes!("../../test/server.crt")).unwrap(), 35 | ) 36 | .build() 37 | .unwrap(); 38 | smoke_test( 39 | "user=ssl_user dbname=postgres sslmode=require", 40 | TlsConnector::new(connector, "localhost"), 41 | ) 42 | .await; 43 | } 44 | 45 | #[tokio::test] 46 | async fn prefer() { 47 | let connector = native_tls::TlsConnector::builder() 48 | .add_root_certificate( 49 | Certificate::from_pem(include_bytes!("../../test/server.crt")).unwrap(), 50 | ) 51 | .build() 52 | .unwrap(); 53 | smoke_test( 54 | "user=ssl_user dbname=postgres", 55 | TlsConnector::new(connector, "localhost"), 56 | ) 57 | .await; 58 | } 59 | 60 | #[tokio::test] 61 | async fn scram_user() { 62 | let connector = native_tls::TlsConnector::builder() 63 | .add_root_certificate( 64 | Certificate::from_pem(include_bytes!("../../test/server.crt")).unwrap(), 65 | ) 66 | .build() 67 | .unwrap(); 68 | smoke_test( 69 | "user=scram_user password=password dbname=postgres sslmode=require", 70 | TlsConnector::new(connector, "localhost"), 71 | ) 72 | .await; 73 | } 74 | 75 | #[tokio::test] 76 | #[cfg(feature = "runtime")] 77 | async fn runtime() { 78 | let connector = native_tls::TlsConnector::builder() 79 | .add_root_certificate( 80 | Certificate::from_pem(include_bytes!("../../test/server.crt")).unwrap(), 81 | ) 82 | .build() 83 | .unwrap(); 84 | let connector = MakeTlsConnector::new(connector); 85 | 86 | let (client, connection) = tokio_postgres::connect( 87 | "host=localhost port=5433 user=postgres sslmode=require", 88 | connector, 89 | ) 90 | .await 91 | .unwrap(); 92 | let connection = connection.map(|r| r.unwrap()); 93 | tokio::spawn(connection); 94 | 95 | let stmt = client.prepare("SELECT $1::INT4").await.unwrap(); 96 | let rows = client.query(&stmt, &[&1i32]).await.unwrap(); 97 | 98 | assert_eq!(rows.len(), 1); 99 | assert_eq!(rows[0].get::<_, i32>(0), 1); 100 | } 101 | -------------------------------------------------------------------------------- /postgres/src/binary_copy.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for working with the PostgreSQL binary copy format. 2 | 3 | use crate::types::{ToSql, Type}; 4 | use crate::{CopyInWriter, CopyOutReader, Error, Rt}; 5 | use fallible_iterator::FallibleIterator; 6 | use futures::StreamExt; 7 | use std::pin::Pin; 8 | #[doc(inline)] 9 | pub use tokio_postgres::binary_copy::BinaryCopyOutRow; 10 | use tokio_postgres::binary_copy::{self, BinaryCopyOutStream}; 11 | 12 | /// A type which serializes rows into the PostgreSQL binary copy format. 13 | /// 14 | /// The copy *must* be explicitly completed via the `finish` method. If it is not, the copy will be aborted. 15 | pub struct BinaryCopyInWriter<'a> { 16 | runtime: Rt<'a>, 17 | sink: Pin>, 18 | } 19 | 20 | impl<'a> BinaryCopyInWriter<'a> { 21 | /// Creates a new writer which will write rows of the provided types. 22 | pub fn new(writer: CopyInWriter<'a>, types: &[Type]) -> BinaryCopyInWriter<'a> { 23 | let stream = writer 24 | .sink 25 | .into_unpinned() 26 | .expect("writer has already been written to"); 27 | 28 | BinaryCopyInWriter { 29 | runtime: writer.runtime, 30 | sink: Box::pin(binary_copy::BinaryCopyInWriter::new(stream, types)), 31 | } 32 | } 33 | 34 | /// Writes a single row. 35 | /// 36 | /// # Panics 37 | /// 38 | /// Panics if the number of values provided does not match the number expected. 39 | pub fn write(&mut self, values: &[&(dyn ToSql + Sync)]) -> Result<(), Error> { 40 | self.runtime.block_on(self.sink.as_mut().write(values)) 41 | } 42 | 43 | /// A maximally-flexible version of `write`. 44 | /// 45 | /// # Panics 46 | /// 47 | /// Panics if the number of values provided does not match the number expected. 48 | pub fn write_raw<'b, I>(&mut self, values: I) -> Result<(), Error> 49 | where 50 | I: IntoIterator, 51 | I::IntoIter: ExactSizeIterator, 52 | { 53 | self.runtime.block_on(self.sink.as_mut().write_raw(values)) 54 | } 55 | 56 | /// Completes the copy, returning the number of rows added. 57 | /// 58 | /// This method *must* be used to complete the copy process. If it is not, the copy will be aborted. 59 | pub fn finish(mut self) -> Result { 60 | self.runtime.block_on(self.sink.as_mut().finish()) 61 | } 62 | } 63 | 64 | /// An iterator of rows deserialized from the PostgreSQL binary copy format. 65 | pub struct BinaryCopyOutIter<'a> { 66 | runtime: Rt<'a>, 67 | stream: Pin>, 68 | } 69 | 70 | impl<'a> BinaryCopyOutIter<'a> { 71 | /// Creates a new iterator from a raw copy out reader and the types of the columns being returned. 72 | pub fn new(reader: CopyOutReader<'a>, types: &[Type]) -> BinaryCopyOutIter<'a> { 73 | let stream = reader 74 | .stream 75 | .into_unpinned() 76 | .expect("reader has already been read from"); 77 | 78 | BinaryCopyOutIter { 79 | runtime: reader.runtime, 80 | stream: Box::pin(BinaryCopyOutStream::new(stream, types)), 81 | } 82 | } 83 | } 84 | 85 | impl FallibleIterator for BinaryCopyOutIter<'_> { 86 | type Item = BinaryCopyOutRow; 87 | type Error = Error; 88 | 89 | fn next(&mut self) -> Result, Error> { 90 | self.runtime.block_on(self.stream.next()).transpose() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tokio-postgres/src/connect.rs: -------------------------------------------------------------------------------- 1 | use futures::{future, pin_mut, Future, FutureExt, Stream}; 2 | use std::{io, task::Poll}; 3 | 4 | use ntex::io::Io; 5 | 6 | use crate::client::SocketConfig; 7 | use crate::config::{Host, TargetSessionAttrs}; 8 | use crate::connect_raw::connect_raw; 9 | use crate::connect_socket::connect_socket; 10 | use crate::tls::{MakeTlsConnect, TlsConnect}; 11 | use crate::{Client, Config, Connection, Error, SimpleQueryMessage, Socket}; 12 | 13 | pub async fn connect(config: &Config) -> Result<(Client, Connection), Error> { 14 | if config.host.is_empty() { 15 | return Err(Error::config("host missing".into())); 16 | } 17 | 18 | if config.port.len() > 1 && config.port.len() != config.host.len() { 19 | return Err(Error::config("invalid number of ports".into())); 20 | } 21 | 22 | let mut error = None; 23 | for (i, host) in config.host.iter().enumerate() { 24 | let port = *config 25 | .port 26 | .get(i) 27 | .or_else(|| config.port.get(0)) 28 | .unwrap_or(&5432); 29 | 30 | let hostname = match host { 31 | Host::Tcp(host) => &**host, 32 | // postgres doesn't support TLS over unix sockets, so the choice here doesn't matter 33 | #[cfg(unix)] 34 | Host::Unix(_) => "", 35 | }; 36 | 37 | match connect_once(host, port, config).await { 38 | Ok((client, connection)) => return Ok((client, connection)), 39 | Err(e) => error = Some(e), 40 | } 41 | } 42 | 43 | Err(error.unwrap()) 44 | } 45 | 46 | async fn connect_once( 47 | host: &Host, 48 | port: u16, 49 | config: &Config, 50 | ) -> Result<(Client, Connection), Error> { 51 | let socket = connect_socket( 52 | host, 53 | port, 54 | config.connect_timeout, 55 | config.keepalives, 56 | config.keepalives_idle, 57 | ) 58 | .await?; 59 | let (client, mut connection) = connect_raw(socket, config).await?; 60 | 61 | if let TargetSessionAttrs::ReadWrite = config.target_session_attrs { 62 | let rows = client.simple_query_raw("SHOW transaction_read_only"); 63 | pin_mut!(rows); 64 | 65 | let rows = future::poll_fn(|cx| { 66 | if connection.poll_unpin(cx)?.is_ready() { 67 | return Poll::Ready(Err(Error::closed())); 68 | } 69 | 70 | rows.as_mut().poll(cx) 71 | }) 72 | .await?; 73 | pin_mut!(rows); 74 | 75 | loop { 76 | let next = future::poll_fn(|cx| { 77 | if connection.poll_unpin(cx)?.is_ready() { 78 | return Poll::Ready(Some(Err(Error::closed()))); 79 | } 80 | 81 | rows.as_mut().poll_next(cx) 82 | }); 83 | 84 | match next.await.transpose()? { 85 | Some(SimpleQueryMessage::Row(row)) => { 86 | if row.try_get(0)? == Some("on") { 87 | return Err(Error::connect(io::Error::new( 88 | io::ErrorKind::PermissionDenied, 89 | "database does not allow writes", 90 | ))); 91 | } else { 92 | break; 93 | } 94 | } 95 | Some(_) => {} 96 | None => return Err(Error::unexpected_message()), 97 | } 98 | } 99 | } 100 | 101 | Ok((client, connection)) 102 | } 103 | -------------------------------------------------------------------------------- /postgres-derive-test/src/domains.rs: -------------------------------------------------------------------------------- 1 | use crate::test_type; 2 | use postgres::{Client, NoTls}; 3 | use postgres_types::{FromSql, ToSql, WrongType}; 4 | use std::error::Error; 5 | 6 | #[test] 7 | fn defaults() { 8 | #[derive(FromSql, ToSql, Debug, PartialEq)] 9 | struct SessionId(Vec); 10 | 11 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 12 | conn.execute( 13 | "CREATE DOMAIN pg_temp.\"SessionId\" AS bytea CHECK(octet_length(VALUE) = 16);", 14 | &[], 15 | ) 16 | .unwrap(); 17 | 18 | test_type( 19 | &mut conn, 20 | "\"SessionId\"", 21 | &[( 22 | SessionId(b"0123456789abcdef".to_vec()), 23 | "'0123456789abcdef'", 24 | )], 25 | ); 26 | } 27 | 28 | #[test] 29 | fn name_overrides() { 30 | #[derive(FromSql, ToSql, Debug, PartialEq)] 31 | #[postgres(name = "session_id")] 32 | struct SessionId(Vec); 33 | 34 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 35 | conn.execute( 36 | "CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", 37 | &[], 38 | ) 39 | .unwrap(); 40 | 41 | test_type( 42 | &mut conn, 43 | "session_id", 44 | &[( 45 | SessionId(b"0123456789abcdef".to_vec()), 46 | "'0123456789abcdef'", 47 | )], 48 | ); 49 | } 50 | 51 | #[test] 52 | fn wrong_name() { 53 | #[derive(FromSql, ToSql, Debug, PartialEq)] 54 | struct SessionId(Vec); 55 | 56 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 57 | conn.execute( 58 | "CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", 59 | &[], 60 | ) 61 | .unwrap(); 62 | 63 | let err = conn 64 | .execute("SELECT $1::session_id", &[&SessionId(vec![])]) 65 | .unwrap_err(); 66 | assert!(err.source().unwrap().is::()); 67 | } 68 | 69 | #[test] 70 | fn wrong_type() { 71 | #[derive(FromSql, ToSql, Debug, PartialEq)] 72 | #[postgres(name = "session_id")] 73 | struct SessionId(i32); 74 | 75 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 76 | conn.execute( 77 | "CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", 78 | &[], 79 | ) 80 | .unwrap(); 81 | 82 | let err = conn 83 | .execute("SELECT $1::session_id", &[&SessionId(0)]) 84 | .unwrap_err(); 85 | assert!(err.source().unwrap().is::()); 86 | } 87 | 88 | #[test] 89 | fn domain_in_composite() { 90 | #[derive(FromSql, ToSql, Debug, PartialEq)] 91 | #[postgres(name = "domain")] 92 | struct Domain(String); 93 | 94 | #[derive(FromSql, ToSql, Debug, PartialEq)] 95 | #[postgres(name = "composite")] 96 | struct Composite { 97 | domain: Domain, 98 | } 99 | 100 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 101 | conn.batch_execute( 102 | " 103 | CREATE DOMAIN pg_temp.domain AS TEXT;\ 104 | CREATE TYPE pg_temp.composite AS ( 105 | domain domain 106 | ); 107 | ", 108 | ) 109 | .unwrap(); 110 | 111 | test_type( 112 | &mut conn, 113 | "composite", 114 | &[( 115 | Composite { 116 | domain: Domain("hello".to_string()), 117 | }, 118 | "ROW('hello')", 119 | )], 120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /postgres-types/src/special.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use postgres_protocol::types; 3 | use std::error::Error; 4 | use std::{i32, i64}; 5 | 6 | use crate::{FromSql, IsNull, ToSql, Type}; 7 | 8 | /// A wrapper that can be used to represent infinity with `Type::Date` types. 9 | #[derive(Debug, Clone, Copy, PartialEq)] 10 | pub enum Date { 11 | /// Represents `infinity`, a date that is later than all other dates. 12 | PosInfinity, 13 | /// Represents `-infinity`, a date that is earlier than all other dates. 14 | NegInfinity, 15 | /// The wrapped date. 16 | Value(T), 17 | } 18 | 19 | impl<'a, T: FromSql<'a>> FromSql<'a> for Date { 20 | fn from_sql(ty: &Type, raw: &'a [u8]) -> Result> { 21 | match types::date_from_sql(raw)? { 22 | i32::MAX => Ok(Date::PosInfinity), 23 | i32::MIN => Ok(Date::NegInfinity), 24 | _ => T::from_sql(ty, raw).map(Date::Value), 25 | } 26 | } 27 | 28 | fn accepts(ty: &Type) -> bool { 29 | *ty == Type::DATE && T::accepts(ty) 30 | } 31 | } 32 | 33 | impl ToSql for Date { 34 | fn to_sql( 35 | &self, 36 | ty: &Type, 37 | out: &mut BytesMut, 38 | ) -> Result> { 39 | let value = match *self { 40 | Date::PosInfinity => i32::MAX, 41 | Date::NegInfinity => i32::MIN, 42 | Date::Value(ref v) => return v.to_sql(ty, out), 43 | }; 44 | 45 | types::date_to_sql(value, out); 46 | Ok(IsNull::No) 47 | } 48 | 49 | fn accepts(ty: &Type) -> bool { 50 | *ty == Type::DATE && T::accepts(ty) 51 | } 52 | 53 | to_sql_checked!(); 54 | } 55 | 56 | /// A wrapper that can be used to represent infinity with `Type::Timestamp` and `Type::Timestamptz` 57 | /// types. 58 | #[derive(Debug, Clone, Copy, PartialEq)] 59 | pub enum Timestamp { 60 | /// Represents `infinity`, a timestamp that is later than all other timestamps. 61 | PosInfinity, 62 | /// Represents `-infinity`, a timestamp that is earlier than all other timestamps. 63 | NegInfinity, 64 | /// The wrapped timestamp. 65 | Value(T), 66 | } 67 | 68 | impl<'a, T: FromSql<'a>> FromSql<'a> for Timestamp { 69 | fn from_sql(ty: &Type, raw: &'a [u8]) -> Result> { 70 | match types::timestamp_from_sql(raw)? { 71 | i64::MAX => Ok(Timestamp::PosInfinity), 72 | i64::MIN => Ok(Timestamp::NegInfinity), 73 | _ => T::from_sql(ty, raw).map(Timestamp::Value), 74 | } 75 | } 76 | 77 | fn accepts(ty: &Type) -> bool { 78 | match *ty { 79 | Type::TIMESTAMP | Type::TIMESTAMPTZ if T::accepts(ty) => true, 80 | _ => false, 81 | } 82 | } 83 | } 84 | 85 | impl ToSql for Timestamp { 86 | fn to_sql( 87 | &self, 88 | ty: &Type, 89 | out: &mut BytesMut, 90 | ) -> Result> { 91 | let value = match *self { 92 | Timestamp::PosInfinity => i64::MAX, 93 | Timestamp::NegInfinity => i64::MIN, 94 | Timestamp::Value(ref v) => return v.to_sql(ty, out), 95 | }; 96 | 97 | types::timestamp_to_sql(value, out); 98 | Ok(IsNull::No) 99 | } 100 | 101 | fn accepts(ty: &Type) -> bool { 102 | match *ty { 103 | Type::TIMESTAMP | Type::TIMESTAMPTZ if T::accepts(ty) => true, 104 | _ => false, 105 | } 106 | } 107 | 108 | to_sql_checked!(); 109 | } 110 | -------------------------------------------------------------------------------- /tokio-postgres/tests/test/parse.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use tokio_postgres::config::{Config, TargetSessionAttrs}; 3 | 4 | fn check(s: &str, config: &Config) { 5 | assert_eq!(s.parse::().expect(s), *config, "`{}`", s); 6 | } 7 | 8 | #[test] 9 | fn pairs_ok() { 10 | check( 11 | r"user=foo password=' fizz \'buzz\\ ' application_name = ''", 12 | Config::new() 13 | .user("foo") 14 | .password(r" fizz 'buzz\ ") 15 | .application_name(""), 16 | ); 17 | } 18 | 19 | #[test] 20 | fn pairs_ws() { 21 | check( 22 | " user\t=\r\n\x0bfoo \t password = hunter2 ", 23 | Config::new().user("foo").password("hunter2"), 24 | ); 25 | } 26 | 27 | #[test] 28 | fn settings() { 29 | check( 30 | "connect_timeout=3 keepalives=0 keepalives_idle=30 target_session_attrs=read-write", 31 | Config::new() 32 | .connect_timeout(Duration::from_secs(3)) 33 | .keepalives(false) 34 | .keepalives_idle(Duration::from_secs(30)) 35 | .target_session_attrs(TargetSessionAttrs::ReadWrite), 36 | ); 37 | } 38 | 39 | #[test] 40 | fn url() { 41 | check("postgresql://", &Config::new()); 42 | check( 43 | "postgresql://localhost", 44 | Config::new().host("localhost").port(5432), 45 | ); 46 | check( 47 | "postgresql://localhost:5433", 48 | Config::new().host("localhost").port(5433), 49 | ); 50 | check( 51 | "postgresql://localhost/mydb", 52 | Config::new().host("localhost").port(5432).dbname("mydb"), 53 | ); 54 | check( 55 | "postgresql://user@localhost", 56 | Config::new().user("user").host("localhost").port(5432), 57 | ); 58 | check( 59 | "postgresql://user:secret@localhost", 60 | Config::new() 61 | .user("user") 62 | .password("secret") 63 | .host("localhost") 64 | .port(5432), 65 | ); 66 | check( 67 | "postgresql://other@localhost/otherdb?connect_timeout=10&application_name=myapp", 68 | Config::new() 69 | .user("other") 70 | .host("localhost") 71 | .port(5432) 72 | .dbname("otherdb") 73 | .connect_timeout(Duration::from_secs(10)) 74 | .application_name("myapp"), 75 | ); 76 | check( 77 | "postgresql://host1:123,host2:456/somedb?target_session_attrs=any&application_name=myapp", 78 | Config::new() 79 | .host("host1") 80 | .port(123) 81 | .host("host2") 82 | .port(456) 83 | .dbname("somedb") 84 | .target_session_attrs(TargetSessionAttrs::Any) 85 | .application_name("myapp"), 86 | ); 87 | check( 88 | "postgresql:///mydb?host=localhost&port=5433", 89 | Config::new().dbname("mydb").host("localhost").port(5433), 90 | ); 91 | check( 92 | "postgresql://[2001:db8::1234]/database", 93 | Config::new() 94 | .host("2001:db8::1234") 95 | .port(5432) 96 | .dbname("database"), 97 | ); 98 | check( 99 | "postgresql://[2001:db8::1234]:5433/database", 100 | Config::new() 101 | .host("2001:db8::1234") 102 | .port(5433) 103 | .dbname("database"), 104 | ); 105 | #[cfg(unix)] 106 | check( 107 | "postgresql:///dbname?host=/var/lib/postgresql", 108 | Config::new() 109 | .dbname("dbname") 110 | .host_path("/var/lib/postgresql"), 111 | ); 112 | #[cfg(unix)] 113 | check( 114 | "postgresql://%2Fvar%2Flib%2Fpostgresql/dbname", 115 | Config::new() 116 | .host_path("/var/lib/postgresql") 117 | .port(5432) 118 | .dbname("dbname"), 119 | ) 120 | } 121 | -------------------------------------------------------------------------------- /postgres-openssl/src/test.rs: -------------------------------------------------------------------------------- 1 | use futures::FutureExt; 2 | use openssl::ssl::{SslConnector, SslMethod}; 3 | use tokio::net::TcpStream; 4 | use tokio_postgres::tls::TlsConnect; 5 | 6 | use super::*; 7 | 8 | async fn smoke_test(s: &str, tls: T) 9 | where 10 | T: TlsConnect, 11 | T::Stream: 'static + Send, 12 | { 13 | let stream = TcpStream::connect("127.0.0.1:5433").await.unwrap(); 14 | 15 | let builder = s.parse::().unwrap(); 16 | let (client, connection) = builder.connect_raw(stream, tls).await.unwrap(); 17 | 18 | let connection = connection.map(|r| r.unwrap()); 19 | tokio::spawn(connection); 20 | 21 | let stmt = client.prepare("SELECT $1::INT4").await.unwrap(); 22 | let rows = client.query(&stmt, &[&1i32]).await.unwrap(); 23 | 24 | assert_eq!(rows.len(), 1); 25 | assert_eq!(rows[0].get::<_, i32>(0), 1); 26 | } 27 | 28 | #[tokio::test] 29 | async fn require() { 30 | let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); 31 | builder.set_ca_file("../test/server.crt").unwrap(); 32 | let ctx = builder.build(); 33 | smoke_test( 34 | "user=ssl_user dbname=postgres sslmode=require", 35 | TlsConnector::new(ctx.configure().unwrap(), "localhost"), 36 | ) 37 | .await; 38 | } 39 | 40 | #[tokio::test] 41 | async fn prefer() { 42 | let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); 43 | builder.set_ca_file("../test/server.crt").unwrap(); 44 | let ctx = builder.build(); 45 | smoke_test( 46 | "user=ssl_user dbname=postgres", 47 | TlsConnector::new(ctx.configure().unwrap(), "localhost"), 48 | ) 49 | .await; 50 | } 51 | 52 | #[tokio::test] 53 | async fn scram_user() { 54 | let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); 55 | builder.set_ca_file("../test/server.crt").unwrap(); 56 | let ctx = builder.build(); 57 | smoke_test( 58 | "user=scram_user password=password dbname=postgres sslmode=require", 59 | TlsConnector::new(ctx.configure().unwrap(), "localhost"), 60 | ) 61 | .await; 62 | } 63 | 64 | #[tokio::test] 65 | async fn require_channel_binding_err() { 66 | let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); 67 | builder.set_ca_file("../test/server.crt").unwrap(); 68 | let ctx = builder.build(); 69 | let connector = TlsConnector::new(ctx.configure().unwrap(), "localhost"); 70 | 71 | let stream = TcpStream::connect("127.0.0.1:5433").await.unwrap(); 72 | let builder = "user=pass_user password=password dbname=postgres channel_binding=require" 73 | .parse::() 74 | .unwrap(); 75 | builder.connect_raw(stream, connector).await.err().unwrap(); 76 | } 77 | 78 | #[tokio::test] 79 | async fn require_channel_binding_ok() { 80 | let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); 81 | builder.set_ca_file("../test/server.crt").unwrap(); 82 | let ctx = builder.build(); 83 | smoke_test( 84 | "user=scram_user password=password dbname=postgres channel_binding=require", 85 | TlsConnector::new(ctx.configure().unwrap(), "localhost"), 86 | ) 87 | .await; 88 | } 89 | 90 | #[tokio::test] 91 | #[cfg(feature = "runtime")] 92 | async fn runtime() { 93 | let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); 94 | builder.set_ca_file("../test/server.crt").unwrap(); 95 | let connector = MakeTlsConnector::new(builder.build()); 96 | 97 | let (client, connection) = tokio_postgres::connect( 98 | "host=localhost port=5433 user=postgres sslmode=require", 99 | connector, 100 | ) 101 | .await 102 | .unwrap(); 103 | let connection = connection.map(|r| r.unwrap()); 104 | tokio::spawn(connection); 105 | 106 | let stmt = client.prepare("SELECT $1::INT4").await.unwrap(); 107 | let rows = client.query(&stmt, &[&1i32]).await.unwrap(); 108 | 109 | assert_eq!(rows.len(), 1); 110 | assert_eq!(rows[0].get::<_, i32>(0), 1); 111 | } 112 | -------------------------------------------------------------------------------- /tokio-postgres/src/simple_query.rs: -------------------------------------------------------------------------------- 1 | use fallible_iterator::FallibleIterator; 2 | use futures::{ready, Stream}; 3 | use log::debug; 4 | use pin_project_lite::pin_project; 5 | use postgres_protocol::message::backend::Message; 6 | use postgres_protocol::message::frontend; 7 | use std::collections::VecDeque; 8 | use std::marker::PhantomPinned; 9 | use std::pin::Pin; 10 | use std::rc::Rc; 11 | use std::task::{Context, Poll}; 12 | 13 | use ntex::util::Bytes; 14 | 15 | use crate::client::{InnerClient, Responses}; 16 | use crate::codec::FrontendMessage; 17 | use crate::{Error, SimpleQueryMessage, SimpleQueryRow}; 18 | 19 | pub async fn simple_query(client: &InnerClient, query: &str) -> Result { 20 | debug!("executing simple query: {}", query); 21 | 22 | let buf = encode(client, query)?; 23 | let messages = client.send(FrontendMessage::Raw(buf))?.receiver.await?; 24 | 25 | Ok(SimpleQueryStream { 26 | messages, 27 | columns: None, 28 | }) 29 | } 30 | 31 | pub async fn batch_execute(client: &InnerClient, query: &str) -> Result<(), Error> { 32 | debug!("executing statement batch: {}", query); 33 | 34 | let buf = encode(client, query)?; 35 | let mut responses = client 36 | .send(FrontendMessage::Raw(buf))? 37 | .receiver 38 | .await? 39 | .into_iter(); 40 | 41 | loop { 42 | match responses.next() { 43 | Some(Message::ReadyForQuery(_)) => return Ok(()), 44 | Some(Message::CommandComplete(_)) 45 | | Some(Message::EmptyQueryResponse) 46 | | Some(Message::RowDescription(_)) 47 | | Some(Message::DataRow(_)) => {} 48 | _ => return Err(Error::unexpected_message()), 49 | } 50 | } 51 | } 52 | 53 | fn encode(client: &InnerClient, query: &str) -> Result { 54 | client.with_buf(|buf| { 55 | frontend::query(query, buf).map_err(Error::encode)?; 56 | Ok(buf.split().freeze()) 57 | }) 58 | } 59 | 60 | /// A stream of simple query results. 61 | pub struct SimpleQueryStream { 62 | messages: VecDeque, 63 | columns: Option>, 64 | } 65 | 66 | impl Stream for SimpleQueryStream { 67 | type Item = Result; 68 | 69 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 70 | let mut this = self.as_mut(); 71 | loop { 72 | match this.messages.pop_front() { 73 | Some(Message::CommandComplete(body)) => { 74 | let rows = body 75 | .tag() 76 | .map_err(Error::parse)? 77 | .rsplit(' ') 78 | .next() 79 | .unwrap() 80 | .parse() 81 | .unwrap_or(0); 82 | return Poll::Ready(Some(Ok(SimpleQueryMessage::CommandComplete(rows)))); 83 | } 84 | Some(Message::EmptyQueryResponse) => { 85 | return Poll::Ready(Some(Ok(SimpleQueryMessage::CommandComplete(0)))); 86 | } 87 | Some(Message::RowDescription(body)) => { 88 | let columns = body 89 | .fields() 90 | .map(|f| Ok(f.name().to_string())) 91 | .collect::>() 92 | .map_err(Error::parse)? 93 | .into(); 94 | this.columns = Some(columns); 95 | } 96 | Some(Message::DataRow(body)) => { 97 | let row = match &this.columns { 98 | Some(columns) => SimpleQueryRow::new(columns.clone(), body)?, 99 | None => return Poll::Ready(Some(Err(Error::unexpected_message()))), 100 | }; 101 | return Poll::Ready(Some(Ok(SimpleQueryMessage::Row(row)))); 102 | } 103 | Some(Message::ReadyForQuery(_)) => return Poll::Ready(None), 104 | _ => return Poll::Ready(Some(Err(Error::unexpected_message()))), 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /postgres-protocol/src/types/test.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use fallible_iterator::FallibleIterator; 3 | use std::collections::HashMap; 4 | 5 | use super::*; 6 | use crate::IsNull; 7 | 8 | #[test] 9 | fn bool() { 10 | let mut buf = BytesMut::new(); 11 | bool_to_sql(true, &mut buf); 12 | assert_eq!(bool_from_sql(&buf).unwrap(), true); 13 | 14 | let mut buf = BytesMut::new(); 15 | bool_to_sql(false, &mut buf); 16 | assert_eq!(bool_from_sql(&buf).unwrap(), false); 17 | } 18 | 19 | #[test] 20 | fn int2() { 21 | let mut buf = BytesMut::new(); 22 | int2_to_sql(0x0102, &mut buf); 23 | assert_eq!(int2_from_sql(&buf).unwrap(), 0x0102); 24 | } 25 | 26 | #[test] 27 | fn int4() { 28 | let mut buf = BytesMut::new(); 29 | int4_to_sql(0x0102_0304, &mut buf); 30 | assert_eq!(int4_from_sql(&buf).unwrap(), 0x0102_0304); 31 | } 32 | 33 | #[test] 34 | fn int8() { 35 | let mut buf = BytesMut::new(); 36 | int8_to_sql(0x0102_0304_0506_0708, &mut buf); 37 | assert_eq!(int8_from_sql(&buf).unwrap(), 0x0102_0304_0506_0708); 38 | } 39 | 40 | #[test] 41 | #[allow(clippy::float_cmp)] 42 | fn float4() { 43 | let mut buf = BytesMut::new(); 44 | float4_to_sql(10343.95, &mut buf); 45 | assert_eq!(float4_from_sql(&buf).unwrap(), 10343.95); 46 | } 47 | 48 | #[test] 49 | #[allow(clippy::float_cmp)] 50 | fn float8() { 51 | let mut buf = BytesMut::new(); 52 | float8_to_sql(10343.95, &mut buf); 53 | assert_eq!(float8_from_sql(&buf).unwrap(), 10343.95); 54 | } 55 | 56 | #[test] 57 | fn hstore() { 58 | let mut map = HashMap::new(); 59 | map.insert("hello", Some("world")); 60 | map.insert("hola", None); 61 | 62 | let mut buf = BytesMut::new(); 63 | hstore_to_sql(map.iter().map(|(&k, &v)| (k, v)), &mut buf).unwrap(); 64 | assert_eq!( 65 | hstore_from_sql(&buf) 66 | .unwrap() 67 | .collect::>() 68 | .unwrap(), 69 | map 70 | ); 71 | } 72 | 73 | #[test] 74 | fn varbit() { 75 | let len = 12; 76 | let bits = [0b0010_1011, 0b0000_1111]; 77 | 78 | let mut buf = BytesMut::new(); 79 | varbit_to_sql(len, bits.iter().cloned(), &mut buf).unwrap(); 80 | let out = varbit_from_sql(&buf).unwrap(); 81 | assert_eq!(out.len(), len); 82 | assert_eq!(out.bytes(), bits); 83 | } 84 | 85 | #[test] 86 | fn array() { 87 | let dimensions = [ 88 | ArrayDimension { 89 | len: 1, 90 | lower_bound: 10, 91 | }, 92 | ArrayDimension { 93 | len: 2, 94 | lower_bound: 0, 95 | }, 96 | ]; 97 | let values = [None, Some(&b"hello"[..])]; 98 | 99 | let mut buf = BytesMut::new(); 100 | array_to_sql( 101 | dimensions.iter().cloned(), 102 | 10, 103 | values.iter().cloned(), 104 | |v, buf| match v { 105 | Some(v) => { 106 | buf.extend_from_slice(v); 107 | Ok(IsNull::No) 108 | } 109 | None => Ok(IsNull::Yes), 110 | }, 111 | &mut buf, 112 | ) 113 | .unwrap(); 114 | 115 | let array = array_from_sql(&buf).unwrap(); 116 | assert_eq!(array.has_nulls(), true); 117 | assert_eq!(array.element_type(), 10); 118 | assert_eq!(array.dimensions().collect::>().unwrap(), dimensions); 119 | assert_eq!(array.values().collect::>().unwrap(), values); 120 | } 121 | 122 | #[test] 123 | fn non_null_array() { 124 | let dimensions = [ 125 | ArrayDimension { 126 | len: 1, 127 | lower_bound: 10, 128 | }, 129 | ArrayDimension { 130 | len: 2, 131 | lower_bound: 0, 132 | }, 133 | ]; 134 | let values = [Some(&b"hola"[..]), Some(&b"hello"[..])]; 135 | 136 | let mut buf = BytesMut::new(); 137 | array_to_sql( 138 | dimensions.iter().cloned(), 139 | 10, 140 | values.iter().cloned(), 141 | |v, buf| match v { 142 | Some(v) => { 143 | buf.extend_from_slice(v); 144 | Ok(IsNull::No) 145 | } 146 | None => Ok(IsNull::Yes), 147 | }, 148 | &mut buf, 149 | ) 150 | .unwrap(); 151 | 152 | let array = array_from_sql(&buf).unwrap(); 153 | assert_eq!(array.has_nulls(), false); 154 | assert_eq!(array.element_type(), 10); 155 | assert_eq!(array.dimensions().collect::>().unwrap(), dimensions); 156 | assert_eq!(array.values().collect::>().unwrap(), values); 157 | } 158 | -------------------------------------------------------------------------------- /tokio-postgres/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.5.1 - 2019-12-25 4 | 5 | ### Fixed 6 | 7 | * Removed some stray `println!`s from `copy_out` internals. 8 | 9 | ## v0.5.0 - 2019-12-23 10 | 11 | ### Changed 12 | 13 | * `Client::copy_in` now returns a `Sink` rather than taking in a `Stream`. 14 | * `CopyStream` has been renamed to `CopyOutStream`. 15 | * `Client::copy_in` and `Client::copy_out` no longer take query parameters as PostgreSQL doesn't support parameters in 16 | COPY queries. 17 | * `TargetSessionAttrs`, `SslMode`, and `ChannelBinding` are now true non-exhaustive enums. 18 | 19 | ### Added 20 | 21 | * Added `Client::query_opt` for queries expected to return zero or one rows. 22 | * Added binary copy format support to the `binary_copy` module. 23 | * Added back query logging. 24 | 25 | ### Removed 26 | 27 | * Removed `uuid` 0.7 support. 28 | 29 | ## v0.5.0-alpha.2 - 2019-11-27 30 | 31 | ### Changed 32 | 33 | * Upgraded `bytes` to 0.5. 34 | * Upgraded `tokio` to 0.2. 35 | * The TLS interface uses a trait to obtain channel binding information rather than returning it after the handshake. 36 | * Changed the value of the `timezone` property from `GMT` to `UTC`. 37 | * Returned `Stream` implementations are now `!Unpin`. 38 | 39 | ### Added 40 | 41 | * Added support for `uuid` 0.8. 42 | * Added the column to `Row::try_get` errors. 43 | 44 | ## v0.5.0-alpha.1 - 2019-10-14 45 | 46 | ### Changed 47 | 48 | * The library now uses `std::futures::Future` and async/await syntax. 49 | * Most methods now take `&self` rather than `&mut self`. 50 | * The transaction API has changed to more closely resemble the synchronous API and is significantly more ergonomic. 51 | * Methods now take `&[&(dyn ToSql + Sync)]` rather than `&[&dyn ToSql]` to allow futures to be `Send`. 52 | * Methods are now "normal" async functions that no longer do work up-front. 53 | * Statements are no longer required to be prepared explicitly before use. Methods taking `&Statement` can now also take 54 | `&str`, and will internally prepare the statement. 55 | * `ToSql` now serializes its value into a `BytesMut` rather than `Vec`. 56 | * Methods that previously returned `Stream`s now return `Vec`. New `*_raw` methods still provide a `Stream` 57 | interface. 58 | 59 | ### Added 60 | 61 | * Added the `channel_binding=disable/allow/require` configuration to control use of channel binding. 62 | * Added the `Client::query_one` method to cover the common case of a query that returns exactly one row. 63 | 64 | ## v0.4.0-rc.3 - 2019-06-29 65 | 66 | ### Fixed 67 | 68 | * Significantly improved the performance of `query` and `copy_in`. 69 | 70 | ### Changed 71 | 72 | * The items of the stream passed to `copy_in` must be `'static`. 73 | 74 | ## v0.4.0-rc.2 - 2019-03-05 75 | 76 | ### Fixed 77 | 78 | * Fixed Cargo features to actually enable the functionality they claim to. 79 | 80 | ## v0.4.0-rc.1 - 2019-03-05 81 | 82 | ### Changed 83 | 84 | * The client API has been significantly overhauled. It now resembles `hyper`'s, with separate `Connection` and `Client` 85 | objects. See the crate-level documentation for more details. 86 | * The TLS connection mode (e.g. `prefer`) is now part of the connection configuration rather than being passed in 87 | separately. 88 | * The Cargo features enabling `ToSql` and `FromSql` implementations for external crates are now versioned. For example, 89 | `with-uuid` is now `with-uuid-0_7`. This enables us to add support for new major versions of the crates in parallel 90 | without breaking backwards compatibility. 91 | * Upgraded from `tokio-core` to `tokio`. 92 | 93 | ### Added 94 | 95 | * Connection string configuration now more fully mirrors libpq's syntax, and supports both URL-style and key-value style 96 | strings. 97 | * `FromSql` implementations can now borrow from the data buffer. In particular, this means that you can deserialize 98 | values as `&str`. The `FromSqlOwned` trait can be used as a bound to restrict code to deserializing owned values. 99 | * Added support for channel binding with SCRAM authentication. 100 | * Added multi-host support in connection configuration. 101 | * The client now supports query pipelining, which can be used as a latency hiding measure. 102 | * While the crate uses `tokio` by default, the base API can be used with any asynchronous stream type on any reactor. 103 | * Added support for simple query requests returning row data. 104 | 105 | ### Removed 106 | 107 | * The `with-openssl` feature has been removed. Use the `tokio-postgres-openssl` crate instead. 108 | * The `with-rustc_serialize` and `with-time` features have been removed. Use `serde` and `SystemTime` or `chrono` 109 | instead. 110 | 111 | ## Older 112 | 113 | Look at the [release tags] for information about older releases. 114 | 115 | [release tags]: https://github.com/sfackler/rust-postgres/releases 116 | -------------------------------------------------------------------------------- /tokio-postgres/tests/test/types/chrono_04.rs: -------------------------------------------------------------------------------- 1 | use chrono_04::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; 2 | use tokio_postgres::types::{Date, Timestamp}; 3 | 4 | use crate::types::test_type; 5 | 6 | #[tokio::test] 7 | async fn test_naive_date_time_params() { 8 | fn make_check(time: &str) -> (Option, &str) { 9 | ( 10 | Some(NaiveDateTime::parse_from_str(time, "'%Y-%m-%d %H:%M:%S.%f'").unwrap()), 11 | time, 12 | ) 13 | } 14 | test_type( 15 | "TIMESTAMP", 16 | &[ 17 | make_check("'1970-01-01 00:00:00.010000000'"), 18 | make_check("'1965-09-25 11:19:33.100314000'"), 19 | make_check("'2010-02-09 23:11:45.120200000'"), 20 | (None, "NULL"), 21 | ], 22 | ) 23 | .await; 24 | } 25 | 26 | #[tokio::test] 27 | async fn test_with_special_naive_date_time_params() { 28 | fn make_check(time: &str) -> (Timestamp, &str) { 29 | ( 30 | Timestamp::Value( 31 | NaiveDateTime::parse_from_str(time, "'%Y-%m-%d %H:%M:%S.%f'").unwrap(), 32 | ), 33 | time, 34 | ) 35 | } 36 | test_type( 37 | "TIMESTAMP", 38 | &[ 39 | make_check("'1970-01-01 00:00:00.010000000'"), 40 | make_check("'1965-09-25 11:19:33.100314000'"), 41 | make_check("'2010-02-09 23:11:45.120200000'"), 42 | (Timestamp::PosInfinity, "'infinity'"), 43 | (Timestamp::NegInfinity, "'-infinity'"), 44 | ], 45 | ) 46 | .await; 47 | } 48 | 49 | #[tokio::test] 50 | async fn test_date_time_params() { 51 | fn make_check(time: &str) -> (Option>, &str) { 52 | ( 53 | Some( 54 | Utc.datetime_from_str(time, "'%Y-%m-%d %H:%M:%S.%f'") 55 | .unwrap(), 56 | ), 57 | time, 58 | ) 59 | } 60 | test_type( 61 | "TIMESTAMP WITH TIME ZONE", 62 | &[ 63 | make_check("'1970-01-01 00:00:00.010000000'"), 64 | make_check("'1965-09-25 11:19:33.100314000'"), 65 | make_check("'2010-02-09 23:11:45.120200000'"), 66 | (None, "NULL"), 67 | ], 68 | ) 69 | .await; 70 | } 71 | 72 | #[tokio::test] 73 | async fn test_with_special_date_time_params() { 74 | fn make_check(time: &str) -> (Timestamp>, &str) { 75 | ( 76 | Timestamp::Value( 77 | Utc.datetime_from_str(time, "'%Y-%m-%d %H:%M:%S.%f'") 78 | .unwrap(), 79 | ), 80 | time, 81 | ) 82 | } 83 | test_type( 84 | "TIMESTAMP WITH TIME ZONE", 85 | &[ 86 | make_check("'1970-01-01 00:00:00.010000000'"), 87 | make_check("'1965-09-25 11:19:33.100314000'"), 88 | make_check("'2010-02-09 23:11:45.120200000'"), 89 | (Timestamp::PosInfinity, "'infinity'"), 90 | (Timestamp::NegInfinity, "'-infinity'"), 91 | ], 92 | ) 93 | .await; 94 | } 95 | 96 | #[tokio::test] 97 | async fn test_date_params() { 98 | fn make_check(time: &str) -> (Option, &str) { 99 | ( 100 | Some(NaiveDate::parse_from_str(time, "'%Y-%m-%d'").unwrap()), 101 | time, 102 | ) 103 | } 104 | test_type( 105 | "DATE", 106 | &[ 107 | make_check("'1970-01-01'"), 108 | make_check("'1965-09-25'"), 109 | make_check("'2010-02-09'"), 110 | (None, "NULL"), 111 | ], 112 | ) 113 | .await; 114 | } 115 | 116 | #[tokio::test] 117 | async fn test_with_special_date_params() { 118 | fn make_check(date: &str) -> (Date, &str) { 119 | ( 120 | Date::Value(NaiveDate::parse_from_str(date, "'%Y-%m-%d'").unwrap()), 121 | date, 122 | ) 123 | } 124 | test_type( 125 | "DATE", 126 | &[ 127 | make_check("'1970-01-01'"), 128 | make_check("'1965-09-25'"), 129 | make_check("'2010-02-09'"), 130 | (Date::PosInfinity, "'infinity'"), 131 | (Date::NegInfinity, "'-infinity'"), 132 | ], 133 | ) 134 | .await; 135 | } 136 | 137 | #[tokio::test] 138 | async fn test_time_params() { 139 | fn make_check(time: &str) -> (Option, &str) { 140 | ( 141 | Some(NaiveTime::parse_from_str(time, "'%H:%M:%S.%f'").unwrap()), 142 | time, 143 | ) 144 | } 145 | test_type( 146 | "TIME", 147 | &[ 148 | make_check("'00:00:00.010000000'"), 149 | make_check("'11:19:33.100314000'"), 150 | make_check("'23:11:45.120200000'"), 151 | (None, "NULL"), 152 | ], 153 | ) 154 | .await; 155 | } 156 | -------------------------------------------------------------------------------- /postgres-types/src/chrono_04.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use chrono_04::{DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc}; 3 | use postgres_protocol::types; 4 | use std::error::Error; 5 | 6 | use crate::{FromSql, IsNull, ToSql, Type}; 7 | 8 | fn base() -> NaiveDateTime { 9 | NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0) 10 | } 11 | 12 | impl<'a> FromSql<'a> for NaiveDateTime { 13 | fn from_sql(_: &Type, raw: &[u8]) -> Result> { 14 | let t = types::timestamp_from_sql(raw)?; 15 | Ok(base() + Duration::microseconds(t)) 16 | } 17 | 18 | accepts!(TIMESTAMP); 19 | } 20 | 21 | impl ToSql for NaiveDateTime { 22 | fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result> { 23 | let time = match self.signed_duration_since(base()).num_microseconds() { 24 | Some(time) => time, 25 | None => return Err("value too large to transmit".into()), 26 | }; 27 | types::timestamp_to_sql(time, w); 28 | Ok(IsNull::No) 29 | } 30 | 31 | accepts!(TIMESTAMP); 32 | to_sql_checked!(); 33 | } 34 | 35 | impl<'a> FromSql<'a> for DateTime { 36 | fn from_sql(type_: &Type, raw: &[u8]) -> Result, Box> { 37 | let naive = NaiveDateTime::from_sql(type_, raw)?; 38 | Ok(DateTime::from_utc(naive, Utc)) 39 | } 40 | 41 | accepts!(TIMESTAMPTZ); 42 | } 43 | 44 | impl ToSql for DateTime { 45 | fn to_sql( 46 | &self, 47 | type_: &Type, 48 | w: &mut BytesMut, 49 | ) -> Result> { 50 | self.naive_utc().to_sql(type_, w) 51 | } 52 | 53 | accepts!(TIMESTAMPTZ); 54 | to_sql_checked!(); 55 | } 56 | 57 | impl<'a> FromSql<'a> for DateTime { 58 | fn from_sql(type_: &Type, raw: &[u8]) -> Result, Box> { 59 | let utc = DateTime::::from_sql(type_, raw)?; 60 | Ok(utc.with_timezone(&Local)) 61 | } 62 | 63 | accepts!(TIMESTAMPTZ); 64 | } 65 | 66 | impl ToSql for DateTime { 67 | fn to_sql( 68 | &self, 69 | type_: &Type, 70 | w: &mut BytesMut, 71 | ) -> Result> { 72 | self.with_timezone(&Utc).to_sql(type_, w) 73 | } 74 | 75 | accepts!(TIMESTAMPTZ); 76 | to_sql_checked!(); 77 | } 78 | 79 | impl<'a> FromSql<'a> for DateTime { 80 | fn from_sql( 81 | type_: &Type, 82 | raw: &[u8], 83 | ) -> Result, Box> { 84 | let utc = DateTime::::from_sql(type_, raw)?; 85 | Ok(utc.with_timezone(&FixedOffset::east(0))) 86 | } 87 | 88 | accepts!(TIMESTAMPTZ); 89 | } 90 | 91 | impl ToSql for DateTime { 92 | fn to_sql( 93 | &self, 94 | type_: &Type, 95 | w: &mut BytesMut, 96 | ) -> Result> { 97 | self.with_timezone(&Utc).to_sql(type_, w) 98 | } 99 | 100 | accepts!(TIMESTAMPTZ); 101 | to_sql_checked!(); 102 | } 103 | 104 | impl<'a> FromSql<'a> for NaiveDate { 105 | fn from_sql(_: &Type, raw: &[u8]) -> Result> { 106 | let jd = types::date_from_sql(raw)?; 107 | Ok(base().date() + Duration::days(i64::from(jd))) 108 | } 109 | 110 | accepts!(DATE); 111 | } 112 | 113 | impl ToSql for NaiveDate { 114 | fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result> { 115 | let jd = self.signed_duration_since(base().date()).num_days(); 116 | if jd > i64::from(i32::max_value()) || jd < i64::from(i32::min_value()) { 117 | return Err("value too large to transmit".into()); 118 | } 119 | 120 | types::date_to_sql(jd as i32, w); 121 | Ok(IsNull::No) 122 | } 123 | 124 | accepts!(DATE); 125 | to_sql_checked!(); 126 | } 127 | 128 | impl<'a> FromSql<'a> for NaiveTime { 129 | fn from_sql(_: &Type, raw: &[u8]) -> Result> { 130 | let usec = types::time_from_sql(raw)?; 131 | Ok(NaiveTime::from_hms(0, 0, 0) + Duration::microseconds(usec)) 132 | } 133 | 134 | accepts!(TIME); 135 | } 136 | 137 | impl ToSql for NaiveTime { 138 | fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result> { 139 | let delta = self.signed_duration_since(NaiveTime::from_hms(0, 0, 0)); 140 | let time = match delta.num_microseconds() { 141 | Some(time) => time, 142 | None => return Err("value too large to transmit".into()), 143 | }; 144 | types::time_to_sql(time, w); 145 | Ok(IsNull::No) 146 | } 147 | 148 | accepts!(TIME); 149 | to_sql_checked!(); 150 | } 151 | -------------------------------------------------------------------------------- /docker/sql_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cat > "$PGDATA/server.key" <<-EOKEY 5 | -----BEGIN RSA PRIVATE KEY----- 6 | MIIEpAIBAAKCAQEAllItXwrj62MkxKVlz2FimJk42WWc3K82Rn2vAl6z38zQxSCj 7 | t9uWwXWTx5YOdGiUcA+JUAruZxqN7vdfphJoYtTrcrpT4rC/FsCMImBxkj1cxdYT 8 | q94SFn9bQBRZk7RUx4Kolt+/h0d3PpNIb4DbyQ8A0MVvNVxLpRRVwc6yQP+NkRMy 9 | gHR+m3P8fxHEtkHCVy7HORbASvN8fRlREMHDL2hkadX0BNM72DDo+DWhPA8GF6WX 10 | tIl1gU6GP6pSbEeMHD3f+uj7f9iSjvkrHrOt2nLUQ9Qnev2nhmU0/dOIweQ17/Fr 11 | lL9jYDUUFNORyjRnlXXUoP5BO/LdEAAqT2A0pwIDAQABAoIBAQCIXu74XUneHuiZ 12 | Wa+eTqwC4mZXmz6OWonzs0vU65NlgksXuv+r6ZO/2GoD1Bcy9jlL3Fxm+DPF56pB 13 | 07u7TtHSb3VWdMFrU4tYGcBH45TE5dRHSmo4LlPcgxeGb6/ANwX+pYNKtJvuHyCH 14 | 7Vf2iEFcCrdjrumv0BZ0IZmXJGxEV+7mK2Og0bZ/zbmJNaH25muuWj6BKlvLhL0N 15 | S2LlBjKx3HqtppUgUqNFqjLs6IA1u79S5dAomOsxZtnuByaX5WFzpktU2pveZmyF 16 | cl0dwHYZIaxR3ewYeQXGF8ANUmIx3nnxD2JOysPkitaGzeqt6dQZV14tPlDZDKat 17 | Vf0b6BHhAoGBAMWV7rG+7nVXoQ30CIcPGklkST3mVOlrzeBbKP1SeAwoGRbfsdhp 18 | rFMkh5UxTexnOzD4O8HPuJ6NGeWRQfqZT1nnjwHPeJWtiMHT6cnWxlzvxAZ61mio 19 | 0jRfb8flhgFKk+G9+Xa6WaYAAwGWdF062EMe2Ym92oKM9ilTPGFVRk1XAoGBAMLD 20 | ETSQd2UqTF/y7wxMPqF3l6d1KBjwpuNuin2IjkXTOfGkDnAU3mSQlr7K1IPX8NPO 21 | gdyMfJoysfRaBuRcNA/o/0l0wyxW4HWtTtPYI0+pRCFtRLsI1MB997QKeaGKb+me 22 | 3nBXkOksPSr9oa0Cs27z2cSoBOkpq2N/zzBseHExAoGAOyq3rKBZNehEwTHnb9I0 23 | 8+9FA3U6zh9LKjkCIEGW00Uapj/cOMsEIG2a8DEwfW84SWS8OEBkr43fSGBkGo/Y 24 | NDrkFw2ytVee0TQNGTTod6IQ2EPmera7I5XEml5/71kOyZWi40vQVqZAQDR2qgha 25 | BFdzmwywJ1Hg0OUs+pSXlccCgYEAgyOVki80NYolovWQwFcWVOKR2s+oECL6PGlS 26 | FvS714hCm9I7ZnymwlAZMJ6iOaRNJFEIX9i4jZtU95Mm0NzEsXHRc0SLpm9Y8+Oe 27 | EEaYgCsZFOjePpHTr0kiYLgs7fipIkU2wa40hMyk4y2kjzoiV7MaDrCTnevQ205T 28 | 0+c1sgECgYBAXKcwdkh9JVSrLXFamsxiOx3MZ0n6J1d28wpdA3y4Y4AAJm4TGgFt 29 | eG/6qHRy6CHdFtJ7a84EMe1jaVLQJYW/VrOC2bWLftkU7qaOnkXHvr4CAHsXQHcx 30 | JhLfvh4ab3KyoK/iimifvcoS5z9gp7IBFKMyh5IeJ9Y75TgcfJ5HMg== 31 | -----END RSA PRIVATE KEY----- 32 | EOKEY 33 | chmod 0600 "$PGDATA/server.key" 34 | 35 | cat > "$PGDATA/server.crt" <<-EOCERT 36 | -----BEGIN CERTIFICATE----- 37 | MIID9DCCAtygAwIBAgIJAIYfg4EQ2pVAMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV 38 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 39 | aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNjA2MjgyMjQw 40 | NDFaFw0yNjA2MjYyMjQwNDFaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21l 41 | LVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV 42 | BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJZS 43 | LV8K4+tjJMSlZc9hYpiZONllnNyvNkZ9rwJes9/M0MUgo7fblsF1k8eWDnRolHAP 44 | iVAK7mcaje73X6YSaGLU63K6U+KwvxbAjCJgcZI9XMXWE6veEhZ/W0AUWZO0VMeC 45 | qJbfv4dHdz6TSG+A28kPANDFbzVcS6UUVcHOskD/jZETMoB0fptz/H8RxLZBwlcu 46 | xzkWwErzfH0ZURDBwy9oZGnV9ATTO9gw6Pg1oTwPBhell7SJdYFOhj+qUmxHjBw9 47 | 3/ro+3/Yko75Kx6zrdpy1EPUJ3r9p4ZlNP3TiMHkNe/xa5S/Y2A1FBTTkco0Z5V1 48 | 1KD+QTvy3RAAKk9gNKcCAwEAAaOBvjCBuzAdBgNVHQ4EFgQUEcuoFxzUZ4VV9VPv 49 | 5frDyIuFA5cwgYsGA1UdIwSBgzCBgIAUEcuoFxzUZ4VV9VPv5frDyIuFA5ehXaRb 50 | MFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJ 51 | bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdIIJAIYf 52 | g4EQ2pVAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHwMzmXdtz3R 53 | 83HIdRQic40bJQf9ucSwY5ArkttPhC8ewQGyiGexm1Tvx9YA/qT2rscKPHXCPYcP 54 | IUE+nJTc8lQb8wPnFwGdHUsJfCvurxE4Yv4Oi74+q1enhHBGsvhFdFY5jTYD9unM 55 | zBEn+ZHX3PlKhe3wMub4khBTbPLK+n/laQWuZNsa+kj7BynkAg8W/6RK0Z0cJzzw 56 | aiVP0bSvatAAcSwkEfKEv5xExjWqoewjSlQLEZYIjJhXdtx/8AMnrcyxrFvKALUQ 57 | 9M15FXvlPOB7ez14xIXQBKvvLwXvteHF6kYbzg/Bl1Q2GE9usclPa4UvTpnLv6gq 58 | NmFaAhoxnXA= 59 | -----END CERTIFICATE----- 60 | EOCERT 61 | 62 | cat >> "$PGDATA/postgresql.conf" <<-EOCONF 63 | port = 5433 64 | ssl = on 65 | ssl_cert_file = 'server.crt' 66 | ssl_key_file = 'server.key' 67 | EOCONF 68 | 69 | cat > "$PGDATA/pg_hba.conf" <<-EOCONF 70 | # TYPE DATABASE USER ADDRESS METHOD 71 | host all pass_user 0.0.0.0/0 password 72 | host all md5_user 0.0.0.0/0 md5 73 | host all scram_user 0.0.0.0/0 scram-sha-256 74 | host all pass_user ::0/0 password 75 | host all md5_user ::0/0 md5 76 | host all scram_user ::0/0 scram-sha-256 77 | 78 | hostssl all ssl_user 0.0.0.0/0 trust 79 | hostssl all ssl_user ::0/0 trust 80 | host all ssl_user 0.0.0.0/0 reject 81 | host all ssl_user ::0/0 reject 82 | 83 | # IPv4 local connections: 84 | host all postgres 0.0.0.0/0 trust 85 | # IPv6 local connections: 86 | host all postgres ::0/0 trust 87 | # Unix socket connections: 88 | local all postgres trust 89 | EOCONF 90 | 91 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL 92 | CREATE ROLE pass_user PASSWORD 'password' LOGIN; 93 | CREATE ROLE md5_user PASSWORD 'password' LOGIN; 94 | SET password_encryption TO 'scram-sha-256'; 95 | CREATE ROLE scram_user PASSWORD 'password' LOGIN; 96 | CREATE ROLE ssl_user LOGIN; 97 | CREATE EXTENSION hstore; 98 | CREATE EXTENSION citext; 99 | EOSQL 100 | -------------------------------------------------------------------------------- /tokio-postgres/src/tls.rs: -------------------------------------------------------------------------------- 1 | //! TLS support. 2 | 3 | use std::error::Error; 4 | use std::future::Future; 5 | use std::pin::Pin; 6 | use std::task::{Context, Poll}; 7 | use std::{fmt, io}; 8 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 9 | 10 | pub(crate) mod private { 11 | pub struct ForcePrivateApi; 12 | } 13 | 14 | /// Channel binding information returned from a TLS handshake. 15 | pub struct ChannelBinding { 16 | pub(crate) tls_server_end_point: Option>, 17 | } 18 | 19 | impl ChannelBinding { 20 | /// Creates a `ChannelBinding` containing no information. 21 | pub fn none() -> ChannelBinding { 22 | ChannelBinding { 23 | tls_server_end_point: None, 24 | } 25 | } 26 | 27 | /// Creates a `ChannelBinding` containing `tls-server-end-point` channel binding information. 28 | pub fn tls_server_end_point(tls_server_end_point: Vec) -> ChannelBinding { 29 | ChannelBinding { 30 | tls_server_end_point: Some(tls_server_end_point), 31 | } 32 | } 33 | } 34 | 35 | /// A constructor of `TlsConnect`ors. 36 | /// 37 | /// Requires the `runtime` Cargo feature (enabled by default). 38 | #[cfg(feature = "runtime")] 39 | pub trait MakeTlsConnect { 40 | /// The stream type created by the `TlsConnect` implementation. 41 | type Stream: TlsStream + Unpin; 42 | /// The `TlsConnect` implementation created by this type. 43 | type TlsConnect: TlsConnect; 44 | /// The error type returned by the `TlsConnect` implementation. 45 | type Error: Into>; 46 | 47 | /// Creates a new `TlsConnect`or. 48 | /// 49 | /// The domain name is provided for certificate verification and SNI. 50 | fn make_tls_connect(&mut self, domain: &str) -> Result; 51 | } 52 | 53 | /// An asynchronous function wrapping a stream in a TLS session. 54 | pub trait TlsConnect { 55 | /// The stream returned by the future. 56 | type Stream: TlsStream + Unpin; 57 | /// The error returned by the future. 58 | type Error: Into>; 59 | /// The future returned by the connector. 60 | type Future: Future>; 61 | 62 | /// Returns a future performing a TLS handshake over the stream. 63 | fn connect(self, stream: S) -> Self::Future; 64 | 65 | #[doc(hidden)] 66 | fn can_connect(&self, _: private::ForcePrivateApi) -> bool { 67 | true 68 | } 69 | } 70 | 71 | /// A TLS-wrapped connection to a PostgreSQL database. 72 | pub trait TlsStream: AsyncRead + AsyncWrite { 73 | /// Returns channel binding information for the session. 74 | fn channel_binding(&self) -> ChannelBinding; 75 | } 76 | 77 | /// A `MakeTlsConnect` and `TlsConnect` implementation which simply returns an error. 78 | /// 79 | /// This can be used when `sslmode` is `none` or `prefer`. 80 | #[derive(Debug, Copy, Clone)] 81 | pub struct NoTls; 82 | 83 | #[cfg(feature = "runtime")] 84 | impl MakeTlsConnect for NoTls { 85 | type Stream = NoTlsStream; 86 | type TlsConnect = NoTls; 87 | type Error = NoTlsError; 88 | 89 | fn make_tls_connect(&mut self, _: &str) -> Result { 90 | Ok(NoTls) 91 | } 92 | } 93 | 94 | impl TlsConnect for NoTls { 95 | type Stream = NoTlsStream; 96 | type Error = NoTlsError; 97 | type Future = NoTlsFuture; 98 | 99 | fn connect(self, _: S) -> NoTlsFuture { 100 | NoTlsFuture(()) 101 | } 102 | 103 | fn can_connect(&self, _: private::ForcePrivateApi) -> bool { 104 | false 105 | } 106 | } 107 | 108 | /// The future returned by `NoTls`. 109 | pub struct NoTlsFuture(()); 110 | 111 | impl Future for NoTlsFuture { 112 | type Output = Result; 113 | 114 | fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { 115 | Poll::Ready(Err(NoTlsError(()))) 116 | } 117 | } 118 | 119 | /// The TLS "stream" type produced by the `NoTls` connector. 120 | /// 121 | /// Since `NoTls` doesn't support TLS, this type is uninhabited. 122 | pub enum NoTlsStream {} 123 | 124 | impl AsyncRead for NoTlsStream { 125 | fn poll_read( 126 | self: Pin<&mut Self>, 127 | _: &mut Context<'_>, 128 | _: &mut ReadBuf<'_>, 129 | ) -> Poll> { 130 | match *self {} 131 | } 132 | } 133 | 134 | impl AsyncWrite for NoTlsStream { 135 | fn poll_write(self: Pin<&mut Self>, _: &mut Context<'_>, _: &[u8]) -> Poll> { 136 | match *self {} 137 | } 138 | 139 | fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { 140 | match *self {} 141 | } 142 | 143 | fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { 144 | match *self {} 145 | } 146 | } 147 | 148 | impl TlsStream for NoTlsStream { 149 | fn channel_binding(&self) -> ChannelBinding { 150 | match *self {} 151 | } 152 | } 153 | 154 | /// The error returned by `NoTls`. 155 | #[derive(Debug)] 156 | pub struct NoTlsError(()); 157 | 158 | impl fmt::Display for NoTlsError { 159 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 160 | fmt.write_str("no TLS implementation configured") 161 | } 162 | } 163 | 164 | impl Error for NoTlsError {} 165 | -------------------------------------------------------------------------------- /postgres-derive/src/tosql.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use std::iter; 4 | use syn::{Data, DataStruct, DeriveInput, Error, Fields, Ident}; 5 | 6 | use crate::accepts; 7 | use crate::composites::Field; 8 | use crate::enums::Variant; 9 | use crate::overrides::Overrides; 10 | 11 | pub fn expand_derive_tosql(input: DeriveInput) -> Result { 12 | let overrides = Overrides::extract(&input.attrs)?; 13 | 14 | let name = overrides.name.unwrap_or_else(|| input.ident.to_string()); 15 | 16 | let (accepts_body, to_sql_body) = match input.data { 17 | Data::Enum(ref data) => { 18 | let variants = data 19 | .variants 20 | .iter() 21 | .map(Variant::parse) 22 | .collect::, _>>()?; 23 | ( 24 | accepts::enum_body(&name, &variants), 25 | enum_body(&input.ident, &variants), 26 | ) 27 | } 28 | Data::Struct(DataStruct { 29 | fields: Fields::Unnamed(ref fields), 30 | .. 31 | }) if fields.unnamed.len() == 1 => { 32 | let field = fields.unnamed.first().unwrap(); 33 | (accepts::domain_body(&name, &field), domain_body()) 34 | } 35 | Data::Struct(DataStruct { 36 | fields: Fields::Named(ref fields), 37 | .. 38 | }) => { 39 | let fields = fields 40 | .named 41 | .iter() 42 | .map(Field::parse) 43 | .collect::, _>>()?; 44 | ( 45 | accepts::composite_body(&name, "ToSql", &fields), 46 | composite_body(&fields), 47 | ) 48 | } 49 | _ => { 50 | return Err(Error::new_spanned( 51 | input, 52 | "#[derive(ToSql)] may only be applied to structs, single field tuple structs, and enums", 53 | )); 54 | } 55 | }; 56 | 57 | let ident = &input.ident; 58 | let out = quote! { 59 | impl postgres_types::ToSql for #ident { 60 | fn to_sql(&self, 61 | _type: &postgres_types::Type, 62 | buf: &mut postgres_types::private::BytesMut) 63 | -> std::result::Result> { 67 | #to_sql_body 68 | } 69 | 70 | fn accepts(type_: &postgres_types::Type) -> bool { 71 | #accepts_body 72 | } 73 | 74 | postgres_types::to_sql_checked!(); 75 | } 76 | }; 77 | 78 | Ok(out) 79 | } 80 | 81 | fn enum_body(ident: &Ident, variants: &[Variant]) -> TokenStream { 82 | let idents = iter::repeat(ident); 83 | let variant_idents = variants.iter().map(|v| &v.ident); 84 | let variant_names = variants.iter().map(|v| &v.name); 85 | 86 | quote! { 87 | let s = match *self { 88 | #( 89 | #idents::#variant_idents => #variant_names, 90 | )* 91 | }; 92 | 93 | buf.extend_from_slice(s.as_bytes()); 94 | std::result::Result::Ok(postgres_types::IsNull::No) 95 | } 96 | } 97 | 98 | fn domain_body() -> TokenStream { 99 | quote! { 100 | let type_ = match *_type.kind() { 101 | postgres_types::Kind::Domain(ref type_) => type_, 102 | _ => unreachable!(), 103 | }; 104 | 105 | postgres_types::ToSql::to_sql(&self.0, type_, buf) 106 | } 107 | } 108 | 109 | fn composite_body(fields: &[Field]) -> TokenStream { 110 | let field_names = fields.iter().map(|f| &f.name); 111 | let field_idents = fields.iter().map(|f| &f.ident); 112 | 113 | quote! { 114 | let fields = match *_type.kind() { 115 | postgres_types::Kind::Composite(ref fields) => fields, 116 | _ => unreachable!(), 117 | }; 118 | 119 | buf.extend_from_slice(&(fields.len() as i32).to_be_bytes()); 120 | 121 | for field in fields { 122 | buf.extend_from_slice(&field.type_().oid().to_be_bytes()); 123 | 124 | let base = buf.len(); 125 | buf.extend_from_slice(&[0; 4]); 126 | let r = match field.name() { 127 | #( 128 | #field_names => postgres_types::ToSql::to_sql(&self.#field_idents, field.type_(), buf), 129 | )* 130 | _ => unreachable!(), 131 | }; 132 | 133 | let count = match r? { 134 | postgres_types::IsNull::Yes => -1, 135 | postgres_types::IsNull::No => { 136 | let len = buf.len() - base - 4; 137 | if len > i32::max_value() as usize { 138 | return std::result::Result::Err( 139 | std::convert::Into::into("value too large to transmit")); 140 | } 141 | len as i32 142 | } 143 | }; 144 | 145 | buf[base..base + 4].copy_from_slice(&count.to_be_bytes()); 146 | } 147 | 148 | std::result::Result::Ok(postgres_types::IsNull::No) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /postgres/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.17.0 - 2019-12-23 4 | 5 | ### Changed 6 | 7 | * Each `Client` now has its own non-threaded tokio `Runtime` rather than sharing a global threaded `Runtime`. This 8 | significantly improves performance by minimizing context switches and cross-thread synchronization. 9 | * `Client::copy_in` now returns a writer rather than taking in a reader. 10 | * `Client::query_raw` now returns a named type. 11 | * `Client::copy_in` and `Client::copy_out` no longer take query parameters as PostgreSQL doesn't support them in COPY 12 | queries. 13 | 14 | ### Removed 15 | 16 | * Removed support for `uuid` 0.7. 17 | 18 | ### Added 19 | 20 | * Added `Client::query_opt` for queries that are expected to return zero or one rows. 21 | * Added binary copy support in the `binary_copy` module. 22 | * The `fallible-iterator` crate is now publicly reexported. 23 | 24 | ## v0.17.0-alpha.2 - 2019-11-27 25 | 26 | ### Changed 27 | 28 | * Changed `Config::executor` to `Config::spawner`. 29 | 30 | ### Added 31 | 32 | * Added support for `uuid` 0.8. 33 | * Added `Transaction::query_one`. 34 | 35 | ## v0.17.0-alpha.1 - 2019-10-14 36 | 37 | ### Changed 38 | 39 | * Updated `tokio-postgres` to 0.5.0-alpha.1. 40 | 41 | ## v0.16.0-rc.2 - 2019-06-29 42 | 43 | ### Fixed 44 | 45 | * Documentation fixes 46 | 47 | ## v0.16.0-rc.1 - 2019-04-06 48 | 49 | ### Changed 50 | 51 | * `Connection` has been renamed to `Client`. 52 | * The `Client` type is now a thin wrapper around the tokio-postgres nonblocking client. By default, this is handled 53 | transparently by spawning connections onto an internal tokio `Runtime`, but this can also be controlled explicitly. 54 | * The `ConnectParams` type and `IntoConnectParams` trait have been replaced by a builder-style `Config` type. 55 | 56 | Before: 57 | ```rust 58 | let params = ConnectParams::builder() 59 | .user("postgres", None) 60 | .build(Host::Tcp("localhost".to_string())) 61 | .build(); 62 | let conn = Connection::connect(params, &TlsMode::None)?; 63 | ``` 64 | After: 65 | ```rust 66 | let client = Client::configure() 67 | .user("postgres") 68 | .host("localhost") 69 | .connect(NoTls)?; 70 | ``` 71 | * The TLS connection mode (e.g. `prefer`) is now part of the connection configuration instead of being passed in 72 | separately. 73 | 74 | Before: 75 | ```rust 76 | let conn = Connection::connect("postgres://postgres@localhost", &TlsMode::Prefer(connector))?; 77 | ``` 78 | After: 79 | ```rust 80 | let client = Client::connect("postgres://postgres@localhost?sslmode=prefer", connector)?; 81 | ``` 82 | * `Client` and `Transaction` methods take `&mut self` rather than `&self`, and correct use of the active transaction is 83 | verified at compile time rather than runtime. 84 | * `Row` no longer borrows any data. 85 | * `Statement` is now a "token" which is passed into methods on `Client` and `Transaction` and does not borrow the 86 | client: 87 | 88 | Before: 89 | ```rust 90 | let statement = conn.prepare("SELECT * FROM foo WHERE bar = $1")?; 91 | let rows = statement.query(&[&1i32])?; 92 | ``` 93 | After: 94 | ```rust 95 | let statement = client.prepare("SELECT * FROM foo WHERE bar = $1")?; 96 | let rows = client.query(&statement, &[1i32])?; 97 | ``` 98 | * `Statement::lazy_query` has been replaced with `Transaction::bind`, which returns a `Portal` type that can be used 99 | with `Transaction::query_portal`. 100 | * `Statement::copy_in` and `Statement::copy_out` have been moved to `Client` and `Transaction`. 101 | * `Client::copy_out` and `Transaction::copy_out` now return a `Read`er rather than consuming in a `Write`r. 102 | * `Connection::batch_execute` and `Transaction::batch_execute` have been replaced with `Client::simple_query` and 103 | `Transaction::simple_query`. 104 | * The Cargo features enabling `ToSql` and `FromSql` implementations for external crates are now versioned. For example, 105 | `with-uuid` is now `with-uuid-0_7`. This enables us to add support for new major versions of the crates in parallel 106 | without breaking backwards compatibility. 107 | 108 | ### Added 109 | 110 | * Connection string configuration now more fully mirrors libpq's syntax, and supports both URL-style and key-value style 111 | strings. 112 | * `FromSql` implementations can now borrow from the data buffer. In particular, this means that you can deserialize 113 | values as `&str`. The `FromSqlOwned` trait can be used as a bound to restrict code to deserializing owned values. 114 | * Added support for channel binding with SCRAM authentication. 115 | * Added multi-host support in connection configuration. 116 | * Added support for simple query requests returning row data. 117 | * Added variants of query methods which return fallible iterators of values and avoid fully buffering the response in 118 | memory. 119 | 120 | ### Removed 121 | 122 | * The `with-openssl` and `with-native-tls` Cargo features have been removed. Use the `tokio-postgres-openssl` and 123 | `tokio-postgres-native-tls` crates instead. 124 | * The `with-rustc_serialize` and `with-time` Cargo features have been removed. Use `serde` and `SystemTime` or `chrono` 125 | instead. 126 | * The `Transaction::set_commit` and `Transaction::set_rollback` methods have been removed. The only way to commit a 127 | transaction is to explicitly consume it via `Transaction::commit`. 128 | * The `Rows` type has been removed; methods now return `Vec` instead. 129 | * `Connection::prepare_cache` has been removed, as `Statement` is now `'static` and can be more easily cached 130 | externally. 131 | * Some other slightly more obscure features have been removed in the initial release. If you depended on them, please 132 | file an issue and we can find the right design to add them back! 133 | 134 | ## Older 135 | 136 | Look at the [release tags] for information about older releases. 137 | 138 | [release tags]: https://github.com/sfackler/rust-postgres/releases 139 | -------------------------------------------------------------------------------- /postgres-derive/src/fromsql.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use quote::quote; 3 | use std::iter; 4 | use syn::{Data, DataStruct, DeriveInput, Error, Fields, Ident}; 5 | 6 | use crate::accepts; 7 | use crate::composites::Field; 8 | use crate::enums::Variant; 9 | use crate::overrides::Overrides; 10 | 11 | pub fn expand_derive_fromsql(input: DeriveInput) -> Result { 12 | let overrides = Overrides::extract(&input.attrs)?; 13 | 14 | let name = overrides.name.unwrap_or_else(|| input.ident.to_string()); 15 | 16 | let (accepts_body, to_sql_body) = match input.data { 17 | Data::Enum(ref data) => { 18 | let variants = data 19 | .variants 20 | .iter() 21 | .map(Variant::parse) 22 | .collect::, _>>()?; 23 | ( 24 | accepts::enum_body(&name, &variants), 25 | enum_body(&input.ident, &variants), 26 | ) 27 | } 28 | Data::Struct(DataStruct { 29 | fields: Fields::Unnamed(ref fields), 30 | .. 31 | }) if fields.unnamed.len() == 1 => { 32 | let field = fields.unnamed.first().unwrap(); 33 | ( 34 | domain_accepts_body(&name, field), 35 | domain_body(&input.ident, field), 36 | ) 37 | } 38 | Data::Struct(DataStruct { 39 | fields: Fields::Named(ref fields), 40 | .. 41 | }) => { 42 | let fields = fields 43 | .named 44 | .iter() 45 | .map(Field::parse) 46 | .collect::, _>>()?; 47 | ( 48 | accepts::composite_body(&name, "FromSql", &fields), 49 | composite_body(&input.ident, &fields), 50 | ) 51 | } 52 | _ => { 53 | return Err(Error::new_spanned( 54 | input, 55 | "#[derive(FromSql)] may only be applied to structs, single field tuple structs, and enums", 56 | )) 57 | } 58 | }; 59 | 60 | let ident = &input.ident; 61 | let out = quote! { 62 | impl<'a> postgres_types::FromSql<'a> for #ident { 63 | fn from_sql(_type: &postgres_types::Type, buf: &'a [u8]) 64 | -> std::result::Result<#ident, 65 | std::boxed::Box> { 68 | #to_sql_body 69 | } 70 | 71 | fn accepts(type_: &postgres_types::Type) -> bool { 72 | #accepts_body 73 | } 74 | } 75 | }; 76 | 77 | Ok(out) 78 | } 79 | 80 | fn enum_body(ident: &Ident, variants: &[Variant]) -> TokenStream { 81 | let variant_names = variants.iter().map(|v| &v.name); 82 | let idents = iter::repeat(ident); 83 | let variant_idents = variants.iter().map(|v| &v.ident); 84 | 85 | quote! { 86 | match std::str::from_utf8(buf)? { 87 | #( 88 | #variant_names => std::result::Result::Ok(#idents::#variant_idents), 89 | )* 90 | s => { 91 | std::result::Result::Err( 92 | std::convert::Into::into(format!("invalid variant `{}`", s))) 93 | } 94 | } 95 | } 96 | } 97 | 98 | // Domains are sometimes but not always just represented by the bare type (!?) 99 | fn domain_accepts_body(name: &str, field: &syn::Field) -> TokenStream { 100 | let ty = &field.ty; 101 | let normal_body = accepts::domain_body(name, field); 102 | 103 | quote! { 104 | if <#ty as postgres_types::FromSql>::accepts(type_) { 105 | return true; 106 | } 107 | 108 | #normal_body 109 | } 110 | } 111 | 112 | fn domain_body(ident: &Ident, field: &syn::Field) -> TokenStream { 113 | let ty = &field.ty; 114 | quote! { 115 | <#ty as postgres_types::FromSql>::from_sql(_type, buf).map(#ident) 116 | } 117 | } 118 | 119 | fn composite_body(ident: &Ident, fields: &[Field]) -> TokenStream { 120 | let temp_vars = &fields 121 | .iter() 122 | .map(|f| Ident::new(&format!("__{}", f.ident), Span::call_site())) 123 | .collect::>(); 124 | let field_names = &fields.iter().map(|f| &f.name).collect::>(); 125 | let field_idents = &fields.iter().map(|f| &f.ident).collect::>(); 126 | 127 | quote! { 128 | let fields = match *_type.kind() { 129 | postgres_types::Kind::Composite(ref fields) => fields, 130 | _ => unreachable!(), 131 | }; 132 | 133 | let mut buf = buf; 134 | let num_fields = postgres_types::private::read_be_i32(&mut buf)?; 135 | if num_fields as usize != fields.len() { 136 | return std::result::Result::Err( 137 | std::convert::Into::into(format!("invalid field count: {} vs {}", num_fields, fields.len()))); 138 | } 139 | 140 | #( 141 | let mut #temp_vars = std::option::Option::None; 142 | )* 143 | 144 | for field in fields { 145 | let oid = postgres_types::private::read_be_i32(&mut buf)? as u32; 146 | if oid != field.type_().oid() { 147 | return std::result::Result::Err(std::convert::Into::into("unexpected OID")); 148 | } 149 | 150 | match field.name() { 151 | #( 152 | #field_names => { 153 | #temp_vars = std::option::Option::Some( 154 | postgres_types::private::read_value(field.type_(), &mut buf)?); 155 | } 156 | )* 157 | _ => unreachable!(), 158 | } 159 | } 160 | 161 | std::result::Result::Ok(#ident { 162 | #( 163 | #field_idents: #temp_vars.unwrap(), 164 | )* 165 | }) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /postgres-derive-test/src/composites.rs: -------------------------------------------------------------------------------- 1 | use crate::test_type; 2 | use postgres::{Client, NoTls}; 3 | use postgres_types::{FromSql, ToSql, WrongType}; 4 | use std::error::Error; 5 | 6 | #[test] 7 | fn defaults() { 8 | #[derive(FromSql, ToSql, Debug, PartialEq)] 9 | struct InventoryItem { 10 | name: String, 11 | supplier_id: i32, 12 | price: Option, 13 | } 14 | 15 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 16 | conn.batch_execute( 17 | "CREATE TYPE pg_temp.\"InventoryItem\" AS ( 18 | name TEXT, 19 | supplier_id INT, 20 | price DOUBLE PRECISION 21 | );", 22 | ) 23 | .unwrap(); 24 | 25 | let item = InventoryItem { 26 | name: "foobar".to_owned(), 27 | supplier_id: 100, 28 | price: Some(15.50), 29 | }; 30 | 31 | let item_null = InventoryItem { 32 | name: "foobar".to_owned(), 33 | supplier_id: 100, 34 | price: None, 35 | }; 36 | 37 | test_type( 38 | &mut conn, 39 | "\"InventoryItem\"", 40 | &[ 41 | (item, "ROW('foobar', 100, 15.50)"), 42 | (item_null, "ROW('foobar', 100, NULL)"), 43 | ], 44 | ); 45 | } 46 | 47 | #[test] 48 | fn name_overrides() { 49 | #[derive(FromSql, ToSql, Debug, PartialEq)] 50 | #[postgres(name = "inventory_item")] 51 | struct InventoryItem { 52 | #[postgres(name = "name")] 53 | _name: String, 54 | #[postgres(name = "supplier_id")] 55 | _supplier_id: i32, 56 | #[postgres(name = "price")] 57 | _price: Option, 58 | } 59 | 60 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 61 | conn.batch_execute( 62 | "CREATE TYPE pg_temp.inventory_item AS ( 63 | name TEXT, 64 | supplier_id INT, 65 | price DOUBLE PRECISION 66 | );", 67 | ) 68 | .unwrap(); 69 | 70 | let item = InventoryItem { 71 | _name: "foobar".to_owned(), 72 | _supplier_id: 100, 73 | _price: Some(15.50), 74 | }; 75 | 76 | let item_null = InventoryItem { 77 | _name: "foobar".to_owned(), 78 | _supplier_id: 100, 79 | _price: None, 80 | }; 81 | 82 | test_type( 83 | &mut conn, 84 | "inventory_item", 85 | &[ 86 | (item, "ROW('foobar', 100, 15.50)"), 87 | (item_null, "ROW('foobar', 100, NULL)"), 88 | ], 89 | ); 90 | } 91 | 92 | #[test] 93 | fn wrong_name() { 94 | #[derive(FromSql, ToSql, Debug, PartialEq)] 95 | struct InventoryItem { 96 | name: String, 97 | supplier_id: i32, 98 | price: Option, 99 | } 100 | 101 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 102 | conn.batch_execute( 103 | "CREATE TYPE pg_temp.inventory_item AS ( 104 | name TEXT, 105 | supplier_id INT, 106 | price DOUBLE PRECISION 107 | );", 108 | ) 109 | .unwrap(); 110 | 111 | let item = InventoryItem { 112 | name: "foobar".to_owned(), 113 | supplier_id: 100, 114 | price: Some(15.50), 115 | }; 116 | 117 | let err = conn 118 | .execute("SELECT $1::inventory_item", &[&item]) 119 | .unwrap_err(); 120 | assert!(err.source().unwrap().is::()); 121 | } 122 | 123 | #[test] 124 | fn extra_field() { 125 | #[derive(FromSql, ToSql, Debug, PartialEq)] 126 | #[postgres(name = "inventory_item")] 127 | struct InventoryItem { 128 | name: String, 129 | supplier_id: i32, 130 | price: Option, 131 | foo: i32, 132 | } 133 | 134 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 135 | conn.batch_execute( 136 | "CREATE TYPE pg_temp.inventory_item AS ( 137 | name TEXT, 138 | supplier_id INT, 139 | price DOUBLE PRECISION 140 | );", 141 | ) 142 | .unwrap(); 143 | 144 | let item = InventoryItem { 145 | name: "foobar".to_owned(), 146 | supplier_id: 100, 147 | price: Some(15.50), 148 | foo: 0, 149 | }; 150 | 151 | let err = conn 152 | .execute("SELECT $1::inventory_item", &[&item]) 153 | .unwrap_err(); 154 | assert!(err.source().unwrap().is::()); 155 | } 156 | 157 | #[test] 158 | fn missing_field() { 159 | #[derive(FromSql, ToSql, Debug, PartialEq)] 160 | #[postgres(name = "inventory_item")] 161 | struct InventoryItem { 162 | name: String, 163 | supplier_id: i32, 164 | } 165 | 166 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 167 | conn.batch_execute( 168 | "CREATE TYPE pg_temp.inventory_item AS ( 169 | name TEXT, 170 | supplier_id INT, 171 | price DOUBLE PRECISION 172 | );", 173 | ) 174 | .unwrap(); 175 | 176 | let item = InventoryItem { 177 | name: "foobar".to_owned(), 178 | supplier_id: 100, 179 | }; 180 | 181 | let err = conn 182 | .execute("SELECT $1::inventory_item", &[&item]) 183 | .unwrap_err(); 184 | assert!(err.source().unwrap().is::()); 185 | } 186 | 187 | #[test] 188 | fn wrong_type() { 189 | #[derive(FromSql, ToSql, Debug, PartialEq)] 190 | #[postgres(name = "inventory_item")] 191 | struct InventoryItem { 192 | name: String, 193 | supplier_id: i32, 194 | price: i32, 195 | } 196 | 197 | let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap(); 198 | conn.batch_execute( 199 | "CREATE TYPE pg_temp.inventory_item AS ( 200 | name TEXT, 201 | supplier_id INT, 202 | price DOUBLE PRECISION 203 | );", 204 | ) 205 | .unwrap(); 206 | 207 | let item = InventoryItem { 208 | name: "foobar".to_owned(), 209 | supplier_id: 100, 210 | price: 0, 211 | }; 212 | 213 | let err = conn 214 | .execute("SELECT $1::inventory_item", &[&item]) 215 | .unwrap_err(); 216 | assert!(err.source().unwrap().is::()); 217 | } 218 | -------------------------------------------------------------------------------- /postgres-native-tls/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! TLS support for `tokio-postgres` and `postgres` via `native-tls. 2 | //! 3 | //! # Examples 4 | //! 5 | //! ```no_run 6 | //! use native_tls::{Certificate, TlsConnector}; 7 | //! use postgres_native_tls::MakeTlsConnector; 8 | //! use std::fs; 9 | //! 10 | //! # fn main() -> Result<(), Box> { 11 | //! let cert = fs::read("database_cert.pem")?; 12 | //! let cert = Certificate::from_pem(&cert)?; 13 | //! let connector = TlsConnector::builder() 14 | //! .add_root_certificate(cert) 15 | //! .build()?; 16 | //! let connector = MakeTlsConnector::new(connector); 17 | //! 18 | //! let connect_future = tokio_postgres::connect( 19 | //! "host=localhost user=postgres sslmode=require", 20 | //! connector, 21 | //! ); 22 | //! 23 | //! // ... 24 | //! # Ok(()) 25 | //! # } 26 | //! ``` 27 | //! 28 | //! ```no_run 29 | //! use native_tls::{Certificate, TlsConnector}; 30 | //! use postgres_native_tls::MakeTlsConnector; 31 | //! use std::fs; 32 | //! 33 | //! # fn main() -> Result<(), Box> { 34 | //! let cert = fs::read("database_cert.pem")?; 35 | //! let cert = Certificate::from_pem(&cert)?; 36 | //! let connector = TlsConnector::builder() 37 | //! .add_root_certificate(cert) 38 | //! .build()?; 39 | //! let connector = MakeTlsConnector::new(connector); 40 | //! 41 | //! let client = postgres::Client::connect( 42 | //! "host=localhost user=postgres sslmode=require", 43 | //! connector, 44 | //! )?; 45 | //! # Ok(()) 46 | //! # } 47 | //! ``` 48 | #![doc(html_root_url = "https://docs.rs/postgres-native-tls/0.3")] 49 | #![warn(rust_2018_idioms, clippy::all, missing_docs)] 50 | 51 | use bytes::{Buf, BufMut}; 52 | use std::future::Future; 53 | use std::io; 54 | use std::mem::MaybeUninit; 55 | use std::pin::Pin; 56 | use std::task::{Context, Poll}; 57 | use tokio::io::{AsyncRead, AsyncWrite}; 58 | use tokio_postgres::tls; 59 | #[cfg(feature = "runtime")] 60 | use tokio_postgres::tls::MakeTlsConnect; 61 | use tokio_postgres::tls::{ChannelBinding, TlsConnect}; 62 | 63 | #[cfg(test)] 64 | mod test; 65 | 66 | /// A `MakeTlsConnect` implementation using the `native-tls` crate. 67 | /// 68 | /// Requires the `runtime` Cargo feature (enabled by default). 69 | #[cfg(feature = "runtime")] 70 | #[derive(Clone)] 71 | pub struct MakeTlsConnector(native_tls::TlsConnector); 72 | 73 | #[cfg(feature = "runtime")] 74 | impl MakeTlsConnector { 75 | /// Creates a new connector. 76 | pub fn new(connector: native_tls::TlsConnector) -> MakeTlsConnector { 77 | MakeTlsConnector(connector) 78 | } 79 | } 80 | 81 | #[cfg(feature = "runtime")] 82 | impl MakeTlsConnect for MakeTlsConnector 83 | where 84 | S: AsyncRead + AsyncWrite + Unpin + 'static + Send, 85 | { 86 | type Stream = TlsStream; 87 | type TlsConnect = TlsConnector; 88 | type Error = native_tls::Error; 89 | 90 | fn make_tls_connect(&mut self, domain: &str) -> Result { 91 | Ok(TlsConnector::new(self.0.clone(), domain)) 92 | } 93 | } 94 | 95 | /// A `TlsConnect` implementation using the `native-tls` crate. 96 | pub struct TlsConnector { 97 | connector: tokio_tls::TlsConnector, 98 | domain: String, 99 | } 100 | 101 | impl TlsConnector { 102 | /// Creates a new connector configured to connect to the specified domain. 103 | pub fn new(connector: native_tls::TlsConnector, domain: &str) -> TlsConnector { 104 | TlsConnector { 105 | connector: tokio_tls::TlsConnector::from(connector), 106 | domain: domain.to_string(), 107 | } 108 | } 109 | } 110 | 111 | impl TlsConnect for TlsConnector 112 | where 113 | S: AsyncRead + AsyncWrite + Unpin + 'static + Send, 114 | { 115 | type Stream = TlsStream; 116 | type Error = native_tls::Error; 117 | #[allow(clippy::type_complexity)] 118 | type Future = Pin, native_tls::Error>> + Send>>; 119 | 120 | fn connect(self, stream: S) -> Self::Future { 121 | let future = async move { 122 | let stream = self.connector.connect(&self.domain, stream).await?; 123 | 124 | Ok(TlsStream(stream)) 125 | }; 126 | 127 | Box::pin(future) 128 | } 129 | } 130 | 131 | /// The stream returned by `TlsConnector`. 132 | pub struct TlsStream(tokio_tls::TlsStream); 133 | 134 | impl AsyncRead for TlsStream 135 | where 136 | S: AsyncRead + AsyncWrite + Unpin, 137 | { 138 | unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [MaybeUninit]) -> bool { 139 | self.0.prepare_uninitialized_buffer(buf) 140 | } 141 | 142 | fn poll_read( 143 | mut self: Pin<&mut Self>, 144 | cx: &mut Context<'_>, 145 | buf: &mut [u8], 146 | ) -> Poll> { 147 | Pin::new(&mut self.0).poll_read(cx, buf) 148 | } 149 | 150 | fn poll_read_buf( 151 | mut self: Pin<&mut Self>, 152 | cx: &mut Context<'_>, 153 | buf: &mut B, 154 | ) -> Poll> 155 | where 156 | Self: Sized, 157 | { 158 | Pin::new(&mut self.0).poll_read_buf(cx, buf) 159 | } 160 | } 161 | 162 | impl AsyncWrite for TlsStream 163 | where 164 | S: AsyncRead + AsyncWrite + Unpin, 165 | { 166 | fn poll_write( 167 | mut self: Pin<&mut Self>, 168 | cx: &mut Context<'_>, 169 | buf: &[u8], 170 | ) -> Poll> { 171 | Pin::new(&mut self.0).poll_write(cx, buf) 172 | } 173 | 174 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 175 | Pin::new(&mut self.0).poll_flush(cx) 176 | } 177 | 178 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 179 | Pin::new(&mut self.0).poll_shutdown(cx) 180 | } 181 | 182 | fn poll_write_buf( 183 | mut self: Pin<&mut Self>, 184 | cx: &mut Context<'_>, 185 | buf: &mut B, 186 | ) -> Poll> 187 | where 188 | Self: Sized, 189 | { 190 | Pin::new(&mut self.0).poll_write_buf(cx, buf) 191 | } 192 | } 193 | 194 | impl tls::TlsStream for TlsStream 195 | where 196 | S: AsyncRead + AsyncWrite + Unpin, 197 | { 198 | fn channel_binding(&self) -> ChannelBinding { 199 | // FIXME https://github.com/tokio-rs/tokio/issues/1383 200 | ChannelBinding::none() 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /tokio-postgres/tests/test/binary_copy.rs: -------------------------------------------------------------------------------- 1 | use crate::connect; 2 | use futures::{pin_mut, TryStreamExt}; 3 | use tokio_postgres::binary_copy::{BinaryCopyInWriter, BinaryCopyOutStream}; 4 | use tokio_postgres::types::Type; 5 | 6 | #[tokio::test] 7 | async fn write_basic() { 8 | let client = connect("user=postgres").await; 9 | 10 | client 11 | .batch_execute("CREATE TEMPORARY TABLE foo (id INT, bar TEXT)") 12 | .await 13 | .unwrap(); 14 | 15 | let sink = client 16 | .copy_in("COPY foo (id, bar) FROM STDIN BINARY") 17 | .await 18 | .unwrap(); 19 | let writer = BinaryCopyInWriter::new(sink, &[Type::INT4, Type::TEXT]); 20 | pin_mut!(writer); 21 | writer.as_mut().write(&[&1i32, &"foobar"]).await.unwrap(); 22 | writer 23 | .as_mut() 24 | .write(&[&2i32, &None::<&str>]) 25 | .await 26 | .unwrap(); 27 | writer.finish().await.unwrap(); 28 | 29 | let rows = client 30 | .query("SELECT id, bar FROM foo ORDER BY id", &[]) 31 | .await 32 | .unwrap(); 33 | assert_eq!(rows.len(), 2); 34 | assert_eq!(rows[0].get::<_, i32>(0), 1); 35 | assert_eq!(rows[0].get::<_, Option<&str>>(1), Some("foobar")); 36 | assert_eq!(rows[1].get::<_, i32>(0), 2); 37 | assert_eq!(rows[1].get::<_, Option<&str>>(1), None); 38 | } 39 | 40 | #[tokio::test] 41 | async fn write_many_rows() { 42 | let client = connect("user=postgres").await; 43 | 44 | client 45 | .batch_execute("CREATE TEMPORARY TABLE foo (id INT, bar TEXT)") 46 | .await 47 | .unwrap(); 48 | 49 | let sink = client 50 | .copy_in("COPY foo (id, bar) FROM STDIN BINARY") 51 | .await 52 | .unwrap(); 53 | let writer = BinaryCopyInWriter::new(sink, &[Type::INT4, Type::TEXT]); 54 | pin_mut!(writer); 55 | 56 | for i in 0..10_000i32 { 57 | writer 58 | .as_mut() 59 | .write(&[&i, &format!("the value for {}", i)]) 60 | .await 61 | .unwrap(); 62 | } 63 | 64 | writer.finish().await.unwrap(); 65 | 66 | let rows = client 67 | .query("SELECT id, bar FROM foo ORDER BY id", &[]) 68 | .await 69 | .unwrap(); 70 | for (i, row) in rows.iter().enumerate() { 71 | assert_eq!(row.get::<_, i32>(0), i as i32); 72 | assert_eq!(row.get::<_, &str>(1), format!("the value for {}", i)); 73 | } 74 | } 75 | 76 | #[tokio::test] 77 | async fn write_big_rows() { 78 | let client = connect("user=postgres").await; 79 | 80 | client 81 | .batch_execute("CREATE TEMPORARY TABLE foo (id INT, bar BYTEA)") 82 | .await 83 | .unwrap(); 84 | 85 | let sink = client 86 | .copy_in("COPY foo (id, bar) FROM STDIN BINARY") 87 | .await 88 | .unwrap(); 89 | let writer = BinaryCopyInWriter::new(sink, &[Type::INT4, Type::BYTEA]); 90 | pin_mut!(writer); 91 | 92 | for i in 0..2i32 { 93 | writer 94 | .as_mut() 95 | .write(&[&i, &vec![i as u8; 128 * 1024]]) 96 | .await 97 | .unwrap(); 98 | } 99 | 100 | writer.finish().await.unwrap(); 101 | 102 | let rows = client 103 | .query("SELECT id, bar FROM foo ORDER BY id", &[]) 104 | .await 105 | .unwrap(); 106 | for (i, row) in rows.iter().enumerate() { 107 | assert_eq!(row.get::<_, i32>(0), i as i32); 108 | assert_eq!(row.get::<_, &[u8]>(1), &*vec![i as u8; 128 * 1024]); 109 | } 110 | } 111 | 112 | #[tokio::test] 113 | async fn read_basic() { 114 | let client = connect("user=postgres").await; 115 | 116 | client 117 | .batch_execute( 118 | " 119 | CREATE TEMPORARY TABLE foo (id INT, bar TEXT); 120 | INSERT INTO foo (id, bar) VALUES (1, 'foobar'), (2, NULL); 121 | ", 122 | ) 123 | .await 124 | .unwrap(); 125 | 126 | let stream = client 127 | .copy_out("COPY foo (id, bar) TO STDIN BINARY") 128 | .await 129 | .unwrap(); 130 | let rows = BinaryCopyOutStream::new(stream, &[Type::INT4, Type::TEXT]) 131 | .try_collect::>() 132 | .await 133 | .unwrap(); 134 | assert_eq!(rows.len(), 2); 135 | 136 | assert_eq!(rows[0].get::(0), 1); 137 | assert_eq!(rows[0].get::>(1), Some("foobar")); 138 | assert_eq!(rows[1].get::(0), 2); 139 | assert_eq!(rows[1].get::>(1), None); 140 | } 141 | 142 | #[tokio::test] 143 | async fn read_many_rows() { 144 | let client = connect("user=postgres").await; 145 | 146 | client 147 | .batch_execute( 148 | " 149 | CREATE TEMPORARY TABLE foo (id INT, bar TEXT); 150 | INSERT INTO foo (id, bar) SELECT i, 'the value for ' || i FROM generate_series(0, 9999) i;" 151 | ) 152 | .await 153 | .unwrap(); 154 | 155 | let stream = client 156 | .copy_out("COPY foo (id, bar) TO STDIN BINARY") 157 | .await 158 | .unwrap(); 159 | let rows = BinaryCopyOutStream::new(stream, &[Type::INT4, Type::TEXT]) 160 | .try_collect::>() 161 | .await 162 | .unwrap(); 163 | assert_eq!(rows.len(), 10_000); 164 | 165 | for (i, row) in rows.iter().enumerate() { 166 | assert_eq!(row.get::(0), i as i32); 167 | assert_eq!(row.get::<&str>(1), format!("the value for {}", i)); 168 | } 169 | } 170 | 171 | #[tokio::test] 172 | async fn read_big_rows() { 173 | let client = connect("user=postgres").await; 174 | 175 | client 176 | .batch_execute("CREATE TEMPORARY TABLE foo (id INT, bar BYTEA)") 177 | .await 178 | .unwrap(); 179 | for i in 0..2i32 { 180 | client 181 | .execute( 182 | "INSERT INTO foo (id, bar) VALUES ($1, $2)", 183 | &[&i, &vec![i as u8; 128 * 1024]], 184 | ) 185 | .await 186 | .unwrap(); 187 | } 188 | 189 | let stream = client 190 | .copy_out("COPY foo (id, bar) TO STDIN BINARY") 191 | .await 192 | .unwrap(); 193 | let rows = BinaryCopyOutStream::new(stream, &[Type::INT4, Type::BYTEA]) 194 | .try_collect::>() 195 | .await 196 | .unwrap(); 197 | assert_eq!(rows.len(), 2); 198 | 199 | for (i, row) in rows.iter().enumerate() { 200 | assert_eq!(row.get::(0), i as i32); 201 | assert_eq!(row.get::<&[u8]>(1), &vec![i as u8; 128 * 1024][..]); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /postgres/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::{CopyInWriter, CopyOutReader, Portal, RowIter, Rt, Statement, ToStatement}; 2 | use tokio::runtime::Runtime; 3 | use tokio_postgres::types::{ToSql, Type}; 4 | use tokio_postgres::{Error, Row, SimpleQueryMessage}; 5 | 6 | /// A representation of a PostgreSQL database transaction. 7 | /// 8 | /// Transactions will implicitly roll back by default when dropped. Use the `commit` method to commit the changes made 9 | /// in the transaction. Transactions can be nested, with inner transactions implemented via savepoints. 10 | pub struct Transaction<'a> { 11 | runtime: &'a mut Runtime, 12 | transaction: tokio_postgres::Transaction<'a>, 13 | } 14 | 15 | impl<'a> Transaction<'a> { 16 | pub(crate) fn new( 17 | runtime: &'a mut Runtime, 18 | transaction: tokio_postgres::Transaction<'a>, 19 | ) -> Transaction<'a> { 20 | Transaction { 21 | runtime, 22 | transaction, 23 | } 24 | } 25 | 26 | fn rt(&mut self) -> Rt<'_> { 27 | Rt(self.runtime) 28 | } 29 | 30 | /// Consumes the transaction, committing all changes made within it. 31 | pub fn commit(self) -> Result<(), Error> { 32 | self.runtime.block_on(self.transaction.commit()) 33 | } 34 | 35 | /// Rolls the transaction back, discarding all changes made within it. 36 | /// 37 | /// This is equivalent to `Transaction`'s `Drop` implementation, but provides any error encountered to the caller. 38 | pub fn rollback(self) -> Result<(), Error> { 39 | self.runtime.block_on(self.transaction.rollback()) 40 | } 41 | 42 | /// Like `Client::prepare`. 43 | pub fn prepare(&mut self, query: &str) -> Result { 44 | self.runtime.block_on(self.transaction.prepare(query)) 45 | } 46 | 47 | /// Like `Client::prepare_typed`. 48 | pub fn prepare_typed(&mut self, query: &str, types: &[Type]) -> Result { 49 | self.runtime 50 | .block_on(self.transaction.prepare_typed(query, types)) 51 | } 52 | 53 | /// Like `Client::execute`. 54 | pub fn execute(&mut self, query: &T, params: &[&(dyn ToSql + Sync)]) -> Result 55 | where 56 | T: ?Sized + ToStatement, 57 | { 58 | self.runtime 59 | .block_on(self.transaction.execute(query, params)) 60 | } 61 | 62 | /// Like `Client::query`. 63 | pub fn query(&mut self, query: &T, params: &[&(dyn ToSql + Sync)]) -> Result, Error> 64 | where 65 | T: ?Sized + ToStatement, 66 | { 67 | self.runtime.block_on(self.transaction.query(query, params)) 68 | } 69 | 70 | /// Like `Client::query_one`. 71 | pub fn query_one(&mut self, query: &T, params: &[&(dyn ToSql + Sync)]) -> Result 72 | where 73 | T: ?Sized + ToStatement, 74 | { 75 | self.runtime 76 | .block_on(self.transaction.query_one(query, params)) 77 | } 78 | 79 | /// Like `Client::query_opt`. 80 | pub fn query_opt( 81 | &mut self, 82 | query: &T, 83 | params: &[&(dyn ToSql + Sync)], 84 | ) -> Result, Error> 85 | where 86 | T: ?Sized + ToStatement, 87 | { 88 | self.runtime 89 | .block_on(self.transaction.query_opt(query, params)) 90 | } 91 | 92 | /// Like `Client::query_raw`. 93 | pub fn query_raw<'b, T, I>(&mut self, query: &T, params: I) -> Result, Error> 94 | where 95 | T: ?Sized + ToStatement, 96 | I: IntoIterator, 97 | I::IntoIter: ExactSizeIterator, 98 | { 99 | let stream = self 100 | .runtime 101 | .block_on(self.transaction.query_raw(query, params))?; 102 | Ok(RowIter::new(self.rt(), stream)) 103 | } 104 | 105 | /// Binds parameters to a statement, creating a "portal". 106 | /// 107 | /// Portals can be used with the `query_portal` method to page through the results of a query without being forced 108 | /// to consume them all immediately. 109 | /// 110 | /// Portals are automatically closed when the transaction they were created in is closed. 111 | /// 112 | /// # Panics 113 | /// 114 | /// Panics if the number of parameters provided does not match the number expected. 115 | pub fn bind(&mut self, query: &T, params: &[&(dyn ToSql + Sync)]) -> Result 116 | where 117 | T: ?Sized + ToStatement, 118 | { 119 | self.runtime.block_on(self.transaction.bind(query, params)) 120 | } 121 | 122 | /// Continues execution of a portal, returning the next set of rows. 123 | /// 124 | /// Unlike `query`, portals can be incrementally evaluated by limiting the number of rows returned in each call to 125 | /// `query_portal`. If the requested number is negative or 0, all remaining rows will be returned. 126 | pub fn query_portal(&mut self, portal: &Portal, max_rows: i32) -> Result, Error> { 127 | self.runtime 128 | .block_on(self.transaction.query_portal(portal, max_rows)) 129 | } 130 | 131 | /// The maximally flexible version of `query_portal`. 132 | pub fn query_portal_raw( 133 | &mut self, 134 | portal: &Portal, 135 | max_rows: i32, 136 | ) -> Result, Error> { 137 | let stream = self 138 | .runtime 139 | .block_on(self.transaction.query_portal_raw(portal, max_rows))?; 140 | Ok(RowIter::new(self.rt(), stream)) 141 | } 142 | 143 | /// Like `Client::copy_in`. 144 | pub fn copy_in(&mut self, query: &T) -> Result, Error> 145 | where 146 | T: ?Sized + ToStatement, 147 | { 148 | let sink = self.runtime.block_on(self.transaction.copy_in(query))?; 149 | Ok(CopyInWriter::new(self.rt(), sink)) 150 | } 151 | 152 | /// Like `Client::copy_out`. 153 | pub fn copy_out(&mut self, query: &T) -> Result, Error> 154 | where 155 | T: ?Sized + ToStatement, 156 | { 157 | let stream = self.runtime.block_on(self.transaction.copy_out(query))?; 158 | Ok(CopyOutReader::new(self.rt(), stream)) 159 | } 160 | 161 | /// Like `Client::simple_query`. 162 | pub fn simple_query(&mut self, query: &str) -> Result, Error> { 163 | self.runtime.block_on(self.transaction.simple_query(query)) 164 | } 165 | 166 | /// Like `Client::batch_execute`. 167 | pub fn batch_execute(&mut self, query: &str) -> Result<(), Error> { 168 | self.runtime.block_on(self.transaction.batch_execute(query)) 169 | } 170 | 171 | /// Like `Client::transaction`. 172 | pub fn transaction(&mut self) -> Result, Error> { 173 | let transaction = self.runtime.block_on(self.transaction.transaction())?; 174 | Ok(Transaction { 175 | runtime: self.runtime, 176 | transaction, 177 | }) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /tokio-postgres/src/row.rs: -------------------------------------------------------------------------------- 1 | //! Rows. 2 | 3 | use crate::row::sealed::{AsName, Sealed}; 4 | use crate::statement::Column; 5 | use crate::types::{FromSql, Type, WrongType}; 6 | use crate::{Error, Statement}; 7 | use fallible_iterator::FallibleIterator; 8 | use postgres_protocol::message::backend::DataRowBody; 9 | use std::fmt; 10 | use std::ops::Range; 11 | use std::rc::Rc; 12 | use std::str; 13 | 14 | mod sealed { 15 | pub trait Sealed {} 16 | 17 | pub trait AsName { 18 | fn as_name(&self) -> &str; 19 | } 20 | } 21 | 22 | impl AsName for Column { 23 | fn as_name(&self) -> &str { 24 | self.name() 25 | } 26 | } 27 | 28 | impl AsName for String { 29 | fn as_name(&self) -> &str { 30 | self 31 | } 32 | } 33 | 34 | /// A trait implemented by types that can index into columns of a row. 35 | /// 36 | /// This cannot be implemented outside of this crate. 37 | pub trait RowIndex: Sealed { 38 | #[doc(hidden)] 39 | fn __idx(&self, columns: &[T]) -> Option 40 | where 41 | T: AsName; 42 | } 43 | 44 | impl Sealed for usize {} 45 | 46 | impl RowIndex for usize { 47 | #[inline] 48 | fn __idx(&self, columns: &[T]) -> Option 49 | where 50 | T: AsName, 51 | { 52 | if *self >= columns.len() { 53 | None 54 | } else { 55 | Some(*self) 56 | } 57 | } 58 | } 59 | 60 | impl Sealed for str {} 61 | 62 | impl RowIndex for str { 63 | #[inline] 64 | fn __idx(&self, columns: &[T]) -> Option 65 | where 66 | T: AsName, 67 | { 68 | if let Some(idx) = columns.iter().position(|d| d.as_name() == self) { 69 | return Some(idx); 70 | }; 71 | 72 | // FIXME ASCII-only case insensitivity isn't really the right thing to 73 | // do. Postgres itself uses a dubious wrapper around tolower and JDBC 74 | // uses the US locale. 75 | columns 76 | .iter() 77 | .position(|d| d.as_name().eq_ignore_ascii_case(self)) 78 | } 79 | } 80 | 81 | impl<'a, T> Sealed for &'a T where T: ?Sized + Sealed {} 82 | 83 | impl<'a, T> RowIndex for &'a T 84 | where 85 | T: ?Sized + RowIndex, 86 | { 87 | #[inline] 88 | fn __idx(&self, columns: &[U]) -> Option 89 | where 90 | U: AsName, 91 | { 92 | T::__idx(*self, columns) 93 | } 94 | } 95 | 96 | /// A row of data returned from the database by a query. 97 | pub struct Row { 98 | statement: Statement, 99 | body: DataRowBody, 100 | ranges: Vec>>, 101 | } 102 | 103 | impl Row { 104 | pub(crate) fn new(statement: Statement, body: DataRowBody) -> Result { 105 | let ranges = body.ranges().collect().map_err(Error::parse)?; 106 | Ok(Row { 107 | statement, 108 | body, 109 | ranges, 110 | }) 111 | } 112 | 113 | /// Returns information about the columns of data in the row. 114 | pub fn columns(&self) -> &[Column] { 115 | self.statement.columns() 116 | } 117 | 118 | /// Determines if the row contains no values. 119 | pub fn is_empty(&self) -> bool { 120 | self.len() == 0 121 | } 122 | 123 | /// Returns the number of values in the row. 124 | pub fn len(&self) -> usize { 125 | self.columns().len() 126 | } 127 | 128 | /// Deserializes a value from the row. 129 | /// 130 | /// The value can be specified either by its numeric index in the row, or by its column name. 131 | /// 132 | /// # Panics 133 | /// 134 | /// Panics if the index is out of bounds or if the value cannot be converted to the specified type. 135 | pub fn get<'a, I, T>(&'a self, idx: I) -> T 136 | where 137 | I: RowIndex + fmt::Display, 138 | T: FromSql<'a>, 139 | { 140 | match self.get_inner(&idx) { 141 | Ok(ok) => ok, 142 | Err(err) => panic!("error retrieving column {}: {}", idx, err), 143 | } 144 | } 145 | 146 | /// Like `Row::get`, but returns a `Result` rather than panicking. 147 | pub fn try_get<'a, I, T>(&'a self, idx: I) -> Result 148 | where 149 | I: RowIndex + fmt::Display, 150 | T: FromSql<'a>, 151 | { 152 | self.get_inner(&idx) 153 | } 154 | 155 | fn get_inner<'a, I, T>(&'a self, idx: &I) -> Result 156 | where 157 | I: RowIndex + fmt::Display, 158 | T: FromSql<'a>, 159 | { 160 | let idx = match idx.__idx(self.columns()) { 161 | Some(idx) => idx, 162 | None => return Err(Error::column(idx.to_string())), 163 | }; 164 | 165 | let ty = self.columns()[idx].type_(); 166 | if !T::accepts(ty) { 167 | return Err(Error::from_sql( 168 | Box::new(WrongType::new::(ty.clone())), 169 | idx, 170 | )); 171 | } 172 | 173 | let buf = self.ranges[idx].clone().map(|r| &self.body.buffer()[r]); 174 | FromSql::from_sql_nullable(ty, buf).map_err(|e| Error::from_sql(e, idx)) 175 | } 176 | } 177 | 178 | /// A row of data returned from the database by a simple query. 179 | pub struct SimpleQueryRow { 180 | columns: Rc<[String]>, 181 | body: DataRowBody, 182 | ranges: Vec>>, 183 | } 184 | 185 | impl SimpleQueryRow { 186 | #[allow(clippy::new_ret_no_self)] 187 | pub(crate) fn new(columns: Rc<[String]>, body: DataRowBody) -> Result { 188 | let ranges = body.ranges().collect().map_err(Error::parse)?; 189 | Ok(SimpleQueryRow { 190 | columns, 191 | body, 192 | ranges, 193 | }) 194 | } 195 | 196 | /// Determines if the row contains no values. 197 | pub fn is_empty(&self) -> bool { 198 | self.len() == 0 199 | } 200 | 201 | /// Returns the number of values in the row. 202 | pub fn len(&self) -> usize { 203 | self.columns.len() 204 | } 205 | 206 | /// Returns a value from the row. 207 | /// 208 | /// The value can be specified either by its numeric index in the row, or by its column name. 209 | /// 210 | /// # Panics 211 | /// 212 | /// Panics if the index is out of bounds or if the value cannot be converted to the specified type. 213 | pub fn get(&self, idx: I) -> Option<&str> 214 | where 215 | I: RowIndex + fmt::Display, 216 | { 217 | match self.get_inner(&idx) { 218 | Ok(ok) => ok, 219 | Err(err) => panic!("error retrieving column {}: {}", idx, err), 220 | } 221 | } 222 | 223 | /// Like `SimpleQueryRow::get`, but returns a `Result` rather than panicking. 224 | pub fn try_get(&self, idx: I) -> Result, Error> 225 | where 226 | I: RowIndex + fmt::Display, 227 | { 228 | self.get_inner(&idx) 229 | } 230 | 231 | fn get_inner(&self, idx: &I) -> Result, Error> 232 | where 233 | I: RowIndex + fmt::Display, 234 | { 235 | let idx = match idx.__idx(&self.columns) { 236 | Some(idx) => idx, 237 | None => return Err(Error::column(idx.to_string())), 238 | }; 239 | 240 | let buf = self.ranges[idx].clone().map(|r| &self.body.buffer()[r]); 241 | FromSql::from_sql_nullable(&Type::TEXT, buf).map_err(|e| Error::from_sql(e, idx)) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /tokio-postgres/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::FrontendMessage; 2 | use crate::query::RowStream; 3 | #[cfg(feature = "runtime")] 4 | use crate::tls::MakeTlsConnect; 5 | use crate::tls::TlsConnect; 6 | use crate::types::{ToSql, Type}; 7 | #[cfg(feature = "runtime")] 8 | use crate::Socket; 9 | use crate::{bind, query, Client, Error, Portal, Row, SimpleQueryMessage, Statement, ToStatement}; 10 | use bytes::Buf; 11 | use futures::TryStreamExt; 12 | use postgres_protocol::message::frontend; 13 | use tokio::io::{AsyncRead, AsyncWrite}; 14 | 15 | /// A representation of a PostgreSQL database transaction. 16 | /// 17 | /// Transactions will implicitly roll back when dropped. Use the `commit` method to commit the changes made in the 18 | /// transaction. Transactions can be nested, with inner transactions implemented via safepoints. 19 | pub struct Transaction<'a> { 20 | client: &'a mut Client, 21 | depth: u32, 22 | done: bool, 23 | } 24 | 25 | impl<'a> Drop for Transaction<'a> { 26 | fn drop(&mut self) { 27 | if self.done { 28 | return; 29 | } 30 | 31 | let query = if self.depth == 0 { 32 | "ROLLBACK".to_string() 33 | } else { 34 | format!("ROLLBACK TO sp{}", self.depth) 35 | }; 36 | let buf = self.client.inner().with_buf(|buf| { 37 | frontend::query(&query, buf).unwrap(); 38 | buf.split().freeze() 39 | }); 40 | let _ = self.client.inner().send(FrontendMessage::Raw(buf)); 41 | } 42 | } 43 | 44 | impl<'a> Transaction<'a> { 45 | pub(crate) fn new(client: &'a mut Client) -> Transaction<'a> { 46 | Transaction { 47 | client, 48 | depth: 0, 49 | done: false, 50 | } 51 | } 52 | 53 | /// Consumes the transaction, committing all changes made within it. 54 | pub async fn commit(mut self) -> Result<(), Error> { 55 | self.done = true; 56 | let query = if self.depth == 0 { 57 | "COMMIT".to_string() 58 | } else { 59 | format!("RELEASE sp{}", self.depth) 60 | }; 61 | self.client.batch_execute(&query).await 62 | } 63 | 64 | /// Rolls the transaction back, discarding all changes made within it. 65 | /// 66 | /// This is equivalent to `Transaction`'s `Drop` implementation, but provides any error encountered to the caller. 67 | pub async fn rollback(mut self) -> Result<(), Error> { 68 | self.done = true; 69 | let query = if self.depth == 0 { 70 | "ROLLBACK".to_string() 71 | } else { 72 | format!("ROLLBACK TO sp{}", self.depth) 73 | }; 74 | self.client.batch_execute(&query).await 75 | } 76 | 77 | /// Like `Client::prepare`. 78 | pub async fn prepare(&self, query: &str) -> Result { 79 | self.client.prepare(query).await 80 | } 81 | 82 | /// Like `Client::prepare_typed`. 83 | pub async fn prepare_typed( 84 | &self, 85 | query: &str, 86 | parameter_types: &[Type], 87 | ) -> Result { 88 | self.client.prepare_typed(query, parameter_types).await 89 | } 90 | 91 | /// Like `Client::query`. 92 | pub async fn query( 93 | &self, 94 | statement: &Statement, 95 | params: &[&(dyn ToSql)], 96 | ) -> Result, Error> { 97 | self.client.query(statement, params).await 98 | } 99 | 100 | /// Like `Client::query_one`. 101 | pub async fn query_one( 102 | &self, 103 | statement: &Statement, 104 | params: &[&(dyn ToSql)], 105 | ) -> Result { 106 | self.client.query_one(statement, params).await 107 | } 108 | 109 | /// Like `Client::query_opt`. 110 | pub async fn query_opt( 111 | &self, 112 | statement: &Statement, 113 | params: &[&(dyn ToSql)], 114 | ) -> Result, Error> { 115 | self.client.query_opt(statement, params).await 116 | } 117 | 118 | /// Like `Client::query_raw`. 119 | pub async fn query_raw( 120 | &self, 121 | statement: &Statement, 122 | params: &[&(dyn ToSql)], 123 | ) -> Result { 124 | self.client.query_raw(statement, params).await 125 | } 126 | 127 | /// Like `Client::execute`. 128 | pub async fn execute(&self, statement: &T, params: &[&(dyn ToSql)]) -> Result 129 | where 130 | T: ?Sized + ToStatement, 131 | { 132 | self.client.execute(statement, params).await 133 | } 134 | 135 | /// Like `Client::execute_iter`. 136 | pub async fn execute_raw( 137 | &self, 138 | statement: &Statement, 139 | params: &[&(dyn ToSql)], 140 | ) -> Result { 141 | self.client.execute_raw(statement, params).await 142 | } 143 | 144 | /// Binds a statement to a set of parameters, creating a `Portal` which can be incrementally queried. 145 | /// 146 | /// Portals only last for the duration of the transaction in which they are created, and can only be used on the 147 | /// connection that created them. 148 | /// 149 | /// # Panics 150 | /// 151 | /// Panics if the number of parameters provided does not match the number expected. 152 | pub async fn bind(&self, statement: &T, params: &[&(dyn ToSql)]) -> Result 153 | where 154 | T: ?Sized + ToStatement, 155 | { 156 | self.bind_raw(statement, params).await 157 | } 158 | 159 | /// A maximally flexible version of [`bind`]. 160 | /// 161 | /// [`bind`]: #method.bind 162 | pub async fn bind_raw(&self, statement: &T, params: &[&(dyn ToSql)]) -> Result 163 | where 164 | T: ?Sized + ToStatement, 165 | { 166 | let statement = statement.__convert().into_statement(&self.client).await?; 167 | bind::bind(self.client.inner(), statement, params).await 168 | } 169 | 170 | /// Continues execution of a portal, returning a stream of the resulting rows. 171 | /// 172 | /// Unlike `query`, portals can be incrementally evaluated by limiting the number of rows returned in each call to 173 | /// `query_portal`. If the requested number is negative or 0, all rows will be returned. 174 | pub async fn query_portal(&self, portal: &Portal, max_rows: i32) -> Result, Error> { 175 | self.query_portal_raw(portal, max_rows) 176 | .await? 177 | .try_collect() 178 | .await 179 | } 180 | 181 | /// The maximally flexible version of [`query_portal`]. 182 | /// 183 | /// [`query_portal`]: #method.query_portal 184 | pub async fn query_portal_raw( 185 | &self, 186 | portal: &Portal, 187 | max_rows: i32, 188 | ) -> Result { 189 | query::query_portal(self.client.inner(), portal, max_rows).await 190 | } 191 | 192 | /// Like `Client::simple_query`. 193 | pub async fn simple_query(&self, query: &str) -> Result, Error> { 194 | self.client.simple_query(query).await 195 | } 196 | 197 | /// Like `Client::batch_execute`. 198 | pub async fn batch_execute(&self, query: &str) -> Result<(), Error> { 199 | self.client.batch_execute(query).await 200 | } 201 | 202 | /// Like `Client::transaction`. 203 | pub async fn transaction(&mut self) -> Result, Error> { 204 | let depth = self.depth + 1; 205 | let query = format!("SAVEPOINT sp{}", depth); 206 | self.batch_execute(&query).await?; 207 | 208 | Ok(Transaction { 209 | client: self.client, 210 | depth, 211 | done: false, 212 | }) 213 | } 214 | } 215 | --------------------------------------------------------------------------------