gcloud_spanner_derive/
lib.rs

1//! # google-cloud-spanner-derive
2//!
3//! Procedural macro for [google-cloud-spanner](../spanner).
4//!
5//! ## Quick Start
6//!
7//! ### Table derive
8//!
9//! `#[derive(Table)]` generates the implementation for following traits.
10//! * `TryFromStruct`
11//! * `ToStruct`
12//! * `TryFrom<Row>`
13//!
14//! ```
15//! use time::OffsetDateTime;
16//! use google_cloud_spanner::client::{Client,Error};
17//! use google_cloud_spanner::mutation::insert_struct;
18//! use google_cloud_spanner::statement::Statement;
19//! use google_cloud_spanner_derive::Table;
20//!
21//! #[derive(Table)]
22//! pub struct UserCharacter {
23//!     pub user_id: String,
24//!     pub character_id: i64,
25//!     // #[spanner(name=...) is used when the column name does not appear in camel case of the field name
26//!     #[spanner(name="LevelX")]
27//!     pub level: i64,
28//!     #[spanner(commitTimestamp)]
29//!     pub updated_at: OffsetDateTime,
30//! }
31//!
32//! impl Default for UserCharacter {
33//!     fn default() -> Self {
34//!         Self {
35//!             user_id: Default::default(),
36//!             character_id: Default::default(),
37//!             level: Default::default(),
38//!             updated_at: OffsetDateTime::UNIX_EPOCH,
39//!         }
40//!     }
41//! }
42//!
43//! async fn run(client: &Client) -> Result<Vec<UserCharacter>, Error> {
44//!     let user = UserCharacter {
45//!         user_id: "user_id".to_string(),
46//!         ..Default::default()
47//!     };
48//!     client.apply(vec![insert_struct("UserCharacter", user)]).await?;
49//!
50//!     let mut tx = client.read_only_transaction().await?;
51//!     let stmt = Statement::new("SELECT * From UserCharacter Limit 10");
52//!     let mut reader = tx.query(stmt).await?;
53//!     let mut result = vec![];
54//!     while let Some(row) = reader.next().await? {
55//!         result.push(row.try_into()?);
56//!     }
57//!     Ok(result)
58//! }
59//!```
60//!
61//! Here is the generated implementation.
62//!```
63//! use time::OffsetDateTime;
64//! use google_cloud_spanner::statement::{ToStruct, ToKind, Kinds, Types};
65//! use google_cloud_spanner::row::{Struct, TryFromValue, TryFromStruct, Row, Error as RowError};
66//! use google_cloud_spanner::value::CommitTimestamp;
67//! use std::convert::TryFrom;
68//!
69//! pub struct UserCharacter {
70//!     pub user_id: String,
71//!     pub character_id: i64,
72//!     pub level: i64,
73//!     pub updated_at: OffsetDateTime,
74//! }
75//!
76//! impl ToStruct for UserCharacter {
77//!     fn to_kinds(&self) -> Kinds {
78//!         vec![
79//!             ("UserId", self.user_id.to_kind()),
80//!             ("CharacterId", self.character_id.to_kind()),
81//!             ("LevelX", self.level.to_kind()),
82//!             ("UpdatedAt", self.updated_at.to_kind()),
83//!         ]
84//!     }
85//!
86//!    fn get_types() -> Types {
87//!        vec![
88//!           ("UserId", String::get_type()),
89//!            ("CharacterId", i64::get_type()),
90//!            ("LevelX", i64::get_type()),
91//!            ("UpdatedAt", CommitTimestamp::get_type()),
92//!        ]
93//!    }
94//! }
95//!
96//! impl TryFromStruct for UserCharacter {
97//!    fn try_from_struct(s: Struct<'_>) -> Result<Self, RowError> {
98//!        Ok(UserCharacter {
99//!            user_id: s.column_by_name("UserId")?,
100//!            character_id: s.column_by_name("CharacterId")?,
101//!            level: s.column_by_name("LevelX")?,
102//!            updated_at: s.column_by_name("UpdatedAt")?,
103//!        })
104//!    }
105//! }
106//!
107//! impl TryFrom<Row> for UserCharacter {
108//!    type Error = RowError;
109//!    fn try_from(s: Row) -> Result<Self, RowError> {
110//!        Ok(UserCharacter {
111//!            user_id: s.column_by_name("UserId")?,
112//!            character_id: s.column_by_name("CharacterId")?,
113//!            level: s.column_by_name("LevelX")?,
114//!            updated_at: s.column_by_name("UpdatedAt")?,
115//!        })
116//!    }
117//! }
118//!```
119//!
120//! ### Query derive
121//!
122//! `#[derive(Query)]` generates the implementation for following traits.
123//! * `TryFrom<Row>`
124//!
125//!```
126//! use google_cloud_spanner::transaction::Transaction;
127//! use google_cloud_spanner::client::{Client, Error};
128//! use google_cloud_spanner::statement::Statement;
129//! use google_cloud_spanner_derive::{Table, Query};
130//!
131//! #[derive(Table, Default)]
132//! pub struct UserCharacter {
133//!     pub user_id: String,
134//!     pub character_id: i64,
135//! }
136//!
137//! #[derive(Table, Default)]
138//! pub struct UserItem {
139//!     pub user_id: String,
140//!     pub item_id: i64,
141//! }
142//!
143//! #[derive(Query, Default)]
144//! pub struct UserBundle {
145//!    pub user_id: String,
146//!    pub user_characters: Vec<UserCharacter>,
147//!    #[spanner(name="Items")]
148//!    pub user_items: Vec<UserItem>
149//! }
150//!
151//! async fn run(user_id: &str, tx: &mut Transaction) -> Result<Option<UserBundle>, Error> {
152//!    let mut stmt = Statement::new("
153//!        SELECT
154//!            UserId,
155//!            ARRAY(SELECT AS STRUCT * FROM UserCharacter WHERE UserId = @UserId) AS UserCharacters,
156//!            ARRAY(SELECT AS STRUCT * FROM UserItem WHERE UserId = @UserId) AS Items,
157//!        From User WHERE UserID = @UserID",
158//!    );
159//!    stmt.add_param("UserID", &user_id);
160//!    let mut reader = tx.query(stmt).await?;
161//!    match reader.next().await? {
162//!        Some(row) => Ok(Some(row.try_into()?)),
163//!        None => Ok(None)
164//!    }
165//! }
166//! ```
167
168use proc_macro::TokenStream;
169
170use quote::{quote, ToTokens};
171use syn::{parse_macro_input, ItemStruct};
172
173mod column;
174mod query;
175mod symbol;
176mod table;
177
178#[proc_macro_derive(Table, attributes(spanner))]
179pub fn table(input: TokenStream) -> TokenStream {
180    let item = parse_macro_input!(input as ItemStruct);
181    let table = table::generate_table_methods(item.clone());
182    let query = query::generate_query_methods(item);
183    wrap_in_dummy_mod(quote! {
184        #table
185        #query
186    })
187}
188
189#[proc_macro_derive(Query, attributes(spanner))]
190pub fn query(input: TokenStream) -> TokenStream {
191    let item = parse_macro_input!(input as ItemStruct);
192    let query = query::generate_query_methods(item);
193    wrap_in_dummy_mod(query)
194}
195
196fn wrap_in_dummy_mod(item: impl ToTokens) -> TokenStream {
197    //reference https://github.com/diesel-rs/diesel/blob/94599bdc86692900c888974bb4a03568799978d3/diesel_derives/src/util.rs
198    let wrapped = quote! {
199        #[allow(unused_imports)]
200        const _: () = {
201            use google_cloud_spanner::statement::{ToStruct, ToKind, Kinds, Types};
202            use google_cloud_spanner::row::{Struct, TryFromValue, TryFromStruct, Row, Error as RowError};
203            use google_cloud_spanner::value::CommitTimestamp;
204            use std::convert::TryFrom;
205
206            #item
207        };
208    };
209    wrapped.into()
210}