Skip to content

Commit

Permalink
Merge pull request #14 from SeaQL/sea-query-derive
Browse files Browse the repository at this point in the history
Sea query derive
  • Loading branch information
billy1624 authored Feb 9, 2021
2 parents a9bda78 + c97f312 commit 6b2e768
Show file tree
Hide file tree
Showing 14 changed files with 435 additions and 35 deletions.
28 changes: 21 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
[workspace]
members = [
".",
"sea-query-derive",
]

[package]
name = "sea-query"
version = "0.4.0"
authors = ["Billy Chan <[email protected]>"]
version = "0.5.0"
authors = [ "Billy Chan <[email protected]>" ]
edition = "2018"
description = "A database agnostic runtime query builder for Rust"
license = "MIT OR Apache-2.0"
documentation = "https://docs.rs/sea-query"
repository = "https://github.com/SeaQL/Sea-Query"
categories = ["database"]
keywords = ["database", "sql", "mysql", "postgres", "sqlite"]
repository = "https://github.com/SeaQL/sea-query"
categories = [ "database" ]
keywords = [ "database", "sql", "mysql", "postgres", "sqlite" ]

[lib]
name = "sea_query"
path = "src/lib.rs"

[dependencies]
serde_json = "1.0"
sea-query-derive = { version = "0.1.0", path = "sea-query-derive", default-features = false, optional = true }

[dev-dependencies]
async-std = "1.8"
sqlx = { version = "0.4.0", default-features = false, features = [ "runtime-async-std-native-tls", "macros", "any", "mysql", "postgres", "sqlite", "tls", "migrate", "decimal" ] }

[features]
sqlx-driver = []
default = [ "derive" ]
sqlx-driver = [ ]
derive = [ "sea-query-derive" ]

[[example]]
name = "sqlx"
required-features = ["sqlx-driver"]
required-features = [ "sqlx-driver" ]

[[example]]
name = "derive"
required-features = [ "derive" ]
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ impl Iden for Character {
}
```

If you're okay with running another procedural macro, you can activate
the `derive` feature on the crate to save you some boilerplate:

```rust
use sea_query::Iden;

