use std::{fmt, iter::repeat};
#[inline]
pub(crate) fn format_list<T: fmt::Display>(items: &[T], separator: &str) -> String {
items
.iter()
.map(|item| format!("{}", item).to_string())
.collect::<Vec<String>>()
.join(separator)
}
#[inline]
pub(crate) fn format_iter<T: fmt::Display, I: IntoIterator<Item = T>>(
items: I,
separator: &str,
) -> String {
items
.into_iter()
.map(|item| format!("{}", item).to_string())
.collect::<Vec<String>>()
.join(separator)
}
#[inline]
pub(crate) fn ordered_keys(object: &serde_json::Map<String, serde_json::Value>) -> Vec<String> {
object.keys().map(|key| (*key).clone()).collect()
}
#[inline]
pub(crate) fn to_numbered_placeholders(query: &str) -> String {
let mut result = String::new();
let mut counter = 1;
for c in query.chars() {
if c == '?' {
result.push_str(&format!("${counter}"));
counter += 1;
} else {
result.push(c);
}
}
result
}
#[inline]
pub(crate) fn placeholders(count: usize) -> String {
let str_placeholders = repeat("?".to_string())
.take(count)
.collect::<Vec<String>>()
.join(", ");
format!("({str_placeholders})")
}
#[inline]
pub(crate) fn repeat_placeholders(count: usize, n_repeat: usize) -> String {
repeat(placeholders(count))
.take(n_repeat)
.collect::<Vec<String>>()
.join(", ")
}
#[inline]
pub(crate) fn sanitize_identifier(str: &str) -> String {
str.replace(|c: char| !c.is_alphanumeric() && c != '_', "")
}
#[inline]
pub(crate) fn update_statement(table: &str, keys: &[String]) -> String {
let table = sanitize_identifier(table);
let columns = keys
.iter()
.map(|key| format!("\"{}\" = ?", sanitize_identifier(key)))
.collect::<Vec<String>>()
.join(", ");
format!("UPDATE {table} SET {columns} WHERE id = ? RETURNING *")
}
#[inline]
pub(crate) fn insert_statement(table: &str, keys: &[String]) -> String {
let table = sanitize_identifier(table);
let values_placeholders = placeholders(keys.len());
let columns = format_iter(keys.iter().map(|s| sanitize_identifier(s)), ", ");
format!("INSERT INTO {table} ({columns}) VALUES {values_placeholders} RETURNING *")
}
#[inline]
pub(crate) fn insert_many_statement(table: &str, keys: &[String], n_rows: usize) -> String {
let table = sanitize_identifier(table);
let values_placeholders = repeat_placeholders(keys.len(), n_rows);
let columns = format_iter(keys.iter().map(|s| sanitize_identifier(s)), ", ");
format!("INSERT INTO {table} ({columns}) VALUES {values_placeholders} RETURNING *")
}
#[inline]
pub(crate) fn delete_statement(table: &str) -> String {
let table = sanitize_identifier(table);
format!("DELETE FROM {table} WHERE id = ? RETURNING *")
}
pub(crate) fn sql_like(filter: &str, value: &str) -> bool {
fn match_helper(f: &[char], v: &[char]) -> bool {
match (f, v) {
([], []) => true,
([first, rest @ ..], value) if *first == '%' => {
match_helper(rest, value) || (!value.is_empty() && match_helper(f, &value[1..]))
}
([first, rest @ ..], [_, v_rest @ ..]) if *first == '_' => match_helper(rest, v_rest),
([first, rest @ ..], [v_first, v_rest @ ..]) if first == v_first => {
match_helper(rest, v_rest)
}
_ => false,
}
}
match_helper(
&filter.chars().collect::<Vec<_>>(),
&value.chars().collect::<Vec<_>>(),
)
}
pub(crate) fn sql_ilike(filter: &str, value: &str) -> bool {
sql_like(&filter.to_lowercase(), &value.to_lowercase())
}
#[cfg(test)]
mod test_utils {
use super::sql_like;
#[test]
fn test_sql_like() {
assert!(sql_like("he_lo", "hello"));
assert!(sql_like("h%o", "hello"));
assert!(!sql_like("h%o", "hi"));
assert!(sql_like("%", "anything"));
assert!(sql_like("_____", "12345"));
assert!(sql_like("_%_", "abc"));
assert!(sql_like("h_llo", "hello"));
assert!(!sql_like("he_lo", "heeeelo"));
}
}