postgres_query 0.3.3

Write and execute SQL queries with ease
Documentation
use super::Parameter;
use crate::error::{Error, ParseError, Result};
use std::fmt::Write;
use std::iter::Peekable;

pub fn parse<'a>(
    text: &str,
    bindings: &[(&str, Parameter<'a>)],
) -> Result<(String, Vec<Parameter<'a>>)> {
    let mut sql = String::with_capacity(text.len());
    let mut parameters = Vec::with_capacity(bindings.len());
    let mut param_indices = vec![None; bindings.len()];

    let mut chars = text.chars().peekable();

    while let Some(ch) = chars.next() {
        if ch != '$' {
            sql.push(ch);
        } else if let Some('$') = chars.peek() {
            sql.push(chars.next().unwrap());
        } else {
            let name = next_identifier(&mut chars)?;

            let argument = bindings
                .iter()
                .position(|(binding, _)| *binding == name)
                .ok_or_else(|| ParseError::UndefinedBinding { binding: name })?;

            let index = param_indices[argument].unwrap_or_else(|| {
                let (_, value) = bindings[argument];
                parameters.push(value);
                let index = parameters.len();
                param_indices[argument] = Some(index);
                index
            });

            write!(sql, "${}", index).unwrap();
        }
    }

    Ok((sql, parameters))
}

fn next_identifier(chars: &mut Peekable<impl Iterator<Item = char>>) -> Result<String> {
    let mut name = String::new();

    while let Some(&ch) = chars.peek() {
        if ch.is_ascii_alphanumeric() || ch == '_' {
            name.push(chars.next().unwrap());
        } else {
            break;
        }
    }

    if name.is_empty() {
        let found = chars.peek().copied();
        return Err(Error::from(ParseError::EmptyIdentifier { found }));
    }

    Ok(name)
}