clicktype-transport 0.2.0

Transport layer for ClickType - HTTP client for ClickHouse
Documentation
//! Integration tests with real ClickHouse instance using the actual client
//! These tests use the real clicktype-transport Client with clickhouse-rs

use clicktype_transport::ClientBuilder;
use clicktype_core::ClickTable;
use clicktype_macros::ClickTable;

/// Test table structures - each test uses a different table to avoid conflicts
#[derive(ClickTable, Debug, Clone, serde::Serialize, serde::Deserialize, clickhouse::Row)]
#[click_table(name = "clicktype_test_table_data", primary_key = "id")]
pub struct TestTableData {
    pub id: u64,
    pub name: String,
    pub value: i32,
}

#[derive(ClickTable, Debug, Clone, serde::Serialize, serde::Deserialize, clickhouse::Row)]
#[click_table(name = "clicktype_test_table_ddl", primary_key = "id")]
pub struct TestTableDdl {
    pub id: u64,
    pub name: String,
    pub value: i32,
}

#[derive(ClickTable, Debug, Clone, serde::Serialize, serde::Deserialize, clickhouse::Row)]
#[click_table(name = "clicktype_test_table_create", primary_key = "id")]
pub struct TestTableCreate {
    pub id: u64,
    pub name: String,
    pub value: i32,
}

#[tokio::test]
async fn test_connection() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to ClickHouse via HTTP using REAL client
    let client = ClientBuilder::default()
        .host("84.247.133.129")
        .port(8123)  // HTTP port
        .user("default")
        .password("tQg6cKagcgF1f0lGfr0wFl0BqGWgrxjyoNw1dhZdjuRRaYofmmk8NPwhWGnmmayh")
        .database("default")
        .build()
        .await?;

    // Test connection with a simple query
    let result = client.query_check("SELECT 1").await?;
    println!("✓ Successfully connected to ClickHouse");
    println!("  Database: {}", client.database());
    println!("  Test query result rows: {}", result);

    assert_eq!(result, 1, "SELECT 1 should return 1 row");

    Ok(())
}

#[tokio::test]
async fn test_execute_simple_query() -> Result<(), Box<dyn std::error::Error>> {
    let client = ClientBuilder::default()
        .host("84.247.133.129")
        .port(8123)
        .user("default")
        .password("tQg6cKagcgF1f0lGfr0wFl0BqGWgrxjyoNw1dhZdjuRRaYofmmk8NPwhWGnmmayh")
        .database("default")
        .build()
        .await?;

    println!("✓ Connected to ClickHouse");

    // Execute a simple query - SELECT 1
    let result = client.query_check("SELECT 1").await?;
    assert_eq!(result, 1, "SELECT 1 should return 1 row");
    println!("✓ SELECT 1 executed successfully (rows: {})", result);

    Ok(())
}

#[tokio::test]
async fn test_create_and_drop_table() -> Result<(), Box<dyn std::error::Error>> {
    let client = ClientBuilder::default()
        .host("84.247.133.129")
        .port(8123)
        .user("default")
        .password("tQg6cKagcgF1f0lGfr0wFl0BqGWgrxjyoNw1dhZdjuRRaYofmmk8NPwhWGnmmayh")
        .database("default")
        .build()
        .await?;

    println!("✓ Connected to ClickHouse");

    // Clean up first
    let _ = client.execute("DROP TABLE IF EXISTS clicktype_test_table_create").await;

    // Generate DDL
    let ddl = TestTableCreate::create_table_ddl();
    println!("✓ Generated DDL:\n{}", ddl);

    // Create table
    client.execute(&ddl).await?;
    println!("✓ Table created successfully");

    // Verify table exists by querying system.tables
    let check_query = format!(
        "SELECT name FROM system.tables WHERE database = '{}' AND name = 'clicktype_test_table_create'",
        client.database()
    );
    let count = client.query_check(&check_query).await?;
    assert_eq!(count, 1, "Table should exist");
    println!("✓ Table verified in system.tables (count: {})", count);

    // Drop table
    client.execute("DROP TABLE IF EXISTS clicktype_test_table_create").await?;
    println!("✓ Table dropped successfully");

    Ok(())
}

