Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Macros 1.1 port #453

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
`#[derive(AsChangeset)] #[table_name="foo"]`. If you were specifying
`treat_none_as_null = "true"`, you should additionally have
`#[changeset_options(treat_none_as_null = "true")]`.
* `#[insertable_into(foo)]` should now be written as
`#[derive(Insertable)] #[table_name="foo"]`.

## [0.7.2] - 2016-08-20

Expand Down
2 changes: 1 addition & 1 deletion diesel/src/macros/as_changeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ macro_rules! AsChangeset {
) => {
AsChangeset! {
$($headers)*
self_to_columns = $struct_name { $($field_name: ref $column_name,)+ ..},
self_to_columns = $struct_name { $($field_name: ref $column_name,)+ .. },
columns = ($($column_name, $field_kind),+),
field_names = [$($field_name)+],
changeset_ty = $changeset_ty,
Expand Down
5 changes: 4 additions & 1 deletion diesel/src/macros/queryable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,12 @@ macro_rules! Queryable {
(
struct_ty = $struct_ty:ty,
generics = ($($generics:ident),*),
lifetimes = ($($lifetimes:tt),*),
row_ty = $row_ty:ty,
row_pat = $row_pat:pat,
build_expr = $build_expr:expr,
) => {
impl<$($generics,)* __DB, __ST> $crate::Queryable<__ST, __DB> for $struct_ty where
impl<$($lifetimes,)* $($generics,)* __DB, __ST> $crate::Queryable<__ST, __DB> for $struct_ty where
__DB: $crate::backend::Backend + $crate::types::HasSqlType<__ST>,
$row_ty: $crate::types::FromSqlRow<__ST, __DB>,
{
Expand All @@ -131,6 +132,7 @@ macro_rules! Queryable {
struct_name = $struct_name,
struct_ty = $struct_name<$($generics),*>,
generics = ($($generics),*),
lifetimes = (),
),
callback = Queryable,
body = $body,
Expand All @@ -147,6 +149,7 @@ macro_rules! Queryable {
struct_name = $struct_name,
struct_ty = $struct_name,
generics = (),
lifetimes = (),
),
callback = Queryable,
body = $body,
Expand Down
9 changes: 4 additions & 5 deletions diesel/src/persistable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ use result::QueryResult;
use query_builder::{QueryBuilder, BuildQueryResult};
use query_source::{Table, Column};

/// Represents that a structure can be used to to insert a new row into the database.
/// Implementations can be automatically generated by
/// [`#[insertable_into]`](https://github.com/sgrif/diesel/tree/master/diesel_codegen#insertable_intotable_name).
/// This is automatically implemented for `&[T]` and `&Vec<T>` for inserting more than
/// one record.
/// Represents that a structure can be used to to insert a new row into the
/// database. Implementations can be automatically generated by
/// `#[derive(Insertable)]` This is automatically implemented for `&[T]` and
/// `&Vec<T>` for inserting more than one record.
pub trait Insertable<T: Table, DB: Backend> {
type Values: InsertValues<DB>;

Expand Down
17 changes: 10 additions & 7 deletions diesel_cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions diesel_codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,20 @@ Adds an implementation of the [`Queryable`][queryable] trait to the annotated
item. At this time it only supports structs with named fields. Enums and tuple
structs are not supported.

### `#[insertable_into(table_name)]`
### `#[derive(Insertable)]`

Adds an implementation of the [`Insertable`][insertable] trait to the annotated
item, targeting the given table. Can only annotate structs and tuple structs.
Enums are not supported. See [field annotations][#field-annotations] for
additional configurations.

Structs which derive `Insertable` must have a table name annotation like so:
`#[table_name = "something"]`

### `#[derive(AsChangeset)]`

Adds an implementation of the [`AsChangeset`][as_changeset] trait to the
annotated item, targeting the given table. At this time, it only supports
annotated item, targeting the given table. at this time, it only supports
structs with named fields. Tuple structs and enums are not supported. See [field
annotations][#field-annotations] for additional configurations.

Expand Down
16 changes: 0 additions & 16 deletions diesel_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,6 @@ pub fn register(reg: &mut rustc_plugin::Registry) {
intern("derive_Associations"),
MultiDecorator(Box::new(associations::expand_derive_associations)),
);
reg.register_syntax_extension(
intern("derive_AsChangeset"),
MultiDecorator(Box::new(update::expand_derive_as_changeset)),
);
reg.register_syntax_extension(
intern("derive_Identifiable"),
MultiDecorator(Box::new(identifiable::expand_derive_identifiable))
);
reg.register_syntax_extension(
intern("derive_Queryable"),
MultiDecorator(Box::new(queryable::expand_derive_queryable))
);
reg.register_syntax_extension(
intern("insertable_into"),
MultiDecorator(Box::new(insertable::expand_insert))
);
reg.register_macro("embed_migrations", migrations::expand_embed_migrations);
reg.register_macro("infer_table_from_schema", schema_inference::expand_load_table);
reg.register_macro("infer_schema", schema_inference::expand_infer_schema);
Expand Down
11 changes: 11 additions & 0 deletions diesel_codegen_new/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "diesel_codegen_new"
version = "0.8.0"
authors = ["Sean Griffin <sean@seantheprogrammer.com>"]

[dependencies]
syn = "0.4.0"
quote = "0.1.1"

[lib]
rustc-macro = true
62 changes: 62 additions & 0 deletions diesel_codegen_new/src/as_changeset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use syn;
use quote;

use model::Model;
use util::attr_with_name;

pub fn derive_as_changeset(item: syn::Item) -> quote::Tokens {
let treat_none_as_null = format!("{}", treat_none_as_null(&item.attrs));
let model = t!(Model::from_item(&item, "AsChangeset"));

let struct_name = &model.name;
let table_name = model.table_name();
let struct_ty = &model.ty;
let mut lifetimes = item.generics.lifetimes;
let attrs = model.attrs.into_iter()
.filter(|a| a.column_name != Some(syn::Ident::new("id")))
.collect::<Vec<_>>();

if lifetimes.is_empty() {
lifetimes.push(syn::LifetimeDef {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added syn::LifetimeDef::new("'a") in syn 0.8.5 to simplify this a bit.

lifetime: syn::Lifetime {
ident: syn::Ident::new("'a"),
},
bounds: Vec::new(),
});
}

quote!(AsChangeset! {
(
struct_name = #struct_name,
table_name = #table_name,
treat_none_as_null = #treat_none_as_null,
struct_ty = #struct_ty,
lifetimes = (#(lifetimes),*),
),
fields = [#(attrs)*],
})
}

fn treat_none_as_null(attrs: &[syn::Attribute]) -> bool {
let options_attr = match attr_with_name(attrs, "changeset_options") {
Some(attr) => attr,
None => return false,
};

let usage_err = || panic!(r#"`#[changeset_options]` must be in the form \
`#[changeset_options(treat_none_as_null = "true")]`"#);

match options_attr.value {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed dtolnay/syn#25 to make attribute parsing less annoying.

syn::MetaItem::List(_, ref values) => {
if values.len() != 1 {
usage_err();
}
match values[0] {
syn::MetaItem::NameValue(ref name, ref value)
if name.as_ref() == "treat_none_as_null" => value == "true",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an impl<T: ?Sized> PartialEq<T> for Ident where T: AsRef<str> in syn 0.5.0 so the explicit as_ref() will no longer be necessary.

_ => usage_err(),
}
}
_ => usage_err(),
}
}
16 changes: 16 additions & 0 deletions diesel_codegen_new/src/ast_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use syn::{Ident, Ty, Path, PathSegment};

pub fn ty_ident(ident: Ident) -> Ty {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it makes sense to add these upstream as constructors? syn::Ty::ident(i), syn::Ty::path(p), syn::Path::ident(i) or something like that. And should the ident ones take Ident or Into<Ident> so they can be called with strings?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense for syn to only provide the AST definitions and parsing, and having a separate crate for the AST builder. I definitely think that abstraction needs to exist. I'm not confident enough in the usage patterns that have emerged to really find the right abstractions yet though.

ty_path(path_ident(ident))
}

pub fn ty_path(path: Path) -> Ty {
Ty::Path(None, path)
}

pub fn path_ident(ident: Ident) -> Path {
Path {
global: false,
segments: vec![PathSegment::ident(ident)],
}
}
59 changes: 59 additions & 0 deletions diesel_codegen_new/src/attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use quote;
use syn;

use util::{ident_value_of_attr_with_name, is_option_ty};

pub struct Attr {
pub column_name: Option<syn::Ident>,
pub field_name: Option<syn::Ident>,
pub ty: syn::Ty,
}

impl Attr {
pub fn from_struct_field(field: &syn::Field) -> Self {
let field_name = field.ident.clone();
let column_name = ident_value_of_attr_with_name(&field.attrs, "column_name")
.map(Clone::clone)
.or_else(|| field_name.clone());
let ty = field.ty.clone();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, there's a lot of cloning going on in these lines. I don't have any concrete advice to reduce that right now, just seems like a bad smell.


Attr {
column_name: column_name,
field_name: field_name,
ty: ty,
}
}

fn field_kind(&self) -> &str {
if is_option_ty(&self.ty) {
"option"
} else if self.column_name.is_none() && self.field_name.is_none() {
"bare"
} else {
"regular"
}
}
}

impl quote::ToTokens for Attr {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memo to self: This impl is used by quote! which calls Tokens::append_separated which calls ToTokens::to_tokens

fn to_tokens(&self, tokens: &mut quote::Tokens) {
tokens.append("{");
if let Some(ref name) = self.field_name {
tokens.append("field_name: ");
name.to_tokens(tokens);
tokens.append(", ");
}
if let Some(ref name) = self.column_name {
tokens.append("column_name: ");
name.to_tokens(tokens);
tokens.append(", ");
}
tokens.append("field_ty: ");
self.ty.to_tokens(tokens);
tokens.append(", ");
tokens.append("field_kind: ");
tokens.append(self.field_kind());
tokens.append(", ");
tokens.append("}");
}
}
22 changes: 22 additions & 0 deletions diesel_codegen_new/src/identifiable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use quote::Tokens;
use syn;

use model::Model;

pub fn derive_identifiable(item: syn::Item) -> Tokens {
let model = t!(Model::from_item(&item, "Queryable"));
let table_name = model.table_name();
let struct_ty = &model.ty;
let fields = model.attrs;
if !fields.iter().any(|f| f.field_name == Some(syn::Ident::new("id"))) {
panic!("Could not find a field named `id` on `{}`", &model.name);
}

quote!(Identifiable! {
(
table_name = #table_name,
struct_ty = #struct_ty,
),
fields = [#(fields)*],
})
}
33 changes: 33 additions & 0 deletions diesel_codegen_new/src/insertable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use syn;
use quote;

use model::Model;

pub fn derive_insertable(item: syn::Item) -> quote::Tokens {
let model = t!(Model::from_item(&item, "Insertable"));

if !model.has_table_name_annotation() {
panic!(r#"`#[derive(Insertable)]` requires the struct to be annotated \
with `#[table_name="something"]`"#);
}

if !model.generics.ty_params.is_empty() {
panic!("`#[derive(Insertable)]` does not support generic types");
}

let struct_name = &model.name;
let struct_ty = &model.ty;
let table_name = &model.table_name();
let lifetimes = model.generics.lifetimes;
let fields = model.attrs;

quote!(Insertable! {
(
struct_name = #struct_name,
table_name = #table_name,
struct_ty = #struct_ty,
lifetimes = (#(lifetimes),*),
),
fields = [#(fields)*],
})
}
Loading