cassandra_macro_derive/
lib.rs

1//! Derive of the crate `cassandra_macro`
2//!
3//! Create Cassandra tables and CRUD CQL prepared statements
4//! from Rust structs.
5//!
6//! This crate intends to reduce the boilerplate when create
7//! and executing basic Cassandra statements. We can see that
8//! the Rust struct holds the metadata for executing basic
9//! operations. Before this crate, you will need to write the
10//! Cassandra Table definition, create the Rust struct and
11//! manually write all CQL statements for all operations.
12//!
13//! Worse than duplicated metadata, is refactoring code when you
14//! add a new table, or change tables names and the statements
15//! blows up because the table no longer exists.
16//!
17//! This crate is not perfect, and you are welcome to contribute.
18//!
19//! # Installation
20//!
21//! The crate `cdrs` is required since we make prepared statements
22//! and the values of your struct must be mapped into Cassandra
23//! datatypes and this crate does this mapping, and also, you will
24//! need a driver between Rust code and your Cassandra cluster.
25//!
26//! ```toml
27//! [dependencies]
28//! cdrs = { version = "2" }
29//! cassandra_macro = "0.1.3"
30//! cassandra_macro_derive = "0.1.3"
31//! ```
32//!
33//! In your `main.rs`
34//!
35//! ```
36//! #[macro_use]
37//! extern crate cdrs;
38//! ```
39//!
40//! # Example
41//! ```
42//! #[macro_use]
43//!extern crate cdrs;
44//!
45//!use std::sync::Arc;
46//!
47//!use cassandra_macro::{CassandraTable, DeleteQuery, Projection, UpdateQuery};
48//!use cassandra_macro::StoreQuery;
49//!use cassandra_macro_derive::CassandraTable;
50//!use cdrs::authenticators::StaticPasswordAuthenticator;
51//!use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder, TcpConnectionPool};
52//!use cdrs::cluster::session::{new_lz4, Session};
53//!use cdrs::Error as CassandraDriverError;
54//!use cdrs::frame::TryFromRow;
55//!use cdrs::load_balancing::RoundRobinSync;
56//!use cdrs::query::{QueryExecutor, QueryValues};
57//!use cdrs::types::ByName;
58//!use cdrs::types::rows::Row;
59//!use cdrs::types::value::Value;
60//!use chrono::Utc;
61//!use uuid::Uuid;
62//!
63//!#[table(keyspace = "test", options = "comment='Only for RUST users' | COMPACTION = {'class':'SizeTieredCompactionStrategy'}")]
64//!#[derive(Debug, CassandraTable)]
65//!pub struct User {
66//!    #[column(type = "TEXT", primary_key)]
67//!    username: String,
68//!
69//!    #[column(type = "UUID")]
70//!    user_internal_id: Uuid,
71//!
72//!    #[column(type = "TEXT")]
73//!    first_name: String,
74//!
75//!    #[column(type = "TIMESTAMP", cluster_key(order = "ASC", position = 1))]
76//!    created: i64,
77//!
78//!    #[column(type = "TIMESTAMP")]
79//!    updated: i64,
80//!}
81//!
82//!impl User {
83//!    fn set_first_name(&mut self, first_name: String) {
84//!        self.first_name = first_name;
85//!    }
86//!}
87//!
88//!impl Default for User {
89//!    fn default() -> Self {
90//!        User {
91//!            username: "Rust".to_string(),
92//!            user_internal_id: Uuid::new_v4(),
93//!            first_name: "rust".to_string(),
94//!            created: Utc::now().timestamp_millis(),
95//!            updated: Utc::now().timestamp_millis(),
96//!        }
97//!    }
98//!}
99//!
100//!impl TryFromRow for User {
101//!    fn try_from_row(row: Row) -> Result<Self, cdrs::Error> {
102//!        let username = row.r_by_name::<String>("username")?;
103//!        let user_internal_id = row.r_by_name::<Uuid>("user_internal_id")?;
104//!        let first_name = row.r_by_name::<String>("first_name")?;
105//!        let created: i64 = row.r_by_name::<i64>("created")?;
106//!        let updated: i64 = row.r_by_name::<i64>("updated")?;
107//!
108//!        Ok(User {
109//!            username,
110//!            user_internal_id,
111//!            first_name,
112//!            created,
113//!            updated,
114//!        })
115//!    }
116//!}
117//!
118//!
119//!pub struct CassandraConfig {
120//!    nodes: Vec<String>,
121//!    user: String,
122//!    password: String,
123//!}
124//!
125//!pub struct CassandraDriver {
126//!    connection: Arc<Session<RoundRobinSync<TcpConnectionPool<StaticPasswordAuthenticator>>>>
127//!}
128//!
129//!impl CassandraDriver {
130//!    pub fn execute_simple_statement<Q: ToString>(&self, query: Q) -> Result<bool, CassandraDriverError> {
131//!        match self.connection.query(query) {
132//!            Ok(_) => Ok(true),
133//!            Err(e) => {
134//!                Err(e)
135//!            }
136//!        }
137//!    }
138//!
139//!    pub fn execute_store_query(&self, query: &StoreQuery) -> Result<bool, CassandraDriverError> {
140//!        self.execute_query(query.query(), query.values())
141//!    }
142//!
143//!    pub fn execute_update_query(&self, query: &UpdateQuery) -> Result<bool, CassandraDriverError> {
144//!        self.execute_query(query.query(), query.values())
145//!    }
146//!
147//!    pub fn execute_delete_query(&self, query: &DeleteQuery) -> Result<bool, CassandraDriverError> {
148//!        self.execute_query(query.query(), query.values())
149//!    }
150//!
151//!    pub fn execute_query(&self, query: &String, values: &QueryValues) -> Result<bool, CassandraDriverError> {
152//!        let result = self.connection
153//!            .query_with_values(query, values.to_owned());
154//!
155//!        result.map(|_| true)
156//!    }
157//!
158//!    pub fn find<T: TryFromRow + CassandraTable>(&self, keys: Vec<String>) -> Result<Option<T>, CassandraDriverError> {
159//!        let stmt = T::select_by_primary_keys(Projection::All);
160//!
161//!        let values = keys.iter().map(|k| Value::from(k.to_string())).collect::<Vec<Value>>();
162//!
163//!        let result_frame = self.connection.query_with_values(stmt, QueryValues::SimpleValues(values))?;
164//!
165//!        Ok(result_frame.get_body()?.into_rows()
166//!            .map(|r| { r.first().map(|r| T::try_from_row(r.to_owned()).unwrap()) }).flatten())
167//!    }
168//!
169//!    pub fn new_from_config(cassandra_configs: &CassandraConfig) -> Self {
170//!        let mut nodes = Vec::with_capacity(cassandra_configs.nodes.len());
171//!
172//!        for node in cassandra_configs.nodes.iter() {
173//!            let authenticator: StaticPasswordAuthenticator = StaticPasswordAuthenticator::new(cassandra_configs.user.clone(),
174//!                                                                                              cassandra_configs.password.clone());
175//!
176//!            let node_tcp = NodeTcpConfigBuilder::new(node.as_str(), authenticator).build();
177//!
178//!            nodes.push(node_tcp);
179//!        }
180//!
181//!        let cluster_config = ClusterTcpConfig(nodes);
182//!
183//!        let cassandra_session = new_lz4(&cluster_config, RoundRobinSync::new())
184//!            .expect("Cassandra session must be created");
185//!
186//!        CassandraDriver {
187//!            connection: Arc::new(cassandra_session)
188//!        }
189//!    }
190//!}
191//!
192//!fn main() {
193//!    let driver_conf = CassandraConfig {
194//!        nodes: vec!["aella:9042".to_string()],
195//!        user: String::from("test"),
196//!        password: String::from("test"),
197//!    };
198//!
199//!    let connection = CassandraDriver::new_from_config(&driver_conf);
200//!
201//!    println!("Keyspace:.{}.", User::key_space());
202//!    println!("Table name:.{}.", User::table_name());
203//!    println!("Creating table:{}", User::create_table_cql());
204//!    connection.execute_simple_statement(User::create_table_cql()).expect("Must create table");
205//!
206//!    println!("You can test those by yourself");
207//!    println!("{}", User::select_by_primary_keys(Projection::Columns(vec!["created".to_string()])));
208//!    println!("{}", User::select_by_primary_and_cluster_keys(Projection::All));
209//!    println!("{}", User::update_by_primary_keys(vec!["updated".to_string()]));
210//!    println!("{}", User::update_by_primary_and_cluster_keys(vec!["updated".to_string()]));
211//!    println!("{}", User::delete_by_primary_keys());
212//!    println!("{}", User::delete_by_primary_and_cluster_keys());
213//!
214//!    let mut rust_user = User::default();
215//!
216//!    println!("Storing rust: {}", rust_user.store_query().query());
217//!    connection.execute_store_query(&rust_user.store_query()).expect("User must be stored");
218//!
219//!    let rust_user_from_db: Option<User> = connection.find::<User>(vec!["Rust".to_string()]).unwrap();
220//!    assert!(rust_user_from_db.unwrap().username.eq(&rust_user.username), "Must be the same");
221//!
222//!    println!("Update rust:{}", rust_user.update_query().unwrap().query());
223//!    rust_user.set_first_name(String::from("IamRoot"));
224//!
225//!    connection.execute_update_query(&rust_user.update_query().unwrap()).unwrap();
226//!
227//!    let rust_user_from_db_1 = connection.find::<User>(vec!["Rust".to_string()]).unwrap();
228//!
229//!    assert!(rust_user_from_db_1.unwrap().username.eq(&rust_user.username), "Must be the same");
230//!
231//!    println!("Delete:{}", rust_user.delete_query().query());
232//!    connection.execute_delete_query(&rust_user.delete_query()).expect("Must be deleted");
233//!
234//!    println!("Dropping table: {}", User::drop_table_cql());
235//!    connection.execute_simple_statement(User::drop_table_cql()).expect("Table must be removed");
236//!}
237//! ```
238#![recursion_limit = "128"]
239extern crate proc_macro;
240
241use proc_macro::TokenStream;
242use std::collections::{BTreeMap, HashMap};
243
244use syn;
245use syn::NestedMeta;
246use std::str::FromStr;
247
248use quote::{quote, ToTokens};
249
250#[proc_macro_derive(CassandraTable, attributes(column, table))]
251pub fn cassandra_macro_derive(input: TokenStream) -> TokenStream {
252    // Construct a representation of Rust code as a syntax tree
253    // that we can manipulate
254    let ast = syn::parse(input).unwrap();
255
256    // Build the trait implementation
257    impl_cassandra_macro(&ast)
258}
259
260fn impl_cassandra_macro(ast: &syn::DeriveInput) -> TokenStream {
261    let table_name = pascal_case_to_snake_case(&ast.ident.to_string());
262
263    let mut table_meta = TableMeta::with_name(&table_name);
264
265    // Ensure the macro is on a struct with named fields
266    let fields = match ast.data {
267        syn::Data::Struct(syn::DataStruct { ref fields, .. }) => {
268            if fields.iter().any(|field| field.ident.is_none()) {
269                panic!("struct has unnamed fields");
270            }
271            fields.iter().cloned().collect()
272        }
273        _ => panic!("#[derive(CassandraConfig)] can only be used with structs"),
274    };
275
276    extract_struct_attributes(&mut table_meta, &fields);
277
278    for attr in ast.attrs.iter() {
279        match attr.parse_meta() {
280            Ok(syn::Meta::List(syn::MetaList { ref path, ref nested, .. })) => {
281                let ident = path.get_ident().unwrap();
282                match ident.to_string().as_ref() {
283                    "table" => {
284                        let mut meta_items_iter = nested.iter();
285
286                        let mut meta_items = Vec::new();
287
288                        while let Some(n) = meta_items_iter.next() {
289                            meta_items.push(n);
290                        }
291
292                        let (key_space, options) = extract_table_properties(&meta_items);
293
294                        table_meta.set_key_space(&key_space);
295                        table_meta.set_table_options(&options);
296                    }
297                    _ => {}
298                }
299            }
300            Err(_) => unreachable!(
301                "Got something other than a list of attributes while checking table attribute"),
302            _ => {}
303        }
304    }
305
306    let create_table_sql = table_meta.create_table_cql();
307    let drop_table_sql = table_meta.drop_table_cql();
308    let key_space = table_meta.key_space();
309    let table_name = table_meta.table_name();
310    let select_by_key = table_meta.select_by_key();
311    let select_by_keys = table_meta.select_by_keys();
312
313    let update_by_key = table_meta.update_by_key();
314    let update_by_keys = table_meta.update_by_keys();
315
316    let delete_by_key = table_meta.delete_by_key();
317    let delete_by_keys = table_meta.delete_by_keys();
318
319    let store_stmt = table_meta.store_stmt();
320    let store_values = table_meta.store_values();
321
322    let (update_stmt, update_values) = table_meta.update_stmt()
323        .unwrap_or((String::new(), proc_macro2::TokenStream::new()));
324
325    let (delete_stmt, delete_values) = table_meta.delete_stmt();
326
327    let ident = &ast.ident;
328
329    // Helper is provided for handling complex generic types correctly and effortlessly
330    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
331    let impl_ast = quote!(
332
333        impl #impl_generics CassandraTable for #ident #ty_generics #where_clause {
334
335            fn create_table_cql() -> &'static str {
336                &#create_table_sql
337            }
338
339            fn drop_table_cql() -> &'static str {
340                &#drop_table_sql
341            }
342
343            fn key_space() -> &'static str {
344                &#key_space
345            }
346
347            fn table_name() -> &'static str {
348                &#table_name
349            }
350
351            fn select_by_primary_keys(projection: cassandra_macro::Projection) -> String {
352                match projection {
353                    cassandra_macro::Projection::Count => {
354                         #select_by_key.to_string().replace("*", "count(*) as count")
355                    },
356                    cassandra_macro::Projection::All => {
357                        #select_by_key.to_string()
358                    },
359                    cassandra_macro::Projection::Columns(c) => {
360                         let column_selection : String = c.join(",");
361
362                         #select_by_key.to_string().replace("*", column_selection.as_str())
363                    }
364                }
365            }
366
367            fn select_by_primary_and_cluster_keys(projection: cassandra_macro::Projection) -> String {
368                match projection {
369                    cassandra_macro::Projection::Count => {
370                         #select_by_keys.to_string().replace("*", "count(*) as count")
371                    },
372                    cassandra_macro::Projection::All => {
373                        #select_by_keys.to_string()
374                    },
375                    cassandra_macro::Projection::Columns(c) => {
376                         let column_selection : String = c.join(",");
377
378                         #select_by_keys.to_string().replace("*", column_selection.as_str())
379                    }
380                }
381            }
382
383            fn update_by_primary_keys(columns: Vec<String>) -> String {
384
385                let update_columns = columns.iter().map(|c| format!(" {}=?", c)).collect::<Vec<String>>().join(",");
386
387                #update_by_key.to_string().replace(":columns", update_columns.as_str())
388            }
389
390            fn update_by_primary_and_cluster_keys(columns: Vec<String>) -> String {
391
392                let update_columns = columns.iter().map(|c| format!(" {}=?", c)).collect::<Vec<String>>().join(",");
393
394                #update_by_keys.to_string().replace(":columns", update_columns.as_str())
395            }
396
397            fn delete_by_primary_keys() -> String {
398                #delete_by_key.to_string()
399            }
400
401            fn delete_by_primary_and_cluster_keys() -> String {
402                #delete_by_keys.to_string()
403            }
404
405            fn store_query(&self) -> cassandra_macro::StoreQuery {
406                cassandra_macro::StoreQuery::new(#store_stmt.to_string(), query_values!(#store_values))
407            }
408
409            fn update_query(&self) -> Result<cassandra_macro::UpdateQuery, cassandra_macro::TableWithNoUpdatableColumnsError>
410            {
411               if #update_stmt.to_string().is_empty() {
412                    return Err(cassandra_macro::TableWithNoUpdatableColumnsError::new(format!("Table {} does not have any updatable column", #table_name)) );
413               }
414
415               Ok(cassandra_macro::UpdateQuery::new(#update_stmt.to_string(), query_values!(#update_values)))
416            }
417
418            fn delete_query(&self) -> cassandra_macro::DeleteQuery {
419                cassandra_macro::DeleteQuery::new(#delete_stmt.to_string(), query_values!(#delete_values))
420            }
421
422        }
423    );
424
425    impl_ast.into()
426}
427
428struct TableMeta {
429    name: String,
430    key_space: String,
431    table_options: String,
432    columns: HashMap<String, String>,
433    static_columns: Vec<String>,
434    primary_keys: BTreeMap<u8, String>,
435    cluster_keys: BTreeMap<u8, (String, String)>,
436}
437
438/// @TODO Refactor duplicated code
439impl TableMeta {
440    fn with_name(name: &String) -> Self {
441        TableMeta {
442            name: name.to_owned(),
443            key_space: String::new(),
444            table_options: String::new(),
445            columns: HashMap::new(),
446            static_columns: Vec::new(),
447            primary_keys: BTreeMap::new(),
448            cluster_keys: BTreeMap::new(),
449        }
450    }
451
452    fn delete_stmt(&self) -> (String, proc_macro2::TokenStream) {
453        let pk_values: Vec<String> = self.primary_keys.values().map(|p| p.to_owned()).collect();
454
455        let ck_values: Vec<String> = self.cluster_keys.values().map(|(c, _)| c.to_owned()).collect();
456
457        let keys: Vec<(String, String)> = [&pk_values[..], &ck_values[..]]
458            .concat()
459            .iter()
460            .map(|c| {
461                (format!("{}=?", c), format!("self.{}.clone()", c))
462            })
463            .collect::<Vec<(String, String)>>();
464
465        (format!("DELETE FROM {}.{} WHERE {}",
466                 self.key_space,
467                 self.name,
468                 keys.iter().map(|(v, _)| v.to_owned())
469                     .collect::<Vec<String>>()
470                     .join(" AND ")),
471         proc_macro2::TokenStream::from_str(keys.iter().map(|(_, v)| v.to_owned())
472             .collect::<Vec<String>>()
473             .join(",").as_str()).unwrap()
474        )
475    }
476
477    fn update_stmt(&self) -> Option<(String, proc_macro2::TokenStream)> {
478        let mut updatable_columns = Vec::new();
479
480        for (column_name, _) in self.columns.iter() {
481            if self.primary_keys.values().any(|p| p.eq(column_name)) {
482                continue;
483            }
484
485            if self.cluster_keys.values().any(|ck| ck.0.eq(column_name)) {
486                continue;
487            }
488
489            updatable_columns.push(column_name);
490        }
491
492        if updatable_columns.is_empty() {
493            return None;
494        }
495
496        let update_values = updatable_columns.iter().map(|c| {
497            (format!("{}=?", c), format!("self.{}.clone()", c))
498        }).collect::<Vec<(String, String)>>();
499
500        let p_keys = self.primary_keys.iter().map(|(_, pk)| {
501            (format!("{}=?", pk), format!("self.{}.clone()", pk))
502        }).collect::<Vec<(String, String)>>();
503
504        let ck_keys = self.cluster_keys.iter().map(|(_, (ck, _))| {
505            (format!("{}=?", ck), format!("self.{}.clone()", ck))
506        }).collect::<Vec<(String, String)>>();
507
508        let values: String = [&update_values[..], &p_keys[..], &ck_keys[..]]
509            .concat()
510            .iter()
511            .map(|(_, c)| c.to_owned())
512            .collect::<Vec<String>>()
513            .join(",");
514
515        let pk_values: Vec<String> = self.primary_keys.values().map(|p| p.to_owned()).collect();
516
517        let ck_values: Vec<String> = self.cluster_keys.values().map(|(c, _)| c.to_owned()).collect();
518
519        let keys: Vec<(String, String)> = [&pk_values[..], &ck_values[..]]
520            .concat()
521            .iter()
522            .map(|c| {
523                (format!("{}=?", c), format!("self.{}.clone()", c))
524            })
525            .collect::<Vec<(String, String)>>();
526
527        Some((format!("UPDATE {}.{} SET {} WHERE {}",
528                      self.key_space,
529                      self.name,
530                      update_values.iter().map(|(v, _)| v.to_owned()).collect::<Vec<String>>().join(","),
531                      keys.iter().map(|(v, _)| v.to_owned()).collect::<Vec<String>>().join(" AND ")),
532              proc_macro2::TokenStream::from_str(values.as_str()).unwrap()
533        ))
534    }
535
536    fn store_stmt(&self) -> String {
537        let fields = self.columns.iter().map(|(n, _)| n.to_owned()).collect::<Vec<String>>().join(",");
538
539        let mut bind_marks = "?,".repeat(self.columns.len());
540        bind_marks.pop();
541
542        format!("INSERT INTO {}.{} ({}) VALUES ({})", self.key_space, self.name, fields, bind_marks)
543    }
544
545    fn store_values(&self) -> proc_macro2::TokenStream {
546        let fields_tokens = self.columns.iter().map(|(v, _)| {
547            format!("self.{}.clone()", v.to_owned())
548        }).collect::<Vec<String>>().join(",");
549
550        proc_macro2::TokenStream::from_str(fields_tokens.as_str()).unwrap()
551    }
552
553    fn set_key_space(&mut self, key_space: &String) {
554        self.key_space = key_space.to_owned();
555    }
556
557    fn select_by_key(&self) -> String {
558        let where_part = self.primary_keys
559            .iter()
560            .map(|(_, v)| format!(" {}=? ", v))
561            .collect::<Vec<String>>()
562            .join("AND");
563
564        format!("SELECT * FROM {}.{} WHERE {}", self.key_space, self.name, where_part)
565    }
566
567    fn select_by_keys(&self) -> String {
568        let pk_select = self.select_by_key();
569
570        if self.cluster_keys.is_empty() {
571            pk_select
572        } else {
573            let where_part = self.cluster_keys
574                .iter()
575                .map(|(_, (c, _))| format!(" {}=? ", c))
576                .collect::<Vec<String>>()
577                .join("AND");
578
579            format!("{} AND {}", pk_select, where_part)
580        }
581    }
582
583    fn update_by_key(&self) -> String {
584        let where_part = self.primary_keys
585            .iter()
586            .map(|(_, v)| format!(" {}=? ", v))
587            .collect::<Vec<String>>()
588            .join("AND");
589
590        format!("UPDATE {}.{} SET :columns WHERE {}", self.key_space, self.name, where_part)
591    }
592
593    fn update_by_keys(&self) -> String {
594        let pk_select = self.update_by_key();
595
596        if self.cluster_keys.is_empty() {
597            pk_select
598        } else {
599            let where_part = self.cluster_keys
600                .iter()
601                .map(|(_, (c, _))| format!(" {}=? ", c))
602                .collect::<Vec<String>>()
603                .join("AND");
604
605            format!("{} AND {}", pk_select, where_part)
606        }
607    }
608
609    fn delete_by_key(&self) -> String {
610        let where_part = self.primary_keys
611            .iter()
612            .map(|(_, v)| format!(" {}=? ", v))
613            .collect::<Vec<String>>()
614            .join("AND");
615
616        format!("DELETE FROM {}.{} WHERE {}", self.key_space, self.name, where_part)
617    }
618
619    fn delete_by_keys(&self) -> String {
620        let pk_select = self.delete_by_key();
621
622        if self.cluster_keys.is_empty() {
623            pk_select
624        } else {
625            let where_part = self.cluster_keys
626                .iter()
627                .map(|(_, (c, _))| format!(" {}=? ", c))
628                .collect::<Vec<String>>()
629                .join("AND");
630
631            format!("{} AND {}", pk_select, where_part)
632        }
633    }
634
635    fn set_table_options(&mut self, table_options: &String) {
636        self.table_options = table_options.to_owned();
637    }
638
639    fn new_column(&mut self, name: &String, data_type: &String) {
640        self.columns.insert(name.to_owned(), data_type.to_owned());
641    }
642
643    fn set_column_as_static(&mut self, name: &String) {
644        self.static_columns.push(name.to_owned());
645    }
646
647    fn new_primary_key(&mut self, key: &String, position: Option<u8>) {
648        self.primary_keys.insert(position.unwrap_or(1), key.to_owned());
649    }
650
651    fn new_cluster_key(&mut self, name: &String, order: &String, position: Option<u8>) {
652        self.cluster_keys.insert(position.unwrap_or(1), (name.to_owned(), order.to_owned()));
653    }
654
655    fn key_space(&self) -> &String {
656        &self.key_space
657    }
658
659    fn table_name(&self) -> &String {
660        &self.name
661    }
662
663    fn drop_table_cql(&self) -> String {
664        format!("DROP TABLE IF EXISTS {}.{}", self.key_space, self.name)
665    }
666
667    fn create_table_cql(&self) -> String {
668        let mut table_options = String::new();
669        let mut c_order = Vec::new();
670        let mut c_keys = Vec::new();
671
672        let columns: String = self.columns
673            .iter()
674            .map(|(k, t)| {
675                if self.static_columns.contains(k) {
676                    format!("{} {} STATIC", k, t)
677                } else {
678                    format!("{} {}", k, t.to_uppercase())
679                }
680            })
681            .collect::<Vec<String>>()
682            .join(",");
683
684        let opt_parts: Vec<&str> = self.table_options.split("|").filter(|opt| !opt.is_empty()).collect();
685
686        if self.cluster_keys.len() > 0 {
687            for (_, (column, order)) in self.cluster_keys.iter() {
688                c_order.push(format!("{} {}", column, order));
689                c_keys.push(format!("{}", column))
690            }
691            table_options = format!("WITH CLUSTERING ORDER BY ({})", c_order.join(","));
692
693            if opt_parts.len() > 0 {
694                table_options = format!("{} AND {}", table_options, opt_parts.join(" AND "))
695            }
696        } else {
697            if opt_parts.len() > 0 {
698                table_options = format!("WITH {}", opt_parts.join(" AND "))
699            }
700        }
701
702        let primary_keys: String = self.primary_keys
703            .iter()
704            .map(|(_, k)| format!("{}", k))
705            .collect::<Vec<String>>()
706            .join(",");
707
708        let create_stmt = format!("CREATE TABLE IF NOT EXISTS {}.{} ", self.key_space, self.name);
709
710        if c_keys.len() > 0 {
711            format!("{} ({}, PRIMARY KEY (({}), {}) ) {}", create_stmt, columns, primary_keys, c_keys.join(","), table_options)
712        } else {
713            format!("{} ({}, PRIMARY KEY ({}) ) {}", create_stmt, columns, primary_keys, table_options)
714        }
715    }
716}
717
718/// Parse struct attributes
719fn extract_struct_attributes(table_meta: &mut TableMeta, fields: &Vec<syn::Field>) {
720    for field in fields {
721        let field_ident = field.ident.clone().unwrap().to_string();
722
723        if field.attrs.len() > 0 {
724            for attr in &field.attrs {
725                if !attr.path.to_token_stream().to_string().contains("column") {
726                    continue;
727                }
728
729                match attr.parse_meta() {
730                    Ok(syn::Meta::List(syn::MetaList { ref nested, .. })) => {
731                        let mut meta_items_iter = nested.iter();
732
733                        let mut meta_items = Vec::new();
734
735                        while let Some(n) = meta_items_iter.next() {
736                            meta_items.push(n);
737                        }
738
739                        // only validation from there on
740                        for meta_item in meta_items {
741                            match *meta_item {
742                                syn::NestedMeta::Meta(ref item) => match *item {
743                                    syn::Meta::Path(ref name) => {
744                                        match name.get_ident().unwrap().to_string().as_ref() {
745                                            "primary_key" => {
746                                                table_meta.new_primary_key(&field_ident, None);
747                                            }
748                                            "static" => {
749                                                table_meta.set_column_as_static(&field_ident);
750                                            }
751                                            _ => panic!("Unexpected validator: {:?}", name.get_ident()),
752                                        }
753                                    }
754                                    syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. }) => {
755                                        let ident = path.get_ident().unwrap();
756                                        match ident.to_string().as_ref() {
757                                            "type" => {
758                                                table_meta.new_column(&field_ident.clone(), &lit_to_string(lit).unwrap_or(String::new()));
759                                            }
760                                            v => panic!("unexpected name value validator: {:?}", v),
761                                        };
762                                    }
763                                    syn::Meta::List(syn::MetaList { ref path, ref nested, .. }) => {
764                                        let mut meta_items_iter = nested.iter();
765
766                                        let mut meta_items: Vec<&NestedMeta> = Vec::new();
767
768                                        while let Some(n) = meta_items_iter.next() {
769                                            meta_items.push(n);
770                                        }
771
772                                        let ident = path.get_ident().unwrap();
773                                        match ident.to_string().as_ref() {
774                                            "cluster_key" => {
775                                                let (order, position) = extract_cluster_properties(&meta_items);
776
777                                                table_meta.new_cluster_key(&field_ident, &order, Some(position));
778                                            }
779                                            "compound_key" => {
780                                                let (_, position) = extract_cluster_properties(&meta_items);
781
782                                                table_meta.new_primary_key(&field_ident, Some(position))
783                                            }
784                                            v => panic!("unexpected list validator: {:?}", v),
785                                        }
786                                    }
787                                },
788                                _ => unreachable!("Found a non Meta while looking for validators"),
789                            };
790                        }
791                    }
792                    Ok(syn::Meta::NameValue(_)) => panic!("Unexpected name=value argument"),
793                    Err(e) => unreachable!(
794                        "Got something other than a list of attributes while checking field `{}`: {:?}",
795                        field_ident, e
796                    ),
797                    _ => {}
798                }
799            }
800        }
801    }
802}
803
804fn lit_to_string(lit: &syn::Lit) -> Option<String> {
805    match *lit {
806        syn::Lit::Str(ref s) => Some(s.value()),
807        _ => None,
808    }
809}
810
811fn lit_to_int(lit: &syn::Lit) -> Option<i64> {
812    match *lit {
813        syn::Lit::Int(ref s) => Some(s.base10_parse().unwrap()),
814        _ => None,
815    }
816}
817
818fn extract_cluster_properties(meta_items: &Vec<&syn::NestedMeta>) -> (String, u8) {
819    let mut order = String::from("DESC");
820    let mut position = 1;
821
822    for meta_item in meta_items {
823        if let syn::NestedMeta::Meta(ref item) = **meta_item {
824            if let syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. }) = *item {
825                let ident = path.get_ident().unwrap();
826                match ident.to_string().as_ref() {
827                    "order" => {
828                        order = lit_to_string(lit).unwrap_or(String::from("DESC"))
829                    }
830                    "position" => {
831                        position = lit_to_int(lit).unwrap_or(1) as u8;
832                    }
833                    v => panic!("unknown argument `{}` for column `cluster_key`", v)
834                }
835            } else {
836                panic!("unexpected item while parsing `cluster_key` column of field")
837            }
838        }
839    }
840
841    (order, position)
842}
843
844fn extract_table_properties(meta_items: &Vec<&syn::NestedMeta>) -> (String, String) {
845    let mut keyspace = String::new();
846    let mut options = String::new();
847
848    for meta_item in meta_items {
849        if let syn::NestedMeta::Meta(ref item) = **meta_item {
850            if let syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. }) = *item {
851                let ident = path.get_ident().unwrap();
852                match ident.to_string().as_ref() {
853                    "keyspace" => {
854                        keyspace = lit_to_string(lit).unwrap_or(String::new())
855                    }
856                    "options" => {
857                        options = lit_to_string(lit).unwrap_or(String::new());
858                    }
859                    v => panic!("unknown argument `{}` for column `table`", v)
860                }
861            } else {
862                panic!("unexpected item while parsing `table` column of field")
863            }
864        }
865    }
866
867    (keyspace, options)
868}
869
870const OFFSET: u8 = 32;
871const UNDERSCORE: u8 = 95;
872
873fn pascal_case_to_snake_case(table_name: &String) -> String {
874    let word_size = table_name.len();
875
876    if word_size < 2 {
877        return String::from(table_name);
878    }
879
880    let mut counter = 1;
881    let chars = table_name.as_bytes();
882    let mut sk_table_name: Vec<u8> = Vec::new();
883
884    if chars[0] < 90 {
885        sk_table_name.push(chars[0] + OFFSET);
886    } else {
887        sk_table_name.push(chars[0]);
888    }
889
890    while counter < word_size {
891        let current = chars[counter];
892
893        if current < 90 {
894            sk_table_name.push(UNDERSCORE);
895            sk_table_name.push(current + OFFSET)
896        } else {
897            sk_table_name.push(current);
898        }
899
900        counter += 1;
901    }
902
903    String::from_utf8(sk_table_name).unwrap()
904}
905
906#[cfg(test)]
907mod tests {
908    use crate::pascal_case_to_snake_case;
909
910    #[test]
911    fn test_pascal_case_to_snake_case() {
912        let table_1 = String::from("Test");
913
914        let new_table_1 = pascal_case_to_snake_case(&table_1);
915
916        assert_eq!(new_table_1, String::from("test"));
917
918        let table_2 = String::from("TestHello");
919
920        let new_table_2 = pascal_case_to_snake_case(&table_2);
921
922        assert_eq!(new_table_2, String::from("test_hello"));
923    }
924}