diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e9a21d49af..efde159e3108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/diesel/Cargo.toml b/diesel/Cargo.toml index b1a948614f48..21ccd588f89c 100644 --- a/diesel/Cargo.toml +++ b/diesel/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "diesel" -version = "0.7.0" +version = "0.7.1" authors = ["Sean Griffin "] license = "MIT OR Apache-2.0" description = "A safe, extensible ORM and Query builder" diff --git a/diesel/src/macros/as_changeset.rs b/diesel/src/macros/as_changeset.rs index 0c4d31ea5cba..09c08d08bebf 100644 --- a/diesel/src/macros/as_changeset.rs +++ b/diesel/src/macros/as_changeset.rs @@ -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, diff --git a/diesel/src/macros/queryable.rs b/diesel/src/macros/queryable.rs index 649a9e637da6..d5f9a131cdd4 100644 --- a/diesel/src/macros/queryable.rs +++ b/diesel/src/macros/queryable.rs @@ -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>, { @@ -131,6 +132,7 @@ macro_rules! Queryable { struct_name = $struct_name, struct_ty = $struct_name<$($generics),*>, generics = ($($generics),*), + lifetimes = (), ), callback = Queryable, body = $body, @@ -147,6 +149,7 @@ macro_rules! Queryable { struct_name = $struct_name, struct_ty = $struct_name, generics = (), + lifetimes = (), ), callback = Queryable, body = $body, diff --git a/diesel/src/persistable.rs b/diesel/src/persistable.rs index 23b29a0ea2c9..4125c918010f 100644 --- a/diesel/src/persistable.rs +++ b/diesel/src/persistable.rs @@ -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` 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` for inserting more than one record. pub trait Insertable { type Values: InsertValues; diff --git a/diesel_cli/Cargo.lock b/diesel_cli/Cargo.lock index dc0daf77a311..53b9261364ea 100644 --- a/diesel_cli/Cargo.lock +++ b/diesel_cli/Cargo.lock @@ -259,32 +259,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9" -"checksum ansi_term 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "30275ad0ad84ec1c06dde3b3f7d23c6006b7d76d61a85e7060b426b747eff70d" -"checksum bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "32866f4d103c4e438b1db1158aa1b1a80ee078e5d77a59a2f906fd62a577389c" +"checksum ansi_term 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c877397e09fec7a240af5fa74ad0124054b8066149d6544cd1ace93f8de3be68" +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "29b2aa490a8f546381308d68fc79e6bd753cd3ad839f7a7172897f1feedfa175" "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" -"checksum clap 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a430ed980b54e5e6f5249d67abd1e9251e95a5491fafb33639e71c9608d23361" +"checksum clap 2.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62db8e9e3ab6792670f99338be3dbc416f8728ba5d874c33e27aa9933e534512" "checksum diesel 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "57577c0fd5ad12d03ce0f4496299e41af842e8795caf5f260a20505d3554411a" "checksum dotenv 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eea1395d2df3b5344dc577809296d9578303296e8d105c408aa80ed67d598ef1" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "23e3757828fa702a20072c37ff47938e9dd331b92fac6e223d26d4b7a55f7ee2" "checksum libsqlite3-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c8ae599fc97c180c41389b91ce9ca01813d8f65a89f8cdb8d76f19f4bee59b" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" -"checksum num 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "d2ee34a0338c16ae67afb55824aaf8852700eb0f77ccd977807ccb7606b295f6" +"checksum num 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9699207fab8b02bd0e56f8f06fee3f26d640303130de548898b4c9704f6d01" "checksum num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "fb24d9bfb3f222010df27995441ded1e954f8f69cd35021f6bef02ca9552fb92" "checksum num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "287a1c9969a847055e1122ec0ea7a5c5d6f72aad97934e131c83d5c08ab4e45c" -"checksum num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "95e58eac34596aac30ab134c8a8da9aa2dc99caa4b4b4838e6fc6e298016278f" +"checksum num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "8359ea48994f253fa958b5b90b013728b06f54872e5a58bce39540fcdd0f2527" "checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa" "checksum pq-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d50c9cdaa6f0e09c7ddc29cfcf573ba18e00bb29bbfd7c2d49e4196d9851436b" "checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" "checksum regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)" = "56b7ee9f764ecf412c6e2fff779bca4b22980517ae335a21aeaf4e32625a5df2" "checksum regex-syntax 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "31040aad7470ad9d8c46302dcffba337bb4289ca5da2e3cd6e37b64109a85199" -"checksum strsim 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0d5f575d5ced6634a5c4cb842163dab907dc7e9148b28dc482d81b8855cbe985" +"checksum strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "50c069df92e4b01425a8bf3576d5d417943a6a7272fbabaf5bd80b1aaa76442e" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" +"checksum term_size 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d470ef1b870a5c71e691676ff34397b175820fd35e30550e5244f35079be02bf" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55dd963dbaeadc08aa7266bf7f91c3154a7805e32bb94b820b769d2ef3b4744d" "checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" +"checksum unicode-segmentation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b905d0fc2a1f0befd86b0e72e31d1787944efef9d38b9358a9e92a69757f7e3b" +"checksum unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6722facc10989f63ee0e20a83cd4e1714a9ae11529403ac7e0afd069abc39e" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" -"checksum vec_map 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1805118a0d7c72d427e9d9c8134b549ec9e88942c707b100deef357ec0948f02" +"checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/diesel_codegen/README.md b/diesel_codegen/README.md index 10d3ccdb54cb..d1d9e7a77b58 100644 --- a/diesel_codegen/README.md +++ b/diesel_codegen/README.md @@ -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. diff --git a/diesel_codegen/src/lib.rs b/diesel_codegen/src/lib.rs index 3f9e84ff6985..1169ec5b64c0 100644 --- a/diesel_codegen/src/lib.rs +++ b/diesel_codegen/src/lib.rs @@ -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); diff --git a/diesel_codegen_new/Cargo.toml b/diesel_codegen_new/Cargo.toml new file mode 100644 index 000000000000..eddb6615ac6c --- /dev/null +++ b/diesel_codegen_new/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "diesel_codegen_new" +version = "0.8.0" +authors = ["Sean Griffin "] + +[dependencies] +syn = "0.8.5" +quote = "0.2.1" + +[lib] +rustc-macro = true diff --git a/diesel_codegen_new/src/as_changeset.rs b/diesel_codegen_new/src/as_changeset.rs new file mode 100644 index 000000000000..16b96acd3c95 --- /dev/null +++ b/diesel_codegen_new/src/as_changeset.rs @@ -0,0 +1,57 @@ +use syn; +use quote; + +use model::Model; +use util::attr_with_name; + +pub fn derive_as_changeset(item: syn::MacroInput) -> 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::>(); + + if lifetimes.is_empty() { + lifetimes.push(syn::LifetimeDef::new("'a")); + } + + 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 { + syn::MetaItem::List(_, ref values) => { + if values.len() != 1 { + usage_err(); + } + match values[0] { + syn::MetaItem::NameValue(ref name, syn::Lit::Str(ref value, _)) + if name == "treat_none_as_null" => value == "true", + _ => usage_err(), + } + } + _ => usage_err(), + } +} diff --git a/diesel_codegen_new/src/ast_builder.rs b/diesel_codegen_new/src/ast_builder.rs new file mode 100644 index 000000000000..ae4a4e013356 --- /dev/null +++ b/diesel_codegen_new/src/ast_builder.rs @@ -0,0 +1,16 @@ +use syn::{Ident, Ty, Path, PathSegment}; + +pub fn ty_ident(ident: Ident) -> Ty { + 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)], + } +} diff --git a/diesel_codegen_new/src/attr.rs b/diesel_codegen_new/src/attr.rs new file mode 100644 index 000000000000..9579c37b5d05 --- /dev/null +++ b/diesel_codegen_new/src/attr.rs @@ -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, + pub field_name: Option, + 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(); + + 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 { + 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("}"); + } +} diff --git a/diesel_codegen_new/src/identifiable.rs b/diesel_codegen_new/src/identifiable.rs new file mode 100644 index 000000000000..eff55d04db48 --- /dev/null +++ b/diesel_codegen_new/src/identifiable.rs @@ -0,0 +1,22 @@ +use quote::Tokens; +use syn; + +use model::Model; + +pub fn derive_identifiable(item: syn::MacroInput) -> 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)*], + }) +} diff --git a/diesel_codegen_new/src/insertable.rs b/diesel_codegen_new/src/insertable.rs new file mode 100644 index 000000000000..d3abe45a30a2 --- /dev/null +++ b/diesel_codegen_new/src/insertable.rs @@ -0,0 +1,33 @@ +use syn; +use quote; + +use model::Model; + +pub fn derive_insertable(item: syn::MacroInput) -> 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)*], + }) +} diff --git a/diesel_codegen_new/src/lib.rs b/diesel_codegen_new/src/lib.rs new file mode 100644 index 000000000000..cde98d27997c --- /dev/null +++ b/diesel_codegen_new/src/lib.rs @@ -0,0 +1,85 @@ +#![feature(rustc_macro, rustc_macro_lib)] + +macro_rules! t { + ($expr:expr) => { + match $expr { + Ok(val) => val, + Err(e) => panic!("{}", e), + } + }; +} + +#[macro_use] +extern crate quote; +extern crate rustc_macro; +extern crate syn; + +mod as_changeset; +mod ast_builder; +mod attr; +mod identifiable; +mod insertable; +mod model; +mod queryable; +mod util; + +use rustc_macro::TokenStream; +use syn::parse_macro_input; + +use self::util::{list_value_of_attr_with_name, strip_attributes, strip_field_attributes}; + +const KNOWN_CUSTOM_DERIVES: &'static [&'static str] = &[ + "AsChangeset", + "Identifiable", + "Insertable", + "Queryable", +]; + +const KNOWN_CUSTOM_ATTRIBUTES: &'static [&'static str] = &[ + "table_name", + "changeset_options" +]; + +const KNOWN_FIELD_ATTRIBUTES: &'static [&'static str] = &[ + "column_name", +]; + +#[rustc_macro_derive(Queryable)] +pub fn derive_queryable(input: TokenStream) -> TokenStream { + expand_derive(input, queryable::derive_queryable) +} + +#[rustc_macro_derive(Identifiable)] +pub fn derive_identifiable(input: TokenStream) -> TokenStream { + expand_derive(input, identifiable::derive_identifiable) +} + +#[rustc_macro_derive(Insertable)] +pub fn derive_insertable(input: TokenStream) -> TokenStream { + expand_derive(input, insertable::derive_insertable) +} + +#[rustc_macro_derive(AsChangeset)] +pub fn derive_as_changeset(input: TokenStream) -> TokenStream { + expand_derive(input, as_changeset::derive_as_changeset) +} + +fn expand_derive(input: TokenStream, f: fn(syn::MacroInput) -> quote::Tokens) -> TokenStream { + let mut item = parse_macro_input(&input.to_string()).unwrap(); + let output = f(item.clone()); + + let finished_deriving_diesel_traits = { + let remaining_derives = list_value_of_attr_with_name(&item.attrs, "derive"); + !remaining_derives + .unwrap_or(Vec::new()) + .iter() + .any(|trait_name| KNOWN_CUSTOM_DERIVES.contains(&trait_name.as_ref())) + }; + + if finished_deriving_diesel_traits { + item.attrs = strip_attributes(item.attrs, KNOWN_CUSTOM_ATTRIBUTES); + strip_field_attributes(&mut item, KNOWN_FIELD_ATTRIBUTES); + } + + quote!(#item #output).to_string().parse().unwrap() +} diff --git a/diesel_codegen_new/src/model.rs b/diesel_codegen_new/src/model.rs new file mode 100644 index 000000000000..8b8ac0dc1fdd --- /dev/null +++ b/diesel_codegen_new/src/model.rs @@ -0,0 +1,80 @@ +use syn; + +use attr::Attr; +use util::{struct_ty, str_value_of_attr_with_name}; + +pub struct Model { + pub ty: syn::Ty, + pub attrs: Vec, + pub name: syn::Ident, + pub generics: syn::Generics, + table_name_from_annotation: Option, +} + +impl Model { + pub fn from_item(item: &syn::MacroInput, derived_from: &str) -> Result { + let fields = match item.body { + syn::Body::Enum(..) => return Err(format!( + "#[derive({})] cannot be used with enums", derived_from)), + syn::Body::Struct(ref fields) => fields.fields(), + }; + let attrs = fields.into_iter().map(Attr::from_struct_field).collect(); + let ty = struct_ty(item.ident.clone(), &item.generics); + let name = item.ident.clone(); + let generics = item.generics.clone(); + let table_name_from_annotation = str_value_of_attr_with_name( + &item.attrs, "table_name").map(syn::Ident::new); + + Ok(Model { + ty: ty, + attrs: attrs, + name: name, + generics: generics, + table_name_from_annotation: table_name_from_annotation, + }) + } + + pub fn table_name(&self) -> syn::Ident { + self.table_name_from_annotation.clone().unwrap_or_else(|| { + syn::Ident::new(infer_table_name(self.name.as_ref())) + }) + } + + pub fn has_table_name_annotation(&self) -> bool { + self.table_name_from_annotation.is_some() + } +} + +pub fn infer_association_name(name: &str) -> String { + let mut result = String::with_capacity(name.len()); + result.push_str(&name[..1].to_lowercase()); + for character in name[1..].chars() { + if character.is_uppercase() { + result.push('_'); + for lowercase in character.to_lowercase() { + result.push(lowercase); + } + } else { + result.push(character); + } + } + result +} + +fn infer_table_name(name: &str) -> String { + let mut result = infer_association_name(name); + result.push('s'); + result +} + +#[test] +fn infer_table_name_pluralizes_and_downcases() { + assert_eq!("foos", &infer_table_name("Foo")); + assert_eq!("bars", &infer_table_name("Bar")); +} + +#[test] +fn infer_table_name_properly_handles_underscores() { + assert_eq!("foo_bars", &infer_table_name("FooBar")); + assert_eq!("foo_bar_bazs", &infer_table_name("FooBarBaz")); +} diff --git a/diesel_codegen_new/src/queryable.rs b/diesel_codegen_new/src/queryable.rs new file mode 100644 index 000000000000..6975bc796f12 --- /dev/null +++ b/diesel_codegen_new/src/queryable.rs @@ -0,0 +1,24 @@ +use quote::Tokens; +use syn; + +use model::Model; + +pub fn derive_queryable(item: syn::MacroInput) -> Tokens { + let model = t!(Model::from_item(&item, "Queryable")); + + let struct_ty = &model.ty; + let struct_name = &model.name; + let ty_params = &model.generics.ty_params; + let attrs = model.attrs; + let lifetimes = &model.generics.lifetimes; + + quote!(Queryable! { + ( + struct_name = #struct_name, + struct_ty = #struct_ty, + generics = (#(ty_params),*), + lifetimes = (#(lifetimes),*), + ), + fields = [#(attrs)*], + }) +} diff --git a/diesel_codegen_new/src/util.rs b/diesel_codegen_new/src/util.rs new file mode 100644 index 000000000000..71fcaa60bf3f --- /dev/null +++ b/diesel_codegen_new/src/util.rs @@ -0,0 +1,122 @@ +use std::mem; +use syn::*; + +use ast_builder::ty_ident; + +pub fn struct_ty(name: Ident, generics: &Generics) -> Ty { + let lifetimes = generics.lifetimes.iter().map(|lt| lt.lifetime.clone()).collect(); + let ty_params = generics.ty_params.iter() + .map(|param| ty_ident(param.ident.clone())) + .collect(); + let parameter_data = AngleBracketedParameterData { + lifetimes: lifetimes, + types: ty_params, + bindings: Vec::new(), + }; + let parameters = PathParameters::AngleBracketed(parameter_data); + Ty::Path(None, Path { + global: false, + segments: vec![ + PathSegment { + ident: name, + parameters: parameters, + }, + ], + }) +} + +pub fn str_value_of_attr_with_name<'a>( + attrs: &'a [Attribute], + name: &str, +) -> Option<&'a str> { + attr_with_name(attrs, name).map(|attr| str_value_of_attr(attr, name)) +} + +pub fn ident_value_of_attr_with_name<'a>( + attrs: &'a [Attribute], + name: &str, +) -> Option<&'a Ident> { + attr_with_name(attrs, name).map(|attr| single_arg_value_of_attr(attr, name)) +} + +pub fn list_value_of_attr_with_name<'a>( + attrs: &'a [Attribute], + name: &str, +) -> Option> { + attr_with_name(attrs, name).map(|attr| list_value_of_attr(attr, name)) +} + +pub fn attr_with_name<'a>( + attrs: &'a [Attribute], + name: &str, +) -> Option<&'a Attribute> { + attrs.into_iter().find(|attr| attr.name() == name) +} + +fn str_value_of_attr<'a>(attr: &'a Attribute, name: &str) -> &'a str { + match attr.value { + MetaItem::NameValue(_, Lit::Str(ref value, _)) => &*value, + _ => panic!(r#"`{}` must be in the form `#[{}="something"]`"#, name, name), + } +} + +fn single_arg_value_of_attr<'a>(attr: &'a Attribute, name: &str) -> &'a Ident { + let usage_err = || panic!(r#"`{}` must be in the form `#[{}(something)]`"#, name, name); + match attr.value { + MetaItem::List(_, ref items) => { + if items.len() != 1 { + return usage_err(); + } + match items[0] { + MetaItem::Word(ref name) => name, + _ => usage_err(), + } + } + _ => usage_err(), + } +} + +fn list_value_of_attr<'a>(attr: &'a Attribute, name: &str) -> Vec<&'a Ident> { + match attr.value { + MetaItem::List(_, ref items) => { + items.iter().map(|item| match *item { + MetaItem::Word(ref name) => name, + _ => panic!("`{}` must be in the form `#[{}(something, something_else)]`", name, name), + }).collect() + } + _ => panic!("`{}` must be in the form `#[{}(something, something_else)]`", name, name), + } +} + +pub fn is_option_ty(ty: &Ty) -> bool { + let option_ident = Ident::new("Option"); + match *ty { + Ty::Path(_, ref path) => { + path.segments.first() + .map(|s| s.ident == option_ident) + .unwrap_or(false) + } + _ => false, + } +} + +pub fn strip_attributes(attrs: Vec, names_to_strip: &[&str]) -> Vec { + attrs.into_iter().filter(|attr| { + !names_to_strip.contains(&attr.name()) + }).collect() +} + +pub fn strip_field_attributes(item: &mut MacroInput, names_to_strip: &[&str]) { + let fields = match item.body { + Body::Struct(VariantData::Struct(ref mut fields)) | + Body::Struct(VariantData::Tuple(ref mut fields)) => fields, + _ => return, + }; + + let mut attrs = Vec::new(); + for field in fields { + mem::swap(&mut attrs, &mut field.attrs); + attrs = strip_attributes(attrs, names_to_strip); + mem::swap(&mut attrs, &mut field.attrs); + } +} diff --git a/diesel_codegen_syntex/src/associations/mod.rs b/diesel_codegen_syntex/src/associations/mod.rs index 4b56e9f9a816..b398888d0821 100644 --- a/diesel_codegen_syntex/src/associations/mod.rs +++ b/diesel_codegen_syntex/src/associations/mod.rs @@ -1,5 +1,5 @@ use syntax::ast::{self, MetaItem, MetaItemKind}; -use syntax::attr::AttrMetaMethods; +use syntax::attr::HasAttrs; use syntax::codemap::Span; use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::parse::token::str_to_ident; @@ -71,8 +71,8 @@ fn build_association_options( }; match meta_item.node { MetaItemKind::List(_, ref options) => { - let association_name = match options[0].node { - MetaItemKind::Word(ref name) => str_to_ident(&name), + let association_name = match options[0].word() { + Some(word) => str_to_ident(&word.name()), _ => return usage_err(), }; let foreign_key_name = options.iter().find(|a| a.check_name("foreign_key")) diff --git a/diesel_codegen_syntex/src/insertable.rs b/diesel_codegen_syntex/src/insertable.rs index ace56d80b2be..666c8506758e 100644 --- a/diesel_codegen_syntex/src/insertable.rs +++ b/diesel_codegen_syntex/src/insertable.rs @@ -1,84 +1,42 @@ -use syntax::ast::{ - self, - Item, - MetaItem, - MetaItemKind, -}; +use syntax::ast::{self, MetaItem}; use syntax::codemap::Span; use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ptr::P; -use syntax::parse::token::{InternedString, str_to_ident}; -use attr::Attr; +use model::Model; use util::{lifetime_list_tokens, struct_ty}; -pub fn expand_insert( +pub fn expand_derive_insertable( cx: &mut ExtCtxt, span: Span, - meta_item: &MetaItem, + _meta_item: &MetaItem, annotatable: &Annotatable, push: &mut FnMut(Annotatable) ) { - if let Annotatable::Item(ref item) = *annotatable { - let tables = insertable_tables(cx, meta_item); - for body in tables.into_iter().filter_map(|t| insertable_impl(cx, span, t, item)) { - push(Annotatable::Item(body)); - } - } else { - cx.span_err(meta_item.span, - "`insertable_into` may only be applied to enums and structs"); - }; -} - -fn insertable_tables(cx: &mut ExtCtxt, meta_item: &MetaItem) -> Vec { - match meta_item.node { - MetaItemKind::List(_, ref meta_items) => { - meta_items.iter().map(|i| table_name(cx, i)).collect() - } - _ => usage_error(cx, meta_item), - } -} - -fn table_name(cx: &mut ExtCtxt, meta_item: &MetaItem) -> InternedString { - match meta_item.node { - MetaItemKind::Word(ref word) => word.clone(), - _ => usage_error(cx, meta_item), + if let Some(model) = Model::from_annotable(cx, span, annotatable) { + insertable_impl(cx, span, model.table_name(), &model) + .map(Annotatable::Item) + .map(push); } } -fn usage_error(cx: &mut ExtCtxt, meta_item: &MetaItem) -> ! { - cx.span_err(meta_item.span, - "`insertable_into` must be used in the form `#[insertable_into(table1, table2)]`"); - panic!() -} - #[allow(unused_imports)] fn insertable_impl( cx: &mut ExtCtxt, span: Span, - table: InternedString, - item: &Item, + table_name: ast::Ident, + model: &Model, ) -> Option> { - let (generics, fields) = match Attr::from_item(cx, item) { - Some(vals) => vals, - None => { - cx.span_err(item.span, - "Expected a struct or tuple struct for `#[insertable_into]`"); - return None; - } - }; - - if !generics.ty_params.is_empty() { - cx.span_err(item.span, "#[insertable_into] does not support generic types"); + if !model.generics.ty_params.is_empty() { + cx.span_err(span, "#[derive(Insertable)] does not support generic types"); return None; } - let struct_name = item.ident; - let ty = struct_ty(cx, span, item.ident, &generics); - let table_name = str_to_ident(&table); + let struct_name = model.name; + let ty = struct_ty(cx, span, struct_name, &model.generics); - let lifetimes = lifetime_list_tokens(&generics.lifetimes, span); - let fields = fields.iter().map(|a| a.to_stable_macro_tokens(cx)).collect::>(); + let lifetimes = lifetime_list_tokens(&model.generics.lifetimes, span); + let fields = model.attrs.iter().map(|a| a.to_stable_macro_tokens(cx)).collect::>(); quote_item!(cx, Insertable! { ( diff --git a/diesel_codegen_syntex/src/lib.rs b/diesel_codegen_syntex/src/lib.rs index a758fd7388ab..5d39d5f10489 100644 --- a/diesel_codegen_syntex/src/lib.rs +++ b/diesel_codegen_syntex/src/lib.rs @@ -33,8 +33,8 @@ pub fn register(reg: &mut syntex::Registry) { reg.add_decorator("derive_AsChangeset", update::expand_derive_as_changeset); reg.add_decorator("derive_Associations", associations::expand_derive_associations); reg.add_decorator("derive_Identifiable", identifiable::expand_derive_identifiable); + reg.add_decorator("derive_Insertable", insertable::expand_derive_insertable); reg.add_decorator("derive_Queryable", queryable::expand_derive_queryable); - reg.add_decorator("insertable_into", insertable::expand_insert); reg.add_macro("embed_migrations", migrations::expand_embed_migrations); reg.add_macro("infer_table_from_schema", schema_inference::expand_load_table); reg.add_macro("infer_schema", schema_inference::expand_infer_schema); diff --git a/diesel_codegen_syntex/src/update.rs b/diesel_codegen_syntex/src/update.rs index cdf775fe213f..94917122d50d 100644 --- a/diesel_codegen_syntex/src/update.rs +++ b/diesel_codegen_syntex/src/update.rs @@ -1,5 +1,5 @@ -use syntax::ast::{self, MetaItem, MetaItemKind}; -use syntax::attr::AttrMetaMethods; +use syntax::ast::{self, MetaItem, NestedMetaItem, MetaItemKind}; +use syntax::attr::HasAttrs; use syntax::codemap::Span; use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ptr::P; @@ -52,7 +52,7 @@ fn extract_treat_none_as_null(cx: &mut ExtCtxt, attr: &ast::Attribute) -> Result match attr.node.value.node { MetaItemKind::List(_, ref items) if items.len() == 1 => { if items[0].check_name("treat_none_as_null") { - boolean_option(cx, &*items[0]) + boolean_option(cx, &items[0]) } else { options_usage_error(cx, attr.span) } @@ -61,11 +61,11 @@ fn extract_treat_none_as_null(cx: &mut ExtCtxt, attr: &ast::Attribute) -> Result } } -fn boolean_option(cx: &mut ExtCtxt, item: &MetaItem) -> Result { +fn boolean_option(cx: &mut ExtCtxt, item: &NestedMetaItem) -> Result { match item.value_str() { Some(ref s) if *s == "true" => Ok(true), Some(ref s) if *s == "false" => Ok(false), - _ => options_usage_error(cx, item.span) + _ => options_usage_error(cx, item.span()) } } diff --git a/diesel_codegen_syntex/src/util.rs b/diesel_codegen_syntex/src/util.rs index 900a19cac91e..82089c928c30 100644 --- a/diesel_codegen_syntex/src/util.rs +++ b/diesel_codegen_syntex/src/util.rs @@ -1,6 +1,5 @@ use syntax::ast::TyKind; use syntax::ast; -use syntax::attr::AttrMetaMethods; use syntax::codemap::Span; use syntax::ext::base::ExtCtxt; use syntax::ext::build::AstBuilder; @@ -38,8 +37,8 @@ fn single_arg_value_of_attr( if items.len() != 1 { return usage_err(); } - match items[0].node { - ast::MetaItemKind::Word(ref value) => Some(str_to_ident(&value)), + match items[0].word() { + Some(word) => Some(str_to_ident(&word.name())), _ => usage_err(), } } diff --git a/diesel_compile_tests/tests/compile-fail/custom_returning_requires_nonaggregate.rs b/diesel_compile_tests/tests/compile-fail/custom_returning_requires_nonaggregate.rs index 7a3c880eb347..4d9720ee1eda 100644 --- a/diesel_compile_tests/tests/compile-fail/custom_returning_requires_nonaggregate.rs +++ b/diesel_compile_tests/tests/compile-fail/custom_returning_requires_nonaggregate.rs @@ -13,7 +13,8 @@ table! { } } -#[insertable_into(users)] +#[derive(Insertable)] +#[table_name="users"] pub struct NewUser { name: String, } diff --git a/diesel_compile_tests/tests/compile-fail/custom_returning_requires_selectable_expression.rs b/diesel_compile_tests/tests/compile-fail/custom_returning_requires_selectable_expression.rs index b81c265622e6..8612f523d0d4 100644 --- a/diesel_compile_tests/tests/compile-fail/custom_returning_requires_selectable_expression.rs +++ b/diesel_compile_tests/tests/compile-fail/custom_returning_requires_selectable_expression.rs @@ -19,7 +19,8 @@ table! { } } -#[insertable_into(users)] +#[derive(Insertable)] +#[table_name="users"] pub struct NewUser { name: String, } diff --git a/diesel_compile_tests/tests/compile-fail/insertable_into_does_not_support_generics.rs b/diesel_compile_tests/tests/compile-fail/insertable_into_does_not_support_generics.rs index 4e0c0d4f523a..7c03242e83d1 100644 --- a/diesel_compile_tests/tests/compile-fail/insertable_into_does_not_support_generics.rs +++ b/diesel_compile_tests/tests/compile-fail/insertable_into_does_not_support_generics.rs @@ -11,7 +11,9 @@ table! { } } -#[insertable_into(users)] -pub struct NewUser { //~ ERROR #[insertable_into] does not support generic types +#[derive(Insertable)] +//~^ ERROR #[derive(Insertable)] does not support generic types +#[table_name="users"] +pub struct NewUser { name: T, } diff --git a/diesel_compile_tests/tests/compile-fail/sqlite_upsert_cannot_be_used_on_pg.rs b/diesel_compile_tests/tests/compile-fail/sqlite_upsert_cannot_be_used_on_pg.rs index a8e2b99b6c51..6bccc287a679 100644 --- a/diesel_compile_tests/tests/compile-fail/sqlite_upsert_cannot_be_used_on_pg.rs +++ b/diesel_compile_tests/tests/compile-fail/sqlite_upsert_cannot_be_used_on_pg.rs @@ -13,7 +13,8 @@ table! { } } -#[insertable_into(users)] +#[derive(Insertable)] +#[table_name="users"] struct User { id: i32, } diff --git a/diesel_tests/Cargo.toml b/diesel_tests/Cargo.toml index c888d1486256..5a6114b8afb6 100644 --- a/diesel_tests/Cargo.toml +++ b/diesel_tests/Cargo.toml @@ -17,13 +17,14 @@ assert_matches = "1.0.1" chrono = { version = "^0.2.17" } diesel = { path = "../diesel", default-features = false, features = ["quickcheck", "chrono", "uuid"] } diesel_codegen = { path = "../diesel_codegen", default-features = false, optional = true } +diesel_codegen_new = { path = "../diesel_codegen_new", optional = true } dotenv_macros = { version = "0.9.0", optional = true } quickcheck = { version = "0.3.1", features = ["unstable"] } uuid = { version = ">=0.2.0, <0.4.0" } [features] default = ["with-syntex"] -unstable = ["diesel/unstable", "dotenv_macros", "diesel_codegen"] +unstable = ["diesel/unstable", "dotenv_macros", "diesel_codegen", "diesel_codegen_new"] with-syntex = ["syntex", "dotenv_codegen", "diesel_codegen_syntex"] postgres = ["diesel/postgres"] sqlite = ["diesel/sqlite"] diff --git a/diesel_tests/tests/annotations.rs b/diesel_tests/tests/annotations.rs index cb8bd9ff2053..fb7b4fbd297f 100644 --- a/diesel_tests/tests/annotations.rs +++ b/diesel_tests/tests/annotations.rs @@ -38,7 +38,8 @@ fn association_where_parent_and_child_have_underscores() { title: String } - #[insertable_into(special_posts)] + #[derive(Insertable)] + #[table_name="special_posts"] struct NewSpecialPost { user_id: i32, title: String @@ -68,7 +69,8 @@ fn association_where_parent_and_child_have_underscores() { } } - #[insertable_into(special_comments)] + #[derive(Insertable)] + #[table_name="special_comments"] struct NewSpecialComment { special_post_id: i32 } @@ -125,7 +127,8 @@ mod multiple_lifetimes_in_insertable_struct_definition { #![allow(dead_code)] use schema::posts; - #[insertable_into(posts)] + #[derive(Insertable)] + #[table_name="posts"] pub struct MyPost<'a> { title: &'a str, body: &'a str, @@ -136,7 +139,8 @@ mod lifetimes_with_names_other_than_a { #![allow(dead_code)] use schema::posts; - #[insertable_into(posts)] + #[derive(Insertable)] + #[table_name="posts"] pub struct MyPost<'a, 'b> { id: i32, title: &'b str, diff --git a/diesel_tests/tests/insert.rs b/diesel_tests/tests/insert.rs index 460995b28dd2..f767147c481d 100644 --- a/diesel_tests/tests/insert.rs +++ b/diesel_tests/tests/insert.rs @@ -132,12 +132,14 @@ fn insert_returning_count_returns_number_of_rows_inserted() { assert_eq!(1, second_count); } -#[insertable_into(users)] +#[derive(Insertable)] +#[table_name="users"] struct BaldUser { name: String, } -#[insertable_into(users)] +#[derive(Insertable)] +#[table_name="users"] struct BorrowedUser<'a> { name: &'a str, } diff --git a/diesel_tests/tests/lib.rs b/diesel_tests/tests/lib.rs index 5aa4e6d6ea06..fc6dee2f327e 100644 --- a/diesel_tests/tests/lib.rs +++ b/diesel_tests/tests/lib.rs @@ -1,9 +1,10 @@ -#![cfg_attr(feature = "unstable", feature(custom_derive, plugin, custom_attribute))] +#![cfg_attr(feature = "unstable", feature(custom_derive, plugin, custom_attribute, rustc_macro))] #![cfg_attr(feature = "unstable", plugin(diesel_codegen, dotenv_macros))] extern crate quickcheck; #[macro_use] extern crate assert_matches; #[macro_use] extern crate diesel; +#[macro_use] extern crate diesel_codegen_new; #[cfg(feature = "unstable")] include!("lib.in.rs"); diff --git a/diesel_tests/tests/schema.rs b/diesel_tests/tests/schema.rs index 3235514e59a6..ca34deaa5682 100644 --- a/diesel_tests/tests/schema.rs +++ b/diesel_tests/tests/schema.rs @@ -2,8 +2,7 @@ use diesel::*; infer_schema!(dotenv!("DATABASE_URL")); -#[derive(PartialEq, Eq, Debug, Clone, Queryable, Identifiable, AsChangeset, Associations)] -#[insertable_into(users)] +#[derive(PartialEq, Eq, Debug, Clone, Queryable, Identifiable, Insertable, AsChangeset, Associations)] #[has_many(posts)] #[table_name = "users"] pub struct User { @@ -65,8 +64,7 @@ select_column_workaround!(comments -> users (id, post_id, text)); join_through!(users -> posts -> comments); -#[derive(Debug, PartialEq, Eq, Queryable, Clone, AsChangeset)] -#[insertable_into(users)] +#[derive(Debug, PartialEq, Eq, Queryable, Clone, Insertable, AsChangeset)] #[table_name = "users"] pub struct NewUser { pub name: String, @@ -82,7 +80,8 @@ impl NewUser { } } -#[insertable_into(posts)] +#[derive(Insertable)] +#[table_name="posts"] pub struct NewPost { user_id: i32, title: String, @@ -99,7 +98,8 @@ impl NewPost { } } -#[insertable_into(comments)] +#[derive(Insertable)] +#[table_name="comments"] pub struct NewComment<'a>( #[column_name(post_id)] pub i32, diff --git a/diesel_tests/tests/schema_inference.rs b/diesel_tests/tests/schema_inference.rs index 1dffbd89b2bb..366d11daa0dd 100644 --- a/diesel_tests/tests/schema_inference.rs +++ b/diesel_tests/tests/schema_inference.rs @@ -3,8 +3,8 @@ mod sqlite { use diesel::*; use schema::*; - #[derive(Queryable, PartialEq, Debug)] - #[insertable_into(infer_all_the_ints)] + #[derive(Queryable, PartialEq, Debug, Insertable)] + #[table_name="infer_all_the_ints"] struct InferredInts { col1: i32, col2: i32, @@ -45,8 +45,8 @@ mod sqlite { assert_eq!(Ok(vec![inferred_ints]), infer_all_the_ints::table.load(&conn)); } - #[derive(Queryable, PartialEq, Debug)] - #[insertable_into(infer_all_the_bools)] + #[derive(Queryable, PartialEq, Debug, Insertable)] + #[table_name="infer_all_the_bools"] struct InferredBools { col1: bool, col2: bool, @@ -69,8 +69,8 @@ mod sqlite { assert_eq!(Ok(vec![inferred_bools]), infer_all_the_bools::table.load(&conn)); } - #[derive(Queryable, PartialEq, Debug)] - #[insertable_into(infer_all_the_strings)] + #[derive(Queryable, PartialEq, Debug, Insertable)] + #[table_name="infer_all_the_strings"] struct InferredStrings { col1: String, col2: String, @@ -105,8 +105,8 @@ mod sqlite { assert_eq!(Ok(vec![inferred_strings]), infer_all_the_strings::table.load(&conn)); } - #[derive(Queryable, PartialEq, Debug)] - #[insertable_into(infer_all_the_floats)] + #[derive(Queryable, PartialEq, Debug, Insertable)] + #[table_name="infer_all_the_floats"] struct InferredFloats { col1: f32, col2: f32, diff --git a/examples/getting_started_step_2/src/models.rs b/examples/getting_started_step_2/src/models.rs index 50797bb45b95..8cec5865b249 100644 --- a/examples/getting_started_step_2/src/models.rs +++ b/examples/getting_started_step_2/src/models.rs @@ -8,7 +8,8 @@ pub struct Post { pub published: bool, } -#[insertable_into(posts)] +#[derive(Insertable)] +#[table_name="posts"] pub struct NewPost<'a> { pub title: &'a str, pub body: &'a str, diff --git a/examples/getting_started_step_3/src/models.rs b/examples/getting_started_step_3/src/models.rs index 50797bb45b95..8cec5865b249 100644 --- a/examples/getting_started_step_3/src/models.rs +++ b/examples/getting_started_step_3/src/models.rs @@ -8,7 +8,8 @@ pub struct Post { pub published: bool, } -#[insertable_into(posts)] +#[derive(Insertable)] +#[table_name="posts"] pub struct NewPost<'a> { pub title: &'a str, pub body: &'a str, diff --git a/examples/getting_started_step_4/src/models.rs b/examples/getting_started_step_4/src/models.rs index 50797bb45b95..8cec5865b249 100644 --- a/examples/getting_started_step_4/src/models.rs +++ b/examples/getting_started_step_4/src/models.rs @@ -8,7 +8,8 @@ pub struct Post { pub published: bool, } -#[insertable_into(posts)] +#[derive(Insertable)] +#[table_name="posts"] pub struct NewPost<'a> { pub title: &'a str, pub body: &'a str,