#[tokio::test]
async fn test_table_with_data() -> Result<(), Box<dyn std::error::Error>> {
    let client = ClientBuilder::default()
        .host("84.247.133.129")
        .port(8123)
        .user("default")
        .password("tQg6cKagcgF1f0lGfr0wFl0BqGWgrxjyoNw1dhZdjuRRaYofmmk8NPwhWGnmmayh")
        .database("default")
        .build()
        .await?;

    println!("✓ Connected to ClickHouse");

    // Clean up any existing table first
    let _ = client.execute("DROP TABLE IF EXISTS clicktype_test_table_data").await;

    // Create table
    let ddl = TestTableData::create_table_ddl();
    client.execute(&ddl).await?;
    println!("✓ Table created");

    // Insert data using the REAL insert() method
    let test_data = vec![
        TestTableData {
            id: 1,
            name: "test1".to_string(),
            value: 100,
        },
        TestTableData {
            id: 2,
            name: "test2".to_string(),
            value: 200,
        },
        TestTableData {
            id: 3,
            name: "test3".to_string(),
            value: 300,
        },
    ];

    client.insert("clicktype_test_table_data", &test_data).await?;
    println!("✓ Data inserted using real insert()");

    // Query data with query_check
    let count = client.query_check("SELECT * FROM clicktype_test_table_data").await?;
    assert_eq!(count, 3, "Should have 3 rows");
    println!("✓ Data verified (rows: {})", count);

    // Query data with typed query<T>()
    let rows: Vec<TestTableData> = client.query("SELECT * FROM clicktype_test_table_data ORDER BY id").await?;
    assert_eq!(rows.len(), 3, "Should have 3 rows");
    assert_eq!(rows[0].id, 1);
    assert_eq!(rows[0].name, "test1");
    assert_eq!(rows[0].value, 100);
    println!("✓ Typed query<T>() works! Retrieved {} rows", rows.len());

    // Count specific rows
    let count_filtered = client.query_check("SELECT * FROM clicktype_test_table_data WHERE value > 150").await?;
    assert_eq!(count_filtered, 2, "Should have 2 rows with value > 150");
    println!("✓ Filtered query verified (rows: {})", count_filtered);

    // Drop table
    client.execute("DROP TABLE IF EXISTS clicktype_test_table_data").await?;
    println!("✓ Table dropped");

    Ok(())
}

#[tokio::test]
async fn test_ddl_generation_compatibility() -> Result<(), Box<dyn std::error::Error>> {
    let client = ClientBuilder::default()
        .host("84.247.133.129")
        .port(8123)
        .user("default")
        .password("tQg6cKagcgF1f0lGfr0wFl0BqGWgrxjyoNw1dhZdjuRRaYofmmk8NPwhWGnmmayh")
        .database("default")
        .build()
        .await?;

    println!("✓ Connected to ClickHouse");

    // Clean up any existing table first
    let _ = client.execute("DROP TABLE IF EXISTS clicktype_test_table_ddl").await;

    // Test that our generated DDL is compatible with ClickHouse
    let ddl = TestTableDdl::create_table_ddl();
    println!("Generated DDL:\n{}\n", ddl);

    // This should not fail
    client.execute(&ddl).await?;
    println!("✓ DDL executed successfully - ClickHouse accepted our generated DDL");

    // Verify table structure by querying columns from system.columns
    let describe_query = "SELECT name FROM system.columns WHERE database = 'default' AND table = 'clicktype_test_table_ddl'";
    let row_count = client.query_check(describe_query).await?;
    assert!(row_count >= 3, "Should have at least 3 columns");
    println!("✓ Table structure verified ({} columns)", row_count);

    // Clean up
    client.execute("DROP TABLE IF EXISTS clicktype_test_table_ddl").await?;
    println!("✓ Cleanup complete");

    Ok(())
}

#[derive(ClickTable, Debug, Clone, serde::Serialize, serde::Deserialize, clickhouse::Row)]
#[click_table(name = "clicktype_test_schema_ok", primary_key = "id")]
pub struct TestSchemaValidation {
    pub id: u64,
    pub name: String,
    pub value: i32,
}

#[derive(ClickTable, Debug, Clone, serde::Serialize, serde::Deserialize, clickhouse::Row)]
#[click_table(name = "clicktype_test_schema_mismatch", primary_key = "id")]
pub struct TestSchemaMismatch {
    pub id: u64,
    pub name: String,
    pub value: i32,
}

#[derive(ClickTable, Debug, Clone, serde::Serialize, serde::Deserialize, clickhouse::Row)]
#[click_table(name = "clicktype_test_schema_order", primary_key = "id")]
pub struct TestSchemaOrder {
    pub id: u64,
    pub name: String,
    pub value: i32,
}

