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}