Skip to content
use clickhouse::Row;
use serde::{Deserialize, Serialize};

use crate::connections::Connection;

use super::table::Ch;

#[derive(Serialize, Deserialize, Row)]
pub struct Database {
    pub name: String,
}

pub async fn all_databases(ch: Ch) -> Vec<Database> {
    let Ok(mut cursor) = ch
        .query("SELECT name FROM system.databases")
        .fetch::<Database>()
    else {
        log::error!("Failed to fetch databases");
        return Vec::new();
    };
    let mut databases = Vec::new();
    while let Ok(Some(row)) = cursor.next().await {
        databases.push(row);
    }
    databases
}

#[derive(Serialize, Deserialize, Row)]
pub struct Table {
    pub name: String,
}

/// Metadata about a single column as returned by `system.columns`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnInfo {
    pub name: String,
    /// The raw ClickHouse type string, e.g. `"UInt64"`, `"String"`, `"DateTime64(3)"`.
    pub column_type: String,
}

/// Return the column names and types for `database.table` in definition order.
pub async fn describe_table(conn: &Connection, database: &str, table: &str) -> Vec<ColumnInfo> {
    let query = format!(
        "SELECT name, type FROM system.columns \
         WHERE database = '{}' AND table = '{}' \
         ORDER BY position \
         FORMAT TSVWithNames",
        database.replace('\'', "''"),
        table.replace('\'', "''")
    );

    let client = reqwest::Client::new();
    let mut request = client
        .post(conn.url())
        .query(&[("user", conn.user.as_str())])
        .body(query);
    if !conn.password.is_empty() {
        request = request.query(&[("password", conn.password.as_str())]);
    }

    let Ok(response) = request.send().await else {
        log::error!("describe_table: request failed for {database}.{table}");
        return Vec::new();
    };
    if !response.status().is_success() {
        log::error!("describe_table: bad status for {database}.{table}");
        return Vec::new();
    }
    let Ok(text) = response.text().await else {
        return Vec::new();
    };

    let mut lines = text.lines();
    let _ = lines.next(); // skip TSV header row

    lines
        .filter_map(|line| {
            let mut parts = line.splitn(2, '\t');
            let name = parts.next()?.to_string();
            let column_type = parts.next()?.to_string();
            Some(ColumnInfo { name, column_type })
        })
        .collect()
}

pub async fn all_tables(ch: Ch, database: &str) -> Vec<Table> {
    let Ok(mut cursor) = ch
        .query("SELECT name FROM system.tables WHERE database = ?")
        .bind(database)
        .fetch::<Table>()
    else {
        log::error!("Failed to fetch tables for database: {}", database);
        return Vec::new();
    };
    let mut tables = Vec::new();
    while let Ok(Some(row)) = cursor.next().await {
        tables.push(row);
    }
    tables
}