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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use std::error::Error;

use diesel::Connection;
use diesel::result::Error::NotFound;
#[cfg(feature = "postgres")]
use diesel::pg::PgConnection;
#[cfg(feature = "sqlite")]
use diesel::sqlite::SqliteConnection;
#[cfg(feature = "mysql")]
use diesel::mysql::MysqlConnection;

use table_data::TableData;
use data_structures::{ColumnInformation, ColumnType};

pub enum InferConnection {
    #[cfg(feature = "sqlite")]
    Sqlite(SqliteConnection),
    #[cfg(feature = "postgres")]
    Pg(PgConnection),
    #[cfg(feature = "mysql")]
    Mysql(MysqlConnection),
}

pub fn load_table_names(database_url: &str, schema_name: Option<&str>)
    -> Result<Vec<TableData>, Box<Error>>
{
    let connection = try!(establish_connection(database_url));

    match connection {
        #[cfg(feature = "sqlite")]
        InferConnection::Sqlite(c) => ::sqlite::load_table_names(&c, schema_name),
        #[cfg(feature = "postgres")]
        InferConnection::Pg(c) => ::information_schema::load_table_names(&c, schema_name),
        #[cfg(feature = "mysql")]
        InferConnection::Mysql(c) => ::information_schema::load_table_names(&c, schema_name),
    }
}

pub fn establish_connection(database_url: &str) -> Result<InferConnection, Box<Error>> {
    match database_url {
        #[cfg(feature = "postgres")]
        _ if database_url.starts_with("postgres://") || database_url.starts_with("postgresql://") => {
            establish_real_connection(database_url).map(InferConnection::Pg)
        }
        #[cfg(feature = "mysql")]
        _ if database_url.starts_with("mysql://") => {
            establish_real_connection(database_url).map(InferConnection::Mysql)
        }
        #[cfg(feature = "sqlite")]
        _ => establish_real_connection(database_url).map(InferConnection::Sqlite),
        #[cfg(all(feature = "postgres", not(feature = "sqlite")))]
        _ => {
            Err(format!(
                "{} is not a valid PG database URL. \
                It must start with postgres:// or postgresql://",
                database_url,
            ).into())
        }
        #[cfg(all(feature = "mysql", not(any(feature = "sqlite", feature = "postgres"))))]
        _ => {
            Err(format!(
                "{} is not a valid MySQL database URL. \
                It must start with mysql://",
                database_url,
            ).into())
        }
    }
}

fn establish_real_connection<Conn>(database_url: &str) -> Result<Conn, Box<Error>> where
    Conn: Connection,
{
    Conn::establish(database_url).map_err(|error| {
        format!(
            "Failed to establish a database connection at {}. Error: {:?}",
            database_url,
            error,
        ).into()
    })
}

pub fn get_table_data(conn: &InferConnection, table: &TableData)
    -> Result<Vec<ColumnInformation>, Box<Error>>
{
    let column_info = match *conn {
        #[cfg(feature = "sqlite")]
        InferConnection::Sqlite(ref c) => ::sqlite::get_table_data(c, table),
        #[cfg(feature = "postgres")]
        InferConnection::Pg(ref c) => ::information_schema::get_table_data(c, table),
        #[cfg(feature = "mysql")]
        InferConnection::Mysql(ref c) => ::information_schema::get_table_data(c, table),
    };
    if let Err(NotFound) = column_info {
        Err(format!("no table exists named {}", table.to_string()).into())
    } else {
        column_info.map_err(Into::into)
    }
}

pub fn determine_column_type(
    attr: &ColumnInformation,
    conn: &InferConnection,
) -> Result<ColumnType, Box<Error>> {
    match *conn {
        #[cfg(feature = "sqlite")]
        InferConnection::Sqlite(_) => ::sqlite::determine_column_type(attr),
        #[cfg(feature = "postgres")]
        InferConnection::Pg(_) => ::pg::determine_column_type(attr),
        #[cfg(feature = "mysql")]
        InferConnection::Mysql(_) => ::mysql::determine_column_type(attr),
    }
}

pub fn get_primary_keys(
    conn: &InferConnection,
    table: &TableData,
) -> Result<Vec<String>, Box<Error>> {
    let primary_keys: Vec<String> = try!(match *conn {
        #[cfg(feature = "sqlite")]
        InferConnection::Sqlite(ref c) => ::sqlite::get_primary_keys(c, table),
        #[cfg(feature = "postgres")]
        InferConnection::Pg(ref c) => ::information_schema::get_primary_keys(c, table),
        #[cfg(feature = "mysql")]
        InferConnection::Mysql(ref c) => ::information_schema::get_primary_keys(c, table),
    });
    if primary_keys.is_empty() {
        Err(format!("Diesel only supports tables with primary keys. \
                    Table {} has no primary key", table.to_string()).into())
    } else if primary_keys.len() > 4 {
        Err(format!("Diesel does not currently support tables with \
                     primary keys consisting of more than 4 columns. \
                     Table {} has {} columns in its primary key. \
                     Please open an issue and we will increase the \
                     limit.", table.to_string(), primary_keys.len()).into())
    } else {
        Ok(primary_keys)
    }
}