use std::collections::HashSet;
use crate::tree::ast::identifier::{Identifier, SimpleIdentifier};
use crate::tree::ast::query::{Query, QueryKind};
use super::CombineQueriesError;
#[derive(Debug, Default)]
pub struct DefNameRegistry {
used: HashSet<Identifier>,
}
impl DefNameRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn extend_from_query(&mut self, query: &Query) {
if let QueryKind::Valid(valid) = &query.kind {
for d in &valid.defs {
if let Ok(id) = d.name.valid_ref() {
self.used.insert(id.clone());
}
}
}
}
pub fn allocate_cte_name(
&mut self,
base: &str,
) -> Result<SimpleIdentifier, CombineQueriesError> {
let candidate = pick_available_def_name(base, &self.used)
.map_err(CombineQueriesError::InvalidCteName)?;
self.used.insert(candidate.clone().into());
Ok(candidate)
}
pub fn used_names(&self) -> &HashSet<Identifier> {
&self.used
}
}
pub fn pick_available_def_name(
base: &str,
blocked: &HashSet<Identifier>,
) -> Result<SimpleIdentifier, String> {
let mut candidate = SimpleIdentifier::parse(base).map_err(|e| e.to_string())?;
if !blocked.contains(&candidate.clone().into()) {
return Ok(candidate);
}
let mut suffix = 2;
loop {
candidate =
SimpleIdentifier::parse(&format!("{base}_{suffix}")).map_err(|e| e.to_string())?;
if !blocked.contains(&candidate.clone().into()) {
return Ok(candidate);
}
suffix += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tree::ast::ParseWithErrors;
#[test]
fn allocate_avoids_collision() {
let mut registry = DefNameRegistry::new();
let (seed, errs) =
Query::parse_with_errors("DEF events_okta = 1 AS int;\nFROM events:base");
assert!(errs.is_empty(), "{errs:?}");
registry.extend_from_query(&seed);
let name = registry.allocate_cte_name("events_okta").unwrap();
assert_eq!(name.as_str(), "events_okta_2");
}
#[test]
fn allocate_rejects_invalid_identifier() {
let mut registry = DefNameRegistry::new();
let err = registry.allocate_cte_name("123bad").unwrap_err();
assert!(matches!(err, CombineQueriesError::InvalidCteName(_)));
}
}