use crate::core::models::*;
use crate::ui::state::{CategoryKind, LeafKind, TreeNode};
pub(super) trait HasName {
fn get_name(&self) -> String;
fn is_valid(&self) -> bool;
fn get_privilege(&self) -> ObjectPrivilege;
}
impl HasName for Table {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
true
}
fn get_privilege(&self) -> ObjectPrivilege {
self.privilege
}
}
impl HasName for View {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
self.valid
}
fn get_privilege(&self) -> ObjectPrivilege {
self.privilege
}
}
impl HasName for Procedure {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
self.valid
}
fn get_privilege(&self) -> ObjectPrivilege {
self.privilege
}
}
impl HasName for Function {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
self.valid
}
fn get_privilege(&self) -> ObjectPrivilege {
self.privilege
}
}
impl HasName for MaterializedView {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
self.valid
}
fn get_privilege(&self) -> ObjectPrivilege {
self.privilege
}
}
impl HasName for Index {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
true
}
fn get_privilege(&self) -> ObjectPrivilege {
ObjectPrivilege::Unknown
}
}
impl HasName for Sequence {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
true
}
fn get_privilege(&self) -> ObjectPrivilege {
ObjectPrivilege::Unknown
}
}
impl HasName for DbType {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
true
}
fn get_privilege(&self) -> ObjectPrivilege {
ObjectPrivilege::Unknown
}
}
impl HasName for Trigger {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
true
}
fn get_privilege(&self) -> ObjectPrivilege {
ObjectPrivilege::Unknown
}
}
impl HasName for DbEvent {
fn get_name(&self) -> String {
self.name.clone()
}
fn is_valid(&self) -> bool {
true
}
fn get_privilege(&self) -> ObjectPrivilege {
ObjectPrivilege::Unknown
}
}
pub(super) fn extract_names(source: &str, kind: &str) -> Vec<String> {
let kind_upper = kind.to_uppercase();
let kind_len = kind_upper.len();
let mut names = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
let trimmed_upper = trimmed.to_uppercase();
if let Some(rest_upper) = trimmed_upper.strip_prefix(&kind_upper)
&& rest_upper.starts_with(|c: char| c.is_whitespace())
{
let original_rest = &trimmed[kind_len..].trim_start();
let name: String = original_rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !name.is_empty() && !names.contains(&name) {
names.push(name);
}
}
}
names
}
pub(super) fn wrap_error_text(error: &str, max_width: usize) -> String {
let mut lines = Vec::new();
let line_num = extract_error_line(error);
let header = match line_num {
Some(n) => format!("-- Query Error (line {n}) --"),
None => "-- Query Error --".to_string(),
};
lines.push(header);
lines.push(String::new());
let cleaned = if let Some(pos) = error.find(" near '") {
let before = &error[..pos];
let after = error[pos..]
.find("' at line ")
.map(|p| &error[pos + p + 1..])
.unwrap_or("");
format!("{before}{after}")
} else {
error.to_string()
};
for section in cleaned.split(": ") {
let section = section.trim();
if section.is_empty() {
continue;
}
let mut current_line = String::new();
for word in section.split_whitespace() {
if current_line.is_empty() {
current_line.push_str(word);
} else if current_line.len() + 1 + word.len() > max_width {
lines.push(current_line);
current_line = format!(" {word}"); } else {
current_line.push(' ');
current_line.push_str(word);
}
}
if !current_line.is_empty() {
lines.push(current_line);
}
}
lines.push(String::new());
lines.join("\n")
}
pub(super) fn extract_error_line(error: &str) -> Option<usize> {
let lower = error.to_lowercase();
if let Some(pos) = lower.find("at line ") {
let after = &error[pos + 8..];
let num: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
if let Ok(n) = num.parse::<usize>() {
return Some(n);
}
}
if let Some(pos) = lower.find("line ") {
let after = &error[pos + 5..];
let num: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
if let Ok(n) = num.parse::<usize>() {
return Some(n);
}
}
None
}
use super::App;
use crate::sql_engine::metadata::ObjectKind as ObjKind;
impl App {
pub(super) fn handle_objects_loaded<T: HasName>(
&mut self,
conn_name: &str,
schema: &str,
items: Vec<T>,
obj_kind: ObjKind,
cat_kind: CategoryKind,
leaf_kind: LeafKind,
) {
let idx = self
.state
.engine
.metadata_indexes
.entry(conn_name.to_string())
.or_default();
for item in &items {
idx.add_object(schema, &item.get_name(), obj_kind);
}
self.insert_leaves(conn_name, schema, cat_kind, items, leaf_kind);
self.finish_loading();
}
pub(super) fn finish_loading(&mut self) {
self.state.loading = false;
self.state.loading_since = None;
}
pub(super) fn insert_leaves<T: HasName>(
&mut self,
conn_name: &str,
schema: &str,
category: CategoryKind,
items: Vec<T>,
leaf_kind: LeafKind,
) {
let cat_idx = self.find_category_in_connection(conn_name, schema, &category);
if let Some(idx) = cat_idx {
self.remove_children_of(idx);
if items.is_empty() {
self.state.sidebar.tree.insert(idx + 1, TreeNode::Empty);
return;
}
let is_table_or_view = matches!(leaf_kind, LeafKind::Table | LeafKind::View);
let batch: Vec<TreeNode> = items
.iter()
.map(|item| TreeNode::Leaf {
name: item.get_name(),
schema: schema.to_string(),
kind: leaf_kind.clone(),
valid: item.is_valid(),
privilege: item.get_privilege(),
})
.collect();
if is_table_or_view {
for item in &items {
self.state
.sidebar
.table_schema_index
.entry(item.get_name().to_uppercase())
.or_insert_with(|| schema.to_string());
}
}
let insert_pos = idx + 1;
self.state
.sidebar
.tree
.splice(insert_pos..insert_pos, batch);
}
}
pub(super) fn insert_package_leaves(
&mut self,
conn_name: &str,
schema: &str,
items: Vec<Package>,
) {
let cat_idx = self.find_category_in_connection(conn_name, schema, &CategoryKind::Packages);
if let Some(idx) = cat_idx {
self.remove_children_of(idx);
if items.is_empty() {
self.state.sidebar.tree.insert(idx + 1, TreeNode::Empty);
return;
}
let batch: Vec<TreeNode> = items
.into_iter()
.map(|pkg| TreeNode::Leaf {
name: pkg.name,
schema: schema.to_string(),
kind: LeafKind::Package,
valid: pkg.valid,
privilege: pkg.privilege,
})
.collect();
let insert_pos = idx + 1;
self.state
.sidebar
.tree
.splice(insert_pos..insert_pos, batch);
}
}
pub(super) fn find_category_in_connection(
&self,
conn_name: &str,
schema: &str,
category: &CategoryKind,
) -> Option<usize> {
let tree = &self.state.sidebar.tree;
let conn_idx = tree
.iter()
.position(|n| matches!(n, TreeNode::Connection { name, .. } if name == conn_name))?;
let conn_depth = tree[conn_idx].depth();
let mut i = conn_idx + 1;
while i < tree.len() && tree[i].depth() > conn_depth {
if matches!(&tree[i], TreeNode::Category { schema: s, kind, .. } if s == schema && kind == category)
{
return Some(i);
}
i += 1;
}
None
}
pub(super) fn remove_children_of(&mut self, parent_idx: usize) {
let parent_depth = self.state.sidebar.tree[parent_idx].depth();
let start = parent_idx + 1;
let mut end = start;
while end < self.state.sidebar.tree.len()
&& self.state.sidebar.tree[end].depth() > parent_depth
{
end += 1;
}
if end > start {
self.state.sidebar.tree.drain(start..end);
}
}
}