// This will implement Iden exactly as shown above
#[derive(Iden)]
pub enum Character {
Table,
Id,
Character,
FontSize,
SizeW,
SizeH,
FontId,
}
```

You can also override the generated column names by specifying an `#[iden = ""]`
attribute on the enum or any of its variants; for more information, look at
[the derive example](https://github.com/SeaQL/sea-query/blob/master/examples/derive.rs).

### Expression

Use [`Expr`] to construct select, join, where and having expression in query.
Expand Down
68 changes: 68 additions & 0 deletions examples/derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use sea_query::Iden;

#[derive(Iden)]
enum User {
Table,
Id,
FirstName,
LastName,
Email,
}

#[derive(Iden)]
// Outer iden attributes overrides what's used for "Table"...
#[iden = "user"]
enum Custom {
Table,
#[iden = "my_id"]
Id,
#[iden = "name"]
FirstName,
#[iden = "surname"]
LastName,
// Custom casing if needed
#[iden = "EMail"]
Email,
}

#[derive(Iden)]
enum Something {
// ...the Table can also be overwritten like this
#[iden = "something_else"]
Table,
Id,
AssetName,
UserId,
}

#[derive(Iden)]
pub struct SomeType;
#[derive(Iden)]
#[iden = "another_name"]
pub struct CustomName;

fn main() {
println!("Default field names");
assert_eq!(dbg!(Iden::to_string(&User::Table)), "user");
assert_eq!(dbg!(Iden::to_string(&User::Id)), "id");
assert_eq!(dbg!(Iden::to_string(&User::FirstName)), "first_name");
assert_eq!(dbg!(Iden::to_string(&User::LastName)), "last_name");
assert_eq!(dbg!(Iden::to_string(&User::Email)), "email");

println!("Custom field names");
assert_eq!(dbg!(Iden::to_string(&Custom::Table)), "user");
assert_eq!(dbg!(Iden::to_string(&Custom::Id)), "my_id");
assert_eq!(dbg!(Iden::to_string(&Custom::FirstName)), "name");
assert_eq!(dbg!(Iden::to_string(&Custom::LastName)), "surname");
assert_eq!(dbg!(Iden::to_string(&Custom::Email)), "EMail");

println!("Single custom field name");
assert_eq!(dbg!(Iden::to_string(&Something::Table)), "something_else");
assert_eq!(dbg!(Iden::to_string(&Something::Id)), "id");
assert_eq!(dbg!(Iden::to_string(&Something::AssetName)), "asset_name");
assert_eq!(dbg!(Iden::to_string(&Something::UserId)), "user_id");

println!("Unit structs");
assert_eq!(dbg!(Iden::to_string(&SomeType)), "some_type");
assert_eq!(dbg!(Iden::to_string(&CustomName)), "another_name");
}
20 changes: 20 additions & 0 deletions sea-query-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "sea-query-derive"
version = "0.1.0"
authors = [ "Follpvosten <[email protected]>" ]
edition = "2018"
description = "Derive macro for sea-query's Iden trait"
license = "MIT OR Apache-2.0"
documentation = "https://docs.rs/sea-query"
repository = "https://github.com/SeaQL/sea-query"
categories = [ "database" ]
keywords = [ "database", "sql", "mysql", "postgres", "sqlite" ]

[lib]
proc-macro = true

[dependencies]
syn = { version = "1", default-features = false, features = [ "derive", "parsing", "proc-macro", "printing" ] }
quote = "1"
sea-query = "0.4"
heck = "0.3"
84 changes: 84 additions & 0 deletions sea-query-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use heck::SnakeCase;
use proc_macro::{self, TokenStream};
use quote::{quote, quote_spanned};
use syn::{parse_macro_input, Attribute, DataEnum, DataStruct, DeriveInput, Fields, Meta, Variant};

fn get_iden_attr(attrs: &[Attribute]) -> Option<syn::Lit> {
for attr in attrs {
let name_value = match attr.parse_meta() {
Ok(Meta::NameValue(nv)) => nv,
_ => continue,
};
if name_value.path.is_ident("iden") {
return Some(name_value.lit);
}
}
None
}

#[proc_macro_derive(Iden, attributes(iden))]
pub fn derive_iden(input: TokenStream) -> TokenStream {
let DeriveInput {
ident, data, attrs, ..
} = parse_macro_input!(input);
let table_name = match get_iden_attr(&attrs) {
Some(lit) => quote! { #lit },
None => {
let normalized = ident.to_string().to_snake_case();
quote! { #normalized }
}
};

// Currently we only support enums and unit structs
let variants =
match data {
syn::Data::Enum(DataEnum { variants, .. }) => variants,
syn::Data::Struct(DataStruct {
fields: Fields::Unit,
..
}) => {
return quote! {
impl sea_query::Iden for #ident {
fn unquoted(&self, s: &mut dyn sea_query::Write) {
write!(s, #table_name).unwrap();
}
}
}
.into()
}
_ => return quote_spanned! {
ident.span() => compile_error!("you can only derive Iden on enums or unit structs");
}
.into(),
};
if variants.is_empty() {
return TokenStream::new();
}

let variant = variants
.iter()
.map(|Variant { ident, .. }| quote! { #ident });
let name = variants.iter().map(|v| {
if let Some(lit) = get_iden_attr(&v.attrs) {
// If the user supplied a name, just adapt it.
return quote! { #lit };
}
if v.ident == "Table" {
table_name.clone()
} else {
let ident = v.ident.to_string().to_snake_case();
quote! { #ident }
}
});

let output = quote! {
impl sea_query::Iden for #ident {
fn unquoted(&self, s: &mut dyn sea_query::Write) {
write!(s, "{}", match self {
#(Self::#variant => #name),*
}).unwrap();
}
}
};
output.into()
}
60 changes: 56 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
//! A database agnostic runtime query builder for Rust.
//!
//! This library is the foundation of upcoming projects on Document ORM (Sea-ORM) and Database Synchor (Sea-Horse).
//! This library aims to provide an ergonomic API to construct Abstract Syntax Trees for SQL.
//! The AST is generic by design and can be serialized to different SQL variants.
//! We align the behaviour between different engines where appropriate, while offering vendor specific features via extensions.
//!
//! This library is the foundation of upcoming projects: Document ORM (SeaORM) and Database Synchor (SeaHorse).
//!
//! # Usage
//!
//! Table of Content
//!
//! 1. [Iden](#iden)
//! 1. [Expression](#expression)
//!
//! 1. [Query Select](#query-select)
//! 1. [Query Insert](#query-insert)
//! 1. [Query Update](#query-update)
//! 1. [Query Delete](#query-delete)
//!
//! 1. [Table Create](#table-create)
//! 1. [Table Alter](#table-alter)
//! 1. [Table Drop](#table-drop)
//! 1. [Table Rename](#table-rename)
//! 1. [Table Truncate](#table-truncate)
//! 1. [Foreign Key Create](#foreign-key-create)
//! 1. [Foreign Key Drop](#foreign-key-drop)
//! 1. [Index Create](#index-create)
//! 1. [Index Drop](#index-drop)
//!
//! Construct a SQL statement with the library then execute the statement with a database connector,
//! see SQLx example [here](https://github.com/SeaQL/sea-query/blob/master/examples/sqlx.rs).
//!
Expand Down Expand Up @@ -45,7 +69,32 @@
//! }
//! }
//! ```
//!
//!
//! If you're okay with running another procedural macro, you can activate
//! the `derive` feature on the crate to save you some boilerplate:
//!
//! ```rust
//! # #[cfg(feature = "derive")]
//! use sea_query::Iden;
//!
//! // This will implement Iden exactly as shown above
//! # #[cfg(feature = "derive")]
//! #[derive(Iden)]
//! pub enum Character {
//! Table,
//! Id,
//! Character,
//! FontSize,
//! SizeW,
//! SizeH,
//! FontId,
//! }
//! ```
//!
//! You can also override the generated column names by specifying an `#[iden = ""]`
//! attribute on the enum or any of its variants; for more information, look at
//! [the derive example](https://github.com/SeaQL/sea-query/blob/master/examples/derive.rs).
//!
//! ## Expression
//!
//! Use [`Expr`] to construct select, join, where and having expression in query.
Expand Down Expand Up @@ -154,7 +203,7 @@
//! use sea_query::{*, tests_cfg::*};
//!
//! let query = Query::update()
//! .into_table(Glyph::Table)
//! .table(Glyph::Table)
//! .values(vec![
//! (Glyph::Aspect, 1.23.into()),
//! (Glyph::Image, "123".into()),
Expand Down Expand Up @@ -517,4 +566,7 @@ pub use expr::*;
pub use prepare::*;
pub use types::*;
pub use token::*;
pub use value::*;
pub use value::*;

#[cfg(feature = "derive")]
pub use sea_query_derive::Iden;
Loading

0 comments on commit 6b2e768

Please sign in to comment.