1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
use std::error::Error;

use quote;
use syn;

use table_data::TableData;
use data_structures::ColumnInformation;
use inference::{establish_connection, get_table_data, determine_column_type, get_primary_keys,
                InferConnection};

pub fn expand_infer_table_from_schema(database_url: &str, table: &TableData)
    -> Result<quote::Tokens, Box<Error>>
{
    let connection = establish_connection(database_url)?;
    let data = get_table_data(&connection, table)?;
    let primary_keys = get_primary_keys(&connection, table)?
        .into_iter()
        .map(syn::Ident::new);
    let table_name = syn::Ident::new(&*table.name);

    let mut tokens = Vec::with_capacity(data.len());

    for a in data {
        tokens.push(column_def_tokens(&a, &connection)?);
    }
    let default_schema = default_schema(&connection);
    if table.schema != default_schema {
        if let Some(ref schema) = table.schema {
            let schema_name = syn::Ident::new(&schema[..]);
            return Ok(quote!(table! {
                #schema_name.#table_name (#(#primary_keys),*) {
                    #(#tokens),*,
                }
            }));
        }
    }
    Ok(quote!(table! {
        #table_name (#(#primary_keys),*) {
            #(#tokens),*,
        }
    }))
}

pub fn handle_schema<I>(tables: I, schema_name: Option<&str>) -> quote::Tokens
    where I: Iterator<Item = quote::Tokens>
{
    match schema_name {
        Some(name) => {
            let schema_ident = syn::Ident::new(name);
            quote! { pub mod #schema_ident { #(#tables)* } }
        }
        None => quote!(#(#tables)*),
    }
}

fn column_def_tokens(
    column: &ColumnInformation,
    connection: &InferConnection,
) -> Result<quote::Tokens, Box<Error>> {
    let column_name = syn::Ident::new(&*column.column_name);
    let column_type = try!(determine_column_type(column, connection));
    let tpe = if column_type.path[0] == "diesel" && column_type.path[1] == "types" {
        let path_segments = column_type.path
            .into_iter()
            .skip(2)
            .map(syn::PathSegment::from)
            .collect();
        syn::Path { global: false, segments: path_segments }
    } else {
        let path_segments = column_type.path
            .into_iter()
            .map(syn::PathSegment::from)
            .collect();
        syn::Path { global: true, segments: path_segments }
    };
    let mut tpe = quote!(#tpe);

    if column_type.is_array {
        tpe = quote!(Array<#tpe>);
    }
    if column_type.is_nullable {
        tpe = quote!(Nullable<#tpe>);
    }
    Ok(quote!(#column_name -> #tpe))
}

fn default_schema(conn: &InferConnection) -> Option<String> {
    #[cfg(feature="mysql")]
    use information_schema::UsesInformationSchema;
    #[cfg(feature="mysql")]
    use diesel::mysql::Mysql;

    match *conn {
        #[cfg(feature="sqlite")]
        InferConnection::Sqlite(_) => None,
        #[cfg(feature="postgres")]
        InferConnection::Pg(_) => Some("public".into()),
        #[cfg(feature="mysql")]
        InferConnection::Mysql(ref c) => Mysql::default_schema(c).ok(),
    }
}