pleingres-sql-plugin 0.2.0

A plugin to derive implementations of pleingres::Request from structs and SQL queries.
//! This plugin allows to generate simple SQL queries from Rust
//! struct. More specifically, it generates implementations of the
//! `pleingres::Request` trait, replacing named variables (such as
//! `$field`, which is invalid SQL) in the query by `$1` in the query,
//! and calling `pleingres::Buffer.bind` on `self.field`.
//!
//! It only works if the fields used in the query implement
//! `pleingres::ToSql`, and if the type is a struct.
//!
//! Example:
//!
//! ```
//! #![feature(plugin)]
//! #![plugin(sql)]
//!
//! #[sql("SELECT id, is_active FROM users WHERE login = $user")]
//! pub struct UserReq {
//!     pub user: String,
//!     pub is_active: bool,
//!     pub id: Option<Uuid>
//! }
//! ```

#![crate_type = "dylib"]
#![feature(quote, concat_idents, plugin_registrar, rustc_private)]
#![feature(custom_attribute)]

extern crate syntax;
extern crate rustc_plugin;
extern crate regex;

use syntax::ext::base::SyntaxExtension;
use syntax::symbol::Symbol;
use syntax::source_map::Span;
use syntax::ast::{Ident, MetaItem, ItemKind, MetaItemKind, NestedMetaItemKind, LitKind};
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ext::build::AstBuilder;
use std::collections::HashMap;
use regex::Captures;


#[doc(hidden)]
#[plugin_registrar]
pub fn registrar(reg: &mut rustc_plugin::Registry) {
    reg.register_syntax_extension(
        Symbol::intern("sql"),
        SyntaxExtension::MultiModifier(Box::new(decorator))
    );
}

fn decorator(ecx: &mut ExtCtxt,
             sp: Span,
             meta_item: &MetaItem,
             annotated: Annotatable) -> Vec<Annotatable> {

    let re = regex::Regex::new(r"\$([a-zA-Z0-9_\.]+)").unwrap();
    let mut sql = Vec::new();
    if let MetaItemKind::List(ref sql_) = meta_item.node {
        for stmt in sql_.iter() {

            if let NestedMetaItemKind::Literal(ref stmt) = stmt.node {
                if let LitKind::Str(ref stmt, _) = stmt.node {

                    let mut variable_numbers = HashMap::new();
                    let mut binds = Vec::new();

                    // Replace all variable names by numbers.
                    let replaced = re.replace_all(&stmt.as_str(), |cap: &Captures| {
                        let var = cap.get(1).unwrap().as_str();
                        if variable_numbers.get(var).is_none() {
                            let len = format!("${}", variable_numbers.len() + 1);

                            let mut expr = Some(ecx.expr_self(sp));
                            for field in var.split('.') {
                                expr = Some(ecx.expr_field_access(
                                    sp,
                                    expr.take().unwrap(),
                                    Ident::from_str(field)
                                ))
                            }

                            binds.push(
                                ecx.expr_addr_of(
                                    sp,
                                    expr.unwrap()
                                )
                            );

                            variable_numbers.insert(var.to_string(), len);
                        }
                        variable_numbers.get(var).unwrap().to_string()
                    }).to_string();
                    let binds = ecx.expr_vec_slice(sp, binds);
                    sql.push(quote_stmt!(
                        ecx,
                        buf.bind($replaced, $binds).execute(0);
                    ).unwrap())
                }
            }
        }
    }
    // println!("SQL: {:?}", sql);
    let mut output = Vec::new();
    match annotated.clone() {
        Annotatable::Item(it) => {
            // println!("it = {:?}", it);
            // let it = it.unwrap();
            if let ItemKind::Struct(_, _) = it.node {
                output.push(annotated);
                // println!("data = {:?}", data);
                // println!("generics = {:?}", generics);
                let name = &it.ident;
                output.push(Annotatable::Item(
                    quote_item!(
                        ecx,
                        impl pleingres::Request for $name {
                            fn request(&mut self, buf: &mut pleingres::Buffer) {
                                $sql
                                debug!("request sent");
                            }
                        }
                    ).unwrap()
                ));
            } else {
                panic!("not a struct")
            }
        },
        _ => panic!("not an item")
    }
    output

}