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