db-derive-impl 0.1.8

Derive proc-macro for db-derive
Documentation
use {
    std::collections::HashMap,
    syn::{parse_str, Ident},
};

#[derive(Clone, Copy, Debug)]
pub enum QueryType {
    PostgreSQL,
    SQLite,
}

pub fn parse_query(typ: QueryType, query: impl AsRef<str>) -> (String, HashMap<Ident, usize>) {
    #[derive(Clone, Copy, Debug)]
    enum State {
        Query,
        Ident,
    }

    let query = query.as_ref();

    let mut postgres = String::with_capacity(query.len());
    let mut sqlite = String::with_capacity(query.len());

    let mut idents = HashMap::new();

    let mut state = State::Query;

    let mut temp = String::with_capacity(10);

    for c in query.chars() {
        match c {
            '{' => match state {
                State::Query => {
                    state = State::Ident;
                }
                State::Ident => {
                    panic!("provided sql is invalid");
                }
            },
            '}' => match state {
                State::Query => {
                    panic!("provided sql is invalid");
                }
                State::Ident => {
                    state = State::Query;

                    let ident = temp.clone();
                    temp.clear();

                    let ident = parse_str(&ident).expect("ident not found");

                    let next_index = idents.len() + 1;
                    let index = *idents.entry(ident).or_insert(next_index);

                    match typ {
                        QueryType::PostgreSQL => {
                            postgres.push('$');
                            postgres.push_str(&index.to_string());
                        }
                        QueryType::SQLite => {
                            sqlite.push('?');
                            sqlite.push_str(&index.to_string());
                        }
                    }
                }
            },
            _ => match state {
                State::Query => {
                    push(typ, &mut postgres, &mut sqlite, c);
                }
                State::Ident => {
                    temp.push(c);
                }
            },
        }
    }

    (
        match typ {
            QueryType::PostgreSQL => postgres,
            QueryType::SQLite => sqlite,
        },
        idents,
    )
}

fn push(typ: QueryType, postgres: &mut String, sqlite: &mut String, c: char) {
    match typ {
        QueryType::PostgreSQL => {
            postgres.push(c);
        }
        QueryType::SQLite => {
            sqlite.push(c);
        }
    }
}

#[cfg(test)]
mod test {
    use super::{parse_query, QueryType};

    const GET_BY_ID: &str = "SELECT Id, Name FROM Tag WHERE Id = {id};";

    fn postgres_parse(query: impl AsRef<str>) -> String {
        parse_query(QueryType::PostgreSQL, query).0
    }

    fn sqlite_parse(query: impl AsRef<str>) -> String {
        parse_query(QueryType::SQLite, query).0
    }

    #[test]
    fn postgres_get_by_id() {
        let query = postgres_parse(&GET_BY_ID);

        assert_eq!("SELECT Id, Name FROM Tag WHERE Id = $1;", query);
    }

    #[test]
    fn sqlite_get_by_id() {
        let query = sqlite_parse(&GET_BY_ID);

        assert_eq!("SELECT Id, Name FROM Tag WHERE Id = ?1;", query);
    }
}