use crate::alloc_prelude::{String, Vec, format};
pub const MAX_IDENTIFIER_LENGTH: usize = 64;
const DICTIONARY: &[u8; 62] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
#[must_use]
pub fn hash(input: &str, len: usize) -> String {
let dict_len = DICTIONARY.len() as u128;
let combinations = dict_len.pow(u32::try_from(len).unwrap_or(u32::MAX));
let mut power: u128 = 1;
let mut value: u128 = 0;
for character in input.chars() {
value = (value + u128::from(u32::from(character)) * power) % combinations;
power = (power * 53) % combinations;
}
let mut digits = Vec::with_capacity(len);
for _ in 0..len {
digits.push(char::from(
DICTIONARY[usize::try_from(value % dict_len).unwrap_or(0)],
));
value /= dict_len;
}
digits.into_iter().rev().collect()
}
#[must_use]
pub fn foreign_key_name(table: &str, columns: &[&str]) -> String {
let desired = format!("{table}_{}_fkey", columns.join("_"));
if desired.chars().count() <= MAX_IDENTIFIER_LENGTH {
return desired;
}
if table.chars().count() + 18 <= MAX_IDENTIFIER_LENGTH {
format!("{table}_{}_fkey", hash(&desired, 12))
} else {
format!("{}_fkey", hash(&desired, 12))
}
}
#[must_use]
pub fn composite_foreign_key_name_columns<'a>(
columns: &'a [&'a str],
collides: bool,
) -> &'a [&'a str] {
if collides || columns.is_empty() {
columns
} else {
&columns[..1]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_matches_drizzle_kit() {
assert_eq!(hash("users_id_fkey", 12), "IFSdtU1x7svu");
assert_eq!(
hash(
"audit_my_l1_organization_membership_invitations_inviting_organization_parent_id_fkey",
12
),
"vjgRHL25Jj6Q"
);
assert_eq!(hash("", 12), "000000000000");
assert_eq!(hash("a", 12).len(), 12);
assert_eq!(hash("abc", 12), hash("abc", 12));
assert_ne!(hash("abc", 12), hash("abd", 12));
}
#[test]
fn long_foreign_key_names_fit_mysql_identifiers() {
let table = "audit_my_l1_organization_membership_invitations";
let name = foreign_key_name(table, &["inviting_organization_parent_id"]);
assert!(name.len() <= MAX_IDENTIFIER_LENGTH, "{name}");
assert_eq!(name, "vjgRHL25Jj6Q_fkey");
assert_eq!(
name,
foreign_key_name(table, &["inviting_organization_parent_id"])
);
let medium = foreign_key_name("audit_my_l1_children", &["x".repeat(50).as_str()]);
assert!(medium.starts_with("audit_my_l1_children_"), "{medium}");
assert_eq!(medium.len(), "audit_my_l1_children".len() + 18);
let long_table = "t".repeat(60);
let name = foreign_key_name(&long_table, &["id"]);
assert_eq!(name.len(), 17);
assert!(name.ends_with("_fkey"));
let exact = format!("{}_id_fkey", "x".repeat(56));
assert_eq!(exact.len(), 64);
assert_eq!(foreign_key_name(&"x".repeat(56), &["id"]), exact);
}
}