diff --git a/Cargo.toml b/Cargo.toml index 8f0e30a..9035a5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "postgres-derive" -version = "0.3.2" +version = "0.3.3" authors = ["Steven Fackler "] license = "MIT/Apache-2.0" description = "Deriving plugin support for Postgres enum, domain, and composite types" @@ -13,8 +13,8 @@ proc-macro = true test = false [dependencies] -syn = "0.12" -quote = "0.4" +syn = "0.13" +quote = "0.5" [dev-dependencies] postgres = "0.15.1" diff --git a/README.md b/README.md index e170617..46617e5 100644 --- a/README.md +++ b/README.md @@ -1,171 +1,3 @@ # postgres-derive -[![CircleCI](https://circleci.com/gh/sfackler/rust-postgres-derive.svg?style=shield)](https://circleci.com/gh/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.14 or 0.15 and Rust 1.15.0 or higher. - -# Usage - -Simply depend on the `postgres-derive` crate and register it as a plugin: - -Cargo.toml -```toml -# ... - -[dependencies] -postgres-derive = "0.3" -postgres = "0.15" -``` - -lib.rs -```rust -#[macro_use] -extern crate postgres; -#[macro_use] -extern crate postgres_derive; - -#[derive(Debug, ToSql, FromSql)] -pub enum Mood { - Sad, - Ok, - Happy, -} - -// ... -``` - -# 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 index a648ab0..36baa63 100644 --- a/circle.yml +++ b/circle.yml @@ -1,9 +1,8 @@ version: 2 jobs: build: - working_directory: ~/build docker: - - image: jimmycuadra/rust:1.19.0 + - image: rust:1.31.0 - image: postgres:9.6 environment: POSTGRES_PASSWORD: password @@ -17,10 +16,10 @@ jobs: paths: - ~/.cargo/registry/index - restore_cache: - key: dependencies-1.19-{{ checksum "Cargo.lock" }} + key: dependencies-1.20-{{ checksum "Cargo.lock" }} - run: cargo test - save_cache: - key: dependencies-1.19-{{ checksum "Cargo.lock" }} + key: dependencies-1.20-{{ checksum "Cargo.lock" }} paths: - target - ~/.cargo/registry/cache diff --git a/src/accepts.rs b/src/accepts.rs index 04310ee..feab6fd 100644 --- a/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); diff --git a/src/composites.rs b/src/composites.rs index f58cbd9..84eebcc 100644 --- a/src/composites.rs +++ b/src/composites.rs @@ -15,7 +15,7 @@ impl Field { 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/src/fromsql.rs b/src/fromsql.rs index 40373f3..1f58029 100644 --- a/src/fromsql.rs +++ b/src/fromsql.rs @@ -19,7 +19,7 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result { } Data::Struct(DataStruct { fields: Fields::Unnamed(ref fields), .. }) if fields.unnamed.len() == 1 => { let field = fields.unnamed.first().unwrap().into_value(); - (domain_accepts_body(field), domain_body(&input.ident, field)) + (domain_accepts_body(&name, field), domain_body(&input.ident, field)) } Data::Struct(DataStruct { fields: Fields::Named(ref fields), .. }) => { let fields = fields.named.iter().map(Field::parse).collect::, _>>()?; @@ -71,10 +71,17 @@ 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 } } diff --git a/src/tosql.rs b/src/tosql.rs index 4ea89ab..f41a905 100644 --- a/src/tosql.rs +++ b/src/tosql.rs @@ -1,5 +1,5 @@ use std::iter; -use syn::{self, Ident, DeriveInput, Data, DataStruct, Fields}; +use syn::{Ident, DeriveInput, Data, DataStruct, Fields}; use quote::Tokens; use accepts; @@ -19,7 +19,7 @@ pub fn expand_derive_tosql(input: DeriveInput) -> Result { } 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()) + (accepts::domain_body(&name, &field), domain_body()) } Data::Struct(DataStruct { fields: Fields::Named(ref fields), .. }) => { let fields = fields.named.iter().map(Field::parse).collect::, _>>()?; @@ -73,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() { diff --git a/tests/domains.rs b/tests/domains.rs index de3c7fa..f7c09f4 100644 --- a/tests/domains.rs +++ b/tests/domains.rs @@ -91,3 +91,35 @@ fn wrong_type() { .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')"), + ], + ); +}