#[tokio::test]
async fn test_schema_validation_success() -> Result<(), Box<dyn std::error::Error>> {
    let client = ClientBuilder::default()
        .host("84.247.133.129")
        .port(8123)
        .user("default")
        .password("tQg6cKagcgF1f0lGfr0wFl0BqGWgrxjyoNw1dhZdjuRRaYofmmk8NPwhWGnmmayh")
        .database("default")
        .build()
        .await?;

    println!("✓ Connected to ClickHouse");

    // Clean up first
    let _ = client.execute("DROP TABLE IF EXISTS clicktype_test_schema_ok").await;

    // Create table with matching schema
    let ddl = TestSchemaValidation::create_table_ddl();
    client.execute(&ddl).await?;
    println!("✓ Table created");

    // Validate schema - should succeed
    let result = client.validate_schema::<TestSchemaValidation>("clicktype_test_schema_ok").await;
    assert!(result.is_ok(), "Schema validation should succeed for matching schema");
    println!("✓ Schema validation passed");

    // Clean up
    client.execute("DROP TABLE IF EXISTS clicktype_test_schema_ok").await?;

    Ok(())
}

#[tokio::test]
async fn test_schema_validation_mismatch() -> Result<(), Box<dyn std::error::Error>> {
    let client = ClientBuilder::default()
        .host("84.247.133.129")
        .port(8123)
        .user("default")
        .password("tQg6cKagcgF1f0lGfr0wFl0BqGWgrxjyoNw1dhZdjuRRaYofmmk8NPwhWGnmmayh")
        .database("default")
        .build()
        .await?;

    println!("✓ Connected to ClickHouse");

    // Clean up first
    let _ = client.execute("DROP TABLE IF EXISTS clicktype_test_schema_mismatch").await;

    // Create table with DIFFERENT schema (value is String instead of i32)
    let wrong_ddl = r#"
        CREATE TABLE clicktype_test_schema_mismatch (
            id UInt64,
            name String,
            value String
        ) ENGINE = MergeTree()
        PRIMARY KEY (id)
        ORDER BY (id)
    "#;
    client.execute(wrong_ddl).await?;
    println!("✓ Table created with mismatched schema");

    // Validate schema - should fail
    let result = client.validate_schema::<TestSchemaMismatch>("clicktype_test_schema_mismatch").await;
    assert!(result.is_err(), "Schema validation should fail for mismatched schema");

    if let Err(e) = result {
        println!("✓ Schema validation correctly failed: {}", e);
        assert!(e.to_string().contains("type mismatch"), "Error should mention type mismatch");
    }

    // Clean up
    client.execute("DROP TABLE IF EXISTS clicktype_test_schema_mismatch").await?;

    Ok(())
}

#[tokio::test]
async fn test_schema_validation_order_mismatch() -> Result<(), Box<dyn std::error::Error>> {
    let client = ClientBuilder::default()
        .host("84.247.133.129")
        .port(8123)
        .user("default")
        .password("tQg6cKagcgF1f0lGfr0wFl0BqGWgrxjyoNw1dhZdjuRRaYofmmk8NPwhWGnmmayh")
        .database("default")
        .build()
        .await?;

    println!("✓ Connected to ClickHouse");

    // Clean up first
    let _ = client.execute("DROP TABLE IF EXISTS clicktype_test_schema_order").await;

    // Create table with DIFFERENT COLUMN ORDER (critical for RowBinary!)
    let wrong_order_ddl = r#"
        CREATE TABLE clicktype_test_schema_order (
            name String,
            value Int32,
            id UInt64
        ) ENGINE = MergeTree()
        ORDER BY (id)
    "#;
    client.execute(wrong_order_ddl).await?;
    println!("✓ Table created with different column order");

    // Validate schema - should fail due to order mismatch
    let result = client.validate_schema::<TestSchemaOrder>("clicktype_test_schema_order").await;
    assert!(result.is_err(), "Schema validation should fail for column order mismatch");

    if let Err(e) = result {
        println!("✓ Schema validation correctly failed: {}", e);
        let err_msg = e.to_string();
        assert!(err_msg.contains("order mismatch") || err_msg.contains("position"),
                "Error should mention column order/position issue, got: {}", err_msg);
    }

    // Clean up
    client.execute("DROP TABLE IF EXISTS clicktype_test_schema_order").await?;

    Ok(())
}