datasources 0.1.12

Async datasource interfaces, middleware, registry, and test doubles
Documentation
pub struct PathParts {
    pub owner: String,
    pub last: String,
    pub root: bool,
}

pub fn select_field_aliases(
    table: &str,
    fields: &[String],
    aliases: &[Option<String>],
) -> Vec<Option<String>> {
    let mut result: Vec<Option<String>> = vec![None; fields.len()];
    let mut used = std::collections::HashSet::new();
    for (index, alias) in aliases.iter().enumerate().take(fields.len()) {
        if let Some(name) = alias {
            used.insert(name.clone());
            result[index] = Some(name.clone());
        }
    }
    for (index, field) in fields.iter().enumerate() {
        if result[index].is_none() {
            if let Some(path) = path_of(field, table) {
                if path.root {
                    used.insert(path.last.clone());
                    result[index] = Some(path.last);
                }
            }
        }
    }
    for (index, field) in fields.iter().enumerate() {
        if result[index].is_none() {
            if let Some(path) = path_of(field, table) {
                if !path.root {
                    let alias = if used.contains(&path.last) {
                        format!("{}{}", path.owner, path.last)
                    } else {
                        path.last
                    };
                    used.insert(alias.clone());
                    result[index] = Some(alias);
                }
            }
        }
    }
    result
}

pub fn apply_select_field_alias(field: &str, alias: Option<&str>) -> String {
    match alias {
        Some(name) => format!("{field} AS {name}"),
        None => field.to_string(),
    }
}

pub fn path_of(field: &str, table: &str) -> Option<PathParts> {
    if as_index(field).is_some() || field.contains('(') {
        return None;
    }
    let parts = segments(field);
    if parts.len() < 2 {
        return None;
    }
    Some(PathParts {
        owner: parts[parts.len() - 2].to_string(),
        last: parts[parts.len() - 1].to_string(),
        root: parts.len() == 2 && parts[0] == table,
    })
}

pub fn function_of(field: &str) -> Option<(&str, &str)> {
    let open = field.find('(')?;
    if open == 0 || !field.ends_with(')') {
        return None;
    }
    Some((field[..open].trim(), field[open + 1..field.len() - 1].trim()))
}

pub fn as_index(field: &str) -> Option<usize> {
    field.to_uppercase().find(" AS ")
}

pub fn segments(name: &str) -> Vec<&str> {
    let mut parts = Vec::new();
    let mut quoted = false;
    let mut start = 0;
    for (index, ch) in name.char_indices() {
        if ch == '"' {
            quoted = !quoted;
        }
        if ch == '.' && !quoted {
            parts.push(&name[start..index]);
            start = index + 1;
        }
    }
    parts.push(&name[start..]);
    parts
}