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}