diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a2c9593..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: rust -sudo: false -rust: -- nightly -- 1.11.0 -script: -- (test $TRAVIS_RUST_VERSION != "nightly" || env RUST_BACKTRACE=1 cargo test --manifest-path=postgres-derive/Cargo.toml) -- cargo test --manifest-path=postgres-derive-codegen/test/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index 8f970a4..9035a5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,20 @@ -[workspace] -members = [ - "postgres-derive", - "postgres-derive-codegen", - "postgres-derive-codegen/test", - "postgres-derive-internals" -] +[package] +name = "postgres-derive" +version = "0.3.3" +authors = ["Steven Fackler "] +license = "MIT/Apache-2.0" +description = "Deriving plugin support for Postgres enum, domain, and composite types" +repository = "https://github.com/sfackler/rust-postgres-derive" +readme = "README.md" +keywords = ["database", "postgres", "postgresql", "sql"] + +[lib] +proc-macro = true +test = false + +[dependencies] +syn = "0.13" +quote = "0.5" + +[dev-dependencies] +postgres = "0.15.1" diff --git a/README.md b/README.md index b026a92..46617e5 100644 --- a/README.md +++ b/README.md @@ -1,232 +1,3 @@ # postgres-derive -[![Build Status](https://travis-ci.org/sfackler/rust-postgres-derive.svg?branch=master)](https://travis-ci.org/sfackler/rust-postgres-derive) - -Syntax extensions to automatically derive `FromSql` and `ToSql` implementations for Postgres enum, -domain, and composite types. - -The generated code requires rust-postgres 0.12.0 or higher and Rust 1.10.0 or higher. - -# Usage - -postgres-derive can be used both as a syntax extension with a nightly build of the compiler, or -as a code generator with stable builds. - -## Nightlies - -Simply depend on the `postgres-derive-macros` crate and register it as a plugin: - - -Cargo.toml -```toml -# ... - -[dependencies] -postgres-derive-macros = "0.2" -postgres = "0.12" -``` - -lib.rs -```rust -#![feature(proc_macro)] - -#[macro_use] -extern crate postgres; -#[macro_use] -extern crate postgres_derive; - -#[derive(Debug, ToSql, FromSql)] -pub enum Mood { - Sad, - Ok, - Happy, -} - -// ... -``` - -## Stable - -Use `postgres-derive-codegen` in a build script: - -Cargo.toml -```toml -[package] -# ... -build = "build.rs" - -[build-dependencies] -postgres-derive-codegen = "0.2" - -[dependencies] -postgres = "0.12" -``` - -build.rs -```rust -extern crate postgres_derive_codegen; - -use std::env; -use std::path::Path; - -pub fn main() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - let src = Path::new("src/types.rs.in"); - let dst = Path::new(&out_dir).join("types.rs"); - - postgres_derive_codegen::expand(src, dst).unwrap(); -} -``` - -types.rs.in -```rust -#[derive(Debug, ToSql, FromSql)] -pub enum Mood { - Sad, - Ok, - Happy, -} -``` - -lib.rs -```rust -#[macro_use] -extern crate postgres; - -include!(concat!(env!("OUT_DIR"), "/types.rs")); - -// ... -``` - -# Types - -## Enums - -Postgres enums correspond to C-like enums in Rust: - -```sql -CREATE TYPE "Mood" AS ENUM ( - 'Sad', - 'Ok', - 'Happy' -); -``` - -```rust -#[derive(Debug, ToSql, FromSql)] -enum Mood { - Sad, - Ok, - Happy, -} -``` - -The implementations will expect exact matches between the type names and variants. The -`#[postgres(...)]` attribute can be used to adjust the names used on the Postgres side: - -```sql -CREATE TYPE mood AS ENUM ( - 'sad', - 'ok', - 'happy' -); -``` - -```rust -#[derive(Debug, ToSql, FromSql)] -#[postgres(name = "mood")] -enum Mood { - #[postgres(name = "sad")] - Sad, - #[postgres(name = "ok")] - Ok, - #[postgres(name = "happy")] - Happy, -} -``` - -## Domains - -Postgres domains correspond to tuple structs with one member in Rust: - -```sql -CREATE DOMAIN "SessionId" AS BYTEA CHECK(octet_length(VALUE) = 16); -``` - -```rust -#[derive(Debug, FromSql, ToSql)] -struct SessionId(Vec); -``` - -As above, the implementations will expect an exact match between the Rust and Postgres type names, -and the `#[postgres(...)]` attribute can be used to adjust that behavior: - -```sql -CREATE DOMAIN session_id AS BYTEA CHECK(octet_length(VALUE) = 16); -``` - -```rust -#[derive(Debug, FromSql, ToSql)] -#[postgres(name = "session_id")] -struct SessionId(Vec); -``` - -## Composites - -Postgres composite types correspond to structs in Rust: - -```sql -CREATE TYPE "InventoryItem" AS ( - name TEXT, - supplier_id INT, - price DOUBLE PRECISION -); -``` - -```rust -#[derive(Debug, FromSql, ToSql)] -struct InventoryItem { - name: String, - supplier_id: i32, - price: Option, -} -``` - -Again, the implementations will expect an exact match between the names of the Rust and Postgres -types and fields, which can be adjusted via the `#[postgres(...)]` attribute: - - -```sql -CREATE TYPE inventory_item AS ( - name TEXT, - supplier_id INT, - the_price DOUBLE PRECISION -); -``` - -```rust -#[derive(Debug, FromSql, ToSql)] -#[postgres(name = "inventory_item")] -struct InventoryItem { - name: String, - supplier_id: i32, - #[postgres(name = "the_price")] - price: Option, -} -``` - -## License - -Licensed under either of - - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. +This has been folded into https://github.com/sfackler/rust-postgres. diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..36baa63 --- /dev/null +++ b/circle.yml @@ -0,0 +1,25 @@ +version: 2 +jobs: + build: + docker: + - image: rust:1.31.0 + - image: postgres:9.6 + environment: + POSTGRES_PASSWORD: password + steps: + - checkout + - restore_cache: + key: registry + - run: cargo generate-lockfile + - save_cache: + key: registry-{{ epoch }} + paths: + - ~/.cargo/registry/index + - restore_cache: + key: dependencies-1.20-{{ checksum "Cargo.lock" }} + - run: cargo test + - save_cache: + key: dependencies-1.20-{{ checksum "Cargo.lock" }} + paths: + - target + - ~/.cargo/registry/cache diff --git a/postgres-derive-codegen/Cargo.toml b/postgres-derive-codegen/Cargo.toml deleted file mode 100644 index 8a628d8..0000000 --- a/postgres-derive-codegen/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "postgres-derive-codegen" -version = "0.2.1" -authors = ["Steven Fackler "] -license = "MIT/Apache-2.0" -description = "Deriving codegen support for Postgres enum, domain, and composite types" -repository = "https://github.com/sfackler/rust-postgres-derive" -readme = "../README.md" -keywords = ["database", "postgres", "postgresql", "sql"] -exclude = ["test"] - -[dependencies] -syntex = "0.48" -syntex_syntax = "0.48" -postgres-derive-internals = { version = "0.2.0", path = "../postgres-derive-internals" } diff --git a/postgres-derive-codegen/src/lib.rs b/postgres-derive-codegen/src/lib.rs deleted file mode 100644 index ebe463d..0000000 --- a/postgres-derive-codegen/src/lib.rs +++ /dev/null @@ -1,106 +0,0 @@ -extern crate syntex; -extern crate syntex_syntax as syntax; -extern crate postgres_derive_internals; - -use syntax::ext::base::{Annotatable, ExtCtxt}; -use syntax::codemap::Span; -use syntax::ast::MetaItem; -use syntax::print::pprust; -use syntax::parse; - -pub fn expand(src: S, dst: D) -> Result<(), syntex::Error> - where S: AsRef, - D: AsRef, -{ - let mut registry = syntex::Registry::new(); - register(&mut registry); - registry.expand("", src.as_ref(), dst.as_ref()) -} - -pub fn register(reg: &mut syntex::Registry) { - use syntax::{ast, fold}; - - fn strip_attributes(krate: ast::Crate) -> ast::Crate { - struct StripAttributeFolder; - - impl fold::Folder for StripAttributeFolder { - fn fold_attribute(&mut self, attr: ast::Attribute) -> Option { - match attr.node.value.node { - ast::MetaItemKind::List(ref n, _) if n == &"postgres" => return None, - _ => {} - } - - Some(attr) - } - - fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { - fold::noop_fold_mac(mac, self) - } - } - - fold::Folder::fold_crate(&mut StripAttributeFolder, krate) - } - - reg.add_attr("feature(custom_derive)"); - reg.add_attr("feature(custom_attribute)"); - - reg.add_decorator("derive_ToSql", expand_tosql); - reg.add_decorator("derive_FromSql", expand_fromsql); - - reg.add_post_expansion_pass(strip_attributes); -} - -fn expand_tosql(ctx: &mut ExtCtxt, - span: Span, - _: &MetaItem, - annotatable: &Annotatable, - push: &mut FnMut(Annotatable)) { - expand_inner(ctx, - span, - "ToSql", - annotatable, - push, - postgres_derive_internals::expand_derive_tosql); -} - -fn expand_fromsql(ctx: &mut ExtCtxt, - span: Span, - _: &MetaItem, - annotatable: &Annotatable, - push: &mut FnMut(Annotatable)) { - expand_inner(ctx, - span, - "FromSql", - annotatable, - push, - postgres_derive_internals::expand_derive_fromsql); -} - -fn expand_inner(ctx: &mut ExtCtxt, - span: Span, - trait_: &str, - annotatable: &Annotatable, - push: &mut FnMut(Annotatable), - expand: fn(&str) -> Result) { - let item = match *annotatable { - Annotatable::Item(ref item) => item, - _ => { - ctx.span_err(span, &format!("#[derive({})] can only be applied to structs, single field \ - tuple structs, and enums", trait_)); - return; - } - }; - - let item = pprust::item_to_string(item); - match expand(&item) { - Ok(source) => { - let mut parser = parse::new_parser_from_source_str(&ctx.parse_sess, - "".to_owned(), - source); - while let Some(item) = parser.parse_item().unwrap() { - push(Annotatable::Item(item)); - } - } - Err(err) => ctx.span_err(span, &err), - } -} diff --git a/postgres-derive-codegen/test/Cargo.toml b/postgres-derive-codegen/test/Cargo.toml deleted file mode 100644 index 59f19e4..0000000 --- a/postgres-derive-codegen/test/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "test" -version = "0.1.0" -authors = ["Steven Fackler "] -build = "build.rs" - -[build-dependencies] -postgres-derive-codegen = { path = ".." } - -[dependencies] -postgres = "0.12" diff --git a/postgres-derive-codegen/test/build.rs b/postgres-derive-codegen/test/build.rs deleted file mode 100644 index 090eb3c..0000000 --- a/postgres-derive-codegen/test/build.rs +++ /dev/null @@ -1,12 +0,0 @@ -extern crate postgres_derive_codegen; - -use std::env; -use std::path::Path; - -pub fn main() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - let src = Path::new("src/types.rs.in"); - let dst = Path::new(&out_dir).join("types.rs"); - - postgres_derive_codegen::expand(src, dst).unwrap(); -} diff --git a/postgres-derive-codegen/test/src/main.rs b/postgres-derive-codegen/test/src/main.rs deleted file mode 100644 index 4f31b99..0000000 --- a/postgres-derive-codegen/test/src/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -#[macro_use] -extern crate postgres; - -use postgres::{Connection, TlsMode}; -use postgres::types::{FromSql, ToSql}; -use std::fmt; - -include!(concat!(env!("OUT_DIR"), "/types.rs")); - -pub fn test_type(conn: &Connection, sql_type: &str, checks: &[(T, S)]) - where T: PartialEq + FromSql + ToSql, S: fmt::Display -{ - for &(ref val, ref repr) in checks.iter() { - let stmt = conn.prepare(&*format!("SELECT {}::{}", *repr, sql_type)).unwrap(); - let result = stmt.query(&[]).unwrap().iter().next().unwrap().get(0); - assert_eq!(val, &result); - - let stmt = conn.prepare(&*format!("SELECT $1::{}", sql_type)).unwrap(); - let result = stmt.query(&[val]).unwrap().iter().next().unwrap().get(0); - assert_eq!(val, &result); - } -} - -#[test] -fn domain() { - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", &[]) - .unwrap(); - - test_type(&conn, "session_id", &[(SessionId(b"0123456789abcdef".to_vec()), - "'0123456789abcdef'")]); -} - -#[test] -fn enum_() { - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE TYPE pg_temp.mood AS ENUM ('sad', 'ok', 'happy')", &[]).unwrap(); - - test_type(&conn, - "mood", - &[(Mood::Sad, "'sad'"), (Mood::Ok, "'ok'"), (Mood::Happy, "'happy'")]); -} diff --git a/postgres-derive-codegen/test/src/types.rs.in b/postgres-derive-codegen/test/src/types.rs.in deleted file mode 100644 index dc4279f..0000000 --- a/postgres-derive-codegen/test/src/types.rs.in +++ /dev/null @@ -1,14 +0,0 @@ -#[derive(FromSql, ToSql, Debug, PartialEq)] -#[postgres(name = "session_id")] -struct SessionId(Vec); - -#[derive(Debug, ToSql, FromSql, PartialEq)] -#[postgres(name = "mood")] -enum Mood { - #[postgres(name = "sad")] - Sad, - #[postgres(name = "ok")] - Ok, - #[postgres(name = "happy")] - Happy, -} diff --git a/postgres-derive-internals/Cargo.toml b/postgres-derive-internals/Cargo.toml deleted file mode 100644 index eae0afc..0000000 --- a/postgres-derive-internals/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "postgres-derive-internals" -version = "0.2.1" -authors = ["Steven Fackler "] -license = "MIT/Apache-2.0" -description = "Unstable internals library used by postgres-derive and postgres-derive-codegen" -repository = "https://github.com/sfackler/rust-postgres-derive" -readme = "../README.md" -keywords = ["database", "postgres", "postgresql", "sql"] - -[dependencies] -syn = "0.10" -quote = "0.3" diff --git a/postgres-derive-internals/src/lib.rs b/postgres-derive-internals/src/lib.rs deleted file mode 100644 index 512543b..0000000 --- a/postgres-derive-internals/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![recursion_limit = "256"] - -extern crate syn; -#[macro_use] -extern crate quote; - -mod accepts; -mod composites; -mod enums; -mod fromsql; -mod overrides; -mod tosql; - -pub fn expand_derive_tosql(source: &str) -> Result { - let input = try!(syn::parse_macro_input(source)); - tosql::expand_derive_tosql(&input) -} - -pub fn expand_derive_fromsql(source: &str) -> Result { - let input = try!(syn::parse_macro_input(source)); - fromsql::expand_derive_fromsql(&input) -} diff --git a/postgres-derive/Cargo.toml b/postgres-derive/Cargo.toml deleted file mode 100644 index ba5c375..0000000 --- a/postgres-derive/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "postgres-derive" -version = "0.2.1" -authors = ["Steven Fackler "] -license = "MIT/Apache-2.0" -description = "Deriving plugin support for Postgres enum, domain, and composite types" -repository = "https://github.com/sfackler/rust-postgres-derive" -readme = "../README.md" -keywords = ["database", "postgres", "postgresql", "sql"] - -[lib] -proc-macro = true -test = false - -[dependencies] -postgres-derive-internals = { version = "0.2.0", path = "../postgres-derive-internals" } -post-expansion = "0.2" -syn = "0.10" -quote = "0.3" - -[dev-dependencies] -postgres = "0.12" diff --git a/postgres-derive/src/lib.rs b/postgres-derive/src/lib.rs deleted file mode 100644 index 14d2dbc..0000000 --- a/postgres-derive/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -#![feature(proc_macro, proc_macro_lib)] - -extern crate proc_macro; -extern crate postgres_derive_internals; -#[macro_use] -extern crate post_expansion; -extern crate syn; -extern crate quote; - -use proc_macro::TokenStream; -use quote::{ToTokens, Tokens}; - -register_post_expansion!(PostExpansion_postgres_derive); - -#[proc_macro_derive(ToSql)] -pub fn derive_tosql(input: TokenStream) -> TokenStream { - derive(input, postgres_derive_internals::expand_derive_tosql) -} - -#[proc_macro_derive(FromSql)] -pub fn derive_fromsql(input: TokenStream) -> TokenStream { - derive(input, postgres_derive_internals::expand_derive_fromsql) -} - -fn derive(input: TokenStream, expand: fn(&str) -> Result) -> TokenStream { - let source = input.to_string(); - - let decl = expand_decl(&source); - - let impl_ = match expand(&source) { - Ok(impl_) => impl_, - Err(e) => panic!("{}", e), - }; - - let expanded = format!("{}\n{}", decl, impl_); - expanded.parse().unwrap() -} - -fn expand_decl(source: &str) -> String { - let ast = syn::parse_macro_input(source).unwrap(); - let stripped = post_expansion::strip_attrs_later(ast, &["postgres"], "postgres_derive"); - - let mut tokens = Tokens::new(); - stripped.to_tokens(&mut tokens); - tokens.to_string() -} diff --git a/postgres-derive/tests/domains.rs b/postgres-derive/tests/domains.rs deleted file mode 100644 index 11a3606..0000000 --- a/postgres-derive/tests/domains.rs +++ /dev/null @@ -1,71 +0,0 @@ -#![feature(proc_macro)] - -#[macro_use] -extern crate postgres_derive; -#[macro_use] -extern crate postgres; - -use postgres::{Connection, TlsMode}; -use postgres::error::Error; -use postgres::types::WrongType; - -mod util; - -#[test] -fn defaults() { - #[derive(FromSql, ToSql, Debug, PartialEq)] - struct SessionId(Vec); - - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE DOMAIN pg_temp.\"SessionId\" AS bytea CHECK(octet_length(VALUE) = 16);", - &[]) - .unwrap(); - - util::test_type(&conn, "\"SessionId\"", &[(SessionId(b"0123456789abcdef".to_vec()), - "'0123456789abcdef'")]); -} - -#[test] -fn name_overrides() { - #[derive(FromSql, ToSql, Debug, PartialEq)] - #[postgres(name = "session_id")] - struct SessionId(Vec); - - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", &[]) - .unwrap(); - - util::test_type(&conn, "session_id", &[(SessionId(b"0123456789abcdef".to_vec()), - "'0123456789abcdef'")]); -} - -#[test] -fn wrong_name() { - #[derive(FromSql, ToSql, Debug, PartialEq)] - struct SessionId(Vec); - - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", &[]) - .unwrap(); - - match conn.execute("SELECT $1::session_id", &[&SessionId(vec![])]) { - Err(Error::Conversion(ref r)) if r.is::() => {} - v => panic!("unexpected response {:?}", v), - } -} - -#[test] -fn wrong_type() { - #[derive(FromSql, ToSql, Debug, PartialEq)] - #[postgres(name = "session_id")] - struct SessionId(i32); - - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", &[]) - .unwrap(); - - match conn.execute("SELECT $1::session_id", &[&SessionId(0)]) { - Err(Error::Conversion(ref r)) if r.is::() => {} - v => panic!("unexpected response {:?}", v), - } -} diff --git a/postgres-derive/tests/enums.rs b/postgres-derive/tests/enums.rs deleted file mode 100644 index 3d3c693..0000000 --- a/postgres-derive/tests/enums.rs +++ /dev/null @@ -1,100 +0,0 @@ -#![feature(proc_macro)] - -#[macro_use] -extern crate postgres_derive; -#[macro_use] -extern crate postgres; - -use postgres::{Connection, TlsMode}; -use postgres::error::Error; -use postgres::types::WrongType; - -mod util; - -#[test] -fn defaults() { - #[derive(Debug, ToSql, FromSql, PartialEq)] - enum Foo { - Bar, - Baz - } - - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE TYPE pg_temp.\"Foo\" AS ENUM ('Bar', 'Baz')", &[]).unwrap(); - - util::test_type(&conn, "\"Foo\"", &[(Foo::Bar, "'Bar'"), (Foo::Baz, "'Baz'")]); -} - -#[test] -fn name_overrides() { - #[derive(Debug, ToSql, FromSql, PartialEq)] - #[postgres(name = "mood")] - enum Mood { - #[postgres(name = "sad")] - Sad, - #[postgres(name = "ok")] - Ok, - #[postgres(name = "happy")] - Happy, - } - - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE TYPE pg_temp.mood AS ENUM ('sad', 'ok', 'happy')", &[]).unwrap(); - - util::test_type(&conn, - "mood", - &[(Mood::Sad, "'sad'"), (Mood::Ok, "'ok'"), (Mood::Happy, "'happy'")]); -} - -#[test] -fn wrong_name() { - #[derive(Debug, ToSql, FromSql, PartialEq)] - enum Foo { - Bar, - Baz - } - - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE TYPE pg_temp.foo AS ENUM ('Bar', 'Baz')", &[]).unwrap(); - - match conn.execute("SELECT $1::foo", &[&Foo::Bar]) { - Err(Error::Conversion(ref r)) if r.is::() => {} - v => panic!("unexpected response {:?}", v), - } -} - -#[test] -fn extra_variant() { - #[derive(Debug, ToSql, FromSql, PartialEq)] - #[postgres(name = "foo")] - enum Foo { - Bar, - Baz, - Buz, - } - - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE TYPE pg_temp.foo AS ENUM ('Bar', 'Baz')", &[]).unwrap(); - - match conn.execute("SELECT $1::foo", &[&Foo::Bar]) { - Err(Error::Conversion(ref r)) if r.is::() => {} - v => panic!("unexpected response {:?}", v), - } -} - -#[test] -fn missing_variant() { - #[derive(Debug, ToSql, FromSql, PartialEq)] - #[postgres(name = "foo")] - enum Foo { - Bar, - } - - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.execute("CREATE TYPE pg_temp.foo AS ENUM ('Bar', 'Baz')", &[]).unwrap(); - - match conn.execute("SELECT $1::foo", &[&Foo::Bar]) { - Err(Error::Conversion(ref r)) if r.is::() => {} - v => panic!("unexpected response {:?}", v), - } -} diff --git a/postgres-derive-internals/src/accepts.rs b/src/accepts.rs similarity index 81% rename from postgres-derive-internals/src/accepts.rs rename to src/accepts.rs index 04266c4..feab6fd 100644 --- a/postgres-derive-internals/src/accepts.rs +++ b/src/accepts.rs @@ -5,6 +5,23 @@ use quote::Tokens; use enums::Variant; use composites::Field; +pub fn domain_body(name: &str, field: &syn::Field) -> Tokens { + let ty = &field.ty; + + quote! { + if type_.name() != #name { + return false; + } + + match *type_.kind() { + ::postgres::types::Kind::Domain(ref type_) => { + <#ty as ::postgres::types::ToSql>::accepts(type_) + } + _ => false, + } + } +} + pub fn enum_body(name: &str, variants: &[Variant]) -> Tokens { let num_variants = variants.len(); let variant_names = variants.iter().map(|v| &v.name); @@ -36,7 +53,7 @@ pub fn enum_body(name: &str, variants: &[Variant]) -> Tokens { pub fn composite_body(name: &str, trait_: &str, fields: &[Field]) -> Tokens { let num_fields = fields.len(); - let trait_ = Ident::new(trait_); + let trait_ = Ident::from(trait_); let traits = iter::repeat(&trait_); let field_names = fields.iter().map(|f| &f.name); let field_types = fields.iter().map(|f| &f.type_); diff --git a/postgres-derive-internals/src/composites.rs b/src/composites.rs similarity index 73% rename from postgres-derive-internals/src/composites.rs rename to src/composites.rs index bbec77c..84eebcc 100644 --- a/postgres-derive-internals/src/composites.rs +++ b/src/composites.rs @@ -1,21 +1,21 @@ -use syn::{self, Ident, Ty}; +use syn::{self, Ident, Type}; use overrides::Overrides; pub struct Field { pub name: String, pub ident: Ident, - pub type_: Ty, + pub type_: Type, } impl Field { pub fn parse(raw: &syn::Field) -> Result { - let overrides = try!(Overrides::extract(&raw.attrs)); + let overrides = Overrides::extract(&raw.attrs)?; let ident = raw.ident.as_ref().unwrap().clone(); Ok(Field { name: overrides.name.unwrap_or_else(|| ident.to_string()), - ident: ident, + ident, type_: raw.ty.clone(), }) } diff --git a/postgres-derive-internals/src/enums.rs b/src/enums.rs similarity index 72% rename from postgres-derive-internals/src/enums.rs rename to src/enums.rs index bc97f23..d213e0a 100644 --- a/postgres-derive-internals/src/enums.rs +++ b/src/enums.rs @@ -1,4 +1,4 @@ -use syn::{self, Ident, VariantData}; +use syn::{self, Ident, Fields}; use overrides::Overrides; @@ -9,12 +9,12 @@ pub struct Variant { impl Variant { pub fn parse(raw: &syn::Variant) -> Result { - match raw.data { - VariantData::Unit => {} + match raw.fields { + Fields::Unit => {} _ => return Err("non-C-like enums are not supported".to_owned()), } - let overrides = try!(Overrides::extract(&raw.attrs)); + let overrides = Overrides::extract(&raw.attrs)?; Ok(Variant { ident: raw.ident.clone(), name: overrides.name.unwrap_or_else(|| raw.ident.to_string()), diff --git a/postgres-derive-internals/src/fromsql.rs b/src/fromsql.rs similarity index 73% rename from postgres-derive-internals/src/fromsql.rs rename to src/fromsql.rs index a311411..1f58029 100644 --- a/postgres-derive-internals/src/fromsql.rs +++ b/src/fromsql.rs @@ -1,5 +1,5 @@ use std::iter; -use syn::{self, Body, Ident, MacroInput, VariantData}; +use syn::{self, Ident, DeriveInput, Data, DataStruct, Fields}; use quote::Tokens; use accepts; @@ -7,22 +7,22 @@ use composites::Field; use enums::Variant; use overrides::Overrides; -pub fn expand_derive_fromsql(input: &MacroInput) -> Result { - let overrides = try!(Overrides::extract(&input.attrs)); +pub fn expand_derive_fromsql(input: DeriveInput) -> Result { + let overrides = Overrides::extract(&input.attrs)?; let name = overrides.name.unwrap_or_else(|| input.ident.to_string()); - let (accepts_body, to_sql_body) = match input.body { - Body::Enum(ref variants) => { - let variants: Vec = try!(variants.iter().map(Variant::parse).collect()); + let (accepts_body, to_sql_body) = match input.data { + Data::Enum(ref data) => { + let variants = data.variants.iter().map(Variant::parse).collect::, _>>()?; (accepts::enum_body(&name, &variants), enum_body(&input.ident, &variants)) } - Body::Struct(VariantData::Tuple(ref fields)) if fields.len() == 1 => { - let field = &fields[0]; - (domain_accepts_body(field), domain_body(&input.ident, field)) + Data::Struct(DataStruct { fields: Fields::Unnamed(ref fields), .. }) if fields.unnamed.len() == 1 => { + let field = fields.unnamed.first().unwrap().into_value(); + (domain_accepts_body(&name, field), domain_body(&input.ident, field)) } - Body::Struct(VariantData::Struct(ref fields)) => { - let fields: Vec = try!(fields.iter().map(Field::parse).collect()); + Data::Struct(DataStruct { fields: Fields::Named(ref fields), .. }) => { + let fields = fields.named.iter().map(Field::parse).collect::, _>>()?; (accepts::composite_body(&name, "FromSql", &fields), composite_body(&input.ident, &fields)) } @@ -36,8 +36,7 @@ pub fn expand_derive_fromsql(input: &MacroInput) -> Result { let out = quote! { impl ::postgres::types::FromSql for #ident { fn from_sql(_type: &::postgres::types::Type, - buf: &[u8], - _info: &::postgres::types::SessionInfo) + buf: &[u8]) -> ::std::result::Result<#ident, ::std::boxed::Box<::std::error::Error + ::std::marker::Sync + @@ -51,7 +50,7 @@ pub fn expand_derive_fromsql(input: &MacroInput) -> Result { } }; - Ok(out.to_string()) + Ok(out) } fn enum_body(ident: &Ident, variants: &[Variant]) -> Tokens { @@ -60,7 +59,7 @@ fn enum_body(ident: &Ident, variants: &[Variant]) -> Tokens { let variant_idents = variants.iter().map(|v| &v.ident); quote! { - match try!(::std::str::from_utf8(buf)) { + match ::std::str::from_utf8(buf)? { #( #variant_names => ::std::result::Result::Ok(#idents::#variant_idents), )* @@ -72,29 +71,36 @@ fn enum_body(ident: &Ident, variants: &[Variant]) -> Tokens { } } -fn domain_accepts_body(field: &syn::Field) -> Tokens { +// Domains are sometimes but not always just represented by the bare type (!?) +fn domain_accepts_body(name: &str, field: &syn::Field) -> Tokens { let ty = &field.ty; + let normal_body = accepts::domain_body(name, field); + quote! { - <#ty as ::postgres::types::FromSql>::accepts(type_) + if <#ty as ::postgres::types::FromSql>::accepts(type_) { + return true; + } + + #normal_body } } fn domain_body(ident: &Ident, field: &syn::Field) -> Tokens { let ty = &field.ty; quote! { - <#ty as ::postgres::types::FromSql>::from_sql(_type, buf, _info).map(#ident) + <#ty as ::postgres::types::FromSql>::from_sql(_type, buf).map(#ident) } } fn composite_body(ident: &Ident, fields: &[Field]) -> Tokens { - let temp_vars = &fields.iter().map(|f| Ident::new(format!("__{}", f.ident))).collect::>(); + let temp_vars = &fields.iter().map(|f| Ident::from(format!("__{}", f.ident))).collect::>(); let field_names = &fields.iter().map(|f| &f.name).collect::>(); let field_idents = &fields.iter().map(|f| &f.ident).collect::>(); quote! { fn read_be_i32(buf: &mut &[u8]) -> ::std::io::Result { let mut bytes = [0; 4]; - try!(::std::io::Read::read_exact(buf, &mut bytes)); + ::std::io::Read::read_exact(buf, &mut bytes)?; let num = ((bytes[0] as i32) << 24) | ((bytes[1] as i32) << 16) | ((bytes[2] as i32) << 8) | @@ -103,15 +109,14 @@ fn composite_body(ident: &Ident, fields: &[Field]) -> Tokens { } fn read_value(type_: &::postgres::types::Type, - buf: &mut &[u8], - info: &::postgres::types::SessionInfo) + buf: &mut &[u8]) -> ::std::result::Result> where T: ::postgres::types::FromSql { - let len = try!(read_be_i32(buf)); + let len = read_be_i32(buf)?; let value = if len < 0 { ::std::option::Option::None } else { @@ -123,7 +128,7 @@ fn composite_body(ident: &Ident, fields: &[Field]) -> Tokens { *buf = tail; ::std::option::Option::Some(&head[..]) }; - ::postgres::types::FromSql::from_sql_nullable(type_, value, info) + ::postgres::types::FromSql::from_sql_nullable(type_, value) } let fields = match *_type.kind() { @@ -132,7 +137,7 @@ fn composite_body(ident: &Ident, fields: &[Field]) -> Tokens { }; let mut buf = buf; - let num_fields = try!(read_be_i32(&mut buf)); + let num_fields = read_be_i32(&mut buf)?; if num_fields as usize != fields.len() { return ::std::result::Result::Err( ::std::convert::Into::into(format!("invalid field count: {} vs {}", num_fields, @@ -144,7 +149,7 @@ fn composite_body(ident: &Ident, fields: &[Field]) -> Tokens { )* for field in fields { - let oid = try!(read_be_i32(&mut buf)) as u32; + let oid = read_be_i32(&mut buf)? as u32; if oid != field.type_().oid() { return ::std::result::Result::Err(::std::convert::Into::into("unexpected OID")); } @@ -153,7 +158,7 @@ fn composite_body(ident: &Ident, fields: &[Field]) -> Tokens { #( #field_names => { #temp_vars = ::std::option::Option::Some( - try!(read_value(field.type_(), &mut buf, _info))); + read_value(field.type_(), &mut buf)?); } )* _ => unreachable!(), diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7f2964a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,27 @@ +#![recursion_limit = "256"] + +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; + +mod accepts; +mod composites; +mod enums; +mod fromsql; +mod overrides; +mod tosql; + +#[proc_macro_derive(ToSql, attributes(postgres))] +pub fn derive_tosql(input: TokenStream) -> TokenStream { + let input = syn::parse(input).unwrap(); + tosql::expand_derive_tosql(input).unwrap().into() +} + +#[proc_macro_derive(FromSql, attributes(postgres))] +pub fn derive_fromsql(input: TokenStream) -> TokenStream { + let input = syn::parse(input).unwrap(); + fromsql::expand_derive_fromsql(input).unwrap().into() +} diff --git a/postgres-derive-internals/src/overrides.rs b/src/overrides.rs similarity index 57% rename from postgres-derive-internals/src/overrides.rs rename to src/overrides.rs index 028f893..4c888f4 100644 --- a/postgres-derive-internals/src/overrides.rs +++ b/src/overrides.rs @@ -1,4 +1,4 @@ -use syn::{Attribute, MetaItem, NestedMetaItem, Lit}; +use syn::{Attribute, Meta, NestedMeta, Lit}; pub struct Overrides { pub name: Option, @@ -11,24 +11,29 @@ impl Overrides { }; for attr in attrs { - if attr.value.name() != "postgres" { + let attr = match attr.interpret_meta() { + Some(meta) => meta, + None => continue, + }; + + if attr.name() != "postgres" { continue; } - let list = match attr.value { - MetaItem::List(_, ref list) => list, + let list = match attr { + Meta::List(ref list) => list, _ => return Err("expected a #[postgres(...)]".to_owned()), }; - for item in list { + for item in &list.nested { match *item { - NestedMetaItem::MetaItem(MetaItem::NameValue(ref name, ref value)) => { - if name != "name" { - return Err(format!("unknown override `{}`", name)); + NestedMeta::Meta(Meta::NameValue(ref meta)) => { + if meta.ident.as_ref() != "name" { + return Err(format!("unknown override `{}`", meta.ident.as_ref())); } - let value = match *value { - Lit::Str(ref s, _) => s.to_owned(), + let value = match meta.lit { + Lit::Str(ref s) => s.value(), _ => return Err("expected a string literal".to_owned()), }; diff --git a/postgres-derive-internals/src/tosql.rs b/src/tosql.rs similarity index 67% rename from postgres-derive-internals/src/tosql.rs rename to src/tosql.rs index 4dd5126..f41a905 100644 --- a/postgres-derive-internals/src/tosql.rs +++ b/src/tosql.rs @@ -1,5 +1,5 @@ use std::iter; -use syn::{self, Body, Ident, MacroInput, VariantData}; +use syn::{Ident, DeriveInput, Data, DataStruct, Fields}; use quote::Tokens; use accepts; @@ -7,22 +7,22 @@ use composites::Field; use enums::Variant; use overrides::Overrides; -pub fn expand_derive_tosql(input: &MacroInput) -> Result { - let overrides = try!(Overrides::extract(&input.attrs)); +pub fn expand_derive_tosql(input: DeriveInput) -> Result { + let overrides = Overrides::extract(&input.attrs)?; let name = overrides.name.unwrap_or_else(|| input.ident.to_string()); - let (accepts_body, to_sql_body) = match input.body { - Body::Enum(ref variants) => { - let variants: Vec = try!(variants.iter().map(Variant::parse).collect()); + let (accepts_body, to_sql_body) = match input.data { + Data::Enum(ref data) => { + let variants = data.variants.iter().map(Variant::parse).collect::, _>>()?; (accepts::enum_body(&name, &variants), enum_body(&input.ident, &variants)) } - Body::Struct(VariantData::Tuple(ref fields)) if fields.len() == 1 => { - let field = &fields[0]; - (domain_accepts_body(&name, &field), domain_body()) + Data::Struct(DataStruct { fields: Fields::Unnamed(ref fields), .. }) if fields.unnamed.len() == 1 => { + let field = fields.unnamed.first().unwrap().into_value(); + (accepts::domain_body(&name, &field), domain_body()) } - Body::Struct(VariantData::Struct(ref fields)) => { - let fields: Vec = try!(fields.iter().map(Field::parse).collect()); + Data::Struct(DataStruct { fields: Fields::Named(ref fields), .. }) => { + let fields = fields.named.iter().map(Field::parse).collect::, _>>()?; (accepts::composite_body(&name, "ToSql", &fields), composite_body(&fields)) } @@ -37,8 +37,7 @@ pub fn expand_derive_tosql(input: &MacroInput) -> Result { impl ::postgres::types::ToSql for #ident { fn to_sql(&self, _type: &::postgres::types::Type, - buf: &mut ::std::vec::Vec, - _info: &::postgres::types::SessionInfo) + buf: &mut ::std::vec::Vec) -> ::std::result::Result<::postgres::types::IsNull, ::std::boxed::Box<::std::error::Error + ::std::marker::Sync + @@ -54,7 +53,7 @@ pub fn expand_derive_tosql(input: &MacroInput) -> Result { } }; - Ok(out.to_string()) + Ok(out) } fn enum_body(ident: &Ident, variants: &[Variant]) -> Tokens { @@ -74,23 +73,6 @@ fn enum_body(ident: &Ident, variants: &[Variant]) -> Tokens { } } -fn domain_accepts_body(name: &str, field: &syn::Field) -> Tokens { - let ty = &field.ty; - - quote! { - if type_.name() != #name { - return false; - } - - match *type_.kind() { - ::postgres::types::Kind::Domain(ref type_) => { - <#ty as ::postgres::types::ToSql>::accepts(type_) - } - _ => false, - } - } -} - fn domain_body() -> Tokens { quote! { let type_ = match *_type.kind() { @@ -98,7 +80,7 @@ fn domain_body() -> Tokens { _ => unreachable!(), }; - ::postgres::types::ToSql::to_sql(&self.0, type_, buf, _info) + ::postgres::types::ToSql::to_sql(&self.0, type_, buf) } } @@ -119,26 +101,25 @@ fn composite_body(fields: &[Field]) -> Tokens { _ => unreachable!(), }; - try!(write_be_i32(buf, fields.len() as i32)); + write_be_i32(buf, fields.len() as i32)?; for field in fields { - try!(write_be_i32(buf, field.type_().oid() as i32)); + write_be_i32(buf, field.type_().oid() as i32)?; let base = buf.len(); - try!(write_be_i32(buf, 0)); + write_be_i32(buf, 0)?; let r = match field.name() { #( #field_names => { ::postgres::types::ToSql::to_sql(&self.#field_idents, field.type_(), - buf, - _info) + buf) } )* _ => unreachable!(), }; - let count = match try!(r) { + let count = match r? { ::postgres::types::IsNull::Yes => -1, ::postgres::types::IsNull::No => { let len = buf.len() - base - 4; @@ -150,7 +131,7 @@ fn composite_body(fields: &[Field]) -> Tokens { } }; - try!(write_be_i32(&mut &mut buf[base..base + 4], count)); + write_be_i32(&mut &mut buf[base..base + 4], count)?; } ::std::result::Result::Ok(::postgres::types::IsNull::No) diff --git a/postgres-derive/tests/composites.rs b/tests/composites.rs similarity index 58% rename from postgres-derive/tests/composites.rs rename to tests/composites.rs index 695009d..0454d29 100644 --- a/postgres-derive/tests/composites.rs +++ b/tests/composites.rs @@ -1,12 +1,9 @@ -#![feature(proc_macro)] - #[macro_use] extern crate postgres_derive; #[macro_use] extern crate postgres; use postgres::{Connection, TlsMode}; -use postgres::error::Error; use postgres::types::WrongType; mod util; @@ -20,12 +17,15 @@ fn defaults() { price: Option, } - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.batch_execute("CREATE TYPE pg_temp.\"InventoryItem\" AS ( + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.batch_execute( + "CREATE TYPE pg_temp.\"InventoryItem\" AS ( name TEXT, supplier_id INT, price DOUBLE PRECISION - );").unwrap(); + );", + ).unwrap(); let item = InventoryItem { name: "foobar".to_owned(), @@ -39,9 +39,14 @@ fn defaults() { price: None, }; - util::test_type(&conn, "\"InventoryItem\"", - &[(item, "ROW('foobar', 100, 15.50)"), - (item_null, "ROW('foobar', 100, NULL)")]); + util::test_type( + &conn, + "\"InventoryItem\"", + &[ + (item, "ROW('foobar', 100, 15.50)"), + (item_null, "ROW('foobar', 100, NULL)"), + ], + ); } #[test] @@ -57,12 +62,15 @@ fn name_overrides() { _price: Option, } - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.batch_execute("CREATE TYPE pg_temp.inventory_item AS ( + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.batch_execute( + "CREATE TYPE pg_temp.inventory_item AS ( name TEXT, supplier_id INT, price DOUBLE PRECISION - );").unwrap(); + );", + ).unwrap(); let item = InventoryItem { _name: "foobar".to_owned(), @@ -76,9 +84,14 @@ fn name_overrides() { _price: None, }; - util::test_type(&conn, "inventory_item", - &[(item, "ROW('foobar', 100, 15.50)"), - (item_null, "ROW('foobar', 100, NULL)")]); + util::test_type( + &conn, + "inventory_item", + &[ + (item, "ROW('foobar', 100, 15.50)"), + (item_null, "ROW('foobar', 100, NULL)"), + ], + ); } #[test] @@ -90,12 +103,15 @@ fn wrong_name() { price: Option, } - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.batch_execute("CREATE TYPE pg_temp.inventory_item AS ( + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.batch_execute( + "CREATE TYPE pg_temp.inventory_item AS ( name TEXT, supplier_id INT, price DOUBLE PRECISION - );").unwrap(); + );", + ).unwrap(); let item = InventoryItem { name: "foobar".to_owned(), @@ -103,10 +119,9 @@ fn wrong_name() { price: Some(15.50), }; - match conn.execute("SELECT $1::inventory_item", &[&item]) { - Err(Error::Conversion(ref r)) if r.is::() => {} - v => panic!("unexpected response {:?}", v), - } + let err = conn.execute("SELECT $1::inventory_item", &[&item]) + .unwrap_err(); + assert!(err.as_conversion().unwrap().is::()); } #[test] @@ -120,12 +135,15 @@ fn extra_field() { foo: i32, } - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.batch_execute("CREATE TYPE pg_temp.inventory_item AS ( + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.batch_execute( + "CREATE TYPE pg_temp.inventory_item AS ( name TEXT, supplier_id INT, price DOUBLE PRECISION - );").unwrap(); + );", + ).unwrap(); let item = InventoryItem { name: "foobar".to_owned(), @@ -134,10 +152,9 @@ fn extra_field() { foo: 0, }; - match conn.execute("SELECT $1::inventory_item", &[&item]) { - Err(Error::Conversion(ref r)) if r.is::() => {} - v => panic!("unexpected response {:?}", v), - } + let err = conn.execute("SELECT $1::inventory_item", &[&item]) + .unwrap_err(); + assert!(err.as_conversion().unwrap().is::()); } #[test] @@ -149,22 +166,24 @@ fn missing_field() { supplier_id: i32, } - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.batch_execute("CREATE TYPE pg_temp.inventory_item AS ( + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.batch_execute( + "CREATE TYPE pg_temp.inventory_item AS ( name TEXT, supplier_id INT, price DOUBLE PRECISION - );").unwrap(); + );", + ).unwrap(); let item = InventoryItem { name: "foobar".to_owned(), supplier_id: 100, }; - match conn.execute("SELECT $1::inventory_item", &[&item]) { - Err(Error::Conversion(ref r)) if r.is::() => {} - v => panic!("unexpected response {:?}", v), - } + let err = conn.execute("SELECT $1::inventory_item", &[&item]) + .unwrap_err(); + assert!(err.as_conversion().unwrap().is::()); } #[test] @@ -177,12 +196,15 @@ fn wrong_type() { price: i32, } - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - conn.batch_execute("CREATE TYPE pg_temp.inventory_item AS ( + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.batch_execute( + "CREATE TYPE pg_temp.inventory_item AS ( name TEXT, supplier_id INT, price DOUBLE PRECISION - );").unwrap(); + );", + ).unwrap(); let item = InventoryItem { name: "foobar".to_owned(), @@ -190,8 +212,7 @@ fn wrong_type() { price: 0, }; - match conn.execute("SELECT $1::inventory_item", &[&item]) { - Err(Error::Conversion(ref r)) if r.is::() => {} - v => panic!("unexpected response {:?}", v), - } + let err = conn.execute("SELECT $1::inventory_item", &[&item]) + .unwrap_err(); + assert!(err.as_conversion().unwrap().is::()); } diff --git a/tests/domains.rs b/tests/domains.rs new file mode 100644 index 0000000..f7c09f4 --- /dev/null +++ b/tests/domains.rs @@ -0,0 +1,125 @@ +#[macro_use] +extern crate postgres_derive; +#[macro_use] +extern crate postgres; + +use postgres::{Connection, TlsMode}; +use postgres::types::WrongType; + +mod util; + +#[test] +fn defaults() { + #[derive(FromSql, ToSql, Debug, PartialEq)] + struct SessionId(Vec); + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.execute( + "CREATE DOMAIN pg_temp.\"SessionId\" AS bytea CHECK(octet_length(VALUE) = 16);", + &[], + ).unwrap(); + + util::test_type( + &conn, + "\"SessionId\"", + &[ + ( + SessionId(b"0123456789abcdef".to_vec()), + "'0123456789abcdef'", + ), + ], + ); +} + +#[test] +fn name_overrides() { + #[derive(FromSql, ToSql, Debug, PartialEq)] + #[postgres(name = "session_id")] + struct SessionId(Vec); + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.execute( + "CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", + &[], + ).unwrap(); + + util::test_type( + &conn, + "session_id", + &[ + ( + SessionId(b"0123456789abcdef".to_vec()), + "'0123456789abcdef'", + ), + ], + ); +} + +#[test] +fn wrong_name() { + #[derive(FromSql, ToSql, Debug, PartialEq)] + struct SessionId(Vec); + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.execute( + "CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", + &[], + ).unwrap(); + + let err = conn.execute("SELECT $1::session_id", &[&SessionId(vec![])]) + .unwrap_err(); + assert!(err.as_conversion().unwrap().is::()); +} + +#[test] +fn wrong_type() { + #[derive(FromSql, ToSql, Debug, PartialEq)] + #[postgres(name = "session_id")] + struct SessionId(i32); + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.execute( + "CREATE DOMAIN pg_temp.session_id AS bytea CHECK(octet_length(VALUE) = 16);", + &[], + ).unwrap(); + + let err = conn.execute("SELECT $1::session_id", &[&SessionId(0)]) + .unwrap_err(); + assert!(err.as_conversion().unwrap().is::()); +} + +#[test] +fn domain_in_composite() { + #[derive(FromSql, ToSql, Debug, PartialEq)] + #[postgres(name = "domain")] + struct Domain(String); + + #[derive(FromSql, ToSql, Debug, PartialEq)] + #[postgres(name = "composite")] + struct Composite { + domain: Domain, + } + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.batch_execute( + " + CREATE DOMAIN pg_temp.domain AS TEXT;\ + CREATE TYPE pg_temp.composite AS ( + domain domain + ); + ", + ).unwrap(); + + util::test_type( + &conn, + "composite", + &[ + (Composite { domain: Domain("hello".to_string()) }, "ROW('hello')"), + ], + ); +} diff --git a/tests/enums.rs b/tests/enums.rs new file mode 100644 index 0000000..d54aeda --- /dev/null +++ b/tests/enums.rs @@ -0,0 +1,113 @@ +#[macro_use] +extern crate postgres_derive; +#[macro_use] +extern crate postgres; + +use postgres::{Connection, TlsMode}; +use postgres::types::WrongType; + +mod util; + +#[test] +fn defaults() { + #[derive(Debug, ToSql, FromSql, PartialEq)] + enum Foo { + Bar, + Baz, + } + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.execute("CREATE TYPE pg_temp.\"Foo\" AS ENUM ('Bar', 'Baz')", &[]) + .unwrap(); + + util::test_type( + &conn, + "\"Foo\"", + &[(Foo::Bar, "'Bar'"), (Foo::Baz, "'Baz'")], + ); +} + +#[test] +fn name_overrides() { + #[derive(Debug, ToSql, FromSql, PartialEq)] + #[postgres(name = "mood")] + enum Mood { + #[postgres(name = "sad")] + Sad, + #[postgres(name = "ok")] + Ok, + #[postgres(name = "happy")] + Happy, + } + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.execute( + "CREATE TYPE pg_temp.mood AS ENUM ('sad', 'ok', 'happy')", + &[], + ).unwrap(); + + util::test_type( + &conn, + "mood", + &[ + (Mood::Sad, "'sad'"), + (Mood::Ok, "'ok'"), + (Mood::Happy, "'happy'"), + ], + ); +} + +#[test] +fn wrong_name() { + #[derive(Debug, ToSql, FromSql, PartialEq)] + enum Foo { + Bar, + Baz, + } + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.execute("CREATE TYPE pg_temp.foo AS ENUM ('Bar', 'Baz')", &[]) + .unwrap(); + + let err = conn.execute("SELECT $1::foo", &[&Foo::Bar]).unwrap_err(); + assert!(err.as_conversion().unwrap().is::()); +} + +#[test] +fn extra_variant() { + #[derive(Debug, ToSql, FromSql, PartialEq)] + #[postgres(name = "foo")] + enum Foo { + Bar, + Baz, + Buz, + } + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.execute("CREATE TYPE pg_temp.foo AS ENUM ('Bar', 'Baz')", &[]) + .unwrap(); + + let err = conn.execute("SELECT $1::foo", &[&Foo::Bar]).unwrap_err(); + assert!(err.as_conversion().unwrap().is::()); +} + +#[test] +fn missing_variant() { + #[derive(Debug, ToSql, FromSql, PartialEq)] + #[postgres(name = "foo")] + enum Foo { + Bar, + } + + let conn = Connection::connect("postgres://postgres:password@localhost", TlsMode::None) + .unwrap(); + conn.execute("CREATE TYPE pg_temp.foo AS ENUM ('Bar', 'Baz')", &[]) + .unwrap(); + + let err = conn.execute("SELECT $1::foo", &[&Foo::Bar]).unwrap_err(); + assert!(err.as_conversion().unwrap().is::()); +} diff --git a/postgres-derive/tests/util/mod.rs b/tests/util/mod.rs similarity index 85% rename from postgres-derive/tests/util/mod.rs rename to tests/util/mod.rs index fb90968..f44d98a 100644 --- a/postgres-derive/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -3,10 +3,13 @@ use postgres::Connection; use std::fmt; pub fn test_type(conn: &Connection, sql_type: &str, checks: &[(T, S)]) - where T: PartialEq + FromSql + ToSql, S: fmt::Display +where + T: PartialEq + FromSql + ToSql, + S: fmt::Display, { for &(ref val, ref repr) in checks.iter() { - let stmt = conn.prepare(&*format!("SELECT {}::{}", *repr, sql_type)).unwrap(); + let stmt = conn.prepare(&*format!("SELECT {}::{}", *repr, sql_type)) + .unwrap(); let result = stmt.query(&[]).unwrap().iter().next().unwrap().get(0); assert_eq!(val, &result);